Страницы

пятница, 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();
        }
    }
}

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

2 комментария:

  1. Анонимный5 мая 2015 г., 14:58

    похоже по nape на русском ваша инфа самая свежая. странно что блог непопулярен, совсем нет комментов. совсем чтоли as уже никто не учит :)
    не выводиться рисунок машинки. контуры есть а png нету. в чём дело подскажите?

    ОтветитьУдалить
  2. Анонимный8 мая 2015 г., 13:34

    а не,
    поспешил. такое же барахло как и всё остальное.
    но спсбо всеравно(если вообще тут ктото есть и заглядываешь), наводит на мысли, дополняет

    ОтветитьУдалить