scnファイルのデータ構造 / magicien 

JSceneKitを実装する上で難しかった点の一つは、scnファイルの解析である。とはいえ、pmmファイルの解析で鍛えられたバイナリ読解力を以ってすれば造作もない。

試しに ship.scn をバイナリエディタで開いてみると、"bplist00" という文字列が最初に入っていることがわかる。
これは、binary property list形式ということなので、plutilコマンドで中身を見ることができる。
$ plutil -p ship.scn

実行結果はこんな感じ。
{
  "$version" => 100000
  "$objects" => [
    0 => "$null"
(中略)
    81 => {
      "$classname" => "SCNScene"
      "$classes" => [
        0 => "SCNScene"
        1 => "NSObject"
      ]
    }
  ]
  "$archiver" => "NSKeyedArchiver"
  "$top" => {
    "root" => <CFKeyedArchiverUID 0x7f90efd00480 [0x7fffbe406da0]>{value = 1}
  }

JSONのような形式で内容が表示されるので、これを元にどこにどのデータが入っているかを解析する。
すると、ファイルの末尾26バイトにパースに必要なデータが入っていることがわかる。
  • オフセットデータのサイズ:unsigned byte
  • オブジェクトのindexのサイズ:unsigned byte
  • オブジェクト数:unsigned long long
  • 最初のオブジェクトのindex:unsigned long long
  • オブジェクトデータの場所:unsigned long long
次に、オブジェクトデータの場所(ファイル先頭からのバイト数)からオブジェクト数の数だけ、整数(バイト数は、オフセットデータのサイズ)を読み込む。これは、各オブジェクトのデータの場所を表している。

最初に読み込むべきオブジェクトは、「最初のオブジェクトのindex」で与えられている。
各オブジェクトデータの先頭1バイトに、データの種類が格納されている。
0x00 なら null、0x08 なら false、 0x09 なら true、0x10〜0x1F なら整数、等。

最初のオブジェクトは、$version、$objects、$archiver、$top というデータの入ったDictionary型になっている。
次に読み込むべきオブジェクトは、$objects[$top.root] のオブジェクト。このオブジェクトが本当に必要なデータということになる。
オブジェクトが何らかのインスタンスである場合は、Dictionary型になっていて、"$class" にどのクラスのデータかが記録されている。
scnファイルの場合は、$class.$classname が SCNScene となっているはず。
Dictionary内の残りのデータはクラスによって異なるので、どれをどう読み込むかはクラス毎に実装が必要。
インスタンスのデータ全てが出力されているかといえばそういうわけではなく、デフォルト値と同じ場合は出力されていないこともある。

文字で書くと意味不明ですね。OmniGraffleの試用期間が終わったので製品版買おうかと思ったけど、Standardでも思ったより値が張るなぁ。
2017/06/28(Wed) 04:03:27