Страницы

понедельник, 7 января 2013 г.

Nape - камера в игре

      Очень часто в играх необходимо реализовать так называемую - камеру. Камера фокусируется на главном герое, и при перемещении главного героя он остается по центру игрового экрана, а весь мир передвигается относительно героя.
      В Nape при Debug отрисовке можно использовать при обновлении экрана:

camera_x = hero.position.x - stage.width/2;
camera_y = hero.position.y - stage.height/2;

debug.transform = Mat23.translation( -camera_x, -camera_y);

      Таким образом мы получаем смещенную отрисовку относительно координат hero, который всегда находится по центру экрана.

      Модифицируем наш пример с машинкой:


package
{ 
    import flash.display.Sprite;
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.events.KeyboardEvent; //клавиатурные события
    import nape.callbacks.BodyCallback;
    import nape.constraint.DistanceJoint;
    import nape.phys.Compound;
    import nape.phys.Material;
    import nape.callbacks.CbType;
    import nape.callbacks.CbEvent;
    import nape.callbacks.BodyListener;
  
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
    import nape.shape.Shape;
    import nape.geom.Mat23;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
  
        private var wheel_front:Body;
        private var wheel_rear:Body;
        private var cabin:Body;
  
        //Ограничение для колес
        private var wheel_f_joint:DistanceJoint;
        private var wheel_r_joint:DistanceJoint;
        private var cabin_front_joint:DistanceJoint;
        private var cabin_rear_joint:DistanceJoint;

        // Объединения
        private var car:Compound;
 
        // Обратные вызовы
        private var cbCarType:CbType;
  
        //Камера
        private var camera_x:Number = 0;
        private var camera_y:Number = 0;
  
        //Кузов
        [Embed(source="../assets/cabin.png")]
        private var Cabin:Class;
  
        //Колеса
        [Embed(source="../assets/wheel.png")]
        private var Wheel:Class;
  
        public function Main():void
        {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
        {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 400);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
                stage.color);
   
            //Рисуем Ограничения
            debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
            
            // Обработчик клавиатуры
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
        {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            //Устанавливаем границы мира
            var border:Body = new Body(BodyType.STATIC);
            border.shapes.add(new Polygon(Polygon.rect(0, 0, w*4, -1)));
            border.shapes.add(new Polygon(Polygon.rect(0, h*3, w*4, 1)));
            border.shapes.add(new Polygon(Polygon.rect(0, 0, -1, h*3)));
            border.shapes.add(new Polygon(Polygon.rect(w*4, 0, 1, h*3)));
            border.space = space;
   
            // Статическая земля   
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(220, 100, 6, 0, false),
                Material.rubber()));
            floor.position.setxy(1420, 1800);
            floor.space = space;
   
            // Объединение - машина
            car = new Compound();
      
            // Колесо переднее
            wheel_front = new Body(BodyType.DYNAMIC);
            wheel_front.position.setxy(290, 106);
            var wheel_frontShape:Shape = new Circle(28, null, Material.rubber());
            wheel_frontShape.body = wheel_front; 
            //wheel_front.angularVel = 20;
            wheel_front.compound = car;
   
            // Колесо заднее
            wheel_rear = new Body(BodyType.DYNAMIC);
            wheel_rear.position.setxy(200, 106);
            var wheel_rearShape:Shape = new Circle(28, null, Material.rubber());
            wheel_rearShape.body = wheel_rear;  
            //wheel_rear.angularVel = 20;
            wheel_rear.compound = car;
   
            // Кузов
            var cabinIso:BitmapDataIso = new BitmapDataIso((new Cabin()).bitmapData, 0x80);
            cabin = IsoBody.run(cabinIso, cabinIso.bounds);
            cabin.mass = 10;
            cabin.position.setxy(245, 70);
            cabin.compound = car;
   
            //Ограничение для колес (держит межколесное расстояние)
            wheel_f_joint = new DistanceJoint(wheel_front, cabin, wheel_front.localCOM,
                cabin.localCOM, 88, 88);
            wheel_f_joint.compound = car;
            wheel_r_joint = new DistanceJoint(wheel_rear, cabin, wheel_rear.localCOM,
                cabin.localCOM, 80, 80);
            wheel_r_joint.compound = car;
   
            //Передний амортизатор
            cabin_front_joint = new DistanceJoint(wheel_front, cabin,
            wheel_front.localCOM, cabin.worldPointToLocal(Vec2.weak(cabin.position.x + 70,
                cabin.position.y)), 52, 55);
            cabin_front_joint.compound = car;
   
            //Задний амортизатор
            cabin_rear_joint = new DistanceJoint(wheel_rear, cabin,
            wheel_rear.localCOM, cabin.worldPointToLocal(Vec2.weak(cabin.position.x - 62,
                cabin.position.y)), 52, 55);
            cabin_rear_joint.compound = car;
   
            //Выводим машинку со всеми ограничениями
            car.space = space;
   
            //Обратный вызов для переднего колеса
            cbCarType = new CbType();
            wheel_front.cbTypes.add(cbCarType);
            wheel_front.space.listeners.add(new BodyListener(CbEvent.SLEEP, cbCarType,
                carSleepHandler));
        }
  
        private function carSleepHandler(cb:BodyCallback):void 
        {
            //Как только засыпает - едем!
            var impulse:Vec2  = Vec2.get(10, 0);
            impulse.length   = 3200;
            cabin.applyImpulse(impulse);
        }
 
        private function enterFrameHandler(ev:Event):void 
        {
            space.step(1 / stage.frameRate);
   
            //Фокус на машину, центрируем
            camera_x = cabin.position.x - stage.width/2;
            camera_y = cabin.position.y - stage.height/2;
   
            debug.clear();
            debug.transform = Mat23.translation( -camera_x, -camera_y);
            debug.draw(space);
            debug.flush();
        }
  
        private function keyDownHandler(e:KeyboardEvent):void
        {
            if (e.keyCode == 37)
            { // влево
                car.rotate(wheel_rear.worldCOM, -0.5);
            }
            if (e.keyCode == 39)
            { // вправо
                car.rotate(wheel_front.worldCOM, 0.5);
            }
            if (e.keyCode == 40)
            { // вниз
                wheel_front.angularVel -= 3;
                wheel_rear.angularVel -= 3;
                if ((wheel_front.angularVel < -30) || (wheel_rear.angularVel < -30))
                {
                     wheel_front.angularVel = -30;
                     wheel_rear.angularVel = -30;
                }
            }
            if (e.keyCode == 38)
            { // вверх
                wheel_front.angularVel += 3;
                wheel_rear.angularVel += 3;
                if ((wheel_front.angularVel > 30) || (wheel_rear.angularVel > 30))
                {
                    wheel_front.angularVel = 30;
                    wheel_rear.angularVel = 30;
                }
            }
        }
    }
}

      В примере пространство Nape увеличено, чтобы можно было посмотреть работу камеры. Управление машинкой: вверх/вниз - увеличение/уменьшение скорости, влево/вправо - кувырки.
      Поиграть можно тут

