I probably took too long to do this, but here it is. This program was written with
in mind (and I tried to keep it small), so I tried to clean it up some. I also made the program run a little faster. If it still doesn't work well on your PC, try setting the timer to a higher value, and/or remove the second jelly bean.
For those of you on *nix or slow PCs, this program generates 2 3D jelly beans (with face shading) that you can rotate with your mouse.
use Win32::API;
use Win32::GUI;
# Get the API calls we'll need to make
# BitBlt does a copy of one area of memory to another
$BitBlt = new Win32::API('gdi32.dll', 'BitBlt', [N,N,N,N,N,N,N,N,N], N
+);
# CreateCompatableBitmap gives us a place to draw
$compBit = new Win32::API('gdi32.dll', 'CreateCompatibleBitmap', [N,N,
+N], N);
# CreateCompatibleDC gives us a place to put our bitmap
$compDC = new Win32::API('gdi32.dll', 'CreateCompatibleDC', [N], N);
# Our window height and width
$width = 800;
$height = 800;
# Create our window
$win = new Win32::GUI::Window(-left=>0, -top=>0, -width=>$width, -heig
+ht=>$height, -name=>"window", -text=>"Jelly Beans");
# Add a timer
# Setting this to a higher number will slow down the rotation,
# but it will also help this program to run better on slower computers
$win->AddTimer("timer1", 100);
# Create our jelly bean
# This program uses quadrilaterals (most use triangles).
# Each point is made up of x,y,z coordinates.
# The last entry in each row defindes the base color for its quadrilat
+eral
@jellybeans = (
[[50,-10,15],[50,-10,-15],[60,-20,-15],[60,-20,15],0],
[[60,-20,15],[75,-20,15],[75,-20,-15],[60,-20,-15],0],
[[75,-20,15],[75,-20,-15],[85,-10,-20],[85,-10,20],0],
[[85,-10,20],[85,-10,-20],[100,-10,-20],[100,-10,20],0],
[[100,-10,20],[100,-10,-20],[110,-20,-15],[110,-20,15],0],
[[110,-20,15],[110,-20,-15],[125,-20,-15],[125,-20,15],0],
[[125,-20,15],[125,-20,-15],[135,-10,-15],[135,-10,15],0],
[[50,-10,15],[60,-20,15],[75,-20,15],[85,-10,20],0],
[[100,-10,20],[110,-20,15],[125,-20,15],[135,-10,15],0],
[[50,-10,-15],[60,-20,-15],[75,-20,-15],[85,-10,-20],0],
[[100,-10,-20],[110,-20,-15],[125,-20,-15],[135,-10,-15],0],
[[50,-10,15],[50,-10,-15],[45,0,-15],[45,0,15],0],
[[135,-10,15],[135,-10,-15],[140,0,-15],[140,0,15],0],
[[45,0,15],[50,-10,15],[85,-10,20],[85,0,20],0],
[[85,0,20],[85,-10,20],[100,-10,20],[100,0,20],0],
[[100,0,20],[100,-10,20],[135,-10,15],[140,0,15],0],
[[45,0,-15],[50,-10,-15],[85,-10,-20],[85,0,-20],0],
[[85,0,-20],[85,-10,-20],[100,-10,-20],[100,0,-20],0],
[[100,0,-20],[100,-10,-20],[135,-10,-15],[140,0,-15],0],
[[50,10,15],[50,10,-15],[45,0,-15],[45,0,15],0],
[[135,10,15],[135,10,-15],[140,0,-15],[140,0,15],0],
[[45,0,15],[50,10,15],[85,10,15],[85,0,20],0],
[[85,0,20],[85,10,15],[100,10,15],[100,0,20],0],
[[100,0,20],[100,10,15],[135,10,15],[140,0,15],0],
[[45,0,-15],[50,10,-15],[85,10,-15],[85,0,-20],0],
[[85,0,-20],[85,10,-15],[100,10,-15],[100,0,-20],0],
[[100,0,-20],[100,10,-15],[135,10,-15],[140,0,-15],0],
[[50,10,15],[60,20,10],[85,20,10],[85,10,15],0],
[[85,20,10],[85,10,15],[100,10,15],[100,20,10],0],
[[100,10,15],[100,20,10],[125,20,10],[135,10,15],0],
[[50,10,-15],[60,20,-10],[85,20,-10],[85,10,-15],0],
[[85,20,-10],[85,10,-15],[100,10,-15],[100,20,-10],0],
[[100,10,-15],[100,20,-10],[125,20,-10],[135,10,-15],0],
[[50,10,15],[50,10,-15],[60,20,-10],[60,20,10],0],
[[60,20,10],[60,20,-10],[85,20,-10],[85,20,10],0],
[[85,20,10],[85,20,-10],[100,20,-10],[100,20,10],0],
[[100,20,10],[100,20,-10],[125,20,-10],[125,20,10],0],
[[125,20,10],[125,20,-10],[135,10,-15],[135,10,15],0]
);
# Duplicate the Jellybean
# Shift it -95 on the x axis to center it on the x axis (for the rotat
+ion)
# Give it a different base color
my @jellybeans2;
for my $count (0..$#jellybeans){
my @tpoint;
for my $count2 (0..3){
push @tpoint,[$jellybeans[$count][$count2][0]-95,$jellybeans[$
+count][$count2][1],$jellybeans[$count][$count2][2]];
}
push @tpoint,1;
push @jellybeans2,\@tpoint;
}
# Rotate the duplicate jelly bean
# Shift it -95 on the x axis to take it off the center of the x axis,
# and to separate it from the other jellybean
@rotation = (2.093,1.099,3.838);
for my $count (0..$#jellybeans2){
for $x(0..$#{$jellybeans2[$count]}){
rotate ($jellybeans2[$count][$x]);
$jellybeans2[$count][$x][0]-=95;
}
}
# Add the duplicate Jellybean coordinates to the origional
for my $tmp (@jellybeans2){
push @jellybeans, $tmp;
}
# Set offsets
$dist = 256;
$xoffset = 400;
$yoffset = 400;
# Get initial mouse position
($mousex, $mousey) = Win32::GUI::GetCursorPos();
# Show our window
$win->Show();
# get the DC for our window
$dc=$win->GetDC;
# Now for some fun with OO
# Create a hash to fool Win32::GUI, by puting a bitmap handle in the -
+handle key
# Create a bitmap to use
$cbm = {"-handle"=>$compBit->Call($$dc{-handle}, $width,$height)};
# Create a Win32::GUI::DC object,
# Create a compatable DC and put its handle in -handle
$cdc = bless ({-handle=>$compDC->Call($$dc{-handle})}, "Win32::GUI::DC
+");
# Select the bitmap
$cdc->SelectObject($cbm);
# Start the Dialog sub
Win32::GUI::Dialog();
# draw is called with no arguments,
sub draw {
# Get the mouse position
my ($newx,$newy) = Win32::GUI::GetCursorPos();
# Create a rotation based on how much the mouse was moved
@rotation = (($mousey-$newy)*.0174,($newx-$mousex)*.0174,.0174);
# Save the new mouse coordinates
$mousex = $newx;
$mousey = $newy;
# Rotate the jellybeans, and create an array of x,y points with pe
+rspective
for my $count (0..$#jellybeans){
for my $count2(0..$#{$jellybeans[$count]}){
rotate($jellybeans[$count][$count2]);
$pjellybeans[$count][$count2] = perspective($jellybeans[$c
+ount][$count2]);
}
# Copy the color code
$pjellybeans[$count][4] = $jellybeans[$count][4];
# Add the z axises together to get a value to sort by (and do
+shading)
$pjellybeans[$count][0][2] = $pjellybeans[$count][0][2]+$pjell
+ybeans[$count][1][2]+$pjellybeans[$count][2][2]+$pjellybeans[$count][
+3][2];
}
# Sort each quadrilateral, so the quadrilaterals are drawn from fa
+rthest to nearest.
@pjellybeans = sort{$$b[0][2]<=>$$a[0][2]}@pjellybeans;
# Draw a black rectangle
$pen = new Win32::GUI::Pen(-color=>[0,0,0]);
$brush = new Win32::GUI::Brush([0,0,0]);
Win32::GUI::DC::SelectObject($cdc, $pen);
Win32::GUI::DC::SelectObject($cdc, $brush);
Win32::GUI::DC::Rectangle($cdc, 0, 0, $width, $height);
# Draw each quadrilateral
for my $tquad (@pjellybeans){
# Do face shading
my $color = 255-(($$tquad[0][2]/6)+350);
my $color2 = 255;
my $color3;
$color=255 if ($color>255);
if ($color < 0){
$color2+=$color;
$color = 0;
$color2 = 0 if ($color2 < 0);
}
$color3 = $color-50;
if ($color3 < 0){
$color2+=$color3;
$color3 = 0;
$color2 = 0 if ($color2<0);
}
$color3 = $color2-25;
$color3 = 0 if ($color3 < 0);
# Check what the base color is and create the appropriate pen
+and brush
if ($$tquad[4]==1){
$pen = new Win32::GUI::Pen(-color=>[$color,$color3,$color]
+);
$brush = new Win32::GUI::Brush(-color=>[$color,$color3,$co
+lor]);
} else {
$pen = new Win32::GUI::Pen(-color=>[$color2,$color,$color]
+);
$brush = new Win32::GUI::Brush(-color=>[$color2,$color,$co
+lor]);
}
# Draw on our bitmap
Win32::GUI::DC::SelectObject($cdc, $pen);
Win32::GUI::DC::SelectObject($cdc, $brush);
Win32::GUI::DC::BeginPath($cdc);
Win32::GUI::DC::MoveTo($cdc, $$tquad[0][0], $$tquad[0][1]);
Win32::GUI::DC::LineTo($cdc, $$tquad[1][0], $$tquad[1][1]);
Win32::GUI::DC::LineTo($cdc, $$tquad[2][0], $$tquad[2][1]);
Win32::GUI::DC::LineTo($cdc, $$tquad[3][0], $$tquad[3][1]);
Win32::GUI::DC::LineTo($cdc, $$tquad[0][0], $$tquad[0][1]);
Win32::GUI::DC::EndPath($cdc);
Win32::GUI::DC::StrokeAndFillPath($cdc);
}
# Do a BitBlt to copy everything from our bitmap to the screen
Win32::API::Call($BitBlt, $$dc{-handle}, 0, 0, $width, $height, $$
+cdc{-handle}, 0, 0, 0xcc0020);
}
# perspective takes an array referance for a point with x,y,z coordina
+tes
# and returns an array referance for screen x,y coordinates (z is also
+ passed to allow sorting).
sub perspective {
my $point = shift;
my @tpoint;
my $tdist = $dist+$$point[2];
$t = 1 unless $t; # Avoid division by 0
$tpoint[0] = $$point[0]*$dist/$tdist+$xoffset;
$tpoint[1] = $$point[1]*$dist/$tdist+$yoffset;
$tpoint[2] = $$point[2];
return \@tpoint;
}
# rotate uses the global array @rotation for its x,y,z rotation amount
+s (in radians)
# rotate takes a referance to an array containing x,y,z coordinates
# Nothing is returned as rotate modifies the referance directly
# For more on how this works, look at any 3D tutorial
sub rotate {
my $point = shift;
my @tpoint;
$tpoint[0] = cos($rotation[2])*$$point[0]-sin($rotation[2])*$$poin
+t[1];
$tpoint[1] = sin($rotation[2])*$$point[0]+cos($rotation[2])*$$poin
+t[1];
$tpoint[2] = $$point[2];
$$point[0] = cos($rotation[1])*$tpoint[0]-sin($rotation[1])*$tpoin
+t[2];
$$point[1] = $tpoint[1];
$$point[2] = sin($rotation[1])*$tpoint[0]+cos($rotation[1])*$tpoin
+t[2];
$tpoint[0] = $$point[0];
$tpoint[1] = sin($rotation[0])*$$point[2]+cos($rotation[0])*$$poin
+t[1];
$tpoint[2] = cos($rotation[0])*$$point[2]-sin($rotation[0])*$$poin
+t[1];
for (0..2){
$$point[$_] = $tpoint[$_];
}
}
# timer1_Timer is called when the timer expires, it only calls draw
sub timer1_Timer{
draw();
}
# window_Terminate is called when the window is closed
sub window_Terminate {
return -1;
}