3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

p5.jsのp5.Geometryで書いたオブジェクトをobj形式で保存する

Last updated at Posted at 2024-11-27

はじめに

p5.Geometryで作った図形をobjファイルの形式で保存したいと思います。参考にしたのは次のサイトです。
 メッシュを.objにしてUnityから出す
objファイルの形式とは次のようなものです。

# positions
v -0.50000000 0.00000000 0.00000000
v 0.00000000 -0.50000000 0.00000000
v 0.00000000 0.00000000 -0.50000000
v 0.00000000 0.00000000 0.50000000
v 0.00000000 0.50000000 0.00000000
v 0.50000000 0.00000000 0.00000000

# normals
vn -0.50000000 0.00000000 0.00000000
vn 0.00000000 -0.50000000 0.00000000
vn 0.00000000 0.00000000 -0.50000000
vn 0.00000000 0.00000000 0.50000000
vn 0.00000000 0.50000000 0.00000000
vn 0.50000000 0.00000000 0.00000000

# triangle faces
f 1//1 3//3 2//2
f 1//1 5//5 3//3
f 2//2 4//4 1//1
f 1//1 4//4 5//5
f 2//2 6//6 4//4
f 4//4 6//6 5//5
f 2//2 3//3 6//6
f 3//3 5//5 6//6

これを拡張子.objで保存すると、たとえばビューアーなどで内容が確認できます:

wfwf3r3fee3rerfe.png

正八面体ですね。色が無いので灰色になってしまっています。
頂点、法線、面のみのデータなので汎用性が高いです。Blenderに落として好きなようにカスタマイズしたりできます。便利です。
押し出しただけ:
efwrf33f.png

これをp5.jsのp5.Geometryで作ったgeomに適用しようというわけです。

コード全文

自作ライブラリでABCのメッシュを作ってそれを保存するコードです。
fisceToyBoxSample:textMesh

// fisceToyBox.
// https://github.com/inaridarkfox4231/fisceToyBox
// code: https://inaridarkfox4231.github.io/fisceToyBox/fisceToyBox.js

// じゃあテキストメッシュのサンプルおいておくか
let font;
let geom;
function preload() {
  font = loadFont('https://inaridarkfox4231.github.io/assets/KosugiMaru-Regular.ttf');
}
function setup() {
  createCanvas(windowWidth,windowHeight, WEBGL);
  // テキストメッシュを取得
  // まずテキストパスのサイクル集合を取得
  const cycles = fisceToyBox.getTextContours({
    font:font, targetText:"ABC", textScale:240
  });
  // これを加工してテッセレーションなどをしやすくする処理
  const result = fisceToyBox.cyclesToCycles(cycles);
  // ここから板を作る。今回は厚さ5で。
  geom = fisceToyBox.createBoardMeshFromCycles({result:result, thick:5});
  // あとはmodelで書くだけ

  const objFile = createObjFile(geom);
  // これでsaveできる。
  // コメントアウトを外せば保存されます。
  //saveTextData(objFile, "myABC", "obj")
}

function draw() {
  background(0);
  fill(255);
  orbitControl();
  lights();
  model(geom);
}

// reference:https://techblog.kayac.com/export-unity-mesh-as-obj
function createObjFile(g){
  const vArray = new Float32Array(g.vertices.length*3);
  const nArray = new Float32Array(g.vertexNormals.length*3);
  for(let k=0; k<g.vertices.length; k++){
    const v = g.vertices[k];
    vArray[3*k] = v.x;
    vArray[3*k+1] = v.y;
    vArray[3*k+2] = v.z;
  }
  for(let k=0; k<g.vertexNormals.length; k++){
    const n = g.vertexNormals[k];
    nArray[3*k] = n.x;
    nArray[3*k+1] = n.y;
    nArray[3*k+2] = n.z;
  }

  let result = "# vertice data\n";
  for(let k=0; k<g.vertices.length; k++){
    result += `v ${vArray[3*k]} ${vArray[3*k+1]} ${vArray[3*k+2]} \n`;
  }
result += "# normal data\n";
  for(let k=0; k<g.vertexNormals.length; k++){
    result += `vn ${nArray[3*k]} ${nArray[3*k+1]} ${nArray[3*k+2]} \n`;
  }
  result += "# faces data\n";
  for(let k=0; k<g.faces.length; k++){
    const f = g.faces[k];
    result += `f ${1+f[0]}//${1+f[0]} ${1+f[1]}//${1+f[1]} ${1+f[2]}//${1+f[2]} \n`;
  }
  return result;
}

// postFixを"obj"にするとobjファイルとして保存される
function saveTextData(data, name, postFix = "txt"){
  const fileName = name.concat(".").concat(postFix);

  // HTMLのリンク要素を生成する。
  const link = document.createElement("a");

  // リンク先にJSON形式の文字列データを置いておく。
  link.href = "data:text/plain," + encodeURIComponent(data);

  // 保存するJSONファイルの名前をリンクに設定する。
  link.download = fileName;

  // ファイルを保存する。
  link.click();

  link.remove();
}

getTextContours()はテキストとフォントとサイズからそのテキストのcontoursを取得する関数です。ざっくり。で、cyclesToCycles()はその相互関係などのメタデータを用意するための関数です。さらにテッセレーションしやすいように下処理を施しています。この形式で、createBoardMeshFromCycles()に渡すとそのcontoursのGeometryができます。この場合は板です。厚さは5としています。このデータをもとにp5.Geometry()を構成していますが、まあp5.Geometry()なら何でも大丈夫でしょう。
 p5.Geometryなら何でも大丈夫です。

テキストを作る

ここでテキストデータを作っています。

