# Line

Line? What a weird topic? Many models need lines, such as Turtle Spiral Generator and Symmetrical triangle generator. They are all based on lines.

Unfortunately, OpenSCAD lacks a built-in module named `polyline`. What should we do now?

# The `polygon` module

OpenSCAD has a built-in `polygon` module which accepts a list of `[x, y]` coordinates to create a multi-sided shape. The official documentation provides several simple examples. The `polygon` module also has a `paths` parameter which indicates the order to traverse the points.

``````polygon(points=[[0,0],[100,0],[130,50],[30,50]]);
polygon([[0,0],[100,0],[130,50],[30,50]], paths=[[0,1,2,3]]);
polygon([[0,0],[100,0],[130,50],[30,50]],[[3,2,1,0]]);
polygon([[0,0],[100,0],[130,50],[30,50]],[[1,0,3,2]]);
``````

The code fragments create the same parallelogram.

# A line from two points

Why do you have to know the `polygon` module? After all, it's for creating a multi-sided shape, not for creating a line, right? Well, let's back to the topic. If you have to define a `polyline` module which accepts a list of points and connects all of them automatically, what do you have to solve first?

Divide and conquer is an essential skill of programming. The minimum task of the above requirement is connecting two points to form a line.

Maybe the first thought which comes to your mind is that OpenSCAD has a built-in `square` module. If it has two relatively short sides, the square looks like a line. Next, calculate the length between two points, the angle between the line and x-axis, rotate the line and move it to the right position and so on. Wow, it's troublesome.

Here is the second thought. OpenSCAD has a built-in `polygon` module. It accepts a list of `[x, y]` coordinates to create a multiple sided shape. Can we give it four points, which the last two points are calculated from the previous two points and the line width, to form a line? According to the thought, we implement a prototype below.

``````module line(p1, p2, width) {
polygon([
p1, p2, [p2[0], p2[1] - width], [p1[0], p1[1] - width]
]);
}

line([1, 2], [-5, -4], 1);
``````

It seems pretty good.

Here, the last two points directly come from moving the previous two points vertically. If you are picky, you might say this is wrong. I cannot just move the two points vertically. It should be the distance between two parallel lines.

Well, I am also picky so let's be picky thoroughly. I want a line like this.

That is, the line is aligned center. According to the figure, we can derive a formula.

``````angle = atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
``````

Then, the four points passed into `polygon` is …

``````offset_x = 0.5 * width * cos(90 - angle);
offset_y = 0.5 * width * sin(90 - angle);
offset1 = [-offset_x, offset_y];
offset2 = [offset_x, -offset_y];
points=[
point1 + offset1, point2 + offset1,
point2 + offset2, point1 + offset2
];
``````

Let's refactor the above code and define a module for it.

``````module line(point1, point2, width = 1) {
angle = 90 - atan((point2[1] - point1[1]) / (point2[0] - point1[0]));
offset_x = 0.5 * width * cos(angle);
offset_y = 0.5 * width * sin(angle);

offset1 = [-offset_x, offset_y];
offset2 = [offset_x, -offset_y];

polygon(points=[
point1 + offset1, point2 + offset1,
point2 + offset2, point1 + offset2
]);
}

line([1, 2], [-5, -4], 1);
``````

We have a line now.

# `polyline`

Once a `line` module which can draw a line from two points is ready, we can implement a `polyline` module based on it. You just take two points and draw a line sequentially until consuming all points.

``````module line(point1, point2, width = 1) {
angle = 90 - atan((point2[1] - point1[1]) / (point2[0] - point1[0]));
offset_x = 0.5 * width * cos(angle);
offset_y = 0.5 * width * sin(angle);

offset1 = [-offset_x, offset_y];
offset2 = [offset_x, -offset_y];

polygon(points=[
point1 + offset1, point2 + offset1,
point2 + offset2, point1 + offset2
]);
}

module polyline(points, width = 1) {
module polyline_inner(points, index) {
if(index < len(points)) {
line(points[index - 1], points[index], width);
polyline_inner(points, index + 1);
}
}

polyline_inner(points, 1);
}

polyline([[1, 2], [-5, -4], [-5, 3], [5, 5]], 1);
``````

The code creates a model like this.

Hmm? What are those gaps? The reason is the `line` module generates a square. One simple solution is adding a small circle at the point.

``````module line(point1, point2, width = 1, cap_round = true) {
angle = 90 - atan((point2[1] - point1[1]) / (point2[0] - point1[0]));
offset_x = 0.5 * width * cos(angle);
offset_y = 0.5 * width * sin(angle);

offset1 = [-offset_x, offset_y];
offset2 = [offset_x, -offset_y];

if(cap_round) {
translate(point1) circle(d = width, \$fn = 24);
translate(point2) circle(d = width, \$fn = 24);
}

polygon(points=[
point1 + offset1, point2 + offset1,
point2 + offset2, point1 + offset2
]);
}

module polyline(points, width = 1) {
module polyline_inner(points, index) {
if(index < len(points)) {
line(points[index - 1], points[index], width);
polyline_inner(points, index + 1);
}
}

polyline_inner(points, 1);
}

polyline([[1, 2], [-5, -4], [-5, 3], [5, 5]], 1);
``````

Finally, we get a `polyline`.

How about this `polyline`? It's just a simple implementation. There are still other implementations. For example, Archimedean spiral generator uses a different idea to implement a line. I'll talk about it in the later document. Try to come up with other ideas by yourself. It's also a joy when modeling by programming.