matter-js icon indicating copy to clipboard operation
matter-js copied to clipboard

Body parts not aligning to same center

Open bfishman opened this issue 8 years ago • 11 comments

Hello. I'm having issues with a concave body not building correctly, and am starting to suspect a bug either in poly-decomp or matter.

here's what I have: image

compared to what I expect: image

Initially, I relied upon poly-decomp, using the Matter.Bodies.fromVertices function to which I passed the following array as the vertices parameter: [{x: 0, y: 0},{x: 60, y: 24},{x: 0, y: 48},{x: 10, y: 24}]

When that failed, I manually decomposed the ship into two convex polygons, as such: [[{x: 0, y: 0},{x: 60, y: 24},{x: 10, y: 24}],[{x: 60, y: 24},{x: 0, y: 48},{x: 10, y: 24}]]

And got (basically) the same result. Well, it was slightly different:

Poly-Decomp: image

Manual decomposition: image

Any ideas what might be going on? Would be happy to post more info in the morning, just let me know what you'd like to see.

bfishman avatar May 16 '16 09:05 bfishman

I can confirm that regardless of whatever offset I give the separate arrays of vectors, I get the same result. ie: [[{x: 0, y: 0-20},{x: 60, y: 24-20},{x: 10, y: 24-20}],[{x: 60, y: 24+20},{x: 0, y: 48+20},{x: 10, y: 24+20}]]

Part1 is shifted up 20 units and part2 is shifted down 20 units, but the result is the same as before. So it looks like the individual parts' CoM are not being respected and used as an offset from the total body's CoM. Still digging through the code to find where that happens.

bfishman avatar May 17 '16 01:05 bfishman

Sorry to spam the comments, but posting as I go.. I just discovered that Vertices.hull returns the erroneous set of four points. Shouldn't the hull of those four vertices be a triangle instead?

EDIT Tried a new hull algo, same result. So the positions are wrong going into the hull function (which is called from within Body.set() -> Body.setParts()). At that point, just prior to hull being called, the body.parts[x].vertices[] indicate that the bodies are separated. They should share the same minimum/maximum y-value, but instead they're separated by approx 16 units in the y direction. Look at the images above, if you take the topmost triangle and move it up another 16 units, you'll have the right shape.

bfishman avatar May 17 '16 01:05 bfishman

Alright, so I found the problem, although don't have the time to actually build a solution tonight. Each 'part' - whether that comes from individual sets of vectors or from the decomp algorithm - is first transformed into its own body. When that happens, the vertices are translated to be relative to the body's CoM ---- but, their original offset is not stored. So, if I add the following lines to Body.setVertices:

// orient vertices around the centre of mass at origin (0, 0)
var centre = Vertices.centre(body.vertices);
Vertices.translate(body.vertices, centre, -1);

// Added these two lines:
    //save the part's offset from its parent      
    body.position.x += centre.x;
    body.position.y += centre.y;

// update inertia while vertices are at origin (0, 0)
Body.setInertia(body, Body._inertiaScale * Vertices.inertia(body.vertices, body.mass));

// update geometry
Vertices.translate(body.vertices, body.position);
Bounds.update(body.bounds, body.vertices, body.velocity);

Then it works in my case:
image

Of course, this naive 'fix' breaks just about every other body.. You probably need to detect when a compound body is being created and only store offset in that case. But hopefully this gets us most of the way there!

bfishman avatar May 17 '16 02:05 bfishman

Hmm, well Bodies.fromVertices is supposed to handle this already. Strange that it isn't working for this case though, I will need to check this out a little closer when I get chance.

liabru avatar May 17 '16 18:05 liabru

Sorry I missed this earlier. I tried this using the latest release of matter-js and polydecomp and it works fine using your original code e.g.

Bodies.fromVertices(100, 100, [{x: 0, y: 0},{x: 60, y: 24},{x: 0, y: 48},{x: 10, y: 24}])

I'm not sure what has changed since your post though. If you're still there, can you confirm?

liabru avatar Jan 15 '17 17:01 liabru

I think this would be the same class of issue.

For reasons that probably aren't of interest, I'm running poly-decomp myself. This leaves me with an array of polygons, each composed of an array of vertices.

