Python+Opengl realizes real-time interactive writing on blocks with B-spline curves

introduction

The blogger is a student of grade 2021 Graduate School of computer science at Beijing University of technology. This task is the first major assignment of the course of computer graphics. Because I haven't contacted opengl before, I spent some effort in completing this operation. Because we can't find the code that meets the needs of teachers on the Internet, the code is patched up by ourselves, and there may still be irregularities. Of course, this blog was released after the end of the course.

rely on

  • python3.9
  • opengl library

It is estimated that Python 3.7 and 3.8 are OK, but I haven't tried. The specific import library code is as follows:

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

Draw a square

Here, the picture function provided by opengl is used for drawing.
Firstly, the point coordinates of each face are given:

vertices2 = 1.4*np.array([
    [[0.2, 0.2, 0.2], [-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [0.2, -0.2, 0.2]],  # front
    [[0.2, 0.2, -0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]],  # after
    [[0.2, 0.2, 0.2], [0.2, 0.2, -0.2], [-0.2, 0.2, -0.2], [-0.2, 0.2, 0.2]],  # Left
    [[0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, -0.2, 0.2]],  # right
    [[0.2, 0.2, 0.2], [0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [0.2, 0.2, -0.2]],  # upper
    [[-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]]  # lower
])

The 1.4 times here is to adjust the size relationship. This coefficient can be adjusted according to the actual situation.
Then give the color of each face:

colours = np.array([
    [0, 1, 1], [1, 0.5, 0.5],
    [1, 1, 0], [0.1, 0.1, 1],
    [0, 1, 0.2], [0.6, 0.6, 0.6]
])

The three numbers here represent RGB red, green and blue respectively, and can also be adjusted by themselves.
Finally, draw the cube in the show function.

    for i in range(vertices2.shape[0]):
        glBegin(GL_QUADS)
        points = vertices2[i, :]
        color = colours[i, :]
        for point in points:
            glColor3f(color[0], color[1], color[2])
            glVertex3f(point[0], point[1], point[2])
        glEnd()

Convert screen coordinates to world coordinates

Many mistakes have been made in this step. Most of the online code is based on C + +.
The conversion is mainly completed through the reflection function in opengl: gluUnProject

gluUnProject(MOUSE_X, viewport[3] - MOUSE_Y, z, modelview_mat, projection_mat, viewport)

This function calculates by calling the mouse click position information (MOUSE_X, MOUSE_Y), picture depth information (z) and three conversion matrices (modelview_mat, projection_mat, viewport).

B-spline curve drawing

This step has tried more mistakes.
The code part is very simple. It uses the gluNurbsCurve function to draw B-spline curves. NURBS is the abbreviation of Non-Uniform Rational B-Splines.

gluNurbsCurve(nurb, KNOTS, POINTS, GL_MAP1_VERTEX_3)

POINTS is the control point in B-spline, that is, the point coordinates obtained after clicking the mouse. However, because KNOTS has not been configured correctly before, the function cannot draw a curve.
KNOTS can be generated directly according to the number of control points.

degree = 3
knotNum = len(POINTS) + degree
KNOTS = [float(i)/(knotNum-1) for i in range(knotNum)]

Complete code

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

vertices2 = 1.4*np.array([
    [[0.2, 0.2, 0.2], [-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [0.2, -0.2, 0.2]],  # front
    [[0.2, 0.2, -0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]],  # after
    [[0.2, 0.2, 0.2], [0.2, 0.2, -0.2], [-0.2, 0.2, -0.2], [-0.2, 0.2, 0.2]],  # Left
    [[0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [-0.2, -0.2, -0.2], [-0.2, -0.2, 0.2]],  # right
    [[0.2, 0.2, 0.2], [0.2, -0.2, 0.2], [0.2, -0.2, -0.2], [0.2, 0.2, -0.2]],  # upper
    [[-0.2, 0.2, 0.2], [-0.2, -0.2, 0.2], [-0.2, -0.2, -0.2], [-0.2, 0.2, -0.2]]  # lower
])

colours = np.array([
    [0, 1, 1], [1, 0.5, 0.5],
    [1, 1, 0], [0.1, 0.1, 1],
    [0, 1, 0.2], [0.6, 0.6, 0.6]
])
IS_PERSPECTIVE = True  # perspective projection 
VIEW = np.array([-0.5, 0.5, -0.5, 0.5, 0.5, 20.0])  # Six left/right/bottom/top/near/far faces of the vista
RIGHT_IS_DOWNED = False
CameraPos = np.array([0.0, 0.0, 1])
CameraFront = np.array([0, 0, 0])
CameraUp = np.array([0, 1, 0])
SCALE_K = np.array([1.0, 1.0, 1.0])
yaw = 0
pitch = 0
MOUSE_X, MOUSE_Y = 0, 0
WIN_W = 480
WIN_H = 480
POINTS = np.array([[0, 0, 0]])


def init():
    glClearColor(0.0, 0.0, 0.0, 1.0)  # Sets the canvas background color. Note: there must be 4 parameters here
    glEnable(GL_DEPTH_TEST)  # Open the depth test to realize the occlusion relationship
    glDepthFunc(GL_LEQUAL) # Set the depth test function (GL_LEQUAL is only one of the options)
    global nurb
    nurb = gluNewNurbsRenderer()
    global samplingTolerance
    gluNurbsProperty(nurb, GLU_SAMPLING_TOLERANCE, samplingTolerance)

nurb=None
samplingTolerance=1.0

def show():
    global IS_PERSPECTIVE, VIEW
    global CameraPos, CameraFront, CameraUp
    global SCALE_K
    global WIN_W, WIN_H
    global vertices2
    global POINTS
    global KNOTS
    global nurb

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    # Set projection (perspective projection)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()

    if IS_PERSPECTIVE:
        glFrustum(VIEW[0], VIEW[1], VIEW[2], VIEW[3], VIEW[4], VIEW[5])

    # Set up model view
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    # Geometric transformation
    glScale(SCALE_K[0], SCALE_K[1], SCALE_K[2])

    # viewpoint
    gluLookAt(
        CameraPos[0], CameraPos[1], CameraPos[2],
        CameraFront[0], CameraFront[1], CameraFront[2],
        CameraUp[0], CameraUp[1], CameraUp[2]
    )

    glViewport(0, 0, WIN_W, WIN_H)

    n = POINTS.shape[0]

    for i in range(n):
        glPointSize(3.0)
        glColor3f(1.0, 0.0, 0.0)
        glBegin(GL_POINTS)
        glVertex3f(POINTS[i, 0], POINTS[i, 1], POINTS[i, 2])
        glEnd()

    #Continuous segment connection
    glLineWidth(0.2)
    glColor3f(1.0, 1.0, 1.0)
    glBegin(GL_LINE_STRIP)
    for i in range(n):
        glVertex3f(POINTS[i, 0], POINTS[i, 1], POINTS[i, 2])
    glEnd()

    if n > 2:
        degree = 3
        knotNum = len(POINTS) + degree
        KNOTS =  [float(i)/(knotNum-1) for i in range(knotNum)]
        gluBeginCurve(nurb)
        glColor3f(0, 0, 0)
        glLineWidth(7.0)
        gluNurbsCurve(nurb, KNOTS, POINTS, GL_MAP1_VERTEX_3)
        gluEndCurve(nurb)

    for i in range(vertices2.shape[0]):
        glBegin(GL_QUADS)
        points = vertices2[i, :]
        color = colours[i, :]
        for point in points:
            glColor3f(color[0], color[1], color[2])
            glVertex3f(point[0], point[1], point[2])
        glEnd()

    glutSwapBuffers()


def Mouse_click(button, state, x, y):
    global RIGHT_IS_DOWNED
    global MOUSE_X, MOUSE_Y
    global SCALE_K
    global WIN_W
    global WIN_H
    global POINTS

    MOUSE_X = x
    MOUSE_Y = y

    if button == GLUT_LEFT_BUTTON and state == 0:

        modelview_mat = OpenGL.GL.glGetDoublev(OpenGL.GL.GL_MODELVIEW_MATRIX)
        projection_mat = OpenGL.GL.glGetDoublev(OpenGL.GL.GL_PROJECTION_MATRIX)
        viewport = OpenGL.GL.glGetIntegerv(OpenGL.GL.GL_VIEWPORT)

        z = OpenGL.GL.glReadPixels(MOUSE_X, viewport[3] - MOUSE_Y, 1, 1, OpenGL.GL.GL_DEPTH_COMPONENT, OpenGL.GL.GL_FLOAT)

        ret = gluUnProject(MOUSE_X, viewport[3] - MOUSE_Y, z, modelview_mat, projection_mat, viewport)
        ret_paint = [[ret[0], ret[1], ret[2]]]

        if abs(ret[0]) < 0.3 and abs(ret[1]) < 0.3 and abs(ret[2]) < 0.3: #Points outside the box are not drawn
            POINTS = np.append(POINTS, ret_paint, axis=0)

    if button == GLUT_RIGHT_BUTTON:
        RIGHT_IS_DOWNED = state == GLUT_DOWN


def Mouse_motion(x, y):
    global RIGHT_IS_DOWNED
    global MOUSE_X, MOUSE_Y
    global yaw, pitch
    global CameraPos

    if RIGHT_IS_DOWNED:
        dx = x - MOUSE_X
        dy = y - MOUSE_Y
        MOUSE_X = x
        MOUSE_Y = y

        sensitivity = 0.4
        dx = dx * sensitivity
        dy = dy * sensitivity

        yaw = yaw + dx
        pitch = pitch + dy

        if pitch > 89:
            pitch = 89
        if pitch < -89:
            pitch = -89

        CameraPos[0] = np.cos(np.radians(yaw)) * np.cos(np.radians(pitch))
        CameraPos[1] = np.sin(np.radians(pitch))
        CameraPos[2] = np.sin(np.radians(yaw)) * np.cos(np.radians(pitch))

        glutPostRedisplay()


if __name__ == '__main__':
    glutInit()
    displayMode = GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH
    glutInitDisplayMode(displayMode)
    glutInitWindowSize(WIN_W, WIN_H)
    glutInitWindowPosition(300, 200)
    glutCreateWindow("CUBE")

    init()
    glutDisplayFunc(show)
    glutMouseFunc(Mouse_click)
    glutMotionFunc(Mouse_motion)
    glutMainLoop()

Keywords: Python Computer Graphics OpenGL

Added by t_galan on Tue, 16 Nov 2021 15:04:32 +0200