How to get the billboarding example working with an ArcBall camera?

includes/GLArcBall.py:

     
import glm
import numpy as np

class GLArcball:
    def __init__(self, eye, center, up):
        self.eye = eye
        self.center = center
        self.up = up

        dir = center - eye
        z_ax = glm.normalize(dir)
        x_ax = glm.normalize(glm.cross(z_ax, glm.normalize(up)))
        y_ax = glm.normalize(glm.cross(x_ax, z_ax))
        x_ax = glm.normalize(glm.cross(z_ax, y_ax))

        self.center_translation = glm.inverse(glm.translate(center))
        self.translation = glm.translate(glm.vec3(0.0, 0.0, -glm.length(dir)))
        self.rotation = glm.normalize(glm.quat_cast(glm.transpose(glm.mat3(x_ax, y_ax, -z_ax))))

        self.update_camera()

    def update_camera(self):
        self.camera = self.translation * glm.mat4_cast(self.rotation) * self.center_translation
        self.inv_camera = glm.inverse(self.camera)

    def transform(self):
        return self.camera
    
    def rotate(self, prev_mouse, cur_mouse):
        cur_mouse = glm.clamp(cur_mouse, glm.vec2(-1, -1), glm.vec2(1, 1))
        prev_mouse = glm.clamp(prev_mouse, glm.vec2(-1, -1), glm.vec2(1, 1))

        mouse_cur_ball = self.screen_to_arcball(cur_mouse)
        mouse_prev_ball = self.screen_to_arcball(prev_mouse)

        rotation = mouse_cur_ball * glm.inverse(mouse_prev_ball)
        self.rotation = rotation * self.rotation

        self.update_camera()
        
    def angle_between(self, v1, v2):
        dot_product = glm.dot(v1, v2)
        lengths = glm.length(v1) * glm.length(v2)
        value = glm.clamp(dot_product / lengths, -1.0, 1.0)
        return glm.acos(value)
    
    def screen_to_arcball(self, p):
        dist = glm.dot(p, p)
        if dist <= 1.0:
            return glm.quat(0.0, p.x, p.y, glm.sqrt(1.0 - dist))
        else:
            proj = glm.normalize(p)
            return glm.quat(0.0, proj.x, proj.y, 0.0)
        
    def zoom(self, zoom_amount):
        # Debug print statements
        print(f"Zoom called with amount: {zoom_amount}")
        
        # Move along the view direction
        sensitivity = 10.0  # Adjust sensitivity
        motion = glm.vec3(0, 0, zoom_amount * sensitivity)
        
        print(f"Motion vector for zoom: {motion}")
        
        # Create a translation matrix and update the current translation
        self.translation = glm.translate(-motion) * self.translation
        
        print(f"Updated translation matrix: {self.translation}")
        
        self.update_camera()

    def pan(self, mouse_delta):
        zoom_amount = np.abs(self.translation[3][2])
        motion = glm.vec4(mouse_delta.x * zoom_amount, mouse_delta.y * zoom_amount, 0.0, 0.0)
        # Find the panning amount in the world space
        motion = self.inv_camera * motion

        self.center_translation = glm.translate(glm.vec3(motion.x, motion.y, motion.z)) * self.center_translation
        self.update_camera()

    def mouse_to_ndc(self, mousex, mousey, screen_width, screen_height):
        return 2.0 * glm.vec2(mousex, mousey) / glm.vec2(screen_width, screen_height) - glm.vec2(1.0)        
    
    def get_camera_position(self):
        # Extract camera position from the inverse of translation part of the camera matrix
        return glm.vec3(self.inv_camera[3])

    def get_camera_origin(self):
        # Extract camera origin (center) from the translation part of the camera matrix
        return glm.vec3(self.camera[3])

includes/GLQuadBillboard.py:


import glm
import numpy as np
from OpenGL.GL import *
import ctypes



from includes.shader_funcs import create_shader_program  

