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.