{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "be24cabf-6168-401f-a9b1-13459cb3c0de",
      "metadata": {},
      "source": [
        "# **Smallpt in xeus-cpp-lite**\n",
        "\n",
        "### *An example inspired by Kevin Beason's [smallpt](https://www.kevinbeason.com/smallpt/)*"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "5c58a22c-e6e8-41eb-9570-fa1609ddffbf",
      "metadata": {},
      "outputs": [],
      "source": [
        "// LICENSE\n",
        "//\n",
        "// Copyright (c) 2006-2008 Kevin Beason (kevin.beason@gmail.com)\n",
        "//\n",
        "// Permission is hereby granted, free of charge, to any person obtaining\n",
        "// a copy of this software and associated documentation files (the\n",
        "// \"Software\"), to deal in the Software without restriction, including\n",
        "// without limitation the rights to use, copy, modify, merge, publish,\n",
        "// distribute, sublicense, and/or sell copies of the Software, and to\n",
        "// permit persons to whom the Software is furnished to do so, subject to\n",
        "// the following conditions:\n",
        "//\n",
        "// The above copyright notice and this permission notice shall be included\n",
        "// in all copies or substantial portions of the Software.\n",
        "//\n",
        "// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n",
        "// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n",
        "// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n",
        "// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n",
        "// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n",
        "// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n",
        "// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
        "#include <math.h>\n",
        "#include <stdlib.h>\n",
        "#include <vector>\n",
        "\n",
        "struct Vec {\n",
        "  double x, y, z; // position, also color (r,g,b)\n",
        "  Vec(double x_ = 0, double y_ = 0, double z_ = 0) {\n",
        "    x = x_;\n",
        "    y = y_;\n",
        "    z = z_;\n",
        "  }\n",
        "  Vec operator+(const Vec &b) const { return Vec(x + b.x, y + b.y, z + b.z); }\n",
        "  Vec operator-(const Vec &b) const { return Vec(x - b.x, y - b.y, z - b.z); }\n",
        "  Vec operator*(double b) const { return Vec(x * b, y * b, z * b); }\n",
        "  Vec mult(const Vec &b) const { return Vec(x * b.x, y * b.y, z * b.z); }\n",
        "  Vec &norm() { return *this = *this * (1 / sqrt(x * x + y * y + z * z)); }\n",
        "  double dot(const Vec &b) const { return x * b.x + y * b.y + z * b.z; }\n",
        "  Vec operator%(Vec &b) { // cross\n",
        "    return Vec(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);\n",
        "  }\n",
        "};\n",
        "struct Ray {\n",
        "  Vec o, d;\n",
        "  Ray(Vec o_, Vec d_) : o(o_), d(d_) {}\n",
        "};\n",
        "enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance()\n",
        "struct Sphere {\n",
        "  double rad;  // radius\n",
        "  Vec p, e, c; // position, emission, color\n",
        "  Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive)\n",
        "  Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_)\n",
        "      : rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {}\n",
        "  double intersect(const Ray &r) const { // returns distance, 0 if nohit\n",
        "    Vec op = p - r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0\n",
        "    double t, eps = 1e-4, b = op.dot(r.d), det = b * b - op.dot(op) + rad * rad;\n",
        "    if (det < 0)\n",
        "      return 0;\n",
        "    else\n",
        "      det = sqrt(det);\n",
        "    return (t = b - det) > eps ? t : ((t = b + det) > eps ? t : 0);\n",
        "  }\n",
        "};"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "f36cf039-931c-49a2-ac40-0b194cb11c4b",
      "metadata": {},
      "outputs": [],
      "source": [
        "std::vector<Sphere> spheres = {\n",
        "    // Scene: radius, position, emission, color, material\n",
        "    Sphere(1e5, Vec(1e5 + 1, 40.8, 81.6), Vec(), Vec(.75, .25, .25),\n",
        "           DIFF), // Left\n",
        "    Sphere(1e5, Vec(-1e5 + 99, 40.8, 81.6), Vec(), Vec(.25, .25, .75),\n",
        "           DIFF),                                                     // Rght\n",
        "    Sphere(1e5, Vec(50, 40.8, 1e5), Vec(), Vec(.75, .75, .75), DIFF), // Back\n",
        "    Sphere(1e5, Vec(50, 40.8, -1e5 + 170), Vec(), Vec(), DIFF),       // Frnt\n",
        "    Sphere(1e5, Vec(50, 1e5, 81.6), Vec(), Vec(.75, .75, .75), DIFF), // Botm\n",
        "    Sphere(1e5, Vec(50, -1e5 + 81.6, 81.6), Vec(), Vec(.75, .75, .75),\n",
        "           DIFF),                                                      // Top\n",
        "    Sphere(16.5, Vec(27, 16.5, 47), Vec(), Vec(1, 1, 1) * .999, SPEC), // Mirr\n",
        "    Sphere(16.5, Vec(73, 16.5, 78), Vec(), Vec(1, 1, 1) * .999, REFR), // Glas\n",
        "    Sphere(600, Vec(50, 681.6 - .27, 81.6), Vec(12, 12, 12), Vec(),\n",
        "           DIFF) // Lite\n",
        "};\n",
        "\n",
        "inline double clamp(double x) { return x < 0 ? 0 : x > 1 ? 1 : x; }\n",
        "inline int toInt(double x) { return int(pow(clamp(x), 1 / 2.2) * 255 + .5); }\n",
        "inline bool intersect(const Ray &r, double &t, int &id) {\n",
        "  double n = spheres.size(), d, inf = t = 1e20;\n",
        "  for (int i = int(n); i--;)\n",
        "    if ((d = spheres[i].intersect(r)) && d < t) {\n",
        "      t = d;\n",
        "      id = i;\n",
        "    }\n",
        "  return t < inf;\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "eb6047cc-8a58-437a-9ec9-4c5f90409ae5",
      "metadata": {},
      "outputs": [],
      "source": [
        "Vec radiance(const Ray &r, int depth, unsigned short *Xi) {\n",
        "  double t;   // distance to intersection\n",
        "  int id = 0; // id of intersected object\n",
        "  if (!intersect(r, t, id))\n",
        "    return Vec();                  // if miss, return black\n",
        "  const Sphere &obj = spheres[id]; // the hit object\n",
        "  Vec x = r.o + r.d * t, n = (x - obj.p).norm(),\n",
        "      nl = n.dot(r.d) < 0 ? n : n * -1, f = obj.c;\n",
        "  double p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z; // max refl\n",
        "  if (++depth > 5) {\n",
        "    if (erand48(Xi) < p) {\n",
        "      f = f * (1 / p);\n",
        "    } else {\n",
        "      return obj.e; // R.R.\n",
        "    }\n",
        "  }\n",
        "  if (obj.refl == DIFF) { // Ideal DIFFUSE reflection\n",
        "    double r1 = 2 * M_PI * erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);\n",
        "    Vec w = nl, u = ((fabs(w.x) > .1 ? Vec(0, 1) : Vec(1)) % w).norm(),\n",
        "        v = w % u;\n",
        "    Vec d = (u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1 - r2)).norm();\n",
        "    return obj.e + f.mult(radiance(Ray(x, d), depth, Xi));\n",
        "  } else if (obj.refl == SPEC) // Ideal SPECULAR reflection\n",
        "    return obj.e +\n",
        "           f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi));\n",
        "  Ray reflRay(x, r.d - n * 2 * n.dot(r.d)); // Ideal dielectric REFRACTION\n",
        "  bool into = n.dot(nl) > 0;                // Ray from outside going in?\n",
        "  double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl),\n",
        "         cos2t;\n",
        "  if ((cos2t = 1 - nnt * nnt * (1 - ddn * ddn)) <\n",
        "      0) // Total internal reflection\n",
        "    return obj.e + f.mult(radiance(reflRay, depth, Xi));\n",
        "  Vec tdir =\n",
        "      (r.d * nnt - n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)))).norm();\n",
        "  double a = nt - nc, b = nt + nc, R0 = a * a / (b * b),\n",
        "         c = 1 - (into ? -ddn : tdir.dot(n));\n",
        "  double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re, P = .25 + .5 * Re,\n",
        "         RP = Re / P, TP = Tr / (1 - P);\n",
        "  return obj.e +\n",
        "         f.mult(depth > 2\n",
        "                    ? (erand48(Xi) < P ? // Russian roulette\n",
        "                           radiance(reflRay, depth, Xi) * RP\n",
        "                                       : radiance(Ray(x, tdir), depth, Xi) * TP)\n",
        "                    : radiance(reflRay, depth, Xi) * Re +\n",
        "                          radiance(Ray(x, tdir), depth, Xi) * Tr);\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "847686b1-f1c9-40a2-9df2-de30a0b03ac5",
      "metadata": {},
      "outputs": [],
      "source": [
        "#include <SDL2/SDL.h>\n",
        "#include <string>\n",
        "#include <fstream>\n",
        "#include <sstream>\n",
        "#include <iostream>\n",
        "#include \"nlohmann/json.hpp\"\n",
        "#include \"xeus/xbase64.hpp\"\n",
        "#include \"xcpp/xdisplay.hpp\"\n",
        "\n",
        "namespace nl = nlohmann;\n",
        "\n",
        "namespace im\n",
        "{\n",
        "    struct image\n",
        "    {   \n",
        "        inline image(const std::string& filename)\n",
        "        {\n",
        "            std::ifstream fin(filename, std::ios::binary);   \n",
        "            m_buffer << fin.rdbuf();\n",
        "        }\n",
        "        \n",
        "        std::stringstream m_buffer;\n",
        "    };\n",
        "    \n",
        "    nl::json mime_bundle_repr(const image& i)\n",
        "    {\n",
        "        auto bundle = nl::json::object();\n",
        "        bundle[\"image/bmp\"] = xeus::base64encode(i.m_buffer.str());\n",
        "        return bundle;\n",
        "    }\n",
        "}\n",
        "\n",
        "// Function to save SDL surface as BMP persistently\n",
        "void save_bmp_to_filesystem(SDL_Surface* surface, const std::string& filename)\n",
        "{\n",
        "    if (surface)\n",
        "    {\n",
        "        if (SDL_SaveBMP(surface, filename.c_str()) == 0)\n",
        "        {\n",
        "            std::cout << \"[DEBUG] Surface saved to \" << filename << std::endl;\n",
        "        }\n",
        "        else\n",
        "        {\n",
        "            std::cerr << \"[ERROR] Failed to save BMP to \" << filename << \": \" << SDL_GetError() << std::endl;\n",
        "        }\n",
        "    }\n",
        "    else\n",
        "    {\n",
        "        std::cerr << \"[ERROR] Surface is null, cannot save BMP.\" << std::endl;\n",
        "    }\n",
        "}\n",
        "\n",
        "// Function to encode an SDL surface and render it in Jupyter\n",
        "void render_sdl_surface_to_jupyter(SDL_Surface* surface, const std::string& filename)\n",
        "{\n",
        "\n",
        "    if (!surface)\n",
        "    {\n",
        "        std::cerr << \"[ERROR] Surface is null\" << std::endl;\n",
        "        return;\n",
        "    }\n",
        "\n",
        "    std::cout << \"[DEBUG] Surface created successfully\" << std::endl;\n",
        "\n",
        "    im::image output(filename);\n",
        "    xcpp::display(output);\n",
        "    \n",
        "    // Cleanup\n",
        "    SDL_FreeSurface(surface);\n",
        "    std::cout << \"[DEBUG] Surface freed\" << std::endl;\n",
        "    remove(filename.c_str());\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "6a863b15-ef45-4eb5-89fc-07e0392f63d2",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[DEBUG] Initializing SDL\n",
            "[DEBUG] SDL initialized successfully\n",
            "[DEBUG] Dimensions: 320x240, Samples: 16\n",
            "[DEBUG] SDL surface created successfully\n",
            "[DEBUG] The image should be on your screen soon\n",
            "[DEBUG] Mapped pixel buffer to surface\n",
            "[DEBUG] Surface saved to render.bmp\n",
            "[DEBUG] Surface created successfully\n"
          ]
        },
        {
          "data": {
            "image/bmp": "[encoded data, 409764 characters]"
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[DEBUG] Surface freed\n",
            "[DEBUG] Exiting main\n"
          ]
        }
      ],
      "source": [
        "int main() {\n",
        "    // Debug: Initialization\n",
        "    std::cout << \"[DEBUG] Initializing SDL\" << std::endl;\n",
        "\n",
        "    if (SDL_Init(SDL_INIT_EVENTS) != 0) {\n",
        "        std::cerr << \"[ERROR] SDL_Init failed: \" << SDL_GetError() << std::endl;\n",
        "        return 1;\n",
        "    }\n",
        "    std::cout << \"[DEBUG] SDL initialized successfully\" << std::endl;\n",
        "\n",
        "    // Define width, height, and samples\n",
        "    int w = 320, h = 240, samps = 16; // # samples\n",
        "    std::cout << \"[DEBUG] Dimensions: \" << w << \"x\" << h << \", Samples: \" << samps << std::endl;\n",
        "\n",
        "    // Create an off-screen surface\n",
        "    SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);\n",
        "    if (!surface) {\n",
        "        std::cerr << \"[ERROR] Failed to create SDL surface: \" << SDL_GetError() << std::endl;\n",
        "        SDL_Quit();\n",
        "        return 1;\n",
        "    }\n",
        "    std::cout << \"[DEBUG] SDL surface created successfully\" << std::endl;\n",
        "\n",
        "    // Prepare the camera and pixel buffer\n",
        "    Ray cam(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm()); // cam pos, dir\n",
        "    Vec cx = Vec(w * .5135 / h), cy = (cx % cam.d).norm() * .5135, r,\n",
        "        *c = new Vec[w * h];\n",
        "\n",
        "    std::cout << \"[DEBUG] The image should be on your screen soon\" << std::endl;\n",
        "    // Render the scene\n",
        "    for (int y = 0; y < h; y++) { // Loop over image rows\n",
        "        unsigned short x, Xi[3] = {0, 0, static_cast<unsigned short>(y * y * y)};\n",
        "        for (x = 0; x < w; x++) { // Loop cols\n",
        "            int sy, i = (h - y - 1) * w + x;\n",
        "            for (sy = 0; sy < 2; sy++) { // 2x2 subpixel rows\n",
        "                int sx;\n",
        "                for (sx = 0; sx < 2; sx++, r = Vec()) { // 2x2 subpixel cols\n",
        "                    for (int s = 0; s < samps; s++) { // Subpixel sampling\n",
        "                        double r1 = 2 * erand48(Xi),\n",
        "                               dx = r1 < 1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);\n",
        "                        double r2 = 2 * erand48(Xi),\n",
        "                               dy = r2 < 1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);\n",
        "                        Vec d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +\n",
        "                                cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;\n",
        "                        r = r + radiance(Ray(cam.o + d * 140, d.norm()), 0, Xi) * (1. / samps);\n",
        "                    }\n",
        "                    c[i] = c[i] + Vec(clamp(r.x), clamp(r.y), clamp(r.z)) * .25;\n",
        "                }\n",
        "            }\n",
        "        }\n",
        "    }\n",
        "\n",
        "    // Map the pixel buffer to the SDL surface\n",
        "    uint8_t* pixels = (uint8_t*)surface->pixels;\n",
        "    for (int i = 0; i < w * h; i++) {\n",
        "        uint32_t* pixel_ptr = (uint32_t*)pixels + i;\n",
        "        *pixel_ptr = SDL_MapRGBA(surface->format, \n",
        "                                 toInt(c[i].x), // Red\n",
        "                                 toInt(c[i].y), // Green\n",
        "                                 toInt(c[i].z), // Blue\n",
        "                                 255);          // Alpha\n",
        "    }\n",
        "    std::cout << \"[DEBUG] Mapped pixel buffer to surface\" << std::endl;\n",
        "\n",
        "    // Save the BMP to the filesystem\n",
        "    const std::string bmp_filename = \"render.bmp\";\n",
        "    save_bmp_to_filesystem(surface, bmp_filename);\n",
        "\n",
        "    // Render the surface to Jupyter\n",
        "    render_sdl_surface_to_jupyter(surface, bmp_filename);\n",
        "\n",
        "    // Cleanup\n",
        "    delete[] c;\n",
        "    SDL_Quit();\n",
        "    std::cout << \"[DEBUG] Exiting main\" << std::endl;\n",
        "\n",
        "    return 0;\n",
        "}\n",
        "\n",
        "main();"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c0bae4e3-f9a2-49d3-9451-859a9e4e504a",
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "C++20",
      "language": "cpp",
      "name": "xcpp20"
    },
    "language_info": {
      "codemirror_mode": "text/x-c++src",
      "file_extension": ".cpp",
      "mimetype": "text/x-c++src",
      "name": "C++",
      "version": "20"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}