cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

動力学シミュレータofxBulletを静的な衝突判定ライブラリとしてだけ使う方法 (その2)任意のメッシュ形状でも衝突判定したい

三角ポリゴンの集合で表現された任意形状をofxBulletで扱うためには, ofxBulletCustomShape()クラスを使います。
普通に動力学シミュレーションで使うだけなら、Exampleに倣って実行すれば問題ないかと思います。
github.com

ofxBullletCustomShapeクラスの改造

衝突判定をしたいときには、衝突グループと衝突マスクの指定を外から与えたいケースが多いのですが、標準ではそのインターフェースが用意されていませんので、ofxBulletCustomShapeクラスを魔改造してしまいましょう。

  • add関数に衝突グループと衝突マスク指定の追加(他のプリミティブクラスのadd関数も同様に引数付きの呼び出しを追加すると良さそうですね。)
  • addMesh関数に重心指定を追加(標準では頂点位置の平均位置になってしまう。)
  • draw関数でmeshを描画、ただしメモリを無駄に食うので注意。

ofxBulletCustomShape.hのクラス定義内に追加

public:
	bool addMesh(ofMesh a_mesh, ofVec3f a_localScaling, bool a_bUseConvexHull, ofVec3f a_centroid);
        void addMaterial(ofMaterial a_material);
	void add(short group, short mask);

protected:
	std::vector<ofMesh>		meshes;
        std::vector<ofMaterial>            materials;

ofxBulletCustomShape.cppに追加及び変更

//--------------------------------------------------------------
bool ofxBulletCustomShape::addMesh(ofMesh a_mesh, ofVec3f a_localScaling, bool a_bUseConvexHull, ofVec3f a_centroid) {
	if (a_mesh.getMode() != OF_PRIMITIVE_TRIANGLES) {
		ofLog(OF_LOG_ERROR, "ofxBulletCustomShape :: addMesh : mesh must be set to OF_PRIMITIVE_TRIANGLES!! aborting");
		return false;
	}
	if (_bAdded == true) {
		ofLog(OF_LOG_ERROR, "ofxBulletCustomShape :: addMesh : can not call after calling add()");
		return false;
	}

	ofMesh scaled_mesh(a_mesh);
	for (size_t i(0); i < scaled_mesh.getNumVertices(); ++i) {
		scaled_mesh.setVertex(i, scaled_mesh.getVertex(i) * a_localScaling);
	}
	meshes.push_back(scaled_mesh);

	btVector3 localScaling(a_localScaling.x, a_localScaling.y, a_localScaling.z);
	auto indicies = a_mesh.getIndices();
	auto verticies = a_mesh.getVertices();

	btVector3 centroid = btVector3(a_centroid.x * a_localScaling.x,
			a_centroid.y * a_localScaling.y,
			a_centroid.z * a_localScaling.z);

	if (!a_bUseConvexHull) {
		vector<btVector3> newVerts;
		for (int i = 0; i < indicies.size(); i++) {
			btVector3 vertex(verticies[indicies[i]].x, verticies[indicies[i]].y, verticies[indicies[i]].z);
			vertex *= localScaling;
			vertex -= centroid;
			newVerts.push_back(vertex);
		}

		btConvexHullShape* convexShape = new btConvexHullShape(&(newVerts[0].getX()), newVerts.size());
		convexShape->setMargin(0.01f);
		shapes.push_back(convexShape);
		centroids.push_back( ofVec3f(centroid.getX(), centroid.getY(), centroid.getZ()) );
	}
	else {
		// HULL Building code from example ConvexDecompositionDemo.cpp //
		btTriangleMesh* trimesh = new btTriangleMesh();

		for (int i = 0; i < indicies.size() / 3; i++) {
			int index0 = indicies[i * 3];
			int index1 = indicies[i * 3 + 1];
			int index2 = indicies[i * 3 + 2];

			btVector3 vertex0(verticies[index0].x, verticies[index0].y, verticies[index0].z);
			btVector3 vertex1(verticies[index1].x, verticies[index1].y, verticies[index1].z);
			btVector3 vertex2(verticies[index2].x, verticies[index2].y, verticies[index2].z);

			vertex0 *= localScaling;
			vertex1 *= localScaling;
			vertex2 *= localScaling;

			trimesh->addTriangle(vertex0, vertex1, vertex2);
		}

		//cout << "ofxBulletCustomShape :: addMesh : input triangles = " << trimesh->getNumTriangles() << endl;
		//cout << "ofxBulletCustomShape :: addMesh : input indicies = " << indicies.size() << endl;
		//cout << "ofxBulletCustomShape :: addMesh : input verticies = " << verticies.size() << endl;

		btConvexShape* tmpConvexShape = new btConvexTriangleMeshShape(trimesh);

		//create a hull approximation
		btShapeHull* hull = new btShapeHull(tmpConvexShape);
		btScalar margin = tmpConvexShape->getMargin();
		hull->buildHull(margin);
		tmpConvexShape->setUserPointer(hull);

		centroid = btVector3(a_centroid.x * a_localScaling.x,
				a_centroid.y * a_localScaling.y,
				a_centroid.z * a_localScaling.z);

		//printf("ofxBulletCustomShape :: addMesh : new hull numTriangles = %d\n", hull->numTriangles());
		//printf("ofxBulletCustomShape :: addMesh : new hull numIndices = %d\n", hull->numIndices());
		//printf("ofxBulletCustomShape :: addMesh : new hull numVertices = %d\n", hull->numVertices());

		btConvexHullShape* convexShape = new btConvexHullShape();
		for (int i = 0; i<hull->numVertices(); i++) {
			convexShape->addPoint(hull->getVertexPointer()[i] - centroid);
		}

		delete tmpConvexShape;
		delete hull;

		shapes.push_back(convexShape);
		centroids.push_back(ofVec3f(centroid.getX(), centroid.getY(), centroid.getZ()));
	}
	return true;
}

