ラベル Javascript の投稿を表示しています。 すべての投稿を表示
ラベル Javascript の投稿を表示しています。 すべての投稿を表示

2021年6月27日日曜日

Vue.jsのプロジェクトでThree.jsを使う

Vue.jsのプロジェクトでThree.jsを使ってみたのでその手順をまとめておく。


環境

DockerでVue.jsとApacheの環境を作成すると同様にDockerを使う。DockerfileにThree.jsのインストールを追加する。

FROM httpd:2.4

WORKDIR /app

# node.jsなどをインストール
RUN apt-get update \
 && apt-get -y install --no-install-recommends \
    nodejs \
    npm \
	curl \
 # キャッシュ削除
 && apt-get clean

# node.jsを最新にする
RUN npm install -g n \
 && n stable \
 && apt purge -y \
    nodejs \
	npm

# Vue.jsとCLIインストール
RUN ["/bin/bash", "-c", " \
    source ~/.bashrc \
     && npm install vue \
     && npm install -g @vue/cli \
     && npm install three \
 "]

EXPOSE 80 8080


Vue.jsプロジェクトの作成


Dockerコンテナを起動してVueプロジェクトを作成する。まずはコンテナを起動して中に入る。


新規でVueプロジェクトsampleを作成する。

今回はマニュアルを選択して次のように選択。
  • ? Please pick a preset: Manually select features
  • ? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
  • ? Choose a version of Vue.js that you want to start the project with 2.x
  • ? Use history mode for router? (Requires proper server setup for index fallback in production) No
  • ? Pick a linter / formatter config: Standard
  • ? Pick additional lint features: Lint on save
  • ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files


Three.jsでコンテンツ作成

Three.jsのサンプルをVue.jsで動作するようにする。Canvasコンポーネントを作成し、サンプルのHelloworldコンポーネントと置き換える。作成したプロジェクトのsrcディレクトリ配下のファイル構成を以下のようにする。

まずはcomponentsディレクトリ配下にCanvas.vueを追加する。

<template>
    <div ref="container" id="container">
    </div>
</template>

<script>
import * as THREE from 'three';

export default {
  name: 'Canvas',
  data () {
    return {
      camera: null,
      mesh: null,
      scene: null,
      renderer: null
    };
  },
  created () {
    this.init();
    window.addEventListener('resize', this.onWindowResize);
  },
  destroyed () {
    window.removeEventListener('resize', this.onWindowResize);
  },
  mounted () {
    this.animation();
    this.$refs.container.appendChild(this.renderer.domElement);
    this.onWindowResize();
  },
  methods: {
    init: function () {
      this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
      this.camera.position.z = 1;

      this.scene = new THREE.Scene();

      const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
      const material = new THREE.MeshNormalMaterial();

      this.mesh = new THREE.Mesh(geometry, material);
      this.scene.add(this.mesh);

      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.setAnimationLoop(this.animation);
    },
    animation: function (time) {
      this.mesh.rotation.x = time / 2000;
      this.mesh.rotation.y = time / 1000;

      this.renderer.render(this.scene, this.camera);
    },
    onWindowResize: function () {
      this.camera.aspect = window.innerHeight / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerHeight, window.innerHeight);
    }
  }
}
</script>

<style>
#container {
  margin: 0 auto;
  width: max-content;
}
</style>

次に追加したCanvas.vueをサンプルのHelloworldコンポーネントと置き換えるようにHome.vueを修正。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <!--<HelloWorld msg="Welcome to Your Vue.js App"/>-->
    <Canvas>
    </Canvas>
  </div>
</template>


<script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'
import Canvas from '@/components/Canvas.vue'

export default {
  name: 'Home',
  components: {
    // HelloWorld,
    Canvas
  }
}
</script>

このままだとサーバー起動時にエラーが発生するので、プロジェクトディレクトリ配下の.eslintrc.jsのrulesに「'semi': 'off'」を追加してESLintで行末の「;」をエラーとしないように設定する。

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'semi': 'off'
  }
}
ファイルの編集などが終わったら開発サーバを起動。

ブラウザでhttp://localhost:8080にアクセスすると以下のようなThree.jsのサンプルが表示される。


2021年5月16日日曜日

DockerでVue.jsとApacheの環境を作成する

Vue.jsとApacheの環境をDockerで作成する手順をまとめておく。


環境


Docker Desktop(Windows10 Pro)。


Dockerイメージ

Docker HubにあるDocker公式のApacheイメージ(httpd:2.4)を使う。Docerfileを用意してこのイメージにnode.jsやVue.jsなどをインストールする。

FROM httpd:2.4

WORKDIR /app

# node.jsなどをインストール
RUN apt-get update \
 && apt-get -y install --no-install-recommends \
    nodejs \
    npm \
	curl \
 # キャッシュ削除
 && apt-get clean

# node.jsを最新にする
RUN npm install -g n \
 && n stable \
 && apt purge -y \
    nodejs \
	npm

# Vue.jsとCLIインストール
RUN ["/bin/bash", "-c", " \
    source ~/.bashrc \
     && npm install vue \
     && npm install -g @vue/cli \
 "]

EXPOSE 80 8080


docker-compose.ymlの用意

docker-composeでコンテナを起動するためにdocker-compose.ymlを用意する。Dockerfileでイメージをビルドするようにし、ApacheとVue.jsの開発サーバ用のポートをそれぞれホスト側でも使用できるようにしておく。

version: "3"
services:
  vue:
    container_name: vue
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      TZ: 'Asia/Tokyo'
    ports:
    - 80:80
    - 8080:8080


Vue.jsプロジェクトを作成する


コンテナを起動してプロジェクトを作成する。まずはコンテナの起動。


次にコンテナに入る。


Vue.jsプロジェクトを作成する。


開発サーバを起動。

ブラウザでhttp://localhost:8080にアクセスすると次のサンプルページが表示される。


開発サーバで確認ができたらビルドしてApacheで表示してみる。

ブラウザでhttp://localhostにアクセスして開発サーバのときと同じ画面が表示されればOK。


2017年3月5日日曜日

Javascriptで位置情報を取得する

Javascriptで端末の位置情報を取得する方法を調べたのでまとめておく。参考にしたのはGeolocation の利用

位置情報を取得するにはGeolocation APIのgetCurrentPosition() かwatchPosition() メソッドを使う。前者は1回のみ情報取得を行い、後者は一定間隔で情報を取得し続ける。ともに引数として、成功時のコールバック、エラー時のコールバック(省略可)、PositionOptions オブジェクト(省略可)をとる。


成功時のコールバック関数


位置情報と取得時の時間が得られる。取得できる情報は緯度や経度などで、詳細はCoordinatesを参照。

function geo_success(position) {
  // 位置情報を取得した時刻
  var timestamp = new Date(position.timestamp);
  // 緯度
  var latitude  = position.coords.latitude;
  // 経度
  var longitude = position.coords.longitude;
  // 緯度・経度の誤差
  var accuracy = position.coords.accuracy;
  // 高度
  var altitude = position.coords.altitude;
  // 高度の誤差
  var altitudeAccuracy = position.coords.altitudeAccuracy;
  // 移動方向
  var heading = position.coords.heading;
  // 移動速度
  var speed = position.coords.speed;
}

エラー時のコールバック


エラーコードは3種類ある。

