Procedural OpenSCAD: random trees

I've been playing with OpenSCAD, and it has some interesting limitations.  One of those is that variables are actually constants set at compile time.  This became immediately apparent when I tried to save a value to a variable within a for loop - the variable is not allowed to have it's value changed!  It means that OpenSCAD is well suited for parametric design, where changing some constants will scale the end result appropriately.





As such, I set myself the task of generating random trees to try and find a way around this issue.  I think the solution is probably inefficient, as the dynamic nature of the algorithm is actually stored as constants during compilation, rather than used and thrown away during run time.





Anyway, this code might be of interest.  It will generate a different tree every time you compile.  I have been using the OpenSCAD version 2013.6.  Creating a render from this takes my machine a very long time.  Be careful with those top level constants!



INITIAL_SCALE=4;  // used to multiply the thickness.
DEFAULT_THCK=2;  // used with scale above.

MAX_BRANCHES=12;  // how many possible branches
MAX_BRANCH_LEN=45; // how long they grow



growTree();



module growTree() {

 color([0,0.12,0])
 square([300,300],center=true);

 // Place a trunk
 color("Brown")
  cylinder_ep( [0,0,0], [0,10,60], 10,8);
 
 // fire off the branching routine
 growBranches([0,10,60], INITIAL_SCALE);
}


/* Warning: this is a recursive function,
 * meaning it calls itself.  It will not
 * call itself when the scaling factor
 * creates cylinders with a thickness of
 * less than 1.  If you set your initial
 * scale high, you will get a lot of 
 * branches, and may choke your 
 * computer.
 */
module growBranches( origin, scale) {

 // We can't make random numbers at runtime
 // so we have to generate them prior.

 // seed the random number generator
 seed = 42;

 // randomise how much to branch
 // use round() to make it a whole number
 num_branches = round( rands( 1,MAX_BRANCHES,1)[0]);
 
 // we need a length for each branch 
 branch_len = rands( 2,MAX_BRANCH_LEN, num_branches );

 green_shade = rands( 0.2, 0.7, 1)[0];
 
 // We create a rotation on the z axis
 // to move our x,y coordinates, and a 
 // rotation on the x axis to move the z
 // coordinates (i.e, similar to azimuth)
 x_a = rands( 0,100, num_branches);
 z_a = rands( 0,360, num_branches);


 // This function only runs and calls itself
    // if the default thickness scales to more
 // than or equal to 1
 if( DEFAULT_THCK * scale >= 1 ) {

  for( i=[0:num_branches] ) {
  
   color( "Brown" )
   cylinder_ep( origin, 
    [ origin[0] + (branch_len[i]*scale) * cos(z_a[i]) - sin(z_a[i]),
      origin[1] + (branch_len[i]*scale) * sin(z_a[i]) + cos(z_a[i]),
      origin[2] + (branch_len[i]*scale) * cos(x_a[i]) 
    ]
    , DEFAULT_THCK*scale, 1 );
   
   // Call this same function again, passing the end point
           // as the origin of the next call.
   // Note, the scale value is decreased by * 0.4
   growBranches( 
     [ origin[0] + (branch_len[i]*scale) * cos(z_a[i]) - sin(z_a[i]),
       origin[1] + (branch_len[i]*scale) * sin(z_a[i]) + cos(z_a[i]),
         origin[2] + (branch_len[i]*scale) * cos(x_a[i]) ], 
       scale*0.4 );


   if( branch_len[i] > 0 ) {
    // place some green
    translate( 
     [ origin[0] + (branch_len[i]*scale) * cos(z_a[i]) - sin(z_a[i]),
       origin[1] + (branch_len[i]*scale) * sin(z_a[i]) + cos(z_a[i]),
       origin[2] + (branch_len[i]*scale) * cos(x_a[i]) 
     ])
     color( [0,green_shade,0])
      %sphere( r=(5*scale));
    }
  }
  }
}


// Draw a cylinder between two specified 
// vectors p1, p2, with start and end
// radius r1, r2.
// source: http://forum.openscad.org/Cylinders-td2443.html
module cylinder_ep(p1, p2, r1, r2) {

 translate(p1)
  sphere(r=r1,center=true);
 translate(p2)
  sphere(r=r2,center=true);

 // Create a vector storing the differences in positions
 assign(vector = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2] ])

 // store the distance
 assign(distance = sqrt( pow(vector[0], 2) + pow(vector[1], 2) + pow(vector[2], 2)))

 // move from the origin by the differences in position vector
 translate(p1 + vector/2)

 //rotation of XoY plane by the Z axis with the angle 
 // of the [p1 p2] line projection with the X axis on 
 //the XoY plane
 rotate([0, 0, atan2(vector[1], vector[0])] ) 
  // rotation of ZoX plane by the y axis with the angle 
  // given by the z coordinate and the sqrt(x^2 + y^2)) 
  // point in the XoY plane
  rotate([0, atan2(sqrt(pow(vector[0], 2)+pow(vector[1], 2)),vector[2]), 0])
  cylinder(h = distance, r1 = r1, r2 = r2,center=true);
}

2 comments:

monstercolorfun co said...

Hi, I also found that limitation with variables when i wanted to write an array in a loop function. it coudlnt write an array. There is a workaround to writing variables, it's weird i didn't totally understand it: http://stackoverflow.com/questions/32126961/how-can-i-use-multimatrix-in-a-loop-in-openscad

monstercolorfun co said...

I don't know of any other program that can do full CSG by way of code, it's very useful, are there any commercial alternatives?

Post a Comment