Chapter 4: Design Algorithms

4.1 Introduction

In this chapter we will implement a few examples to create mathematical curves & surfaces and solve a few generative algorithms using C# in Grasshopper. Many of the examples involve using loops & recursions, which are not supported in regular Grasshopper components.

4.2 Geometry Algorithms

It is relatively easy to create curves & surfaces that follow certain mathematical equations when you use scripting. You can generate control points to create smooth NURBS or interpolate points to create the geometry.

4.2.1 Sine Curves & Surface

The following example shows how to create NurbsCurves, NurbsSurfaces, and a lofted Brep using the sine of an angle:

Create curves & surface using the sine equation

private void RunScript(int num, ref object OutCurves, ref object OutSurface, ref object OutLoft)
{
  // List of all points
  List<Point3d> allPoints = new List<Point3d>();
  // List of curves
  List<Curve> curves = new List<Curve>();

  for(int y = 0; y < num; y++)
  {
      // Curve points
      List<Point3d> crvPoints= new List<Point3d>();
      for(int x = 0; x < num; x++)
      {
        double z = Math.Sin(Math.PI / 180 + (x + y));
        Point3d pt = new Point3d(x, y, z);
        crvPoints.Add(pt);
        allPoints.Add(pt);
      }
      // Create a degree 3 nurbs curve from control points
      NurbsCurve crv = Curve.CreateControlPointCurve(crvPoints, 3);
      curves.Add(crv);
  }
  // Create a nurbs surface from control points
  NurbsSurface srf = NurbsSurface.CreateFromPoints(allPoints, num, num, 3, 3);

  // Create a lofted brep from curves
  Brep[ ] breps = Brep.CreateFromLoft(curves, Point3d.Unset, Point3d.Unset, LoftType.Tight, false);

  // Assign output
  OutCurves = curves;
  OutSurface = srf;
  OutLoft = breps;
}

4.2.2 De Casteljau Algorithm to Interpolate a Bezier Curve

You can create a cubic Bezier curve from four input points. The De Casteljau algorithm is used in computer graphics to evaluate the Bezier curve at any parameter. If evaluated at multiple parameters, then the points can be connected to draw the curve. The following example shows a recursive function implementation to interpolate through a Bezier curve:

The De Casteljau algorithm to draw a Bezier curve with 2, 4, 8, and 16 segments

private void RunScript(Point3d pt0, Point3d pt1, Point3d pt2, Point3d pt3, int segments, ref object BezierCrv)
{
    if(segments < 2)
      segments = 2;

    List<Point3d> bezierPts = new List<Point3d>();
    bezierPts.Add(pt0);
    bezierPts.Add(pt1);
    bezierPts.Add(pt2);
    bezierPts.Add(pt3);

    List<Point3d> evalPts = new List<Point3d>();
    double step = 1 / (double) segments;
    for(int i = 0; i <= segments; i++)
    {
      double t = i * step;
      Point3d pt = Point3d.Unset;
      EvalPoint(bezierPts, t, ref pt);
      if(pt.IsValid)
        evalPts.Add(pt);
    }
    Polyline pline = new Polyline(evalPts);
    BezierCrv = pline;
}
void EvalPoint(List<Point3d> points, double t, ref Point3d evalPt)
{
    // Stopping condition - point at parameter t is found
    if(points.Count < 2)
      return;
    List<Point3d> tPoints = new List<Point3d>();
    for(int i = 1; i < points.Count; i++)
    {
      Line line = new Line(points[i - 1], points[i]);
      Point3d pt = line.PointAt(t);
      tPoints.Add(pt);
    }
    if(tPoints.Count == 1)
      evalPt = tPoints[0];
    EvalPoint(tPoints, t, ref evalPt);
}

4.2.3 Simple Subdivision Mesh

The following example takes a surface & closed polyline, then creates a subdivision mesh. It pulls the midpoints of the polyline edges to the surface to then subdivide & pull again:

