diff --git a/CMakeLists.txt b/CMakeLists.txt index 7faddc1..8b94016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,5 @@ cmake_minimum_required(VERSION 3.7) - option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/game/ant/morphogenesis.cpp b/src/game/ant/morphogenesis.cpp index bf91cf8..02d4c78 100644 --- a/src/game/ant/morphogenesis.cpp +++ b/src/game/ant/morphogenesis.cpp @@ -19,6 +19,9 @@ #include "game/ant/morphogenesis.hpp" #include "render/material.hpp" +#include "render/vertex-attribute.hpp" +#include +#include namespace game { namespace ant { @@ -27,22 +30,39 @@ static render::model* generate_queen(const ant::breed& breed); static render::model* generate_worker(const ant::breed& breed); static render::model* generate_soldier(const ant::breed& breed); static render::model* generate_male(const ant::breed& breed); -static render::material* build_material(const ant::breed& breed); +static render::material* build_exoskeleton_material +( + const ant::trait::pigmentation& pigmentation, + const ant::trait::sculpturing& sculpturing +); +static void reskin_vertices +( + std::uint8_t* vertex_data, + std::size_t index_count, + const gl::vertex_attribute& position_attribute, + const gl::vertex_attribute& normal_attribute, + const gl::vertex_attribute& tangent_attribute, + const gl::vertex_attribute& bone_index_attribute, + const std::unordered_set& old_bone_indices, + std::uint16_t new_bone_index, + const math::transform& transform +); +static geom::aabb calculate_bounds(std::uint8_t* vertex_data, std::size_t index_count, const gl::vertex_attribute& position_attribute); static render::model* build_model ( render::material* material, - render::model* antennae, - render::model* eyes, - render::model* forewings, - render::model* gaster, - render::model* head, - render::model* hindwings, - render::model* legs, - render::model* mandibles, - render::model* mesosoma, - render::model* ocelli, - render::model* sting, - render::model* waist + const render::model* antennae, + const render::model* eyes, + const render::model* forewings, + const render::model* gaster, + const render::model* head, + const render::model* hindwings, + const render::model* legs, + const render::model* mandibles, + const render::model* mesosoma, + const render::model* ocelli, + const render::model* sting, + const render::model* waist ); render::model* morphogenesis(const ant::breed& breed, ant::caste caste) @@ -72,8 +92,8 @@ render::model* generate_worker(const ant::breed& breed) // Get material parameters - // Build material - render::material* material = build_material(breed); + // Build exoskeleton material + render::material* exoskeleton_material = build_exoskeleton_material(*breed.pigmentation, *breed.sculpturing); // Get worker body part models render::model* antennae_model = breed.antennae->model; @@ -89,7 +109,7 @@ render::model* generate_worker(const ant::breed& breed) // Build worker model render::model* model = build_model ( - material, + exoskeleton_material, antennae_model, eyes_model, nullptr, @@ -118,31 +138,709 @@ render::model* generate_male(const ant::breed& breed) return nullptr; } -render::material* build_material(const ant::breed& breed) +render::material* build_exoskeleton_material +( + const ant::trait::pigmentation& pigmentation, + const ant::trait::sculpturing& sculpturing +) { - return nullptr; + // Allocate copy of pigmentation material + render::material* exoskeleton_material = new render::material(*pigmentation.material); + + // Adjust roughness parameter + if (render::material_property_base* property = exoskeleton_material->get_property("roughness"); property != nullptr) + { + static_cast*>(property)->set_value(sculpturing.roughness); + } + else + { + exoskeleton_material->add_property("roughness")->set_value(sculpturing.roughness); + } + + // Adjust normal map parameter + if (render::material_property_base* property = exoskeleton_material->get_property("normal_map"); property != nullptr) + { + static_cast*>(property)->set_value(sculpturing.normal_map); + } + else + { + exoskeleton_material->add_property("normal_map")->set_value(sculpturing.normal_map); + } + + return exoskeleton_material; } render::model* build_model ( - render::material* material, - render::model* antennae, - render::model* eyes, - render::model* forewings, - render::model* gaster, - render::model* head, - render::model* hindwings, - render::model* legs, - render::model* mandibles, - render::model* mesosoma, - render::model* ocelli, - render::model* sting, - render::model* waist + render::material* exoskeleton_material, + const render::model* antennae, + const render::model* eyes, + const render::model* forewings, + const render::model* gaster, + const render::model* head, + const render::model* hindwings, + const render::model* legs, + const render::model* mandibles, + const render::model* mesosoma, + const render::model* ocelli, + const render::model* sting, + const render::model* waist ) { - return nullptr; + // Get vertex buffers of required body parts + const gl::vertex_buffer* mesosoma_vbo = mesosoma->get_vertex_buffer(); + const gl::vertex_buffer* legs_vbo = legs->get_vertex_buffer(); + const gl::vertex_buffer* head_vbo = head->get_vertex_buffer(); + const gl::vertex_buffer* mandibles_vbo = mandibles->get_vertex_buffer(); + const gl::vertex_buffer* antennae_vbo = antennae->get_vertex_buffer(); + const gl::vertex_buffer* waist_vbo = waist->get_vertex_buffer(); + const gl::vertex_buffer* gaster_vbo = gaster->get_vertex_buffer(); + + // Get vertex buffers of optional body parts + const gl::vertex_buffer* sting_vbo = (sting) ? sting->get_vertex_buffer() : nullptr; + const gl::vertex_buffer* eyes_vbo = (eyes) ? eyes->get_vertex_buffer() : nullptr; + const gl::vertex_buffer* ocelli_vbo = (ocelli) ? ocelli->get_vertex_buffer() : nullptr; + const gl::vertex_buffer* forewings_vbo = (forewings) ? forewings->get_vertex_buffer() : nullptr; + const gl::vertex_buffer* hindwings_vbo = (hindwings) ? hindwings->get_vertex_buffer() : nullptr; + + // Determine combined size of vertex buffers and save offsets + std::size_t vertex_buffer_size = 0; + std::size_t mesosoma_vbo_offset = vertex_buffer_size; + vertex_buffer_size += mesosoma_vbo->get_size(); + std::size_t legs_vbo_offset = vertex_buffer_size; + vertex_buffer_size += legs_vbo->get_size(); + std::size_t head_vbo_offset = vertex_buffer_size; + vertex_buffer_size += head_vbo->get_size(); + std::size_t mandibles_vbo_offset = vertex_buffer_size; + vertex_buffer_size += mandibles_vbo->get_size(); + std::size_t antennae_vbo_offset = vertex_buffer_size; + vertex_buffer_size += antennae_vbo->get_size(); + std::size_t waist_vbo_offset = vertex_buffer_size; + vertex_buffer_size += waist_vbo->get_size(); + std::size_t gaster_vbo_offset = vertex_buffer_size; + vertex_buffer_size += gaster_vbo->get_size(); + std::size_t sting_vbo_offset = vertex_buffer_size; + if (sting) + vertex_buffer_size += sting_vbo->get_size(); + std::size_t eyes_vbo_offset = vertex_buffer_size; + if (eyes) + vertex_buffer_size += eyes_vbo->get_size(); + std::size_t ocelli_vbo_offset = vertex_buffer_size; + if (ocelli) + vertex_buffer_size += ocelli_vbo->get_size(); + std::size_t forewings_vbo_offset = vertex_buffer_size; + if (forewings) + vertex_buffer_size += forewings_vbo->get_size(); + std::size_t hindwings_vbo_offset = vertex_buffer_size; + if (hindwings) + vertex_buffer_size += hindwings_vbo->get_size(); + + // Allocate combined vertex buffer data + std::uint8_t* vertex_buffer_data = new std::uint8_t[vertex_buffer_size]; + + // Read body part vertex buffer data into combined vertex buffer data + mesosoma_vbo->read(0, mesosoma_vbo->get_size(), vertex_buffer_data + mesosoma_vbo_offset); + legs_vbo->read(0, legs_vbo->get_size(), vertex_buffer_data + legs_vbo_offset); + head_vbo->read(0, head_vbo->get_size(), vertex_buffer_data + head_vbo_offset); + mandibles_vbo->read(0, mandibles_vbo->get_size(), vertex_buffer_data + mandibles_vbo_offset); + antennae_vbo->read(0, antennae_vbo->get_size(), vertex_buffer_data + antennae_vbo_offset); + waist_vbo->read(0, waist_vbo->get_size(), vertex_buffer_data + waist_vbo_offset); + gaster_vbo->read(0, gaster_vbo->get_size(), vertex_buffer_data + gaster_vbo_offset); + if (sting) + sting_vbo->read(0, sting_vbo->get_size(), vertex_buffer_data + sting_vbo_offset); + if (eyes) + eyes_vbo->read(0, eyes_vbo->get_size(), vertex_buffer_data + eyes_vbo_offset); + if (ocelli) + ocelli_vbo->read(0, ocelli_vbo->get_size(), vertex_buffer_data + ocelli_vbo_offset); + if (forewings) + forewings_vbo->read(0, forewings_vbo->get_size(), vertex_buffer_data + forewings_vbo_offset); + if (hindwings) + hindwings_vbo->read(0, hindwings_vbo->get_size(), vertex_buffer_data + hindwings_vbo_offset); + + // Allocate model + render::model* model = new render::model(); + + // Setup model VAO + gl::vertex_array* model_vao = model->get_vertex_array(); + for (auto [location, attribute]: mesosoma->get_vertex_array()->get_attributes()) + { + attribute.buffer = model->get_vertex_buffer(); + model_vao->bind(location, attribute); + } + + // Get vertex attributes + const gl::vertex_attribute* position_attribute = nullptr; + const gl::vertex_attribute* normal_attribute = nullptr; + const gl::vertex_attribute* tangent_attribute = nullptr; + const gl::vertex_attribute* bone_index_attribute = nullptr; + const auto& vertex_attribute_map = model_vao->get_attributes(); + if (auto it = vertex_attribute_map.find(render::vertex_attribute::position); it != vertex_attribute_map.end()) + position_attribute = &it->second; + if (auto it = vertex_attribute_map.find(render::vertex_attribute::normal); it != vertex_attribute_map.end()) + normal_attribute = &it->second; + if (auto it = vertex_attribute_map.find(render::vertex_attribute::tangent); it != vertex_attribute_map.end()) + tangent_attribute = &it->second; + if (auto it = vertex_attribute_map.find(render::vertex_attribute::bone_index); it != vertex_attribute_map.end()) + bone_index_attribute = &it->second; + + // Get body part skeletons + const render::skeleton& mesosoma_skeleton = mesosoma->get_skeleton(); + const render::skeleton& legs_skeleton = legs->get_skeleton(); + const render::skeleton& head_skeleton = head->get_skeleton(); + const render::skeleton& mandibles_skeleton = mandibles->get_skeleton(); + const render::skeleton& antennae_skeleton = antennae->get_skeleton(); + const render::skeleton& waist_skeleton = waist->get_skeleton(); + const render::skeleton& gaster_skeleton = gaster->get_skeleton(); + const render::skeleton* sting_skeleton = (sting) ? &sting->get_skeleton() : nullptr; + const render::skeleton* eyes_skeleton = (eyes) ? &eyes->get_skeleton() : nullptr; + const render::skeleton* ocelli_skeleton = (ocelli) ? &ocelli->get_skeleton() : nullptr; + bool postpetiole = (waist_skeleton.bone_map.find("postpetiole") != waist_skeleton.bone_map.end()); + + // Allocate skeleton bones + render::skeleton& skeleton = model->get_skeleton(); + std::size_t bone_count = 34; + if (postpetiole) + bone_count += 1; + if (sting) + bone_count += 1; + if (forewings) + bone_count += 2; + if (hindwings) + bone_count += 2; + skeleton.bones.resize(bone_count); + + // Assign bone indices + std::uint16_t bone_index = 0; + std::uint16_t mesosoma_bone_index = bone_index++; + std::uint16_t foreleg_coxa_l_bone_index = bone_index++; + std::uint16_t foreleg_coxa_r_bone_index = bone_index++; + std::uint16_t foreleg_femur_l_bone_index = bone_index++; + std::uint16_t foreleg_femur_r_bone_index = bone_index++; + std::uint16_t foreleg_tibia_l_bone_index = bone_index++; + std::uint16_t foreleg_tibia_r_bone_index = bone_index++; + std::uint16_t foreleg_tarsus_l_bone_index = bone_index++; + std::uint16_t foreleg_tarsus_r_bone_index = bone_index++; + std::uint16_t midleg_coxa_l_bone_index = bone_index++; + std::uint16_t midleg_coxa_r_bone_index = bone_index++; + std::uint16_t midleg_femur_l_bone_index = bone_index++; + std::uint16_t midleg_femur_r_bone_index = bone_index++; + std::uint16_t midleg_tibia_l_bone_index = bone_index++; + std::uint16_t midleg_tibia_r_bone_index = bone_index++; + std::uint16_t midleg_tarsus_l_bone_index = bone_index++; + std::uint16_t midleg_tarsus_r_bone_index = bone_index++; + std::uint16_t hindleg_coxa_l_bone_index = bone_index++; + std::uint16_t hindleg_coxa_r_bone_index = bone_index++; + std::uint16_t hindleg_femur_l_bone_index = bone_index++; + std::uint16_t hindleg_femur_r_bone_index = bone_index++; + std::uint16_t hindleg_tibia_l_bone_index = bone_index++; + std::uint16_t hindleg_tibia_r_bone_index = bone_index++; + std::uint16_t hindleg_tarsus_l_bone_index = bone_index++; + std::uint16_t hindleg_tarsus_r_bone_index = bone_index++; + std::uint16_t head_bone_index = bone_index++; + std::uint16_t mandible_l_bone_index = bone_index++; + std::uint16_t mandible_r_bone_index = bone_index++; + std::uint16_t scape_l_bone_index = bone_index++; + std::uint16_t scape_r_bone_index = bone_index++; + std::uint16_t pedicel_l_bone_index = bone_index++; + std::uint16_t pedicel_r_bone_index = bone_index++; + std::uint16_t petiole_bone_index = bone_index++; + std::uint16_t postpetiole_bone_index = (postpetiole) ? bone_index++ : static_cast(bone_count); + std::uint16_t gaster_bone_index = bone_index++; + std::uint16_t sting_bone_index = (sting) ? bone_index++ : static_cast(bone_count); + + // Get references and pointers to bones + render::bone& mesosoma_bone = skeleton.bones[mesosoma_bone_index]; + render::bone& foreleg_coxa_l_bone = skeleton.bones[foreleg_coxa_l_bone_index]; + render::bone& foreleg_coxa_r_bone = skeleton.bones[foreleg_coxa_r_bone_index]; + render::bone& foreleg_femur_l_bone = skeleton.bones[foreleg_femur_l_bone_index]; + render::bone& foreleg_femur_r_bone = skeleton.bones[foreleg_femur_r_bone_index]; + render::bone& foreleg_tibia_l_bone = skeleton.bones[foreleg_tibia_l_bone_index]; + render::bone& foreleg_tibia_r_bone = skeleton.bones[foreleg_tibia_r_bone_index]; + render::bone& foreleg_tarsus_l_bone = skeleton.bones[foreleg_tarsus_l_bone_index]; + render::bone& foreleg_tarsus_r_bone = skeleton.bones[foreleg_tarsus_r_bone_index]; + render::bone& midleg_coxa_l_bone = skeleton.bones[midleg_coxa_l_bone_index]; + render::bone& midleg_coxa_r_bone = skeleton.bones[midleg_coxa_r_bone_index]; + render::bone& midleg_femur_l_bone = skeleton.bones[midleg_femur_l_bone_index]; + render::bone& midleg_femur_r_bone = skeleton.bones[midleg_femur_r_bone_index]; + render::bone& midleg_tibia_l_bone = skeleton.bones[midleg_tibia_l_bone_index]; + render::bone& midleg_tibia_r_bone = skeleton.bones[midleg_tibia_r_bone_index]; + render::bone& midleg_tarsus_l_bone = skeleton.bones[midleg_tarsus_l_bone_index]; + render::bone& midleg_tarsus_r_bone = skeleton.bones[midleg_tarsus_r_bone_index]; + render::bone& hindleg_coxa_l_bone = skeleton.bones[hindleg_coxa_l_bone_index]; + render::bone& hindleg_coxa_r_bone = skeleton.bones[hindleg_coxa_r_bone_index]; + render::bone& hindleg_femur_l_bone = skeleton.bones[hindleg_femur_l_bone_index]; + render::bone& hindleg_femur_r_bone = skeleton.bones[hindleg_femur_r_bone_index]; + render::bone& hindleg_tibia_l_bone = skeleton.bones[hindleg_tibia_l_bone_index]; + render::bone& hindleg_tibia_r_bone = skeleton.bones[hindleg_tibia_r_bone_index]; + render::bone& hindleg_tarsus_l_bone = skeleton.bones[hindleg_tarsus_l_bone_index]; + render::bone& hindleg_tarsus_r_bone = skeleton.bones[hindleg_tarsus_r_bone_index]; + render::bone& head_bone = skeleton.bones[head_bone_index]; + render::bone& mandible_l_bone = skeleton.bones[mandible_l_bone_index]; + render::bone& mandible_r_bone = skeleton.bones[mandible_r_bone_index]; + render::bone& scape_l_bone = skeleton.bones[scape_l_bone_index]; + render::bone& scape_r_bone = skeleton.bones[scape_r_bone_index]; + render::bone& pedicel_l_bone = skeleton.bones[pedicel_l_bone_index]; + render::bone& pedicel_r_bone = skeleton.bones[pedicel_r_bone_index]; + render::bone& petiole_bone = skeleton.bones[petiole_bone_index]; + render::bone* postpetiole_bone = (postpetiole) ? &skeleton.bones[postpetiole_bone_index] : nullptr; + render::bone& gaster_bone = skeleton.bones[gaster_bone_index]; + render::bone* sting_bone = (sting) ? &skeleton.bones[sting_bone_index] : nullptr; + + // Skeleton mesosoma + if (auto it = mesosoma_skeleton.bone_map.find("mesosoma"); it != mesosoma_skeleton.bone_map.end()) + mesosoma_bone = mesosoma_skeleton.bones[it->second]; + mesosoma_bone.parent = nullptr; + + // Skeleton forelegs + if (auto it = legs_skeleton.bone_map.find("foreleg_coxa_l"); it != legs_skeleton.bone_map.end()) + foreleg_coxa_l_bone = legs_skeleton.bones[it->second]; + foreleg_coxa_l_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_coxa_r"); it != legs_skeleton.bone_map.end()) + foreleg_coxa_r_bone = legs_skeleton.bones[it->second]; + foreleg_coxa_r_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_femur_l"); it != legs_skeleton.bone_map.end()) + foreleg_femur_l_bone = legs_skeleton.bones[it->second]; + foreleg_femur_l_bone.parent = &foreleg_coxa_l_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_femur_r"); it != legs_skeleton.bone_map.end()) + foreleg_femur_r_bone = legs_skeleton.bones[it->second]; + foreleg_femur_r_bone.parent = &foreleg_coxa_r_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_tibia_l"); it != legs_skeleton.bone_map.end()) + foreleg_tibia_l_bone = legs_skeleton.bones[it->second]; + foreleg_tibia_l_bone.parent = &foreleg_femur_l_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_tibia_r"); it != legs_skeleton.bone_map.end()) + foreleg_tibia_r_bone = legs_skeleton.bones[it->second]; + foreleg_tibia_r_bone.parent = &foreleg_femur_r_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_tarsus_l"); it != legs_skeleton.bone_map.end()) + foreleg_tarsus_l_bone = legs_skeleton.bones[it->second]; + foreleg_tarsus_l_bone.parent = &foreleg_tibia_l_bone; + if (auto it = legs_skeleton.bone_map.find("foreleg_tarsus_r"); it != legs_skeleton.bone_map.end()) + foreleg_tarsus_r_bone = legs_skeleton.bones[it->second]; + foreleg_tarsus_r_bone.parent = &foreleg_tibia_r_bone; + + // Skeleton midlegs + if (auto it = legs_skeleton.bone_map.find("midleg_coxa_l"); it != legs_skeleton.bone_map.end()) + midleg_coxa_l_bone = legs_skeleton.bones[it->second]; + midleg_coxa_l_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_coxa_r"); it != legs_skeleton.bone_map.end()) + midleg_coxa_r_bone = legs_skeleton.bones[it->second]; + midleg_coxa_r_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_femur_l"); it != legs_skeleton.bone_map.end()) + midleg_femur_l_bone = legs_skeleton.bones[it->second]; + midleg_femur_l_bone.parent = &midleg_coxa_l_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_femur_r"); it != legs_skeleton.bone_map.end()) + midleg_femur_r_bone = legs_skeleton.bones[it->second]; + midleg_femur_r_bone.parent = &midleg_coxa_r_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_tibia_l"); it != legs_skeleton.bone_map.end()) + midleg_tibia_l_bone = legs_skeleton.bones[it->second]; + midleg_tibia_l_bone.parent = &midleg_femur_l_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_tibia_r"); it != legs_skeleton.bone_map.end()) + midleg_tibia_r_bone = legs_skeleton.bones[it->second]; + midleg_tibia_r_bone.parent = &midleg_femur_r_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_tarsus_l"); it != legs_skeleton.bone_map.end()) + midleg_tarsus_l_bone = legs_skeleton.bones[it->second]; + midleg_tarsus_l_bone.parent = &midleg_tibia_l_bone; + if (auto it = legs_skeleton.bone_map.find("midleg_tarsus_r"); it != legs_skeleton.bone_map.end()) + midleg_tarsus_r_bone = legs_skeleton.bones[it->second]; + midleg_tarsus_r_bone.parent = &midleg_tibia_r_bone; + + // Skeleton hindlegs + if (auto it = legs_skeleton.bone_map.find("hindleg_coxa_l"); it != legs_skeleton.bone_map.end()) + hindleg_coxa_l_bone = legs_skeleton.bones[it->second]; + hindleg_coxa_l_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_coxa_r"); it != legs_skeleton.bone_map.end()) + hindleg_coxa_r_bone = legs_skeleton.bones[it->second]; + hindleg_coxa_r_bone.parent = &mesosoma_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_femur_l"); it != legs_skeleton.bone_map.end()) + hindleg_femur_l_bone = legs_skeleton.bones[it->second]; + hindleg_femur_l_bone.parent = &hindleg_coxa_l_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_femur_r"); it != legs_skeleton.bone_map.end()) + hindleg_femur_r_bone = legs_skeleton.bones[it->second]; + hindleg_femur_r_bone.parent = &hindleg_coxa_r_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_tibia_l"); it != legs_skeleton.bone_map.end()) + hindleg_tibia_l_bone = legs_skeleton.bones[it->second]; + hindleg_tibia_l_bone.parent = &hindleg_femur_l_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_tibia_r"); it != legs_skeleton.bone_map.end()) + hindleg_tibia_r_bone = legs_skeleton.bones[it->second]; + hindleg_tibia_r_bone.parent = &hindleg_femur_r_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_tarsus_l"); it != legs_skeleton.bone_map.end()) + hindleg_tarsus_l_bone = legs_skeleton.bones[it->second]; + hindleg_tarsus_l_bone.parent = &hindleg_tibia_l_bone; + if (auto it = legs_skeleton.bone_map.find("hindleg_tarsus_r"); it != legs_skeleton.bone_map.end()) + hindleg_tarsus_r_bone = legs_skeleton.bones[it->second]; + hindleg_tarsus_r_bone.parent = &hindleg_tibia_r_bone; + + // Skeleton head + if (auto it = head_skeleton.bone_map.find("head"); it != head_skeleton.bone_map.end()) + head_bone = head_skeleton.bones[it->second]; + head_bone.parent = &mesosoma_bone; + + // Skeleton mandibles + if (auto it = mandibles_skeleton.bone_map.find("mandible_l"); it != mandibles_skeleton.bone_map.end()) + mandible_l_bone = mandibles_skeleton.bones[it->second]; + mandible_l_bone.parent = &head_bone; + if (auto it = mandibles_skeleton.bone_map.find("mandible_r"); it != mandibles_skeleton.bone_map.end()) + mandible_r_bone = mandibles_skeleton.bones[it->second]; + mandible_r_bone.parent = &head_bone; + + // Skeleton antennae + if (auto it = antennae_skeleton.bone_map.find("scape_l"); it != antennae_skeleton.bone_map.end()) + scape_l_bone = antennae_skeleton.bones[it->second]; + scape_l_bone.parent = &head_bone; + if (auto it = antennae_skeleton.bone_map.find("scape_r"); it != antennae_skeleton.bone_map.end()) + scape_r_bone = antennae_skeleton.bones[it->second]; + scape_r_bone.parent = &head_bone; + if (auto it = antennae_skeleton.bone_map.find("pedicel_l"); it != antennae_skeleton.bone_map.end()) + pedicel_l_bone = antennae_skeleton.bones[it->second]; + pedicel_l_bone.parent = &scape_l_bone; + if (auto it = antennae_skeleton.bone_map.find("pedicel_r"); it != antennae_skeleton.bone_map.end()) + pedicel_r_bone = antennae_skeleton.bones[it->second]; + pedicel_r_bone.parent = &scape_r_bone; + + // Skeleton waist + if (auto it = waist_skeleton.bone_map.find("petiole"); it != waist_skeleton.bone_map.end()) + petiole_bone = waist_skeleton.bones[it->second]; + petiole_bone.parent = &mesosoma_bone; + if (postpetiole) + { + if (auto it = waist_skeleton.bone_map.find("postpetiole"); it != waist_skeleton.bone_map.end()) + *postpetiole_bone = waist_skeleton.bones[it->second]; + postpetiole_bone->parent = &petiole_bone; + } + + // Skeleton gaster + if (auto it = gaster_skeleton.bone_map.find("gaster"); it != gaster_skeleton.bone_map.end()) + gaster_bone = gaster_skeleton.bones[it->second]; + gaster_bone.parent = (postpetiole) ? postpetiole_bone : &petiole_bone; + + // Skeleton sting + if (sting) + { + if (auto it = sting_skeleton->bone_map.find("sting"); it != sting_skeleton->bone_map.end()) + *sting_bone = sting_skeleton->bones[it->second]; + sting_bone->parent = &gaster_bone; + } + + // Get number of vertex indices for each body part + std::size_t mesosoma_index_count = (*mesosoma->get_groups())[0]->get_index_count(); + std::size_t legs_index_count = (*legs->get_groups())[0]->get_index_count(); + std::size_t head_index_count = (*head->get_groups())[0]->get_index_count(); + std::size_t mandibles_index_count = (*mandibles->get_groups())[0]->get_index_count(); + std::size_t antennae_index_count = (*antennae->get_groups())[0]->get_index_count(); + std::size_t waist_index_count = (*waist->get_groups())[0]->get_index_count(); + std::size_t gaster_index_count = (*gaster->get_groups())[0]->get_index_count(); + std::size_t sting_index_count = (sting) ? (*sting->get_groups())[0]->get_index_count() : 0; + std::size_t eyes_index_count = (eyes) ? (*eyes->get_groups())[0]->get_index_count() : 0; + std::size_t ocelli_index_count = (ocelli) ? (*ocelli->get_groups())[0]->get_index_count() : 0; + std::size_t forewings_index_count = (forewings) ? (*forewings->get_groups())[0]->get_index_count() : 0; + std::size_t hindwings_index_count = (hindwings) ? (*hindwings->get_groups())[0]->get_index_count() : 0; + std::size_t exoskeleton_index_count = + mesosoma_index_count + + legs_index_count + + head_index_count + + mandibles_index_count + + antennae_index_count + + waist_index_count + + gaster_index_count + + sting_index_count; + + // Calculate transform from head space to body space + math::transform head_to_body; + if (auto it = mesosoma_skeleton.bone_map.find("head"); it != mesosoma_skeleton.bone_map.end()) + head_to_body = mesosoma_skeleton.concatenate(it->second); + + // Reskin head bone + std::unordered_set old_head_bone_indices; + if (auto it = head_skeleton.bone_map.find("head"); it != head_skeleton.bone_map.end()) + old_head_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + head_vbo_offset, head_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_head_bone_indices, head_bone_index, head_to_body); + + // Calculate transforms from mandible space to body space + math::transform mandible_l_to_body; + if (auto it = head_skeleton.bone_map.find("mandible_l"); it != head_skeleton.bone_map.end()) + mandible_l_to_body = head_to_body * head_skeleton.concatenate(it->second); + math::transform mandible_r_to_body; + if (auto it = head_skeleton.bone_map.find("mandible_r"); it != head_skeleton.bone_map.end()) + mandible_r_to_body = head_to_body * head_skeleton.concatenate(it->second); + + // Reskin mandible bones + std::unordered_set old_head_mandible_l_bone_indices; + if (auto it = mandibles_skeleton.bone_map.find("mandible_l"); it != mandibles_skeleton.bone_map.end()) + old_head_mandible_l_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + mandibles_vbo_offset, mandibles_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_head_mandible_l_bone_indices, mandible_l_bone_index, mandible_l_to_body); + std::unordered_set old_head_mandible_r_bone_indices; + if (auto it = mandibles_skeleton.bone_map.find("mandible_r"); it != mandibles_skeleton.bone_map.end()) + old_head_mandible_r_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + mandibles_vbo_offset, mandibles_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_head_mandible_r_bone_indices, mandible_r_bone_index, mandible_r_to_body); + + // Calculate transforms from antennae space to body space + math::transform antenna_l_to_body; + if (auto it = head_skeleton.bone_map.find("scape_l"); it != head_skeleton.bone_map.end()) + antenna_l_to_body = head_to_body * head_skeleton.concatenate(it->second); + math::transform antenna_r_to_body; + if (auto it = head_skeleton.bone_map.find("scape_r"); it != head_skeleton.bone_map.end()) + antenna_r_to_body = head_to_body * head_skeleton.concatenate(it->second); + + // Reskin scape bones + std::unordered_set old_scape_l_indices; + if (auto it = antennae_skeleton.bone_map.find("scape_l"); it != antennae_skeleton.bone_map.end()) + old_scape_l_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + antennae_vbo_offset, antennae_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_scape_l_indices, scape_l_bone_index, antenna_l_to_body); + std::unordered_set old_scape_r_indices; + if (auto it = antennae_skeleton.bone_map.find("scape_r"); it != antennae_skeleton.bone_map.end()) + old_scape_r_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + antennae_vbo_offset, antennae_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_scape_r_indices, scape_r_bone_index, antenna_r_to_body); + + // Reskin pedicel bones + const std::vector pedicel_bone_names = + { + "pedicel", + "flagellomere_1", + "flagellomere_2", + "flagellomere_3", + "flagellomere_4", + "flagellomere_5", + "flagellomere_6", + "flagellomere_7", + "flagellomere_8", + "flagellomere_9", + "flagellomere_10", + "flagellomere_11", + "flagellomere_12" + }; + std::unordered_set old_pedicel_l_indices; + for (const std::string& bone_name: pedicel_bone_names) + if (auto it = antennae_skeleton.bone_map.find(bone_name + "_l"); it != antennae_skeleton.bone_map.end()) + old_pedicel_l_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + antennae_vbo_offset, antennae_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_pedicel_l_indices, pedicel_l_bone_index, antenna_l_to_body); + std::unordered_set old_pedicel_r_indices; + for (const std::string& bone_name: pedicel_bone_names) + if (auto it = antennae_skeleton.bone_map.find(bone_name + "_r"); it != antennae_skeleton.bone_map.end()) + old_pedicel_r_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + antennae_vbo_offset, antennae_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_pedicel_r_indices, pedicel_r_bone_index, antenna_r_to_body); + + // Calculate transform from waist space to body space + math::transform waist_to_body; + if (auto it = mesosoma_skeleton.bone_map.find("petiole"); it != mesosoma_skeleton.bone_map.end()) + waist_to_body = mesosoma_skeleton.concatenate(it->second); + + // Reskin waist bones + std::unordered_set old_petiole_bone_indices; + if (auto it = waist_skeleton.bone_map.find("petiole"); it != waist_skeleton.bone_map.end()) + old_petiole_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + waist_vbo_offset, waist_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_petiole_bone_indices, petiole_bone_index, waist_to_body); + if (postpetiole) + { + std::unordered_set old_postpetiole_bone_indices; + if (auto it = waist_skeleton.bone_map.find("postpetiole"); it != waist_skeleton.bone_map.end()) + old_postpetiole_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + waist_vbo_offset, waist_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_postpetiole_bone_indices, postpetiole_bone_index, waist_to_body); + } + + // Calculate transform from gaster space to body space + math::transform gaster_to_body; + if (auto it = waist_skeleton.bone_map.find("gaster"); it != waist_skeleton.bone_map.end()) + gaster_to_body = waist_to_body * waist_skeleton.concatenate(it->second); + + // Reskin gaster bones + std::unordered_set old_gaster_bone_indices; + if (auto it = gaster_skeleton.bone_map.find("gaster"); it != gaster_skeleton.bone_map.end()) + old_gaster_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + gaster_vbo_offset, gaster_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_gaster_bone_indices, gaster_bone_index, gaster_to_body); + + if (sting) + { + // Calculate transform from sting space to body space + math::transform sting_to_body; + if (auto it = gaster_skeleton.bone_map.find("sting"); it != gaster_skeleton.bone_map.end()) + sting_to_body = gaster_to_body * gaster_skeleton.concatenate(it->second); + + // Reskin sting bones + std::unordered_set old_sting_bone_indices; + if (auto it = sting_skeleton->bone_map.find("sting"); it != sting_skeleton->bone_map.end()) + old_sting_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + sting_vbo_offset, sting_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_sting_bone_indices, sting_bone_index, sting_to_body); + } + + if (eyes) + { + // Calculate transforms from eyes space to body space + math::transform eye_l_to_body; + if (auto it = head_skeleton.bone_map.find("eye_l"); it != head_skeleton.bone_map.end()) + eye_l_to_body = head_to_body * head_skeleton.concatenate(it->second); + math::transform eye_r_to_body; + if (auto it =head_skeleton.bone_map.find("eye_r"); it != head_skeleton.bone_map.end()) + eye_r_to_body = head_to_body * head_skeleton.concatenate(it->second); + + // Reskin eye bones + std::unordered_set old_eye_l_bone_indices; + if (auto it = eyes_skeleton->bone_map.find("eye_l"); it != eyes_skeleton->bone_map.end()) + old_eye_l_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + eyes_vbo_offset, eyes_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_eye_l_bone_indices, head_bone_index, eye_l_to_body); + std::unordered_set old_eye_r_bone_indices; + if (auto it = eyes_skeleton->bone_map.find("eye_r"); it != eyes_skeleton->bone_map.end()) + old_eye_r_bone_indices.emplace(it->second); + reskin_vertices(vertex_buffer_data + eyes_vbo_offset, eyes_index_count, *position_attribute, *normal_attribute, *tangent_attribute, *bone_index_attribute, old_eye_r_bone_indices, head_bone_index, eye_r_to_body); + } + + // Upload vertex buffer data to model VBO + model->get_vertex_buffer()->repurpose(gl::buffer_usage::static_draw, vertex_buffer_size, vertex_buffer_data); + + + + // Construct exoskeleton model group + render::model_group* exoskeleton_group = model->add_group("exoskeleton"); + exoskeleton_group->set_material(exoskeleton_material); + exoskeleton_group->set_drawing_mode(gl::drawing_mode::triangles); + exoskeleton_group->set_start_index(0); + exoskeleton_group->set_index_count(exoskeleton_index_count); + + std::size_t index_offset = exoskeleton_index_count; + if (eyes) + { + // Construct eyes model group + render::model_group* eyes_group = model->add_group("eyes"); + eyes_group->set_material((*eyes->get_groups())[0]->get_material()); + eyes_group->set_drawing_mode(gl::drawing_mode::triangles); + eyes_group->set_start_index(index_offset); + eyes_group->set_index_count(eyes_index_count); + index_offset += eyes_index_count; + } + + if (ocelli) + { + // Construct ocelli model group + render::model_group* ocelli_group = model->add_group("ocelli"); + ocelli_group->set_material((*ocelli->get_groups())[0]->get_material()); + ocelli_group->set_drawing_mode(gl::drawing_mode::triangles); + ocelli_group->set_start_index(index_offset); + ocelli_group->set_index_count(ocelli_index_count); + index_offset += ocelli_index_count; + } + + if (forewings) + { + // Construct forewings model group + render::model_group* forewings_group = model->add_group("forewings"); + forewings_group->set_material((*forewings->get_groups())[0]->get_material()); + forewings_group->set_drawing_mode(gl::drawing_mode::triangles); + forewings_group->set_start_index(index_offset); + forewings_group->set_index_count(forewings_index_count); + index_offset += forewings_index_count; + } + + if (hindwings) + { + // Construct hindwings model group + render::model_group* hindwings_group = model->add_group("hindwings"); + hindwings_group->set_material((*hindwings->get_groups())[0]->get_material()); + hindwings_group->set_drawing_mode(gl::drawing_mode::triangles); + hindwings_group->set_start_index(index_offset); + hindwings_group->set_index_count(hindwings_index_count); + index_offset += hindwings_index_count; + } + + // Calculate model bounding box + geom::aabb bounds = calculate_bounds(vertex_buffer_data, index_offset, *position_attribute); + model->set_bounds(bounds); + + // Free vertex buffer data + delete[] vertex_buffer_data; + + return model; } +void reskin_vertices +( + std::uint8_t* vertex_data, + std::size_t index_count, + const gl::vertex_attribute& position_attribute, + const gl::vertex_attribute& normal_attribute, + const gl::vertex_attribute& tangent_attribute, + const gl::vertex_attribute& bone_index_attribute, + const std::unordered_set& old_bone_indices, + std::uint16_t new_bone_index, + const math::transform& transform +) +{ + std::uint8_t* position_data = vertex_data + position_attribute.offset; + std::uint8_t* normal_data = vertex_data + normal_attribute.offset; + std::uint8_t* tangent_data = vertex_data + tangent_attribute.offset; + std::uint8_t* bone_index_data = vertex_data + bone_index_attribute.offset; + + for (std::size_t i = 0; i < index_count; ++i) + { + // Get bone index of current vertex + float* bone_index = reinterpret_cast(bone_index_data + bone_index_attribute.stride * i); + + // Skip irrelevant bones + if (!old_bone_indices.count(static_cast(*bone_index + 0.5f))) + continue; + + // Get vertex position + float* x = reinterpret_cast(position_data + position_attribute.stride * i); + float* y = x + 1; + float* z = y + 1; + + // Get vertex normal + float* nx = reinterpret_cast(normal_data + normal_attribute.stride * i); + float* ny = nx + 1; + float* nz = ny + 1; + + // Get vertex tangent + float* tx = reinterpret_cast(tangent_data + tangent_attribute.stride * i); + float* ty = tx + 1; + float* tz = ty + 1; + //float* bts = tz + 1; + + // Transform vertex attributes + float3 position = transform * float3{*x, *y, *z}; + float3 normal = math::normalize(transform.rotation * float3{*nx, *ny, *nz}); + float3 tangent = transform.rotation * float3{*tx, *ty, *tz}; + + // Update vertex data + *x = position.x; + *y = position.y; + *z = position.z; + *nx = normal.x; + *ny = normal.y; + *nz = normal.z; + *tx = tangent.x; + *ty = tangent.y; + *tz = tangent.z; + //*bts = ... + *bone_index = static_cast(new_bone_index); + } +} + +geom::aabb calculate_bounds(std::uint8_t* vertex_data, std::size_t index_count, const gl::vertex_attribute& position_attribute) +{ + std::uint8_t* position_data = vertex_data + position_attribute.offset; + + geom::aabb bounds; + bounds.min_point.x = std::numeric_limits::infinity(); + bounds.min_point.y = std::numeric_limits::infinity(); + bounds.min_point.z = std::numeric_limits::infinity(); + bounds.max_point.x = -std::numeric_limits::infinity(); + bounds.max_point.y = -std::numeric_limits::infinity(); + bounds.max_point.z = -std::numeric_limits::infinity(); + + for (std::size_t i = 0; i < index_count; ++i) + { + // Get vertex position + float* x = reinterpret_cast(position_data + position_attribute.stride * i); + float* y = x + 1; + float* z = y + 1; + + bounds.min_point.x = std::min(*x, bounds.min_point.x); + bounds.min_point.y = std::min(*y, bounds.min_point.y); + bounds.min_point.z = std::min(*z, bounds.min_point.z); + bounds.max_point.x = std::max(*x, bounds.max_point.x); + bounds.max_point.y = std::max(*y, bounds.max_point.y); + bounds.max_point.z = std::max(*z, bounds.max_point.z); + } + + return bounds; +} } // namespace ant } // namespace game diff --git a/src/game/ant/trait/loader/pigmentation-loader.cpp b/src/game/ant/trait/loader/pigmentation-loader.cpp index f9cff8e..d18621b 100644 --- a/src/game/ant/trait/loader/pigmentation-loader.cpp +++ b/src/game/ant/trait/loader/pigmentation-loader.cpp @@ -39,23 +39,11 @@ trait::pigmentation* resource_loader::load(resource_manager // Allocate pigmentation trait trait::pigmentation* pigmentation = new trait::pigmentation(); - // Parse pigmentation primary albedo - pigmentation->primary_albedo = {0.0, 0.0, 0.0}; - if (auto primary_albedo_element = pigmentation_element->find("primary_albedo"); primary_albedo_element != pigmentation_element->end()) - { - pigmentation->primary_albedo.x = (*primary_albedo_element)[0].get(); - pigmentation->primary_albedo.y = (*primary_albedo_element)[1].get(); - pigmentation->primary_albedo.z = (*primary_albedo_element)[2].get(); - } - - // Parse pigmentation secondary albedo - pigmentation->secondary_albedo = {0.0, 0.0, 0.0}; - if (auto secondary_albedo_element = pigmentation_element->find("secondary_albedo"); secondary_albedo_element != pigmentation_element->end()) - { - pigmentation->secondary_albedo.x = (*secondary_albedo_element)[0].get(); - pigmentation->secondary_albedo.y = (*secondary_albedo_element)[1].get(); - pigmentation->secondary_albedo.z = (*secondary_albedo_element)[2].get(); - } + // Load pigmentation material + auto material_element = pigmentation_element->find("material"); + if (material_element == pigmentation_element->end()) + throw std::runtime_error("Pigmentation trait doesn't specify pigmentation material."); + pigmentation->material = resource_manager->load(material_element->get()); // Free JSON data delete data; diff --git a/src/game/ant/trait/pigmentation.hpp b/src/game/ant/trait/pigmentation.hpp index b981ab9..58603d7 100644 --- a/src/game/ant/trait/pigmentation.hpp +++ b/src/game/ant/trait/pigmentation.hpp @@ -20,7 +20,7 @@ #ifndef ANTKEEPER_GAME_ANT_TRAIT_PIGMENTATION_HPP #define ANTKEEPER_GAME_ANT_TRAIT_PIGMENTATION_HPP -#include "utility/fundamental-types.hpp" +#include "render/material.hpp" namespace game { namespace ant { @@ -31,11 +31,8 @@ namespace trait { */ struct pigmentation { - /// Primary albedo color. - float3 primary_albedo; - - /// Secondary albedo color. - float3 secondary_albedo; + /// Pigmentation material + render::material* material; }; } // namespace trait diff --git a/src/game/state/nuptial-flight.cpp b/src/game/state/nuptial-flight.cpp index 80286f4..d2db7f0 100644 --- a/src/game/state/nuptial-flight.cpp +++ b/src/game/state/nuptial-flight.cpp @@ -58,18 +58,18 @@ nuptial_flight::nuptial_flight(game::context& ctx): ant::breed breed; // Load morphological traits - breed.head = ctx.resource_manager->load("collared-harvester-head.dna"); + breed.head = ctx.resource_manager->load("square-harvester-head.dna"); breed.mandibles = ctx.resource_manager->load("harvester-mandibles.dna"); breed.antennae = ctx.resource_manager->load("slender-antennae.dna"); - breed.eyes = ctx.resource_manager->load("vestigial-eyes.dna"); + breed.eyes = ctx.resource_manager->load("oval-eyes.dna"); breed.mesosoma = ctx.resource_manager->load("humpback-mesosoma.dna"); breed.legs = ctx.resource_manager->load("trekking-legs.dna"); breed.waist = ctx.resource_manager->load("harvester-waist.dna"); breed.gaster = ctx.resource_manager->load("ovoid-gaster.dna"); breed.ocelli = ctx.resource_manager->load("absent-ocelli.dna"); - breed.sting = ctx.resource_manager->load("bullet-sting.dna"); + breed.sting = ctx.resource_manager->load("sting-absent.dna"); breed.sculpturing = ctx.resource_manager->load("politus-sculpturing.dna"); - breed.pigmentation = ctx.resource_manager->load("onyx-pigmentation.dna"); + breed.pigmentation = ctx.resource_manager->load("rust-pigmentation.dna"); breed.egg = ctx.resource_manager->load("ellipsoid-egg.dna"); breed.larva = ctx.resource_manager->load("old-larva.dna"); breed.cocoon = ctx.resource_manager->load("cocoon-present.dna"); @@ -124,13 +124,6 @@ nuptial_flight::nuptial_flight(game::context& ctx): entity::command::warp_to(*ctx.entity_registry, ruler_10cm_eid, {0, 0, 10}); } - // Create ant_test - { - entity::archetype* ant_test_10cm_archetype = ctx.resource_manager->load("ant-test.ent"); - auto ant_test_eid = ant_test_10cm_archetype->create(*ctx.entity_registry); - entity::command::warp_to(*ctx.entity_registry, ant_test_eid, {10, 0, 0}); - } - // Create keeper if not yet created if (ctx.entities.find("keeper") == ctx.entities.end()) { @@ -144,7 +137,7 @@ nuptial_flight::nuptial_flight(game::context& ctx): auto boid_eid = ctx.entity_registry->create(); entity::component::model model; - model.render_model = ctx.resource_manager->load("ant-test.mdl"); + model.render_model = worker_model;//ctx.resource_manager->load("ant-test.mdl"); model.instance_count = 0; model.layers = 1; ctx.entity_registry->assign(boid_eid, model); @@ -158,6 +151,8 @@ nuptial_flight::nuptial_flight(game::context& ctx): entity::component::locomotion locomotion; locomotion.yaw = 0.0f; ctx.entity_registry->assign(boid_eid, locomotion); + + entity::command::warp_to(*ctx.entity_registry, boid_eid, {0, 2, 0}); // Set target ant ctx.entities["ant"] = boid_eid; diff --git a/src/math/quaternion-functions.hpp b/src/math/quaternion-functions.hpp index ed56e02..f64566b 100644 --- a/src/math/quaternion-functions.hpp +++ b/src/math/quaternion-functions.hpp @@ -336,9 +336,8 @@ inline quaternion mul(const quaternion& q, T s) template vector mul(const quaternion& q, const vector& v) { - const T r = q.w; // Real part - const vector& i = reinterpret_cast&>(q.x); // Imaginary part - return i * dot(i, v) * T(2) + v * (r * r - dot(i, i)) + cross(i, v) * r * T(2); + const vector& i = reinterpret_cast&>(q.x); + return add(add(mul(i, dot(i, v) * T(2)), mul(v, (q.w * q.w - dot(i, i)))), mul(cross(i, v), q.w * T(2))); } template diff --git a/src/math/transform-functions.hpp b/src/math/transform-functions.hpp index e3c06bf..e744c3e 100644 --- a/src/math/transform-functions.hpp +++ b/src/math/transform-functions.hpp @@ -89,14 +89,14 @@ transform mul(const transform& x, const transform& y) { mul(x, y.translation), normalize(mul(x.rotation, y.rotation)), - x.scale * y.scale + mul(x.scale, y.scale) }; } template vector mul(const transform& t, const vector& v) { - return t.translation + (t.rotation * (v * t.scale)); + return add(t.translation, (mul(t.rotation, mul(v, t.scale)))); } } // namespace math diff --git a/src/render/bone.hpp b/src/render/bone.hpp new file mode 100644 index 0000000..a23bce3 --- /dev/null +++ b/src/render/bone.hpp @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +#ifndef ANTKEEPER_RENDER_BONE_HPP +#define ANTKEEPER_RENDER_BONE_HPP + +#include "math/transform-type.hpp" + +namespace render { + +/** + * Skeletal animation bone. + */ +struct bone +{ + /// Pointer to the parent bone. + bone* parent; + + /// Local transform, relative to the parent bone. + math::transform transform; + + /// Length of the bone. + float length; +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_BONE_HPP diff --git a/src/render/model.hpp b/src/render/model.hpp index cf58c82..5e234ca 100644 --- a/src/render/model.hpp +++ b/src/render/model.hpp @@ -20,20 +20,18 @@ #ifndef ANTKEEPER_RENDER_MODEL_HPP #define ANTKEEPER_RENDER_MODEL_HPP +#include "render/skeleton.hpp" #include "gl/vertex-array.hpp" #include "gl/vertex-buffer.hpp" #include "gl/drawing-mode.hpp" +#include "render/material.hpp" #include "geom/aabb.hpp" #include #include #include -class skeleton; - namespace render { -class material; - /** * Part of a model which is associated with exactly one material. */ @@ -149,6 +147,9 @@ public: const gl::vertex_buffer* get_vertex_buffer() const; gl::vertex_buffer* get_vertex_buffer(); + + const skeleton& get_skeleton() const; + skeleton& get_skeleton(); private: aabb_type bounds; @@ -156,7 +157,7 @@ private: std::unordered_map group_map; gl::vertex_array vao; gl::vertex_buffer vbo; - skeleton* skeleton; + render::skeleton skeleton; }; inline void model::set_bounds(const aabb_type& bounds) @@ -194,6 +195,16 @@ inline gl::vertex_buffer* model::get_vertex_buffer() return &vbo; } +inline const skeleton& model::get_skeleton() const +{ + return skeleton; +} + +inline skeleton& model::get_skeleton() +{ + return skeleton; +} + } // namespace render #endif // ANTKEEPER_RENDER_MODEL_HPP diff --git a/src/render/skeleton.cpp b/src/render/skeleton.cpp new file mode 100644 index 0000000..aaaa4d7 --- /dev/null +++ b/src/render/skeleton.cpp @@ -0,0 +1,39 @@ +/* + * 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 "render/skeleton.hpp" +#include "math/transform-operators.hpp" + +namespace render { + +math::transform skeleton::concatenate(std::uint16_t index) const +{ + const bone* bone = &bones[index]; + math::transform transform = bone->transform; + + while (bone->parent) + { + transform = bone->parent->transform * transform; + bone = bone->parent; + } + + return transform; +} + +} // namespace render diff --git a/src/render/skeleton.hpp b/src/render/skeleton.hpp new file mode 100644 index 0000000..f6cd1f3 --- /dev/null +++ b/src/render/skeleton.hpp @@ -0,0 +1,53 @@ +/* + * 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 . + */ + +#ifndef ANTKEEPER_RENDER_SKELETON_HPP +#define ANTKEEPER_RENDER_SKELETON_HPP + +#include "render/bone.hpp" +#include +#include +#include +#include + +namespace render { + +/** + * Skeletal animation skeleton. + */ +struct skeleton +{ + /// Collection of bones. + std::vector bones; + + /// Maps bone names to bone indices. + std::unordered_map bone_map; + + /** + * Calculates the global transform of a bone. + * + * @param index Index of the bone. + * @return Global transform of the bone. + */ + math::transform concatenate(std::uint16_t index) const; +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_SKELETON_HPP diff --git a/src/resources/model-loader.cpp b/src/resources/model-loader.cpp index 074e073..36602ec 100644 --- a/src/resources/model-loader.cpp +++ b/src/resources/model-loader.cpp @@ -24,11 +24,11 @@ #include "gl/vertex-attribute.hpp" #include "gl/drawing-mode.hpp" #include "utility/fundamental-types.hpp" +#include "math/constants.hpp" #include #include #include #include -#include #include static const float3 barycentric_coords[3] = @@ -38,324 +38,6 @@ static const float3 barycentric_coords[3] = float3{0, 0, 1} }; -/* -template <> -model* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) -{ - std::string line; - std::vector positions; - std::vector uvs; - std::vector normals; - std::vector tangents; - std::vector bitangents; - std::vector> faces; - std::vector material_groups; - material_group* current_material_group = nullptr; - 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()} - }; - - while (!PHYSFS_eof(file)) - { - // Read line - physfs_getline(file, line); - - // Tokenize line - std::vector tokens; - std::string token; - std::istringstream linestream(line); - while (linestream >> token) - tokens.push_back(token); - - // Skip empty lines and comments - if (tokens.empty() || tokens[0][0] == '#') - continue; - - if (tokens[0] == "v") - { - if (tokens.size() != 4) - { - std::stringstream stream; - stream << "resource_loader::load(): Invalid line \"" << line << "\"" << std::endl; - throw std::runtime_error(stream.str()); - } - - float3 position; - - std::stringstream(tokens[1]) >> position[0]; - std::stringstream(tokens[2]) >> position[1]; - std::stringstream(tokens[3]) >> position[2]; - - positions.push_back(position); - - // Add position to bounds - for (int i = 0; i < 3; ++i) - { - bounds.min_point[i] = std::min(bounds.min_point[i], position[i]); - bounds.max_point[i] = std::max(bounds.max_point[i], position[i]); - } - } - else if (tokens[0] == "vt") - { - if (tokens.size() != 3) - { - std::stringstream stream; - stream << "resource_loader::load(): Invalid line \"" << line << "\"" << std::endl; - throw std::runtime_error(stream.str()); - } - - float2 uv; - - std::stringstream(tokens[1]) >> uv[0]; - std::stringstream(tokens[2]) >> uv[1]; - - uvs.push_back(uv); - } - else if (tokens[0] == "vn") - { - if (tokens.size() != 4) - { - std::stringstream stream; - stream << "resource_loader::load(): Invalid line \"" << line << "\"" << std::endl; - throw std::runtime_error(stream.str()); - } - - float3 normal; - - std::stringstream(tokens[1]) >> normal[0]; - std::stringstream(tokens[2]) >> normal[1]; - std::stringstream(tokens[3]) >> normal[2]; - - normals.push_back(normal); - } - else if (tokens[0] == "f") - { - if (tokens.size() != 4) - { - std::stringstream stream; - stream << "resource_loader::load(): Invalid line \"" << line << "\"" << std::endl; - throw std::runtime_error(stream.str()); - } - - std::vector face; - - for (std::size_t i = 0; i < 3; ++i) - { - std::stringstream ss(tokens[i + 1]); - while (ss.good()) - { - std::string substring; - std::getline(ss, substring, '/'); - - if (!substring.empty()) - { - std::size_t index = std::stoul(substring) - 1; - face.push_back(index); - } - } - } - - faces.push_back(face); - } - else if (tokens[0] == "usemtl") - { - if (tokens.size() != 2) - { - std::stringstream stream; - stream << "resource_loader::load(): Invalid line \"" << line << "\"" << std::endl; - throw std::runtime_error(stream.str()); - } - - if (current_material_group) - { - current_material_group->index_count = faces.size() * 3 - current_material_group->start_index; - } - - material_groups.push_back(material_group()); - current_material_group = &material_groups.back(); - current_material_group->name = tokens[1]; - current_material_group->start_index = faces.size() * 3; - current_material_group->index_count = 0; - } - } - - if (current_material_group) - { - current_material_group->index_count = faces.size() * 3 - current_material_group->start_index; - } - - // Load material group materials - for (material_group& material_group: material_groups) - { - material_group.material = resource_manager->load(material_group.name + ".mtl"); - } - - bool has_uvs = (!uvs.empty()); - bool has_normals = (!normals.empty()); - bool has_tangents = (has_uvs && has_normals); - bool has_barycentric = false; - has_barycentric = true; - - // Calculate faceted tangents and bitangents - if (has_tangents) - { - tangents.resize(positions.size()); - bitangents.resize(positions.size()); - for (std::size_t i = 0; i < positions.size(); ++i) - { - tangents[i] = {0.0f, 0.0f, 0.0f}; - bitangents[i] = {0.0f, 0.0f, 0.0f}; - } - - for (std::size_t i = 0; i < faces.size(); ++i) - { - const std::vector& face = faces[i]; - - std::size_t ia = face[0]; - std::size_t ib = face[3]; - std::size_t ic = face[6]; - - const float3& a = positions[ia]; - const float3& b = positions[ib]; - const float3& c = positions[ic]; - const float2& uva = uvs[face[1]]; - const float2& uvb = uvs[face[4]]; - const float2& uvc = uvs[face[7]]; - - float3 ba = b - a; - float3 ca = c - a; - float2 uvba = uvb - uva; - float2 uvca = uvc - uva; - - float f = 1.0f / (uvba.x * uvca.y - uvca.x * uvba.y); - float3 tangent = (ba * uvca.y - ca * uvba.y) * f; - float3 bitangent = (ba * -uvca.x + ca * uvba.x) * f; - - tangents[ia] += tangent; - tangents[ib] += tangent; - tangents[ic] += tangent; - bitangents[ia] += bitangent; - bitangents[ib] += bitangent; - bitangents[ic] += bitangent; - } - } - - std::size_t vertex_size = 3; - if (has_uvs) - vertex_size += 2; - if (has_normals) - vertex_size += 3; - if (has_tangents) - vertex_size += 4; - if (has_barycentric) - vertex_size += 3; - - std::size_t vertex_stride = sizeof(float) * vertex_size; - - // Generate vertex buffer - float* vertex_data = new float[vertex_size * faces.size() * 3]; - float* v = &vertex_data[0]; - for (std::size_t i = 0; i < faces.size(); ++i) - { - const std::vector& face = faces[i]; - - std::size_t k = 0; - for (std::size_t j = 0; j < 3; ++j) - { - const float3& position = positions[face[k++]]; - *(v++) = position.x; - *(v++) = position.y; - *(v++) = position.z; - - if (has_uvs) - { - const float2& uv = uvs[face[k++]]; - *(v++) = uv.x; - *(v++) = uv.y; - } - - if (has_normals) - { - const float3& normal = normals[face[k++]]; - *(v++) = normal.x; - *(v++) = normal.y; - *(v++) = normal.z; - } - - if (has_tangents) - { - const float3& n = normals[face[k - 1]]; - const float3& t = tangents[face[k - 3]]; - const float3& b = bitangents[face[k - 3]]; - - // Gram-Schmidt orthogonalize tangent and bitangent - float3 tangent = math::normalize(t - n * dot(n, t)); - float bitangent_sign = (math::dot(math::cross(n, t), b) < 0.0f) ? -1.0f : 1.0f; - - *(v++) = tangent.x; - *(v++) = tangent.y; - *(v++) = tangent.z; - *(v++) = bitangent_sign; - } - - if (has_barycentric) - { - *(v++) = barycentric_coords[j].x; - *(v++) = barycentric_coords[j].y; - *(v++) = barycentric_coords[j].z; - } - } - } - - // Allocate a model - model* model = new ::model(); - model->set_bounds(bounds); - vertex_buffer* vbo = model->get_vertex_buffer(); - vertex_array* vao = model->get_vertex_array(); - vbo->resize(sizeof(float) * vertex_size * faces.size() * 3, vertex_data); - std::size_t offset = 0; - vao->bind_attribute(VERTEX_POSITION_LOCATION, *vbo, 3, vertex_attribute_type::float_32, vertex_stride, 0); - offset += 3; - if (has_uvs) - { - vao->bind_attribute(VERTEX_TEXCOORD_LOCATION, *vbo, 2, vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset); - offset += 2; - } - if (has_normals) - { - vao->bind_attribute(VERTEX_NORMAL_LOCATION, *vbo, 3, vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset); - offset += 3; - } - if (has_tangents) - { - vao->bind_attribute(VERTEX_TANGENT_LOCATION, *vbo, 4, vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset); - offset += 4; - } - if (has_barycentric) - { - vao->bind_attribute(VERTEX_BARYCENTRIC_LOCATION, *vbo, 3, vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset); - offset += 3; - } - - // Add model groups for each material - for (const material_group& material_group: material_groups) - { - model_group* model_group = model->add_group(material_group.name); - model_group->set_material(material_group.material); - model_group->set_drawing_mode(drawing_mode::triangles); - model_group->set_start_index(material_group.start_index); - model_group->set_index_count(material_group.index_count); - } - - // Deallocate vertex data - delete[] vertex_data; - - return model; -} -*/ - template <> render::model* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { @@ -536,5 +218,72 @@ render::model* resource_loader::load(resource_manager* resource_m } } + // 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()] = 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()]; + } + + + // Clear bone transform + bone.transform = math::identity_transform; + + // 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(); + bone.transform.translation.y = (*translation_node)[1].get(); + bone.transform.translation.z = (*translation_node)[2].get(); + } + } + + // 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(); + bone.transform.rotation.x = (*rotation_node)[1].get(); + bone.transform.rotation.y = (*rotation_node)[2].get(); + bone.transform.rotation.z = (*rotation_node)[3].get(); + } + } + + // Find length + if (auto length_node = bone_node.find("length"); length_node != bone_node.end()) + bone.length = length_node->get(); + else + bone.length = 0.0f; + + ++bone_index; + } + } + } + return model; }