カテゴリー別アーカイブ: 未分類

Polyから落としてきたOBJファイルをglTFに変換してThree.jsで表示する

googleが3Dモデルの共有ライブラリPolyを公開しましたね。
このライブラリでは数多くのモデルがOBJ形式でダウンロードできるようになっています。

ただ、googleにしては意外だったのはglTF形式に対応してないこと。
まだ時期尚早だと判断したのかな??

glTFってのは3Dモデルの標準化を狙った次世代の3Dモデルフォーマットで、このところ注目のモデル形式なのです。
今後はglTFが標準になってくることを祈って、Polyから拾ってきたモデルをglTFに変換してThree.jsで表示してみます。

前提
・webpackでビルドできる環境ができてる
・前の投稿のようにsceneに別の形式でモデルを描画できている

 

サンプルとして使わせてもらうのは↓の恐竜
T-Rex scanned with Qlone

ここからダウンロードしたファイルを展開(解凍)して
[projectroot]/src/3dmodels/t_rex
の中に保存しておきます。
※”model_T Rex_20171104_194215374.obj”ってファイル名なんですけど、あとあと面倒なので”t_rex.obj”にリネームしておいてください。
 
 

んで、このOBJ形式のモデルをglTFに変換するのですが、obj2gltf を使わせてもらいます。
https://github.com/AnalyticalGraphicsInc/obj2gltf

公式サイトにある通り、プロジェクトのルートディレクトリで
$ npm install –save obj2gltf
を行います。
package.jsonにobj2gltfが追加されましたね??

んで、npmのスクリプトでPolyから落としてきたモデルをglTFに変換しましょう。
下記のようにスクリプトを追記して

package.json

{
  "scripts": {    
    "gltf:rex": "obj2gltf -b -i 'src/3dmodels/t_rex/t_rex.obj' -o 'public/t_rex/t_rex.glb'"
  },  
  ・・省略・・
}

※ -oで指定するのが出力先ファイル名なのですが、ファイル名は無視されてしまうっぽいです。入力されたobjと同じ名前で出力されてしまいます。

obj2gltfを実行

$ npm run gltf:rex

そしたら public/t_rex/t_rex.glb ってのが作成されたかと思います。
 
 
 

次にこのglTF形式のモデルを表示してみましょう。
全部のコードを載せるのは冗長なので端折って説明しますね。
(それ以外のコードは前の投稿を見てください)

glTFのローダーはOBJと同じようにexamplesに保存されていますのでそれをインポートします。

import 'imports-loader?THREE=three!../../node_modules/three/examples/js/loaders/GLTFLoader'	

で先ほど作ったglTFモデルを読みこんでsceneに追加します。

var loader = new THREE.GLTFLoader();
var url = "t_rex/t_rex.glb";
loader.load( url, function(data) {
    var object = data.scene;
    scene.add( object );
}, undefined, function ( error ) {
    console.error( error );
} );

こんな感じ。


“T-Rex scanned with Qlone” by Ronen Horovitz / CC BY

Three.jsでOBJファイルを読み込む(webpackを使って)

Three.jsでOBJファイルを読み込む時にMTLLoaderとOBJLoaderを使いたいけど、webpackを使った時に導入方法がよくわからなかったので色々調べたメモ。
ついでにStatsでFPSの計測とOrbitControlsを使ったマウス操作にも対応。

前提
・webpackでビルドできる環境ができてる
・objモデルはどこからか拾ってきてください

まずは”three.js”と、three.jsのexampleに保存されているファイルをインポートするために
“imports-loader”と”expose-loader”をpackage.jsonに追加しておく。

package.json

"devDependencies": {
  "imports-loader": "^0.7.0",
  "expose-loader": "^0.7.0",
  "three": "^0.88.0",
  ・・省略・・
}

 

表示するHTMLはwebpackで処理されたapp.jsを読み込む。それだけ。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>three.js sample</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>
<script src="js/app.js"></script>
</body>
</html>

 
 

んで、これが実際の処理

app.js

var THREE = require('three');

