2026-07-23 01:24:42 +02:00
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
2026-05-12 01:16:40 +02:00
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).
2026-07-23 01:24:42 +02:00
for ( char & c : ext ) c = static_cast < char > ( std : : tolower ( static_cast < std : : uint8_t > ( 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
2026-07-23 01:24:42 +02:00
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
}
2026-07-23 01:24:42 +02:00
std : : string BuildOBJBundle ( const fs : : path & objPath , const fs : : path & mtlPath , const fs : : path & outDir , std : : uint16_t albedoSize ) {
2026-05-19 00:50:06 +02:00
# 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 ;
2026-07-23 01:24:42 +02:00
if ( auto it = materials . find ( matName ) ; it ! = materials . end ( ) & & ! it - > second . mapKd . empty ( ) ) a = static_cast < std : : int32_t > ( albedoIndex . at ( it - > second . mapKd ) ) ;
2026-05-19 00:50:06 +02:00
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
}
}