class GLQuadBillboard:
    def __init__(self, bufs_enum, initial_data, vbo_start):
        self.initial_data = initial_data  # Store initial data as numpy array
        self.vbo_start = vbo_start  # Store vbo_start as instance variable
        # Convert initial_data from glm.vec3 to x, y, z, w, r, g, b, a, t1, t2 format
        num_points = len(initial_data)
        converted_data = np.zeros((num_points, 10), dtype=np.float32)

        vec3_data = initial_data
        converted_data = [
            vec3_data.x, vec3_data.y, vec3_data.z, 1.0,  # x, y, z, w (assuming w = 1.0)
            1.0, 0.0, 0.0, 1.0,  # r, g, b, a (example colors, adjust as needed)
            0.0, 0.0  # t1, t2 (example texture coordinates, adjust as needed)
        ]

        self.converted_data = np.array(converted_data)  # Store converted data
        self.vertex_shader_source = """
        #version 460 core

        layout (location = 0) in vec4 aPos;
        layout (location = 1) in vec4 aColor;
        layout (location = 2) in vec2 aTexCoord;

        uniform mat4 view;
        uniform mat4 projection;
        uniform mat4 model;

        out vec4 FragColor;
        out vec2 TexCoord;

        void main()
        {
            gl_Position = projection * view * model * aPos;
            FragColor = aColor;
            TexCoord = aTexCoord;
        }
        """

        self.geometry_shader_source = """
        #version 460 core

        layout (points) in;
        layout (triangle_strip, max_vertices = 4) out;

        uniform vec3 cameraPos;
        uniform vec3 cameraOrigin;
        uniform mat4 view;
        uniform mat4 projection;
        uniform mat4 model;

        in vec4 gs_in[];
        in vec2 TexCoord[];

        out vec2 TexCoord_gs;

        void main() {
            vec3 up = normalize(cameraPos - cameraOrigin);
            vec3 right = cross(vec3(0.0, 1.0, 1.0), up);

            // Calculate offsets for the quad
            float t1 = TexCoord[0].x;
            float t2 = TexCoord[0].y;

            vec4 pos = gs_in[0];

            // Extend right and up to vec4 with appropriate 'w' component
            vec4 right4 = vec4(right, 0.0);
            vec4 up4 = vec4(up, 0.0);

            vec4 pos0 = pos + right4 * t1 - up4 * t2;
            vec4 pos1 = pos + right4 * t1 + up4 * t2;
            vec4 pos2 = pos - right4 * t1 - up4 * t2;
            vec4 pos3 = pos - right4 * t1 + up4 * t2;

            gl_Position = projection * view * pos0;
            TexCoord_gs = TexCoord[0];
            EmitVertex();

            gl_Position = projection * view * pos1;
            TexCoord_gs = TexCoord[0];
            EmitVertex();

            gl_Position = projection * view * pos2;
            TexCoord_gs = TexCoord[0];
            EmitVertex();

            gl_Position = projection * view * pos3;
            TexCoord_gs = TexCoord[0];
            EmitVertex();

            EndPrimitive();
        }
        """

        self.fragment_shader_source = """
        #version 460 core
        in vec4 FragColor;
        in vec2 TexCoord;

        out vec4 outColor;

        void main()
        {
            outColor = FragColor;
        }
        """

       # Identity matrix as default model matrix
        self.model = glm.mat4(1.0)

        # Use existing VAO and VBO
        self.vao = bufs_enum['VAO']
        self.vbo = bufs_enum['VBO']

        # Initialize VAO and VBO
        glBindVertexArray(self.vao)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)

        # Buffer converted_data to GPU starting from vbo_start
        ##glBufferData(GL_ARRAY_BUFFER, self.converted_data.nbytes, None, GL_STATIC_DRAW)
        glBufferSubData(GL_ARRAY_BUFFER, vbo_start * ctypes.sizeof(ctypes.c_float), self.converted_data)

        # Specify vertex attributes
        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 10 * ctypes.sizeof(ctypes.c_float), ctypes.c_void_p(0))
        glEnableVertexAttribArray(0)

        glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 10 * ctypes.sizeof(ctypes.c_float), ctypes.c_void_p(4 * ctypes.sizeof(ctypes.c_float)))
        glEnableVertexAttribArray(1)

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 10 * ctypes.sizeof(ctypes.c_float), ctypes.c_void_p(8 * ctypes.sizeof(ctypes.c_float)))
        glEnableVertexAttribArray(2)

        # Unbind VAO and VBO
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)

        self.shader_program = create_shader_program(self.vertex_shader_source, self.fragment_shader_source, self.geometry_shader_source)

    def draw(self, view, projection, camera_pos, camera_origin):
        glUseProgram(self.shader_program)
        view_loc = glGetUniformLocation(self.shader_program, "view")
        projection_loc = glGetUniformLocation(self.shader_program, "projection")
        model_loc = glGetUniformLocation(self.shader_program, "model")
        camera_pos_loc = glGetUniformLocation(self.shader_program, "cameraPos")
        camera_origin_loc = glGetUniformLocation(self.shader_program, "cameraOrigin")

        glUniformMatrix4fv(view_loc, 1, GL_FALSE, glm.value_ptr(view))
        glUniformMatrix4fv(projection_loc, 1, GL_FALSE, glm.value_ptr(projection))
        glUniformMatrix4fv(model_loc, 1, GL_FALSE, glm.value_ptr(self.model))
        glUniform3fv(camera_pos_loc, 1, glm.value_ptr(camera_pos))
        glUniform3fv(camera_origin_loc, 1, glm.value_ptr(camera_origin))

        glBindVertexArray(self.vao)

        # Draw using glDrawArrays starting from vbo_start
        #num_points = len(self.initial_data) // 10  # Assuming each entry is 10 floats wide
        glDrawArrays(GL_POINTS, self.vbo_start, 1)

        glBindVertexArray(0)
        glUseProgram(0)

