http://www.perlmonks.org?node_id=68983

I probably took too long to do this, but here it is. This program was written with obfuscation 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.

Some ways to make this faster:
1. Use DirectX
2. Use triangles
3. Use a look-up table for cos and sin
4. Put the program in a loop instead of using a timer
5. Use two arrays, one for the points, and the other with the indexes (less calculations)
6. Find a good 3D C library, and write an XS to use it ;)

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.

UPDATE: Since people are calling me an experiance whore, I posted this because someone had asked me too. If you look under A gift from me to you, a couple of people asked me to post this. I didn't think anyone was serious though. A week ago, crazyinsomniac /msg me asking when I would post the unobfuscated version. So I unobfuscated it and posted it here (with some minor improvements).

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; }