function geo_error(error) {
  switch(error.code)
  {
    case 1:
      // PERMISSION_DENIED
      output.innerHTML = 'このページにはアクセス許可がないため、位置情報の取得に失敗しました。';
      break;
    case 2:
      // POSITION_UNAVAILABLE
      output.innerHTML = '少なくともひとつの位置情報ソースが内部的なエラーを返したため、位置情報の取得に失敗しました。';
      break;
    case 3:
      // TIMEOUT
      output.innerHTML = 'PositionOptions.timeout によって指定された制限時間内に位置情報を取得することができませんでした。';
      break;
}

オプション


オプションは3つ。オプションについてはPositionOptions

var geo_options = {
  // trueのときに高精度な位置情報を提供できる場合に利用する
  enableHighAccuracy: true,
  // キャッシュ済みの位置情報の有効期限 (ミリ秒)
  maximumAge        : 30000,
  // 位置情報の取得にかかる時間の上限 (ミリ秒)
  timeout           : 10000
};

コードを書いてみる


Geolocation APIで位置情報を取得するJavascriptコード書いてみる。

    function geo_success(position) {
      // 位置情報を取得した時刻
      var timestamp = new Date(position.timestamp);
      // 緯度
      var latitude  = position.coords.latitude;
      // 経度
      var longitude = position.coords.longitude;
      // 緯度・経度の誤差
      var accuracy = position.coords.accuracy;
      // 高度
      var altitude = position.coords.altitude;
      // 高度の誤差
      var altitudeAccuracy = position.coords.altitudeAccuracy;
      // 移動方向
      var heading = position.coords.heading;
      // 移動速度
      var speed = position.coords.speed;

    };

    function geo_error(error) {
      switch(error.code)
      {
        case 1:
          // PERMISSION_DENIED
          output.innerHTML = 'このページにはアクセス許可がないため、位置情報の取得に失敗しました。';
          break;
        case 2:
          // POSITION_UNAVAILABLE
          output.innerHTML = '少なくともひとつの位置情報ソースが内部的なエラーを返したため、位置情報の取得に失敗しました。';
          break;
        case 3:
          // TIMEOUT
          output.innerHTML = 'PositionOptions.timeout によって指定された制限時間内に位置情報を取得することができませんでした。';
          break;
      }
    };

    var geo_options = {
      // trueのときに高精度な位置情報を提供できる場合に利用する
      enableHighAccuracy: true,
      // キャッシュ済みの位置情報の有効期限 (ミリ秒)
      maximumAge        : 30000,
      // 位置情報の取得にかかる時間の上限 (ミリ秒)
      timeout           : 10000
    };

    // Geolocation APIが使用できるかの確認
    if (!navigator.geolocation){
      alert('お使いのブラウザではGeolocationがサポートされていません');
    } else  {
      //var wpid = navigator.geolocation.watchPosition(geo_success, geo_error, geo_options);
      var wpid = navigator.geolocation.getCurrentPosition(geo_success, geo_error, geo_options);
    }

仕様では取得できない情報にはnullが入るとあるが、Firefoxではaltitude(高度)に0が入ったり、heading(移動方向)にNaNが入ったりする。Chromeだとちゃんとnullが入る。それでも、緯度、経度情報はとれているので、Three.jsと合わせたらポケモンGOみたいなゲームが作れそう。

2017年1月22日日曜日

Three.jsで3Dデジタル時計を作成する

Three.js(r79)の3D文字を利用してデジタル時計を作成してみる。手順は以下の通り。

  • フォントのロード
  • 3D文字の作成
  • 現在時刻の取得
  • デジタル時計の作成


フォントのロード


Three.jsで3D文字を作成するには、まずフォントをロードする必要がある。使えるフォントファイルはフォントをJSON形式に変換したもの。Facetype.jsで変換できるが、Three.jsのダウンロードファイルに使えるフォントファイルがいくつか含まれているのでそれを使う。

Three.jsのホームページからダウンロードした圧縮ファイルを解凍して、\three.js-r79\examples\fonts\optimer_regular.typeface.jsonを適当な場所にコピーする。フォントのロードは以下のような感じ。
var loader = new THREE.FontLoader();
loader.load("optimer_regular.typeface.json", (function(font) {
 // オブジェクト作成などの処理を行う
 // fontでロードされたフォントを参照できる
}));


3D文字の作成


3D文字の作成には、上記でロードしたフォントとTHREE.TextGeometryでジオメトリを作成し、あとは通常のThree.jsと同様にメッシュを作成する。
  // 第1引数に作成する文字列を指定し、残りの引数にパラメータを設定する
  // font: 使用するフォント
  // size: オブジェクトサイズ
  // height: オブジェクトの奥行き
  // curveSegments: 曲線の滑らかさ
  // bevelThickness: 斜体の傾き度
  // bevelSize: アウトラインからどの程度傾けるか
  // bevelEnabled: 斜体にするかどうか
  var geometry = new THREE.TextGeometry(char, {
   font: font,
   size: size,
   height: 0.1,
   curveSegments: 15,
   bevelThickness: 10,
   bevelSize: 8,
   bevelEnabled: false
  } );

  // マテリアルの作成
  var material = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.7});

  // オブジェクトの作成
  var mesh = new THREE.Mesh( geometry, material );


現在時刻の取得


Javascriptでは以下のようにして現在時刻を取得できる。
    // 現在時刻取得
  var now = new Date();
  // 時の取得
    var hour = now.getHours();
  // 分の取得
    var min = now.getMinutes();
  // 秒の取得
    var sec = now.getSeconds();


デジタル時計の作成


とりあえず思いついたのが、デジタル時計の桁ごとに0から9の数字オブジェクトを作成して非表示にし、現在時間の数字オブジェクトだけを表示させる方法。この方法で3Dデジタル時計用のオブジェクトを作成するクラスを作成した(GitHubで公開)ので、これを利用して実際に3Dデジタル時計を表示してみる。

以下のようなデジタル時計が表示される。



2016年11月20日日曜日

Three.jsで3Dオブジェクトをドラッグして移動させる

Three.js(r79)で、3Dオブジェクトをマウスで移動させてみる。具体的には、平面を作成して、その平面に対して立法体が平行に移動するようにする。


1.jsファイルの読み込み


three.min.jsとTrackballControlsを読み込む。ともにjs_r79というディレクトリにあるとする。




2.オブジェクトの作成


Three.jsで立法体を2つ作成して表示できるようにする。

// オブジェクトを格納する配列
var objects = [];

// シーンの作成
var scene = new THREE.Scene();
// カメラの作成
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
// レンダラーの作成
var renderer = new THREE.WebGLRenderer();

// レンダラーが描画するキャンバスサイズの設定
renderer.setSize( window.innerWidth, window.innerHeight );
// キャンバスをDOMツリーに追加
document.body.appendChild( renderer.domElement );

// TrackballControlsインスタンス作成
var controls = new THREE.TrackballControls( camera );

// ジオメトリーの作成
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
// マテリアルの作成
var material = new THREE.MeshNormalMaterial( { color: 0x00ff00 } );
// オブジェクトの作成
var cube = new THREE.Mesh( geometry, material );
// オブジェクトの位置調整
cube.position.x = 2.0;
// オブジェクトをシーンに追加
scene.add( cube );
objects.push( cube );

// オブジェクトを複製
var cube2 = cube.clone();
// オブジェクトの位置調整
cube.position.x = -2.0;
// オブジェクトをシーンに追加
scene.add( cube2 );
objects.push( cube2 );

// カメラ位置設定
camera.position.z = 5;
camera.position.x = 0.5;
camera.position.y = 0.5;

animate();
function animate() {
 requestAnimationFrame( animate );
 controls.update();
 renderer.render( scene, camera );
}

3.マウスイベントの追加


mousedown、mousemove、mouseupイベントのリスナーを登録する。平面を作成し、平面に対して平行に立法体を移動させる。平面の角度は、カメラの角度に合わせて変化させる(TrackballControlsで視点が変化するため)。

// この平面に対してオブジェクトを平行に動かす
var plane = new THREE.Plane();

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var offset = new THREE.Vector3();
var intersection = new THREE.Vector3();

// マウスオーバーしているオブジェクト
var mouseoveredObj;
// ドラッグしているオブジェクト
var draggedObj;

renderer.domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
renderer.domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
renderer.domElement.addEventListener( 'mouseup', onDocumentMouseUp, false );

function onDocumentMouseDown( event ) {
 event.preventDefault();

 raycaster.setFromCamera( mouse, camera );
 var intersects = raycaster.intersectObjects( objects );

 if ( intersects.length > 0 ) {
  // マウスドラッグしている間はTrackballControlsを無効にする
  controls.enabled = false;

  draggedObj = intersects[ 0 ].object;

  // rayとplaneの交点を求めてintersectionに設定
  if ( raycaster.ray.intersectPlane( plane, intersection ) ) {
   // ドラッグ中のオブジェクトとplaneの距離
   offset.copy( intersection ).sub( draggedObj.position );
  }
 }
}

