openclonk/src/object/C4DefGraphics.cpp

1202 lines
35 KiB
C++

/*
* 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<StdMeshInstance> 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<StdMeshInstance::AttachedMesh*> attached_meshes;
for(StdMeshInstance::AttachedMeshIter iter = instance->AttachedMeshesBegin(); iter != instance->AttachedMeshesEnd(); ++iter)
attached_meshes.push_back(*iter);
for(std::vector<StdMeshInstance::AttachedMesh*>::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<C4DefGraphicsPtrBackupEntry*>::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<C4DefGraphicsPtrBackupEntry*>::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<C4DefGraphicsPtrBackupEntry*>::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<int32_t>(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<C4Object*>(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<C4Object*>(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;
}