geomotion 0.10.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +104 -0
- data/app/app_delegate.rb +5 -0
- data/app/next_controller.rb +19 -0
- data/app/perspective_controller.rb +76 -0
- data/app/shearing_controller.rb +77 -0
- data/lib/geomotion/ca_transform_3d.rb +223 -0
- data/lib/geomotion/cg_affine_transform.rb +181 -0
- data/lib/geomotion/cg_rect.rb +4 -4
- data/lib/geomotion/version.rb +1 -1
- data/resources/perspective.png +0 -0
- data/resources/shearing.png +0 -0
- data/spec/ca_transform_3d_spec.rb +345 -0
- data/spec/cg_affine_transform_spec.rb +247 -0
- data/spec/cg_rect_spec.rb +8 -1
- metadata +14 -3
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -257,6 +257,110 @@ point.angle_to(CGPoint.make(x: 20, y:110))
|
|
257
257
|
=> 0.785398163397 (pi/4)
|
258
258
|
```
|
259
259
|
|
260
|
+
### CGAffineTransform
|
261
|
+
|
262
|
+
These are assigned to the `UIView#transform` parameter. See `CATransform3D` for
|
263
|
+
the transforms that are designed for `CALayer` object.
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
# you *can* create it manually
|
267
|
+
transform = CGAffineTransform.make(a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0)
|
268
|
+
|
269
|
+
# but don't! the `make` method accepts `translate`, `scale`, and `rotate` args
|
270
|
+
transform = CGAffineTransform.make(scale: 2, translate: [10, 10], rotate: Math::PI)
|
271
|
+
|
272
|
+
# identity transform is easy
|
273
|
+
CGAffineTransform.identity
|
274
|
+
|
275
|
+
# just to be sure
|
276
|
+
CGAffineTransform.identity.identity? # => true
|
277
|
+
|
278
|
+
# Operator Overloading
|
279
|
+
transform1 = CGAffineTransform.make(scale: 2)
|
280
|
+
transform2 = CGAffineTransform.make(translate: [10, 10])
|
281
|
+
# concatenate transforms
|
282
|
+
transform1 + transform2
|
283
|
+
transform1 << transform2 # alias
|
284
|
+
transform1 - transform2
|
285
|
+
# => transform1 + -transform2
|
286
|
+
# => transform1 + transform2.invert
|
287
|
+
transform1 - transform1 # => CGAffineTransform.identity
|
288
|
+
|
289
|
+
# create new transforms by calling `translate`, `scale`, or `rotate` as factory
|
290
|
+
# methods
|
291
|
+
CGAffineTransform.translate(10, 10)
|
292
|
+
CGAffineTransform.scale(2) # scale x and y by 2
|
293
|
+
CGAffineTransform.scale(2, 4) # scale x by 2 and y by 4
|
294
|
+
CGAffineTransform.rotate(Math::PI / 4)
|
295
|
+
|
296
|
+
# "shearing" turns a rectangle into a parallelogram
|
297
|
+
# see sceenshot below or run geomotion app
|
298
|
+
CGAffineTransform.shear(0.5, 0) # in x direction
|
299
|
+
CGAffineTransform.shear(0, 0.5) # in y direction
|
300
|
+
# you can combine these, but it looks kind of strange. better to pick one
|
301
|
+
# direction
|
302
|
+
|
303
|
+
# or you can chain these methods
|
304
|
+
CGAffineTransform.identity.translate(10, 10).scale(2).rotate(Math::PI / 4)
|
305
|
+
```
|
306
|
+
|
307
|
+
###### Shearing
|
308
|
+
|
309
|
+

|
310
|
+
|
311
|
+
### CATransform3D
|
312
|
+
|
313
|
+
`CALayer`s can take on full 3D transforms.
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
# these are really gnarly
|
317
|
+
transform = CATransform3D.make(
|
318
|
+
m11: 1, m12: 0, m13: 0, m14: 0,
|
319
|
+
m21: 0, m22: 1, m23: 0, m24: 0,
|
320
|
+
m31: 0, m32: 0, m33: 1, m34: 0,
|
321
|
+
m41: 0, m42: 0, m43: 0, m44: 1,)
|
322
|
+
|
323
|
+
# accepts transforms like CGAffineTransform, but many take 3 instead of 2 args
|
324
|
+
transform = CATransform3D.make(scale: [2, 2, 1], translate: [10, 10, 10], rotate: Math::PI)
|
325
|
+
|
326
|
+
# identity transform
|
327
|
+
CATransform3D.identity
|
328
|
+
CATransform3D.identity.identity? # => true
|
329
|
+
|
330
|
+
# Operator Overloading
|
331
|
+
transform1 = CATransform3D.make(scale: 2)
|
332
|
+
transform2 = CATransform3D.make(translate: [10, 10])
|
333
|
+
# concatenate transforms
|
334
|
+
transform1 + transform2
|
335
|
+
transform1 << transform2 # alias
|
336
|
+
transform1 - transform2
|
337
|
+
# => transform1 + -transform2
|
338
|
+
# => transform1 + transform2.invert
|
339
|
+
transform1 - transform1 # => CATransform3D.identity
|
340
|
+
|
341
|
+
# create new transforms by calling factory methods
|
342
|
+
CATransform3D.translate(10, 10, 10)
|
343
|
+
CATransform3D.scale(2) # scale x and y by 2
|
344
|
+
CATransform3D.scale(2, 4, 3) # scale x by 2, y by 4, z by 3
|
345
|
+
CATransform3D.rotate(Math::PI / 4)
|
346
|
+
|
347
|
+
# "shearing" works the same as CGAffineTransform
|
348
|
+
CATransform3D.shear(0.5, 0) # in x direction
|
349
|
+
CATransform3D.shear(0, 0.5) # in y direction
|
350
|
+
# "perspective" changes are better than rotation because they make one side
|
351
|
+
# bigger and one side smaller
|
352
|
+
# see sceenshot below or run geomotion app
|
353
|
+
CATransform3D.perspective(0.002, 0) # similar to rotating around x-axis
|
354
|
+
CATransform3D.perspective(0, 0.002) # "rotates" around the y-axis
|
355
|
+
|
356
|
+
# or you can chain these methods
|
357
|
+
CATransform3D.identity.translate(10, 10, 10).scale(2).rotate(Math::PI / 4)
|
358
|
+
```
|
359
|
+
|
360
|
+
###### Perspective
|
361
|
+
|
362
|
+

