朝早くの投稿になります。
PMX(MMDモデルのファイル形式)のロードを行うプログラムをC++で作っている時に、どうしても解決できない箇所があったので質問させていただきました。
解決できない箇所とは、モデル情報の部分で、そのデータ変換に.NET Frameworkを使用しているらしい(仕様より)のですが、私の開発環境がubuntuのため.NETが使えず、更に、PMXはバイナリなのでどうすることも出来ずに詰まっている状況です。(Wineは使いたくありません)
(同様に、テクスチャパス、材質名、ボーン等、他のTextBufを使っている箇所も出来そうにありません。)
どうにか.NETを使わずにロードする方法はありませんか?
どんなに些細な情報でも結構です。なにか知っていることがあれば、ご教授願いたいです。
また、PMXを扱うにあたって気を付けなければならないことなどあればお願いします。
開発環境
Ubuntu13.10
g++ 4.8.1
以下PMXの仕様になります。
[C#/HLSL方式+] : バイトサイズ | [C方式]
byte : 1 - 符号なし | unsigned char
sbyte : 1 - 符号あり | char
ushort : 2 - 符号なし | unsigned short
short : 2 - 符号あり | short
uint : 4 - 符号なし | unsigned int
int : 4 - 符号あり | int (32bit固定)
float : 4 - 単精度実数 | float
float2 : 8 (4 * 2) | Vector2 - X,Y(or U,V) の順
float3 : 12 (4 * 3) | Vector3 - X,Y,Z(or R,G,B) の順
float4 : 16 (4 * 4) | Vector4 - X,Y,Z,W(or R,G,B,A) の順
TextBuf : 可変 | 4 + バッファ長
bitFlag : 1 | 1byte につき 8フラグ bit-0:OFF 1:ON
○TextBuf (テキストバッファ)
int : バイト長
byte * バイト長 : byte列 エンコード形式はPMXヘッダに記載
※バッファサイズは 4 + n [byte]
※エディタ内のエンコード処理は、
→バイト列
byte[] buf = Encoding.Unicode.GetBytes(str); // UTF16
byte[] buf = Encoding.UTF8.GetBytes(str); // UTF8
→文字列
string str = Encoding.Unicode.GetString(buf, 0, buf.Length); // UTF16
string str = Encoding.UTF8.GetString(buf, 0, buf.Length); // UTF8
これだけです。
変換はすべてライブラリ(.NET Framework)にまかせているので、内部で発生する問題などには対応できないかと思われます。
※サロゲートペアの取り扱いについてなど詳細不明
PMX他環境でエンコードされたファイルのデコードについて
Re: PMX他環境でエンコードされたファイルのデコードについて
WindowsならShift_JISに変換した方が扱いやすく感じるかもしれないですが、
Ubuntu13.10ならわざわざ変換しなくても、UTF-8のまま文字列を扱えばいいと思います。
ただし、エンコードがUnicode(UTF-16)の場合もある(どっちかははヘッダでわかる)が、それは規則に従って自分でUTF-8に変換すればいいはずです。
ここが参考になります。UTF-8 - Wikipedia
Ubuntu13.10ならわざわざ変換しなくても、UTF-8のまま文字列を扱えばいいと思います。
ただし、エンコードがUnicode(UTF-16)の場合もある(どっちかははヘッダでわかる)が、それは規則に従って自分でUTF-8に変換すればいいはずです。
ここが参考になります。UTF-8 - Wikipedia
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: PMX他環境でエンコードされたファイルのデコードについて
返信ありがとうございますみけCAT さんが書きました:WindowsならShift_JISに変換した方が扱いやすく感じるかもしれないですが、
Ubuntu13.10ならわざわざ変換しなくても、UTF-8のまま文字列を扱えばいいと思います。
ただし、エンコードがUnicode(UTF-16)の場合もある(どっちかははヘッダでわかる)が、それは規則に従って自分でUTF-8に変換すればいいはずです。
ここが参考になります。UTF-8 - Wikipedia
文字列が何byteかはどこでわかるのでしょうか?
unsigned char[]で適当に14個くらい取ってきたのですが、一文字且つ文字化けをしてしまっている状況です
Re: PMX他環境でエンコードされたファイルのデコードについて
自分で「int : バイト長」と書いているので、そこのデータでわかるはずです。Saigo さんが書きました:文字列が何byteかはどこでわかるのでしょうか?
unsigned char[]で適当に14個くらい取ってきたのですが、一文字且つ文字化けをしてしまっている状況です
モデルを1個バイナリエディタで見てみましたが、リトルエンディアンのようですね。
C++なら、必要なサイズの配列をnew 型名[要素数]で確保すればいいと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: PMX他環境でエンコードされたファイルのデコードについて
とりあえず、モデル情報までの読み込みを実装してみました。
#include <cstdio>
#include <string>
unsigned char read_byte(FILE* fp) {
int rb=getc(fp);
if(rb==EOF)throw (const char*)"unexpected end of file";
return rb;
}
unsigned short read_ushort(FILE* fp) {
int high,low;
low=read_byte(fp);
high=read_byte(fp);
return (high<<8)|low;
}
unsigned int read_uint(FILE* fp) {
unsigned int high,low;
low=read_ushort(fp);
high=read_ushort(fp);
return (high<<16)|low;
}
float read_float(FILE* fp) {
/* TODO : エンディアンの相違に対応 */
float buf=0.0f;
if(fread(&buf,sizeof(float),1,fp)!=1)throw (const char*)"unexpected end of file";
return buf;
}
void read_char_array(unsigned char* out,FILE* fp,int len) {
for(int i=0;i<len;i++)out[i]=read_byte(fp);
}
std::string utf16_to_utf8_string(const unsigned char* str) {
int i;
unsigned int next_number;
std::string ret="";
for(i=0;(next_number=str[i]|(str[i+1]<<8))!=0;i+=2) {
unsigned char buffer[8]={};
if(next_number<=0x7f) {
buffer[0]=next_number;
} else if(next_number<=0x7ff) {
buffer[0]=0xc0 | ((next_number>>6)&0x1f);
buffer[1]=0x80 | (next_number&0x3f);
} else { // 仕様よりnext_number<=0xffff
buffer[0]=0xe0 | ((next_number>>12)&0x1f);
buffer[1]=0x80 | ((next_number>>6)&0x3f);
buffer[2]=0x80 | (next_number&0x3f);
}
ret+=std::string((const char*)buffer);
}
return ret;
}
std::string read_text_buf(FILE* fp,unsigned char encode) {
unsigned int length;
unsigned char* buffer;
std::string res;
length=read_uint(fp);
buffer=new unsigned char[length+2];
read_char_array(buffer,fp,length);
buffer[length]=buffer[length+1]='\0';
switch(encode) {
case 0: // UTF16
res=utf16_to_utf8_string(buffer);
break;
case 1: // UTF8
res=std::string((const char*)buffer);
break;
default:
delete[] buffer;
throw (const char*)"unsupported encode";
}
delete[] buffer;
return res;
}
struct pmx_header {
float version;
unsigned char encode;
unsigned char add_uv;
unsigned char node_index_size;
unsigned char texture_index_size;
unsigned char material_index_size;
unsigned char bone_index_size;
unsigned char moofu_index_size;
unsigned char goutai_index_size;
};
struct pmx_model_info {
std::string name;
std::string name_english;
std::string comment;
std::string comment_english;
};
struct pmx_t {
pmx_header header;
pmx_model_info model_info;
};
bool float_is_equal(float a,float b) {
static const float EPS=1e-5;
return a-b<EPS && b-a<EPS;
}
pmx_header read_pmx_header(FILE* fp) {
pmx_header pmh;
unsigned char magic[4];
unsigned char next_size;
read_char_array(magic,fp,4);
if(magic[0]!=0x50 || magic[1]!=0x4d || magic[2]!=0x58 || magic[3]!=0x20) {
throw (const char*)"this is not a PMX file";
}
pmh.version=read_float(fp);
if(!float_is_equal(pmh.version,2.0f) && !float_is_equal(pmh.version,2.1f)) {
throw (const char*)"this version of PMX file is not supported";
}
next_size=read_byte(fp);
if(next_size!=8) {
throw (const char*)"invalid header size";
}
pmh.encode=read_byte(fp);
pmh.add_uv=read_byte(fp);
pmh.node_index_size=read_byte(fp);
pmh.texture_index_size=read_byte(fp);
pmh.material_index_size=read_byte(fp);
pmh.bone_index_size=read_byte(fp);
pmh.moofu_index_size=read_byte(fp);
pmh.goutai_index_size=read_byte(fp);
return pmh;
}
pmx_model_info read_pmx_model_info(FILE* fp,const pmx_header& header) {
pmx_model_info pmi;
pmi.name=read_text_buf(fp,header.encode);
pmi.name_english=read_text_buf(fp,header.encode);
pmi.comment=read_text_buf(fp,header.encode);
pmi.comment_english=read_text_buf(fp,header.encode);
return pmi;
}
pmx_t read_pmx(FILE* fp) {
pmx_t pmx;
pmx.header=read_pmx_header(fp);
pmx.model_info=read_pmx_model_info(fp,pmx.header);
return pmx;
}
int main(int argc,char* argv[]) {
FILE* fp;
pmx_t pmx;
if(argc<2) {
fprintf(stderr,"Usage: %s <model file>\n",argv[0]);
return 1;
}
fp=fopen(argv[1],"rb");
if(fp==NULL) {
fprintf(stderr,"failed to open \"%s\"\n",argv[1]);
return 1;
}
try {
// PMXファイルを読み込む
pmx=read_pmx(fp);
// 読み込んだ情報を出力する
printf("version = %f\n",pmx.header.version);
printf("name = %s\n",pmx.model_info.name.c_str());
printf("name (eng) = %s\n",pmx.model_info.name_english.c_str());
printf("---------- comment ----------\n%s\n",pmx.model_info.comment.c_str());
printf("---------- comment (eng) ----------\n%s\n",pmx.model_info.comment_english.c_str());
} catch(const char* e) {
fprintf(stderr,"ERROR: %s\n",e);
}
fclose(fp);
return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: PMX他環境でエンコードされたファイルのデコードについて
反応おくれてすいません(部活をやっているので...)みけCAT さんが書きました:とりあえず、モデル情報までの読み込みを実装してみました。
コードまで書いていただきありがとうございます。
少し読ませて頂きます。
Re: PMX他環境でエンコードされたファイルのデコードについて
なんとなくですが理解出来ました。みけCAT さんが書きました:とりあえず、モデル情報までの読み込みを実装してみました。
自宅に帰って実装してみたいと思います。
本当にありがとうございました。