diff --git a/samples/.clang-format b/.clang-format
similarity index 97%
rename from samples/.clang-format
rename to .clang-format
index a114609f392618fec5fd1d6907e7298cc71d0ee7..cb0ac8b90a4575b18e8f0491ba08dca9ec846bd4 100644
--- a/samples/.clang-format
+++ b/.clang-format
@@ -54,3 +54,6 @@ AccessModifierOffset: -4
 FixNamespaceComments: false
 AlignTrailingComments: true
 CommentPragmas: '!Api.*'
+
+# Includes
+IncludeBlocks: Preserve
\ No newline at end of file
diff --git a/extern/AntTweakBar b/extern/AntTweakBar
index 1095daea8d2577809aeb5293c445ef53f433bed8..ff1f8588a0e2a45fdb5f113d5d8a87775ceac3c7 160000
--- a/extern/AntTweakBar
+++ b/extern/AntTweakBar
@@ -1 +1 @@
-Subproject commit 1095daea8d2577809aeb5293c445ef53f433bed8
+Subproject commit ff1f8588a0e2a45fdb5f113d5d8a87775ceac3c7
diff --git a/extern/glow b/extern/glow
index 9d530fdab79ef0ab428142e730866cb5e66a3188..fc0789b07bbb79044b020a4a3a8deee4b877d70b 160000
--- a/extern/glow
+++ b/extern/glow
@@ -1 +1 @@
-Subproject commit 9d530fdab79ef0ab428142e730866cb5e66a3188
+Subproject commit fc0789b07bbb79044b020a4a3a8deee4b877d70b
diff --git a/extern/glow-extras b/extern/glow-extras
index 2f3aa099ae50bed454a76911421db7279782464b..fef1175dbf808a72f4c497fd6f693dddbbeaa4b7 160000
--- a/extern/glow-extras
+++ b/extern/glow-extras
@@ -1 +1 @@
-Subproject commit 2f3aa099ae50bed454a76911421db7279782464b
+Subproject commit fef1175dbf808a72f4c497fd6f693dddbbeaa4b7
diff --git a/extern/polymesh b/extern/polymesh
index 57ca2e57105f4672f031dad0356f863343410cc8..129ee6f9efa0302b4ee9a0678326bee07cad0996 160000
--- a/extern/polymesh
+++ b/extern/polymesh
@@ -1 +1 @@
-Subproject commit 57ca2e57105f4672f031dad0356f863343410cc8
+Subproject commit 129ee6f9efa0302b4ee9a0678326bee07cad0996
diff --git a/extern/typed-geometry b/extern/typed-geometry
index ac9db8c462fa3f41e84519b97ecfb8bfc9730820..aab762b3f57a650c6fe95a3762c79dfc65a699b6 160000
--- a/extern/typed-geometry
+++ b/extern/typed-geometry
@@ -1 +1 @@
-Subproject commit ac9db8c462fa3f41e84519b97ecfb8bfc9730820
+Subproject commit aab762b3f57a650c6fe95a3762c79dfc65a699b6
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index aa2a58b2277d8e7513e31ffca3e97f4b34462df5..d008c685d00f65aedfd25ca7dbcd2d18201f0327 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -8,6 +8,7 @@ endif()
 file(GLOB_RECURSE mains "main.cc")
 foreach(main ${mains})
     get_filename_component(path ${main} DIRECTORY)
-    message(STATUS "Adding sample ${path}")
+    string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" spath ${path})
+    message(STATUS "[glow samples] adding sample ${spath}")
     add_subdirectory(${path})
 endforeach()
diff --git a/samples/wip/viewer/CMakeLists.txt b/samples/basic/viewer/CMakeLists.txt
similarity index 87%
rename from samples/wip/viewer/CMakeLists.txt
rename to samples/basic/viewer/CMakeLists.txt
index 51759ec1c736e26f6101775d5ccbb833cfe4d7dd..7c84d677f87492f56196ec8100fa610537ffbf2c 100644
--- a/samples/wip/viewer/CMakeLists.txt
+++ b/samples/basic/viewer/CMakeLists.txt
@@ -8,7 +8,7 @@ file(GLOB_RECURSE SOURCES "*.cc" "*.hh" "*.*sh")
 add_executable(${PROJECT_NAME} ${SOURCES})
 target_link_libraries(${PROJECT_NAME} PUBLIC glow glow-extras glfw polymesh)
 target_compile_options(${PROJECT_NAME} PUBLIC ${GLOW_SAMPLES_DEF_OPTIONS})
