Draw a tetrahedron on OpenGL with only one call

Hi,

I want to display tetrahedrons volumes with OpenGL

I think use this function for to implement it

void glTetra3fv( float *v0, float *v1, float *v2, float *v3 ) 
{
    glBegin(GL_TRIANGLES);

        glVertex3fv(v0);   // face A (face entry)
        glVertex3fv(v1);
        glVertex3fv(v2);
 
        glVertex3fv(v3);   // face B
        glVertex3fv(v0);
        glVertex3fv(v1);

        glVertex3fv(v3);   // face C
        glVertex3fv(v1);
        glVertex3fv(v2);
 
        glVertex3fv(v3);   // face D
        glVertex3fv(v2);
        glVertex3fv(v0);

    glEnd();
}

Think you that it is better to use the glBegin() / glEnd() calls inside or outside this function ?

This is for to call this fonction something like this :
(4 tetrahedrons = three tetrahedrons glued to one first)

// glBegin(GL_TRIANGLES);

    glTetra3fv( &v1 , &v2 , &v3 , &v4 );
    glTetra3fv( &v1 , &v2 , &v4 , &v5 );
    glTetra3fv( &v2 , &v3 , &v4 , &v6 );
    glTetra3fv( &v3 , &v1 , &v4 , &v7 );

// glEnd();

or a very little more complex code like this for other example :
(one internal black tetrahedron glued by red, green, blue and gray tetrahedrons on his faces)

// glBegin(GL_TRIANGLES);

    // a black tetrahedron in the interior
    glColor3fv( 0.0f, 0.0f, 0.0f );
    glTetra3fv( &v1 , &v2 , &v3 , &v4 );

    // a red tetrahedron glued to the 1th extern face of this tetrahedron
    glColor3fv(1.0f,0.0f,0.0f);
    glTetra3fv( &v1 , &v2 , &v4 , &v5 );

    // a green tetrahedron glued to the 2th extern face of this tetrahedron
    glColor3fv(0.0f,1.0f,0.0f);
    glTetra3fv( &v2 , &v3 , &v4 , &v6 );

    // a blue tetrahedron glued to the 3th extern face of this tetrahedron
    glColor3fv(0.0f,0.0f,1.0f);
    glTetra3fv( &v3 , &v1 , &v4 , &v7 );

    // a gray tetrahedron glued to the entry face of this tetrahedron
    glColor3fv( 0.5f, 0.5f, 0.5f );
    glTetra3fv( &v1 , &v2 , &v3 , &v8 );

// glEnd();

I have used the wonderfull IA agent Le Chat from Mistral for to give you a view of a tetrahedron glued with a blue, green and red tetrahedrons on his externals faces.
(the entry face of the initial tetrahedron is display in gray)

Note that this use now VAO et VBO instead glBegin(GL_TRIANGLES) / numerous glVertex3fv() / glEnd()
(this have loose some genericity compared to the glTetra3fv() way but I think correct this after a good studing / IA analyze of this code for to see where/how correct this)

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>

// Variables pour la gestion de la souris et de la fenêtre
float lastX = 400.0f, lastY = 300.0f;
bool firstMouse = true;
bool mousePressed = false;
float fov = 45.0f;
float sensitivity = 0.5f;
float rotationAngleX = 30.0f;
float rotationAngleY = 45.0f;
int windowWidth = 800;
int windowHeight = 600;

// Structure pour stocker les sommets d'un tétraèdre
struct Tetrahedron {
    std::vector<glm::vec3> vertices;
    std::vector<glm::vec3> colors;
    std::vector<GLuint> indices;
};

