RedisGraphの気になったのをもうちょい試す

はじめに

crssnky.hatenablog.jp

前回やり残したことをやります。
というのも、エッジに任意の属性が付けられるというもの。
RDFでは、許可されたものしか付けられないため試してみました。

本編

準備

RedisGraphのQuickStart通り、

docker run -p 6379:6379 -it --rm redislabs/redisgraph

を立ち上げておきます。

グラフ構築

GRAPH.QUERY imasparql "CREATE (:Idol {name:'如月千早'})-[:callTable {called:'風花さん'}]->(:Idol {name:'豊川風花'})"
GRAPH.QUERY imasparql "CREATE (:Idol {name:'如月千早'})-[:callTable {called:'春香'}]->(:Idol {name:'天海春香'})"

f:id:crssnky:20200812154945p:plain

千早さんの呼称表から"豊川風花""天海春香"を登録しています。それぞれのエッジには"どう呼んでいるか"をcalledで登録しています。

検索

GRAPH.QUERY imasparql "MATCH (caller:Idol)-[c:callTable]->(callee:Idol) WHERE caller.name='如月千早' RETURN caller.name,callee.name, c.called"

呼称表の中から千早さんのものを検索し、呼ぶ側・呼ばれる側・どう呼んでいるかを表示します。

caller.name
callee.name
c.called
如月千早
豊川風花
風花さん
如月千早
天海春香
春香

エッジの属性が取れました。

おわりに

ノードだろうがエッジだろうが好き勝手に属性を付けて良いみたいですね。
im@sparqlは全てをノードで表しているので、ある程度属性として持たせられればグラフは簡素にできそうです。(するかどうかは別)

RedisGraphでアイマスのGraphを作ってみる

はじめに

RedisGraphどころかRedisすら触ったことなかったけど、QuickStartのクエリを見様見真似でやったのでそこまで詳しくないです。
でも、RedisGraphの日本語記事が無かったんで試しに使ってみた。みたいな記事です。

RedisGraphとは

oss.redislabs.com

公式サイトにあるPrimary featuresを見ていきましょう。

  • Property Graph Modelに基づいてる。
    たぶんこんな感じの、ノードとノードの間に意味を持つエッジ(ブランチ)があるグラフのことでしょう。
    f:id:crssnky:20200809172004p:plain
  • ノードとエッジは属性を持つこともできる。
    f:id:crssnky:20200809172556p:plain im@sparqlのように、あらゆる事象をエッジとノードで表すのではなく、
    f:id:crssnky:20200809172646p:plain このように、ノードにある程度の情報を持たせても良いらしい。
    im@sparql(RDF)では名前の文字列を与えるにしてもエッジを用意していたので、これは結構助かるかも?でも、属性にするか、別ノードにするかはルールが要るかもですね。
  • ノードに名前が付けられる。
    付けられます。
  • エッジにはtypeを付けられる。
    f:id:crssnky:20200809173456p:plain エッジにはいろいろ付けられます。
  • グラフはスパース隣接行列で表現される。
    スパース行列は、疎行列。隣接行列はグラフのエッジを行列で表すアレです。
    使ってみたレベルの今は内部構造は気にしません。
  • クエリ言語はCypherです。
    Graph query language(GQL) standardへの取っ掛かりとなる直感的な言語らしいです。
    まぁ確かに、
CREATE (:Idol {name:'如月千早'})-[:BelongTo]->(:Production {name:'765プロダクション'})

となり、上の図を形まで同じように記号で書き起こした感じ。
書くのは煩雑かもだけど、初学者でも読み取りやすいですね。

  • Cypherクエリは線形代数式に変換される。
    使ってみたレベルの今は内部構造は気にしません。

グラフを書いてみた

github.com

im@sparqlにアイドルとユニットの所属情報を問い合わせて、結果をredisgraphに書き込むやつです。Rustで書いてます。
これ何も難しいことしてないです...結果

{
  "units": { "type": "literal" , "value": "ARCANA,ルナ,ハニーサウンド,Engage!,TORICO,ミッシングムーン,you-i,765ProAllstars,エターナルハーモニー,SLEEPING BEAUTY,765MillionStars,花鳥風月" } ,
  "idol": { "type": "literal" , "value": "如月千早" }
}

を分解して

