Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ endif()
add_library(lightrt STATIC
lightrt.cc
lightrt.hh
lightrt_c.cc
lightrt_c.h
)

target_include_directories(lightrt PUBLIC
Expand Down Expand Up @@ -170,7 +172,7 @@ install(TARGETS lightrt
LIBRARY DESTINATION lib
)

install(FILES lightrt.hh
install(FILES lightrt.hh lightrt_c.h
DESTINATION include
)

Expand Down
109 changes: 109 additions & 0 deletions lightrt_c.cc
Original file line number Diff line number Diff line change
@@ -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 <cfloat>
#include <cstdlib>
#include <new>

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<const lrt_scene *>(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<lrt_scene *>(static_cast<const lrt_scene *>(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)"; }
67 changes: 67 additions & 0 deletions lightrt_c.h
Original file line number Diff line number Diff line change
@@ -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 */
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
Loading