著者名が「magicien
41〜50件目 / 225件
前へ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 次へ

 GitHub AppsでOrganizationにリポジトリを新規作成する / magicien 

GitHub Appsでは、今のところユーザ用リポジトリの作成ができない。Organization用であれば作成できるようなので、作成までの手順をメモしておく。

事前準備

Appの作成とJWT生成の準備は「GitHub Appsでリポジトリにファイル追加・更新」と同様に済ませておく。

操作対象のOrganizationを決める

これも前回とほぼ同じなので流れだけ簡単に書いておく。

ユーザを判別する

ユーザに下記URLへアクセスしてもらう。
https://github.com/login/oauth/authorize?child_id=ChildID&redirect_uri=自分のサイトのどこか&state=ランダムな文字列

戻ってきたリダイレクト先でcodeを取得する。
https://自分のサイト/どこかのページ.html?code=1234567890abcdef&state=先ほどのstate

アクセストークン生成

curl -X POST -d "code=先ほどのcode" -d "client_id=ClientID" -d "client_secret=ClientSecret" https://github.com/login/oauth/access_token

応答からアクセストークン取得
access_token=アクセストークン&scope=&token_type=bearer

ユーザが管理していてAppをインストール済みのOrganizationを取得する

取得方法はユーザのリポジトリを見つけるときと同じ。
curl -H "Authorization: token アクセストークン" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/user/installations

Organization用のInstallationは、target_typeがOrganizationになっている(ユーザ用はUserになっている)。
{
  "total_count": 1,
  "installations": [
    {
      "id": 56789,
      "account": {
        "login": "Organization名",
        中略
      },
      中略
      "integration_id": 1234,
      "app_id": 1234,
      "target_type": "Organization",
      中略
    }
  ],
  "integration_installations": [
    {
      "id": 56789,
      "account": {
        "login": "Organization名",
        中略
      },
      中略
      "integration_id": 1234,
      "app_id": 1234,
      "target_type": "Organization",
      中略
    }
  ]
}
installations[i].app_id が一致し、target_typeがOrganizationのものを見つける。 必要なのは、id(installationのid)とaccount.login(Organization名)。

Installationとしての認証

ここは前回と同じ。
curl -X POST -H "Authorization: Bearer JWTのトークン" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/installations/先ほど取得したinstallationのid/access_tokens

応答も同じ。
{
  "token": "アクセストークンその2",
  "expires_at": "2017-01-01T00:00:00Z"
}

リポジトリを生成する

公式の説明はここ。
最低限必要なパラメータはnameだけ。
curl -X POST -H "Authorization: token アクセストークンその2" -H "Accept: application/vnd.github.machine-man-preview+json" -d '{"name": "リポジトリ名"}' https://api.github.com/orgs/Organization名/repos

おしまい!

2017/09/10(Sun) 17:09:58

 GitHub Appsでリポジトリにファイル追加・更新 / magicien 

GitHub Appsでリポジトリにファイル追加・更新する手順メモ。ゲームエディタの編集データをGitHubに保存したいと思ったので。手元のMacBook Airの空き容量は500MBくらいで虫の息。シンクライアント的な使い方しかできない状態なのです。

はじめに。GitHub Appsで新規リポジトリを作ろうとしたけどだめだった。ユーザ側でAppを使うリポジトリを決める必要があるので、App側で勝手にリポジトリを追加することはできないのかも。OAuth Appsなら作成できるはずなので、やるとすれば、一旦OAuthで作成してから、GitHub Appでそのリポジトリをアクセス対象に追加する感じかな(Appが2つ必要だとするとユーザ側の操作もかなり面倒なことに...)よくよく見たら、APIの説明に「Currently, only the organization endpoint is enabled for GitHub Apps.」って書いてあったぁぁぁ。Organizationで試してみたら問題なくリポジトリ作れたぁぁぁ。ゲームエディタ用Organization作ってそこでリポジトリ管理することにしよう。その方が安全だし。

GitHub Apps作成