// Threeのサンプルソースからインポート
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/loaders/MTLLoader'
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/loaders/OBJLoader'
import 'imports-loader?THREE=three!../../node_modules/three/examples/js/controls/OrbitControls.js'
// ついでにStatsも使えるように
require("expose-loader?Stats!../../node_modules/three/examples/js/libs/stats.min.js");

(function(){
  var container;
  var stats;
  var scene,camera,ambient;
  var renderer;
  var initialized = false;

 
  init();
  animate();

  function init() {

    container = document.createElement( 'div' );
	  document.body.appendChild( container );

    var width = window.innerWidth,
        height = window.innerHeight;
        
    scene = new THREE.Scene();

    // 読み込むモデルの指定
    var modelPath = "cat/";   // mtlとobjの保存されてるパス
    var mtlName = "cat.mtl";  // mtlファイル名
    var objName = "cat.obj";  // objファイル名


    var objLoader = new THREE.OBJLoader();
    var mtlLoader = new THREE.MTLLoader();
    
    // onProgressとonErrorの引数にはProgressEventってオブジェクトが渡されます
    var onProgress = function ( evt ) {
      if ( evt.lengthComputable ) {
          var completed = evt.loaded / evt.total * 100;
          console.log( Math.round(completed, 2) + '% downloaded' );
      }
    };
    var onError = function ( evt ) {
        console.error("モデルの読み込みに失敗したみたい");
        var xhr = evt.target;
        if (xhr){
          console.error(xhr.responseURL + " status:" + xhr.status + "(" + xhr.statusText + ")");
        }
    };

    mtlLoader.setPath( modelPath ); //パスを指定。これを指定しないとテクスチャ画像の読み込み先が変なことになる。
    mtlLoader.load(mtlName, function (materials){
        materials.preload();

        objLoader.setMaterials(materials);
        objLoader.setPath( modelPath);
        objLoader.load(objName, function (object){
            
            scene.add( object );

            initialized = true;
        }, onProgress, onError);

    }, onProgress, onError);


    ambient = new THREE.AmbientLight(0xffffff);
    scene.add(ambient);

    camera = new THREE.PerspectiveCamera(45, width / height, 1, 5000);
    camera.position.set(0, 100, 200);
    camera.lookAt(new THREE.Vector3(0, 0, 0));


    renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    renderer.shadowMap.enabled = true;
    container.appendChild( renderer.domElement );


    // マウスで操作可能とする
    var controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.maxPolarAngle = Math.PI * 0.5;
    controls.minDistance = 100;
    controls.maxDistance = 3000;

    // FPSなどのモニタリング
    stats = new Stats();
    container.appendChild( stats.dom );
  }

  function animate() {
    requestAnimationFrame( animate );
    if (!initialized) return;

    renderer.render( scene, camera );
    stats.update();
  }

})();

 
 

よくあるThree.jsのサンプルと変わらないけど、先頭のインポートたちが重要。
この記述

import 'imports-loader?THREE=three!../../node_modules/three/・・省略・・'

でnode_modulesの中に展開されているjsファイルをインポート。

“mtlLoader.load”あたりでインポートされたMTLLoaderとOBJLoaderを使ってモデルを読み込んでます。

 

[注意点]
たまに正しく読み込めないOBJファイルがある。(Free3Dにあるブガッディとかはダメだった)
なんか変だな?と思ったら別のOBJファイル、MTLファイルを使ってみて。

iOS11のデザインって・・

iOS11にしたけど、なんかダサい。
妙に太いフォント、文字のサイズ、UIの細かい所、アイコンの角丸のアール、影の統一感がない。
細かいところがつめきれていないというか、バランスが悪いというか。。

自分の感性が間違ってるのかもしれんけど、Apple製品にあるまじきレベル。
これでOKだすクックがダメだわ。

「Server aborted the SSL handshake」とか怒られる件

なんですかね。
gitも


$ git push
fatal: unable to access 'https://****.***/': Server aborted the SSL handshake

って言われるし、curlも


$ curl https://****.***/
curl: (35) Server aborted the SSL handshake

pipも


