Created
November 23, 2018 23:35
-
-
Save 10se1ucgo/bdc24417a67e3354e49bae5a7d7cb881 to your computer and use it in GitHub Desktop.
c++ grapher
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 "scene/grapher.h" | |
#include "game_client.h" | |
#include "draw/sprite.h" | |
#define exprtk_disable_comments | |
#define exprtk_disable_break_continue | |
#define exprtk_disable_sc_andor | |
#define exprtk_disable_return_statement | |
#define exprtk_disable_string_capabilities | |
#define exprtk_disable_enhanced_features | |
#include "exprtk/exprtk.hpp" | |
namespace ace { namespace scene { | |
Grapher::Grapher(GameClient &client): | |
Scene(client), | |
projection_2d(glm::ortho<float>(0.f, this->client.width(), this->client.height(), 0.0f)), | |
graph_sprite(this->generate_graph()), | |
graph(graph_sprite) { | |
this->table.add_variable("x", this->expr_x); | |
this->table.add_pi(); | |
this->table.add_constant("e", 2.71828182845904523536028747135266249775724709369996); | |
this->expr.register_symbol_table(this->table); | |
this->graph.position = { 0, 0 }; | |
this->graph.alignment = draw::Align::TOP_LEFT; | |
this->graph.scale = this->client.size() / glm::vec2(this->graph_sprite->w(), this->graph_sprite->h()); | |
} | |
void Grapher::start() { | |
} | |
void Grapher::update(double dt) { | |
Scene::update(dt); | |
if (this->client.text_input_active()) { | |
auto fnt = this->client.fonts.get("Vera.ttf", 24); | |
auto &bfr(this->client.input_buffer); | |
fnt->draw(fmt::format("Enter Equation:\n{}_{}", bfr.substr(0, this->client.input_cursor), bfr.substr(this->client.input_cursor)), glm::vec2(30, 30), glm::vec3(0), { 1, 1 }, draw::Align::TOP_LEFT); | |
} | |
} | |
void Grapher::draw() { | |
glClearColor(0, 0, 0, 1); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
// 2d | |
glEnable(GL_BLEND); | |
glDisable(GL_CULL_FACE); | |
glDisable(GL_DEPTH_TEST); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
this->graph.draw(); | |
this->client.shaders->sprite.bind(); | |
this->client.shaders->sprite.uniform("projection", this->projection_2d); | |
this->client.sprites.flush(this->client.shaders->sprite); | |
this->client.shaders->text.bind(); | |
this->client.fonts.flush(this->projection_2d, this->client.shaders->text); | |
} | |
void Grapher::on_key(SDL_Scancode scancode, int modifiers, bool pressed) { | |
if (!pressed) return; | |
if (scancode == SDL_SCANCODE_RETURN) { | |
this->clear_graph(); | |
this->client.toggle_text_input(); | |
} | |
} | |
void Grapher::on_text_finished(bool cancelled) { | |
if (cancelled) return; | |
this->parser.compile(this->client.input_buffer, this->expr); | |
this->draw_graph(); | |
} | |
void Grapher::clear_graph() { | |
this->graph_sprite->tex.fill_pixels(255); | |
} | |
void Grapher::draw_graph() { | |
double draw_time = 0.75; | |
// draw x and y axis | |
// TODO: these are pretty repetitive and easily wrapped in a function | |
this->client.tasks.schedule(0, [this, x = 0, y = this->graph_y_to_tex(0), time = draw_time / this->graph_sprite->w()](util::Task &t) mutable { | |
// x axis | |
if (y < 0 || y >= this->graph_sprite->h()) { | |
return; | |
} | |
this->set_pixel({x, y}, draw::color32::black()); | |
if (++x < this->graph_sprite->w()) { | |
t.repeat_in(time); | |
} | |
}).after([this, x = this->graph_x_to_tex(0), y = 0, time = draw_time / this->graph_sprite->h()](util::Task &t) mutable { | |
// y axis | |
if (x < 0 || x >= this->graph_sprite->w()) { | |
return; | |
} | |
this->set_pixel({ x, y }, draw::color32::black()); | |
if (++y < this->graph_sprite->h()) { | |
t.repeat_in(time); | |
} | |
}); | |
// draw ticks on x and y axes | |
// TODO: these are pretty repetitive and easily wrapped in a function | |
this->client.tasks.schedule(0, [this, x = this->minx, y = this->graph_y_to_tex(0), time = draw_time / (this->maxx - this->minx)](util::Task &t) mutable { | |
if (y < 0 || y >= this->graph_sprite->h()) { | |
return; | |
} | |
auto gx = this->graph_x_to_tex(x); | |
this->set_pixel({ gx, y + 1 }, draw::color32::black()); | |
this->set_pixel({ gx, y - 1 }, draw::color32::black()); | |
if (x++ < this->maxx) { | |
t.repeat_in(time); | |
} | |
}).after([this, x = this->graph_x_to_tex(0), y = this->maxy, time = draw_time / (this->maxy - this->miny)](util::Task &t) mutable { | |
if (x < 0 || x >= this->graph_sprite->h()) { | |
return; | |
} | |
auto gy = this->graph_y_to_tex(y); | |
this->set_pixel({ x + 1, gy }, draw::color32::black()); | |
this->set_pixel({ x - 1, gy }, draw::color32::black()); | |
if (y-- > this->miny) { | |
t.repeat_in(time); | |
} | |
}).after([this](util::Task &t) { | |
this->draw_equation(); | |
}); | |
} | |
void Grapher::draw_equation() { | |
this->client.tasks.schedule(0, [this, x = 0](util::Task &t) mutable { | |
int prev = this->eval_tex(x - 1); | |
int y = this->eval_tex(x); | |
int next = this->eval_tex(x + 1); | |
if (y != -1) { | |
// Draw up to the midpoint for both the last and next values, prevents skips in the graph with large slopes. | |
// TODO: wrap these in a function | |
if (prev != -1) { | |
int mid = (prev + y) / 2; | |
for (int i = std::min(mid, y); i <= std::max(mid, y); i++) { | |
this->set_pixel({ x, i }, draw::color32::blue()); | |
} | |
} | |
if (next != -1) { | |
int mid = (y + next) / 2; | |
for (int i = std::min(mid, y); i <= std::max(mid, y); i++) { | |
this->set_pixel({ x, i }, draw::color32::blue()); | |
} | |
} | |
this->set_pixel({ x, y }, draw::color32::blue()); | |
} | |
if (++x < this->graph_sprite->w()) { | |
t.repeat_in(1.0 / this->graph_sprite->w()); | |
} | |
}); | |
} | |
draw::SpriteGroup *Grapher::generate_graph() const { | |
gl::experimental::texture2d tex(this->client.width(), this->client.height()); | |
auto graph = this->client.sprites.get("graph", std::move(tex)); | |
graph->set_antialias(false); | |
return graph; | |
} | |
}} |
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
#pragma once | |
#include "scene/scene.h" | |
#include "draw/sprite.h" | |
// exprtk breaks my compiler with its object size without these lol | |
#define exprtk_disable_comments | |
#define exprtk_disable_break_continue | |
#define exprtk_disable_sc_andor | |
#define exprtk_disable_return_statement | |
#define exprtk_disable_string_capabilities | |
#define exprtk_disable_enhanced_features | |
#include "exprtk/exprtk.hpp" | |
namespace ace { namespace scene { | |
class Grapher final : public Scene { | |
public: | |
Grapher(GameClient &client); | |
void start() override; | |
void update(double dt) override; | |
void draw() override; | |
void on_key(SDL_Scancode scancode, int modifiers, bool pressed) override; | |
void on_text_finished(bool cancelled) override; | |
void clear_graph(); | |
void draw_graph(); | |
void draw_equation(); | |
double tex_x_to_graph(int x) const { | |
return double(x) * ((this->maxx - this->minx) / this->graph_sprite->w()) + this->minx; | |
// return (double(x) / this->graph->w()) * (this->rx * 2) - this->rx; | |
} | |
double tex_y_to_graph(int y) const { | |
return double(this->graph_sprite->h() - y) * ((this->maxy - this->miny) / this->graph_sprite->h()) + this->miny; | |
} | |
// pixel coordinates -> graph coordinates. | |
glm::dvec2 tex_to_graph(glm::ivec2 tex) const { | |
return { tex_x_to_graph(tex.x), tex_y_to_graph(tex.y) }; | |
} | |
int graph_x_to_tex(double x) const { | |
return round(x - this->minx) * (this->graph_sprite->w() / (this->maxx - this->minx)); | |
} | |
int graph_y_to_tex(double y) const { | |
return round(this->graph_sprite->h() - (y - this->miny) * (this->graph_sprite->h() / (this->maxy - this->miny))); | |
} | |
// graph coordinates -> pixel coordinates. Will be out-of-bounds if x is not between [minx, maxx] and y not between [miny, maxy] | |
glm::ivec2 graph_to_tex(glm::dvec2 graph) const { | |
return { graph_x_to_tex(graph.x), graph_y_to_tex(graph.y) }; | |
} | |
// Evaluates and returns y = f(x) | |
double eval(double x) { | |
// double old_x = this->expr_x; | |
this->expr_x = x; | |
double y = this->expr.value(); | |
// this->expr_x = old_x; | |
return y; | |
} | |
// Evaluates and returns y = f(x), where x and y are both in pixel coordinates. | |
// If y is not a finite number, -1 is returned instead. | |
int eval_tex(int x) { | |
double y = this->eval(this->tex_x_to_graph(x)); | |
if(!std::isfinite(y)) { | |
return -1; | |
} | |
return this->graph_y_to_tex(y); | |
} | |
// Sets a pixel on the graph texture, optionally ignoring out-of-bounds exceptions. | |
void set_pixel(glm::ivec2 coord, glm::u8vec3 color, bool ignore_oob = true) { | |
if(!ignore_oob || (coord.y < this->graph_sprite->h() && coord.y >= 0) && (coord.x < this->graph_sprite->w() && coord.x >= 0)) { | |
this->graph_sprite->tex.set_pixel(coord.x, coord.y, glm::u8vec4(color, 255)); | |
} | |
} | |
// Create and add graph sprite batch to sprite batch map | |
draw::SpriteGroup *generate_graph() const; | |
glm::mat4 projection_2d; | |
draw::SpriteGroup *graph_sprite; | |
draw::Sprite graph; | |
// expression evaluator | |
// todo rewrite my own, smaller and simpler recursive descent parser expression evaluator. | |
exprtk::expression<double> expr; | |
exprtk::symbol_table<double> table; | |
exprtk::parser<double> parser; | |
double expr_x = 0; | |
// graph window | |
double minx = -10, maxx = 10, miny = -10, maxy = 10; | |
}; | |
}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment