diff --git a/.gitmodules b/.gitmodules
index 5a28698ccb2d772a7e978d1f2110e5f2fad269a1..f148c2e91ccebc968ca1ed17d2f338d38bf6a9ca 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,13 +1,14 @@
 [submodule "glow"]
 	path = extern/glow
 	url = https://www.graphics.rwth-aachen.de:9000/Glow/glow.git
-	branch = master
+	branch = develop
 [submodule "glfw"]
 	path = extern/glfw
 	url = https://github.com/glfw/glfw.git
 [submodule "glow-extras"]
 	path = extern/glow-extras
 	url = https://www.graphics.rwth-aachen.de:9000/Glow/glow-extras.git
+	branch = develop
 [submodule "aion"]
 	path = extern/aion
 	url = https://www.graphics.rwth-aachen.de:9000/ptrettner/aion.git
@@ -23,6 +24,7 @@
 [submodule "polymesh"]
 	path = extern/polymesh
 	url = https://www.graphics.rwth-aachen.de:9000/ptrettner/polymesh.git
+	branch = develop
 [submodule "extern/assimp"]
 	path = extern/assimp
 	url = https://www.graphics.rwth-aachen.de:9000/ptrettner/assimp-lean.git
@@ -32,3 +34,4 @@
 [submodule "extern/typed-geometry"]
 	path = extern/typed-geometry
 	url = https://www.graphics.rwth-aachen.de:9000/ptrettner/typed-geometry.git
+	branch = develop
diff --git a/extern/glow-extras b/extern/glow-extras
index 88e35565bda9d2c0e956eedc3eceea42d1441694..ef463745e04f6bdb40b4cd2fc52b448e9c3ed180 160000
--- a/extern/glow-extras
+++ b/extern/glow-extras
@@ -1 +1 @@
-Subproject commit 88e35565bda9d2c0e956eedc3eceea42d1441694
+Subproject commit ef463745e04f6bdb40b4cd2fc52b448e9c3ed180
diff --git a/extern/typed-geometry b/extern/typed-geometry
index 74db2df1814d0764ed95cad2a0ce10dfbfef9f05..f1ad563649a30184d2ec524632997ea30f922a4f 160000
--- a/extern/typed-geometry
+++ b/extern/typed-geometry
@@ -1 +1 @@
-Subproject commit 74db2df1814d0764ed95cad2a0ce10dfbfef9f05
+Subproject commit f1ad563649a30184d2ec524632997ea30f922a4f
diff --git a/samples/basic/viewer/main.cc b/samples/basic/viewer/main.cc
index 62d9d23df7712ba08a46d63337d9743d117bd21c..452f80f23a1e996dbd0b6910cde2623e5f886a58 100644
--- a/samples/basic/viewer/main.cc
+++ b/samples/basic/viewer/main.cc
@@ -55,8 +55,8 @@ void basic_concepts(pm::vertex_attribute<tg::pos3> const& pos)
     // multiple objects in the same scene
     {
         auto v = gv::view(pos);
-        gv::view(pos, tg::translation(2, 0, 0)); // a matrix can be passed to transform the object
-        gv::view(tg::sphere3::unit, tg::translation(-2, 0, 0));
+        gv::view(pos, tg::translation(2.f, 0.f, 0.f)); // a matrix can be passed to transform the object
+        gv::view(tg::sphere3::unit, tg::translation(-2.f, 0.f, 0.f) * tg::scaling(tg::size3(0.5f, 1.5, 1.f)));
     }
 }
 
@@ -91,7 +91,7 @@ void advanced_objects(pm::vertex_attribute<tg::pos3> const& pos)
         gv::view(gv::polygons(pos).face_normals(), "face normals (default)");
         gv::view(gv::polygons(pos).smooth_normals(), "smooth normals");
 
-        pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
+        const pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
         gv::view(gv::polygons(pos).normals(vnormals), "custom normals");
 
         // see PolygonBuilder.hh for more information
@@ -101,13 +101,16 @@ void advanced_objects(pm::vertex_attribute<tg::pos3> const& pos)
         // by default, lines are rendered as 3D capsule with a fixed screen-space size
         // customization includes
         //   - billboard mode (camera-facing or oriented by normals)
