mirror of
https://github.com/TheAlgorithms/C
synced 2024-11-24 06:19:37 +03:00
[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 8d570b4c28
* 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>
This commit is contained in:
parent
cdf8453db8
commit
9a9781064f
@ -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 \
|
||||
freeglut3 \
|
||||
freeglut3-dev \
|
||||
&& pip install cpplint \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
@ -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})
|
||||
|
@ -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)
|
||||
|
||||
|
88
graphics/CMakeLists.txt
Normal file
88
graphics/CMakeLists.txt
Normal file
@ -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} )
|
288
graphics/spirograph.c
Normal file
288
graphics/spirograph.c
Normal file
@ -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:
|
||||
*
|
||||
* <a
|
||||
* href="https://commons.wikimedia.org/wiki/File:Resonance_Cascade.svg"><img
|
||||
* src="https://upload.wikimedia.org/wikipedia/commons/3/39/Resonance_Cascade.svg"
|
||||
* alt="Spirograph geometry from Wikipedia" style="width: 250px"/></a>
|
||||
*/
|
||||
#define _USE_MATH_DEFINES /**< required for MSVC compiler */
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
/** 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<k<1\f$
|
||||
* * \f$R\f$ is the radius of outer circle
|
||||
* * \f$t\f$ is the angle of rotation of the point i.e., represents the time
|
||||
* parameter
|
||||
*
|
||||
* Since we are considering ratios, the actual values of \f$r\f$ and
|
||||
* \f$R\f$ are immaterial.
|
||||
*
|
||||
* @param [out] x output array containing absicca of points (must be
|
||||
* pre-allocated)
|
||||
* @param [out] y output array containing ordinates of points (must be
|
||||
* pre-allocated)
|
||||
* @param l the relative distance of marker from the centre of
|
||||
* inner circle and \f$0\le l\le1\f$
|
||||
* @param k the ratio of radius of inner circle to outer circle and
|
||||
* \f$0<k<1\f$
|
||||
* @param N number of sample points along the trajectory (higher = better
|
||||
* resolution but consumes more time and memory)
|
||||
* @param num_rot the number of rotations to perform (can be fractional value)
|
||||
*/
|
||||
void spirograph(double *x, double *y, double l, double k, size_t N, double rot)
|
||||
{
|
||||
double dt = rot * 2.f * M_PI / N;
|
||||
double t = 0.f, R = 1.f;
|
||||
const double k1 = 1.f - k;
|
||||
|
||||
for (size_t dk = 0; dk < N; dk++, t += dt)
|
||||
{
|
||||
x[dk] = R * (k1 * cos(t) + l * k * cos(k1 * t / k));
|
||||
y[dk] = R * (k1 * sin(t) - l * k * sin(k1 * t / k));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test function to save resulting points to a CSV file.
|
||||
*
|
||||
*/
|
||||
void test(void)
|
||||
{
|
||||
size_t N = 500;
|
||||
double l = 0.3, k = 0.75, rot = 10.;
|
||||
char fname[50];
|
||||
snprintf(fname, 50, "spirograph_%.2f_%.2f_%.2f.csv", l, k, rot);
|
||||
FILE *fp = fopen(fname, "wt");
|
||||
if (!fp)
|
||||
{
|
||||
perror(fname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
double *x = (double *)malloc(N * sizeof(double));
|
||||
double *y = (double *)malloc(N * sizeof(double));
|
||||
|
||||
spirograph(x, y, l, k, N, rot);
|
||||
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
fprintf(fp, "%.5g, %.5g", x[i], y[i]);
|
||||
if (i < N - 1)
|
||||
{
|
||||
fputc('\n', fp);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
free(x);
|
||||
free(y);
|
||||
}
|
||||
|
||||
#ifdef USE_GLUT // this is set by CMAKE automatically, if available
|
||||
#ifdef __APPLE__
|
||||
#include <GLUT/glut.h> // include path on Macs is different
|
||||
#else
|
||||
#include <GL/glut.h>
|
||||
#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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user