/****************************************************************************
 * apps/graphics/traveler/src/trv_raycast.c
 * This file contains the low-level ray casting logic
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included files
 ****************************************************************************/

#include "trv_types.h"
#include "trv_world.h"
#include "trv_doors.h"
#include "trv_plane.h"
#include "trv_bitmaps.h"
#include "trv_trigtbl.h"
#include "trv_rayrend.h"
#include "trv_rayprune.h"
#include "trv_raycast.h"

/****************************************************************************
 * Compilation switches
 ****************************************************************************/

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* The following switch enables view correction logic. */

#define ENABLE_VIEW_CORRECTION 1

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static void trv_ray_xcaster14(FAR struct trv_raycast_s *result);
static void trv_ray_xcaster23(FAR struct trv_raycast_s *result);
static void trv_ray_ycaster12(FAR struct trv_raycast_s *result);
static void trv_ray_ycaster34(FAR struct trv_raycast_s *result);
static void trv_ray_zcasteru(FAR struct trv_raycast_s *result);
static void trv_ray_zcasterl(FAR struct trv_raycast_s *result);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* The following are the tangent and the cotangent of the pitch angle
 * adjusted for the viewing yaw angle so that the view is correct for the
 * "fish eye" effect which results from the projection of the polar ray cast
 * onto the flat display
 */

static int32_t g_adj_tanpitch;
static int32_t g_adj_cotpitch;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: trv_ray_xcaster14
 *
 * Description:
 *   This function casts a ray along the Y-Axis looking at points of
 *   intersection on the X-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "right" half of the view, then the X ray cast will
 *   proceed in a positive along the X axis and that all possible X-axis
 *   intersections will occur to at the "left" of a cell.
 *
 *   NOTE:  The X-Ray caster must run first because it initializes a
 *   data structure needed by both the Y and Z ray casters.
 *
 ****************************************************************************/

static void trv_ray_xcaster14(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current X plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t relx;             /* Relative position of the X plane */
  trv_coord_t absy;             /* Absolute Y position at relx given yaw */
  trv_coord_t absz;             /* Absolute Z position at relx given pitch */
  trv_coord_t lastrelx1 = -1;   /* Last relative X position processed */
  trv_coord_t lastrelx2 = -1;   /* Last relative X position processed */
  int32_t dydx;                 /* Rate of change of Y wrt X (double) */
  int32_t dzdx;                 /* Rate of change of Z wrt X (double) */

  /* At a viewing angle of 270 degrees, no intersections with the g_ray_xplanes
   * are possible!
   */

  if (g_camera.yaw == ANGLE_270)
    {
      return;
    }

  /* Pre-calculate the rate of change of Y and Z with respect to X */
  /* The tangent is equal to the rate of change of Y with respect to the
   * X-axis.  The tangent is stored at double the "normal" scaling.
   */

  dydx = TAN(g_camera.yaw);

  /* Determine the rate of change of the Z with respect to X. The tangent is
   * "double" precision; the secant is "double" precision.  dzdx will be
   * retained as "double" precision.
   */

  dzdx = qTOd(g_adj_tanpitch * ABS(g_sec_table[g_camera.yaw]));

  /* Look at every rectangle lying in the X plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_xplane.head; list; list = list->flink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "beyond" the current camera
       * position
       */

      if (rect->plane > g_camera.x)
        {
          /* get the X distance to the plane */

          relx = rect->plane - g_camera.x;

#if 0
          /* g_ray_xplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (relx > result->xdist)
            {
              return;
            }
#endif

          /* Calculate the Y position at this relative X position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

          if (relx != lastrelx1)
            {
              int32_t deltay;  /* Scale == "triple" */

              /* The dydx is stored at double the"normal" scaling -- so
               * deltay is "triple" precision
               */

              deltay    = dydx * ((int32_t) relx);
              absy      = tTOs(deltay) + g_camera.y; /* back to "single" */
              lastrelx1 = relx;
            }

          /* Check if this Y position intersects the rectangle */

          if (absy >= rect->hstart && absy <= rect->hend)
            {
              /* The Y position lies in the rectangle.  Now, calculate the
               * theZ position at this relative X position.  We can skip
               * this step if we are processing another rectangle at the
               * same relx distance.
               */

              if (relx != lastrelx2)
                {
                  int32_t deltaz;      /* Scale == TRIPLE */

                  /* The dzdx is stored at double the"normal" scaling -- so
                   * deltaz is "triple" precision
                   */

                  deltaz    = dzdx * ((int32_t) relx);
                  absz      = tTOs(deltaz) + g_camera.z; /* Back to single */
                  lastrelx2 = relx;
                }

              /* Check if this Z position intersects the rectangle */

              if (absz >= rect->vstart && absz <= rect->vend)
                {
                  /* We've got a potential hit, let's see what it is */
                  /* Check if we just hit an ordinary opaque wall */

                  if (IS_NORMAL(rect))
                    {
                      /* Yes..Save the parameters associated with the normal
                       * wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(FRONT_HIT, X_HIT);
                      result->xpos = absy;
                      result->ypos = absz;

                      result->xdist = relx;
                      result->ydist = ABS(absy - g_camera.y);
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate X casting */

                      return;
                    }
                  else if (IS_DOOR(rect))
                    {
                      /* Check if the door is in motion. */

                      if (!IS_MOVING_DOOR(rect))
                        {
                          /* Save the parameters associated with the normal
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(FRONT_HIT, X_HIT);
                          result->xpos = absy;
                          result->ypos = absz;

                          result->xdist = relx;
                          result->ydist = ABS(absy - g_camera.y);
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate X casting */

                          return;
                        }

                      /* The door is in motion, the Z-position to see if we can
                       * see under the door
                       */

                      else if (absz > g_opendoor.zbottom)
                        {
                          /* Save the parameters associated with the moving
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(FRONT_HIT, X_HIT);
                          result->xpos = absy;
                          result->ypos = absz - g_opendoor.zdist;

                          result->xdist = relx;
                          result->ydist = ABS(absy - g_camera.y);
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate X casting */

                          return;
                        }
                    }

                  /* Otherwise, it must be a transparent wall.  We'll need to
                   * make our decision based upon the pixel that we hit
                   */

                  /* Check if the pixel at this location is visible */

                  else if (GET_FRONT_PIXEL(rect, absy, absz) != INVISIBLE_PIXEL)
                    {
                      /* Its visible, save the parameters associated with the
                       * transparent wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(FRONT_HIT, X_HIT);
                      result->xpos = absy;
                      result->ypos = absz;

                      result->xdist = relx;
                      result->ydist = ABS(absy - g_camera.y);
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate X casting */

                      return;
                    }
                }
            }
        }
    }
}

