pax_global_header00006660000000000000000000000064134561066660014527gustar00rootroot0000000000000052 comment=3cdb16286261b97c11997f02dbfd0bfa70db8229 Pyrr-0.10.3/000077500000000000000000000000001345610666600125445ustar00rootroot00000000000000Pyrr-0.10.3/.gitignore000066400000000000000000000004551345610666600145400ustar00rootroot00000000000000*.py[co] # Packages *.egg *.egg-info dist build eggs parts var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # OS-X thumbnails .DS_Store # vi swap files *.swp # Pycharm ide *.ideaPyrr-0.10.3/.travis.yml000066400000000000000000000002531345610666600146550ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" install: - pip install -r requirements-dev.txt --upgrade script: PYTHONPATH=. nosetests --exe Pyrr-0.10.3/CHANGELOG.md000066400000000000000000000214671345610666600143670ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.10.3] - 2019-04-19 - Fix some of the plane.create_(xz,xy,yz) not inverting correctly. - Fix incorrect distance in point_height_above_plane. ## [0.10.2] - 2019-03-13 - Add MANIFEST.in and add /tests and /docs to source dist (#90) ## [0.10.1] - Unreleased ## [0.10.0] - 2018-12-23 - Fix unit tests failing due to changes in numpy.testing (#77) - Drop Python 3.3 as numpy no longer supports it. - Ensure the latest versions of dependencies are installed when testing (#79) - Add quaternion exp method (#74) - Remove todo notes about quaternion methods that exist (#73) - Remove Python 2.6 Support (#76) - Add methods to create a quaternion from an axis (#72) - Handle negative values in ray_intersect_aabb (#71) - Made create_from_eulers use configurable order from euler.py (#69) - Affine matrix decomposition (#65) - Added equality operators for quaternion and matrices objects (#67) ## [0.9.2] - 2017-07-18 ### Added - Add `pyrr.vector3.generate_vertex_normals` ### Fixed - Fix ignored `normalize_result` in `pyrr.vector3.generate_normals` ## [0.9.1] - 2017-07-06 ### Fixed - Fix `dtype` to numpy array conversion. ## [0.9.0] - 2017-06-28 Thanks to [Szabolcs Dombi](https://github.com/cprogrammer1994) for his contributions to this release. ### Added - Add slerp / lerp to Quaternion. - Add American spelling variation of 'normalise' ('normalize'). - Deprecate matrix functions with `*_matrix` in the name. Use the new alternatives provided. - Add `create_look_at` in Matrix. ## [0.8.4] ### Fixed - Fix Matrix33 / Matrix44 object API multiplication being logically backwards. ## [0.8.3] ### Fixed - Fix rotation of zero length vector by a quaternion causing error ## [0.8.2] ### Fixed - Fix Matrix <-> Quaternion conversions ## [0.8.1] ### Removed - Remove notes about customising euler indices. If you change the axis indices then create_from_(x,y,z)_rotation will no longer work. ## [0.8.0] ### Changed - Change euler parameter order for create function, was (pitch, roll, yaw, ...), now (roll, pitch, yaw, ..). - Make eulers always use the indices for extracting and putting values. - Make euler indices configurable by modifying euler.index.(pitch,roll,yaw) ### Fixed - Fix euler -> matrix33 conversion. ### Added - Add euler.create_from_(x,y,z)_rotation. This ignores pitch, roll, yaw indices and is a straight insertion into the x, y z values of the array. - Add tests to ensure euler, quaternion, and matrix rotations are all equivalent. ## [0.7.2] ### Fixed - Merge #37 - fix test suite name ## [0.7.1] ### Fixed - Fix #36 Move tests inside pyrr, include in pkg ## [0.7.0] > Note: that this version corrected an issue with quaternion rotations (quaternion cross) being inverted. Please ensure any quaternion logic is updated accordingly. ### Fixed - Fixed quaternion cross product being reversed. ### Added - Add more tests including quaternion identities. - Added subtraction support to object api, this is to support np.allclose. ## [0.6.5] ### Added * Add tests for Vector3/4 |^ operators * Add support for numpy.number types ## [0.6.4] ### Fixed * Fix Vector3/4 normalise failing. ## [0.6.3] ### Added * Add support for +- Matrix33/44. Fixes #24. * Add scalar support to Matrix33/44. Fixes #23. * Add scalar support to Vector3/4. Fixes #22. ### Fixed * Update version (0.6.2 was marked as 0.6.1!) ## [0.6.2] * Remove import of unicode_literals. It seems to bebuggering up 'from pyrr import *' * Move import tests into each module ## [0.6.1] * Readd vector3|4.from_matrix44_translation. * Remove unused import * Use type(obj) instead of obj.__class__ * Add tests for = and += on NpProxy ## [0.6.0] * Remove unused imports. * Add docstrings to functions. * Remove Matrix44.translation. Multiply a vector by the matrix to get this. * Rename vector3|4.negative to inverse. This matches matrix. * Remove ambiguous conversions in object api Matrix44->Vector, etc. * Add Vector4.from_vector3 with w parameter, default w=0.0. * Add Vector4.xyw. ## [0.5.1] * Add multipledispatch to setup.py requirements ## [0.5.0] * Add quaternion.is_identity * Remove vector3.create_from_vector4, this is ambiguous and was assuming 3d graphics style conversion (w=1.0). Do this manually instead to remove ambiguities. * Remove vector3|4.create_from_matrix44_translation. This was assuming vector4 had w=1.0 again. Instead create a basic vector and multiply it by the matrix. * Move vector.cross into vector3. This is ambiguous for vector4. * Move vector.generate_normals into vector3. This is ambiguous for vector4. * Change how quaternion.apply_to_vector works. Previously vectors were converted to vec3s with w=0. Now vectors are converted to vec4s, with vector 4's being untouched. * Change how matrix44 works with vector4s. It no longer divides by W for vector4s. Vector 3's are converted to vec4s during the calculation and back again. They were previously not divided by W if W was 0. Now they are converted to [np.inf, ...] in the case that W is 0. * Remove conversions from Vector4 and Vector3 in the OO API. These were ambiguous. * Remove Vector3|4.vector3 and Vector3|4.vector4. These conversions were ambiguous, convert manually. * Remove ability to transform vector4's in matrix33.apply_to_vector. This was ambiguous. Perform the conversion manually then call this. Or convert to a matrix44 and then transform. * Make matrix44.create_perspective_projection_matrix use matrix44.create_perspective_projection_matrix_from_bounds based on code from GLU. ## [0.4.0] * Make vec4 w component default to 0. * Remove Vector3 and Vector4 interoperability ## [0.3.0] * Add object oriented API. * Fix python 3 errors and numpy deprecated calls (np.array != None). * Finally track down and fully resolve matrix / quaternion rotations. Matrices were rotating in the opposite direction. ## [0.2.4] * Remove unittest2 ## [0.2.3] * Add unittest2 on Travis for python 2.6 to enable skipping tests rather than commenting them out. * Fixing matrix / quaternion rotation differences. * Add tests for nearly all functions. * Fix plane.create_xy|yz|xz and add a distance parameter. * Change rect default to np.float, was np.int. * Clean up matrix33.create_direction_scale. * Fix some functions incorrectly using 'all_parameters_as_numpy_arrays', which would attempt to np.array the dtype parameters. * Fix geometric_tests.point_closest_point_on_line not keeping the normalised vector and therefore returning incorrect results. * Fix aambb calling np.zeros as np.zeroEs. * Rename aambb functions parameter bbs to aabbs. * Fix aambb.add_points. * Fix aabb.clamp_points. * Fix aabb.zeros. ## [0.2.2] * skipped ## [0.2.1] * Fix matrix44.create_from_eulers calling function with invalid params. * Fix quaternion inverse calling squared_length instead of length. * Fix matrix33.create_from_inverse_of_quaternion, had a - instead of a +. * Fix syntax error in quaternion.power. * Remove matrix33.apply_scale. Function was a duplicate of matrix33.create_from_scale with an incorrect name. [Unreleased]: https://github.com/adamlwgriffiths/Pyrr/compare/0.9.2...master [0.9.2]: https://github.com/adamlwgriffiths/Pyrr/compare/0.9.1...0.9.2 [0.9.1]: https://github.com/adamlwgriffiths/Pyrr/compare/0.9.0...0.9.1 [0.9.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.9.0 [0.8.4]: https://github.com/adamlwgriffiths/Pyrr/compare/0.8.3...0.8.4 [0.8.3]: https://github.com/adamlwgriffiths/Pyrr/compare/0.8.2...0.8.3 [0.8.2]: https://github.com/adamlwgriffiths/Pyrr/compare/0.8.1...0.8.2 [0.8.1]: https://github.com/adamlwgriffiths/Pyrr/compare/0.8.0...0.8.1 [0.8.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.8.0 [0.7.2]: https://github.com/adamlwgriffiths/Pyrr/compare/0.7.1...0.7.2 [0.7.1]: https://github.com/adamlwgriffiths/Pyrr/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.7.0 [0.6.5]: https://github.com/adamlwgriffiths/Pyrr/compare/0.6.4...0.6.5 [0.6.4]: https://github.com/adamlwgriffiths/Pyrr/compare/0.6.3...0.6.4 [0.6.3]: https://github.com/adamlwgriffiths/Pyrr/compare/0.6.2...0.6.3 [0.6.2]: https://github.com/adamlwgriffiths/Pyrr/compare/0.6.1...0.6.2 [0.6.1]: https://github.com/adamlwgriffiths/Pyrr/compare/0.6.0...0.6.1 [0.6.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.6.0 [0.5.1]: https://github.com/adamlwgriffiths/Pyrr/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.5.0 [0.4.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.4.0 [0.3.0]: https://github.com/adamlwgriffiths/Pyrr/tree/0.3.0 [0.2.4]: https://github.com/adamlwgriffiths/Pyrr/compare/0.2.3...0.2.4 [0.2.3]: https://github.com/adamlwgriffiths/Pyrr/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/adamlwgriffiths/Pyrr/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/adamlwgriffiths/Pyrr/tree/0.2.1 Pyrr-0.10.3/LICENSE000066400000000000000000000032311345610666600135500ustar00rootroot00000000000000In the original BSD license, both occurrences of the phrase "COPYRIGHT HOLDERS AND CONTRIBUTORS" in the disclaimer read "REGENTS AND CONTRIBUTORS". Here is the license template: Copyright (c) 2015, Adam Griffiths All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. Pyrr-0.10.3/MANIFEST.in000066400000000000000000000001511345610666600142770ustar00rootroot00000000000000include CHANGELOG.md LICENSE README.md graft tests graft docs global-exclude *.py[co] prune docs/buildPyrr-0.10.3/README000077700000000000000000000000001345610666600146762README.mdustar00rootroot00000000000000Pyrr-0.10.3/README.md000066400000000000000000000141551345610666600140310ustar00rootroot00000000000000# Pyrr [![Build Status](https://travis-ci.org/adamlwgriffiths/Pyrr.png?branch=master)](https://travis-ci.org/adamlwgriffiths/Pyrr) Provides 3D mathematical functions using the power of NumPy. ## Features - Object Oriented and Procedural interfaces - Matrix (3x3, 4x4) - Quaternion - Vector (3D, 4D) - Plane - Ray - Line / Line Segment (3D) - Rectangle (2D) - Axis Aligned Bounding Box (AABB / AAMBB) - Geometric collision / intersection testing ## Documentation [View Pyrr's documentation online](https://pyrr.readthedocs.org/en/latest/). ## Examples Maintain a rotation (quaternion) and translation (vector) and convert to a matrix ### Object Oriented Interface This is a long winded example to demonstrate various features. ```python from pyrr import Quaternion, Matrix44, Vector3 import numpy as np point = Vector3([1.,2.,3.]) orientation = Quaternion() translation = Vector3() scale = Vector3([1.,1.,1.]) # translate along X by 1 translation += [1.0, 0.0, 0.0] # rotate about Y by pi/2 rotation = Quaternion.from_y_rotation(np.pi / 2.0) orientation = rotation * orientation # create a matrix matrix = Matrix44.identity() # apply our translation matrix = matrix * Matrix44.from_translation(translation) # apply our orientation # we can multiply matricies and quaternions directly! matrix = matrix * orientation # apply our scale matrix = matrix * Matrix44.from_scale(scale) # transform our point by the matrix # vectors are transformable by matrices and quaternions directly point = matrix * point ``` ### Procedural Interface ```python from pyrr import quaternion, matrix44, vector3 import numpy as np point = vector3.create(1.,2.,3.) orientation = quaternion.create() translation = vector3.create() scale = vector3.create(1,1,1) # translate along X by 1 translation += [1.0, 0.0, 0.0] # rotate about Y by pi/2 rotation = quaternion.create_from_y_rotation(np.pi / 2.0) orientation = quaternion.cross(rotation, orientation) # create a matrix matrix = matrix44.create_identity() # apply our translation translation_matrix = matrix44.create_from_translation(translation) matrix = matrix44.multiply(matrix, translation_matrix) # apply our orientation orientation_matrix = matrix44.create_from_quaternion(orientation) matrix = matrix44.multiply(matrix, orientation_matrix) # start our matrix off using the scale scale_matrix = matrix44.create_from_scale(scale) matrix = matrix44.multiply(matrix, scale_matrix) # transform our point by the matrix point = matrix44.apply_to_vector(matrix, point) ``` ## Object Oriented Features ### Convertable types ```python from pyrr import Quaternion, Matrix33, Matrix44, Vector3, Vector4 v3 = Vector3([1.,0.,0.]) v4 = Vector4.from_vector3(v3, w=1.0) v3, w = Vector3.from_vector4(v4) m44 = Matrix44() q = Quaternion(m44) m33 = Matrix33(q) m33 = Matrix44().matrix33 m44 = Matrix33().matrix44 q = Matrix44().quaternion q = Matrix33().quaternion m33 = Quaternion().matrix33 m44 = Quaternion().matrix44 ``` ### Convenient Operators ```python from pyrr import Quaternion, Matrix44, Matrix33, Vector3, Vector4 import numpy as np # matrix multiplication m = Matrix44() * Matrix33() m = Matrix44() * Quaternion() m = Matrix33() * Quaternion() # matrix inverse m = ~Matrix44.from_x_rotation(np.pi) # quaternion multiplication q = Quaternion() * Quaternion() q = Quaternion() * Matrix44() q = Quaternion() * Matrix33() # quaternion inverse (conjugate) q = ~Quaternion() # quaternion dot product d = Quaternion() | Quaternion() # vector oprations v = Vector3() + Vector3() v = Vector4() - Vector4() # vector transform v = Quaternion() * Vector3() v = Matrix44() * Vector3() v = Matrix44() * Vector4() v = Matrix33() * Vector3() # dot and cross products dot = Vector3() | Vector3() cross = Vector3() ^ Vector3() ``` ## Installation Pyrr is in the PyPI database and can be installed via pip: ```s pip install pyrr ``` Pyrr requires the following software: - Python 2.7+ / 3.4+ - NumPy - [multipledispatch](https://github.com/mrocklin/multipledispatch/) ## Changelog [View the changelog](CHANGELOG.md). ## Authors - [Adam Griffiths](https://github.com/adamlwgriffiths/) - [Chris Bates](https://github.com/chrsbats/) - [Jakub Stasiak](https://github.com/jstasiak/) - [Bogdan Teleaga](https://github.com/bogdanteleaga/) - [Szabolcs Dombi](https://github.com/cprogrammer1994/) - [Korijn van Golen](https://github.com/Korijn/) - [Armin Becher](https://github.com/ArminBecher) Contributions are welcome. ## License Pyrr is released under the BSD 2-clause license (a very relaxed licence), but it is encouraged that any modifications are submitted back to the master for inclusion. Created by Adam Griffiths. Copyright (c) 2012, Twisted Pair Development. All rights reserved. twistedpairdevelopment.wordpress.com @twistedpairdev Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 1. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. Pyrr-0.10.3/ROADMAP.md000066400000000000000000000010561345610666600141530ustar00rootroot00000000000000Pyrr Roadmap ============ Goals ----- * Add all mathematical data types * 2D * 3D * Add all mathematical functions * collision detection * manipulation Tasks ----- * Complete AABB / AAMBB. * Add LERP / SLERP to matrix?. * Add numba / numexpr or some other numpy optimisation lib. * Fix vector.interpolate. Ongoing work ------------ * More tests! Everything needs to be tested! * Add all geometric tests (on-going). * Make all functions take Nd arrays, not just single values. * Optimise functions. Pyrr-0.10.3/TODO000066400000000000000000000013061345610666600132340ustar00rootroot00000000000000Quaternion Add 'difference' Add log Add squad Add more maths functions from here http://vvvv.org/documentation/3d-vector-mathematics array_index: make this take an N long array and calculate the index dynamically. collision: complete collision detection algs sphere / plane point / frustrum sphere / box sphere / sphere sphere / triangle sphere / N triangles box / plane box / frustrum box / box box / triangle box / N triangles point / sphere point / box point / plane point / frustrum point / point point / triangle point / N triangles triangle / triangle triangle / plane triangle / frustrum triangle / N triangles Interpolate: move vector.interpolate into here rename to linear add sin_interpolate etc Pyrr-0.10.3/docs/000077500000000000000000000000001345610666600134745ustar00rootroot00000000000000Pyrr-0.10.3/docs/Makefile000066400000000000000000000126751345610666600151470ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pyrr.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pyrr.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Pyrr" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pyrr" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." Pyrr-0.10.3/docs/make.bat000066400000000000000000000117551345610666600151120ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Pyrr.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Pyrr.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end Pyrr-0.10.3/docs/source/000077500000000000000000000000001345610666600147745ustar00rootroot00000000000000Pyrr-0.10.3/docs/source/api_aabb.rst000066400000000000000000000003461345610666600172470ustar00rootroot00000000000000.. _api_aabb: Axis Aligned Bounding Box ************************* AABB ==== .. automodule:: pyrr.aabb :members: :undoc-members: .. _api_aambb: AAMBB ===== .. automodule:: pyrr.aambb :members: :undoc-members: Pyrr-0.10.3/docs/source/api_euler.rst000066400000000000000000000001321345610666600174670ustar00rootroot00000000000000.. _api_euler: Euler ***** .. automodule:: pyrr.euler :members: :undoc-members: Pyrr-0.10.3/docs/source/api_geometric_tests.rst000066400000000000000000000002021345610666600215510ustar00rootroot00000000000000.. _api_geometric_tests: Geometric Tests *************** .. automodule:: pyrr.geometric_tests :members: :undoc-members: Pyrr-0.10.3/docs/source/api_geometry.rst000066400000000000000000000001461345610666600202130ustar00rootroot00000000000000.. _api_geometry: Geometry ******** .. automodule:: pyrr.geometry :members: :undoc-members: Pyrr-0.10.3/docs/source/api_integer.rst000066400000000000000000000001421345610666600200110ustar00rootroot00000000000000.. _api_integer: Integer ******* .. automodule:: pyrr.integer :members: :undoc-members: Pyrr-0.10.3/docs/source/api_line.rst000066400000000000000000000001271345610666600173060ustar00rootroot00000000000000.. _api_lines: Line **** .. automodule:: pyrr.line :members: :undoc-members: Pyrr-0.10.3/docs/source/api_matrix.rst000066400000000000000000000004011345610666600176560ustar00rootroot00000000000000.. _api_matrix: Matrix functions **************** .. _api_matrix33: Matrix33 ======== .. automodule:: pyrr.matrix33 :members: :undoc-members: .. _api_matrix44: Matrix44 ======== .. automodule:: pyrr.matrix44 :members: :undoc-members: Pyrr-0.10.3/docs/source/api_plane.rst000066400000000000000000000001321345610666600174520ustar00rootroot00000000000000.. _api_plane: Plane ***** .. automodule:: pyrr.plane :members: :undoc-members: Pyrr-0.10.3/docs/source/api_quaternion.rst000066400000000000000000000001561345610666600205460ustar00rootroot00000000000000.. _api_quaternion: Quaternion ********** .. automodule:: pyrr.quaternion :members: :undoc-members: Pyrr-0.10.3/docs/source/api_ray.rst000066400000000000000000000001221345610666600171450ustar00rootroot00000000000000.. _api_ray: Ray *** .. automodule:: pyrr.ray :members: :undoc-members: Pyrr-0.10.3/docs/source/api_rectangle.rst000066400000000000000000000001521345610666600203210ustar00rootroot00000000000000.. _api_rectangle: Rectangle ********* .. automodule:: pyrr.rectangle :members: :undoc-members: Pyrr-0.10.3/docs/source/api_sphere.rst000066400000000000000000000001361345610666600176450ustar00rootroot00000000000000.. _api_sphere: Sphere ****** .. automodule:: pyrr.sphere :members: :undoc-members: Pyrr-0.10.3/docs/source/api_trig.rst000066400000000000000000000001461345610666600173250ustar00rootroot00000000000000.. _api_trig: Trigonometry ************ .. automodule:: pyrr.trig :members: :undoc-members: Pyrr-0.10.3/docs/source/api_utils.rst000066400000000000000000000001421345610666600175140ustar00rootroot00000000000000.. _api_utils: Utilities ********* .. automodule:: pyrr.utils :members: :undoc-members: Pyrr-0.10.3/docs/source/api_vector.rst000066400000000000000000000006031345610666600176600ustar00rootroot00000000000000.. _api_vector: Vector functions **************** .. _api_vector_common: Common Vector Operations ======================== .. automodule:: pyrr.vector :members: :undoc-members: .. _api_vector3: Vector3 ======= .. automodule:: pyrr.vector3 :members: :undoc-members: .. _api_vector4: Vector4 ======= .. automodule:: pyrr.vector4 :members: :undoc-members: Pyrr-0.10.3/docs/source/conf.py000066400000000000000000000202521345610666600162740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Pyrr documentation build configuration file, created by # sphinx-quickstart on Mon Jul 2 16:14:36 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Pyrr' copyright = u'Adam Griffiths' with open('../../pyrr/version.py', 'r') as f: exec(f.read()) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Pyrrdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Pyrr.tex', u'Pyrr Documentation', u'Adam Griffiths', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pyrr', u'Pyrr Documentation', [u'Adam Griffiths'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Pyrr', u'Pyrr Documentation', u'Adam Griffiths', 'Pyrr', '3D mathematical functions using NumPy.', 'Mathematics'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} # ensure __init__ is always documented # http://stackoverflow.com/questions/5599254/how-to-use-sphinxs-autodoc-to-document-a-classs-init-self-method def skip(app, what, name, obj, skip, options): if name == "__init__": return False return skip def setup(app): app.connect("autodoc-skip-member", skip) Pyrr-0.10.3/docs/source/index.rst000066400000000000000000000014421345610666600166360ustar00rootroot00000000000000Pyrr Maths Library ****************** Pyrr is a Python mathematical library. Overview ======== .. toctree:: :maxdepth: 2 info_data_types info_geometric_tests Object Oriented API =================== .. toctree:: :maxdepth: 2 oo_api_matrix oo_api_quaternion oo_api_vector Procedural API ============== .. toctree:: :maxdepth: 2 api_aabb api_euler api_geometric_tests api_geometry api_integer api_line api_matrix api_plane api_quaternion api_ray api_rectangle api_sphere api_trig api_utils api_vector Development =========== .. toctree:: :maxdepth: 1 info_coding_standard info_contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` Pyrr-0.10.3/docs/source/info_coding_standard.rst000066400000000000000000000134101345610666600216630ustar00rootroot00000000000000.. _coding_standard: Coding Standard *************** * Follow PEP-8 .. _coding_standard_modules: Modules ======= Each value type is given its own module. Functions that reside in these modules include: * Creation * Conversion * Manipulation .. _coding_standard_functions: Functions ========= * Existing numpy operations shall be wrapped where it may not be obvious how to perform the operation. A good example:: def multiply(m1, m2): # it may not be obvious that the 'dot' operator is the # equivalent of multiplication when using arrays as matrices # so this is good to help point users in the right direction return numpy.dot(m1, m2) A bad example:: def add(v1, v2): # this functionality is too obvious and too basic return v1 + v2 * Functions shall not modify data in-place. * Procedural and Class implementations shall remain in lock-step. .. _coding_standard_function_names: Function names ============== * Each type shall provide convenience *create* functions for conversions and other initialisations. * Each type shall have a basic *create* function which returns a zero-ed type, where it makes sense. A good example:: # vector3.py def create(x=0., y=0., z=0., dtype=None): if isinstance(x, (list, np.ndarray)): raise ValueError('Function requires non-list arguments') return np.array([x,y,z], dtype=dtype) def create_unit_length_x(dtype=None): return np.array([1.0, 0.0, 0.0], dtype=dtype) def create_unit_length_y(dtype=None): return np.array([0.0, 1.0, 0.0], dtype=dtype) def create_unit_length_z(dtype=None): return np.array([0.0, 0.0, 1.0], dtype=dtype) A bad example:: # matrix33.py def create(): # matrices aren't initialised manually # so this isn't ok pass * Conversion functions shall be prefixed with *create_from_* followed by the type being converted from:: def create_from_matrix(matrix): # converts from one type to another # this would have to support both matrix33 and matrix44 pass def create_from_matrix33(matrix): # converts from a very specific type # only has to support matrix33 pass .. _coding_standard_supplimentary_data: Supplimentary Data ================== When dealing with arrays and other complex data types, it is helpful to provide methods to identify which array index relates to what data. A good method to do this is to provide a class definition which contains these values:: class index: x = 0 y = 1 z = 2 w = 3 .. _coding_standard_tests: Tests ===== * A test class for each module shall be provided in the *pyrr/test* directory. * A test class shall be the only class in the test module. * Each source file shall have its own test file. * Each test function shall have a test case associated with it * All test cases for a function shall be in a single test function .. _coding_standard_layout: Layout ====== These are not strict rules, but are merely suggestions to keep the layout of code in Pyrr consistent. * Code shall be spaced vertically where doing so helps the readability of complex mathematical functions. Vertical spacing shall be performed at variable or data type boundaries. A good example:: # laying out over multiple lines helps improve readability. # brackets and parenthesis are laid out to more clearly indicate # the end of an array / type. # where appropriate, values are still laid out horizontally. # provide links where appropriate # http://www.example.com/a/link/to/a/relevant/explanation/of/this/code my_value = numpy.array([ # X = some comment about how X is calculated (0.0, 0.0, 0.0), # Y = some comment about how Y is calculated (1.0, 1.0, 1.0) ], dtype=[('position', 'float32', (3,))] ) # laying out parameters vertically can improve readability. # we'll be less likely to accidently pass an invalid value # and we can more easily, and more clearly, add logic to the parameters. some_complex_function_call( param_one, param_two, param_three, param_four, True if param_five else False, ) A more complicated example:: return np.array( [ # m1 [ # m11 = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) 1.0 - 2.0 * (y2 + z2), # m21 = 2.0 * (q.x * q.y + q.w * q.z) 2.0 * (xy + wz), # m31 = 2.0 * (q.x * q.z - q.w * q.y) 2.0 * (xz - wy), ], # m2 [ # m12 = 2.0 * (q.x * q.y - q.w * q.z) 2.0 * (xy - wz), # m22 = 1.0 - 2.0 * (q.x * q.x + q.z * q.z) 1.0 - 2.0 * (x2 + z2), # m32 = 2.0 * (q.y * q.z + q.w * q.x) 2.0 * (yz + wx), ], # m3 [ # m13 = 2.0 * ( q.x * q.z + q.w * q.y) 2.0 * (xz + wy), # m23 = 2.0 * (q.y * q.z - q.w * q.x) 2.0 * (yz - wx), # m33 = 1.0 - 2.0 * (q.x * q.x + q.y * q.y) 1.0 - 2.0 * (x2 + y2), ] ], dtype=dtype ) A bad example:: # leaving this on a single line would not compromise readability my_value = numpy.empty( (3,) ) The same applies to function definitions:: def some_function(that_takes, many_parameters, and_is, hard_to_read, because, its_so, big): pass Should become:: def some_function( that_takes, many_parameters, and_is, hard_to_read, because, its_so, big ): pass * Code may extend beyond 80 columns, where appropriate. Pyrr-0.10.3/docs/source/info_contributing.rst000066400000000000000000000014251345610666600212520ustar00rootroot00000000000000.. _contributing: Contributing ************ Pyrr is an Open Source project and contributions are very much welcomed. If you wish to contribute to Pyrr, do the following: * Fork Pyrr * Make your changes * Send a Pull request Repository access may be granted on request. .. _contributing_authors: Developers ========== Pyrr was initially developed by Adam Griffiths of `Twisted Pair Development `_. Developers and contributors include: * `Adam Griffiths `_ * `Jakub Stasiak `_    * `Korijn van Golen `_ Is your name left out? Post an issue in `Pyrr's bug tracker `_ =) Pyrr-0.10.3/docs/source/info_data_types.rst000066400000000000000000000017751345610666600207100ustar00rootroot00000000000000.. _data_types: Data types ********** .. _data_types_modules: Modules ======= Each data type resides in its own module named after the specified type. Where there may be multiple versions of a data type, each version is located in its own module. Functions which are able to handle all versions of a data type are located in the central module. Otherwise, functions reside in the specific data type's module. For example: * **vector3.py** Provides functions for creating and manipulating 3D vectors (x,y,z). * **vector4.py** Provides functions for creating and manipulating 4D vectors (x,y,z,w). * **vector.py** Provides functions that work with both 3D and 4D vectors. .. _data_types_conversion: Conversion ========== Data conversion functions are provided in the module of the type being converted to. For example:: # module matrix44.py def create_from_matrix33(mat) : pass def create_from_eulers(eulers): pass def create_from_quaternion(quat): pass Pyrr-0.10.3/docs/source/info_geometric_tests.rst000066400000000000000000000033341345610666600217440ustar00rootroot00000000000000.. _geometric_tests: Geometric Tests *************** .. _geometric_tests_naming_scheme: Naming Scheme ============= Geometric Tests, also called Collision Detection, is provided by the geometric_tests module. Functions match a specific naming scheme. The function name provides information on what types are being checked, and what check is being performed. Function names adhere to the following format:: def __( ... ): pass The order of parameters matches the order of types given in the function name. The following are examples of this naming scheme:: def point_intersect_line(point, line): """This function returns the intersection point as a vector or None if there is no intersection. """ pass def point_closest_point_on_line_segment(point, segment): """The function returns the closest on the line segment, to the given point. """ pass .. _geometric_tests_types_of_checks: Types of Checks =============== Below are some of the types of checks provided. These are the names used by the functions and their meaning. * **intersect** Returns the intersection point of the two types or None. * **does_intersect** Returns True if the types are intersecting. * **height_above** Returns the height of one type above another. * **closest_point_on** Returns the closest point on the second type. * **parallel** Returns True if the types are parallel to each other. * **coincident** Returns True if the types are not only parallel, but are along the same direction axis. * **penetration** Returns the penetration distance of one type into the second. There may be more checks provided by the module than are listed here. Pyrr-0.10.3/docs/source/oo_api_matrix.rst000066400000000000000000000003361345610666600203620ustar00rootroot00000000000000.. _oo_api_matrix: Matrices ******** Matrix33 ======== .. automodule:: pyrr.objects.matrix33 :members: :undoc-members: Matrix44 ======== .. automodule:: pyrr.objects.matrix44 :members: :undoc-members: Pyrr-0.10.3/docs/source/oo_api_quaternion.rst000066400000000000000000000002201345610666600212330ustar00rootroot00000000000000.. _oo_api_quaternion: Quaternion ********** Quaternion ========== .. automodule:: pyrr.objects.quaternion :members: :undoc-members: Pyrr-0.10.3/docs/source/oo_api_vector.rst000066400000000000000000000003261345610666600203570ustar00rootroot00000000000000.. _oo_api_vector: Vectors ******* Vector3 ======= .. automodule:: pyrr.objects.vector3 :members: :undoc-members: Vector4 ======= .. automodule:: pyrr.objects.vector4 :members: :undoc-members: Pyrr-0.10.3/pyrr/000077500000000000000000000000001345610666600135405ustar00rootroot00000000000000Pyrr-0.10.3/pyrr/__init__.py000077500000000000000000000022361345610666600156570ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import # the version of software # this is used by the setup.py script from .version import __version__ __all__ = [ 'aabb', 'aambb', 'euler', 'geometric_tests', 'geometry', 'integer', 'line', 'matrix33', 'matrix44', 'plane', 'quaternion', 'ray', 'rectangle', 'sphere', 'trig', 'utils', 'vector', 'vector3', 'vector4', 'Matrix33', 'Matrix44', 'Quaternion', 'Vector3', 'Vector4', ] from . import ( aabb, aambb, euler, geometric_tests, geometry, integer, line, matrix33, matrix44, plane, quaternion, ray, rectangle, sphere, trig, utils, vector, vector3, vector4, ) from .objects import ( Matrix33, Matrix44, Quaternion, Vector3, Vector4 ) # because of circular imports, we cannot put these inside each module # so insert them here setattr(matrix33, 'Matrix33', Matrix33) setattr(matrix44, 'Matrix44', Matrix44) setattr(quaternion, 'Quaternion', Quaternion) setattr(vector3, 'Vector3', Vector3) setattr(vector4, 'Vector4', Vector4) Pyrr-0.10.3/pyrr/aabb.py000077500000000000000000000065261345610666600150130ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provides functions to calculate and manipulate Axis-Aligned Bounding Boxes (AABB). AABB are a simple 3D rectangle with no orientation. It is up to the user to provide translation. An AABB is represented by an array of 2 x 3D vectors. The first vector represents the minimum extent. The second vector represents the maximum extent. It should be noted that rotating the object within an AABB will invalidate the AABB. It is up to the user to either: * recalculate the AABB. * use an AAMBB instead. TODO: add transform( matrix ) """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the minimum vector within the AABB minimum = 0 #: The index of the maximum vector within the AABB maximum = 1 def create_zeros(dtype=None): return np.zeros((2,3), dtype=dtype) @parameters_as_numpy_arrays('min', 'max') def create_from_bounds(min, max, dtype=None): """Creates an AABB using the specified minimum and maximum values. """ dtype = dtype or min.dtype return np.array([min, max], dtype=dtype) @parameters_as_numpy_arrays('points') def create_from_points(points, dtype=None): """Creates an AABB from the list of specified points. Points must be a 2D list. Ie:: numpy.array([ [ x, y, z ], [ x, y, z ], ]) """ dtype = dtype or points.dtype return np.array( [ np.amin(points, axis=0), np.amax(points, axis=0) ], dtype=dtype ) @parameters_as_numpy_arrays('aabbs') def create_from_aabbs(aabbs, dtype=None): """Creates an AABB from a list of existing AABBs. AABBs must be a 2D list. Ie:: numpy.array([ AABB, AABB, ]) """ dtype = dtype or aabbs.dtype # reshape the AABBs as a series of points points = aabbs.reshape((-1, 3)) return create_from_points(points, dtype) @parameters_as_numpy_arrays('aabb') def add_points(aabb, points): """Extends an AABB to encompass a list of points. """ # find the minimum and maximum point values minimum = np.amin(points, axis=0) maximum = np.amax(points, axis=0) # compare to existing AABB return np.array( [ np.minimum(aabb[0], minimum), np.maximum(aabb[1], maximum) ], dtype=aabb.dtype ) @parameters_as_numpy_arrays( 'aabbs' ) def add_aabbs(aabb, aabbs): """Extend an AABB to encompass a list of other AABBs. """ # convert to points and use our existing add_points # function points = aabbs.reshape((-1, 3)) return add_points(aabb, points) @all_parameters_as_numpy_arrays def centre_point(aabb): """Returns the centre point of the AABB. """ return (aabb[0] + aabb[1]) * 0.5 @all_parameters_as_numpy_arrays def minimum(aabb): """Returns the minimum point of the AABB. """ return aabb[0].copy() @all_parameters_as_numpy_arrays def maximum(aabb): """ Returns the maximum point of the AABB. """ return aabb[1].copy() @all_parameters_as_numpy_arrays def clamp_points(aabb, points): """Takes a list of points and modifies them to fit within the AABB. """ return np.clip(points, a_min=aabb[0], a_max=aabb[1]) Pyrr-0.10.3/pyrr/aambb.py000077500000000000000000000107761345610666600151720ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provides functions to calculate and manipulate Axis-Aligned Minimum Bounding Boxes (AAMBB). AAMBB are a simple 3D rectangle with no orientation. It is up to the user to provide translation. AAMBB differ from AABB in that they allow for the content to rotate freely and still be within the AAMBB. An AAMBB is represented in the same way an AABB is; a array of 2 x 3D vectors. The first vector represents the minimum extent. The second vector represents the maximum extent. Note that because the AAMBB set's it's dimensions using the vector length of any points set within it, the user should be careful to avoid adding the AAMBB to itself or the AAMBB will continue to grow. TODO: add transform( matrix ) TODO: add point_within_aabb TODO: use point_within_aabb for unit tests """ from __future__ import absolute_import, division, print_function import numpy as np from . import aabb, vector from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the minimum vector within the AAMBB minimum = 0 #: The index of the minimum vector within the AAMBB maximum = 1 def create_zeros(dtype=None): return np.zeros((2,3), dtype=dtype) @parameters_as_numpy_arrays('min', 'max') def create_from_bounds(min, max, dtype=None): """Creates an AAMBB using the specified minimum and maximum values. """ dtype = dtype or min.dtype # stack our bounds together and add them as points points = np.vstack((min, max)) return create_from_points(points, dtype) @parameters_as_numpy_arrays('points') def create_from_points(points, dtype=None): """Creates an AAMBB from the list of specified points. Points must be a 2D list. Ie:: numpy.array([ [ x, y, z ], [ x, y, z ], ]) """ dtype = dtype or points.dtype # convert any negative values to positive abs_points = np.absolute(points) # find the length of this vector length = np.amax(vector.length(abs_points)) # our AAMBB extends from +length to -length # in all directions return np.array( [ [-length,-length,-length ], [ length, length, length ] ], dtype=dtype ) def create_from_aabbs(aabbs, dtype=None): """Creates an AAMBB from a list of existing AABBs. AABBs must be a 2D list. Ie:: numpy.array([ AABB, AABB, ]) """ aabbs = np.asarray(aabbs) dtype = dtype or aabbs.dtype # reshape the AABBs as a series of points points = aabbs.reshape((-1, 3)) return create_from_points(points, dtype=dtype) @parameters_as_numpy_arrays('bb') def add_points(bb, points): """Extends an AAMBB to encompass a list of points. It should be noted that this ensures that the encompassed points can rotate freely. Calling this using the min / max points from the AAMBB will create an even bigger AAMBB. """ # add our AABB to the list of points values = np.vstack((points, bb[0], bb[1])) # convert any negative values to positive abs_points = np.absolute(values) # extract the maximum extent as a vector #vec = np.amax(abs_points, axis=0) # find the length of this vector #length = vector.length(vec) length = np.amax(vector.length(abs_points)) # our AAMBB extends from +length to -length # in all directions return np.array( [ [-length,-length,-length ], [ length, length, length ] ], dtype=bb.dtype ) @parameters_as_numpy_arrays('bbs') def add_aabbs(bb, bbs): """Extend an AAMBB to encompass a list of other AABBs or AAMBBs. It should be noted that this ensures that the encompassed AABBs can rotate freely. Using the AAMBB itself in this calculation will create an event bigger AAMBB. """ # reshape the AABBs as a series of points points = bbs.reshape((-1, 3)) # use the add_points return add_points(bb, points) def centre_point(bb): """Returns the centre point of the AABB. This should always be [0.0, 0.0, 0.0] """ return aabb.centre_point(bb) def minimum(bb): """Returns the minimum point of the AABB. """ return aabb.minimum(bb) def maximum(bb): """Returns the maximum point of the AABB. """ return aabb.maximum(bb) def clamp_points(bb, points): """Takes a list of points and modifies them to fit within the AABB. """ # use the same function as present in AABB aabb.clamp_points(bb, points) Pyrr-0.10.3/pyrr/euler.py000066400000000000000000000031061345610666600152260ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of Euler angles. Eulers represent 3 rotations: Pitch, Roll and Yaw. Eulers are represented using a numpy.array of shape (3,). """ from __future__ import absolute_import, division, print_function import numpy as np class index: """Defines the indices used to store the Euler values in the numpy array. """ #: The index of the roll value within the euler. roll = 0 #: The index of the pitch value within the euler. pitch = 1 #: The index of the yaw value within the euler. yaw = 2 def create(roll=0., pitch=0., yaw=0., dtype=None): """Creates an array storing the specified euler angles. Input values are in radians. :param float pitch: The pitch in radians. :param float roll: The roll in radians. :param float yaw: The yaw in radians. :rtype: numpy.array """ return np.array((roll, pitch, yaw), dtype=dtype) def create_from_x_rotation(theta, dtype=None): return np.array([theta, 0., 0.], dtype=dtype) def create_from_y_rotation(theta, dtype=None): return np.array([0., theta, 0.], dtype=dtype) def create_from_z_rotation(theta, dtype=None): return np.array([0., 0., theta], dtype=dtype) def roll(eulers): """Extracts the roll value from the euler. :rtype: float. """ return eulers[0] def yaw(eulers): """Extracts the yaw value from the euler. :rtype: float. """ return eulers[2] def pitch(eulers): """Extracts the pitch value from the euler. :rtype: float. """ return eulers[1] Pyrr-0.10.3/pyrr/geometric_tests.py000077500000000000000000000325201345610666600173170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Defines a number of functions to test interactions between various forms data types. """ from __future__ import absolute_import, division, print_function import math import numpy as np from . import rectangle, vector, vector3, plane from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays, solve_quadratic_equation """ TODO: line_intersect_plane TODO: line_segment_intersect_plane TODO: ray_intersect_ray TODO: line_intersect_line TODO: line_segment_intersect_line_segment """ @all_parameters_as_numpy_arrays def point_intersect_line(point, line): """Calculates the intersection point of a point and aline. Performed by checking if the cross-product of the point relative to the line is 0. """ rl = line[1] - line[0] rp = point - line[0] cross = vector3.cross(rl, rp) # check if the cross product is zero if np.count_nonzero(cross) > 0: return None return point @all_parameters_as_numpy_arrays def point_intersect_line_segment(point, line): """Calculates the intersection point of a point and a line segment. Performed by checking if the cross-product of the point relative to the line is 0 and if the dot product of the point relative to the line start AND the end point relative to the line start is less than the segment's squared length. """ rl = line[1] - line[0] rp = point - line[0] cross = vector3.cross(rl, rp) dot = vector.dot(rp, rl) squared_length = vector.squared_length(rl) if np.count_nonzero(cross) > 0: return None if dot < 0.0 or dot > squared_length: return None return point @all_parameters_as_numpy_arrays def point_intersect_rectangle(point, rect): """Calculates the intersection point of a point and a 2D rectangle. For 3D points, the Z axis will be ignored. :return: Returns True if the point is touching or within the rectangle. """ left, right, bottom, top = rectangle.bounds(rect) if \ point[0] < left or \ point[0] > right or \ point[1] < bottom or \ point[1] > top: return None return point @parameters_as_numpy_arrays('ray', 'pl') def ray_intersect_plane(ray, pl, front_only=False): """Calculates the intersection point of a ray and a plane. :param numpy.array ray: The ray to test for intersection. :param numpy.array pl: The plane to test for intersection. :param boolean front_only: Specifies if the ray should only hit the front of the plane. Collisions from the rear of the plane will be ignored. :return The intersection point, or None if the ray is parallel to the plane. Returns None if the ray intersects the back of the plane and front_only is True. """ """ Distance to plane is defined as t = (pd - p0.n) / rd.n where: rd is the ray direction pd is the point on plane . plane normal p0 is the ray position n is the plane normal if rd.n == 0, the ray is parallel to the plane. """ p = plane.position(pl) n = plane.normal(pl) rd_n = vector.dot(ray[1], n) if rd_n == 0.0: return None if front_only == True: if rd_n >= 0.0: return None pd = vector.dot(p, n) p0_n = vector.dot(ray[0], n) t = (pd - p0_n) / rd_n return ray[0] + (ray[1] * t) @all_parameters_as_numpy_arrays def point_closest_point_on_ray(point, ray): """Calculates the point on a ray that is closest to a point. :param numpy.array point: The point to check with. :param numpy.array ray: The ray to check against. :rtype: numpy.array :return: The closest point on the ray to the point. """ """ t = (p - rp).n cp = rp + (n * t) where p is the point rp is the ray origin n is the ray normal of unit length t is the distance along the ray to the point """ normalized_n = vector.normalize(ray[1]) relative_point = (point - ray[0]) t = vector.dot(relative_point, normalized_n) return ray[0] + (normalized_n * t) @all_parameters_as_numpy_arrays def point_closest_point_on_line(point, line): """Calculates the point on the line that is closest to the specified point. :param numpy.array point: The point to check with. :param numpy.array line: The line to check against. :rtype: numpy.array :return: The closest point on the line to the point. """ """ rl = va->b (relative line) rp = va->p (relative point) u' = u / |u| (normalize) cp = a + (u' * (u'.v)) where: a = line start b = line end p = point cp = closest point """ rl = line[1] - line[0] rp = point - line[0] rl = vector.normalize(rl) dot = vector.dot(rl, rp) return line[0] + (rl * dot) @all_parameters_as_numpy_arrays def point_closest_point_on_line_segment(point, segment): """Calculates the point on the line segment that is closest to the specified point. This is similar to point_closest_point_on_line, except this is against the line segment of finite length. Whereas point_closest_point_on_line checks against a line of infinite length. :param numpy.array point: The point to check with. :param numpy.array line_segment: The finite line segment to check against. :rtype: numpy.array :return: The closest point on the line segment to the point. """ # check if the line has any length rl = segment[1] - segment[0] squared_length = vector.squared_length(rl) if squared_length == 0.0: return segment[0] rp = point - segment[0] # check that / squared_length is correct dot = vector.dot(rp, rl) / squared_length; if dot < 0.0: return segment[0] elif dot > 1.0: return segment[1] # within segment # perform the same calculation as closest_point_on_line return segment[0] + (rl * dot) @all_parameters_as_numpy_arrays def vector_parallel_vector(v1, v2): """Checks if two vectors are parallel. :param numpy.array v1, v2: The vectors to check. :rtype: boolean :return: Returns True if the two vectors are parallel. """ # we cross product the 2 vectors # if the result is 0, then they are parallel cross = vector3.cross(v1, v2) return 0 == np.count_nonzero(cross) @all_parameters_as_numpy_arrays def ray_parallel_ray(ray1, ray2): """Checks if two rays are parallel. :param numpy.array ray1, ray2: The rays to check. :rtype: boolean :return: Returns True if the two rays are parallel. """ # we use a cross product in-case the ray direction # isn't unit length return vector_parallel_vector(ray1[ 1 ], ray2[ 1 ]) @all_parameters_as_numpy_arrays def ray_coincident_ray(ray1, ray2): """Check if rays are coincident. Rays must not only be parallel to each other, but reside along the same vector. :param numpy.array ray1, ray2: The rays to check. :rtype: boolean :return: Returns True if the two rays are co-incident. """ # ensure the ray's directions are the same if ray_parallel_ray(ray1, ray2): # get the delta between the two ray's start point delta = ray2[0] - ray1[0] # get the cross product of the ray delta and # the direction of the rays cross = vector3.cross(delta, ray2[1]) # if the cross product is zero, the start of the # second ray is in line with the direction of the # first ray if np.count_nonzero(cross) > 0: return False return True return False @all_parameters_as_numpy_arrays def ray_intersect_aabb(ray, aabb): """Calculates the intersection point of a ray and an AABB :param numpy.array ray1: The ray to check. :param numpy.array aabb: The Axis-Aligned Bounding Box to check against. :rtype: numpy.array :return: Returns a vector if an intersection occurs. Returns None if no intersection occurs. """ """ http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms """ # this is basically "numpy.divide( 1.0, ray[ 1 ] )" # except we're trying to avoid a divide by zero warning # so where the ray direction value is 0.0, just use infinity # which is what we want anyway direction = ray[1] dir_fraction = np.empty(3, dtype = ray.dtype) dir_fraction[direction == 0.0] = np.inf dir_fraction[direction != 0.0] = np.divide(1.0, direction[direction != 0.0]) t1 = (aabb[0,0] - ray[0,0]) * dir_fraction[ 0 ] t2 = (aabb[1,0] - ray[0,0]) * dir_fraction[ 0 ] t3 = (aabb[0,1] - ray[0,1]) * dir_fraction[ 1 ] t4 = (aabb[1,1] - ray[0,1]) * dir_fraction[ 1 ] t5 = (aabb[0,2] - ray[0,2]) * dir_fraction[ 2 ] t6 = (aabb[1,2] - ray[0,2]) * dir_fraction[ 2 ] tmin = max(min(t1, t2), min(t3, t4), min(t5, t6)) tmax = min(max(t1, t2), max(t3, t4), max(t5, t6)) # if tmax < 0, ray (line) is intersecting AABB # but the whole AABB is behind the ray start if tmax < 0: return None # if tmin > tmax, ray doesn't intersect AABB if tmin > tmax: return None # t is the distance from the ray point # to intersection t = min(x for x in [tmin, tmax] if x >= 0) point = ray[0] + (ray[1] * t) return point @all_parameters_as_numpy_arrays def point_height_above_plane(point, pl): """Calculates how high a point is above a plane. :param numpy.array point: The point to check. :param numpy.array plane: The plane to check. :rtype: float :return: The height above the plane as a float. The value will be negative if the point is behind the plane. """ """ Because we store normalised normal, we can simply use: n . (p - p0) where: n is the plane normal p is the plane position p0 is the point """ return vector.dot(plane.normal(pl), point - plane.position(pl)) @all_parameters_as_numpy_arrays def point_closest_point_on_plane(point, pl): """Calculates the point on a plane that is closest to a point. :param numpy.array point: The point to check with. :param numpy.array plane: The infinite plane to check against. :rtype: numpy.array :return: The closest point on the plane to the point. """ """ point on plane is defined as: q' = q + (d - q.n)n where: q' is the point on the plane q is the point we are checking d is the value of normal dot position n is the plane normal """ n = plane.normal(pl) p = n * plane.distance(pl) d = np.dot(p, n) qn = np.dot(point, n) return point + (n * (d - qn)) @all_parameters_as_numpy_arrays def sphere_does_intersect_sphere(s1, s2): """Checks if two spheres overlap. Note: This will return True if the two spheres are touching perfectly but sphere_penetration_sphere will return 0.0 as the touch but don't penetrate. This is faster than circle_penetrate_amount_circle as it avoids a square root calculation. :param numpy.array s1: The first circle. :param numpy.array s2: The second circle. :rtype: boolean :return: Returns True if the circles overlap. Otherwise, returns False. """ delta = s2[:3] - s1[:3] distance_squared = vector.squared_length(delta) radii_squared = math.pow(s1[3] + s2[3], 2.0) if distance_squared > radii_squared: return False return True @all_parameters_as_numpy_arrays def sphere_penetration_sphere(s1, s2): """Calculates the distance two spheres have penetrated into one another. :param numpy.array s1: The first circle. :param numpy.array s2: The second circle. :rtype: float :return: The total overlap of the two spheres. This is essentially: r1 + r2 - distance Where r1 and r2 are the radii of circle 1 and 2 and distance is the length of the vector p2 - p1. Will return 0.0 if the circles do not overlap. """ delta = s2[:3] - s1[:3] distance = vector.length(delta) combined_radii = s1[3] + s2[3] penetration = combined_radii - distance if penetration <= 0.0: return 0.0 return penetration @all_parameters_as_numpy_arrays def ray_intersect_sphere(ray, sphere): """ Returns the intersection points of a ray and a sphere. See: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection The ray is defined via the following equation O+tD. Where O is the origin point and D is a direction vector. A sphere is defined as |P−C|^2=R2 where P is the origin and C is the center of the sphere. R is the radius of the sphere. Args: ray: Ray geometry sphere: Sphere geometry Returns: list: Intersection points as 3D vector list :param numpy.array ray: Ray parameter. :param numpy.array sphere: Sphere parameter. :rtype: float :return: Intersection points as a list of points. """ sphere_center = sphere[:3] sphere_radius = sphere[3] ray_origin = ray[0] ray_direction = ray[1] a = 1 b = 2 * np.dot(ray_direction, (ray_origin - sphere_center)) c = np.dot(ray_origin - sphere_center, ray_origin - sphere_center) - sphere_radius * sphere_radius t_list = solve_quadratic_equation(a, b, c) ret = list() for t in t_list: # We are calculating intersection for ray not line! Use only positive t for ray. if t >= 0: ret.append(ray_origin + ray_direction * t) return ret Pyrr-0.10.3/pyrr/geometry.py000066400000000000000000000376341345610666600157620ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Geometry functions. """ from __future__ import absolute_import, division, print_function import numpy as np def create_quad(scale=(1.0,1.0), st=False, rgba=False, dtype='float32', type='triangles'): """Returns a Quad reading for rendering. Output is a tuple of numpy arrays. The first value is the vertex data, the second is the indices. The first dimension of the vertex data contains the list of vertices. The second dimension is the vertex data. Vertex data is always in the following order:: [x, y, z, s, t, r, g, b, a] ST and RGBA are optional. If ST is dropped but RGBA is included the format will be:: [x, y, z, r, g, b, a] If both ST and RGBA are dropped the format will be:: [x, y, z] RGBA can also be of size 3 (RGB) or 4 (RGBA). Output format is as follows:: numpy.array([ # vertex 1 [x, y, z, s, t, r, g, b, a], # vertex 2 [x, y, z, s, t, r, g, b, a], ... # vertex N [x, y, z, s, t, r, g, b, a], ], dtype = dtype) :param bool,scalar,list,tuple,numpy.ndarray st: The ST texture co-ordinates. Default is False, which means ST will not be included in the array. If True is passed, the default ST values will be provided with the bottom-left of the quad being located at ST=(0.0,0.0) and the top-right being located at ST=(1.0,1.0). If a 2d list, tuple or numpy array is passed, it must have one of the following shapes:: (2,2,), (4,2,), If the shape is (2,2,), the values are interpreted as the minimum and maximum values for ST. For example:: st=((0.1,0.3),(0.2,0.4)) S values will be between 0.1 to 0.2. T values will be between 0.3 to 0.4. The bottom left will receive the minimum of both, and the top right will receive the maximum. If the shape is (4,2,), the values are interpreted as being the actual ST values for the 4 vertices of the Quad. The vertices are in counter-clockwise winding order from the top right:: [top-right, top-left, bottom-left, bottom-right,] :param bool,scalar,list,tuple,numpy.ndarray rgba: The RGBA colour. Default is False, which means RGBA will not be included in the array. If True is passed, the default RGBA values will be provided with all vertices being RGBA=(1.0, 1.0, 1.0, 1.0) If a 2d list, tuple or numpy array is passed, it must have one of the following shapes:: (3,), (4,), (4,3,), (4,4,), If the shape is (3,), the values are interpreted as being an RGB value (no alpha) to set on all vertices. If the shape is (4,), the values are intepreted the same as the shape (3,) except the alpha value is included. If the shape is (4,3,), the values are interpreted as being a colour to set on the 4 vertices of the Quad. The vertices are in counter-clockwise winding order from the top right:: [top-right, top-left, bottom-left, bottom-right] If the shape is (4,4,), the values are intepreted the same as the shape (4,3,) except the alpha value is included. :param string type: The type of indices to generate. Valid values are:: ['triangles', 'triangle_strip', 'triangle_fan', 'quads', 'quad_strip',] If you just want the vertices without any index manipulation, use 'quads'. """ shape = [4, 3] rgba_offset = 3 width, height = scale # half the dimensions width /= 2.0 height /= 2.0 vertices = np.array([ # top right ( width, height, 0.0,), # top left (-width, height, 0.0,), # bottom left (-width,-height, 0.0,), # bottom right ( width,-height, 0.0,), ], dtype=dtype) st_values = None rgba_values = None if st: # default st values st_values = np.array([ (1.0, 1.0,), (0.0, 1.0,), (0.0, 0.0,), (1.0, 0.0,), ], dtype=dtype) if isinstance(st, bool): pass elif isinstance(st, (int, float)): st_values *= st elif isinstance(st, (list, tuple, np.ndarray)): st = np.array(st, dtype=dtype) if st.shape == (2,2,): # min / max st_values *= st[1] - st[0] st_values += st[0] elif st.shape == (4,2,): # st values specified manually st_values[:] = st else: raise ValueError('Invalid shape for st') else: raise ValueError('Invalid value for st') shape[-1] += st_values.shape[-1] rgba_offset += st_values.shape[-1] if rgba: # default rgba values rgba_values = np.tile(np.array([1.0, 1.0, 1.0, 1.0], dtype=dtype), (4,1,)) if isinstance(rgba, bool): pass elif isinstance(rgba, (int, float)): # int / float expands to RGBA with all values == value rgba_values *= rgba elif isinstance(rgba, (list, tuple, np.ndarray)): rgba = np.array(rgba, dtype=dtype) if rgba.shape == (3,): rgba_values = np.tile(rgba, (4,1,)) elif rgba.shape == (4,): rgba_values[:] = rgba elif rgba.shape == (4,3,): rgba_values = rgba elif rgba.shape == (4,4,): rgba_values = rgba else: raise ValueError('Invalid shape for rgba') else: raise ValueError('Invalid value for rgba') shape[-1] += rgba_values.shape[-1] data = np.empty(shape, dtype=dtype) data[:,:3] = vertices if st_values is not None: data[:,3:5] = st_values if rgba_values is not None: data[:,rgba_offset:] = rgba_values if type == 'triangles': # counter clockwise # top right -> top left -> bottom left # top right -> bottom left -> bottom right indices = np.array([0, 1, 2, 0, 2, 3]) elif type == 'triangle_strip': # verify indices = np.arange(len(data)) elif type == 'triangle_fan': # verify indices = np.arange(len(data)) elif type == 'quads': indices = np.arange(len(data)) elif type == 'quad_strip': indices = np.arange(len(data)) else: raise ValueError('Unknown type') return data, indices def create_cube(scale=(1.0,1.0,1.0), st=False, rgba=False, dtype='float32', type='triangles'): """Returns a Cube reading for rendering. Output is a tuple of numpy arrays. The first value is the vertex data, the second is the indices. The first dimension of the vertex data contains the list of vertices. The second dimension is the vertex data. Vertex data is always in the following order:: [x, y, z, s, t, r, g, b, a] ST and RGBA are optional. If ST is dropped but RGBA is included the format will be:: [x, y, z, r, g, b, a] If both ST and RGBA are dropped the format will be:: [x, y, z] RGBA can also be of size 3 (RGB) or 4 (RGBA). Output format is as follows:: numpy.array([ # vertex 1 [x, y, z, s, t, r, g, b, a], # vertex 2 [x, y, z, s, t, r, g, b, a], ... # vertex N [x, y, z, s, t, r, g, b, a], ], dtype = dtype) :param bool,scalar,list,tuple,numpy.ndarray st: The ST texture co-ordinates. Default is False, which means ST will not be included in the array. If True is passed, the default ST values will be provided with the bottom-left of the quad being located at ST=(0.0,0.0) and the top-right being located at ST=(1.0,1.0). If a 2d list, tuple or numpy array is passed, it must have one of the following shapes:: (2,2,), (4,2,), (6,2,), If the shape is (2,2,), the values are interpreted as the minimum and maximum values for ST. For example:: st=((0.1,0.3),(0.2,0.4)) S values will be between 0.1 to 0.2. T values will be between 0.3 to 0.4. The bottom left will receive the minimum of both, and the top right will receive the maximum. If the shape is (4,2,), the values are interpreted as being the actual ST values for the 4 vertices of each face. The vertices are in counter-clockwise winding order from the top right:: [top-right, top-left, bottom-left, bottom-right,] If the shape is (6,2,), the values are interpreted as being the minimum and maximum values for each face of the cube. The faces are in the following order:: [front, right, back, left, top, bottom,] :param bool,scalar,list,tuple,numpy.ndarray rgba: The RGBA colour. Default is False, which means RGBA will not be included in the array. If True is passed, the default RGBA values will be provided with all vertices being RGBA=(1.0, 1.0, 1.0, 1.0). If a 2d list, tuple or numpy array is passed, it must have one of the following shapes.:: (3,), (4,), (4,3,), (4,4,), (6,3,), (6,4,), (24,3,), (24,4,), If the shape is (3,), the values are interpreted as being an RGB value (no alpha) to set on all vertices. If the shape is (4,), the values are intepreted the same as the shape (3,) except the alpha value is included. If the shape is (4,3,), the values are interpreted as being a colour to set on the 4 vertices of each face. The vertices are in counter-clockwise winding order from the top right:: [top-right, top-left, bottom-left, bottom-right] If the shape is (4,4,), the values are intepreted the same as the shape (4,3,) except the alpha value is included. If the shape is (6,3,), the values are interpreted as being one RGB value (no alpha) for each face. The faces are in the following order:: [front, right, back, left, top, bottom,] If the shape is (6,4,), the values are interpreted the same as the shape (6,3,) except the alpha value is included. If the shape is (24,3,), the values are interpreted as being an RGB value (no alpha) to set on each vertex of each face (4 * 6). The faces are in the following order:: [front, right, back, left, top, bottom,] The vertices are in counter-clockwise winding order from the top right:: [top-right, top-left, bottom-left, bottom-right] If the shape is (24,4,), the values are interpreted the same as the shape (24,3,) except the alpha value is included. :param string type: The type of indices to generate. Valid values are:: ['triangles', 'triangle_strip', 'triangle_fan', 'quads', 'quad_strip',] If you just want the vertices without any index manipulation, use 'quads'. """ shape = [24, 3] rgba_offset = 3 width, height, depth = scale # half the dimensions width /= 2.0 height /= 2.0 depth /= 2.0 vertices = np.array([ # front # top right ( width, height, depth,), # top left (-width, height, depth,), # bottom left (-width,-height, depth,), # bottom right ( width,-height, depth,), # right # top right ( width, height,-depth), # top left ( width, height, depth), # bottom left ( width,-height, depth), # bottom right ( width,-height,-depth), # back # top right (-width, height,-depth), # top left ( width, height,-depth), # bottom left ( width,-height,-depth), # bottom right (-width,-height,-depth), # left # top right (-width, height, depth), # top left (-width, height,-depth), # bottom left (-width,-height,-depth), # bottom right (-width,-height, depth), # top # top right ( width, height,-depth), # top left (-width, height,-depth), # bottom left (-width, height, depth), # bottom right ( width, height, depth), # bottom # top right ( width,-height, depth), # top left (-width,-height, depth), # bottom left (-width,-height,-depth), # bottom right ( width,-height,-depth), ], dtype=dtype) st_values = None rgba_values = None if st: # default st values st_values = np.tile( np.array([ (1.0, 1.0,), (0.0, 1.0,), (0.0, 0.0,), (1.0, 0.0,), ], dtype=dtype), (6,1,) ) if isinstance(st, bool): pass elif isinstance(st, (int, float)): st_values *= st elif isinstance(st, (list, tuple, np.ndarray)): st = np.array(st, dtype=dtype) if st.shape == (2,2,): # min / max st_values *= st[1] - st[0] st_values += st[0] elif st.shape == (4,2,): # per face st values specified manually st_values[:] = np.tile(st, (6,1,)) elif st.shape == (6,2,): # st values specified manually st_values[:] = st else: raise ValueError('Invalid shape for st') else: raise ValueError('Invalid value for st') shape[-1] += st_values.shape[-1] rgba_offset += st_values.shape[-1] if rgba: # default rgba values rgba_values = np.tile(np.array([1.0, 1.0, 1.0, 1.0], dtype=dtype), (24,1,)) if isinstance(rgba, bool): pass elif isinstance(rgba, (int, float)): # int / float expands to RGBA with all values == value rgba_values *= rgba elif isinstance(rgba, (list, tuple, np.ndarray)): rgba = np.array(rgba, dtype=dtype) if rgba.shape == (3,): rgba_values = np.tile(rgba, (24,1,)) elif rgba.shape == (4,): rgba_values[:] = np.tile(rgba, (24,1,)) elif rgba.shape == (4,3,): rgba_values = np.tile(rgba, (6,1,)) elif rgba.shape == (4,4,): rgba_values = np.tile(rgba, (6,1,)) elif rgba.shape == (6,3,): rgba_values = np.repeat(rgba, 4, axis=0) elif rgba.shape == (6,4,): rgba_values = np.repeat(rgba, 4, axis=0) elif rgba.shape == (24,3,): rgba_values = rgba elif rgba.shape == (24,4,): rgba_values = rgba else: raise ValueError('Invalid shape for rgba') else: raise ValueError('Invalid value for rgba') shape[-1] += rgba_values.shape[-1] data = np.empty(shape, dtype=dtype) data[:,:3] = vertices if st_values is not None: data[:,3:5] = st_values if rgba_values is not None: data[:,rgba_offset:] = rgba_values if type == 'triangles': # counter clockwise # top right -> top left -> bottom left # top right -> bottom left -> bottom right indices = np.tile(np.array([0, 1, 2, 0, 2, 3], dtype='int'), (6,1)) for face in range(6): indices[face] += (face * 4) indices.shape = (-1,) elif type == 'triangle_strip': raise NotImplementedError elif type == 'triangle_fan': raise NotImplementedError elif type == 'quads': raise NotImplementedError elif type == 'quad_strip': raise NotImplementedError else: raise ValueError('Unknown type') return data, indices Pyrr-0.10.3/pyrr/integer.py000077500000000000000000000011441345610666600155520ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the manipulation of integers. """ def count_bits(value): """Counts the number of bits set to 1 in an integer. For example:: >>> count_bits(0b101111) 5 >>> count_bits(0xf) 4 >>> count_bits(8) 1 >>> count_bits(3) 2 :param int value: An integer. :rtype: integer :return: The count of bits set to 1. .. seealso:: http://wiki.python.org/moin/BitManipulation """ count = 0 while (value): count += (value & 1) value >>= 1 return count Pyrr-0.10.3/pyrr/line.py000077500000000000000000000050761345610666600150540ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of Lines. A Line data structure is simply a numpy.array with 2 vectors:: start = numpy.array( [ -1.0, 0.0, 0.0 ] ) end = numpy.array( [ 1.0, 0.0, 0.0 ] ) line = numpy.array( [ start, end ] ) Both Lines and Line Segments are defined using the same data structure. The only difference is how the data is interpreted. A line is defined by two points but extends infinitely. A line segment only exists between two points. It does not extend forever. The choice to interprete a line as a line or line segment is up to the function being called. Check the function signature of documentation to determine how a line will be interpreted. """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the start vector within the line start = 0 #: The index of the end vector within the line end = 1 def create_zeros(dtype=None): """Creates a line with the start and end at the origin. :rtype: numpy.array :return: A line with both start and end points at (0,0,0). """ return np.zeros((2,3), dtype=dtype) def create_from_points(v1, v2, dtype=None): """Creates a line from 2 vectors. The 2 vectors represent the start and end point of the line. :param numpy.array v1: Start point. :param numpy.array v2: End point. :rtype: numpy.array :return: A line extending from v1 to v2. """ return np.array([v1, v2], dtype=dtype) @all_parameters_as_numpy_arrays def create_from_ray(ray): """Converts a ray to a line. The line will extend from 'ray origin -> ray origin + ray direction'. :param numpy.array ray: The ray to convert. :rtype: numpy.array :return: A line beginning at the ray start and extending for 1 unit in the direction of the ray. """ # convert ray relative direction to absolute # position return np.array([ray[0], ray[0] + ray[1]], dtype=ray.dtype) @all_parameters_as_numpy_arrays def start(line): """Extracts the start point of the line. :param numpy.array line: The line to extract the start from. :rtype: numpy.array :return: The starting point of the line. """ return line[0].copy() @all_parameters_as_numpy_arrays def end(line): """Extracts the end point of the line. :param numpy.array line: The line to extract the end from. :rtype: numpy.array :return: The ending point of the line. """ return line[1].copy() Pyrr-0.10.3/pyrr/matrix33.py000077500000000000000000000265171345610666600156020ustar00rootroot00000000000000# -*- coding: utf-8 -*- """3x3 Matrix which supports rotation, translation, scale and skew. Matrices are laid out in row-major format and can be loaded directly into OpenGL. To convert to column-major format, transpose the array using the numpy.array.T method. """ from __future__ import absolute_import, division, print_function import numpy as np from . import vector, quaternion, euler from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays def create_identity(dtype=None): """Creates a new matrix33 and sets it to an identity matrix. :rtype: numpy.array :return: A matrix representing an identity matrix with shape (3,3). """ return np.identity(3, dtype=dtype) def create_from_matrix44(mat, dtype=None): """Creates a Matrix33 from a Matrix44. :rtype: numpy.array :return: A matrix with shape (3,3) with the input matrix rotation. """ mat = np.asarray(mat) return np.array(mat[0:3,0:3], dtype=dtype) @parameters_as_numpy_arrays('eulers') def create_from_eulers(eulers, dtype=None): """Creates a matrix from the specified Euler rotations. :param numpy.array eulers: A set of euler rotations in the format specified by the euler modules. :rtype: numpy.array :return: A matrix with shape (3,3) with the euler's rotation. """ dtype = dtype or eulers.dtype pitch, roll, yaw = euler.pitch(eulers), euler.roll(eulers), euler.yaw(eulers) sP = np.sin(pitch) cP = np.cos(pitch) sR = np.sin(roll) cR = np.cos(roll) sY = np.sin(yaw) cY = np.cos(yaw) return np.array( [ # m1 [ cY * cP, -cY * sP * cR + sY * sR, cY * sP * sR + sY * cR, ], # m2 [ sP, cP * cR, -cP * sR, ], # m3 [ -sY * cP, sY * sP * cR + cY * sR, -sY * sP * sR + cY * cR, ] ], dtype=dtype ) @parameters_as_numpy_arrays('axis') def create_from_axis_rotation(axis, theta, dtype=None): """Creates a matrix from the specified theta rotation around an axis. :param numpy.array axis: A (3,) vector specifying the axis of rotation. :param float theta: A rotation speicified in radians. :rtype: numpy.array :return: A matrix with shape (3,3). """ dtype = dtype or axis.dtype axis = vector.normalize(axis) x,y,z = axis s = np.sin(theta); c = np.cos(theta); t = 1 - c; # Construct the elements of the rotation matrix return np.array( [ [ x * x * t + c, y * x * t + z * s, z * x * t - y * s], [ x * y * t - z * s, y * y * t + c, z * y * t + x * s], [ x * z * t + y * s, y * z * t - x * s, z * z * t + c] ], dtype= dtype ) @parameters_as_numpy_arrays('quat') def create_from_quaternion(quat, dtype=None): """Creates a matrix with the same rotation as a quaternion. :param quat: The quaternion to create the matrix from. :rtype: numpy.array :return: A matrix with shape (3,3) with the quaternion's rotation. """ dtype = dtype or quat.dtype # the quaternion must be normalized if not np.isclose(np.linalg.norm(quat), 1.): quat = quaternion.normalize(quat) # http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm qx, qy, qz, qw = quat[0], quat[1], quat[2], quat[3] sqw = qw**2 sqx = qx**2 sqy = qy**2 sqz = qz**2 qxy = qx * qy qzw = qz * qw qxz = qx * qz qyw = qy * qw qyz = qy * qz qxw = qx * qw invs = 1 / (sqx + sqy + sqz + sqw) m00 = ( sqx - sqy - sqz + sqw) * invs m11 = (-sqx + sqy - sqz + sqw) * invs m22 = (-sqx - sqy + sqz + sqw) * invs m10 = 2.0 * (qxy + qzw) * invs m01 = 2.0 * (qxy - qzw) * invs m20 = 2.0 * (qxz - qyw) * invs m02 = 2.0 * (qxz + qyw) * invs m21 = 2.0 * (qyz + qxw) * invs m12 = 2.0 * (qyz - qxw) * invs return np.array([ [m00, m01, m02], [m10, m11, m12], [m20, m21, m22], ], dtype=dtype) @parameters_as_numpy_arrays('quat') def create_from_inverse_of_quaternion(quat, dtype=None): """Creates a matrix with the inverse rotation of a quaternion. :param numpy.array quat: The quaternion to make the matrix from (shape 4). :rtype: numpy.array :return: A matrix with shape (3,3) that respresents the inverse of the quaternion. """ dtype = dtype or quat.dtype x, y, z, w = quat x2 = x**2 y2 = y**2 z2 = z**2 wx = w * x wy = w * y xy = x * y wz = w * z xz = x * z yz = y * z return np.array( [ # m1 [ # m11 = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) 1.0 - 2.0 * (y2 + z2), # m21 = 2.0 * (q.x * q.y + q.w * q.z) 2.0 * (xy + wz), # m31 = 2.0 * (q.x * q.z - q.w * q.y) 2.0 * (xz - wy), ], # m2 [ # m12 = 2.0 * (q.x * q.y - q.w * q.z) 2.0 * (xy - wz), # m22 = 1.0 - 2.0 * (q.x * q.x + q.z * q.z) 1.0 - 2.0 * (x2 + z2), # m32 = 2.0 * (q.y * q.z + q.w * q.x) 2.0 * (yz + wx), ], # m3 [ # m13 = 2.0 * ( q.x * q.z + q.w * q.y) 2.0 * (xz + wy), # m23 = 2.0 * (q.y * q.z - q.w * q.x) 2.0 * (yz - wx), # m33 = 1.0 - 2.0 * (q.x * q.x + q.y * q.y) 1.0 - 2.0 * (x2 + y2), ] ], dtype=dtype ) def create_from_scale(scale, dtype=None): """Creates an identity matrix with the scale set. :param numpy.array scale: The scale to apply as a vector (shape 3). :rtype: numpy.array :return: A matrix with shape (3,3) with the scale set to the specified vector. """ # apply the scale to the values diagonally # down the matrix m = np.diagflat(scale) if dtype: m = m.astype(dtype) return m def create_from_x_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the X axis. :param float theta: The rotation, in radians, about the X-axis. :rtype: numpy.array :return: A matrix with the shape (3,3) with the specified rotation about the X-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ cosT = np.cos(theta) sinT = np.sin(theta) return np.array( [ [ 1.0, 0.0, 0.0 ], [ 0.0, cosT,-sinT ], [ 0.0, sinT, cosT ] ], dtype=dtype ) def create_from_y_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the Y axis. :param float theta: The rotation, in radians, about the Y-axis. :rtype: numpy.array :return: A matrix with the shape (3,3) with the specified rotation about the Y-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ cosT = np.cos(theta) sinT = np.sin(theta) return np.array( [ [ cosT, 0.0,sinT ], [ 0.0, 1.0, 0.0 ], [-sinT, 0.0, cosT ] ], dtype=dtype ) def create_from_z_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the Z axis. :param float theta: The rotation, in radians, about the Z-axis. :rtype: numpy.array :return: A matrix with the shape (3,3) with the specified rotation about the Z-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ cosT = np.cos(theta) sinT = np.sin(theta) return np.array( [ [ cosT,-sinT, 0.0 ], [ sinT, cosT, 0.0 ], [ 0.0, 0.0, 1.0 ] ], dtype=dtype ) @parameters_as_numpy_arrays('vec') def apply_to_vector(mat, vec): """Apply a matrix to a vector. The matrix's rotation are applied to the vector. Supports multiple matrices and vectors. :param numpy.array mat: The rotation / translation matrix. Can be a list of matrices. :param numpy.array vec: The vector to modify. Can be a list of vectors. :rtype: numpy.array :return: The vectors rotated by the specified matrix. """ if vec.size == 3: return np.dot(vec, mat) else: raise ValueError("Vector size unsupported") def multiply(m1, m2): """Multiply two matricies, m1 . m2. This is essentially a wrapper around numpy.dot( m1, m2 ) :param numpy.array m1: The first matrix. Can be a list of matrices. :param numpy.array m2: The second matrix. Can be a list of matrices. :rtype: numpy.array :return: A matrix that results from multiplying m1 by m2. """ return np.dot(m1, m2) def inverse(mat): """Returns the inverse of the matrix. This is essentially a wrapper around numpy.linalg.inv. :param numpy.array m: A matrix. :rtype: numpy.array :return: The inverse of the specified matrix. .. seealso:: http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.inv.html """ return np.linalg.inv(mat) def create_direction_scale(direction, scale): """Creates a matrix which can apply a directional scaling to a set of vectors. An example usage for this is to flatten a mesh against a single plane. :param numpy.array direction: a numpy.array of shape (3,) of the direction to scale. :param float scale: a float value for the scaling along the specified direction. A scale of 0.0 will flatten the vertices into a single plane with the direction being the plane's normal. :rtype: numpy.array :return: The scaling matrix. """ """ scaling is defined as: [p'][1 + (k - 1)n.x^2, (k - 1)n.x n.y^2, (k - 1)n.x n.z ] S(n,k) = [q'][(k - 1)n.x n.y, 1 + (k - 1)n.y, (k - 1)n.y n.z ] [r'][(k - 1)n.x n.z, (k - 1)n.y n.z, 1 + (k - 1)n.z^2 ] where: v' is the resulting vector after scaling v is the vector to scale n is the direction of the scaling n.x is the x component of n n.y is the y component of n n.z is the z component of n k is the scaling factor """ if not np.isclose(np.linalg.norm(direction), 1.): direction = vector.normalize(direction) x,y,z = direction x2 = x**2. y2 = y**2. z2 = z**2 scaleMinus1 = scale - 1. return np.array( [ # m1 [ # m11 = 1 + (k - 1)n.x^2 1. + scaleMinus1 * x2, # m12 = (k - 1)n.x n.y^2 scaleMinus1 * x * y2, # m13 = (k - 1)n.x n.z scaleMinus1 * x * z ], # m2 [ # m21 = (k - 1)n.x n.y scaleMinus1 * x * y, # m22 = 1 + (k - 1)n.y 1. + scaleMinus1 * y, # m23 = (k - 1)n.y n.z scaleMinus1 * y * z ], # m3 [ # m31 = (k - 1)n.x n.z scaleMinus1 * x * z, # m32 = (k - 1)n.y n.z scaleMinus1 * y * z, # m33 = 1 + (k - 1)n.z^2 1. + scaleMinus1 * z2 ] ] ) Pyrr-0.10.3/pyrr/matrix44.py000077500000000000000000000422231345610666600155740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """4x4 Matrix which supports rotation, translation, scale and skew. Matrices are laid out in row-major format and can be loaded directly into OpenGL. To convert to column-major format, transpose the array using the numpy.array.T method. """ from __future__ import absolute_import, division, print_function import numpy as np from . import matrix33 from . import vector from . import vector3 from . import quaternion from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays def create_identity(dtype=None): """Creates a new matrix44 and sets it to an identity matrix. :rtype: numpy.array :return: A matrix representing an identity matrix with shape (4,4). """ return np.identity(4, dtype=dtype) def create_from_matrix33(mat, dtype=None): """Creates a Matrix44 from a Matrix33. The translation will be 0,0,0. :rtype: numpy.array :return: A matrix with shape (4,4) with the input matrix rotation. """ mat4 = np.identity(4, dtype=dtype) mat4[0:3, 0:3] = mat return mat4 def create_matrix33_view(mat): """Returns a view into the matrix in Matrix33 format. This is different from matrix33.create_from_matrix44, in that changes to the returned matrix will also alter the original matrix. :rtype: numpy.array :return: A view into the matrix in the format of a matrix33 (shape (3,3)). """ return mat[0:3, 0:3] @parameters_as_numpy_arrays('eulers') def create_from_eulers(eulers, dtype=None): """Creates a matrix from the specified Euler rotations. :param numpy.array eulers: A set of euler rotations in the format specified by the euler modules. :rtype: numpy.array :return: A matrix with shape (4,4) with the euler's rotation. """ dtype = dtype or eulers.dtype # set to identity matrix # this will populate our extra rows for us mat = create_identity(dtype) # we'll use Matrix33 for our conversion mat[0:3, 0:3] = matrix33.create_from_eulers(eulers, dtype) return mat @parameters_as_numpy_arrays('axis') def create_from_axis_rotation(axis, theta, dtype=None): """Creates a matrix from the specified rotation theta around an axis. :param numpy.array axis: A (3,) vector. :param float theta: A rotation in radians. :rtype: numpy.array :return: A matrix with shape (4,4). """ dtype = dtype or axis.dtype # set to identity matrix # this will populate our extra rows for us mat = create_identity(dtype) # we'll use Matrix33 for our conversion mat[0:3, 0:3] = matrix33.create_from_axis_rotation(axis, theta, dtype) return mat @parameters_as_numpy_arrays('quat') def create_from_quaternion(quat, dtype=None): """Creates a matrix with the same rotation as a quaternion. :param quat: The quaternion to create the matrix from. :rtype: numpy.array :return: A matrix with shape (4,4) with the quaternion's rotation. """ dtype = dtype or quat.dtype # set to identity matrix # this will populate our extra rows for us mat = create_identity(dtype) # we'll use Matrix33 for our conversion mat[0:3, 0:3] = matrix33.create_from_quaternion(quat, dtype) return mat @parameters_as_numpy_arrays('quat') def create_from_inverse_of_quaternion(quat, dtype=None): """Creates a matrix with the inverse rotation of a quaternion. This can be used to go from object space to intertial space. :param numpy.array quat: The quaternion to make the matrix from (shape 4). :rtype: numpy.array :return: A matrix with shape (4,4) that respresents the inverse of the quaternion. """ dtype = dtype or quat.dtype # set to identity matrix # this will populate our extra rows for us mat = create_identity(dtype) # we'll use Matrix33 for our conversion mat[0:3, 0:3] = matrix33.create_from_inverse_of_quaternion(quat, dtype) return mat @parameters_as_numpy_arrays('vec') def create_from_translation(vec, dtype=None): """Creates an identity matrix with the translation set. :param numpy.array vec: The translation vector (shape 3 or 4). :rtype: numpy.array :return: A matrix with shape (4,4) that represents a matrix with the translation set to the specified vector. """ dtype = dtype or vec.dtype mat = create_identity(dtype) mat[3, 0:3] = vec[:3] return mat def create_from_scale(scale, dtype=None): """Creates an identity matrix with the scale set. :param numpy.array scale: The scale to apply as a vector (shape 3). :rtype: numpy.array :return: A matrix with shape (4,4) with the scale set to the specified vector. """ # we need to expand 'scale' into it's components # because numpy isn't flattening them properly. m = np.diagflat([scale[0], scale[1], scale[2], 1.0]) if dtype: m = m.astype(dtype) return m def create_from_x_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the X axis. :param float theta: The rotation, in radians, about the X-axis. :rtype: numpy.array :return: A matrix with the shape (4,4) with the specified rotation about the X-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ mat = create_identity(dtype) mat[0:3, 0:3] = matrix33.create_from_x_rotation(theta, dtype) return mat def create_from_y_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the Y axis. :param float theta: The rotation, in radians, about the Y-axis. :rtype: numpy.array :return: A matrix with the shape (4,4) with the specified rotation about the Y-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ mat = create_identity(dtype) mat[0:3, 0:3] = matrix33.create_from_y_rotation(theta, dtype) return mat def create_from_z_rotation(theta, dtype=None): """Creates a matrix with the specified rotation about the Z axis. :param float theta: The rotation, in radians, about the Z-axis. :rtype: numpy.array :return: A matrix with the shape (4,4) with the specified rotation about the Z-axis. .. seealso:: http://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions """ mat = create_identity(dtype) mat[0:3, 0:3] = matrix33.create_from_z_rotation(theta, dtype) return mat @all_parameters_as_numpy_arrays def apply_to_vector(mat, vec): """Apply a matrix to a vector. The matrix's rotation and translation are applied to the vector. Supports multiple matrices and vectors. :param numpy.array mat: The rotation / translation matrix. Can be a list of matrices. :param numpy.array vec: The vector to modify. Can be a list of vectors. :rtype: numpy.array :return: The vectors rotated by the specified matrix. """ if vec.size == 3: # convert to a vec4 vec4 = np.array([vec[0], vec[1], vec[2], 1.], dtype=vec.dtype) vec4 = np.dot(vec4, mat) if np.allclose(vec4[3], 0.): vec4[:] = [np.inf, np.inf, np.inf, np.inf] else: vec4 /= vec4[3] return vec4[:3] elif vec.size == 4: return np.dot(vec, mat) else: raise ValueError("Vector size unsupported") def multiply(m1, m2): """Multiply two matricies, m1 . m2. This is essentially a wrapper around numpy.dot(m1, m2) :param numpy.array m1: The first matrix. Can be a list of matrices. :param numpy.array m2: The second matrix. Can be a list of matrices. :rtype: numpy.array :return: A matrix that results from multiplying m1 by m2. """ return np.dot(m1, m2) def create_perspective_projection(fovy, aspect, near, far, dtype=None): """Creates perspective projection matrix. .. seealso:: http://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml .. seealso:: http://www.geeks3d.com/20090729/howto-perspective-projection-matrix-in-opengl/ :param float fovy: field of view in y direction in degrees :param float aspect: aspect ratio of the view (width / height) :param float near: distance from the viewer to the near clipping plane (only positive) :param float far: distance from the viewer to the far clipping plane (only positive) :rtype: numpy.array :return: A projection matrix representing the specified perpective. """ ymax = near * np.tan(fovy * np.pi / 360.0) xmax = ymax * aspect return create_perspective_projection_from_bounds(-xmax, xmax, -ymax, ymax, near, far, dtype=dtype) def create_perspective_projection_matrix(fovy, aspect, near, far, dtype=None): # TDOO: mark as deprecated """Creates perspective projection matrix. .. seealso:: http://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml .. seealso:: http://www.geeks3d.com/20090729/howto-perspective-projection-matrix-in-opengl/ :param float fovy: field of view in y direction in degrees :param float aspect: aspect ratio of the view (width / height) :param float near: distance from the viewer to the near clipping plane (only positive) :param float far: distance from the viewer to the far clipping plane (only positive) :rtype: numpy.array :return: A projection matrix representing the specified perpective. """ return create_perspective_projection(fovy, aspect, near, far, dtype) def create_perspective_projection_from_bounds( left, right, bottom, top, near, far, dtype=None ): """Creates a perspective projection matrix using the specified near plane dimensions. :param float left: The left of the near plane relative to the plane's centre. :param float right: The right of the near plane relative to the plane's centre. :param float top: The top of the near plane relative to the plane's centre. :param float bottom: The bottom of the near plane relative to the plane's centre. :param float near: The distance of the near plane from the camera's origin. It is recommended that the near plane is set to 1.0 or above to avoid rendering issues at close range. :param float far: The distance of the far plane from the camera's origin. :rtype: numpy.array :return: A projection matrix representing the specified perspective. .. seealso:: http://www.gamedev.net/topic/264248-building-a-projection-matrix-without-api/ .. seealso:: http://www.glprogramming.com/red/chapter03.html """ """ E 0 A 0 0 F B 0 0 0 C D 0 0-1 0 A = (right+left)/(right-left) B = (top+bottom)/(top-bottom) C = -(far+near)/(far-near) D = -2*far*near/(far-near) E = 2*near/(right-left) F = 2*near/(top-bottom) """ A = (right + left) / (right - left) B = (top + bottom) / (top - bottom) C = -(far + near) / (far - near) D = -2. * far * near / (far - near) E = 2. * near / (right - left) F = 2. * near / (top - bottom) return np.array(( ( E, 0., 0., 0.), ( 0., F, 0., 0.), ( A, B, C,-1.), ( 0., 0., D, 0.), ), dtype=dtype) def create_perspective_projection_matrix_from_bounds( left, right, bottom, top, near, far, dtype=None): # TDOO: mark as deprecated """Creates a perspective projection matrix using the specified near plane dimensions. :param float left: The left of the near plane relative to the plane's centre. :param float right: The right of the near plane relative to the plane's centre. :param float top: The top of the near plane relative to the plane's centre. :param float bottom: The bottom of the near plane relative to the plane's centre. :param float near: The distance of the near plane from the camera's origin. It is recommended that the near plane is set to 1.0 or above to avoid rendering issues at close range. :param float far: The distance of the far plane from the camera's origin. :rtype: numpy.array :return: A projection matrix representing the specified perspective. .. seealso:: http://www.gamedev.net/topic/264248-building-a-projection-matrix-without-api/ .. seealso:: http://www.glprogramming.com/red/chapter03.html """ """ E 0 A 0 0 F B 0 0 0 C D 0 0-1 0 A = (right+left)/(right-left) B = (top+bottom)/(top-bottom) C = -(far+near)/(far-near) D = -2*far*near/(far-near) E = 2*near/(right-left) F = 2*near/(top-bottom) """ return create_perspective_projection_from_bounds( left, right, bottom, top, near, far, dtype ) def create_orthogonal_projection( left, right, bottom, top, near, far, dtype=None ): """Creates an orthogonal projection matrix. :param float left: The left of the near plane relative to the plane's centre. :param float right: The right of the near plane relative to the plane's centre. :param float top: The top of the near plane relative to the plane's centre. :param float bottom: The bottom of the near plane relative to the plane's centre. :param float near: The distance of the near plane from the camera's origin. It is recommended that the near plane is set to 1.0 or above to avoid rendering issues at close range. :param float far: The distance of the far plane from the camera's origin. :rtype: numpy.array :return: A projection matrix representing the specified orthogonal perspective. .. seealso:: http://msdn.microsoft.com/en-us/library/dd373965(v=vs.85).aspx """ """ A 0 0 Tx 0 B 0 Ty 0 0 C Tz 0 0 0 1 A = 2 / (right - left) B = 2 / (top - bottom) C = -2 / (far - near) Tx = (right + left) / (right - left) Ty = (top + bottom) / (top - bottom) Tz = (far + near) / (far - near) """ rml = right - left tmb = top - bottom fmn = far - near A = 2. / rml B = 2. / tmb C = -2. / fmn Tx = -(right + left) / rml Ty = -(top + bottom) / tmb Tz = -(far + near) / fmn return np.array(( ( A, 0., 0., 0.), (0., B, 0., 0.), (0., 0., C, 0.), (Tx, Ty, Tz, 1.), ), dtype=dtype) def create_orthogonal_projection_matrix( left, right, bottom, top, near, far, dtype=None): # TDOO: mark as deprecated """Creates an orthogonal projection matrix. :param float left: The left of the near plane relative to the plane's centre. :param float right: The right of the near plane relative to the plane's centre. :param float top: The top of the near plane relative to the plane's centre. :param float bottom: The bottom of the near plane relative to the plane's centre. :param float near: The distance of the near plane from the camera's origin. It is recommended that the near plane is set to 1.0 or above to avoid rendering issues at close range. :param float far: The distance of the far plane from the camera's origin. :rtype: numpy.array :return: A projection matrix representing the specified orthogonal perspective. .. seealso:: http://msdn.microsoft.com/en-us/library/dd373965(v=vs.85).aspx """ """ A 0 0 Tx 0 B 0 Ty 0 0 C Tz 0 0 0 1 A = 2 / (right - left) B = 2 / (top - bottom) C = -2 / (far - near) Tx = (right + left) / (right - left) Ty = (top + bottom) / (top - bottom) Tz = (far + near) / (far - near) """ return create_orthogonal_projection( left, right, bottom, top, near, far, dtype ) def create_look_at(eye, target, up, dtype=None): """Creates a look at matrix according to OpenGL standards. :param numpy.array eye: Position of the camera in world coordinates. :param numpy.array target: The position in world coordinates that the camera is looking at. :param numpy.array up: The up vector of the camera. :rtype: numpy.array :return: A look at matrix that can be used as a viewMatrix """ eye = np.asarray(eye) target = np.asarray(target) up = np.asarray(up) forward = vector.normalize(target - eye) side = vector.normalize(np.cross(forward, up)) up = vector.normalize(np.cross(side, forward)) return np.array(( (side[0], up[0], -forward[0], 0.), (side[1], up[1], -forward[1], 0.), (side[2], up[2], -forward[2], 0.), (-np.dot(side, eye), -np.dot(up, eye), np.dot(forward, eye), 1.0) ), dtype=dtype) def inverse(m): """Returns the inverse of the matrix. This is essentially a wrapper around numpy.linalg.inv. :param numpy.array m: A matrix. :rtype: numpy.array :return: The inverse of the specified matrix. .. seealso:: http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.inv.html """ return np.linalg.inv(m) def decompose(m): """Decomposes an affine transformation matrix into its scale, rotation and translation components. :param numpy.array m: A matrix. :return: tuple (scale, rotation, translation) numpy.array scale vector3 numpy.array rotation quaternion numpy.array translation vector3 """ m = np.asarray(m) scale = np.linalg.norm(m[:3, :3], axis=1) det = np.linalg.det(m) if det < 0: scale[0] *= -1 position = m[3, :3] rotation = m[:3, :3] * (1 / scale)[:, None] return scale, quaternion.create_from_matrix(rotation), position Pyrr-0.10.3/pyrr/objects/000077500000000000000000000000001345610666600151715ustar00rootroot00000000000000Pyrr-0.10.3/pyrr/objects/__init__.py000066400000000000000000000007041345610666600173030ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function __all__ = [ 'base', 'matrix33', 'matrix44', 'quaternion', 'vector3', 'vector4', ] from . import ( base, matrix33, matrix44, quaternion, vector3, vector4, ) from .matrix33 import Matrix33 from .matrix44 import Matrix44 from .quaternion import Quaternion from .vector3 import Vector3 from .vector4 import Vector4 Pyrr-0.10.3/pyrr/objects/base.py000066400000000000000000000104211345610666600164530ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import numpy as np from pyrr import vector, vector3 class NpProxy(object): def __init__(self, index): self._index = index def __get__(self, obj, cls): return obj[self._index] def __set__(self, obj, value): obj[self._index] = value class BaseObject(np.ndarray): _module = None _shape = None def __new__(cls, obj): # ensure the object matches the required shape obj.shape = cls._shape return obj def _unsupported_type(self, method, other): raise ValueError('Cannot {} a {} to a {}'.format(method, type(other).__name__, type(self).__name__)) ######################## # Redirect assignment operators def __iadd__(self, other): self[:] = self.__add__(other) return self def __isub__(self, other): self[:] = self.__sub__(other) return self def __imul__(self, other): self[:] = self.__mul__(other) return self def __idiv__(self, other): self[:] = self.__div__(other) return self class BaseMatrix(BaseObject): @classmethod def identity(cls, dtype=None): """Creates an identity Matrix. """ return cls(cls._module.create_identity(dtype), dtype) @classmethod def from_eulers(cls, eulers, dtype=None): """Creates a Matrix from the specified Euler angles. """ return cls(cls._module.create_from_eulers(eulers, dtype=dtype)) @classmethod def from_quaternion(cls, quat, dtype=None): """Creates a Matrix from a Quaternion. """ return cls(cls._module.create_from_quaternion(quat, dtype=dtype)) @classmethod def from_inverse_of_quaternion(cls, quat, dtype=None): """Creates a Matrix from the inverse of the specified Quaternion. """ return cls(cls._module.create_from_inverse_of_quaternion(quat, dtype=dtype)) @classmethod def from_scale(cls, scale, dtype=None): return cls(cls._module.create_from_scale(scale, dtype=dtype)) @classmethod def from_x_rotation(cls, theta, dtype=None): """Creates a Matrix with a rotation around the X-axis. """ return cls(cls._module.create_from_x_rotation(theta, dtype=dtype)) @classmethod def from_y_rotation(cls, theta, dtype=None): return cls(cls._module.create_from_y_rotation(theta, dtype=dtype)) @classmethod def from_z_rotation(cls, theta, dtype=None): """Creates a Matrix with a rotation around the Z-axis. """ return cls(cls._module.create_from_z_rotation(theta, dtype=dtype)) @property def inverse(self): """Returns the inverse of this matrix. """ return type(self)(self._module.inverse(self)) class BaseVector(BaseObject): @classmethod def from_matrix44_translation(cls, matrix, dtype=None): return cls(cls._module.create_from_matrix44_translation(matrix, dtype)) def normalize(self): self[:] = self.normalized @property def normalized(self): return type(self)(self._module.normalize(self)) def normalise(self): # TODO: mark as deprecated self[:] = self.normalized @property def normalised(self): # TODO: mark as deprecated return type(self)(self._module.normalize(self)) @property def squared_length(self): return self._module.squared_length(self) @property def length(self): return self._module.length(self) @length.setter def length(self, length): self[:] = vector.set_length(self, length) def dot(self, other): return vector.dot(self, type(self)(other)) def cross(self, other): return type(self)(vector3.cross(self[:3], other[:3])) def interpolate(self, other, delta): return type(self)(vector.interpolate(self, type(self)(other), delta)) def normal(self, v2, v3, normalize_result=True): return type(self)(vector3.generate_normals(self, type(self)(v2), type(self)(v3), normalize_result)) class BaseQuaternion(BaseObject): pass # pre-declarations to prevent circular imports class BaseMatrix33(BaseMatrix): pass class BaseMatrix44(BaseMatrix): pass class BaseVector3(BaseVector): pass class BaseVector4(BaseVector): pass Pyrr-0.10.3/pyrr/objects/matrix33.py000066400000000000000000000157101345610666600172210ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Represents a 3x3 Matrix. The Matrix33 class provides a number of convenient functions and conversions. :: import numpy as np from pyrr import Quaternion, Matrix33, Matrix44, Vector3 m = Matrix33() m = Matrix33([[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]]) # copy constructor m = Matrix44(Matrix44()) # explicit creation m = Matrix33.identity() m = Matrix33.from_matrix44(Matrix44()) # inferred conversions m = Matrix33(Quaternion()) m = Matrix33(Matrix44()) # multiply matricies together m = Matrix33() * Matrix33() m = Matrix33() * Matrix44() # extract a quaternion from a matrix q = m.quaternion # convert from quaternion back to matrix m = q.matrix33 m = Matrix33(q) # rotate a matrix by a quaternion m = Matrix33.identity() * Quaternion() # rotate a vector 3 by a matrix v = Matrix33.from_x_rotation(np.pi) * Vector3([1.,2.,3.]) # undo a rotation m = Matrix33.from_x_rotation(np.pi) v = m * Vector3([1.,1.,1.]) # ~m is the same as m.inverse v = ~m * v # access specific parts of the matrix # first row m1 = m.m1 # first element, first row m11 = m.m11 # third element, third row m33 = m.m33 # first row, same as m1 r1 = m.r1 # first column c1 = m.c1 """ from __future__ import absolute_import from numbers import Number import numpy as np from multipledispatch import dispatch from .base import BaseObject, BaseMatrix, BaseMatrix33, BaseQuaternion, BaseVector, NpProxy from .. import matrix33 class Matrix33(BaseMatrix33): _module = matrix33 _shape = (3,3,) # m style access #: The first row of this Matrix as a numpy.ndarray. m1 = NpProxy(0) #: The second row of this Matrix as a numpy.ndarray. m2 = NpProxy(1) #: The third row of this Matrix as a numpy.ndarray. m3 = NpProxy(2) # m access #: The [0,0] value of this Matrix. m11 = NpProxy((0,0)) #: The [0,1] value of this Matrix. m12 = NpProxy((0,1)) #: The [0,2] value of this Matrix. m13 = NpProxy((0,2)) #: The [1,0] value of this Matrix. m21 = NpProxy((1,0)) #: The [1,1] value of this Matrix. m22 = NpProxy((1,1)) #: The [1,2] value of this Matrix. m23 = NpProxy((1,2)) #: The [2,0] value of this Matrix. m31 = NpProxy((2,0)) #: The [2,1] value of this Matrix. m32 = NpProxy((2,1)) #: The [2,2] value of this Matrix. m33 = NpProxy((2,2)) # rows #: The first row of this Matrix as a numpy.ndarray. This is the same as m1. r1 = NpProxy(0) #: The second row of this Matrix as a numpy.ndarray. This is the same as m2. r2 = NpProxy(1) #: The third row of this Matrix as a numpy.ndarray. This is the same as m3. r3 = NpProxy(2) # columns #: The first column of this Matrix as a numpy.ndarray. c1 = NpProxy((slice(0,3),0)) #: The second column of this Matrix as a numpy.ndarray. c2 = NpProxy((slice(0,3),1)) #: The third column of this Matrix as a numpy.ndarray. c3 = NpProxy((slice(0,3),2)) ######################## # Creation @classmethod def from_matrix44(cls, matrix, dtype=None): """Creates a Matrix33 from a Matrix44. The Matrix44 translation will be lost. """ return cls(matrix33.create_from_matrix44(matrix, dtype)) def __new__(cls, value=None, dtype=None): if value is not None: obj = value if not isinstance(value, np.ndarray): obj = np.array(value, dtype=dtype) # matrix44 if obj.shape == (4,4) or isinstance(obj, Matrix44): obj = matrix33.create_from_matrix44(obj, dtype=dtype) # quaternion elif obj.shape == (4,) or isinstance(obj, Quaternion): obj = matrix33.create_from_quaternion(obj, dtype=dtype) else: obj = np.zeros(cls._shape, dtype=dtype) obj = obj.view(cls) return super(Matrix33, cls).__new__(cls, obj) ######################## # Basic Operators @dispatch(BaseObject) def __add__(self, other): self._unsupported_type('add', other) @dispatch(BaseObject) def __sub__(self, other): self._unsupported_type('subtract', other) @dispatch(BaseObject) def __mul__(self, other): self._unsupported_type('multiply', other) @dispatch(BaseObject) def __truediv__(self, other): self._unsupported_type('divide', other) @dispatch(BaseObject) def __div__(self, other): self._unsupported_type('divide', other) def __invert__(self): return self.inverse ######################## # Matrices @dispatch((BaseMatrix, np.ndarray, list)) def __add__(self, other): return Matrix33(super(Matrix33, self).__add__(Matrix33(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __sub__(self, other): return Matrix33(super(Matrix33, self).__sub__(Matrix33(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __mul__(self, other): return Matrix33(matrix33.multiply(Matrix33(other), self)) @dispatch((BaseMatrix, np.ndarray, list)) def __ne__(self, other): return bool(np.any(super(Matrix33, self).__ne__(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __eq__(self, other): return bool(np.all(super(Matrix33, self).__eq__(other))) ######################## # Quaternions @dispatch(BaseQuaternion) def __mul__(self, other): m = other.matrix33 return self * m ######################## # Vectors @dispatch(BaseVector) def __mul__(self, other): return type(other)(matrix33.apply_to_vector(self, other)) ######################## # Number @dispatch((Number, np.number)) def __add__(self, other): return Matrix33(super(Matrix33, self).__add__(other)) @dispatch((Number, np.number)) def __sub__(self, other): return Matrix33(super(Matrix33, self).__sub__(other)) @dispatch((Number, np.number)) def __mul__(self, other): return Matrix33(super(Matrix33, self).__mul__(other)) @dispatch((Number, np.number)) def __truediv__(self, other): return Matrix33(super(Matrix33, self).__truediv__(other)) @dispatch((Number, np.number)) def __div__(self, other): return Matrix33(super(Matrix33, self).__div__(other)) ######################## # Methods and Properties @property def matrix33(self): """Returns the Matrix33. This can be handy if you're not sure what type of Matrix class you have but require a Matrix33. """ return self @property def matrix44(self): """Returns a Matrix44 representing this matrix. """ return Matrix44(self) @property def quaternion(self): """Returns a Quaternion representing this matrix. """ return Quaternion(self) from .matrix44 import Matrix44 from .quaternion import Quaternion Pyrr-0.10.3/pyrr/objects/matrix44.py000066400000000000000000000225601345610666600172240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Represents a 4x4 Matrix. The Matrix44 class provides a number of convenient functions and conversions. :: import numpy as np from pyrr import Quaternion, Matrix33, Matrix44, Vector4 m = Matrix44() m = Matrix44([[1.,0.,0.,0.],[0.,1.,0.,0.],[0.,0.,1.,0.],[0.,0.,0.,1.]]) # copy constructor m = Matrix44(Matrix44()) # explicit creation m = Matrix44.identity() m = Matrix44.from_matrix44(Matrix44()) # inferred conversions m = Matrix44(Quaternion()) m = Matrix44(Matrix33()) # multiply matricies together m = Matrix44() * Matrix44() # extract a quaternion from a matrix q = m.quaternion # convert from quaternion back to matrix m = q.matrix44 m = Matrix44(q) # rotate a matrix by a quaternion m = Matrix44.identity() * Quaternion() # rotate a vector 4 by a matrix v = Matrix44.from_x_rotation(np.pi) * Vector4([1.,2.,3.,1.]) # undo a rotation m = Matrix44.from_x_rotation(np.pi) v = m * Vector4([1.,1.,1.,1.]) # ~m is the same as m.inverse v = ~m * v # access specific parts of the matrix # first row m1 = m.m1 # first element, first row m11 = m.m11 # fourth element, fourth row m44 = m.m44 # first row, same as m1 r1 = m.r1 # first column c1 = m.c1 """ from __future__ import absolute_import from numbers import Number import numpy as np from multipledispatch import dispatch from .base import BaseObject, BaseMatrix, BaseMatrix44, BaseQuaternion, BaseVector, NpProxy from .. import matrix44 class Matrix44(BaseMatrix44): _module = matrix44 _shape = (4,4,) # m style access #: The first row of this Matrix as a numpy.ndarray. m1 = NpProxy(0) #: The second row of this Matrix as a numpy.ndarray. m2 = NpProxy(1) #: The third row of this Matrix as a numpy.ndarray. m3 = NpProxy(2) #: The fourth row of this Matrix as a numpy.ndarray. m4 = NpProxy(3) # m access #: The [0,0] value of this Matrix. m11 = NpProxy((0,0)) #: The [0,1] value of this Matrix. m12 = NpProxy((0,1)) #: The [0,2] value of this Matrix. m13 = NpProxy((0,2)) #: The [0,3] value of this Matrix. m14 = NpProxy((0,3)) #: The [1,0] value of this Matrix. m21 = NpProxy((1,0)) #: The [1,1] value of this Matrix. m22 = NpProxy((1,1)) #: The [1,2] value of this Matrix. m23 = NpProxy((1,2)) #: The [1,3] value of this Matrix. m24 = NpProxy((1,3)) #: The [2,0] value of this Matrix. m31 = NpProxy((2,0)) #: The [2,1] value of this Matrix. m32 = NpProxy((2,1)) #: The [2,2] value of this Matrix. m33 = NpProxy((2,2)) #: The [2,3] value of this Matrix. m34 = NpProxy((2,3)) #: The [3,0] value of this Matrix. m41 = NpProxy((3,0)) #: The [3,1] value of this Matrix. m42 = NpProxy((3,1)) #: The [3,2] value of this Matrix. m43 = NpProxy((3,2)) #: The [3,3] value of this Matrix. m44 = NpProxy((3,3)) # rows #: The first row of this Matrix as a numpy.ndarray. This is the same as m1. r1 = NpProxy(0) #: The second row of this Matrix as a numpy.ndarray. This is the same as m2. r2 = NpProxy(1) #: The third row of this Matrix as a numpy.ndarray. This is the same as m3. r3 = NpProxy(2) #: The fourth row of this Matrix as a numpy.ndarray. This is the same as m4. r4 = NpProxy(3) # columns #: The first column of this Matrix as a numpy.ndarray. c1 = NpProxy((slice(0,4),0)) #: The second column of this Matrix as a numpy.ndarray. c2 = NpProxy((slice(0,4),1)) #: The third column of this Matrix as a numpy.ndarray. c3 = NpProxy((slice(0,4),2)) #: The fourth column of this Matrix as a numpy.ndarray. c4 = NpProxy((slice(0,4),3)) ######################## # Creation @classmethod def from_matrix33(cls, matrix, dtype=None): """Creates a Matrix44 from a Matrix33. """ return cls(matrix44.create_from_matrix33(matrix, dtype)) @classmethod def perspective_projection(cls, fovy, aspect, near, far, dtype=None): """Creates a Matrix44 for use as a perspective projection matrix. """ return cls(matrix44.create_perspective_projection(fovy, aspect, near, far, dtype)) @classmethod def perspective_projection_bounds(cls, left, right, top, bottom, near, far, dtype=None): """Creates a Matrix44 for use as a perspective projection matrix. """ return cls(matrix44.create_perspective_projection_from_bounds(left, right, top, bottom, near, far, dtype)) @classmethod def orthogonal_projection(cls, left, right, top, bottom, near, far, dtype=None): """Creates a Matrix44 for use as an orthogonal projection matrix. """ return cls(matrix44.create_orthogonal_projection(left, right, top, bottom, near, far, dtype)) @classmethod def look_at(cls, eye, target, up, dtype=None): """Creates a Matrix44 for use as a lookAt matrix. """ return cls(matrix44.create_look_at(eye, target, up, dtype)) @classmethod def from_translation(cls, translation, dtype=None): """Creates a Matrix44 from the specified translation. """ return cls(matrix44.create_from_translation(translation, dtype=dtype)) def __new__(cls, value=None, dtype=None): if value is not None: obj = value if not isinstance(value, np.ndarray): obj = np.array(value, dtype=dtype) # matrix33 if obj.shape == (3,3) or isinstance(obj, Matrix33): obj = matrix44.create_from_matrix33(obj, dtype=dtype) # quaternion elif obj.shape == (4,) or isinstance(obj, Quaternion): obj = matrix44.create_from_quaternion(obj, dtype=dtype) else: obj = np.zeros(cls._shape, dtype=dtype) obj = obj.view(cls) return super(Matrix44, cls).__new__(cls, obj) ######################## # Basic Operators @dispatch(BaseObject) def __add__(self, other): self._unsupported_type('add', other) @dispatch(BaseObject) def __sub__(self, other): self._unsupported_type('subtract', other) @dispatch(BaseObject) def __mul__(self, other): self._unsupported_type('multiply', other) @dispatch(BaseObject) def __truediv__(self, other): self._unsupported_type('divide', other) @dispatch(BaseObject) def __div__(self, other): self._unsupported_type('divide', other) def __invert__(self): return self.inverse ######################## # Matrices @dispatch((BaseMatrix, np.ndarray, list)) def __add__(self, other): return Matrix44(super(Matrix44, self).__add__(Matrix44(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __sub__(self, other): return Matrix44(super(Matrix44, self).__sub__(Matrix44(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __mul__(self, other): return Matrix44(matrix44.multiply(Matrix44(other), self)) @dispatch((BaseMatrix, np.ndarray, list)) def __ne__(self, other): return bool(np.any(super(Matrix44, self).__ne__(other))) @dispatch((BaseMatrix, np.ndarray, list)) def __eq__(self, other): return bool(np.all(super(Matrix44, self).__eq__(other))) ######################## # Quaternions @dispatch(BaseQuaternion) def __mul__(self, other): m = other.matrix44 return self * m ######################## # Vectors @dispatch(BaseVector) def __mul__(self, other): return type(other)(matrix44.apply_to_vector(self, other)) ######################## # Number @dispatch((Number, np.number)) def __add__(self, other): return Matrix44(super(Matrix44, self).__add__(other)) @dispatch((Number, np.number)) def __sub__(self, other): return Matrix44(super(Matrix44, self).__sub__(other)) @dispatch((Number, np.number)) def __mul__(self, other): return Matrix44(super(Matrix44, self).__mul__(other)) @dispatch((Number, np.number)) def __truediv__(self, other): return Matrix44(super(Matrix44, self).__truediv__(other)) @dispatch((Number, np.number)) def __div__(self, other): return Matrix44(super(Matrix44, self).__div__(other)) ######################## # Methods and Properties @property def matrix33(self): """Returns a Matrix33 representing this matrix. """ return Matrix33(self) @property def matrix44(self): """Returns the Matrix44. This can be handy if you're not sure what type of Matrix class you have but require a Matrix44. """ return self @property def quaternion(self): """Returns a Quaternion representing this matrix. """ return Quaternion(self) def decompose(self): """Decomposes an affine transformation matrix into its scale, rotation and translation components. :param numpy.array m: A matrix. :return: tuple (scale, rotation, translation) Vector3 scale Quaternion rotation Vector3 translation """ scale, rotate, translate = matrix44.decompose(self) return Vector3(scale), Quaternion(rotate), Vector3(translate) from .matrix33 import Matrix33 from .quaternion import Quaternion from .vector3 import Vector3 Pyrr-0.10.3/pyrr/objects/quaternion.py000066400000000000000000000242121345610666600177310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Represents a Quaternion rotation. The Quaternion class provides a number of convenient functions and conversions. :: import numpy as np from pyrr import Quaternion, Matrix33, Matrix44, Vector3, Vector4 q = Quaternion() # explicit creation q = Quaternion.from_x_rotation(np.pi / 2.0) q = Quaternion.from_matrix(Matrix33.identity()) q = Quaternion.from_matrix(Matrix44.identity()) # inferred conversions q = Quaternion(Quaternion()) q = Quaternion(Matrix33.identity()) q = Quaternion(Matrix44.identity()) # apply one quaternion to another q1 = Quaternion.from_y_rotation(np.pi / 2.0) q2 = Quaternion.from_x_rotation(np.pi / 2.0) q3 = q1 * q2 # extract a matrix from the quaternion m33 = q3.matrix33 m44 = q3.matrix44 # convert from matrix back to quaternion q4 = Quaternion(m44) # rotate a quaternion by a matrix q = Quaternion() * Matrix33.identity() q = Quaternion() * Matrix44.identity() # apply quaternion to a vector v3 = Quaternion() * Vector3() v4 = Quaternion() * Vector4() # undo a rotation q = Quaternion.from_x_rotation(np.pi / 2.0) v = q * Vector3([1.,1.,1.]) # ~q is the same as q.conjugate original = ~q * v assert np.allclose(original, v) # get the dot product of 2 Quaternions dot = Quaternion() | Quaternion.from_x_rotation(np.pi / 2.0) """ from __future__ import absolute_import import numpy as np from multipledispatch import dispatch from .base import BaseObject, BaseQuaternion, BaseMatrix, BaseVector, NpProxy from .. import quaternion class Quaternion(BaseQuaternion): _module = quaternion _shape = (4,) #: The X value of this Quaternion. x = NpProxy(0) #: The Y value of this Quaternion. y = NpProxy(1) #: The Z value of this Quaternion. z = NpProxy(2) #: The W value of this Quaternion. w = NpProxy(3) #: The X,Y value of this Quaternion as a numpy.ndarray. xy = NpProxy([0,1]) #: The X,Y,Z value of this Quaternion as a numpy.ndarray. xyz = NpProxy([0,1,2]) #: The X,Y,Z,W value of this Quaternion as a numpy.ndarray. xyzw = NpProxy([0,1,2,3]) #: The X,Z value of this Quaternion as a numpy.ndarray. xz = NpProxy([0,2]) #: The X,Z,W value of this Quaternion as a numpy.ndarray. xzw = NpProxy([0,2,3]) #: The X,Y,W value of this Quaternion as a numpy.ndarray. xyw = NpProxy([0,1,3]) #: The X,W value of this Quaternion as a numpy.ndarray. xw = NpProxy([0,3]) ######################## # Creation @classmethod def from_x_rotation(cls, theta, dtype=None): """Creates a new Quaternion with a rotation around the X-axis. """ return cls(quaternion.create_from_x_rotation(theta, dtype)) @classmethod def from_y_rotation(cls, theta, dtype=None): """Creates a new Quaternion with a rotation around the Y-axis. """ return cls(quaternion.create_from_y_rotation(theta, dtype)) @classmethod def from_z_rotation(cls, theta, dtype=None): """Creates a new Quaternion with a rotation around the Z-axis. """ return cls(quaternion.create_from_z_rotation(theta, dtype)) @classmethod def from_axis_rotation(cls, axis, theta, dtype=None): """Creates a new Quaternion with a rotation around the specified axis. """ return cls(quaternion.create_from_axis_rotation(axis, theta, dtype)) @classmethod def from_axis(cls, axis, dtype=None): """Creates a new Quaternion from an axis with angle magnitude. """ return cls(quaternion.create_from_axis(axis, dtype)) @classmethod def from_matrix(cls, matrix, dtype=None): """Creates a Quaternion from the specified Matrix (Matrix33 or Matrix44). """ return cls(quaternion.create_from_matrix(matrix, dtype)) @classmethod def from_eulers(cls, eulers, dtype=None): """Creates a Quaternion from the specified Euler angles. """ return cls(quaternion.create_from_eulers(eulers, dtype)) @classmethod def from_inverse_of_eulers(cls, eulers, dtype=None): """Creates a Quaternion from the inverse of the specified Euler angles. """ return cls(quaternion.create_from_inverse_of_eulers(eulers, dtype)) def __new__(cls, value=None, dtype=None): if value is not None: obj = value if not isinstance(value, np.ndarray): obj = np.array(value, dtype=dtype) # matrix33, matrix44 if obj.shape in ((4,4,), (3,3,)) or isinstance(obj, (Matrix33, Matrix44)): obj = quaternion.create_from_matrix(obj, dtype=dtype) else: obj = quaternion.create(dtype=dtype) obj = obj.view(cls) return super(Quaternion, cls).__new__(cls, obj) ######################## # Basic Operators @dispatch(BaseObject) def __add__(self, other): self._unsupported_type('add', other) @dispatch(BaseObject) def __sub__(self, other): self._unsupported_type('subtract', other) @dispatch(BaseObject) def __mul__(self, other): self._unsupported_type('multiply', other) @dispatch(BaseObject) def __truediv__(self, other): self._unsupported_type('divide', other) @dispatch(BaseObject) def __div__(self, other): self._unsupported_type('divide', other) ######################## # Quaternions @dispatch((BaseQuaternion, np.ndarray, list)) def __sub__(self, other): return Quaternion(super(Quaternion, self).__sub__(other)) @dispatch((BaseQuaternion, list)) def __mul__(self, other): return self.cross(other) @dispatch((BaseQuaternion, list)) def __or__(self, other): return self.dot(other) def __invert__(self): return self.conjugate @dispatch((BaseQuaternion, np.ndarray, list)) def __ne__(self, other): # For quaternions q and -q represent the same rotation return bool(np.any(super(Quaternion, self).__ne__(other)))\ or bool(np.all(super(Quaternion, self).__eq__(-other))) @dispatch((BaseQuaternion, np.ndarray, list)) def __eq__(self, other): # For quaternions q and -q represent the same rotation return bool(np.all(super(Quaternion, self).__eq__(other))) \ or bool(np.all(super(Quaternion, self).__eq__(-other))) ######################## # Matrices @dispatch(BaseMatrix) def __mul__(self, other): return self * Quaternion(other) ######################## # Vectors @dispatch(BaseVector) def __mul__(self, other): return type(other)(quaternion.apply_to_vector(self, other)) ######################## # Methods and Properties @property def length(self): """Returns the length of this Quaternion. """ return quaternion.length(self) def normalize(self): """normalizes this Quaternion in-place. """ self[:] = quaternion.normalize(self) @property def normalized(self): """Returns a normalized version of this Quaternion as a new Quaternion. """ return Quaternion(quaternion.normalize(self)) def normalise(self): # TODO: mark as deprecated """normalizes this Quaternion in-place. """ self[:] = quaternion.normalize(self) @property def normalised(self): # TODO: mark as deprecated """Returns a normalized version of this Quaternion as a new Quaternion. """ return Quaternion(quaternion.normalize(self)) @property def angle(self): """Returns the angle around the axis of rotation of this Quaternion as a float. """ return quaternion.rotation_angle(self) @property def axis(self): """Returns the axis of rotation of this Quaternion as a Vector3. """ return Vector3(quaternion.rotation_axis(self)) def cross(self, other): """Returns the cross of this Quaternion and another. This is the equivalent of combining Quaternion rotations (like Matrix multiplication). """ return Quaternion(quaternion.cross(self, other)) def lerp(self, other, t): """Interpolates between quat1 and quat2 by t. The parameter t is clamped to the range [0, 1] """ return Quaternion(quaternion.lerp(self, other, t)) def slerp(self, other, t): """Spherically interpolates between quat1 and quat2 by t. The parameter t is clamped to the range [0, 1] """ return Quaternion(quaternion.slerp(self, other, t)) def dot(self, other): """Returns the dot of this Quaternion and another. """ return quaternion.dot(self, other) @property def conjugate(self): """Returns the conjugate of this Quaternion. This is a Quaternion with the opposite rotation. """ return Quaternion(quaternion.conjugate(self)) @property def inverse(self): """Returns the inverse of this quaternion. """ return Quaternion(quaternion.inverse(self)) def exp(self): """Returns a new Quaternion representing the exponentional of this Quaternion """ return Quaternion(quaternion.exp(self)) def power(self, exponent): """Returns a new Quaternion representing this Quaternion to the power of the exponent. """ return Quaternion(quaternion.power(self, exponent)) @property def negative(self): """Returns the negative of the Quaternion. """ return Quaternion(quaternion.negate(self)) @property def is_identity(self): """Returns True if the Quaternion has no rotation (0.,0.,0.,1.). """ return quaternion.is_identity(self) @property def matrix44(self): """Returns a Matrix44 representation of this Quaternion. """ return Matrix44.from_quaternion(self) @property def matrix33(self): """Returns a Matrix33 representation of this Quaternion. """ return Matrix33.from_quaternion(self) from .vector3 import Vector3 from .matrix33 import Matrix33 from .matrix44 import Matrix44 Pyrr-0.10.3/pyrr/objects/vector3.py000066400000000000000000000132461345610666600171360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Represents a 3 dimensional Vector. The Vector3 class provides a number of convenient functions and conversions. :: import numpy as np from pyrr import Quaternion, Matrix33, Matrix44, Vector3 v = Vector3() v = Vector3([1.,2.,3.]) # copy constructor v = Vector3(Vector3()) # add / subtract vectors v = Vector3([1.,2.,3.]) + Vector3([4.,5.,6.]) # rotate a vector by a Matrix v = Matrix33.identity() * Vector3([1.,2.,3.]) v = Matrix44.identity() * Vector3([1.,2.,3.]) # rotate a vector by a Quaternion v = Quaternion() * Vector3([1.,2.,3.]) # get the dot-product of 2 vectors d = Vector3([1.,0.,0.]) | Vector3([0.,1.,0.]) # get the cross-product of 2 vectors x = Vector3([1.,0.,0.]) ^ Vector3([0.,1.,0.]) # access specific parts of the vector # x value x,y,z = v.x, v.y, v.z # access groups of values as np.ndarray's xy = v.xy xz = v.xz xyz = v.xyz """ from __future__ import absolute_import, division from numbers import Number import numpy as np from multipledispatch import dispatch from .base import BaseObject, BaseVector3, BaseMatrix44, NpProxy from .. import vector3 # TODO: add < <= > >= == != operators class Vector3(BaseVector3): _module = vector3 _shape = (3,) #: The X value of this Vector. x = NpProxy(0) #: The Y value of this Vector. y = NpProxy(1) #: The Z value of this Vector. z = NpProxy(2) #: The X,Y values of this Vector as a numpy.ndarray. xy = NpProxy([0,1]) #: The X,Y,Z values of this Vector as a numpy.ndarray. xyz = NpProxy([0,1,2]) #: The X,Z values of this Vector as a numpy.ndarray. xz = NpProxy([0,2]) ######################## # Creation @classmethod def from_vector4(cls, vector, dtype=None): """Create a Vector3 from a Vector4. Returns the Vector3 and the W component as a tuple. """ vec, w = vector3.create_from_vector4(vector, dtype) return (cls(vec), w) def __new__(cls, value=None, w=0.0, dtype=None): if value is not None: obj = value if not isinstance(value, np.ndarray): obj = np.array(value, dtype=dtype) # matrix44 if obj.shape in ((4,4,)) or isinstance(obj, BaseMatrix44): obj = vector3.create_from_matrix44_translation(obj, dtype=dtype) else: obj = np.zeros(cls._shape, dtype=dtype) obj = obj.view(cls) return super(Vector3, cls).__new__(cls, obj) ######################## # Basic Operators @dispatch(BaseObject) def __add__(self, other): self._unsupported_type('add', other) @dispatch(BaseObject) def __sub__(self, other): self._unsupported_type('subtract', other) @dispatch(BaseObject) def __mul__(self, other): self._unsupported_type('multiply', other) @dispatch(BaseObject) def __truediv__(self, other): self._unsupported_type('divide', other) @dispatch(BaseObject) def __div__(self, other): self._unsupported_type('divide', other) @dispatch((BaseObject, Number, np.number)) def __xor__(self, other): self._unsupported_type('XOR', other) @dispatch((BaseObject, Number, np.number)) def __or__(self, other): self._unsupported_type('OR', other) @dispatch((BaseObject, Number, np.number)) def __ne__(self, other): self._unsupported_type('NE', other) @dispatch((BaseObject, Number, np.number)) def __eq__(self, other): self._unsupported_type('EQ', other) ######################## # Vectors @dispatch((BaseVector3, np.ndarray, list)) def __add__(self, other): return Vector3(super(Vector3, self).__add__(other)) @dispatch((BaseVector3, np.ndarray, list)) def __sub__(self, other): return Vector3(super(Vector3, self).__sub__(other)) @dispatch((BaseVector3, np.ndarray, list)) def __mul__(self, other): return Vector3(super(Vector3, self).__mul__(other)) @dispatch((BaseVector3, np.ndarray, list)) def __truediv__(self, other): return Vector3(super(Vector3, self).__truediv__(other)) @dispatch((BaseVector3, np.ndarray, list)) def __div__(self, other): return Vector3(super(Vector3, self).__div__(other)) @dispatch((BaseVector3, np.ndarray, list)) def __xor__(self, other): return self.cross(other) @dispatch((BaseVector3, np.ndarray, list)) def __or__(self, other): return self.dot(other) @dispatch((BaseVector3, np.ndarray, list)) def __ne__(self, other): return bool(np.any(super(Vector3, self).__ne__(other))) @dispatch((BaseVector3, np.ndarray, list)) def __eq__(self, other): return bool(np.all(super(Vector3, self).__eq__(other))) ######################## # Number @dispatch((Number,np.number)) def __add__(self, other): return Vector3(super(Vector3, self).__add__(other)) @dispatch((Number,np.number)) def __sub__(self, other): return Vector3(super(Vector3, self).__sub__(other)) @dispatch((Number,np.number)) def __mul__(self, other): return Vector3(super(Vector3, self).__mul__(other)) @dispatch((Number,np.number)) def __truediv__(self, other): return Vector3(super(Vector3, self).__truediv__(other)) @dispatch((Number,np.number)) def __div__(self, other): return Vector3(super(Vector3, self).__div__(other)) ######################## # Methods and Properties @property def inverse(self): """Returns the opposite of this vector. """ return Vector3(-self) @property def vector3(self): return self Pyrr-0.10.3/pyrr/objects/vector4.py000066400000000000000000000143001345610666600171270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Represents a 4 dimensional Vector. The Vector4 class provides a number of convenient functions and conversions. :: import numpy as np from pyrr import Quaternion, Matrix33, Matrix44, Vector4 v = Vector4() v = Vector4([1.,2.,3.]) # explicit creation v = Vector4.from_vector3(Vector3([1.,2.,3.]), w=1.0) # copy constructor v = Vector4(Vector4()) # add / subtract vectors v = Vector4([1.,2.,3.,4.]) + Vector4([4.,5.,6.,7.]) # rotate a vector by a Matrix v = Matrix44.identity() * Vector4([1.,2.,3.,4.]) # rotate a vector by a Quaternion v = Quaternion() * Vector4([1.,2.,3.,4.]) # get the dot-product of 2 vectors d = Vector4([1.,0.,0.,0.]) | Vector4([0.,1.,0.,0.]) # access specific parts of the vector # x value x,y,z,w = v.x, v.y, v.z, v.w # access groups of values as np.ndarray's xy = v.xy xyz = v.xyz xyzw = v.xyzw xz = v.xz xw = v.xw xyw = v.xyw xzw = v.xzw """ from __future__ import absolute_import from numbers import Number import numpy as np from multipledispatch import dispatch from .base import BaseObject, BaseVector4, BaseMatrix44, NpProxy from .. import vector4 # TODO: add < <= > >= == != operators class Vector4(BaseVector4): _module = vector4 _shape = (4,) #: The X value of this Vector. x = NpProxy(0) #: The Y value of this Vector. y = NpProxy(1) #: The Z value of this Vector. z = NpProxy(2) #: The W value of this Vector. w = NpProxy(3) #: The X,Y values of this Vector as a numpy.ndarray. xy = NpProxy([0,1]) #: The X,Y,Z values of this Vector as a numpy.ndarray. xyz = NpProxy([0,1,2]) #: The X,Y,Z,W values of this Vector as a numpy.ndarray. xyzw = NpProxy(slice(0,4)) #: The X,Z values of this Vector as a numpy.ndarray. xz = NpProxy([0,2]) #: The X,W values of this Vector as a numpy.ndarray. xw = NpProxy([0,3]) #: The X,Y,W values of this Vector as a numpy.ndarray. xyw = NpProxy([0,1,3]) #: The X,Z,W values of this Vector as a numpy.ndarray. xzw = NpProxy([0,2,3]) ######################## # Creation @classmethod def from_vector3(cls, vector, w=0.0, dtype=None): """Create a Vector4 from a Vector3. By default, the W value is 0.0. """ return cls(vector4.create_from_vector3(vector, w, dtype)) def __new__(cls, value=None, dtype=None): if value is not None: obj = value if not isinstance(value, np.ndarray): obj = np.array(value, dtype=dtype) # matrix44 if obj.shape in ((4,4,)) or isinstance(obj, BaseMatrix44): obj = vector4.create_from_matrix44_translation(obj, dtype=dtype) else: obj = np.zeros(cls._shape, dtype=dtype) obj = obj.view(cls) return super(Vector4, cls).__new__(cls, obj) ######################## # Basic Operators @dispatch(BaseObject) def __add__(self, other): self._unsupported_type('add', other) @dispatch(BaseObject) def __sub__(self, other): self._unsupported_type('subtract', other) @dispatch(BaseObject) def __mul__(self, other): self._unsupported_type('multiply', other) @dispatch(BaseObject) def __truediv__(self, other): self._unsupported_type('divide', other) @dispatch(BaseObject) def __div__(self, other): self._unsupported_type('divide', other) @dispatch((BaseObject, Number, np.number)) def __xor__(self, other): self._unsupported_type('XOR', other) @dispatch((BaseObject, Number, np.number)) def __or__(self, other): self._unsupported_type('OR', other) @dispatch((BaseObject, Number, np.number)) def __ne__(self, other): self._unsupported_type('NE', other) @dispatch((BaseObject, Number, np.number)) def __eq__(self, other): self._unsupported_type('EQ', other) ######################## # Vectors @dispatch((BaseVector4, np.ndarray, list)) def __add__(self, other): return Vector4(super(Vector4, self).__add__(other)) @dispatch((BaseVector4, np.ndarray, list)) def __sub__(self, other): return Vector4(super(Vector4, self).__sub__(other)) @dispatch((BaseVector4, np.ndarray, list)) def __mul__(self, other): return Vector4(super(Vector4, self).__mul__(other)) @dispatch((BaseVector4, np.ndarray, list)) def __truediv__(self, other): return Vector4(super(Vector4, self).__truediv__(other)) @dispatch((BaseVector4, np.ndarray, list)) def __div__(self, other): return Vector4(super(Vector4, self).__div__(other)) #@dispatch(BaseVector) #def __xor__(self, other): # return self.cross(Vector4(other)) @dispatch((BaseVector4, np.ndarray, list)) def __or__(self, other): return self.dot(Vector4(other)) @dispatch((BaseVector4, np.ndarray, list)) def __ne__(self, other): return bool(np.any(super(Vector4, self).__ne__(other))) @dispatch((BaseVector4, np.ndarray, list)) def __eq__(self, other): return bool(np.all(super(Vector4, self).__eq__(other))) ######################## # Number @dispatch((Number, np.number)) def __add__(self, other): return Vector4(super(Vector4, self).__add__(other)) @dispatch((Number, np.number)) def __sub__(self, other): return Vector4(super(Vector4, self).__sub__(other)) @dispatch((Number, np.number)) def __mul__(self, other): return Vector4(super(Vector4, self).__mul__(other)) @dispatch((Number, np.number)) def __truediv__(self, other): return Vector4(super(Vector4, self).__truediv__(other)) @dispatch((Number, np.number)) def __div__(self, other): return Vector4(super(Vector4, self).__div__(other)) ######################## # Methods and Properties @property def inverse(self): """Returns the opposite of this vector. """ return Vector4(-self) @property def vector3(self): """Returns a Vector3 and the W component as a tuple. """ return (Vector3(self[:3]), self[3]) from .matrix44 import Matrix44 from .vector3 import Vector3 Pyrr-0.10.3/pyrr/plane.py000077500000000000000000000120001345610666600152050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of Planes. Planes are represented using a numpy.array of shape (4,). The values represent the plane equation using the values A,B,C,D. The first three values are the normal vector. The fourth value is the distance of the origin from the plane, down the normal. A negative value indicates the origin is behind the plane, relative to the normal. .. seealso: http://en.wikipedia.org/wiki/Plane_(geometry) .. seealso: http://mathworld.wolfram.com/Plane.html """ from __future__ import absolute_import, division, print_function import numpy as np from . import vector from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays def create(normal=None, distance=0.0, dtype=None): """Creates a plane oriented toward the normal, at distance below the origin. If no normal is provided, the plane will by created at the origin with a normal of [0, 0, 1]. Negative distance indicates the plane is facing toward the origin. :rtype: numpy.array :return: A plane with the specified normal at a distance from the origin of -distance. """ if normal is None: normal = [0.0, 0.0, 1.0] return np.array([normal[0], normal[1], normal[2], distance], dtype=dtype) @parameters_as_numpy_arrays('vector1', 'vector2', 'vector3') def create_from_points(vector1, vector2, vector3, dtype=None): """Create a plane from 3 co-planar vectors. The vectors must all lie on the same plane or an exception will be thrown. The vectors must not all be in a single line or the plane is undefined. The order the vertices are passed in will determine the normal of the plane. :param numpy.array vector1: a vector that lies on the desired plane. :param numpy.array vector2: a vector that lies on the desired plane. :param numpy.array vector3: a vector that lies on the desired plane. :raise ValueError: raised if the vectors are co-incident (in a single line). :rtype: numpy.array :return: A plane that contains the 3 specified vectors. """ dtype = dtype or vector1.dtype # make the vectors relative to vector2 relV1 = vector1 - vector2 relV2 = vector3 - vector2 # cross our relative vectors normal = np.cross(relV1, relV2) if np.count_nonzero(normal) == 0: raise ValueError("Vectors are co-incident") # create our plane return create_from_position(position=vector2, normal=normal, dtype=dtype) @parameters_as_numpy_arrays('position', 'normal') def create_from_position(position, normal, dtype=None): """Creates a plane at position with the normal being above the plane and up being the rotation of the plane. :param numpy.array position: The position of the plane. :param numpy.array normal: The normal of the plane. Will be normalized during construction. :rtype: numpy.array :return: A plane that crosses the specified position with the specified normal. """ dtype = dtype or position.dtype # -d = a * x + b * y + c * z n = vector.normalize(normal) d = -np.sum(n * position) return create(n, -d, dtype) def create_xy(invert=False, distance=0., dtype=None): """Create a plane on the XY plane, starting at the origin with +Z being the up vector. The plane is distance units along the Z axis. -Z if inverted. """ pl = np.array([0., 0., 1., distance]) if invert: pl = invert_normal(pl) return pl def create_xz(invert=False, distance=0., dtype=None): """Create a plane on the XZ plane, starting at the origin with +Y being the up vector. The plane is distance units along the Y axis. -Y if inverted. """ pl = np.array([0., 1., 0., distance]) if invert: pl = invert_normal(pl) return pl def create_yz(invert=False, distance=0., dtype=None): """Create a plane on the YZ plane, starting at the origin with +X being the up vector. The plane is distance units along the X axis. -X if inverted. """ pl = np.array([1., 0., 0., distance]) if invert: pl = invert_normal(pl) return pl def invert_normal(plane): """Flips the normal of the plane. The plane is **not** changed in place. :rtype: numpy.array :return: The plane with the normal inverted. """ # flip the normal, and the distance return -plane def position(plane): """Extracts the position vector from a plane. This will be a vector co-incident with the plane's normal. :param numpy.array plane: The plane. :rtype: numpy.array :return: A valid position that lies on the plane. """ return normal(plane) * distance(plane) def normal(plane): """Extracts the normal vector from a plane. :param numpy.array plane: The plane. :rtype: numpy.array :return: The normal vector of the plane. """ return plane[:3].copy() def distance(plane): """Distance the plane is from the origin along its the normal. Negative value indicates the plane is facing the origin. """ return plane[3] Pyrr-0.10.3/pyrr/quaternion.py000077500000000000000000000341351345610666600163100ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of Quaternions. """ from __future__ import absolute_import, division, print_function import numpy as np from . import vector, vector3, vector4, euler from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the X value within the quaternion x = 0 #: The index of the Y value within the quaternion y = 1 #: The index of the Z value within the quaternion z = 2 #: The index of the W value within the quaternion w = 3 def create(x=0., y=0., z=0., w=1., dtype=None): return np.array([x, y, z, w], dtype=dtype) def create_from_x_rotation(theta, dtype=None): thetaOver2 = theta * 0.5 return np.array( [ np.sin(thetaOver2), 0.0, 0.0, np.cos(thetaOver2) ], dtype=dtype ) def create_from_y_rotation(theta, dtype=None): thetaOver2 = theta * 0.5 return np.array( [ 0.0, np.sin(thetaOver2), 0.0, np.cos(thetaOver2) ], dtype=dtype ) def create_from_z_rotation(theta, dtype=None): thetaOver2 = theta * 0.5 return np.array( [ 0.0, 0.0, np.sin(thetaOver2), np.cos(thetaOver2) ], dtype=dtype ) @parameters_as_numpy_arrays('axis') def create_from_axis_rotation(axis, theta, dtype=None): dtype = dtype or axis.dtype # make sure the vector is normalized if not np.isclose(np.linalg.norm(axis), 1.): axis = vector.normalize(axis) thetaOver2 = theta * 0.5 sinThetaOver2 = np.sin(thetaOver2) return np.array( [ sinThetaOver2 * axis[0], sinThetaOver2 * axis[1], sinThetaOver2 * axis[2], np.cos(thetaOver2) ], dtype=dtype ) @parameters_as_numpy_arrays('axis') def create_from_axis(axis, dtype=None): dtype = dtype or axis.dtype theta = np.linalg.norm(axis) return create_from_axis_rotation(axis, theta, dtype) @parameters_as_numpy_arrays('mat') def create_from_matrix(mat, dtype=None): # http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm # optimised "alternative version" does not produce correct results # see issue #42 dtype = dtype or mat.dtype trace = mat[0][0] + mat[1][1] + mat[2][2] if trace > 0: s = 0.5 / np.sqrt(trace + 1.0) qx = (mat[2][1] - mat[1][2]) * s qy = (mat[0][2] - mat[2][0]) * s qz = (mat[1][0] - mat[0][1]) * s qw = 0.25 / s elif mat[0][0] > mat[1][1] and mat[0][0] > mat[2][2]: s = 2.0 * np.sqrt(1.0 + mat[0][0] - mat[1][1] - mat[2][2]) qx = 0.25 * s qy = (mat[0][1] + mat[1][0]) / s qz = (mat[0][2] + mat[2][0]) / s qw = (mat[2][1] - mat[1][2]) / s elif mat[1][1] > mat[2][2]: s = 2.0 * np.sqrt(1.0 + mat[1][1] - mat[0][0] - mat[2][2]) qx = (mat[0][1] + mat[1][0]) / s qy = 0.25 * s qz = (mat[1][2] + mat[2][1]) / s qw = (mat[0][2] - mat[2][0]) / s else: s = 2.0 * np.sqrt(1.0 + mat[2][2] - mat[0][0] - mat[1][1]) qx = (mat[0][2] + mat[2][0]) / s qy = (mat[1][2] + mat[2][1]) / s qz = 0.25 * s qw = (mat[1][0] - mat[0][1]) / s quat = np.array([qx, qy, qz, qw], dtype=dtype) return quat @parameters_as_numpy_arrays('eulers') def create_from_eulers(eulers, dtype=None): """Creates a quaternion from a set of Euler angles. Eulers are an array of length 3 in the following order: [roll, pitch, yaw] """ dtype = dtype or eulers.dtype roll, pitch, yaw = euler.roll(eulers), euler.pitch(eulers), euler.yaw(eulers) halfRoll = roll * 0.5 sR = np.sin(halfRoll) cR = np.cos(halfRoll) halfPitch = pitch * 0.5 sP = np.sin(halfPitch) cP = np.cos(halfPitch) halfYaw = yaw * 0.5 sY = np.sin(halfYaw) cY = np.cos(halfYaw) return np.array( [ (sR * cP * cY) + (cR * sP * sY), (cR * sP * cY) - (sR * cP * sY), (cR * cP * sY) + (sR * sP * cY), (cR * cP * cY) - (sR * sP * sY), ], dtype=dtype ) @parameters_as_numpy_arrays('eulers') def create_from_inverse_of_eulers(eulers, dtype=None): """Creates a quaternion from the inverse of a set of Euler angles. Eulers are an array of length 3 in the following order: [roll, pitch, yaw] """ dtype = dtype or eulers.dtype roll, pitch, yaw = euler.roll(eulers), euler.pitch(eulers), euler.yaw(eulers) halfRoll = roll * 0.5 sinRoll = np.sin(halfRoll) cosRoll = np.cos(halfRoll) halfPitch = pitch * 0.5 sinPitch = np.sin(halfPitch) cosPitch = np.cos(halfPitch) halfYaw = yaw * 0.5 sinYaw = np.sin(halfYaw) cosYaw = np.cos(halfYaw) return np.array( [ # x = cy * sp * cr + sy * cp * sr (cosYaw * sinPitch * cosRoll) + (sinYaw * cosPitch * sinRoll), # y = -cy * sp * sr + sy * cp * cr (-cosYaw * sinPitch * sinRoll) + (sinYaw * cosPitch * cosRoll), # z = -sy * sp * cr + cy * cp * sr (-sinYaw * sinPitch * cosRoll) + (cosYaw * cosPitch * sinRoll), # w = cy * cp * cr + sy * sp * sr (cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll) ], dtype=dtype ) @all_parameters_as_numpy_arrays def cross(quat1, quat2): """Returns the cross-product of the two quaternions. Quaternions are **not** communicative. Therefore, order is important. This is NOT the same as a vector cross-product. Quaternion cross-product is the equivalent of matrix multiplication. """ q1x, q1y, q1z, q1w = quat1 q2x, q2y, q2z, q2w = quat2 return np.array( [ q1x * q2w + q1y * q2z - q1z * q2y + q1w * q2x, -q1x * q2z + q1y * q2w + q1z * q2x + q1w * q2y, q1x * q2y - q1y * q2x + q1z * q2w + q1w * q2z, -q1x * q2x - q1y * q2y - q1z * q2z + q1w * q2w, ], dtype=quat1.dtype ) def lerp(quat1, quat2, t): """Interpolates between quat1 and quat2 by t. The parameter t is clamped to the range [0, 1] """ quat1 = np.asarray(quat1) quat2 = np.asarray(quat2) t = np.clip(t, 0, 1) return normalize(quat1 * (1 - t) + quat2 * t) def slerp(quat1, quat2, t): """Spherically interpolates between quat1 and quat2 by t. The parameter t is clamped to the range [0, 1] """ quat1 = np.asarray(quat1) quat2 = np.asarray(quat2) t = np.clip(t, 0, 1) dot = vector4.dot(quat1, quat2) if dot < 0.0: dot = -dot quat3 = -quat2 else: quat3 = quat2 if dot < 0.95: angle = np.arccos(dot) res = (quat1 * np.sin(angle * (1 - t)) + quat3 * np.sin(angle * t)) / np.sin(angle) else: res = lerp(quat1, quat2, t) return res def is_zero_length(quat): """Checks if a quaternion is zero length. :param numpy.array quat: The quaternion to check. :rtype: boolean. :return: True if the quaternion is zero length, otherwise False. """ return quat[0] == quat[1] == quat[2] == quat[3] == 0.0 def is_non_zero_length(quat): """Checks if a quaternion is not zero length. This is the opposite to 'is_zero_length'. This is provided for readability. :param numpy.array quat: The quaternion to check. :rtype: boolean :return: False if the quaternion is zero length, otherwise True. .. seealso:: is_zero_length """ return not is_zero_length(quat) def squared_length(quat): """Calculates the squared length of a quaternion. Useful for avoiding the performanc penalty of the square root function. :param numpy.array quat: The quaternion to measure. :rtype: float, numpy.array :return: If a 1d array was passed, it will be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ return vector4.squared_length(quat) def length(quat): """Calculates the length of a quaternion. :param numpy.array quat: The quaternion to measure. :rtype: float, numpy.array :return: If a 1d array was passed, it will be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ return vector4.length(quat) def normalize(quat): """Ensure a quaternion is unit length (length ~= 1.0). The quaternion is **not** changed in place. :param numpy.array quat: The quaternion to normalize. :rtype: numpy.array :return: The normalized quaternion(s). """ return vector4.normalize(quat) def normalise(quat): # TODO: mark as deprecated """Ensure a quaternion is unit length (length ~= 1.0). The quaternion is **not** changed in place. :param numpy.array quat: The quaternion to normalize. :rtype: numpy.array :return: The normalized quaternion(s). """ return vector4.normalize(quat) def rotation_angle(quat): """Calculates the rotation around the quaternion's axis. :param numpy.array quat: The quaternion. :rtype: float. :return: The quaternion's rotation about the its axis in radians. """ # extract the W component thetaOver2 = np.arccos(quat[3]) return thetaOver2 * 2.0 @all_parameters_as_numpy_arrays def rotation_axis(quat): """Calculates the axis of the quaternion's rotation. :param numpy.array quat: The quaternion. :rtype: numpy.array. :return: The quaternion's rotation axis. """ # extract W component sinThetaOver2Sq = 1.0 - (quat[3] ** 2) # check for zero before we sqrt if sinThetaOver2Sq <= 0.0: # identity quaternion or numerical imprecision. # return a valid vector # we'll treat -Z as the default return np.array([0.0, 0.0, -1.0], dtype=quat.dtype) oneOverSinThetaOver2 = 1.0 / np.sqrt(sinThetaOver2Sq) # we use the x,y,z values return np.array( [ quat[0] * oneOverSinThetaOver2, quat[1] * oneOverSinThetaOver2, quat[2] * oneOverSinThetaOver2 ], dtype=quat.dtype ) def dot(quat1, quat2): """Calculate the dot product of quaternions. This is the same as a vector dot product. :param numpy.array quat1: The first quaternion(s). :param numpy.array quat2: The second quaternion(s). :rtype: float, numpy.array :return: If a 1d array was passed, it will be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ return vector4.dot(quat1, quat2) @all_parameters_as_numpy_arrays def conjugate(quat): """Calculates a quaternion with the opposite rotation. :param numpy.array quat: The quaternion. :rtype: numpy.array. :return: A quaternion representing the conjugate. """ # invert x,y,z and leave w as is return np.array( [ -quat[0], -quat[1], -quat[2], quat[3] ], dtype=quat.dtype ) @parameters_as_numpy_arrays('quat') def exp(quat): """Calculate the exponential of the quaternion :param numpy.array quat: The quaternion. :rtype: numpy.array. :return: The exponential of the quaternion """ e = np.exp(quat[3]) vector_norm = np.linalg.norm(quat[:3]) if np.isclose(vector_norm, 0): return np.array( [0, 0, 0, e], dtype = quat.dtype ) s = np.sin(vector_norm) / vector_norm return e * np.array( [ quat[0] * s, quat[1] * s, quat[2] * s, np.cos(vector_norm), ], dtype = quat.dtype ) @parameters_as_numpy_arrays('quat') def power(quat, exponent): """Multiplies the quaternion by the exponent. The quaternion is **not** changed in place. :param numpy.array quat: The quaternion. :param float scalar: The exponent. :rtype: numpy.array. :return: A quaternion representing the original quaternion to the specified power. """ # check for identify quaternion if np.fabs(quat[3]) > 0.9999: # assert for the time being assert False print("rotation axis was identity") return quat alpha = np.arccos(quat[3]) newAlpha = alpha * exponent multi = np.sin(newAlpha) / np.sin(alpha) return np.array( [ quat[0] * multi, quat[1] * multi, quat[2] * multi, np.cos(newAlpha) ], dtype=quat.dtype ) def inverse(quat): """Calculates the inverse quaternion. The inverse of a quaternion is defined as the conjugate of the quaternion divided by the magnitude of the original quaternion. :param numpy.array quat: The quaternion to invert. :rtype: numpy.array. :return: The inverse of the quaternion. """ return conjugate(quat) / length(quat) @all_parameters_as_numpy_arrays def negate(quat): """Calculates the negated quaternion. This is essentially the quaternion * -1.0. :param numpy.array quat: The quaternion. :rtype: numpy.array :return: The negated quaternion. """ return quat * -1.0 def is_identity(quat): return np.allclose(quat, [0.,0.,0.,1.]) @all_parameters_as_numpy_arrays def apply_to_vector(quat, vec): """Rotates a vector by a quaternion. :param numpy.array quat: The quaternion. :param numpy.array vec: The vector. :rtype: numpy.array :return: The vector rotated by the quaternion. :raise ValueError: raised if the vector is an unsupported size """ def apply(quat, vec4): result = cross(quat, cross(vec4, conjugate(quat))) return result if vec.size == 3: # convert to vector4 # ignore w component by setting it to 0. vec = np.array([vec[0], vec[1], vec[2], 0.0], dtype=vec.dtype) vec = apply(quat, vec) vec = vec[:3] return vec elif vec.size == 4: vec = apply(quat, vec) return vec else: raise ValueError("Vector size unsupported") Pyrr-0.10.3/pyrr/ray.py000077500000000000000000000031671345610666600147170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of Rays. A ray begins as a single point and extends infinitely in a direction. The first vector is the origin of the ray. The second vector is the direction of the ray relative to the origin. The following functions will normalize the ray direction to unit length. Some functions may work correctly with directions that are not unit length, but this may vary from function to function. """ from __future__ import absolute_import, division, print_function import numpy as np from . import vector from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the start vector within the ray position = 0 #: The index of the direction vector within the ray direction = 1 @parameters_as_numpy_arrays('start', 'direction') def create(start, direction, dtype=None): dtype = dtype or start.dtype return np.array( [ start, vector.normalize(direction) ], dtype=dtype ) @parameters_as_numpy_arrays('line') def create_from_line(line, dtype=None): """Converts a line or line segment to a ray. """ dtype = dtype or line.dtype # direction = vend - vstart return np.array( [ line[0], vector.normalize(line[1] - line[0]) ], dtype=dtype ) @all_parameters_as_numpy_arrays def invert(r): r2 = r.copy() r2[1] *= -1 return r2 @all_parameters_as_numpy_arrays def position(ray): return ray[0].copy() @all_parameters_as_numpy_arrays def direction(ray): return ray[1].copy() Pyrr-0.10.3/pyrr/rectangle.py000077500000000000000000000137761345610666600160770ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of 2D Rectangles. Rectangles are represented using a numpy.array of shape (2,2,). The first value is a vector of x, y position of the rectangle. The second value is a vector with the width, height of the rectangle. """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays class index: #: The index of the position vector within the rectangle position = 0 #: The index of the size vector within the rectangle size = 1 def create(x=0., y=0., width=1., height=1., dtype=None): """Creates a rectangle from the specified position and sizes. This function will interpret the values literally. A negative width or height will be represented by the returned value. :rtype: numpy.array :return: Returns a rectangle with the specified values. """ return np.array([[x, y], [width, height]], dtype=dtype) def create_zeros(dtype=None): return np.zeros((2,2), dtype=dtype) def create_from_bounds(left, right, bottom, top, dtype=None): """Creates a rectangle from the specified boundaries. This caters for the left and right, and for the top and bottom being swapped. :rtype: numpy.array :return: Returns a rectangle with the specified values. The rectangle will have a positive width and height regardless of the values passed in. """ xmin = min(left, right) xmax = max(left, right) ymin = min(top, bottom) ymax = max(top, bottom) return create( xmin, ymin, xmax - xmin, ymax - ymin, dtype ) @all_parameters_as_numpy_arrays def bounds(rect): """Returns the absolute boundaries of the rectangle. This caters for rectangles with a negative width. :rtype: Tuple of 4 floats :return: The absolute left, right, bottom and top of the rectangle. """ left = rect[0,0] right = rect[0,0] + rect[1,0] top = rect[0,1] bottom = rect[0,1] + rect[1,1] xmin = min(left, right) xmax = max(left, right) ymin = min(top, bottom) ymax = max(top, bottom) return xmin, xmax, ymin, ymax @all_parameters_as_numpy_arrays def position(rect): """Returns the literal position of the rectangle. This is the bottom-left point of the rectangle for rectangles with positive width and height :rtype: numpy.array :return: The position of the rectangle. """ return rect[0].copy() @all_parameters_as_numpy_arrays def size(rect): """Returns the literal size of the rectangle. These values may be negative. :rtype: numpy.array :return: The size of the rectangle. """ return rect[1].copy() def abs_size(rect): """Returns the absolute size of the rectangle. :rtype: numpy.array :return: The absolute size of the rectangle. """ return np.absolute(rect[1]) def x(rect): """Returns the X position of the rectangle. This will be the left for rectangles with positive height values. :rtype: float :return: The X position of the rectangle. This value will be further right than the 'right' if the width is negative. """ return rect[0][0] def y(rect): """Returns the Y position of the rectangle. This will be the bottom for rectangles with positive height values. :rtype: float :return: The Y position of the rectangle. This value will be above the bottom if the height is negative. """ return rect[0][1] def width(rect): """Returns the literal width of the rectangle. :rtype: float :return: The width of the rectangle. This can be a negative value. """ return rect[1][0] def abs_width(rect): """Returns the absolute width of the rectangle. This caters for rectangles with a negative width. :rtype: float :return: The absolute width of the rectangle. """ return abs(width(rect)) def height(rect): """Returns the literal height of the rectangle. :rtype: float :return: The height of the rectangle. This can be a negative value. """ return rect[1][1] def abs_height(rect): """Returns the absolute height of the rectangle. This caters for rectangles with a negative height. :rtype: float :return: The absolute height of the rectangle. """ return abs(height(rect)) def top(rect): """Returns the top most Y value of the rectangle. This caters for rectangles with a negative height. :rtype: float :return: The biggest Y value. """ return max( rect[0][1], rect[0][1] + rect[1][1] ) def bottom(rect): """Returns the bottom most Y value of the rectangle. This caters for rectangles with a negative height. :rtype: float :return: The smallest Y value. """ return min( rect[0][1], rect[0][1] + rect[1][1] ) def left(rect): """Returns the left most X value of the rectangle. This caters for rectangles with a negative width. :rtype: float :return: The smallest X value. """ return min( rect[0][0], rect[0][0] + rect[1][0] ) def right(rect): """Returns the right most X value of the rectangle. This caters for rectangles with a negative width. :rtype: float :return: The biggest X value. """ return max( rect[0][0], rect[0][0] + rect[1][0] ) @parameters_as_numpy_arrays('rect') def scale_by_vector(rect, vec): """Scales a rectangle by a 2D vector. Note that this will also scale the X,Y value of the rectangle, which will cause the rectangle to move, not just increase in size. :param numpy.array rect: the rectangle to scale. Both x,y and width,height will be scaled. :param vec: A 2D vector to scale the rect by. :rtype: numpy.array. """ return rect * vec[:2] def aspect_ratio(rect): width = float(abs_width(rect)) height = float(abs_height(rect)) return width / height Pyrr-0.10.3/pyrr/sphere.py000077500000000000000000000035031345610666600154040ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the creation and manipulation of 3D Spheres. Sphere are represented using a numpy.array of shape (4,). The first three values are the sphere's position. The fourth value is the sphere's radius. """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays @parameters_as_numpy_arrays('center') def create(center=None, radius=1.0, dtype=None): if center is None: center = [0.,0.,0.] return np.array([center[0], center[1], center[2], radius], dtype=dtype) @parameters_as_numpy_arrays('points') def create_from_points(points, dtype=None): """Creates a sphere centred around 0,0,0 that encompasses the furthest point in the provided list. :param numpy.array points: An Nd array of vectors. :rtype: A sphere as a two value tuple. """ dtype = dtype or points.dtype # calculate the lengths of all the points # use squared length to save processing lengths = np.apply_along_axis( np.sum, points.ndim - 1, points**2 ) # find the maximum value maximum = lengths.max() # square root this, this is the radius radius = np.sqrt(maximum) return np.array([0.0, 0.0, 0.0, radius], dtype=dtype) @all_parameters_as_numpy_arrays def position(sphere): """Returns the position of the sphere. :param numpy.array sphere: The sphere to extract the position from. :rtype: numpy.array :return: The centre of the sphere. """ return sphere[:3].copy() @all_parameters_as_numpy_arrays def radius(sphere): """Returns the radius of the sphere. :param numpy.array sphere: The sphere to extract the radius from. :rtype: float :return: The radius of the sphere. """ return sphere[3] Pyrr-0.10.3/pyrr/trig.py000077500000000000000000000050561345610666600150700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provide functions for the trigonometric functions. """ from __future__ import absolute_import, division, print_function import math def aspect_ratio(width, height): return float(width) / float(height) def calculate_fov(zoom, height=1.0): """Calculates the required FOV to set the view frustrum to have a view with the specified height at the specified distance. :param float zoom: The distance to calculate the FOV for. :param float height: The desired view height at the specified distance. The default is 1.0. :rtype: A float representing the FOV to use in degrees. """ # http://www.glprogramming.com/red/chapter03.html rad_theta = 2.0 * math.atan2(height / 2.0, zoom) return math.degrees(rad_theta) def calculate_zoom(fov, height=1.0): """Calculates the zoom (distance) from the camera with the specified FOV and height of image. :param float fov: The FOV to use. :param float height: The height of the image at the desired distance. :rtype: A float representing the zoom (distance) from the camera for the desired height at the specified FOV. :raise ZeroDivisionError: Raised if the fov is 0.0. """ return float(height) / math.tan(fov / 2.0) def calculate_height(fov, zoom): """Performs the opposite of calculate_fov. Used to find the current height at a specific distance. :param float fov: The current FOV. :param float zoom: The distance to calculate the height for. :rtype: A float representing the height at the specified distance for the specified FOV. """ height = zoom * (math.tan(fov / 2.0)) return height def calculate_plane_size(aspect_ratio, fov, distance): """Calculates the width and height of a plane at the specified distance using the FOV of the frustrum and aspect ratio of the viewport. :param float aspect_ratio: The aspect ratio of the viewport. :param float fov: The FOV of the frustrum. :param float distance: The distance from the origin/camera of the plane to calculate. :rtype: A tuple of two floats: width and height: The width and height of the plane. """ # http://www.songho.ca/opengl/gl_transform.html # http://nehe.gamedev.net/article/replacement_for_gluperspective/21002/ # http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/gluperspective&printable=1 tangent = math.radians(fov) height = distance * tangent width = height * aspect_ratio return width * 2.0, height * 2.0 Pyrr-0.10.3/pyrr/utils.py000066400000000000000000000066331345610666600152620ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provides common utility functions. """ import inspect from functools import wraps import numpy as np def all_parameters_as_numpy_arrays(fn): """Converts all of a function's arguments to numpy arrays. Used as a decorator to reduce duplicate code. """ # wraps allows us to pass the docstring back # or the decorator will hide the function from our doc generator @wraps(fn) def wrapper(*args, **kwargs): args = list(args) for i, v in enumerate(args): if v is not None: args[i] = np.asarray(v) for k,v in kwargs.items(): if v is not None: kwargs[k] = np.asarray(v) return fn(*args, **kwargs) return wrapper def parameters_as_numpy_arrays(*args_to_convert): """Converts specific arguments to numpy arrays. Used as a decorator to reduce duplicate code. Arguments are specified by their argument name. For example :: @parameters_as_numpy_arrays('a', 'b', 'optional') def myfunc(a, b, *args, **kwargs): pass myfunc(1, [2,2], optional=[3,3,3]) """ def decorator(fn): # wraps allows us to pass the docstring back # or the decorator will hide the function from our doc generator try: getfullargspec = inspect.getfullargspec except AttributeError: getfullargspec = inspect.getargspec @wraps(fn) def wrapper(*args, **kwargs): # get the arguements of the function we're decorating fn_args = getfullargspec(fn) # convert any values that are specified # if the argument isn't in our list, just pass it through # convert the *args list # we zip the args with the argument names we received from # the inspect function args = list(args) for i, (k, v) in enumerate(zip(fn_args.args, args)): if k in args_to_convert and v is not None: args[i] = np.array(v) # convert the **kwargs dict for k,v in kwargs.items(): if k in args_to_convert and v is not None: kwargs[k] = np.array(v) # pass the converted values to our function return fn(*args, **kwargs) return wrapper return decorator def solve_quadratic_equation(a, b, c): """Quadratic equation solver. Solve function of form f(x) = ax^2 + bx + c :param float a: Quadratic part of equation. :param float b: Linear part of equation. :param float c: Static part of equation. :rtype: list :return: List contains either two elements for two solutions, one element for one solution, or is empty if no solution for the quadratic equation exists. """ delta = b * b - 4 * a * c if delta > 0: # Two solutions # See https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection # Why not use simple form: # s1 = (-b + math.sqrt(delta)) / (2 * a) # s2 = (-b - math.sqrt(delta)) / (2 * a) q = -0.5 * (b + np.math.sqrt(delta)) if b > 0 else -0.5 * (b - np.math.sqrt(delta)) s1 = q / a s2 = c / q return [s1, s2] elif delta == 0: # One solution return [-b / (2 * a)] else: # No solution exists return list() Pyrr-0.10.3/pyrr/vector.py000077500000000000000000000125021345610666600154170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Common Vector manipulation functions. """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import all_parameters_as_numpy_arrays, parameters_as_numpy_arrays @all_parameters_as_numpy_arrays def normalize(vec): """normalizes an Nd list of vectors or a single vector to unit length. The vector is **not** changed in place. For zero-length vectors, the result will be np.nan. :param numpy.array vec: an Nd array with the final dimension being vectors :: numpy.array([ x, y, z ]) Or an NxM array:: numpy.array([ [x1, y1, z1], [x2, y2, z2] ]). :rtype: A numpy.array the normalized value """ # calculate the length # this is a duplicate of length(vec) because we # always want an array, even a 0-d array. return (vec.T / np.sqrt(np.sum(vec**2,axis=-1))).T @all_parameters_as_numpy_arrays def normalise(vec): # TODO: mark as deprecated """normalizes an Nd list of vectors or a single vector to unit length. The vector is **not** changed in place. For zero-length vectors, the result will be np.nan. :param numpy.array vec: an Nd array with the final dimension being vectors :: numpy.array([ x, y, z ]) Or an NxM array:: numpy.array([ [x1, y1, z1], [x2, y2, z2] ]). :rtype: A numpy.array the normalized value """ # calculate the length # this is a duplicate of length(vec) because we # always want an array, even a 0-d array. return (vec.T / np.sqrt(np.sum(vec**2,axis=-1))).T @all_parameters_as_numpy_arrays def squared_length(vec): """Calculates the squared length of a vector. Useful when trying to avoid the performance penalty of a square root operation. :param numpy.array vec: An Nd numpy.array. :rtype: If one vector is supplied, the result with be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ lengths = np.sum(vec ** 2., axis=-1) return lengths @all_parameters_as_numpy_arrays def length(vec): """Returns the length of an Nd list of vectors or a single vector. :param numpy.array vec: an Nd array with the final dimension being size 3 (a vector). Single vector:: numpy.array([ x, y, z ]) Nd array:: numpy.array([ [x1, y1, z1], [x2, y2, z2] ]). :rtype: If a 1d array was passed, it will be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ return np.sqrt(np.sum(vec**2,axis=-1)) @parameters_as_numpy_arrays('vec') def set_length(vec, len): """Resizes an Nd list of vectors or a single vector to 'length'. The vector is **not** changed in place. :param numpy.array vec: an Nd array with the final dimension being size 3 (a vector). Single vector:: numpy.array([ x, y, z ]) Nd array:: numpy.array([ [x1, y1, z1], [x2, y2, z2] ]). :rtype: A numpy.array of shape vec.shape. """ # calculate the length # this is a duplicate of length(vec) because we # always want an array, even a 0-d array. return (vec.T / np.sqrt(np.sum(vec**2,axis=-1)) * len).T @all_parameters_as_numpy_arrays def dot(v1, v2): """Calculates the dot product of two vectors. :param numpy.array v1: an Nd array with the final dimension being size 3. (a vector) :param numpy.array v2: an Nd array with the final dimension being size 3 (a vector) :rtype: If a 1d array was passed, it will be a scalar. Otherwise the result will be an array of scalars with shape vec.ndim with the last dimension being size 1. """ return np.sum(v1 * v2, axis=-1) @parameters_as_numpy_arrays('v1', 'v2') def interpolate(v1, v2, delta): """Interpolates between 2 arrays of vectors (shape = N,3) by the specified delta (0.0 <= delta <= 1.0). :param numpy.array v1: an Nd array with the final dimension being size 3. (a vector) :param numpy.array v2: an Nd array with the final dimension being size 3. (a vector) :param float delta: The interpolation percentage to apply, where 0.0 <= delta <= 1.0. When delta is 0.0, the result will be v1. When delta is 1.0, the result will be v2. Values inbetween will be an interpolation. :rtype: A numpy.array with shape v1.shape. """ # scale the difference based on the time # we must do it this 'unreadable' way to avoid # loss of precision. # the 'readable' method (f_now = f_0 + (f1 - f0) * delta) # causes floating point errors due to the small values used # in md2 files and the values become corrupted. # this horrible code curtousey of this comment: # http://stackoverflow.com/questions/5448322/temporal-interpolation-in-numpy-matplotlib return v1 + ((v2 - v1) * delta) #return v1 * (1.0 - delta ) + v2 * delta t = delta t0 = 0.0 t1 = 1.0 delta_t = t1 - t0 return (t1 - t) / delta_t * v1 + (t - t0) / delta_t * v2 Pyrr-0.10.3/pyrr/vector3.py000066400000000000000000000115131345610666600155000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provides functions for creating and manipulating 3D vectors. """ from __future__ import absolute_import, division, print_function, unicode_literals import numpy as np # import common vector operations from .vector import * def create(x=0., y=0., z=0., dtype=None): if isinstance(x, (list, np.ndarray)): raise ValueError('Function requires non-list arguments') return np.array([x,y,z], dtype=dtype) def create_unit_length_x(dtype=None): return np.array([1.0, 0.0, 0.0], dtype=dtype) def create_unit_length_y(dtype=None): return np.array([0.0, 1.0, 0.0], dtype=dtype) def create_unit_length_z(dtype=None): return np.array([0.0, 0.0, 1.0], dtype=dtype) @parameters_as_numpy_arrays('vector') def create_from_vector4(vector, dtype=None): """Returns a vector3 and the W component as a tuple. """ dtype = dtype or vector.dtype return (np.array([vector[0], vector[1], vector[2]], dtype=dtype), vector[3]) @parameters_as_numpy_arrays('mat') def create_from_matrix44_translation(mat, dtype=None): return np.array(mat[3, :3], dtype=dtype) def cross(v1, v2): """Calculates the cross-product of two vectors. :param numpy.array v1: an Nd array with the final dimension being size 3. (a vector) :param numpy.array v2: an Nd array with the final dimension being size 3. (a vector) :rtype: A np.array with shape v1.shape. """ return np.cross(v1, v2) def generate_normals(v1, v2, v3, normalize_result=True): """Generates a normal vector for 3 vertices. The result is a normalized vector. It is assumed the ordering is counter-clockwise starting at v1, v2 then v3:: v1 v3 \ / v2 The vertices are Nd arrays and may be 1d or Nd. As long as the final axis is of size 3. For 1d arrays:: >>> v1 = numpy.array( [ 1.0, 0.0, 0.0 ] ) >>> v2 = numpy.array( [ 0.0, 0.0, 0.0 ] ) >>> v3 = numpy.array( [ 0.0, 1.0, 0.0 ] ) >>> vector.generate_normals( v1, v2, v3 ) array([ 0., 0., -1.]) For Nd arrays:: >>> v1 = numpy.array( [ [ 1.0, 0.0, 0.0 ], [ 1.0, 0.0, 0.0 ] ] ) >>> v2 = numpy.array( [ [ 0.0, 0.0, 0.0 ], [ 0.0, 0.0, 0.0 ] ] ) >>> v3 = numpy.array( [ [ 0.0, 1.0, 0.0 ], [ 0.0, 1.0, 0.0 ] ] ) >>> vector.generate_normals( v1, v2, v3 ) array([[ 0., 0., -1.], [ 0., 0., -1.]]) :param numpy.array v1: an Nd array with the final dimension being size 3. (a vector) :param numpy.array v2: an Nd array with the final dimension being size 3. (a vector) :param numpy.array v3: an Nd array with the final dimension being size 3. (a vector) :param boolean normalize_result: Specifies if the result should be normalized before being returned. """ # make vectors relative to v2 # we assume opengl counter-clockwise ordering a = v1 - v2 b = v3 - v2 n = cross(b, a) if normalize_result: n = normalize(n) return n def generate_vertex_normals(vertices, index, normalize_result=True): """Generates a normal vector for each vertex. The result is a normalized vector. The index array should list the faces by indexing into the vertices array. It is assumed the ordering in index is counter-clockwise. The vertices and index arrays are Nd arrays and must be 2d, where the final axis is of size 3. An example:: >>> vertices = numpy.array( [ [ 1.0, 0.0, 0.0 ], [ 0.0, 0.0, 0.0 ], [ 0.0, 1.0, 0.0 ] ] ) >>> index = numpy.array( [ [ 0, 2, 1 ] ] ) >>> vector.generate_vertex_normals( vertices, index ) array([[ 0., 0., 1.], [ 0., 0., 1.], [ 0., 0., 1.]]) :param numpy.array vertices: an 2d array with the final dimension being size 3. (a vector) :param numpy.array index: an Nd array with the final dimension being size 3. (a vector) :param boolean normalize_result: Specifies if the result should be normalized before being returned. """ v1, v2, v3 = np.rollaxis(vertices[index], axis=-2) face_normals = generate_normals(v1, v2, v3, normalize_result=False) vertex_normals = np.zeros_like(vertices) for i in range(3): np.add.at(vertex_normals, index[..., i], face_normals) if normalize_result: vertex_normals = normalize(vertex_normals) return vertex_normals class index: #: The index of the X value within the vector x = 0 #: The index of the Y value within the vector y = 1 #: The index of the Z value within the vector z = 2 class unit: #: A vector of unit length in the X-axis. (1.0, 0.0, 0.0) x = create_unit_length_x() #: A vector of unit length in the Y-axis. (0.0, 1.0, 0.0) y = create_unit_length_y() #: A vector of unit length in the Z-axis. (0.0, 0.0, 1.0) z = create_unit_length_z() Pyrr-0.10.3/pyrr/vector4.py000066400000000000000000000033331345610666600155020ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Provides functions for creating and manipulating 4D vectors. """ from __future__ import absolute_import, division, print_function import numpy as np from .utils import parameters_as_numpy_arrays # import common vector operations from .vector import * def create(x=0., y=0., z=0., w=0.0, dtype=None): if isinstance(x, (list, np.ndarray)): raise ValueError('Function requires non-list arguments') return np.array([x,y,z,w], dtype=dtype) def create_unit_length_x(dtype=None): return np.array([1.0, 0.0, 0.0, 0.0], dtype=dtype) def create_unit_length_y(dtype=None): return np.array([0.0, 1.0, 0.0, 0.0], dtype=dtype) def create_unit_length_z(dtype=None): return np.array([0.0, 0.0, 1.0, 0.0], dtype=dtype) def create_unit_length_w(dtype=None): return np.array([0.0, 0.0, 0.0, 1.0], dtype=dtype) @parameters_as_numpy_arrays('vector') def create_from_vector3(vector, w=0., dtype=None): dtype = dtype or vector.dtype return np.array([vector[0], vector[1], vector[2], w], dtype=dtype) @parameters_as_numpy_arrays('mat') def create_from_matrix44_translation(mat, dtype=None): return np.array(mat[3, :4], dtype=dtype) class index: #: The index of the X value within the vector x = 0 #: The index of the Y value within the vector y = 1 #: The index of the Z value within the vector z = 2 #: The index of the W value within the vector w = 3 class unit: #: A vector of unit length in the X-axis. (1.0, 0.0, 0.0, 0.0) x = create_unit_length_x() #: A vector of unit length in the Y-axis. (0.0, 1.0, 0.0, 0.0) y = create_unit_length_y() #: A vector of unit length in the Z-axis. (0.0, 0.0, 1.0, 0.0) z = create_unit_length_z() Pyrr-0.10.3/pyrr/version.py000077500000000000000000000001601345610666600155770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # the version of software # this is used by the setup.py script __version__ = '0.10.3' Pyrr-0.10.3/requirements-dev.txt000066400000000000000000000000371345610666600166040ustar00rootroot00000000000000-r requirements.txt nose twine Pyrr-0.10.3/requirements-docs.txt000066400000000000000000000000371345610666600167560ustar00rootroot00000000000000-r requirements-dev.txt sphinx Pyrr-0.10.3/requirements.txt000066400000000000000000000000271345610666600160270ustar00rootroot00000000000000numpy multipledispatch Pyrr-0.10.3/scripts/000077500000000000000000000000001345610666600142335ustar00rootroot00000000000000Pyrr-0.10.3/scripts/make_documentation000077500000000000000000000001771345610666600200340ustar00rootroot00000000000000#!/bin/bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd $DIR cd ../docs rm -rf build/* make html popd Pyrr-0.10.3/scripts/make_package000077500000000000000000000001741345610666600165530ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd $DIR cd .. python setup.py sdist bdist_wheel popd Pyrr-0.10.3/scripts/run_tests000077500000000000000000000002071345610666600162060ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd $DIR cd .. python -m unittest discover #nosetests --exe popd Pyrr-0.10.3/scripts/upload_package000077500000000000000000000002201345610666600171120ustar00rootroot00000000000000#!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd $DIR cd .. python setup.py sdist bdist_wheel twine upload dist/* popd Pyrr-0.10.3/setup.py000066400000000000000000000023621345610666600142610ustar00rootroot00000000000000#!/usr/bin/env python try: from setuptools import setup except ImportError: from distutils.core import setup # get our version but don't import it # or we'll need our dependencies already installed # https://github.com/todddeluca/happybase/commit/63573cdaefe3a2b98ece87e19d9ceb18f00bc0d9 with open('pyrr/version.py', 'r') as f: exec(f.read()) setup( name='pyrr', version=__version__, description='3D mathematical functions using NumPy', license='BSD', author='Adam Griffiths', url='https://github.com/adamlwgriffiths/Pyrr', install_requires=['numpy', 'multipledispatch'], platforms=['any'], test_suite='tests', packages=['pyrr', 'pyrr.objects'], classifiers=[ 'Natural Language :: English', 'Intended Audience :: Developers', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python', 'License :: OSI Approved :: BSD License', 'Topic :: Multimedia :: Graphics', 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ], ) Pyrr-0.10.3/tests/000077500000000000000000000000001345610666600137065ustar00rootroot00000000000000Pyrr-0.10.3/tests/__init__.py000066400000000000000000000000001345610666600160050ustar00rootroot00000000000000Pyrr-0.10.3/tests/objects/000077500000000000000000000000001345610666600153375ustar00rootroot00000000000000Pyrr-0.10.3/tests/objects/__init__.py000066400000000000000000000000001345610666600174360ustar00rootroot00000000000000Pyrr-0.10.3/tests/objects/test_equivalence.py000066400000000000000000000045331345610666600212560ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import Quaternion, Matrix44, Matrix33, Vector3, Vector4, euler class test_matrix_quaternion(unittest.TestCase): def test_m44_q_equivalence(self): """Test for equivalance of matrix and quaternion rotations. Create a matrix and quaternion, rotate each by the same values then convert matrix<->quaternion and check the results are the same. """ m = Matrix44.from_x_rotation(np.pi / 2.) mq = Quaternion.from_matrix(m) q = Quaternion.from_x_rotation(np.pi / 2.) qm = Matrix44.from_quaternion(q) self.assertTrue(np.allclose(np.dot([1., 0., 0., 1.], m), [1., 0., 0., 1.])) self.assertTrue(np.allclose(np.dot([1., 0., 0., 1.], qm), [1., 0., 0., 1.])) self.assertTrue(np.allclose(q * Vector4([1., 0., 0., 1.]), [1., 0., 0., 1.])) self.assertTrue(np.allclose(mq * Vector4([1., 0., 0., 1.]), [1., 0., 0., 1.])) np.testing.assert_almost_equal(np.array(q), np.array(mq), decimal=5) np.testing.assert_almost_equal(np.array(m), np.array(qm), decimal=5) def test_euler_equivalence(self): eulers = euler.create_from_x_rotation(np.pi / 2.) m = Matrix33.from_x_rotation(np.pi / 2.) q = Quaternion.from_x_rotation(np.pi / 2.) qm = Matrix33.from_quaternion(q) em = Matrix33.from_eulers(eulers) self.assertTrue(np.allclose(qm, m)) self.assertTrue(np.allclose(qm, em)) self.assertTrue(np.allclose(m, em)) def test_quaternion_matrix_conversion(self): # https://au.mathworks.com/help/robotics/ref/quat2rotm.html?requestedDomain=www.mathworks.com q = Quaternion([0.7071, 0., 0., 0.7071]) m33 = q.matrix33 expected = np.array([ [1., 0., 0.], [0.,-0.,-1.], [0., 1.,-0.], ]) self.assertTrue(np.allclose(m33, expected)) # issue #42 q = Quaternion([0.80087974, 0.03166748, 0.59114721,-0.09018753]) m33 = q.matrix33 q2 = Quaternion.from_matrix(m33) print(q, q2) self.assertTrue(np.allclose(q, q2)) q3 = Quaternion.from_matrix(m33.T) self.assertFalse(np.allclose(q2, q3)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/objects/test_examples.py000066400000000000000000000051711345610666600205720ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest class test_oo_examples(unittest.TestCase): def test_oo_examples(self): from pyrr import Quaternion, Matrix44, Vector3 import numpy as np point = Vector3([1.,2.,3.]) orientation = Quaternion() translation = Vector3() scale = Vector3([1.,1.,1.]) # translate along X by 1 translation += [1.0, 0.0, 0.0] # rotate about Y by pi/2 rotation = Quaternion.from_y_rotation(np.pi / 2.0) orientation = rotation * orientation # create a matrix # start our matrix off using the scale matrix = Matrix44.from_scale(scale) # apply our orientation # we can multiply matricies and quaternions directly! matrix = matrix * orientation # apply our translation translation = Matrix44.from_translation(translation) matrix = matrix * translation # transform our point by the matrix # vectors are transformable by matrices and quaternions directly point = matrix * point def test_conversions(self): from pyrr import Quaternion, Matrix33, Matrix44, Vector3, Vector4 v3 = Vector3([1.,0.,0.]) v4 = Vector4.from_vector3(v3, w=1.0) v3, w = Vector3.from_vector4(v4) m44 = Matrix44() q = Quaternion(m44) m33 = Matrix33(q) m33 = Matrix44().matrix33 m44 = Matrix33().matrix44 q = Matrix44().quaternion q = Matrix33().quaternion m33 = Quaternion().matrix33 m44 = Quaternion().matrix44 def test_operators(self): from pyrr import Quaternion, Matrix44, Matrix33, Vector3, Vector4 import numpy as np # matrix multiplication m = Matrix44() * Matrix33() m = Matrix44() * Quaternion() m = Matrix33() * Quaternion() # matrix inverse m = ~Matrix44.from_x_rotation(np.pi) # quaternion multiplication q = Quaternion() * Quaternion() q = Quaternion() * Matrix44() q = Quaternion() * Matrix33() # quaternion inverse (conjugate) q = ~Quaternion() # quaternion dot product d = Quaternion() | Quaternion() # vector oprations v = Vector3() + Vector3() v = Vector4() - Vector4() # vector transform v = Quaternion() * Vector3() v = Matrix44() * Vector3() v = Matrix44() * Vector4() v = Matrix33() * Vector3() # dot and cross products dot = Vector3() | Vector3() cross = Vector3() ^ Vector3() Pyrr-0.10.3/tests/objects/test_matrix33.py000066400000000000000000000236371345610666600204350ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest import numpy as np from pyrr.objects.matrix33 import Matrix33 from pyrr.objects.matrix44 import Matrix44 from pyrr.objects.quaternion import Quaternion from pyrr.objects.vector3 import Vector3 from pyrr.objects.vector4 import Vector4 from pyrr import matrix33 from pyrr import matrix44 from pyrr import quaternion class test_object_matrix33(unittest.TestCase): _shape = (3,3) _size = np.multiply.reduce(_shape) def test_imports(self): import pyrr pyrr.Matrix33() pyrr.matrix33.Matrix33() pyrr.objects.matrix33.Matrix33() from pyrr import Matrix33 from pyrr.objects import Matrix33 from pyrr.objects.matrix33 import Matrix33 def test_create(self): m = Matrix33() self.assertTrue(np.array_equal(m, np.zeros(self._shape))) self.assertEqual(m.shape, self._shape) m = Matrix33(np.arange(self._size)) self.assertEqual(m.shape, self._shape) m = Matrix33([[1,2,3],[4,5,6],[7,8,9]]) self.assertEqual(m.shape, self._shape) m = Matrix33(Matrix33()) self.assertTrue(np.array_equal(m, np.zeros(self._shape))) self.assertEqual(m.shape, self._shape) def test_identity(self): m = Matrix33.identity() self.assertTrue(np.array_equal(m, np.eye(3))) @unittest.skip('Not implemented') def test_perspective_projection(self): pass @unittest.skip('Not implemented') def test_perspective_projection_bounds(self): pass @unittest.skip('Not implemented') def test_orthogonal_projection(self): pass @unittest.skip('Not implemented') def test_from_translation(self): pass def test_create_from_matrix44(self): m1 = Matrix44.identity() m = Matrix33.from_matrix44(m1) self.assertTrue(np.array_equal(m, np.eye(3))) m = Matrix33(m1) self.assertTrue(np.array_equal(m, np.eye(3))) def test_create_from_scale(self): v = Vector3([1,2,3]) m = Matrix33.from_scale(v) self.assertTrue(np.array_equal(m, np.diag([1,2,3]))) def test_create_from_eulers(self): e = Vector3([1,2,3]) m = Matrix33.from_eulers(e) self.assertTrue(np.array_equal(m, matrix33.create_from_eulers([1,2,3]))) def test_create_from_quaternion(self): q = Quaternion() m = Matrix33.from_quaternion(q) self.assertTrue(np.array_equal(m, np.eye(3))) self.assertTrue(np.array_equal(m.quaternion, q)) m = Matrix33(q) self.assertTrue(np.array_equal(m, np.eye(3))) def test_create_from_inverse_quaternion(self): q = Quaternion.from_x_rotation(0.5) m = Matrix33.from_inverse_of_quaternion(q) expected = matrix33.create_from_quaternion(quaternion.inverse(quaternion.create_from_x_rotation(0.5))) np.testing.assert_almost_equal(np.array(m), expected, decimal=5) #self.assertTrue(np.array_equal(m, expected)) def test_multiply(self): m1 = Matrix33(np.arange(self._size)) m2 = Matrix33(np.arange(self._size)[::-1]) m = m1 * m2 self.assertTrue(np.array_equal(m, matrix33.multiply(m2, m1))) m1 = Matrix33(np.arange(self._size)) m2 = Matrix44(np.arange(16)) m = m1 * m2 self.assertTrue(np.array_equal(m, matrix33.multiply(matrix33.create_from_matrix44(m2), m1))) def test_inverse(self): m1 = Matrix33.identity() * Matrix33.from_x_rotation(0.5) m = m1.inverse self.assertTrue(np.array_equal(m, matrix33.inverse(m1))) def test_matrix33(self): m1 = Matrix33.identity() * Matrix33.from_x_rotation(0.5) m = m1.matrix33 self.assertTrue(m1 is m) def test_matrix44(self): m1 = Matrix33.identity() * Matrix33.from_x_rotation(0.5) m = m1.matrix44 self.assertTrue(np.array_equal(m, matrix44.create_from_matrix33(m1))) def test_operators_matrix33(self): m1 = Matrix33.identity() m2 = Matrix33.from_x_rotation(0.5) # add self.assertTrue(np.array_equal(m1 + m2, matrix33.create_identity() + matrix33.create_from_x_rotation(0.5))) # subtract self.assertTrue(np.array_equal(m1 - m2, matrix33.create_identity() - matrix33.create_from_x_rotation(0.5))) # multiply self.assertTrue(np.array_equal(m1 * m2, matrix33.multiply(matrix33.create_from_x_rotation(0.5), matrix33.create_identity()))) # divide self.assertRaises(ValueError, lambda: m1 / m2) # inverse self.assertTrue(np.array_equal(~m2, matrix33.inverse(matrix33.create_from_x_rotation(0.5)))) # == self.assertTrue(Matrix33() == Matrix33()) self.assertFalse(Matrix33() == Matrix33([1. for n in range(9)])) # != self.assertTrue(Matrix33() != Matrix33([1. for n in range(9)])) self.assertFalse(Matrix33() != Matrix33()) def test_operators_matrix44(self): m1 = Matrix33.identity() m2 = Matrix44.from_x_rotation(0.5) # add self.assertTrue(np.array_equal(m1 + m2, matrix33.create_identity() + matrix33.create_from_x_rotation(0.5))) # subtract self.assertTrue(np.array_equal(m1 - m2, matrix33.create_identity() - matrix33.create_from_x_rotation(0.5))) # multiply self.assertTrue(np.array_equal(m1 * m2, matrix33.multiply(matrix33.create_from_x_rotation(0.5), matrix33.create_identity()))) # divide self.assertRaises(ValueError, lambda: m1 / m2) def test_operators_quaternion(self): m = Matrix33.identity() q = Quaternion.from_x_rotation(0.7) # add self.assertRaises(ValueError, lambda: m + q) # subtract self.assertRaises(ValueError, lambda: m - q) # multiply self.assertTrue(np.array_equal(m * q, matrix33.multiply(matrix33.create_from_quaternion(quaternion.create_from_x_rotation(0.7)), matrix33.create_identity()))) # divide self.assertRaises(ValueError, lambda: m / q) def test_operators_vector3(self): m = Matrix33.identity() v = Vector3([1,1,1]) # add self.assertRaises(ValueError, lambda: m + v) # subtract self.assertRaises(ValueError, lambda: m - v) # multiply self.assertTrue(np.array_equal(m * v, matrix33.apply_to_vector(matrix33.create_identity(), [1,1,1]))) # divide self.assertRaises(ValueError, lambda: m / v) def test_operators_vector4(self): m = Matrix33.identity() v = Vector4([1,1,1,1]) # add self.assertRaises(ValueError, lambda: m + v) # subtract self.assertRaises(ValueError, lambda: m - v) # multiply self.assertTrue(ValueError, lambda: m * v) # divide self.assertRaises(ValueError, lambda: m / v) def test_operators_number(self): m = Matrix33.identity() fv = np.empty((1,), dtype=[('i', np.int16, 1),('f', np.float32, 1)]) fv[0] = (2, 2.0) # add self.assertTrue(np.array_equal(m + 1.0, matrix33.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + 1, matrix33.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + np.float(1.), matrix33.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + fv[0]['f'], matrix33.create_identity()[:] + 2.0)) self.assertTrue(np.array_equal(m + fv[0]['i'], matrix33.create_identity()[:] + 2.0)) # subtract self.assertTrue(np.array_equal(m - 1.0, matrix33.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - 1, matrix33.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - np.float(1.), matrix33.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - fv[0]['f'], matrix33.create_identity()[:] - 2.0)) self.assertTrue(np.array_equal(m - fv[0]['i'], matrix33.create_identity()[:] - 2.0)) # multiply self.assertTrue(np.array_equal(m * 2.0, matrix33.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * 2, matrix33.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * np.float(2.), matrix33.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * fv[0]['f'], matrix33.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * fv[0]['i'], matrix33.create_identity()[:] * 2.0)) # divide self.assertTrue(np.array_equal(m / 2.0, matrix33.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / 2, matrix33.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / np.float(2.), matrix33.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / fv[0]['f'], matrix33.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / fv[0]['i'], matrix33.create_identity()[:] / 2.0)) def test_accessors(self): m = Matrix33(np.arange(self._size)) self.assertTrue(np.array_equal(m.m1,[0,1,2])) self.assertTrue(np.array_equal(m.m2,[3,4,5])) self.assertTrue(np.array_equal(m.m3,[6,7,8])) self.assertTrue(np.array_equal(m.r1,[0,1,2])) self.assertTrue(np.array_equal(m.r2,[3,4,5])) self.assertTrue(np.array_equal(m.r3,[6,7,8])) self.assertTrue(np.array_equal(m.c1,[0,3,6])) self.assertTrue(np.array_equal(m.c2,[1,4,7])) self.assertTrue(np.array_equal(m.c3,[2,5,8])) self.assertEqual(m.m11, 0) self.assertEqual(m.m12, 1) self.assertEqual(m.m13, 2) self.assertEqual(m.m21, 3) self.assertEqual(m.m22, 4) self.assertEqual(m.m23, 5) self.assertEqual(m.m31, 6) self.assertEqual(m.m32, 7) self.assertEqual(m.m33, 8) m.m11 = 1 self.assertEqual(m.m11, 1) self.assertEqual(m[0,0], 1) m.m11 += 1 self.assertEqual(m.m11, 2) self.assertEqual(m[0,0], 2) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/objects/test_matrix44.py000066400000000000000000000311071345610666600204260ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest import numpy as np from pyrr.objects.matrix33 import Matrix33 from pyrr.objects.matrix44 import Matrix44 from pyrr.objects.quaternion import Quaternion from pyrr.objects.vector3 import Vector3 from pyrr.objects.vector4 import Vector4 from pyrr import matrix33 from pyrr import matrix44 from pyrr import quaternion class test_object_matrix44(unittest.TestCase): _shape = (4,4) _size = np.multiply.reduce(_shape) def test_imports(self): import pyrr pyrr.Matrix44() pyrr.matrix44.Matrix44() pyrr.objects.matrix44.Matrix44() from pyrr import Matrix44 from pyrr.objects import Matrix44 from pyrr.objects.matrix44 import Matrix44 def test_create(self): m = Matrix44() self.assertTrue(np.array_equal(m, np.zeros(self._shape))) self.assertEqual(m.shape, self._shape) m = Matrix44(np.arange(self._size)) self.assertEqual(m.shape, self._shape) m = Matrix44([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]) self.assertEqual(m.shape, self._shape) m = Matrix44(Matrix44()) self.assertTrue(np.array_equal(m, np.zeros(self._shape))) self.assertEqual(m.shape, self._shape) def test_identity(self): m = Matrix44.identity() self.assertTrue(np.array_equal(m, np.eye(4))) @unittest.skip('Not implemented') def test_perspective_projection(self): pass @unittest.skip('Not implemented') def test_perspective_projection_bounds(self): pass @unittest.skip('Not implemented') def test_orthogonal_projection(self): pass @unittest.skip('Not implemented') def test_from_translation(self): pass def test_create_from_matrix44(self): m1 = Matrix33.identity() m = Matrix44.from_matrix33(m1) self.assertTrue(np.array_equal(m, np.eye(4))) m = Matrix44(m1) self.assertTrue(np.array_equal(m, np.eye(4))) def test_create_from_scale(self): v = Vector3([1,2,3]) m = Matrix44.from_scale(v) self.assertTrue(np.array_equal(m, np.diag([1,2,3,1]))) def test_create_from_eulers(self): e = Vector3([1,2,3]) m = Matrix44.from_eulers(e) self.assertTrue(np.array_equal(m, matrix44.create_from_eulers([1,2,3]))) def test_create_from_quaternion(self): q = Quaternion() m = Matrix44.from_quaternion(q) self.assertTrue(np.array_equal(m, np.eye(4))) self.assertTrue(np.array_equal(m.quaternion, q)) m = Matrix44(q) self.assertTrue(np.array_equal(m, np.eye(4))) def test_create_from_inverse_quaternion(self): q = Quaternion.from_x_rotation(0.5) m = Matrix44.from_inverse_of_quaternion(q) expected = matrix44.create_from_quaternion(quaternion.inverse(quaternion.create_from_x_rotation(0.5))) np.testing.assert_almost_equal(np.array(m), expected, decimal=5) #self.assertTrue(np.array_equal(m, expected)) def test_multiply(self): m1 = Matrix44(np.arange(self._size)) m2 = Matrix44(np.arange(self._size)[::-1]) m = m1 * m2 self.assertTrue(np.array_equal(m, matrix44.multiply(m2, m1))) m1 = Matrix44(np.arange(self._size)) m2 = Matrix33(np.arange(9)) m = m1 * m2 self.assertTrue(np.array_equal(m, matrix44.multiply(matrix44.create_from_matrix33(m2), m1))) def test_inverse(self): m1 = Matrix44.identity() * Matrix44.from_x_rotation(0.5) m = m1.inverse self.assertTrue(np.array_equal(m, matrix44.inverse(m1))) def test_matrix33(self): m1 = Matrix44.identity() * Matrix44.from_x_rotation(0.5) m = m1.matrix33 self.assertTrue(np.array_equal(m, matrix33.create_from_matrix44(m1))) def test_matrix44(self): m1 = Matrix44.identity() * Matrix44.from_x_rotation(0.5) m = m1.matrix44 self.assertTrue(m1 is m) def test_operators_matrix33(self): m1 = Matrix44.identity() m2 = Matrix33.from_x_rotation(0.5) # add self.assertTrue(np.array_equal(m1 + m2, matrix44.create_identity() + matrix44.create_from_x_rotation(0.5))) # subtract self.assertTrue(np.array_equal(m1 - m2, matrix44.create_identity() - matrix44.create_from_x_rotation(0.5))) # multiply self.assertTrue(np.array_equal(m1 * m2, matrix44.multiply(matrix44.create_from_x_rotation(0.5), matrix44.create_identity()))) # divide self.assertRaises(ValueError, lambda: m1 / m2) def test_operators_matrix44(self): m1 = Matrix44.identity() m2 = Matrix44.from_x_rotation(0.5) # add self.assertTrue(np.array_equal(m1 + m2, matrix44.create_identity() + matrix44.create_from_x_rotation(0.5))) # subtract self.assertTrue(np.array_equal(m1 - m2, matrix44.create_identity() - matrix44.create_from_x_rotation(0.5))) # multiply self.assertTrue(np.array_equal(m1 * m2, matrix44.multiply(matrix44.create_from_x_rotation(0.5), matrix44.create_identity()))) # divide self.assertRaises(ValueError, lambda: m1 / m2) # inverse self.assertTrue(np.array_equal(~m2, matrix44.inverse(matrix44.create_from_x_rotation(0.5)))) # == self.assertTrue(Matrix44() == Matrix44()) self.assertFalse(Matrix44() == Matrix44([1. for n in range(16)])) # != self.assertTrue(Matrix44() != Matrix44([1. for n in range(16)])) self.assertFalse(Matrix44() != Matrix44()) def test_operators_quaternion(self): m = Matrix44.identity() q = Quaternion.from_x_rotation(0.7) # add self.assertRaises(ValueError, lambda: m + q) # subtract self.assertRaises(ValueError, lambda: m - q) # multiply self.assertTrue(np.array_equal(m * q, matrix44.multiply(matrix44.create_from_quaternion(quaternion.create_from_x_rotation(0.7)), matrix44.create_identity()))) # divide self.assertRaises(ValueError, lambda: m / q) def test_operators_vector3(self): m = Matrix44.identity() v = Vector3([1,1,1]) # add self.assertRaises(ValueError, lambda: m + v) # subtract self.assertRaises(ValueError, lambda: m - v) # multiply self.assertTrue(np.array_equal(m * v, matrix44.apply_to_vector(matrix44.create_identity(), [1,1,1]))) # divide self.assertRaises(ValueError, lambda: m / v) def test_operators_vector4(self): m = Matrix44.identity() v = Vector4([1,1,1,1]) # add self.assertRaises(ValueError, lambda: m + v) # subtract self.assertRaises(ValueError, lambda: m - v) # multiply self.assertTrue(np.array_equal(m * v, matrix44.apply_to_vector(matrix44.create_identity(), [1,1,1,1]))) # divide self.assertRaises(ValueError, lambda: m / v) def test_operators_number(self): m = Matrix44.identity() fv = np.empty((1,), dtype=[('i', np.int16, 1),('f', np.float32, 1)]) fv[0] = (2, 2.0) # add self.assertTrue(np.array_equal(m + 1.0, matrix44.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + 1, matrix44.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + np.float(1.), matrix44.create_identity()[:] + 1.0)) self.assertTrue(np.array_equal(m + fv[0]['f'], matrix44.create_identity()[:] + 2.0)) self.assertTrue(np.array_equal(m + fv[0]['i'], matrix44.create_identity()[:] + 2.0)) # subtract self.assertTrue(np.array_equal(m - 1.0, matrix44.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - 1, matrix44.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - np.float(1.), matrix44.create_identity()[:] - 1.0)) self.assertTrue(np.array_equal(m - fv[0]['f'], matrix44.create_identity()[:] - 2.0)) self.assertTrue(np.array_equal(m - fv[0]['i'], matrix44.create_identity()[:] - 2.0)) # multiply self.assertTrue(np.array_equal(m * 2.0, matrix44.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * 2, matrix44.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * np.float(2.), matrix44.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * fv[0]['f'], matrix44.create_identity()[:] * 2.0)) self.assertTrue(np.array_equal(m * fv[0]['i'], matrix44.create_identity()[:] * 2.0)) # divide self.assertTrue(np.array_equal(m / 2.0, matrix44.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / 2, matrix44.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / np.float(2.), matrix44.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / fv[0]['f'], matrix44.create_identity()[:] / 2.0)) self.assertTrue(np.array_equal(m / fv[0]['i'], matrix44.create_identity()[:] / 2.0)) def test_accessors(self): m = Matrix44(np.arange(self._size)) self.assertTrue(np.array_equal(m.m1,[0,1,2,3])) self.assertTrue(np.array_equal(m.m2,[4,5,6,7])) self.assertTrue(np.array_equal(m.m3,[8,9,10,11])) self.assertTrue(np.array_equal(m.m4,[12,13,14,15])) self.assertTrue(np.array_equal(m.r1,[0,1,2,3])) self.assertTrue(np.array_equal(m.r2,[4,5,6,7])) self.assertTrue(np.array_equal(m.r3,[8,9,10,11])) self.assertTrue(np.array_equal(m.r4,[12,13,14,15])) self.assertTrue(np.array_equal(m.c1,[0,4,8,12])) self.assertTrue(np.array_equal(m.c2,[1,5,9,13])) self.assertTrue(np.array_equal(m.c3,[2,6,10,14])) self.assertTrue(np.array_equal(m.c4,[3,7,11,15])) self.assertEqual(m.m11, 0) self.assertEqual(m.m12, 1) self.assertEqual(m.m13, 2) self.assertEqual(m.m14, 3) self.assertEqual(m.m21, 4) self.assertEqual(m.m22, 5) self.assertEqual(m.m23, 6) self.assertEqual(m.m24, 7) self.assertEqual(m.m31, 8) self.assertEqual(m.m32, 9) self.assertEqual(m.m33, 10) self.assertEqual(m.m34, 11) self.assertEqual(m.m41, 12) self.assertEqual(m.m42, 13) self.assertEqual(m.m43, 14) self.assertEqual(m.m44, 15) m.m11 = 1 self.assertEqual(m.m11, 1) self.assertEqual(m[0,0], 1) m.m11 += 1 self.assertEqual(m.m11, 2) self.assertEqual(m[0,0], 2) def test_decompose(self): # define expectations for multiple cases testsets = [ ( Vector3([1, 1, 2], dtype='f4'), Quaternion.from_y_rotation(np.pi, dtype='f4'), Vector3([10, 0, -5], dtype='f4'), Matrix44([ [-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -2, 0], [10, 0, -5, 1], ], dtype='f4') ), ( Vector3([-1, 3, .5], dtype='f4'), Quaternion.from_axis_rotation(Vector3([.75, .75, 0], dtype='f4').normalized, np.pi, dtype='f4').normalized, Vector3([1, -1, 1], dtype='f4'), Matrix44([ [0, -1, 0, 0], [3, 0, 0, 0], [0, 0, -.5, 0], [1, -1, 1, 1], ], dtype='f4') ), ] for expected_scale, expected_rotation, expected_translation, expected_model in testsets: # compose model matrix using original inputs s = Matrix44.from_scale(expected_scale, dtype='f4') r = Matrix44.from_quaternion(expected_rotation, dtype='f4') t = Matrix44.from_translation(expected_translation, dtype='f4') m = t * r * s # check that it's the same as the expected matrix np.testing.assert_almost_equal(np.array(m), np.array(expected_model)) self.assertTrue(m.dtype == expected_model.dtype) self.assertTrue(isinstance(m, expected_model.__class__)) # decompose this matrix and recompose the model matrix from the decomposition ds, dr, dt = m.decompose() ds = Matrix44.from_scale(ds, dtype='f4') dr = Matrix44.from_quaternion(dr, dtype='f4') dt = Matrix44.from_translation(dt, dtype='f4') dm = dt * dr * ds # check that it's the same as the original matrix np.testing.assert_almost_equal(np.array(m), np.array(dm)) self.assertTrue(m.dtype == dm.dtype) self.assertTrue(isinstance(dm, m.__class__)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/objects/test_quaternion.py000066400000000000000000000335321345610666600211430ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest import numpy as np from pyrr.objects.matrix33 import Matrix33 from pyrr.objects.matrix44 import Matrix44 from pyrr.objects.quaternion import Quaternion from pyrr.objects.vector3 import Vector3 from pyrr.objects.vector4 import Vector4 from pyrr import matrix33 from pyrr import matrix44 from pyrr import quaternion class test_object_quaternion(unittest.TestCase): _shape = (4,) _size = np.multiply.reduce(_shape) def test_imports(self): import pyrr pyrr.Quaternion() pyrr.quaternion.Quaternion() pyrr.objects.quaternion.Quaternion() from pyrr import Quaternion from pyrr.objects import Quaternion from pyrr.objects.quaternion import Quaternion def test_create(self): q = Quaternion() self.assertTrue(np.array_equal(q, [0., 0., 0., 1.])) self.assertEqual(q.shape, self._shape) q = Quaternion([1., 2., 3., 4.]) self.assertTrue(np.array_equal(q, [1., 2., 3., 4.])) self.assertEqual(q.shape, self._shape) q = Quaternion(Quaternion([1., 2., 3., 4.])) self.assertTrue(np.array_equal(q, [1., 2., 3., 4.])) self.assertEqual(q.shape, self._shape) def test_from_x_rotation(self): # 180 degree turn around X axis q = Quaternion.from_x_rotation(np.pi) self.assertTrue(np.allclose(q, [1., 0., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 0.,-1.])) # 90 degree rotation around X axis q = Quaternion.from_x_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [np.sqrt(0.5), 0., 0., np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 0., 1.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0.,-1., 0.])) # -90 degree rotation around X axis q = Quaternion.from_x_rotation(-np.pi / 2.) self.assertTrue(np.allclose(q, [-np.sqrt(0.5), 0., 0., np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 0.,-1.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 1., 0.])) def test_from_y_rotation(self): # 180 degree turn around Y axis q = Quaternion.from_y_rotation(np.pi) self.assertTrue(np.allclose(q, [0., 1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 0.,-1.])) # 90 degree rotation around Y axis q = Quaternion.from_y_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [0., np.sqrt(0.5), 0., np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [0., 0.,-1.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [1., 0., 0.])) # -90 degree rotation around Y axis q = Quaternion.from_y_rotation(-np.pi / 2.) self.assertTrue(np.allclose(q, [0., -np.sqrt(0.5), 0., np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [0., 0., 1.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [-1., 0., 0.])) def test_from_z_rotation(self): # 180 degree turn around Z axis q = Quaternion.from_z_rotation(np.pi) self.assertTrue(np.allclose(q, [0., 0., 1., 0.])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 0., 1.])) # 90 degree rotation around Z axis q = Quaternion.from_z_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [0., 0., np.sqrt(0.5), np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 0., 1.])) # -90 degree rotation around Z axis q = Quaternion.from_z_rotation(-np.pi / 2.) self.assertTrue(np.allclose(q, [0., 0., -np.sqrt(0.5), np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0., 0., 1.])) def test_from_axis_rotation(self): q = Quaternion.from_axis_rotation([1., 0., 0.], np.pi / 2.) self.assertTrue(np.allclose(q, [np.sqrt(0.5), 0., 0., np.sqrt(0.5)])) self.assertTrue(np.allclose(q * Vector3([1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(q * Vector3([0., 1., 0.]), [0., 0., 1.])) self.assertTrue(np.allclose(q * Vector3([0., 0., 1.]), [0.,-1., 0.])) def test_from_axis(self): source = np.array([np.pi / 2, 0, 0]) result = Quaternion.from_axis(source) expected = np.array([np.sqrt(0.5), 0, 0, np.sqrt(0.5)]) self.assertTrue(np.allclose(result, expected)) source = np.array([0, np.pi, 0]) result = Quaternion.from_axis(source) expected = np.array([0, 1, 0, 0]) self.assertTrue(np.allclose(result, expected)) source = np.array([0, 0, 2 * np.pi]) result = Quaternion.from_axis(source) expected = np.array([0, 0, 0, -1]) self.assertTrue(np.allclose(result, expected)) @unittest.skip('Not implemented') def test_from_eulers(self): pass @unittest.skip('Not implemented') def test_from_inverse_of_eulers(self): pass def test_length(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.length, quaternion.length(q))) def test_normalize(self): q = Quaternion([1., 2., 3., 4.]) self.assertFalse(np.allclose(q.length, 1.)) q.normalize() self.assertTrue(np.allclose(q.length, 1.)) def test_normalized(self): q1 = Quaternion([1., 2., 3., 4.]) self.assertFalse(np.allclose(q1.length, 1.)) q2 = q1.normalized self.assertFalse(np.allclose(q1.length, 1.)) self.assertTrue(np.allclose(q2.length, 1.)) def test_angle(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertEqual(q.angle, quaternion.rotation_angle(q)) def test_axis(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.axis, quaternion.rotation_axis(q))) def test_cross(self): q1 = Quaternion.from_x_rotation(np.pi / 2.0) q2 = Quaternion.from_y_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q1.cross(q2), quaternion.cross(q1, q2))) def test_dot(self): q1 = Quaternion.from_x_rotation(np.pi / 2.0) q2 = Quaternion.from_y_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q1.dot(q2), quaternion.dot(q1, q2))) def test_conjugate(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.conjugate, quaternion.conjugate(q))) def test_inverse(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.inverse, quaternion.inverse(q))) def test_exp(self): source = Quaternion.from_eulers([0, np.pi / 2, 0]) result = source.exp() expected = np.array([0, 1.31753841, 0, 1.54186346]) self.assertTrue(np.allclose(result, expected)) def test_power(self): q1 = Quaternion.from_x_rotation(np.pi / 2.0) q2 = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q1.power(2.0), quaternion.power(q2, 2.0))) def test_negative(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.negative, quaternion.negate(q))) def test_is_identity(self): self.assertTrue(quaternion.is_identity(Quaternion())) self.assertTrue(quaternion.is_identity(Quaternion([0., 0., 0., 1.]))) self.assertFalse(quaternion.is_identity(Quaternion([1., 0., 0., 0.]))) def test_matrix33(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.matrix33, matrix33.create_from_quaternion(q))) def test_matrix44(self): q = Quaternion.from_x_rotation(np.pi / 2.0) self.assertTrue(np.allclose(q.matrix44, matrix44.create_from_quaternion(q))) def test_operators_matrix33(self): q = Quaternion() m = Matrix33.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: q + m) # subtract self.assertRaises(ValueError, lambda: q - m) # multiply self.assertTrue(np.array_equal(q * m, quaternion.cross(quaternion.create(), quaternion.create_from_matrix(matrix33.create_from_x_rotation(0.5))))) # divide self.assertRaises(ValueError, lambda: q / m) def test_operators_matrix44(self): q = Quaternion() m = Matrix44.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: q + m) # subtract self.assertRaises(ValueError, lambda: q - m) # multiply self.assertTrue(np.array_equal(q * m, quaternion.cross(quaternion.create(), quaternion.create_from_matrix(matrix44.create_from_x_rotation(0.5))))) # divide self.assertRaises(ValueError, lambda: q / m) def test_operators_quaternion(self): q1 = Quaternion() q2 = Quaternion.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: q1 + q2) # subtract # we had to add this to enable np.array_equal to work # as it uses subtraction #self.assertRaises(ValueError, lambda: q1 - q2) # multiply self.assertTrue(np.array_equal(q1 * q2, quaternion.cross(quaternion.create(), quaternion.create_from_x_rotation(0.5)))) # divide self.assertRaises(ValueError, lambda: q1 / q2) # or self.assertTrue(np.array_equal(q1 | q2, quaternion.dot(quaternion.create(), quaternion.create_from_x_rotation(0.5)))) # inverse self.assertTrue(np.array_equal(~q2, quaternion.conjugate(quaternion.create_from_x_rotation(0.5)))) # == self.assertTrue(Quaternion() == Quaternion()) self.assertFalse(Quaternion() == Quaternion([0., 0., 0., 0.])) # != self.assertTrue(Quaternion() != Quaternion([1., 1., 1., 1.])) self.assertFalse(Quaternion() != Quaternion()) def test_operators_vector3(self): q = Quaternion.from_x_rotation(0.5) v = Vector3([1., 0., 0.]) # add self.assertRaises(ValueError, lambda: q + v) # subtract self.assertRaises(ValueError, lambda: q - v) # multiply self.assertTrue(np.array_equal(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(0.5), [1., 0., 0.]))) # divide self.assertRaises(ValueError, lambda: q / v) def test_operators_vector4(self): q = Quaternion.from_x_rotation(0.5) v = Vector4([1., 0., 0., 1.]) # add self.assertRaises(ValueError, lambda: q + v) # subtract self.assertRaises(ValueError, lambda: q - v) # multiply self.assertTrue(np.array_equal(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(0.5), [1., 0., 0., 1.]))) # divide self.assertRaises(ValueError, lambda: q / v) def test_apply_to_vector_non_unit(self): q = Quaternion.from_x_rotation(np.pi) # zero length v = Vector3([0., 0., 0.]) self.assertTrue(np.allclose(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(np.pi), [0., 0., 0.]))) # >1 length v = Vector3([2., 0., 0.]) self.assertTrue(np.allclose(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(np.pi), [2., 0., 0.]))) v = Vector3([0., 2., 0.]) self.assertTrue(np.allclose(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(np.pi), [0., 2., 0.]))) v = Vector3([0., 0., 2.]) self.assertTrue(np.allclose(q * v, quaternion.apply_to_vector(quaternion.create_from_x_rotation(np.pi), [0., 0., 2.]))) def test_accessors(self): q = Quaternion(np.arange(self._size)) self.assertTrue(np.array_equal(q.xy, [0, 1])) self.assertTrue(np.array_equal(q.xyz, [0, 1, 2])) self.assertTrue(np.array_equal(q.xyzw, [0, 1, 2, 3])) self.assertTrue(np.array_equal(q.xz, [0, 2])) self.assertTrue(np.array_equal(q.xyz, [0, 1, 2])) self.assertTrue(np.array_equal(q.xyw, [0, 1, 3])) self.assertTrue(np.array_equal(q.xw, [0, 3])) self.assertEqual(q.x, 0) self.assertEqual(q.y, 1) self.assertEqual(q.z, 2) self.assertEqual(q.w, 3) q.x = 1 self.assertEqual(q.x, 1) self.assertEqual(q[0], 1) q.x += 1 self.assertEqual(q.x, 2) self.assertEqual(q[0], 2) def test_equality(self): q1 = Quaternion([0, 0, 0, 1]) q2 = Quaternion([0, 0, 0, 1]) q3 = Quaternion([0, 0, 1, -1]) self.assertEqual(q1, q2) self.assertNotEqual(q1, q3) self.assertNotEqual(q2, q3) def test_equality_negative(self): q1 = Quaternion([0, 0, 0, 1]) q2 = Quaternion([0, 0, 0, -1]) q3 = Quaternion([0, 0, 1, -1]) self.assertEqual(q1, q2) self.assertNotEqual(q1, q3) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/objects/test_vector3.py000066400000000000000000000213311345610666600203350ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest import numpy as np from pyrr.objects.matrix33 import Matrix33 from pyrr.objects.matrix44 import Matrix44 from pyrr.objects.quaternion import Quaternion from pyrr.objects.vector3 import Vector3 from pyrr.objects.vector4 import Vector4 from pyrr import vector3 class test_object_vector3(unittest.TestCase): _shape = (3,) _size = np.multiply.reduce(_shape) def test_imports(self): import pyrr pyrr.Vector3() pyrr.vector3.Vector3() pyrr.objects.vector3.Vector3() def test_imports_1(self): from pyrr import Vector3 Vector3 def test_imports_2(self): from pyrr.objects import Vector3 Vector3 def test_imports_3(self): from pyrr.objects.vector3 import Vector3 Vector3 def test_create(self): v = Vector3() self.assertTrue(np.array_equal(v, [0.,0.,0.])) self.assertEqual(v.shape, self._shape) v = Vector3([1.,2.,3.]) self.assertTrue(np.array_equal(v, [1.,2.,3.])) self.assertEqual(v.shape, self._shape) v = Vector3(Vector3()) self.assertTrue(np.array_equal(v, [0.,0.,0.])) self.assertEqual(v.shape, self._shape) v4 = [1., 2., 3., 4.] result = vector3.create_from_vector4(v4) v, w = result np.testing.assert_almost_equal(v, [1.,2.,3.], decimal=5) np.testing.assert_almost_equal(w, 4., decimal=5) v4 = Vector4([1., 2., 3., 4.]) result = vector3.create_from_vector4(v4) v, w = result np.testing.assert_almost_equal(v, [1.,2.,3.], decimal=5) np.testing.assert_almost_equal(w, 4., decimal=5) m = Matrix44.from_translation([1.,2.,3.]) v = Vector3.from_matrix44_translation(m) self.assertTrue(np.array_equal(v, [1.,2.,3.])) m = Matrix44.from_translation([1.,2.,3.]) v = Vector3(m) self.assertTrue(np.array_equal(v, [1.,2.,3.])) def test_inverse(self): v = Vector3([1.,2.,3.]) self.assertTrue(np.array_equal(v.inverse, [-1.,-2.,-3.])) def test_normalize(self): v = Vector3([1.,1.,1.]) np.testing.assert_almost_equal(np.array(v.normalized), [0.57735, 0.57735, 0.57735], decimal=5) v.normalize() np.testing.assert_almost_equal(np.array(v), [0.57735, 0.57735, 0.57735], decimal=5) def test_operators_matrix33(self): v = Vector3() m = Matrix33.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + m) # subtract self.assertRaises(ValueError, lambda: v - m) # multiply self.assertRaises(ValueError, lambda: v - m) # divide self.assertRaises(ValueError, lambda: v / m) def test_operators_matrix44(self): v = Vector3() m = Matrix44.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + m) # subtract self.assertRaises(ValueError, lambda: v - m) # multiply self.assertRaises(ValueError, lambda: v * m) # divide self.assertRaises(ValueError, lambda: v / m) def test_operators_quaternion(self): v = Vector3() q = Quaternion.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + q) # subtract self.assertRaises(ValueError, lambda: v - q) # multiply self.assertRaises(ValueError, lambda: v * q) # divide self.assertRaises(ValueError, lambda: v / q) def test_operators_vector3(self): v1 = Vector3() v2 = Vector3([1.,2.,3.]) # add self.assertTrue(np.array_equal(v1 + v2, [1.,2.,3.])) # subtract self.assertTrue(np.array_equal(v1 - v2, [-1.,-2.,-3.])) # multiply self.assertTrue(np.array_equal(v1 * v2, [0.,0.,0.])) # divide self.assertTrue(np.array_equal(v1 / v2, [0.,0.,0.])) # or self.assertTrue(np.array_equal(v1 | v2, vector3.dot(v1, v2))) # xor self.assertTrue(np.array_equal(v1 ^ v2, vector3.cross(v1, v2))) # == self.assertTrue(Vector3() == Vector3()) self.assertFalse(Vector3() == Vector3([1.,1.,1.])) # != self.assertTrue(Vector3() != Vector3([1.,1.,1.])) self.assertFalse(Vector3() != Vector3()) def test_operators_vector4(self): v1 = Vector3() v2 = Vector4([1.,2.,3.,4.]) # add self.assertRaises(ValueError, lambda: v1 + v2) # subtract self.assertRaises(ValueError, lambda: v1 - v2) # multiply self.assertRaises(ValueError, lambda: v1 * v2) # divide self.assertRaises(ValueError, lambda: v1 / v2) # or self.assertRaises(ValueError, lambda: v1 | v2) # xor self.assertRaises(ValueError, lambda: v1 ^ v2) # == self.assertRaises(ValueError, lambda: Vector3() == Vector4()) # != self.assertRaises(ValueError, lambda: Vector3() != Vector4([1.,1.,1.,1.])) def test_operators_number(self): v1 = Vector3([1.,2.,3.]) fv = np.empty((1,), dtype=[('i', np.int16, 1),('f', np.float32, 1)]) fv[0] = (2, 2.0) # add self.assertTrue(np.array_equal(v1 + 1., [2., 3., 4.])) self.assertTrue(np.array_equal(v1 + 1, [2., 3., 4.])) self.assertTrue(np.array_equal(v1 + np.float(1.), [2., 3., 4.])) self.assertTrue(np.array_equal(v1 + fv[0]['f'], [3., 4., 5.])) self.assertTrue(np.array_equal(v1 + fv[0]['i'], [3., 4., 5.])) # subtract self.assertTrue(np.array_equal(v1 - 1., [0., 1., 2.])) self.assertTrue(np.array_equal(v1 - 1, [0., 1., 2.])) self.assertTrue(np.array_equal(v1 - np.float(1.), [0., 1., 2.])) self.assertTrue(np.array_equal(v1 - fv[0]['f'], [-1., 0., 1.])) self.assertTrue(np.array_equal(v1 - fv[0]['i'], [-1., 0., 1.])) # multiply self.assertTrue(np.array_equal(v1 * 2., [2., 4., 6.])) self.assertTrue(np.array_equal(v1 * 2, [2., 4., 6.])) self.assertTrue(np.array_equal(v1 * np.float(2.), [2., 4., 6.])) self.assertTrue(np.array_equal(v1 * fv[0]['f'], [2., 4., 6.])) self.assertTrue(np.array_equal(v1 * fv[0]['i'], [2., 4., 6.])) # divide self.assertTrue(np.array_equal(v1 / 2., [.5, 1., 1.5])) self.assertTrue(np.array_equal(v1 / 2, [.5, 1., 1.5])) self.assertTrue(np.array_equal(v1 / np.float(2.), [.5, 1., 1.5])) self.assertTrue(np.array_equal(v1 / fv[0]['f'], [.5, 1., 1.5])) self.assertTrue(np.array_equal(v1 / fv[0]['i'], [.5, 1., 1.5])) # or self.assertRaises(ValueError, lambda: v1 | .5) self.assertRaises(ValueError, lambda: v1 | 5) self.assertRaises(ValueError, lambda: v1 | np.float(2.)) self.assertRaises(ValueError, lambda: v1 | fv[0]['f']) self.assertRaises(ValueError, lambda: v1 | fv[0]['i']) # xor self.assertRaises(ValueError, lambda: v1 ^ .5) self.assertRaises(ValueError, lambda: v1 ^ 5) self.assertRaises(ValueError, lambda: v1 ^ np.float(2.)) self.assertRaises(ValueError, lambda: v1 ^ fv[0]['f']) self.assertRaises(ValueError, lambda: v1 ^ fv[0]['i']) # == self.assertRaises(ValueError, lambda: v1 == .5) self.assertRaises(ValueError, lambda: v1 == 5) self.assertRaises(ValueError, lambda: v1 == np.float(2.)) self.assertRaises(ValueError, lambda: v1 == fv[0]['f']) self.assertRaises(ValueError, lambda: v1 == fv[0]['i']) # != self.assertRaises(ValueError, lambda: v1 != .5) self.assertRaises(ValueError, lambda: v1 != 5) self.assertRaises(ValueError, lambda: v1 != np.float(2.)) self.assertRaises(ValueError, lambda: v1 != fv[0]['f']) self.assertRaises(ValueError, lambda: v1 != fv[0]['i']) def test_bitwise(self): v1 = Vector3([1.,0.,0.]) v2 = Vector3([0.,1.,0.]) # xor (cross) self.assertTrue(np.array_equal(v1 ^ v2, vector3.cross(v1, v2))) # or (dot) self.assertTrue(np.array_equal(v1 | v2, vector3.dot(v1, v2))) def test_accessors(self): v = Vector3(np.arange(self._size)) self.assertTrue(np.array_equal(v.xy,[0,1])) self.assertTrue(np.array_equal(v.xyz,[0,1,2])) self.assertTrue(np.array_equal(v.xz,[0,2])) self.assertTrue(np.array_equal(v.xyz,[0,1,2])) self.assertEqual(v.x, 0) self.assertEqual(v.y, 1) self.assertEqual(v.z, 2) v.x = 1 self.assertEqual(v.x, 1) self.assertEqual(v[0], 1) v.x += 1 self.assertEqual(v.x, 2) self.assertEqual(v[0], 2) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/objects/test_vector4.py000066400000000000000000000205461345610666600203450ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest import numpy as np from pyrr.objects.matrix33 import Matrix33 from pyrr.objects.matrix44 import Matrix44 from pyrr.objects.quaternion import Quaternion from pyrr.objects.vector3 import Vector3 from pyrr.objects.vector4 import Vector4 from pyrr import vector4 class test_object_vector4(unittest.TestCase): _shape = (4,) _size = np.multiply.reduce(_shape) def test_imports(self): import pyrr pyrr.Vector4() pyrr.vector4.Vector4() pyrr.objects.vector4.Vector4() from pyrr import Vector4 from pyrr.objects import Vector4 from pyrr.objects.vector4 import Vector4 def test_create(self): v = Vector4() self.assertTrue(np.array_equal(v, [0.,0.,0.,0.])) self.assertEqual(v.shape, self._shape) v = Vector4([1.,2.,3.,4.]) self.assertTrue(np.array_equal(v, [1.,2.,3.,4.])) self.assertEqual(v.shape, self._shape) v = Vector4.from_vector3([1.,2.,3.], w=0.0) self.assertTrue(np.array_equal(v, [1.,2.,3.,0.])) self.assertEqual(v.shape, self._shape) v = Vector4(Vector4()) self.assertTrue(np.array_equal(v, [0.,0.,0.,0.])) self.assertEqual(v.shape, self._shape) m = Matrix44.from_translation([1.,2.,3.]) v = Vector4.from_matrix44_translation(m) self.assertTrue(np.array_equal(v, [1.,2.,3.,1.])) m = Matrix44.from_translation([1.,2.,3.]) v = Vector4(m) self.assertTrue(np.array_equal(v, [1.,2.,3.,1.])) def test_inverse(self): v = Vector4([1.,2.,3.,4.]) self.assertTrue(np.array_equal(v.inverse, [-1.,-2.,-3.,-4.])) def test_normalize(self): v = Vector4([1.,1.,1.,1.]) np.testing.assert_almost_equal(np.array(v.normalized), [0.5, 0.5, 0.5, 0.5], decimal=5) v.normalize() np.testing.assert_almost_equal(np.array(v), [0.5, 0.5, 0.5, 0.5], decimal=5) def test_operators_matrix33(self): v = Vector4() m = Matrix33.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + m) # subtract self.assertRaises(ValueError, lambda: v - m) # multiply self.assertRaises(ValueError, lambda: v - m) # divide self.assertRaises(ValueError, lambda: v / m) def test_operators_matrix44(self): v = Vector4() m = Matrix44.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + m) # subtract self.assertRaises(ValueError, lambda: v - m) # multiply self.assertRaises(ValueError, lambda: v * m) # divide self.assertRaises(ValueError, lambda: v / m) def test_operators_quaternion(self): v = Vector4() q = Quaternion.from_x_rotation(0.5) # add self.assertRaises(ValueError, lambda: v + q) # subtract self.assertRaises(ValueError, lambda: v - q) # multiply self.assertRaises(ValueError, lambda: v * q) # divide self.assertRaises(ValueError, lambda: v / q) def test_operators_vector3(self): v1 = Vector4() v2 = Vector3([1.,2.,3.]) # add self.assertRaises(ValueError, lambda: v1 + v2) # subtract self.assertRaises(ValueError, lambda: v1 - v2) # multiply self.assertRaises(ValueError, lambda: v1 * v2) # divide #self.assertRaises(ValueError, lambda: v1 / v2) # or self.assertRaises(ValueError, lambda: v1 | v2) # xor #self.assertRaises(ValueError, lambda: v1 ^ v2) # == self.assertRaises(ValueError, lambda: Vector4() == Vector3()) # != self.assertRaises(ValueError, lambda: Vector4() != Vector3([1.,1.,1.])) def test_operators_vector4(self): v1 = Vector4() v2 = Vector4([1.,2.,3.,4.]) # add self.assertTrue(np.array_equal(v1 + v2, [1.,2.,3.,4.])) # subtract self.assertTrue(np.array_equal(v1 - v2, [-1.,-2.,-3.,-4])) # multiply self.assertTrue(np.array_equal(v1 * v2, [0.,0.,0.,0.])) # divide self.assertTrue(np.array_equal(v1 / v2, [0.,0.,0.,0.])) # or self.assertTrue(np.array_equal(v1 | v2, vector4.dot([0.,0.,0.,0.], [1.,2.,3.,4.]))) # xor #self.assertTrue(np.array_equal(v1 ^ v2, vector4.cross([0.,0.,0.,0.], [1.,2.,3.,4.]))) # == self.assertTrue(Vector4() == Vector4()) self.assertFalse(Vector4() == Vector4([1.,1.,1.,1.])) # != self.assertTrue(Vector4() != Vector4([1.,1.,1.,1.])) self.assertFalse(Vector4() != Vector4()) def test_operators_number(self): v1 = Vector4([1.,2.,3.,4.]) fv = np.empty((1,), dtype=[('i', np.int16, 1),('f', np.float32, 1)]) fv[0] = (2, 2.0) # add self.assertTrue(np.array_equal(v1 + 1., [2., 3., 4., 5.])) self.assertTrue(np.array_equal(v1 + 1, [2., 3., 4., 5.])) self.assertTrue(np.array_equal(v1 + np.float(1.), [2., 3., 4., 5.])) self.assertTrue(np.array_equal(v1 + fv[0]['f'], [3., 4., 5., 6.])) self.assertTrue(np.array_equal(v1 + fv[0]['i'], [3., 4., 5., 6.])) # subtract self.assertTrue(np.array_equal(v1 - 1., [0., 1., 2., 3.])) self.assertTrue(np.array_equal(v1 - 1, [0., 1., 2., 3.])) self.assertTrue(np.array_equal(v1 - np.float(1.), [0., 1., 2., 3.])) self.assertTrue(np.array_equal(v1 - fv[0]['f'], [-1., 0., 1., 2.])) self.assertTrue(np.array_equal(v1 - fv[0]['i'], [-1., 0., 1., 2.])) # multiply self.assertTrue(np.array_equal(v1 * 2., [2., 4., 6., 8.])) self.assertTrue(np.array_equal(v1 * 2, [2., 4., 6., 8.])) self.assertTrue(np.array_equal(v1 * np.float(2.), [2., 4., 6., 8.])) self.assertTrue(np.array_equal(v1 * fv[0]['f'], [2., 4., 6., 8.])) self.assertTrue(np.array_equal(v1 * fv[0]['i'], [2., 4., 6., 8.])) # divide self.assertTrue(np.array_equal(v1 / 2., [.5, 1., 1.5, 2.])) self.assertTrue(np.array_equal(v1 / 2, [.5, 1., 1.5, 2.])) self.assertTrue(np.array_equal(v1 / np.float(2.), [.5, 1., 1.5, 2.])) self.assertTrue(np.array_equal(v1 / fv[0]['f'], [.5, 1., 1.5, 2.])) self.assertTrue(np.array_equal(v1 / fv[0]['i'], [.5, 1., 1.5, 2.])) # or self.assertRaises(ValueError, lambda: v1 | .5) self.assertRaises(ValueError, lambda: v1 | 5) self.assertRaises(ValueError, lambda: v1 | np.float(2.)) self.assertRaises(ValueError, lambda: v1 | fv[0]['f']) self.assertRaises(ValueError, lambda: v1 | fv[0]['i']) # xor self.assertRaises(ValueError, lambda: v1 ^ .5) self.assertRaises(ValueError, lambda: v1 ^ 5) self.assertRaises(ValueError, lambda: v1 ^ np.float(2.)) self.assertRaises(ValueError, lambda: v1 ^ fv[0]['f']) self.assertRaises(ValueError, lambda: v1 ^ fv[0]['i']) # == self.assertRaises(ValueError, lambda: v1 == .5) self.assertRaises(ValueError, lambda: v1 == 5) self.assertRaises(ValueError, lambda: v1 == np.float(2.)) self.assertRaises(ValueError, lambda: v1 == fv[0]['f']) self.assertRaises(ValueError, lambda: v1 == fv[0]['i']) # != self.assertRaises(ValueError, lambda: v1 != .5) self.assertRaises(ValueError, lambda: v1 != 5) self.assertRaises(ValueError, lambda: v1 != np.float(2.)) self.assertRaises(ValueError, lambda: v1 != fv[0]['f']) self.assertRaises(ValueError, lambda: v1 != fv[0]['i']) def test_bitwise(self): v1 = Vector4([1.,0.,0.,1.]) v2 = Vector4([0.,1.,0.,1.]) # or (dot) self.assertTrue(np.array_equal(v1 | v2, vector4.dot(v1, v2))) def test_accessors(self): v = Vector4(np.arange(self._size)) self.assertTrue(np.array_equal(v.xy,[0,1])) self.assertTrue(np.array_equal(v.xyz,[0,1,2])) self.assertTrue(np.array_equal(v.xz,[0,2])) self.assertTrue(np.array_equal(v.xyz,[0,1,2])) self.assertEqual(v.x, 0) self.assertEqual(v.y, 1) self.assertEqual(v.z, 2) v.x = 1 self.assertEqual(v.x, 1) self.assertEqual(v[0], 1) v.x += 1 self.assertEqual(v.x, 2) self.assertEqual(v[0], 2) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_aabb.py000066400000000000000000000105421345610666600162060ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import aabb class test_aabb(unittest.TestCase): def test_import(self): import pyrr pyrr.aabb from pyrr import aabb def test_create_zeros(self): result = aabb.create_zeros() self.assertTrue(np.array_equal(result, [[0,0,0],[0,0,0]])) def test_create_from_bounds(self): result = aabb.create_from_bounds([-1,-1,-1],[1,1,1]) self.assertTrue(np.array_equal(result, [[-1,-1,-1],[1,1,1]])) def test_create_from_points(self): points = np.array([[-1.0,-1.0,-1.0]]) result = aabb.create_from_points(points) expected = np.array([ [-1.0,-1.0,-1.0], [-1.0,-1.0,-1.0] ]) self.assertTrue(np.array_equal(result, expected)) points = np.array([ [-1.0,-1.0,-1.0], [-1.0, 2.0,-2.0], ]) result = aabb.create_from_points(points) expected = np.array([ [-1.0,-1.0,-2.0], [-1.0, 2.0,-1.0], ]) self.assertTrue(np.array_equal(result, expected)) def test_create_from_aabbs(self): # -1 a1 = np.array([ [-1.0, 0.0, 0.0 ], [-1.0,-1.0,-1.0 ] ]) # +1 a2 = np.array([ [ 1.0,-1.0,-1.0 ], [ 1.0, 1.0, 1.0 ] ]) # -1 to +1 result = aabb.create_from_aabbs(np.array([a1, a2])) expected = np.array([ [-1.0,-1.0,-1.0 ], [ 1.0, 1.0, 1.0 ] ]) self.assertTrue(np.array_equal(result, expected)) def test_add_point(self): obj = np.array([ [-1.0,-1.0,-1.0], [-1.0,-1.0,-1.0] ]) points = np.array([1.0, 1.0, 1.0]) result = aabb.add_points(obj, points) expected = np.array([ [-1.0,-1.0,-1.0 ], [ 1.0, 1.0, 1.0 ] ]) self.assertTrue(np.array_equal(result, expected)) def test_add_aabbs(self): a = aabb.create_zeros() a1 = np.array([ [-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0] ]) result = aabb.add_aabbs(a, a1) expected = np.array([ [-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0] ]) self.assertTrue(np.array_equal(result, expected)) a = np.array([ [-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0] ]) a2 = np.array([ [-1.0, 0.0,-1.0], [ 2.0, 1.0, 1.0] ]) result = aabb.add_aabbs(a, a2) expected = np.array([ [-1.0,-1.0,-1.0], [ 2.0, 1.0, 1.0] ]) self.assertTrue(np.array_equal(result, expected)) def test_centre_point_single_point(self): points = np.array([[-1.0,-1.0,-1.0]]) obj = aabb.create_from_points(points) result = aabb.centre_point(obj) expected = np.array([-1.0,-1.0,-1.0]) self.assertTrue(np.array_equal(result, expected)) def test_centre_point_multiple_points(self): points = np.array([ [ 1.0, 1.0, 1.0], [-1.0,-1.0,-1.0] ]) obj = aabb.create_from_points(points) result = aabb.centre_point(obj) expected = np.zeros(3) self.assertTrue(np.array_equal(result, expected)) def test_minimum(self): a = aabb.create_from_bounds([-1,-1,-1],[1,1,1]) result = aabb.minimum(a) self.assertTrue(np.array_equal(result, [-1,-1,-1])) def test_maximum(self): a = aabb.create_from_bounds([-1,-1,-1],[1,1,1]) result = aabb.maximum(a) self.assertTrue(np.array_equal(result, [1,1,1])) def test_clamp_points_single(self): a = aabb.create_from_bounds([-1,-1,-1],[1,1,1]) points = np.array([2,1,1]) result = aabb.clamp_points(a, points) expected = np.array([1,1,1]) self.assertTrue(np.array_equal(result, expected)) def test_clamp_points_list(self): a = aabb.create_from_bounds([-1,-1,-1],[1,1,1]) points = np.array([ [1,1,1], [2,1,1], [-1,-1,-1], [-2,-2,-2], ]) result = aabb.clamp_points(a, points) expected = np.array([[1,1,1],[1,1,1],[-1,-1,-1],[-1,-1,-1]]) self.assertTrue(np.array_equal(result, expected)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_aambb.py000066400000000000000000000056311345610666600163660ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import aambb from pyrr import aabb from pyrr import vector class test_aambb(unittest.TestCase): def test_import(self): import pyrr pyrr.aambb from pyrr import aambb def test_create_zeros(self): result = aambb.create_zeros() self.assertTrue(np.array_equal(result, [[0.,0.,0.],[0.,0.,0.]])) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_create_from_bounds(self): bounds = [[-1.,1.,-1.], [2.,1.,0.]] result = aambb.create_from_bounds(*bounds) length = max(vector.length(bounds[0]), vector.length(bounds[1])) self.assertTrue(np.array_equal(result, [[-length,-length,-length],[length,length,length]])) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_create_from_points(self): result = aambb.create_from_points(np.array([[-1.0, 0.0, 0.0]])) self.assertTrue(np.array_equal(result, [[-1.0,-1.0,-1.0],[ 1.0, 1.0, 1.0]])) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_center_point(self): # this should always be 0,0,0 result = aambb.create_from_bounds([-1.,1.,-1.], [2.,1.,0.]) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_create_from_aabbs(self): a1 = aambb.create_from_points([ [ 0.0, 0.0, 0.0], [ 1.0, 1.0,-1.0] ]) a2 = aambb.create_from_points([ [ 0.0, 0.0, 2.0], [-1.0,-1.0, 1.0] ]) result = aambb.create_from_aabbs([a1, a2]) length = np.amax(vector.length([a1, a2])) self.assertTrue(np.array_equal(result, [[-length,-length,-length],[length,length,length]]), (result,)) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_add_point(self): a = aambb.create_from_bounds([-0.5,-0.5,-0.5], [0.5,0.5,0.5]) points = np.array([ [ 2.0,-1.0,-1.0], [ 1.0, 3.0,-1.0], ]) result = aambb.add_points(a, points) length = np.amax(vector.length([a, points])) self.assertTrue(np.array_equal(result, [[-length,-length,-length],[length,length,length]]), (result,)) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) def test_add_aabbs(self): a1 = aambb.create_from_bounds([-0.5,-0.5,-0.5], [0.5,0.5,0.5]) a2 = aambb.create_from_bounds([1.0,-2.0, 1.0], [2.0,-1.0, 1.0]) result = aambb.add_aabbs(a1, [a2]) length = np.amax(vector.length([a1, a2])) self.assertTrue(np.array_equal(result, [[-length,-length,-length],[length,length,length]]), (result,)) self.assertTrue(np.array_equal(aambb.centre_point(result), [0.0,0.0,0.0])) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_equivalence.py000066400000000000000000000047101345610666600176220ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import quaternion, matrix44, matrix33, euler class test_matrix_quaternion(unittest.TestCase): def test_m44_q_equivalence(self): """Test for equivalance of matrix and quaternion rotations. Create a matrix and quaternion, rotate each by the same values then convert matrix<->quaternion and check the results are the same. """ m = matrix44.create_from_x_rotation(np.pi / 2.) mq = quaternion.create_from_matrix(m) q = quaternion.create_from_x_rotation(np.pi / 2.) qm = matrix44.create_from_quaternion(q) self.assertTrue(np.allclose(np.dot([1., 0., 0., 1.], m), [1., 0., 0., 1.])) self.assertTrue(np.allclose(np.dot([1., 0., 0., 1.], qm), [1., 0., 0., 1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0., 1.]), [1., 0., 0., 1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(mq, [1., 0., 0., 1.]), [1., 0., 0., 1.])) np.testing.assert_almost_equal(q, mq, decimal=5) np.testing.assert_almost_equal(m, qm, decimal=5) def test_euler_equivalence(self): eulers = euler.create_from_x_rotation(np.pi / 2.) m = matrix33.create_from_x_rotation(np.pi / 2.) q = quaternion.create_from_x_rotation(np.pi / 2.) qm = matrix33.create_from_quaternion(q) em = matrix33.create_from_eulers(eulers) self.assertTrue(np.allclose(qm, m)) self.assertTrue(np.allclose(qm, em)) self.assertTrue(np.allclose(m, em)) def test_quaternion_matrix_conversion(self): # https://au.mathworks.com/help/robotics/ref/quat2rotm.html?requestedDomain=www.mathworks.com q = quaternion.create(0.7071, 0., 0., 0.7071) m33 = matrix33.create_from_quaternion(q) expected = np.array([ [1., 0., 0.], [0.,-0.,-1.], [0., 1.,-0.], ]) self.assertTrue(np.allclose(m33, expected)) # issue #42 q = quaternion.create(*[0.80087974, 0.03166748, 0.59114721,-0.09018753]) m33 = matrix33.create_from_quaternion(q) q2 = quaternion.create_from_matrix(m33) print(q, q2) self.assertTrue(np.allclose(q, q2)) q3 = quaternion.create_from_matrix(m33.T) self.assertFalse(np.allclose(q, q3)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_euler.py000066400000000000000000000011701345610666600164320ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import euler class test_euler(unittest.TestCase): def test_import(self): import pyrr pyrr.euler from pyrr import euler def test_create(self): self.assertTrue(np.array_equal(euler.create(), [0., 0., 0.])) e = euler.create(roll=1., pitch=2., yaw=3.) self.assertEqual(euler.roll(e), 1.) self.assertEqual(euler.pitch(e), 2.) self.assertEqual(euler.yaw(e), 3.) self.assertTrue(np.array_equal(e, [1., 2., 3.])) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_examples.py000066400000000000000000000023151345610666600171360ustar00rootroot00000000000000from __future__ import absolute_import try: import unittest2 as unittest except: import unittest class test_procedural_examples(unittest.TestCase): def test_procedural_examples(self): from pyrr import quaternion, matrix44, vector3 import numpy as np point = vector3.create(1.,2.,3.) orientation = quaternion.create() translation = vector3.create() scale = vector3.create(1,1,1) # translate along X by 1 translation += [1.0, 0.0, 0.0] # rotate about Y by pi/2 rotation = quaternion.create_from_y_rotation(np.pi / 2.0) orientation = quaternion.cross(rotation, orientation) # create a matrix # start our matrix off using the scale matrix = matrix44.create_from_scale(scale) # apply our orientation orientation = matrix44.create_from_quaternion(orientation) matrix = matrix44.multiply(matrix, orientation) # apply our translation translation_matrix = matrix44.create_from_translation(translation) matrix = matrix44.multiply(matrix, translation_matrix) # transform our point by the matrix point = matrix44.apply_to_vector(matrix, point) Pyrr-0.10.3/tests/test_geometric_tests.py000066400000000000000000000267051345610666600205310ustar00rootroot00000000000000from pyrr.geometric_tests import ray_intersect_sphere try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import geometric_tests as gt from pyrr import line, plane, ray, sphere class test_geometric_tests(unittest.TestCase): def test_import(self): import pyrr pyrr.geometric_tests from pyrr import geometric_tests def test_point_intersect_line(self): p = np.array([1.,1.,1.]) l = np.array([[0.,0.,0.],[2.,2.,2.]]) result = gt.point_intersect_line(p, l) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_line_invalid(self): p = np.array([3.,3.,3.]) l = np.array([[0.,0.,0.],[2.,2.,2.]]) result = gt.point_intersect_line(p, l) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_line_segment(self): p = np.array([1.,1.,1.]) l = np.array([[0.,0.,0.],[2.,2.,2.]]) result = gt.point_intersect_line_segment(p, l) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_line_segment_invalid(self): p = np.array([3.,3.,3.]) l = np.array([[0.,0.,0.],[2.,2.,2.]]) result = gt.point_intersect_line_segment(p, l) self.assertEqual(result, None) def test_point_intersect_rectangle_valid_intersections_1(self): r = np.array([ [0.0, 0.0], [5.0, 5.0] ]) p = [ 0.0, 0.0] result = gt.point_intersect_rectangle(p, r) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_rectangle_valid_intersections_2(self): r = np.array([ [0.0, 0.0], [5.0, 5.0] ]) p = [ 5.0, 5.0] result = gt.point_intersect_rectangle(p, r) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_rectangle_valid_intersections_3(self): r = np.array([ [0.0, 0.0], [5.0, 5.0] ]) p = [ 1.0, 1.0] result = gt.point_intersect_rectangle(p, r) self.assertTrue(np.array_equal(result, p)) def test_point_intersect_rectangle_invalid_intersections_1(self): r = np.array([ [0.0, 0.0], [5.0, 5.0] ]) p = [-1.0, 1.0] result = gt.point_intersect_rectangle(p, r) self.assertFalse(np.array_equal(result, p)) def test_point_intersect_rectangle_invalid_intersections_2(self): r = np.array([ [0.0, 0.0], [5.0, 5.0] ]) p = [ 1.0, 10.0] result = gt.point_intersect_rectangle(p, r) self.assertFalse(np.array_equal(result, p)) def test_point_intersect_rectangle_invalid_intersections_3(self): rect = np.array([ [0.0, 0.0], [5.0, 5.0] ]) point = [ 1.0,-1.0] result = gt.point_intersect_rectangle(point, rect) self.assertFalse(np.array_equal(result, point)) def test_ray_intersect_plane(self): r = ray.create([0.,-1.,0.],[0.,1.,0.]) p = plane.create([0.,1.,0.], 0.) result = gt.ray_intersect_plane(r, p) self.assertFalse(np.array_equal(result, [0.,1.,0.])) def test_ray_intersect_plane_front_only(self): r = ray.create([0.,-1.,0.],[0.,1.,0.]) p = plane.create([0.,1.,0.], 0.) result = gt.ray_intersect_plane(r, p, front_only=True) self.assertEqual(result, None) def test_ray_intersect_plane_invalid(self): r = ray.create([0.,-1.,0.],[1.,0.,0.]) p = plane.create([0.,1.,0.], 0.) result = gt.ray_intersect_plane(r, p) self.assertEqual(result, None) def test_point_closest_point_on_ray(self): l = line.create_from_points( [ 0.0, 0.0, 0.0 ], [10.0, 0.0, 0.0 ] ) p = np.array([ 0.0, 1.0, 0.0]) result = gt.point_closest_point_on_ray(p, l) self.assertTrue(np.array_equal(result, [ 0.0, 0.0, 0.0])) def test_point_closest_point_on_line(self): p = np.array([0.,1.,0.]) l = np.array([[0.,0.,0.],[2.,0.,0.]]) result = gt.point_closest_point_on_line(p, l) self.assertTrue(np.array_equal(result, [0.,0.,0.]), (result,)) def test_point_closest_point_on_line_2(self): p = np.array([3.,0.,0.]) l = np.array([[0.,0.,0.],[2.,0.,0.]]) result = gt.point_closest_point_on_line(p, l) self.assertTrue(np.array_equal(result, [3.,0.,0.]), (result,)) def test_point_closest_point_on_line_segment(self): p = np.array([0.,1.,0.]) l = np.array([[0.,0.,0.],[2.,0.,0.]]) result = gt.point_closest_point_on_line_segment(p, l) self.assertTrue(np.array_equal(result, [0.,0.,0.]), (result,)) def test_vector_parallel_vector(self): v1 = np.array([1.,0.,0.]) v2 = np.array([2.,0.,0.]) self.assertTrue(gt.vector_parallel_vector(v1,v2)) def test_vector_parallel_vector_invalid(self): v1 = np.array([1.,0.,0.]) v2 = np.array([0.,1.,0.]) self.assertTrue(False == gt.vector_parallel_vector(v1,v2)) def test_ray_parallel_ray(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([1.,0.,0.],[2.,0.,0.]) self.assertTrue(gt.ray_parallel_ray(r1,r2)) def test_ray_parallel_ray_2(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([1.,0.,0.],[0.,1.,0.]) self.assertTrue(False == gt.ray_parallel_ray(r1,r2)) def test_ray_parallel_ray_3(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([0.,1.,0.],[1.,0.,0.]) self.assertTrue(gt.ray_parallel_ray(r1,r2)) def test_ray_coincident_ray(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([1.,0.,0.],[2.,0.,0.]) self.assertTrue(gt.ray_coincident_ray(r1,r2)) def test_ray_coincident_ray_2(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([1.,0.,0.],[0.,1.,0.]) self.assertTrue(False == gt.ray_coincident_ray(r1,r2)) def test_ray_coincident_ray_3(self): r1 = ray.create([0.,0.,0.],[1.,0.,0.]) r2 = ray.create([0.,1.,0.],[1.,0.,0.]) self.assertTrue(False == gt.ray_coincident_ray(r1,r2)) def test_ray_intersect_aabb_valid_1(self): a = np.array([[-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0]]) r = np.array([[ 0.5, 0.5, 0.0], [ 0.0, 0.0,-1.0]]) result = gt.ray_intersect_aabb(r, a) self.assertTrue(np.array_equal(result, [ 0.5, 0.5,-1.0])) def test_ray_intersect_aabb_valid_2(self): a = np.array([[-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0]]) r = np.array([[2.0, 2.0, 2.0], [ -1.0, -1.0, -1.0]]) result = gt.ray_intersect_aabb(r, a) self.assertTrue(np.array_equal(result, [1.0, 1.0, 1.0])) def test_ray_intersect_aabb_valid_3(self): a = np.array([[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]]) r = np.array([[.5, .5, .5], [0, 0, 1.0]]) result = gt.ray_intersect_aabb(r, a) self.assertTrue(np.array_equal(result, [.5, .5, 1.0])) def test_ray_intersect_aabb_invalid_1(self): a = np.array([[-1.0,-1.0,-1.0], [ 1.0, 1.0, 1.0]]) r = np.array([[2.0, 2.0, 2.0], [ 1.0, 1.0, 1.0]]) result = gt.ray_intersect_aabb(r, a) self.assertEqual(result, None) def test_point_height_above_plane(self): pl = plane.create([0., 1., 0.], 1.) p = np.array([0., 1., 0.]) result = gt.point_height_above_plane(p, pl) self.assertEqual(result, 0.) p = np.array([0., 0., 0.]) result = gt.point_height_above_plane(p, pl) self.assertEqual(result, -1.) v1 = np.array([ 0.0, 0.0, 1.0]) v2 = np.array([ 1.0, 0.0, 1.0]) v3 = np.array([ 0.0, 1.0, 1.0]) p = np.array([0.0, 0.0, 20.0]) pl = plane.create_from_points(v1, v2, v3) pl = plane.invert_normal(pl) result = gt.point_height_above_plane(p, pl) self.assertEqual(result, 19.) pl = plane.create_xz(distance=5.) p = np.array([0., 5., 0.]) h = gt.point_height_above_plane(p, pl) self.assertEqual(h, 0.) def test_point_closest_point_on_plane(self): pl = np.array([ 0.0, 1.0, 0.0, 0.0]) p = np.array([ 5.0, 20.0, 5.0]) result = gt.point_closest_point_on_plane(p, pl) self.assertTrue(np.array_equal(result, [ 5.0, 0.0, 5.0])) def test_sphere_does_intersect_sphere_1(self): s1 = sphere.create() s2 = sphere.create() self.assertTrue(gt.sphere_does_intersect_sphere(s1, s2)) def test_sphere_does_intersect_sphere_2(self): s1 = sphere.create() s2 = sphere.create([1.,0.,0.]) self.assertTrue(gt.sphere_does_intersect_sphere(s1, s2)) def test_sphere_does_intersect_sphere_3(self): s1 = sphere.create() s2 = sphere.create([2.,0.,0.], 1.0) self.assertTrue(gt.sphere_does_intersect_sphere(s1, s2)) def test_sphere_does_intersect_sphere_4(self): s1 = sphere.create() s2 = sphere.create([2.,0.,0.], 0.5) self.assertTrue(False == gt.sphere_does_intersect_sphere(s1, s2)) def test_sphere_penetration_sphere_1(self): s1 = sphere.create() s2 = sphere.create() self.assertEqual(gt.sphere_penetration_sphere(s1, s2), 2.0) def test_sphere_penetration_sphere_2(self): s1 = sphere.create() s2 = sphere.create([1.,0.,0.], 1.0) self.assertEqual(gt.sphere_penetration_sphere(s1, s2), 1.0) def test_sphere_penetration_sphere_3(self): s1 = sphere.create() s2 = sphere.create([2.,0.,0.], 1.0) self.assertEqual(gt.sphere_penetration_sphere(s1, s2), 0.0) def test_sphere_penetration_sphere_4(self): s1 = sphere.create() s2 = sphere.create([3.,0.,0.], 1.0) self.assertEqual(gt.sphere_penetration_sphere(s1, s2), 0.0) def test_ray_intersect_sphere_no_solution_1(self): r = ray.create([0, 2, 0], [1, 0, 0]) s = sphere.create([0, 0, 0], 1) intersections = ray_intersect_sphere(r, s) self.assertEqual(len(intersections), 0) def test_ray_intersect_sphere_no_solution_2(self): r = ray.create([0, 0, 0], [1, 0, 0]) s = sphere.create([0, 2, 0], 1) intersections = ray_intersect_sphere(r, s) self.assertEqual(len(intersections), 0) def test_ray_intersect_sphere_one_solution_1(self): r = ray.create([0, 0, 0], [1, 0, 0]) s = sphere.create([0, 0, 0], 1) intersections = ray_intersect_sphere(r, s) self.assertEqual(len(intersections), 1) np.testing.assert_array_almost_equal(intersections[0], np.array([1, 0, 0]), decimal=2) def test_ray_intersect_sphere_two_solutions_1(self): r = ray.create([-2, 0, 0], [1, 0, 0]) s = sphere.create([0, 0, 0], 1) intersections = ray_intersect_sphere(r, s) self.assertEqual(len(intersections), 2) np.testing.assert_array_almost_equal(intersections[0], np.array([1, 0, 0]), decimal=2) np.testing.assert_array_almost_equal(intersections[1], np.array([-1, 0, 0]), decimal=2) def test_ray_intersect_sphere_two_solutions_2(self): r = ray.create([2.48, 1.45, 1.78], [-3.1, 0.48, -3.2]) s = sphere.create([1, 1, 0], 1) intersections = ray_intersect_sphere(r, s) self.assertEqual(len(intersections), 2) np.testing.assert_array_almost_equal(intersections[0], np.array([0.44, 1.77, -0.32]), decimal=2) np.testing.assert_array_almost_equal(intersections[1], np.array([1.41, 1.62, 0.67]), decimal=2) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_geometry.py000066400000000000000000000102341345610666600171520ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import geometry # TODO: test all combinations of st, rgba, and type class test_geometry(unittest.TestCase): def test_import(self): import pyrr pyrr.geometry from pyrr import geometry def test_create_quad(self): v, i = geometry.create_quad() expected_v = np.array([ [ 0.5, 0.5, 0.], [-0.5, 0.5, 0.], [-0.5,-0.5, 0.], [ 0.5,-0.5, 0.], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_quad_scale(self): v, i = geometry.create_quad((2.0,0.5)) expected_v = np.array([ [ 1., 0.25, 0.], [-1., 0.25, 0.], [-1.,-0.25, 0.], [ 1.,-0.25, 0.], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_quad_st(self): v, i = geometry.create_quad(st=True) expected_v = np.array([ [ 0.5, 0.5, 0., 1., 1.], [-0.5, 0.5, 0., 0., 1.], [-0.5,-0.5, 0., 0., 0.], [ 0.5,-0.5, 0., 1., 0.], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_quad_st_values(self): v, i = geometry.create_quad(st=((0.1,0.2),(0.3,0.4))) expected_v = np.array([ [ 0.5, 0.5, 0., 0.3, 0.4], [-0.5, 0.5, 0., 0.1, 0.4], [-0.5,-0.5, 0., 0.1, 0.2], [ 0.5,-0.5, 0., 0.3, 0.2], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_quad_rgba(self): v, i = geometry.create_quad(rgba=True) expected_v = np.array([ [ 0.5, 0.5, 0., 1., 1., 1., 1.], [-0.5, 0.5, 0., 1., 1., 1., 1.], [-0.5,-0.5, 0., 1., 1., 1., 1.], [ 0.5,-0.5, 0., 1., 1., 1., 1.], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_quad_rgba_values(self): v, i = geometry.create_quad(rgba=((0.1,0.2,0.3,0.4),(0.5,0.6,0.7,0.8),(0.9,1.0,1.1,1.2),(1.3,1.4,1.5,1.6))) expected_v = np.array([ [ 0.5, 0.5, 0., .1, .2, .3, .4], [-0.5, 0.5, 0., .5, .6, .7, .8], [-0.5,-0.5, 0., .9, 1., 1.1, 1.2], [ 0.5,-0.5, 0., 1.3, 1.4, 1.5, 1.6], ]) expected_i = np.array([0,1,2,0,2,3]) self.assertTrue(np.allclose(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i)) def test_create_cube(self): v, i = geometry.create_cube() expected_v = np.array([ [ 0.5, 0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, -0.5, 0.5], [ 0.5, -0.5, 0.5], [ 0.5, 0.5, -0.5], [ 0.5, 0.5, 0.5], [ 0.5, -0.5, 0.5], [ 0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [ 0.5, 0.5, -0.5], [ 0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, -0.5, 0.5], [ 0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, 0.5, 0.5], [ 0.5, 0.5, 0.5], [ 0.5, -0.5, 0.5], [-0.5, -0.5, 0.5], [-0.5, -0.5, -0.5], [ 0.5, -0.5, -0.5] ]) expected_i = np.array([ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 ]) self.assertTrue(np.array_equal(v, expected_v), (v,)) self.assertTrue(np.array_equal(i, expected_i), (i,)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_integer.py000066400000000000000000000006001345610666600167500ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest from pyrr import integer class test_integer(unittest.TestCase): def test_import(self): import pyrr pyrr.integer from pyrr import integer def test_count_bits(self): i = 0b010101 self.assertEqual(integer.count_bits(i), 3) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_line.py000066400000000000000000000021501345610666600162440ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import line, ray class test_line(unittest.TestCase): def test_import(self): import pyrr pyrr.line from pyrr import line def test_create_zeros(self): result = line.create_zeros() self.assertTrue(np.allclose(result, [[0,0,0],[0,0,0]])) def test_create_from_points(self): result = line.create_from_points([-1.,0.,0.],[1.,0.,0.]) self.assertTrue(np.allclose(result, [[-1,0,0],[1,0,0]])) def test_create_from_ray(self): r = ray.create([0.,0.,0.], [1., 0.,0.]) result = line.create_from_ray(r) self.assertTrue(np.allclose(result, [[0,0,0],[1,0,0]])) def test_start(self): l = line.create_from_points([-1.,0.,0.],[1.,0.,0.]) result = line.start(l) self.assertTrue(np.allclose(result, [-1,0,0])) def test_end(self): l = line.create_from_points([-1.,0.,0.],[1.,0.,0.]) result = line.end(l) self.assertTrue(np.allclose(result, [1,0,0])) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_matrix33.py000066400000000000000000000216311345610666600167740ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import matrix33, quaternion, vector3 class test_matrix33(unittest.TestCase): # use wolfram alpha to get information on quaternion conversion values # be aware that wolfram lists it as w,x,y,z def test_import(self): import pyrr pyrr.matrix33 from pyrr import matrix33 def test_create_from_quaternion_unit(self): result = matrix33.create_from_quaternion([0.,0.,0.,1.]) np.testing.assert_almost_equal(result, np.eye(3), decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_x(self): result = matrix33.create_from_quaternion([1.,0.,0.,0.]) expected = [ [1.,0.,0.], [0.,-1.,0.], [0.,0.,-1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_y(self): result = matrix33.create_from_quaternion([0.,1.,0.,0.]) expected = [ [-1.,0.,0.], [0.,1.,0.], [0.,0.,-1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_z(self): result = matrix33.create_from_quaternion([0.,0.,1.,0.]) expected = [ [-1.,0.,0.], [0.,-1.,0.], [0.,0.,1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_rotation(self): result = matrix33.create_from_quaternion([.57735,.57735,.57735,0.]) expected = [ [-0.333333, 0.666667, 0.666667], [0.666667, -0.333333, 0.666667], [0.666667, 0.666667, -0.333333], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_equivalent(self): result = matrix33.create_from_quaternion(quaternion.create_from_x_rotation(0.5)) expected = matrix33.create_from_x_rotation(0.5) np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_inverse_equivalence(self): q = [5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17] result = matrix33.create_from_quaternion(quaternion.inverse(q)) expected = matrix33.inverse(matrix33.create_from_quaternion(q)) np.testing.assert_almost_equal(result, expected, decimal=5) q = quaternion.create_from_x_rotation(0.5) result = matrix33.create_from_inverse_of_quaternion(q) expected = matrix33.inverse(matrix33.create_from_quaternion(q)) np.testing.assert_almost_equal(result, expected, decimal=5) def test_create_from_inverse_of_quaternion(self): q = quaternion.create_from_x_rotation(np.pi / 2.0) result = matrix33.create_from_inverse_of_quaternion(q) self.assertTrue(np.allclose(result, matrix33.create_from_x_rotation(-np.pi / 2.0))) def test_create_identity(self): result = matrix33.create_identity() np.testing.assert_almost_equal(result, np.eye(3), decimal=5) self.assertTrue(result.dtype == np.float) def create_from_matrix44(self): m44 = np.arange((4,4)) result = matrix33.create_from_matrix44(m44) self.assertTrue(np.allclose(result, m44[:3][:3])) @unittest.skip('Not implemented') def test_create_from_eulers(self): # just call the function # TODO: check the result matrix33.create_from_eulers([1,2,3]) def test_create_from_axis_rotation(self): # wolfram alpha can be awesome sometimes result = matrix33.create_from_axis_rotation([0.57735, 0.57735, 0.57735],np.pi) np.testing.assert_almost_equal(result, matrix33.create_from_quaternion([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]), decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_axis_rotation_non_normalized(self): result = matrix33.create_from_axis_rotation([1.,1.,1.], np.pi) np.testing.assert_almost_equal(result, matrix33.create_from_quaternion([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]), decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_x_rotation(self): mat = matrix33.create_from_x_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.], mat), [1.,0.,0.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.], mat), [0.,0.,-1.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.], mat), [0.,1.,0.])) def test_create_from_y_rotation(self): mat = matrix33.create_from_y_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.], mat), [0.,0.,1.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.], mat), [0.,1.,0.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.], mat), [-1.,0.,0.])) def test_create_from_z_rotation(self): mat = matrix33.create_from_z_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.], mat), [0.,-1.,0.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.], mat), [1.,0.,0.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.], mat), [0.,0.,1.])) def test_create_from_scale(self): scale = np.array([ 2.0, 3.0, 4.0]) mat = matrix33.create_from_scale(scale) result = mat.diagonal() expected = scale self.assertTrue(np.array_equal(result, expected)) def test_create_from_quaternion_identity(self): quat = quaternion.create() result = matrix33.create_from_quaternion(quat) expected = np.eye(3) self.assertTrue(np.array_equal(result, expected)) def test_create_from_quaternion_rotated_x(self): quat = quaternion.create_from_x_rotation(np.pi) result = matrix33.create_from_quaternion(quat) expected = matrix33.create_from_x_rotation(np.pi) self.assertTrue(np.allclose(result, expected)) def test_create_from_quaternion_rotated_y(self): quat = quaternion.create_from_y_rotation(np.pi) result = matrix33.create_from_quaternion(quat) expected = matrix33.create_from_y_rotation(np.pi) self.assertTrue(np.allclose(result, expected)) def test_create_from_quaternion_rotated_z(self): quat = quaternion.create_from_z_rotation(np.pi) result = matrix33.create_from_quaternion(quat) expected = matrix33.create_from_z_rotation(np.pi) self.assertTrue(np.allclose(result, expected)) def test_apply_to_vector_identity(self): mat = matrix33.create_identity() vec = vector3.unit.x result = matrix33.apply_to_vector(mat, vec) expected = vec self.assertTrue(np.array_equal(result, expected)) def test_apply_to_vector_rotated_x(self): mat = matrix33.create_from_x_rotation(np.pi) vec = vector3.unit.y result = matrix33.apply_to_vector(mat, vec) expected = -vec self.assertTrue(np.allclose(result, expected)) def test_apply_to_vector_rotated_y(self): mat = matrix33.create_from_y_rotation(np.pi) vec = vector3.unit.x result = matrix33.apply_to_vector(mat, vec) expected = -vec self.assertTrue(np.allclose(result, expected)) def test_apply_to_vector_rotated_z(self): mat = matrix33.create_from_z_rotation(np.pi) vec = vector3.unit.x result = matrix33.apply_to_vector(mat, vec) expected = -vec self.assertTrue(np.allclose(result, expected)) def test_multiply_identity(self): m1 = matrix33.create_identity() m2 = matrix33.create_identity() result = matrix33.multiply(m1, m2) self.assertTrue(np.allclose(result, np.dot(m1,m2))) def test_multiply_rotation(self): m1 = matrix33.create_from_x_rotation(np.pi) m2 = matrix33.create_from_y_rotation(np.pi / 2.0) result = matrix33.multiply(m1, m2) self.assertTrue(np.allclose(result, np.dot(m1,m2))) def test_inverse(self): m = matrix33.create_from_y_rotation(np.pi) result = matrix33.inverse(m) self.assertTrue(np.allclose(result, matrix33.create_from_y_rotation(-np.pi))) def test_create_direction_scale(self): m = matrix33.create_direction_scale([0.,1.,0.], 0.5) v = np.array([ [1.,0.,0.], [1.,1.,1.], [10.,10.,10.] ]) result = np.array([ matrix33.apply_to_vector(m, v[0]), matrix33.apply_to_vector(m, v[1]), matrix33.apply_to_vector(m, v[2]), ]) expected = np.array([ [1.,0.,0.], [1.,.5,1.], [10.,5.,10.] ]) self.assertTrue(np.allclose(result, expected)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_matrix44.py000066400000000000000000000502071345610666600167770ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import matrix44, quaternion, vector3 class test_matrix44(unittest.TestCase): def test_import(self): import pyrr pyrr.matrix44 from pyrr import matrix44 def test_create_identity(self): result = matrix44.create_identity() np.testing.assert_almost_equal(result, np.eye(4), decimal=5) def test_create_from_quaternion_unit(self): result = matrix44.create_from_quaternion([0.,0.,0.,1.]) np.testing.assert_almost_equal(result, np.eye(4), decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_x(self): result = matrix44.create_from_quaternion([1.,0.,0.,0.]) expected = [ [1.,0.,0.,0.], [0.,-1.,0.,0.], [0.,0.,-1.,0.], [0.,0.,0.,1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_y(self): result = matrix44.create_from_quaternion([0.,1.,0.,0.]) expected = [ [-1.,0.,0.,0.], [0.,1.,0.,0.], [0.,0.,-1.,0.], [0.,0.,0.,1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_z(self): result = matrix44.create_from_quaternion([0.,0.,1.,0.]) expected = [ [-1.,0.,0.,0.], [0.,-1.,0.,0.], [0.,0.,1.,0.], [0.,0.,0.,1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_quaternion_rotation(self): result = matrix44.create_from_quaternion([.57735,.57735,.57735,0.]) expected = [ [-0.333333, 0.666667, 0.666667,0.], [0.666667, -0.333333, 0.666667,0.], [0.666667, 0.666667, -0.333333,0.], [0.,0.,0.,1.], ] np.testing.assert_almost_equal(result, expected, decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_axis_rotation(self): # wolfram alpha can be awesome sometimes result = matrix44.create_from_axis_rotation([0.57735, 0.57735, 0.57735],np.pi) np.testing.assert_almost_equal(result, matrix44.create_from_quaternion([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]), decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_axis_rotation_non_normalized(self): result = matrix44.create_from_axis_rotation([1.,1.,1.], np.pi) np.testing.assert_almost_equal(result, matrix44.create_from_quaternion([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]), decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_inverse_of_quaternion(self): q = quaternion.create_from_x_rotation(np.pi / 2.0) result = matrix44.create_from_inverse_of_quaternion(q) self.assertTrue(np.allclose(result, matrix44.create_from_x_rotation(-np.pi / 2.0))) def test_create_from_translation( self ): result = matrix44.create_from_translation([2.,3.,4.]) expected = np.eye(4) expected[3,:3] = [2.,3.,4.] np.testing.assert_almost_equal(result, expected, decimal=5) def test_create_from_scale( self ): result = matrix44.create_from_scale([2.,3.,4.]) np.testing.assert_almost_equal(result.diagonal()[:-1], [2.,3.,4.], decimal=5) def test_create_matrix33_view( self ): mat = matrix44.create_identity() result = matrix44.create_matrix33_view(mat) np.testing.assert_almost_equal(result, mat[:3,:3], decimal=5) mat[0,0] = 2. np.testing.assert_almost_equal(result, mat[:3,:3], decimal=5) def test_create_from_matrix33( self ): mat = np.array([ [1.,2.,3.], [3.,4.,5.], [6.,7.,8.] ]) result = matrix44.create_from_matrix33(mat) np.testing.assert_almost_equal(result[:3,:3], mat, decimal=5) orig = mat.copy() mat[0,0] = 2. np.testing.assert_almost_equal(result[:3,:3], orig, decimal=5) def test_create_perspective_projection_matrix_vector3(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) self.assertTrue(inside == (np.amax(np.absolute(p)) <= 1.), (inside, point, p)) m = matrix44.create_perspective_projection_matrix(90, 1024./768., 1., 10.) apply_test(m, np.array((0.,0.,0.)), False) apply_test(m, np.array((0.,0.,-.5)), False) apply_test(m, np.array((0.,0.,-1.)), True) apply_test(m, np.array((0.,0.,-2.)), True) apply_test(m, np.array((0.,0.,-9.)), True) apply_test(m, np.array((0.,0.,-11.)), False) apply_test(m, np.array((1.,1.,-5.)), True) def test_create_perspective_projection_matrix_dtype(self): m1 = matrix44.create_perspective_projection_matrix(90, 1024./768., 1., 10., dtype='float32') m2 = matrix44.create_perspective_projection_matrix(90, 1024./768., 1., 10., dtype='float64') self.assertEqual(m1.dtype, np.float32) self.assertEqual(m2.dtype, np.float64) def test_create_perspective_projection_matrix_vector4_inside(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) if np.allclose(p[3], 0.): self.assertFalse(inside) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) if np.allclose(p[3],0.): p[:] = [np.inf,np.inf,np.inf,np.inf] else: p[:3] /= p[3] self.assertTrue(inside == (np.amax(np.absolute(p[:3])) <= 1.), (inside, point, p)) m = matrix44.create_perspective_projection_matrix(90, 1024./768., 1., 10.) apply_test(m, np.array((0.,0.,0.,1.)), False) apply_test(m, np.array((0.,0.,-.5,1.)), False) apply_test(m, np.array((0.,0.,-1.,1.)), True) apply_test(m, np.array((0.,0.,-2.,1.)), True) apply_test(m, np.array((0.,0.,-9.,1.)), True) apply_test(m, np.array((0.,0.,-11.,1.)), False) apply_test(m, np.array((1.,1.,-5.,1.)), True) def test_create_orthogonal_projection_matrix_vector3(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) self.assertTrue(inside == (np.amax(np.absolute(p[:3])) <= 1.), (inside, point, p)) m = matrix44.create_orthogonal_projection_matrix(-1., 1., -1., 1., 1., 10.) # +Z apply_test(m, np.array((0.,0.,0.)), False) apply_test(m, np.array((0.,0.,1.)), False) # -Z but outside near, far apply_test(m, np.array((0.,0.,-.5)), False) apply_test(m, np.array((0.,0.,-11.)), False) apply_test(m, np.array((0.,0.,1.)), False) # Valid apply_test(m, np.array((0.,0.,-10.)), True) apply_test(m, np.array((0.,0.,-1.)), True) apply_test(m, np.array((0.,0.,-2.)), True) apply_test(m, np.array((0.,0.,-9.)), True) apply_test(m, np.array((-1.,-1.,-1.)), True) apply_test(m, np.array((-1.,-1.,-10.)), True) apply_test(m, np.array((1.,1.,-1.)), True) apply_test(m, np.array((1.,1.,-10.)), True) # Outside left, right, top, bottom apply_test(m, np.array((1.1,1.1,-1.)), False) apply_test(m, np.array((-1.1,-1.1,-1.)), False) apply_test(m, np.array((1.1,1.1,-10.)), False) apply_test(m, np.array((-1.1,-1.1,-10.)), False) def test_create_orthogonal_projection_matrix_vector4(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) if p[3] == 0.: self.assertFalse(inside) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) self.assertTrue(inside == (np.amax(np.absolute(p[:3])) <= 1.), (inside, point, p)) m = matrix44.create_orthogonal_projection_matrix(-1., 1., -1., 1., 1., 10.) # +Z apply_test(m, np.array((0.,0.,0.,1.)), False) apply_test(m, np.array((0.,0.,1.,1.)), False) # -Z but outside near, far apply_test(m, np.array((0.,0.,-.5,1.)), False) apply_test(m, np.array((0.,0.,-11.,1.)), False) apply_test(m, np.array((0.,0.,1.,1.)), False) # Valid apply_test(m, np.array((0.,0.,-10.,1.)), True) apply_test(m, np.array((0.,0.,-1.,1.)), True) apply_test(m, np.array((0.,0.,-2.,1.)), True) apply_test(m, np.array((0.,0.,-9.,1.)), True) apply_test(m, np.array((-1.,-1.,-1.,1.)), True) apply_test(m, np.array((-1.,-1.,-10.,1.)), True) apply_test(m, np.array((1.,1.,-1.,1.)), True) apply_test(m, np.array((1.,1.,-10.,1.)), True) # Outside left, right, top, bottom apply_test(m, np.array((1.1,1.1,-1.,1.)), False) apply_test(m, np.array((-1.1,-1.1,-1.,1.)), False) apply_test(m, np.array((1.1,1.1,-10.,1.)), False) apply_test(m, np.array((-1.1,-1.1,-10.,1.)), False) def create_perspective_projection_matrix_from_bounds_vector3(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) self.assertTrue(inside == (np.amax(np.absolute(p[:3])) <= 1.), (inside, point, p)) m = matrix44.create_perspective_projection_matrix_from_bounds(-1.,1.,-1.,1.,1.,10.) # +Z apply_test(m, np.array((0.,0.,0.)), False) apply_test(m, np.array((0.,0.,1.)), False) # -Z but outside near, far apply_test(m, np.array((0.,0.,-.5)), False) apply_test(m, np.array((0.,0.,-11.)), False) apply_test(m, np.array((0.,0.,1.)), False) # Valid apply_test(m, np.array((0.,0.,-10.)), True) apply_test(m, np.array((0.,0.,-1.)), True) apply_test(m, np.array((0.,0.,-2.)), True) apply_test(m, np.array((0.,0.,-9.)), True) apply_test(m, np.array((-1.,-1.,-1.)), True) apply_test(m, np.array((-1.,-1.,-10.)), True) apply_test(m, np.array((1.,1.,-1.)), True) apply_test(m, np.array((1.,1.,-10.)), True) # Outside left, right, top, bottom apply_test(m, np.array((1.1,1.1,-1.)), False) apply_test(m, np.array((-1.1,-1.1,-1.)), False) apply_test(m, np.array((1.1,1.1,-10.)), False) apply_test(m, np.array((-1.1,-1.1,-10.)), False) def create_perspective_projection_matrix_from_bounds_vector4(self): def apply_test(m, point, inside): p = matrix44.apply_to_vector(m, point) if p[3] == 0.: self.assertFalse(inside) # the values are now in clip space from (-1.,-1.,-1.) -> (1.,1.,1.) # to be inside = all(-1. < value < 1.) self.assertTrue(inside == (np.amax(np.absolute(p[:3])) <= 1.), (inside, point, p)) m = matrix44.create_perspective_projection_matrix_from_bounds(-1.,1.,-1.,1.,1.,10.) # +Z apply_test(m, np.array((0.,0.,0.,1.)), False) apply_test(m, np.array((0.,0.,1.,1.)), False) # -Z but outside near, far apply_test(m, np.array((0.,0.,-.5,1.)), False) apply_test(m, np.array((0.,0.,-11.,1.)), False) apply_test(m, np.array((0.,0.,1.,1.)), False) # Valid apply_test(m, np.array((0.,0.,-10.,1.)), True) apply_test(m, np.array((0.,0.,-1.,1.)), True) apply_test(m, np.array((0.,0.,-2.,1.)), True) apply_test(m, np.array((0.,0.,-9.,1.)), True) apply_test(m, np.array((-1.,-1.,-1.,1.)), True) apply_test(m, np.array((-1.,-1.,-10.,1.)), True) apply_test(m, np.array((1.,1.,-1.,1.)), True) apply_test(m, np.array((1.,1.,-10.,1.)), True) # Outside left, right, top, bottom apply_test(m, np.array((1.1,1.1,-1.,1.)), False) apply_test(m, np.array((-1.1,-1.1,-1.,1.)), False) apply_test(m, np.array((1.1,1.1,-10.,1.)), False) apply_test(m, np.array((-1.1,-1.1,-10.,1.)), False) def test_create_look_at_determinant(self): m = matrix44.create_look_at( np.array((300.0, 200.0, 100.0)), np.array((0.0, 0.0, 0.0)), np.array((0.0, 0.0, 1.0)), ) self.assertAlmostEqual(np.linalg.det(m), 1.0) def test_create_look_at(self): m = matrix44.create_look_at( np.array((300.0, 200.0, 100.0)), np.array((0.0, 0.0, 10.0)), np.array((0.0, 0.0, 1.0)), ) points = [ (-10.0, -10.0, 0.0, 1.0), (-10.0, 10.0, 0.0, 1.0), (10.0, -10.0, 0.0, 1.0), (10.0, 10.0, 0.0, 1.0), (-10.0, -10.0, 20.0, 1.0), (-10.0, 10.0, 20.0, 1.0), (10.0, -10.0, 20.0, 1.0), (10.0, 10.0, 20.0, 1.0), ] for point in points: x, y, z, w = matrix44.apply_to_vector(m, point) self.assertTrue(-20.0 < x and x < 20.0) self.assertTrue(-20.0 < y and y < 20.0) self.assertTrue(z < 0.0) self.assertAlmostEqual(w, 1.0) def test_create_look_at_2(self): m = matrix44.create_look_at( np.array((10.0, 0.0, 0.0)), np.array((-10.0, 0.0, 0.0)), np.array((0.0, 1.0, 0.0)), ) x, y, z, _ = matrix44.apply_to_vector(m, (1.0, 0.0, 0.0, 1.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, -9.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 1.0, 0.0, 1.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 1.0) self.assertAlmostEqual(z, -10.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 0.0, 1.0, 1.0)) self.assertAlmostEqual(x, -1.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, -10.0) def test_create_look_at_3(self): m = matrix44.create_look_at( np.array((10.0, 0.0, 0.0)), np.array((-10.0, 0.0, 0.0)), np.array((0.0, 1.0, 0.0)), ) x, y, z, _ = matrix44.apply_to_vector(m, (1.0, 0.0, 0.0, 0.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, 1.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 1.0, 0.0, 0.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 1.0) self.assertAlmostEqual(z, 0.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 0.0, 1.0, 0.0)) self.assertAlmostEqual(x, -1.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, 0.0) def test_create_look_at_4(self): m = matrix44.create_look_at( np.array((0.0, 0.0, 0.0)), np.array((0.0, 0.0, -1.0)), np.array((0.0, 1.0, 0.0)), ) x, y, z, _ = matrix44.apply_to_vector(m, (1.0, 0.0, 0.0, 0.0)) self.assertAlmostEqual(x, 1.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, 0.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 1.0, 0.0, 0.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 1.0) self.assertAlmostEqual(z, 0.0) x, y, z, _ = matrix44.apply_to_vector(m, (0.0, 0.0, 1.0, 0.0)) self.assertAlmostEqual(x, 0.0) self.assertAlmostEqual(y, 0.0) self.assertAlmostEqual(z, 1.0) def test_apply_to_vector_identity(self): mat = matrix44.create_identity() result = matrix44.apply_to_vector(mat, [1.,0.,0.]) np.testing.assert_almost_equal(result, [1.,0.,0.], decimal=5) def test_apply_to_vector_x_rotation(self): mat = matrix44.create_from_x_rotation(np.pi) result = matrix44.apply_to_vector(mat, [0.,1.,0.]) np.testing.assert_almost_equal(result, [0.,-1.,0.], decimal=5) def test_apply_to_vector_y_rotation(self): mat = matrix44.create_from_y_rotation(np.pi) result = matrix44.apply_to_vector(mat, [1.,0.,0.]) np.testing.assert_almost_equal(result, [-1.,0.,0.], decimal=5) def test_apply_to_vector_z_rotation(self): mat = matrix44.create_from_z_rotation(np.pi) result = matrix44.apply_to_vector(mat, [1.,0.,0.]) np.testing.assert_almost_equal(result, [-1.,0.,0.], decimal=5) def test_apply_to_vector_with_translation(self): mat = matrix44.create_from_translation([2.,3.,4.]) result = matrix44.apply_to_vector(mat, [1.,1.,1.]) np.testing.assert_almost_equal(result, [3.,4.,5.], decimal=5) @unittest.skip('Not implemented') def test_create_from_eulers(self): # just call the function # TODO: check the result matrix44.create_from_eulers([1,2,3]) def test_create_from_x_rotation(self): mat = matrix44.create_from_x_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.,1.], mat), [1.,0.,0.,1.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.,1.], mat), [0.,0.,-1.,1.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.,1.], mat), [0.,1.,0.,1.])) def test_create_from_y_rotation(self): mat = matrix44.create_from_y_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.,1.], mat), [0.,0.,1.,1.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.,1.], mat), [0.,1.,0.,1.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.,1.], mat), [-1.,0.,0.,1.])) def test_create_from_z_rotation(self): mat = matrix44.create_from_z_rotation(np.pi / 2.) self.assertTrue(np.allclose(np.dot([1.,0.,0.,1.], mat), [0.,-1.,0.,1.])) self.assertTrue(np.allclose(np.dot([0.,1.,0.,1.], mat), [1.,0.,0.,1.])) self.assertTrue(np.allclose(np.dot([0.,0.,1.,1.], mat), [0.,0.,1.,1.])) def test_multiply_identity(self): m1 = matrix44.create_identity() m2 = matrix44.create_identity() result = matrix44.multiply(m1, m2) self.assertTrue(np.allclose(result, np.dot(m1,m2))) def test_multiply_rotation(self): m1 = matrix44.create_from_x_rotation(np.pi) m2 = matrix44.create_from_y_rotation(np.pi / 2.0) result = matrix44.multiply(m1, m2) self.assertTrue(np.allclose(result, np.dot(m1,m2))) def test_inverse(self): m = matrix44.create_from_y_rotation(np.pi) result = matrix44.inverse(m) self.assertTrue(np.allclose(result, matrix44.create_from_y_rotation(-np.pi))) def test_decompose(self): # define expectations expected_scale = vector3.create(*[1, 1, 2], dtype='f4') expected_rotation = quaternion.create_from_y_rotation(np.pi, dtype='f4') expected_translation = vector3.create(*[10, 0, -5], dtype='f4') expected_model = np.array([ [-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -2, 0], [10, 0, -5, 1], ], dtype='f4') # compose matrix using Pyrr s = matrix44.create_from_scale(expected_scale, dtype='f4') r = matrix44.create_from_quaternion(expected_rotation, dtype='f4') t = matrix44.create_from_translation(expected_translation, dtype='f4') model = s.dot(r).dot(t) np.testing.assert_almost_equal(model, expected_model) self.assertTrue(model.dtype == expected_model.dtype) # decompose matrix scale, rotation, translation = matrix44.decompose(model) np.testing.assert_almost_equal(scale, expected_scale) self.assertTrue(scale.dtype == expected_scale.dtype) np.testing.assert_almost_equal(rotation, expected_rotation) self.assertTrue(rotation.dtype == expected_rotation.dtype) np.testing.assert_almost_equal(translation, expected_translation) self.assertTrue(translation.dtype == expected_translation.dtype) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_plane.py000066400000000000000000000064211345610666600164210ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import plane, vector class test_plane(unittest.TestCase): def test_import(self): import pyrr pyrr.plane from pyrr import plane def test_create(self): result = plane.create() self.assertTrue(np.allclose(result, [0,0,1,0])) result = plane.create([1.,0.,0.], 5.) self.assertTrue(np.allclose(result, [1.,0.,0.,5.])) def test_create_from_points(self): result = plane.create_from_points( [1., 0., 0.], [0., 1., 0.], [1., 1., 0.], ) self.assertTrue(np.allclose(result, [0.,0.,1.,0.])) self.assertTrue(np.allclose(plane.position(result), [0., 0., 0.])) result = plane.create_from_points( [1., 1., 0.], [1., 1., 1.], [0., 1., 1.], ) expected = plane.create([0.,1.,0.], 1.) self.assertTrue(np.allclose(result, expected)) self.assertTrue(np.allclose(plane.position(result), [0., 1., 0.])) def test_create_from_position(self): position = np.array([1.0, 0.0, 0.0]) normal = np.array([0.0, 3.0, 0.0]) result = plane.create_from_position(position, normal) self.assertTrue(np.allclose(result, [0., 1., 0., 0.])) p0 = position + [1., 0., 0.] p = position n = vector.normalise(normal) coplanar = p - p0 self.assertEqual(np.sum(n * coplanar), 0.) def test_create_xy(self): result = plane.create_xy() self.assertTrue(np.allclose(result, [0., 0., 1., 0.])) result = plane.create_xy(distance=2.) self.assertTrue(np.allclose(result, [0., 0., 1., 2.])) result = plane.create_xy(invert=True, distance=2.) self.assertTrue(np.allclose(result, [0., 0., -1., -2.])) def test_create_xz(self): result = plane.create_xz() self.assertTrue(np.allclose(result, [0., 1., 0., 0.])) result = plane.create_xz(distance=2.) self.assertTrue(np.allclose(result, [0., 1., 0., 2.])) result = plane.create_xz(invert=True, distance=2.) self.assertTrue(np.allclose(result, [0., -1., 0., -2.])) def test_create_yz(self): result = plane.create_yz() self.assertTrue(np.allclose(result, [1., 0., 0., 0.])) result = plane.create_yz(distance=2.) self.assertTrue(np.allclose(result, [1., 0., 0., 2.])) result = plane.create_yz(invert=True, distance=2.) print(result) self.assertTrue(np.allclose(result, [-1., 0., 0., -2.])) def test_invert_normal(self): p = np.array([1.0, 0.0, 0.0, 1.0]) result = plane.invert_normal(p) self.assertTrue(np.allclose(result, [-1.0, 0.0, 0.0, -1.0])) def test_position(self): p = plane.create_xz(distance=-5.) result = plane.position(p) self.assertTrue(np.allclose(result, [0.,-5.,0.])) p = plane.create_from_position(position=[0., 0., 1.], normal=[0., 0., 1.]) self.assertTrue(np.allclose(plane.position(p), [0., 0., 1.])) def test_normal(self): p = plane.create_xz(distance=5.) result = plane.normal(p) self.assertTrue(np.allclose(result, [0.,1.,0.])) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_quaternion.py000066400000000000000000000501541345610666600175110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import quaternion class test_quaternion(unittest.TestCase): # many of these values are taken from searches on wolfram alpha def test_import(self): import pyrr pyrr.quaternion from pyrr import quaternion def test_create(self): result = quaternion.create() np.testing.assert_almost_equal(result, [0., 0., 0., 1.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_parameters(self): result = quaternion.create(1.0, 2.0, 3.0, 4.0) np.testing.assert_almost_equal(result, [1.0, 2.0, 3.0, 4.0], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_x_rotation(self): # 180 degree turn around X axis q = quaternion.create_from_x_rotation(np.pi) self.assertTrue(np.allclose(q, [1., 0., 0., 0.])) # 90 degree rotation around X axis q = quaternion.create_from_x_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [np.sqrt(0.5), 0., 0., np.sqrt(0.5)])) # -90 degree rotation around X axis q = quaternion.create_from_x_rotation(-np.pi / 2.) self.assertTrue(np.allclose(q, [-np.sqrt(0.5), 0., 0., np.sqrt(0.5)])) def test_create_from_y_rotation(self): # 180 degree turn around Y axis q = quaternion.create_from_y_rotation(np.pi) self.assertTrue(np.allclose(q, [0., 1., 0., 0.])) # 90 degree rotation around Y axis q = quaternion.create_from_y_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [0., np.sqrt(0.5), 0., np.sqrt(0.5)])) # -90 degree rotation around Y axis q = quaternion.create_from_y_rotation(-np.pi / 2.) def test_create_from_z_rotation(self): # 180 degree turn around Z axis q = quaternion.create_from_z_rotation(np.pi) self.assertTrue(np.allclose(q, [0., 0., 1., 0.])) # 90 degree rotation around Z axis q = quaternion.create_from_z_rotation(np.pi / 2.) self.assertTrue(np.allclose(q, [0., 0., np.sqrt(0.5), np.sqrt(0.5)])) # -90 degree rotation around Z axis q = quaternion.create_from_z_rotation(-np.pi / 2.) def test_create_from_axis_rotation(self): # wolfram alpha can be awesome sometimes result = quaternion.create_from_axis_rotation([0.57735, 0.57735, 0.57735], np.pi) np.testing.assert_almost_equal(result, [5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17], decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_axis_rotation_non_normalized(self): result = quaternion.create_from_axis_rotation([1., 1., 1.], np.pi) np.testing.assert_almost_equal(result, [5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17], decimal=3) self.assertTrue(result.dtype == np.float) def test_create_from_axis(self): source = np.array([np.pi, np.pi, np.pi]) result = quaternion.create_from_axis(source) expected = np.array([0.2358916, 0.2358916, 0.2358916, -0.9127242]) np.testing.assert_almost_equal(result, expected) self.assertTrue(result.dtype == np.float) def test_create_from_matrix_unit(self): result = quaternion.create_from_matrix(np.eye(3)) np.testing.assert_almost_equal(result, [0., 0., 0., 1.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_matrix_x(self): result = quaternion.create_from_matrix([ [1., 0., 0.], [0., -1., 0.], [0., 0., -1.], ]) np.testing.assert_almost_equal(result, [1., 0., 0., 0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_matrix_y(self): result = quaternion.create_from_matrix([ [-1., 0., 0.], [0., 1., 0.], [0., 0., -1.], ]) np.testing.assert_almost_equal(result, [0., 1., 0., 0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_matrix_z(self): result = quaternion.create_from_matrix([ [-1., 0., 0.], [0., -1., 0.], [0., 0., 1.], ]) np.testing.assert_almost_equal(result, [0., 0., 1., 0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_eulers_identity(self): result = quaternion.create_from_eulers([0., 0., 0.]) np.testing.assert_equal(result, [0., 0., 0., 1.]) self.assertTrue(result.dtype == np.float) def test_create_from_eulers(self): result = quaternion.create_from_eulers([1.0, 2.0, 3.0]) np.testing.assert_almost_equal(result, [0.7549338, -0.2061492, 0.5015091, -0.3688714], decimal=5) self.assertTrue(result.dtype == np.float) @unittest.skip('Not implemented') def test_create_from_inverse_of_eulers(self): pass def test_cross(self): q1 = quaternion.create_from_x_rotation(np.pi / 2.0) q2 = quaternion.create_from_x_rotation(-np.pi / 2.0) result = quaternion.cross(q1, q2) np.testing.assert_almost_equal(result, quaternion.create(), decimal=5) def test_quaternion_slerp(self): sqrt2 = np.sqrt(2) / 2 identity = np.array([0.0, 0.0, 0.0, 1.0]) y90rot = np.array([0.0, sqrt2, 0.0, sqrt2]) y180rot = np.array([0.0, 1.0, 0.0, 0.0]) # Testing a == 0 # Must be id result = quaternion.slerp(identity, y90rot, 0.0) np.testing.assert_almost_equal(result, identity, decimal=4) # Testing a == 1 # Must be 90° rotation on Y : 0 0.7 0 0.7 result = quaternion.slerp(identity, y90rot, 1.0) np.testing.assert_almost_equal(result, y90rot, decimal=4) # Testing standard, easy case # Must be 45° rotation on Y : 0 0.38 0 0.92 y45rot1 = quaternion.slerp(identity, y90rot, 0.5) # Testing reverse case # Must be 45° rotation on Y : 0 0.38 0 0.92 y45rot2 = quaternion.slerp(y90rot, identity, 0.5) np.testing.assert_almost_equal(y45rot1, y45rot2, decimal=4) # Testing against full circle around the sphere instead of shortest path # Must be 45° rotation on Y # certainly not a 135° rotation # y45rot3 = quaternion.slerp(identity, quaternion.negate(y90rot), 0.5) y45rot3 = quaternion.slerp(identity, y90rot, 0.5) y45angle3 = quaternion.rotation_angle(y45rot3) np.testing.assert_almost_equal(y45angle3 * 180 / np.pi, 45, decimal=4) np.testing.assert_almost_equal(y45angle3, np.pi / 4, decimal=4) # # Same, but inverted # # Must also be 45° rotation on Y : 0 0.38 0 0.92 # # -0 -0.38 -0 -0.92 is ok too y45rot4 = quaternion.slerp(-y90rot, identity, 0.5) np.testing.assert_almost_equal(np.abs(y45rot4), y45rot2, decimal=4) # # Testing q1 = q2 # # Must be 90° rotation on Y : 0 0.7 0 0.7 y90rot3 = quaternion.slerp(y90rot, y90rot, 0.5); np.testing.assert_almost_equal(y90rot3, y90rot, decimal=4) # # Testing 180° rotation # # Must be 90° rotation on almost any axis that is on the XZ plane xz90rot = quaternion.slerp(identity, -y90rot, 0.5) xz90rot = quaternion.rotation_angle(xz90rot) np.testing.assert_almost_equal(xz90rot, np.pi / 4, decimal=4) def test_is_zero_length(self): result = quaternion.is_zero_length([1., 0., 0., 0.]) self.assertFalse(result) def test_is_zero_length_zero(self): result = quaternion.is_zero_length([0., 0., 0., 0.]) self.assertTrue(result) def test_is_non_zero_length(self): result = quaternion.is_non_zero_length([1., 0., 0., 0.]) self.assertTrue(result) def test_is_non_zero_length_zero(self): result = quaternion.is_non_zero_length([0., 0., 0., 0.]) self.assertFalse(result) def test_squared_length_identity(self): result = quaternion.squared_length([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_squared_length(self): result = quaternion.squared_length([1., 1., 1., 1.]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_squared_length_batch(self): result = quaternion.squared_length([ [0., 0., 0., 1.], [1., 1., 1., 1.], ]) np.testing.assert_almost_equal(result, [1., 4.], decimal=5) def test_length_identity(self): result = quaternion.length([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_length(self): result = quaternion.length([1., 1., 1., 1.]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_length_batch(self): result = quaternion.length([ [0., 0., 0., 1.], [1., 1., 1., 1.], ]) np.testing.assert_almost_equal(result, [1., 2.], decimal=5) def test_normalize_identity(self): # normalize an identity quaternion result = quaternion.normalize([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, [0., 0., 0., 1.], decimal=5) def test_normalize_non_identity(self): # normalize an identity quaternion result = quaternion.normalize([1., 2., 3., 4.]) np.testing.assert_almost_equal(result, [1. / np.sqrt(30.), np.sqrt(2. / 15.), np.sqrt(3. / 10.), 2. * np.sqrt(2. / 15.)], decimal=5) def test_normalize_batch(self): # normalize an identity quaternion result = quaternion.normalize([ [0., 0., 0., 1.], [1., 2., 3., 4.], ]) expected = [ [0., 0., 0., 1.], [1. / np.sqrt(30.), np.sqrt(2. / 15.), np.sqrt(3. / 10.), 2. * np.sqrt(2. / 15.)], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_rotation_angle(self): result = quaternion.rotation_angle([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]) np.testing.assert_almost_equal(result, np.pi, decimal=5) def test_rotation_axis(self): result = quaternion.rotation_axis([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]) np.testing.assert_almost_equal(result, [0.57735, 0.57735, 0.57735], decimal=5) def test_dot_adjacent(self): result = quaternion.dot([1., 0., 0., 0.], [0., 1., 0., 0.]) np.testing.assert_almost_equal(result, 0.0, decimal=5) def test_dot_parallel(self): result = quaternion.dot([0., 1., 0., 0.], [0., 1., 0., 0.]) np.testing.assert_almost_equal(result, 1.0, decimal=5) def test_dot_angle(self): result = quaternion.dot([.2, .2, 0., 0.], [2., -.2, 0., 0.]) np.testing.assert_almost_equal(result, 0.36, decimal=5) def test_dot_batch(self): result = quaternion.dot([ [1., 0., 0., 0.], [0., 1., 0., 0.], [.2, .2, 0., 0.] ], [ [0., 1., 0., 0.], [0., 1., 0., 0.], [2., -.2, 0., 0.] ]) expected = [0., 1., 0.36] np.testing.assert_almost_equal(result, expected, decimal=5) def test_conjugate(self): #result = quaternion.conjugate([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]) result = quaternion.conjugate([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, [0., 0., 0., 1.], decimal=5) def test_conjugate_rotation(self): result = quaternion.conjugate([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]) np.testing.assert_almost_equal(result, [-0.57735, -0.57735, -0.57735, 6.12323e-17], decimal=5) def test_exp(self): source = np.array([0, 0, 0, 1.0]) result = quaternion.exp(source) expected = np.array([0, 0, 0, np.exp(1)]) np.testing.assert_almost_equal(result, expected) source = quaternion.create_from_eulers([np.pi, 0, 0]) result = quaternion.exp(source) expected = np.array([0.84147098, 0, 0, 0.54030231]) np.testing.assert_almost_equal(result, expected) # Tests from the boost::math::quaternion source = np.array([4 * np.arctan(1), 0, 0, 0]) result = quaternion.exp(source) + [0, 0, 0, 1.0] result = np.linalg.norm(result) expected = 2 * np.finfo(result.dtype).eps np.testing.assert_almost_equal(result, expected) source = np.array([0, 4 * np.arctan(1), 0, 0]) result = quaternion.exp(source) + [0, 0, 0, 1.0] result = np.linalg.norm(result) expected = 2 * np.finfo(result.dtype).eps np.testing.assert_almost_equal(result, expected) source = np.array([0, 0, 4 * np.arctan(1), 0]) result = quaternion.exp(source) + [0, 0, 0, 1.0] result = np.linalg.norm(result) expected = 2 * np.finfo(result.dtype).eps np.testing.assert_almost_equal(result, expected) @unittest.skip('Not implemented') def test_power(self): pass def test_inverse(self): result = quaternion.inverse([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, [0., 0., 0., 1.], decimal=5) def test_inverse_rotation(self): result = quaternion.inverse([5.77350000e-01, 5.77350000e-01, 5.77350000e-01, 6.12323400e-17]) np.testing.assert_almost_equal(result, [-0.577351, -0.577351, -0.577351, 6.12324e-17], decimal=5) def test_inverse_non_unit(self): q = [1, 2, 3, 4] result = quaternion.inverse(q) expected = quaternion.conjugate(q) / quaternion.length(q) np.testing.assert_almost_equal(result, expected, decimal=5) def test_negate_unit(self): result = quaternion.negate([0., 0., 0., 1.]) np.testing.assert_almost_equal(result, [0., 0., 0., -1.], decimal=5) def test_negate(self): result = quaternion.negate([1., 2., 3., 4.]) np.testing.assert_almost_equal(result, [-1., -2., -3., -4.], decimal=5) def test_apply_to_vector_unit_x(self): result = quaternion.apply_to_vector([0., 0., 0., 1.], [1., 0., 0.]) np.testing.assert_almost_equal(result, [1., 0., 0.], decimal=5) def test_apply_to_vector_x(self): # 180 degree turn around X axis q = quaternion.create_from_x_rotation(np.pi) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 0.,-1.])) # 90 degree rotation around X axis q = quaternion.create_from_x_rotation(np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0., 0., 1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0.,-1., 0.])) # -90 degree rotation around X axis q = quaternion.create_from_x_rotation(-np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0., 0.,-1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 1., 0.])) def test_apply_to_vector_y(self): # 180 degree turn around Y axis q = quaternion.create_from_y_rotation(np.pi) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 0.,-1.])) # 90 degree rotation around Y axis q = quaternion.create_from_y_rotation(np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [0., 0.,-1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [1., 0., 0.])) # -90 degree rotation around Y axis q = quaternion.create_from_y_rotation(-np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [0., 0., 1.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [-1., 0., 0.])) def test_apply_to_vector_z(self): # 180 degree turn around Z axis q = quaternion.create_from_z_rotation(np.pi) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 0., 1.])) # 90 degree rotation around Z axis q = quaternion.create_from_z_rotation(np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [0., 1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [-1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 0., 1.])) # -90 degree rotation around Z axis q = quaternion.create_from_z_rotation(-np.pi / 2.) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [1., 0., 0.]), [0.,-1., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 1., 0.]), [1., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 1.]), [0., 0., 1.])) def test_apply_to_vector_non_unit(self): q = quaternion.create_from_x_rotation(np.pi) # zero length self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 0.]), [0., 0., 0.])) # >1 length self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [2., 0., 0.]), [2., 0., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 2., 0.]), [0.,-2., 0.])) self.assertTrue(np.allclose(quaternion.apply_to_vector(q, [0., 0., 2.]), [0., 0.,-2.])) def test_identity(self): # https://en.wikipedia.org/wiki/Quaternion i = quaternion.create(1., 0., 0., 0.) j = quaternion.create(0., 1., 0., 0.) k = quaternion.create(0., 0., 1., 0.) one = quaternion.create(0., 0., 0., 1.) # i * 1 = i # j * 1 = j # k * 1 = k # 1 * i = i # 1 * j = j # 1 * k = k i1 = quaternion.cross(i, one) j1 = quaternion.cross(j, one) k1 = quaternion.cross(k, one) _1i = quaternion.cross(one, i) _1j = quaternion.cross(one, j) _1k = quaternion.cross(one, k) self.assertTrue(np.allclose(i1, _1i, i)) self.assertTrue(np.allclose(j1, _1j, j)) self.assertTrue(np.allclose(k1, _1k, k)) # result = -1 ii = quaternion.cross(i, i) kk = quaternion.cross(k, k) jj = quaternion.cross(j, j) ijk = quaternion.cross(quaternion.cross(i, j), k) self.assertTrue(np.allclose(ii, -one)) self.assertTrue(np.allclose(jj, -one)) self.assertTrue(np.allclose(kk, -one)) self.assertTrue(np.allclose(ijk, -one)) # ij = k # ji = -k # jk = i # kj = -i # ki = j # ik = -j ij = quaternion.cross(i, j) ji = quaternion.cross(j, i) jk = quaternion.cross(j, k) kj = quaternion.cross(k, j) ki = quaternion.cross(k, i) ik = quaternion.cross(i, k) self.assertTrue(np.allclose(ij, k)) self.assertTrue(np.allclose(ji, -k)) self.assertTrue(np.allclose(jk, i)) self.assertTrue(np.allclose(kj, -i)) self.assertTrue(np.allclose(ki, j)) self.assertTrue(np.allclose(ik, -j)) # -k = ijkk = ij(k^2) = ij(-1) ijkk = quaternion.cross(quaternion.cross(ij, k), k) ijk2 = quaternion.cross(ij, quaternion.cross(k, k)) ij_m1 = quaternion.cross(ij, -one) self.assertTrue(np.allclose(ijkk, ijk2)) self.assertTrue(np.allclose(ijk2, ij_m1)) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_ray.py000066400000000000000000000027611345610666600161200ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import ray class test_ray(unittest.TestCase): def test_import(self): import pyrr pyrr.ray from pyrr import ray def test_create(self): result = ray.create([0.,0.,0.],[0.,0.,1.]) np.testing.assert_almost_equal(result, [[0.,0.,0.],[0.,0.,1.]], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_dtype(self): result = ray.create([0,0,0],[0,0,1], dtype=np.int) np.testing.assert_almost_equal(result, [[0,0,0],[0,0,1]], decimal=5) self.assertTrue(result.dtype == np.int) def test_create_from_line(self): result = ray.create_from_line([ [0.,10.,0.], [10.,10.,0.] ]) np.testing.assert_almost_equal(result, [[0.,10.,0.],[1.,0.,0.]], decimal=5) self.assertTrue(result.dtype == np.float) def test_invert(self): result = ray.invert([[0.,10.,0.],[1.,0.,0.]]) np.testing.assert_almost_equal(result, [[0.,10.,0.],[-1.,0.,0.]], decimal=5) self.assertTrue(result.dtype == np.float) def test_position(self): result = ray.position([[0.,10.,0.],[1.,0.,0.]]) np.testing.assert_almost_equal(result, [0.,10.,0.], decimal=5) def test_direction(self): result = ray.direction([[0.,10.,0.],[1.,0.,0.]]) np.testing.assert_almost_equal(result, [1.,0.,0.], decimal=5) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_rectangle.py000066400000000000000000000112201345610666600172570ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import rectangle class test_rectangle(unittest.TestCase): def test_import(self): import pyrr pyrr.rectangle from pyrr import rectangle def test_create(self): result = rectangle.create() np.testing.assert_almost_equal(result, [[0,0],[1,1]], decimal=5) def test_create_dtype(self): result = rectangle.create(dtype=np.float) np.testing.assert_almost_equal(result, [[0.,0.],[1.,1.]], decimal=5) def test_create_zeros(self): result = rectangle.create_zeros() np.testing.assert_almost_equal(result, [[0,0],[0,0]], decimal=5) def test_create_from_bounds(self): result = rectangle.create_from_bounds(-1, 1, -2, 2) np.testing.assert_almost_equal(result, [[-1,-2],[2,4]], decimal=5) def test_bounds(self): rect = rectangle.create_from_bounds(-1, 1, -2, 2) result = rectangle.bounds(rect) np.testing.assert_almost_equal(result, (-1,1,-2,2), decimal=5) def test_scale_by_vector(self): result = rectangle.scale_by_vector([[-1.,-2.],[2.,4.]], [2.,3.]) np.testing.assert_almost_equal(result, [[-2.,-6.],[4.,12.]], decimal=5) def test_scale_by_vector3(self): result = rectangle.scale_by_vector([[-1.,-2.],[2.,4.]], [2.,3.,4.]) np.testing.assert_almost_equal(result, [[-2.,-6.],[4.,12.]], decimal=5) def test_right(self): result = rectangle.right([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_right_negative(self): result = rectangle.right([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_left(self): result = rectangle.left([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_left_negative(self): result = rectangle.left([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, -2., decimal=5) def test_top(self): result = rectangle.top([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 6., decimal=5) def test_top_negative(self): result = rectangle.top([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_bottom(self): result = rectangle.bottom([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_bottom_negative(self): result = rectangle.bottom([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, -2., decimal=5) def test_x(self): result = rectangle.x([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_x_negative(self): result = rectangle.x([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 1., decimal=5) def test_y(self): result = rectangle.y([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_y_negative(self): result = rectangle.y([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_width(self): result = rectangle.width([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 3., decimal=5) def test_width_negative(self): result = rectangle.width([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, -3., decimal=5) def test_height(self): result = rectangle.height([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_height_negative(self): result = rectangle.height([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, -4., decimal=5) def test_abs_height(self): result = rectangle.abs_height([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_abs_height_negative(self): result = rectangle.abs_height([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_abs_width(self): result = rectangle.abs_width([[1.,2.],[3.,4.]]) np.testing.assert_almost_equal(result, 3., decimal=5) def test_abs_width_negative(self): result = rectangle.abs_width([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, 3., decimal=5) def test_position(self): result = rectangle.position([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, [1.,2.], decimal=5) def test_size(self): result = rectangle.size([[1.,2.],[-3.,-4.]]) np.testing.assert_almost_equal(result, [-3.,-4.], decimal=5) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_sphere.py000066400000000000000000000017311345610666600166070ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import sphere class test_sphere(unittest.TestCase): def test_import(self): import pyrr pyrr.sphere from pyrr import sphere def test_create_from_points(self): # the biggest should be 5,5,5 result = sphere.create_from_points([ [ 0.0, 0.0, 0.0 ], [ 5.0, 5.0, 5.0 ], [ 0.0, 0.0, 5.0 ], [-5.0, 0.0, 0.0 ], ]) # centred around 0,0,0 # with MAX LENGTH as radius np.testing.assert_almost_equal(result, [0.,0.,0., 8.66025], decimal=5) def test_position(self): result = sphere.position([1.,2.,3.,4.]) np.testing.assert_almost_equal(result, [1.,2.,3.], decimal=5) def test_radius(self): result = sphere.radius([1.,2.,3.,4.]) np.testing.assert_almost_equal(result, 4., decimal=5) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_trig.py000066400000000000000000000013321345610666600162630ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest from pyrr import trig class test_trig(unittest.TestCase): def test_import(self): import pyrr pyrr.trig from pyrr import trig def test_aspec_ratio(self): self.assertEqual(trig.aspect_ratio(1920, 1080), 1920./1080.) @unittest.skip('Need a test here') def test_calculate_fov(self): pass @unittest.skip('Need a test here') def test_calculate_zoom(self): pass @unittest.skip('Need a test here') def test_calculate_height(self): pass @unittest.skip('Need a test here') def test_calculate_plane_size(self): pass if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_vector.py000066400000000000000000000074071345610666600166310ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import vector, vector3, vector4 class test_vector(unittest.TestCase): def test_import(self): import pyrr pyrr.vector from pyrr import vector def test_normalize_single_vector(self): result = vector3.normalize([1.,1.,1.]) np.testing.assert_almost_equal(result, [0.57735, 0.57735, 0.57735], decimal=5) def test_normalize_batch(self): result = vector3.normalize([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ [0.57735, 0.57735, 0.57735], [-0.57735,-0.57735,-0.57735], [0., 0.274721, 0.961524], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_squared_length_single_vector(self): result = vector3.squared_length([1.,1.,1.]) np.testing.assert_almost_equal(result, 3., decimal=5) def test_squared_length_batch(self): result = vector3.squared_length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ 3., 3., 53., ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_length_vector3(self): result = vector3.length([1.,1.,1.]) np.testing.assert_almost_equal(result, 1.73205, decimal=5) def test_length_vector4(self): result = vector3.length([1.,1.,1.,1.]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_length_vector3_batch(self): result = vector3.length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ 1.73205, 1.73205, 7.28011, ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length_vector3(self): result = vector3.set_length([1.,1.,1.],2.) expected = [1.15470,1.15470,1.15470] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length_vector4(self): result = vector4.set_length([1.,1.,1.,1.],2.) expected = [1.,1.,1.,1.] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length_batch_vector(self): result = vector3.set_length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ], 2.0) expected = [ [1.15470,1.15470,1.15470], [-1.15470,-1.15470,-1.15470], [0.,0.54944,1.92304], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_dot_adjacent(self): result = vector3.dot([1.,0.,0.], [0.,1.,0.]) np.testing.assert_almost_equal(result, 0.0, decimal=5) def test_dot_parallel(self): result = vector3.dot([0.,1.,0.], [0.,1.,0.]) np.testing.assert_almost_equal(result, 1.0, decimal=5) def test_dot_angle(self): result = vector3.dot([.2,.2,0.], [2.,-.2,0.]) np.testing.assert_almost_equal(result, 0.36, decimal=5) def test_dot_batch(self): result = vector3.dot([ [1.,0.,0.], [0.,1.,0.], [.2,.2,0.] ],[ [0.,1.,0.], [0.,1.,0.], [2.,-.2,0.] ]) expected = [0.,1.,0.36] np.testing.assert_almost_equal(result, expected, decimal=5) def test_interoplation(self): result = vector3.interpolate([0.,0.,0.], [1.,1.,1.], 0.5) np.testing.assert_almost_equal(result, [.5,.5,.5], decimal=5) result = vector3.interpolate([0.,0.,0.], [2.,2.,2.], 0.5) np.testing.assert_almost_equal(result, [1.,1.,1.], decimal=5) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_vector3.py000066400000000000000000000222551345610666600167120ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import vector3 class test_vector3(unittest.TestCase): def test_import(self): import pyrr pyrr.vector3 from pyrr import vector3 def test_create(self): result = vector3.create() np.testing.assert_almost_equal(result, [0.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_values(self): result = vector3.create(1., 2., 3., dtype=np.float32) np.testing.assert_almost_equal(result, [1.,2.,3.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_list(self): with self.assertRaises(ValueError): vector3.create([1., 2., 3.]) def test_create_unit_length_x(self): result = vector3.create_unit_length_x() np.testing.assert_almost_equal(result, [1.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_x_dtype(self): result = vector3.create_unit_length_x(dtype=np.float32) np.testing.assert_almost_equal(result, [1.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_unit_length_y(self): result = vector3.create_unit_length_y() np.testing.assert_almost_equal(result, [0.,1.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_y_dtype(self): result = vector3.create_unit_length_y(dtype=np.float32) np.testing.assert_almost_equal(result, [0.,1.,0.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_unit_length_z(self): result = vector3.create_unit_length_z() np.testing.assert_almost_equal(result, [0.,0.,1.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_z_dtype(self): result = vector3.create_unit_length_z(dtype=np.float32) np.testing.assert_almost_equal(result, [0.,0.,1.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_from_vector4(self): v4 = [1., 2., 3., 4.] result = vector3.create_from_vector4(v4) v, w = result np.testing.assert_almost_equal(v, [1.,2.,3.], decimal=5) np.testing.assert_almost_equal(w, 4., decimal=5) def test_create_from_matrix44_translation(self): mat = np.array([ [1.,2.,3.,4.,], [5.,6.,7.,8.,], [9.,10.,11.,12.,], [13.,14.,15.,16.,], ]) result = vector3.create_from_matrix44_translation(mat) np.testing.assert_almost_equal(result, [13.,14.,15.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_matrix44_translation_dtype_matches(self): mat = np.array([ [1.,2.,3.,4.,], [5.,6.,7.,8.,], [9.,10.,11.,12.,], [13.,14.,15.,16.,], ], dtype=np.float32) result = vector3.create_from_matrix44_translation(mat) np.testing.assert_almost_equal(result, [13.,14.,15.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_normalize_single_vector(self): result = vector3.normalize([1.,1.,1.]) np.testing.assert_almost_equal(result, [0.57735, 0.57735, 0.57735], decimal=5) def test_normalize_batch(self): result = vector3.normalize([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ [0.57735, 0.57735, 0.57735], [-0.57735,-0.57735,-0.57735], [0., 0.274721, 0.961524], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_generate_normals(self): vertices = np.array([ [2.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 2.0, 0.0], [2.0, 2.0, 0.0] ]) index = np.array([ [0, 2, 1], [0, 3, 2], ]) v1, v2, v3 = np.rollaxis(vertices[index], axis=1) result = vector3.generate_normals(v1, v2, v3) expected = np.array([ [0., 0., 1.], [0., 0., 1.] ]) np.testing.assert_array_equal(result, expected) def test_generate_normals_unnormalized(self): vertices = np.array([ [2.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 2.0, 0.0], [2.0, 2.0, 0.0] ]) index = np.array([ [0, 2, 1], [0, 3, 2], ]) v1, v2, v3 = np.rollaxis(vertices[index], axis=1) result = vector3.generate_normals(v1, v2, v3, normalize_result=False) expected = np.array([ [0., 0., 4.], [0., 0., 4.] ]) np.testing.assert_array_equal(result, expected) def test_generate_vertex_normals(self): vertices = np.array([ [1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0] ]) index = np.array([ [0, 2, 1], [0, 3, 2], ]) result = vector3.generate_vertex_normals(vertices, index) expected = np.array([ [0., 0., 1.], [0., 0., 1.], [0., 0., 1.], [0., 0., 1.] ]) np.testing.assert_array_equal(result, expected) def test_generate_vertex_normals_unnormalized(self): vertices = np.array([ [1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0] ]) index = np.array([ [0, 2, 1], [0, 3, 2], ]) result = vector3.generate_vertex_normals( vertices, index, normalize_result=False) expected = np.array([ [0., 0., 2.], [0., 0., 1.], [0., 0., 2.], [0., 0., 1.] ]) np.testing.assert_array_equal(result, expected) def test_squared_length_single_vector(self): result = vector3.squared_length([1.,1.,1.]) np.testing.assert_almost_equal(result, 3., decimal=5) def test_squared_length_batch(self): result = vector3.squared_length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ 3., 3., 53., ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_length(self): result = vector3.length([1.,1.,1.]) np.testing.assert_almost_equal(result, 1.73205, decimal=5) def test_length_batch(self): result = vector3.length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ]) expected = [ 1.73205, 1.73205, 7.28011, ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length(self): result = vector3.set_length([1.,1.,1.],2.) expected = [1.15470,1.15470,1.15470] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length_batch_vector(self): result = vector3.set_length([ [1.,1.,1.], [-1.,-1.,-1.], [0.,2.,7.], ], 2.0) expected = [ [1.15470,1.15470,1.15470], [-1.15470,-1.15470,-1.15470], [0.,0.54944,1.92304], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_dot_adjacent(self): result = vector3.dot([1.,0.,0.], [0.,1.,0.]) np.testing.assert_almost_equal(result, 0.0, decimal=5) def test_dot_parallel(self): result = vector3.dot([0.,1.,0.], [0.,1.,0.]) np.testing.assert_almost_equal(result, 1.0, decimal=5) def test_dot_angle(self): result = vector3.dot([.2,.2,0.], [2.,-.2,0.]) np.testing.assert_almost_equal(result, 0.36, decimal=5) def test_dot_batch(self): result = vector3.dot([ [1.,0.,0.], [0.,1.,0.], [.2,.2,0.] ],[ [0.,1.,0.], [0.,1.,0.], [2.,-.2,0.] ]) expected = [ 0., 1., 0.36 ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_cross_single_vector(self): result = vector3.cross([1.,0.,0.], [0.,1.,0.]) np.testing.assert_almost_equal(result, [0.,0.,1.], decimal=5) def test_cross_coincident(self): result = vector3.cross([1.,0.,0.], [1.,0.,0.]) np.testing.assert_almost_equal(result, [0.,0.,0.], decimal=5) def test_cross_batch(self): result = vector3.cross([ [1.,0.,0.], [0.,0.,1.] ],[ [0.,1.,0.], [0.,1.,0.], ]) expected = [ [0.,0.,1.], [-1.,0.,0.], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_interoplation( self ): result = vector3.interpolate([0.,0.,0.], [1.,1.,1.], 0.5) np.testing.assert_almost_equal(result, [.5,.5,.5], decimal=5) result = vector3.interpolate([0.,0.,0.], [2.,2.,2.], 0.5) np.testing.assert_almost_equal(result, [1.,1.,1.], decimal=5) if __name__ == '__main__': unittest.main() Pyrr-0.10.3/tests/test_vector4.py000066400000000000000000000144371345610666600167160ustar00rootroot00000000000000try: import unittest2 as unittest except: import unittest import numpy as np from pyrr import vector4 class test_vector4(unittest.TestCase): def test_import(self): import pyrr pyrr.vector4 from pyrr import vector4 def test_create(self): result = vector4.create() np.testing.assert_almost_equal(result, [0.,0.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_values(self): result = vector4.create(1.,2.,3.,4., dtype=np.float32) np.testing.assert_almost_equal(result, [1.,2.,3.,4.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_list(self): with self.assertRaises(ValueError): vector4.create([1., 2., 3., 4.]) def test_create_unit_length_x(self): result = vector4.create_unit_length_x() np.testing.assert_almost_equal(result, [1.,0.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_x_dtype(self): result = vector4.create_unit_length_x(dtype=np.float32) np.testing.assert_almost_equal(result, [1.,0.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_unit_length_y(self): result = vector4.create_unit_length_y() np.testing.assert_almost_equal(result, [0.,1.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_y_dtype(self): result = vector4.create_unit_length_y(dtype=np.float32) np.testing.assert_almost_equal(result, [0.,1.,0.,0.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_unit_length_z(self): result = vector4.create_unit_length_z() np.testing.assert_almost_equal(result, [0.,0.,1.,0.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_z_dtype(self): result = vector4.create_unit_length_z(dtype=np.float32) np.testing.assert_almost_equal(result, [0.,0.,1.,0.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_unit_length_w(self): result = vector4.create_unit_length_w() np.testing.assert_almost_equal(result, [0.,0.,0.,1.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_unit_length_w_dtype(self): result = vector4.create_unit_length_w(dtype=np.float32) np.testing.assert_almost_equal(result, [0.,0.,0.,1.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_create_from_matrix44_translation(self): mat = np.array([ [1.,2.,3.,4.,], [5.,6.,7.,8.,], [9.,10.,11.,12.,], [13.,14.,15.,16.,], ]) result = vector4.create_from_matrix44_translation(mat) np.testing.assert_almost_equal(result, [13.,14.,15.,16.], decimal=5) self.assertTrue(result.dtype == np.float) def test_create_from_matrix44_translation_dtype_matches(self): mat = np.array([ [1.,2.,3.,4.,], [5.,6.,7.,8.,], [9.,10.,11.,12.,], [13.,14.,15.,16.,], ], dtype=np.float32) result = vector4.create_from_matrix44_translation(mat) np.testing.assert_almost_equal(result, [13.,14.,15.,16.], decimal=5) self.assertTrue(result.dtype == np.float32) def test_normalize_single_vector(self): result = vector4.normalize([1.,1.,1.,1.]) np.testing.assert_almost_equal(result, [0.5, 0.5, 0.5, 0.5], decimal=5) def test_normalize_batch(self): result = vector4.normalize([ [1.,1.,1.,1.], [-1.,-1.,-1.,1.], [0.,2.,7.,1.], ]) expected = [ [0.5, 0.5, 0.5, 0.5], [-0.5,-0.5,-0.5, 0.5], [0., 0.27216553, 0.95257934, 0.13608276], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_squared_length_single_vector(self): result = vector4.squared_length([1.,1.,1.,1.]) np.testing.assert_almost_equal(result, 4., decimal=5) def test_squared_length_batch(self): result = vector4.squared_length([ [1.,1.,1.,1.], [-1.,-1.,-1.,1.], [0.,2.,7.,1.], ]) expected = [ 4., 4., 54., ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_length(self): result = vector4.length([1.,1.,1.,1.]) np.testing.assert_almost_equal(result, 2., decimal=5) def test_length_batch(self): result = vector4.length([ [1.,1.,1.,1.], [-1.,-1.,-1.,1.], [0.,2.,7.,1.], ]) expected = [ 2., 2., 7.34846923, ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length(self): result = vector4.set_length([1.,1.,1.,1.],3.) expected = [1.5,1.5,1.5,1.5] np.testing.assert_almost_equal(result, expected, decimal=5) def test_set_length_batch_vector(self): result = vector4.set_length([ [1.,1.,1.,1.], [-1.,-1.,-1.,1.], [0.,2.,7.,1.], ], 2.0) expected = [ [1.,1.,1.,1.], [-1.,-1.,-1.,1.], [0., 0.54433105, 1.90515869, 0.27216553], ] np.testing.assert_almost_equal(result, expected, decimal=5) def test_dot_adjacent(self): result = vector4.dot([1.,0.,0.,1.], [0.,1.,0.,1.]) np.testing.assert_almost_equal(result, 1.0, decimal=5) def test_dot_parallel(self): result = vector4.dot([0.,1.,0.,1.], [0.,1.,0.,1.]) np.testing.assert_almost_equal(result, 2.0, decimal=5) def test_dot_angle(self): result = vector4.dot([.2,.2,0.,1.], [2.,-.2,0.,1.]) np.testing.assert_almost_equal(result, 1.359999, decimal=5) def test_dot_batch(self): result = vector4.dot([ [1.,0.,0.,1.], [0.,1.,0.,1.], [.2,.2,0.,1.] ],[ [0.,1.,0.,1.], [0.,1.,0.,1.], [2.,-.2,0.,1.] ]) expected = [ 1., 2., 1.36 ] np.testing.assert_almost_equal(result, expected, decimal=5) if __name__ == '__main__': unittest.main()