+        //   - line width (screen space or world space) measures the whole line, i.e. diameter and not radius, which differs from point_size
         //   - cap mode (round, square, none)
-        //   - line width (screen space or world space)
+        //   - extrapolation (on/off) of the color gradient between vertices onto the caps
+        // configurations can be combined in many ways, warnings are printed on the console if something is not compatible
 
         auto g = gv::grid();
 
-        pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
-        pm::vertex_attribute<float> line_widths = pos.map([](tg::pos3 p) { return 0.01f * tg::abs(p.x); });
+        const pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
+        const pm::edge_attribute<float> line_widths
+            = m.edges().map([&](pm::edge_handle e) { return 0.25f * distance(pos(e.vertexA()), pos(e.vertexB())); });
 
         gv::view(gv::lines(pos).line_width_world(0.01f).normals(vnormals), "oriented billboard lines");
         gv::view(gv::lines(pos).line_width_px(10), "10px line width");
@@ -119,7 +122,7 @@ void advanced_objects(pm::vertex_attribute<tg::pos3> const& pos)
     {
         // build some planar connected random lines
         std::vector<tg::segment3> lines;
-        auto bb = tg::aabb3({-1, 0, -1}, {1, 0, 1});
+        const auto bb = tg::aabb3({-1, 0, -1}, {1, 0, 1});
         auto p = uniform(rng, bb);
         for (auto i = 0; i < 30; ++i)
         {
@@ -132,23 +135,23 @@ void advanced_objects(pm::vertex_attribute<tg::pos3> const& pos)
         auto g = gv::grid();
         gv::view(gv::lines(lines).line_width_world(0.04f).normals(tg::vec3::unit_y).round_caps(), "round caps");
         gv::view(gv::lines(lines).line_width_world(0.04f).normals(tg::vec3::unit_y).square_caps(), "square caps");
+        gv::view(gv::lines(lines).line_width_world(0.04f), "3D lines always have round caps");
         gv::view(gv::lines(lines).line_width_world(0.04f).normals(tg::vec3::unit_y).no_caps(), "no caps");
     }
     {
         auto g = gv::grid();
 
-        pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
+        const pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
 
         // compute per-vertex average edge length as point size
         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); });
+        const pm::vertex_attribute<float> ptsize = m.vertices().map([&](pm::vertex_handle v) { return 0.5f * v.edges().avg(edge_lengths); });
 
-        // Square, oriented point cloud with adaptive point size
-        gv::view(gv::points(pos).point_size_world(ptsize).normals(vnormals).square(), "normal oriented squares");
-
-        gv::view(gv::points(pos).spheres().point_size_world(0.03f), "3D sphere cloud");
-
-        gv::view(gv::points(pos).point_size_px(10), "10px screen-size points");
+        gv::view(gv::points(pos).point_size_px(10), "10px screen-size spheres");
+        gv::view(gv::points(pos).point_size_world(0.03f).spheres(), "3D world space spheres"); // The default is 3D spheres, calling .spheres() is not necessary
+        gv::view(gv::points(pos).camera_facing().square(), "camera facing billboard squares"); // The default is camera facing, calling .camera_facing() is not necessary
+        gv::view(gv::points(pos).point_size_world(ptsize).normals(vnormals).round(), "normal oriented disks with adaptive point size");
+        // note: point sizes measure the radius of the points, which differs from line_width
 
         // see PointBuilder.hh for more information
     }
@@ -182,15 +185,23 @@ void advanced_visualization(pm::vertex_attribute<tg::pos3> const& pos)
         // map positions to colors
         pm::vertex_attribute<tg::color3> vcolors = m.vertices().map([&](pm::vertex_handle v) { return tg::color3(pos[v] * 0.5f + 0.5f); });
 
+        // Normals are needed for two colored lines
+        pm::vertex_attribute<tg::vec3> vnormals = pm::vertex_normals_by_area(pos);
+
         gv::view(pos, tg::color3::red, "solid color for whole mesh");
         gv::view(pos, random_face_colors, "random face colors");
         gv::view(pos, random_vertex_colors, "random vertex colors");
         gv::view(pos, random_halfedge_colors, "random halfedge colors");
         gv::view(gv::points(pos), vcolors, "point cloud with xyz colors");
         gv::view(gv::points(pos), random_vertex_colors, gv::no_shading, "unlit points");
