Skip to content

Commit 7a0d110

Browse files
authored
Add smallpt as an example notebook (#235)
1 parent ed69a4c commit 7a0d110

File tree

3 files changed

+362
-1
lines changed

3 files changed

+362
-1
lines changed

.github/workflows/deploy-github-page.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ jobs:
7272
--XeusAddon.prefix=${{ env.PREFIX }} \
7373
--contents README.md \
7474
--contents notebooks/xeus-cpp-lite-demo.ipynb \
75+
--contents notebooks/smallpt.ipynb \
7576
--contents notebooks/images/marie.png \
7677
--contents notebooks/audio/audio.wav \
7778
--output-dir dist

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,9 @@ if(EMSCRIPTEN)
445445
# TODO: Remove the exported runtime methods
446446
# after the next xeus release.
447447
target_link_options(xcpp
448+
PUBLIC "SHELL: -s USE_SDL=2"
448449
PUBLIC "SHELL: -s EXPORTED_RUNTIME_METHODS='[\"FS\",\"PATH\",\"LDSO\",\"loadDynamicLibrary\",\"ERRNO_CODES\"]'"
449-
PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/include@/include"
450+
PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/@/"
450451
PUBLIC "SHELL: --post-js ${CMAKE_CURRENT_SOURCE_DIR}/wasm_patches/post.js"
451452
)
452453
# TODO: Emscripten supports preloading files just once before it generates

notebooks/smallpt.ipynb

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "72cd5c0c-3a52-43a5-a714-0baee1f7464c",
7+
"metadata": {
8+
"trusted": true,
9+
"vscode": {
10+
"languageId": "c++"
11+
}
12+
},
13+
"outputs": [],
14+
"source": [
15+
"// Adapted from smallpt, a Path Tracer by Kevin Beason, 2008\n",
16+
"#include <math.h>\n",
17+
"#include <stdlib.h>\n",
18+
"#include <vector>\n",
19+
"\n",
20+
"struct Vec {\n",
21+
" double x, y, z; // position, also color (r,g,b)\n",
22+
" Vec(double x_ = 0, double y_ = 0, double z_ = 0) {\n",
23+
" x = x_;\n",
24+
" y = y_;\n",
25+
" z = z_;\n",
26+
" }\n",
27+
" Vec operator+(const Vec &b) const { return Vec(x + b.x, y + b.y, z + b.z); }\n",
28+
" Vec operator-(const Vec &b) const { return Vec(x - b.x, y - b.y, z - b.z); }\n",
29+
" Vec operator*(double b) const { return Vec(x * b, y * b, z * b); }\n",
30+
" Vec mult(const Vec &b) const { return Vec(x * b.x, y * b.y, z * b.z); }\n",
31+
" Vec &norm() { return *this = *this * (1 / sqrt(x * x + y * y + z * z)); }\n",
32+
" double dot(const Vec &b) const { return x * b.x + y * b.y + z * b.z; }\n",
33+
" Vec operator%(Vec &b) { // cross\n",
34+
" return Vec(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);\n",
35+
" }\n",
36+
"};\n",
37+
"struct Ray {\n",
38+
" Vec o, d;\n",
39+
" Ray(Vec o_, Vec d_) : o(o_), d(d_) {}\n",
40+
"};\n",
41+
"enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance()\n",
42+
"struct Sphere {\n",
43+
" double rad; // radius\n",
44+
" Vec p, e, c; // position, emission, color\n",
45+
" Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive)\n",
46+
" Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_)\n",
47+
" : rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {}\n",
48+
" double intersect(const Ray &r) const { // returns distance, 0 if nohit\n",
49+
" Vec op = p - r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0\n",
50+
" double t, eps = 1e-4, b = op.dot(r.d), det = b * b - op.dot(op) + rad * rad;\n",
51+
" if (det < 0)\n",
52+
" return 0;\n",
53+
" else\n",
54+
" det = sqrt(det);\n",
55+
" return (t = b - det) > eps ? t : ((t = b + det) > eps ? t : 0);\n",
56+
" }\n",
57+
"};"
58+
]
59+
},
60+
{
61+
"cell_type": "code",
62+
"execution_count": 2,
63+
"id": "acc1ec30-5f0a-446f-a066-3dc3369ed39e",
64+
"metadata": {
65+
"trusted": true,
66+
"vscode": {
67+
"languageId": "c++"
68+
}
69+
},
70+
"outputs": [],
71+
"source": [
72+
"std::vector<Sphere> spheres = {\n",
73+
" // Scene: radius, position, emission, color, material\n",
74+
" Sphere(1e5, Vec(1e5 + 1, 40.8, 81.6), Vec(), Vec(.75, .25, .25),\n",
75+
" DIFF), // Left\n",
76+
" Sphere(1e5, Vec(-1e5 + 99, 40.8, 81.6), Vec(), Vec(.25, .25, .75),\n",
77+
" DIFF), // Rght\n",
78+
" Sphere(1e5, Vec(50, 40.8, 1e5), Vec(), Vec(.75, .75, .75), DIFF), // Back\n",
79+
" Sphere(1e5, Vec(50, 40.8, -1e5 + 170), Vec(), Vec(), DIFF), // Frnt\n",
80+
" Sphere(1e5, Vec(50, 1e5, 81.6), Vec(), Vec(.75, .75, .75), DIFF), // Botm\n",
81+
" Sphere(1e5, Vec(50, -1e5 + 81.6, 81.6), Vec(), Vec(.75, .75, .75),\n",
82+
" DIFF), // Top\n",
83+
" Sphere(16.5, Vec(27, 16.5, 47), Vec(), Vec(1, 1, 1) * .999, SPEC), // Mirr\n",
84+
" Sphere(16.5, Vec(73, 16.5, 78), Vec(), Vec(1, 1, 1) * .999, REFR), // Glas\n",
85+
" Sphere(600, Vec(50, 681.6 - .27, 81.6), Vec(12, 12, 12), Vec(),\n",
86+
" DIFF) // Lite\n",
87+
"};\n",
88+
"\n",
89+
"inline double clamp(double x) { return x < 0 ? 0 : x > 1 ? 1 : x; }\n",
90+
"inline int toInt(double x) { return int(pow(clamp(x), 1 / 2.2) * 255 + .5); }\n",
91+
"inline bool intersect(const Ray &r, double &t, int &id) {\n",
92+
" double n = spheres.size(), d, inf = t = 1e20;\n",
93+
" for (int i = int(n); i--;)\n",
94+
" if ((d = spheres[i].intersect(r)) && d < t) {\n",
95+
" t = d;\n",
96+
" id = i;\n",
97+
" }\n",
98+
" return t < inf;\n",
99+
"}\n"
100+
]
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": 3,
105+
"id": "cf0fd047-c5da-4606-aeae-4358d2064f10",
106+
"metadata": {
107+
"trusted": true,
108+
"vscode": {
109+
"languageId": "c++"
110+
}
111+
},
112+
"outputs": [],
113+
"source": [
114+
"Vec radiance(const Ray &r, int depth, unsigned short *Xi) {\n",
115+
" double t; // distance to intersection\n",
116+
" int id = 0; // id of intersected object\n",
117+
" if (!intersect(r, t, id))\n",
118+
" return Vec(); // if miss, return black\n",
119+
" const Sphere &obj = spheres[id]; // the hit object\n",
120+
" Vec x = r.o + r.d * t, n = (x - obj.p).norm(),\n",
121+
" nl = n.dot(r.d) < 0 ? n : n * -1, f = obj.c;\n",
122+
" double p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z; // max refl\n",
123+
" if (++depth > 5) {\n",
124+
" if (erand48(Xi) < p) {\n",
125+
" f = f * (1 / p);\n",
126+
" } else {\n",
127+
" return obj.e; // R.R.\n",
128+
" }\n",
129+
" }\n",
130+
" if (obj.refl == DIFF) { // Ideal DIFFUSE reflection\n",
131+
" double r1 = 2 * M_PI * erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);\n",
132+
" Vec w = nl, u = ((fabs(w.x) > .1 ? Vec(0, 1) : Vec(1)) % w).norm(),\n",
133+
" v = w % u;\n",
134+
" Vec d = (u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1 - r2)).norm();\n",
135+
" return obj.e + f.mult(radiance(Ray(x, d), depth, Xi));\n",
136+
" } else if (obj.refl == SPEC) // Ideal SPECULAR reflection\n",
137+
" return obj.e +\n",
138+
" f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi));\n",
139+
" Ray reflRay(x, r.d - n * 2 * n.dot(r.d)); // Ideal dielectric REFRACTION\n",
140+
" bool into = n.dot(nl) > 0; // Ray from outside going in?\n",
141+
" double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl),\n",
142+
" cos2t;\n",
143+
" if ((cos2t = 1 - nnt * nnt * (1 - ddn * ddn)) <\n",
144+
" 0) // Total internal reflection\n",
145+
" return obj.e + f.mult(radiance(reflRay, depth, Xi));\n",
146+
" Vec tdir =\n",
147+
" (r.d * nnt - n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)))).norm();\n",
148+
" double a = nt - nc, b = nt + nc, R0 = a * a / (b * b),\n",
149+
" c = 1 - (into ? -ddn : tdir.dot(n));\n",
150+
" double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re, P = .25 + .5 * Re,\n",
151+
" RP = Re / P, TP = Tr / (1 - P);\n",
152+
" return obj.e +\n",
153+
" f.mult(depth > 2\n",
154+
" ? (erand48(Xi) < P ? // Russian roulette\n",
155+
" radiance(reflRay, depth, Xi) * RP\n",
156+
" : radiance(Ray(x, tdir), depth, Xi) * TP)\n",
157+
" : radiance(reflRay, depth, Xi) * Re +\n",
158+
" radiance(Ray(x, tdir), depth, Xi) * Tr);\n",
159+
"}"
160+
]
161+
},
162+
{
163+
"cell_type": "code",
164+
"execution_count": 4,
165+
"id": "70a01b15-3b76-4008-b674-0cc05b0342dd",
166+
"metadata": {
167+
"trusted": true,
168+
"vscode": {
169+
"languageId": "c++"
170+
}
171+
},
172+
"outputs": [],
173+
"source": [
174+
"#include <SDL2/SDL.h>\n",
175+
"#include <string>\n",
176+
"#include <fstream>\n",
177+
"#include <sstream>\n",
178+
"#include <iostream>\n",
179+
"#include \"nlohmann/json.hpp\"\n",
180+
"#include \"xeus/xbase64.hpp\"\n",
181+
"#include \"xcpp/xdisplay.hpp\"\n",
182+
"\n",
183+
"namespace nl = nlohmann;\n",
184+
"\n",
185+
"namespace im\n",
186+
"{\n",
187+
" struct image\n",
188+
" { \n",
189+
" inline image(const std::string& filename)\n",
190+
" {\n",
191+
" std::ifstream fin(filename, std::ios::binary); \n",
192+
" m_buffer << fin.rdbuf();\n",
193+
" }\n",
194+
" \n",
195+
" std::stringstream m_buffer;\n",
196+
" };\n",
197+
" \n",
198+
" nl::json mime_bundle_repr(const image& i)\n",
199+
" {\n",
200+
" auto bundle = nl::json::object();\n",
201+
" bundle[\"image/bmp\"] = xeus::base64encode(i.m_buffer.str());\n",
202+
" return bundle;\n",
203+
" }\n",
204+
"}\n",
205+
"\n",
206+
"// Function to save SDL surface as BMP persistently\n",
207+
"void save_bmp_to_filesystem(SDL_Surface* surface, const std::string& filename)\n",
208+
"{\n",
209+
" if (surface)\n",
210+
" {\n",
211+
" if (SDL_SaveBMP(surface, filename.c_str()) == 0)\n",
212+
" {\n",
213+
" std::cout << \"[DEBUG] Surface saved to \" << filename << std::endl;\n",
214+
" }\n",
215+
" else\n",
216+
" {\n",
217+
" std::cerr << \"[ERROR] Failed to save BMP to \" << filename << \": \" << SDL_GetError() << std::endl;\n",
218+
" }\n",
219+
" }\n",
220+
" else\n",
221+
" {\n",
222+
" std::cerr << \"[ERROR] Surface is null, cannot save BMP.\" << std::endl;\n",
223+
" }\n",
224+
"}\n",
225+
"\n",
226+
"// Function to encode an SDL surface and render it in Jupyter\n",
227+
"void render_sdl_surface_to_jupyter(SDL_Surface* surface, const std::string& filename)\n",
228+
"{\n",
229+
"\n",
230+
" if (!surface)\n",
231+
" {\n",
232+
" std::cerr << \"[ERROR] Surface is null\" << std::endl;\n",
233+
" return;\n",
234+
" }\n",
235+
"\n",
236+
" std::cout << \"[DEBUG] Surface created successfully\" << std::endl;\n",
237+
"\n",
238+
" im::image output(filename);\n",
239+
" xcpp::display(output);\n",
240+
" \n",
241+
" // Cleanup\n",
242+
" SDL_FreeSurface(surface);\n",
243+
" std::cout << \"[DEBUG] Surface freed\" << std::endl;\n",
244+
" remove(filename.c_str());\n",
245+
"}"
246+
]
247+
},
248+
{
249+
"cell_type": "code",
250+
"execution_count": null,
251+
"id": "2fd6e42b-6e84-46e5-9197-0e0c2e054ad1",
252+
"metadata": {
253+
"trusted": true,
254+
"vscode": {
255+
"languageId": "c++"
256+
}
257+
},
258+
"outputs": [],
259+
"source": [
260+
"int main() {\n",
261+
" // Debug: Initialization\n",
262+
" std::cout << \"[DEBUG] Initializing SDL\" << std::endl;\n",
263+
"\n",
264+
" if (SDL_Init(SDL_INIT_EVENTS) != 0) {\n",
265+
" std::cerr << \"[ERROR] SDL_Init failed: \" << SDL_GetError() << std::endl;\n",
266+
" return 1;\n",
267+
" }\n",
268+
" std::cout << \"[DEBUG] SDL initialized successfully\" << std::endl;\n",
269+
"\n",
270+
" // Define width, height, and samples\n",
271+
" int w = 320, h = 240, samps = 16; // # samples\n",
272+
" std::cout << \"[DEBUG] Dimensions: \" << w << \"x\" << h << \", Samples: \" << samps << std::endl;\n",
273+
"\n",
274+
" // Create an off-screen surface\n",
275+
" SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);\n",
276+
" if (!surface) {\n",
277+
" std::cerr << \"[ERROR] Failed to create SDL surface: \" << SDL_GetError() << std::endl;\n",
278+
" SDL_Quit();\n",
279+
" return 1;\n",
280+
" }\n",
281+
" std::cout << \"[DEBUG] SDL surface created successfully\" << std::endl;\n",
282+
"\n",
283+
" // Prepare the camera and pixel buffer\n",
284+
" Ray cam(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm()); // cam pos, dir\n",
285+
" Vec cx = Vec(w * .5135 / h), cy = (cx % cam.d).norm() * .5135, r,\n",
286+
" *c = new Vec[w * h];\n",
287+
"\n",
288+
" std::cout << \"[DEBUG] The image should be on your screen soon\" << std::endl;\n",
289+
" // Render the scene\n",
290+
" for (int y = 0; y < h; y++) { // Loop over image rows\n",
291+
" unsigned short x, Xi[3] = {0, 0, static_cast<unsigned short>(y * y * y)};\n",
292+
" for (x = 0; x < w; x++) { // Loop cols\n",
293+
" int sy, i = (h - y - 1) * w + x;\n",
294+
" for (sy = 0; sy < 2; sy++) { // 2x2 subpixel rows\n",
295+
" int sx;\n",
296+
" for (sx = 0; sx < 2; sx++, r = Vec()) { // 2x2 subpixel cols\n",
297+
" for (int s = 0; s < samps; s++) { // Subpixel sampling\n",
298+
" double r1 = 2 * erand48(Xi),\n",
299+
" dx = r1 < 1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);\n",
300+
" double r2 = 2 * erand48(Xi),\n",
301+
" dy = r2 < 1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);\n",
302+
" Vec d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +\n",
303+
" cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;\n",
304+
" r = r + radiance(Ray(cam.o + d * 140, d.norm()), 0, Xi) * (1. / samps);\n",
305+
" }\n",
306+
" c[i] = c[i] + Vec(clamp(r.x), clamp(r.y), clamp(r.z)) * .25;\n",
307+
" }\n",
308+
" }\n",
309+
" }\n",
310+
" }\n",
311+
"\n",
312+
" // Map the pixel buffer to the SDL surface\n",
313+
" uint8_t* pixels = (uint8_t*)surface->pixels;\n",
314+
" for (int i = 0; i < w * h; i++) {\n",
315+
" uint32_t* pixel_ptr = (uint32_t*)pixels + i;\n",
316+
" *pixel_ptr = SDL_MapRGBA(surface->format, \n",
317+
" toInt(c[i].x), // Red\n",
318+
" toInt(c[i].y), // Green\n",
319+
" toInt(c[i].z), // Blue\n",
320+
" 255); // Alpha\n",
321+
" }\n",
322+
" std::cout << \"[DEBUG] Mapped pixel buffer to surface\" << std::endl;\n",
323+
"\n",
324+
" // Save the BMP to the filesystem\n",
325+
" const std::string bmp_filename = \"render.bmp\";\n",
326+
" save_bmp_to_filesystem(surface, bmp_filename);\n",
327+
"\n",
328+
" // Render the surface to Jupyter\n",
329+
" render_sdl_surface_to_jupyter(surface, bmp_filename);\n",
330+
"\n",
331+
" // Cleanup\n",
332+
" delete[] c;\n",
333+
" SDL_Quit();\n",
334+
" std::cout << \"[DEBUG] Exiting main\" << std::endl;\n",
335+
"\n",
336+
" return 0;\n",
337+
"}\n",
338+
"\n",
339+
"main();"
340+
]
341+
}
342+
],
343+
"metadata": {
344+
"kernelspec": {
345+
"display_name": "C++20",
346+
"language": "cpp",
347+
"name": "xcpp20"
348+
},
349+
"language_info": {
350+
"codemirror_mode": "text/x-c++src",
351+
"file_extension": ".cpp",
352+
"mimetype": "text/x-c++src",
353+
"name": "C++",
354+
"version": "20"
355+
}
356+
},
357+
"nbformat": 4,
358+
"nbformat_minor": 5
359+
}

0 commit comments

Comments
 (0)