#include "CK3dEntity.hpp" #include "../CKStateChunk.hpp" #include "../CKContext.hpp" #include "CKMesh.hpp" namespace LibCmo::CK2::ObjImpls { CK3dEntity::CK3dEntity(CKContext* ctx, CK_ID ckid, CKSTRING name) : CKRenderObject(ctx, ckid, name), m_PotentialMeshes(), m_CurrentMesh(nullptr), m_WorldMatrix(), m_ZOrder(0), m_MoveableFlags(YYCC::EnumHelper::Merge( VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_PICKABLE, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_VISIBLE, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_RENDERCHANNELS, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_INVERSEWORLDMATVALID )), m_3dEntityFlags(static_cast(0)) {} CK3dEntity::~CK3dEntity() {} void CK3dEntity::CheckPreDeletion() { CKRenderObject::CheckPreDeletion(); // check active mesh if (m_CurrentMesh->IsToBeDeleted()) { m_CurrentMesh = nullptr; } // check potential meshes XContainer::NSXObjectPointerArray::PreDeletedCheck(m_PotentialMeshes, m_Context); } bool CK3dEntity::Save(CKStateChunk* chunk, CKFileVisitor* file, CKDWORD flags) { bool suc = CKRenderObject::Save(chunk, file, flags); if (!suc) return false; // write associated mesh data if (m_CurrentMesh != nullptr || m_PotentialMeshes.size() != 0) { chunk->WriteIdentifier(CK_STATESAVEFLAGS_3DENTITY::CK_STATESAVE_MESHS); // write current mesh chunk->WriteObjectPointer(m_CurrentMesh); // write potential meshes chunk->WriteXObjectPointerArray(m_PotentialMeshes); } // write core entity data { chunk->WriteIdentifier(CK_STATESAVEFLAGS_3DENTITY::CK_STATESAVE_3DENTITYNDATA); // regulate self flag again // MARK: originally we should check parent here. // but we do not support parent and hierarchy feature, so we simply remove flag YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PARENTVALID); // MARK: originally we should check grouped into CKPlace here. // but we do not support CKPlace, so we simply remove this flag YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PLACEVALID); // check z-order, if not zero, save it if (m_ZOrder != 0) { YYCC::EnumHelper::Add(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_ZORDERVALID); } else { YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_ZORDERVALID); } // write 2 flags chunk->WriteStruct(m_3dEntityFlags); chunk->WriteStruct(m_MoveableFlags); // write world matrix chunk->WriteStruct(reinterpret_cast(&m_WorldMatrix[0])); chunk->WriteStruct(reinterpret_cast(&m_WorldMatrix[1])); chunk->WriteStruct(reinterpret_cast(&m_WorldMatrix[2])); chunk->WriteStruct(reinterpret_cast(&m_WorldMatrix[3])); // MARK: because we do not support Parent and CKPlace, // and the IDA code also instruct that no need to write any data if no Parent or CKPlace. // so we skip the Parent and CKPlace writing // write z-order if (m_ZOrder != 0) { chunk->WriteStruct(m_ZOrder); } } chunk->SetClassId(CK_CLASSID::CKCID_3DENTITY); return true; } bool CK3dEntity::Load(CKStateChunk* chunk, CKFileVisitor* file) { bool suc = CKRenderObject::Load(chunk, file); if (!suc) return false; // backup moveable flags bool hasWorldAligned = YYCC::EnumHelper::Has(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_WORLDALIGNED); // MARK: object animation is skipped // read associated meshs data if (chunk->SeekIdentifier(CK_STATESAVEFLAGS_3DENTITY::CK_STATESAVE_MESHS)) { // MARK: I don't know why origianl code do not clear potential mesh list // so I clear it in there. m_PotentialMeshes.clear(); // read current mesh CKObject* pendingMesh = nullptr; chunk->ReadObjectPointer(pendingMesh); if (pendingMesh != nullptr && pendingMesh->GetClassID() == CK_CLASSID::CKCID_MESH) { m_CurrentMesh = static_cast(pendingMesh); } // read other meshs XContainer::XObjectPointerArray potentials; chunk->ReadXObjectPointerArray(potentials); for (const auto& ptr : potentials) { if (ptr == nullptr) continue; XContainer::NSXObjectPointerArray::AddIfNotHere(m_PotentialMeshes, ptr); } // add current mesh to potential meshes if (m_CurrentMesh != nullptr) { XContainer::NSXObjectPointerArray::AddIfNotHere(m_PotentialMeshes, m_CurrentMesh); } } // read core entity data if (chunk->SeekIdentifier(CK_STATESAVEFLAGS_3DENTITY::CK_STATESAVE_3DENTITYNDATA)) { // read 2 flags chunk->ReadStruct(m_3dEntityFlags); chunk->ReadStruct(m_MoveableFlags); // remove some properties YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_UPDATELASTFRAME, CK_3DENTITY_FLAGS::CK_3DENTITY_RESERVED0 ); YYCC::EnumHelper::Remove(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_RESERVED2, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_STENCILONLY, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_DONTUPDATEFROMPARENT, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_INVERSEWORLDMATVALID, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_HASMOVED, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_BOXVALID, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_USERBOX, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_UPTODATE ); if (hasWorldAligned) { YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_WORLDALIGNED); } // if order render first if (YYCC::EnumHelper::Has(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_RENDERFIRST)) { m_ZOrder = 10000; } // read matrix // reset m_WorldMatrix.ResetToIdentity(); // force read as vector3 chunk->ReadStruct(reinterpret_cast(&m_WorldMatrix[0])); chunk->ReadStruct(reinterpret_cast(&m_WorldMatrix[1])); chunk->ReadStruct(reinterpret_cast(&m_WorldMatrix[2])); chunk->ReadStruct(reinterpret_cast(&m_WorldMatrix[3])); // MARK: check right-hand? // I don't know how it checked, just reinterpter IDA code. VxMath::VxVector3 col2(*reinterpret_cast(&m_WorldMatrix[2])), col1(*reinterpret_cast(&m_WorldMatrix[1])), col0(*reinterpret_cast(&m_WorldMatrix[0])); VxMath::VxVector3 crossProduct = VxMath::NSVxVector::CrossProduct(col0, col1); CKFLOAT dotProduct = VxMath::NSVxVector::DotProduct(crossProduct, col2); if (dotProduct >= 0.0f) { YYCC::EnumHelper::Remove(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_INDIRECTMATRIX); } else { YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_INDIRECTMATRIX); } // copy visible data // process direct visible if (YYCC::EnumHelper::Has(m_ObjectFlags, CK_OBJECT_FLAGS::CK_OBJECT_VISIBLE)) { YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_VISIBLE); } else { YYCC::EnumHelper::Remove(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_VISIBLE); } // process indirect visible if (YYCC::EnumHelper::Has(m_ObjectFlags, CK_OBJECT_FLAGS::CK_OBJECT_HIERACHICALHIDE)) { YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_HIERARCHICALHIDE); } else { YYCC::EnumHelper::Remove(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_HIERARCHICALHIDE); } // read associated CKPlace if (YYCC::EnumHelper::Has(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PLACEVALID)) { // MARK: we drop the support of CKPlace. // so we just read it and skip it. CK_ID placeid; chunk->ReadObjectID(placeid); // and remove this flag YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PLACEVALID); } // read parent if (YYCC::EnumHelper::Has(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PARENTVALID)) { // MAKR: we drop the support of parent and the whole 3dentity hierarchy system // we ignore this field. CK_ID parentid; chunk->ReadObjectID(parentid); // and remove this flag YYCC::EnumHelper::Remove(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_PARENTVALID); } // read priority (non-zero zorder) if (YYCC::EnumHelper::Has(m_3dEntityFlags, CK_3DENTITY_FLAGS::CK_3DENTITY_ZORDERVALID)) { chunk->ReadStruct(m_ZOrder); } } // MARK: compatibility alternative core data read code removed because I don't need them // MARK: skin and bone are skipped. return true; } void CK3dEntity::Show(CK_OBJECT_SHOWOPTION show) { CKObject::Show(show); YYCC::EnumHelper::Remove(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_VISIBLE, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_HIERARCHICALHIDE ); switch (show) { case CK_OBJECT_SHOWOPTION::CKSHOW: YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_VISIBLE); break; case CK_OBJECT_SHOWOPTION::CKHIERARCHICALHIDE: YYCC::EnumHelper::Add(m_MoveableFlags, VxMath::VX_MOVEABLE_FLAGS::VX_MOVEABLE_HIERARCHICALHIDE); break; case CK_OBJECT_SHOWOPTION::CKHIDE: break; } } bool CK3dEntity::IsVisible() const { // MARK: originally there is a call to this->IsHiddenByParent. // but we drop the support of parent, so we drop that condition. return CKObject::IsVisible(); } #pragma region Misc Oper const VxMath::VxMatrix& CK3dEntity::GetWorldMatrix() const { return m_WorldMatrix; } void CK3dEntity::SetWorldMatrix(const VxMath::VxMatrix& mat) { m_WorldMatrix = mat; } CK_3DENTITY_FLAGS CK3dEntity::GetEntityFlags() const { return m_3dEntityFlags; } void CK3dEntity::SetEntityFlags(CK_3DENTITY_FLAGS flags) { m_3dEntityFlags = flags; } VxMath::VX_MOVEABLE_FLAGS CK3dEntity::GetMoveableFlags() const { return m_MoveableFlags; } void CK3dEntity::SetMoveableFlags(VxMath::VX_MOVEABLE_FLAGS flags) { m_MoveableFlags = flags; } CKDWORD CK3dEntity::GetZOrder() const { return m_ZOrder; } void CK3dEntity::SetZOrder(CKDWORD ord) { m_ZOrder = ord; } #pragma endregion #pragma region Mesh Oper void CK3dEntity::AddPotentialMesh(CKMesh* mesh) { XContainer::NSXObjectPointerArray::AddIfNotHere(m_PotentialMeshes, mesh); } void CK3dEntity::RemovePotentialMesh(CKMesh* mesh) { std::erase(m_PotentialMeshes, mesh); } CKDWORD CK3dEntity::GetPotentialMeshCount() const { return static_cast(m_PotentialMeshes.size()); } CKMesh* CK3dEntity::GetPotentialMesh(CKDWORD idx) const { if (idx >= m_PotentialMeshes.size()) return nullptr; return static_cast(m_PotentialMeshes[idx]); } CKMesh* CK3dEntity::GetCurrentMesh() const { return m_CurrentMesh; } void CK3dEntity::SetCurrentMesh(CKMesh* mesh) { m_CurrentMesh = mesh; if (mesh != nullptr) { AddPotentialMesh(mesh); } } #pragma endregion }