Различать ориентацию поверхности столкновения в box2d

Я работал над проектом iOS, используя Cocos2D 1.0 и Box2D, и столкнулся с небольшой проблемой.

Что мне нужно сделать, так это определить ориентацию поверхности, на которую наткнулся мой игрок. Например, если у нас есть прямоугольная платформа, и игрок сталкивается с ней, мне нужно знать, ударился ли игрок о ее левую, правую, верхнюю или нижнюю грань. ВСЕ объекты в игре квадратные, а движется ЕДИНСТВЕННЫЙ игрок.

В настоящее время я использую b2ContactListener в Box2D (ну, во всяком случае, мой собственный подкласс одного из них) и играю с локальной нормалью коллектора из контакта в BeginContact. Основная проблема, с которой я столкнулся, заключается в том, что на эту нормаль, по-видимому, влияет вращение тела игрока (например, игрок повернулся на 90 градусов, ИЛИ игрок дико вращается при ударе - обе ситуации доставляют мне проблемы), и я, кажется, закончатся двусмысленностью (т. е. столкновениями с разными лицами, которые дают одну и ту же нормаль...), если я попытаюсь учесть это - хотя, конечно, я мог просто делать что-то ужасно неправильное. Я не очень хорошо разбираюсь в коллекторах, поэтому, возможно, моя проблема связана с этим, или, может быть, я упускаю что-то очевидное.

Какие-либо предложения? Я бы предпочел сделать это самым чистым и наименее уродливым способом. Имейте в виду, что основная категоризация, которая меня волнует, это «игрок приземляется на что-то сверху» против «все остальное», но мне может понадобиться точная информация. Если вам нужна дополнительная информация или разъяснение о чем-либо, просто спросите.

РЕДАКТИРОВАТЬ: Просто чтобы уточнить, я знаю, что нормальные точки от A до B (при столкновении между A и B) по соглашению в Box2D, и мой код действительно проверяет, какой из них является игроком, и принимает это во внимание, прежде чем делать любые расчеты, чтобы определить, какое лицо было поражено.


person Iskar Jarak    schedule 18.09.2011    source источник
comment
Нельзя просто посмотреть на положение игрока относительно точки контакта?   -  person iforce2d    schedule 18.09.2011
comment
Если у вас есть предложения относительно того, как обрабатывать а) две точки контакта (столкновения многоугольника с многоугольником могут и часто приводят к этому) и б) странные случаи, например, если игрок вращается и сталкивается с углом платформы , тогда да. Я все равно попытаюсь, но обращение с делом может стать немного грязным...   -  person Iskar Jarak    schedule 19.09.2011
comment
попробуйте посмотреть b2Manifold (у b2Contact это поле)   -  person Andrew    schedule 19.09.2011
comment
Я предположил, что использование либо контактного коллектора, либо мирового коллектора для получения точек контакта было очевидным, поскольку именно так я получаю нормальное...   -  person Iskar Jarak    schedule 19.09.2011
comment
Используйте другое приспособление для игрока (leftHalf, Righthalf) и используйте прослушиватель контактов, чтобы определить, какая часть игрока попала   -  person Guru    schedule 21.07.2012


Ответы (1)


Итак, я чувствую себя немного неловко, отвечая на свой вопрос, но, видимо, это официально поощряется.

Так или иначе, проблема с тем, как я подходил к вещам, была двоякой. Во-первых, я использовал локальную нормаль контактного коллектора вместо мировой нормали. Во-вторых, мой код для реверсирования преобразований объектов содержал ошибки (мне никогда не понадобилось бы это делать, если бы я использовал мировое многообразие).

Мировое многообразие учитывает преобразования и размеры объектов и, как таковые, содержит данные, более легко применимые к мировой системе координат.

По соглашению в Box2d нормаль столкновения (как для мирового многообразия, так и для контактного многообразия) указывает от A к B - это необходимо учитывать для некоторых применений, поскольку нормаль от A к B является обратной нормали от Б к А, так что вы не можете просто предположить, что одно тело всегда будет А.

Итак, решение состоит в том, чтобы использовать получение мирового многообразия для каждого столкновения, исследовать его нормали, а затем принимать любые решения, которые вы хотите принять.