пятница, 4 января 2013 г.

Nape - создание произвольных тел

      В физическом движке Nape можно создавать произвольные тела на основании графического рисунка.
     Графику для физики игр я подготавливаю в Paint.NET, который позволяет указывать так называемую маску рисунка, где прозрачный задний фон alpha=0, там отсутствует физические элементы тела.
      Попробуем улучшить отображение нашей машинки из предыдущих примеров. Для начала нарисуем кузов размером 200 на 60 пикселей (при создании в Paint.NET нового спрайта по умолчанию он заливается непрозрачным белым цветом, необходимо его выделить и удалить, прозрачность отображается шахматной доской):


      Сохраняем кузов машины в папку assets проекта. Для преобразования рисунков в физические тела в Nape можно использовать следующие файлы (копируем их в папку src проекта):

Файл IsoBody.as
package 
{ 
 import nape.geom.AABB;
 import nape.geom.GeomPoly;
 import nape.geom.GeomPolyList;
 import nape.geom.IsoFunction;
 import nape.geom.MarchingSquares;
 import nape.geom.Vec2;
 import nape.phys.Body;
 import nape.shape.Polygon;
 
 public class IsoBody
 {
  public static function run(iso:IsoFunction, bounds:AABB, granularity:Vec2 = null, quality:int = 2, simplification:Number = 1.5):Body
  {
   var body:Body = new Body();
  
   if (granularity == null) granularity = Vec2.weak(8, 8);
   var polys:GeomPolyList = MarchingSquares.run(iso, bounds, granularity, quality);
   for (var i:int = 0; i < polys.length; i++)
   {
    var p:GeomPoly = polys.at(i);
  
    var qolys:GeomPolyList = p.simplify(simplification).convexDecomposition(true);
    for (var j:int = 0; j < qolys.length; j++)
    {
     var q:GeomPoly = qolys.at(j);
  
     body.shapes.add(new Polygon(q));
  
     // Recycle GeomPoly and its vertices
     q.dispose();
    }
    // Recycle list nodes
    qolys.clear();
  
    // Recycle GeomPoly and its vertices
    p.dispose();
   }
   // Recycle list nodes
   polys.clear();
  
   // Align body with its centre of mass.
   // Keeping track of our required graphic offset.
   var pivot:Vec2 = body.localCOM.mul(-1);
   body.translateShapes(pivot);
  
   body.userData.graphicOffset = pivot;
   return body;
  }
 }
}

Файл DisplayObjectIso.as
package
{
 import nape.geom.AABB;
 import flash.display.DisplayObject;
  
 public class DisplayObjectIso implements IsoFunction
 {
  public var displayObject:DisplayObject;
  public var bounds:AABB;
  
  public function DisplayObjectIso(displayObject:DisplayObject):void
  {
   this.displayObject = displayObject;
   this.bounds = AABB.fromRect(displayObject.getBounds(displayObject));
  }
  
  public function iso(x:Number, y:Number):Number
  {
   // Best we can really do with a generic DisplayObject
   // is to return a binary value {-1, 1} depending on
   // if the sample point is in or out side.
  
   return (displayObject.hitTestPoint(x, y, true) ? -1.0 : 1.0);
  }
 }
}

Файл BitmapDataIso.as
package
{ 
 import nape.geom.AABB;

 import nape.geom.IsoFunction;
 
 import flash.display.Bitmap;
 import flash.display.BitmapData;
 import flash.display.DisplayObject;

 public class BitmapDataIso implements IsoFunction
 {
  public var bitmap:BitmapData;
  public var alphaThreshold:Number;
  public var bounds:AABB;
  
  public function BitmapDataIso(bitmap:BitmapData, alphaThreshold:Number = 0x80):void
  {
   this.bitmap = bitmap;
   this.alphaThreshold = alphaThreshold;
   bounds = new AABB(0, 0, bitmap.width, bitmap.height);
  }
  
  public function graphic():DisplayObject
  {
   return new Bitmap(bitmap);
  }
  
  public function iso(x:Number, y:Number):Number
  {
   // Take 4 nearest pixels to interpolate linearly.
   // This gives us a smooth iso-function for which
   // we can use a lower quality in MarchingSquares for
   // the root finding.
  
   var ix:int = int(x); var iy:int = int(y);
   //clamp in-case of numerical inaccuracies
   if(ix<0 data-blogger-escaped-if="if" data-blogger-escaped-ix="ix" data-blogger-escaped-iy="0;">=bitmap.width)  ix = bitmap.width-1;
   if(iy>=bitmap.height) iy = bitmap.height-1;
  
   // iso-function values at each pixel centre.
   var a11:Number = alphaThreshold - (bitmap.getPixel32(ix,iy)>>>24);
   var a12:Number = alphaThreshold - (bitmap.getPixel32(ix+1,iy)>>>24);
   var a21:Number = alphaThreshold - (bitmap.getPixel32(ix,iy+1)>>>24);
   var a22:Number = alphaThreshold - (bitmap.getPixel32(ix+1,iy+1)>>>24);
  
   // Bilinear interpolation for sample point (x,y)
   var fx:Number = x - ix; var fy:Number = y - iy;
   return a11*(1-fx)*(1-fy) + a12*fx*(1-fy) + a21*(1-fx)*fy + a22*fx*fy;
  }
 }
}

      Модернизируем наш проект движения машинки, с учетом новых знаний:


