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 ADDED
@@ -0,0 +1,2 @@
1
+ language: objective-c
2
+ before_install: rvm use 1.9.3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geomotion (0.9.0)
4
+ geomotion (0.11.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
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