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:
creates a b2Hull object, then
passes the set of vertices to a static method b2ComputeHull of the b2Hull class
b2ComputeHull (as we will see later) creates a convex hull from the set of vertices
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);
}
Comments