mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2025-02-26 21:20:57 +00:00
- separated models.cpp as well into engine and game parts.
This commit is contained in:
parent
46d263b5a8
commit
d6dca40cb7
5 changed files with 245 additions and 196 deletions
|
@ -783,7 +783,6 @@ set( FASTMATH_SOURCES
|
||||||
rendering/hwrenderer/scene/hw_walls.cpp
|
rendering/hwrenderer/scene/hw_walls.cpp
|
||||||
rendering/hwrenderer/scene/hw_walls_vertex.cpp
|
rendering/hwrenderer/scene/hw_walls_vertex.cpp
|
||||||
rendering/hwrenderer/scene/hw_weapon.cpp
|
rendering/hwrenderer/scene/hw_weapon.cpp
|
||||||
r_data/models/models.cpp
|
|
||||||
common/utility/matrix.cpp
|
common/utility/matrix.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1007,6 +1006,8 @@ set (PCH_SOURCES
|
||||||
r_data/models/models_voxel.cpp
|
r_data/models/models_voxel.cpp
|
||||||
r_data/models/models_ue1.cpp
|
r_data/models/models_ue1.cpp
|
||||||
r_data/models/models_obj.cpp
|
r_data/models/models_obj.cpp
|
||||||
|
r_data/models/models.cpp
|
||||||
|
r_data/models/model.cpp
|
||||||
scripting/vmiterators.cpp
|
scripting/vmiterators.cpp
|
||||||
scripting/vmthunks.cpp
|
scripting/vmthunks.cpp
|
||||||
scripting/vmthunks_actors.cpp
|
scripting/vmthunks_actors.cpp
|
||||||
|
|
236
src/r_data/models/model.cpp
Normal file
236
src/r_data/models/model.cpp
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2005-2016 Christoph Oelckers
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
** gl_models.cpp
|
||||||
|
**
|
||||||
|
** General model handling code
|
||||||
|
**
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "sc_man.h"
|
||||||
|
#include "m_crc32.h"
|
||||||
|
#include "printf.h"
|
||||||
|
#include "r_data/models/models.h"
|
||||||
|
#include "r_data/models/model_ue1.h"
|
||||||
|
#include "r_data/models/model_obj.h"
|
||||||
|
#include "r_data/models/model_md2.h"
|
||||||
|
#include "r_data/models/model_md3.h"
|
||||||
|
#include "r_data/models/model_kvx.h"
|
||||||
|
#include "i_time.h"
|
||||||
|
#include "texturemanager.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
|
||||||
|
|
||||||
|
TDeletingArray<FModel*> Models;
|
||||||
|
TArray<FSpriteModelFrame> SpriteModelFrames;
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void FlushModels()
|
||||||
|
{
|
||||||
|
for (int i = Models.Size() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
Models[i]->DestroyVertexBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
FModel::FModel()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NumModelRendererTypes; i++)
|
||||||
|
mVBuf[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FModel::~FModel()
|
||||||
|
{
|
||||||
|
DestroyVertexBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FModel::DestroyVertexBuffer()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NumModelRendererTypes; i++)
|
||||||
|
{
|
||||||
|
delete mVBuf[i];
|
||||||
|
mVBuf[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FindGFXFile
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
static int FindGFXFile(FString & fn)
|
||||||
|
{
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fn); // if we find something that matches the name plus the extension, return it and do not enter the substitution logic below.
|
||||||
|
if (lump != -1) return lump;
|
||||||
|
|
||||||
|
int best = -1;
|
||||||
|
int dot = fn.LastIndexOf('.');
|
||||||
|
int slash = fn.LastIndexOf('/');
|
||||||
|
if (dot > slash) fn.Truncate(dot);
|
||||||
|
|
||||||
|
static const char * extensions[] = { ".png", ".jpg", ".tga", ".pcx", nullptr };
|
||||||
|
|
||||||
|
for (const char ** extp=extensions; *extp; extp++)
|
||||||
|
{
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fn + *extp);
|
||||||
|
if (lump >= best) best = lump;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// LoadSkin
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
FTextureID LoadSkin(const char * path, const char * fn)
|
||||||
|
{
|
||||||
|
FString buffer;
|
||||||
|
|
||||||
|
buffer.Format("%s%s", path, fn);
|
||||||
|
|
||||||
|
int texlump = FindGFXFile(buffer);
|
||||||
|
const char * const texname = texlump < 0 ? fn : fileSystem.GetFileFullName(texlump);
|
||||||
|
return TexMan.CheckForTexture(texname, ETextureType::Any, FTextureManager::TEXMAN_TryAny);
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// ModelFrameHash
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
int ModelFrameHash(FSpriteModelFrame * smf)
|
||||||
|
{
|
||||||
|
const uint32_t *table = GetCRCTable ();
|
||||||
|
uint32_t hash = 0xffffffff;
|
||||||
|
|
||||||
|
const char * s = (const char *)(&smf->type); // this uses type, sprite and frame for hashing
|
||||||
|
const char * se= (const char *)(&smf->hashnext);
|
||||||
|
|
||||||
|
for (; s<se; s++)
|
||||||
|
{
|
||||||
|
hash = CRC1 (hash, *s, table);
|
||||||
|
}
|
||||||
|
return hash ^ 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FindModel
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
unsigned FindModel(const char * path, const char * modelfile)
|
||||||
|
{
|
||||||
|
FModel * model = nullptr;
|
||||||
|
FString fullname;
|
||||||
|
|
||||||
|
fullname.Format("%s%s", path, modelfile);
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fullname);
|
||||||
|
|
||||||
|
if (lump<0)
|
||||||
|
{
|
||||||
|
Printf("FindModel: '%s' not found\n", fullname.GetChars());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(unsigned i = 0; i< Models.Size(); i++)
|
||||||
|
{
|
||||||
|
if (!Models[i]->mFileName.CompareNoCase(fullname)) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = fileSystem.FileLength(lump);
|
||||||
|
FileData lumpd = fileSystem.ReadFile(lump);
|
||||||
|
char * buffer = (char*)lumpd.GetMem();
|
||||||
|
|
||||||
|
if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 )
|
||||||
|
{
|
||||||
|
FString anivfile = fullname.GetChars();
|
||||||
|
anivfile.Substitute("_d.3d","_a.3d");
|
||||||
|
if ( fileSystem.CheckNumForFullName(anivfile) > 0 )
|
||||||
|
{
|
||||||
|
model = new FUE1Model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 )
|
||||||
|
{
|
||||||
|
FString datafile = fullname.GetChars();
|
||||||
|
datafile.Substitute("_a.3d","_d.3d");
|
||||||
|
if ( fileSystem.CheckNumForFullName(datafile) > 0 )
|
||||||
|
{
|
||||||
|
model = new FUE1Model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 )
|
||||||
|
{
|
||||||
|
model = new FOBJModel;
|
||||||
|
}
|
||||||
|
else if (!memcmp(buffer, "DMDM", 4))
|
||||||
|
{
|
||||||
|
model = new FDMDModel;
|
||||||
|
}
|
||||||
|
else if (!memcmp(buffer, "IDP2", 4))
|
||||||
|
{
|
||||||
|
model = new FMD2Model;
|
||||||
|
}
|
||||||
|
else if (!memcmp(buffer, "IDP3", 4))
|
||||||
|
{
|
||||||
|
model = new FMD3Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model != nullptr)
|
||||||
|
{
|
||||||
|
if (!model->Load(path, lump, buffer, len))
|
||||||
|
{
|
||||||
|
delete model;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// try loading as a voxel
|
||||||
|
FVoxel *voxel = R_LoadKVX(lump);
|
||||||
|
if (voxel != nullptr)
|
||||||
|
{
|
||||||
|
model = new FVoxelModel(voxel, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf("LoadModel: Unknown model format in '%s'\n", fullname.GetChars());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The vertex buffer cannot be initialized here because this gets called before OpenGL is initialized
|
||||||
|
model->mFileName = fullname;
|
||||||
|
return Models.Push(model);
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@ class FModelRenderer;
|
||||||
class FGameTexture;
|
class FGameTexture;
|
||||||
class IModelVertexBuffer;
|
class IModelVertexBuffer;
|
||||||
class FModel;
|
class FModel;
|
||||||
|
struct FSpriteModelFrame;
|
||||||
|
|
||||||
FTextureID LoadSkin(const char* path, const char* fn);
|
FTextureID LoadSkin(const char* path, const char* fn);
|
||||||
void FlushModels();
|
void FlushModels();
|
||||||
extern TDeletingArray<FModel*> Models;
|
extern TDeletingArray<FModel*> Models;
|
||||||
|
extern TArray<FSpriteModelFrame> SpriteModelFrames;
|
||||||
|
|
||||||
#define MAX_MODELS_PER_FRAME 4
|
#define MAX_MODELS_PER_FRAME 4
|
||||||
#define MD3_MAX_SURFACES 32
|
#define MD3_MAX_SURFACES 32
|
||||||
|
@ -75,3 +77,6 @@ private:
|
||||||
IModelVertexBuffer *mVBuf[NumModelRendererTypes];
|
IModelVertexBuffer *mVBuf[NumModelRendererTypes];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int ModelFrameHash(FSpriteModelFrame* smf);
|
||||||
|
unsigned FindModel(const char* path, const char* modelfile);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#define MD2_MAGIC 0x32504449
|
#define MD2_MAGIC 0x32504449
|
||||||
#define DMD_MAGIC 0x4D444D44
|
#define DMD_MAGIC 0x4D444D44
|
||||||
|
#define MAX_LODS 4
|
||||||
|
|
||||||
class FDMDModel : public FModel
|
class FDMDModel : public FModel
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,11 +39,7 @@
|
||||||
#include "g_levellocals.h"
|
#include "g_levellocals.h"
|
||||||
#include "r_utility.h"
|
#include "r_utility.h"
|
||||||
#include "r_data/models/models.h"
|
#include "r_data/models/models.h"
|
||||||
#include "r_data/models/model_ue1.h"
|
#include "model_kvx.h"
|
||||||
#include "r_data/models/model_obj.h"
|
|
||||||
#include "r_data/models/model_md2.h"
|
|
||||||
#include "r_data/models/model_md3.h"
|
|
||||||
#include "r_data/models/model_kvx.h"
|
|
||||||
#include "i_time.h"
|
#include "i_time.h"
|
||||||
#include "texturemanager.h"
|
#include "texturemanager.h"
|
||||||
#include "modelrenderer.h"
|
#include "modelrenderer.h"
|
||||||
|
@ -59,8 +55,6 @@ EXTERN_CVAR (Bool, r_drawvoxels)
|
||||||
extern TDeletingArray<FVoxel *> Voxels;
|
extern TDeletingArray<FVoxel *> Voxels;
|
||||||
extern TDeletingArray<FVoxelDef *> VoxelDefs;
|
extern TDeletingArray<FVoxelDef *> VoxelDefs;
|
||||||
|
|
||||||
TDeletingArray<FModel*> Models;
|
|
||||||
|
|
||||||
void RenderFrameModels(FModelRenderer* renderer, FLevelLocals* Level, const FSpriteModelFrame* smf, const FState* curState, const int curTics, const PClass* ti, int translation);
|
void RenderFrameModels(FModelRenderer* renderer, FLevelLocals* Level, const FSpriteModelFrame* smf, const FState* curState, const int curTics, const PClass* ti, int translation);
|
||||||
|
|
||||||
|
|
||||||
|
@ -290,198 +284,10 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
void FlushModels()
|
|
||||||
{
|
|
||||||
for (int i = Models.Size() - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
Models[i]->DestroyVertexBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
FModel::FModel()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NumModelRendererTypes; i++)
|
|
||||||
mVBuf[i] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
FModel::~FModel()
|
|
||||||
{
|
|
||||||
DestroyVertexBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FModel::DestroyVertexBuffer()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NumModelRendererTypes; i++)
|
|
||||||
{
|
|
||||||
delete mVBuf[i];
|
|
||||||
mVBuf[i] = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static TArray<FSpriteModelFrame> SpriteModelFrames;
|
|
||||||
static TArray<int> SpriteModelHash;
|
static TArray<int> SpriteModelHash;
|
||||||
//TArray<FStateModelFrame> StateModelFrames;
|
//TArray<FStateModelFrame> StateModelFrames;
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
// FindGFXFile
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
static int FindGFXFile(FString & fn)
|
|
||||||
{
|
|
||||||
int lump = fileSystem.CheckNumForFullName(fn); // if we find something that matches the name plus the extension, return it and do not enter the substitution logic below.
|
|
||||||
if (lump != -1) return lump;
|
|
||||||
|
|
||||||
int best = -1;
|
|
||||||
int dot = fn.LastIndexOf('.');
|
|
||||||
int slash = fn.LastIndexOf('/');
|
|
||||||
if (dot > slash) fn.Truncate(dot);
|
|
||||||
|
|
||||||
static const char * extensions[] = { ".png", ".jpg", ".tga", ".pcx", nullptr };
|
|
||||||
|
|
||||||
for (const char ** extp=extensions; *extp; extp++)
|
|
||||||
{
|
|
||||||
int lump = fileSystem.CheckNumForFullName(fn + *extp);
|
|
||||||
if (lump >= best) best = lump;
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
// LoadSkin
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
FTextureID LoadSkin(const char * path, const char * fn)
|
|
||||||
{
|
|
||||||
FString buffer;
|
|
||||||
|
|
||||||
buffer.Format("%s%s", path, fn);
|
|
||||||
|
|
||||||
int texlump = FindGFXFile(buffer);
|
|
||||||
const char * const texname = texlump < 0 ? fn : fileSystem.GetFileFullName(texlump);
|
|
||||||
return TexMan.CheckForTexture(texname, ETextureType::Any, FTextureManager::TEXMAN_TryAny);
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
// ModelFrameHash
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
static int ModelFrameHash(FSpriteModelFrame * smf)
|
|
||||||
{
|
|
||||||
const uint32_t *table = GetCRCTable ();
|
|
||||||
uint32_t hash = 0xffffffff;
|
|
||||||
|
|
||||||
const char * s = (const char *)(&smf->type); // this uses type, sprite and frame for hashing
|
|
||||||
const char * se= (const char *)(&smf->hashnext);
|
|
||||||
|
|
||||||
for (; s<se; s++)
|
|
||||||
{
|
|
||||||
hash = CRC1 (hash, *s, table);
|
|
||||||
}
|
|
||||||
return hash ^ 0xffffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
// FindModel
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
static unsigned FindModel(const char * path, const char * modelfile)
|
|
||||||
{
|
|
||||||
FModel * model = nullptr;
|
|
||||||
FString fullname;
|
|
||||||
|
|
||||||
fullname.Format("%s%s", path, modelfile);
|
|
||||||
int lump = fileSystem.CheckNumForFullName(fullname);
|
|
||||||
|
|
||||||
if (lump<0)
|
|
||||||
{
|
|
||||||
Printf("FindModel: '%s' not found\n", fullname.GetChars());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(unsigned i = 0; i< Models.Size(); i++)
|
|
||||||
{
|
|
||||||
if (!Models[i]->mFileName.CompareNoCase(fullname)) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = fileSystem.FileLength(lump);
|
|
||||||
FileData lumpd = fileSystem.ReadFile(lump);
|
|
||||||
char * buffer = (char*)lumpd.GetMem();
|
|
||||||
|
|
||||||
if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 )
|
|
||||||
{
|
|
||||||
FString anivfile = fullname.GetChars();
|
|
||||||
anivfile.Substitute("_d.3d","_a.3d");
|
|
||||||
if ( fileSystem.CheckNumForFullName(anivfile) > 0 )
|
|
||||||
{
|
|
||||||
model = new FUE1Model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 )
|
|
||||||
{
|
|
||||||
FString datafile = fullname.GetChars();
|
|
||||||
datafile.Substitute("_a.3d","_d.3d");
|
|
||||||
if ( fileSystem.CheckNumForFullName(datafile) > 0 )
|
|
||||||
{
|
|
||||||
model = new FUE1Model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 )
|
|
||||||
{
|
|
||||||
model = new FOBJModel;
|
|
||||||
}
|
|
||||||
else if (!memcmp(buffer, "DMDM", 4))
|
|
||||||
{
|
|
||||||
model = new FDMDModel;
|
|
||||||
}
|
|
||||||
else if (!memcmp(buffer, "IDP2", 4))
|
|
||||||
{
|
|
||||||
model = new FMD2Model;
|
|
||||||
}
|
|
||||||
else if (!memcmp(buffer, "IDP3", 4))
|
|
||||||
{
|
|
||||||
model = new FMD3Model;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model != nullptr)
|
|
||||||
{
|
|
||||||
if (!model->Load(path, lump, buffer, len))
|
|
||||||
{
|
|
||||||
delete model;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// try loading as a voxel
|
|
||||||
FVoxel *voxel = R_LoadKVX(lump);
|
|
||||||
if (voxel != nullptr)
|
|
||||||
{
|
|
||||||
model = new FVoxelModel(voxel, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Printf("LoadModel: Unknown model format in '%s'\n", fullname.GetChars());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The vertex buffer cannot be initialized here because this gets called before OpenGL is initialized
|
|
||||||
model->mFileName = fullname;
|
|
||||||
return Models.Push(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
//
|
//
|
||||||
// InitModels
|
// InitModels
|
||||||
|
|
Loading…
Reference in a new issue