/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2004-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // graphics used by object definitions (object and portraits) #include "C4Include.h" #include "object/C4DefGraphics.h" #include "object/C4Def.h" #include "object/C4DefList.h" #include "object/C4Object.h" #include "object/C4ObjectInfo.h" #include "config/C4Config.h" #include "c4group/C4Components.h" #include "game/C4Application.h" #include "game/C4Game.h" #include "gui/C4Menu.h" #include "object/C4ObjectMenu.h" #include "player/C4Player.h" #include "lib/C4Log.h" #include "landscape/C4Material.h" #include "player/C4PlayerList.h" #include "object/C4GameObjects.h" #include "player/C4RankSystem.h" #include "graphics/C4GraphicsResource.h" #include "graphics/C4Draw.h" #include "graphics/C4Surface.h" #include "object/C4MeshAnimation.h" #include "lib/StdMeshLoader.h" #include "lib/StdMeshUpdate.h" //-------------------------------- C4DefGraphics ----------------------------------------------- C4DefGraphics::C4DefGraphics(C4Def *pOwnDef) { // store def pDef = pOwnDef; // zero fields Type = TYPE_None; Bmp.Bitmap = Bmp.BitmapClr = Bmp.BitmapNormal = NULL; pNext = NULL; fColorBitmapAutoCreated = false; } C4DefGraphics *C4DefGraphics::GetLast() { C4DefGraphics *pLast = this; while (pLast->pNext) pLast = pLast->pNext; return pLast; } void C4DefGraphics::Clear() { // zero own fields switch (Type) { case TYPE_None: break; case TYPE_Bitmap: if (Bmp.BitmapNormal) { delete Bmp.BitmapNormal; Bmp.BitmapNormal=NULL; } if (Bmp.BitmapClr) { delete Bmp.BitmapClr; Bmp.BitmapClr=NULL; } if (Bmp.Bitmap) { delete Bmp.Bitmap; Bmp.Bitmap=NULL; } break; case TYPE_Mesh: if (Mesh) { delete Mesh; Mesh = NULL; } break; } Type = TYPE_None; // delete additonal graphics C4AdditionalDefGraphics *pGrp2N = pNext, *pGrp2; while ((pGrp2=pGrp2N)) { pGrp2N = pGrp2->pNext; pGrp2->pNext = NULL; delete pGrp2; } pNext = NULL; fColorBitmapAutoCreated = false; } bool C4DefGraphics::LoadBitmap(C4Group &hGroup, const char *szFilename, const char *szOverlay, const char *szNormal, bool fColorByOwner) { if (!szFilename) return false; Type = TYPE_Bitmap; // will be reset to TYPE_None in Clear() if loading fails Bmp.Bitmap = new C4Surface(); if (!Bmp.Bitmap->Load(hGroup, szFilename, false, true, C4SF_MipMap)) { Clear(); return false; } // Create owner color bitmaps if (fColorByOwner) { // Create additionmal bitmap Bmp.BitmapClr=new C4Surface(); // if overlay-surface is present, load from that if (szOverlay && Bmp.BitmapClr->Load(hGroup, szOverlay, false, false, C4SF_MipMap)) { // set as Clr-surface, also checking size if (!Bmp.BitmapClr->SetAsClrByOwnerOf(Bmp.Bitmap)) { DebugLogF(" Gfx loading error in %s: %s (%d x %d) doesn't match overlay %s (%d x %d) - invalid file or size mismatch", hGroup.GetFullName().getData(), szFilename, Bmp.Bitmap ? Bmp.Bitmap->Wdt : -1, Bmp.Bitmap ? Bmp.Bitmap->Hgt : -1, szOverlay, Bmp.BitmapClr->Wdt, Bmp.BitmapClr->Hgt); Clear(); return false; } } else { // otherwise, create by all blue shades if (!Bmp.BitmapClr->CreateColorByOwner(Bmp.Bitmap)) { Clear(); return false; } } fColorBitmapAutoCreated = true; } if (szNormal) { Bmp.BitmapNormal = new C4Surface(); if (Bmp.BitmapNormal->Load(hGroup, szNormal, false, true, C4SF_MipMap)) { // Normal map loaded. Sanity check and link. if(Bmp.BitmapNormal->Wdt != Bmp.Bitmap->Wdt || Bmp.BitmapNormal->Hgt != Bmp.Bitmap->Hgt) { DebugLogF(" Gfx loading error in %s: %s (%d x %d) doesn't match normal %s (%d x %d) - invalid file or size mismatch", hGroup.GetFullName().getData(), szFilename, Bmp.Bitmap ? Bmp.Bitmap->Wdt : -1, Bmp.Bitmap ? Bmp.Bitmap->Hgt : -1, szNormal, Bmp.BitmapNormal->Wdt, Bmp.BitmapNormal->Hgt); Clear(); return false; } Bmp.Bitmap->pNormalSfc = Bmp.BitmapNormal; if(Bmp.BitmapClr) Bmp.BitmapClr->pNormalSfc = Bmp.BitmapNormal; } else { // No normal map delete Bmp.BitmapNormal; Bmp.BitmapNormal = NULL; } } Type = TYPE_Bitmap; // success return true; } bool C4DefGraphics::LoadMesh(C4Group &hGroup, const char* szFileName, StdMeshSkeletonLoader& loader) { char* buf = NULL; size_t size; try { if(!hGroup.LoadEntry(szFileName, &buf, &size, 1)) return false; if (SEqualNoCase(GetExtension(szFileName), "xml")) { Mesh = StdMeshLoader::LoadMeshXml(buf, size, ::MeshMaterialManager, loader, hGroup.GetName()); } else { Mesh = StdMeshLoader::LoadMeshBinary(buf, size, ::MeshMaterialManager, loader, hGroup.GetName()); } delete[] buf; Mesh->SetLabel(pDef->id.ToString()); // order submeshes Mesh->PostInit(); } catch (const std::runtime_error& ex) { DebugLogF("Failed to load mesh in definition %s: %s", hGroup.GetName(), ex.what()); delete[] buf; return false; } Type = TYPE_Mesh; return true; } bool C4DefGraphics::LoadSkeleton(C4Group &hGroup, const char* szFileName, StdMeshSkeletonLoader& loader) { char* buf = NULL; size_t size; try { if (!hGroup.LoadEntry(szFileName, &buf, &size, 1)) return false; // delete skeleton from the map for reloading, or else if you delete or rename // a skeleton file in the folder the old skeleton will still exist in the map loader.RemoveSkeleton(hGroup.GetName(), szFileName); if (SEqualNoCase(GetExtension(szFileName), "xml")) { loader.LoadSkeletonXml(hGroup.GetName(), szFileName, buf, size); } else { loader.LoadSkeletonBinary(hGroup.GetName(), szFileName, buf, size); } delete[] buf; } catch (const std::runtime_error& ex) { DebugLogF("Failed to load skeleton in definition %s: %s", hGroup.GetName(), ex.what()); delete[] buf; return false; } return true; } bool C4DefGraphics::Load(C4Group &hGroup, StdMeshSkeletonLoader &loader, bool fColorByOwner) { char Filename[_MAX_PATH+1]; *Filename=0; // load skeletons hGroup.ResetSearch(); while (hGroup.FindNextEntry("*", Filename, NULL, !!*Filename)) { if (!WildcardMatch(C4CFN_DefSkeleton, Filename) && !WildcardMatch(C4CFN_DefSkeletonXml, Filename)) continue; LoadSkeleton(hGroup, Filename, loader); } // Try from Mesh first if (!LoadMesh(hGroup, C4CFN_DefMesh, loader)) if(!LoadMesh(hGroup, C4CFN_DefMeshXml, loader)) LoadBitmap(hGroup, C4CFN_DefGraphics, C4CFN_ClrByOwner, C4CFN_NormalMap, fColorByOwner); // load additional graphics C4DefGraphics *pLastGraphics = this; const int32_t iOverlayWildcardPos = SCharPos('*', C4CFN_ClrByOwnerEx); hGroup.ResetSearch(); *Filename=0; const char* const AdditionalGraphics[] = { C4CFN_DefGraphicsEx, C4CFN_DefGraphicsExMesh, C4CFN_DefGraphicsExMeshXml, NULL }; while (hGroup.FindNextEntry("*", Filename, NULL, !!*Filename)) { for(const char* const* szWildcard = AdditionalGraphics; *szWildcard != NULL; ++szWildcard) { if(!WildcardMatch(*szWildcard, Filename)) continue; // skip def graphics if (SEqualNoCase(Filename, C4CFN_DefGraphics) || SEqualNoCase(Filename, C4CFN_DefMesh) || SEqualNoCase(Filename, C4CFN_DefMeshXml)) continue; // skip scaled def graphics if (WildcardMatch(C4CFN_DefGraphicsScaled, Filename)) continue; // get name char GrpName[_MAX_PATH+1]; const int32_t iWildcardPos = SCharPos('*', *szWildcard); SCopy(Filename + iWildcardPos, GrpName, _MAX_PATH); RemoveExtension(GrpName); // remove trailing number for scaled graphics int32_t extpos; int scale; if ((extpos = SCharLastPos('.', GrpName)) > -1) if (sscanf(GrpName+extpos+1, "%d", &scale) == 1) GrpName[extpos] = '\0'; // clip to max length GrpName[C4MaxName]=0; // create new graphics pLastGraphics->pNext = new C4AdditionalDefGraphics(pDef, GrpName); pLastGraphics = pLastGraphics->pNext; if(*szWildcard == AdditionalGraphics[0]) { // create overlay-filename char OverlayFn[_MAX_PATH+1]; if(fColorByOwner) { // GraphicsX.png -> OverlayX.png SCopy(C4CFN_ClrByOwnerEx, OverlayFn, _MAX_PATH); OverlayFn[iOverlayWildcardPos]=0; SAppend(Filename + iWildcardPos, OverlayFn); EnforceExtension(OverlayFn, GetExtension(C4CFN_ClrByOwnerEx)); } // create normal filename char NormalFn[_MAX_PATH+1]; SCopy(C4CFN_NormalMapEx, NormalFn, _MAX_PATH); NormalFn[iOverlayWildcardPos]=0; SAppend(Filename + iWildcardPos, NormalFn); EnforceExtension(NormalFn, GetExtension(C4CFN_NormalMapEx)); // load them if (!pLastGraphics->LoadBitmap(hGroup, Filename, fColorByOwner ? OverlayFn : NULL, NormalFn, fColorByOwner)) return false; } else { if (!pLastGraphics->LoadMesh(hGroup, Filename, loader)) return false; } } } // done, success return true; } C4DefGraphics *C4DefGraphics::Get(const char *szGrpName) { // no group or empty string: base graphics if (!szGrpName || !szGrpName[0]) return this; // search additional graphics for (C4AdditionalDefGraphics *pGrp = pNext; pGrp; pGrp=pGrp->pNext) if (SEqualNoCase(pGrp->GetName(), szGrpName)) return pGrp; // nothing found return NULL; } void C4DefGraphics::Draw(C4Facet &cgo, DWORD iColor, C4Object *pObj, int32_t iPhaseX, int32_t iPhaseY, C4DrawTransform* trans) { // default: def picture rect C4Rect fctPicRect = pDef->PictureRect; C4Facet fctPicture; // if assigned: use object specific rect and graphics if (pObj) if (pObj->PictureRect.Wdt) fctPicRect = pObj->PictureRect; // specific object color? if (pObj) pObj->PrepareDrawing(); switch(Type) { case C4DefGraphics::TYPE_None: // Def has no graphics break; case C4DefGraphics::TYPE_Bitmap: fctPicture.Set(GetBitmap(iColor),fctPicRect.x,fctPicRect.y,fctPicRect.Wdt,fctPicRect.Hgt); fctPicture.DrawTUnscaled(cgo,true,iPhaseX,iPhaseY,trans); break; case C4DefGraphics::TYPE_Mesh: // TODO: Allow rendering of a mesh directly, without instance (to render pose; no animation) std::unique_ptr dummy; StdMeshInstance* instance; C4Value value; if (pObj) { instance = pObj->pMeshInstance; pObj->GetProperty(P_PictureTransformation, &value); } else { dummy.reset(new StdMeshInstance(*Mesh, 1.0f)); instance = dummy.get(); pDef->GetProperty(P_PictureTransformation, &value); } StdMeshMatrix matrix; if (C4ValueToMatrix(value, &matrix)) pDraw->SetMeshTransform(&matrix); pDraw->SetPerspective(true); pDraw->RenderMesh(*instance, cgo.Surface, cgo.X,cgo.Y, cgo.Wdt, cgo.Hgt, pObj ? pObj->Color : iColor, trans); pDraw->SetPerspective(false); pDraw->SetMeshTransform(NULL); break; } if (pObj) pObj->FinishedDrawing(); // draw overlays if (pObj && pObj->pGfxOverlay) for (C4GraphicsOverlay *pGfxOvrl = pObj->pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext()) if (pGfxOvrl->IsPicture()) pGfxOvrl->DrawPicture(cgo, pObj, trans); } void C4DefGraphics::DrawClr(C4Facet &cgo, bool fAspect, DWORD dwClr) { if (Type != TYPE_Bitmap) return; // TODO // create facet and draw it C4Surface *pSfc = Bmp.BitmapClr ? Bmp.BitmapClr : Bmp.Bitmap; if (!pSfc) return; C4Facet fct(pSfc, 0,0,pSfc->Wdt, pSfc->Hgt); fct.DrawClr(cgo, fAspect, dwClr); } void C4DefGraphicsAdapt::CompileFunc(StdCompiler *pComp) { bool fCompiler = pComp->isCompiler(); // nothing? if (!fCompiler && !pDefGraphics) return; // definition C4ID id; if (!fCompiler) id = pDefGraphics->pDef->id; pComp->Value(id); // go over two separators ("::"). Expect them if an id was found. if (!pComp->Separator(StdCompiler::SEP_PART2) || !pComp->Separator(StdCompiler::SEP_PART2)) pComp->excCorrupt("DefGraphics: expected \"::\""); // compile name StdStrBuf Name; if (!fCompiler) Name = pDefGraphics->GetName(); pComp->Value(mkDefaultAdapt(mkParAdapt(Name, StdCompiler::RCT_Idtf), "")); // reading: search def-graphics if (fCompiler) { // search definition, throw expection if not found C4Def *pDef = ::Definitions.ID2Def(id); // search def-graphics if (!pDef || !( pDefGraphics = pDef->Graphics.Get(Name.getData()) )) pComp->excCorrupt("DefGraphics: could not find graphics \"%s\" in %s(%s)!", Name.getData(), id.ToString(), pDef ? pDef->GetName() : "def not found"); } } C4AdditionalDefGraphics::C4AdditionalDefGraphics(C4Def *pOwnDef, const char *szName) : C4DefGraphics(pOwnDef) { // store name SCopy(szName, Name, C4MaxName); } // --------------------------------------------------------------------------- // C4DefGraphicsPtrBackup: Functionality to reload def graphics at runtime C4DefGraphicsPtrBackupEntry::C4DefGraphicsPtrBackupEntry(C4DefGraphics *pSourceGraphics): pMeshUpdate(NULL) { // assign graphics + def pGraphicsPtr = pSourceGraphics; pDef = pSourceGraphics->pDef; // assign name const char *szName = pGraphicsPtr->GetName(); if (szName) SCopy(szName, Name, C4MaxName); else *Name=0; // assign mesh update if(pSourceGraphics->Type == C4DefGraphics::TYPE_Mesh) pMeshUpdate = new StdMeshUpdate(*pSourceGraphics->Mesh); } C4DefGraphicsPtrBackupEntry::~C4DefGraphicsPtrBackupEntry() { // graphics ptr still assigned? then remove dead graphics pointers from objects if (pGraphicsPtr) AssignRemoval(); delete pMeshUpdate; } void C4DefGraphicsPtrBackupEntry::AssignUpdate() { // Update all attached meshes that were using this mesh UpdateAttachedMeshes(); // only if graphics are assigned if (pGraphicsPtr) { // check all objects for (C4Object *pObj : Objects) { if (pObj && pObj->Status) { if (pObj->pGraphics == pGraphicsPtr) { // same graphics found. Update mesh graphics if any. if(pMeshUpdate) { assert(pObj->pMeshInstance != NULL); // object had mesh graphics, so mesh instance should be present assert(&pObj->pMeshInstance->GetMesh() == &pMeshUpdate->GetOldMesh()); // mesh instance of correct type even // Get new mesh from reloaded graphics C4DefGraphics *pGrp = pDef->Graphics.Get(Name); if(pGrp && pGrp->Type == C4DefGraphics::TYPE_Mesh) pMeshUpdate->Update(pObj->pMeshInstance, *pGrp->Mesh); } // try to set new graphics if (!pObj->SetGraphics(Name, pDef)) if (!pObj->SetGraphics(Name, pObj->Def)) { // shouldn't happen pObj->AssignRemoval(); pObj->pGraphics=NULL; } } // remove any overlay graphics for (;;) { C4GraphicsOverlay *pGfxOverlay; for (pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext()) if (pGfxOverlay->GetGfx() == pGraphicsPtr) { // then remove this overlay and redo the loop, because iterator has become invalid pObj->RemoveGraphicsOverlay(pGfxOverlay->GetID()); break; } // looped through w/o removal? if (!pGfxOverlay) break; } // update menu frame decorations - may do multiple updates to the same deco if multiple menus share it... C4GUI::FrameDecoration *pDeco; if (pDef && pObj->Menu && (pDeco = pObj->Menu->GetFrameDecoration())) if (pDeco->idSourceDef == pDef->id) if (!pDeco->UpdateGfx()) pObj->Menu->SetFrameDeco(NULL); } } // done; reset field to indicate finished update pGraphicsPtr = NULL; } } void C4DefGraphicsPtrBackupEntry::AssignRemoval() { // Remove all attached meshes that were using this mesh UpdateAttachedMeshes(); // only if graphics are assigned if (pGraphicsPtr) { // check all objects for (C4Object *pObj : Objects) if (pObj && pObj->Status) { if (pObj->pGraphics == pGraphicsPtr) { // same graphics found. If these are mesh graphics then remove // the object because the StdMesh has already been unloaded. if(pObj->pMeshInstance) { assert(&pObj->pMeshInstance->GetMesh() == &pMeshUpdate->GetOldMesh()); pObj->AssignRemoval(); delete pObj->pMeshInstance; pObj->pMeshInstance = NULL; pObj->pGraphics = NULL; } // sprite graphics; reset them else if (!pObj->SetGraphics()) { pObj->AssignRemoval(); pObj->pGraphics=NULL; } } // remove any overlay graphics for (;;) { C4GraphicsOverlay *pGfxOverlay; for (pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext()) if (pGfxOverlay->GetGfx() == pGraphicsPtr) { // then remove this overlay and redo the loop, because iterator has become invalid pObj->RemoveGraphicsOverlay(pGfxOverlay->GetID()); break; } // looped through w/o removal? if (!pGfxOverlay) break; } // remove menu frame decorations C4GUI::FrameDecoration *pDeco; if (pDef && pObj->Menu && (pDeco = pObj->Menu->GetFrameDecoration())) if (pDeco->idSourceDef == pDef->id) pObj->Menu->SetFrameDeco(NULL); } // done; reset field to indicate finished update pGraphicsPtr = NULL; } } void C4DefGraphicsPtrBackupEntry::UpdateAttachedMeshes() { for (C4Object *pObj : Objects) { if (pObj && pObj->Status) { if(pObj->pMeshInstance) UpdateAttachedMesh(pObj->pMeshInstance); for (C4GraphicsOverlay* pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext()) if(pGfxOverlay->pMeshInstance) UpdateAttachedMesh(pGfxOverlay->pMeshInstance); } } } void C4DefGraphicsPtrBackupEntry::UpdateAttachedMesh(StdMeshInstance* instance) { // Update if instance is an owned attached mesh if(pMeshUpdate && &instance->GetMesh() == &pMeshUpdate->GetOldMesh() && instance->GetAttachParent() && instance->GetAttachParent()->OwnChild) { C4DefGraphics *pGrp = pDef->Graphics.Get(Name); // null for failed def. reload if(pGrp && pGrp->Type == C4DefGraphics::TYPE_Mesh) { pMeshUpdate->Update(instance, *pGrp->Mesh); // might detach from parent } else { instance->GetAttachParent()->Parent->DetachMesh(instance->GetAttachParent()->Number); } } // Non-attached meshes and unowned attached meshes are updated in // AssignUpdate or AssignRemoval, respectively, since they are // contained in the object list. // Copy the attached mesh list before recursion because the recursive // call might detach meshes, altering the list and invalidating // iterators. std::vector attached_meshes; for(StdMeshInstance::AttachedMeshIter iter = instance->AttachedMeshesBegin(); iter != instance->AttachedMeshesEnd(); ++iter) attached_meshes.push_back(*iter); for(std::vector::iterator iter = attached_meshes.begin(); iter != attached_meshes.end(); ++iter) // TODO: Check that this mesh is still attached? UpdateAttachedMesh((*iter)->Child); } C4DefGraphicsPtrBackup::C4DefGraphicsPtrBackup(): MeshMaterialUpdate(::MeshMaterialManager), MeshAnimationUpdate(::Definitions.GetSkeletonLoader()), fApplied(false) { } C4DefGraphicsPtrBackup::~C4DefGraphicsPtrBackup() { if(!fApplied) AssignRemoval(); for(std::list::iterator iter = Entries.begin(); iter != Entries.end(); ++iter) delete *iter; } void C4DefGraphicsPtrBackup::Add(C4DefGraphics* pGfx) { for(C4DefGraphics* pCur = pGfx; pCur != NULL; pCur = pCur->pNext) Entries.push_back(new C4DefGraphicsPtrBackupEntry(pCur)); // Remove all mesh materials that were loaded from this definition C4Def* pDef = pGfx->pDef; for(StdMeshMatManager::Iterator iter = ::MeshMaterialManager.Begin(); iter != MeshMaterialManager.End(); ) { StdStrBuf Filename; Filename.Copy(pDef->Filename); Filename.Append("/"); Filename.Append(GetFilename(iter->FileName.getData())); if(Filename == iter->FileName) iter = ::MeshMaterialManager.Remove(iter, &MeshMaterialUpdate); else ++iter; } } void C4DefGraphicsPtrBackup::AssignRemoval() { MeshMaterialUpdate.Cancel(); // Remove gfx for(std::list::iterator iter = Entries.begin(); iter != Entries.end(); ++iter) (*iter)->AssignRemoval(); fApplied = true; } void C4DefGraphicsPtrBackup::AssignUpdate() { // Update mesh materials for all meshes for(C4DefList::Table::iterator iter = Definitions.table.begin(); iter != Definitions.table.end(); ++iter) if(iter->second->Graphics.Type == C4DefGraphics::TYPE_Mesh) MeshMaterialUpdate.Update(iter->second->Graphics.Mesh); // Then, update mesh references in instances, attach bones by name, and update sprite gfx for(std::list::iterator iter = Entries.begin(); iter != Entries.end(); ++iter) (*iter)->AssignUpdate(); // Update mesh materials and animations for all mesh instances. for (C4Object *pObj : Objects) { if (pObj && pObj->Status) { if(pObj->pMeshInstance) UpdateMesh(pObj->pMeshInstance); for (C4GraphicsOverlay* pGfxOverlay = pObj->pGfxOverlay; pGfxOverlay; pGfxOverlay = pGfxOverlay->GetNext()) if(pGfxOverlay->pMeshInstance) UpdateMesh(pGfxOverlay->pMeshInstance); } } fApplied = true; } void C4DefGraphicsPtrBackup::UpdateMesh(StdMeshInstance* instance) { MeshMaterialUpdate.Update(instance); MeshAnimationUpdate.Update(instance); // Recursive for attached meshes not in object list for (StdMeshInstance::AttachedMeshIter iter = instance->AttachedMeshesBegin(); iter != instance->AttachedMeshesEnd(); ++iter) if ((*iter)->OwnChild) UpdateMesh((*iter)->Child); } // --------------------------------------------------------------------------- // C4GraphicsOverlay: graphics overlay used to attach additional graphics to objects C4GraphicsOverlay::~C4GraphicsOverlay() { // Free mesh instance delete pMeshInstance; pMeshInstance = NULL; // free any additional overlays C4GraphicsOverlay *pNextOther = pNext, *pOther; while ((pOther = pNextOther)) { pNextOther = pOther->pNext; pOther->pNext = NULL; delete pOther; } } void C4GraphicsOverlay::UpdateFacet() { // special: Nothing to update for object and pSourceGfx may be NULL // If there will ever be something to init here, UpdateFacet() will also need to be called when objects have been loaded if (eMode == MODE_Object) return; // otherwise, source graphics must be specified if (!pSourceGfx) return; C4Def *pDef = pSourceGfx->pDef; assert(pDef); fZoomToShape = false; // Clear old mesh instance, if any delete pMeshInstance; pMeshInstance = NULL; // update by mode switch (eMode) { case MODE_None: break; case MODE_Base: // def base graphics if (pSourceGfx->Type == C4DefGraphics::TYPE_Bitmap) fctBlit.Set(pSourceGfx->GetBitmap(), 0, 0, pDef->Shape.Wdt, pDef->Shape.Hgt, pDef->Shape.x+pDef->Shape.Wdt/2, pDef->Shape.y+pDef->Shape.Hgt/2); else if (pSourceGfx->Type == C4DefGraphics::TYPE_Mesh) pMeshInstance = new StdMeshInstance(*pSourceGfx->Mesh, 1.0f); break; case MODE_Action: // graphics of specified action { // Clear old facet fctBlit.Default(); // Ensure there is actually an action set if (!Action[0]) return; C4Value v; pDef->GetProperty(P_ActMap, &v); C4PropList *actmap = v.getPropList(); if (!actmap) return; actmap->GetPropertyByS(::Strings.RegString(Action), &v); C4PropList *action = v.getPropList(); if (!action) return; if (pSourceGfx->Type == C4DefGraphics::TYPE_Bitmap) { fctBlit.Set(pSourceGfx->GetBitmap(), action->GetPropertyInt(P_X), action->GetPropertyInt(P_Y), action->GetPropertyInt(P_Wdt), action->GetPropertyInt(P_Hgt)); // FIXME: fctBlit.TargetX has to be set here } else if (pSourceGfx->Type == C4DefGraphics::TYPE_Mesh) { C4String* AnimationName = action->GetPropertyStr(P_Animation); if (!AnimationName) return; pMeshInstance = new StdMeshInstance(*pSourceGfx->Mesh, 1.0f); const StdMeshAnimation* Animation = pSourceGfx->Mesh->GetSkeleton().GetAnimationByName(AnimationName->GetData()); if (!Animation) return; pMeshInstance->PlayAnimation(*Animation, 0, NULL, new C4ValueProviderRef(iPhase, ftofix(Animation->Length / action->GetPropertyInt(P_Length))), new C4ValueProviderConst(itofix(1)), true); } break; } case MODE_ObjectPicture: // ingame picture of object // calculated at runtime break; case MODE_IngamePicture: case MODE_Picture: // def picture fZoomToShape = true; // drawn at runtime break; case MODE_ExtraGraphics: // like ColorByOwner-sfc // calculated at runtime break; case MODE_Rank: // drawn at runtime break; case MODE_Object: // TODO break; } } void C4GraphicsOverlay::Set(Mode aMode, C4DefGraphics *pGfx, const char *szAction, DWORD dwBMode, C4Object *pOvrlObj) { // set values eMode = aMode; pSourceGfx = pGfx; if (szAction) SCopy(szAction, Action, C4MaxName); else *Action=0; dwBlitMode = dwBMode; OverlayObj = pOvrlObj; // (keep transform) // reset phase iPhase = 0; // update used facet UpdateFacet(); } bool C4GraphicsOverlay::IsValid(const C4Object *pForObj) const { assert(pForObj); if (eMode == MODE_Object || eMode == MODE_Rank || eMode == MODE_ObjectPicture) { if (!OverlayObj || !OverlayObj->Status) return false; return (eMode == MODE_Rank) || !OverlayObj->HasGraphicsOverlayRecursion(pForObj); } else if (eMode == MODE_ExtraGraphics) { return !!pSourceGfx; } else if (pSourceGfx) { if(eMode == MODE_Picture || eMode == MODE_IngamePicture) return true; else if (pSourceGfx->Type == C4DefGraphics::TYPE_Bitmap) return !!fctBlit.Surface; else if (pSourceGfx->Type == C4DefGraphics::TYPE_Mesh) return !!pMeshInstance; return false; } else { return false; } } void C4GraphicsOverlay::CompileFunc(StdCompiler *pComp) { // read ID pComp->Value(iID); pComp->Separator(); // read def-graphics pComp->Value(mkDefaultAdapt(C4DefGraphicsAdapt(pSourceGfx), (C4DefGraphics *)NULL)); pComp->Separator(); // read mode pComp->Value(mkIntAdapt(eMode)); pComp->Separator(); // read action (identifier) pComp->Value(mkStringAdaptMIE(Action)); pComp->Separator(); // read blit mode pComp->Value(dwBlitMode); pComp->Separator(); // read phase pComp->Value(iPhase); pComp->Separator(); // read transform pComp->Separator(StdCompiler::SEP_START); pComp->Value(Transform); pComp->Separator(StdCompiler::SEP_END); // read color-modulation if (pComp->Separator()) pComp->Value(mkIntAdapt(dwClrModulation)); else // default if (pComp->isCompiler()) dwClrModulation = 0xffffff; // read overlay target object if (pComp->Separator()) pComp->Value(OverlayObj); else // default if (pComp->isCompiler()) OverlayObj = NULL; // update used facet according to read data if (pComp->isCompiler()) UpdateFacet(); } void C4GraphicsOverlay::DenumeratePointers() { OverlayObj.DenumeratePointers(); } void C4GraphicsOverlay::Draw(C4TargetFacet &cgo, C4Object *pForObj, int32_t iByPlayer) { assert(!IsPicture()); // note: Also called with pForObj==NULL for editor placement preview // get target pos float offX, offY; float newzoom; if (pForObj) { pForObj->GetDrawPosition(cgo, offX, offY, newzoom); } else { // offset in editor mode preview offX = cgo.X; offY = cgo.Y; newzoom = cgo.Zoom; } ZoomDataStackItem zdsi(newzoom); // special blit mode if (dwBlitMode == C4GFXBLIT_PARENT) { assert(pForObj); (OverlayObj ? static_cast(OverlayObj) : pForObj)->PrepareDrawing(); } else { pDraw->SetBlitMode(dwBlitMode); if (dwClrModulation != 0xffffff) pDraw->ActivateBlitModulation(dwClrModulation); if (pMeshInstance) pMeshInstance->SetFaceOrderingForClrModulation(dwClrModulation); } if (eMode == MODE_Rank) { assert(pForObj); C4TargetFacet ccgo; ccgo.Set(cgo.Surface, offX+pForObj->Shape.x,offY+pForObj->Shape.y,pForObj->Shape.Wdt,pForObj->Shape.Hgt, cgo.TargetX, cgo.TargetY); DrawRankSymbol(ccgo, OverlayObj); } // drawing specific object? else if (OverlayObj) { assert(pForObj); // TODO: Shouldn't have called PrepareDrawing/set ClrModulation here, since // OverlayObj drawing will do it on its own. if (eMode == MODE_ObjectPicture) { C4Facet fctTarget; fctTarget.Set(cgo.Surface, offX+pForObj->Shape.x, offY+pForObj->Shape.y, pForObj->Shape.Wdt, pForObj->Shape.Hgt); OverlayObj->DrawPicture(fctTarget, false, &C4DrawTransform(Transform, fctTarget.X+float(fctTarget.Wdt)/2, fctTarget.Y+float(fctTarget.Hgt)/2)); } else { // Draw specified object at target pos of this object; offset by transform. OverlayObj->Draw(cgo, iByPlayer, C4Object::ODM_Overlay, offX + Transform.GetXOffset(), offY + Transform.GetYOffset()); OverlayObj->DrawTopFace(cgo, iByPlayer, C4Object::ODM_Overlay, offX + Transform.GetXOffset(), offY + Transform.GetYOffset()); } } else if (eMode == MODE_ExtraGraphics) { assert(pForObj); // draw self with specified gfx if (pSourceGfx) { C4DefGraphics *pPrevGfx = pForObj->GetGraphics(); C4DrawTransform *pPrevTrf = pForObj->pDrawTransform; C4DrawTransform trf; if (pPrevTrf) { trf = *pPrevTrf; trf *= Transform; } else { trf = Transform; } pForObj->SetGraphics(pSourceGfx, true); pForObj->pDrawTransform = &trf; pForObj->Draw(cgo, iByPlayer, C4Object::ODM_BaseOnly); pForObj->DrawTopFace(cgo, iByPlayer, C4Object::ODM_BaseOnly); pForObj->SetGraphics(pPrevGfx, true); pForObj->pDrawTransform = pPrevTrf; } } else if(eMode == MODE_Picture || eMode == MODE_IngamePicture) { assert(pForObj); float twdt, thgt; if (fZoomToShape) { twdt = pForObj->Shape.Wdt; thgt = pForObj->Shape.Hgt; } else { twdt = pSourceGfx->pDef->Shape.Wdt; thgt = pSourceGfx->pDef->Shape.Hgt; } C4TargetFacet ccgo; ccgo.Set(cgo.Surface, offX-twdt/2, offY-thgt/2, twdt, thgt, cgo.TargetX, cgo.TargetY); C4DrawTransform trf(Transform, offX, offY); // Don't set pForObj because we don't draw the picture of pForObj, but the picture of another definition on top of pForObj: pSourceGfx->Draw(ccgo, pForObj->Color, NULL, iPhase, 0, &trf); } else { // no object specified: Draw from fctBlit // update by object color if (fctBlit.Surface && pForObj) fctBlit.Surface->SetClr(pForObj->Color); if (!pMeshInstance) { // draw there C4DrawTransform trf(Transform, offX, offY); if (fZoomToShape) { assert(pForObj); float fZoom = std::min(pForObj->Shape.Wdt / std::max(fctBlit.Wdt, 1.0f), pForObj->Shape.Hgt / std::max(fctBlit.Hgt, 1.0f)); trf.ScaleAt(fZoom, fZoom, offX, offY); } fctBlit.DrawT(cgo.Surface, offX - fctBlit.Wdt/2 + fctBlit.TargetX, offY - fctBlit.Hgt/2 + fctBlit.TargetY, iPhase, 0, &trf); } else { C4Def *pDef = pSourceGfx->pDef; // draw there C4DrawTransform trf(Transform, offX, offY); if (fZoomToShape) { assert(pForObj); float fZoom = std::min((float)pForObj->Shape.Wdt / std::max(pDef->Shape.Wdt, 1), (float)pForObj->Shape.Hgt / std::max(pDef->Shape.Hgt, 1)); trf.ScaleAt(fZoom, fZoom, offX, offY); } C4Value value; pDef->GetProperty(P_MeshTransformation, &value); StdMeshMatrix matrix; if (C4ValueToMatrix(value, &matrix)) pDraw->SetMeshTransform(&matrix); pDraw->RenderMesh(*pMeshInstance, cgo.Surface, offX - pDef->Shape.Wdt/2.0, offY - pDef->Shape.Hgt/2.0, pDef->Shape.Wdt, pDef->Shape.Hgt, pForObj ? pForObj->Color : 0xff, &trf); pDraw->SetMeshTransform(NULL); } } // cleanup if (dwBlitMode == C4GFXBLIT_PARENT) (OverlayObj ? static_cast(OverlayObj) : pForObj)->FinishedDrawing(); else { pDraw->ResetBlitMode(); pDraw->DeactivateBlitModulation(); } } void C4GraphicsOverlay::DrawRankSymbol(C4Facet &cgo, C4Object *rank_obj) { // Determine source gfx for rank if (!rank_obj || !rank_obj->Info) return; C4RankSystem *pRankSys = &::DefaultRanks; C4Facet *pRankRes=&::GraphicsResource.fctRank; int iRankCnt=::GraphicsResource.iNumRanks; C4Def *rank_def=rank_obj->Def; if (rank_def->pRankSymbols) { pRankRes=rank_def->pRankSymbols; iRankCnt=rank_def->iNumRankSymbols; } if (rank_def->pRankNames) { pRankSys = rank_def->pRankNames; iRankCnt = rank_def->pRankNames->GetBaseRankNum(); } pRankSys->DrawRankSymbol(NULL, rank_obj->Info->Rank, pRankRes, iRankCnt, false, 0, &cgo); } void C4GraphicsOverlay::DrawPicture(C4Facet &cgo, C4Object *pForObj, C4DrawTransform* trans) { assert(IsPicture()); // special blit mode if (dwBlitMode == C4GFXBLIT_PARENT) { if (pForObj) pForObj->PrepareDrawing(); } else { pDraw->SetBlitMode(dwBlitMode); if (dwClrModulation != 0xffffff) pDraw->ActivateBlitModulation(dwClrModulation); if (pMeshInstance) pMeshInstance->SetFaceOrderingForClrModulation(dwClrModulation); } // the picture we are rendering is the one with trans applied, and the overlay transformation // is applied to the picture we are rendering, so apply it afterwards. Note that // C4BltTransform::operator*= does this = other * this. C4DrawTransform trf(Transform, cgo.X + cgo.Wdt/2.0f, cgo.Y + cgo.Hgt/2.0f); if(trans) trf *= *trans; // Don't set pForObj because we don't draw the picture of pForObj, but the picture of another definition on top of pForObj: pSourceGfx->Draw(cgo, pForObj->Color, NULL, iPhase, 0, &trf); // cleanup if (dwBlitMode == C4GFXBLIT_PARENT) { if (pForObj) pForObj->FinishedDrawing(); } else { pDraw->ResetBlitMode(); pDraw->DeactivateBlitModulation(); } } bool C4GraphicsOverlay::operator == (const C4GraphicsOverlay &rCmp) const { // compare relevant fields // ignoring phase, because different animation state may be concatenated in graphics display return (eMode == rCmp.eMode) && (pSourceGfx == rCmp.pSourceGfx) && SEqual(Action, rCmp.Action) && (dwBlitMode == rCmp.dwBlitMode) && (dwClrModulation == rCmp.dwClrModulation) && (Transform == rCmp.Transform) && (OverlayObj == rCmp.OverlayObj); } void C4GraphicsOverlayListAdapt::CompileFunc(StdCompiler *pComp) { bool fNaming = pComp->hasNaming(); if (pComp->isCompiler()) { // clear list delete [] pOverlay; pOverlay = NULL; // read the whole list C4GraphicsOverlay *pLast = NULL; bool fContinue; do { C4GraphicsOverlay *pNext = new C4GraphicsOverlay(); try { // read an overlay pComp->Value(*pNext); } catch (StdCompiler::NotFoundException *e) { delete e; // delete unused overlay delete pNext; pNext = NULL; // clear up if (!pLast) pOverlay = NULL; // done return; } // link it if (pLast) pLast->SetNext(pNext); else pOverlay = pNext; // step pLast = pNext; // continue? if (fNaming) fContinue = pComp->Separator(StdCompiler::SEP_SEP2) || pComp->Separator(StdCompiler::SEP_SEP); else pComp->Value(fContinue); } while (fContinue); } else { // write everything bool fContinue = true; for (C4GraphicsOverlay *pPos = pOverlay; pPos; pPos = pPos->GetNext()) { // separate if (pPos != pOverlay) { if (fNaming) pComp->Separator(StdCompiler::SEP_SEP2); else pComp->Value(fContinue); } // write pComp->Value(*pPos); } // terminate if (!fNaming) { fContinue = false; pComp->Value(fContinue); } } } C4Surface *C4DefGraphics::GetBitmap(DWORD dwClr) { if (Type != TYPE_Bitmap) return NULL; if (Bmp.BitmapClr) { Bmp.BitmapClr->SetClr(dwClr); return Bmp.BitmapClr; } else return Bmp.Bitmap; }