An Example Use of the HTML Formatter

This is a rather long example which logs the steps of a simplistic sphere tesselation algorithm. Each time a new vertex is added to the tesselation a 3D scene is added to the log to visualize the step of the algorithm.

The Log

The output log for dividing each great circle in 3 parts is here.

Log Configuration and Logging Functions

#include <fstream>
#include <stack>

//
// Operation Log configuration:
//
#define OPERATION_LOG_INIT_FUNCTION_NAMESPACE  output_sphere_config
#define OPERATION_LOG_INIT_FUNCTION_NAME       operation_log_init

#include <operation_log.h>

namespace output_sphere_config
{

void operation_log_init(operation_log::DefaultOperationLog &log)
{
    static std::ofstream output_stream("operation-log.html");
    static operation_log::HtmlFormatter formatter(output_stream, "output_sphere");

    // Load THREE.js:
    formatter.extra_header_code =
        operation_log::HtmlFormatter::three_js_header_code;

    log.set_formatter(formatter);
}

}


//
// Functions for logging CGAL vertices as THREE.js scenes:
//

#include <CGAL/Polyhedron_incremental_builder_3.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Shape_detection_3/Shape_base.h>

#include "reference_frame.h"

namespace output_sphere_log
{

//
// Render a given number of vertices from a CGAL polyhedron incremental
// builder as a point cloud scene.
//
// @builder: The incremental builder to read vertices from.
// @vertex_count: The number of vertices to read.
// @extra_code: Extra Java Script code to add to the end of the scene.
//
template <class HDS>
void log_polyhedron_builder_vertices(
    CGAL::Polyhedron_incremental_builder_3<HDS> &builder, int vertex_count,
    std::string extra_code = "")
{
    std::stringstream code;

    code << R"code(
<script type="text/javascript">

(function ()
{
    var camera = new THREE.PerspectiveCamera(70, 500 / 500, 0.01, 1000);

    camera.position.z = 50;

    var scene = new THREE.Scene();

    // Show the x, y and z axes in the secene:
    var axesHelper = new THREE.AxesHelper( 20 );
    scene.add( axesHelper );

    // Create the point cloud:
    var material = new THREE.PointsMaterial({ size: 2, vertexColors: THREE.VertexColors });
    var geometry = new THREE.Geometry();
    var colors = [];

    addVertex = function(x, y, z)
    {
        geometry.vertices.push(new THREE.Vector3(x, y, z));
        colors.push(new THREE.Color(0.15, 0.15, 0.85));
    };
)code";

    for (int i = 0; i < vertex_count; ++i)
    {
        typename HDS::Vertex_handle vertex = builder.vertex(i);

        code << "    addVertex(" <<
            vertex->point().x() << ", " <<
            vertex->point().y() << ", " <<
            vertex->point().z() << ");" << std::endl;
    }

        code <<
R"code(

    geometry.colors = colors;
    geometry.computeBoundingBox();

    var pointCloud = new THREE.Points(geometry, material);

    scene.add(pointCloud);

    // Book keeping for rendering the scene:
    var renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(250, 250);
    renderer.autoClear = false;

    var sceneView = new operation_log_3js.SceneView(scene, renderer, camera);

    // Add a label with the vertex index to each vertex:
    sceneView.labelVertices(pointCloud);

    // Add mouse controls for rotating the scene camera:
    var controls = new THREE.TrackballControls( camera );

    controls.rotateSpeed = 1.0;
    controls.zoomSpeed = 1.2;
    controls.panSpeed = 0.8;
    controls.noZoom = false;
    controls.noPan = false;
    controls.staticMoving = true;
    controls.dynamicDampingFactor = 0.3;
    controls.keys = [ 65, 83, 68 ];
    controls.addEventListener('change', function()
        {
            sceneView.render();
        });
    
    sceneView.controls = controls;

    sceneView.animationFrame = function(time) {
            sceneView.render();
        };
)code" <<
        extra_code <<
R"code(
    operation_log_3js.document.addSceneView(sceneView);
})();

