Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine

A gift from me to you (unobfuscated)

by c-era (Curate)
on Apr 02, 2001 at 16:46 UTC ( #68983=CUFP: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
Re: A gift from me to you (unobfuscated)
by cLive ;-) (Prior) on Apr 03, 2001 at 10:04 UTC
    damn, I can't ++ it for another 52 minutes :)

    cLive ;-)

Holy jumping mother o' god in a sidecar with chocolate jimmies and a lobster bib!
by PsionicMan (Beadle) on Apr 05, 2001 at 01:52 UTC
    Man, I just used up all my votes... rest assured, this will get one tomorrow.

    VERY cool.

    Two minor things: Damn, that's slow. Of course, that's most likely more my machine than anything else... it's expected to be slow, of course, but it was reeeeeeeeeeaaaaaaaallllllly slow. That's what I get for using a 233... Of course, I do have a nice geforce card, so if you can work hardware t&l in there I'll be set. ;)

    And for whatever reasons (probably related to my slow computer, too) the beans first appear filled in, but are then wireframes... still, that's amazing.

    if only I could use up all my daily votes on one post...



      The combination of perl, unoptimized 3D and windows gdi leave something to be desired (speed). The reason you only see the wire frame, is that is all your cpu can render before timer is fired again. Try increasing the value for the timer, and you may get to see it filled in.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://68983]
Approved by root
[chacham]: i'm reading an article on and thee is a disturbing message on the bottom right that says, "Fork me on GitHub". Maybe i just have a dirty mind.
[choroba]: Check the ninja on

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (7)
As of 2017-08-17 12:56 GMT
Find Nodes?
    Voting Booth?
    Who is your favorite scientist and why?

    Results (287 votes). Check out past polls.