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?