My Surrealist Object

For 3D Sensing and Visualization class, we had to construct our own 3D objects. I was inspired by the drawing assignment, and wanted to create a physical object out of my own drawing with the Kinect. Building the drawing component turned out to be a little more intensive than I thought, so I decided to save the Kinect for my final. I wrote a program that takes the path of the user’s mouse and extrudes it into a closed tube that can be printed in a 3D printer. I made a video of the process.

I started with a polyline, which adds vertices to itself at every point the mouse touches in space. The polyline reference is fed into the buildTube function, which constructs a 3D frame around it. It assumes that there is a planar (flat) polygon at each point along the polyline. It then takes an up vector (i.e. a vector that always points up) and the vector that describes the distance between the drawn points and spirals around the polyline building a mesh. Each point in the mesh is then connected by triangles.

All of this is necessary to build a printable 3D model. In order to build an object with a 3D printer, all of the surfaces of the 3D model must be topologically closed. It’s a little like the paint bucket tool in Photoshop; if any part of the selected area has a hole, the color will leak out. In this case, the printer will get confused about the structure of the layer and refuse to print. All of the points in the model must be connected.

The main code is below. Read the rest on Github.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "ofApp.h"
 
void ofApp::setup() {
	ofSetVerticalSync(true);
}
 
void ofApp::update() {
}
 
void ofApp::draw() {
    easycam.begin();
 
	ofBackground(0);
	ofSetColor(255);
 
	if(ofGetKeyPressed('c')) {
        line.clear();
	} else if(ofGetKeyPressed(' ')) {
        if(mouseX != ofGetPreviousMouseX() &&
           mouseY != ofGetPreviousMouseY()) {
            line.addVertex(ofVec2f(mouseX-ofGetWidth()/2, mouseY-ofGetHeight()/2));
        }
	}
    line.draw();
 
    mesh = buildTube(line, 6, 40);
    ofSetColor(ofColor::red);
    mesh.draw();
    ofSetColor(0);
    mesh.drawWireframe();
 
    easycam.end();
}
 
void ofApp::keyPressed(int key) {
	if(key == 'f') {
		ofToggleFullscreen();
	}
    if(key == 's') {
        mesh.save("out.ply");
    }
}
 
//mesh indices
int getIndex(int i, int j, int sides)
{
    return i * sides + (j % sides);
}
 
ofMesh ofApp::buildTube(const ofPolyline& path, int sides, float radius)   
{
    ofMesh mesh;
    mesh.setMode(OF_PRIMITIVE_TRIANGLES);
 
    //up vector orthogonal to polygonal slice surface
    ofVec3f up(0, 1, 0);
 
    for (int i = 0; i < path.size(); i++)
    {
        ofVec3f diff, cur, next;
        if(i + 1 == path.size()) {
            cur = path[i];
            next = path[i-1];
            diff = cur - next;
        } else {
            cur = path[i];
            next = path[i+1];
            diff = next - cur;
        }
 
        //get perpendicular vectors with cross products
        ofVec3f u = diff.getCrossed(up).normalize() * radius;
        ofVec3f v = u.getCrossed(diff).normalize() * radius;
 
        //rotate around polyline building mesh
        for (int j = 0; j < sides; j++) {
            float theta = ofMap(j, 0, sides, 0, TWO_PI);
            float x = cos(theta);
            float y = sin(theta);
            ofVec3f pos = x*u + y*v + cur;
            mesh.addVertex(pos);
        }        
 
    }
 
    //closing tube sides - connect side indices to mesh by forming triangles
    for (int i = 0; i+1 < path.size(); i++)
    {
        for (int j = 0; j < sides; j++) {
            mesh.addIndex(getIndex(i, j, sides));
            mesh.addIndex(getIndex(i+1, j, sides));
            mesh.addIndex(getIndex(i, j+1, sides));
            mesh.addIndex(getIndex(i+1, j, sides));
            mesh.addIndex(getIndex(i+1, j+1, sides));
            mesh.addIndex(getIndex(i, j+1, sides));
        }
    }
 
    //closing tube ends
    if (path.size() > 0){
        int centerStart = mesh.getNumVertices(); //center of tube start
        mesh.addVertex(path[0]);
        int centerEnd = mesh.getNumVertices();  //center of tube end
        mesh.addVertex(path[path.size()-1]);
 
        //connect side indices to centers of tube ends
        for (int j = 0; j < sides; j++) {
            mesh.addIndex(centerStart);
            mesh.addIndex(getIndex(0, j, sides));
            mesh.addIndex(getIndex(0, j+1, sides));
 
            mesh.addIndex(centerEnd);
            mesh.addIndex(getIndex(path.size()-1, j+1, sides));
            mesh.addIndex(getIndex(path.size()-1, j, sides));
        }
 
    }
    return mesh;
}