2015年10月17日土曜日

お茶缶3D: three.jsで円柱にテクスチャを貼りたいが、平らな面は貼りたくない時

概要
円柱型の間の側面に「お茶」と書きたいけど、蓋と底は書かなくていいような場合に、どうしたらよいか悩んでいました。
webページはこちら
普通に、「お茶」テクスチャをかぶせると全面を包んでしまうため、左の筒のように蓋の上にも文字が書かれます。困った。
解決方法

右のように側面だけにするには、 MeshFaceMaterialを使用します。そうすることで複数のMaterialを使い分けられます。 MeshFaceMaterialはそれらをリストにして持ちます。side_materialが0番目、end_materialが1番目のMaterialで、これをfaceIndexといいます。face(Geometryを分割したSegmentごとの面)に使うfaceIndexを変えることで、複数のface
  • side_materialはテクスチャつきの円周用のMaterial
  • side_materialは蓋と底と面でテクスチャのないMaterial
  • 右の円柱のコードの主要部分
    var side_material = new THREE.MeshPhongMaterial({ map: texture });
    var end_material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
    material = new THREE.MeshFaceMaterial([side_material, end_material]);
    
    //
    obj = new THREE.Mesh(geometry, material);
    
    //後ろ radialSegments*2個は end_material
    //それ以外は side_material
    for(var i = 0; i < geometry.faces.length-geometry.parameters.radialSegments*2; i++){
        geometry.faces[i].materialIndex = 0;
    }
    for(var i = geometry.faces.length-geometry.parameters.radialSegments*2; i < geometry.faces.length; i++){
        geometry.faces[i].materialIndex = 1;
    }
    
    コードはこちら
    おまけ
    どのfaceをend_materialにするかがポイントですが、今回はCylinderGeometryのコードの内部で、先に側面のfaceを追加し、後で蓋、底のfaceを追加していることを利用して、geometry.facesの配列上の位置で特定しました。
    本来なら、face(というかsegment)ごとに法線ベクトルを調べてcap/endを特定するのが良さそうではあります。
    "cap", "end"みたいな名前を指定してfaceの集合が得られるような関数(かそれに続けて関数をmapするとか)があると嬉しい。。?