//--------------------------------------------------------------
void ofxBulletCustomShape::add(short group, short mask) {
	_bAdded = true;
	btTransform trans;
	trans.setIdentity();

	for (int i = 0; i < centroids.size(); i++) {
		_centroid += centroids[i];
	}
	if (centroids.size() > 0)
		_centroid /= (float)centroids.size();
	btVector3 shiftCentroid;
	for (int i = 0; i < shapes.size(); i++) {
		shiftCentroid = btVector3(centroids[i].x, centroids[i].y, centroids[i].z);
		shiftCentroid -= btVector3(_centroid.x, _centroid.y, _centroid.z);
		trans.setOrigin((shiftCentroid));
		((btCompoundShape*)_shape)->addChildShape(trans, shapes[i]);
	}
	_rigidBody = ofGetBtRigidBodyFromCollisionShape(_shape, _startTrans, _mass);
	setCreated(_rigidBody);
	createInternalUserData();
	_world->addRigidBody(_rigidBody, group, mask); ///// changed
	setProperties(.4, .75);
	setDamping(.25);
}

//--------------------------------------------------------------
void ofxBulletCustomShape::addMaterial(ofMaterial a_material) {
	materials.push_back(a_material);
}

//--------------------------------------------------------------
void ofxBulletCustomShape::draw() {
	if (meshes.size()) {
		ofPushMatrix();
		transformGL();
		for (size_t i(0); i < meshes.size(); ++i) {
			if (i < materials.size()) {
				materials[i].begin();
				meshes[i].draw();
				materials[i].end();
			}
			else
				meshes[i].draw();
		}
		restoreTransformGL();
		ofPopMatrix();
	}
}

呼び出し方

呼び出しのサンプルは、前回のページとの差分を部分的に抽出すると
モデル作成

	shapes.push_back(new ofxBulletCustomShape());
	for (int i = 0; i < model.getNumMeshes(); i++) {
		((ofxBulletCustomShape*)shapes.back())->addMesh(model.getMesh(i), ofVec3f(1.f, 1.f, 1.f), false, ofVec3f(0.f, 0.f, 0.f));

		ofxAssimpMeshHelper & meshHelper = assimpModel.getMeshHelper(i);
		logos[i]->addMaterial(meshHelper.material);
	}
	((ofxBulletCustomShape*)shapes.back())->create(world.world, ofVec3f(), 0.1);
	shapes.back()->add(1, 7);

位置の更新

	btTransform trans = ofGetBtTransform( model.getModelMatrix().getTranslation() + model_mat.getTranslation(),  model.getModelMatrix().getRotate() * model_mat.getRotate());
	shapes[id]->getRigidBody()->setWorldTransform(trans);
	shapes[id]->getRigidBody()->getMotionState()->setWorldTransform(trans);

	world.update(0.f, 1);

Assimpの座標への変換がややこしい。addModelの際に頂点座標を変換して織り込んでしまったほうが良いかもしれない。
描画の更新

	ofEnableLighting();
	ofPushStyle();
	//world.drawDebug();
	for (size_t i(0); i < shapes.size(); i++) {
		if (bColliding[i]) {
			ofSetColor(ofColor::yellow, 50);
		}
		else {
			ofSetColor(ofColor::white, 50);
		}
		shapes[i]->draw();
	}
	ofPopStyle();

*1->addMesh(model.getMesh(i), ofVec3f(1.f, 1.f, 1.f), false, ofVec3f(0.f, 0.f, 0.f));の3番目の引数のfalseが重要。
falseだと簡易化した多面体として衝突テストを行い、trueだと生のポリゴンで衝突テストを行う。実用的な速さを求めるなら、false。精度が欲しいならtrue。

今日の本文

レイリが面白い。

*1:ofxBulletCustomShape*)shapes.back(