/****************************************************************************
 * Name: trv_ray_xcaster23
 *
 * Description:
 *   This function casts a ray along the Y-Axis looking at points of
 *   intersection on the X-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "left" half of the view, then the X ray cast will
 *   proceed in a negative along the X axis and that all possible X-axis
 *   intersections will occur on the "right" of a cell.
 *
 *   NOTE:  The X-Ray caster must run first because it initializes a
 *   data structure needed by both the Y and Z ray casters.
 *
 ****************************************************************************/

static void trv_ray_xcaster23(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current X plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t relx;             /* Relative position of the X plane */
  trv_coord_t absy;             /* Absolute Y position at relx given yaw */
  trv_coord_t absz;             /* Absolute Z position at relx given pitch */
  trv_coord_t lastrelx1 = -1;   /* Last relative X position processed */
  trv_coord_t lastrelx2 = -1;   /* Last relative X position processed */
  int32_t dydx;                 /* Rate of change of Y wrt X (double) */
  int32_t dzdx;                 /* Rate of change of Z wrt X (double) */

  /* At a view angle of 90 degrees, no intersections with the g_ray_xplanes are
   * possible!
   */

  if (g_camera.yaw == ANGLE_90)
    {
      return;
    }

  /* Pre-calculate the rate of change of Y and Z with respect to X */
  /* The negative tangent is equal to the rate of change of Y with respect
   * to the X-axis.  The tangent is stored at double the "normal" scaling.
   */

  dydx = -TAN(g_camera.yaw);

  /* Determine the rate of change of the Z with respect to X. dydx is
   * "double" precision; the secant is "double" precision.  dzdx will be
   * retained as "double" precision.
   */

  dzdx = qTOd(g_adj_tanpitch * ABS(g_sec_table[g_camera.yaw]));

  /* Look at every rectangle lying in the X plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_xplane.tail; list; list = list->blink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "before" the current camera
       * position
       */

      if (rect->plane < g_camera.x)
        {
          /* get the X distance to the plane */

          relx = g_camera.x - rect->plane;
#if 0
          /* g_ray_xplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (relx > result->xdist)
            {
              return;
            }
#endif

          /* Calculate the Y position at this relative X position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

          if (relx != lastrelx1)
            {
              int32_t deltay;  /* Scale == "triple" */

              /* The dydx is stored at double the"normal" scaling -- so deltay
               * is "triple" precision
               */

              deltay    = dydx * ((int32_t) relx);
              absy      = tTOs(deltay) + g_camera.y; /* back to "single" */
              lastrelx1 = relx;
            }

          /* Check if this Y position intersects the rectangle */

          if (absy >= rect->hstart && absy <= rect->hend)
            {
              /* The Y position lies in the rectangle.  Now, calculate the
               * Z position at this relative X position.  We can skip this
               * step if we are processing another rectangle at the same
               * relx distance.
               */

              if (relx != lastrelx2)
                {
                  int32_t deltaz;      /* Scale == TRIPLE */

                  /* The dzdx is stored at double the"normal" scaling -- so
                   * deltaz is "triple" precision
                   */

                  deltaz    = dzdx * ((int32_t) relx);
                  absz      = tTOs(deltaz) + g_camera.z; /* Back to single */
                  lastrelx2 = relx;
                }

              /* Check if this Z position intersects the rectangle */

              if (absz >= rect->vstart && absz <= rect->vend)
                {
                  /* We've got a potential hit, let's see what it is */
                  /* Check if we just hit an ordinary opaque wall */

                  if (IS_NORMAL(rect))
                    {
                      /* Yes..Save the parameters associated with the normal
                       * wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(BACK_HIT, X_HIT);
                      result->xpos = absy;
                      result->ypos = absz;

                      result->xdist = relx;
                      result->ydist = ABS(absy - g_camera.y);
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate X casting */

                      return;
                    }
                  else if (IS_DOOR(rect))
                    {
                      /* Check if the door is in motion. */

                      if (!IS_MOVING_DOOR(rect))
                        {
                          /* Save the parameters associated with the normal
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(BACK_HIT, X_HIT);
                          result->xpos = absy;
                          result->ypos = absz;

                          result->xdist = relx;
                          result->ydist = ABS(absy - g_camera.y);
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate X casting */

                          return;
                        }

                      /* The door is in motion, the Z-position to see if we can
                       * see under the door
                       */

                      else if (absz > g_opendoor.zbottom)
                        {
                          /* Save the parameters associated with the moving
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(BACK_HIT, X_HIT);
                          result->xpos = absy;
                          result->ypos = absz - g_opendoor.zdist;

                          result->xdist = relx;
                          result->ydist = ABS(absy - g_camera.y);
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate X casting */

                          return;
                        }
                    }

                  /* Otherwise, it must be a transparent wall.  We'll need to
                   * make our decision based upon the pixel that we hit
                   */

                  /* Check if the pixel at this location is visible */

                  else if (GET_BACK_PIXEL(rect, absy, absz) != INVISIBLE_PIXEL)
                    {
                      /* Its visible, save the parameters associated with the
                       * transparent wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(BACK_HIT, X_HIT);
                      result->xpos = absy;
                      result->ypos = absz;

                      result->xdist = relx;
                      result->ydist = ABS(absy - g_camera.y);
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate X casting */

                      return;
                    }
                }
            }
        }
    }
}

