Iavra

Particle Engine - Zones

Jan 28th, 2016 (edited)
268
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*:
  2.  * @plugindesc v1.00 Extension, that comes with predefined zones for use by some behaviours.
  3.  * @author Iavra
  4.  *
  5.  * @help
  6.  * To use one of the zones included in this plugin, provide it as an argument to behaviours requiring a zone argument:
  7.  *
  8.  * new IAVRA.PARTICLE.B.Position(new IAVRA.PARTICLE.Z.Point(100, 100));
  9.  *
  10.  * This example will create a "Position" behaviour with a "Point" zone, that will cause particles to spawn at the
  11.  * point (100, 100).
  12.  *
  13.  * The following zones are contained in the IAVRA.PARTICLE.Z / IAVRA.PARTICLE.ZONE namespace:
  14.  *
  15.  * Multi(...zone)                             Can be used to merge multiple zones, which are weighted depending on
  16.  *                                            their area.
  17.  *
  18.  * Point(x, y)                                Defines a single point at the position (x, y).
  19.  *
  20.  * Line(x1, y1, x2, y2)                       Creates a line from the start point (x1, y1) to the end point (x2, y2).
  21.  *
  22.  * Circle(center, outerRadius, innerRadius)   Creates a center around the given center point with the given radius.
  23.  *                                            The "innerRadius" parameter can be used to turn the circle into a ring.
  24.  *
  25.  * Rect(x, y, width, height)                  Defines a rectangle parallel to the axes with the given upper left edge
  26.  *                                            (x, y), as well as the given width an height.
  27.  *
  28.  * Image(imageData, x, y, skip)               Creates a zone, that contains all non-transparent pixels from the given
  29.  *                                            image data, offset by the given x and y values, The parameter skip can
  30.  *                                            be used to skip pixels, which speeds up the creation of this zone.
  31.  *
  32.  * Regarding the Image zone, there are multiple ways to aquire an image data object. The following describes how to do
  33.  * it using PIXI.RenderTexture:
  34.  *
  35.  * var sprite = PIXI.Sprite.fromImage('img/imageToBeUsed.png');  // Make sure, that this is loaded, before continuing.
  36.  * var render = new PIXI.RenderTexture(sprite.width, sprite.height);
  37.  * var imageData = render.getCanvas.getContext('2d').getImageData(0, 0, render.width, render.height);
  38.  */
  39.  
  40. (function($, undefined) {
  41.     "use strict";
  42.  
  43.     if(!$.PARTICLE) { throw new Error("This plugin depends on 'Iavra Particle - Core'."); }
  44.  
  45.     /**
  46.      * Extends a given object with all properties from another given object. Mainly used to simplify subclassing.
  47.      * @param {Object} base - Base object to be extended.
  48.      * @param {Object} extend - Object containing the properties to be appended to the given base object.
  49.      * @returns {Object} The base object, after it has been extended.
  50.      */
  51.     var _extend = function(base, extend) {
  52.         for(var key in extend) { base[key] = extend[key]; }
  53.         return base;
  54.     };
  55.  
  56.     /**
  57.      * Returns a random floating point number between the given minimum (inclusive) and maximum (exclusive).
  58.      * @param {number} min - Minimum number to be generated (inclusive).
  59.      * @param {number} max - Maximum number to be generated (exclusive).
  60.      * @returns {number} A random number between the given minimum and maximum numbers.
  61.      */
  62.     var _random = function(min, max) {
  63.         return min + (max - min) * Math.random();
  64.     };
  65.  
  66.     /**
  67.      * Constructs a polar vector with the given radius and angle (in radians) and returns its cartesian representation.
  68.      * @param {number} radius - Radius component of the polar vector, which defines the resulting vector's length.
  69.      * @param {number} angle - Angle component of the polar vector, in radians.
  70.      * @returns {number[]} An array containing the cartesian representation of the polar vector.
  71.      */
  72.     var _polarVector = function(radius, angle) {
  73.         return [radius * Math.cos(angle), -radius * Math.sin(angle)]
  74.     };
  75.  
  76.     //=============================================================================
  77.     // IAVRA.PARTICLE.ZONE
  78.     //=============================================================================
  79.  
  80.     $.PARTICLE.ZONE = $.PARTICLE.Z = {
  81.         Multi: function(zone) { this._initialize.apply(this, arguments); },
  82.         Point: function(x, y) { this._initialize(x, y); },
  83.         Line: function(x1, y1, x2, y2) { this._initialize(x1, y1, x2, y2); },
  84.         Circle: function(center, outerRadius, innerRadius) { this._initialize(center, outerRadius, innerRadius); },
  85.         Rect: function(x, y, width, height) { this._initialize(x, y, width, height); },
  86.         Image: function(imageData, x, y, skip) { this._initialize(imageData, x, y, skip); }
  87.     };
  88.  
  89.     //=============================================================================
  90.     // IAVRA.PARTICLE.ZONE.Multi
  91.     //=============================================================================
  92.  
  93.     /**
  94.      * A Multi zone contains of multiple zones, which are balanced according to their area, when determining a random
  95.      * point inside of them.
  96.      */
  97.     $.PARTICLE.ZONE.Multi.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  98.  
  99.         /**
  100.          * Initializes the zone.
  101.          * @param {...IAVRA.PARTICLE.Zone} zone - One of more zones to be used inside the multi zone.
  102.          */
  103.         _initialize: function(zone) {
  104.             this._zones = Array.prototype.slice.call(arguments);
  105.             this.refresh();
  106.         },
  107.  
  108.         /**
  109.          * Needs to be called every time one of the supplied zone's area changes, since it's cached.
  110.          */
  111.         refresh: function() {
  112.             this._areas = [];
  113.             this._totalArea = 0;
  114.             for(var i = 0, max = this._zones.length, area; i < max; ++i) {
  115.                 area = this._zones[i].getArea();
  116.                 this._areas.push(area);
  117.                 this._totalArea += area;
  118.             }
  119.         },
  120.  
  121.         /**
  122.          * Tests, whether a given point is located inside the zone.
  123.          * @param {number} x - X coordinate of the point to be tested.
  124.          * @param {number} y - Y coordinate of the point to be tested.
  125.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  126.          */
  127.         contains: function(x, y) {
  128.             for(var i = 0, max = this._zones.length; i < max; ++i) {
  129.                 if(this._zones[i].contains(x, y)) { return true; }
  130.             }
  131.             return false;
  132.         },
  133.  
  134.         /**
  135.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  136.          * @returns {PIXI.Point} A random point inside this zone.
  137.          */
  138.         getPoint: function() {
  139.             var select = Math.random() * this._totalArea;
  140.             for(var i = 0, max = this._zones.length; i < max; ++i) {
  141.                 if((select -= this._areas[i]) <= 0) { return this._zones[i].getPoint(); }
  142.             }
  143.             return this._zones.length ? this._zones[0].getPoint() : new PIXI.Point();
  144.         },
  145.  
  146.         /**
  147.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  148.          * @returns {number} This zone's area.
  149.          */
  150.         getArea: function() {
  151.             return this._totalArea;
  152.         },
  153.  
  154.         /**
  155.          * Handles collision between the given particle and this zone.
  156.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  157.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  158.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  159.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  160.          */
  161.         collide: function(particle, bounce) {
  162.             var collide = false;
  163.             for(var i = 0, max = this._zones.length; i < max; ++i) {
  164.                 collide = this._zones[i].collide(particle, bounce) || collide;
  165.             }
  166.             return collide;
  167.         }
  168.  
  169.     });
  170.  
  171.     //=============================================================================
  172.     // IAVRA.PARTICLE.ZONE.Point
  173.     //=============================================================================
  174.  
  175.     /**
  176.      * A Point zone describes a single point and is mostly used to create particles there. The internal point can be
  177.      * changed, which effectively moves the "emitter".
  178.      */
  179.     $.PARTICLE.ZONE.Point.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  180.  
  181.         /**
  182.          * Initializes the zone.
  183.          * @param {number} [x=0] - X coordinate of the point.
  184.          * @param {number} [y=0] - Y coordinate of the point.
  185.          */
  186.         _initialize: function(x, y) {
  187.             this._point = new PIXI.Point(x, y);
  188.         },
  189.  
  190.         /**
  191.          * Tests, whether a given point is located inside the zone.
  192.          * @param {number} x - X coordinate of the point to be tested.
  193.          * @param {number} y - Y coordinate of the point to be tested.
  194.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  195.          */
  196.         contains: function(x, y) {
  197.             return x === this._point.x && y === this._point.y;
  198.         },
  199.  
  200.         /**
  201.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  202.          * This also doubles, as the returned point is the internal representation of this zone, so it can be changed
  203.          * to effectively move the zone around.
  204.          * @returns {PIXI.Point} A random point inside this zone.
  205.          */
  206.         getPoint: function() {
  207.             return this._point;
  208.         },
  209.  
  210.         /**
  211.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  212.          * @returns {number} This zone's area.
  213.          */
  214.         getArea: function() {
  215.             return 1;
  216.         },
  217.  
  218.         /**
  219.          * Handles collision between the given particle and this zone.
  220.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  221.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  222.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  223.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  224.          */
  225.         collide: function(particle, bounce) {
  226.             var relPrevX = particle.oldPosition.x - this._point.x, relPrevY = particle.oldPosition.y - this._point.y;
  227.             var dot = relPrevX * particle.velocity.x + relPrevY * particle.velocity.y;
  228.             if(dot >= 0) { return false; }
  229.             var relX = particle.position.x - this._point.x, relY = particle.position.y - this._point.y;
  230.             var radius = particle.radius * particle.scale.x;
  231.             dot = relX * particle.velocity.x + relY * particle.velocity.y;
  232.             if(dot <= 0) {
  233.                 if(relX > radius || relX < -radius) { return false; }
  234.                 if(relY > radius || relY < -radius) { return false; }
  235.                 if(relX * relX + relY * relY > radius * radius) { return false; }
  236.             }
  237.             var frameVelX = relX - relPrevX, frameVelY = relY - relPrevY;
  238.             var a = frameVelX * frameVelX + frameVelY * frameVelY;
  239.             var b = 2 * (relPrevX * frameVelX + relPrevY * frameVelY);
  240.             var c = relPrevX * relPrevX + relPrevY * relPrevY - radius * radius;
  241.             var sq= b * b - 4 * a * c;
  242.             if(sq < 0) { return false; }
  243.             var srt = Math.sqrt(sq), t1 = (-b + srt) / (2 * a), t2 = (-b - srt) / (2 * a), t = [];
  244.             if(t1 > 0 && t1 <= 1) { t.push(t1); }
  245.             if(t2 > 0 && t2 <= 1) { t.push(t2); }
  246.             if(!t.length) { return false; }
  247.             var time = (t.length === 1) ? t[0] : Math.min(t1, t2);
  248.             var cx = relPrevX + time * frameVelX + this._point.x, cy = relPrevY + time * frameVelY + this._point.y;
  249.             var nx = cx - this._point.x, ny = cy - this._point.y, d = Math.sqrt(nx * nx + ny * ny);
  250.             nx /= d;
  251.             ny /= d;
  252.             var n = frameVelX * nx + frameVelY * ny;
  253.             frameVelX -= 2 * nx * n;
  254.             frameVelY -= 2 * ny * n;
  255.             particle.position.x = cx + (1 - time) * frameVelX;
  256.             particle.position.y = cy + (1 - time) * frameVelY;
  257.             var normalVel = particle.velocity.x * nx + particle.velocity.y * ny;
  258.             particle.velocity.x -= (1 + bounce) * nx * normalVel;
  259.             particle.velocity.y -= (1 + bounce) * ny * normalVel;
  260.             return true;
  261.         }
  262.  
  263.     });
  264.  
  265.     //=============================================================================
  266.     // IAVRA.PARTICLE.ZONE.Line
  267.     //=============================================================================
  268.  
  269.     /**
  270.      * A Line zone describes a line from a given start to end point.
  271.      */
  272.     $.PARTICLE.ZONE.Line.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  273.  
  274.         /**
  275.          * Initializes the zone.
  276.          * @param {number} x1 - X coordinate of the line's starting point.
  277.          * @param {number} y1 - Y coordinate of the line's starting point.
  278.          * @param {number} x2 - X coordinate of the line's end point.
  279.          * @param {number} y2 - Y coordinate of the line's end point.
  280.          */
  281.         _initialize: function(x1, y1, x2, y2) {
  282.             this._start = new PIXI.Point(x1, y1);
  283.             this._end = new PIXI.Point(x2, y2);
  284.             this._length = new PIXI.Point(this._end.x - this._start.x, this._end.y - this._start.y);
  285.             var length = Math.sqrt(this._length.x * this._length.x + this._length.y * this._length.y);
  286.             this._parallel = new PIXI.Point(this._length.x / length, this._length.y / length);
  287.             this._normal = new PIXI.Point(this._parallel.y, -this._parallel.x);
  288.  
  289.         },
  290.  
  291.         /**
  292.          * Tests, whether a given point is located inside the zone.
  293.          * @param {number} x - X coordinate of the point to be tested.
  294.          * @param {number} y - Y coordinate of the point to be tested.
  295.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  296.          */
  297.         contains: function(x, y) {
  298.             if ((x - this._start.x) * this._length.y - (y - this._start.y) * this._length.x !== 0) { return false; }
  299.             return (x - this._start.x ) * (x - this._end.x) + (y - this._start.y) * (y - this._end.y) <= 0;
  300.         },
  301.  
  302.         /**
  303.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  304.          * @returns {PIXI.Point} A random point inside this zone.
  305.          */
  306.         getPoint: function() {
  307.             var scale = Math.random();
  308.             return new PIXI.Point(this._start.x + this._length.x * scale, this._start.y + this._length.y * scale);
  309.         },
  310.  
  311.         /**
  312.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  313.          * @returns {number} This zone's area.
  314.          */
  315.         getArea: function() {
  316.             return Math.sqrt(this._length.x * this._length.x + this._length.y + this._length.y);
  317.         },
  318.  
  319.         /**
  320.          * Handles collision between the given particle and this zone.
  321.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  322.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  323.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  324.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  325.          */
  326.         collide: function(particle, bounce) {
  327.             var radius = particle.radius * particle.scale.x;
  328.             var prevDist = (particle.oldPosition.x - this._start.x) * this._normal.x +
  329.                 (particle.oldPosition.y - this._start.y) * this._normal.y;
  330.             var velDist = particle.velocity.x * this._normal.x + particle.velocity.y * this._normal.y;
  331.             if(prevDist * velDist >= 0) { return false; }
  332.             var distance = (particle.position.x - this._start.x) * this._normal.x +
  333.                 (particle.position.y - this._start.y) * this._normal.y;
  334.             if(distance * prevDist > 0 && (distance > radius || distance < -radius)) { return false; }
  335.             var offsetX = this._normal.x * radius, offsetY = this._normal.y * radius;
  336.             if(prevDist >= 0) {
  337.                 offsetX *= -1;
  338.                 offsetY *= -1;
  339.             }
  340.             var thenX = particle.oldPosition.x + offsetX, thenY = particle.oldPosition.y + offsetY;
  341.             var nowX = particle.position.x + offsetX, nowY = particle.position.y + offsetY;
  342.             var startX = this._start.x - this._parallel.x * radius, startY = this._start.y - this._parallel.y * radius;
  343.             var endX = this._end.x + this._parallel.x * radius, endY = this._end.y + this._parallel.y * radius;
  344.             var den = 1 / ((nowY - thenY) * (endX - startX) - (nowX - thenX) * (endY - startY));
  345.             var u = den * ((nowX - thenX) * (startY - thenY) - (nowY - thenY) * (startX - thenX));
  346.             if(u < 0 || u > 1) { return false; }
  347.             var v = -den * ((endX - startX) * (thenY - startY) - (endY - startY) * (thenX - startX));
  348.             if(v < 0 || v > 1) { return false; }
  349.             particle.position.x = particle.oldPosition.x + v * (particle.position.x - particle.oldPosition.x);
  350.             particle.position.y = particle.oldPosition.y + v * (particle.position.y - particle.oldPosition.y);
  351.             var normalSpeed = this._normal.x * particle.velocity.x + this._normal.y * particle.velocity.y;
  352.             var factor = (1 + bounce) * normalSpeed;
  353.             particle.velocity.x -= factor * this._normal.x;
  354.             particle.velocity.y -= factor * this._normal.y;
  355.             return true;
  356.         }
  357.  
  358.     });
  359.  
  360.     //=============================================================================
  361.     // IAVRA.PARTICLE.ZONE.Circle
  362.     //=============================================================================
  363.  
  364.     /**
  365.      * A Circle zone describes a circle around a given center with an optional inner radius to turn it into a ring.
  366.      */
  367.     $.PARTICLE.ZONE.Circle.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  368.  
  369.         /**
  370.          * Initializes the zone.
  371.          * @param {PIXI.Point} center - Center point of the circle. If this parameter is cached somewhere, it can be
  372.          * modified to move the circle around.
  373.          * @param {number} [outerRadius=0] - Radius of the circle.
  374.          * @param {number} [innerRadius=0] - Can be used to turn the circle in a ring. Can't be bigger than the
  375.          * outer radius.
  376.          */
  377.         _initialize: function(center, outerRadius, innerRadius) {
  378.             this._center = center;
  379.             this._outerRadius = outerRadius || 0;
  380.             this._innerRadius = innerRadius || 0;
  381.             if(this._outerRadius < this._innerRadius) { throw new Error("Invalid radius values."); }
  382.             this._outerSq = this._outerRadius * this._outerRadius;
  383.             this._innerSq = this._innerRadius * this._innerRadius;
  384.         },
  385.  
  386.         /**
  387.          * Tests, whether a given point is located inside the zone.
  388.          * @param {number} x - X coordinate of the point to be tested.
  389.          * @param {number} y - Y coordinate of the point to be tested.
  390.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  391.          */
  392.         contains: function(x, y) {
  393.             x -= this._center.x;
  394.             y -= this._center.y;
  395.             var distSq = x * x + y * y;
  396.             return distSq <= this._outerSq && distSq >= this._innerSq;
  397.         },
  398.  
  399.         /**
  400.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  401.          * @returns {PIXI.Point} A random point inside this zone.
  402.          */
  403.         getPoint: function() {
  404.             var rand = Math.random();
  405.             var vector = _polarVector(this._innerRadius + (1 - rand * rand) * (this._outerRadius - this._innerRadius),
  406.                 Math.random() * Math.PI * 2);
  407.             return new PIXI.Point(vector[0] + this._center.x, vector[1] + this._center.y);
  408.         },
  409.  
  410.         /**
  411.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  412.          * @returns {number} This zone's area.
  413.          */
  414.         getArea: function() {
  415.             return Math.PI * (this._outerSq - this._innerSq);
  416.         },
  417.  
  418.         /**
  419.          * Handles collision between the given particle and this zone.
  420.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  421.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  422.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  423.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  424.          */
  425.         collide: function(particle, bounce) {
  426.             var outerLimit, innerLimit, outerLimitSq, innerLimitSq, distanceSq, distance, pdx, pdy, pDistanceSq;
  427.             var adjustSpeed, positionRatio, epsilon = 0.001, radius = particle.radius * particle.scale.x;
  428.             var dx = particle.position.x - this._center.x, dy = particle.position.y - this._center.y;
  429.             var dot = particle.velocity.x * dx + particle.velocity.y * dy;
  430.             if(dot < 0) {
  431.                 outerLimit = this._outerRadius + radius;
  432.                 if(Math.abs(dx) > outerLimit || Math.abs(dy) > outerLimit) { return false; }
  433.                 distanceSq = dx * dx + dy * dy;
  434.                 outerLimitSq =  outerLimit * outerLimit;
  435.                 if(distanceSq > outerLimitSq) { return false; }
  436.                 pdx = particle.oldPosition.x - this._center.x, pdy = particle.oldPosition.y - this._center.y;
  437.                 pDistanceSq = pdx * pdx + pdy * pdy;
  438.                 if(pDistanceSq > outerLimitSq) {
  439.                     adjustSpeed = (1 + bounce) * dot / distanceSq;
  440.                     particle.velocity.x -= adjustSpeed * dx;
  441.                     particle.velocity.y -= adjustSpeed * dy;
  442.                     distance = Math.sqrt(distanceSq);
  443.                     positionRatio = (2 * outerLimit - distance) / distance + epsilon;
  444.                     particle.position.x = this._center.x + dx * positionRatio;
  445.                     particle.position.y = this._center.y + dy * positionRatio;
  446.                     return true;
  447.                 }
  448.                 if(this._innerRadius !== 0 && this._innerRadius !== this._outerRadius ) {
  449.                     innerLimit = this._innerRadius + radius;
  450.                     if(Math.abs(dx) > innerLimit || Math.abs(dy) > innerLimit) { return false; }
  451.                     innerLimitSq = innerLimit * innerLimit;
  452.                     if(distanceSq > innerLimitSq) { return false; }
  453.                     if(pDistanceSq > innerLimitSq) {
  454.                         adjustSpeed = (1 + bounce) * dot / distanceSq;
  455.                         particle.velocity.x -= adjustSpeed * dx;
  456.                         particle.velocity.y -= adjustSpeed * dy;
  457.                         distance = Math.sqrt(distanceSq);
  458.                         positionRatio = (2 * innerLimit - distance) / distance + epsilon;
  459.                         particle.position.x = this._center.x + dx * positionRatio;
  460.                         particle.position.y = this._center.y + dy * positionRatio;
  461.                         return true;
  462.                     }
  463.                 }
  464.                 return false;
  465.             } else {
  466.                 outerLimit = this._outerRadius - radius;
  467.                 pdx = particle.oldPosition.x - this._center.x, pdy = particle.oldPosition.y - this._center.y;
  468.                 if(Math.abs(pdx) > outerLimit || Math.abs(pdy) > outerLimit) { return false; }
  469.                 pDistanceSq = pdx * pdx + pdy * pdy;
  470.                 outerLimitSq = outerLimit * outerLimit;
  471.                 if(pDistanceSq > outerLimitSq) { return false; }
  472.                 distanceSq = dx * dx + dy * dy;
  473.                 if(this._innerRadius !== 0 && this._innerRadius !== this._outerRadius) {
  474.                     innerLimit = this._innerRadius - radius;
  475.                     innerLimitSq = innerLimit * innerLimit;
  476.                     if(pDistanceSq < innerLimitSq && distanceSq >= innerLimitSq) {
  477.                         adjustSpeed = (1 + bounce) * dot / distanceSq;
  478.                         particle.velocity.x -= adjustSpeed * dx;
  479.                         particle.velocity.y -= adjustSpeed * dy;
  480.                         distance = Math.sqrt(distanceSq);
  481.                         positionRatio = (2 * innerLimit - distance) / distance - epsilon;
  482.                         particle.position.x = this._center.x + dx * positionRatio;
  483.                         particle.position.y = this._center.y + dy * positionRatio;
  484.                         return true;
  485.                     }
  486.                 }
  487.                 if(distanceSq >= outerLimitSq) {
  488.                     adjustSpeed = (1 + bounce) * dot / distanceSq;
  489.                     particle.velocity.x -= adjustSpeed * dx;
  490.                     particle.velocity.y -= adjustSpeed * dy;
  491.                     distance = Math.sqrt(distanceSq);
  492.                     positionRatio = (2 * outerLimit - distance) / distance - epsilon;
  493.                     particle.position.x = this._center.x + dx * positionRatio;
  494.                     particle.position.y = this._center.y + dy * positionRatio;
  495.                     return true;
  496.                 }
  497.                 return false;
  498.             }
  499.             return false;
  500.         }
  501.  
  502.     });
  503.  
  504.     //=============================================================================
  505.     // IAVRA.PARTICLE.ZONE.Rect
  506.     //=============================================================================
  507.  
  508.     /**
  509.      * A Rect zone describes a rectangle parallel to the axes.
  510.      */
  511.     $.PARTICLE.ZONE.Rect.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  512.  
  513.         /**
  514.          * Initializes the zone.
  515.          * @param {number} x - X coordinate of the rect's upper left point.
  516.          * @param {number} y - X coordinate of the rect's upper left point.
  517.          * @param {number} width - Width of the rect.
  518.          * @param {number} height - Height of the rect.
  519.          */
  520.         _initialize: function(x, y, width, height) {
  521.             this._left = x;
  522.             this._top = y;
  523.             this._right = x + width;
  524.             this._bottom = y + height;
  525.             this._width = width;
  526.             this._height = height;
  527.         },
  528.  
  529.         /**
  530.          * Tests, whether a given point is located inside the zone.
  531.          * @param {number} x - X coordinate of the point to be tested.
  532.          * @param {number} y - Y coordinate of the point to be tested.
  533.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  534.          */
  535.         contains: function(x, y) {
  536.             return x >= this._left && x <= this._right && y >= this._top && y <= this._bottom;
  537.         },
  538.  
  539.         /**
  540.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  541.          * @returns {PIXI.Point} A random point inside this zone.
  542.          */
  543.         getPoint: function() {
  544.             return new PIXI.Point(this._left + Math.random() * this._width, this._top + Math.random() * this._height);
  545.         },
  546.  
  547.         /**
  548.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  549.          * @returns {number} This zone's area.
  550.          */
  551.         getArea: function() {
  552.             return this._width * this._height;
  553.         },
  554.  
  555.         /**
  556.          * Handles collision between the given particle and this zone.
  557.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  558.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  559.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  560.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  561.          */
  562.         collide: function(particle, bounce) {
  563.             var position, prevPosition, intersect, collision = false, radius = particle.radius * particle.scale.x;
  564.             if(particle.velocity.x > 0) {
  565.                 position = particle.position.x + radius, prevPosition = particle.oldPosition.x + radius;
  566.                 if(prevPosition < this._left && position >= this._left) {
  567.                     intersect = particle.oldPosition.y + (particle.position.y - particle.oldPosition.y) *
  568.                         (this._left - prevPosition) / (position - prevPosition);
  569.                     if(intersect >= this._top - radius && intersect <= this._bottom + radius) {
  570.                         particle.velocity.x *= -bounce;
  571.                         particle.position.x += 2 * (this._left - position);
  572.                         collision = true;
  573.                     }
  574.                 } else if(prevPosition <= this._right && position > this._right) {
  575.                     intersect = particle.oldPosition.y + (particle.position.y - particle.oldPosition.y) *
  576.                         (this._right - prevPosition) / (position - prevPosition);
  577.                     if(intersect >= this._top - radius && intersect <= this._bottom + radius) {
  578.                         particle.velocity.x *= -bounce;
  579.                         particle.position.x += 2 * (this._right - position);
  580.                         collision = true;
  581.                     }
  582.                 }
  583.             } else if (particle.velocity.x < 0) {
  584.                 position = particle.position.x - radius, prevPosition = particle.oldPosition.x - radius;
  585.                 if(prevPosition > this._right && position <= this._right) {
  586.                     intersect = particle.oldPosition.y + (particle.position.y - particle.oldPosition.y) *
  587.                         (this._right - prevPosition) / (position - prevPosition);
  588.                     if(intersect >= this._top - radius && intersect <= this._bottom + radius) {
  589.                         particle.velocity.x *= -bounce;
  590.                         particle.position.x += 2 * (this._right - position);
  591.                         collision = true;
  592.                     }
  593.                 } else if(prevPosition >= this._left && position < this._left) {
  594.                     intersect = particle.oldPosition.y + (particle.position.y - particle.oldPosition.y) *
  595.                         (this._left - prevPosition) / (position - prevPosition);
  596.                     if(intersect >= this._top - radius && intersect <= this._bottom + radius) {
  597.                         particle.velocity.x *= -bounce;
  598.                         particle.position.x += 2 * (this._left - position);
  599.                         collision = true;
  600.                     }
  601.                 }
  602.             }
  603.             if (particle.velocity.y > 0) {
  604.                 position = particle.position.y + radius, prevPosition = particle.oldPosition.y + radius;
  605.                 if(prevPosition < this._top && position >= this._top) {
  606.                     intersect = particle.oldPosition.x + (particle.position.x - particle.oldPosition.x) *
  607.                         (this._top - prevPosition) / (position - prevPosition);
  608.                     if(intersect >= this._left - radius && intersect <= this._right + radius) {
  609.                         particle.velocity.y *= -bounce;
  610.                         particle.position.y += 2 * (this._top - position);
  611.                         collision = true;
  612.                     }
  613.                 } else if(prevPosition <= this._bottom && position > this._bottom) {
  614.                     intersect = particle.oldPosition.x + (particle.position.x - particle.oldPosition.x) *
  615.                         (this._bottom - prevPosition) / (position - prevPosition);
  616.                     if(intersect >= this._left - radius && intersect <= this._right + radius) {
  617.                         particle.velocity.y *= -bounce;
  618.                         particle.position.y += 2 * (this._bottom - position);
  619.                         collision = true;
  620.                     }
  621.                 }
  622.             } else if (particle.velocity.y < 0) {
  623.                 position = particle.position.y - radius, prevPosition = particle.oldPosition.y - radius;
  624.                 if(prevPosition > this._bottom && position <= this._bottom) {
  625.                     intersect = particle.oldPosition.x + (particle.position.x - particle.oldPosition.x) *
  626.                         (this._bottom - prevPosition) / (position - prevPosition);
  627.                     if(intersect >= this._left - radius && intersect <= this._right + radius) {
  628.                         particle.velocity.y *= -bounce;
  629.                         particle.position.y += 2 * (this._bottom - position);
  630.                         collision = true;
  631.                     }
  632.                 } else if(prevPosition >= this._top && position < this._top) {
  633.                     intersect = particle.oldPosition.x + (particle.position.x - particle.oldPosition.x) *
  634.                         (this._top - prevPosition) / (position - prevPosition);
  635.                     if(intersect >= this._left - radius && intersect <= this._right + radius) {
  636.                         particle.velocity.y *= -bounce;
  637.                         particle.position.y += 2 * (this._top - position);
  638.                         collision = true;
  639.                     }
  640.                 }
  641.             }
  642.             return collision;
  643.         }
  644.  
  645.     });
  646.  
  647.     //=============================================================================
  648.     // IAVRA.PARTICLE.ZONE.Image
  649.     //=============================================================================
  650.  
  651.     /**
  652.      * An Image zone describes all non-transparent pixels inside the given image data.
  653.      */
  654.     $.PARTICLE.ZONE.Image.prototype = _extend(Object.create($.PARTICLE.Zone.prototype), {
  655.  
  656.         /**
  657.          * Initializes the zone.
  658.          * @param {ImageData} imageData - The data of a given image. How to aquire such an object is described in this
  659.          * plugin's help section.
  660.          * @param {number} [x=2] - X offset used for the zone.
  661.          * @param {number} [y=2] - Y offset used for the zone.
  662.          * @param {number} [skip=2] - Skips pixels, when reading image data. Using this greatly reduces the time needed
  663.          * to setup this zone. Shouldn't be set much higher than 10, or you will skip too many pixels or even run the
  664.          * risk to read outside the image data.
  665.          */
  666.         _initialize: function(imageData, x, y, skip) {
  667.             this._imageData = imageData;
  668.             this._x = x || 0;
  669.             this._y = y || 0;
  670.             this._skip = skip === undefined ? 2 : skip;
  671.             this._vectors = [];
  672.             this._setupVectors();
  673.         },
  674.  
  675.         /**
  676.          * Sets up the vectors used for this zone. This happens synchronous, but might take time, depending on the
  677.          * given image's size, so it might be a good idea to initialize the zone inside a scene's "create" function.
  678.          */
  679.         _setupVectors: function() {
  680.             for (var i = 0, width = this._imageData.width; i < width; i += this._skip) {
  681.                 for (var j = 0, height = this._imageData.height; j < height; j += this._skip) {
  682.                     if(this._imageData.data[(((j|0) * width + (i|0)) * 4) + 3] > 0) {
  683.                         this._vectors.push({ x: i + this._x, y: j + this._y });
  684.                     }
  685.                 }
  686.             }
  687.         },
  688.  
  689.         /**
  690.          * Tests, whether a given point is located inside the zone.
  691.          * @param {number} x - X coordinate of the point to be tested.
  692.          * @param {number} y - Y coordinate of the point to be tested.
  693.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  694.          */
  695.         contains: function(x, y) {
  696.             return this._imageData.data[(((y|0) * width + (x|0)) * 4) + 3] > 0;
  697.         },
  698.  
  699.         /**
  700.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  701.          * @returns {PIXI.Point} A random point inside this zone.
  702.          */
  703.         getPoint: function() {
  704.             return this._vectors[(this._vectors.length * Math.random())|0];
  705.         },
  706.  
  707.         /**
  708.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  709.          * @returns {number} This zone's area.
  710.          */
  711.         getArea: function() {
  712.             return this._vectors.length;
  713.         },
  714.  
  715.         /**
  716.          * Collision has been disabled for the Image zone, since it would be sketchy at best, seeing that it's
  717.          * impossible to account for all possible images.
  718.          */
  719.         collide: function(particle, bounce) {
  720.             throw new Error('Image zone doesn\'t support collision.');
  721.         }
  722.  
  723.     });
  724.  
  725. })(this.IAVRA || (this.IAVRA = {}));
Add Comment
Please, Sign In to add comment