"CREATE (:Idol {{name:'[アイドル名]'}})-[:memberOf]->(:Unit {{name: '[ユニット名]'}})"

上のやつにこんな感じで入れてるだけです。↓

  for element in json {
    let units = element.units.value;
    let idol = element.idol.value;
    for unit in units.split(separator) {
      let create_query = format!(
        "CREATE (:Idol {{name:'{}'}})-[:memberOf]->(:Unit {{name: '{}'}})",
        idol,
        percent_decode(unit.as_bytes()) // ユニット名はエンコードされてるかも
          .decode_utf8()
          .unwrap()
          .to_string()
          .replace("'", "\\'")
      );
      result = graph.mutate(&create_query);
      if !result.is_ok() {
        return result;
      }
    }
    println!("{}", idol);
  }

グラフに問い合わせてみた

redisgraphのQuickStart通り、redis-cliコマンドを使って問い合わせます。
ここで注意なのが、--rawオプションを付けなければ日本語が文字化けするということです。
というわけでクエリはこちら

GRAPH.QUERY imasparql "MATCH (i:Idol)-[:memberOf]->(u:Unit) WHERE i.name = '如月千早' RETURN i.name, u.name"

クエリは説明しなくてもなんとなく分かるよね?(逆に言えばなんとなく分かる程度にしか自分も説明できない...) 結果はこちら

i.name
u.name
如月千早
ARCANA
如月千早
ルナ
如月千早
ハニーサウンド
如月千早
Engage!
如月千早
TORICO
如月千早
ミッシングムーン
如月千早
you-i
如月千早
765ProAllstars
如月千早
エターナルハーモニー
如月千早
SLEEPING BEAUTY
如月千早
765MillionStars
如月千早
花鳥風月
Query internal execution time: 1.649300 milliseconds

わぁ、羅列!
--rawさえ付けなければ

127.0.0.1:6379> GRAPH.QUERY imasparql "MATCH (i:Idol)-[:memberOf]->(u:Unit) WHERE i.name = '黛冬優子' RETURN i.name, u.name"
1) 1) "i.name"
   2) "u.name"
2) 1) 1) "\xe9\xbb\x9b\xe5\x86\xac\xe5\x84\xaa\xe5\xad\x90"
      2) "Straylight"
3) 1) "Query internal execution time: 0.962100 milliseconds"

こんな感じで、塊ごとに分けてもらえるのですが....
まぁ、コマンドラインでやる人なんていないでしょうし、ソースコード上で使うクライアント次第じゃないでしょうか。

おわりに

使ってみた程度としてはこんな感じです。
今回はCREATEMATCHをメインに使いましたが、他にも命令はあるみたいなのでもっと便利になるとは思います。

おまけ

GitHubのリポジトリを見て気付いたかもしれませんが、redisgraphにim@sparqlのアイドルとユニットの所属情報を書き込んでくれるDocker Imageを配布しています。

hub.docker.com

コンテナ生成時にim@sparqlへ問い合わせるので、生成しなおせば新鮮なグラフになります。
ユニット情報だけでなく、いろいろ追加していこうかと思っているので、im@sparqlでは速度に不満がある方はぜひ使ってみてください。

UE4.24時代のimposter製作方法

まえがき

おそらくの話ですが、昔の頭良い人は考えました。
「遠景オブジェクトはどうせ見える角度が変わらないんだから、板ポリで表現すれば良いじゃん。」
そんなわけで、以下のように負荷軽減策として使われるようになりました。
f:id:crssnky:20200509220833p:plain

左側のメイン部分のより奥にある、宙に浮く岩

f:id:crssnky:20200509220850p:plain

こんな感じで板ポリで表現されています。

以上、Epic Zen Gardenの紹介でした。
めでたしめでたし。

Imposterとは

日本語に直訳すると「詐欺師」らしいですが、UE4では遠景オブジェクトを表現する手法の一つとして存在しています。

docs.unrealengine.com

「まえがき」では、見える角度が変わらないオブジェクトに対して板ポリで表現するということを紹介しましたが、UE4のImposterは一味違います。カメラの位置に対して、パラパラ漫画のように板ポリに表示する画像を変えることで、ポリゴン数を抑えつつもどのような向きにも対応できるオブジェクトが表現できます。つまり、こう↓

