mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-01 12:46:12 +00:00
a/tracking: Introduce t_camera_models.h
This commit is contained in:
parent
3cdc02c1bb
commit
bfdeaa7d8f
555
src/xrt/auxiliary/tracking/t_camera_models.h
Normal file
555
src/xrt/auxiliary/tracking/t_camera_models.h
Normal file
|
@ -0,0 +1,555 @@
|
|||
// Copyright 2023, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Simple, untemplated, C, float-only, camera (un)projection functions for various camera models.
|
||||
* @author Moses Turner <moses@collabora.com>
|
||||
* @ingroup aux_tracking
|
||||
*
|
||||
* Some notes:
|
||||
* These functions should return exactly the same values as basalt-headers, down to floating point bits.
|
||||
*
|
||||
* They were mainly written as an expedient way to stop depending on OpenCV-based (un)projection code in Monado's hand
|
||||
* tracking code, and to encourage compiler optimizations through inlining.
|
||||
*
|
||||
* Current users:
|
||||
*
|
||||
* * Mercury hand tracking
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "math/m_vec2.h"
|
||||
#include "math/m_matrix_2x2.h"
|
||||
#include "math/m_mathinclude.h"
|
||||
|
||||
#include "t_tracking.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/*!
|
||||
* Floating point parameters for @ref T_DISTORTION_FISHEYE_KB4
|
||||
* @ingroup aux_tracking
|
||||
*/
|
||||
struct t_camera_calibration_kb4_params_float
|
||||
{
|
||||
float k1, k2, k3, k4;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Floating point parameters for @ref T_DISTORTION_OPENCV_RT8, also including metric_radius.
|
||||
* @ingroup aux_tracking
|
||||
*/
|
||||
struct t_camera_calibration_rt8_params_float
|
||||
{
|
||||
float k1, k2, p1, p2, k3, k4, k5, k6, metric_radius;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Floating point calibration data for a single calibrated camera.
|
||||
* @note This is basically @ref t_camera_calibration, just without some compatibility stuff and using single floats
|
||||
* instead of doubles.
|
||||
* @ingroup aux_tracking
|
||||
*/
|
||||
struct t_camera_model_params
|
||||
{
|
||||
float fx, fy, cx, cy;
|
||||
union {
|
||||
struct t_camera_calibration_kb4_params_float fisheye;
|
||||
struct t_camera_calibration_rt8_params_float rt8;
|
||||
};
|
||||
// This model gets reinterpreted from values in the main t_camera_calibration struct to either
|
||||
// * T_DISTORTION_FISHEYE_KB4
|
||||
// * T_DISTORTION_OPENCV_RADTAN_8
|
||||
enum t_camera_distortion_model model;
|
||||
};
|
||||
|
||||
|
||||
const float SQRT_EPSILON = 0.00316; // sqrt(1e-05)
|
||||
|
||||
/*
|
||||
* Functions for @ref T_DISTORTION_FISHEYE_KB4 (un)projections
|
||||
*/
|
||||
|
||||
static inline float
|
||||
kb4_calc_r_theta(const struct t_camera_model_params *dist, //
|
||||
const float theta, //
|
||||
const float theta2)
|
||||
{
|
||||
float r_theta = dist->fisheye.k4 * theta2;
|
||||
r_theta += dist->fisheye.k3;
|
||||
r_theta *= theta2;
|
||||
r_theta += dist->fisheye.k2;
|
||||
r_theta *= theta2;
|
||||
r_theta += dist->fisheye.k1;
|
||||
r_theta *= theta2;
|
||||
r_theta += 1.0f;
|
||||
r_theta *= theta;
|
||||
|
||||
return r_theta;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
kb4_project(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
const float z, //
|
||||
float *out_x, //
|
||||
float *out_y)
|
||||
{
|
||||
bool is_valid = true;
|
||||
const float r2 = x * x + y * y;
|
||||
const float r = sqrtf(r2);
|
||||
|
||||
if (r > SQRT_EPSILON) {
|
||||
|
||||
|
||||
const float theta = atan2(r, z);
|
||||
const float theta2 = theta * theta;
|
||||
|
||||
float r_theta = kb4_calc_r_theta(dist, theta, theta2);
|
||||
|
||||
const float mx = x * r_theta / r;
|
||||
const float my = y * r_theta / r;
|
||||
|
||||
*out_x = dist->fx * mx + dist->cx;
|
||||
*out_y = dist->fy * my + dist->cy;
|
||||
} else {
|
||||
// Check that the point is not cloze to zero norm
|
||||
if (z < SQRT_EPSILON) {
|
||||
is_valid = false;
|
||||
}
|
||||
*out_x = dist->fx * x / z + dist->cx;
|
||||
*out_y = dist->fy * y / z + dist->cy;
|
||||
}
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static inline float
|
||||
kb4_solve_theta(const struct t_camera_model_params *dist, const float *r_theta, float *d_func_d_theta)
|
||||
{
|
||||
float theta = *r_theta;
|
||||
for (int i = 4; i > 0; i--) {
|
||||
float theta2 = theta * theta;
|
||||
|
||||
float func = dist->fisheye.k4 * theta2;
|
||||
func += dist->fisheye.k3;
|
||||
func *= theta2;
|
||||
func += dist->fisheye.k2;
|
||||
func *= theta2;
|
||||
func += dist->fisheye.k1;
|
||||
func *= theta2;
|
||||
func += 1.0f;
|
||||
func *= theta;
|
||||
|
||||
*d_func_d_theta = 9.0f * dist->fisheye.k4 * theta2;
|
||||
*d_func_d_theta += 7.0f * dist->fisheye.k3;
|
||||
*d_func_d_theta *= theta2;
|
||||
*d_func_d_theta += 5.0f * dist->fisheye.k2;
|
||||
*d_func_d_theta *= theta2;
|
||||
*d_func_d_theta += 3.0f * dist->fisheye.k1;
|
||||
*d_func_d_theta *= theta2;
|
||||
*d_func_d_theta += 1.0f;
|
||||
|
||||
// Iteration of Newton method
|
||||
theta += ((*r_theta) - func) / (*d_func_d_theta);
|
||||
}
|
||||
|
||||
return theta;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
kb4_unproject(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
float *out_x, //
|
||||
float *out_y, //
|
||||
float *out_z)
|
||||
{
|
||||
const float mx = (x - dist->cx) / dist->fx;
|
||||
const float my = (y - dist->cy) / dist->fy;
|
||||
|
||||
float theta(0);
|
||||
float sin_theta(0);
|
||||
float cos_theta(1);
|
||||
float thetad = sqrt(mx * mx + my * my);
|
||||
float scaling(1);
|
||||
float d_func_d_theta(0);
|
||||
|
||||
if (thetad > SQRT_EPSILON) {
|
||||
theta = kb4_solve_theta(dist, &thetad, &d_func_d_theta);
|
||||
|
||||
sin_theta = sin(theta);
|
||||
cos_theta = cos(theta);
|
||||
scaling = sin_theta / thetad;
|
||||
}
|
||||
|
||||
*out_x = mx * scaling;
|
||||
*out_y = my * scaling;
|
||||
*out_z = cos_theta;
|
||||
|
||||
//!@todo I'm not 100% sure if kb4 is always non-injective. basalt-headers always returns true here, so it might
|
||||
//! be wrong too.
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for radial-tangential (un)projections
|
||||
*/
|
||||
|
||||
static inline bool
|
||||
rt8_project(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
const float z, //
|
||||
float *out_x, //
|
||||
float *out_y)
|
||||
{
|
||||
const float xp = x / z;
|
||||
const float yp = y / z;
|
||||
const float rp2 = xp * xp + yp * yp;
|
||||
const float cdist = (1.0f + rp2 * (dist->rt8.k1 + rp2 * (dist->rt8.k2 + rp2 * dist->rt8.k3))) /
|
||||
(1.0f + rp2 * (dist->rt8.k4 + rp2 * (dist->rt8.k5 + rp2 * dist->rt8.k6)));
|
||||
const float deltaX = 2.0f * dist->rt8.p1 * xp * yp + dist->rt8.p2 * (rp2 + 2.0f * xp * xp);
|
||||
const float deltaY = 2.0f * dist->rt8.p2 * xp * yp + dist->rt8.p1 * (rp2 + 2.0f * yp * yp);
|
||||
const float xpp = xp * cdist + deltaX;
|
||||
const float ypp = yp * cdist + deltaY;
|
||||
const float u = dist->fx * xpp + dist->cx;
|
||||
const float v = dist->fy * ypp + dist->cy;
|
||||
|
||||
*out_x = u;
|
||||
*out_y = v;
|
||||
|
||||
const float rpmax = dist->rt8.metric_radius;
|
||||
bool positive_z = z >= SQRT_EPSILON; // Sophus::Constants<Scalar>::epsilonSqrt();
|
||||
bool in_injective_area = rpmax == 0.0f ? true : rp2 <= rpmax * rpmax;
|
||||
bool is_valid = positive_z && in_injective_area;
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static inline void
|
||||
rt8_distort(const t_camera_model_params *params,
|
||||
const xrt_vec2 *undist,
|
||||
xrt_vec2 *dist,
|
||||
xrt_matrix_2x2 *d_dist_d_undist)
|
||||
{
|
||||
const float &k1 = params->rt8.k1;
|
||||
const float &k2 = params->rt8.k2;
|
||||
const float &p1 = params->rt8.p1;
|
||||
const float &p2 = params->rt8.p2;
|
||||
const float &k3 = params->rt8.k3;
|
||||
const float &k4 = params->rt8.k4;
|
||||
const float &k5 = params->rt8.k5;
|
||||
const float &k6 = params->rt8.k6;
|
||||
|
||||
const float xp = undist->x;
|
||||
const float yp = undist->y;
|
||||
const float rp2 = xp * xp + yp * yp;
|
||||
const float cdist = (1.0f + rp2 * (k1 + rp2 * (k2 + rp2 * k3))) / (1.0f + rp2 * (k4 + rp2 * (k5 + rp2 * k6)));
|
||||
const float deltaX = 2.0f * p1 * xp * yp + p2 * (rp2 + 2.0f * xp * xp);
|
||||
const float deltaY = 2.0f * p2 * xp * yp + p1 * (rp2 + 2.0f * yp * yp);
|
||||
const float xpp = xp * cdist + deltaX;
|
||||
const float ypp = yp * cdist + deltaY;
|
||||
dist->x = xpp;
|
||||
dist->y = ypp;
|
||||
|
||||
// Jacobian part!
|
||||
// Expressions derived with sympy
|
||||
const float v0 = xp * xp;
|
||||
const float v1 = yp * yp;
|
||||
const float v2 = v0 + v1;
|
||||
const float v3 = k6 * v2;
|
||||
const float v4 = k4 + v2 * (k5 + v3);
|
||||
const float v5 = v2 * v4 + 1.0f;
|
||||
const float v6 = v5 * v5;
|
||||
const float v7 = 1.0f / v6;
|
||||
const float v8 = p1 * yp;
|
||||
const float v9 = p2 * xp;
|
||||
const float v10 = 2.0f * v6;
|
||||
const float v11 = k3 * v2;
|
||||
const float v12 = k1 + v2 * (k2 + v11);
|
||||
const float v13 = v12 * v2 + 1.0f;
|
||||
const float v14 = v13 * (v2 * (k5 + 2.0f * v3) + v4);
|
||||
const float v15 = 2.0f * v14;
|
||||
const float v16 = v12 + v2 * (k2 + 2.0f * v11);
|
||||
const float v17 = 2.0f * v16;
|
||||
const float v18 = xp * yp;
|
||||
const float v19 = 2.0f * v7 * (-v14 * v18 + v16 * v18 * v5 + v6 * (p1 * xp + p2 * yp));
|
||||
|
||||
const float dxpp_dxp = v7 * (-v0 * v15 + v10 * (v8 + 3.0f * v9) + v5 * (v0 * v17 + v13));
|
||||
const float dxpp_dyp = v19;
|
||||
const float dypp_dxp = v19;
|
||||
const float dypp_dyp = v7 * (-v1 * v15 + v10 * (3.0f * v8 + v9) + v5 * (v1 * v17 + v13));
|
||||
|
||||
d_dist_d_undist->v[0] = dxpp_dxp;
|
||||
d_dist_d_undist->v[1] = dxpp_dyp;
|
||||
d_dist_d_undist->v[2] = dypp_dxp;
|
||||
d_dist_d_undist->v[3] = dypp_dyp;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
rt8_unproject(
|
||||
const struct t_camera_model_params *hg_dist, const float u, const float v, float *out_x, float *out_y, float *out_z)
|
||||
{
|
||||
|
||||
const float x0 = (u - hg_dist->cx) / hg_dist->fx;
|
||||
const float y0 = (v - hg_dist->cy) / hg_dist->fy;
|
||||
|
||||
//! @todo Decide if besides rpmax, it could be useful to have an rppmax
|
||||
//! field. A good starting point to having this would be using the sqrt of
|
||||
//! the max rpp2 value computed in the optimization of `computeRpmax()`.
|
||||
|
||||
// Newton solver
|
||||
struct xrt_vec2 dist = {x0, y0};
|
||||
struct xrt_vec2 undist = dist;
|
||||
|
||||
const int N = 5; // Max iterations
|
||||
for (int i = 0; i < N; i++) {
|
||||
struct xrt_matrix_2x2 J;
|
||||
struct xrt_vec2 fundist;
|
||||
|
||||
rt8_distort(hg_dist, &undist, &fundist, &J);
|
||||
struct xrt_vec2 residual = m_vec2_sub(fundist, dist);
|
||||
|
||||
// fundist - dist;
|
||||
struct xrt_matrix_2x2 J_inverse;
|
||||
|
||||
m_mat2x2_invert(&J, &J_inverse);
|
||||
|
||||
struct xrt_vec2 undist_sub;
|
||||
|
||||
m_mat2x2_transform_vec2(&J_inverse, &residual, &undist_sub);
|
||||
|
||||
undist = m_vec2_sub(undist, undist_sub);
|
||||
if (m_vec2_len(residual) < SQRT_EPSILON) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const float xp = undist.x;
|
||||
const float yp = undist.y;
|
||||
|
||||
|
||||
const float norm_inv = 1.0f / sqrt(xp * xp + yp * yp + 1.0f);
|
||||
*out_x = xp * norm_inv;
|
||||
*out_y = yp * norm_inv;
|
||||
*out_z = norm_inv;
|
||||
|
||||
|
||||
|
||||
const float rp2 = xp * xp + yp * yp;
|
||||
bool in_injective_area =
|
||||
hg_dist->rt8.metric_radius == 0.0f ? true : rp2 <= hg_dist->rt8.metric_radius * hg_dist->rt8.metric_radius;
|
||||
bool is_valid = in_injective_area;
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline bool
|
||||
zero_distortion_pinhole_project(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
const float z, //
|
||||
float *out_x, //
|
||||
float *out_y)
|
||||
{
|
||||
*out_x = ((dist->fx * x / z) + dist->cx);
|
||||
*out_y = ((dist->fy * y / z) + dist->cy);
|
||||
|
||||
bool is_valid = z >= SQRT_EPSILON;
|
||||
return is_valid;
|
||||
}
|
||||
static inline bool
|
||||
zero_distortion_pinhole_unproject(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
float *out_x, //
|
||||
float *out_y, //
|
||||
float *out_z)
|
||||
{
|
||||
const float mx = (x - dist->cx) / dist->fx;
|
||||
const float my = (y - dist->cy) / dist->fy;
|
||||
|
||||
const float r2 = mx * mx + my * my;
|
||||
|
||||
const float norm = sqrtf(1.0f + r2);
|
||||
|
||||
const float norm_inv = 1.0f / norm;
|
||||
|
||||
*out_x = mx * norm_inv;
|
||||
*out_y = my * norm_inv;
|
||||
*out_z = norm_inv;
|
||||
|
||||
// Pinhole unprojection is always valid :)
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Misc functions.
|
||||
*/
|
||||
|
||||
static inline void
|
||||
interpret_as_rt8(const struct t_camera_calibration *cc, struct t_camera_model_params *out_params)
|
||||
{
|
||||
// Make a temporary buffer that definitely has zeros in it.
|
||||
double distortion_tmp[XRT_DISTORTION_MAX_DIM] = {0};
|
||||
|
||||
if (cc->distortion_model != T_DISTORTION_OPENCV_RADTAN_8) {
|
||||
U_LOG_W("Reinterpreting %s distortion as %s", t_stringify_camera_distortion_model(cc->distortion_model),
|
||||
t_stringify_camera_distortion_model(T_DISTORTION_OPENCV_RADTAN_8));
|
||||
}
|
||||
|
||||
size_t dist_num = t_num_params_from_distortion_model(cc->distortion_model);
|
||||
|
||||
for (size_t i = 0; i < dist_num; i++) {
|
||||
// Copy only the valid values over. The high indices will be zero, which means that rt4 and rt5
|
||||
// calibrations will work correctly.
|
||||
distortion_tmp[i] = cc->distortion_parameters_as_array[i];
|
||||
}
|
||||
|
||||
int acc_idx = 0;
|
||||
out_params->rt8.k1 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.k2 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.p1 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.p2 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.k3 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.k4 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.k5 = distortion_tmp[acc_idx++];
|
||||
out_params->rt8.k6 = distortion_tmp[acc_idx++];
|
||||
|
||||
|
||||
|
||||
if (cc->distortion_model == T_DISTORTION_WMR) {
|
||||
out_params->rt8.metric_radius = cc->wmr.rpmax;
|
||||
} else {
|
||||
out_params->rt8.metric_radius = 0;
|
||||
}
|
||||
out_params->model = T_DISTORTION_OPENCV_RADTAN_8;
|
||||
}
|
||||
|
||||
/*
|
||||
* "Exported" functions.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Takes a @ref t_camera_calibration through \p cc, and returns a @ref t_camera_model_params that shadows \p cc
|
||||
* 's parameters through \p out_params
|
||||
*/
|
||||
static inline void
|
||||
t_camera_model_params_from_t_camera_calibration(const struct t_camera_calibration *cc,
|
||||
struct t_camera_model_params *out_params)
|
||||
{
|
||||
// Paranoia.
|
||||
U_ZERO(out_params);
|
||||
|
||||
// First row, first column.
|
||||
out_params->fx = (float)cc->intrinsics[0][0];
|
||||
// Second row, second column.
|
||||
out_params->fy = (float)cc->intrinsics[1][1];
|
||||
// First row, third column.
|
||||
out_params->cx = (float)cc->intrinsics[0][2];
|
||||
// Second row, third column.
|
||||
out_params->cy = (float)cc->intrinsics[1][2];
|
||||
|
||||
out_params->model = cc->distortion_model;
|
||||
|
||||
|
||||
|
||||
switch (cc->distortion_model) {
|
||||
case T_DISTORTION_FISHEYE_KB4: {
|
||||
out_params->fisheye.k1 = (float)cc->kb4.k1;
|
||||
out_params->fisheye.k2 = (float)cc->kb4.k2;
|
||||
out_params->fisheye.k3 = (float)cc->kb4.k3;
|
||||
out_params->fisheye.k4 = (float)cc->kb4.k4;
|
||||
} break;
|
||||
case T_DISTORTION_OPENCV_RADTAN_14:
|
||||
case T_DISTORTION_OPENCV_RADTAN_5: break;
|
||||
case T_DISTORTION_OPENCV_RADTAN_8:
|
||||
case T_DISTORTION_WMR: interpret_as_rt8(cc, out_params); break;
|
||||
default:
|
||||
U_LOG_E("t_camera_un_projections doesn't support camera model %s yet!",
|
||||
t_stringify_camera_distortion_model(cc->distortion_model));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Takes a 2D image-space point through \p x and \p y, unprojects it to a normalized 3D direction, and returns
|
||||
* the result through \p out_x, \p out_y and \p out_z
|
||||
*/
|
||||
static inline bool
|
||||
t_camera_models_unproject(
|
||||
const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
|
||||
{
|
||||
switch (dist->model) {
|
||||
case T_DISTORTION_OPENCV_RADTAN_8: {
|
||||
return rt8_unproject(dist, x, y, out_x, out_y, out_z);
|
||||
}; break;
|
||||
case T_DISTORTION_FISHEYE_KB4: {
|
||||
return kb4_unproject(dist, x, y, out_x, out_y, out_z);
|
||||
}; break;
|
||||
// Return false so we don't get warnings on Release builds.
|
||||
default: assert(false); return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Takes a 2D image-space point through \p x and \p y, unprojects it to a normalized 3D direction, flips its Y
|
||||
* and Z values (performing a coordinate space transform from +Z forward -Y up to -Z forward +Y up) and returns the
|
||||
* result through \p out_x, \p out_y and \p out_z
|
||||
*/
|
||||
static inline bool
|
||||
t_camera_models_unproject_and_flip(
|
||||
const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
|
||||
{
|
||||
bool ret = t_camera_models_unproject(dist, x, y, out_x, out_y, out_z);
|
||||
|
||||
*out_z *= -1;
|
||||
*out_y *= -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* Takes a 3D point through \p x, \p y, and \p z, projects it into image space, and returns the result
|
||||
* through \p out_x and \p out_y
|
||||
*/
|
||||
static inline bool
|
||||
t_camera_models_project(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
const float z, //
|
||||
float *out_x, //
|
||||
float *out_y)
|
||||
{
|
||||
switch (dist->model) {
|
||||
case T_DISTORTION_OPENCV_RADTAN_8: {
|
||||
return rt8_project(dist, x, y, z, out_x, out_y);
|
||||
}; break;
|
||||
case T_DISTORTION_FISHEYE_KB4: {
|
||||
return kb4_project(dist, x, y, z, out_x, out_y);
|
||||
}; break;
|
||||
// Return false so we don't get warnings on Release builds.
|
||||
default: assert(false); return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Takes a 3D point through \p x, \p y, and \p z, flips its Y and Z values (performing a coordinate space
|
||||
* transform from -Z forward +Y up to +Z forward -Y up), projects it into image space, and returns the result through
|
||||
* \p out_x and \p out_y
|
||||
*/
|
||||
static inline bool
|
||||
t_camera_models_flip_and_project(const struct t_camera_model_params *dist, //
|
||||
const float x, //
|
||||
const float y, //
|
||||
const float z, //
|
||||
float *out_x, //
|
||||
float *out_y)
|
||||
{
|
||||
float _y = y * -1;
|
||||
float _z = z * -1;
|
||||
|
||||
return t_camera_models_project(dist, x, _y, _z, out_x, out_y);
|
||||
}
|
Loading…
Reference in a new issue