summaryrefslogtreecommitdiffstats
path: root/qtruby/rubylib/examples/ruboids/ruboids
diff options
context:
space:
mode:
Diffstat (limited to 'qtruby/rubylib/examples/ruboids/ruboids')
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Boid.rb141
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/BoidView.rb159
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Camera.rb24
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/CameraDialog.rb213
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Canvas.rb144
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Cloud.rb61
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/CloudView.rb54
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Flock.rb47
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Graphics.rb278
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Params.rb87
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Point.rb153
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Thing.rb34
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/Triangle.rb21
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/View.rb88
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/World.rb82
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/WorldWindow.rb54
-rw-r--r--qtruby/rubylib/examples/ruboids/ruboids/info.rb12
-rwxr-xr-xqtruby/rubylib/examples/ruboids/ruboids/ruboids.rb29
18 files changed, 1681 insertions, 0 deletions
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Boid.rb b/qtruby/rubylib/examples/ruboids/ruboids/Boid.rb
new file mode 100644
index 00000000..38ac7bcc
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Boid.rb
@@ -0,0 +1,141 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'BoidView'
+require 'Flock'
+require 'Point'
+require 'Params'
+
+class Boid < Thing
+
+ attr_accessor :maxSpeed, :maxSpeedSquared, :perchingTurnsLeft,
+ :wingFlapPos, :almostGroundLevel, :flock
+
+ def initialize(pos = nil)
+ super(pos, nil)
+ init
+ end
+
+ def init
+ @maxSpeed = $PARAMS['boid_max_speed']
+ @maxSpeedSquared = @maxSpeed * @maxSpeed
+ @flock = nil # set by flock when flock adds to self
+ @wingFlapPos = rand(7)
+ @perchingTurnsLeft = 0
+ @almostGroundLevel = 5.0
+
+ @view = BoidView.new(self)
+ end
+
+ def move
+ # Flap wings. Only flap occasionally if not perching.
+ if (@perchingTurnsLeft == 0 ||
+ rand(100) < $PARAMS['boid_perch_wing_flap_percent'])
+ @wingFlapPos = (@wingFlapPos + 1) & 7
+ end
+
+ if @perchingTurnsLeft > 0
+ # Only take off when wing flap position == 2.
+ if --@perchingTurnsLeft == 0 && @wingFlapPos != 2
+ @perchingTurnsLeft = (8 + 2 - @wingFlapPos) & 7
+ return
+ end
+ end
+
+ moveTowardsFlockCenter()
+ avoidOthers()
+ matchOthersVelocities()
+ boundPosition()
+ limitSpeed()
+
+ super() # Add velocity vector to position.
+
+ # Boids at ground level perch for a while.
+ if @position.y < @almostGroundLevel
+ @position.y = @almostGroundLevel
+ @vector.x = @vector.y = @vector.z = 0
+ @perchingTurnsLeft =
+ rand($PARAMS['boid_max_perching_turns'])
+ end
+ end
+
+ def moveTowardsFlockCenter()
+ flockCenter = @flock.centerExcluding(self)
+ flockCenter.subtractPoint(@position)
+ # Move 1% of the way towards the center
+ flockCenter.divideBy(100.0)
+
+ @vector.addPoint(flockCenter)
+ end
+
+ def avoidOthers()
+ c = Point.new()
+ @flock.members.each { | b |
+ if b != self
+ otherPos = b.position
+ if @position.squareOfDistanceTo(otherPos) <
+ $PARAMS['boid_square_of_personal_space_dist']
+ c.addPoint(@position)
+ c.subtractPoint(otherPos)
+ end
+ end
+ }
+ @vector.addPoint(c)
+ end
+
+ def matchOthersVelocities()
+ vel = Point.new()
+ flock.members.each { | b |
+ if b != self
+ vel.addPoint(b.vector)
+ end
+ }
+ vel.divideBy(flock.members.length - 1)
+ vel.subtractPoint(@vector)
+ vel.divideBy(8)
+
+ @vector.addPoint(vel)
+ end
+
+ def boundPosition()
+ v = Point.new
+
+ halfWidth = $PARAMS['world_width'] / 2
+ halfHeight = $PARAMS['world_height'] / 2
+ halfDepth = $PARAMS['world_depth'] / 2
+
+ if position.x < -halfWidth
+ v.x = $PARAMS['boid_bounds_limit_pull']
+ elsif position.x > halfWidth
+ v.x = -$PARAMS['boid_bounds_limit_pull']
+ end
+
+ if position.y < -halfHeight + almostGroundLevel +
+ $PARAMS['boid_bounds_limit_above_ground_level']
+ v.y = $PARAMS['boid_bounds_limit_pull']
+ elsif position.y > halfHeight
+ v.y = -$PARAMS['boid_bounds_limit_pull']
+ end
+
+ if position.z < -halfDepth
+ v.z = $PARAMS['boid_bounds_limit_pull']
+ elsif position.z > halfDepth
+ v.z = -$PARAMS['boid_bounds_limit_pull']
+ end
+
+ @vector.addPoint(v)
+ end
+
+ def limitSpeed()
+ speedSquared = Point::ORIGIN.squareOfDistanceTo(@vector)
+ if speedSquared > @maxSpeedSquared
+ f = Math.sqrt(speedSquared) * @maxSpeed
+ @vector.divideBy(f)
+ end
+ end
+end
+
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/BoidView.rb b/qtruby/rubylib/examples/ruboids/ruboids/BoidView.rb
new file mode 100644
index 00000000..f2fc1288
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/BoidView.rb
@@ -0,0 +1,159 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'View'
+
+class BoidView < View
+
+ BODY_COLOR = [0, 0, 0]
+ BEAK_COLOR = [0.75, 0.5, 0.0]
+ SHADOW_COLOR = [0.25, 0.55, 0.25]
+
+ HALF_WING_BASE = 3
+ HALF_LENGTH = 5
+ HALF_THICKNESS = 1
+ NOSE_LENGTH = 3
+
+ @@object = nil
+ @@shadow = nil
+ @@wings = nil
+ @@wingsShadows = nil
+
+ def initialize(model)
+ super(model, [0, 0, 0])
+ @wings = nil
+ @wingsShadows = nil
+ end
+
+ def makeObject
+ @@object = BoidView.makeObject() unless @@object
+ @object = @@object
+ @wings = @@wings
+ end
+
+ def makeShadow
+ BoidView.makeShadow() unless @@shadow
+ @shadow = @@shadow
+ @wingsShadows = @@wingsShadows
+ end
+
+ def drawObject
+ super()
+
+ angle = 0
+ case model.wingFlapPos
+ when 0
+ angle = 60
+ when 1, 7
+ angle = 30
+ when 2, 6
+ angle = 0
+ when 3, 5
+ angle = -30
+ when 4
+ angle = -60
+ end
+
+ PushMatrix()
+ Rotate(angle, 0, 0, 1)
+ CallList(@wings[0])
+ Rotate(angle * -2, 0, 0, 1)
+ CallList(@wings[1])
+ PopMatrix()
+ end
+
+ def BoidView.makeObject
+ makeWings()
+
+ object = GenLists(1)
+ NewList(object, COMPILE)
+
+ makeBody()
+ makeNose()
+
+ EndList()
+
+ return object
+ end
+
+ def BoidView.makeShadow
+ @@shadow = GenLists(1)
+ NewList(@@shadow, COMPILE)
+
+ p0 = Point::ORIGIN.dup()
+ p1 = Point::ORIGIN.dup()
+ dims = Point.new(HALF_THICKNESS, HALF_THICKNESS, HALF_LENGTH)
+ p0.subtractPoint(dims)
+ p1.addPoint(dims)
+
+ groundLevel = -($PARAMS['world_height'] / 2) + 1
+
+ Color(SHADOW_COLOR)
+ Begin(QUADS)
+ Vertex(p1.x, groundLevel, p0.z)
+ Vertex(p0.x, groundLevel, p0.z)
+ Vertex(p0.x, groundLevel, p1.z)
+ Vertex(p1.x, groundLevel, p1.z)
+ End()
+# Begin(TRIANGLES)
+# Vertex(p1.x, groundLevel, p1.z)
+# Vertex(0, groundLevel, p0.z)
+# Vertex(p0.x, groundLevel, p1.z)
+# End()
+
+ EndList()
+ end
+
+ def BoidView.makeBody
+ p0 = Point::ORIGIN.dup()
+ p1 = Point::ORIGIN.dup()
+ dims = Point.new(HALF_THICKNESS, HALF_THICKNESS, HALF_LENGTH)
+ p0.subtractPoint(dims)
+ p1.addPoint(dims)
+
+ Color(BODY_COLOR)
+ Graphics.boxFromCorners(p0, p1)
+ end
+
+ def BoidView.makeWings
+ @@wings = []
+ len = -$PARAMS['boid_wing_length']
+ @@wings << makeOneWing(len)
+ @@wings << makeOneWing(-len)
+ end
+
+ def BoidView.makeOneWing(len)
+ wing = GenLists(1)
+ NewList(wing, COMPILE)
+
+ Color(BODY_COLOR)
+ Begin(TRIANGLES)
+
+ Vertex(0, 0, -HALF_WING_BASE)
+ Vertex(len, 0, 0)
+ Vertex(0, 0, HALF_WING_BASE)
+
+ End()
+ EndList()
+ return wing
+ end
+
+ def BoidView.makeNose()
+ Color(BEAK_COLOR)
+ Begin(TRIANGLE_FAN)
+
+ Vertex(0, 0, HALF_LENGTH + NOSE_LENGTH)
+ Vertex( HALF_THICKNESS, HALF_THICKNESS, HALF_LENGTH)
+ Vertex(-HALF_THICKNESS, HALF_THICKNESS, HALF_LENGTH)
+ Vertex(-HALF_THICKNESS, -HALF_THICKNESS, HALF_LENGTH)
+ Vertex( HALF_THICKNESS, -HALF_THICKNESS, HALF_LENGTH)
+
+ End()
+ end
+
+end
+
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Camera.rb b/qtruby/rubylib/examples/ruboids/ruboids/Camera.rb
new file mode 100644
index 00000000..787fc4af
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Camera.rb
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Params'
+
+class Camera
+
+ attr_accessor :position, :rotation, :zoom
+
+ def initialize
+ @position = Point.new($PARAMS['camera_x'],
+ $PARAMS['camera_y'],
+ $PARAMS['camera_z'])
+ @rotation = Point.new($PARAMS['camera_rot_x'],
+ $PARAMS['camera_rot_y'],
+ $PARAMS['camera_rot_z'])
+ @zoom = $PARAMS['camera_zoom']
+ end
+end
+
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/CameraDialog.rb b/qtruby/rubylib/examples/ruboids/ruboids/CameraDialog.rb
new file mode 100644
index 00000000..6e01db15
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/CameraDialog.rb
@@ -0,0 +1,213 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Qt'
+require 'World'
+require 'Camera'
+
+class Adjustor
+ attr_accessor :slider, :num, :origValue
+ def initialize(slider, num, origValue = 0)
+ @slider = slider
+ @num = num
+ @origValue = origValue
+ end
+ def setSlider(val); @slider.setValue(val); end
+ def setNum(val); @num.setNum(val); end
+ def set(val)
+ setSlider(val)
+ setNum(val)
+ end
+ def reset
+ set(@origValue)
+ return @origValue
+ end
+end
+
+class CameraDialog < Qt::Dialog
+ slots 'slotReset()', 'slotLocXChanged(int)',
+ 'slotLocYChanged(int)', 'slotLocZChanged(int)',
+ 'slotRotationXChanged(int)', 'slotRotationYChanged(int)',
+ 'slotRotationZChanged(int)', 'slotZoomChanged(int)'
+
+ def initialize(parent)
+ super
+ @locAdjustors = []
+ @rotationAdjustors = []
+ @otherAdjustors = []
+ @avoidUpdates = false
+
+ @camera = World.instance.camera
+
+ # Remember values for reset
+ @origCamera = @camera.dup()
+
+ # Group and layout widgets
+ vLayout = Qt::VBoxLayout.new(self, 5)
+
+ locBox = Qt::GroupBox.new('Location', self, 'locBox')
+ rotationBox = Qt::GroupBox.new('Rotation', self, 'rotationBox')
+ otherBox = Qt::GroupBox.new('Other', self, 'otherBox')
+
+ locLayout = Qt::GridLayout.new(locBox, 5, 3, 20)
+ rotationLayout = Qt::GridLayout.new(rotationBox, 5, 3, 20)
+ otherLayout = Qt::GridLayout.new(otherBox, 3, 3, 20)
+ buttonLayout = Qt::HBoxLayout.new()
+
+ vLayout.addWidget(locBox)
+ vLayout.addWidget(rotationBox)
+ vLayout.addWidget(otherBox)
+ vLayout.addSpacing(10)
+ vLayout.addLayout(buttonLayout)
+
+ # Add extra space at the top of each layout so the group box title
+ # doesn't get squished.
+ locLayout.addRowSpacing(0, 15)
+ rotationLayout.addRowSpacing(0, 15)
+ otherLayout.addRowSpacing(0, 15)
+
+ # Contents of camera location box
+ @locAdjustors << addSlider(1, locBox, locLayout, 'X', -1000, 1000, 1,
+ 'slotLocXChanged(int)', @camera.position.x)
+ @locAdjustors << addSlider(2, locBox, locLayout, 'Y', -1000, 1000, 1,
+ 'slotLocYChanged(int)', @camera.position.y)
+ @locAdjustors << addSlider(3, locBox, locLayout, 'Z', -1000, 1000, 1,
+ 'slotLocZChanged(int)', @camera.position.z)
+
+ # Contents of camera rotation box
+ @rotationAdjustors << addSlider(1, rotationBox, rotationLayout, 'X',
+ 0, 360, 1, 'slotRotationXChanged(int)',
+ @camera.rotation.x)
+ @rotationAdjustors << addSlider(2, rotationBox, rotationLayout, 'Y',
+ 0, 360, 1, 'slotRotationYChanged(int)',
+ @camera.rotation.y)
+ @rotationAdjustors << addSlider(3, rotationBox, rotationLayout, 'Z',
+ 0, 360, 1, 'slotRotationZChanged(int)',
+ @camera.rotation.z)
+
+ @otherAdjustors << addSlider(1, otherBox, otherLayout, 'Zoom',
+ 1, 100, 1, 'slotZoomChanged(int)',
+ @camera.zoom * 10.0)
+ @otherAdjustors[0].origValue = @camera.zoom
+
+ # The Close button
+ button = Qt::PushButton.new('Close', self, 'Dialog Close')
+ connect(button, SIGNAL('clicked()'), self, SLOT('close()'))
+ button.setDefault(true)
+ button.setFixedSize(button.sizeHint())
+ buttonLayout.addWidget(button)
+
+ # The Close button
+ button = Qt::PushButton.new('Reset', self, 'Dialog Reset')
+ connect(button, SIGNAL('clicked()'), self, SLOT('slotReset()'))
+ button.setFixedSize(button.sizeHint())
+ buttonLayout.addWidget(button)
+
+ # 15 layout management
+ locLayout.activate()
+ rotationLayout.activate()
+ otherLayout.activate()
+ vLayout.activate()
+
+ resize(0, 0)
+
+ setCaption('Camera Settings')
+ end
+
+ def addSlider(row, box, layout, label, min, max, pageStep, slot,
+ initialValue)
+ # Label
+ text = Qt::Label.new(label, box)
+ text.setMinimumSize(text.sizeHint())
+ layout.addWidget(text, row, 0)
+
+ # Slider
+ slider = Qt::Slider.new(min, max, pageStep, initialValue,
+ Qt::Slider::Horizontal, box)
+ slider.setMinimumSize(slider.sizeHint())
+ slider.setMinimumWidth(180)
+ layout.addWidget(slider, row, 1)
+
+ # Connection from slider signal to our slot
+ connect(slider, SIGNAL('valueChanged(int)'), self, SLOT(slot))
+
+ # Number display
+ num = Qt::Label.new('XXXXX', box)
+ num.setMinimumSize(num.sizeHint())
+ num.setFrameStyle(Qt::Frame::Panel | Qt::Frame::Sunken)
+ num.setAlignment(AlignRight | AlignVCenter)
+ num.setNum(initialValue)
+
+ layout.addWidget(num, row, 2)
+
+ return Adjustor.new(slider, num, initialValue)
+ end
+
+ def cameraChanged
+ World.instance.setupTranslation() unless @avoidUpdates
+ end
+
+ def slotLocXChanged(val)
+ @locAdjustors[0].setNum(val)
+ @camera.position.x = val
+ cameraChanged()
+ end
+
+ def slotLocYChanged(val)
+ @locAdjustors[1].setNum(val)
+ @camera.position.y = val
+ cameraChanged()
+ end
+
+ def slotLocZChanged(val)
+ @locAdjustors[2].setNum(val)
+ @camera.position.z = val
+ cameraChanged()
+ end
+
+ def slotRotationXChanged(val)
+ @rotationAdjustors[0].setNum(val)
+ @camera.rotation.x = val
+ cameraChanged()
+ end
+
+ def slotRotationYChanged(val)
+ @rotationAdjustors[1].setNum(val)
+ @camera.rotation.y = val
+ cameraChanged()
+ end
+
+ def slotRotationZChanged(val)
+ @rotationAdjustors[2].setNum(val)
+ @camera.rotation.z = val
+ cameraChanged()
+ end
+
+ def slotZoomChanged(val)
+ @otherAdjustors[0].setNum(val)
+ @camera.zoom = val / 10.0
+ cameraChanged()
+ end
+
+ def slotReset
+ @avoidUpdates = true
+
+ @camera.position.x = @locAdjustors[0].reset()
+ @camera.position.y = @locAdjustors[1].reset()
+ @camera.position.z = @locAdjustors[2].reset()
+
+ @camera.rotation.x = @rotationAdjustors[0].reset()
+ @camera.rotation.y = @rotationAdjustors[1].reset()
+ @camera.rotation.z = @rotationAdjustors[2].reset()
+
+ @camera.zoom = @otherAdjustors[0].reset()
+
+ @avoidUpdates = false
+ cameraChanged()
+ end
+
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Canvas.rb b/qtruby/rubylib/examples/ruboids/ruboids/Canvas.rb
new file mode 100644
index 00000000..91ed934b
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Canvas.rb
@@ -0,0 +1,144 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Qt'
+require 'opengl'
+require 'World'
+require 'Cloud'
+require 'Flock'
+require 'Params'
+require 'Camera'
+
+include GL
+
+class Canvas < Qt::GLWidget
+
+ GRASS_COLOR = [0, 0.75, 0]
+ MDA_ROTATE = :MDA_ROTATE
+ MDA_ZOOM = :MDA_ZOOM
+ MDA_CHANGE_FOCUS = :MDA_CHANGE_FOCUS
+
+ def initialize(parent = nil, name = '')
+ super
+ @grassObject = nil
+# catchEvent
+ end
+
+ def update
+ updateGL()
+ end
+
+ def initializeGL()
+ ClearColor(0.4, 0.4, 1.0, 0.0) # Let OpenGL clear to light blue
+ @grassObject = makeGrassObject()
+ ShadeModel(FLAT)
+ end
+
+ def paintGL()
+ Enable(DEPTH_TEST)
+ Clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT)
+
+ MatrixMode(MODELVIEW)
+
+ camera = World.instance.camera
+
+ LoadIdentity()
+ Rotate(camera.rotation.x, 1, 0, 0)
+ Rotate(camera.rotation.y, 0, 1, 0)
+ Rotate(camera.rotation.z, 0, 0, 1.0)
+ Translate(-camera.position.x, -camera.position.y, -camera.position.z)
+ Scale(camera.zoom, camera.zoom, camera.zoom)
+
+ CallList(@grassObject)
+
+ World.instance.clouds.each { | cloud | cloud.draw() }
+ World.instance.flock.draw()
+ end
+
+ # Set up the OpenGL view port, matrix mode, etc.
+ def resizeGL(w, h)
+ Viewport(0, 0, w, h)
+ MatrixMode(PROJECTION)
+ LoadIdentity()
+
+# # left, right, bottom, top, front, back (focal_length)
+ halfXSize = $PARAMS['world_width'] / 2 * 1.25
+ halfYSize = $PARAMS['world_height'] / 2 * 1.25
+ halfZSize = $PARAMS['world_depth'] / 2 * 1.25
+
+# Frustum(-halfXSize, halfXSize, -halfYSize, halfYSize,
+# 5, halfZSize * 2)
+
+ Ortho(-halfXSize, halfXSize, -halfYSize, halfYSize,
+ -halfZSize, halfZSize)
+
+ MatrixMode(MODELVIEW)
+ end
+
+ def makeGrassObject
+ halfXSize = $PARAMS['world_width']
+ halfYSize = $PARAMS['world_depth'] / 2
+ halfZSize = $PARAMS['world_height']
+
+ list = GenLists(1)
+ NewList(list, COMPILE)
+ LineWidth(2.0)
+ Begin(QUADS)
+
+ Color(GRASS_COLOR)
+ # Counter-clockwise
+ Vertex( halfXSize, -halfYSize, halfZSize)
+ Vertex(-halfXSize, -halfYSize, halfZSize)
+ Vertex(-halfXSize, -halfYSize, -halfZSize)
+ Vertex( halfXSize, -halfYSize, -halfZSize)
+
+ End()
+ EndList()
+ return list
+ end
+
+ def mousePressEvent(e)
+ @mouseLoc = e.pos()
+ case e.button()
+ when Qt::LeftButton
+ @mouseDragAction = MDA_ZOOM
+ when Qt::RightButton
+ @mouseDragAction = MDA_ROTATE
+ when Qt::MidButton
+ @mouseDragAction = MDA_CHANGE_FOCUS
+ end
+ end
+
+ # Rotate around sphere with right (#2) button. Zoom with left button.
+ # Change focus with left button.
+ def mouseMoveEvent(e)
+ return if @mouseLoc.nil?
+
+ dx = dy = 0
+ if e.x() != @mouseLoc.x()
+ dx = e.x() - @mouseLoc.x() # move right increases dx
+ @mouseLoc.setX(e.x())
+ end
+ if e.y() != @mouseLoc.y()
+ dy = @mouseLoc.y() - e.y() # move up increases dy
+ @mouseLoc.setY(e.y())
+ end
+
+ return if dx == 0 && dy == 0
+
+ case @mouseDragAction
+ when MDA_ZOOM
+ return if (dy == 0)
+ World.instance.camera.zoom += 0.1 * -dy
+ when MDA_ROTATE
+ break
+ when MDA_CHANGE_FOCUS
+ break
+ end
+ World.instance.setupTranslation()
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Cloud.rb b/qtruby/rubylib/examples/ruboids/ruboids/Cloud.rb
new file mode 100644
index 00000000..5d30222a
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Cloud.rb
@@ -0,0 +1,61 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Thing'
+require 'CloudView'
+require 'Params'
+
+class Bubble
+
+ attr_reader :loc, :radius, :color
+
+ def initialize
+ @radius = rand($PARAMS['cloud_max_bubble_radius']) + 1
+ @loc = Point.new(0, rand(8) - 4, rand(8) - 4)
+ c = 0.85 + rand() * 0.15
+ @color = [c, c, c]
+ end
+
+end
+
+
+class Cloud < Thing
+
+ attr_reader :speed, :bubbles, :width
+
+ def initialize
+ minSpeed = $PARAMS['cloud_min_speed']
+ minBubbles = $PARAMS['cloud_min_bubbles']
+ @speed = rand($PARAMS['cloud_max_speed'] - minSpeed) + minSpeed
+ numBubbles = rand($PARAMS['cloud_max_bubbles'] - minBubbles) +
+ minBubbles
+ @bubbles = []
+ prevBubble = nil
+ (0 ... numBubbles).each { | i |
+ bubble = Bubble.new()
+ if !prevBubble.nil?
+ bubble.loc.x = prevBubble.loc.x +
+ rand((prevBubble.radius + bubble.radius) * 0.66)
+ end
+
+ @bubbles[i] = prevBubble = bubble
+ }
+
+ @width = bubbles.last.loc.x +
+ @bubbles.first.radius + @bubbles.last.radius
+
+ @view = CloudView.new(self)
+ end
+
+ def move
+ @position.x += pixelsPerSecToPixelsPerMove(speed)
+ halfWorldWidth = $PARAMS['world_width']
+ if (@position.x >= halfWorldWidth / 2)
+ @position.x = -(halfWorldWidth + @width)
+ end
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/CloudView.rb b/qtruby/rubylib/examples/ruboids/ruboids/CloudView.rb
new file mode 100644
index 00000000..75c62177
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/CloudView.rb
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Qt'
+require 'View'
+require 'Cloud'
+require 'Params'
+require 'World'
+require 'Graphics'
+
+class CloudView < View
+
+ def initialize(cloud)
+ super(cloud)
+ end
+
+ def makeObject
+ @object = GenLists(1)
+ NewList(@object, COMPILE)
+
+ @model.bubbles.each { | bubble |
+ Color(bubble.color)
+ PushMatrix()
+ Translate(bubble.loc.x, bubble.loc.y, bubble.loc.z)
+ Scale(bubble.radius, bubble.radius, bubble.radius)
+ Graphics.sphere()
+ PopMatrix()
+ }
+
+ EndList()
+ end
+
+ def makeShadow
+ @shadow = GenLists(1)
+ NewList(@shadow, COMPILE)
+
+ groundLevel = -($PARAMS['world_height'] / 2) + 1
+ @model.bubbles.each { | bubble |
+ Color(shadowColorForHeight(model.position.y + bubble.loc.y))
+ PushMatrix()
+ Translate(bubble.loc.x, groundLevel, bubble.loc.z)
+ Scale(bubble.radius, 1.0, bubble.radius)
+ Graphics.circle(2)
+ PopMatrix()
+ }
+
+ EndList()
+ end
+
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Flock.rb b/qtruby/rubylib/examples/ruboids/ruboids/Flock.rb
new file mode 100644
index 00000000..4d476a2b
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Flock.rb
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Flock'
+require 'Boid'
+require 'Params'
+
+class Flock
+ attr_reader :members
+
+ def initialize
+ @members = []
+ end
+
+ def add(boid)
+ @members << boid
+ boid.flock = self
+ end
+
+ def draw
+ @members.each { | boid | boid.draw() }
+ end
+
+ def move
+ @members.each { | boid | boid.move() }
+ end
+
+ # Return distance between two boid's positions.
+ def distBetween(b1, b2)
+ return b1.position.distanceTo(b2.position)
+ end
+
+ # Center of mass
+ def centerExcluding(b)
+ p = Point.new()
+ @members.each { | boid |
+ p.addPoint(boid.position) unless boid == b
+ }
+ p.divideBy(@members.length - 1)
+ return p
+ end
+end
+
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Graphics.rb b/qtruby/rubylib/examples/ruboids/ruboids/Graphics.rb
new file mode 100644
index 00000000..5e982208
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Graphics.rb
@@ -0,0 +1,278 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Triangle'
+
+class Graphics
+
+ DEFAULT_SPHERE_ITERATIONS = 3
+
+ XPLUS = Point.new(1, 0, 0) # X
+ XMINUS = Point.new(-1, 0, 0)# -X
+ YPLUS = Point.new(0, 1, 0) # Y
+ YMINUS = Point.new(0, -1, 0)# -Y
+ ZPLUS = Point.new(0, 0, 1) # Z
+ ZMINUS = Point.new(0, 0, -1)# -Z
+
+ # defined w/counter-clockwise triangles
+ OCTAHEDRON = [
+ Triangle.new(YPLUS, ZPLUS, XPLUS),
+ Triangle.new(XMINUS, ZPLUS, YPLUS),
+ Triangle.new(YMINUS, ZPLUS, XMINUS),
+ Triangle.new(XPLUS, ZPLUS, YMINUS),
+ Triangle.new(ZMINUS, YPLUS, XPLUS),
+ Triangle.new(ZMINUS, XMINUS , YPLUS),
+ Triangle.new(ZMINUS, YMINUS , XMINUS),
+ Triangle.new(ZMINUS, XPLUS, YMINUS)
+ ]
+ # Defines counter-clockwise points used in OpenGL TRIANGLE_STRIP to
+ # create a circle on the X/Z plane. Don't include center point here;
+ # It is added when outputting the circle.
+ SQUARE = [
+ XPLUS, ZMINUS, XMINUS, ZPLUS, XPLUS
+ ]
+
+ @@spheres = Hash.new()
+ @@circles = Hash.new()
+
+ def Graphics.radiansToDegrees(rad)
+ return rad * 180.0 / Math::PI
+ end
+
+ def Graphics.degreesToRadians(deg)
+ return deg * Math::PI / 180.0
+ end
+
+ # Given a vector, return a point containing x, y, z rotation angles.
+ #
+ # atan2(x, y) = the angle formed with the x axis by the ray from the
+ # origin to the point {x,y}
+ def Graphics.rotations(v)
+ return Point::ORIGIN.dup() if v.nil?
+ return v if v == Point::ORIGIN
+
+ x = Math.atan2(v.y, v.z)
+ y = Math.atan2(v.z, v.x)
+ z = Math.atan2(v.y, v.x)
+
+ rot = Point.new(z, x, y)
+ rot.add(Math::PI).multiplyBy(180.0).divideBy(Math::PI)
+
+ rot.x = rot.x.to_i
+ rot.y = rot.y.to_i
+ rot.z = rot.z.to_i
+
+ return rot
+ end
+
+ # Build box from corners. All faces are counter-clockwise.
+ def Graphics.boxFromCorners(p0, p1)
+ pa = p0.dup()
+ pb = p1.dup()
+
+ # Make sure all coords of pa are < all coords of pb
+ if pa.x > pb.x
+ tmp = pa.x; pa.x = pb.x; pb.x = tmp
+ end
+ if pa.y > pb.y
+ tmp = pa.y; pa.y = pb.y; pb.y = tmp
+ end
+ if pa.z > pb.z
+ tmp = pa.z; pa.z = pb.z; pb.z = tmp
+ end
+
+ Begin(QUAD_STRIP)
+
+ # top
+ Vertex(pb.x, pb.y, pa.z)
+ Vertex(pa.x, pb.y, pa.z)
+ # top/front
+ Vertex(pb.x, pb.y, pb.z)
+ Vertex(pa.x, pb.y, pb.z)
+ # front/bottom
+ Vertex(pb.x, pa.y, pb.z)
+ Vertex(pa.x, pa.y, pb.z)
+ # bottom/back
+ Vertex(pb.x, pa.y, pa.z)
+ Vertex(pa.x, pa.y, pa.z)
+ # back/top
+ Vertex(pb.x, pb.y, pa.z)
+ Vertex(pa.x, pb.y, pa.z)
+
+ End()
+
+ Begin(QUADS)
+
+ # left
+ Vertex(pa.x, pa.y, pb.z)
+ Vertex(pa.x, pa.y, pa.z)
+ Vertex(pa.x, pb.y, pa.z)
+ Vertex(pa.x, pb.y, pb.z)
+
+ # right
+ Vertex(pb.x, pa.y, pb.z)
+ Vertex(pb.x, pa.y, pa.z)
+ Vertex(pb.x, pb.y, pa.z)
+ Vertex(pb.x, pb.y, pb.z)
+
+ End()
+ end
+
+ # sphere() (and buildSphere()) - generate a triangle mesh approximating
+ # a sphere by recursive subdivision. First approximation is an
+ # octahedron; each level of refinement increases the number of
+ # triangles by a factor of 4.
+ #
+ # Level 3 (128 triangles) is a good tradeoff if gouraud shading is used
+ # to render the database.
+ #
+ # Usage: sphere [level] [counterClockwise]
+ #
+ # The value level is an integer >= 1 setting the recursion level
+ # (default = DEFAULT_SPHERE_ITERATIONS).
+ # The boolean counterClockwise causes triangles to be generated
+ # with vertices in counterclockwise order as viewed from
+ # the outside in a RHS coordinate system. The default is
+ # counter-clockwise.
+ #
+ # @author Jon Leech (leech@cs.unc.edu) 3/24/89 (C version)
+ # Ruby version by Jim Menard (jimm@io.com), May 2001.
+ def Graphics.sphere(iterations = DEFAULT_SPHERE_ITERATIONS,
+ counterClockwise = true)
+ if @@spheres[iterations].nil?
+ @@spheres[iterations] = buildSphere(iterations, OCTAHEDRON)
+ end
+ sphere = @@spheres[iterations]
+
+ Begin(TRIANGLES)
+ sphere.each { | triangle |
+ triangle.points.each { | p |
+ Vertex(p.x, p.y, p.z) if counterClockwise
+ Vertex(p.z, p.y, p.x) if !counterClockwise
+ }
+ }
+ End()
+ end
+
+ #
+ # Subdivide each triangle in the oldObj approximation and normalize
+ # the new points thus generated to lie on the surface of the unit
+ # sphere.
+ # Each input triangle with vertices labelled [0,1,2] as shown
+ # below will be turned into four new triangles:
+ #
+ # Make new points
+ # a = (0+2)/2
+ # b = (0+1)/2
+ # c = (1+2)/2
+ # 1
+ # /\ Normalize a, b, c
+ # / \
+ # b/____\ c Construct new counter-clockwise triangles
+ # /\ /\ [a,b,0]
+ # / \ / \ [c,1,b]
+ # /____\/____\ [c,b,a]
+ # 0 a 2 [2,c,a]
+ #
+ #
+ # The normalize step (which makes each point a, b, c unit distance
+ # from the origin) is where we can modify the sphere's shape.
+ #
+ def Graphics.buildSphere(iterations, sphere)
+ oldObj = sphere
+ # Subdivide each starting triangle (maxlevel - 1) times
+ iterations -= 1
+ iterations.times {
+ # Create a new object. Allocate 4 * the number of points in the
+ # the current approximation.
+ newObj = Array.new(oldObj.length * 4)
+
+ j = 0
+ oldObj.each { | oldt |
+ # New midpoints
+ a = Point.midpoint(oldt.points[0], oldt.points[2])
+ a.normalize!()
+ b = Point.midpoint(oldt.points[0], oldt.points[1])
+ b.normalize!()
+ c = Point.midpoint(oldt.points[1], oldt.points[2])
+ c.normalize!()
+
+ # New triangeles. Their vertices are counter-clockwise.
+ newObj[j] = Triangle.new(a, b, oldt.points[0])
+ j += 1
+ newObj[j] = Triangle.new(c, oldt.points[1], b)
+ j += 1
+ newObj[j] = Triangle.new(c, b, a)
+ j += 1
+ newObj[j] = Triangle.new(oldt.points[2], c, a)
+ j += 1
+ }
+
+ # Continue subdividing new triangles
+ oldObj = newObj
+ }
+ return oldObj
+ end
+
+ # Creates a circle in the X/Z plane. To have the circle's normal
+ # point down (-Y), specify clockwise instead of counter-clockwise.
+ # To create the circle in another plane, call OpenGL's Rotate() method
+ # before calling this.
+ def Graphics.circle(iterations = DEFAULT_SPHERE_ITERATIONS,
+ counterClockwise = true)
+ if @@circles[iterations].nil?
+ @@circles[iterations] = buildCircle(iterations, SQUARE)
+ end
+ circle = @@circles[iterations]
+
+ Begin(TRIANGLE_FAN)
+ Vertex(0, 0, 0)
+ if counterClockwise
+ circle.each { | p | Vertex(p.x, 0, p.z) }
+ else
+ circle.reverse.each { | p | Vertex(p.x, 0, p.z) }
+ end
+ End()
+ end
+
+ # Different than buildSphere because we are creating triangles to
+ # be used in an OpenGL TRIANGLE_FAN operation. Thus the first point
+ # (the center) is always inviolate. We create new points between
+ # the remaining points.
+ def Graphics.buildCircle(iterations, circle)
+ oldObj = circle
+ # Subdivide each starting line segment (maxlevel - 1) times
+ iterations -= 1
+ iterations.times {
+ # Create a new object. Allocate 2 * the number of points in the
+ # the current approximation. Subtract one because the last point
+ # (same as the first point) is simply copied.
+ newObj = Array.new(oldObj.length * 2 - 1)
+
+ prevP = nil
+ j = 0
+ oldObj.each { | p |
+ if !prevP.nil?
+ newObj[j] = prevP
+ j += 1
+
+ # New midpoint
+ a = Point.midpoint(prevP, p)
+ a.normalize!()
+ newObj[j] = a
+ j += 1
+ end
+ prevP = p
+ }
+ newObj[j] = prevP # Copy last point
+
+ # Continue subdividing new triangles
+ oldObj = newObj
+ }
+ return oldObj
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Params.rb b/qtruby/rubylib/examples/ruboids/ruboids/Params.rb
new file mode 100644
index 00000000..9ff57851
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Params.rb
@@ -0,0 +1,87 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'singleton'
+
+$PARAMS = {
+ 'world_sleep_millis' => 75,
+ 'world_width' => 400,
+ 'world_height' => 400,
+ 'world_depth' => 400,
+ 'window_width' => 500,
+ 'window_height' => 500,
+ 'flock_boids' => 10,
+ 'boid_max_speed' => 30,
+ 'boid_bounds_limit_pull' => 5,
+ 'boid_bounds_limit_above_ground_level' => 5,
+ 'boid_wing_length' => 10,
+ 'boid_personal_space_dist' => 12,
+ 'boid_square_of_personal_space_dist' => 144,
+ 'boid_max_perching_turns' => 150,
+ 'boid_perch_wing_flap_percent' => 30,
+ 'cloud_count' => 10,
+ 'cloud_min_speed' => 2,
+ 'cloud_max_speed' => 50,
+ 'cloud_min_bubbles' => 3,
+ 'cloud_max_bubbles' => 10,
+ 'cloud_max_bubble_radius' => 10,
+ 'cloud_min_altitude' => 250,
+ 'camera_x' => 0,
+ 'camera_y' => 0,
+ 'camera_z' => 60,
+ 'camera_rot_x' => 50,
+ 'camera_rot_y' => 10,
+ 'camera_rot_z' => 0,
+ 'camera_zoom' => 1
+}
+
+class Params
+
+ @@reals = %w(
+world_width
+world_height
+world_depth
+boid_max_speed
+boid_bounds_limit_pull
+boid_bounds_limit_above_ground_level
+boid_wing_length
+boid_personal_space_dist
+boid_square_of_personal_space_dist
+cloud_min_speed
+cloud_max_speed
+cloud_max_bubble_radius
+cloud_min_altitude
+camera_x
+camera_y
+camera_z
+camera_rot_x
+camera_rot_y
+camera_rot_z
+camera_zoom
+)
+
+ def Params.readParamsFromFile(paramFileName)
+ File.open(paramFileName).each { | line |
+ line.chomp!
+ next if line.empty? || line =~ /^#/
+
+ key, value = line.split(/\s*=\s*/)
+ next unless value
+ key.downcase!()
+ key.gsub!(/\./, '_')
+
+ isReal = @@reals.include?(key)
+ value = value.to_f if isReal
+ value = value.to_i if !isReal
+ $PARAMS[key] = value
+ }
+ $PARAMS['boid_square_of_personal_space_dist'] =
+ $PARAMS['boid_personal_space_dist'] *
+ $PARAMS['boid_personal_space_dist']
+ end
+
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Point.rb b/qtruby/rubylib/examples/ruboids/ruboids/Point.rb
new file mode 100644
index 00000000..0331f795
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Point.rb
@@ -0,0 +1,153 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+class Point
+
+ attr_accessor :x, :y, :z
+
+ # Return a new Point that is the midpoint on the line between two
+ # points.
+ def Point.midpoint(a, b)
+ return Point.new((a.x + b.x) * 0.5, (a.y + b.y) * 0.5,
+ (a.z + b.z) * 0.5)
+ end
+
+ def initialize(x = 0, y = 0, z = 0)
+ if x.kind_of?(Point)
+ @x = x.x
+ @y = x.y
+ @z = x.z
+ else
+ @x = x
+ @y = y
+ @z = z
+ end
+ end
+
+ ORIGIN = Point.new(0, 0, 0)
+
+ def ==(point)
+ return point.kind_of?(Point) &&
+ @x == point.x && @y == point.y && @z == point.z
+ end
+
+ # Normalize this point.
+ def normalize!
+ mag = @x * @x + @y * @y + @z * @z
+ if mag != 1.0
+ mag = 1.0 / Math.sqrt(mag)
+ @x *= mag
+ @y *= mag
+ @z *= mag
+ end
+ return self
+ end
+
+ # Return a new point that is a normalized version of this point.
+ def normalize
+ return self.dup().normalize!()
+ end
+
+ # Return a new point that is the cross product of this point and another.
+ # The cross product of two unit vectors is another vector that's at
+ # right angles to the first two (for example, a surface normal).
+ def crossProduct(p)
+ return Point.new(@y * p.z - @z * p.y, @z * p.x - @x * p.z,
+ @x * p.y - @y * p.x)
+ end
+
+ # Return the (scalar) dot product of this vector and another.
+ # The dot product of two vectors produces the cosine of the angle
+ # between them, multiplied by the lengths of those vectors. (The dot
+ # product of two normalized vectors equals cosine of the angle.)
+ def dotProduct(p)
+ return @x * p.x + @y * p.y + @z * p.z
+ end
+
+ # Return square of distance between this point and another.
+ def squareOfDistanceTo(p)
+ dx = p.x - @x
+ dy = p.y - @y
+ dz = p.z - @z
+ return dx * dx + dy * dy + dz * dz
+ end
+
+ # Return distance between this point and another.
+ def distanceTo(p)
+ dx = p.x - @x
+ dy = p.y - @y
+ dz = p.z - @z
+ return Math.sqrt(dx * dx + dy * dy + dz * dz)
+ end
+
+ def add(d)
+ @x += d
+ @y += d
+ @z += d
+ return self
+ end
+
+ def addPoint(p)
+ @x += p.x
+ @y += p.y
+ @z += p.z
+ return self
+ end
+
+
+ def subtract(d)
+ @x -= d
+ @y -= d
+ @z -= d
+ return self
+ end
+
+ def subtractPoint(p)
+ @x -= p.x
+ @y -= p.y
+ @z -= p.z
+ return self
+ end
+
+
+ def multiplyBy(d)
+ @x *= d
+ @y *= d
+ @z *= d
+ return self
+ end
+
+ def multiplyByPoint(p)
+ @x *= p.x
+ @y *= p.y
+ @z *= p.z
+ return self
+ end
+
+ def divideBy(d)
+ @x = @x / d
+ @y = @y / d
+ @z = @z / d
+ return self
+ end
+
+ def divideByPoint(p)
+ @x = @x / p.x
+ @y = @y / p.y
+ @z = @z / p.z
+ return self
+ end
+
+ def to_a
+ return [@x, @y, @z]
+ end
+
+ def to_s
+ return "Point<#{@x}, #{@y}, #{@z}>"
+ end
+
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Thing.rb b/qtruby/rubylib/examples/ruboids/ruboids/Thing.rb
new file mode 100644
index 00000000..9b6bfe5b
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Thing.rb
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Point'
+
+class Thing
+
+ attr_accessor :position, :vector, :view
+
+ def initialize(pos = nil, vec = nil)
+ @position = pos ? pos : Point.new
+ @vector = vec ? vec : Point.new
+ end
+
+ def move
+ position.x += vector.x
+ position.y += vector.y
+ position.z += vector.z
+ end
+
+ def draw
+ view.draw() if view
+ end
+
+ def pixelsPerSecToPixelsPerMove(pixelsPerSecond)
+ pps = (pixelsPerSecond.to_f / (1000.0 / 75.0)).to_i
+ pps = 1 if pps == 0
+ return pps
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/Triangle.rb b/qtruby/rubylib/examples/ruboids/ruboids/Triangle.rb
new file mode 100644
index 00000000..eedf69f9
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/Triangle.rb
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Point'
+
+class Triangle
+ attr_accessor :points
+
+ def initialize(p0 = Point::ORIGIN,
+ p1 = Point::ORIGIN,
+ p2 = Point::ORIGIN)
+ @points = []
+ @points << p0 ? p0 : Point::ORIGIN.dup()
+ @points << p1 ? p1 : Point::ORIGIN.dup()
+ @points << p2 ? p2 : Point::ORIGIN.dup()
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/View.rb b/qtruby/rubylib/examples/ruboids/ruboids/View.rb
new file mode 100644
index 00000000..a5323629
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/View.rb
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+# A lightweight view
+class View
+
+ SHADOW_COLOR = [ 0.25, 0.25, 0.25 ]
+
+ attr_accessor :model, :color, :object, :shadow
+
+ def initialize(model, color = nil)
+ super()
+ @model = model
+ @color = color
+ @object = nil
+ @shadow = nil
+ end
+
+ def makeObject
+ raise "subclass should implement"
+ end
+
+ def makeShadow
+ # Don't raise error; some models may not have a shadow
+ end
+
+ def drawObject
+ CallList(@object)
+ end
+
+ def drawShadow
+ CallList(@shadow) if @shadow
+ end
+
+ def draw
+ # We don't always have enough information to make the 3D objects
+ # at initialize() time.
+ makeObject() unless @object
+ makeShadow() unless @shadow
+
+ rot = Graphics.rotations(model.vector)
+
+ PushMatrix()
+
+ # Translate and rotate shadow. Rotation around y axis only.
+ Translate(model.position.x, 0, model.position.z)
+ Rotate(rot.y, 0, 1, 0) if rot.y.nonzero?
+
+ # Draw shadow.
+ drawShadow() unless @shadow.nil?
+
+ # Translate and rotate object. Rotate object around x and z axes (y
+ # axis already done for shadow).
+ Translate(0, model.position.y, 0)
+ Rotate(rot.x, 1, 0, 0) if rot.x.nonzero?
+ Rotate(rot.z, 0, 0, 1) if rot.z.nonzero?
+
+ # Draw object.
+ drawObject()
+
+ PopMatrix()
+ end
+
+ # Given the height of an object, return a shadow color. The shadow color
+ # gets lighter as heigt increases.
+ def shadowColorForHeight(height)
+ wh = $PARAMS['world_height']
+ ratio = (height + wh / 2.0) / wh
+
+ shadowColor = []
+ SHADOW_COLOR.each_with_index { | c0, i |
+ min = c0
+ max = Canvas::GRASS_COLOR[i]
+ if min > max
+ tmp = min
+ min = max
+ max = tmp
+ end
+ shadowColor << min + ratio * (max - min)
+ }
+ return shadowColor
+ end
+
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/World.rb b/qtruby/rubylib/examples/ruboids/ruboids/World.rb
new file mode 100644
index 00000000..17608bca
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/World.rb
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'singleton'
+require 'Qt'
+require 'Params'
+require 'Cloud'
+require 'Flock'
+require 'Boid'
+require 'Camera'
+require 'Canvas'
+
+class World < Qt::Object
+ slots 'slotMove()'
+
+ include Singleton
+
+ attr_accessor :canvas
+ attr_reader :width, :height, :depth, :camera, :clouds, :flock
+
+ def initialize
+ super
+ @width = $PARAMS['world_width']
+ @height = $PARAMS['world_height']
+ @depth = $PARAMS['world_depth']
+
+ @clouds = []
+ minAltitude = $PARAMS['cloud_min_altitude']
+ $PARAMS['cloud_count'].times {
+ c = Cloud.new
+ c.position =
+ Point.new(rand(@width) - @width / 2,
+ rand(@height) - @height / 2,
+ rand(@depth - minAltitude) - @depth / 2 + minAltitude)
+ @clouds << c
+ }
+ # Sort clouds by height so lower/darker shadows are drawn last
+ @clouds.sort { |a, b| a.position.y <=> b.position.y }
+
+ @flock = Flock.new
+ $PARAMS['flock_boids'].times {
+ b = Boid.new
+ b.position = Point.new(rand(@width) - @width / 2,
+ rand(@height) - @height / 2,
+ rand(@depth) - @depth / 2)
+ @flock.add(b) # flock will delete boid
+ }
+
+ @clock = Qt::Timer.new()
+ connect(@clock, SIGNAL('timeout()'), self, SLOT('slotMove()'))
+
+ @camera = Camera.new # Reads values from params
+ setupTranslation()
+ end
+
+ # Should be called whenever camera or screen changes.
+ def setupTranslation
+ @canvas.update() if @canvas
+ end
+
+ def start
+ @clock.start($PARAMS['world_sleep_millis'])
+ end
+
+ def slotMove
+ @clouds.each { | c | c.move() }
+ @flock.move()
+ @canvas.update() if @canvas
+
+ # Camera follow boid.
+# b = @flock.members.first
+# @camera.position = b.position
+# @camera.rotation = Graphics.rotations(b.vector)
+# @camera.zoom = 1.0
+
+ end
+end
+
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/WorldWindow.rb b/qtruby/rubylib/examples/ruboids/ruboids/WorldWindow.rb
new file mode 100644
index 00000000..56650ece
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/WorldWindow.rb
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Qt'
+require 'Canvas'
+require 'CameraDialog'
+
+class WorldWindow < Qt::MainWindow
+ slots 'slotMenuActivated(int)'
+
+ MENU_CAMERA_DIALOG = 1
+
+ attr_accessor :canvas
+
+ def initialize
+ super
+ setCaption("Boids")
+ setupMenubar()
+
+ @canvas = Canvas.new(self, "TheDamnCanvas")
+ setCentralWidget(@canvas)
+ setGeometry(0, 0, $PARAMS['window_width'],
+ $PARAMS['window_height'])
+ end
+
+ def setupMenubar
+
+ # Create and populate file menu
+ menu = Qt::PopupMenu.new(self)
+ menu.insertItem("Exit", $qApp, SLOT("quit()"), Qt::KeySequence.new(CTRL+Key_Q))
+
+ # Add file menu to menu bar
+ menuBar.insertItem("&File", menu)
+
+ # Create and populate options menu
+ menu = Qt::PopupMenu.new(self)
+ menu.insertItem("&Camera...", MENU_CAMERA_DIALOG, -1)
+
+ # Add options menu to menu bar and link it to method below
+ menuBar.insertItem("&Options", menu)
+ connect(menu, SIGNAL("activated(int)"), self, SLOT('slotMenuActivated(int)'))
+
+ end
+
+ def slotMenuActivated(id)
+ if id == MENU_CAMERA_DIALOG
+ CameraDialog.new(nil).exec()
+ end
+ end
+end
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/info.rb b/qtruby/rubylib/examples/ruboids/ruboids/info.rb
new file mode 100644
index 00000000..fcfc50f6
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/info.rb
@@ -0,0 +1,12 @@
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+VERSION_MAJOR = 0
+VERSION_MINOR = 0
+VERSION_TWEAK = 1
+Version = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_TWEAK}"
+Copyright = 'Copyright (c) 2001 by Jim Menard <jimm@io.com>'
diff --git a/qtruby/rubylib/examples/ruboids/ruboids/ruboids.rb b/qtruby/rubylib/examples/ruboids/ruboids/ruboids.rb
new file mode 100755
index 00000000..b9bdecba
--- /dev/null
+++ b/qtruby/rubylib/examples/ruboids/ruboids/ruboids.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby
+#
+# Copyright (c) 2001 by Jim Menard <jimm@io.com>
+#
+# Released under the same license as Ruby. See
+# http://www.ruby-lang.org/en/LICENSE.txt.
+#
+
+require 'Qt'
+require 'World'
+require 'WorldWindow'
+require 'Canvas'
+require 'Params'
+
+app = Qt::Application.new(ARGV)
+if (!Qt::GLFormat::hasOpenGL())
+ warning("This system has no OpenGL support. Exiting.")
+ exit -1
+end
+
+Params.readParamsFromFile(ARGV[0] || 'boids.properties')
+world = World.instance
+win = WorldWindow.new
+app.mainWidget = win
+
+World.instance.canvas = win.canvas
+win.show
+World.instance.start
+app.exec