2017-06-29 3 views
0

私はC++でアニメーションライブラリを構築しています。図書館には、場面のモデリングとレンダリングのためのシステムが含まれています。システムの要件はのモデリングとレンダリングのC++でカプセル化されているが拡張可能なアニメーションライブラリを構築する

  1. 分離されています。シーンの状態に関する情報は、シーンをレンダリングする手順とは別に保存する必要があります。
  2. 拡張可能なモデリングとレンダリング。ライブラリ自体にnodeクラスが定義されている場合、ライブラリのユーザはnodeの機能を拡張する新しいタイプcustom_nodeを定義できなければなりません(おそらく継承を通じて、しかし他の手段を介して)。ユーザーはcustom_nodeをレンダリングするためのカスタムプロシージャーを指定できるはずです。そうすることで、ユーザーは何とかライブラリーに既に存在するレンダリング手順を利用できるはずです。ユーザは、ライブラリノードをレンダリングするための新しいプロシージャを定義することもできるはずです。 追加:ユーザーは、全体のレンダリングシステムを定義し、シーンをレンダリングするために使用するレンダリングシステムを選択できる必要があります。たとえば、ライブラリにフォトリアリスティックレンダリングシステムが含まれているとしますが、ユーザはベアボーンの概略レンダリングシステムでシーンをレンダリングする必要があるとします。ユーザは、アニメーションライブラリがアニメーションループ(フレームのレンダリング、シーンの更新、次のフレームのレンダリングなど)中にフードの下でアニメーションライブラリが使用する共通のレンダリングインターフェイスを使用して、そのようなレンダラを実装できる必要があります。
  3. ライブラリーのカプセル化。ライブラリの機能をカスタムnodeに拡張してレンダリングするには、ライブラリの基礎となるコードを編集する必要はありません。

失敗したアプローチ:シーンのモデルとしてnodeのツリーを使用します。サブクラスnodeを使用して新しいノードタイプを作成します。実行時まではノードの子の型が分からないため、ノードの子はvector<std::shared_ptr<node>>に格納されます。 トップレベルrendererクラスを定義し、特定の種類のレンダリングを提供するサブクラスrendererも定義します。

class image; 

class node { 
    virtual image render(renderer &r) {return r.render(*this);} 
    std::vector<std::shared_ptr<node>> children; 
    std::weak_ptr<node> parent; 
    // ... 
} 

class renderer { 
    image render(node &n) {/*rendering code */} 
// ... 
} 

シーンをレンダリングするには、レンダラを定義

renderer r{}; 

やお気に入りのトラバーサル方法でノードのツリーをトラバース。あなたは、各std::shared_ptr<node>nが発生したとして、このアプローチは、モデリングとレンダリングを分離

n->render(r); 

呼び出して、それが拡張することができます。我々はcustom_nodeをレンダリングするカスタム手段を提供しようとするまでcustom_nodeを作成するには、図書館の利用者は、単にnode

class custom_node : public node { 
    virtual image render(renderer &r) override {return r.render(*this)} 
} 

をサブクラスこのアプローチは、正常に動作します。それ自体で、これは動作しません

class custom_renderer : public renderer { 
    image render(custom_node &n) {/*custom rendering code*/} 
} 

:そうするために、我々は、サブクラス化rendererrender方法をオーバーロードしてみてください。考えてみましょう:

renderer &r = custom_renderer{}; 
std::shared_ptr<node> n = std::make_shared<custom_node>{}; 
n->render(r); // calls renderer::render(node &) 

をcustom_renderer ::レンダリング(custom_node & n)は、必要に応じて、我々は、元のレンダラークラスに仮想過負荷を追加する必要が呼び出すために:

class renderer { 
    image render(node &n) {/*rendering code */} 
    virtual image render(custom_node &n) = 0; 
} 

残念ながら、この破棄をライブラリクラスの1つを編集したので、ライブラリのカプセル化。

どのようにすれば、3つの要件すべてを満たすシステムを設計できますか?

答えて

0

私自身の解決策、Yakkによって提案されたタイプ消去法の変形。問題とこの特定のアプローチの詳細は、hereを参照してください。

struct image{}; 

struct renderable_concept { 
    virtual image render() const = 0; 
}; 

template <class WRAPPED, class RENDERER> 
struct renderable_model : public renderable_concept { 
    WRAPPED *w; 
    RENDERER r; 
    virtual image render() const final override { 
    return r.render(*w); 
    } 
    renderable_model(WRAPPED *w_, RENDERER r_) : w(w_), r(r_) {} 
}; 

struct node { 
    template <class WRAPPED, class RENDERER> 
    node(WRAPPED *w_, RENDERER r_) : 
    p_renderable(new renderable_model<WRAPPED,RENDERER>(w_,r_)) {} 

