Last active
July 14, 2016 22:57
-
-
Save sansumbrella/c747bb75ac51c8d5d9448f13c379d6a3 to your computer and use it in GitHub Desktop.
Tiny Cinder C++ Scene Graph
This file contains hidden or 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 "cinder/app/App.h" | |
#include "cinder/app/RendererGl.h" | |
#include "cinder/gl/gl.h" | |
#include "cinder/Utilities.h" | |
#include "cinder/Log.h" | |
#include "cinder/Json.h" | |
using namespace ci; | |
using namespace ci::app; | |
using namespace std; | |
class GraphRenderer { | |
public: | |
virtual ~GraphRenderer() = default; | |
virtual void setOrigin(const ci::vec2 &pos) = 0; | |
virtual void translateOrigin(const ci::vec2 &pos) = 0; | |
virtual void addRectangle(const ci::Rectf &rect) = 0; | |
virtual void addText(const std::string &text) = 0; | |
}; | |
class ImmediateRenderer : public GraphRenderer { | |
public: | |
void setOrigin(const ci::vec2 &pos) override; | |
void translateOrigin(const ci::vec2 &pos) override; | |
void addRectangle(const ci::Rectf &rect) override; | |
void addText(const std::string &text) override; | |
private: | |
ci::gl::TextureFontRef _font = gl::TextureFont::create(Font("Helvetica", 12.0f)); | |
ci::vec2 _origin; | |
}; | |
void ImmediateRenderer::translateOrigin(const ci::vec2 &pos) { | |
_origin += pos; | |
} | |
void ImmediateRenderer::setOrigin(const ci::vec2 &pos) { | |
_origin = pos; | |
} | |
void ImmediateRenderer::addRectangle(const ci::Rectf &rect) { | |
gl::ScopedModelMatrix mat; | |
gl::translate(_origin); | |
gl::drawSolidRect(rect); | |
} | |
void ImmediateRenderer::addText(const std::string &text) { | |
gl::ScopedModelMatrix mat; | |
gl::translate(_origin); | |
_font->drawString(text, vec2(0)); | |
} | |
#pragma mark - Node | |
using NodeRef = std::shared_ptr<class Node>; | |
class Node { | |
public: | |
Node() = default; | |
virtual ~Node() = default; | |
explicit Node(const ci::vec2 &position); | |
void draw(GraphRenderer &renderer); | |
virtual void drawCustom(GraphRenderer &renderer) {} | |
void appendChild(const NodeRef &child); | |
void clearChildren() { _children.clear(); } | |
ci::vec2& position() { return _position; } | |
private: | |
std::vector<NodeRef> _children; | |
ci::vec2 _position; | |
}; | |
Node::Node(const ci::vec2 &position) | |
: _position(position) | |
{} | |
void Node::appendChild(const NodeRef &child) | |
{ | |
_children.push_back(child); | |
} | |
void Node::draw(GraphRenderer &renderer) | |
{ | |
renderer.translateOrigin(_position); | |
drawCustom(renderer); | |
for (auto &c : _children) { | |
c->draw(renderer); | |
} | |
renderer.translateOrigin(-_position); | |
} | |
#pragma mark - TextNode | |
class TextNode : public Node { | |
public: | |
using Node::Node; // inherit constructors | |
TextNode(const std::string &text, const ci::vec2 &position) | |
: Node(position), | |
_text(text) | |
{} | |
void drawCustom(GraphRenderer &renderer) override; | |
private: | |
std::string _text; | |
}; | |
void TextNode::drawCustom(GraphRenderer &renderer) { | |
renderer.addText(_text); | |
} | |
#pragma mark - RectangleNode | |
class RectangleNode : public Node { | |
public: | |
RectangleNode(const ci::vec2 &position, const ci::vec2 &size) | |
: Node(position), | |
_rectangle(0, -size.y / 2.0f, size.x, size.y / 2.0f) | |
{} | |
void drawCustom(GraphRenderer &renderer) override; | |
private: | |
ci::Rectf _rectangle; | |
}; | |
void RectangleNode::drawCustom(GraphRenderer &renderer) { | |
renderer.addRectangle(_rectangle); | |
} | |
#pragma mark - FlexiNode | |
class FlexiNode : public Node { | |
public: | |
explicit FlexiNode(const std::function<void ()> &draw_fn) | |
: _draw_fn(draw_fn) | |
{} | |
void drawCustom(GraphRenderer &renderer) override; | |
private: | |
std::function<void ()> _draw_fn; | |
}; | |
void FlexiNode::drawCustom(GraphRenderer &renderer) { | |
if (_draw_fn) { | |
_draw_fn(); | |
} | |
} | |
#pragma mark - App | |
class CinderNoodlingApp : public App { | |
public: | |
~CinderNoodlingApp(); | |
void setup() override; | |
void mouseDown( MouseEvent event ) override; | |
void update() override; | |
void draw() override; | |
void graphData(const std::string &data); | |
private: | |
std::future<string> _future; | |
Node _root; | |
ImmediateRenderer _renderer; | |
}; | |
CinderNoodlingApp::~CinderNoodlingApp() | |
{ | |
} | |
void CinderNoodlingApp::setup() | |
{ | |
auto load_fixer_data = [] { | |
return loadString(loadUrl("https://api.fixer.io/latest")); | |
}; | |
Timer timer(true); | |
// load_fixer_data(); | |
_future = std::async(std::launch::async, load_fixer_data); | |
timer.stop(); | |
cout << timer.getSeconds() * 1000 << "ms" << endl; | |
for (auto i = 0; i < 10; i += 1) { | |
auto t = i / (10 - 1.0f); | |
auto y = mix(0.0f, 200.0f, t); | |
_root.appendChild(make_shared<Node>(vec2(50.0f, y))); | |
} | |
_root.position() = vec2(getWindowSize()) * vec2(0.1f); | |
} | |
void CinderNoodlingApp::mouseDown( MouseEvent event ) | |
{ | |
} | |
void CinderNoodlingApp::update() | |
{ | |
if (_future.valid()) { | |
auto status = _future.wait_for(chrono::nanoseconds(0)); | |
if (status == future_status::ready) { | |
graphData(_future.get()); | |
} | |
} | |
} | |
void CinderNoodlingApp::graphData(const std::string &data) | |
{ | |
_root.clearChildren(); | |
JsonTree json(data); | |
auto rates = json.getChild("rates"); | |
auto pos = vec2(50.0f, 0.0f); | |
auto lowest = std::numeric_limits<float>::max(); | |
auto highest = std::numeric_limits<float>::lowest(); | |
for (auto &rate : rates) { | |
auto value = rate.getValue<float>(); | |
lowest = min(value, lowest); | |
highest = max(value, highest); | |
} | |
for (auto &rate : rates) { | |
auto r = logf(rate.getValue<float>()); | |
auto width = lmap(r, logf(lowest), logf(highest), 10.0f, 100.0f); | |
auto size = vec2(width, 12.0f); | |
auto child = make_shared<Node>(pos); | |
auto text = make_shared<TextNode>(rate.getKey(), vec2(0, 0)); | |
auto bar = make_shared<RectangleNode>(vec2(30, 0), size); | |
child->appendChild(text); | |
child->appendChild(bar); | |
_root.appendChild(child); | |
pos.y += 20.0f; | |
} | |
auto node = make_shared<FlexiNode>([] { | |
gl::ScopedDepth depth(true); | |
gl::drawColorCube(vec3(0), vec3(50.0f)); | |
}); | |
_root.appendChild(node); | |
} | |
void CinderNoodlingApp::draw() | |
{ | |
gl::clear( Color( 0, 0, 0 ) ); | |
gl::setMatricesWindowPersp(getWindowSize()); | |
Timer timer(true); | |
_root.draw(_renderer); | |
timer.stop(); | |
console() << "Draw time: " << timer.getSeconds() * 1000 << "ms" << endl; | |
} | |
CINDER_APP( CinderNoodlingApp, RendererGl ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Rudimentary scene graph (parent->child flow only) with separate renderer (so nodes don't need to know about openGL/asset management).