function onDocumentMouseMove( event ) {
 event.preventDefault();

 mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
 mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

 raycaster.setFromCamera( mouse, camera );

 if ( draggedObj ) {
  // オブジェクトをドラッグして移動させているとき

  // rayとplaneの交点をintersectionに設定
  if ( raycaster.ray.intersectPlane( plane, intersection ) ) {
   // オブジェクトをplaneに対して平行に移動させる
   draggedObj.position.copy( intersection.sub( offset ) );
  }
 } else {
  // オブジェクトをドラッグしないでマウスを動かしている場合
  var intersects = raycaster.intersectObjects( objects );

  if ( intersects.length > 0 ) {
   if ( mouseoveredObj != intersects[ 0 ].object ) {
    // マウスオーバー中のオブジェクトを入れ替え
    mouseoveredObj = intersects[ 0 ].object;

    // plane.normalにカメラの方向ベクトルを設定
    // 平面の角度をカメラの向きに対して垂直に維持する
    camera.getWorldDirection( plane.normal );
   }
  } else {
   mouseoveredObj = null;
  }
 }
}

function onDocumentMouseUp( event ) {
 event.preventDefault();

 controls.enabled = true;

 if ( mouseoveredObj ) {
  draggedObj = null;
 }
}

4.実行結果


1~3を合わせると、以下のように立法体が2つ表示され、マウスでドラッグして移動させることができる。


2016年8月28日日曜日

Three.jsでマテリアルを動かす

Three.js(r79)でオブジェクトのマテリアルを動かしてみる。具体的には、平面オブジェクトのマテリアルとして画像を読み込み、その画像を左方向に移動させる。

以下のようにマテリアルを変化させる。


 ↓↓↓



以下は、THREE.ShaderMaterialとプログラマブルシェーダであるGLSLでマテリアルを移動させる手順。


1.GLSLのコーディング(頂点シェーダ)


今回は頂点シェーダを変更する必要はないが、この場合でも頂点シェーダーの記述は必要。

 

※座標変換をしているだけで、頂点位置が変化しているわけではない。

2.GLSLのコーディング(フラグメントシェーダ)


フラグメントシェーダで画像が左に移動する動きを記述する。画像のどの位置の色情報を参照するかUV座標で指定するが、そのUV座標を左にずらしていくことで、画像を左に移動させる。




3.オブジェクトの作成


GLSLを利用するためにTHREE.ShaderMaterialでマテリアルを作成する。

// ジオメトリの作成
var geometry = new THREE.PlaneGeometry( 4, 3);

// テクスチャの作成
var texture = new THREE.TextureLoader().load( '../image/persimmon.jpg' );

// マテリアルの作成
// uniforms: シェーダーに渡す変数を指定
// vertexShader: 頂点シェーダーを指定
// fragmentShader: フラグメントシェーダーを指定
var material = new THREE.ShaderMaterial( {
  uniforms: {
   delta: { type: 'f', value: 0.0 },
   texture:   { type: 't', value: texture }
  },
  vertexShader:   document.getElementById( 'vshader' ).textContent,
  fragmentShader: document.getElementById( 'fshader' ).textContent
 });

// オブジェクトの作成
var plane = new THREE.Mesh( geometry, material );

4.マテリアルを移動させる


setIntervalでマテリアルの移動速度を調整。

// 画像の横サイズ
var imgWidth = 1024;
setInterval( function(){
 if( plane.material.uniforms.delta.value > imgWidth ) {
  plane.material.uniforms.delta.value = 0.0;
 }
 plane.material.uniforms.delta.value += 1.0;
}, 100 );

後は、シーンやカメラなど、通常Three.jsで必要なものを追加するだけ。



2016年8月17日水曜日

Three.jsで鏡をつかう

Three.js(r79)で鏡に映るオブジェクトのシーンをつくってみたかったので調べたところ、鏡オブジェクトを作成できるらしい。そこで、球体の左右に鏡を配置して、球をたくさん表示させてみる。

以下のようなシーンを作成する。


1.Mirror.jsの読み込み


Three.jsのライブラリファイルを解凍し、\examples\js\Mirror.jsを適当なフォルダにコピーする(ここではjs_r79)。それをhtmlから読み込む


2.球体の作成


球体を作成する。
// 球体の作成
var geometry = new THREE.SphereGeometry( 5, 32, 16 );
// テクスチャ画像の読み込み
var texture = new THREE.TextureLoader().load( '../image/sunflower_er.jpg' );
// マテリアルの作成
var material = new THREE.MeshPhongMaterial( { map: texture, color: 0xffffff } );
// 球体の作成
var mesh = new THREE.Mesh( geometry, material );
// 球体をシーンに追加
scene.add( mesh );

3.ミラーの作成


THREE.Mirrorインスタンスを作成し、そのmaterialをミラーオブジェクトのマテリアルとして指定する。
var width = window.innerWidth;
var height = window.innerHeight;

// ミラーのジオメトリ作成
var plane = new THREE.PlaneBufferGeometry( 100, 100 );

// 左ミラーの作成
var leftMirror = new THREE.Mirror( renderer, camera, { clipBias: 0.003, textureWidth: width, textureHeight: height, color: 0x777777 } );
var leftMirrorMesh = new THREE.Mesh( plane, leftMirror.material );
leftMirrorMesh.add( leftMirror );
// 左ミラーの位置調整
leftMirrorMesh.rotateY( Math.PI / 2 );
leftMirrorMesh.position.x = -10;
// 左ミラーをシーンへ追加
scene.add( leftMirrorMesh );

// 右ミラーの作成
var rightMirror = new THREE.Mirror( renderer, camera, { clipBias: 0.003, textureWidth: width, textureHeight: height, color: 0x777777 } );
var rightMirrorMesh = new THREE.Mesh( plane, rightMirror.material );
rightMirrorMesh.add( rightMirror );
// 右ミラーの位置調整
rightMirrorMesh.rotateY( -Math.PI / 2 );
rightMirrorMesh.position.x = 10;
// 右ミラーをシーンへ追加
scene.add( rightMirrorMesh );

4.ミラーのレンダリング


ミラー用のレンダリングを追加する。
function render(){
 requestAnimationFrame( render );
 // レンダリング
 renderer.render( scene, camera );
 // ミラー用レンダリング
 leftMirror.renderWithMirror( rightMirror );
 rightMirror.renderWithMirror( leftMirror );
}
render();




2016年8月13日土曜日

Three.jsでミラーボールをつくる

ミラーボールはたくさんのミラーパネルを球状に取り付けたもので、ダンスホールなんかで使われる。そのミラーボールをThree.js(r79)でつくってみる。

以下のようなミラーボールをつくる。

キューブマップの作成


キューブマップについてはThree.jsでキューブマップを使うを参照。

キューブマップ用の画像を用意し、以下のコードでキューブマップテクスチャを作成する。
// キューブマップ用のテクスチャ作成
var cubeTexture = new THREE.CubeTextureLoader()
  .setPath( 'sunflower/' )
  .load( [ 'sunflower_er_left.png', 'sunflower_er_right.png', 'sunflower_er_up.png', 'sunflower_er_down.png', 'sunflower_er_front.png', 'sunflower_er_back.png' ] );
cubeTexture.format = THREE.RGBFormat;


ミラーボールオブジェクトの作成


球体オブジェクトを作成する。マテリアルの作成で、環境マップに作成したキューブマップを指定する。また、フラットシェーディングを使うと、鏡のパネルが並んでいるように見える。

// 球体ジオメトリの作成
var geometry = new THREE.SphereGeometry( 200, 64, 32 );
// マテリアルの作成
// 環境マップ(envmap)に作成したテクスチャ(cubeTexture)を設定
// 鏡のパネルが並んでいるようにTHREE.FlatShadingを使う
var material = new THREE.MeshStandardMaterial( { 
 color: 0xffffff,
 envMap: cubeTexture,
 shading: THREE.FlatShading,
 roughness: 0.1,
 metalness: 0.9
} );
// オブジェクトの作成
var mball = new THREE.Mesh( geometry, material );

