Files
DarkflameServer/dUgcServer/Render/UgcRays.h
Aaron Kimbrell 57110a7081 feat(ugc): rays behind a backend interface, with Embree as a second backend
UgcRays::Scene answers the two ray queries the UGC server makes: the nearest
hit for the hidden faces' paths (never the triangle a path leaves) and any hit
for the ambient occlusion rays. Two backends:

- builtin: the two hierarchies the queries had before, moved unchanged (each
  built the first time it is asked), so the files made are the same bytes
- embree: Embree 4 on the job's thread (a device per worker thread, no threads
  of its own, so its time counts in the CPU budget), watertight, the skipped
  triangle filtered out

UgcHsr::Options::rays and UgcRender::AoOptions::rays pick the backend; both
default to builtin, and nothing sets them yet.

Check: the UGC tests (builtin's files keep their hashes; UgcRays tests compare
embree's hits, hidden faces and occlusion with builtin's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:29:08 -05:00

60 lines
2.4 KiB
C++

#pragma once
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <string_view>
#include <glm/glm.hpp>
#include "UgcModel.h"
/**
* Rays against a mesh's triangles, for the hidden faces' paths (the nearest hit) and the ambient occlusion rays
* (whether anything is hit), by one of several backends (the ugc_ray_backend setting, or per job):
* builtin: the UGC server's own bounding volume hierarchies (the ones it always had)
* embree: Intel's Embree 4 on the CPU, on the thread that asks (no threads of its own)
* A scene is built and traced on the thread that asks, so its time counts towards that thread's CPU time
* (UgcThrottle). Scenes aren't shared between threads. docs/UgcServer.md ("Processing options") has the details.
*/
namespace UgcRays {
constexpr uint32_t NONE = std::numeric_limits<uint32_t>::max();
constexpr float INF = std::numeric_limits<float>::infinity();
enum class eBackend : uint8_t { BUILTIN = 0, EMBREE, HIPRT };
// The setting's name of a backend (builtin, embree, hiprt)
std::string_view Name(eBackend backend);
// A backend by its name (case sensitive); nullopt for anything else
std::optional<eBackend> Parse(std::string_view name);
// Whether this build and machine can use the backend (builtin and embree always)
bool Available(eBackend backend);
// The backend that is used when `wanted` is asked for: itself, or embree when it isn't available
eBackend Resolve(eBackend wanted);
struct Hit {
float t{ INF };
uint32_t triangle{ NONE };
float u{}, v{}; // weights of the triangle's second and third vertex
};
class Scene {
public:
virtual ~Scene() = default;
// The nearest triangle along the ray (unit direction) further than 0 and before `maxT`, never `skip` (the
// triangle the ray leaves, as in Cycles); triangle NONE (and t = maxT) when there is none
virtual Hit Closest(const glm::vec3& origin, const glm::vec3& direction, uint32_t skip = NONE, float maxT = INF) const = 0;
// Whether the ray (unit direction) hits a triangle further than `minT` and nearer than `maxT`
virtual bool Occluded(const glm::vec3& origin, const glm::vec3& direction, float minT, float maxT) const = 0;
};
/**
* The mesh's triangles as they are now (copied) in the backend Resolve(backend) picks. Throws std::runtime_error
* when the backend fails.
*/
std::unique_ptr<Scene> Make(eBackend backend, const UgcModel::Mesh& mesh);
}