-        gv::view(gv::lines(pos), random_edge_colors, "lines with random edge colors");
+        gv::view(gv::lines(pos).normals(vnormals).force3D(), random_face_colors, "two colored lines with random face colors");
         gv::view(gv::lines(pos), random_vertex_colors, "lines with random vertex colors");
-        gv::view(gv::lines(pos), random_vertex_colors, gv::no_shading, "unlit lines");
+        gv::view(gv::lines(pos), random_edge_colors, "lines with random edge colors");
+        gv::view(gv::lines(pos), random_edge_colors, gv::no_shading, "unlit lines");
+        // note: when normals are provided, line rendering defaults to normal aligned flat lines.
+        // use force3D() to render these lines as 3D capsules anyway.
+        // without normals, the default rendering is 3D capsules, so force3D is not necessary there.
+        // The default values of gv::lines can be seen and changed in LineRenderable.cc LineRenderable::initFromBuilder (respectively PointRenderable for gv::points)
     }
 
     // data mapping
@@ -205,14 +216,19 @@ void advanced_visualization(pm::vertex_attribute<tg::pos3> const& pos)
         // map vertex data to black-red, from 0.1 .. 0.3 (repeats outside)
         gv::view(pos, gv::mapping(vdata).linear(tg::color3::black, tg::color3::red, 0.1f, 0.3f), "vertex data");
 
-        // map face data to red (small) to green (big) from 0.05 .. 0.5 (data outside this range is clamped)
-        gv::view(pos, gv::mapping(fdata).linear(tg::color3::red, tg::color3::green, 0.05f, 0.5f).clamped(), "face data");
+        // map face data to red (small) to green (big) (data outside the range is clamped)
+        gv::view(pos, gv::mapping(fdata).linear(tg::color3::red, tg::color3::green, 0.00001f, 0.001f).clamped(), "face data");
     }
 
     // masking
     {
         auto g = gv::grid();
 
+        // two cuts along the same plane, one smooth the other along primitive edges
+        // note that they are the same because the first is compared against the default threshold of 0.5 and the second is converted to boolean
+        auto smooth_cut_mask = pm::vertex_attribute<float>(pos.map([](tg::pos3 v) { return v.x + 0.3f; }));
+        auto polygon_cut_mask = pm::vertex_attribute<bool>(pos.map([](tg::pos3 v) { return v.x > 0.2f; }));
+
         // some random boolean masks
         pm::face_attribute<bool> random_face_mask = m.faces().map([&](pm::face_handle) { return tg::uniform(rng, {true, false}); });
         pm::vertex_attribute<bool> random_vertex_mask = m.vertices().map([&](pm::vertex_handle) { return tg::uniform(rng, {true, false}); });
@@ -221,10 +237,14 @@ void advanced_visualization(pm::vertex_attribute<tg::pos3> const& pos)
         pm::vertex_attribute<float> vdata = m.vertices().map([&](pm::vertex_handle v) { return tg::cos(720_deg * (pos[v].x + pos[v].y + pos[v].z)); });
 
         // with gv::masked(...) it is possible to discard geometry from being rendered
-        // (this is done on a per-pixel basis)
+        // (this is done on a per-pixel basis, that interpolates between the values at the vertices)
+        // Note that the default threshold is 0.5, since that works better with the conversion of false to 0 and true to 1
+        gv::view(pos, gv::masked(smooth_cut_mask), "smooth cut mask");
         gv::view(pos, gv::masked(random_face_mask), "randomly masked-out faces");
         gv::view(pos, gv::masked(random_vertex_mask), "randomly masked-out vertices");
+        gv::view(pos, gv::masked(polygon_cut_mask), "cut along primitive edges");
         gv::view(pos, gv::masked(vdata, 0.2f), "threshold-based mask");
+        gv::view(gv::lines(pos), gv::masked(vdata, 0.2f), "also works for lines"); // and points as well
     }
 
     // textures
@@ -276,32 +296,36 @@ void typed_geometry_objects()
     // TODO: support more tg objects
 
     // 3D typed geometry objects can be rendered directly
