Show:

File: src/factory/Bodies.js

                                /**
                                * The `Matter.Bodies` module contains factory methods for creating rigid body models 
                                * with commonly used body configurations (such as rectangles, circles and other polygons).
                                *
                                * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
                                *
                                * @class Bodies
                                */
                                
                                // TODO: true circle bodies
                                
                                var Bodies = {};
                                
                                module.exports = Bodies;
                                
                                var Vertices = require('../geometry/Vertices');
                                var Common = require('../core/Common');
                                var Body = require('../body/Body');
                                var Bounds = require('../geometry/Bounds');
                                var Vector = require('../geometry/Vector');
                                
                                (function() {
                                
                                    /**
                                     * Creates a new rigid body model with a rectangle hull. 
                                     * The options parameter is an object that specifies any properties you wish to override the defaults.
                                     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
                                     * @method rectangle
                                     * @param {number} x
                                     * @param {number} y
                                     * @param {number} width
                                     * @param {number} height
                                     * @param {object} [options]
                                     * @return {body} A new rectangle body
                                     */
                                    Bodies.rectangle = function(x, y, width, height, options) {
                                        options = options || {};
                                
                                        var rectangle = { 
                                            label: 'Rectangle Body',
                                            position: { x: x, y: y },
                                            vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
                                        };
                                
                                        if (options.chamfer) {
                                            var chamfer = options.chamfer;
                                            rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius, 
                                                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
                                            delete options.chamfer;
                                        }
                                
                                        return Body.create(Common.extend({}, rectangle, options));
                                    };
                                    
                                    /**
                                     * Creates a new rigid body model with a trapezoid hull. 
                                     * The `slope` is parameterised as a fraction of `width` and must be < 1 to form a valid trapezoid. 
                                     * The options parameter is an object that specifies any properties you wish to override the defaults.
                                     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
                                     * @method trapezoid
                                     * @param {number} x
                                     * @param {number} y
                                     * @param {number} width
                                     * @param {number} height
                                     * @param {number} slope Must be a number < 1.
                                     * @param {object} [options]
                                     * @return {body} A new trapezoid body
                                     */
                                    Bodies.trapezoid = function(x, y, width, height, slope, options) {
                                        options = options || {};
                                
                                        if (slope >= 1) {
                                            Common.warn('Bodies.trapezoid: slope parameter must be < 1.');
                                        }
                                
                                        slope *= 0.5;
                                        var roof = (1 - (slope * 2)) * width;
                                        
                                        var x1 = width * slope,
                                            x2 = x1 + roof,
                                            x3 = x2 + x1,
                                            verticesPath;
                                
                                        if (slope < 0.5) {
                                            verticesPath = 'L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
                                        } else {
                                            verticesPath = 'L 0 0 L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
                                        }
                                
                                        var trapezoid = { 
                                            label: 'Trapezoid Body',
                                            position: { x: x, y: y },
                                            vertices: Vertices.fromPath(verticesPath)
                                        };
                                
                                        if (options.chamfer) {
                                            var chamfer = options.chamfer;
                                            trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius, 
                                                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
                                            delete options.chamfer;
                                        }
                                
                                        return Body.create(Common.extend({}, trapezoid, options));
                                    };
                                
                                    /**
                                     * Creates a new rigid body model with a circle hull. 
                                     * The options parameter is an object that specifies any properties you wish to override the defaults.
                                     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
                                     * @method circle
                                     * @param {number} x
                                     * @param {number} y
                                     * @param {number} radius
                                     * @param {object} [options]
                                     * @param {number} [maxSides]
                                     * @return {body} A new circle body
                                     */
                                    Bodies.circle = function(x, y, radius, options, maxSides) {
                                        options = options || {};
                                
                                        var circle = {
                                            label: 'Circle Body',
                                            circleRadius: radius
                                        };
                                        
                                        // approximate circles with polygons until true circles implemented in SAT
                                        maxSides = maxSides || 25;
                                        var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));
                                
                                        // optimisation: always use even number of sides (half the number of unique axes)
                                        if (sides % 2 === 1)
                                            sides += 1;
                                
                                        return Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
                                    };
                                
                                    /**
                                     * Creates a new rigid body model with a regular polygon hull with the given number of sides. 
                                     * The options parameter is an object that specifies any properties you wish to override the defaults.
                                     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
                                     * @method polygon
                                     * @param {number} x
                                     * @param {number} y
                                     * @param {number} sides
                                     * @param {number} radius
                                     * @param {object} [options]
                                     * @return {body} A new regular polygon body
                                     */
                                    Bodies.polygon = function(x, y, sides, radius, options) {
                                        options = options || {};
                                
                                        if (sides < 3)
                                            return Bodies.circle(x, y, radius, options);
                                
                                        var theta = 2 * Math.PI / sides,
                                            path = '',
                                            offset = theta * 0.5;
                                
                                        for (var i = 0; i < sides; i += 1) {
                                            var angle = offset + (i * theta),
                                                xx = Math.cos(angle) * radius,
                                                yy = Math.sin(angle) * radius;
                                
                                            path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
                                        }
                                
                                        var polygon = { 
                                            label: 'Polygon Body',
                                            position: { x: x, y: y },
                                            vertices: Vertices.fromPath(path)
                                        };
                                
                                        if (options.chamfer) {
                                            var chamfer = options.chamfer;
                                            polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius, 
                                                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
                                            delete options.chamfer;
                                        }
                                
                                        return Body.create(Common.extend({}, polygon, options));
                                    };
                                
                                    /**
                                     * Utility to create a compound body based on set(s) of vertices.
                                     * 
                                     * _Note:_ To optionally enable automatic concave vertices decomposition the [poly-decomp](https://github.com/schteppe/poly-decomp.js) 
                                     * package must be first installed and provided see `Common.setDecomp`, otherwise the convex hull of each vertex set will be used.
                                     * 
                                     * The resulting vertices are reorientated about their centre of mass,
                                     * and offset such that `body.position` corresponds to this point.
                                     * 
                                     * The resulting offset may be found if needed by subtracting `body.bounds` from the original input bounds.
                                     * To later move the centre of mass see `Body.setCentre`.
                                     * 
                                     * Note that automatic conconcave decomposition results are not always optimal. 
                                     * For best results, simplify the input vertices as much as possible first.
                                     * By default this function applies some addtional simplification to help.
                                     * 
                                     * Some outputs may also require further manual processing afterwards to be robust.
                                     * In particular some parts may need to be overlapped to avoid collision gaps.
                                     * Thin parts and sharp points should be avoided or removed where possible.
                                     *
                                     * The options parameter object specifies any `Matter.Body` properties you wish to override the defaults.
                                     * 
                                     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
                                     * @method fromVertices
                                     * @param {number} x
                                     * @param {number} y
                                     * @param {array} vertexSets One or more arrays of vertex points e.g. `[[{ x: 0, y: 0 }...], ...]`.
                                     * @param {object} [options] The body options.
                                     * @param {bool} [flagInternal=false] Optionally marks internal edges with `isInternal`.
                                     * @param {number} [removeCollinear=0.01] Threshold when simplifying vertices along the same edge.
                                     * @param {number} [minimumArea=10] Threshold when removing small parts.
                                     * @param {number} [removeDuplicatePoints=0.01] Threshold when simplifying nearby vertices.
                                     * @return {body}
                                     */
                                    Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea, removeDuplicatePoints) {
                                        var decomp = Common.getDecomp(),
                                            canDecomp,
                                            body,
                                            parts,
                                            isConvex,
                                            isConcave,
                                            vertices,
                                            i,
                                            j,
                                            k,
                                            v,
                                            z;
                                
                                        // check decomp is as expected
                                        canDecomp = Boolean(decomp && decomp.quickDecomp);
                                
                                        options = options || {};
                                        parts = [];
                                
                                        flagInternal = typeof flagInternal !== 'undefined' ? flagInternal : false;
                                        removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : 0.01;
                                        minimumArea = typeof minimumArea !== 'undefined' ? minimumArea : 10;
                                        removeDuplicatePoints = typeof removeDuplicatePoints !== 'undefined' ? removeDuplicatePoints : 0.01;
                                
                                        // ensure vertexSets is an array of arrays
                                        if (!Common.isArray(vertexSets[0])) {
                                            vertexSets = [vertexSets];
                                        }
                                
                                        for (v = 0; v < vertexSets.length; v += 1) {
                                            vertices = vertexSets[v];
                                            isConvex = Vertices.isConvex(vertices);
                                            isConcave = !isConvex;
                                
                                            if (isConcave && !canDecomp) {
                                                Common.warnOnce(
                                                    'Bodies.fromVertices: Install the \'poly-decomp\' library and use Common.setDecomp or provide \'decomp\' as a global to decompose concave vertices.'
                                                );
                                            }
                                
                                            if (isConvex || !canDecomp) {
                                                if (isConvex) {
                                                    vertices = Vertices.clockwiseSort(vertices);
                                                } else {
                                                    // fallback to convex hull when decomposition is not possible
                                                    vertices = Vertices.hull(vertices);
                                                }
                                
                                                parts.push({
                                                    position: { x: x, y: y },
                                                    vertices: vertices
                                                });
                                            } else {
                                                // initialise a decomposition
                                                var concave = vertices.map(function(vertex) {
                                                    return [vertex.x, vertex.y];
                                                });
                                
                                                // vertices are concave and simple, we can decompose into parts
                                                decomp.makeCCW(concave);
                                                if (removeCollinear !== false)
                                                    decomp.removeCollinearPoints(concave, removeCollinear);
                                                if (removeDuplicatePoints !== false && decomp.removeDuplicatePoints)
                                                    decomp.removeDuplicatePoints(concave, removeDuplicatePoints);
                                
                                                // use the quick decomposition algorithm (Bayazit)
                                                var decomposed = decomp.quickDecomp(concave);
                                
                                                // for each decomposed chunk
                                                for (i = 0; i < decomposed.length; i++) {
                                                    var chunk = decomposed[i];
                                
                                                    // convert vertices into the correct structure
                                                    var chunkVertices = chunk.map(function(vertices) {
                                                        return {
                                                            x: vertices[0],
                                                            y: vertices[1]
                                                        };
                                                    });
                                
                                                    // skip small chunks
                                                    if (minimumArea > 0 && Vertices.area(chunkVertices) < minimumArea)
                                                        continue;
                                
                                                    // create a compound part
                                                    parts.push({
                                                        position: Vertices.centre(chunkVertices),
                                                        vertices: chunkVertices
                                                    });
                                                }
                                            }
                                        }
                                
                                        // create body parts
                                        for (i = 0; i < parts.length; i++) {
                                            parts[i] = Body.create(Common.extend(parts[i], options));
                                        }
                                
                                        // flag internal edges (coincident part edges)
                                        if (flagInternal) {
                                            var coincident_max_dist = 5;
                                
                                            for (i = 0; i < parts.length; i++) {
                                                var partA = parts[i];
                                
                                                for (j = i + 1; j < parts.length; j++) {
                                                    var partB = parts[j];
                                
                                                    if (Bounds.overlaps(partA.bounds, partB.bounds)) {
                                                        var pav = partA.vertices,
                                                            pbv = partB.vertices;
                                
                                                        // iterate vertices of both parts
                                                        for (k = 0; k < partA.vertices.length; k++) {
                                                            for (z = 0; z < partB.vertices.length; z++) {
                                                                // find distances between the vertices
                                                                var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])),
                                                                    db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length]));
                                
                                                                // if both vertices are very close, consider the edge concident (internal)
                                                                if (da < coincident_max_dist && db < coincident_max_dist) {
                                                                    pav[k].isInternal = true;
                                                                    pbv[z].isInternal = true;
                                                                }
                                                            }
                                                        }
                                
                                                    }
                                                }
                                            }
                                        }
                                
                                        if (parts.length > 1) {
                                            // create the parent body to be returned, that contains generated compound parts
                                            body = Body.create(Common.extend({ parts: parts.slice(0) }, options));
                                
                                            // offset such that body.position is at the centre off mass
                                            Body.setPosition(body, { x: x, y: y });
                                
                                            return body;
                                        } else {
                                            return parts[0];
                                        }
                                    };
                                
                                })();