向きが断続的なことと、真上真下は若干怪しい感じでしたが、遠景だったり大量に出ている等の負荷削減のための場所に適用すれば気付かれにくいでしょう。
本家本元であるEpicGamesはFortnite内で、遠景の木などにしようしてるらしいですね。

Imposterの作り方

基本的には先程貼った

3D Imposter スプライトをレンダリングする | Unreal Engine Documentation

や、ヒストリアさんの

[UE4] Imposterを使ってみる(多視点対応のビルボード)|株式会社ヒストリア

を参考にすれば大丈夫です。
しかし、とある一点について注意が必要で、UE4.24では上手くいきません。

DecalMaskが無い問題

どちらの手順でも、CaptureするG Bufferの選択でDecal Maskを選択しています。
これは当たり前の話で、板ポリにテクスチャを貼るので余白をMaskするために出力します。 しかし、出力できません。他のものは出力されますが、DecalMaskは出力されません。

forums.unrealengine.com

なんでだろうと思い、探しましたらありました。
どうやら2017年の2月末のバージョンのではもう、DecalMaskは無いようです。
出力できないからSceneDepthを使ってねとあります。

んじゃ使うか~と思いきや、SceneDepthもありません。 f:id:crssnky:20200509225712p:plain

Scene Depth World Unitsはあるが、目的のものでは無い

答え

おまじないコマンドke * rendertexturesを、
r.BufferVisualizationOverviewTargets BaseColor,SceneDepthHighResShot N(Nは高精細スクショの倍数)にしましょう。

解説

おまじないコマンドke * rendertexturesを簡単に見ていきます。
keというのはKismetEventの略称でBlueprintのイベントを呼び出すコンソールコマンドです。
どのBlueprintに対してかを次に入力するのですが、*とすることで全てのBlueprintに対して送ります。 その次がBlueprintのイベント名でrendertexturesというイベントを呼び出します。
参考
[UE4] コンソールコマンドの使い方&よく使うコマンド一覧|株式会社ヒストリア

rendertexturesイベントを持つのは、GameModeに定められているRenderToTexture_Pawnです。
Event Graphを見ると、rendertexturesイベントは以下の2つを実行しているだけだと分かります。 f:id:crssnky:20200509231842p:plain このGeneratorというのは、Levelに配置して設定を行うRenderToTexture_LevelBPのことです。

BufferCommand

r.BufferVisualizationOverviewTargets BaseColor,SceneDepth側ですね。
RenderToTexture_LevelBPSetBufferCommand関数を見ると、チェックを付けるとr.BufferVisualizationOverviewTargetsの後ろにG Bufferを追加していく様子が分かります。そして、この追加される文字列にはSceneDepthが無いことも分かります。
つまり後ろに撮りたいG Bufferを繋げれば良いので、r.BufferVisualizationOverviewTargets BaseColor,SceneDepthとなります。
Normalも欲しければr.BufferVisualizationOverviewTargets BaseColor,SceneDepth, WorldNormalとなるでしょう。

ShotCommand

HighResShot N側です。同じくSetBufferCommand関数にあります。
なんやかんやで数値を決めていますが、つまり自分の欲しい解像度の倍数を入れれば良いです。
参考
Taking Screenshots | Unreal Engine Documentation

おわりに

自動化されている部分がバージョンアップに追いついていないので、手作業でやる方法を示しました。
EngineContentを変えちゃっても良いかな。っていう人は、DecalMaskをチェックしたときにSceneDepthが追加されるようにすれば良いのではないでしょうか。
まぁこれで、ようやくUE4.24でもImposterが書き出せるということで。

え?!UE4.25ってもうリリースされちゃったの?!?!?!?!

(Rust + Rocket + Tera) × プロデュース = im@sparql_details

github.com

タイトル通り、Rust言語とそのCrateであるRocket、テンプレートエンジンのTeraを使ってim@sparqlのdetailsのアレ(特に名前がないゆえの表現)を置き換えました。

アレの例→Kisaragi_Chihaya detail

Rust

Rust言語については特に大丈夫ですかね。
コンパイル時のチェックが厳しいでお馴染みのシステムプログラミング言語ですね。
C++に足を浸けてる人間としては、今後学びたい言語でした。    ja.wikipedia.org

Rocket

