teien 0.0.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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in teien.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 abexsoft@gmail.com
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Teien
2
+
3
+ Teien will be a platform to make 3D world easily with Ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'teien'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ruby-ogre
18
+ $ gem install ruby-bullet
19
+ $ gem install teienlib
20
+ $ gem install pkg/teien-<version>.gem
21
+
22
+ ## Usage
23
+
24
+ Run a sample application.
25
+
26
+ $ /var/lib/gems/1.9.1/gems/teien-<version>/sample/standalone/hello_garden/run
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/teien ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script is a environment setting helper on the startup.
4
+ # 1. set a LD_LIBRARY_PATH(or PATH) value for dynamic libraries.
5
+ # 2. restart with above value.
6
+ # 3. set a LOAD_PATH value for ruby libraries.
7
+
8
+ if (ARGV[0] == "-h" || ARGV[0] == "--help")
9
+ puts "Usage: teien file"
10
+ exit
11
+ end
12
+
13
+ #
14
+ # require this file directly at first line, unless you set
15
+ # RUBYLIB and LD_LIBRARY_PATH. (check sample code)
16
+ #
17
+ require 'rbconfig'
18
+ require 'OgreConfig'
19
+
20
+ $topDir = File.dirname(File.dirname(File.expand_path(__FILE__)))
21
+ libPath = $topDir + "/lib"
22
+ extPath = OgreConfig::getDepsLibPath
23
+
24
+ # needed by dynamic library.
25
+ if (/mingw/ =~ RUBY_PLATFORM)
26
+ ldLibPath = extPath + ";" + OgreConfig::getLib + ";" + libPath
27
+ if (ENV["PATH"] == nil)
28
+ ENV["PATH"] = ldLibPath
29
+ exec("ruby #{$0} #{ARGV.join(" ")}")
30
+ elsif (!ENV["PATH"].include?(ldLibPath))
31
+ ENV["PATH"] = ldLibPath + ";" +ENV["PATH"]
32
+ exec("ruby #{$0} #{ARGV.join(" ")}")
33
+ end
34
+ else
35
+ ldLibPath = extPath + ":" + libPath
36
+ if (ENV["LD_LIBRARY_PATH"] == nil)
37
+ ENV["LD_LIBRARY_PATH"] = ldLibPath
38
+ exec($0, *ARGV)
39
+ elsif(!ENV["LD_LIBRARY_PATH"].include?(ldLibPath))
40
+ ENV["LD_LIBRARY_PATH"] = ldLibPath + ":" + ENV["LD_LIBRARY_PATH"]
41
+ exec($0, *ARGV)
42
+ end
43
+ end
44
+
45
+ $LOAD_PATH.push(libPath)
46
+ $LOAD_PATH.push("./")
47
+
48
+ load ARGV[0]
49
+
@@ -0,0 +1,142 @@
1
+ class AnimationOperator
2
+ BL_MODE_SWITCH = 0 # stop current animation and start next animation.
3
+ BL_MODE_CONCURRENT = 1 # cross fade, blend current animation out while blending next animation in.
4
+ BL_MODE_FIRSTFRAME = 2 # blend current animation to first frame of next animation, when done, start next animation.
5
+
6
+ def initialize(entity)
7
+ @entity = entity
8
+ @state = nil
9
+ @nextState = nil
10
+ @mode = BL_MODE_CONCURRENT
11
+ @duration = 0.2
12
+ @timeLeft = 0
13
+ @complete = false
14
+ @loop = false
15
+
16
+ # @blender = Ogrelet::AnimationBlender.new(entity)
17
+ end
18
+
19
+ def init(name, loop)
20
+ initializeAllAnimations()
21
+ play(name, loop)
22
+ end
23
+
24
+ def initializeAllAnimations()
25
+ set = @entity.getAllAnimationStates()
26
+ set.each_AnimationState {|state|
27
+ state.setEnabled(false)
28
+ state.setWeight(0)
29
+ state.setTimePosition(0)
30
+ }
31
+ end
32
+
33
+ def setEnabled(bl)
34
+ # @blender.getSource().setEnabled(bl)
35
+ end
36
+
37
+ def setBlendingMode(mode)
38
+ @mode = mode
39
+ end
40
+
41
+ def setBlengingDuration(duration)
42
+ @duration = duration
43
+ end
44
+
45
+ def play(name, loop)
46
+ @loop = loop
47
+
48
+ unless @state
49
+ @state = @entity.getAnimationState(name)
50
+ @state.setEnabled(true)
51
+ @state.setWeight(1)
52
+ @state.setTimePosition(0)
53
+ @state.setLoop(loop)
54
+ return
55
+ end
56
+
57
+ case @mode
58
+ when BL_MODE_SWITCH
59
+ @state.setEnabled(false)
60
+ @state = @entity.getAnimationState(name)
61
+ @state.setEnabled(true)
62
+ @state.setWeight(1)
63
+ @state.setTimePosition(0)
64
+ @timeLeft = 0
65
+ else
66
+ newState = @entity.getAnimationState(name)
67
+ if @timeLeft > 0
68
+ if newState.getAnimationName == @nextState.getAnimationName
69
+ return
70
+ elsif newState.getAnimationName == @state.getAnimationName
71
+ # going back to the source state
72
+ @state = @nextState
73
+ @nextState = newState
74
+ @timeLeft = @duration - @timeLeft
75
+ else
76
+ if @timeLeft < @duration * 0.5
77
+ # simply replace the target with this one
78
+ @nextState.setEnabled(false)
79
+ @nextState.setWeight(0)
80
+ else
81
+ # old target becomes new source
82
+ @state.setEnabled(false)
83
+ @state.setWeight(0)
84
+ @state = @nextState
85
+ end
86
+
87
+ @nextState = newState
88
+ @nextState.setEnabled(true)
89
+ @nextState.setWeight( 1.0 - @timeLeft / @duration )
90
+ @nextState.setTimePosition(0)
91
+ end
92
+ else
93
+ return if newState.getAnimationName == @state.getAnimationName
94
+
95
+ # assert( target == 0, "target should be 0 when not blending" )
96
+ # @state.setEnabled(true)
97
+ # @state.setWeight(1)
98
+ # mTransition = transition;
99
+ @timeLeft = @duration
100
+ @nextState = newState
101
+ @nextState.setEnabled(true)
102
+ @nextState.setWeight(0)
103
+ @nextState.setTimePosition(0)
104
+ end
105
+ end
106
+ # @blender.blend(name, Ogrelet::AnimationBlender::BlendWhileAnimating, 0.2, loop)
107
+ end
108
+
109
+ def addTime(delta)
110
+ if @state
111
+ if @timeLeft > 0
112
+ @timeLeft -= delta
113
+
114
+ if @timeLeft < 0
115
+ @state.setEnabled(false)
116
+ @state.setWeight(0)
117
+ @state = @nextState
118
+ @state.setEnabled(true)
119
+ @state.setWeight(1)
120
+ @nextState = nil
121
+ else
122
+ # still blending, advance weights
123
+ @state.setWeight(@timeLeft / @duration)
124
+ @nextState.setWeight(1.0 - @timeLeft / @duration)
125
+ if(@mode == BL_MODE_CONCURRENT)
126
+ @nextState.addTime(delta)
127
+ end
128
+ end
129
+ end
130
+
131
+ if @state.getTimePosition() >= @state.getLength()
132
+ @complete = true
133
+ else
134
+ @complete = false
135
+ end
136
+
137
+ @state.addTime(delta)
138
+ @state.setLoop(@loop)
139
+ end
140
+ # @blender.addTime(delta)
141
+ end
142
+ end
@@ -0,0 +1,25 @@
1
+ require 'teien/camera_mover'
2
+
3
+ module Teien
4
+
5
+ class Camera
6
+ def initialize(cam)
7
+ # Ogre::Camera
8
+ @camera = cam
9
+ @mover = CameraMover.new(cam)
10
+ end
11
+
12
+ def get_mover()
13
+ return @mover
14
+ end
15
+
16
+ def get_position
17
+ return Vector3D.to_self(@camera.getPosition())
18
+ end
19
+
20
+ def get_direction
21
+ return Vector3D.to_self(@camera.getDirection())
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,193 @@
1
+ module Teien
2
+
3
+ class CameraMover
4
+ CS_FREELOOK = 0
5
+ CS_ORBIT = 1
6
+ CS_MANUAL = 2
7
+ CS_TPS = 3
8
+
9
+ CAM_HEIGHT = 5.0
10
+
11
+ attr_accessor :height
12
+ attr_accessor :camera_pivot
13
+ attr_accessor :camera
14
+
15
+ def initialize(cam)
16
+ @camera = cam
17
+ @camera.setPosition(0, 0, 0)
18
+ @camera.setNearClipDistance(0.1)
19
+
20
+ @style = CS_FREELOOK
21
+
22
+ # CS_FREELOOK, CS_ORBIT, CS_MANUAL
23
+ @sdk_camera_man = OgreBites::SdkCameraMan.new(@camera)
24
+ @evt_frame = Ogre::FrameEvent.new
25
+
26
+ # CS_TPS
27
+ @height = CAM_HEIGHT
28
+ @camera_pivot = cam.getSceneManager().getRootSceneNode().createChildSceneNode()
29
+ @camera_goal = @camera_pivot.createChildSceneNode(Ogre::Vector3.new(0, 0, 5))
30
+
31
+ @camera_pivot.setFixedYawAxis(true)
32
+ @camera_goal.setFixedYawAxis(true)
33
+
34
+ @pivot_pitch = 0
35
+
36
+ end
37
+
38
+ def set_style(style)
39
+ @style = style
40
+ case @style
41
+ when CS_FREELOOK
42
+ @sdk_camera_man.setStyle(OgreBites::CS_FREELOOK)
43
+ when CS_ORBIT
44
+ @sdk_camera_man.setStyle(OgreBites::CS_ORBIT)
45
+ else # CS_MANUAL, CS_TPS
46
+ @sdk_camera_man.setStyle(OgreBites::CS_MANUAL)
47
+ end
48
+ end
49
+
50
+ def set_target(target)
51
+ @target = target
52
+ if @style == CS_TPS
53
+ @camera.setAutoTracking(false)
54
+ @camera.moveRelative(Ogre::Vector3.new(0, 0, 0))
55
+ update_camera(1.0)
56
+ else
57
+ @sdk_camera_man.setTarget(target.pivotSceneNode)
58
+ end
59
+ end
60
+
61
+ def set_position(pos)
62
+ @camera.setPosition(Vector3D.to_ogre(pos)) if @style == CS_FREELOOK
63
+ end
64
+
65
+ def look_at(pos)
66
+ @camera.lookAt(Vector3D.to_ogre(pos)) if @style == CS_FREELOOK
67
+ end
68
+
69
+ def set_yaw_pitch_dist(yaw, pitch, dist)
70
+ @sdk_camera_man.setYawPitchDist(yaw, pitch, dist) if @style == CS_ORBIT
71
+ end
72
+
73
+ def move_forward(bl)
74
+ evt = OIS::KeyEvent.new(nil, OIS::KC_W, 0)
75
+ if bl
76
+ @sdk_camera_man.injectKeyDown(evt)
77
+ else
78
+ @sdk_camera_man.injectKeyUp(evt)
79
+ end
80
+ end
81
+
82
+ def move_backward(bl)
83
+ evt = OIS::KeyEvent.new(nil, OIS::KC_S, 0)
84
+ if bl
85
+ @sdk_camera_man.injectKeyDown(evt)
86
+ else
87
+ @sdk_camera_man.injectKeyUp(evt)
88
+ end
89
+ end
90
+
91
+ def move_left(bl)
92
+ evt = OIS::KeyEvent.new(nil, OIS::KC_A, 0)
93
+ if bl
94
+ @sdk_camera_man.injectKeyDown(evt)
95
+ else
96
+ @sdk_camera_man.injectKeyUp(evt)
97
+ end
98
+ end
99
+
100
+ def move_right(bl)
101
+ evt = OIS::KeyEvent.new(nil, OIS::KC_D, 0)
102
+ if bl
103
+ @sdk_camera_man.injectKeyDown(evt)
104
+ else
105
+ @sdk_camera_man.injectKeyUp(evt)
106
+ end
107
+ end
108
+
109
+
110
+ def update(delta)
111
+ if (@style == CS_TPS)
112
+ update_camera(delta)
113
+ else
114
+ @evt_frame.timeSinceLastFrame = delta
115
+ @sdk_camera_man.frameRenderingQueued(@evt_frame)
116
+ end
117
+ end
118
+
119
+ #
120
+ # This method moves this camera position to the goal position smoothly.
121
+ # In general, should be called in the frameRenderingQueued handler.
122
+ #
123
+ def update_camera(deltaTime)
124
+ # place the camera pivot roughly at the character's shoulder
125
+ @camera_pivot.setPosition(Vector3D::to_ogre(@target.get_position()) + Ogre::Vector3.UNIT_Y * @height)
126
+ # move the camera smoothly to the goal
127
+ goalOffset = @camera_goal._getDerivedPosition() - @camera.getPosition()
128
+ @camera.move(goalOffset * deltaTime * 9.0)
129
+ # always look at the pivot
130
+ @camera.lookAt(@camera_pivot._getDerivedPosition())
131
+ end
132
+
133
+ def mouse_moved(evt)
134
+ if @style == CS_TPS
135
+
136
+ # deal with a warp.
137
+ if evt.state.X.rel.abs > 300
138
+ #puts "#{evt.state.X.rel}, #{evt.state.X.abs}"
139
+ return true
140
+ end
141
+
142
+ update_camera_goal(-0.05 * evt.state.X.rel,
143
+ -0.05 * evt.state.Y.rel,
144
+ -0.0005 * evt.state.Z.rel)
145
+ else
146
+ @sdk_camera_man.injectMouseMove(evt)
147
+ end
148
+ return true
149
+ end
150
+
151
+ #
152
+ # This method updates the goal position, which this camera should be placed finally.
153
+ # In general, should be called when the mouse is moved.
154
+ # *deltaYaw*::_float_, degree value.
155
+ # *deltaPitch*::_float_, degree value.
156
+ # *deltaZoom*::_float_, zoom
157
+ #
158
+ def update_camera_goal(deltaYaw, deltaPitch, deltaZoom)
159
+
160
+ @camera_pivot.yaw(Ogre::Radian.new(Ogre::Degree.new(deltaYaw)), Ogre::Node::TS_WORLD);
161
+
162
+ # bound the pitch
163
+ if (!(@pivot_pitch + deltaPitch > 25 && deltaPitch > 0) &&
164
+ !(@pivot_pitch + deltaPitch < -60 && deltaPitch < 0))
165
+ @camera_pivot.pitch(Ogre::Radian.new(Ogre::Degree.new(deltaPitch)), Ogre::Node::TS_LOCAL)
166
+ @pivot_pitch += deltaPitch;
167
+ end
168
+ dist = @camera_goal._getDerivedPosition().distance(@camera_pivot._getDerivedPosition())
169
+ distChange = deltaZoom * dist;
170
+
171
+ # puts "dist: #{dist}:#{distChange}"
172
+
173
+ # bound the zoom
174
+ if (!(dist + distChange < 8 && distChange < 0) &&
175
+ !(dist + distChange > 25 && distChange > 0))
176
+
177
+ @camera_goal.translate(Ogre::Vector3.new(0, 0, distChange), Ogre::Node::TS_LOCAL)
178
+ end
179
+ end
180
+
181
+ def mouse_pressed(mouseEvent, mouseButtonID)
182
+ @sdk_camera_man.injectMouseDown(mouseEvent, mouseButtonID) if @style == CS_ORBIT
183
+ return true
184
+ end
185
+
186
+ def mouse_released(mouseEvent, mouseButtonID)
187
+ @sdk_camera_man.injectMouseUp(mouseEvent, mouseButtonID) if @style == CS_ORBIT
188
+ return true
189
+ end
190
+
191
+ end
192
+
193
+ end
@@ -0,0 +1,203 @@
1
+ require "teien/garden_object.rb"
2
+ require "teien/light_object.rb"
3
+ require "teien/object_factory.rb"
4
+ require "teien/view.rb"
5
+ #require "teien/server_view.rb"
6
+ require "teien/physics.rb"
7
+ require "teien/user_interface.rb"
8
+ #require "teien/server_user_interface.rb"
9
+
10
+ module Teien
11
+
12
+ # This is a top object of 3D world.
13
+ class Garden
14
+ attr_accessor :view
15
+ attr_accessor :physics
16
+ attr_accessor :resources_cfg
17
+ attr_accessor :plugins_cfg
18
+ attr_accessor :objects
19
+ attr_accessor :object_factory
20
+ attr_accessor :is_server
21
+
22
+ #
23
+ # _script_klass_:: : set a user define class.
24
+ #
25
+ def initialize(script_klass)
26
+ @view = nil
27
+ @physics = nil
28
+ @script_klass = script_klass
29
+
30
+ @resources_cfg = nil
31
+ @plugins_cfg = nil
32
+ @objects = {}
33
+ @object_num = 0
34
+
35
+ @object_factory = ObjectFactory.new(self)
36
+
37
+ @is_server = false
38
+ @debug_draw = false
39
+ @quit = false
40
+ end
41
+
42
+ #
43
+ # set a title name on the window title bar.
44
+ #
45
+ # _title_ :: : set a name(String).
46
+ #
47
+ def set_window_title(title)
48
+ @view.window_title = title unless @is_server
49
+ end
50
+
51
+ #
52
+ # set the gravity of the world.
53
+ #
54
+ # _grav_ :: : set a vector(Vector3D) as the gravity.
55
+ #
56
+ def set_gravity(grav)
57
+ @physics.set_gravity(grav)
58
+ end
59
+
60
+ #
61
+ # set the ambient light of the world.
62
+ #
63
+ # _color_:: : set a color(Color).
64
+ #
65
+ def set_ambient_light(color)
66
+ @view.scene_mgr.setAmbientLight(color)
67
+ end
68
+
69
+ def set_sky_dome(enable, materialName, curvature = 10, tiling = 8, distance = 4000)
70
+ @view.scene_mgr.setSkyDome(enable, materialName, curvature, tiling, distance)
71
+ end
72
+
73
+ def set_debug_draw(bl)
74
+ if (bl)
75
+ @debug_draw = bl
76
+ @debug_drawer = Ogrelet::DebugDrawer.new(@view.scene_mgr)
77
+ @physics.dynamicsWorld.setDebugDrawer(@debug_drawer)
78
+ end
79
+ end
80
+
81
+ def create_user_interface()
82
+ if @is_server
83
+ return ServerUserInterface.new(@view)
84
+ else
85
+ return UserInterface.new(@view)
86
+ end
87
+ end
88
+
89
+ def create_object(name, objectInfo, physicsInfo)
90
+ return @object_factory.create_object(name, objectInfo, physicsInfo)
91
+ end
92
+
93
+ def create_light(name)
94
+ return LightObject.new(self, name)
95
+ end
96
+
97
+ def add_object(obj, collision_filter = nil)
98
+ if (@objects[obj.name] == nil)
99
+ if (obj.rigid_body != nil)
100
+ if (collision_filter)
101
+ @physics.add_rigid_body(obj.rigid_body, collision_filter)
102
+ else
103
+ @physics.add_rigid_body(obj.rigid_body)
104
+ end
105
+ end
106
+ @objects[obj.name] = obj
107
+ obj.id = @object_num
108
+ @object_num += 1
109
+ else
110
+ raise RuntimeError, "There is a object with the same name (#{obj.name})"
111
+ end
112
+ end
113
+
114
+ def check_collision(objectA, objectB)
115
+ result = @physics.contact_pair_test(objectA.rigid_body, objectB.rigid_body)
116
+ return result.isCollided
117
+ end
118
+
119
+ def setup()
120
+ if @is_server
121
+ @view = ServerView.new(self)
122
+ else
123
+ @view = View.new(self)
124
+ end
125
+
126
+ @physics = Physics.new(self)
127
+ @script = @script_klass.new(self)
128
+
129
+ if @view.setup()
130
+ @view.start(@script)
131
+ @view.prepare_render_loop()
132
+
133
+ @physics.setup()
134
+ @script.setup()
135
+
136
+ return true
137
+ else
138
+ return false
139
+ end
140
+ end
141
+
142
+ #
143
+ # mainloop
144
+ #
145
+ def run()
146
+ return false unless setup()
147
+
148
+ last_time = Time.now.to_f
149
+ while !@quit
150
+ now_time = Time.now.to_f
151
+ delta = now_time - last_time
152
+ last_time = now_time
153
+ break unless update(delta)
154
+ end
155
+
156
+ finalize()
157
+
158
+ return true
159
+ end
160
+
161
+
162
+ def update(delta)
163
+ @physics.dynamics_world.debugDrawWorld() if @debug_draw
164
+
165
+ return @view.update(delta)
166
+ end
167
+
168
+ # called from view.update()
169
+ # This function is divided from update() by the purpose of an optimization,
170
+ # which archives to run in parallel with GPU process.
171
+ def updateInFrameRenderingQueued(delta)
172
+ return false unless @physics.update(delta)
173
+
174
+ @objects.each_value {|obj|
175
+ obj.update(delta)
176
+ }
177
+
178
+ return @script.update(delta)
179
+ end
180
+
181
+ def clean_up()
182
+ @physics.finalize()
183
+ @objects = {}
184
+ @view.stop()
185
+ end
186
+
187
+ # called by Garden class.
188
+ # clear all managers.
189
+ def finalize()
190
+ @physics.finalize()
191
+ @view.root.saveConfig()
192
+ @view.finalize()
193
+ @objects = {}
194
+ end
195
+
196
+
197
+ # quit the current running garden.
198
+ def quit()
199
+ @quit = true
200
+ end
201
+ end
202
+
203
+ end # module