Feat: add reusable lens map files

This commit is contained in:
wyj committed 2026-09-03 19:46:05 -04:00
1 parent 1a54e59eb4
commit 1ee3a8cc26
4 files changed
+388 -6

No files matched your search

+174
View File
@@ -0,0 +1,174 @@
#include "lens_map.h"
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* All scalar fields are explicitly little-endian; never serialize C structs
* because their padding and size_t width are ABI-dependent. */
static const unsigned char lens_map_magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
enum { LENS_MAP_VERSION = 1, LENS_MAP_ENDIAN = 0x01020304u };
static uint32_t crc32_update(uint32_t crc, const void *data, size_t size) {
const unsigned char *bytes = data;
for (size_t i = 0; i < size; ++i) {
crc ^= bytes[i];
for (int bit = 0; bit < 8; ++bit)
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int)(crc & 1));
}
return crc;
}
static int write_bytes(FILE *file, const void *data, size_t size, uint32_t *crc) {
if (fwrite(data, 1, size, file) != size) return -1;
if (crc != NULL) *crc = crc32_update(*crc, data, size);
return 0;
}
static int read_bytes(FILE *file, void *data, size_t size, uint32_t *crc) {
if (fread(data, 1, size, file) != size) return -1;
if (crc != NULL) *crc = crc32_update(*crc, data, size);
return 0;
}
static int write_u32(FILE *f, uint32_t v, uint32_t *c) {
unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8),
(unsigned char)(v >> 16), (unsigned char)(v >> 24)};
return write_bytes(f, b, sizeof b, c);
}
static int read_u32(FILE *f, uint32_t *v, uint32_t *c) {
unsigned char b[4]; if (read_bytes(f, b, sizeof b, c)) return -1;
*v = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) |
((uint32_t)b[3] << 24); return 0;
}
static int write_u64(FILE *f, uint64_t v, uint32_t *c) {
unsigned char b[8]; for (int i = 0; i < 8; ++i) b[i] = (unsigned char)(v >> (8*i));
return write_bytes(f, b, sizeof b, c);
}
static int read_u64(FILE *f, uint64_t *v, uint32_t *c) {
unsigned char b[8]; if (read_bytes(f, b, sizeof b, c)) return -1;
*v = 0; for (int i = 0; i < 8; ++i) *v |= (uint64_t)b[i] << (8*i); return 0;
}
static int write_double(FILE *f, double v, uint32_t *c) {
uint64_t bits; memcpy(&bits, &v, sizeof bits); return write_u64(f, bits, c);
}
static int read_double(FILE *f, double *v, uint32_t *c) {
uint64_t bits; if (read_u64(f, &bits, c)) return -1; memcpy(v, &bits, sizeof bits); return 0;
}
static int unit_vector(const double v[3]) {
const double n2 = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
return isfinite(n2) && fabs(n2 - 1.0) <= 1e-9;
}
static int valid_mesh(const FrameLensMesh *m) {
if (m == NULL || m->vertex_count == 0 || m->triangle_count == 0) return 0;
for (size_t i = 0; i < m->vertex_count; ++i) {
const LensVertex *v = &m->vertices[i];
if (!v->traced || v->status < RAY_ENDPOINT_ESCAPED ||
v->status > RAY_ENDPOINT_INTEGRATION_FAILURE || !isfinite(v->image_x) ||
!isfinite(v->image_y) || !isfinite(v->log_frequency_ratio) ||
!unit_vector(v->camera_direction) ||
(v->status == RAY_ENDPOINT_ESCAPED && !unit_vector(v->n_infinity))) return 0;
}
for (size_t i = 0; i < m->triangle_count; ++i)
for (int j = 0; j < 3; ++j)
if (m->triangles[i].vertex[j] >= m->vertex_count) return 0;
return 1;
}
int lens_map_write(const char *path, int width, int height, double fov,
const LensMapFrame *frames, size_t frame_count) {
if (path == NULL || frames == NULL || width <= 0 || height <= 0 ||
!isfinite(fov) || fov <= 0.0 || fov >= 179.0 || frame_count == 0 ||
frame_count > UINT64_MAX) return -1;
for (size_t i = 0; i < frame_count; ++i) if (!valid_mesh(&frames[i].mesh)) return -1;
FILE *file = fopen(path, "wb"); if (file == NULL) return -1;
int failed = write_bytes(file, lens_map_magic, sizeof lens_map_magic, NULL) ||
write_u32(file, LENS_MAP_VERSION, NULL) || write_u32(file, LENS_MAP_ENDIAN, NULL) ||
write_u32(file, (uint32_t)width, NULL) || write_u32(file, (uint32_t)height, NULL) ||
write_double(file, fov, NULL) || write_u64(file, (uint64_t)frame_count, NULL);
for (size_t f = 0; !failed && f < frame_count; ++f) {
const FrameLensMesh *m = &frames[f].mesh; uint32_t crc = UINT32_MAX;
failed = write_u64(file, frames[f].frame_id, NULL) ||
write_double(file, frames[f].coordinate_time, NULL) ||
write_double(file, frames[f].proper_time, NULL) ||
write_u64(file, (uint64_t)m->vertex_count, NULL) ||
write_u64(file, (uint64_t)m->triangle_count, NULL);
for (size_t i = 0; !failed && i < m->vertex_count; ++i) {
const LensVertex *v = &m->vertices[i];
failed = write_double(file, v->image_x, &crc) || write_double(file, v->image_y, &crc);
for (int j = 0; !failed && j < 3; ++j) failed = write_double(file, v->camera_direction[j], &crc);
for (int j = 0; !failed && j < 3; ++j) failed = write_double(file, v->n_infinity[j], &crc);
failed = failed || write_double(file, v->log_frequency_ratio, &crc) ||
write_u32(file, (uint32_t)v->status, &crc);
}
for (size_t i = 0; !failed && i < m->triangle_count; ++i) {
for (int j = 0; j < 3; ++j) failed = failed || write_u64(file, m->triangles[i].vertex[j], &crc);
failed = failed || write_u32(file, m->triangles[i].level, &crc);
}
failed = failed || write_u32(file, crc ^ UINT32_MAX, NULL);
}
if (fclose(file)) failed = 1;
return failed ? -1 : 0;
}
void lens_map_destroy(LensMap *map) {
if (map == NULL) return;
for (size_t i = 0; i < map->frame_count; ++i) frame_lens_mesh_destroy(&map->frames[i].mesh);
free(map->frames); *map = (LensMap){0};
}
int lens_map_read(const char *path, LensMap *map) {
if (path == NULL || map == NULL) return -1;
*map = (LensMap){0}; FILE *file = fopen(path, "rb"); if (file == NULL) return -1;
unsigned char magic[8]; uint32_t version, endian, width, height; uint64_t count;
int failed = read_bytes(file, magic, sizeof magic, NULL) || memcmp(magic, lens_map_magic, sizeof magic) ||
read_u32(file, &version, NULL) || read_u32(file, &endian, NULL) ||
read_u32(file, &width, NULL) || read_u32(file, &height, NULL) ||
read_double(file, &map->horizontal_fov_deg, NULL) || read_u64(file, &count, NULL) ||
version != LENS_MAP_VERSION || endian != LENS_MAP_ENDIAN || width == 0 || height == 0 ||
width > INT32_MAX || height > INT32_MAX || !isfinite(map->horizontal_fov_deg) ||
map->horizontal_fov_deg <= 0.0 || map->horizontal_fov_deg >= 179.0 || count == 0 ||
count > SIZE_MAX / sizeof *map->frames;
if (failed) goto done;
map->width = (int)width; map->height = (int)height; map->frame_count = (size_t)count;
map->frames = calloc(map->frame_count, sizeof *map->frames); if (map->frames == NULL) { failed = 1; goto done; }
for (size_t f = 0; !failed && f < map->frame_count; ++f) {
LensMapFrame *frame = &map->frames[f]; uint64_t vertices, triangles; uint32_t stored_crc, crc = UINT32_MAX;
failed = read_u64(file, &frame->frame_id, NULL) || read_double(file, &frame->coordinate_time, NULL) ||
read_double(file, &frame->proper_time, NULL) || read_u64(file, &vertices, NULL) || read_u64(file, &triangles, NULL) ||
!isfinite(frame->coordinate_time) || !isfinite(frame->proper_time) || vertices == 0 || triangles == 0 ||
vertices > SIZE_MAX / sizeof *frame->mesh.vertices || triangles > SIZE_MAX / sizeof *frame->mesh.triangles;
if (failed) break;
frame->mesh.vertices = calloc((size_t)vertices, sizeof *frame->mesh.vertices);
frame->mesh.triangles = calloc((size_t)triangles, sizeof *frame->mesh.triangles);
if (frame->mesh.vertices == NULL || frame->mesh.triangles == NULL) { failed = 1; break; }
frame->mesh.vertex_count = frame->mesh.vertex_capacity = (size_t)vertices;
frame->mesh.triangle_count = frame->mesh.triangle_capacity = (size_t)triangles;
for (size_t i = 0; !failed && i < frame->mesh.vertex_count; ++i) {
LensVertex *v = &frame->mesh.vertices[i]; uint32_t status;
failed = read_double(file, &v->image_x, &crc) || read_double(file, &v->image_y, &crc);
for (int j = 0; !failed && j < 3; ++j) failed = read_double(file, &v->camera_direction[j], &crc);
for (int j = 0; !failed && j < 3; ++j) failed = read_double(file, &v->n_infinity[j], &crc);
failed = failed || read_double(file, &v->log_frequency_ratio, &crc) || read_u32(file, &status, &crc) ||
status > RAY_ENDPOINT_INTEGRATION_FAILURE;
v->status = (RayEndpointStatus)status; v->traced = 1;
}
for (size_t i = 0; !failed && i < frame->mesh.triangle_count; ++i) {
for (int j = 0; j < 3; ++j) {
uint64_t index = 0;
if (read_u64(file, &index, &crc) || index > SIZE_MAX) {
failed = 1;
break;
}
frame->mesh.triangles[i].vertex[j] = (size_t)index;
}
failed = failed || read_u32(file, &frame->mesh.triangles[i].level, &crc); frame->mesh.triangles[i].evaluated = 1;
}
failed = failed || read_u32(file, &stored_crc, NULL) || stored_crc != (crc ^ UINT32_MAX) || !valid_mesh(&frame->mesh);
}
done:
if (fclose(file)) failed = 1;
if (failed) { lens_map_destroy(map); return -1; }
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef LENS_MAP_H
#define LENS_MAP_H
#include "frame.h"
#include <stddef.h>
#include <stdint.h>
/* A finalized, render-only lens map. It deliberately contains no adaptive
* refinement work queues: imported maps may be splatted or drawn, but cannot
* be refined without tracing new rays. */
typedef struct {
uint64_t frame_id;
double coordinate_time;
double proper_time;
FrameLensMesh mesh;
} LensMapFrame;
typedef struct {
int width, height;
double horizontal_fov_deg;
LensMapFrame *frames;
size_t frame_count;
} LensMap;
int lens_map_write(const char *path, int width, int height,
double horizontal_fov_deg, const LensMapFrame *frames,
size_t frame_count);
int lens_map_read(const char *path, LensMap *map);
void lens_map_destroy(LensMap *map);
#endif
+131 -6
View File
@@ -1,5 +1,6 @@
#include "catalog.h" #include "catalog.h"
#include "frame.h" #include "frame.h"
#include "lens_map.h"
#include "movie.h" #include "movie.h"
#include "observer_track.h" #include "observer_track.h"
#include "optics.h" #include "optics.h"
@@ -33,6 +34,9 @@ typedef struct {
const char *catalog_path; const char *catalog_path;
const char *all_sky_catalog_path; const char *all_sky_catalog_path;
const char *output_path; const char *output_path;
const char *lens_map_input_path;
const char *lens_map_output_path;
int width_specified, height_specified, fov_specified;
#ifdef ENABLE_HDR_OUTPUT #ifdef ENABLE_HDR_OUTPUT
int write_hdr_output; int write_hdr_output;
char hdr_output_path[PATH_MAX]; char hdr_output_path[PATH_MAX];
@@ -216,14 +220,20 @@ static int parse_args(int argc, char **argv, Settings *s,
s->all_sky_catalog_path = argv[++i]; s->all_sky_catalog_path = argv[++i];
else if (!strcmp(argv[i], "--output") && i + 1 < argc) else if (!strcmp(argv[i], "--output") && i + 1 < argc)
s->output_path = argv[++i]; s->output_path = argv[++i];
else if (!strcmp(argv[i], "--lens-map-input") && i + 1 < argc)
s->lens_map_input_path = argv[++i];
else if (!strcmp(argv[i], "--lens-map-output") && i + 1 < argc)
s->lens_map_output_path = argv[++i];
#ifdef ENABLE_HDR_OUTPUT #ifdef ENABLE_HDR_OUTPUT
else if (!strcmp(argv[i], "--hdr-output")) else if (!strcmp(argv[i], "--hdr-output"))
s->write_hdr_output = 1; s->write_hdr_output = 1;
#endif #endif
else if (!strcmp(argv[i], "--width") && i + 1 < argc && else if (!strcmp(argv[i], "--width") && i + 1 < argc &&
!parse_int(argv[++i], &s->width)) { !parse_int(argv[++i], &s->width)) {
s->width_specified = 1;
} else if (!strcmp(argv[i], "--height") && i + 1 < argc && } else if (!strcmp(argv[i], "--height") && i + 1 < argc &&
!parse_int(argv[++i], &s->height)) { !parse_int(argv[++i], &s->height)) {
s->height_specified = 1;
} else if (!strcmp(argv[i], "--coarse-cell-pixels") && i + 1 < argc && } else if (!strcmp(argv[i], "--coarse-cell-pixels") && i + 1 < argc &&
!parse_int(argv[++i], &s->coarse_cell_pixels)) { !parse_int(argv[++i], &s->coarse_cell_pixels)) {
} else if (!strcmp(argv[i], "--refine-max-level") && i + 1 < argc && } else if (!strcmp(argv[i], "--refine-max-level") && i + 1 < argc &&
@@ -243,6 +253,7 @@ static int parse_args(int argc, char **argv, Settings *s,
s->draw_mesh = 1; s->draw_mesh = 1;
} else if (!strcmp(argv[i], "--fov-deg") && i + 1 < argc && } else if (!strcmp(argv[i], "--fov-deg") && i + 1 < argc &&
!parse_double(argv[++i], &s->horizontal_fov_deg)) { !parse_double(argv[++i], &s->horizontal_fov_deg)) {
s->fov_specified = 1;
} else if (!strcmp(argv[i], "--look-ra-deg") && i + 1 < argc && } else if (!strcmp(argv[i], "--look-ra-deg") && i + 1 < argc &&
!parse_ra_deg(argv[++i], &s->look_ra_deg)) { !parse_ra_deg(argv[++i], &s->look_ra_deg)) {
} else if (!strcmp(argv[i], "--look-dec-deg") && i + 1 < argc && } else if (!strcmp(argv[i], "--look-dec-deg") && i + 1 < argc &&
@@ -522,6 +533,21 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
"%zu vertices, %zu triangles.\n", "%zu vertices, %zu triangles.\n",
omp_get_wtime() - refinement_start, mesh.vertex_count, omp_get_wtime() - refinement_start, mesh.vertex_count,
mesh.triangle_count); mesh.triangle_count);
if (s->lens_map_output_path != NULL) {
const LensMapFrame map_frame = {.frame_id = 0,
.coordinate_time = 0.0,
.proper_time = 0.0,
.mesh = mesh};
if (lens_map_write(s->lens_map_output_path, s->width, s->height,
s->horizontal_fov_deg, &map_frame, 1)) {
fprintf(stderr, "Failed to write lens map: %s\n", s->lens_map_output_path);
frame_lens_mesh_destroy(&mesh);
free(hdr);
return -1;
}
fprintf(stderr, "Wrote lens map: %s (%zu vertices, %zu triangles)\n",
s->lens_map_output_path, mesh.vertex_count, mesh.triangle_count);
}
if (s->verbose) if (s->verbose)
fprintf(stderr, "Frame 0: traced %zu lens vertices; starting catalog render.\n", fprintf(stderr, "Frame 0: traced %zu lens vertices; starting catalog render.\n",
mesh.vertex_count); mesh.vertex_count);
@@ -697,6 +723,25 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
if (traced == 0) if (traced == 0)
break; break;
} }
if (s->lens_map_output_path != NULL) {
LensMapFrame *map_frames = calloc(movie.frame_count, sizeof *map_frames);
if (map_frames == NULL) goto done;
for (size_t i = 0; i < movie.frame_count; ++i)
map_frames[i] = (LensMapFrame){.frame_id = movie.frames[i].frame_id,
.coordinate_time = movie.frames[i].coordinate_time,
.proper_time = movie.frames[i].proper_time,
.mesh = movie.frames[i].mesh};
const int write_failed = lens_map_write(s->lens_map_output_path, s->width,
s->height, s->horizontal_fov_deg,
map_frames, movie.frame_count);
free(map_frames);
if (write_failed) {
fprintf(stderr, "Failed to write lens map: %s\n", s->lens_map_output_path);
goto done;
}
fprintf(stderr, "Wrote lens-map movie: %s (%zu frames)\n",
s->lens_map_output_path, movie.frame_count);
}
for (size_t i = 0; i < movie.frame_count; ++i) { for (size_t i = 0; i < movie.frame_count; ++i) {
char output_path[PATH_MAX]; char output_path[PATH_MAX];
double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr); double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr);
@@ -739,6 +784,74 @@ done:
return result; return result;
} }
static int render_lens_map(const Settings *s, StarCatalog *catalog) {
LensMap map = {0};
if (lens_map_read(s->lens_map_input_path, &map)) {
fprintf(stderr, "Failed to read or validate lens map: %s\n", s->lens_map_input_path);
return -1;
}
if ((s->width_specified && s->width != map.width) ||
(s->height_specified && s->height != map.height) ||
(s->fov_specified && fabs(s->horizontal_fov_deg - map.horizontal_fov_deg) > 1e-12)) {
fputs("Imported lens-map geometry conflicts with --width, --height, or --fov-deg.\n", stderr);
lens_map_destroy(&map);
return -1;
}
if ((map.frame_count == 1 && s->frames_dir != NULL) ||
(map.frame_count > 1 && s->frames_dir == NULL)) {
fputs("A single-frame lens map uses --output; a multi-frame lens map requires --frames-dir.\n", stderr);
lens_map_destroy(&map);
return -1;
}
int result = 0;
for (size_t i = 0; i < map.frame_count; ++i) {
const char *output_path = s->output_path;
char movie_path[PATH_MAX];
if (map.frame_count > 1) {
if (frame_output_path(movie_path, s, (size_t)map.frames[i].frame_id)) {
fputs("Could not construct imported-map movie output path.\n", stderr);
result = -1; break;
}
output_path = movie_path;
}
double *hdr = calloc((size_t)map.width * map.height * 3, sizeof *hdr);
if (hdr == NULL) { result = -1; break; }
CatalogPrefetchStats prefetch = {0};
PsfSplatStats psf_stats = {0};
RenderProgress progress = {.verbose = s->verbose,
.frame_id = (size_t)map.frames[i].frame_id,
.prefetch = &prefetch,
.catalog_load_workers = s->catalog_load_workers,
.all_sky_catalog = catalog->kind == STAR_CATALOG_ALL_SKY};
const size_t images = frame_splat_catalog(
&map.frames[i].mesh, catalog, hdr, map.width, map.height, s->exposure,
&s->psf, &s->psf_cache, s->max_magnification, s->max_cache_psf_flux,
s->psf_relative_tail, s->psf_min_y, 0, s->catalog_load_workers,
&prefetch, &psf_stats,
&(FrameSplatProgress){report_splat_progress,
s->verbose ? report_splat_worker_progress : NULL,
&progress});
if (s->draw_mesh)
frame_draw_mesh(&map.frames[i].mesh, hdr, map.width, map.height, 0.5, 0.5);
#ifdef ENABLE_HDR_OUTPUT
if (s->write_hdr_output &&
write_hdr_fits(s->hdr_output_path, hdr, map.width, map.height,
map.horizontal_fov_deg)) {
fprintf(stderr, "Failed to write HDR FITS image: %s\n", s->hdr_output_path);
free(hdr); result = -1; break;
}
#endif
const int write_result = write_tonemapped_image(output_path, hdr, map.width, map.height);
free(hdr);
fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s; imported lens map)\n",
images, catalog->count, output_path, write_result == 0 ? "ok" : "write failed");
psf_kernel_cache_report(&s->psf_cache, &psf_stats, stderr);
if (write_result) { result = -1; break; }
}
lens_map_destroy(&map);
return result;
}
static int write_minkowski_accel_track(const Settings *s) { static int write_minkowski_accel_track(const Settings *s) {
ObserverTrack track = {0}; ObserverTrack track = {0};
const int result = observer_track_generate_minkowski_acceleration( const int result = observer_track_generate_minkowski_acceleration(
@@ -763,6 +876,7 @@ int main(int argc, char **argv) {
fprintf(stderr, fprintf(stderr,
"Usage: %s [--catalog PATH | --all-sky-catalog DIR] [--output PATH] [--width N] [--height " "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] " "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-inward-speed V] "
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] " "[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
"[--max-magnification M] [--max-cache-psf-flux F] " "[--max-magnification M] [--max-cache-psf-flux F] "
@@ -794,6 +908,10 @@ int main(int argc, char **argv) {
return write_minkowski_accel_track(&settings) == 0 return write_minkowski_accel_track(&settings) == 0
? 0 ? 0
: (perror(settings.write_minkowski_accel_track_path), 1); : (perror(settings.write_minkowski_accel_track_path), 1);
if (settings.lens_map_input_path != NULL && settings.lens_map_output_path != NULL) {
fputs("--lens-map-input and --lens-map-output are mutually exclusive.\n", stderr);
return 2;
}
#ifdef ENABLE_HDR_OUTPUT #ifdef ENABLE_HDR_OUTPUT
if (settings.frames_dir != NULL && settings.write_hdr_output) { if (settings.frames_dir != NULL && settings.write_hdr_output) {
fputs("--hdr-output is available only for a single-frame render.\n", stderr); fputs("--hdr-output is available only for a single-frame render.\n", stderr);
@@ -824,16 +942,23 @@ int main(int argc, char **argv) {
} }
fprintf(stderr, "Created test catalog: %s\n", settings.catalog_path); fprintf(stderr, "Created test catalog: %s\n", settings.catalog_path);
} }
SpacetimeSource spacetime = {0};
if (spacetime_create_default(&spacetime)) {
fputs("Could not create spacetime source\n", stderr);
catalog_destroy(&catalog);
return 1;
}
if (!settings.psf_direct && psf_kernel_cache_init(&settings.psf_cache, &settings.psf, if (!settings.psf_direct && psf_kernel_cache_init(&settings.psf_cache, &settings.psf,
settings.psf_relative_tail)) settings.psf_relative_tail))
fputs("PSF cache construction failed; using direct evaluator.\n", stderr); fputs("PSF cache construction failed; using direct evaluator.\n", stderr);
psf_kernel_cache_report_ready(&settings.psf_cache, stderr); psf_kernel_cache_report_ready(&settings.psf_cache, stderr);
if (settings.lens_map_input_path != NULL) {
const int result = render_lens_map(&settings, &catalog);
catalog_destroy(&catalog);
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 int result = settings.frames_dir != NULL
? render_movie(&settings, &catalog, &spacetime) ? render_movie(&settings, &catalog, &spacetime)
: render_frame(&settings, &catalog, &spacetime); : render_frame(&settings, &catalog, &spacetime);
+51
View File
@@ -1,4 +1,5 @@
#include "frame.h" #include "frame.h"
#include "lens_map.h"
#include "optics.h" #include "optics.h"
#include <math.h> #include <math.h>
@@ -6,6 +7,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) { static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle) for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle)
@@ -84,6 +86,55 @@ int main(void) {
fputs("flat-space inverse lens-map regression failed\n", stderr); fputs("flat-space inverse lens-map regression failed\n", stderr);
goto done; goto done;
} }
/* A finalized mesh can be persisted independently of spacetime and then
* drive the exact same catalog inverse-map and PSF pass. */
const char *lens_map_path = "/tmp/gr_lens_map_test.grlens";
const LensMapFrame saved_frame = {.frame_id = 7,
.coordinate_time = 3.0,
.proper_time = 2.0,
.mesh = mesh};
LensMap loaded_map = {0};
double *roundtrip_hdr = calloc((size_t)width * height * 3, sizeof *roundtrip_hdr);
if (roundtrip_hdr == NULL ||
lens_map_write(lens_map_path, width, height, 30.0, &saved_frame, 1) ||
lens_map_read(lens_map_path, &loaded_map) || loaded_map.frame_count != 1 ||
loaded_map.frames[0].frame_id != 7 || loaded_map.width != width ||
loaded_map.height != height ||
loaded_map.frames[0].mesh.vertex_count != mesh.vertex_count ||
frame_splat_catalog(&loaded_map.frames[0].mesh, &catalog, roundtrip_hdr,
width, height, test_exposure, &psf, NULL, INFINITY,
1.0, psf_relative_tail, 0.0, 0, 1, NULL, NULL, NULL) != images) {
fputs("lens-map round-trip regression failed\n", stderr);
free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
for (int value = 0; value < width * height * 3; ++value)
if (hdr[value] != roundtrip_hdr[value]) {
fputs("lens-map round-trip HDR regression failed\n", stderr);
free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
free(roundtrip_hdr);
lens_map_destroy(&loaded_map);
/* A damaged payload must not be mistaken for a reusable physical map. */
FILE *damaged = fopen(lens_map_path, "r+b");
int damage_failed = damaged == NULL;
if (!damage_failed) {
if (fseek(damaged, -5L, SEEK_END))
damage_failed = 1;
const int original = damage_failed ? EOF : fgetc(damaged);
if (damage_failed || fseek(damaged, -5L, SEEK_END) || original == EOF ||
fputc(original ^ 0xff, damaged) == EOF)
damage_failed = 1;
}
if (damaged != NULL && fclose(damaged))
damage_failed = 1;
if (damage_failed || !lens_map_read(lens_map_path, &loaded_map)) {
fputs("lens-map corruption rejection regression failed\n", stderr);
lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
unlink(lens_map_path);
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr); memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
PsfSplatStats min_y_stats = {0}; PsfSplatStats min_y_stats = {0};
if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure, if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,