/* * 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 . */ #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 #include #include #include #include static const float3 barycentric_coords[3] = { float3{1, 0, 0}, float3{0, 1, 0}, float3{0, 0, 1} }; template <> render::model* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); std::vector 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>> 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(); // Allocate attribute in attribute map auto& attribute = attributes[attribute_name]; std::size_t& attribute_size = std::get<0>(attribute); std::vector& 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(); // 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(); } } } // Load bounds geom::aabb bounds = { {std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, {-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::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(); } 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(); } } // 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 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(); if (auto offset_node = material_node.value().find("offset"); offset_node != material_node.value().end()) group_offset = offset_node.value().get(); if (auto size_node = material_node.value().find("size"); size_node != material_node.value().end()) group_size = size_node.value().get(); // 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(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()) { ::skeleton& skeleton = model->get_skeleton(); pose& bind_pose = skeleton.bind_pose; std::uint8_t bone_index = 0; for (const auto& bone_node: bones_node.value()) { // Find parent bone std::uint8_t bone_parent_index = bone_index; if (auto parent_node = bone_node.find("parent"); parent_node != bone_node.end()) { if (!parent_node->is_null()) bone_parent_index = parent_node->get(); } // Construct bone identifier ::bone bone = make_bone(bone_index, bone_parent_index); // Get reference to the bone's bind pose transform auto& bone_transform = bind_pose[bone]; // Get bone 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(); bone_transform.translation.y = (*translation_node)[1].get(); bone_transform.translation.z = (*translation_node)[2].get(); } } // Get bone 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(); bone_transform.rotation.x = (*rotation_node)[1].get(); bone_transform.rotation.y = (*rotation_node)[2].get(); bone_transform.rotation.z = (*rotation_node)[3].get(); } } // Set bone scale bone_transform.scale = {1, 1, 1}; // Get bone length /* if (auto length_node = bone_node.find("length"); length_node != bone_node.end()) bone.length = length_node->get(); else bone.length = 0.0f; */ // Get 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()] = bone; } ++bone_index; } // Calculate inverse skeleton-space bind pose ::concatenate(skeleton.bind_pose, skeleton.inverse_bind_pose); ::inverse(skeleton.inverse_bind_pose, skeleton.inverse_bind_pose); } } return model; }