// Fonction pour ajouter un tétraèdre à un ensemble de sommets et indices
void addTetrahedron(std::vector<glm::vec3>& vertices, std::vector<glm::vec3>& colors, std::vector<GLuint>& indices,
                    const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3, const glm::vec3& v4,
                    const glm::vec3& color) {
    GLuint baseIndex = vertices.size();

    vertices.push_back(v1);
    vertices.push_back(v2);
    vertices.push_back(v3);
    vertices.push_back(v4);

    colors.push_back(color);
    colors.push_back(color);
    colors.push_back(color);
    colors.push_back(color);

    // Ajouter les indices pour les 4 faces du tétraèdre
    indices.push_back(baseIndex + 0); indices.push_back(baseIndex + 1); indices.push_back(baseIndex + 2); // Face 1
    indices.push_back(baseIndex + 0); indices.push_back(baseIndex + 1); indices.push_back(baseIndex + 3); // Face 2
    indices.push_back(baseIndex + 0); indices.push_back(baseIndex + 2); indices.push_back(baseIndex + 3); // Face 3
    indices.push_back(baseIndex + 1); indices.push_back(baseIndex + 2); indices.push_back(baseIndex + 3); // Face 4
}

// Vertex shader (GLSL)
const char* vertexShaderSource = R"(
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    out vec3 ourColor;
    void main() {
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        ourColor = aColor;
    }
)";

// Fragment shader (GLSL)
const char* fragmentShaderSource = R"(
    #version 330 core
    in vec3 ourColor;
    out vec4 FragColor;
    void main() {
        FragColor = vec4(ourColor, 1.0);
    }
)";

// Fonction pour compiler un shader
GLuint compileShader(GLenum type, const char* source) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);

    int success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        std::cerr << "Erreur de compilation du shader : " << infoLog << std::endl;
    }
    return shader;
}

// Fonction pour créer un programme de shaders
GLuint createShaderProgram() {
    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    int success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cerr << "Erreur de linkage du programme : " << infoLog << std::endl;
    }

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    return program;
}

// Callback pour le mouvement de la souris
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    if (!mousePressed) return;

    if (firstMouse) {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    xoffset *= sensitivity;
    yoffset *= sensitivity;

    rotationAngleY += xoffset;
    rotationAngleX += yoffset;
}

// Callback pour le clic de la souris
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) {
    if (button == GLFW_MOUSE_BUTTON_LEFT) {
        if (action == GLFW_PRESS) {
            mousePressed = true;
            firstMouse = true;
        } else if (action == GLFW_RELEASE) {
            mousePressed = false;
        }
    }
}

// Callback pour la molette de la souris
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
    if (fov >= 1.0f && fov <= 45.0f)
        fov -= yoffset;
    if (fov <= 1.0f)
        fov = 1.0f;
    if (fov >= 45.0f)
        fov = 45.0f;
}

// Callback pour le redimensionnement de la fenêtre
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    windowWidth = width;
    windowHeight = height;
    glViewport(0, 0, width, height);
}

