# A second Pi Project: 3D Xmas Trees!

Posted by shabaz in Get Started With Pi on Jul 29, 2017 7:06:00 PM# Introduction

In a previous blog, there was a project for complete beginners, to draw a tree with zero programming experience required.

In this blog post, we go 3D. Again, no programming experience is needed to follow this project and extend it to draw different 3D objects. We will also rotate objects.

# Designing the Tree

First off, a tree was sketched on graph paper. It was decided that the origin (0,0) point would be at the bottom of the tree, so that the base of the tree trunk has co-ordinates (-10,0) and (10,0) as shown in the sketch. All the other points were also marked onto the graph paper.

To get this representation into the computer, one could use lots of line statements as in the previous blog post, such as the following to draw the base of the trunk:

g2_line(id, -10, 0, 10, 0);

Such a technique would result in a lot of code! Ordinarily the desire is to write as little as possible. There is a theory that for every ten lines of code that is written, there will likely be one software bug. It is better to write a 10-line program and fix the (likely) single bug, than write 100 lines and search for 10 bugs.

Nevertheless, there are 15 co-ordinates in the sketch, and that will likely translate to at least 15 lines of code to represent the co-ordinates. Perhaps it could be halved by recognizing that there is symmetry in the tree, but for now code won’t be so optimized.

The 15 points can be just typed out. Since the tree is to be in three dimensions, we will actually use (x,y,z) co-ordinates, not (x,y):

double points[15][3]={ {-10, 0, 0}, {-10, 40, 0}, {-70, 30, 0}, {-20, 80, 0}, {-60, 70, 0}, {-10, 120, 0}, {-40, 110, 0}, {0, 160, 0}, {40, 110, 0}, {10, 120, 0}, {60, 70, 0}, {20, 80, 0}, {70, 30, 0}, {10, 40, 0}, {10, 0, 0}};

This is really a flat 2D Xmas tree in three dimensions, but there is nothing stopping anyone from designing a more fancy truly 3-dimensional tree.

Although the numbers are in blocks of three, in reality the computer stores all these numbers in one long block of memory. The memory will contain -10, 0, 0, -10, 40, 0, -70, 30, 0….

Notice the curly brackets (also known as curly braces). Generally you can provide the computer with a sequence or array of numbers by placing them inside curly braces and separating each number with a comma.

The example above is more complex because there are curly braces within the outer curly braces. There is a sequence of fifteen sequences of three numbers representing x, y and z values.

# Rotation

Sines and cosines can be used to rotate points by any desired angle. It is beyond the scope of this blog post, but code was written to perform the rotation. It isn’t important to know how it works, just that it can take a three-dimensional point and an angle, and spit out a modified 3-dimensional result. A vector can be rotated using sine and cosines, and the precise formula isn’t essential to know (it is part of math lessons for schoolkids around the age of 16-18, but even if you have not studied this, you could use google to find the right formula).

So, a rotation of 90 degrees about the y-axis could be imagined to be a paper cut-out tree that was held vertically and turned by 90 degrees. A rotation of 90 degrees about the x-axis would result in the paper being placed horizontally on a table. 90 degrees about the z-axis would result in the tree sideways on edge on the table.

Here is the code to perform rotation about the y-axis:

r[0]=cos(a)*p[0]-sin(a)*p[2]; r[1]=p[1]; r[2]=sin(a)*p[0]+cos(a)*p[2];

In this code, r[0] represents the x co-ordinate of the result, and r[1] represents the y co-ordinate of the result. The letter 'a' is a variable that contains the desired rotation angle.

The input x, y, and z co-ordinates are p[0], p[1] and p[2] respectively. The numbers in the square brackets are an index, the computer stores all these values in memory in sequence. It was just convenient in the follow-on code for these values to be in a sequential array for easier storage in memory, because we want to save many co-ordinates in memory, for the entire Xmas tree. Whenever more than one item needs to be stored, it is worth exploring if an array of values can be used instead; it can save coding effort.

# Isometric View

The screen only displays two dimensions so some form of translation is needed from the 3D world to 2D. In this example, we do an isometric transformation and again the exact formula for this isn’t important. The important thing is that the result is an array of two-dimensional points. The formula takes the x,y,x co-ordinates and converts then to x,y co-ordinates.

Finally, all the mapped points are drawn connected by lines.

# Putting it all together

The entire code is shown here. Like the earlier blog post, it isn’t necessary to have a detailed understanding to begin to see what this code does. Examining the code, it can be seen that the first few lines are a description. As before, the next lines are referring to g2 which is the graphics library.

Further down there are some lines with the text ‘xrot’, ‘yrot’ and ‘zrot’ and this is the code that takes a 3D point and rotates it by an angle.

There is also some code called ‘to2d’ that converts a 3D point into a location on a two-dimensional isometric projection.

