日記
blog移行しました。新しいblogで更新を続けています。
XMLェ…
ブログ作りなおそうかなーと思って、この Webサイト をみなおしてたら、Web ページのメタ情報としてダブリンコア (RDF) を混在させていたことを思い出した。バリデーターにかければ、グラフも取り出せて
みたいな感じになる。でも結局あまり意味なかったです多分。いまは OGP とかありますしね。
Web ページは XHTML にしてたけど、ブログのコメントで参照先のない数値参照とか混ぜられると XML パースエラーになるし、XML だから他の語彙混在できるけど、RDF くらいしか混ぜてなかったし、XHTML 意味なかったです多分。いまは HTML に SVG 混在できますしね。
XML で人と繋がりたいと思って FOAF 書いたけど、意味なかった多分。いまは Facebook とか Twitter とかソーシャルサービスがいっぱいありますしね。
昔の人が XML でやりたかったことは HTML5 や Web サービスでいまできていて、結果的にはよかったんじゃないですかね。
つぎブログ作りなおすときは XHTML にはしません…。
THREE.js をつかった WebGL サンプル集つくった
これまで少しずつ作りためた THREE.js のサンプルを整理して集めました。 基本的な内容から少しだけ応用したものまでいろいろです。THREE.js をこれから触ってみるっていう人にはコードを見て参考にしていただけるかも。 Github に置いてあるのでまるごとダウンロードしてゆっくり観察することもできます。 気が向いたら少しづつ増やしていきます。メディアクエリーと相性がよさそうな CSS 小技いくつか
新しい技術というと CSS3 のプロパティーの活用に目が行きがちですが、CSS2 でこれまで使う機会がなかった display:table 系の表示に注目してみるとかなり面白いことができます。table 系の表示は、IE7 までサポートされてきませんでしたが、IE8 からサポートされています。メディアクエリーはというと、IE9 以降ですので、メディアクエリーを採用した Web ページの構築とかなり相性がいいと言えるでしょう。
IE | 9+ |
---|---|
FIrefox | 3.5+ |
Chrome | 4 (or before) + |
Safari | 3.1+ |
Opera | 9.5+ |
table 系の表示にあまり注目されることがないように感じますので、これまで仕事でメディアクエリーを利用してくる中で考えた小技をこの記事に共有します。ここに載せているのは一応実際につかって検証済みですが、もし変な動作があったら教えてください。
上下を入れ替える
display プロパティーの table-row-group や table-header-group などの値を応用すれば、上下の入れ替えが可能です。説明だと分かりづらいので、demo をご参照ください。
この方法は、対象デバイスによって、コンテンツのプライオリティーが変わる場合に有効でしょう。また、table-footer-group をさらに加えれば、上、中、下の 3つを自由に入れ替えることもできます。
なお、これをさらに使いやすくしたような仕組みの CSS Grid Layout モジュールが CSS3 として策定中で、IE10 では利用することができます。
フレキシブルな横並び
フレキシブルな横並びというと、CSS3 の Flexible Box を思い浮かべる人も多いかもしれませんが、IE 9 では Flexible Box は利用できません。そのかわり、table 系の表示を利用すればそれに近いことができます。説明だと分かりづらいので、demo をご参照ください。
table-layout:fixed; を宣言すれば、均等の幅になりますし、これを外せば内容に合わせた横幅になります。
表組みを崩す
ここまで挙げた2つの例は、block の表示から table の表示にするという例でしたが、その逆も可能です。例えば、表組みは小さい画面だとかえって見づらいものです。そこで、小さい画面の場合には、table 表示を block 表示へと変えてあげることもできます。実際の動作は demo をご参照ください。
ただ、各ブラウザーにおいて少しだけ注意点があり、少し回りくどい方法で行わなければいけません
Webkit 向け
表組み関連の要素を全て block 表示にしておかないと、意図しない動作が起こります。忘れがちになりますが、tbody や tr も併せて block 表示にしておきます。
Firefox 向け
col 要素で幅を決めていると、block にした後もその幅が維持されてしまいます。ですので col 関連の要素の表示を消しておきます。
IE 向け
display:block; にしても表組みの表示を変更することができませんが、float :left などで強制的に block にしてやることで表組みの表示を崩すことができます。併せて width:100%; もしれておきます。padding を使えるように、box-sizing も加えておきます。
おまけ 1 : リンク部分を大きくする
メディアクエリーというと、大胆な変形を想像されるかもしれませんが、変形せずとも、指での操作を想定して、リンク部分だけ大きくしてあげる、といった気配りも大切です。こういった気配りだけでも十分にメディアクエリーを利用する価値があるでしょう。
おまけ 2 : 画像をフレキシブルにする
画像は width:100; height:auto; にするとグネグネと伸縮するようになります。これにより、大きな画像でも、デバイスの幅にフィットさせることができます。
さいごに
CSS2 でも色々できます。ここでは挙げませんでしたが、セレクターや before,after擬似要素も CSS2 です。そしてこれらの多くは IE8 でも使えます。CSS2 も研究してみるのと未だに面白いです。
あ、あと、メディアクエリーを利用するときには <meta name="viewport" content="width=device-width, initial-scale=1.0">
も忘れずに宣言しておきましょう
detecting click event in THREE.js
WebGL は、2D canvas と同様に 1つの canvas 要素の中に描画されるため、描画された 3D オブジェクトに対して、マウスイベント等を判定するのは少し大変そうに思えます。
しかし、THREE.js には Ray 判定の機能を提供しており、Ray によりクリックやマウスオーバーなどのイベントを取得することができます。
Ray というのは、3D のプログラムでは結構知られている方法で、例えば DirectX 用の資料にも同様の方法の解説があります。
Ray の仕組みは図にすると以下のようになります。画面上では 2D 空間のため、x, y だけの世界です。

