extract Mesh and GLMesh
This commit is contained in:
parent
07ed96f16d
commit
e16915428c
@ -2,8 +2,7 @@
|
||||
#include "Mesh.h"
|
||||
#include "Shader.h"
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include "gl.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/mat3x3.hpp>
|
||||
@ -27,30 +26,6 @@ using glm::uvec3;
|
||||
using std::map;
|
||||
using std::vector;
|
||||
|
||||
static bool glck(const char* call) {
|
||||
int err = glGetError();
|
||||
if (err == 0) {
|
||||
return true;
|
||||
} else {
|
||||
switch (err) {
|
||||
case GL_INVALID_VALUE:
|
||||
printf("GL_INVALID_VALUE: %s\n", call);
|
||||
break;
|
||||
case GL_INVALID_ENUM:
|
||||
printf("GL_INVALID_ENUM: %s\n", call);
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
printf("GL_INVALID_OPERATION: %s\n", call);
|
||||
break;
|
||||
default:
|
||||
printf("UNKNOWN Error: %s\n", call);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define GLCK(a) {a; assert(glck(#a));}
|
||||
|
||||
static void error_callback(int error, const char* description) {
|
||||
fprintf(stderr, "Error: %s\n", description);
|
||||
}
|
||||
@ -80,12 +55,11 @@ static void char_callback(GLFWwindow* window, unsigned int c) {
|
||||
Application::Application() :
|
||||
window(0), density(), densityIsolevel(0.2), polygonizer(density,
|
||||
densityIsolevel), textureSize(512), texturemapper(density,
|
||||
textureSize), programId(0), vPositionLoc(-1), vTexCoordLoc(-1), vNormalLoc(
|
||||
-1), meteoridCount(1000), meteroidMinRadius(0.005), meteroidMaxRadius(
|
||||
0.2), meteroidSizeExponent(2048), densityFrequency(2), densityOctave(
|
||||
2), densityScale(1.0f), resolution(0.1), smoothMesh(true), viewDistance(
|
||||
3.0f), fpsMode(false), meshUploadRequest(false), textureUploadRequest(
|
||||
false) {
|
||||
textureSize), programId(0), meteoridCount(1000), meteroidMinRadius(
|
||||
0.005), meteroidMaxRadius(0.2), meteroidSizeExponent(2048), densityFrequency(
|
||||
2), densityOctave(2), densityScale(1.0f), resolution(0.1), smoothMesh(
|
||||
true), viewDistance(3.0f), fpsMode(false), meshUploadRequest(
|
||||
false), textureUploadRequest(false) {
|
||||
init();
|
||||
}
|
||||
|
||||
@ -145,31 +119,20 @@ void Application::init() {
|
||||
|
||||
ImGui_ImplGlfwGL3_Init(window, false);
|
||||
|
||||
GLCK(glGenVertexArrays(1, &vertexArrayId))
|
||||
GLCK(glGenBuffers(1, &vPositionId))
|
||||
GLCK(glGenBuffers(1, &vTexCoordId))
|
||||
GLCK(glGenBuffers(1, &vNormalId))
|
||||
GLCK(glGenBuffers(1, &indexBufferId))
|
||||
|
||||
GLCK(glGenTextures(1, &tAlbedoId))
|
||||
GLCK(glGenTextures(1, &tNormalId))
|
||||
GLCK(glGenTextures(1, &tRoughnessId))
|
||||
GLCK(glGenTextures(1, &tMetalicId))
|
||||
|
||||
loadShader();
|
||||
updateVertexArrayObject();
|
||||
glMesh.create();
|
||||
glMesh.update(vPositionLoc, vNormalLoc, vTexCoordLoc);
|
||||
generateAsteroid();
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
ImGui_ImplGlfwGL3_Shutdown();
|
||||
|
||||
GLCK(glDeleteBuffers(1, &vPositionId))
|
||||
GLCK(glDeleteBuffers(1, &vTexCoordId))
|
||||
GLCK(glDeleteBuffers(1, &vNormalId))
|
||||
GLCK(glDeleteBuffers(1, &indexBufferId))
|
||||
GLCK(glDeleteVertexArrays(1, &vertexArrayId))
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
@ -217,15 +180,16 @@ void Application::toogleFpsMode() {
|
||||
|
||||
void Application::generateAsteroid() {
|
||||
if (meshProgress.begin("Random Impacts", 1)) {
|
||||
//if (density.getImpacts().size() == 0)
|
||||
density.addRandomImpacts(1000, densityIsolevel, 0.01, 0.005, 0.2, 256);
|
||||
if (density.getImpacts().size() == 0)
|
||||
density.addRandomImpacts(1000, densityIsolevel, 0.01, 0.005, 0.2,
|
||||
256);
|
||||
|
||||
//density.saveCrossSection("density.png", 512);
|
||||
}
|
||||
polygonizer.polygonize(resolution, meshProgress);
|
||||
|
||||
if (smoothMesh && meshProgress.begin("Smooth", 1)) {
|
||||
smooth(polygonizer.vertices, polygonizer.indices);
|
||||
polygonizer.mesh.smooth();
|
||||
// {
|
||||
// std::ofstream out("stage2.obj");
|
||||
// saveAttrib(out, "v ", polygonizer.vertices);
|
||||
@ -233,72 +197,40 @@ void Application::generateAsteroid() {
|
||||
// }
|
||||
}
|
||||
|
||||
uvec4v_t edges;
|
||||
if (meshProgress.begin("Find Edges", 1)) {
|
||||
findEdges(polygonizer.vertices, polygonizer.indices, edges);
|
||||
}
|
||||
uvec3v_t adjacents;
|
||||
if (meshProgress.begin("Find Adjacent", 1)) {
|
||||
findAdjacent(edges, polygonizer.indices.size(), adjacents);
|
||||
}
|
||||
vec3v_t faceNormals;
|
||||
calculateFaceNormals(polygonizer.vertices, polygonizer.indices,
|
||||
faceNormals);
|
||||
|
||||
vec3v_t positions;
|
||||
uvec3v_t indices;
|
||||
uintv_t patches;
|
||||
|
||||
createPatches(polygonizer.vertices, polygonizer.indices, faceNormals,
|
||||
adjacents, positions, indices, patches, 0.9);
|
||||
{
|
||||
std::ofstream out("stage1.obj");
|
||||
saveAttrib(out, "v ", positions);
|
||||
saveFaces(out, indices, 1);
|
||||
if (meshProgress.begin("Generate Edges", 1)) {
|
||||
polygonizer.mesh.generateEdges();
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Calculate Vertex Normals", 1)) {
|
||||
calculateVertexNormals(polygonizer.vertices, polygonizer.indices,
|
||||
normals);
|
||||
// {
|
||||
// std::ofstream out("stage3.obj");
|
||||
// saveAttrib(out, "v ", polygonizer.vertices);
|
||||
// saveAttrib(out, "vn ", normals);
|
||||
// saveFaces(out, polygonizer.indices, 2);
|
||||
// }
|
||||
|
||||
if (meshProgress.begin("Generate Adjacent", 1)) {
|
||||
polygonizer.mesh.generateAdjacent();
|
||||
}
|
||||
|
||||
std::cout << "Vertices: " << polygonizer.vertices.size() << std::endl;
|
||||
std::cout << "Triangles: " << polygonizer.indices.size() << std::endl;
|
||||
if (meshProgress.begin("Generate Face Normals", 1)) {
|
||||
polygonizer.mesh.generateFaceNormals();
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Generate Vertex Normals", 1)) {
|
||||
polygonizer.mesh.generateNormals();
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Generate Patched", 1)) {
|
||||
mesh = polygonizer.mesh.createPatched(0.9);
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Save Patched", 1)) {
|
||||
mesh.save("patched.obj");
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Map Textures", 1)) {
|
||||
texturemapper.map(polygonizer.vertices, normals, polygonizer.indices);
|
||||
texturemapper.map(mesh.positions, mesh.normals, mesh.indices);
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Move To Mean", 1)) {
|
||||
moveToMean(texturemapper.vertices);
|
||||
|
||||
// {
|
||||
// std::ofstream out("stage4.obj");
|
||||
// saveAttrib(out, "v ", texturemapper.vertices);
|
||||
// saveAttrib(out, "vn ", texturemapper.normals);
|
||||
// saveAttrib(out, "vt ", texturemapper.texcoords);
|
||||
// saveFaces(out, texturemapper.indices, 3);
|
||||
// }
|
||||
|
||||
mesh.moveToMean();
|
||||
}
|
||||
|
||||
if (meshProgress.begin("Save", 4)) {
|
||||
std::ofstream out("asteroid.obj");
|
||||
saveAttrib(out, "v ", texturemapper.vertices);
|
||||
meshProgress.advance();
|
||||
saveAttrib(out, "vn ", texturemapper.normals);
|
||||
meshProgress.advance();
|
||||
saveAttrib(out, "vt ", texturemapper.texcoords);
|
||||
meshProgress.advance();
|
||||
saveFaces(out, texturemapper.indices, 3);
|
||||
meshProgress.advance();
|
||||
mesh.save("final.obj");
|
||||
}
|
||||
|
||||
meshProgress.finish();
|
||||
@ -310,40 +242,6 @@ void Application::generateAsteroid() {
|
||||
|
||||
}
|
||||
|
||||
bool Application::bindVertexAttrib(GLuint id, GLint loc, GLuint size) {
|
||||
if (loc < 0)
|
||||
return false;
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, id))
|
||||
GLCK(glEnableVertexAttribArray(loc))
|
||||
GLCK(glVertexAttribPointer(loc, // attribute
|
||||
size,// size
|
||||
GL_FLOAT,// type
|
||||
GL_FALSE,// normalized?
|
||||
0,// stride
|
||||
(void* ) 0// array buffer offset
|
||||
))
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::updateVertexArrayObject() {
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
|
||||
if (vPositionLoc >= 0)
|
||||
GLCK(glDisableVertexAttribArray(vPositionLoc))
|
||||
if (vNormalLoc >= 0)
|
||||
GLCK(glDisableVertexAttribArray(vNormalLoc))
|
||||
if (vTexCoordLoc >= 0)
|
||||
GLCK(glDisableVertexAttribArray(vTexCoordLoc))
|
||||
|
||||
bindVertexAttrib(vPositionId, vPositionLoc, 3);
|
||||
bindVertexAttrib(vNormalId, vNormalLoc, 3);
|
||||
bindVertexAttrib(vTexCoordId, vTexCoordLoc, 2);
|
||||
|
||||
GLCK(glBindVertexArray(0))
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Application::loadShader() {
|
||||
if (programId >= 0)
|
||||
GLCK(glDeleteProgram(programId))
|
||||
@ -365,29 +263,6 @@ void Application::loadShader() {
|
||||
GLCK(tMetalicLoc = glGetAttribLocation(programId, "tMetalic"))
|
||||
}
|
||||
|
||||
void Application::uploadMesh() {
|
||||
printf("upload mesh\n");
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vPositionId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, texturemapper.vertices.size() * sizeof(vec3), texturemapper.vertices.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vTexCoordId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, texturemapper.texcoords.size() * sizeof(glm::vec2), texturemapper.texcoords.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vNormalId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, texturemapper.normals.size() * sizeof(vec3), texturemapper.normals.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId))
|
||||
GLCK(
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, texturemapper.indices.size() * sizeof(uvec3), texturemapper.indices.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindVertexArray(0))
|
||||
}
|
||||
|
||||
void Application::uploadTexture() {
|
||||
printf("upload texture\n");
|
||||
glBindTexture(GL_TEXTURE_2D, tAlbedoId);
|
||||
@ -399,11 +274,11 @@ void Application::uploadTexture() {
|
||||
}
|
||||
|
||||
void Application::renderMesh() {
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
GLCK(
|
||||
glDrawElements(GL_TRIANGLES, texturemapper.indices.size() * 3, GL_UNSIGNED_INT, (void* ) 0))
|
||||
GLCK(glBindVertexArray(0))
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LESS);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glMesh.render();
|
||||
}
|
||||
|
||||
void Application::prepareShader() {
|
||||
@ -507,8 +382,8 @@ void Application::prepareShader() {
|
||||
|
||||
void Application::gui() {
|
||||
ImGui::Begin("Mesh Generation");
|
||||
ImGui::Text("Vertices: %lu, Triangles: %lu", polygonizer.vertices.size(),
|
||||
polygonizer.indices.size());
|
||||
ImGui::Text("Vertices: %lu, Triangles: %lu", mesh.positions.size(),
|
||||
mesh.indices.size());
|
||||
//ImGui::InputFloat("Resolution", &resolution);
|
||||
ImGui::SliderFloat("Resolution", &resolution, 0.1, 0.001, "%.3f", 0.1);
|
||||
if (ImGui::SliderFloat("Frequency", &densityFrequency, 1, 10, "%.0f", 1))
|
||||
@ -546,7 +421,7 @@ void Application::gui() {
|
||||
ImGui::Begin("Rendering");
|
||||
if (ImGui::Button("Reload Shader")) {
|
||||
loadShader();
|
||||
updateVertexArrayObject();
|
||||
glMesh.update(vPositionLoc, vNormalLoc, vTexCoordLoc);
|
||||
}
|
||||
if (ImGui::Button("Wireframe"))
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
||||
@ -574,12 +449,9 @@ void Application::run() {
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LESS);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
if (meshUploadRequest) {
|
||||
uploadMesh();
|
||||
glMesh.upload(mesh);
|
||||
meshUploadRequest = false;
|
||||
}
|
||||
if (textureUploadRequest) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "Polygoniser.h"
|
||||
#include "TextureMapper.h"
|
||||
#include "ProgressMonitor.h"
|
||||
#include "gl.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
@ -23,18 +24,14 @@ class Application {
|
||||
float textureSize;
|
||||
TextureMapper texturemapper;
|
||||
|
||||
unsigned int vertexArrayId, vPositionId, vTexCoordId, vNormalId,
|
||||
indexBufferId, programId, tAlbedoId, tNormalId, tRoughnessId,
|
||||
tMetalicId;
|
||||
int vPositionLoc, vTexCoordLoc, vNormalLoc;
|
||||
unsigned int programId, tAlbedoId, tNormalId, tRoughnessId, tMetalicId;
|
||||
|
||||
int MVPloc, Vloc, Mloc, MVloc, LightPosition_worldspaceloc;
|
||||
int vPositionLoc, vTexCoordLoc, vNormalLoc, tAlbedoLoc, tNormalLoc,
|
||||
tRoughnessLoc, tMetalicLoc;
|
||||
int tAlbedoLoc, tNormalLoc, tRoughnessLoc, tMetalicLoc;
|
||||
|
||||
vec3v_t vertices;
|
||||
vec3v_t normals;
|
||||
vec2v_t texcoords;
|
||||
uvec3v_t indices;
|
||||
Mesh mesh;
|
||||
GLMesh glMesh;
|
||||
|
||||
size_t meteoridCount;
|
||||
float meteroidMinRadius;
|
||||
|
278
src/Mesh.cpp
278
src/Mesh.cpp
@ -1,10 +1,12 @@
|
||||
#include "Mesh.h"
|
||||
#include "gl.h"
|
||||
|
||||
#include <glm/geometric.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <stack>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace glm;
|
||||
@ -12,11 +14,59 @@ using namespace glm;
|
||||
static const uvec3::value_type InvalidIndex =
|
||||
numeric_limits<uvec3::value_type>::max();
|
||||
|
||||
void calculateVertexNormals(const vec3v_t &positions, uvec3v_t &indices,
|
||||
vec3v_t &normals) {
|
||||
Mesh::Mesh() {
|
||||
|
||||
}
|
||||
Mesh::Mesh(Mesh &other) {
|
||||
positions = other.positions;
|
||||
normals = other.normals;
|
||||
texcoords = other.texcoords;
|
||||
|
||||
faceNormals = other.faceNormals;
|
||||
indices = other.indices;
|
||||
patches = other.patches;
|
||||
edges = other.edges;
|
||||
|
||||
adjacents = other.adjacents;
|
||||
tangents = other.tangents;
|
||||
bitangents = other.bitangents;
|
||||
}
|
||||
|
||||
Mesh::Mesh(Mesh &&other) {
|
||||
positions.swap(other.positions);
|
||||
normals.swap(other.normals);
|
||||
texcoords.swap(other.texcoords);
|
||||
|
||||
faceNormals.swap(other.faceNormals);
|
||||
indices.swap(other.indices);
|
||||
patches.swap(other.patches);
|
||||
edges.swap(other.edges);
|
||||
|
||||
adjacents.swap(other.adjacents);
|
||||
tangents.swap(other.tangents);
|
||||
bitangents.swap(other.bitangents);
|
||||
}
|
||||
|
||||
Mesh& Mesh::operator=(Mesh &&other) {
|
||||
positions.swap(other.positions);
|
||||
normals.swap(other.normals);
|
||||
texcoords.swap(other.texcoords);
|
||||
|
||||
faceNormals.swap(other.faceNormals);
|
||||
indices.swap(other.indices);
|
||||
patches.swap(other.patches);
|
||||
edges.swap(other.edges);
|
||||
|
||||
adjacents.swap(other.adjacents);
|
||||
tangents.swap(other.tangents);
|
||||
bitangents.swap(other.bitangents);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Mesh::generateNormals() {
|
||||
normals.clear();
|
||||
normals.resize(positions.size(), vec3(0));
|
||||
#pragma omp parallel for
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < indices.size(); i++) {
|
||||
int a = indices[i].x, b = indices[i].y, c = indices[i].z;
|
||||
vec3 faceNormal = normalize(
|
||||
@ -31,99 +81,19 @@ void calculateVertexNormals(const vec3v_t &positions, uvec3v_t &indices,
|
||||
|
||||
}
|
||||
|
||||
void calculateFaceNormals(const vec3v_t &positions, uvec3v_t &indices,
|
||||
vec3v_t &normals) {
|
||||
normals.clear();
|
||||
normals.resize(indices.size(), vec3(uninitialize));
|
||||
#pragma omp parallel for
|
||||
void Mesh::generateFaceNormals() {
|
||||
faceNormals.clear();
|
||||
faceNormals.resize(indices.size(), vec3(uninitialize));
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < indices.size(); i++) {
|
||||
int a = indices[i].x, b = indices[i].y, c = indices[i].z;
|
||||
normals[i] = normalize(
|
||||
faceNormals[i] = normalize(
|
||||
cross(positions[b] - positions[a],
|
||||
positions[c] - positions[a]));
|
||||
}
|
||||
}
|
||||
|
||||
void moveToMean(vec3v_t &positions) {
|
||||
vec3 mean(0);
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
mean += positions[i];
|
||||
}
|
||||
mean *= vec3(1.f / positions.size());
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
positions[i] -= mean;
|
||||
}
|
||||
}
|
||||
|
||||
void smooth(vec3v_t &positions, const uvec3v_t &indices) {
|
||||
vec3v_t cogs(positions.size(), vec3(0.f));
|
||||
vector<int> valence(positions.size(), 0);
|
||||
|
||||
for (size_t iTri = 0; iTri < indices.size(); iTri++) {
|
||||
const uvec3 &idx = indices[iTri];
|
||||
for (size_t iE = 0; iE < 3; iE++) {
|
||||
valence[idx[iE]] += 2;
|
||||
cogs[idx[iE]] += positions[idx[(iE + 1) % 3]];
|
||||
cogs[idx[iE]] += positions[idx[(iE + 2) % 3]];
|
||||
}
|
||||
}
|
||||
/*
|
||||
for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) {
|
||||
cog[0] = cog[1] = cog[2] = valence = 0.0;
|
||||
|
||||
for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) {
|
||||
cog += mesh.point(*vv_it);
|
||||
++valence;
|
||||
}
|
||||
cogs.push_back(cog / valence);
|
||||
}
|
||||
for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end;
|
||||
++v_it, ++cog_it)
|
||||
if (!mesh.is_boundary(*v_it))
|
||||
mesh.set_point(*v_it, *cog_it);
|
||||
|
||||
*/
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
// vtx[i] = vtx[i] * vec3(0.8)
|
||||
// + cogs[i] * vec3(0.2f / valence[i]);
|
||||
positions[i] = cogs[i] * vec3(1.f / valence[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void saveAttrib(std::ostream &out, const char *prefix, vec3v_t &elements) {
|
||||
for (size_t i = 0; i < elements.size(); i++) {
|
||||
out << prefix << elements[i].x << " " << elements[i].y << " "
|
||||
<< elements[i].z << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void saveAttrib(std::ostream &out, const char *prefix, vec2v_t &elements) {
|
||||
for (size_t i = 0; i < elements.size(); i++) {
|
||||
out << prefix << elements[i].x << " " << elements[i].y << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void saveFaces(std::ostream &out, const uvec3v_t &incdices, size_t attribs) {
|
||||
for (size_t i = 0; i < incdices.size(); i++) {
|
||||
out << "f";
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
int v = incdices[i][j] + 1;
|
||||
out << " " << v;
|
||||
if (attribs > 1)
|
||||
out << "/" << v;
|
||||
if (attribs > 2)
|
||||
out << "/" << v;
|
||||
out << " ";
|
||||
}
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void findEdges(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
uvec4v_t &edges) {
|
||||
void Mesh::generateEdges() {
|
||||
edges.clear();
|
||||
map<pair<uint32_t, uint32_t>, uint32_t> edgeMap;
|
||||
for (size_t iTri = 0; iTri < indices.size(); iTri++) {
|
||||
@ -145,8 +115,7 @@ void findEdges(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
}
|
||||
}
|
||||
|
||||
void computeTangentBasis(vec3v_t & positions, vec2v_t & texcoords,
|
||||
vec3v_t & normals, vec3v_t & tangents, vec3v_t & bitangents) {
|
||||
void Mesh::generateTangentBasis() {
|
||||
|
||||
for (size_t i = 0; i < positions.size(); i += 3) {
|
||||
|
||||
@ -185,9 +154,8 @@ void computeTangentBasis(vec3v_t & positions, vec2v_t & texcoords,
|
||||
}
|
||||
}
|
||||
|
||||
void findAdjacent(const uvec4v_t &edges, size_t nFaces, uvec3v_t &adjacents) {
|
||||
printf("findAdjacent: %lu\n", nFaces);
|
||||
adjacents.resize(nFaces, uvec3(InvalidIndex));
|
||||
void Mesh::generateAdjacent() {
|
||||
adjacents.resize(indices.size(), uvec3(InvalidIndex));
|
||||
for (size_t i = 0; i < edges.size(); i++) {
|
||||
int a = edges[i].z;
|
||||
int b = edges[i].w;
|
||||
@ -209,14 +177,93 @@ void findAdjacent(const uvec4v_t &edges, size_t nFaces, uvec3v_t &adjacents) {
|
||||
}
|
||||
}
|
||||
|
||||
void createPatches(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
const vec3v_t &faceNormals, const uvec3v_t &adjacents,
|
||||
vec3v_t &oPositions, uvec3v_t &oIndices, uintv_t &patches,
|
||||
float threshold) {
|
||||
void Mesh::moveToMean() {
|
||||
vec3 mean(0);
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
mean += positions[i];
|
||||
}
|
||||
mean *= vec3(1.f / positions.size());
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
positions[i] -= mean;
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::smooth() {
|
||||
if (positions.size() == 0 || indices.size() == 0)
|
||||
return;
|
||||
|
||||
vec3v_t cogs(positions.size(), vec3(0.f));
|
||||
vector<int> valence(positions.size(), 0);
|
||||
|
||||
for (size_t iTri = 0; iTri < indices.size(); iTri++) {
|
||||
const uvec3 &idx = indices[iTri];
|
||||
for (size_t iE = 0; iE < 3; iE++) {
|
||||
valence[idx[iE]] += 2;
|
||||
cogs[idx[iE]] += positions[idx[(iE + 1) % 3]];
|
||||
cogs[idx[iE]] += positions[idx[(iE + 2) % 3]];
|
||||
}
|
||||
}
|
||||
/*
|
||||
for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) {
|
||||
cog[0] = cog[1] = cog[2] = valence = 0.0;
|
||||
|
||||
for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) {
|
||||
cog += mesh.point(*vv_it);
|
||||
++valence;
|
||||
}
|
||||
cogs.push_back(cog / valence);
|
||||
}
|
||||
for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end;
|
||||
++v_it, ++cog_it)
|
||||
if (!mesh.is_boundary(*v_it))
|
||||
mesh.set_point(*v_it, *cog_it);
|
||||
|
||||
*/
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < positions.size(); i++) {
|
||||
// vtx[i] = vtx[i] * vec3(0.8)
|
||||
// + cogs[i] * vec3(0.2f / valence[i]);
|
||||
positions[i] = cogs[i] * vec3(1.f / valence[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::saveAttrib(std::ostream &out, const char *prefix,
|
||||
const vec3v_t &elements) const {
|
||||
for (size_t i = 0; i < elements.size(); i++) {
|
||||
out << prefix << elements[i].x << " " << elements[i].y << " "
|
||||
<< elements[i].z << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::saveAttrib(std::ostream &out, const char *prefix,
|
||||
const vec2v_t &elements) const {
|
||||
for (size_t i = 0; i < elements.size(); i++) {
|
||||
out << prefix << elements[i].x << " " << elements[i].y << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::saveFaces(std::ostream &out, const uvec3v_t &incdices,
|
||||
size_t attribs) const {
|
||||
for (size_t i = 0; i < incdices.size(); i++) {
|
||||
out << "f";
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
int v = incdices[i][j] + 1;
|
||||
out << " " << v;
|
||||
if (attribs > 1)
|
||||
out << "/" << v;
|
||||
if (attribs > 2)
|
||||
out << "/" << v;
|
||||
out << " ";
|
||||
}
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Mesh Mesh::createPatched(float threshold) const {
|
||||
assert(adjacents.size() == indices.size());
|
||||
patches.clear();
|
||||
oPositions.clear();
|
||||
oIndices.clear();
|
||||
Mesh mesh;
|
||||
vector<bool> processed(indices.size(), false);
|
||||
|
||||
for (size_t i = 0; i < indices.size(); i++) {
|
||||
@ -251,18 +298,39 @@ void createPatches(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
for (size_t k = 0; k < 3; k++) {
|
||||
auto it = vertexMap.find(idx[k]);
|
||||
if (it == vertexMap.end()) {
|
||||
uint32_t newIdx = oPositions.size();
|
||||
oPositions.push_back(positions[idx[k]]);
|
||||
uint32_t newIdx = mesh.positions.size();
|
||||
mesh.positions.push_back(positions[idx[k]]);
|
||||
if (normals.size())
|
||||
mesh.normals.push_back(normals[idx[k]]);
|
||||
vertexMap[idx[k]] = newIdx;
|
||||
idx[k] = newIdx;
|
||||
} else {
|
||||
idx[k] = it->second;
|
||||
}
|
||||
}
|
||||
oIndices.push_back(idx);
|
||||
mesh.indices.push_back(idx);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void Mesh::save(std::ostream &out) const {
|
||||
size_t nAttribs = 1;
|
||||
saveAttrib(out, "v ", positions);
|
||||
if (normals.size()) {
|
||||
nAttribs++;
|
||||
saveAttrib(out, "vn ", normals);
|
||||
}
|
||||
if (texcoords.size()) {
|
||||
nAttribs++;
|
||||
saveAttrib(out, "vt ", texcoords);
|
||||
}
|
||||
saveFaces(out, indices, nAttribs);
|
||||
}
|
||||
|
||||
void Mesh::save(std::string filename) const {
|
||||
std::ofstream out(filename.c_str());
|
||||
save(out);
|
||||
}
|
||||
|
||||
|
54
src/Mesh.h
54
src/Mesh.h
@ -5,31 +5,43 @@
|
||||
#include <vector>
|
||||
#include <ostream>
|
||||
|
||||
void calculateVertexNormals(const vec3v_t &positions, uvec3v_t &indices,
|
||||
vec3v_t &normals);
|
||||
void calculateFaceNormals(const vec3v_t &positions, uvec3v_t &indices,
|
||||
vec3v_t &normals);
|
||||
void findEdges(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
uvec4v_t &edges);
|
||||
//void createPatches(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
// const uvec4v_t &edges, vec3v_t &oPositions, uvec3v_t &oIndices,
|
||||
// uvec4v_t &oEdges, uintv_t &patches, float threshold);
|
||||
struct Mesh {
|
||||
vec3v_t positions;
|
||||
vec3v_t normals;
|
||||
vec2v_t texcoords;
|
||||
|
||||
void moveToMean(vec3v_t &positions);
|
||||
vec3v_t faceNormals;
|
||||
uvec3v_t indices;
|
||||
uintv_t patches;
|
||||
uvec4v_t edges;
|
||||
uvec3v_t adjacents;
|
||||
vec3v_t tangents;
|
||||
vec3v_t bitangents;
|
||||
|
||||
void smooth(vec3v_t &positions, const uvec3v_t &indices);
|
||||
Mesh();
|
||||
Mesh(Mesh &other);
|
||||
Mesh(Mesh &&other);
|
||||
|
||||
void saveAttrib(std::ostream &out, const char *prefix, vec3v_t &elements);
|
||||
Mesh& operator=(Mesh&&);
|
||||
|
||||
void saveAttrib(std::ostream &out, const char *prefix, vec2v_t &elements);
|
||||
void generateNormals();
|
||||
void generateTangentBasis();
|
||||
|
||||
void saveFaces(std::ostream &out, const uvec3v_t &indicess, size_t attributes);
|
||||
void generateAdjacent();
|
||||
void generateFaceNormals();
|
||||
void generateEdges();
|
||||
|
||||
void findAdjacent(const uvec4v_t &edges, size_t nFaces, uvec3v_t &adjacents);
|
||||
void moveToMean();
|
||||
void smooth();
|
||||
|
||||
void computeTangentBasis(vec3v_t & vertices, vec2v_t & texcoords,
|
||||
vec3v_t & normals, vec3v_t & tangents, vec3v_t & bitangents);
|
||||
void createPatches(const vec3v_t &positions, const uvec3v_t &indices,
|
||||
const vec3v_t &faceNormals, const uvec3v_t &adjacents,
|
||||
vec3v_t &oPositions, uvec3v_t &oIndices, uintv_t &patches,
|
||||
float threshold);
|
||||
Mesh createPatched(float threshold) const;
|
||||
|
||||
void saveAttrib(std::ostream &out, const char *prefix,
|
||||
const vec3v_t &elements) const;
|
||||
void saveAttrib(std::ostream &out, const char *prefix,
|
||||
const vec2v_t &elements) const;
|
||||
void saveFaces(std::ostream &out, const uvec3v_t &indicess,
|
||||
size_t attributes) const;
|
||||
void save(std::ostream &out) const;
|
||||
void save(std::string filename) const;
|
||||
};
|
||||
|
@ -114,7 +114,7 @@ void Polygonizer::polygonize(const vec3 &lower, const vec3 &upper,
|
||||
for (int iTri = 0; iTri < nTris; iTri++) {
|
||||
vec3 *ps = tris[iTri].p;
|
||||
|
||||
// skip degenerate vertices
|
||||
// skip degenerate positions
|
||||
if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
|
||||
|| all(epsilonEqual(ps[0], ps[2], 1e-8f))
|
||||
|| all(epsilonEqual(ps[2], ps[1], 1e-8f)))
|
||||
@ -128,8 +128,8 @@ void Polygonizer::polygonize(const vec3 &lower, const vec3 &upper,
|
||||
if (vit != vertexLookup.end()) {
|
||||
idc[k] = vit->second;
|
||||
} else {
|
||||
idc[k] = vertices.size();
|
||||
vertices.push_back(p);
|
||||
idc[k] = positions.size();
|
||||
positions.push_back(p);
|
||||
vertexLookup[p] = idc[k];
|
||||
}
|
||||
}
|
||||
@ -142,7 +142,7 @@ void Polygonizer::polygonize(const vec3 &lower, const vec3 &upper,
|
||||
|
||||
void Polygonizer::polygonize(float resolution) {
|
||||
vertexLookup.clear();
|
||||
vertices.clear();
|
||||
positions.clear();
|
||||
indices.clear();
|
||||
|
||||
const float isocellSize = 1.f / isocells;
|
||||
@ -187,11 +187,11 @@ void Polygonizer::calculateLayer(std::vector<float> &layer, float resolution,
|
||||
|
||||
void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
vertexLookup.clear();
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
mesh.positions.clear();
|
||||
mesh.indices.clear();
|
||||
|
||||
vertices.reserve(pow(1. / resolution, 2) * 8);
|
||||
indices.reserve(pow(1. / resolution, 2) * 8);
|
||||
mesh.positions.reserve(pow(1. / resolution, 2) * 8);
|
||||
mesh.indices.reserve(pow(1. / resolution, 2) * 8);
|
||||
size_t nSteps = 1.f / resolution;
|
||||
size_t nSamples = nSteps + 1;
|
||||
|
||||
@ -258,7 +258,7 @@ void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
for (int iTri = 0; iTri < nTris; iTri++) {
|
||||
vec3 *ps = tris[iTri].p;
|
||||
|
||||
// skip degenerate vertices
|
||||
// skip degenerate positions
|
||||
if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
|
||||
|| all(epsilonEqual(ps[0], ps[2], 1e-8f))
|
||||
|| all(epsilonEqual(ps[2], ps[1], 1e-8f)))
|
||||
@ -272,12 +272,12 @@ void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
if (vit != vertexLookup.end()) {
|
||||
idc[k] = vit->second;
|
||||
} else {
|
||||
idc[k] = vertices.size();
|
||||
vertices.push_back(p);
|
||||
idc[k] = mesh.positions.size();
|
||||
mesh.positions.push_back(p);
|
||||
vertexLookup[p] = idc[k];
|
||||
}
|
||||
}
|
||||
indices.push_back(idc);
|
||||
mesh.indices.push_back(idc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -287,7 +287,7 @@ void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
|
||||
//void Polygonizer::polygonize(float resolution) {
|
||||
// vertexLookup.clear();
|
||||
// vertices.clear();
|
||||
// positions.clear();
|
||||
// indices.clear();
|
||||
//
|
||||
// size_t nSteps = 1.f / resolution;
|
||||
@ -329,7 +329,7 @@ void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
// for (int iTri = 0; iTri < nTris; iTri++) {
|
||||
// vec3 *ps = tris[iTri].p;
|
||||
//
|
||||
// // skip degenerate vertices
|
||||
// // skip degenerate positions
|
||||
// if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
|
||||
// || all(epsilonEqual(ps[0], ps[2], 1e-8f))
|
||||
// || all(epsilonEqual(ps[2], ps[1], 1e-8f)))
|
||||
@ -343,8 +343,8 @@ void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
|
||||
// if (vit != vertexLookup.end()) {
|
||||
// idc[k] = vit->second;
|
||||
// } else {
|
||||
// idc[k] = vertices.size();
|
||||
// vertices.push_back(p);
|
||||
// idc[k] = positions.size();
|
||||
// positions.push_back(p);
|
||||
// vertexLookup[p] = idc[k];
|
||||
// }
|
||||
// }
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "types.h"
|
||||
#include "Mesh.h"
|
||||
#include "Density.h"
|
||||
#include "ProgressMonitor.h"
|
||||
|
||||
@ -47,8 +47,7 @@ class Polygonizer {
|
||||
void calculateLayer(std::vector<float> &layer, float resolution, size_t nSamples, float z);
|
||||
|
||||
public:
|
||||
vec3v_t vertices;
|
||||
uvec3v_t indices;
|
||||
Mesh mesh;
|
||||
std::map<glm::vec3, int, CompareVec3> vertexLookup;
|
||||
|
||||
|
||||
|
@ -6,7 +6,8 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
SkyBox::SkyBox() {
|
||||
SkyBox::SkyBox() :
|
||||
textureId(GL_INVALID_INDEX), programId(GL_INVALID_INDEX) {
|
||||
glGenTextures(1, &textureId);
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ void SkyBox::Render() {
|
||||
|
||||
glCullFace(GL_FRONT);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
/*
|
||||
/*
|
||||
Pipeline p;
|
||||
p.Scale(20.0f, 20.0f, 20.0f);
|
||||
p.Rotate(0.0f, 0.0f, 0.0f);
|
||||
@ -67,7 +68,7 @@ void SkyBox::Render() {
|
||||
m_pSkyboxTechnique->SetWVP(p.GetWVPTrans());
|
||||
m_pCubemapTex->Bind(GL_TEXTURE0);
|
||||
m_pMesh->Render();
|
||||
*/
|
||||
*/
|
||||
glCullFace(OldCullFaceMode);
|
||||
glDepthFunc(OldDepthFuncMode);
|
||||
}
|
||||
|
121
src/gl.h
Normal file
121
src/gl.h
Normal file
@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
static bool glck(const char* call) {
|
||||
int err = glGetError();
|
||||
if (err == 0) {
|
||||
return true;
|
||||
} else {
|
||||
switch (err) {
|
||||
case GL_INVALID_VALUE:
|
||||
printf("GL_INVALID_VALUE: %s\n", call);
|
||||
break;
|
||||
case GL_INVALID_ENUM:
|
||||
printf("GL_INVALID_ENUM: %s\n", call);
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
printf("GL_INVALID_OPERATION: %s\n", call);
|
||||
break;
|
||||
default:
|
||||
printf("UNKNOWN Error: %s\n", call);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define GLCK(a) {a; assert(glck(#a));}
|
||||
|
||||
class GLMesh {
|
||||
unsigned int vertexArrayId, vPositionId, vTexCoordId, vNormalId,
|
||||
indexBufferId;
|
||||
unsigned int indexCount;
|
||||
|
||||
public:
|
||||
|
||||
GLMesh() :
|
||||
vertexArrayId(0), vPositionId(0), vTexCoordId(0), vNormalId(0), indexBufferId(
|
||||
0), indexCount(0) {
|
||||
|
||||
}
|
||||
|
||||
~GLMesh() {
|
||||
GLCK(glDeleteBuffers(1, &vPositionId))
|
||||
GLCK(glDeleteBuffers(1, &vTexCoordId))
|
||||
GLCK(glDeleteBuffers(1, &vNormalId))
|
||||
GLCK(glDeleteBuffers(1, &indexBufferId))
|
||||
GLCK(glDeleteVertexArrays(1, &vertexArrayId))
|
||||
}
|
||||
|
||||
void create() {
|
||||
GLCK(glGenVertexArrays(1, &vertexArrayId))
|
||||
GLCK(glGenBuffers(1, &vPositionId))
|
||||
GLCK(glGenBuffers(1, &vTexCoordId))
|
||||
GLCK(glGenBuffers(1, &vNormalId))
|
||||
GLCK(glGenBuffers(1, &indexBufferId))
|
||||
}
|
||||
|
||||
void upload(const Mesh &mesh) {
|
||||
printf("upload mesh\n");
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vPositionId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.positions.size() * sizeof(glm::vec3), mesh.positions.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vTexCoordId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.texcoords.size() * sizeof(glm::vec2), mesh.texcoords.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, vNormalId))
|
||||
GLCK(
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId))
|
||||
GLCK(
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices.size() * sizeof(glm::uvec3), mesh.indices.data(), GL_STATIC_DRAW))
|
||||
|
||||
GLCK(glBindVertexArray(0))
|
||||
|
||||
indexCount = mesh.indices.size() * 3;
|
||||
}
|
||||
|
||||
void render() {
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
GLCK(
|
||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void* ) 0))
|
||||
GLCK(glBindVertexArray(0))
|
||||
|
||||
}
|
||||
|
||||
bool bindVertexAttrib(GLuint id, GLint loc, GLuint size) const {
|
||||
if (loc < 0) {
|
||||
GLCK(glDisableVertexAttribArray(loc))
|
||||
} else {
|
||||
GLCK(glBindBuffer(GL_ARRAY_BUFFER, id))
|
||||
GLCK(glEnableVertexAttribArray(loc))
|
||||
GLCK(glVertexAttribPointer(loc, // attribute
|
||||
size,// size
|
||||
GL_FLOAT,// type
|
||||
GL_FALSE,// normalized?
|
||||
0,// stride
|
||||
(void* ) 0// array buffer offset
|
||||
))
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void update(int vPositionLoc, int vNormalLoc, int vTexCoordLoc) const {
|
||||
GLCK(glBindVertexArray(vertexArrayId))
|
||||
|
||||
bindVertexAttrib(vPositionId, vPositionLoc, 3);
|
||||
bindVertexAttrib(vNormalId, vNormalLoc, 3);
|
||||
bindVertexAttrib(vTexCoordId, vTexCoordLoc, 2);
|
||||
|
||||
GLCK(glBindVertexArray(0))
|
||||
}
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "AlignedAllocator.h"
|
||||
#include <glm/vec4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <vector>
|
||||
|
||||
typedef glm::tvec4<uint8_t> ucvec4_t;
|
||||
|
Loading…
Reference in New Issue
Block a user