Day 3 (Matrix) - C++ Builder pattern is... questionable
Jan 26, 2025
int main() {
AppBuilder config;
config.setWindowSize(SCR_WIDTH, SCR_HEIGHT);
config.setOpenGLVersion(3, 3);
config.setWindowName("LearnOpenGL");
// This is where bug happens (Guess why!)
App app = config.build();
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f), glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f)};
for (int i = 0; i < 10; i++) {
app->spawn(
CubeObject(cubePositions[i],
(glm::quat)glm::rotate(glm::quat(1.0f, 0.0f, 0.0f, 0.0f),
glm::radians(20.0f * i),
glm::vec3(1.0f, 0.3f, 0.5f))));
}
app->run();
return 0;
}
With an attempt to refactor the code into a builder pattern, I realized it’s not as clean as I thought. But it’s fine for now.
I am planning to refactor the inside of the App
class into a more modular design.
containing Shader
, Texture
, Model
(vertex buffer, vertex array, etc), and
the abstract Component
classes like Camera or Cube.
However, I ran into the biggest barrier of all:
I cannot find a way to register a callback function using a member function of a class.
member function in C++ doesn’t capture this
and return a function pointer.
Hence the error:
Reference to non-static member function must be called
I look up on the internet and found out that I have to use std::bind
but it seems like
for GLFW, it’s not possible to use std::bind
because it’s a C library (💀!!)
The solution is?
We define a WindowUserPointer
and pass the this
pointer to it.
glfwSetWindowUserPointer(window, this);
Then we get the this
pointer back in the callback function instead of
doing the reverse: capturing this
into the callback function.
glfwSetCursorPosCallback(window, [](GLFWwindow *w, double x, double y) {
static_cast<App *>(glfwGetWindowUserPointer(w))->mouse_callback(w, x, y);
});
glfwSetScrollCallback(window, [](GLFWwindow *w, double x, double y) {
static_cast<App *>(glfwGetWindowUserPointer(w))->scroll_callback(w, x, y);
});
And that’s how you do it!
In my opinion it just feel like an elaborated global variable but it’s fine for now.
Although we only have one instance of App
and one instance of Window
, doing this
feels a bit overkill. but alr we ball.
Camera doesn’t move…
There is a very strange bug where the camera doesn’t orbit (rotate) but does translate.
It seems like the value are being updated properly but the camera doesn’t move.
The front
vector of the camera in the event loop doesn’t follow the updated value
for some reason.
I didn’t expect this to be a bug until I actually log the important values out.
Guess what’s wrong with this code?
App AppBuilder::build() {
app.init();
return app;
}
App app = config.build();
The App
object is being copied instead of being referenced!
So the .init()
that we called in the build
function did init the app
that belongs to the config
The returned app is also the app
that belongs to the config
.
Only when you write it too App app
it will be passed by value.
Hence, the callbacks that we registered stayed in the config
object and not the copied app
object.
The reason why translating still works because it triggers the function in the event loop directly, not through registering a callback.
I have two choices which are:
Change to return by reference
App *AppBuilder::build() {
app.init();
return &app;
}
or do not init in the build
function
App AppBuilder::build() {
return app;
}
App app = config.build();
app.init();
The answer is surprisingly, not both.
std::unique_ptr
Instead, we
use something called unique_ptr
which is a smart pointer that
will dictate the ownership of the object, in the case, the caller of the build
function.
std::unique_ptr<App> AppBuilder::build() {
auto app = std::make_unique<App>();
app.init();
return app;
}
Also to remove those ->
, we can use the &
operator to the function
AppBuilder &setWindowSize(int width, int height) {
window_width = width;
window_height = height;
return *this;
}
This is like fn set_window_size(&mut self, width: i32, height: i32) -> &mut Self
in Rust.
int main() {
auto app = AppBuilder()
.setWindowSize(SCR_WIDTH, SCR_HEIGHT)
.setOpenGLVersion(3, 3)
.setWindowName("LearnOpenGL")
.build();
for (int i = 0; i < 10; i++) {
app.spawn(
CubeObject(cubePositions[i],
(glm::quat)glm::rotate(glm::quat(1.0f, 0.0f, 0.0f, 0.0f),
glm::radians(20.0f * i),
glm::vec3(1.0f, 0.3f, 0.5f))));
}
app.insert_texture(*TextureLoader()
.from_path("textures/container.jpg")
.set_wrap_s(Wrap::REPEAT)
.set_wrap_t(Wrap::REPEAT)
.set_min_filter(MinFilter::LINEAR_MIPMAP_LINEAR)
.set_mag_filter(MagFilter::NEAREST)
.set_flip_vertically(true)
.load());
app.run();
return 0;
}
So beautiful… I feel like a proud dad.
Day 3
Day 3 is about matrix transformation and camera. Transformation is basically local -> world -> view -> projection. So a nested matrix multiplication.
Camera was heavy on the lookAt but glm
has lookAt built in so nothing to worry about.
and yeah