rocket.rs

Rustで使えるWebフレームワークです。初学者にも満たないので、Webフレームワークって何ができればWebフレームワークなのか分かりませんが、ルーティングとかフォーム入力とか当たり前の機能はあるみたいです。
なんだこの頭悪い説明は...

Tera

tera.netlify.app

Rocketで使えるテンプレートエンジンの一つです。

Inspired by Jinja2 and Django templates

ですって。

やったこと

最初にも例出したIchikawa_Hinana detailの実装を置き換えました。
今まではNode.jsとexpressだったのですが、Rustを勉強するために差し替えた次第です。
なお、見た目は全く変わってないのでユーザーには特に益はありません。

内容

Rustでも非常に簡単に書けました。126行のうち、半分くらいはJSONのデシリアライズなのでめっちゃ少ないです。

JSONシリアライズ部(展開可)

#[derive(Debug, Deserialize, Serialize)]
struct N {
  r#type: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct O {
  r#type: String,
  #[serde(default)]
  datatype: String,
  #[serde(default, rename = "xml:lang")]
  xml_lang: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct Bindings {
  n: N,
  o: O,
}
#[derive(Debug, Deserialize)]
struct Results {
  bindings: Vec<Bindings>,
}
#[derive(Debug, Deserialize)]
struct Head {
  vars: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct Response {
  head: Head,
  results: Results,
}
#[derive(Serialize)]
struct MessageContent {
  title: String,
  num: usize,
  json: Vec<Bindings>,
}

im@sparqlのJSONの結果を見たことある人は納得の構造体群ですね。
ちなみにクエリは

PREFIX schema: <http://schema.org/>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>
SELECT * WHERE {
  { imas:{} ?n ?o;}
}order by (?n)

です。{}には適切な主語が入ります。

処理部(展開可)

#[get("/<subject>")]
fn get_data(subject: String) -> Template {
  const FRAGMENT: &AsciiSet = &CONTROLS;
  let encoded_subject = utf8_percent_encode(&subject, FRAGMENT).to_string();
  let quety = format!("PREFIX schema: <http://schema.org/>PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>SELECT * WHERE {{ imas:{} ?n ?o;}}order by (?n)", encoded_subject);
  let encoded_query = form_urlencoded::Serializer::new(String::new())
    .append_pair("output", "json")
    .append_pair("force-accept", "text/plain")
    .append_pair("query", &quety)
    .finish();
  let base_url = format!(
    "https://sparql.crssnky.xyz/spql/imas/query?{}",
    encoded_query
  );
  let res = ureq::get(&base_url).call();

  if res.ok() {
    let json_str = res.into_string().unwrap();
    let res_json: Response = serde_json::from_str(&json_str).unwrap();
    let mut json = res_json.results.bindings;
    let json_num = json.len();
    if json_num > 0 {
      for data in &mut json {
        match &*(data.n.value) {
          "http://schema.org/memberOf" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          "http://schema.org/owns" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          _ => (),
        }
      }
      let content = MessageContent {
        title: subject,
        num: json_num,
        json: json,
      };
      return Template::render("detail", &content);
    }
  }
  let content = MessageContent {
    title: subject,
    num: 0,
    json: vec![],
  };
  Template::render("error", &content)
}

キモなのは#[get("/<subject>")]fn get_data(subject: String)でしょうか。いわゆるルーティングの文字列を引数として取得しています。これをすることで、URLhttps://sparql.crssnky.xyz/imasrdf/RDFs/detail/の後ろに主語が続くと適切に処理されます。
引数として取得した文字列は、そのままクエリの主語の部分に連結されます。そしてクエリを送って結果をデシリアライズします。
im@sparql特有の処理として、for文の部分があります。一部の主語は日本語をそのままURLエンコードしたものをつっこんでいるので、表示には適しません。なので、テンプレートエンジンに送る前にデコードしてあげています。
それ以外はそのままのJSONをテンプレートに渡して描画しています。

こんな感じで、簡単にWebフレームワークを使ったRustを書けます。みなさんも安全性に守られたコーディングしませんか。

DECLARE_DYNAMIC_MULTICAST_DELEGATEとC++

ちょっとしたお話

みんな大好き動的マルチキャストデリゲート

マルチキャスト デリゲート | Unreal Engine ドキュメント

動的デリゲート | Unreal Engine ドキュメント

個人的な主観では、C++でグイッと処理したあとにBPに結果を渡すときによく使うんですけど、C++とBP両方にイベントを発行したい時もあります。
BPであればBindノードを使うことでイベントを登録できます。
C++であれば、動的デリゲートの説明にあるようにAddDynamicを使って関数を登録します。

でも、VisualStudioのIntellisenseにはAddAddUniqueくらいしか出てきません。

なんでやろなぁ

動的デリゲートの説明をよく見たらヘルパーマクロって書いてありました。
そうです、マクロなんです。
Delegate.h#define AddDynamic(....となってました。

というわけで備忘録

週刊IM@Study Vol.4 と アイマスハッカソン2019の続き

こちらはアイドルマスター Advent Calendar 2019の10日目の記事です。


12/5のアイマスハッカソン2019にご参加いただいたみなさんありがとうございました。
そして、関東・関西のスタッフさん、お疲れ様でした!

10時から行われていた本イベントですが、18時からの成果発表・プロデューサーLT大会の様子はこちらでご確認いただけます。


アイマスハッカソン2019 in 東京&関西 成果発表・プロデューサーLT

そこでは週刊IM@Studyの寄稿内容の概要について発表しました。僕が発表してる部分はこちら↓

https://youtu.be/SMEKvULOnvc?t=4055

発表している対象についてはこちらを見てもらったほうが早いかもしれません。

UE4アイマス楽曲をさらに盛り上げてけ☆」と題しまして、UE4で楽曲のビジュアライズ的なことをしています。
こんな感じでUE4の簡単な説明をし、操作方法を学びつつ詳しく編集を説明しています。

f:id:crssnky:20191209232153p:plain

今回はUE4のエディタ機能のみを使用しているため、C++を1字も書きません。その代わりにBlueprintを引いてもらいます。世の中の評価としても、エンジニア以外にも簡単に使えるというものということで普及しており、フローチャートとだいたい同じと考えても良いと思います。

f:id:crssnky:20191209232929p:plain

もちろん、C++に慣れた方には少し煩雑かもしれませんし、C++と比べて関数呼び出しが死ぬほど遅いといったデメリットもあります。しかし、小さなプロジェクトであったり、大きなプロジェクトでも用法を正しく使えば開発イテレーションを大幅に上げることのできる素晴らしいツールです。

そんなUE4のハンズオンや他3本の記事、ミニコラム3本が詰まった100ページに及ぶ週刊IM@Study Vol.4は、第2回技術書同人誌博覧会で頒布されるので、ぜひお手にとってみてください。Vol.3の時にあった電子決済に引き続き対応している他、電子版も頒布されるので、100ページの本は...という方もぜひどうぞ!

f:id:crssnky:20191208214932p:plain

gishohaku.dev


さてお話は変わり。
Youtubeコメでもありましたが、今回はスライドソフトにUE4を使用しています。
UE4上でスライドを表示することで、シームレスにデモへ移行することができました。
スライド自体は前日に作っており、当日はそのシステムを作ってたので今回はそれについてもお話しようかと思います。

Step 0 スライドを作る

なんの変哲もないです。僕はPowerPointで作りました。

アイマスハッカソン2019in関西 - Speaker Deck

作り終えたら、各ページを画像出力してください。パワポにはそういう機能あるし、Webサービス使わなくて良き。

f:id:crssnky:20191210001222p:plain

Step 1 UE4に取り込む

テクスチャとして取り込みます。なんとなくそんな気分なので、Mipmapは作りません。 f:id:crssnky:20191210230824p:plain

Step 2 Materialを作る

テクスチャパラメータのノードを一つ持つMaterialを一つ作ります。
ハッキリ見えてほしいので、Unlitです。 f:id:crssnky:20191210231117p:plain

Step 3 スライドBPを作る

ConstructionScriptで全部仕上げます。
InstanceEditableなInt型変数からどのTextureObjectを使うかを分岐し、Step2で作ったMaterialからMaterialInstanceDynamics(MID)を作ってそこにTextureObjectを投入します。

f:id:crssnky:20191210231502p:plain

f:id:crssnky:20191210231534p:plain

ConstructionScriptで済ますことで、Levelに配置した後でもぐりぐり~って変えられます。

Step 4 スライドLevelを作る

逆に言えばConstructionScriptで作っちゃったので、Levelが開始してしまったら変更ができません。せっかくなので、LevelStreamingを試してみたいし、良い感じにしましょう。
まず、テキトーにスライドBPが一つだけあるレベルを量産します。各BPは順番に四角の各辺をなぞるように配置します。