ここら辺はずいぶん前の話で忘れてしまったため適当。

Developer Memberに登録

事前にメンバー登録が必要だったと思う。登録に手間取った記憶がないので、要求されたものを入力しただけだったはず。

App作成

ここの手順通りに実施したはず。
登録手順の前に、GitHub AppsとOAuth Appsの二種類があって、こんな違いがありますよーみたいな話が延々と書いてあって手順を見つけるのに苦労した。

App情報を確認

「Setting > GitHub Apps > アプリ名」を選ぶと、アプリ情報の一覧が表示される。最下部にある「OAuth credentials」の「Client ID」「Client secret」を認証に使うので覚えておく。
「Private key」も必要なので、「Generate private key」ボタンを押して生成、覚えておく。

認証用トークン生成準備

認証には JSON Web Token(JWT)なるものを使うので、それを生成する何らかの仕組みを用意する必要がある。
JWTのページにライブラリがたくさんあるので、良さげなものを選ぶ。
自分はサーバでPHPを使っているので、lcobucci/jwtにした。
JWT生成コードはこんな感じ。 Private Keyはボタンを押して生成したものを貼り付け。setIssuerに指定するIDは「Settings > GitHub Apps」で見られる。
有効期限は最大10分なので、手動でのんびり実行していると時間切れになってしまう。

作ったAppのインストール

もしかしたらアプリ生成の時にインストール先を選択済みかも。インストールしていない場合は、
「https://github.com/apps/アプリ名」にアクセスすると、インストールできる。

操作対象のリポジトリを決める

ユーザを判別する

まずは誰がAppを使おうとしているのか判別する。公式の説明はここ
ユーザに下記URLにGETでアクセスしてもらう。(child_idはApp登録時にメモしたもの)

stateはリクエストフォージェリ対策で付けておくと良さげ。何か情報を受けわたす為に使っても良い、とのこと。
https://github.com/login/oauth/authorize?child_id=ChildID&redirect_uri=自分のサイトのどこか&state=ランダムな文字列

すると、アプリが認証を要求してるけど?というページが表示される。ユーザが「良いよ!」ということであれば、redirect_uriで指定したURLに戻ってくる。が、この時、codeパラメータが追加されている。stateが先ほど付けたものと同じかどうかをチェックすると良さげ。
https://自分のサイト/どこかのページ.html?code=1234567890abcdef&state=先ほどのstate

アクセストークン生成

ここから先はサーバ・GitHub間でゴリゴリする。
先ほど返ってきたcodeを使ってアクセストークンを生成する。(JWTを使った認証とは別なので要注意)

curl -X POST -d "code=先ほどのcode" -d "client_id=ClientID" -d "client_secret=ClientSecret" https://github.com/login/oauth/access_token

一応、redirect_urlで次のリダイレクト先を指定できるけど、クライアント側からclient_secretを送ることは無いと思うので使い道なさげ。stateも指定できるけど同じ理由で使い道無さげ(GitHub側でcodeとstateの組をチェックしている様子は無かった)。応答はこんな感じで返ってくる。
access_token=アクセストークン&scope=&token_type=bearer

ユーザが使っているApp(Installation)のIDを取得する

Github AppのIDとは別に、ユーザ・App毎にIDが割り当てられているので、それを取得する。
現状、Acceptヘッダを付けないと怒られるけど、そのうち要らなくなるはず。
curl -H "Authorization: token アクセストークン" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/user/installations
応答はJSONで返ってくる。
{
  "total_count": 1,
  "installations": [
    {
      "id": 56789,
      中略
      "integration_id": 1234,
      "app_id": 1234,
    }
  ],
  "integration_installations": [
    {
      "id": 56789,
      中略
      "integration_id": 1234,
      "app_id": 1234,
    }
  ]
}
installations[i].app_id が一致するものを見つけて、installations[i].id を取得する。integration_installationsに全く同じ情報が入ってるんだけど、どっちを見るべきかよくわからん。

アクセスできるリポジトリ一覧を取得する