includes/GLSphere.py:

import glm 
import numpy as np 
from OpenGL.GL import * 

from includes.shader_funcs import create_shader_program

class GLSphere:
    def __init__(self, bufs, vbo_Start, ebo_start, radius, rings, sectors):

        self.triangle_fill_vs = """
        #version 460 core
        layout (location = 0) in vec4 aPos;
        layout (location = 1) in vec4 aColor; 
        layout (location = 2) in vec2 aTexCoord;

        out vec4 ourColor; 

        uniform mat4 model;
        uniform mat4 view;
        uniform mat4 projection;

        void main()
        {
            gl_Position = projection * view * model * aPos;
            ourColor = aColor; 
        }
        """
    
        self.triangle_fill_fs = """
        #version 460 core
        out vec4 FragColor;

        in vec4 ourColor;

        void main()
        {
            FragColor = ourColor;
        }
        """  
        self.shader_program = create_shader_program(self.triangle_fill_vs, self.triangle_fill_fs)         
        self.model = glm.mat4(1.0)  
        self.bufwidth=10
        self.vxyz = self.generateVertices(radius, rings, sectors)
        self.v = np.zeros(len(self.vxyz)*self.bufwidth, dtype=np.float32)
        self.i = np.zeros(100000, dtype=np.uint32)
        self.vbo_start = vbo_Start
        self.ebo_start = ebo_start

        vcount=0
        for vi in range(0,len(self.v*self.bufwidth),self.bufwidth):
            self.v[vi]=self.vxyz[vcount][0]
            self.v[vi + 1]=self.vxyz[vcount][1]
            self.v[vi + 2]=self.vxyz[vcount][2]
            self.v[vi + 3]=1.0

            self.v[vi + 4]=1.0
            self.v[vi + 5]=0.0
            self.v[vi + 6]=0.0
            self.v[vi + 7]=1.0
            
            self.v[vi + 8]=0.0
            self.v[vi + 9]=0.0
            vcount+=1

        icount = 0
        for r in range(rings - 1):
            for s in range(sectors):
                v1 = r * sectors + s
                v2 = r * sectors + (s + 1) % sectors
                v3 = (r + 1) * sectors + (s + 1) % sectors
                v4 = (r + 1) * sectors + s
                self.i[icount] = v1
                self.i[icount + 1] = v2
                self.i[icount + 2] = v4
                icount += 3
                self.i[icount] = v2
                self.i[icount + 1] = v3
                self.i[icount + 2] = v4
                icount += 3
                
        glBindBuffer(GL_ARRAY_BUFFER, bufs['VBO'])
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufs['EBO'])
        self.vbo_end = self.vbo_start + self.v.nbytes
        self.ebo_end = self.ebo_start + self.i.nbytes
        glBufferSubData(GL_ARRAY_BUFFER, self.vbo_start, self.v.nbytes, self.v)
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, self.ebo_start, self.i.nbytes, self.i)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)


        print('Sphere()')

    def generateVertices(self, rho, rings, sectors):
        phi_values = np.linspace(0, 2 * np.pi, sectors)
        theta_values = np.linspace(0, np.pi, rings)
        coords=[]
        for phi in phi_values:
            for theta in theta_values:
                x=rho * np.sin(theta) * np.cos(phi)
                y=rho * np.sin(theta) * np.sin(phi)
                z=rho * np.cos(theta)
                coords.append([x,y,z])

        print('breakpoint')
        return coords
    
    def draw(self, bufs, view, projection, wireframe=True):
        glUseProgram(self.shader_program)
        model = glGetUniformLocation(self.shader_program, "model")
        view_loc = glGetUniformLocation(self.shader_program, "view")
        projection_loc = glGetUniformLocation(self.shader_program, "projection")

        glUniformMatrix4fv(model, 1, GL_FALSE, glm.value_ptr(self.model))        
        glUniformMatrix4fv(view_loc, 1, GL_FALSE, glm.value_ptr(view))
        glUniformMatrix4fv(projection_loc, 1, GL_FALSE, glm.value_ptr(projection))
        glDisable(GL_CULL_FACE)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 


        glBindVertexArray(bufs['VAO']) 
        glBindBuffer(GL_ARRAY_BUFFER, bufs['VBO']) 
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufs['EBO'])
        if wireframe:
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
        else:
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

        glDrawElements(GL_TRIANGLES, len(self.i), GL_UNSIGNED_INT, ctypes.c_void_p(self.ebo_start * glm.sizeof(glm.uint32)))