ミラーボールの色を変える


ミラーボールの色をパネルごとに変えてみる。
// 球体のミラーパネルごと(face2つごと)に色を変更
var fIndices = [ 'a', 'b', 'c' ];
var color, f, vIndex, hue;
for ( var i = 0; i < geometry.faces.length; i++ ) {
 f = geometry.faces[ i ];
 if( i % 2 == 0 ) hue = Math.random();
 for( var j = 0; j < 3; j ++ ) {
  vIndex = f[ fIndices[ j ] ];
  color = new THREE.Color( 0xffffff );
  color.setHSL( hue, 1.0, 0.7 );
  f.vertexColors[ j ] = color;
 }
}
// 球体ジオメトリの作成
var geometry = new THREE.SphereGeometry( 200, 64, 32 );
// マテリアルの作成
// 環境マップ(envmap)に作成したテクスチャ(cubeTexture)を設定
// 鏡のパネルが並んでいるようにTHREE.FlatShadingを使う
// vertexColors: THREE.VertexColorsを設定して色の変更を反映させる
var material = new THREE.MeshStandardMaterial( {
 color: 0xffffff,
 envMap: cubeTexture,
 shading: THREE.FlatShading,
 vertexColors: THREE.VertexColors,
 roughness: 0.1,
 metalness: 0.9
});
// オブジェクトの作成
var mball = new THREE.Mesh( geometry, material );

結果


色つき色なし2種類のミラーボールを作成する。結果は以下の通り。





2016年8月10日水曜日

Three.jsでオブジェクトをフェードイン/アウトする

Three.js(r79)でオブジェクトを消したり、途中から表示させたりするときに、フェードイン/アウトを使ってみる。ここでは2種類の方法を試す。


オブジェクトの準備


球体を作成する。後で透明度を変化させるので、マテリアルの作成でtransparent: trueとしておく。

// ジオメトリの作成
var geometry = new THREE.SphereGeometry( 2, 32, 16 );
// テクスチャ画像の読み込み
var texture = new THREE.TextureLoader().load( '../image/sunflower_er.jpg' );
// マテリアルの作成
var material = new THREE.MeshBasicMaterial( { map: texture, color: 0xffffff, transparent: true, opacity: 1.0 } );
// 球体作成
var mesh = new THREE.Mesh( geometry, material );
// 球体の向きを調整
mesh.rotation.z = -Math.PI/2;

以下のような球体が作成される。

方法1:マテリアルの色を変化させる


マテリアルの色(RGB)をsetIntervalを使って変化させていく。

var delta = -0.01;
setInterval( function(){
 if( material.color.r <= 0.0 ) {
  delta = 0.01;
 } else if( material.color.r >= 1.0 ) {
  delta = -0.01;
 }
 var color = material.color;
 material.color = new THREE.Color( color.r+delta, color.g+delta, color.b+delta );
}, 100);

方法2:透明度を変化させる


マテリアルの透明度をsetIntervalを使って変化させていく。
var delta = -0.01;
setInterval( function(){
 if( material.opacity < 0.0 ) {
  delta = 0.01;
 } else if( material.opacity > 1.0 ) {
  delta = -0.01;
 }
 material.opacity += delta;
}, 100);


2つの球体を交互にフェードイン/アウトさせるサンプル




2016年8月7日日曜日

Three.jsでスマホのジャイロセンサーを利用する

スマートフォンのジャイロセンサーで傾きを検知できるが、それを利用して、スマートフォンを傾けることでThree.jsの3D空間での視点を変えることができる。360度画像(Equirectangular)を、スマートフォンを傾けて見られるようにする。Three.jsのバージョンはr79。

1.DeviceOrientationControls.jsの読み込み


ダウンロードしたThree.jsライブラリの圧縮ファイルを解凍し、\three.js-master\examples\js\controls\DeviceOrientationControls.jsを適当なフォルダにコピーする。ここではjs_r79というフォルダを作成してコピー。それをhtmlから読み込む。


2.DeviceOrientationControlsインスタンスの作成


// DeviceOrientationControlsインスタンス作成
var controls = new THREE.DeviceOrientationControls( camera );

3.オブジェクトの作成


360度画像を読み込み、球体の裏側にマップする。
// 球体の作成
var geometry = new THREE.SphereGeometry( 5, 32, 16 );
// テクスチャを球体の裏側にマップする
geometry.scale( - 1, 1, 1 );
// テクスチャ画像の読み込み
var texture = new THREE.TextureLoader().load( 'sunflower_er.jpg' );
// マテリアルの作成
var material = new THREE.MeshPhongMaterial( { map: texture, color: 0xffffff } );
// オブジェクトの作成
var mesh = new THREE.Mesh( geometry, material );
// オブジェクトをy軸に沿って回転
mesh.rotation.y = Math.PI/2;

4.DeviceOrientationControlsのアップデート


レンダリングループでDeviceOrientationControlsをアップデートする。
controls.update();

5.結果


スマートフォンのブラウザで開くと以下のような画像が表示され、傾きに応じて視点を変えて画像を見ることができる。



2016年7月27日水曜日

Three.jsでメタボールをつくる

Googleで「メタボール」を検索すると、n 次元の有機的なオブジェクトという説明が見つかる。この説明だといまひとつピンとこないので、自分なりに調べてみた結果、球体などの曲線で形成される立体を融合した立体というものらしい。このメタボールをThree.js(r79)でつくる。

Three.jsではメタボールをマーチングキューブという方法で描画する。Three.jsのサンプル集にマーチングキューブ法によるメタボールのデモ(webgl_marchingcubes.html)があるので、これを参考にする。

MarchingCubes.jsの読み込み


ダウンロードしたファイルを解凍し、three.js-master\examples\js\MarchingCubes.jsを
適当なフォルダにコピーする。ここではjs_r79というフォルダにコピー。それをhtmlから読み込む。


キューブマップの準備


今回はメタボールのテクスチャにキューブマップを使用するのでその準備をする。キューブマップについてはThree.jsでキューブマップを使うを参照。

// キューブマップ用のテクスチャ作成
var cubeTexture = new THREE.CubeTextureLoader()
    .setPath( 'sunflower/' )
    .load( [ 'sunflower_er_left.png', 'sunflower_er_right.png', 'sunflower_er_up.png', 'sunflower_er_down.png', 'sunflower_er_front.png', 'sunflower_er_back.png' ] );
cubeTexture.format = THREE.RGBFormat;

// シーンの作成
var scene = new THREE.Scene();
// シーンの背景テクスチャを設定
scene.background = cubeTexture;

// マテリアル作成
// 環境マップ(envmap)に作成したテクスチャ(cubeTexture)を設定
var material = new THREE.MeshStandardMaterial( { color: 0xdddddd, envMap: cubeTexture, roughness: 0.1, metalness: 0.5, transparent: true, opacity: 0.7 } );

MarchingCubesインスタンスの作成


MarchingCubesインスタンスを作成し、シーンに追加する。

// MarchingCubesインスタンスの作成
// 第1引数はresolutionで、値を大きくするほど滑らかな描画になる
var effect = new THREE.MarchingCubes( 64, material, true, true );
effect.position.set( 0, 0, 0 );
effect.scale.set( 1000, 1000, 1000 );

// シーンに追加
scene.add( effect );

メタボールの作成


以下のような関数を作成し、その中でMarchingCubes.addBallにより球体を作成する。この関数をレンダリングループで呼ぶ。

// Three.jsサンプル集にあるwebgl_marchingcubes.htmlのupdateCubesの一部を省いてほぼそのまま使用
// object: 作成したMarchingCubesインスタンス
// time: 経過時間の差分
// numblobs: 作成する球体の数
function updateCubes( object, time, numblobs) {

 object.reset();

 var i, ballx, bally, ballz, subtract, strength;

 subtract = 100;
 strength = 1.2 / ( ( Math.sqrt( numblobs ) - 1 ) / 4 + 1 );

 for ( i = 0; i < numblobs; i ++ ) {

  ballx = Math.sin( i + 1.26 * time * ( 1.03 + 0.5 * Math.cos( 0.21 * i ) ) ) * 0.27 + 0.5;
  bally = Math.abs( Math.cos( i + 1.12 * time * Math.cos( 1.22 + 0.1424 * i ) ) ) * 0.77;
  ballz = Math.cos( i + 1.32 * time * 0.1 * Math.sin( ( 0.92 + 0.53 * i ) ) ) * 0.27 + 0.5;

  object.addBall( ballx, bally, ballz, strength, subtract );
 }
}

