## Foldlings (MS Thesis)

### iPad app: Swift, Scenekit, CocoaPods

Foldlings was a joint project where my partner and I created an interactive 3D pop-up card design app. The user sketches a 2D design and our iPad app converts that design into a 3D representation in kirigami pop-up form. For this project I was responsible for transforming the 2D shapes drawn by the user into 3D planes, rendering the interactive 3D simulation that allows the user to pinch to open and close the pop-up card, and creating the svg file to send to a laser cutter. My full thesis is here.

### The Process

To create 3D plane objects from user's sketch, we reduce the sketch into edges and created a Swift method to follow those edges forming planes using the Polygon Walk method. Those planes are then linked together into an acyclic graph which defines how each plane behaves in the 3D simulation. After the user is satisfied with their card, they can create an svg file to send to a laser cutter and have the physical card in seconds. Full code here.

### The GetPlanes Method:

This Swift method creates Plane objects that are passed to Scenekit to become 3-D planes from 2-D Edge objects by using the Polygon Walk method. This function iterates through all the edges connecting endpoints to startpoints until it forms a closed shape. There are many constraints including whether the edge has been previously visited or if the shape created is just a line. The function also sets certain attributes of the newly created Plane object such as the number of folds and the top and bottom edges.

```
// does a traversal of all the edges to find all the planes
func getPlanes(){
self.visited = []
planelist = []
for (i, start) in enumerate(self.edges)//traverse edges
{
//keep a fold count by plane to catch flaps and holes
var foldcount = 0
var folds:[Edge] = []
if start.dirty {
var p : [Edge] = []//plane
var isContained = contains(self.visited, start)
if !isContained// skipped over already visited edges
{ p.append(start)
if start.kind == .Fold{
foldcount++
folds.append(start)
}
// set the start as top and bottom edge
var topEdge : Edge = start
var bottomEdge : Edge = start
self.visited.append(start)
var closest = self.getClosest(start)// get closest adjacent edge
// check if twin has not been crossed and not in plane
while !CGPointEqualToPoint(closest.end, start.start) || contains(p, closest)
{ p.append(closest)
if closest.kind == .Fold{
foldcount++
folds.append(closest)
}
//mark if this is a top edge here by comparing y's, this shold be the midpoint
if(makeMid(closest.start.y, closest.end.y) < makeMid(topEdge.start.y, topEdge.end.y)) {
topEdge = closest
}
//mark if this is a bottom edge here by comparing y's, this shold be the midpoint
if(makeMid(closest.start.y, closest.end.y) > makeMid(bottomEdge.start.y, bottomEdge.end.y)) {
bottomEdge = closest
}
self.visited.append(closest)
closest = self.getClosest(closest)
}
//if the edge is the last edge and the edge isn't start edge
if CGPointEqualToPoint(closest.end, start.start) && !CGPointEqualToPoint(start.start, start.end)
{ p.append(closest)
self.visited.append(closest)
}
//// if you didn't cross twin or if the edge is one point, make it a plane
if !closest.crossed || CGPointEqualToPoint(start.start, start.end)
{ var plane = Plane(edges: p)
//set plane's top and bottom edge
plane.topEdge = topEdge
plane.bottomEdge = bottomEdge
//set foldcount
plane.foldcount = foldcount
//set plane for edges
plane.edges.map({$0.plane = plane})
// add planes to planelist
planelist.insertIntoOrdered(plane, ordering: {makeMid($0.topEdge.start.y, $0.topEdge.end.y) < makeMid($1.topEdge.start.y, $1.topEdge.end.y)})
}
closest.crossed = false
}
}
}
self.planes.linkPlanes(planelist)
}
```