появился небольшой вопрос - правда не совсем по теме статьи. если появляется два контакта - их надо разрешать последовательно или нужно как то привести к одной точке и уже к ней применять усредненный импульс? а то у меня при столкновении грань-грань квадрат поворачивается немного и зависатет в повернутом положении
все пляски именно для того, чтобы получить больше одной одной точки контакта, но не слишком много. этот алгоритм генерирует в двумерном случае две точки и это - оптимальный вариант. в трёхмере всё сложнее.
как их разрешать - отдельный очень непростой вопрос, но эту тему написана ещё целая серия статей. и моя тоже есть: http://www.gamedev.ru/code/articles/?id=4706
если вкратце, то решать контакты нужно не по отдельности, а одновременно. не как отдельные уравнения, а как систему уравнений. но если решать контакты просто по очереди, то это будет математически эквивалентно решению системы уравнений итеративным методом projected gauss-seidel solver
я как раз по этой статье пишу. пока глючит. буду копать дальше
а как найти самою дальнюю точку ?
раздел "support mapping" в этой статье http://www.gamedev.ru/community/gd_physcomm/articles/?id=18 . "самая дальняя точка в направлении dir" и есть support map(dir)
Добрый день. Разбирался с алгоритмом год назад, не закончил, появилась необходимость закончить). хотел спросить, правильно ли я все понимаю. Буду благодарен за помощь.
алгоритм понимаю так (прошу прощения за код, но так проще показать то что я понял):
SMap(v) геометрии A - это поиск точки многранника A, скалярное произведение которой на вектор v максимально:
public AGVector2D getSupport(AGVector2D normal){ //поворачиваем вершину AGVector2D rotatedVec = _vertices[0].rotate(_orientation); //задаем начальное значение double max = normal.multiply(new AGVector2D(rotatedVec.getX()+_position.getX(), rotatedVec.getY()+_position.getY())); int index=0; //ищем по циклу максимальное for (int i=1;i<_size/2;i++) { //также поворачиваем rotatedVec = _vertices[i].rotate(_orientation); //и ищем произведение входящего вектора на точку double tmp = normal.multiply(new AGVector2D(rotatedVec.getX()+_position.getX(), rotatedVec.getY()+_position.getY())); if(max<tmp) { max=tmp; index = i; } } _last_index = index; rotatedVec = _vertices[index].rotate(_orientation); //возвращаем найденную точку return new AGVector2D(rotatedVec.getX()+_position.getX(), rotatedVec.getY()+_position.getY()); }
поиск саппорта в CSV
public AGVector2D getSupport(AGObject2D b, AGVector2D direction){ //возвращаем по фформуле из статьи return this.getSupport(direction).minus(b.getSupport(direction.minus())); }
и первая часть алгоритма - поиск PD
protected boolean findPD(AGObject2D inObject){ //инициализируем pd AGVector2D pd; //если сохранили значение с предыдущей итерации - кладе его if(direction.getLength()>0) pd = new AGVector2D(direction); //иначе - вектор разности центров объектов else pd = (inObject.getPosition().minus(_position)).normalize(); AGVector2D v = new AGVector2D(pd); double angEps = 1e-10f; double eps = 0.001f; int break1 =0; double OAlengthOld = 10000; //начинаем основной цикл while(true){ //инициалзируем v v.setPosition(pd); int break2 =0; //решаем вспомогательную задачу из пункта 2 while(true){ //ищем точку S в направлении v геометрии CSO AGVector2D S = this.getSupport(inObject, v); //ищем точку пересечения прямой проходящей через S и вектором pd //здесь не очень понял по поводу "касательной плоскости" - решил взять прямую, перпендикулярную v и проходящую через S //findCross - ищет точку пересечения, calcCoefViaNormal - ищет коеф-ты прямой через нормаль и точку. //calcCoefViaCenter - ищет прямую через 0 в направлении pd AGVector2D A = _geom.findCross(_geom.calcCoefViaNormal(v, S), _geom.calcCoefViaCenter(pd)); //если OA приитовоположнонаправлен pd - выходим из алгоритма if(!A.isSameDirection(pd)) return false; //если длина SA меньше eps выходим if(A.minus(S).getLength()<=eps || break2>5) break; //иначе кладем в v сумму v и нормализованного SA else v.setPosition(v.plus((A.minus(S).multiply(eps)).normalize())); //здесь проверяем, укорачивается OA или нет if(A.getLength()>=OAlengthOld)break2++; else{ break2 = 0; OAlengthOld = A.getLength(); } } //если угол между pd и v меньше eps то выходим и переходим в следующую часть алгоритма - поиск контактов if(pd.getAngle(v)<angEps || break1>5) break; //иначе кладем в pd значение v else pd.setPosition(v); break1++; } //сохраняем текущее значение pd direction.setPosition(pd.normalize()); return true; }
прошу прощения за простыню кода, долго бьюсь, ника не могу ошибку найти
fysx
в чём именно проблема? на каком этапе твоя реализация работает неверно? при отладке таких алгоритмов принципиально важно иметь удобный тестовый стенд, где бы как-нибудь наглядно визуализировались все PD векторы, support point'ы и контакты. кинь пару скриншотов что ли.
Пока проверяю первый этап (поиск pd вектора). Разбирал код, кой чего поправил. У меня были сомнения по поводу выбора прямой, проходящей через саппорт-поинт. Также нашел что ошибся в поиске пересечения прямых (точка А), опечатся в клэффициенте. Тесты нарисовал, скриншоты приложил.
испрвленный цикл
if(direction.getLength( )>0) pd = new AGVector2D( direction); else pd = ( inObject.getPosition( ).minus( _position)).normalize( ); AGVector2D v = new AGVector2D( pd); double angEps = 0.0001; double eps = 0.001f; int break1 =0; double OAlengthOld = 10000; while( true){ v.setPosition( pd); int break2 =0; while( true){ //ищем точку S в направлении v геометрии CSO AGVector2D S = this.getSupport( inObject, v); AGVector2D A = _geom.findCross( _geom.calcCoefViaNormal( v, S), _geom.calcCoefViaCenter( pd)); if( !A.isSameDirection( pd)){ direction.setPosition( v); return false; } if( A.minus( S).getLength( )<=eps || break2>3) break; else v.setPosition( v.plus( A.minus( S).multiply( eps)).normalize( )); if( A.getLength( )>=OAlengthOld)break2++; else{ break2 = 0; OAlengthOld = A.getLength( ); } } if( Math.abs( pd.getAngle( v))<angEps || break1>5) break; else pd.setPosition( v); break1++; }
красная линия - касательная к саппорт поинту, перпендикулярная к pd
зеленая линия - pd
синяя линия - прямая через саппорт и 0
белая - проходит через (1, 1) и A (пересечение касательной и pd)
синим квадратом отмечена найденная точка пересечения.
сейчас вроде нормально находит, но реакции пока нет. разбираюсь
синяя хреновина - разница минковского? она должна быть выпуклой. попробуй просто взять точки окружности(равномерное распределение по углу) и взять support point последовательно в направлении каждой. отрисуй получившуюся ломаную.
Да, она самая. она по идее выпуклая, просто точки не упорядочены, поэтому она еправильной формы, но работает нормально. Поясни пжлст по поводу точек окружности - не понял
короче псевдокод, как я отображаю cso для дебага:
int segmentsCount = 100; Vector2 prevDir(1, 0, 0); for( int i = 1; i < segmentsCount; i++) { Vector2 currDir( cosf( i / segmentsCount * 2.0f * pi), sinf( i / segmentsCount * 2.0f * pi)); renderer->RenderLine( shape->GetSupportPoint( prevDir), shape->GetSupportPoint( currDir)); prevDir = currDir; }
короче это жуткий псевдокод, но примерно так можно рендерить вообще любой support map - хоть cso, хоть просто тела.
Точка в данный момент вроде бы находится, но как то странно передается в ContactManifold, а если точнее передается не то. возможно накосячил в функции проверки принадлежности точки отрезку, буду проверять
существенная особенность всех подобных алгоритмов - они очень графичные. если ты понимаешь, что должно получиться, то по дебажной отрисовке обычно легко самостоятельно найти, в чём баг. ты, вроде, понимаешь, что должно получиться, так что дерзай.
CSO строится, кажется, примерно правильно. можешь запостить свою функцию проекции точки на отрезок(проверки принадлежности точки отрезку), если считаешь, что она работает плохо.
Да вот по мере написания постов сам вникаю в ошибки, разбираю.
функция принадлежности
public boolean checkPointInSegment(AGVector2D a, AGVector2D b, AGVector2D x){ double EPS = 1e-3f; double coef[] = calcCoef( a, b); //if(coef[0]*x.getX()+coef[1]*x.getY()+coef[2]>EPS) return false; double p=-1; if( a.getX( ) - b.getX( )!=0) p = ( x.getX( ) - b.getX( ))/( a.getX( ) - b.getX( )); else if( a.getY( ) - b.getY( )!=0) p = ( x.getY( ) - b.getY( ))/( a.getY( ) - b.getY( )); if( p>=0 && p<=1) return true; else return false; } public double[] calcCoef( AGVector2D v1, AGVector2D v2){ double x1, y1, x2, y2, dx, dy, A, B, C; x1 = v1.getX( ); y1 = v1.getY( ); x2 = v2.getX( ); y2 = v2.getY( ); dx = x2 - x1; dy = y2 - y1; if( dx == 0.0f){ A = 1; B = 0; C = -x1; } else if( dy == 0.0f){ A = 0; if( x2>x1) {B = -1; C = y1;} else {B = 1; C = y2;} } else { A = dy / dx; if( y2>y1){ B = -1; C = -A * x1 + y1;} else{ B = 1; C = -A * x2 + y2;} } /*A = y1-y2; B = x2-x1; C = x1*y2 - x2*y1;*/ return new double[] {A, B, C}; }
Тема в архиве.