.NET2003(C++)とDirectX9SDKで開発しています。
以下のように点と点の距離を求める関数を作ったのですが、最初勘違いして配列のように[]でアクセスしていたのですが、その後間違いに気づいて.xや.zのようなアクセスに直しました。
ところがバグの影響を調べるために色々試してみると、前者の[]のアクセス方法でも正しく動いているのを発見しました。
個人的には[]では全然違う場所(メモリ)を参照するため絶対に駄目そうに思うのですが、なぜ上手く動くのでしょうか?
それとも私が何か初歩的な勘違いをしているのでしょうか?
もしくはD3DXVECTOR3の宣言に秘密があるのでしょうか?一応[]のオーバーロードとかはしてないようなのですが…
float getXZLength( D3DXVECTOR3 &pos0, D3DXVECTOR3 &pos1 )
{
float xLen,zLen;
//正しい
xLen = pos0.x - pos1.x;
zLen = pos0.z - pos1.z;
//間違い
// xLen = pos0[0] - pos1[0];
// zLen = pos0[2] - pos1[2];
return sqrtf( xLen * xLen + zLen * zLen );
}
ちなみにD3DXVECTOR3の宣言はこれです。
typedef struct D3DXVECTOR3 : public D3DVECTOR
{
public:
D3DXVECTOR3() {};
D3DXVECTOR3( CONST FLOAT * );
D3DXVECTOR3( CONST D3DVECTOR& );
D3DXVECTOR3( CONST D3DXFLOAT16 * );
D3DXVECTOR3( FLOAT x, FLOAT y, FLOAT z );
// casting
operator FLOAT* ();
operator CONST FLOAT* () const;
// assignment operators
D3DXVECTOR3& operator += ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator -= ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator *= ( FLOAT );
D3DXVECTOR3& operator /= ( FLOAT );
// unary operators
D3DXVECTOR3 operator + () const;
D3DXVECTOR3 operator - () const;
// binary operators
D3DXVECTOR3 operator + ( CONST D3DXVECTOR3& ) const;
D3DXVECTOR3 operator - ( CONST D3DXVECTOR3& ) const;
D3DXVECTOR3 operator * ( FLOAT ) const;
D3DXVECTOR3 operator / ( FLOAT ) const;
friend D3DXVECTOR3 operator * ( FLOAT, CONST struct D3DXVECTOR3& );
BOOL operator == ( CONST D3DXVECTOR3& ) const;
BOOL operator != ( CONST D3DXVECTOR3& ) const;
} D3DXVECTOR3, *LPD3DXVECTOR3;
D3DXVECTOR3::operator FLOAT * が起動して、
FLOAT配列として扱えるようなポインタが返されているんだと思います。
なるほど!
配列の[]アクセスを直接オーバーロードするのでは無く、ポインターへと成り下がった方を間接的にオーバーロードするのですね。そんな方法は全く思いつきませんでした^^;
ちなみにもし宜しければ、それを実現するために具体的にどういったコードを書けばよいのか教えてもらえますでしょうか?たぶん
returh (float*)this;
のような感じだと思うのですが、自信がありませんで…
DirectXの方はライブラリになっていて上記のヘッターしかありませんので、お願いできると助かりますm(__)m
多分そんな感じになってると思います。
ですが、Cスタイルのキャストではconst性とかを無視した強制的なキャストができてしまうので、
C++のキャストを使うのがベストでしょう。
operator FLOAT * にconst版と非const版があるのは、
ここら辺に厳密な注意を払っているからだと思われます。
operator FLOAT * () {return reinterpret_cast<FLOAT *>(this);}
operator CONST FLOAT * () const {return reinterpret_cast<const FLOAT *>(this);}
なお、こういったテクは仮想関数を持つクラスではうまくいかないと思います。
仮想関数ポインタが追加されちゃうので。
thisをfloat*にごり押しcastっすか、うーむ…
struct point3D {
float x,y,z;
operator float*() { return &x; }
operator const float*() const { return &x; }
...
};
むしろこんな感じではないかと。
# x,y,zの間に"詰め物"がないなら。
VC6に最初から入ってたヘッダー(多分DirectX5SDK)で継承元D3DVECTORを追ってみたら、以下のようになってました。
#D3DXVECTOR3はこのバージョンではまだできてないのか、見つかりませんでした。
(D3DTYPES.Hから抜粋)
typedef float D3DVALUE, *LPD3DVALUE;
typedef struct _D3DVECTOR {
union {
D3DVALUE x;
D3DVALUE dvX;
};
union {
D3DVALUE y;
D3DVALUE dvY;
};
union {
D3DVALUE z;
D3DVALUE dvZ;
};
#if (defined __cplusplus) && (defined D3D_OVERLOADS)
〜中略〜
const D3DVALUE&operator[](int i) const;
D3DVALUE&operator[](int i);
〜中略〜
#endif
} D3DVECTOR, *LPD3DVECTOR;
継承元が operator[]定義してました。
これが呼ばれていたのではないでしょうか?
すぐ下に中身もあったので見てみたら・・・
(D3DVEC.INLより抜粋)
inline const D3DVALUE&
_D3DVECTOR::operator[](int i) const
{
return (&x)[i];
}
とありました。パディングはない前提のようです。
あー、return &x; の方がよさそうですね。
あと、MSDNをみると最近のDirectXではoperator[]がなくなってるっぽいです。
(ヘッダは見てないのでちょっと自信ないですが。)
operator float *があれば、引数がfloat *の関数にも渡せたりするからかな?
皆さん、返信有難うございますm(__)m
今「解決」の投稿をする前に色々試していたところなのですが、取り急ぎ継承元のD3DVECTORの定義を投稿します。両方の定義ともDirectX9SDKのものです。
どうもDirectX8辺りで大幅に定義が変わったようですね。
typedef struct _D3DVECTOR {
float x;
float y;
float z;
} D3DVECTOR;
返信遅れましたが解決しました。皆さん有難うございました。
余談になりますが、仕事で複数の処理系の3Dプログラムをすることがあり、その時にベクトルが配列で定義されていたり、構造体で定義されていたりして非常に困っていました(OpenGLは配列、DirectXは構造体など)。
ゼロから書くアプリ独自の部分は良いのですが、どこでも使うような汎用的な算術関数などは、OpenGL用に書いた奴をDirectXに移植する場合、pos[0]をpos.x等に全て手で修正していましたので非常に苦労してました(もちろんエディターの置換を使った上での話ですが)。ちなみに今回のバグもそういう修正部分で修正を忘れていて発生しました。
それが今回のテクニックを使えば、pos[0]の形でソースを書いておけば、定義が配列・構造体のどちらであっても共通して使えますので、非常に助かります。まさにここ数年の間で最も画期的なテクニックの発見でした。皆さん有難うございます(知っている人は当たり前に使っているテクニックなのでしょうが、今までの仕事では残念ながら一度も出会えませんで…というかあと数年早くこのテクニックに出会えていれば相当楽が出来たのに^^;)。
一応関数にベクトルをポインタとして渡す部分の関数定義だけは書き直さないといけないようですが、それでもソース全体を書き直すのに比べれば大したことありませんので大助かりです。
皆さん本当に有難うございましたm(__)m