main.odin
main :: proc() {
run_engine()
}main :: proc() {
run_engine()
}clear_window :: proc() {
gl.ClearColor(0.20, 0.20, 0.20, 1.0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
}delete_engine :: proc() {
delete_VBO(&vbo)
delete_VAO(&vao)
delete_window()
}enable_feature :: proc(cap: u32) {
gl.Enable(cap)
}init_engine :: proc() {
context.logger = log.create_console_logger()
// setup window
SUCCESS = init_window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
if !SUCCESS {
log.error("Failed to initialize window")
} else {
log.info("Window initialized successfully")
}
// setup shader
shader, SUCCESS = init_shader(VERTEX_PATH, FRAGMENT_PATH)
if !SUCCESS {
log.error("Failed to compile shader file")
return
} else {
log.info("Shader file compiled successfully")
}
// setup config
enable_feature(gl.DEPTH_TEST)
set_input_mode()
set_glfw_callback()
// setup texture
texture_01 = init_texture(gl.TEXTURE_2D, gl.REPEAT, gl.LINEAR_MIPMAP_LINEAR, gl.LINEAR)
SUCCESS = load_texture(TEXTURE_PATH_01, gl.TEXTURE_2D, gl.RGB, gl.RGB)
if !SUCCESS {
log.errorf("Failed to load texture: %v", texture_01.id)
} else {
log.infof("Texture loaded successfully: %v", texture_01.id)
}
// setup buffers
vao, SUCCESS = create_VAO()
if !SUCCESS {
log.errorf("Failed to create vertex array object: %v", vao.id)
} else {
log.infof("Vertex array object created successfully: %v", vao.id)
}
vbo, SUCCESS = create_VBO()
if !SUCCESS {
log.errorf("Failed to create vertex buffer object: %v", vbo.id)
} else {
log.infof("Vertex buffer object created successfully: %v", vbo.id)
}
// bind buffers
SUCCESS = bind_VAO(&vao)
if !SUCCESS {
log.errorf("Failed to bind vertex array object: %v", vao.id)
} else {
log.infof("Vertex array object binded successfully: %v", vao.id)
}
SUCCESS = bind_VBO(&vbo, cube_vertices, gl.STATIC_DRAW)
if !SUCCESS {
log.errorf("Failed to bind vertex buffer object: %v", vbo.id)
} else {
log.infof("Vertex buffer object binded successfully: %v", vbo.id)
}
// link buffers
// position attribute
stride : i32 = 5
SUCCESS = link_attrib(0, 3, stride, 0)
if !SUCCESS {
log.errorf("Failed to link attributes to vertex array object: %v", vao.id)
} else {
log.infof("Linked attributes to vertex array object successfully: %v", vao.id)
}
// texture attribute
SUCCESS = link_attrib(1, 2, stride, 3)
if !SUCCESS {
log.errorf("Failed to link attributes to vertex array object: %v", vao.id)
} else {
log.infof("Linked attributes to vertex array object successfully: %v", vao.id)
}
// setup camera
SUCCESS = init_camera(&camera)
if !SUCCESS {
log.error("Failed to initialize camera")
} else {
log.info("Camera initialize successfully")
}
camera.aspect_ratio = {f32(WINDOW_WIDTH), f32(WINDOW_HEIGHT)}
}post_render_engine :: proc() {
swap_buffer_window()
}pre_render_engine :: proc() {
clear_window()
set_current_window_size()
activate_texture(gl.TEXTURE0)
bind_texture(gl.TEXTURE_2D, texture_01)
use_shader(shader)
camera_projection := get_camera_mode(.PERSPECTIVE, &camera)
set_uniform(shader, "projection", &camera_projection)
camera_view := get_camera_view(&camera)
set_uniform(shader, "view", &camera_view)
}process_input :: proc() {
set_deltatime()
close_window(window)
move_camera(&camera)
reset_camera(&camera)
}render_engine :: proc() {
bind_VAO(&vao)
for i in 0..<10 {
init_transform(&cube_transform)
angle := 20.0 * i
move(cube_positions[i], &cube_transform)
rotate(f32(angle), {1.0, 0.3, 0.5}, &cube_transform)
apply_transform(shader, &cube_transform)
gl.DrawArrays(gl.TRIANGLES, 0, 36)
}
}run_engine :: proc() {
init_engine()
defer delete_engine()
init_ui()
defer delete_ui()
for (!window_close()) {
process_input()
pre_render_engine()
render_engine()
pre_render_ui()
draw_status_bottom_bar_ui(&camera)
render_ui()
post_render_engine()
}
}set_glfw_callback :: proc() {
glfw.SetWindowUserPointer(window, &camera)
glfw.SetCursorPosCallback(window, mouse_callback)
glfw.SetScrollCallback(window, mouse_scroll_callback)
}delete_window :: proc() {
glfw.Terminate()
}@(private="file")
fb_size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
gl.Viewport(0, 0, width, height)
}init_window :: proc(window_width: i32, window_height: i32, window_title: cstring) -> (bool) {
if !glfw.Init() {
return false
}
glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, GL_VERSION_MAJOR)
glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, GL_VERSION_MINOR)
glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
window = glfw.CreateWindow(window_width, window_height, window_title, nil, nil)
if window == nil {
glfw.Terminate()
return false
}
glfw.MakeContextCurrent(window)
gl.load_up_to(GL_VERSION_MAJOR, GL_VERSION_MINOR, glfw.gl_set_proc_address)
log.info(gl.GetString(gl.VERSION))
glfw.SetFramebufferSizeCallback(window, fb_size_callback)
return true
}set_current_window_size :: proc() {
current_window_width, current_window_height = glfw.GetWindowSize(window)
}set_deltatime :: proc () {
current_frame := glfw.GetTime()
delta_time = current_frame - last_frame
last_frame = current_frame
}swap_buffer_window :: proc() {
glfw.SwapBuffers(window)
glfw.PollEvents()
}window_close :: proc() -> bool {
for (!glfw.WindowShouldClose(window)) {
return false
}
return true
}Shader :: struct {
id: u32,
}init_shader :: proc(vertex_path, fragment_path: string) -> (Shader, bool) {
// Read file
program, ok := gl.load_shaders_file(vertex_path, fragment_path)
if !ok {
return {}, false
}
return Shader{id = program}, true
}set_uniform :: proc(shader: Shader, name: cstring, value: ^matrix[4,4]f32) {
location := gl.GetUniformLocation(shader.id, name)
gl.UniformMatrix4fv(location, 1, gl.FALSE, &value[0][0])
}use_shader :: proc(shader: Shader) {
gl.UseProgram(shader.id)
}Texture :: struct {
id: u32,
}activate_texture :: proc (texture: u32) {
gl.ActiveTexture(texture)
}bind_texture :: proc (target: u32, texture: Texture) {
gl.BindTexture(target, texture.id)
}@require_results
init_texture :: proc (target: u32, wrap, filter_min, filter_max: i32) -> (Texture) {
texture: u32
gl.GenTextures(1, &texture)
gl.BindTexture(target, texture)
gl.TexParameteri(target, gl.TEXTURE_WRAP_S, wrap)
gl.TexParameteri(target, gl.TEXTURE_WRAP_T, wrap)
gl.TexParameteri(target, gl.TEXTURE_MIN_FILTER, filter_min)
gl.TexParameteri(target, gl.TEXTURE_MAG_FILTER, filter_max)
return Texture{id = texture}
}load_texture :: proc (filepath: cstring, target: u32, internal_format:i32, format:u32) -> (bool) {
width, height, num_channel : i32
data := stb.load(filepath, &width, &height, &num_channel, 0)
if (data == nil) {
return false
} else {
gl.TexImage2D(target, 0, internal_format, width, height, 0, format, gl.UNSIGNED_BYTE, data)
gl.GenerateMipmap(target)
return true
}
stb.image_free(data)
return true
}EBO :: struct {
id: u32,
}VAO :: struct {
id: u32,
}VBO :: struct {
id: u32,
}bind_EBO :: proc(ebo:^EBO, indices: []u32, usage:u32) -> (bool) {
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo.id)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(indices) * len(indices), raw_data(indices), usage)
return true
}bind_VAO :: proc(vao:^VAO) -> (bool) {
gl.BindVertexArray(vao.id)
return true
}bind_VBO :: proc(vbo:^VBO, vertices: []f32, usage:u32) -> (bool) {
gl.BindBuffer(gl.ARRAY_BUFFER, vbo.id)
gl.BufferData(gl.ARRAY_BUFFER, size_of(vertices) * len(vertices), raw_data(vertices), usage)
return true
}@require_results
create_EBO :: proc() -> (EBO, bool) {
ebo_id: u32
gl.GenBuffers(1, &ebo_id)
return EBO{id = ebo_id}, true
}@require_results
create_VAO :: proc() -> (VAO, bool) {
vao_id: u32
gl.GenVertexArrays(1, &vao_id)
return VAO{id = vao_id}, true
}@require_results
create_VBO :: proc() -> (VBO, bool) {
vbo_id: u32
gl.GenBuffers(1, &vbo_id)
return VBO{id = vbo_id}, true
}delete_EBO :: proc(ebo:^EBO) {
gl.DeleteBuffers(1, &ebo.id)
}delete_VAO :: proc(vao:^VAO) {
gl.DeleteVertexArrays(1, &vao.id)
}delete_VBO :: proc(vbo:^VBO) {
gl.DeleteBuffers(1, &vbo.id)
}link_attrib :: proc(index: u32, size, stride: i32, offset: u32) -> (bool) {
gl.VertexAttribPointer(index, size, gl.FLOAT, false, stride * size_of(f32), uintptr(offset * size_of(f32)))
gl.EnableVertexAttribArray(index)
return true
}Camera :: struct {
pos: la.Vector3f32,
rot: la.Vector3f32,
target: la.Vector3f32,
up: la.Vector3f32,
fov: f32,
aspect_ratio: la.Vector2f32,
near: f32,
far: f32,
}Camera_mode :: enum {
PERSPECTIVE,
ORTHOGRAPHIC,
}@require_results
get_camera_mode :: proc(cam_mode: Camera_mode, camera: ^Camera) -> la.Matrix4f32 {
switch cam_mode {
case .PERSPECTIVE:
return la.matrix4_perspective_f32(ma.to_radians_f32(camera.fov), f32(camera.aspect_ratio.x) / f32(camera.aspect_ratio.y), camera.near, camera.far)
// not yet implement ortho logic
case .ORTHOGRAPHIC:
return 0
}
return 0
}@require_results
get_camera_view :: proc(camera: ^Camera) -> la.Matrix4f32 {
return la.matrix4_look_at_f32(camera.pos, camera.pos + camera.target, camera.up)
}init_camera :: proc(camera: ^Camera) -> (bool) {
pitch : f32 = 0.0
yaw : f32 = -90.0
camera.pos = {0.0, 0.0, 3.0}
camera.rot = {pitch, yaw, 0.0}
camera.target = {0.0, 0.0, -1.0}
camera.up = {0.0, 1.0, 0.0}
camera.fov = 45.0
camera.near = 0.1
camera.far = 100.0
return true
}close_window:: proc(window: glfw.WindowHandle) {
if (glfw.GetKey(window, glfw.KEY_ESCAPE) == glfw.PRESS) {
glfw.SetWindowShouldClose(window, true)
}
}mouse_callback :: proc "c" (window: glfw.WindowHandle, x_pos: f64, y_pos: f64) {
camera_ptr := (^Camera)(glfw.GetWindowUserPointer(window))
if (first_mouse) {
last_x = f32(x_pos)
last_y = f32(y_pos)
first_mouse = false
}
x_offset := f32(x_pos) - last_x
y_offset := last_y - f32(y_pos)
last_x = f32(x_pos)
last_y = f32(y_pos)
sensitivity : f32 = 0.1
x_offset *= sensitivity
y_offset *= sensitivity
camera_ptr.rot.y += x_offset
camera_ptr.rot.x += y_offset
// limit pitch
if (camera_ptr.rot.x > 89.0) {camera_ptr.rot.x = 89.0}
if (camera_ptr.rot.x < -89.0) {camera_ptr.rot.x = -89.0}
direction : la.Vector3f32
direction.x = ma.cos(ma.to_radians_f32(camera_ptr.rot.y)) * ma.cos(ma.to_radians_f32(camera_ptr.rot.x))
direction.y = ma.sin(ma.to_radians_f32(camera_ptr.rot.x))
direction.z = ma.sin(ma.to_radians_f32(camera_ptr.rot.y)) * ma.cos(ma.to_radians_f32(camera_ptr.rot.x))
camera_ptr.target = la.normalize(direction)
}mouse_scroll_callback :: proc "c" (window: glfw.WindowHandle, x_offset, y_offset: f64) {
camera_ptr := (^Camera)(glfw.GetWindowUserPointer(window))
// limit field of view
camera_ptr.fov -= f32(y_offset)
if (camera_ptr.fov < 1.0) {camera_ptr.fov = 1.0}
if (camera_ptr.fov > 45.0) {camera_ptr.fov = 45.0}
}move_camera:: proc(camera: ^Camera) {
base_camera_speed := 4.0 * f32(delta_time)
camera_speed_multiplier := 6.0
current_camera_speed := base_camera_speed
// increase & revert camera speed based on shift state
if (glfw.GetKey(window, glfw.KEY_LEFT_SHIFT) == glfw.PRESS) {
current_camera_speed = base_camera_speed * f32(camera_speed_multiplier)
} else if (glfw.GetKey(window, glfw.KEY_LEFT_SHIFT) == glfw.RELEASE) {
current_camera_speed = base_camera_speed
}
// move camera
if (glfw.GetKey(window, glfw.KEY_W) == glfw.PRESS) {
camera.pos += f32(current_camera_speed) * camera.target
} else if (glfw.GetKey(window, glfw.KEY_S) == glfw.PRESS) {
camera.pos -= f32(current_camera_speed) * camera.target
} else if (glfw.GetKey(window, glfw.KEY_A) == glfw.PRESS) {
camera.pos -= la.normalize(la.cross(camera.target, camera.up)) * f32(current_camera_speed)
} else if (glfw.GetKey(window, glfw.KEY_D) == glfw.PRESS) {
camera.pos += la.normalize(la.cross(camera.target, camera.up)) * f32(current_camera_speed)
}
}reset_camera:: proc(camera: ^Camera) {
if (glfw.GetKey(window, glfw.KEY_R) == glfw.PRESS) {
camera.pos = {0.0, 0.0, 0.0}
camera.fov = 45.0
}
}set_input_mode :: proc() {
glfw.SetInputMode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
}Transform :: struct {
id : la.Matrix4f32,
}apply_transform :: proc(shader: Shader, trans: ^Transform) {
set_uniform(shader, "model", &trans.id )
}init_transform :: proc(trans: ^Transform) {
trans.id = la.MATRIX4F32_IDENTITY
}move :: proc(obj: la.Vector3f32, trans: ^Transform) {
trans.id = trans.id * la.matrix4_translate_f32(obj)
}rotate :: proc(angle: f32, v:la.Vector3f32, trans: ^Transform) {
trans.id = trans.id * la.matrix4_rotate_f32(ma.to_radians(angle), v)
}scale :: proc(v:la.Vector3f32, trans: ^Transform) {
trans.id = trans.id * la.matrix4_scale_f32(v)
}delete_ui :: proc() {
imgui_impl_opengl3.Shutdown()
imgui_impl_glfw.Shutdown()
im.DestroyContext()
}draw_f32_colored_ui :: proc(label: string, format: cstring, v: f32) {
im.Text("%s: ", label)
im.SameLine(0, 0)
im.TextColored({1.0, 1.0, 0.4, 1.0}, format, v) // X - Red
im.SameLine(0, 4)
}draw_status_bottom_bar_ui :: proc(camera: ^Camera) {
bar_height : f32 = 25.0
im.SetNextWindowPos({0, f32(current_window_height) - bar_height})
im.SetNextWindowSize({f32(current_window_width) + 2, bar_height})
flags := im.WindowFlags{.NoTitleBar, .NoResize, .NoMove, .NoScrollbar, .NoBackground}
im.Begin("Status Bar", nil, flags)
draw_f32_colored_ui("FPS", "%.2f", f32(1.0 / delta_time))
im.SameLine()
im.Text("|")
im.SameLine()
draw_f32_colored_ui("DT", "%.4f", f32(delta_time))
im.SameLine()
im.Text("|")
im.SameLine()
im.Text("=O=")
im.SameLine()
draw_vec3_colored_ui("Pos", camera.pos)
im.SameLine()
im.Text("*")
im.SameLine()
draw_vec2_colored_ui("Rot", camera.rot)
im.SameLine()
draw_f32_colored_ui("Fov", "%.2f", camera.fov)
im.End()
}draw_vec2_colored_ui :: proc(label: string, v: [3]f32) {
im.Text("%s: ", label)
im.SameLine(0, 0)
im.TextColored({1.0, 0.4, 0.4, 1.0}, "%.2f", v.x) // X - Red
im.SameLine(0, 0)
im.Text(", ")
im.SameLine(0, 2)
im.TextColored({0.4, 1.0, 0.4, 1.0}, "%.2f", v.y) // Y - Green
im.SameLine(0, 0)
}draw_vec3_colored_ui :: proc(label: string, v: [3]f32) {
im.Text("%s: ", label)
im.SameLine(0, 0)
im.TextColored({1.0, 0.4, 0.4, 1.0}, "%.2f", v.x) // X - Red
im.SameLine(0, 0)
im.Text(", ")
im.SameLine(0, 2)
im.TextColored({0.4, 1.0, 0.4, 1.0}, "%.2f", v.y) // Y - Green
im.SameLine(0, 0)
im.Text(", ")
im.SameLine(0, 2)
im.TextColored({0.4, 0.4, 1.0, 1.0}, "%.2f", v.z) // Z - Blue
im.SameLine(0, 0)
}init_ui :: proc() {
im.CreateContext()
imgui_impl_glfw.InitForOpenGL(window, true)
imgui_impl_opengl3.Init("#version 460")
}pre_render_ui :: proc() {
// ui default
imgui_impl_opengl3.NewFrame()
imgui_impl_glfw.NewFrame()
im.NewFrame()
}render_ui :: proc() {
im.Render()
imgui_impl_opengl3.RenderDrawData(im.GetDrawData())
}