from typing import Optional
import numpy as np
from OpenGL.GL import glMaterialfv, glLightfv
from OpenGL.raw.GL.VERSION.GL_1_0 import GL_FRONT_AND_BACK, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, glMaterialf, \
GL_SHININESS, glClearColor, glEnable, GL_DEPTH_TEST, GL_LIGHTING, GL_LIGHT0, GL_COLOR_MATERIAL, glColorMaterial, \
GL_AMBIENT_AND_DIFFUSE, GL_POSITION, glMatrixMode, GL_PROJECTION, glLoadIdentity, GL_MODELVIEW, glClear, \
GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, glRotatef
from OpenGL.raw.GLU import gluPerspective, gluLookAt
from PySide6.QtCore import QTimer, Qt
from PySide6.QtWidgets import QWidget
from examples import g_vertex_buffer_data, g_color_buffer_data
from picogl.logger import Logger as log
from picogl.renderer import MeshData
from picogl.renderer.legacy_glmesh import LegacyGLMesh
from picogl.ui.backend.qt.base import GLBase
[docs]
class LegacyQtObjectRenderer(GLBase):
"""
Qt-based cube renderer using legacy OpenGL
This class extends GLBase to provide a simple cube renderer with
mouse controls for rotation and zoom.
"""
def __init__(self, parent: Optional[QWidget] = None):
"""Initialize the cube renderer"""
super().__init__(parent, gl_use_legacy=True)
# Cube data
[docs]
self._initialized: bool = False
[docs]
self.gl_mesh_data = None
[docs]
self.mesh_data: Optional[MeshData] = None
[docs]
self.vertices = np.array(g_vertex_buffer_data, dtype=np.float32)
[docs]
self.colors = np.array(g_color_buffer_data, dtype=np.float32)
# Generate indices for the cube (36 vertices = 12 triangles)
[docs]
self.indices = np.arange(36, dtype=np.uint32)
# Animation and control state
[docs]
self.auto_rotate = True
[docs]
self.rotation_speed = 1.0
# Initialize mvp_parameters to avoid None values
self.mvp_parameters.rotation_x = 0.0
self.mvp_parameters.rotation_y = 0.0
self.mvp_parameters.pan_x = 0.0
self.mvp_parameters.pan_y = 0.0
# Initialize camera_parameters to avoid None values
self.camera_parameters.rotation_x_axis = 0.0
self.camera_parameters.rotation_y_axis = 0.0
self.camera_parameters.rotation_z_axis = 0.0
self.camera_parameters.translation_x_axis = 0.0
self.camera_parameters.translation_y_axis = 0.0
self.camera_parameters.translation_zoom = 0.0
# Setup animation timer
self.timer.timeout.connect(self.animate)
self.timer.start(16) # ~60 FPS
[docs]
def initializeGL(self):
"""Initialize OpenGL state for cube rendering"""
super().initializeGL()
self.initialize_state()
self.initialize_lighting()
self.initialize_materials()
# Create and upload mesh data
self.initialize()
[docs]
def initialize(self):
if self._initialized:
return
self.mesh_data = MeshData.from_raw(
vertices=self.vertices, colors=self.colors, indices=self.indices
)
self.gl_mesh_data = LegacyGLMesh.from_mesh_data(mesh=self.mesh_data)
# self.gl_mesh_data.upload()
self._initialized = True
log.message("✅ Qt Cube Renderer initialized")
[docs]
def initialize_materials(self):
# Set up material properties
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0])
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0])
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0)
[docs]
def initialize_state(self):
# Set up OpenGL state
glClearColor(0.1, 0.1, 0.2, 1.0) # Dark blue background
glEnable(GL_DEPTH_TEST)
[docs]
def initialize_lighting(self):
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
# Set up lighting
glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0])
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.3, 0.3, 0.3, 1.0])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0])
glLightfv(GL_LIGHT0, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
[docs]
def resizeGL(self, w: int, h: int):
"""Handle window resize"""
super().resizeGL(w, h)
# Set up projection matrix
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45.0, w / h, 0.1, 100.0)
# Return to modelview matrix
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
[docs]
def _emit_rotation_feedback(self):
pass
[docs]
def paintGL(self):
"""Render the cube scene"""
# Clear buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Set up modelview matrix
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# Position camera
gluLookAt(0, 0, self.zoom, 0, 0, 0, 0, 1, 0)
# Apply rotations
glRotatef(self.rotation_x, 1, 0, 0)
glRotatef(self.rotation_y, 0, 1, 0)
# Draw the cube using legacy OpenGL
self.draw()
[docs]
def draw(self):
"""Draw the cube using LegacyGLMesh"""
# Draw using LegacyGLMesh (already created and uploaded in initializeGL)
if self.gl_mesh_data is not None:
self.gl_mesh_data.draw()
[docs]
def animate(self):
"""Animation loop - called by timer"""
if self.auto_rotate:
self.rotation_y += self.rotation_speed
if self.rotation_y >= 360.0:
self.rotation_y -= 360.0
self.update()
[docs]
def mouseMoveEvent(self, event):
"""Handle mouse movement for manual rotation"""
if event.buttons() & Qt.LeftButton:
# Manual rotation
self.auto_rotate = False
delta = event.position() - self.last_mouse_pos
self.rotation_x += delta.y() * 0.5
self.rotation_y += delta.x() * 0.5
# Clamp rotation
self.rotation_x = max(-90, min(90, self.rotation_x))
# Update mvp_parameters for compatibility
self.mvp_parameters.rotation_x = self.rotation_x
self.mvp_parameters.rotation_y = self.rotation_y
super().mouseMoveEvent(event)
[docs]
def wheelEvent(self, event):
"""Handle mouse wheel for zoom"""
delta = event.angleDelta().y()
zoom_factor = 0.1
if delta > 0:
self.zoom = max(1.0, self.zoom - zoom_factor)
else:
self.zoom = min(20.0, self.zoom + zoom_factor)
print(f"Zoom: {self.zoom:.1f}")
super().wheelEvent(event)
[docs]
def keyPressEvent(self, event):
"""Handle keyboard input"""
if event.key() == Qt.Key_Space:
# Toggle auto-rotation
self.auto_rotate = not self.auto_rotate
print(f"Auto-rotation: {'ON' if self.auto_rotate else 'OFF'}")
elif event.key() == Qt.Key_R:
# Reset rotation
self.rotation_x = 0.0
self.rotation_y = 0.0
self.zoom = 5.0
self.mvp_parameters.rotation_x = 0.0
self.mvp_parameters.rotation_y = 0.0
self.mvp_parameters.pan_x = 0.0
self.mvp_parameters.pan_y = 0.0
self.camera_parameters.rotation_x_axis = 0.0
self.camera_parameters.rotation_y_axis = 0.0
self.camera_parameters.rotation_z_axis = 0.0
self.camera_parameters.translation_x_axis = 0.0
self.camera_parameters.translation_y_axis = 0.0
self.camera_parameters.translation_zoom = 0.0
print("Reset view")
elif event.key() == Qt.Key_Escape:
# Close application
self.parent().close()
else:
super().keyPressEvent(event)