includes/init_buffers.py:


from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
import glm
import ctypes

# Define a custom exception for OpenGL errors
class OpenGLError(Exception):
    pass

# Function to check for OpenGL errors
def check_opengl_error():
    err_code = glGetError()
    if err_code != GL_NO_ERROR:
        err_msg = gluErrorString(err_code)
        raise OpenGLError(f"OpenGL Error: {err_msg.decode()}")

def init_buffers(num_float_elements, num_uint_elements):
    try:
        try:
            standalone_v = np.zeros(num_float_elements, dtype=np.float32)
            standalone_i = np.zeros(num_uint_elements, dtype=np.uint32)
            VAO = glGenVertexArrays(1)
            VBO = glGenBuffers(1)
            EBO = glGenBuffers(1)
            glBindVertexArray(VAO)
            glBindBuffer(GL_ARRAY_BUFFER, VBO)
            glBufferData(GL_ARRAY_BUFFER, standalone_v.nbytes, ctypes.c_void_p(standalone_v.ctypes.data), GL_STATIC_DRAW)
            check_opengl_error()  # Check for OpenGL errors after glBufferData
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO)
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, standalone_i.nbytes, ctypes.c_void_p(standalone_i.ctypes.data), GL_STATIC_DRAW)
            check_opengl_error()  # Check for OpenGL errors after glBufferData
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 10 * glm.sizeof(glm.float32), None)
            glEnableVertexAttribArray(0)
            glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 10 * glm.sizeof(glm.float32), ctypes.c_void_p(4 * glm.sizeof(glm.float32)))
            glEnableVertexAttribArray(1)
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 10 * glm.sizeof(glm.float32), ctypes.c_void_p(8 * glm.sizeof(glm.float32)))
            glEnableVertexAttribArray(2)
            check_opengl_error()  # Check for OpenGL errors after setup
            bufs = {
                'VAO': VAO,
                'VBO': VBO,
                'EBO': EBO,
                'standalone_v': standalone_v,
                'standalone_i': standalone_i,
            }
            return bufs
        except OpenGLError as e:
            print(e)
            # Handle the OpenGL error as needed
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        # Handle other exceptions as needed

