1. NodeBox 1
    1. Homepage
    2. NodeBox 3Node-based app for generative design and data visualization
    3. NodeBox OpenGLHardware-accelerated cross-platform graphics library
    4. NodeBox 1Generate 2D visuals using Python code (Mac OS X only)
  2. Gallery
  3. Documentation
  4. Forum
  5. Blog

Hexagonal mousecurves

Posted by Karsten Wolf on Jun 19, 2010

I had to shorten this a bit. there seems to be a limit about 8k of what a post can hold.

Run this in full screen mode:

background(0)
# try to make size match the main display
try:
    import Foundation, AppKit
    s = AppKit.NSScreen.mainScreen()
    X,Y = s.frame().size
except ImportError:
    # duh...
    X = 1024.0
    Y = 768.0
size(X, Y)
 
import math
import random
import time
import pprint
 
dbg_frame = True
 
# sceen mid
x_half = X / 2
y_half = Y / 2
 
roffset = (X - Y) / 2
 
speed(0.2)
 
class FGPoint(object):
    def __init__(self, *args):
        if len(args) == 2:
            try:
                self.x = float(args[0])
                self.y = float(args[1])
            except:
                raise TypeError, "Wrong initializer for FGPoint object"
        else:
            self.x = self.y = 0.0
    def __repr__(self):
        return "FGPoint(%f, %f)" % (self.x, self.y)
    def __eq__(self, other):
        if checkpoint(other):
            return self.x == other.x and self.y == other.y
        else:
            return False
    def __ne__(self, other):
        return not self.__eq__(other)
    def __gt__(self, other):
        if checkpoint(other):
            return ((self.x > other.x) and (self.y > other.y))
        else:
            return False
    def __lt__(self, other):
        return not self.__gt__(other)
    def __le__(self, other):
        return ( self.__eq__(other) or self.__lt__(other) )
    def __ge__(self, other):
        return ( self.__eq__(other) or self.__gt__(other) )
    def __add__(self, other):
        t = type(other)
        if t == FGPoint:
            return FGPoint(self.x + other.x, self.y + other.y)
        elif t in (tuple, list) and len(other) == 2:
            return FGPoint(self.x + other[0], self.y + other[1])
        elif t in (long, int, float):
            # a numeric scalar
            v = float(other)
            return FGPoint(self.x + v, self.y + v)
        else:
            raise ValueError, "FGPoint addition error; value must be Point, FGPoint or number-like"
    def __sub__(self, other):
        return self.__add__(other.__neg__())
    def __mul__(self, other):
        t = type(other)
        if t == int or t == float:
            return FGPoint(self.x * other, self.y * other)
        elif t == FGPoint:
            return FGPoint(self.x * other.x, self.y * other.y)
        else:
            raise ValueError, "FGPoint multiplication error; value must be FGPoint or number-like"
    def __div__(self, other):
        t = type(other)
        if t == FGPoint:
            if other.x == 0.0:
                x = 0.0
            else:
                x = self.x / other.x
            if other.y == 0.0:
                y = 0.0
            else:
                y = self.y / other.y
            return FGPoint(x, y)
        elif t == float or t == int:
            other = float(other)
            if other != 0.0:
                return FGPoint(self.x / other, self.y / other)
            else:
                return FGPoint(0.0, 0.0)
        else:
            raise ValueError, "FGPoint multiplication error; value must be FGPoint or number-like"
    def __neg__(self):
        return FGPoint(-self.x, -self.y)
    def as_list(self):
        return [self.x, self.y]
    def as_tuple(self):
        return (self.x, self.y)
class FGHexagon(object):
    """Hexagon class"""
    def __init__(self, p, r):
        """p center point, r radius"""
        r30 = math.radians(30)
        c30 = math.cos( r30 )
        s30 = math.sin( r30 )
        t = type(p)
        if t == FGPoint:
            self.p = p
        elif t in (tuple, list) and len(p) == 2:
            self.p = FGPoint(p[0], p[1])
        else:
            raise ValueError, "FGHexagon initialisation error."
        self.r = r
        self.s = r * c30
        self.points = [
            self.p + (self.r, 0),
            self.p + ( self.r / 2, -self.s),
            self.p + (-self.r / 2, -self.s),
            self.p + (-self.r, 0),
            self.p + (-self.r / 2,  self.s),
            self.p + ( self.r / 2,  self.s)]
    def as_tuples(self):
        p = self.points
        l = [ pt.as_tuple() for pt in p]
        return tuple(l)
    def as_triangles(self):
        return [
            (self.p, self.points[0], self.points[1]),
            (self.p, self.points[1], self.points[2]),
            (self.p, self.points[2], self.points[3]),
            (self.p, self.points[3], self.points[4]),
            (self.p, self.points[4], self.points[5]),
            (self.p, self.points[5], self.points[0])]
    def __repr__(self):
        s = "FGHexagon(p=%s, r=%.2f)"
        return s % (sepf.p, self.r)
 
