//------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        internal Matrix3D GetProjectionMatrix(double aspectRatio, double zn, double zf)
        {
            double fov = M3DUtil.DegreesToRadians(FieldOfView);

            // Note: h and w are 1/2 of the inverse of the width/height ratios:
            //
            //  h = 1/(heightDepthRatio) * (1/2)
            //  w = 1/(widthDepthRatio) * (1/2)
            //
            // Computation for h is a bit different than what you will find in
            // D3DXMatrixPerspectiveFovRH because we have a horizontal rather
            // than vertical FoV.

            double halfWidthDepthRatio = Math.Tan(fov / 2);
            double h = aspectRatio / halfWidthDepthRatio;
            double w = 1 / halfWidthDepthRatio;

            double m22 = zf != Double.PositiveInfinity ? zf / (zn - zf) : -1;
            double m32 = zn * m22;

            return(new Matrix3D(
                       w, 0, 0, 0,
                       0, h, 0, 0,
                       0, 0, m22, -1,
                       0, 0, m32, 0));
        }
        internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment)
        {
            // The camera may be animating.  Take a snapshot of the current value
            // and get the property values we need. (Window OS #992662)
            Point3D     position      = Position;
            Vector3D    lookDirection = LookDirection;
            Vector3D    upDirection   = UpDirection;
            Transform3D transform     = Transform;
            double      zn            = NearPlaneDistance;
            double      zf            = FarPlaneDistance;
            double      fov           = M3DUtil.DegreesToRadians(FieldOfView);

            //
            //  Compute rayParameters
            //

            // Find the point on the projection plane in post-projective space where
            // the viewport maps to a 2x2 square from (-1,1)-(1,-1).
            Point np = M3DUtil.GetNormalizedPoint(p, viewSize);

            // Note: h and w are 1/2 of the inverse of the width/height ratios:
            //
            //  h = 1/(heightDepthRatio) * (1/2)
            //  w = 1/(widthDepthRatio) * (1/2)
            //
            // Computation for h is a bit different than what you will find in
            // D3DXMatrixPerspectiveFovRH because we have a horizontal rather
            // than vertical FoV.
            double aspectRatio         = M3DUtil.GetAspectRatio(viewSize);
            double halfWidthDepthRatio = Math.Tan(fov / 2);
            double h = aspectRatio / halfWidthDepthRatio;
            double w = 1 / halfWidthDepthRatio;

            // To get from projective space to camera space we apply the
            // width/height ratios to find our normalized point at 1 unit
            // in front of the camera.  (1 is convenient, but has no other
            // special significance.) See note above about the construction
            // of w and h.
            Vector3D rayDirection = new Vector3D(np.X / w, np.Y / h, -1);

            // Apply the inverse of the view matrix to our rayDirection vector
            // to convert it from camera to world space.
            //
            // NOTE: Because our construction of the ray assumes that the
            //       viewMatrix translates the position to the origin we pass
            //       null for the Camera.Transform below and account for it
            //       later.

            Matrix3D viewMatrix = CreateViewMatrix(/* trasform = */ null, ref position, ref lookDirection, ref upDirection);
            Matrix3D invView    = viewMatrix;

            invView.Invert();
            invView.MultiplyVector(ref rayDirection);

            // The we have the ray direction, now we need the origin.  The camera's
            // position would work except that we would intersect geometry between
            // the camera plane and the near plane so instead we must find the
            // point on the project plane where the ray (position, rayDirection)
            // intersect (Windows OS #1005064):
            //
            //                     | _.>       p = camera position
            //                rd  _+"          ld = camera look direction
            //                 .-" |ro         pp = projection plane
            //             _.-"    |           rd = ray direction
            //         p +"--------+--->       ro = desired ray origin on pp
            //                ld   |
            //                     pp
            //
            // Above we constructed the direction such that it's length projects to
            // 1 unit on the lookDirection vector.
            //
            //
            //                rd  _.>
            //                 .-"        rd = unnormalized rayDirection
            //             _.-"           ld = normalized lookDirection (length = 1)
            //           -"--------->
            //                 ld
            //
            // So to find the desired rayOrigin on the projection plane we simply do:
            Point3D rayOrigin = position + zn * rayDirection;

            rayDirection.Normalize();

            // Account for the Camera.Transform we ignored during ray construction above.
            if (transform != null && transform != Transform3D.Identity)
            {
                Matrix3D m = transform.Value;
                m.MultiplyPoint(ref rayOrigin);
                m.MultiplyVector(ref rayDirection);

                PrependInverseTransform(m, ref viewMatrix);
            }

            RayHitTestParameters rayParameters = new RayHitTestParameters(rayOrigin, rayDirection);

            //
            //  Compute HitTestProjectionMatrix
            //

            Matrix3D projectionMatrix = GetProjectionMatrix(aspectRatio, zn, zf);

            // The projectionMatrix takes camera-space 3D points into normalized clip
            // space.

            // The viewportMatrix will take normalized clip space into
            // viewport coordinates, with an additional 2D translation
            // to put the ray at the rayOrigin.
            Matrix3D viewportMatrix = new Matrix3D();

            viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0));
            viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1));
            viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0));

            // `First world-to-camera, then camera's projection, then normalized clip space to viewport.
            rayParameters.HitTestProjectionMatrix =
                viewMatrix *
                projectionMatrix *
                viewportMatrix;

            //
            // Perspective camera doesn't allow negative NearPlanes, so there's
            // not much point in adjusting the ray origin. Hence, the
            // distanceAdjustment remains 0.
            //
            distanceAdjustment = 0.0;

            return(rayParameters);
        }