Crafter.Build/implementations/Crafter.Build-Asset.cpp

120 lines
5 KiB
C++
Raw Permalink Normal View History

2026-05-12 01:16:40 +02:00
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module Crafter.Build:Asset_impl;
import :Asset;
import std;
#ifdef CRAFTER_BUILD_HAS_ASSET
import Crafter.Asset;
import Crafter.Math;
#endif
namespace fs = std::filesystem;
namespace Crafter {
std::string CompressAsset(const fs::path& input, const fs::path& output) {
std::string ext = input.extension().string();
2026-05-19 00:50:06 +02:00
// Case-insensitive extension match — Sponza's textures dir mixes
// case (.tga and .TGA both appear in the wild).
for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
2026-05-12 01:16:40 +02:00
#ifdef CRAFTER_BUILD_HAS_ASSET
try {
2026-05-19 00:50:06 +02:00
// stb_image (the loader behind LoadPNG) handles all of these,
// so a single branch covers every raster format we ship. The
// function is misnamed for historical reasons.
if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {
2026-05-12 01:16:40 +02:00
auto tex = TextureAsset<Vector<std::uint8_t, 4, 4>>::LoadPNG<std::uint8_t>(input);
tex.SaveCompressed(output);
} else if (ext == ".obj") {
auto mesh = MeshAsset<VertexNormalTangentUVPacked>::LoadOBJ(input);
mesh.SaveCompressed(output);
} else {
return std::format("{}: unsupported asset extension '{}'", input.string(), ext);
}
} catch (const std::exception& e) {
return std::format("{}: {}", input.string(), e.what());
}
return {};
#else
return std::format(
"{}: crafter-build was bootstrapped without Crafter.Asset linkage; "
"rebuild via `./bin/crafter-build` so the self-host pass picks up the "
"Crafter.Asset dep declared in project.cpp",
input.string());
2026-05-19 00:50:06 +02:00
#endif
}
std::string BuildOBJBundle(
const fs::path& objPath,
const fs::path& mtlPath,
const fs::path& outDir,
std::uint16_t albedoSize)
{
#ifdef CRAFTER_BUILD_HAS_ASSET
const fs::path manifest = outDir / "scene.txt";
if (fs::exists(manifest)) return {};
try {
fs::create_directories(outDir);
auto materials = LoadMTL(mtlPath);
auto meshes = MeshAsset<VertexNormalTangentUVPacked>::LoadOBJSplit(objPath);
std::vector<std::string> uniqueAlbedos;
std::unordered_map<std::string, std::uint32_t> albedoIndex;
for (auto& [matName, _] : meshes) {
auto matIt = materials.find(matName);
if (matIt == materials.end() || matIt->second.mapKd.empty()) continue;
const std::string& kd = matIt->second.mapKd;
if (!albedoIndex.contains(kd)) {
albedoIndex.emplace(kd, static_cast<std::uint32_t>(uniqueAlbedos.size()));
uniqueAlbedos.push_back(kd);
}
}
std::vector<std::int32_t> meshAlbedoIdx;
std::uint32_t emitted = 0;
for (auto& [matName, mesh] : meshes) {
if (mesh.vertexes.empty() || mesh.indexes.empty()) continue;
mesh.SaveCompressed(outDir / std::format("mesh_{}.cmesh", emitted));
std::int32_t a = -1;
if (auto it = materials.find(matName);
it != materials.end() && !it->second.mapKd.empty())
a = static_cast<std::int32_t>(albedoIndex.at(it->second.mapKd));
meshAlbedoIdx.push_back(a);
++emitted;
}
for (std::uint32_t i = 0; i < uniqueAlbedos.size(); ++i) {
const fs::path texPath = mtlPath.parent_path() / uniqueAlbedos[i];
auto tex = TextureAsset<Vector<std::uint8_t, 4, 4>>::LoadPNG<std::uint8_t>(texPath);
tex.Resize(albedoSize, albedoSize);
tex.SaveCompressed(outDir / std::format("tex_{}.ctex", i));
}
std::ofstream m(manifest);
m << uniqueAlbedos.size() << "\n" << emitted << "\n";
for (std::int32_t a : meshAlbedoIdx) m << a << "\n";
} catch (const std::exception& e) {
return std::format("BuildOBJBundle: {}", e.what());
}
return {};
#else
return "BuildOBJBundle: crafter-build was bootstrapped without Crafter.Asset linkage; "
"rebuild via crafter-build itself to pick up the Crafter.Asset dep";
2026-05-12 01:16:40 +02:00
#endif
}
}