Pages

Politicoの季節

次期アメリカ大統領選の出馬宣言が始まり、ワシントンに「政治の季節」が近づくにつれて、政治専門サイト、ポリティコの閲覧者が増加し、ユニークユーザーで月間400万人を超えたらしい。類似サイトのTheHill.comは100万人程度、RollCall.comは67万人(7月)で、圧倒的だ。ニュースレター形式で主要政治ニュースをまとめたPlaybookはワシントン住民の必読になっている。

約33,000部のフリーペーパー(プリント版)の求人広告がこれまでは主収入(8割)だったが、現在ではオンライン収入がプリント収入を上回ったと、theWrap紙は書いている。年間2500ドルの有料サービス「Politico Pro」は、エネルギー、テクノロジー、ヘルスケアのニュースを特別に配信している。これらの分野のシンポジウムも主催し、収入源にしている。

80人から始まった記者は150人まで増え、近いうちに200人を超える見通し。激務だが、メジャー紙の政治記者に匹敵するサラリーを払っている。Politicoは4年周期で必ず来る「書き入れ時」を迎える。

theWrap : D.C. Drama Has Been Very Good for Politico

日経、民主党代表選を生中継


日経はメディアプレーヤーウインドウで、埋め込みではない。

ニコニコ動画

民主党主体のUStreamサイト
日経が29日、会員向けに民主党代表選挙をオンラインで生中継した。映像協力としてテレビ東京、配信協力でJストリームが明示されていたが、プレーヤーの技術は、Flashと非Flash(iPhoneなど)を共存させるBrightcoveを使っていた。地上波デジタルとの時差は1秒程度だった。

ただ、解説のないだだ漏れ映像ほど悲しいものはない。NYTimesがトップページに埋め込み生中継をしてもう2年になる。

一方、民主党自身もUstreamを使って映像を配信、配信時間は3時間46分で、延べ視聴者数は108万1283人、最大同時接続数13万7988人を記録したとしている。また、ニコニコ動画も10万人以上集めていた。

気になるのは、果たして、この視聴者のうち、どの程度の割合がNHKの解説付き生中継を見ないでいられたかどうかだ。生中継というより、現場というのはかくも散漫で、暇な人間しかつき合えない。日経なら、暇な政治部OBや予備デスクが床屋談義さえすれば面白いコンテンツになったのに。

巨大台風の報道手法

27日から28日にかけてワシントンやニューヨークなどを直撃するおそれがあるハリケーン「アイリーン」について、刻々と変わる気象予報や避難命令について、関係があるマイアミ・ヘラルド(Tampa Bay.com)やワシントンポスト、NYタイムズが、新しい種類のマルチメディアコンテンツを展開した。
NYTimseは、(震災時の日経と同様)公的サービスとして、アクセスを開放し、公衆無線LAN会社はマンハッタン地域のアクセスを無料にした。

最強コンテンツメーカー、NYTimesグラフィックチームの避難命令地域マップは強烈だ。衛星画像を入手して、GoogleMapに貼付けるのは、オルソ化などの画像処理をしなければならない。
停電地図はシンプルながらセンスが光る

マイアミヘラルドの多機能地図は、多機能すぎて使いにくいが、コンピュータ予想などの多元データをフラッシュ地図にオーバーレイしなければならない(ビットマップの重ね置きではなく、フラッシュのオブジェクトとして配置しているので、clickableになっている)。ここ数年の技術的な試行錯誤が生きている。







WebGLの基本1




Learning webGLのチュートリアルの4まで読んだコメント。これは大変だ。

基本的に、シェーダー言語で計算式を記述しておき、javascript側からデータを転送し、描画命令を出す。
反射やテキスチャーもシェーダーに計算を依頼すればよく、高度なシェーダーをプログラムすればCPU側の負担はほとんどなくなるというのが最大の利点ということのようだ。

以下のコードを基盤にして作ったのがこれ。結果は選外でした。

<script type="text/javascript"> 
    var gl;
    // Loadしたら呼ぶ
    function webGLStart() {
        var canvas = document.getElementById("canvas20110811");
        initGL(canvas);
        initShaders()
        initBuffers();
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);
        tick();
    }
    // 初期化 canvasでwebGLが使えるかどうか
    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }
   // Shaderの初期化
    var shaderProgram;
    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");
 
        shaderProgram = gl.createProgram();
        // プログラムに対してシェーダーを割当
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);
 
        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }
 
        gl.useProgram(shaderProgram);
        //  属性を付加する(もともとある訳ではない)
        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        // 関連つける
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
 
        shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
        gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
 
        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    }
 
    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) return null;
        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }
        // シェーダープログラムを取り込む 
        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }
        //  コンパイルする 
        gl.shaderSource(shader, str);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
    }
  

