added documentation, merged mesh and meshbuilder, deleted useless classes
This commit is contained in:
parent
cd3a1d12c0
commit
c2039b331c
@ -1,9 +1,8 @@
|
|||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
|
#include "sparrowrenderer.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
GLenum Buffer<T>::getGLEnum(BufferType type)
|
GLenum Buffer<T>::getGLEnum(BufferType type)
|
||||||
{
|
{
|
||||||
@ -65,7 +64,7 @@ Buffer<T>::Buffer(const std::vector<T> &data, BufferType type, bool isDynamic) :
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void Buffer<T>::setVertexAttrib(int location, int nbComponents, int offset, int instanceDivisor)
|
void Buffer<T>::setVertexAttrib(int location, int nbComponents, int offset, int instanceDivisor)
|
||||||
{
|
{
|
||||||
if(m_type == VBO)
|
if(m_type == VBO && SparrowRenderer::isModernOpenGLAvailable())
|
||||||
{
|
{
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_id);
|
glBindBuffer(GL_ARRAY_BUFFER, m_id);
|
||||||
glEnableVertexAttribArray(location);
|
glEnableVertexAttribArray(location);
|
||||||
@ -74,8 +73,18 @@ void Buffer<T>::setVertexAttrib(int location, int nbComponents, int offset, int
|
|||||||
glVertexAttribPointer(location, nbComponents, GL_FLOAT, GL_FALSE, sizeof(T), BUFFER_OFFSET(offset));
|
glVertexAttribPointer(location, nbComponents, GL_FLOAT, GL_FALSE, sizeof(T), BUFFER_OFFSET(offset));
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
fprintf(stderr, "WTF\n");
|
|
||||||
|
template <typename T>
|
||||||
|
void Buffer<T>::bind()
|
||||||
|
{
|
||||||
|
glBindBuffer(getGLEnum(m_type), m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Buffer<T>::unbind()
|
||||||
|
{
|
||||||
|
glBindBuffer(getGLEnum(m_type), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
#include "glew.h"
|
#include "glew.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class Buffer
|
class Buffer
|
||||||
{
|
{
|
||||||
@ -32,6 +34,9 @@ public:
|
|||||||
|
|
||||||
void setVertexAttrib(int location, int nbComponents, int offset = 0, int instanceDivisor = 0);
|
void setVertexAttrib(int location, int nbComponents, int offset = 0, int instanceDivisor = 0);
|
||||||
|
|
||||||
|
void bind();
|
||||||
|
void unbind();
|
||||||
|
|
||||||
GLuint getId() {return m_id;}
|
GLuint getId() {return m_id;}
|
||||||
BufferType getType() {return m_type;}
|
BufferType getType() {return m_type;}
|
||||||
bool isDynamic() {return m_isDynamic;}
|
bool isDynamic() {return m_isDynamic;}
|
||||||
|
@ -7,9 +7,8 @@ class Camera{
|
|||||||
public:
|
public:
|
||||||
virtual glm::mat4 getProjectionMatrix() = 0;
|
virtual glm::mat4 getProjectionMatrix() = 0;
|
||||||
virtual glm::mat4 getViewMatrix() = 0;
|
virtual glm::mat4 getViewMatrix() = 0;
|
||||||
|
|
||||||
virtual void resize(int width, int height) = 0;
|
virtual void resize(int width, int height) = 0;
|
||||||
virtual ~Camera(){}//polymorphism
|
virtual ~Camera() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CAMERA_H
|
#endif // CAMERA_H
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
class Shader;
|
class Shader;
|
||||||
class PhongEntity;
|
class PhongEntity;
|
||||||
|
|
||||||
|
// TODO : everything
|
||||||
|
|
||||||
class GBuffer : public FrameBuffer
|
class GBuffer : public FrameBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
11
src/entity.h
11
src/entity.h
@ -1,11 +0,0 @@
|
|||||||
#ifndef ENTITY
|
|
||||||
#define ENTITY
|
|
||||||
|
|
||||||
class Entity
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void draw(const glm::mat4 viewMatrix, const glm::mat4 projectionMatrix) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ENTITY
|
|
||||||
|
|
@ -55,6 +55,7 @@ void FrameBuffer::deleteTextures()
|
|||||||
for(Texture* t : textures)
|
for(Texture* t : textures)
|
||||||
delete(t);
|
delete(t);
|
||||||
textures.clear();
|
textures.clear();
|
||||||
|
glDrawBuffer(GL_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrameBuffer::bindFBO(GLenum target) const
|
void FrameBuffer::bindFBO(GLenum target) const
|
||||||
|
@ -9,21 +9,53 @@ class Texture;
|
|||||||
class FrameBuffer
|
class FrameBuffer
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief check() checks the state of the framebuffer and returns false if it isn't ready for use
|
||||||
|
*/
|
||||||
bool check();
|
bool check();
|
||||||
|
|
||||||
const bool allocated;
|
const bool allocated;
|
||||||
GLuint fbo;
|
GLuint fbo;
|
||||||
std::vector<Texture*> textures;
|
std::vector<Texture*> textures;
|
||||||
std::vector<GLuint> attachments;
|
std::vector<GLuint> attachments;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief this constructor should be used to create a framebuffer, once created,
|
||||||
|
* use addTexture to attach textures to the framebuffer, then use initColorAttachments to
|
||||||
|
* effectively bind them to the Framebuffer, at this point, the framebuffer should be ready to use.
|
||||||
|
*/
|
||||||
FrameBuffer();
|
FrameBuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief this constructor is only used to get a handle on an existing Framebuffer created by another library
|
||||||
|
*/
|
||||||
FrameBuffer(GLuint id) : allocated(false), fbo(id) {}
|
FrameBuffer(GLuint id) : allocated(false), fbo(id) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief the destructor does not destroy the textures, however, deleteTextures will do that
|
||||||
|
*/
|
||||||
~FrameBuffer();
|
~FrameBuffer();
|
||||||
|
|
||||||
void addTexture(Texture* tex, GLenum attachment);
|
void addTexture(Texture* tex, GLenum attachment);
|
||||||
|
|
||||||
void initColorAttachments();
|
void initColorAttachments();
|
||||||
|
|
||||||
void deleteTextures();
|
void deleteTextures();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief bindFBO binds the framebuffer on the specified target
|
||||||
|
*/
|
||||||
void bindFBO(GLenum target = GL_FRAMEBUFFER) const;
|
void bindFBO(GLenum target = GL_FRAMEBUFFER) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getTexture returns an attached texture, indexed from their adding order.
|
||||||
|
*/
|
||||||
Texture* getTexture(int texId);
|
Texture* getTexture(int texId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief screen is a Framebuffer handle on the screen
|
||||||
|
*/
|
||||||
static const FrameBuffer* screen;
|
static const FrameBuffer* screen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,9 +12,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void bindAttributes(Shader*) = 0;
|
virtual void bindAttributes(Shader*) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getFlags should return flags beginning with MATERIAL from the enum available in mesh.h
|
||||||
|
*/
|
||||||
virtual unsigned int getFlags() = 0;
|
virtual unsigned int getFlags() = 0;
|
||||||
|
|
||||||
virtual ~Material(){}
|
virtual ~Material() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MATERIAL_H
|
#endif // MATERIAL_H
|
||||||
|
107
src/mesh.cpp
107
src/mesh.cpp
@ -21,6 +21,7 @@ const char* const Mesh::flagStr[Mesh::NB_FLAGS] =
|
|||||||
"COLOR_TEXTURE",
|
"COLOR_TEXTURE",
|
||||||
"ALPHA_MASK",
|
"ALPHA_MASK",
|
||||||
|
|
||||||
|
"PHONG",
|
||||||
"DIFFUSE_TEXTURE",
|
"DIFFUSE_TEXTURE",
|
||||||
"AMBIENT_TEXTURE",
|
"AMBIENT_TEXTURE",
|
||||||
"SPECULAR_TEXTURE",
|
"SPECULAR_TEXTURE",
|
||||||
@ -36,13 +37,30 @@ Mesh::Mesh() :
|
|||||||
depth(0),
|
depth(0),
|
||||||
vao(0),
|
vao(0),
|
||||||
primitive_type(GL_TRIANGLES)
|
primitive_type(GL_TRIANGLES)
|
||||||
{}
|
{
|
||||||
|
clearBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
Mesh::~Mesh()
|
Mesh::~Mesh()
|
||||||
{
|
{
|
||||||
destroyGL();
|
destroyGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mesh::addBuffer(Buffer *b, int bufferType)
|
||||||
|
{
|
||||||
|
buffersId[bufferType] = buffers.size();
|
||||||
|
buffers.push_back(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::clearBuffers()
|
||||||
|
{
|
||||||
|
for(int i=0; i<NB_BUFFERS; ++i)
|
||||||
|
buffersId[i] = -1;
|
||||||
|
for(Buffer *b : buffers)
|
||||||
|
delete(b);
|
||||||
|
buffers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void Mesh::initGL()
|
void Mesh::initGL()
|
||||||
{
|
{
|
||||||
destroyGL();
|
destroyGL();
|
||||||
@ -58,14 +76,14 @@ void Mesh::initGL()
|
|||||||
{
|
{
|
||||||
b = new Buffer(positions3D, Buffer::VBO);
|
b = new Buffer(positions3D, Buffer::VBO);
|
||||||
b->setVertexAttrib(0, 3);
|
b->setVertexAttrib(0, 3);
|
||||||
buffers.push_back(b);
|
addBuffer(b, POSITION_BUFFER);
|
||||||
|
|
||||||
// init normals vbo
|
// init normals vbo
|
||||||
if(!normals.empty())
|
if(!normals.empty())
|
||||||
{
|
{
|
||||||
b = new Buffer(normals, Buffer::VBO);
|
b = new Buffer(normals, Buffer::VBO);
|
||||||
b->setVertexAttrib(1, 3);
|
b->setVertexAttrib(1, 3);
|
||||||
buffers.push_back(b);
|
addBuffer(b, NORMAL_BUFFER);
|
||||||
}
|
}
|
||||||
// init tangents vbo
|
// init tangents vbo
|
||||||
if(!tangents.empty())
|
if(!tangents.empty())
|
||||||
@ -73,14 +91,14 @@ void Mesh::initGL()
|
|||||||
b = new Buffer(tangents, Buffer::VBO);
|
b = new Buffer(tangents, Buffer::VBO);
|
||||||
b->setVertexAttrib(3, 3);
|
b->setVertexAttrib(3, 3);
|
||||||
b->setVertexAttrib(4, 3);
|
b->setVertexAttrib(4, 3);
|
||||||
buffers.push_back(b);
|
addBuffer(b, TANGENT_BUFFER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(!positions2D.empty())
|
else if(!positions2D.empty())
|
||||||
{
|
{
|
||||||
b = new Buffer(positions2D, Buffer::VBO);
|
b = new Buffer(positions2D, Buffer::VBO);
|
||||||
b->setVertexAttrib(0, 2);
|
b->setVertexAttrib(0, 2);
|
||||||
buffers.push_back(b);
|
addBuffer(b, POSITION_BUFFER);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -92,7 +110,7 @@ void Mesh::initGL()
|
|||||||
{
|
{
|
||||||
b = new Buffer(texCoords, Buffer::VBO);
|
b = new Buffer(texCoords, Buffer::VBO);
|
||||||
b->setVertexAttrib(2, 2);
|
b->setVertexAttrib(2, 2);
|
||||||
buffers.push_back(b);
|
addBuffer(b, TEXCOORD_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// init instances vbo
|
// init instances vbo
|
||||||
@ -100,14 +118,14 @@ void Mesh::initGL()
|
|||||||
{
|
{
|
||||||
b = new Buffer(instances_offsets, Buffer::VBO);
|
b = new Buffer(instances_offsets, Buffer::VBO);
|
||||||
b->setVertexAttrib(5, 3, 0, 1);
|
b->setVertexAttrib(5, 3, 0, 1);
|
||||||
buffers.push_back(b);
|
addBuffer(b, INSTANCE_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// init EBO
|
// init EBO
|
||||||
if(!indices.empty())
|
if(!indices.empty())
|
||||||
{
|
{
|
||||||
b = new Buffer(indices, Buffer::EBO);
|
b = new Buffer(indices, Buffer::EBO);
|
||||||
buffers.push_back(b);
|
addBuffer(b, INDICES_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// unbind vao
|
// unbind vao
|
||||||
@ -117,37 +135,51 @@ void Mesh::initGL()
|
|||||||
void Mesh::draw(Shader* shader, bool drawNormals, bool drawTexCoord, bool drawTangents)
|
void Mesh::draw(Shader* shader, bool drawNormals, bool drawTexCoord, bool drawTangents)
|
||||||
{
|
{
|
||||||
if(isDoubleSided)
|
if(isDoubleSided)
|
||||||
{
|
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
}
|
|
||||||
bool crappy = (shader == NULL);
|
bool crappy = (shader == NULL);
|
||||||
material->bindAttributes(shader);
|
material->bindAttributes(shader);
|
||||||
glBindVertexArray(vao);
|
glBindVertexArray(vao);
|
||||||
|
|
||||||
if(crappy)
|
if(crappy)
|
||||||
{
|
{
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[POSITION_BUFFER]);
|
Buffer *b = buffers[buffersId[POSITION_BUFFER]];
|
||||||
|
b->bind();
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
glEnableClientState(GL_VERTEX_ARRAY);
|
||||||
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); // TODO : check 2D positions
|
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); // TODO : check 2D positions
|
||||||
if(!texCoords.empty() && drawTexCoord)
|
if(!texCoords.empty() && drawTexCoord)
|
||||||
{
|
{
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[TEXCOORD_BUFFER]);
|
b = buffers[buffersId[TEXCOORD_BUFFER]];
|
||||||
|
b->bind();
|
||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
glTexCoordPointer(2, GL_FLOAT, 0, BUFFER_OFFSET(0));
|
glTexCoordPointer(2, GL_FLOAT, 0, BUFFER_OFFSET(0));
|
||||||
}
|
}
|
||||||
if(!normals.empty() && drawNormals)
|
if(!normals.empty() && drawNormals)
|
||||||
{
|
{
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[NORMAL_BUFFER]);
|
b = buffers[buffersId[NORMAL_BUFFER]];
|
||||||
|
b->bind();
|
||||||
glEnableClientState(GL_NORMAL_ARRAY);
|
glEnableClientState(GL_NORMAL_ARRAY);
|
||||||
glNormalPointer(GL_FLOAT, 0, BUFFER_OFFSET(0));
|
glNormalPointer(GL_FLOAT, 0, BUFFER_OFFSET(0));
|
||||||
}
|
}
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
b->unbind();
|
||||||
|
}
|
||||||
|
if(indices.empty())
|
||||||
|
{
|
||||||
|
int size = positions3D.empty() ? positions2D.size() : positions3D.size();
|
||||||
|
if(!instances_offsets.empty() && !crappy)
|
||||||
|
glDrawArraysInstanced(primitive_type, 0, size, instances_offsets.size());
|
||||||
|
else
|
||||||
|
glDrawArrays(primitive_type, 0, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Buffer *b = buffers[buffersId[INDICES_BUFFER]];
|
||||||
|
b->bind();
|
||||||
|
if(!instances_offsets.empty() && !crappy)
|
||||||
|
glDrawElementsInstanced(primitive_type, indices.size(), GL_UNSIGNED_INT, NULL, instances_offsets.size());
|
||||||
|
else
|
||||||
|
glDrawElements(primitive_type, indices.size(), GL_UNSIGNED_INT, NULL);
|
||||||
|
b->unbind();
|
||||||
}
|
}
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getEBO());
|
|
||||||
if(!instances_offsets.empty() && !crappy)
|
|
||||||
glDrawElementsInstanced(primitive_type, indices.size(), GL_UNSIGNED_INT, NULL, instances_offsets.size());
|
|
||||||
else
|
|
||||||
glDrawElements(primitive_type, indices.size(), GL_UNSIGNED_INT, NULL);
|
|
||||||
|
|
||||||
if(crappy)
|
if(crappy)
|
||||||
{
|
{
|
||||||
@ -160,9 +192,7 @@ void Mesh::draw(Shader* shader, bool drawNormals, bool drawTexCoord, bool drawTa
|
|||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
if(isDoubleSided)
|
if(isDoubleSided)
|
||||||
{
|
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::destroyGL()
|
void Mesh::destroyGL()
|
||||||
@ -172,9 +202,7 @@ void Mesh::destroyGL()
|
|||||||
glDeleteVertexArrays(1, &vao);
|
glDeleteVertexArrays(1, &vao);
|
||||||
vao = 0;
|
vao = 0;
|
||||||
}
|
}
|
||||||
for(Buffer *b : buffers)
|
clearBuffers();
|
||||||
delete(b);
|
|
||||||
buffers.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Mesh::getFlags()
|
unsigned int Mesh::getFlags()
|
||||||
@ -217,14 +245,14 @@ struct VertexComparator
|
|||||||
return (mesh->positions3D[vertId1].y < mesh->positions3D[vertId2].y);
|
return (mesh->positions3D[vertId1].y < mesh->positions3D[vertId2].y);
|
||||||
if(mesh->positions3D[vertId1].z != mesh->positions3D[vertId2].z)
|
if(mesh->positions3D[vertId1].z != mesh->positions3D[vertId2].z)
|
||||||
return (mesh->positions3D[vertId1].z < mesh->positions3D[vertId2].z);
|
return (mesh->positions3D[vertId1].z < mesh->positions3D[vertId2].z);
|
||||||
if(mesh->hasTexCoords())
|
if(!mesh->texCoords.empty())
|
||||||
{
|
{
|
||||||
if(mesh->texCoords[vertId1].x != mesh->texCoords[vertId2].x)
|
if(mesh->texCoords[vertId1].x != mesh->texCoords[vertId2].x)
|
||||||
return (mesh->texCoords[vertId1].x < mesh->texCoords[vertId2].x);
|
return (mesh->texCoords[vertId1].x < mesh->texCoords[vertId2].x);
|
||||||
if(mesh->texCoords[vertId1].y != mesh->texCoords[vertId2].y)
|
if(mesh->texCoords[vertId1].y != mesh->texCoords[vertId2].y)
|
||||||
return (mesh->texCoords[vertId1].y < mesh->texCoords[vertId2].y);
|
return (mesh->texCoords[vertId1].y < mesh->texCoords[vertId2].y);
|
||||||
}
|
}
|
||||||
if(mesh->hasNormals())
|
if(!mesh->normals.empty())
|
||||||
{
|
{
|
||||||
if(mesh->normals[vertId1].x != mesh->normals[vertId2].x)
|
if(mesh->normals[vertId1].x != mesh->normals[vertId2].x)
|
||||||
return (mesh->normals[vertId1].x < mesh->normals[vertId2].x);
|
return (mesh->normals[vertId1].x < mesh->normals[vertId2].x);
|
||||||
@ -241,6 +269,8 @@ Mesh* VertexComparator::mesh = NULL;
|
|||||||
|
|
||||||
void Mesh::mergeVertices()
|
void Mesh::mergeVertices()
|
||||||
{
|
{
|
||||||
|
if(positions3D.empty())
|
||||||
|
return;
|
||||||
bool *deleted = new bool[positions3D.size()];
|
bool *deleted = new bool[positions3D.size()];
|
||||||
int *offsets = new int[positions3D.size()];
|
int *offsets = new int[positions3D.size()];
|
||||||
std::set<int, VertexComparator> vertexSet;
|
std::set<int, VertexComparator> vertexSet;
|
||||||
@ -252,7 +282,7 @@ void Mesh::mergeVertices()
|
|||||||
deleted[indices[i]] = !ret.second && *(ret.first) != indices[i];
|
deleted[indices[i]] = !ret.second && *(ret.first) != indices[i];
|
||||||
if(deleted[indices[i]])
|
if(deleted[indices[i]])
|
||||||
{
|
{
|
||||||
if(hasTangents())
|
if(!tangents.empty())
|
||||||
{
|
{
|
||||||
tangents[*(ret.first)].tangent += tangents[indices[i]].tangent;
|
tangents[*(ret.first)].tangent += tangents[indices[i]].tangent;
|
||||||
tangents[*(ret.first)].binormal += tangents[indices[i]].binormal;
|
tangents[*(ret.first)].binormal += tangents[indices[i]].binormal;
|
||||||
@ -272,11 +302,11 @@ void Mesh::mergeVertices()
|
|||||||
if(offset != 0)
|
if(offset != 0)
|
||||||
{
|
{
|
||||||
positions3D[pos] = positions3D[i];
|
positions3D[pos] = positions3D[i];
|
||||||
if(hasTexCoords())
|
if(!texCoords.empty())
|
||||||
texCoords[pos] = texCoords[i];
|
texCoords[pos] = texCoords[i];
|
||||||
if(hasNormals())
|
if(!normals.empty())
|
||||||
normals[pos] = normals[i];
|
normals[pos] = normals[i];
|
||||||
if(hasTangents())
|
if(!tangents.empty())
|
||||||
tangents[pos] = tangents[i];
|
tangents[pos] = tangents[i];
|
||||||
}
|
}
|
||||||
++pos;
|
++pos;
|
||||||
@ -286,11 +316,11 @@ void Mesh::mergeVertices()
|
|||||||
indices[i] -= offsets[indices[i]];
|
indices[i] -= offsets[indices[i]];
|
||||||
|
|
||||||
positions3D.resize(positions3D.size()-offset);
|
positions3D.resize(positions3D.size()-offset);
|
||||||
if(hasTexCoords())
|
if(!texCoords.empty())
|
||||||
texCoords.resize(texCoords.size()-offset);
|
texCoords.resize(texCoords.size()-offset);
|
||||||
if(hasNormals())
|
if(!normals.empty())
|
||||||
normals.resize(normals.size()-offset);
|
normals.resize(normals.size()-offset);
|
||||||
if(hasTangents())
|
if(!tangents.empty())
|
||||||
tangents.resize(tangents.size()-offset);
|
tangents.resize(tangents.size()-offset);
|
||||||
for(Tangents &t : tangents)
|
for(Tangents &t : tangents)
|
||||||
{
|
{
|
||||||
@ -299,8 +329,17 @@ void Mesh::mergeVertices()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mesh::computeNeighbors()
|
||||||
|
{
|
||||||
|
if(positions3D.empty())
|
||||||
|
return;
|
||||||
|
// TODO : compute adjacency and change primitivetype
|
||||||
|
}
|
||||||
|
|
||||||
void Mesh::computeNormals()
|
void Mesh::computeNormals()
|
||||||
{
|
{
|
||||||
|
if(positions3D.empty())
|
||||||
|
return;
|
||||||
normals.resize(positions3D.size());
|
normals.resize(positions3D.size());
|
||||||
std::memset(normals.data(), 0, normals.size());
|
std::memset(normals.data(), 0, normals.size());
|
||||||
for (std::size_t i=0; i < indices.size(); i += 3)
|
for (std::size_t i=0; i < indices.size(); i += 3)
|
||||||
@ -319,7 +358,7 @@ void Mesh::computeNormals()
|
|||||||
|
|
||||||
void Mesh::computeTangents()
|
void Mesh::computeTangents()
|
||||||
{
|
{
|
||||||
if(!hasTexCoords())
|
if(texCoords.empty())
|
||||||
return;
|
return;
|
||||||
tangents = std::vector<Tangents>(positions3D.size());
|
tangents = std::vector<Tangents>(positions3D.size());
|
||||||
|
|
||||||
|
208
src/mesh.h
208
src/mesh.h
@ -14,6 +14,10 @@ struct Mesh
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* MESH AND MATERIAL FLAGS */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
// Mesh properties
|
// Mesh properties
|
||||||
enum {
|
enum {
|
||||||
// Geometric properties
|
// Geometric properties
|
||||||
@ -34,6 +38,7 @@ public:
|
|||||||
MATERIAL_ALPHA_MASK,
|
MATERIAL_ALPHA_MASK,
|
||||||
|
|
||||||
// 3D phong material
|
// 3D phong material
|
||||||
|
MATERIAL_PHONG,
|
||||||
MATERIAL_PHONG_DIFFUSE_TEXTURE,
|
MATERIAL_PHONG_DIFFUSE_TEXTURE,
|
||||||
MATERIAL_PHONG_AMBIENT_TEXTURE,
|
MATERIAL_PHONG_AMBIENT_TEXTURE,
|
||||||
MATERIAL_PHONG_SPECULAR_TEXTURE,
|
MATERIAL_PHONG_SPECULAR_TEXTURE,
|
||||||
@ -41,44 +46,170 @@ public:
|
|||||||
|
|
||||||
// 3D Beckman-like materials
|
// 3D Beckman-like materials
|
||||||
MATERIAL_BACKMANN_BUMP_MAP,
|
MATERIAL_BACKMANN_BUMP_MAP,
|
||||||
// TODO
|
|
||||||
|
|
||||||
NB_FLAGS
|
NB_FLAGS
|
||||||
};
|
};
|
||||||
|
|
||||||
// define strings associated to the properties
|
// shader "#define" strings associated to the properties
|
||||||
static const char* const flagStr[NB_FLAGS];
|
static const char* const flagStr[NB_FLAGS];
|
||||||
|
|
||||||
// geometry data
|
/*************************************************************/
|
||||||
|
/* GEOMETRY DATA */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
// 3D public data
|
||||||
|
std::vector<glm::vec3> positions3D;
|
||||||
|
std::vector<glm::vec3> normals;
|
||||||
|
|
||||||
|
// 2D public data
|
||||||
|
std::vector<glm::vec2> positions2D;
|
||||||
|
|
||||||
|
// public data common to 2D and 3D
|
||||||
|
std::vector<glm::vec2> texCoords;
|
||||||
|
std::vector<glm::vec3> instances_offsets;
|
||||||
|
std::vector<GLuint> indices;
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* MAIN METHODS */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mesh builds an empty mesh, to be renderable, a mesh must have vertices and a material.
|
||||||
|
*/
|
||||||
|
Mesh();
|
||||||
|
|
||||||
|
virtual ~Mesh();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenGL methods :
|
||||||
|
* - initGL allocates the GPU buffers and sends the mesh data to the GPU
|
||||||
|
* - draw binds the material attributes and draws the mesh geometry
|
||||||
|
* - destroyGL releases the allocated GPU memory, destroyGL is called automatically in Mesh's destructor and at the beginning of initGL
|
||||||
|
*/
|
||||||
|
void initGL();
|
||||||
|
void draw(Shader* shader = NULL, bool drawNormals = true, bool drawTexCoord = true, bool drawTangents = true);
|
||||||
|
void destroyGL();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getFlags returns the flags that defines the specificities of the mesh and his material
|
||||||
|
*/
|
||||||
|
unsigned int getFlags();
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* MESH BUILDING METHODS */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
// add vertex
|
||||||
|
void addVertex(float x, float y, float z) {addVertex(glm::vec3(x, y, z));}
|
||||||
|
void addVertex(const glm::vec3 &position) {positions3D.push_back(position);}
|
||||||
|
void addVertex(float x, float y) {addVertex(glm::vec2(x, y));}
|
||||||
|
void addVertex(const glm::vec2 &position) {positions2D.push_back(position);}
|
||||||
|
void addVertex(const glm::vec3 &position, const glm::vec2 &texCoord) {addVertex(position); addTexCoord(texCoord);}
|
||||||
|
void addVertex(const glm::vec2 &position, const glm::vec2 &texCoord) {addVertex(position); addTexCoord(texCoord);}
|
||||||
|
void addVertex(const glm::vec3 &position, const glm::vec3 &normal) {addVertex(position); addNormal(normal);}
|
||||||
|
void addVertex(const glm::vec3 &position, const glm::vec3 &normal, const glm::vec2 &texCoord) {addVertex(position, normal); addTexCoord(texCoord);}
|
||||||
|
|
||||||
|
// add indices
|
||||||
|
void addTriangle(int i1, int i2, int i3) {indices.push_back(i1), indices.push_back(i2), indices.push_back(i3);}
|
||||||
|
void addLine(int i1, int i2) {indices.push_back(i1), indices.push_back(i2);}
|
||||||
|
|
||||||
|
// Material accessers
|
||||||
|
void setMaterial(Material* mat) {material = mat;}
|
||||||
|
Material *getMaterial() {return material;}
|
||||||
|
|
||||||
|
// other accessers
|
||||||
|
void addNormal(float x, float y, float z) {addNormal(glm::vec3(x, y, z));}
|
||||||
|
void addNormal(const glm::vec3 &normal) {normals.push_back(normal);}
|
||||||
|
void addTexCoord(float u, float v) {addTexCoord(glm::vec2(u, v));}
|
||||||
|
void addTexCoord(const glm::vec2 &texCoord) {texCoords.push_back(texCoord);}
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* 2D MESH PROPERTIES */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setDepth allows to set the depth of a 2D mesh, the depth must be between -1 and 1, -1 being the closest to the camera
|
||||||
|
*/
|
||||||
|
void setDepth(float d) {depth = d;}
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* 3D MESH PROPERTIES */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setIsDoubleSided allows to enable or disable face culling for this Mesh
|
||||||
|
*/
|
||||||
|
void setIsDoubleSided(bool val) {isDoubleSided = val;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setIsBillboard allows to enable or disable billboarding,
|
||||||
|
* a billboard mesh will always follow the camera orientation
|
||||||
|
*/
|
||||||
|
void setIsBillboard(bool val) {isBillboard = val;}
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* 3D MESH TOOLS */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief mergeVertices simplifies an indexed mesh by merging all vertices that
|
||||||
|
* have exactly the same position, texcoord, and normals, the tangents will be averaged.
|
||||||
|
*/
|
||||||
|
void mergeVertices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief computeNormals computes adjacency for a triangle mesh, the mesh type changes to GL_TRIANGLES_ADJACENCY
|
||||||
|
*/
|
||||||
|
void computeNeighbors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compute normals from an indexed mesh (positions + indices)
|
||||||
|
*/
|
||||||
|
void computeNormals();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compute tangent space from a textured indexed mesh (positions + normals + texcoords + indices)
|
||||||
|
*/
|
||||||
|
void computeTangents();
|
||||||
|
|
||||||
|
/*************************************************************/
|
||||||
|
/* ADVANCED CUSTOMISATION */
|
||||||
|
/*************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies what kind of primitives to render. Symbolic constants GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY,
|
||||||
|
* GL_LINES_ADJACENCY, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY, GL_TRIANGLES_ADJACENCY and GL_PATCHES are accepted.
|
||||||
|
*
|
||||||
|
* default is GL_TRIANGLES
|
||||||
|
*/
|
||||||
|
void setPrimitiveType(GLenum type) {primitive_type = type;}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
glm::vec3 tangent;
|
glm::vec3 tangent;
|
||||||
glm::vec3 binormal;
|
glm::vec3 binormal;
|
||||||
} Tangents;
|
} Tangents;
|
||||||
|
|
||||||
|
std::vector<Tangents> tangents;
|
||||||
|
|
||||||
Material* material;
|
Material* material;
|
||||||
bool isDoubleSided;
|
bool isDoubleSided;
|
||||||
bool isBillboard;
|
bool isBillboard;
|
||||||
float depth;
|
float depth;
|
||||||
|
|
||||||
std::vector<glm::vec3> positions3D;
|
GLenum primitive_type;
|
||||||
std::vector<glm::vec2> positions2D;
|
|
||||||
std::vector<glm::vec3> normals;
|
|
||||||
std::vector<glm::vec2> texCoords;
|
|
||||||
std::vector<glm::vec3> instances_offsets;
|
|
||||||
std::vector<Tangents> tangents;
|
|
||||||
std::vector<GLuint> indices;
|
|
||||||
|
|
||||||
std::vector<Buffer*> buffers;
|
std::vector<Buffer*> buffers;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
// required buffer
|
// required buffer
|
||||||
POSITION_BUFFER,
|
POSITION_BUFFER,
|
||||||
|
|
||||||
// indices buffers
|
// indices buffers
|
||||||
INDICES_BUFFER,
|
INDICES_BUFFER,
|
||||||
|
|
||||||
// optionnal buffers :
|
// optionnal buffers :
|
||||||
NORMAL_BUFFER,
|
NORMAL_BUFFER,
|
||||||
TEXCOORD_BUFFER,
|
TEXCOORD_BUFFER,
|
||||||
@ -89,48 +220,13 @@ public:
|
|||||||
|
|
||||||
NB_BUFFERS
|
NB_BUFFERS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int buffersId[NB_BUFFERS];
|
||||||
|
|
||||||
|
void addBuffer(Buffer *b, int bufferType);
|
||||||
|
void clearBuffers();
|
||||||
|
|
||||||
GLuint vao;
|
GLuint vao;
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
GLenum primitive_type;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Mesh();
|
|
||||||
virtual ~Mesh();
|
|
||||||
|
|
||||||
void initGL();
|
|
||||||
void draw(Shader* shader = NULL, bool drawNormals = true, bool drawTexCoord = true, bool drawTangents = true);
|
|
||||||
void destroyGL();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief getFlags returns the flags that defines the specificities of the mesh and his material
|
|
||||||
*/
|
|
||||||
unsigned int getFlags();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this class is intended to be used with a 3D mesh of the default GL_TRIANGLES primitives,
|
|
||||||
* the methods mergeVertices, computeNormals, and computeTangents will not
|
|
||||||
* work with other primitive types.
|
|
||||||
*/
|
|
||||||
void setPrimitiveType(GLenum type) {primitive_type = type;}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief mergeVertices simplifies an indexed mesh by merging all vertices that
|
|
||||||
* have exactly the same position, texcoord, and normals, the tangents will be averaged.
|
|
||||||
*/
|
|
||||||
void mergeVertices();
|
|
||||||
/**
|
|
||||||
* compute normals from an indexed mesh (positions + indices)
|
|
||||||
*/
|
|
||||||
void computeNormals();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* compute tangent space from a textured indexed mesh (positions + normals + texcoords + indices)
|
|
||||||
*/
|
|
||||||
void computeTangents();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MESH_H
|
#endif // MESH_H
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
#include "meshbuilder.h"
|
|
||||||
|
|
||||||
void MeshBuilder::addPosition(float x, float y, float z)
|
|
||||||
{
|
|
||||||
addPosition(glm::vec3(x, y, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addPosition(const glm::vec3 &position)
|
|
||||||
{
|
|
||||||
positions3D.push_back(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addNormal(float x, float y, float z)
|
|
||||||
{
|
|
||||||
addNormal(glm::vec3(x, y, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addNormal(const glm::vec3 &normal)
|
|
||||||
{
|
|
||||||
normals.push_back(normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addTexCoord(float u, float v)
|
|
||||||
{
|
|
||||||
addTexCoord(glm::vec2(u, v));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addTexCoord(const glm::vec2 &texCoord)
|
|
||||||
{
|
|
||||||
texCoords.push_back(texCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void MeshBuilder::addVertex(const glm::vec3 &position, const glm::vec3 &normal)
|
|
||||||
{
|
|
||||||
addPosition(position);
|
|
||||||
addNormal(normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addVertex(const glm::vec3 &position, const glm::vec2 &texCoord)
|
|
||||||
{
|
|
||||||
addPosition(position);
|
|
||||||
addTexCoord(texCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::addVertex(const glm::vec3 &position, const glm::vec3 &normal, const glm::vec2 &texCoord)
|
|
||||||
{
|
|
||||||
addVertex(position, normal);
|
|
||||||
addTexCoord(texCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void MeshBuilder::addTriangle(int i1, int i2, int i3)
|
|
||||||
{
|
|
||||||
indices.push_back(i1);
|
|
||||||
indices.push_back(i2);
|
|
||||||
indices.push_back(i3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshBuilder::setMaterial(Material* myMaterial)
|
|
||||||
{
|
|
||||||
material = myMaterial;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
#ifndef MESHBUILDER_H
|
|
||||||
#define MESHBUILDER_H
|
|
||||||
|
|
||||||
#include "mesh.h"
|
|
||||||
|
|
||||||
class MeshBuilder : public Mesh
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void addTriangle(int i1, int i2, int i3);
|
|
||||||
|
|
||||||
void addPosition(float x, float y, float z);
|
|
||||||
void addPosition(const glm::vec3 &position);
|
|
||||||
void addNormal(float x, float y, float z);
|
|
||||||
void addNormal(const glm::vec3 &normal);
|
|
||||||
void addTexCoord(float u, float v);
|
|
||||||
void addTexCoord(const glm::vec2 &texCoord);
|
|
||||||
|
|
||||||
void addVertex(const glm::vec3 &position, const glm::vec3 &normal);
|
|
||||||
void addVertex(const glm::vec3 &position, const glm::vec2 &texCoord);
|
|
||||||
void addVertex(const glm::vec3 &position, const glm::vec3 &normal, const glm::vec2 &texCoord);
|
|
||||||
|
|
||||||
void setMaterial(Material* myMaterial);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MESHBUILDER_H
|
|
@ -68,41 +68,17 @@ void PhongMaterial::bindAttributes(Shader* myShader)
|
|||||||
|
|
||||||
unsigned int PhongMaterial::getFlags()
|
unsigned int PhongMaterial::getFlags()
|
||||||
{
|
{
|
||||||
unsigned int flags = 0;
|
unsigned int flags = 1 << Mesh::MATERIAL_PHONG;
|
||||||
if(normal_map != NULL)
|
if(normal_map != NULL)
|
||||||
flags |= Mesh::MATERIAL_PHONG_NORMAL_MAP;
|
flags |= 1 << Mesh::MATERIAL_PHONG_NORMAL_MAP;
|
||||||
if(ambient_texture != NULL)
|
if(ambient_texture != NULL)
|
||||||
flags |= Mesh::MATERIAL_PHONG_AMBIENT_TEXTURE;
|
flags |= 1 << Mesh::MATERIAL_PHONG_AMBIENT_TEXTURE;
|
||||||
if(diffuse_texture != NULL)
|
if(diffuse_texture != NULL)
|
||||||
flags |= Mesh::MATERIAL_PHONG_DIFFUSE_TEXTURE;
|
flags |= 1 << Mesh::MATERIAL_PHONG_DIFFUSE_TEXTURE;
|
||||||
if(specular_texture != NULL)
|
if(specular_texture != NULL)
|
||||||
flags |= Mesh::MATERIAL_PHONG_SPECULAR_TEXTURE;
|
flags |= 1 << Mesh::MATERIAL_PHONG_SPECULAR_TEXTURE;
|
||||||
if(alpha_mask != NULL)
|
if(alpha_mask != NULL)
|
||||||
flags |= Mesh::MATERIAL_ALPHA_MASK;
|
flags |= 1 << Mesh::MATERIAL_ALPHA_MASK;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhongMaterial::setAmbientTexture(Texture* myTexture)
|
|
||||||
{
|
|
||||||
ambient_texture = myTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhongMaterial::setDiffuseTexture(Texture* myTexture)
|
|
||||||
{
|
|
||||||
diffuse_texture = myTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhongMaterial::setSpecularTexture(Texture* myTexture)
|
|
||||||
{
|
|
||||||
specular_texture = myTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhongMaterial::setNormalMap(Texture* myNormalMap)
|
|
||||||
{
|
|
||||||
normal_map = myNormalMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhongMaterial::setAlphaMask(Texture* myAlphaMask)
|
|
||||||
{
|
|
||||||
alpha_mask = myAlphaMask;
|
|
||||||
}
|
|
||||||
|
@ -50,11 +50,11 @@ public:
|
|||||||
|
|
||||||
virtual unsigned int getFlags();
|
virtual unsigned int getFlags();
|
||||||
|
|
||||||
void setAmbientTexture(Texture* myTexture);
|
void setAmbientTexture(Texture* myTexture) {ambient_texture = myTexture;}
|
||||||
void setDiffuseTexture(Texture* myTexture);
|
void setDiffuseTexture(Texture* myTexture) {diffuse_texture = myTexture;}
|
||||||
void setSpecularTexture(Texture* myTexture);
|
void setSpecularTexture(Texture* myTexture) {specular_texture = myTexture;}
|
||||||
void setNormalMap(Texture* myNormalMap);
|
void setNormalMap(Texture* myNormalMap) {normal_map = myNormalMap;}
|
||||||
void setAlphaMask(Texture* myAlphaMask);
|
void setAlphaMask(Texture* myAlphaMask) {alpha_mask = myAlphaMask;}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PHONGMATERIAL_H
|
#endif // PHONGMATERIAL_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user