star-camera

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

commit 5ab5050f67bab87e8955aa91a59b3fd7412b9d5c
parent 939fa0923226953c5fef27cceecec4ac2dd87cdc
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri, 23 Jul 2021 16:33:09 +0200

First (untested) implementation of the thin lens camera

Diffstat:
Mcmake/CMakeLists.txt | 2+-
Msrc/scam.c | 4++--
Msrc/scam.h | 30++++++++++++++++++++----------
Msrc/scam_c.h | 34+++++++++++++++++++++++-----------
Asrc/scam_perspective.c | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/scam_pinhole.c | 134-------------------------------------------------------------------------------
Msrc/test_scam_cbox.c | 4++--
Msrc/test_scam_pinhole.c | 34+++++++++++++++++-----------------
8 files changed, 317 insertions(+), 177 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -44,7 +44,7 @@ set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(SCAM_FILES_SRC scam.c scam_log.c - scam_pinhole.c) + scam_perspective.c) set(SCAM_FILES_INC scam_c.h scam_log.h) diff --git a/src/scam.c b/src/scam.c @@ -72,8 +72,8 @@ scam_generate_ray } switch(cam->type) { - case SCAM_PINHOLE: - pinhole_generate_ray(cam, sample, ray); + case SCAM_PERSPECTIVE: + perspective_generate_ray(cam, sample, ray); break; default: FATAL("Unreachable code.\n"); break; } diff --git a/src/scam.h b/src/scam.h @@ -37,7 +37,7 @@ #endif enum scam_type { - SCAM_PINHOLE, + SCAM_PERSPECTIVE, SCAM_TYPES_COUNT__, SCAM_NONE = SCAM_TYPES_COUNT__ }; @@ -57,22 +57,32 @@ struct scam_ray { #define SCAM_RAY_NULL__ {{0,0,0},{0,0,0}} static const struct scam_ray SCAM_RAY_NULL = SCAM_RAY_NULL__; -struct scam_pinhole_args { - double position[3]; /* Focal point */ +struct scam_perspective_args { + double position[3]; /* Lens position */ double target[3]; /* Targeted point. target-position = image plane normal */ double up[3]; /* Vector defining the upward orientation */ double aspect_ratio; /* Image plane aspect ratio (width / height) */ - double field_of_view; /* Vertical field of view in radians */ + double lens_radius; /* Radius of 0 <=> pinhole */ + + /* Vertical field of view in radians. Used when lens_radius == 0 */ + double field_of_view; + + /* Distance to focus on. Used when lens_radius != 0. Note that the focal + * distance is not the focal length that is the distance to the focal point + * behind the lens */ + double focal_distance; }; -#define SCAM_PINHOLE_ARGS_DEFAULT__ { \ +#define SCAM_PERSPECTIVE_ARGS_DEFAULT__ { \ {0,0,0}, /* Position */ \ {0,1,0}, /* Target */ \ {0,0,1}, /* Up */ \ 1.0, /* Aspect ratio */ \ - 1.22173047639603070383, /* ~70 degrees */ \ + 0.0, /* Lens radius */ \ + 1.22173047639603070383, /* Fov ~70 degrees */ \ + 1.0, /* Focal distance. Unused for radius == 0 */ \ } -static const struct scam_pinhole_args SCAM_PINHOLE_ARGS_DEFAULT = - SCAM_PINHOLE_ARGS_DEFAULT__; +static const struct scam_perspective_args SCAM_PERSPECTIVE_ARGS_DEFAULT = + SCAM_PERSPECTIVE_ARGS_DEFAULT__; /* Forward declaration of external data types */ struct logger; @@ -87,11 +97,11 @@ struct scam; BEGIN_DECLS SCAM_API res_T -scam_create_pinhole +scam_create_perspective (struct logger* logger, /* NULL <=> use builtin logger */ struct mem_allocator* allocator, /* NULL <=> use default allocator */ const int verbose, /* Verbosity level */ - struct scam_pinhole_args* args, + struct scam_perspective_args* args, struct scam** camera); SCAM_API res_T diff --git a/src/scam_c.h b/src/scam_c.h @@ -21,20 +21,32 @@ #include <rsys/logger.h> #include <rsys/ref_count.h> -struct pinhole { - /* Orthogonal basis of the camera */ - double axis_x[3]; - double axis_y[3]; - double axis_z[3]; - - /* Focal point */ - double position[3]; +struct perspective { + double screen2world[9]; + double camera2world[9]; + + double position[3]; /* Lens position */ + + double rcp_tan_half_fov; /* 1 / tan(vertical_fov / 2) */ + double aspect_ratio; /* width / height */ + double lens_radius; /* 0 <=> pinhole camera */ + double focal_distance; /* Unused when lens_radius == 0 */ }; +#define PERSPECTIVE_DEFAULT__ { \ + {1,0,0, 0,1,0, 0,0,1}, /* Screen to world transformation */ \ + {1,0,0, 0,1,0, 0,0,1}, /* Camera to world transformation */ \ + {0,0,0}, /* Lens position */ \ + 1.0, /* 1/tan(vertical_fov/2) */ \ + 1.0, /* Aspect ratio */ \ + 0.0, /* Lens radius */ \ + -1.0 /* Focal distance */ \ +} +static const struct perspective PERSPECTIVE_DEFAULT = PERSPECTIVE_DEFAULT__; struct scam { enum scam_type type; union { - struct pinhole pinhole; + struct perspective persp; } param; int verbose; @@ -54,9 +66,9 @@ camera_create struct scam** scam); extern LOCAL_SYM void -pinhole_generate_ray +perspective_generate_ray (const struct scam* cam, - const struct scam_sample* sample, + const struct scam_sample* sample, struct scam_ray* ray); #endif /* SCAM_C_H */ diff --git a/src/scam_perspective.c b/src/scam_perspective.c @@ -0,0 +1,252 @@ +/* Copyright (C) 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "scam_c.h" +#include "scam_log.h" + +#include <rsys/double3.h> +#include <rsys/double33.h> +#include <rsys/math.h> + +#include <math.h> + +/******************************************************************************* + * Helper function + ******************************************************************************/ +static res_T +setup_perspective(struct scam* cam, const struct scam_perspective_args* args) +{ + double x[3], y[3], z[3]; + int pinhole = 0; + res_T res = RES_OK; + ASSERT(cam && args && cam->type == SCAM_PERSPECTIVE); + + cam->param.persp = PERSPECTIVE_DEFAULT; + pinhole = args->lens_radius == 0; + + if(args->aspect_ratio <= 0) { + log_err(cam, + "perspective camera: invalid aspect ratio: %g\n", + args->aspect_ratio); + res = RES_BAD_ARG; + goto error; + } + + if(args->lens_radius < 0) { + log_err(cam, + "perspective camera: invalid negative lens radius: %g\n", + args->lens_radius); + res = RES_BAD_ARG; + goto error; + } + + if(!pinhole && args->focal_distance < 0) { + log_err(cam, + "perspective camera: invalid negative focal distance: %g\n", + args->focal_distance); + } + + if(pinhole && (args->field_of_view <= 0 || args->field_of_view >= PI)) { + log_err(cam, + "perspective camera: invalid vertical field of view: %g\n", + args->field_of_view); + res = RES_BAD_ARG; + goto error; + } + + if(d3_normalize(z, d3_sub(z, args->target, args->position)) <= 0 + || d3_normalize(x, d3_cross(x, z, args->up)) <= 0 + || d3_normalize(y, d3_cross(y, z, x)) <= 0) { + log_err(cam, + "perspective camera: invalid point of view:\n" + " position = %g %g %g\n" + " target = %g %g %g\n" + " up = %g %g %g\n", + SPLIT3(args->position), SPLIT3(args->target), SPLIT3(args->up)); + res = RES_BAD_ARG; + goto error; + } + + cam->param.persp.rcp_tan_half_fov = 1.0/tan(args->field_of_view*0.5); + cam->param.persp.aspect_ratio = args->aspect_ratio; + cam->param.persp.lens_radius = args->lens_radius; + cam->param.persp.focal_distance = args->focal_distance; + + d3_set(cam->param.persp.position, args->position); + + d3_muld(cam->param.persp.screen2world+0, x, args->aspect_ratio); + d3_set (cam->param.persp.screen2world+3, y); + d3_muld(cam->param.persp.screen2world+6, z, cam->param.persp.rcp_tan_half_fov); + + d3_set(cam->param.persp.camera2world+0, x); + d3_set(cam->param.persp.camera2world+3, y); + d3_set(cam->param.persp.camera2world+6, z); + +exit: + return res; +error: + goto exit; +} + +static INLINE void +pinhole_generate_ray + (const struct scam* cam, + const struct scam_sample* sample, + struct scam_ray* ray) +{ + double x[3], y[3], z[3], len; + double pos[3]; + (void)len; + + ASSERT(cam && sample && ray); + ASSERT(cam->param.persp.lens_radius == 0); + ASSERT(cam->type == SCAM_PERSPECTIVE); + ASSERT(0 <= sample->film[0] && sample->film[0] < 1); + ASSERT(0 <= sample->film[1] && sample->film[1] < 1); + + /* Transform the sampled position in screen space */ + pos[0] = sample->film[0]*2-1; + pos[1] = sample->film[1]*2-1; + pos[2] = 1; + + /* Transform the sampled position in world space. Note that no translation is + * performed to directly obtain the (un-normalized) ray direction. */ + d3_muld(x, cam->param.persp.screen2world+0, pos[0]); + d3_muld(y, cam->param.persp.screen2world+3, pos[1]); + d3_set (z, cam->param.persp.screen2world+6); + d3_add(ray->dir, x, y); + d3_add(ray->dir, ray->dir, z); + len = d3_normalize(ray->dir, ray->dir); + ASSERT(len >= 1.e-6); + + /* Setup the ray origin */ + d3_set(ray->org, cam->param.persp.position); +} + +static INLINE void +thin_lens_generate_ray + (const struct scam* cam, + const struct scam_sample* sample, + struct scam_ray* ray) +{ + double focus_pt[3]; + double dir[3]; + double theta; + double len; + double t; + double r; + (void)len; + + ASSERT(cam && sample && ray); + ASSERT(cam->param.persp.lens_radius > 0); + ASSERT(cam->type == SCAM_PERSPECTIVE); + ASSERT(0 <= sample->film[0] && sample->film[0] < 1); + ASSERT(0 <= sample->film[1] && sample->film[1] < 1); + ASSERT(0 <= sample->lens[0] && sample->lens[0] < 1); + ASSERT(0 <= sample->lens[1] && sample->lens[1] < 1); + + /* Transform the sampled position in screen space and use it as the + * (un-normalized) direction starting from the lens center and intersecting + * the sample */ + dir[0] = sample->film[0]*2-1; + dir[1] = sample->film[1]*2-1; + dir[2] = 1; + + /* Transform the computed direction in camera space */ + dir[0] = dir[0] * cam->param.persp.aspect_ratio; + dir[1] = dir[1]; + dir[2] = dir[2] * cam->param.persp.rcp_tan_half_fov; + len = d3_normalize(dir, dir); + ASSERT(len >= 1.e-6); + + /* find the focus point by intersecting dir with the focus plane */ + t = cam->param.persp.focal_distance / dir[2]; + focus_pt[0] = /*0 +*/ t*dir[0]; + focus_pt[1] = /*0 +*/ t*dir[1]; + focus_pt[2] = /*0 +*/ t*dir[2]; + + /* Uniformly sample a position onto the lens in camera space */ + theta = 2 * PI * sample->lens[0]; + r = cam->param.persp.lens_radius * sqrt(sample->lens[1]); + ray->org[0] = r * cos(theta); + ray->org[1] = r * sin(theta); + ray->org[2] = 0; + + /* Compute the ray direction in camera space */ + d3_sub(ray->dir, focus_pt, ray->org); + len = d3_normalize(ray->dir, ray->dir); + ASSERT(len >= 1.e-6); + + /* Transform the ray from camera space to world space */ + d33_muld3(ray->dir, cam->param.persp.camera2world, ray->dir); + d33_muld3(ray->org, cam->param.persp.camera2world, ray->org); + d3_add(ray->org, cam->param.persp.position, ray->org); +} + +/******************************************************************************* + * Exported function + ******************************************************************************/ +SCAM_API res_T +scam_create_perspective + (struct logger* logger, /* NULL <=> use builtin logger */ + struct mem_allocator* allocator, /* NULL <=> use default allocator */ + const int verbose, /* Verbosity level */ + struct scam_perspective_args* args, + struct scam** out_cam) +{ + struct scam* cam = NULL; + res_T res = RES_OK; + + if(!args || !out_cam) { + res = RES_BAD_ARG; + goto error; + } + + res = camera_create(logger, allocator, verbose, SCAM_PERSPECTIVE, &cam); + if(res != RES_OK) goto error; + res = setup_perspective(cam, args); + if(res != RES_OK) goto error; + + exit: + if(out_cam) *out_cam = cam; + return res; +error: + if(cam) { + SCAM(ref_put(cam)); + cam = NULL; + } + goto exit; +} + +/******************************************************************************* + * Local function + ******************************************************************************/ +void +perspective_generate_ray + (const struct scam* cam, + const struct scam_sample* sample, + struct scam_ray* ray) +{ + ASSERT(cam && sample && ray); + ASSERT(cam->type == SCAM_PERSPECTIVE); + ASSERT(0 <= sample->film[0] && sample->film[0] < 1); + ASSERT(0 <= sample->film[1] && sample->film[1] < 1); + + if(cam->param.persp.lens_radius == 0) { + pinhole_generate_ray(cam, sample, ray); + } else { + thin_lens_generate_ray(cam, sample, ray); + } +} diff --git a/src/scam_pinhole.c b/src/scam_pinhole.c @@ -1,134 +0,0 @@ -/* Copyright (C) 2021 |Meso|Star> (contact@meso-star.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "scam_c.h" -#include "scam_log.h" - -#include <rsys/double3.h> -#include <rsys/math.h> - -#include <math.h> - -/******************************************************************************* - * Helper function - ******************************************************************************/ -static res_T -setup_pinhole(struct scam* cam, const struct scam_pinhole_args* args) -{ - double x[3], y[3], z[3]; - double img_plane_depth; - res_T res = RES_OK; - ASSERT(cam && args && cam->type == SCAM_PINHOLE); - - if(args->field_of_view <= 0 || args->field_of_view >= PI) { - log_err(cam, - "pinhole camera: invalid vertical field of view: %g\n", - args->field_of_view); - res = RES_BAD_ARG; - goto error; - } - - if(args->aspect_ratio <= 0) { - log_err(cam, - "pinhole camera: invalid aspect ratio: %g\n", - args->aspect_ratio); - res = RES_BAD_ARG; - goto error; - } - - if(d3_normalize(z, d3_sub(z, args->target, args->position)) <= 0 - || d3_normalize(x, d3_cross(x, z, args->up)) <= 0 - || d3_normalize(y, d3_cross(y, z, x)) <= 0) { - log_err(cam, - "pinhole camera: invalid point of view:\n" - " position = %g %g %g\n" - " target = %g %g %g\n" - " up = %g %g %g\n", - SPLIT3(args->position), SPLIT3(args->target), SPLIT3(args->up)); - res = RES_BAD_ARG; - goto error; - } - - img_plane_depth = 1.0/tan(args->field_of_view*0.5); - d3_muld(cam->param.pinhole.axis_x, x, args->aspect_ratio); - d3_set(cam->param.pinhole.axis_y, y); - d3_muld(cam->param.pinhole.axis_z, z, img_plane_depth); - d3_set(cam->param.pinhole.position, args->position); - -exit: - return res; -error: - goto exit; -} - -/******************************************************************************* - * Exported function - ******************************************************************************/ -SCAM_API res_T -scam_create_pinhole - (struct logger* logger, /* NULL <=> use builtin logger */ - struct mem_allocator* allocator, /* NULL <=> use default allocator */ - const int verbose, /* Verbosity level */ - struct scam_pinhole_args* args, - struct scam** out_cam) -{ - struct scam* cam = NULL; - res_T res = RES_OK; - - if(!args || !out_cam) { - res = RES_BAD_ARG; - goto error; - } - - res = camera_create(logger, allocator, verbose, SCAM_PINHOLE, &cam); - if(res != RES_OK) goto error; - res = setup_pinhole(cam, args); - if(res != RES_OK) goto error; - - exit: - if(out_cam) *out_cam = cam; - return res; -error: - if(cam) { - SCAM(ref_put(cam)); - cam = NULL; - } - goto exit; -} - -/******************************************************************************* - * Local function - ******************************************************************************/ -void -pinhole_generate_ray - (const struct scam* cam, - const struct scam_sample* sample, - struct scam_ray* ray) -{ - double x[3], y[3], len; - (void)len; - - ASSERT(cam && sample && ray); - ASSERT(cam->type == SCAM_PINHOLE); - ASSERT(0 <= sample->film[0] && sample->film[0] < 1); - ASSERT(0 <= sample->film[1] && sample->film[1] < 1); - - d3_muld(x, cam->param.pinhole.axis_x, sample->film[0]*2-1); - d3_muld(y, cam->param.pinhole.axis_y, sample->film[1]*2-1); - d3_add(ray->dir, d3_add(ray->dir, x, y), cam->param.pinhole.axis_z); - len = d3_normalize(ray->dir, ray->dir); - ASSERT(len >= 1.e-6); - d3_set(ray->org, cam->param.pinhole.position); -} diff --git a/src/test_scam_cbox.c b/src/test_scam_cbox.c @@ -127,7 +127,7 @@ draw int main(int argc, char** argv) { - struct scam_pinhole_args args = SCAM_PINHOLE_ARGS_DEFAULT; + struct scam_perspective_args args = SCAM_PERSPECTIVE_ARGS_DEFAULT; struct s3d_scene_view* view = NULL; struct scam* cam = NULL; struct image img; @@ -140,7 +140,7 @@ main(int argc, char** argv) d3(args.up, 0, 0, 1); args.aspect_ratio = (double)IMG_WIDTH/(double)IMG_HEIGHT; args.field_of_view = PI*0.25; - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_OK); image_init(NULL, &img); CHK(image_setup diff --git a/src/test_scam_pinhole.c b/src/test_scam_pinhole.c @@ -36,7 +36,7 @@ int main(int argc, char** argv) { struct logger logger; - struct scam_pinhole_args args = SCAM_PINHOLE_ARGS_DEFAULT; + struct scam_perspective_args args = SCAM_PERSPECTIVE_ARGS_DEFAULT; struct scam_sample sample = SCAM_SAMPLE_NULL; struct scam_ray ray = SCAM_RAY_NULL; struct scam* cam = NULL; @@ -52,14 +52,14 @@ main(int argc, char** argv) enum scam_type type = SCAM_NONE; (void)argc, (void)argv; - CHK(scam_create_pinhole(NULL, NULL, 0, NULL, &cam) == RES_BAD_ARG); - CHK(scam_create_pinhole(NULL, NULL, 0, &args, NULL) == RES_BAD_ARG); - CHK(scam_create_pinhole(NULL, NULL, 0, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, NULL, 0, NULL, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 0, &args, NULL) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 0, &args, &cam) == RES_OK); CHK(scam_get_type(NULL, &type) == RES_BAD_ARG); CHK(scam_get_type(cam, NULL) == RES_BAD_ARG); CHK(scam_get_type(cam, &type) == RES_OK); - CHK(type == SCAM_PINHOLE); + CHK(type == SCAM_PERSPECTIVE); CHK(scam_ref_get(NULL) == RES_BAD_ARG); CHK(scam_ref_get(cam) == RES_OK); @@ -72,34 +72,34 @@ main(int argc, char** argv) logger_set_stream(&logger, LOG_ERROR, log_stream, NULL); logger_set_stream(&logger, LOG_WARNING, log_stream, NULL); - CHK(scam_create_pinhole(&logger, NULL, 0, &args, &cam) == RES_OK); + CHK(scam_create_perspective(&logger, NULL, 0, &args, &cam) == RES_OK); CHK(scam_ref_put(cam) == RES_OK); - CHK(scam_create_pinhole(NULL, &mem_default_allocator, 0, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, &mem_default_allocator, 0, &args, &cam) == RES_OK); CHK(scam_ref_put(cam) == RES_OK); d3_set(args.target, args.position); - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); d3(args.position, 0, 0, 0); d3(args.target, 0, 1, 0); d3(args.up, 0, 1, 0); - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); - args = SCAM_PINHOLE_ARGS_DEFAULT; + args = SCAM_PERSPECTIVE_ARGS_DEFAULT; args.aspect_ratio = 0; - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); - args = SCAM_PINHOLE_ARGS_DEFAULT; + args = SCAM_PERSPECTIVE_ARGS_DEFAULT; args.field_of_view = 0; - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); args.field_of_view = PI; - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_BAD_ARG); args.field_of_view = nextafter(0, PI); - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_OK); CHK(scam_ref_put(cam) == RES_OK); args.field_of_view = nextafter(PI, 0); - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_OK); CHK(scam_ref_put(cam) == RES_OK); args.position[0] = rand_canonical(); @@ -114,7 +114,7 @@ main(int argc, char** argv) args.field_of_view = PI/2.0; args.aspect_ratio = 4.0/3.0; - CHK(scam_create_pinhole(NULL, NULL, 1, &args, &cam) == RES_OK); + CHK(scam_create_perspective(NULL, NULL, 1, &args, &cam) == RES_OK); /* Precompute some view frustum constants */ d3_normalize(axis_z, d3_sub(axis_z, args.target, args.position));