物体の設定は以下のようになる。


    var mvMatrix = mat4.create();
    var mvMatrixStack = [];
    var pMatrix = mat4.create();
 
    var pyramidVertexPositionBuffer;
    var pyramidVertexColorBuffer;
    var cubeVertexPositionBuffer;
    var cubeVertexColorBuffer;
    var cubeVertexIndexBuffer;
 
    function initBuffers() {
 //  ひとつ目の物体
        // webGL側に頂点座標のバッファーを作り、バインドする
        pyramidVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
        //  描く3角形の頂点座標の連続
        var vertices = [
            // Front face
             0.0,  1.0,  0.0,
            -1.0, -1.0,  1.0,
             1.0, -1.0,  1.0,
            // Right face
             0.0,  1.0,  0.0,
             1.0, -1.0,  1.0,
             1.0, -1.0, -1.0,
            // Back face
             0.0,  1.0,  0.0,
             1.0, -1.0, -1.0,
            -1.0, -1.0, -1.0,
            // Left face
             0.0,  1.0,  0.0,
            -1.0, -1.0, -1.0,
            -1.0, -1.0,  1.0
        ];
        // webGL側(この場合はバインドしたpyramidVertexPositionBuffer)にverticesを転送する
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        // あとで使うため
        pyramidVertexPositionBuffer.itemSize = 3;
        pyramidVertexPositionBuffer.numItems = 12;
 
        //  同じことを色についても行う
        pyramidVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
        var colors = [
            // Front face
            1.0, 0.0, 0.0, 1.0,
            0.0, 1.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 1.0,
 
            // Right face
            1.0, 0.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 1.0,
            0.0, 1.0, 0.0, 1.0,
 
            // Back face
            1.0, 0.0, 0.0, 1.0,
            0.0, 1.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 1.0,
 
            // Left face
            1.0, 0.0, 0.0, 1.0,
            0.0, 0.0, 1.0, 1.0,
            0.0, 1.0, 0.0, 1.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        pyramidVertexColorBuffer.itemSize = 4;
        pyramidVertexColorBuffer.numItems = 12;
 
 //  二つ目の物体
        cubeVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
        vertices = [
            // Front face
            -1.0, -1.0,  1.0,
             1.0, -1.0,  1.0,
             1.0,  1.0,  1.0,
            -1.0,  1.0,  1.0,
 
            // Back face
            -1.0, -1.0, -1.0,
            -1.0,  1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0, -1.0, -1.0,
 
            // Top face
            -1.0,  1.0, -1.0,
            -1.0,  1.0,  1.0,
             1.0,  1.0,  1.0,
             1.0,  1.0, -1.0,
 
            // Bottom face
            -1.0, -1.0, -1.0,
             1.0, -1.0, -1.0,
             1.0, -1.0,  1.0,
            -1.0, -1.0,  1.0,
 
            // Right face
             1.0, -1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0,  1.0,  1.0,
             1.0, -1.0,  1.0,
 
            // Left face
            -1.0, -1.0, -1.0,
            -1.0, -1.0,  1.0,
            -1.0,  1.0,  1.0,
            -1.0,  1.0, -1.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        cubeVertexPositionBuffer.itemSize = 3;
        cubeVertexPositionBuffer.numItems = 24;
 
        cubeVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
        colors = [
            [1.0, 0.0, 0.0, 1.0], // Front face
            [1.0, 1.0, 0.0, 1.0], // Back face
            [0.0, 1.0, 0.0, 1.0], // Top face
            [1.0, 0.5, 0.5, 1.0], // Bottom face
            [1.0, 0.0, 1.0, 1.0], // Right face
            [0.0, 0.0, 1.0, 1.0]  // Left face
        ];

        var unpackedColors = [];
        for (var i in colors) {
            var color = colors[i];
            for (var j=0; j < 4; j++) {
                unpackedColors = unpackedColors.concat(color);
            }
        }
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unpackedColors), gl.STATIC_DRAW);
        cubeVertexColorBuffer.itemSize = 4;
        cubeVertexColorBuffer.numItems = 24;
 
        cubeVertexIndexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
        var cubeVertexIndices = [
            0, 1, 2,      0, 2, 3,    // Front face
            4, 5, 6,      4, 6, 7,    // Back face
            8, 9, 10,     8, 10, 11,  // Top face
            12, 13, 14,   12, 14, 15, // Bottom face
            16, 17, 18,   16, 18, 19, // Right face
            20, 21, 22,   20, 22, 23  // Left face
        ];
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
        cubeVertexIndexBuffer.itemSize = 1;
        cubeVertexIndexBuffer.numItems = 36;
    }
    function mvPushMatrix() {
        var copy = mat4.create();
        mat4.set(mvMatrix, copy);
        mvMatrixStack.push(copy);
    }
    function mvPopMatrix() {
        if (mvMatrixStack.length == 0) {
            throw "Invalid popMatrix!";
        }
        mvMatrix = mvMatrixStack.pop();
    }
    function setMatrixUniforms() {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    }
    function degToRad(degrees) {
        return degrees * Math.PI / 180;
    }

    var rPyramid = 0;
    var rCube = 0;
    // 描画
    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        // クリア
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        // 画角45度、画面比、0.1単位より近いものと100単位より遠いものは描かない
        mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
        // 移動行列を生成する
        //  最初はmvMatrix単位行列にする
        mat4.identity(mvMatrix);
        //  左に1.4、奥に8移動する
        mat4.translate(mvMatrix, [-1.5, 0.0, -8.0]);
        // 
        mvPushMatrix();
        mat4.rotate(mvMatrix, degToRad(rPyramid), [0, 1, 0]);

        // 描画を引き起こす
        gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, pyramidVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
        // 現在のモデルビュー行列をセットする
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
        // 描く命令
        gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems);
 
        mvPopMatrix();
        // キューブについて繰り返す
        mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
        mvPushMatrix();
        mat4.rotate(mvMatrix, degToRad(rCube), [1, 1, 1]);
 
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
        gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
        mvPopMatrix();
    }
 
    var lastTime = 0;
    function animate() {
        var timeNow = new Date().getTime();
        if (lastTime != 0) {
            var elapsed = timeNow - lastTime;
            rPyramid += (90 * elapsed) / 1000.0;
            rCube -= (75 * elapsed) / 1000.0;
        }
        lastTime = timeNow;
    }
    function tick() {
        requestAnimFrame(tick);
        drawScene();
        animate();
    } 