先ほど取得したinstallationのidを使って、リポジトリ一覧を取得する。
アプリとしては、ユーザに一覧を表示して選んでもらう感じになると思う。
curl -H "Authorization: token アクセストークン" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/user/installations/さっき取得したinstallationのid/repositories
応答はJSONで返ってくる。今回は repositories[].full_name が分かれば良い。
{
  "total_count": 1,
  "repositories": [
    {
      "id": リポジトリID,
      "name": "リポジトリ名",
      "full_name": "ユーザ名/リポジトリ名",
      中略
    }
  ]
}

Installationとしての認証

これ要る?公式の説明はここ
先ほど取得したアクセストークンだとリポジトリ操作ができないようなので、Installation用のアクセストークンを別途取得する。
最初の方で準備しておいたJWTのトークンを生成して、次のコマンドを実行する。
curl -X POST -H "Authorization: Bearer JWTのトークン" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/installations/先ほど取得したinstallationのid/access_tokens
応答はJSONで返ってくる。
{
  "token": "アクセストークンその2",
  "expires_at": "2017-01-01T00:00:00Z"
}

ファイルを追加する

ようやくファイルを追加する。公式の説明はここ
curl -X PUT -H "Authorization: token アクセストークンその2" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/repos/ユーザ名/リポジトリ名/contents/ファイルパス -d '{"path":"ファイルパス","message":"commitメッセージ","content":"ファイルの内容"}'
オプションでcomitterの情報も付けられる。デフォルトだと、Appのbotがcomitterになる。branchでブランチの指定も可。
contentはbase64にエンコードしないといけないので、base64コマンドを使うなりして変換しておく。

ファイルを更新する

ファイルを追加する場合とほぼ同じ。唯一の違いは、元のファイルのshaを指定すること。

元ファイルのshaを取得する

認証トークンは特に要らない。
curl https://api.github.com/repos/ユーザ名/リポジトリ名/contents/ファイルパス
応答はJSON形式。
{
    中略
    "sha": "ハッシュ値",
    中略
}

ファイル更新

curl -X PUT -H "Authorization: token アクセストークンその2" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/repos/ユーザ名/リポジトリ名/contents/ファイルパス-d '{"path":"ファイルパス","message":"commitメッセージ","content":"ファイルの内容", "sha":"ハッシュ値"}'

2017/09/10(Sun) 09:04:16

 Undo/RedoできるCanvasを作った / magicien 

まーた言ったそばから脇道に逸れているわけ。
HTMLCanvasElement (CanvasRenderingContext2D) に undo/redo機能が欲しいな、と思ったので作りました。
undo-canvasデモページ1

CanvasRenderingContext2Dのプロパティに片っ端からフックをかますという荒技を使い、変更したプロパティ、呼び出したメソッドを全て記録しています。
副産物として、上書きしたメソッドを prototype で定義されているものに戻す reset-object を別モジュールとして作りました。

使ってみたい人は、scriptタグでundo-canvasを読み込んでください。
<script src="https://cdn.rawgit.com/magicien/undo-canvas/v0.1.2/undo-canvas.js"></script>
ブラウザ向けnpmモジュールを作っている人は、npmコマンドでインストールできます。
npm install --save undo-canvas
jsファイルを読み込むと、グローバル変数として UndoCanvas が定義されるので、
const canvas = document.getElementById('myCanvas')
const context = canvas.getContext('2d')
UndoCanvas.enableUndo(context)
こんな感じで、UndoCanvas.enableUndoにコンテキストを引数として渡してください。contextに undo/redo 関数が追加され、以降、このcanvasに対する操作が記録されていきます。
undo/redoしたくなったら、
context.undo()
context.redo()
で行ったり来たりできます。
undo/redoが必要なくなったら、disableUndoで無効化できます(記録されていた操作履歴は全て消えます)。
UndoCanvas.disableUndo(context)
その他機能はGithubの記載を更新していくので、そちらを参照してください。

undo/redoを実現する仕組みはOracleのようなDBの考え方をぱく参考にしたのですが、思いの外うまくハマったので、気が向いたら解説記事でも書くかもしれません。

