whoa/src/model/CM2Shared.cpp

808 lines
23 KiB
C++

#include "model/CM2Shared.hpp"
#include "async/AsyncFile.hpp"
#include "gx/Buffer.hpp"
#include "gx/Shader.hpp"
#include "gx/Texture.hpp"
#include "model/CM2Cache.hpp"
#include "model/CM2Model.hpp"
#include "model/M2Data.hpp"
#include "model/M2Init.hpp"
#include "model/M2Types.hpp"
#include "util/CStatus.hpp"
#include "util/SFile.hpp"
#include <cstring>
void CM2Shared::LoadFailedCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
}
void CM2Shared::LoadSucceededCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
uint8_t* base = reinterpret_cast<uint8_t*>(shared->m_data);
uint32_t size = shared->m_dataSize;
M2Data& data = *shared->m_data;
if (!M2Init(base, size, data)) {
return;
}
if (!shared->Initialize()) {
return;
}
// TODO
// - allocate space for low priority sequence pointers
shared->m_m2DataLoaded = 1;
}
void CM2Shared::SkinProfileLoadedCallback(void* arg) {
CM2Shared* shared = static_cast<CM2Shared*>(arg);
shared->FinishLoadingSkinProfile(shared->asyncObject->size);
AsyncFileReadDestroyObject(shared->asyncObject);
shared->asyncObject = nullptr;
}
void CM2Shared::AddRef() {
// TODO
}
int32_t CM2Shared::CallbackWhenLoaded(CM2Model* model) {
if (model->m_flags & 0x20) {
return 1;
}
if (this->m_m2DataLoaded && this->m_skinProfileLoaded) {
model->InitializeLoaded();
return 1;
}
model->LinkToCallbackListTail();
return 1;
}
CShaderEffect* CM2Shared::CreateSimpleEffect(uint32_t textureCount, uint16_t shader, uint16_t textureCoordComboIndex) {
uint32_t combiner[2];
uint32_t envmap[2];
combiner[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_OP_MASK; // T1 Combiner
combiner[1] = (shader >> 0) & M2COMBINER_OP_MASK; // T2 Combiner
envmap[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_ENVMAP; // T1 Env Mapped
envmap[1] = (shader >> 0) & M2COMBINER_ENVMAP; // T2 Env Mapped
const char* vsName;
const char* psName;
// 1 texture
if (textureCount == 1) {
if (envmap[0]) {
vsName = "Diffuse_Env";
} else if (this->m_data->textureCoordCombos[textureCoordComboIndex] == 0) {
vsName = "Diffuse_T1";
} else {
vsName = "Diffuse_T2";
}
switch (combiner[0]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Opaque";
break;
case M2COMBINER_MOD:
psName = "Combiners_Mod";
break;
case M2COMBINER_DECAL:
psName = "Combiners_Decal";
break;
case M2COMBINER_ADD:
psName = "Combiners_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Mod2x";
break;
case M2COMBINER_FADE:
psName = "Combiners_Fade";
break;
default:
psName = "Combiners_Mod";
break;
}
// 2 textures
} else {
if (envmap[0] && envmap[1]) {
vsName = "Diffuse_Env_Env";
} else if (envmap[0]) {
vsName = "Diffuse_Env_T2";
} else if (envmap[1]) {
vsName = "Diffuse_T1_Env";
} else {
vsName = "Diffuse_T1_T2";
}
switch (combiner[0]) {
case M2COMBINER_OPAQUE:
switch (combiner[1]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Opaque_Opaque";
break;
case M2COMBINER_ADD:
psName = "Combiners_Opaque_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Opaque_Mod2x";
break;
case M2COMBINER_MOD2X_NA:
psName = "Combiners_Opaque_Mod2xNA";
break;
case M2COMBINER_ADD_NA:
psName = "Combiners_Opaque_AddNA";
break;
default:
psName = "Combiners_Opaque_Mod";
break;
}
break;
case M2COMBINER_MOD:
switch (combiner[1]) {
case M2COMBINER_OPAQUE:
psName = "Combiners_Mod_Opaque";
break;
case M2COMBINER_MOD:
psName = "Combiners_Mod_Mod";
break;
case M2COMBINER_ADD:
psName = "Combiners_Mod_Add";
break;
case M2COMBINER_MOD2X:
psName = "Combiners_Mod_Mod2x";
break;
case M2COMBINER_MOD2X_NA:
psName = "Combiners_Mod_Mod2xNA";
break;
case M2COMBINER_ADD_NA:
psName = "Combiners_Mod_AddNA";
break;
default:
psName = "Combiners_Mod_Mod";
break;
}
break;
case M2COMBINER_ADD:
switch (combiner[1]) {
case M2COMBINER_MOD:
psName = "Combiners_Add_Mod";
break;
default:
return nullptr;
}
break;
case M2COMBINER_MOD2X:
switch (combiner[1]) {
case M2COMBINER_MOD2X:
psName = "Combiners_Mod2x_Mod2x";
break;
default:
return nullptr;
}
break;
}
}
char effectName[256];
// Create effect name for hashing
strcpy(effectName, vsName);
strcat(effectName, psName);
CShaderEffect* effect = CShaderEffectManager::GetEffect(effectName);
if (!effect) {
effect = CShaderEffectManager::CreateEffect(effectName);
effect->InitEffect(vsName, psName);
// M2GetCombinerOps (inlined)
for (int32_t textureIndex = 0; textureIndex < textureCount; textureIndex++) {
// TODO
// colorOps[textureIndex] = colorOpTable[combiner[textureIndex]];
// alphaOps[textureIndex] = alphaOpTable[combiner[textureIndex]];
}
// TODO
// effect->InitFixedFuncPass(colorOps, alphaOps, textureCount);
}
return effect;
}
int32_t CM2Shared::FinishLoadingSkinProfile(uint32_t size) {
if (this->m_skinProfileLoaded) {
return 1;
}
uint8_t* base = reinterpret_cast<uint8_t*>(this->skinProfile);
M2Data& data = *this->m_data;
M2SkinProfile& skinProfile = *this->skinProfile;
if (!M2Init(base, size, data, skinProfile)) {
return 0;
}
if (!this->InitializeSkinProfile()) {
return 0;
}
this->m_skinProfileLoaded = 1;
for (auto model = this->m_callbackList; model; model = this->m_callbackList) {
STORM_ASSERT(model->m_callbackPrev);
STORM_ASSERT(*model->m_callbackPrev == this->m_callbackList);
model->UnlinkFromCallbackList();
model->InitializeLoaded();
}
return 1;
}
CShaderEffect* CM2Shared::GetEffect(M2Batch* batch) {
CShaderEffect* effect;
// Simple effect
if (!(batch->shader & 0x8000)) {
effect = this->CreateSimpleEffect(batch->textureCount, batch->shader, batch->textureCoordComboIndex);
// Fallback
// 0000000000010001
// 1 texture: Combiners_Mod
// 2 texture: Combiners_Mod_Mod
if (!effect) {
effect = this->CreateSimpleEffect(batch->textureCount, 0x11, batch->textureCoordComboIndex);
}
return effect;
}
// Specialized effect
const char* vsName = nullptr;
const char* psName = nullptr;
uint32_t colorOp = 0;
uint32_t alphaOp = 0;
switch (batch->shader & 0x7FFF) {
case 0:
return nullptr;
case 1:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_Mod2xNA_Alpha";
colorOp = 0;
alphaOp = 3;
break;
case 2:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_AddAlpha";
colorOp = 0;
alphaOp = 3;
break;
case 3:
vsName = "Diffuse_T1_Env";
psName = "Combiners_Opaque_AddAlpha_Alpha";
colorOp = 0;
alphaOp = 3;
break;
default:
break;
}
char effectName[256];
// Create effect name for hashing
strcpy(effectName, vsName);
strcat(effectName, psName);
effect = CShaderEffectManager::GetEffect(effectName);
if (!effect) {
effect = CShaderEffectManager::CreateEffect(effectName);
effect->InitEffect(vsName, psName);
// TODO
// effect->InitFixedFuncPass(effect, &colorOp, &alphaOp, 1);
}
return effect;
}
int32_t CM2Shared::Initialize() {
this->skinProfile = nullptr;
// TODO
// implement logic to select skin profile
uint32_t profile = this->m_data->numSkinProfiles - 1;
if (!this->LoadSkinProfile(profile)) {
return 0;
}
void* textures = SMemAlloc(sizeof(HTEXTURE) * this->m_data->textures.count, __FILE__, __LINE__, 0x8);
this->textures = static_cast<HTEXTURE*>(textures);
if (!textures) {
// TODO
// CM2Model::ErrorSetFileLine(__FILE__, __LINE__);
// CM2Model::ErrorSet("Failed to allocate texture array: %s", this + 60);
return 0;
}
for (int32_t i = 0; i < this->m_data->materials.count; i++) {
M2Material& material = this->m_data->materials[i];
if (material.blendMode >= M2BLEND_MOD) {
material.flags |= 0x1;
}
}
for (int32_t i = 0; i < this->m_data->textures.count; i++) {
M2Texture& texture = this->m_data->textures[i];
if (texture.filename.count > 1) {
CGxTexFlags texFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1);
int32_t createFlags = 0;
if (this->m_flag4) {
createFlags |= 0x2;
}
if (this->m_flag40) {
createFlags |= 0x30;
}
CStatus* status = &GetGlobalStatusObj();
this->textures[i] = TextureCreate(&texture.filename[0], texFlags, status, createFlags);
} else {
static CImVector FRIENDLY_WHITE = { 0xFF, 0xFF, 0xFF, 0xFF };
this->textures[i] = TextureCreateSolid(FRIENDLY_WHITE);
}
}
for (int32_t i = 0; i < this->m_data->bones.count; i++) {
M2CompBone& bone = this->m_data->bones[i];
if (bone.flags & (0x200 | 0x80 | 0x40 | 0x20 | 0x10 | 0x8)) {
// TODO
}
}
return 1;
}
int32_t CM2Shared::InitializeSkinProfile() {
this->uint194 = this->skinProfile->indices.Count()
? 65536 / this->skinProfile->indices.Count()
: 1;
for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) {
uint32_t v6 = this->skinProfile->boneCountMax / this->skinProfile->skinSections[i].boneCount;
if (this->uint194 > v6) {
this->uint194 = v6;
}
}
if (!this->uint194) {
this->uint194 = 1;
}
this->uint190 = 1;
uint32_t dataSize = sizeof(M2SkinSection) * this->skinProfile->skinSections.Count();
if (this->skinProfile->indices.Count()) {
dataSize += sizeof(CShaderEffect*) * this->skinProfile->batches.Count();
}
char* data = static_cast<char*>(SMemAlloc(dataSize, __FILE__, __LINE__, 0x0));
if (!data) {
return 0;
}
this->m_skinSections = reinterpret_cast<M2SkinSection*>(data);
if (this->skinProfile->skinSections.Count()) {
data += sizeof(M2SkinSection) * this->skinProfile->skinSections.Count();
memcpy(this->m_skinSections, this->skinProfile->skinSections.Data(), this->skinProfile->skinSections.Count() * sizeof(M2SkinSection));
}
if (this->skinProfile->indices.Count()) {
this->m_batchShaders = reinterpret_cast<CShaderEffect**>(data);
memset(this->m_batchShaders, 0, sizeof(CShaderEffect*) * this->skinProfile->batches.Count());
}
this->SubstituteSimpleShaders();
this->SubstituteSpecializedShaders();
if (this->skinProfile->indices.Count()) {
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
this->m_batchShaders[i] = this->GetEffect(&this->skinProfile->batches[i]);
}
}
if (!(this->m_cache->m_flags & 0x8)) {
// TODO
// - non-shader-path vertex logic
}
if (!(this->m_cache->m_flags & 0x8)) {
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
auto& batch = this->skinProfile->batches[i];
if (batch.textureCount > 1) {
this->skinProfile->batches[i - batch.materialLayer].flags |= 0x40;
}
}
for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) {
auto& batch = this->skinProfile->batches[i];
if (batch.materialLayer) {
if (this->skinProfile->batches[i - batch.materialLayer].flags & 0x40) {
batch.flags |= 0x40;
}
}
}
}
return 1;
}
int32_t CM2Shared::Load(SFile* file, int32_t a3, CAaBox* a4) {
// TODO
// this->dword8 ^= (this->dword8 ^ (4 * (a3 != 0))) & 4;
this->m_dataSize = SFile::GetFileSize(file, 0);
// TODO use proper allocation function here
this->m_data = static_cast<M2Data*>(SMemAlloc(this->m_dataSize, __FILE__, __LINE__, 0));
if (!this->m_data) {
return 0;
}
if (a4) {
this->aaBox154 = *a4;
}
this->asyncObject = AsyncFileReadAllocObject();
if (!this->asyncObject) {
return 0;
}
this->asyncObject->file = file;
this->asyncObject->buffer = this->m_data;
this->asyncObject->size = this->m_dataSize;
this->asyncObject->userArg = this;
this->asyncObject->userPostloadCallback = &CM2Shared::LoadSucceededCallback;
this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback;
this->asyncObject->isRead = 0;
this->asyncObject->isProcessed = 0;
AsyncFileReadObject(this->asyncObject, 0);
return 1;
}
int32_t CM2Shared::LoadSkinProfile(uint32_t profile) {
// TODO
// the file path logic is in its own function
char skinFilePath[STORM_MAX_PATH];
strcpy(skinFilePath, this->m_filePath);
char* v4 = strrchr(skinFilePath, '.');
if (v4) {
*v4 = 0;
}
sprintf(&skinFilePath[strlen(skinFilePath)], "%02d.skin", profile);
SFile* fileptr;
if (!SFile::OpenEx(nullptr, skinFilePath, this->m_flag4, &fileptr)) {
// TODO
// error handling
return 0;
}
uint32_t size = SFile::GetFileSize(fileptr, nullptr);
// TODO use proper allocation function here
this->skinProfile = static_cast<M2SkinProfile*>(SMemAlloc(size, __FILE__, __LINE__, 0));
if (!this->skinProfile) {
SFile::Close(fileptr);
return 0;
}
this->asyncObject = AsyncFileReadAllocObject();
if (!this->asyncObject) {
SFile::Close(fileptr);
delete this->skinProfile;
return 0;
}
this->asyncObject->file = fileptr;
this->asyncObject->buffer = this->skinProfile;
this->asyncObject->size = size,
this->asyncObject->userArg = this;
this->asyncObject->userPostloadCallback = &CM2Shared::SkinProfileLoadedCallback;
this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback;
this->asyncObject->isRead = 0;
this->asyncObject->isProcessed = 0;
this->asyncObject->priority = 125;
AsyncFileReadObject(this->asyncObject, 1);
return 1;
}
void CM2Shared::Release() {
// TODO
}
int32_t CM2Shared::SetIndices() {
if (!this->m_indexPool) {
this->m_indexPool = GxPoolCreate(
GxPoolTarget_Index,
GxPoolUsage_Dynamic,
2 * this->uint190 * this->skinProfile->indices.Count(),
GxPoolHintBit_Unk1,
this->ext
);
this->m_indexBuf = GxBufCreate(
this->m_indexPool,
2,
this->uint190 * this->skinProfile->indices.Count(),
0
);
if (!this->m_indexPool || !this->m_indexBuf) {
return 0;
}
}
if (!this->m_indexBuf->unk1C || !this->m_indexBuf->unk1D) {
char* indexData = GxBufLock(this->m_indexBuf);
uint16_t* indexBuf = reinterpret_cast<uint16_t*>(indexData);
if (!indexData) {
return 0;
}
int32_t v10 = (this->m_cache->m_flags & 0x8) != 0
|| (this->m_data->bones.Count() == 1 && (this->m_cache->m_flags & 0x40) != 0);
uint32_t v21 = 0;
for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) {
auto& skinSection = this->m_skinSections[i];
auto indexStart = this->skinProfile->skinSections[i].indexStart;
auto v25 = v10 ? 0 : -skinSection.vertexStart;
for (int32_t j = 0; j < this->uint190; j++) {
for (int32_t k = 0; k < skinSection.indexCount; k++) {
indexBuf[k] = this->skinProfile->indices[indexStart + k + v25];
}
indexBuf += skinSection.indexCount;
v25 += v10 ? this->skinProfile->vertices.Count() : skinSection.vertexCount;
}
skinSection.indexStart = v21;
v21 += this->uint190 * skinSection.indexCount;
}
GxBufUnlock(this->m_indexBuf, 0);
}
GxPrimIndexPtr(this->m_indexBuf);
return 1;
}
int32_t CM2Shared::SetVertices(uint32_t a2) {
if (!this->m_vertexPool) {
this->m_vertexPool = GxPoolCreate(
GxPoolTarget_Vertex,
GxPoolUsage_Static,
sizeof(CGxVertexPBNT2) * this->uint190 * this->skinProfile->vertices.Count(),
GxPoolHintBit_Unk1,
this->ext
);
this->m_vertexBuf = GxBufCreate(
this->m_vertexPool,
sizeof(CGxVertexPBNT2),
this->uint190 * this->skinProfile->vertices.Count(),
0
);
if (!this->m_vertexPool || !this->m_vertexBuf) {
return 0;
}
}
if (CShaderEffect::s_enableShaders) {
if (!this->m_vertexBuf->unk1C || !this->m_vertexBuf->unk1D) {
char* vertexData = GxBufLock(this->m_vertexBuf);
CGxVertexPBNT2* vertexBuf = reinterpret_cast<CGxVertexPBNT2*>(vertexData);
if (!vertexData) {
return 0;
}
auto v27 = 0;
for (int32_t i = 0; i < this->uint190; i++) {
for (int32_t j = 0; j < this->skinProfile->skinSections.Count(); j++) {
auto& skinSection = this->skinProfile->skinSections[j];
auto vertexStart = skinSection.vertexStart;
auto vertexEnd = vertexStart + skinSection.vertexCount;
uint32_t v25 = 0x1010101 * i * skinSection.boneCount;
if (vertexStart < vertexEnd) {
for (int32_t k = vertexStart; k < vertexEnd; k++) {
auto vertex = &this->m_data->vertices[this->skinProfile->vertices[k]];
memcpy(&vertexBuf[k], vertex, sizeof(CGxVertexPBNT2));
vertexBuf[k].bi.u = v25 + this->skinProfile->bones[k].u;
}
}
}
vertexBuf += this->skinProfile->vertices.Count();
}
GxBufUnlock(this->m_vertexBuf, 0);
}
GxPrimVertexPtr(this->m_vertexBuf, GxVBF_PBNT2);
return 1;
} else {
// TODO
// non-shader code path
return 1;
}
}
void CM2Shared::SubstituteSimpleShaders() {
for (int32_t batchIndex = 0; batchIndex < this->skinProfile->batches.Count(); batchIndex++) {
auto& batch = this->skinProfile->batches[batchIndex];
if (batch.shader & 0x8000) {
continue;
}
auto& material = this->m_data->materials[batch.materialIndex];
uint16_t textureCoordComboIndex = batch.textureCoordComboIndex;
// M2Data flag 0x8: use combiner combos
if (this->m_data->flags & 0x8) {
uint16_t textureCombinerComboIndex = batch.shader;
batch.shader = 0;
uint16_t shader[2] = { 0, 0 };
for (int32_t textureIndex = 0; textureIndex < batch.textureCount; textureIndex++) {
bool isFirstTexture = textureIndex == 0;
bool isLastTexture = textureIndex == batch.textureCount - 1;
// If this is the first texture and the batch material's blending mode is opaque,
// override the combiner mode to opaque; otherwise, use the combiner mode from the
// combiner combos
uint16_t textureCombiner = isFirstTexture && material.blendMode == M2BLEND_OPAQUE
? M2COMBINER_OPAQUE
: this->m_data->textureCombinerCombos[textureCombinerComboIndex + textureIndex];
shader[textureIndex] |= textureCombiner;
uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex + textureIndex];
// If the texture coord is env, set env bit for texture
if (textureCoord > 2) {
shader[textureIndex] |= M2COMBINER_ENVMAP;
}
// If this is the last texture and the texture coord is T2, enable bit 15
if (isLastTexture && textureCoord == 1) {
batch.shader |= 0x4000;
}
}
batch.shader |= (shader[0] << M2COMBINER_STAGE_SHIFT) | shader[1];
} else {
uint16_t shader = 0;
// If the material blend mode is opaque, force the combiner to opaque; otherwise,
// default combiner to mod
uint16_t textureCombiner = material.blendMode == M2BLEND_OPAQUE
? M2COMBINER_OPAQUE
: M2COMBINER_MOD;
shader |= textureCombiner;
uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex];
// If the texture coord is env, set env bit for texture
if (textureCoord > 2) {
shader |= M2COMBINER_ENVMAP;
}
shader <<= M2COMBINER_STAGE_SHIFT;
// If the texture coord is T2, enable bit 15
if (textureCoord == 1) {
shader |= 0x4000;
}
batch.shader = shader;
}
}
}
void CM2Shared::SubstituteSpecializedShaders() {
// TODO
}