Add observer track PNG movie mode
This commit is contained in:
1 parent
d3dc2e5836
commit
c8b321a62a
10 files changed
+552
-33
No files matched your search
+1
-1
@@ -1,3 +1,3 @@
|
||||
/build
|
||||
/output/imgs
|
||||
/output
|
||||
/scripts/__pycache__
|
||||
@@ -17,6 +17,7 @@ CORE_MINKOWSKI_SOURCES := $(COMMON_SOURCES) src/spacetime_minkowski.c
|
||||
TEST_TARGET := build/test_geodesic
|
||||
FRAME_TEST_TARGET := build/test_frame
|
||||
SCHWARZSCHILD_TEST_TARGET := build/test_schwarzschild
|
||||
OBSERVER_TRACK_TEST_TARGET := build/test_observer_track
|
||||
|
||||
.PHONY: all clean run test minkowski schwarzschild
|
||||
|
||||
@@ -55,10 +56,14 @@ $(FRAME_TEST_TARGET): tests/test_frame.c $(CORE_MINKOWSKI_SOURCES) | build
|
||||
$(SCHWARZSCHILD_TEST_TARGET): tests/test_schwarzschild.c $(COMMON_SOURCES) src/spacetime_schwarzschild.c | build
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
test: $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET)
|
||||
$(OBSERVER_TRACK_TEST_TARGET): tests/test_observer_track.c $(COMMON_SOURCES) | build
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
|
||||
|
||||
test: $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET)
|
||||
./$(TEST_TARGET)
|
||||
./$(FRAME_TEST_TARGET)
|
||||
./$(SCHWARZSCHILD_TEST_TARGET)
|
||||
./$(OBSERVER_TRACK_TEST_TARGET)
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
@@ -24,6 +24,31 @@ color.
|
||||
The output is a binary PPM at `output/imgs/minkowski_sky.ppm`; it can be inspected by
|
||||
most image viewers or converted to PNG with ImageMagick.
|
||||
|
||||
## Movie PNG sequence (Phase A)
|
||||
|
||||
Movie mode consumes a canonical observer-track CSV rather than a fixed camera.
|
||||
Each row stores coordinate time, proper time, Cartesian position, and the full
|
||||
four-by-four tetrad (21 columns total). Generate the first reproducible
|
||||
Minkowski benchmark—two coordinate seconds at 30 fps, accelerating from rest
|
||||
to about `0.95c`—then render its numbered PNG frames:
|
||||
|
||||
```sh
|
||||
make clean && make ENABLE_PNG=1
|
||||
mkdir -p output/imgs
|
||||
./build/minkowski_sky --write-minkowski-accel-track output/minkowski_accel_2s.csv \
|
||||
--duration 2 --fps 30 --proper-acceleration 1.52
|
||||
./build/minkowski_sky --observer-track output/minkowski_accel_2s.csv \
|
||||
--frames-dir output/imgs --frames-prefix minkowski_accel \
|
||||
--start-time 0 --duration 2 --fps 30
|
||||
```
|
||||
|
||||
This writes `minkowski_accel_000000.png` through
|
||||
`minkowski_accel_000060.png`. The renderer treats the CSV as its observer
|
||||
input; the acceleration generator is only a reproducible flat-spacetime test
|
||||
fixture. The current movie path still renders frames independently while the
|
||||
time-slab/RayPool scheduler is implemented next, so it must not yet be used
|
||||
as a performance measurement for numerical-relativity data.
|
||||
|
||||
PNG output is optional so the default build has no `libpng` dependency. Build
|
||||
with `make ENABLE_PNG=1`, then select it with a `.png` output path:
|
||||
|
||||
|
||||
+144
-31
@@ -1,12 +1,17 @@
|
||||
#include "catalog.h"
|
||||
#include "frame.h"
|
||||
#include "movie.h"
|
||||
#include "observer_track.h"
|
||||
#include "optics.h"
|
||||
#include "spacetime.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
typedef struct {
|
||||
int width, height;
|
||||
@@ -17,6 +22,12 @@ typedef struct {
|
||||
PointSpreadFunction psf;
|
||||
const char *catalog_path;
|
||||
const char *output_path;
|
||||
const char *observer_track_path;
|
||||
const char *frames_dir;
|
||||
const char *frames_prefix;
|
||||
const char *write_minkowski_accel_track_path;
|
||||
double movie_start_time, movie_duration, movie_fps;
|
||||
double minkowski_proper_acceleration;
|
||||
} Settings;
|
||||
|
||||
static int parse_int(const char *text, int *value) {
|
||||
@@ -57,6 +68,13 @@ static int parse_positive(const char *text, double *value) {
|
||||
return errno || *end || *value <= 0.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_nonnegative(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
*value = strtod(text, &end);
|
||||
return errno || *end || !isfinite(*value) || *value < 0.0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int parse_speed(const char *text, double *value) {
|
||||
char *end;
|
||||
errno = 0;
|
||||
@@ -73,18 +91,20 @@ static int parse_moffat_beta(const char *text, double *value) {
|
||||
|
||||
static int parse_args(int argc, char **argv, Settings *s,
|
||||
const char **write_path) {
|
||||
*s = (Settings){1280,
|
||||
720,
|
||||
16,
|
||||
0,
|
||||
30.0,
|
||||
270.0,
|
||||
0.0,
|
||||
100.0,
|
||||
0.0,
|
||||
{2.7, 4.5},
|
||||
"assets/sky_grid_5deg.csv",
|
||||
"output/imgs/minkowski_sky.ppm"};
|
||||
*s = (Settings){.width = 1280,
|
||||
.height = 720,
|
||||
.coarse_cell_pixels = 16,
|
||||
.horizontal_fov_deg = 30.0,
|
||||
.look_ra_deg = 270.0,
|
||||
.look_dec_deg = 0.0,
|
||||
.exposure = 100.0,
|
||||
.psf = {2.7, 4.5},
|
||||
.catalog_path = "assets/sky_grid_5deg.csv",
|
||||
.output_path = "output/imgs/minkowski_sky.ppm",
|
||||
.frames_prefix = "frame",
|
||||
.movie_duration = 2.0,
|
||||
.movie_fps = 30.0,
|
||||
.minkowski_proper_acceleration = 1.52};
|
||||
*write_path = NULL;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!strcmp(argv[i], "--catalog") && i + 1 < argc)
|
||||
@@ -115,35 +135,63 @@ static int parse_args(int argc, char **argv, Settings *s,
|
||||
!parse_moffat_beta(argv[++i], &s->psf.moffat_beta)) {
|
||||
} else if (!strcmp(argv[i], "--write-catalog") && i + 1 < argc)
|
||||
*write_path = argv[++i];
|
||||
else if (!strcmp(argv[i], "--observer-track") && i + 1 < argc)
|
||||
s->observer_track_path = argv[++i];
|
||||
else if (!strcmp(argv[i], "--frames-dir") && i + 1 < argc)
|
||||
s->frames_dir = argv[++i];
|
||||
else if (!strcmp(argv[i], "--frames-prefix") && i + 1 < argc)
|
||||
s->frames_prefix = argv[++i];
|
||||
else if (!strcmp(argv[i], "--start-time") && i + 1 < argc &&
|
||||
!parse_nonnegative(argv[++i], &s->movie_start_time)) {
|
||||
} else if (!strcmp(argv[i], "--duration") && i + 1 < argc &&
|
||||
!parse_nonnegative(argv[++i], &s->movie_duration)) {
|
||||
} else if (!strcmp(argv[i], "--fps") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->movie_fps)) {
|
||||
} else if (!strcmp(argv[i], "--proper-acceleration") && i + 1 < argc &&
|
||||
!parse_nonnegative(argv[++i],
|
||||
&s->minkowski_proper_acceleration)) {
|
||||
} else if (!strcmp(argv[i], "--write-minkowski-accel-track") &&
|
||||
i + 1 < argc)
|
||||
s->write_minkowski_accel_track_path = argv[++i];
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int render_frame(const Settings *s, const StarCatalog *catalog,
|
||||
const SpacetimeSource *spacetime) {
|
||||
static GeodesicTraceConfig trace_config(void) {
|
||||
#ifdef SPACETIME_SCHWARZSCHILD
|
||||
ObserverState observer;
|
||||
if (observer_inward_schwarzschild_ks(1.0, 30.0,
|
||||
s->observer_inward_speed, &observer))
|
||||
return -1;
|
||||
const GeodesicTraceConfig trace = {.coordinate_time_step = 0.1,
|
||||
.max_steps = 4096,
|
||||
.capture_log_alpha_p0 = 8.0};
|
||||
return (GeodesicTraceConfig){.coordinate_time_step = 0.1,
|
||||
.max_steps = 4096,
|
||||
.capture_log_alpha_p0 = 8.0};
|
||||
#else
|
||||
const ObserverState observer =
|
||||
observer_fixed_at_origin_look_at(s->look_ra_deg, s->look_dec_deg);
|
||||
const GeodesicTraceConfig trace = {.coordinate_time_step = 1.0,
|
||||
.max_steps = 2048};
|
||||
return (GeodesicTraceConfig){.coordinate_time_step = 1.0,
|
||||
.max_steps = 2048};
|
||||
#endif
|
||||
}
|
||||
|
||||
static int default_observer(const Settings *s, ObserverState *observer) {
|
||||
#ifdef SPACETIME_SCHWARZSCHILD
|
||||
return observer_inward_schwarzschild_ks(1.0, 30.0,
|
||||
s->observer_inward_speed, observer);
|
||||
#else
|
||||
*observer = observer_fixed_at_origin_look_at(s->look_ra_deg, s->look_dec_deg);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int render_observer_frame(const Settings *s, const StarCatalog *catalog,
|
||||
const SpacetimeSource *spacetime,
|
||||
const ObserverState *observer,
|
||||
const char *output_path) {
|
||||
const GeodesicTraceConfig trace = trace_config();
|
||||
FrameLensMesh mesh = {0};
|
||||
double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr);
|
||||
if (hdr == NULL ||
|
||||
frame_lens_mesh_build_coarse(&mesh, s->width, s->height,
|
||||
s->coarse_cell_pixels,
|
||||
s->horizontal_fov_deg) ||
|
||||
frame_lens_mesh_trace(&mesh, spacetime, &observer, &trace)) {
|
||||
frame_lens_mesh_trace(&mesh, spacetime, observer, &trace)) {
|
||||
frame_lens_mesh_destroy(&mesh);
|
||||
free(hdr);
|
||||
return -1;
|
||||
@@ -152,15 +200,72 @@ static int render_frame(const Settings *s, const StarCatalog *catalog,
|
||||
s->exposure, &s->psf);
|
||||
if (s->draw_mesh)
|
||||
frame_draw_mesh(&mesh, hdr, s->width, s->height, 0.5, 0.5);
|
||||
int result = write_tonemapped_image(s->output_path, hdr, s->width, s->height);
|
||||
int result = write_tonemapped_image(output_path, hdr, s->width, s->height);
|
||||
fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s)\n",
|
||||
images, catalog->count, s->output_path,
|
||||
images, catalog->count, output_path,
|
||||
result == 0 ? "ok" : "write failed");
|
||||
frame_lens_mesh_destroy(&mesh);
|
||||
free(hdr);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int render_frame(const Settings *s, const 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) {
|
||||
struct stat st;
|
||||
if (s->frames_dir == NULL || s->frames_prefix == NULL ||
|
||||
strchr(s->frames_prefix, '/') != NULL || stat(s->frames_dir, &st) ||
|
||||
!S_ISDIR(st.st_mode))
|
||||
return -1;
|
||||
const int written = snprintf(path, PATH_MAX, "%s/%s_%06zu.png", s->frames_dir,
|
||||
s->frames_prefix, frame_id);
|
||||
return written < 0 || written >= PATH_MAX ? -1 : 0;
|
||||
}
|
||||
|
||||
static int render_movie(const Settings *s, const StarCatalog *catalog,
|
||||
const SpacetimeSource *spacetime) {
|
||||
ObserverTrack track = {0};
|
||||
Movie movie = {0};
|
||||
int result = -1;
|
||||
if (s->observer_track_path == NULL ||
|
||||
observer_track_load_csv(&track, s->observer_track_path) ||
|
||||
movie_init(&movie, &track, s->movie_start_time, s->movie_duration,
|
||||
s->movie_fps))
|
||||
goto done;
|
||||
for (size_t i = 0; i < movie.frame_count; ++i) {
|
||||
char output_path[PATH_MAX];
|
||||
if (frame_output_path(output_path, s, movie.frames[i].frame_id) ||
|
||||
render_observer_frame(s, catalog, spacetime, &movie.frames[i].observer,
|
||||
output_path))
|
||||
goto done;
|
||||
}
|
||||
result = 0;
|
||||
done:
|
||||
movie_destroy(&movie);
|
||||
observer_track_destroy(&track);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int write_minkowski_accel_track(const Settings *s) {
|
||||
ObserverTrack track = {0};
|
||||
const int result = observer_track_generate_minkowski_acceleration(
|
||||
&track, s->minkowski_proper_acceleration, s->movie_duration,
|
||||
1.0 / s->movie_fps) ||
|
||||
observer_track_write_csv(&track,
|
||||
s->write_minkowski_accel_track_path)
|
||||
? -1
|
||||
: 0;
|
||||
observer_track_destroy(&track);
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Settings settings;
|
||||
const char *write_path;
|
||||
@@ -170,14 +275,20 @@ int main(int argc, char **argv) {
|
||||
"N] [--fov-deg D] [--look-ra-deg D] [--look-dec-deg D] "
|
||||
"[--exposure E] [--observer-inward-speed V] "
|
||||
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
|
||||
"[--coarse-cell-pixels N] [--draw-mesh] "
|
||||
"[--write-catalog PATH]\n",
|
||||
"[--coarse-cell-pixels N] [--draw-mesh] [--write-catalog PATH] "
|
||||
"[--observer-track PATH --frames-dir DIR --frames-prefix NAME "
|
||||
"--start-time T --duration T --fps N] "
|
||||
"[--proper-acceleration A --write-minkowski-accel-track PATH]\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
if (write_path != NULL)
|
||||
return catalog_write_octant_grid(write_path) == 0 ? 0
|
||||
: (perror(write_path), 1);
|
||||
if (settings.write_minkowski_accel_track_path != NULL)
|
||||
return write_minkowski_accel_track(&settings) == 0
|
||||
? 0
|
||||
: (perror(settings.write_minkowski_accel_track_path), 1);
|
||||
StarCatalog catalog;
|
||||
if (catalog_load_csv(&catalog, settings.catalog_path)) {
|
||||
if (catalog_write_octant_grid(settings.catalog_path) ||
|
||||
@@ -193,7 +304,9 @@ int main(int argc, char **argv) {
|
||||
catalog_destroy(&catalog);
|
||||
return 1;
|
||||
}
|
||||
int result = render_frame(&settings, &catalog, &spacetime);
|
||||
int result = settings.frames_dir != NULL
|
||||
? render_movie(&settings, &catalog, &spacetime)
|
||||
: render_frame(&settings, &catalog, &spacetime);
|
||||
spacetime_destroy(&spacetime);
|
||||
catalog_destroy(&catalog);
|
||||
return result == 0 ? 0 : 1;
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#include "movie.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int movie_init(Movie *movie, const ObserverTrack *track, double start_time,
|
||||
double duration, double frames_per_second) {
|
||||
if (movie == NULL || track == NULL || !isfinite(start_time) ||
|
||||
!isfinite(duration) || duration < 0.0 || !isfinite(frames_per_second) ||
|
||||
frames_per_second <= 0.0)
|
||||
return -1;
|
||||
*movie = (Movie){0};
|
||||
const double frame_intervals = duration * frames_per_second;
|
||||
const size_t intervals = (size_t)llround(frame_intervals);
|
||||
if (fabs(frame_intervals - (double)intervals) > 1e-9 ||
|
||||
intervals == SIZE_MAX || intervals + 1 > SIZE_MAX / sizeof *movie->frames)
|
||||
return -1;
|
||||
movie->frames = calloc(intervals + 1, sizeof *movie->frames);
|
||||
if (movie->frames == NULL)
|
||||
return -1;
|
||||
movie->frame_count = intervals + 1;
|
||||
for (size_t i = 0; i < movie->frame_count; ++i) {
|
||||
MovieFrame *frame = &movie->frames[i];
|
||||
frame->frame_id = i;
|
||||
frame->coordinate_time = start_time + i / frames_per_second;
|
||||
if (observer_track_interpolate(track, frame->coordinate_time,
|
||||
&frame->observer, &frame->proper_time)) {
|
||||
movie_destroy(movie);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void movie_destroy(Movie *movie) {
|
||||
if (movie == NULL)
|
||||
return;
|
||||
free(movie->frames);
|
||||
*movie = (Movie){0};
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#ifndef MOVIE_H
|
||||
#define MOVIE_H
|
||||
|
||||
#include "observer_track.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
size_t frame_id;
|
||||
double coordinate_time;
|
||||
double proper_time;
|
||||
ObserverState observer;
|
||||
} MovieFrame;
|
||||
|
||||
typedef struct {
|
||||
MovieFrame *frames;
|
||||
size_t frame_count;
|
||||
} Movie;
|
||||
|
||||
int movie_init(Movie *movie, const ObserverTrack *track, double start_time,
|
||||
double duration, double frames_per_second);
|
||||
void movie_destroy(Movie *movie);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "observer_track.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { observer_csv_columns = 21 };
|
||||
|
||||
static int append_sample(ObserverTrack *track, size_t *capacity,
|
||||
const ObserverSample *sample) {
|
||||
if (track->count == *capacity) {
|
||||
const size_t new_capacity = *capacity ? 2 * *capacity : 64;
|
||||
if (new_capacity < *capacity ||
|
||||
new_capacity > SIZE_MAX / sizeof *track->samples)
|
||||
return -1;
|
||||
ObserverSample *samples =
|
||||
realloc(track->samples, new_capacity * sizeof *track->samples);
|
||||
if (samples == NULL)
|
||||
return -1;
|
||||
track->samples = samples;
|
||||
*capacity = new_capacity;
|
||||
}
|
||||
track->samples[track->count++] = *sample;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_sample(char *line, ObserverSample *sample) {
|
||||
double values[observer_csv_columns];
|
||||
char *cursor = line;
|
||||
for (int i = 0; i < observer_csv_columns; ++i) {
|
||||
char *end;
|
||||
while (isspace((unsigned char)*cursor))
|
||||
++cursor;
|
||||
values[i] = strtod(cursor, &end);
|
||||
if (end == cursor || !isfinite(values[i]))
|
||||
return -1;
|
||||
cursor = end;
|
||||
while (isspace((unsigned char)*cursor))
|
||||
++cursor;
|
||||
if (i + 1 < observer_csv_columns) {
|
||||
if (*cursor != ',')
|
||||
return -1;
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
while (isspace((unsigned char)*cursor))
|
||||
++cursor;
|
||||
if (*cursor != '\0')
|
||||
return -1;
|
||||
sample->coordinate_time = values[0];
|
||||
sample->proper_time = values[1];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
sample->coordinate_position[i] = values[2 + i];
|
||||
for (int row = 0, value = 5; row < 4; ++row)
|
||||
for (int column = 0; column < 4; ++column)
|
||||
sample->tetrad[row][column] = values[value++];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int observer_track_load_csv(ObserverTrack *track, const char *path) {
|
||||
if (track == NULL || path == NULL)
|
||||
return -1;
|
||||
*track = (ObserverTrack){0};
|
||||
FILE *file = fopen(path, "r");
|
||||
if (file == NULL)
|
||||
return -1;
|
||||
char line[4096];
|
||||
size_t capacity = 0;
|
||||
int result = -1;
|
||||
while (fgets(line, sizeof line, file) != NULL) {
|
||||
ObserverSample sample;
|
||||
char *start = line;
|
||||
while (isspace((unsigned char)*start))
|
||||
++start;
|
||||
if (*start == '\0' || *start == '#')
|
||||
continue;
|
||||
if (track->count == 0 && isalpha((unsigned char)*start))
|
||||
continue;
|
||||
if (parse_sample(start, &sample) ||
|
||||
(track->count &&
|
||||
sample.coordinate_time <=
|
||||
track->samples[track->count - 1].coordinate_time) ||
|
||||
append_sample(track, &capacity, &sample))
|
||||
goto done;
|
||||
}
|
||||
if (ferror(file) || track->count == 0)
|
||||
goto done;
|
||||
result = 0;
|
||||
done:
|
||||
if (fclose(file) != 0)
|
||||
result = -1;
|
||||
if (result)
|
||||
observer_track_destroy(track);
|
||||
return result;
|
||||
}
|
||||
|
||||
int observer_track_write_csv(const ObserverTrack *track, const char *path) {
|
||||
if (track == NULL || track->samples == NULL || track->count == 0 ||
|
||||
path == NULL)
|
||||
return -1;
|
||||
FILE *file = fopen(path, "w");
|
||||
if (file == NULL)
|
||||
return -1;
|
||||
int result = fprintf(file, "t,tau,x,y,z,e0t,e0x,e0y,e0z,e1t,e1x,e1y,e1z,"
|
||||
"e2t,e2x,e2y,e2z,e3t,e3x,e3y,e3z\n") < 0
|
||||
? -1
|
||||
: 0;
|
||||
for (size_t i = 0; result == 0 && i < track->count; ++i) {
|
||||
const ObserverSample *s = &track->samples[i];
|
||||
if (fprintf(file, "%.17g,%.17g,%.17g,%.17g,%.17g", s->coordinate_time,
|
||||
s->proper_time, s->coordinate_position[0],
|
||||
s->coordinate_position[1], s->coordinate_position[2]) < 0)
|
||||
result = -1;
|
||||
for (int row = 0; result == 0 && row < 4; ++row)
|
||||
for (int column = 0; column < 4; ++column)
|
||||
if (fprintf(file, ",%.17g", s->tetrad[row][column]) < 0)
|
||||
result = -1;
|
||||
if (result == 0 && fputc('\n', file) == EOF)
|
||||
result = -1;
|
||||
}
|
||||
if (fclose(file) != 0)
|
||||
result = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
int observer_track_interpolate(const ObserverTrack *track, double coordinate_time,
|
||||
ObserverState *state, double *proper_time) {
|
||||
if (track == NULL || track->samples == NULL || track->count == 0 ||
|
||||
state == NULL || !isfinite(coordinate_time) ||
|
||||
coordinate_time < track->samples[0].coordinate_time ||
|
||||
coordinate_time > track->samples[track->count - 1].coordinate_time)
|
||||
return -1;
|
||||
size_t high = 0;
|
||||
while (high < track->count &&
|
||||
track->samples[high].coordinate_time < coordinate_time)
|
||||
++high;
|
||||
const size_t low = high == 0 ? 0 : high - 1;
|
||||
if (high == track->count)
|
||||
high = low;
|
||||
const ObserverSample *a = &track->samples[low];
|
||||
const ObserverSample *b = &track->samples[high];
|
||||
const double fraction = high == low
|
||||
? 0.0
|
||||
: (coordinate_time - a->coordinate_time) /
|
||||
(b->coordinate_time - a->coordinate_time);
|
||||
state->coordinate_time = coordinate_time;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
state->coordinate_position[i] =
|
||||
a->coordinate_position[i] +
|
||||
fraction * (b->coordinate_position[i] - a->coordinate_position[i]);
|
||||
for (int row = 0; row < 4; ++row)
|
||||
for (int column = 0; column < 4; ++column)
|
||||
state->tetrad[row][column] =
|
||||
a->tetrad[row][column] +
|
||||
fraction * (b->tetrad[row][column] - a->tetrad[row][column]);
|
||||
if (proper_time != NULL)
|
||||
*proper_time = a->proper_time + fraction * (b->proper_time - a->proper_time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void observer_track_destroy(ObserverTrack *track) {
|
||||
if (track == NULL)
|
||||
return;
|
||||
free(track->samples);
|
||||
*track = (ObserverTrack){0};
|
||||
}
|
||||
|
||||
int observer_track_generate_minkowski_acceleration(ObserverTrack *track,
|
||||
double acceleration,
|
||||
double duration,
|
||||
double sample_interval) {
|
||||
if (track == NULL || !isfinite(acceleration) || acceleration < 0.0 ||
|
||||
!isfinite(duration) || duration < 0.0 || !isfinite(sample_interval) ||
|
||||
sample_interval <= 0.0)
|
||||
return -1;
|
||||
*track = (ObserverTrack){0};
|
||||
size_t capacity = 0;
|
||||
const size_t intervals = (size_t)ceil(duration / sample_interval);
|
||||
for (size_t i = 0; i <= intervals; ++i) {
|
||||
const double t = i == intervals ? duration : i * sample_interval;
|
||||
const double at = acceleration * t;
|
||||
const double gamma = sqrt(1.0 + at * at);
|
||||
const double rapidity = asinh(at);
|
||||
const double displacement = acceleration == 0.0 ? 0.0 :
|
||||
(gamma - 1.0) / acceleration;
|
||||
const double sinh_eta = at;
|
||||
ObserverSample sample = {
|
||||
.coordinate_time = t,
|
||||
.proper_time = acceleration == 0.0 ? t : rapidity / acceleration,
|
||||
.coordinate_position = {0.0, 0.0, -displacement},
|
||||
.tetrad = {{gamma, 0.0, 0.0, -sinh_eta},
|
||||
{sinh_eta, 0.0, 0.0, -gamma},
|
||||
{0.0, 0.0, 1.0, 0.0},
|
||||
{0.0, 1.0, 0.0, 0.0}}};
|
||||
if (append_sample(track, &capacity, &sample)) {
|
||||
observer_track_destroy(track);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef OBSERVER_TRACK_H
|
||||
#define OBSERVER_TRACK_H
|
||||
|
||||
#include "observer.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
double coordinate_time;
|
||||
double proper_time;
|
||||
double coordinate_position[3];
|
||||
double tetrad[4][4];
|
||||
} ObserverSample;
|
||||
|
||||
typedef struct {
|
||||
ObserverSample *samples;
|
||||
size_t count;
|
||||
} ObserverTrack;
|
||||
|
||||
/* Canonical CSV has 21 columns: t,tau,x,y,z,e0t,e0x,...,e3z. */
|
||||
int observer_track_load_csv(ObserverTrack *track, const char *path);
|
||||
int observer_track_write_csv(const ObserverTrack *track, const char *path);
|
||||
int observer_track_interpolate(const ObserverTrack *track, double coordinate_time,
|
||||
ObserverState *state, double *proper_time);
|
||||
void observer_track_destroy(ObserverTrack *track);
|
||||
|
||||
/* Samples an observer initially at rest at the origin, undergoing constant
|
||||
* proper acceleration along the fixed observer's forward axis. */
|
||||
int observer_track_generate_minkowski_acceleration(ObserverTrack *track,
|
||||
double acceleration,
|
||||
double duration,
|
||||
double sample_interval);
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "geodesic.h"
|
||||
#include "observer_track.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
@@ -38,6 +39,24 @@ int main(void) {
|
||||
result = result || check_ray(&source, &look_at_ra_zero,
|
||||
(double[]){1.0, 0.0, 0.0},
|
||||
(double[]){1.0, 0.0, 0.0});
|
||||
ObserverTrack accelerated = {0};
|
||||
ObserverState final_observer;
|
||||
if (observer_track_generate_minkowski_acceleration(&accelerated, 1.52, 2.0,
|
||||
1.0 / 30.0) ||
|
||||
observer_track_interpolate(&accelerated, 2.0, &final_observer, NULL)) {
|
||||
result = 1;
|
||||
} else {
|
||||
const RayEndpoint forward = geodesic_trace_past(
|
||||
&source, &final_observer, (double[]){1.0, 0.0, 0.0},
|
||||
&(GeodesicTraceConfig){.coordinate_time_step = 0.25, .max_steps = 100});
|
||||
const double expected_g = sqrt(1.0 + 3.04 * 3.04) + 3.04;
|
||||
if (forward.status != RAY_ENDPOINT_ESCAPED ||
|
||||
!nearly_equal(forward.frequency_ratio, expected_g)) {
|
||||
fputs("accelerated-observer Doppler regression failed\n", stderr);
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
observer_track_destroy(&accelerated);
|
||||
spacetime_destroy(&source);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "movie.h"
|
||||
#include "observer_track.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int nearly_equal(double a, double b) { return fabs(a - b) < 1e-12; }
|
||||
|
||||
int main(void) {
|
||||
const char *path = "/tmp/gr_raytracing_observer_track.csv";
|
||||
ObserverTrack generated = {0}, loaded = {0};
|
||||
Movie movie = {0};
|
||||
int result = 1;
|
||||
if (observer_track_generate_minkowski_acceleration(&generated, 1.52, 2.0,
|
||||
1.0 / 30.0) ||
|
||||
generated.count != 61 ||
|
||||
!nearly_equal(generated.samples[60].coordinate_time, 2.0) ||
|
||||
!nearly_equal(generated.samples[60].tetrad[0][0],
|
||||
sqrt(1.0 + 3.04 * 3.04)) ||
|
||||
observer_track_write_csv(&generated, path) ||
|
||||
observer_track_load_csv(&loaded, path) || loaded.count != generated.count ||
|
||||
movie_init(&movie, &loaded, 0.0, 2.0, 30.0) || movie.frame_count != 61 ||
|
||||
!nearly_equal(movie.frames[60].coordinate_time, 2.0) ||
|
||||
!nearly_equal(movie.frames[60].observer.tetrad[0][3], -3.04))
|
||||
goto done;
|
||||
ObserverState interpolated;
|
||||
double proper_time = 0.0;
|
||||
if (observer_track_interpolate(&loaded, 1.0, &interpolated, &proper_time) ||
|
||||
!nearly_equal(interpolated.coordinate_position[2],
|
||||
-(sqrt(1.0 + 1.52 * 1.52) - 1.0) / 1.52) ||
|
||||
!nearly_equal(proper_time, asinh(1.52) / 1.52))
|
||||
goto done;
|
||||
{
|
||||
FILE *bad = fopen(path, "w");
|
||||
ObserverTrack invalid = {0};
|
||||
if (bad == NULL)
|
||||
goto done;
|
||||
if (fputs("0,0,0\n", bad) < 0 || fclose(bad) != 0)
|
||||
goto done;
|
||||
if (observer_track_load_csv(&invalid, path) == 0) {
|
||||
observer_track_destroy(&invalid);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
result = 0;
|
||||
done:
|
||||
movie_destroy(&movie);
|
||||
observer_track_destroy(&loaded);
|
||||
observer_track_destroy(&generated);
|
||||
remove(path);
|
||||
if (result)
|
||||
fputs("observer-track/movie regression failed\n", stderr);
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user