bulldog_physics 0.1.0

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.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +22 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +25 -0
  6. data/Rakefile +55 -0
  7. data/VERSION +1 -0
  8. data/bulldog_physics.gemspec +92 -0
  9. data/lib/Particles/particle.rb +75 -0
  10. data/lib/Particles/particle_anchored_spring.rb +42 -0
  11. data/lib/Particles/particle_cable.rb +52 -0
  12. data/lib/Particles/particle_contact.rb +121 -0
  13. data/lib/Particles/particle_contact_generator.rb +28 -0
  14. data/lib/Particles/particle_contact_resolver.rb +49 -0
  15. data/lib/Particles/particle_drag.rb +30 -0
  16. data/lib/Particles/particle_force_generator.rb +23 -0
  17. data/lib/Particles/particle_force_registration.rb +15 -0
  18. data/lib/Particles/particle_force_registry.rb +50 -0
  19. data/lib/Particles/particle_gravity.rb +31 -0
  20. data/lib/Particles/particle_ground_contacts.rb +43 -0
  21. data/lib/Particles/particle_link.rb +41 -0
  22. data/lib/Particles/particle_particle_contacts.rb +48 -0
  23. data/lib/Particles/particle_rod.rb +58 -0
  24. data/lib/Particles/particle_spring.rb +44 -0
  25. data/lib/Particles/particle_world.rb +116 -0
  26. data/lib/Particles/projectile.rb +15 -0
  27. data/lib/bulldog_physics.rb +43 -0
  28. data/lib/examples/GlStuff/gl_utility.rb +21 -0
  29. data/lib/examples/GlStuff/lighting.rb +35 -0
  30. data/lib/examples/GlStuff/material.rb +24 -0
  31. data/lib/examples/GlStuff/terrain.rb +216 -0
  32. data/lib/examples/simple_game.rb +289 -0
  33. data/lib/matrix3.rb +242 -0
  34. data/lib/matrix4.rb +230 -0
  35. data/lib/quaternion.rb +86 -0
  36. data/lib/vector3.rb +155 -0
  37. data/test/helper.rb +18 -0
  38. data/test/test_bulldog_physics.rb +7 -0
  39. metadata +190 -0
