Skip to content

Instantly share code, notes, and snippets.

@8Observer8
Created January 29, 2023 12:52
Show Gist options
  • Save 8Observer8/ac4d3cd1a41c9bb6c93449f6c7422e01 to your computer and use it in GitHub Desktop.
Save 8Observer8/ac4d3cd1a41c9bb6c93449f6c7422e01 to your computer and use it in GitHub Desktop.
Click detection with AABB, Box2D, OpenGL 2.1, PyQt6, Python 3.8
precision mediump float;
uniform vec3 uColor;
void main()
{
gl_FragColor = vec4(uColor, 1.0);
}
attribute vec2 aPosition;
uniform mat4 uMvpMatrix;
void main()
{
gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);
}
from Box2D import b2Draw
from OpenGL.GL import *
from PyQt6.QtGui import QMatrix4x4, QQuaternion, QVector3D
class DebugDrawer(b2Draw):
def __init__(self, program, worldScale):
super().__init__()
self.program = program
self.WORLD_SCALE = worldScale
self.mvpMatrixLocation = self.program.uniformLocation("uMvpMatrix")
self.colorLocation = self.program.uniformLocation("uColor")
self.modelMatrix = QMatrix4x4()
self.projMatrix = None
self.viewMatrix = None
self.projViewMatrix = None
self.color = None
self.lineWidth = 4
def DrawSolidPolygon(self, vertexes, color):
# print("Polygon. Begin")
# print(vertexes[0][0] * 30, vertexes[0][1] * 30)
# print(vertexes[1][0] * 30, vertexes[1][1] * 30)
# print(vertexes[2][0] * 30, vertexes[2][1] * 30)
# print(vertexes[3][0] * 30, vertexes[3][1] * 30)
# print("Polygon. End")
self.projViewMatrix = self.projMatrix * self.viewMatrix
self.color = QVector3D(color[0], color[1], color[2])
self.drawLine(vertexes[0], vertexes[1])
self.drawLine(vertexes[1], vertexes[2])
self.drawLine(vertexes[2], vertexes[3])
self.drawLine(vertexes[3], vertexes[0])
def drawLine(self, pointA, pointB):
centerX, centerY = 0, 0
tempVec = QVector3D()
fromX = pointA[0] * self.WORLD_SCALE
fromY = pointA[1] * self.WORLD_SCALE
toX = pointB[0] * self.WORLD_SCALE
toY = pointB[1] * self.WORLD_SCALE
if fromX > toX:
centerX = toX + abs(fromX - toX) / 2
else:
centerX = fromX + abs(toX - fromX) / 2
if fromY > toY:
centerY = toY + abs(fromY - toY) / 2
else:
centerY = fromY + abs(toY - fromY) / 2
tempVec.setX(toX - fromX)
tempVec.setY(toY - fromY)
length = tempVec.length()
tempVec.normalize()
quat = QQuaternion.rotationTo(QVector3D(1, 0, 0), tempVec)
self.modelMatrix.setToIdentity()
self.modelMatrix.translate(QVector3D(centerX, centerY, 0))
self.modelMatrix.rotate(quat)
self.modelMatrix.scale(QVector3D(length, self.lineWidth, 1))
self.mvpMatrix = self.projViewMatrix * self.modelMatrix
self.program.setUniformValue(self.mvpMatrixLocation, self.mvpMatrix)
self.program.setUniformValue(self.colorLocation, self.color)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
def DrawPolygon(self, vertexes, color):
pass
def DrawSegment(self, p1, p2, color):
pass
def DrawPoint(self, p, size, color):
pass
def DrawCircle(self, center, radius, color, drawwidth=1):
pass
def DrawSolidCircle(self, center, radius, axis, color):
pass
def DrawTransform(self, xf):
pass
import math
import sys
import numpy as np
from Box2D import (b2_staticBody, b2AABB, b2BodyDef, b2FixtureDef,
b2PolygonShape, b2Vec2, b2World)
from OpenGL.GL import *
from PyQt6.QtCore import QFile, QIODevice, QSize, Qt
from PyQt6.QtGui import QMatrix4x4, QSurfaceFormat, QVector3D
from PyQt6.QtOpenGL import QOpenGLBuffer, QOpenGLShader, QOpenGLShaderProgram
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtWidgets import QApplication
from debug_drawer import DebugDrawer
from query_callback import QueryCallback
class OpenGLWidget(QOpenGLWidget):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(300, 300))
self.setWindowTitle("OpenGL21 PyQt6 Python")
self.boxPosition = QVector3D(150, 150, 0)
self.boxAngle = 30
self.boxSize = QVector3D(150, 150, 1)
self.world = b2World(gravity=(0, 0))
self.pixelsPerMeter = 30
boxShape = b2PolygonShape()
boxShape.SetAsBox(self.boxSize.x() / 2 / self.pixelsPerMeter,
self.boxSize.y() / 2 / self.pixelsPerMeter)
boxBodyDef = b2BodyDef(userData="box")
boxBodyDef.type = b2_staticBody
boxBody = self.world.CreateBody(boxBodyDef)
boxFixtureDef = b2FixtureDef()
boxFixtureDef.shape = boxShape
boxBody.CreateFixture(boxFixtureDef)
boxBody.position = b2Vec2(self.boxPosition.x() / self.pixelsPerMeter,
self.boxPosition.y() / self.pixelsPerMeter)
boxBody.angle = math.radians(self.boxAngle)
def initializeGL(self):
glClearColor(0.1, 0.3, 0.2, 1)
self.colorProgram = QOpenGLShaderProgram(self)
self.colorProgram.addShaderFromSourceFile(
QOpenGLShader.ShaderTypeBit.Vertex, "assets/shaders/color.vert")
self.colorProgram.addShaderFromSourceFile(
QOpenGLShader.ShaderTypeBit.Fragment, "assets/shaders/color.frag")
self.colorProgram.link()
self.colorProgram.bind()
vertPositions = np.array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5], dtype=np.float32)
self.vertPosBuffer = QOpenGLBuffer()
self.vertPosBuffer.create()
self.vertPosBuffer.bind()
self.vertPosBuffer.allocate(vertPositions, len(vertPositions) * 4)
self.aPositionLocation = self.colorProgram.attributeLocation("aPosition")
self.colorProgram.setAttributeBuffer(self.aPositionLocation, GL_FLOAT, 0, 2)
self.colorProgram.enableAttributeArray(self.aPositionLocation)
self.mvpMatrix = QMatrix4x4()
self.modelMatrix = QMatrix4x4()
self.viewMatrix = QMatrix4x4()
self.projMatrix = QMatrix4x4()
self.projViewMatrix = QMatrix4x4()
self.viewMatrix.lookAt(
QVector3D(0, 0, 1),
QVector3D(0, 0, 0),
QVector3D(0, 1, 0))
self.uMvpMatrixLocation = self.colorProgram.uniformLocation("uMvpMatrix")
self.uColorLocation = self.colorProgram.uniformLocation("uColor")
self.debugDrawer = DebugDrawer(self.colorProgram, self.pixelsPerMeter)
self.debugDrawer.flags = { "drawShapes": True }
self.world.renderer = self.debugDrawer
def resizeGL(self, w, h):
glViewport(0, 0, w, h)
self.projMatrix.setToIdentity()
self.projMatrix.ortho(0, 300, 0, 300, 1, -1)
self.debugDrawer.projMatrix = self.projMatrix
self.debugDrawer.viewMatrix = self.viewMatrix
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT)
self.projViewMatrix = self.projMatrix * self.viewMatrix
self.world.DrawDebugData()
self.modelMatrix.setToIdentity()
self.modelMatrix.translate(self.boxPosition)
self.modelMatrix.rotate(self.boxAngle, QVector3D(0, 0, 1))
self.modelMatrix.scale(self.boxSize)
self.mvpMatrix = self.projViewMatrix * self.modelMatrix
self.colorProgram.setUniformValue(self.uMvpMatrixLocation, self.mvpMatrix)
self.colorProgram.setUniformValue(self.uColorLocation, QVector3D(1, 0, 0))
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
x = event.pos().x() / self.pixelsPerMeter
y = (self.width() - event.pos().y() - 1) / self.pixelsPerMeter
p = b2Vec2(x, y)
# Make a small box
aabb = b2AABB(lowerBound=p-(0.001, 0.001), upperBound=p+(0.001, 0.001))
# Query the world for overlapping shapes
query = QueryCallback()
self.world.QueryAABB(query, aabb)
if __name__ == "__main__":
QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL)
app = QApplication(sys.argv)
w = OpenGLWidget()
format = QSurfaceFormat()
format.setSamples(4)
w.setFormat(format)
w.show()
sys.exit(app.exec())
from Box2D import b2QueryCallback
class QueryCallback(b2QueryCallback):
def __init__(self):
b2QueryCallback.__init__(self)
def ReportFixture(self, fixture):
name = fixture.body.userData
print(name)
# Continue the query by returning True
return True
@8Observer8
Copy link
Author

8Observer8 commented Jan 29, 2023

At the moment, the Box2D module works with Python version 3.8 maximum.

pip install Box2D numpy PyOpenGL PyQt6

The yellow rectangle was drawn after the screenshot was taken.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment