diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff259d..f0fe698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,8 @@ endif() add_library(lightrt STATIC lightrt.cc lightrt.hh + lightrt_c.cc + lightrt_c.h ) target_include_directories(lightrt PUBLIC @@ -170,7 +172,7 @@ install(TARGETS lightrt LIBRARY DESTINATION lib ) -install(FILES lightrt.hh +install(FILES lightrt.hh lightrt_c.h DESTINATION include ) diff --git a/lightrt_c.cc b/lightrt_c.cc new file mode 100644 index 0000000..e5ac21a --- /dev/null +++ b/lightrt_c.cc @@ -0,0 +1,109 @@ +/* + * lightrt_c.cc — C11 binding implementation over LightRT's MMapGenericBVH. + * + * The fp32 BVH does broad-phase traversal; the user's fp64 intersection + * callback solves each candidate primitive. The authoritative fp64 ray and the + * best fp64 hit are kept in the scene's per-query scratch (reached by the + * trampolines via the BVH's prim_data pointer), so precision is preserved + * through the callback. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "lightrt_c.h" +#include "lightrt.hh" + +#include +#include +#include + +using lightrt::AABB; +using lightrt::MMapGenericBVH; +using lightrt::Ray; +using lightrt::Vec3; + +struct lrt_scene { + MMapGenericBVH bvh; + unsigned nprims; + lrt_bounds_cb bounds_cb; + lrt_intersect_cb isect_cb; + void *user; + + /* per-query scratch (authoritative fp64 ray + best fp64 hit) */ + double org[3], dir[3], tmin, tmax; + double best_t, best_u, best_v; + unsigned best_prim; +}; + +static AABB bounds_trampoline(const void *prim_data, uint32_t i) noexcept +{ + const lrt_scene *s = static_cast(prim_data); + lrt_aabb a = s->bounds_cb(i, s->user); + return AABB(Vec3((float)a.lo[0], (float)a.lo[1], (float)a.lo[2]), + Vec3((float)a.hi[0], (float)a.hi[1], (float)a.hi[2])); +} + +static bool isect_trampoline(const Ray & /*ray_f32*/, const void *prim_data, + uint32_t i, float &t, float &u, float &v) noexcept +{ + /* prim_data is the build-time pointer; we passed the scene itself. The + * authoritative fp64 ray lives in the scene scratch. */ + lrt_scene *s = const_cast(static_cast(prim_data)); + double td = 0.0, ud = 0.0, vd = 0.0; + int hit = s->isect_cb(s->org, s->dir, s->tmin, s->tmax, i, s->user, &td, &ud, &vd); + if (!hit) return false; + /* report fp32 t to the BVH for ordering / tmax culling */ + t = (float)td; u = (float)ud; v = (float)vd; + if (td < s->best_t) { + s->best_t = td; s->best_u = ud; s->best_v = vd; s->best_prim = i; + } + return true; +} + +lrt_scene *lrt_scene_create(unsigned nprims, lrt_bounds_cb bounds_cb, + lrt_intersect_cb isect_cb, void *user) +{ + if (!bounds_cb || !isect_cb) return nullptr; + lrt_scene *s = new (std::nothrow) lrt_scene(); + if (!s) return nullptr; + s->nprims = nprims; + s->bounds_cb = bounds_cb; + s->isect_cb = isect_cb; + s->user = user; + s->best_prim = LRT_NO_HIT; + return s; +} + +int lrt_scene_build(lrt_scene *s) +{ + if (!s) return 0; + /* prim_data = the scene itself; the bounds trampoline reads bounds_cb. */ + return s->bvh.build(s, s->nprims, bounds_trampoline) ? 1 : 0; +} + +unsigned lrt_scene_intersect(lrt_scene *s, const double org[3], const double dir[3], + double tmin, double tmax, + double *t, double *u, double *v) +{ + if (!s) return LRT_NO_HIT; + for (int k = 0; k < 3; k++) { s->org[k] = org[k]; s->dir[k] = dir[k]; } + s->tmin = tmin; s->tmax = tmax; + s->best_t = DBL_MAX; s->best_u = 0.0; s->best_v = 0.0; s->best_prim = LRT_NO_HIT; + + float ft = (tmin > 0.0) ? (float)tmin : 0.0f; + Ray rf(Vec3((float)org[0], (float)org[1], (float)org[2]), + Vec3((float)dir[0], (float)dir[1], (float)dir[2]), + ft, (float)tmax); + float ht = 0.f, hu = 0.f, hv = 0.f; + s->bvh.traverse(rf, isect_trampoline, ht, hu, hv); + + if (s->best_prim != LRT_NO_HIT) { + if (t) *t = s->best_t; + if (u) *u = s->best_u; + if (v) *v = s->best_v; + } + return s->best_prim; +} + +void lrt_scene_free(lrt_scene *s) { delete s; } + +const char *lrt_backend_name(void) { return "LightRT MMapGenericBVH (fp32 BVH, fp64 callback)"; } diff --git a/lightrt_c.h b/lightrt_c.h new file mode 100644 index 0000000..e66041a --- /dev/null +++ b/lightrt_c.h @@ -0,0 +1,67 @@ +/* + * lightrt_c.h — C11 binding for LightRT's generic BVH + custom-primitive ray + * intersection. + * + * Lets C code drive LightRT's BVH while supplying its own primitive bounds and + * intersection in *double precision*. A scene is a BVH over `nprims` opaque + * primitives; the user provides a bounds callback (for building) and an + * intersection callback (invoked per candidate primitive during traversal). + * + * The BVH itself is single precision (broad phase); the intersection callback + * receives the original fp64 ray, so an analytic surface can be solved at full + * double precision (e.g. sub-nm OPL for aspheric lens tracing). + * + * NOTE: a scene keeps per-query scratch, so a single lrt_scene must not be + * intersected from multiple threads concurrently. Use one scene per thread (or + * per surface) for parallel queries. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef LIGHTRT_C_H +#define LIGHTRT_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Returned by lrt_scene_intersect when nothing was hit. */ +#define LRT_NO_HIT 0xFFFFFFFFu + +typedef struct lrt_scene lrt_scene; + +/* Axis-aligned bounding box in world (fp64) coordinates. */ +typedef struct { double lo[3]; double hi[3]; } lrt_aabb; + +/* Bounds of primitive `prim`. Called during lrt_scene_build. */ +typedef lrt_aabb (*lrt_bounds_cb)(unsigned prim, void *user); + +/* Intersect the fp64 ray with primitive `prim`. On hit, write the ray parameter + * to *t (and optional surface params to *u,*v) and return 1; else return 0. + * Only hits with tmin <= t <= tmax should be reported. */ +typedef int (*lrt_intersect_cb)(const double org[3], const double dir[3], + double tmin, double tmax, unsigned prim, + void *user, double *t, double *u, double *v); + +/* Create a scene over `nprims` primitives. Callbacks + user are retained. */ +lrt_scene *lrt_scene_create(unsigned nprims, lrt_bounds_cb bounds_cb, + lrt_intersect_cb isect_cb, void *user); + +/* Build the BVH. Returns 1 on success, 0 on failure. */ +int lrt_scene_build(lrt_scene *s); + +/* Closest-hit query. Returns the hit primitive id, or LRT_NO_HIT. Writes the + * fp64 ray parameter / surface params to *t,*u,*v when non-NULL on hit. */ +unsigned lrt_scene_intersect(lrt_scene *s, const double org[3], const double dir[3], + double tmin, double tmax, + double *t, double *u, double *v); + +void lrt_scene_free(lrt_scene *s); + +/* Human-readable backend description, e.g. "LightRT MMapGenericBVH". */ +const char *lrt_backend_name(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LIGHTRT_C_H */ diff --git a/meson.build b/meson.build index b684a3b..f7033da 100644 --- a/meson.build +++ b/meson.build @@ -13,7 +13,7 @@ add_project_arguments('-Wall -Wextra -O3', # Library target static_lib = static_library('lightrt', - sources: ['lightrt.cc'], + sources: ['lightrt.cc', 'lightrt_c.cc'], install: true )