diff --git a/src/Imath/ImathFrustum.h b/src/Imath/ImathFrustum.h index 90337467..a504ab38 100644 --- a/src/Imath/ImathFrustum.h +++ b/src/Imath/ImathFrustum.h @@ -39,6 +39,8 @@ IMATH_INTERNAL_NAMESPACE_HEADER_ENTER template class IMATH_EXPORT_TEMPLATE_TYPE Frustum { public: + using value_type = T; + /// @{ /// @name Constructors and Assignment /// diff --git a/src/Imath/ImathMatrix.h b/src/Imath/ImathMatrix.h index d00bcd30..f261ef1d 100644 --- a/src/Imath/ImathMatrix.h +++ b/src/Imath/ImathMatrix.h @@ -800,6 +800,8 @@ template class IMATH_EXPORT_TEMPLATE_TYPE Matrix33 template class IMATH_EXPORT_TEMPLATE_TYPE Matrix44 { public: + using value_type = T; + /// @{ /// @name Direct access to elements diff --git a/src/pybind11/PyBindImath/CMakeLists.txt b/src/pybind11/PyBindImath/CMakeLists.txt index 511abb86..06e5403f 100644 --- a/src/pybind11/PyBindImath/CMakeLists.txt +++ b/src/pybind11/PyBindImath/CMakeLists.txt @@ -13,9 +13,10 @@ set(PYBINDIMATH_SOURCES PyBindImathVec.cpp PyBindImathPlane.cpp PyBindImathLine.cpp - + PyBindImathMatrix44.cpp + # PyBindImathEuler build Error pybind test - # PyBindImathFrustum build failing + PyBindImathFrustum.cpp ) set(PYBINDIMATH_HEADERS @@ -69,12 +70,12 @@ endif() if (IMATH_INSTALL) # module - + install(TARGETS ${PYBINDIMATH_MODULE} DESTINATION ${PYTHON_INSTALL_DIR} COMPONENT python) # shared library - install(TARGETS ${PYBINDIMATH_LIBRARY} + install(TARGETS ${PYBINDIMATH_LIBRARY} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/src/pybind11/PyBindImath/PyBindImath.h b/src/pybind11/PyBindImath/PyBindImath.h index 047a297b..3d2420ac 100644 --- a/src/pybind11/PyBindImath/PyBindImath.h +++ b/src/pybind11/PyBindImath/PyBindImath.h @@ -21,8 +21,10 @@ PYBINDIMATH_EXPORT void register_imath_vec(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_box(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_plane(pybind11::module& m); PYBINDIMATH_EXPORT void register_imath_line(pybind11::module& m); +PYBINDIMATH_EXPORT void register_imath_matrix(pybind11::module& m); + // PYBINDIMATH_EXPORT void register_imath_euler(pybind11::module& m) -// PYBINDIMATH_EXPORT void register_imath_frustum(pybind11::module& m) +PYBINDIMATH_EXPORT void register_imath_frustum(pybind11::module& m); } #endif diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum b/src/pybind11/PyBindImath/PyBindImathFrustum deleted file mode 100644 index d4029eba..00000000 --- a/src/pybind11/PyBindImath/PyBindImathFrustum +++ /dev/null @@ -1,55 +0,0 @@ -#include "PyBindImath.h" -#include -#include -#include - -namespace PyBindImath { - -template -void register_frustum(pybind11::module& m, const char *name) -{ - pybind11::class_ c(m, name); - c.def(pybind11::init<>(), "Uninitialized by default") - .def(pybind11::init(), pybind11::arg("frustum"), "Copy constructor") - .def(pybind11::init(), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Initialize with basic frustum properties") - - .def_readwrite("nearPlane", &T::nearPlane, "The near clipping plane") - .def_readwrite("farPlane", &T::farPlane, "The far clipping plane") - .def_readwrite("fovx", &T::fovx, "The field of view in x direction") - .def_readwrite("aspect", &T::aspect, "The aspect ratio") - - .def("set", pybind11::overload_cast(&T::set), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Set frustum properties") - .def("projectionMatrix", &T::projectionMatrix, "Returns the projection matrix of the frustum") - .def("transform", &T::transform, pybind11::arg("matrix"), "Applies a transformation matrix to the frustum") - .def("intersects", [](T& self, const V& point) { - bool result = self.intersects(point); - return result; - }, pybind11::arg("point"), "Determines if the point is inside the frustum") - - .def("screenToWorld", [](T& self, const V& screenPoint) { - V worldPoint; - self.screenToWorld(screenPoint, worldPoint); - return worldPoint; - }, pybind11::arg("screenPoint"), "Convert a screen space point to world space") - - .def("worldToScreen", [](T& self, const V& worldPoint) { - V screenPoint; - self.worldToScreen(worldPoint, screenPoint); - return screenPoint; - }, pybind11::arg("worldPoint"), "Convert a world space point to screen space") - - .def("__str__", [](const T &obj) { - std::stringstream ss; - ss << obj; - return ss.str(); - }); -} - -void register_imath_frustum(pybind11::module &m) -{ - register_frustum(m, "Frustumf"); - register_frustum(m, "Frustumd"); -} - -} - diff --git a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp index bcfd2616..8a03f119 100644 --- a/src/pybind11/PyBindImath/PyBindImathFrustum.cpp +++ b/src/pybind11/PyBindImath/PyBindImathFrustum.cpp @@ -1,59 +1,219 @@ -// -// SPDX-License-Identifier: BSD-3-Clause -// Copyright Contributors to the OpenEXR Project. -// - #include "PyBindImath.h" #include -#include -#include - namespace PyBindImath { +namespace py = pybind11; +namespace { + +template +struct GetClassName {}; -template -void register_frustum(pybind11::module& m, const char *name) +template <> +struct GetClassName { + static constexpr const char* value = "Frustumf"; +}; + +template <> +struct GetClassName { + static constexpr const char* value = "Frustumd"; +}; + +template +void register_Frustum(py::module& m, const char *name) { - pybind11::class_ c(m, name); - c.def(pybind11::init<>(), "Uninitialized by default") - .def(pybind11::init(), pybind11::arg("frustum"), "Copy constructor") - .def(pybind11::init(), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Initialize with basic frustum properties") - - .def_readwrite("nearPlane", &T::nearPlane, "The near clipping plane") - .def_readwrite("farPlane", &T::farPlane, "The far clipping plane") - .def_readwrite("fovx", &T::fovx, "The field of view in x direction") - .def_readwrite("aspect", &T::aspect, "The aspect ratio") - - .def("set", pybind11::overload_cast(&T::set), pybind11::arg("nearPlane"), pybind11::arg("farPlane"), pybind11::arg("fovx"), pybind11::arg("aspect"), "Set frustum properties") - .def("projectionMatrix", &T::projectionMatrix, "Returns the projection matrix of the frustum") - .def("transform", &T::transform, pybind11::arg("matrix"), "Applies a transformation matrix to the frustum") - .def("intersects", [](T& self, const V& point) { - bool result = self.intersects(point); - return result; - }, pybind11::arg("point"), "Determines if the point is inside the frustum") + py::class_(m, name) + .def(py::init<>(), "Frustum() default construction") + .def(py::init(), py::arg("frustum"), "Copy constructor") + .def(py::init(), "Frustum(nearPlane,farPlane,left,right,top,bottom,ortho) construction") + .def(py::init(), "Frustum(nearPlane,farPlane,fovx,fovy,aspect) construction") + + .def("set", py::overload_cast(&F::set), + "F.set(nearPlane, farPlane, left, right, top, bottom, " + "[ortho])\n" + "F.set(nearPlane, farPlane, fovx, fovy, aspect) " + " -- sets the entire state of " + "frustum F as specified. Only one of " + "fovx or fovy may be non-zero.") + .def("set", py::overload_cast(&F::set)) + + .def("modifyNearAndFar", &F::modifyNearAndFar, + "F.modifyNearAndFar(nearPlane, farPlane) -- modifies " + "the already-valid frustum F as specified") + + .def("setOrthographic", &F::setOrthographic, + "F.setOrthographic(b) -- modifies the " + "already-valid frustum F to be orthographic " + "or not") + + .def("nearPlane", &F::nearPlane, + "F.nearPlane() -- returns the coordinate of the " + "near clipping plane of frustum F") + + .def("farPlane", &F::farPlane, + "F.farPlane() -- returns the coordinate of the " + "far clipping plane of frustum F") + + // The following two functions provide backwards compatibility + // with the previous API for this class. + + .def("near", &F::nearPlane, + "F.near() -- returns the coordinate of the " + "near clipping plane of frustum F") - .def("screenToWorld", [](T& self, const V& screenPoint) { - V worldPoint; - self.screenToWorld(screenPoint, worldPoint); - return worldPoint; - }, pybind11::arg("screenPoint"), "Convert a screen space point to world space") + .def("far", &F::farPlane, + "F.far() -- returns the coordinate of the " + "far clipping plane of frustum F") + + .def("left", &F::left, + "F.left() -- returns the left coordinate of " + "the near clipping window of frustum F") + + .def("right", &F::right, + "F.right() -- returns the right coordinate of " + "the near clipping window of frustum F") + + .def("top", &F::top, + "F.top() -- returns the top coordinate of " + "the near clipping window of frustum F") + + .def("bottom", &F::bottom, + "F.bottom() -- returns the bottom coordinate " + "of the near clipping window of frustum F") + + .def("orthographic", &F::orthographic, + "F.orthographic() -- returns whether frustum " + "F is orthographic or not") + + .def("planes", [](F const& self, Imath::Plane3* planes) -> void + { + self.planes(planes); + }) + .def("planes", [](F const& self, Imath::Plane3 *p, Imath::Matrix44 const& m) -> void + { + self.planes(p, m); + }) + .def("planes", [](F const& self, Imath::Matrix44 const& m) + { + Imath::Plane3 p[6]; + self.planes(p, m); + return py::make_tuple(p[0], p[1], p[2], p[3], p[4], p[5]); + }) + .def("planes", [](F const& self) + { + Imath::Plane3 p[6]; + self.planes(p); - .def("worldToScreen", [](T& self, const V& worldPoint) { - V screenPoint; - self.worldToScreen(worldPoint, screenPoint); - return screenPoint; - }, pybind11::arg("worldPoint"), "Convert a world space point to screen space") + return py::make_tuple(p[0],p[1],p[2],p[3],p[4],p[5]); + }) + + .def("fovx", &F::fovx, + "F.fovx() -- derives and returns the " + "x field of view (in radians) for frustum F") + + .def("fovy", &F::fovy, + "F.fovy() -- derives and returns the " + "y field of view (in radians) for frustum F") + + .def("aspect", &F::aspect, + "F.aspect() -- derives and returns the " + "aspect ratio for frustum F") + + .def("projectionMatrix", &F::projectionMatrix, + "F.projectionMatrix() -- derives and returns " + "the projection matrix for frustum F") + + .def("window", &F::window, + "F.window(l,r,b,t) -- takes a rectangle in " + "the screen space (i.e., -1 <= l <= r <= 1, " + "-1 <= b <= t <= 1) of F and returns a new " + "Frustum whose near clipping-plane window " + "is that rectangle in local space") + + .def("projectScreenToRay", &F::projectScreenToRay, + "F.projectScreenToRay(V) -- returns a Line3 " + "through V, a V2 point in screen space") + + .def("projectScreenToRay", [](F const& self, py::sequence const& seq) -> Imath::Line3 { + if(seq.size() != 2) { + throw std::invalid_argument ( "projectScreenToRay expects a sequence of length 2"); + } + + Imath::Vec2 const point{py::cast(seq[0]), py::cast(seq[1])}; + return self.projectScreenToRay(point); + }) + + .def("projectPointToScreen", &F::projectPointToScreen, + "F.projectPointToScreen(V) -- returns the " + "projection of V3 V into screen space") + + .def("projectPointToScreen", [](F const& self, py::sequence const& seq) -> Imath::Vec2 { + if(seq.size() != 3) { + throw std::invalid_argument ( "projectPointToScreen expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.projectPointToScreen(point); + }) - .def("__str__", [](const T &obj) { - std::stringstream ss; - ss << obj; - return ss.str(); - }); + .def("ZToDepth", &F::ZToDepth, + "F.ZToDepth(z, zMin, zMax) -- returns the " + "depth (Z in the local space of the " + "frustum F) corresponding to z (a result of " + "transformation by F's projection matrix) " + "after normalizing z to be between zMin " + "and zMax") + + .def("normalizedZToDepth", &F::normalizedZToDepth, + "F.normalizedZToDepth(z) -- returns the " + "depth (Z in the local space of the " + "frustum F) corresponding to z (a result of " + "transformation by F's projection matrix), " + "which is assumed to have been normalized " + "to [-1, 1]") + + .def("DepthToZ", &F::DepthToZ, + "F.DepthToZ(depth, zMin, zMax) -- converts " + "depth (Z in the local space of the frustum " + "F) to z (a result of transformation by F's " + "projection matrix) which is normalized to " + "[zMin, zMax]") + + .def("worldRadius", &F::worldRadius, + "F.worldRadius(V, r) -- returns the radius " + "in F's local space corresponding to the " + "point V and radius r in screen space") + + .def("worldRadius", [](F const& self, py::sequence const& seq, T radius) -> T { + if(seq.size() != 3) { + throw std::invalid_argument ( "worldRadius expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.worldRadius(point, radius); + }) + + .def("screenRadius", &F::screenRadius, + "F.screenRadius(V, r) -- returns the radius " + "in screen space corresponding to " + "the point V and radius r in F's local " + "space") + + .def("screenRadius", [](F const& self, py::sequence const& seq, T radius) -> T{ + if(seq.size() != 3) { + throw std::invalid_argument ("screenRadius expects a sequence of length 3"); + } + + Imath::Vec3 const point{py::cast(seq[0]), py::cast(seq[1]), py::cast(seq[2])}; + return self.screenRadius(point, radius); + }) + ; } -void register_imath_frustum(pybind11::module &m) +} // namespace + +void register_imath_frustum(py::module &m) { - register_frustum(m, "Frustumf"); - register_frustum(m, "Frustumd"); + register_Frustum(m, "Frustumf"); + register_Frustum(m, "Frustumd"); } -} +} // PyBindImath diff --git a/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp new file mode 100644 index 00000000..faabe8e3 --- /dev/null +++ b/src/pybind11/PyBindImath/PyBindImathMatrix44.cpp @@ -0,0 +1,104 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenEXR Project. +// + +#include "PyBindImath.h" +#include + +#include + +namespace PyBindImath { +namespace py = pybind11; + +namespace { + +template +struct GetClassName {}; + +template <> +struct GetClassName { + static constexpr const char* value = "Matrix44f"; +}; + +template <> +struct GetClassName { + static constexpr const char* value = "Matrix44d"; +}; + +template +struct MatrixRow { + explicit MatrixRow(T *data) : _data(data) {} + T& operator [] (std::size_t i) { return _data[i]; } + T*_data; + + static void register_class(py::module& m, std::string const& rowName) + { + // no default constructor + py::class_(m, rowName.c_str()) + .def("__len__", []{return SIZE;}) + .def("__getitem__", [](MatrixRow const& self, std::size_t index) -> T + { + // TODO: Check data boundaries + return self._data[index]; + }) + .def("__setitem__", [](MatrixRow& self, std::size_t index, T const& value) + { + // TODO: Check data boundaries + self._data[index] = value; + }) + ; + } +}; + +template +std::string Matrix44_repr(M const& v) { + std::ostringstream oss; + + oss.precision(9); + oss << std::fixed; + + oss << GetClassName::value << "((" + << v[0][0] << ", " << v[0][1] << ", " << v[0][2] << ", " << v[0][3] << "), (" + << v[1][0] << ", " << v[1][1] << ", " << v[1][2] << ", " << v[1][3] << "), (" + << v[2][0] << ", " << v[2][1] << ", " << v[2][2] << ", " << v[2][3] << "), (" + << v[3][0] << ", " << v[3][1] << ", " << v[3][2] << ", " << v[3][3] << "))"; + + return oss.str(); +} + +template > +void register_Matrix44(py::module& m) +{ + // TODO: Finish implementation of Matrix class + + std::string const matrixName = GetClassName::value; + + // Register MatrixRow + constexpr char const* rowSuffix = "Row"; + R::register_class(m, matrixName + rowSuffix); + + // Register Matrix type + py::class_(m, matrixName.c_str()) + .def(py::init<>()) + .def("__getitem__", [](M& self, std::size_t index) -> R { + T* data = self[index]; + return R{data}; + }, py::arg("i"), + "Access element at the given index.") + // set item is not required since MatrixRow is returned for set and get operations + .def("__repr__", &Matrix44_repr) + ; +} + +} // namespace + +void register_imath_matrix(py::module& m) +{ + // TODO: M22 and M33 + + register_Matrix44(m); + register_Matrix44(m); +} + +} // PyBindImath \ No newline at end of file diff --git a/src/pybind11/PyBindImath/pybindimathmodule.cpp b/src/pybind11/PyBindImath/pybindimathmodule.cpp index 79f6b2f7..884456f4 100644 --- a/src/pybind11/PyBindImath/pybindimathmodule.cpp +++ b/src/pybind11/PyBindImath/pybindimathmodule.cpp @@ -15,8 +15,9 @@ PYBIND11_MODULE(pybindimath, m) PyBindImath::register_imath_box(m); PyBindImath::register_imath_plane(m); PyBindImath::register_imath_line(m); + PyBindImath::register_imath_matrix(m); // PyBindImath::register_imath_euler(m) - // PyBindImath::register_imath_frustum(m) + PyBindImath::register_imath_frustum(m); // diff --git a/src/pybind11/test/test_frustum.py b/src/pybind11/test/test_frustum.py new file mode 100644 index 00000000..7485f72e --- /dev/null +++ b/src/pybind11/test/test_frustum.py @@ -0,0 +1,43 @@ +import sys +import os +import pytest + +import pybindimath + +@pytest.mark.parametrize("Frustum", [pybindimath.Frustumf, pybindimath.Frustumd]) +def test_frustum(Frustum): + + # The following tests do not check for the correctness of the arguments, + # but if bindings work correctly + + f = Frustum() + assert Frustum(f) is not None + f = Frustum(1, 100, 0, 1, 2, 3.0, False) + assert f.near() == pytest.approx(1.0) + assert f.far() == pytest.approx(100.0) + assert f.left() == pytest.approx(0.0) + assert f.right() == pytest.approx(1.0) + assert f.top() == pytest.approx(2.0) + assert f.bottom() == pytest.approx(3.0) + assert f.orthographic() is False + + f = Frustum(10, 1000, 0.5, 0.5, 1.0) + assert f.near() == pytest.approx(10.0) + assert f.far() == pytest.approx(1000.0) + assert f.fovx() == pytest.approx(0.5) + assert f.fovy() == pytest.approx(0.5) + assert f.aspect() == pytest.approx(1.0) + + f.modifyNearAndFar(20, 2000.0) + assert f.near() == pytest.approx(20.0) + assert f.far() == pytest.approx(2000.0) + + m = f.projectionMatrix() + assert m is not None + assert m[0][0] is not None + + assert f.projectPointToScreen((1, 2, 3)) is not None + with pytest.raises(ValueError) as e: + f.projectPointToScreen((1, 2)) + + assert str(e.value) == "projectPointToScreen expects a sequence of length 3"