さて、今度こそエディタ制作作業に戻ろう...

2017/09/06(Wed) 04:58:25

 React/Reduxでゲームエディタを作る時のメモ / magicien 

色々と脇道に逸れていたけれど、ゲームエディタを作り始めることにした。
フレームワークは勉強も兼ねて React/Redux を使ってみることにする。この選択が正しいかどうかは分からない。間違っていたら作り直せば良いので気楽に行こう。

以前Reactをほんの少し触ったことはあったけれど、存在意義も含めほぼ理解できずに終わったので、今回は1から勉強し直す。以下、困ったことリスト。回答とか感想とかは未来の自分に任せることにする。React/Reduxの正しい設計が知りたくて見に来た人は参考にしてはいけない。

モジュールのバージョンは?

とりあえず最新のものを使おう。React v15.6.1、Redux v3.7.2。それと、React Routerがバージョンによって大きく挙動が変わるみたい。現状最新の React Router v4.2.0 を使う。色々試した結果、react-routerを使わずに、redux-first-routerを使うことにした。モジュールは随時最新化していくことにする。

デザインをどうするか

色々探してみたけれど、結局Bootstrapを使う。というか、他に選択肢が見つからなかった。自由度も良い感じに制限されていて迷わなくて済むし、もうこれで良いんじゃないかな。デフォルトだとボタンの余白が大きいのが少し気になる。スマホ向けには良さそうだけど、PC向けにはもうちょっと小さくしたい。

各国語対応はどうするか

後になってi18n対応するのは大変そうなので、予め設計を考えておく。react-boilerplateがi18n対応していたので、それを参考に実装する。ソースコードに日本語を埋め込むのも微妙だし、デフォルトの文言は英語にしよう。英語用のメッセージ一覧は無くても良いけれど、他の言語に翻訳する時にメッセージ一覧があった方が便利なので用意する。というか自動生成したい。

ルーティングはどうするか

最初、react-routerを使っていたけれど、URLに応じてコンテンツを変更しようと考えたときに、redux-first-routerの方が楽だということに気づき、乗り換えることにした。そのページを表示する直前・直後に実行する処理も書けるし便利。
redux-first-routerを使うと、ページを移動時にURLに応じたactionがdispatchされるようになる。逆に、actionをdispatchすることでページを移動させることも可能。このactionを使ってURLとstoreをうまいこと同期させてあげれば、コンポーネント側でURLを気にしなくても良くなる。いちいちdispatchしなくてもurlのリンクを書いておけばそれに応じたactionが実行される。便利。
この変更によって、redux-sagaは要らなくなってしまった。

データのフェッチはどうするか

react-boilerplateredux-sagaを使っているので、それに倣ってみる。どうやらGeneratorで実現しているようだ。Sagaの定義はStore/Actionと一緒に置いておく感じかな。actionをdispatchする前にsagaをrunしておく必要があるけど、全ページで全sagaをrunするのは無駄かな。全部runするか、ページ毎に対象を指定するか、うまい方法が無いかは実装しながら考えよう。

今のbabelの設定だと、async/awaitが使えるので、そっちの方が良い気がしてきた。Sagaをrunするとactionの受け取り手が無駄に増えちゃったりするし。GeneratorでもPromiseでも使えるみたいなので、async/awaitに全て移行してしまおう。

テストはどうするか

boilerplateだと各jsファイルの横にtestsディレクトリを作ってテストを書いているようだ。モジュールはJestを使っている。この構成も倣ってみよう。使い方はmocha/chaiとほとんど同じかな?

Undo/Redoはどうするか

