ПРЕДПОСЫЛКИ
Позвольте мне начать вопрос, предоставив некоторый шаблонный код, который мы будем использовать для игры:
mcve_framework.py:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import glm
from glm import cross, normalize, unProject, vec2, vec3, vec4
# -------- Camera --------
class BaseCamera():
def __init__(
self,
eye=None, target=None, up=None,
fov=None, near=0.1, far=100000,
delta_zoom=10
):
self.eye = eye or glm.vec3(0, 0, 1)
self.target = target or glm.vec3(0, 0, 0)
self.up = up or glm.vec3(0, 1, 0)
self.original_up = glm.vec3(self.up)
self.fov = fov or glm.radians(45)
self.near = near
self.far = far
self.delta_zoom = delta_zoom
def update(self, aspect):
self.view = glm.lookAt(
self.eye, self.target, self.up
)
self.projection = glm.perspective(
self.fov, aspect, self.near, self.far
)
def move(self, dx, dy, dz, dt):
if dt == 0:
return
forward = normalize(self.target - self.eye) * dt
right = normalize(cross(forward, self.up)) * dt
up = self.up * dt
offset = right * dx
self.eye += offset
self.target += offset
offset = up * dy
self.eye += offset
self.target += offset
offset = forward * dz
self.eye += offset
self.target += offset
def zoom(self, *args):
x = args[2]
y = args[3]
v = glGetIntegerv(GL_VIEWPORT)
viewport = vec4(float(v[0]), float(v[1]), float(v[2]), float(v[3]))
height = viewport.w
pt_wnd = vec3(x, height - y, 1.0)
pt_world = unProject(pt_wnd, self.view, self.projection, viewport)
ray_cursor = glm.normalize(pt_world - self.eye)
delta = args[1] * self.delta_zoom
self.eye = self.eye + ray_cursor * delta
self.target = self.target + ray_cursor * delta
def load_projection(self):
width = glutGet(GLUT_WINDOW_WIDTH)
height = glutGet(GLUT_WINDOW_HEIGHT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(glm.degrees(self.fov), width / height, self.near, self.far)
def load_modelview(self):
e = self.eye
t = self.target
u = self.up
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(e.x, e.y, e.z, t.x, t.y, t.z, u.x, u.y, u.z)
class GlutController():
FPS = 0
ORBIT = 1
PAN = 2
def __init__(self, camera, velocity=100, velocity_wheel=100):
self.velocity = velocity
self.velocity_wheel = velocity_wheel
self.camera = camera
def glut_mouse(self, button, state, x, y):
self.mouse_last_pos = vec2(x, y)
self.mouse_down_pos = vec2(x, y)
if button == GLUT_LEFT_BUTTON:
self.mode = self.FPS
elif button == GLUT_RIGHT_BUTTON:
self.mode = self.ORBIT
else:
self.mode = self.PAN
def glut_motion(self, x, y):
pos = vec2(x, y)
move = self.mouse_last_pos - pos
self.mouse_last_pos = pos
if self.mode == self.FPS:
self.camera.rotate_target(move * 0.005)
elif self.mode == self.ORBIT:
self.camera.rotate_around_origin(move * 0.005)
def glut_mouse_wheel(self, *args):
self.camera.zoom(*args)
def process_inputs(self, keys, dt):
dt *= 10 if keys[' '] else 1
ammount = self.velocity * dt
if keys['w']:
self.camera.move(0, 0, 1, ammount)
if keys['s']:
self.camera.move(0, 0, -1, ammount)
if keys['d']:
self.camera.move(1, 0, 0, ammount)
if keys['a']:
self.camera.move(-1, 0, 0, ammount)
if keys['q']:
self.camera.move(0, -1, 0, ammount)
if keys['e']:
self.camera.move(0, 1, 0, ammount)
if keys['+']:
self.camera.fov += radians(ammount)
if keys['-']:
self.camera.fov -= radians(ammount)
# -------- Mcve --------
class BaseWindow:
def __init__(self, w, h, camera):
self.width = w
self.height = h
glutInit()
glutSetOption(GLUT_MULTISAMPLE, 16)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE)
glutInitWindowSize(w, h)
glutCreateWindow('OpenGL Window')
self.keys = {chr(i): False for i in range(256)}
self.startup()
glutReshapeFunc(self.reshape)
glutDisplayFunc(self.display)
glutMouseFunc(self.controller.glut_mouse)
glutMotionFunc(self.controller.glut_motion)
glutMouseWheelFunc(self.controller.glut_mouse_wheel)
glutKeyboardFunc(self.keyboard_func)
glutKeyboardUpFunc(self.keyboard_up_func)
glutIdleFunc(self.idle_func)
def keyboard_func(self, *args):
try:
key = args[0].decode("utf8")
if key == "\x1b":
glutLeaveMainLoop()
self.keys[key] = True
except Exception as e:
import traceback
traceback.print_exc()
def keyboard_up_func(self, *args):
try:
key = args[0].decode("utf8")
self.keys[key] = False
except Exception as e:
pass
def startup(self):
raise NotImplementedError
def display(self):
raise NotImplementedError
def run(self):
glutMainLoop()
def idle_func(self):
glutPostRedisplay()
def reshape(self, w, h):
glViewport(0, 0, w, h)
self.width = w
self.height = h
Если вы хотите использовать приведенный выше код, вам просто нужно установить pyopengl и pygml.После этого вы можете просто создать свой собственный подкласс BaseWindow
, переопределить startup
и render
, и у вас должно появиться очень простое окно переналадки с простыми функциями, такими как поворот / масштабирование камеры, а также некоторые методы для визуализации точек / треугольников./ quads and indexed_triangles / indexed_quads.
ЧТО СДЕЛАНО
mcve_camera_arcball.py
import time
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import glm
from mcve_framework import BaseCamera, BaseWindow, GlutController
def line(p0, p1, color=None):
c = color or glm.vec3(1, 1, 1)
glColor3f(c.x, c.y, c.z)
glVertex3f(p0.x, p0.y, p0.z)
glVertex3f(p1.x, p1.y, p1.z)
def grid(segment_count=10, spacing=1, yup=True):
size = segment_count * spacing
right = glm.vec3(1, 0, 0)
forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
x_axis = right * size
z_axis = forward * size
i = -segment_count
glBegin(GL_LINES)
while i <= segment_count:
p0 = -x_axis + forward * i * spacing
p1 = x_axis + forward * i * spacing
line(p0, p1)
p0 = -z_axis + right * i * spacing
p1 = z_axis + right * i * spacing
line(p0, p1)
i += 1
glEnd()
def axis(size=1.0, yup=True):
right = glm.vec3(1, 0, 0)
forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
x_axis = right * size
z_axis = forward * size
y_axis = glm.cross(forward, right) * size
glBegin(GL_LINES)
line(x_axis, glm.vec3(0, 0, 0), glm.vec3(1, 0, 0))
line(y_axis, glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))
line(z_axis, glm.vec3(0, 0, 0), glm.vec3(0, 0, 1))
glEnd()
class Camera(BaseCamera):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def rotate_target(self, delta):
right = glm.normalize(glm.cross(self.target - self.eye, self.up))
M = glm.mat4(1)
M = glm.translate(M, self.eye)
M = glm.rotate(M, delta.y, right)
M = glm.rotate(M, delta.x, self.up)
M = glm.translate(M, -self.eye)
self.target = glm.vec3(M * glm.vec4(self.target, 1.0))
def rotate_around_target(self, target, delta):
right = glm.normalize(glm.cross(self.target - self.eye, self.up))
ammount = (right * delta.y + self.up * delta.x)
M = glm.mat4(1)
M = glm.rotate(M, ammount.z, glm.vec3(0, 0, 1))
M = glm.rotate(M, ammount.y, glm.vec3(0, 1, 0))
M = glm.rotate(M, ammount.x, glm.vec3(1, 0, 0))
self.eye = glm.vec3(M * glm.vec4(self.eye, 1.0))
self.target = target
self.up = self.original_up
def rotate_around_origin(self, delta):
return self.rotate_around_target(glm.vec3(0), delta)
class McveCamera(BaseWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def startup(self):
glEnable(GL_DEPTH_TEST)
self.start_time = time.time()
self.camera = Camera(
eye=glm.vec3(200, 200, 200),
target=glm.vec3(0, 0, 0),
up=glm.vec3(0, 1, 0),
delta_zoom=30
)
self.model = glm.mat4(1)
self.controller = GlutController(self.camera)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
def display(self):
self.controller.process_inputs(self.keys, 0.005)
self.camera.update(self.width / self.height)
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.camera.load_projection()
self.camera.load_modelview()
glLineWidth(5)
axis(size=70, yup=True)
glLineWidth(1)
grid(segment_count=7, spacing=10, yup=True)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-1, 1, -1, 1, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glutSwapBuffers()
if __name__ == '__main__':
window = McveCamera(800, 600, Camera())
window.run()
TODO
Конечная цель здесь - выяснить, как имитировать вращение, используемое 3dsmax при нажатии Alt + MMB.
Прямо сейчас с текущим кодом вы можетеперемещайтесь с помощью клавиш WASDQE (сдвиг для ускорения), влево / вправо, чтобы вращать камеру вокруг центра / сцены или масштабирование с помощью колесика мыши.Как видите, значения смещения жестко заданы, просто настройте их так, чтобы они плавно работали в вашем боксе (я знаю, что существуют подходящие методы, позволяющие сделать векторы кинетики камеры независимыми от процессора, это не главное в моем вопросе)
ССЫЛКИ
Давайте попробуем немного разобрать, как ведет себя камера при нажатии alt + MMB на 3dsmax2018.
1) Поворот в «доме» (камера вДомой происходит, когда вы нажимаете кнопку «Домой» в правом верхнем углу гизмо, которая установит положение камеры в фиксированном месте и цель (0,0,0)):
2) Панорамирование и вращение:
3) Масштабирование / панорамирование и вращение:
4) Интерфейс пользователя
ВОПРОС: Итак, следующим будет добавление необходимых битов для реализации вращения арбалета при нажатии alt + MMB ... Я говорю вращение арбола, потому что я предполагаю 3dsМакс использует этот метод за кулисами, но я не совсем уверен, что это метод уsed по max, так что сначала я бы хотел, чтобы знал, какие именно математические выражения используются 3ds max при нажатии alt + MMB, а затем просто добавляют необходимый код в класс Camera
.для достижения этой задачи