Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ set(SOURCE_FILES
${FUNGT_BASE_DIR}/Geometries/square.cpp
${FUNGT_BASE_DIR}/Geometries/sphere.cpp
${FUNGT_BASE_DIR}/Geometries/box.cpp
${FUNGT_BASE_DIR}/Geometries/torus.cpp
${FUNGT_BASE_DIR}/Model/model.cpp
${FUNGT_BASE_DIR}/Helpers/helpers.cpp
${FUNGT_BASE_DIR}/Animation/animation.cpp
Expand Down
211 changes: 211 additions & 0 deletions Geometries/primitives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,215 @@ void Primitive::InitGraphics()
if (this->getNumOfIndices() > 0) {
m_vi.unbind();
}
buildTopology();
}
void Primitive::clearSelection()
{
// Clear all vertex selection flags
std::fill(m_vertexSelected.begin(), m_vertexSelected.end(), false);

// Clear all face selection flags
for (auto& face : m_faces) {
face.selected = false;
}
}

void Primitive::selectVertex(uint32_t idx, bool additive)
{
if (idx >= m_vertex.size()) {
std::cerr << "selectVertex: index " << idx << " out of range" << std::endl;
return;
}

if (!additive) {
clearSelection();
}

m_vertexSelected[idx] = true;
}

void Primitive::selectFace(uint32_t idx, bool additive)
{
if (idx >= m_faces.size()) {
std::cerr << "selectFace: index " << idx << " out of range" << std::endl;
return;
}

if (!additive) {
clearSelection();
}

m_faces[idx].selected = true;
}
void Primitive::updateGPUBuffers()
{
if (m_vertex.empty()) {
return;
}

// Bind VBO and re-upload vertex data
m_vb.bind();
m_vb.bufferData(this->getVertices(), this->sizeOfVertices());
m_vb.unbind();

// Bind EBO and re-upload index data (if we have indices)
if (!m_index.empty()) {
m_vi.bind();
m_vi.indexData(this->getIndices(), this->sizeOfIndices());
m_vi.unbind();
}
}
void Primitive::recalculateNormals()
{
if (m_vertex.empty() || m_index.empty()) {
return;
}

// Zero out all vertex normals
for (auto& v : m_vertex) {
v.normal = glm::vec3(0.0f);
}

// Accumulate face normals into vertices (area-weighted)
for (size_t i = 0; i < m_index.size(); i += 3) {
uint32_t i0 = m_index[i];
uint32_t i1 = m_index[i + 1];
uint32_t i2 = m_index[i + 2];

glm::vec3 v0 = m_vertex[i0].position;
glm::vec3 v1 = m_vertex[i1].position;
glm::vec3 v2 = m_vertex[i2].position;

// Face normal (cross product gives area-weighted normal)
glm::vec3 edge1 = v1 - v0;
glm::vec3 edge2 = v2 - v0;
glm::vec3 faceNormal = glm::cross(edge1, edge2);

// Accumulate into vertex normals (don't normalize yet)
m_vertex[i0].normal += faceNormal;
m_vertex[i1].normal += faceNormal;
m_vertex[i2].normal += faceNormal;
}

// Normalize all vertex normals
for (auto& v : m_vertex) {
if (glm::length(v.normal) > 0.0001f) {
v.normal = glm::normalize(v.normal);
}
}
}
void Primitive::buildTopology()
{
// Clear existing topology
m_halfEdges.clear();
m_faces.clear();
m_vertexSelected.clear();

if (m_index.empty() || m_vertex.empty()) {
m_topologyDirty = false;
return;
}

// Initialize vertex selection flags
m_vertexSelected.resize(m_vertex.size(), false);

// Count triangles
size_t numTriangles = m_index.size() / 3;
m_faces.resize(numTriangles);

// Allocate space for half-edges (3 per triangle)
m_halfEdges.resize(numTriangles * 3);

// Build half-edges from triangles
for (size_t faceIdx = 0; faceIdx < numTriangles; ++faceIdx) {
uint32_t i0 = m_index[faceIdx * 3 + 0];
uint32_t i1 = m_index[faceIdx * 3 + 1];
uint32_t i2 = m_index[faceIdx * 3 + 2];

// Three half-edges for this triangle
uint32_t he0 = faceIdx * 3 + 0;
uint32_t he1 = faceIdx * 3 + 1;
uint32_t he2 = faceIdx * 3 + 2;

// Half-edge 0: i0 -> i1
m_halfEdges[he0].vertex = i1;
m_halfEdges[he0].next = he1;
m_halfEdges[he0].face = faceIdx;
m_halfEdges[he0].twin = UINT32_MAX; // Will find twins next

// Half-edge 1: i1 -> i2
m_halfEdges[he1].vertex = i2;
m_halfEdges[he1].next = he2;
m_halfEdges[he1].face = faceIdx;
m_halfEdges[he1].twin = UINT32_MAX;

// Half-edge 2: i2 -> i0
m_halfEdges[he2].vertex = i0;
m_halfEdges[he2].next = he0;
m_halfEdges[he2].face = faceIdx;
m_halfEdges[he2].twin = UINT32_MAX;

// Store one half-edge reference in the face
m_faces[faceIdx].halfEdge = he0;
m_faces[faceIdx].selected = false;
}

// Find twin half-edges
// For each half-edge, search for its opposite
for (size_t i = 0; i < m_halfEdges.size(); ++i) {
if (m_halfEdges[i].twin != UINT32_MAX) {
continue; // Already has a twin
}

// This half-edge goes: vertexStart -> vertexEnd
// Find the triangle index and position within triangle for edge i
uint32_t faceIdx = i / 3;
uint32_t edgeInFace = i % 3;

// Get the starting vertex of this half-edge
uint32_t vertexStart;
if (edgeInFace == 0) {
vertexStart = m_index[faceIdx * 3 + 0];
}
else if (edgeInFace == 1) {
vertexStart = m_index[faceIdx * 3 + 1];
}
else {
vertexStart = m_index[faceIdx * 3 + 2];
}

uint32_t vertexEnd = m_halfEdges[i].vertex;

// Search for twin: an edge going vertexEnd -> vertexStart
for (size_t j = i + 1; j < m_halfEdges.size(); ++j) {
uint32_t otherFaceIdx = j / 3;
uint32_t otherEdgeInFace = j % 3;

uint32_t otherVertexStart;
if (otherEdgeInFace == 0) {
otherVertexStart = m_index[otherFaceIdx * 3 + 0];
}
else if (otherEdgeInFace == 1) {
otherVertexStart = m_index[otherFaceIdx * 3 + 1];
}
else {
otherVertexStart = m_index[otherFaceIdx * 3 + 2];
}

uint32_t otherVertexEnd = m_halfEdges[j].vertex;

// Check if this is the twin
if (vertexStart == otherVertexEnd && vertexEnd == otherVertexStart) {
m_halfEdges[i].twin = j;
m_halfEdges[j].twin = i;
break;
}
}
}

m_topologyDirty = false;

std::cout << "Topology built: " << m_vertex.size() << " vertices, "
<< m_faces.size() << " faces, "
<< m_halfEdges.size() << " half-edges" << std::endl;
}
38 changes: 37 additions & 1 deletion Geometries/primitives.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ class Primitive {
std::vector<PrimitiveVertex> m_vertex;
std::vector<GLuint> m_index;

//Topology data for editing
struct HalfEdge {
uint32_t vertex; // Which vertex this edge points TO
uint32_t twin; // Opposite half-edge (UINT32_MAX if boundary)
uint32_t next; // Next half-edge in the same face
uint32_t face; // Which face this edge belongs to
};

struct EditFace {
uint32_t halfEdge; // One (any) half-edge of this face
bool selected; // Is this face selected?
};

std::vector<HalfEdge> m_halfEdges;
std::vector<EditFace> m_faces;
std::vector<bool> m_vertexSelected; // Per-vertex selection flags

bool m_topologyDirty; // Does topology need rebuilding?

public:
VertexArrayObject m_vao;
VertexBuffer m_vb;
Expand All @@ -49,7 +68,24 @@ class Primitive {
const std::vector<unsigned int>& getIndices() const;
// Geometry-specific virtuals
virtual void setData() = 0;

// NEW: Topology management
void buildTopology();
bool isTopologyBuilt() const { return !m_topologyDirty; }

// Selection
void clearSelection();
void selectVertex(uint32_t idx, bool additive = false);
void selectFace(uint32_t idx, bool additive = false);
const std::vector<bool>& getVertexSelection() const { return m_vertexSelected; }
const std::vector<EditFace>& getFaces() const { return m_faces; }
//Drawing wireframe and vertices for edit mode: all primitives will use the same method, so we can implement it here in the base class
void drawWireframe();
void drawVertices();
// GPU sync
void updateGPUBuffers();

// Geometry operations
void recalculateNormals();
// Graphics initialization
void setTexture(const std::string &pathToTexture);
void InitGraphics();
Expand Down
40 changes: 40 additions & 0 deletions Geometries/pyramid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,43 @@ void Pyramid::setData()

this->set(vertices,nOfvertices);
}
void Primitive::drawWireframe()
{
if (m_vertex.empty()) return;

// Save current polygon mode
GLint polygonMode[2];
glGetIntegerv(GL_POLYGON_MODE, polygonMode);

// Draw as wireframe
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(2.0f);

m_vao.bind();

if (!m_index.empty()) {
glDrawElements(GL_TRIANGLES, m_index.size(), GL_UNSIGNED_INT, 0);
}
else {
glDrawArrays(GL_TRIANGLES, 0, m_vertex.size());
}

m_vao.unbind();

// Restore polygon mode
glPolygonMode(GL_FRONT_AND_BACK, polygonMode[0]);
glLineWidth(1.0f);
}

void Primitive::drawVertices()
{
if (m_vertex.empty()) return;

glPointSize(8.0f);

m_vao.bind();
glDrawArrays(GL_POINTS, 0, m_vertex.size());
m_vao.unbind();

glPointSize(1.0f);
}
Loading