After that there is code titled ‘main function’. The array of the fifteen 3D points of the Xmas tree are there, and it is followed by some code that prints a message on the screen and prompts the user to type in an angle. All fifteen points are then rotated by that angle, and then converted to isometric projections. Finally, the lines are drawn between each point and the next consecutive point, to ‘join the dots’.

/**************************** * 3D Xmas Tree * * rev 1 June 2017 ****************************/ // include #include <stdio.h> #include <math.h> #include <g2.h> #include <g2_X11.h> // defines #define width 400 #define height 300 #define NUMPOINTS 15 #define WHITE 0 #define BLACK 1 #define BLUE 3 #define GREEN 7 #define RED 19 #define YELLOW 25 /**************************** * functions ****************************/ // rotation functions // these rotate a 3D point p by angle a, // and store the result in r void xrot(double* p, double a, double* r) { r[0]=p[0]; r[1]=cos(a)*p[1]+sin(a)*p[2]; r[2]=cos(a)*p[2]-sin(a)*p[1]; } void yrot(double* p, double a, double* r) { r[0]=cos(a)*p[0]-sin(a)*p[2]; r[1]=p[1]; r[2]=sin(a)*p[0]+cos(a)*p[2]; } void zrot(double* p, double a, double* r) { r[0]=cos(a)*p[0]+sin(a)*p[1]; r[1]=cos(a)*p[1]-sin(a)*p[0]; r[2]=p[2]; } // 3D to 2D mapping function // This maps a 3D point p to a 2D point r // in an isometric style representation void to2d(double* p, double* r) { r[0]=(p[0]-p[2])*sqrt(3)/2; r[1]=((p[0]+p[2])*0.5)+p[1]; } /**************************** * main function ****************************/ int main(void) { int id; int i; double angle; int forever=1; // These points represent an Xmas tree double points[NUMPOINTS][3]={ {-10, 0, 0}, {-10, 40, 0}, {-70, 30, 0}, {-20, 80, 0}, {-60, 70, 0}, {-10, 120, 0}, {-40, 110, 0}, {0, 160, 0}, {40, 110, 0}, {10, 120, 0}, {60, 70, 0}, {20, 80, 0}, {70, 30, 0}, {10, 40, 0}, {10, 0, 0}}; double points_manip[NUMPOINTS][3]; double points2d[NUMPOINTS][2]; id=g2_open_X11(width, height); while(forever) { printf("Enter an angle in degrees (0-360): "); scanf("%lf", &angle); printf("angle is %f\n", angle); angle=angle*3.14/180; g2_clear(id); g2_pen(id, BLACK); for (i=0; i<NUMPOINTS; i++) { // rotate yrot(points[i], angle, points_manip[i]); // get the 2D point to2d(points_manip[i], points2d[i]); // move (translate) the 2D point so that it sits on the screen points2d[i][0]+=100; points2d[i][1]+=100; } // draw the 2D lines for (i=0; i<NUMPOINTS-1; i++) { g2_line(id, points2d[i][0], points2d[i][1], points2d[i+1][0], points2d[i+1][1]); } g2_line(id, points2d[NUMPOINTS-1][0], points2d[NUMPOINTS-1][1], points2d[0][0], points2d[0][1]); } // end of while(forever) // these lines won't ever execute getchar(); g2_close(id); return(0); }

To run the code, follow the steps in the earlier blog post to install the graphics library, and then save the code here in a file called (say) 3dtree.c and then type the following to compile the code:

gcc 3dtree.c -lg2 -lm -o 3dtree

To run it, type:

./3dtree

The program will prompt you to type an angle and then press Enter. It will draw the tree rotated at that angle.

# Next Steps

As an experiment, try to imagine a cube in 3D space, write down its x,y,z co-ordinates for all eight corners of the cube, and then rotate and convert to isometric. It would be great to see people’s attempts.

In summary the code in this blog demonstrated a few techniques; by experimenting with the code and trying to create new shapes to rotate, the power of arrays and 'for' loops is exercised. The ability to take user input and printing messages is very useful when debugging. Not all the code will be understandable by a complete beginner, but enough is understandable to be able to copy the code and modify it to draw and manipulate complex shapes under user control. The rest of the code (including all the brackets and semi-colons and so on) will begin to make sense as more software based projects are attempted; there is no need to try to understand it all at once.

Here is my attempt at drawing the death star using same code, very slightly modified. The circles are actually made of lines; the array of points consisted of 40 points per circle and therefore when the points were joined by straight lines, it approximated a circle.

The code of course still allows the object to be rotated by selecting a desired angle. It would be interesting to rotate a physical object at the same time. Servos are useful for that! Another idea would be to read input from an external sensor such as an accelerometer, and move the 3D representation accordingly.

One could also take the ideas here and (say) draw a robot arm in 3D and move a real 3D arm at the same time.

## Comments