#!/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 segments) # 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 from the # current set of polygons, turning it into an x or y point as needed. # 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 48 # (ord('z') - 74). # # 2. The second point (and successive ones) for each polygon is calculated # relative to the previous point. If the difference between the x and # y pairs is each within the range (-4 ... 4), a single character 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 'strict' 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 starting point # relative to the center of the screen, followed by a set of points relative # 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]*JPKTKK]*OJ[QIO[PIQIO[[gUKU\\k*JE+J+J5R5AI*EJ00BCB*DMKKJIR[Q+*EJ0*EK'); # # Given a reference to the current shape (polyogon), extracts and returns 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 value 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 $speed. # This causes the object to 'zoom' towards us, as the 'lens' which # 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); }