includes/init_glfw.py:

from OpenGL.GL import *
import glfw
from glfw.GLFW import *
import platform

def init_glfw(title):
    if not glfw.init():
        raise Exception('glfw can not be initialized')

    # Set window hints for the OpenGL version and profile
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6)
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)

    # Additional window hint for macOS compatibility
    if platform.system() == "Darwin":
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE)

    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(640, 480, title, None, None)
    if not window:
        glfw.terminate()
        raise Exception('glfw window can not be created')

    # Make the window's context current
    glfw.make_context_current(window)

    # Maximize the window
    glfw.maximize_window(window)

    win_width, win_height = glfwGetWindowSize(window)
    # The window is now maximized but not in full-screen mode
    return win_width, win_height, window 

includes/shader_funcs.py:


from OpenGL.GL import *

def compile_shader(shader_source, shader_type):
    shader = glCreateShader(shader_type)
    glShaderSource(shader, shader_source)
    glCompileShader(shader)

    # Check compilation status
    status = glGetShaderiv(shader, GL_COMPILE_STATUS)
    if status != GL_TRUE:
        raise RuntimeError(f"Shader compilation failed:\n{glGetShaderInfoLog(shader).decode()}")

    return shader

def create_shader_program(vertex_source, fragment_source, geometry_source=None):
    vertex_shader = compile_shader(vertex_source, GL_VERTEX_SHADER)
    fragment_shader = compile_shader(fragment_source, GL_FRAGMENT_SHADER)

    # Compile geometry shader if provided
    geometry_shader = None
    if geometry_source:
        geometry_shader = compile_shader(geometry_source, GL_GEOMETRY_SHADER)

    # Create shader program
    shader_program = glCreateProgram()
    glAttachShader(shader_program, vertex_shader)
    glAttachShader(shader_program, fragment_shader)
    if geometry_shader:
        glAttachShader(shader_program, geometry_shader)

    glLinkProgram(shader_program)

    # Check linking status
    status = glGetProgramiv(shader_program, GL_LINK_STATUS)
    if status != GL_TRUE:
        raise RuntimeError(f"Shader program linking failed:\n{glGetProgramInfoLog(shader_program).decode()}")

    # Clean up shaders
    glDeleteShader(vertex_shader)
    glDeleteShader(fragment_shader)
    if geometry_shader:
        glDeleteShader(geometry_shader)

    return shader_program

main.py:


import sys 
import os 
sys.path.append(os.getcwd())

from OpenGL.GL import *
import glfw 
from glfw.GLFW import *
from glfw import _GLFWwindow as GLFWwindow
import glm 
import math 

from includes.GLArcball import GLArcball 
from includes.GLSphere import GLSphere 
from includes.GLQuadBillboard import GLQuadBillboard 
from includes.GLTextImage import GLTextImage 
from includes.init_glfw import init_glfw
from includes.init_buffers import init_buffers 


global view_state
view_state = {}
view_state['eye'] = glm.vec3( 0, 0, 50.0)
view_state['position'] = glm.vec3( 0, 0, 0)
view_state['up'] = glm.vec3( 0, 1, 0)
view_state['near'] = -500000
view_state['far'] = 500000
view_state['window_width'] = None
view_state['window_height'] = None
view_state['view_width'] = None
view_state['view_height'] = None
view_state['lookAt'] = glm.mat4(1.0)
view_state['lookAt'] = glm.lookAt(glm.vec3(0, 0, 1000), glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))