$ pip install -U pip
Could not fetch URL https://pypi.python.org/simple/pip/: There was a problem confirming the ssl certificate: EOF occurred in violation of protocol (_ssl.c:661) - skipping

とか言われて実行できない。
困った。
2日ほど困った。

これまでは動いてたのに・・

ふと思いついた。
こんな時、大概悪いのはセキュリティソフト – Avast – だ!!!!

で、AvastのWebシールドを無効にしたらオールオッケー!!!

Avastのおかげで無駄な時間を使わされました!くそっ!くそっ!

Macメインになってセキュリティソフトから受けるストレスはかなり(体感97%ほど)減りましたけど、たまにこんなのあるんですよねぇ。

※ちなみに環境は↓
– OSX El Capitan (10.11.6)
– Avast Mac セキュリティ 2016
 プログラムバージョン12.6
 ウイルス定義(17042000)

サーバをEC2に引っ越しました。

まぁ、仮住まいですが。

これまではこのブログはネットオウルのミニバードってサービス使ってました。

このミニバード、安い割にMySQLのデータベースを複数作成できたりサブドメイン・マルチドメインに対応していたり、
何かと融通が利いて気に入っていたのですけど、最近ちょっと遅いかなーと感じてきて。。
特に混んでいる時間帯はWordpressの管理画面を表示するのをためらう事がありました。

うーん、どこかに引っ越そう。。
ちょうどawsのLightsailがサービス開始されたのでそこに移ろうと思ったのですが、
Tokyoリージョンではまだリリースされていないみたい。

そこで仕事でよく使っているEC2に引っ越しです。
1年間は無料ですし。(1年内にきっとLightsailが東京に来るでしょう??)

サーバー構築は1年以内に再度引っ越す事に備えansibleで行いました。
多少の修正があるにしても次の引っ越し時、大いに役立ってくれることでしょう。

混んでる時間帯でもサクッと表示されるのは気持ちいいですね。

DLNAで再生してたら途中で中断される問題!(DMCアプリ落ち)