-set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Samples/WIP")
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Samples/Basic")
 
 if (GLOW_EXTRAS_EMBED_SHADERS)
     target_compile_definitions(${PROJECT_NAME} PUBLIC GLOW_SAMPLES_VIEWER_EMBED_SHADERS)
diff --git a/samples/basic/viewer/main.cc b/samples/basic/viewer/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c16c926b20649bd76e08d8c0e73d289323e87685
--- /dev/null
+++ b/samples/basic/viewer/main.cc
@@ -0,0 +1,444 @@
+#include <imgui/imgui.h>
+
+#include <glow/common/str_utils.hh>
+#include <glow/data/TextureData.hh>
+#include <glow/objects/TextureCubeMap.hh>
+#include <glow/util/AsyncTextureLoader.hh>
+
+#include <polymesh/algorithms/normalize.hh>
+#include <polymesh/formats.hh>
+#include <polymesh/objects/quad.hh>
+
+#include <glow-extras/glfw/GlfwContext.hh>
+#include <glow-extras/vector/graphics2D.hh>
+#include <glow-extras/vector/image2D.hh>
+#include <glow-extras/viewer/view.hh>
+
+#include <typed-geometry/tg.hh>
+
+void simple_view(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    // the simplest possible view: just a mesh
+    gv::view(pos);
+}
+
+void basic_concepts(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    // gv::view(obj, args...) takes some object and a list of arguments
+    // - the object is converted via make_renderable(obj) into a gv::SharedRenderable
+    // - for each arg, configure(r, arg) is called (where r is the created gv::Renderable&)
+    // - arguments can change the renderable, the scene, or global settings (see advanced_config function for more)
+
+    // gv::view(...); opens a view at the end of the statement
+    // auto v = gv::view(...); opens the view when `v` is destroyed
+    // - all views created while `v` is alive are added to the same scene
+    // - auto g = gv::grid(); creates a container view and views are added as grid cells while `g` is alive
+
+    auto g = gv::grid();
+
+    // a named view (shown in lower-left corner)
+    gv::view(pos, "Suzanne");
+
+    // no grid
+    gv::view(pos, gv::no_grid);
+
+    // colored mesh
+    gv::view(pos, tg::color3::red);
+
+    // different object
+    gv::view(tg::sphere3::unit);
+
+    // multiple objects in the same scene
+    {
+        auto v = gv::view(pos);
+        gv::view(pos, tg::translation(2, 0, 0));
+        gv::view(tg::sphere3::unit, tg::translation(-2, 0, 0));
+    }
+}
+
+void advanced_objects(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    //
+    // gv::opaque, gv::transparent
+}
+
+void advanced_visualization(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    //
+    // gv::opaque, gv::transparent
+}
+
+void advanced_configs(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    // with this macro, all configurations are added to ALL views created until the end of the scope
+    GLOW_VIEWER_CONFIG(gv::dark_ui);
+
+    auto g = gv::grid();
+
+    // graphics settings
+    gv::view(pos, gv::no_grid, "no grid");
+    gv::view(pos, gv::no_shadow, "no shadow");
+    gv::view(pos, gv::no_outline, "no outline");
+    gv::view(pos, gv::no_ssao, "no ssao");
+    gv::view(pos, gv::print_mode, "print-friendly mode");
+    gv::view(pos, gv::ssao_power(1.0f), "weaker SSAO");
+    gv::view(pos, gv::ssao_radius(0.2f), "smaller SSAO");
+    gv::view(pos, gv::background_color(tg::color3::blue), "custom BG color");
+    gv::view(pos, gv::tonemap_exposure(1.5f), "tonemapping");
+
+    // some configurations have booleans that can be passed
+    gv::view(pos, gv::print_mode(false), "disabled print mode");
+}
+
+void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    //
+}
+
+void custom_renderables()
+{
+    //
+}
+
+void headless_screenshot(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    // Viewer never shows a window, returns once screenshot is rendered
+    GLOW_VIEWER_CONFIG(gv::headless_screenshot(tg::ivec2(1000, 1000), 32, "demo_screenshot.png"));
+    gv::view(pos);
+}
+
+void special_use_cases(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    gv::view(pos, gv::maybe_empty, "allows empty renderable");
+
+    gv::view(pos, gv::infinite_accumulation, "progressive rendering is not stopped early");
+
+    // global settings (affect complete viewer)
+    gv::view(pos, gv::dark_ui, "dark mode");
+    gv::view(pos, gv::no_left_mouse_control, "disabled left mouse interaction");
+    gv::view(pos, gv::no_right_mouse_control, "disabled right mouse interaction");
+    {
+        auto g = gv::grid();
+        gv::view(pos, gv::subview_margin(5, tg::color3::red), "custom grid margin and color");
+        gv::view(pos);
+        gv::view(pos);
+        gv::view(pos);
+    }
+}
+
+int main()
+{
+    std::string const dataPath = glow::util::pathOf(__FILE__) + "/../../../data/";
+
+    // Create a context
+    glow::glfw::GlfwContext ctx;
+
+    tg::rng rng;
+
+    // Load a sample polymesh mesh
+    pm::Mesh m;
+    auto pos = m.vertices().make_attribute<tg::pos3>();
+    load(dataPath + "suzanne.obj", m, pos);
+    normalize(pos); // make it -1..1
+
+    // prepare some data
+    pm::vertex_attribute<glm::vec2> uv = pos.map([](tg::pos3 v) { return glm::vec2(v.x, v.y); });
+    pm::vertex_attribute<float> vdata = pos.map([](tg::pos3 v) { return v.y; });
+    pm::vertex_attribute<tg::vec3> vnormals = vertex_normals_by_area(pos);
+    pm::face_attribute<float> fdata = m.faces().map([&](pm::face_handle f) { return f.vertices().avg(pos).z; });
+    glow::SharedTexture2D tex = glow::Texture2D::createFromFile(dataPath + "textures/tiles.color.png", glow::ColorSpace::sRGB);
+    glow::AsyncTexture2D atex = glow::AsyncTextureLoader::load2D(dataPath + "textures/tiles.color.png", glow::ColorSpace::sRGB);
+    pm::face_attribute<tg::color3> fcolors = m.faces().map([&](pm::face_handle) { return tg::uniform<tg::color3>(rng); });
+    pm::vertex_attribute<tg::color3> vcolors = attribute(m.vertices(), tg::color3(1, 0, 0));
+    pm::edge_attribute<float> edge_lengths = m.edges().map([&](pm::edge_handle e) { return edge_length(e, pos); });
+    pm::vertex_attribute<float> ptsize = m.vertices().map([&](pm::vertex_handle v) { return v.edges().avg(edge_lengths); });
+
+    // demo views
+    {
+        simple_view(pos);
+
+        basic_concepts(pos);
+
+        advanced_objects(pos);
+
+        advanced_visualization(pos);
+
+        advanced_configs(pos);
+
+        interactive_viewer(pos);
+
+        custom_renderables();
+
+        special_use_cases(pos);
+
+        headless_screenshot(pos);
+    }
+
+    // Grid of examples
+    {
+        auto v = gv::grid();
+
+        // Scoped configuration
+        {
+            GLOW_VIEWER_CONFIG(gv::no_grid);
+
+            // ADL view
+            gv::view(pos);
+        }
+
+        // smooth normals
+        gv::view(gv::polygons(pos).smooth_normals(), "smoothed normals");
+
+        // Configuration nesting
+        {
+            GLOW_VIEWER_CONFIG(gv::print_mode);
+
+            // Colored faces
+            gv::view(pos, fcolors, "colored faces");
+
+            {
+                GLOW_VIEWER_CONFIG(gv::no_shadow);
+
+                // Colored vertices
+                gv::view(pos, vcolors, "colored vertices");
+            }
+        }
+
+        // Mapped 1D vertex data
+        gv::view(pos, gv::mapping(vdata).linear({0, 0, 0}, {1, 0, 0}, 0.1f, 0.3f), "vertex data");
+
+        // Mapped 1D face data
+        gv::view(pos, gv::mapping(fdata).linear({0, 1, 0}, {0, 0, 1}, -0.5f, 0.5f).clamped(), "face data");
+
+        // Textured mesh
+        gv::view(pos, gv::textured(uv, tex), "textured");
+
+        // Textured mesh (async texture)
+        gv::view(pos, gv::textured(uv, atex), "textured (async)");
+
+        // Simple point cloud
+        gv::view(gv::points(pos), "point cloud");
+
+        // Square, oriented point cloud with adaptive point size
+        gv::view(gv::points(pos).point_size_world(ptsize).normals(vnormals).square(), "normal oriented squares");
+
+        // Points rendered as 3D spheres
+        gv::view(gv::points(pos).spheres().point_size_world(0.03f), "sphere cloud");
+
+        // Simple line soup
+        gv::view(gv::lines(pos), "edges");
+
+        // Configure view
+        {
+            auto v = gv::view();
+            v.view(pos, glow::colors::color(0, 1, 0), "configured view");
+            // when out-of-scope, v immediately shows
+        }
+
+        // Multiple objects
+        {
+            auto v = gv::view();
+            v.view(gv::polygons(pos).move({-1.5f, 0, 0}), glow::colors::color(1, 0, 0), "multiple objects");
+            v.view(gv::polygons(pos).move({+1.5f, 0, 0}), glow::colors::color(0, 0, 1));
+
+            // or:
+            v.view(pos, scaling(tg::size3(0.5f, 1, 1.5f)), glow::colors::color(0, 1, 0));
+        }
+
+        // transparencies
+        gv::view(pos, tg::color4(0, 0.4f, 0.3f, 0.2f), "transparency with fresnel");
+        gv::view(pos, tg::color4(0, 0.4f, 0.3f, 0.2f), gv::no_fresnel, "transparency without fresnel");
+
+        // Complex material
+        // TODO
+
+        // Text
+        // TODO
+
+        // Vector Graphics
+        // see Vector2DSample for more sample code
+        {
+            glow::vector::image2D img;
+            auto g = graphics(img);
+            g.fill(tg::disk2({100.f, 100.f}, 70), tg::color3::red);
+            g.draw(tg::circle2({100.f, 100.f}, 70), {tg::color3::black, 2});
+            gv::view(img);
+        }
+
+        // Images
+        // TODO
+
+        // Scene setup
+        // TODO
+
+        // Multiple views
+        {
+            auto v = gv::columns();
+            gv::view(pos, glow::colors::color(0, 0, 1)); // attaches to columns view
+            v.view(pos, glow::colors::color(0, 0, 1));   // explicitly adds view
+            {
+                auto r = v.rows();
+                r.view(pos, glow::colors::color(0, 1, 1));
+                r.view(pos, glow::colors::color(1, 1, 0));
+                // when out-of-scope, r attaches to v
+            }
+            // when out-of-scope, v immediately shows
+        }
+
+        // Picking
+        {
+            // TODO
+        }
+    }
+
+    // Interactive
+    {
+        auto v = gv::grid();
+
+        // Non-interactive
+        gv::view(pos);
+
+        // Interactive
+        {
+            // Creating renderables is expensive, cache them whenever possible, and capture by value!
+            auto r1 = gv::make_renderable(pos);
+            gv::interactive([r1](auto dt) {
+                static auto time = 0.f;
+                time += dt;
+
+                // Always a cleared view, resetting accumulation each frame
+                gv::view_cleared(r1, tg::translation(tg::vec3(tg::sin(tg::radians(time * .5f)) * .5f, 0.f, 0.f)));
+            });
+
+            // Using imgui in an interactive view
+            auto r2 = gv::make_renderable(pos);
+            gv::interactive([r2](auto) {
+                static float configurable = 0.f;
+
+                auto const input = ImGui::SliderFloat("Height", &configurable, -3.f, 3.f);
+
+                // conditionally clear view if input has changed
+                gv::view(r2, tg::translation(tg::vec3(0.f, configurable, 0.f)), gv::clear_accumulation(input));
+            });
+        }
+    }
+
+    // Conditional RAII objects
+    {
+        // Use an outer viewer object conditionally
+        auto o = (sizeof(int) > 1) ? gv::rows() : gv::nothing();
+
+        {
+            // Camera starting position
+            GLOW_VIEWER_CONFIG(gv::camera_orientation(125_deg, -15_deg, 1.7f));
+
+            // Tonemapping
+            GLOW_VIEWER_CONFIG(gv::tonemap_exposure(3.5f));
+
+            // These two either nest into the outer object, or create their own windows
+            gv::view(pos, gv::textured(uv, tex));
+            gv::view(gv::points(pos));
+        }
+    }
+
+    // tg objects
+    {
+        auto v = gv::grid();
+        tg::rng rng;
+
+        gv::view(tg::segment3(tg::pos3(0, 0, 0), tg::pos3(1, 0, 0)), tg::color3::red);
+        gv::view(tg::triangle3({0, 0, 0}, {1, 0, 0}, {0, 1, 0}), tg::color3::blue);
+        gv::view(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f}), tg::color3::green);
+        gv::view(gv::lines(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f})));
+
+        // vector versions
+        {
+            std::vector<tg::segment3> segs;
+            for (auto i = 0; i < 20; ++i)
+                segs.emplace_back(uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit));
+            gv::view(segs);
+        }
+        {
+            std::vector<tg::triangle3> tris;
+            for (auto i = 0; i < 20; ++i)
+                tris.emplace_back(uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit));
+            gv::view(tris);
+        }
+        {
+            std::vector<tg::pos3> pts;
+            for (auto i = 0; i < 500; ++i)
+                pts.push_back(uniform(rng, tg::sphere3::unit));
+            gv::view(pts);
+        }
+
+        {
+            std::vector<tg::box3> bbs;
+            for (auto i = 0; i < 4; ++i)
+            {
+                auto e0 = uniform_vec(rng, tg::sphere3::unit);
+                auto e1 = cross(e0, uniform_vec(rng, tg::sphere3::unit));
+                auto e2 = cross(e1, e0);
+
+                auto c = uniform(rng, tg::sphere3::unit);
+
+                e0 = normalize(e0) * uniform(rng, 0.2f, 0.5f);
+                e1 = normalize(e1) * uniform(rng, 0.2f, 0.5f);
+                e2 = normalize(e2) * uniform(rng, 0.2f, 0.5f);
+                bbs.push_back(tg::box3(c, {e0, e1, e2}));
+            }
+
+            gv::view(gv::lines(bbs));
+            gv::view(bbs);
+        }
+    }
+
+    // custom close keys
+    {
+        gv::view(pos, gv::close_keys('A', 'B', 'C'));
+        glow::info() << gv::get_last_close_info().closed_by_key;
+    }
+
+    // Interactive Torus
+    {
+        pm::Mesh m;
+
+        auto pos = m.vertices().make_attribute<tg::pos3>();
+        auto uv = m.vertices().make_attribute<glm::vec2>();
+        pm::objects::add_quad(
+            m,
+            [&](pm::vertex_handle v, float x, float y) {
+                auto [cx, sx] = tg::sin_cos(tg::pi<float> * 2 * x);
+                auto [cy, sy] = tg::sin_cos(tg::pi<float> * 2 * y);
+                auto orad = 8.f;
+                auto irad = 3.f;
+                tg::vec3 t;
+                t.x = cx;
+                t.z = sx;
+                tg::pos3 p;
+                p.x = orad * cx;
+                p.y = irad * cy;
+                p.z = orad * sx;
+                p += t * irad * sy;
+                pos[v] = p;
+                uv[v] = {1 - x, y};
+            },
+            32, 32);
+
+        auto a = 0.f;
+        auto animate = false;
+        gv::interactive([&](auto dt) {
+            ImGui::Begin("Torus");
+            auto changed = ImGui::SliderFloat("angle", &a, 0.f, 360.f);
+            ImGui::Checkbox("Animate", &animate);
+            ImGui::End();
+
+            if (animate)
+                a += 5 * dt;
+            changed |= animate;
+
+            view(pos, gv::textured(uv, tex).transform(tg::rotation_around(tg::pos2::zero, tg::degree(a))), gv::clear_accumulation(changed));
+        });
+    }
+
+    return 0;
+}
diff --git a/samples/wip/viewer/main.cc b/samples/wip/viewer/main.cc
deleted file mode 100644
index 1410ed925149e65c72ea644f7f22931124696d45..0000000000000000000000000000000000000000
--- a/samples/wip/viewer/main.cc
+++ /dev/null
@@ -1,330 +0,0 @@
-#include <random>
-
-#include <imgui/imgui.h>
-
-#include <glow/common/str_utils.hh>
-#include <glow/data/TextureData.hh>
-#include <glow/objects/TextureCubeMap.hh>
-#include <glow/util/AsyncTextureLoader.hh>
-
-#include <polymesh/algorithms/normalize.hh>
-#include <polymesh/formats.hh>
-#include <polymesh/objects/quad.hh>
-
-#include <glow-extras/glfw/GlfwContext.hh>
-#include <glow-extras/vector/graphics2D.hh>
-#include <glow-extras/vector/image2D.hh>
-#include <glow-extras/viewer/view.hh>
-
-#include <GLFW/glfw3.h>
-
-#include <typed-geometry/tg.hh>
-
-int main()
-{
-    std::string const dataPath = glow::util::pathOf(__FILE__) + "/../../../data/";
-
-    // Create a context
-    glow::glfw::GlfwContext ctx;
-
-    std::uniform_real_distribution<float> dis(0.0f, 1.0f);
-    std::default_random_engine rng;
-
-    // Load a polymesh mesh
-    pm::Mesh m;
-    auto pos = m.vertices().make_attribute<tg::pos3>();
-    load(dataPath + "suzanne.obj", m, pos);
-    normalize(pos); // make it -1..1
-
-    // prepare some data
-    pm::vertex_attribute<glm::vec2> uv = pos.map([](tg::pos3 v) { return glm::vec2(v.x, v.y); });
-    pm::vertex_attribute<float> vdata = pos.map([](tg::pos3 v) { return v.y; });
-    pm::vertex_attribute<tg::vec3> vnormals = vertex_normals_by_area(pos);
-    pm::face_attribute<float> fdata = m.faces().map([&](pm::face_handle f) { return f.vertices().avg(pos).z; });
-    glow::SharedTexture2D tex = glow::Texture2D::createFromFile(dataPath + "textures/tiles.color.png", glow::ColorSpace::sRGB);
-    glow::AsyncTexture2D atex = glow::AsyncTextureLoader::load2D(dataPath + "textures/tiles.color.png", glow::ColorSpace::sRGB);
-    pm::face_attribute<glow::colors::color> fcolors = m.faces().map([&](pm::face_handle) { return glow::colors::color(dis(rng), dis(rng), dis(rng)); });
-    pm::vertex_attribute<glow::colors::color> vcolors = attribute(m.vertices(), glow::colors::color(1, 0, 0));
-    pm::edge_attribute<float> edge_lengths = m.edges().map([&](pm::edge_handle e) { return edge_length(e, pos); });
-    pm::vertex_attribute<float> ptsize = m.vertices().map([&](pm::vertex_handle v) { return v.edges().avg(edge_lengths); });
-
-    // Simplest view
-    gv::view(pos);
-
-    // Grid of examples
-    {
-        auto v = glow::viewer::grid();
-
-        // Scoped configuration
-        {
-            GLOW_VIEWER_CONFIG(glow::viewer::no_grid);
-
-            // ADL view
-            view(pos);
-        }
-
-        // named view
-        view(pos, "Suzanne");
-
-        // smooth normals
-        view(polygons(pos).smooth_normals(), "smoothed normals");
-
-        // Configuration nesting
-        {
-            GLOW_VIEWER_CONFIG(glow::viewer::print_mode);
-
-            // Colored faces
-            view(pos, fcolors, "colored faces");
-
-            {
-                GLOW_VIEWER_CONFIG(glow::viewer::no_shadow);
-
-                // Colored vertices
-                view(pos, vcolors, "colored vertices");
-            }
-        }
-
-        // Mapped 1D vertex data
-        view(pos, mapping(vdata).linear({0, 0, 0}, {1, 0, 0}, 0.1f, 0.3f), "vertex data");
-
-        // Mapped 1D face data
-        view(pos, mapping(fdata).linear({0, 1, 0}, {0, 0, 1}, -0.5f, 0.5f).clamped(), "face data");
-
-        // Textured mesh
-        view(pos, textured(uv, tex), "textured");
-
-        // Textured mesh (async texture)
-        view(pos, textured(uv, atex), "textured (async)");
-
-        // Simple point cloud
-        view(points(pos), "point cloud");
-
-        // Square, oriented point cloud with adaptive point size
-        view(points(pos).point_size_world(ptsize).normals(vnormals).square(), "normal oriented squares");
-
-        // Points rendered as 3D spheres
-        view(points(pos).spheres().point_size_world(0.03f), "sphere cloud");
-
-        // Simple line soup
-        view(lines(pos), "edges");
-
-        // Configure view
-        {
-            auto v = glow::viewer::view();
-            v.view(pos, glow::colors::color(0, 1, 0), "configured view");
-            // when out-of-scope, v immediately shows
-        }
-
-        // Multiple objects
-        {
-            auto v = glow::viewer::view();
-            v.view(polygons(pos).move({-1.5f, 0, 0}), glow::colors::color(1, 0, 0), "multiple objects");
-            v.view(polygons(pos).move({+1.5f, 0, 0}), glow::colors::color(0, 0, 1));
-
-            // or:
-            v.view(pos, scaling(tg::size3(0.5f, 1, 1.5f)), glow::colors::color(0, 1, 0));
-        }
-
-        // transparencies
-        view(pos, tg::color4(0, 0.4f, 0.3f, 0.2f), "transparency with fresnel");
-        view(pos, tg::color4(0, 0.4f, 0.3f, 0.2f), glow::viewer::no_fresnel, "transparency without fresnel");
-
-        // Complex material
-        // TODO
-
-        // Text
-        // TODO
-
-        // Vector Graphics
-        // see Vector2DSample for more sample code
-        {
-            glow::vector::image2D img;
-            auto g = graphics(img);
-            g.fill(tg::disk2({100, 100}, 70), tg::color3::red);
-            g.draw(tg::circle2({100, 100}, 70), {tg::color3::black, 2});
-            view(img);
-        }
-
-        // Images
-        // TODO
-
-        // Scene setup
-        // TODO
-
-        // Multiple views
-        {
-            auto v = glow::viewer::columns();
-            glow::viewer::view(pos, glow::colors::color(0, 0, 1)); // attaches to columns view
-            v.view(pos, glow::colors::color(0, 0, 1));             // explicitly adds view
-            {
-                auto r = v.rows();
-                r.view(pos, glow::colors::color(0, 1, 1));
-                r.view(pos, glow::colors::color(1, 1, 0));
-                // when out-of-scope, r attaches to v
-            }
-            // when out-of-scope, v immediately shows
-        }
-
-        // Picking
-        {
-            // TODO
-        }
-    }
-
-    // Interactive
-    {
-        auto v = glow::viewer::grid();
-
-        // Non-interactive
-        glow::viewer::view(pos);
-
-        // Interactive
-        {
-            // Creating renderables is expensive, cache them whenever possible, and capture by value!
-            auto r1 = glow::viewer::make_renderable(pos);
-            glow::viewer::interactive([r1](auto dt) {
-                static auto time = 0.f;
-                time += dt;
-
-                // Always a cleared view, resetting accumulation each frame
-                glow::viewer::view_cleared(r1, tg::translation(tg::vec3(tg::sin(tg::radians(time * .5f)) * .5f, 0.f, 0.f)));
-            });
-
-            // Using imgui in an interactive view
-            auto r2 = glow::viewer::make_renderable(pos);
-            glow::viewer::interactive([r2](auto) {
-                static float configurable = 0.f;
-
-                auto const input = ImGui::SliderFloat("Height", &configurable, -3.f, 3.f);
-
-                // conditionally clear view if input has changed
-                glow::viewer::view(r2, tg::translation(tg::vec3(0.f, configurable, 0.f)), glow::viewer::clear_accumulation(input));
-            });
-        }
-    }
-
-    // Create a "headless" screenshot
-    {
-        // Viewer never shows a window, returns once screenshot is rendered
-        GLOW_VIEWER_CONFIG(glow::viewer::headless_screenshot(tg::ivec2(1000, 1000), 32, "demo_screenshot.png"));
-        glow::viewer::view(pos);
-    }
-
-    // Conditional RAII objects
-    {
-        // Use an outer viewer object conditionally
-        auto o = (sizeof(int) > 1) ? glow::viewer::rows() : glow::viewer::nothing();
-
-        {
-            // Camera starting position
-            GLOW_VIEWER_CONFIG(glow::viewer::camera_orientation(125_deg, -15_deg, 1.7f));
-
-            // Tonemapping
-            GLOW_VIEWER_CONFIG(glow::viewer::tonemap_exposure(3.5f));
-
-            // These two either nest into the outer object, or create their own windows
-            view(pos, textured(uv, tex));
-            view(points(pos));
-        }
-    }
-
-    // tg objects
-    {
-        auto v = glow::viewer::grid();
-        tg::rng rng;
-
-        view(tg::segment3(tg::pos3(0, 0, 0), tg::pos3(1, 0, 0)), tg::color3::red);
-        view(tg::triangle3({0, 0, 0}, {1, 0, 0}, {0, 1, 0}), tg::color3::blue);
-        view(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f}), tg::color3::green);
-        view(lines(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f})));
-
-        // vector versions
-        {
-            std::vector<tg::segment3> segs;
-            for (auto i = 0; i < 20; ++i)
-                segs.emplace_back(uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit));
-            view(segs);
-        }
-        {
-            std::vector<tg::triangle3> tris;
-            for (auto i = 0; i < 20; ++i)
-                tris.emplace_back(uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit));
-            view(tris);
-        }
-        {
-            std::vector<tg::pos3> pts;
-            for (auto i = 0; i < 500; ++i)
-                pts.push_back(uniform(rng, tg::sphere3::unit));
-            view(pts);
-        }
-
-        {
-            std::vector<tg::box3> bbs;
-            for (auto i = 0; i < 4; ++i)
-            {
-                auto e0 = uniform_vec(rng, tg::sphere3::unit);
-                auto e1 = cross(e0, uniform_vec(rng, tg::sphere3::unit));
-                auto e2 = cross(e1, e0);
-
-                auto c = uniform(rng, tg::sphere3::unit);
-
-                e0 = normalize(e0) * uniform(rng, 0.2f, 0.5f);
-                e1 = normalize(e1) * uniform(rng, 0.2f, 0.5f);
-                e2 = normalize(e2) * uniform(rng, 0.2f, 0.5f);
-                bbs.push_back(tg::box3(c, {e0, e1, e2}));
-            }
-
-            view(lines(bbs));
-            view(bbs);
-        }
-    }
-
-    // custom close keys
-    {
-        gv::view(pos, gv::close_keys('A', 'B', 'C'));
-        glow::info() << gv::get_last_close_info().closed_by_key;
-    }
-
-    // Interactive Torus
-    {
-        pm::Mesh m;
-
-        auto pos = m.vertices().make_attribute<tg::pos3>();
-        auto uv = m.vertices().make_attribute<glm::vec2>();
-        pm::objects::add_quad(m,
-                              [&](pm::vertex_handle v, float x, float y) {
-                                  auto [cx, sx] = tg::sin_cos(tg::pi<float> * 2 * x);
-                                  auto [cy, sy] = tg::sin_cos(tg::pi<float> * 2 * y);
-                                  auto orad = 8.f;
-                                  auto irad = 3.f;
-                                  tg::vec3 t;
-                                  t.x = cx;
-                                  t.z = sx;
-                                  tg::pos3 p;
-                                  p.x = orad * cx;
-                                  p.y = irad * cy;
-                                  p.z = orad * sx;
-                                  p += t * irad * sy;
-                                  pos[v] = p;
-                                  uv[v] = {1 - x, y};
-                              },
-                              32, 32);
-
-        auto a = 0.f;
-        auto animate = false;
-        glow::viewer::interactive([&](auto dt) {
-            ImGui::Begin("Torus");
-            auto changed = ImGui::SliderFloat("angle", &a, 0.f, 360.f);
-            ImGui::Checkbox("Animate", &animate);
-            ImGui::End();
-
-            if (animate)
-                a += 5 * dt;
-            changed |= animate;
-
-            view(pos, glow::viewer::textured(uv, tex).transform(tg::rotation_around(tg::pos2::zero, tg::degree(a))), glow::viewer::clear_accumulation(changed));
-        });
-    }
-
-    return 0;
-}