From 9a9781064fe7c9de3e7d5c50d104ed007424b6c7 Mon Sep 17 00:00:00 2001 From: Krishna Vedala <7001608+kvedala@users.noreply.github.com> Date: Fri, 10 Jul 2020 15:48:07 -0400 Subject: [PATCH] [enhancement] New Graphics implementation with algorithm for spirograph (#557) * skeleton of spirograph * add graphics to cmake * updating DIRECTORY.md * added cmake to graphics folder * add stub test function * working program * set pre-processor macro if GLUT is available * use snprintf details: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm * conditional include for mac * corrected conditional include for mac * fix cmake for MACOS * OpenGL animation if available, else plot to CSV * MacOS does not provide glutBitmapString function * formatting source-code for 8d570b4c28c95dd1371bde7c81258034df602c90 * fix parameter * try caps include path GL * provide custom glutBitmapString cuntion * add glut library to gitpod docker * enable VNC in gitpod * better documentation and cmake configuration * enable keyboard inputs to pause and change parameters * fix lgtm alerts * implementation similar to one in C++ repo * fix compilation errors on MSVC Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .gitpod.dockerfile | 16 ++- CMakeLists.txt | 62 +++++---- DIRECTORY.md | 3 + graphics/CMakeLists.txt | 88 ++++++++++++ graphics/spirograph.c | 288 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 423 insertions(+), 34 deletions(-) create mode 100644 graphics/CMakeLists.txt create mode 100644 graphics/spirograph.c diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 6d6a4e89..f3891405 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,9 +1,11 @@ -FROM gitpod/workspace-full +FROM gitpod/workspace-full-vnc RUN sudo apt-get update \ - && sudo apt-get install -y \ - doxygen \ - graphviz \ - ninja-build \ - && pip install cpplint \ - && sudo rm -rf /var/lib/apt/lists/* + && sudo apt-get install -y \ + doxygen \ + graphviz \ + ninja-build \ + freeglut3 \ + freeglut3-dev \ + && pip install cpplint \ + && sudo rm -rf /var/lib/apt/lists/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 967dd044..03ed954b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,38 @@ -cmake_minimum_required(VERSION 3.3) +cmake_minimum_required(VERSION 3.6) project(Algorithms_in_C LANGUAGES C VERSION 1.0.0 DESCRIPTION "Set of algorithms implemented in C." ) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED YES) + +if(MSVC) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + # add_compile_options(/Za) +endif(MSVC) + +find_library(MATH_LIBRARY m) + option(USE_OPENMP "flag to use OpenMP for multithreading" ON) +if(USE_OPENMP) + find_package(OpenMP) + if (OpenMP_C_FOUND) + message(STATUS "Building with OpenMP Multithreading.") + else() + message(STATUS "No OpenMP found, no multithreading.") + endif() +endif() + +add_subdirectory(misc) +add_subdirectory(sorting) +add_subdirectory(graphics) +add_subdirectory(searching) +add_subdirectory(conversions) +add_subdirectory(project_euler) +add_subdirectory(machine_learning) +add_subdirectory(numerical_methods) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0057 NEW) @@ -21,6 +48,7 @@ if(DOXYGEN_FOUND) set(DOXYGEN_GENERATE_TREEVIEW YES) set(DOXYGEN_JAVADOC_AUTOBRIEF YES) set(DOXYGEN_STRIP_CODE_COMMENTS NO) + set(DOXYGEN_ENABLE_PREPROCESSING YES) set(DOXYGEN_EXT_LINKS_IN_WINDOW YES) set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) set(DOXYGEN_CLANG_ASSISTED_PARSING YES) @@ -34,6 +62,12 @@ if(DOXYGEN_FOUND) set(DOXYGEN_INTERACTIVE_SVG YES) set(DOXYGEN_DOT_IMAGE_FORMAT "svg") endif() + if(OPENMP_FOUND) + set(DOXYGEN_PREDEFINED "_OPENMP=1") + endif() + if(GLUT_FOUND) + set(DOXYGEN_PREDEFINED ${DOXYGEN_PREDEFINED} "GLUT_FOUND=1") + endif() doxygen_add_docs( doc @@ -42,32 +76,6 @@ if(DOXYGEN_FOUND) ) endif() -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED YES) - -if(MSVC) - add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - # add_compile_options(/Za) -endif(MSVC) - -find_library(MATH_LIBRARY m) - -if(USE_OPENMP) - find_package(OpenMP) - if (OpenMP_C_FOUND) - message(STATUS "Building with OpenMP Multithreading.") - else() - message(STATUS "No OpenMP found, no multithreading.") - endif() -endif() - -add_subdirectory(misc) -add_subdirectory(sorting) -add_subdirectory(searching) -add_subdirectory(conversions) -add_subdirectory(project_euler) -add_subdirectory(machine_learning) -add_subdirectory(numerical_methods) set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) diff --git a/DIRECTORY.md b/DIRECTORY.md index d5d9be97..139d6343 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -107,6 +107,9 @@ * [Word Count](https://github.com/TheAlgorithms/C/blob/master/exercism/word_count/word_count.c) * [Word Count](https://github.com/TheAlgorithms/C/blob/master/exercism/word_count/word_count.h) +## Graphics + * [Spirograph](https://github.com/TheAlgorithms/C/blob/master/graphics/spirograph.c) + ## Greedy Approach * [Djikstra](https://github.com/TheAlgorithms/C/blob/master/greedy_approach/djikstra.c) diff --git a/graphics/CMakeLists.txt b/graphics/CMakeLists.txt new file mode 100644 index 00000000..046160b6 --- /dev/null +++ b/graphics/CMakeLists.txt @@ -0,0 +1,88 @@ +find_package(OpenGL) +if(OpenGL_FOUND) + find_package(GLUT) + if(NOT GLUT_FOUND) + message("FreeGLUT library will be downloaded and built.") + include(ExternalProject) + ExternalProject_Add ( + FREEGLUT-PRJ + URL https://sourceforge.net/projects/freeglut/files/freeglut/3.2.1/freeglut-3.2.1.tar.gz + URL_MD5 cd5c670c1086358598a6d4a9d166949d + CMAKE_GENERATOR ${CMAKE_GENERATOR} --config Release + CMAKE_GENERATOR_TOOLSET ${CMAKE_GENERATOR_TOOLSET} + CMAKE_GENERATOR_PLATFORM ${CMAKE_GENERATOR_PLATFORM} + CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release + -DFREEGLUT_BUILD_SHARED_LIBS=OFF + -DFREEGLUT_BUILD_STATIC_LIBS=ON + -DFREEGLUT_BUILD_DEMOS=OFF + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/freeglut + # BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/freeglut-build + # BUILD_IN_SOURCE ON + # UPDATE_COMMAND "" + INSTALL_COMMAND "" + # CONFIGURE_COMMAND "" + # BUILD_COMMAND "" + ) + ExternalProject_Get_Property(FREEGLUT-PRJ SOURCE_DIR) + ExternalProject_Get_Property(FREEGLUT-PRJ BINARY_DIR) + set(FREEGLUT_BIN_DIR ${BINARY_DIR}) + set(FREEGLUT_SRC_DIR ${SOURCE_DIR}) + # add_library(libfreeglut STATIC IMPORTED) + # set_target_properties(libfreeglut PROPERTIES IMPORTED_LOCATION ${FREEGLUT_BIN_DIR}) + + # set(FREEGLUT_BUILD_DEMOS OFF CACHE BOOL "") + # set(FREEGLUT_BUILD_SHARED_LIBS OFF CACHE BOOL "") + # set(FREEGLUT_BUILD_STATIC_LIBS ON CACHE BOOL "") + # add_subdirectory(${FREEGLUT_SRC_DIR} ${FREEGLUT_BIN_DIR} EXCLUDE_FROM_ALL) + # add_subdirectory(${BINARY_DIR}) + # find_package(FreeGLUT) + endif(NOT GLUT_FOUND) +else(OpenGL_FOUND) + message(WARNING "OPENGL not found. Will not build graphical outputs.") +endif(OpenGL_FOUND) + +# If necessary, use the RELATIVE flag, otherwise each source file may be listed +# with full pathname. RELATIVE may makes it easier to extract an executable name +# automatically. +file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c ) +# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c ) +# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES) +foreach( testsourcefile ${APP_SOURCES} ) + # I used a simple string replace, to cut off .cpp. + string( REPLACE ".c" "" testname ${testsourcefile} ) + add_executable( ${testname} ${testsourcefile} ) + + # set_target_properties(${testname} PROPERTIES LINKER_LANGUAGE C) + + if(OpenMP_C_FOUND) + target_link_libraries(${testname} PRIVATE OpenMP::OpenMP_C) + endif() + + if(MATH_LIBRARY) + target_link_libraries(${testname} PRIVATE ${MATH_LIBRARY}) + endif() + + if(OpenGL_FOUND) + if(NOT GLUT_FOUND) + add_dependencies(${testname} FREEGLUT-PRJ) + target_compile_definitions(${testname} PRIVATE FREEGLUT_STATIC) + target_include_directories(${testname} PRIVATE ${FREEGLUT_SRC_DIR}/include) + target_link_directories(${testname} PRIVATE ${FREEGLUT_BIN_DIR}/lib) + target_link_libraries(${testname} PRIVATE OpenGL::GL) + target_link_libraries(${testname} INTERFACE FREEGLUT-PRJ) + # target_include_directories(${testname} PRIVATE ${FREEGLUT_INCLUDE_DIRS}) + # target_link_libraries(${testname} INTERFACE freeglut_static) + else() + target_include_directories(${testname} PRIVATE ${GLUT_INCLUDE_DIRS}) + target_link_libraries(${testname} PRIVATE OpenGL::GL ${GLUT_LIBRARIES}) + endif() + target_compile_definitions(${testname} PRIVATE USE_GLUT) + endif(OpenGL_FOUND) + + if(APPLE) + target_compile_options(${testname} PRIVATE -Wno-deprecated) + endif(APPLE) + + install(TARGETS ${testname} DESTINATION "bin/graphics") + +endforeach( testsourcefile ${APP_SOURCES} ) diff --git a/graphics/spirograph.c b/graphics/spirograph.c new file mode 100644 index 00000000..2a4aebc1 --- /dev/null +++ b/graphics/spirograph.c @@ -0,0 +1,288 @@ +/** + * @file + * @author [Krishna Vedala](https://github.com/kvedala) + * @brief Implementation of + * [Spirograph](https://en.wikipedia.org/wiki/Spirograph) + * + * @details + * Implementation of the program is based on the geometry shown in the figure + * below: + * + * Spirograph geometry from Wikipedia + */ +#define _USE_MATH_DEFINES /**< required for MSVC compiler */ +#include +#include +#include +#include +#include + +/** Generate spirograph curve into arrays `x` and `y` such that the i^th point + * in 2D is represented by `(x[i],y[i])`. The generating function is given by: + * \f{eqnarray*}{ + * x &=& R\left[ (1-k) \cos (t) + l\cdot k\cdot\cos \left(\frac{1-k}{k}t\right) + * \right]\\ + * y &=& R\left[ (1-k) \sin (t) - l\cdot k\cdot\sin \left(\frac{1-k}{k}t\right) + * \right] \f} + * where + * * \f$R\f$ is the scaling parameter that we will consider \f$=1\f$ + * * \f$l=\frac{\rho}{r}\f$ is the relative distance of marker from the centre + * of inner circle and \f$0\le l\le1\f$ + * * \f$\rho\f$ is physical distance of marker from centre of inner circle + * * \f$r\f$ is the radius of inner circle + * * \f$k=\frac{r}{R}\f$ is the ratio of radius of inner circle to outer circle + * and \f$0 // include path on Macs is different +#else +#include +#endif + +static bool paused = 0; /**< flag to set pause/unpause animation */ +static const int animation_speed = 25; /**< animation delate in ms */ + +static const double step = 0.01; /**< animation step size */ +static double l_ratio = 0.1; /**< the l-ratio defined in docs */ +static double k_ratio = 0.1; /**< the k-ratio defined in docs */ +static const double num_rot = 20.; /**< number of rotations to simulate */ + +/** A wrapper that is not available in all GLUT implementations. + */ +static inline void glutBitmapString(void *font, char *string) +{ + for (char *ch = string; *ch != '\0'; ch++) glutBitmapCharacter(font, *ch); +} + +/** + * @brief Function to graph (x,y) points on the OpenGL graphics window. + * + * @param x array containing absicca of points (must be pre-allocated) + * @param y array containing ordinates of points (must be pre-allocated) + * @param N number of points in the the arrays + */ +void display_graph(const double *x, const double *y, size_t N, double l, + double k) +{ + glClearColor(1.0f, 1.0f, 1.0f, + 0.0f); // Set background color to white and opaque + glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer (background) + + if (x && y) + { + glBegin(GL_LINES); // draw line segments + glColor3f(0.f, 0.f, 1.f); // blue + glPointSize(2.f); // point size in pixels + + for (size_t i = 1; i < N; i++) + { + glVertex2f(x[i - 1], y[i - 1]); // line from + glVertex2f(x[i], y[i]); // line to + } + glEnd(); + } + glColor3f(0.f, 0.f, 0.f); + char buffer[20]; + snprintf(buffer, 20, "l = %.3f", l); + glRasterPos2f(-.85, .85); + glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer); + snprintf(buffer, 20, "k = %.3f", k); + glRasterPos2f(-.85, .75); + glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer); + + glutSwapBuffers(); +} + +/** + * @brief Test function with animation + * + */ +void test2(void) +{ + const size_t N = 1000; // number of samples + + static bool direction1 = true; // increment if true, otherwise decrement + static bool direction2 = true; // increment if true, otherwise decrement + + double *x = (double *)malloc(N * sizeof(double)); + double *y = (double *)malloc(N * sizeof(double)); + + spirograph(x, y, l_ratio, k_ratio, N, num_rot); + display_graph(x, y, N, l_ratio, k_ratio); + + free(x); // free dynamic memories + free(y); + + if (paused) + // if paused, do not update l_ratio and k_ratio + return; + + if (direction1) // increment k_ratio + { + if (k_ratio >= (1.f - step)) // maximum limit + direction1 = false; // reverse direction of k_ratio + else + k_ratio += step; + } + else // decrement k_ratio + { + if (k_ratio <= step) // minimum limit + { + direction1 = true; // reverse direction of k_ratio + + if (direction2) // increment l_ratio + { + if (l_ratio >= (1.f - step)) // max limit of l_ratio + direction2 = false; // reverse direction of l_ratio + else + l_ratio += step; + } + else // decrement l_ratio + { + if (l_ratio <= step) // minimum limit of l_ratio + direction2 = true; // reverse direction of l_ratio + else + l_ratio -= step; + } + } + else // no min limit of k_ratio + k_ratio -= step; + } +} + +/** + * @brief GLUT timer callback function to add animation delay. + */ +void timer_cb(int id) +{ + glutPostRedisplay(); + glutTimerFunc(animation_speed, timer_cb, 0); +} + +/** + * @brief Keypress event call back function. + * + * @param key ID of the key pressed + * @param x mouse pointer position at event + * @param y mouse pointer position at event + */ +void keyboard_cb(unsigned char key, int x, int y) +{ + switch (key) + { + case ' ': // spacebar toggles pause + paused = !paused; // toggle + break; + case '+': // up arrow key + k_ratio += step; + display_graph(NULL, NULL, 1, l_ratio, k_ratio); + break; + case '_': // down arrow key + k_ratio -= step; + display_graph(NULL, NULL, 1, l_ratio, k_ratio); + break; + case '=': // left arrow key + l_ratio += step; + display_graph(NULL, NULL, 1, l_ratio, k_ratio); + break; + case '-': // right arrow key + l_ratio -= step; + display_graph(NULL, NULL, 1, l_ratio, k_ratio); + break; + case 0x1B: // escape key exits + exit(EXIT_SUCCESS); + } +} +#endif + +/** Main function */ +int main(int argc, char **argv) +{ + test(); + +#ifdef USE_GLUT + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); + glutCreateWindow("Spirograph"); + glutInitWindowSize(400, 400); + // glutIdleFunc(glutPostRedisplay); + glutTimerFunc(animation_speed, timer_cb, 0); + glutKeyboardFunc(keyboard_cb); + glutDisplayFunc(test2); + glutMainLoop(); +#endif + + return 0; +}