
Новый nape. Приложение сил к телам и raycast
Новый nape:
1. Новый nape. Hello world
2. Новый nape. Обработка событий
3. Новый nape. Testbed. И немного теории
4. Новый nape. Создание тел, материалы, фильтрация столкновений
5. Новый nape. Соединение тел
6. Новый nape. Приложение сил к телам и raycast (Его вы сейчас читаете)
Все примеры кода в Testbed: Скачать проект для FlashDevelop.
Приложение сил и импульсов к телам.
Как мы можем повлиять на тело в nape? Можно просто указать ему новое положение или скорость — но такое воздействие лежит за пределами физической симуляции, можно прицепить к нему constraint — это вполне в рамках физики, но не всегда подходит, есть еще один вариант, и его мы не рассматривали, — можно приложить к телу силу или импульс.
Для начала разберемся, что такое сила и импульс. Школьный курс наверное помнят все, но не будем вдаваться в детали, для нас важно следующее: с точки зрения nape приложить силу — это тоже самое, что приложить импульс равный величине силы, умноженной на длительность одного шага физической симуляции. Т.е. с практической точки зрения, если есть некая длительно действующая сила, то нужно прикладывать к телу силу (например, если вы решили сделать например эффект ветра, то просто каждый шаг прикладываете к нужным телам соответствующую силу), а если возникло какое-то мгновенное событие (например, взрыв), то нужно прикладывать импульс. Если следовать этому правилу, то можно менять длительность шага симуляции, и это не приведет к необходимости менять размеры прикладываемых сил и импульсов.
В nape у класса Body есть шесть функций:
Первые три функции прикладывают силу, а следующие три — импульс. В остальном эти функции отличаются только тем, как им передаются координаты точки приложения силы: в функции со словом local в названии передается точка в локальных координатах тела, в функции со словом relative в названии передается точка в «относительных координатах», т.е. начало координат совпадает с положением тела, а оси направлены как глобальной системе координат, и, наконец, функции с названием, содержащим world, принимают глобальные координаты. Сила или импульс всегда передаются в глобальных координатах.
В Testbed я добавил тест с эффектами ветра и взрыва, а также добавил ракету. Там используются описанные функции. Эффект ветра и взрыва не учитывают размер попавших зону их действия предметов, а должны бы. В остальном надеюсь там все очевидно.
Код. Ракета, тут ИМХО все очевидно, смотрим где находится условный двигатель, где — центр ракеты, проводим линию получаем направление силы, прикладываем ее в точке крепления условного двигателя.
Ветер. Перебираем все тела касающиеся сенсора, задающего область действия ветра и прикладываем к ним силу.
Взрыв. Пробегаем по телам касающимся сенсора, отвечающего за взрыв (а можно было бы пробегать и по всем телам вообще или воспользоваться функцией space.bodiesInCircle) и прикладываем к ним импульс направленный от центра взрыва и обратно пропорциональный квадрату расстояния.
Raycast
В nape есть функции для поиска тел, пересекаемых заданным лучом. Очень полезно, если делать что-нибудь лазерное :-)
Таких функций всего две, обе в классе Space: rayCast и rayMultiCast. Первая нужна, если нужно найти первое пересечение, а вторая — если нужно найти все пересечения.
Немного кода для ясности:
Вначале создается луч, при этом указывается точка начала и вектор направления, также я задал максимальную длину, можно этого не делать, тогда ограничения по длине не будет. Далее вызывается собственно функция rayCast: первый параметр — луч, второй — нужно ли учитывать пересечения изнутри наружу фигуры, а третий — фильтр столкновений, пересечение луча будет только с телом, с которым бы столкнулось другое тело с таким фильтром. Ну и в качестве демонстрации я прикладываю силу в этой точке к найденному телу.
RayMultiCast отличается тем, что возвращает список из всех найденных точек столкновения, отсортированный по удаленности. Я написал небольшой пример разрезания тел на куски лучом с использованием этой функции. Общая идея в том, что мы находим две точки пересечения с одним и тем же полигоном, а потом отбираем вершины лежащие выше и ниже луча (для этого надо знать, что такое скалярное произведение, и что по-английски оно зовется dot product, а также что в nape его выполняет функция Vec2.dot()) И из них и точек пересечения получаем два новых полигона.
Смотрим результат. Найдите тесты с названиями Test 3 и Test 4.
1. Новый nape. Hello world
2. Новый nape. Обработка событий
3. Новый nape. Testbed. И немного теории
4. Новый nape. Создание тел, материалы, фильтрация столкновений
5. Новый nape. Соединение тел
6. Новый nape. Приложение сил к телам и raycast (Его вы сейчас читаете)
Все примеры кода в Testbed: Скачать проект для FlashDevelop.
Приложение сил и импульсов к телам.
Как мы можем повлиять на тело в nape? Можно просто указать ему новое положение или скорость — но такое воздействие лежит за пределами физической симуляции, можно прицепить к нему constraint — это вполне в рамках физики, но не всегда подходит, есть еще один вариант, и его мы не рассматривали, — можно приложить к телу силу или импульс.
Для начала разберемся, что такое сила и импульс. Школьный курс наверное помнят все, но не будем вдаваться в детали, для нас важно следующее: с точки зрения nape приложить силу — это тоже самое, что приложить импульс равный величине силы, умноженной на длительность одного шага физической симуляции. Т.е. с практической точки зрения, если есть некая длительно действующая сила, то нужно прикладывать к телу силу (например, если вы решили сделать например эффект ветра, то просто каждый шаг прикладываете к нужным телам соответствующую силу), а если возникло какое-то мгновенное событие (например, взрыв), то нужно прикладывать импульс. Если следовать этому правилу, то можно менять длительность шага симуляции, и это не приведет к необходимости менять размеры прикладываемых сил и импульсов.
В nape у класса Body есть шесть функций:
applyLocalForce
applyRelativeForce
applyWorldForce
applyLocalImpulse
applyRelativeImpulse
applyWorldImpulse
Первые три функции прикладывают силу, а следующие три — импульс. В остальном эти функции отличаются только тем, как им передаются координаты точки приложения силы: в функции со словом local в названии передается точка в локальных координатах тела, в функции со словом relative в названии передается точка в «относительных координатах», т.е. начало координат совпадает с положением тела, а оси направлены как глобальной системе координат, и, наконец, функции с названием, содержащим world, принимают глобальные координаты. Сила или импульс всегда передаются в глобальных координатах.
В Testbed я добавил тест с эффектами ветра и взрыва, а также добавил ракету. Там используются описанные функции. Эффект ветра и взрыва не учитывают размер попавших зону их действия предметов, а должны бы. В остальном надеюсь там все очевидно.
Код. Ракета, тут ИМХО все очевидно, смотрим где находится условный двигатель, где — центр ракеты, проводим линию получаем направление силы, прикладываем ее в точке крепления условного двигателя.
var rocketForceVec:Vec2 = rocket.position.sub(rocketEngine.worldCOM);
rocketForceVec.muleq(rocketForce.value / rocketForceVec.length);
rocket.applyWorldForce(rocketForceVec, rocketEngine.worldCOM);
Ветер. Перебираем все тела касающиеся сенсора, задающего область действия ветра и прикладываем к ним силу.
for (var i:int = 0; i < windSensor.arbiters.length; ++i)
{
var arb:Arbiter = windSensor.arbiters.at(i);
if (arb.isSensorArbiter())
{
var body:Body = arb.body1 == windSensor ? arb.body2 : arb.body1;
if (body.isDynamic())
body.applyRelativeForce(new Vec2(windForce.value, 0));
}
}
Взрыв. Пробегаем по телам касающимся сенсора, отвечающего за взрыв (а можно было бы пробегать и по всем телам вообще или воспользоваться функцией space.bodiesInCircle) и прикладываем к ним импульс направленный от центра взрыва и обратно пропорциональный квадрату расстояния.
for (i = 0; i < explosionSensor.arbiters.length; ++i)
{
arb = explosionSensor.arbiters.at(i);
if (arb.isSensorArbiter())
{
body = arb.body1 == explosionSensor ? arb.body2 : arb.body1;
if (body.isDynamic())
{
var impulse:Vec2 = body.worldCOM.sub(explosionPoint);
impulse.muleq(explosionCoefficient.value / Math.max(100, impulse.lsq(), 2));
body.applyLocalImpulse(impulse);
}
}
}
Raycast
В nape есть функции для поиска тел, пересекаемых заданным лучом. Очень полезно, если делать что-нибудь лазерное :-)
Таких функций всего две, обе в классе Space: rayCast и rayMultiCast. Первая нужна, если нужно найти первое пересечение, а вторая — если нужно найти все пересечения.
Немного кода для ясности:
var ray:Ray = new Ray(laserPoint.worldCOM, Vec2.fromPolar(1, laser.rotation));
ray.maxDistance = 300;
var rayResult:RayResult = space.rayCast(ray, false, new InteractionFilter(1, 0x10));
if (rayResult != null)
{
var resultPt:Vec2 = ray.at(rayResult.distance);
var body:Body = rayResult.shape.body;
body.applyWorldForce(ray.direction.mul(1000), resultPt);
}
Вначале создается луч, при этом указывается точка начала и вектор направления, также я задал максимальную длину, можно этого не делать, тогда ограничения по длине не будет. Далее вызывается собственно функция rayCast: первый параметр — луч, второй — нужно ли учитывать пересечения изнутри наружу фигуры, а третий — фильтр столкновений, пересечение луча будет только с телом, с которым бы столкнулось другое тело с таким фильтром. Ну и в качестве демонстрации я прикладываю силу в этой точке к найденному телу.
RayMultiCast отличается тем, что возвращает список из всех найденных точек столкновения, отсортированный по удаленности. Я написал небольшой пример разрезания тел на куски лучом с использованием этой функции. Общая идея в том, что мы находим две точки пересечения с одним и тем же полигоном, а потом отбираем вершины лежащие выше и ниже луча (для этого надо знать, что такое скалярное произведение, и что по-английски оно зовется dot product, а также что в nape его выполняет функция Vec2.dot()) И из них и точек пересечения получаем два новых полигона.
var rayResults:RayResultList = space.rayMultiCast(ray, true, rayFilter);
if (rayResults.length > 1)
{
var polygon:Polygon = rayResults.at(0).shape as Polygon;
if (polygon != null)
{
var pt0:Vec2 = ray.at(rayResults.at(0).distance);
var pt1:Vec2 = null;
for (var i:int = 1; i < rayResults.length; ++i)
{
if (rayResults.at(i).shape == polygon)
{
pt1 = ray.at(rayResults.at(i).distance);
break;
}
}
if (pt1 != null)
{
var normal:Vec2 = new Vec2( -(pt1.y - pt0.y), pt1.x - pt0.x);
var vertN:int = polygon.worldVerts.length;
function side(n:int):Boolean
{
return normal.dot(polygon.worldVerts.at(n).sub(pt0)) > 0;
}
//find first vertice over pt0-pt1
//iterate while vertices are over
for (i = 0; side(i) == true; i = (i + 1) % vertN) {}
//iterate while vertices are below, stop when vertice is again over
for (; side(i) == false; i = (i + 1) % vertN) {}
//get distance from pt0 and from pt1 to edge (i)-(i-1). intersection0 - current intersection, intersection1 - next intersection
var distPt0:Number = Math.abs(polygon.worldVerts.at(i).sub(pt0).dot(polygon.edges.at((i - 1 + vertN) % vertN).worldNormal));
var distPt1:Number = Math.abs(polygon.worldVerts.at(i).sub(pt1).dot(polygon.edges.at((i - 1 + vertN) % vertN).worldNormal));
var intersection0:Vec2;
var intersection1:Vec2;
if (distPt0 < distPt1)
{
intersection0 = pt0;
intersection1 = pt1;
}
else
{
intersection0 = pt1;
intersection1 = pt0;
}
//create slices
for (var n:int = 0; n < 2; ++n)
{
var verts:Array = [n == 0 ? intersection0 : intersection1];
for (; side(i) == (n == 0); i = (i + 1) % vertN)
verts.push(polygon.worldVerts.at(i));
verts.push(n == 0 ? intersection1 : intersection0);
var newB:Body = new Body(BodyType.DYNAMIC);
newB.shapes.add(new Polygon(verts, polygon.material, polygon.filter, polygon.cbType));
newB.align();
newB.space = space;
}
polygon.body.space = null;
}
}
Смотрим результат. Найдите тесты с названиями Test 3 и Test 4.
- +22
- romamik
Комментарии (3)
Если решу пересесть на nape — всё будет гораздо легче чем могло бы...:)