2019-05-15 14:52:37 +00:00
|
|
|
#include "common.h"
|
2020-04-17 13:31:11 +00:00
|
|
|
|
2020-04-23 20:25:18 +00:00
|
|
|
#include "RwHelper.h"
|
2019-10-29 23:12:58 +00:00
|
|
|
#include "General.h"
|
2020-04-23 20:25:18 +00:00
|
|
|
#include "Bones.h"
|
|
|
|
#include "SurfaceTable.h"
|
2019-07-18 02:26:46 +00:00
|
|
|
#include "Ped.h"
|
2019-05-15 14:52:37 +00:00
|
|
|
#include "NodeName.h"
|
|
|
|
#include "VisibilityPlugins.h"
|
|
|
|
#include "ModelInfo.h"
|
|
|
|
|
|
|
|
void
|
|
|
|
CPedModelInfo::DeleteRwObject(void)
|
|
|
|
{
|
|
|
|
if(m_hitColModel)
|
|
|
|
delete m_hitColModel;
|
|
|
|
m_hitColModel = nil;
|
2020-04-23 20:25:18 +00:00
|
|
|
#ifdef PED_SKIN
|
|
|
|
RwFrame *frame;
|
|
|
|
if(m_head){
|
|
|
|
frame = RpAtomicGetFrame(m_head);
|
|
|
|
RpAtomicDestroy(m_head);
|
|
|
|
RwFrameDestroy(frame);
|
|
|
|
}
|
|
|
|
if(m_lhand){
|
|
|
|
frame = RpAtomicGetFrame(m_lhand);
|
|
|
|
RpAtomicDestroy(m_lhand);
|
|
|
|
RwFrameDestroy(frame);
|
|
|
|
}
|
|
|
|
if(m_rhand){
|
|
|
|
frame = RpAtomicGetFrame(m_rhand);
|
|
|
|
RpAtomicDestroy(m_rhand);
|
|
|
|
RwFrameDestroy(frame);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
CClumpModelInfo::DeleteRwObject(); // PC calls this first
|
2019-05-15 14:52:37 +00:00
|
|
|
}
|
|
|
|
|
2020-04-23 20:25:18 +00:00
|
|
|
RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[PED_NODE_MAX] = {
|
2019-07-18 02:26:46 +00:00
|
|
|
{ "Smid", PED_MID, 0, }, // that is strange...
|
2019-05-15 14:52:37 +00:00
|
|
|
{ "Shead", PED_HEAD, 0, },
|
|
|
|
{ "Supperarml", PED_UPPERARML, 0, },
|
|
|
|
{ "Supperarmr", PED_UPPERARMR, 0, },
|
|
|
|
{ "SLhand", PED_HANDL, 0, },
|
|
|
|
{ "SRhand", PED_HANDR, 0, },
|
|
|
|
{ "Supperlegl", PED_UPPERLEGL, 0, },
|
|
|
|
{ "Supperlegr", PED_UPPERLEGR, 0, },
|
|
|
|
{ "Sfootl", PED_FOOTL, 0, },
|
|
|
|
{ "Sfootr", PED_FOOTR, 0, },
|
|
|
|
{ "Slowerlegr", PED_LOWERLEGR, 0, },
|
2019-06-30 10:53:39 +00:00
|
|
|
{ nil, 0, 0, },
|
2019-05-15 14:52:37 +00:00
|
|
|
};
|
|
|
|
|
2020-04-23 20:25:18 +00:00
|
|
|
#ifdef PED_SKIN
|
|
|
|
struct LimbCBarg
|
|
|
|
{
|
|
|
|
CPedModelInfo *mi;
|
|
|
|
RpClump *clump;
|
|
|
|
int32 frameIDs[3];
|
|
|
|
};
|
|
|
|
|
|
|
|
RpAtomic*
|
|
|
|
CPedModelInfo::findLimbsCb(RpAtomic *atomic, void *data)
|
|
|
|
{
|
|
|
|
LimbCBarg *limbs = (LimbCBarg*)data;
|
|
|
|
RwFrame *frame = RpAtomicGetFrame(atomic);
|
|
|
|
const char *name = GetFrameNodeName(frame);
|
|
|
|
if(CGeneral::faststricmp(name, "Shead01") == 0){
|
|
|
|
limbs->frameIDs[0] = RpHAnimFrameGetID(frame);
|
|
|
|
limbs->mi->m_head = atomic;
|
|
|
|
RpClumpRemoveAtomic(limbs->clump, atomic);
|
|
|
|
RwFrameRemoveChild(frame);
|
|
|
|
}else if(CGeneral::faststricmp(name, "SLhand01") == 0){
|
|
|
|
limbs->frameIDs[1] = RpHAnimFrameGetID(frame);
|
|
|
|
limbs->mi->m_lhand = atomic;
|
|
|
|
RpClumpRemoveAtomic(limbs->clump, atomic);
|
|
|
|
RwFrameRemoveChild(frame);
|
|
|
|
}else if(CGeneral::faststricmp(name, "SRhand01") == 0){
|
|
|
|
limbs->frameIDs[2] = RpHAnimFrameGetID(frame);
|
|
|
|
limbs->mi->m_rhand = atomic;
|
|
|
|
RpClumpRemoveAtomic(limbs->clump, atomic);
|
|
|
|
RwFrameRemoveChild(frame);
|
|
|
|
}
|
|
|
|
return atomic;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-05-15 14:52:37 +00:00
|
|
|
void
|
|
|
|
CPedModelInfo::SetClump(RpClump *clump)
|
|
|
|
{
|
2020-04-23 20:25:18 +00:00
|
|
|
#ifdef PED_SKIN
|
|
|
|
|
|
|
|
// CB has to be set here before atomics are detached from clump
|
|
|
|
if(strncmp(GetName(), "player", 7) == 0)
|
|
|
|
RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
|
|
|
|
if(IsClumpSkinned(clump)){
|
|
|
|
LimbCBarg limbs = { this, clump, { 0, 0, 0 } };
|
|
|
|
RpClumpForAllAtomics(clump, findLimbsCb, &limbs);
|
|
|
|
}
|
|
|
|
CClumpModelInfo::SetClump(clump);
|
|
|
|
SetFrameIds(m_pPedIds);
|
|
|
|
if(m_hitColModel == nil && !IsClumpSkinned(clump))
|
|
|
|
CreateHitColModel();
|
|
|
|
// And again because CClumpModelInfo resets it
|
|
|
|
if(strncmp(GetName(), "player", 7) == 0)
|
|
|
|
RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
|
|
|
|
else if(IsClumpSkinned(clump))
|
|
|
|
// skinned peds have no low detail version, so they don't have the right render Cb
|
|
|
|
RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedCB);
|
|
|
|
#else
|
2019-05-15 14:52:37 +00:00
|
|
|
CClumpModelInfo::SetClump(clump);
|
|
|
|
SetFrameIds(m_pPedIds);
|
|
|
|
if(m_hitColModel == nil)
|
|
|
|
CreateHitColModel();
|
|
|
|
if(strncmp(GetName(), "player", 7) == 0)
|
2019-06-30 10:53:39 +00:00
|
|
|
RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
|
2020-04-23 20:25:18 +00:00
|
|
|
#endif
|
2019-05-15 14:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RpAtomic*
|
|
|
|
CountAtomicsCB(RpAtomic *atomic, void *data)
|
|
|
|
{
|
|
|
|
(*(int32*)data)++;
|
|
|
|
return atomic;
|
|
|
|
}
|
|
|
|
|
|
|
|
RpAtomic*
|
|
|
|
GetAtomicListCB(RpAtomic *atomic, void *data)
|
|
|
|
{
|
|
|
|
**(RpAtomic***)data = atomic;
|
|
|
|
(*(RpAtomic***)data)++;
|
|
|
|
return atomic;
|
|
|
|
}
|
|
|
|
|
|
|
|
RwFrame*
|
|
|
|
FindPedFrameFromNameCB(RwFrame *frame, void *data)
|
|
|
|
{
|
|
|
|
RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data;
|
|
|
|
|
2019-10-29 23:12:58 +00:00
|
|
|
if(CGeneral::faststricmp(GetFrameNodeName(frame)+1, assoc->name+1)){
|
2019-05-15 14:52:37 +00:00
|
|
|
RwFrameForAllChildren(frame, FindPedFrameFromNameCB, assoc);
|
|
|
|
return assoc->frame ? nil : frame;
|
|
|
|
}else{
|
|
|
|
assoc->frame = frame;
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
CPedModelInfo::SetLowDetailClump(RpClump *lodclump)
|
|
|
|
{
|
|
|
|
RpAtomic *atomics[16];
|
|
|
|
RpAtomic **pAtm;
|
|
|
|
int32 numAtm, numLodAtm;
|
|
|
|
int i;
|
|
|
|
RwObjectNameAssociation assoc;
|
|
|
|
|
|
|
|
numAtm = 0;
|
|
|
|
numLodAtm = 0;
|
|
|
|
RpClumpForAllAtomics(m_clump, CountAtomicsCB, &numAtm); // actually unused
|
|
|
|
RpClumpForAllAtomics(lodclump, CountAtomicsCB, &numLodAtm);
|
|
|
|
|
2019-06-30 10:53:39 +00:00
|
|
|
RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedHiDetailCB);
|
|
|
|
RpClumpForAllAtomics(lodclump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedLowDetailCB);
|
2019-05-15 14:52:37 +00:00
|
|
|
|
|
|
|
pAtm = atomics;
|
|
|
|
RpClumpForAllAtomics(lodclump, GetAtomicListCB, &pAtm);
|
|
|
|
|
|
|
|
for(i = 0; i < numLodAtm; i++){
|
|
|
|
assoc.name = GetFrameNodeName(RpAtomicGetFrame(atomics[i]));
|
|
|
|
assoc.frame = nil;
|
|
|
|
RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindPedFrameFromNameCB, &assoc);
|
|
|
|
if(assoc.frame){
|
|
|
|
RpAtomicSetFrame(atomics[i], assoc.frame);
|
|
|
|
RpClumpRemoveAtomic(lodclump, atomics[i]);
|
|
|
|
RpClumpAddAtomic(m_clump, atomics[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ColNodeInfo
|
|
|
|
{
|
|
|
|
char *name;
|
|
|
|
int pedNode;
|
|
|
|
int pieceType;
|
|
|
|
float x, z;
|
|
|
|
float radius;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define NUMPEDINFONODES 8
|
|
|
|
ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = {
|
2019-07-18 02:41:57 +00:00
|
|
|
{ nil, PED_HEAD, PEDPIECE_HEAD, 0.0f, 0.05f, 0.2f },
|
|
|
|
{ "Storso", 0, PEDPIECE_TORSO, 0.0f, 0.15f, 0.2f },
|
|
|
|
{ "Storso", 0, PEDPIECE_TORSO, 0.0f, -0.05f, 0.3f },
|
|
|
|
{ nil, PED_MID, PEDPIECE_MID, 0.0f, -0.07f, 0.3f },
|
|
|
|
{ nil, PED_UPPERARML, PEDPIECE_LEFTARM, 0.07f, -0.1f, 0.2f },
|
|
|
|
{ nil, PED_UPPERARMR, PEDPIECE_RIGHTARM, -0.07f, -0.1f, 0.2f },
|
|
|
|
{ "Slowerlegl", 0, PEDPIECE_LEFTLEG, 0.0f, 0.07f, 0.25f },
|
|
|
|
{ nil, PED_LOWERLEGR, PEDPIECE_RIGHTLEG, 0.0f, 0.07f, 0.25f },
|
2019-05-15 14:52:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
RwObject*
|
|
|
|
FindHeadRadiusCB(RwObject *object, void *data)
|
|
|
|
{
|
|
|
|
RpAtomic *atomic = (RpAtomic*)object;
|
|
|
|
*(float*)data = RpAtomicGetBoundingSphere(atomic)->radius;
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
CPedModelInfo::CreateHitColModel(void)
|
|
|
|
{
|
|
|
|
RwObjectNameAssociation nameAssoc;
|
|
|
|
RwObjectIdAssociation idAssoc;
|
|
|
|
CVector center;
|
|
|
|
RwFrame *nodeFrame;
|
|
|
|
CColModel *colmodel = new CColModel;
|
|
|
|
CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere));
|
|
|
|
RwFrame *root = RpClumpGetFrame(m_clump);
|
|
|
|
RwMatrix *mat = RwMatrixCreate();
|
|
|
|
for(int i = 0; i < NUMPEDINFONODES; i++){
|
|
|
|
nodeFrame = nil;
|
|
|
|
if(m_pColNodeInfos[i].name){
|
|
|
|
nameAssoc.name = m_pColNodeInfos[i].name;
|
|
|
|
nameAssoc.frame = nil;
|
|
|
|
RwFrameForAllChildren(root, FindFrameFromNameCB, &nameAssoc);
|
|
|
|
nodeFrame = nameAssoc.frame;
|
|
|
|
}else{
|
|
|
|
idAssoc.id = m_pColNodeInfos[i].pedNode;
|
|
|
|
idAssoc.frame = nil;
|
|
|
|
RwFrameForAllChildren(root, FindFrameFromIdCB, &idAssoc);
|
|
|
|
nodeFrame = idAssoc.frame;
|
|
|
|
}
|
|
|
|
if(nodeFrame){
|
|
|
|
float radius = m_pColNodeInfos[i].radius;
|
2020-04-23 20:25:18 +00:00
|
|
|
if(m_pColNodeInfos[i].pieceType == PEDPIECE_HEAD)
|
2019-05-15 14:52:37 +00:00
|
|
|
RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius);
|
|
|
|
RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE);
|
|
|
|
const char *name = GetFrameNodeName(nodeFrame);
|
|
|
|
for(nodeFrame = RwFrameGetParent(nodeFrame);
|
|
|
|
nodeFrame;
|
|
|
|
nodeFrame = RwFrameGetParent(nodeFrame)){
|
|
|
|
name = GetFrameNodeName(nodeFrame);
|
|
|
|
RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEPOSTCONCAT);
|
|
|
|
if(RwFrameGetParent(nodeFrame) == root)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
center.x = mat->pos.x + m_pColNodeInfos[i].x;
|
|
|
|
center.y = mat->pos.y + 0.0f;
|
|
|
|
center.z = mat->pos.z + m_pColNodeInfos[i].z;
|
2020-04-23 20:25:18 +00:00
|
|
|
spheres[i].Set(radius, center, SURFACE_FLESH, m_pColNodeInfos[i].pieceType);
|
2019-05-15 14:52:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
RwMatrixDestroy(mat);
|
|
|
|
colmodel->spheres = spheres;
|
|
|
|
colmodel->numSpheres = NUMPEDINFONODES;
|
|
|
|
center.x = center.y = center.z = 0.0f;
|
|
|
|
colmodel->boundingSphere.Set(2.0f, center, 0, 0);
|
|
|
|
CVector min, max;
|
|
|
|
min.x = min.y = -0.5f;
|
|
|
|
min.z = -1.2f;
|
|
|
|
max.x = max.y = 0.5f;
|
|
|
|
max.z = 1.2f;
|
|
|
|
colmodel->boundingBox.Set(min, max, 0, 0);
|
2020-04-23 20:25:18 +00:00
|
|
|
colmodel->level = LEVEL_NONE;
|
2019-05-15 14:52:37 +00:00
|
|
|
m_hitColModel = colmodel;
|
|
|
|
}
|
|
|
|
|
2019-08-17 19:03:57 +00:00
|
|
|
CColModel*
|
|
|
|
CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame)
|
|
|
|
{
|
|
|
|
RwObjectNameAssociation nameAssoc;
|
|
|
|
RwObjectIdAssociation idAssoc;
|
|
|
|
RwMatrix* mat = RwMatrixCreate();
|
|
|
|
CColSphere* spheres = colmodel->spheres;
|
|
|
|
|
|
|
|
for (int i = 0; i < NUMPEDINFONODES; i++) {
|
|
|
|
RwFrame* f = nil;
|
|
|
|
if (m_pColNodeInfos[i].name) {
|
|
|
|
nameAssoc.name = m_pColNodeInfos[i].name;
|
|
|
|
nameAssoc.frame = nil;
|
|
|
|
RwFrameForAllChildren(frame, FindFrameFromNameCB, &nameAssoc);
|
|
|
|
f = nameAssoc.frame;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
idAssoc.id = m_pColNodeInfos[i].pedNode;
|
|
|
|
idAssoc.frame = nil;
|
|
|
|
RwFrameForAllChildren(frame, FindFrameFromIdCB, &idAssoc);
|
|
|
|
f = idAssoc.frame;
|
|
|
|
}
|
|
|
|
if (f) {
|
|
|
|
RwMatrixCopy(mat, RwFrameGetMatrix(f));
|
|
|
|
|
|
|
|
for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) {
|
2020-04-09 14:35:24 +00:00
|
|
|
RwMatrixTransform(mat, RwFrameGetMatrix(f), rwCOMBINEPOSTCONCAT);
|
2019-08-17 19:03:57 +00:00
|
|
|
if (RwFrameGetParent(f) == frame)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
spheres[i].center.x = mat->pos.x + m_pColNodeInfos[i].x;
|
|
|
|
spheres[i].center.y = mat->pos.y + 0.0f;
|
|
|
|
spheres[i].center.z = mat->pos.z + m_pColNodeInfos[i].z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return colmodel;
|
|
|
|
}
|
2020-04-23 20:25:18 +00:00
|
|
|
|
|
|
|
#ifdef PED_SKIN
|
|
|
|
void
|
|
|
|
CPedModelInfo::CreateHitColModelSkinned(RpClump *clump)
|
|
|
|
{
|
|
|
|
CVector center;
|
|
|
|
RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
|
|
|
|
CColModel *colmodel = new CColModel;
|
|
|
|
CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere));
|
|
|
|
RwFrame *root = RpClumpGetFrame(m_clump);
|
|
|
|
RwMatrix *invmat = RwMatrixCreate();
|
|
|
|
RwMatrix *mat = RwMatrixCreate();
|
|
|
|
RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump)));
|
|
|
|
|
|
|
|
for(int i = 0; i < NUMPEDINFONODES; i++){
|
|
|
|
*mat = *invmat;
|
|
|
|
int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); // this is wrong, wtf R* ???
|
|
|
|
int idx = RpHAnimIDGetIndex(hier, id);
|
|
|
|
|
|
|
|
// This doesn't really work as the positions are not initialized yet
|
|
|
|
RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT);
|
|
|
|
RwV3d pos = { 0.0f, 0.0f, 0.0f };
|
|
|
|
RwV3dTransformPoints(&pos, &pos, 1, mat);
|
|
|
|
|
|
|
|
center.x = pos.x + m_pColNodeInfos[i].x;
|
|
|
|
center.y = pos.y + 0.0f;
|
|
|
|
center.z = pos.z + m_pColNodeInfos[i].z;
|
|
|
|
spheres[i].Set(m_pColNodeInfos[i].radius, center, SURFACE_FLESH, m_pColNodeInfos[i].pieceType);
|
|
|
|
}
|
|
|
|
RwMatrixDestroy(invmat);
|
|
|
|
RwMatrixDestroy(mat);
|
|
|
|
colmodel->spheres = spheres;
|
|
|
|
colmodel->numSpheres = NUMPEDINFONODES;
|
|
|
|
center.x = center.y = center.z = 0.0f;
|
|
|
|
colmodel->boundingSphere.Set(2.0f, center, 0, 0);
|
|
|
|
CVector min, max;
|
|
|
|
min.x = min.y = -0.5f;
|
|
|
|
min.z = -1.2f;
|
|
|
|
max.x = max.y = 0.5f;
|
|
|
|
max.z = 1.2f;
|
|
|
|
colmodel->boundingBox.Set(min, max, 0, 0);
|
|
|
|
colmodel->level = LEVEL_NONE;
|
|
|
|
m_hitColModel = colmodel;
|
|
|
|
}
|
|
|
|
|
|
|
|
CColModel*
|
|
|
|
CPedModelInfo::AnimatePedColModelSkinned(RpClump *clump)
|
|
|
|
{
|
|
|
|
if(m_hitColModel == nil){
|
|
|
|
CreateHitColModelSkinned(clump);
|
|
|
|
return m_hitColModel;
|
|
|
|
}
|
|
|
|
RwMatrix *invmat, *mat;
|
|
|
|
CColSphere *spheres = m_hitColModel->spheres;
|
|
|
|
RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
|
|
|
|
invmat = RwMatrixCreate();
|
|
|
|
mat = RwMatrixCreate();
|
|
|
|
RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump)));
|
|
|
|
|
|
|
|
for(int i = 0; i < NUMPEDINFONODES; i++){
|
|
|
|
*mat = *invmat;
|
|
|
|
int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode);
|
|
|
|
int idx = RpHAnimIDGetIndex(hier, id);
|
|
|
|
|
|
|
|
RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT);
|
|
|
|
RwV3d pos = { 0.0f, 0.0f, 0.0f };
|
|
|
|
RwV3dTransformPoints(&pos, &pos, 1, mat);
|
|
|
|
|
|
|
|
spheres[i].center.x = pos.x + m_pColNodeInfos[i].x;
|
|
|
|
spheres[i].center.y = pos.y + 0.0f;
|
|
|
|
spheres[i].center.z = pos.z + m_pColNodeInfos[i].z;
|
|
|
|
}
|
|
|
|
RwMatrixDestroy(invmat);
|
|
|
|
RwMatrixDestroy(mat);
|
|
|
|
return m_hitColModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|