You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.8 KiB
263 lines
9.8 KiB
# |
|
# This file is part of Magnum. |
|
# |
|
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
|
# Vladimír Vondruš <mosra@centrum.cz> |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a |
|
# copy of this software and associated documentation files (the "Software"), |
|
# to deal in the Software without restriction, including without limitation |
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
# and/or sell copies of the Software, and to permit persons to whom the |
|
# Software is furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included |
|
# in all copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
# DEALINGS IN THE SOFTWARE. |
|
# |
|
|
|
import sys |
|
import unittest |
|
|
|
from magnum import * |
|
from magnum import scenegraph |
|
from magnum.scenegraph.matrix import Object3D, Scene3D |
|
|
|
class Object(unittest.TestCase): |
|
def test_hierarchy(self): |
|
scene = Scene3D() |
|
scene_refcount = sys.getrefcount(scene) |
|
|
|
a = Object3D() |
|
a_refcount = sys.getrefcount(a) |
|
self.assertIs(a.scene, None) |
|
|
|
b = Object3D(parent=scene) |
|
b_refcount = sys.getrefcount(b) |
|
self.assertIs(b.scene, scene) |
|
self.assertIs(b.parent, scene) |
|
|
|
# B should be referenced by the scene, but not cyclically |
|
self.assertEqual(sys.getrefcount(b), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(scene), scene_refcount) |
|
|
|
c = Object3D(parent=b) |
|
c_refcount = sys.getrefcount(c) |
|
self.assertIs(c.scene, scene) |
|
self.assertIs(c.parent, b) |
|
|
|
# C should be referenced by B |
|
self.assertEqual(sys.getrefcount(b), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(c), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(scene), scene_refcount) |
|
|
|
# Delete B. Because B has a parent as well, it's not deleted yet |
|
del b |
|
self.assertIsNotNone(c.parent) |
|
self.assertEqual(sys.getrefcount(c.parent), b_refcount - 1) |
|
self.assertEqual(sys.getrefcount(c), scene_refcount + 1) |
|
|
|
# Delete a scene. That also makes B deleted and C is then orphaned |
|
del scene |
|
self.assertIsNone(c.parent) |
|
self.assertEqual(sys.getrefcount(c), c_refcount - 1) |
|
|
|
def test_hierarchy_set_parent(self): |
|
# Same as test_hierarchy, but setting the parent later |
|
|
|
scene = Scene3D() |
|
scene_refcount = sys.getrefcount(scene) |
|
|
|
a = Object3D() |
|
a_refcount = sys.getrefcount(a) |
|
self.assertIs(a.scene, None) |
|
|
|
b = Object3D() |
|
b.parent = scene |
|
b_refcount = sys.getrefcount(b) |
|
self.assertIs(b.scene, scene) |
|
self.assertIs(b.parent, scene) |
|
|
|
# B should be referenced by the scene, but not cyclically |
|
self.assertEqual(sys.getrefcount(b), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(scene), scene_refcount) |
|
|
|
c = Object3D() |
|
c.parent = b |
|
c_refcount = sys.getrefcount(c) |
|
self.assertIs(c.scene, scene) |
|
self.assertIs(c.parent, b) |
|
|
|
# C should be referenced by B |
|
self.assertEqual(sys.getrefcount(b), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(c), scene_refcount + 1) |
|
self.assertEqual(sys.getrefcount(scene), scene_refcount) |
|
|
|
# Delete B. Because B has a parent as well, it's not deleted yet |
|
del b |
|
self.assertIsNotNone(c.parent) |
|
self.assertEqual(sys.getrefcount(c.parent), b_refcount - 1) |
|
self.assertEqual(sys.getrefcount(c), scene_refcount + 1) |
|
|
|
# Delete a scene. That also makes B deleted and C is then orphaned |
|
del scene |
|
self.assertIsNone(c.parent) |
|
self.assertEqual(sys.getrefcount(c), c_refcount - 1) |
|
|
|
def test_set_parent_invalid(self): |
|
a = Object3D() |
|
with self.assertRaisesRegex(TypeError, "expected Scene, Object or None, got <class 'str'>"): |
|
a.parent = "noo" |
|
|
|
def test_transformation(self): |
|
scene = Scene3D() |
|
|
|
a = Object3D(scene) |
|
a.rotate_local(Deg(35.0), Vector3.x_axis()) |
|
self.assertEqual(a.transformation, Matrix4.rotation_x(Deg(35.0))) |
|
self.assertEqual(a.absolute_transformation(), Matrix4.rotation_x(Deg(35.0))) |
|
|
|
b = Object3D(a) |
|
b.translate((3.0, 4.0, 5.0)) |
|
self.assertEqual(b.transformation, Matrix4.translation((3.0, 4.0, 5.0))) |
|
self.assertEqual(b.absolute_transformation(), |
|
Matrix4.rotation_x(Deg(35.0))@ |
|
Matrix4.translation((3.0, 4.0, 5.0))) |
|
|
|
c = Object3D(scene) |
|
self.assertEqual(c.transformation, Matrix4.identity_init()) |
|
self.assertEqual(c.absolute_transformation(), Matrix4.identity_init()) |
|
|
|
def test_drawable(self): |
|
object = Object3D() |
|
object_refcount = sys.getrefcount(object) |
|
|
|
a = scenegraph.Drawable3D(object) |
|
a_refcount = sys.getrefcount(a) |
|
self.assertIs(a.object, object) |
|
|
|
b = scenegraph.Drawable3D(object) |
|
b_refcount = sys.getrefcount(b) |
|
self.assertIs(b.object, object) |
|
|
|
# Drawables should be referenced by the object, but not cyclically |
|
self.assertEqual(sys.getrefcount(object), object_refcount) |
|
self.assertEqual(sys.getrefcount(a), object_refcount + 1) |
|
self.assertEqual(sys.getrefcount(b), object_refcount + 1) |
|
|
|
# Delete the object. The drawable should be still alive, but |
|
# disconnected from the object (and thus useless). |
|
del object |
|
self.assertIsNone(a.object) |
|
self.assertIsNone(b.object) |
|
self.assertEqual(sys.getrefcount(a), a_refcount - 1) |
|
self.assertEqual(sys.getrefcount(b), b_refcount - 1) |
|
|
|
def test_drawable_group(self): |
|
object = Object3D() |
|
drawables = scenegraph.DrawableGroup3D() |
|
|
|
deleted = 0 |
|
class MyDrawable(scenegraph.Drawable3D): |
|
def __del__(self): |
|
nonlocal deleted |
|
deleted += 1 |
|
|
|
a = MyDrawable(object, drawables) |
|
b = MyDrawable(object, drawables) |
|
|
|
# The drawable group should have these listed |
|
self.assertEqual([i for i in drawables], [a, b]) |
|
|
|
# Deleting each of them should do nothing, since they're still |
|
# referenced by the object |
|
del a, b |
|
self.assertEqual(deleted, 0) |
|
self.assertEqual(len(drawables), 2) |
|
|
|
# Deleting the holder object will, tho |
|
del object |
|
self.assertEqual(deleted, 2) |
|
self.assertEqual(len(drawables), 0) |
|
|
|
def test_camera(self): |
|
object = Object3D() |
|
object.translate(Vector3.z_axis(5.0)) |
|
object_refcount = sys.getrefcount(object) |
|
|
|
a = scenegraph.Camera3D(object) |
|
a.viewport = (400, 300) |
|
a.projection_matrix = Matrix4.perspective_projection( |
|
fov=Deg(45.0), near=0.01, far=100.0, aspect_ratio=1.0) |
|
a.aspect_ratio_policy = scenegraph.AspectRatioPolicy.EXTEND |
|
a_refcount = sys.getrefcount(a) |
|
self.assertEqual(a.viewport, Vector2i(400, 300)) |
|
self.assertEqual(a.projection_matrix, Matrix4.perspective_projection( |
|
fov=Deg(57.82240), near=0.01, far=100.0, aspect_ratio=1.33333333)) |
|
self.assertEqual(a.camera_matrix, Matrix4.translation(-Vector3.z_axis(5.0))) |
|
self.assertEqual(a.aspect_ratio_policy, scenegraph.AspectRatioPolicy.EXTEND) |
|
self.assertIs(a.object, object) |
|
|
|
# Camera should be referenced by the object, but not cyclically |
|
self.assertEqual(sys.getrefcount(object), object_refcount) |
|
self.assertEqual(sys.getrefcount(a), object_refcount + 1) |
|
|
|
# Delete the object. The camera should be still alive, but disconnected |
|
# from the object (and thus useless). |
|
del object |
|
self.assertIsNone(a.object) |
|
self.assertEqual(sys.getrefcount(a), a_refcount - 1) |
|
|
|
def test_camera_draw(self): |
|
scene = Scene3D() |
|
drawables = scenegraph.DrawableGroup3D() |
|
|
|
camera_object = Object3D(scene) |
|
camera_object.translate((0.0, 1.0, 5.0)) |
|
|
|
camera = scenegraph.Camera3D(camera_object) |
|
|
|
rendered = None, None |
|
deleted = "no :)" |
|
class MyDrawable(scenegraph.Drawable3D): |
|
def draw(self, transformation_matrix: Matrix4, camera: scenegraph.Camera3D): |
|
nonlocal rendered |
|
rendered = (transformation_matrix, camera) |
|
|
|
def __del__(self): |
|
nonlocal deleted |
|
deleted = "yes :(" |
|
|
|
class MySilentDrawable(scenegraph.Drawable3D): |
|
def draw(self, transformation_matrix: Matrix4, camera: scenegraph.Camera3D): |
|
pass |
|
|
|
object = Object3D(scene) |
|
object.translate(Vector3.x_axis(5.0)) |
|
a = MyDrawable(object, drawables) |
|
b = MySilentDrawable(object, drawables) |
|
|
|
# The drawable group should have these listed |
|
self.assertEqual([i for i in drawables], [a, b]) |
|
|
|
# Deleting the object, the camera holder and drawable does nothing |
|
del camera_object, object, a, b |
|
|
|
camera.draw(drawables) |
|
self.assertEqual(rendered[0], |
|
Matrix4.translation(Vector3.x_axis(5.0))@ |
|
Matrix4.translation((0.0, -1.0, -5.0))) |
|
self.assertIs(rendered[1], camera) |
|
|
|
# Deleting the scene will delete A and the drawable as well |
|
del scene |
|
self.assertEqual(deleted, "yes :(") |
|
self.assertIsNone(camera.object) |
|
self.assertIs(len(drawables), 0)
|
|
|