Feat: support general single-frame cameras and add a near-horizon example
This commit is contained in:
1 parent
94149d75e4
commit
6ae223a642
15 files changed
+771
-166
No files matched your search
+2
-1
@@ -413,7 +413,8 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
|
||||
return -1;
|
||||
}
|
||||
if (config->max_level == 0)
|
||||
return 0;
|
||||
/* Coarse vertices still need tracing when refinement is disabled. */
|
||||
return (int)mesh->sample_count;
|
||||
/* Every generation may batch newly inserted vertices with probes for its
|
||||
* new leaves: probe positions depend only on image-plane geometry. Their
|
||||
* endpoints are considered only after this complete generation finishes. */
|
||||
|
||||
+125
-35
@@ -28,7 +28,9 @@ typedef struct {
|
||||
double psf_relative_tail;
|
||||
double psf_min_y;
|
||||
double observer_radius;
|
||||
double observer_inward_speed;
|
||||
double observer_position[3], observer_velocity[3], camera_roll_deg;
|
||||
int position_specified, velocity_specified, look_specified;
|
||||
int radius_specified, roll_specified;
|
||||
PointSpreadFunction psf;
|
||||
PsfKernelCache psf_cache;
|
||||
const char *catalog_path;
|
||||
@@ -83,14 +85,14 @@ static int parse_ra_deg(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
*value = strtod(text, &end);
|
||||
return errno || *end || *value < 0.0 || *value >= 360.0 ? -1 : 0;
|
||||
return errno || end == text || *end || !isfinite(*value) || *value < 0.0 || *value >= 360.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_dec_deg(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
*value = strtod(text, &end);
|
||||
return errno || *end || *value < -90.0 || *value > 90.0 ? -1 : 0;
|
||||
return errno || end == text || *end || !isfinite(*value) || *value < -90.0 || *value > 90.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_positive(const char *text, double *value) {
|
||||
@@ -107,11 +109,11 @@ static int parse_nonnegative(const char *text, double *value) {
|
||||
return errno || *end || !isfinite(*value) || *value < 0.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_speed(const char *text, double *value) {
|
||||
static int parse_finite(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
*value = strtod(text, &end);
|
||||
return errno || *end || *value < 0.0 || *value >= 1.0 ? -1 : 0;
|
||||
return errno || end == text || *end || !isfinite(*value) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_moffat_beta(const char *text, double *value) {
|
||||
@@ -132,7 +134,7 @@ static int parse_finite_positive(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
*value = strtod(text, &end);
|
||||
return errno || *end || !isfinite(*value) || *value <= 0.0 ? -1 : 0;
|
||||
return errno || end == text || *end || !isfinite(*value) || *value <= 0.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int validate_tonemapped_output_path(const char *path) {
|
||||
@@ -256,14 +258,26 @@ static int parse_args(int argc, char **argv, Settings *s,
|
||||
s->fov_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--look-ra-deg") && i + 1 < argc &&
|
||||
!parse_ra_deg(argv[++i], &s->look_ra_deg)) {
|
||||
s->look_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--look-dec-deg") && i + 1 < argc &&
|
||||
!parse_dec_deg(argv[++i], &s->look_dec_deg)) {
|
||||
s->look_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--exposure") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->exposure)) {
|
||||
} else if (!strcmp(argv[i], "--observer-inward-speed") && i + 1 < argc &&
|
||||
!parse_speed(argv[++i], &s->observer_inward_speed)) {
|
||||
} else if ((!strcmp(argv[i], "--observer-position") ||
|
||||
!strcmp(argv[i], "--observer-velocity")) && i + 3 < argc) {
|
||||
const int position = !strcmp(argv[i], "--observer-position");
|
||||
double *v = position ? s->observer_position : s->observer_velocity;
|
||||
for (int component = 0; component < 3; ++component)
|
||||
if (parse_finite(argv[++i], &v[component])) return -1;
|
||||
if (position) s->position_specified = 1;
|
||||
else s->velocity_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--camera-roll-deg") && i + 1 < argc &&
|
||||
!parse_finite(argv[++i], &s->camera_roll_deg)) {
|
||||
s->roll_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--observer-radius") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->observer_radius)) {
|
||||
!parse_finite_positive(argv[++i], &s->observer_radius)) {
|
||||
s->radius_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--psf-fwhm-pixels") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->psf.fwhm_pixels)) {
|
||||
} else if (!strcmp(argv[i], "--psf-moffat-beta") && i + 1 < argc &&
|
||||
@@ -339,8 +353,18 @@ static void print_help(const char *program) {
|
||||
" --look-ra-deg D ICRS look direction right ascension in degrees (default: 90)\n"
|
||||
" --look-dec-deg D ICRS look direction declination in degrees (default: -90)\n"
|
||||
" --exposure E Linear exposure multiplier (default: 1e-3)\n"
|
||||
" --observer-radius R Observer radius in Schwarzschild units (default: 30)\n"
|
||||
" --observer-inward-speed V Inward observer speed as a fraction of c (default: 0)\n"
|
||||
" --observer-position X Y Z Coordinate position; alone implies looking at the origin\n"
|
||||
" --observer-radius R Infer position = -R * look direction (default R: 30); conflicts with position\n"
|
||||
" --observer-velocity VX VY VZ Coordinate dx/dt, dy/dt, dz/dt (default: 0 0 0); must be timelike\n"
|
||||
" --camera-roll-deg ANGLE Rotate up toward right about forward (default: 0)\n"
|
||||
" Explicit look alone implies position = -R * look direction.\n"
|
||||
" Look is projected into the moving camera rest space.\n"
|
||||
#ifdef SPACETIME_SCHWARZSCHILD
|
||||
" Default position: (0,0,30); look RA=90, Dec=-90.\n"
|
||||
#else
|
||||
" Default position: (0,0,0); look RA=90, Dec=-90.\n"
|
||||
#endif
|
||||
" Single-frame camera options conflict with track/map input.\n"
|
||||
"\nPSF and catalog splatting:\n"
|
||||
" --psf-fwhm-pixels N Moffat PSF FWHM in pixels (default: 2.7)\n"
|
||||
" --psf-moffat-beta N Moffat PSF beta, greater than 1 (default: 4.5)\n"
|
||||
@@ -483,15 +507,78 @@ static GeodesicTraceConfig trace_config(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static int default_observer(const Settings *s, ObserverState *observer) {
|
||||
static int resolve_camera(Settings *s) {
|
||||
const int camera_specified = s->position_specified || s->look_specified ||
|
||||
s->radius_specified || s->velocity_specified || s->roll_specified;
|
||||
if (s->position_specified && s->radius_specified) {
|
||||
fputs("--observer-position and --observer-radius are mutually exclusive.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
if (camera_specified && (s->observer_track_path || s->frames_dir ||
|
||||
s->lens_map_input_path)) {
|
||||
fputs("Single-frame camera options cannot be combined with movie/observer-track or --lens-map-input.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
if (s->position_specified && !s->look_specified) {
|
||||
const double *x = s->observer_position;
|
||||
const double radius = hypot(hypot(x[0], x[1]), x[2]);
|
||||
if (!isfinite(radius) || radius == 0.0) {
|
||||
fputs("Cannot infer a look direction from this position; specify --look-ra-deg and/or --look-dec-deg.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
const double degrees = 180.0 / 3.14159265358979323846;
|
||||
s->look_ra_deg = (x[0] == 0.0 && x[1] == 0.0)
|
||||
? 0.0 : atan2(-x[1], -x[0]) * degrees;
|
||||
if (s->look_ra_deg < 0.0) s->look_ra_deg += 360.0;
|
||||
if (s->look_ra_deg >= 360.0) s->look_ra_deg = 0.0;
|
||||
s->look_dec_deg = atan2(-x[2], hypot(x[0], x[1])) * degrees;
|
||||
}
|
||||
int infer_position = s->look_specified || s->radius_specified;
|
||||
#ifdef SPACETIME_SCHWARZSCHILD
|
||||
return observer_inward_schwarzschild_ks_look_at(
|
||||
1.0, s->observer_radius, s->look_ra_deg, s->look_dec_deg,
|
||||
s->observer_inward_speed, observer);
|
||||
#else
|
||||
*observer = observer_fixed_at_origin_look_at(s->look_ra_deg, s->look_dec_deg);
|
||||
return 0;
|
||||
infer_position = 1;
|
||||
#endif
|
||||
if (!s->position_specified && infer_position) {
|
||||
const ObserverState pointing = observer_fixed_at_origin_look_at(
|
||||
s->look_ra_deg, s->look_dec_deg);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
s->observer_position[i] = -s->observer_radius * pointing.tetrad[1][i + 1];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int build_observer(const Settings *s, const SpacetimeSource *spacetime,
|
||||
ObserverState *observer) {
|
||||
ObserverCamera camera = {.look_ra_deg = s->look_ra_deg,
|
||||
.look_dec_deg = s->look_dec_deg,
|
||||
.roll_deg = s->camera_roll_deg};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
camera.position[i] = s->observer_position[i];
|
||||
camera.velocity[i] = s->observer_velocity[i];
|
||||
}
|
||||
if (spacetime_classify(spacetime, camera.coordinate_time, camera.position) ==
|
||||
SPACETIME_RAY_CAPTURED) {
|
||||
fputs("Camera position is inside the backend capture cutoff or invalid.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
MetricData metric;
|
||||
if (spacetime_eval(spacetime, camera.coordinate_time, camera.position, &metric)) {
|
||||
fputs("Could not evaluate metric at the camera event.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
double q;
|
||||
const ObserverBuildResult result = observer_from_coordinate_camera(
|
||||
&metric, &camera, observer, &q);
|
||||
if (result != OBSERVER_BUILD_OK) {
|
||||
fprintf(stderr, "Camera construction failed (%s): position=(%.17g, %.17g, %.17g), "
|
||||
"coordinate velocity=(%.17g, %.17g, %.17g), Q=%.17g.\n",
|
||||
result == OBSERVER_BUILD_NON_TIMELIKE ? "velocity is not timelike" :
|
||||
result == OBSERVER_BUILD_INVALID_INPUT ? "invalid input/metric" :
|
||||
"invalid tetrad",
|
||||
camera.position[0], camera.position[1], camera.position[2],
|
||||
camera.velocity[0], camera.velocity[1], camera.velocity[2], q);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
@@ -599,14 +686,6 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
|
||||
return result;
|
||||
}
|
||||
|
||||
static int render_frame(const Settings *s, StarCatalog *catalog,
|
||||
const SpacetimeSource *spacetime) {
|
||||
ObserverState observer;
|
||||
return default_observer(s, &observer) ? -1
|
||||
: render_observer_frame(s, catalog, spacetime,
|
||||
&observer, s->output_path);
|
||||
}
|
||||
|
||||
static int frame_output_path(char path[PATH_MAX], const Settings *s,
|
||||
size_t frame_id) {
|
||||
#ifdef ENABLE_PNG
|
||||
@@ -895,7 +974,8 @@ int main(int argc, char **argv) {
|
||||
"Usage: %s [--catalog PATH | --all-sky-catalog DIR] [--output PATH] [--width N] [--height "
|
||||
"N] [--fov-deg D] [--look-ra-deg D] [--look-dec-deg D] "
|
||||
"[--lens-map-input FILE | --lens-map-output FILE] "
|
||||
"[--exposure E] [--observer-radius R] [--observer-inward-speed V] "
|
||||
"[--exposure E] [--observer-radius R | --observer-position X Y Z] "
|
||||
"[--observer-velocity VX VY VZ] [--camera-roll-deg ANGLE] "
|
||||
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
|
||||
"[--max-magnification M] [--max-cache-psf-flux F] "
|
||||
"[--psf-relative-tail R] [--psf-min-y Y] "
|
||||
@@ -946,16 +1026,32 @@ int main(int argc, char **argv) {
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
if (resolve_camera(&settings)) return 2;
|
||||
SpacetimeSource spacetime = {0};
|
||||
ObserverState observer;
|
||||
if (settings.lens_map_input_path == NULL) {
|
||||
if (spacetime_create_default(&spacetime)) {
|
||||
fputs("Could not create spacetime source\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
if (settings.frames_dir == NULL &&
|
||||
build_observer(&settings, &spacetime, &observer)) {
|
||||
spacetime_destroy(&spacetime);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
StarCatalog catalog = {0};
|
||||
if (settings.all_sky_catalog_path != NULL) {
|
||||
if (catalog_load_all_sky(&catalog, settings.all_sky_catalog_path)) {
|
||||
perror(settings.all_sky_catalog_path);
|
||||
spacetime_destroy(&spacetime);
|
||||
return 1;
|
||||
}
|
||||
} else if (catalog_load_csv(&catalog, settings.catalog_path)) {
|
||||
if (catalog_write_octant_grid(settings.catalog_path) ||
|
||||
catalog_load_csv(&catalog, settings.catalog_path)) {
|
||||
perror(settings.catalog_path);
|
||||
spacetime_destroy(&spacetime);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Created test catalog: %s\n", settings.catalog_path);
|
||||
@@ -970,16 +1066,10 @@ int main(int argc, char **argv) {
|
||||
psf_kernel_cache_destroy(&settings.psf_cache);
|
||||
return result == 0 ? 0 : 1;
|
||||
}
|
||||
SpacetimeSource spacetime = {0};
|
||||
if (spacetime_create_default(&spacetime)) {
|
||||
fputs("Could not create spacetime source\n", stderr);
|
||||
catalog_destroy(&catalog);
|
||||
psf_kernel_cache_destroy(&settings.psf_cache);
|
||||
return 1;
|
||||
}
|
||||
int result = settings.frames_dir != NULL
|
||||
? render_movie(&settings, &catalog, &spacetime)
|
||||
: render_frame(&settings, &catalog, &spacetime);
|
||||
: render_observer_frame(&settings, &catalog, &spacetime,
|
||||
&observer, settings.output_path);
|
||||
spacetime_destroy(&spacetime);
|
||||
catalog_destroy(&catalog);
|
||||
psf_kernel_cache_destroy(&settings.psf_cache);
|
||||
|
||||
+85
-75
@@ -1,5 +1,6 @@
|
||||
#include "observer.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -32,84 +33,93 @@ ObserverState observer_fixed_at_origin_look_at(double ra_deg, double dec_deg) {
|
||||
{0.0, right[0], right[1], right[2]}}};
|
||||
}
|
||||
|
||||
static int schwarzschild_look_direction(double ra_deg, double dec_deg,
|
||||
double direction[3], double up[3],
|
||||
double right[3]) {
|
||||
if (!isfinite(ra_deg) || !isfinite(dec_deg) || ra_deg < 0.0 ||
|
||||
ra_deg >= 360.0 || dec_deg < -90.0 || dec_deg > 90.0)
|
||||
return -1;
|
||||
const double ra = ra_deg * pi / 180.0;
|
||||
const double dec = dec_deg * pi / 180.0;
|
||||
const double cos_ra = cos(ra), sin_ra = sin(ra);
|
||||
const double cos_dec = cos(dec), sin_dec = sin(dec);
|
||||
direction[0] = cos_dec * cos_ra;
|
||||
direction[1] = cos_dec * sin_ra;
|
||||
direction[2] = sin_dec;
|
||||
up[0] = -sin_dec * cos_ra;
|
||||
up[1] = -sin_dec * sin_ra;
|
||||
up[2] = cos_dec;
|
||||
right[0] = sin_ra;
|
||||
right[1] = -cos_ra;
|
||||
right[2] = 0.0;
|
||||
return 0;
|
||||
/* Use the 3+1 form directly, including the shift in every four-vector. */
|
||||
static double inner(const MetricData *m, const double a[4], const double b[4]) {
|
||||
double value = -m->alpha * m->alpha * a[0] * b[0];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
value += m->gamma[i][j] * (a[i + 1] + m->beta[i] * a[0]) *
|
||||
(b[j + 1] + m->beta[j] * b[0]);
|
||||
return value;
|
||||
}
|
||||
|
||||
int observer_static_schwarzschild_ks_look_at(double mass, double radius,
|
||||
double look_ra_deg,
|
||||
double look_dec_deg,
|
||||
ObserverState *out) {
|
||||
double direction[3], up[3], right[3];
|
||||
if (out == NULL || mass <= 0.0 || radius <= 2.0 * mass ||
|
||||
schwarzschild_look_direction(look_ra_deg, look_dec_deg, direction, up,
|
||||
right))
|
||||
return -1;
|
||||
const double f = 2.0 * mass / radius;
|
||||
const double normalization = sqrt(1.0 - f);
|
||||
*out = (ObserverState){
|
||||
.coordinate_time = 0.0,
|
||||
.coordinate_position = {-radius * direction[0], -radius * direction[1],
|
||||
-radius * direction[2]},
|
||||
/* e_(0) is the static four-velocity. e_(1) points inward, toward the
|
||||
* origin; its time component makes the tetrad orthonormal in the KS
|
||||
* metric. */
|
||||
.tetrad = {{1.0 / normalization, 0.0, 0.0, 0.0},
|
||||
{-f / normalization, normalization * direction[0],
|
||||
normalization * direction[1], normalization * direction[2]},
|
||||
{0.0, up[0], up[1], up[2]},
|
||||
{0.0, right[0], right[1], right[2]}}};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int observer_static_schwarzschild_ks(double mass, double radius,
|
||||
ObserverState *out) {
|
||||
return observer_static_schwarzschild_ks_look_at(mass, radius, 180.0, 0.0,
|
||||
out);
|
||||
}
|
||||
|
||||
int observer_inward_schwarzschild_ks_look_at(double mass, double radius,
|
||||
double look_ra_deg,
|
||||
double look_dec_deg,
|
||||
double inward_speed,
|
||||
ObserverState *out) {
|
||||
ObserverState static_observer;
|
||||
if (inward_speed < 0.0 || inward_speed >= 1.0 ||
|
||||
observer_static_schwarzschild_ks_look_at(
|
||||
mass, radius, look_ra_deg, look_dec_deg, &static_observer))
|
||||
return -1;
|
||||
|
||||
const double gamma = 1.0 / sqrt(1.0 - inward_speed * inward_speed);
|
||||
*out = static_observer;
|
||||
for (int mu = 0; mu < 4; ++mu) {
|
||||
const double e0 = static_observer.tetrad[0][mu];
|
||||
const double forward = static_observer.tetrad[1][mu];
|
||||
out->tetrad[0][mu] = gamma * (e0 + inward_speed * forward);
|
||||
out->tetrad[1][mu] = gamma * (inward_speed * e0 + forward);
|
||||
static int valid_metric(const MetricData *m) {
|
||||
if (!isfinite(m->alpha) || m->alpha <= 0.0) return 0;
|
||||
double l[3][3] = {{0}};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!isfinite(m->beta[i])) return 0;
|
||||
for (int j = 0; j <= i; ++j) {
|
||||
double value = m->gamma[i][j];
|
||||
const double transposed = m->gamma[j][i];
|
||||
if (!isfinite(value) || !isfinite(transposed) ||
|
||||
fabs(value - transposed) > 32 * DBL_EPSILON *
|
||||
fmax(fabs(value), fabs(transposed)))
|
||||
return 0;
|
||||
for (int k = 0; k < j; ++k) value -= l[i][k] * l[j][k];
|
||||
if (i == j) {
|
||||
if (!isfinite(value) || value <= 0.0) return 0;
|
||||
l[i][j] = sqrt(value);
|
||||
} else l[i][j] = value / l[j][j];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int observer_inward_schwarzschild_ks(double mass, double radius,
|
||||
double inward_speed, ObserverState *out) {
|
||||
return observer_inward_schwarzschild_ks_look_at(mass, radius, 180.0, 0.0,
|
||||
inward_speed, out);
|
||||
ObserverBuildResult observer_from_coordinate_camera(
|
||||
const MetricData *metric, const ObserverCamera *camera,
|
||||
ObserverState *out, double *q) {
|
||||
if (q) *q = NAN;
|
||||
if (!metric || !camera || !out || !valid_metric(metric) ||
|
||||
!isfinite(camera->coordinate_time) ||
|
||||
!isfinite(camera->look_ra_deg) || camera->look_ra_deg < 0.0 ||
|
||||
camera->look_ra_deg >= 360.0 || !isfinite(camera->look_dec_deg) ||
|
||||
fabs(camera->look_dec_deg) > 90.0 || !isfinite(camera->roll_deg))
|
||||
return OBSERVER_BUILD_INVALID_INPUT;
|
||||
ObserverState state = observer_fixed_at_origin_look_at(
|
||||
camera->look_ra_deg, camera->look_dec_deg);
|
||||
state.coordinate_time = camera->coordinate_time;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!isfinite(camera->position[i]) || !isfinite(camera->velocity[i]))
|
||||
return OBSERVER_BUILD_INVALID_INPUT;
|
||||
state.coordinate_position[i] = camera->position[i];
|
||||
state.tetrad[0][i + 1] = camera->velocity[i];
|
||||
}
|
||||
const double norm = inner(metric, state.tetrad[0], state.tetrad[0]);
|
||||
if (q) *q = norm;
|
||||
if (!isfinite(norm) || norm >= 0.0) return OBSERVER_BUILD_NON_TIMELIKE;
|
||||
for (int mu = 0; mu < 4; ++mu) state.tetrad[0][mu] /= sqrt(-norm);
|
||||
for (int a = 1; a < 4; ++a) {
|
||||
/* Modified Gram-Schmidt with reorthogonalization in the observer rest
|
||||
* space; the coordinate forward seed is given first priority. */
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
for (int b = 0; b < a; ++b) {
|
||||
const double projection = inner(metric, state.tetrad[a], state.tetrad[b]);
|
||||
for (int mu = 0; mu < 4; ++mu)
|
||||
state.tetrad[a][mu] -= (b == 0 ? -projection : projection) *
|
||||
state.tetrad[b][mu];
|
||||
}
|
||||
const double length2 = inner(metric, state.tetrad[a], state.tetrad[a]);
|
||||
if (!isfinite(length2) || length2 <= 0.0)
|
||||
return OBSERVER_BUILD_INVALID_TETRAD;
|
||||
for (int mu = 0; mu < 4; ++mu) state.tetrad[a][mu] /= sqrt(length2);
|
||||
}
|
||||
const double roll = remainder(camera->roll_deg, 360.0) * pi / 180.0;
|
||||
for (int mu = 0; mu < 4; ++mu) {
|
||||
const double up = state.tetrad[2][mu], right = state.tetrad[3][mu];
|
||||
state.tetrad[2][mu] = cos(roll) * up + sin(roll) * right;
|
||||
state.tetrad[3][mu] = -sin(roll) * up + cos(roll) * right;
|
||||
}
|
||||
for (int a = 0; a < 4; ++a) {
|
||||
for (int mu = 0; mu < 4; ++mu)
|
||||
if (!isfinite(state.tetrad[a][mu])) return OBSERVER_BUILD_INVALID_TETRAD;
|
||||
for (int b = 0; b <= a; ++b) {
|
||||
const double error = inner(metric, state.tetrad[a], state.tetrad[b]) -
|
||||
(a == b ? (a == 0 ? -1.0 : 1.0) : 0.0);
|
||||
if (!isfinite(error) || fabs(error) > 1e-8)
|
||||
return OBSERVER_BUILD_INVALID_TETRAD;
|
||||
}
|
||||
}
|
||||
if (state.tetrad[0][0] <= 0.0) return OBSERVER_BUILD_INVALID_TETRAD;
|
||||
*out = state;
|
||||
return OBSERVER_BUILD_OK;
|
||||
}
|
||||
+25
-19
@@ -1,6 +1,8 @@
|
||||
#ifndef OBSERVER_H
|
||||
#define OBSERVER_H
|
||||
|
||||
#include "spacetime.h"
|
||||
|
||||
typedef struct {
|
||||
double coordinate_time;
|
||||
double coordinate_position[3];
|
||||
@@ -13,24 +15,28 @@ ObserverState observer_fixed_at_origin(void);
|
||||
* direction. The local spatial axes are (forward, celestial north,
|
||||
* celestial west), so a north-up image has decreasing RA to the right. */
|
||||
ObserverState observer_fixed_at_origin_look_at(double ra_deg, double dec_deg);
|
||||
/* Static camera at Cartesian Kerr--Schild position -radius * look_direction,
|
||||
* directed toward the Schwarzschild black hole at the origin. look_direction
|
||||
* uses the same ICRS-style RA/Dec convention as the flat-space camera. */
|
||||
int observer_static_schwarzschild_ks_look_at(double mass, double radius,
|
||||
double look_ra_deg,
|
||||
double look_dec_deg,
|
||||
ObserverState *out);
|
||||
/* Compatibility shortcut for the +X camera directed toward the origin. */
|
||||
int observer_static_schwarzschild_ks(double mass, double radius,
|
||||
ObserverState *out);
|
||||
/* As above, with a local radial inward boost relative to the static observer. */
|
||||
int observer_inward_schwarzschild_ks_look_at(double mass, double radius,
|
||||
double look_ra_deg,
|
||||
double look_dec_deg,
|
||||
double inward_speed,
|
||||
ObserverState *out);
|
||||
/* Compatibility shortcut for the +X camera directed toward the origin. */
|
||||
int observer_inward_schwarzschild_ks(double mass, double radius,
|
||||
double inward_speed, ObserverState *out);
|
||||
/* Fully resolved instantaneous camera; velocity is dx^i/dt, not a local boost.
|
||||
* Look angles specify a coordinate direction projected into the rest space of
|
||||
* the resulting four-velocity. No static reference observer is required. */
|
||||
typedef struct {
|
||||
double coordinate_time;
|
||||
double position[3], velocity[3];
|
||||
double look_ra_deg, look_dec_deg, roll_deg;
|
||||
} ObserverCamera;
|
||||
|
||||
typedef enum {
|
||||
OBSERVER_BUILD_OK = 0,
|
||||
OBSERVER_BUILD_INVALID_INPUT,
|
||||
OBSERVER_BUILD_NON_TIMELIKE,
|
||||
OBSERVER_BUILD_INVALID_TETRAD
|
||||
} ObserverBuildResult;
|
||||
|
||||
/* Metric belongs to the camera event. q, if non-NULL, receives g((1,v),(1,v)).
|
||||
* Positive roll: up' = cos(roll) up + sin(roll) right,
|
||||
* right' = -sin(roll) up + cos(roll) right.
|
||||
* out is only written on success. */
|
||||
ObserverBuildResult observer_from_coordinate_camera(
|
||||
const MetricData *metric, const ObserverCamera *camera,
|
||||
ObserverState *out, double *q);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user