January 6, 2019

Random Fantasy Map Generator Project - Part 0 - OpenGL

The first part of this project starts with the arguably simplest part - draw a line, in a window, on a Mac. Since Makefiles make me sad, I will be using Bazel to compile my C++. So, the first thing I am going to do is make a BUILD file for the top level executable, and one for the rendering library, and an empty top level WORKSPACE file.

The top level BUILD file:

cc_binary(
    name = "fantasy_map",
    srcs = ["fantasy_map.cpp"],
    deps = ["//render"]
)

The render BUILD file:

cc_library(
    name = "render",
    hdrs = ["render.hpp"],
    srcs = ["render.cpp"],
    linkopts = ["-framework OpenGL", "-lglfw", "-lglew"],
    visibility = ["//visibility:public"]
)

This also means I need a fantasy_map.cpp at the top level, and the render.hpp and render.cpp files in the render folder. After that, I have to make sure I have these dependencies on my machine.

To get the deps, I ran:

brew install glew
brew install glfw3
brew install glm

So, with a bunch of empty files, my folder structure looks like this:

├── BUILD
├── WORKSPACE
├── fantasy_map.cpp
└── render
    ├── BUILD
    ├── render.cpp
    └── render.hpp

I used this tutorial to help me start, since it's been a while since I've had to make a new GL program from scratch. I took the code in the tutorial and adapted it a bit into a C++ class, so that I can keep the GL code isolated and contained in its own little section of the program. It is far from beautiful, but we can refactor and fix it up later - for now it gets us what we want.

render.cpp

#include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>
#include <glm/glm.hpp>

#include "render.hpp"

namespace render {


void renderer::draw()
{
	if( !glfwInit() )
	{
		fprintf( stderr, "Failed to initialize GLFW\n" );
		getchar();
		return;
	}

	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
	window_ = glfwCreateWindow(1024, 768, "Render!", NULL, NULL);
	if( window_ == NULL ){
		fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
		getchar();
		glfwTerminate();
		return;
	}

	glfwSetInputMode(window_, GLFW_STICKY_KEYS, GL_TRUE);

	glfwMakeContextCurrent(window_);
	glfwSwapInterval( 1 );

	glViewport( 0, 0, 1024, 768 );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	glOrtho(0.0,1024.0,0.0,768.0,0.0,1.0);
	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

	do{
    	glClear( GL_COLOR_BUFFER_BIT );

		// Draw code goes here!

		glfwSwapBuffers(window_);
		glfwPollEvents();

	}
	while( glfwGetKey(window_, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
		   glfwWindowShouldClose(window_) == 0 );

	glfwTerminate();

	return;
};
} // namespace render

render.hpp

#include <GLFW/glfw3.h>

namespace render {
class renderer {
  public:
  void draw();
private:
  GLFWwindow* window_;

};
} // namespace render

fantasy_map.cpp

#include "render/render.hpp"

int main(int argc, char const *argv[])
{
	render::renderer renderer;
	renderer.draw();
	return 0;
}

With this, we have the bare minimum to make sure that OpenGL works and can give us a place to render our Voronoi diagram later on.

Let's compile the program ...

bazel build ...

... and run it:

./bazel-bin/fantasy_map

We should see something like this:

Our first OpenGL Window

So far so good! Now we want to draw a couple of lines and points, just to make sure we can. We are going to add some models to represent what we will draw, and while we are at it, let's change the signatures and callsite a bit, and add a render loop as well:

Models

struct point {
    float x;
    float y;
};

struct line {
    point a;
    point b;
};

Render Loop

void render_loop(const std::vector<line>& lines)
{
	glClearColor ( 1.0f, 1.0f, 1.0f, 1.0f );
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPointSize(10);
	glLineWidth(2.5);
	glColor3f(1.0, 0.0, 0.0);
	glBegin(GL_LINES);
	for (const auto& l : lines) {
	    glVertex3f(l.a.x, l.a.y,0.0);
	    glVertex3f(l.b.x, l.b.y,0.0);
	}
	glEnd();
}

Here render loop takes a vector of lines and renders them, but we still need to call the render loop, so in the do step of the draw method:

...
// Draw code goes here!
render_loop(lines);
...

But where do these lines come from? Let's pass them in from the top of our main method. Later on, when we finish our Voronoi Diagram producing code, we can change it to properly render the Diagram instead of our harcoded lines.

In fantasy_map.cpp

render::renderer renderer;
render::point a{10, 10};
render::point b{100, 100};

render::line line_a{a, b};
renderer.draw({line_a});

Recompile and run our code, and we should see:

Tada! A quick little class to render lines on OSX. Up next, we are going to take a step away from rendering and write Fortune's Algorithm to produce our Voronoi Diagram, and use our helpful little renderer to help make sure everything works.

The full source code will be made available on github once the project is finished.