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 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