diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 90825e2392b2d70e43c7a25b8a3752299a933894 (patch) | |
tree | e33aa27f02b74604afbfd0ea4f1cfca8833d882a /qtruby/rubylib/examples/ruboids | |
download | tdebindings-90825e2392b2d70e43c7a25b8a3752299a933894.tar.gz tdebindings-90825e2392b2d70e43c7a25b8a3752299a933894.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebindings@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'qtruby/rubylib/examples/ruboids')
25 files changed, 2163 insertions, 0 deletions
diff --git a/qtruby/rubylib/examples/ruboids/Manifest b/qtruby/rubylib/examples/ruboids/Manifest new file mode 100644 index 00000000..4fe3f97a --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/Manifest @@ -0,0 +1,26 @@ +Manifest +README +TODO +boids.properties +generateManifest.rb +index.html +release.rb +ruboids/ + Boid.rb + BoidView.rb + Camera.rb + CameraDialog.rb + Canvas.rb + Cloud.rb + CloudView.rb + Flock.rb + Graphics.rb + Params.rb + Point.rb + Thing.rb + Triangle.rb + View.rb + World.rb + WorldWindow.rb + info.rb + ruboids.rb diff --git a/qtruby/rubylib/examples/ruboids/README b/qtruby/rubylib/examples/ruboids/README new file mode 100644 index 00000000..5417037a --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/README @@ -0,0 +1,53 @@ +INTRODUCTION +============ + +RuBoids is a Boids simulation written in Ruby and using OpenGL and Qt. For +information on Boids, see http://www.red3d.com/cwr/boids/. + +Ruby is an object-oriented scripting language by Yukihiro Matsumoto. The +official Ruby Web site (http://www.ruby-lang.org/) contains information and +pointers to resources for this wonderful language. + +RuBoids is developed and maintained by Jim Menard (<jimm@io.com>). The +latest version of RuBoids can be found on the Ruby Application Archive or +on the official RuBoids Web page +(http://www.io.com/~jimm/downloads/ruboids/). + +RUNNING +======= + + cd ruboids + ruboids.rb + +RuBoids looks for an optional properties file. If none is specified on the +command line, it looks for a file named 'boids.properties' in the current +directory. For example, to load the example properties file in this +directory: + + cd ruboids + ruboids.rb ../boids.properties + +DEPENDENCIES +============ + +RuBoids requires the OpenGL and Qt packages, which can be found on the Ruby +Application Archive. + +COPYING +======= + +RuBoids is copyrighted free software by Jim Menard and is released under the +same license as Ruby. See the Ruby license +(http://www.ruby-lang.org/en/LICENSE.txt). + +RuBoids may be freely copied in its entirety providing this notice, all +source code, all documentation, and all other files are included. + +RuBoids is copyright (c) 2001 by Jim Menard. + +WARRANTY +======== + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff --git a/qtruby/rubylib/examples/ruboids/TODO b/qtruby/rubylib/examples/ruboids/TODO new file mode 100644 index 00000000..25fe501d --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/TODO @@ -0,0 +1,29 @@ +BUGS +==== + +* Boid rotation. + +* Frustum. + +TO DO +===== + +* Comment the code. + +* More documentation. + +* Save/restore params (e.g. camera position). + +POSSIBLE ADDITIONS +================== + +* Make sphere output more efficient by using strips or fans. + +* Boids-eye view: camera follows position of a boid. This is not hard, but + I have to fix boid rotation first. + +* Velocity and destination influence proportional to distance from other + boids. Boids that are farther away have less influence on your velocity + and destination. + +* Boids that are behind you don't influence you because you can't see them. diff --git a/qtruby/rubylib/examples/ruboids/boids.properties b/qtruby/rubylib/examples/ruboids/boids.properties new file mode 100644 index 00000000..20bb5001 --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/boids.properties @@ -0,0 +1,33 @@ +# This is an example configuration file for RuBoids. It sets all of the +# possible properties. The values here are the default values set in +# Params.rb. + +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 diff --git a/qtruby/rubylib/examples/ruboids/generateManifest.rb b/qtruby/rubylib/examples/ruboids/generateManifest.rb new file mode 100755 index 00000000..f877b0b3 --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/generateManifest.rb @@ -0,0 +1,42 @@ +#! /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. +# +# This script builds the Manifest file. It can be run stand-alone, but +# is normally used from within release.rb. +# + +def recurseDirectory(io, dirName, indentLevel) + Dir.entries(dirName).sort.each { | f | + next if f =~ /^\.\.?/ + fileName = "#{dirName}/#{f}" + fileName.sub!(/^\.\//, '') + if File.directory?(fileName) + io.puts "\t" * indentLevel + fileName + '/' + recurseDirectory(io, fileName, indentLevel + 1) + else + io.puts "\t" * indentLevel + f + end + } +end + +def generateManifest + io = nil + begin + io = File.open('Manifest', 'w') + recurseDirectory(io, '.', 0) + ensure + io.close() if io + end +end + +if $0 == __FILE__ + generateManifest() +end + + + + diff --git a/qtruby/rubylib/examples/ruboids/index.html b/qtruby/rubylib/examples/ruboids/index.html new file mode 100644 index 00000000..9b320f8e --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/index.html @@ -0,0 +1,147 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> +<html> +<head> +<title>RuBoids</title> +<link rel="stylesheet" href="../../style.css" type="text/css"> +</head> +<body background="../../images/gradient_bg.gif" bgcolor="white"> +<!--#exec cgi="../../cgi-bin/log_visitor.cgi" --> + +<table width="100%"> + <tr> + <td valign="top"> + <table width="100" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td valign="top"><font size="-1"> +<script language="JavaScript"><!-- +PREFIX = "../../"; // --> +</script> +<!-- #include virtual="/~jimm/menu.js"--> +<A href="../../index.html"> +<IMG src="../../images/KeyMaster.gif" +width="32" height="32" alt="Home" border="0"></A><BR> +<A href="../../index.html">Home</A><BR> +<A href="../nqxml/index.html">NQXML</A><BR> +RuBoids<BR> +<A href="../../computers.html">Computers</A><BR> +<A href="../../java.html">Java</A><BR> +<A href="../../beos.html">BeOS</A><BR> +<A href="../../ftp_sites.html">FTP sites</A><BR> +<A href="../../music.html">Music</A><BR> +<A href="../../midi_ref.html">MIDI Reference</A><BR> +<A href="../../keymaster.html">KeyMaster</A><BR> +<A href="../../MIDI_Through.html">MIDI Through</A><BR> +<A href="../../jimm.html">Narcissism</A><BR> +<A href="../../resume.html">Resume</A><BR> +<A href="../../urls.html">Links</A><BR> +<A href="../../map.html">Site Map</A><BR> + + + </font></td> + </tr> + </table> + </td> + <td valign="top"> + <table border="0" cellpadding="0" cellspacing="0"> + <tr valign="top"> + <td> + +<div align="right"> +<font size="+3"><b>RuBoids</b></font> +<img src="../../images/computer.gif" width="32" height="32" + alt="[home]" border="0"> +</div><br> +<hr> + +<h1>Introduction</h1> + +<p> +RuBoids is a Boids simulation written in Ruby and using OpenGL and Qt. For +information on Boids, see <a +href="http://www.red3d.com/cwr/boids/">http://www.red3d.com/cwr/boids/</a>. +</p> + +<p> +Ruby is an object-oriented scripting language by Yukihiro Matsumoto. Visit +the official <a href="http://www.ruby-lang.org/">Ruby Web site</a> for more +information. +</p> + +<p> +</p> + +<p> +RuBoids is developed and maintained by Jim Menard, <a +href="mailto:jimm@io.com">jimm@io.com</a>. The official +Web page of RuBoids is <a +href="http://www.io.com/~jimm/downloads/RuBoids/">http://www.io.com/~jimm/downloads/RuBoids/</a>, +where the latest release may be found. +</p> + + +<h1>Dependencies</h1> + +<p> +RuBoids requires the OpenGL and Qt packages, which can be found on the <a +href="html://www.ruby-lang.org/en/raa.html">Ruby Application Archive</a>. +</p> + + +<h1>Download</h1> + +<p> +Download the latest version, <a href="ruboids-0.0.1.tar.gz">RuBoids version +0.0.1</a>. +</p> + +<!-- +<p> +Earlier versions: +</p> + +<ul> +<li><a href="RuBoids-0.0.1.tar.gz">Version 0.0.1</a></li> +</ul> +--> + +<h1>Bugs</h1> + +<p> +Boids don't rotate properly. In other words, they point in the wrong +direction. +</p> + + +<h1>Copying</h1> + +<p> +RuBoids is copyrighted free software by Jim Menard and is released under +the same license as Ruby. +</p> + +<h1>Warranty</h1> + +<p> +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +</p> + +<hr> +Back to my <a href="../../index.html">home page</a>, or the +<a href="../../map.html">Site Map</a>.<br> +<font size="-1"> +<i>Page last modified on +<!--#config timefmt="%B %d, %Y" --> +<!--#echo var="LAST_MODIFIED" --> +by <a href="mailto:jimm@io.com">me</a>.</i> +<br>Contents © 2001 by Jim Menard. All rights reserved. +</font> +</td> +</tr> +</table> +</td> +</tr> +</table> +</body> +</html> diff --git a/qtruby/rubylib/examples/ruboids/release.rb b/qtruby/rubylib/examples/ruboids/release.rb new file mode 100755 index 00000000..d82ba154 --- /dev/null +++ b/qtruby/rubylib/examples/ruboids/release.rb @@ -0,0 +1,152 @@ +#! /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. +# +# This script gathers everything needed to release RuBoids into one directory +# and, if requested, publishes the contents to the RuBoids Web site. +# +# usage: +# +# release.rb [--publish, -p] +# +# Specifying --publish or -p causes the resulting files to be published +# to the Web site. +# + +require 'net/ftp' +require 'ftools' # For makedirs and install +require 'generateManifest' # For--you guessed it--generating the Manifest + +# Start looking for RUBOIDS classes in this directory. +# This forces us to use the local copy of RUBOIDS, even if there is +# a previously installed version out there somewhere. +$LOAD_PATH[0, 0] = '.' + +require 'ruboids/info' # For Version string + +FILE_PERMISSION = 0644 +DIR_PERMISSION = 0755 + +PUBLISH_FLAG = '-p' +RUBOIDS_DIR = 'ruboids' +DOCS_DIR = '.' +#DOCS_HTML_DIR = "#{DOCS_DIR}/README" + +RUBOIDS_DIR_WITH_VERSION = "#{RUBOIDS_DIR}-#{Version}" +RELEASE_DIR = "/tmp/#{RUBOIDS_DIR_WITH_VERSION}_release" +#RELEASE_HTML_DIR = "#{RELEASE_DIR}/README" + +DOWNLOAD_FILE = "#{DOCS_DIR}/index.html" +#DOCBOOK_FILE = "#{DOCS_DIR}/README.sgml" + +WEB_SITE = 'io.com' +WEB_DIR = 'public-web/downloads/ruboids' + +# Copies all files from `fromDir' into the release directory. Sets the +# permissions of all files to 0644. +def copyFiles(fromDir, toDir, match=nil) + Dir.foreach(fromDir) { | f | + next if f =~ /^\.\.?/ || (!match.nil? && !(f =~ match)) + File.install("#{fromDir}/#{f}", toDir, FILE_PERMISSION) + } +end + +# Recursively removes the contents of a directory. +def rmDirectory(dirName) + return unless File.exists?(dirName) + Dir.foreach(dirName) { | f | + next if f =~ /^\.\.?/ + path = "#{dirName}/#{f}" + rmDirectory(path) if File.directory?(path) + File.delete(path) if !File.directory?(path) + } + +end + +# Recursively sends files and directories. +def sendToWebSite(ftp, releaseDir, webDir) + ftp.chdir(webDir) + Dir.foreach(releaseDir) { | f | + next if f =~ /^\.\.?/ + path = "#{releaseDir}/#{f}" + if File.directory?(path) + begin + ftp.mkdir(f) + rescue Net::FTPPermError + # ignore; it's OK if the directory already exists + end + sendToWebSite(ftp, path, f) + ftp.chdir('..') + else + ftp.putbinaryfile(path, f) + end + } +end + +def ensureVersionInFile(fileName, regex) + lines = File.open(fileName).grep(regex) + found = lines.detect { | line | line =~ /#{Version}/o } + if !found + $stderr.puts "Warning: it looks like the #{fileName} version number" + + " is incorrect" + end +end + +# ================================================================ +# main +# ================================================================ + +# Make sure the docs mention the correct version number. +#ensureVersionInFile(DOWNLOAD_FILE, /Download the latest/) +#ensureVersionInFile(DOCBOOK_FILE, /releaseinfo/) + +# Empty release dir if it already exists. +rmDirectory(RELEASE_DIR) + +# (Re)create release dir. This makes RELEASE_HTML_DIR, whose parent +# is RELEASE_DIR. Therefore, RELEASE_DIR is created as well. +#File.makedirs(RELEASE_HTML_DIR) +File.makedirs(RELEASE_DIR) + +# Recreate the full documentation (creating README and docs/README) and copy +# the HTML files to the release directory. Finally, clean up the docs +# directory. + +#system("cd #{DOCS_DIR} && make") +#copyFiles(DOCS_DIR, RELEASE_DIR, /\.html$/) +#copyFiles(DOCS_HTML_DIR, RELEASE_HTML_DIR, /\.html$/) +copyFiles(DOCS_DIR, RELEASE_DIR, 'index.html') + +# Generate the Manifest file. +generateManifest() + +# Create .tar.gz file. We temporarily rename the RUBOIDS folder to +# "ruboids-X.Y.Z", tar and gzip that directory, then restore its original +# name. +Dir.chdir('..') +File.rename(RUBOIDS_DIR, RUBOIDS_DIR_WITH_VERSION) +system("tar -czf #{RELEASE_DIR}/#{RUBOIDS_DIR_WITH_VERSION}.tar.gz " + + RUBOIDS_DIR_WITH_VERSION) +File.chmod(FILE_PERMISSION, "#{RELEASE_DIR}/#{RUBOIDS_DIR_WITH_VERSION}.tar.gz") +File.rename(RUBOIDS_DIR_WITH_VERSION, RUBOIDS_DIR) + +# ftp files if requested +if !ARGV.empty? && ARGV[0] == PUBLISH_FLAG + require 'net/ftp' + + # Ask for ftp username and password + guess = ENV['LOGNAME'] || ENV['USER'] + print "username [#{guess}]: " + username = $stdin.gets().chomp() + username = guess if username.empty? + print "password: " + password = $stdin.gets().chomp() + + # ftp files to web site + ftp = Net::FTP.open(WEB_SITE, username, password) + sendToWebSite(ftp, RELEASE_DIR, WEB_DIR) + ftp.close() +end 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 |