最終更新:ID:szzaOOe0DA 2021年10月12日(火) 11:09:44履歴
SDKやゲーム用DLLの開発にむけて、ソースコードを解析しましょう。
(SDKというより.anieを他のプログラムで再生するためのもの)
ソースコードは2017/4/29のものを使い、ファイルの読み込み部分、アニメーション再生の部分を中心に解析していきます。
やはり分析の間違いが見つかるので、発見次第直していきます…
Anime Effectsのソースコードは、
1.OpenGL
2.Qt
3.QtのOpenGL
の機能をふんだんに使っています。
目標:自作ゲームなどで.anie(またはそれを変換したデータ)を使えるようにする
さらに進んだ目標:AnimeEffectsの解析結果を参考にしてもらい、
ポリゴンベースの2Dアニメーションソフトの開発者を増やす(特にフリーソフトなどで)
Todo:
・AnimeEffectsのアニメーション再生の仕組みを理解する
・独自シェーダーの解析
(SDKというより.anieを他のプログラムで再生するためのもの)
ソースコードは2017/4/29のものを使い、ファイルの読み込み部分、アニメーション再生の部分を中心に解析していきます。
やはり分析の間違いが見つかるので、発見次第直していきます…
Anime Effectsのソースコードは、
1.OpenGL
2.Qt
3.QtのOpenGL
の機能をふんだんに使っています。
目標:自作ゲームなどで.anie(またはそれを変換したデータ)を使えるようにする
さらに進んだ目標:AnimeEffectsの解析結果を参考にしてもらい、
ポリゴンベースの2Dアニメーションソフトの開発者を増やす(特にフリーソフトなどで)
Todo:
・AnimeEffectsのアニメーション再生の仕組みを理解する
・独自シェーダーの解析
ファイルに出てくる順番 | バイトサイズ | 説明 |
1. | 8 | シグネーチャ("ObjTree_"など文字列でもある) |
2. | 8 | 開始ブロック中身サイズ(次のファイルオフセット+このデータ=次のブロック) |
ログを吐かれているだけで、ファイルから読み取っている形跡はないため、ファイル中においてあるデータではない |
以降のデータについて
「:」の前のアルファベット-数字の組み合わせは、元のソースコードに関係なく、ここでの分析の都合上付けたデータのIDです。
次のデータ(A-何たら)は、.anieファイルの先頭部分です。
ファイルオフセット(16進数) | バイトサイズ(10進数) | 説明 |
0 | 6 | シグネーチャ AnimeEffects用ファイルを表す |
6 | 2 | エンディアンチェック 1バイト目が0xff,2バイト目が0x00でなければfalse(多分0x00ffなら「リトルエンディアン」で、そうでなければエラーで終わるようになってる) |
8 | 4 | 符号なし4バイト整数 メジャーバージョン |
0x0c | 4 | 4:符号なし4バイト整数 マイナーバージョン |
0x10 | 16 | 空き領域 |
ファイルオフセット(16進数) | バイトサイズ(10進数) | 説明 |
0x20 | 4 | テキストタイプでシグネーチャ"GLBL" |
0x24 | 4 | 符号なし4バイト整数 中身が64でなければエラーでおわるようになってる lengthという一時変数に入れられている |
0x28 | 4 | 横幅,符号なし整数 |
0x2c | 4 | 縦幅,符号なし整数 |
0x30 | 4 | 最大フレーム数,符号なし整数 |
0x34 | 4 | FPS(1秒ごとのフレーム数),符号なし整数 |
0x38 | 4 | ループ設定があるかどうか,符号なし整数 |
0x3c | 44 | 空き領域 |
0x68 | 可変 | B-1リソースホルダ、C-1オブジェクトツリーの順へ続く |
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"Resource" |
2. | 符号付4バイト整数 | トップノードの数 |
トップノードの数だけ.anieファイル上に「3.」「4.」が連続している
3. | 文字列(可変サイズ、4バイト境界) | ファイルパス |
4. | B-2リソースノード | |
5. | エンドブロック |
リソースとは「=画像データの事」とみても良さそうです。
.psdファイルや、.pngファイルなどを読み込んだ後のデータの事で、
.anieでは画像本体も圧縮して中に保持するようです。
比較的初歩的な圧縮ですが、元の.psdファイルより.anieの方がサイズが小さくなったりすることもあります。
データの行先:
core/ProjcetにあるProjectクラスが
ResourceHolder mResourceHolder;を持ちます。
そこに読み込んだデータが入ります。
ProjectクラスはAnimeEffectsで「ファイルを開く」とかの時に作成されていると思います。
トップノードの数だけ
ImageTree型データ(参照元:core/ResourceHolder.h)を確保し、
そこに「3.」「4.」のデータが入る
「3.ファイルパス」はImageTree構造体のQString filePath;に入る
「4.リソースノードデータ」はimg::ResourceNode* topNode;に入る
そのImageTree型データはアドレスが
ResourceHolderクラス(参照元:core/ResourceHolder.h)が持つ
std::list<ImageTree> mImageTrees;
という連結リストの末尾に入れられる(ファイルに出てきた順に連結リストに並ぶはず?)
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"ResNode_" |
2. | 文字列(4バイト境界) | ID |
3. | 符号付き4バイト整数 | 子供の数 |
4. | 符号付き4バイト整数 | バインドID |
5. | 符号なし4バイト整数 | レイヤーかどうか |
6. | 符号付4バイト整数×4 | 長方形位置データ(左、上、横幅、縦幅の順) |
7. | 1バイト×4 | 文字列互換形式でブレンドID(レイヤーの合成方法) |
8. | 符号なし4バイト整数 | 圧縮形式 0=圧縮なし(画像なしの場合に使われるようです),1以上=圧縮 |
9. | 符号なし4バイト整数 | 画像横幅 |
10. | 符号なし4バイト整数 | 画像縦幅 |
11. | 符号なし8バイト整数 | 中身データのバイトサイズ(圧縮されている場合(現バージョンでは中身データがある場合圧縮されている)、圧縮された状態でのサイズ) |
チャンネルの順番不明で、次の「12.」「13.」が連続したデータが4チャンネル分ファイル上に連続しています。合わせて横1ライン分の画像データです。それが縦位置順に並んでいます。
12. | 符号なし4バイト整数 | ARGBどれか1チャンネルの横1ラインのバイトサイズ |
13. | 可変サイズ(「12.」のサイズ) | ARGBどれか1チャンネルの横1ラインの中身データ |
14. | エンドブロック | |
15. | 可変 | 「3.」の子供の数だけ、子階層を表す「B-2:リソースノード」データが続く |
補足:ファイル上のデータの並び:
「ノード」とは:
.psdファイルのフォルダ、レイヤーを反映した単位でデータを扱うため、ツリー構造でデータを扱ったりします。
その際のデータ単位などを「ノード」と言っているようです。
リソースノードはツリー構造で、ファイル上での並び方は「兄弟は後回し、子階層優先」順で、ファイルに出てくる順番=通常のなぞり方の順番 になっていると思います。
ロードするプログラム上は、
B-2:リソースノードを読み込む関数があり、
「15.子階層のリソースノード」の部分で自分を再帰呼び出ししています。
データの行先:
リソースノードのデータ
はResourceNodeクラス(参照元:img/ResouceNode)
にあり、それがさらに
ResourceHandleクラス(参照元:img/ResourceHandle)
を持ちます。
変数名は
ResourceHandle mHandle;リソースハンドルはさらに
ResourceDataクラス(参照元:img/ResourceData)
を持ちます。
データの入り方は
std::shared_ptr<ResourceData> mData;ただし、AnimeEffectsのソースはクラスで演算子オーバーライド
(「->」とか「ポインタ*」の意味を勝手に上書き変更して使っている)
を使う場合があるので、
データの取得の仕方はかなりひねくれているので注意です。
さらにResourceDataクラスは
img::Bufferクラス(参照元:img/Buffer)を持ち、
画像の解凍後のデータなどはココに入ります。
img::Buffer mBuffer;という名前で入っています。
「2.リソースID」はResourceDataクラスの
QString mIdentifier;
に入れられます。
「4.バインドID」は、
DeserializerクラスのmIDSolver(util::IDSolverクラス)
に、自分のResourceNodeクラスのアドレスとセットで保存されます。
「5.レイヤーかどうか」は、
ResourceDataクラスの bool mIsLayer;に入れられています。
「6.長方形位置データ」は、
左上の左位置、左上の上位置を取り、
ResourceDataクラスの QPoint mPos;に入ります。
「7.文字列互換4バイトブレンドID」は、
img::getBlendModeFromQuadIdメソッドで
enum型のIDに変換されています。
参照元はimg/BlendMode.hで
enum BlendMode
{
BlendMode_Normal, BlendMode_Darken, BlendMode_Multiply, BlendMode_ColorBurn, BlendMode_LinearBurn, BlendMode_Lighten, BlendMode_Screen, BlendMode_ColorDodge, BlendMode_LinearDodge, // Equal to Add BlendMode_Overlay, BlendMode_SoftLight, BlendMode_HardLight, BlendMode_VividLight, BlendMode_LinearLight, BlendMode_PinLight, BlendMode_HardMix, BlendMode_Difference, BlendMode_Exclusion, BlendMode_Subtract, BlendMode_Divide,
BlendMode_TERM};
psdのレイヤー合成方法などを元にしていると思います。
enum型のブレンドIDは
ResourceDataのBlendMode mBlendMode;
にセットされます。
ResourceDataクラスが持つBufferクラスに以下のデータが入ります。
mFormat = Format_RGBA8(固定)
mBlock = 解凍した中身データ
mWidth = 「6.長方形位置データ」の横幅
mHeight = 「6.長方形位置データ」の縦幅
|解凍後のデータはARGBで1ピクセル=4バイト形式
解凍後のデータサイズは単純に画像縦幅×横幅×4バイトで良い
データ中身の構造
ARGB4つのチャンネルの1つ(1ピクセル1バイト)で、
横1ライン分のバイトサイズ、続いて横1ライン分の中身データ
が並んでいて、それが4チャンネル分連続している
横1ライン分のデータ=
Aチャンネル横1ラインのバイトサイズ
Aチャンネル横1ライン分
Rチャンネル横1ラインのバイトサイズ
Rチャンネル横1ライン分
Gチャンネル横1ラインのバイトサイズ
Gチャンネル横1ライン分
Bチャンネル横1ラインのバイトサイズ
Bチャンネル横1ライン分
横1ライン分のデータが縦幅分並んでいる
1つのチャンネルの横1ライン分のデータは、
一般的なPackBits法というので圧縮されています
解凍部分のコードが置いてあるファイル名もutil/PackBitsです
(1チャンネルの)横1ラインのデータは、
ヘッダ(1バイト)+中身データ(1バイト×可変個)の連続で成り立っています。
ヘッダは符号付1バイト整数値で見て、
0以上の値なら「どれだけ同じバイト値が続かないか=圧縮されていないか」です。
ヘッダを+1した数が、続く中身データのサイズです。
ヘッダがマイナスの値なら「どれだけ同じバイト値が連続しているか」
を表すデータです。ヘッダを+にして、+1した数が、連続させる数です。
続く1バイトデータが連続させる中身の値です。
解凍後のデータサイズは単純に画像縦幅×横幅×4バイトで良い
データ中身の構造
ARGB4つのチャンネルの1つ(1ピクセル1バイト)で、
横1ライン分のバイトサイズ、続いて横1ライン分の中身データ
が並んでいて、それが4チャンネル分連続している
横1ライン分のデータ=
Aチャンネル横1ラインのバイトサイズ
Aチャンネル横1ライン分
Rチャンネル横1ラインのバイトサイズ
Rチャンネル横1ライン分
Gチャンネル横1ラインのバイトサイズ
Gチャンネル横1ライン分
Bチャンネル横1ラインのバイトサイズ
Bチャンネル横1ライン分
横1ライン分のデータが縦幅分並んでいる
1つのチャンネルの横1ライン分のデータは、
一般的なPackBits法というので圧縮されています
解凍部分のコードが置いてあるファイル名もutil/PackBitsです
(1チャンネルの)横1ラインのデータは、
ヘッダ(1バイト)+中身データ(1バイト×可変個)の連続で成り立っています。
ヘッダは符号付1バイト整数値で見て、
0以上の値なら「どれだけ同じバイト値が続かないか=圧縮されていないか」です。
ヘッダを+1した数が、続く中身データのサイズです。
ヘッダがマイナスの値なら「どれだけ同じバイト値が連続しているか」
を表すデータです。ヘッダを+にして、+1した数が、連続させる数です。
続く1バイトデータが連続させる中身の値です。
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"ObjTree_" |
2. | 符号付4バイト整数 | トップノードの数(0か1のみ) |
3. | C-2:オブジェクトノードのデータ | |
4. | エンドブロック |
オブジェクトとは:
リソースが画像データなのに対して、オブジェクトはポリゴンやアニメーション用のデータの抽象的な総称です。
データの行先:
core/ProjcetにあるProjectクラスが
ObjectTree mObjectTree;を持ちます。
そこに読み込んだデータが入ります。
「3.C-2:オブジェクトノードのデータ」はトップノードデータで、
ObjectTreeクラス(参照元:core/ObjectTree.h)の
QScopedPointer<ObjectNode> mTopNode;に入ります。
リソースホルダと違い、トップノードはなし(データが空)か、1つのみかどちらかです。
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"ObjNode_" |
2. | 符号付4バイト整数 | オブジェクトノードのタイプ(0=レイヤー,1=フォルダ) |
3. | 符号付4バイト整数 | 子供の数 |
4. | C-3:フォルダーノードまたはC-4:レイヤーノードのデータ | |
5. | エンドブロック | |
6. | 「3.子供の数」分、子階層のC-2:オブジェクトノードデータの並び |
補足:
構成としてはC-2:オブジェクトノードは
レイヤーかフォルダかによって、「4.」の部分が変化するというデータです。
.anieファイル的には別のブロックとして扱われているので1段階複雑に見えます。
データの行先:
ObjectNodeクラスはutil::TreeNodeBaseを継承しており、
ObjectTreeクラスのmTopNodeから
template <class tTreeNode>
class TreeNodeBase
typedef tTreeNode TreeNodeType; typedef TreeChildren<tTreeNode> Children;
Children mChildren;という形で保持されます。
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"FolderNd" |
2. | 文字列(可変サイズ4バイト境界) | フォルダ名 |
3. | 符号なし4バイト整数 | 表示中かどうか |
4. | 符号なし4バイト整数 | IsSlimmedDownフラグ |
5. | 符号付4バイト整数 | 位置データ(横位置、縦位置、横幅、縦幅の順) |
6. | 符号なし4バイト整数 | クリッピングしているかどうか |
7. | C-5:タイムラインデータ | |
8. | エンドブロック |
データの行先:
「2.フォルダ名」はFolderNodeクラス(参照元:core/FolderNode.h)の
QString mName;に入ります。
「3.表示中かどうか」はFolderNodeクラスの
bool mIsVisible;に入ります。
「4.IsSlimmedDownフラグ」はFolderNodeクラスの
bool mIsSlimmedDown;に入ります。
「5.位置データ」はFolderNodeクラスの
QRect mInitialRect;に入ります。
「6.クリッピングしているかどうか」はFolderNodeクラスの
bool mIsClipped;に入ります。
「7.C-5:タイムラインデータ」はFolderNodeクラスの
TimeLine mTimeLine;に入ります。
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"LayerNd_" |
2. | 文字列(可変サイズ4バイト境界) | レイヤー名 |
3. | 符号なし4バイト整数 | 表示中かどうか |
4. | 符号なし4バイト整数 | IsSlimmedDownフラグ |
5. | 符号付4バイト整数 | 位置データ(横位置、縦位置、横幅、縦幅の順) |
6. | 符号なし4バイト整数 | クリッピングしているかどうか |
7. | C-5:タイムラインデータ | |
8. | エンドブロック |
基本的にフォルダーノードもレイヤーノードも.anieファイル上は同じデータ構成です。
レイヤーノードの場合、読み込み時にグリッドシェーダーとクリッピングシェーダーのコンパイルもしています。
(グリッドシェーダーの頂点シェーダー+フラグメントシェーダーはdata/GridDrawing.glslex
クリッピング部分はdata/ClipperWriting.glslex)
「2.フォルダ名」はLayerNodeクラス(参照元:core/LayerNode.h)の
QString mName;に入ります。
「3.表示中かどうか」はLayerNodeクラスの
bool mIsVisible;に入ります。
「4.IsSlimmedDownフラグ」はLayerNodeクラスの
bool mIsSlimmedDown;に入ります。
「5.位置データ」はLayerNodeクラスの
QRect mInitialRect;に入ります。
「6.クリッピングしているかどうか」はLayerNodeクラスの
bool mIsClipped;に入ります。
「7.C-5:タイムラインデータ」はLayerNodeクラスの
TimeLine mTimeLine;に入ります。
ファイルに出てくる順番 | データタイプ | 説明 |
1. | 開始ブロック16バイト | シグネーチャ"TimeLine" |
2. | 符号付4バイト整数 | タイムマップの数 |
3. | 開始ブロック16バイト | シグネーチャ"TimeMap_" |
4. | 文字列最大8バイト4バイト境界(0終端部分を含むサイズで4バイト境界) | タイムキーのタイプ名 ("FFD"と"Opa"のみ4バイト、それ以外は8バイトファイルサイズを占領している) |
5. | 符号なし4バイト整数 | このキータイプのデフォルトキーを持つかどうか |
6. | デフォルトキーがあればデフォルトキーのC-6:タイムキーデータ | |
7. | 符号付4バイト整数 | このキータイプのタイムキーの数(デフォルトキーを除く) |
8. | 符号付4バイト整数 | タイムキーの位置(インデックス) |
9. | C-6:タイムキーデータ | |
10. | タイムマップのエンドブロック | |
11. | C-5:タイムラインデータのエンドブロック |
データの行先:
「4.タイムキーのタイプ名」はcore/TimeKey.cppで書かれています。
"Move" "Rotate" "Scale" "Depth" "Opa" "Bone" "Pose" "Mesh" "FFD" "Image"があります。
これはIDに直されると同時に、描画する際の処理順に
static const std::array<core::TimeKeyType, core::TimeKeyType_TERM> kTimeKeyTypeInOrderOfOperations = {
core::TimeKeyType_Image, core::TimeKeyType_Mesh, core::TimeKeyType_FFD, core::TimeKeyType_Bone, core::TimeKeyType_Pose, core::TimeKeyType_Move, core::TimeKeyType_Rotate, core::TimeKeyType_Scale, core::TimeKeyType_Depth, core::TimeKeyType_Opa};
とされています。これはおそらくプログラム順で、処理そのものは通常は拡大縮小、回転、移動の順にします。OpenGLでは行列の変換は逆順にプログラムにいれるのでこうなっていると思います。
(実行順は先にOpa=不透明度、後にImageKey)
.glslexについて:
基本的に通常のglsl形式と同じはずですが、
「#」を使ってプリプロセッサ的なものを入れています(AnimeEffectsオリジナル仕様)。
.glslexでは頂点シェーダーとフラグメントシェーダーを同じファイルに入れています。
ライセンスがGPL3な事もあるので、AnimeEffectsのソースファイルを使用せず、
自分で切り分けて各バージョンの頂点シェーダーとフラグメントシェーダーファイルを作成してそれを使うと良いかもしれません。
gl/ExtendShaderの
bool ExtendShader::resolveVariation()
で切り分け(プリプロセッサ部分)が行われています。
(他にもプリプロセッサ処理を行う関数があるかもしれません)
例:resolveVariation()で行われている
ClipperWriting.glslexの切り分け
#begin_vertex_shader //---------------------------------------------------------------
以降は頂点シェーダー(頂点位置などを3Dエンジン(GPUが使用可能なら使われる)でいじるイメージ)
#begin_fragment_shader //---------------------------------------------------------------
以降はフラグメントシェーダー(ドットを打つ部分(主に不透明度合成など)を3Dエンジンでいじる部分)
#version 330
など、バージョン情報の部分もありますが、
開発停止中なのでチェック無しで無視しても良いかもしれません。
#variation IS_CLIPPEE 0
#if IS_CLIPPEE
何たらA
#else
何たらB
#endif
とあったら、
IS_CLIPPEE が0でなければ何たらAの部分、
0なら何たらBの部分のソースを作り、コンパイル
基本的に通常のglsl形式と同じはずですが、
「#」を使ってプリプロセッサ的なものを入れています(AnimeEffectsオリジナル仕様)。
.glslexでは頂点シェーダーとフラグメントシェーダーを同じファイルに入れています。
ライセンスがGPL3な事もあるので、AnimeEffectsのソースファイルを使用せず、
自分で切り分けて各バージョンの頂点シェーダーとフラグメントシェーダーファイルを作成してそれを使うと良いかもしれません。
gl/ExtendShaderの
bool ExtendShader::resolveVariation()
で切り分け(プリプロセッサ部分)が行われています。
(他にもプリプロセッサ処理を行う関数があるかもしれません)
例:resolveVariation()で行われている
ClipperWriting.glslexの切り分け
#begin_vertex_shader //---------------------------------------------------------------
以降は頂点シェーダー(頂点位置などを3Dエンジン(GPUが使用可能なら使われる)でいじるイメージ)
#begin_fragment_shader //---------------------------------------------------------------
以降はフラグメントシェーダー(ドットを打つ部分(主に不透明度合成など)を3Dエンジンでいじる部分)
#version 330
など、バージョン情報の部分もありますが、
開発停止中なのでチェック無しで無視しても良いかもしれません。
#variation IS_CLIPPEE 0
#if IS_CLIPPEE
何たらA
#else
何たらB
#endif
とあったら、
IS_CLIPPEE が0でなければ何たらAの部分、
0なら何たらBの部分のソースを作り、コンパイル
プロジェクトファイル「.anie」の即席の解析結果です。 内容に誤まりなどがあるかもしれませんので自己責任でお願いします。 表ができたら消しますが、それまで残しておきます。 読み込みの基本関数はcore/Deserializer.h(.cpp)で行われています。 具体的な読み込みは例えばMeshKey.cpp内のDeserialize系のメソッドという風に各データのメソッドで行われます。 画像の読み込みは bool Deserializer::readImage(XCMemBlock& aValue) で書かれています。 ;;各データ(個別化して表示) ;補足:文字列の「4バイト境界」とは、次のデータが4バイト境界で始まるように0などを追加で埋めるという意味 ;「符号付整数4バイト ID(バインド用)」は ;Deserializer::bindIDData関数で読まれた部分 ;「符号付4バイト整数 ID(order)」は ;Deserializer::orderIDData関数で読まれた部分 ;.anieヘッダ ファイルオフセット(16進数)、バイトサイズ(10進数)、説明の順 0:6:シグネーチャ AnimeEffects用ファイルを表す 6:2:エンディアンチェック 1バイト目が0xff,2バイト目が0x00でなければfalse(多分0x00ffなら「リトルエンディアン」で、そうでなければエラーで終わるようになってる) 8:4:符号なし4バイト整数 メジャーバージョン 0x0c:4:符号なし4バイト整数 マイナーバージョン 0x10:16バイト:空き 0x20: ;animeグローバルブロック 0x20:4:テキストタイプでシグネーチャ"GLBL" 0x24:4:符号なし4バイト整数 中身が64でなければエラーでおわるようになってる lengthという一時変数に入れられている 0x28:4:横幅,符号なし整数 0x2c:4:縦幅,符号なし整数 0x30:4:最大フレーム数,符号なし整数 0x34:4:FPS(1秒ごとのフレーム数),符号なし整数 0x38:4:ループ設定があるかどうか,符号なし整数 0x3c::空き 0x68:リソースホルダ、続いてオブジェクトツリー 以降のデータは、「数字.」説明の順で表記します。 数字.はファイル上に出てくる順番を指します。 ;;開始ブロックのデータ型(16バイト) ;1. シグネーチャ(1バイト×8)("ObjTree_"などANSI) ;2. ブロックサイズ(8バイト)(「1.」と「2.」を除いたブロック中身の部分のバイトサイズ。「2.」のデータの次のファイルオフセット+「2.」データ=次のブロック) ;;エンドブロックのデータ型 ;1.ログを吐かれているだけで、ファイルから読み取っている形跡はないため、ファイル中においてあるデータではない ;;リソースホルダーのデータ ;1.開始ブロック"Resource" ;2.符号付4バイト整数 リソースのトップノード数 ;3.リソースのトップノードデータ(可変個) ;リソースのトップノードデータ ;1.Null終端文字列(4バイト境界) ファイルパス ;2.リソースのノードデータ ;リソースのノードデータ ;1.開始ブロック"ResNode_" ;2.ID(Null終端文字列、4バイト境界) ;3.符号付4バイト整数 子供の数 ;4.符号付4バイト整数 ID(バインド) ;5.符号なし4バイト整数 レイヤーかどうか(1ならレイヤー) ;6.符号付4バイト整数 ×4 Rect(横位置、縦位置、横幅、縦幅の順 ;7.4バイト固定文字列 ブレンド名 ;8.符号なし4バイト整数 圧縮タイプ ;9.符号なし4バイト整数 画像の横幅 ;10.符号なし4バイト整数 画像の縦幅 ;11.符号なし8バイト整数 圧縮データのバイトサイズ ;中身データ ;12.符号なし4バイト整数 チャンネル1つの横1ラインのバイトサイズ ;13.「12.」のバイトサイズ分のデータ ;「12.」「13.」合わせてARGBのどれか1つのチャンネルの横1ライン分のデータで、これを1セットとし4チャンネル分(4セット)連続している ;このデータは圧縮されているらしく、解凍らしき処理が含まれている(画像データの圧縮形式を参照) ;さらに、それが縦幅分並んでいる ;中身データの一番最後は4バイト境界 ;14.エンドブロック ;15.子供の数だけ再帰的にリソースのノードデータ(トップノードと違ってファイルパスを含まない) ;;オブジェクトツリーのデータ(1つのみ) ;1.開始ブロック"ObjTree_" ;2. 符号付整数4バイト topNodeCount(1になるはずで、そうでなければエラー) ;3.オブジェクトツリーノードのデータ ;4.エンドブロック ;;オブジェクトツリーノードのデータ(可変個数) ;1.開始ブロック"ObjNode_" ;2.4バイト符号付整数 ノードタイプ ;ObjectType_Layer ;ObjectType_Folder ;3.4バイト符号付整数 子供の数 ;4.符号付整数4バイト ノードのID(バインド用) ;5.レイヤーノードorフォルダーノード ;6.エンドブロック ;7.子供の数だけ子供のオブジェクトツリーノードのデータ ;参考: ;ノードタイプはcore/ObjectType.hで次のように定義 enum ObjectType { ObjectType_Layer, ObjectType_Folder, ObjectType_TERM }; ;;フォルダノードのデータ ;1.開始ブロック"FolderNd" ;2.Null終端文字列データ フォルダ名(4バイト境界?)(可変サイズ) ;3.符号なし4バイト整数 表示状態 ;4.符号なし4バイト整数 SlimmedDown状態かどうか ;5.符号付4バイト整数×4 初期Rect(横位置、縦位置、横幅、縦幅の順) ;6.符号なし4バイト整数 クリッピングしてるかどうか ;7.タイムラインデータ ;8.エンドブロック ;タイムラインデータはレイヤーノードのと一緒(下記参照) ;;レイヤーノードのデータ ;1.開始ブロック"LayerNd_" ;2.Null終端文字列データ レイヤー名(4バイト境界?)(可変サイズ) ;3.符号なし整数4バイト 表示状態 ;4.符号なし整数4バイト IsSlimmedDown ;5.符号付整数4バイト×4 初期Rect(横位置、縦位置、横幅、縦幅の順) ;6.符号なし整数4バイト クリッピングしているかどうか ;7.タイムラインデータ 読み込み時にここでオリジナルシェーダーの読み込みなどシェーダーの設定がされています ;8.エンドブロック ;;タイムラインデータ ;1.開始ブロック"TimeLine" ;2.符号付整数4バイト タイムマップ数 ;3.タイムマップデータ(可変個) ;4.エンドブロック ;タイムマップデータ ;1.開始ブロック"TimeMap_" ;2.可変最大8バイト(4バイト境界?)タイムキー名 ;3.符号なし整数4バイト 1の時デフォルトキー ;3-1.デフォルトキーならデフォルトキー用タイムキーデータ読み込み ;4.符号なし整数 4バイト キー数 ;5.符号なし整数4バイト キーインデックス(このキーの該当フレーム位置) ;6.タイムキーデータ ;7.エンドブロック ;補足:「5.」「6.」の順のデータを1セットとして、それがキー数分並んでいる ;キーインデックス(フレーム位置)はソースファイルのデータ上では ;各キーの親クラスであるTimeKeyクラスのint型変数「mFrame」に入ります。 ;;参考:タイムキー名(core/TimeLine.cppにある) Names = { "Move", "Rotate", "Scale", "Depth", "Opa", "Bone", "Pose", "Mesh", "FFD", "Image" }; ;;タイムキーデータ ;タイムマップデータの 2.タイムキー名 によるタイプによって、 ;MoveKey,RotateKey,ScaleKey,DepthKey,OpaKey,BoneKey,PoseKey,MeshKey ;FFDKey,ImageKeyに分かれる(現バージョンで10種類) ;↓に各タイプ別に読み込み方を書きます ;;タイムキーデータ(MoveKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.符号付4バイト整数 スプラインIdx(SplineType_) ;6.float4バイト×2 位置 横、縦の順 ;7.float4バイト×2 centroid 横、縦の順 ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(RotateKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.float 4バイト 回転度 ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(ScaleKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.float 4バイト ×2 拡大縮小度 横、縦の順 ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(DepthKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.float 4バイト 深度(Depth) ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(OpaKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.float 4バイト Opacity(不透明度) ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(BoneKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト ボーンの数 ;3.「2.」のボーンの数だけボーンデータ(可変個) ;4.符号付4バイト整数 ID(order) キャッシュ系のデータ(cash owner) ;5.符号付4バイト整数 キャッシュ数 ;6.「5.」のキャッシュ数だけボーンのキャッシュデータ(可変個数) ;7.符号付4バイト整数 バインディングキャッシュ数 ;8.「7.」のバインディングキャッシュ数だけボーンのバインディングキャッシュデータ(可変個数) ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;ボーンデータ ;1.符号付4バイト整数 子供の数 ;2.符号付4バイト整数 ID(バインド用) ;3.符号付4バイト整数 ID(order) ;4.float 4バイト×2 ローカル座標での位置(X,Yの順) ;5.float 4バイト ローカル座標でのAngle ;6.float4バイト×2 X,Yの順Rangeその1 ;7.float4バイト×2 X,Yの順Rangeその2 ;以下はボーンシェイプデータ ;8.符号なし4バイト整数 有効かどうか ;9.float4バイト×2 セグメントstart X,Yの順 ;10.float4バイト×2 セグメントdir X,Yの順 ;11.float4バイト×2 Vユニット X,Yの順 ;12.float4バイト DirAngle ;13.float4バイト Length ;14.float4バイト×2 Radiusその1 X,Yの順 ;15.float4バイト×2 Radiusその2 X,Yの順 ;16.float4バイト RootBendRange中のangleその1 ;17.float4バイト RootBendRange中のangleその2 ;18.float4バイト TailBendRange中のangleその1 ;19.float4バイト TailBendRange中のangleその2 ;20.float4バイト×4 バウンディングRect 横位置、縦位置、横幅、縦幅の順 ;21.符号付4バイト整数 ポリゴン頂点数 ;22.float4バイト2つ(頂点位置X,Yの順)×ポリゴン頂点数 (可変個) ;ボーンシェイプデータここまで ;23,符号付4バイト整数 バインド数 ;24.符号付4バイト整数×バインド数 ID(order)(可変個) ;25.float4バイト×2 ワールド座標での位置(X,Yの順) ;26.float4バイト ワールド座標でのAngle ;27.float4バイト Rotate ;28.「1.」の子供の数だけボーンデータ(可変個) ;;ボーンのキャッシュデータ ;1.符号付4バイト整数 ID(order) ;2.float4バイト×2 origin offset(X,Yの順、「obsolete」とコメントあり) ;BoneInfluenceデータ系 ;3.符号付4バイト整数 BoneInfluenceデータ:頂点数 ;4.符号付4バイト整数 BoneInfluenceデータ:最大ボーン数 ;5.符号付4バイト整数×上記頂点数 Indicesその1 ;6.符号付4バイト整数×上記頂点数 Indicesその2 ;7.float4バイト×上記頂点数 ウェイトその1 ;8.float4バイト×上記頂点数 ウェイトその2 ;BoneInfluenceデータ系ここまで ;;ボーンのバインディングキャッシュデータ ;1.符号付4バイト整数 ID(order) ;2.符号付4バイト整数 boneIndex ;3.float4バイト×16 innnerMtx(QMatrix4x4) ;;タイムキーデータ (PoseKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3,符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージングウェイト ;5.符号付4バイト整数 ボーン数 ;6.ボーンデータ 「5.」のボーン数だけ (可変個) ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(MeshKey) ;1.符号付4バイト整数 ID(バインド) ;2.float 4バイト オリジンオフセット x位置 ;3.float 4バイト オリジンオフセット y位置 ;4. 符号付4バイト整数 頂点の数 ;5.float 4バイト×2 頂点の位置(横、縦の順)(可変個) ;6.符号付4バイト整数 辺の数 ;7.符号付4バイト整数×2 v0とv1 辺(可変個) ;8.符号付4バイト整数 面の数 ;9.符号付4バイト整数×3 e0 e1 e2面データ ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(FFDKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3.符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージング:ウェイト ;5.符号付4バイト整数 頂点数 ;6.float4バイト×3で1つの位置データ×頂点数 ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;タイムキーデータ(ImageKey) ;1.符号付4バイト整数 ID(バインド) ;2.符号付4バイト整数 イージング:タイプ ;3.符号付4バイト整数 イージング:レンジ ;4.float 4バイト イージング:ウェイト ;5.符号付4バイト整数 ID(order) ;6.1×4バイトでブレンドモード名 ;7.float4バイト×2でイメージオフセット ;8.グリッドメッシュデータ ;符号付4バイト整数 子供の数 ;符号付4バイト整数 子供の数だけ子供のID(order) ;;グリッドメッシュデータ(GridMesh) ;1.開始ブロック"GridMesh" ;2.符号付4バイト整数 タイプ ;3.符号付4バイト整数×2 サイズ横、縦の順 ;4.float4バイト×2 横、縦 オリジンオフセット ;5.符号付4バイト整数 セルピクセル ;6.符号付4バイト整数 プリミティブタイプ ;7.符号付4バイト整数 インデックス数 ;8.符号なし整数4バイト×インデックス数 でインデックスの配列 ;9.符号付4バイト整数 頂点数 ;10.float4バイト×3で1つの頂点位置×頂点数 (x,y,zの順×頂点数) ;11.float4バイト×3で1つの頂点のオフセット×頂点数 ;12.float4バイト×2で1つのテクスチャコード×頂点数(x,yの順×頂点数) ;13.float4バイト×3で1つの法線×頂点数 ;14.符号なし8バイト整数 生サイズ ;15.「14.」の生サイズ分中身データ ;16.Qtバージョン0.5以上の場合 符号付4バイト整数×4 頂点のRectデータ横位置縦位置横幅縦幅 ;17.エンドブロック
とりあえず連番画像エクスポート用の処理の部分 ctrl/Exporter.h .cppあたりから辿ってみます
現在分析中です(超苦戦)
概要:
描画は基本的にはQTの「QOpenGLWidget」クラスを使っていますが、
メッシュトランスフォームなど、アニメーションの変形部分は主に独自シェーダーの変数にデータをぶちこみ、プログラマブルシェーダーで行っているようです。
独自シェーダーはdata/shader/なんたら.glslex にあります。
全体の描画の流れ
1.ctrl/Exporterはメニューなどでエクスポートしたとき呼び出される
2.Exporter::Result Exporter::execute()メソッドはいくつかタイプがあるが、本体のものは
start(),全フレーム数分update()、最後にfinish()で終る構成です。
start()はOpenGL仕様でフレームバッファ(オフスクリーンの描画領域)の設定などをしている部分です。
update()メソッドが.anieを読み込んだデータを描画する本体です。
3.update()内で、
ObjectTreeクラスのrenderメソッド呼び出し
そこでTimeCacheAccessorクラスを作成
そのコンストラクタでTimeKeyBlenderクラスを作成、updateCurrent()メソッドを呼び出し。
TimeKeyBlenderクラスで、ロードしたデータを
TimeKeyExpansクラスに加工してセットしています(ここが一番難所で、イージングなども踏まえたデータが作成されます)
最終的な描画はTimeKeyExpansから取ります。
ObjectTreeのrenderメソッドに帰ってきて、
SortAndRenderCallクラスのinvokeメソッドを呼び出しています。
4.invoke()内で、
全ノード(レイヤーノード)にprerender()
Depth順にノードを配列に並べる
並べ替えた順に全ノードにrender()
prerender()やrender()では独自シェーダーにデータを渡しています
FolderNodeクラスはprerender()、render()が描画処理がないようです
現在分析中です(超苦戦)
概要:
描画は基本的にはQTの「QOpenGLWidget」クラスを使っていますが、
メッシュトランスフォームなど、アニメーションの変形部分は主に独自シェーダーの変数にデータをぶちこみ、プログラマブルシェーダーで行っているようです。
独自シェーダーはdata/shader/なんたら.glslex にあります。
全体の描画の流れ
1.ctrl/Exporterはメニューなどでエクスポートしたとき呼び出される
2.Exporter::Result Exporter::execute()メソッドはいくつかタイプがあるが、本体のものは
start(),全フレーム数分update()、最後にfinish()で終る構成です。
start()はOpenGL仕様でフレームバッファ(オフスクリーンの描画領域)の設定などをしている部分です。
update()メソッドが.anieを読み込んだデータを描画する本体です。
3.update()内で、
ObjectTreeクラスのrenderメソッド呼び出し
そこでTimeCacheAccessorクラスを作成
そのコンストラクタでTimeKeyBlenderクラスを作成、updateCurrent()メソッドを呼び出し。
TimeKeyBlenderクラスで、ロードしたデータを
TimeKeyExpansクラスに加工してセットしています(ここが一番難所で、イージングなども踏まえたデータが作成されます)
最終的な描画はTimeKeyExpansから取ります。
ObjectTreeのrenderメソッドに帰ってきて、
SortAndRenderCallクラスのinvokeメソッドを呼び出しています。
4.invoke()内で、
全ノード(レイヤーノード)にprerender()
Depth順にノードを配列に並べる
並べ替えた順に全ノードにrender()
prerender()やrender()では独自シェーダーにデータを渡しています
FolderNodeクラスはprerender()、render()が描画処理がないようです
タグ
このページへのコメント
ちょっと見ない間にだいぶ進んでる