作成者別アーカイブ: tateo

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だすクックがダメだわ。

SwiftでQRコードを生成する

Core Imageを使うと簡単に作れるんですね〜
Swiftで実装するとこんな感じです。

func makeQRCodeImage(code:String) -> UIImage? {    
    guard let ciFilter = CIFilter(name: "CIQRCodeGenerator") else {
        return nil
    }
    ciFilter.setDefaults()
    
    // QRコードを設定
    ciFilter.setValue(code.data(using: String.Encoding.utf8), forKey: "inputMessage")
    
    // 誤り訂正レベルを設定
    // "L" "M" "H" が設定できるみたい
    ciFilter.setValue("M", forKey: "inputCorrectionLevel")
    
    if let outputImage = ciFilter.outputImage {        
    	// 作成されたQRコードのイメージが小さいので拡大する
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let zoomedCiImage = outputImage.applying(sizeTransform)
        return UIImage(ciImage: zoomedCiImage, scale: 1.0, orientation: .up)
    }
    
    return nil
}

SharedPreferencesでputStringSetを使う時の注意点

こんな感じでgetStringSetで得た結果に直接要素を追加して、
putStringSetしてもダメっぽい。

SharedPreferences pref 
  = context.getSharedPreferences("name",Context.MODE_PRIVATE);

String key = "keyA";

// Setを取得する
Set<String> set1 = pref.getStringSet(key, new HashSet<String>());

// Setに文字列を追加
set1.add("追加する文字列");

// そのまま保存
SharedPreferences.Editor editor = pref.edit();
editor.putStringSet(key, set1);
editor.apply();

 

この状態でgetStringSetするとちゃんと設定されてて正常に動いているように見えるんだけど、
プロセスが再起動されたあとに消えてます。(2件目以降)

対応としては下記のように別のSetを作ってコピーしてあげるとちゃんと動いてくれた。

// Setをクローンする
HashSet<String> set2 = new HashSet<>();
set2.addAll(set1);

SharedPreferences.Editor editor = pref.edit();
editor.putStringSet(key, set2);
editor.apply();

モヤモヤしますな。

ルビ付きのNSAttributedStringを使うとiOS8でBAD_ACCESSが発生する

原因としてはルビに空文字を設定してるのが悪いっぽい。

たとえばこんな感じ

// 空文字をルビとして設定する
let ruby = ""
var textRef: [Unmanaged<CFString>?] = [Unmanaged<CFString>.passRetained(ruby as CFString) as Unmanaged<CFString>, .none, .none, .none]
let annotation = CTRubyAnnotationCreate(.auto, .auto, 0.5, &textRef[0]!)
                
let attributedString = NSAttributedString(
                    string: "漢字",
                    attributes: [kCTRubyAnnotationAttributeName as String: annotation]

// ↓ ここでEXC_BAD_ACCESSが発生する
let line = CTLineCreateWithAttributedString(attributedString)

 

ほかにも

let framesetter = CTFramesetterCreateWithAttributedString( attributedText)

// ↓ ここでEXC_BAD_ACCESSが発生する
let frame = CTFramesetterCreateFrame(framesetter, CFRange(), path, nil)

ちなみにiOS10じゃ発生しないよ。

Javaで青空文庫のルビをパースする

Swiftで作ったサンプルのJava版です。
仕様などはSwift版と同様なので→Swift版

こちらもJavaで正規表現を利用するサンプルにもなってます。
 

使い方

String sampleText = "てめえらの|血《ち》は|何色《なにいろ》だーっ!!";
AozoraRubyParser parser = new AozoraRubyParser(sampleText);
List<AozoraRubyParser.Result> tokens = parser.parse();
for (AozoraRubyParser.Result r : tokens){
  Log.d("", r.text +  (r.ruby != null ? "[" + r.ruby + "]" : "") );
}

 

パーサーのコード

public class AozoraRubyParser {

  static public class Result {
    public String text;
    public String ruby;

    public Result(String t, String r) {
      this.text = t;
      this.ruby = r;
    }
  }


  private String text;

  public AozoraRubyParser(String text) {
    this.text = text;
  }

  public List<Result> parse() {

    ArrayList<Result> result = new ArrayList<>();

    String patternStr = "|(.+?)《(.+?)》";
    Pattern ptn = Pattern.compile(patternStr);

    int position = 0;

    Matcher matcher = ptn.matcher(text);
    while (matcher.find()) {
      if (position < matcher.start()) {
        String subText = this.text.substring(position, matcher.start());
        result.add(new Result(subText, null));
      }
      position = matcher.end();

      // 文字と読み仮名を抽出
      result.add(new Result(matcher.group(1), matcher.group(2)));
    }

    // 文末の残りを追加
    if (this.text.length() > position) {
      String subText = this.text.substring(position, this.text.length());
      result.add(new Result(subText, null));
    }

    return result;
  }
}

Swiftで青空文庫のルビをパースする

青空文庫のルビ表記をパースするサンプルを作ってみました。

青空文庫のルビはこんな感じで定義されてます。
http://www.aozora.gr.jp/KOSAKU/MANUAL_2.html

本来はリンクの通り、いろいろと細かな仕様があるのですが、今回はプログラムで「ルビ付き文字列を定義する」ということだけを狙ってシンプルな仕様で実装してみます。
具体的には↓のフォーマットだけを対象とします。

|漢字《よみかた》

例えばこんな感じですね。

「てめえらの|血《ち》は|何色《なにいろ》だーっ!!」

先に使い方ですが

let text = "てめえらの|血《ち》は|何色《なにいろ》だーっ!!"
let parser = AozoraRubyParser(text: text)
if let result = parser.parse() {
	for r in result {
		print("\(r.text) \( (r.ruby != nil) ? "[" + r.ruby! + "]" : "" )")
	}
}

こんな感じで出力されます
> てめえらの
> 血 [ち]
> は
> 何色 [なにいろ]
> だーっ!!

class AozoraRubyParser {
  
  class Result {
    let text:String
    let ruby:String?
    init(text:String,ruby:String?) {
      self.text = text
      self.ruby = ruby
    }
  }
  
  
  let text:String;  
  
  init(text:String) {
    self.text = text;
  }
    
  func parse() -> [Result]?{    
    
    var result:[Result] = []
    do {
  
      var position = 0;
      let nsText = text as NSString
      let totalLength = nsText.length
      
      let re = try NSRegularExpression(pattern: "|(.+?)《(.+?)》", options: [])
  
      let matches = re.matches(in: text, options: [], range: NSRange(location: 0, length: totalLength))
      for match in matches {
        let matchRange = match.range;
        
        // 現在の位置からrangeの先頭が離れていたら、その間の文字を抽出する
        if position < matchRange.location {
          let subRange = NSRange(location: position, length: matchRange.location - position)
          result.append(Result(text: nsText.substring(with:subRange), ruby: nil))
        }
        position = matchRange.location + matchRange.length;
	
        // 文字と読み仮名を抽出
        let str = nsText.substring(with: match.rangeAt(1))
        let ruby = nsText.substring(with: match.rangeAt(2))
        result.append(Result(text: str, ruby: ruby))
        
      }
      
      // 文末の残りを追加
      if totalLength > position {
        let subRange = NSRange(location: position, length: totalLength - position)
        result.append(Result(text: nsText.substring(with:subRange), ruby: nil))
      }
      
      
    }catch let err  {      
      log.error("\(err)")
      return nil;
    }
  
    return result
  }

}

Swiftで正規表現を使うサンプルとしてもどうぞ。

「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で行いました。
多少の修正があるにしても次の引っ越し時、大いに役立ってくれることでしょう。

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