Feat: add reusable lens map files
This commit is contained in:
1 parent
1a54e59eb4
commit
1ee3a8cc26
4 files changed
+388
-6
No files matched your search
+174
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1,5 +1,6 @@
|
||||
#include "catalog.h"
|
||||
#include "frame.h"
|
||||
#include "lens_map.h"
|
||||
#include "movie.h"
|
||||
#include "observer_track.h"
|
||||
#include "optics.h"
|
||||
@@ -33,6 +34,9 @@ typedef struct {
|
||||
const char *catalog_path;
|
||||
const char *all_sky_catalog_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
|
||||
int write_hdr_output;
|
||||
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];
|
||||
else if (!strcmp(argv[i], "--output") && i + 1 < argc)
|
||||
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
|
||||
else if (!strcmp(argv[i], "--hdr-output"))
|
||||
s->write_hdr_output = 1;
|
||||
#endif
|
||||
else if (!strcmp(argv[i], "--width") && i + 1 < argc &&
|
||||
!parse_int(argv[++i], &s->width)) {
|
||||
s->width_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--height") && i + 1 < argc &&
|
||||
!parse_int(argv[++i], &s->height)) {
|
||||
s->height_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--coarse-cell-pixels") && i + 1 < argc &&
|
||||
!parse_int(argv[++i], &s->coarse_cell_pixels)) {
|
||||
} 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;
|
||||
} else if (!strcmp(argv[i], "--fov-deg") && i + 1 < argc &&
|
||||
!parse_double(argv[++i], &s->horizontal_fov_deg)) {
|
||||
s->fov_specified = 1;
|
||||
} else if (!strcmp(argv[i], "--look-ra-deg") && i + 1 < argc &&
|
||||
!parse_ra_deg(argv[++i], &s->look_ra_deg)) {
|
||||
} 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",
|
||||
omp_get_wtime() - refinement_start, mesh.vertex_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)
|
||||
fprintf(stderr, "Frame 0: traced %zu lens vertices; starting catalog render.\n",
|
||||
mesh.vertex_count);
|
||||
@@ -697,6 +723,25 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
|
||||
if (traced == 0)
|
||||
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) {
|
||||
char output_path[PATH_MAX];
|
||||
double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr);
|
||||
@@ -739,6 +784,74 @@ done:
|
||||
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) {
|
||||
ObserverTrack track = {0};
|
||||
const int result = observer_track_generate_minkowski_acceleration(
|
||||
@@ -763,6 +876,7 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr,
|
||||
"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] "
|
||||
"[--psf-fwhm-pixels N] [--psf-moffat-beta N] "
|
||||
"[--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
|
||||
? 0
|
||||
: (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
|
||||
if (settings.frames_dir != NULL && settings.write_hdr_output) {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
settings.psf_relative_tail))
|
||||
fputs("PSF cache construction failed; using direct evaluator.\n", 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
|
||||
? render_movie(&settings, &catalog, &spacetime)
|
||||
: render_frame(&settings, &catalog, &spacetime);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "frame.h"
|
||||
#include "lens_map.h"
|
||||
#include "optics.h"
|
||||
|
||||
#include <math.h>
|
||||
@@ -6,6 +7,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
|
||||
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);
|
||||
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);
|
||||
PsfSplatStats min_y_stats = {0};
|
||||
if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,
|
||||
|
||||
Reference in new issue
Block a user