Например, в методе BeginContact подкласса b2ContactListener (если вы не понимаете, о чем я говорю, посмотрите часть 2 этого учебник):

void ContactListener::BeginContact(b2Contact* contact)
{
    b2WorldManifold worldManifold;
    contact->GetWorldManifold(&worldManifold); // this method calls b2WorldManifold::Initialize with the appropriate transforms and radii so you don't have to worry about that
    b2Vec2 worldNormal = worldManifold.normal;
    // inspect it or do whatever you want based on that...  
}

Так как вам, вероятно, потребуется проверить, какие тела сталкиваются, и какое из них является A, а какое B, вы можете сохранить вектор структур, содержащих столкнувшиеся фикстуры (как в этом учебник) и нормаль и повторите вектор в вашем методе tick() или аналогичном. (Вы можете получить их из контакта с contact->GetFixtureA() и contact->GetFixtureB().)

Теперь вы можете получать точечные данные из мирового многообразия и принимать решения на их основе, но зачем вам это, когда нормаль уже доступна, ведь в этом конкретном case нормаль (в сочетании с которой формируют точки нормали от и до) — это все, что нужно.


Изменить (для @iBradApps):

Во-первых, я предполагаю, что вы следовали руководству, на которое я ссылался, и настроили прослушиватель контактов. Если нет, следуйте ему, потому что Рэй довольно подробно объясняет его.

Во-вторых, я хочу отметить, что нет абсолютной гарантии, какой объект является А, а какой Б (ну, это зависит от того, что это за объекты Box2D; достаточно сказать, что если они оба могут двигаться, вы не можете гарантировать, что порядок, по крайней мере, насколько я знаю), поэтому в моем случае я хотел увидеть, ударил ли объект игрока что-то, поэтому я создал переменную класса (b2Fixture *playerF) в своем прослушивателе контактов, в которой хранится ссылка на объект игрока, поэтому я мог определить, был ли игрок контактом A или контактом B.

Вы спрашивали об обнаружении столкновения, когда что-то еще столкнулось с вершиной B. Что-то вроде следующего должно работать, хотя у меня не было возможности проверить это для вас:

В вашем ContactListener.h:

public:
    b2Fixture *playerF;
    // along with the vector etc mentioned in Ray's tutorial
    // and anything else you want

Когда вы создаете ContactListener в своем init() (при условии, что вы назвали его _contactListener):

_contactListener->playerF = playerFixture; // or whatever you called the player body fixture

BeginContact метод:

void ContactListener::BeginContact(b2Contact* contact)
{
    b2WorldManifold worldManifold;
    contact->GetWorldManifold(&worldManifold); // this method calls b2WorldManifold::Initialize with the appropriate transforms and radii so you don't have to worry about that
    b2Vec2 worldNormal = worldManifold.normal; // this points from A to B

    if (playerF == contact->GetFixtureA()) {

        // note that +ve y-axis is "up" in Box2D but down in OpenGL and Cocos2D
        if (worldNormal.y < -0.707) { // use a constant for performance reasons

            // if the y component is less than -1/sqrt(2) (approximately -0.707),
            // then the normal points more downwards than across, so A must be hitting B 
            // from roughly above. You could tune this more towards the top by increasing 
            // towards -1 if you want but it worked fine for me like this last time and 
            // you might run into issues with missing hits


            NSLog(@"Player (A) hit B roughly on the top side!");

            // here you can set any class variables you want to check in 
            // your update()/tick(), such as flags for whether the player has died from
            // falling or whatever
    }

    } else if (playerF == contact->GetFixtureB()) {

        if (worldNormal.y > 0.707) {

            NSLog(@"Player (B) hit A roughly on the top side!");
        }

    } else {
    // it's something else hitting something else and we don't care about it
    }
}

Что касается того, чтобы сделать это в вашем методе tick(), то да, вы можете. На самом деле я делал все свои вещи в PostSolve в прослушивателе контактов, потому что мне нужно было знать, насколько сильно игрок ударил, но все, что меня заботило, кроме этого, было то, достаточно ли сильно игрок ударил, чтобы убить его, поэтому мне не нужно или не хотелось чтобы перебрать все контакты в моем tick() - я просто установил флаг в прослушивателе контактов, который сказал, что игрок пострадал от смертельного удара.