/****************************************************************************
 * Name: trv_ray_ycaster12
 *
 * Description:
 *   This function casts a ray along the X-Axis looking at points of
 *   intersection on the Y-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "forward" half of the view, then the Y ray cast will
 *   proceed in a positive along the Y axis and that all possible Y-axis
 *   intersections will occur on the "front" of a cell.
 *
 *   NOTE:  The X-Ray is assumed to have been performed first!
 *
 ****************************************************************************/

static void trv_ray_ycaster12(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current P plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t rely;             /* Relative position of the Y plane */
  trv_coord_t absx;             /* Absolute X position at rely given yaw */
  trv_coord_t absz;             /* Absolute Z position at rely given pitch */
  trv_coord_t lastrely1 = -1;   /* Last relative Y position processed */
  trv_coord_t lastrely2 = -1;   /* Last relative Y position processed */
  int32_t dxdy;                 /* Rate of change of X wrt Y (double) */
  int32_t dzdy;                 /* Rate of change of Z wrt Y (double) */

  /* At a viewing angle of 0 degrees, no intersections with the g_ray_yplane is
   * possible!
   */

  if (g_camera.yaw == ANGLE_0)
    {
      return;
    }

  /* Pre-calculate the rate of change of X and Z with respect to Y */
  /* The inverted tangent is equal to the rate of change of X with respect to
   * the Y-axis.  The cotangent is stored at double the the "normal" scaling.
   */

  dxdy = g_cot_table(g_camera.yaw);

  /* Determine the rate of change of the Z with respect to Y.  The tangent
   * is "double" precision; the cosecant is "double" precision.  dzdy will
   * be retained as "double" precision.
   */

  dzdy = qTOd(g_adj_tanpitch * ABS(g_csc_table[g_camera.yaw]));

  /* Look at every rectangle lying in a Y plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_yplane.head; list; list = list->flink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "beyond" the current camera
       * position
       */

      if (rect->plane > g_camera.y)
        {
          /* get the Y distance to the plane */

          rely = rect->plane - g_camera.y;

          /* g_ray_yplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (rely > result->ydist)
            {
              return;
            }

          /* Calculate the Y position at this relative X position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

          if (rely != lastrely1)
            {
              int32_t deltax;  /* Scale == "triple" */

              /* The dxdy is stored at double the"normal" scaling -- so deltax
               * is "triple" precision
               */

              deltax    = dxdy * ((int32_t) rely);
              absx      = tTOs(deltax) + g_camera.x; /* back to "single" */
              lastrely1 = rely;
            }

          /* Check if this X position intersects the rectangle */

          if (absx >= rect->hstart && absx <= rect->hend)
            {
              /* The X position lies in the rectangle.  Now, calculate the the
               * Z position at this relative X position.  We can skip this step
               * if we are processing another rectangle at the same relx
               * distance.
               */

              if (rely != lastrely2)
                {
                  int32_t deltaz;      /* Scale == TRIPLE */

                  /* The dzdy is stored at double the"normal" scaling -- so
                   * deltaz is "triple" precision
                   */

                  deltaz    = dzdy * ((int32_t) rely);
                  absz      = tTOs(deltaz) + g_camera.z; /* Back to single */
                  lastrely2 = rely;
                }

              /* Check if this Z position intersects the rectangle */

              if (absz >= rect->vstart && absz <= rect->vend)
                {
                  /* We've got a potential hit, let's see what it is */
                  /* Check if we just hit an ordinary opaque wall */

                  if (IS_NORMAL(rect))
                    {
                      /* Yes..Save the parameters associated with the normal
                       * wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(FRONT_HIT, Y_HIT);
                      result->xpos = absx;
                      result->ypos = absz;

                      result->xdist = ABS(absx - g_camera.x);
                      result->ydist = rely;
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate Y casting */

                      return;
                    }
                  else if (IS_DOOR(rect))
                    {
                      /* Check if the door is in motion. */

                      if (!IS_MOVING_DOOR(rect))
                        {
                          /* Save the parameters associated with the normal
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(FRONT_HIT, Y_HIT);
                          result->xpos = absx;
                          result->ypos = absz;

                          result->xdist = ABS(absx - g_camera.x);
                          result->ydist = rely;
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate Y casting */

                          return;
                        }

                      /* The door is in motion, the Z-position to see if we can
                       * see under the door
                       */

                      else if (absz > g_opendoor.zbottom)
                        {
                          /* Save the parameters associated with the moving
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(FRONT_HIT, Y_HIT);
                          result->xpos = absx;
                          result->ypos = absz - g_opendoor.zdist;

                          result->xdist = ABS(absx - g_camera.x);
                          result->ydist = rely;
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate Y casting */

                          return;
                        }
                    }

                  /* Otherwise, it must be a transparent wall.  We'll need to
                   * make our decision based upon the pixel that we hit
                   */

                  /* Check if the pixel at this location is visible */

                  else if (GET_FRONT_PIXEL(rect, absx, absz) != INVISIBLE_PIXEL)
                    {
                      /* Its visible, save the parameters associated with the
                       * transparent wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(FRONT_HIT, Y_HIT);
                      result->xpos = absx;
                      result->ypos = absz;

                      result->xdist = ABS(absx - g_camera.x);
                      result->ydist = rely;
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate Y casting */

                      return;
                    }
                }
            }
        }
    }
}

