トップページ > 過去ログ > 記事閲覧
VCosについて
名前:thanmz 日時: 2011/06/23 16:21

あるVECTOR a, bについて float x = VCos( a, b ) はちゃんと求まっていて、続いて acos( x ) をとると -1.#IND00 となってしまいます。 xが範囲外なのかと思って if ( fabs( x ) > 0.999999999F ) { x *= 0.99999F ; } float rad = acos( x ) ; などとしたらエラーは出なくなりました。 VCos( a, b ) が正常にでれば必ず[ -1, 1 ]だと 思うのですが…。 もちろん他の部分のコードに原因があるかもしれませんが、この部分だけでこの様な事が起こる原因は何かあるのでしょうか?

Page: 1 |

Re: VCosについて ( No.1 )
名前:いっち 日時:2011/06/23 20:39

> もちろん他の部分のコードに原因があるかもしれませんが、この部分だけでこの様な事が起こる原因は何かあるのでしょうか? 思い当たりません。 事象発生時の a と b の値を教えて下さい。
Re: VCosについて ( No.2 )
名前:thanmz 日時:2011/06/23 23:42

a, b が同じベクトルになることがある計算をしているのですが、調べてみたところ例えば a, b ともに ( 1.0f, 1.0f, 1.0f )だとerrorとなり、 a, b ともに ( 1, 1, 1 ) だと acos( VCos( a, b ) ) = 0.000345 となりました。 浮動小数点の扱いが出来ていないということでしょうか?
Re: VCosについて ( No.3 )
名前:いっち 日時:2011/06/24 00:03

私の環境では再現しません。 thanmzさんがご使用の環境とDXライブラリのバージョンを教えてください。 //- 以下、テストコード -// #include <cmath> #include "DxLib.h" int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) { ChangeWindowMode( TRUE ); SetWindowText( "DxLib:" DXLIB_VERSION_STR ); if ( DxLib_Init( ) == -1 ) return -1; int white = GetColor( 255, 255, 255 ); SetDrawScreen( DX_SCREEN_BACK ); while ( ProcessMessage( ) == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0 ) { ClearDrawScreen( ); DrawFormatString( 0, 0, white, "%f", acos( VCos( VGet( 1, 1, 1 ), VGet( 1, 1, 1) ) ) ); DrawFormatString( 0, 20, white, "%f", acos( VCos( VGet( 1.0f, 1.0f, 1.0f ), VGet( 1.0f,1.0f,1.0f) ) ) ); ScreenFlip( ); } DxLib_End( ); return 0; }
Re: VCosについて ( No.4 )
名前:thanmz 日時:2011/06/24 14:59

コメントありがとうございます。 ver3.05です。 環境は visual studio 2010 express edition です。 改めて計算してみましたら、 a, b = ( 1.0f, 1.0f, 1.0f ) ではエラーは出ませんでした。 a, b = ( -33.163461F, 7975.794922F, -20.918570F ) は実際に処理で求められた数値で、これだと #ind00になりましたが、どうでしょうか?
Re: VCosについて ( No.5 )
名前:いっち 日時:2011/06/24 19:54

情報ありがとうございます。事象確認できました。 VCos の結果が微妙に範囲を超えているようです。 VCos の中でベクトルの正規化が行われるのですが、 そのときのベクトルの大きさの計算精度が問題となって正規化しきれていないのではないかと思います。 こういった場合、どのように対応するのが良いのかちょっとわかりません。 thanmzさんが行ったように VCos の値を監視してそのつど補正するというのも1つの対策ですが、 とりあえずは管理人さんの見解をお待ちするのが良いと思います。
Re: VCosについて ( No.6 )
名前:Sura 日時:2011/06/25 01:49

横やり失礼します。 単に数学の話ですが、 VCos は a と b の内積を計算した後、|a||b|で割った値(即ち a と b のなす角度の cos 値)なので、 >a, b が同じベクトルになることがある計算をしているのですが、 における確認において、 a, bの内積:(a, b) と、a, bの距離:|a|, |b| の関係は必ず {|a|*|b| >= abs((a, b))} イコールのときに角度が0°または180° なので、acos() を使わずに、 if(|a|*|b| - abs((a, b)) < epsilon) のような計算をすれば、エラーは出ないと思います。
Re: VCosについて ( No.7 )
名前:いっち 日時:2011/06/25 12:27

Suraさん> > ような計算をすれば、エラーは出ないと思います。 計算量の問題は別にして、VCos の計算結果は現在でも全く意味不明な値が返ってきているわけではないので、 誤差を想定した比較を行えば、2つのベクトルがなす角が0度か180度(の近似)かを知ることはできます。 また、thanmzさんは結果として2つのベクトルがなす角の角度を得たいのだと思います。
Re: VCosについて ( No.8 )
名前:thanmz 日時:2011/06/25 13:36

コメントありがとうございます。 最終的に求めたいのは MATRIX m = MGetRotVec2( a, b ) ; です。 そこで MGetRotVec2の定義を調べてみたら acos() が原因ではないかと考えたのでした。
Re: VCosについて ( No.9 )
名前:Sura 日時:2011/06/25 18:57

