Skip to content

Commit

Permalink
Merge pull request #187 from ABRG-Models/dev/constexpr_geodesics_vec
Browse files Browse the repository at this point in the history
Dev/constexpr geodesics vec. constexpr operator+, operator/ and renormalize in morph::vec seem to be safe.
  • Loading branch information
sebjameswml authored Jun 10, 2024
2 parents de5b934 + 5fed986 commit aee42d2
Show file tree
Hide file tree
Showing 13 changed files with 439 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ if(wxWidgets_FOUND)
endif()
endif()

# Make it possible to compile complex constexpr functions
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fconstexpr-ops-limit=5000000000")

# rapidxml is bundled in the source, but its headers will be installed in ${CMAKE_INSTALL_PREFIX}/morph/, and they're symlinked in ${PROJECT_SOURCE_DIR}/morph
#include_directories("${PROJECT_SOURCE_DIR}/include/rapidxml-1.13")

Expand Down
13 changes: 12 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ if(USE_GLEW)
endif()

add_executable(scatter scatter.cpp)
set_property(TARGET scatter PROPERTY CXX_STANDARD 20)
target_link_libraries(scatter OpenGL::GL glfw Freetype::Freetype)
if(USE_GLEW)
target_link_libraries(scatter GLEW::GLEW)
Expand Down Expand Up @@ -326,15 +327,25 @@ if(USE_GLEW)
target_link_libraries(icosahedron GLEW::GLEW)
endif()

# Need C++20 for the way I use constexpr here so that it is legal to
# have "a definition of a variable for which no initialization is
# performed".
add_executable(testce testce.cpp)
set_property(TARGET testce PROPERTY CXX_STANDARD 23)
set_property(TARGET testce PROPERTY CXX_STANDARD 20)

add_executable(geodesic geodesic.cpp)
target_link_libraries(geodesic OpenGL::GL glfw Freetype::Freetype)
if(USE_GLEW)
target_link_libraries(geodesic GLEW::GLEW)
endif()

add_executable(geodesic_ce geodesic_ce.cpp)
set_property(TARGET geodesic_ce PROPERTY CXX_STANDARD 20)
target_link_libraries(geodesic_ce OpenGL::GL glfw Freetype::Freetype)
if(USE_GLEW)
target_link_libraries(geodesic_ce GLEW::GLEW)
endif()