@@ -0,0 +1,289 @@
1
+ require 'rubygems'
2
+ require 'rubygame'
3
+ begin
4
+ require 'opengl'
5
+ include Gl,Glu,Glut
6
+ rescue LoadError
7
+ puts "ATTENTION: This demo requires the opengl extension for ruby."
8
+ raise
9
+ end
10
+
11
+ require 'bulldog_physics'
12
+
13
+ require_all 'GlStuff'
14
+
15
+ include BulldogPhysics
16
+ include BulldogPhysics::Particles
17
+ include BulldogPhysics::Particles::Generators
18
+ include BulldogPhysics::Particles::Collisions
19
+ include Rubygame
20
+ include GlStuff
21
+
22
+ ROD_SPECULAR = [1.0, 0.0, 0.0, 0.5]
23
+ BALL_SPECULAR = [1.0, 0.0, 1.0, 0.5]
24
+ SPRING_SPECULAR = [0.0, 1.0, 0.0, 0.5]
25
+ ANCHORED_SPECULAR = [0.0, 0.0, 1.0, 0.5]
26
+ ANCHOR_SPECULAR = [ 0.0, 0.5, 1.0, 0.5]
27
+ PARTICLE_DIFFUSE = [1.0, 0.0, 0.0, 0.5]
28
+ SCALE = 500.0
29
+ BULLET_LIFE = 20000.0
30
+ class Game
31
+
32
+
33
+ GRAVITATIONAL_CONST = 6.673 * (10 ** -4)
34
+
35
+ START_POSITION = Vector3.new(0.75,15,0)
36
+
37
+ SMOOTH = true
38
+ def initialize(width,height)
39
+
40
+ @width = width
41
+ @height = height
42
+ @queue = EventQueue.new
43
+ @queue.enable_new_style_events
44
+ @clock = Clock.new
45
+ @lighting = Lighting.new
46
+ @x_trans = 0; @y_trans = -5; @z_trans = -25
47
+ @font = TTF.new "/Library/Fonts/Times New Roman.ttf",point_size
48
+
49
+ @world = ParticleWorld.new(20, 1)
50
+
51
+
52
+
53
+ @anchor = Particle.new
54
+ @anchor.mass = 1
55
+ @anchor.position = Vector3.new(-5,0,0)
56
+ @anchor.damping = 1
57
+ @anchor.velocity = Vector3.new(0,0.0,0)
58
+ @anchor.material.specular = ANCHOR_SPECULAR
59
+ @anchor.material.diffuse = PARTICLE_DIFFUSE
60
+
61
+ #@world.add_gravity()
62
+
63
+
64
+ @ball = Particle.new
65
+ @ball.mass = 1
66
+ @ball.position = START_POSITION.dup
67
+ @ball.damping = 1
68
+ @ball.material.specular = BALL_SPECULAR
69
+ @ball.material.diffuse = PARTICLE_DIFFUSE
70
+ @ball.frozen = true
71
+
72
+
73
+ @current_ball = Particle.new
74
+ @current_ball.position = @ball.position
75
+ @current_ball.velocity = @ball.velocity
76
+ @current_ball.mass = @ball.mass
77
+ @current_ball.damping = @ball.damping
78
+ @current_ball.frozen = true
79
+ @world.registry.add( @current_ball, ParticleGravity.new())
80
+ @world.particles << @current_ball
81
+
82
+
83
+ particle1 = Particle.new
84
+ particle1.mass = 1
85
+ particle1.position = Vector3.new(5,2,0)
86
+ particle1.damping = 1
87
+ particle1.velocity = Vector3.new(0,2.0,0)
88
+ particle1.material.specular = ANCHOR_SPECULAR
89
+ particle1.material.diffuse = PARTICLE_DIFFUSE
90
+
91
+ particle2 = Particle.new
92
+ particle2.mass = 1
93
+ particle2.position = Vector3.new(0,2,0)
94
+ particle2.damping = 1
95
+ particle2.velocity = Vector3.new(0,0.0,0)
96
+ particle2.material.specular = ANCHOR_SPECULAR
97
+ particle2.material.diffuse = PARTICLE_DIFFUSE
98
+
99
+ prod = ParticleRod.new(particle1, particle2, 2.0)
100
+
101
+ #@world.particles << particle1
102
+ #@world.particles << particle2
103
+ #@world.contact_generators << prod
104
+
105
+ @world.contact_generators << ParticleGroundContacts.new(@world.particles)
106
+ @world.contact_generators << ParticleParticleContacts.new(@world.particles)
107
+
108
+ for i in 1..5
109
+ for j in 1..5
110
+ part = Particle.new
111
+ part.mass = 0
112
+ offset = j % 2 == 0 ? 1 : 0
113
+ part.position = Vector3.new( -5 + i * 2 + offset, j * 2, 0)
114
+ part.material.specular = SPRING_SPECULAR
115
+ part.material.diffuse = PARTICLE_DIFFUSE
116
+ @world.particles << part
117
+ end
118
+ end
119
+
120
+
121
+ puts "PARTICLE COUNT [ #{@world.particles.size} ] "
122
+ puts "Contact Generators count: #{@world.contact_generators.size}"
123
+
124
+
125
+ @w_down, @a_down, @s_down, @d_down = false, false, false, false
126
+
127
+ end
128
+
129
+ def init_game()
130
+ Rubygame.init
131
+
132
+ Rubygame::GL.set_attrib(Rubygame::GL::RED_SIZE, 5)
133
+ Rubygame::GL.set_attrib(Rubygame::GL::GREEN_SIZE, 5)
134
+ Rubygame::GL.set_attrib(Rubygame::GL::BLUE_SIZE, 5)
135
+ Rubygame::GL.set_attrib(Rubygame::GL::DEPTH_SIZE, 16)
136
+ Rubygame::GL.set_attrib(Rubygame::GL::DOUBLEBUFFER, 1)
137
+
138
+ @maximum_resolution = Screen.get_resolution
139
+ @screen = Screen.new([@width,@height], 16, [OPENGL])
140
+ @clock.target_framerate = 30
141
+ @clock.calibrate
142
+ @clock.enable_tick_events
143
+ ObjectSpace.garbage_collect
144
+
145
+ glViewport( 0, 0, WIDE, HIGH )
146
+ glMatrixMode( GL::PROJECTION )
147
+ glLoadIdentity( )
148
+ #GLU::Perspective( -35, WIDE/(HIGH.to_f), 0.5, 30.0)
149
+ GLU::Perspective( 45, WIDE/(HIGH.to_f), 0.5, 100.0)
150
+ glMatrixMode( GL::MODELVIEW )
151
+ glLoadIdentity( )
152
+
153
+
154
+
155
+ @lighting.setup
156
+
157
+ glShadeModel (GL_SMOOTH);
158
+ glEnable(GL::DEPTH_TEST)
159
+ glEnable(GL_LIGHTING)
160
+ glEnable(GL_LIGHT0)
161
+ glDepthFunc(GL::LESS)
162
+
163
+
164
+
165
+ end
166
+
167
+
168
+ def run()
169
+ catch(:rubygame_quit) do
170
+ loop do
171
+ @queue.each do |event|
172
+ case event
173
+ when Rubygame::Events::KeyPressed
174
+ # puts event.key
175
+ case event.key
176
+ when :escape
177
+ throw :rubygame_quit
178
+ when :q
179
+ throw :rubygame_quit
180
+ when :w
181
+ @w_down = true
182
+ when :s
183
+ @s_down = true
184
+ when :a
185
+ @a_down = true
186
+ when :d
187
+ @d_down = true
188
+ when :l
189
+ @world.particles.each do |part|
190
+ puts part.position
191
+ puts "NUM CONTACTS: #{@world.contacts.size}"
192
+ end
193
+ when :space
194
+ @current_ball.frozen = !@current_ball.frozen
195
+ when :r
196
+ puts "RESET"
197
+ @current_ball = Particle.new
198
+ @current_ball.position = START_POSITION.dup
199
+ @current_ball.velocity = @ball.velocity.dup
200
+ @current_ball.mass = @ball.mass
201
+ @current_ball.damping = @ball.damping
202
+ @current_ball.frozen = true
203
+ @world.registry.add( @current_ball, ParticleGravity.new())
204
+ @world.particles << @current_ball
205
+ end
206
+ when Rubygame::Events::KeyReleased
207
+ case event.key
208
+ when :w
209
+ @w_down = false
210
+ when :a
211
+ @a_down = false
212
+ when :d
213
+ @d_down = false
214
+ when :s
215
+ @s_down = false
216
+ end
217
+ when Rubygame::Events::MouseMoved
218
+ puts "Mouse moved to #{event.rel.inspect} with #{event.buttons.inspect} pressed"
219
+ if( event.buttons.include? :mouse_right )
220
+ puts "mouse_right"
221
+ @current_ball.position.x += event.rel[0] / 100.0
222
+ @current_ball.position.y -= event.rel[1] / 100.0
223
+ end
224
+ when Rubygame::Events::QuitRequested
225
+ throw :rubygame_quit
226
+ end
227
+ end
228
+
229
+ if @w_down;@current_ball.position.y += 0.01; end;
230
+ if @s_down;@current_ball.position.y -= 0.01; end;
231
+ if @a_down;@current_ball.position.x -= 0.01; end;
232
+ if @d_down;@current_ball.position.x += 0.01; end;
233
+
234
+ @time_elapsed = @clock.tick()
235
+ @time_alive = @clock.lifetime
236
+ seconds_elapsed = @time_elapsed.milliseconds / 1000.0
237
+ @world.start_frame
238
+
239
+ glClearColor(0.0, 0.0, 0.0, -1.0)
240
+ glClear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT)
241
+ glLoadIdentity()
242
+
243
+ glTranslatef(@x_trans,@y_trans,@z_trans)
244
+
245
+ glPushMatrix()
246
+ glBegin(GL_QUADS)
247
+ glVertex3f(-1000, -1 , 1000)
248
+ glVertex3f(-1000, -1, -1000)
249
+ glVertex3f(1000, -1, -1000)
250
+ glVertex3f(1000, -1, 1000)
251
+ glEnd
252
+ glPopMatrix()
253
+
254
+
255
+ draw_skybox()
256
+
257
+ #glClear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT)
258
+ @world.particles.each do |particle|
259
+ p = particle.position
260
+ glPushMatrix()
261
+ glTranslatef(p.x, p.y, p.z)
262
+ particle.material.setup_material()
263
+ glutSolidSphere(particle.radius, 16, 16)
264
+ glPopMatrix()
265
+ end
266
+
267
+ @world.run_physics(seconds_elapsed)
268
+
269
+ Rubygame::GL.swap_buffers()
270
+ @screen.update
271
+ @screen.flip
272
+ ObjectSpace.garbage_collect
273
+
274
+ end
275
+ end
276
+
277
+ end
278
+
279
+ end
280
+
281
+
282
+ REAL_MAX = 10000000
283
+ WIDE = 1280
284
+ HIGH = 1024
285
+
286
+
287
+ game = Game.new(WIDE, HIGH)
288
+ game.init_game
289
+ game.run
@@ -0,0 +1,242 @@
1
+ module BulldogPhysics
2
+
3
+ class Matrix3
4
+
5
+
6
+ attr_accessor :data
7
+
8
+
9
+ def initialize(*args)
10
+ @data = Array.new
11
+
12
+ if(args.size == 0)
13
+ @data = [ 0, 0, 0, \
14
+ 0, 0, 0, \
15
+ 0, 0, 0]
16
+ elsif( args[0].is_a? Float)
17
+ @data = [ args[0], args[1], args[2],\
18
+ args[3], args[4], args[5],\
19
+ args[6], args[7], args[8]]
20
+ elsif( args[0].is_a? Vector3)
21
+ setComponents(args[0], args[1], args[2])
22
+ end
23
+ end
24
+
25
+
26
+
27
+ def *(vector)
28
+
29
+ if( @data.size < 8)
30
+ raise Exception.new("@data not big enough for vector math")
31
+ end
32
+
33
+ if( vector.is_a? Matrix3)
34
+ o = vector.dup
35
+ return Matrix3.new(@data[0]*o.data[0] + @data[1]*o.data[3] + @data[2]*o.data[6], @data[0]*o.data[1] + @data[1]*o.data[4] + @data[2]*o.data[7], @data[0]*o.data[2] + @data[1]*o.data[5] + @data[2]*o.data[8], \
36
+ @data[3]*o.data[0] + @data[4]*o.data[3] + @data[5]*o.data[6], @data[3]*o.data[1] + @data[4]*o.data[4] + @data[5]*o.data[7], @data[3]*o.data[2] + @data[4]*o.data[5] + @data[5]*o.data[8], \
37
+ @data[6]*o.data[0] + @data[7]*o.data[3] + @data[8]*o.data[6], @data[6]*o.data[1] + @data[7]*o.data[4] + @data[8]*o.data[7], @data[6]*o.data[2] + @data[7]*o.data[5] + @data[8]*o.data[8] )
38
+ elsif vector.is_a? Float
39
+ scalar = vector
40
+ return Matrix3.new(@data[0] * scalar, @data[1] * scalar, @data[2] * scalar, \
41
+ @data[3] * scalar, @data[4] * scalar, @data[5] * scalar, \
42
+ @data[6] * scalar, @data[7] * scalar, @data[8] * scalar)
43
+ else
44
+
45
+ return Vector3.new( (vector.x * @data[0]) + (vector.y * @data[1]) + (vector.z * @data[2]) , \
46
+ vector.x * @data[3] + vector.y * @data[4] + vector.z * @data[5] , \
47
+ vector.x * @data[6] + vector.y * @data[7] + vector.z * @data[8])
48
+ end
49
+
50
+ end
51
+
52
+ def +(o)
53
+ Matrix3.new(
54
+ @data[0] + o.data[0], @data[1] + o.data[1], @data[2] + o.data[2], \
55
+ @data[3] + o.data[3], @data[4] + o.data[4], @data[5] + o.data[5], \
56
+ @data[6] + o.data[6], @data[7] + o.data[7], @data[8] + o.data[8] \
57
+ )
58
+ end
59
+
60
+ def multiplyByMatrix(o)
61
+ o = o.dup
62
+
63
+ t1 = @data[0]*o.data[0] + @data[1]*o.data[3] + @data[2]*o.data[6];
64
+ t2 = @data[0]*o.data[1] + @data[1]*o.data[4] + @data[2]*o.data[7];
65
+ t3 = @data[0]*o.data[2] + @data[1]*o.data[5] + @data[2]*o.data[8];
66
+ @data[0] = t1;
67
+ @data[1] = t2;
68
+ @data[2] = t3;
69
+ t1 = @data[3]*o.data[0] + @data[4]*o.data[3] + @data[5]*o.data[6];
70
+ t2 = @data[3]*o.data[1] + @data[4]*o.data[4] + @data[5]*o.data[7];
71
+ t3 = @data[3]*o.data[2] + @data[4]*o.data[5] + @data[5]*o.data[8];
72
+ @data[3] = t1;
73
+ @data[4] = t2;
74
+ @data[5] = t3;
75
+ t1 = @data[6]*o.data[0] + @data[7]*o.data[3] + @data[8]*o.data[6];
76
+ t2 = @data[6]*o.data[1] + @data[7]*o.data[4] + @data[8]*o.data[7];
77
+ t3 = @data[6]*o.data[2] + @data[7]*o.data[5] + @data[8]*o.data[8];
78
+ @data[6] = t1;
79
+ @data[7] = t2;
80
+ @data[8] = t3;
81
+ end
82
+
83
+ def multiplyByScalar(scalar)
84
+ @data[0] *= scalar; @data[1] *= scalar; @data[2] *= scalar;
85
+ @data[3] *= scalar; @data[4] *= scalar; @data[5] *= scalar;
86
+ @data[6] *= scalar; @data[7] *= scalar; @data[8] *= scalar;
87
+ end
88
+
89
+ def transform(vector)
90
+ self * vector
91
+ end
92
+
93
+ def transformTranspose(vector)
94
+ Vector3.new(
95
+ vector.x * @data[0] + vector.y * @data[3] + vector.z * @data[6],
96
+ vector.x * @data[1] + vector.y * @data[4] + vector.z * @data[7],
97
+ vector.x * @data[2] + vector.y * @data[5] + vector.z * @data[8]
98
+ )
99
+ end
100
+
101
+ def setOrientation(q)
102
+ @data[0] = 1 - (2*q.j*q.j + 2*q.k*q.k);
103
+ @data[1] = 2*q.i*q.j + 2*q.k*q.r;
104
+ @data[2] = 2*q.i*q.k - 2*q.j*q.r;
105
+ @data[3] = 2*q.i*q.j - 2*q.k*q.r;
106
+ @data[4] = 1 - (2*q.i*q.i + 2*q.k*q.k);
107
+ @data[5] = 2*q.j*q.k + 2*q.i*q.r;
108
+ @data[6] = 2*q.i*q.k + 2*q.j*q.r;
109
+ @data[7] = 2*q.j*q.k - 2*q.i*q.r;
110
+ @data[8] = 1 - (2*q.i*q.i + 2*q.j*q.j);
111
+ end
112
+
113
+ def getAxisVector(i)
114
+ Vector3.new( @data[i], @data[i+3], @data[i+6])
115
+ end
116
+
117
+
118
+ def setInverse(m)
119
+ t4 = m.data[0]*m.data[4];
120
+ t6 = m.data[0]*m.data[5];
121
+ t8 = m.data[1]*m.data[3];
122
+ t10 = m.data[2]*m.data[3];
123
+ t12 = m.data[1]*m.data[6];
124
+ t14 = m.data[2]*m.data[6];
125
+
126
+ # Calculate the determinant
127
+ t16 = (t4*m.data[8] - t6*m.data[7] - t8*m.data[8]+
128
+ t10*m.data[7] + t12*m.data[5] - t14*m.data[4]);
129
+
130
+ # Make sure the determinant is non-zero.
131
+ if (t16 == 0.0)
132
+ return
133
+ end
134
+
135
+ t17 = 1/t16;
136
+
137
+ @data[0] = (m.data[4]*m.data[8]-m.data[5]*m.data[7])*t17;
138
+ @data[1] = -(m.data[1]*m.data[8]-m.data[2]*m.data[7])*t17;
139
+ @data[2] = (m.data[1]*m.data[5]-m.data[2]*m.data[4])*t17;
140
+ @data[3] = -(m.data[3]*m.data[8]-m.data[5]*m.data[6])*t17;
141
+ @data[4] = (m.data[0]*m.data[8]-t14)*t17;
142
+ @data[5] = -(t6-t10)*t17;
143
+ @data[6] = (m.data[3]*m.data[7]-m.data[4]*m.data[6])*t17;
144
+ @data[7] = -(m.data[0]*m.data[7]-t12)*t17;
145
+ @data[8] = (t4-t8)*t17;
146
+ end
147
+
148
+ def inverse()
149
+ result = Matrix3.new
150
+ result.setInverse(self)
151
+ return result
152
+ end
153
+
154
+ def invert()
155
+ setInverse(self)
156
+ end
157
+
158
+ def setTranspose(m)
159
+ @data[0] = m.data[0];
160
+ @data[1] = m.data[3];
161
+ @data[2] = m.data[6];
162
+ @data[3] = m.data[1];
163
+ @data[4] = m.data[4];
164
+ @data[5] = m.data[7];
165
+ @data[6] = m.data[2];
166
+ @data[7] = m.data[5];
167
+ @data[8] = m.data[8];
168
+ end
169
+
170
+
171
+ def transform_transpose(vector)
172
+ return Vector3.new(
173
+ vector.x * @data[0] + vector.y * @data[3] + vector.z * @data[6],
174
+ vector.x * @data[1] + vector.y * @data[4] + vector.z * @data[7],
175
+ vector.x * @data[2] + vector.y * @data[5] + vector.z * @data[8]
176
+ );
177
+ end
178
+
179
+ def transpose()
180
+ result = Matrix3.new
181
+ result.setTranspose(self)
182
+ return result
183
+ end
184
+
185
+ def setDiagonal(a, b, c)
186
+ setInertiaTensorCoeffs(a, b, c)
187
+ end
188
+
189
+ def setInertiaTensorCoeffs(ix, iy, iz, ixy = 0, ixz = 0,iyz = 0)
190
+ @data[0] = ix;
191
+ @data[1] = @data[3] = -ixy;
192
+ @data[2] = @data[6] = -ixz;
193
+ @data[4] = iy;
194
+ @data[5] = @data[7] = -iyz;
195
+ @data[8] = iz;
196
+ end
197
+
198
+
199
+ def setBlockInertiaTensor(vHalfSizes, mass)
200
+ squares = vHalfSizes.componentProduct(vHalfSizes)
201
+ setInertiaTensorCoeffs(0.3 * mass * (squares.y + squares.z),
202
+ 0.3 * mass * (squares.x + squares.z),
203
+ 0.3 * mass * (squares.x + squares.y))
204
+ end
205
+
206
+ def setSkewSymmetric(vector)
207
+ @data[0] = @data[4] = @data[8] = 0;
208
+ @data[1] = -vector.z;
209
+ @data[2] = vector.y;
210
+ @data[3] = vector.z;
211
+ @data[5] = -vector.x;
212
+ @data[6] = -vector.y;
213
+ @data[7] = vector.x;
214
+ end
215
+
216
+ def setComponents(vCompOne, vCompTwo, vCompThree)
217
+ @data[0] = vCompOne.x
218
+ @data[1] = vCompTwo.x
219
+ @data[2] = vCompThree.x
220
+ @data[3] = vCompOne.y
221
+ @data[4] = vCompTwo.y
222
+ @data[5] = vCompThree.y
223
+ @data[6] = vCompOne.z
224
+ @data[7] = vCompTwo.z
225
+ @data[8] = vCompThree.z
226
+ end
227
+
228
+ def self.linearInterpolate(a, b, prop)
229
+ result = Matrix3.new
230
+ for i in 0..9
231
+ result.data[i] = a.data * (1-prop) + b.data[i] * prop
232
+ end
233
+ return result
234
+ end
235
+
236
+ def to_s
237
+ "[ #{@data[0]} #{@data[1]} #{@data[2]}\n \ #{@data[3]} #{@data[4]} #{@data[5]}\n \ #{@data[6]} #{@data[7]} #{@data[8]} ]"
238
+ end
239
+
240
+ end
241
+
242
+ end