回転行列内の要素は、 角度のcos値、sin値及びその積の組み合わせのはずですから、 やはり、角度自体を求める必要はないと思います。 デバッグやプロジェクトの仕様から、求める必要があれば、 a, b の外積からsin値を求め、atan2()を使えば良いと思います。 p.s. 参考までに、 a(a1, a2, a3), b(b1, b2, b3) としてその外積 a × b は a × b = (a2 * b3 - a3 * b2, a3 * b1 - a1 * b3, a1 * b2 - a2 * b1) sinθは sinθ = |a × b|/(|a| * |b|)
Re: VCosについて ( No.10 )
名前:いっち 日時:2011/06/25 20:54

やってみましたが、確かに精度の問題も出ない(出にくい?)ようでした。 (精度の問題じゃないのかも?未検証です。すみません) //- 以下、テストコード -// #include <cmath> #include "DxLib.h" int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) { ChangeWindowMode( TRUE ); SetWindowText( "DxLib:" DXLIB_VERSION_STR ); if ( DxLib_Init( ) == -1 ) return -1; int white = GetColor( 255, 255, 255 ); VECTOR p[2] = { {-33.163461F, 7975.794922F, -20.918570F}, {-33.163461F, 7975.794922F, -20.918570F} }; float v_cos = VCos( p[0], p[1] ); float v_rad = VRad( p[0], p[1] ); float p_size[2] = { VSize( p[0] ), VSize( p[1] ) }; float p_dot = VDot( p[0], p[1] ); float p_cos = p_dot / (p_size[0] * p_size[1]); VECTOR p_cross = VCross( p[0], p[1] ); float p_cross_size = VSize( p_cross ); float p_sin = p_cross_size / (p_size[0] * p_size[1]); SetDrawScreen( DX_SCREEN_BACK ); while ( ProcessMessage( ) == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0 ) { ClearDrawScreen( ); DrawFormatString( 0, 0, white, "p1 = %f, %f, %f, size = %.7f", p[0].x, p[0].y, p[0].z, p_size[0] ); DrawFormatString( 0, 20, white, "p2 = %f, %f, %f, size = %.7f", p[1].x, p[1].y, p[1].z, p_size[1] ); DrawFormatString( 0, 40, white, "VCos = %.7f, VRad = %.7f", v_cos, v_rad ); DrawFormatString( 0, 60, white, "dot_cos = %.7f, cross_sin = %.7f", p_cos, p_sin ); DrawFormatString( 0, 80, white, "acos = %.7f, asin = %.7f, atan = %.7f", acos( p_cos ), asin( p_sin ), atan2( p_sin, p_cos ) ); ScreenFlip( ); } DxLib_End( ); return 0; }
Re: VCosについて ( No.11 )
名前:Sura 日時:2011/06/26 00:52

いっち さん 横槍の立場でありながら、丁寧な検証ありがとうございます。 確かに、thanmz さん の言われるように、 VRad() は、定義域を超えた引数が直接の原因のように思われます。 私の憶測ですが、VCos() に関しては、何らかの最適化によるものかもしれません。 が、VRad() を求める際には、定義域の制約が厳しいacos() を使うより、 多少の処理の負荷があれど VSize( VCross() ) と VDot() による atan2() を求めたほうが、例外を出さずに良いと思います。 角度自体を求める必要性は、あまり無いように思われますので、 計算負荷を優先する必要が、無いと思います。 何れにせよ、管理人様の判断に任せたいと思います。
Re: VCosについて ( No.12 )
名前:管理人 日時:2011/06/26 16:54

すいません、計算精度の関係で VCos の戻り値が 1.0f 以上や -1.0f 以下になってしまうことがあったようです ( 1.0f 以上や -1.0f 以下の値を acos に渡すと #ind00 が返ってきます ) 1.0f 以上や -1.0f 以下だった場合はクリップするように修正したバージョンを アップしましたので、よろしければお試しになってみてください m(_ _)m http://homepage2.nifty.com/natupaji/DxLib/DxLibVCTest.exe // VisualC++ 用 http://homepage2.nifty.com/natupaji/DxLib/DxLibBCCTest.exe // BorlandC++ 用 http://homepage2.nifty.com/natupaji/DxLib/DxLibGCCTest.exe // Gnu C++ 用 (中身を既存のライブラリのファイルに上書きして、BCCをお使いの 場合は『再構築』を、VCをお使いの場合は『リビルド』を、 Dev-C++をお使いの方は「Rebuild All(Ctrl+F11)」をして下さい)
Re: VCosについて ( No.13 )
名前:thanmz ( 解決 ) 日時:2011/06/27 15:16

>> いっち様 >> Sura様 >> 管理人様 貴重なご意見ありがとうございました! とても参考になりました! これにて解決とさせて頂きます!
Re: VCosについて ( No.14 )
名前:いっち 日時:2011/08/04 21:10

今更な補足ですが、レス(No.10)のような方法で、サイン、コサインの値を求めた場合でも、 やはり精度の問題で -1 〜 1 を超えることがあるようでした。 2つのベクトルから角度を求めたい場合は、やはり管理人さんがなさったように、 サイン、コサインの値を監視して補正するのが良いようです。

Page: 1 |