</script> 
 

GLSL(シェーダー言語)で描いたプログラムを用意する。

lt;script id="shader-fs" type="x-shader/x-fragment"> 
    #ifdef GL_ES
    precision highp float;
    #endif
    varying vec4 vColor;
    void main(void) {
        gl_FragColor = vColor;
    }
</script> 
 
<script id="shader-vs" type="x-shader/x-vertex"> 
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;
    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;
    varying vec4 vColor;
    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vColor = aVertexColor;
    }
</script>  



























ChemDoodleのWebGLライブラリ



iPadのようにFlashに対応していないプラットフォームで3D表現をする方法として、OpenGLのWeb版であるWebGLを使う手がある。

iPadの雑誌アプリで3Dで回転する人体などを実現するような技術を展開する会社「ChemDoodle」は元々デスクトップ向けに化学物質の組成を表現するグラフィックソフトを開発していた会社で、現在ではWebGL向けのライブラリを販売している。

果たしてiPhone/iPad対応が必要なのかはさておき、なめらかな球形や、原子炉の3Dモデルを完全にインタラクティブにするためにはWebGLを取り込む必要がある。(Flashでは力不足。ただし、相当しっかりしたライブラリを作らないとFlashの数倍の手間がかかる)
以下はChemDoodleの公開ライブラリによるもの。古いブラウザでは表示されない。

ActionscriptによるDelaunay Triangulation

Tercel::Diary::ProcessingでDelaunay分割(実装篇)を参考にしてAS3版を作ってみた。
オリジナルはProcessingを使っている(!)。鮮やかな重複削除処理は感動的。
ただ、特殊な場合には問題があるかも。

