Using Blender to draw physics shapes

I thought I’d try to use blender as a tool to draw physics shapes used in cocos2d, since it’s practically got everything you’d need for the job: good 3d tools; vertices; scriptable in python etc. etc. and after making this:

(the light gray area is a semi-transparent png used for reference)

I wrote small script that prints out the x/y positions of each of the vertices (I’ll include it here if you want to test it)

std::vector<Vec2> pts = {
		Vec2(32.518404722213745, 58.081066608428955),
		Vec2(43.27123761177063, 57.54342079162598),
		Vec2(60.20730137825012, 65.87674617767334),
		Vec2(75.79915523529053, 68.0275559425354),
		Vec2(91.92895293235779, 72.8662371635437),
		Vec2(107.67183303833008, 83.74323844909668),
		Vec2(121.66688442230225, 100.84570646286011),
		Vec2(130.253267288208, 122.99180030822754),
		Vec2(133.74805450439453, 140.73420763015747),
		Vec2(133.479106426239, 153.63761186599731),
		Vec2(134.0165138244629, 160.62719821929932),
		Vec2(139.86798524856567, 166.89422130584717),
		Vec2(138.7926697731018, 171.19542360305786),
		Vec2(135.0290298461914, 172.80877828598022),
		Vec2(111.91021203994751, 174.42169189453125),
		Vec2(114.5986557006836, 188.6688232421875),
		Vec2(129.38368320465088, 189.4756555557251),
		Vec2(138.52382898330688, 184.3677043914795),
		Vec2(144.7065830230713, 180.33549785614014),
		Vec2(150.35194158554077, 172.5395917892456),
		Vec2(160.8363151550293, 168.50738525390625),
		Vec2(165.0554656982422, 158.94253253936768),
		Vec2(169.35659646987915, 140.93117713928223),
		Vec2(165.0554656982422, 117.81235933303833),
		Vec2(155.50761222839355, 91.25792980194092),
		Vec2(145.02370357513428, 68.40780973434448),
		Vec2(127.55024433135986, 53.08489799499512),
		Vec2(111.44776344299316, 42.537662386894226),
		Vec2(88.32874298095703, 37.16111183166504),
		Vec2(55.80129623413086, 37.967449426651),
		Vec2(42.93651878833771, 49.84023571014404),
		Vec2(34.239304065704346, 49.627724289894104)
	};

So far so good, but… then I used this data to make a PhysicsBody:

auto tstBodyNode = Sprite::create("images/banana.png");
auto tstBody = PhysicsBody::createPolygon(pts.data(), pts.size(), PHYSICSBODY_MATERIAL_DEFAULT, Vec2(-100,-100));
tstBody->setDynamic(false);
tstBodyNode->setPosition(org.x + screen.width * 0.5, screen.height * 0.5);
tstBodyNode->setPhysicsBody(tstBody);
addChild(tstBodyNode);

and this was the result:

It became a bit simplified… So, will the body need to be broken up into smaller shapes for this to work, or why do you think it ended up the way it did?

Fyi, the solution was to triangulate the shape in blender and then iterate over each polygon, save the vertices for those and then create a new shape for every three vertices and put those shapes into a new body.

What if you tried PolygonSprite? Does that work better initially. Can you share any code of your final solution?

Sure, I’ll explain how it works.

You run the tool from the terminal like this:
# blender myscene.blend --background --python myscript.py --objname MyCube --maxx 1.0 --maxy 0.8

Then if it worked it give you an output like this in the terminal:

std::vector<Vec2> tris = { Vec2(0.0006512153702668655, 0.000291843170998618) ...
std::vector<Vec2> quads = { Vec2(0.0006512153702668655, 0.000291843170998618) ...

You paste that in your code and use them to create your shapes or whatever. Here’s my setup for that:

std::vector<Vec2> tris = { Vec2(0.0006512153702668655, 0.000291843170998618) ...
std::vector<Vec2> quads = { Vec2(0.13594012229870528, 0.9001200795173645) ...
auto jarMat = PhysicsMaterial();
jarMat.restitution = 0.1;
jarMat.friction = 0.2;
jarMat.density = 0.6;

auto jar = Util::makePhysSprite("images/jar.png", tris, quads, false, true, jarMat);
// Util.h
cocos2d::Sprite* makePhysSprite(const std::string &fName, std::vector<Vec2> &tris, std::vector<Vec2> &quads, bool dynamicType, bool scalePtsRelativeToSprite = true, PhysicsMaterial material = PHYSICSBODY_MATERIAL_DEFAULT);
// Util.cpp
cocos2d::Sprite* makePhysSprite(const std::string &fName, std::vector<Vec2> &tris, std::vector<Vec2> &quads, bool dynamicType, bool scalePtsRelativeToSprite, const PhysicsMaterial material)
	{
		std::vector<Vec2> tmpPts = {};
		auto sprite = Sprite::create(fName);
		auto spriteBounds = sprite->getBoundingBox().size;
		float bOffsetX = (sprite->getContentSize().width * -0.5);
		float bOffsetY = (sprite->getContentSize().height * -0.5);

		auto spriteBody = PhysicsBody::create();
		spriteBody->setDynamic(dynamicType);

		for (size_t i = 0; i < tris.size(); i++)
		{
			if (scalePtsRelativeToSprite)
			{
				tris.at(i).set(
					tris.at(i).x * spriteBounds.width, 
					tris.at(i).y * spriteBounds.height
					);
			}

			tmpPts.push_back(tris.at(i));

			if (tmpPts.size() == 3)
			{
				auto s = PhysicsShapePolygon::create(tmpPts.data(), tmpPts.size(), material, Vec2(bOffsetX, bOffsetY));
				spriteBody->addShape(s);
				tmpPts.clear();
			}
		}

		for (size_t i = 0; i < quads.size(); i++)
		{
			if (scalePtsRelativeToSprite)
			{
				quads.at(i).set(quads.at(i).x * spriteBounds.width, quads.at(i).y * spriteBounds.height);
			}

			tmpPts.push_back(quads.at(i));

			if (tmpPts.size() == 4)
			{
				auto s = PhysicsShapePolygon::create(tmpPts.data(), tmpPts.size(), material, Vec2(bOffsetX, bOffsetY));
				spriteBody->addShape(s);
				tmpPts.clear();
			}
		}

		sprite->setPhysicsBody(spriteBody);
		return sprite;
	}

Thanks to this I can turn this

Into this

The tool is not perfect, however, for some reason I sometimes got bad results with polygons going all crazy, but it can easily be fixed if you split the edges of the faces (in Blender) so that they’re not connected (just overlapping) to the ones around them, then it works.

(If you’re modifying the script I can recommend Visual Studio Code, because it has a built-in terminal and python extensions. Works really well.)

Oh, and the max x/y values are used when normalizing the vertices. You want to scale the object down so that it doesn’t exceed 1.0 on either x or y axis - global blender coordinates. But if your object is not as wide as it is high, then just make its height 1.0 (global) in Blender and then put its max x value (global) on the command line. (I just realized I made a function that gets those values automatically, but they might not be used in the script I attached… oh well.)

vert_tool.zip (2.8 KB)

This is very cool. I think I want to try your idea in a project and perhaps we can put this in our docs.

Yeah sure, that’d be cool. I should also mention that you need to add the path to Blender’s program folder to the system variables in order to be able to run it from the terminal.