private void RunScript(Surface srf, List<Polyline> inPolylines, int degree, ref object OutPolylines, ref object OutMesh)
{
    // Instantiate the collection of all panels
    List<Polyline> outPanels = new List<Polyline>();
    // Limit to 6 subdivisions
    if( degree > 6)
      degree = 6;
    for(int i = 0; i < degree; i++)
    {
      // Outer polylines
      List<Polyline> plines = new List<Polyline>();
      // Mid polylines
      List<Polyline> midPlines = new List<Polyline>();
      // Generate subdivided panels
      bool result = SubPanelOnSurface(srf, inPolylines, ref plines, ref midPlines);
      if( result == false)
        break;
      // Add outer panels
      outPanels.AddRange(plines);
      // Add mid panels only in the last iteration
      if(i == degree - 1)
        outPanels.AddRange(midPlines);
      else // Subdivide mid panels only
        inPolylines = midPlines;
    }
    // Create a mesh from all polylines
    Mesh joinedMesh = new Mesh();
    for(int i = 0; i < outPanels.Count; i++)
    {
      Mesh mesh = Mesh.CreateFromClosedPolyline(outPanels[i]);
      joinedMesh.Append(mesh);
    }
    // Make sure all mesh faces normals are in the same general direction
    joinedMesh.UnifyNormals();

    // Assign output
    OutPolylines = outPanels;
    OutMesh = joinedMesh;
    bool SubPanelOnSurface( Surface srf, List<Polyline>
 inputPanels, ref List<Polyline> outPanels, ref List<Polyline> midPanels)
{
    // Check for a valid input
    if (inputPanels.Count == 0 || null == srf)
      return false;
    for (int i = 0; i < inputPanels.Count; i++)
    {
      Polyline ipline = inputPanels[i];
      if (!ipline.IsValid || !ipline.IsClosed)
        continue;
      // Stack of points
      List<Point3d> stack = new List<Point3d>();
      Polyline newPline = new Polyline();
      for (int j = 1; j < ipline.Count; j++)
      {
        Line line = new Line(ipline[j - 1], ipline[j]);
        if (line.IsValid)
        {
          Point3d mid = line.PointAt(0.5);
          double s, t;
          srf.ClosestPoint(mid, out s, out t);
          mid = srf.PointAt(s, t);
          newPline.Add(mid);
          stack.Add(ipline[j - 1]);
          stack.Add(mid);
        }
      }
      // Add the first 2 point to close last triangle
      stack.Add(stack[0]);
      stack.Add(stack[1]);
      // Close
      newPline.Add(newPline[0]);
      midPanels.Add(newPline);

      for (int j = 2; j < stack.Count; j = j + 1)
      {
        Polyline pl = new Polyline { stack[j - 2], stack[j - 1], stack[j], stack[j - 2] };
        outPanels.Add(pl);
      }
    }
    return true;
}

4.3 Generative Algorithms

Most of the generative algorithms require recursive functions that are only possible through scripting in Grasshopper. The following are four examples of generative solutions to generate the dragon curve, fractals, penrose tiling, and game of life:

4.3.1 Dragon Curve

private void RunScript(string startString, string ruleX, string ruleY, int Num, double Length, ref object DragonCurve)
{
    // Declare string
    string dragonString = startString;
    // Generate the string
    GrowString(ref Num, ref dragonString , ruleX, ruleY);
    // Generate the points
    List<Point3d> dragonPoints = new List<Point3d>();;
    ParseDeagonString(dragonString, Length, ref dragonPoints);
    // Create the curve
    PolylineCurve dragonCrv= new PolylineCurve(dragonPoints); 
    // Assign output
    DragonCurve = dragonCrv;
}
void GrowString(ref int Num, ref string finalString, string ruleX, string ruleY)
  {
    // Decrement the count with each new execution of the grow function
    Num = Num - 1;
    char rule;
    // Create new string
    string newString = "";
    for (int i = 0; i < finalString.Length ; i++)
    {
      rule = finalString[i];
      if (rule == 'X')
        newString = newString + ruleX;
      if (rule == 'Y')
        newString = newString + ruleY;
      if (rule == 'F' | rule == '+' | rule == '-')
        newString = newString + rule;
    }
    finalString = newString;
    // Stopper condition
    if (Num == 0)
      return;
    // Grow again
    GrowString(ref Num, ref finalString, ruleX, ruleY);
}
void ParseDeagonString(string dragonString, double Length, ref List<Point3d> dragonPoints)
{
    // Parse instruction string to generate points
    // Let base point be world origin
    Point3d pt = Point3d.Origin;
    dragonPoints .Add(pt);

    // Drawing direction vector - strat along the x-axis
    // Vector direction will be rotated depending on (+,-) instructions
    Vector3d V = new Vector3d(1.0, 0.0, 0.0);

    char rule;
    for(int i = 0 ; i < dragonString.Length;i++)
    {
      // Always start for 1 & length 1 to get one char at a time
      rule = DragonString[i];
      // Move forward using direction vector
      if( rule == 'F')
      {
        pt = pt + (V * Length);
        dragonPoints.Add(pt);
      }
      // Rotate Left
      if( rule == '+')
        V.Rotate(Math.PI / 2, Vector3d.ZAxis);
      // Rotate Right
      if( rule == '-')
        V.Rotate(-Math.PI / 2, Vector3d.ZAxis);
    }
}

4.3.2 Fractal Tree

private void RunScript(string startString, string ruleX, string ruleY, int num, double length, ref object FractalLines)
{
    // Declare string
    string fractalString = startString;
    // Denerate the string
    GrowString(ref num, ref dragonString , ruleX, ruleY);
    // Generate the points
    List<Line> fractalLines = new List<Line>();;
    ParsefractalString(fractalString, length, ref fractalLines );
    // Assign output
    FractalLines = fractalLines ;
}
void GrowString(ref int num, ref string finalString, string ruleX, string ruleF)
{
    // Decrement the count with each new execution of the grow function
    num = num - 1;
    char rule;
    // Create new string
    string newString = "";
    for (int i = 0; i < finalString.Length ; i++)
    {
      rule = finalString[i];
      if (rule == 'X')
        newString = newString + ruleX;
      if (rule == 'F')
        newString = newString + ruleF;
      if (rule == '[' || rule == ']' || rule == '+' || rule == '-')
        newString = newString + rule;
    }
    finalString = newString;
    // Stopper condition
    if (num == 0)
      return;
    // Grow again
    GrowString(ref num, ref finalString, ruleX, ruleF);
}
void ParsefractalString(string fractalString, double length, ref List<Line> fractalLines)
{
    // Parse instruction string to generate points
    // Let base point be world origin
    Point3d pt = Point3d.Origin;

    // Declare points array
    // Vector rotates with (+,-) instructions by 30 degrees
    List<Point3d> arrPoints = new List<Point3d>();

    // Draw forward direction
    // Vector direction will be rotated depending on (+,-) instructions
    Vector3d vec = new Vector3d(0.0, 1.0, 0.0);

    // Stacks of points and vectors
    List<Point3d> ptStack = new List<Point3d>();
    List<Vector3d> vStack = new List<Vector3d>();

    // Declare loop variables
    char rule;
    for(int i = 0 ; i < fractalString.Length; i++)
    {
      // Always start for 1 & length 1 to get one char at a time
      rule = fractalString[i];
      // Rotate Left
      if( rule == '+')
        vec.Rotate(Math.PI / 6, Vector3d.ZAxis);
      // Rotate Right
      if( rule == '-')
        vec.Rotate(-Math.PI / 6, Vector3d.ZAxis);
      // Draw Forward by direction
      if( rule == 'F')
      {
        // Add current points
        Point3d newPt1 = new Point3d(pt);
        arrPoints.Add(newPt1);
        // Calculate next point
        Point3d newPt2 = new Point3d(pt);
        newPt2 = newPt2 + (vec * length);
        // Add next point
        arrPoints.Add(newPt2);
        // Save new location
        pt = newPt2;
      }
      // Save point location
      if( rule == '[')
      {
        // Save current point & direction
        Point3d newPt = new Point3d(pt);
        ptStack.Add(newPt);

        Vector3d newV = new Vector3d(vec);
        vStack.Add(newV);
      }
      // Retrieve point & direction
      if( rule == ']')
      {
        pt = ptStack[ptStack.Count - 1];
        vec = vStack[vStack.Count - 1];
        // Remove from stack
        ptStack.RemoveAt(ptStack.Count - 1);
        vStack.RemoveAt(vStack.Count - 1);
      }
    }
    // Generate lines
    List<Line> allLines = new List<Line>();
    for(int i = 1; i < arrPoints.Count; i = i + 2)
    {
      Line line = new Line(arrPoints[i - 1], arrPoints[i]);
      allLines.Add(line);
    }
}

4.3.3 Penrose Tiling

private void RunScript(string startString, string rule6, string rule7, string rule8, string rule9, int num, ref object PenroseString)
  {
    // Declare string
    string finalString;
    finalString = startString;
    // Generate the string
    GrowString(ref num, ref finalString, rule6, rule7, rule8, rule9);
    // Return the string
    PenroseString = finalString;
  }
  void GrowString(ref int num, ref string finalString, string rule6, string rule7, string rule8, string rule9)
  {
    // Decrement the count with each new execution of the grow function
    num = num - 1;
    char rule;

    // Create new string
    string newString = "";
    for (int i = 0; i < finalString.Length; i++)
    {
      rule = finalString[i];
      if (rule == '6')
        newString = newString + rule6;
      if (rule == '7')
        newString = newString + rule7;
      if (rule == '8')
        newString = newString + rule8;
      if (rule == '9')
        newString = newString + rule9;

      if (rule == '[' || rule == ']' || rule == '+' || rule == '-')
        newString = newString + rule;
    }
    finalString = newString;

    // Stopper condition
    if (num == 0)
      return;

    // Grow again
    GrowString(ref num, ref finalString, rule6, rule7, rule8, rule9);
  }
private void RunScript(string penroseString, double length, ref object PenroseLines)
  {
    // Parse instruction string to generate points
    // Let base point be world origin
    Point3d pt = Point3d.Origin;

    // Declare points array
    // Vector rotates with (+,-) instructions by 36 degrees
    List<Point3d> arrPoints = new List<Point3d>();

    // Draw forward direction
    // Vector direction will be rotated depending on (+,-) instructions
    Vector3d vec = new Vector3d(1.0, 0.0, 0.0);

    // Stacks of points & vectors
    List<Point3d> ptStack = new List<Point3d>();
    List<Vector3d> vStack = new List<Vector3d>();

    // Declare loop variables
    char rule;
    for(int i = 0 ; i < penroseString.Length; i++)
    {
      // Always start for 1 & length 1 to get one char at a time
      rule = penroseString[i];
      // Rotate Left
      if( rule == '+')
        vec.Rotate(36 * (Math.PI / 180), Vector3d.ZAxis);
      // Rotate Right
      if( rule == '-')
        vec.Rotate(-36 * (Math.PI / 180), Vector3d.ZAxis);
      // Draw Forward by direction
      if( rule == '1')
      {
        // Add current points
        Point3d newPt1 = new Point3d(pt);
        arrPoints.Add(newPt1);
        // Calculate next point
        Point3d newPt2 = pt + (vec * length);
        // Add next point
        arrPoints.Add(newPt2);
        // Save new location
        pt = newPt2;
      }

      // Save point location
      if( rule == '[')
      {
        // Save current point & direction
        Point3d newPt = new Point3d(pt);
        ptStack.Add(newPt);

        Vector3d newVec = new Vector3d(vec);
        vStack.Add(newVec);
      }

      // Retrieve point & direction
      if( rule == ']')
      {
        pt = ptStack[ptStack.Count - 1];
        vec = vStack[vStack.Count - 1];

        // Remove from stack
        ptStack.RemoveAt(ptStack.Count - 1);
        vStack.RemoveAt(vStack.Count - 1);
      }
    }

    // Generate lines
    List<Line> allLines = new List<Line>();
    for(int i = 1; i < arrPoints.Count; i = i + 2)
    {
      Line line = new Line(arrPoints[i - 1], arrPoints[i]);
      allLines.Add(line);
    }

    PenroseLines = allLines;
  }

4.3.4 Conway Game of Life

A cellular automaton consists of a regular grid of cells, each in one of a finite number of states, “On” & “Off” for example. The grid can be in any finite number of dimensions. For each cell, a set of cells called its neighborhood (usually including the cell itself) is defined relative to the specified cell. For example, the neighborhood of a cell might be defined as the set of cells a distance of 2 or less from the cell. An initial state (time t=0) is selected by assigning a state for each cell. A new generation is created (advancing t by 1), according to some fixed rule (generally, a mathematical function) that determines the new state of each cell in terms of the current state of the cell and the states of the cells in its neighborhood. Check wikipedia for full details and examples.

  private void RunScript(Surface srf, int uNum, int vNum, int seed, ref object PointGrid, ref object StateGrid)
  {
    if(uNum < 2)
      uNum = 2;
    if(vNum < 2)
      vNum = 2;

    double uStep = srf.Domain(0).Length / uNum;
    double vStep = srf.Domain(1).Length / vNum;
    double uMin = srf.Domain(0).Min;
    double vMin = srf.Domain(1).Min;

    // Create a grid of points & a grid of states
    DataTree<Point3d> pointsTree = new DataTree<Point3d>();
    DataTree<int> statesTree = new DataTree<int>();
    int pathIndex = 0;
    Random rand = new Random(seed);
    for (int i = 0; i <= uNum; i++)
    {
      List<Point3d> ptList = new List<Point3d>();
      List<int> stateList = new List<int>();
      GH_Path path = new GH_Path(pathIndex);
      pathIndex = pathIndex + 1;
      for (int j = 0; j <= vNum; j++)
      {
        Point3d srfPt = srf.PointAt(uMin + i * uStep, vMin + j * vStep);
        ptList.Add(srfPt);
        int randState = rand.Next(0, 2);
        stateList.Add(randState);
      }
      pointsTree.AddRange(ptList, path);
      statesTree.AddRange(stateList, path);
    }

    PointGrid = pointsTree;
    StateGrid = statesTree;

  }


  private void RunScript(DataTree<int> grid, int gen, ref object OutGrid)
  {
    // Get state at the defined generation
    for(int i = 0; i < gen; i++)
      grid = NewGeneration(grid);

    OutGrid = grid;
  }


  public DataTree<int> NewGeneration(DataTree<int> inStates)
  {
    int i, j, c, nc;
    List<int> prvBranch;
    List<int> nxtBranch;
    List<int> branch;
    DataTree<int> states = new DataTree<int>();
    states = inStates;

    for (i = 0; i <= states.Branches.Count - 1; i++)
    {
      branch = states.Branches[i];
      for (j = 0; j <= branch.Count - 1; j++)
      {
        c = branch[j];
        nc = 0;

        // Check neighbouring states
        // Next
        nc = nc + branch[(j + 1 + branch.Count) % branch.Count];
        // Prev
        nc = nc + branch[(j - 1 + branch.Count) % branch.Count];

        // Top
        nxtBranch = states.Branches[(i + 1 + states.Branches.Count) % states.Branches.Count];
        nc = nc + nxtBranch[(j + 1 + nxtBranch.Count) % nxtBranch.Count];
        nc = nc + nxtBranch[(j + nxtBranch.Count) % nxtBranch.Count];
        nc = nc + nxtBranch[(j - 1 + nxtBranch.Count) % nxtBranch.Count];

        // Bottom
        prvBranch = states.Branches[(i - 1 + states.Branches.Count) % states.Branches.Count];
        nc = nc + prvBranch[(j + 1 + prvBranch.Count) % prvBranch.Count];
        nc = nc + prvBranch[(j + prvBranch.Count) % prvBranch.Count];
        nc = nc + prvBranch[(j - 1 + prvBranch.Count) % prvBranch.Count];

        // Set the new state
        if (c == 1)
        {
          if (nc < 2 | nc > 3)
            c = 0;
        }
        else if (c == 0)
        {
          if (nc == 3)
            c = 1;
        }
        branch[j] = c;
      }
    }
    return states;
  }

End of Guide

This is part 4 of the Essential C# Scripting for Grasshopper guide.