</script>    
)code";

    operation_log::OperationLogInstance::get().write_html(code.str());
}


//
// Render a given number of vertices from a spherical tesselation as a point
// cloud scene including the sphere being tesselated, and latitude lines for
// the vertices.
//
template <class HDS>
void log_sphere_tessalation_builder_vertices(
    double circumsphere_r, double latitude_step, double max_latitude,
    CGAL::Polyhedron_incremental_builder_3<HDS> &builder, int vertex_count,
    std::string extra_code = "")
{
    std::stringstream extra_code_buf(extra_code);

    // Add the sphere being tessalated:
    extra_code_buf << R"code(
    geometry = new THREE.SphereGeometry(
)code" << circumsphere_r << R"code(, 32, 32);
    material = new THREE.MeshBasicMaterial( { color: 0xdcb856, transparent: true, opacity: 0.5 } );
    var mesh = new THREE.Mesh(geometry, material);

    scene.add(mesh);
)code";

    // Add latitude lines:
    for (double latitude = -CGAL_M_PI_2 + latitude_step;
        latitude <= max_latitude;
        latitude += latitude_step)
    {
        extra_code_buf << R"code(

    geometry = new THREE.CircleGeometry()code" << circumsphere_r * cos(latitude) + 0.1 << R"code(, 32 );
    geometry.translate(0, 0, )code" << circumsphere_r * sin(latitude) << R"code();
    geometry.vertices.shift();
    geometry.vertices.push(geometry.vertices[0]);
    material = new THREE.LineDashedMaterial( {
            color: 0xffffff,
            linewidth: 1,
            dashSize: 4,
            gapSize: 1,
        } );
    mesh = new THREE.Line( geometry, material );
    mesh.computeLineDistances();
    scene.add( mesh );

)code";
    }

    log_polyhedron_builder_vertices(builder, vertex_count, extra_code_buf.str());
}

}

The Tesselation Builder Class

#include <operation_log.h>


namespace cpp_cad
{

// A class that uses a polyhedron incremental builer to build the faces of a
// sphere tessalation.
template <class HDS>
class Sphere_3_TessalationBuilder : public CGAL::Modifier_base<HDS>
{
private:
    Kernel::FT circumsphere_r;
    int linear_subdivisions;
    CGAL::Polyhedron_3<Kernel> polyhedron;
    int vertex_count;
    CGAL::Polyhedron_incremental_builder_3<HDS> builder;
    double latitude;
    double latitude_step;
    double longitude;
    double longitude_step;
    double prev_longitude;
    double prev_longitude_step;
    double parallel_r; // Radius of the current parallel circle.
    int half_meridian_subdivision_c;
    int parallel_subdivision_c;
    int prev_parallel_subdivision_c;
    int parallel_vertex_i;
    int prev_parallel_vertex_i;
    int parallel_last_vertex_i;
    int prev_parallel_last_vertex_i;

    // Difference between the previous parallel vertex's longitude and the
    // current parallel vertex's longitude in subdivision counts, where the
    // subdivision denominator is
    // prev_parallel_subdivision_c * parallel_subdivision_c.
    int longitude_difference_subdiv;

public:
    inline Sphere_3_TessalationBuilder(CGAL::Polyhedron_3<Kernel> &polyhedron, HDS& hds, Kernel::FT circumsphere_r = 1, int linear_subdivisions = 2)
    : circumsphere_r(circumsphere_r),
        linear_subdivisions(linear_subdivisions),
        polyhedron(polyhedron),
        vertex_count(0),
        CGAL::Modifier_base<HDS>(),
        builder(hds, true)
    {}

    // Required when deriving from CGAL::Modifier_base<HDS> to make this class
    // not abstract:
    void operator()(HDS& hds)
    {}