global input_state
input_state = {}
input_state['shift_left'] = {
    'isDown': False
}
input_state['mouse_right'] = {
    'isDown': False
}
input_state['mouse_X_prev'] = -2
input_state['mouse_Y_prev'] = -2
input_state['mouse_X_cur'] = -2
input_state['mouse_X_cur'] = -2


global camera 
camera = GLArcball( view_state['eye'], view_state['position'], view_state['up'])





def mouse_callback(window, xpos, ypos):
    if input_state['shift_left']['isDown'] and input_state['mouse_right']['isDown']:
        input_state['mouse_X_cur'] = xpos
        input_state['mouse_Y_cur'] = ypos

        cur_mouse_ndc = camera.mouse_to_ndc(xpos, ypos, view_state['window_width'], view_state['window_height'])

        if input_state['mouse_X_prev'] != -2:
            prev_mouse_ndc = camera.mouse_to_ndc(input_state['mouse_X_prev'], input_state['mouse_Y_prev'],
                                                 view_state['window_width'], view_state['window_height'])
            camera.rotate(prev_mouse_ndc, cur_mouse_ndc)

        input_state['mouse_X_prev'] = xpos
        input_state['mouse_Y_prev'] = ypos














def mouse_button_callback(window, button, action, mods):
    if button == glfw.MOUSE_BUTTON_RIGHT:
        if action == glfw.PRESS:
            input_state['mouse_right']['isDown'] = True
        elif action == glfw.RELEASE:
            input_state['mouse_right']['isDown'] = False



def key_callback(window, key, scancode, action, mods):
    if key == glfw.KEY_LEFT_SHIFT:
        if action == glfw.PRESS:
            input_state['shift_left']['isDown'] = True
        elif action == glfw.RELEASE:
            input_state['shift_left']['isDown'] = False





def scrollwheel_callback(window, xdelta, ydelta):
    sign = math.copysign(1, ydelta)  

    if sign > 0:
        print('zoomin:')
        scale_factor = .98
        view_state['view_width'] = view_state['view_width'] * scale_factor
        view_state['view_height'] = view_state['view_height'] * scale_factor 
        view_state['ortho']  = glm.mat4(1.0)
        view_state['ortho']  = glm.ortho(-(view_state['view_width'] / 2), view_state['view_width'] / 2, 
                                         -(view_state['view_height'] / 2), view_state['view_height'] / 2, view_state['near'], view_state['far'])
        pass
    else:
        print('zoomout:')
        scale_factor = 1.1
        view_state['view_width'] = view_state['view_width'] * scale_factor
        view_state['view_height'] = view_state['view_height'] * scale_factor 
        view_state['ortho']  = glm.mat4(1.0)
        view_state['ortho']  = glm.ortho(-(view_state['view_width'] / 2), view_state['view_width'] / 2, 
                                         -(view_state['view_height'] / 2), view_state['view_height'] / 2, view_state['near'], view_state['far'])
        pass



def framebuffer_size_callback(window: GLFWwindow, width: int, height: int) -> None:
    view_state['ortho'] = glm.mat4(1.0)
    view_state['ortho'] = glm.ortho(-(view_state['view_width'] / 2), view_state['view_width'] / 2, 
                                         -(view_state['view_height'] / 2), view_state['view_height'] / 2, view_state['near'], view_state['far'])
    glViewport(0, 0, width, height) 




def main():
    win_width, win_height, window = init_glfw("LearnOpenGL")
    global bufs_enum 
    bufs_enum = init_buffers(2100000, 2100000)
    view_state['window_width'] = win_width 
    view_state['window_height'] = win_height
    view_state['view_width'] = win_width * .15
    view_state['view_height'] = win_height * .15
    view_state['lookAt'] = glm.mat4(1.0)
    view_state['lookAt'] = glm.lookAt(glm.vec3(0, 0, 1000), glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))
    view_state['ortho'] = glm.mat4(1.0)
    view_state['ortho'] = glm.ortho(-(view_state['view_width'] / 2), view_state['view_width'] / 2, 
                                         -(view_state['view_height'] / 2), view_state['view_height'] / 2, view_state['near'], view_state['far'])
    view_state['win_ortho'] = glm.ortho(-(view_state['window_width'] / 2), view_state['window_width'] / 2, 
                                         -(view_state['window_height'] / 2), view_state['window_height'] / 2, view_state['near'], view_state['far']) 
       
    glfwSetScrollCallback(window, scrollwheel_callback)
    glfwSetWindowAspectRatio(window, int(win_width), int(win_height))
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback) 
    glfwSetKeyCallback(window, key_callback)  
    glfwSetMouseButtonCallback(window, mouse_button_callback) 
    glfwSetCursorPosCallback(window, mouse_callback)
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glViewport(0, 0, win_width, win_height)


    init_objects(bufs_enum)

    while not glfwWindowShouldClose(window):
        render(window)

    glfwTerminate()
    print('done...')