これも後から対応するの大変そうだから先に考えておきたい。
大部分のデータは、redux-undoで実現できそう。undo/redoしたい粒度でデータをhistory/futureにとっておく。問題はどういう粒度で何のデータを保存しておくか。エディタのメイン画面はマップエディタにしたいと考えていて、マップの地面の高低はheightmapのテクスチャという形で保存したいと考えている。
この場合、画像編集のデータをどう保存すれば良いのか。画像を丸ごと保存してしまう?グレースケールだから行けそうか...でも変更箇所の差分だけ取っておく方が良いよなぁ。それだとredux-undoの使い方にはマッチしない。Photoshopの実装がどうなっているか知りたい。DBみたいに適当なタイミングでチェックポイント(画像丸ごと保存)を作って、そこからredoが妥当かなぁ。よくよく考えたら、動画フォーマットもそんな感じだよね。でも実装がめちゃくちゃ複雑になりそう。→ Undo/RedoできるCanvasを作った

データの保存はどうするか

一時保存は localStorage IndexedDB へ、永続化はGithubへ、という感じにしたい。Github APIはまだ動作確認中。→GitHub Appsでリポジトリにファイル追加・更新
クライアント側で何を使うかも悩みどころ。IndexedDBはそのままでもシンプルかつ高機能なので、特にラッパー無しでもいけそうな予感。まさか仕様としてブラウザ上にDBが実装されるようになるとは。

ファイル構成をどうするか

Action/Reducer/Selector/Storeを1ファイルにまとめる

React/Reduxの実装例とか見ると、ほとんどの例でファイルが粉々に分割されていて非常に読み辛かった。なので、ducks-modular-reduxを参考に一ファイルにまとめてしまうことにした。テストするときに困るだろうか。

ContainerとグローバルのStateを分離する

ContainerとAction/Reducer/Stateが一対一で対応している例が多かったけれど、その構成のままContainerを100、200と増やしていくと地獄と化すのが目に見えているので、分離することにした。(実装例の規模が小さすぎて1組ずつしか無かっただけかもしれない)
Containerとデータ構造・操作をくっつけると再利用性が上がるのってReduxを使っていない場合の話だよね...?同じデータを複数のContainerで使う時にどう実装すべきか分からない。ContainerにStateを管理させると、設計がぐちゃぐちゃになって拡張が難しくなるようにしか思えない。

最上位のContainer(ページ)は別ディレクトリに分離する

Container同士にも親子関係があるので、同一ディレクトリにフラットに並べると分かりづらい。各画面の最上位のContainerは別ディレクトリに分けることにした。後でページ毎にwebpackするjsファイルを分けるのにも便利じゃないかと思う。

jsのファイル名はindex.jsにする

importするときにファイル名まで書かずに済むから楽なんじゃないかと。

ファイル構成(暫定)

app
├index.html HTMLの外枠。
├app.js  Storeの生成、Router、i18nの仕込み。
├pages  最上位のContainer(ページレイアウト)置き場。
│└pageA
│ ├index.js react-helmetでヘッダ上書き。必要に応じてconnectする。
│ ├Component.js そのページ専用のコンポーネントは一緒に置いてしまう。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  ├index.test.js index.jsのテスト
│  └Component.test.js Component.jsのテスト
├containers 各ページ共通で使えそうなパーツ置き場
│└containerA i18n用コンテナとか。
│ ├index.js React.Component等。必要に応じてconnectする。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  └index.test.js index.jsのテスト
├components 各ページ共通で使えそうなパーツ置き場
│└componentA ボタンとか。
│ ├index.js React.Componentか関数。connectしない。
│ ├messages.js 翻訳対応するメッセージ一覧
│ └tests
│  └index.test.js index.jsのテスト
├redux
│├configureStore.js Storeを生成。Middleware、Reducer取り込み。
│├reducers.js modulesからreducerを一括でロードする。
│├routesMap.js URLとpagesのマッピングを書く。
││       ページ毎のデータフェッチとかアクセス制御(誘導?)とかも書いておく。
│├modules ActionType/Action/Selector/Reducer置き場
││└moduleA
││ ├index.js 全部まとめて一ファイルに。reducerをexport defaultする。
││ └tests
││  └index.test.js index.jsのテスト
│└tests
│ ├configureStore.test.js 
│ └reducers.test.js
├translations
│├en.json 英語用メッセージ。自動生成したい。
│└ja.json 日本語用メッセージ
├config
│└index.js convictを使って説明とか型とか書いておく。
└tests
 └app.test.js app.jsのテスト

