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

problem with transformation

Posted by vedran on Dec 19, 2008

i'm having a problem with rotation(), when i try to apply it to some object that i draw it draws one more object... please try this code:

size(640,780)
colormode(HSB)
background(color(0.9, 0, 0.92))
#-------------------------------------------------------------------------------- funkcije
def makeQuadUnionLayer(h,s,b, yShift):
    _compound = None
    
    cFill = color(h,s,b)
    fill(cFill)
    nostroke()
    
    for i in range(0,4):
        r = random(40,180)
        xPos = random(400)+50
        yPos = random(100)+ yShift
        
        rct = rect(xPos, yPos, r, r)
        rct.rotate(random(180)-90)
        #rotate(random(180)-90)
 
        if not _compound:
            _compound = rct
        _compound = _compound.union(rct)
        drawpath(_compound)
        #drawpath(path)
    #reset()
#-------------------------------------------------------------------------------- crtanje
makeQuadUnionLayer(0.6,0.8,random(0.1,0.9),250)


 
Posted by vedran on Dec 19, 2008

how can i rotate only this rect that i made, without creating new one?



Posted by impiaaa on Dec 19, 2008

Add

draw=False
to the
rect
call.



Posted by Josh Caswell on Dec 20, 2008

impiaaa is right. One of your problems is that the initial call to rect() is drawing the path.

Unfortunately, it's not as simple as that. The short answer is that
rct.rotate() does not do quite what you might expect.

It attaches a transform to the path object, but the new coordinates don't get calculated until draw time. This, in addition to the fact that the NB code is wrapping up Cocoa code that is doing the work, means that the call to union() doesn't use the rotated points. What you need to do is described in the NB compound paths tutorial.

Unfortunately, it's also not as simple as that. NB uses NSAffineTransform to do the actual transformation, and the Cocoa side has no idea about NB's transform mode (CENTER/CORNER). NSAffineTransform does all its transforms in relation to the canvas's origin, at the top left. If you just make a Transform object and use its rotate() method, each square will be moved away from its own centerpoint. So you have to do a little shuffling by hand. (I finally figured this out by looking at NB's BezierPath._get_transform() method.)

This code does what I think you want: draws just the union of the squares rotated about their centers. (I don't know why you are taking the union of filled, un-outlined shapes, since it doesn't look any different, but that's your business.) Note that I moved the drawpath() call out of the loop. Otherwise you are drawing the union repeatedly, as you add pieces to it. You will see this if turn fill off and stroke on: there will be internal thinner lines. Also note the else:, which seems like good style to me.

size(640,780)
colormode(HSB)
background(color(0.9, 0, 0.92))
#---------------------------------------------------------------------
def makeQuadUnionLayer(h,s,b, yShift):
    _compound = None
    
    cFill = color(h,s,b)
    fill(cFill)
    nostroke()
    
    for i in range(4):
        r = random(40,180)
        xPos = random(400)+50
        yPos = random(100)+ yShift
        
        rct = rect(xPos, yPos, r, r, draw=False)
        
        t, t1, t2 = Transform(), Transform(), Transform()
        deltaX = xPos + r/2
        deltaY = yPos + r/2
        
        t1.translate(-deltaX, -deltaY)
        t2.translate(deltaX, deltaY)
        
        t.rotate( random(180) - 90 )
        
        t.prepend(t1)
        t.append(t2)
        
        rct = t.transformBezierPath(rct)
 
        if not _compound:
            _compound = rct
        else:
            _compound = _compound.union(rct)
    
    drawpath(_compound)
#----------------------------------------------------------------------
makeQuadUnionLayer(0.6,0.8,random(0.1,0.9),250)



Posted by Josh Caswell on Dec 20, 2008

vedran, hope you can work with that. If you want a more thorough explanation, let me know.



Posted by vedran on Jan 18, 2009

hi Josh, thank you for this detailed explanation. I'm back to try nodebox so I'll try this code today. it helps a lot!



Posted by vedran on Jan 19, 2009

Hi again!
ok, this is workig, but i'll need deeper explanation. i simply dont understand how transform object apply to rect. i've try some modifications but i cant get anything as i would like. can you explain your code Transform definitions step by step? that would be great.

btw. i cant find prepend and append methods description on site.



Posted by Josh Caswell on Jan 23, 2009

The Transform object is described in the NodeBox reference, but strangely, the only way to get to that page seems to be through the Paths tutorial that I mentioned. You'll see the prepend() and append() methods there. I don't think they're described anywhere, though being that a month has passed, I can't remember for sure. You may have to grovel through the source to see exactly how the mechanics work.

The gist of it is that a Transform object holds a calculation that you can perform on a path you pass to it. So, you instantiate, e.g.,

my_trans = Transform()
my_trans.translate(x=50, y=50)
and you now have an object which will apply that translation on just those paths that you give it.

The translate() or rotate() methods you call on a path (i.e., rct.rotate()) don't do the actual calculations of the transformed points until NodeBox finally draws them to the screen. If you make a Transform object, you still apply it only to specified paths, but it will do the calculation right away and return the new path. So if my_rect has corners [(0,0), (10,0), (10,10), (0,10)], and you use my_trans from above:
my_rect = my_trans.transformBezierPath(my_rect)
my_rect is reassigned to [(50,50), (60,50), (60,60), (50,60)], and anything further you do with it will use those new coordinates, including taking a union with another path.

The prepend() and append() methods just allow you to simplify your life a little bit. If you have several transformations you want to apply on a single path, and they have to be applied one at a time, in a specific order, you create a Transform for each, then stick them together in the order you want, just as if you were making a list. Then you only need to call transformBezierPath() once, using the combined Transform, and each of the transformations will be calculated in turn.

The specific transformations that I gave you work like this: you have a square somewhere on your canvas, with center at (x, y) and top left corner (x-r, y-r). Any transformations you apply (particularly a rotation) are going to originate at that corner, not the center. However, if you slide the rectangle by (-r, -r), so that the center is now where the corner was, the transformation origin doesn't move. You can now do your rotation, and then you can slide the square back by (r, r), putting the center where it originally was, and making the square appear to have been rotated around its center. That seems a little crazy, I'm sure, but I believe that it has to do with the interaction, like I mentioned before, between Cocoa drawing and NodeBox bookkeeping.

If you haven't already, I suggest turning off the random generation of parameters when figuring out what's going on. That way you don't have to guess where everything should be. Draw one square in the center of the canvas, at coordinates that you have chosen, and try applying some transformations that you choose to it. The other thing that might be useful, which definitely helped me puzzle this out, is drawing a dot at the center of the square: oval(x, y, 2, 2) Then you'll see where the square was before you transformed it, and how it may have moved as a result. Or even make a copy of the square and draw it before and after transformation. In both these cases, turning fill off and stroke on will help you see everything. I think you'll be able to sort it out fairly easily. Good luck!