結果


以上をもとに作成したメタボールがこちら。




2016年7月23日土曜日

Three.jsでキューブマップを使う

Three.js(r79)でオブジェクトの表面にまわりの景色を反射させたい場合、キューブマップを使うとできる。キューブマップは、各面に周囲画像を割り当てた立法体でオブジェクトを囲む空間をつくり、オブジェクトの表面に周囲画像を写すことで、周囲の景色の反射を擬似的に再現する。

実際にThree.jsでキューブマップを使うまでの手順としては、まずキューブマップ画像を用意して、Three.jeでコーディングをする。

キューブマップ画像の作成

キューブマップでは周囲360度を画像で囲むことになるので、360度の画像(Equirectangular)を使うと境界のない自然な周囲景色を作れる。Equirectangular形式の画像は、Ricohのthetaなど360度の写真が撮れるカメラがあれば簡単に作成できるが、残念ながら僕はそういうカメラを持っていないので、今回は1台のKodak SP360で360度パノラマ写真を撮るで作成したEquirectangular形式の画像を使う。

キューブマップでは立法体の各面に画像を割り当てるので、キューブマップ画像というのは立法体の各面に対応する6つの正方形画像から成る。この画像を作成するのにUnityのAssetであるPanorama To Cubemapを使用する。以下はPanorma To Cubemapでキューブマップ画像を作成する手順。

1.Unityでプロジェクトを開いて、UnityのAsset StoreからPanorama To Cubemapをダウンロードする。追加されると、Assetsに表示される。


2.プロジェクトのAssetsにEquirectangular形式の画像を追加する。Assetsにドラッグ&ドロップで追加できる。


3.メニューの[Windows]>[Panorama To Cubemap]をクリックして、Panorama To Cubemapのウィンドウを開く。

4.Selectを押して、Assetsに追加したEquirectangular形式の画像を選択し、Convertを押す。


5.変換された画像はAssetsのoutput_imagesに保存される。


Three.jsでのコーディング

Three.jsのコーディングでは以下の2つのことを行う。

・THREE.CubeTextureLoaderで、作成した6つのキューブマップ画像を読み込み(left、right、up、down、front、backの順)、THREE.Sceneのbackgroundプロパティに設定する。

・マテリアル作成時に、envMapにTHREE.Sceneのbackgroundプロパティを設定する。

// シーンの作成
var scene = new THREE.Scene();
scene.background = new THREE.CubeTextureLoader()
 .setPath( 'panorama/' )
 .load( [ 'Panorama_left.png', 'Panorama_right.png', 'Panorama_up.png', 'Panorama_down.png', 'Panorama_front.png', 'Panorama_back.png' ] );

// 球体ジオメトリの作成
var geometry = new THREE.SphereBufferGeometry( 100, 32, 16 );
// マテリアルの作成
// envMap(環境マップ)にscene.backgroundを指定する
var material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: scene.background } );
// 球体オブジェクトの作成
var mesh = new THREE.Mesh( geometry, material );
// 球体オブジェクトをシーンに追加
scene.add( mesh );

結果

結果は以下の通り


元のEquirectangular形式の画像



2016年7月14日木曜日

Chromeとnode.jsでりんなと会話する

Chromeの音声認識と合成音声、それにnode.jsのモジュールであるsocket.ioを使って、マイクロソフトのAIりんなとTwitterを介して会話してみる。ツイートとりんなからのリプライの取得にはTwitterのAPIでできるが、ここではnode.jsでTwitter APIを利用できるtwitを使う。大まかな流れとしては、Chromeで自分の発音を音声認識してテキストに変換し、それをsocket.ioでnode.jsサーバに送る。node.jsサーバからりんなにツイートし、りんならリプライがきたらそれを受け取り、socket.ioでChromeに送る。Chromeはりんなのリプライを受け取ったら、音声合成の機能で再生する。以下、次の手順で進める。

1.Twitter APIの利用設定

2.node.jsと必要なモジュールのインストール

3.サーバサイドのコーディング
・りんなのツイートを取得してブラウザへ送信する
・ブラウザから受信したテキストをツイートする

4.クライアントサイドのコーディング
・サーバから受信したツイートを音声再生する
・音声認識でツイート内容とテキストにしてサーバに送信する

5.りんなと会話

Twitter APIの利用設定

ツイートとツイートの取得にはnode.jsのモジュールであるtwitを利用するが、このモジュールはTwitter APIを使用しているので、Twitter APIの使用申請をする必要がある。Twitterのアカウントを作成したら、電話番号を登録しておく(電話番号を登録しておかないとAPIの申請はできない)。申請はTwitterの開発者向けのページから行う。ページを開いたら、「Manage Your Apps」、「Create New App」の順で進めていくと申請ができる。申請が完了したら、「Keys and Access Tokens」、「Create my access token」の順でメニューを開き、APIキーとトークンを取得する。

後で必要になるので以下の4つをメモしておく。
・Consumer Key (API Key)
・Consumer Secret (API Secret)
・Access Token
・Access Token Secret

node.jsと必要なモジュールのインストール

ubuntuの場合の手順は以下の通り。

node.jsに加えてモジュールのインストールに使うnpmもインストールする。


インストールするとnode.jsのコマンド名はデフォルトではnodejsになっているので、シンボリックリンクを作成してnodeで実行できるようにする。


socket.ioとtwitのインストール。


サーバサイドのコーディング

サーバサイドで行うことは主に以下の2つ。

①りんなのツイートを取得してブラウザへ送信する
②ブラウザから受信したテキストをツイートする

socket.ioとtwitを組み合わせて使うことになるが、socket.ioについてはsocket.ioでブラウザ双方向通信をするで説明しているので、twitの部分だけここでは記述する。①のコードは以下のようになる。
// twitモジュールの読み込み
var Twit = require('twit');

// Twitter API申請時に取得した4つのトークンを設定する
var tw = new Twit({
  consumer_key: 'xxxxxxxxxx',
  consumer_secret: 'xxxxxxxxxx',
  access_token: 'xxxxxxxxxx',
  access_token_secret: 'xxxxxxxxxx',
  timeout_ms:           60*1000,
});

// りんなのユーザIDを設定
var targetID = '3274075003';
// 自分のユーザID
var myID = '';

// followに取得したいユーザのIDを指定する
var stream = tw.stream('statuses/filter', {'follow': targetID} );
stream.on('tweet', function ( data ) {
  // 結果(data)はJSON形式
  // りんなからのリプライにしぼる
  if( data['user']['id_str'] === targetID && data['in_reply_to_user_id_str'] === myID ) {
    // @スクリーンネームを取り除く
    var message = data['text'];
    while( message.indexOf('@') != -1 ) {
      message = message.substr( message.indexOf(' ') );
    }
    console.log( message );

    // ここでsocket.ioでブラウザにりんなのツイート(message)を送る
  }
});

上記で設定しているユーザIDはstring型を使う。TwitterのIDについてはTwitterの開発者向けサイトを参照。

②のコードは以下のようになる。socket.ioでChromeからツイートするテキストを受け取ったら、twitでツイートする。りんな向けのツイートなので、@ms_rinnaではじめる。
// ここでsocket.ioでブラウザからツイートするテキスト(message)を受け取る

// statusにツイートするメッセージを指定する
tw.post('statuses/update', { status: '@ms_rinna ' + message }, function( err, data, response ) {
  console.log(data)
});


クライアントサイドのコーディング

クライアントサイドで行うことは主に以下の2つ。

①サーバから受信したツイートを音声再生する
②音声認識でツイート内容をテキストにしてサーバに送信する