-    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::points(tg::pos3::zero).point_size_world(0.25f), "tg::pos"); // point_size_world is only used to give the grid on the ground a reference scale
+    gv::view(tg::segment3(tg::pos3(0, 0, 0), tg::pos3(1, 0, 0)), tg::color3::red, "tg::segment");
+    gv::view(tg::triangle3({0, 0, 0}, {1, 0, 0}, {0, 1, 0}), tg::color3::blue, "tg::triangle");
+    gv::view(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f}), tg::color3::green, "tg::aabb");
 
     // gv::lines can be used to make them into line drawings instead of solids
-    gv::view(gv::lines(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f})));
+    gv::view(gv::lines(tg::aabb3({-0.3f, 0, -0.4f}, {0.2f, 0.5f, 0.1f})), "lines(tg::aabb)");
 
     // vector versions
+    {
+        std::vector<tg::pos3> pts;
+        pts.reserve(500);
+        for (auto i = 0; i < 500; ++i)
+            pts.push_back(uniform(rng, tg::sphere3::unit));
+        gv::view(pts, "point cloud");
+    }
     {
         std::vector<tg::segment3> segs;
+        segs.reserve(20);
         for (auto i = 0; i < 20; ++i)
             segs.emplace_back(uniform(rng, tg::sphere3::unit), uniform(rng, tg::sphere3::unit));
         gv::view(segs, "tg::segments");
     }
     {
         std::vector<tg::triangle3> tris;
+        tris.reserve(20);
         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, "tg::triangle soup");
     }
-    {
-        std::vector<tg::pos3> pts;
-        for (auto i = 0; i < 500; ++i)
-            pts.push_back(uniform(rng, tg::sphere3::unit));
-        gv::view(pts, "point cloud");
-    }
     {
         std::vector<tg::box3> bbs;
         for (auto i = 0; i < 4; ++i)
@@ -310,7 +334,7 @@ void typed_geometry_objects()
             auto e1 = cross(e0, uniform_vec(rng, tg::sphere3::unit));
             auto e2 = cross(e1, e0);
 
-            auto c = uniform(rng, tg::sphere3::unit);
+            const 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);
@@ -318,8 +342,8 @@ void typed_geometry_objects()
             bbs.push_back(tg::box3(c, {e0, e1, e2}));
         }
 
-        gv::view(gv::lines(bbs), "tg::box frames");
         gv::view(bbs, "tg::box soup");
+        gv::view(gv::lines(bbs), "tg::box frames");
     }
 }
 
@@ -336,20 +360,23 @@ void advanced_configs(pm::vertex_attribute<tg::pos3> const& pos)
     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");
     gv::view(pos, tg::aabb3(-2, 2), "custom scene AABB");
 
+    gv::view(pos, gv::print_mode, "print-friendly mode");
+    gv::view(pos, gv::background_color(tg::color3::blue), "custom BG color");
+    // note: what may look like a vignette effect is the default background containing a gradient.
+    // it can be disabled by settings a constant background_color.
+
     // some configurations have booleans that can be passed
     gv::view(pos, gv::print_mode(false), "disabled print mode");
 
     // explicit view configure
     {
-        auto v = gv::view(pos);
-        v.configure(gv::no_grid);
+        auto v = gv::view(pos, "explicit view configure");
+        v.configure(gv::no_grid, gv::no_outline);
     }
 }
 
@@ -360,12 +387,12 @@ void advanced_layouting(pm::vertex_attribute<tg::pos3> const& pos)
     // gv::grid(1.6f) would try a 16 : 10 ratio
     // gv::grid(3, 4) would make 3 columns with 4 rows
     auto g = gv::grid();
-    gv::view(pos);
+    gv::view(pos, "nested grid layout");
     gv::view(pos);
     {
         // a column sub layout
         auto c = gv::columns();
-        gv::view(pos);
+        gv::view(pos, " with columns");
         gv::view(pos);
         gv::view(pos);
     }
@@ -374,7 +401,7 @@ void advanced_layouting(pm::vertex_attribute<tg::pos3> const& pos)
         auto c = gv::rows();
         gv::view(pos);
         gv::view(pos);
-        gv::view(pos);
+        gv::view(pos, "and rows");
     }
 }
 
@@ -393,7 +420,7 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
         if (ImGui::Button("close viewer"))
             gv::close_viewer();
 