Code Splittingはどうするか

webpackするときに、ページ毎のjsと共通のjsをそれぞれ別ファイルにしたい。
React + Reduxを使った大規模商用サービスの開発」だと、require.ensureを使っていたけれど、例によってReactRouterの仕様変更によりそのままでは使えなかった。
でもReactRouterのページにCode Splittingの方法が書いてあったので、それをそのまま使うことにした。bundle loaderがうまいことやってくれるらしい。生成されるjsファイルは、0.[ハッシュ].chunk.js、1.[ハッシュ].chunk.js みたいになっていて、ページ数が増えると数字がずれたりする。上の資料だと、モジュールの並び順を制御してハッシュ値が変わらないよう工夫していたけれど、今回はそこまで神経質にしなくても良いかな。
react-routerを使うのをやめたので、代わりにuniversal-importを使うことにした。大してコード修正せずにそのまま乗り換えられたので一安心。

Stateに溜まったゴミはどうすればいいのか

画面遷移を続けていくと、Stateにゴミデータがどんどん溜まっていく。データの後処理はどうするのが良いのかな?これもReduxを使うが故の悩みだろうか。

ネット上の情報はいろんなバージョンのReactを使っていてよく分からない

バージョン間の差異が大きすぎる。どうしたらいいんでしょうか。とりあえず半年以上前の記事は話半分で読むことにする。

参考になったもの

React + Reduxを使った大規模商用サービスの開発

画面とかStateとか大量にある場合の設計の実例。実サービスとして作っただけあって、実践的。本格的に作り始める前にこの資料を見つけられて本当に良かった。性能測定はどうやっているのだろう。

ducks-modular-redux

action-types/actions/reducersを別々のファイルにしてもあんまり意味がないから一つのファイルにまとめちゃおうという提案。自分も同じ疑問を持っていたので、しっくりきた。

react-boilerplate

実際にReact/Reduxのアプリを作る上で最小限必要なものが一通り揃っている。i18n対応しているのが非常に素晴らしい。Herokuへのデプロイに対応しているのもポイント高い。React Router v3だったので、残念ながらそのまま使うことはできなかったけれど、大いに参考になった。

wp-calypso

WordPressのReact/Redux実装。大規模アプリの実例として参考になった。規模が大きすぎる気がしなくもない。ライセンスがGPLなので要注意。

2017/09/02(Sat) 18:32:24

 GLTFSceneKit: SceneKit用glTFローダ / magicien 

image作った。ソースとサンプルアプリはGithubにあります。

未完成な部分もあるけど、基本的な機能は大体出来たかなぁというところ。
SceneKitデフォルトのライトだと、isDoubleSided が機能しない問題があって、どうしようかと考え中。
現状 macOS版だけなので、iOS等にも対応するのと、technique拡張に対応するのが次の課題。
JSceneKitに移植するのがその次の課題。

実際にglTFのパーサを書いてみて感じた利点・欠点はこんな感じ。

利点

  • ロイヤリティフリー
  • JSON形式なので構文解析は自前で作らなくて済む
  • パラメータがOpenGL/WebGLと同じ定数を使っているので、値の変換をせずにそのまま使える(WebGLで使うなら)
  • 上手くやればバイナリデータをそのままGPUに転送できる(WebGLで使うなら)
  • 物理ベースレンダリングに対応している
  • 拡張性が考慮されている

欠点

  • まだ仕様が定まってなさそうな箇所が多い(特に拡張仕様)
  • データ間の関係性が曖昧(一つのデータを違う用途で使い回せたりしてパースし辛いとか、weightsとprimitives/targetsの対応付けとか)
  • WebGL以外で対応しようとするとちょっと大変(特にユーザ定義のシェーダ対応)
  • モーフィングのアニメーションデータが膨大になる(キーフレーム毎に全モーフィングのウェイトを記録する仕様なので、MMDみたいに表情がたくさんあるモデルだと、データ量が増える)
  • MMDのようにモデルとモーションの組み合わせを変えるのが困難(MMDが特殊なのかも)
  • IK・物理演算には対応していない
