Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Re: Spatial Japh

by liverpole (Monsignor)
on Feb 12, 2006 at 21:48 UTC ( [id://529691]=note: print w/replies, xml ) Need Help??


in reply to Spatial Japh

Here's the original program, with a short explanation of how it works...

#!/usr/bin/perl -w # # # Notes for Perlmonks: # # 1. Runs in both Windows and Linux # 2. Terminal must be at least 120 wide by 50 tall # # Steps to making a 3D moving image: # # 1. Create a description of the polygons (defined by line segment +s) # 2. Project those lines into one screen "frame" # 3. Display the frame # 4. Zoom the polygons through the different planes on the Z axis # 5. Obfuscate the program # # Notes on the data encryption algorithm: # # 1. The first point is an absolute offset from the center of the +screen. # The subroutine decode_point() strips off the next character f +rom the # current set of polygons, turning it into an x or y point as n +eeded. # If the next character starts with one or more of '(' or ')', +the # value -32 or 31 is respectively added, to increase the range +for # starting points with are too far from the center. Otherwise, + each # x or y value can be a value between -32 (ord('*') - 74) and 4 +8 # (ord('z') - 74). # # 2. The second point (and successive ones) for each polygon is ca +lculated # relative to the previous point. If the difference between th +e x and # y pairs is each within the range (-4 ... 4), a single charact +er is # used to encode both x and y of the new point. Otherwise, a ' +*' is # used as an 'escape' to denote that the next 2 characters will + be used # (as in step 1) to signify a larger delta distance. # # Strict use warnings; # Warnings can remain in the final version, but 'stric +t' use strict; # will unfortunately have to be dropped, since most of + the # 'my' variables will be replaced by globals (to save +space). # # Globals # my ($minx, $maxx) = (-59, 59); my ($miny, $maxy) = (-23, 23); my $pscreen = {}; my $pixel; my $lens; my $zoom; my $zoom_factor; # # Set $is_windows to 0 or 1 based on existence of the Win32::Console +::ANSI # module, which is only needed for (and available under) Windows. # my $is_windows = eval { require Win32::Console::ANSI }; my $escape = $is_windows ? '1;7;' : '0'; # # Definitions of the polygons for each letter (and the final camel). + Each # animated letter is defined by a polygon. That polygon is a startin +g point # relative to the center of the screen, followed by a set of points r +elative # to the last. # my ($j, $u, $s, $t, $a, $n, $o, $h, $c, $k, $p, $e, $r, $l, $camel) = +split(/}/, 'Tw*JSK8IAg*PJ[*J@wR}*JR]*QJ[*JBA*JQK8I*JC}KUz]BAIJT]*QJ[R +?-R[e]\\RI}Tn*JQ]wRAI*JDnR8QAU}wT8KT]n*JEI*EJR*QJ]*JR*DJ@IQ[}*JSe*JD[ +n]*JPe*JBI/KI}T8@?PcdnfgVCBRcP?ABKV]]}*JWe*JD[n]*JPe*JC?8B*JE};Vq*OJQ +/IP[wQ}*JWeOe{n*EERk8;J*JC}/U*OJd[OI@*BJ*JXn*J>w]U}CWq*OJc8KJ?O[e]U/T +*QJP?}*JSe*JCnTeQIAKJR}*JV]wRAI*J?}T]*RJcJI[\\]3;U]Uq*PM[wV]W]WCT*DM* +SJZP[ZPZa[\\]UKVgogK9K*QJ[\\]n[RI@*EH@IddR[Q[]T]T]T3o[dk*JE[Z\\U{T]*J +PKTKK]*OJ[QIO[PIQIO[[gUKU\\k*JE+J+J5R5AI*EJ00BCB*DMKKJIR[Q+*EJ0*EK'); # # Given a reference to the current shape (polyogon), extracts and ret +urns the # next (x,y) coordinate point. # sub decode_point (\$) { my ($P) = @_; my $V = 0; while ($$P =~ s/^([()])//x) { $V += '(' eq $1 ? -32 : 31; } $V += ord(substr $$P, 0, 1, '') - 74; } # # Given two points (x0,y0) and (x1,y1), uses a very simple algorithm # to construct a line between those points. # sub create_line { my ($x0, $y0, $x1, $y1) = @_; $x0 = int $x0 * $lens / $zoom_factor; $y0 = int $y0 * $lens / $zoom_factor; $x1 = int $x1 * $lens / $zoom_factor; $y1 = int $y1 * $lens / $zoom_factor; my $G = $x1 - $x0; my $F = $y1 - $y0; my $E = abs $G >= abs $F ? $G : $F; ($x0, $y0) = ($x1, $y1) if $E < 0; $E ||= 0.01; for (my $i = 0; $i <= abs $E; ++$i) { $$pscreen{$y0 + int($i * $F / $E)}{$x0 + int($i * $G / $E)} = +1; } } # # Given a set of points representing a single polygon, calculates and + draws # all of its lines "into" the current frame, and displays the frame. # sub create_polygons { my (@points) = @_; $pscreen = {}; $zoom_factor = $zoom || 0.01; my ($nextx, $nexty); map { my $polygon = $_; my $x0 = my $x1 = decode_point($polygon); my $y0 = my $y1 = decode_point($polygon); while ($polygon) { my $q = substr($polygon, 0, 1, ''); if ($q eq '*') { # Special case -- next (x,y) point is encoded # into 2 distinct characters # $nextx = decode_point($polygon); $nexty = decode_point($polygon); } else { # Usual case -- convert a single character into an (x, +y) point # relative to the previous point, where the absolute v +alue of # the delta x or y values is a maximum of 4. # $q = ord($q) - 43; $nexty = $q % 9; $nextx = ($q - $nexty) / 9; $nexty = $q - 9 * $nextx - 4; $nextx -= 4; } $nextx += $x0; $nexty += $y0; create_line($x0, $y0, $nextx, $nexty); ($x0, $y0) = ($nextx, $nexty); } # Reconnect the final point with the first point create_line($x0, $y0, $x1, $y1); } @points; # Render the frame my $frame; map { my $y = $_; map { $frame .= $$pscreen{$y}{$_} ? $pixel : ' ' } ($minx..$maxx); $frame .= "\n" } ($miny..$maxy); # Display the next frame print "\e[H$frame"; } # # Animate each of the "words" (plus a final camel's image). A random # "pixel" is chosen for all of the lines, as well as a random color. # sub animate { # Choose one of 4 random pixels $pixel = ('%', '&', '@', 'x')[int rand 4]; map { my ($speed, $acceleration, $nseconds, $reverse_zoom) = @_; my @points; ($lens, @points) = split(/}/, $_); printf "\e[$escape;%dm", int(rand 6) + 101 - 60 * ($is_windows + || 0); $is_windows and system("cls"); # # Set the Z coordinate to values from 256 to 0, varying by $sp +eed. # This causes the object to 'zoom' towards us, as the 'lens' w +hich # is our terminal screen is at (z=0). # for ($zoom = 256; $zoom > 0; $zoom -= $speed) { $speed *= $acceleration; create_polygons(@points); } sleep $nseconds; while ($reverse_zoom and ($zoom += $speed) <= 256) { create_polygons(@points); } } ( "32}7D$j}AG$u}OG$s}WG$t", "24}(IJ$a}1G$n}CO$o}GG$t}QC$h}^G$e})IG$r", "32}?H$p}FG$e}QG$r}ZC$l", "28}(LC$h}:J$a}EG$c}MC$k}ZG$e}dG$r", "18}(D;$camel" ); } while (1) { animate(16, 1, 1, 0); animate(8, 0.98, 0, 0); animate(16, 1, 1, 1); animate(8, 0.98, 0, 1); }
Each word ("Just", "another", "Perl", "hacker") is made up of polygons, where the final point is automatically reconnected to the starting point. The final "camel" is a single, huge polygon.  Each polygon is encoded into a string of ascii characters. The first (x,y) point is saved as an offset relative to the center of the screen, and successive points are calculated as the relative (x,y) distance from the previous point.

When the animate function is called, it cycles through each set of polygons, decoding the ascii characters into the individual points which represent each line of each polygon.  For each plane of the Z-axis (representing distance away from the viewer), the (x,y) points are all scaled to conform to that distance.  Once all the lines have been packed into the frame, the frame is displayed, and causes the word to appear as though it is zooming closer or farther away from the viewer.  I found that I had to "slow down" the characters if they were zooming "past" the viewer, otherwise they were too hard to read (hence the "acceleration" variable).

Originally, the data for the points was almost twice as large as the final version, and I wanted to compress it further, so I used a mechanism whereby each ascii character represented a value from 0 to 80, for a total of 81 combinations, which, for any point of a polygon which was not the first, could represent both the x and y value, by taking the delta-x and delta-y with the previous (x,y) point, as long as both deltas were within the range (-4 ... 4). (Since the full range of 9 values, squared, equals 81). If this were not the case for any given point, the ascii character '*' was used as an 'escape' character to revert to using the next 2 ascii characters to encode the point.

It was a lot of fun learning how to create a simple 3D animation.  I hope you enjoyed watching it!


@ARGV=split//,"/:L"; map{print substr crypt($_,ord pop),2,3}qw"PerlyouC READPIPE provides"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://529691]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2024-04-16 06:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found