Created
March 2, 2013 17:23
-
-
Save roxlu/5072080 to your computer and use it in GitHub Desktop.
Oil Painting a la Petros Vrellis Starry Night
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Simulation.h> | |
#include <iostream> | |
Simulation* sim_ptr; | |
// CALLBACKS | |
// --------- | |
void window_size_callback(GLFWwindow* window, int w, int h); | |
int window_close_callback(GLFWwindow* window); | |
void mouse_button_callback(GLFWwindow* window, int button, int action); | |
void cursor_callback(GLFWwindow* window, int x, int y); | |
void scroll_callback(GLFWwindow* window, double x, double y); | |
void key_callback(GLFWwindow* window, int key, int action); | |
void error_callback(int err, const char* msg); | |
void char_callback(GLFWwindow* window, int chr); | |
// APPLICATION ENTRY | |
// ----------------- | |
int main() { | |
int width = 800; | |
int height = 600; | |
sim_ptr = NULL; | |
int c = 0; | |
GLFWmonitor** m = glfwGetMonitors(&c); | |
// init | |
glfwSetErrorCallback(error_callback); | |
if(!glfwInit()) { | |
printf("ERROR: cannot initialize GLFW.\n"); | |
exit(EXIT_FAILURE); | |
} | |
// glfwWindowHint(GLFW_DEPTH_BITS, 16); | |
//GLFWwindow window = glfwCreateWindow(width, height, GLFW_WINDOWED, "Simulation", NULL); | |
GLFWwindow* window = glfwCreateWindow(width, height, "Simulation", NULL, NULL); | |
if(!window) { | |
printf("ERROR: cannot open window.\n"); | |
exit(EXIT_FAILURE); | |
} | |
glfwSetWindowSizeCallback(window, window_size_callback); | |
glfwSetWindowCloseCallback(window, window_close_callback); | |
glfwSetMouseButtonCallback(window, mouse_button_callback); | |
glfwSetCursorPosCallback(window, cursor_callback); | |
glfwSetScrollCallback(window, scroll_callback); | |
glfwSetKeyCallback(window, key_callback); | |
glfwSetCharCallback(window, char_callback); | |
glfwMakeContextCurrent(window); | |
// glewExperimental = true; | |
GLenum err = glewInit(); | |
if (GLEW_OK != err) { | |
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err)); | |
exit(EXIT_FAILURE); | |
} | |
Simulation sim; | |
sim_ptr = ∼ | |
sim.window = window; | |
sim.window_w = width; | |
sim.window_h = height; | |
sim.setup(); | |
bool running = true; | |
while(running) { | |
glfwPollEvents(); | |
sim.update(); | |
sim.draw(); | |
glfwSwapBuffers(window); | |
running = !(glfwGetKey(window, GLFW_KEY_ESC)); | |
} | |
glfwTerminate(); | |
return EXIT_SUCCESS; | |
}; | |
// CALLBACK DEFS | |
// -------------- | |
void window_size_callback(GLFWwindow* window, int w, int h) { | |
if(sim_ptr) { | |
sim_ptr->onWindowResize(w,h); | |
} | |
} | |
int window_close_callback(GLFWwindow* window) { | |
if(sim_ptr) { | |
sim_ptr->onWindowClose(); | |
} | |
return GL_TRUE; | |
} | |
void mouse_button_callback(GLFWwindow* window, int button, int action) { | |
if(!sim_ptr) { | |
return; | |
} | |
if(action == GLFW_PRESS) { | |
sim_ptr->onMouseDown(sim_ptr->mouse_x, sim_ptr->mouse_y, button); | |
sim_ptr->pressed_mouse_button = button; | |
sim_ptr->is_mouse_down = true; | |
} | |
else { | |
sim_ptr->onMouseUp(sim_ptr->mouse_x, sim_ptr->mouse_y, button); | |
sim_ptr->pressed_mouse_button = -1; | |
sim_ptr->is_mouse_down = false; | |
} | |
} | |
void cursor_callback(GLFWwindow* window, int x, int y) { | |
if(!sim_ptr) { | |
return; | |
} | |
sim_ptr->mouse_x = x; | |
sim_ptr->mouse_y = y; | |
if(sim_ptr->is_mouse_down) { | |
sim_ptr->onMouseDrag(x, y, (sim_ptr->prev_mouse_x - x), (sim_ptr->prev_mouse_y - y), sim_ptr->pressed_mouse_button); | |
} | |
else { | |
sim_ptr->onMouseMove(x,y); | |
} | |
sim_ptr->prev_mouse_x = sim_ptr->mouse_x; | |
sim_ptr->prev_mouse_y = sim_ptr->mouse_y; | |
} | |
void scroll_callback(GLFWwindow* window, double x, double y) { | |
if(!sim_ptr) { | |
return; | |
} | |
} | |
void char_callback(GLFWwindow* window, int ch) { | |
if(!sim_ptr) { | |
return; | |
} | |
sim_ptr->onChar(ch); | |
} | |
void key_callback(GLFWwindow* window, int key, int action) { | |
if(!sim_ptr) { | |
return; | |
} | |
if(action == GLFW_PRESS) { | |
sim_ptr->onKeyDown(key); | |
} | |
else { | |
sim_ptr->onKeyUp(key); | |
} | |
} | |
void error_callback(int err, const char* msg) { | |
printf("ERROR: %s, %d\n", msg, err); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Oil.h" | |
Oil::Oil() | |
:gray_pixels(0) | |
,color_pixels(0) | |
{ | |
} | |
Oil::~Oil() { | |
} | |
void Oil::setup(int w, int h, int components) { | |
this->w = w; | |
this->h = h; | |
this->components = components; | |
color_pixels = new unsigned char[w * h * components]; | |
gray_pixels = new unsigned char[w * h]; | |
} | |
void Oil::setPixels(unsigned char* pix) { | |
memcpy(color_pixels, pix, sizeof(unsigned char) * w * h * components); | |
for(int i = 0; i < w; ++i) { | |
for(int j = 0; j < h; ++j) { | |
int dx = j * w + i; | |
int src_dx = j * w * components + i * components; | |
gray_pixels[dx] = pix[src_dx + 0] * 0.2126 | |
+ pix[src_dx + 1] * 0.07152 | |
+ pix[src_dx + 2] * 0.0722; | |
} | |
} | |
} | |
void Oil::createVectors() { | |
vectors.clear(); | |
vectors.assign(w * h, Vec2()); | |
int d = 1; | |
for(int i = d; i < w - d; ++i) { | |
for(int j = d; j < h - d; ++j) { | |
int dxj = j; | |
float left = gray_pixels[DX(i-d,dxj,w)]; | |
float right = gray_pixels[DX(i+d,dxj,w)]; | |
float top = gray_pixels[DX(i,dxj-d,w)]; | |
float bottom = gray_pixels[DX(i,dxj+d,w)]; | |
float dx = (right - left) / 2; | |
float dy = (bottom - top) / 2; | |
float a = atan2(-dy,dx); | |
Vec2 vec(cos(a), sin(a)); | |
vectors[DX(i,h-j,w)] = vec; | |
} | |
} | |
} | |
void Oil::update() { | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ROXLU_OIL_H | |
#define ROXLU_OIL_H | |
#include <assert.h> | |
#include <roxlu/Roxlu.h> | |
class Oil { | |
public: | |
Oil(); | |
~Oil(); | |
void setup(int w, int h, int components); | |
void update(); | |
void setPixels(unsigned char* pix); | |
void createVectors(); | |
unsigned char* getColorPixels(); | |
unsigned char* getGrayPixels(); | |
Vec2 getVector(int i, int j); | |
Vec3 getColor(int i, int j); | |
public: | |
std::vector<Vec2> vectors; | |
int w; | |
int h; | |
int components; | |
unsigned char* color_pixels; | |
unsigned char* gray_pixels; | |
}; | |
inline unsigned char* Oil::getColorPixels() { | |
return color_pixels; | |
} | |
inline unsigned char* Oil::getGrayPixels() { | |
return gray_pixels; | |
} | |
inline Vec2 Oil::getVector(int i, int j) { | |
Vec2 result; | |
if(i < 0 || i > w) { | |
return result; | |
} | |
if(j < 0 || j > h) { | |
return result; | |
} | |
return vectors[DX(i, h-j, w)]; | |
} | |
inline Vec3 Oil::getColor(int i, int j) { | |
assert(i < w && i >= 0); | |
assert(j < h && j >= 0); | |
unsigned char* p = color_pixels + (j * w * components + i * components); | |
Vec3 col(*(p + 0) / 255.0f, *(p + 1) / 255.0f, *(p + 2) / 255.0); | |
return col; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "OilDrawer.h" | |
OilDrawer::OilDrawer(Oil& o, Particles& ps) | |
:oil(o) | |
,ps(ps) | |
,debug_draw(false) | |
{ | |
} | |
OilDrawer::~OilDrawer() { | |
} | |
GLuint OilDrawer::createTexture(int w, int h, int c, unsigned char* pixels) { | |
GLuint tmp; | |
glGenTextures(1, &tmp); | |
glBindTexture(GL_TEXTURE_2D, tmp); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, (c == 4) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, pixels); | |
return tmp; | |
} | |
void OilDrawer::setup() { | |
col_tex = createTexture(oil.w, oil.h, oil.components, oil.getColorPixels()); | |
Image img; | |
img.load(rx_to_data_path("brush0.png")); | |
tex0 = createTexture(img.getWidth(), img.getHeight(), img.getComponents(), img.getPixels()); | |
img.load(rx_to_data_path("brush1.png")); | |
tex1 = createTexture(img.getWidth(), img.getHeight(), img.getComponents(), img.getPixels()); | |
img.load(rx_to_data_path("brush2.png")); | |
tex2 = createTexture(img.getWidth(), img.getHeight(), img.getComponents(), img.getPixels()); | |
img.load(rx_to_data_path("brush3.png")); | |
tex3 = createTexture(img.getWidth(), img.getHeight(), img.getComponents(), img.getPixels()); | |
prog = rx_create_shader(O_VS, O_FS); | |
glBindAttribLocation(prog, 0, "a_pos"); | |
glBindAttribLocation(prog, 1, "a_tex"); | |
glLinkProgram(prog); | |
glUseProgram(prog); | |
u_pm = glGetUniformLocation(prog, "u_projection_matrix"); | |
u_mm = glGetUniformLocation(prog, "u_model_matrix"); | |
u_color = glGetUniformLocation(prog, "u_color"); | |
u_tex = glGetUniformLocation(prog, "u_tex"); | |
glUniform1i(u_tex, 0); | |
printf("pm: %d, mm: %d\n", u_pm, u_mm); | |
ortho.orthoTopLeft(800, 600, 0.0, -1.0); | |
glUniformMatrix4fv(u_pm, 1, GL_FALSE, ortho.getPtr()); | |
float ww = 16.0f; | |
float hh = 4.0f; | |
GLfloat vertices[] = { | |
-ww, -hh, 0.0f, 0.0f, | |
ww, -hh, 0.0f, 1.0f, | |
ww, hh, 1.0f, 1.0f, | |
-ww, -hh, 0.0f, 0.0f, | |
ww, hh, 1.0f, 1.0f, | |
-ww, hh, 1.0f, 0.0f | |
}; | |
/* | |
-ww, -hh, 0.0f, 0.0f, | |
ww, -hh, 1.0f, 0.0f, | |
ww, hh, 1.0f, 1.0f, | |
-ww, -hh, 0.0f, 0.0f, | |
ww, hh, 1.0f, 1.0f, | |
-ww, hh, 0.0f, 1.0f | |
*/ | |
glGenVertexArrays(1, &vao); | |
glBindVertexArray(vao); | |
glGenBuffers(1,&vbo); | |
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); | |
glEnableVertexAttribArray(0); | |
glEnableVertexAttribArray(1); | |
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (GLvoid*)0); | |
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (GLvoid*)8); | |
} | |
void OilDrawer::update() { | |
} | |
void OilDrawer::draw() { | |
// DEBUG | |
if(debug_draw) { | |
dd.drawTexture(col_tex, 0,0, 800, 600); | |
// VECTORS | |
glLineWidth(1.0f); | |
dd.begin(GL_LINES); | |
Vec4 col(1.0, 0.0, 1.0, 1.0); | |
int step = 16; | |
float len = step * 0.8; | |
for(int i = 0; i < oil.w - step; i += step) { | |
for(int j = 0; j < oil.h - step; j += step) { | |
Vec2& v = oil.vectors[DX(i,j,oil.w)]; | |
Vec2 pos(i, j); | |
dd.addVertex(pos, col); | |
dd.addVertex(pos + (v * len), col); | |
} | |
} | |
dd.end(); | |
#if 0 | |
// PARTICLES | |
glPointSize(3.0f); | |
col.set(0.1, 0.8, 0.2, 1.0); | |
dd.begin(GL_POINTS); | |
for(int i = 0; i < ps.particles.size(); ++i) { | |
Particle& p = *ps.particles[i]; | |
col.set(p.color.x, p.color.y, p.color.z, 1.0); | |
dd.addVertex(ps.particles[i]->position, col); | |
} | |
dd.end(); | |
#endif | |
Mat4 ident; | |
dd.draw(ortho.getPtr(), ident.getPtr()); | |
} | |
else { | |
// SHADED | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
//glBlendFunc(GL_ONE, GL_SRC_COLOR); | |
//glBlendFunc(GL_ONE, GL_ONE); | |
glActiveTexture(GL_TEXTURE0); | |
glBindVertexArray(vao); | |
glUseProgram(prog); | |
Mat4 mm; | |
for(int i = 0; i < ps.particles.size(); ++i) { | |
Particle& p = *ps.particles[i]; | |
mm.identity(); | |
mm.setPosition(p.position.x, p.position.y, 0); | |
Vec3 v = p.velnorm; | |
mm[0] = v.x; | |
mm[1] = v.y; | |
mm[4] = -v.y; | |
mm[5] = v.x; | |
glUniformMatrix4fv(u_mm, 1, GL_FALSE, mm.getPtr()); | |
glUniform3fv(u_color, 1, p.color.getPtr()); | |
if(p.tex == 0) { | |
glBindTexture(GL_TEXTURE_2D, tex0); | |
} | |
else if(p.tex == 1) { | |
glBindTexture(GL_TEXTURE_2D, tex1); | |
} | |
else if(p.tex == 2) { | |
glBindTexture(GL_TEXTURE_2D, tex2); | |
} | |
else if(p.tex == 3) { | |
glBindTexture(GL_TEXTURE_2D, tex3); | |
} | |
glDrawArrays(GL_TRIANGLES, 0, 6); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ROXLU_OIL_DRAWER_H | |
#define ROXLU_OIL_DRAWER_H | |
#include <roxlu/Roxlu.h> | |
#include "Oil.h" | |
#include "Particles.h" | |
static const char* O_VS = GLSL(120, | |
uniform mat4 u_projection_matrix; | |
uniform mat4 u_model_matrix; | |
attribute vec4 a_pos; | |
attribute vec2 a_tex; | |
varying vec2 v_tex; | |
void main() { | |
v_tex = a_tex; | |
gl_Position = u_projection_matrix * u_model_matrix * a_pos; | |
} | |
); | |
static const char* O_FS = GLSL(120, | |
varying vec2 v_tex; | |
uniform vec3 u_color; | |
uniform sampler2D u_tex; | |
void main() { | |
vec4 texcol = texture2D(u_tex, v_tex); | |
gl_FragColor.a = texcol.a * .5; | |
gl_FragColor.rgb = texcol.rgb * u_color; | |
} | |
); | |
class OilDrawer { | |
public: | |
OilDrawer(Oil& o, Particles& ps); | |
~OilDrawer(); | |
void setup(); | |
void update(); | |
void draw(); | |
GLuint createTexture(int w, int h, int c, unsigned char* pixels); | |
void toggleDebugDraw(); | |
public: | |
bool debug_draw; | |
Oil& oil; | |
Particles& ps; | |
DebugDrawer dd; | |
GLuint gray_tex; | |
GLuint col_tex; | |
Mat4 ortho; | |
GLuint vbo; | |
GLuint vao; | |
GLuint prog; | |
GLuint tex0; | |
GLuint tex1; | |
GLuint tex2; | |
GLuint tex3; | |
GLint u_pm; | |
GLint u_mm; | |
GLint u_color; | |
GLint u_tex; | |
}; | |
inline void OilDrawer::toggleDebugDraw() { | |
debug_draw = !debug_draw; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Particles.h" | |
Particle::Particle(Vec3 pos, float m) | |
:position(pos) | |
{ | |
if(m < 0.001) { | |
mass = 0.0f; | |
inv_mass = 0.0f; | |
} | |
else { | |
mass = m; | |
inv_mass = 1.0f / m; | |
} | |
tex = rx_random(0,4); | |
} | |
Particle::~Particle() { | |
} | |
void Particle::addForce(Vec3 f) { | |
forces += f; | |
} | |
void Particle::update(float dt) { | |
forces *= inv_mass; | |
velocity += (forces * dt); | |
position += (velocity * dt); | |
forces.set(0.0f, 0.0f, 0.0f); | |
velocity *= 0.99; | |
} | |
// --- | |
Particles::Particles(Oil& oil) | |
:oil(oil) | |
{ | |
} | |
Particles::~Particles() { | |
for(int i = 0; i < particles.size(); ++i) { | |
delete particles[i]; | |
} | |
particles.clear(); | |
} | |
void Particles::addParticle(Particle* p) { | |
p->color = oil.getColor(p->position.x, p->position.y); | |
particles.push_back(p); | |
} | |
void Particles::update(float dt) { | |
for(int k = 0; k < 4; ++k) { | |
for(int i = 0; i < particles.size(); ++i) { | |
Particle& p = *particles[i]; | |
Vec2 vec = oil.getVector(p.position.x, p.position.y); | |
Vec3 force = Vec3(vec.x, vec.y, 0); | |
p.addForce(force * 10); | |
particles[i]->update(dt); | |
} | |
} | |
for(int i = 0; i < particles.size(); ++i) { | |
Particle& p = *particles[i]; | |
p.velnorm = p.velocity.getNormalized(); | |
} | |
} | |
void Particles::addForce(Vec3 f) { | |
for(int i = 0; i < particles.size(); ++i) { | |
particles[i]->addForce(f); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef OIL_PARTICLES_H | |
#define OIL_PARTICLES_H | |
#include <vector> | |
#include <roxlu/Roxlu.h> | |
#include "Oil.h" | |
class Particle { | |
public: | |
Particle(Vec3 pos, float mass); | |
~Particle(); | |
void addForce(Vec3 f); | |
void update(float dt); | |
public: | |
Vec3 color; | |
Vec3 position; | |
Vec3 forces; | |
Vec3 velocity; | |
Vec3 velnorm; | |
float mass; | |
float inv_mass; | |
int tex; | |
}; | |
class Particles { | |
public: | |
Particles(Oil& oil); | |
~Particles(); | |
void addParticle(Particle* p); | |
void addForce(Vec3 f); | |
void update(float dt); | |
public: | |
Oil& oil; | |
vector<Particle*> particles; | |
}; | |
#endif | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Simulation.h> | |
Simulation::Simulation() | |
:SimulationBase() | |
,ps(oil) | |
,oil_drawer(oil, ps) | |
{ | |
} | |
void Simulation::setup() { | |
Image img; | |
img.load(rx_to_data_path("asteroids.jpg")); | |
printf("%d, %d, %d\n", img.getWidth(), img.getHeight(), img.getComponents()); | |
oil.setup(img.getWidth(), img.getHeight(), img.getComponents()); | |
oil.setPixels(img.getPixels()); | |
oil.createVectors(); | |
oil_drawer.setup(); | |
glClearColor(0.0, 0.0, 0.0, 0.0); | |
} | |
void Simulation::update() { | |
oil.update(); | |
ps.update(1.0f/60.0f); | |
oil_drawer.update(); | |
} | |
void Simulation::draw() { | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
oil_drawer.draw(); | |
if(oil_drawer.debug_draw) { | |
fps.draw(); | |
} | |
} | |
void Simulation::onMouseUp(int x, int y, int button) { | |
} | |
void Simulation::onMouseDown(int x, int y, int button) { | |
int d = 100; | |
int niter = 250; | |
for(int i = 0; i < niter; ++i) { | |
ps.addParticle(new Particle(Vec3(rx_random(x-d,x+d),rx_random(y-d, y+d),0), rx_random(1.0f, 5.0f))); | |
} | |
} | |
void Simulation::onMouseDrag(int x, int y, int dx, int dy, int button) { | |
} | |
void Simulation::onMouseMove(int x, int y) { | |
} | |
void Simulation::onChar(int ch) { | |
if(ch == 'f') { | |
int niter = 2500; | |
for(int i = 0; i < niter; ++i) { | |
ps.addParticle(new Particle(Vec3(rx_random(0, oil.w),rx_random(0, oil.h),0), rx_random(1.0f, 5.0f))); | |
} | |
} | |
else if(ch == 'd') { | |
oil_drawer.toggleDebugDraw(); | |
} | |
} | |
void Simulation::onKeyDown(int key) { | |
} | |
void Simulation::onKeyUp(int key) { | |
} | |
void Simulation::onWindowClose() { | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <glfw_wrapper/SimulationBase.h> | |
#include "Oil.h" | |
#include "OilDrawer.h" | |
#include "Particles.h" | |
class Simulation : public SimulationBase { | |
public: | |
Simulation(); | |
void setup(); | |
void update(); | |
void draw(); | |
void onMouseDown(int x, int y, int buton); | |
void onMouseUp(int x, int y, int button); | |
void onMouseDrag(int x, int y, int dx, int dy, int button); | |
void onMouseMove(int x, int y); | |
void onChar(int ch); | |
void onKeyDown(int key); | |
void onKeyUp(int key); | |
void onWindowClose(); | |
private: | |
Oil oil; | |
Particles ps; | |
OilDrawer oil_drawer; | |
FPS fps; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment