From 8290c32cda39466862cf4f30ba7471b80e5fb507 Mon Sep 17 00:00:00 2001 From: Adrian Scripca Date: Sat, 23 Apr 2022 16:41:33 +0300 Subject: [PATCH] Added glm, fbo, shader, texture and texture generator --- .gitmodules | 3 + CMakeLists.txt | 1 + lib/glm | 1 + main.cpp | 27 ++--- src/CMakeLists.txt | 27 +++-- src/colour.h | 14 +++ src/defines.h | 3 + src/fbo.cpp | 73 ++++++++++++ src/fbo.h | 36 ++++++ src/peripherals_glfw.h | 1 - src/shader.cpp | 114 +++++++++++++++++++ src/shader.h | 34 ++++++ src/texture.cpp | 62 ++++++++++ src/texture.h | 27 +++++ src/texture_generator.cpp | 232 ++++++++++++++++++++++++++++++++++++++ src/texture_generator.h | 43 +++++++ todo.txt | 3 + 17 files changed, 676 insertions(+), 25 deletions(-) create mode 100644 .gitmodules create mode 160000 lib/glm create mode 100644 src/colour.h create mode 100644 src/fbo.cpp create mode 100644 src/fbo.h create mode 100644 src/shader.cpp create mode 100644 src/shader.h create mode 100644 src/texture.cpp create mode 100644 src/texture.h create mode 100644 src/texture_generator.cpp create mode 100644 src/texture_generator.h create mode 100644 todo.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cd40596 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/glm"] + path = lib/glm + url = git@github.com:g-truc/glm.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e6b04d3..b7e62e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ find_package(OpenGL REQUIRED) include_directories("src/") include_directories("gl/include") +add_subdirectory(lib/glm EXCLUDE_FROM_ALL) add_subdirectory(src) add_executable(demo main.cpp gl/src/glad.c ${SOURCE}) diff --git a/lib/glm b/lib/glm new file mode 160000 index 0000000..cc98465 --- /dev/null +++ b/lib/glm @@ -0,0 +1 @@ +Subproject commit cc98465e3508535ba8c7f6208df934c156a018dc diff --git a/main.cpp b/main.cpp index 9ec56ab..48a91b0 100644 --- a/main.cpp +++ b/main.cpp @@ -1,23 +1,20 @@ #include "peripherals_glfw.h" - int main() { - acidrain::peripherals_glfw peripherals; - peripherals.init(); + acidrain::peripherals_glfw peripherals; + peripherals.init(); - glDisable(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); - double alpha = 0.0; - // while (!glfwWindowShouldClose(window)) { - while (!peripherals.should_close()) { - alpha += 0.01; - if (alpha > 1.0) alpha = 0.0; + double alpha = 0.0; + while (!peripherals.should_close()) { + alpha += 0.01; + if (alpha > 1.0) alpha = 0.0; - glClearColor(0.0 * alpha, 0.1 * alpha, 1.0 * alpha, 1 * alpha); - glClear(GL_COLOR_BUFFER_BIT); - - peripherals.swap_buffers(); - peripherals.poll_events(); - } + glClearColor(0.0 * alpha, 0.1 * alpha, 1.0 * alpha, 1 * alpha); + glClear(GL_COLOR_BUFFER_BIT); + peripherals.swap_buffers(); + peripherals.poll_events(); + } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2de8ef4..3be11bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,11 +1,20 @@ set(HEADERS - ${HEADERS} - ${CMAKE_CURRENT_SOURCE_DIR}/peripherals.h - ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.h - PARENT_SCOPE -) + ${HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/colour.h + ${CMAKE_CURRENT_SOURCE_DIR}/fbo.h + ${CMAKE_CURRENT_SOURCE_DIR}/peripherals.h + ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.h + ${CMAKE_CURRENT_SOURCE_DIR}/shader.h + ${CMAKE_CURRENT_SOURCE_DIR}/texture.h + ${CMAKE_CURRENT_SOURCE_DIR}/texture_generator.h + PARENT_SCOPE + ) set(SOURCE - ${SOURCE} - ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.cpp - PARENT_SCOPE -) + ${SOURCE} + ${CMAKE_CURRENT_SOURCE_DIR}/fbo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/texture.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/texture_generator.cpp + PARENT_SCOPE + ) diff --git a/src/colour.h b/src/colour.h new file mode 100644 index 0000000..b68923a --- /dev/null +++ b/src/colour.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "glm/vec4.hpp" + +typedef glm::vec4 colour; + +inline uint32_t as_int(const glm::vec4 &col) { + unsigned char rr = static_cast(col.r * 255); + unsigned char gg = static_cast(col.g * 255); + unsigned char bb = static_cast(col.b * 255); + unsigned char aa = static_cast(col.a * 255); + return rr + (gg << 8) + (bb << 16) + (aa << 24); +} diff --git a/src/defines.h b/src/defines.h index 037d124..12ec08d 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,5 +1,8 @@ #pragma once +#include #define BEGIN_NAMESPACE namespace acidrain { #define END_NAMESPACE }; +static int SCREEN_WIDTH = 800; +static int SCREEN_HEIGHT = 600; diff --git a/src/fbo.cpp b/src/fbo.cpp new file mode 100644 index 0000000..06216f0 --- /dev/null +++ b/src/fbo.cpp @@ -0,0 +1,73 @@ +#include "fbo.h" + +#include + +BEGIN_NAMESPACE + +fbo::fbo(int w, int h) + : width(w), + height(h) { + + glGenFramebuffers(1, &frame_buffer_id); + glGenTextures(1, &color_buffer_id); + glGenRenderbuffers(1, &depth_buffer_id); + + glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id); + + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, color_buffer_id); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, + 4, // number of samples for multisampling + GL_RGBA8, + width, height, + false); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, color_buffer_id, + 0); + + glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer_id); +} + +int fbo::get_width() const { + return width; +} + +int fbo::get_height() const { + return height; +} + +GLuint fbo::get_color_buffer_id() const { + return color_buffer_id; +} + +GLuint fbo::get_depth_buffer_id() const { + return depth_buffer_id; +} + +void fbo::use() { + old_width = SCREEN_WIDTH; + old_height = SCREEN_HEIGHT; + + SCREEN_WIDTH = width; + SCREEN_HEIGHT = height; + + glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id); + glViewport(0, 0, width, height); +} + +void fbo::unuse() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, old_width, old_height); + + SCREEN_WIDTH = old_width; + SCREEN_HEIGHT = old_height; +} + +std::shared_ptr fbo::get_texture() { + return std::make_shared(color_buffer_id, width, height); +} + +END_NAMESPACE \ No newline at end of file diff --git a/src/fbo.h b/src/fbo.h new file mode 100644 index 0000000..261b3d8 --- /dev/null +++ b/src/fbo.h @@ -0,0 +1,36 @@ +#pragma once + +#include "texture.h" +#include + +BEGIN_NAMESPACE + +class fbo { +public: + explicit fbo(int width, int height); + ~fbo() = default; + + void use(); + void unuse(); + + int get_width() const; + int get_height() const; + + GLuint get_color_buffer_id() const; + GLuint get_depth_buffer_id() const; + + std::shared_ptr get_texture(); + +private: + int width; + int height; + + int old_width; + int old_height; + + GLuint frame_buffer_id; + GLuint color_buffer_id; + GLuint depth_buffer_id; +}; + +END_NAMESPACE \ No newline at end of file diff --git a/src/peripherals_glfw.h b/src/peripherals_glfw.h index 9e74de1..e934f52 100644 --- a/src/peripherals_glfw.h +++ b/src/peripherals_glfw.h @@ -1,6 +1,5 @@ #pragma once #include "peripherals.h" -#include // forward declares class GLFWwindow; diff --git a/src/shader.cpp b/src/shader.cpp new file mode 100644 index 0000000..4dfaad8 --- /dev/null +++ b/src/shader.cpp @@ -0,0 +1,114 @@ +#include "shader.h" +#include +#include +#include + +BEGIN_NAMESPACE + +void check_compile_error(GLuint shaderId) { + GLint isCompiled = 0; + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) { + GLint maxLength = 0; + glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength); + + //The maxLength includes the NULL character + char errorLog[maxLength]; + glGetShaderInfoLog(shaderId, maxLength, &maxLength, &errorLog[0]); + + std::cout << "Shader error: " << errorLog << std::endl; + + //Provide the infolog in whatever manor you deem best. + //Exit with failure. + glDeleteShader(shaderId); //Don't leak the shader. + } +} + + +shader::shader(const char *vs_content, const char *ps_content) { + shader_ids[0] = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(shader_ids[0], 1, &vs_content, nullptr); + glCompileShader(shader_ids[0]); + check_compile_error(shader_ids[0]); + + shader_ids[1] = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(shader_ids[1], 1, &ps_content, nullptr); + glCompileShader(shader_ids[1]); + check_compile_error(shader_ids[1]); + + program_id = glCreateProgram(); + glAttachShader(program_id, shader_ids[0]); + glAttachShader(program_id, shader_ids[1]); + + // TODO Adrian: these should come from an external enumeration + glBindAttribLocation(program_id, 0, "position"); + glBindAttribLocation(program_id, 1, "normal"); + glBindAttribLocation(program_id, 2, "texCoords"); + + glLinkProgram(program_id); + glUseProgram(program_id); +} + +shader::~shader() { + glDetachShader(program_id, shader_ids[0]); + glDetachShader(program_id, shader_ids[1]); + + glDeleteShader(shader_ids[0]); + glDeleteShader(shader_ids[1]); + + glDeleteProgram(program_id); +} + +void shader::use() const { + glUseProgram(program_id); +} + +void shader::unuse() const { + glUseProgram(0); +} + +int shader::getUniform(const char *uniformName) { + if (uniformCache.count(uniformName) == 0) { + uniformCache[uniformName] = glGetUniformLocation(program_id, uniformName); + } + return uniformCache[uniformName]; +} + +void shader::setMatrix3Uniform(float *matrix, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniformMatrix3fv(location, 1, false, matrix); +} + +void shader::setMatrix4Uniform(float *matrix, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniformMatrix4fv(location, 1, false, matrix); +} + +void shader::setVec3Uniform(float *value, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniform3fv(location, 1, value); +} + +void shader::setVec4Uniform(float *value, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniform4fv(location, 1, value); +} + +void shader::setIntUniform(int value, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniform1i(location, value); +} + +void shader::setFloatUniform(float value, const char *uniformName) { + GLint location = getUniform(uniformName); + if (location != -1) + glUniform1f(location, value); +} + + +END_NAMESPACE diff --git a/src/shader.h b/src/shader.h new file mode 100644 index 0000000..539489f --- /dev/null +++ b/src/shader.h @@ -0,0 +1,34 @@ +#pragma once + +#include "defines.h" +#include + +BEGIN_NAMESPACE + +class shader { +public: + + shader(const char *vs_content, const char *ps_content); + virtual ~shader(); + + void use() const; + void unuse() const; + + GLuint getId() const { return program_id; } + int getUniform(const char *uniformName); + + void setMatrix3Uniform(float *value, const char *uniformName); + void setMatrix4Uniform(float *value, const char *uniformName); + void setVec3Uniform(float *value, const char *uniformName); + void setVec4Uniform(float *value, const char *uniformName); + void setFloatUniform(float value, const char *uniformName); + void setIntUniform(int value, const char *uniformName); + +private: + GLuint program_id; + GLuint shader_ids[2]; + std::map uniformCache; +}; + + +END_NAMESPACE diff --git a/src/texture.cpp b/src/texture.cpp new file mode 100644 index 0000000..ea27547 --- /dev/null +++ b/src/texture.cpp @@ -0,0 +1,62 @@ +#include "texture.h" + +BEGIN_NAMESPACE + +texture::texture(int w, int h, uint8_t *buffer) + : width(w), + height(h), + destroyable(true) { + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + // select modulate to mix texture with color for shading +// glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + +// // only set this when we do have mipmaps +// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + // for versions up to 3 +// glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + width, height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, + buffer); +} + + +texture::texture(GLuint id, int w, int h) + : id(id), + width(w), + height(h), + destroyable(false) { +} + + +texture::~texture() { + if (destroyable) + glDeleteTextures(1, &id); +} + +int texture::get_width() const { + return width; +} + +int texture::get_height() const { + return height; +} + +void texture::use() const { + glBindTexture(GL_TEXTURE_2D, id); +} + +void texture::unuse() const { + glBindTexture(GL_TEXTURE_2D, 0); +} + +END_NAMESPACE diff --git a/src/texture.h b/src/texture.h new file mode 100644 index 0000000..fa53cdf --- /dev/null +++ b/src/texture.h @@ -0,0 +1,27 @@ +#pragma once + +#include "peripherals.h" + +BEGIN_NAMESPACE + +class texture { +public: + texture(int width, int height, uint8_t *buffer); + texture(GLuint id, int width, int height); + virtual ~texture(); + + int get_width() const; + int get_height() const; + GLuint get_id() const { return id; } + + void use() const; + void unuse() const; + +private: + int width; + int height; + bool destroyable; + GLuint id = 0; +}; + +END_NAMESPACE \ No newline at end of file diff --git a/src/texture_generator.cpp b/src/texture_generator.cpp new file mode 100644 index 0000000..5ce970f --- /dev/null +++ b/src/texture_generator.cpp @@ -0,0 +1,232 @@ +#include "texture_generator.h" +#include +#include + +BEGIN_NAMESPACE + +texture_generator::texture_generator(uint16_t w, uint16_t h) + : width(w), height(h) { + + for (auto &layer: layers) + layer = new uint8_t[width * height * 4]; +} + +texture_generator::~texture_generator() { + for (auto &layer: layers) + delete[] layer; +} + +texture_generator &texture_generator::checker_board(uint8_t layer, + uint8_t block_size, + colour on, + colour off) { + + auto *input = reinterpret_cast(layers[layer]); + + uint32_t on_color = as_int(on); + uint32_t off_color = as_int(off); + + for (auto y = 0; y < height; y++) { + for (auto x = 0; x < width; x++) { + bool in_block = ( + ((x % (2 * block_size)) < block_size) && + ((y % (2 * block_size)) < block_size) + ); + + *input++ = in_block ? on_color : off_color; + // *input++ = 0xffffffff; + } + } + + return *this; +} + + +texture_generator &texture_generator::env_map(uint8_t layer, uint8_t size) { + auto *input = reinterpret_cast(layers[layer]); + + colour col{1}; + + float size_squared = size * size; + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + int32_t rx = x - width / 2; + int32_t ry = y - height / 2; + float r_square = static_cast(rx * rx + ry * ry); + + if (r_square <= size_squared) { + float a = 1.0f - (r_square / size_squared); + col.r = col.g = col.b = a; + // col.a = 1; + } else { + col.r = col.g = col.b = 0; + // col.a = 1; + } + + *input++ = as_int(col); + } + } + + return *this; +} + + +texture_generator &texture_generator::lens(uint8_t layer, uint8_t size) { + auto *input = reinterpret_cast(layers[layer]); + + colour col{0}; + + float size_squared = size * size; + float c; + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + int32_t rx = x - width / 2; + int32_t ry = y - height / 2; + auto r_squared = static_cast(rx * rx + ry * ry); + + if (r_squared <= size_squared) { + // double r = sqrt(static_castrsquare) / static_cast(size); + float r = 0; + c = 1.0f - r; + c = c * c; + if (r > 1.0f) + c = 0.0f; + col = glm::vec4(c); + } else { + col = glm::vec4(0); + } + + *input++ = as_int(col); + } + } + + return *this; +} + + +texture_generator &texture_generator::roll(uint8_t layer, uint8_t x, uint8_t y) { + uint8_t *input = layers[layer]; + auto *output = new uint8_t[width * height * 4]; + uint8_t *out_ptr = output; + + for (uint16_t j = 0; j < height; j++) { + for (uint16_t i = 0; i < width; i++) { + uint32_t index = ((j + y) % height) * width + ((i + x) % width); + *out_ptr++ = input[index * 4 + 0]; + *out_ptr++ = input[index * 4 + 1]; + *out_ptr++ = input[index * 4 + 2]; + *out_ptr++ = input[index * 4 + 3]; + } + } + + memcpy(input, output, width * height * 4); + delete[] output; + + return *this; +} + + +uint8_t *texture_generator::get(uint8_t layer) { + return layers[layer]; +} + +std::shared_ptr texture_generator::get_texture(uint8_t layer) { + return std::make_shared(width, height, get(layer)); +} + +texture_generator &texture_generator::white_noise(uint8_t layer) { + auto *input = reinterpret_cast(layers[layer]); + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + *input++ = rand(); + } + } + + return *this; +} + +texture_generator &texture_generator::polar_grid(uint8_t layer) { + uint8_t *input = layers[layer]; + + auto *output = new uint8_t[width * height * 4]; + uint8_t *out_ptr = output; + + for (int j = 0; j < height; j++) { + double theta = M_PI * (j - (height - 1) / 2.0) / static_cast(height - 1); + for (int i = 0; i < width; i++) { + double phi = 2 * M_PI * (i - width / 2.0) / static_cast(width); + double phi2 = phi * cos(theta); + int i2 = phi2 * width / (2 * M_PI) + width / 2; + if (i2 < 0 || i2 > width - 1) { + *out_ptr++ = 255; + *out_ptr++ = 0; + *out_ptr++ = 0; + *out_ptr++ = 1; + + } else { + *out_ptr++ = input[(j * width + i2) * 4 + 0]; + *out_ptr++ = input[(j * width + i2) * 4 + 1]; + *out_ptr++ = input[(j * width + i2) * 4 + 2]; + *out_ptr++ = input[(j * width + i2) * 4 + 3]; + } + } + } + + memcpy(input, output, width * height * 4); + delete[] output; + + return *this; +} + +texture_generator &texture_generator::brick(uint8_t layer, + uint8_t brick_width, + uint8_t brick_height, + uint8_t gap_size, + colour mortar_col, + colour brick_col) { + + auto *input = reinterpret_cast(layers[layer]); + + uint32_t mortar = as_int(mortar_col); + uint32_t brick = as_int(brick_col); + + for (int i = 0; i < width * height; i++) { + input[i] = brick; + } + + int y = 0; + while (y < height) { + for (int i = 0; i < gap_size; i++) { + if ((y + i) < height) { + for (int x = 0; x < width; x++) { + input[x + (y + i) * width] = mortar; + } + } + } + y += brick_height + gap_size; + } + + int offset = 0; + bool is_odd_row = false; + y = gap_size; + while (y < height) { + int x = is_odd_row ? 0 : brick_width / 2; + while (x < width) { + for (int i = 0; i < gap_size; i++) { + for (int current_brick_y = 0; current_brick_y < brick_height; current_brick_y++) { + input[x + i + (current_brick_y + y) * width] = mortar; + } + } + + x += gap_size + brick_width; + } + + y += brick_height + gap_size; + is_odd_row = !is_odd_row; + } + + return *this; +} + + +END_NAMESPACE diff --git a/src/texture_generator.h b/src/texture_generator.h new file mode 100644 index 0000000..e78f64f --- /dev/null +++ b/src/texture_generator.h @@ -0,0 +1,43 @@ +#pragma once + +#include "texture.h" +#include "colour.h" +#include + +BEGIN_NAMESPACE + +class texture_generator { +public: + texture_generator(uint16_t width, uint16_t height); + virtual ~texture_generator(); + + texture_generator &checker_board(uint8_t layer, + uint8_t block_size, + colour on, + colour off); + texture_generator &brick(uint8_t layer, + uint8_t brick_width, + uint8_t brick_height, + uint8_t gap_size, + colour mortar_col, + colour brick_col); + + texture_generator &white_noise(uint8_t layer); + texture_generator &env_map(uint8_t layer, uint8_t size); + texture_generator &lens(uint8_t layer, uint8_t size); + + texture_generator &roll(uint8_t layer, uint8_t x, uint8_t y); + texture_generator &polar_grid(uint8_t layer); + + uint8_t *get(uint8_t layer); + std::shared_ptr get_texture(uint8_t layer); + + uint16_t width; + uint16_t height; + +private: + static const int NUM_LAYERS = 4; + uint8_t *layers[NUM_LAYERS] = {nullptr}; +}; + +END_NAMESPACE diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..cd4d1ad --- /dev/null +++ b/todo.txt @@ -0,0 +1,3 @@ +TODO: +- add random generator (maybe mersene twister) +- \ No newline at end of file