socket.ioとWeb Speech APIを組み合わせる。①はsocket.ioとChromeの合成音声によるテキスト読み上げ機能(SpeechSynthesis)を使う。SpeechSynthesisについてはブラウザの音声合成でテキスト読み上げを行う。②はsocket.ioとChromeの音声認識(SpeechRecognition)を使う。SpeechRecognitionについてはChromeの音声認識を使う

全体のコード

上記を元にして作ったデモをgithubで公開。りんなと会話するには以下の箇所を変更する。

server.jsの変更

Twitter APIの4つのトークンとりんなのIDを設定
// Twitter APIの4つのトークンを設定
var tw = new Twit({
  consumer_key:         '',
  consumer_secret:      '',
  access_token:         '',
  access_token_secret:  '',
  timeout_ms:           60*1000,  // optional HTTP request timeout to apply to all requests.
});

// りんなのIDを設定
var targetID = '3274075003';

index.htmlの変更

Web Speech APIに日本語を設定し、女子高生っぽい声になるように速度と音程を少し上げる。
var options = {
  // Voice:0-19
  voice: 11,
  // Language
  lang: 'ja-JP',
  // Volume: 0-1.0
  volume: 1.0,
  // Rate: 0.1-10.0
  rate: 1.2,
  // Pitch: 0-2.0
  pitch: 1.3
};
var speechSynth = new SpeechSynth( options );

var speechRec = new SpeechRec( 'ja-JP' );

りんなと会話してみる

server.js、speech.js、index.htmlをnode.jsサーバにコピーして、以下のコマンドで実行する。


Chromeでサーバ(サーバのIPアドレス:9001)にアクセスすると、Tweetというボタン、自分のユーザ名、りんなのプロフィールが表示される。ツイートするときは、ボタンを押してからマイクに向かって話す。話した内容が認識されると、りんなにツイートされる。りんなからリプライがあれば、自動的にChromeで再生・表示される。

結果は以下の通り。合成音声は棒読みに近いので不自然な感じはあるものの、会話はできる。

既知の問題

  • socket.ioによるブラウザとサーバ間の接続が2つ以上になってしまい、りんなの同じツイートが複数回再生・表示されてしまうことがある。
  • 音声認識が途中で止まってしまうことがある。この場合はボタンをを2回押して音声認識を再開できる。

2016年7月13日水曜日

ブラウザの音声合成でテキスト読み上げを行う

ブラウザで音声合成を使うためのSpeechSynthesisというWeb APIがある。この機能を使うとテキストの読み上げができる。今のところChromeとFirefox、Safariで対応しているが、ここではChromeで音声合成を試してみる。

音声の読み込み

はじめに音声を読み込む。数種類の言語の音声が用意されており、読み上げる言語によって音声を変えることができる。window.speechSynthesisを参照し、そのメソッドであるgetVoicesを使うと音声の配列を取得できる。Chromeの場合は、onvoiceschangedイベントのタイミングでないと取得できない。
var synth = window.speechSynthesis;
var voices = [];

if ( speechSynthesis.onvoiceschanged !== undefined ) {
  // Chromeではonvoiceschangedというイベントがあり、onvoiceschangedが呼ばれたタイミングでないと音声を取得できない
  speechSynthesis.onvoiceschanged  = function (){
    voices = synth.getVoices();
  };
} else {
 // Firefoxではこれで音声が読み込める
    voices = synth.getVoices();
}

ちなみに、ChromeとFirefoxでは使える音声がずいぶん違う。

Chrome


Firefox

※先頭の数字は配列の番号

ただし、音声をgetVoicesで読み込まなくてもテキスト読み上げはできる。その場合は、HTMLで定義された言語を元に音声が選択される。


音声オプションの設定

音声の音程などを変えるにはSpeechSynthesisUtteranceを使う。
  // SpeechSynthesisUtteranceインスタンスの作成
  var utterThis = new SpeechSynthesisUtterance();

  // 音声の指定(ここでは日本語音声を指定)
  utterThis.voice = voices[11];
  // 音量の指定(0-1.0)
  utterThis.volume = 1.0;
  // 速度の指定(0.1-10.0)
  utterThis.rate = 1.0;
  // 音程の指定(0-2.0)
  utterThis.pitch = 1.0;
  // 言語の指定
  utterThis.lang = 'ja-JP';

テキストの読み上げ

SpeechSynthesisUtterance.textに読み上げるテキストを設定し、speechSynthesis.speakで読み上げを行う。
  utterThis.text = '読み上げるテキスト';

  // 読み上げの実行
  synth.speak( utterThis );

実装

テキスト入力欄と音声再生用のボタンを配置し、ボタンを押して入力されたテキストを読み上げるようにする。


2016年7月11日月曜日

Chromeの音声認識を使う

SpeechRecognitionというWeb APIがあり、ブラウザで音声認識を使える。ただ、今のところ対応しているのはChromeのみ(Firefoxは対応予定あり)。そういうわけで、Chrome限定ではあるが、ブラウザの音声認識を使ってみる。

インスタンス作成とオプションの設定


まずはSpeechRecognitionインスタンスの作成。Chromeで使うには、プレフィックスwebkitが必要。オプションはいくつかあるが、とりあえずcontinuousとlangを設定しておけば音声認識を試せる。
// インスタンス作成
recognition = new webkitSpeechRecognition();
// 連続して音声認識を行うかどうか
recognition.continuous = true;
// 認識する言語の設定
recognition.lang = 'ja-JP';

音声認識の開始と停止


音声認識の開始と停止にはそれぞれstart、stopメソッドを使う。
// 音声認識開始
recognition.start();
// 音声認識停止
recognition.stop();

startが実行されると、ブラウザでマイク使用許可を求めるポップアップが表示される。ここでユーザーが許可しないと、音声認識ははじまらない。また、SpeechRecognitionはサーバーにhtmlを置いてアクセスしないと使えないので、ローカルにあるhtmlを開いても、マイク使用許可を求めるポップアップは表示されない。

イベントの設定


SpeechRecognition APIにはいくつかイベントが用意されているが、よく使いそうなのは以下の通り。中でも認識結果が得られる度に呼ばれるonresultは必ず必要になる。onresultが呼ばれる度にevent.results配列に要素が追加されていき、認識された音声のテキストはevent.results[i][0].transcriptから取得できる。event.results配列には音声認識が停止されるまでに認識された結果がすべて入っているので、1回目の認識で「あいうえお」と認識されたあとに、2回目の認識で「かきくけこ」と認識されてonresultが呼ばれたときには、event.results[0][0].transcript = 'あいうえお'、event.results[1][0].transcript = 'かきくけこ' となる。

// 音声認識が開始されたとき(ユーザーがマイクの使用を許可した後)に呼ばれる
recognition.onstart = function() {
};

// 音声認識が停止したときに呼ばれる
recognition.onend = function() {
};


// 音声認識される度に呼ばれる
recognition.onresult = function( event ) {
  // 音声認識されるごとに、event.results配列に要素が追加されていく
  for ( var i = event.resultIndex; i < event.results.length; ++i ) {
    if ( event.results[i].isFinal ) {
     // 認識された音声のテキストを出力
          console.log( event.results[i][0].transcript );
      }
    }
  }
};

// エラーが起きたときに呼ばれる
recognition.onerror = function( event ) {
   // エラーメッセージの出力
   console.log( event.error );
};


実装

ボタンで音声認識の開始/停止を切り替えるようにした。


Startボタンを押すと、マイクの使用許可を求めるポップアップウィンドウが開くので、許可すると音声認識がはじまる。マイクに向かって何かしゃべると、認識された音声のテキストが画面に表示される。

結果はなかなか優秀。最後の認識については、3番目の「木」は「気」が正解。でもそれ以外はちゃんと認識できた。


2016年6月25日土曜日

socket.ioを使ってブラウザ間で画像を送る

socket.ioでブラウザ双方向通信をするで、node.jsサーバを介してブラウザ間でテキストを送れることが確認できたので、今度は画像を送ってみる。画像は直接は送れないようなので、base64形式で送る。ブラウザでファイルを読み込んで、base64形式でnode.jsサーバに送り、サーバはその画像データをもう一方のブラウザへ送るという流れ。


