714 lines
18 KiB
C++
714 lines
18 KiB
C++
/*
|
|
* This file is part of the Colobot: Gold Edition source code
|
|
* Copyright (C) 2001-2022, Daniel Roux, EPSITEC SA & TerranovaTeam
|
|
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program. If not, see http://gnu.org/licenses
|
|
*/
|
|
|
|
#include "graphics/model/model_mod.h"
|
|
|
|
#include "common/ioutils.h"
|
|
#include "common/logger.h"
|
|
#include "common/resources/inputstream.h"
|
|
|
|
#include "graphics/model/model_io_exception.h"
|
|
#include "graphics/model/model_io_structs.h"
|
|
|
|
#include <vector>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using namespace IOUtils;
|
|
|
|
using json = nlohmann::json;
|
|
|
|
namespace Gfx::ModelIO
|
|
{
|
|
|
|
struct BufferView
|
|
{
|
|
int buffer = 0;
|
|
size_t offset = 0;
|
|
size_t length = 0;
|
|
};
|
|
|
|
struct Accessor
|
|
{
|
|
int bufferView = 0;
|
|
size_t byteOffset = 0;
|
|
int componentType = 0;
|
|
int count = 0;
|
|
std::string type = "";
|
|
};
|
|
|
|
struct Texture
|
|
{
|
|
int sampler = -1;
|
|
int source = 0;
|
|
};
|
|
|
|
struct Image
|
|
{
|
|
std::string uri;
|
|
};
|
|
|
|
struct Sampler
|
|
{
|
|
|
|
};
|
|
|
|
class GLTFLoader
|
|
{
|
|
public:
|
|
std::unique_ptr<CModel> Load(const std::filesystem::path& path);
|
|
|
|
private:
|
|
void ReadBuffers();
|
|
void ReadBufferViews();
|
|
void ReadMaterials();
|
|
void ReadAccessors();
|
|
void ReadSamplers();
|
|
void ReadImages();
|
|
void ReadTextures();
|
|
void ReadMeshes();
|
|
|
|
std::vector<glm::vec3> ReadPositions(int index);
|
|
std::vector<glm::vec3> ReadNormals(int index);
|
|
std::vector<glm::vec2> ReadUVs(int index);
|
|
std::vector<glm::u8vec4> ReadColors(int index);
|
|
std::vector<unsigned> ReadIndices(int index);
|
|
|
|
std::unique_ptr<CModel> m_model;
|
|
|
|
std::filesystem::path m_directory;
|
|
json m_root;
|
|
|
|
std::vector<std::vector<char>> m_buffers;
|
|
std::vector<BufferView> m_bufferViews;
|
|
std::vector<Material> m_materials;
|
|
std::vector<Accessor> m_accessors;
|
|
std::vector<Texture> m_textures;
|
|
std::vector<Image> m_images;
|
|
std::vector<Sampler> m_samplers;
|
|
};
|
|
|
|
std::unique_ptr<CModel> ReadGLTFModel(const std::filesystem::path& path)
|
|
{
|
|
GLTFLoader loader;
|
|
|
|
return loader.Load(path);
|
|
}
|
|
|
|
std::unique_ptr<CModel> GLTFLoader::Load(const std::filesystem::path& path)
|
|
{
|
|
m_directory = path.parent_path();
|
|
|
|
CInputStream stream(path);
|
|
|
|
stream >> m_root;
|
|
|
|
m_model = std::make_unique<CModel>();
|
|
|
|
ReadBuffers();
|
|
ReadBufferViews();
|
|
ReadAccessors();
|
|
ReadSamplers();
|
|
ReadImages();
|
|
ReadTextures();
|
|
ReadMaterials();
|
|
ReadMeshes();
|
|
|
|
return std::move(m_model);
|
|
}
|
|
|
|
void GLTFLoader::ReadBuffers()
|
|
{
|
|
m_buffers.clear();
|
|
|
|
for (const auto& node : m_root["buffers"])
|
|
{
|
|
size_t length = node["byteLength"].get<int>();
|
|
|
|
std::vector<char> buffer(length);
|
|
|
|
if (node.contains("uri"))
|
|
{
|
|
auto uri = m_directory / node["uri"].get<std::string>();
|
|
|
|
CInputStream stream(uri);
|
|
|
|
stream.read(buffer.data(), buffer.size());
|
|
}
|
|
else
|
|
{
|
|
GetLogger()->Error("Base64 not yet supported\n");
|
|
}
|
|
|
|
m_buffers.emplace_back(std::move(buffer));
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadBufferViews()
|
|
{
|
|
m_bufferViews.clear();
|
|
|
|
for (const auto& node : m_root["bufferViews"])
|
|
{
|
|
BufferView bufferView{};
|
|
|
|
bufferView.buffer = node["buffer"].get<int>();
|
|
bufferView.offset = node["byteOffset"].get<int>();
|
|
bufferView.length = node["byteLength"].get<int>();
|
|
|
|
m_bufferViews.push_back(bufferView);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadMaterials()
|
|
{
|
|
m_materials.clear();
|
|
|
|
for (const auto& material : m_root["materials"])
|
|
{
|
|
Material mat;
|
|
|
|
if (material.contains("doubleSided"))
|
|
{
|
|
mat.cullFace = material["doubleSided"].get<bool>() ? CullFace::NONE : CullFace::BACK;
|
|
}
|
|
|
|
if (material.contains("extras"))
|
|
{
|
|
const auto& extras = material["extras"];
|
|
|
|
if (extras.contains("dirt"))
|
|
{
|
|
int dirt = extras["dirt"].get<int>();
|
|
|
|
std::string texName = std::string("dirty0") + char('0' + dirt) + ".png";
|
|
|
|
mat.detailTexture = texName;
|
|
}
|
|
|
|
if (extras.contains("tag"))
|
|
{
|
|
mat.tag = extras["tag"].get<std::string>();
|
|
}
|
|
|
|
if (extras.contains("recolor"))
|
|
{
|
|
mat.recolor = extras["recolor"].get<std::string>();
|
|
}
|
|
|
|
if (extras.contains("recolor_ref"))
|
|
{
|
|
const auto& color = extras["recolor_ref"];
|
|
|
|
float r = color[0];
|
|
float g = color[1];
|
|
float b = color[2];
|
|
|
|
mat.recolorReference = Color(r, g, b);
|
|
|
|
}
|
|
}
|
|
|
|
if (material.contains("emissiveFactor"))
|
|
{
|
|
const auto& color = material["emissiveFactor"];
|
|
|
|
mat.emissiveColor = {
|
|
color[0].get<float>(),
|
|
color[1].get<float>(),
|
|
color[2].get<float>(),
|
|
0.0
|
|
};
|
|
|
|
mat.emissiveColor = Gfx::ToLinear(mat.emissiveColor);
|
|
}
|
|
else
|
|
{
|
|
mat.emissiveColor = { 0.0, 0.0, 0.0, 0.0 };
|
|
}
|
|
|
|
if (material.contains("emissiveTexture"))
|
|
{
|
|
const auto& tex = material["emissiveTexture"];
|
|
|
|
if (tex.contains("index"))
|
|
{
|
|
int index = tex["index"].get<int>();
|
|
|
|
const auto& texture = m_textures[index];
|
|
|
|
const auto& image = m_images[texture.source];
|
|
|
|
mat.emissiveTexture = image.uri;
|
|
}
|
|
}
|
|
|
|
if (material.contains("pbrMetallicRoughness"))
|
|
{
|
|
const auto& pbr = material["pbrMetallicRoughness"];
|
|
|
|
if (pbr.contains("baseColorFactor"))
|
|
{
|
|
const auto& color = pbr["baseColorFactor"];
|
|
|
|
mat.albedoColor = {
|
|
color[0].get<float>(),
|
|
color[1].get<float>(),
|
|
color[2].get<float>(),
|
|
color[3].get<float>()
|
|
};
|
|
|
|
mat.albedoColor = Gfx::ToLinear(mat.albedoColor);
|
|
}
|
|
else
|
|
{
|
|
mat.albedoColor = { 1.0, 1.0, 1.0, 1.0 };
|
|
}
|
|
|
|
if (pbr.contains("baseColorTexture"))
|
|
{
|
|
const auto& tex = pbr["baseColorTexture"];
|
|
|
|
if (tex.contains("index"))
|
|
{
|
|
int index = tex["index"].get<int>();
|
|
|
|
const auto& texture = m_textures[index];
|
|
|
|
const auto& image = m_images[texture.source];
|
|
|
|
mat.albedoTexture = image.uri;
|
|
}
|
|
}
|
|
|
|
if (pbr.contains("metallicFactor"))
|
|
{
|
|
mat.metalness = pbr["metallicFactor"].get<float>();
|
|
}
|
|
else
|
|
{
|
|
mat.metalness = 1.0;
|
|
}
|
|
|
|
if (pbr.contains("roughnessFactor"))
|
|
{
|
|
mat.roughness = pbr["roughnessFactor"].get<float>();
|
|
}
|
|
else
|
|
{
|
|
mat.roughness = 1.0;
|
|
}
|
|
|
|
if (pbr.contains("metallicRoughnessTexture"))
|
|
{
|
|
const auto& tex = pbr["metallicRoughnessTexture"];
|
|
|
|
if (tex.contains("index"))
|
|
{
|
|
int index = tex["index"].get<int>();
|
|
|
|
const auto& texture = m_textures[index];
|
|
|
|
const auto& image = m_images[texture.source];
|
|
|
|
mat.materialTexture = image.uri;
|
|
}
|
|
}
|
|
|
|
if (pbr.contains("occlusionTexture"))
|
|
{
|
|
const auto& tex = pbr["occlusionTexture"];
|
|
|
|
if (tex.contains("index"))
|
|
{
|
|
int index = tex["index"].get<int>();
|
|
|
|
const auto& texture = m_textures[index];
|
|
|
|
[[maybe_unused]] const auto& image = m_images[texture.source];
|
|
|
|
if (tex.contains("strength"))
|
|
{
|
|
mat.aoStrength = tex["strength"].get<float>();
|
|
}
|
|
else
|
|
{
|
|
mat.aoStrength = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (material.contains("normalTexture"))
|
|
{
|
|
const auto& tex = material["normalTexture"];
|
|
|
|
if (tex.contains("index"))
|
|
{
|
|
int index = tex["index"].get<int>();
|
|
|
|
const auto& texture = m_textures[index];
|
|
|
|
const auto& image = m_images[texture.source];
|
|
|
|
mat.normalTexture = image.uri;
|
|
}
|
|
}
|
|
|
|
if (material.contains("alphaMode"))
|
|
{
|
|
auto mode = material["alphaMode"].get<std::string>();
|
|
|
|
if (mode == "OPAQUE")
|
|
mat.alphaMode = AlphaMode::NONE;
|
|
else if (mode == "MASK")
|
|
mat.alphaMode = AlphaMode::MASK;
|
|
else if (mode == "BLEND")
|
|
mat.alphaMode = AlphaMode::BLEND;
|
|
}
|
|
|
|
if (material.contains("alphaCutoff"))
|
|
{
|
|
mat.alphaThreshold = material["alphaCutoff"].get<float>();
|
|
}
|
|
|
|
m_materials.push_back(mat);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadAccessors()
|
|
{
|
|
m_accessors.clear();
|
|
|
|
for (const auto& node : m_root["accessors"])
|
|
{
|
|
Accessor accessor{};
|
|
|
|
accessor.bufferView = node["bufferView"].get<int>();
|
|
|
|
if (node.contains("byteOffset"))
|
|
accessor.byteOffset = node["byteOffset"].get<int>();
|
|
else
|
|
accessor.byteOffset = 0;
|
|
accessor.count = node["count"].get<int>();
|
|
accessor.componentType = node["componentType"].get<int>();
|
|
accessor.type = node["type"].get<std::string>();
|
|
|
|
m_accessors.push_back(accessor);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadSamplers()
|
|
{
|
|
m_samplers.clear();
|
|
|
|
for ([[maybe_unused]] const auto& node : m_root["samplers"])
|
|
{
|
|
Sampler sampler{};
|
|
|
|
m_samplers.push_back(sampler);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadImages()
|
|
{
|
|
m_images.clear();
|
|
|
|
for (const auto& node : m_root["images"])
|
|
{
|
|
Image image{};
|
|
|
|
if (node.contains("uri"))
|
|
{
|
|
image.uri = node["uri"].get<std::string>();
|
|
}
|
|
|
|
m_images.push_back(image);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadTextures()
|
|
{
|
|
m_textures.clear();
|
|
|
|
for (const auto& node : m_root["textures"])
|
|
{
|
|
Texture texture{};
|
|
|
|
if (node.contains("sampler"))
|
|
texture.sampler = node["sampler"].get<int>();
|
|
|
|
texture.source = node["source"].get<int>();
|
|
|
|
m_textures.push_back(texture);
|
|
}
|
|
}
|
|
|
|
void GLTFLoader::ReadMeshes()
|
|
{
|
|
m_model = std::make_unique<CModel>();
|
|
|
|
for (const auto& node : m_root["meshes"])
|
|
{
|
|
auto name = node["name"].get<std::string>();
|
|
|
|
auto mesh = std::make_unique<CModelMesh>();
|
|
|
|
for (const auto& primitive : node["primitives"])
|
|
{
|
|
const auto& material = m_materials[primitive["material"].get<int>()];
|
|
|
|
auto part = mesh->AddPart(material);
|
|
|
|
if (primitive.contains("attributes"))
|
|
{
|
|
const auto& attributes = primitive["attributes"];
|
|
|
|
auto positions = ReadPositions(attributes["POSITION"].get<int>());
|
|
|
|
if (positions.empty()) continue;
|
|
|
|
part->SetVertices(positions.size());
|
|
|
|
for (size_t i = 0; i < positions.size(); i++)
|
|
part->GetVertex(i).SetPosition(positions[i] * glm::vec3(1.0f, 1.0f, -1.0f));
|
|
|
|
if (attributes.contains("NORMAL"))
|
|
{
|
|
part->Add(VertexAttribute::NORMAL);
|
|
|
|
auto normals = ReadNormals(attributes["NORMAL"].get<int>());
|
|
|
|
for (size_t i = 0; i < normals.size(); i++)
|
|
part->GetVertex(i).SetNormal(normals[i] * glm::vec3(1.0f, 1.0f, -1.0f));
|
|
}
|
|
|
|
if (attributes.contains("TEXCOORD_0"))
|
|
{
|
|
part->Add(VertexAttribute::UV1);
|
|
|
|
auto uvs = ReadUVs(attributes["TEXCOORD_0"].get<int>());
|
|
|
|
for (size_t i = 0; i < uvs.size(); i++)
|
|
part->GetVertex(i).SetUV1(uvs[i]);
|
|
}
|
|
|
|
if (attributes.contains("TEXCOORD_1"))
|
|
{
|
|
part->Add(VertexAttribute::UV2);
|
|
|
|
auto uvs = ReadUVs(attributes["TEXCOORD_1"].get<int>());
|
|
|
|
for (size_t i = 0; i < uvs.size(); i++)
|
|
part->GetVertex(i).SetUV2(uvs[i]);
|
|
}
|
|
|
|
if (attributes.contains("COLOR_0"))
|
|
{
|
|
part->Add(VertexAttribute::COLOR);
|
|
|
|
auto colors = ReadColors(attributes["COLOR_0"].get<int>());
|
|
|
|
for (size_t i = 0; i < colors.size(); i++)
|
|
part->GetVertex(i).SetColor(colors[i]);
|
|
}
|
|
}
|
|
|
|
if (primitive.contains("indices"))
|
|
{
|
|
auto indices = ReadIndices(primitive["indices"].get<int>());
|
|
|
|
part->SetIndices(indices.size());
|
|
|
|
for (unsigned i = 0; i < indices.size(); i += 3)
|
|
{
|
|
part->SetIndex(i + 0, indices[i + 0]);
|
|
part->SetIndex(i + 1, indices[i + 2]);
|
|
part->SetIndex(i + 2, indices[i + 1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t vertices = part->GetVertexCount();
|
|
|
|
part->SetIndices(vertices);
|
|
|
|
for (unsigned i = 0; i < vertices; i += 3)
|
|
{
|
|
part->SetIndex(i + 0, i + 0);
|
|
part->SetIndex(i + 1, i + 2);
|
|
part->SetIndex(i + 2, i + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mesh->GetPartCount() > 0)
|
|
m_model->AddMesh(name, std::move(mesh));
|
|
}
|
|
}
|
|
|
|
std::vector<glm::vec3> GLTFLoader::ReadPositions(int index)
|
|
{
|
|
const auto& accessor = m_accessors[index];
|
|
|
|
std::vector<glm::vec3> positions(accessor.count);
|
|
|
|
const auto& bufferView = m_bufferViews[accessor.bufferView];
|
|
|
|
const auto& buffer = m_buffers[bufferView.buffer];
|
|
|
|
auto data = reinterpret_cast<const glm::vec3*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
positions[i] = data[i];
|
|
|
|
return positions;
|
|
}
|
|
|
|
std::vector<glm::vec3> GLTFLoader::ReadNormals(int index)
|
|
{
|
|
const auto& accessor = m_accessors[index];
|
|
|
|
std::vector<glm::vec3> normals(accessor.count);
|
|
|
|
const auto& bufferView = m_bufferViews[accessor.bufferView];
|
|
|
|
const auto& buffer = m_buffers[bufferView.buffer];
|
|
|
|
auto data = reinterpret_cast<const glm::vec3*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
normals[i] = data[i];
|
|
|
|
return normals;
|
|
}
|
|
|
|
std::vector<glm::vec2> GLTFLoader::ReadUVs(int index)
|
|
{
|
|
const auto& accessor = m_accessors[index];
|
|
|
|
std::vector<glm::vec2> uvs(accessor.count);
|
|
|
|
const auto& bufferView = m_bufferViews[accessor.bufferView];
|
|
|
|
const auto& buffer = m_buffers[bufferView.buffer];
|
|
|
|
if (accessor.componentType == 5126)
|
|
{
|
|
auto data = reinterpret_cast<const glm::vec2*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
uvs[i] = data[i];
|
|
}
|
|
else
|
|
{
|
|
GetLogger()->Error("Invalid UV type: %d\n", accessor.componentType);
|
|
}
|
|
|
|
return uvs;
|
|
}
|
|
|
|
std::vector<glm::u8vec4> GLTFLoader::ReadColors(int index)
|
|
{
|
|
const auto& accessor = m_accessors[index];
|
|
|
|
std::vector<glm::u8vec4> colors(accessor.count);
|
|
|
|
const auto& bufferView = m_bufferViews[accessor.bufferView];
|
|
|
|
const auto& buffer = m_buffers[bufferView.buffer];
|
|
|
|
if (accessor.componentType == 5121)
|
|
{
|
|
auto data = reinterpret_cast<const glm::u8vec4*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
colors[i] = data[i];
|
|
}
|
|
else if (accessor.componentType == 5123)
|
|
{
|
|
auto data = reinterpret_cast<const glm::u16vec4*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
colors[i] = glm::u8vec4(data[i] / glm::u16vec4(256));
|
|
}
|
|
else if (accessor.componentType == 5126)
|
|
{
|
|
auto data = reinterpret_cast<const glm::vec4*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
colors[i] = glm::u8vec4(data[i] * 255.0f);
|
|
}
|
|
else
|
|
{
|
|
GetLogger()->Error("Invalid color type: %d\n", accessor.componentType);
|
|
}
|
|
|
|
// Fix for bug in Blender where it exports vertex colors in sRGB instead of linear space
|
|
for (size_t i = 0; i < colors.size(); i++)
|
|
{
|
|
auto color = Gfx::IntColorToColor(Gfx::IntColor(colors[i]));
|
|
|
|
color = Gfx::ToLinear(color);
|
|
|
|
colors[i] = Gfx::ColorToIntColor(color);
|
|
}
|
|
|
|
return colors;
|
|
}
|
|
|
|
std::vector<unsigned> GLTFLoader::ReadIndices(int index)
|
|
{
|
|
const auto& accessor = m_accessors[index];
|
|
|
|
std::vector<unsigned> indices(accessor.count);
|
|
|
|
const auto& bufferView = m_bufferViews[accessor.bufferView];
|
|
|
|
const auto& buffer = m_buffers[bufferView.buffer];
|
|
|
|
// Unsigned byte components
|
|
if (accessor.componentType == 5121)
|
|
{
|
|
auto data = reinterpret_cast<const uint8_t*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
indices[i] = data[i];
|
|
}
|
|
// Unsigned short components
|
|
else if (accessor.componentType == 5123)
|
|
{
|
|
auto data = reinterpret_cast<const uint16_t*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
indices[i] = data[i];
|
|
}
|
|
// Unsigned int components
|
|
else if (accessor.componentType == 5125)
|
|
{
|
|
auto data = reinterpret_cast<const uint32_t*>(buffer.data() + bufferView.offset);
|
|
|
|
for (size_t i = 0; i < accessor.count; i++)
|
|
indices[i] = data[i];
|
|
}
|
|
|
|
return indices;
|
|
}
|
|
|
|
}
|