├── .gitignore ├── CMakeLists.txt ├── Makefile ├── README.md ├── constants.h ├── derivatives.frag ├── euler.frag ├── main.cc ├── rk4.frag ├── shaders.cc ├── shaders.h ├── ship.cc ├── ship.frag ├── ship.h ├── ship.vert └── texture.vert /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | set(CMAKE_CXX_COMPILER "clang++") 3 | project(pixelsim) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -std=c++11") 6 | 7 | set(SRCS main.cc ship.cc shaders.cc) 8 | add_executable(${CMAKE_PROJECT_NAME} ${SRCS}) 9 | 10 | find_package(PkgConfig REQUIRED) 11 | pkg_search_module(GLFW REQUIRED glfw3) 12 | 13 | find_library(OPENGL_LIBRARY OpenGL) 14 | 15 | find_package(PNG REQUIRED) 16 | 17 | include_directories(${GLFW_INCLUDE_DIRS} ${PNG_INCLUDE_DIR}) 18 | target_link_libraries(${CMAKE_PROJECT_NAME} 19 | ${GLFW_LIBRARIES} 20 | ${OPENGL_LIBRARY} 21 | ${PNG_LIBRARY}) 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p build 3 | cd build && cmake .. && make 4 | cp build/pixelsim . 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | This project implements a parallel mass-spring-damper system to make squishy pixel-art spaceships. 3 | All of the calculations are done by GLSL shaders on the GPU. 4 | 5 | For more information, look at this [project page](http://mattkeeter.com/projects/pixelsim). 6 | 7 | ![Yellow ship](http://mattkeeter.com/projects/pixelsim/yellow.gif) 8 | 9 | ![Purple ship](http://mattkeeter.com/projects/pixelsim/purple.gif) 10 | 11 | ![Viper ship](http://mattkeeter.com/projects/pixelsim/viper.gif) 12 | 13 | ## Copyright 14 | (c) Matthew Keeter, 2013. 15 | 16 | ## License 17 | This work may be reproduced, modified, distributed, performed, and displayed for any purpose. 18 | Copyright is retained and must be preserved. 19 | The work is provided as is; no warranty is provided, and users accept all liability. 20 | -------------------------------------------------------------------------------- /constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | // Thrust engine 5 | #define SHIP_ENGINE_THRUST_R 255 6 | #define SHIP_ENGINE_THRUST_G 0 7 | #define SHIP_ENGINE_THRUST_B 0 8 | #define SHIP_ENGINE_THRUST 2 9 | 10 | // Left engine 11 | #define SHIP_ENGINE_RIGHT_R 255 12 | #define SHIP_ENGINE_RIGHT_G 0 13 | #define SHIP_ENGINE_RIGHT_B 1 14 | #define SHIP_ENGINE_RIGHT 3 15 | 16 | // Right engine 17 | #define SHIP_ENGINE_LEFT_R 255 18 | #define SHIP_ENGINE_LEFT_G 0 19 | #define SHIP_ENGINE_LEFT_B 2 20 | #define SHIP_ENGINE_LEFT 4 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /derivatives.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | //////////////////////////////////////////////////////////////////////////////// 4 | 5 | uniform sampler2D filled; 6 | uniform sampler2D state; 7 | 8 | uniform ivec2 ship_size; 9 | 10 | uniform float k; // linear spring constant 11 | uniform float c; // linear damping 12 | 13 | uniform float m; // point's mass 14 | uniform float I; // point's inertia 15 | 16 | uniform int thrustEnginesOn; 17 | uniform int leftEnginesOn; 18 | uniform int rightEnginesOn; 19 | 20 | out vec4 fragColor; 21 | 22 | //////////////////////////////////////////////////////////////////////////////// 23 | 24 | vec2 accel(vec2 a, vec2 a_dot, vec2 d, 25 | vec2 b, vec2 b_dot) 26 | { 27 | vec2 v = b.xy - a.xy; 28 | vec2 v_ = normalize(v.xy); 29 | 30 | // Force from linear spring 31 | vec2 F_kL = -k * (length(d.xy) - length(v.xy)) * v_.xy; 32 | 33 | // Force from linear damper (check this!) 34 | vec2 F_cL = v_.xy * c * dot(b_dot.xy - a_dot.xy, v_.xy); 35 | 36 | return (F_kL + F_cL) / m; 37 | } 38 | 39 | //////////////////////////////////////////////////////////////////////////////// 40 | 41 | void main() 42 | { 43 | vec2 tex_coord = vec2(gl_FragCoord.x / float(ship_size.x + 1), 44 | gl_FragCoord.y / float(ship_size.y + 1)); 45 | 46 | if (texture(filled, tex_coord).r == 0) 47 | { 48 | fragColor = vec4(0.0f, 0.0f, 0.0f, 0.0f); 49 | return; 50 | } 51 | 52 | vec4 near_state = texture(state, tex_coord); 53 | vec2 near_pos = near_state.rg; 54 | vec2 near_vel = near_state.ba; 55 | 56 | vec2 total_accel = vec2(0.0f); 57 | vec2 total_angle = vec2(0.0f); 58 | 59 | // Iterate over the nine neighboring cells, accumulating forces. 60 | for (int dx=-1; dx <= 1; ++dx) { 61 | for (int dy=-1; dy <= 1; ++dy) { 62 | // Find the texture coordinate of the far pixel's data 63 | vec2 far_tex_coord = tex_coord + 64 | vec2(float(dx) / float(ship_size.x + 2), 65 | float(dy) / float(ship_size.y + 2)); 66 | // If the chosen pixel is within the image (i.e. it has texture 67 | // coordinates between 0 and 1) and is marked as filled in the 68 | // pixel occupancy texture, then find and add its acceleration. 69 | if (far_tex_coord.x > 0.0f && far_tex_coord.x < 1.0f && 70 | far_tex_coord.y > 0.0f && far_tex_coord.y < 1.0f && 71 | (dx != 0 || dy != 0) && 72 | texture(filled, far_tex_coord).r != 0) 73 | { 74 | // Get the actual state of the far point from the textures 75 | vec4 far_state = texture(state, far_tex_coord); 76 | vec2 far_pos = far_state.rg; 77 | vec2 far_vel = far_state.ba; 78 | 79 | // Find the nominal offset and angle between the points. 80 | vec2 delta = vec2(float(dx), float(dy)); 81 | 82 | // Accumulate angle between desired and actual positions. 83 | float angle = atan(far_pos.y - near_pos.y, 84 | far_pos.x - near_pos.x) - atan(dy, dx); 85 | total_angle += vec2(cos(angle), sin(angle)); 86 | 87 | // Find the acceleration caused by this node-neighbor linkage 88 | total_accel += accel(near_pos, near_vel, delta, 89 | far_pos, far_vel); 90 | } 91 | } 92 | } 93 | 94 | // Accelerate engine pixels upwards 95 | float type = texture(filled, tex_coord).r; 96 | if ((type == SHIP_ENGINE_THRUST/255.0f && thrustEnginesOn != 0) || 97 | (type == SHIP_ENGINE_RIGHT/255.0f && rightEnginesOn != 0) || 98 | (type == SHIP_ENGINE_LEFT/255.0f && leftEnginesOn != 0)) 99 | { 100 | float angle = atan(total_angle.y, total_angle.x); 101 | total_accel += vec2(-sin(angle), cos(angle))*1000.0f; 102 | } 103 | 104 | // Output the final derivatives: 105 | fragColor = vec4(near_vel, total_accel); 106 | } 107 | -------------------------------------------------------------------------------- /euler.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D state; 4 | uniform sampler2D accel; 5 | 6 | uniform float dt; 7 | uniform ivec2 size; 8 | 9 | out vec4 fragColor; 10 | 11 | void main() 12 | { 13 | vec2 tex_coord = vec2(gl_FragCoord.x / float(size.x + 1), 14 | gl_FragCoord.y / float(size.y + 1)); 15 | 16 | fragColor = texture(state, tex_coord) + texture(accel, tex_coord) * dt; 17 | } 18 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "ship.h" 12 | #include "shaders.h" 13 | 14 | //////////////////////////////////////////////////////////////////////////////// 15 | 16 | struct WindowSize 17 | { 18 | WindowSize(int w, int h) : width(w), height(h) {} 19 | int width, height; 20 | }; 21 | 22 | struct State 23 | { 24 | State(WindowSize* ws, Ship* s) : 25 | window_size(ws), ship(s) {} 26 | 27 | WindowSize* window_size; 28 | Ship* ship; 29 | }; 30 | 31 | //////////////////////////////////////////////////////////////////////////////// 32 | 33 | void resize_callback(GLFWwindow* window, int width, int height) 34 | { 35 | WindowSize* ws = static_cast( 36 | glfwGetWindowUserPointer(window))->window_size; 37 | ws->width = width; 38 | ws->height = height; 39 | } 40 | 41 | void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) 42 | { 43 | Ship* ship = static_cast( 44 | glfwGetWindowUserPointer(window))->ship; 45 | 46 | if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) 47 | glfwSetWindowShouldClose(window, GL_TRUE); 48 | if (key == GLFW_KEY_UP) 49 | { 50 | if (action == GLFW_PRESS) ship->thrustEnginesOn = true; 51 | else if (action == GLFW_RELEASE) ship->thrustEnginesOn = false; 52 | } 53 | else if (key == GLFW_KEY_LEFT) 54 | { 55 | if (action == GLFW_PRESS) ship->leftEnginesOn = true; 56 | else if (action == GLFW_RELEASE) ship->leftEnginesOn = false; 57 | } 58 | else if (key == GLFW_KEY_RIGHT) 59 | { 60 | if (action == GLFW_PRESS) ship->rightEnginesOn = true; 61 | else if (action == GLFW_RELEASE) ship->rightEnginesOn = false; 62 | } 63 | } 64 | 65 | //////////////////////////////////////////////////////////////////////////////// 66 | 67 | void SaveImage(const std::string& filename, 68 | const size_t width, const size_t height) 69 | { 70 | png_structp png_ptr = png_create_write_struct( 71 | PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 72 | png_infop info_ptr = png_create_info_struct(png_ptr); 73 | 74 | FILE* output = fopen(filename.c_str(), "wb"); 75 | 76 | png_set_IHDR(png_ptr, info_ptr, width, height, 77 | 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, 78 | PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); 79 | 80 | GLubyte* pixels = new GLubyte[width*height*3]; 81 | glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels); 82 | 83 | GLubyte** rows = new GLubyte*[height]; 84 | for (size_t i=0; i < height; ++i) 85 | { 86 | rows[i] = &pixels[(height - i - 1)*width*3]; 87 | } 88 | 89 | png_init_io(png_ptr, output); 90 | png_set_rows(png_ptr, info_ptr, rows); 91 | png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); 92 | 93 | fclose(output); 94 | png_destroy_write_struct(&png_ptr, &info_ptr); 95 | delete [] rows; 96 | delete [] pixels; 97 | } 98 | 99 | 100 | //////////////////////////////////////////////////////////////////////////////// 101 | 102 | void PrintUsage() 103 | { 104 | std::cout << "Usage: pixelsim [...] filename.png\n\n" 105 | << "Arguments:\n" 106 | << " --size WxH Render window size (default: 640x480)\n" 107 | << " --scale f Ship render scale (default: 0.9)\n" 108 | << " --record Save frames as frames/FRAMENUMBER.png\n" 109 | << " --track Center ship's centroid in the window\n"; 110 | } 111 | 112 | //////////////////////////////////////////////////////////////////////////////// 113 | 114 | void GetArgs(int argc, char** argv, 115 | std::string* filename, WindowSize* window_size, 116 | bool* record, bool* track, float* scale) 117 | { 118 | if (argc < 2) 119 | { 120 | PrintUsage(); 121 | exit(-1); 122 | } 123 | 124 | // The last argument is the target filename. 125 | *filename = argv[--argc]; 126 | // Verify that it at least ends in ".png" 127 | if (filename->rfind(".png") != filename->length() - 4) 128 | { 129 | std::cerr << "[pixelsim] Error: Invalid image name '" 130 | << *filename << "'" << std::endl; 131 | exit(-1); 132 | } 133 | 134 | // Attempt to open the file to verify that it exists. 135 | FILE* input = fopen(filename->c_str(), "rb"); 136 | if (input == NULL) 137 | { 138 | std::cerr << "[pixelsim] Error: Cannot open file '" 139 | << *filename << "'" << std::endl; 140 | exit(-1); 141 | } 142 | fclose(input); 143 | 144 | 145 | // Parse any other arguments that may have been provided. 146 | for (int a=1; a < argc; ++a) 147 | { 148 | if (!strcmp(argv[a], "--size")) 149 | { 150 | if (++a >= argc) 151 | { 152 | std::cerr << "[pixelsim] Error: No size provided!" 153 | << std::endl; 154 | exit(-1); 155 | } 156 | 157 | const std::string size = argv[a]; 158 | const size_t x = size.find("x"); 159 | if (x == std::string::npos) 160 | { 161 | std::cerr << "[pixelsim] Error: Invalid size specification '" 162 | << size << "'" << std::endl; 163 | exit(-1); 164 | } 165 | 166 | const std::string w = size.substr(0, x); 167 | const std::string h = size.substr(x + 1, size.size() - x - 1); 168 | 169 | window_size->width = std::atoi(w.c_str()); 170 | window_size->height = std::atoi(h.c_str()); 171 | 172 | if (window_size->height == 0 || window_size->width == 0) 173 | { 174 | std::cerr << "[pixelsim] Error: Invalid size specification '" 175 | << size << "'" << std::endl; 176 | exit(-1); 177 | } 178 | } 179 | else if (!strcmp(argv[a], "--scale")) 180 | { 181 | if (++a >= argc) 182 | { 183 | std::cerr << "[pixelsim] Error: No scale provided!" 184 | << std::endl; 185 | exit(-1); 186 | } 187 | *scale = std::atof(argv[a]); 188 | if (*scale == 0) 189 | { 190 | std::cerr << "[pixelsim] Error: Invalid scale specification '" 191 | << argv[a] << "'" << std::endl; 192 | exit(-1); 193 | } 194 | } 195 | else if (!strcmp(argv[a], "--record")) 196 | { 197 | *record = true; 198 | } 199 | else if (!strcmp(argv[a], "--track")) 200 | { 201 | *track = true; 202 | } 203 | else 204 | { 205 | std::cerr << "[pixelsim] Error: Unrecognized argument '" 206 | << argv[a] << "'" << std::endl; 207 | exit(-1); 208 | } 209 | } 210 | } 211 | 212 | //////////////////////////////////////////////////////////////////////////////// 213 | 214 | int main(int argc, char** argv) 215 | { 216 | WindowSize window_size(640, 480); 217 | 218 | bool record = false; 219 | bool track = false; 220 | float scale = 0.9; 221 | std::string filename; 222 | GetArgs(argc, argv, &filename, &window_size, &record, &track, &scale); 223 | 224 | // Initialize the library 225 | if (!glfwInit()) return -1; 226 | 227 | glfwWindowHint(GLFW_SAMPLES, 8); // multisampling! 228 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 229 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 230 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 231 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 232 | 233 | // Create a windowed mode window and its OpenGL context 234 | GLFWwindow* const window = glfwCreateWindow( 235 | window_size.width, window_size.height, "pixelsim", NULL, NULL); 236 | 237 | if (!window) 238 | { 239 | std::cerr << "[pixelsim] Error: failed to create window!" << std::endl; 240 | glfwTerminate(); 241 | return -1; 242 | } 243 | 244 | // Make the window's context current 245 | glfwMakeContextCurrent(window); 246 | 247 | // Initialize the ship! 248 | Ship ship(filename); 249 | Shaders::init(); 250 | 251 | // Store pointers to window and ship objects. They will be 252 | // modified by resize and key-press callbacks, respectively. 253 | State state(&window_size, &ship); 254 | 255 | // Use a callback to update glViewport when the window is resized, by 256 | // saving a pointer to a WindowSize struct in the window's user pointer 257 | // field. 258 | glfwSetWindowUserPointer(window, static_cast(&state)); 259 | glfwSetFramebufferSizeCallback(window, resize_callback); 260 | glfwSetKeyCallback(window, key_callback); 261 | 262 | // Get the actual framebuffer size (so that it works properly on 263 | // retina displays, rather than only filling 1/4 of the window) 264 | glfwGetFramebufferSize(window, &window_size.width, &window_size.height); 265 | 266 | // Loop until the user closes the window 267 | size_t frame=0; 268 | while (!glfwWindowShouldClose(window)) 269 | { 270 | // Store the start time of this update loop 271 | const auto t0 = std::chrono::high_resolution_clock::now(); 272 | 273 | // Update the ship 274 | ship.Update(1.0e0/60, 50); 275 | 276 | // Draw the scene 277 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 278 | glClearColor(0.933f, 0.933f, 0.933f, 1.0f); 279 | 280 | glClear(GL_COLOR_BUFFER_BIT); 281 | ship.Draw(window_size.width, window_size.height, track, scale); 282 | 283 | // Swap front and back buffers 284 | glfwSwapBuffers(window); 285 | 286 | // Poll for and process events 287 | glfwPollEvents(); 288 | 289 | // Sleep to maintain a framerate of 60 FPS 290 | std::this_thread::sleep_for( 291 | std::chrono::microseconds(std::micro::den / 61) - 292 | (std::chrono::high_resolution_clock::now() - t0)); 293 | 294 | // Print the FPS (for debugging) 295 | std::cout << std::chrono::seconds(1) / 296 | (std::chrono::high_resolution_clock::now() - t0) 297 | << std::endl; 298 | 299 | if (record && frame) 300 | { 301 | std::stringstream ss; 302 | ss << std::setw(3) << std::setfill('0') << frame; 303 | SaveImage("frames/" + ss.str() + ".png", 304 | window_size.width, window_size.height); 305 | } 306 | frame++; 307 | } 308 | 309 | glfwTerminate(); 310 | return 0; 311 | } 312 | -------------------------------------------------------------------------------- /rk4.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D y; 4 | 5 | uniform sampler2D k1; 6 | uniform sampler2D k2; 7 | uniform sampler2D k3; 8 | uniform sampler2D k4; 9 | 10 | uniform float dt; 11 | uniform ivec2 size; 12 | 13 | out vec4 fragColor; 14 | 15 | void main() 16 | { 17 | vec2 tex_coord = vec2(gl_FragCoord.x / float(size.x + 1), 18 | gl_FragCoord.y / float(size.y + 1)); 19 | 20 | fragColor = texture(y, tex_coord) + dt/6.0f * 21 | (texture(k1, tex_coord) + 2*texture(k2, tex_coord) + 22 | 2*texture(k3, tex_coord) + texture(k4, tex_coord)); 23 | } 24 | -------------------------------------------------------------------------------- /shaders.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "shaders.h" 7 | 8 | std::string Shaders::constants; 9 | 10 | GLuint Shaders::ship = 0; 11 | GLuint Shaders::derivatives = 0; 12 | GLuint Shaders::euler = 0; 13 | GLuint Shaders::RK4sum = 0; 14 | 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | void Shaders::init() 18 | { 19 | // Load "constants.h" as a string. We'll paste this string 20 | // into all of the shaders (to #define a bunch of macros). 21 | std::ifstream c("constants.h"); 22 | std::string line; 23 | std::string program; 24 | while (getline(c, line)) constants += line + '\n'; 25 | 26 | ship = CreateProgram(CompileShader("ship.vert"), 27 | CompileShader("ship.frag")); 28 | derivatives = CreateProgram(CompileShader("texture.vert"), 29 | CompileShader("derivatives.frag")); 30 | euler = CreateProgram(CompileShader("texture.vert"), 31 | CompileShader("euler.frag")); 32 | RK4sum = CreateProgram(CompileShader("texture.vert"), 33 | CompileShader("rk4.frag")); 34 | } 35 | 36 | GLuint Shaders::CompileShader(const std::string& filename) 37 | { 38 | const std::string extension = filename.substr(filename.find_last_of(".")); 39 | assert(extension == ".vert" || extension == ".frag"); 40 | 41 | GLenum type = extension == ".vert" ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER; 42 | GLuint shader = glCreateShader(type); 43 | 44 | std::ifstream t(filename.c_str()); 45 | std::string line; 46 | std::string program; 47 | 48 | // Get the first line (the #version directive) 49 | getline(t, program); 50 | // Then add all of the constants defined in constants.h 51 | program += '\n' + constants; 52 | 53 | // Finally read the rest of the shader in 54 | while (getline(t, line)) program += line + '\n'; 55 | 56 | const char* txt = program.c_str(); 57 | glShaderSource(shader, 1, &txt, NULL); 58 | glCompileShader(shader); 59 | 60 | GLint status; 61 | glGetShaderiv(shader, GL_COMPILE_STATUS, &status); 62 | if (status == GL_FALSE) 63 | { 64 | GLint infoLogLength; 65 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); 66 | 67 | GLchar* strInfoLog = new GLchar[infoLogLength + 1]; 68 | glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog); 69 | 70 | std::cerr << "Compile failure in " 71 | << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") 72 | << " shader (" << filename << "):\n" << strInfoLog << std::endl; 73 | delete [] strInfoLog; 74 | } 75 | 76 | return shader; 77 | } 78 | 79 | GLuint Shaders::CreateProgram(const GLuint vert, const GLuint frag) 80 | { 81 | GLuint program = glCreateProgram(); 82 | glAttachShader(program, vert); 83 | glAttachShader(program, frag); 84 | glLinkProgram(program); 85 | 86 | GLint status; 87 | glGetProgramiv(program, GL_LINK_STATUS, &status); 88 | if (status == GL_FALSE) 89 | { 90 | GLint infoLogLength; 91 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); 92 | 93 | GLchar* strInfoLog = new GLchar[infoLogLength + 1]; 94 | glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog); 95 | 96 | std::cerr << "Linker failure: " << strInfoLog << std::endl; 97 | delete [] strInfoLog; 98 | } 99 | 100 | return program; 101 | } 102 | -------------------------------------------------------------------------------- /shaders.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADERS_H 2 | #define SHADERS_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class Shaders 9 | { 10 | public: 11 | static void init(); 12 | 13 | static GLuint ship; 14 | static GLuint derivatives; 15 | static GLuint euler; 16 | static GLuint RK4sum; 17 | private: 18 | static std::string constants; 19 | 20 | static GLuint CompileShader(const std::string& filename); 21 | static GLuint CreateProgram(const GLuint vert, const GLuint frag); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /ship.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include // memset 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define GLFW_INCLUDE_GLCOREARB 11 | #include 12 | 13 | #include "ship.h" 14 | #include "shaders.h" 15 | 16 | //////////////////////////////////////////////////////////////////////////////// 17 | 18 | Ship::Ship(const std::string& imagename) 19 | : thrustEnginesOn(false), leftEnginesOn(false), rightEnginesOn(false) 20 | { 21 | LoadImage(imagename); 22 | MakeTextures(); 23 | MakeBuffers(); 24 | MakeFramebuffer(); 25 | MakeVertexArray(); 26 | } 27 | 28 | //////////////////////////////////////////////////////////////////////////////// 29 | 30 | Ship::~Ship() 31 | { 32 | delete [] data; 33 | delete [] filled; 34 | 35 | glDeleteBuffers(1, &vertex_buf); 36 | glDeleteBuffers(1, &color_buf); 37 | glDeleteBuffers(1, &rect_buf); 38 | 39 | GLuint* textures[] = { 40 | &filled_tex, &state_tex[0], &state_tex[1], 41 | &derivative_tex[0], &derivative_tex[2], 42 | &derivative_tex[2], &derivative_tex[3] 43 | }; 44 | 45 | for (auto t : textures) glDeleteTextures(1, t); 46 | 47 | glDeleteFramebuffers(1, &fbo); 48 | glDeleteVertexArrays(1, &vao); 49 | } 50 | 51 | //////////////////////////////////////////////////////////////////////////////// 52 | 53 | void Ship::GetDerivatives(const int source, const int out) 54 | { 55 | const GLuint program = Shaders::derivatives; 56 | glUseProgram(program); 57 | 58 | // Load boolean occupancy texture 59 | glActiveTexture(GL_TEXTURE0); 60 | glBindTexture(GL_TEXTURE_2D, filled_tex); 61 | glUniform1i(glGetUniformLocation(program, "filled"), 0); 62 | 63 | // Load RGB32F position and velocity textures 64 | glActiveTexture(GL_TEXTURE1); 65 | glBindTexture(GL_TEXTURE_2D, state_tex[source]); 66 | glUniform1i(glGetUniformLocation(program, "state"), 1); 67 | 68 | // Load various uniform values 69 | glUniform2i(glGetUniformLocation(program, "ship_size"), width, height); 70 | 71 | glUniform1f(glGetUniformLocation(program, "k"), 10000.0f); 72 | glUniform1f(glGetUniformLocation(program, "c"), 1000.0f); 73 | glUniform1f(glGetUniformLocation(program, "m"), 1.0f); 74 | glUniform1f(glGetUniformLocation(program, "I"), 1.0f); 75 | 76 | glUniform1i(glGetUniformLocation(program, "thrustEnginesOn"), 77 | thrustEnginesOn); 78 | glUniform1i(glGetUniformLocation(program, "leftEnginesOn"), 79 | leftEnginesOn || (thrustEnginesOn&& !rightEnginesOn)); 80 | glUniform1i(glGetUniformLocation(program, "rightEnginesOn"), 81 | rightEnginesOn || (thrustEnginesOn && !leftEnginesOn)); 82 | 83 | RenderToFBO(program, derivative_tex[out]); 84 | } 85 | 86 | //////////////////////////////////////////////////////////////////////////////// 87 | 88 | void Ship::ApplyDerivatives(const float dt, const int source) 89 | { 90 | const GLuint program = Shaders::euler; 91 | glUseProgram(program); 92 | 93 | // Bind old velocity texture 94 | glActiveTexture(GL_TEXTURE0); 95 | glBindTexture(GL_TEXTURE_2D, state_tex[tick]); 96 | glUniform1i(glGetUniformLocation(program, "state"), 0); 97 | 98 | // Bind acceleration texture 99 | glActiveTexture(GL_TEXTURE1); 100 | glBindTexture(GL_TEXTURE_2D, derivative_tex[source]); 101 | glUniform1i(glGetUniformLocation(program, "accel"), 1); 102 | 103 | // Set time-step value 104 | glUniform1f(glGetUniformLocation(program, "dt"), dt); 105 | 106 | // Set the texture size 107 | glUniform2i(glGetUniformLocation(program, "size"), width, height); 108 | 109 | RenderToFBO(program, state_tex[!tick]); 110 | } 111 | 112 | //////////////////////////////////////////////////////////////////////////////// 113 | 114 | void Ship::FindPosition() 115 | { 116 | float state[(width+1)*(height+1)*4]; 117 | glBindTexture(GL_TEXTURE_2D, state_tex[tick]); 118 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &state); 119 | 120 | centroid[0] = 0; 121 | centroid[1] = 0; 122 | velocity[0] = 0; 123 | velocity[1] = 0; 124 | size_t count = 0; 125 | 126 | for (size_t j=0; j <= height; ++j) 127 | { 128 | for (size_t i=0; i <= width; ++i) 129 | { 130 | if (filled[i + j*(width+1)]) 131 | { 132 | centroid[0] += state[4*(i + j*(width+1))]; 133 | centroid[1] += state[4*(i + j*(width+1)) + 1]; 134 | velocity[0] += state[4*(i + j*(width+1)) + 2]; 135 | velocity[1] += state[4*(i + j*(width+1)) + 3]; 136 | count++; 137 | } 138 | } 139 | } 140 | 141 | centroid[0] /= float(count); 142 | centroid[1] /= float(count); 143 | velocity[0] /= float(count); 144 | velocity[1] /= float(count); 145 | } 146 | 147 | //////////////////////////////////////////////////////////////////////////////// 148 | 149 | void Ship::PrintTextureValues() 150 | { 151 | float tex[(width+1)*(height+1)*4]; 152 | 153 | glBindTexture(GL_TEXTURE_2D, state_tex[tick]); 154 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &tex); 155 | std::cout << "State:\n"; 156 | for (int i=0; i < (width+1)*(height+1)*4; i += 4) 157 | { 158 | std::cout << tex[i] << ',' << tex[i+1] << " "; 159 | } 160 | std::cout << std::endl; 161 | 162 | std::cout << "Velocities:\n"; 163 | for (int i=0; i < (width+1)*(height+1)*4; i += 4) 164 | { 165 | std::cout << tex[i+2] << ',' << tex[i+3] << " "; 166 | } 167 | std::cout << std::endl; 168 | 169 | GetDerivatives(tick, 0); 170 | glBindTexture(GL_TEXTURE_2D, derivative_tex[0]); 171 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &tex); 172 | std::cout << "Derived velocities:\n"; 173 | for (int i=0; i < (width+1)*(height+1)*4; i += 4) 174 | { 175 | std::cout << tex[i] << ',' << tex[i+1] << " "; 176 | } 177 | std::cout << std::endl; 178 | std::cout << "Accelerations:\n"; 179 | for (int i=0; i < (width+1)*(height+1)*4; i += 4) 180 | { 181 | std::cout << tex[i+2] << ',' << tex[i+3] << " "; 182 | } 183 | std::cout << std::endl << std::endl; 184 | 185 | } 186 | 187 | //////////////////////////////////////////////////////////////////////////////// 188 | 189 | void Ship::Update(const float dt, const int steps) 190 | { 191 | //PrintTextureValues(); 192 | 193 | const float dt_ = dt / steps; 194 | for (int i=0; i < steps; ++i) { 195 | GetDerivatives(tick, 0); // k1 = f(y) 196 | 197 | ApplyDerivatives(dt_/2, 0); // Calculate y + dt/2 * k1 198 | GetDerivatives(!tick, 1); // k2 = f(y + dt/2 * k1) 199 | 200 | ApplyDerivatives(dt_/2, 1); // Calculate y + dt/2 * k2 201 | GetDerivatives(!tick, 2); // k3 = f(y + dt/2 * k2) 202 | 203 | ApplyDerivatives(dt_, 2); // Calculate y + dt * k3 204 | GetDerivatives(!tick, 3); // k4 = f(y + dt * k3) 205 | 206 | // Update state and swap which texture is active 207 | GetNextState(dt_); 208 | } 209 | 210 | // Update centroid and velocity arrays. 211 | FindPosition(); 212 | } 213 | 214 | //////////////////////////////////////////////////////////////////////////////// 215 | 216 | void Ship::GetNextState(const float dt) 217 | { 218 | const GLuint program = Shaders::RK4sum; 219 | glUseProgram(program); 220 | 221 | glActiveTexture(GL_TEXTURE0); 222 | glBindTexture(GL_TEXTURE_2D, state_tex[tick]); 223 | glUniform1i(glGetUniformLocation(program, "y"), 0); 224 | 225 | glActiveTexture(GL_TEXTURE1); 226 | glBindTexture(GL_TEXTURE_2D, derivative_tex[0]); 227 | glUniform1i(glGetUniformLocation(program, "k1"), 1); 228 | 229 | glActiveTexture(GL_TEXTURE2); 230 | glBindTexture(GL_TEXTURE_2D, derivative_tex[1]); 231 | glUniform1i(glGetUniformLocation(program, "k2"), 2); 232 | 233 | glActiveTexture(GL_TEXTURE3); 234 | glBindTexture(GL_TEXTURE_2D, derivative_tex[2]); 235 | glUniform1i(glGetUniformLocation(program, "k3"), 3); 236 | 237 | glActiveTexture(GL_TEXTURE4); 238 | glBindTexture(GL_TEXTURE_2D, derivative_tex[3]); 239 | glUniform1i(glGetUniformLocation(program, "k4"), 4); 240 | 241 | glUniform1f(glGetUniformLocation(program, "dt"), dt); 242 | 243 | // Set the texture size 244 | glUniform2i(glGetUniformLocation(program, "size"), width, height); 245 | 246 | RenderToFBO(program, state_tex[!tick]); 247 | 248 | tick = !tick; 249 | } 250 | 251 | //////////////////////////////////////////////////////////////////////////////// 252 | 253 | void Ship::Draw(const int window_width, const int window_height, 254 | const bool track, const float scale) const 255 | { 256 | glViewport(0, 0, window_width, window_height); 257 | 258 | const GLuint program = Shaders::ship; 259 | glUseProgram(program); 260 | 261 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buf); 262 | glEnableVertexAttribArray(0); 263 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0); 264 | 265 | glBindBuffer(GL_ARRAY_BUFFER, color_buf); 266 | glEnableVertexAttribArray(1); 267 | glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 3*sizeof(GLbyte), 0); 268 | 269 | glUniform2i(glGetUniformLocation(program, "window_size"), 270 | window_width, window_height); 271 | glUniform2i(glGetUniformLocation(program, "ship_size"), 272 | width, height); 273 | 274 | if (track) 275 | { 276 | glUniform2f(glGetUniformLocation(program, "offset"), 277 | centroid[0], centroid[1]); 278 | } 279 | else 280 | { 281 | glUniform2f(glGetUniformLocation(program, "offset"), 282 | width/2.0f, height/2.0f); 283 | } 284 | 285 | glUniform1f(glGetUniformLocation(program, "scale"), scale); 286 | 287 | glActiveTexture(GL_TEXTURE0); 288 | glBindTexture(GL_TEXTURE_2D, state_tex[tick]); 289 | glUniform1i(glGetUniformLocation(program, "pos"), 0); 290 | 291 | glUniform1i(glGetUniformLocation(program, "thrustEnginesOn"), 292 | thrustEnginesOn); 293 | glUniform1i(glGetUniformLocation(program, "leftEnginesOn"), 294 | leftEnginesOn || (thrustEnginesOn && !rightEnginesOn)); 295 | glUniform1i(glGetUniformLocation(program, "rightEnginesOn"), 296 | rightEnginesOn || (thrustEnginesOn && !leftEnginesOn)); 297 | 298 | glDrawArrays(GL_TRIANGLES, 0, pixel_count*2*3); 299 | } 300 | 301 | //////////////////////////////////////////////////////////////////////////////// 302 | 303 | void Ship::RenderToFBO(const GLuint program, const GLuint tex) 304 | { 305 | // Bind the desired texture to the framebuffer 306 | glBindFramebuffer(GL_FRAMEBUFFER, fbo); 307 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 308 | GL_TEXTURE_2D, tex, 0); 309 | 310 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 311 | glViewport(0, 0, width+1, height+1); 312 | 313 | // Load triangles that draw a flat rectangle from -1, -1, to 1, 1 314 | glBindBuffer(GL_ARRAY_BUFFER, rect_buf); 315 | glEnableVertexAttribArray(0); 316 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0); 317 | 318 | // Draw the full rectangle into the FBO 319 | glDrawArrays(GL_TRIANGLES, 0, 6); 320 | 321 | // Switch back to the default framebuffer. 322 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 323 | } 324 | 325 | //////////////////////////////////////////////////////////////////////////////// 326 | 327 | // Minimal function to load a .png image. 328 | // Assumes that the file exists and does minimal error checking. 329 | void Ship::LoadImage(const std::string& imagename) 330 | { 331 | png_structp png_ptr = png_create_read_struct( 332 | PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 333 | png_infop info_ptr = png_create_info_struct(png_ptr); 334 | 335 | FILE* input = fopen(imagename.c_str(), "rb"); 336 | png_init_io(png_ptr, input); 337 | png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); 338 | fclose(input); 339 | 340 | if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA) 341 | { 342 | std::cerr << "[pixelsim] Error: Image must have alpha channel." 343 | << std::endl; 344 | exit(-1); 345 | } 346 | else if (png_get_bit_depth(png_ptr, info_ptr) != 8) 347 | { 348 | std::cerr << "[pixelsim] Error: Image must have 8-bit depth." 349 | << std::endl; 350 | exit(-1); 351 | } 352 | 353 | width = png_get_image_width(png_ptr, info_ptr); 354 | height = png_get_image_height(png_ptr, info_ptr); 355 | 356 | data = new uint8_t[width*height*4]; 357 | png_bytep* rows = png_get_rows(png_ptr, info_ptr); 358 | for (size_t j=0; j < height; ++j) { 359 | memmove(&data[j*width*4], rows[j], width*4); 360 | } 361 | 362 | png_destroy_read_struct(&png_ptr, &info_ptr, NULL); 363 | } 364 | 365 | //////////////////////////////////////////////////////////////////////////////// 366 | 367 | void Ship::MakeBuffers() 368 | { 369 | std::vector vertices; 370 | std::vector colors; 371 | 372 | for (size_t y=0; y < height; ++y) { 373 | for (size_t x=0; x < width; ++x) { 374 | if (data[y*width*4 + x*4 + 3]) { 375 | // First triangle 376 | vertices.push_back(x); 377 | vertices.push_back(height-y-1); 378 | 379 | vertices.push_back(x+1); 380 | vertices.push_back(height-y-1); 381 | 382 | vertices.push_back(x+1); 383 | vertices.push_back(height-y); 384 | 385 | // Second triangle 386 | vertices.push_back(x+1); 387 | vertices.push_back(height-y); 388 | 389 | vertices.push_back(x); 390 | vertices.push_back(height-y); 391 | 392 | vertices.push_back(x); 393 | vertices.push_back(height-y-1); 394 | 395 | // Every vertex gets a color from the image 396 | for (int v=0; v < 6; ++v) { 397 | colors.push_back(data[y*width*4 + x*4]); 398 | colors.push_back(data[y*width*4 + x*4 + 1]); 399 | colors.push_back(data[y*width*4 + x*4 + 2]); 400 | } 401 | } 402 | } 403 | } 404 | 405 | // Save the total number of filled pixels 406 | pixel_count = vertices.size() / 12; 407 | 408 | // Allocate space for the vertices, colors, and position data 409 | glGenBuffers(1, &vertex_buf); 410 | glBindBuffer(GL_ARRAY_BUFFER, vertex_buf); 411 | glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(vertices[0]), 412 | &vertices[0], GL_STATIC_DRAW); 413 | 414 | glGenBuffers(1, &color_buf); 415 | glBindBuffer(GL_ARRAY_BUFFER, color_buf); 416 | glBufferData(GL_ARRAY_BUFFER, colors.size()*sizeof(colors[0]), 417 | &colors[0], GL_STATIC_DRAW); 418 | 419 | 420 | // Make a screen-filling flat pane used for texture FBO rendering 421 | GLfloat rect[12] = { 422 | -1, -1, 423 | 1, -1, 424 | 1, 1, 425 | -1, -1, 426 | 1, 1, 427 | -1, 1}; 428 | glGenBuffers(1, &rect_buf); 429 | glBindBuffer(GL_ARRAY_BUFFER, rect_buf); 430 | glBufferData(GL_ARRAY_BUFFER, 12*sizeof(rect[0]), 431 | &rect[0], GL_STATIC_DRAW); 432 | 433 | } 434 | 435 | //////////////////////////////////////////////////////////////////////////////// 436 | 437 | void Ship::MakeTextures() 438 | { 439 | { // Load a byte-map recording occupancy 440 | // Bytes are byte-aligned, so set unpack alignment to 1 441 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 442 | filled = new GLubyte[(width+1)*(height+1)]; 443 | memset(filled, 0, sizeof(GLubyte)*(width+1)*(height+1)); 444 | 445 | for (size_t y=0; y < height; ++y) { 446 | for (size_t x=0; x < width; ++x) { 447 | // Get the pixel's address in the data array: 448 | uint8_t* const pixel = &data[4*(width*(height-1-y) + x)]; 449 | const uint8_t r = pixel[0]; 450 | const uint8_t g = pixel[1]; 451 | const uint8_t b = pixel[2]; 452 | const uint8_t a = pixel[3]; 453 | 454 | // Pure red nodes are thruster engines 455 | // Red with 1 bit of blue are leftward engines 456 | // Red with 2 bits of blue are rightward engines 457 | GLubyte type; 458 | if (r == SHIP_ENGINE_THRUST_R && 459 | g == SHIP_ENGINE_THRUST_G && 460 | b == SHIP_ENGINE_THRUST_B && a) type = THRUST; 461 | else if (r == SHIP_ENGINE_LEFT_R && 462 | g == SHIP_ENGINE_LEFT_G && 463 | b == SHIP_ENGINE_LEFT_B && a) type = LEFT; 464 | else if (r == SHIP_ENGINE_RIGHT_R && 465 | g == SHIP_ENGINE_RIGHT_G && 466 | b == SHIP_ENGINE_RIGHT_B && a) type = RIGHT; 467 | else if (a) type = SHIP; 468 | else type = EMPTY; 469 | 470 | const size_t indices[] = { 471 | y*(width+1) + x, (y+1)*(width+1) + x, 472 | y*(width+1) + x + 1, (y+1)*(width+1) + x + 1}; 473 | 474 | 475 | for (size_t i : indices) { 476 | if (filled[i] == EMPTY) 477 | { 478 | filled[i] = type; 479 | } 480 | else if (type != EMPTY && filled[i] != type) 481 | { 482 | filled[i] = SHIP; 483 | } 484 | } 485 | } 486 | } 487 | 488 | glGenTextures(1, &filled_tex); 489 | glBindTexture(GL_TEXTURE_2D, filled_tex); 490 | glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width+1, height+1, 491 | 0, GL_RED, GL_UNSIGNED_BYTE, filled); 492 | SetTextureDefaults(); 493 | } 494 | 495 | 496 | { // Make a texture that stores position and velocity, and initialize 497 | // it with each pixel centered in the proper position with velocity 0. 498 | 499 | // Floats are 4-byte-aligned. 500 | glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 501 | GLfloat* const pos = new GLfloat[(width+1)*(height+1)*4]; 502 | size_t i=0; 503 | for (size_t y=0; y <= height; ++y) { 504 | for (size_t x=0; x <= width; ++x) { 505 | pos[i++] = x; 506 | pos[i++] = y; 507 | pos[i++] = 0; 508 | pos[i++] = 0; 509 | } 510 | } 511 | 512 | GLuint* textures[] = {&state_tex[0], &state_tex[1], 513 | &derivative_tex[0], &derivative_tex[1], 514 | &derivative_tex[2], &derivative_tex[3]}; 515 | for (auto t : textures) 516 | { 517 | glGenTextures(1, t); 518 | glBindTexture(GL_TEXTURE_2D, *t); 519 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width+1, height+1, 520 | 0, GL_RGBA, GL_FLOAT, pos); 521 | SetTextureDefaults(); 522 | } 523 | delete [] pos; 524 | } 525 | } 526 | 527 | void Ship::MakeFramebuffer() 528 | { 529 | glGenFramebuffers(1, &fbo); 530 | } 531 | 532 | void Ship::MakeVertexArray() 533 | { 534 | glGenVertexArrays(1, &vao); 535 | glBindVertexArray(vao); // we'll use this VAO all the time 536 | } 537 | 538 | void Ship::SetTextureDefaults() const 539 | { 540 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 541 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 542 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 543 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 544 | } 545 | -------------------------------------------------------------------------------- /ship.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | flat in vec4 color_out; 4 | 5 | uniform int thrustEnginesOn; 6 | uniform int leftEnginesOn; 7 | uniform int rightEnginesOn; 8 | 9 | out vec4 fragColor; 10 | 11 | void main() 12 | { 13 | if ((color_out.r == SHIP_ENGINE_THRUST_R/255.0f && 14 | color_out.g == SHIP_ENGINE_THRUST_G/255.0f && 15 | color_out.b == SHIP_ENGINE_THRUST_B/255.0f && thrustEnginesOn == 0) || 16 | (color_out.r == SHIP_ENGINE_LEFT_R/255.0f && 17 | color_out.g == SHIP_ENGINE_LEFT_G/255.0f && 18 | color_out.b == SHIP_ENGINE_LEFT_B/255.0f && leftEnginesOn == 0) || 19 | (color_out.r == SHIP_ENGINE_RIGHT_R/255.0f && 20 | color_out.g == SHIP_ENGINE_RIGHT_G/255.0f && 21 | color_out.b == SHIP_ENGINE_RIGHT_B/255.0f && rightEnginesOn == 0)) 22 | { 23 | discard; 24 | } 25 | else 26 | { 27 | fragColor = color_out; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ship.h: -------------------------------------------------------------------------------- 1 | #ifndef SHIP_H 2 | #define SHIP_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "constants.h" 9 | 10 | class Ship 11 | { 12 | public: 13 | Ship(const std::string& imagename); 14 | ~Ship(); 15 | 16 | bool thrustEnginesOn; 17 | bool leftEnginesOn; 18 | bool rightEnginesOn; 19 | 20 | void Update(const float dt=0.1, const int steps=5); 21 | void Draw(const int window_width, const int window_height, 22 | const bool track, const float scale) const; 23 | 24 | private: 25 | enum NodeType {EMPTY=0, SHIP=1, 26 | THRUST=SHIP_ENGINE_THRUST, 27 | LEFT =SHIP_ENGINE_LEFT, 28 | RIGHT =SHIP_ENGINE_RIGHT}; 29 | 30 | void MakeBuffers(); 31 | void LoadImage(const std::string& imagename); 32 | void MakeTextures(); 33 | void MakeFramebuffer(); 34 | void MakeVertexArray(); 35 | 36 | // Set reasonable OpenGL defaults for a texture. 37 | void SetTextureDefaults() const; 38 | 39 | // Calculate derivatives of state_tex[source], storing them 40 | // in derivative_tex[out] 41 | void GetDerivatives(const int source, const int out); 42 | 43 | // Applies derivative_tex[source] to state_tex[tick], storing 44 | // new state in state_tex[!tick] 45 | void ApplyDerivatives(const float dt, const int source); 46 | 47 | // Uses RK4 to get the new state, then flips tick. 48 | void GetNextState(const float dt); 49 | 50 | // Extract centroid and velocity from state_tex[tick] 51 | void FindPosition(); 52 | 53 | // Helper function draw a flat quad to a FBO. 54 | void RenderToFBO(const GLuint program, const GLuint tex); 55 | 56 | // Debug: print out texture values. 57 | void PrintTextureValues(); 58 | 59 | size_t width; 60 | size_t height; 61 | uint8_t* data; 62 | GLubyte* filled; 63 | 64 | float centroid[2]; 65 | float velocity[2]; 66 | 67 | // Number of filled pixels 68 | size_t pixel_count; 69 | 70 | // Buffers 71 | GLuint vertex_buf; 72 | GLuint color_buf; 73 | GLuint rect_buf; 74 | 75 | // Textures 76 | GLuint filled_tex; // boolean storing occupancy 77 | 78 | GLuint state_tex[2]; // position & velocity of each pixel 79 | GLuint derivative_tex[4]; // derivatives of position and velocity (for RK4) 80 | 81 | bool tick; 82 | 83 | // Frame-buffer object 84 | GLuint fbo; 85 | 86 | // Vertex array object 87 | GLuint vao; 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /ship.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location=0) in vec2 vertex_position; 4 | layout(location=1) in vec3 color_in; 5 | 6 | flat out vec4 color_out; 7 | 8 | uniform ivec2 window_size; 9 | uniform ivec2 ship_size; 10 | 11 | uniform vec2 offset; 12 | uniform float scale; 13 | 14 | uniform sampler2D pos; 15 | 16 | void main() 17 | { 18 | color_out = vec4(color_in, 1.0f); 19 | 20 | vec2 xy = texture(pos, vec2( 21 | (vertex_position.x + 1.0f) / float(ship_size.x + 2), 22 | (vertex_position.y + 1.0f) / float(ship_size.y + 2))).xy; 23 | 24 | vec2 centered = xy - offset; 25 | 26 | // If the ship is wider than the window, fit the x axis 27 | if ((ship_size.x*window_size.y) / (ship_size.y*window_size.x) >= 1) 28 | { 29 | centered *= 2.0f / ship_size.x; 30 | centered.y *= float(window_size.x) / float(window_size.y); 31 | } 32 | else // otherwise, fit the y axis 33 | { 34 | centered *= 2.0f / ship_size.y; 35 | centered.x *= float(window_size.y) / float(window_size.x); 36 | } 37 | 38 | gl_Position = vec4(centered*scale, 0.0, 1.0); 39 | } 40 | -------------------------------------------------------------------------------- /texture.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location=0) in vec2 vertex_position; 4 | 5 | // Expects to get a rectangle from -1, -1 to 1, 1 6 | void main() 7 | { 8 | gl_Position = vec4(vertex_position, 0.0f, 1.0f); 9 | } 10 | --------------------------------------------------------------------------------