def init_objects(bufs_enum):
    global vbo_start 
    vbo_start = 0
    global ebo_start 
    ebo_start = 0

    sphere = GLSphere(bufs_enum, vbo_start, ebo_start, 50.0, 50, 50)
    vbo_start = sphere.vbo_end
    ebo_start = sphere.ebo_end

    global objects_dict
    objects_dict = {}
    objects_dict['sphere'] = {
        'object': sphere,
        'vbo_start': None,
        'ebo_start': None,
        'vbo_end': None,
        'ebo_end': None
    }

    quads = GLQuadBillboard(bufs_enum, glm.vec3(0,0,0), vbo_start)

    objects_dict['quads'] = {
        'object': quads,
        'vbo_start': None,
        'ebo_start': None,
        'vbo_end': None,
        'ebo_end': None
    }

def render(window):
    last_frame = glfwGetTime()
    while not glfwWindowShouldClose(window):
        current_time = glfwGetTime()
        delta_time = current_time - last_frame
        last_frame = current_time

        # Render
        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        camera_pos = glm.vec3(0.0, 0.0, 50.0)  # Example camera position
        view_matrix = camera.transform()      # Get view matrix from camera

        # Calculate billboard transformation matrix
        billboard_transform = glm.mat4(1.0)
        billboard_transform = glm.mat4(glm.mat3(view_matrix))  # Extract rotation part of view matrix
        objects_dict['quads']['object'].model = billboard_transform

        objects_dict['sphere']['object'].draw(bufs_enum, camera.transform(), view_state['ortho'], wireframe=True)
        objects_dict['quads']['object'].draw(camera.transform(), view_state['ortho'], 
                                             camera.get_camera_position(), camera.get_camera_origin())

        glfwSwapBuffers(window)
        glfwPollEvents()






if __name__ == "__main__":
    main()

Currently the issue to resolve is:


Traceback (most recent call last):
  File "c:\Users\franc\source\repos\GLBillboardingText\main.py", line 242, in <module>
    main()
  File "c:\Users\franc\source\repos\GLBillboardingText\main.py", line 165, in main
    init_objects(bufs_enum)
  File "c:\Users\franc\source\repos\GLBillboardingText\main.py", line 200, in init_objects
    quads = GLQuadBillboard(bufs_enum, glm.vec3(0,0,0), vbo_start)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\franc\source\repos\GLBillboardingText\includes\GLQuadBillboard.py", line 146, in __init__
    self.shader_program = create_shader_program(self.vertex_shader_source, self.fragment_shader_source, self.geometry_shader_source)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\franc\source\repos\GLBillboardingText\includes\shader_funcs.py", line 36, in create_shader_program
    raise RuntimeError(f"Shader program linking failed:\n{glGetProgramInfoLog(shader_program).decode()}")
RuntimeError: Shader program linking failed:
The ♥ shader uses varying gs_in, but previous shader does not write to it.
Out of resource error.

Why is this happening? How to resolve this and get the vertex, fragment and geometry shader to compile?

What I would like to have happen is for a single quad to be drawn at the point specified of some size that can be adjusted that will always face the camera.

Could I get some assistance to get this working?

Screenshot 2024-06-23 100051

Pypengl
Pyglm
Glfw
Numpy
Python 3.12±