    void run()
    {
        // First parallel (pole):
        int vertex_count = 1;
        // We over-count the faces by 2, since we count previous parallel
        // vertex count + current parallel vertex count number of faces for
        // each parallel.  However, the poles have one vertex, but need 0
        // triangles to complete the path on the pole parallel (i.e., the
        // 0-radius circle from the pole to itself).
        int face_count = -2;
        int prev_vertex_c = 1;

        // Subdivide a half meridian in, at least, 3 parts:
        half_meridian_subdivision_c =
            std::max(3, (linear_subdivisions + 1) / 2);

        latitude_step = M_PI / half_meridian_subdivision_c;
        double latitude = -CGAL_M_PI_2 + latitude_step;

        for (int parallel_c = 2;
            parallel_c <= half_meridian_subdivision_c;
            ++parallel_c, latitude += latitude_step)
        {
            // Subdivide each parallel into, at least, 3 parts:
            parallel_subdivision_c = std::max(
                3,
                static_cast<int>(ceil(cos(latitude) * linear_subdivisions)));
            int vertex_c = parallel_subdivision_c;

            vertex_count += vertex_c;
            face_count += prev_vertex_c + vertex_c;

            prev_vertex_c = vertex_c;
        }

        // Last parallel (pole):
        face_count += prev_vertex_c;
        ++vertex_count;

        // Each face has 3 halfedges.
        int halfedge_count = 3 * face_count;

        builder.begin_surface(vertex_count, face_count, halfedge_count);
        add_tessalation();
        builder.end_surface();
    }

private:
    void add_tessalation()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        longitude_step = 2 * M_PI / parallel_subdivision_c;

        latitude = -CGAL_M_PI_2;
        parallel_r = 0.0;
        prev_parallel_vertex_i = 0;
        add_first_parallel();

        latitude += latitude_step;
        parallel_r = circumsphere_r * cos(latitude);
        prev_parallel_subdivision_c = parallel_subdivision_c;
        prev_parallel_last_vertex_i = parallel_last_vertex_i;
        add_second_parallel();

        // Add the remaining parallel circles, except the last pole:
        latitude += latitude_step;
        for (int parallel_c = 3;
            parallel_c <= half_meridian_subdivision_c;
            ++parallel_c, latitude += latitude_step)
        {
            parallel_r = circumsphere_r * cos(latitude);
            prev_longitude = 0;
            prev_parallel_subdivision_c = parallel_subdivision_c;
            prev_longitude_step = longitude_step;
            prev_parallel_last_vertex_i = parallel_last_vertex_i;

            add_parallel();
        }

        parallel_r = 0.0;
        prev_longitude = 0;
        prev_parallel_subdivision_c = parallel_subdivision_c;
        prev_longitude_step = longitude_step;
        prev_parallel_last_vertex_i = parallel_last_vertex_i;
        add_last_parallel();

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Adds the first parallel (radius = 0):
    inline void add_first_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        add_vertex(-CGAL_M_PI_2, 0);
        parallel_subdivision_c = 1;
        parallel_last_vertex_i = 0;
        parallel_vertex_i = 1;

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Adds the second parallel:
    inline void add_second_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        // Subdivide each parallel into, at least, 3 parts:
        parallel_subdivision_c = std::max(
            3, static_cast<int>(ceil(cos(latitude) * linear_subdivisions)));
        longitude_step = 2*M_PI / parallel_subdivision_c;
        parallel_last_vertex_i = parallel_vertex_i + parallel_subdivision_c - 1;

        // Add the first vertex for the current parallel:
        add_vertex(latitude, 0);
        ++parallel_vertex_i;

        for (longitude = longitude_step;
            parallel_vertex_i <= parallel_last_vertex_i;
            ++parallel_vertex_i, longitude += longitude_step)
        {
            add_vertex(latitude, longitude);

            add_face(
                prev_parallel_vertex_i,
                parallel_vertex_i - 1,
                parallel_vertex_i);
        }

        // Add the last face:
        add_face(
            prev_parallel_vertex_i,
            parallel_vertex_i - 1,
            parallel_vertex_i - parallel_subdivision_c);

