css3で表示中のサイトのDOM構造を3Dにして回転させるブックマークレット

ChromeSafariの最新版で動きます。

  • 自動で回転
javascript:(function(){var%20url='http://ndruger.lolipop.jp/hatena/20110919/css_3d_bookmarklet.js';var%20e=document.createElement('script');e.charset='UTF-8';e.src=url;document.getElementsByTagName('head')[0].appendChild(e);})();
  • 手動で回転
javascript:(function(){var%20url='http://ndruger.lolipop.jp/hatena/20110919/css_3d_bookmarklet.js?mode=manual';var%20e=document.createElement('script');e.charset='UTF-8';e.src=url;document.getElementsByTagName('head')[0].appendChild(e);})();
説明

DOMツリー構造の子孫を辿ってcss transforms 3Dのtransform3Dを設定しています。子孫を辿るたびにz座標を増やしています。
ページの表示がDOMツリーでどのように実現されているのかが視覚的に分かるかもと思って作ったのですが、役立つケースは少なそうです。

iPhoneのブラウザ上で30FPS以上でヌルヌル動く3Dゲームを作ってみた

iPhoneでのCSS 3D Transforms利用の可能性を探るために、CSS 3D Transformsを使って3Dゲームを作ってみました。
javascriptによる3DゲームですがiPhoneのブラウザで結構スムーズに動きます。

デモページ

http://ndruger.lolipop.jp/hatena/20110705/css_3d_game/css_3d_game.htm

  • 動作環境
    • iPhone4 OS 4.3
    • Chrome14, Safari5
      • iPhone用ゲームですが、デバッグ用にマウスのドラッグでも操作できます
  • 非対応環境の例
    • Android
      • 傾きがブラウザから取れないので動きません。
    • iPod touch 第2世代 OS 4.2.1
      • deviceorientationイベントが発火しないので動きません。devicemotionイベントは発火するのでそちらで回転の検出を実装するようにすれば動きそうですがまだしていません。
    • Firefox
      • CSS 3D Transformsに対応してないので動きません

ゲーム内容

  • iPhoneを傾けて赤い立方体を転がらせて、ゴールである青い立方体まで移動させます
  • 床にある穴に落ちるとゲームオーバーです
  • ステージは2-4まであります

iPhoneのブラウザでの3Dゲーム作成の問題と解決

本ゲーム作成の動機です。

問題

まず、iPhoneは現在WebGLに対応していません。

手動でjavascriptモデリングして3Dをcanvasに描画する場合でも、iPhonecanvasは描画する対象が多くなればすぐにFPSが下がるため、高いFPSを維持したまま全体を再描画するケース(視点移動など)に対応させることが難しいです。

CSS 3D Transformsによる解決

iPhoneCSS 3D Transformsに対応しており、これを利用すると、ブラウザとその移植層の3D処理により高速な描画が実現できます。
例えば下記の例では、transformプロパティのtranslateを指定した300個のdiv要素の親を、フリックを使ってiPhoneでもスムーズに回転させることが出来ます。

CSS 3D Transformsを利用すれば、divを好きな角度に回転でき、位置もtranslate()を利用して再レイアウトなしで変更できます。
これにより、下記のdivを「ポリゴン」として利用して下記のようなことが実現できます。

  • divを4個使って4角錐の実現(背景画像を利用すればdivを表示上3角形としても利用できます)。
  • divを6個使って立方体の実現

また、上記の例のように、CSS 3D Transformsは祖先に適用させれば、その子孫全体が影響するため、3Dのモデルを扱いやすい形になっています(SceneJSのjsonツリーがDOMツリーになった感じです)。
よって、動かしたいグループには親要素を作って、それにCSS 3D Transformsを適応するとグループ全体が動きます。

本ゲームの実装説明

  • 表示されているFPSは、赤い立方体の移動のループのFPSです。
  • 端末の傾きによる赤いブロックの移動と回転
    • 上で取得した回転はクォータニオンなので、回転行列を経由して、オイラー角(x,y,z)に変換します
    • オイラー角(x,y,z)の回転から、x,y軸の傾きを使って移動方向・移動量・回転量を決めます
  • ゲームオブジェクトの実装
    • 上下左右の壁をそれぞれボックスとして作っています。1つのボックスは6つのdivから出来ています。
    • 最初はシンプルに壁や床などもタイル単位でボックスにしてそれを敷き詰めようとしたのですが、動作が重くなったのでまとめてしまいました。

結果の考察

