star-camera

Camera models
git clone git://git.meso-star.fr/star-camera.git
Log | Files | Refs | README | LICENSE

scam_perspective.c (9085B)


      1 /* Copyright (C) 2021-2023 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #include "scam_c.h"
     17 #include "scam_log.h"
     18 
     19 #include <rsys/double3.h>
     20 #include <rsys/double33.h>
     21 #include <rsys/math.h>
     22 
     23 #include <math.h>
     24 
     25 /*******************************************************************************
     26  * Helper functions
     27  ******************************************************************************/
     28 static res_T
     29 check_perspective_args
     30   (struct scam* cam,
     31    const struct scam_perspective_args* args)
     32 {
     33   if(!args) return RES_BAD_ARG;
     34 
     35   /* Invalid aspect ratio */
     36   if(args->aspect_ratio <= 0) {
     37     log_err(cam,"perspective camera: invalid aspect ratio: %g\n",
     38       args->aspect_ratio);
     39     return RES_BAD_ARG;
     40   }
     41 
     42   /* Invalid lens radius */
     43   if(args->lens_radius < 0) {
     44     log_err(cam,"perspective camera: invalid negative lens radius: %g\n",
     45       args->lens_radius);
     46     return RES_BAD_ARG;
     47   }
     48 
     49   /* Invalid focal distance */
     50   if(args->lens_radius > 0 && args->focal_distance < 0) {
     51     log_err(cam, "perspective camera: invalid negative focal distance: %g\n",
     52       args->focal_distance);
     53     return RES_BAD_ARG;
     54   }
     55 
     56   /* Invalid field of view */
     57   if(args->field_of_view <= 0 || args->field_of_view >= PI) {
     58     log_err(cam, "perspective camera: invalid vertical field of view: %g\n",
     59       args->field_of_view);
     60     return RES_BAD_ARG;
     61   }
     62 
     63   return RES_OK;
     64 }
     65 
     66 static res_T
     67 setup_perspective(struct scam* cam, const struct scam_perspective_args* args)
     68 {
     69   double hfov; /* Horizotal field of view */
     70   double half_vfov; /* (Vertical vield of view) / 2 */
     71   double tan_half_vfov;
     72   double x[3], y[3], z[3];
     73   res_T res = RES_OK;
     74   ASSERT(cam && args && cam->type == SCAM_PERSPECTIVE);
     75 
     76   cam->param.persp = PERSPECTIVE_DEFAULT;
     77 
     78   if(d3_normalize(z, d3_sub(z, args->target, args->position)) <= 0
     79   || d3_normalize(x, d3_cross(x, z, args->up)) <= 0
     80   || d3_normalize(y, d3_cross(y, z, x)) <= 0) {
     81     log_err(cam,
     82       "perspective camera: invalid point of view:\n"
     83       "  position = %g %g %g\n"
     84       "  target   = %g %g %g\n"
     85       "  up       = %g %g %g\n",
     86       SPLIT3(args->position), SPLIT3(args->target), SPLIT3(args->up));
     87     res = RES_BAD_ARG;
     88     goto error;
     89   }
     90 
     91   half_vfov = args->field_of_view*0.5;
     92   tan_half_vfov = tan(half_vfov);
     93 
     94   cam->param.persp.rcp_tan_half_fov = 1.0/tan_half_vfov;
     95   cam->param.persp.aspect_ratio = args->aspect_ratio;
     96   cam->param.persp.lens_radius = args->lens_radius;
     97   cam->param.persp.focal_distance = args->focal_distance;
     98 
     99   d3_set(cam->param.persp.position, args->position);
    100 
    101   d3_muld(cam->param.persp.screen2world+0, x, args->aspect_ratio);
    102   d3_set (cam->param.persp.screen2world+3, y);
    103   d3_muld(cam->param.persp.screen2world+6, z, cam->param.persp.rcp_tan_half_fov);
    104 
    105   d3_set(cam->param.persp.camera2world+0, x);
    106   d3_set(cam->param.persp.camera2world+3, y);
    107   d3_set(cam->param.persp.camera2world+6, z);
    108 
    109   /* Compute the solid angle of the camera */
    110   hfov = 2*atan(args->aspect_ratio * tan_half_vfov);
    111   cam->param.persp.solid_angle = hfov * 2*sin(half_vfov);
    112 
    113 exit:
    114   return res;
    115 error:
    116   goto exit;
    117 }
    118 
    119 static INLINE void
    120 pinhole_generate_ray
    121   (const struct scam* cam,
    122    const struct scam_sample* sample,
    123    struct scam_ray* ray)
    124 {
    125   double x[3], y[3], z[3], len;
    126   double pos[3];
    127   (void)len;
    128 
    129   ASSERT(cam && sample && ray);
    130   ASSERT(cam->param.persp.lens_radius == 0);
    131   ASSERT(cam->type == SCAM_PERSPECTIVE);
    132   ASSERT(0 <= sample->film[0] && sample->film[0] < 1);
    133   ASSERT(0 <= sample->film[1] && sample->film[1] < 1);
    134 
    135   /* Transform the sampled position in screen space */
    136   pos[0] = sample->film[0]*2-1;
    137   pos[1] = sample->film[1]*2-1;
    138   pos[2] = 1;
    139 
    140   /* Transform the sampled position in world space. Note that no translation is
    141    * performed to directly obtain the (un-normalized) ray direction. */
    142   d3_muld(x, cam->param.persp.screen2world+0, pos[0]);
    143   d3_muld(y, cam->param.persp.screen2world+3, pos[1]);
    144   d3_set (z, cam->param.persp.screen2world+6);
    145   d3_add(ray->dir, x, y);
    146   d3_add(ray->dir, ray->dir, z);
    147   len = d3_normalize(ray->dir, ray->dir);
    148   ASSERT(len >= 1.e-6);
    149 
    150   /* Setup the ray origin */
    151   d3_set(ray->org, cam->param.persp.position);
    152 }
    153 
    154 static INLINE void
    155 thin_lens_generate_ray
    156   (const struct scam* cam,
    157    const struct scam_sample* sample,
    158    struct scam_ray* ray)
    159 {
    160   double focus_pt[3];
    161   double dir[3];
    162   double theta;
    163   double len;
    164   double t;
    165   double r;
    166   (void)len;
    167 
    168   ASSERT(cam && sample && ray);
    169   ASSERT(cam->param.persp.lens_radius > 0);
    170   ASSERT(cam->type == SCAM_PERSPECTIVE);
    171   ASSERT(0 <= sample->film[0] && sample->film[0] < 1);
    172   ASSERT(0 <= sample->film[1] && sample->film[1] < 1);
    173   ASSERT(0 <= sample->lens[0] && sample->lens[0] < 1);
    174   ASSERT(0 <= sample->lens[1] && sample->lens[1] < 1);
    175 
    176   /* Transform the sampled position in screen space and use it as the
    177    * (un-normalized) direction starting from the lens center and intersecting
    178    * the sample */
    179   dir[0] = sample->film[0]*2-1;
    180   dir[1] = sample->film[1]*2-1;
    181   dir[2] = 1;
    182 
    183   /* Transform the sampled direction in camera space */
    184   dir[0] = dir[0] * cam->param.persp.aspect_ratio;
    185   dir[1] = dir[1];
    186   dir[2] = dir[2] * cam->param.persp.rcp_tan_half_fov;
    187   len = d3_normalize(dir, dir);
    188   ASSERT(len >= 1.e-6);
    189 
    190   /* find the focus point by intersecting dir with the focus plane */
    191   t = cam->param.persp.focal_distance / dir[2];
    192   focus_pt[0] = /* null ray origin + */ t*dir[0];
    193   focus_pt[1] = /* null ray origin + */ t*dir[1];
    194   focus_pt[2] = /* null ray origin + */ t*dir[2];
    195 
    196   /* Uniformly sample a position onto the lens in camera space */
    197   theta = 2 * PI * sample->lens[0];
    198   r = cam->param.persp.lens_radius * sqrt(sample->lens[1]);
    199   ray->org[0] = r * cos(theta);
    200   ray->org[1] = r * sin(theta);
    201   ray->org[2] = 0;
    202 
    203   /* Compute the ray direction in camera space */
    204   d3_sub(ray->dir, focus_pt, ray->org);
    205   len = d3_normalize(ray->dir, ray->dir);
    206   ASSERT(len >= 1.e-6);
    207 
    208   /* Transform the ray from camera space to world space */
    209   d33_muld3(ray->dir, cam->param.persp.camera2world, ray->dir);
    210   d33_muld3(ray->org, cam->param.persp.camera2world, ray->org);
    211   d3_add(ray->org, cam->param.persp.position, ray->org);
    212 }
    213 
    214 /*******************************************************************************
    215  * Exported functions
    216  ******************************************************************************/
    217 SCAM_API res_T
    218 scam_create_perspective
    219   (struct logger* logger, /* NULL <=> use builtin logger */
    220    struct mem_allocator* allocator, /* NULL <=> use default allocator */
    221    const int verbose, /* Verbosity level */
    222    struct scam_perspective_args* args,
    223    struct scam** out_cam)
    224 {
    225   struct scam* cam = NULL;
    226   res_T res = RES_OK;
    227 
    228   if(!args || !out_cam) {
    229     res = RES_BAD_ARG;
    230     goto error;
    231   }
    232   res = camera_create(logger, allocator, verbose, SCAM_PERSPECTIVE, &cam);
    233   if(res != RES_OK) goto error;
    234   res = check_perspective_args(cam, args);
    235   if(res != RES_OK) goto error;
    236   res = setup_perspective(cam, args);
    237   if(res != RES_OK) goto error;
    238 
    239 exit:
    240   if(out_cam) *out_cam = cam;
    241   return res;
    242 error:
    243   if(cam) {
    244     SCAM(ref_put(cam));
    245     cam = NULL;
    246   }
    247   goto exit;
    248 }
    249 
    250 res_T
    251 scam_perspective_get_solid_angle(const struct scam* camera, double* solid_angle)
    252 {
    253   if(!camera || camera->type != SCAM_PERSPECTIVE || !solid_angle)
    254     return RES_BAD_ARG;
    255 
    256   *solid_angle = camera->param.persp.solid_angle;
    257   return RES_OK;
    258 }
    259 
    260 res_T
    261 scam_focal_length_to_field_of_view
    262   (const double lens_radius,
    263    const double focal_length,
    264    double* field_of_view)
    265 {
    266   if(lens_radius < 0 || focal_length <= 0 || !field_of_view)
    267     return RES_BAD_ARG;
    268   *field_of_view = 2 * atan(lens_radius /focal_length);
    269   return RES_OK;
    270 }
    271 
    272 res_T
    273 scam_field_of_view_to_focal_length
    274   (const double lens_radius,
    275    const double field_of_view,
    276    double* focal_length)
    277 {
    278   if(lens_radius < 0 || field_of_view <= 0 || field_of_view >= PI || !focal_length)
    279     return RES_BAD_ARG;
    280   *focal_length = lens_radius / tan(field_of_view*0.5);
    281   return RES_OK;
    282 }
    283 
    284 /*******************************************************************************
    285  * Local function
    286  ******************************************************************************/
    287 void
    288 perspective_generate_ray
    289   (const struct scam* cam,
    290    const struct scam_sample* sample,
    291    struct scam_ray* ray)
    292 {
    293   ASSERT(cam && cam->type == SCAM_PERSPECTIVE);
    294   if(cam->param.persp.lens_radius == 0) {
    295     pinhole_generate_ray(cam, sample, ray);
    296   } else {
    297     thin_lens_generate_ray(cam, sample, ray);
    298   }
    299 }