def calculatePoints(points, smallerscale, steps):
    """Create a mousecurve from a list of points
 
    Iterate steps times and each point moves with smallerscale
    to he next point.
    """
 
    result = []
 
    # work with a copy because it's overwritten in the loop
    pointList = points[:]
    n = len(pointList)
 
    for k in range(steps):
 
        # a new polygon type thingy
        # this should change; return a list of point list
        # that None stopmark made drawPoints() messy
        result.append( None )
 
        nextLevel = []
        for i,p in enumerate(pointList):
 
            # cyclic nextPoint
            # if it's the last point, next one is first point
            if 0 <= i < n-1:
                nextPoint = pointList[ i+1 ]
            elif i == n-1:
                nextPoint = pointList[ 0 ]
            else:
                # should never happen
                print "BOGEY",i,n
 
            # line curPoint, nextPoint
            result.append( nextPoint )
 
            # FGPoint made this very nice...
            scaledPoint = p + ((nextPoint - p) * smallerscale)
 
            nextLevel.append( scaledPoint )
        # copy the calculated points as input for next iteration
        pointList = nextLevel[:]
 
    return result
 
 
def drawPoints(points):
    """Draw a list of points which has None interspersed as a stopmark
    for different polygons.
    """
    start = False
    during = False
    firstpoint = False
 
    beginpath()
 
    for p in points:
 
        if not p:
            # it's a None stopmark
            if firstpoint:
                lineto( firstpoint.x, firstpoint.y )
                firstpoint = False
            start = True
 
        else:
            if start:
                # print "start MOVETO", p
                moveto( p.x, p.y )
                firstpoint = p
                start = False
            else:
                lineto( p.x, p.y )
    endpath()
 
 
 
def hexagonCurve(x, y, size_, smallerscale, steps, shuffle):
    """make a hexagon mousecurve
    """
 
    h = FGHexagon( (x, y), size_)
 
    p = h.points
 
    if shuffle:
        random.shuffle(p)
 
    # the "reverse" points
    q = p[::-1]
 
    # the mousecurves
    r = calculatePoints(p, smallerscale, steps)
    s = calculatePoints(q, smallerscale, steps)
 
    return (r, s)
 
def hexagonTriangleCurve(x, y, size_, smallerscale, steps, shuffle):
    """make a hexagon consisting of triangle mousecurves
 
    The orientation of the mousecurve is randomized per triangle.
    """
 
    h = FGHexagon( (x, y), size_)
    p = h.as_triangles()
 
    result = []
    for t in p:
        q = t[::-1]
        if random.random() > 0.5:
            r = calculatePoints(t, smallerscale, steps)
        else:
            r = calculatePoints(q, smallerscale, steps)
        result.extend(r)
 
    return result
 
# the frame
def dbgframe(angle=0):
    push()
    reset()
    stroke(0.665,0.665,0.665)
    strokewidth(0.14142 * 4)
    nofill()
 
    oval(roffset, 0, Y, Y)
    rect(roffset, 0, Y, Y)
    transform(CORNER)
    translate(x_half, y_half)
 
    rotate(angle)
    for i in range(6):
        rotate(60)
        line(0,0, y_half, 0)
    pop()
 
def drawIt(angle=0):
    """Not really necessary. Is a randomisation of the next two functions."""
    if random.random() > 0.5:
        r, s = hexagonCurve( 0, 0, y_half, 0.0066, 266, True)
        r.extend(s)
    else:
        r = hexagonTriangleCurve( 0, 0, y_half, 0.0166, 166, True)
 
    reset()
    transform(CENTER)
    if angle == -361:
        angle = random.random() * 60 - 30
 
    reset()
    translate(x_half, y_half)
    rotate(angle)
    nofill()
    stroke(0.665,0.665,0)
    strokewidth(0.14142 * 2)
 
    drawPoints(r)
 
def makeHex(angle=0):
    """Create and draw a hexagonal mousecurve, centered at the screen
    screen.y/2 in size."""
    r, s = hexagonCurve( 0, 0, y_half, 0.0066, 266, True)
 
    reset()
    transform(CENTER)
 
    reset()
    translate(x_half, y_half)
    rotate(angle)
    nofill()
    stroke(0.665,0.665,0)
    strokewidth(0.14142 * 2)
 
    drawPoints(r)
    # drawing the oppsite curve in a different color make as nice effect
    stroke(0.0, 0.332, 0.332)
    drawPoints(s)
 
def makeTri(angle=0):
    # hexagon of triangles
    r = hexagonTriangleCurve( 0, 0, y_half, 0.0166, 166, True)
 
    reset()
    transform(CENTER)
 
    reset()
    stroke(0.665,0.665,0)
    translate(x_half, y_half)
    rotate(angle)
    nofill()
    strokewidth(0.14142 * 2)
 
    drawPoints(r)
 
g_angle = 0
 
def setup():
    # draw one since speed is 1/5 fps
    global g_angle
    g_angle += 1
    background(0)
    if dbg_frame:
        dbgframe(g_angle)
    makeHex(g_angle)
    makeTri(g_angle)
 
def draw():
    global g_angle
    g_angle += 1
    a = g_angle # random.random() * 360
    background(0)
 
    # fun loving folks should reverse this switch
    if True:
        if dbg_frame:
            dbgframe(a)
        makeHex(a)
    else:
        for i in range(2):
            if dbg_frame:
                dbgframe(a)
            makeHex(a)
            drawIt(a)
            if random.random() > 0.5:
                makeHex(a)
            if random.random() > 0.5:
                makeTri(a)