// パッケージ外クラス
import flash.geom.*;
class Circle{
 public var center:Point;
 public var radius:Number
 public function Circle(c:Point, r:Number){
  center = c;radius = r;
 }
}
class Triangle{
 public var p1:Point, p2:Point, p3:Point;  // 頂点
 public var valid:Boolean = true;
 public function Triangle(p1:Point, p2:Point, p3:Point){
     this.p1 = p1;this.p2 = p2;this.p3 = p3;
 }
 public function equals(t:Triangle):int{
     if (p1.equals(t.p1) && p2.equals(t.p2) && p3.equals(t.p3) ||
             p1.equals(t.p2) && p2.equals(t.p3) && p3.equals(t.p1) ||
             p1.equals(t.p3) && p2.equals(t.p1) && p3.equals(t.p2)) return 1;
        if (p1.equals(t.p3) && p2.equals(t.p2) && p3.equals(t.p1) ||
             p1.equals(t.p2) && p2.equals(t.p1) && p3.equals(t.p3) ||
             p1.equals(t.p1) && p2.equals(t.p3) && p3.equals(t.p2) ) return -1;
  return 0;
 }
 public function hasCommonPoints(t:Triangle):Boolean{
     return (p1.equals(t.p1) || p1.equals(t.p2) || p1.equals(t.p3) ||
            p2.equals(t.p1) || p2.equals(t.p2) || p2.equals(t.p3) ||
            p3.equals(t.p1) || p3.equals(t.p2) || p3.equals(t.p3) );  
 }
}
class delaunay{
 public var triangleSet:Array;  // 三角形リスト
 public function delaunay(ArrayList:Array) {
  DelaunayTriangulation(ArrayList);
 }
 public function DelaunayTriangulation(ArrayList:Array) {
  triangleSet = new Array();
  var hugeTriangle:Triangle = getHugeTriangle(new Point(0, 0), new Point(10000, 10000));
  triangleSet.push(hugeTriangle);
  var p:Point;
  var tmpTriangleSet:Vector.<Triangle>;
  var t:Triangle;
  var c:Circle;
  var i:int, j:int;
  for (i = 0;i < ArrayList.length;i++){
   p = Point(ArrayList[i]);
   tmpTriangleSet = new Vector.<Triangle>();
         for(j = 0;j < triangleSet.length;j++){
    t = Triangle(triangleSet[j]);
          c = getCircumscribedCirclesOfTriangle(t);
            if (Point.distance(c.center, p) <= c.radius) {
              addElementToRedundanciesMap(tmpTriangleSet, new Triangle(p, t.p1, t.p2));
              addElementToRedundanciesMap(tmpTriangleSet, new Triangle(p, t.p2, t.p3));
              addElementToRedundanciesMap(tmpTriangleSet, new Triangle(p, t.p3, t.p1));
              triangleSet.splice(j, 1);
     j--;
    }
         }
         // 重複のないものを三角形リストに追加 
         for(j = 0;j < tmpTriangleSet.length; j++) {
    if(tmpTriangleSet[j].valid) triangleSet.push(tmpTriangleSet[j]);
   }
       }
     for(i = 0 ; i < triangleSet.length; i++){
         t = Triangle(triangleSet[i]);
         if(hugeTriangle.hasCommonPoints(t)) {
             triangleSet.splice(i, 1);
    i--;
         }
       }
 }
 private function addElementToRedundanciesMap(v:Vector.<Triangle>, t:Triangle):void {
  for(var i = 0;i < v.length;i++){
   if(Triangle(v[i]).equals(t) != 0){
    v[i].valid = false;
    return;
   }
  }
  v.push(t);
  return;
 }
 private function getHugeTriangle(start:Point, end:Point):Triangle {
  return new Triangle(new Point(-int.MAX_VALUE,-int.MAX_VALUE),
       new Point(int.MAX_VALUE,-int.MAX_VALUE),
       new Point(0,int.MAX_VALUE));
 }
 private function getCircumscribedCirclesOfTriangle(t:Triangle):Circle{
     var x1:Number = t.p1.x;
     var y1:Number = t.p1.y;
     var x2:Number = t.p2.x;
     var y2:Number = t.p2.y;
     var x3:Number = t.p3.x;
     var y3:Number = t.p3.y;
     var c:Number = 2.0 * ((x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1));
     var x:Number = ((y3 - y1) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) + (y1 - y2) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1))/c;
     var y:Number = ((x1 - x3) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) + (x2 - x1) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1))/c;
     var center:Point = new Point(x, y);
     var r:Number = Point.distance(center, t.p1);
  return new Circle(center, r);
 }
}