276 lines
7.2 KiB
Python
276 lines
7.2 KiB
Python
# -------------------------------
|
|
# Difficult Rocket
|
|
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
|
|
# All rights reserved
|
|
# -------------------------------
|
|
# Huge thanks to pyglet developers
|
|
|
|
from typing import Tuple, Optional
|
|
|
|
from pyglet.gl import gl
|
|
from pyglet.math import Mat4, Vec3
|
|
from pyglet.graphics import Group
|
|
|
|
|
|
class Camera:
|
|
"""
|
|
|
|
>>> from pyglet.window import Window
|
|
>>> window = Window()
|
|
|
|
>>> camera = Camera(window)
|
|
>>> @window.event
|
|
|
|
>>> def on_draw():
|
|
>>> camera.begin()
|
|
>>> window.clear()
|
|
>>> camera.end()
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
window,
|
|
zoom: Optional[float] = 1.0,
|
|
dx: Optional[float] = 1.0,
|
|
dy: Optional[float] = 1.0,
|
|
min_zoom: Optional[float] = 1.0,
|
|
max_zoom: Optional[float] = 1.0,
|
|
) -> None:
|
|
self.window = window
|
|
self.dx = dx or 1.0
|
|
self.dy = dy or 1.0
|
|
|
|
self.zoom = zoom or 1.0
|
|
self.min_zoom = min_zoom or 1.0
|
|
self.max_zoom = max_zoom or 1.0
|
|
|
|
self._stored_view = window.view
|
|
|
|
@property
|
|
def position(self) -> Tuple[float, float]:
|
|
return self.dx, self.dy
|
|
|
|
@position.setter
|
|
def position(self, value: Tuple[float, float]):
|
|
self.dx, self.dy = value
|
|
|
|
@property
|
|
def zoom_level(self) -> float:
|
|
return self.zoom
|
|
|
|
@zoom_level.setter
|
|
def zoom_level(self, value: float) -> None:
|
|
self.zoom = min(max(value, self.min_zoom), self.max_zoom)
|
|
|
|
def begin(self) -> None:
|
|
view = self.window.view
|
|
self._stored_view = view
|
|
x = self.window.width / self.zoom + (self.dx / self.zoom)
|
|
y = self.window.height / self.zoom + (self.dy / self.zoom)
|
|
|
|
view_matrix = view.translate((x * self.zoom, y * self.zoom, 0))
|
|
view_matrix = view_matrix.scale((self.zoom, self.zoom, 1))
|
|
|
|
self.window.view = view_matrix
|
|
|
|
def end(self) -> None:
|
|
self.window.view = self._stored_view
|
|
|
|
def reset(self):
|
|
self.zoom = 1
|
|
self.dx = 0
|
|
self.dy = 0
|
|
|
|
def __enter__(self):
|
|
self.begin()
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.end()
|
|
|
|
|
|
class CenterCamera(Camera):
|
|
"""
|
|
A camera that centers the view in the center of the window
|
|
|
|
>>> from pyglet.window import Window
|
|
>>> window = Window()
|
|
|
|
>>> camera = CenterCamera(window)
|
|
>>> @window.event
|
|
|
|
>>> def on_draw():
|
|
>>> camera.begin()
|
|
>>> window.clear()
|
|
>>> camera.end()
|
|
"""
|
|
|
|
def begin(self) -> None:
|
|
view = self.window.view
|
|
self._stored_view = view
|
|
x = self.window.width / 2.0 / self.zoom + (self.dx / self.zoom)
|
|
y = self.window.height / 2.0 / self.zoom + (self.dy / self.zoom)
|
|
|
|
view_matrix = view.translate((x * self.zoom, y * self.zoom, 0))
|
|
view_matrix = view_matrix.scale((self.zoom, self.zoom, 1))
|
|
|
|
self.window.view = view_matrix
|
|
|
|
def end(self) -> None:
|
|
self.window.view = self._stored_view
|
|
|
|
|
|
class GroupCamera(Group):
|
|
"""
|
|
A camera by group
|
|
can be used by just added to your widget
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
window,
|
|
order: int = 0,
|
|
parent: Optional[Group] = None,
|
|
view_x: Optional[int] = 0,
|
|
view_y: Optional[int] = 0,
|
|
zoom: Optional[float] = 1.0,
|
|
min_zoom: Optional[float] = 1.0,
|
|
max_zoom: Optional[float] = 1.0,
|
|
):
|
|
super().__init__(order=order, parent=parent)
|
|
self._window = window
|
|
self._previous_view = None
|
|
|
|
self._view_x = view_x or 0
|
|
self._view_y = view_y or 0
|
|
self._zoom = zoom or 1.0
|
|
self.min_zoom = min_zoom or 1.0
|
|
self.max_zoom = max_zoom or 1.0
|
|
|
|
@property
|
|
def view_x(self) -> int:
|
|
return self._view_x
|
|
|
|
@view_x.setter
|
|
def view_x(self, value: int):
|
|
self._view_x = value
|
|
|
|
@property
|
|
def view_y(self) -> int:
|
|
return self._view_y
|
|
|
|
@view_y.setter
|
|
def view_y(self, value: int):
|
|
self._view_y = value
|
|
|
|
@property
|
|
def zoom(self) -> float:
|
|
return min(max(self._zoom, self.min_zoom), self.max_zoom)
|
|
|
|
@zoom.setter
|
|
def zoom(self, value: float):
|
|
self._zoom = value
|
|
|
|
def reset(self):
|
|
self._view_x = 0
|
|
self._view_y = 0
|
|
self.zoom = 1
|
|
|
|
def set_state(self):
|
|
self._previous_view = self._window.view
|
|
|
|
view = Mat4.from_translation(Vec3(self._view_x, self._view_y, 0))
|
|
if self._zoom == 1.0:
|
|
self._window.view = view
|
|
else:
|
|
view = view.scale(Vec3(self._zoom, self._zoom, 1))
|
|
self._window.view = view
|
|
|
|
def unset_state(self):
|
|
self._window.view = self._previous_view
|
|
|
|
|
|
class CenterGroupCamera(GroupCamera):
|
|
"""
|
|
A camera by group
|
|
can be used by just added to your widget
|
|
"""
|
|
|
|
def set_state(self):
|
|
self._previous_view = self._window.view
|
|
x = (self._window.width / 2) / self._zoom + (self._view_x / self._zoom)
|
|
y = (self._window.height / 2) / self._zoom + (self._view_y / self._zoom)
|
|
|
|
view = Mat4.from_translation(Vec3(x * self._zoom, y * self._zoom, 0))
|
|
# 不懂就问 为啥这里 * zoom 下面还 * zoom
|
|
if self._zoom == 1.0:
|
|
self._window.view = view
|
|
else:
|
|
view = view.scale(Vec3(self._zoom, self._zoom, 1))
|
|
self._window.view = view
|
|
|
|
def unset_state(self):
|
|
self._window.view = self._previous_view
|
|
|
|
|
|
class CenterGroupFrame(Group):
|
|
"""
|
|
A camera by group
|
|
can be used by just added to your widget
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
window,
|
|
order: int = 0,
|
|
parent: Optional[Group] = None,
|
|
dx: Optional[int] = 0,
|
|
dy: Optional[int] = 0,
|
|
width: Optional[int] = 0,
|
|
height: Optional[int] = 0,
|
|
zoom: Optional[float] = 1.0,
|
|
min_zoom: Optional[float] = 1.0,
|
|
max_zoom: Optional[float] = 1.0,
|
|
):
|
|
super().__init__(order=order, parent=parent)
|
|
self.window = window
|
|
self.dx = dx or 0
|
|
self.dy = dy or 0
|
|
|
|
self._width = width or 0
|
|
self._height = height or 0
|
|
|
|
self._zoom = zoom or 1.0
|
|
self.min_zoom = min_zoom or 1.0
|
|
self.max_zoom = max_zoom or 1.0
|
|
|
|
@property
|
|
def zoom(self) -> float:
|
|
return self._zoom
|
|
|
|
@zoom.setter
|
|
def zoom(self, value: float):
|
|
self._zoom = min(max(value, self.min_zoom), self.max_zoom)
|
|
|
|
def set_state(self):
|
|
self._previous_view = self.window.view
|
|
|
|
gl.glScissor(int(self.dx), int(self.dy), int(self._width), int(self._height))
|
|
gl.glViewport(
|
|
int(self.dx), int(self.dy), int(self.window.width), int(self.window.height)
|
|
)
|
|
gl.glEnable(gl.GL_SCISSOR_TEST)
|
|
|
|
x = (self.window.width / 2) / self._zoom + (self.dx / self._zoom)
|
|
y = (self.window.height / 2) / self._zoom + (self.dy / self._zoom)
|
|
|
|
view = Mat4.from_translation(Vec3(x * self._zoom, y * self._zoom, 0))
|
|
view.scale(Vec3(self._zoom, self._zoom, 1))
|
|
self.window.view = view
|
|
|
|
def unset_state(self):
|
|
self.window.view = self._previous_view
|
|
gl.glDisable(gl.GL_SCISSOR_TEST)
|
|
gl.glViewport(0, 0, int(self.window.width), int(self.window.height))
|