@@ -727,6 +727,101 @@ def normal(
727727 "position / sqrt((_x / r)**2 + (_y / r)**2 + 1) / r" ,
728728 )
729729
730+ def intercept (
731+ self ,
732+ rays : optika .rays .AbstractRayVectorArray ,
733+ ) -> optika .rays .RayVectorArray :
734+ r"""
735+ Compute the intercept of a ray with this parabolic surface.
736+
737+ Parameters
738+ ----------
739+ rays
740+ The rays to find the intercept for.
741+
742+ Notes
743+ -----
744+
745+ The equation for a paraboloid is
746+
747+ .. math::
748+
749+ z(x, y) = \frac{x^2 + y^2}{4 f},
750+
751+ where :math:`x`, :math:`y`, and :math:`z` are points on the paraboloid
752+ and :math:`f` is the focal length of the paraboloid.
753+ The equation for a line is
754+
755+ .. math::
756+
757+ \mathbf{x} = \mathbf{o} + d \mathbf{u},
758+
759+ where :math:`\mathbf{o}` is the starting point of the line,
760+ :math:`\mathbf{u}` is a unit vector pointing in the direction of the line,
761+ and :math:`d` is the distance from the origin to the intercept with
762+ the paraboloid.
763+ Combining these equations gives
764+
765+ .. math::
766+
767+ o_z + d u_z = \frac{(o_x + d u_x)^2 + (o_y + d u_y)^2}{4 f},
768+
769+ which can then be solved for :math:`d` using the quadratic equation,
770+
771+ .. math::
772+
773+ d = \frac{-o_x u_x - o_y u_y + 2 f u_z
774+ - \text{sgn}(f u_z) \sqrt{-o_y^2 u_x^2 - o_x^2 u_y^2 + 2 o_y u_y (o_x u_x - 2 f u_z)
775+ + 4 f (o_z (u_x^2 + u_y^2) - o_x u_x u_z + f u_z^2}
776+ }{u_x^2 + u_y^2}.
777+
778+ If the line is parallel to the :math:`z` axis, then the above equation
779+ is singular and we need to solve the corresponding linear equation to find
780+
781+ .. math::
782+
783+ d = \frac{o_x^2 + o_y^2 - 4 f o_z}{4 f u_z}.
784+ """
785+
786+ if self .transformation is not None :
787+ rays = self .transformation .inverse (rays )
788+
789+ f = self .focal_length
790+
791+ unit = f .unit
792+
793+ f = f .value # noqa: F841
794+
795+ o = rays .position .to (unit ).value
796+
797+ u = rays .direction .value
798+
799+ ox = o .x # noqa: F841
800+ oy = o .y # noqa: F841
801+ oz = o .z # noqa: F841
802+
803+ ux = u .x # noqa: F841
804+ uy = u .y # noqa: F841
805+ uz = u .z # noqa: F841
806+
807+ position = na .numexpr .evaluate (
808+ "o + u * where("
809+ " (ux**2 + uy**2) > 1e-10,"
810+ " (-ox * ux - oy * uy + 2 * f * uz - sign(f * uz) * sqrt("
811+ " -(oy * ux)**2 - (ox * uy)**2 + 2 * oy * uy * (ox * ux - 2 * f * uz)"
812+ " + 4 * f * (oz * (ux**2 + uy**2) - ox * ux * uz + f * uz**2)"
813+ " )) / (ux**2 + uy**2),"
814+ " (ox**2 + oy**2 - 4 * f * oz) / (4 * f * uz),"
815+ ")"
816+ )
817+
818+ result = rays .replace (position = position << unit )
819+
820+ if self .transformation is not None :
821+ result = self .transformation (result )
822+
823+ return result
824+
730825
731826@dataclasses .dataclass (eq = False , repr = False )
732827class ToroidalSag (
0 commit comments