あとこれ → "Positive rotation is counterclockwise."
OpenGLは回転方向右ねじだったのに逆になったのかな?と思って逆回転で実装したけど、やっぱり右ねじじゃないか!
どっちの方向から見た時に反時計回りなのか書いておくれよ!案の定サンプルモデルのGIF画像が仕様と逆回転しちゃってるでしょうが!

仕様は未完成っぽいけど、今後使いやすくなっていきそう。SceneKitはそのうちglTFを正式サポートするかも?

2017/08/27(Sun) 04:59:04

 SceneKit でテクスチャが赤くなる問題 / magicien 

imageSceneKitでモデルに適用したテクスチャが赤くなる問題があった。

問題が発生するのは、indexed-colorのPNGファイルの一部。原因はよく分からない。
青と緑の成分が無視されている上に、本来sRGBなのにリニアRGBとして処理されているようだ。
モデル自体を変更できるなら、画像ファイルを別のフォーマットに変換すれば良いけれど、できることならプログラム側で対処したい。

で、色々試した結果、NSImageで読み込むと赤くなるが、CGImageで読み込むと大丈夫だということに気づいた。何が何やら...

追記:iOSで試したらUIImageでもCGImageでも色が赤くなってしまった...BugReport送ってみたけど対応してもらえるかなぁ。

2017/08/23(Wed) 07:56:58

 Swift4 で JSON のパース / magicien 

Swift4からCodable、JSONDecoderが使えるようになって、JSONが簡単にパースできるようになった。
とはいえ、すんなり実装できなかった箇所もあったので、試した結果をいくつか記録しておこう。Gistを埋め込む練習ともいう。

基本的な使い方

struct に Codable を付けてあげるだけで勝手にパースしてくれる。structではなく、classでも同じ。
構造体とJSONのデータが食い違っている場合は、DecodingErrorが返ってくる。

必須でない項目、入れ子

Optional型にしておくと、対象のデータが無くてもエラーにならない。 配列はそのまま配列として定義すれば良い。object型の場合は、Codableな構造体かクラスを使う。

キー名を変える

JSONでは user_name だけど、Swiftでは userName という名前を使いたい、という場合、enum CodingKeys を設定する。
CodingKeysを書いた場合、caseに書いてあるものだけパースされる。

自分でパースする

大分大変だけど、init(for decoder: Decoder) を使って自分でパースもできる。
初期化時に何か計算とかしたい場合に。

デフォルト値を指定する

ここから、ちょっとハマったところ。
やりたかったことは次のとおり。
  • 値が存在しなかった場合のデフォルト値を指定したい
  • initは実装したくない
  • JSONとSwiftで同じキー名を使いたい
  • せっかくデフォルト値があるのだから、Optional型にしたくない
  • デフォルト値と同じ値を明示的に設定したかどうか分かるようにしたい
ちなみに、var name: String? = "NoName" としても、パース時に nil で上書きされてしまう。

パースするキーを後から追加する

これもかなり複雑。
Decoderは、CodingUserInfoKey を使ってユーザデータを受け渡しすることができる。
decoder.userInfo[ CodingUserInfoKey(rawValue: "hogehoge")! ] = "UserData" // こんな感じ
やりたかったことは、次のとおり。
  • JSONをパースして何かしらデータを生成するframeworkを作りたい
  • Codableの構造体はユーザには見えないようにしたい
  • ユーザがデコード時にパースするキーとデータ構造を動的に追加できるようにしたい
この例だと、"additionalInfo" の下の "website" の情報を取得している。
ぶっちゃけてしまうと、glTF の extensions/extras のデータをパースしたかった。

パース中のデータのパスを確認する

