top of page
Search
  • cedarcantab

Extending Box2D-Lite in Javascript: Clipping for Contact Points

Updated: Apr 8





This is a continuation of our look into the CollidePolgyons method.


We have delved into the Lite implementation in quite some detail and the full version is very similar in structure. Hence this will be more of a refresher of the logic.


There is however, one very important difference between the two implementations.


Lite implementation returns the clipped points in world space coordinates.
Full implementation returns the clipped points in local space coordinates of the reference body.

As I understand it, the preference is to keep as much of the calculations as possible in local space coordinates.


ClipVertex

As a reminder, as with the Lite implementation, an object called ClipVertex is used to identify the vertices to be clipped.


/// Used for computing contact manifolds.
class b2ClipVertex {

    constructor() {

         this.v = new Vec2(); //b2Vec2
         this.id = new b2ContactID(); //b2ContactID
    
    }
    
};

CollidePolygon method - continued


In the previous post we looked at the collision detection part, where we identified:


  • the polygons are overlapping

  • the significant faces


Now we want to identify the incident vs the reference edges, before we start the clipping process.


Identifying the Incident Edge vs Reference Edge

	//  if get to here, means polys are colliding

	let poly1;	// reference polygon
	let poly2;	// incident polygon
	let xf1, xf2;
	let edge1;					// reference edge
	let flip;
	const k_tol = 0.1  b2_linearSlop;

	if (separationB.separation > separationA.separation + k_tol)	{
	
		poly1 = polyB;
		poly2 = polyA;
		xf1 = xfB;
		xf2 = xfA;
		edge1 = separationB.edgeIndex;
		manifold.type = b2Manifold.e_faceB;
		flip = true;
	}
	else
	{
	
		poly1 = polyA;
		poly2 = polyB;
		xf1 = xfA;
		xf2 = xfB;
		edge1 = separationA.edgeIndex;
		manifold.type = b2Manifold.e_faceA;
		flip = false;
	}

	let incidentEdge = [new b2ClipVertex(), new b2ClipVertex()]; //b2ClipVertex 
	b2FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2);
	

Finding the Incident Edge



/**
  Find Incident Edge
	  @param b2ClipVertex c[2]
      @param const b2ClipVertex vIn[2]
      @param const const b2Vec2& normal 
      @param float offset
      @param int32 vertexIndexA
 /
function b2FindIncidentEdge(c,	poly1, xf1, edge1,	poly2, xf2) {

	const normals1 = poly1.m_normals;

	let count2 = poly2.m_count;

	const vertices2 = poly2.m_vertices;
	const normals2 = poly2.m_normals;

	b2Assert(0 <= edge1 && edge1 < poly1.m_count);

	// Get the normal of the reference edge in poly2's frame.
	let normal1 = Rot.MulQTV(xf2.q, Rot.MulQV(xf1.q, normals1[edge1]));

	// Find the incident edge on poly2.
	let index = 0;
	let minDot = Number.MAX_VALUE;
	for (let i = 0; i < count2; i++) {
		let dot = Vec2.b2Dot(normal1, normals2[i]);
		if (dot < minDot) {
			minDot = dot;
			index = i;

		}
	}

	// Build the clip vertices for the incident edge.
	let i1 = index;
	let i2 = i1 + 1 < count2 ? i1 + 1 : 0;
	c[0].v = b2Mul(xf2, vertices2[i1]);
	c[0].id.cf.indexA = edge1;
	c[0].id.cf.indexB = i1;
	c[0].id.cf.typeA = b2ContactFeature.e_face;
	c[0].id.cf.typeB = b2ContactFeature.e_vertex;

	c[1].v = b2Mul(xf2, vertices2[i2]);
	c[1].id.cf.indexA = edge1;
	c[1].id.cf.indexB = i2;
	c[1].id.cf.typeA = b2ContactFeature.e_face;
	c[1].id.cf.typeB = b2ContactFeature.e_vertex;

}


ClipSegmentToLine



/**
 * Clipping for contact manifolds.
	 * @param b2ClipVertex vOut[2]
     * @param const b2ClipVertex vIn[2]
     * @param const const b2Vec2& normal 
     * @param float offset
     * @param int32 vertexIndexA
 */

