There are a function f(x, y)
and a given region in the xy-plane. If we can create and print a model, we'll be able to touch the function physically. Will mathematical functions be interesting?
It's not difficult when modeling is based on programming. There are several ways to do that. Which way would be best depends on what your requirement is.
The simplest way
Placing a small cube at every point [x, y, f(x, y)]
is the simplest way. If every cube is small enough, all cubes will compose the function graph. The code is easy to understand as well.
function f(x, y) = (pow(y,2)/pow(2, 2))-(pow(x,2)/pow(2, 2));
min_value = -3;
max_value = 3;
resolution = 0.5;
thickness = 1;
for(x = [min_value:resolution:max_value]) {
for(y = [min_value:resolution:max_value]) {
translate([x, y, f(x, y)])
linear_extrude(thickness, center = true)
square(resolution, center = true);
}
}
In the above code, resolution
determines what the next x
or y
is. A smaller resolution
creates a smoother graph but takes more time to render. The exported model file is larger, too. The figure below using resolution = 0.01
. It takes more than one minute to show the preview on my computer.
It's a trade-off between resolution and smoothness for a model. Even in the way introduced later, you also have to balance these two opposing qualities. When creating a smoother graph, however, the above way costs more indeed.
Introducing polyhedron
If you want a smoother graph but a relatively lower cost, try the built-in polyhdedron
module. In the document “Line“, we talked about the polygon
module which can create a multiple sided shape from a list of x, y coordinates. You may think the polyhdedron
module is a 3D version of the polygon
module. The polyhdedron
module can be used to create any regular or irregular shape.
Even the polyhdedron
module is like a 3D version of the polygon
module, it's more complex than using the polygon
module. Simply put, you have to know and index every vertex of the polyhedron. The official document of polyhedron gives a simple example.
To generate cube([10, 7, 5])
, you have to index it's eight vertices. Then, figure out the indices used by each face. OpenSCAD requires a clockwise order.
After that, invoke polyhedron
with the vertices and the vector of faces.
CubePoints = [
[ 0, 0, 0 ], //0
[ 10, 0, 0 ], //1
[ 10, 7, 0 ], //2
[ 0, 7, 0 ], //3
[ 0, 0, 5 ], //4
[ 10, 0, 5 ], //5
[ 10, 7, 5 ], //6
[ 0, 7, 5 ]]; //7
CubeFaces = [
[0,1,2,3], // bottom
[4,5,1,0], // front
[7,6,5,4], // top
[5,6,2,1], // right
[6,7,3,2], // back
[7,4,0,3]]; // left
polyhedron( CubePoints, CubeFaces );
A simple example, right? What will happen if we change the z value of the vertex 6 from 5 to 7?
CubePoints = [
[ 0, 0, 0 ], //0
[ 10, 0, 0 ], //1
[ 10, 7, 0 ], //2
[ 0, 7, 0 ], //3
[ 0, 0, 5 ], //4
[ 10, 0, 5 ], //5
[ 10, 7, 7 ], //6
[ 0, 7, 5 ]]; //7
CubeFaces = [
[0,1,2,3], // bottom
[4,5,1,0], // front
[7,6,5,4], // top
[5,6,2,1], // right
[6,7,3,2], // back
[7,4,0,3]]; // left
polyhedron( CubePoints, CubeFaces );
The vertices indexed from 4 to 7 are not on the same face. OpenSCAD will try to slice the rectangle into triangles automatically in this situation because three points must compose a face. (That's why you need a three-legged stool on the uneven ground.) There's no warning message when you render
the model.
However, multiple polyhedron
operations may generate a warning message. (It seems that OpenSCAD will merge some calculation.)
PolySet has nonplanar faces. Attempting alternate construction
For example:
points = [
[0, 0, 1], [1, 0, 2],
[1, 1, 4], [0, 1, 1],
[0, 0, 2], [1, 0, 3],
[1, 1, 5], [0, 1, 2]
];
points2 = [
[1, 0, 2], [2, 0, 2],
[2, 1, 0], [1, 1, 4],
[1, 0, 3], [2, 0, 3],
[2, 1, 1], [1, 1, 5],
];
faces = [
[0,1,2,3], // bottom
[4,5,1,0], // front
[7,6,5,4], // top
[5,6,2,1], // right
[6,7,3,2], // back
[7,4,0,3] // left
];
polyhedron(points, faces);
polyhedron(points2, faces);
If you delete one of the polyhedron
operation in the above code and render, "PolySet has nonplanar faces"
won't appear; however, rendering both causes the message. To avoid this problem, we may try to slice a face into triangles by ourselves.
Slicing a face into triangles
For function graphs, it's easy to slice a face into triangles because we are increasing the value of x, y constantly. For example, given the points below.
For any point, we may take its right and upper-right points to compose a triangle.
And, we may take its upper and upper-right points to compose a triangle, too.
Now, we slice a rectangle into two triangles. We can continue this process until slicing all rectangles.
For all [x, y, f(x, y)]
points, we group them into groups of three. For each f(x, y)
of a triangle, we can use f(x, y) - thickness
to create a bottom face. After that, we can use the polyhedron
module to draw it.
Follow the same process for all triangles, you can create a function graph.
points = [
[[0, 0, 1], [1, 0, 2], [2, 0, 2], [3, 0, 3]],
[[0, 1, 1], [1, 1, 4], [2, 1, 0], [3, 1, 3]],
[[0, 2, 1], [1, 2, 3], [2, 2, 1], [3, 2, 3]],
[[0, 3, 1], [1, 3, 3], [2, 3, 1], [3, 3, 3]]
];
thickness = 1;
faces = [
[2, 1, 0],
[3, 4, 5],
[0, 1, 4, 3],
[1, 2, 5, 4],
[2, 0, 3, 5]
];
z_offset = [0, 0, -thickness];
for(yi = [0:len(points) - 2]) {
for(xi = [0:len(points[yi]) - 2]) {
tri1_top = [
points[yi][xi],
points[yi][xi + 1],
points[yi + 1][xi + 1]
];
tri1_bottom = [
tri1_top[0] + z_offset,
tri1_top[1] + z_offset,
tri1_top[2] + z_offset
];
tri2_top = [
points[yi][xi],
points[yi + 1][xi + 1],
points[yi + 1][xi]
];
tri2_bottom = [
tri2_top[0] + z_offset,
tri2_top[1] + z_offset,
tri2_top[2] + z_offset
];
polyhedron(
points = concat(tri1_top, tri1_bottom),
faces = faces
);
polyhedron(
points = concat(tri2_top, tri2_bottom),
faces = faces
);
}
}
The built-in concat
module returns a vector containing the arguments of the given vectors. The preview is as below.
The code below also shows to use a mathematical function to draw a graph.
function f(x, y) = (pow(y,2)/pow(2, 2))-(pow(x,2)/pow(2, 2));
min_value = -3;
max_value = 3;
resolution = 0.5;
thickness = 1;
points = [
for(y = [min_value:resolution:max_value])
[
for(x = [min_value:resolution:max_value])
[x, y, f(x, y)]
]
];
faces = [
[2, 1, 0],
[3, 4, 5],
[0, 1, 4, 3],
[1, 2, 5, 4],
[2, 0, 3, 5]
];
z_offset = [0, 0, -thickness];
for(yi = [0:len(points) - 2]) {
for(xi = [0:len(points[yi]) - 2]) {
tri1_top = [
points[yi][xi],
points[yi][xi + 1],
points[yi + 1][xi + 1]
];
tri1_bottom = [
tri1_top[0] + z_offset,
tri1_top[1] + z_offset,
tri1_top[2] + z_offset
];
tri2_top = [
points[yi][xi],
points[yi + 1][xi + 1],
points[yi + 1][xi]
];
tri2_bottom = [
tri2_top[0] + z_offset,
tri2_top[1] + z_offset,
tri2_top[2] + z_offset
];
polyhedron(
points = concat(tri1_top, tri1_bottom),
faces = faces
);
polyhedron(
points = concat(tri2_top, tri2_bottom),
faces = faces
);
}
}
It's smoother than the previous graph created by cubes, right? And, the rendering speed is fast, too.
You may try to improve this example. For example, if you want to draw the mesh of a function graph, how can you do?
In my example code, we slice each rectangle from the bottom-left to the upper-right. How about providing an option to slice from the upper-left to the bottom-right?