  • Slide1はx,y,z=1000,0,0・Pitch,Yaw,Roll=0, 0, 0
  • Slide2はx,y,z=0,1000,0・Pitch,Yaw,Roll=0, 90, 0
  • Slide3はx,y,z=-1000,0,0・Pitch,Yaw,Roll=0, 180, 0
  • Slide4はx,y,z=0,-1000,0・Pitch,Yaw,Roll=0, 270, 0

みたいな感じ

f:id:crssnky:20191210233303p:plain

スライドの数だけ...スライドの数だけ....多い....

f:id:crssnky:20191210233133p:plain

Step 5 LevelStreamingを整える

"HackathonPersistant"的な名前のパーシスタントレベルを作り、Levelsウィンドウで下にぶら下げます。
今回は、スライド以外にもデモ用のレベル(Test)もぶら下げています。

f:id:crssnky:20191210234320p:plain

Step 6 LevelBPを書く

次へキー・戻るキーを設定し、それのイベントに紐づけて次のスライドを持つレベルをその場で読み込むBPを記述します。Timelineノードを使えば、良い感じにカメラを回すことも出来ます。

f:id:crssnky:20191210234605p:plain

f:id:crssnky:20191210234705p:plain

Step 7 試す

LevelStreamingによって、必要なスライドのみが違和感なく読み込まれてることが確認できます。とってもメモリに優しいですね!!


こんな感じでアイマスハッカソン2019ではスライドを流していました。これを使い回せば、UE4で作ったものを簡単に見せられますね!

お詫び

WebRemoteControlの発表を書きたかったのですが、本日公開されたUE4.24でも難しいみたいです...

UE4とRTTI ~MSVC(Windows) V.S. clang(Mac, Linux)~

RTTIとMSVC, clangの環境の違いには気をつけろの情報共有である。

背景

im@sparqlをUE4で触りたいなと思ったら、JSONが使えるようにしたかった。
そこで、ちょうどよいものを見つけたので導入した。

usagi.hatenablog.jp

こいつ(cereal)は最高だ、JSONを構造体のように扱える。
いや、実際に構造体を定義して、そこにJSONを流し込んでるから当たり前だ。

これに関しては、技術書典で頒布した週刊IM@Studyの2019/04号に記載している。
当然リポジトリもある。

github.com

問題

昨年末のpaypay祭りの時に思い切って、ノートPCをWin機からMac機に切り替えた。
当然開発環境は変わり、Xcodeなのでclangとなる。
これが問題。
おうちのデスクトップPCはWin機なため、VS2019のMSVCで作業している。
こいつで動作確認をしているのだが、clangではどうもビルドが通らない。

具体的には、RTTI関連でエラーが出る。
MSVCはRTTIはデフォルトでONなので気にすることは無かったが、cerealではtypeinfoを用いる。
clangではデフォルトでRTTIはOFFなので、エラー文で叱られてしまった。
コンパイラに従いBuild.csでbUseRTTI=true;を指定してビルドすると見知らぬリンカエラーに遭遇した。
似た人↓
https://answers.unrealengine.com/questions/745636/undefined-symbols-for-architecture-arm64-1.html

理由

理由↓
https://answers.unrealengine.com/questions/871773/view.html

意訳すると

  • LinuxではRTTIと非RTTIを混ぜちゃダメ
  • モジュールごとにRTTIの有無は分けられるから、必要な部分に切り出して
  • その時に、Engineのクラスを混ぜちゃダメ
  • それを"ブリッジ"するラッパーを書くのが良い
  • "OpenEXRWrapper"プラグインを参考にしてね

質問者も「Windowsでは動くから、変に気を取られたよ」(意訳)と、自分と同じ心境を語ってる。

現在

モジュール分割をしている。
でも上手くいかない。
C++なんもわからん
何がわからないのか分からない

RTTIとMSVC, clangの環境の違いには気をつけろの情報共有でした。