/****************************************************************************
 * Name: trv_ray_ycaster34
 *
 * Description:
 *   This function casts a ray along the X-Axis looking at points of
 *   intersection on the Y-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "back" half of the view, then the Y ray cast will
 *   proceed in a negative along the Y axis and that all possible Y-axis
 *   intersections will occur on the "back" of a cell.
 *
 *   NOTE:  The X-Ray is assumed to have been performed first!
 *
 ****************************************************************************/

static void trv_ray_ycaster34(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current P plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t rely;             /* Relative position of the Y plane */
  trv_coord_t absx;             /* Absolute X position at rely given yaw */
  trv_coord_t absz;             /* Absolute Z position at rely given pitch */
  trv_coord_t lastrely1 = -1;   /* Last relative Y position processed */
  trv_coord_t lastrely2 = -1;   /* Last relative Y position processed */
  int32_t dxdy;                 /* Rate of change of X wrt Y (double) */
  int32_t dzdy;                 /* Rate of change of Z wrt Y (double) */

  /* At a viewing angle of 180 degrees, no intersections with the line x = bXi
   * are possible!
   */

  if (g_camera.yaw == ANGLE_180)
    {
      return;
    }

  /* Pre-calculate the rate of change of X and Z with respect to Y */
  /* The negative inverted tangent is equal to the rate of change of X with
   * respect to the Y-axis.  The cotangent is stored at double the the
   * "normal" scaling.
   */

  dxdy = -g_cot_table(g_camera.yaw - ANGLE_180);

  /* Determine the rate of change of the Z with respect to Y.  The tangent
   * is "double" precision; the cosecant is "double" precision.  dzdy will
   * be retained as "double" precision.
   */

  dzdy = qTOd(g_adj_tanpitch * ABS(g_csc_table[g_camera.yaw]));

  /* Look at every rectangle lying in a Y plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_yplane.tail; list; list = list->blink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "before" the current camera
       * position
       */

      if (rect->plane < g_camera.y)
        {
          /* get the Y distance to the plane */

          rely = g_camera.y - rect->plane;

          /* g_ray_yplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (rely > result->ydist)
            {
              return;
            }

          /* Calculate the Y position at this relative X position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

           if (rely != lastrely1)
            {
              int32_t deltax;  /* Scale == "triple" */

              /* The dxdy is stored at double the"normal" scaling -- so deltax
               * is "triple" precision
               */

              deltax    = dxdy * ((int32_t) rely);
              absx      = tTOs(deltax) + g_camera.x; /* back to "single" */
              lastrely1 = rely;
            }

          /* Check if this X position intersects the rectangle */

          if (absx >= rect->hstart && absx <= rect->hend)
            {

              /* The X position lies in the rectangle.  Now, calculate the
               * Z position at this relative X position.  We can skip this
               * step if we are processing another rectangle at the same
               * relx distance.
               */

              if (rely != lastrely2)
                {
                  int32_t deltaz;      /* Scale == TRIPLE */

                  /* The dzdy is stored at double the"normal" scaling -- so
                   * deltaz is "triple" precision
                   */

                  deltaz    = dzdy * ((int32_t) rely);
                  absz      = tTOs(deltaz) + g_camera.z; /* Back to single */
                  lastrely2 = rely;
                }

              /* Check if this Z position intersects the rectangle */

              if (absz >= rect->vstart && absz <= rect->vend)
                {

                  /* We've got a potential hit, let's see what it is */
                  /* Check if we just hit an ordinary opaque wall */

                  if (IS_NORMAL(rect))
                    {
                      /* Yes..Save the parameters associated with the normal
                       * wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(BACK_HIT, Y_HIT);
                      result->xpos = absx;
                      result->ypos = absz;

                      result->xdist = ABS(absx - g_camera.x);
                      result->ydist = rely;
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate Y casting */

                      return;
                    }
                  else if (IS_DOOR(rect))
                    {
                      /* Check if the door is in motion. */

                      if (!IS_MOVING_DOOR(rect))
                        {
                          /* Save the parameters associated with the normal
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(BACK_HIT, Y_HIT);
                          result->xpos = absx;
                          result->ypos = absz;

                          result->xdist = ABS(absx - g_camera.x);
                          result->ydist = rely;
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate Y casting */

                          return;
                        }

                      /* The door is in motion, the Z-position to see if we can
                       * see under the door
                       */

                      else if (absz > g_opendoor.zbottom)
                        {
                          /* Save the parameters associated with the moving
                           * door hit
                           */

                          result->rect = rect;
                          result->type = MK_HIT_TYPE(BACK_HIT, Y_HIT);
                          result->xpos = absx;
                          result->ypos = absz - g_opendoor.zdist;

                          result->xdist = ABS(absx - g_camera.x);
                          result->ydist = rely;
                          result->zdist = ABS(absz - g_camera.z);

                          /* Terminate Y casting */

                          return;
                        }
                    }

                  /* Otherwise, it must be a transparent wall.  We'll need to
                   * make our decision based upon the pixel that we hit
                   */

                  /* Check if the pixel at this location is visible */

                  else if (GET_BACK_PIXEL(rect, absx, absz) != INVISIBLE_PIXEL)
                    {
                      /* Its visible, save the parameters associated with the
                       * transparent wall hit
                       */

                      result->rect = rect;
                      result->type = MK_HIT_TYPE(BACK_HIT, Y_HIT);
                      result->xpos = absx;
                      result->ypos = absz;

                      result->xdist = ABS(absx - g_camera.x);
                      result->ydist = rely;
                      result->zdist = ABS(absz - g_camera.z);

                      /* Terminate Y casting */

                      return;
                    }
                }
            }
        }
    }
}