1.クライアントサイドのコーディング


①socket.ioライブラリの読み込み
    
    
②HTML部の記述(bodyタグ内)

socket.io test

画像ファイルを読み込むためのボタンを設置。

③javascript部の記述
// ソケットへ接続(node.jsサーバとブラウザを起動するPCが別の場合は、node.jsサーバのアドレスを指定)
  var socket = io( 'http://localhost:9001/' );
  socket.on( 'connect', function () {

    // サーバから送られた画像を受け取って表示
    socket.on( 'image', function( imageData ) {
      if ( imageData ) {
        var canvas = document.createElement( 'canvas' );
        var ctx = canvas.getContext( '2d' );
        var img = new Image();
        img.src = imageData;
        img.onload = function() {
          // キャンバスに画像を描画
          // ロードし終わってからでないとキャンバスに描画されない
          canvas.width = img.width;
          canvas.height = img.height
          ctx.drawImage( img, 0, 0, 640, 480 );
          // bodyにcanvasを追加
          document.body.appendChild( canvas );
        }
      }
    });

  });
  
// 相手ブラウザに画像を送る
function sendImage( event ) {
  // 選択したファイルのうち1つ目を取得
  var file = event.target.files[0];

  var reader = new FileReader();

  reader.onload = function( event ) {
    // Data URLで読み込むとデータはbase64形式になっているので、そのまま送信する
    socket.emit( 'image', event.target.result );
 };
 // Data URLで画像を読み込む
 reader.readAsDataURL( file );
}
//ファイルを読み込むボタンのリスナー登録
var file = document.getElementById( 'file' );
file.addEventListener( 'change', sendImage, false );
ボタンを押して画像ファイルを選択すると、sendImageで選択したファイル(ひとつ目)をbase64形式で読み込んでsocket.emitでサーバに送る。また、socket.onでサーバから送られてきた画像を受け取り表示する。

2.サーバサイドのコーディング

1で作成したHTMLをindex.htmとして保存し、それをサーバに置いて読み込む。ポートは9001を使う。

// モジュール読み込み
var http = require( 'http' );
var socketio = require( 'socket.io' ); // socket.ioモジュールの読み込み
var fs = require( 'fs' );

// HTTPサーバ(ポート9001)の生成
var port = 9001;
var server = http.createServer( function( req, res ) {
    res.writeHead( 200, { 'Content-Type' : 'text/html' } );
    // 表示するhtmlの読み込み
    res.end( fs.readFileSync( __dirname + '/index.html', 'utf-8' ) );
}).listen( port );

// HTTPサーバへのソケットのひも付け
var io = socketio.listen( server );

io.sockets.on( 'connection', function (socket) {
  // ブラウザから画像を受け取る
  socket.on( 'image', function ( imageData ) {
    // ブラウザから受け取った画像をもう一方のブラウザへ送る
    socket.broadcast.emit( 'image', imageData );
  });

});

socket.onでブラウザからの画像を受け取り、socket.broadcast.emitでもう一方のブラウザに画像を送る。

3.ファイルをnode.jsサーバに配置

サーバサイド、クライアントサイド両方のファイルをnode.jsサーバに配置する。ここではサーバサイドのファイルをtest.js、クライアントサイドのファイルをindex.htmlとする。

4.ブラウザからのアクセス

node.jsサーバを以下のコマンドで起動し、ブラウザの画面を2つ開き、http://localhost:9001/にアクセスする。ブラウザを起動するPCがnode.jsサーバと別の場合は、localhostをnode.jsサーバのIPアドレスに変える。


ブラウザに以下のような画面が表示される。


「参照...」ボタンを押して画像ファイルを選択すると、もう一方のブラウザ画面に画像が表示される。


2016年6月21日火曜日

socket.ioでブラウザ双方向通信をする

ブラウザで双方向通信できないかと調べたところ、socket.ioというnode.js用ライブラリを使うとできるということなので、実際に動かしてみた。socket.ioはWebSocketを扱うためのライブラリ。WebSocketはサーバとクライアント間の双方向通信するための規格で、Ajaxなどの従来の技術よりも通信効率がいい。

今回はUbuntuにnode.jsをインストールし、ブラウザはFirefoxを使用。node.jsサーバを起動し、ブラウザで2つ画面を開いて、その2つの画面間でnode.jsサーバ経由の通信を行う。以下はそのときの手順。

1.node.jsインストール

node.jsに加えてsocket.ioのインストールに使うnpmもインストールする。


インストールするとnode.jsのコマンド名はデフォルトではnodejsになっているので、シンボリックリンクを作成してnodeで実行できるようにする。


バージョン確認。


2.socket.ioインストール

socket.ioをインストールすると、ホームディレクトリにnode_modules/socket.ioディレクトリが作成される。


3.クライアントサイドのコーディング

①socket.ioライブラリの読み込み
    
    
②HTML部の記述(bodyタグ内)

socket.io test

③javascript部の記述
// ソケットへ接続(node.jsサーバとブラウザを起動するPCが別の場合は、node.jsサーバのアドレスを指定)
  var socket = io( 'http://localhost:9001/' );
  socket.on( 'connect', function () {
    // 相手ブラウザからのメッセージを受け取る
    socket.on( 'message', function ( message ) {
      // 相手ブラウザのメッセージを表示
      dispData( message );
    });


    // 相手ブラウザが切断した通知を受け取る
    socket.on( 'disconnect', function () {
      dispData( 'The other browser was disconnected' );
    });

  });
  
// 相手ブラウザにデータを送信
function sendData() {
  var message = document.form.input.value;
  socket.emit( 'message', message );
}
// 相手ブラウザから送られてきたメッセージを表示
function dispData( message ) {
  var display = document.getElementById( 'display' );
  display.innerHTML = message;
}
socket.onでサーバからのデータを受信し、socket.emitでサーバにデータを送信する。 その際に第1引数にmessageなどとイベント名を指定することで、送受信するデータを特定する。

4.サーバサイドのコーディング

node.js用のコードは以下の通り。ここではポート9001を使い、3で作成したHTMLをindex.htmとして保存し、それをサーバに置いて読み込む。
// モジュール読み込み
var http = require( 'http' );
var socketio = require( 'socket.io' ); // socket.ioモジュールの読み込み
var fs = require( 'fs' );

// HTTPサーバ(ポート9001)の生成
var port = 9001;
var server = http.createServer( function( req, res ) {
    res.writeHead( 200, { 'Content-Type' : 'text/html' } );
    // 表示するhtmlの読み込み
    res.end( fs.readFileSync( __dirname + '/index.html', 'utf-8' ) );
}).listen( port );

// HTTPサーバへのソケットのひも付け
var io = socketio.listen( server );

io.sockets.on( 'connection', function (socket) {
  // ブラウザからメッセージを受け取る
  socket.on( 'message', function ( message ) {
    // もう一方のブラウザへメッセージを送る
    socket.broadcast.emit( 'message', message);
  });

  // ブラウザ切断時にもう一方に切断したことを伝える
  socket.on( 'disconnect', function () {
    socket.broadcast.emit( 'disconnect' );
  });
});

socket.onでブラウザからのデータを受信し、socket.broadcast.emitでもう一方のブラウザにデータを送信する。 クライアントサイドと同様に、第1引数にmessageなどとイベント名を指定することで、送受信するデータを特定する。  socket.broadcast.emitはデータを受け取ったクライアント以外すべてのクライアントに送信する。

5.ファイルをnode.jsサーバに配置

サーバサイド、クライアントサイド両方のファイルをnode.jsサーバに配置する。ここではサーバサイドのファイルをtest.js、クライアントサイドのファイルをindex.htmlとする。

6.node.jsサーバの起動



7.ブラウザからアクセス

ブラウザの画面を2つ開き、以下のurlにアクセスする。ブラウザを起動するPCがnode.jsサーバと別の場合は、localhostをnode.jsサーバのIPアドレスに変える。