// reference:https://techblog.kayac.com/export-unity-mesh-as-obj
function createObjFile(g){
  const vArray = new Float32Array(g.vertices.length*3);
  const nArray = new Float32Array(g.vertexNormals.length*3);
  for(let k=0; k<g.vertices.length; k++){
    const v = g.vertices[k];
    vArray[3*k] = v.x;
    vArray[3*k+1] = v.y;
    vArray[3*k+2] = v.z;
  }
  for(let k=0; k<g.vertexNormals.length; k++){
    const n = g.vertexNormals[k];
    nArray[3*k] = n.x;
    nArray[3*k+1] = n.y;
    nArray[3*k+2] = n.z;
  }

  let result = "# vertice data\n";
  for(let k=0; k<g.vertices.length; k++){
    result += `v ${vArray[3*k]} ${vArray[3*k+1]} ${vArray[3*k+2]} \n`;
  }
result += "# normal data\n";
  for(let k=0; k<g.vertexNormals.length; k++){
    result += `vn ${nArray[3*k]} ${nArray[3*k+1]} ${nArray[3*k+2]} \n`;
  }
  result += "# faces data\n";
  for(let k=0; k<g.faces.length; k++){
    const f = g.faces[k];
    result += `f ${1+f[0]}//${1+f[0]} ${1+f[1]}//${1+f[1]} ${1+f[2]}//${1+f[2]} \n`;
  }
  return result;
}

glsl内部ではFloat32Arrayでデータが処理されるので、同じ長さのFloat32Arrayを作り、そこに頂点と法線のデータを放り込んでいます。あとはobjファイルの形式にのっとって、
 v v.x v.y v.z
 vn n.x n.y n.z
などとします。改行で切り替えます。#でコメントを書けます。 f については、さっきのサイトにあるように、
 f a//a b//b c//c
などとしますが、1ベースなので1を足さないといけません。そこだけ注意です。ほんとはuvとかも設定できたりします。自分で調べてください。

objファイルとして出力する。

 最初はsaveStringsを使ってtextで出力してから拡張子を書き換えようと思っていたんですが、改行の仕様が意味不明で失敗したのでやめました。代わりに自作メソッドを使っています。

// postFixを"obj"にするとobjファイルとして保存される
function saveTextData(data, name, postFix = "txt"){
  const fileName = name.concat(".").concat(postFix);

  // HTMLのリンク要素を生成する。
  const link = document.createElement("a");

  // リンク先にJSON形式の文字列データを置いておく。
  link.href = "data:text/plain," + encodeURIComponent(data);

  // 保存するJSONファイルの名前をリンクに設定する。
  link.download = fileName;

  // ファイルを保存する。
  link.click();

  link.remove();
}

こちらを参考にしました。jsonですが、まあ何でもありです。
JavaScriptでJSONファイルを保存する/開く方法
第一引数にさっき作ったテキスト、次にファイル名、最後に"obj"を指定します。ここが"txt"のままだとテキストファイルとして出力されます。

実行結果

swfetfef343433.png
ひっくりかえっているのはp5.jsではy軸を下向きに設定しているからです。面の向きに矛盾は無いので大丈夫です。気になる人は送る前にrotateすればいいです。
これをBlenderに落とせば加工したりできます。
wrg4efreg4444.png

おわりに

 ここまでお読みいただいてありがとうございました。

追記(saveObj)

 p5.jsにはsaveObjという関数があり、これをp5.Geometryに使うとobjファイルを吐きます。
 saveObj
こっちのやり方との相違点。まずFloat32化はしていないようです。おそらくjsで使われている64bitの値そのままですね。次にコメントは一切なく延々とvやvnについての記述が並びます。この例のようにv,vn,fからなる構成の場合、 f の記述の仕方はこっちと同じです。そのくらいですね。こっちのやり方の場合は#でコメントを付記できます。差別化点はそのくらいです。またsaveObj()はもちろんUVにも対応しています。

この記事の価値

 そういうわけでこの記事は価値を失ってしまいました。saveObj()を使えばいいわけですから。ただsaveObj()は1.10.0で実装された関数です。実装されていない場合、この記事のようなやり方でやるしかありません。またobjファイルの内容について知っていること。それは無駄にならないはずです。保存の仕方についても、です。

 以上です。

蛇足🐍

 コメントくらいしか差別化点が無いので、どうせならと頂点と面の個数を追記できるように修正しました。

function createObjFile(g){
  const vArray = new Float32Array(g.vertices.length*3);
  const nArray = new Float32Array(g.vertexNormals.length*3);
  for(let k=0; k<g.vertices.length; k++){
    const v = g.vertices[k];
    vArray[3*k] = v.x;
    vArray[3*k+1] = v.y;
    vArray[3*k+2] = v.z;
  }
  for(let k=0; k<g.vertexNormals.length; k++){
    const n = g.vertexNormals[k];
    nArray[3*k] = n.x;
    nArray[3*k+1] = n.y;
    nArray[3*k+2] = n.z;
  }

  // ここ。
  let result = `# vertice data (${g.vertices.length} vertices)\n`;

  for(let k=0; k<g.vertices.length; k++){
    result += `v ${vArray[3*k]} ${vArray[3*k+1]} ${vArray[3*k+2]} \n`;
  }
result += "# normal data\n";
  for(let k=0; k<g.vertexNormals.length; k++){
    result += `vn ${nArray[3*k]} ${nArray[3*k+1]} ${nArray[3*k+2]} \n`;
  }

  // こっちも
  result += `# faces data (${g.faces.length} faces)\n`;

  for(let k=0; k<g.faces.length; k++){
    const f = g.faces[k];
    result += `f ${1+f[0]}//${1+f[0]} ${1+f[1]}//${1+f[1]} ${1+f[2]}//${1+f[2]} \n`;
  }
  return result;
}

カスタマイズは楽しいものです。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?