/****************************************************************************
 * Name: trv_ray_zcasteru
 *
 * Description:
 *   This function casts a ray along the rotated Y-Axis looking at points of
 *   intersection on the Z-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "upper" half of the view, then the Z ray cast will
 *   proceed along the positive Z axis and that all possible Z-axis
 *   intersections will occur on the "bottom" of a cell.
 *
 *   NOTE: It is assumed that both the X and Y ray casters have already
 *   ran!
 ****************************************************************************/

static void trv_ray_zcasteru(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current Z plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t relz;             /* Relative position of the Z plane */
  trv_coord_t absx;             /* Absolute X position at relz given yaw */
  trv_coord_t absy;             /* Absolute Y position at relz given yaw */
  trv_coord_t lastrelz1 = -1;   /* Last relative Z position processed */
  trv_coord_t lastrelz2 = -1;   /* Last relative Z position processed */
  int32_t dxdz;                 /* Rate of change of X wrt Z (double) */
  int32_t dydz;                 /* Rate of change of Y wrt Z (double) */

  /* At a viewing angle of 0 degrees, no intersections with the g_ray_zplanes are
   * possible!
   */

  if (g_camera.pitch == ANGLE_0)
    {
      return;
    }

  /* Pre-calculate the rate of change of X and Y with respect to Z */
  /* Calculate the rate of change of X with respect to the Z-axis. The
   * cotangent is stored at double the "normal" scaling and the cosine is
   * also at double scaling.  dxdz will be also be stored at double
   * precision.
   */

  dxdz = qTOd(g_adj_cotpitch * ((int32_t) g_cos_table[g_camera.yaw]));

  /* Calculate the rate of change of Y with respect to the Z-axis. The
   * cotangent is stored at double the "normal" scaling and the sine is also
   * at double scaling.  dxdz will be also be stored at double precision.
   */

  dydz = qTOd(g_adj_cotpitch * ((int32_t) g_sin_table[g_camera.yaw]));

  /* Look at every rectangle lying in the Z plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_zplane.head; list; list = list->flink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "beyond" the current camera
       * position
       */

      if (rect->plane > g_camera.z)
        {
          /* get the Z distance to the plane */

          relz = rect->plane - g_camera.z;

          /* g_ray_zplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (relz > result->zdist)
            {
              return;
            }

          /* Calculate the X position at this relative Z position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

          if (relz != lastrelz1)
            {
              int32_t deltax;  /* Scale == "triple" */

              /* The dxdz is stored at double the"normal" scaling -- so deltax
               * is "triple" precision
               */

              deltax    = dxdz * ((int32_t) relz);
              absx      = tTOs(deltax) + g_camera.x; /* back to "single" */
              lastrelz1 = relz;
            }

          /* Check if this X position intersects the rectangle */

          if (absx >= rect->hstart && absx <= rect->hend)
            {
              /* The X position lies in the rectangle.  Now, calculate the the
               * Y position at this relative Z position.  We can skip this step
               * if we are processing another rectangle at the same relx
               * distance.
               */

              if (relz != lastrelz2)
                {
                  int32_t deltay;      /* Scale == TRIPLE */

                  /* The dydz is stored at double the"normal" scaling -- so
                   * deltay is "triple" precision
                   */

                  deltay    = dydz * ((int32_t) relz);
                  absy      = tTOs(deltay) + g_camera.y; /* back to "single" */
                  lastrelz2 = relz;
                }

              /* Check if this Y position intersects the rectangle */

              if (absy >= rect->vstart && absy <= rect->vend)
                {
                  /* We've got a hit, ..Save the parameters associated with the
                   * ceiling hit
                   */

                  result->rect = rect;
                  result->type = MK_HIT_TYPE(BACK_HIT, Z_HIT);
                  result->xpos = absx;
                  result->ypos = absy;

                  result->xdist = ABS(absx - g_camera.x);
                  result->ydist = ABS(absy - g_camera.y);
                  result->zdist = relz;

                  /* Terminate Z casting */

                  return;
                }
            }
        }
    }
}