Ray の考え方は x, y の 2D の点を、near 面から far 面まで伸びる線として扱います。そしてこの線に 3D オブジェクトが接したかどうかを判断します。

THREE.js では、次のような手順で Ray を利用します。
1. 判定するメッシュの集合を決める
判定したい mesh を meshArray
などのような配列に入れる。scene 内の全ての 3D オブジェクトを判定するなら特にここで配列を用意せず、あとで scene マルごとを利用することも可能
2. Projector を用意
new THREE.Projector();
3. カーソル位置をとって 3次元ベクトルに変換する
var x = (mouseX / renderer.domElement.width) * 2 - 1;
var y = - (mouseY / renderer.domElement.height) * 2 + 1;
var vector = new THREE.Vector3(x, y, 1);
4. Projector にカーソル位置のベクトルとカメラ位置を渡す
projector.unprojectVector(vector, camera);
5. カーソル位置の3次元ベクトルとカメラの位置を Ray に渡す
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
6. イベントで判定された 3D オブジェクトを取り出す
ray.intersectObjects(meshArray)
あるいは ray.intersectScene( scene )
に判定の結果、Ray と交差した全ての Mesh が手前から順番に入っている配列を取得できるのでそれをいろいろできる。判定を貫通させたくなければ、配列の [0] 番目のみ操作すればいい
例えば次のようなコードを書けばOK : detecting click event in THREE.js
window.addEventListener('DOMContentLoaded', (function(){
var width = 600;
var height = 400;
//シーン
var scene = new THREE.Scene();
//カメラ
var camera = new THREE.PerspectiveCamera( 40, width / height, 1, 1000 );
camera.position.x = 10;
camera.position.z = 20;
scene.add( camera );
//ライティング
var light = new THREE.DirectionalLight( 0xffffff, 1.5 );
light.position.set( 1, 1, 1 ).normalize();
scene.add( light );
var light2 = new THREE.DirectionalLight( 0xffffff );
light2.position.set( -1, -1, -1 ).normalize();
scene.add( light2 );
//レンダラー
renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
//レンダラーを append
document.body.appendChild( renderer.domElement );
var meshArray = [];
var geometry = new THREE.CubeGeometry(2, 2, 2, 6, 6, 6);
for(var i = 0; i < 5; i++){
meshArray[i] = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
meshArray[i].position.x = 2 * i;
meshArray[i].position.z = -i;
scene.add( meshArray[i] );
}
var theta = 0;
(function(){
renderer.render( scene, camera );
camera.position.x = 20 * Math.sin( theta * Math.PI / 360 );
camera.position.y = 20 * Math.sin( theta * Math.PI / 360 );
camera.position.z = 20 * Math.cos( theta * Math.PI / 360 );
camera.lookAt( scene.position );
theta++;
setTimeout(arguments.callee, 1000 / 32);
})();
// 任意の要素のオフセットを取得用関数 (あとで canvas のオフセット位置取得用)
var getElementPosition = function(element) {
var top = left = 0;
do {
top += element.offsetTop || 0;
left += element.offsetLeft || 0;
element = element.offsetParent;
}
while (element);
return {top: top, left: left};
}
//Ray 関連一式 たぶんこれ使いまわせば何でも行けると思う。
var projector = new THREE.Projector();
renderer.domElement.addEventListener('click', function(e){
var mouseX = e.clientX - getElementPosition(renderer.domElement).left;
var mouseY = e.clientY - getElementPosition(renderer.domElement).top;
var x = (mouseX / renderer.domElement.width) * 2 - 1;
var y = - (mouseY / renderer.domElement.height) * 2 + 1;
var vector = new THREE.Vector3(x, y, 1);
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObjects(meshArray);
if(intersects.length > 0){
console.log(intersects[ 0 ].object);
var color = Math.random() * 0xffffff;
intersects[ 0 ].object.material.color.setHex( color );
}
renderer.render( scene, camera );
}, false);
}), false);
New 3D physics engine – cannon.js
最近、3D の物理演算エンジンとして cannon.js 作ってるよ という方が現れました。
3D の物理演算エンジンとしてよく知られているライブラリーに、ammo.js や jiglib.js があります。そして、どちらかと言うと ammo.js が有名なようですが、元が bullet という C++ のライブラリーからの移植というだけあってかなかなかクセがあってめんどくさいです。
一方で、cannon.js は THREE.js のような扱いやすさと ammo.js のような物理演算を提供してくれます。現在はまだできることが少ないのですが THREE.js とセットでいい感じに育ってくれるといいなぁと思いつつ。
cannon.js と THREE.js を組み合わせると次のような物理演算が可能です : 球体と平面衝突の demo
cannon.js の利用方法は THREE.js に近く、次のように書いていきます。
1 初期設定など
var world = new CANNON.World();
world.gravity(new CANNON.Vec3(0 ,0, -50));
var bp = new CANNON.NaiveBroadphase();
world.broadphase(bp);
world.iterations(2);
読んだままのごとく…。broadphase というのは、ぶつかりそうなオブジェクトどうしにまとめて全部処理しないで済むようするための集合みたい
2 あとで使う配列の用意
var phys_bodies = [];
var phys_visuals = [];
後で使うための配列を用意しておきます
phys_bodies
: 剛体化した cannon のオブジェクト格納用phys_visuals
: 剛体化した 3D オブジェクト格納用 (THREE を併用しているならその Mesh)
3. 平面 (地面) の剛体をつくる
var groundShape = new CANNON.Plane(new CANNON.Vec3(0, 0, 1));
var groundBody = new CANNON.RigidBody(0, groundShape);
world.add(groundBody);
CANNON.Plane で平面を用意し、CANNON.RigidBody で剛体化し、cannon の world に add します
4. 球体の剛体を作る
var sphereShape = new CANNON.Sphere(1);
var sphereBody = new CANNON.RigidBody(5,sphereShape);
半径を決めてCANNON.Sphereで球体を作ります(上記は半径1)。そして剛体化します。このとき、THREE.js などの見た目側の半径と一致するようにしておきます。例えば new THREE.SphereGeometry( 1, 8, 8); みたいに。
4.2. 球体の初期位置を決める
var pos = new CANNON.Vec3(0, 0, i * 4 + 4);
sphereBody.setPosition(pos.x + randX, pos.y + randY, pos.z);
3 次元ベクトルにして剛体化した cannon の球体に渡します
4.3. world に球体情報を追加
phys_bodies.push(sphereBody);
phys_visuals.push(spheremesh);
world.add(sphereBody);
のように cannon の world に add します。ついでに一番最初に用意した配列に格納しておきます。
5. アニメーションさせる
function updatePhysics(){
// Step world
if(!world.paused){
world.step(1.0 / 60.0);
for(var i = 0, l = phys_bodies.length; i < l; i++){
phys_bodies[i].getPosition(phys_visuals[i].position);
phys_bodies[i].getOrientation(phys_visuals[i].quaternion);
}
}
}
連続レンダリングする際に、次のような関数で見た目オブジェクトと、cannon の位置情報を紐付けます
まとめ
THREE.js を併用している場合は、次のようになります。すごくわかりやすい! 実際に動く demo も用意してあります。ただ、2012 年4 月 2 日の段階では box 同士の衝突などはまだ解決されてないみたいです。でも今後にすごく期待。
<script src="Three_r48.js"></script>
<script src="cannon.min.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function(){
// Physics
var world = new CANNON.World();
world.gravity(new CANNON.Vec3(0 ,0, -50));
var bp = new CANNON.NaiveBroadphase();
world.broadphase(bp);
world.iterations(2);
var phys_bodies = [];
var phys_visuals = [];
var width = 1000;
var height = 400;
init();
animate();
function init() {
// scene
scene = new THREE.Scene();
// vamera
var near = 1;
var far = 1000;
camera = new THREE.PerspectiveCamera( 40, width / height, near, far );
camera.position.x = 0;
camera.position.y = -20;
camera.position.z = 10;
scene.add( camera );
// light
var light = new THREE.DirectionalLight( 0xffffff, 1.5 );
light.position.set(1, -1, 1 ).normalize();
scene.add( light );
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );
createScene();
}
function createScene( ) {
// ground
var geometry = new THREE.PlaneGeometry( 20, 20 );
var planeMaterial = new THREE.MeshBasicMaterial( { color: 0xeeeeee } );
var ground = new THREE.Mesh( geometry, planeMaterial );
scene.add( ground );
// ground plane
var groundShape = new CANNON.Plane(new CANNON.Vec3(0, 0, 1));
var groundBody = new CANNON.RigidBody(0, groundShape);
world.add(groundBody);
var sphere_geometry = new THREE.SphereGeometry( 1, 8, 8); //SphereGeometry( radius, segments, rings)
// Sphere on plane
var sphereShape = new CANNON.Sphere(1); // Sharing shape saves memory
for(var i = 0; i < 10; i++){
var sphereMaterial = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } );
var spheremesh = new THREE.Mesh( sphere_geometry, sphereMaterial );
scene.add( spheremesh );
spheremesh.useQuaternion = true;
// Physics
var randX = (Math.round(Math.random() * 9) + 1 - 5) * .2;
var randY = (Math.round(Math.random() * 9) + 1 - 5) * .2;
var sphereBody = new CANNON.RigidBody(5,sphereShape);
// start pos
var pos = new CANNON.Vec3(0, 0, i * 4 + 4);
sphereBody.setPosition(pos.x + randX, pos.y + randY, pos.z);
// Save initial positions for later
phys_bodies.push(sphereBody);
phys_visuals.push(spheremesh);
world.add(sphereBody);
}
}
function animate(){
requestAnimationFrame( animate );
updatePhysics();
render();
}
function updatePhysics(){
// Step world
if(!world.paused){
world.step(1.0 / 60.0);
for(var i = 0, l = phys_bodies.length; i < l; i++){
phys_bodies[i].getPosition(phys_visuals[i].position);
phys_bodies[i].getOrientation(phys_visuals[i].quaternion);
}
}
}
function render(){
camera.lookAt( scene.position );
renderer.clear();
renderer.render( scene, camera );
}
}, false);
</script>