diff --git a/CMakeLists.txt b/CMakeLists.txt index d34d45e..c093be8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,7 @@ if(pcg32_ADDED) target_include_directories(pcg32 INTERFACE "${pcg32_SOURCE_DIR}") endif() -CPMAddPackage("gh:wkjarosz/galois#2bf22e85952c8612832838a26baf68ad93beff7a") +CPMAddPackage("gh:wkjarosz/galois#6105d219a6f26777fc02a1649c2e1d56f6e8e51e") if(galois_ADDED) message(STATUS "galois++ library added") target_include_directories(galois++ INTERFACE "${galois_SOURCE_DIR}/include") diff --git a/assets/shaders/grid.frag b/assets/shaders/grid.frag index 705a4fa..0995796 100644 --- a/assets/shaders/grid.frag +++ b/assets/shaders/grid.frag @@ -1,6 +1,5 @@ uniform float alpha; uniform ivec2 size; -in vec4 v_position; in vec2 v_texcoord; // The MIT License diff --git a/assets/shaders/point_instance.frag b/assets/shaders/point_instance.frag new file mode 100644 index 0000000..c433969 --- /dev/null +++ b/assets/shaders/point_instance.frag @@ -0,0 +1,29 @@ +uniform mat4 mvp; +uniform mat3 rotation; +uniform vec3 color; +uniform float point_size; +in vec2 v_texcoord; + +// taken from https://gist.github.com/romainguy/6415cbf511233c063a1abe691ad4883b +vec3 Irradiance_SphericalHarmonics(const vec3 n) +{ + // Uniform array of 4 vec3 for SH environment lighting + // Computed from "Ditch River" (http://www.hdrlabs.com/sibl/archive.html) + const vec3 sh0 = vec3(0.754554516862612, 0.748542953903366, 0.790921515418539); + const mat3 sh1 = mat3(vec3(-0.188884931542396, -0.277402551592231, -0.377844212327557), + vec3(0.308152705331738, 0.366796330467391, 0.466698181299906), + vec3(-0.083856548007422, 0.092533500963210, 0.322764661032516)); + return max(sh0 + sh1 * n, 0.0); +} + +void main() +{ + float alpha = 1.0; + float radius2 = dot(v_texcoord, v_texcoord); + if (radius2 > 1.0) + discard; + vec3 n = vec3(v_texcoord, sqrt(1.0 - radius2)); + vec3 sh = Irradiance_SphericalHarmonics(n); + sh = mix(mix(sh, vec3(dot(sh, vec3(1.0 / 3.0))), 0.5), vec3(1.0), 0.25); + fo_FragColor = vec4(sh * color * alpha, alpha); +} diff --git a/assets/shaders/point_instance.vert b/assets/shaders/point_instance.vert new file mode 100644 index 0000000..cf4e972 --- /dev/null +++ b/assets/shaders/point_instance.vert @@ -0,0 +1,14 @@ +uniform mat4 mvp; +uniform mat4 smash; +uniform mat3 rotation; +uniform float point_size; +in vec3 vertices; +in vec3 center; +out vec2 v_texcoord; + +void main() +{ + gl_Position = + mvp * (vec4(rotation * (point_size * vertices), 1.0) + vec4((smash * vec4(center - 0.5, 1.0)).xyz, 0.0)); + v_texcoord = 2.0 * vertices.xy; +} \ No newline at end of file diff --git a/assets/shaders/points.frag b/assets/shaders/points.frag deleted file mode 100644 index e766672..0000000 --- a/assets/shaders/points.frag +++ /dev/null @@ -1,15 +0,0 @@ -uniform vec3 color; -uniform float point_size; -void main() -{ - float alpha = 1.0; - // if (point_size > 3.0) - { - vec2 circCoord = 2.0 * gl_PointCoord - 1.0; - float radius2 = dot(circCoord, circCoord); - if (radius2 > 1.0) - discard; - // alpha = 1.0 - smoothstep(1.0 - 2.0 / point_size, 1.0, sqrt(radius2)); - } - fo_FragColor = vec4(color * alpha, alpha); -} diff --git a/assets/shaders/points.vert b/assets/shaders/points.vert deleted file mode 100644 index ad7e830..0000000 --- a/assets/shaders/points.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 mvp; -uniform float point_size; -in vec3 position; - -void main() -{ - gl_Position = mvp * vec4(position - vec3(0.5), 1.0); - gl_PointSize = point_size; -} \ No newline at end of file diff --git a/include/app.h b/include/app.h index 0300142..ef6a171 100644 --- a/include/app.h +++ b/include/app.h @@ -63,14 +63,14 @@ enum CameraType struct CameraParameters { - Arcball arcball; - float persp_factor = 0.0f; - float zoom = 1.0f, view_angle = 30.0f; - float dnear = 0.05f, dfar = 1000.0f; - float3 eye = float3{0.0f, 0.0f, 2.0f}; - float3 center = float3{0.0f, 0.0f, 0.0f}; - float3 up = float3{0.0f, 1.0f, 0.0f}; - CameraType camera_type = CAMERA_CURRENT; + Arcball arcball; + float persp_factor = 0.0f; + float zoom = 1.0f, view_angle = 30.0f; + float dnear = 0.05f, dfar = 1000.0f; + static constexpr float3 eye = float3{0.0f, 0.0f, 2.0f}; + static constexpr float3 center = float3{0.0f, 0.0f, 0.0f}; + static constexpr float3 up = float3{0.0f, 1.0f, 0.0f}; + CameraType camera_type = CAMERA_CURRENT; float4x4 matrix(float window_aspect) const; }; @@ -114,7 +114,7 @@ class SampleViewer void populate_point_subset(); void draw_text(const int2 &pos, const std::string &text, const float4 &col, ImFont *font = nullptr, int align = TextAlign_RIGHT | TextAlign_BOTTOM) const; - void draw_points(const float4x4 &mvp, const float3 &color); + void draw_points(const float4x4 &mvp, const float4x4 &smash, const float3 &color); void draw_trigrid(Shader *shader, const float4x4 &mvp, float alpha, const int2x3 &count); void draw_2D_points_and_grid(const float4x4 &mvp, int2 dims, int plotIndex); int2 get_draw_range() const; @@ -126,7 +126,7 @@ class SampleViewer int m_num_dimensions = 3; int3 m_dimension{0, 1, 2}; Array2d m_points, m_subset_points; - vector m_3d_points; + vector m_3d_points, m_2d_points; int m_target_point_count = 256, m_point_count = 256; int m_subset_count = 0; @@ -142,7 +142,7 @@ class SampleViewer bool m_show_1d_projections = false, m_show_point_nums = false, m_show_point_coords = false, m_show_coarse_grid = false, m_show_fine_grid = false, m_show_custom_grid = false, m_show_bbox = false; - Shader *m_point_shader = nullptr, *m_2d_point_shader = nullptr, *m_grid_shader = nullptr; + Shader *m_3d_point_shader = nullptr, *m_2d_point_shader = nullptr, *m_grid_shader = nullptr; int2 m_viewport_pos, m_viewport_pos_GL, m_viewport_size; float m_animate_start_time = 0.0f; diff --git a/include/arcball.h b/include/arcball.h index 258720d..1fff420 100644 --- a/include/arcball.h +++ b/include/arcball.h @@ -113,10 +113,14 @@ struct Arcball return true; } + Quatf quat() const + { + return linalg::qmul(m_incr, m_quat); + } + Mat44f matrix() const { - auto m = linalg::qmat(linalg::qmul(m_incr, m_quat)); - return {{m.x, 0.f}, {m.y, 0.f}, {m.z, 0.f}, {0.f, 0.f, 0.f, 1.f}}; + return linalg::rotation_matrix(quat()); } private: diff --git a/include/shader.h b/include/shader.h index dde4411..301d735 100644 --- a/include/shader.h +++ b/include/shader.h @@ -102,6 +102,13 @@ class Shader set_buffer(name, get_type(), 2, shape, vecs.data()); } + template + void set_buffer(const std::string &name, const std::vector> &vecs, size_t offset, size_t count) + { + size_t shape[3] = {count, M, 1}; + set_buffer(name, get_type(), 2, shape, &vecs[offset]); + } + template void set_buffer(const std::string &name, const std::vector &data) { diff --git a/src/app.cpp b/src/app.cpp index a11c599..3fcc5b6 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -86,11 +86,6 @@ static const vector> g_help_strings = { {"p", "Toggle display of 1D X, Y, Z projections of the points"}}; static const map g_tooltip_map(g_help_strings.begin(), g_help_strings.end()); -static float map_slider_to_radius(float sliderValue) -{ - return sliderValue * sliderValue * 32.0f + 2.0f; -} - static float4x4 layout_2d_matrix(int num_dims, int2 dims) { float cell_spacing = 1.f / (num_dims - 1); @@ -151,7 +146,7 @@ SampleViewer::SampleViewer() m_camera[CAMERA_XY].persp_factor = 0.0f; m_camera[CAMERA_XY].camera_type = CAMERA_XY; - m_camera[CAMERA_ZY].arcball.set_state(linalg::rotation_quat({0.f, 1.f, 0.f}, float(M_PI_2))); + m_camera[CAMERA_ZY].arcball.set_state(linalg::rotation_quat({0.f, -1.f, 0.f}, float(M_PI_2))); m_camera[CAMERA_ZY].persp_factor = 0.0f; m_camera[CAMERA_ZY].camera_type = CAMERA_ZY; @@ -346,13 +341,20 @@ SampleViewer::SampleViewer() glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); #endif + auto quad_verts = + vector{{-0.5f, -0.5f, 0.f}, {-0.5f, 0.5f, 0.0f}, {0.5f, 0.5f, 0.0f}, {0.5f, -0.5f, 0.0f}}; + m_2d_point_shader = new Shader("2D point shader", "shaders/point_instance.vert", + "shaders/point_instance.frag", Shader::BlendMode::AlphaBlend); + m_2d_point_shader->set_buffer("vertices", quad_verts); + m_2d_point_shader->set_buffer_divisor("vertices", 0); + + m_3d_point_shader = new Shader("3D point shader", "shaders/point_instance.vert", + "shaders/point_instance.frag", Shader::BlendMode::AlphaBlend); + m_3d_point_shader->set_buffer("vertices", quad_verts); + m_3d_point_shader->set_buffer_divisor("vertices", 0); - m_point_shader = - new Shader("Point shader", "shaders/points.vert", "shaders/points.frag", Shader::BlendMode::AlphaBlend); m_grid_shader = new Shader("Grid shader", "shaders/grid.vert", "shaders/grid.frag", Shader::BlendMode::AlphaBlend); - m_2d_point_shader = new Shader("Point shader 2D", "shaders/points.vert", "shaders/points.frag", - Shader::BlendMode::AlphaBlend); m_grid_shader->set_buffer( "position", vector{{-0.5f, -0.5f, 0.5f}, {-0.5f, 1.5f, 0.5f}, {1.5f, 1.5f, 0.5f}, {1.5f, -0.5f, 0.5f}}); @@ -420,9 +422,7 @@ void SampleViewer::draw_gui() int2{int(central_node->Pos.x), int(io.DisplaySize.y - (central_node->Pos.y + central_node->Size.y))}; } - float radius = map_slider_to_radius(m_radius); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); float4x4 mvp = m_camera[CAMERA_CURRENT].matrix(float(m_viewport_size.x) / m_viewport_size.y); @@ -872,7 +872,7 @@ void SampleViewer::draw_editor() { ImGui::ColorEdit3("Bg color", (float *)&m_bg_color); ImGui::ColorEdit3("Point color", (float *)&m_point_color); - ImGui::SliderFloat("Radius", &m_radius, 0.f, 1.f, ""); + ImGui::SliderFloat("Radius", &m_radius, 0.f, 1.f, "%4.3f"); ImGui::SameLine(); { ImGui::ToggleButton(ICON_FA_COMPRESS, &m_scale_radius_with_points); @@ -952,17 +952,20 @@ void SampleViewer::draw_editor() ImGui::PushItemWidth(widget_w); m_subset_by_coord = false; static bool disjoint = true; - ImGui::Checkbox("Disjoint batches", &disjoint); - ImGui::SliderInt("First point", &m_first_draw_point, 0, m_point_count - 1, "%d", - ImGuiSliderFlags_AlwaysClamp); + if (ImGui::Checkbox("Disjoint batches", &disjoint)) + m_gpu_points_dirty = true; + if (ImGui::SliderInt("First point", &m_first_draw_point, 0, m_point_count - 1, "%d", + ImGuiSliderFlags_AlwaysClamp)) + m_gpu_points_dirty = true; if (disjoint) // round to nearest multiple of m_point_draw_count m_first_draw_point = (m_first_draw_point / m_point_draw_count) * m_point_draw_count; tooltip("Display points starting at this index."); - ImGui::SliderInt("Num points##2", &m_point_draw_count, 1, m_point_count - m_first_draw_point, "%d", - ImGuiSliderFlags_AlwaysClamp); + if (ImGui::SliderInt("Num points##2", &m_point_draw_count, 1, m_point_count - m_first_draw_point, "%d", + ImGuiSliderFlags_AlwaysClamp)) + m_gpu_points_dirty = true; tooltip("Display this many points from the first index."); ImGui::PopItemWidth(); ImGui::Unindent(); @@ -1164,17 +1167,13 @@ void SampleViewer::update_points(bool regenerate) m_time1 = timer.elapsed(); - m_points.resize(m_point_count, m_num_dimensions); + m_points.resize(m_num_dimensions, m_point_count); + m_points.reset(0.5f); m_3d_points.resize(m_point_count); timer.reset(); for (int i = 0; i < m_point_count; ++i) - { - vector r(m_num_dimensions, 0.5f); - generator->sample(r.data(), i); - for (int j = 0; j < m_points.sizeY(); ++j) - m_points(i, j) = r[j]; - } + generator->sample(m_points.row(i), i); m_time2 = timer.elapsed(); } catch (const std::exception &e) @@ -1194,14 +1193,14 @@ void SampleViewer::update_points(bool regenerate) if (m_subset_by_coord) { m_subset_count = 0; - for (int i = 0; i < m_points.sizeX(); ++i) + for (int i = 0; i < m_points.sizeY(); ++i) { - float v = m_points(i, std::clamp(m_subset_axis, 0, m_num_dimensions - 1)); + float v = m_points(std::clamp(m_subset_axis, 0, m_num_dimensions - 1), i); if (v >= (m_subset_level + 0.0f) / m_num_subset_levels && v < (m_subset_level + 1.0f) / m_num_subset_levels) { // copy all dimensions (rows) of point i - for (int j = 0; j < m_subset_points.sizeY(); ++j) - m_subset_points(m_subset_count, j) = m_points(i, j); + for (int dim = 0; dim < m_points.sizeX(); ++dim) + m_subset_points(dim, m_subset_count) = m_points(dim, i); ++m_subset_count; } } @@ -1209,25 +1208,29 @@ void SampleViewer::update_points(bool regenerate) int3 dims = linalg::clamp(m_dimension, int3{0}, int3{m_num_dimensions - 1}); for (size_t i = 0; i < m_3d_points.size(); ++i) - m_3d_points[i] = float3{m_subset_points(i, dims.x), m_subset_points(i, dims.y), m_subset_points(i, dims.z)}; + m_3d_points[i] = float3{m_subset_points(dims.x, i), m_subset_points(dims.y, i), m_subset_points(dims.z, i)}; // // Upload points to the GPU // - m_point_shader->set_buffer("position", m_3d_points); + auto range = get_draw_range(); + m_3d_point_shader->set_buffer("center", m_3d_points, range.x, range.y); + m_3d_point_shader->set_buffer_divisor("center", 1); // one center per quad/instance // // create a temporary array to store all the 2D projections of the points. // each 2D plot actually needs 3D points, and there are num2DPlots of them - int num2DPlots = m_num_dimensions * (m_num_dimensions - 1) / 2; - vector points2D(num2DPlots * m_subset_count); + int num2DPlots = m_num_dimensions * (m_num_dimensions - 1) / 2; + m_2d_points.resize(num2DPlots * m_subset_count); for (int y = 0, plot_index = 0; y < m_num_dimensions; ++y) for (int x = 0; x < y; ++x, ++plot_index) for (int i = 0; i < m_subset_count; ++i) - points2D[plot_index * m_subset_count + i] = float3{m_subset_points(i, x), m_subset_points(i, y), 0.5f}; + m_2d_points[plot_index * m_subset_count + i] = + float3{m_subset_points(x, i), m_subset_points(y, i), 0.5f}; - m_2d_point_shader->set_buffer("position", points2D); + m_2d_point_shader->set_buffer("center", m_2d_points); + m_2d_point_shader->set_buffer_divisor("center", 1); // one center per quad/instance m_gpu_points_dirty = false; } @@ -1238,16 +1241,22 @@ void SampleViewer::draw_2D_points_and_grid(const float4x4 &mvp, int2 dims, int p // Render the point set m_2d_point_shader->set_uniform("mvp", mul(mvp, pos)); - float radius = map_slider_to_radius(m_radius / (m_num_dimensions - 1)); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); + m_2d_point_shader->set_uniform("rotation", float3x3(linalg::identity)); + m_2d_point_shader->set_uniform("smash", float4x4(linalg::identity)); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); m_2d_point_shader->set_uniform("point_size", radius); m_2d_point_shader->set_uniform("color", m_point_color); int2 range = get_draw_range(); - m_2d_point_shader->begin(); - m_2d_point_shader->draw_array(Shader::PrimitiveType::Point, m_subset_count * plot_index + range.x, range.y); - m_2d_point_shader->end(); + m_2d_point_shader->set_buffer("center", m_2d_points, m_subset_count * plot_index + range.x, range.y); + m_2d_point_shader->set_buffer_divisor("center", 1); // one center per quad/instance + + if (range.y > 0) + { + m_2d_point_shader->begin(); + m_2d_point_shader->draw_array(Shader::PrimitiveType::TriangleFan, 0, 4, false, range.y); + m_2d_point_shader->end(); + } auto mat = mul(mvp, mul(pos, m_camera[CAMERA_2D].arcball.matrix())); @@ -1388,19 +1397,19 @@ void SampleViewer::draw_scene() { // smash the points against the axes and draw float4x4 smashX = - mul(mvp, mul(translation_matrix(float3{-0.51f, 0.f, 0.f}), scaling_matrix(float3{0.f, 1.f, 1.f}))); - draw_points(smashX, {0.8f, 0.3f, 0.3f}); + mul(translation_matrix(float3{-0.51f, 0.f, 0.f}), scaling_matrix(float3{0.f, 1.f, 1.f})); + draw_points(mvp, smashX, {0.8f, 0.3f, 0.3f}); float4x4 smashY = - mul(mvp, mul(translation_matrix(float3{0.f, -0.51f, 0.f}), scaling_matrix(float3{1.f, 0.f, 1.f}))); - draw_points(smashY, {0.3f, 0.8f, 0.3f}); + mul(translation_matrix(float3{0.f, -0.51f, 0.f}), scaling_matrix(float3{1.f, 0.f, 1.f})); + draw_points(mvp, smashY, {0.3f, 0.8f, 0.3f}); float4x4 smashZ = - mul(mvp, mul(translation_matrix(float3{0.f, 0.f, -0.51f}), scaling_matrix(float3{1.f, 1.f, 0.f}))); - draw_points(smashZ, {0.3f, 0.3f, 0.8f}); + mul(translation_matrix(float3{0.f, 0.f, -0.51f}), scaling_matrix(float3{1.f, 1.f, 0.f})); + draw_points(mvp, smashZ, {0.3f, 0.3f, 0.8f}); } - draw_points(mvp, m_point_color); + draw_points(mvp, float4x4(linalg::identity), m_point_color); if (m_show_custom_grid) { @@ -1447,21 +1456,23 @@ void SampleViewer::set_view(CameraType view) } } -void SampleViewer::draw_points(const float4x4 &mvp, const float3 &color) +void SampleViewer::draw_points(const float4x4 &mvp, const float4x4 &smash, const float3 &color) { - // Render the point set - m_point_shader->set_uniform("mvp", mvp); - float radius = map_slider_to_radius(m_radius); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); - m_point_shader->set_uniform("point_size", radius); - m_point_shader->set_uniform("color", color); - - int2 range = get_draw_range(); + auto range = get_draw_range(); + if (range.y <= 0) + return; - m_point_shader->begin(); - m_point_shader->draw_array(Shader::PrimitiveType::Point, range.x, range.y); - m_point_shader->end(); + // Render the point set + m_3d_point_shader->set_uniform("mvp", mvp); + m_3d_point_shader->set_uniform("rotation", qmat(qconj(m_camera[CAMERA_CURRENT].arcball.quat()))); + m_3d_point_shader->set_uniform("smash", smash); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); + m_3d_point_shader->set_uniform("point_size", radius); + m_3d_point_shader->set_uniform("color", color); + + m_3d_point_shader->begin(); + m_3d_point_shader->draw_array(Shader::PrimitiveType::TriangleFan, 0, 4, false, range.y); + m_3d_point_shader->end(); } /*! @@ -1515,9 +1526,10 @@ void SampleViewer::draw_text(const int2 &pos, const string &text, const float4 & string SampleViewer::export_XYZ_points(const string &format) { - float radius = map_slider_to_radius(m_radius); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); + // float radius = map_slider_to_radius(m_radius); + // if (m_scale_radius_with_points) + // radius *= 64.0f / std::sqrt(m_point_count); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); string out = (format == "eps") ? header_eps(m_point_color, 1.f, radius) : header_svg(m_point_color); @@ -1545,9 +1557,10 @@ string SampleViewer::export_XYZ_points(const string &format) string SampleViewer::export_points_2d(const string &format, CameraType camera_type, int3 dim) { - float radius = map_slider_to_radius(m_radius); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); + // float radius = map_slider_to_radius(m_radius); + // if (m_scale_radius_with_points) + // radius *= 64.0f / std::sqrt(m_point_count); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); string out = (format == "eps") ? header_eps(m_point_color, 1.f, radius) : header_svg(m_point_color); @@ -1574,9 +1587,10 @@ string SampleViewer::export_all_points_2d(const string &format) { float scale = 1.0f / (m_num_dimensions - 1); - float radius = map_slider_to_radius(m_radius); - if (m_scale_radius_with_points) - radius *= 64.0f / std::sqrt(m_point_count); + // float radius = map_slider_to_radius(m_radius); + // if (m_scale_radius_with_points) + // radius *= 64.0f / std::sqrt(m_point_count); + float radius = m_radius / (m_scale_radius_with_points ? std::sqrt(m_point_count) : 1.0f); string out = (format == "eps") ? header_eps(m_point_color, scale, radius) : header_svg(m_point_color, scale); diff --git a/src/export_to_file.cpp b/src/export_to_file.cpp index 107e1f5..7744902 100644 --- a/src/export_to_file.cpp +++ b/src/export_to_file.cpp @@ -149,7 +149,7 @@ string draw_points_eps(float4x4 mat, int3 dim, const Array2d &points, int for (int i = range.x; i < range.x + range.y; ++i) { - auto v4d = mul(mat, float4{points(i, dim.x), points(i, dim.y), points(i, dim.z), 1.0f}); + auto v4d = mul(mat, float4{points(dim.x, i), points(dim.y, i), points(dim.z, i), 1.0f}); auto v2d = float2{v4d.x / v4d.w, v4d.y / v4d.w} * page_size; out += fmt::format("{} {} p\n", v2d.x, v2d.y); } @@ -279,7 +279,7 @@ string draw_points_svg(float4x4 mat, int3 dim, const Array2d &points, int for (int i = range.x; i < range.x + range.y; ++i) { - auto v4d = mul(mat, float4{points(i, dim.x), points(i, dim.y), points(i, dim.z), 1.0f}); + auto v4d = mul(mat, float4{points(dim.x, i), points(dim.y, i), points(dim.z, i), 1.0f}); auto v2d = float2{v4d.x / v4d.w, v4d.y / v4d.w} * float2{page_size, -page_size}; out += fmt::format(" \n", v2d.x, v2d.y, radius * 0.3f); } @@ -301,7 +301,7 @@ string draw_points_csv(const Array2d &points, int2 range) { if (d > 0) out += ", "; - out += fmt::format("{}", points(i, d)); + out += fmt::format("{}", points(d, i)); } out += "\n"; }