From 3efe451d3c3c7c992ec547ed91231227f59e4355 Mon Sep 17 00:00:00 2001 From: Mohammad Bashiri Date: Tue, 12 Jun 2018 13:24:21 +0200 Subject: [PATCH 1/5] generalized the CameraGroup class, and added StereoCameraGroup --- ratcave/__init__.py | 2 +- ratcave/camera.py | 41 ++++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/ratcave/__init__.py b/ratcave/__init__.py index 28ad6f9..98476c8 100644 --- a/ratcave/__init__.py +++ b/ratcave/__init__.py @@ -8,7 +8,7 @@ except ImportError: pass from . import utils -from .camera import Camera, PerspectiveProjection, OrthoProjection, CameraGroup +from .camera import Camera, PerspectiveProjection, OrthoProjection, CameraGroup, StereoCameraGroup from .collision import CylinderCollisionChecker, SphereCollisionChecker from .fbo import FBO from .gl_states import GLStateManager, default_states diff --git a/ratcave/camera.py b/ratcave/camera.py index 5dfbfd7..b02c790 100644 --- a/ratcave/camera.py +++ b/ratcave/camera.py @@ -251,17 +251,29 @@ def projection_matrix(self): class CameraGroup(PhysicalGraph): - def __init__(self, position=(0, 0, 0), rotation=(0, 0, 0), distance=.1, look_at=(0, 0, 0), projection=None, *args, **kwargs): + def __init__(self, cameras=None, *args, **kwargs): """ Creates a group of cameras that behave dependently""" - super(CameraGroup, self).__init__(position=position, rotation=rotation, *args, **kwargs) - self.cam_left = Camera(position=(-distance / 2, 0., 0.)) - self.cam_right = Camera(position=(distance / 2, 0., 0.)) - self.projection = PerspectiveProjection() if not projection else projection + super(CameraGroup, self).__init__(*args, **kwargs) + self.cameras = cameras + self.add_children(*self.cameras) + + def look_at(self, x, y, z): + """Converges the two cameras to look at the specific point""" + for camera in self.cameras: + camera.look_at(x, y, z) + + +class StereoCameraGroup(CameraGroup): + def __init__(self, distance=.1, projection=None, *args, **kwargs): + """ Creates a group of cameras that behave dependently""" + + self.left = Camera(position=(-distance / 2, 0., 0.)) + self.right = Camera(position=(distance / 2, 0., 0.)) + super(StereoCameraGroup, self).__init__(cameras=[self.left, self.right], *args, **kwargs) + self.projection = PerspectiveProjection() if not projection else projection self.distance = distance - self.look_at(*look_at) - self.add_children(self.cam_left, self.cam_right) @property def projection(self): @@ -270,19 +282,14 @@ def projection(self): @projection.setter def projection(self, value): self._projection = value - self.cam_left.projection = self._projection - self.cam_right.projection = self._projection + for camera in self.cameras: + camera.projection = self._projection @property def distance(self): - return self.cam_right.position.x - self.cam_left.position.x + return self.right.position.x - self.left.position.x @distance.setter def distance(self, value): - self.cam_left.position.x = -value / 2 - self.cam_right.position.x = value / 2 - - def look_at(self, x, y, z): - """Converges the two cameras to look at the specific point""" - self.cam_left.look_at(x, y, z) - self.cam_right.look_at(x, y, z) \ No newline at end of file + self.left.position.x = -value / 2 + self.right.position.x = value / 2 \ No newline at end of file From e7fc00be688914c629573c11368e26eb791ed374 Mon Sep 17 00:00:00 2001 From: Mohammad Bashiri Date: Tue, 12 Jun 2018 15:35:18 +0200 Subject: [PATCH 2/5] added convergence method to StereoCameraGroup --- ratcave/camera.py | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/ratcave/camera.py b/ratcave/camera.py index b02c790..bd014c5 100644 --- a/ratcave/camera.py +++ b/ratcave/camera.py @@ -65,6 +65,16 @@ def update(self): def viewport(self): return get_viewport() + def copy(self): + """Returns a copy of the projection matrix""" + params = {} + for key, val in self.__dict__.items(): + if 'matrix' not in key: + k = key[1:] if key[0] == '_' else key + params[k] = val + # params = {param: params[param] for param in params} + return self.__class__(**params) + ScreenEdges = namedtuple('ScreenEdges', 'left right bottom top') @@ -266,24 +276,18 @@ def look_at(self, x, y, z): class StereoCameraGroup(CameraGroup): - def __init__(self, distance=.1, projection=None, *args, **kwargs): + def __init__(self, distance=.1, projection=None, convergence=0., *args, **kwargs): """ Creates a group of cameras that behave dependently""" - - self.left = Camera(position=(-distance / 2, 0., 0.)) - self.right = Camera(position=(distance / 2, 0., 0.)) - super(StereoCameraGroup, self).__init__(cameras=[self.left, self.right], *args, **kwargs) - self.projection = PerspectiveProjection() if not projection else projection + cameras = [Camera(projection=projection) for _ in range(2)] + super(StereoCameraGroup, self).__init__(cameras=cameras, *args, **kwargs) + for camera, x in zip(self.cameras, [-distance / 2, distance / 2]): + project = projection.copy() if isinstance(projection, ProjectionBase) else PerspectiveProjection() + camera.projection = project + camera.position.x = x + + self.left, self.right = self.cameras self.distance = distance - - @property - def projection(self): - return self._projection - - @projection.setter - def projection(self, value): - self._projection = value - for camera in self.cameras: - camera.projection = self._projection + self.convergence = convergence @property def distance(self): @@ -292,4 +296,15 @@ def distance(self): @distance.setter def distance(self, value): self.left.position.x = -value / 2 - self.right.position.x = value / 2 \ No newline at end of file + self.right.position.x = value / 2 + + @property + def convergence(self): + return self._convergence + + @convergence.setter + def convergence(self, value): + self.left.projection.x_shift = value + self.right.projection.x_shift = -value + self._convergence = value + \ No newline at end of file From 6524dff7efd3dbeb9454d65ecab363667502f786 Mon Sep 17 00:00:00 2001 From: Mohammad Bashiri Date: Tue, 12 Jun 2018 15:55:15 +0200 Subject: [PATCH 3/5] added and modified tests for CameraGroup and StereoCamereGroup --- tests/test_camera.py | 63 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/tests/test_camera.py b/tests/test_camera.py index 80cfa6d..3d50dcf 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -1,4 +1,4 @@ -from ratcave import Camera, CameraGroup, PerspectiveProjection, OrthoProjection +from ratcave import Camera, CameraGroup, StereoCameraGroup, PerspectiveProjection, OrthoProjection import pytest import numpy as np import pyglet @@ -96,52 +96,65 @@ def test_projection_matrix_updates_when_assigning_new_projection(): assert (cam.projection_matrix == old_projmat).all() -def test_cameragroup_contains_two_cameras(): - cam = CameraGroup() - assert hasattr(cam, "cam_right") and hasattr(cam, "cam_left") +def test_cameraggroups_has_all_cameras(): + cameras_n = np.random.randint(1, 11) + cameras = [Camera() for _ in range(cameras_n)] + + cam = CameraGroup(cameras=cameras) + assert len(cam.cameras) == cameras_n def test_cameras_are_cameragroup_childrens(): - cam = CameraGroup() - assert cam.cam_right in cam.children - assert cam.cam_left in cam.children + cameras_n = np.random.randint(1, 11) + cameras = [Camera() for _ in range(cameras_n)] + + cam = CameraGroup(cameras=cameras) + for camera in cam.cameras: + assert camera in cam.children + +def test_stereocameragroup_contains_two_cameras(): + cam = StereoCameraGroup() + assert hasattr(cam, "right") and hasattr(cam, "left") -def test_cameras_have_correct_distance(): + +def test_cameras_have_correct_distance_in_stereocameragroup(): dist = 10 - cam = CameraGroup(distance=dist) + cam = StereoCameraGroup(distance=dist) assert cam.distance == dist - assert (cam.cam_right.position.x - cam.cam_left.position.x) == cam.distance + assert (cam.right.position.x - cam.left.position.x) == cam.distance cam.distance = 20 - assert (cam.cam_right.position.x - cam.cam_left.position.x) == cam.distance + assert (cam.right.position.x - cam.left.position.x) == cam.distance -def test_projection_instance_is_shared_between_cameragroup_and_its_children(): - cam = CameraGroup() - assert (cam.projection == cam.cam_right.projection) and (cam.projection == cam.cam_left.projection) +def test_projection_instance_is_not_shared_between_children_of_stereocameragroup(): + cam = StereoCameraGroup() + assert (cam.left.projection is not cam.right.projection) - cam.projection = OrthoProjection() - assert isinstance(cam.projection, OrthoProjection) - assert (cam.projection == cam.cam_right.projection) and (cam.projection == cam.cam_left.projection) + cam.left.projection = OrthoProjection() + assert isinstance(cam.left.projection, OrthoProjection) + assert isinstance(cam.right.projection, PerspectiveProjection) + assert (cam.left.projection is not cam.right.projection) def test_look_at_updates_for_children(): dist = 2. - cam = CameraGroup(distance=dist) + cam = StereoCameraGroup(distance=dist) point = np.array([0, 0, 0, 1]).reshape(-1, 1) point[2] = -1 #np.random.randint(1, 6) angle = np.arctan(point[2]/(cam.distance/2))[0] - cam.cam_right.rotation.y = -np.rad2deg(angle) - cam.cam_left.rotation.y = np.rad2deg(angle) - point_view_mat_left = np.dot(cam.cam_left.view_matrix, point) - point_view_mat_right = np.dot(cam.cam_right.view_matrix, point) + cam.right.rotation.y = -np.rad2deg(angle) + cam.left.rotation.y = np.rad2deg(angle) + point_view_mat_left = np.dot(cam.left.view_matrix, point) + point_view_mat_right = np.dot(cam.right.view_matrix, point) assert (point_view_mat_left == point_view_mat_right).all() - cam2 = CameraGroup(distance=dist, look_at=point[:3]) - point_view_mat_left2 = np.dot(cam2.cam_left.view_matrix, point) - point_view_mat_right2 = np.dot(cam2.cam_right.view_matrix, point) + cam2 = StereoCameraGroup(distance=dist) + cam2.look_at(*point[:3]) + point_view_mat_left2 = np.dot(cam2.left.view_matrix, point) + point_view_mat_right2 = np.dot(cam2.right.view_matrix, point) assert (point_view_mat_left == point_view_mat_left2).all() and (point_view_mat_right == point_view_mat_right2).all() From a38bc159352fc39a1259d2cb0b7232fc2a0e01c7 Mon Sep 17 00:00:00 2001 From: Mohammad Bashiri Date: Tue, 12 Jun 2018 18:50:32 +0200 Subject: [PATCH 4/5] added a test to cover copy method used in StereoCameraGroup --- tests/test_camera.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_camera.py b/tests/test_camera.py index 3d50dcf..02cd609 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -128,6 +128,12 @@ def test_cameras_have_correct_distance_in_stereocameragroup(): assert (cam.right.position.x - cam.left.position.x) == cam.distance +def test_projection_set_when_given_as_input_to_StereoCameraGroup(): + cam = StereoCameraGroup(projection=OrthoProjection()) + assert isinstance(cam.left.projection, OrthoProjection) + assert isinstance(cam.left.projection, OrthoProjection) + + def test_projection_instance_is_not_shared_between_children_of_stereocameragroup(): cam = StereoCameraGroup() assert (cam.left.projection is not cam.right.projection) From 697b008a3ada10a05017a938f61aa20c8244b1a1 Mon Sep 17 00:00:00 2001 From: Mohammad Bashiri Date: Tue, 12 Jun 2018 18:52:53 +0200 Subject: [PATCH 5/5] added test for convergence property --- tests/test_camera.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_camera.py b/tests/test_camera.py index 02cd609..83aab6d 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -128,10 +128,12 @@ def test_cameras_have_correct_distance_in_stereocameragroup(): assert (cam.right.position.x - cam.left.position.x) == cam.distance -def test_projection_set_when_given_as_input_to_StereoCameraGroup(): - cam = StereoCameraGroup(projection=OrthoProjection()) +def test_attributes_given_as_input_to_StereoCameraGroup(): + cam = StereoCameraGroup(projection=OrthoProjection(), convergence=10.) assert isinstance(cam.left.projection, OrthoProjection) assert isinstance(cam.left.projection, OrthoProjection) + assert cam.convergence == 10. + def test_projection_instance_is_not_shared_between_children_of_stereocameragroup():