package
{ 
    import flash.display.Sprite;
 import flash.display.DisplayObject;
    import flash.events.Event;
 import nape.callbacks.BodyCallback;
 import nape.constraint.DistanceJoint;
 import nape.constraint.WeldJoint;
 import nape.phys.Compound;
 import nape.phys.Material;
 import nape.callbacks.CbType; // типы обратных вызовов
 import nape.callbacks.CbEvent; // события обратных вызовов
 import nape.callbacks.BodyListener; //слушатель для колеса
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
 import nape.shape.Shape;
 
    public class Main extends Sprite
 {
        private var space:Space;
        private var debug:Debug;
  
  private var wheel_front:Body;
  private var wheel_rear:Body;
  private var cabin:Body;
  
  //Ограничение для колес
  private var wheel_f_joint:DistanceJoint; //Держит расстояние между колес
  private var wheel_r_joint:DistanceJoint;
  private var cabin_front_joint:DistanceJoint; //Амортизатор спереди машины на сварку
  private var cabin_rear_joint:DistanceJoint; //Амортизатор сзади машины на сварку

  // Объединения
  private var car:Compound;
 
  // Обратные вызовы
  private var cbCarType:CbType;
  
  //Кузов
  [Embed(source="../assets/cabin.png")]
        private var Cabin:Class;
  
  //Колеса
  [Embed(source="../assets/wheel.png")]
        private var Wheel:Class;
  
        public function Main():void
  {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
  {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 900);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
       stage.color);
   
   //Рисуем Ограничения
   debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
  {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
       Material.wood()));
            
   floor.position.setxy(320, 300);
   floor.space = space;  
   
   // Объединение - машина
   car = new Compound();
      
   // Колесо переднее
   wheel_front = new Body(BodyType.DYNAMIC);
   wheel_front.position.setxy(290, 106);
   var wheel_frontShape:Shape = new Circle(12, null, Material.rubber());
   wheel_frontShape.body = wheel_front; 
   //wheel_front.angularVel = 20;
   wheel_front.compound = car;
   
   // Колесо заднее
   wheel_rear = new Body(BodyType.DYNAMIC);
   wheel_rear.position.setxy(200, 106);
   var wheel_rearShape:Shape = new Circle(12, null, Material.rubber());
   wheel_rearShape.body = wheel_rear;  
   //wheel_rear.angularVel = 20;
   wheel_rear.compound = car;
   
   // Кузов
   var cabinIso:BitmapDataIso = new BitmapDataIso((new Cabin()).bitmapData, 0x80);
            cabin = IsoBody.run(cabinIso, cabinIso.bounds);
   cabin.mass = 10;
   cabin.position.setxy(245, 70);
   cabin.compound = car;
   
   //Ограничение для колес (держит межколесное растояние)
   wheel_f_joint = new DistanceJoint(wheel_front, cabin, wheel_front.localCOM,
    cabin.localCOM, 78, 78);
   wheel_f_joint.compound = car;
   wheel_r_joint = new DistanceJoint(wheel_rear, cabin, wheel_rear.localCOM,
    cabin.localCOM, 70, 70);
   wheel_r_joint.compound = car;
   
   
   //Передний амортизатор
   cabin_front_joint = new DistanceJoint(wheel_front, cabin,
    wheel_front.localCOM, cabin.worldPointToLocal(Vec2.weak(cabin.position.x + 70, cabin.position.y)), 32, 35);
   cabin_front_joint.compound = car;
   
   //Задний амортизатор
   cabin_rear_joint = new DistanceJoint(wheel_rear, cabin,
    wheel_rear.localCOM, cabin.worldPointToLocal(Vec2.weak(cabin.position.x - 62, cabin.position.y)), 32, 35);
   cabin_rear_joint.compound = car;
   
   //Выводим машинку со всеми ограничениями
   car.space = space;
   
   //Обратный вызов для переднего колеса
   cbCarType = new CbType();
   wheel_front.cbTypes.add(cbCarType);
   wheel_front.space.listeners.add(new BodyListener(CbEvent.SLEEP, cbCarType, carSleepHandler));
   
        }
  
  private function carSleepHandler(cb:BodyCallback):void 
  {
   //Как только засыпает - едем!
   
   var impulse:Vec2  = Vec2.get(10, 0);
   impulse.length   = 3200;
   cabin.applyImpulse(impulse);
  }
 
        private function enterFrameHandler(ev:Event):void 
  {
            space.step(1 / stage.frameRate);
   
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

      Пример работы можно посмотреть тут.

четверг, 27 декабря 2012 г.

Система обратных вызовов (Callbacks) в Nape

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

      Типы обратных вызовов (CbTypes) - можно рассматривать как теги, которые присваиваются объектам. На один объект можно назначить несколько CbTypes.
      Например, представим что вы пишите игру в которой есть объекты - Шипы (Spikes), и мы назначаем им CbType SPIKE, а любому объекту, который может быть убит этими шипами назначаем CbType SPIKEABLE. Или например есть в игре объекты, которые можно собирать, назначаем им CbType COLLECABLE, а объектам, которые могут их собирать - CbType COLLECTOR. Различным объектам можно назначать несколько типов CbType.


var SPIKE       = new CbType();
var SPIKEABLE   = new CbType();
var COLLECTABLE = new CbType();
var COLLECTOR   = new CbType();

for (obj in spikeyObjects) {
    obj.cbTypes.add(SPIKE);
}
for (obj in collectables) {
    obj.cbTypes.add(COLLECTABLE);
}
// etc.

      Слушатели (Listeners), такие как BodyListeners, ConstraintsListeners, InteractionListeners оперируют через CbTypes, или в частности через наборы CbTypes используя API исключения/включения. Задается посредством OptionType. Типы обратных функций
CbTypes назначаются объектам, когда добавляются в список включения, и их нет в списке исключения.
      Для конструкторов слушателей можно передать набор CbTypes по разному: CbTypes, OptionTypes, CbTypeList, Array<CbType>, flash.Vector<CbType>:


// Соответствует любому объекту, у которого CbType SPIKE.
options = SPIKE

// Соответствует любому объекту, у которого CbType SPIKE или/и COLLECTABLE
options = [SPIKE, COLLECTABLE]

// Соответствует любому объекту, у которого CbType SPIKE, но у которого нет COLLECTABLE
options = SPIKE.excluding(COLLECTABLE)

// Соответствует любому объекту, у которого CbType SPIKE или/и COLLECTABLE,
// но которые не относятся к типам COLLECTOR или SPIKEABLE.
options = new OptionType([SPIKE, COLLECTABLE]).excluding([COLLECTOR, SPIKEABLE])
// можно так
options = SPIKE.including(COLLECTABLE).excluding(COLLECTOR).excluding(SPIKEABLE)
// и т.д.


      Для типов обратных вызовов CbTypes есть предопределенные свойства:

  • ANY_BODY - для любого тела
  • ANY_SHAPE - для любой фигуры
  • ANY_COMPOUND - для любого объединения
  • ANY_CONSTRAINT - для любого ограничения
      Например:
// Назначается любому Телу, исключая тип - SPIKE
options = CbType.ANY_BODY.excluding(SPIKE)

// Назначается любой Фигуре или Телу
options = [CbType.ANY_BODY, CbType.ANY_SHAPE]


      Слушатели Тел (BodyListeners) обрабатывают события, связанные с Телом, обратный вызов происходит в конце вызова шага расчета пространства (space.step()). Поддерживается 2 типа обратных вызова для Тел:

  • CbEvent.SLEEP
  • CbEvent.WAKE
      При создании BodyListener необходимо использовать конструктор следующего формата:

var listener:Listener = new BodyListener(event, options, handler, precedence = 0);


      Обработчик:


function bodyWakeHandler(cb:BodyCallback):Void {
    var wokenBody:Body = cb.body;
}


      Слушатели Ограничений (ConstraintListeners) обрабатывают события, связанные с Ограничениями, обратный вызов происходит в конце вызова шага расчета пространства (space.step()). Поддерживается 3 типа обратных вызовов для Ограничений:

  • CbEvent.SLEEP
  • CbEvent.WAKE
  • CbEvent.BREAK
      Конструктор:

var listener:Listener = new ConstraintListener(event, options, handler, precedence = 0);


      Слушатели Взаимодействия (InteractionListeners) обрабатывают события, связанные с Взаимодействиями объектов (Фигур, Тел, Объединений).  Обратный вызов также происходит в конце space.step(). Поддерживается 3 типа обратных вызовов для Взаимодействий:

  • CbEvent.BEGIN
  • CbEvent.ONGOING
  • CbEvent.END
      Событие BEGIN наступает на первом шаге, когда произошло взаимодействие. Событие END возникает на первом шаге, когда взаимодействие пропадает. Событие ONGOING продолжается все время пока происходит взаимодействие, сразу после BEGIN.

      Конструктор:

var listener = new InteractionListener(event, interactionType, options1, options2, handler, precedence = 0);


      Для определенных событий в Nape есть специальный тип InteractionListeners - это PreListeners. Это событие происходит, когда два объекта взаимодействия находят пересечение, но физика еще не обработана. Обработчик может принять решение игнорировать взаимодействие или выполнить действие, например изменение значения Арбитра (Arbiter) трения.
      Обработчик для этих событий происходит прям о в середине шага расчета пространства, поэтому тут мы имеем некоторые ограничения, например мы не можем добавлять/удалять тела и т.п. Это лучше сделать в BEGIN обработчике InteractionListener.
      Конструктор:

var listener = new PreListener(interactionType, options1, options2, handler, precedence = 0, pure = false);


      Обработчик этого события возвращает тип PreFlag либо null. PreFlag может иметь следующие значения:

  • PreFlag.ACCEPT - разрешает взаимодействие для этого шага и для последующих шагов. Если это финальное состояние взаимодействия, то PreListener больше не будет влиять на объекты, пока те не отделятся и не начнут взаимодействовать снова.
  • PreFlag.IGNORE - игнорирует взаимодействие для этого шага и для всех последующих шагов. Если это финальное состояние взаимодействия, то PreListener больше не будет влиять на объекты, пока те не отделятся и не начнут взаимодействовать снова.
  • PreFlag.ACCEPT_ONCE - разрешает взаимодействие для этого шага. Если это финальное состояние взаимодействия, то PreListener будет вызван в следующем шаге, чтобы спросить снова.
  • PreFlag.IGNORE_ONCE - игнорирует взаимодействие на этом шаге. Если это финальное состояние взаимодействия, то PreListener будет вызван в следующем шаге, чтобы спросить снова.
      Если обработчик события возвращает null, то он никак не повлияет на взаимодействие. Значение по умолчанию для PreFlag - PreFlag.ACCEPT_ONCE.
      Параметр - pure важен, когда дело доходит до использования ACCEPT_ONCE, IGNORE_ONCE состояний. Если ваш обработчик не детерминирован (non-deterministic), то для Nape нет возможности разрешить объектам заснуть, поскольку ваш обработчик может внезапно передумать, что делать с взаимодействием. Если ваш обработчик детерминирован - то вы должны установить параметр pure - в true и больше нет необходимости останавливать засыпание объектов.

// Non-deterministic (pure=false) handler
function impureHandler(cb:PreCallback):PreFlag {
    if (Math.random() < 0.5) return PreFlag.IGNORE_ONCE;
    else return null;
}

// Non-deterministic (pure=false) handler
function impureHandler2(cb:PreCallback):PreFlag {
    if (getTimer() > 2000) return PreFlag.ACCEPT_ONCE;
    else return PreFlag.IGNORE_ONCE;
}

// Deterministic (pure=true) handler.
function pureHandler(cb:PreCallback):PreFlag {
    // cb.arbiter is an input to the function, so we can make decisions with it.
    if (cb.arbiter.normal.y > 0) return PreFlag.IGNORE_ONCE;
    else return PreFlag.ACCEPT_ONCE;
}


      Возвращаемся к нашему примеру с машинкой. Сделал обработку обратного вызова - засыпание машины (а точнее правого колеса):

package
{ 
    import flash.display.Sprite;
    import flash.events.Event;
 import nape.callbacks.BodyCallback;
 import nape.constraint.DistanceJoint;
 import nape.phys.Compound;
 import nape.phys.Material;
 import nape.callbacks.CbType; // типы обратных вызовов
 import nape.callbacks.CbEvent; // события обратных вызовов
 import nape.callbacks.BodyListener; //слушатель для колеса
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
 import nape.shape.Shape;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
  
  private var wheel_front:Body;
  private var wheel_rear:Body;
  private var cabin:Body;
  
  //Ограничение для колес
  private var wheels_joint:DistanceJoint; //Держит расстояние между колес
  private var cabin_front_joint:DistanceJoint; //Амортизатор спереди машины
  private var cabin_rear_joint:DistanceJoint; //Амортизатор сзади машины

  // Объединения
  private var car:Compound;
 
  // Обратные вызовы
  private var cbCarType:CbType;
  
        public function Main():void
  {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
     {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 900);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
       stage.color);
   
   //Рисуем Ограничения
   debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
     {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
       Material.ice()));
            
   floor.position.setxy(340, 300);
   floor.space = space;  
   
   // Объединение - машина
   car = new Compound();
   //car.cbTypes.add(carType);
      
   // Колесо переднее
   wheel_front = new Body(BodyType.DYNAMIC);
   wheel_front.position.setxy(290, 106);
   var wheel_frontShape:Shape = new Circle(30, null, Material.rubber());
   wheel_frontShape.body = wheel_front; 
   //wheel_front.angularVel = 20;
   wheel_front.compound = car;
   
   // Колесо заднее
   wheel_rear = new Body(BodyType.DYNAMIC);
   wheel_rear.position.setxy(200, 106);
   var wheel_rearShape:Shape = new Circle(30, null, Material.rubber());
   wheel_rearShape.body = wheel_rear;  
   //wheel_rear.angularVel = 20;
   wheel_rear.compound = car;
   
   // Кузов
   cabin = new Body(BodyType.DYNAMIC);
   cabin.position.setxy(245, 70);
            var cabinShape:Shape = new Polygon(Polygon.box(200, 60),
    new Material(0, 1, 2, 2, 0.01));
   cabinShape.body = cabin;
   cabin.compound = car;
   
   //Ограничение для колес (держит межколесное растояние)
   wheels_joint = new DistanceJoint(wheel_front, wheel_rear, wheel_front.localCOM,
    wheel_rear.localCOM, 120, 120);
   wheels_joint.compound = car;
   
   //Передний амортизатор
   cabin_front_joint = new DistanceJoint(wheel_front, cabin,
    wheel_front.localCOM, cabin.localCOM, 90, 90);
   cabin_front_joint.compound = car;
   
   //Задний амортизатор
   cabin_rear_joint = new DistanceJoint(wheel_rear, cabin,
    wheel_rear.localCOM, cabin.localCOM, 90, 90);
   cabin_rear_joint.compound = car;
   
   //Выводим машинку со всеми ограничениями
   car.space = space;
   
   //Обратный вызов для переднего колеса
   cbCarType = new CbType();
   wheel_front.cbTypes.add(cbCarType);
   wheel_front.space.listeners.add(new BodyListener(CbEvent.SLEEP, cbCarType, carSleepHandler));
        }
  
     private function carSleepHandler(cb:BodyCallback):void 
     {
            //Как только засыпает правое колесо, едем назад!
      var wf:Body = cb.body;
      wf.angularVel = -100;
     }
 
        private function enterFrameHandler(ev:Event):void 
     {
            space.step(1 / stage.frameRate);
   
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

Реализацию этого примера можно посмотреть тут

среда, 26 декабря 2012 г.

Объединения (Compounds) в Nape

     Объединения (составные объекты) - это логическая группировка Тел, Ограничений и других объединений в единый объект.

     Пример объединения:

var c = new Compound();
body1.compound = c;
body2.compound = c;
constraint.compound = c;
c.space = space;


Объединения (Compounds) в Nape

     Когда дело доходит до копирования объединений, можно столкнуться с тем, что не все Ограничения привязаны к Телам. На схеме есть ограничение Co1, которое присвоено Объединению C2, но ссылка на Тело B2 находится вне видимости Объединения C2.
     В этом примере, если сделать копию Объединения C1, то все будет хорошо. Но если сделать копию C2, то Ограничение Co1 будет иметь null ссылку на Тело B2, так как Объединение С2 не видит Тело B2.
     В следующем примере покажу как использовать Ограничения и Объединение:

package
{ 
    import flash.display.Sprite;
    import flash.events.Event;
    import nape.constraint.DistanceJoint;
    import nape.phys.Compound;
    import nape.phys.Material;
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
    import nape.shape.Shape;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
  
        private var wheel_front:Body;
        private var wheel_rear:Body;
        private var cabin:Body;
  
        //Ограничение для колес
        private var wheels_joint:DistanceJoint; //Держит расстояние между колес
        private var cabin_front_joint:DistanceJoint; //Амортизатор спереди машины
        private var cabin_rear_joint:DistanceJoint; //Амортизатор сзади машины

        // Объединение - машинка
        private var car:Compound;
 
        public function Main():void
        {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
        {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 900);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
                stage.color);
   
            //Рисуем Ограничения
            debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
        {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false), Material.ice()));
            
            floor.position.setxy(340, 300);
            floor.space = space;  
   
            // Объединение - машина
            car = new Compound();
            //car.cbTypes.add(carType);
      
            // Колесо переднее
            wheel_front = new Body(BodyType.DYNAMIC);
            wheel_front.position.setxy(290, 106);
            var wheel_frontShape:Shape = new Circle(30, null, Material.rubber());
            wheel_frontShape.body = wheel_front; 
            wheel_front.angularVel = 20;
            wheel_front.compound = car;
   
            // Колесо заднее
            wheel_rear = new Body(BodyType.DYNAMIC);
            wheel_rear.position.setxy(200, 106);
            var wheel_rearShape:Shape = new Circle(30, null, Material.rubber());
            wheel_rearShape.body = wheel_rear;  
            wheel_rear.angularVel = 20;
            wheel_rear.compound = car;
   
            // Кузов
            cabin = new Body(BodyType.DYNAMIC);
            cabin.position.setxy(245, 70);
            var cabinShape:Shape = new Polygon(Polygon.box(200, 60), new Material(0, 1, 2, 2, 0.01));
            cabinShape.body = cabin;
            cabin.compound = car;
   
            //Выводим машинку
            car.space = space;
   
            //Ограничение для колес (держит межколесное расстояние)
            wheels_joint = new DistanceJoint(wheel_front, wheel_rear, wheel_front.localCOM,
                wheel_rear.localCOM, 120, 120);
            wheels_joint.compound = car;
   
            //Передний амортизатор
            cabin_front_joint = new DistanceJoint(wheel_front, cabin,
                wheel_front.localCOM, cabin.localCOM, 90, 90);
            cabin_front_joint.compound = car;
   
            //Задний амортизатор
            cabin_rear_joint = new DistanceJoint(wheel_rear, cabin,
                wheel_rear.localCOM, cabin.localCOM, 90, 90);
            cabin_rear_joint.compound = car;
   
            //Выводим машинку со всеми ограничениями
            car.space = space;
        }
 
        private function enterFrameHandler(ev:Event):void 
        {
            space.step(1 / stage.frameRate);
   
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

      Реализацию этого примера можно посмотреть тут.

      При использовании объединений удобно их копировать:

var car2:Compound = car.copy();
car2.space = space;


Ограничения (Constraints) в Nape

      Ограничения в Nape связывают тела между собой. Любое ограничение можно отключить установив свойство ignore в true.
      Большинство типов Ограничений - это позиционные ограничения, такие как PivotJoint, который, учитывая две локальных позиции на Телах, блокирует эти точки вместе, что позволяет телам взаимно вращаться вокруг той точки. Некоторым ограничениям дают верхние и нижние пределы, например для DistanceJoint, который блокирует точки на двух Телах, чтобы тела были в данном диапазоне значений.
      Все позиционные ограничения могут быть установлены как жесткие (stiff) или эластичные (elastic). При использовании эластичных ограничений мы можем установить частоту и затухание для управления эластичным поведением, и можем установить maxForce и maxError, чтобы определить, сколько силы используется, и сколько ошибок мы решаем в секунду.


constraint.stiff = false;
constraint.frequency = 30; //Hz
constraint.damping = 10; //Large over-dampening

// Constraint will use no more than 1000 Newtons to resolve constraint.
constraint.maxForce = 1000;
// Constraint will resolve no more than 10 units of positional error per second.
constraint.maxError = 10;
// The exact behaviour of maxError is dependent on constraint formulation.


      Устанавливая надлежащие свойства, мы можем также разрешить разрыв Ограничения, если значение ошибки становится слишком большим, или для эластичных ограничений, если сила, требуемая, чтобы разорвать ограничение, становится слишком большой. С системой обратных вызовов мы можем получить обратный вызов, когда ограничение разорвано.


constraint.breakUnderError = true;
constraint.maxError = 10; // If constraint exceeds 10 units of error, constraint will break.
constraint.removeOnBreak = false; // Instead of removing constraint, it will simply be de-activated when broken.


      В отличие от других физических движков, в Nape если удалить Тело, то любые Ограничения, которые использовали Тело в Пространстве не будут удалены автоматически. Это сделано для того, что бы можно было свободно изменять Тела, на которые действует Ограничение.

     В Nape есть 7 предопределенных ограничений и плюс "Пользовательское ограничение"

  • PivotJoint
  • WeldJoint
  • DistanceJoint
  • AngleJoint
  • LineJoint
  • MotorJoint
  • PulleyJoint
  • UserConstraint (Пользовательское ограничение)
     Рассмотрим подробнее использование DistanceJoint на взаимодействии Шарика и Кубика из прошлого примера:

package
{ 
    import flash.display.Sprite;
    import flash.events.Event;
    import nape.constraint.DistanceJoint;
    import nape.phys.Compound;
    import nape.phys.Material;
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
  
        private var ball:Body;
        private var cube:Body;
  
        //Ограничение для Шара и Кубика
        private var joint:DistanceJoint;

 
        public function Main():void
        {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
        {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 900);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
                stage.color);
   
            //Отображать Ограничения
            debug.drawConstraints = true;
            addChild(debug.display);
   
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
 
        private function setUp():void
        {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
   
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
                new Material(0, 0.1, 0.1, 100, 0.0001)));
            
            floor.position.setxy(300, 300);
            floor.space = space;  
   
            ball = new Body(BodyType.DYNAMIC);
            ball.shapes.add(new Circle(30, null, new Material(0, 3, 3, 3, 30)));
            ball.position.setxy(200, 176);
            ball.angularVel = 55;
            ball.space = space;
   
            cube = new Body(BodyType.DYNAMIC);
            cube.shapes.add(new Polygon(Polygon.box(60, 60),
                new Material(0, 0.05, 0.05, 2, 0.01)));
            cube.position.setxy(100, 220);
            cube.space = space;
   
            //Ограничение для шара и кубика
            joint = new DistanceJoint(ball, cube, ball.localCOM,
                cube.worldPointToLocal(Vec2.get(cube.position.x + 100,
                    cube.position.y - 10)), 50, 90);
            
            //joint.stiff = false;
            joint.space = space;
        }
 
        private function enterFrameHandler(ev:Event):void 
        {
            space.step(1 / stage.frameRate);
 
            joint.anchor1 = ball.localCOM;
            joint.anchor2 = cube.localCOM +
                cube.worldPointToLocal(Vec2.get(cube.position.x + 100,
                    cube.position.y - 10));
   
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

      Работу этого примера можно посмотреть тут.

      Подробнее про типы ограничений можно посмотреть на примере.

Взаимодействия объектов в Nape

      У физического движка Nape есть 3 типа взаимодействия объектов:

  • Столкновение (Collision) - это взаимодействие по умолчанию, которое возникает когда две Фигуры пересекаются и могут столкнуться. Сталкивание рассчитывается в зависимости от Материалов Фигур.
  • Датчик (Sensor) - с этим типом взаимодействия две пересекающихся фигуры пройдут друг через друга. Арбитры объектов все еще будут созданы и вы все еще будете получать обратные вызовы (callbacks).
  • Жидкость (Fluid) - этот тип взаимодействия отвечает в Nape за плавучесть. Когда две Фигуры пересекаются, на Тела начинает действовать архимедова сила (свойство Фигур FluidProperties fluidEnabled).
      То, какой из типов взаимодействия произойдет, зависит от фильтра IntercationFilters и флагов Фигур sensorEnabled и fluidEnabled.
      Два статических тела не могут иметь какого либо взаимодействия, за исключением взаимодействия Датчик, по крайней мере хотя бы одно тело должно быть динамическим. Это означает, что единственное взаимодействие, которое может произойти между двумя кинематическими телами - это взаимодействие Датчик.
      Взаимодействия Датчик наступают первыми, затем следуют взаимодействия Жидкость и уже после - взаимодействия Столкновений.
      При использовании InteractionFilters, фильтры определяют группу и маску и два фильтра "соглашаются" взаимодействовать если:

(filterA.group & filterB.mask != 0) && (filterB.group & filterA.mask != 0)

      В Nape есть понятие "На шаг вперед", что позволяет иметь отдельную группу и маски для каждого типа взаимодействия. Благодаря этому расчет взаимодействий осуществляется более точно.
      Рассмотрим понятие группа/маска на примере 3-х типов объектов: Ящики(Boxes), Шары(Balls) и Пончики(Donuts). При этом условимся, что Шары не должны соприкасаться с Ящиками и Пончиками, а Пончики не должны сталкиваться с другими Пончиками.

BallsBoxesDonuts
BallsYesNoNo
BoxesNoYesYes
DonutsNoYesNo


      Группы для фильтров назначаются так:


ballFilter    .collisionGroup = 1; // in binary this is 0000...00001
boxFilter     .collisionGroup = 2; // in binary this is 0000...00010
doughnutFilter.collisionGroup = 4; // in binary this is 0000...00100


      Теперь можно задать маску фильтрам согласно таблице:


ballFilter    .collisionMask = ~(2|4); // in binary this is 1111...11001
boxFilter     .collisionMask = ~1;     // in binary this is 1111...11110
doughnutFilter.collisionMask = ~(1|4); // in binary this is 1111...11010


      Столкновение срабатывает, когда у группы и маски бит = 1.

      Свойство InteractionGroups обеспечивает дополнительное управление взаимодействиями между объектами. InteractionGroups может быть сформировано в деревья и назначено Фигурам, Телам и Объединениям (Составным телам).
      Прежде две Фигуры начнут взаимодействовать, мы ищем общего предка в дереве InteractionGroup; и если эта группа существует и установлено проигнорировать взаимодействия, то эти две Фигуры не будет взаимодействовать.

     Рассмотрим на примере следующего дерева:

физический движок Nape - InteractionGroups
     В этом примере у нас есть группа G1, присвоенная телу B1, группа G2, присвоенная фигуре S2 и объединению C1, и имеет G1 как его родителя. Группа G3 присвоена объединению C2. Мы можем сжать этот пример (правый рисунок), чтобы увидеть ближайший InteractionGroup.
      В этом примере, взглянем на фигуры S2 и S3 - если в G2 установлено использовать взаимодействия, то S2 и S3 будут взаимодействовать. Если в G1 будет установлено игнорировать взаимодействия, то S1 и S2 не будут взаимодействовать.
     

понедельник, 24 декабря 2012 г.

Параметры объектов Nape Продолжение

      Продолжим изучать возможности физического движка Nape на основании предыдущего примера.
      Тела в Nape всегда вращаются вокруг их центра, независимо от того где их центр масс находится. Поэтому важно, чтобы у динамических тел был свой центр масс, который находится в их локальных координатах (0, 0).
      У объектов Тел есть методы перевода Фигур в локальную систему координат, и наоборот метод переноса центра масс в координаты источника. (напр. localPointToWorld и worldPointToLocal).
      Фигурам Тел можно назначить материал (Material), который определяет коэффициенты динамического, статического трения (friction) и трения качения, а также эластичность и плотность. Плотность (density) используется в вычислениях массы, момента инерции и центра масс Тела. Другие параметры в совокупности участвуют в вычислении при контактах Тел и других вычислениях.
      Статическое и динамические трение это некая сила, которая проявляется при взаимодействии двух материалов. Статическое трение происходит между двумя не двигающимися объектами. Оно выражается как сила, которая предотвращает скольжение одного объекта на другом. Например, есть у вас блок который лежит на наклонной поверхности. Сила которая не дает блоку скользить по этой поверхности и есть выражение статического трения.
      Динамическое трение происходит между двумя объектами, которые двигаются. Это сила, которая затрудняет толкание тяжелой коробки по полу. Другими словами, это сила, которая пытается вернуть двигающийся объект в состояние покоя.
      Еще как пример этих двух сил можно привести такой пример. Есть тяжелая коробка, которая лежит на какой то поверхности. Она находится в состоянии покоя и ее пытаются сдвинуть. Как раз в этот момент и действует статическая сила и обычно надо приложить больше сил (чем при динамическом трении), чтобы победить статическое трение и заставить коробку двигаться.
      А когда коробка начала двигаться теперь в работу вступает динамическое трение, которое пытается вернуть ее в состояние покоя. В этом случае надо прилагать гораздо меньше усилий, чтобы поддерживать объект в движущемся состоянии. Надо помнить, что статическое трение всегда выше динамического.

      В Nape материал Фигур Тел можно менять в любое время, даже для статических Тел. Для нескольких типов материалов, таких как сталь, лед, резина и др. есть предустановленные значения. Их параметры можно посмотреть в API Nape.
      Пример назначения материала:

// Устанавливаем индивидуальные значения
circleShape.material.elasticity = 1;
circleShape.material.density = 4;

// Устанавливаем материал - резина
circleShape.material = Material.rubber();

Рассмотрим подробнее конструктор материала:


Constructor

function new(elasticity:Float = 0.0, dynamicFriction:Float = 1.0, staticFriction:Float = 2.0, density:Float = 1.0, rollingFriction:Float = 0.001)
- Construct a new Material object.
parameters
elasticity :: The coeffecient of elasticity for material.
dynamicFriction :: The coeffecient of dynamic friction for material.
staticFriction :: The coeffecient of static friction for material.
density :: The density of the shape using this material in units of g/pixel/pixel.
rollingFriction :: The coeffecient of rolling friction for material used in circle friction computations.
returns
The constructed Material object.

      Первым параметром задаем коэффициент эластичности материала. Его можно задать любым значением, даже очень большим. Коэффициенты эластичности объединяются в среднее зафиксированное значение между [0, 1]. При 1 объекты не теряют свою энергию.
      Вторым параметром задаем коэффициент динамического трения материала. Задается от 0 до любого положительного значения, чем выше значение тем быстрее объект замедляется при скольжении.
      Третьим параметром задается коэффициент статического трения. Может иметь значение от 0 и выше. Чем выше значение тем быстрее объект остановится и тяжелее его будет сдвинуть.
      Четвертым параметром задается плотность, которая измеряется в граммах/пиксель в квадрате. Чем выше плотность, тем больше масса тела.
      Пятым параметром задается трение кручения. Может иметь значение от 0 и выше, чем выше значение, тем быстрее крутящийся объект остановится при касании.
   
      Рассмотрим пример взаимодействия объектов разных материалов:

Кручение резинового шарика по резиновой поверхности:

package
{ 
    import flash.display.Sprite;
    import flash.events.Event;
    import nape.phys.Material;
 
    import nape.geom.Vec2;
    import nape.phys.Body;
    import nape.phys.BodyType;
    import nape.shape.Circle;
    import nape.shape.Polygon;
    import nape.space.Space;
    import nape.util.BitmapDebug;
    import nape.util.Debug;
 
    public class Main extends Sprite
    {
        private var space:Space;
        private var debug:Debug;
 
        public function Main():void
        {
            super();
 
            if (stage != null) {
                initialise(null);
            }
            else {
                addEventListener(Event.ADDED_TO_STAGE, initialise);
            }
        }
 
        private function initialise(ev:Event):void
        {
            if (ev != null) {
                removeEventListener(Event.ADDED_TO_STAGE, initialise);
            }
 
            var gravity:Vec2 = Vec2.weak(0, 400);
            space = new Space(gravity);
 
            debug = new BitmapDebug(stage.stageWidth, stage.stageHeight,
                stage.color);
            addChild(debug.display);
 
            setUp();
 
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
            }
 
        private function setUp():void
        {
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
 
            // Пол
            var floor:Body = new Body(BodyType.STATIC);
            floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
                Material.rubber()));
            
            floor.position.setxy(300, 300);
            floor.space = space;
 
            // Создаем динамический крутящийся шар посредством Circle с радиусом 30px
            // Установим  шару угловую скорость 50, когда он соприкоснется с полом
            // покатится вперед
            var ball:Body = new Body(BodyType.DYNAMIC);
            ball.shapes.add(new Circle(30, null, Material.rubber()));
            ball.position.setxy(80, 200);
            ball.angularVel = 50;
            ball.space = space;
        }
 
        private function enterFrameHandler(ev:Event):void 
        {
            space.step(1 / stage.frameRate);
 
            debug.clear();
            debug.draw(space);
            debug.flush();
        }
    }
}

      Тут можно посмотреть как взаимодействует резина с резиной. Если заменить материал пола на лед:

floor.shapes.add(new Polygon(Polygon.regular(320, 100, 6, 0, false),
    Material.ice()));
      То взаимодействие уже такое.