top of page
Search
cedarcantab

Studying Box2D-Lite in Javascript, Part 10: Polygon Shape

Updated: Mar 1


We have now recreated the basic functionalities of Box2D-Lite (but with a lot more code...) and added a circle.


Although we created a class called Polygon, we have only created boxes with it, using the method SetAsBox.



/// A solid convex polygon. It is assumed that the interior of the polygon is to
/// the left of each edge.
/// Polygons have a maximum number of vertices equal to b2_maxPolygonVertices.
/// In most cases you should not need many vertices for a convex polygon.

class b2PolygonShape extends b2Shape {

    constructor( density =1) {
		super(density)

        this.m_type = b2Shape.e_polygon;
		this.m_vertices = Vec2.MakeArray(b2_maxPolygonVertices);
		this.m_normals = Vec2.MakeArray(b2_maxPolygonVertices)
        this.m_radius = b2_polygonRadius;
        this.m_count;
        this.m_centroid = new Vec2();

    }

}


However, as outlined earlier, the PolygonShape class is basically an array of vertices, together with an array of the face normals. There is in fact a method called Set, which accepts an array of vertices and creates a polygonshape from those vertices.



The code for this method is shown below. As you can see, it does not simply accept the set of vertices and set it to the m_vertices property. Rather it:


  1. creates a b2Hull object, then

  2. passes the set of vertices to a static method b2ComputeHull of the b2Hull class

  3. b2ComputeHull (as we will see later) creates a convex hull from the set of vertices

  4. convex hull is then passed to a method called setFromHull, which as we will see later copies the vertices of the convex hull to create the PolygonShape object.

  /// Create a convex hull from the given array of local points.
	/// The count must be in the range [3, b2_maxPolygonVertices].
	/// @warning the points may be re-ordered, even if they form a convex polygon
	/// @warning if this fails then the polygon is invalid
	/// @returns true if valid

    set(vertices) {

		let count = vertices.length;
		
		let hull = new b2Hull();
		hull.b2ComputeHull(vertices, count);

		if (hull.count < 3) {
			return false;
		}

		this.setFromHull(hull);

		return this;
    }






    /// Create a polygon from a given convex hull (see b2ComputeHull).
	/// @warning the hull must be valid or this will crash or have unexpected behavior

    setFromHull(hull) {
	
        b2Assert(hull.count >= 3);

		this.m_count = hull.count;

		// Copy vertices
		for (let i = 0; i < hull.count; ++i) {
			this.m_vertices[i].copy(hull.points[i]);
		}

		// Compute normals. Ensure the edges have non-zero length.
		for (let i = 0; i < this.m_count; ++i) {
			let i1 = i;
			let i2 = i + 1 < this.m_count ? i + 1 : 0;
			let edge = Vec2.Subtract(this.m_vertices[i2], this.m_vertices[i1]);
			b2Assert(edge.lengthSquared() > b2_epsilon * b2_epsilon);
			this.m_normals[i] = b2Cross(edge, 1.0);
			this.m_normals[i].normalize();
		}

		// Compute the polygon centroid.
		this.m_centroid = b2PolygonShape.ComputeCentroid(this.m_vertices, this.m_count);
	}










 static ComputeCentroid(vs, count) {

        b2Assert(count >= 3);

        let c = new Vec2(0.0, 0.0);
        let area = 0.0;

        // Get a reference point for forming triangles.
        // Use the first vertex to reduce round-off errors.
        let s = vs[0];
		
        const  inv3 = 1.0 / 3.0;

        for (let i = 0; i < count; i++)	{
            // Triangle vertices.
            let p1 = Vec2.Subtract(vs[0], s);
            let p2 = Vec2.Subtract(vs[i], s);
            let p3 = (i + 1) < count ? Vec2.Subtract(vs[i+1], s) : Vec2.Subtract(vs[0], s);
			
            let e1 = Vec2.Subtract(p2, p1);
            let e2 = Vec2.Subtract(p3, p1);
	        let D = b2Cross(e1, e2);

            let triangleArea = 0.5 * D;
	        area += triangleArea;
		
	        // Area weighted centroid
            c.addScaled(triangleArea, new Vec2((p1.x + p2.x + p3.x)*inv3, (p1.y + p2.y + p3.y)*inv3));
	    }
	    // Centroid
        b2Assert(area > b2_epsilon);
        c = Vec2.Add(Vec2.Scale((1.0 / area), c), s) ;
        return c;
    }



	computeMass() {
		
		// Polygon mass, centroid, and inertia.
		// Let rho be the polygon density in mass per unit area.
		// Then:
		// mass = rho * int(dA)
		// centroid.x = (1/mass) * rho * int(x * dA)
		// centroid.y = (1/mass) * rho * int(y * dA)
		// I = rho * int((x*x + y*y) * dA)
		//
		// We can compute these integrals by summing all the integrals
		// for each triangle of the polygon. To evaluate the integral
		// for a single triangle, we make a change of variables to
		// the (u,v) coordinates of the triangle:
		// x = x0 + e1x * u + e2x * v
		// y = y0 + e1y * u + e2y * v
		// where 0 <= u && 0 <= v && u + v <= 1.
		//
		// We integrate u from [0,1-v] and then v from [0,1].
		// We also need to use the Jacobian of the transformation:
		// D = cross(e1, e2)
		//
		// Simplification: triangle centroid = (1/3) * (p1 + p2 + p3)
		//
		// The rest of the derivation is handled by computer algebra.
		const density = this.m_density;
		
		b2Assert(this.m_count >= 3);
	
		let center = new Vec2(0.0, 0.0);
		let area = 0.0;
		let I = 0.0;

		// Get a reference point for forming triangles.
		// Use the first vertex to reduce round-off errors.
		let s = this.m_vertices[0];

		const k_inv3 = 1.0 / 3.0;

		for (let i = 0; i < this.m_count; i++) {
			// Triangle vertices.
			let e1 = Vec2.Subtract(this.m_vertices[i], s);
			let e2 = (i + 1) < this.m_count ? Vec2.Subtract(this.m_vertices[i+1], s) : Vec2.Subtract(this.m_vertices[0], s);

			let D = b2Cross(e1, e2);

			let triangleArea = 0.5 * D;
			area += triangleArea;

			// Area weighted centroid
			center.addScaled(triangleArea * k_inv3, Vec2.Add(e1, e2));

			let ex1 = e1.x, ey1 = e1.y;
			let ex2 = e2.x, ey2 = e2.y;

			let intx2 = ex1*ex1 + ex2*ex1 + ex2*ex2;
			let inty2 = ey1*ey1 + ey2*ey1 + ey2*ey2;

			I += (0.25 * k_inv3 * D) * (intx2 + inty2);
		}
		
	
		// Total mass
		this.m_mass.mass = density * area;
		this.m_mass.invMass = 1 / this.m_mass.mass;
		// Center of mass
		b2Assert(area > b2_epsilon);
		center.scale(1.0 / area);
		this.m_mass.center = Vec2.Add(center, s);

		// Inertia tensor relative to the local origin (point s).
		this.m_mass.I = density * I;
		this.m_mass.invI = 1 / this.m_mass.I;
		// Shift to center of mass then to original body origin.
		this.m_mass.I += this.m_mass.mass * (b2Dot(this.m_mass.center, this.m_mass.center) - b2Dot(center, center));
	}


	computeAABB(aabb, xf,  childIndex) {

		let lower = b2Mul(xf, this.m_vertices[0]);
		let upper = lower;

		for (let i = 1; i < this.m_count; i++) {
			let v = b2Mul(xf, this.m_vertices[i]);
			lower = b2Min(lower, v);
			upper = b2Max(upper, v);
		}

		let r = new Vec2(this.m_radius, this.m_radius);
		aabb.lowerBound = Vec2.Subtract(lower, r);
		aabb.upperBound = Vec2.Add(upper, r);
	}


3 views0 comments

Recent Posts

See All

p2 naive broadphase

var Broadphase = require('../collision/Broadphase'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2...

sopiro motor constranit

import { Matrix2, Vector2 } from "./math.js"; import { RigidBody } from "./rigidbody.js"; import { Settings } from "./settings.js";...

Comments


記事: Blog2_Post
bottom of page