This project is about creating a simplified 3D graphic representation of a relief landscape. (in C)
In this project you will discover the basics of graphic programming, and in particular how to place points in space, how to join them with segments and most importantly how to observe the scene from a particular viewpoint.
You will also discover your first graphic library: miniLibX. This library was developed internally and includes the minimum necessary to open a window, light a pixel and deal with events linked to this window: keyboard and mouse. This project introduces you to “events” programming. [email protected]
Many thanks to:
Original gif Generated by fdf
Given a .fdf file with this format
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 10 10 0 0 10 10 0 0 0 10 10 10 10 10 0 0 0
0 0 10 10 0 0 10 10 0 0 0 0 0 0 0 10 10 0 0
0 0 10 10 0 0 10 10 0 0 0 0 0 0 0 10 10 0 0
0 0 10 10 10 10 10 10 0 0 0 0 10 10 10 10 0 0 0
0 0 0 10 10 10 10 10 0 0 0 10 10 0 0 0 0 0 0
0 0 0 0 0 0 10 10 0 0 0 10 10 0 0 0 0 0 0
0 0 0 0 0 0 10 10 0 0 0 10 10 10 10 10 10 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Each number represents a point with a height in integer.
The goal is to draw a line connecting each point and represent them all together as a landscape.
There is no set limit of how big the fdf files should be. However, a valid file should have same amount of points on each line. Each line is ended with a newline. There are no extra empty lines between lines, at the end or at the beginning of the file. Value of the points must be within integer limit.
To further represent a landscape, an integer represent in hex for color can be added behind the points separated by a comma.
0,0xffffff 0,ffffff
0,0xFFFFFF 0,FFFFFF
# All are valid representation of color white
0,0xff 0,0x0000ff
0,0xFF 0,0x0000FF
# Works with 0 paddings as well.
In order to read the files, students are only allowed to use open() and read().
The 2nd project get_next_line() at 42 is the main key for me to parse the file and grab each line. It works similar to getline(3) function.
prototype:
int get_next_line(const int fd, char **line);
returns
-1 if there's an error 0 if end of file 1 if successful
After grabbing the line, I use ft_strsplit(), a function that I made in my first libft project at 42, to split all the digits up using a space (' ') as a delimiter or 32 in ascii. After splitting them up, I use ft_atoi() similar to atoi() to convert them into integers. I also use ft_strsplit() with (',') as delimiter to extract the color behind the points. To convert the hex to integer, I make a simple ft_atoi_base() that allows me to convert the hex string into integer using base 16. I store the points in t_map struct.
typedef struct s_map
{
int *map; // store points
int *c_map; // store color
int map_h; // height (how many lines in the file)
int map_w; // width (how many points in a line)
int map_size; // width x height
bool map_color; // true if color mapping exist, false then use default color.
} t_map;
After seeing so many students at the school use the Bresenham's line algorithm for their project, I decided to try to Xiaolin Wu's algorithm instead. Since I cannot have more than 5 variables declared in a function, as stated by school rule, I have to use a struct to store them.
typedef struct s_var
{
bool steep;
bool swap; // color swap
bool swap_d; // color swap
double dx;
double dy;
double gradient;
double xend;
double yend;
double xgap;
double ygap;
int xpxl1;
int ypxl1;
double intery;
double interx;
int xpxl2;
int ypxl2;
} t_var;
The wikipedia does not declare variables type, so in the beginning all my variables are doubles. But after referencing the
rosettacode, I decide to change some doubles to int.
It is not hard to implement the wikipedia algorithm. However I run into issues with color and lightning (brightness).
if (var->steep)
{
var->swap = true;
ft_swap(&(p0->x), &(p0->y));
ft_swap(&(p1->x), &(p1->y));
}
if (p0->x > p1->x)
{
if (!var->steep)
var->swap = true;
var->swap_d = true;
ft_swap(&(p0->x), &(p1->x));
ft_swap(&(p0->y), &(p1->y));
}
The points are being swapped around in the algorithm. Each of the points has a certain rgb value and having them swapped around causes all sort of problems for me when I implement color gradient. That is the main reason why I have to add boolean variables to keep track of what's being swapped so the program will draw correct color.
# Debug mode
...
bright: 0.658824 percent: 0.811765 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(55, 39, 23)
bright: 0.882353 percent: 0.823529 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(74, 53, 31)
bright: 0.117647 percent: 0.823529 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(9, 7, 4)
bright: 0.423529 percent: 0.835294 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(35, 25, 15)
bright: 0.576471 percent: 0.835294 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(48, 34, 20)
bright: 0.964706 percent: 0.847059 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(81, 58, 34)
bright: 0.035294 percent: 0.847059 rgb_start(87,63,38) rgb_end(84,60,35) rgb_ret(2, 2, 1)
...
ret.r = ((start.r * (1.0 - percent)) + (end.r * percent)) * brightness;
Looking at the value, you can see that the brightness never reaches 1.0 (maximum brightness). That means, the pixel I got out of the algorithm never reaches their true color. The colors return are much dimmer. To counter it, I just add 0.2 brightness to make them 20% brighter. To fully make it brightest possible, I modify my main loop like so.
// before
plot_pixel(fdf, var.xpxl1, ipart(var.intery), get_color(p0, p1, percent, rfpart(var.intery))); //anti-aliasing
// after
plot_pixel(fdf, var.xpxl1, ipart(var.intery), get_color(p0, p1, percent, 1.0)); // not anti-aliasing
By increasing the brightness to 1.0 for every pixel, the lines are no longer anti-aliasing. So basically I have "2" line algorithm inside the program. After the switch, I can see that the lines are twice as thick in parallel view. I make the anti-alias button for users to see the difference.
Anti-aliasing with 20% increased With 100% brightness
(only tested on this machine)
about this mac
iMac (Retina 5K, 27-inch, Late 2015)
3.2 GHz Intel Core i5
AMD Radeon R9 M380 2048 MB
gcc --version
Apple LLVM version 9.1.0 (clang-902.0.39.1)
Target: x86_64-apple-darwin17.5.0
Thread model: posix
make --version
GNU Make 3.81
make
./fdf [path to .fdf files]
Pressing S will enter shell mode.
You can modify fdf maps while the progam is running by using built in fdf_shell command
#for example
change maps maps/t1.fdf // change maps to t1.fdf
change brightness 100 // change brightness to 100
animate maps/animate/matrix_bullet_fdf // enter animation mode and read multiple fdf files
Use the resume command to resume the program.
There are multiple linear gradients in the themes I implement. I intend the colors to work like a color ramp. 0 height will be the middle color. Highest z will have the bottom color. Lowest z will have the top color. To get the color index, I have the following function:
int get_color_index(int z, int size) // size is how many nodes in the color ramp.
{
int idx_steps;
int mid_idx;
idx_steps = 1000 / size; // since there are no maps with z over +-1000, I just leave 1000 as default
mid_idx = size / 2;
if (!idx_steps)
idx_steps = 1;
mid_idx += (z / idx_steps);
if (mid_idx < 0)
mid_idx = 0;
if (mid_idx >= size)
mid_idx = size - 1;
return (mid_idx);
Colors macros I got are from wikipedia about web colors. Macro naming is same with X11 color naming.
You can customize your own theme (custom default is black to white) in fdf_theme.c.
void fdf_theme_custom(t_ramp **ramp)
{
color_ramp(ramp, FDF_A, steps, FDF_B); //starting color ramp FDF_A is for lowest z
...
color_ramp(ramp, FDF_Y, steps, FDF_Z); //ending color ramp FDF_Z is for highest z
}
Gulf of California Ocean
Parallel view:
![]() Without anti-aliasing |
![]() With anti-aliasing |
Isometric view:
for f in [BMP FOLDER PATH]/*.bmp; do
python3 pixel.py $f [OUTPUT FOLDER PATH]; done
# replace PREFIX with name of your file without numbers at the end
printf '%s\n' *(n) | cat -n | while read n f; do mv $f `printf "[PREFIX]%04d.fdf" $n`; done
# Your files now have 0 paddings.
You can get results like these:
Fighting Climax Ignition: Shana
path: maps/animate/shana_combo_fdf
Matrix bullet dodging
path: maps/animate/matrix_bullet_fdf
Isopoly for "3D"
path: maps/animate/isopoly_fdf
// Something like this
void delay(int milliseconds)
{
unsigned long pause;
clock_t now;
clock_t then;
pause = milliseconds * (CLOCKS_PER_SEC / 1000);
then = clock();
now = then;
while ((now - then) < pause)
now = clock();
}
in fdf_minishell use
change delay [integer] (milliseconds)
09/08/2019
09/07/2019
09/06/2019
09/05/2019