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
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"; |
|
} |
|
} |
|
} |
|
|
|
|