以下のような画面が表示される。送信ボックスに何か入力して「送信」ボタンを押すと、もう一方の画面に入力した内容が表示される。



この送信ボックスからはHTMLタグやスタイルシートも送れる。例えば「<b style="color: #ff0000;">test</b>」を送ると太字で赤いtestが相手ブラウザに表示される。さらには、googleマップやYouTubeの埋め込みコードも送れるので、相手のブラウザに突然地図や動画を表示させることもできる。インターネット上で使うにはセキュリティ対策が必要だろうけれど、使い方次第で面白いことができそう。

2016年6月12日日曜日

Three.jsのTrackballControlsの使い方

Three.jsでオブジェクトを動かしたり視点を移動させるときに便利なのがTrackballControls。利用するには、Three.jsのサイトからダウンロードできるThree.jsライブラリ圧縮ファイル内に含まれるTrackballControls.jsを読み込む。場所はthree.js-master\examples\js\controls\TrackballControls.js。

使うのに最低限必要なことは以下の3点のみ。

1.TrackballControls.jsの読み込み

HTMLに以下のコードを記述する。
<script src="TrackballControls.js"></script>


2.TrackballControlsインスタンスの作成

Threeのカメラ(camera)を渡してTrackballControlsインスタンスを作成する。
var controls = new THREE.TrackballControls( camera );    

3.アップデート

レンダリングループ内でアップデートする。
controls.update();

サンプルの表示

以上だけでも十分使えるが、移動速度などを変えたいときもあるので、使えるプロパティやメソッドを知っておくと便利。

プロパティ(設定値はデフォルト値)

rotateSpeed = 1.0; // 回転速度
zoomSpeed = 1.2; // ズーム速度
panSpeed = 0.3; // 移動速度

noRotate = false; // 回転の有効/無効
noZoom = false; // ズームの有効/無効
noPan = false; // 移動の有効/無効

minDistance = 0; // ズームの最小距離
maxDistance = Infinity; // ズームの最大距離

メソッド

reset() // カメラを初期位置(TrackballControlsインスタンス 作成時の位置)に戻す
dispose() // イベントリスナーの削除

2016年6月9日木曜日

Three.jsでPCカメラの映像をテクスチャにする

Three.js(r74)でPC内蔵カメラの映像をテクスチャにしたので、そのときの方法をまとめる。大まかな流れは、WebRTCでカメラ映像のストリームデータを取得し、それをもとにURLオブジェクトを作成してvideoタグのsrcに設定する。あとはThree.jsで動画をテクスチャにすると同じ。

1.カメラ映像のストリームデータ取得


WebRTCのnavigator.getUserMediaを使い、カメラ映像のストリームデータを取得する。
// ブラウザ間差異対応
navigator.getUserMedia = ( navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
// ユーザにメディアデバイス(カメラ、マイクなど)の使用をを尋ねる
navigator.getUserMedia(
   // カメラを使用して、マイクを使用しない場合
   { video: true, audio: false },

   // localMediaStream(カメラ映像のストリームデータが含まれる)が取得できた場合
   function( localMediaStream ) {
      // ここにlocalMediaStreamを取得できたときの処理を記述
   },

   // localMediaStreamが取得できなかった場合
   function( err ) {
      console.log( err );
   }
);
navigator.getUserMediaを使うと、ブラウザで開いたときに以下のようなポップアップウィンドウ(Firefoxの場合)が表示され、「選択したデバイスを共有」が押されると、内部的にはlocalMediaStreamを取得できる。


ちなみに、navigator.getUserMediaはChrome、Firefox、Operaでは対応しているが、IE、Safariは未対応。


2.videoエレメントの作成とURLオブジェクト作成


videoエレメントを作成し、window.URL.createObjectURLでURLオブジェクトを作成する。
// ブラウザ間差異対応
window.URL = window.URL || window.webkitURL;
// videoエレメント作成
var video = document.createElement( 'video' );
// URLオブジェクトを作成して、videoエレメントのsrcに設定
video.src = window.URL.createObjectURL( localMediaStream );
// 動画の再生
video.play();


3.videoエレメントから動画テクスチャを作成


videoエレメントからTHREE.VideoTextureで動画テクスチャを作成する。
// 動画テクスチャ作成
var texture = new THREE.VideoTexture( video );
// 1テクセルが1ピクセルより大きな範囲をカバーするときのテクスチャサンプリング方法の指定
texture.magFilter = THREE.LinearFilter;
// 1テクセルが1ピクセルより小さな範囲をカバーするときのテクスチャサンプリング方法の指定
texture.minFilter = THREE.LinearFilter;
// 動画テクスチャフォーマットの指定
texture.format = THREE.RGBFormat;


・立方体にカメラの映像を表示させたサンプル

サンプルの表示

・navigator.getUserMediaを利用してつくったのがWebGLで遊ぶのCube Face。PCカメラ映像を写真として切り出して、それをテクスチャにした立法体をランダムに配置するWebアプリ。

2016年5月30日月曜日

Three.jsでYouTube動画のオブジェクトを作成する

Three.js(r74)でYouTube動画をマテリアルとして使えないかと調べたが方法が見つからない。代わりに、平面オブジェクトとして作成する方法はわかったので、その内容をまとめる。簡単に言うと、divエレメントを作成し、それにYouTubeのHTML用埋め込みコード(iframeエレメント)を追加。divエレメントからTHREE.CSS3DObjectでオブジェクトを作成する。レンダラーはTHREE.WebGLRendererではなくTHREE.CSS3DRendererで作成する。以下はその手順。

1.CSS3DRenderer.jsのコピー
CSS3DRenderer.jsはThree.jsのサイトからダウンロードしたファイルに含まれている。ファイルを解凍して以下の場所にあるCSS3DRenderer.jsを適当な場所にコピーする。ここではjsというフォルダにコピー。
\three.js-master\examples\js\renderers

2.CSS3DRenderer.jsの読み込み
HTMLでCSS3DRenderer.jsを読み込む。


3.YouTubeのHTML用埋め込みコードの取得
YouTubeで使いたい動画を開き、動画の下にある[共有]>[埋め込みコード]をクリッすると、HTML用の埋め込みコードが表示される。「src="..."」のダブルクォーテーションで囲まれた箇所をコピーする。


4.レンダラーの作成
THREE.CSS3DRendererでレンダラーを作成する。
// THREE.CSS3DRendererでレンダラーを作成
var renderer = new THREE.CSS3DRenderer();
// レンダラーが描画するキャンバスサイズの設定
renderer.setSize( window.innerWidth, window.innerHeight );
// キャンバスをDOMツリーに追加
document.body.appendChild( renderer.domElement );

5.エレメントの作成
divエレメントとiframeエレメントを作成し、iframe.srcにコピーしておいたYouTube動画へのリンク設定する。そしてdivエレメントの子エレメントとしてiframeエレメントを追加する。
// divエレメント作成
var div = document.createElement( 'div' );
div.style.width = '480px';
div.style.height = '180px';
div.style.backgroundColor = '#000';

// iframeエレメント作成
var iframe = document.createElement( 'iframe' );
iframe.style.width = '240px';
iframe.style.height = '180px';
iframe.style.border = '0px';

// iframeエレメントのsrcにYouTube動画を指定
iframe.src = 'https://www.youtube.com/embed/rD5yoLWPR5w?rel=0&controls=0&showinfo=0';
// divエレメントの子エレメントとしてiframeエレメントを追加
div.appendChild( iframe );

var iframe2 = document.createElement( 'iframe' );
iframe2.style.width = '240px';
iframe2.style.height = '180px';
iframe2.style.border = '0px';
iframe2.src = 'https://www.youtube.com/embed/GZcLdvm_6lA?rel=0&controls=0&showinfo=0';
div.appendChild( iframe2 );

6.オブジェクトの作成
THREE.CSS3DObjectでオブジェクトを作成する。
// THREE.CSS3DObjectインスタンス作成
var object = new THREE.CSS3DObject( div );
// オブジェクトをシーンに追加
scene.add( object );

Three.jsを使うのに必要な他のコードを追加すると、以下のようなオブジェクトが作成される。

サンプルの表示