add_executable(tri tri.cpp)
target_link_libraries(tri OpenGL::GL glfw Freetype::Freetype)
if(USE_GLEW)
Expand Down
18 changes: 4 additions & 14 deletions examples/geodesic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ int main()
{
int rtn = -1;

morph::Visual v(1024, 768, "Geodesic Polyhedron");
morph::Visual v(1024, 768, "Geodesic Polyhedra (ordered vertices/faces)");
v.showCoordArrows = true;

try {
Expand All @@ -31,25 +31,15 @@ int main()
std::string lbl = std::string("iterations = ") + std::to_string(i);
gv1->addLabel (lbl, {0, -1, 0}, morph::TextFeatures(0.06f));
gv1->cm.setType (morph::ColourMapType::Jet);
//gv1->colourScale.do_autoscale = false;
//gv1->colourScale.compute_autoscale (0.0f, 2.0f);
gv1->finalize();
#if 0 // no re-colouring
v.addVisualModel (gv1);
#else // re-colour after construction
auto gv1p = v.addVisualModel (gv1);

// re-colour after construction
auto gv1p = v.addVisualModel (gv1);
float imax_mult = 1.0f / static_cast<float>(imax);
# if 0 // Funky pattern colouring
for (unsigned int j = 0; j < gv1p->data.size(); ++j) {
gv1p->data[j] = j%3 ? 0 : (1+i) * imax_mult;
}
# else // sequential colouring
// sequential colouring:
size_t sz1 = gv1p->data.size();
gv1p->data.linspace (0.0f, 1+i * imax_mult, sz1);
# endif
gv1p->updateColours();
#endif
}

v.keepOpen();
Expand Down
64 changes: 64 additions & 0 deletions examples/geodesic_ce.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Visualize an Icosahedron using the Geodesic Visual that co-ops the unordered
* constexpr geodesic function.
*/
#include <morph/Visual.h>
#include <morph/ColourMap.h>
#include <morph/GeodesicVisualCE.h>
#include <morph/vec.h>
#include <iostream>
#include <fstream>
#include <cmath>
#include <array>
#include <stdexcept>
#include <string>

int main()
{
int rtn = -1;

morph::Visual v(1024, 768, "(constexpr) Geodesic Polyhedra");
v.showCoordArrows = true;
v.lightingEffects (true);

try {
morph::vec<float, 3> offset = { 0.0, 0.0, 0.0 };
morph::vec<float, 3> step = { 2.2, 0.0, 0.0 };

auto gv1 = std::make_unique<morph::GeodesicVisualCE<float, 0>> (offset + step * 0, 0.9f);
v.bindmodel (gv1);
std::string lbl = std::string("iterations = 0");
gv1->addLabel (lbl, {0, -1, 0}, morph::TextFeatures(0.06f));
gv1->finalize();
v.addVisualModel (gv1);

auto gv2 = std::make_unique<morph::GeodesicVisualCE<float, 1>> (offset + step * 1, 0.9f);
v.bindmodel (gv2);
lbl = std::string("iterations = 1");
gv2->addLabel (lbl, {0, -1, 0}, morph::TextFeatures(0.06f));
gv2->finalize();
v.addVisualModel (gv2);

auto gv3 = std::make_unique<morph::GeodesicVisualCE<float, 2>> (offset + step * 2, 0.9f);
v.bindmodel (gv3);
lbl = std::string("iterations = 2");
gv3->addLabel (lbl, {0, -1, 0}, morph::TextFeatures(0.06f));
gv3->finalize();
v.addVisualModel (gv3);

auto gv4 = std::make_unique<morph::GeodesicVisualCE<float, 3>> (offset + step * 3, 0.9f);
v.bindmodel (gv4);
lbl = std::string("iterations = 4");
gv4->addLabel (lbl, {0, -1, 0}, morph::TextFeatures(0.06f));
gv4->finalize();
v.addVisualModel (gv4);

v.keepOpen();

} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
rtn = -1;
}

return rtn;
}
1 change: 1 addition & 0 deletions examples/scatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ int main()
while (v.readyToFinish == false) {
v.waitevents (0.018);
v.render();
v.readyToFinish = true; // debug/profile
}

} catch (const std::exception& e) {
Expand Down
6 changes: 5 additions & 1 deletion examples/testce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ int main()
{
// Note that we force the compile time evaluation of geometry_ce::icosahedron by returning a constexpr object
constexpr morph::geometry_ce::polyhedron<double, 12, 20> ico = morph::geometry_ce::icosahedron<double>();
std::cout << "constexpr vertices:\n" << ico.vertices.str() << std::endl;
std::cout << "constexpr 1st vertices:\n" << static_cast<morph::vec<double, 3>>(ico.vertices[0]) << std::endl;

constexpr
auto geo = morph::geometry_ce::make_icosahedral_geodesic<double, 3>();
std::cout << "constexpr geo.vertices: " << static_cast<morph::vec<double, 3>>(geo.poly.vertices[0]).str() << std::endl;
return 0;
}
68 changes: 68 additions & 0 deletions morph/GeodesicVisualCE.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <morph/vec.h>
#include <morph/VisualModel.h>
#include <morph/mathconst.h>
#include <morph/Scale.h>
#include <morph/ColourMap.h>
#include <array>

namespace morph {

/*!
* This class creates the vertices for an geodesic polyhedron in a 3D scene using
* the constexpr function.
*
* \tparam T the type for the data to be visualized as face (or maybe vertex) colours
*
* \tparam glver The usual OpenGL version code; match this to everything else in
* your program.
*/
template<typename T, int iterations, int glver = morph::gl::version_4_1>
class GeodesicVisualCE : public VisualModel<glver>
{
public:
GeodesicVisualCE() { this->init ({0.0, 0.0, 0.0}, 1.0f); }

//! Initialise with offset, start and end coordinates, radius and a single colour.
GeodesicVisualCE(const vec<float, 3> _offset, const float _radius)
{
this->init (_offset, _radius);
}

~GeodesicVisualCE () {}

void init (const vec<float, 3> _offset, const float _radius)
{
// Set up...
this->mv_offset = _offset;
this->viewmatrix.translate (this->mv_offset);
this->radius = _radius;
}

//! Initialize vertex buffer objects and vertex array object.
void initializeVertices()
{
this->vertexPositions.clear();
this->vertexNormals.clear();
this->vertexColors.clear();
this->indices.clear();

if (iterations > 5) {
// Note odd necessity to stick in the 'template' keyword after this->
this->template computeSphereGeoFast<double, iterations> (this->idx, morph::vec<float, 3>({0,0,0}),
this->colour, this->radius);
} else {
// computeSphereGeo F defaults to float
this->template computeSphereGeoFast<float, iterations> (this->idx, morph::vec<float, 3>({0,0,0}),
this->colour, this->radius);
}
}

//! The radius of the geodesic
float radius = 1.0f;
//! Fixed colour.
std::array<float, 3> colour = { 0.2f, 0.1f, 0.7f };
};

} // namespace morph
14 changes: 5 additions & 9 deletions morph/ScatterVisual.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ namespace morph {
if (this->sizeFactor == Flt{0}) {
if constexpr (draw_spheres_as_geodesics) {
// Slower than regular computeSphere(). 2 iterations gives 320 faces
this->computeSphereGeo (this->idx, (*this->dataCoords)[i], clr, this->radiusFixed, 2);
this->template computeSphereGeoFast<float, 3> (this->idx, (*this->dataCoords)[i], clr, this->radiusFixed);
} else {
// (16+2) * 20 gives 360 faces
this->computeSphere (this->idx, (*this->dataCoords)[i], clr, this->radiusFixed, 16, 20);
}
} else {
if constexpr (draw_spheres_as_geodesics) {
this->computeSphereGeo (this->idx, (*this->dataCoords)[i], clr, dcopy[i]*this->sizeFactor, 2);
this->template computeSphereGeoFast<float, 3> (this->idx, (*this->dataCoords)[i], clr, dcopy[i]*this->sizeFactor);
} else {
this->computeSphere (this->idx, (*this->dataCoords)[i], clr, dcopy[i]*this->sizeFactor, 16, 20);
}
Expand All @@ -134,13 +134,9 @@ namespace morph {
}
}

// I've tested geodesic sphere drawing code, but it probably isn't yet suitable
// for use where many spheres are repeatedly computed (the base geodesic is
// repeatedly computed, which is a waste of cycles). In fact, the overall
// approach here, where each sphere model is repeatedly re-computed for each
// point is probably about the least performant way you could draw a bunch of
// spheres in OpenGL! However, using geodesic code is noticably slower even than
// the regular VisualModel::computeSphere()
// The constexpr, unordered geodesic code is no slower than the regular
// VisualModel::computeSphere(), but leave this off for now (if true, C++-20 is
// required)
static constexpr bool draw_spheres_as_geodesics = false;

//! Set this->radiusFixed, then re-compute vertices.
Expand Down
34 changes: 34 additions & 0 deletions morph/VisualModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,40 @@ namespace morph {
return gi.n_faces;
}

//! Fast computeSphereGeo, which uses constexpr make_icosahedral_geodesic. The
//! resulting vertices and faces are NOT in any kind of order, but ok for
//! plotting, e.g. scatter graph spheres.
template<typename F=float, int iterations = 2>
int computeSphereGeoFast (GLuint& idx, vec<float> so, std::array<float, 3> sc,
float r = 1.0f)
{
// test if type F is float
if constexpr (std::is_same<std::decay_t<F>, float>::value == true) {
static_assert (iterations <= 5, "computeSphereGeoFast: For iterations > 5, F needs to be double precision");
} else {
static_assert (iterations <= 10, "computeSphereGeoFast: This is an abitrary iterations limit (10 gives 20971520 faces)");
}
// Note that we need double precision to compute higher iterations of the geodesic (iterations > 5)
constexpr morph::geometry_ce::icosahedral_geodesic<F, iterations> geo = morph::geometry_ce::make_icosahedral_geodesic<F, iterations>();

// Now essentially copy geo into vertex buffers
for (auto v : geo.poly.vertices) {
this->vertex_push (v.as_float() * r + so, this->vertexPositions);
this->vertex_push (v.as_float(), this->vertexNormals);
this->vertex_push (sc, this->vertexColors);
}
for (auto f : geo.poly.faces) {
this->indices.push_back (idx + f[0]);
this->indices.push_back (idx + f[1]);
this->indices.push_back (idx + f[2]);
}
// idx is the *vertex index* and should be incremented by the number of vertices in the polyhedron
int n_verts = static_cast<int>(geo.poly.vertices.size());
idx += n_verts;

return n_verts;
}

/*!
* Sphere, 1 colour version.
*
Expand Down
Loading

0 comments on commit aee42d2

Please sign in to comment.