387 lines
11 KiB
C++
387 lines
11 KiB
C++
#include "Model.h"
|
||
|
||
#include <QOpenGLTexture>
|
||
#include <QOpenGLContext>
|
||
#include <QTextCodec>
|
||
#include <iostream>
|
||
#include "PaintingMesh.h"
|
||
#include <QtMath>
|
||
#include "BvhTree.h"
|
||
|
||
Model::Model(QString path, QOpenGLContext* context, QOpenGLShaderProgram* shaderProgram)
|
||
: context(context)
|
||
, glFunc(context->versionFunctions<QOpenGLFunctions_4_5_Compatibility>())
|
||
, shaderProgram(shaderProgram)
|
||
, directory(path)
|
||
{
|
||
|
||
Assimp::Importer importer;
|
||
const aiScene* scene = importer.ReadFile(directory.absolutePath().toUtf8(), aiProcess_Triangulate | aiProcess_FlipUVs);
|
||
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
|
||
{
|
||
qDebug() << "ERROR::ASSIMP::" << importer.GetErrorString() << endl;
|
||
return;
|
||
}
|
||
qDebug() << directory.absolutePath() << "Loaded Successfully";
|
||
qDebug() << "NumMeshes: " << scene->mNumMeshes;
|
||
qDebug() << "NumMaterials: " << scene->mNumMaterials;
|
||
qDebug() << "NumTextures: " << scene->mNumTextures;
|
||
directory.cdUp();
|
||
processNode(scene->mRootNode, scene);
|
||
}
|
||
|
||
Model::Model(QString path, QOpenGLContext* context, QOpenGLShaderProgram* shaderProgram, QOpenGLShaderProgram* paintingProgram, QOpenGLShaderProgram* shadowProgram, PaintingHelper* paintingHelper)
|
||
: context(context)
|
||
, glFunc(context->versionFunctions<QOpenGLFunctions_4_5_Compatibility>())
|
||
, shaderProgram(shaderProgram)
|
||
, paintingProgram(paintingProgram)
|
||
, shadowProgram(shadowProgram)
|
||
, paintingHelper(paintingHelper)
|
||
, directory(path)
|
||
{
|
||
Assimp::Importer importer;
|
||
const aiScene* scene = importer.ReadFile(directory.absolutePath().toUtf8(), aiProcess_Triangulate | aiProcess_FlipUVs);
|
||
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
|
||
{
|
||
qDebug() << "ERROR::ASSIMP::" << importer.GetErrorString() << endl;
|
||
return;
|
||
}
|
||
qDebug() << directory.absolutePath() << "Loaded Successfully";
|
||
qDebug() << "NumMeshes: " << scene->mNumMeshes;
|
||
qDebug() << "NumMaterials: " << scene->mNumMaterials;
|
||
qDebug() << "NumTextures: " << scene->mNumTextures;
|
||
directory.cdUp();
|
||
processNode(scene->mRootNode, scene);
|
||
}
|
||
|
||
Model::~Model() //<2F><><EFBFBD>ٶ<EFBFBD><D9B6><EFBFBD>
|
||
{
|
||
for (auto& it : textures_loaded) {
|
||
it->texture.destroy();
|
||
delete it;
|
||
}
|
||
for (auto& it : meshes) {
|
||
delete it;
|
||
}
|
||
}
|
||
|
||
void Model::draw() {
|
||
//shaderProgram->bind();
|
||
for (Drawable* mesh : meshes) {
|
||
mesh->draw();
|
||
}
|
||
}
|
||
|
||
void Model::drawShadow() {
|
||
//shaderProgram->bind();
|
||
for (Drawable* mesh : meshes) {
|
||
mesh->drawShadow();
|
||
}
|
||
}
|
||
|
||
void Model::destroy()
|
||
{
|
||
context->doneCurrent();
|
||
delete this;
|
||
}
|
||
|
||
Model* Model::createModel(QString path, QOpenGLContext* context, QOpenGLShaderProgram* shaderProgram)
|
||
{
|
||
return new Model(path, context, shaderProgram);
|
||
}
|
||
|
||
void Model::processNode(aiNode* node, const aiScene* scene, aiMatrix4x4 mat4)
|
||
{
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD><DAB5><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>еĻ<D0B5><C4BB><EFBFBD>
|
||
for (unsigned int i = 0; i < node->mNumMeshes; i++)
|
||
{
|
||
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
||
meshes.push_back(processMesh(mesh, scene, mat4));
|
||
|
||
}
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӽڵ<D3BD><DAB5>ظ<EFBFBD><D8B8><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>
|
||
for (unsigned int i = 0; i < node->mNumChildren; i++)
|
||
{
|
||
processNode(node->mChildren[i], scene, mat4 * node->mChildren[i]->mTransformation);
|
||
}
|
||
}
|
||
Drawable* Model::processMesh(aiMesh* mesh, const aiScene* scene, aiMatrix4x4 model)
|
||
{
|
||
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||
|
||
aiString str;
|
||
material->GetTexture(aiTextureType_BASE_COLOR, 0, &str);
|
||
|
||
if (paintingProgram != nullptr && std::strcmp(str.C_Str(), "17876391417123941155.jpg") == 0)
|
||
{
|
||
qDebug() << str.C_Str() << "Replaced";
|
||
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
PaintingMesh* m_mesh = new PaintingMesh(glFunc, paintingProgram, shadowProgram, model);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
|
||
{
|
||
PaintingVertex vertex;
|
||
vertex.Position = QVector3D(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if (mesh->mNormals)
|
||
vertex.Normal = QVector3D(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if (mesh->mTextureCoords[0])
|
||
vertex.TexCoords = QVector2D(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
|
||
|
||
if (mesh->mTangents)
|
||
vertex.Tangent = QVector3D(mesh->mTangents[i].x, mesh->mTangents[i].y, mesh->mTangents[i].z);
|
||
|
||
if (mesh->mBitangents)
|
||
vertex.Bitangent = QVector3D(mesh->mBitangents[i].x, mesh->mBitangents[i].y, mesh->mBitangents[i].z);;
|
||
|
||
m_mesh->vertices.push_back(vertex);
|
||
}
|
||
|
||
for (unsigned int i = 0; i < mesh->mNumFaces; i++)
|
||
{
|
||
aiFace face = mesh->mFaces[i];
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD><D3B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
for (unsigned int j = 0; j < face.mNumIndices; j++) {
|
||
m_mesh->indices.push_back(face.mIndices[j]);
|
||
}
|
||
}
|
||
|
||
BvhTree bvhTree;
|
||
std::vector<QVector4D> initBound;
|
||
for (int i = 0; i < 30000; i++)
|
||
{
|
||
float x = (float)rand() / RAND_MAX * 2 - 1;
|
||
float y = (float)rand() / RAND_MAX * 2 - 1;
|
||
float z = 0.01 + x;//(float)rand() / RAND_MAX * (0.1) + x;
|
||
float w = 0.01 + y;//(float)rand() / RAND_MAX * (0.1) + y;
|
||
initBound.push_back(QVector4D(x, y, z, w));
|
||
}
|
||
/* initBound.push_back(QVector4D(-0.8, -0.8, -0.7, -0.7));
|
||
initBound.push_back(QVector4D(-0.8, 0.7, -0.7, 0.8));
|
||
initBound.push_back(QVector4D(0.7, -0.8, 0.8, -0.7));
|
||
initBound.push_back(QVector4D(0.7, 0.7, 0.8, 0.8));*/
|
||
bvhTree.buildBvhTree(initBound.data(), initBound.size());
|
||
std::vector<GLuint> children;
|
||
std::vector<QVector4D> bounds;
|
||
bvhTree.getBvhArray(children, bounds);
|
||
|
||
std::vector<GLuint> bvhChildren = {
|
||
//root
|
||
1,2,
|
||
3,4, 5,6,
|
||
7,0, 7,GLuint(30. / 360 * 65536 + 1 * 65536) /*<2A>Ҷ<EFBFBD><D2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>ת<EFBFBD>ǶȺ<C7B6>zIndex*/, 8,0, 7,0,
|
||
//elememt0
|
||
1,2,
|
||
5 + 0/*contour<75><72><EFBFBD><EFBFBD>*/,5 + 12/*style<6C><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>elementData<74><61>λ<EFBFBD><CEBB>*/, 3,4,
|
||
5 + 2,5 + 12, 5 + 1,5 + 12,
|
||
//elememt1
|
||
1 + 0/*line<6E><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD>element<6E>еڼ<D0B5><DABC><EFBFBD>*/,1 + 25
|
||
|
||
};
|
||
std::vector<QVector4D> bvhBounds = {
|
||
//root
|
||
QVector4D(-1,-1,1,1),
|
||
QVector4D(-0.9,-0.9,-0.1,0.9), QVector4D(0.1, -0.9,0.9,0.9),
|
||
QVector4D(-0.8,-0.8,-0.2,-0.1), QVector4D(-0.7,0.2,-0.2,0.7), QVector4D(0.2,-0.8,0.8,-0.1), QVector4D(0.2,0.1,0.8,0.8),
|
||
//elememt0
|
||
QVector4D(-1,-1,1,1),
|
||
QVector4D(-1,-0.5,1,1), QVector4D(-1,-1,1,0.5),
|
||
QVector4D(-1,-1,1,-0.5), QVector4D(-1,-0.5,1,0.5),
|
||
//elememt1
|
||
QVector4D(-1,0,1,1),
|
||
};
|
||
std::vector<GLuint> elementOffset = {
|
||
//element0
|
||
7, //elementBvhRoot
|
||
5, //elementBvhLength
|
||
0, //pointsOffset
|
||
0, //linesOffset
|
||
28, //contoursOffset
|
||
//element1
|
||
12, //elementBvhRoot
|
||
1, //elementBvhLength
|
||
19, //pointsOffset
|
||
40, //linesOffset
|
||
44 //contoursOffset
|
||
};
|
||
|
||
std::vector<GLuint> elementIndex = {
|
||
//element0
|
||
//lines, ȫ<><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ױ<EFBFBD><D7B1><EFBFBD><EFBFBD><EFBFBD>, ÿ<><C3BF><EFBFBD><EFBFBD><EFBFBD>ĸ<EFBFBD><C4B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
4,2,2,0,
|
||
0,0,1,1,
|
||
1,1,4,4,
|
||
1,1,5,5,
|
||
4,4,5,5,
|
||
1,1,3,3,
|
||
3,3,5,5,
|
||
//contours, ÿ<><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
0,1,2,
|
||
2,3,4,
|
||
3,5,6,
|
||
//element2
|
||
//lines
|
||
0,1,2
|
||
};
|
||
|
||
std::vector<GLfloat> elementData = {
|
||
//element0
|
||
//points
|
||
-1,0.5, -1,-0.5, 0,1, 0,-1, 1,0.5, 1,-0.5,
|
||
//fillStyle
|
||
//fill
|
||
0,
|
||
//fillType
|
||
0, //<2F><>ɫ
|
||
//fillColorMetallicRoughness
|
||
1,1,0, 0,0.8,
|
||
|
||
//element1
|
||
//points
|
||
-1,0.5, 0,1, 1,0.5,
|
||
//strokeStyle
|
||
//stroke
|
||
1,
|
||
//strokeWidth
|
||
0.02,
|
||
//strokeEndType
|
||
0, //Բ<><D4B2>
|
||
//strokeFillType
|
||
0, //<2F><>ɫ
|
||
//strokeFillColorMetallicRoughness
|
||
0,1,0, 0,0.8
|
||
};
|
||
|
||
//m_mesh->paintingIndex = paintingHelper->addPainting(bounds.size(), std::vector<GLuint>(children.begin()+2, children.end()), bounds,
|
||
// elementOffset, elementIndex, elementData);
|
||
m_mesh->paintingIndex = paintingHelper->addPainting(7, bvhChildren, bvhBounds,
|
||
elementOffset, elementIndex, elementData);
|
||
|
||
m_mesh->setupMesh();
|
||
return m_mesh;
|
||
}
|
||
else
|
||
{
|
||
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Mesh* m_mesh = new Mesh(glFunc, shaderProgram, shadowProgram, model);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
|
||
{
|
||
Vertex vertex;
|
||
QVector3D vector; //<2F><>assimp<6D><70><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ת<EFBFBD><D7AA>ΪQtOpenGL֧<4C>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD>
|
||
|
||
// λ<><CEBB>
|
||
vector.setX(mesh->mVertices[i].x);
|
||
vector.setY(mesh->mVertices[i].y);
|
||
vector.setZ(mesh->mVertices[i].z);
|
||
vertex.Position = vector;
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if (mesh->mNormals) {
|
||
vector.setX(mesh->mNormals[i].x);
|
||
vector.setY(mesh->mNormals[i].y);
|
||
vector.setZ(mesh->mNormals[i].z);
|
||
vertex.Normal = vector;
|
||
}
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if (mesh->mTextureCoords[0]) // does the mesh contain texture coordinates?
|
||
{
|
||
QVector2D vec;
|
||
//һ<><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><D4B0><EFBFBD>8<EFBFBD><38><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꡣ<EFBFBD><EAA1A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǽ<EFBFBD><C7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Dz<EFBFBD><C7B2><EFBFBD>
|
||
//ʹ<><CAB9>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD>ͣ<EFBFBD><CDA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(0)<29><>
|
||
vec.setX(mesh->mTextureCoords[0][i].x);
|
||
vec.setY(mesh->mTextureCoords[0][i].y);
|
||
vertex.TexCoords = vec;
|
||
}
|
||
else {
|
||
vertex.TexCoords = QVector2D(0, 0);
|
||
}
|
||
if (mesh->mTangents) {
|
||
// tangent
|
||
vector.setX(mesh->mTangents[i].x);
|
||
vector.setY(mesh->mTangents[i].y);
|
||
vector.setZ(mesh->mTangents[i].z);
|
||
vertex.Tangent = vector;
|
||
}
|
||
if (mesh->mBitangents) {
|
||
vector.setX(mesh->mBitangents[i].x);
|
||
vector.setY(mesh->mBitangents[i].y);
|
||
vector.setZ(mesh->mBitangents[i].z);
|
||
vertex.Bitangent = vector;
|
||
}
|
||
// bitangent
|
||
m_mesh->vertices.push_back(vertex);
|
||
}
|
||
|
||
for (unsigned int i = 0; i < mesh->mNumFaces; i++)
|
||
{
|
||
aiFace face = mesh->mFaces[i];
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD><D3B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
for (unsigned int j = 0; j < face.mNumIndices; j++) {
|
||
m_mesh->indices.push_back(face.mIndices[j]);
|
||
}
|
||
}
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
QVector<Texture*> diffuseMaps = loadMaterialTextures(material, aiTextureType_BASE_COLOR, "texture_basecolor");
|
||
for (auto& it : diffuseMaps)
|
||
m_mesh->textures.push_back(it);
|
||
|
||
QVector<Texture*> metalnessMaps = loadMaterialTextures(material, aiTextureType_METALNESS, "texture_metallic_roughness");
|
||
for (auto& it : metalnessMaps)
|
||
m_mesh->textures.push_back(it);
|
||
|
||
QVector<Texture*> normalMaps = loadMaterialTextures(material, aiTextureType_NORMALS, "texture_normal");
|
||
for (auto& it : normalMaps)
|
||
m_mesh->textures.push_back(it);
|
||
|
||
m_mesh->setupMesh();
|
||
return m_mesh;
|
||
}
|
||
|
||
|
||
}
|
||
|
||
QVector<Texture*> Model::loadMaterialTextures(aiMaterial* mat, aiTextureType type, QString typeName)
|
||
{
|
||
|
||
QVector<Texture*> textures;
|
||
|
||
for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
|
||
{
|
||
//qDebug() << typeName;
|
||
aiString str;
|
||
mat->GetTexture(type, i, &str);
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>֮ǰ<D6AE><C7B0><EFBFBD>ع<EFBFBD><D8B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
bool skip = false;
|
||
for (unsigned int j = 0; j < textures_loaded.size(); j++)
|
||
{
|
||
if (std::strcmp(textures_loaded[j]->path.toStdString().c_str(), str.C_Str()) == 0)
|
||
{
|
||
textures.push_back(textures_loaded[j]);
|
||
skip = true; //<2F><><EFBFBD>Ż<EFBFBD><C5BB><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬfilepath<74><68><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD>أ<EFBFBD><D8A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>
|
||
break;
|
||
}
|
||
}
|
||
if (!skip)
|
||
{ // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʻ<EFBFBD>û<EFBFBD>м<EFBFBD><D0BC>أ<EFBFBD><D8A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Texture* texture = new Texture;
|
||
QImage data(directory.filePath(str.C_Str()));
|
||
if (!data.isNull()) {
|
||
texture->texture.setData(data);
|
||
texture->type = typeName;
|
||
texture->path = str.C_Str();
|
||
textures.push_back(texture);
|
||
textures_loaded.push_back(texture); // store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
|
||
}
|
||
else {
|
||
qDebug() << "δ<EFBFBD>ܳɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" << directory.filePath(str.C_Str());
|
||
}
|
||
}
|
||
}
|
||
return textures;
|
||
}
|