本ゲームにより、transformプロパティを指定しているdiv要素が少ない場合、transformの変更による3Dアニメーションは非常にスムーズに動くことが確認できました。

興味深いことに、CSS Animationでtransformプロパティを変更中に、メインループにて他の要素のtransformプロパティを変更すると、傾きの変更による表示が時々カクカクするようになりました。
そのため今回のゲームでは、青いブロックの回転もCSS Animationを使わず、メインループの中で回転するようにしています。

うまく使えばiPhoneのブラウザでスムーズに動く単純な3Dゲームがいろいろ作れそうです。ルーブックキューブとか。

iPhoneでヌルヌルキャラクターが動くゲームを作る方法

iPhoneのキャラクターがヌルヌル動いて、それらのキャラクターにタップ・フリックでいろいろ出来るアクションゲームの作成方法

  • canvasを使ったり、left, topをいじるとキャラクターのスムーズでないため、css animations + css transformsのtranslate()でキャラクターを動かす
  • 1) touchイベントのリスナーで、動いているキャラクターに対するアクションが実装できる場合
    • touchイベントのリスナーを使う
  • 2) touchイベントのリスナーで、動いているキャラクターに対するアクションが実装できない場合(例: 特定直線上の敵キャラクターへの攻撃等)
    • 自分でキャラクターの座標を計算して、ヒットテストを行う
      • css transformsの動きは、ベジェ曲線を利用しているので、ベジェ曲線の式を使えば、時刻tから現在位置が割り出せる
      • transition-timing-functionごとの値も仕様に書いてあるので、その係数を利用する
      • 問題は、css transformsの時刻tは、横軸上の値であり、ベジェ曲線では、横軸の値から縦軸の値を直接出すことはできない。
      • そこで、ニュートン法を使ってt(時刻のtではない)を近似して、近似したtから縦軸上の値を算出する
      • 後は縦軸上の値にtranslate()で渡したx,yの値をそれぞれかければ、現在時刻tのx,yの位置が計算できる
      • 50%などで区切ると、それぞれの区間にtransition-timing-functionが適応されるので注意

iPhoneのブラウザでのアニメーション手法を比較

基本

  • 拡大・縮小を避ける
    • 下記の指定
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;">
      • iPhone4の解像度はiPhone3Gより縦・横に倍大きいが、width=device-widthを指定してもiPhone3Gと同じ320pxが返り、表示時に2倍に拡大されて描画される。style指定で見かけ上の拡大を避けれなくはないが、今回はしない。
    • img.width / img.heighが画像の実サイズと異なる値を入れたり、canvasでの拡大をすると遅くなるのでしない
  • タイマーはまとめる
    • ゲームと同じように1つのループで行う。タイマーを増やすと遅くなるし、canvasならclearRect()が増える。
  • 移動量は経過時間ベースで計算する
    • 遅くなってスムーズさが落ちても、全体の完了時間は変わらないようにする。
  • 少数の位置で描画しない
    • 遅くなるため、CSSのtop, leftやdrawImage()の引数に小数を利用しない。整数化はMath.floor()より~~の方が速い。

比較

iPhone4(MC605J) OS 4.3で比較。

canvas
DOMからinline styleのleft/topを変更
CSS Animationでleft/topを利用
CSS Animationでtranslate()を利用

他のテクニック

canvasでclearRect()を全体にせず、個々のキャラクターの範囲に対して行う
動いてない奴の描画を防ぐためdirty flag等を利用する
  • styleやcanvasに変更など非常に重い処理の前に、動かす必要のある物か判定して、不要なら変更しない。
postMessage()によるsetZeroTimeout()の利用

適切な選択

  • スムーズさを優先するならば、可能な限りCSS Animationでtranslate()を利用するが、やりたいことが制限にひっかかった場合、その制限に応じて、他の奴を選択する

javascriptを使わず、html + cssだけでクリックシューティングゲームを作ってみた

CSSで描かれたぐぬぬ画像などを見てたら、ふとhtml + CSSだけでアクションゲームが作れないかと思ったので作ってみました。
ページ移動すればいくらでも可能ですが、それだとつまらないので、1ページ内で作るという縛りもつけてます。

デモページ

http://ndruger.lolipop.jp//hatena/20110429/css_game/css_game.htm
Chrome / Safariの最新版で動きます。Firefox / IEだと動きません。

技術的な説明

アニメーションはCSS Animationを使うだけなので簡単ですが、問題は状態管理です。