/****************************************************************************
 * Name: trv_ray_zcasterl
 *
 * Description:
 *   This function casts a ray along the rotated Y-Axis looking at points of
 *   intersection on the Z-Axis.  If a block is defined at this intersection
 *   then a "hit" is found and the distance to this hit is determined.
 *
 *   If we are in the "lower" half of the view, then the Z ray cast will
 *   proceed along the negative Z axis and that all possible Z-axis
 *   intersections will occur on the "top" of a cell.
 *
 *   NOTE: It is assumed that both the X and Y ray casters have already
 *   ran!
 *
 ****************************************************************************/

static void trv_ray_zcasterl(FAR struct trv_raycast_s *result)
{
  struct trv_rect_list_s *list; /* Points to the current Z plane rectangle */
  struct trv_rect_data_s *rect; /* Points to the rectangle data */
  trv_coord_t relz;             /* Relative position of the Z plane */
  trv_coord_t absx;             /* Absolute X position at relz given yaw */
  trv_coord_t absy;             /* Absolute Y position at relz given yaw */
  trv_coord_t lastrelz1 = -1;   /* Last relative Z position processed */
  trv_coord_t lastrelz2 = -1;   /* Last relative Z position processed */
  int32_t dxdz;                 /* Rate of change of X wrt Z (double) */
  int32_t dydz;                 /* Rate of change of Y wrt Z (double) */

  /* At a viewing angle of 0 degrees, no intersections with the g_ray_zplanes are
   * possible!
   */

  if (g_camera.pitch == ANGLE_0)
    {
      return;
    }

  /* Pre-calculate the rate of change of X and Y with respect to Z */
  /* Calculate the rate of change of X with respect to the Z-axis. The
   * cotangent is stored at double the "normal" scaling and the cosine is
   * also at double scaling.  dxdz will be also be stored at double
   * precision.
   */

  dxdz = qTOd(g_adj_cotpitch * ((int32_t) g_cos_table[g_camera.yaw]));

  /* Calculate the rate of change of Y with respect to the Z-axis. The
   * cotangent is stored at double the "normal" scaling and the sine is
   * also at double scaling.  dxdz will be also be stored at double
   * precision.
   */

  dydz = qTOd(g_adj_cotpitch * ((int32_t) g_sin_table[g_camera.yaw]));

  /* Look at every rectangle lying in the Z plane */
  /* This logic should be improved at some point so that non-visible planes
   * are "pruned" from the list prior to ray casting!
   */

  for (list = g_ray_zplane.tail; list; list = list->blink)
    {
      rect = &list->d;

      /* Search for a rectangle which lies "before" the current camera
       * position
       */

      if (rect->plane < g_camera.z)
        {
          /* get the Z distance to the plane */

          relz = g_camera.z - rect->plane;

          /* g_ray_zplane is an ordered list, if we have already hit something
           * closer, then we can abort the casting now.
           */

          if (relz > result->zdist)
            {
              return;
            }

          /* Calculate the X position at this relative Z position.  We can skip
           * this step if we are processing another rectangle at the same relx
           * distance.
           */

          if (relz != lastrelz1)
            {
              int32_t deltax;  /* Scale == "triple" */

              /* The dxdz is stored at double the"normal" scaling -- so deltax
               * is "triple" precision
               */

              deltax    = dxdz * ((int32_t) relz);
              absx      = tTOs(deltax) + g_camera.x; /* back to "single" */
              lastrelz1 = relz;
            }

          /* Check if this X position intersects the rectangle */

          if (absx >= rect->hstart && absx <= rect->hend)
            {
              /* The X position lies in the rectangle.  Now, calculate the the
               * Y position at this relative Z position.  We can skip this step
               * if we are processing another rectangle at the same relx
               * distance.
               */

              if (relz != lastrelz2)
                {
                  int32_t deltay;      /* Scale == TRIPLE */

                  /* The dydz is stored at double the"normal" scaling -- so
                   * deltay is "triple" precision
                   */

                  deltay    = dydz * ((int32_t) relz);
                  absy      = tTOs(deltay) + g_camera.y; /* back to "single" */
                  lastrelz2 = relz;
                }

              /* Check if this Y position intersects the rectangle */

              if (absy >= rect->vstart && absy <= rect->vend)
                {
                  /* We've got a hit, ..Save the parameters associated with the
                   * floor hit
                   */

                  result->rect = rect;
                  result->type = MK_HIT_TYPE(FRONT_HIT, Z_HIT);
                  result->xpos = absx;
                  result->ypos = absy;

                  result->xdist = ABS(absx - g_camera.x);
                  result->ydist = ABS(absy - g_camera.y);
                  result->zdist = relz;

                  /* Terminate Z casting */

                  return;
                }
            }
        }
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: trv_raycast
 *
 * Description:
 *   This function performs a single ray cast.  It decomposes the cast by
 *   quadrants so that simpler casting algorithms can be used.  It also
 *   enforces the order of casting:  X first, then Y, and finally Z.
 *
 ****************************************************************************/

void trv_raycast(int16_t pitch, int16_t yaw, int16_t screenyaw,
                FAR struct trv_raycast_s *result)
{
  /* Set the camera pitch and yaw angles for this cast */

  g_camera.pitch = pitch;
  g_camera.yaw = yaw;

  /* Initialize the result structure, assuming that there will be no hit */

  result->rect  = NULL;
  result->type  = NO_HIT;
  result->xpos  = 0;
  result->ypos  = 0;
  result->xdist = TRV_INFINITY;
  result->ydist = TRV_INFINITY;
  result->zdist = TRV_INFINITY;

  /* Prepare for X and Y ray casts.  These casts will need the adjusted tangent
   * of the pitch angle in order to correct for "fish eye" distortion.  This
   * correction consists of multiplying by the cosine of the relative screen
   * yaw position. The tangent is double precision, the cosine is double
   * precision, the result will be retained as double precision.
   */

  screenyaw = ABS(screenyaw);
#if ENABLE_VIEW_CORRECTION
  g_adj_tanpitch = qTOd(TAN(pitch) * ((int32_t) g_cos_table[screenyaw]));
#else
  g_adj_tanpitch = TAN(pitch);
#endif

  /* Perform X & Y raycasting based on the quadrant of the yaw angle */

  if (g_camera.yaw < ANGLE_90)
    {
      trv_ray_xcaster14(result);
      trv_ray_ycaster12(result);
    }
  else if (g_camera.yaw < ANGLE_180)
    {
      trv_ray_xcaster23(result);
      trv_ray_ycaster12(result);
    }
  else if (g_camera.yaw < ANGLE_270)
    {
      trv_ray_xcaster23(result);
      trv_ray_ycaster34(result);
    }
  else
    {
      trv_ray_xcaster14(result);
      trv_ray_ycaster34(result);
    }

  /* Perform Z ray casting based upon if we are looking up or down */

  if (g_camera.pitch < ANGLE_90)
    {
      /* Get the adjusted cotangent of the pitch angle which is used to correct
       * for the "fish eye" distortion.  This correction consists of
       * multiplying by the inverted cosine of the relative screen yaw
       * position.  The cotangent is double precision, the secant is double
       * precision, the result will be retained as double precision.
       */

#if ENABLE_VIEW_CORRECTION
      g_adj_cotpitch = qTOd(g_cot_table(pitch) * g_sec_table[screenyaw]);
#else
      g_adj_cotpitch = g_cot_table(pitch);
#endif
      trv_ray_zcasteru(result);
    }
  else
    {
      /* Get the adjusted cotangent of the pitch angle which is used to correct
       * for the "fish eye" distortion.  This correction consists of
       * multiplying by the inverted cosine of the relative screen yaw
       * position. The cotangent is double precision, the secant is double
       * precision, the result will be retained as double precision.
       */

#if ENABLE_VIEW_CORRECTION
      g_adj_cotpitch =
        qTOd(g_cot_table(ANGLE_360 - pitch) * g_sec_table[screenyaw]);
#else
      g_adj_cotpitch = g_cot_table(ANGLE_360 - pitch);
#endif
      trv_ray_zcasterl(result);
    }
}