ネットワークオーディオ機器を使ってみたくて「Marantz M-CR611」を購入したんですよ。こないだ。(→記事)
「Marantz Hifi Remote」からNASに保存されている上原ひろみの「SPARK」を再生して「ええ音や・・(涙」と聞いてたら数曲再生しただけで止まってしまう・・萎えるー

これが「DMCアプリ落ち問題!」
→ なんらかの原因(※)でDMCアプリが停止したら次の曲以降が再生されない。
※スリープ、バッテリー切れ、ゲームなどの高負荷アプリを動かす・・など

比較的新しいiPhoneであればバックグラウンドでそれなりの時間までは再生してくれるんですけど、なにぶんちょっと古めのAndroidとかだとすぐに再生が止まってしまいます。
最初はこれ、なんでか分かりませんでしたがDLNAの機能を考えるとまぁしょうがない。
DMRは再生するリスト(以下プレイリストと呼ぶ)を保持せず、DMCからの指示で動くのでDMCが動いてくれないとどうしようも無いのだ。
(とはいえ、「Marantz Hifi Remote」はMarantz純正アプリでもあるんだから機器に専用コマンドで送信してM-CR611をDMPとして動かしてくれよ!・・とは思います。)

補足1.
DLNAを構成する機能

DMS(デジタルメディアサーバー)
 - コンテンツを保存してそれらを配信する機能を有する機器。DLNA対応のNASとかがこれにあたる。
DMP(デジタルメディアプレイヤー)
 - DMSのコンテンツを自分で検索/再生する機器。M-CR611のようなネットワークオーディオ機器は大概これ。
DMC(デジタルメディアコントローラー)
 - DMSのコンテンツを選択してそれを再生する機器(DMR)に指示するリモコンみたいな機能。スマホアプリ(Kazooとか)とか。
DMR(デジタルメディアレンダラー)
 - DMCから指令を受ける再生機器 。DMPとの大きな違いはDMCからの指令で再生すれば良いのでDMSのコンテンツを検索する機能を持たなくても良い。

補足2.
M-CR611のようなネットワークオーディオプレイヤーは大抵DMPにもDMRにも対応しています。
DMPとして動かせば(例えば本体でサーバの曲を選んで再生するとか)DMCは関係無いので、この場合はアプリが落ちようと関係無く再生されます。

で、色々と調べているとこの問題を解決する「OpenHome」という規格があるらしい。
しかもどうやら 「BubbleUPnP Server」というものを使うとOpenHome非対応の機器もOpenHome対応機器として動かすことができるらしい。
やってみよう!

BubbleUPnP Server

「BubbleUPnP Server」はMacやWindowsはもちろん、LinuxやAndroid、さらにはSynologyやQNAPなどのNASでも動作する。

用途を考えると常時起動している機器にインストールするのがベスト。
RaspberryPiへインストールする事も考えたが、今回は自宅のNAS「Synology DS215j」にインストールすることとした。

※ Kazoo Server も気になりますが、まだsynology用のパッケージは提供されていないようです。
 
 

Synology DS215j(DSM 6.0)へのインストール手順

(以下は執筆時点の手順なので公式のインストール手順を事前に確認してください)
※自分は今のところ正常に動いていますけど、自己責任でお願いしますね。大事なデータが消えたりしても自己責任で!

1.Java8のインストール
DSMの パッケージセンター → デベロッパーツール から 「Java8」 を選んでインストール。

2.Java8をOracleJDKに更新
DSMのメインメニューから「Java8」の画面を開いて

20170113_001
→ [Javaをアップグレード]

20170113_002
→ 「Java SE Download」

20170113_003
→ 「Linux ARM 32 Hard Float ABI」をダウンロード
※執筆時には「jdk-8u111-linux-arm32-vfp-hflt.tar.gz」こんなファイルがダウンロードされました。

先ほどの画面でダウンロードしたファイルを参照して[OK]

※ダウンロードするファイルは機種によって違うらしいです。↓のサイトを参照してくれとのこと。
http://minimserver.com/install-synology.html

3.事前の設定

コントロールパネル > ユーザー > 詳細
20170113_004
[ユーザーホームサービスを有効にする]にチェックをいれて[適用]

証明書のチェックを無効に
パッケージセンター > 設定
20170113_005
トラストレベルを「全ての製造元」に変更して[OK]

4.「BubbleUPnP Server」のインストール

下記URLからSynology用のパッケージをダウンロード
https://www.bubblesoftapps.com/bubbleupnpserver
※[Download Synology package]ってところ
※自分が行った時は BubbleUPnPServer.spk というファイルでした

パッケージセンター を開き[手動インストール]からダウンロードしたファイルを選びインストールします。
スクリーンショット_2017-01-13_16_57_30
※ffmpegのインストールが推奨されているけど今回はインストールせず。
 
  

BubbleUPnP Serverの設定

インストールできたら次は設定
※インストール直後に設定画面が開かれますけど、まだ起動に時間がかかっていたりすると空白ページが開かれてしまいます。
その時は、ちょっと待ってからメインメニューから[BubbleUPnP Server]を選んで再度開きましょう。
20170113_006

1.ネットワーク系
インターネットからアクセスしたくはないので、[Network and Security]というタブを開いて[Allow to access the server from the Internet]のチェックを外す。

2.DMRをOpenHome機器として公開する設定
[Media Renderers]というタブを開く
20170113_007

出力対象とするDMR (この場合はM-CR611)を選んで、[Create an OpenHome renderer]にチェックを入れる

以上でサーバ側は完了。簡単。

後はHomeHomeに対応したアプリ(※)でレンダラーを指定すればOK。
DMRとしてのM-CR611とOpenHome化された同機器がどちらも表示されているのでOpenHomeの方を選択

同じレンダラーを参照したアプリでは同じプレイリストが表示されるし、アプリを落としてもそのまま再生されます。
※ AndroidだとBubbleUPnP、iPhoneだとKazooとか

NASのCPU負荷も特に上がっていませんね。
20170113_008
これなら大丈夫そう。

ScalaでSeq(List)をソート

要素をそのまま比較する単純なソート (sorted)

val s1 = Seq(6,1,3,3)
val r1 = s1.sorted

r1: Seq[Int] = List(1, 3, 3, 6)

 

要素に演算とかを施して比較する場合 (sortBy)

// tupleの最初の要素を大文字にした結果でソート
val s2 = Seq(("C",3),("a",1),("B",2))
val r2 = s2.sortBy(_._1.toUpperCase) 


 r2: Seq[(String, Int)] = List((a,1), (B,2), (C,3))

インスタンスの特定要素で単純にソートするならこれが便利

 

関数を渡して比較するソート (sortWith)

val s3 = Seq("a","Z","C","e")
val r3 = s3.sortWith((a,b) => a.compareToIgnoreCase(b) < 0)

r3: Seq[String] = List(a, C, e, Z)

これは色んなケースに対応できて一番使う気がします。つかこれを覚えておけば大体いける。

 

Javaで言うComparator的な比較用インスタンスを渡してソート (sorted + Ordering )

val s4 = Seq("a","Z","C","e")
val r4 = s4.sorted(new Ordering[String]{
  def compare(a:String,b:String):Int = {
    a.compareToIgnoreCase(b)
  }
})

r4: Seq[String] = List(a, C, e, Z)

うん、面倒。
だけどOrderingクラスを作っておけば使いまわせるのがイイですね。
複雑な要件でのソートであればこれか。

ランニングコース作成アプリ「RouteDesigner」

姉妹ブログの方でも紹介させてもらっていますが、ランニングコース作成アプリ「RouteDesigner」をリリースしました。
もちろん全面Swiftで作成してます。
対応OSはちょっと迷いましたが、今後のメンテナンスを考えてiOS8以降としました。
すりガラス効果使いただけなんですけどね。

ランニングやサイクリングに興味のある方は是非見てみて下さい。
紹介エントリ

AppStoreはこちら

bitcasaの退会がクソ面倒な件

bitcasaにちょっと期待していた自分がいたんですよね。
数ヶ月ほど前。
で、ご存知の方も多いでしょうけど11/21に価格改正が行われ、99ドル/1年だったものが999ドル/1年のビックリ価格になったのです。
(無制限プランの場合ね)

当初からサービスには99ドルで維持可能なのか疑問視されていたわけですが、CEOさんは「うちの重複除去と圧縮アルゴリズムすげーし大丈夫よ!w」と自身満々でおっしゃってらして「これは期待!」と思ってたわけです。
開発者用APIの公開もあることですしね。

しかし結局約10.1倍の価格改正となりガッカリしたわけです。
これが11/21の私。

んで、ここからが本題。
もうbitcasa見限ったのでアカウント消そうと思ったんですけど、
やってみるとこれがクソ面倒!

まずWEBのUIで退会を探してもどこにもない!
もしかして日本語UIだけないのかな?と思って英語UIで探しても無い!
しょうがないのでサポートから退会方法を探してみると↓が見つかった。
How Do I Downgrade/Cancel My Bitcasa Account?

なんでもsupport requestのDescriptionに”Delete my account.”と入力して送れとか。。。マジか!
※support requestはここね→ https://support.bitcasa.com/requests/new

送信すると1日位たってからこんなメールが飛んできます。

bitcasa-del1

マジでいいの?ファイル全部消えるけど?みたいな事聞いてきたので

Yes. of course!
Please delete my account!

とメールで返信。
これで一安心と思ったのですが、しばらくたってもアカウントが生きている。
うーん?
くそ改悪のせいで退会者が多いのかな??とか思って待ってたらこんなメールが。。

bitcasa-del2

はぁ?
そんなURL全く通知されてませんけど??
ペンディングってボール持ってるのはそっちでしょが!

・・どうやら削除の確認はメールで送るのではなく、このリンクにあるサポートサイトで返信しなければならないらしい。

bitcasa-del3

これでようやく削除できましたとさ。

ちなみにリクエストチケットのURLはhttps://support.bitcasa.com/requests/リクエスト番号
です。