# Function grapher

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.

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 = [
[0, 1, 2],
[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.

But, rendeing it will generate a warning message.

``````WARNING: Object may not be a valid 2-manifold and may need repair!
``````

Basically, it's not the problem of our code. The `polyhedron` should be “closed” explicitly by making every adjacent face really “attached” to each other. It's the problem of floating point precision when calculating. The problem causes non-attached faces. One simple way to solve the problem is using the `hull` operation. 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 = [
[0, 1, 2],
[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
];

// hull them to avoid non-attached faces
hull() polyhedron(
points = concat(tri1_top, tri1_bottom),
faces = faces
);

``````hull() 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?