アクションゲームを作るには、基本的に下記の要素が必要です。

  • 1. ユーザー入力に対する状態の変更
  • 2. 状態の保持
  • 3. 状態に応じて見た目を変更

これらをhtml + CSSで実現する方法を考えます。

"1. ユーザー入力に対する状態の変更"を実装するには、:hover, :activeなどが思い浮かびますが、それらでは"2. 状態の保持"ができません。
よって、疑似クラスの状態変化が継続する、:visited, :focus, :checkedなどが使えそうです。

最初は、aタグのフラグメントジャンプ()を使って:visitedで状態を変えようとしたのですが、最近のブラウザは訪問履歴読み取り防止のため、:visitedで有効なプロパティがcolorなどに限られていて、ゲームに使うのは難しいと分かりました。
:focusは別な場所をクリックすると解除されるのであきらめました。
残ったのは:checkedですが、inputタグのtype=radio, checkboxならユーザー入力に応じて状態を記憶できるので、これを使います。


次に"3. 状態に応じて見た目を変更"の実現ですが、inputタグのtype=radio, checkboxを使った場合、その要素にどうやってキャラクター画像とマップさせるかが問題です。

  • CSSには親・祖先セレクタがないので、input:checkedの親要素でbackground-imageプロパティを適応できない
  • inputタグのtype=radio, checkboxはbackground-imageプロパティが反映されない
  • inputタグはDHTMLで無理矢理付けない限り子要素を持てない -> 子要素で画像を表示するなどのテクニックができない

この問題を解決するために、CSS3のappearanceプロパティを使います。
使っているコンテンツを今まで見たことがないのですが、このプロパティを"-moz-appearance: button;-webkit-appearance: button;"のように指定すると、background-imageプロパティが反映されるようになります。CSS3の仕様ではこの挙動は明示されてません。


ここまでいけば後は簡単です。
アニメーションしている牛がクリックされて:checkedが有効になると、牛の取得アイコンとして適切なbackground-image, positionに変えてしまいます。
ついでにスコアを表示するため、type=radioの同じnameの要素を作って、ユーザーのクリックでそちらは:checkedが解除されるようにします。
そして、:checkedでレイアウトが変更され、スコアの画像がずれるようにすると、牛のクリックごとにスコアが上がるようにできます。

Documentに繋がってないDOMツリーから要素を探す場合querySelector()を使う

正確にはDocumentのroot elementが属するツリーのサブツリーでないDOMツリーから要素を探す場合。下記のようなケース。

  • htmlの塊をテンプレートとして使いたいので、Documentに繋がってない要素にinnerHTMLでhtmlの塊を挿入する
  • そのhtmlの塊の中に、属性の変更をしたい要素があるので、何らかの方法でその要素を取り出したい
  • その時点でDocumentに繋がってないので、getElementById()等で取り出せない(DocumentFragmentはDocumentを継承してないのでそのようなIFはない)
  • ツリーをjavascriptの世界で辿るのはやりたくない

querySelector()やquerySelectorAll()はNodeSelector IFのメソッドなので、DocumentFragment, Elementなどが継承している。
よって、これらを利用する。

	var ele = document.createElement('div');
	ele.innerHTML = '<div id="neko1">neko1<div id="neko2">neko2</div></div>';
	console.log(ele.querySelector('#neko2'));

Stream APIを使ってAndroidのカメラの映像をjavascriptでリアルタイム顔認識(難航中)

こてさきAjax:Stream APIで、カメラの映像をWebSocket ライブ配信 - livedoor Blog(ブログ)
http://blog.livedoor.jp/kotesaki/archives/1667452.html

上記のブログで、Stream APIを使ってカメラの映像をブラウザで取得できると知ったので、下記のjavascriptで顔認識ができるライブラリを使って、カメラの映像の顔認識を試してみた。

JavaScript+canvasで顔認識 - 開発な日々
http://hinata.in/blog/20100227231617.html

実行

結果

顔認識に成功すると、下記のように正しく検出される。
左がカメラの映像そのままで、右が顔認識により赤い枠を追加した表示。
http://ndruger.lolipop.jp/hatena/20110329/js_camera_face_detect.png

ただ、顔認識に失敗したときに、非常に重たくなり、Opera mobileが落ちる。要分析と改善。

Stream APIの利用方法の検討

  • カメラの映像に対してjavascriptVRとかもしたいけど厳しそう。typed arrayでの改善度合いをチェックしたい。
  • いっそwebsocketでサーバーに流して、サーバーがいろいろな処理をした結果を返すのもおもしろいかも。