#pragma once #include "lantern.hpp" #include #include #include #include #include #include #include #include namespace lantern { // A small, explicit RGB transport model: two-sided Lambertian spheres and // two-sided surface emission, constant environment, no light sampling or MIS. struct Surface { Sphere sphere; Vec3 reflectance; Vec3 emission; }; struct Frame { Vec3 tangent, bitangent, normal; }; [[nodiscard]] std::expected frame_from_normal(Vec3 normal); [[nodiscard]] Vec3 to_world(const Frame& frame, Vec3 local) noexcept; enum class PathEnd { environment, depth_limit, absorbed }; struct PathStep { std::optional object; Vec3 point{}, normal{}, local_direction{}; double pdf{}, cosine{}; Vec3 throughput{1,1,1}, radiance{}; }; struct PathTrace { Vec3 radiance{}; PathEnd end{}; std::size_t count{}; std::array steps{}; // At most eight scatters plus the terminal visit. }; class DiffuseScene { public: [[nodiscard]] static std::expected build(std::span surfaces, Vec3 environment); // max_bounces counts scattered rays, not visits. At the terminal visit, // emission or the environment is still accumulated before stopping. [[nodiscard]] std::expected trace(Ray ray, Rng& rng, unsigned max_bounces) const; private: std::vector surfaces_; SceneIndex index_; Vec3 environment_{}; }; struct Image { std::size_t width{}, height{}; std::vector linear; }; struct RenderSettings { std::size_t width{96}, height{64}, samples{32}; unsigned max_bounces{4}; std::uint64_t seed{47}; }; [[nodiscard]] std::expected study_scene(); [[nodiscard]] std::expected render_diffuse(const DiffuseScene&, RenderSettings); [[nodiscard]] std::expected write_ppm(const Image&, const std::filesystem::path&); } // namespace lantern