Skip to content

Instantly share code, notes, and snippets.

@10se1ucgo
Created November 23, 2018 23:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 10se1ucgo/bdc24417a67e3354e49bae5a7d7cb881 to your computer and use it in GitHub Desktop.
Save 10se1ucgo/bdc24417a67e3354e49bae5a7d7cb881 to your computer and use it in GitHub Desktop.
c++ grapher
#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;
}
}}
#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