# HG changeset patch # User John Tsiombikas # Date 1450156553 -7200 # Node ID 8e524989c904488362f606d3e01a12e46a7842ef getting there diff -r 000000000000 -r 8e524989c904 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,4 @@ +\.o$ +\.d$ +\.swp$ +^curvedraw$ diff -r 000000000000 -r 8e524989c904 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,45 @@ +PREFIX = /usr/local + +src = $(wildcard src/*.cc) +obj = $(src:.cc=.o) +dep = $(obj:.o=.d) +bin = curvedraw + +CXXFLAGS = -pedantic -Wall -g +LDFLAGS = $(libgl) -lvmath -ldrawtext -lm + +sys := $(shell uname -s | sed 's/MINGW.*/win32/') + +ifeq ($(sys), Darwin) + libgl = -framework OpenGL -framework GLUT +else ifeq ($(sys), win32) + libgl = -lopengl32 -lglut32 +else + libgl = -lGL -lglut +endif + +$(bin): $(obj) + $(CXX) -o $@ $(obj) $(LDFLAGS) + +-include $(dep) + +%.d: %.cc + @$(CPP) $(CXXFLAGS) $< -MM -MT $(@:.d=.o) >$@ + + +.PHONY: clean +clean: + rm -f $(obj) $(bin) + +.PHONY: cleandep +cleandep: + rm -f $(dep) + +.PHONY: install +install: $(bin) + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp $(bin) $(DESTDIR)$(PREFIX)/bin/$(bin) + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(bin) diff -r 000000000000 -r 8e524989c904 src/app.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app.cc Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include "opengl.h" +#include "app.h" +#include "curve.h" +#include "widgets.h" + +static void draw_grid(float sz, float sep, float alpha = 1.0f); +static void draw_curve(const Curve *curve); +static void on_click(int bn, float u, float v); + +static int win_width, win_height; +static float win_aspect; + +static float view_pan_x, view_pan_y; +static float view_scale = 1.0f; + +static std::vector curves; +static Curve *sel_curve, *new_curve; +static int sel_pidx = -1; + +static Label *weight_label; + + +bool app_init(int argc, char **argv) +{ + glEnable(GL_MULTISAMPLE); + glEnable(GL_CULL_FACE); + return true; +} + +void app_cleanup() +{ + for(size_t i=0; idraw(); + } +} + +static void draw_grid(float sz, float sep, float alpha) +{ + float x = 0.0f; + + glLineWidth(1.0); + glBegin(GL_LINES); + glColor4f(0.6, 0.3, 0.2, alpha); + glVertex2f(-sz, 0); + glVertex2f(sz, 0); + glColor4f(0.2, 0.3, 0.6, alpha); + glVertex2f(0, -sz); + glVertex2f(0, sz); + glColor4f(0.4, 0.4, 0.4, alpha); + while(x < sz) { + x += sep; + glVertex2f(-sz, x); + glVertex2f(sz, x); + glVertex2f(-sz, -x); + glVertex2f(sz, -x); + glVertex2f(x, -sz); + glVertex2f(x, sz); + glVertex2f(-x, -sz); + glVertex2f(-x, sz); + } + glEnd(); +} + +static void draw_curve(const Curve *curve) +{ + int numpt = curve->get_point_count(); + int segm = numpt * 16.0f; + + glBegin(GL_LINE_STRIP); + if(curve == sel_curve) { + glLineWidth(3.0); + glColor3f(0.2, 0.8, 0.3); + } else { + glLineWidth(2.0); + glColor3f(0.8, 0.75, 0.3); + } + for(int i=0; iinterpolate(t); + glVertex2f(v.x, v.y); + } + glEnd(); + glLineWidth(1.0); + + glPointSize(5.0); + glBegin(GL_POINTS); + glColor3f(1.0, 0.5, 0.2); + for(int i=0; iget_point(i); + glVertex2f(pt.x, pt.y); + } + glEnd(); + glPointSize(1.0); +} + +void app_reshape(int x, int y) +{ + win_width = x; + win_height = y; + win_aspect = (float)x / (float)y; + + glViewport(0, 0, x, y); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1); +} + +void app_keyboard(int key, bool pressed) +{ + if(pressed) { + switch(key) { + case 27: + exit(0); + + case 'l': + case 'L': + if(sel_curve) { + sel_curve->set_type(CURVE_LINEAR); + post_redisplay(); + } + break; + + case 'b': + case 'B': + if(sel_curve) { + sel_curve->set_type(CURVE_BSPLINE); + post_redisplay(); + } + break; + + case 'h': + case 'H': + if(sel_curve) { + sel_curve->set_type(CURVE_HERMITE); + post_redisplay(); + } + break; + } + } +} + +static Vector2 pixel_to_uv(int x, int y) +{ + float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0); + float v = 1.0 - 2.0 * (float)y / (float)win_height; + return Vector2(u, v); +} + +static int prev_x, prev_y; +static int click_pos[8][2]; +static unsigned int bnstate; + +#define BNBIT(x) (1 << (x)) + +void app_mouse_button(int bn, bool pressed, int x, int y) +{ + prev_x = x; + prev_y = y; + if(pressed) { + bnstate |= BNBIT(bn); + } else { + bnstate &= ~BNBIT(bn); + } + + if(pressed) { + click_pos[bn][0] = x; + click_pos[bn][1] = y; + } else { + int dx = x - click_pos[bn][0]; + int dy = y - click_pos[bn][1]; + + if(abs(dx) + abs(dy) < 3) { + Vector2 uv = pixel_to_uv(x, y); + on_click(bn, uv.x, uv.y); + } + + if(!(bnstate & BNBIT(2))) { + delete weight_label; + weight_label = 0; + post_redisplay(); + } + } +} + +static void hover(int x, int y) +{ + float thres = pixel_to_uv(5, 0).x - pixel_to_uv(0, 0).x; + + Vector2 uv = pixel_to_uv(x, y); + for(size_t i=0; inearest_point(uv); + if(pidx == -1) continue; + + Vector2 cp = curves[i]->get_point(pidx); + if((cp - uv).length_sq() < thres * thres) { + sel_curve = curves[i]; + sel_pidx = pidx; + return; + } + } + + sel_curve = 0; + sel_pidx = -1; +} + +void app_mouse_motion(int x, int y) +{ + int dx = x - prev_x; + int dy = y - prev_y; + prev_x = x; + prev_y = y; + + if(!dx && !dy) return; + + if(!new_curve && !bnstate) { + hover(x, y); + post_redisplay(); + } + + if(sel_curve && sel_pidx != -1) { + if(bnstate & BNBIT(0)) { + float w = sel_curve->get_weight(sel_pidx); + sel_curve->set_point(sel_pidx, pixel_to_uv(x, y), w); + post_redisplay(); + } + + if(bnstate & BNBIT(2)) { + float w = sel_curve->get_weight(sel_pidx); + w -= dy * 0.1; + if(w < FLT_MIN) w = FLT_MIN; + sel_curve->set_weight(sel_pidx, w); + + if(!weight_label) { + weight_label = new Label; + } + weight_label->set_position(pixel_to_uv(x, y)); + weight_label->set_textf("w=%g", w); + post_redisplay(); + } + } +} + +static void on_click(int bn, float u, float v) +{ + switch(bn) { + case 0: + if(!new_curve) { + new_curve = new Curve; + } + new_curve->add_point(Vector2(u, v)); + post_redisplay(); + break; + + case 2: + curves.push_back(new_curve); + new_curve = 0; + post_redisplay(); + break; + + default: + break; + } +} diff -r 000000000000 -r 8e524989c904 src/app.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app.h Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,15 @@ +#ifndef APP_H_ +#define APP_H_ + +bool app_init(int argc, char **argv); +void app_cleanup(); + +void app_draw(); +void app_reshape(int x, int y); +void app_keyboard(int key, bool pressed); +void app_mouse_button(int bn, bool pressed, int x, int y); +void app_mouse_motion(int x, int y); + +void post_redisplay(); // in main.cc + +#endif // APP_H_ diff -r 000000000000 -r 8e524989c904 src/curve.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/curve.cc Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,144 @@ +#include +#include "curve.h" + +Curve::Curve(CurveType type) +{ + this->type = type; +} + +void Curve::set_type(CurveType type) +{ + this->type = type; +} + +CurveType Curve::get_type() const +{ + return type; +} + +void Curve::add_point(const Vector2 &p, float weight) +{ + cp.push_back(Vector3(p.x, p.y, weight)); +} + +bool Curve::remove_point(int idx) +{ + if(idx < 0 || idx >= (int)cp.size()) { + return false; + } + cp.erase(cp.begin() + idx); + return true; +} + +int Curve::nearest_point(const Vector2 &p) +{ + int res = -1; + float bestsq = FLT_MAX; + + for(size_t i=0; i= (int)cp.size()) { + return false; + } + cp[idx] = Vector3(p.x, p.y, weight); + return true; +} + +bool Curve::set_weight(int idx, float weight) +{ + if(idx < 0 || idx >= (int)cp.size()) { + return false; + } + cp[idx].z = weight; + return true; +} + +Vector2 Curve::interpolate(float t, CurveType type) const +{ + int num_cp = (int)cp.size(); + if(!num_cp) { + return Vector2(0, 0); + } + if(num_cp == 1) { + return Vector2(cp[0].x, cp[0].y); + } + + Vector3 res; + int idx0 = (int)floor(t * (num_cp - 1)); + int idx1 = idx0 + 1; + + float dt = 1.0 / (float)(num_cp - 1); + float t0 = (float)idx0 * dt; + float t1 = (float)idx1 * dt; + + t = (t - t0) / (t1 - t0); + if(t < 0.0) t = 0.0; + if(t > 1.0) t = 1.0; + + if(type == CURVE_LINEAR || num_cp <= 2) { + res = lerp(cp[idx0], cp[idx1], t); + } else { + int idx_prev = idx0 <= 0 ? idx0 : idx0 - 1; + int idx_next = idx1 >= num_cp - 1 ? idx1 : idx1 + 1; + + if(type == CURVE_HERMITE) { + res = catmull_rom_spline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t); + } else { + res = bspline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t); + if(res.z != 0.0f) { + res.x /= res.z; + res.y /= res.z; + } + } + } + + return Vector2(res.x, res.y); +} + +Vector2 Curve::interpolate(float t) const +{ + return interpolate(t, type); +} + +Vector2 Curve::operator ()(float t) const +{ + return interpolate(t); +} + +float Curve::map_param(float x) const +{ + if(x < 0.0f) x = 0.0f; + if(x >= 1.0f) x = 1.0f; + return x * ((float)cp.size() - 1); +} diff -r 000000000000 -r 8e524989c904 src/curve.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/curve.h Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,44 @@ +#ifndef CURVE_H_ +#define CURVE_H_ + +#include +#include + +enum CurveType { + CURVE_LINEAR, + CURVE_HERMITE, + CURVE_BSPLINE +}; + +class Curve { +private: + std::vector cp; + CurveType type; + + float map_param(float x) const; + +public: + Curve(CurveType type = CURVE_HERMITE); + + void set_type(CurveType type); + CurveType get_type() const; + + void add_point(const Vector2 &p, float weight = 1.0f); + bool remove_point(int idx); + + int nearest_point(const Vector2 &p); + + int get_point_count() const; + const Vector3 &get_homo_point(int idx) const; // homogeneous point + Vector2 get_point(int idx) const; + float get_weight(int idx) const; + + bool set_point(int idx, const Vector2 &p, float weight = 1.0f); + bool set_weight(int idx, float weight); + + Vector2 interpolate(float t, CurveType type) const; + Vector2 interpolate(float t) const; + Vector2 operator ()(float t) const; +}; + +#endif // CURVE_H_ diff -r 000000000000 -r 8e524989c904 src/main.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,68 @@ +#include +#include +#ifdef __APPLE__ +#include +#else +#include +#endif +#include "app.h" + +static void display(); +static void keydown(unsigned char key, int x, int y); +static void keyup(unsigned char key, int x, int y); +static void mouse(int bn, int st, int x, int y); + +int main(int argc, char **argv) +{ + glutInit(&argc, argv); + glutInitWindowSize(1280, 720); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_MULTISAMPLE); + glutCreateWindow("Curve Draw"); + + glutDisplayFunc(display); + glutReshapeFunc(app_reshape); + glutKeyboardFunc(keydown); + glutKeyboardUpFunc(keyup); + glutMouseFunc(mouse); + glutMotionFunc(app_mouse_motion); + glutPassiveMotionFunc(app_mouse_motion); + + if(!app_init(argc, argv)) { + return 1; + } + atexit(app_cleanup); + + glutMainLoop(); + return 0; +} + +void post_redisplay() +{ + glutPostRedisplay(); +} + +static void display() +{ + app_draw(); + glutSwapBuffers(); +} + +static void reshape(int x, int y) +{ + app_reshape(x, y); +} + +static void keydown(unsigned char key, int x, int y) +{ + app_keyboard(key, true); +} + +static void keyup(unsigned char key, int x, int y) +{ + app_keyboard(key, false); +} + +static void mouse(int bn, int st, int x, int y) +{ + app_mouse_button(bn - GLUT_LEFT_BUTTON, st == GLUT_DOWN, x, y); +} diff -r 000000000000 -r 8e524989c904 src/opengl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/opengl.h Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,14 @@ +#ifndef OPENGL_H_ +#define OPENGL_H_ + +#ifdef WIN32 +#include +#endif + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#endif // OPENGL_H_ diff -r 000000000000 -r 8e524989c904 src/widgets.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets.cc Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include "opengl.h" +#include "widgets.h" + +static dtx_font *font; + +static void init_font() +{ + if(font) return; + + if(!(font = dtx_open_font("data/droid_sans.ttf", 16))) { + fprintf(stderr, "failed to load font\n"); + abort(); + } + dtx_use_font(font, 16); +} + +Widget::Widget() +{ + text = 0; +} + +Widget::~Widget() +{ + delete [] text; +} + +void Widget::set_position(const Vector2 &p) +{ + pos = p; +} + +const Vector2 &Widget::get_position() const +{ + return pos; +} + +void Widget::set_text(const char *str) +{ + text = new char[strlen(str) + 1]; + strcpy(text, str); +} + +void Widget::set_textf(const char *str, ...) +{ + va_list ap; + int sz = strlen(str) * 4; + char *buf = (char*)alloca(sz); + + va_start(ap, str); + vsnprintf(buf, sz - 1, text, ap); + va_end(ap); + + set_text(buf); +} + +const char *Widget::get_text() const +{ + return text; +} + +// ---- label ---- + +void Label::draw() const +{ + init_font(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(pos.x, pos.y, 0); + glScalef(0.1, 0.1, 1); + + dtx_string(text); + dtx_flush(); + + glPopMatrix(); +} diff -r 000000000000 -r 8e524989c904 src/widgets.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/widgets.h Tue Dec 15 07:15:53 2015 +0200 @@ -0,0 +1,30 @@ +#ifndef WIDGETS_H_ +#define WIDGETS_H_ + +#include + +class Widget { +protected: + Vector2 pos; + char *text; + +public: + Widget(); + virtual ~Widget(); + + virtual void set_position(const Vector2 &p); + virtual const Vector2 &get_position() const; + + virtual void set_text(const char *str); + virtual void set_textf(const char *str, ...); + virtual const char *get_text() const; + + virtual void draw() const = 0; +}; + +class Label : public Widget { +public: + virtual void draw() const; +}; + +#endif // WIDGETS_H_