#!/usr/bin/env python ### hilbert_pic.py -- 3D wireframe hilbert curve # Steve Witham ess doubleyou at tiac remove-this dot net. # http://www.tiac.net/~sw/2008/10/Hilbert from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter, A4 from reportlab.lib.units import inch from reportlab.lib import colors # from arrowline import arrowLine from os import system from sys import argv from math import sqrt from hilbert import int_to_Hilbert, Hilbert_to_int # unit_vector -- Unit vector in the same direction as ( dx, dy ), # or (0,0) if given (0,0). def unit_vector( dx, dy ): d = sqrt( dx*dx + dy*dy ) if d == 0: return 0, 0 else: return dx / d, dy / d def draw_hilbert_pic( c, size ): nD = 3 # Choose some colors... c.setStrokeColor( colors.black ) # ...as long as they're black. c.setFillColor( colors.black ) # Fill color is used for text. point = 1 # The pdf default c.setFont("Helvetica", 14 * point ) c.drawString( 3 * inch, 9 * inch, "%d x %d x %d Hilbert Walk" % ( size, size, size ) ) # Set the origin: c.translate( 1.25 * inch, 2.5 * inch ) twosies = 4.125 def stretch( n ): # Stretch numbers in a fractal way: mult = 1 result = 0 while n > 0: result = result + ( n % 2 ) * mult n /= 2 mult *= twosies return result # Map from 3D ( x, y, z ) => paper ( x, y ) using complex numbers: projection = [ 2.+0.j, 0+2.j, 1.+.67j ] def project( xyz ): pt = 0+0j for d in range( 3 ): pt += stretch( xyz[d] ) * projection[d] return ( pt.real, pt.imag ) # Scale it... # NOTE: THIS MEANS POINT != 1 FOR LINE WIDTHS AND FONTS. farcorner_x, farcorner_y = project( ( size-1, size-1, size-1 ) ) stepsize = 6. * inch / farcorner_x c.scale( stepsize, stepsize ) point /= ( stepsize ) # Collect 3D lines in a list: lines = [] prev_xyz = int_to_Hilbert( 0, 3 ) for i in range( 1, size ** 3 ): pt_xyz = int_to_Hilbert( i, 3 ) lines.append( ( prev_xyz, pt_xyz ) ) prev_xyz = pt_xyz width = 48 * point / farcorner_x shadow_width = width * 3 def avg_z( line ): start, end = line return( ( start[2] + end[2] ) * .5 ) def highest_avg_z_first( line1, line2 ): return -cmp( avg_z( line1 ), avg_z( line2 ) ) # Display furthest-back-first, with white "shadows": lines.sort( highest_avg_z_first ) for line in lines: x0, y0 = project( line[0] ) x1, y1 = project( line[1] ) dx, dy = unit_vector( x1-x0, y1-y0 ) # The shadow is a little shorter than the black line: c.setLineWidth( shadow_width ) c.setStrokeColor( colors.white ) c.line( x0 + dx * shadow_width, y0 + dy * shadow_width, x1 - dx * shadow_width, y1 - dy * shadow_width ) # Now the actual line: c.setLineWidth( width ) c.setStrokeColor( colors.black ) c.line( x0, y0, x1, y1 ) c.showPage() output_file = "hilbert_pic.pdf" # pageCompression = 0 if I can look at the text in the output. # set to 1 in rl_config.py. c = canvas.Canvas( output_file, pagesize=letter ) width, height = letter #keep for later draw_hilbert_pic( c, 4 ) draw_hilbert_pic( c, 8 ) draw_hilbert_pic( c, 16 ) c.save() if len( argv ) > 1 and argv[1] == "--open": system( "open " + output_file )