// Sutherland-Hodgman clipping.
function b2ClipSegmentToLine(vOut, vIn, normal, offset, vertexIndexA) {

    // Start with no output points
    let count = 0;

    // Calculate the distance of end points to the line
    let distance0 = Vec2.b2Dot(normal, vIn[0].v) - offset;
    let distance1 = Vec2.b2Dot(normal, vIn[1].v) - offset;

    // If the points are behind the plane
    if (distance0 <= 0.0) vOut[count++] = vIn[0];
    if (distance1 <= 0.0) vOut[count++] = vIn[1];

    // If the points are on different sides of the plane
    if (distance0 * distance1 < 0.0) {

        // Find intersection point of edge and plane
        let interp = distance0 / (distance0 - distance1);
        vOut[count].v = Vec2.AddScaled(vIn[0].v, Vec2.Subtract(vIn[1].v, vIn[0].v), interp);

        // VertexA is hitting edgeB.
        vOut[count].id.cf.indexA = vertexIndexA;
        vOut[count].id.cf.indexB = vIn[0].id.cf.indexB;
        vOut[count].id.cf.typeA = b2ContactFeature.e_vertex;
        vOut[count].id.cf.typeB = b2ContactFeature.e_face;
        count++;

        b2Assert(count == 2);

    }

    return count;

}

The actual clipping


Going back to the CollidePolygons class, we now start the actual clipping process.


Remember, there are 3 main stages to the clipping process:


  1. Clip against the positive side of the reference edge

  2. Clip against the negative side of the reference edge

  3. Clip against the reference edge itself



Clipping against the adjacent sides

The code goes onto prepare for the clipping process.


let count1 = poly1.m_count;
	const vertices1 = poly1.m_vertices;
	
	let iv1 = edge1;
	let iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0;

	let v11 = vertices1[iv1];
	let v12 = vertices1[iv2];

	let localTangent = Vec2.Subtract(v12, v11);
	localTangent.normalize();
	
	let localNormal = Vec2.b2Cross(localTangent, 1.0); // vector
	let planePoint = Vec2.Scale(Vec2.Add(v11, v12), 0.5); // vector

	let tangent = Rot.MulQV(xf1.q, localTangent); // vector
	let normal = Vec2.b2Cross(tangent, 1.0); // vector
	
	v11 = b2Mul(xf1, v11);
	v12 = b2Mul(xf1, v12);

	// Face offset.
	let frontOffset = Vec2.b2Dot(normal, v11);

	// Side offsets, extended by polytope skin thickness.
	let sideOffset1 = -Vec2.b2Dot(tangent, v11) + totalRadius;
	let sideOffset2 = Vec2.b2Dot(tangent, v12) + totalRadius;

	// Clip incident edge against extruded edge1 side edges.
	let clipPoints1 =  [new b2ClipVertex(), new b2ClipVertex()];
	let clipPoints2 =  [new b2ClipVertex(), new b2ClipVertex()];

	let np; // integer

Clip against positive side of reference edge

Clip the incident edge against the positive side of reference edge


	
	// Clip to box side 1
	np = b2ClipSegmentToLine(clipPoints1, incidentEdge, Vec2.Negate(tangent), sideOffset1, iv1);

	if (np < 2)
		return;

	
	

Clip against negative side of reference edge

Now clip the incident edge against the negative side of reference edge


// Clip to negative box side 1
	np = b2ClipSegmentToLine(clipPoints2, clipPoints1,  tangent, sideOffset2, iv2);

	if (np < 2) {
		return;
	}

	// Now clipPoints2 contains the clipped points.

Final Clip

The code that deals with step 3 - clipping against the reference edge itself - is shown below. Unlike clipping the incident edge as in the previous stage, we now just remove all points that lie inside the clipping region.




manifold.localNormal = localNormal;
	manifold.localPoint = planePoint;

	let pointCount = 0;
	for (let i = 0; i < b2_maxManifoldPoints; i++) {
		let separation = Vec2.b2Dot(normal, clipPoints2[i].v) - frontOffset;

		if (separation <= totalRadius) {
			let cp = manifold.points[pointCount]; // b2ManifoldPoint* 
		//	cp.localPoint = Transform.MulTTV(xf2, clipPoints2[i].v);
	cp.localPoint = b2MulT(xf2, clipPoints2[i].v);
		//cp.localPoint = clipPoints2[i].v
			cp.id = clipPoints2[i].id;
			if (flip) {
				// Swap features
				let cf = cp.id.cf; // b2ContactFeature
				cp.id.cf.indexA = cf.indexB;
				cp.id.cf.indexB = cf.indexA;
				cp.id.cf.typeA = cf.typeB;
				cp.id.cf.typeB = cf.typeA;
			}
			pointCount++;
		}
	}

	manifold.pointCount = pointCount;

What remains from this final clipping process are copied from clipPoints2 to the manifold.


Note, that the above code is a direct port of Box2D method. As noted earlier, the CollidePolygons method gives you the contact points and the normal in local space of the reference polygon.



12 views0 comments
記事: Blog2_Post
bottom of page