I made a script that does the heavy-lifting part of this. It still needs some refinement before making it into the default menu, but I'll give you what I have for now since it should still save you a lot of time.
Before running the script, the expected starting state is:
- there must be a fixture named 'output' somewhere in the scene
- at least two polygon fixtures (excluding the 'output' fixture) should be selected
- the selected fixtures should all be wound the same way
The script will set the vertices of the 'output' fixture from the merged outline of the selected fixtures. It does this by starting with the left-most vertex of the selected fixtures and tracing around the outside looking for intersections with the other selected fixtures.
Here are some examples which will probably illustrate this more clearly. Between each of the following before and after shots, I have pressed F5 and then delete, to run the script and then delete the original fixtures for clarity. If your original fixtures are all on different bodies, you might want to delete those bodies instead of just their fixtures.
The square fixture on the right is the one called 'output' which will be reshaped, and remains as the merged result.
Basic example.

- merge1.png (62.49 KiB) Viewed 93093 times
Example with hole - the interior outline will be ignored.

- merge3.png (57.48 KiB) Viewed 93093 times
Example with two islands (note the removal of one small fixture near the bottom) - because the outline tracing starts with the leftmost vertex of the selection, the five fixtures in the bottom right have been completely missed out.

- merge2.png (50.43 KiB) Viewed 93093 times
In cases where the resulting merge causes vertices to be within 0.005 units of each other, those vertices will be welded into a single vertex to avoid a decomposition with sliver triangles.
For your case, the outcome of this script will give you many more polygons than the original un-merged fixtures which will be a huge step backward from an efficiency point of view. I think I mentioned in our other discussion before, but you would only want to do this if you were going to make the ground a loop shape. The 'output' fixture can be a loop shape before running the script, since the only thing the script does is change the vertices of the output shape.
Rubescript first draft, a little rough but should work:
Code: Select all
vec2 cross(float s, vec2 a)
{
return mv(-s * a.y, s * a.x);
}
bool linesCross(vec2 v0, vec2 v1, vec2 t0, vec2 t1, vec2 &out intersectionPoint, float &out f)
{
if ( v1 == t0 || v0 == t0 || v1 == t1 || v0 == t1 )
return false;
vec2 vnormal = v1 - v0;
vnormal = cross(1.0f, vnormal);
float v0d = vnormal.dot(v0);
float t0d = vnormal.dot(t0);
float t1d = vnormal.dot(t1);
if ( t0d > v0d && t1d > v0d )
return false;
if ( t0d < v0d && t1d < v0d )
return false;
vec2 tnormal = t1 - t0;
tnormal = cross(1.0f, tnormal);
t0d = tnormal.dot(t0);
v0d = tnormal.dot(v0);
float v1d = tnormal.dot(v1);
if ( v0d > t0d && v1d > t0d )
return false;
if ( v0d < t0d && v1d < t0d )
return false;
if ( (v1d - v0d) == 0 )
return false;
f = (t0d-v0d) / (v1d-v0d);
intersectionPoint = v0 + f * (v1-v0);
return true;
}
bool fixtureIsPolygon(fixture f) {
return f.getShape(0).type == 1;
}
vertex findLeftmostVertex( fixture[] &fs ) {
float lx = 999999; // no FLT_MAX?
vertex best;
for (uint i = 0; i < fs.length; i++) {
fixture f = fs[i];
vertex[] vs = f.getVertices();
for (uint k = 0; k < vs.length; k++) {
if ( vs[k].wx < lx ) {
best = vs[k];
lx = best.wx;
}
}
}
return best;
}
float findFirstIntersection( vertex originVertex, vec2 startVec, vertex nextVertex, fixture[] fs, vec2[] &inout pts, vec2 &out intersection, vertex &out nextPt ) {
//print("Finding:");
//print( startVec );
//print( nextVertex.wpos );
float dist = 999999;
if ( nextVertex == originVertex ) {
//print("Found loop");
return dist;
}
fixture thisFixture = nextVertex.getFixture();
vec2 v0 = startVec;
vec2 v1 = nextVertex.wpos;
for (int c = 0; dist == 999999 && c < thisFixture.getNumVertices(); c++) {
pts.insertLast(v0);
for (uint i = 0; i < fs.length; i++) {
fixture f = fs[i];
if ( f == thisFixture )
continue;
vertex[] vs = f.getVertices();
for (uint k = 0; k < vs.length; k++) {
int nk = (k+1) % vs.length;
vec2 q0 = vs[k].wpos;
vec2 q1 = vs[nk].wpos;
float tmpDist;
vec2 tmpIntersection;
if ( linesCross( v0, v1, q0, q1, tmpIntersection, tmpDist ) ) {
//print("tmpDist");
//print(tmpDist);
if ( tmpDist > 0.005 && tmpDist < dist ) {
intersection = tmpIntersection;
nextPt = vs[nk];
dist = tmpDist;
}
}
}
}
if ( nextVertex == originVertex ) {
//print("breaking");
break;
}
nextVertex = nextVertex.next();
v0 = v1;
v1 = nextVertex.wpos;
}
//print("dist");
//print(dist);
return dist;
}
void main()
{
if ( sf().length < 2 ) {
print("Need at least two polygon fixtures to merge");
return;
}
// only handle polygons
fixture[] fs = filterFixtures( sf(), fixtureIsPolygon );
if ( fs.length < 2 ) {
print("Need at least two polygon fixtures to merge");
return;
}
vertex originVertex = findLeftmostVertex( fs );
vertex nextVertex = originVertex.next();
vec2 firstIntersection = originVertex.wpos;
vec2[] pts;
//vertex nextPt = nextVertex;
int count = 0;
// trace outline
while ( findFirstIntersection( originVertex, firstIntersection, nextVertex, fs, pts, firstIntersection, nextVertex ) < 999999 ) {
if ( count++ > 100 ) {
print("Reached limit");
break;
}
}
// prevent consecutive vertices from being too close to each other
for (uint i = 1; i < pts.length; i++) {
if ( pts[i].distanceTo(pts[i-1]) < 0.005 ) {
pts[i-1] = 0.5 * (pts[i] + pts[i-1]); // merge at midpoint
pts.removeAt(i);
i = 1; // start again
}
}
// set the merged vertices for the fixture called 'output'
fixture f = getFixture("output");
f.setVertices(pts);
// the vertices in the pts array are in world coordinates,
// need to shift the created fixture to where it should be
vec2 offset = originVertex.wpos - f.getVertices()[0].wpos;
translate( f, offset );
}