Если вы хотите сделать все это в методе обновления, то, начиная с того, что есть у Рэя, добавьте b2Vec2 в структуру MyContact, а в BeginContact добавьте два прибора (как это делает Рэй) и получите столкновение нормальное (как у меня) и добавить его тоже.

Модифицированная структура MyContact:

struct MyContact {
    b2Fixture *fixtureA;
    b2Fixture *fixtureB;
    b2Vec2 normal;
    bool operator==(const MyContact& other) const
    {
        return (fixtureA == other.fixtureA) && (fixtureB == other.fixtureB);
    }
};

Новый метод BeginContact:

void MyContactListener::BeginContact(b2Contact* contact) {
    b2WorldManifold wordManifold;
    contact->GetWorldManifold(&worldManifold);

    MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB(), worldManifold.normal };
    _contacts.push_back(myContact);
}

Это даст вам всю информацию, необходимую для проверки, которую я изначально описал в вашем tick().


Изменить еще раз: ваш метод tick() может содержать что-то подобное, если вы хотите выполнить обработку там, предполагая, что вы вызвали приспособление игрока (или приспособление мяча, как в учебнике, или что-то еще, что вы интересует) _playerFixture, что у вас есть прослушиватель контактов с тем же именем, что и в руководстве, что вы добавили нормаль b2Vec2 в структуру MyContact, что вы добавляете контакты в вектор (как указано выше) в BeginContact, и что вы удаляете контакты из вектора в EndContact (как показано в учебнике - это, вероятно, нормально):

