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; }