💿🐜 Antkeeper source code https://antkeeper.com
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.

289 lines
9.8 KiB

/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resources/resource-loader.hpp"
#include "resources/resource-manager.hpp"
#include "render/model.hpp"
#include "render/vertex-attribute.hpp"
#include "gl/vertex-attribute.hpp"
#include "gl/drawing-mode.hpp"
#include "utility/fundamental-types.hpp"
#include "math/constants.hpp"
#include <sstream>
#include <stdexcept>
#include <limits>
#include <physfs.h>
#include <nlohmann/json.hpp>
static const float3 barycentric_coords[3] =
{
float3{1, 0, 0},
float3{0, 1, 0},
float3{0, 0, 1}
};
template <>
render::model* resource_loader<render::model>::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path)
{
// Read file into buffer
std::size_t size = static_cast<int>(PHYSFS_fileLength(file));
std::vector<std::uint8_t> buffer(size);
PHYSFS_readBytes(file, &buffer.front(), size);
// Parse CBOR in file buffer
nlohmann::json json = nlohmann::json::from_cbor(buffer);
// Find model name
std::string model_name;
if (auto it = json.find("name"); it != json.end())
model_name = it.value();
// Load attributes
std::unordered_map<std::string, std::tuple<std::size_t, std::vector<float>>> attributes;
if (auto attributes_node = json.find("attributes"); attributes_node != json.end())
{
for (const auto& attribute_node: attributes_node.value().items())
{
// Look up attribute name
std::string attribute_name;
if (auto type_node = attribute_node.value().find("name"); type_node != attribute_node.value().end())
attribute_name = type_node.value().get<std::string>();
// Allocate attribute in attribute map
auto& attribute = attributes[attribute_name];
std::size_t& attribute_size = std::get<0>(attribute);
std::vector<float>& attribute_data = std::get<1>(attribute);
// Look up attribute size (per vertex)
attribute_size = 0;
if (auto size_node = attribute_node.value().find("size"); size_node != attribute_node.value().end())
attribute_size = size_node.value().get<std::size_t>();
// Look up attribute data
if (auto data_node = attribute_node.value().find("data"); data_node != attribute_node.value().end())
{
// Resize attribute data
attribute_data.resize(data_node.value().size());
// Fill attribute data
float* v = &attribute_data.front();
for (const auto& element: data_node.value())
*(v++) = element.get<float>();
}
}
}
// Load bounds
geom::aabb<float> bounds =
{
{std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity()},
{-std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity()}
};
if (auto bounds_node = json.find("bounds"); bounds_node != json.end())
{
if (auto min_node = bounds_node.value().find("min"); min_node != bounds_node.value().end())
{
float* v = &bounds.min_point.x;
for (const auto& element: min_node.value())
*(v++) = element.get<float>();
}
if (auto max_node = bounds_node.value().find("max"); max_node != bounds_node.value().end())
{
float* v = &bounds.max_point.x;
for (const auto& element: max_node.value())
*(v++) = element.get<float>();
}
}
// Allocate a model
render::model* model = new render::model();
// Set the model bounds
model->set_bounds(bounds);
// Calculate vertex size, count, and stride
std::size_t vertex_size = 0;
std::size_t vertex_count = 0;
for (auto it = attributes.begin(); it != attributes.end(); ++it)
{
vertex_size += std::get<0>(it->second);
vertex_count = std::get<1>(it->second).size() / std::get<0>(it->second);
}
std::size_t vertex_stride = sizeof(float) * vertex_size;
// Build interleaved vertex data buffer
float* vertex_data = new float[vertex_size * vertex_count];
float* v = &vertex_data[0];
for (std::size_t i = 0; i < vertex_count; ++i)
{
for (auto it = attributes.begin(); it != attributes.end(); ++it)
{
std::size_t attribute_size = std::get<0>(it->second);
const float* a = &(std::get<1>(it->second)[i * attribute_size]);
for (std::size_t j = 0; j < attribute_size; ++j)
*(v++) = *(a++);
}
}
// Resize VBO and upload vertex data
gl::vertex_buffer* vbo = model->get_vertex_buffer();
vbo->resize(sizeof(float) * vertex_size * vertex_count, vertex_data);
// Free interleaved vertex data buffer
delete[] vertex_data;
// Map attribute names to locations
static const std::unordered_map<std::string, unsigned int> attribute_location_map =
{
{"position", render::vertex_attribute::position},
{"texcoord", render::vertex_attribute::uv},
{"normal", render::vertex_attribute::normal},
{"tangent", render::vertex_attribute::tangent},
{"color", render::vertex_attribute::color},
{"bone_index", render::vertex_attribute::bone_index},
{"bone_weight", render::vertex_attribute::bone_weight},
{"barycentric", render::vertex_attribute::barycentric},
{"target", render::vertex_attribute::target}
};
// Bind attributes to VAO
gl::vertex_array* vao = model->get_vertex_array();
std::size_t attribute_offset = 0;
for (auto attribute_it = attributes.begin(); attribute_it != attributes.end(); ++attribute_it)
{
std::string attribute_name = attribute_it->first;
if (auto location_it = attribute_location_map.find(attribute_name); location_it != attribute_location_map.end())
{
gl::vertex_attribute attribute;
attribute.buffer = vbo;
attribute.offset = attribute_offset;
attribute.stride = vertex_stride;
attribute.type = gl::vertex_attribute_type::float_32;
attribute.components = std::get<0>(attribute_it->second);
vao->bind(location_it->second, attribute);
attribute_offset += attribute.components * sizeof(float);
}
}
// Load materials
if (auto materials_node = json.find("materials"); materials_node != json.end())
{
for (const auto& material_node: materials_node.value().items())
{
std::string group_name;
std::size_t group_offset = 0;
std::size_t group_size = 0;
render::material* group_material = nullptr;
if (auto name_node = material_node.value().find("name"); name_node != material_node.value().end())
group_name = name_node.value().get<std::string>();
if (auto offset_node = material_node.value().find("offset"); offset_node != material_node.value().end())
group_offset = offset_node.value().get<std::size_t>();
if (auto size_node = material_node.value().find("size"); size_node != material_node.value().end())
group_size = size_node.value().get<std::size_t>();
// Slugify material filename
std::string material_filename = group_name + ".mtl";
std::replace(material_filename.begin(), material_filename.end(), '_', '-');
// Load material from file
group_material = resource_manager->load<render::material>(material_filename);
render::model_group* model_group = model->add_group(group_name);
model_group->set_drawing_mode(gl::drawing_mode::triangles);
model_group->set_start_index(group_offset * 3);
model_group->set_index_count(group_size * 3);
model_group->set_material(group_material);
}
}
// Build skeleton
if (auto skeleton_node = json.find("skeleton"); skeleton_node != json.end())
{
if (auto bones_node = skeleton_node->find("bones"); bones_node != skeleton_node->end())
{
render::skeleton& skeleton = model->get_skeleton();
skeleton.bones.resize(bones_node->size());
std::size_t bone_index = 0;
for (const auto& bone_node: bones_node.value())
{
render::bone& bone = skeleton.bones[bone_index];
// Find bone name
if (auto name_node = bone_node.find("name"); name_node != bone_node.end())
{
// Add bone to bone map
skeleton.bone_map[name_node->get<std::string>()] = bone_index;
}
// Find parent bone
bone.parent = nullptr;
if (auto parent_node = bone_node.find("parent"); parent_node != bone_node.end())
{
// Link bone to parent bone (if any)
if (!parent_node->is_null())
bone.parent = &skeleton.bones[parent_node->get<std::uint16_t>()];
}
// Clear bone transform
bone.transform = math::identity_transform<float>;
// Find translation
if (auto translation_node = bone_node.find("translation"); translation_node != bone_node.end())
{
if (translation_node->size() == 3)
{
bone.transform.translation.x = (*translation_node)[0].get<float>();
bone.transform.translation.y = (*translation_node)[1].get<float>();
bone.transform.translation.z = (*translation_node)[2].get<float>();
}
}
// Find rotation
if (auto rotation_node = bone_node.find("rotation"); rotation_node != bone_node.end())
{
if (rotation_node->size() == 4)
{
bone.transform.rotation.w = (*rotation_node)[0].get<float>();
bone.transform.rotation.x = (*rotation_node)[1].get<float>();
bone.transform.rotation.y = (*rotation_node)[2].get<float>();
bone.transform.rotation.z = (*rotation_node)[3].get<float>();
}
}
// Find length
if (auto length_node = bone_node.find("length"); length_node != bone_node.end())
bone.length = length_node->get<float>();
else
bone.length = 0.0f;
++bone_index;
}
}
}
return model;
}