    template <class RENDERER> 
    node(RENDERER r_) : node(this,r_) {} 

    image render() {return p_renderable->render();} 
    vector<shared_ptr<node>> children; 
    unique_ptr<renderable_concept> p_renderable; 
}; 

struct text_node : public node { 
    template<class RENDERER> 
    text_node(RENDERER r) : node(this,r) {} 

    string val; 
}; 

struct shape_node : public node { 
    template<class RENDERER> 
    shape_node(RENDERER r) : node(this,r) {} 
}; 

struct color_renderer { 
    image render(node &) const {/*implementation*/}; 
    image render(text_node &) const {/*implementation*/}; 
    image render(shape_node &) const {/*implementation*/}; 
}; 

struct grayscale_renderer { 
    image render(node &) const {/*implementation*/}; 
    image render(text_node &) const {/*implementation*/}; 
    image render(shape_node &) const {/*implementation*/}; 
}; 
0

消去をタイプします。ライブラリは、render(some_data)関数を提供します。

いくつかの種類のノードから始めます。プリミティブとは、プリミティブを描画するノードのことです。

リストノードには子があり、レンダー(list_node)はその内容を描画します。

generic_nodeには、レンダリング(?)オーバーロードを持つものがすべて格納されます。これは、レンダリング(?)操作を消去します。 render(generic_node)を呼び出すと、含まれているデータに対して型消去された操作が呼び出されます。

list_nodeには、generic_nodeのベクトルが含まれています。

新しいレンダリングタイプを追加するには、単純に新しいタイプを定義し、オーバーロードレンダリング(new_type)してから、それをgeneric_nodeに格納します。ここで

は原始的な実装です:

struct render_target { 
    // stuff about the thing we are rendering on 
}; 
struct renderable_concept { 
    virtual ~renderable_concept() {} 
    virtual void render_on(render_target*) const = 0; 
}; 
template<class T> 
void render(render_target*, T const&) = delete; // by default, nothing renders 

struct emplace_tag {}; 
template<class T> 
struct renderable_model : renderable_concept { 
    T t; 
    template<class...Us> 
    renderable_model(emplace_tag, Us&&...us): 
    t{std::forward<Us>(us)...} 
    {} 
    void render_on(render_target* target) const final override { 
    render(target, t); 
    } 
}; 
template<class T> 
struct emplace_as {}; 
struct generic_node { 
    friend void render(render_target* target, generic_node const& node) { 
    if (!node.pImpl) return; 
    node.pImpl->render_on(target); 
    } 
    template<class T, class...Us> 
    generic_node(emplace_as<T>, Us&&... us): 
    pImpl(std::make_shared<renderable_model<T>>(emplace_tag{}, std::forward<Us>(us)...)) 
    {} 
    generic_node() = default; 
    generic_node(generic_node&&)=default; 
    generic_node(generic_node const&)=default; 
    generic_node& operator=(generic_node&&)=default; 
    generic_node& operator=(generic_node const&)=default; 
private: 
    std::shared_ptr<renderable_concept> pImpl; 
}; 

、リストノードを作成する方法。

struct list_node { 
    std::vector<generic_node> nodes; 
    friend void render(render_target* target, list_node const& self) { 
    for (auto&& node:self.nodes) 
     render(target, node); 
    } 
    list_node(std::vector<generic_node> ns):nodes(std::move(ns)) {} 
    list_node() = default; 
    list_node(list_node&&)=default; 
    list_node& operator=(list_node&&)=default; 
}; 

template<class T, class...Args> 
generic_node make_node(Args&&... args) { 
    return {emplace_as<T>{}, std::forward<Args>(args)...}; 
} 
template<class T> 
generic_node make_node(T&& t) { 
    return {emplace_as<std::decay_t<T>>{}, std::forward<T>(t) }; 
} 

レンダリング時にhello worldを印刷するノードについてはどうでしょうか?

struct printing_node { 
    std::string message; 
    friend void render(render_target* target, printing_node const& self) { 
    std::cout << self.message; 
    } 
}; 

テストコード:

auto list = make_node(list_node{{ 
    make_node(printing_node{{"hello"}}), 
    make_node(printing_node{{"world"}}) 
}}); 
render_target target; 
render(&target, list); 

Live example

汎用ノードは、コピー時にほとんど動作しない不変の共有ポインタベースの値型です。

+0

この方法でカスタムレンダリングを処理するにはどうすればよいですか?標準レンダリングシステムと大胆なレンダリングシステムがあるとします。私たちは次のようなものを望んでいます: render(standard、printing_node {{"hello"}}); // "hello"を印刷します レンダリング(太字、print_node {{"hello"}}); // "HELLO"を印刷 カスタムの大文字のコードはどこに行きますか? – Chad

関連する問題