int main() {
    // Initialisation de GLFW
    if (!glfwInit()) {
        std::cerr << "Échec de l'initialisation de GLFW" << std::endl;
        return -1;
    }

    // Configuration de la fenêtre
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Création de la fenêtre
    GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "Tétraèdres avec arêtes épaisses - YANNOO", NULL, NULL);
    if (!window) {
        std::cerr << "Échec de la création de la fenêtre" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    // Configuration des callbacks
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetMouseButtonCallback(window, mouse_button_callback);
    glfwSetScrollCallback(window, scroll_callback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);

    // Initialisation de GLEW
    if (glewInit() != GLEW_OK) {
        std::cerr << "Échec de l'initialisation de GLEW" << std::endl;
        return -1;
    }

    // Activation du test de profondeur
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    // Création du programme de shaders
    GLuint shaderProgram = createShaderProgram();

    // Définition des sommets du tétraèdre principal
    glm::vec3 v1(-0.5f, -0.5f, 0.0f);
    glm::vec3 v2(0.5f, -0.5f, 0.0f);
    glm::vec3 v3(0.0f, 0.5f, 0.0f);
    glm::vec3 v4(0.0f, 0.0f, 0.707f);

    // Définition des sommets des sous-tétraèdres
    glm::vec3 v5 = (v1 + v2 + v4) / 2.0f + glm::normalize(glm::cross(v2 - v1, v4 - v1)) * 0.2f; // Sommet pour le tétraèdre rouge
    glm::vec3 v6 = (v2 + v3 + v4) / 2.0f + glm::normalize(glm::cross(v3 - v2, v4 - v2)) * 0.2f; // Sommet pour le tétraèdre vert
    glm::vec3 v7 = (v3 + v1 + v4) / 2.0f + glm::normalize(glm::cross(v1 - v3, v4 - v3)) * 0.2f; // Sommet pour le tétraèdre bleu
    glm::vec3 v8 = (v1 + v2 + v3) / 2.0f + glm::normalize(glm::cross(v2 - v1, v3 - v1)) * 0.2f; // Sommet pour le tétraèdre gris

    // Vecteurs pour stocker les sommets, couleurs et indices
    std::vector<glm::vec3> vertices;
    std::vector<glm::vec3> colors;
    std::vector<GLuint> indices;

    // Ajout du tétraèdre principal
    addTetrahedron(vertices, colors, indices, v1, v2, v3, v4, glm::vec3(0.5f, 0.5f, 0.5f));

    // Ajout des sous-tétraèdres
    addTetrahedron(vertices, colors, indices, v1, v2, v4, v5, glm::vec3(1.0f, 0.0f, 0.0f)); // Rouge
    addTetrahedron(vertices, colors, indices, v2, v3, v4, v6, glm::vec3(0.0f, 1.0f, 0.0f)); // Vert
    addTetrahedron(vertices, colors, indices, v3, v1, v4, v7, glm::vec3(0.0f, 0.0f, 1.0f)); // Bleu
    addTetrahedron(vertices, colors, indices, v1, v2, v3, v8, glm::vec3(0.5f, 0.5f, 0.5f)); // Gris

    // Création des buffers pour les faces
    GLuint VBO, VAO, EBO, colorVBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);
    glGenBuffers(1, &colorVBO);

    // Binding du VAO
    glBindVertexArray(VAO);

    // Binding et remplissage du VBO pour les sommets
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(0);

    // Binding et remplissage du VBO pour les couleurs
    glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
    glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec3), &colors[0], GL_STATIC_DRAW);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(1);

    // Binding et remplissage de l'EBO
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW);

    // Création des buffers pour les arêtes
    GLuint edgeVBO, edgeVAO;
    glGenVertexArrays(1, &edgeVAO);
    glGenBuffers(1, &edgeVBO);

    // Définition des arêtes
    std::vector<glm::vec3> edgeVertices;
    std::vector<GLuint> edgeIndices;

    // Ajout des arêtes du tétraèdre principal
    edgeVertices.insert(edgeVertices.end(), {v1, v2, v3, v4});
    edgeIndices = {
        0, 1,  // Arête v1-v2
        1, 2,  // Arête v2-v3
        2, 0,  // Arête v3-v1
        0, 3,  // Arête v1-v4
        1, 3,  // Arête v2-v4
        2, 3   // Arête v3-v4
    };

    // Ajout des arêtes des sous-tétraèdres
    GLuint baseIndex = edgeVertices.size();
    edgeVertices.insert(edgeVertices.end(), {v1, v2, v4, v5});
    edgeIndices.insert(edgeIndices.end(), {
        baseIndex + 0, baseIndex + 1,  // Arête v1-v2
        baseIndex + 1, baseIndex + 2,  // Arête v2-v4
        baseIndex + 2, baseIndex + 0,  // Arête v4-v1
        baseIndex + 0, baseIndex + 3,  // Arête v1-v5
        baseIndex + 1, baseIndex + 3,  // Arête v2-v5
        baseIndex + 2, baseIndex + 3   // Arête v4-v5
    });

    baseIndex = edgeVertices.size();
    edgeVertices.insert(edgeVertices.end(), {v2, v3, v4, v6});
    edgeIndices.insert(edgeIndices.end(), {
        baseIndex + 0, baseIndex + 1,  // Arête v2-v3
        baseIndex + 1, baseIndex + 2,  // Arête v3-v4
        baseIndex + 2, baseIndex + 0,  // Arête v4-v2
        baseIndex + 0, baseIndex + 3,  // Arête v2-v6
        baseIndex + 1, baseIndex + 3,  // Arête v3-v6
        baseIndex + 2, baseIndex + 3   // Arête v4-v6
    });

    baseIndex = edgeVertices.size();
    edgeVertices.insert(edgeVertices.end(), {v3, v1, v4, v7});
    edgeIndices.insert(edgeIndices.end(), {
        baseIndex + 0, baseIndex + 1,  // Arête v3-v1
        baseIndex + 1, baseIndex + 2,  // Arête v1-v4
        baseIndex + 2, baseIndex + 0,  // Arête v4-v3
        baseIndex + 0, baseIndex + 3,  // Arête v3-v7
        baseIndex + 1, baseIndex + 3,  // Arête v1-v7
        baseIndex + 2, baseIndex + 3   // Arête v4-v7
    });

    baseIndex = edgeVertices.size();
    edgeVertices.insert(edgeVertices.end(), {v1, v2, v3, v8});
    edgeIndices.insert(edgeIndices.end(), {
        baseIndex + 0, baseIndex + 1,  // Arête v1-v2
        baseIndex + 1, baseIndex + 2,  // Arête v2-v3
        baseIndex + 2, baseIndex + 0,  // Arête v3-v1
        baseIndex + 0, baseIndex + 3,  // Arête v1-v8
        baseIndex + 1, baseIndex + 3,  // Arête v2-v8
        baseIndex + 2, baseIndex + 3   // Arête v3-v8
    });

    // Binding du VAO pour les arêtes
    glBindVertexArray(edgeVAO);

    // Binding et remplissage du VBO pour les arêtes
    glBindBuffer(GL_ARRAY_BUFFER, edgeVBO);
    glBufferData(GL_ARRAY_BUFFER, edgeVertices.size() * sizeof(glm::vec3), &edgeVertices[0], GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(0);

    // Création d'un buffer pour les indices des arêtes
    GLuint edgeEBO;
    glGenBuffers(1, &edgeEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, edgeEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, edgeIndices.size() * sizeof(GLuint), &edgeIndices[0], GL_STATIC_DRAW);

    // Récupérer les emplacements des matrices dans le shader
    GLuint modelLoc = glGetUniformLocation(shaderProgram, "model");
    GLuint viewLoc = glGetUniformLocation(shaderProgram, "view");
    GLuint projectionLoc = glGetUniformLocation(shaderProgram, "projection");

    // Boucle de rendu
    while (!glfwWindowShouldClose(window)) {
        // Quitter avec la touche Échap
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
            glfwSetWindowShouldClose(window, true);

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Matrice de projection
        glm::mat4 projection = glm::perspective(glm::radians(fov), (float)windowWidth / (float)windowHeight, 0.1f, 100.0f);
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

        // Matrice de vue
        glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -4.0f));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));

        // Matrice de modèle avec rotation
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::rotate(model, glm::radians(rotationAngleX), glm::vec3(1.0f, 0.0f, 0.0f));
        model = glm::rotate(model, glm::radians(rotationAngleY), glm::vec3(0.0f, 1.0f, 0.0f));
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

        // Dessiner les faces des tétraèdres
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);

        // Dessiner les arêtes des tétraèdres en noir
        glUniform3f(glGetUniformLocation(shaderProgram, "ourColorOverride"), 0.0f, 0.0f, 0.0f);
        glBindVertexArray(edgeVAO);
        glLineWidth(3.0f); // Épaisseur des arêtes
        glDrawElements(GL_LINES, edgeIndices.size(), GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // Nettoyage
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteBuffers(1, &colorVBO);

    glDeleteVertexArrays(1, &edgeVAO);
    glDeleteBuffers(1, &edgeVBO);
    glDeleteBuffers(1, &edgeEBO);

    glDeleteProgram(shaderProgram);

    glfwTerminate();
    return 0;
}

I compile this code on my linux box with this :

g++ tetrahedrons.cpp -lglfw -lGLEW -lGL -o tetrahedrons


https://drive.google.com/file/d/1kkjJLiLOmuVtsuaF951FC4bfPkjhgQVV/view?usp=sharing