std::vector<MyContact>::iterator pos;
for(pos = _contactListener->_contacts.begin(); pos != _contactListener->_contacts.end(); ++pos) {
    MyContact contact = *pos;
    if (_playerFixture == contact.fixtureA && contact.normal.y < -0.707) {
            NSLog(@"Player (A) hit B roughly on the top side!");
    } else if (_playerFixture == contact.fixtureB && contact.normal.y > 0.707) {
            NSLog(@"Player (B) hit A roughly on the top side!");
    } else {
    // it's something else hitting something else and we don't care about it
    }
}
person Iskar Jarak    schedule 19.09.2011
comment
Извините, что возвращаю этот старый вопрос, но мне нужно сделать то же самое. Если я вас не беспокою, не могли бы вы расширить код в этом ответе, чтобы определить, столкнулся ли «fixtureB» сверху? Буду очень благодарен! Также я проголосовал за вопрос и ответ: P - person SimplyKiwi; 19.07.2012
comment
@iBradApps Конечно, хотя я сейчас на работе, так что я отредактирую это сегодня вечером. - person Iskar Jarak; 20.07.2012
comment
Спасибо за ответ! У меня есть еще несколько вопросов, и я ненавижу тратить ваше время, поэтому я дам вам награду, когда у меня все получится! :) В любом случае, 1. С помощью приведенного выше кода вы можете добавить проверку, если это приспособлениеA, а затем поменять местами ‹ на › в зависимости от этой проверки? Я просто не понимаю, как это сделать (извините!) 2. Есть ли способ переместить этот код в метод тика? В частности, если вы посмотрите на этот учебник 2 в разделе «Уничтожение блоков», код под ним. Могу ли я переместить эти чеки туда, так как я думаю, что это будет проще с моей стороны? Вот и все мои вопросы на данный момент! Дай мне знать!!! :) - person SimplyKiwi; 20.07.2012
comment
@iBradApps 1) Добавлено. 2) Да, смотрите, что я добавил :D - person Iskar Jarak; 21.07.2012
comment
Спасибо! Я проверю это позже или завтра и дам вам знать! Еще раз спасибо. - person SimplyKiwi; 21.07.2012
comment
Обновите, я начал собирать ваш ответ и код и столкнулся с проблемой. Я действительно запутался в том, как должен выглядеть метод tick (уничтожающий код блоков из tut), поскольку это другой класс, чем класс MyContactListener. Можете ли вы просто показать, как будет выглядеть этот метод галочки вместе с кодом метода Begin Contact? Все, что вы сейчас делаете в методе Begin Contact, мне нужно сделать в моем методе tick, а некоторых вещей там нет, например, b2Contact. Если бы вы могли просто показать, как должен выглядеть этот метод галочки, я думаю, у меня все получится. Также я добавил щедрость, спасибо! - person SimplyKiwi; 21.07.2012
comment
Вы не можете делать абсолютно все в тике - вам все равно нужно использовать BeginContact и EndContact прослушивателя контактов, чтобы добавлять/удалять контакты в/из вектора, который вы перебираете в tick(). Во всяком случае, добавил больше деталей в ответ. - person Iskar Jarak; 23.07.2012
comment
Спасибо! Это работает довольно хорошо (намного лучше, чем моя техника до: P). Я решил, что вместо того, чтобы проверять совпадающие приборы, я просто использовал теги, потому что они мне проще. Последний и последний вопрос: как мне изменить число 0,707, чтобы оно принимало немного более широкий угол, чем сейчас? - person SimplyKiwi; 23.07.2012
comment
Кроме того, наряду с моим комментарием выше, если есть какая-либо программа для Mac, которая может помочь мне визуализировать, насколько большой угол я хочу, а затем вычислить значение, дайте мне знать !!! :П - person SimplyKiwi; 23.07.2012
comment
Просто увеличьте число до 1.0. Я не знаю ни одной программы, которая могла бы визуализировать это для вас, но вы можете использовать косинус (угол_в_градусах), где угол — это угол от вертикали к каждой стороне (например, \|/). ~ 0,707 - это cos (45 градусов). 1 это потому что (0 градусов) - person Iskar Jarak; 23.07.2012
comment
Хорошо, спасибо за все до сих пор! Я наградил вас наградой и проголосовал за ваш ответ. И просто чтобы уточнить, что вы имели в виду под вертикалью и стороной, скажем, квадрата. О какой точке квадрата вы говорили? Я просто хотел как следует визуализировать это в своей голове. - person SimplyKiwi; 23.07.2012
comment
Нет проблем, и спасибо. Я имею в виду угол между вертикальной плоскостью и нормалью столкновения. Скажем, А — квадрат, а В — прямоугольник. Если A приземлится поверх B, нормаль столкновения от A к B будет направлена ​​прямо вниз, поэтому угол между нормалью столкновения и вертикальной линией равен нулю. Теперь представьте, что B — это круг, а A не приземляется идеально на него. Нормаль столкновения от A до B больше не будет направлена ​​прямо вниз. Угол между ним и вертикальной линией больше не будет равен нулю. - person Iskar Jarak; 23.07.2012
comment
Если вы используете для сравнения 0,707, то мы рассматриваем это как столкновение, при котором A приземлился (примерно) на вершину B, если угол составляет 45 градусов или меньше (мы определяем это, глядя на размер y компонента нормали столкновения, которая всегда будет иметь общую длину 1, поскольку это единичный вектор). - person Iskar Jarak; 23.07.2012
comment
Хорошо, спасибо, это объясняет это. После некоторого тестирования, даже если я поставлю 1 для обоих возможных столкновений, все равно кажется, что угол очень мал со стороны, на которую он может приземлиться. Взгляните на это изображение, которое я сделал: imgur.com/t1Elu Мне очень нужна нижняя часть персонажа. чтобы коснуться зеленых областей, и это будет считаться столкновением, а красная часть не будет считаться столкновением. Я думаю, что ваш код уже делает все это, я просто пытаюсь найти правильное значение, но я просто не знаю, что. Может быть, эта картинка поможет вам понять, чего именно я пытаюсь добиться! Спасибо! - person SimplyKiwi; 23.07.2012
comment
Ах! Мне так жаль. Я сказал увеличить его до 1, потому что я думал, что вы хотите меньший угол. Совершенно неправильно прочитал один из ваших комментариев. Уменьшите значения до 0. Однако не делайте их слишком низкими. Кроме того, может быть очень, очень сложно сделать все именно так, как вы этого хотите — те маленькие стороны, которые вы выделили, скорее всего, будут болью. - person Iskar Jarak; 23.07.2012
comment
Хорошо, я сделаю еще несколько проб и ошибок завтра, и я дам вам знать. Спасибо за всю помощь! - person SimplyKiwi; 23.07.2012
comment
Хорошо, мне удалось успеть провести некоторые тесты. Даже если я перейду к диапазону от 0,100 до 0,200, он все равно не допустит большинства столкновений со стороны, которая находится очень близко к вершине. Игроку становится намного сложнее приземлиться на пол, поэтому я хочу сделать его немного проще. Так можно ли достичь области, которую я выделил на этой картинке? - person SimplyKiwi; 23.07.2012
comment
Вы пытались зарегистрировать значения нормали, чтобы увидеть, что она делает? - person Iskar Jarak; 23.07.2012
comment
Да, когда я нахожусь прямо на этаже с персонажем, он записывает: -1.000000. Если я захожу сбоку под углом, который он не принимает, но я хочу, он регистрирует: -0,002030 - person SimplyKiwi; 23.07.2012
comment
Да, это меня не удивляет - в последнем случае нормаль столкновения будет в основном горизонтальной, точно так же, как если бы он ударил дальше по стороне. Вы можете попробовать сделать углы коробки закругленными (если это возможно). Заставить это работать будет очень сложно. ИМО лучшее решение - не делать этого. Вместо этого сделайте объект Box2D, который вы хотите, чтобы игрок приземлился (пол/платформа/что угодно), немного короче и, возможно, немного шире, чем спрайт/изображение, представляющее землю. Это довольно распространенная настройка, поэтому она кажется более щадящей. img687.imageshack.us/img687/2774/box2dexample.png - person Iskar Jarak; 24.07.2012
comment
Это хорошая идея, позвольте мне попробовать, и я свяжусь с вами! - person SimplyKiwi; 24.07.2012
comment
Знаете ли вы какую-нибудь хорошую программу, которая может помочь мне получить определенные значения для выполнения чего-то вроде рисунка, который вы мне показали? Просто делать пробы и ошибки кажется, что это займет вечность! :P P.S - Как ты сделал это изображение, в какой программе? - person SimplyKiwi; 25.07.2012
comment
Извините, я ничего не знаю из головы. Да, пробы и ошибки — это боль, но иногда это единственный путь. Я сделал это изображение с помощью www.pixlr.com. - person Iskar Jarak; 25.07.2012
comment
Извините за отсутствие обновлений, наконец-то я смог вернуться к этому вопросу. Я использую программу Physics Editor. Я реализую пользовательские формы сегодня или в течение следующих нескольких дней, и я дам вам знать, как это происходит. - person SimplyKiwi; 21.08.2012
comment
Я смог заставить все формы хорошо работать с редактором физики. Теперь моя последняя проблема с обнаружением столкновений — это попытка получить точку столкновения между b2Body. Это возможно? Вы когда-нибудь пытались сделать что-то подобное? Если так, я могу дать вам еще одну награду, если вы покажете мне! - person SimplyKiwi; 31.08.2012
comment
Извините за медленный ответ. Что вы имеете в виду, когда говорите о точке столкновения тел? Вы имеете в виду площадь контакта между двумя телами (это не всегда будет просто точка)... - person Iskar Jarak; 17.09.2012
comment
Да, область столкновения, но TBH, я думаю, вместо того, чтобы делать это, что будет сложно, я просто переверну свою собственную систему определения значения столкновения Y. - person SimplyKiwi; 17.09.2012
comment
Хм. На самом деле, мировое многообразие (считайте его чем-то вроде области) должно содержать массив точек контакта (b2Vec2s). worldManifold.points - это массив. - person Iskar Jarak; 17.09.2012
comment
Кроме того, я думаю (не проверял), что worldManifold.m_pointCount дает количество точек, хотя я считаю, что вы не должны видеть больше двух. - person Iskar Jarak; 17.09.2012
comment
Да, я помню, как делал это, а затем каким-то образом получил точку Y. Однако точка Y никогда не менялась во время столкновения, хотя она должна была явно измениться. Если я правильно помню, он стоял на отметке 311. - person SimplyKiwi; 17.09.2012
comment
Я предлагаю вам попробовать еще раз - поведение, которое вы описываете, звучит неправильно. Просто начните с использования NSLog, чтобы распечатать координаты x и y любых точек массива во время столкновения, и подтвердите, видите ли вы какие-либо изменения. - person Iskar Jarak; 19.09.2012