-        gv::view(pos);
+        gv::view(pos, "interactive viewer with custom buttons. It is slow because nothing is cached.");
     });
 
     // creating renderables is expensive, cache them whenever possible
@@ -404,8 +431,9 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
             static auto time = 0.f;
             time += dt;
 
-            // gv::view_cleared creates an always a cleared view, resetting accumulation each frame
-            gv::view_cleared(r, tg::translation(tg::vec3(tg::sin(tg::radians(time * .5f)) * .5f, 0.f, 0.f)));
+            // gv::view_cleared creates an always cleared view, resetting accumulation each frame
+            gv::view_cleared(r, tg::translation(tg::vec3(tg::sin(tg::radians(time * .5f)) * .5f, 0.f, 0.f)),
+                             "Caching renderables in interactive views increases performance.");
         });
     }
 
@@ -419,7 +447,7 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
             changed |= ImGui::SliderFloat("Height", &configurable, -3.f, 3.f);
 
             // gv::clear_accumulation conditionally clears the view accumulation
-            gv::view(r, tg::translation(tg::vec3(0.f, configurable, 0.f)), gv::clear_accumulation(changed));
+            gv::view(r, tg::translation(tg::vec3(0.f, configurable, 0.f)), gv::clear_accumulation(changed), "interactive translation of a model");
         });
     }
 
@@ -436,8 +464,10 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
             [&](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;
+
+                auto const orad = 8.f;
+                auto const irad = 3.f;
+
                 tg::vec3 t;
                 t.x = cx;
                 t.z = sx;
@@ -446,6 +476,7 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
                 p.y = irad * cy;
                 p.z = orad * sx;
                 p += t * irad * sy;
+
                 pos[v] = p;
                 uv[v] = {1 - x, y};
             },
@@ -464,11 +495,88 @@ void interactive_viewer(pm::vertex_attribute<tg::pos3> const& pos)
             changed |= animate;
 
             // texture coordinates are rotated by "a" degrees
-            view(pos, gv::textured(uv, tex).transform(tg::rotation_around(tg::pos2::zero, tg::degree(a))), gv::clear_accumulation(changed));
+            view(pos, gv::textured(uv, tex).transform(tg::rotation_around(tg::pos2::zero, tg::degree(a))), gv::clear_accumulation(changed),
+                 "animated texture coordinates");
         });
     }
 }
 
+void scenarios()
+{
+    // in contrast to the other example groups, this group does not introduce new concepts.
+    // it combines the single features and shows how they can be used in some scenarios
+
+    // debugging / visualizing geometric operations
+    {
+        auto g = gv::grid();
+        tg::rng rng;
+
+        // construct a random box
+        const auto c = uniform(rng, tg::sphere3::unit);
+        auto e0 = uniform_vec(rng, tg::sphere3::unit);
+        auto e1 = cross(e0, uniform_vec(rng, tg::sphere3::unit));
+        auto e2 = cross(e1, e0);
+        e0 = normalize(e0) * uniform(rng, 0.5f, 0.9f);
+        e1 = normalize(e1) * uniform(rng, 0.5f, 0.9f);
+        e2 = normalize(e2) * uniform(rng, 0.5f, 0.9f);
+        const auto box = tg::box3(c, {e0, e1, e2});
+
+        // check for some random points if they are inside or outside the box
+        const auto range = tg::sphere3(tg::pos3::zero, 2.f);
+        std::vector<tg::pos3> insidePoints;
+        std::vector<tg::pos3> outsidePoints;
+        for (auto i = 0; i < 1000; ++i)
+        {
+            const auto p = uniform(rng, range);
+            if (contains(box, p))
+                insidePoints.push_back(p);
+            else
+                outsidePoints.push_back(p);
+        }
+
+        {
+            auto v = gv::view();
+            gv::view(box, tg::color4(tg::color3::cyan, 0.5f), "Visualize contained and outside points");
+            gv::view(gv::lines(box), tg::color3::cyan);
+            gv::view(insidePoints, gv::maybe_empty, tg::color3::green);
+            gv::view(outsidePoints, gv::maybe_empty, tg::color3::red);
+        }
+
+        // project outside points onto the box
+        std::vector<tg::segment3> projections;
+        projections.reserve(outsidePoints.size());
+        for (const auto& p : outsidePoints)
+            projections.emplace_back(p, project(p, box));
+
+        {
+            auto v = gv::view();
+            gv::view(box, tg::color3::cyan, "Visualize projection of outside points");
+            gv::view(outsidePoints, gv::maybe_empty);
+            gv::view(projections, gv::maybe_empty);
+        }
+
+        // measure distance between points and box
+        std::vector<tg::pos3> points;
+        std::vector<float> distances;
+        const auto num = 25;
+        points.reserve(num);
+        distances.reserve(num);
+        for (auto i = 0; i < num; ++i)
+        {
+            auto p = uniform(rng, range);
+            points.push_back(p);
+            distances.push_back(distance(p, box));
+        }
+
+        {
+            auto v = gv::view();
+            gv::view(box, tg::color3::cyan, "Use point spheres to visualize distances");
+            gv::view(points);
+            gv::view(gv::points(points).point_size_world(distances)); // point_size specifies the radius of the rendered spheres
+        }
+    }
+}
+
 void vector_graphics()
 {
     // TODO: text
@@ -479,7 +587,7 @@ void vector_graphics()
     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);
