I solved a problem for cuboids:

assets/shaders/color.frag
uniform vec3 uColor;
void main()
{
gl_FragColor = vec4(uColor, 1.0);
}
assets/shaders/color.vert
attribute vec3 aPosition;
uniform mat4 uMvpMatrix;
void main()
{
gl_Position = uMvpMatrix * vec4(aPosition, 1.0);
}
assets/shaders/texture.frag
uniform sampler2D uSampler;
varying vec2 vTexCoord;
void main()
{
// gl_FragColor = texture2D(uSampler, vTexCoord);
// For Y-flip
gl_FragColor = texture2D(uSampler, vec2(vTexCoord.s, 1.0 - vTexCoord.t));
}
assets/shaders/texture.vert
attribute vec3 aPosition;
attribute vec2 aTexCoord;
uniform mat4 uMvpMatrix;
varying vec2 vTexCoord;
void main()
{
gl_Position = uMvpMatrix * vec4(aPosition, 1.0);
vTexCoord = aTexCoord;
}
box_drawer.py
import glm
import numpy as np
from OpenGL.GL import *
class BoxDrawer():
def __init__(self, program):
self.program = program
glUseProgram(self.program)
self.aPositionLocation = glGetAttribLocation(self.program, "aPosition")
self.aTexCoordLocation = glGetAttribLocation(self.program, "aTexCoord")
self.uMvpMatrixLocation = glGetUniformLocation(self.program, "uMvpMatrix")
uSamplerLocation = glGetUniformLocation(self.program, "uSampler")
glUniform1i(uSamplerLocation, 0)
self.initVertexBuffers()
def initVertexBuffers(self):
vertPositions = np.array([
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5
], dtype=np.float32)
self.vertPosBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.vertPosBuffer)
glBufferData(GL_ARRAY_BUFFER, len(vertPositions) * 4,
vertPositions, GL_STATIC_DRAW)
texCoords = np.array([
1, 1,
0, 1,
0, 0,
1, 1,
0, 1,
0, 0,
1, 1,
0, 1,
0, 0,
1, 1,
0, 1,
0, 0,
0, 1,
0, 0,
1, 0,
1, 0,
1, 1,
0, 1,
1, 0,
1, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
1, 0,
0, 0,
1, 0,
0, 1
], dtype=np.float32)
self.texCoordBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, self.texCoordBuffer)
glBufferData(GL_ARRAY_BUFFER, len(texCoords) * 4,
texCoords, GL_STATIC_DRAW)
def bind(self):
glUseProgram(self.program)
glBindBuffer(GL_ARRAY_BUFFER, self.vertPosBuffer)
glVertexAttribPointer(self.aPositionLocation, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(self.aPositionLocation)
glBindBuffer(GL_ARRAY_BUFFER, self.texCoordBuffer)
glVertexAttribPointer(self.aTexCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(self.aTexCoordLocation)
def draw(self, projViewMatrix, position, size, rotation, texture):
self.bind()
glBindTexture(GL_TEXTURE_2D, texture)
# Create a model matrix, that is, a matrix combining the
# translation matrix, rotation matrix, and the scale matrix
modelMatrix = glm.translate(glm.mat4(1), position)
rotationMatrix = glm.mat4_cast(rotation)
modelMatrix = modelMatrix * rotationMatrix
modelMatrix = glm.scale(modelMatrix, size)
# Combine projView matrix and model matrix into one MVP matrix
mvpMatrix = projViewMatrix * modelMatrix
# Send MVP matrix to the vertex shader
glUniformMatrix4fv(self.uMvpMatrixLocation, 1, GL_FALSE, glm.value_ptr(mvpMatrix))
# Draw a rectangle
glDrawArrays(GL_TRIANGLES, 0, 36)
collider.py
from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode
from panda3d.core import Point3, Quat, TransformState, Vec3
class Collider():
def __init__(self, name, position, rotation, size, mass, world):
self.position = position
self.rotation = rotation
self.size = size
shape = BulletBoxShape(Vec3(size.x / 2, size.y / 2, size.z / 2))
self.node = BulletRigidBodyNode(name)
self.node.addShape(shape)
self.node.setMass(mass)
p = Point3(self.position.x, self.position.y, self.position.z)
q = Quat.identQuat()
s = Vec3(1, 1, 1)
self.transform = TransformState.make_pos_quat_scale(p, q, s)
self.node.setTransform(self.transform)
world.attachRigidBody(self.node)
def update(self):
self.position.x = self.node.getTransform().pos.x
self.position.y = self.node.getTransform().pos.y
self.position.z = self.node.getTransform().pos.z
hpr = self.node.getTransform().getHpr()
quat = Quat()
quat.setHpr(hpr)
self.rotation.w = quat.getX()
self.rotation.x = quat.getY()
self.rotation.y = quat.getZ()
self.rotation.z = quat.getW()
line_drawer.py
import math
import glm
import numpy as np
from OpenGL.GL import *
from math_helper import MathHelper
class LineDrawer:
def __init__(self, program, projViewMatrix):
# Save the program and projView matrix
self.program = program
self.projViewMatrix = projViewMatrix
# Activate the current shader program to access shader variables
glUseProgram(self.program)
# Access the uniform variables in the shaders
self.aPositionLocation = glGetAttribLocation(self.program, "aPosition")
self.uColorLocation = glGetUniformLocation(self.program, "uColor")
self.uMvpMatrixLocation = glGetUniformLocation(self.program, "uMvpMatrix")
# Create a buffer in the video card's RAM
self.vertPosBuffer = glGenBuffers(1)
self.initVertexBuffers()
def initVertexBuffers(self):
# Set the vertices of the square
vertPositions = np.array([
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5
], dtype=np.float32)
# Bind to the created buffer
glBindBuffer(GL_ARRAY_BUFFER, self.vertPosBuffer)
# Copy vertex array to buffer
glBufferData(GL_ARRAY_BUFFER, len(vertPositions) * 4,
vertPositions, GL_STATIC_DRAW)
# This method should be called if the window
# size changes or the camera position changes.
def setProjViewMatrix(self, projViewMatrix):
self.projViewMatrix = projViewMatrix
def bind(self):
# Activate Shader Program Object
glUseProgram(self.program)
# Bind to the buffer
glBindBuffer(GL_ARRAY_BUFFER, self.vertPosBuffer)
# Set up buffer
glVertexAttribPointer(self.aPositionLocation, 3, GL_FLOAT, GL_FALSE,
0, ctypes.c_void_p(0))
glEnableVertexAttribArray(self.aPositionLocation)
def draw(self, start, end, color, thickness):
# Find the center of the segment
v = end - start
centerPosition = start + v / 2
# Find the length of the segment
length = glm.length(v)
# Normalize the segment vector
norm = glm.normalize(v);
# Calculate the angle of a segment
rotation = MathHelper.rotationTo(glm.vec3(1, 0, 0), norm);
# Create a model matrix, that is, a matrix combining the
# translation matrix, rotation matrix, and the scale matrix
# Create a translation matrix
modelMatrix = glm.translate(glm.mat4(1), centerPosition)
# Create a rotation matrix
rotationMatrix = glm.mat4_cast(rotation)
modelMatrix = modelMatrix * rotationMatrix
# Create a scale matrix
modelMatrix = glm.scale(modelMatrix, glm.vec3(length, thickness, thickness))
# Combine projView matrix and model matrix into one MVP matrix
mvpMatrix = self.projViewMatrix * modelMatrix
self.bind()
# Send MVP matrix to the vertex shader
glUniformMatrix4fv(self.uMvpMatrixLocation, 1, GL_FALSE,
glm.value_ptr(mvpMatrix))
# Send color value to fragment shader
glUniform3fv(self.uColorLocation, 1, glm.value_ptr(color))
# Call a draw command that will cause the vertex shader
# to be called 4 times - once for each vertex of the square
glDrawArrays(GL_TRIANGLE_STRIP, 0, 36)
main.py
import ctypes
import os
import glm
from OpenGL.GL import *
from OpenGL.GL.shaders import *
os.environ["SDL_MAIN_USE_CALLBACKS"] = "1"
os.environ["SDL_RENDER_DRIVER"] = "opengl"
import sdl3
from panda3d.bullet import BulletWorld
from panda3d.core import Vec3
from box_drawer import BoxDrawer
from collider import Collider
from line_drawer import LineDrawer
from texture_loader import loadTexture
glContext = None
window = None
boxDrawer = None
crateTexture = None
green3Texture = None
lineDrawer = None
showCollider = True
canvasWidth = 400
canvasHeight = 400
# Create a perspective projection matrix and a view matrix
fovy = 45
near = 0.1
far = 100
projMatrix = glm.perspective(glm.radians(fovy),
canvasWidth / canvasHeight, near, far)
viewMatrix = glm.lookAt(
glm.vec3(10, 20, 25), # Camera position
glm.vec3(0, 0, 0), # Target position
glm.vec3(0, 1, 0)) # Camera up vector
# Combine them to one projView matrix
projViewMatrix = projMatrix * viewMatrix
world = BulletWorld()
world.setGravity(Vec3(0, -9.81, 0))
boxCollider = None
box2Collider = None
floorCollider = None
keys = { "left": False, "right": False, "up": False, "down": False }
def getCubeVertices(position, rotationQuat, halfSize):
# Create a cube
# v5----- v1
# /| /|
# v4------v0|
# | | | |
# | |v7---|-|v3
# |/ |/
# v6------v2
# Local vertices (untransformed, centered at origin)
localVertices = [
glm.vec3( halfSize, halfSize, halfSize), # v0
glm.vec3( halfSize, halfSize, -halfSize), # v1
glm.vec3( halfSize, -halfSize, halfSize), # v2
glm.vec3( halfSize, -halfSize, -halfSize), # v3
glm.vec3(-halfSize, halfSize, halfSize), # v4
glm.vec3(-halfSize, halfSize, -halfSize), # v5
glm.vec3(-halfSize, -halfSize, halfSize), # v6
glm.vec3(-halfSize, -halfSize, -halfSize) # v7
]
# Rotate and translate each vertex
worldVertices = []
for vertex in localVertices:
# Apply rotation (quaternion * vertex)
rotated = rotationQuat * vertex
# Apply translation
worldVertex = rotated + position
worldVertices.append(worldVertex)
return worldVertices
@sdl3.SDL_AppInit_func
def SDL_AppInit(appstate, argc, argv):
global glContext
global window
global boxDrawer
global crateTexture
global green3Texture
global boxCollider
global box2Collider
global floorCollider
global lineDrawer
if not sdl3.SDL_Init(sdl3.SDL_INIT_VIDEO):
sdl3.SDL_Log("Couldn't initialize SDL: %s".encode() % sdl3.SDL_GetError())
return sdl3.SDL_APP_FAILURE
sdl3.SDL_GL_SetAttribute(sdl3.SDL_GL_MULTISAMPLEBUFFERS, 1) # Enable MULTISAMPLE
sdl3.SDL_GL_SetAttribute(sdl3.SDL_GL_MULTISAMPLESAMPLES, 2) # Can be 2, 4, 8 or 16
windowTitle = "PySDL3, PyGLM, Pillow, OpenGL, Python".encode()
window = sdl3.SDL_CreateWindow(windowTitle, canvasWidth, canvasHeight,
sdl3.SDL_WINDOW_OPENGL | sdl3.SDL_WINDOW_RESIZABLE)
if not window:
sdl3.SDL_Log("Couldn't create a window: %s".encode() % sdl3.SDL_GetError())
return sdl3.SDL_APP_FAILURE
# Create an OpenGL context
glContext = sdl3.SDL_GL_CreateContext(window)
if not glContext:
sdl3.SDL_Log("Couldn't create a glContext: %s".encode() % sdl3.SDL_GetError())
return sdl3.SDL_APP_FAILURE
sdl3.SDL_GL_SetSwapInterval(1) # Turn on vertical sync
glClearColor(0.15, 0.15, 0.15, 1)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
vertexShaderSource = None
fragmentShaderSource = None
with open("./assets/shaders/texture.vert") as file:
vertexShaderSource = file.read()
with open("./assets/shaders/texture.frag") as file:
fragmentShaderSource = file.read()
textureProgram = compileProgram(
compileShader(vertexShaderSource, GL_VERTEX_SHADER),
compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER))
with open("./assets/shaders/color.vert") as file:
vertexShaderSource = file.read()
with open("./assets/shaders/color.frag") as file:
fragmentShaderSource = file.read()
colorProgram = compileProgram(
compileShader(vertexShaderSource, GL_VERTEX_SHADER),
compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER))
boxDrawer = BoxDrawer(textureProgram)
crateTexture = loadTexture("./assets/images/crate.png")
green3Texture = loadTexture("./assets/images/green-3.png")
floorName = "floor"
floorPosition = glm.vec3(0, -10, 0)
floorRotation = glm.quat(1, 0, 0, 0)
floorSize = glm.vec3(20, 20, 20)
floorCollider = Collider(floorName, floorPosition,
floorRotation, floorSize, 0, world)
floorCollider.node.setFriction(1)
boxName = "box"
boxPosition = glm.vec3(7, 2, 0)
boxRotation = glm.quat(1, 0, 0, 0)
boxSize = glm.vec3(3, 3, 3)
boxCollider = Collider(boxName, boxPosition,
boxRotation, boxSize, 10, world)
boxCollider.node.setAngularFactor(Vec3(0, 0, 0))
boxCollider.node.setFriction(1)
boxCollider.node.setLinearSleepThreshold(False)
box2Name = "box2"
# box2Position = glm.vec3(2, 10, 0)
box2Position = glm.vec3(0, 3, 0)
box2Rotation = glm.quat(1, 0, 0, 0)
box2Size = glm.vec3(3, 3, 3)
box2Collider = Collider(box2Name, box2Position,
box2Rotation, box2Size, 10, world)
box2Collider.node.setFriction(1)
box2Collider.node.setLinearSleepThreshold(False)
# Create an object for drawing segments
lineDrawer = LineDrawer(colorProgram, projViewMatrix)
return sdl3.SDL_APP_CONTINUE
@sdl3.SDL_AppEvent_func
def SDL_AppEvent(appstate, event):
global projViewMatrix
if sdl3.SDL_DEREFERENCE(event).type == sdl3.SDL_EVENT_QUIT:
return sdl3.SDL_APP_SUCCESS
elif sdl3.SDL_DEREFERENCE(event).type == sdl3.SDL_EVENT_WINDOW_RESIZED:
w = sdl3.SDL_GetWindowSurface(window).contents.w
h = sdl3.SDL_GetWindowSurface(window).contents.h
glViewport(0, 0, w, h)
projMatrix = glm.perspective(glm.radians(fovy), w / h, near, far)
projViewMatrix = projMatrix * viewMatrix
lineDrawer.setProjViewMatrix(projViewMatrix)
elif sdl3.SDL_DEREFERENCE(event).type == sdl3.SDL_EVENT_KEY_DOWN:
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_W or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_UP
):
keys["up"] = True
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_S or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_DOWN
):
keys["down"] = True
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_A or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_LEFT
):
keys["left"] = True
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_D or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_RIGHT
):
keys["right"] = True
elif sdl3.SDL_DEREFERENCE(event).type == sdl3.SDL_EVENT_KEY_UP:
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_W or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_UP
):
keys["up"] = False
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_S or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_DOWN
):
keys["down"] = False
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_A or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_LEFT
):
keys["left"] = False
if (
sdl3.SDL_DEREFERENCE(event).key.scancode == sdl3.SDL_SCANCODE_D or
sdl3.SDL_DEREFERENCE(event).button.button == sdl3.SDL_SCANCODE_RIGHT
):
keys["right"] = False
return sdl3.SDL_APP_CONTINUE
def keyboard():
if keys["up"]:
boxCollider.node.setLinearVelocity(Vec3(0, 0, -5))
if keys["down"]:
boxCollider.node.setLinearVelocity(Vec3(0, 0, 5))
if keys["left"]:
boxCollider.node.setLinearVelocity(Vec3(-5, 0, 0))
if keys["right"]:
boxCollider.node.setLinearVelocity(Vec3(5, 0, 0))
@sdl3.SDL_AppIterate_func
def SDL_AppIterate(appstate):
keyboard()
world.doPhysics(0.016)
boxCollider.update()
box2Collider.update()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if showCollider:
vertices = getCubeVertices(box2Collider.position, box2Collider.rotation, 1.5)
# Create a cube
# v5----- v1
# /| /|
# v4------v0|
# | | | |
# | |v7---|-|v3
# |/ |/
# v6------v2
# v0 - v1
lineDrawer.draw(start = vertices[0], end = vertices[1],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v0 - v2
lineDrawer.draw(start = vertices[0], end = vertices[2],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v2 - v3
lineDrawer.draw(start = vertices[2], end = vertices[3],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v3 - v1
lineDrawer.draw(start = vertices[3], end = vertices[1],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v1 - v0
lineDrawer.draw(start = vertices[1], end = vertices[0],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v4 - v5
lineDrawer.draw(start = vertices[4], end = vertices[5],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v5 - v7
lineDrawer.draw(start = vertices[5], end = vertices[7],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v7 - v6
lineDrawer.draw(start = vertices[7], end = vertices[6],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v6 - v4
lineDrawer.draw(start = vertices[6], end = vertices[4],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v0 - v4
lineDrawer.draw(start = vertices[0], end = vertices[4],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v2 - v6
lineDrawer.draw(start = vertices[2], end = vertices[6],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v3 - v7
lineDrawer.draw(start = vertices[3], end = vertices[7],
color = glm.vec3(0, 1, 0), thickness = 0.2)
# v1 - v5
lineDrawer.draw(start = vertices[1], end = vertices[5],
color = glm.vec3(0, 1, 0), thickness = 0.2)
axesSize = 50
# X
lineDrawer.draw(start = glm.vec3(-axesSize, 0, 0), end = glm.vec3(axesSize, 0, 0),
color = glm.vec3(1, 0, 0), thickness = 0.1)
# Y
lineDrawer.draw(start = glm.vec3(0, -axesSize, 0), end = glm.vec3(0, axesSize, 0),
color = glm.vec3(0, 1, 0), thickness = 0.1)
# Z
lineDrawer.draw(start = glm.vec3(0, 0, -axesSize), end = glm.vec3(0, 0, axesSize),
color = glm.vec3(0, 0, 1), thickness = 0.1)
# Static collider
position = floorCollider.position
size = floorCollider.size
rotation = floorCollider.rotation
boxDrawer.draw(projViewMatrix, position, size, rotation, green3Texture)
# Box
position = boxCollider.position
size = boxCollider.size
rotation = boxCollider.rotation
boxDrawer.draw(projViewMatrix, position, size, rotation, crateTexture)
# Box2
position = box2Collider.position
size = box2Collider.size
rotation = box2Collider.rotation
boxDrawer.draw(projViewMatrix, position, size, rotation, crateTexture)
sdl3.SDL_GL_SwapWindow(window)
return sdl3.SDL_APP_CONTINUE
@sdl3.SDL_AppQuit_func
def SDL_AppQuit(appstate, result):
global glContext
sdl3.SDL_GL_DestroyContext(glContext)
# SDL will clean up the window/renderer for us
math_helper.py
import math
import glm
class MathHelper:
# Sets a quat from the given angle and rotation axis, then returns it
@staticmethod
def setAxisAngle(axis, rad):
rad = rad * 0.5
s = math.sin(rad)
out = glm.quat()
out.x = s * axis[0]
out.y = s * axis[1]
out.z = s * axis[2]
out.w = math.cos(rad)
return out
# This is port of https://glmatrix.net/docs/quat.js.html#line652
# Sets a quaternion to represent the shortest rotation from one vector to another
# Both vectors are assumed to be unit length
@staticmethod
def rotationTo(initialVector, destinationVector):
xUnitVec3 = glm.vec3(1, 0, 0)
yUnitVec3 = glm.vec3(0, 1, 0)
out = glm.quat()
dot = glm.dot(destinationVector, initialVector)
if dot < -0.999999:
tmpvec3 = glm.cross(initialVector, xUnitVec3)
if glm.length(tmpvec3) < 0.000001:
tmpvec3 = glm.cross(initialVector, yUnitVec3)
tmpvec3 = glm.normalize(tmpvec3)
out = MathHelper.setAxisAngle(tmpvec3, math.pi)
return out
elif dot > 0.999999:
out.x = 0
out.y = 0
out.z = 0
out.w = 1
return out
else:
tmpvec3 = glm.cross(initialVector, destinationVector)
out.x = tmpvec3[0]
out.y = tmpvec3[1]
out.z = tmpvec3[2]
out.w = 1 + dot
return glm.normalize(out)
texture_loader.py
from OpenGL.GL import *
from PIL import Image
def loadTexture(path):
image = Image.open(path)
data = image.convert("RGBA").tobytes()
glEnable(GL_TEXTURE_2D)
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, data)
glBindTexture(GL_TEXTURE_2D, 0)
return texture
To run this example you should:
pip Panda3D PySDL3 numpy PyOpenGL Pillow PyGLM
py main.py
- Move a cube with WASD or arrow-keys
- You can disable drawing of colliders by setting the
showColliders
variable to False
in the beginning of the main.cpp
I will try to write a sphere with lines later. I will explain a little how I solve it below.
I can get the position and quaternion of object:
def update(self):
self.position.x = self.node.getTransform().pos.x
self.position.y = self.node.getTransform().pos.y
self.position.z = self.node.getTransform().pos.z
hpr = self.node.getTransform().getHpr()
quat = Quat()
quat.setHpr(hpr)
self.rotation.w = quat.getX()
self.rotation.x = quat.getY()
self.rotation.y = quat.getZ()
self.rotation.z = quat.getW()
I asked the following question to Deep Seek: I know the position, size and quaternion of the cube. How do I find out the coordinates of the vertices?
I asked to show examples in three languages: Python (PyGLM), C++ (GLM), JavaScript (glMatrix):
Python Implementation with PyGLM
import glm # Make sure PyGLM is installed: `pip install PyGLM`
def get_cube_vertices(position, size, rotation_quat):
# Half-size for local vertices
s = size * 0.5
# Local vertices (untransformed, centered at origin)
local_vertices = [
glm.vec3( s, s, s), # v0
glm.vec3( s, s, -s), # v1
glm.vec3( s, -s, s), # v2
glm.vec3( s, -s, -s), # v3
glm.vec3(-s, s, s), # v4
glm.vec3(-s, s, -s), # v5
glm.vec3(-s, -s, s), # v6
glm.vec3(-s, -s, -s) # v7
]
# Rotate and translate each vertex
world_vertices = []
for vertex in local_vertices:
# Apply rotation (quaternion * vertex)
rotated = rotation_quat * vertex
# Apply translation
world_vertex = rotated + position
world_vertices.append(world_vertex)
return world_vertices
# Example usage
position = glm.vec3(1.0, 2.0, 3.0) # Cube center
size = 2.0 # Side length
rotation_quat = glm.quat(glm.vec3(0, glm.radians(45), 0)) # 45° around Y-axis
vertices = get_cube_vertices(position, size, rotation_quat)
# Print vertices
for i, vertex in enumerate(vertices):
print(f"v{i}: ({vertex.x:.2f}, {vertex.y:.2f}, {vertex.z:.2f})")
Output:
The program prints the world-space coordinates of all 8 vertices, e.g.:
v0: (2.41, 3.00, 3.00)
v1: (2.41, 3.00, 1.00)
v2: (0.00, 1.00, 3.00)
...
C++ Implementation with GLM
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <vector>
#include <iostream>
std::vector<glm::vec3> getCubeVertices(
const glm::vec3& position,
float size,
const glm::quat& rotation
) {
// Half-size for local vertices
float s = size * 0.5f;
// Local vertices (untransformed, centered at origin)
std::vector<glm::vec3> localVertices = {
{ s, s, s}, // v0
{ s, s, -s}, // v1
{ s, -s, s}, // v2
{ s, -s, -s}, // v3
{-s, s, s}, // v4
{-s, s, -s}, // v5
{-s, -s, s}, // v6
{-s, -s, -s} // v7
};
// Rotate and translate each vertex
std::vector<glm::vec3> worldVertices;
for (const auto& vertex : localVertices) {
// Apply rotation (quaternion * vertex)
glm::vec3 rotated = rotation * vertex;
// Apply translation
glm::vec3 worldVertex = rotated + position;
worldVertices.push_back(worldVertex);
}
return worldVertices;
}
int main() {
// Example usage
glm::vec3 position(1.0f, 2.0f, 3.0f); // Cube center
float size = 2.0f; // Side length
glm::quat rotation = glm::angleAxis(glm::radians(45.0f), glm::vec3(0.0f, 1.0f, 0.0f)); // 45° around Y-axis
std::vector<glm::vec3> vertices = getCubeVertices(position, size, rotation);
// Print vertices
for (size_t i = 0; i < vertices.size(); ++i) {
std::cout << "v" << i << ": ("
<< vertices[i].x << ", "
<< vertices[i].y << ", "
<< vertices[i].z << ")\n";
}
return 0;
}
Output:
The program prints the world-space coordinates of all 8 vertices, e.g.:
v0: (2.41421, 3, 3.0)
v1: (2.41421, 3, 1.0)
v2: (0.0, 1, 3.0)
...
JavaScript Implementation with glMatrix
import { vec3, quat } from 'gl-matrix';
function getCubeVertices(position, size, rotationQuat) {
// Half-size for local vertices
const s = size * 0.5;
// Local vertices (untransformed, centered at origin)
const localVertices = [
[ s, s, s], // v0
[ s, s, -s], // v1
[ s, -s, s], // v2
[ s, -s, -s], // v3
[-s, s, s], // v4
[-s, s, -s], // v5
[-s, -s, s], // v6
[-s, -s, -s] // v7
];
// Rotate and translate each vertex
const worldVertices = [];
const rotated = vec3.create();
const worldVertex = vec3.create();
for (const vertex of localVertices) {
// Apply rotation (quaternion * vertex)
vec3.transformQuat(rotated, vertex, rotationQuat);
// Apply translation
vec3.add(worldVertex, rotated, position);
worldVertices.push([...worldVertex]); // Convert to plain array
}
return worldVertices;
}
// Example usage
const position = [1.0, 2.0, 3.0]; // Cube center
const size = 2.0; // Side length
const rotationQuat = quat.create();
quat.setAxisAngle(rotationQuat, [0, 1, 0], Math.PI / 4); // 45° around Y-axis
const vertices = getCubeVertices(position, size, rotationQuat);
// Print vertices
vertices.forEach((vertex, i) => {
console.log(`v${i}: (${vertex[0].toFixed(2)}, ${vertex[1].toFixed(2)}, ${vertex[2].toFixed(2)})`);
});
Output:
The program logs the world-space coordinates of all 8 vertices, e.g.:
v0: (2.41, 3.00, 3.00)
v1: (2.41, 3.00, 1.00)
v2: (0.00, 1.00, 3.00)
...