        ++prev_parallel_vertex_i;

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    inline void add_last_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        parallel_subdivision_c = 1;
        parallel_last_vertex_i = parallel_vertex_i + parallel_subdivision_c - 1;

        // Add the pole:
        add_vertex(CGAL_M_PI_2, 0);

        // Add the faces:
        for (++prev_parallel_vertex_i;
            prev_parallel_vertex_i <= prev_parallel_last_vertex_i;
            ++prev_parallel_vertex_i)
        {
            add_face(
                prev_parallel_vertex_i - 1,
                parallel_vertex_i,
                prev_parallel_vertex_i);
        }

        // Add the last face:
        add_face(
            prev_parallel_vertex_i - 1,
            parallel_vertex_i,
            prev_parallel_vertex_i - prev_parallel_subdivision_c);

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    inline void add_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        // Subdivide each parallel into, at least, 3 parts:
        parallel_subdivision_c = std::max(
            3, static_cast<int>(ceil(cos(latitude) * linear_subdivisions)));
        longitude_step = 2*M_PI / parallel_subdivision_c;
        parallel_last_vertex_i = parallel_vertex_i + parallel_subdivision_c - 1;

        OPERATION_LOG_DUMP_VARS(latitude, parallel_subdivision_c,
            longitude_step, prev_parallel_vertex_i, parallel_vertex_i,
            prev_parallel_last_vertex_i, parallel_last_vertex_i);

        // Add the first vertex for the current longitude circle:
        add_vertex(latitude, 0);
        longitude = longitude_step;

        longitude_difference_subdiv = 0;

        while (true)
        {
            // Advance to the next vertex on the previous parallel
            // (longitude_difference_subdiv += parallel_subdivision_c), or
            // advance to the next vertex on the current parallel
            // (longitude_difference_subdiv -= prev_parallel_subdivision_c):
            if (abs(longitude_difference_subdiv + parallel_subdivision_c) <
            abs(longitude_difference_subdiv - prev_parallel_subdivision_c))
            {
                advance_prev_parallel_vertex();
                if (prev_parallel_vertex_i > prev_parallel_last_vertex_i)
                {
                    complete_parallel();
                    break;
                }
            }
            else
            {
                advance_parallel_vertex();
                if (parallel_vertex_i > parallel_last_vertex_i)
                {
                    complete_prev_parallel();
                    break;
                }
            }
        }

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Add a face that connects the current and next vertices on the
    // previous parallel to the current vertex on the current parallel.
    inline void advance_prev_parallel_vertex()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        int res_next_vertex_i = prev_parallel_vertex_i + 1;
        int next_vertex_i = res_next_vertex_i;

        if (res_next_vertex_i > prev_parallel_last_vertex_i)
        {
            next_vertex_i -= prev_parallel_subdivision_c;
        }

        assert(next_vertex_i != prev_parallel_vertex_i);
        add_face(prev_parallel_vertex_i, parallel_vertex_i, next_vertex_i);

        prev_parallel_vertex_i = res_next_vertex_i;
        longitude_difference_subdiv += parallel_subdivision_c;

        OPERATION_LOG_DUMP_VARS(prev_parallel_vertex_i, longitude_difference_subdiv);
        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Add a face that connects the current and next vertices on the
    // current parallel to the current vertex on the previous parallel.
    inline void advance_parallel_vertex()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        int res_next_vertex_i = parallel_vertex_i + 1;
        int next_vertex_i = res_next_vertex_i;

        if (res_next_vertex_i > parallel_last_vertex_i)
        {
            next_vertex_i -= parallel_subdivision_c;
        }
        else
        {
            OPERATION_LOG_DUMP_VARS(latitude, longitude, longitude_step);

            add_vertex(latitude, longitude);
            longitude += longitude_step;
        }

        assert(next_vertex_i != parallel_vertex_i);
        add_face(prev_parallel_vertex_i, parallel_vertex_i, next_vertex_i);

        parallel_vertex_i = res_next_vertex_i;
        longitude_difference_subdiv -= prev_parallel_subdivision_c;

