The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

# object oriented programming example
#
# Example developed entirely by Stan Melax (stan@arc.ab.ca).
# I wanted to try some object-oriented programming in perl,
# but I wanted to do something more real than just a "foo-bar" example :-)
#
# I developed the smooth "chaseing" behavior used by the spaceship
# years ago in grad school for a VR assignment. When Randy Pausch
# (a VR God from U of Virginia) visited the U of Alberta, he was
# impressed and took a copy of the algorithm.
use OpenGL;
sub abs{
(($_[0]>0)?$_[0]:-$_[0]);
}
$cow=1;
$plane=2;
$floor=3;
$enterprise=4;
sub initlists{
glNewList($cow,GL_COMPILE);
glColor3f(1.0,0.0,0.0);
glBegin(GL_POLYGON);
glNormal3f(-1.0, 0.0, 0.0);
glVertex3f(-1.0 , -1.0, -1.0);
glVertex3f(-1.0 , 1.0, -1.0);
glVertex3f(-1.0 , 1.0, 1.0);
glVertex3f(-1.0 , -1.0, 1.0);
glEnd();
glColor3f(0.0,1.0,0.0);
glBegin(GL_POLYGON);
glNormal3f( 1.0, 0.0, 0.0);
glVertex3f( 1.0 , -1.0, -1.0);
glVertex3f( 1.0 , 1.0, -1.0);
glVertex3f( 1.0 , 1.0, 1.0);
glVertex3f( 1.0 , -1.0, 1.0);
glEnd();
glEndList();
glNewList($plane,GL_COMPILE);
glColor3f(0,0,0);
glBegin(GL_POLYGON);
glVertex3f( 1 , 2, 1.0);
glVertex3f( 2 , 1, 1.0);
glVertex3f( 2 , -1, 1.0);
glVertex3f( 1 , -2, 1.0);
glVertex3f( -1 , -2, 1.0);
glVertex3f( -2 , -1, 1.0);
glVertex3f( -2 , 1, 1.0);
glVertex3f( -1 , 2, 1.0);
glEnd();
glColor3f(1.0,0.0,1.0);
glBegin(GL_POLYGON);
glVertex3f( 1 , 2, 3.0);
glVertex3f( 2 , 1, 3.0);
glVertex3f( 2 , -1, 3.0);
glVertex3f( 1 , -2, 3.0);
glVertex3f( -1 , -2, 3.0);
glVertex3f( -2 , -1, 3.0);
glVertex3f( -2 , 1, 3.0);
glVertex3f( -1 , 2, 3.0);
glEnd();
glEndList();
glNewList($floor,GL_COMPILE);
for($i=0;$i<10;$i++) {
for($j=0;$j<10;$j++) {
$b = 1-sqrt(($i-4.5)*($i-4.5)+($j-4.5)*($j-4.5))/6.4;
glColor3f($b*0.5,(($i%2) ^ ($j%2) )?0.7*$b : 0 ,0);
glBegin(GL_POLYGON);
glVertex3f( (0+$i-5)*10 , (0+$j-5) *10, 0.0);
glVertex3f( (1+$i-5)*10 , (0+$j-5) *10, 0.0);
glVertex3f( (1+$i-5)*10 , (1+$j-5) *10, 0.0);
glVertex3f( (0+$i-5)*10 , (1+$j-5) *10, 0.0);
glEnd();
}
}
glEndList();
}
sub readnff{
my $file = 'spaceship.nff';
$file = "examples/$file" unless -r $file;
open(FILE,"<$file") || die "cant open $file";
$_="";
while(!(/^82\s*$/)){$_=<FILE>;}
$n=82;
for($i=0;$i<$n;$i++) {
$_=<FILE>;
/(\S+)\s+(\S+)\s+(\S+)\s*$/ || die "couldn't parse file";
($x[$i],$y[$i],$z[$i])=($1,$2,$3);
}
$_=<FILE>;
/^140/ || die "couldn't parse file";
$p=140;
glNewList($enterprise,GL_COMPILE);
glScalef(0.02,0.02,0.02);
glRotatef(90.0,0.0,0.0,1.0);
glColor3f(0.0,1.0,1.0);
for($i=0;$i<$p;$i++) {
glBegin(GL_POLYGON);
$_=<FILE>;
/3\s+(\d+)\s+(\d+)\s+(\d+)\s/ || die "couldn't parse file";
glVertex3f($x[$1],$y[$1],$z[$1]);
glVertex3f($x[$2],$y[$2],$z[$2]);
glVertex3f($x[$3],$y[$3],$z[$3]);
glEnd();
}
close(FILE);
glEndList();
$enterprise;
}
package A;
use OpenGL;
%defaults = (
'name' => 'unnamed',
'x' => 0 ,
'y' => 0 ,
'z' => 0 ,
'dl'=> $main::plane,
);
sub initialize {
my $self=shift;
local %v = @_;
foreach $k (keys(%v)) {
$self->{$k} = $v{$k};
}
$self;
}
sub print{
my $self=shift;
print "\tObject '",$self->{'name'},"' is a '",ref($self),"'\n";
foreach $k (sort keys(%$self)) {
print("\t\t$k\t$self->{$k}\n") if($k cmp 'name');
}
print "\n";
}
sub new {
my $type = shift;
my $self = {};
initialize($self,%defaults);
initialize($self,@_);
push(@objects,$self);
bless $self;
}
sub move {
}
sub draw {
my $self=shift;
glPushMatrix();
glTranslatef($self->{'x'},$self->{'y'},$self->{'z'});
glCallList($self->{'dl'});
glPopMatrix ();
}
#------------------------------------------
package B;
@ISA=qw(A);
%defaults = (
'dx',0,
'dy',0,
'dz',0,
);
sub new {
my $type = shift;
my $self = A->new(%defaults,@_);
push(@objects,$self);
bless $self;
}
$WORLD_BOUNDARY =(20.0);
$WORLD_CEILING =(10.0);
$WORLD_FLOOR =(0.0);
sub move {
my $self = shift;
local($x,$y,$z,$dx,$dy,$dz)=
(\$self->{'x'}, \$self->{'y'}, \$self->{'z'},
\$self->{'dx'},\$self->{'dy'},\$self->{'dz'});
$$x+=$$dx;
$$y+=$$dy;
$$z+=$$dz;
# Bounce off Walls
($$x> $WORLD_BOUNDARY) && ($$dx= -abs($$dx));
($$x< -$WORLD_BOUNDARY) && ($$dx= abs($$dx));
($$y> $WORLD_BOUNDARY) && ($$dy= -abs($$dy));
($$y< -$WORLD_BOUNDARY) && ($$dy= abs($$dy));
($$z> $WORLD_CEILING ) && ($$dz= -abs($$dz));
($$z< $WORLD_FLOOR ) && ($$dz= abs($$dz));
}
#------------------------------------------
package T;
@ISA=qw(B);
%defaults = (
'name','target',
'wait',0,
);
$damping = 0.99;
sub new {
my $type = shift;
my $self = B->new(%defaults,@_);
push(@objects,$self);
bless $self;
}
sub move {
my $self = shift;
$self->{'dx'} *= $damping;
$self->{'dy'} *= $damping;
$self->{'wait'}-- if($self->{'wait'}>0) ;
$self->B::move;
}
sub takeoff {
my $self = shift;
#print "takeoff\n";
return if($self->{'wait'}>0) ;
$self->{'dx'} = rand(2)-1.0;
$self->{'dy'} = rand(2)-1.0;
$self->{'wait'}=10;
}
#------------------------------------------
package C;
@ISA=qw(B);
use OpenGL;
%defaults = (
'target' => 0,
'h' => 0,
'dh' => 0,
);
sub new {
my $type = shift;
my $self = B->new(%defaults,@_);
push(@objects,$self);
bless $self;
}
$DAMPING = (0.98);
$ACCELLERATION = (0.01);
$BANK_DAMPING = (0.8);
sub move{
my $self = shift;
local($x,$y,$z,$dx,$dy,$dz,$h,$dh)=
(\$self->{'x'}, \$self->{'y'}, \$self->{'z'},
\$self->{'dx'},\$self->{'dy'},\$self->{'dz'},
\$self->{'h'},\$self->{'dh'});
local $t = $self->{'target'};
($t) || die "No Target\n";
local ($tx,$ty) = ($t->{'x'},$t->{'y'});
local $theading = atan2($ty-$$y,$tx-$$x);
local $speed = sqrt($$dx*$$dx+$$dy*$$dy);
$$h = ($$dy==0.0&&$$dx==0.0)? $$h : atan2($$dy,$$dx);
$tdheading = $theading-$$h;
while($tdheading > 3.14){ $tdheading -= 2*3.14};
while($tdheading <-3.14){ $tdheading += 2*3.14};
$$dh*= $BANK_DAMPING;
$$dh += $tdheading* 0.01;
$$h += $$dh;
if($tdheading <3.14/6 && $tdheading>-3.14/6 ) {
$speed += $ACCELLERATION;
}
elsif ($tdheading <3.14/3 && $tdheading>-3.14/3 ) {
$speed += $ACCELLERATION/4;
}
$$dx = cos($$h)*$speed;
$$dy = sin($$h)*$speed;
#if( (t%10==0) && tdheading <3.14/6 && tdheading>-3.14/6 ){
# # fire();
#}
# damping
$$dx *= $DAMPING;
$$dy *= $DAMPING;
$$dz *= $DAMPING;
#$self->B::move;
$$x+=$$dx;
$$y+=$$dy;
$$z+=$$dz;
$t->takeoff if($$x > $tx-5 && $$x < $tx+5 &&
$$y > $ty-5 && $$y < $ty+5 );
}
sub draw {
my $self=shift;
glPushMatrix();
glTranslatef($self->{'x'},$self->{'y'},$self->{'z'});
glRotatef($self->{'h'}*180/3.14, 0.0,0.0,1.0);
glRotatef(-60.0 * $self->{'dh'}/(1.0/(1.0-0.8)*0.01*3.14), 1.0, 0.0, 0.0);
glCallList($self->{'dl'});
glPopMatrix ();
}
#------------------------------------------
package main;
glpOpenWindow(width=>400,height=>400,
mask => StructureNotifyMask|KeyPressMask,
attributes=>[GLX_RGBA,GLX_DOUBLEBUFFER]);
glShadeModel (GL_FLAT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 500.0);
glMatrixMode(GL_MODELVIEW);
initlists;
glColor3f(1,0,0);
glClearColor(0,0,0.3,1);
A->new('dl'=>$floor,'name'=>'the happy floor');
#$a = A->new( 'y' => 0.2, 'name' => 'useless dude');
$b = T->new( 'x' => 10, 'dx'=> 0.4, 'name' => 'bad dude');
readnff;
$c = C->new('z' => 5,'dl'=>$enterprise, 'x' => -10, 'target'=>$b, 'name' => 'killer');
$w=100.0;$t= time;$p= $t-1;
$rin=$win=$ein='';
$spf = 1;
$spin=0;
$cb{&ConfigureNotify} = sub { local($e,$w,$h)=@_;glViewport(0,0,$w,$h);
# print "viewport $w,$h\n";
};
$cb{&KeyPress} = sub { # print "@_[1] ",ord(@_[1])," keypress @_\n";
local($ss); &$ss(@_[1]) if ($ss=$kcb{@_[1]}); };
$kcb{'q'} = $kcb{'Q'} = $kcb{"\033"} = sub{ print "Good-Bye\n"; exit 0;};
sub setspeed{$C::ACCELLERATION = $_[0]/100;}
foreach $i (0..9){
$kcb{"$i"}=\&setspeed;
}
$is_pm = OpenGL::_have_glp && ! OpenGL::_have_glx;
while(1){
$spf = ($spf*$w + $t-$p) /($w+1.0);
$fps = ($spf)?1.0/$spf:0;
$p=$t;
$t= time;
while($p=XPending) {
@e=&glpXNextEvent;
&$s(@e) if($s=$cb{$e[0]});
}
vec($rin,0,1) = 1;
if(!$is_pm && select($rout=$rin,undef,undef,0)) {
$_=<> || die "End Of File";
eval;
}
foreach $x (@A::objects) {
$x->move;
#$x->print;
}
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity ();
# set the viewpoint
glTranslatef (0.0, 0.0, -30.0);
glRotatef(-45.0, 1.0,0.0,0.0);
$spin += 1;
glRotatef($spin, 0.0,0.0,1.0);
foreach $x (@A::objects) {
$x->draw;
}
glFlush();
glXSwapBuffers;
}
__END__