System info & network
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
server_monitor
|
||||||
|
cache_build
|
||||||
BIN
assets/fonts/Archivo-VariableFont.ttf
Normal file
BIN
assets/fonts/Archivo-VariableFont.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/DejaVuSans.ttf
Normal file
BIN
assets/fonts/DejaVuSans.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/DejaVuSerif-Bold.ttf
Normal file
BIN
assets/fonts/DejaVuSerif-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/DejaVuSerif.ttf
Normal file
BIN
assets/fonts/DejaVuSerif.ttf
Normal file
Binary file not shown.
93
build.sh
Executable file
93
build.sh
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
start_time=$(date +%s%N) # Performance timer
|
||||||
|
|
||||||
|
ProgramName="server_monitor"
|
||||||
|
|
||||||
|
# User defines
|
||||||
|
UserDefines="-DDEBUG"
|
||||||
|
|
||||||
|
# Compiler
|
||||||
|
CXX="g++"
|
||||||
|
|
||||||
|
CompilerFlags="-std=c++11"
|
||||||
|
CompilerFlags+=" -g"
|
||||||
|
#CompilerFlags+=" -fsanitize=address"
|
||||||
|
CompilerFlags+=" -fno-rtti -fno-exceptions"
|
||||||
|
CompilerFlags+=" -Wall -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare -Wno-unused-value -Wno-unused-function -Werror=return-type -Wno-narrowing"
|
||||||
|
|
||||||
|
# Definitions and configuration
|
||||||
|
CompilerFlags+=" -DPLATFORM_LINUX=1"
|
||||||
|
#CompilerFlags+=" -DGUI_PERFORMANCE_REPORT"
|
||||||
|
CompilerFlags+=" -DSWAP_INTERVAL=1"
|
||||||
|
|
||||||
|
# Source files
|
||||||
|
SourceFiles="code/*.cpp"
|
||||||
|
SourceFiles+=" code/**/*.cpp"
|
||||||
|
|
||||||
|
# X11
|
||||||
|
CompilerFlags+=" -lX11 -lXi"
|
||||||
|
|
||||||
|
# OpenGL
|
||||||
|
CompilerFlags+=" -lGL -ldl"
|
||||||
|
CompilerFlags+=" -DGL_GLEXT_PROTOTYPES"
|
||||||
|
CompilerFlags+=" -DGLX_GLXEXT_PROTOTYPES"
|
||||||
|
|
||||||
|
# Pulseaudio
|
||||||
|
CompilerFlags+=" -lpulse"
|
||||||
|
|
||||||
|
# NetworkManager
|
||||||
|
CompilerFlags+=" "$(pkg-config --libs --cflags libnm)
|
||||||
|
|
||||||
|
# External libs
|
||||||
|
CompilerFlags+=" -Iexternal"
|
||||||
|
ExternalFiles="external/*.cpp"
|
||||||
|
ExternalObjects=""
|
||||||
|
for f in $ExternalFiles;
|
||||||
|
do
|
||||||
|
ObjName="cache_build/${f#"external/"}"
|
||||||
|
ObjName=${ObjName%.cpp}.o
|
||||||
|
ExternalObjects+=" ${ObjName}"
|
||||||
|
done
|
||||||
|
SourceFiles+="${ExternalObjects}"
|
||||||
|
|
||||||
|
|
||||||
|
# Cache external libs building. They are big and take a lot of time to compile
|
||||||
|
build_external=false
|
||||||
|
for obj in $ExternalObjects;
|
||||||
|
do
|
||||||
|
if [ ! -e $obj ]
|
||||||
|
then
|
||||||
|
build_external=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $build_external == true ]
|
||||||
|
then
|
||||||
|
echo "Building external libraries cache..."
|
||||||
|
mkdir -p cache_build
|
||||||
|
for f in $ExternalFiles;
|
||||||
|
do
|
||||||
|
ObjName="cache_build/${f#"external/"}"
|
||||||
|
ObjName=${ObjName%.cpp}.o
|
||||||
|
echo $ObjName
|
||||||
|
$CXX $CompilerFlags $UserDefines -O2 $f -c -o $ObjName
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Compiling..."
|
||||||
|
$CXX $CompilerFlags $UserDefines $SourceFiles -o $ProgramName
|
||||||
|
|
||||||
|
compiled=$?
|
||||||
|
if [ $compiled != 0 ]
|
||||||
|
then
|
||||||
|
exit $compiled
|
||||||
|
fi
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
|
||||||
|
end_time=$(date +%s%N) # Performance timer
|
||||||
|
duration_nanoseconds=$((end_time - start_time)) # Performance timer
|
||||||
|
duration_string="$((duration_nanoseconds/1000000/1000)).$((duration_nanoseconds/1000000%1000))"
|
||||||
|
echo #newline
|
||||||
|
echo "Duration: "$duration_string"s"
|
||||||
705
code/assets.cpp
Normal file
705
code/assets.cpp
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
#include "assets.h"
|
||||||
|
#include "file_formats/wavefront.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "stb_vorbis.h"
|
||||||
|
#include <limits.h>
|
||||||
|
#include "debug/logger.h"
|
||||||
|
#include "render/render.h"
|
||||||
|
#include "cgltf.h"
|
||||||
|
|
||||||
|
|
||||||
|
Buffer read_file_into_buffer(const char *filename);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int wf_face_index_comp(const void *_a, const void *_b)
|
||||||
|
{
|
||||||
|
wf_face_index *a = (wf_face_index *)_a;
|
||||||
|
wf_face_index *b = (wf_face_index *)_b;
|
||||||
|
|
||||||
|
if(a->vertex < b->vertex) return -1;
|
||||||
|
if(a->vertex > b->vertex) return +1;
|
||||||
|
if(a->texture < b->texture) return -1;
|
||||||
|
if(a->texture > b->texture) return +1;
|
||||||
|
if(a->normal < b->normal) return -1;
|
||||||
|
if(a->normal > b->normal) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool assets_load_object_obj(asset_manager *am, const char *filename, r_object *result)
|
||||||
|
{
|
||||||
|
wf_obj_file obj_file;
|
||||||
|
wf_mtl_file mtl_file;
|
||||||
|
bool has_mtl;
|
||||||
|
|
||||||
|
char path[512]; // @Buf: Files with paths longer than 512 bytes will fail to load
|
||||||
|
p_file file;
|
||||||
|
u64 size;
|
||||||
|
Buffer content;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
// Load obj file
|
||||||
|
strcpy(path, "assets/");
|
||||||
|
strncat(path, filename, 512 - strlen(path));
|
||||||
|
p_file_init(&file, path);
|
||||||
|
size = p_file_size(&file);
|
||||||
|
content.data = (u8*)p_alloc(size);
|
||||||
|
content.size = 0;
|
||||||
|
p_file_read(&file, &content, size);
|
||||||
|
p_file_deinit(&file);
|
||||||
|
|
||||||
|
success = wf_parse_obj((char*)content.data, content.size, filename, &obj_file);
|
||||||
|
p_free(content.data);
|
||||||
|
|
||||||
|
if(!success)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
has_mtl = false;
|
||||||
|
|
||||||
|
// Load mtl file (optional)
|
||||||
|
if(obj_file.mtllib_names_count > 0)
|
||||||
|
{
|
||||||
|
has_mtl = true;
|
||||||
|
char *mtl_name = obj_file.mtllib_names[0];
|
||||||
|
|
||||||
|
strcpy(path, "assets/");
|
||||||
|
strncat(path, mtl_name, 512 - strlen(path));
|
||||||
|
p_file_init(&file, path);
|
||||||
|
size = p_file_size(&file);
|
||||||
|
content.data = (u8*)p_alloc(size);
|
||||||
|
content.size = 0;
|
||||||
|
p_file_read(&file, &content, size);
|
||||||
|
p_file_deinit(&file);
|
||||||
|
|
||||||
|
success = wf_parse_mtl((char*)content.data, content.size, mtl_name, &mtl_file);
|
||||||
|
p_free(content.data);
|
||||||
|
|
||||||
|
if(!success)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// We take only the first object
|
||||||
|
if(obj_file.objects_count == 0)
|
||||||
|
{
|
||||||
|
LOG(LOG_WARNING, "Asset: cannot load model. File does not contain any objects.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
wf_object *object = obj_file.objects;
|
||||||
|
|
||||||
|
// Setup model
|
||||||
|
r_mesh *res_model = assets_new_models(am, 1);
|
||||||
|
memset(res_model, 0, sizeof(r_mesh));
|
||||||
|
// Actual conversion
|
||||||
|
{
|
||||||
|
wf_face_index *unique_indices;
|
||||||
|
u32 unique_indices_count;
|
||||||
|
|
||||||
|
unique_indices_count = object->faces_count * 3;
|
||||||
|
unique_indices = (wf_face_index *)p_alloc(sizeof(wf_face_index) * unique_indices_count);
|
||||||
|
memcpy(unique_indices, object->faces, sizeof(wf_face_index) * unique_indices_count);
|
||||||
|
|
||||||
|
qsort(unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp);
|
||||||
|
u32 removed = 0;
|
||||||
|
for(u32 i = 1; i < unique_indices_count; i++)
|
||||||
|
{
|
||||||
|
wf_face_index *prev = unique_indices + i - 1;
|
||||||
|
wf_face_index *curr = unique_indices + i;
|
||||||
|
if(wf_face_index_comp(prev, curr) == 0) // Remove duplicate
|
||||||
|
removed++;
|
||||||
|
unique_indices[i - removed] = unique_indices[i];
|
||||||
|
}
|
||||||
|
unique_indices_count -= removed;
|
||||||
|
|
||||||
|
res_model->vertices_count = unique_indices_count;
|
||||||
|
res_model->vertices = (v3*)p_alloc(sizeof(v3) * unique_indices_count);
|
||||||
|
if(object->texture_coords)
|
||||||
|
res_model->uvs = (v2*)p_alloc(sizeof(v2) * unique_indices_count);
|
||||||
|
else
|
||||||
|
res_model->uvs = NULL;
|
||||||
|
res_model->normals = (v3*)p_alloc(sizeof(v3) * unique_indices_count);
|
||||||
|
for(u32 i = 0; i < unique_indices_count; i++)
|
||||||
|
{
|
||||||
|
wf_index vertex_i = (unique_indices[i].vertex > 0 ? unique_indices[i].vertex - 1 : 0);
|
||||||
|
wf_index texture_coord_i = (unique_indices[i].texture > 0 ? unique_indices[i].texture - 1 : 0);
|
||||||
|
wf_index normal_i = (unique_indices[i].normal > 0 ? unique_indices[i].normal - 1 : 0);
|
||||||
|
|
||||||
|
res_model->vertices[i] = object->vertices [vertex_i].xyz / object->vertices[vertex_i].w;
|
||||||
|
if(object->texture_coords)
|
||||||
|
res_model->uvs[i] = object->texture_coords[texture_coord_i].uv;
|
||||||
|
res_model->normals[i] = object->normals[normal_i];
|
||||||
|
}
|
||||||
|
|
||||||
|
res_model->indices_count = object->faces_count * 3;
|
||||||
|
res_model->indices = (u32*)p_alloc(sizeof(u32) * object->faces_count * 3);
|
||||||
|
for(u32 f = 0; f < object->faces_count; f++)
|
||||||
|
{
|
||||||
|
wf_face *curr_face = object->faces + f;
|
||||||
|
for(u32 fi = 0; fi < 3; fi++)
|
||||||
|
{
|
||||||
|
wf_face_index *curr_index = curr_face->indices + fi;
|
||||||
|
wf_face_index *found_ptr = (wf_face_index *)bsearch(curr_index, unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp);
|
||||||
|
res_model->indices[3 * f + fi] = (found_ptr - unique_indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p_free(unique_indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup material
|
||||||
|
r_material *res_material = assets_new_materials(am, 1);
|
||||||
|
memset(res_material, 0, sizeof(r_material));
|
||||||
|
res_material->shader = &r_render_state.shader_pbr;
|
||||||
|
|
||||||
|
// Load textures
|
||||||
|
if(object->material_name != NULL && has_mtl)
|
||||||
|
{
|
||||||
|
// @Feature: Load multiple materials
|
||||||
|
wf_material *material = NULL;
|
||||||
|
for(wf_size i = 0; i < mtl_file.materials_count; i++)
|
||||||
|
{
|
||||||
|
if(strcmp(object->material_name, mtl_file.materials[i].name) == 0)
|
||||||
|
{
|
||||||
|
material = mtl_file.materials + i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(material)
|
||||||
|
{
|
||||||
|
// Load diffuse texture
|
||||||
|
if(material->map_Kd && strcmp(material->map_Kd, "") != 0)
|
||||||
|
{
|
||||||
|
strcpy(path, "assets/");
|
||||||
|
strncat(path, material->map_Kd, 512 - strlen(path));
|
||||||
|
|
||||||
|
stbi_set_flip_vertically_on_load(true);
|
||||||
|
v2s size; s32 channels;
|
||||||
|
u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4);
|
||||||
|
|
||||||
|
r_texture *texture = assets_new_textures(am, 1);
|
||||||
|
*texture = r_texture_create(data, size, R_TEXTURE_SRGB);
|
||||||
|
res_material->albedo_texture = texture;
|
||||||
|
res_material->albedo_factor = v4{1,1,1,1};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load emissive texture
|
||||||
|
if(material->map_Ke && strcmp(material->map_Ke, "") != 0)
|
||||||
|
{
|
||||||
|
strcpy(path, "assets/");
|
||||||
|
strncat(path, material->map_Ke, 512 - strlen(path));
|
||||||
|
|
||||||
|
stbi_set_flip_vertically_on_load(true);
|
||||||
|
v2s size; s32 channels;
|
||||||
|
u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4);
|
||||||
|
|
||||||
|
r_texture *texture = assets_new_textures(am, 1);
|
||||||
|
*texture = r_texture_create(data, size, R_TEXTURE_SRGB);
|
||||||
|
res_material->emissive_texture = texture;
|
||||||
|
res_material->emissive_factor = v4{1,1,1,1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result->meshes = (r_mesh**)p_alloc(sizeof(void*));
|
||||||
|
result->mesh_material = (r_material**)p_alloc(sizeof(void*));
|
||||||
|
result->meshes[0] = res_model;
|
||||||
|
result->mesh_material[0] = res_material;
|
||||||
|
result->mesh_local_transform = (m4*)p_alloc(sizeof(m4));
|
||||||
|
result->mesh_local_transform[0] = m4_identity;
|
||||||
|
result->count = 1;
|
||||||
|
result->scale = v3{1,1,1};
|
||||||
|
result->position = v3{0,0,0};
|
||||||
|
result->rotation = v3{0,0,0};
|
||||||
|
|
||||||
|
wf_cleanup_obj(&obj_file);
|
||||||
|
if(has_mtl)
|
||||||
|
{
|
||||||
|
wf_cleanup_mtl(&mtl_file);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct gltf_loader_state
|
||||||
|
{
|
||||||
|
asset_manager *am;
|
||||||
|
cgltf_data *data;
|
||||||
|
|
||||||
|
r_texture *texture_buffer;
|
||||||
|
u32 texture_capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
void gltf_loader_reserve_texture_space(gltf_loader_state *state, u32 count)
|
||||||
|
{
|
||||||
|
state->texture_capacity = count;
|
||||||
|
state->texture_buffer = assets_new_textures(state->am, state->texture_capacity);
|
||||||
|
memset(state->texture_buffer, 0, sizeof(r_texture) * state->texture_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_texture *gltf_lazy_load_texture(gltf_loader_state *state, u32 texture_index, bool sRGB)
|
||||||
|
{
|
||||||
|
assert(texture_index < state->texture_capacity);
|
||||||
|
|
||||||
|
if(state->texture_buffer[texture_index].flags & R_TEXTURE_INITIALIZED)
|
||||||
|
{
|
||||||
|
bool is_buffered_sRGB = !!(state->texture_buffer[texture_index].flags & (sRGB ? R_TEXTURE_SRGB : 0));
|
||||||
|
assert(sRGB == is_buffered_sRGB);
|
||||||
|
return &state->texture_buffer[texture_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
cgltf_data *data = state->data;
|
||||||
|
cgltf_buffer_view *t_view = data->images[texture_index].buffer_view;
|
||||||
|
|
||||||
|
LOG(LOG_DEBUG, "Loading texture %s, sRGB: %d", (t_view->name ? t_view->name : t_view->buffer->name), sRGB);
|
||||||
|
u8 *t_data;
|
||||||
|
s32 t_width, t_height, t_channels;
|
||||||
|
stbi_set_flip_vertically_on_load(false);
|
||||||
|
t_data = stbi_load_from_memory((u8*)t_view->buffer->data + t_view->offset, t_view->size, &t_width, &t_height, &t_channels, 4);
|
||||||
|
|
||||||
|
u32 flags = (sRGB ? R_TEXTURE_SRGB : 0);
|
||||||
|
state->texture_buffer[texture_index] = r_texture_create(t_data, v2s{t_width, t_height}, flags);
|
||||||
|
return &state->texture_buffer[texture_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void gltf_visit(gltf_loader_state *state, r_object *object, m4 transform, cgltf_node *node)
|
||||||
|
{
|
||||||
|
cgltf_data *data = state->data;
|
||||||
|
|
||||||
|
// Transform
|
||||||
|
if(node->has_matrix)
|
||||||
|
{
|
||||||
|
transform = transpose(*(m4*)node->matrix) * transform;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(node->has_translation)
|
||||||
|
{
|
||||||
|
v3 t = {node->translation[0], node->translation[1], node->translation[2]};
|
||||||
|
transform = translation_v3(t) * transform;
|
||||||
|
}
|
||||||
|
if(node->has_rotation)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Quaternion rotation not supported yet");
|
||||||
|
}
|
||||||
|
if(node->has_scale)
|
||||||
|
{
|
||||||
|
v3 s = {node->scale[0], node->scale[1], node->scale[2]};
|
||||||
|
transform = scale_v3(s) * transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Feature: Skin
|
||||||
|
if(node->mesh)
|
||||||
|
{
|
||||||
|
cgltf_mesh *mesh = node->mesh;
|
||||||
|
for(cgltf_size p = 0; p < mesh->primitives_count; p++)
|
||||||
|
{
|
||||||
|
cgltf_primitive *primitive = &mesh->primitives[p];
|
||||||
|
|
||||||
|
if(primitive->type == cgltf_primitive_type_triangles)
|
||||||
|
{
|
||||||
|
// Mesh
|
||||||
|
v3 *vertices = NULL;
|
||||||
|
v3 *normals = NULL;
|
||||||
|
v3 *tangents = NULL;
|
||||||
|
v2 *uvs = NULL;
|
||||||
|
u64 vertices_count = 0;
|
||||||
|
|
||||||
|
u32 *indices = NULL;
|
||||||
|
u64 indices_count = 0;
|
||||||
|
|
||||||
|
if(primitive->indices->type != cgltf_type_scalar)
|
||||||
|
{
|
||||||
|
(LOG_ERROR, "glTF: Unhandled indices type %d", primitive->indices->type);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cgltf_accessor *a = primitive->indices;
|
||||||
|
indices = (u32*)p_alloc(a->count * sizeof(u32));
|
||||||
|
indices_count = a->count;
|
||||||
|
cgltf_accessor_unpack_indices(a, indices, a->count);
|
||||||
|
|
||||||
|
vertices_count = primitive->attributes[0].data->count;
|
||||||
|
for(cgltf_size i = 0; i < primitive->attributes_count; i++)
|
||||||
|
{
|
||||||
|
if(primitive->attributes[i].type == cgltf_attribute_type_position)
|
||||||
|
{
|
||||||
|
cgltf_accessor *a = primitive->attributes[i].data;
|
||||||
|
vertices = (v3*)p_alloc(a->count * sizeof(v3));
|
||||||
|
if(vertices_count != a->count)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "glTF: vertex count (%d) != previously stored vertex count (%d)", a->count, vertices_count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cgltf_accessor_unpack_floats(a, (f32*)vertices, a->count * 3);
|
||||||
|
}
|
||||||
|
else if(primitive->attributes[i].type == cgltf_attribute_type_normal)
|
||||||
|
{
|
||||||
|
cgltf_accessor *a = primitive->attributes[i].data;
|
||||||
|
normals = (v3*)p_alloc(a->count * sizeof(v3));
|
||||||
|
if(vertices_count != a->count)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "glTF: normal count (%d) != vertex count (%d)", a->count, vertices_count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cgltf_accessor_unpack_floats(a, (f32*)normals, a->count * 3);
|
||||||
|
}
|
||||||
|
else if(primitive->attributes[i].type == cgltf_attribute_type_tangent)
|
||||||
|
{
|
||||||
|
cgltf_accessor *a = primitive->attributes[i].data;
|
||||||
|
tangents = (v3*)p_alloc(a->count * sizeof(v3));
|
||||||
|
v4 *tangents_tmp = (v4*)p_alloc(a->count * sizeof(v4));
|
||||||
|
if(vertices_count != a->count)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "glTF: tangent count (%d) != vertex count (%d)", a->count, vertices_count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cgltf_accessor_unpack_floats(a, (f32*)tangents_tmp, a->count * 4);
|
||||||
|
for(u32 i = 0; i < a->count; i++)
|
||||||
|
tangents[i] = tangents_tmp[i].xyz;
|
||||||
|
p_free(tangents_tmp);
|
||||||
|
}
|
||||||
|
else if(primitive->attributes[i].type == cgltf_attribute_type_texcoord)
|
||||||
|
{
|
||||||
|
cgltf_accessor *a = primitive->attributes[i].data;
|
||||||
|
uvs = (v2*)p_alloc(a->count * sizeof(v2));
|
||||||
|
if(vertices_count != a->count)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "glTF: uv count (%d) != vertex count (%d)", a->count, vertices_count);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cgltf_accessor_unpack_floats(a, (f32*)uvs, a->count * 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//LOG(LOG_WARNING, "Unmanaged attribute type: %d", primitive->attributes[i].type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r_mesh *mesh = (r_mesh*)p_alloc(sizeof(r_mesh));
|
||||||
|
*mesh = r_mesh_create(indices_count, indices, vertices_count, vertices, normals, tangents, uvs);
|
||||||
|
|
||||||
|
// @Feature: Calculate tangents if missing
|
||||||
|
|
||||||
|
|
||||||
|
// Material
|
||||||
|
r_material *material = assets_new_materials(state->am, 1);
|
||||||
|
memset(material, 0, sizeof(r_material));
|
||||||
|
material->shader = &r_render_state.shader_pbr;
|
||||||
|
|
||||||
|
// Find image index
|
||||||
|
u32 albedo_texture_index = primitive->material->pbr_metallic_roughness.base_color_texture.texture->image - data->images;
|
||||||
|
if(albedo_texture_index > data->images_count)
|
||||||
|
LOG(LOG_ERROR, "Albedo image index (%u) out of bounds (%u)", albedo_texture_index, data->images_count);
|
||||||
|
|
||||||
|
material->albedo_texture = gltf_lazy_load_texture(state, albedo_texture_index, true);
|
||||||
|
material->albedo_factor = V4(primitive->material->pbr_metallic_roughness.base_color_factor);
|
||||||
|
|
||||||
|
|
||||||
|
// @Cleanup: @Performance: Merge metallic and roughness texture or split them up?
|
||||||
|
u32 metallic_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images;
|
||||||
|
if(metallic_texture_index > data->images_count)
|
||||||
|
LOG(LOG_ERROR, "Metallic image index (%u) out of bounds (%u)", metallic_texture_index, data->images_count);
|
||||||
|
|
||||||
|
material->metallic_texture = gltf_lazy_load_texture(state, metallic_texture_index, false);
|
||||||
|
material->metallic_factor = primitive->material->pbr_metallic_roughness.metallic_factor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
u32 roughness_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images;
|
||||||
|
if(roughness_texture_index > data->images_count)
|
||||||
|
LOG(LOG_ERROR, "Roughness image index (%u) out of bounds (%u)", roughness_texture_index, data->images_count);
|
||||||
|
|
||||||
|
material->roughness_texture = gltf_lazy_load_texture(state, roughness_texture_index, false);
|
||||||
|
material->roughness_factor = primitive->material->pbr_metallic_roughness.roughness_factor;
|
||||||
|
|
||||||
|
|
||||||
|
if(primitive->material->normal_texture.texture)
|
||||||
|
{
|
||||||
|
u32 normal_texture_index = primitive->material->normal_texture.texture->image - data->images;
|
||||||
|
if(normal_texture_index > data->images_count)
|
||||||
|
LOG(LOG_ERROR, "Normal image index (%u) out of bounds (%u)", normal_texture_index, data->images_count);
|
||||||
|
|
||||||
|
material->normal_texture = gltf_lazy_load_texture(state, normal_texture_index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(primitive->material->emissive_texture.texture)
|
||||||
|
{
|
||||||
|
u32 emissive_texture_index = primitive->material->emissive_texture.texture->image - data->images;
|
||||||
|
if(emissive_texture_index > data->images_count)
|
||||||
|
LOG(LOG_ERROR, "Emission image index (%u) out of bounds (%u)", emissive_texture_index, data->images_count);
|
||||||
|
|
||||||
|
material->emissive_texture = gltf_lazy_load_texture(state, emissive_texture_index, true);
|
||||||
|
f32 strength = primitive->material->has_emissive_strength ? primitive->material->has_emissive_strength : 1.0;
|
||||||
|
material->emissive_factor = V4(V3(primitive->material->emissive_factor) * strength, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object->meshes = (r_mesh**)p_realloc(object->meshes, (object->count + 1) * sizeof(r_mesh));
|
||||||
|
object->mesh_material = (r_material**)p_realloc(object->mesh_material, (object->count + 1) * sizeof(r_material));
|
||||||
|
object->mesh_local_transform = (m4*)p_realloc(object->mesh_local_transform, (object->count + 1) * sizeof(m4));
|
||||||
|
|
||||||
|
object->meshes [object->count] = mesh;
|
||||||
|
object->mesh_material[object->count] = material;
|
||||||
|
object->mesh_local_transform[object->count] = transform;
|
||||||
|
object->count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "glTF: Unhandled primitive type %d", primitive->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(cgltf_size i = 0; i < node->children_count; i++)
|
||||||
|
{
|
||||||
|
gltf_visit(state, object, transform, node->children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool assets_load_object_gltf(asset_manager *am, const char *filename, r_object *result)
|
||||||
|
{
|
||||||
|
gltf_loader_state state;
|
||||||
|
state.am = am;
|
||||||
|
|
||||||
|
cgltf_options options = {};
|
||||||
|
cgltf_data *data = NULL;
|
||||||
|
|
||||||
|
cgltf_result status = cgltf_parse_file(&options, filename, &data);
|
||||||
|
if(status != cgltf_result_success)
|
||||||
|
{
|
||||||
|
LOG(LOG_DEBUG, "Error parsing glTF file \"%s\".", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = cgltf_load_buffers(&options, data, filename);
|
||||||
|
if(status != cgltf_result_success)
|
||||||
|
{
|
||||||
|
LOG(LOG_DEBUG, "Erorr loading glTF buffers from file \"%s\".", filename);
|
||||||
|
cgltf_free(data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.data = data;
|
||||||
|
|
||||||
|
// Reserve texture space
|
||||||
|
gltf_loader_reserve_texture_space(&state, data->images_count);
|
||||||
|
|
||||||
|
// Prepare object
|
||||||
|
r_object object;
|
||||||
|
memset(&object, 0, sizeof(r_object));
|
||||||
|
object.scale = v3{1,1,1};
|
||||||
|
object.position = v3{0,0,0};
|
||||||
|
object.rotation = v3{0,0,0};
|
||||||
|
object.has_shadow = true;
|
||||||
|
|
||||||
|
for(cgltf_size i = 0; i < data->scene->nodes_count; i++)
|
||||||
|
{
|
||||||
|
gltf_visit(&state, &object, m4_identity, data->scene->nodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize unused space
|
||||||
|
|
||||||
|
*result = object;
|
||||||
|
cgltf_free(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool assets_load_audio(asset_manager *am, const char *name, p_audio_buffer *result)
|
||||||
|
{
|
||||||
|
s32 music_channels;
|
||||||
|
s32 music_sample_rate;
|
||||||
|
s16 *music_data;
|
||||||
|
s32 music_samples = stb_vorbis_decode_filename(name, &music_channels, &music_sample_rate, &music_data);
|
||||||
|
|
||||||
|
result->samples = (p_audio_sample*) p_alloc(sizeof(p_audio_sample) * music_samples);
|
||||||
|
for(u32 i = 0; i < music_samples; i++)
|
||||||
|
{
|
||||||
|
result->samples[i].left = (f32)music_data[2*i] / -SHRT_MIN; // Left
|
||||||
|
result->samples[i].right = (f32)music_data[2*i + 1] / -SHRT_MIN; // Right
|
||||||
|
}
|
||||||
|
result->size = music_samples;
|
||||||
|
|
||||||
|
free(music_data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool assets_load_cubemap(asset_manager *am, const char *px, const char *nx, const char *py, const char *ny, const char *pz, const char *nz, r_cubemap *result)
|
||||||
|
{
|
||||||
|
stbi_set_flip_vertically_on_load(false);
|
||||||
|
//stbi_ldr_to_hdr_scale(1.0f);
|
||||||
|
//stbi_ldr_to_hdr_gamma(2.2f);
|
||||||
|
|
||||||
|
float *data[6];
|
||||||
|
v2s size; s32 channels;
|
||||||
|
|
||||||
|
v2s s;
|
||||||
|
data[0] = stbi_loadf(px, &s.x, &s.y, &channels, 3);
|
||||||
|
size = s;
|
||||||
|
data[1] = stbi_loadf(nx, &s.x, &s.y, &channels, 3);
|
||||||
|
assert(size == s);
|
||||||
|
data[2] = stbi_loadf(py, &s.x, &s.y, &channels, 3);
|
||||||
|
assert(size == s);
|
||||||
|
data[3] = stbi_loadf(ny, &s.x, &s.y, &channels, 3);
|
||||||
|
assert(size == s);
|
||||||
|
data[4] = stbi_loadf(pz, &s.x, &s.y, &channels, 3);
|
||||||
|
assert(size == s);
|
||||||
|
data[5] = stbi_loadf(nz, &s.x, &s.y, &channels, 3);
|
||||||
|
assert(size == s);
|
||||||
|
|
||||||
|
r_cubemap cubemap = r_cubemap_create(data, size, 0);
|
||||||
|
if(result)
|
||||||
|
*result = cubemap;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void assets_init(asset_manager *am)
|
||||||
|
{
|
||||||
|
am->shaders = NULL;
|
||||||
|
am->shaders_count = 0;
|
||||||
|
|
||||||
|
am->models = NULL;
|
||||||
|
am->models_count = 0;
|
||||||
|
|
||||||
|
am->textures = NULL;
|
||||||
|
am->textures_count = 0;
|
||||||
|
|
||||||
|
am->materials = NULL;
|
||||||
|
am->materials_count = 0;
|
||||||
|
|
||||||
|
am->objects = NULL;
|
||||||
|
am->objects_count = 0;
|
||||||
|
|
||||||
|
am->allocations = NULL;
|
||||||
|
am->allocations_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets_free(asset_manager *am)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assets_add_allocation(asset_manager *am, void *data)
|
||||||
|
{
|
||||||
|
u32 old_count = am->allocations_count;
|
||||||
|
am->allocations_count++;
|
||||||
|
am->allocations = (void**)p_realloc(am->allocations, am->allocations_count * sizeof(void*));
|
||||||
|
am->allocations[old_count] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
r_shader *assets_new_shaders(asset_manager *am, u32 count)
|
||||||
|
{
|
||||||
|
u32 old_count = am->shaders_count;
|
||||||
|
am->shaders_count = old_count + count;
|
||||||
|
|
||||||
|
r_shader *data = (r_shader*) p_alloc(count * sizeof(r_shader));
|
||||||
|
assets_add_allocation(am, data);
|
||||||
|
|
||||||
|
am->shaders = (r_shader**) p_realloc(am->shaders, am->shaders_count * sizeof(r_shader*));
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
am->shaders[old_count + i] = data + i;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_mesh *assets_new_models(asset_manager *am, u32 count)
|
||||||
|
{
|
||||||
|
u32 old_count = am->models_count;
|
||||||
|
am->models_count = old_count + count;
|
||||||
|
|
||||||
|
r_mesh *data = (r_mesh*) p_alloc(count * sizeof(r_mesh));
|
||||||
|
assets_add_allocation(am, data);
|
||||||
|
|
||||||
|
am->models = (r_mesh**) p_realloc(am->models, am->models_count * sizeof(r_mesh*));
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
am->models[old_count + i] = data + i;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_texture *assets_new_textures(asset_manager *am, u32 count)
|
||||||
|
{
|
||||||
|
u32 old_count = am->textures_count;
|
||||||
|
am->textures_count = old_count + count;
|
||||||
|
|
||||||
|
r_texture *data = (r_texture*) p_alloc(count * sizeof(r_texture));
|
||||||
|
assets_add_allocation(am, data);
|
||||||
|
|
||||||
|
am->textures = (r_texture**) p_realloc(am->textures, am->textures_count * sizeof(r_texture*));
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
am->textures[old_count + i] = data + i;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_material *assets_new_materials(asset_manager *am, u32 count)
|
||||||
|
{
|
||||||
|
u32 old_count = am->materials_count;
|
||||||
|
am->materials_count = old_count + count;
|
||||||
|
|
||||||
|
r_material *data = (r_material*) p_alloc(count * sizeof(r_material));
|
||||||
|
assets_add_allocation(am, data);
|
||||||
|
|
||||||
|
am->materials = (r_material**) p_realloc(am->materials, am->materials_count * sizeof(r_material*));
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
am->materials[old_count + i] = data + i;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_object *assets_new_objects(asset_manager *am, u32 count)
|
||||||
|
{
|
||||||
|
u32 old_count = am->objects_count;
|
||||||
|
am->objects_count = old_count + count;
|
||||||
|
|
||||||
|
r_object *data = (r_object*) p_alloc(count * sizeof(r_object));
|
||||||
|
assets_add_allocation(am, data);
|
||||||
|
|
||||||
|
am->objects = (r_object**) p_realloc(am->objects, am->objects_count * sizeof(r_object*));
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
am->objects[old_count + i] = data + i;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Buffer read_file_into_buffer(const char *filename)
|
||||||
|
{
|
||||||
|
Buffer empty = { .size = 0, .data = NULL };
|
||||||
|
Buffer result;
|
||||||
|
p_file file;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
success = p_file_init(&file, filename);
|
||||||
|
if(success) {
|
||||||
|
result.size = p_file_size(&file);
|
||||||
|
result.data = (u8*)p_alloc(result.size);
|
||||||
|
success = p_file_read(&file, &result, result.size);
|
||||||
|
|
||||||
|
p_file_deinit(&file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!success)
|
||||||
|
return empty;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
49
code/assets.h
Normal file
49
code/assets.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef _PIUMA_ASSETS_H_
|
||||||
|
#define _PIUMA_ASSETS_H_
|
||||||
|
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "render/render.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct asset_manager
|
||||||
|
{
|
||||||
|
r_shader **shaders;
|
||||||
|
u32 shaders_count;
|
||||||
|
|
||||||
|
r_mesh **models;
|
||||||
|
u32 models_count;
|
||||||
|
|
||||||
|
r_texture **textures;
|
||||||
|
u32 textures_count;
|
||||||
|
|
||||||
|
r_material **materials;
|
||||||
|
u32 materials_count;
|
||||||
|
|
||||||
|
r_object **objects;
|
||||||
|
u32 objects_count;
|
||||||
|
|
||||||
|
void **allocations;
|
||||||
|
u32 allocations_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// @TODO: return pointer to loaded asset, instead of copying data into *result
|
||||||
|
bool assets_load_object_obj(asset_manager *am, const char *filename, r_object *result); // Models, textures, materials get inserted in the gamestate arrays
|
||||||
|
bool assets_load_object_gltf(asset_manager *am, const char *filename, r_object *result);
|
||||||
|
bool assets_load_audio(asset_manager *am, const char *name, p_audio_buffer *result);
|
||||||
|
bool assets_load_cubemap(asset_manager *am, const char *px, const char *nx, const char *py, const char *ny, const char *pz, const char *nz, r_cubemap *result);
|
||||||
|
|
||||||
|
|
||||||
|
void assets_init(asset_manager *am);
|
||||||
|
void assets_free(asset_manager *am);
|
||||||
|
|
||||||
|
r_shader *assets_new_shaders (asset_manager *am, u32 count);
|
||||||
|
r_mesh *assets_new_models (asset_manager *am, u32 count);
|
||||||
|
r_texture *assets_new_textures (asset_manager *am, u32 count);
|
||||||
|
r_material *assets_new_materials(asset_manager *am, u32 count);
|
||||||
|
r_object *assets_new_objects (asset_manager *am, u32 count);
|
||||||
|
|
||||||
|
// @Feature: remove/free single assets
|
||||||
|
|
||||||
|
#endif
|
||||||
156
code/audio.cpp
Normal file
156
code/audio.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#include "audio.h"
|
||||||
|
#include "enginestate.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
void audio_cb(p_audio_buffer *buffer);
|
||||||
|
|
||||||
|
|
||||||
|
void audio_init()
|
||||||
|
{
|
||||||
|
audio_player *player = &engine.audio;
|
||||||
|
|
||||||
|
player->track_count = 0;
|
||||||
|
player->track_capacity = 32;
|
||||||
|
player->tracks = (audio_track *) p_alloc(player->track_capacity * sizeof(audio_track));
|
||||||
|
player->next_id = 0;
|
||||||
|
player->sample_rate = p_audio_sample_rate();
|
||||||
|
|
||||||
|
p_audio_register_data_callback(audio_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
track_id audio_add_track(p_audio_buffer *data, u32 flags)
|
||||||
|
{
|
||||||
|
// @Correctness: fix track if it has a different sample rate
|
||||||
|
audio_player *player = &engine.audio;
|
||||||
|
audio_track track;
|
||||||
|
|
||||||
|
track.id = player->next_id;
|
||||||
|
player->next_id++;
|
||||||
|
track.data = *data;
|
||||||
|
track.progress = 0;
|
||||||
|
track.playing = !(flags & AUDIO_PAUSED);
|
||||||
|
track.loop = !!(flags & AUDIO_LOOP);
|
||||||
|
track.free_on_finish = !!(flags & AUDIO_FREE_ON_FINISH);
|
||||||
|
track.volume = 1.0;
|
||||||
|
|
||||||
|
if (player->track_count + 1 < player->track_capacity)
|
||||||
|
{
|
||||||
|
u64 index = player->track_count;
|
||||||
|
player->tracks[index] = track;
|
||||||
|
player->track_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u32 replace_index = 0;
|
||||||
|
for(u32 i = 0; i < player->track_count; i++)
|
||||||
|
{
|
||||||
|
audio_track *current = &player->tracks[i];
|
||||||
|
if(track.loop == current->loop)
|
||||||
|
{
|
||||||
|
replace_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player->tracks[replace_index] = track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return track.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_remove_track(track_id id)
|
||||||
|
{
|
||||||
|
audio_player *player = &engine.audio;
|
||||||
|
for(int i = 0; i < player->track_count; i++)
|
||||||
|
{
|
||||||
|
audio_track *current = &player->tracks[i];
|
||||||
|
if(id == current->id)
|
||||||
|
{
|
||||||
|
u32 move_count = player->track_count - (i + 1);
|
||||||
|
if(current->free_on_finish)
|
||||||
|
p_free(current->data.samples);
|
||||||
|
memmove(current, current + 1, move_count * sizeof(audio_track));
|
||||||
|
player->track_count--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_pause_track(track_id id)
|
||||||
|
{
|
||||||
|
audio_track *track = audio_track_from_id(id);
|
||||||
|
if(track)
|
||||||
|
track->playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_play_track(track_id id)
|
||||||
|
{
|
||||||
|
audio_track *track = audio_track_from_id(id);
|
||||||
|
if(track)
|
||||||
|
track->playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_change_track_volume(track_id id, f32 volume)
|
||||||
|
{
|
||||||
|
audio_track *track = audio_track_from_id(id);
|
||||||
|
if(track)
|
||||||
|
track->volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_track *audio_track_from_id(track_id id)
|
||||||
|
{
|
||||||
|
audio_player *player = &engine.audio;
|
||||||
|
for(int i = 0; i < player->track_count; i++)
|
||||||
|
{
|
||||||
|
audio_track *current = &player->tracks[i];
|
||||||
|
if(id == current->id)
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @Correctness: audio cb modifies the array of tracks asynchronously. All r/w operations on tracks should be protected by a mutex.
|
||||||
|
void audio_cb(p_audio_buffer *buffer)
|
||||||
|
{
|
||||||
|
audio_player *player = &engine.audio;
|
||||||
|
|
||||||
|
buffer->size = minimum(buffer->size, 2048); // Low latency
|
||||||
|
|
||||||
|
for(u64 i = 0; i < buffer->size; i++)
|
||||||
|
{
|
||||||
|
buffer->samples[i].left = 0;
|
||||||
|
buffer->samples[i].right = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int track_i = 0; track_i < player->track_count; track_i++)
|
||||||
|
{
|
||||||
|
audio_track *current = &player->tracks[track_i];
|
||||||
|
if(current->playing)
|
||||||
|
{
|
||||||
|
u64 remaining = current->data.size - current->progress;
|
||||||
|
u64 copy_count = minimum(remaining, buffer->size);
|
||||||
|
if(current->loop)
|
||||||
|
copy_count = buffer->size;
|
||||||
|
|
||||||
|
for(u64 i = 0; i < copy_count; i++)
|
||||||
|
{
|
||||||
|
buffer->samples[i].left += current->volume * current->data.samples[current->progress].left;
|
||||||
|
buffer->samples[i].right += current->volume * current->data.samples[current->progress].right;
|
||||||
|
current->progress = (current->progress + 1) % current->data.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!current->loop && current->progress == 0) // current->progress == 0 because we finished and then we looped around
|
||||||
|
{
|
||||||
|
// Finished playing. Remove track
|
||||||
|
u32 move_count = player->track_count - (track_i + 1);
|
||||||
|
if(current->free_on_finish)
|
||||||
|
p_free(current->data.samples); // @Bug: double free when the same sound is played 2 times. Find a way to signal we reached the end of the track so the user code can make its own free (if needed)
|
||||||
|
memmove(current, current + 1, move_count * sizeof(audio_track));
|
||||||
|
track_i--;
|
||||||
|
player->track_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
code/audio.h
Normal file
47
code/audio.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef _PIUMA_AUDIO_H_
|
||||||
|
#define _PIUMA_AUDIO_H_
|
||||||
|
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
typedef u64 track_id;
|
||||||
|
|
||||||
|
struct audio_track
|
||||||
|
{
|
||||||
|
track_id id;
|
||||||
|
p_audio_buffer data;
|
||||||
|
u64 progress;
|
||||||
|
bool playing;
|
||||||
|
bool loop;
|
||||||
|
bool free_on_finish;
|
||||||
|
|
||||||
|
f32 volume;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct audio_player
|
||||||
|
{
|
||||||
|
audio_track *tracks;
|
||||||
|
u32 track_count;
|
||||||
|
u32 track_capacity;
|
||||||
|
track_id next_id;
|
||||||
|
|
||||||
|
u32 sample_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum audio_flags
|
||||||
|
{
|
||||||
|
AUDIO_NONE = 0,
|
||||||
|
AUDIO_LOOP = 1,
|
||||||
|
AUDIO_PAUSED = 2,
|
||||||
|
AUDIO_FREE_ON_FINISH = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
void audio_init();
|
||||||
|
track_id audio_add_track(p_audio_buffer *data, u32 flags);
|
||||||
|
void audio_remove_track(track_id id);
|
||||||
|
void audio_pause_track(track_id id);
|
||||||
|
void audio_play_track(track_id id);
|
||||||
|
void audio_change_track_volume(track_id id, f32 volume);
|
||||||
|
audio_track *audio_track_from_id(track_id id);
|
||||||
|
|
||||||
|
#endif
|
||||||
107
code/camera.cpp
Normal file
107
code/camera.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include "camera.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
void r_camera_base_look_at(r_camera_base *c, v3 target)
|
||||||
|
{
|
||||||
|
c->direction = normalize(target - c->position);
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_camera_base_view(r_camera_base *c)
|
||||||
|
{
|
||||||
|
return r_view_matrix(c->position, c->direction, c->up);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void r_camera_fps_look_at(r_camera_fps *c, v3 target)
|
||||||
|
{
|
||||||
|
v3 direction = normalize(target - c->position);
|
||||||
|
c->pitch = degrees(asin(direction.z));
|
||||||
|
direction = normalize(v3{direction.x, direction.y, 0});
|
||||||
|
c->yaw = degrees(asin(direction.y));
|
||||||
|
if(direction.x < 0)
|
||||||
|
c->yaw = 180 - c->yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_camera_fps_view(r_camera_fps *c)
|
||||||
|
{
|
||||||
|
v3 up;
|
||||||
|
v3 direction;
|
||||||
|
|
||||||
|
up = r_camera_up_vector;
|
||||||
|
direction = r_camera_fps_direction(c);
|
||||||
|
|
||||||
|
return r_view_matrix(c->position, direction, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
v3 r_camera_fps_direction(r_camera_fps *c)
|
||||||
|
{
|
||||||
|
v3 direction;
|
||||||
|
direction.x = cos(radians(c->yaw)) * cos(radians(c->pitch));
|
||||||
|
direction.y = sin(radians(c->yaw)) * cos(radians(c->pitch));
|
||||||
|
direction.z = sin(radians(c->pitch));
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m4 r_view_matrix(v3 position, v3 direction, v3 up)
|
||||||
|
{
|
||||||
|
v3 right = normalize(cross(up, -direction));
|
||||||
|
v3 camera_up = normalize(cross(-direction, right));
|
||||||
|
|
||||||
|
m4 result = m4_identity;
|
||||||
|
|
||||||
|
// new X axis
|
||||||
|
result.E[0][0] = right.x;
|
||||||
|
result.E[0][1] = right.y;
|
||||||
|
result.E[0][2] = right.z;
|
||||||
|
|
||||||
|
// new Y axis
|
||||||
|
result.E[1][0] = camera_up.x;
|
||||||
|
result.E[1][1] = camera_up.y;
|
||||||
|
result.E[1][2] = camera_up.z;
|
||||||
|
|
||||||
|
// new Z axis
|
||||||
|
result.E[2][0] = -direction.x;
|
||||||
|
result.E[2][1] = -direction.y;
|
||||||
|
result.E[2][2] = -direction.z;
|
||||||
|
|
||||||
|
// translation
|
||||||
|
result.E[0][3] = -dot(right , position);
|
||||||
|
result.E[1][3] = -dot(camera_up, position);
|
||||||
|
result.E[2][3] = -dot(-direction, position);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_perspective_matrix(f32 fov, f32 aspect_ratio, f32 near_plane, f32 far_plane)
|
||||||
|
{
|
||||||
|
m4 result = m4_zero;
|
||||||
|
|
||||||
|
f32 tanf2 = tan(radians(fov) / 2.0);
|
||||||
|
|
||||||
|
result.E[0][0] = 1.0 / (aspect_ratio * tanf2);
|
||||||
|
result.E[1][1] = 1.0 / tanf2;
|
||||||
|
result.E[2][2] = - (far_plane + near_plane) / (far_plane - near_plane);
|
||||||
|
|
||||||
|
result.E[2][3] = -2.0 * far_plane * near_plane / (far_plane - near_plane);
|
||||||
|
result.E[3][2] = -1.0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_orthographic_matrix(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far)
|
||||||
|
{
|
||||||
|
m4 result = m4_zero;
|
||||||
|
|
||||||
|
result.E[0][0] = 2 / (right - left);
|
||||||
|
result.E[1][1] = 2 / (top - bottom);
|
||||||
|
result.E[2][2] = -2 / (far - near);
|
||||||
|
result.E[3][3] = 1;
|
||||||
|
|
||||||
|
result.E[0][3] = - (right + left) / (right - left);
|
||||||
|
result.E[1][3] = - (top + bottom) / (top - bottom);
|
||||||
|
result.E[2][3] = - (far + near) / (far - near);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
38
code/camera.h
Normal file
38
code/camera.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef _PIUMA_CAMERA_H_
|
||||||
|
#define _PIUMA_CAMERA_H_
|
||||||
|
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "lib/math.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const v3 r_camera_up_vector = {0,0,1};
|
||||||
|
|
||||||
|
struct r_camera_base
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
v3 direction;
|
||||||
|
v3 up;
|
||||||
|
};
|
||||||
|
|
||||||
|
void r_camera_base_look_at(r_camera_base *c, v3 target);
|
||||||
|
m4 r_camera_base_view(r_camera_base *c);
|
||||||
|
|
||||||
|
|
||||||
|
struct r_camera_fps
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
// Forward = +y, Right = +x, Up = +z
|
||||||
|
f32 yaw;
|
||||||
|
f32 pitch;
|
||||||
|
};
|
||||||
|
|
||||||
|
void r_camera_fps_look_at(r_camera_fps *c, v3 target);
|
||||||
|
m4 r_camera_fps_view(r_camera_fps *c);
|
||||||
|
v3 r_camera_fps_direction(r_camera_fps *c);
|
||||||
|
|
||||||
|
|
||||||
|
m4 r_view_matrix(v3 position, v3 direction, v3 up);
|
||||||
|
m4 r_perspective_matrix(f32 fov, f32 aspect_ratio, f32 near_plane, f32 far_plane);
|
||||||
|
m4 r_orthographic_matrix(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
|
||||||
|
|
||||||
|
#endif
|
||||||
73
code/debug/log_viewer.cpp
Normal file
73
code/debug/log_viewer.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include "log_viewer.h"
|
||||||
|
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "../gui/gui.h"
|
||||||
|
|
||||||
|
LogViewer LogViewer::Init(Logger *logger)
|
||||||
|
{
|
||||||
|
LogViewer viewer;
|
||||||
|
viewer.logger = logger;
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogViewer::Print_New_Messages_On_Console()
|
||||||
|
{
|
||||||
|
u64 message_count = Logger_MessageCount(logger);
|
||||||
|
if(message_count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
u64 last_message_index = message_count - 1;
|
||||||
|
LogEntry *last_message = Logger_MessageAt(logger, last_message_index);
|
||||||
|
u64 id_diff = last_message->id - next_id_to_print_on_console;
|
||||||
|
u64 index_start = (last_message_index > id_diff) ? (last_message_index - id_diff) : 0;
|
||||||
|
|
||||||
|
u64 max_id = next_id_to_print_on_console;
|
||||||
|
for(int i = index_start; i < message_count; i++)
|
||||||
|
{
|
||||||
|
LogEntry *entry = Logger_MessageAt(logger, i);
|
||||||
|
if(entry->id >= next_id_to_print_on_console)
|
||||||
|
{
|
||||||
|
// printf("%lu ", entry->id);
|
||||||
|
// if (entry->level >= LOG_DEBUG) printf("DEBUG");
|
||||||
|
// else if(entry->level >= LOG_INFO) printf("INFO");
|
||||||
|
// else if(entry->level >= LOG_WARNING) printf("WARNING");
|
||||||
|
// else if(entry->level >= LOG_ERROR) printf("ERROR");
|
||||||
|
// printf(": ");
|
||||||
|
fprintf((entry->level < LOG_INFO ? stderr : stdout), "%s\n", entry->message);
|
||||||
|
|
||||||
|
max_id = maximum(max_id, entry->id + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_id_to_print_on_console = max_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogViewer::Draw_New_Messages_On_GUI()
|
||||||
|
{
|
||||||
|
u64 messages_to_draw = 5;
|
||||||
|
|
||||||
|
u64 message_count = Logger_MessageCount(logger);
|
||||||
|
if(message_count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Rect r = { .position = {0, (f32)global_gui_state.default_context.height}, .size = {0,0} };
|
||||||
|
messages_to_draw = minimum(messages_to_draw, message_count);
|
||||||
|
for(u64 i = 0; i < messages_to_draw; i++)
|
||||||
|
{
|
||||||
|
u64 index = message_count - 1 - i;
|
||||||
|
LogEntry *entry = Logger_MessageAt(logger, index);
|
||||||
|
char id_string[24]; sprintf(id_string, "%lu: ", entry->id);
|
||||||
|
|
||||||
|
v2 id_size = gui_text_compute_size(id_string);
|
||||||
|
v2 message_size = gui_text_compute_size(entry->message);
|
||||||
|
|
||||||
|
r.position.y -= maximum(id_size.y, message_size.y);
|
||||||
|
r.position.x = 0;
|
||||||
|
r.size = id_size;
|
||||||
|
gui_text(r, id_string);
|
||||||
|
|
||||||
|
r.position.x += id_size.x;
|
||||||
|
r.size = message_size;
|
||||||
|
gui_text(r, entry->message);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
code/debug/log_viewer.h
Normal file
20
code/debug/log_viewer.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef _PIUMA_LOG_VIEWER_H_
|
||||||
|
#define _PIUMA_LOG_VIEWER_H_
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
struct LogViewer
|
||||||
|
{
|
||||||
|
Logger *logger = NULL;
|
||||||
|
|
||||||
|
LogId next_id_to_print_on_console = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static LogViewer Init(Logger *logger);
|
||||||
|
|
||||||
|
void Print_New_Messages_On_Console();
|
||||||
|
void Draw_New_Messages_On_GUI();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
94
code/debug/logger.cpp
Normal file
94
code/debug/logger.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "logger.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct Logger global_logger;
|
||||||
|
|
||||||
|
void Logger_Init(struct Logger *logger)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
|
||||||
|
u32 initial_capacity = 512;
|
||||||
|
|
||||||
|
logger->message_queue = Queue_Alloc(p_alloc, struct LogEntry, initial_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger_Deinit(struct Logger *logger)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
|
||||||
|
Logger_Clear(logger);
|
||||||
|
Queue_Free(p_free, logger->message_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger_Clear(struct Logger *logger)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
|
||||||
|
while(Queue_Size(logger->message_queue) > 0)
|
||||||
|
{
|
||||||
|
Logger_RemoveOldestMessage(logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger_AddMessage(struct Logger *logger, LogSourceInfo source_info, LogLevel level, const char *format_message, ...)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
|
||||||
|
// Build formatted message
|
||||||
|
char *message;
|
||||||
|
va_list var_args;
|
||||||
|
va_start(var_args, format_message);
|
||||||
|
int bytes_size = vsnprintf(NULL, 0, format_message, var_args) + 1;
|
||||||
|
va_end(var_args);
|
||||||
|
message = (char *) p_alloc(bytes_size * sizeof(char));
|
||||||
|
va_start(var_args, format_message);
|
||||||
|
int written = vsnprintf(message, bytes_size, format_message, var_args) + 1;
|
||||||
|
va_end(var_args);
|
||||||
|
|
||||||
|
// Add log entry to queue, checking if we have enough space
|
||||||
|
struct LogEntry entry = {
|
||||||
|
.level = level,
|
||||||
|
.message = message,
|
||||||
|
.id = logger->last_id++,
|
||||||
|
.source = source_info
|
||||||
|
};
|
||||||
|
|
||||||
|
// fprintf(stderr, message);
|
||||||
|
// fprintf(stderr, "\n");
|
||||||
|
|
||||||
|
if(Queue_Size(logger->message_queue) >= QUEUE_HEADER_PTR(logger->message_queue)->capacity)
|
||||||
|
{
|
||||||
|
logger->overflow_count++;
|
||||||
|
Logger_RemoveOldestMessage(logger);
|
||||||
|
}
|
||||||
|
Queue_Push(logger->message_queue, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger_RemoveOldestMessage(struct Logger *logger)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
assert(Queue_Size(logger->message_queue) > 0);
|
||||||
|
|
||||||
|
struct LogEntry entry = Queue_Pop(logger->message_queue);
|
||||||
|
p_free(entry.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u64 Logger_MessageCount(struct Logger *logger)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
|
||||||
|
return Queue_Size(logger->message_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogEntry *Logger_MessageAt(struct Logger *logger, u64 index)
|
||||||
|
{
|
||||||
|
assert(logger != NULL);
|
||||||
|
assert(Queue_Size(logger->message_queue) > index);
|
||||||
|
|
||||||
|
return &Queue_At(logger->message_queue, index);
|
||||||
|
}
|
||||||
59
code/debug/logger.h
Normal file
59
code/debug/logger.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef _PIUMA_LOGGER_H_
|
||||||
|
#define _PIUMA_LOGGER_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/queue.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef u8 LogLevel;
|
||||||
|
#define LOG_ERROR 0
|
||||||
|
#define LOG_WARNING 50
|
||||||
|
#define LOG_INFO 100
|
||||||
|
#define LOG_DEBUG 150
|
||||||
|
|
||||||
|
struct LogSourceInfo
|
||||||
|
{
|
||||||
|
const char *filename;
|
||||||
|
u32 line;
|
||||||
|
const char *function;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef u64 LogId;
|
||||||
|
struct LogEntry
|
||||||
|
{
|
||||||
|
LogLevel level;
|
||||||
|
char *message;
|
||||||
|
|
||||||
|
LogId id;
|
||||||
|
LogSourceInfo source;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Logger
|
||||||
|
{
|
||||||
|
QUEUE_TYPE(struct LogEntry) message_queue = NULL;
|
||||||
|
|
||||||
|
LogId last_id = 0;
|
||||||
|
u64 overflow_count = 0; // Number of times the buffer was full, but we added a message anyway removing the oldest one
|
||||||
|
};
|
||||||
|
|
||||||
|
void Logger_Init(struct Logger *logger);
|
||||||
|
void Logger_Deinit(struct Logger *logger);
|
||||||
|
|
||||||
|
void Logger_Clear(struct Logger *logger);
|
||||||
|
void Logger_AddMessage(struct Logger *logger, LogSourceInfo source_info, LogLevel level, const char *format_message, ...);
|
||||||
|
void Logger_RemoveOldestMessage(struct Logger *logger);
|
||||||
|
|
||||||
|
u64 Logger_MessageCount(struct Logger *logger);
|
||||||
|
struct LogEntry *Logger_MessageAt(struct Logger *logger, u64 index);
|
||||||
|
|
||||||
|
|
||||||
|
extern struct Logger global_logger;
|
||||||
|
|
||||||
|
|
||||||
|
#define LOG_INIT() Logger_Init(&global_logger)
|
||||||
|
#define LOG_DEINIT() Logger_Deinit(&global_logger)
|
||||||
|
#define LOG_CLEAR() Logger_Clear(&global_logger)
|
||||||
|
#define LOG(level, /*format_message,*/ ...) Logger_AddMessage(&global_logger, LogSourceInfo{.filename = __FILE__, .line = __LINE__, .function = __func__}, level, /*format_message,*/ __VA_ARGS__)
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
39
code/enginestate.cpp
Normal file
39
code/enginestate.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "enginestate.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "assets.h"
|
||||||
|
#include "debug/logger.h"
|
||||||
|
|
||||||
|
engine_state engine;
|
||||||
|
|
||||||
|
|
||||||
|
bool enginestate_init()
|
||||||
|
{
|
||||||
|
u32 status;
|
||||||
|
|
||||||
|
assets_init(&engine.am);
|
||||||
|
|
||||||
|
engine.input.mouse_sensitivity = 1.4;
|
||||||
|
engine.input.movement_sensitivity = 7;
|
||||||
|
engine.input.init = true;
|
||||||
|
engine.input.target = INPUT_TARGET_PLAYER;
|
||||||
|
engine.input.player_movement = {0, 0};
|
||||||
|
engine.input.camera_rotation = {0, 0, 0};
|
||||||
|
engine.input.camera_movement = {0, 0, 0};
|
||||||
|
|
||||||
|
audio_init();
|
||||||
|
|
||||||
|
engine.time = 0;
|
||||||
|
engine.delta_t = 0;
|
||||||
|
|
||||||
|
engine.gui_scaling = 14;
|
||||||
|
|
||||||
|
engine.world = phy_create_world({0, 0, -9.81});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enginestate_deinit()
|
||||||
|
{
|
||||||
|
phy_destroy_world(engine.world);
|
||||||
|
// @Correctness: complete this
|
||||||
|
}
|
||||||
56
code/enginestate.h
Normal file
56
code/enginestate.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef _PIUMA_ENGINESTATE_H_
|
||||||
|
#define _PIUMA_ENGINESTATE_H_
|
||||||
|
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "lib/math.h"
|
||||||
|
#include "render/render.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "camera.h"
|
||||||
|
#include "assets.h"
|
||||||
|
#include "physics/physics.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum input_target
|
||||||
|
{
|
||||||
|
INPUT_TARGET_PLAYER,
|
||||||
|
INPUT_TARGET_CAMERA,
|
||||||
|
INPUT_TARGET_EDITOR
|
||||||
|
};
|
||||||
|
|
||||||
|
struct es_input
|
||||||
|
{
|
||||||
|
f32 mouse_sensitivity;
|
||||||
|
f32 movement_sensitivity;
|
||||||
|
bool init;
|
||||||
|
|
||||||
|
enum input_target target;
|
||||||
|
|
||||||
|
v2s player_movement;
|
||||||
|
|
||||||
|
v3 camera_rotation;
|
||||||
|
v3 camera_movement;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct engine_state
|
||||||
|
{
|
||||||
|
es_input input;
|
||||||
|
audio_player audio;
|
||||||
|
|
||||||
|
asset_manager am;
|
||||||
|
|
||||||
|
f64 time;
|
||||||
|
f64 delta_t;
|
||||||
|
|
||||||
|
f32 gui_scaling;
|
||||||
|
|
||||||
|
phy_world *world;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern engine_state engine;
|
||||||
|
|
||||||
|
bool enginestate_init();
|
||||||
|
void enginestate_deinit();
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
1323
code/file_formats/wavefront.cpp
Normal file
1323
code/file_formats/wavefront.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
code/file_formats/wavefront.h
Normal file
121
code/file_formats/wavefront.h
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#ifndef _PIUMA_WAVEFRONT_H_
|
||||||
|
#define _PIUMA_WAVEFRONT_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
|
||||||
|
typedef void *(*wf_alloc_t)(u64 size);
|
||||||
|
typedef void *(*wf_realloc_t)(void *ptr, u64 size);
|
||||||
|
typedef void (*wf_free_t)(void *ptr);
|
||||||
|
|
||||||
|
/* Reassing wf_alloc and wf_free with your own alloc/free functions if you don't
|
||||||
|
* want to use malloc and free.
|
||||||
|
*/
|
||||||
|
extern wf_alloc_t wf_alloc;
|
||||||
|
extern wf_realloc_t wf_realloc;
|
||||||
|
extern wf_free_t wf_free;
|
||||||
|
|
||||||
|
typedef u32 wf_index;
|
||||||
|
typedef u32 wf_size;
|
||||||
|
|
||||||
|
|
||||||
|
// Indices are 1-based in Wavefront. 0 is used to signal the index was omitted.
|
||||||
|
struct wf_face_index
|
||||||
|
{
|
||||||
|
wf_index vertex;
|
||||||
|
wf_index texture;
|
||||||
|
wf_index normal;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wf_face
|
||||||
|
{
|
||||||
|
wf_face_index indices[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wf_object
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
// @Feature: groups are not supported yet
|
||||||
|
char *material_name;
|
||||||
|
|
||||||
|
// Vertices
|
||||||
|
v4 *vertices;
|
||||||
|
wf_size vertices_count;
|
||||||
|
// Texture coordinates
|
||||||
|
v3 *texture_coords;
|
||||||
|
wf_size texture_coords_count;
|
||||||
|
// Vertex normals
|
||||||
|
v3 *normals;
|
||||||
|
wf_size normals_count;
|
||||||
|
// Faces
|
||||||
|
wf_face *faces;
|
||||||
|
wf_size faces_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wf_obj_file
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
char **mtllib_names;
|
||||||
|
wf_size mtllib_names_count;
|
||||||
|
|
||||||
|
wf_object *objects;
|
||||||
|
wf_size objects_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct wf_material
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
v3 Ka; // ambient color
|
||||||
|
v3 Kd; // diffuse color
|
||||||
|
v3 Ke; // emissive color
|
||||||
|
v3 Ks; // specular color
|
||||||
|
f32 Ns; // specular exposure
|
||||||
|
f32 d; // dissolved - transparency where 0.0 is fully transparent and 1.0 is opaque
|
||||||
|
f32 Ni; // optical density - refractive index
|
||||||
|
int illum; // illumination model
|
||||||
|
/* illum is described at https://en.wikipedia.org/wiki/Wavefront_.obj_file#Basic_materials as follows:
|
||||||
|
* 0. Color on and Ambient off
|
||||||
|
* 1. Color on and Ambient on
|
||||||
|
* 2. Highlight on
|
||||||
|
* 3. Reflection on and Ray trace on
|
||||||
|
* 4. Transparency: Glass on, Reflection: Ray trace on
|
||||||
|
* 5. Reflection: Fresnel on and Ray trace on
|
||||||
|
* 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
|
||||||
|
* 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
|
||||||
|
* 8. Reflection on and Ray trace off
|
||||||
|
* 9. Transparency: Glass on, Reflection: Ray trace off
|
||||||
|
* 10. Casts shadows onto invisible surfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *map_Ka; // ambient texture map name
|
||||||
|
char *map_Kd; // diffuse texture map name
|
||||||
|
char *map_Ke; // emissive texture map name
|
||||||
|
char *map_Ks; // specular color texture map name
|
||||||
|
char *map_Ns; // specular highlight texture map name
|
||||||
|
char *map_d; // alpha texture map name
|
||||||
|
char *bump; // bump map name
|
||||||
|
char *disp; // displacement map name
|
||||||
|
char *decal; // stencil decal texture map name
|
||||||
|
|
||||||
|
// @Feature: Texture options are not supported yet
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wf_mtl_file
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
wf_material *materials;
|
||||||
|
wf_size materials_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool wf_parse_obj(char *data, u64 size, const char *name, wf_obj_file *return_obj);
|
||||||
|
bool wf_parse_mtl(char *data, u64 size, const char *name, wf_mtl_file *return_mtl);
|
||||||
|
void wf_cleanup_obj(wf_obj_file *obj_file);
|
||||||
|
void wf_cleanup_mtl(wf_mtl_file *mtl_file);
|
||||||
|
|
||||||
|
#endif
|
||||||
807
code/gui/gui.cpp
Normal file
807
code/gui/gui.cpp
Normal file
@@ -0,0 +1,807 @@
|
|||||||
|
#include "gui.h"
|
||||||
|
#include "../render/2d.h"
|
||||||
|
#include "../render/render.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/color.h"
|
||||||
|
#include "text_draw.h"
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "string.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include "../lib/hashing.h"
|
||||||
|
|
||||||
|
Gui_State global_gui_state;
|
||||||
|
|
||||||
|
void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect = NULL);
|
||||||
|
|
||||||
|
bool gui_init()
|
||||||
|
{
|
||||||
|
gui_context_init(&global_gui_state.default_context);
|
||||||
|
global_gui_state.selected_context = &global_gui_state.default_context;
|
||||||
|
bool success = gui_text_draw_init();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_deinit()
|
||||||
|
{
|
||||||
|
gui_text_draw_deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_context_init(Gui_Context *ctx)
|
||||||
|
{
|
||||||
|
ctx->width = 100;
|
||||||
|
ctx->height = 100;
|
||||||
|
|
||||||
|
ctx-> last_frame_time = 0;
|
||||||
|
ctx->current_frame_time = 0;
|
||||||
|
|
||||||
|
ctx-> active = NULL;
|
||||||
|
ctx-> hot = NULL;
|
||||||
|
ctx->possibly_hot = NULL;
|
||||||
|
ctx->active_start_time = 0;
|
||||||
|
ctx-> hot_start_time = 0;
|
||||||
|
ctx->active_status = 0;
|
||||||
|
|
||||||
|
ctx->text_cursor_position = 0;
|
||||||
|
ctx->text_length = 0;
|
||||||
|
|
||||||
|
ctx->windows = NULL;
|
||||||
|
ctx->window_count = 0;
|
||||||
|
ctx->window_capacity = 0;
|
||||||
|
ctx->current_window = NULL;
|
||||||
|
|
||||||
|
ctx->id_count = 0;
|
||||||
|
ctx->id_capacity = 8;
|
||||||
|
ctx->id_stack = (Gui_Id*)p_alloc(sizeof(Gui_Id) * ctx->id_capacity);
|
||||||
|
|
||||||
|
ctx->input.pointer_position = {0, 0};
|
||||||
|
ctx->input.absolute_pointer_position = {0, 0};
|
||||||
|
ctx->input.mouse_pressed = false;
|
||||||
|
ctx->input.mouse_pressed_this_frame = false;
|
||||||
|
ctx->input.mouse_released_this_frame = false;
|
||||||
|
ctx->input.text_cursor_move = 0;
|
||||||
|
ctx->input.text[0] = '\0';
|
||||||
|
|
||||||
|
ctx->input.absolute_pointer_position_last_frame = {0, 0};
|
||||||
|
|
||||||
|
|
||||||
|
ctx->style.font_size = 12;
|
||||||
|
ctx->style.animation_base_time = 0.100;
|
||||||
|
|
||||||
|
ctx->style.text_color = v4{1.0, 1.0, 1.0, 1.0};
|
||||||
|
ctx->style.text_align = GUI_ALIGN_CENTER;
|
||||||
|
|
||||||
|
|
||||||
|
ctx->style.button_color = v4{0.4f, 0.4f, 0.4f, 1.0f}*0.8f;
|
||||||
|
ctx->style.button_color_hovered = v4{0.3f, 0.3f, 0.3f, 1.0f}*0.9f;
|
||||||
|
ctx->style.button_color_pressed = v4{0.1f, 0.1f, 0.1f, 1.0f};
|
||||||
|
ctx->style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
ctx->style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
ctx->style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
ctx->style.button_radius = 3;
|
||||||
|
|
||||||
|
ctx->style.slider_fill_color = {0.0f, 0.3f, 0.9f, 1.0f};
|
||||||
|
|
||||||
|
ctx->style.window_background_color = {0.01,0.01,0.01, 0.98};
|
||||||
|
ctx->style.window_border_color = {1.0,0.06,0.0,1.0};
|
||||||
|
ctx->style.window_background_color_inactive = {0.05,0.05,0.05, 0.95};
|
||||||
|
ctx->style.window_border_color_inactive = {0.3,0.3,0.3, 1.0};
|
||||||
|
ctx->style.window_corner_radius = 5;
|
||||||
|
ctx->style.window_titlebar_color = ctx->style.window_border_color;
|
||||||
|
ctx->style.window_titlebar_color_inactive = {0.1,0.1,0.1,0.1};
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_context_select(Gui_Context *ctx)
|
||||||
|
{
|
||||||
|
global_gui_state.selected_context = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_frame_begin(Gui_Context *ctx, f64 curr_time)
|
||||||
|
{
|
||||||
|
ctx-> last_frame_time = ctx->current_frame_time;
|
||||||
|
ctx->current_frame_time = curr_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_frame_begin(f64 curr_time)
|
||||||
|
{
|
||||||
|
gui_frame_begin(&global_gui_state.default_context, curr_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_frame_end(Gui_Context *ctx)
|
||||||
|
{
|
||||||
|
// Render windows
|
||||||
|
for(u32 i = 0; i < ctx->window_count; i++)
|
||||||
|
{
|
||||||
|
Gui_Window *w = &ctx->windows[i];
|
||||||
|
if(w->still_open)
|
||||||
|
{
|
||||||
|
Rect r_uv = Rect{0,0,1,-1}; // y is flipped when rendering framebuffer's textures
|
||||||
|
r_2d_immediate_rectangle(w->r, v4{1,1,1,1}, r_uv, &w->framebuffer.color_texture);
|
||||||
|
w->still_open = false; // Will be set to true if still open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @Performance: cleanup unused windows
|
||||||
|
|
||||||
|
// Fix state
|
||||||
|
if(ctx->hot != ctx->possibly_hot)
|
||||||
|
{
|
||||||
|
ctx->hot = ctx->possibly_hot;
|
||||||
|
ctx->hot_start_time = ctx->current_frame_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->input.mouse_pressed_this_frame = false;
|
||||||
|
ctx->input.mouse_released_this_frame = false;
|
||||||
|
|
||||||
|
ctx->input.absolute_pointer_position_last_frame = ctx->input.absolute_pointer_position;
|
||||||
|
|
||||||
|
ctx->input.text[0] = '\0';
|
||||||
|
ctx->input.text_cursor_move = 0;
|
||||||
|
|
||||||
|
ctx->possibly_hot = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_frame_end()
|
||||||
|
{
|
||||||
|
gui_frame_end(&global_gui_state.default_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_handle_event(Gui_Context *ctx, Event *e)
|
||||||
|
{
|
||||||
|
switch(e->type)
|
||||||
|
{
|
||||||
|
case EVENT_MOUSE_MOVE: {
|
||||||
|
if(!e->mouse_move.relative)
|
||||||
|
{
|
||||||
|
ctx->input.pointer_position = e->mouse_move.position;
|
||||||
|
ctx->input.absolute_pointer_position = e->mouse_move.position;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case EVENT_KEY: {
|
||||||
|
switch(e->key.key_code)
|
||||||
|
{
|
||||||
|
case KEY_MOUSE_LEFT: {
|
||||||
|
ctx->input.mouse_pressed = e->key.pressed;
|
||||||
|
if(e->key.pressed)
|
||||||
|
ctx->input.mouse_pressed_this_frame = true;
|
||||||
|
else
|
||||||
|
ctx->input.mouse_released_this_frame = true;
|
||||||
|
} break;
|
||||||
|
case KEY_ARROW_LEFT: {
|
||||||
|
if(e->key.pressed)
|
||||||
|
ctx->input.text_cursor_move--;
|
||||||
|
} break;
|
||||||
|
case KEY_ARROW_RIGHT: {
|
||||||
|
if(e->key.pressed)
|
||||||
|
ctx->input.text_cursor_move++;
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case EVENT_RESIZE: {
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case EVENT_TEXT: {
|
||||||
|
strcat(ctx->input.text, e->text.data);
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_handle_event(Event *e)
|
||||||
|
{
|
||||||
|
gui_handle_event(&global_gui_state.default_context, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### Widgets ###
|
||||||
|
// Text
|
||||||
|
void gui_text(Gui_Context *ctx, Rect r, const char *text)
|
||||||
|
{
|
||||||
|
// @Feature: Clip text to Rect r
|
||||||
|
gui_text_draw(r, text, ctx->style.font_size, ctx->style.text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_text(Rect r, const char *text)
|
||||||
|
{
|
||||||
|
gui_text(&global_gui_state.default_context, r, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment)
|
||||||
|
{
|
||||||
|
// @Cleanup: this should not depend on setting state. We should have a function that gets alignment as an argument
|
||||||
|
Gui_Text_Align old_alignment = ctx->style.text_align;
|
||||||
|
ctx->style.text_align = alignment;
|
||||||
|
gui_button_draw_inner_text(ctx, r, text, ctx->style.text_color);
|
||||||
|
ctx->style.text_align = old_alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_text_aligned(Rect r, const char *text, Gui_Text_Align alignment)
|
||||||
|
{
|
||||||
|
gui_text_aligned(&global_gui_state.default_context, r, text, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
v2 gui_text_compute_size(Gui_Context *ctx, const char *text)
|
||||||
|
{
|
||||||
|
return gui_text_draw_size(text, ctx->style.font_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 gui_text_compute_size(const char *text)
|
||||||
|
{
|
||||||
|
return gui_text_compute_size(&global_gui_state.default_context, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button
|
||||||
|
bool gui_button(Gui_Context *ctx, Rect r, const char *text)
|
||||||
|
{
|
||||||
|
Gui_Id widget_id = gui_id_from_pointer(ctx, text);
|
||||||
|
bool behaviuor = gui_button_behaviuor(ctx, widget_id, r);
|
||||||
|
|
||||||
|
// Compute colors
|
||||||
|
v4 button_color = ctx->style.button_color;
|
||||||
|
v4 text_color = ctx->style.button_text_color;
|
||||||
|
{
|
||||||
|
if(ctx->hot == widget_id)
|
||||||
|
{
|
||||||
|
f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time);
|
||||||
|
f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time);
|
||||||
|
button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation);
|
||||||
|
text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation);
|
||||||
|
}
|
||||||
|
if(ctx->active == widget_id)
|
||||||
|
{
|
||||||
|
f64 delta_t = (ctx->current_frame_time - ctx->active_start_time);
|
||||||
|
f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time);
|
||||||
|
button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6);
|
||||||
|
text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw button and text
|
||||||
|
r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, button_color);
|
||||||
|
gui_button_draw_inner_text(ctx, r, text, text_color);
|
||||||
|
|
||||||
|
return behaviuor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_button(Rect r, const char *text)
|
||||||
|
{
|
||||||
|
return gui_button(&global_gui_state.default_context, r, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect)
|
||||||
|
{
|
||||||
|
v2 text_size = gui_text_draw_size(text, ctx->style.font_size);
|
||||||
|
// Alignment (center, left, right)
|
||||||
|
v2 text_position = r.position + (r.size - text_size) * v2{0.5, 0.5};
|
||||||
|
if(ctx->style.text_align == GUI_ALIGN_LEFT)
|
||||||
|
text_position = r.position + (r.size - text_size) * v2{0, 0.5};
|
||||||
|
if(ctx->style.text_align == GUI_ALIGN_RIGHT)
|
||||||
|
text_position = r.position + (r.size - text_size) * v2{1, 0.5};
|
||||||
|
// Draw
|
||||||
|
Rect text_rect = { .position = text_position, .size = text_size };
|
||||||
|
// @Feature: Clip text to Rect r
|
||||||
|
gui_text_draw(text_rect, text, ctx->style.font_size, color);
|
||||||
|
|
||||||
|
if(actual_drawn_rect)
|
||||||
|
*actual_drawn_rect = text_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value)
|
||||||
|
{
|
||||||
|
Gui_Id widget_id = gui_id_from_pointer(ctx, value);
|
||||||
|
bool behaviour = gui_button_behaviuor(ctx, widget_id, r);
|
||||||
|
|
||||||
|
if(ctx->active == widget_id)
|
||||||
|
{
|
||||||
|
f32 pointer_ratio = (ctx->input.pointer_position.x - r.position.x) / r.size.x;
|
||||||
|
*value = clamp(0.0f, 1.0f, pointer_ratio) * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
v4 button_color = ctx->style.button_color;
|
||||||
|
v4 text_color = ctx->style.button_text_color;
|
||||||
|
{
|
||||||
|
f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time);
|
||||||
|
f32 interpolation = sin(10 * delta_t) * 0.5 + 0.5;
|
||||||
|
if(ctx->hot == widget_id)
|
||||||
|
{
|
||||||
|
button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation);
|
||||||
|
text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation);
|
||||||
|
}
|
||||||
|
if(ctx->active == widget_id)
|
||||||
|
{
|
||||||
|
button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6);
|
||||||
|
text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
f32 border = 2;
|
||||||
|
f32 radius = ctx->style.button_radius;
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
v4 background_color = ctx->style.button_color;
|
||||||
|
r_2d_immediate_rounded_rectangle(r, radius, background_color); // Background
|
||||||
|
|
||||||
|
// Draw fill
|
||||||
|
f32 ratio = (*value - min) / (max - min);
|
||||||
|
Rect fill_r = r;
|
||||||
|
fill_r.position += v2{border, border};
|
||||||
|
fill_r.size = v2{maximum(0, fill_r.size.x - 2*border), maximum(0, fill_r.size.y - 2*border)};
|
||||||
|
f32 fill_radius = maximum(0, radius - border);
|
||||||
|
fill_r.size.x = fill_r.size.x * ratio;
|
||||||
|
r_2d_immediate_rounded_rectangle(fill_r, fill_radius, ctx->style.slider_fill_color);
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
v4 border_color = ctx->style.button_color_pressed;
|
||||||
|
Rect border_r = r;
|
||||||
|
border_r.position += v2{border, border} * 0.5;
|
||||||
|
border_r.size = v2{maximum(0, border_r.size.x - border), maximum(0, border_r.size.y - border)};
|
||||||
|
f32 border_radius = maximum(0, radius - border*0.5);
|
||||||
|
r_2d_immediate_rounded_rectangle_outline(border_r, border_radius, border_color, border);
|
||||||
|
|
||||||
|
// Draw value text
|
||||||
|
char text[64];
|
||||||
|
sprintf(text, "%f", *value);
|
||||||
|
gui_button_draw_inner_text(ctx, r, text, text_color);
|
||||||
|
|
||||||
|
return behaviour || ctx->active == widget_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_slider(Rect r, f32 min, f32 max, f32 *value)
|
||||||
|
{
|
||||||
|
return gui_slider(&global_gui_state.default_context, r, min, max, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Images
|
||||||
|
bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture)
|
||||||
|
{
|
||||||
|
Gui_Id widget_id = gui_id_from_pointer(ctx, texture);
|
||||||
|
|
||||||
|
v4 color = {1,1,1,1};
|
||||||
|
r_2d_immediate_rectangle(r, color, {0,0,1,1}, texture);
|
||||||
|
|
||||||
|
return gui_button_behaviuor(ctx, widget_id, r);;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_image(Rect r, r_texture *texture)
|
||||||
|
{
|
||||||
|
return gui_image(&global_gui_state.default_context, r, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags)
|
||||||
|
{
|
||||||
|
r_texture texture = r_texture_create((u8*)bmp, {width, height}, flags | R_TEXTURE_DONT_OWN);
|
||||||
|
bool result = gui_image(ctx, r, &texture);
|
||||||
|
r_texture_destroy(&texture);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags)
|
||||||
|
{
|
||||||
|
return gui_image(&global_gui_state.default_context, r, bmp, width, height, channels, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Text input
|
||||||
|
bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size)
|
||||||
|
{
|
||||||
|
Gui_Id widget_id = gui_id_from_pointer(ctx, text);
|
||||||
|
bool behaviour = gui_text_input_behaviuor(ctx, widget_id, r);
|
||||||
|
bool edited = false;
|
||||||
|
|
||||||
|
// Cursor, mouse click, input from keyboard/os
|
||||||
|
if(ctx->active == widget_id && ctx->input.mouse_pressed_this_frame)
|
||||||
|
{
|
||||||
|
ctx->text_length = strlen(text);
|
||||||
|
ctx->text_cursor_position = ctx->text_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursors between UTF8 codepoints (not bytes)
|
||||||
|
if(ctx->input.text_cursor_move != 0)
|
||||||
|
{
|
||||||
|
while(ctx->input.text_cursor_move > 0)
|
||||||
|
{
|
||||||
|
if(text[ctx->text_cursor_position] == '\0')
|
||||||
|
{
|
||||||
|
ctx->input.text_cursor_move = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ctx->text_cursor_position += utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position);
|
||||||
|
ctx->input.text_cursor_move--;
|
||||||
|
}
|
||||||
|
while(ctx->input.text_cursor_move < 0)
|
||||||
|
{
|
||||||
|
if(ctx->text_cursor_position == 0)
|
||||||
|
{
|
||||||
|
ctx->input.text_cursor_move = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ctx->text_cursor_position -= utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position);
|
||||||
|
ctx->input.text_cursor_move++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->active == widget_id && ctx->input.text[0] != 0)
|
||||||
|
{
|
||||||
|
// @Bug: Should iterate on utf8 codepoints. If we don't, there's the possibility
|
||||||
|
// of inserting half of a multi-byte codepoint.
|
||||||
|
for(char *c = ctx->input.text; *c != 0; c++)
|
||||||
|
{
|
||||||
|
if(*c == 0x08) // Backspace
|
||||||
|
{
|
||||||
|
if(ctx->text_cursor_position > 0)
|
||||||
|
{
|
||||||
|
u32 codepoint_bytes = utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position);
|
||||||
|
u64 from_index = ctx->text_cursor_position;
|
||||||
|
u64 to_index = ctx->text_cursor_position - codepoint_bytes;
|
||||||
|
memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index);
|
||||||
|
ctx->text_length -= codepoint_bytes;
|
||||||
|
ctx->text_cursor_position -= codepoint_bytes;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(*c == 0x7F) // Delete
|
||||||
|
{
|
||||||
|
if(ctx->text_cursor_position < ctx->text_length)
|
||||||
|
{
|
||||||
|
u32 codepoint_bytes = utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position);
|
||||||
|
u64 from_index = ctx->text_cursor_position + codepoint_bytes;
|
||||||
|
u64 to_index = ctx->text_cursor_position;
|
||||||
|
memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index);
|
||||||
|
ctx->text_length -= codepoint_bytes;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->text_length < max_size - 1) // Leave space for 0 terminator
|
||||||
|
{
|
||||||
|
memmove(text + ctx->text_cursor_position + 1, text + ctx->text_cursor_position, ctx->text_length + 1 - ctx->text_cursor_position);
|
||||||
|
text[ctx->text_cursor_position] = *c;
|
||||||
|
ctx->text_length += 1;
|
||||||
|
ctx->text_cursor_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, ctx->style.button_color);
|
||||||
|
Rect text_rect;
|
||||||
|
gui_button_draw_inner_text(ctx, r, text, ctx->style.button_text_color, &text_rect);
|
||||||
|
|
||||||
|
if(ctx->active == widget_id)
|
||||||
|
{
|
||||||
|
// Draw cursor
|
||||||
|
f64 delta_t = ctx->current_frame_time - ctx->active_start_time;
|
||||||
|
f32 u = clamp(0, 1, sin(delta_t * 5) * 0.7 + 0.6);
|
||||||
|
v4 cursor_color = ctx->style.button_text_color;
|
||||||
|
cursor_color *= lerp(0, cursor_color.a, u);
|
||||||
|
|
||||||
|
char replaced = text[ctx->text_cursor_position];
|
||||||
|
text[ctx->text_cursor_position] = 0;
|
||||||
|
v2 cursor_position;
|
||||||
|
v2 text_size = gui_text_draw_size(text, ctx->style.font_size, &cursor_position);
|
||||||
|
text[ctx->text_cursor_position] = replaced;
|
||||||
|
|
||||||
|
Rect cursor_rect =
|
||||||
|
{
|
||||||
|
.position = text_rect.position + cursor_position - v2{0, ctx->style.font_size},
|
||||||
|
.size = ctx->style.font_size * v2{0.1, 0.9}
|
||||||
|
};
|
||||||
|
r_2d_immediate_rectangle(cursor_rect, cursor_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return edited;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_text_input(Rect r, char *text, u64 max_size)
|
||||||
|
{
|
||||||
|
return gui_text_input(&global_gui_state.default_context, r, text, max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id)
|
||||||
|
{
|
||||||
|
gui_id_stack_push(ctx, id);
|
||||||
|
Gui_Window *window = gui_window_by_id(ctx, r, id);
|
||||||
|
window->still_open = true;
|
||||||
|
gui_window_update_rect(ctx, window, r);
|
||||||
|
u32 window_index = window - ctx->windows;
|
||||||
|
|
||||||
|
bool hovered = gui_is_hovered(ctx, id, r);
|
||||||
|
if(hovered && ctx->input.mouse_pressed_this_frame)
|
||||||
|
{
|
||||||
|
// Bring window on top
|
||||||
|
u32 move_count = ctx->window_count - 1 - window_index;
|
||||||
|
if(move_count > 0)
|
||||||
|
{
|
||||||
|
Gui_Window tmp = *window;
|
||||||
|
memmove(ctx->windows + window_index, ctx->windows + window_index + 1, sizeof(Gui_Window) * move_count);
|
||||||
|
|
||||||
|
ctx->windows[ctx->window_count - 1] = tmp;
|
||||||
|
window_index = ctx->window_count - 1;
|
||||||
|
window = &ctx->windows[window_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->current_window = window;
|
||||||
|
ctx->input.pointer_position = ctx->input.absolute_pointer_position - window->r.position;
|
||||||
|
|
||||||
|
ctx->old_framebuffer = r_render_state.current_framebuffer;
|
||||||
|
r_framebuffer_select(&window->framebuffer);
|
||||||
|
|
||||||
|
|
||||||
|
bool is_inactive = window_index != ctx->window_count-1;
|
||||||
|
v4 background_color = is_inactive ? ctx->style.window_background_color_inactive :
|
||||||
|
ctx->style.window_background_color;
|
||||||
|
v4 border_color = is_inactive ? ctx->style.window_border_color_inactive :
|
||||||
|
ctx->style.window_border_color;
|
||||||
|
r_clear({0,0,0,0});
|
||||||
|
Rect background_rect = {0.5, 0.5, floor(r.w)-1.0, floor(r.h)-1.0};
|
||||||
|
r_2d_immediate_rounded_rectangle(background_rect, ctx->style.window_corner_radius, background_color);
|
||||||
|
r_2d_immediate_rounded_rectangle_outline(background_rect, ctx->style.window_corner_radius, border_color, 1.0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_window_start(Rect r, Gui_Id id)
|
||||||
|
{
|
||||||
|
return gui_window_start(&global_gui_state.default_context, r, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_window_end(Gui_Context *ctx)
|
||||||
|
{
|
||||||
|
gui_id_stack_pop(ctx);
|
||||||
|
ctx->current_window = NULL;
|
||||||
|
ctx->input.pointer_position = ctx->input.absolute_pointer_position;
|
||||||
|
|
||||||
|
r_framebuffer_select(ctx->old_framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_window_end()
|
||||||
|
{
|
||||||
|
return gui_window_end(&global_gui_state.default_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move)
|
||||||
|
{
|
||||||
|
Gui_Id widget_id = gui_id_from_pointer(ctx, title);
|
||||||
|
bool behaviour = gui_button_behaviuor(ctx, widget_id, r);
|
||||||
|
if(close)
|
||||||
|
*close = false;
|
||||||
|
if(move)
|
||||||
|
*move = {0,0};
|
||||||
|
|
||||||
|
// Background
|
||||||
|
v4 titlebar_color = ctx->style.window_titlebar_color_inactive;
|
||||||
|
if(ctx->current_window && ctx->current_window - ctx->windows == ctx->window_count - 1)
|
||||||
|
{
|
||||||
|
titlebar_color = ctx->style.window_titlebar_color;
|
||||||
|
}
|
||||||
|
r_2d_immediate_rounded_rectangle(r, ctx->style.window_corner_radius, titlebar_color);
|
||||||
|
|
||||||
|
// Title
|
||||||
|
v2 title_size = gui_text_compute_size(title);
|
||||||
|
Rect title_r = r;
|
||||||
|
title_r.size = title_size;
|
||||||
|
title_r.position = r.position + (r.size - title_r.size) / 2;
|
||||||
|
gui_text(title_r, title);
|
||||||
|
|
||||||
|
// Exit button
|
||||||
|
f32 smallest_side = minimum(r.w, r.h);
|
||||||
|
f32 exit_size = smallest_side;
|
||||||
|
Gui_Style exit_style = ctx->style;
|
||||||
|
exit_style.button_color = v4{0.8f, 0.8f, 0.8f, 1.0f}*0.0f;
|
||||||
|
exit_style.button_color_hovered = v4{1.0f, 0.0f, 0.0f, 1.0f}*1.0f;
|
||||||
|
exit_style.button_color_pressed = v4{0.8f, 0.0f, 0.0f, 1.0f};
|
||||||
|
exit_style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
exit_style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
exit_style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
exit_style.button_radius = ctx->style.window_corner_radius;
|
||||||
|
Gui_Style old_style = ctx->style;
|
||||||
|
ctx->style = exit_style;
|
||||||
|
Rect exit_button_r = {r.x + r.w - exit_size, r.y, exit_size, exit_size};
|
||||||
|
if(gui_button(ctx, exit_button_r, "⨯"))
|
||||||
|
{
|
||||||
|
if(close)
|
||||||
|
*close = true;
|
||||||
|
behaviour = true;
|
||||||
|
}
|
||||||
|
ctx->style = old_style;
|
||||||
|
|
||||||
|
// Move
|
||||||
|
if(ctx->active == widget_id && !ctx->input.mouse_pressed_this_frame)
|
||||||
|
{
|
||||||
|
if(move)
|
||||||
|
{
|
||||||
|
*move = ctx->input.absolute_pointer_position - ctx->input.absolute_pointer_position_last_frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return behaviour || ctx->active == widget_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move)
|
||||||
|
{
|
||||||
|
return gui_window_titlebar(&global_gui_state.default_context, r, title, close, move);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id)
|
||||||
|
{
|
||||||
|
Gui_Window *window = NULL;
|
||||||
|
for(u32 i = 0; i < ctx->window_count; i++)
|
||||||
|
{
|
||||||
|
if(ctx->windows[i].id == id)
|
||||||
|
{
|
||||||
|
window = &ctx->windows[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!window)
|
||||||
|
{
|
||||||
|
if(ctx->window_count >= ctx->window_capacity)
|
||||||
|
{
|
||||||
|
if(ctx->window_capacity == 0)
|
||||||
|
ctx->window_capacity = 1;
|
||||||
|
ctx->window_capacity *= 2;
|
||||||
|
ctx->windows = (Gui_Window*) p_realloc(ctx->windows, sizeof(Gui_Window) * ctx->window_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
window = &ctx->windows[ctx->window_count];
|
||||||
|
ctx->window_count++;
|
||||||
|
|
||||||
|
window->id = id;
|
||||||
|
window->r = r;
|
||||||
|
window->framebuffer = r_framebuffer_create(V2S(r.size), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r)
|
||||||
|
{
|
||||||
|
if(window->r.size != r.size)
|
||||||
|
{
|
||||||
|
r_framebuffer_update_size(&window->framebuffer, V2S(r.size));
|
||||||
|
}
|
||||||
|
window->r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
bool gui_is_hovered(Gui_Context *ctx, Gui_Id widget_id, Rect r)
|
||||||
|
{
|
||||||
|
if(is_inside(r, ctx->input.pointer_position))
|
||||||
|
{
|
||||||
|
s32 current_window_index = -1; // We use -1 to indicate we are not in a window. When we iterate over windows we do a +1 and start from 0, aka the first window. If we used 0, we would start from 1 and skip over window index 0.
|
||||||
|
|
||||||
|
// The ctx->windows array is sorted from back to front. If we are inside a window, only the following windows in the array can overlap up. The ones before are covered by the current window.
|
||||||
|
if(ctx->current_window)
|
||||||
|
current_window_index = ctx->current_window - ctx->windows;
|
||||||
|
|
||||||
|
// Am I a window? If so, we start checking from us. If ctx->current_window is set and widget_id is a window, it means we are a subwindow.
|
||||||
|
// Subwindow are not supported yet though (20 September 2023), so this should be a bug in the user code. Yeah we don't check to prevent this, but anyways.
|
||||||
|
for(s32 i = current_window_index + 1; i < ctx->window_count; i++)
|
||||||
|
{
|
||||||
|
Gui_Id window_id = ctx->windows[i].id;
|
||||||
|
if(widget_id == window_id)
|
||||||
|
{
|
||||||
|
current_window_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over windows that cover the current one
|
||||||
|
for(u32 i = current_window_index + 1; i < ctx->window_count; i++)
|
||||||
|
{
|
||||||
|
Gui_Id window_id = ctx->windows[i].id;
|
||||||
|
Rect window_rect = ctx->windows[i].r;
|
||||||
|
if(is_inside(window_rect, ctx->input.absolute_pointer_position))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gui_button_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r)
|
||||||
|
{
|
||||||
|
bool behaviour = false;
|
||||||
|
if(gui_is_hovered(ctx, widget_id, r))
|
||||||
|
{
|
||||||
|
if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT))
|
||||||
|
ctx->possibly_hot = widget_id;
|
||||||
|
|
||||||
|
if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame)
|
||||||
|
{
|
||||||
|
ctx->active = widget_id;
|
||||||
|
ctx->active_start_time = ctx->current_frame_time;
|
||||||
|
ctx->active_status = GUI_WIDGET_STATUS_PREVENT_HOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
|
||||||
|
{
|
||||||
|
behaviour = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
|
||||||
|
{
|
||||||
|
ctx->active = NULL;
|
||||||
|
ctx->active_status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return behaviour;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r)
|
||||||
|
{
|
||||||
|
bool behaviour = false;
|
||||||
|
if(gui_is_hovered(ctx, widget_id, r))
|
||||||
|
{
|
||||||
|
if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT))
|
||||||
|
ctx->possibly_hot = widget_id;
|
||||||
|
|
||||||
|
if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame)
|
||||||
|
{
|
||||||
|
ctx->active = widget_id;
|
||||||
|
ctx->active_start_time = ctx->current_frame_time;
|
||||||
|
ctx->active_status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
|
||||||
|
{
|
||||||
|
behaviour = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
|
||||||
|
{
|
||||||
|
// ctx->active = NULL;
|
||||||
|
// ctx->active_status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return behaviour;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr)
|
||||||
|
{
|
||||||
|
u32 seed = 0xFFFFFFFF;
|
||||||
|
if(ctx->id_count)
|
||||||
|
seed = ctx->id_stack[ctx->id_count - 1];
|
||||||
|
return hash_crc32(&ptr, sizeof(void*), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_id_stack_push(Gui_Context *ctx, Gui_Id id)
|
||||||
|
{
|
||||||
|
if(ctx->id_capacity <= ctx->id_count)
|
||||||
|
{
|
||||||
|
u32 new_capacity = maximum(ctx->id_count + 1, ctx->id_capacity * 2);
|
||||||
|
ctx->id_stack = (Gui_Id*)p_realloc(ctx->id_stack, sizeof(Gui_Id) * new_capacity);
|
||||||
|
ctx->id_capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->id_stack[ctx->id_count] = id;
|
||||||
|
ctx->id_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_id_stack_pop(Gui_Context *ctx)
|
||||||
|
{
|
||||||
|
if(ctx->id_count > 0)
|
||||||
|
ctx->id_count--;
|
||||||
|
}
|
||||||
195
code/gui/gui.h
Normal file
195
code/gui/gui.h
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#ifndef _PIUMA_GUI_H_
|
||||||
|
#define _PIUMA_GUI_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "../render/2d.h"
|
||||||
|
#include "../lib/text.h"
|
||||||
|
#include "../lib/event.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef u32 Gui_Id;
|
||||||
|
|
||||||
|
|
||||||
|
struct Gui_Input
|
||||||
|
{
|
||||||
|
v2 pointer_position;
|
||||||
|
v2 absolute_pointer_position;
|
||||||
|
bool mouse_pressed;
|
||||||
|
bool mouse_pressed_this_frame;
|
||||||
|
bool mouse_released_this_frame;
|
||||||
|
s64 text_cursor_move;
|
||||||
|
char text[32];
|
||||||
|
|
||||||
|
v2 absolute_pointer_position_last_frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Gui_Text_Align
|
||||||
|
{
|
||||||
|
GUI_ALIGN_CENTER,
|
||||||
|
GUI_ALIGN_LEFT,
|
||||||
|
GUI_ALIGN_RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gui_Style
|
||||||
|
{
|
||||||
|
f32 font_size;
|
||||||
|
f32 animation_base_time;
|
||||||
|
|
||||||
|
v4 text_color;
|
||||||
|
Gui_Text_Align text_align;
|
||||||
|
|
||||||
|
v4 button_color;
|
||||||
|
v4 button_color_hovered;
|
||||||
|
v4 button_color_pressed;
|
||||||
|
v4 button_text_color;
|
||||||
|
v4 button_text_color_hovered;
|
||||||
|
v4 button_text_color_pressed;
|
||||||
|
f32 button_radius;
|
||||||
|
|
||||||
|
v4 slider_fill_color;
|
||||||
|
|
||||||
|
v4 window_background_color;
|
||||||
|
v4 window_border_color;
|
||||||
|
v4 window_background_color_inactive;
|
||||||
|
v4 window_border_color_inactive;
|
||||||
|
f32 window_corner_radius;
|
||||||
|
v4 window_titlebar_color;
|
||||||
|
v4 window_titlebar_color_inactive;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gui_Window
|
||||||
|
{
|
||||||
|
Gui_Id id;
|
||||||
|
Rect r;
|
||||||
|
r_framebuffer framebuffer;
|
||||||
|
bool still_open;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Gui_Widget_Status_Flags : u8
|
||||||
|
{
|
||||||
|
GUI_WIDGET_STATUS_NONE,
|
||||||
|
GUI_WIDGET_STATUS_PREVENT_HOT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gui_Context
|
||||||
|
{
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
|
||||||
|
f64 last_frame_time;
|
||||||
|
f64 current_frame_time;
|
||||||
|
|
||||||
|
Gui_Id active; // Are we interacting with the widget (pressed for buttons, ready to receive text for text inputs)
|
||||||
|
Gui_Id hot; // Hovered the previous frame
|
||||||
|
Gui_Id possibly_hot; // Might become hot this frame
|
||||||
|
f64 active_start_time;
|
||||||
|
f64 hot_start_time;
|
||||||
|
u8 active_status;
|
||||||
|
|
||||||
|
// Text input state
|
||||||
|
u64 text_cursor_position;
|
||||||
|
u64 text_length;
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
Gui_Window *windows;
|
||||||
|
u32 window_count;
|
||||||
|
u32 window_capacity;
|
||||||
|
Gui_Window *current_window;
|
||||||
|
r_framebuffer *old_framebuffer;
|
||||||
|
|
||||||
|
// ID
|
||||||
|
Gui_Id *id_stack;
|
||||||
|
u32 id_count;
|
||||||
|
u32 id_capacity;
|
||||||
|
|
||||||
|
Gui_Input input;
|
||||||
|
Gui_Style style;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gui_State
|
||||||
|
{
|
||||||
|
Gui_Context default_context;
|
||||||
|
Gui_Context *selected_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern Gui_State global_gui_state;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool gui_init();
|
||||||
|
void gui_deinit();
|
||||||
|
|
||||||
|
void gui_context_init(Gui_Context *ctx);
|
||||||
|
//@Correctness: gui_context_deinit
|
||||||
|
|
||||||
|
void gui_context_select(Gui_Context *ctx); // Set implicit Gui_Context
|
||||||
|
|
||||||
|
void gui_frame_begin(Gui_Context *ctx, f64 curr_time);
|
||||||
|
void gui_frame_begin(f64 curr_time);
|
||||||
|
void gui_frame_end(Gui_Context *ctx);
|
||||||
|
void gui_frame_end();
|
||||||
|
|
||||||
|
void gui_handle_event(Gui_Context *ctx, Event *e);
|
||||||
|
void gui_handle_event(Event *e);
|
||||||
|
|
||||||
|
// ### Widgets ###
|
||||||
|
// Text
|
||||||
|
void gui_text(Gui_Context *ctx, Rect r, const char *text);
|
||||||
|
void gui_text(Rect r, const char *text);
|
||||||
|
void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment);
|
||||||
|
void gui_text_aligned(Rect r, const char *text, Gui_Text_Align alignment);
|
||||||
|
|
||||||
|
v2 gui_text_compute_size(Gui_Context *ctx, const char *text);
|
||||||
|
v2 gui_text_compute_size(const char *text);
|
||||||
|
|
||||||
|
// Button
|
||||||
|
bool gui_button(Gui_Context *ctx, Rect r, const char *text);
|
||||||
|
bool gui_button(Rect r, const char *text);
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value);
|
||||||
|
bool gui_slider(Rect r, f32 min, f32 max, f32 *value);
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
// Option buttons
|
||||||
|
// Combo box
|
||||||
|
// Tooltips
|
||||||
|
|
||||||
|
// Images
|
||||||
|
bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture);
|
||||||
|
bool gui_image(Rect r, r_texture *texture);
|
||||||
|
bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags = 0);
|
||||||
|
bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags = 0);
|
||||||
|
|
||||||
|
// Text input
|
||||||
|
bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size);
|
||||||
|
bool gui_text_input(Rect r, char *text, u64 max_size);
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id); // You have to provide some kind of unique id to identify windows
|
||||||
|
bool gui_window_start(Rect r, Gui_Id id);
|
||||||
|
void gui_window_end(Gui_Context *ctx);
|
||||||
|
void gui_window_end();
|
||||||
|
|
||||||
|
bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move);
|
||||||
|
bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move);
|
||||||
|
|
||||||
|
Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id); // Rect r might be needed for creation
|
||||||
|
void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r);
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
bool gui_is_hovered (Gui_Context *ctx, Gui_Id widget_id, Rect r);
|
||||||
|
bool gui_button_behaviuor (Gui_Context *ctx, Gui_Id widget_id, Rect r);
|
||||||
|
bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r);
|
||||||
|
bool gui_window_behaviour (Gui_Context *ctx, Gui_Id widget_id, Rect r);
|
||||||
|
|
||||||
|
Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr);
|
||||||
|
void gui_id_stack_push(Gui_Context *ctx, Gui_Id id);
|
||||||
|
void gui_id_stack_pop(Gui_Context *ctx);
|
||||||
|
|
||||||
|
#endif
|
||||||
159
code/gui/layout.cpp
Normal file
159
code/gui/layout.cpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#include "layout.h"
|
||||||
|
#include "gui.h"
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
Rect Gui_Layout_Grid::rect()
|
||||||
|
{
|
||||||
|
return last_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gui_Layout_Grid::row(u32 count)
|
||||||
|
{
|
||||||
|
cursor.x = 0;
|
||||||
|
cursor.y += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect Gui_Layout_Grid::cell(u32 count)
|
||||||
|
{
|
||||||
|
count = minimum(count, max_cells_count.x);
|
||||||
|
s32 free_space_in_row = max_cells_count.x - cursor.x;
|
||||||
|
if(free_space_in_row < count)
|
||||||
|
row();
|
||||||
|
|
||||||
|
last_rect = rect_at(cursor, {count, 1});
|
||||||
|
cursor.x += count;
|
||||||
|
|
||||||
|
return last_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect Gui_Layout_Grid::rect_at(v2s cell_index, v2s size)
|
||||||
|
{
|
||||||
|
Rect result;
|
||||||
|
v2 cell_border_v = v2{cell_border, cell_border};
|
||||||
|
result.position = window_position + cell_border_v + (cell_border_v + cell_size) * V2(cell_index);
|
||||||
|
result.size = cell_size * V2(size) + cell_border_v * (V2(size - v2s{1,1}));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Gui_Layout_Grid gui_layout_grid_create_by_divisions(v2 position, v2 window_size, u32 divisions_x, u32 divisions_y, f32 border)
|
||||||
|
{
|
||||||
|
Gui_Layout_Grid layout;
|
||||||
|
layout.window_position = position;
|
||||||
|
layout.window_size = window_size;
|
||||||
|
layout.cell_size = (window_size - v2{1,1}*border) / v2{divisions_x, divisions_y} - v2{1,1}*border;
|
||||||
|
layout.cell_border = border;
|
||||||
|
layout.max_cells_count = v2s{divisions_x, divisions_y};
|
||||||
|
|
||||||
|
layout.cursor = {0,0};
|
||||||
|
layout.last_rect = layout.rect_at({0,0});
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui_Layout_Grid gui_layout_grid_create_by_cell_size(v2 position, v2 window_size, v2 cell_size, f32 border)
|
||||||
|
{
|
||||||
|
Gui_Layout_Grid layout;
|
||||||
|
layout.window_position = position;
|
||||||
|
layout.window_size = window_size;
|
||||||
|
layout.cell_size = cell_size;
|
||||||
|
layout.cell_border = border;
|
||||||
|
layout.max_cells_count = V2S((window_size - v2{1,1}*border) / (cell_size + v2{1,1}*border));
|
||||||
|
|
||||||
|
layout.cursor = {0,0};
|
||||||
|
layout.last_rect = layout.rect_at({0,0});
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
Rect Gui_Layout_Basic::rect()
|
||||||
|
{
|
||||||
|
return last_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gui_Layout_Basic::push_rect(Rect r)
|
||||||
|
{
|
||||||
|
cursor.x = cursor.x + r.size.x + element_distance.x;
|
||||||
|
cursor.y = cursor.y;
|
||||||
|
|
||||||
|
last_rect = r;
|
||||||
|
next_row_y = maximum(next_row_y, cursor.y + r.size.y + element_distance.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gui_Layout_Basic::row()
|
||||||
|
{
|
||||||
|
cursor.x = window_padding.x;
|
||||||
|
cursor.y = next_row_y;
|
||||||
|
next_row_y = cursor.y + font_size + element_distance.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect Gui_Layout_Basic::label_rect(const char *text)
|
||||||
|
{
|
||||||
|
Rect r;
|
||||||
|
r.size.y = font_size;
|
||||||
|
r.size.x = gui_text_compute_size(text).x;
|
||||||
|
|
||||||
|
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
|
||||||
|
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
|
||||||
|
row();
|
||||||
|
|
||||||
|
r.position = window_position + cursor;
|
||||||
|
|
||||||
|
push_rect(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect Gui_Layout_Basic::button_rect(const char *text)
|
||||||
|
{
|
||||||
|
Rect r;
|
||||||
|
r.size.y = font_size;
|
||||||
|
r.size.x = gui_text_compute_size(text).x;
|
||||||
|
r.size += 2*button_padding;
|
||||||
|
|
||||||
|
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
|
||||||
|
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
|
||||||
|
row();
|
||||||
|
|
||||||
|
r.position = window_position + cursor;
|
||||||
|
|
||||||
|
push_rect(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect Gui_Layout_Basic::button_rect(f32 length)
|
||||||
|
{
|
||||||
|
Rect r;
|
||||||
|
r.size.y = font_size;
|
||||||
|
r.size.x = length;
|
||||||
|
r.size.y += 2*button_padding.y; // No x padding in this case. We already have an assigned length
|
||||||
|
|
||||||
|
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
|
||||||
|
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
|
||||||
|
row();
|
||||||
|
|
||||||
|
r.position = window_position + cursor;
|
||||||
|
|
||||||
|
push_rect(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui_Layout_Basic gui_layout_basic_create(v2 position, v2 size, f32 font_size)
|
||||||
|
{
|
||||||
|
Gui_Layout_Basic layout;
|
||||||
|
layout.window_position = position;
|
||||||
|
layout.window_size = size;
|
||||||
|
|
||||||
|
layout.cursor = {0,0};
|
||||||
|
layout.next_row_y = 0;
|
||||||
|
layout.last_rect = {0,0,0,0};
|
||||||
|
|
||||||
|
layout.font_size = font_size;
|
||||||
|
layout.button_padding = {0,0};
|
||||||
|
layout.element_distance = {0,0};
|
||||||
|
layout.window_padding = {0,0};
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
57
code/gui/layout.h
Normal file
57
code/gui/layout.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef _PIUMA_GUI_LAYOUT_H_
|
||||||
|
#define _PIUMA_GUI_LAYOUT_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
const s32 GUI_LAYOUT_MAX_CELLS = 0x7FFFFFFF;
|
||||||
|
|
||||||
|
struct Gui_Layout_Grid
|
||||||
|
{
|
||||||
|
v2 window_position;
|
||||||
|
v2 window_size;
|
||||||
|
v2 cell_size;
|
||||||
|
f32 cell_border;
|
||||||
|
v2s max_cells_count;
|
||||||
|
|
||||||
|
v2s cursor;
|
||||||
|
Rect last_rect;
|
||||||
|
|
||||||
|
Rect rect(); // Get last rect
|
||||||
|
void row(u32 count = 1);
|
||||||
|
Rect cell(u32 count = 1);
|
||||||
|
Rect rect_at(v2s position, v2s size = {1,1}); // Does not modify cursor. You have to assign it yourself.
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Gui_Layout_Grid gui_layout_grid_create_by_divisions(v2 position, v2 window_size, u32 divisions_x, u32 divisions_y, f32 border = 0);
|
||||||
|
Gui_Layout_Grid gui_layout_grid_create_by_cell_size(v2 position, v2 window_size, v2 cell_size, f32 border = 0);
|
||||||
|
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
struct Gui_Layout_Basic
|
||||||
|
{
|
||||||
|
v2 window_position;
|
||||||
|
v2 window_size;
|
||||||
|
|
||||||
|
v2 cursor = {0,0};
|
||||||
|
f32 next_row_y = 0;
|
||||||
|
Rect last_rect = {0,0,0,0};
|
||||||
|
|
||||||
|
f32 font_size;
|
||||||
|
v2 button_padding = {0,0};
|
||||||
|
v2 element_distance = {0,0};
|
||||||
|
v2 window_padding = {0,0};
|
||||||
|
|
||||||
|
Rect rect();
|
||||||
|
void push_rect(Rect r);
|
||||||
|
void row();
|
||||||
|
Rect label_rect(const char *text = "");
|
||||||
|
Rect button_rect(const char *text = "");
|
||||||
|
Rect button_rect(f32 length);
|
||||||
|
};
|
||||||
|
|
||||||
|
Gui_Layout_Basic gui_layout_basic_create(v2 position, v2 size, f32 font_size);
|
||||||
|
|
||||||
|
#endif
|
||||||
548
code/gui/text_draw.cpp
Normal file
548
code/gui/text_draw.cpp
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
#include "text_draw.h"
|
||||||
|
#include "../lib/color.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "../lib/text.h"
|
||||||
|
#include "../lib/ds.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include "stb_truetype.h"
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
#include "../assets.h"
|
||||||
|
#include "../enginestate.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct gui_glyph_info
|
||||||
|
{
|
||||||
|
utf8_codepoint codepoint;
|
||||||
|
Rect box; // .position = offset from position for alignment, .size = size of the bitmap to draw
|
||||||
|
v2u position; // Position of the top left border in the texture
|
||||||
|
s32 advance;
|
||||||
|
u32 next; // Anything >= container_capacity is to be considered NULL
|
||||||
|
u32 previous;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Glyph caching:
|
||||||
|
* We store a small number of sizes and a fairly large number of characters for each size.
|
||||||
|
* Each slot will have the same width and height, so that we can easily replace it without
|
||||||
|
* re-packing everything. This will also make the code simpler.
|
||||||
|
* We use 0xFFFFFFFF as a placeholder for empty slots.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct gui_glyph_codepoint_map
|
||||||
|
{
|
||||||
|
utf8_codepoint codepoint;
|
||||||
|
u32 index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gui_glyph_texture
|
||||||
|
{
|
||||||
|
f32 font_size;
|
||||||
|
|
||||||
|
struct gui_glyph_codepoint_map *sorted_indices;
|
||||||
|
gui_glyph_info *info;
|
||||||
|
u32 oldest;
|
||||||
|
u32 newest;
|
||||||
|
u32 capacity;
|
||||||
|
|
||||||
|
v2s glyph_max_size;
|
||||||
|
|
||||||
|
r_texture *texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gui_glyph_cache
|
||||||
|
{
|
||||||
|
gui_glyph_texture *glyphs;
|
||||||
|
u32 capacity;
|
||||||
|
u32 oldest;
|
||||||
|
|
||||||
|
u32 max_glyphs_per_texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void gui_glyph_cache_init();
|
||||||
|
static void gui_glyph_cache_deinit();
|
||||||
|
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity);
|
||||||
|
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs);
|
||||||
|
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Globals
|
||||||
|
static u8 *Font_File;
|
||||||
|
static stbtt_fontinfo Font_Info;
|
||||||
|
static gui_glyph_cache Glyph_Cache;
|
||||||
|
|
||||||
|
|
||||||
|
bool gui_text_draw_init()
|
||||||
|
{
|
||||||
|
// @Feature: user specified font
|
||||||
|
// @Cleanup: one-line file read
|
||||||
|
p_file f;
|
||||||
|
p_file_init(&f, "assets/fonts/DejaVuSerif-Bold.ttf");
|
||||||
|
Buffer buf;
|
||||||
|
buf.size = p_file_size(&f);
|
||||||
|
buf.data = (u8*)p_alloc(buf.size + 1);
|
||||||
|
buf.data[buf.size] = '\0';
|
||||||
|
p_file_read(&f, &buf, buf.size);
|
||||||
|
p_file_deinit(&f);
|
||||||
|
Font_File = buf.data;
|
||||||
|
|
||||||
|
if(!stbtt_InitFont(&Font_Info, Font_File, 0))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load font.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gui_glyph_cache_init();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_text_draw_deinit()
|
||||||
|
{
|
||||||
|
gui_glyph_cache_deinit();
|
||||||
|
p_free(Font_File);
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position)
|
||||||
|
{
|
||||||
|
// UTF8 conversion
|
||||||
|
u64 text_length = utf8_codepoint_count(text);
|
||||||
|
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
|
||||||
|
{
|
||||||
|
u64 bytes_read = 0;
|
||||||
|
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute size
|
||||||
|
v2 result = gui_utf8_text_draw_size(codepoints, text_length, font_size, cursor_position);
|
||||||
|
|
||||||
|
p_free(codepoints);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color)
|
||||||
|
{
|
||||||
|
// UTF8 conversion
|
||||||
|
u64 text_length = utf8_codepoint_count(text);
|
||||||
|
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
|
||||||
|
{
|
||||||
|
u64 bytes_read = 0;
|
||||||
|
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
gui_utf8_text_draw(r, codepoints, text_length, font_size, color);
|
||||||
|
|
||||||
|
p_free(codepoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position)
|
||||||
|
{
|
||||||
|
f32 font_scale;
|
||||||
|
s32 font_ascent;
|
||||||
|
s32 font_descent;
|
||||||
|
s32 font_line_gap;
|
||||||
|
s32 font_baseline;
|
||||||
|
|
||||||
|
{
|
||||||
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
||||||
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
||||||
|
font_baseline = font_ascent;
|
||||||
|
|
||||||
|
font_ascent *= font_scale;
|
||||||
|
font_descent *= font_scale;
|
||||||
|
font_line_gap *= font_scale;
|
||||||
|
font_baseline *= font_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute size
|
||||||
|
v2 size = {0, (f32)(font_ascent - font_descent)};
|
||||||
|
{
|
||||||
|
v2 cursor = {0, (f32)(font_ascent - font_descent)};
|
||||||
|
for(u64 i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
s32 advance, lsb;
|
||||||
|
stbtt_GetCodepointHMetrics(&Font_Info, text[i], &advance, &lsb);
|
||||||
|
|
||||||
|
// Special characters
|
||||||
|
if(text[i] == 10) // '\n'
|
||||||
|
{
|
||||||
|
advance = 0;
|
||||||
|
cursor.x = 0;
|
||||||
|
cursor.y += font_ascent - font_descent + font_line_gap;
|
||||||
|
size.y = cursor.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal characters
|
||||||
|
cursor.x += floor(advance * font_scale); // Remember to consider kerning
|
||||||
|
size.x = maximum(size.x, cursor.x);
|
||||||
|
}
|
||||||
|
if(cursor_position)
|
||||||
|
*cursor_position = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color)
|
||||||
|
{
|
||||||
|
f32 font_scale;
|
||||||
|
s32 font_ascent;
|
||||||
|
s32 font_descent;
|
||||||
|
s32 font_line_gap;
|
||||||
|
s32 font_baseline;
|
||||||
|
|
||||||
|
{
|
||||||
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
||||||
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
||||||
|
font_baseline = font_ascent;
|
||||||
|
|
||||||
|
font_ascent *= font_scale;
|
||||||
|
font_descent *= font_scale;
|
||||||
|
font_line_gap *= font_scale;
|
||||||
|
font_baseline *= font_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute glyphs
|
||||||
|
gui_glyph_texture *glyphs = gui_glyph_cache_texture_for_codepoints(font_size, text, length);
|
||||||
|
|
||||||
|
// Map text to quads
|
||||||
|
v2 *vertices;
|
||||||
|
v2 *uvs;
|
||||||
|
u64 draw_count = 0;
|
||||||
|
{
|
||||||
|
vertices = (v2*) p_alloc(6 * length * sizeof(v2)); // 2 triangles, 3 vertices each = 6 vertices
|
||||||
|
uvs = (v2*) p_alloc(6 * length * sizeof(v2));
|
||||||
|
|
||||||
|
|
||||||
|
v2 position = r.position;
|
||||||
|
position.y += font_baseline;
|
||||||
|
for(u64 i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&text[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
||||||
|
if(found == NULL)
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot find codepoint 0x%X in glyph list.", text[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 glyph_i = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
|
||||||
|
gui_glyph_info *info = &glyphs->info[glyph_i];
|
||||||
|
|
||||||
|
// Special characters
|
||||||
|
if(text[i] == 10) // '\n'
|
||||||
|
{
|
||||||
|
position.x = r.position.x;
|
||||||
|
position.y += font_ascent - font_descent + font_line_gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal characters
|
||||||
|
// Map character to its vertices
|
||||||
|
{
|
||||||
|
Rect r;
|
||||||
|
Rect r_uv;
|
||||||
|
r.position = position + info->box.position;
|
||||||
|
r.position.x = floor(r.position.x + 0.5);
|
||||||
|
r.position.y = floor(r.position.y + 0.5);
|
||||||
|
r.size = info->box.size;
|
||||||
|
|
||||||
|
r_uv.position = V2(info->position) / V2(glyphs->texture->size);
|
||||||
|
r_uv.size = info->box.size / V2(glyphs->texture->size);
|
||||||
|
|
||||||
|
v2 a = r.position + v2{r.w, 0 };
|
||||||
|
v2 b = r.position + v2{0 , 0 };
|
||||||
|
v2 c = r.position + v2{0 , r.h};
|
||||||
|
v2 d = r.position + v2{r.w, r.h};
|
||||||
|
v2 a_uv = r_uv.position + v2{r_uv.w, 0 };
|
||||||
|
v2 b_uv = r_uv.position + v2{0 , 0 };
|
||||||
|
v2 c_uv = r_uv.position + v2{0 , r_uv.h};
|
||||||
|
v2 d_uv = r_uv.position + v2{r_uv.w, r_uv.h};
|
||||||
|
|
||||||
|
vertices[6*draw_count + 0] = a;
|
||||||
|
vertices[6*draw_count + 1] = b;
|
||||||
|
vertices[6*draw_count + 2] = c;
|
||||||
|
vertices[6*draw_count + 3] = a;
|
||||||
|
vertices[6*draw_count + 4] = c;
|
||||||
|
vertices[6*draw_count + 5] = d;
|
||||||
|
uvs[6*draw_count + 0] = a_uv;
|
||||||
|
uvs[6*draw_count + 1] = b_uv;
|
||||||
|
uvs[6*draw_count + 2] = c_uv;
|
||||||
|
uvs[6*draw_count + 3] = a_uv;
|
||||||
|
uvs[6*draw_count + 4] = c_uv;
|
||||||
|
uvs[6*draw_count + 5] = d_uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
position.x += info->advance; // Remember to consider kerning
|
||||||
|
draw_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render quads
|
||||||
|
r_2d_immediate_mesh(6*draw_count, vertices, color, uvs, glyphs->texture);
|
||||||
|
|
||||||
|
p_free(vertices);
|
||||||
|
p_free(uvs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void gui_glyph_cache_init()
|
||||||
|
{
|
||||||
|
Glyph_Cache.capacity = 4;
|
||||||
|
Glyph_Cache.oldest = 0;
|
||||||
|
Glyph_Cache.glyphs = (gui_glyph_texture*)p_alloc(sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
|
||||||
|
memset(Glyph_Cache.glyphs, 0, sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
|
||||||
|
Glyph_Cache.max_glyphs_per_texture = 256; // @Correctness: test with small values, to trigger the glyph replacement code
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gui_glyph_cache_deinit()
|
||||||
|
{
|
||||||
|
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
|
||||||
|
gui_glyph_texture_destroy(&Glyph_Cache.glyphs[i]);
|
||||||
|
p_free(Glyph_Cache.glyphs);
|
||||||
|
Glyph_Cache.glyphs = NULL;
|
||||||
|
Glyph_Cache.capacity = 0;
|
||||||
|
Glyph_Cache.oldest = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity)
|
||||||
|
{
|
||||||
|
// Init container for glyphs and info
|
||||||
|
gui_glyph_texture glyphs;
|
||||||
|
glyphs.font_size = font_size;
|
||||||
|
glyphs.sorted_indices = (gui_glyph_codepoint_map*) p_alloc(sizeof(gui_glyph_codepoint_map) * capacity);
|
||||||
|
glyphs.info = (gui_glyph_info*) p_alloc(sizeof(gui_glyph_info) * capacity);
|
||||||
|
glyphs.oldest = 0;
|
||||||
|
glyphs.newest = capacity - 1;
|
||||||
|
glyphs.capacity = capacity;
|
||||||
|
|
||||||
|
// Estimate max glyph size.
|
||||||
|
// @Correctness: Text draw will fail if a bigger glyph is used.
|
||||||
|
f32 font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
||||||
|
utf8_codepoint cp[] = {' ', 'M', 'j', '{', '=', 'w', 'W'};
|
||||||
|
glyphs.glyph_max_size = v2s{0, 0};
|
||||||
|
for(s32 i = 0; i < sizeof(cp)/sizeof(utf8_codepoint); i++)
|
||||||
|
{
|
||||||
|
v2s top_left, bottom_right;
|
||||||
|
stbtt_GetCodepointBitmapBox(&Font_Info, cp[i], font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
|
||||||
|
v2s size = bottom_right - top_left;
|
||||||
|
|
||||||
|
glyphs.glyph_max_size.x = maximum(glyphs.glyph_max_size.x, size.x);
|
||||||
|
glyphs.glyph_max_size.y = maximum(glyphs.glyph_max_size.y, size.y);
|
||||||
|
}
|
||||||
|
LOG(LOG_DEBUG, "Font size %f not in cache. Slot size (%d %d)", font_size, glyphs.glyph_max_size.x, glyphs.glyph_max_size.y);
|
||||||
|
|
||||||
|
// Precompile some info data
|
||||||
|
for(u32 i = 0; i < glyphs.capacity; i++)
|
||||||
|
{
|
||||||
|
glyphs.info[i] = gui_glyph_info{
|
||||||
|
.codepoint = 0xFFFFFFFF,
|
||||||
|
.box = {0,0,0,0},
|
||||||
|
.position = v2u{glyphs.glyph_max_size.x * i, 0},
|
||||||
|
.advance = 0,
|
||||||
|
.next = i + 1, // Last .next will be >= capacity (== capacity to be precise), so we will consider it to be NULL
|
||||||
|
.previous = ((i == 0) ? glyphs.capacity : (i - 1))
|
||||||
|
};
|
||||||
|
|
||||||
|
glyphs.sorted_indices[i] = gui_glyph_codepoint_map{0xFFFFFFFF, i};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize texture
|
||||||
|
v2s texture_size = v2s{glyphs.glyph_max_size.x * glyphs.capacity, glyphs.glyph_max_size.y};
|
||||||
|
u8 *texture_data = (u8*)p_alloc(sizeof(u8) * texture_size.x * texture_size.y);
|
||||||
|
memset(texture_data, 0, sizeof(u8) * texture_size.x * texture_size.y);
|
||||||
|
|
||||||
|
glyphs.texture = assets_new_textures(&engine.am, 1);
|
||||||
|
*glyphs.texture = r_texture_create(texture_data, texture_size, R_TEXTURE_ALPHA | R_TEXTURE_NO_MIPMAP);
|
||||||
|
|
||||||
|
return glyphs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs)
|
||||||
|
{
|
||||||
|
if(glyphs->sorted_indices)
|
||||||
|
p_free(glyphs->sorted_indices);
|
||||||
|
if(glyphs->info)
|
||||||
|
p_free(glyphs->info);
|
||||||
|
if(glyphs->texture)
|
||||||
|
r_texture_destroy(glyphs->texture);
|
||||||
|
glyphs->sorted_indices = NULL;
|
||||||
|
glyphs->info = NULL;
|
||||||
|
glyphs->texture = NULL;
|
||||||
|
|
||||||
|
glyphs->font_size = 0;
|
||||||
|
glyphs->oldest = 0;
|
||||||
|
glyphs->newest = 0;
|
||||||
|
glyphs->capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count)
|
||||||
|
{
|
||||||
|
// Approximate font_size. We don't really want to build different bitmaps for size 12.000000 and 12.000001.
|
||||||
|
// This will also prevent floating point rounding errors from rebuilding the cache.
|
||||||
|
font_size = floor(font_size * 10) / 10;
|
||||||
|
|
||||||
|
// Find cached texture for this size or build a new one
|
||||||
|
gui_glyph_texture *glyphs = NULL;
|
||||||
|
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
|
||||||
|
{
|
||||||
|
//LOG(LOG_DEBUG, "Font size: %f - Cached: %f", font_size, Glyph_Cache.glyphs[i].font_size);
|
||||||
|
if(abs(Glyph_Cache.glyphs[i].font_size - font_size) < 0.01)
|
||||||
|
{
|
||||||
|
glyphs = &Glyph_Cache.glyphs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(glyphs == NULL)
|
||||||
|
{
|
||||||
|
//LOG(LOG_DEBUG, "Size not matched");
|
||||||
|
glyphs = &Glyph_Cache.glyphs[Glyph_Cache.oldest];
|
||||||
|
Glyph_Cache.oldest = (Glyph_Cache.oldest + 1) % Glyph_Cache.capacity;
|
||||||
|
|
||||||
|
gui_glyph_texture_destroy(glyphs);
|
||||||
|
*glyphs = gui_glyph_texture_create(font_size, Glyph_Cache.max_glyphs_per_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Build list of unique codepoints (so that we don't render the same codepoint twice)
|
||||||
|
utf8_codepoint *unique = (utf8_codepoint*) p_alloc(count * sizeof(utf8_codepoint));
|
||||||
|
memcpy(unique, codepoints, count * sizeof(utf8_codepoint));
|
||||||
|
u64 unique_count = make_unique(unique, count, sizeof(utf8_codepoint), u32_cmp);
|
||||||
|
if(unique_count > glyphs->capacity)
|
||||||
|
LOG(LOG_ERROR, "Unique codepoints count > cache capacity. Some codepoints will not be rendered.");
|
||||||
|
|
||||||
|
// Find which codepoints are not already in the cache and need to be rendered
|
||||||
|
utf8_codepoint to_render[unique_count];
|
||||||
|
u32 to_render_count = 0;
|
||||||
|
for(u32 i = 0; i < unique_count; i++)
|
||||||
|
{
|
||||||
|
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&unique[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
||||||
|
if(found == NULL)
|
||||||
|
{
|
||||||
|
// Not found -> add to the list of glyphs to render
|
||||||
|
to_render[to_render_count] = unique[i];
|
||||||
|
to_render_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Found -> mark it as recent, so that is does not get deleted prematurely
|
||||||
|
u32 index = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
|
||||||
|
if(index == glyphs->newest)
|
||||||
|
{
|
||||||
|
// Already the newest. Do nothing.
|
||||||
|
}
|
||||||
|
else if(index == glyphs->oldest)
|
||||||
|
{
|
||||||
|
// We have no previous to fix, only next
|
||||||
|
u32 next = glyphs->info[index].next;
|
||||||
|
glyphs->info[next].previous = glyphs->capacity; // Next is the new oldest -> no previous
|
||||||
|
glyphs->oldest = next;
|
||||||
|
|
||||||
|
// Set index as last element
|
||||||
|
glyphs->info[index].next = glyphs->capacity;
|
||||||
|
glyphs->info[index].previous = glyphs->newest;
|
||||||
|
glyphs->info[glyphs->newest].next = index;
|
||||||
|
glyphs->newest = index;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We in between the list. We have both previous and next elements to fix.
|
||||||
|
u32 previous = glyphs->info[index].previous;
|
||||||
|
u32 next = glyphs->info[index].next;
|
||||||
|
glyphs->info[previous].next = next;
|
||||||
|
glyphs->info[next].previous = previous;
|
||||||
|
|
||||||
|
// Set index as last element
|
||||||
|
glyphs->info[index].next = glyphs->capacity;
|
||||||
|
glyphs->info[index].previous = glyphs->newest;
|
||||||
|
glyphs->info[glyphs->newest].next = index;
|
||||||
|
glyphs->newest = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p_free(unique);
|
||||||
|
|
||||||
|
|
||||||
|
// Get info for rendering
|
||||||
|
f32 font_scale;
|
||||||
|
s32 font_ascent;
|
||||||
|
s32 font_descent;
|
||||||
|
s32 font_line_gap;
|
||||||
|
s32 font_baseline;
|
||||||
|
{
|
||||||
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
||||||
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
||||||
|
font_baseline = font_ascent;
|
||||||
|
|
||||||
|
font_ascent *= font_scale;
|
||||||
|
font_descent *= font_scale;
|
||||||
|
font_line_gap *= font_scale;
|
||||||
|
font_baseline *= font_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render glyph in its appropriate place
|
||||||
|
for(u32 i = 0; i < to_render_count; i++)
|
||||||
|
{
|
||||||
|
u32 index = glyphs->oldest;
|
||||||
|
glyphs->oldest = glyphs->info[index].next;
|
||||||
|
glyphs->info[glyphs->oldest].previous = glyphs->capacity;
|
||||||
|
|
||||||
|
glyphs->info[index].next = glyphs->capacity;
|
||||||
|
glyphs->info[index].previous = glyphs->newest;
|
||||||
|
glyphs->info[index].codepoint = to_render[i];
|
||||||
|
|
||||||
|
glyphs->info[glyphs->newest].next = index;
|
||||||
|
glyphs->newest = index;
|
||||||
|
|
||||||
|
|
||||||
|
// Complete gui_glyph_info structure and render
|
||||||
|
gui_glyph_info *info = &glyphs->info[index];
|
||||||
|
|
||||||
|
v2s top_left, bottom_right;
|
||||||
|
stbtt_GetCodepointBitmapBox(&Font_Info, info->codepoint, font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
|
||||||
|
s32 advance, lsb;
|
||||||
|
stbtt_GetCodepointHMetrics(&Font_Info, info->codepoint, &advance, &lsb);
|
||||||
|
|
||||||
|
v2s size = bottom_right - top_left;
|
||||||
|
|
||||||
|
// Special codepoints
|
||||||
|
if(info->codepoint == 10) // '\n'
|
||||||
|
{
|
||||||
|
size = {0, 0};
|
||||||
|
advance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->box.position = V2(top_left);
|
||||||
|
info->box.size = V2(size);
|
||||||
|
//info->position = v2u{glyphs->glyph_max_size.x * index, 0}; // Commented because it's already pre-computed.
|
||||||
|
info->advance = advance * font_scale;
|
||||||
|
|
||||||
|
// @Correctness: needs to be premultiplied alpha
|
||||||
|
stbtt_MakeCodepointBitmap(&Font_Info, glyphs->texture->data + info->position.x, info->box.size.x, info->box.size.y, glyphs->texture->size.x, font_scale, font_scale, info->codepoint);
|
||||||
|
r_texture_update(glyphs->texture, glyphs->texture->data + info->position.x, V2S(info->box.size), V2S(info->position), glyphs->texture->size.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build sorted array with indices that point to the element
|
||||||
|
u32 nonempty_count = glyphs->capacity;
|
||||||
|
for(u32 i = 0; i < glyphs->capacity; i++)
|
||||||
|
{
|
||||||
|
glyphs->sorted_indices[i] = gui_glyph_codepoint_map{glyphs->info[i].codepoint, i};
|
||||||
|
if(glyphs->info[i].codepoint == 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
// When the cache is mostly empty, this makes the sorting way faster.
|
||||||
|
nonempty_count = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qsort(glyphs->sorted_indices, nonempty_count, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
||||||
|
|
||||||
|
return glyphs;
|
||||||
|
}
|
||||||
18
code/gui/text_draw.h
Normal file
18
code/gui/text_draw.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef _PIUMA_GUI_TEXT_DRAW_H_
|
||||||
|
#define _PIUMA_GUI_TEXT_DRAW_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "../lib/text.h"
|
||||||
|
|
||||||
|
bool gui_text_draw_init();
|
||||||
|
void gui_text_draw_deinit();
|
||||||
|
|
||||||
|
v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position = NULL);
|
||||||
|
void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color);
|
||||||
|
v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position = NULL);
|
||||||
|
void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
12
code/lib/all.h
Normal file
12
code/lib/all.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _PIUMA_LIB_ALL_H_
|
||||||
|
#define _PIUMA_LIB_ALL_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "ds.h"
|
||||||
|
#include "queue.h"
|
||||||
|
#include "math.h"
|
||||||
|
#include "geometry.h"
|
||||||
|
#include "color.h"
|
||||||
|
#include "text.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
26
code/lib/bits.h
Normal file
26
code/lib/bits.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef _PIUMA_LIB_BITS_H_
|
||||||
|
#define _PIUMA_LIB_BITS_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
inline u32 bits_endian_swap(u32 x)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(x << 24 ) |
|
||||||
|
(x << 8 & 0x00FF0000) |
|
||||||
|
(x >> 8 & 0x0000FF00) |
|
||||||
|
(x >> 24 );
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u32 bits_reverse(u32 x)
|
||||||
|
{
|
||||||
|
u32 result = 0;
|
||||||
|
for(int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
result = result << 1 | (x & 1);
|
||||||
|
x = x >> 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
25
code/lib/color.h
Normal file
25
code/lib/color.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef _PIUMA_LIB_COLOR_H_
|
||||||
|
#define _PIUMA_LIB_COLOR_H_
|
||||||
|
|
||||||
|
#include "math.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
inline u32 convert_color_to_ABGR_u32(v4 color)
|
||||||
|
{
|
||||||
|
u32 r = 0xFF * color.r;
|
||||||
|
u32 g = 0xFF * color.g;
|
||||||
|
u32 b = 0xFF * color.b;
|
||||||
|
u32 a = 0xFF * color.a;
|
||||||
|
return (a << 24) | (b << 16) | (g << 8) | (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v4 convert_ABGR_u32_to_color(u32 color)
|
||||||
|
{
|
||||||
|
f32 r = (f32)((color >> 0) & 0xFF) / (f32)(0xFF);
|
||||||
|
f32 g = (f32)((color >> 8) & 0xFF) / (f32)(0xFF);
|
||||||
|
f32 b = (f32)((color >> 16) & 0xFF) / (f32)(0xFF);
|
||||||
|
f32 a = (f32)((color >> 24) & 0xFF) / (f32)(0xFF);
|
||||||
|
return v4{r, g, b, a};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
87
code/lib/ds.cpp
Normal file
87
code/lib/ds.cpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#include "ds.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int char_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
char _a = *(char*)a;
|
||||||
|
char _b = *(char*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int u8_cmp (const void *a, const void *b)
|
||||||
|
{
|
||||||
|
u8 _a = *(u8*)a;
|
||||||
|
u8 _b = *(u8*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int u32_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
u32 _a = *(u32*)a;
|
||||||
|
u32 _b = *(u32*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int u64_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
u64 _a = *(u64*)a;
|
||||||
|
u64 _b = *(u64*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int s32_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
s32 _a = *(s32*)a;
|
||||||
|
s32 _b = *(s32*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int s64_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
s64 _a = *(s64*)a;
|
||||||
|
s64 _b = *(s64*)b;
|
||||||
|
if(_a < _b) return -1;
|
||||||
|
if(_a > _b) return +1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
u64 make_unique(void *array, u64 count, u64 element_size, compare_fn *cmp)
|
||||||
|
{
|
||||||
|
qsort(array, count, element_size, cmp);
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
u8 *start = (u8*)array;
|
||||||
|
u8 *end = start + element_size * count;
|
||||||
|
|
||||||
|
u64 deleted = 0;
|
||||||
|
|
||||||
|
u8 *prev = start;
|
||||||
|
u8 *curr = start + element_size;
|
||||||
|
while(curr < end)
|
||||||
|
{
|
||||||
|
if(cmp(prev, curr) == 0)
|
||||||
|
{
|
||||||
|
deleted++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev += element_size;
|
||||||
|
memcpy(prev, curr, element_size);
|
||||||
|
}
|
||||||
|
curr += element_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count - deleted;
|
||||||
|
}
|
||||||
29
code/lib/ds.h
Normal file
29
code/lib/ds.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef _PIUMA_LIB_DS_H_
|
||||||
|
#define _PIUMA_LIB_DS_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
typedef int compare_fn(const void *, const void *);
|
||||||
|
|
||||||
|
// Basic compare functions
|
||||||
|
int char_cmp(const void *a, const void *b);
|
||||||
|
int u8_cmp (const void *a, const void *b);
|
||||||
|
int u32_cmp(const void *a, const void *b);
|
||||||
|
int u64_cmp(const void *a, const void *b);
|
||||||
|
int s32_cmp(const void *a, const void *b);
|
||||||
|
int s64_cmp(const void *a, const void *b);
|
||||||
|
|
||||||
|
// Modifies "array" and returns the number of unique things
|
||||||
|
u64 make_unique(void *array, u64 count, u64 element_size, compare_fn *cmp);
|
||||||
|
|
||||||
|
// @Cleanup: put this in the right place
|
||||||
|
template<typename T>
|
||||||
|
void swap(T &a, T &b)
|
||||||
|
{
|
||||||
|
T tmp = a;
|
||||||
|
a = b;
|
||||||
|
b = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
220
code/lib/event.h
Normal file
220
code/lib/event.h
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#ifndef _PIUMA_LIB_EVENT_H_
|
||||||
|
#define _PIUMA_LIB_EVENT_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "math.h"
|
||||||
|
|
||||||
|
// Mouse events
|
||||||
|
// Keyboard events
|
||||||
|
// Window events (resize, hide, focus)
|
||||||
|
// Quit
|
||||||
|
// Other OS events (signals?)
|
||||||
|
|
||||||
|
|
||||||
|
enum Event_Type
|
||||||
|
{
|
||||||
|
EVENT_NONE,
|
||||||
|
|
||||||
|
EVENT_QUIT,
|
||||||
|
|
||||||
|
EVENT_MOUSE_MOVE,
|
||||||
|
EVENT_KEY,
|
||||||
|
|
||||||
|
EVENT_RESIZE,
|
||||||
|
EVENT_FOCUS,
|
||||||
|
EVENT_UNFOCUS,
|
||||||
|
|
||||||
|
EVENT_TEXT,
|
||||||
|
|
||||||
|
EVENT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// @Performance: a lot of this codes are sequential in Linux. Check with other OSs if that's the case. If it is, we can have faster mapping by subtracting and adding an offset.
|
||||||
|
enum Key_Code
|
||||||
|
{
|
||||||
|
KEY_UNKNOWN = 0,
|
||||||
|
|
||||||
|
// @Correctness: check all ascii characters that are on a keyboard
|
||||||
|
KEY_ENTER = '\r',
|
||||||
|
KEY_ESCAPE = '\e',
|
||||||
|
KEY_BACKSPACE = '\b',
|
||||||
|
KEY_TAB = '\t',
|
||||||
|
KEY_SPACE = ' ',
|
||||||
|
KEY_EXCLAMATION = '!',
|
||||||
|
KEY_DOUBLE_QUOTE = '"',
|
||||||
|
KEY_HASH = '#',
|
||||||
|
KEY_PERCENT = '%',
|
||||||
|
KEY_DOLLAR = '$',
|
||||||
|
KEY_AMPERSAND = '&',
|
||||||
|
KEY_SINGLE_QUOTE = '\'',
|
||||||
|
KEY_LEFT_PARENTHESIS = '(',
|
||||||
|
KEY_RIGHT_PARENTHESIS = ')',
|
||||||
|
KEY_ASTERISK = '*',
|
||||||
|
KEY_PLUS = '+',
|
||||||
|
KEY_COMMA = ',',
|
||||||
|
KEY_MINUS = '-',
|
||||||
|
KEY_PERIOD = '.',
|
||||||
|
KEY_SLASH = '/',
|
||||||
|
KEY_0 = '0',
|
||||||
|
KEY_1 = '1',
|
||||||
|
KEY_2 = '2',
|
||||||
|
KEY_3 = '3',
|
||||||
|
KEY_4 = '4',
|
||||||
|
KEY_5 = '5',
|
||||||
|
KEY_6 = '6',
|
||||||
|
KEY_7 = '7',
|
||||||
|
KEY_8 = '8',
|
||||||
|
KEY_9 = '9',
|
||||||
|
KEY_COLON = ':',
|
||||||
|
KEY_SEMICOLON = ';',
|
||||||
|
KEY_LESS = '<',
|
||||||
|
KEY_EQUALS = '=',
|
||||||
|
KEY_GREATER = '>',
|
||||||
|
KEY_QUESTION = '?',
|
||||||
|
KEY_AT = '@',
|
||||||
|
KEY_LEFT_BRACKET = '[',
|
||||||
|
KEY_RIGHT_BRACKET = ']',
|
||||||
|
KEY_BACKSLASH = '\\',
|
||||||
|
KEY_CARET = '^',
|
||||||
|
KEY_UNDERSCORE = '_',
|
||||||
|
KEY_BACKQUOTE = '`',
|
||||||
|
KEY_A = 'A',
|
||||||
|
KEY_B = 'B',
|
||||||
|
KEY_C = 'C',
|
||||||
|
KEY_D = 'D',
|
||||||
|
KEY_E = 'E',
|
||||||
|
KEY_F = 'F',
|
||||||
|
KEY_G = 'G',
|
||||||
|
KEY_H = 'H',
|
||||||
|
KEY_I = 'I',
|
||||||
|
KEY_J = 'J',
|
||||||
|
KEY_K = 'K',
|
||||||
|
KEY_L = 'L',
|
||||||
|
KEY_M = 'M',
|
||||||
|
KEY_N = 'N',
|
||||||
|
KEY_O = 'O',
|
||||||
|
KEY_P = 'P',
|
||||||
|
KEY_Q = 'Q',
|
||||||
|
KEY_R = 'R',
|
||||||
|
KEY_S = 'S',
|
||||||
|
KEY_T = 'T',
|
||||||
|
KEY_U = 'U',
|
||||||
|
KEY_V = 'V',
|
||||||
|
KEY_W = 'W',
|
||||||
|
KEY_X = 'X',
|
||||||
|
KEY_Y = 'Y',
|
||||||
|
KEY_Z = 'Z',
|
||||||
|
|
||||||
|
KEY_CAPSLOCK = 1000,
|
||||||
|
KEY_F1,
|
||||||
|
KEY_F2,
|
||||||
|
KEY_F3,
|
||||||
|
KEY_F4,
|
||||||
|
KEY_F5,
|
||||||
|
KEY_F6,
|
||||||
|
KEY_F7,
|
||||||
|
KEY_F8,
|
||||||
|
KEY_F9,
|
||||||
|
KEY_F10,
|
||||||
|
KEY_F11,
|
||||||
|
KEY_F12,
|
||||||
|
|
||||||
|
KEY_PRINTSCREEN,
|
||||||
|
KEY_SCROLLLOCK,
|
||||||
|
KEY_PAUSE,
|
||||||
|
KEY_INSERT,
|
||||||
|
KEY_DELETE,
|
||||||
|
|
||||||
|
KEY_HOME,
|
||||||
|
KEY_END,
|
||||||
|
KEY_PAGEUP,
|
||||||
|
KEY_PAGEDOWN,
|
||||||
|
|
||||||
|
KEY_ARROW_UP,
|
||||||
|
KEY_ARROW_DOWN,
|
||||||
|
KEY_ARROW_LEFT,
|
||||||
|
KEY_ARROW_RIGHT,
|
||||||
|
|
||||||
|
KEY_NUMLOCK,
|
||||||
|
KEY_PAD_DIVIDE,
|
||||||
|
KEY_PAD_MULTIPLY,
|
||||||
|
KEY_PAD_MINUS,
|
||||||
|
KEY_PAD_PLUS,
|
||||||
|
KEY_PAD_ENTER,
|
||||||
|
KEY_PAD_1,
|
||||||
|
KEY_PAD_2,
|
||||||
|
KEY_PAD_3,
|
||||||
|
KEY_PAD_4,
|
||||||
|
KEY_PAD_5,
|
||||||
|
KEY_PAD_6,
|
||||||
|
KEY_PAD_7,
|
||||||
|
KEY_PAD_8,
|
||||||
|
KEY_PAD_9,
|
||||||
|
KEY_PAD_0,
|
||||||
|
KEY_PAD_PERIOD,
|
||||||
|
|
||||||
|
KEY_LEFT_CTRL,
|
||||||
|
KEY_RIGHT_CTRL,
|
||||||
|
KEY_LEFT_SHIFT,
|
||||||
|
KEY_RIGHT_SHIFT,
|
||||||
|
KEY_LEFT_ALT,
|
||||||
|
KEY_RIGHT_ALT,
|
||||||
|
|
||||||
|
KEY_MOUSE_LEFT,
|
||||||
|
KEY_MOUSE_MIDDLE,
|
||||||
|
KEY_MOUSE_RIGHT,
|
||||||
|
KEY_MOUSE_WHEEL_UP,
|
||||||
|
KEY_MOUSE_WHEEL_DOWN,
|
||||||
|
KEY_MOUSE_4,
|
||||||
|
KEY_MOUSE_5,
|
||||||
|
|
||||||
|
// @Correctness: check for more keys in: USB spec, linux libinput/x11/wayland/whatever, windows win32
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Event_Key
|
||||||
|
{
|
||||||
|
bool pressed;
|
||||||
|
Key_Code key_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Event_Mouse_Move
|
||||||
|
{
|
||||||
|
bool relative;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
v2 position;
|
||||||
|
v2 delta;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Event_Resize
|
||||||
|
{
|
||||||
|
s32 width, height;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Event_Text
|
||||||
|
{
|
||||||
|
char data[16]; // @Correctness: We have a bug if the os sends text that is longer
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Event
|
||||||
|
{
|
||||||
|
Event_Type type;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
Event_Key key;
|
||||||
|
Event_Mouse_Move mouse_move;
|
||||||
|
Event_Resize resize;
|
||||||
|
Event_Text text;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
319
code/lib/geometry.h
Normal file
319
code/lib/geometry.h
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
#ifndef _PIUMA_LIB_GEOMETRY_H_
|
||||||
|
#define _PIUMA_LIB_GEOMETRY_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "math.h"
|
||||||
|
|
||||||
|
// Rect
|
||||||
|
union Rect
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
f32 x, y;
|
||||||
|
f32 w, h;
|
||||||
|
};
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
v2 position; // Usually, the top-left corner of the rectangle
|
||||||
|
v2 size;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_inside(Rect rect, v2 p)
|
||||||
|
{
|
||||||
|
bool in_range_x = rect.x <= p.x && p.x <= (rect.x + rect.w);
|
||||||
|
bool in_range_y = rect.y <= p.y && p.y <= (rect.y + rect.h);
|
||||||
|
return in_range_x && in_range_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Feature: add Cube/Parallelepiped? Unlike Box, it has rotations
|
||||||
|
// Box
|
||||||
|
struct Box
|
||||||
|
{
|
||||||
|
v3 min;
|
||||||
|
v3 max;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_inside(Box b, v3 p)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(p.x < b.min.x || p.x > b.max.x) ||
|
||||||
|
(p.y < b.min.y || p.y > b.max.y) ||
|
||||||
|
(p.z < b.min.z || p.z > b.max.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool overlaps(Box a, Box b)
|
||||||
|
{
|
||||||
|
if(a.min.x > b.max.x) // no overlap
|
||||||
|
return false;
|
||||||
|
if(a.max.x < b.min.x) // no overlap
|
||||||
|
return false;
|
||||||
|
if(a.min.y > b.max.y) // no overlap
|
||||||
|
return false;
|
||||||
|
if(a.max.y < b.min.y) // no overlap
|
||||||
|
return false;
|
||||||
|
if(a.min.z > b.max.z) // no overlap
|
||||||
|
return false;
|
||||||
|
if(a.max.z < b.min.z) // no overlap
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Box box_from_point_cloud(v3 *points, u32 count)
|
||||||
|
{
|
||||||
|
if(count == 0)
|
||||||
|
return Box{.min = {0,0,0}, .max = {0,0,0}};
|
||||||
|
|
||||||
|
Box box;
|
||||||
|
box.min = points[0];
|
||||||
|
box.max = points[0];
|
||||||
|
for(u32 i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
v3 p = points[i];
|
||||||
|
box.min.x = minimum(box.min.x, p.x);
|
||||||
|
box.min.y = minimum(box.min.y, p.y);
|
||||||
|
box.min.z = minimum(box.min.z, p.z);
|
||||||
|
box.max.x = maximum(box.max.x, p.x);
|
||||||
|
box.max.y = maximum(box.max.y, p.y);
|
||||||
|
box.max.z = maximum(box.max.z, p.z);
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v3 box_closest_point(Box b, v3 point);
|
||||||
|
inline f32 box_SDF(Box b, v3 point, v3* res_closest = NULL)
|
||||||
|
{
|
||||||
|
v3 closest = box_closest_point(b, point);
|
||||||
|
f32 sign = -1 * is_inside(b, point);
|
||||||
|
|
||||||
|
if(res_closest)
|
||||||
|
*res_closest = closest;
|
||||||
|
return sign * distance(closest, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Ray
|
||||||
|
struct Ray
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
v3 direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Circle
|
||||||
|
// @Cleanup: Should Circle be merged with Sphere?
|
||||||
|
struct Circle
|
||||||
|
{
|
||||||
|
v2 center;
|
||||||
|
f32 radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_inside(Circle c, v2 p)
|
||||||
|
{
|
||||||
|
v2 v = p - c.center;
|
||||||
|
return dot(v, v) <= square(c.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sphere
|
||||||
|
struct Sphere
|
||||||
|
{
|
||||||
|
v3 center;
|
||||||
|
f32 radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool is_inside(Sphere s, v3 p)
|
||||||
|
{
|
||||||
|
v3 v = p - s.center;
|
||||||
|
return dot(v, v) <= square(s.radius); // distance² <= radius²
|
||||||
|
}
|
||||||
|
|
||||||
|
inline f32 sphere_SDF(Sphere s, v3 point)
|
||||||
|
{
|
||||||
|
return distance(s.center, point) - s.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Segment
|
||||||
|
struct Segment
|
||||||
|
{
|
||||||
|
v3 a;
|
||||||
|
v3 b;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Plane
|
||||||
|
struct Plane
|
||||||
|
{
|
||||||
|
v3 normal;
|
||||||
|
f32 offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline f32 plane_SDF(Plane plane, v3 point)
|
||||||
|
{
|
||||||
|
f32 projected = dot(point, plane.normal);
|
||||||
|
return plane.offset - projected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Projections
|
||||||
|
inline f32 project_point_on_vector(v3 vector, v3 point)
|
||||||
|
{
|
||||||
|
return dot(vector, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v3 project_point_on_plane(Plane plane, v3 point)
|
||||||
|
{
|
||||||
|
f32 distance = plane_SDF(plane, point);
|
||||||
|
return point - plane.normal * distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Closest point
|
||||||
|
inline v3 segment_closest_point(Segment seg, v3 point)
|
||||||
|
{
|
||||||
|
v3 ab = seg.b - seg.a;
|
||||||
|
v3 ap = point - seg.a;
|
||||||
|
f32 u = dot(ab, ap);
|
||||||
|
return seg.a + ab * clamp(0.0, 1.0, u);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v3 box_closest_point(Box b, v3 point)
|
||||||
|
{
|
||||||
|
v3 closest;
|
||||||
|
// Closest point on the box is the one with coords closer to the ones of the point,
|
||||||
|
// so for each axis we can clamp to the nearest point of the border.
|
||||||
|
|
||||||
|
f32 dx1 = abs(b.min.x - point.x);
|
||||||
|
f32 dx2 = abs(b.max.x - point.x);
|
||||||
|
closest.x = (dx1 < dx2) ? b.min.x : b.max.x;
|
||||||
|
f32 dy1 = abs(b.min.y - point.y);
|
||||||
|
f32 dy2 = abs(b.max.y - point.y);
|
||||||
|
closest.y = (dy1 < dy2) ? b.min.y : b.max.y;
|
||||||
|
f32 dz1 = abs(b.min.z - point.z);
|
||||||
|
f32 dz2 = abs(b.max.z - point.z);
|
||||||
|
closest.z = (dz1 < dz2) ? b.min.z : b.max.z;
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Triangles functions
|
||||||
|
inline v3 triangle_normal(v3 a, v3 b, v3 c)
|
||||||
|
{
|
||||||
|
v3 ba = b - a;
|
||||||
|
v3 ca = c - a;
|
||||||
|
v3 orthogonal = cross(ba, ca);
|
||||||
|
return normalize(orthogonal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Transformations (all angles in radians)
|
||||||
|
inline m4 rotation_x(f32 angle)
|
||||||
|
{
|
||||||
|
f32 c = cos(angle);
|
||||||
|
f32 s = sin(angle);
|
||||||
|
|
||||||
|
m4 result = m4_identity;
|
||||||
|
result.E[1][1] = c;
|
||||||
|
result.E[1][2] = -s;
|
||||||
|
result.E[2][1] = s;
|
||||||
|
result.E[2][2] = c;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 rotation_y(f32 angle)
|
||||||
|
{
|
||||||
|
f32 c = cos(angle);
|
||||||
|
f32 s = sin(angle);
|
||||||
|
|
||||||
|
m4 result = m4_identity;
|
||||||
|
result.E[0][0] = c;
|
||||||
|
result.E[0][2] = s;
|
||||||
|
result.E[2][0] = -s;
|
||||||
|
result.E[2][2] = c;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 rotation_z(f32 angle)
|
||||||
|
{
|
||||||
|
f32 c = cos(angle);
|
||||||
|
f32 s = sin(angle);
|
||||||
|
|
||||||
|
m4 result = m4_identity;
|
||||||
|
result.E[0][0] = c;
|
||||||
|
result.E[0][1] = -s;
|
||||||
|
result.E[1][0] = s;
|
||||||
|
result.E[1][1] = c;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 rotation(f32 x, f32 y, f32 z)
|
||||||
|
{
|
||||||
|
return rotation_z(z) * rotation_y(y) * rotation_x(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 rotation_v3(v3 angle)
|
||||||
|
{
|
||||||
|
return rotation(angle.x, angle.y, angle.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline m4 translation(f32 x, f32 y, f32 z)
|
||||||
|
{
|
||||||
|
m4 result = m4_identity;
|
||||||
|
|
||||||
|
result.E[0][3] = x;
|
||||||
|
result.E[1][3] = y;
|
||||||
|
result.E[2][3] = z;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 translation_v3(v3 t)
|
||||||
|
{
|
||||||
|
return translation(t.x, t.y, t.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 scale(f32 x, f32 y, f32 z)
|
||||||
|
{
|
||||||
|
m4 result = m4_zero;
|
||||||
|
|
||||||
|
result.E[0][0] = x;
|
||||||
|
result.E[1][1] = y;
|
||||||
|
result.E[2][2] = z;
|
||||||
|
result.E[3][3] = 1.0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 scale_v3(v3 factor)
|
||||||
|
{
|
||||||
|
return scale(factor.x, factor.y, factor.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline m4 scale(f32 factor)
|
||||||
|
{
|
||||||
|
return scale(factor, factor, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Primitives
|
||||||
|
// Pass array of 8 elements to fill with coordinates
|
||||||
|
inline void build_cube_vertices(v3 *vertices)
|
||||||
|
{
|
||||||
|
for(int x = 0; x < 2; x++)
|
||||||
|
for(int y = 0; y < 2; y++)
|
||||||
|
for(int z = 0; z < 2; z++)
|
||||||
|
vertices[x*2*2 + y*2 + z] = v3{.x = x - 0.5f, .y = y - 0.5f, .z = z - 0.5f};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
97
code/lib/hashing.h
Normal file
97
code/lib/hashing.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#ifndef _PIUMA_LIB_HASHING_H_
|
||||||
|
#define _PIUMA_LIB_HASHING_H_
|
||||||
|
|
||||||
|
//#include "bits.h"
|
||||||
|
|
||||||
|
static const u32 hash_crc32_table[256] = {
|
||||||
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||||
|
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||||
|
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||||
|
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||||
|
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||||
|
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||||
|
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||||
|
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||||
|
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||||
|
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||||
|
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||||
|
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||||
|
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||||
|
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||||
|
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||||
|
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||||
|
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||||
|
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||||
|
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||||
|
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||||
|
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||||
|
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||||
|
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||||
|
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||||
|
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||||
|
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||||
|
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||||
|
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||||
|
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||||
|
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||||
|
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||||
|
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Code to compute the lookup table for CRC32
|
||||||
|
// table needs 256 spaces
|
||||||
|
static const u32 hash_crc32_polynomial = 0x04C11DB7;
|
||||||
|
inline void hash_crc32_make_table(u32 *table)
|
||||||
|
{
|
||||||
|
u32 polynomial = bits_reverse(hash_crc32_polynomial);
|
||||||
|
for(u32 byte = 0; byte <= 0xFF; byte++)
|
||||||
|
{
|
||||||
|
u32 crc = byte;
|
||||||
|
for(u8 bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
if(crc & 1)
|
||||||
|
crc = (crc >> 1) ^ polynomial;
|
||||||
|
else
|
||||||
|
crc = (crc >> 1);
|
||||||
|
}
|
||||||
|
table[byte] = crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
inline u32 hash_crc32(const void *data, u64 size, u32 seed = 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
u32 crc = seed;
|
||||||
|
u8 *d = (u8*)data;
|
||||||
|
while(size--)
|
||||||
|
{
|
||||||
|
crc = hash_crc32_table[(crc & 0xFF) ^ *d] ^ (crc >> 8);
|
||||||
|
d++;
|
||||||
|
}
|
||||||
|
return ~crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Simpler code to compute CRC32, one bit at a time.
|
||||||
|
// The version with the lookup table computes 8 bit at a time.
|
||||||
|
inline u32 hash_crc32(const void *data, u64 size, u32 seed = 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
u8 *d = (u8*)data;
|
||||||
|
u32 crc = seed;
|
||||||
|
while(size--)
|
||||||
|
{
|
||||||
|
crc = crc ^ (*d++ << 24);
|
||||||
|
for(int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if(crc & (1L<<31))
|
||||||
|
crc = (crc << 1) ^ hash_crc32_polynomial;
|
||||||
|
else
|
||||||
|
crc = (crc << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bits_endian_swap(~crc);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif
|
||||||
1432
code/lib/math.h
Normal file
1432
code/lib/math.h
Normal file
File diff suppressed because it is too large
Load Diff
10
code/lib/memory.h
Normal file
10
code/lib/memory.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef _PIUMA_LIB_MEMORY_H_
|
||||||
|
#define _PIUMA_LIB_MEMORY_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef void *(*alloc_t)(size_t size);
|
||||||
|
typedef void *(*realloc_t)(void *ptr, size_t size);
|
||||||
|
typedef void (*free_t)(void *ptr);
|
||||||
|
|
||||||
|
#endif
|
||||||
93
code/lib/queue.h
Normal file
93
code/lib/queue.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef _PIUMA_LIB_QUEUE_H_
|
||||||
|
#define _PIUMA_LIB_QUEUE_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
struct queue_header
|
||||||
|
{
|
||||||
|
u64 start;
|
||||||
|
u64 size;
|
||||||
|
|
||||||
|
u64 capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define QUEUE_HEADER_PTR(queue) ((queue_header *)(((u8*)queue) - sizeof(queue_header)))
|
||||||
|
|
||||||
|
#define QUEUE_TYPE(type) type *
|
||||||
|
#define Queue_Alloc(alloc_func, type, capacity) ((type*) _queue_alloc(alloc_func, sizeof(type), capacity))
|
||||||
|
#define Queue_Free(free_func, queue) free_func(QUEUE_HEADER_PTR(queue))
|
||||||
|
|
||||||
|
#define Queue_Pop(queue) (queue[_queue_pop_index((u8*)queue)])
|
||||||
|
#define Queue_Push(queue, element) { queue[_queue_at_index((u8*)queue, QUEUE_HEADER_PTR(queue)->size)] = element; _queue_push_fix_indices((u8*)queue); }
|
||||||
|
#define Queue_At(queue, index) (queue[_queue_at_index((u8*)queue, index)])
|
||||||
|
#define Queue_Size(queue) _queue_size((u8*)queue)
|
||||||
|
#define Queue_Capacity(queue) _queue_capacity((u8*)queue)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef void * (*alloc_func_t)(u64);
|
||||||
|
typedef void (*free_func_t)(void *);
|
||||||
|
|
||||||
|
inline u8 * _queue_alloc(alloc_func_t alloc_func, u64 sizeof_type, u64 capacity)
|
||||||
|
{
|
||||||
|
u8 *data;
|
||||||
|
queue_header *header;
|
||||||
|
|
||||||
|
data = (u8 *)alloc_func(sizeof(queue_header) + sizeof_type * capacity);
|
||||||
|
header = (queue_header *)data;
|
||||||
|
|
||||||
|
header->capacity = capacity;
|
||||||
|
header->start = 0;
|
||||||
|
header->size = 0;
|
||||||
|
|
||||||
|
return data + sizeof(queue_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 _queue_pop_index(u8 *queue)
|
||||||
|
{
|
||||||
|
queue_header *header = QUEUE_HEADER_PTR(queue);
|
||||||
|
assert(header->size > 0);
|
||||||
|
|
||||||
|
u64 element_index = header->start;
|
||||||
|
header->start = (header->start + 1) % header->capacity;
|
||||||
|
header->size--;
|
||||||
|
|
||||||
|
return element_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void _queue_push_fix_indices(u8 *queue)
|
||||||
|
{
|
||||||
|
queue_header *header = QUEUE_HEADER_PTR(queue);
|
||||||
|
|
||||||
|
header->size++;
|
||||||
|
if(header->size > header->capacity)
|
||||||
|
{
|
||||||
|
// Queue is full. Remove oldest element
|
||||||
|
header->start = (header->start + 1) % header->capacity;
|
||||||
|
header->size = header->capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 _queue_at_index(u8 *queue, u64 index)
|
||||||
|
{
|
||||||
|
queue_header *header = QUEUE_HEADER_PTR(queue);
|
||||||
|
|
||||||
|
return (header->start + index) % header->capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 _queue_size(u8 *queue)
|
||||||
|
{
|
||||||
|
queue_header *header = QUEUE_HEADER_PTR(queue);
|
||||||
|
return header->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 _queue_capacity(u8 *queue)
|
||||||
|
{
|
||||||
|
queue_header *header = QUEUE_HEADER_PTR(queue);
|
||||||
|
return header->capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
196
code/lib/text.cpp
Normal file
196
code/lib/text.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include "text.h"
|
||||||
|
|
||||||
|
u64 utf8_codepoint_count(const char *s)
|
||||||
|
{
|
||||||
|
u64 count = 0;
|
||||||
|
while(*s)
|
||||||
|
{
|
||||||
|
if(utf8_is_codepoint_start(s))
|
||||||
|
count++;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool utf8_is_codepoint_start(const char *s)
|
||||||
|
{
|
||||||
|
return (*s & 0b11000000) != 0b10000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 utf8_codepoint_bytes(const char *s)
|
||||||
|
{
|
||||||
|
if(!utf8_is_codepoint_start(s))
|
||||||
|
return 0; // Error: This byte belongs to a previous codepoint
|
||||||
|
|
||||||
|
if((*s & 0b10000000) == 0b00000000) //1 byte codepoint
|
||||||
|
return 1;
|
||||||
|
else if((*s & 0b11100000) == 0b11000000) //2 bytes codepoint
|
||||||
|
return 2;
|
||||||
|
else if((*s & 0b11110000) == 0b11100000) //3 bytes codepoint
|
||||||
|
return 3;
|
||||||
|
else if((*s & 0b11111000) == 0b11110000) //4 bytes codepoint
|
||||||
|
return 4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If bytes_read returns 0, we either reached the end of the string or there was a decoding error */
|
||||||
|
utf8_codepoint utf8_extract_codepoint(const char *s, u64 current_index, u32 *bytes_read)
|
||||||
|
{
|
||||||
|
s += current_index;
|
||||||
|
// UTF8:
|
||||||
|
// First byte: (0xxxxxxx = 1 byte, 110xxxxx = 2 byte, 1110xxxx = 3 byte, 11110xxx = 4 byte)
|
||||||
|
// Next bytes: 10xxxxxx
|
||||||
|
// To get a Codepoint: concatenate all the xxxx
|
||||||
|
utf8_codepoint codepoint = 0;
|
||||||
|
*bytes_read = 0;
|
||||||
|
u8 next_bytes = 0;
|
||||||
|
|
||||||
|
if(!utf8_is_codepoint_start(s))
|
||||||
|
{
|
||||||
|
// Error: This byte belongs to a previous codepoint
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((*s & 0b10000000) == 0b00000000) //1 byte codepoint
|
||||||
|
{
|
||||||
|
codepoint = *s;
|
||||||
|
next_bytes = 0;
|
||||||
|
}
|
||||||
|
else if((*s & 0b11100000) == 0b11000000) //2 bytes codepoint
|
||||||
|
{
|
||||||
|
codepoint = (*s & 0b00011111);
|
||||||
|
next_bytes = 1;
|
||||||
|
}
|
||||||
|
else if((*s & 0b11110000) == 0b11100000) //3 bytes codepoint
|
||||||
|
{
|
||||||
|
codepoint = (*s & 0b00001111);
|
||||||
|
next_bytes = 2;
|
||||||
|
}
|
||||||
|
else if((*s & 0b11111000) == 0b11110000) //4 bytes codepoint
|
||||||
|
{
|
||||||
|
codepoint = (*s & 0b00000111);
|
||||||
|
next_bytes = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u8 i = 0; i < next_bytes; i++)
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
if(*s == 0)
|
||||||
|
{
|
||||||
|
// Error: End of string reached before completing codepoint
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if((*s & 0b11000000) != 0b10000000)
|
||||||
|
{
|
||||||
|
// Error: Byte prefix does not match with the expected one. Broken codepoint
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
codepoint = codepoint << 6;
|
||||||
|
codepoint |= (*s & 0b00111111);
|
||||||
|
}
|
||||||
|
*bytes_read = next_bytes + 1;
|
||||||
|
|
||||||
|
return codepoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 utf8_bytes_to_next_valid_codepoint(const char *s, u64 current_index)
|
||||||
|
{
|
||||||
|
s += current_index;
|
||||||
|
u64 bytes = 1;
|
||||||
|
while(*(s + bytes))
|
||||||
|
{
|
||||||
|
if(utf8_is_codepoint_start(s + bytes))
|
||||||
|
break;
|
||||||
|
bytes++;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 utf8_bytes_to_prev_valid_codepoint(const char *s, u64 current_index)
|
||||||
|
{
|
||||||
|
s += current_index;
|
||||||
|
u64 bytes = 0;
|
||||||
|
while(bytes < current_index)
|
||||||
|
{
|
||||||
|
bytes++;
|
||||||
|
if(utf8_is_codepoint_start(s - bytes))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u64 utf8_from_string(const char *s, u64 *bytes_read, utf8_codepoint *result, u64 result_size)
|
||||||
|
{
|
||||||
|
u64 decoded = 0;
|
||||||
|
bytes_read = 0;
|
||||||
|
while(*s && decoded < result_size)
|
||||||
|
{
|
||||||
|
u32 read = 0;
|
||||||
|
result[decoded] = utf8_extract_codepoint(s, 0, &read);
|
||||||
|
if(read == 0)
|
||||||
|
{
|
||||||
|
bytes_read = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
s += read;
|
||||||
|
bytes_read += read;
|
||||||
|
decoded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 utf8_to_string(utf8_codepoint *codepoints, u64 count, char *result, u64 result_size)
|
||||||
|
{
|
||||||
|
result_size--; // Reserve space for zero-terminator
|
||||||
|
u64 i = 0;
|
||||||
|
u64 result_i = 0;
|
||||||
|
for(i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
utf8_codepoint cp = codepoints[i];
|
||||||
|
if((cp & 0xFFFFFF80) == 0) // 1 byte
|
||||||
|
{
|
||||||
|
if(result_i + 1 >= result_size) // Not enought space left
|
||||||
|
break;
|
||||||
|
|
||||||
|
result[result_i++] = cp & 0b01111111;
|
||||||
|
}
|
||||||
|
else if((cp & 0xFFFFF800) == 0) // 2 bytes
|
||||||
|
{
|
||||||
|
if(result_i + 2 >= result_size) // Not enought space left
|
||||||
|
break;
|
||||||
|
|
||||||
|
result[result_i++] = 0b11000000 | ((cp >> 6) & 0b00011111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
|
||||||
|
}
|
||||||
|
else if((cp & 0xFFFF0000) == 0) // 3 bytes
|
||||||
|
{
|
||||||
|
if(result_i + 3 >= result_size) // Not enought space left
|
||||||
|
break;
|
||||||
|
|
||||||
|
result[result_i++] = 0b11100000 | ((cp >> 12) & 0b00001111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp >> 6) & 0b00111111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
|
||||||
|
}
|
||||||
|
else if((cp & 0xFFE00000) == 0) // 4 bytes
|
||||||
|
{
|
||||||
|
if(result_i + 4 >= result_size) // Not enought space left
|
||||||
|
break;
|
||||||
|
|
||||||
|
result[result_i++] = 0b11110000 | ((cp >> 18) & 0b00000111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp >> 12) & 0b00111111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp >> 6) & 0b00111111);
|
||||||
|
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invalid codepoint
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result[result_i] = 0;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
22
code/lib/text.h
Normal file
22
code/lib/text.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef _PIUMA_LIB_TEXT_H_
|
||||||
|
#define _PIUMA_LIB_TEXT_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef u32 utf8_codepoint;
|
||||||
|
|
||||||
|
u64 utf8_codepoint_count(const char *s);
|
||||||
|
bool utf8_is_codepoint_start(const char *s);
|
||||||
|
u32 utf8_codepoint_bytes(const char *s);
|
||||||
|
/* If bytes_read returns 0, we either reached the end of the string or there was a decoding error */
|
||||||
|
utf8_codepoint utf8_extract_codepoint(const char *s, u64 current_index, u32 *bytes_read);
|
||||||
|
u32 utf8_bytes_to_next_valid_codepoint(const char *s, u64 current_index);
|
||||||
|
u32 utf8_bytes_to_prev_valid_codepoint(const char *s, u64 current_index);
|
||||||
|
|
||||||
|
|
||||||
|
/* Returns the number of codepoints read. If bytes_read returns 0, there was a decoding error */
|
||||||
|
u64 utf8_from_string(const char *s, u64 *bytes_read, utf8_codepoint *result, u64 result_size);
|
||||||
|
/* Returns the number of codepoints written. */
|
||||||
|
u64 utf8_to_string(utf8_codepoint *codepoints, u64 count, char *result, u64 result_size);
|
||||||
|
|
||||||
|
#endif
|
||||||
42
code/lib/types.h
Normal file
42
code/lib/types.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef _PIUMA_LIB_TYPES_H_
|
||||||
|
#define _PIUMA_LIB_TYPES_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Integers
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
// Real numbers
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
typedef u8 b8;
|
||||||
|
typedef u16 b16;
|
||||||
|
typedef u32 b32;
|
||||||
|
typedef u64 b64;
|
||||||
|
|
||||||
|
|
||||||
|
// Buffer
|
||||||
|
struct Buffer
|
||||||
|
{
|
||||||
|
u64 size;
|
||||||
|
u8 *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct String
|
||||||
|
{
|
||||||
|
u64 size;
|
||||||
|
u8 *text;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
1092
code/linux_platform.cpp
Normal file
1092
code/linux_platform.cpp
Normal file
File diff suppressed because it is too large
Load Diff
12
code/linux_platform.h
Normal file
12
code/linux_platform.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
=== PLATFORM IMPLEMENTATION ===
|
||||||
|
Here we define the structs that were left incomplete in platform.h
|
||||||
|
|
||||||
|
===============================
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct p_file
|
||||||
|
{
|
||||||
|
FILE *handle;
|
||||||
|
};
|
||||||
315
code/main.cpp
Normal file
315
code/main.cpp
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "render/render.h"
|
||||||
|
#include "enginestate.h"
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "lib/math.h"
|
||||||
|
#include "gui/gui.h"
|
||||||
|
#include "debug/logger.h"
|
||||||
|
#include "debug/log_viewer.h"
|
||||||
|
#include "gui/layout.h"
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <sys/sysinfo.h>
|
||||||
|
#include <NetworkManager.h>
|
||||||
|
|
||||||
|
|
||||||
|
bool process_input(); // Returns true when the program needs to exit
|
||||||
|
void process_gui();
|
||||||
|
void app_init();
|
||||||
|
void app_deinit();
|
||||||
|
|
||||||
|
u32 seconds_to_duration_text(char *text, f64 seconds, bool show_millis = false)
|
||||||
|
{
|
||||||
|
u32 written = 0;
|
||||||
|
u32 days = seconds / (24 * 3600);
|
||||||
|
seconds -= days * (24 * 3600);
|
||||||
|
u32 hours = seconds / 3600;
|
||||||
|
seconds -= hours * 3600;
|
||||||
|
u32 minutes = seconds / 60;
|
||||||
|
seconds -= minutes * 60;
|
||||||
|
|
||||||
|
if(days)
|
||||||
|
written += sprintf(text + written, "%s%dd", (written ? " " : ""), days);
|
||||||
|
if(days || hours)
|
||||||
|
written += sprintf(text + written, "%s%dh", (written ? " " : ""), hours);
|
||||||
|
if(days || hours || minutes)
|
||||||
|
written += sprintf(text + written, "%s%dm", (written ? " " : ""), minutes);
|
||||||
|
if(days || hours || minutes || seconds)
|
||||||
|
{
|
||||||
|
if(show_millis)
|
||||||
|
written += sprintf(text + written, "%s%.3lfs", (written ? " " : ""), seconds);
|
||||||
|
else
|
||||||
|
written += sprintf(text + written, "%s%.0lfs", (written ? " " : ""), seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void load_image(const char *filename, u8 **data, s32 *width, s32 *height, s32 *channels)
|
||||||
|
{
|
||||||
|
stbi_set_flip_vertically_on_load(false);
|
||||||
|
*data = stbi_load(filename, width, height, channels, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
bool status;
|
||||||
|
|
||||||
|
LOG_INIT();
|
||||||
|
p_init(true);
|
||||||
|
|
||||||
|
LogViewer log_viewer = LogViewer::Init(&global_logger);
|
||||||
|
|
||||||
|
p_window_open();
|
||||||
|
p_window_name((char*)"Server Monitor");
|
||||||
|
|
||||||
|
r_init();
|
||||||
|
gui_init();
|
||||||
|
|
||||||
|
log_viewer.Print_New_Messages_On_Console();
|
||||||
|
|
||||||
|
|
||||||
|
app_init();
|
||||||
|
|
||||||
|
|
||||||
|
f64 start = p_time();
|
||||||
|
f64 prev_t = 0;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
// Engine
|
||||||
|
engine.time = p_time() - start;
|
||||||
|
engine.delta_t = engine.time - prev_t;
|
||||||
|
//LOG(LOG_INFO, "Frame time: %.3lf ms FPS: %.2lf", 1000*delta_t, 1/delta_t);
|
||||||
|
r_time_update(engine.time);
|
||||||
|
|
||||||
|
// Input
|
||||||
|
bool exit = process_input();
|
||||||
|
if(exit)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// GUI
|
||||||
|
r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
|
||||||
|
r_clear({0,0,0,0});
|
||||||
|
gui_frame_begin(engine.time);
|
||||||
|
|
||||||
|
process_gui();
|
||||||
|
|
||||||
|
gui_frame_end();
|
||||||
|
log_viewer.Print_New_Messages_On_Console();
|
||||||
|
r_swap();
|
||||||
|
prev_t = engine.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
app_deinit();
|
||||||
|
gui_deinit();
|
||||||
|
p_window_close();
|
||||||
|
|
||||||
|
LOG_DEINIT();
|
||||||
|
p_deinit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool process_input()
|
||||||
|
{
|
||||||
|
// Events
|
||||||
|
Event event;
|
||||||
|
while(p_next_event(&event))
|
||||||
|
{
|
||||||
|
gui_handle_event(&event);
|
||||||
|
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
|
case EVENT_QUIT: {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
case EVENT_MOUSE_MOVE:
|
||||||
|
{
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case EVENT_KEY:
|
||||||
|
{
|
||||||
|
switch(event.key.key_code)
|
||||||
|
{
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// Other keys. Put default to suppress warnings
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case EVENT_RESIZE:
|
||||||
|
{
|
||||||
|
//LOG(LOG_DEBUG, "New size: %u %u", signal.resize.width, signal.resize.height);
|
||||||
|
s32 width = event.resize.width;
|
||||||
|
s32 height = event.resize.height;
|
||||||
|
r_size_update(width, height);
|
||||||
|
|
||||||
|
global_gui_state.default_context.width = width;
|
||||||
|
global_gui_state.default_context.height = height;
|
||||||
|
|
||||||
|
engine.gui_scaling = minimum(width, height) * 0.03;
|
||||||
|
global_gui_state.default_context.style.font_size = engine.gui_scaling;
|
||||||
|
global_gui_state.default_context.style.button_radius = engine.gui_scaling / 10;
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case EVENT_FOCUS:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
case EVENT_UNFOCUS:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NMClient *nmclient;
|
||||||
|
void app_init()
|
||||||
|
{
|
||||||
|
nmclient = nm_client_new(NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_deinit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void system_info_window()
|
||||||
|
{
|
||||||
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,10}*engine.gui_scaling, 3, 6, 0.4*engine.gui_scaling);
|
||||||
|
gui_window_start(Rect{0.1*engine.gui_scaling, 0.1*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef01);
|
||||||
|
|
||||||
|
// Hostname
|
||||||
|
struct utsname host_info;
|
||||||
|
uname(&host_info);
|
||||||
|
|
||||||
|
char hostname[128] = "Server manager";
|
||||||
|
if(host_info.nodename[0])
|
||||||
|
strcpy(hostname, host_info.nodename);
|
||||||
|
char kernel[256];
|
||||||
|
sprintf(kernel, "%s %s", host_info.sysname, host_info.release);
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
time_t time_now = time(NULL);
|
||||||
|
struct tm *time_info = localtime(&time_now);
|
||||||
|
char date_string[128];
|
||||||
|
strftime(date_string, 128, "%a %e %b %Y", time_info);
|
||||||
|
char time_string[128];
|
||||||
|
strftime(time_string, 128, "%H:%M:%S %Z", time_info);
|
||||||
|
|
||||||
|
|
||||||
|
gui_text_aligned(layout.cell(), hostname, GUI_ALIGN_LEFT);
|
||||||
|
gui_text_aligned(layout.cell(), time_string, GUI_ALIGN_CENTER);
|
||||||
|
gui_text_aligned(layout.cell(), date_string, GUI_ALIGN_RIGHT);
|
||||||
|
|
||||||
|
|
||||||
|
// Load, Memory, Uptime
|
||||||
|
struct sysinfo sys_info;
|
||||||
|
sysinfo(&sys_info);
|
||||||
|
char uptime[128] = "Uptime: "; seconds_to_duration_text(uptime + strlen("Uptime: "), sys_info.uptime);
|
||||||
|
f32 load_scale = 1.0f / (1 << SI_LOAD_SHIFT);
|
||||||
|
f32 loads[3] = {
|
||||||
|
load_scale * sys_info.loads[1],
|
||||||
|
load_scale * sys_info.loads[1],
|
||||||
|
load_scale * sys_info.loads[1]
|
||||||
|
};
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
loads[i] = round(load_scale * sys_info.loads[i] * 100) / 100;
|
||||||
|
char load[128]; sprintf(load, "Load: %.2f %.2f %.2f", loads[0], loads[1], loads[2]);
|
||||||
|
|
||||||
|
int n_processors = get_nprocs();
|
||||||
|
int n_processors_active = get_nprocs_conf();
|
||||||
|
char processors[128]; sprintf(processors, "CPUs: %d/%d", n_processors_active, n_processors);
|
||||||
|
|
||||||
|
u64 ram_total = sys_info.totalram * sys_info.mem_unit;
|
||||||
|
u64 ram_used = (sys_info.totalram - sys_info.freeram - sys_info.bufferram) * sys_info.mem_unit;
|
||||||
|
char ram[128]; sprintf(ram, "RAM: %.2f/%.2f GiB", ram_used / (1024.0*1024.0*1024.0), ram_total / (1024.0*1024.0*1024.0));
|
||||||
|
|
||||||
|
layout.row(2);
|
||||||
|
gui_text_aligned(layout.cell(), processors, GUI_ALIGN_LEFT);
|
||||||
|
gui_text_aligned(layout.cell(), load, GUI_ALIGN_CENTER);
|
||||||
|
gui_text_aligned(layout.cell(), uptime, GUI_ALIGN_RIGHT);
|
||||||
|
gui_text_aligned(layout.cell(), ram, GUI_ALIGN_LEFT);
|
||||||
|
|
||||||
|
|
||||||
|
gui_window_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void network_window()
|
||||||
|
{
|
||||||
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,12}*engine.gui_scaling, 3, 7, 0.4*engine.gui_scaling);
|
||||||
|
gui_window_start(Rect{0.1*engine.gui_scaling, 11*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef02);
|
||||||
|
|
||||||
|
const GPtrArray *devices = nm_client_get_devices(nmclient);
|
||||||
|
for(int i = 0; i < devices->len; i++)
|
||||||
|
{
|
||||||
|
NMDevice *device = (NMDevice*)devices->pdata[i];
|
||||||
|
const char *device_name = nm_device_get_iface(device);
|
||||||
|
gui_button(layout.cell(), device_name);
|
||||||
|
|
||||||
|
Gui_Context *ctx = &global_gui_state.default_context;
|
||||||
|
gui_id_stack_push(ctx, gui_id_from_pointer(ctx, device_name));
|
||||||
|
|
||||||
|
switch(nm_device_get_device_type(device))
|
||||||
|
{
|
||||||
|
case NM_DEVICE_TYPE_ETHERNET: gui_button(layout.cell(), "ETHERNET"); break;
|
||||||
|
case NM_DEVICE_TYPE_WIFI: gui_button(layout.cell(), "WIFI"); break;
|
||||||
|
case NM_DEVICE_TYPE_TUN: gui_button(layout.cell(), "TAP or TUN"); break;
|
||||||
|
case NM_DEVICE_TYPE_BRIDGE: gui_button(layout.cell(), "BRIDGE"); break;
|
||||||
|
case NM_DEVICE_TYPE_VLAN: gui_button(layout.cell(), "VLAN"); break;
|
||||||
|
case NM_DEVICE_TYPE_WIREGUARD: gui_button(layout.cell(), "WIREGUARD"); break;
|
||||||
|
default: gui_button(layout.cell(), ""); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(nm_device_get_state(device))
|
||||||
|
{
|
||||||
|
case NM_DEVICE_STATE_UNKNOWN: gui_button(layout.cell(), "UNKNOWN"); break;
|
||||||
|
case NM_DEVICE_STATE_UNMANAGED: gui_button(layout.cell(), "UNMANAGED"); break;
|
||||||
|
case NM_DEVICE_STATE_UNAVAILABLE: gui_button(layout.cell(), "UNAVAILABLE"); break;
|
||||||
|
case NM_DEVICE_STATE_DISCONNECTED: gui_button(layout.cell(), "DISCONNECTED"); break;
|
||||||
|
case NM_DEVICE_STATE_PREPARE: gui_button(layout.cell(), "PREPARE"); break;
|
||||||
|
case NM_DEVICE_STATE_CONFIG: gui_button(layout.cell(), "CONFIG"); break;
|
||||||
|
case NM_DEVICE_STATE_NEED_AUTH: gui_button(layout.cell(), "NEED_AUTH"); break;
|
||||||
|
case NM_DEVICE_STATE_IP_CONFIG: gui_button(layout.cell(), "IP_CONFIG"); break;
|
||||||
|
case NM_DEVICE_STATE_IP_CHECK: gui_button(layout.cell(), "IP_CHECK"); break;
|
||||||
|
case NM_DEVICE_STATE_SECONDARIES: gui_button(layout.cell(), "SECONDARIES"); break;
|
||||||
|
case NM_DEVICE_STATE_ACTIVATED: gui_button(layout.cell(), "ACTIVATED"); break;
|
||||||
|
case NM_DEVICE_STATE_DEACTIVATING: gui_button(layout.cell(), "DEACTIVATING"); break;
|
||||||
|
case NM_DEVICE_STATE_FAILED: gui_button(layout.cell(), "FAILED"); break;
|
||||||
|
default: gui_button(layout.cell(), ""); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
gui_id_stack_pop(ctx);
|
||||||
|
layout.row();
|
||||||
|
}
|
||||||
|
gui_window_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vm_window()
|
||||||
|
{
|
||||||
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,8}*engine.gui_scaling, 3, 7, 0.4*engine.gui_scaling);
|
||||||
|
gui_window_start(Rect{0.1*engine.gui_scaling, 24*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef03);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gui_window_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_gui()
|
||||||
|
{
|
||||||
|
g_main_context_iteration(NULL, false);
|
||||||
|
system_info_window();
|
||||||
|
network_window();
|
||||||
|
vm_window();
|
||||||
|
}
|
||||||
17
code/physics/base.h
Normal file
17
code/physics/base.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_BASE_H_
|
||||||
|
#define _PIUMA_PHYSICS_BASE_H_
|
||||||
|
|
||||||
|
#include "../lib/memory.h"
|
||||||
|
|
||||||
|
/* Reassing phy_alloc and phy_free with your own alloc/free functions if you don't
|
||||||
|
* want to use malloc and free.
|
||||||
|
*/
|
||||||
|
extern alloc_t phy_alloc;
|
||||||
|
extern realloc_t phy_realloc;
|
||||||
|
extern free_t phy_free;
|
||||||
|
|
||||||
|
#define PHY_ALLOC(type, size) (type *)phy_alloc(sizeof(type) * size)
|
||||||
|
#define PHY_REALLOC(type, ptr, size) (type *)phy_realloc(ptr, sizeof(type) * size)
|
||||||
|
#define PHY_FREE(ptr) phy_free(ptr)
|
||||||
|
|
||||||
|
#endif
|
||||||
28
code/physics/body.cpp
Normal file
28
code/physics/body.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#include "body.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
Box phy_aabb_from_body(phy_body *body)
|
||||||
|
{
|
||||||
|
Box aabb;
|
||||||
|
switch(body->shape)
|
||||||
|
{
|
||||||
|
case PHY_SHAPE_SPHERE: {
|
||||||
|
aabb.min = body->position - body->sphere.radius * v3{1,1,1};
|
||||||
|
aabb.max = body->position + body->sphere.radius * v3{1,1,1};
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_BOX: {
|
||||||
|
v3 box[8]; build_cube_vertices(box);
|
||||||
|
m3 rotoscale = M3(rotation_v3(body->rotation) * scale_v3(body->box.dimensions));
|
||||||
|
for(u32 i = 0; i < 8; i++)
|
||||||
|
box[i] = body->position + rotoscale * box[i];
|
||||||
|
|
||||||
|
aabb = box_from_point_cloud(box, 8);
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_MESH: {
|
||||||
|
assert(false && "Yet to be implemented");
|
||||||
|
} break;
|
||||||
|
default: { assert(false && "Unknown shape"); }
|
||||||
|
}
|
||||||
|
return aabb;
|
||||||
|
}
|
||||||
61
code/physics/body.h
Normal file
61
code/physics/body.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_OBJECT_H_
|
||||||
|
#define _PIUMA_PHYSICS_OBJECT_H_
|
||||||
|
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
|
||||||
|
enum phy_shape_type
|
||||||
|
{
|
||||||
|
PHY_SHAPE_SPHERE,
|
||||||
|
PHY_SHAPE_BOX,
|
||||||
|
PHY_SHAPE_MESH,
|
||||||
|
|
||||||
|
PHY_SHAPE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct phy_sphere
|
||||||
|
{
|
||||||
|
f32 radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct phy_box
|
||||||
|
{
|
||||||
|
v3 dimensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct phy_mesh
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct phy_body
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
v3 rotation;
|
||||||
|
|
||||||
|
f32 mass;
|
||||||
|
v3 center_of_mass;
|
||||||
|
f32 gravity_multiplier;
|
||||||
|
f32 friction;
|
||||||
|
f32 bounciness;
|
||||||
|
|
||||||
|
// Dynamics
|
||||||
|
v3 velocity;
|
||||||
|
v3 angular_velocity;
|
||||||
|
|
||||||
|
// Collision
|
||||||
|
phy_shape_type shape;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
phy_sphere sphere;
|
||||||
|
phy_box box;
|
||||||
|
phy_mesh mesh;
|
||||||
|
};
|
||||||
|
Box aabb;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Box phy_aabb_from_body(phy_body *body);
|
||||||
|
|
||||||
|
#endif
|
||||||
336
code/physics/collision.cpp
Normal file
336
code/physics/collision.cpp
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
#include "collision.h"
|
||||||
|
#include "../lib/ds.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
|
||||||
|
void phy_collisions_broadphase(phy_world *world, phy_body_pair *pair_list, u32 max_pairs, u32 *num_pairs)
|
||||||
|
{
|
||||||
|
// Update AABBs
|
||||||
|
for(u32 i = 0; i < world->bodies_count; i++)
|
||||||
|
{
|
||||||
|
phy_body *body = world->bodies + i;
|
||||||
|
body->aabb = phy_aabb_from_body(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u32 pairs_count = 0;
|
||||||
|
|
||||||
|
for(u32 a = 0; a < world->bodies_count; a++)
|
||||||
|
{
|
||||||
|
phy_body *body_a = world->bodies + a;
|
||||||
|
for(u32 b = a + 1; b < world->bodies_count; b++)
|
||||||
|
{
|
||||||
|
phy_body *body_b = world->bodies + b;
|
||||||
|
|
||||||
|
if(overlaps(body_a->aabb, body_b->aabb))
|
||||||
|
{
|
||||||
|
// Add to list of possible collisions.
|
||||||
|
if(pairs_count < max_pairs)
|
||||||
|
{
|
||||||
|
pair_list[pairs_count] = phy_body_pair{
|
||||||
|
.body_a = body_a,
|
||||||
|
.body_b = body_b
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pairs_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(num_pairs)
|
||||||
|
*num_pairs = pairs_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @Cleanup: put this in the right place
|
||||||
|
// @Performance: this is probably not a good way of doing this
|
||||||
|
void project_box_on_axis(phy_body *body, v3 axis, f32 *min, f32 *max)
|
||||||
|
{
|
||||||
|
v3 points[8];
|
||||||
|
build_cube_vertices(points);
|
||||||
|
|
||||||
|
m4 transform = translation_v3(body->position) * rotation_v3(body->rotation) * scale_v3(body->box.dimensions);
|
||||||
|
|
||||||
|
*min = *max = project_point_on_vector(axis, body->position);
|
||||||
|
for(int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
f32 projected = project_point_on_vector(axis, (transform * V4(points[i], 1)).xyz);
|
||||||
|
*min = minimum(*min, projected);
|
||||||
|
*max = maximum(*max, projected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 axis_range_distance(f32 a_min, f32 a_max, f32 b_min, f32 b_max)
|
||||||
|
{
|
||||||
|
f32 a_range = a_max - a_min;
|
||||||
|
f32 a_center = a_min + a_range / 2;
|
||||||
|
f32 b_range = b_max - b_min;
|
||||||
|
f32 b_center = b_min + b_range / 2;
|
||||||
|
return fabs(a_center - b_center) - (a_range + b_range) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sat_box_face_point_collision_test(phy_body *body_a, phy_body *body_b, v3 axis, f32 a_size_on_current_axis, f32 *depth, v3 *furthest_a, v3 *furthest_b)
|
||||||
|
{
|
||||||
|
f32 a_middle = project_point_on_vector(axis, body_a->position);
|
||||||
|
f32 b_middle = project_point_on_vector(axis, body_b->position);
|
||||||
|
f32 direction = (b_middle - a_middle < 0) ? -1 : 1;
|
||||||
|
|
||||||
|
f32 boundary = a_middle + direction * a_size_on_current_axis / 2;
|
||||||
|
f32 dist = +INFINITY;
|
||||||
|
v3 point;
|
||||||
|
|
||||||
|
v3 points[8]; build_cube_vertices(points);
|
||||||
|
m4 transform = translation_v3(body_b->position) * rotation_v3(body_b->rotation) * scale_v3(body_b->box.dimensions);
|
||||||
|
|
||||||
|
for(int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
v3 p = (transform * V4(points[i], 1)).xyz;
|
||||||
|
f32 projected = project_point_on_vector(axis, p);
|
||||||
|
f32 curr_dist = direction * (projected - boundary);
|
||||||
|
if(curr_dist < dist)
|
||||||
|
{
|
||||||
|
dist = curr_dist;
|
||||||
|
point = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dist < 0) // Possible collision
|
||||||
|
{
|
||||||
|
v3 furthest_point_b = point;
|
||||||
|
v3 furthest_point_a = point + -dist * -direction * axis;
|
||||||
|
|
||||||
|
*depth = -dist * direction;
|
||||||
|
*furthest_a = furthest_point_a;
|
||||||
|
*furthest_b = furthest_point_b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// else Separated on this axis. No collision
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sat_box_collision_test(phy_body *body_a, phy_body *body_b, phy_collision *res_collision)
|
||||||
|
{
|
||||||
|
// Separating axis test
|
||||||
|
// 3 + 3 = 6 face normals
|
||||||
|
// 3 * 3 = 9 right angle to pair of edges
|
||||||
|
v3 axes[15];
|
||||||
|
m4 a_rotation = rotation_v3(body_a->rotation);
|
||||||
|
axes[0] = extract_column(a_rotation, 0).xyz;
|
||||||
|
axes[1] = extract_column(a_rotation, 1).xyz;
|
||||||
|
axes[2] = extract_column(a_rotation, 2).xyz;
|
||||||
|
m4 b_rotation = rotation_v3(body_b->rotation);
|
||||||
|
axes[3] = -extract_column(b_rotation, 0).xyz;
|
||||||
|
axes[4] = -extract_column(b_rotation, 1).xyz;
|
||||||
|
axes[5] = -extract_column(b_rotation, 2).xyz;
|
||||||
|
|
||||||
|
for(int a = 0; a < 3; a++)
|
||||||
|
for(int b = 0; b < 3; b++)
|
||||||
|
{
|
||||||
|
(axes+6)[3*a + b] = cross(axes[a], axes[b + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
phy_collision collision;
|
||||||
|
collision.body_a = body_a;
|
||||||
|
collision.body_b = body_b;
|
||||||
|
collision.depth = +INFINITY;
|
||||||
|
|
||||||
|
// Face axes: body a
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
v3 axis = axes[i];
|
||||||
|
f32 depth; v3 furthest_point_a, furthest_point_b;
|
||||||
|
bool collides = sat_box_face_point_collision_test(body_a, body_b, axis, body_a->box.dimensions.E[i], &depth, &furthest_point_a, &furthest_point_b);
|
||||||
|
|
||||||
|
if(!collides)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(abs(depth) < abs(collision.depth))
|
||||||
|
{
|
||||||
|
collision.depth = depth;
|
||||||
|
collision.furthest_point_a = furthest_point_a;
|
||||||
|
collision.furthest_point_b = furthest_point_b;
|
||||||
|
collision.best_separation_direction = axis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Face axes: body b
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
v3 axis = axes[3 + i];
|
||||||
|
f32 depth; v3 furthest_point_a, furthest_point_b;
|
||||||
|
bool collides = sat_box_face_point_collision_test(body_b, body_a, axis, body_b->box.dimensions.E[i], &depth, &furthest_point_b, &furthest_point_a);
|
||||||
|
|
||||||
|
if(!collides)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(abs(depth) < abs(collision.depth))
|
||||||
|
{
|
||||||
|
collision.depth = depth;
|
||||||
|
collision.furthest_point_a = furthest_point_a;
|
||||||
|
collision.furthest_point_b = furthest_point_b;
|
||||||
|
collision.best_separation_direction = -axis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: edge-edge
|
||||||
|
|
||||||
|
*res_collision = collision;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_collisions_detection(phy_world *world, phy_body_pair *pair_list, u32 num_pairs, phy_collision *collision_list, u32 max_collisions, u32 *num_collisions)
|
||||||
|
{
|
||||||
|
u32 n_collisions = 0;
|
||||||
|
for(u32 i = 0; i < num_pairs; i++)
|
||||||
|
{
|
||||||
|
phy_body *body_a = pair_list[i].body_a;
|
||||||
|
phy_body *body_b = pair_list[i].body_b;
|
||||||
|
|
||||||
|
if(body_b->shape < body_a->shape)
|
||||||
|
swap(body_b, body_a);
|
||||||
|
|
||||||
|
|
||||||
|
switch(body_a->shape)
|
||||||
|
{
|
||||||
|
case PHY_SHAPE_SPHERE:
|
||||||
|
{
|
||||||
|
switch(body_b->shape)
|
||||||
|
{
|
||||||
|
case PHY_SHAPE_SPHERE:
|
||||||
|
{
|
||||||
|
f32 dist = distance(body_a->position, body_b->position);
|
||||||
|
f32 radius_sum = body_a->sphere.radius + body_b->sphere.radius;
|
||||||
|
if(dist <= radius_sum)
|
||||||
|
{
|
||||||
|
phy_collision collision;
|
||||||
|
|
||||||
|
collision.body_a = body_a;
|
||||||
|
collision.body_b = body_b;
|
||||||
|
collision.depth = radius_sum - dist;
|
||||||
|
v3 direction = normalize(body_b->position - body_a->position);
|
||||||
|
collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction;
|
||||||
|
collision.furthest_point_b = body_b->position + (body_b->sphere.radius - collision.depth) * -direction;
|
||||||
|
collision.best_separation_direction = direction;
|
||||||
|
|
||||||
|
if(n_collisions < max_collisions)
|
||||||
|
collision_list[n_collisions] = collision;
|
||||||
|
n_collisions++;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_BOX:
|
||||||
|
{
|
||||||
|
// Use box's cordinate space
|
||||||
|
m4 box_b_coord_space_transform = rotation_v3(-body_b->rotation) * translation_v3(-body_b->position);
|
||||||
|
|
||||||
|
v4 sphere_center4 = box_b_coord_space_transform * V4(body_a->position, 1.0);
|
||||||
|
v3 sphere_center = sphere_center4.xyz / sphere_center4.w;
|
||||||
|
|
||||||
|
Box box = {
|
||||||
|
.min = -0.5*body_b->box.dimensions,
|
||||||
|
.max = 0.5*body_b->box.dimensions
|
||||||
|
};
|
||||||
|
|
||||||
|
v3 closest_point;
|
||||||
|
f32 dist = box_SDF(box, sphere_center, &closest_point);
|
||||||
|
|
||||||
|
if(dist - body_a->sphere.radius <= 0)
|
||||||
|
{
|
||||||
|
phy_collision collision;
|
||||||
|
|
||||||
|
collision.body_a = body_a;
|
||||||
|
collision.body_b = body_b;
|
||||||
|
collision.depth = dist - body_a->sphere.radius;
|
||||||
|
v3 direction = normalize(closest_point - body_a ->position);
|
||||||
|
collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction;
|
||||||
|
collision.furthest_point_b = body_a->position + body_a->sphere.radius * direction;
|
||||||
|
collision.best_separation_direction = direction;
|
||||||
|
|
||||||
|
if(n_collisions < max_collisions)
|
||||||
|
collision_list[n_collisions] = collision;
|
||||||
|
n_collisions++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_MESH:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_BOX:
|
||||||
|
{
|
||||||
|
switch(body_b->shape)
|
||||||
|
{
|
||||||
|
// case PHY_SHAPE_SPHERE: // Already managed
|
||||||
|
case PHY_SHAPE_BOX:
|
||||||
|
{
|
||||||
|
phy_collision collision;
|
||||||
|
bool collides = sat_box_collision_test(body_a, body_b, &collision);
|
||||||
|
if(collides)
|
||||||
|
{
|
||||||
|
if(n_collisions < max_collisions)
|
||||||
|
collision_list[n_collisions] = collision;
|
||||||
|
n_collisions++;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_MESH:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PHY_SHAPE_MESH:
|
||||||
|
{
|
||||||
|
switch(body_b->shape)
|
||||||
|
{
|
||||||
|
// case PHY_SHAPE_SPHERE: // Already managed
|
||||||
|
// case PHY_SHAPE_BOX: // Already managed
|
||||||
|
case PHY_SHAPE_MESH:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE pair");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
assert(false && "Unmanaged PHY_SHAPE");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*num_collisions = n_collisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_collisions_resolution(phy_world *world, phy_collision *collision_list, u32 num_collisions)
|
||||||
|
{
|
||||||
|
|
||||||
|
for(u32 i = 0; i < num_collisions; i++)
|
||||||
|
{
|
||||||
|
phy_body *body_a = collision_list[i].body_a;
|
||||||
|
phy_body *body_b = collision_list[i].body_b;
|
||||||
|
v3 separation_direction = collision_list[i].best_separation_direction;
|
||||||
|
f32 depth = collision_list[i].depth;
|
||||||
|
|
||||||
|
if(body_a->mass != 0) // Not a static body
|
||||||
|
{
|
||||||
|
body_a->position += -separation_direction * depth;//-normalize(body_a->velocity) * dot(normalize(body_a->velocity), separation_direction * depth);
|
||||||
|
body_a->velocity = {0,0,0};//length(body_a->velocity) * separation_direction * body_a->bounciness;
|
||||||
|
}
|
||||||
|
if(body_b->mass != 0) // Not a static body
|
||||||
|
{
|
||||||
|
body_b->position += separation_direction * depth;//-normalize(body_b->velocity) * dot(normalize(body_b->velocity), separation_direction * depth);
|
||||||
|
body_b->velocity = {0,0,0};//length(body_a->velocity) * -separation_direction * body_b->bounciness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
code/physics/collision.h
Normal file
35
code/physics/collision.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_COLLISION_H_
|
||||||
|
#define _PIUMA_PHYSICS_COLLISION_H_
|
||||||
|
|
||||||
|
#include "world.h"
|
||||||
|
|
||||||
|
struct phy_body_pair
|
||||||
|
{
|
||||||
|
phy_body *body_a;
|
||||||
|
phy_body *body_b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct phy_collision
|
||||||
|
{
|
||||||
|
phy_body *body_a;
|
||||||
|
phy_body *body_b;
|
||||||
|
|
||||||
|
f32 depth;
|
||||||
|
v3 furthest_point_a;
|
||||||
|
v3 furthest_point_b;
|
||||||
|
v3 best_separation_direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Generates possibile collision pairs and saves it in the memory pointed by pair_list, up to a maximum of max_pairs. The number of possible collisions is returned in num_pairs (might be >= max_pairs).
|
||||||
|
void phy_collisions_broadphase(phy_world *world, phy_body_pair *pair_list, u32 max_pairs, u32 *num_pairs);
|
||||||
|
|
||||||
|
// Generates list of collisions and saves it in the memory pointed by collision_list, up to a maximum of max_pairs. The number of collisions is returned in num_collisions (might be >= max_collisions).
|
||||||
|
// Uses pair_list (with size num_pairs) as a list of possible collisions (computed with a faster algorithm).
|
||||||
|
void phy_collisions_detection(phy_world *world, phy_body_pair *pair_list, u32 num_pairs, phy_collision *collision_list, u32 max_collisions, u32 *num_collisions);
|
||||||
|
|
||||||
|
// Modifies world state based on collision_list
|
||||||
|
void phy_collisions_resolution(phy_world *world, phy_collision *collision_list, u32 num_collisions);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
25
code/physics/physics.cpp
Normal file
25
code/physics/physics.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#include "physics.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
alloc_t phy_alloc = malloc;
|
||||||
|
realloc_t phy_realloc = realloc;
|
||||||
|
free_t phy_free = free;
|
||||||
|
|
||||||
|
|
||||||
|
void phy_init()
|
||||||
|
{
|
||||||
|
phy_init(malloc, realloc, free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_init(alloc_t alloc, realloc_t realloc, free_t free)
|
||||||
|
{
|
||||||
|
phy_alloc = alloc;
|
||||||
|
phy_realloc = realloc;
|
||||||
|
phy_free = free;
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_deinit()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
16
code/physics/physics.h
Normal file
16
code/physics/physics.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_PHYSICS_H_
|
||||||
|
#define _PIUMA_PHYSICS_PHYSICS_H_
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
#include "body.h"
|
||||||
|
#include "world.h"
|
||||||
|
#include "simulation.h"
|
||||||
|
#include "collision.h"
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
void phy_init();
|
||||||
|
void phy_init(alloc_t alloc, realloc_t realloc, free_t free);
|
||||||
|
void phy_deinit();
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
40
code/physics/simulation.cpp
Normal file
40
code/physics/simulation.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "base.h"
|
||||||
|
#include "simulation.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "collision.h"
|
||||||
|
|
||||||
|
void phy_integrate(phy_world *world, f64 delta_t)
|
||||||
|
{
|
||||||
|
for(u32 i = 0; i < world->bodies_count; i++)
|
||||||
|
{
|
||||||
|
phy_body *body = world->bodies + i;
|
||||||
|
|
||||||
|
body->velocity += world->gravity * body->gravity_multiplier * delta_t;
|
||||||
|
|
||||||
|
body->position += body->velocity * delta_t;
|
||||||
|
body->rotation += body->angular_velocity * delta_t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void phy_simulate(phy_world *world, f64 delta_t)
|
||||||
|
{
|
||||||
|
// @Performance: Grouping for optimizations (active/inactive, constraints)
|
||||||
|
|
||||||
|
phy_integrate(world, delta_t);
|
||||||
|
|
||||||
|
u32 max_pairs = 1000; // @Correctness: this should be dynamic and should reuse memory when possible
|
||||||
|
u32 num_pairs = 0;
|
||||||
|
phy_body_pair *pair_list = PHY_ALLOC(phy_body_pair, max_pairs);
|
||||||
|
phy_collisions_broadphase(world, pair_list, max_pairs, &num_pairs);
|
||||||
|
|
||||||
|
u32 max_collisions = 1000;
|
||||||
|
u32 num_collisions = 0;
|
||||||
|
phy_collision *collision_list = PHY_ALLOC(phy_collision, max_collisions);
|
||||||
|
phy_collisions_detection(world, pair_list, minimum(max_pairs, num_pairs), collision_list, max_collisions, &num_collisions);
|
||||||
|
|
||||||
|
phy_collisions_resolution(world, collision_list, minimum(max_collisions, num_collisions));
|
||||||
|
|
||||||
|
PHY_FREE(pair_list);
|
||||||
|
PHY_FREE(collision_list);
|
||||||
|
}
|
||||||
9
code/physics/simulation.h
Normal file
9
code/physics/simulation.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_SIMULATION_H_
|
||||||
|
#define _PIUMA_PHYSICS_SIMULATION_H_
|
||||||
|
|
||||||
|
#include "world.h"
|
||||||
|
|
||||||
|
void phy_integrate(phy_world *world, f64 delta_t);
|
||||||
|
void phy_simulate(phy_world *world, f64 delta_t);
|
||||||
|
|
||||||
|
#endif
|
||||||
72
code/physics/world.cpp
Normal file
72
code/physics/world.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "world.h"
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
|
||||||
|
phy_world *phy_create_world(v3 gravity)
|
||||||
|
{
|
||||||
|
phy_world *world = PHY_ALLOC(phy_world, 1);
|
||||||
|
world->bodies = NULL;
|
||||||
|
world->bodies_count = 0;
|
||||||
|
world->gravity = gravity;
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_destroy_world(phy_world *world)
|
||||||
|
{
|
||||||
|
PHY_FREE(world->bodies);
|
||||||
|
world->bodies_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
phy_body_offset phy_new_body(phy_world *world)
|
||||||
|
{
|
||||||
|
phy_body_offset offset = world->bodies_count;
|
||||||
|
world->bodies_count++;
|
||||||
|
world->bodies = PHY_REALLOC(phy_body, world->bodies, world->bodies_count);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void phy_body_init_default(phy_body *body)
|
||||||
|
{
|
||||||
|
body->position = {0, 0, 0};
|
||||||
|
body->rotation = {0, 0, 0};
|
||||||
|
body->mass = 0;
|
||||||
|
body->center_of_mass = {0, 0, 0};
|
||||||
|
|
||||||
|
body->velocity = {0, 0, 0};
|
||||||
|
body->angular_velocity = {0, 0, 0};
|
||||||
|
|
||||||
|
body->gravity_multiplier = 1;
|
||||||
|
|
||||||
|
body->friction = 0;
|
||||||
|
body->bounciness = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
phy_body_offset phy_create_box(phy_world *world, v3 position, v3 rotation, f32 mass, v3 dimensions)
|
||||||
|
{
|
||||||
|
phy_body_offset offset = phy_new_body(world);
|
||||||
|
phy_body *body = PHY_BODY(world, offset);
|
||||||
|
|
||||||
|
phy_body_init_default(body);
|
||||||
|
body->position = position;
|
||||||
|
body->rotation = rotation;
|
||||||
|
body->mass = mass;
|
||||||
|
body->shape = PHY_SHAPE_BOX;
|
||||||
|
body->box.dimensions = dimensions;
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
phy_body_offset phy_create_sphere(phy_world *world, v3 position, v3 rotation, f32 mass, f32 radius)
|
||||||
|
{
|
||||||
|
phy_body_offset offset = phy_new_body(world);
|
||||||
|
phy_body *body = PHY_BODY(world, offset);
|
||||||
|
|
||||||
|
phy_body_init_default(body);
|
||||||
|
body->position = position;
|
||||||
|
body->rotation = rotation;
|
||||||
|
body->mass = mass;
|
||||||
|
body->shape = PHY_SHAPE_SPHERE;
|
||||||
|
body->sphere.radius = radius;
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
23
code/physics/world.h
Normal file
23
code/physics/world.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef _PIUMA_PHYSICS_WORLD_H_
|
||||||
|
#define _PIUMA_PHYSICS_WORLD_H_
|
||||||
|
|
||||||
|
#include "body.h"
|
||||||
|
|
||||||
|
struct phy_world
|
||||||
|
{
|
||||||
|
phy_body *bodies;
|
||||||
|
u32 bodies_count;
|
||||||
|
|
||||||
|
v3 gravity;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef u32 phy_body_offset;
|
||||||
|
#define PHY_BODY(world, offset) (world->bodies + offset)
|
||||||
|
|
||||||
|
phy_world *phy_create_world(v3 gravity);
|
||||||
|
void phy_destroy_world(phy_world *world);
|
||||||
|
|
||||||
|
phy_body_offset phy_create_box(phy_world *world, v3 position, v3 rotation, f32 mass, v3 dimensions);
|
||||||
|
phy_body_offset phy_create_sphere(phy_world *world, v3 position, v3 rotation, f32 mass, f32 radius);
|
||||||
|
|
||||||
|
#endif
|
||||||
92
code/platform.h
Normal file
92
code/platform.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#ifndef _PIUMA_PLATFORM_H_
|
||||||
|
#define _PIUMA_PLATFORM_H_
|
||||||
|
/*
|
||||||
|
=== PLATFORM INTERFACE ===
|
||||||
|
Every function or struct must be implemented in the code for each platform
|
||||||
|
|
||||||
|
==========================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lib/types.h"
|
||||||
|
#include "lib/event.h"
|
||||||
|
|
||||||
|
// @Feature: Initialization structures with preferred initial state (window resolution, fullcreen)
|
||||||
|
|
||||||
|
// Generic platform initialization
|
||||||
|
void p_init(bool capture_os_signals = false);
|
||||||
|
void p_deinit();
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
void * p_alloc(u64 size);
|
||||||
|
void * p_realloc(void *ptr, u64 new_size);
|
||||||
|
void p_free(void *ptr);
|
||||||
|
|
||||||
|
// File IO
|
||||||
|
#define P_FILE_CREATE_IF_NOT_EXISTS 1
|
||||||
|
|
||||||
|
struct p_file; // Defined by specific platform implementation
|
||||||
|
|
||||||
|
bool p_file_init(p_file *file, const char *filename, b32 flags = 0);
|
||||||
|
u64 p_file_size(p_file *file);
|
||||||
|
bool p_file_read(p_file *file, Buffer *buf, u64 max_size);
|
||||||
|
bool p_file_write(p_file *file, u8 *data, u64 size);
|
||||||
|
// @Performance: add append + write at position. Maybe not needed
|
||||||
|
void p_file_deinit(p_file *file);
|
||||||
|
|
||||||
|
// Timers
|
||||||
|
f64 p_time(); // Returns seconds
|
||||||
|
void p_wait(f64 milliseconds);
|
||||||
|
|
||||||
|
// Windowing
|
||||||
|
// We suppose we only need 1 window. Every GL command will render to it.
|
||||||
|
void p_window_open();
|
||||||
|
void p_window_name(char *name);
|
||||||
|
void p_window_resize(u32 width, u32 height, bool fullscreen = false);
|
||||||
|
void p_window_dimensions(u32 *width, u32 *height);
|
||||||
|
void p_window_close();
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
void p_graphics_swap_interval(s32 frames); // -1 for Adaptive Sync, 0 for no interval, >0 to wait 'frames' frames between each swap
|
||||||
|
void p_graphics_swap();
|
||||||
|
|
||||||
|
// Input (keyboard / mouse / controller)
|
||||||
|
void p_mouse_grab(bool grab);
|
||||||
|
|
||||||
|
// Events
|
||||||
|
bool p_next_event(Event *e);
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
struct p_audio_buffer;
|
||||||
|
typedef void (*p_audio_callback)(p_audio_buffer *);
|
||||||
|
|
||||||
|
void p_audio_register_data_callback(p_audio_callback cb);
|
||||||
|
u32 p_audio_sample_rate();
|
||||||
|
|
||||||
|
|
||||||
|
// Threads
|
||||||
|
// @Feature: OS threads and syncronization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Audio structs
|
||||||
|
struct p_audio_sample
|
||||||
|
{
|
||||||
|
f32 left;
|
||||||
|
f32 right;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct p_audio_buffer
|
||||||
|
{
|
||||||
|
p_audio_sample *samples;
|
||||||
|
u64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include "linux_platform.h"
|
||||||
|
#else
|
||||||
|
#error "Platform not supported"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
382
code/render/2d.cpp
Normal file
382
code/render/2d.cpp
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
#include "2d.h"
|
||||||
|
#include "state.h"
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
|
||||||
|
static const u32 PIXELS_PER_SEGMENT = 3;
|
||||||
|
|
||||||
|
// Internal use functions
|
||||||
|
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color = {1,1,1,1}, u32 texture_index = 0);
|
||||||
|
|
||||||
|
// Immediate functions
|
||||||
|
void r_2d_immediate_segment(v2 a, v2 b, v4 a_color, v4 b_color, f32 thickness)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
glUniform1i(r_render_state.shader_2d.has_texture[0], 0);
|
||||||
|
|
||||||
|
// Vertex buffer data
|
||||||
|
GLuint gl_VAO, gl_VBO;
|
||||||
|
glGenVertexArrays(1, &gl_VAO);
|
||||||
|
glBindVertexArray(gl_VAO);
|
||||||
|
glGenBuffers(1, &gl_VBO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
|
||||||
|
|
||||||
|
f32 data[12] = { a.x, a.y, b.x, b.y, a_color.r, a_color.g, a_color.b, a_color.a, b_color.r, b_color.g, b_color.b, b_color.a };
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 2*sizeof(v2)+2*sizeof(v4), data, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(2*sizeof(v2)));
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glLineWidth(thickness);
|
||||||
|
glDrawArrays(GL_LINES, 0, 2);
|
||||||
|
|
||||||
|
// Deinit
|
||||||
|
glDeleteBuffers(1, &gl_VBO);
|
||||||
|
glDeleteVertexArrays(1, &gl_VAO);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_polygonal_chain(u64 count, v2 *vertices, v4 color, f32 thickness)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
glUniform1i(r_render_state.shader_2d.has_texture[0], 0);
|
||||||
|
|
||||||
|
// Vertex buffer data
|
||||||
|
GLuint gl_VAO, gl_VBO;
|
||||||
|
glGenVertexArrays(1, &gl_VAO);
|
||||||
|
glBindVertexArray(gl_VAO);
|
||||||
|
glGenBuffers(1, &gl_VBO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
|
||||||
|
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, count*sizeof(v2), vertices, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
//glDisableVertexAttribArray(1);
|
||||||
|
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glLineWidth(thickness);
|
||||||
|
glDrawArrays(GL_LINE_STRIP, 0, count);
|
||||||
|
|
||||||
|
// Deinit
|
||||||
|
glDeleteBuffers(1, &gl_VBO);
|
||||||
|
glDeleteVertexArrays(1, &gl_VAO);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_triangle(v2 a, v2 b, v2 c, v4 a_color, v4 b_color, v4 c_color, v2 a_uv, v2 b_uv, v2 c_uv, r_texture *texture)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
// Texture
|
||||||
|
set_texture_in_shader(&r_render_state.shader_2d, texture);
|
||||||
|
|
||||||
|
// Vertex buffer data
|
||||||
|
GLuint gl_VAO, gl_VBO;
|
||||||
|
glGenVertexArrays(1, &gl_VAO);
|
||||||
|
glBindVertexArray(gl_VAO);
|
||||||
|
glGenBuffers(1, &gl_VBO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
|
||||||
|
|
||||||
|
f32 data[24] = {
|
||||||
|
a.x, a.y, b.x, b.y, c.x, c.y,
|
||||||
|
a_color.r, a_color.g, a_color.b, a_color.a,
|
||||||
|
b_color.r, b_color.g, b_color.b, b_color.a,
|
||||||
|
c_color.r, c_color.g, c_color.b, c_color.a,
|
||||||
|
a_uv.x, a_uv.y, b_uv.x, b_uv.y, c_uv.x, c_uv.y
|
||||||
|
};
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 3*sizeof(v2)+3*sizeof(v4)+3*sizeof(v2), data, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(3*sizeof(v2)));
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(3*sizeof(v2)+3*sizeof(v4)));
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
|
|
||||||
|
// Deinit
|
||||||
|
glDeleteBuffers(1, &gl_VBO);
|
||||||
|
glDeleteVertexArrays(1, &gl_VAO);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_quad(v2 a, v2 b, v2 c, v2 d, v4 color, v2 a_uv, v2 b_uv, v2 c_uv, v2 d_uv, r_texture *texture)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
// Texture
|
||||||
|
set_texture_in_shader(&r_render_state.shader_2d, texture);
|
||||||
|
|
||||||
|
// Vertex buffer data
|
||||||
|
GLuint gl_VAO, gl_VBO;
|
||||||
|
glGenVertexArrays(1, &gl_VAO);
|
||||||
|
glBindVertexArray(gl_VAO);
|
||||||
|
glGenBuffers(1, &gl_VBO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
|
||||||
|
|
||||||
|
f32 data[16] = {
|
||||||
|
a.x, a.y, b.x, b.y, d.x, d.y, c.x, c.y,
|
||||||
|
a_uv.x, a_uv.y, b_uv.x, b_uv.y, d_uv.x, d_uv.y, c_uv.x, c_uv.y
|
||||||
|
};
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 4*sizeof(v2)+4*sizeof(v2), data, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
//glDisableVertexAttribArray(1);
|
||||||
|
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(4*sizeof(v2)));
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
|
// Deinit
|
||||||
|
glDeleteBuffers(1, &gl_VBO);
|
||||||
|
glDeleteVertexArrays(1, &gl_VAO);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_rectangle(Rect r, v4 color, Rect uv, r_texture *texture)
|
||||||
|
{
|
||||||
|
v2 a = r.position + v2{r.w, 0 };
|
||||||
|
v2 b = r.position + v2{0 , 0 };
|
||||||
|
v2 c = r.position + v2{0 , r.h};
|
||||||
|
v2 d = r.position + v2{r.w, r.h};
|
||||||
|
v2 a_uv = uv.position + v2{uv.w, 0 };
|
||||||
|
v2 b_uv = uv.position + v2{0 , 0 };
|
||||||
|
v2 c_uv = uv.position + v2{0 , uv.h};
|
||||||
|
v2 d_uv = uv.position + v2{uv.w, uv.h};
|
||||||
|
r_2d_immediate_quad(a, b, c, d, color, a_uv, b_uv, c_uv, d_uv, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_rounded_rectangle(Rect r, f32 radius, v4 color)
|
||||||
|
{
|
||||||
|
radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);
|
||||||
|
|
||||||
|
// Should compute PIXELS_PER_SEGMENT from the size of the radius
|
||||||
|
u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);
|
||||||
|
|
||||||
|
// Split into 9 sections:
|
||||||
|
// - 5 quads (center, top, bottom, left, right)
|
||||||
|
// - 4 semicircles (corners)
|
||||||
|
// Inner vertices (CCW starting from 1st quadrant/upper-right)
|
||||||
|
v2 i0, i1, i2, i3;
|
||||||
|
i0.x = r.x + r.w - radius;
|
||||||
|
i0.y = r.y + radius;
|
||||||
|
i1.x = r.x + radius;
|
||||||
|
i1.y = r.y + radius;
|
||||||
|
i2.x = r.x + radius;
|
||||||
|
i2.y = r.y + r.h - radius;
|
||||||
|
i3.x = r.x + r.w - radius;
|
||||||
|
i3.y = r.y + r.h - radius;
|
||||||
|
|
||||||
|
// Outer vertices
|
||||||
|
v2 o0, o1, o2, o3, o4, o5, o6, o7;
|
||||||
|
o0.x = i0.x;
|
||||||
|
o0.y = i0.y - radius;
|
||||||
|
o1.x = i1.x;
|
||||||
|
o1.y = i1.y - radius;
|
||||||
|
o2.x = i1.x - radius;
|
||||||
|
o2.y = i1.y;
|
||||||
|
o3.x = i2.x - radius;
|
||||||
|
o3.y = i2.y;
|
||||||
|
o4.x = i2.x;
|
||||||
|
o4.y = i2.y + radius;
|
||||||
|
o5.x = i3.x;
|
||||||
|
o5.y = i3.y + radius;
|
||||||
|
o6.x = i3.x + radius;
|
||||||
|
o6.y = i3.y;
|
||||||
|
o7.x = i0.x + radius;
|
||||||
|
o7.y = i0.y;
|
||||||
|
|
||||||
|
// Reserve space for vertices
|
||||||
|
u32 vertices_count = 30; // 5 quads, specified by 2 triangles each = 5 quads * 2 triangles * 3 vertices = 30 vertices
|
||||||
|
vertices_count += num_of_segments * 12; // Add corner semicircles = 4 corners * N triangles * 3 vertices = N * 12
|
||||||
|
|
||||||
|
|
||||||
|
// Build 5 quads
|
||||||
|
v2 vertices[vertices_count] = {
|
||||||
|
i0, i1, i2, i0, i2, i3, // Center quad: i0, i1, i2, i3
|
||||||
|
o0, o1, i1, o0, i1, i0, // Top quad: o0, o1, i1, i0
|
||||||
|
i1, o2, o3, i1, o3, i2, // Left quad: i1, o2, o3, i2
|
||||||
|
i3, i2, o4, i3, o4, o5, // Bottom quad: i3, i2, o4, o5
|
||||||
|
o7, i0, i3, o7, i3, o6 // Right quad: o7, i0, i3, o6
|
||||||
|
};
|
||||||
|
u32 corner_offset = 30;
|
||||||
|
// Corner semicircles
|
||||||
|
f32 factor = TAU * .25 / num_of_segments;
|
||||||
|
v2 inner_vertices[4] = {i0, i1, i2, i3};
|
||||||
|
for(u32 quadrant = 0; quadrant < 4; quadrant++)
|
||||||
|
{
|
||||||
|
for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
|
||||||
|
{
|
||||||
|
v2 inner = inner_vertices[quadrant];
|
||||||
|
v2 a = inner + radius * v2{cos( i * factor), -sin( i * factor)};
|
||||||
|
v2 b = inner + radius * v2{cos((i+1) * factor), -sin((i+1) * factor)};
|
||||||
|
vertices[corner_offset + 3*i + 0] = inner;
|
||||||
|
vertices[corner_offset + 3*i + 1] = a;
|
||||||
|
vertices[corner_offset + 3*i + 2] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r_2d_immediate_mesh(vertices_count, vertices, color, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_mesh(u64 count, v2 *vertices, v4 color, v2 *uvs, r_texture *texture)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
// Texture
|
||||||
|
set_texture_in_shader(&r_render_state.shader_2d, texture);
|
||||||
|
|
||||||
|
// Vertex buffer data
|
||||||
|
GLuint gl_VAO, gl_vertices, gl_uvs;
|
||||||
|
glGenVertexArrays(1, &gl_VAO);
|
||||||
|
glBindVertexArray(gl_VAO);
|
||||||
|
glGenBuffers(1, &gl_vertices);
|
||||||
|
glGenBuffers(1, &gl_uvs);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_vertices);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), vertices, GL_STATIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
//glDisableVertexAttribArray(1);
|
||||||
|
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gl_uvs);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), uvs , GL_STATIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, count);
|
||||||
|
|
||||||
|
// Deinitvoid r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture);
|
||||||
|
glDeleteBuffers(1, &gl_vertices);
|
||||||
|
glDeleteBuffers(1, &gl_uvs);
|
||||||
|
glDeleteVertexArrays(1, &gl_VAO);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void r_2d_immediate_rectangle_outline(Rect r, v4 color, f32 thickness)
|
||||||
|
{
|
||||||
|
v2 vertices[5];
|
||||||
|
vertices[0] = r.position + v2{ 0, 0};
|
||||||
|
vertices[1] = r.position + v2{ 0, r.h};
|
||||||
|
vertices[2] = r.position + v2{r.w, r.h};
|
||||||
|
vertices[3] = r.position + v2{r.w, 0};
|
||||||
|
vertices[4] = r.position + v2{ 0, 0};
|
||||||
|
r_2d_immediate_polygonal_chain(5, vertices, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_immediate_rounded_rectangle_outline(Rect r, f32 radius, v4 color, f32 thickness)
|
||||||
|
{
|
||||||
|
radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);
|
||||||
|
|
||||||
|
// Should compute PIXELS_PER_SEGMENT from the size of the radius
|
||||||
|
u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);
|
||||||
|
|
||||||
|
// Split into 9 sections:
|
||||||
|
// - 5 quads (center, top, bottom, left, right)
|
||||||
|
// - 4 semicircles (corners)
|
||||||
|
// Inner vertices (CCW starting from 1st quadrant/upper-right)
|
||||||
|
v2 i0, i1, i2, i3;
|
||||||
|
i0.x = r.x + r.w - radius;
|
||||||
|
i0.y = r.y + radius;
|
||||||
|
i1.x = r.x + radius;
|
||||||
|
i1.y = r.y + radius;
|
||||||
|
i2.x = r.x + radius;
|
||||||
|
i2.y = r.y + r.h - radius;
|
||||||
|
i3.x = r.x + r.w - radius;
|
||||||
|
i3.y = r.y + r.h - radius;
|
||||||
|
|
||||||
|
// Reserve space for vertices
|
||||||
|
u32 vertices_count = 1 + 4 + 4*num_of_segments; // Starting vertex (1) + one for each side (4) + one for each segment (4*num_of_segments)
|
||||||
|
|
||||||
|
v2 inner_vertices[4] = {i0, i1, i2, i3};
|
||||||
|
|
||||||
|
v2 vertices[vertices_count] = {
|
||||||
|
i3 + v2{radius, 0} // Starting vertex
|
||||||
|
};
|
||||||
|
u32 v_index = 1;
|
||||||
|
|
||||||
|
// Corner semicircles
|
||||||
|
f32 factor = TAU * .25 / num_of_segments;
|
||||||
|
for(u32 quadrant = 0; quadrant < 4; quadrant++)
|
||||||
|
{
|
||||||
|
v2 inner = inner_vertices[quadrant];
|
||||||
|
for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
|
||||||
|
{
|
||||||
|
v2 a = inner + radius * v2{cos( i * factor), -sin( i * factor)};
|
||||||
|
vertices[v_index] = a;
|
||||||
|
v_index++;
|
||||||
|
}
|
||||||
|
vertices[v_index] = inner + radius * v2{cos( (quadrant+1)*num_of_segments * factor), -sin( (quadrant+1)*num_of_segments * factor)};
|
||||||
|
v_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_2d_immediate_polygonal_chain(vertices_count, vertices, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture)
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Shader
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
// Texture
|
||||||
|
set_texture_in_shader(&r_render_state.shader_2d, texture);
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
glBindVertexArray(mesh->gl_VAO);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, mesh->count);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Internal use functions
|
||||||
|
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color, u32 texture_index)
|
||||||
|
{
|
||||||
|
// Remember to call glUseProgram before using this
|
||||||
|
if(texture)
|
||||||
|
{
|
||||||
|
glUniform1i(shader->has_texture[texture_index], 1);
|
||||||
|
glUniform1i(shader->texture[texture_index], 0);
|
||||||
|
glUniform1i(shader->texture_channels[texture_index], r_texture_channels(texture));
|
||||||
|
glActiveTexture(GL_TEXTURE0 + texture_index);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
|
||||||
|
glUniform4f(shader->color[texture_index], color.r, color.g, color.b, color.a);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glUniform1i(shader->has_texture[texture_index], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
code/render/2d.h
Normal file
27
code/render/2d.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_2D_H_
|
||||||
|
#define _PIUMA_RENDER_2D_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "primitives.h"
|
||||||
|
|
||||||
|
// Immediate functions: less efficient but easy to use
|
||||||
|
void r_2d_immediate_segment(v2 a, v2 b, v4 a_color, v4 b_color, f32 thickness = 1.0f);
|
||||||
|
void r_2d_immediate_polygonal_chain(u64 count, v2 *vertices, v4 color, f32 thickness = 1.0f);
|
||||||
|
void r_2d_immediate_triangle(v2 a, v2 b, v2 c, v4 a_color, v4 b_color, v4 c_color, v2 a_uv = {0,0}, v2 b_uv = {0,0}, v2 c_uv = {0,0}, r_texture *texture = NULL);
|
||||||
|
void r_2d_immediate_quad(v2 a, v2 b, v2 c, v2 d, v4 color, v2 a_uv = {0,0}, v2 b_uv = {0,0}, v2 c_uv = {0,0}, v2 d_uv = {0,0}, r_texture *texture = NULL);
|
||||||
|
void r_2d_immediate_rectangle(Rect r, v4 color, Rect uv = {0,0,1,1}, r_texture *texture = NULL);
|
||||||
|
void r_2d_immediate_rounded_rectangle(Rect r, f32 radius, v4 color);
|
||||||
|
void r_2d_immediate_mesh(u64 count, v2 *vertices, v4 color, v2 *uvs = NULL, r_texture *texture = NULL);
|
||||||
|
|
||||||
|
void r_2d_immediate_rectangle_outline(Rect r, v4 color, f32 thickness = 1.0f);
|
||||||
|
void r_2d_immediate_rounded_rectangle_outline(Rect r, f32 radius, v4 color, f32 thickness = 1.0f);
|
||||||
|
|
||||||
|
// Draw functions: usual interface (create objects / send to gpu, draw call, remove from gpu)
|
||||||
|
void r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture);
|
||||||
|
// @Feature: @Performance: something for text rendering (a lot of quads/rects)
|
||||||
|
// Maybe we could send to the gpu the following: texture of the characters, array of uvs for indexing the texture, character position + index of its uv.
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
52
code/render/gl_helpers.h
Normal file
52
code/render/gl_helpers.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_GL_HELPERS_H_
|
||||||
|
#define _PIUMA_RENDER_GL_HELPERS_H_
|
||||||
|
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
|
||||||
|
inline void APIENTRY glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char *message, const void *userParam)
|
||||||
|
{
|
||||||
|
// ignore non-significant error/warning codes
|
||||||
|
if(id == 131169 || id == 131185 || id == 131218 || id == 131204)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// @Cleanup: LOG does not replace printf here. We need to merge the multiple printfs into a single log message
|
||||||
|
LOG(LOG_DEBUG, "---------------");
|
||||||
|
LOG(LOG_DEBUG, "Debug message (%u): %s", id, message);
|
||||||
|
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case GL_DEBUG_SOURCE_API: LOG(LOG_DEBUG, "Source: API"); break;
|
||||||
|
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: LOG(LOG_DEBUG, "Source: Window System"); break;
|
||||||
|
case GL_DEBUG_SOURCE_SHADER_COMPILER: LOG(LOG_DEBUG, "Source: Shader Compiler"); break;
|
||||||
|
case GL_DEBUG_SOURCE_THIRD_PARTY: LOG(LOG_DEBUG, "Source: Third Party"); break;
|
||||||
|
case GL_DEBUG_SOURCE_APPLICATION: LOG(LOG_DEBUG, "Source: Application"); break;
|
||||||
|
case GL_DEBUG_SOURCE_OTHER: LOG(LOG_DEBUG, "Source: Other"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case GL_DEBUG_TYPE_ERROR: LOG(LOG_DEBUG, "Type: Error"); break;
|
||||||
|
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: LOG(LOG_DEBUG, "Type: Deprecated Behaviour"); break;
|
||||||
|
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: LOG(LOG_DEBUG, "Type: Undefined Behaviour"); break;
|
||||||
|
case GL_DEBUG_TYPE_PORTABILITY: LOG(LOG_DEBUG, "Type: Portability"); break;
|
||||||
|
case GL_DEBUG_TYPE_PERFORMANCE: LOG(LOG_DEBUG, "Type: Performance"); break;
|
||||||
|
case GL_DEBUG_TYPE_MARKER: LOG(LOG_DEBUG, "Type: Marker"); break;
|
||||||
|
case GL_DEBUG_TYPE_PUSH_GROUP: LOG(LOG_DEBUG, "Type: Push Group"); break;
|
||||||
|
case GL_DEBUG_TYPE_POP_GROUP: LOG(LOG_DEBUG, "Type: Pop Group"); break;
|
||||||
|
case GL_DEBUG_TYPE_OTHER: LOG(LOG_DEBUG, "Type: Other"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (severity)
|
||||||
|
{
|
||||||
|
case GL_DEBUG_SEVERITY_HIGH: LOG(LOG_DEBUG, "Severity: high"); break;
|
||||||
|
case GL_DEBUG_SEVERITY_MEDIUM: LOG(LOG_DEBUG, "Severity: medium"); break;
|
||||||
|
case GL_DEBUG_SEVERITY_LOW: LOG(LOG_DEBUG, "Severity: low"); break;
|
||||||
|
case GL_DEBUG_SEVERITY_NOTIFICATION: LOG(LOG_DEBUG, "Severity: notification"); break;
|
||||||
|
}
|
||||||
|
LOG(LOG_DEBUG, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
0
code/render/lights.cpp
Normal file
0
code/render/lights.cpp
Normal file
44
code/render/lights.h
Normal file
44
code/render/lights.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_LIGHTS_H_
|
||||||
|
#define _PIUMA_RENDER_LIGHTS_H_
|
||||||
|
|
||||||
|
struct r_sun_light
|
||||||
|
{
|
||||||
|
v3 direction;
|
||||||
|
f32 _padding0;
|
||||||
|
v3 color;
|
||||||
|
f32 intensity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_point_light
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
f32 _padding0;
|
||||||
|
v3 color;
|
||||||
|
f32 intensity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_spot_light
|
||||||
|
{
|
||||||
|
v3 position;
|
||||||
|
f32 inner_radius;
|
||||||
|
v3 color;
|
||||||
|
f32 intensity;
|
||||||
|
v3 direction;
|
||||||
|
f32 outer_radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_SUN_LIGHTS 4
|
||||||
|
#define MAX_POINT_LIGHTS 128
|
||||||
|
#define MAX_SPOT_LIGHTS 128
|
||||||
|
struct r_light_container
|
||||||
|
{
|
||||||
|
u32 sun_light_count;
|
||||||
|
u32 point_light_count;
|
||||||
|
u32 spot_light_count;
|
||||||
|
f32 ambient_light;
|
||||||
|
r_sun_light sun_lights[MAX_SUN_LIGHTS];
|
||||||
|
r_point_light point_lights[MAX_POINT_LIGHTS];
|
||||||
|
r_spot_light spot_lights[MAX_SPOT_LIGHTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
79
code/render/object.cpp
Normal file
79
code/render/object.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "object.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../lib/geometry.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include "string.h"
|
||||||
|
#include "state.h"
|
||||||
|
#include "2d.h"
|
||||||
|
|
||||||
|
|
||||||
|
r_object r_object_create(u64 count, r_mesh **meshes, r_material **materials, v3 position, v3 rotation, v3 scale)
|
||||||
|
{
|
||||||
|
r_object object;
|
||||||
|
|
||||||
|
object.meshes = (r_mesh**)p_alloc(count * sizeof(r_mesh*));
|
||||||
|
object.mesh_material = (r_material**)p_alloc(count * sizeof(r_material*));
|
||||||
|
object.mesh_local_transform = (m4*)p_alloc(count * sizeof(m4));
|
||||||
|
|
||||||
|
memcpy(object.meshes, meshes, count * sizeof(r_mesh*));
|
||||||
|
memcpy(object.mesh_material, materials, count * sizeof(r_material*));
|
||||||
|
for(u64 i = 0; i < count; i++)
|
||||||
|
object.mesh_local_transform[i] = m4_identity;
|
||||||
|
|
||||||
|
object.count = count;
|
||||||
|
|
||||||
|
object.position = position;
|
||||||
|
object.rotation = rotation;
|
||||||
|
object.scale = scale;
|
||||||
|
|
||||||
|
object.has_shadow = true;
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_object r_object_allocate(u64 count)
|
||||||
|
{
|
||||||
|
r_object object;
|
||||||
|
|
||||||
|
object.meshes = (r_mesh**)p_alloc(count * sizeof(r_mesh*));
|
||||||
|
object.mesh_material = (r_material**)p_alloc(count * sizeof(r_material*));
|
||||||
|
object.mesh_local_transform = (m4*)p_alloc(count * sizeof(m4));
|
||||||
|
|
||||||
|
for(u64 i = 0; i < count; i++)
|
||||||
|
object.mesh_local_transform[i] = m4_identity;
|
||||||
|
|
||||||
|
object.count = count;
|
||||||
|
|
||||||
|
object.position = v3{0,0,0};
|
||||||
|
object.rotation = v3{0,0,0};
|
||||||
|
object.scale = v3{1,1,1};
|
||||||
|
|
||||||
|
object.has_shadow = true;
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_object_destroy(r_object *object)
|
||||||
|
{
|
||||||
|
p_free(object->mesh_material);
|
||||||
|
p_free(object->meshes);
|
||||||
|
p_free(object->mesh_local_transform);
|
||||||
|
object->mesh_material = NULL;
|
||||||
|
object->meshes = NULL;
|
||||||
|
object->mesh_local_transform = NULL;
|
||||||
|
object->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_object_transform_matrix(r_object *object)
|
||||||
|
{
|
||||||
|
// @Performance: replace with rototranslation matrix
|
||||||
|
return translation_v3(object->position) * rotation_v3(object->rotation) * scale_v3(object->scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
m4 r_object_mesh_transform_matrix(r_object *object, u64 mesh_i)
|
||||||
|
{
|
||||||
|
// @Performance: replace with rototranslation matrix
|
||||||
|
if(object->mesh_local_transform)
|
||||||
|
return r_object_transform_matrix(object) * object->mesh_local_transform[mesh_i];
|
||||||
|
return r_object_transform_matrix(object);
|
||||||
|
}
|
||||||
64
code/render/object.h
Normal file
64
code/render/object.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_OBJECT_H_
|
||||||
|
#define _PIUMA_RENDER_OBJECT_H_
|
||||||
|
|
||||||
|
#include "primitives.h"
|
||||||
|
#include "shader.h"
|
||||||
|
|
||||||
|
struct r_material
|
||||||
|
{
|
||||||
|
// @Feature: PBR materials
|
||||||
|
r_shader *shader;
|
||||||
|
|
||||||
|
r_texture *albedo_texture;
|
||||||
|
v4 albedo_factor;
|
||||||
|
|
||||||
|
r_texture *metallic_texture;
|
||||||
|
f32 metallic_factor;
|
||||||
|
|
||||||
|
r_texture *roughness_texture;
|
||||||
|
f32 roughness_factor;
|
||||||
|
|
||||||
|
r_texture *normal_texture;
|
||||||
|
|
||||||
|
r_texture *emissive_texture;
|
||||||
|
v4 emissive_factor;
|
||||||
|
/*
|
||||||
|
albedo |
|
||||||
|
metallic | --> Disney + UE4 paper
|
||||||
|
roughness / specular |
|
||||||
|
cavity |
|
||||||
|
subsurface |
|
||||||
|
anisotropy | --> Disney paper
|
||||||
|
clearcoat |
|
||||||
|
sheen |
|
||||||
|
ambient occlusion | --> Valve Source2, same as cavity?
|
||||||
|
emission
|
||||||
|
normals
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct r_object
|
||||||
|
{
|
||||||
|
// @Feature: actually support multiple meshes/materials in the implementation code
|
||||||
|
r_mesh **meshes;
|
||||||
|
r_material **mesh_material;
|
||||||
|
m4 *mesh_local_transform;
|
||||||
|
u64 count;
|
||||||
|
|
||||||
|
v3 scale;
|
||||||
|
v3 position;
|
||||||
|
v3 rotation;
|
||||||
|
|
||||||
|
bool has_shadow;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_object r_object_create(u64 count, r_mesh **meshes, r_material **materials, v3 position = {0,0,0}, v3 rotation = {0,0,0}, v3 scale = {1,1,1});
|
||||||
|
r_object r_object_allocate(u64 count);
|
||||||
|
void r_object_destroy(r_object *object);
|
||||||
|
m4 r_object_transform_matrix(r_object *object);
|
||||||
|
m4 r_object_mesh_transform_matrix(r_object *object, u64 mesh_i);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
424
code/render/primitives.cpp
Normal file
424
code/render/primitives.cpp
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
#include "primitives.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// GL utils
|
||||||
|
static GLint gl_texture_internal_format(u32 texture_flags);
|
||||||
|
static GLenum gl_texture_format (u32 texture_flags);
|
||||||
|
static GLenum gl_texture_type (u32 texture_flags);
|
||||||
|
|
||||||
|
// Texture
|
||||||
|
r_texture r_texture_create(u8 *data, v2s size, u32 flags)
|
||||||
|
{
|
||||||
|
r_texture texture;
|
||||||
|
|
||||||
|
texture.data = data;
|
||||||
|
texture.size = size;
|
||||||
|
texture.flags = flags | R_TEXTURE_INITIALIZED;
|
||||||
|
|
||||||
|
glGenTextures(1, &texture.gl_id);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture.gl_id);
|
||||||
|
|
||||||
|
GLint internal_format = gl_texture_internal_format(flags);
|
||||||
|
GLenum format = gl_texture_format (flags);
|
||||||
|
GLenum type = gl_texture_type (flags);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, data);
|
||||||
|
|
||||||
|
if(flags & R_TEXTURE_NO_MIPMAP)
|
||||||
|
{
|
||||||
|
// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(flags & R_TEXTURE_DONT_OWN)
|
||||||
|
texture.data = NULL;
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_texture_resize(r_texture *texture, v2s size)
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
|
||||||
|
GLint internal_format = gl_texture_internal_format(texture->flags);
|
||||||
|
GLenum format = gl_texture_format (texture->flags);
|
||||||
|
GLenum type = gl_texture_type (texture->flags);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, NULL);
|
||||||
|
texture->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_texture_update(r_texture *texture, u8 *data, v2s size, v2s position, u32 stride)
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
|
||||||
|
GLenum format = gl_texture_format(texture->flags);
|
||||||
|
GLenum type = gl_texture_type (texture->flags);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, size.x, size.y, format, type, data);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
|
||||||
|
if(texture->flags & R_TEXTURE_NO_MIPMAP)
|
||||||
|
{
|
||||||
|
// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_texture_destroy(r_texture *texture)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &texture->gl_id);
|
||||||
|
|
||||||
|
if(texture->data && !(texture->flags & R_TEXTURE_DONT_OWN))
|
||||||
|
p_free(texture->data);
|
||||||
|
texture->size = {0,0};
|
||||||
|
texture->flags = R_TEXTURE_DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 r_texture_channels(r_texture *texture)
|
||||||
|
{
|
||||||
|
if(texture->flags & R_TEXTURE_ALPHA)
|
||||||
|
return 1;
|
||||||
|
if(texture->flags & R_TEXTURE_RGB)
|
||||||
|
return 3;
|
||||||
|
if(texture->flags & (R_TEXTURE_RGBA | R_TEXTURE_SRGB))
|
||||||
|
return 4;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Cubemap
|
||||||
|
r_cubemap r_cubemap_create(float *data[6], v2s size, u32 flags)
|
||||||
|
{
|
||||||
|
r_cubemap cubemap;
|
||||||
|
|
||||||
|
for(u32 i = 0; i < 6; i++)
|
||||||
|
cubemap.data[i] = data[i];
|
||||||
|
cubemap.size = size;
|
||||||
|
cubemap.flags = flags | R_CUBEMAP_INITIALIZED;
|
||||||
|
|
||||||
|
glGenTextures(1, &cubemap.gl_id);
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.gl_id);
|
||||||
|
for(u32 i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size.x, size.y, 0, GL_RGB, GL_FLOAT, data[i]);
|
||||||
|
|
||||||
|
if(flags & R_CUBEMAP_DONT_OWN)
|
||||||
|
cubemap.data[i] = NULL;
|
||||||
|
}
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
|
||||||
|
|
||||||
|
return cubemap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_cubemap_destroy(r_cubemap *cubemap)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &cubemap->gl_id);
|
||||||
|
|
||||||
|
for(u32 i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
if(cubemap->data[i] && !(cubemap->flags & R_CUBEMAP_DONT_OWN))
|
||||||
|
p_free(cubemap->data[i]);
|
||||||
|
}
|
||||||
|
cubemap->size = {0,0};
|
||||||
|
cubemap->flags = R_CUBEMAP_DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 2D mesh
|
||||||
|
r_2d_mesh r_2d_mesh_create(u64 count, v2 *vertices, v4 *colors, v2 *uvs)
|
||||||
|
{
|
||||||
|
r_2d_mesh mesh;
|
||||||
|
|
||||||
|
mesh.vertices = vertices;
|
||||||
|
mesh.colors = colors;
|
||||||
|
mesh.uvs = uvs;
|
||||||
|
mesh.count = count;
|
||||||
|
|
||||||
|
glGenVertexArrays(1, &mesh.gl_VAO);
|
||||||
|
glBindVertexArray(mesh.gl_VAO);
|
||||||
|
|
||||||
|
glGenBuffers(1, &mesh.gl_vertex_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_color_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_uv_buffer);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.vertices, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_color_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v4), mesh.colors, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)0);
|
||||||
|
|
||||||
|
if(mesh.uvs)
|
||||||
|
{
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_2d_mesh_destroy(r_2d_mesh *mesh)
|
||||||
|
{
|
||||||
|
glBindVertexArray(mesh->gl_VAO);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_vertex_buffer);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_color_buffer);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_uv_buffer);
|
||||||
|
glDeleteVertexArrays(1, &mesh->gl_VAO);
|
||||||
|
|
||||||
|
if(mesh->vertices)
|
||||||
|
p_free(mesh->vertices);
|
||||||
|
if(mesh->colors)
|
||||||
|
p_free(mesh->colors);
|
||||||
|
if(mesh->uvs)
|
||||||
|
p_free(mesh->uvs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 3D mesh
|
||||||
|
r_mesh r_mesh_create(u64 indices_count, u32 *indices, u64 vertices_count, v3 *vertices, v3 *normals, v3 *tangents, v2 *uvs)
|
||||||
|
{
|
||||||
|
r_mesh mesh;
|
||||||
|
|
||||||
|
mesh.vertices = vertices;
|
||||||
|
mesh.normals = normals;
|
||||||
|
mesh.tangents = tangents;
|
||||||
|
mesh.uvs = uvs;
|
||||||
|
mesh.vertices_count = vertices_count;
|
||||||
|
mesh.indices = indices;
|
||||||
|
mesh.indices_count = indices_count;
|
||||||
|
|
||||||
|
glGenVertexArrays(1, &mesh.gl_VAO);
|
||||||
|
glBindVertexArray(mesh.gl_VAO);
|
||||||
|
|
||||||
|
glGenBuffers(1, &mesh.gl_vertex_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_normal_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_tangent_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_uv_buffer);
|
||||||
|
glGenBuffers(1, &mesh.gl_index_buffer);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl_index_buffer);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices_count * sizeof(u32), mesh.indices, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.vertices, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
|
||||||
|
|
||||||
|
if(mesh.normals)
|
||||||
|
{
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_normal_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.normals, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mesh.tangents)
|
||||||
|
{
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_tangent_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.tangents, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mesh.uvs)
|
||||||
|
{
|
||||||
|
glEnableVertexAttribArray(3);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
|
||||||
|
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_mesh_destroy(r_mesh *mesh)
|
||||||
|
{
|
||||||
|
glBindVertexArray(mesh->gl_VAO);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_vertex_buffer);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_normal_buffer);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_uv_buffer);
|
||||||
|
glDeleteBuffers(1, &mesh->gl_index_buffer);
|
||||||
|
glDeleteVertexArrays(1, &mesh->gl_VAO);
|
||||||
|
|
||||||
|
if(mesh->vertices)
|
||||||
|
p_free(mesh->vertices);
|
||||||
|
if(mesh->normals)
|
||||||
|
p_free(mesh->normals);
|
||||||
|
if(mesh->uvs)
|
||||||
|
p_free(mesh->uvs);
|
||||||
|
if(mesh->indices)
|
||||||
|
p_free(mesh->indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Common meshes
|
||||||
|
r_mesh r_mesh_build_cube()
|
||||||
|
{
|
||||||
|
v3 v[24] = {
|
||||||
|
{0.5, 0.5, -0.5},
|
||||||
|
{0.5, 0.5, -0.5},
|
||||||
|
{0.5, 0.5, -0.5},
|
||||||
|
{0.5, -0.5, -0.5},
|
||||||
|
{0.5, -0.5, -0.5},
|
||||||
|
{0.5, -0.5, -0.5},
|
||||||
|
{0.5, 0.5, 0.5},
|
||||||
|
{0.5, 0.5, 0.5},
|
||||||
|
{0.5, 0.5, 0.5},
|
||||||
|
{0.5, -0.5, 0.5},
|
||||||
|
{0.5, -0.5, 0.5},
|
||||||
|
{0.5, -0.5, 0.5},
|
||||||
|
{-0.5, 0.5, -0.5},
|
||||||
|
{-0.5, 0.5, -0.5},
|
||||||
|
{-0.5, 0.5, -0.5},
|
||||||
|
{-0.5, -0.5, -0.5},
|
||||||
|
{-0.5, -0.5, -0.5},
|
||||||
|
{-0.5, -0.5, -0.5},
|
||||||
|
{-0.5, 0.5, 0.5},
|
||||||
|
{-0.5, 0.5, 0.5},
|
||||||
|
{-0.5, 0.5, 0.5},
|
||||||
|
{-0.5, -0.5, 0.5},
|
||||||
|
{-0.5, -0.5, 0.5},
|
||||||
|
{-0.5, -0.5, 0.5}
|
||||||
|
};
|
||||||
|
v3 n[24] = {
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{1.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, -1.0},
|
||||||
|
{0.0, -1.0, 0.0},
|
||||||
|
{1.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, -1.0},
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{0.0, 0.0, 1.0},
|
||||||
|
{1.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, 1.0},
|
||||||
|
{0.0, -1.0, 0.0},
|
||||||
|
{1.0, 0.0, 0.0},
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{-1.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, -1.0},
|
||||||
|
{-1.0, 0.0, 0.0},
|
||||||
|
{0.0, -1.0, 0.0},
|
||||||
|
{0.0, 0.0, -1.0},
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{0.0, 0.0, 1.0},
|
||||||
|
{-1.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, 1.0},
|
||||||
|
{-1.0, 0.0, 0.0},
|
||||||
|
{0.0, -1.0, 0.0}
|
||||||
|
};
|
||||||
|
u32 i[36] = {12, 6, 0, 7, 21, 9, 20, 15, 22, 3, 23, 16, 1, 11, 4, 14, 5, 17, 12, 18, 6, 7, 19, 21, 20, 13, 15, 3, 10, 23, 1, 8, 11, 14, 2, 5};
|
||||||
|
|
||||||
|
v3 *vertices = (v3*)p_alloc(24 * sizeof(v3));
|
||||||
|
v3 *normals = (v3*)p_alloc(24 * sizeof(v3));
|
||||||
|
u32 *indices = (u32*)p_alloc(36 * sizeof(u32));
|
||||||
|
|
||||||
|
memcpy(vertices, v, 24 * sizeof(v3));
|
||||||
|
memcpy(normals , n, 24 * sizeof(v3));
|
||||||
|
memcpy(indices , i, 36 * sizeof(u32));
|
||||||
|
|
||||||
|
return r_mesh_create(36, indices, 24, vertices, normals, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Framebuffers
|
||||||
|
r_framebuffer r_framebuffer_create(v2s size, u32 flags)
|
||||||
|
{
|
||||||
|
r_framebuffer fb;
|
||||||
|
fb.flags = flags;
|
||||||
|
fb.size = size;
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &fb.gl_id);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fb.gl_id);
|
||||||
|
|
||||||
|
fb.color_texture = r_texture_create(NULL, size, R_TEXTURE_RGBA | R_TEXTURE_HDR | R_TEXTURE_NO_MIPMAP);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color_texture.gl_id, 0);
|
||||||
|
|
||||||
|
fb.gl_depth_id = 0;
|
||||||
|
if(flags & R_FRAMEBUFFER_DEPTH)
|
||||||
|
{
|
||||||
|
glGenRenderbuffers(1, &fb.gl_depth_id);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, fb.gl_depth_id);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.gl_depth_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_framebuffer_destroy(r_framebuffer *fb)
|
||||||
|
{
|
||||||
|
r_texture_destroy(&fb->color_texture);
|
||||||
|
if(fb->flags & R_FRAMEBUFFER_DEPTH)
|
||||||
|
glDeleteRenderbuffers(1, &fb->gl_depth_id);
|
||||||
|
glDeleteFramebuffers(1, &fb->gl_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_framebuffer_update_size(r_framebuffer *fb, v2s size)
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id);
|
||||||
|
|
||||||
|
r_texture_resize(&fb->color_texture, size);
|
||||||
|
|
||||||
|
if(fb->flags & R_FRAMEBUFFER_DEPTH)
|
||||||
|
{
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, fb->gl_depth_id);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fb->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// GL utils
|
||||||
|
|
||||||
|
static GLint gl_texture_internal_format(u32 texture_flags)
|
||||||
|
{
|
||||||
|
if(texture_flags & R_TEXTURE_ALPHA)
|
||||||
|
return GL_RED;
|
||||||
|
else if(texture_flags & R_TEXTURE_RGB)
|
||||||
|
return (texture_flags & R_TEXTURE_HDR) ? GL_RGB16F : GL_RGB;
|
||||||
|
else if(texture_flags & R_TEXTURE_RGBA)
|
||||||
|
return (texture_flags & R_TEXTURE_HDR) ? GL_RGBA16F : GL_RGBA;
|
||||||
|
else if(texture_flags & R_TEXTURE_SRGB)
|
||||||
|
return GL_SRGB_ALPHA;
|
||||||
|
return GL_RGBA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLenum gl_texture_format(u32 texture_flags)
|
||||||
|
{
|
||||||
|
if(texture_flags & R_TEXTURE_ALPHA)
|
||||||
|
return GL_RED;
|
||||||
|
else if(texture_flags & R_TEXTURE_RGB)
|
||||||
|
return GL_RGB;
|
||||||
|
else if(texture_flags & R_TEXTURE_RGBA)
|
||||||
|
return GL_RGBA;
|
||||||
|
else if(texture_flags & R_TEXTURE_SRGB)
|
||||||
|
return GL_RGBA;
|
||||||
|
return GL_RGBA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLenum gl_texture_type(u32 texture_flags)
|
||||||
|
{
|
||||||
|
if(texture_flags & R_TEXTURE_HDR)
|
||||||
|
return GL_FLOAT;
|
||||||
|
return GL_UNSIGNED_BYTE;
|
||||||
|
}
|
||||||
149
code/render/primitives.h
Normal file
149
code/render/primitives.h
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_PRIMITIVES_H_
|
||||||
|
#define _PIUMA_RENDER_PRIMITIVES_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "GL/glcorearb.h"
|
||||||
|
|
||||||
|
enum r_texture_flags : u32
|
||||||
|
{
|
||||||
|
R_TEXTURE_NONE = 0x00,
|
||||||
|
|
||||||
|
R_TEXTURE_ALPHA = 0x01,
|
||||||
|
R_TEXTURE_RGB = 0x02,
|
||||||
|
R_TEXTURE_RGBA = 0x04,
|
||||||
|
R_TEXTURE_SRGB = 0x08,
|
||||||
|
|
||||||
|
R_TEXTURE_HDR = 0x10,
|
||||||
|
|
||||||
|
R_TEXTURE_NO_MIPMAP = 0x00010000,
|
||||||
|
|
||||||
|
R_TEXTURE_DONT_OWN = 0x20000000,
|
||||||
|
R_TEXTURE_INITIALIZED = 0x40000000,
|
||||||
|
R_TEXTURE_DESTROYED = 0x80000000
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_texture
|
||||||
|
{
|
||||||
|
u8 *data;
|
||||||
|
v2s size;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
// OpenGL
|
||||||
|
GLuint gl_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_texture r_texture_create(u8 *data, v2s size, u32 flags);
|
||||||
|
void r_texture_destroy(r_texture *texture);
|
||||||
|
void r_texture_resize(r_texture *texture, v2s size);
|
||||||
|
void r_texture_update(r_texture *texture, u8 *data, v2s size, v2s position = {0,0}, u32 stride = 0);
|
||||||
|
s32 r_texture_channels(r_texture *texture);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum r_cubemap_flags : u32
|
||||||
|
{
|
||||||
|
R_CUBEMAP_NONE = 0x00,
|
||||||
|
|
||||||
|
// R_TEXTURE_ALPHA = 0x01,
|
||||||
|
// R_TEXTURE_RGB = 0x02,
|
||||||
|
// R_TEXTURE_RGBA = 0x04,
|
||||||
|
// R_TEXTURE_SRGB = 0x08,
|
||||||
|
|
||||||
|
R_CUBEMAP_DONT_OWN = 0x20000000,
|
||||||
|
R_CUBEMAP_INITIALIZED = 0x40000000,
|
||||||
|
R_CUBEMAP_DESTROYED = 0x80000000
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_cubemap
|
||||||
|
{
|
||||||
|
float *data[6];
|
||||||
|
v2s size;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
// OpenGL
|
||||||
|
GLuint gl_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_cubemap r_cubemap_create(float *data[6], v2s size, u32 flags);
|
||||||
|
void r_cubemap_destroy(r_cubemap *cubemap);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct r_2d_mesh
|
||||||
|
{
|
||||||
|
v2 *vertices;
|
||||||
|
v4 *colors;
|
||||||
|
v2 *uvs;
|
||||||
|
u64 count;
|
||||||
|
|
||||||
|
// OpenGL
|
||||||
|
GLuint gl_VAO;
|
||||||
|
GLuint gl_vertex_buffer;
|
||||||
|
GLuint gl_color_buffer;
|
||||||
|
GLuint gl_uv_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_2d_mesh r_2d_mesh_create(u64 count, v2 *vertices, v4 *colors, v2 *uvs = NULL);
|
||||||
|
void r_2d_mesh_destroy(r_2d_mesh *mesh);
|
||||||
|
// r_2d_mesh_update/change
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct r_mesh
|
||||||
|
{
|
||||||
|
v3 *vertices;
|
||||||
|
v3 *normals;
|
||||||
|
v3 *tangents;
|
||||||
|
v2 *uvs;
|
||||||
|
u64 vertices_count;
|
||||||
|
|
||||||
|
u32 *indices;
|
||||||
|
u64 indices_count;
|
||||||
|
|
||||||
|
// OpenGL
|
||||||
|
GLuint gl_VAO;
|
||||||
|
GLuint gl_vertex_buffer;
|
||||||
|
GLuint gl_normal_buffer;
|
||||||
|
GLuint gl_tangent_buffer;
|
||||||
|
GLuint gl_uv_buffer;
|
||||||
|
GLuint gl_index_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_mesh r_mesh_create(u64 indices_count, u32 *indices, u64 vertices_count, v3 *vertices, v3 *normals = NULL, v3 *tangents = NULL, v2 *uvs = NULL);
|
||||||
|
void r_mesh_destroy(r_mesh *mesh);
|
||||||
|
// r_mesh_update/change
|
||||||
|
|
||||||
|
|
||||||
|
// Common meshes
|
||||||
|
r_mesh r_mesh_build_cube();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Framebuffers
|
||||||
|
enum r_framebuffer_flags : u32
|
||||||
|
{
|
||||||
|
R_FRAMEBUFFER_NONE = 0,
|
||||||
|
R_FRAMEBUFFER_DEPTH,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_framebuffer
|
||||||
|
{
|
||||||
|
u32 gl_id;
|
||||||
|
u32 gl_depth_id;
|
||||||
|
r_texture color_texture;
|
||||||
|
v2s size;
|
||||||
|
u32 flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_framebuffer r_framebuffer_create(v2s size, u32 flags);
|
||||||
|
void r_framebuffer_destroy(r_framebuffer *fb);
|
||||||
|
void r_framebuffer_update_size(r_framebuffer *fb, v2s size);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
513
code/render/render.cpp
Normal file
513
code/render/render.cpp
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
#include "render.h"
|
||||||
|
#include "state.h"
|
||||||
|
#include "../platform.h"
|
||||||
|
#include "../lib/math.h"
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
#include "gl_helpers.h"
|
||||||
|
#include "string.h"
|
||||||
|
#include "../camera.h" // @Cleanup: remove dependency from camera
|
||||||
|
|
||||||
|
r_state r_render_state;
|
||||||
|
|
||||||
|
|
||||||
|
void r_init()
|
||||||
|
{
|
||||||
|
// OpenGL debug messages
|
||||||
|
glEnable(GL_DEBUG_OUTPUT);
|
||||||
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||||
|
glDebugMessageCallback(glDebugOutput, nullptr);
|
||||||
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
|
||||||
|
|
||||||
|
// Depth test
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
// Face culling
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
// Texture pixel alignment (default is 4, so rgb textures should have every pixel padded to 4 bytes. With this we can have 3 byte pixels with no padding between eachother)
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
// Blending
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendEquation(GL_FUNC_ADD);
|
||||||
|
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
// Cubemap
|
||||||
|
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||||
|
|
||||||
|
|
||||||
|
// Load shaders
|
||||||
|
if(!r_shader_from_files(&r_render_state.shader_2d, "shaders/2d.vert", "shaders/2d.frag"))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load 2D shader.");
|
||||||
|
}
|
||||||
|
if(!r_shader_from_files(&r_render_state.shader_postprocessing, "shaders/postprocessing.vert", "shaders/postprocessing.frag"))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load postprocessing shader.");
|
||||||
|
}
|
||||||
|
if(!r_shader_from_files(&r_render_state.shader_pbr, "shaders/pbr.vert", "shaders/pbr.frag"))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load pbr shader.");
|
||||||
|
}
|
||||||
|
if(!r_shader_from_files(&r_render_state.shader_shadow_map, "shaders/shadow_map.vert", "shaders/shadow_map.frag"))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load shadow_map shader.");
|
||||||
|
}
|
||||||
|
if(!r_shader_from_files(&r_render_state.shader_environment_map, "shaders/environment_map.vert", "shaders/environment_map.frag"))
|
||||||
|
{
|
||||||
|
LOG(LOG_ERROR, "Cannot load environment_map shader.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen size
|
||||||
|
u32 width, height;
|
||||||
|
p_window_dimensions(&width, &height);
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
|
||||||
|
|
||||||
|
/* We have to apply some post-processing effects only to the 3D world
|
||||||
|
* and some effects to both the 3D world and the HUD.
|
||||||
|
*
|
||||||
|
* So we make 2 framebuffers:
|
||||||
|
* 1. target for 3D world
|
||||||
|
* 2. target for HUD
|
||||||
|
* After rendering, we apply the correct post-processing effects and
|
||||||
|
* merge them toghether.
|
||||||
|
*/
|
||||||
|
// Init framebuffers
|
||||||
|
v2s size = {width, height};
|
||||||
|
r_render_state.framebuffer_SCREEN = {.gl_id = 0, .size = size, .flags = 0}; // This is a special framebuffer, because it's already there. We don't need to create it
|
||||||
|
r_render_state.framebuffer_HUD = r_framebuffer_create(size, 0);
|
||||||
|
r_render_state.framebuffer_3D = r_framebuffer_create(size, R_FRAMEBUFFER_DEPTH);
|
||||||
|
|
||||||
|
// Default framebuffer
|
||||||
|
r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
|
||||||
|
|
||||||
|
// Init fullscreen quad
|
||||||
|
glGenVertexArrays(1, &r_render_state.gl_screen_quad_VAO);
|
||||||
|
glBindVertexArray(r_render_state.gl_screen_quad_VAO);
|
||||||
|
glGenBuffers(1, &r_render_state.gl_screen_quad_VBO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, r_render_state.gl_screen_quad_VBO);
|
||||||
|
v2 quad_vertices[2*6] =
|
||||||
|
{
|
||||||
|
// position UV coords
|
||||||
|
v2{-1, 1}, v2{0, 1},
|
||||||
|
v2{-1, -1}, v2{0, 0},
|
||||||
|
v2{ 1, -1}, v2{1, 0},
|
||||||
|
v2{-1, 1}, v2{0, 1},
|
||||||
|
v2{ 1, -1}, v2{1, 0},
|
||||||
|
v2{ 1, 1}, v2{1, 1}
|
||||||
|
};
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 2*6 * sizeof(v2), quad_vertices, GL_STATIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(0));
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(sizeof(v2)));
|
||||||
|
|
||||||
|
// Render state
|
||||||
|
r_size_update(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_deinit()
|
||||||
|
{
|
||||||
|
r_framebuffer_destroy(&r_render_state.framebuffer_HUD);
|
||||||
|
r_framebuffer_destroy(&r_render_state.framebuffer_3D);
|
||||||
|
|
||||||
|
glDeleteBuffers(1, &r_render_state.gl_screen_quad_VBO);
|
||||||
|
glDeleteVertexArrays(1, &r_render_state.gl_screen_quad_VAO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_size_update(u32 width, u32 height)
|
||||||
|
{
|
||||||
|
// Screen size
|
||||||
|
r_render_state.width = width;
|
||||||
|
r_render_state.height = height;
|
||||||
|
// Update shaders
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
glUniform1i(r_render_state.shader_2d.width , width);
|
||||||
|
glUniform1i(r_render_state.shader_2d.height, height);
|
||||||
|
|
||||||
|
// Update framebuffers size
|
||||||
|
v2s size = {width, height};
|
||||||
|
r_render_state.framebuffer_SCREEN.size = size;
|
||||||
|
r_framebuffer_update_size(&r_render_state.framebuffer_HUD, size);
|
||||||
|
r_framebuffer_update_size(&r_render_state.framebuffer_3D , size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_time_update(f64 time)
|
||||||
|
{
|
||||||
|
r_render_state.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void r_clear(v4 color)
|
||||||
|
{
|
||||||
|
glClearColor(color.r, color.g, color.b, color.a);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_swap()
|
||||||
|
{
|
||||||
|
p_graphics_swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
r_scene r_scene_create()
|
||||||
|
{
|
||||||
|
r_scene scene;
|
||||||
|
|
||||||
|
scene.view_matrix = m4_identity;
|
||||||
|
scene.view_matrix_inverse = m4_identity;
|
||||||
|
|
||||||
|
scene.projection_matrix = m4_identity;
|
||||||
|
scene.projection_matrix_inverse = m4_identity;
|
||||||
|
|
||||||
|
scene.objects = NULL;
|
||||||
|
scene.objects_count = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// Init lights memory
|
||||||
|
memset(&scene.lights, 0, sizeof(r_light_container));
|
||||||
|
glGenBuffers(1, &scene.gl_lights);
|
||||||
|
glBindBuffer(GL_UNIFORM_BUFFER, scene.gl_lights);
|
||||||
|
glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene.lights, GL_STATIC_DRAW);
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, 0, scene.gl_lights);
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_scene_destroy(r_scene *scene)
|
||||||
|
{
|
||||||
|
glDeleteBuffers(1, &scene->gl_lights);
|
||||||
|
r_cubemap_destroy(&scene->environment_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_scene_set_view(r_scene *scene, m4 m)
|
||||||
|
{
|
||||||
|
scene->view_matrix = m;
|
||||||
|
scene->view_matrix_inverse = inverse(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_scene_set_projection(r_scene *scene, m4 m)
|
||||||
|
{
|
||||||
|
scene->projection_matrix = m;
|
||||||
|
scene->projection_matrix_inverse = inverse(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_update_lights(r_scene *scene)
|
||||||
|
{
|
||||||
|
glBindBuffer(GL_UNIFORM_BUFFER, scene->gl_lights);
|
||||||
|
glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene->lights, GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
r_object build_basic_cube()
|
||||||
|
{
|
||||||
|
r_object object = r_object_allocate(1);
|
||||||
|
|
||||||
|
object.meshes[0] = (r_mesh*)p_alloc(sizeof(r_mesh));
|
||||||
|
*object.meshes[0] = r_mesh_build_cube();
|
||||||
|
|
||||||
|
object.mesh_material[0] = (r_material*)p_alloc(sizeof(r_material));
|
||||||
|
memset(object.mesh_material[0], 0, sizeof(r_material));
|
||||||
|
object.mesh_material[0]->shader = &r_render_state.shader_pbr;
|
||||||
|
object.mesh_material[0]->albedo_factor = {1,1,1,1};
|
||||||
|
object.mesh_material[0]->emissive_factor = {1,1,1,0};
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_render_scene(r_scene *scene)
|
||||||
|
{
|
||||||
|
// Render shadow maps
|
||||||
|
scene->shadow_map = r_build_shadow_map_sun(scene, &scene->lights.sun_lights[0]);
|
||||||
|
|
||||||
|
// Render environment map
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
static r_object cube = build_basic_cube();
|
||||||
|
cube.position = extract_column(scene->view_matrix_inverse, 3).xyz;
|
||||||
|
glUseProgram(r_render_state.shader_environment_map.id);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id);
|
||||||
|
glUniform1i(r_render_state.shader_environment_map.has_environment_map, 1);
|
||||||
|
glUniform1i(r_render_state.shader_environment_map.environment_map , 0);
|
||||||
|
r_render_object(scene, &cube, &r_render_state.shader_environment_map);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
// Render objects
|
||||||
|
glUseProgram(r_render_state.shader_pbr.id);
|
||||||
|
|
||||||
|
// Set shadow map
|
||||||
|
// @Cleanup: this should be in r_render_objects
|
||||||
|
glActiveTexture(GL_TEXTURE5);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, scene->shadow_map.gl_depth_texture);
|
||||||
|
glUniform1i (r_render_state.shader_pbr.has_shadow_map, 1);
|
||||||
|
glUniform1i (r_render_state.shader_pbr.shadow_map , 5);
|
||||||
|
glUniformMatrix4fv(r_render_state.shader_pbr.shadow_matrix , 1, GL_TRUE, (const f32 *)(scene->shadow_map.view_matrix.E));
|
||||||
|
|
||||||
|
// Environment map
|
||||||
|
glActiveTexture(GL_TEXTURE6);
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id);
|
||||||
|
glUniform1i(r_render_state.shader_pbr.has_environment_map, 1);
|
||||||
|
glUniform1i(r_render_state.shader_pbr.environment_map , 6);
|
||||||
|
|
||||||
|
// Render objects
|
||||||
|
for(u32 i = 0; i < scene->objects_count; i++)
|
||||||
|
{
|
||||||
|
r_render_object(scene, &scene->objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
glUniform1i(r_render_state.shader_pbr.has_shadow_map, 0);
|
||||||
|
glUniform1i(r_render_state.shader_pbr.has_environment_map, 0);
|
||||||
|
r_free_shadow_map(&scene->shadow_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Single object rendering functions (batched funcs are below)
|
||||||
|
void r_render_object(r_scene *scene, r_object *object, r_shader *shader_override)
|
||||||
|
{
|
||||||
|
// @Performance: Minimize draw calls by batching mesh rendering
|
||||||
|
for(u32 i = 0; i < object->count; i++)
|
||||||
|
{
|
||||||
|
r_mesh *mesh = object->meshes[i];
|
||||||
|
r_material *material = object->mesh_material[i];
|
||||||
|
r_shader *shader = material->shader;
|
||||||
|
if(shader_override)
|
||||||
|
shader = shader_override;
|
||||||
|
|
||||||
|
glUseProgram(shader->id);
|
||||||
|
|
||||||
|
glUniform1f(shader->time , (f32)r_render_state.time );
|
||||||
|
glUniform1f(shader->width , (f32)r_render_state.width );
|
||||||
|
glUniform1f(shader->height, (f32)r_render_state.height);
|
||||||
|
|
||||||
|
// Transform matrices
|
||||||
|
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
|
||||||
|
m4 projview_matrix_inverse = inverse(projview_matrix);
|
||||||
|
glUniformMatrix4fv(shader->view_matrix, 1, GL_TRUE, (const f32 *)(&projview_matrix));
|
||||||
|
glUniformMatrix4fv(shader->view_matrix_inverse, 1, GL_TRUE, (const f32 *)(&projview_matrix_inverse));
|
||||||
|
|
||||||
|
m4 model_transform = r_object_mesh_transform_matrix(object, i);
|
||||||
|
glUniformMatrix4fv(shader->model_matrix, 1, GL_TRUE, (const f32 *)(&model_transform));
|
||||||
|
|
||||||
|
// Textures
|
||||||
|
// Albedo
|
||||||
|
glUniform1i(shader->has_albedo_texture, material->albedo_texture != NULL);
|
||||||
|
if(material->albedo_texture)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material->albedo_texture->gl_id);
|
||||||
|
glUniform1i(shader->albedo_texture, 0);
|
||||||
|
}
|
||||||
|
glUniform4f(shader->albedo_factor, material->albedo_factor.r, material->albedo_factor.g, material->albedo_factor.b, material->albedo_factor.a);
|
||||||
|
|
||||||
|
// Metallic
|
||||||
|
glUniform1i(shader->has_metallic_texture, material->metallic_texture != NULL);
|
||||||
|
if(material->metallic_texture)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material->metallic_texture->gl_id);
|
||||||
|
glUniform1i(shader->metallic_texture, 1);
|
||||||
|
}
|
||||||
|
glUniform1f(shader->metallic_factor, material->metallic_factor);
|
||||||
|
|
||||||
|
// Roughness
|
||||||
|
glUniform1i(shader->has_roughness_texture, material->roughness_texture != NULL);
|
||||||
|
if(material->roughness_texture)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE2);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material->roughness_texture->gl_id);
|
||||||
|
glUniform1i(shader->roughness_texture, 2);
|
||||||
|
}
|
||||||
|
glUniform1f(shader->roughness_factor, material->roughness_factor);
|
||||||
|
|
||||||
|
// Normal
|
||||||
|
glUniform1i(shader->has_normal_texture, material->normal_texture != NULL);
|
||||||
|
if(material->normal_texture)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE3);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material->normal_texture->gl_id);
|
||||||
|
glUniform1i(shader->normal_texture, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emissive
|
||||||
|
glUniform1i(shader->has_emissive_texture, material->emissive_texture != NULL);
|
||||||
|
if(material->emissive_texture)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE4);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material->emissive_texture->gl_id);
|
||||||
|
glUniform1i(shader->emissive_texture, 4);
|
||||||
|
}
|
||||||
|
glUniform4f(shader->emissive_factor, material->emissive_factor.r, material->emissive_factor.g, material->emissive_factor.b, material->emissive_factor.a);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Draw call
|
||||||
|
glBindVertexArray(mesh->gl_VAO);
|
||||||
|
glDrawElements(GL_TRIANGLES, mesh->indices_count, GL_UNSIGNED_INT, (void*)(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_render_object_wireframe(r_scene *scene, r_object *object)
|
||||||
|
{
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||||
|
r_render_object(scene, object);
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_render_line(r_scene *scene, v3 a, v3 b, v4 a_color, v4 b_color, f32 thickness)
|
||||||
|
{
|
||||||
|
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
|
||||||
|
v4 a_trans = projview_matrix * V4(a, 1);
|
||||||
|
v4 b_trans = projview_matrix * V4(b, 1);
|
||||||
|
a_trans /= a_trans.w;
|
||||||
|
b_trans /= b_trans.w;
|
||||||
|
if(a_trans.z > 1 || b_trans.z > 1)
|
||||||
|
return;
|
||||||
|
v2 a_2d = a_trans.xy;
|
||||||
|
v2 b_2d = b_trans.xy;
|
||||||
|
a_2d = (v2{.5,.5} + v2{.5,-0.5} * a_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height};
|
||||||
|
b_2d = (v2{.5,.5} + v2{.5,-0.5} * b_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height};
|
||||||
|
|
||||||
|
r_2d_immediate_segment(a_2d, b_2d, a_color, b_color, thickness); // @Cleanup: This should be independent from the 2D drawing functions, probably.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Batch render functions
|
||||||
|
void r_render_objects(r_scene *scene, u64 count, r_object **objects)
|
||||||
|
{
|
||||||
|
// @Feature: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
r_shadow_map r_build_shadow_map_sun(r_scene *scene, r_sun_light *sun)
|
||||||
|
{
|
||||||
|
glCullFace(GL_FRONT);
|
||||||
|
|
||||||
|
r_shadow_map sm;
|
||||||
|
u32 width = 2*1024;
|
||||||
|
u32 height = 2*1024;
|
||||||
|
|
||||||
|
// Init depth texture/framebuffer
|
||||||
|
glGenTextures(1, &sm.gl_depth_texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, sm.gl_depth_texture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &sm.gl_FBO);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, sm.gl_FBO);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sm.gl_depth_texture, 0);
|
||||||
|
glDrawBuffer(GL_NONE);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
// Compute light frustum
|
||||||
|
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
|
||||||
|
m4 projview_matrix_inverse = inverse(projview_matrix);
|
||||||
|
v4 center_point_4 = projview_matrix_inverse * v4{0, 0, 0, 1};
|
||||||
|
v3 center_point = center_point_4.xyz / center_point_4.w;
|
||||||
|
f32 size = 1;
|
||||||
|
v4 bottom = projview_matrix_inverse * v4{ 0,-1, 0, 1};
|
||||||
|
v4 top = projview_matrix_inverse * v4{ 0, 1, 0, 1};
|
||||||
|
v4 left = projview_matrix_inverse * v4{-1, 0, 0, 1};
|
||||||
|
v4 right = projview_matrix_inverse * v4{ 1, 0, 0, 1};
|
||||||
|
v4 front = projview_matrix_inverse * v4{ 0, 0,-1, 1};
|
||||||
|
v4 back = projview_matrix_inverse * v4{ 0, 0, 1, 1};
|
||||||
|
size = maximum(size, length(top.xyz/top.w - bottom.xyz/bottom.w));
|
||||||
|
size = maximum(size, length(right.xyz/right.w - left.xyz/left.w));
|
||||||
|
size = maximum(size, length(back.xyz/back.w - front.xyz/front.w));
|
||||||
|
size /= 2; // @Cleanup: hack to have better precision. There are multiple ways to do it right: smallest box around all objects, cascaded shadow maps
|
||||||
|
|
||||||
|
|
||||||
|
v3 light_position = center_point - size * sun->direction;
|
||||||
|
m4 light_camera_matrix = r_view_matrix(light_position, sun->direction, v3{0, 0, 1});
|
||||||
|
m4 projection_matrix = r_orthographic_matrix(-size/2, size/2, -size/2, size/2, 0.1, 100.0);
|
||||||
|
|
||||||
|
sm.view_matrix = projection_matrix * light_camera_matrix;
|
||||||
|
|
||||||
|
// Render shadow map
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// @Feature @Cleanup: Some objects might not need to be drawn. Add a "hidden" flag. Maybe take the list of objects to render as an argument
|
||||||
|
m4 old_view = scene->view_matrix;
|
||||||
|
m4 old_proj = scene->projection_matrix;
|
||||||
|
r_scene_set_view (scene, sm.view_matrix);
|
||||||
|
r_scene_set_projection(scene, m4_identity);
|
||||||
|
for(u32 i = 0; i < scene->objects_count; i++)
|
||||||
|
{
|
||||||
|
if(scene->objects[i].has_shadow)
|
||||||
|
{
|
||||||
|
r_render_object(scene, &scene->objects[i], &r_render_state.shader_shadow_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r_scene_set_view (scene, old_view);
|
||||||
|
r_scene_set_projection(scene, old_proj);
|
||||||
|
|
||||||
|
// Restore framebuffer
|
||||||
|
r_framebuffer_select(r_render_state.current_framebuffer);
|
||||||
|
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
return sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void r_free_shadow_map(r_shadow_map *sm)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &sm->gl_depth_texture);
|
||||||
|
glDeleteFramebuffers(1, &sm->gl_FBO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void r_merge_and_postprocess()
|
||||||
|
{
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||||
|
|
||||||
|
glUseProgram(r_render_state.shader_postprocessing.id);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_3D.color_texture.gl_id);
|
||||||
|
glUniform1i(r_render_state.shader_postprocessing.texture[0], 0);
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_HUD.color_texture.gl_id);
|
||||||
|
glUniform1i(r_render_state.shader_postprocessing.texture[1], 1);
|
||||||
|
|
||||||
|
glBindVertexArray(r_render_state.gl_screen_quad_VAO);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void r_framebuffer_select(r_framebuffer *fb)
|
||||||
|
{
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id);
|
||||||
|
glViewport(0, 0, fb->size.x, fb->size.y);
|
||||||
|
|
||||||
|
glUseProgram(r_render_state.shader_2d.id);
|
||||||
|
glUniform1i(r_render_state.shader_2d.width , fb->size.x);
|
||||||
|
glUniform1i(r_render_state.shader_2d.height, fb->size.y);
|
||||||
|
|
||||||
|
r_render_state.current_framebuffer = fb;
|
||||||
|
}
|
||||||
90
code/render/render.h
Normal file
90
code/render/render.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// 2d: Functions to render 2d things immediatly. Simple things like meshes, lines, triangles, quads.
|
||||||
|
// render: Renderer, takes complex objects with materials and renders them.
|
||||||
|
// Might use an object buffer/queue and render things later, to enable
|
||||||
|
// sorting and other complex rendering procedures (culling).
|
||||||
|
// shader: Shaders! They have enough code to have their own source files
|
||||||
|
// object: Objects, materials, maybe other things. To be used by the renderer.
|
||||||
|
// light: Lights to be used by the renderer. There are enought types to separate them from objects.
|
||||||
|
|
||||||
|
// Might (or might not) add: compute shaders, vfx/postprocessing passes, animations
|
||||||
|
|
||||||
|
#ifndef _PIUMA_RENDER_RENDER_H_
|
||||||
|
#define _PIUMA_RENDER_RENDER_H_
|
||||||
|
|
||||||
|
#include "primitives.h"
|
||||||
|
#include "shader.h"
|
||||||
|
#include "2d.h"
|
||||||
|
#include "lights.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "state.h"
|
||||||
|
|
||||||
|
void r_init();
|
||||||
|
void r_deinit();
|
||||||
|
|
||||||
|
void r_size_update(u32 width, u32 height);
|
||||||
|
void r_time_update(f64 time);
|
||||||
|
|
||||||
|
void r_clear(v4 color = {0.0, 0.0, 0.0, 0.0});
|
||||||
|
void r_swap();
|
||||||
|
|
||||||
|
|
||||||
|
struct r_shadow_map
|
||||||
|
{
|
||||||
|
// FBO + depth
|
||||||
|
u32 gl_FBO;
|
||||||
|
u32 gl_depth_texture;
|
||||||
|
m4 view_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct r_scene
|
||||||
|
{
|
||||||
|
m4 view_matrix;
|
||||||
|
m4 view_matrix_inverse;
|
||||||
|
|
||||||
|
m4 projection_matrix;
|
||||||
|
m4 projection_matrix_inverse;
|
||||||
|
|
||||||
|
r_object *objects;
|
||||||
|
u64 objects_count;
|
||||||
|
|
||||||
|
r_light_container lights;
|
||||||
|
GLuint gl_lights;
|
||||||
|
|
||||||
|
r_shadow_map shadow_map;
|
||||||
|
|
||||||
|
r_cubemap environment_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
r_scene r_scene_create();
|
||||||
|
void r_scene_destroy(r_scene *scene);
|
||||||
|
|
||||||
|
void r_scene_set_view (r_scene *scene, m4 m);
|
||||||
|
void r_scene_set_projection(r_scene *scene, m4 m);
|
||||||
|
void r_update_lights(r_scene *scene);
|
||||||
|
|
||||||
|
|
||||||
|
void r_render_scene(r_scene *scene);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Single object rendering functions (batched funcs are below)
|
||||||
|
void r_render_object(r_scene *scene, r_object *object, r_shader *shader_override = NULL);
|
||||||
|
void r_render_object_wireframe(r_scene *scene, r_object *object);
|
||||||
|
void r_render_line(r_scene *scene, v3 a, v3 b, v4 a_color, v4 b_color, f32 thickness = 1.0);
|
||||||
|
|
||||||
|
|
||||||
|
// Batch render functions
|
||||||
|
void r_render_objects(r_scene *scene, u64 count, r_object **objects);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Shadow maps
|
||||||
|
r_shadow_map r_build_shadow_map_sun(r_scene *scene, r_sun_light *sun);
|
||||||
|
void r_free_shadow_map(r_shadow_map *sm);
|
||||||
|
|
||||||
|
|
||||||
|
void r_framebuffer_select(r_framebuffer *fb);
|
||||||
|
void r_merge_and_postprocess();
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
193
code/render/shader.cpp
Normal file
193
code/render/shader.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include "shader.h"
|
||||||
|
#include "../debug/logger.h"
|
||||||
|
|
||||||
|
#define STB_INCLUDE_IMPLEMENTATION
|
||||||
|
#define STB_INCLUDE_LINE_GLSL
|
||||||
|
#include "stb_include.h"
|
||||||
|
|
||||||
|
// Internal functions
|
||||||
|
void r_shader_set_uniform_locations(r_shader *shader);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool r_shader_from_files(r_shader *shader, const char *vertex, const char *fragment, const char *geometry, const char *tesseletion_control, const char *tesseletion_evaluation, const char *compute)
|
||||||
|
{
|
||||||
|
const int count = 6;
|
||||||
|
char *source[count] = {NULL, NULL, NULL, NULL, NULL, NULL};
|
||||||
|
const char *filename[count] = {vertex, fragment, geometry, tesseletion_control, tesseletion_evaluation, compute};
|
||||||
|
const char *type[count] = {"vertex", "fragment", "geometry", "tesseletion_control", "tesseletion_evaluation", "compute"};
|
||||||
|
|
||||||
|
// Load sources
|
||||||
|
bool has_error = false;
|
||||||
|
char error[256];
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if(filename[i])
|
||||||
|
{
|
||||||
|
char path[4096];
|
||||||
|
u32 len = strlen(filename[i]);
|
||||||
|
memcpy(path, filename[i], len+1);
|
||||||
|
int last_slash = len;
|
||||||
|
while(last_slash > 0)
|
||||||
|
{
|
||||||
|
if(filename[i][last_slash] == '/')
|
||||||
|
break;
|
||||||
|
last_slash--;
|
||||||
|
}
|
||||||
|
path[last_slash] = '\0';
|
||||||
|
|
||||||
|
source[i] = stb_include_file((char*)filename[i], NULL, path, error);
|
||||||
|
if(!source[i])
|
||||||
|
{
|
||||||
|
has_error = true;
|
||||||
|
LOG(LOG_ERROR, "Error loading %s shader: %s", type[i], error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile shader
|
||||||
|
if(!has_error)
|
||||||
|
{
|
||||||
|
has_error = !r_shader_from_text(shader, source[0], source[1], source[2], source[3], source[5], source[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if(source[i])
|
||||||
|
free(source[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !has_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool r_shader_from_text(r_shader *shader, const char *vertex, const char *fragment, const char *geometry, const char *tesseletion_control, const char *tesseletion_evaluation, const char *compute)
|
||||||
|
{
|
||||||
|
int count = 6;
|
||||||
|
GLuint type[count] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_COMPUTE_SHADER};
|
||||||
|
const char *source[count] = {vertex, fragment, geometry, tesseletion_control, tesseletion_evaluation, compute};
|
||||||
|
|
||||||
|
// Create shader program
|
||||||
|
shader->id = glCreateProgram();
|
||||||
|
|
||||||
|
bool has_error = false;
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if(!source[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Compile current shader source
|
||||||
|
GLuint source_id = glCreateShader(type[i]);
|
||||||
|
GLint size = strlen(source[i]);
|
||||||
|
glShaderSource(source_id, 1, &source[i], &size);
|
||||||
|
glCompileShader(source_id);
|
||||||
|
|
||||||
|
// Check for success/errors
|
||||||
|
char build_info[2048];
|
||||||
|
GLint build_success;
|
||||||
|
glGetShaderiv(source_id, GL_COMPILE_STATUS, &build_success);
|
||||||
|
if (!build_success)
|
||||||
|
{
|
||||||
|
glGetShaderInfoLog(source_id, 2048, NULL, build_info);
|
||||||
|
LOG(LOG_ERROR, "Cannot compile shader %.*s: %s", (int)size, source, build_info);
|
||||||
|
glDeleteShader(source_id);
|
||||||
|
has_error = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach compiler shader to program
|
||||||
|
glAttachShader(shader->id, source_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Correctness: Clean up shader sources after fail
|
||||||
|
if(has_error)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Link program
|
||||||
|
glLinkProgram(shader->id);
|
||||||
|
|
||||||
|
// Check for success/error
|
||||||
|
GLint build_success;
|
||||||
|
char build_info[512];
|
||||||
|
glGetProgramiv(shader->id, GL_LINK_STATUS, &build_success);
|
||||||
|
if(!build_success) {
|
||||||
|
glGetProgramInfoLog(shader->id, 512, NULL, build_info);
|
||||||
|
LOG(LOG_ERROR, "Cannot link shader program: %s", build_info);
|
||||||
|
glDeleteShader(shader->id);
|
||||||
|
has_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load uniform locations
|
||||||
|
if(!has_error)
|
||||||
|
r_shader_set_uniform_locations(shader);
|
||||||
|
|
||||||
|
return !has_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void r_shader_delete(r_shader *shader)
|
||||||
|
{
|
||||||
|
glDeleteProgram(shader->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Internal functions
|
||||||
|
void r_shader_set_uniform_locations(r_shader *shader)
|
||||||
|
{
|
||||||
|
shader->time = glGetUniformLocation(shader->id, "time");
|
||||||
|
shader->width = glGetUniformLocation(shader->id, "width");
|
||||||
|
shader->height = glGetUniformLocation(shader->id, "height");
|
||||||
|
|
||||||
|
shader->view_matrix = glGetUniformLocation(shader->id, "view_matrix");
|
||||||
|
shader->view_matrix_inverse = glGetUniformLocation(shader->id, "view_matrix_inverse");
|
||||||
|
shader->model_matrix = glGetUniformLocation(shader->id, "model_matrix");
|
||||||
|
shader->camera_position = glGetUniformLocation(shader->id, "camera_position");
|
||||||
|
|
||||||
|
for(int i = 0; i < R_SHADER_COLOR_MAX; i++)
|
||||||
|
{
|
||||||
|
char uniform_name[32]; sprintf(uniform_name, "color%d", i);
|
||||||
|
shader->color[i] = glGetUniformLocation(shader->id, uniform_name);
|
||||||
|
}
|
||||||
|
for(int i = 0; i < R_SHADER_TEXTURES_MAX; i++)
|
||||||
|
{
|
||||||
|
char uniform_name[32];
|
||||||
|
sprintf(uniform_name, "has_texture%d", i);
|
||||||
|
shader->has_texture [i] = glGetUniformLocation(shader->id, uniform_name);
|
||||||
|
sprintf(uniform_name, "texture%d", i);
|
||||||
|
shader->texture [i] = glGetUniformLocation(shader->id, uniform_name);
|
||||||
|
sprintf(uniform_name, "texture_channels%d", i);
|
||||||
|
shader->texture_channels[i] = glGetUniformLocation(shader->id, uniform_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader->lights = glGetUniformBlockIndex(shader->id, "lights");
|
||||||
|
if(shader->lights != GL_INVALID_INDEX)
|
||||||
|
glUniformBlockBinding(shader->id, shader->lights, 0);
|
||||||
|
shader->has_shadow_map = glGetUniformLocation(shader->id, "has_shadow_map");
|
||||||
|
shader->shadow_map = glGetUniformLocation(shader->id, "shadow_map");
|
||||||
|
shader->shadow_matrix = glGetUniformLocation(shader->id, "shadow_matrix");
|
||||||
|
|
||||||
|
shader->has_environment_map = glGetUniformLocation(shader->id, "has_environment_map");
|
||||||
|
shader->environment_map = glGetUniformLocation(shader->id, "environment_map");
|
||||||
|
|
||||||
|
shader->has_albedo_texture = glGetUniformLocation(shader->id, "has_albedo_texture");
|
||||||
|
shader->albedo_texture = glGetUniformLocation(shader->id, "albedo_texture");
|
||||||
|
shader->albedo_factor = glGetUniformLocation(shader->id, "albedo_factor");
|
||||||
|
shader->has_metallic_texture = glGetUniformLocation(shader->id, "has_metallic_texture");
|
||||||
|
shader->metallic_texture = glGetUniformLocation(shader->id, "metallic_texture");
|
||||||
|
shader->metallic_factor = glGetUniformLocation(shader->id, "metallic_factor");
|
||||||
|
shader->has_roughness_texture = glGetUniformLocation(shader->id, "has_roughness_texture");
|
||||||
|
shader->roughness_texture = glGetUniformLocation(shader->id, "roughness_texture");
|
||||||
|
shader->roughness_factor = glGetUniformLocation(shader->id, "roughness_factor");
|
||||||
|
shader->has_normal_texture = glGetUniformLocation(shader->id, "has_normal_texture");
|
||||||
|
shader->normal_texture = glGetUniformLocation(shader->id, "normal_texture");
|
||||||
|
shader->has_emissive_texture = glGetUniformLocation(shader->id, "has_emissive_texture");
|
||||||
|
shader->emissive_texture = glGetUniformLocation(shader->id, "emissive_texture");
|
||||||
|
shader->emissive_factor = glGetUniformLocation(shader->id, "emissive_factor");
|
||||||
|
}
|
||||||
85
code/render/shader.h
Normal file
85
code/render/shader.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_SHADER_H_
|
||||||
|
#define _PIUMA_RENDER_SHADER_H_
|
||||||
|
|
||||||
|
#include "../lib/types.h"
|
||||||
|
#include "GL/glcorearb.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
I put the uniform ids of all the shaders in the r_shader structure. Then I use only the
|
||||||
|
ones I need. Yeah, it's not very flexible.
|
||||||
|
|
||||||
|
I would like to have automatic syncronization between shader and C++ code.
|
||||||
|
There are 3 ways to do that:
|
||||||
|
- Parse shader code and auto-generate C++ structures and code;
|
||||||
|
- Parse C++ structures and generate a shader source, or update an existing one;
|
||||||
|
- Use a language than let me define shader code directly in that language, then it's compiled
|
||||||
|
directly to GPU ASM, or it generates GLSL code that is then given to the GPU driver. This is
|
||||||
|
the ideal solution, but yeah, that language is not C++... if it event exists. Let's hope for
|
||||||
|
a better future.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define R_SHADER_TEXTURES_MAX 4
|
||||||
|
#define R_SHADER_COLOR_MAX 4
|
||||||
|
|
||||||
|
struct r_shader
|
||||||
|
{
|
||||||
|
GLuint id;
|
||||||
|
|
||||||
|
// Common state
|
||||||
|
GLint time;
|
||||||
|
GLint width;
|
||||||
|
GLint height;
|
||||||
|
|
||||||
|
// View and camera
|
||||||
|
GLint view_matrix;
|
||||||
|
GLint view_matrix_inverse;
|
||||||
|
GLint model_matrix;
|
||||||
|
GLint camera_position;
|
||||||
|
|
||||||
|
// For arrays, the name in the shader is in the format [name][index].
|
||||||
|
// Example: color[4] will be: color0, color1, color2, color3
|
||||||
|
|
||||||
|
// Generic parameters that can be used for different purpose based on the shader needs.
|
||||||
|
// Example: texture1 could be a diffuse texture, texture2 an emissive texture, texture3 bump mapping
|
||||||
|
GLint color[R_SHADER_COLOR_MAX];
|
||||||
|
GLint has_texture [R_SHADER_TEXTURES_MAX]; // Is textureX assigned or not?
|
||||||
|
GLint texture [R_SHADER_TEXTURES_MAX]; // Actual texture
|
||||||
|
GLint texture_channels[R_SHADER_TEXTURES_MAX]; // Number of channels in the texture data
|
||||||
|
|
||||||
|
// Lights and shadows
|
||||||
|
// @Cleanup: maybe merge this with the generic texture parameter?
|
||||||
|
GLint lights;
|
||||||
|
GLint has_shadow_map;
|
||||||
|
GLint shadow_map;
|
||||||
|
GLint shadow_matrix;
|
||||||
|
|
||||||
|
// Environment map
|
||||||
|
GLint has_environment_map;
|
||||||
|
GLint environment_map;
|
||||||
|
|
||||||
|
// PBR material
|
||||||
|
GLint has_albedo_texture;
|
||||||
|
GLint albedo_texture;
|
||||||
|
GLint albedo_factor;
|
||||||
|
GLint has_metallic_texture;
|
||||||
|
GLint metallic_texture;
|
||||||
|
GLint metallic_factor;
|
||||||
|
GLint has_roughness_texture;
|
||||||
|
GLint roughness_texture;
|
||||||
|
GLint roughness_factor;
|
||||||
|
GLint has_normal_texture;
|
||||||
|
GLint normal_texture;
|
||||||
|
GLint has_emissive_texture;
|
||||||
|
GLint emissive_texture;
|
||||||
|
GLint emissive_factor;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool r_shader_from_files(r_shader *shader, const char *vertex, const char *fragment, const char *geometry = NULL, const char *tesseletion_control = NULL, const char *tesseletion_evaluation = NULL, const char *compute = NULL);
|
||||||
|
bool r_shader_from_text(r_shader *shader, const char *vertex, const char *fragment, const char *geometry = NULL, const char *tesseletion_control = NULL, const char *tesseletion_evaluation = NULL, const char *compute = NULL);
|
||||||
|
|
||||||
|
void r_shader_delete(r_shader *shader);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
36
code/render/state.h
Normal file
36
code/render/state.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef _PIUMA_RENDER_STATE_H_
|
||||||
|
#define _PIUMA_RENDER_STATE_H_
|
||||||
|
|
||||||
|
#include "shader.h"
|
||||||
|
#include "primitives.h"
|
||||||
|
|
||||||
|
struct r_state
|
||||||
|
{
|
||||||
|
// Shaders
|
||||||
|
r_shader shader_2d;
|
||||||
|
r_shader shader_postprocessing;
|
||||||
|
r_shader shader_pbr;
|
||||||
|
r_shader shader_shadow_map;
|
||||||
|
r_shader shader_environment_map;
|
||||||
|
|
||||||
|
// Screen size
|
||||||
|
u32 width, height;
|
||||||
|
|
||||||
|
// Time
|
||||||
|
f64 time;
|
||||||
|
|
||||||
|
// Framebuffers
|
||||||
|
r_framebuffer *current_framebuffer;
|
||||||
|
|
||||||
|
r_framebuffer framebuffer_SCREEN;
|
||||||
|
r_framebuffer framebuffer_HUD;
|
||||||
|
r_framebuffer framebuffer_3D;
|
||||||
|
|
||||||
|
// Quads
|
||||||
|
u32 gl_screen_quad_VAO;
|
||||||
|
u32 gl_screen_quad_VBO;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern r_state r_render_state;
|
||||||
|
|
||||||
|
#endif
|
||||||
6
external/cgltf.cpp
vendored
Normal file
6
external/cgltf.cpp
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#define CGLTF_IMPLEMENTATION
|
||||||
|
#include "cgltf.h"
|
||||||
|
|
||||||
|
#undef CGLTF_IMPLEMENTATION
|
||||||
|
#define CGLTF_WRITE_IMPLEMENTATION
|
||||||
|
#include "cgltf_write.h"
|
||||||
7050
external/cgltf.h
vendored
Normal file
7050
external/cgltf.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1506
external/cgltf_write.h
vendored
Normal file
1506
external/cgltf_write.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
external/stb_image.cpp
vendored
Normal file
2
external/stb_image.cpp
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
7987
external/stb_image.h
vendored
Normal file
7987
external/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
295
external/stb_include.h
vendored
Normal file
295
external/stb_include.h
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
// stb_include.h - v0.02 - parse and process #include directives - public domain
|
||||||
|
//
|
||||||
|
// To build this, in one source file that includes this file do
|
||||||
|
// #define STB_INCLUDE_IMPLEMENTATION
|
||||||
|
//
|
||||||
|
// This program parses a string and replaces lines of the form
|
||||||
|
// #include "foo"
|
||||||
|
// with the contents of a file named "foo". It also embeds the
|
||||||
|
// appropriate #line directives. Note that all include files must
|
||||||
|
// reside in the location specified in the path passed to the API;
|
||||||
|
// it does not check multiple directories.
|
||||||
|
//
|
||||||
|
// If the string contains a line of the form
|
||||||
|
// #inject
|
||||||
|
// then it will be replaced with the contents of the string 'inject' passed to the API.
|
||||||
|
//
|
||||||
|
// Options:
|
||||||
|
//
|
||||||
|
// Define STB_INCLUDE_LINE_GLSL to get GLSL-style #line directives
|
||||||
|
// which use numbers instead of filenames.
|
||||||
|
//
|
||||||
|
// Define STB_INCLUDE_LINE_NONE to disable output of #line directives.
|
||||||
|
//
|
||||||
|
// Standard libraries:
|
||||||
|
//
|
||||||
|
// stdio.h FILE, fopen, fclose, fseek, ftell
|
||||||
|
// stdlib.h malloc, realloc, free
|
||||||
|
// string.h strcpy, strncmp, memcpy
|
||||||
|
//
|
||||||
|
// Credits:
|
||||||
|
//
|
||||||
|
// Written by Sean Barrett.
|
||||||
|
//
|
||||||
|
// Fixes:
|
||||||
|
// Michal Klos
|
||||||
|
|
||||||
|
#ifndef STB_INCLUDE_STB_INCLUDE_H
|
||||||
|
#define STB_INCLUDE_STB_INCLUDE_H
|
||||||
|
|
||||||
|
// Do include-processing on the string 'str'. To free the return value, pass it to free()
|
||||||
|
char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
|
||||||
|
|
||||||
|
// Concatenate the strings 'strs' and do include-processing on the result. To free the return value, pass it to free()
|
||||||
|
char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
|
||||||
|
|
||||||
|
// Load the file 'filename' and do include-processing on the string therein. note that
|
||||||
|
// 'filename' is opened directly; 'path_to_includes' is not used. To free the return value, pass it to free()
|
||||||
|
char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef STB_INCLUDE_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char *stb_include_load_file(char *filename, size_t *plen)
|
||||||
|
{
|
||||||
|
char *text;
|
||||||
|
size_t len;
|
||||||
|
FILE *f = fopen(filename, "rb");
|
||||||
|
if (f == 0) return 0;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
len = (size_t) ftell(f);
|
||||||
|
if (plen) *plen = len;
|
||||||
|
text = (char *) malloc(len+1);
|
||||||
|
if (text == 0) return 0;
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
fread(text, 1, len, f);
|
||||||
|
fclose(f);
|
||||||
|
text[len] = 0;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int offset;
|
||||||
|
int end;
|
||||||
|
char *filename;
|
||||||
|
int next_line_after;
|
||||||
|
} include_info;
|
||||||
|
|
||||||
|
static include_info *stb_include_append_include(include_info *array, int len, int offset, int end, char *filename, int next_line)
|
||||||
|
{
|
||||||
|
include_info *z = (include_info *) realloc(array, sizeof(*z) * (len+1));
|
||||||
|
z[len].offset = offset;
|
||||||
|
z[len].end = end;
|
||||||
|
z[len].filename = filename;
|
||||||
|
z[len].next_line_after = next_line;
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stb_include_free_includes(include_info *array, int len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i=0; i < len; ++i)
|
||||||
|
free(array[i].filename);
|
||||||
|
free(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stb_include_isspace(int ch)
|
||||||
|
{
|
||||||
|
return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// find location of all #include and #inject
|
||||||
|
static int stb_include_find_includes(char *text, include_info **plist)
|
||||||
|
{
|
||||||
|
int line_count = 1;
|
||||||
|
int inc_count = 0;
|
||||||
|
char *s = text, *start;
|
||||||
|
include_info *list = NULL;
|
||||||
|
while (*s) {
|
||||||
|
// parse is always at start of line when we reach here
|
||||||
|
start = s;
|
||||||
|
while (*s == ' ' || *s == '\t')
|
||||||
|
++s;
|
||||||
|
if (*s == '#') {
|
||||||
|
++s;
|
||||||
|
while (*s == ' ' || *s == '\t')
|
||||||
|
++s;
|
||||||
|
if (0==strncmp(s, "include", 7) && stb_include_isspace(s[7])) {
|
||||||
|
s += 7;
|
||||||
|
while (*s == ' ' || *s == '\t')
|
||||||
|
++s;
|
||||||
|
if (*s == '"') {
|
||||||
|
char *t = ++s;
|
||||||
|
while (*t != '"' && *t != '\n' && *t != '\r' && *t != 0)
|
||||||
|
++t;
|
||||||
|
if (*t == '"') {
|
||||||
|
char *filename = (char *) malloc(t-s+1);
|
||||||
|
memcpy(filename, s, t-s);
|
||||||
|
filename[t-s] = 0;
|
||||||
|
s=t;
|
||||||
|
while (*s != '\r' && *s != '\n' && *s != 0)
|
||||||
|
++s;
|
||||||
|
// s points to the newline, so s-start is everything except the newline
|
||||||
|
list = stb_include_append_include(list, inc_count++, start-text, s-text, filename, line_count+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (0==strncmp(s, "inject", 6) && (stb_include_isspace(s[6]) || s[6]==0)) {
|
||||||
|
while (*s != '\r' && *s != '\n' && *s != 0)
|
||||||
|
++s;
|
||||||
|
list = stb_include_append_include(list, inc_count++, start-text, s-text, NULL, line_count+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (*s != '\r' && *s != '\n' && *s != 0)
|
||||||
|
++s;
|
||||||
|
if (*s == '\r' || *s == '\n') {
|
||||||
|
s = s + (s[0] + s[1] == '\r' + '\n' ? 2 : 1);
|
||||||
|
}
|
||||||
|
++line_count;
|
||||||
|
}
|
||||||
|
*plist = list;
|
||||||
|
return inc_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid dependency on sprintf()
|
||||||
|
static void stb_include_itoa(char str[9], int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i=0; i < 8; ++i)
|
||||||
|
str[i] = ' ';
|
||||||
|
str[i] = 0;
|
||||||
|
|
||||||
|
for (i=1; i < 8; ++i) {
|
||||||
|
str[7-i] = '0' + (n % 10);
|
||||||
|
n /= 10;
|
||||||
|
if (n == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *stb_include_append(char *str, size_t *curlen, char *addstr, size_t addlen)
|
||||||
|
{
|
||||||
|
str = (char *) realloc(str, *curlen + addlen);
|
||||||
|
memcpy(str + *curlen, addstr, addlen);
|
||||||
|
*curlen += addlen;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename, char error[256])
|
||||||
|
{
|
||||||
|
char temp[4096];
|
||||||
|
include_info *inc_list;
|
||||||
|
int i, num = stb_include_find_includes(str, &inc_list);
|
||||||
|
size_t source_len = strlen(str);
|
||||||
|
char *text=0;
|
||||||
|
size_t textlen=0, last=0;
|
||||||
|
for (i=0; i < num; ++i) {
|
||||||
|
text = stb_include_append(text, &textlen, str+last, inc_list[i].offset - last);
|
||||||
|
// write out line directive for the include
|
||||||
|
#ifndef STB_INCLUDE_LINE_NONE
|
||||||
|
#ifdef STB_INCLUDE_LINE_GLSL
|
||||||
|
if (textlen != 0) // GLSL #version must appear first, so don't put a #line at the top
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
strcpy(temp, "#line ");
|
||||||
|
stb_include_itoa(temp+6, 1);
|
||||||
|
strcat(temp, " ");
|
||||||
|
#ifdef STB_INCLUDE_LINE_GLSL
|
||||||
|
stb_include_itoa(temp+15, i+1);
|
||||||
|
#else
|
||||||
|
strcat(temp, "\"");
|
||||||
|
if (inc_list[i].filename == 0)
|
||||||
|
strcmp(temp, "INJECT");
|
||||||
|
else
|
||||||
|
strcat(temp, inc_list[i].filename);
|
||||||
|
strcat(temp, "\"");
|
||||||
|
#endif
|
||||||
|
strcat(temp, "\n");
|
||||||
|
text = stb_include_append(text, &textlen, temp, strlen(temp));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (inc_list[i].filename == 0) {
|
||||||
|
if (inject != 0)
|
||||||
|
text = stb_include_append(text, &textlen, inject, strlen(inject));
|
||||||
|
} else {
|
||||||
|
char *inc;
|
||||||
|
strcpy(temp, path_to_includes);
|
||||||
|
strcat(temp, "/");
|
||||||
|
strcat(temp, inc_list[i].filename);
|
||||||
|
inc = stb_include_file(temp, inject, path_to_includes, error);
|
||||||
|
if (inc == NULL) {
|
||||||
|
stb_include_free_includes(inc_list, num);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
text = stb_include_append(text, &textlen, inc, strlen(inc));
|
||||||
|
free(inc);
|
||||||
|
}
|
||||||
|
// write out line directive
|
||||||
|
#ifndef STB_INCLUDE_LINE_NONE
|
||||||
|
strcpy(temp, "\n#line ");
|
||||||
|
stb_include_itoa(temp+6, inc_list[i].next_line_after);
|
||||||
|
strcat(temp, " ");
|
||||||
|
#ifdef STB_INCLUDE_LINE_GLSL
|
||||||
|
stb_include_itoa(temp+15, 0);
|
||||||
|
#else
|
||||||
|
strcat(temp, filename != 0 ? filename : "source-file");
|
||||||
|
#endif
|
||||||
|
text = stb_include_append(text, &textlen, temp, strlen(temp));
|
||||||
|
// no newlines, because we kept the #include newlines, which will get appended next
|
||||||
|
#endif
|
||||||
|
last = inc_list[i].end;
|
||||||
|
}
|
||||||
|
text = stb_include_append(text, &textlen, str+last, source_len - last + 1); // append '\0'
|
||||||
|
stb_include_free_includes(inc_list, num);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename, char error[256])
|
||||||
|
{
|
||||||
|
char *text;
|
||||||
|
char *result;
|
||||||
|
int i;
|
||||||
|
size_t length=0;
|
||||||
|
for (i=0; i < count; ++i)
|
||||||
|
length += strlen(strs[i]);
|
||||||
|
text = (char *) malloc(length+1);
|
||||||
|
length = 0;
|
||||||
|
for (i=0; i < count; ++i) {
|
||||||
|
strcpy(text + length, strs[i]);
|
||||||
|
length += strlen(strs[i]);
|
||||||
|
}
|
||||||
|
result = stb_include_string(text, inject, path_to_includes, filename, error);
|
||||||
|
free(text);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256])
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
char *result;
|
||||||
|
char *text = stb_include_load_file(filename, &len);
|
||||||
|
if (text == NULL) {
|
||||||
|
strcpy(error, "Error: couldn't load '");
|
||||||
|
strcat(error, filename);
|
||||||
|
strcat(error, "'");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
result = stb_include_string(text, inject, path_to_includes, filename, error);
|
||||||
|
free(text);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // @TODO, GL_ARB_shader_language_include-style system that doesn't touch filesystem
|
||||||
|
char *stb_include_preloaded(char *str, char *inject, char *includes[][2], char error[256])
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // STB_INCLUDE_IMPLEMENTATION
|
||||||
2
external/stb_truetype.cpp
vendored
Normal file
2
external/stb_truetype.cpp
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
|
#include "stb_truetype.h"
|
||||||
5077
external/stb_truetype.h
vendored
Normal file
5077
external/stb_truetype.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
external/stb_vorbis.cpp
vendored
Normal file
2
external/stb_vorbis.cpp
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define STB_VORBIS_IMPLEMENTATION
|
||||||
|
#include "stb_vorbis.h"
|
||||||
5470
external/stb_vorbis.h
vendored
Normal file
5470
external/stb_vorbis.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
38
shaders/2d.frag
Normal file
38
shaders/2d.frag
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
in Fragment
|
||||||
|
{
|
||||||
|
// @Performance: Are this fields aligned to vec4? Is it better to reorder them?
|
||||||
|
vec2 position;
|
||||||
|
vec4 color;
|
||||||
|
vec2 uv;
|
||||||
|
} frag;
|
||||||
|
|
||||||
|
uniform int width; // Width of the screen in pixels
|
||||||
|
uniform int height; // Height of the screen in pixels
|
||||||
|
|
||||||
|
uniform int has_texture0;
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
uniform int texture_channels0;
|
||||||
|
uniform vec4 color0;
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
FragColor = frag.color;
|
||||||
|
|
||||||
|
if(has_texture0 != 0)
|
||||||
|
{
|
||||||
|
vec4 texture_color;
|
||||||
|
if(texture_channels0 == 1)
|
||||||
|
texture_color = vec4(1.0, 1.0, 1.0, texture(texture0, frag.uv).r);
|
||||||
|
else if(texture_channels0 == 3)
|
||||||
|
texture_color = vec4(texture(texture0, frag.uv).rgb, 1.0);
|
||||||
|
else
|
||||||
|
texture_color = texture(texture0, frag.uv);
|
||||||
|
|
||||||
|
FragColor = color0 * frag.color * texture_color;
|
||||||
|
FragColor.rgb *= FragColor.a; // Premultiplied alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
30
shaders/2d.vert
Normal file
30
shaders/2d.vert
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 position;
|
||||||
|
layout (location = 1) in vec4 color;
|
||||||
|
layout (location = 2) in vec2 uv;
|
||||||
|
|
||||||
|
out Fragment
|
||||||
|
{
|
||||||
|
// @Performance: Are this fields aligned to vec4? Is it better to reorder them?
|
||||||
|
vec2 position;
|
||||||
|
vec4 color;
|
||||||
|
vec2 uv;
|
||||||
|
} frag;
|
||||||
|
|
||||||
|
uniform int width; // Width of the screen in pixels
|
||||||
|
uniform int height; // Height of the screen in pixels
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
frag.position = position;
|
||||||
|
frag.color = color;
|
||||||
|
frag.uv = uv;
|
||||||
|
|
||||||
|
vec2 screen = vec2(width, height);
|
||||||
|
|
||||||
|
vec2 p = position / screen * 2.0 - 1.0; // Position relative to the screen size, in range [-1,+1)
|
||||||
|
p.y = -p.y; // OpenGL coordinates have y up, pixel coordinates have y down
|
||||||
|
|
||||||
|
gl_Position = vec4(p, 0.0, 1.0);
|
||||||
|
}
|
||||||
13
shaders/environment_map.frag
Normal file
13
shaders/environment_map.frag
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
in vec3 frag_position;
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
uniform samplerCube environment_map;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
FragColor = texture(environment_map, frag_position.xzy);
|
||||||
|
}
|
||||||
18
shaders/environment_map.vert
Normal file
18
shaders/environment_map.vert
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 position;
|
||||||
|
|
||||||
|
out vec3 frag_position;
|
||||||
|
|
||||||
|
uniform mat4 view_matrix;
|
||||||
|
uniform mat4 view_matrix_inverse;
|
||||||
|
uniform mat4 model_matrix;
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
frag_position = position;
|
||||||
|
vec4 world_position = model_matrix * vec4(position, 1.0);
|
||||||
|
gl_Position = view_matrix * world_position;
|
||||||
|
}
|
||||||
320
shaders/pbr.frag
Normal file
320
shaders/pbr.frag
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
const float PI = 3.141592653589793238462643383;
|
||||||
|
const float TAU = 6.283185307179586476925286766;
|
||||||
|
const float EPSILON = 0.000001;
|
||||||
|
|
||||||
|
in vec3 frag_position;
|
||||||
|
in vec3 frag_normal;
|
||||||
|
in vec3 frag_tangent;
|
||||||
|
in vec3 frag_bitangent;
|
||||||
|
in vec2 frag_texture_coord;
|
||||||
|
in vec4 view_position;
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
uniform int has_albedo_texture;
|
||||||
|
uniform sampler2D albedo_texture;
|
||||||
|
uniform vec4 albedo_factor;
|
||||||
|
uniform int has_metallic_texture;
|
||||||
|
uniform sampler2D metallic_texture;
|
||||||
|
uniform float metallic_factor;
|
||||||
|
uniform int has_roughness_texture;
|
||||||
|
uniform sampler2D roughness_texture;
|
||||||
|
uniform float roughness_factor;
|
||||||
|
uniform int has_normal_texture;
|
||||||
|
uniform sampler2D normal_texture;
|
||||||
|
uniform int has_emissive_texture;
|
||||||
|
uniform sampler2D emissive_texture;
|
||||||
|
uniform vec4 emissive_factor;
|
||||||
|
|
||||||
|
uniform mat4 view_matrix;
|
||||||
|
uniform mat4 view_matrix_inverse;
|
||||||
|
uniform mat4 model_matrix;
|
||||||
|
|
||||||
|
uniform int has_shadow_map;
|
||||||
|
uniform sampler2DShadow shadow_map;
|
||||||
|
uniform mat4 shadow_matrix;
|
||||||
|
|
||||||
|
uniform int has_environment_map;
|
||||||
|
uniform samplerCube environment_map;
|
||||||
|
|
||||||
|
|
||||||
|
struct SunLight
|
||||||
|
{
|
||||||
|
vec3 direction;
|
||||||
|
float _padding0;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
};
|
||||||
|
struct PointLight
|
||||||
|
{
|
||||||
|
vec3 position;
|
||||||
|
float _padding0;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
};
|
||||||
|
struct SpotLight
|
||||||
|
{
|
||||||
|
vec3 position;
|
||||||
|
float inner_radius;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
vec3 direction;
|
||||||
|
float outer_radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_SUN_LIGHTS 4
|
||||||
|
#define MAX_POINT_LIGHTS 128
|
||||||
|
#define MAX_SPOT_LIGHTS 128
|
||||||
|
layout (std140) uniform lights
|
||||||
|
{
|
||||||
|
uint sun_light_count;
|
||||||
|
uint point_light_count;
|
||||||
|
uint spot_light_count;
|
||||||
|
float ambient_light;
|
||||||
|
SunLight sun_lights[MAX_SUN_LIGHTS];
|
||||||
|
PointLight point_lights[MAX_POINT_LIGHTS];
|
||||||
|
SpotLight spot_lights[MAX_SPOT_LIGHTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialInfo
|
||||||
|
{
|
||||||
|
vec4 Albedo;
|
||||||
|
float Metallic;
|
||||||
|
float Roughness;
|
||||||
|
vec4 Emissive;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
uniform float width;
|
||||||
|
uniform float height;
|
||||||
|
|
||||||
|
|
||||||
|
float clamped_dot(vec3 v, vec3 w)
|
||||||
|
{
|
||||||
|
return max( dot(v, w) , 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 DiffuseBRDF(MaterialInfo material, vec3 L, vec3 V)
|
||||||
|
{
|
||||||
|
// Lambertian
|
||||||
|
return material.Albedo.xyz / PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SpecularNDF(MaterialInfo material, vec3 N, vec3 H)
|
||||||
|
{
|
||||||
|
// GGX
|
||||||
|
float a = material.Roughness * material.Roughness;
|
||||||
|
float a2 = a * a;
|
||||||
|
|
||||||
|
float n_dot_h = clamped_dot(N, H);
|
||||||
|
float n_dot_h2 = n_dot_h * n_dot_h;
|
||||||
|
|
||||||
|
float d = n_dot_h2 * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / (PI * d*d + EPSILON);
|
||||||
|
}
|
||||||
|
|
||||||
|
float SpecularGeometricAttenuation(MaterialInfo material, vec3 N, vec3 L, vec3 V, vec3 H)
|
||||||
|
{
|
||||||
|
// Schlick
|
||||||
|
float r = material.Roughness + 1;
|
||||||
|
float k = r*r / 8.0;
|
||||||
|
|
||||||
|
float n_dot_v = clamped_dot(N, V);
|
||||||
|
float n_dot_l = clamped_dot(N, L);
|
||||||
|
|
||||||
|
float denom_v = n_dot_v * (1.0 - k) + k;
|
||||||
|
float denom_l = n_dot_l * (1.0 - k) + k;
|
||||||
|
return (n_dot_v / denom_v) * (n_dot_l / denom_l);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 SpecularFresnel(vec3 F0, vec3 V, vec3 H)
|
||||||
|
{
|
||||||
|
// Schlick, Epic paper
|
||||||
|
float v_dot_h = clamped_dot(V, H);
|
||||||
|
float exp = (-5.55473 * v_dot_h - 6.98316) * v_dot_h;
|
||||||
|
return F0 + (1.0 - F0) * pow(2, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 BRDF(MaterialInfo material, vec3 radiance, vec3 N, vec3 L, vec3 V, vec3 H)
|
||||||
|
{
|
||||||
|
// Cook-Torrance
|
||||||
|
vec3 F0 = mix(vec3(0.04), material.Albedo.xyz, material.Metallic);
|
||||||
|
|
||||||
|
float D = SpecularNDF(material, N, H);
|
||||||
|
vec3 F = SpecularFresnel(F0, N, H);
|
||||||
|
float G = SpecularGeometricAttenuation(material, N, L, V, H);
|
||||||
|
|
||||||
|
float n_dot_v = clamped_dot(N, V);
|
||||||
|
float n_dot_l = clamped_dot(N, L);
|
||||||
|
|
||||||
|
float specular_denom = (4.0 * n_dot_l * n_dot_v) + EPSILON;
|
||||||
|
vec3 specular = D * F * G / specular_denom;
|
||||||
|
vec3 diffuse = (1.0 - F) * DiffuseBRDF(material, L, V);
|
||||||
|
|
||||||
|
return (diffuse + specular) * radiance * n_dot_l;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 final_color = vec4(0,0,0,1);
|
||||||
|
|
||||||
|
// PBR Parameters
|
||||||
|
MaterialInfo material;
|
||||||
|
material.Albedo = albedo_factor;
|
||||||
|
if(has_albedo_texture != 0)
|
||||||
|
material.Albedo = albedo_factor * texture(albedo_texture, frag_texture_coord);
|
||||||
|
|
||||||
|
material.Metallic = metallic_factor;
|
||||||
|
if(has_metallic_texture != 0)
|
||||||
|
material.Metallic = metallic_factor * texture(metallic_texture, frag_texture_coord).b;
|
||||||
|
|
||||||
|
material.Roughness = roughness_factor;
|
||||||
|
if(has_roughness_texture != 0)
|
||||||
|
material.Roughness = roughness_factor * texture(roughness_texture, frag_texture_coord).g;
|
||||||
|
|
||||||
|
material.Emissive = emissive_factor;
|
||||||
|
if(has_emissive_texture != 0)
|
||||||
|
material.Emissive = emissive_factor * texture(emissive_texture, frag_texture_coord);
|
||||||
|
|
||||||
|
vec3 Normal = normalize(frag_normal);
|
||||||
|
if(has_normal_texture != 0)
|
||||||
|
{
|
||||||
|
vec3 normal_map = normalize(texture(normal_texture, frag_texture_coord).rgb * 2.0 - 1.0);
|
||||||
|
mat3 TBN = mat3(normalize(frag_tangent), normalize(frag_bitangent), normalize(frag_normal));
|
||||||
|
Normal = normalize(TBN * normal_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec4 camera_position_4 = view_matrix_inverse * vec4(view_position.xy / view_position.w, 0, 1);
|
||||||
|
vec3 camera_position = camera_position_4.xyz / camera_position_4.w;
|
||||||
|
// vec4 pixel_position_4 = view_matrix_inverse * view_position;
|
||||||
|
// vec3 pixel_position = pixel_position_4.xyz / pixel_position_4.w;
|
||||||
|
// vec3 view_direction = normalize(pixel_position - camera_position);
|
||||||
|
vec3 view_direction = normalize(frag_position - camera_position);
|
||||||
|
vec3 V = -view_direction;
|
||||||
|
vec3 N = Normal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sun lights
|
||||||
|
for(uint i = 0; i < sun_light_count; i++)
|
||||||
|
{
|
||||||
|
SunLight light = sun_lights[i];
|
||||||
|
|
||||||
|
float shadow = 1.0;
|
||||||
|
if(has_shadow_map != 0)
|
||||||
|
{
|
||||||
|
shadow = 0;
|
||||||
|
vec2 texel_size = 1.0 / textureSize(shadow_map, 0);
|
||||||
|
float bias = 0.0001;
|
||||||
|
|
||||||
|
vec4 from_light_view = shadow_matrix * vec4(frag_position, 1.0);
|
||||||
|
from_light_view /= from_light_view.w;
|
||||||
|
|
||||||
|
from_light_view = from_light_view * 0.5 + 0.5; // [-1,1] => [0,1] uv coords
|
||||||
|
from_light_view.z = from_light_view.z - bias; // Bias to attenuate z fighting
|
||||||
|
|
||||||
|
for(int x = -1; x <= 1; x++)
|
||||||
|
for(int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
float s = texture(shadow_map, from_light_view.xyz + vec3(x * texel_size.x, y * texel_size.y, 0));
|
||||||
|
s = clamp(s, 0.0, 1.0);
|
||||||
|
shadow += s;
|
||||||
|
}
|
||||||
|
shadow /= 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 L = -light.direction;
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
|
||||||
|
vec3 radiance = light.color * light.intensity * shadow;
|
||||||
|
vec3 color = BRDF(material, radiance, N, L, V, H);
|
||||||
|
final_color.xyz += color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point lights
|
||||||
|
for(uint i = 0; i < point_light_count; i++)
|
||||||
|
{
|
||||||
|
PointLight light = point_lights[i];
|
||||||
|
|
||||||
|
vec3 diff = light.position - frag_position;
|
||||||
|
float dist2 = abs(dot(diff, diff));
|
||||||
|
float attenuation = 1.0 / (dist2 + EPSILON);
|
||||||
|
float intensity = light.intensity * attenuation;
|
||||||
|
|
||||||
|
vec3 L = normalize(light.position - frag_position);
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
|
||||||
|
vec3 radiance = light.color * intensity;
|
||||||
|
vec3 color = BRDF(material, radiance, N, L, V, H);
|
||||||
|
final_color.xyz += color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spot lights
|
||||||
|
for(uint i = 0; i < spot_light_count; i++)
|
||||||
|
{
|
||||||
|
SpotLight light = spot_lights[i];
|
||||||
|
|
||||||
|
vec3 light_direction = normalize(frag_position - light.position);
|
||||||
|
|
||||||
|
float dist2 = abs(dot(light.position, frag_position));
|
||||||
|
float attenuation = 1.0 / (dist2 + EPSILON);
|
||||||
|
float intensity = light.intensity * attenuation;
|
||||||
|
|
||||||
|
float angle = acos(dot(light.direction, light_direction));
|
||||||
|
float spot_factor = (angle - light.outer_radius) / (light.inner_radius - light.outer_radius);
|
||||||
|
spot_factor = clamp(spot_factor, 0.0, 1.0);
|
||||||
|
|
||||||
|
intensity *= spot_factor;
|
||||||
|
|
||||||
|
vec3 L = normalize(light.position - frag_position);
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
|
||||||
|
vec3 radiance = light.color * intensity;
|
||||||
|
vec3 color = BRDF(material, radiance, N, L, V, H);
|
||||||
|
final_color.xyz += color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment map light
|
||||||
|
if(has_environment_map != 0)
|
||||||
|
{
|
||||||
|
vec3 L = normalize(reflect(-V, N));
|
||||||
|
vec3 H = N;//normalize(V + L);
|
||||||
|
|
||||||
|
float levels = textureQueryLevels(environment_map);
|
||||||
|
vec3 radiance = textureLod(environment_map, L.xzy, material.Roughness * levels).rgb;
|
||||||
|
|
||||||
|
//vec3 color = BRDF(material, radiance, N, L, V, H);
|
||||||
|
vec3 F0 = mix(vec3(0.04), material.Albedo.xyz, material.Metallic);
|
||||||
|
|
||||||
|
float D = SpecularNDF(material, N, H);
|
||||||
|
vec3 F = SpecularFresnel(F0, N, H);
|
||||||
|
float G = SpecularGeometricAttenuation(material, N, L, V, H);
|
||||||
|
|
||||||
|
float n_dot_v = clamped_dot(N, V);
|
||||||
|
float n_dot_l = clamped_dot(N, L);
|
||||||
|
|
||||||
|
float specular_denom = (4.0 * n_dot_l * n_dot_v) + EPSILON;
|
||||||
|
vec3 specular = D * F * G / specular_denom;
|
||||||
|
vec3 diffuse = (1.0 - F) * DiffuseBRDF(material, L, V);
|
||||||
|
|
||||||
|
vec3 color = (diffuse /*+ specular*/) * radiance * n_dot_l;
|
||||||
|
|
||||||
|
final_color.xyz += color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambient light
|
||||||
|
final_color.xyz += ambient_light * material.Albedo.xyz;
|
||||||
|
|
||||||
|
final_color.a = material.Albedo.a;
|
||||||
|
|
||||||
|
// Emissive color
|
||||||
|
final_color.xyz += material.Emissive.xyz * material.Emissive.a;
|
||||||
|
|
||||||
|
FragColor = final_color;
|
||||||
|
//FragColor.xyz = Normal;
|
||||||
|
}
|
||||||
34
shaders/pbr.vert
Normal file
34
shaders/pbr.vert
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 position;
|
||||||
|
layout (location = 1) in vec3 normal;
|
||||||
|
layout (location = 2) in vec3 tangent;
|
||||||
|
layout (location = 3) in vec2 texture_coord;
|
||||||
|
|
||||||
|
out vec3 frag_position;
|
||||||
|
out vec3 frag_normal;
|
||||||
|
out vec3 frag_tangent;
|
||||||
|
out vec3 frag_bitangent;
|
||||||
|
out vec2 frag_texture_coord;
|
||||||
|
out vec4 view_position;
|
||||||
|
|
||||||
|
uniform mat4 view_matrix;
|
||||||
|
uniform mat4 view_matrix_inverse;
|
||||||
|
uniform mat4 model_matrix;
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
|
||||||
|
uniform mat4 shadow_matrix;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
mat3 model_inverse_matrix = mat3(transpose(inverse(model_matrix))); // @Performance: Compute this only once, before calling the shader
|
||||||
|
frag_normal = normalize(model_inverse_matrix * normal );
|
||||||
|
frag_tangent = normalize(model_inverse_matrix * tangent);
|
||||||
|
frag_bitangent = normalize(model_inverse_matrix * cross(normal, tangent));
|
||||||
|
vec4 world_position = model_matrix * vec4(position, 1.0);
|
||||||
|
frag_position = world_position.xyz / world_position.w;
|
||||||
|
frag_texture_coord = texture_coord;
|
||||||
|
gl_Position = view_matrix * world_position;
|
||||||
|
view_position = gl_Position;
|
||||||
|
}
|
||||||
23
shaders/postprocessing.frag
Normal file
23
shaders/postprocessing.frag
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
in vec2 frag_uv;
|
||||||
|
|
||||||
|
uniform sampler2D texture0; // rendered 3D
|
||||||
|
uniform sampler2D texture1; // rendered HUD
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 hdr_color = texture(texture0, frag_uv).rgb;
|
||||||
|
|
||||||
|
// HDR - TODO
|
||||||
|
vec3 sdr_color = min(hdr_color, 1.0);
|
||||||
|
|
||||||
|
|
||||||
|
vec4 hud_color = texture(texture1, frag_uv);
|
||||||
|
|
||||||
|
vec3 mixed = mix(sdr_color, hud_color.rgb, hud_color.a); // TODO: SDR color
|
||||||
|
FragColor = vec4(mixed, 1.0);
|
||||||
|
}
|
||||||
11
shaders/postprocessing.vert
Normal file
11
shaders/postprocessing.vert
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 position;
|
||||||
|
layout (location = 1) in vec2 uv;
|
||||||
|
out vec2 frag_uv;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
frag_uv = uv;
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
5
shaders/shadow_map.frag
Normal file
5
shaders/shadow_map.frag
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
}
|
||||||
1
shaders/shadow_map.vert
Normal file
1
shaders/shadow_map.vert
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "pbr.vert"
|
||||||
Reference in New Issue
Block a user