|
363
|
+
|
260
364
|
## Install
|
261
365
|
|
262
366
|
1. `gem install geomotion`
|
data/app/app_delegate.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
class AppDelegate
|
2
2
|
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
4
|
+
ctlr = ShearingController.new
|
5
|
+
first = UINavigationController.alloc.initWithRootViewController(ctlr)
|
6
|
+
@window.rootViewController = first
|
7
|
+
@window.makeKeyAndVisible
|
3
8
|
true
|
4
9
|
end
|
5
10
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ShowNextController < UIViewController
|
2
|
+
|
3
|
+
def init
|
4
|
+
super.tap do
|
5
|
+
if next_controller
|
6
|
+
navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithTitle('Next', style:UIBarButtonItemStylePlain, target:self, action: :show_next)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def show_next
|
12
|
+
navigationController.pushViewController(next_controller, animated:true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_controller
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class PerspectiveController < UIViewController
|
2
|
+
|
3
|
+
def viewDidLoad
|
4
|
+
self.title = 'Perspective'
|
5
|
+
|
6
|
+
@restore = {
|
7
|
+
red: false, green: false, blue: false,
|
8
|
+
}
|
9
|
+
red = UIButton.buttonWithType(UIButtonTypeCustom)
|
10
|
+
red.setTitle('0.004, 0', forState: UIControlStateNormal)
|
11
|
+
red.backgroundColor = UIColor.redColor
|
12
|
+
red.frame = [[80, 16], [160, 100]]
|
13
|
+
red.addTarget(self, action: :transform_red, forControlEvents:UIControlEventTouchUpInside)
|
14
|
+
|
15
|
+
green = UIButton.buttonWithType(UIButtonTypeCustom)
|
16
|
+
green.setTitle('0, 0.004', forState: UIControlStateNormal)
|
17
|
+
green.backgroundColor = UIColor.greenColor
|
18
|
+
green.frame = red.frame.below(30)
|
19
|
+
green.addTarget(self, action: :transform_green, forControlEvents:UIControlEventTouchUpInside)
|
20
|
+
|
21
|
+
blue = UIButton.buttonWithType(UIButtonTypeCustom)
|
22
|
+
blue.setTitle("0.004, 0.004", forState: UIControlStateNormal)
|
23
|
+
blue.backgroundColor = UIColor.blueColor
|
24
|
+
blue.frame = green.frame.below(30)
|
25
|
+
blue.addTarget(self, action: :transform_blue, forControlEvents:UIControlEventTouchUpInside)
|
26
|
+
|
27
|
+
self.view.addSubview(@red = red)
|
28
|
+
self.view.addSubview(@green = green)
|
29
|
+
self.view.addSubview(@blue = blue)
|
30
|
+
end
|
31
|
+
|
32
|
+
def animate(&block)
|
33
|
+
UIView.animateWithDuration(0.3,
|
34
|
+
delay:0,
|
35
|
+
options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState,
|
36
|
+
animations:block,
|
37
|
+
completion:nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
def transform_red
|
41
|
+
animate do
|
42
|
+
if @restore[:red]
|
43
|
+
@red.layer.transform = CATransform3D.identity
|
44
|
+
else
|
45
|
+
@red.layer.transform = CATransform3D.perspective(0.004, 0)
|
46
|
+
end
|
47
|
+
@restore[:red] = ! @restore[:red]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def transform_green
|
52
|
+
animate do
|
53
|
+
if @restore[:green]
|
54
|
+
@green.layer.transform = CATransform3D.identity
|
55
|
+
else
|
56
|
+
@green.layer.transform = CATransform3D.perspective(0, 0.004)
|
57
|
+
end
|
58
|
+
@restore[:green] = ! @restore[:green]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def transform_blue
|
63
|
+
animate do
|
64
|
+
if @restore[:blue]
|
65
|
+
@blue.layer.transform = CATransform3D.identity
|
66
|
+
else
|
67
|
+
@blue.layer.transform = CATransform3D.perspective(0.004, 0.004)
|
68
|
+
end
|
69
|
+
@restore[:blue] = ! @restore[:blue]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def next_controller
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class ShearingController < ShowNextController
|
2
|
+
|
3
|
+
def viewDidLoad
|
4
|
+
self.title = 'Shearing'
|
5
|
+
|
6
|
+
@restore = {
|
7
|
+
red: false, green: false, blue: false,
|
8
|
+
}
|
9
|
+
red = UIButton.buttonWithType(UIButtonTypeCustom)
|
10
|
+
red.setTitle('0.5, 0', forState: UIControlStateNormal)
|
11
|
+
red.backgroundColor = UIColor.redColor
|
12
|
+
red.frame = [[80, 16], [160, 100]]
|
13
|
+
red.addTarget(self, action: :transform_red, forControlEvents:UIControlEventTouchUpInside)
|
14
|
+
|
15
|
+
green = UIButton.buttonWithType(UIButtonTypeCustom)
|
16
|
+
green.setTitle('0, 0.5', forState: UIControlStateNormal)
|
17
|
+
green.backgroundColor = UIColor.greenColor
|
18
|
+
green.frame = red.frame.below(30)
|
19
|
+
green.addTarget(self, action: :transform_green, forControlEvents:UIControlEventTouchUpInside)
|
20
|
+
|
21
|
+
blue = UIButton.buttonWithType(UIButtonTypeCustom)
|
22
|
+
blue.setTitle('0.5, 0.5', forState: UIControlStateNormal)
|
23
|
+
blue.backgroundColor = UIColor.blueColor
|
24
|
+
blue.frame = green.frame.below(30)
|
25
|
+
blue.addTarget(self, action: :transform_blue, forControlEvents:UIControlEventTouchUpInside)
|
26
|
+
|
27
|
+
self.view.addSubview(@red = red)
|
28
|
+
self.view.addSubview(@green = green)
|
29
|
+
self.view.addSubview(@blue = blue)
|
30
|
+
end
|
31
|
+
|
32
|
+
def animate(&block)
|
33
|
+
UIView.animateWithDuration(0.3,
|
34
|
+
delay:0,
|
35
|
+
options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState,
|
36
|
+
animations:block,
|
37
|
+
completion:nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
def transform_red
|
41
|
+
animate do
|
42
|
+
if @restore[:red]
|
43
|
+
@red.transform = CGAffineTransform.identity
|
44
|
+
else
|
45
|
+
@red.transform = CGAffineTransform.shear(0.5, 0)
|
46
|
+
end
|
47
|
+
@restore[:red] = ! @restore[:red]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def transform_green
|
52
|
+
animate do
|
53
|
+
if @restore[:green]
|
54
|
+
@green.transform = CGAffineTransform.identity
|
55
|
+
else
|
56
|
+
@green.transform = CGAffineTransform.shear(0, 0.5)
|
57
|
+
end
|
58
|
+
@restore[:green] = ! @restore[:green]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def transform_blue
|
63
|
+
animate do
|
64
|
+
if @restore[:blue]
|
65
|
+
@blue.transform = CGAffineTransform.identity
|
66
|
+
else
|
67
|
+
@blue.transform = CGAffineTransform.shear(0.5, 0.5)
|
68
|
+
end
|
69
|
+
@restore[:blue] = ! @restore[:blue]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def next_controller
|
74
|
+
PerspectiveController.new
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
class CATransform3D
|
2
|
+
# CATransform3D.make # default transform: identity matrix
|
3
|
+
# # make a transform from scratch. please don't ever do this ;-)
|
4
|
+
# CATransform3D.make(
|
5
|
+
# m11: 1, m12: 0, m13: 0, m14: 0,
|
6
|
+
# m21: 0, m22: 1, m23: 0, m24: 0,
|
7
|
+
# m31: 0, m32: 0, m33: 1, m34: 0,
|
8
|
+
# m41: 0, m42: 0, m43: 0, m44: 1,
|
9
|
+
# )
|
10
|
+
#
|
11
|
+
# # make a transform using primitives
|
12
|
+
# CATransform3D.make(scale: 2, translate: [10, 10, 0], rotate: Math::PI)
|
13
|
+
def self.make(options = {})
|
14
|
+
if options[:m11]
|
15
|
+
args = [
|
16
|
+
:m11, :m12, :m13, :m14,
|
17
|
+
:m21, :m22, :m23, :m24,
|
18
|
+
:m31, :m32, :m33, :m34,
|
19
|
+
:m41, :m42, :m43, :m44,
|
20
|
+
].map do |key|
|
21
|
+
raise "#{key.inspect} is required" unless options.key? key
|
22
|
+
options[key]
|
23
|
+
end
|
24
|
+
return self.new(*args)
|
25
|
+
else
|
26
|
+
retval = self.identity
|
27
|
+
if options[:translate]
|
28
|
+
retval = retval.translate(options[:translate])
|
29
|
+
end
|
30
|
+
if options[:scale]
|
31
|
+
retval = retval.scale(options[:scale])
|
32
|
+
end
|
33
|
+
if options[:rotate]
|
34
|
+
retval = retval.rotate(options[:rotate])
|
35
|
+
end
|
36
|
+
return retval
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a transform that is translated. Accepts one or three arguments. One
|
41
|
+
# argument can be an Array with three numbers, three arguments should be the
|
42
|
+
# x, y, z values.
|
43
|
+
# @return [CATransform3D]
|
44
|
+
def self.translate(point, ty=nil, tz=nil)
|
45
|
+
if ty
|
46
|
+
tx = point
|
47
|
+
else
|
48
|
+
tx = point[0]
|
49
|
+
ty = point[1]
|
50
|
+
tz = point[2]
|
51
|
+
end
|
52
|
+
CATransform3DMakeTranslation(tx, ty, tz)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a transform that is scaled. Accepts one or three arguments. One
|
56
|
+
# argument can be an Array with three items or a number that will be used to
|
57
|
+
# scale both x and y directions (z will be scale 1). Three arguments should be
|
58
|
+
# the x, y, z values.
|
59
|
+
def self.scale(scale, sy=nil, sz=nil)
|
60
|
+
if sy
|
61
|
+
sx = scale
|
62
|
+
elsif scale.is_a?(Numeric)
|
63
|
+
sx = sy = scale
|
64
|
+
sz = 1
|
65
|
+
else
|
66
|
+
sx = scale[0]
|
67
|
+
sy = scale[1]
|
68
|
+
sz = scale[2]
|
69
|
+
end
|
70
|
+
CATransform3DMakeScale(sx, sy, sz)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a transform that is rotated by `angle` (+ => counterclockwise, - => clockwise)
|
74
|
+
# @return [CATransform3D]
|
75
|
+
def self.rotate(angle, point=nil, y=nil, z=nil)
|
76
|
+
if point && y && z
|
77
|
+
x = point
|
78
|
+
elsif point
|
79
|
+
x = point[0]
|
80
|
+
y = point[1]
|
81
|
+
z = point[2]
|
82
|
+
else
|
83
|
+
# default: spins around z-axis
|
84
|
+
x = 0
|
85
|
+
y = 0
|
86
|
+
z = 1
|
87
|
+
end
|
88
|
+
CATransform3DMakeRotation(angle, x, y, z)
|
89
|
+
end
|
90
|
+
|
91
|
+
# A "shear" translation turns a rectangle into a parallelogram. Delegates to
|
92
|
+
# the CGAffineTransform with the same name
|
93
|
+
# @param px [Numeric] how much to shear in x direction
|
94
|
+
# @param py [Numeric] how much to shear in x direction
|
95
|
+
# @return CATransform3D
|
96
|
+
def self.shear(px, py)
|
97
|
+
CGAffineTransform.shear(px, py).to_transform3d
|
98
|
+
end
|
99
|
+
|
100
|
+
# A perspective transform is a lot like a rotation... but different. These
|
101
|
+
# numbers should be very small, like 0.002 is a decent perspective shift.
|
102
|
+
# @return CATransform3D
|
103
|
+
def self.perspective(x, y)
|
104
|
+
CATransform3D.new(1,0,0,x, 0,1,0,y, 0,0,1,0, 0,0,0,1)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the CATransform3D identity matrix
|
108
|
+
# @return [CATransform3D]
|
109
|
+
def self.identity
|
110
|
+
CATransform3DMakeTranslation(0, 0, 0)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return true if the receiver is the identity matrix, false otherwise
|
114
|
+
# @return [Boolean]
|
115
|
+
def identity?
|
116
|
+
CATransform3DIsIdentity(self)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return true if the receiver can be represented as an affine transform
|
120
|
+
# @return [Boolean]
|
121
|
+
def affine?
|
122
|
+
CATransform3DIsAffine(self)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [CGAffineTransform]
|
126
|
+
def to_affine_transform
|
127
|
+
CATransform3DGetAffineTransform(self)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @return [Boolean] true if the two matrices are equal
|
131
|
+
def ==(transform)
|
132
|
+
CATransform3DEqualToTransform(self, transform)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns self
|
136
|
+
# @return [CATransform3D]
|
137
|
+
def +@
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# Concatenates the two transforms
|
142
|
+
# @return [CATransform3D]
|
143
|
+
def concat(transform)
|
144
|
+
CATransform3DConcat(self, transform)
|
145
|
+
end
|
146
|
+
alias :+ :concat
|
147
|
+
alias :<< :concat
|
148
|
+
|
149
|
+
# Inverts the transform
|
150
|
+
# @return [CATransform3D]
|
151
|
+
def invert
|
152
|
+
CATransform3DInvert(self)
|
153
|
+
end
|
154
|
+
alias :-@ :invert
|
155
|
+
|
156
|
+
# Inverts the second transform and adds the result to `self`
|
157
|
+
# @return [CATransform3D]
|
158
|
+
def -(transform)
|
159
|
+
self.concat transform.invert
|
160
|
+
end
|
161
|
+
|
162
|
+
# Applies a translation transform to the receiver
|
163
|
+
# @return [CATransform3D]
|
164
|
+
def translate(point, ty=nil, tz=nil)
|
165
|
+
if ty
|
166
|
+
tx = point
|
167
|
+
else
|
168
|
+
tx = point[0]
|
169
|
+
ty = point[1]
|
170
|
+
tz = point[2]
|
171
|
+
end
|
172
|
+
CATransform3DTranslate(self, tx, ty, tz)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Applies a scale transform to the receiver
|
176
|
+
# @return [CATransform3D]
|
177
|
+
def scale(scale, sy=nil, sz=nil)
|
178
|
+
if sy
|
179
|
+
sx = scale
|
180
|
+
elsif scale.is_a?(Numeric)
|
181
|
+
sx = sy = scale
|
182
|
+
sz = 1
|
183
|
+
else
|
184
|
+
sx = scale[0]
|
185
|
+
sy = scale[1]
|
186
|
+
sz = scale[2]
|
187
|
+
end
|
188
|
+
CATransform3DScale(self, sx, sy, sz)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Applies a rotation transform to the receiver
|
192
|
+
# @return [CATransform3D]
|
193
|
+
def rotate(angle, point=nil, y=nil, z=nil)
|
194
|
+
if point && y && z
|
195
|
+
x = point
|
196
|
+
elsif point
|
197
|
+
x = point[0]
|
198
|
+
y = point[1]
|
199
|
+
z = point[2]
|
200
|
+
else
|
201
|
+
# default: spins around z-axis
|
202
|
+
x = 0
|
203
|
+
y = 0
|
204
|
+
z = 1
|
205
|
+
end
|
206
|
+
CATransform3DRotate(self, angle, x, y, z)
|
207
|
+
end
|
208
|
+
|
209
|
+
def shear(px, py)
|
210
|
+
self.concat CGAffineTransform.shear(px, py).to_transform3d
|
211
|
+
end
|
212
|
+
|
213
|
+
# Applies a perspective transform to the receiver
|
214
|
+
# @return CATransform3D
|
215
|
+
def perspective(x, y)
|
216
|
+
self.concat CATransform3D.perspective(x, y)
|
217
|
+
end
|
218
|
+
|
219
|
+
def to_a
|
220
|
+
[self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24, self.m31, self.m32, self.m33, self.m34, self.m41, self.m42, self.m43, self.m44]
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|