geomotion 0.10.0 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Shearing](https://raw.github.com/colinta/geomotion/master/resources/shearing.png)
|
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
|
+
![Perspective](https://raw.github.com/colinta/geomotion/master/resources/perspective.png)
|
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
|