My first attempt was to create a body, passing that array (after converting the vertices to objects) directly to fromVertices as the documentation says that it supports an array containing multiple sets of vertices. That resulted in only one of the polygons being drawn.

I moved from there to creating a compound body composed of several other bodies which is how I originally assumed I would have to tackle it:

// polygons is an array, each element being an array, each element of that being an object with properties x and y
var bodies = [];
for (var i=0; i<polygons.length; i++)
{
	bodies.push(Matter.Bodies.fromVertices(400, 400, polygons[i]));
}
var compound = Matter.Body.create({
	parts: bodies
});
Matter.World.add(myEngine.world, [compound]);

Which would result in this:

image

The issue seems to be related to the one bfishman was describing - matter is ignoring the actual offset of the vertices when creating the body, and centering them around the point passed to fromVertices. In my case, that means all of my polygons end up overlapping and centered.

It's not clear from the documentation that this would be the intended behaviour (though in retrospect makes sense), so it led to some confusion for sure, and the obvious fix for this (passing in all the polygons at once so that matter could handle it) didn't seem to work. Is there another method I missed that allows creating several polygons at once while retaining their relative positioning in some way?

What I ended up doing, based off of bfishman's solution, was:

// polygons is an array, each element being an array, each element of that being an object with properties x and y
var bodies = [];
for (var i=0; i<polygons.length; i++)
{
	var body = Matter.Bodies.fromVertices(400, 400, polygons[i]);
	bodies.push(body);
	var centre = Matter.Vertices.centre(polygons[i]);
	Matter.Body.setPosition(body, {
		x: body.position.x + centre.x,
		y: body.position.y + centre.y
	});
}
var compound = Matter.Body.create({
	parts: bodies
});
Matter.World.add(myEngine.world, [compound]);

Which resulted in my intended behaviour:

image

I can't imagine this is a horribly unusual use-case, so I'm sure I must have missed an easier solution somewhere? If not, then hopefully this can help someone else that ends up googling their weird overlapping polygon issues. :)

EDIT: Sorry, forgot to include: I'm using Matter 0.14.1 / 2018-01-10.

NuclearDog avatar Jan 31 '18 16:01 NuclearDog

Are you saying that Matter.Bodies.fromVertices(400, 400, polygons) alone doesn't work? Assuming your polygons is like [[vector ...], [vector...]...], I think that should do the job.

The issue seems to be related to the one bfishman was describing - matter is ignoring the actual offset of the vertices when creating the body, and centering them around the point passed to fromVertices.

This is expected behaviour when using Body.create, since it must centre your vertices around the centre of mass for the engine to work correctly. Because parts are also bodies that created through Body.create they need to be manually moved after creation (since there is no knowledge of the parent upon creation). The function Matter.Bodies.fromVertices is meant to handle all this for you.

I do realise this is a little complex and not so intuitive though, I'd like to revisit it ideally at some point. It's probably true that Body.setParts needs to handle updating the part offsets instead, which I don't think it does currently.

liabru avatar Feb 05 '18 23:02 liabru

I'm not sure what weird path I was taking to only get the first polygon added, but I wiped everything out and replaced with with a minimal case of:

var body = Matter.Bodies.fromVertices(400, 400, polygons);
Matter.World.add(myEngine.world, [body]);

And rather than just getting the first polygon as I did previously, I ended up with the overlapping polygons again. So fromVertices definitely doesn't appear to be working as intended.

I console.log(JSON.stringify(polygons)) in my above code and put the result at: https://gist.github.com/NuclearDog/bb324f26c43f128e3d66ee705e253ed4 so you have the exact data I'm working with here. This should result in something largely similar (but not necessarily identical) to the shape in the screenshots of my last post.

Thanks, by the way!

NuclearDog avatar Feb 13 '18 19:02 NuclearDog

It seems that I have the same problem as NuclearDog. I'm calling Matter.Bodies.fromVertices with an array of vertex sets that are already convex and they all get mushed to the center.

I couldn't get NuclearDogs solution to work, but after a lot of experiments I got this working:

function fromConvexVerticesFixed(x: number, y: number, vertexSets: Vector[][], options?: IBodyDefinition, flagInternal?: boolean, removeCollinear?: number, minimumArea?: number): Body {
    const body = Bodies.fromVertices(0, 0, vertexSets, options, flagInternal, removeCollinear, minimumArea);

    // We only had one vertex set and it became exactly one body part - so we don't have any sub-parts. Properly move it.
    if ((body.parts.length === 1) && (vertexSets.length === 1)) {
        const centre = Vertices.centre(vertexSets[0]);
        x += centre.x;
        y += centre.y;
    } else {
        // The first part is always the parent body, so length - 1
        const subBodyPartsCount = body.parts.length - 1;

        if (subBodyPartsCount === vertexSets.length) {
            // Exactly as many sub body parts as vertex sets? Nice. Move them according to the centre of the input vertices.
            for (let i = 0; i < vertexSets.length; i++) {
                // body.parts[i + 1] since the first part is always the parent body
                Body.setPosition(body.parts[i + 1], Vertices.centre(vertexSets[i]));
            }
        } else if (subBodyPartsCount > vertexSets.length) {
            // If we have more sub body parts than vertices, this wasn't convex and Bodies.fromVertices hopefully has properly dealt with it.
            console.log("fromConvexVerticesFixed called with non-convex vertices", vertexSets);
        } else {
            console.log("fromConvexVerticesFixed has no idea what to do with this. Body parts", body.parts.length, "for", vertexSets.length, "vertexSets?");
        }

        // Manually recompute autohull after changing the body parts' positions
        // We can't call Body.setParts(body, body.parts.slice(1), true); because that would recenter everything for some reason
        recomputeAutohull(body);

        // Now we have to overwrite axes, area, mass and inertia again because Body.setVertices in recomputeAutohull() resets them
        Body.set(body, {
            axes: options.axes || body.axes,
            area: options.area || body.area,
            mass: options.mass || body.mass,
            inertia: options.inertia || body.inertia
        });
    }

    Body.setPosition(body, { x, y });
    return body;
}

// Taken from Body.setParts
function recomputeAutohull(body: Body) {
    let vertices: Vector[] = [];
    for (let i = 0; i < body.parts.length; i++) {
        vertices = vertices.concat(body.parts[i].vertices);
    }

    Vertices.clockwiseSort(vertices);

    const hull = Vertices.hull(vertices);
    const hullCentre = Vertices.centre(hull);

    Body.setVertices(body, hull);
    Vertices.translate(body.vertices, hullCentre, 1);
}

It seems to me that the problem might be that you set the position directly in https://github.com/liabru/matter-js/blob/master/build/matter.js#L6779 as opposed to using the centre like https://github.com/liabru/matter-js/blob/master/build/matter.js#L6814.

TobiasWehrum avatar Jun 23 '19 18:06 TobiasWehrum

I can confirm that what @TobiasWehrum mentioned here might be the solution:

It seems to me that the problem might be that you set the position directly in https://github.com/liabru/matter-js/blob/master/build/matter.js#L6779 as opposed to using the centre like https://github.com/liabru/matter-js/blob/master/build/matter.js#L6814.

Using Vertices.centre(vertices) wil align parts properly. Screenshot 2019-11-10 at 13 23 55 vs. Screenshot 2019-11-10 at 13 24 07

I can make a pull request when I'm done with my project. Tight deadline 😅

Edit: To be more precise, Vertices.centre don't work when it's only one set in the vertexSet, so what I had to do was this:

    return {
      position: vertexSets.length > 1 ? Vertices.centre(vertices) : {x: x, y: y},
      vertices: vertices
    };

Note that I have written a custom fromVertices since I running decomp outsite Matter.js in some preprocessing of my geometry.

lassemt avatar Nov 10 '19 12:11 lassemt

I also had this problem with Bodies.fromVertices and an array of convex polygons: They would all center themselves around the center position, overlapping each other. After some testing, I discovered that the decomposition of the outer hull with poly-decomp worked as expected, so I investigated what is different between parts creation with poly-decomp and without: The bug is in parts.push in Bodies.fromVertices when if (isConvex || !canDecomp) is true. Following lassemts suggestion, parts.push here should be replaced with

parts.push({
    position: vertexSets.length > 1 ? Vertices.centre(vertices) : {x: x, y: y},
    vertices: vertices
});

The solution from TobiasWehrum did not work for me when the angle of the polygon changed, but lassemt solutions seems to work perfectly so far (tested with matter-js 0.19.0).

rev111 avatar May 03 '24 09:05 rev111