aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-02-29 03:05:36 +0100
committerdec05eba <dec05eba@protonmail.com>2021-11-18 15:22:10 +0100
commit12c36c61c3f8d19c44cb2e5ffdf3ed812a0390d2 (patch)
tree5c45ed12702335ba5a30706ba413daab95995af9
parentc3453fedbd270afe8d9dfc7f288ea7205f029b86 (diff)
Implement .a3d model loader (amalgine specific format)
-rw-r--r--README.md8
-rw-r--r--include/ModelLoader.hpp15
-rw-r--r--include/model_loader/ObjModelLoader.hpp2
-rw-r--r--src/ModelLoader.cpp230
-rw-r--r--src/main.cpp9
-rw-r--r--src/model_loader/ObjModelLoader.cpp5
6 files changed, 261 insertions, 8 deletions
diff --git a/README.md b/README.md
index 4261947..22c10bf 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
-## Prerequisite
+# Amalgine
+## Models
+Use https://gitlab.com/DEC05EBA/amalgine-model-converter to convert a 3d model to a format that amalgine can use.
+
+# Prerequisite
`glfw3`, `glew`, `glm`, [sibs](https://github.com/DEC05EBA/sibs) and a C++14 compiler.
# TODO
* Make use shader a no-op if already in use.
-* Render vertices using glDrawElements instead of glDrawArrays.
+* Render vertices using glDrawElements instead of glDrawArrays (support rendering using indices to vertices/normals/texcoords).
* Use opencl with bullet instead of using cpu.
* Remove obj loader and create an external program that uses assimp to convert different models to an amalgine specific binary file format.
This will allow amalgine to easily load models and animations with minimal code, no matter what the original format is.
diff --git a/include/ModelLoader.hpp b/include/ModelLoader.hpp
new file mode 100644
index 0000000..bdbc458
--- /dev/null
+++ b/include/ModelLoader.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Triangle.hpp"
+#include "Vec.hpp"
+#include <vector>
+
+namespace amalgine {
+ class Image;
+
+ // A model loader for .a3d files
+ class ModelLoader {
+ public:
+ static bool load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image);
+ };
+} \ No newline at end of file
diff --git a/include/model_loader/ObjModelLoader.hpp b/include/model_loader/ObjModelLoader.hpp
index 5078244..1020348 100644
--- a/include/model_loader/ObjModelLoader.hpp
+++ b/include/model_loader/ObjModelLoader.hpp
@@ -9,6 +9,6 @@ namespace amalgine {
class ObjModelLoader {
public:
- static void load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image);
+ static bool load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image);
};
} \ No newline at end of file
diff --git a/src/ModelLoader.cpp b/src/ModelLoader.cpp
new file mode 100644
index 0000000..60626f1
--- /dev/null
+++ b/src/ModelLoader.cpp
@@ -0,0 +1,230 @@
+#include "../include/ModelLoader.hpp"
+#include "../include/File.hpp"
+#include "../include/Image.hpp"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <libgen.h>
+#include <string>
+
+namespace amalgine {
+ class ModelLoaderInternal {
+ public:
+ ModelLoaderInternal(const char *data, size_t size) : data(data), size(size), offset(0) {}
+
+ template <typename T>
+ bool extract(T *result) {
+ if(size - offset >= sizeof(T)) {
+ // This is fine, because all data in .a3d files are aligned correctly.
+ // However this could be incorrect if the file is not an .a3d file, in which case this would crash on ARM processors for example..
+ // TODO: Use memcpy on ARM (and other platforms that dont support unaligned memory access)
+ *result = *(T*)(data + offset);
+ offset += sizeof(T);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ template <typename T>
+ bool extract_list(T *result, size_t num_items) {
+ const size_t bytes_to_extract = sizeof(T) * num_items;
+ if(size - offset >= bytes_to_extract) {
+ memcpy(result, data + offset, bytes_to_extract);
+ offset += bytes_to_extract;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ std::string extract_null_terminated_string() {
+ std::string result;
+ const char *p = data + offset;
+ while(true) {
+ char c = *p++;
+ if(c == '\0')
+ break;
+ else
+ result += c;
+ }
+ offset += p - (data + offset);
+ return result;
+ }
+
+ bool skip(size_t num_bytes) {
+ if(size - offset >= num_bytes) {
+ offset += num_bytes;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool empty() const { return offset == size; }
+ private:
+ const char *data;
+ const size_t size;
+ size_t offset;
+ };
+
+ struct Face {
+ unsigned int index[3];
+ };
+
+ // TODO: Check bounds of indices and size of data before allocating
+ static bool model_loader_version_1(ModelLoaderInternal &model_loader, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image, const std::string &dir_path) {
+ // TODO: Remove these temporary variables when amalgine has been modified to use indices instead of
+ // data directly in shaders
+ std::vector<Triangle3D> temp_vertices;
+ std::vector<vec2f> temp_texcoords;
+ std::vector<Face> indices;
+ while(!model_loader.empty()) {
+ // TODO: Verify num_vertices isn't too large?
+ unsigned int num_vertices;
+ if(!model_loader.extract<unsigned int>(&num_vertices) || num_vertices == 0) {
+ fprintf(stderr, "Error: failed to extract num_vertices\n");
+ return false;
+ }
+
+ unsigned int num_triangles = num_vertices / 3;
+ temp_vertices.resize(num_triangles);
+ if(!model_loader.extract_list(&temp_vertices[0], num_triangles)) {
+ fprintf(stderr, "Error: failed to extract vertices\n");
+ return false;
+ }
+
+ if(!model_loader.skip(num_vertices * sizeof(vec3f))) {
+ fprintf(stderr, "Error: failed to extract normals\n");
+ return false;
+ }
+
+ unsigned int has_texcoords;
+ if(!model_loader.extract<unsigned int>(&has_texcoords) || has_texcoords > 1) {
+ fprintf(stderr, "Error: failed to extract has_texcoords\n");
+ return false;
+ }
+
+ if(has_texcoords) {
+ temp_texcoords.resize(num_vertices);
+ if(!model_loader.extract_list(&temp_texcoords[0], num_vertices)) {
+ fprintf(stderr, "Error: failed to extract texture coordinates\n");
+ return false;
+ }
+ }
+
+ unsigned int num_faces;
+ if(!model_loader.extract<unsigned int>(&num_faces)) {
+ fprintf(stderr, "Error: failed to extract num_faces\n");
+ return false;
+ }
+
+ indices.resize(num_faces);
+ if(!model_loader.extract_list(&indices[0], num_faces)) {
+ fprintf(stderr, "Error: failed to extract face indices\n");
+ return false;
+ }
+
+ unsigned int triangles_offset = triangles.size();
+ triangles.resize(triangles.size() + indices.size());
+ const Vertex3D *temp_vertices_begin = &temp_vertices[0].p1;
+ unsigned int max_vertex_index = (unsigned int)(temp_vertices.size() - 1) * 3;
+ for(unsigned int i = 0; i < indices.size(); ++i) {
+ triangles[triangles_offset + i].p1 = temp_vertices_begin[std::min(indices[i].index[0], max_vertex_index)];
+ triangles[triangles_offset + i].p2 = temp_vertices_begin[std::min(indices[i].index[1], max_vertex_index)];
+ triangles[triangles_offset + i].p3 = temp_vertices_begin[std::min(indices[i].index[2], max_vertex_index)];
+ }
+
+ unsigned int texcoords_offset = texture_coords.size();
+ texture_coords.resize(texture_coords.size() + indices.size() * 3);
+ if(has_texcoords) {
+ unsigned int max_texcoord_index = (unsigned int)temp_texcoords.size() - 1;
+ for(unsigned int i = 0; i < indices.size(); ++i) {
+ texture_coords[texcoords_offset + 0] = temp_texcoords[std::min(indices[i].index[0], max_texcoord_index)];
+ texture_coords[texcoords_offset + 1] = temp_texcoords[std::min(indices[i].index[1], max_texcoord_index)];
+ texture_coords[texcoords_offset + 2] = temp_texcoords[std::min(indices[i].index[2], max_texcoord_index)];
+ texcoords_offset += 3;
+ }
+ } else {
+ for(unsigned int i = 0; i < indices.size(); ++i) {
+ // TODO: is this needed even with the resize above? or use memset?
+ texture_coords[texcoords_offset + i + 0] = { 0.0f, 0.0f };
+ texture_coords[texcoords_offset + i + 1] = { 0.0f, 0.0f };
+ texture_coords[texcoords_offset + i + 2] = { 0.0f, 0.0f };
+ }
+ }
+
+ unsigned int has_materials;
+ if(!model_loader.extract<unsigned int>(&has_materials) || has_materials > 1) {
+ fprintf(stderr, "Error: failed to extract has_materials\n");
+ return false;
+ }
+
+ if(has_materials) {
+ for(unsigned int i = 0; i < 3; ++i) {
+ unsigned int num_textures;
+ if(!model_loader.extract<unsigned int>(&num_textures)) {
+ fprintf(stderr, "Error: failed to extract num_textures\n");
+ return false;
+ }
+
+ for(unsigned int j = 0; j < num_textures; ++j) {
+ std::string texture_name = model_loader.extract_null_terminated_string();
+ fprintf(stderr, "texture name: %s\n", texture_name.c_str());
+ std::string texture_path = dir_path + "/" + texture_name;
+ assert(!*image); // TODO: Support multiple images
+ Result<Image*> image_result = Image::loadFromFile(texture_path.c_str());
+ if(!image_result) {
+ fprintf(stderr, "%s\n", image_result.getErrorMsg().c_str());
+ return false;
+ }
+ *image = image_result.unwrap();
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ // TODO: Instead of reading all file data to a buffer and then copying it, maybe read from file
+ // directly into data structures?
+ bool ModelLoader::load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image) {
+ triangles.clear();
+ texture_coords.clear();
+ *image = nullptr;
+ bool result = true;
+
+ size_t file_size;
+ char *file_data = file_get_content(filepath, &file_size);
+ if(!file_data)
+ return false;
+
+ std::string dir_path = filepath;
+ dirname(&dir_path[0]);
+ const int dir_path_size = strlen(dir_path.c_str());
+ dir_path.resize(dir_path_size);
+
+ ModelLoaderInternal model_loader(file_data, file_size);
+
+ unsigned int magic_number;
+ unsigned int version = 0;
+
+ if(!model_loader.extract<unsigned int>(&magic_number) || magic_number != 0x036144AF) {
+ fprintf(stderr, "Error: file %s is not an .a3d file (magic number not found)\n", filepath);
+ result = false;
+ goto cleanup;
+ }
+
+ if(model_loader.extract<unsigned int>(&version) && version == 1) {
+ result = model_loader_version_1(model_loader, triangles, texture_coords, image, dir_path);
+ } else {
+ fprintf(stderr, "Error: version not supported: %u\n", version);
+ result = false;
+ }
+
+ cleanup:
+ free(file_data);
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index f2b2c3f..7cefb17 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -8,6 +8,7 @@
#include "../include/ThirdPersonCamera.hpp"
#include "../include/model_loader/ObjModelLoader.hpp"
+#include "../include/ModelLoader.hpp"
#include <cstdio>
#include <chrono>
@@ -96,7 +97,7 @@ int main() {
std::unique_ptr<Shader> vertex_shader = load_shader_from_file("shaders/vertex.vert", Shader::Type::VERTEX);
std::unique_ptr<Shader> pixel_shader = load_shader_from_file("shaders/fragment.frag", Shader::Type::PIXEL);
std::unique_ptr<ShaderProgram> shader_program = build_shader_program_from_shaders({ vertex_shader.get(), pixel_shader.get() });
- Model buddha_model = load_model("/home/dec05eba/Downloads/ELI4OS.obj", shader_program.get());
+ Model buddha_model = load_model("/home/dec05eba/Downloads/ELI4OS.a3d", shader_program.get());
buddha_model.frame.get_uniform_by_name("proj")->set(proj);
Result<Uniform> view_uniform = buddha_model.frame.get_uniform_by_name("view");
Result<Uniform> model_uniform = buddha_model.frame.get_uniform_by_name("model");
@@ -104,7 +105,7 @@ int main() {
std::unique_ptr<Shader> vertex_no_texture_shader = load_shader_from_file("shaders/vertex_no_texture.vert", Shader::Type::VERTEX);
std::unique_ptr<Shader> pixel_no_texture_shader = load_shader_from_file("shaders/fragment_no_texture.frag", Shader::Type::PIXEL);
std::unique_ptr<ShaderProgram> no_texture_shader_program = build_shader_program_from_shaders({ vertex_no_texture_shader.get(), pixel_no_texture_shader.get() });
- Model character_model = load_model("/home/dec05eba/Downloads/FinalBaseMesh.obj", no_texture_shader_program.get());
+ Model character_model = load_model("/home/dec05eba/Downloads/FinalBaseMesh.a3d", no_texture_shader_program.get());
character_model.frame.get_uniform_by_name("proj")->set(proj);
Result<Uniform> char_view_uniform = character_model.frame.get_uniform_by_name("view");
Result<Uniform> char_model_uniform = character_model.frame.get_uniform_by_name("model");
@@ -260,7 +261,9 @@ Model load_model(const char *filepath, ShaderProgram *shader_program) {
std::vector<Triangle3D> triangles;
std::vector<vec2f> texture_coords;
Image *image;
- ObjModelLoader::load_from_file(filepath, triangles, texture_coords, &image);
+ if(!ModelLoader::load_from_file(filepath, triangles, texture_coords, &image)) {
+ fprintf(stderr, "Error: failed to load model from file: %s\n", filepath);
+ }
// TODO: This needs to be done to prevent crash in glDrawArrays, but this should not be used when shader doesn't handle any texture
//if(texture_coords.size() < triangles.size())
// texture_coords.resize(triangles.size());
diff --git a/src/model_loader/ObjModelLoader.cpp b/src/model_loader/ObjModelLoader.cpp
index 7976bc9..7c4ee5c 100644
--- a/src/model_loader/ObjModelLoader.cpp
+++ b/src/model_loader/ObjModelLoader.cpp
@@ -101,7 +101,7 @@ namespace amalgine {
static Result<Material> load_material_from_file(const char *filepath);
- void ObjModelLoader::load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image) {
+ bool ObjModelLoader::load_from_file(const char *filepath, std::vector<Triangle3D> &triangles, std::vector<vec2f> &texture_coords, Image **image) {
triangles.clear();
texture_coords.clear();
*image = nullptr;
@@ -109,7 +109,7 @@ namespace amalgine {
size_t file_size;
char *file_data = file_get_content(filepath, &file_size);
if(!file_data)
- return;
+ return false;
std::string dir_path = filepath;
dirname(&dir_path[0]);
@@ -238,6 +238,7 @@ namespace amalgine {
cleanup:
free(file_data);
+ return true;
}
Result<Material> load_material_from_file(const char *filepath) {