+    gv::view(img, "2D vector graphics");
 }
 
 void custom_renderables()
@@ -503,7 +611,27 @@ void special_use_cases(pm::vertex_attribute<tg::pos3> const& pos)
 {
     // TODO: decoupled camera
 
-    gv::view(pos, gv::maybe_empty, "allows empty renderable");
+    // extrapolation of attributes on line caps
+    {
+        pm::Mesh mLine;
+        const auto v0 = mLine.vertices().add();
+        const auto v1 = mLine.vertices().add();
+        mLine.edges().add_or_get(v0, v1);
+        const auto line = mLine.vertices().make_attribute_from_data<tg::pos3>({{0, 0, 0}, {1, 0, 0}});
+        const auto lineColors = mLine.vertices().make_attribute_from_data<glow::colors::color>({{0.7f, 0.2f, 0.2f}, {0.2f, 0.7f, 0.2f}});
+
+        auto v = glow::viewer::grid();
+        gv::view(gv::lines(line).line_width_world(0.25f).extrapolate(), lineColors, "extrapolated color at line caps");
+        gv::view(gv::lines(line).line_width_world(0.25f), lineColors, "whole line cap has the color of the vertex");
+    }
+
+    // empty renderable
+    {
+        auto g = gv::grid();
+        const std::vector<tg::triangle3> tris;
+        gv::view(tris, gv::maybe_empty, "allows empty renderable");
+        gv::view(pos, gv::maybe_empty, "but does not necessarily have to be empty if specified");
+    }
 
     gv::view(pos, gv::infinite_accumulation, "progressive rendering is not stopped early");
 
@@ -527,40 +655,40 @@ void special_use_cases(pm::vertex_attribute<tg::pos3> const& pos)
     // gv::get_last_close_info() returns:
     //   - which key closed the viewer
     //   - camera position and target when viewer was closed
-    gv::view(pos, gv::close_keys('A', 'B', 'C'));
+    gv::view(pos, gv::close_keys('A', 'B', 'C'), "close the viewer by pressing A, B, or C");
     glow::info() << gv::get_last_close_info().closed_by_key;
     glow::info() << gv::get_last_close_info().cam_pos;
     glow::info() << gv::get_last_close_info().cam_target;
 
     // use an outer viewer object conditionally
     {
-        auto some_condition = false;
+        const auto some_condition = false;
         auto v = some_condition ? gv::rows() : gv::nothing();
 
         // these two either nest into the outer object, or create their own windows
-        gv::view(pos);
-        gv::view(pos);
+        gv::view(pos, "conditional grouping 1");
+        gv::view(pos, "conditional grouping 2");
     }
 
     // a view can be explicitly shown before scope end
     // (it will not show again at the end of the scope)
     {
-        auto v = gv::view(pos);
+        auto v = gv::view(pos, "show viewer before end of scope");
         v.show();
     }
 
     // per default, cameras are shared for all views
     // by creating custom camera controllers, custom sharing can be configured
     {
-        auto camA = gv::CameraController::create();
-        auto camB = gv::CameraController::create();
+        const auto camA = gv::CameraController::create();
+        const auto camB = gv::CameraController::create();
         auto g = gv::grid();
         gv::view(pos, "built-in camera");
         gv::view(pos, "built-in camera");
         gv::view(pos, camA, "custom camera A");
-        gv::view(pos, camA, "custom camera A");
-        gv::view(pos, camB, "custom camera B");
+        gv::view(tg::aabb3::unit_centered, camA, "custom camera A");
         gv::view(pos, camB, "custom camera B");
+        gv::view(gv::lines(pos), camB, "custom camera B");
     }
 
     // per default, the camera is reset for each new view (and fit to the scene)
@@ -568,7 +696,7 @@ void special_use_cases(pm::vertex_attribute<tg::pos3> const& pos)
     // configuring gv::reuse_camera means that the current view will use the last camera
     // these can be chained indefinitely
     {
-        gv::view(pos, gv::preserve_camera, "next view uses the same camera");
+        gv::view(pos, gv::preserve_camera, "next view will use the same camera");
         gv::view(pos);
         gv::view(pos, gv::reuse_camera, "re-used previous camera");
         gv::view(pos, gv::reuse_camera, "re-used previous camera (again)");
@@ -589,6 +717,41 @@ void subtle_cases(pm::vertex_attribute<tg::pos3> const& pos)
     gv::view(pos, tg::scaling(2.0, 2.0, 2.0), "double transform");
 }
 
+void known_issues(pm::vertex_attribute<tg::pos3> const& pos)
+{
+    pm::Mesh mLine;
+    const auto v0 = mLine.vertices().add();
+    const auto v1 = mLine.vertices().add();
+    mLine.edges().add_or_get(v0, v1);
+    const auto line = mLine.vertices().make_attribute_from_data<tg::pos3>({{0, 0, 0}, {1, 0, 0}});
+    const auto lineColors = mLine.vertices().make_attribute_from_data<glow::colors::color>({{0.7f, 0.2f, 0.2f}, {0.2f, 0.7f, 0.2f}});
+
+    // Intersection of screen space camera facing lines and viewer grid creates artifacts
+    // Also the round caps of large screen space camera facing lines are not smoothly connecting to the straight line part
+    {
+        auto v = glow::viewer::view();
+        gv::view(gv::lines(line).camera_facing().line_width_px(500.0f), tg::color3::red, "ground shadow intersects red screen space camera facing line");
+        // It is fine for the green world space because the bounding box of world space moves the object up
+        gv::view(gv::lines(line).camera_facing().line_width_world(0.25f), tg::color3::green, tg::translation(0.0f, 0.0f, 1.0f));
+    }
+
+    // When setting a custom camera target view, the camera jumps the first time it is dragged with the left or right mouse button
+    {
+        gv::view(pos, gv::camera_transform(tg::pos3(1, 1, 1), tg::pos3(0, 0, 0)), "explicit start position and target creates jumping camera upon first drag");
+        // gv::camera_orientation works fine
+        gv::view(pos, gv::camera_orientation(125_deg, -15_deg, 1.7f), "explicit camera azimuth/altitude/distance works fine");
+    }
+
+    // Artifacts on grazing angles
+    // When the camera is outside of the capsule but close to the line spanned by its axis, all sorts of artifacts occur
+    {
+        gv::view(gv::lines(line).line_width_px(250.0f), lineColors, "artifacts on grazing angles (screen space)");
+        gv::view(gv::lines(line).line_width_world(1.5f), lineColors, tg::translation(0.f, 0.f, 5.f), "artifacts on grazing angles (screen space)");
+        // Screen space camera facing lines seem fine
+        // gv::view(gv::lines(line).line_width_px(250.0f).camera_facing(), lineColors, tg::translation(0.0f, 0.0f, -25.f));
+    }
+}
+
 int main()
 {
     // create a rendering context
@@ -620,6 +783,8 @@ int main()
         advanced_layouting(pos);
 
         interactive_viewer(pos);
+
+        scenarios();
     }
 
     // expert / specific demos
@@ -635,5 +800,8 @@ int main()
         headless_screenshot(pos);
     }
 
+    // known issues
+    known_issues(pos);
+
     return EXIT_SUCCESS;
 }