        OPERATION_LOG_DUMP_VARS(parallel_vertex_i, longitude_difference_subdiv);
        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Connect all remaining vertices on the previous parallel to the current
    // vertex on the current parallel by forming traiangles.
    inline void complete_prev_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        assert(prev_parallel_subdivision_c > 2);

        int prev_vertex_i = prev_parallel_vertex_i;
        int parallel_vertex_i = this->parallel_vertex_i;

        if (parallel_vertex_i > parallel_last_vertex_i)
        {
            parallel_vertex_i -= parallel_subdivision_c;
        }

        OPERATION_LOG_DUMP_VARS(prev_parallel_vertex_i);

        while (prev_parallel_vertex_i <= prev_parallel_last_vertex_i)
        {
            ++prev_parallel_vertex_i;
            int next_vertex_i = prev_parallel_vertex_i;

            if (next_vertex_i > prev_parallel_last_vertex_i)
            {
                next_vertex_i -= prev_parallel_subdivision_c;
            }

            OPERATION_LOG_DUMP_VARS(prev_vertex_i, parallel_vertex_i, prev_parallel_vertex_i);

            add_face(prev_vertex_i, parallel_vertex_i, next_vertex_i);

            prev_vertex_i = prev_parallel_vertex_i;
        }

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Connect all remaining vertices on the current parallel to the current
    // vertex on the previous parallel by forming traiangles.
    inline void complete_parallel()
    {
        OPERATION_LOG_ENTER_NO_ARG_FUNCTION();

        assert(parallel_subdivision_c > 2);

        int prev_vertex_i = parallel_vertex_i;
        int prev_parallel_vertex_i = this->prev_parallel_vertex_i;

        if (prev_parallel_vertex_i > prev_parallel_last_vertex_i)
        {
            prev_parallel_vertex_i -= prev_parallel_subdivision_c;
        }

        OPERATION_LOG_DUMP_VARS(parallel_vertex_i);

        while (parallel_vertex_i <= parallel_last_vertex_i)
        {
            ++parallel_vertex_i;
            int next_vertex_i = parallel_vertex_i;

            OPERATION_LOG_DUMP_VARS(prev_vertex_i, parallel_vertex_i, prev_parallel_vertex_i);

            if (next_vertex_i > parallel_last_vertex_i)
            {
                next_vertex_i -= parallel_subdivision_c;
            }
            else
            {
                OPERATION_LOG_MESSAGE("Adding next vertex.");

                add_vertex(latitude, longitude);
                longitude += longitude_step;
            }

            add_face(prev_vertex_i, next_vertex_i, prev_parallel_vertex_i);

            prev_vertex_i = parallel_vertex_i;
        }

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    // Adds a triangusar face to the polyhedron.
    //     The vertices must have already been added.
    inline void add_face(int v0_index, int v1_index, int v2_index)
    {
        OPERATION_LOG_ENTER_FUNCTION(v0_index, v1_index, v2_index);

        builder.begin_facet();
        builder.add_vertex_to_facet(v0_index);
        builder.add_vertex_to_facet(v1_index);
        builder.add_vertex_to_facet(v2_index);
        builder.end_facet();

        OPERATION_LOG_LEAVE_FUNCTION();
    }

    inline void add_vertex(double latitude, double longitude)
    {
        OPERATION_LOG_ENTER_FUNCTION(latitude / M_PI, longitude / M_PI);

        assert(abs(parallel_r - circumsphere_r * cos(latitude)) < 1e-15);

        Kernel::Point_3 point(
                parallel_r * cos(longitude),
                parallel_r * sin(longitude),
                circumsphere_r * sin(latitude)
            );

        OPERATION_LOG_MESSAGE_STREAM(<<
            "Vertex " << vertex_count << ": " << point);

        builder.add_vertex(point);
        vertex_count++;

        cpp_cad_log::log_sphere_tessalation_builder_vertices(
            circumsphere_r, latitude_step, latitude, builder, vertex_count);

        OPERATION_LOG_LEAVE_FUNCTION();
    }
};

}