#include "model/CM2Scene.hpp" #include "gx/Shader.hpp" #include "gx/Transform.hpp" #include "model/CM2Cache.hpp" #include "model/CM2Light.hpp" #include "model/CM2Model.hpp" #include "model/CM2SceneRender.hpp" #include "model/CM2Shared.hpp" #include "model/M2Internal.hpp" #include "model/M2Sort.hpp" #include #include #include uint32_t CM2Scene::s_optFlags = 0xFFFFFFFF; void CM2Scene::AnimateThread(void* arg) { // TODO } void CM2Scene::ComputeElementShaders(M2Element* element) { auto model = element->model; auto batch = element->batch; auto material = &model->m_shared->m_data->materials[batch->materialIndex]; auto lighting = model->m_currentLighting; int32_t shaded; int32_t lightCount; if (material->flags & 0x1 || CM2SceneRender::s_shadedList[material->blendMode] == 0) { shaded = 0; lightCount = material->flags & 0x1 ? 0 : lighting->m_lightCount; } else { shaded = 1; lightCount = lighting->m_lightCount; } int32_t boneInfluences = element->skinSection->boneInfluences; int32_t v18; if (material->blendMode == M2BLEND_OPAQUE) { v18 = 0; } else if (material->blendMode == M2BLEND_ALPHA_KEY) { v18 = CMath::fuint(element->alpha * 224.0f); } else { v18 = 1; } int32_t v8 = 0; if (!(material->flags & 0x1) && !(material->flags & 0x100) && lighting->m_flags & 0x10) { // TODO // v8 = Sub873FF0(); if (v8) { if (lighting->m_flags & 0x8) { v8 = 1; } if (element->type == 1) { v8 = 0; } } } int32_t v9 = v18 && (/* TODO !GxCaps().dword130 ||*/ v8); int32_t v10 = std::min(boneInfluences, 2); int32_t v11 = std::min(v8, 2); element->vertexPermute = shaded + 2 * (v11 + v10 + 2 * v11 + lightCount + 4 * (v11 + v10 + 2 * v11)); element->pixelPermute = v8 + 4 * (CShaderEffect::s_usePcfFiltering + 2 * v9); // TODO // element->dword3C = v8; } int32_t CM2Scene::SortOpaque(uint32_t a, uint32_t b, const void* userArg) { auto elements = static_cast(userArg)->m_elements.Ptr(); auto elementA = const_cast(&elements[a]); auto elementB = const_cast(&elements[b]); if (elementA->type < elementB->type) { return -1; } if (elementA->type > elementB->type) { return 1; } switch (elementA->type) { case 0: case 1: return CM2Scene::SortOpaqueGeoBatches(elementA, elementB); case 3: return CM2Scene::SortOpaqueRibbons(elementA, elementB); case 4: return CM2Scene::SortOpaqueParticles(elementA, elementB); default: return 0; } } int32_t CM2Scene::SortOpaqueGeoBatches(M2Element* elementA, M2Element* elementB) { auto modelA = elementA->model; auto dataA = modelA->m_shared->m_data; auto batchA = elementA->batch; auto modelB = elementB->model; auto dataB = modelB->m_shared->m_data; auto batchB = elementB->batch; if (elementA->type == 0) { if (batchA->materialLayer < batchB->materialLayer) { return -1; } if (batchA->materialLayer > batchB->materialLayer) { return 1; } if (elementA->effect && elementB->effect) { auto effectA = elementA->effect; auto effectB = elementB->effect; auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; if (vertexShaderA < vertexShaderB) { return -1; } if (vertexShaderA > vertexShaderB) { return 1; } if (pixelShaderA < pixelShaderB) { return -1; } if (pixelShaderA > pixelShaderB) { return 1; } } if (modelA->m_shared < modelB->m_shared) { return -1; } if (modelA->m_shared > modelB->m_shared) { return 1; } if ((elementA->flags & 0x4) < (elementB->flags & 0x4)) { return -1; } if ((elementA->flags & 0x4) > (elementB->flags & 0x4)) { return 1; } if (modelA < modelB) { return -1; } if (modelA > modelB) { return 1; } if (elementA->skinSection->boneComboIndex < elementB->skinSection->boneComboIndex) { return -1; } if (elementA->skinSection->boneComboIndex > elementB->skinSection->boneComboIndex) { return 1; } } auto materialA = &dataA->materials[batchA->materialIndex]; auto materialB = &dataB->materials[batchB->materialIndex]; if (materialA->blendMode < materialB->blendMode) { return -1; } if (materialA->blendMode > materialB->blendMode) { return 1; } if ((materialA->flags & 0x1F) < (materialB->flags & 0x1F)) { return -1; } if ((materialA->flags & 0x1F) > (materialB->flags & 0x1F)) { return 1; } if (batchA->textureCount > 0 && batchB->textureCount > 0) { for (int32_t i = 0; i < std::min(batchA->textureCount, batchB->textureCount); i++) { auto textureIndexA = dataA->textureCombos[batchA->textureComboIndex]; auto textureA = textureIndexA >= dataA->textures.Count() ? 0 : reinterpret_cast(modelA->m_textures[textureIndexA]); auto textureIndexB = dataB->textureCombos[batchB->textureComboIndex]; auto textureB = textureIndexB >= dataB->textures.Count() ? 0 : reinterpret_cast(modelB->m_textures[textureIndexB]); if ((textureA - textureB) / sizeof(void*) < 0) { return -1; } if ((textureA - textureB) / sizeof(void*) > 0) { return 1; } } } if (batchA->textureCount < batchB->textureCount) { return -1; } if (batchA->textureCount > batchB->textureCount) { return 1; } if (batchA < batchB) { return -1; } return batchA > batchB; } int32_t CM2Scene::SortOpaqueParticles(M2Element* elementA, M2Element* elementB) { // TODO return 0; } int32_t CM2Scene::SortOpaqueRibbons(M2Element* elementA, M2Element* elementB) { // TODO return 0; } int32_t CM2Scene::SortTransparent(uint32_t a, uint32_t b, const void* userArg) { auto elements = static_cast(userArg)->m_elements.Ptr(); auto elementA = const_cast(&elements[a]); auto elementB = const_cast(&elements[b]); if (elementA->float10 > elementB->float10) { return -1; } if (elementA->float10 < elementB->float10) { return 1; } if ((elementA->flags & 0x1) > (elementB->flags & 0x1)) { return -1; } if ((elementA->flags & 0x1) < (elementB->flags & 0x1)) { return 1; } if (elementA->priorityPlane < elementB->priorityPlane) { return -1; } if (elementA->priorityPlane > elementB->priorityPlane) { return 1; } if (elementA->float14 > elementB->float14) { return -1; } if (elementA->float14 < elementB->float14) { return 1; } if ((CM2Scene::s_optFlags & 0x4000) && (elementA->type != elementB->type || elementA->model != elementB->model) && elementA->effect && elementB->effect ) { auto effectA = elementA->effect; auto effectB = elementB->effect; auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; if (vertexShaderA < vertexShaderB) { return -1; } if (vertexShaderA > vertexShaderB) { return 1; } if (pixelShaderA < pixelShaderB) { return -1; } if (pixelShaderA > pixelShaderB) { return 1; } } if (elementA->model < elementB->model) { return -1; } if (elementA->model > elementB->model) { return 1; } if (elementA->type < elementB->type) { return -1; } if (elementA->type > elementB->type) { return 1; } if (elementA->type <= 2) { if (elementA->batch->materialLayer < elementB->batch->materialLayer) { return -1; } if (elementA->batch->materialLayer > elementB->batch->materialLayer) { return 1; } } if (!(CM2Scene::s_optFlags & 0x4000) || !elementA->effect || !elementB->effect) { return CM2Scene::SortOpaque(a, b, userArg); } auto effectA = elementA->effect; auto effectB = elementB->effect; auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; if (vertexShaderA < vertexShaderB) { return -1; } if (vertexShaderA > vertexShaderB) { return 1; } if (pixelShaderA < pixelShaderB) { return -1; } if (pixelShaderA > pixelShaderB) { return 1; } return CM2Scene::SortOpaque(a, b, userArg); } void CM2Scene::AdvanceTime(uint32_t a2) { this->m_time += a2; this->m_cache->UpdateShared(); this->m_cache->GarbageCollect(0); this->m_flags |= 0x4; this->uint10 = a2; if (a2) { for (auto model = this->m_animateList; model; model = model->m_animateNext) { model->ProcessCallbacksRecursive(); } } this->m_flags &= ~0x4; } void CM2Scene::Animate(const C3Vector& cameraPos) { this->uint14++; uint32_t optFlags = this->m_cache->m_flags & 0xE000; if (CM2Scene::s_optFlags != optFlags) { CM2Scene::s_optFlags = optFlags; } GxXformView(this->m_view); C3Vector invCameraPos = { -cameraPos.x, -cameraPos.y, -cameraPos.z }; this->m_view.Translate(invCameraPos); this->m_viewInv = this->m_view.Inverse(this->m_view.Determinant()); if (this->m_cache->m_flags & 0x4) { // In multithreaded mode, iteration over the animate list is interleaved: // - the current thread animates entries 0, 2, 4, ... // - the newly created thread animates entries 1, 3, 5, ... this->m_cache->BeginThread(CM2Scene::AnimateThread, this); CM2Model* nextModel; for (auto model = this->m_animateList; model; model = nextModel->m_animateNext) { if (!model->m_attachParent) { if (model->m_flag1000) { C3Vector v222 = { 0.0f, 0.0f, 0.0f }; C3Vector v218 = { 1.0f, 1.0f, 1.0f }; model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f); } else { C3Vector v220 = { 0.0f, 0.0f, 0.0f }; C3Vector v221 = { 1.0f, 1.0f, 1.0f }; model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f); } } nextModel = model->m_animateNext; if (!nextModel) { break; } } this->m_cache->WaitThread(); } else { for (auto model = this->m_animateList; model; model = model->m_animateNext) { if (!model->m_attachParent) { if (model->m_flag1000 != 0) { C3Vector v222 = { 0.0f, 0.0f, 0.0f }; C3Vector v218 = { 1.0f, 1.0f, 1.0f }; model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f); } else { C3Vector v220 = { 0.0f, 0.0f, 0.0f }; C3Vector v221 = { 1.0f, 1.0f, 1.0f }; model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f); } } } } for (auto model = this->m_animateList; model; model = model->m_animateNext) { if (!model->m_attachParent) { model->AnimateST(); } } while (this->m_animateList) { // TODO // - this is clearing out the animate list; why? something must reattach things to it... auto model = this->m_animateList; this->m_animateList = model->m_animateNext; model->m_animatePrev = nullptr; model->m_animateNext = nullptr; model->SetupLighting(); } this->array44.SetCount(0); for (int32_t i = 0; i < M2PASS_COUNT; i++) { this->array54[i].SetCount(0); } this->m_elements.SetCount(0); int32_t elementIndex = 0; while (this->m_drawList) { auto model = this->m_drawList; this->m_drawList = model->m_drawNext; model->m_flag8 = 0; model->m_flag10000 = 0; model->m_drawPrev = nullptr; model->m_drawNext = nullptr; if (!model->IsDrawable(0, 0) || model->m_flag4000) { continue; } auto v19 = model->m_currentLighting; auto data = model->m_shared->m_data; auto v21 = v19->m_flags & 0x20; auto v22 = v19->m_flags & 0x40; if (v21 && v22) { // TODO // - liquid plane stuff } auto skinProfile = model->m_shared->skinProfile; auto v17 = (this->m_cache->m_flags & 0x1) == 0; int32_t v229; if (v17 || (model->m_flags & 0x1) != 0 || (v17 = (model->m_flag40) == 0, v229 = 1, v17)) { v229 = 0; } uint32_t batchCount; if (model->ptr2D0) { // TODO // batchCount = (model->ptr2D0 + 4); assert(false); } else { batchCount = skinProfile->batches.Count(); } for (int32_t batchIndex = 0; batchIndex < batchCount; batchIndex++) { M2Batch* batch; M2SkinSection* skinSection; CShaderEffect* effect; int32_t v221; int32_t v222; if (model->ptr2D0) { // TODO // batch = &model->m_optGeo->batches[batchIndex]; // skinSection = model->m_optGeo->skinSections[batch->skinSectionIndex]; assert(false); } else { batch = &skinProfile->batches[batchIndex]; skinSection = &model->m_shared->m_skinSections[batch->skinSectionIndex]; if (!model->m_skinSections[batch->skinSectionIndex]) { continue; } } if (batch->shader == 0x8000) { continue; } float alpha = model->alpha19C; if (batch->colorIndex < data->colors.Count()) { auto& color = model->m_colors[batch->colorIndex]; alpha *= color.alphaTrack.currentValue; } if (batch->textureCount) { auto& textureWeight = model->m_textureWeights[data->textureWeightCombos[batch->textureWeightComboIndex]]; alpha *= textureWeight.weightTrack.currentValue; } if (alpha < 0.000099999997f) { continue; } M2Material* material = &data->materials[batch->materialIndex]; auto v17 = (batch->flags & 0x4) == 0; if (v17 || (v17 = this->uint104 == 0, v222 = 1, v17)) { v222 = 0; } M2Material* layerMaterial = batch->materialLayer ? &data->materials[batch->materialIndex - batch->materialLayer] : &data->materials[batch->materialIndex]; if (layerMaterial->blendMode > 1 || (v221 = 0, alpha < 0.99998999f)) { v221 = 1; } if (model->ptr2D0) { // TODO // effect = model->m_optGeo->effects[batchIndex]; assert(false); } else { effect = model->m_shared->m_batchShaders[batchIndex]; } if (!effect) { continue; } auto element = this->m_elements.New(); if (v222) { element->type = 1; } else if (!model->IsBatchDoodadCompatible(batch) || v221) { element->type = 0; } else { element->type = 2; } element->model = model; element->flags = 0x0; if (v221 == 1 && v21 && v22 && !v222) { element->flags |= 0x2; } if (model->ptr2D0) { element->flags |= 0x4; } element->alpha = alpha; element->index = batchIndex; element->priorityPlane = batch->priorityPlane; element->batch = batch; element->skinSection = skinSection; element->effect = effect; CM2Scene::ComputeElementShaders(element); float v58; if (v221 < 1) { element->float14 = model->float88; v58 = model->float88; } else if (data->flags & 0x10) { element->float14 = (skinSection->sortCenterPosition * model->m_boneMatrices[skinSection->centerBoneIndex]).SquaredMag(); v58 = model->float88; } else { // TODO other sort position logic v58 = model->float88; } element->float10 = v58; if (element->type == 2) { // TODO } else if (v221 == 1) { if (v222) { if (v22) { *this->array54[2].New() = elementIndex; } else { *this->array54[1].New() = elementIndex; } } else { if (v21) { *this->array54[1].New() = elementIndex; } if (v22) { *this->array54[2].New() = elementIndex; } } } else { *this->array54[v221].New() = elementIndex; } elementIndex++; if (v229 && !v222 && v221 >= 1 && !(material->flags & 0x10)) { // TODO } } // TODO // - ribbons // TODO // - draw callbacks } M2HeapSort(CM2Scene::SortOpaque, this->array54[0].Ptr(), this->array54[0].Count(), this); M2HeapSort(CM2Scene::SortTransparent, this->array54[1].Ptr(), this->array54[1].Count(), this); M2HeapSort(CM2Scene::SortTransparent, this->array54[2].Ptr(), this->array54[2].Count(), this); // TODO sort additive particles } CM2Model* CM2Scene::CreateModel(const char* file, uint32_t a3) { if (!file) { return nullptr; } CM2Shared* shared = this->m_cache->CreateShared(file, a3); if (!shared) { shared = this->m_cache->CreateShared("Spells\\ErrorCube.mdx", 0); } CM2Model* model = nullptr; if (shared) { model = CM2Model::AllocModel(g_modelPool); if (model) { if (!model->Initialize(this, shared, nullptr, a3)) { // TODO } } shared->Release(); } return model; } int32_t CM2Scene::Draw(M2PASS pass) { // TODO // - conditional check on this->dword144 if (CM2Scene::s_optFlags != (this->m_cache->m_flags & 0xE000)) { CM2Scene::s_optFlags = this->m_cache->m_flags & 0xE000; } CM2SceneRender render(this); render.Draw(pass, this->m_elements.m_data, this->array54[pass].m_data, this->array54[pass].Count()); if (pass == M2PASS_0) { render.Draw(pass, this->m_elements.m_data, this->array44.m_data, this->array44.Count()); } return 1; } void CM2Scene::SelectLights(CM2Lighting* lighting) { for (auto light = this->m_lightList; light; light = light->m_lightNext) { lighting->AddLight(light); } // TODO }