A small OpenGL 3+ renderer and game engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

430 lines
11 KiB

#include <cassert>
#include <cstring>
#define GLM_FORCE_XYZW_ONLY
#include <glm/ext/matrix_transform.hpp>
#include "tiny_gltf.h"
#include "asset.h"
#include "dumbLog.h"
// forward declarations
// TODO: move to GLDebug.h?
void dumpNodes(tinygltf::Model t_mdl);
bool parseMeshNode(Mesh* m,
MemoryArena* arena,
const tinygltf::Node& node,
const tinygltf::Model& t_mdl);
Model* getCachedModel(Assets* assets, u64 path_hash);
Model* loadModelFile(Assets* assets, const char* filename);
Texture* getCachedTexture(Assets* assets, u64 path_hash);
// interface
Mesh*
meshInit(MemoryArena* arena,
u32 num_vertices,
u32 num_indices,
bool use_normals,
bool use_colors,
bool use_uvs)
{
Mesh* m = ARENA_ALLOC(arena, Mesh, 1);
m->num_vertices = num_vertices;
m->num_indices = num_indices;
m->vertices = ARENA_ALLOC(arena, vec3, num_vertices);
m->indices = ARENA_ALLOC(arena, u16, num_indices);
if (use_normals)
m->normals = ARENA_ALLOC(arena, vec3, num_vertices);
if (use_colors)
m->colors = ARENA_ALLOC(arena, vec3, num_vertices);
if (use_uvs)
m->uvs = ARENA_ALLOC(arena, vec2, num_vertices);
m->xform = ARENA_ALLOC(arena, mat4, 1);
*m->xform = mat4(1);
return m;
}
Model*
modelInitManual(MemoryArena* arena, u32 num_meshes, Mesh* meshes)
{
Model* mdl = ARENA_ALLOC(arena, Model, 1);
mdl->meshes = meshes; // NOTE: defaults to nullptr
mdl->num_meshes = num_meshes;
return mdl;
}
Model*
getModelByPath(Assets* assets, const char* filepath)
{
Model* mdl = getCachedModel(assets, utilFNV64a_str(filepath));
if (!mdl)
mdl = loadModelFile(assets, filepath);
return mdl;
}
Texture*
getTextureByPath(Assets* assets, const char* filepath)
{
Texture* texture =
getCachedTexture(assets, utilFNV64a_str(filepath));
// NOTE: the texture should be loaded when the model is loaded, so it's an
// error if we don't find it in cache
if (!texture) {
LOG(Error) << "texture file, " << filepath << " not loaded\n";
return nullptr;
}
return texture;
}
// internal
Model*
initModel(Assets* assets,
tinygltf::Model t_mdl,
const char* filename)
{
// TODO: re-alloc array when out of space
assert(assets->num_models < assets->max_models && assets->arena != nullptr);
Model* mdl = &assets->models[assets->num_models];
assets->num_models++;
uint buf_count = t_mdl.bufferViews.size();
mdl->meshes = ARENA_ALLOC(assets->arena, Mesh, buf_count);
mdl->num_meshes = t_mdl.meshes.size();
mdl->filepath = arenaCopyCStr(assets->arena, filename, MAX_PATH_SIZE);
mdl->filepath_hash = utilFNV64a_str(mdl->filepath);
return mdl;
}
Texture*
getFreeTexture(Assets* assets)
{
if (assets->num_textures < assets->max_textures)
return &assets->textures[assets->num_textures++];
LOGF(Error, "no free textures\n");
return nullptr;
}
Texture*
copyDiffuseTexture(Assets* assets, const tinygltf::Model& t_mdl)
{
// NOTE: assuming material[0] since we're using pallete texture
assert(t_mdl.materials.size() == 1
&& t_mdl.textures.size() == 1
&& t_mdl.images.size() == 1
&& t_mdl.images[0].image.size() > 0);
tinygltf::Image t_img = t_mdl.images[0];
Texture* dtex = getFreeTexture(assets);
if (!dtex) {
LOG(Error) << "Error Loading diffuse texture\n";
// TODO: reclaim arena memory
return nullptr;
}
dtex->w = t_img.width;
dtex->h = t_img.height;
dtex->bits_per_channel = t_img.bits;
dtex->num_channels = t_img.component;
dtex->data_len = t_img.image.size();
dtex->pixels = ARENA_ALLOC(assets->arena, u8, dtex->data_len);
std::strncpy(dtex->file_path, t_img.uri.c_str(), sizeof(dtex->file_path));
dtex->filepath_hash = utilFNV64a_str(t_img.uri.c_str());
std::memcpy(dtex->pixels, t_img.image.data(), dtex->data_len);
return dtex;
}
Model*
loadModelFile(Assets* assets, const char* filename)
{
tinygltf::Model t_mdl;
tinygltf::TinyGLTF gltf_ctx;
std::string err;
std::string warn;
LOG(Info) << "Loading model: " << filename << "\n";
if (!gltf_ctx.LoadASCIIFromFile(&t_mdl, &err, &warn, filename)) {
LOG(Error) << "Error loading file: " << filename
<< " , msg: " << err << "\n";
return nullptr;
}
#if 0
dumpNodes(t_mdl);
#endif
// NOTE: assume we're working with a single buffer
assert(t_mdl.buffers.size() == 1);
Model* mdl = initModel(assets, t_mdl, filename);
mdl->diffuse_texture = copyDiffuseTexture(assets, t_mdl);
if (mdl->diffuse_texture == nullptr) return nullptr;
uint mesh_idx = 0;
for (tinygltf::Node node : t_mdl.nodes) {
if (node.mesh >= 0) {
if (!parseMeshNode(&mdl->meshes[mesh_idx++],
assets->arena,
node,
t_mdl))
{
LOG(Error) << "Error parsing node\n";
return nullptr;
}
}
}
return mdl;
}
Model*
getCachedModel(Assets* assets, u64 path_hash)
{
for (u32 i = 0; i < assets->num_models; i++) {
if (assets->models[i].filepath_hash == path_hash)
return &assets->models[i];
}
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n";
return nullptr;
}
Texture*
getCachedTexture(Assets* assets, u64 path_hash)
{
for (u32 i = 0; i < assets->num_textures; i++) {
if (assets->textures[i].filepath_hash == path_hash)
return &assets->textures[i];
}
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n";
return nullptr;
}
bool
copyBuffer(uint8_t*& buffer_ref,
MemoryArena* arena,
int acc_idx,
const tinygltf::Model& t_mdl)
{
const tinygltf::Accessor& acc = t_mdl.accessors[acc_idx];
const tinygltf::BufferView& bv = t_mdl.bufferViews[acc.bufferView];
const tinygltf::Buffer& t_buf = t_mdl.buffers[bv.buffer];
// TODO: clean up validation
if (bv.target == TINYGLTF_TARGET_ARRAY_BUFFER) {
assert(acc.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT
&& (acc.type == TINYGLTF_TYPE_VEC3
|| acc.type == TINYGLTF_TYPE_VEC2));
} else if (bv.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
assert(acc.type == TINYGLTF_TYPE_SCALAR
&& acc.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT);
} else {
LOG(Error) << "unknown target\n";
return false;
}
assert(bv.byteStride == 0);
buffer_ref = ARENA_ALLOC(arena, u8, bv.byteLength);
std::memcpy(buffer_ref, &t_buf.data[bv.byteOffset], bv.byteLength);
return buffer_ref != nullptr;
}
// FIXME: need to implement tree structure for blender models to work properly
mat4*
parseNodeTransform(MemoryArena* arena, const tinygltf::Node* node)
{
if (node->rotation.size() == 4
&& node->scale.size() == 3
&& node->translation.size() == 3)
{
mat4* xform = ARENA_ALLOC(arena, mat4, 1);
*xform = mat4(1.0f);
*xform = glm::rotate(*xform, (float) node->rotation[3],
vec3((float) node->rotation[0],
(float) node->rotation[1],
(float) node->rotation[2])
);
*xform = glm::scale(*xform,
vec3((float) node->scale[0],
(float) node->scale[0],
(float) node->scale[0])
);
*xform = glm::translate(*xform,
vec3((float) node->translation[0],
(float) node->translation[0],
(float) node->translation[0])
);
return xform;
}
LOG(Error) << "Unknown transform\n";
return nullptr;
}
bool
parseMeshNode(Mesh* m,
MemoryArena* arena,
const tinygltf::Node& node,
const tinygltf::Model& t_mdl)
{
tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh];
// NOTE: assume only 1 primitive object per mesh
assert(t_mdl.meshes[node.mesh].primitives.size() == 1);
tinygltf::Primitive prim = t_mesh.primitives[0];
// NOTE: verify assumptions about input
assert(prim.attributes.find("POSITION") != prim.attributes.end()
&& prim.attributes.find("NORMAL") != prim.attributes.end()
&& prim.attributes.find("TEXCOORD_0") != prim.attributes.end()
&& prim.indices >= 0);
const tinygltf::Accessor& vert_acc =
t_mdl.accessors[prim.attributes["POSITION"]];
const tinygltf::Accessor& index_acc = t_mdl.accessors[prim.indices];
m->num_vertices = vert_acc.count;
m->num_indices = index_acc.count;
// FIXME: the node transforms from blender only work as part of a node tree
#if 1
m->xform = ARENA_ALLOC(arena, mat4, 1);
*m->xform = mat4(1.0f);
#else
m->xform = parseNodeTransform(arena, &node);
#endif
if (m->xform != nullptr
&& copyBuffer((uint8_t*&) m->vertices, arena,
prim.attributes["POSITION"], t_mdl)
&& copyBuffer((uint8_t*&) m->normals, arena,
prim.attributes["NORMAL"], t_mdl)
&& copyBuffer((uint8_t*&) m->uvs, arena,
prim.attributes["TEXCOORD_0"], t_mdl)
&& copyBuffer((uint8_t*&) m->indices, arena,
prim.indices, t_mdl))
{
return true;
}
return false;
}
const char*
getTargetStr(int target)
{
switch (target) {
case 34962: return "GL_ARRAY_BUFFER";
case 34963: return "GL_ELEMENT_ARRAY_BUFFER";
default: return "UNKNOWN";
}
}
const char*
getElementType(int type)
{
switch (type) {
case 2: return "TINYGLTF_TYPE_VEC2";
case 3: return "TINYGLTF_TYPE_VEC3";
case 4: return "TINYGLTF_TYPE_VEC4";
case 65: return "TINYGLTF_TYPE_SCALAR";
default: return "UNKNOWN";
}
}
const char*
getComponentType(int componentType)
{
switch (componentType) {
case 5123: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT";
case 5124: return "TINYGLTF_COMPONENT_TYPE_INT";
case 5125: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT";
case 5126: return "TINYGLTF_COMPONENT_TYPE_FLOAT";
case 5130: return "TINYGLTF_COMPONENT_TYPE_DOUBLE";
default: return "UNKOWN"; }
}
const char*
getDrawMode(int drawMode)
{
switch (drawMode) {
case 0: return "TINYGLTF_MODE_POINTS";
case 1: return "TINYGLTF_MODE_LINE";
case 2: return "TINYGLTF_MODE_LINE_LOOP";
case 3: return "TINYGLTF_MODE_LINE_STRIP";
case 4: return "TINYGLTF_MODE_TRIANGLES";
case 5: return "TINYGLTF_MODE_TRIANGLE_STRIP";
case 6: return "TINYGLTF_MODE_TRIANGLE_FAN";
default: return "UNKOWN MODE";
}
}
void
dumpBuffer(tinygltf::Model mdl, tinygltf::Accessor acc)
{
size_t bv_idx = acc.bufferView;
assert(bv_idx >= 0 && bv_idx < mdl.bufferViews.size());
tinygltf::BufferView bv = mdl.bufferViews[bv_idx];
LOG(Debug) << "-----------------------\n";
LOG(Debug) << "buf idx: " << bv_idx << "\n";
LOG(Debug) << "buf target: " << getTargetStr(bv.target) << "\n";
LOG(Debug) << "buf len: " << bv.byteLength << "\n";
LOG(Debug) << "buf offset: " << bv.byteOffset << "\n";
LOG(Debug) << "buf stride: " << bv.byteStride << "\n";
LOG(Debug) << "acc component type: "
<< getComponentType(acc.componentType) << "\n";
LOG(Debug) << "acc element type: "
<< getElementType(acc.type) << "\n";
}
void
dumpNodes(tinygltf::Model t_mdl)
{
for (tinygltf::Node node : t_mdl.nodes) {
LOG(Debug) << "##################\n";
LOG(Debug) << "node name: " << node.name << "\n";
LOG(Debug) << "node mesh idx: " << node.mesh << "\n";
if (node.mesh >= 0) {
tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh];
LOG(Debug) << "node mesh name: " << t_mesh.name << "\n";
// NOTE: assume only 1 primitive object per mesh
assert(t_mdl.meshes[node.mesh].primitives.size() == 1);
tinygltf::Primitive prim = t_mesh.primitives[0];
LOG(Debug) << "node draw mode: " << getDrawMode(prim.mode) << "\n";
for (auto& att : prim.attributes) {
LOG(Debug) << "dumping buffer: " << att.first << "\n";
dumpBuffer(t_mdl, t_mdl.accessors[att.second]);
}
LOG(Debug) << "dumping index buffer\n";
dumpBuffer(t_mdl, t_mdl.accessors[prim.indices]);
} else {
LOG(Debug) << "Not a mesh node\n";
}
}
}