mirror of https://github.com/AxioDL/metaforce.git
Refactor for original/pc dataspec handling
This commit is contained in:
parent
a866939b09
commit
fedc93912d
|
@ -72,14 +72,20 @@ set(HECL_DATASPEC_DECLS
|
||||||
namespace DataSpec
|
namespace DataSpec
|
||||||
{
|
{
|
||||||
extern hecl::Database::DataSpecEntry SpecEntMP1;
|
extern hecl::Database::DataSpecEntry SpecEntMP1;
|
||||||
|
extern hecl::Database::DataSpecEntry SpecEntMP1PC;
|
||||||
extern hecl::Database::DataSpecEntry SpecEntMP2;
|
extern hecl::Database::DataSpecEntry SpecEntMP2;
|
||||||
|
extern hecl::Database::DataSpecEntry SpecEntMP2PC;
|
||||||
extern hecl::Database::DataSpecEntry SpecEntMP3;
|
extern hecl::Database::DataSpecEntry SpecEntMP3;
|
||||||
|
extern hecl::Database::DataSpecEntry SpecEntMP3PC;
|
||||||
}")
|
}")
|
||||||
set(HECL_DATASPEC_PUSHES
|
set(HECL_DATASPEC_PUSHES
|
||||||
" /* RetroCommon */
|
" /* RetroCommon */
|
||||||
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP1);
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP1);
|
||||||
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP1PC);
|
||||||
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP2);
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP2);
|
||||||
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3);")
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP2PC);
|
||||||
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3);
|
||||||
|
hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3PC);")
|
||||||
add_subdirectory(hecl)
|
add_subdirectory(hecl)
|
||||||
add_definitions(${BOO_SYS_DEFINES})
|
add_definitions(${BOO_SYS_DEFINES})
|
||||||
add_subdirectory(specter)
|
add_subdirectory(specter)
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace DNAParticle
|
||||||
template <class IDType>
|
template <class IDType>
|
||||||
struct GPSM : BigYAML
|
struct GPSM : BigYAML
|
||||||
{
|
{
|
||||||
static const char* DNAType() {return "urde::GPSM";}
|
static const char* DNAType() {return "GPSM";}
|
||||||
const char* DNATypeV() const {return DNAType();}
|
const char* DNATypeV() const {return DNAType();}
|
||||||
|
|
||||||
VectorElementFactory x0_PSIV;
|
VectorElementFactory x0_PSIV;
|
||||||
|
|
|
@ -2,13 +2,67 @@
|
||||||
#include <squish.h>
|
#include <squish.h>
|
||||||
#include "TXTR.hpp"
|
#include "TXTR.hpp"
|
||||||
#include "PAK.hpp"
|
#include "PAK.hpp"
|
||||||
|
#include "athena/FileWriter.hpp"
|
||||||
|
|
||||||
namespace DataSpec
|
namespace DataSpec
|
||||||
{
|
{
|
||||||
|
|
||||||
static logvisor::Module Log("libpng");
|
static logvisor::Module Log("libpng");
|
||||||
|
|
||||||
/* GX uses this upsampling technique to prevent banding on downsampled texture formats */
|
static int CountBits(uint32_t n)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
for (int i=0 ; i<32 ; ++i)
|
||||||
|
if (((n >> i) & 1) != 0)
|
||||||
|
++ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Box filter algorithm (for mipmapping) */
|
||||||
|
static void BoxFilter(const uint8_t* input, unsigned chanCount,
|
||||||
|
unsigned inWidth, unsigned inHeight, uint8_t* output)
|
||||||
|
{
|
||||||
|
unsigned mipWidth = 1;
|
||||||
|
unsigned mipHeight = 1;
|
||||||
|
if (inWidth > 1)
|
||||||
|
mipWidth = inWidth / 2;
|
||||||
|
if (inHeight > 1)
|
||||||
|
mipHeight = inHeight / 2;
|
||||||
|
|
||||||
|
int y,x,c;
|
||||||
|
for (y=0 ; y<mipHeight ; ++y)
|
||||||
|
{
|
||||||
|
unsigned mip_line_base = mipWidth * y;
|
||||||
|
unsigned in1_line_base = inWidth * (y*2);
|
||||||
|
unsigned in2_line_base = inWidth * (y*2+1);
|
||||||
|
for (x=0 ; x<mipWidth ; ++x)
|
||||||
|
{
|
||||||
|
uint8_t* out = &output[(mip_line_base+x)*chanCount];
|
||||||
|
for (c=0 ; c<chanCount ; ++c)
|
||||||
|
{
|
||||||
|
out[c] = 0;
|
||||||
|
out[c] += input[(in1_line_base+(x*2))*chanCount+c] / 4;
|
||||||
|
out[c] += input[(in1_line_base+(x*2+1))*chanCount+c] / 4;
|
||||||
|
out[c] += input[(in2_line_base+(x*2))*chanCount+c] / 4;
|
||||||
|
out[c] += input[(in2_line_base+(x*2+1))*chanCount+c] / 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t ComputeMippedTexelCount(unsigned inWidth, unsigned inHeight)
|
||||||
|
{
|
||||||
|
size_t ret = 0;
|
||||||
|
while (inWidth > 0 && inHeight > 0)
|
||||||
|
{
|
||||||
|
ret += inWidth * inHeight;
|
||||||
|
inWidth /= 2;
|
||||||
|
inHeight /= 2;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GX uses this upsampling technique with downsampled texture formats */
|
||||||
static inline uint8_t Convert3To8(uint8_t v)
|
static inline uint8_t Convert3To8(uint8_t v)
|
||||||
{
|
{
|
||||||
/* Swizzle bits: 00000123 -> 12312312 */
|
/* Swizzle bits: 00000123 -> 12312312 */
|
||||||
|
@ -500,6 +554,11 @@ bool TXTR::Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath)
|
||||||
png_init_io(png, fp);
|
png_init_io(png, fp);
|
||||||
png_infop info = png_create_info_struct(png);
|
png_infop info = png_create_info_struct(png);
|
||||||
|
|
||||||
|
png_text textStruct = {};
|
||||||
|
textStruct.key = png_charp("urde_nomip");
|
||||||
|
if (numMips == 1)
|
||||||
|
png_set_text(png, info, &textStruct, 1);
|
||||||
|
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -547,4 +606,226 @@ bool TXTR::Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TXTR::CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath)
|
||||||
|
{
|
||||||
|
FILE* inf = hecl::Fopen(inPath.getAbsolutePath().c_str(), _S("rb"));
|
||||||
|
if (!inf)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error,
|
||||||
|
_S("Unable to open '%s' for reading"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate PNG */
|
||||||
|
char header[8];
|
||||||
|
fread(header, 1, 8, inf);
|
||||||
|
if (png_sig_cmp((png_const_bytep)header, 0, 8))
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("invalid PNG signature in '%s'"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
fclose(inf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup PNG reader */
|
||||||
|
png_structp pngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
if (!pngRead)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, "unable to initialize libpng");
|
||||||
|
fclose(inf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
png_infop info = png_create_info_struct(pngRead);
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, "unable to initialize libpng info");
|
||||||
|
fclose(inf);
|
||||||
|
png_destroy_read_struct(&pngRead, nullptr, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(pngRead)))
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to initialize libpng I/O for '%s'"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
fclose(inf);
|
||||||
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_init_io(pngRead, inf);
|
||||||
|
png_set_sig_bytes(pngRead, 8);
|
||||||
|
|
||||||
|
png_read_info(pngRead, info);
|
||||||
|
|
||||||
|
png_uint_32 width = png_get_image_width(pngRead, info);
|
||||||
|
png_uint_32 height = png_get_image_height(pngRead, info);
|
||||||
|
png_byte colorType = png_get_color_type(pngRead, info);
|
||||||
|
png_byte bitDepth = png_get_bit_depth(pngRead, info);
|
||||||
|
|
||||||
|
/* Disable mipmapping if urde_nomip embedded */
|
||||||
|
bool mipmap = true;
|
||||||
|
png_text* textStruct;
|
||||||
|
int numText;
|
||||||
|
png_get_text(pngRead, info, &textStruct, &numText);
|
||||||
|
for (int i=0 ; i<numText ; ++i)
|
||||||
|
if (!strcmp(textStruct[i].key, "urde_nomip"))
|
||||||
|
mipmap = false;
|
||||||
|
|
||||||
|
/* Compute mipmap levels */
|
||||||
|
size_t numMips = 0;
|
||||||
|
if (mipmap && CountBits(width) == 1 && CountBits(height) == 1)
|
||||||
|
{
|
||||||
|
size_t index = std::min(width, height);
|
||||||
|
while (index >>= 1) ++numMips;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
numMips = 1;
|
||||||
|
|
||||||
|
if (bitDepth != 8)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("'%s' is not 8 bits-per-channel"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
fclose(inf);
|
||||||
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t rowSize = 0;
|
||||||
|
switch (colorType)
|
||||||
|
{
|
||||||
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
|
rowSize = width;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
|
rowSize = width * 2;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_RGB:
|
||||||
|
rowSize = width * 3;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
|
rowSize = width * 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.report(logvisor::Error, _S("unsupported color type in '%s'"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
fclose(inf);
|
||||||
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Intermediate row-read buf (file components) */
|
||||||
|
std::unique_ptr<uint8_t[]> rowBuf(new uint8_t[rowSize]);
|
||||||
|
|
||||||
|
/* Final mipmapped buf (RGBA components) */
|
||||||
|
std::unique_ptr<uint8_t[]> bufOut;
|
||||||
|
size_t bufLen = 0;
|
||||||
|
if (numMips > 1)
|
||||||
|
bufLen = ComputeMippedTexelCount(width, height) * 4;
|
||||||
|
else
|
||||||
|
bufLen = width * height * 4;
|
||||||
|
bufOut.reset(new uint8_t[bufLen]);
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(pngRead)))
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to read image in '%s'"),
|
||||||
|
inPath.getAbsolutePath().c_str());
|
||||||
|
fclose(inf);
|
||||||
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read and make RGBA */
|
||||||
|
for (png_uint_32 r=0 ; r<height ; ++r)
|
||||||
|
{
|
||||||
|
png_read_row(pngRead, rowBuf.get(), nullptr);
|
||||||
|
switch (colorType)
|
||||||
|
{
|
||||||
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
|
for (int i=0 ; i<width ; ++i)
|
||||||
|
{
|
||||||
|
size_t outbase = (r*width+i)*4;
|
||||||
|
bufOut[outbase] = rowBuf[i];
|
||||||
|
bufOut[outbase+1] = rowBuf[i];
|
||||||
|
bufOut[outbase+2] = rowBuf[i];
|
||||||
|
bufOut[outbase+3] = rowBuf[i];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
|
for (int i=0 ; i<width ; ++i)
|
||||||
|
{
|
||||||
|
size_t inbase = i*2;
|
||||||
|
size_t outbase = (r*width+i)*4;
|
||||||
|
bufOut[outbase] = rowBuf[inbase];
|
||||||
|
bufOut[outbase+1] = rowBuf[inbase];
|
||||||
|
bufOut[outbase+2] = rowBuf[inbase];
|
||||||
|
bufOut[outbase+3] = rowBuf[inbase+1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_RGB:
|
||||||
|
for (int i=0 ; i<width ; ++i)
|
||||||
|
{
|
||||||
|
size_t inbase = i*3;
|
||||||
|
size_t outbase = (r*width+i)*4;
|
||||||
|
bufOut[outbase] = rowBuf[inbase];
|
||||||
|
bufOut[outbase+1] = rowBuf[inbase+1];
|
||||||
|
bufOut[outbase+2] = rowBuf[inbase+2];
|
||||||
|
bufOut[outbase+3] = 0xff;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
|
for (int i=0 ; i<width ; ++i)
|
||||||
|
{
|
||||||
|
size_t inbase = i*4;
|
||||||
|
size_t outbase = (r*width+i)*4;
|
||||||
|
bufOut[outbase] = rowBuf[inbase];
|
||||||
|
bufOut[outbase+1] = rowBuf[inbase+1];
|
||||||
|
bufOut[outbase+2] = rowBuf[inbase+2];
|
||||||
|
bufOut[outbase+3] = rowBuf[inbase+3];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png_destroy_read_struct(&pngRead, &info, nullptr);
|
||||||
|
fclose(inf);
|
||||||
|
|
||||||
|
/* Perform box-filter mipmap */
|
||||||
|
if (numMips > 1)
|
||||||
|
{
|
||||||
|
const uint8_t* filterIn = bufOut.get();
|
||||||
|
uint8_t* filterOut = bufOut.get() + width * height * 4;
|
||||||
|
unsigned filterWidth = width;
|
||||||
|
unsigned filterHeight = height;
|
||||||
|
for (size_t i=1 ; i<numMips ; ++i)
|
||||||
|
{
|
||||||
|
BoxFilter(filterIn, 4, filterWidth, filterHeight, filterOut);
|
||||||
|
filterIn += filterWidth * filterHeight * 4;
|
||||||
|
filterWidth /= 2;
|
||||||
|
filterHeight /= 2;
|
||||||
|
filterOut += filterWidth * filterHeight * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do write out */
|
||||||
|
athena::io::FileWriter outf(outPath.getAbsolutePath(), true, false);
|
||||||
|
if (outf.hasError())
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error,
|
||||||
|
_S("Unable to open '%s' for writing"),
|
||||||
|
outPath.getAbsolutePath().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outf.writeInt32Big(16);
|
||||||
|
outf.writeInt16Big(width);
|
||||||
|
outf.writeInt16Big(height);
|
||||||
|
outf.writeInt32Big(numMips);
|
||||||
|
outf.writeUBytes(bufOut.get(), bufLen);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ struct TXTR
|
||||||
{
|
{
|
||||||
static bool Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath);
|
static bool Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath);
|
||||||
static bool Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath);
|
static bool Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath);
|
||||||
|
static bool CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,7 @@ struct SpecMP1 : SpecBase
|
||||||
{
|
{
|
||||||
for (const hecl::SystemString& arg : args)
|
for (const hecl::SystemString& arg : args)
|
||||||
{
|
{
|
||||||
#if HECL_UCS2
|
std::string lowerArg = hecl::SystemUTF8View(arg).str();
|
||||||
std::string lowerArg = hecl::WideToUTF8(arg);
|
|
||||||
#else
|
|
||||||
std::string lowerArg = arg;
|
|
||||||
#endif
|
|
||||||
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
||||||
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
||||||
good = true;
|
good = true;
|
||||||
|
@ -327,6 +323,14 @@ hecl::Database::DataSpecEntry SpecEntMP1 =
|
||||||
-> hecl::Database::IDataSpec* {return new struct SpecMP1(project);}
|
-> hecl::Database::IDataSpec* {return new struct SpecMP1(project);}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hecl::Database::DataSpecEntry SpecEntMP1PC =
|
||||||
|
{
|
||||||
|
_S("MP1-PC"),
|
||||||
|
_S("Data specification for PC-optimized Metroid Prime engine"),
|
||||||
|
[](hecl::Database::Project& project, hecl::Database::DataSpecTool)
|
||||||
|
-> hecl::Database::IDataSpec* {return nullptr;}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -78,11 +78,7 @@ struct SpecMP2 : SpecBase
|
||||||
{
|
{
|
||||||
for (const hecl::SystemString& arg : args)
|
for (const hecl::SystemString& arg : args)
|
||||||
{
|
{
|
||||||
#if HECL_UCS2
|
std::string lowerArg = hecl::SystemUTF8View(arg).str();
|
||||||
std::string lowerArg = hecl::WideToUTF8(arg);
|
|
||||||
#else
|
|
||||||
std::string lowerArg = arg;
|
|
||||||
#endif
|
|
||||||
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
||||||
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
||||||
good = true;
|
good = true;
|
||||||
|
@ -309,4 +305,12 @@ hecl::Database::DataSpecEntry SpecEntMP2
|
||||||
-> hecl::Database::IDataSpec* {return new struct SpecMP2(project);}
|
-> hecl::Database::IDataSpec* {return new struct SpecMP2(project);}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
hecl::Database::DataSpecEntry SpecEntMP2PC =
|
||||||
|
{
|
||||||
|
_S("MP2-PC"),
|
||||||
|
_S("Data specification for PC-optimized Metroid Prime 2 engine"),
|
||||||
|
[](hecl::Database::Project& project, hecl::Database::DataSpecTool)
|
||||||
|
-> hecl::Database::IDataSpec* {return nullptr;}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,11 +103,7 @@ struct SpecMP3 : SpecBase
|
||||||
{
|
{
|
||||||
for (const hecl::SystemString& arg : args)
|
for (const hecl::SystemString& arg : args)
|
||||||
{
|
{
|
||||||
#if HECL_UCS2
|
std::string lowerArg = hecl::SystemUTF8View(arg).str();
|
||||||
std::string lowerArg = hecl::WideToUTF8(arg);
|
|
||||||
#else
|
|
||||||
std::string lowerArg = arg;
|
|
||||||
#endif
|
|
||||||
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower);
|
||||||
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
if (!lowerArg.compare(0, lowerBase.size(), lowerBase))
|
||||||
good = true;
|
good = true;
|
||||||
|
@ -493,4 +489,12 @@ hecl::Database::DataSpecEntry SpecEntMP3
|
||||||
-> hecl::Database::IDataSpec* {return new struct SpecMP3(project);}
|
-> hecl::Database::IDataSpec* {return new struct SpecMP3(project);}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
hecl::Database::DataSpecEntry SpecEntMP3PC =
|
||||||
|
{
|
||||||
|
_S("MP3-PC"),
|
||||||
|
_S("Data specification for PC-optimized Metroid Prime 3 engine"),
|
||||||
|
[](hecl::Database::Project& project, hecl::Database::DataSpecTool)
|
||||||
|
-> hecl::Database::IDataSpec* {return nullptr;}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@ add_executable(urde WIN32 MACOSX_BUNDLE
|
||||||
ParticleEditor.hpp ParticleEditor.cpp atdna_ParticleEditor.cpp
|
ParticleEditor.hpp ParticleEditor.cpp atdna_ParticleEditor.cpp
|
||||||
InformationCenter.hpp InformationCenter.hpp atdna_InformationCenter.cpp
|
InformationCenter.hpp InformationCenter.hpp atdna_InformationCenter.cpp
|
||||||
ProjectManager.hpp ProjectManager.cpp
|
ProjectManager.hpp ProjectManager.cpp
|
||||||
ProjectResourceFactory.hpp ProjectResourceFactory.cpp
|
ProjectResourceFactoryBase.hpp ProjectResourceFactoryBase.cpp
|
||||||
|
ProjectResourceFactoryMP1.hpp ProjectResourceFactoryMP1.cpp
|
||||||
ViewManager.hpp ViewManager.cpp
|
ViewManager.hpp ViewManager.cpp
|
||||||
Resource.hpp Resource.cpp
|
Resource.hpp Resource.cpp
|
||||||
Camera.hpp Camera.cpp)
|
Camera.hpp Camera.cpp)
|
||||||
|
|
|
@ -6,22 +6,9 @@ namespace urde
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("URDE::ProjectManager");
|
static logvisor::Module Log("URDE::ProjectManager");
|
||||||
|
|
||||||
void ProjectManager::IndexMP1Resources()
|
|
||||||
{
|
|
||||||
const std::vector<hecl::Database::Project::ProjectDataSpec>& specs = m_proj->getDataSpecs();
|
|
||||||
for (const hecl::Database::Project::ProjectDataSpec& spec : m_proj->getDataSpecs())
|
|
||||||
{
|
|
||||||
if (&spec.spec == &DataSpec::SpecEntMP1)
|
|
||||||
{
|
|
||||||
m_factory.BuildObjectMap(spec);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectManager::m_registeredSpecs = false;
|
bool ProjectManager::m_registeredSpecs = false;
|
||||||
ProjectManager::ProjectManager(ViewManager &vm)
|
ProjectManager::ProjectManager(ViewManager &vm)
|
||||||
: m_vm(vm), m_objStore(m_factory)
|
: m_vm(vm), m_objStore(m_factoryMP1)
|
||||||
{
|
{
|
||||||
if (!m_registeredSpecs)
|
if (!m_registeredSpecs)
|
||||||
{
|
{
|
||||||
|
@ -103,7 +90,7 @@ bool ProjectManager::openProject(const hecl::SystemString& path)
|
||||||
m_vm.ProjectChanged(*m_proj);
|
m_vm.ProjectChanged(*m_proj);
|
||||||
m_vm.SetupEditorView(r);
|
m_vm.SetupEditorView(r);
|
||||||
|
|
||||||
IndexMP1Resources();
|
m_factoryMP1.IndexMP1Resources(*m_proj);
|
||||||
m_vm.BuildTestPART(m_objStore);
|
m_vm.BuildTestPART(m_objStore);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include <hecl/Database.hpp>
|
#include <hecl/Database.hpp>
|
||||||
#include <athena/DNAYaml.hpp>
|
#include <athena/DNAYaml.hpp>
|
||||||
#include "ProjectResourceFactory.hpp"
|
#include "ProjectResourceFactoryMP1.hpp"
|
||||||
#include "Runtime/CSimplePool.hpp"
|
#include "Runtime/CSimplePool.hpp"
|
||||||
|
|
||||||
namespace urde
|
namespace urde
|
||||||
|
@ -18,11 +18,9 @@ class ProjectManager
|
||||||
ViewManager& m_vm;
|
ViewManager& m_vm;
|
||||||
std::unique_ptr<hecl::Database::Project> m_proj;
|
std::unique_ptr<hecl::Database::Project> m_proj;
|
||||||
static bool m_registeredSpecs;
|
static bool m_registeredSpecs;
|
||||||
ProjectResourceFactory m_factory;
|
ProjectResourceFactoryMP1 m_factoryMP1;
|
||||||
urde::CSimplePool m_objStore;
|
urde::CSimplePool m_objStore;
|
||||||
|
|
||||||
void IndexMP1Resources();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProjectManager(ViewManager& vm);
|
ProjectManager(ViewManager& vm);
|
||||||
operator bool() const {return m_proj.operator bool();}
|
operator bool() const {return m_proj.operator bool();}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
#include "ProjectResourceFactory.hpp"
|
|
||||||
#include "Runtime/IOStreams.hpp"
|
|
||||||
|
|
||||||
#include "Runtime/Particle/CParticleDataFactory.hpp"
|
|
||||||
#include "Runtime/Particle/CGenDescription.hpp"
|
|
||||||
#include "Runtime/Particle/CElectricDescription.hpp"
|
|
||||||
#include "Runtime/Particle/CSwooshDescription.hpp"
|
|
||||||
#include "Runtime/GuiSys/CGuiFrame.hpp"
|
|
||||||
#include "Runtime/GuiSys/CRasterFont.hpp"
|
|
||||||
#include "Runtime/Graphics/CModel.hpp"
|
|
||||||
#include "Runtime/Graphics/CTexture.hpp"
|
|
||||||
|
|
||||||
namespace urde
|
|
||||||
{
|
|
||||||
|
|
||||||
ProjectResourceFactory::ProjectResourceFactory()
|
|
||||||
{
|
|
||||||
m_factoryMgr.AddFactory(FOURCC('TXTR'), urde::FTextureFactory);
|
|
||||||
m_factoryMgr.AddFactory(FOURCC('PART'), urde::FParticleFactory);
|
|
||||||
m_factoryMgr.AddFactory(FOURCC('FRME'), urde::RGuiFrameFactoryInGame);
|
|
||||||
m_factoryMgr.AddFactory(FOURCC('FONT'), urde::FRasterFontFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectResourceFactory::BuildObjectMap(const hecl::Database::Project::ProjectDataSpec &spec)
|
|
||||||
{
|
|
||||||
#if 0
|
|
||||||
m_tagToPath.clear();
|
|
||||||
m_catalogNameToTag.clear();
|
|
||||||
hecl::SystemString catalogPath = hecl::ProjectPath(spec.cookedPath, hecl::SystemString(spec.spec.m_name) + _S("/catalog.yaml")).getAbsolutePath();
|
|
||||||
FILE* catalogFile = hecl::Fopen(catalogPath.c_str(), _S("r"));
|
|
||||||
athena::io::YAMLDocReader reader;
|
|
||||||
yaml_parser_set_input_file(reader.getParser(), catalogFile);
|
|
||||||
reader.parse();
|
|
||||||
const athena::io::YAMLNode* catalogRoot = reader.getRootNode();
|
|
||||||
if (catalogRoot)
|
|
||||||
{
|
|
||||||
m_catalogNameToPath.reserve(catalogRoot->m_mapChildren.size());
|
|
||||||
for (const std::pair<std::string, std::unique_ptr<athena::io::YAMLNode>>& ch : catalogRoot->m_mapChildren)
|
|
||||||
{
|
|
||||||
if (ch.second->m_type == YAML_SCALAR_NODE)
|
|
||||||
m_catalogNameToPath[ch.first] = hecl::ProjectPath(spec.cookedPath, hecl::SystemString(ch.second->m_scalarString));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hecl::StrCmp(spec.spec.m_name, _S("MP3")))
|
|
||||||
{
|
|
||||||
RecursiveAddDirObjects(spec.cookedPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RecursiveAddDirObjects(spec.cookedPath);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<urde::IObj> ProjectResourceFactory::Build(const urde::SObjectTag& tag,
|
|
||||||
const urde::CVParamTransfer& paramXfer)
|
|
||||||
{
|
|
||||||
auto search = m_tagToPath.find(tag);
|
|
||||||
if (search == m_tagToPath.end())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
//fprintf(stderr, "Loading resource %s\n", search->second.getRelativePath().c_str());
|
|
||||||
athena::io::FileReader fr(search->second.getAbsolutePath(), 32 * 1024, false);
|
|
||||||
if (fr.hasError())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return m_factoryMgr.MakeObject(tag, fr, paramXfer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectResourceFactory::BuildAsync(const urde::SObjectTag& tag,
|
|
||||||
const urde::CVParamTransfer& paramXfer,
|
|
||||||
urde::IObj** objOut)
|
|
||||||
{
|
|
||||||
std::unique_ptr<urde::IObj> obj = Build(tag, paramXfer);
|
|
||||||
*objOut = obj.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectResourceFactory::CancelBuild(const urde::SObjectTag&)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProjectResourceFactory::CanBuild(const urde::SObjectTag& tag)
|
|
||||||
{
|
|
||||||
auto search = m_tagToPath.find(tag);
|
|
||||||
if (search == m_tagToPath.end())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
athena::io::FileReader fr(search->second.getAbsolutePath(), 32 * 1024, false);
|
|
||||||
if (fr.hasError())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const urde::SObjectTag* ProjectResourceFactory::GetResourceIdByName(const char* name) const
|
|
||||||
{
|
|
||||||
if (m_catalogNameToTag.find(name) == m_catalogNameToTag.end())
|
|
||||||
return nullptr;
|
|
||||||
const urde::SObjectTag& tag = m_catalogNameToTag.at(name);
|
|
||||||
return &tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
#ifndef URDE_PROJECT_RESOURCE_FACTORY_HPP
|
|
||||||
#define URDE_PROJECT_RESOURCE_FACTORY_HPP
|
|
||||||
|
|
||||||
#include "Runtime/IFactory.hpp"
|
|
||||||
#include "Runtime/CFactoryMgr.hpp"
|
|
||||||
|
|
||||||
namespace urde
|
|
||||||
{
|
|
||||||
|
|
||||||
class ProjectResourceFactory : public urde::IFactory
|
|
||||||
{
|
|
||||||
std::unordered_map<urde::SObjectTag, hecl::ProjectPath> m_tagToPath;
|
|
||||||
std::unordered_map<std::string, urde::SObjectTag> m_catalogNameToTag;
|
|
||||||
std::unordered_map<std::string, hecl::ProjectPath> m_catalogNameToPath;
|
|
||||||
urde::CFactoryMgr m_factoryMgr;
|
|
||||||
void RecursiveAddDirObjects(const hecl::ProjectPath& path)
|
|
||||||
{
|
|
||||||
hecl::DirectoryEnumerator de = path.enumerateDir();
|
|
||||||
const int idLen = 5 + 8;
|
|
||||||
for (const hecl::DirectoryEnumerator::Entry& ent : de)
|
|
||||||
{
|
|
||||||
if (ent.m_isDir)
|
|
||||||
RecursiveAddDirObjects(hecl::ProjectPath(path, ent.m_name));
|
|
||||||
if (ent.m_name.size() == idLen && ent.m_name[4] == _S('_'))
|
|
||||||
{
|
|
||||||
hecl::SystemUTF8View entu8(ent.m_name);
|
|
||||||
#if _WIN32
|
|
||||||
u64 id = _strtoui64(entu8.c_str() + 5, nullptr, 16);
|
|
||||||
#else
|
|
||||||
u64 id = strtouq(entu8.c_str() + 5, nullptr, 16);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (id)
|
|
||||||
{
|
|
||||||
urde::SObjectTag objTag = {hecl::FourCC(entu8.c_str()), id};
|
|
||||||
if (m_tagToPath.find(objTag) == m_tagToPath.end())
|
|
||||||
m_tagToPath[objTag] = hecl::ProjectPath(path, ent.m_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if 0
|
|
||||||
hecl::SystemUTF8View nameView(ent.m_name);
|
|
||||||
auto it = std::find_if(catalog.namedResources.begin(), catalog.namedResources.end(),
|
|
||||||
[&nameView](const typename DataSpec::NamedResourceCatalog<IDType>::NamedResource& res) -> bool
|
|
||||||
{ return res.name == nameView.str(); });
|
|
||||||
if (it == catalog.namedResources.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const typename DataSpec::NamedResourceCatalog<IDType>::NamedResource& nr = *it;
|
|
||||||
pshag::SObjectTag objTag = GetTag<IDType>(nr);
|
|
||||||
|
|
||||||
m_catalogNameToTag[nr.name.c_str()] = objTag;
|
|
||||||
m_tagToPath[objTag] = HECL::ProjectPath(path, ent.m_name);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
template <class IDType>
|
|
||||||
pshag::SObjectTag GetTag(const DataSpec::NamedResourceCatalog<DataSpec::UniqueID32>::NamedResource &nr,
|
|
||||||
typename std::enable_if<std::is_same<IDType, DataSpec::UniqueID32>::value>::type* = 0)
|
|
||||||
{ return { nr.type, nr.uid.toUint32() }; }
|
|
||||||
|
|
||||||
template <class IDType>
|
|
||||||
pshag::SObjectTag GetTag(const typename DataSpec::NamedResourceCatalog<IDType>::NamedResource& nr,
|
|
||||||
typename std::enable_if<std::is_same<IDType, DataSpec::UniqueID64>::value>::type* = 0)
|
|
||||||
{ return { nr.type, nr.uid.toUint64() }; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
|
||||||
ProjectResourceFactory();
|
|
||||||
void BuildObjectMap(const hecl::Database::Project::ProjectDataSpec& spec);
|
|
||||||
|
|
||||||
std::unique_ptr<urde::IObj> Build(const urde::SObjectTag&, const urde::CVParamTransfer&);
|
|
||||||
void BuildAsync(const urde::SObjectTag&, const urde::CVParamTransfer&, urde::IObj**);
|
|
||||||
void CancelBuild(const urde::SObjectTag&);
|
|
||||||
bool CanBuild(const urde::SObjectTag&);
|
|
||||||
const urde::SObjectTag* GetResourceIdByName(const char*) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // URDE_PROJECT_RESOURCE_FACTORY_HPP
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
#include "ProjectResourceFactoryBase.hpp"
|
||||||
|
#include "Runtime/IObj.hpp"
|
||||||
|
|
||||||
|
namespace urde
|
||||||
|
{
|
||||||
|
static logvisor::Module Log("urde::ProjectResourceFactoryBase");
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::Clear()
|
||||||
|
{
|
||||||
|
m_tagToPath.clear();
|
||||||
|
m_catalogNameToTag.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
hecl::BlenderConnection& ProjectResourceFactoryBase::GetBackgroundBlender() const
|
||||||
|
{
|
||||||
|
std::experimental::optional<hecl::BlenderConnection>& shareConn =
|
||||||
|
((ProjectResourceFactoryBase*)this)->m_backgroundBlender;
|
||||||
|
if (!shareConn)
|
||||||
|
shareConn.emplace(hecl::VerbosityLevel);
|
||||||
|
return *shareConn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::ReadCatalog(const hecl::ProjectPath& catalogPath)
|
||||||
|
{
|
||||||
|
FILE* fp = hecl::Fopen(catalogPath.getAbsolutePath().c_str(), _S("r"));
|
||||||
|
if (!fp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
athena::io::YAMLDocReader reader;
|
||||||
|
yaml_parser_set_input_file(reader.getParser(), fp);
|
||||||
|
bool res = reader.parse();
|
||||||
|
fclose(fp);
|
||||||
|
if (!res)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const athena::io::YAMLNode* root = reader.getRootNode();
|
||||||
|
for (const auto& p : root->m_mapChildren)
|
||||||
|
{
|
||||||
|
hecl::ProjectPath path(m_proj->getProjectWorkingPath(), p.second->m_scalarString);
|
||||||
|
if (path.getPathType() != hecl::ProjectPath::Type::File)
|
||||||
|
continue;
|
||||||
|
SObjectTag pathTag = TagFromPath(path);
|
||||||
|
if (pathTag)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(m_backgroundIndexMutex);
|
||||||
|
m_catalogNameToTag[p.first] = pathTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::BackgroundIndexRecursiveProc(const hecl::SystemString& path, int level)
|
||||||
|
{
|
||||||
|
hecl::DirectoryEnumerator dEnum(path,
|
||||||
|
hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted,
|
||||||
|
false, false, true);
|
||||||
|
|
||||||
|
/* Enumerate all items */
|
||||||
|
for (const hecl::DirectoryEnumerator::Entry& ent : dEnum)
|
||||||
|
{
|
||||||
|
if (ent.m_isDir)
|
||||||
|
BackgroundIndexRecursiveProc(ent.m_path, level+1);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hecl::ProjectPath path(path, ent.m_name);
|
||||||
|
if (path.getPathType() != hecl::ProjectPath::Type::File)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Read catalog.yaml for .pak directory if exists */
|
||||||
|
if (level == 1 && !ent.m_name.compare(_S("catalog.yaml")))
|
||||||
|
{
|
||||||
|
ReadCatalog(path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Classify intermediate into tag */
|
||||||
|
SObjectTag pathTag = TagFromPath(path);
|
||||||
|
if (pathTag)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(m_backgroundIndexMutex);
|
||||||
|
m_tagToPath[pathTag] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bail if cancelled by client */
|
||||||
|
if (!m_backgroundRunning)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::BackgroundIndexProc()
|
||||||
|
{
|
||||||
|
hecl::ProjectPath specRoot(m_proj->getProjectWorkingPath(), m_origSpec->m_name);
|
||||||
|
BackgroundIndexRecursiveProc(specRoot.getAbsolutePath(), 0);
|
||||||
|
m_backgroundRunning = false;
|
||||||
|
m_backgroundBlender = std::experimental::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::CancelBackgroundIndex()
|
||||||
|
{
|
||||||
|
if (m_backgroundRunning && m_backgroundIndexTh.joinable())
|
||||||
|
{
|
||||||
|
m_backgroundRunning = false;
|
||||||
|
m_backgroundIndexTh.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::BeginBackgroundIndex
|
||||||
|
(const hecl::Database::Project& proj,
|
||||||
|
const hecl::Database::DataSpecEntry& origSpec,
|
||||||
|
const hecl::Database::DataSpecEntry& pcSpec)
|
||||||
|
{
|
||||||
|
CancelBackgroundIndex();
|
||||||
|
Clear();
|
||||||
|
m_proj = &proj;
|
||||||
|
m_origSpec = &origSpec;
|
||||||
|
m_pcSpec = &pcSpec;
|
||||||
|
m_backgroundRunning = true;
|
||||||
|
m_backgroundIndexTh =
|
||||||
|
std::thread(std::bind(&ProjectResourceFactoryBase::BackgroundIndexProc, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
CFactoryFnReturn ProjectResourceFactoryBase::MakeObject(const SObjectTag& tag,
|
||||||
|
const hecl::ProjectPath& path,
|
||||||
|
const CVParamTransfer& paramXfer)
|
||||||
|
{
|
||||||
|
/* Ensure requested resource is on the filesystem */
|
||||||
|
if (path.getPathType() != hecl::ProjectPath::Type::File)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to find resource path '%s'"),
|
||||||
|
path.getAbsolutePath().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get cooked representation path */
|
||||||
|
hecl::ProjectPath cooked = GetCookedPath(tag, path, true);
|
||||||
|
|
||||||
|
/* Perform mod-time comparison */
|
||||||
|
if (cooked.getPathType() != hecl::ProjectPath::Type::File ||
|
||||||
|
cooked.getModtime() < path.getModtime())
|
||||||
|
{
|
||||||
|
if (!DoCook(tag, path, cooked, true))
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to cook resource path '%s'"),
|
||||||
|
path.getAbsolutePath().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure cooked rep is on the filesystem */
|
||||||
|
athena::io::FileReader fr(cooked.getAbsolutePath(), 32 * 1024, false);
|
||||||
|
if (fr.hasError())
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to open cooked resource path '%s'"),
|
||||||
|
cooked.getAbsolutePath().c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All good, build resource */
|
||||||
|
return m_factoryMgr.MakeObject(tag, fr, paramXfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<urde::IObj> ProjectResourceFactoryBase::Build(const urde::SObjectTag& tag,
|
||||||
|
const urde::CVParamTransfer& paramXfer)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(m_backgroundIndexMutex);
|
||||||
|
auto search = m_tagToPath.find(tag);
|
||||||
|
if (search == m_tagToPath.end())
|
||||||
|
{
|
||||||
|
if (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
while (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
lk.unlock();
|
||||||
|
lk.lock();
|
||||||
|
search = m_tagToPath.find(tag);
|
||||||
|
if (search != m_tagToPath.end())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeObject(tag, search->second, paramXfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::BuildAsync(const urde::SObjectTag& tag,
|
||||||
|
const urde::CVParamTransfer& paramXfer,
|
||||||
|
urde::IObj** objOut)
|
||||||
|
{
|
||||||
|
if (m_asyncLoadList.find(tag) != m_asyncLoadList.end())
|
||||||
|
return;
|
||||||
|
m_asyncLoadList[tag] = CResFactory::SLoadingData(tag, objOut, paramXfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::CancelBuild(const urde::SObjectTag& tag)
|
||||||
|
{
|
||||||
|
m_asyncLoadList.erase(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectResourceFactoryBase::CanBuild(const urde::SObjectTag& tag)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(m_backgroundIndexMutex);
|
||||||
|
auto search = m_tagToPath.find(tag);
|
||||||
|
if (search == m_tagToPath.end())
|
||||||
|
{
|
||||||
|
if (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
while (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
lk.unlock();
|
||||||
|
lk.lock();
|
||||||
|
search = m_tagToPath.find(tag);
|
||||||
|
if (search != m_tagToPath.end())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search->second.getPathType() == hecl::ProjectPath::Type::File)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urde::SObjectTag* ProjectResourceFactoryBase::GetResourceIdByName(const char* name) const
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(((ProjectResourceFactoryBase*)this)->m_backgroundIndexMutex);
|
||||||
|
auto search = m_catalogNameToTag.find(name);
|
||||||
|
if (search == m_catalogNameToTag.end())
|
||||||
|
{
|
||||||
|
if (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
while (m_backgroundRunning)
|
||||||
|
{
|
||||||
|
lk.unlock();
|
||||||
|
lk.lock();
|
||||||
|
search = m_catalogNameToTag.find(name);
|
||||||
|
if (search != m_catalogNameToTag.end())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &search->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryBase::AsyncIdle()
|
||||||
|
{
|
||||||
|
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||||
|
for (auto it=m_asyncLoadList.begin() ; it != m_asyncLoadList.end() ; ++it)
|
||||||
|
{
|
||||||
|
/* Allow 8 milliseconds (roughly 1/2 frame-time) for each async build cycle */
|
||||||
|
if (std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - start).count() > 8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Ensure requested resource is in the index */
|
||||||
|
std::unique_lock<std::mutex> lk(m_backgroundIndexMutex);
|
||||||
|
CResFactory::SLoadingData& data = it->second;
|
||||||
|
auto search = m_tagToPath.find(data.x0_tag);
|
||||||
|
if (search == m_tagToPath.end())
|
||||||
|
{
|
||||||
|
if (!m_backgroundRunning)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Error, _S("unable to find async load resource (%s, %08X)"),
|
||||||
|
data.x0_tag.type.toString().c_str(), data.x0_tag.id);
|
||||||
|
it = m_asyncLoadList.erase(it);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform build (data not actually loaded asynchronously for now) */
|
||||||
|
CFactoryFnReturn ret = MakeObject(data.x0_tag, search->second, data.x18_cvXfer);
|
||||||
|
*data.xc_targetPtr = ret.release();
|
||||||
|
it = m_asyncLoadList.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP
|
||||||
|
#define URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP
|
||||||
|
|
||||||
|
#include "Runtime/IFactory.hpp"
|
||||||
|
#include "Runtime/CFactoryMgr.hpp"
|
||||||
|
#include "Runtime/CResFactory.hpp"
|
||||||
|
#include "Runtime/optional.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace urde
|
||||||
|
{
|
||||||
|
|
||||||
|
class ProjectResourceFactoryBase : public urde::IFactory
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::unordered_map<urde::SObjectTag, hecl::ProjectPath> m_tagToPath;
|
||||||
|
std::unordered_map<std::string, urde::SObjectTag> m_catalogNameToTag;
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
const hecl::Database::Project* m_proj = nullptr;
|
||||||
|
const hecl::Database::DataSpecEntry* m_origSpec = nullptr;
|
||||||
|
const hecl::Database::DataSpecEntry* m_pcSpec = nullptr;
|
||||||
|
urde::CFactoryMgr m_factoryMgr;
|
||||||
|
|
||||||
|
std::experimental::optional<hecl::BlenderConnection> m_backgroundBlender;
|
||||||
|
std::thread m_backgroundIndexTh;
|
||||||
|
std::mutex m_backgroundIndexMutex;
|
||||||
|
bool m_backgroundRunning = false;
|
||||||
|
|
||||||
|
std::unordered_map<SObjectTag, CResFactory::SLoadingData> m_asyncLoadList;
|
||||||
|
|
||||||
|
virtual SObjectTag TagFromPath(const hecl::ProjectPath& path) const=0;
|
||||||
|
|
||||||
|
hecl::BlenderConnection& GetBackgroundBlender() const;
|
||||||
|
void ReadCatalog(const hecl::ProjectPath& catalogPath);
|
||||||
|
void BackgroundIndexRecursiveProc(const hecl::SystemString& path, int level);
|
||||||
|
void BackgroundIndexProc();
|
||||||
|
void CancelBackgroundIndex();
|
||||||
|
void BeginBackgroundIndex(const hecl::Database::Project& proj,
|
||||||
|
const hecl::Database::DataSpecEntry& origSpec,
|
||||||
|
const hecl::Database::DataSpecEntry& pcSpec);
|
||||||
|
|
||||||
|
virtual hecl::ProjectPath GetCookedPath(const SObjectTag& tag,
|
||||||
|
const hecl::ProjectPath& working,
|
||||||
|
bool pcTarget) const=0;
|
||||||
|
virtual bool DoCook(const SObjectTag& tag, const hecl::ProjectPath& working,
|
||||||
|
const hecl::ProjectPath& cooked,
|
||||||
|
bool pcTarget)=0;
|
||||||
|
CFactoryFnReturn MakeObject(const SObjectTag& tag, const hecl::ProjectPath& path,
|
||||||
|
const CVParamTransfer& paramXfer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::unique_ptr<urde::IObj> Build(const urde::SObjectTag&, const urde::CVParamTransfer&);
|
||||||
|
void BuildAsync(const urde::SObjectTag&, const urde::CVParamTransfer&, urde::IObj**);
|
||||||
|
void CancelBuild(const urde::SObjectTag&);
|
||||||
|
bool CanBuild(const urde::SObjectTag&);
|
||||||
|
const urde::SObjectTag* GetResourceIdByName(const char*) const;
|
||||||
|
|
||||||
|
void AsyncIdle();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP
|
|
@ -0,0 +1,141 @@
|
||||||
|
#include "ProjectResourceFactoryMP1.hpp"
|
||||||
|
#include "Runtime/IOStreams.hpp"
|
||||||
|
|
||||||
|
#include "Runtime/Particle/CParticleDataFactory.hpp"
|
||||||
|
#include "Runtime/Particle/CGenDescription.hpp"
|
||||||
|
#include "Runtime/Particle/CElectricDescription.hpp"
|
||||||
|
#include "Runtime/Particle/CSwooshDescription.hpp"
|
||||||
|
#include "Runtime/GuiSys/CGuiFrame.hpp"
|
||||||
|
#include "Runtime/GuiSys/CRasterFont.hpp"
|
||||||
|
#include "Runtime/Graphics/CModel.hpp"
|
||||||
|
#include "Runtime/Graphics/CTexture.hpp"
|
||||||
|
|
||||||
|
#include "DataSpec/DNACommon/TXTR.hpp"
|
||||||
|
|
||||||
|
namespace DataSpec
|
||||||
|
{
|
||||||
|
extern hecl::Database::DataSpecEntry SpecEntMP1;
|
||||||
|
extern hecl::Database::DataSpecEntry SpecEntMP1PC;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace urde
|
||||||
|
{
|
||||||
|
|
||||||
|
ProjectResourceFactoryMP1::ProjectResourceFactoryMP1()
|
||||||
|
{
|
||||||
|
m_factoryMgr.AddFactory(FOURCC('TXTR'), urde::FTextureFactory);
|
||||||
|
m_factoryMgr.AddFactory(FOURCC('PART'), urde::FParticleFactory);
|
||||||
|
m_factoryMgr.AddFactory(FOURCC('FRME'), urde::RGuiFrameFactoryInGame);
|
||||||
|
m_factoryMgr.AddFactory(FOURCC('FONT'), urde::FRasterFontFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectResourceFactoryMP1::IndexMP1Resources(const hecl::Database::Project& proj)
|
||||||
|
{
|
||||||
|
BeginBackgroundIndex(proj, DataSpec::SpecEntMP1, DataSpec::SpecEntMP1PC);
|
||||||
|
}
|
||||||
|
|
||||||
|
SObjectTag ProjectResourceFactoryMP1::TagFromPath(const hecl::ProjectPath& path) const
|
||||||
|
{
|
||||||
|
if (hecl::IsPathBlend(path))
|
||||||
|
{
|
||||||
|
hecl::BlenderConnection& conn = GetBackgroundBlender();
|
||||||
|
if (!conn.openBlend(path))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
switch (conn.getBlendType())
|
||||||
|
{
|
||||||
|
case hecl::BlenderConnection::BlendType::Mesh:
|
||||||
|
return {SBIG('CMDL'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::Actor:
|
||||||
|
return {SBIG('ANCS'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::Area:
|
||||||
|
return {SBIG('MREA'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::World:
|
||||||
|
return {SBIG('MLVL'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::MapArea:
|
||||||
|
return {SBIG('MAPA'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::MapUniverse:
|
||||||
|
return {SBIG('MAPU'), path.hash()};
|
||||||
|
case hecl::BlenderConnection::BlendType::Frame:
|
||||||
|
return {SBIG('FRME'), path.hash()};
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (hecl::IsPathPNG(path))
|
||||||
|
{
|
||||||
|
return {SBIG('TXTR'), path.hash()};
|
||||||
|
}
|
||||||
|
else if (hecl::IsPathYAML(path))
|
||||||
|
{
|
||||||
|
FILE* fp = hecl::Fopen(path.getAbsolutePath().c_str(), _S("r"));
|
||||||
|
if (!fp)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
athena::io::YAMLDocReader reader;
|
||||||
|
yaml_parser_set_input_file(reader.getParser(), fp);
|
||||||
|
bool res = reader.parse();
|
||||||
|
fclose(fp);
|
||||||
|
if (!res)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
SObjectTag resTag;
|
||||||
|
if (reader.ClassTypeOperation([&](const char* className) -> bool
|
||||||
|
{
|
||||||
|
if (!strcmp(className, "GPSM"))
|
||||||
|
{
|
||||||
|
resTag.type = SBIG('PART');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (!strcmp(className, "FONT"))
|
||||||
|
{
|
||||||
|
resTag.type = SBIG('FONT');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
resTag.id = path.hash();
|
||||||
|
return resTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
hecl::ProjectPath ProjectResourceFactoryMP1::GetCookedPath(const SObjectTag& tag,
|
||||||
|
const hecl::ProjectPath& working,
|
||||||
|
bool pcTarget) const
|
||||||
|
{
|
||||||
|
if (!pcTarget)
|
||||||
|
return working.getCookedPath(*m_origSpec);
|
||||||
|
|
||||||
|
switch (tag.type)
|
||||||
|
{
|
||||||
|
case SBIG('TXTR'):
|
||||||
|
case SBIG('CMDL'):
|
||||||
|
case SBIG('MREA'):
|
||||||
|
return working.getCookedPath(*m_pcSpec);
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return working.getCookedPath(*m_origSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectResourceFactoryMP1::DoCook(const SObjectTag& tag,
|
||||||
|
const hecl::ProjectPath& working,
|
||||||
|
const hecl::ProjectPath& cooked,
|
||||||
|
bool pcTarget)
|
||||||
|
{
|
||||||
|
switch (tag.type)
|
||||||
|
{
|
||||||
|
case SBIG('TXTR'):
|
||||||
|
if (pcTarget)
|
||||||
|
return DataSpec::TXTR::CookPC(working, cooked);
|
||||||
|
else
|
||||||
|
return DataSpec::TXTR::Cook(working, cooked);
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP
|
||||||
|
#define URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP
|
||||||
|
|
||||||
|
#include "ProjectResourceFactoryBase.hpp"
|
||||||
|
|
||||||
|
namespace urde
|
||||||
|
{
|
||||||
|
|
||||||
|
class ProjectResourceFactoryMP1 : public ProjectResourceFactoryBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProjectResourceFactoryMP1();
|
||||||
|
void IndexMP1Resources(const hecl::Database::Project& proj);
|
||||||
|
SObjectTag TagFromPath(const hecl::ProjectPath& path) const;
|
||||||
|
hecl::ProjectPath GetCookedPath(const SObjectTag& tag,
|
||||||
|
const hecl::ProjectPath& working,
|
||||||
|
bool pcTarget) const;
|
||||||
|
bool DoCook(const SObjectTag& tag, const hecl::ProjectPath& working,
|
||||||
|
const hecl::ProjectPath& cooked,
|
||||||
|
bool pcTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP
|
|
@ -17,11 +17,15 @@ public:
|
||||||
struct SLoadingData
|
struct SLoadingData
|
||||||
{
|
{
|
||||||
SObjectTag x0_tag;
|
SObjectTag x0_tag;
|
||||||
IDvdRequest* x8_dvdReq;
|
IDvdRequest* x8_dvdReq = nullptr;
|
||||||
IObj** xc_targetPtr;
|
IObj** xc_targetPtr = nullptr;
|
||||||
void* x10_loadBuffer;
|
void* x10_loadBuffer = nullptr;
|
||||||
u32 x14_resSize;
|
u32 x14_resSize = 0;
|
||||||
CVParamTransfer x18_cvXfer;
|
CVParamTransfer x18_cvXfer;
|
||||||
|
|
||||||
|
SLoadingData() = default;
|
||||||
|
SLoadingData(const SObjectTag& tag, IObj** ptr, const CVParamTransfer& xfer)
|
||||||
|
: x0_tag(tag), xc_targetPtr(ptr), x18_cvXfer(xfer) {}
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
std::unordered_map<SObjectTag, SLoadingData> m_loadList;
|
std::unordered_map<SObjectTag, SLoadingData> m_loadList;
|
||||||
|
@ -31,8 +35,16 @@ public:
|
||||||
std::unique_ptr<IObj> Build(const SObjectTag&, const CVParamTransfer&);
|
std::unique_ptr<IObj> Build(const SObjectTag&, const CVParamTransfer&);
|
||||||
void BuildAsync(const SObjectTag&, const CVParamTransfer&, IObj**);
|
void BuildAsync(const SObjectTag&, const CVParamTransfer&, IObj**);
|
||||||
void CancelBuild(const SObjectTag&);
|
void CancelBuild(const SObjectTag&);
|
||||||
bool CanBuild(const SObjectTag& tag) {return x4_loader.ResourceExists(tag);}
|
|
||||||
const SObjectTag* GetResourceIdByName(const char* name) const {return x4_loader.GetResourceIdByName(name);}
|
bool CanBuild(const SObjectTag& tag)
|
||||||
|
{
|
||||||
|
return x4_loader.ResourceExists(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SObjectTag* GetResourceIdByName(const char* name) const
|
||||||
|
{
|
||||||
|
return x4_loader.GetResourceIdByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string, SObjectTag>> GetResourceIdToNameList() const
|
std::vector<std::pair<std::string, SObjectTag>> GetResourceIdToNameList() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct SObjectTag
|
||||||
{
|
{
|
||||||
FourCC type;
|
FourCC type;
|
||||||
TResId id = -1;
|
TResId id = -1;
|
||||||
|
operator bool() const {return id != -1;}
|
||||||
bool operator!=(const SObjectTag& other) const {return id != other.id;}
|
bool operator!=(const SObjectTag& other) const {return id != other.id;}
|
||||||
bool operator==(const SObjectTag& other) const {return id == other.id;}
|
bool operator==(const SObjectTag& other) const {return id == other.id;}
|
||||||
SObjectTag() = default;
|
SObjectTag() = default;
|
||||||
|
|
2
hecl
2
hecl
|
@ -1 +1 @@
|
||||||
Subproject commit beab85bd01599155d655872349d7a93b4e6b7373
|
Subproject commit 2fe1611e63d2993a1892ce00c1a108226aaecb52
|
Loading…
Reference in New Issue