init(from decoder: Decoder) で、今自分がどのパスにいるのかによって処理を変えたい場合。
glTF の KHR_materials_common の対応に必要だった。ライトとかノードのマテリアルとか、用途も中身も違うものに同じ名前をつけるのやめてよ!

良い例が思いつかないので、実際のglTFのデータの抜粋をパースしてみる。

ジェネリクスとか、Computed Propertiesとか使えば、もうちょっとおしゃれになるかもしれない。

[Float]をSCNVector3・4に変換する

おまけ。encodeも真面目に書いた。
2017/08/22(Tue) 18:20:12

 SceneKitの物理ベースレンダリング(PBR) / magicien 

SceneKitで物理ベースレンダリングを使うには、SCNMaterial.lightingModel = .physicallyBased と設定すれば良い。
つまり、材質によって物理ベースにしたりしなかったりできる。ややこしいね!

physicallyBased の説明によると、
  • 描画に使う SCNMaterial の主なパラメータは、diffuse、roughness、metalness の3つ。
  • 細かい表現には、normal、ambientOcclusion、selfIllumination を使う。
  • ambient、specular、reflective、shininess、fresnelExponent、locksAmbientWithDiffuse は無視される。
  • 他に SCNScene.lightingEnvironment、SCNCamera.wantsHDR を設定すると良い感じになる。
ということらしい。言及すらされない emission さんかわいそう。
ただし、renderingAPI が Metal でないとPBRが有効にならない。GLSLでも実装上問題無いと思うんだけど、シェーダを二種類書くのが面倒だったのかな。

物理ベースでない場合、水面に映り込む背景等は、reflectiveに設定していたけれど、物理ベースの場合は、SCNScene.lightingEnvironment を使うようだ。材質毎に背景を変える理由もないし、こっちの方が良い気がする。
個別に設定したい場合は、selfIllumination を使うと、diffuseの計算に使うテクスチャを変えることができるらしい。
PBRでemissionを使いたい場合、emissionを設定した上で、selfIllumination.content を nil に設定すれば良いようだ。

JSceneKitでシェーダを書くと、上記情報だけでは、フレネル項のパラメータが一つ足りないことに気づく。物理ベースじゃなくてもフレネル反射はしていたので、これまで適当に固定値を割り当てて計算していたけれど、SceneKitではどう計算しているのだろう。
試しにFox2をPBRで描画してみたら、地面が暗すぎてよく見えなくなった。というわけでHDR(トーンマッピング)にも対応せねばなるまい。

2017/08/16(Wed) 04:10:41

 JSceneKitでMMDモデル・モーション表示デモ公開 / magicien 

紹介動画をアップしました。

デモページはこちら。
ニコニ立体ちゃん(アリシア・ソリッド)のモデルを初めて表示したとき、パンツ丸見えじゃん...どこがどうバグったらこんなことになるんだ...と思って、公式サイトを確認しに行ったら、元から丸見えだったよ!せっかく縦回転の角度に制限付けたのに台無しだよ!

2017/08/13(Sun) 09:58:25

 TGA画像表示用ライブラリを作った / magicien 

とあるモデルを表示しようとしたら、テクスチャがtgaファイルのためブラウザでうまく読み込めなかった。
仕方ないのでパーサを作り、ついでにnpmのパッケージとして公開した。

Githubページはこちら
調べてみたらTGAは結構古いフォーマットみたいで、今後ブラウザがサポートするのは望み薄。需要はほぼ無い気がするけど、WebGLでtgaのテクスチャを使いたい人が自分以外にもいるかもしれないので、パッケージ化しておくことにした。

サンプルページにはレナさんの画像を使わせていただいた。Gimpでtgaに変換したものを表示させてみたが、うまくいったようだ。画像処理系はサンプルに困ったらレナさんを使えばいいや、というのがあって気が楽で良い。

3Dでサンプルとして自由に使えそうなのだと、ティーポットとかうさぎとかだろうか。でもMMDみたいにボーンがあって、さらにアニメーションも含めて気兼ねなく使えて、自由に共有できるものがなかなか無い。

2017/08/05(Sat) 13:50:10