Bullet提供了幾個類btBvhTriangleMeshShape,btHeightfieldTerrainShape去創建一些網格圖形,首先了解btHeightfieldTerrainShape,通過高度圖數據創建一個3D地形。
A static mesh that is optimised for and described by the surface of a height map.
建議先閱讀官網介紹
首先可以下幾個效果圖
根據高度圖數據.raw生成的高度地形圖
參數設置HeightfieldInfo info(128, 128, _heightMapData.getBytes(), PHY_UCHAR, 1.6f / uData, -1.f, 1.f, btVector3(25.f / uData, 1.f, 25.f / uData));
(uData爲_heightMapData的最大值)
自定義數據生成高度地形圖(PHY_FLOAT)
參數設置HeightfieldInfo info(128, 128, mapData, PHY_FLOAT, 1.f, -1.f, 1.f, btVector3(1.f, 1.f, 1.f));
mapData自定義數據,隨機0~1的數據
自定義數據生成高度地形圖(PHY_FLOAT)
參數設置HeightfieldInfo info(128, 128, mapData, PHY_SHORT, 1.f, -1.f, 1.f, btVector3(1.f, 1.f, 1.f));
mapData自定義數據0,1的數據
Bullet 自帶的Demo中的例子
btHeightfieldTerrainShape 有兩個構造函數,這裏分析較複雜的一個
btHeightfieldTerrainShape(
int heightStickWidth, x軸總寬度
int heightStickLength, z軸總長度
比如 width = 128, length = 64 則x軸方向爲128,z軸方向爲64
const void* heightfieldData, 高度數據
btScalar heightScale, 每個字節*heightScale = 實際高度
btScalar minHeight, 最小高度
btScalar maxHeight, 最大高度
地形原點 = (minHeight + maxHeight) * 0.5
int upAxis, 方向軸 取值 0=x, 1=y, 2=z,決定地形的朝向,類似法向量
PHY_ScalarType heightDataType, 數據格式 3種, PHY_UCHAR, PHY_SHORT, PHY_FLOAT
bool flipQuadEdges 方形裁剪
);
舉個例子
50*50 數據
for (int i=0; i<50; ++i)
{
for (int j=0; j<50; ++j)
{
heightMap[i*50+j] = j % 2;
}
}
對於heightMap[i*50+j]
1.如果爲0, minHeight = 0.f, maxHeight = 6.f;
最低點正好爲-3.f
2.如果爲0, minHeight = 0.f, maxHeight = 12.f;
最低點正好爲-6.f
3.如果爲0, minHeight = 0.f, maxHeight = 3.f;
最低點正好爲-1.5f
1.如果爲2, minHeight = 0.f, maxHeight = 6.f;
最低點正好爲-4.f
2.如果爲2, minHeight = 0.f, maxHeight = 12.f;
最低點正好爲-7.f
3.如果爲2, minHeight = 0.f, maxHeight = 3.f;
最低點正好爲-2.5f
地形偏移offsetY = -(minHeight + maxHeight);
不推薦minHeight + maxHeight < 0, 不穩定
heightScale * value(heightfieldData[i])爲實際高度
高度計算:
對於PHY_UCHAR
最低點y = offsetY + min(heightfieldData); minY = 0
最高點y = offsetY + max(heightfieldData) * heightScale;
對於PHY_SHORT, PHY_FLOAT
最高點y = offsetY + max(heightfieldData) * heightScale;
最低點y = offsetY + min(heightfieldData) * heightScale;
注意:
網格間隔不要過大,過大會出現物體穿過。
自定義數據類型簡化參數傳遞
- struct HeightfieldInfo
- {
- int heightStickWidth;
- int heightStickLength;
- void* heightfieldData;
- PHY_ScalarType hdt;
- btScalar heightScale;
- btScalar minHeight;
- btScalar maxHeight;
- int upAxis;
- bool useFloatData;
- bool flipQuadEdges;
- btVector3 localScaling;
- HeightfieldInfo(int width, int length, void* data, PHY_ScalarType type = PHY_SHORT,
- btScalar heiScale = 1.f, btScalar minHei = 0.f, btScalar maxHei = 1.f,
- const btVector3& scale = btVector3(1, 1, 1), int up = 1,
- bool useFloat = false, bool filpQuad = false) :
- heightStickWidth(width), heightStickLength(length), heightfieldData(data),
- heightScale(heiScale), minHeight(minHei), maxHeight(maxHei),
- localScaling(scale), upAxis(up),
- hdt(type), useFloatData(useFloat), flipQuadEdges(filpQuad)
- {}
- };
PhysicsWorld3D 創建高度地形圖
- btRigidBody* PhysicsWorld3D::addHeightfieldTerrain(const HeightfieldInfo& fieldInfo, const btVector3& position, const PhysicsMaterial3D& material)
- {
- CCAssert(material.mass == 0.f, "height field's mass must be 0.");
- btHeightfieldTerrainShape* heightfieldShape = new btHeightfieldTerrainShape(
- fieldInfo.heightStickWidth, fieldInfo.heightStickLength, fieldInfo.heightfieldData, fieldInfo.heightScale,
- fieldInfo.minHeight, fieldInfo.maxHeight, fieldInfo.upAxis, fieldInfo.hdt, fieldInfo.flipQuadEdges);
- heightfieldShape->setUseDiamondSubdivision(true); // 鑽石細分矩形方格會出現對角線
- heightfieldShape->setLocalScaling(fieldInfo.localScaling);
- auto body = getBody(heightfieldShape, position, material);
- _world->addRigidBody(body);
- return body;
- }
下面來介紹btBvhTriangleMeshShape,通過載入三角網格,實現網格形狀的物理模擬
http://bulletphysics.com/Bullet/BulletFull/classbtBvhTriangleMeshShape.html
看看效果
地形能夠與模型完美的融合在一起,而且即使半徑爲0.1的球體也不會穿過地形
使用的shape就是btBvhTriangleMeshShape, 構造方法有兩個btBvhTriangleMeshShape(
btStridingMeshInterface* meshInterface, // 網格接口,存放網格數據
bool useQuantizedAabbCompression,// 壓縮?只有buildBvh爲true纔有效
const btVector3& bvhAabbMin,
const btVector3& bvhAabbMax, // mesh不可超過bvhaabb包圍盒,只有buildBvh爲true纔有效
bool buildBvh = true);// 優化BVH
btBvhTriangleMeshShape(
btStridingMeshInterface* meshInterface,
bool useQuantizedAabbCompression,
bool buildBvh = true);
通過導入一個模型的原始三角形數據,就可以建立上圖的地形
如何載入模型數據,官網類關係圖
提供btTriangleIndexVertexArray,載入網格數據
btTriangleIndexVertexArray(
int numTriangles, // 三角個數
int* triangleIndexBase, // 三角形索引數組首地址
int triangleIndexStride, // 每個三角形索引大小 = 索引類型大小 * 3
int numVertices, // 頂點個數
btScalar* vertexBase, // 頂點數組首地址
int vertexStride); // 每個頂點字節 = 頂點元素 * 3
既然索引類型爲int,就用int
關於原始三角形數據如何得到,
1.可以利用cocos2dx的載入模型函數獲取(有待實驗)
2.利用Blender或者可以導出模型原始三角數據的軟件,直接導出數據
關於Blender一款開源的3D建模軟件,官網:http://www.blender.org/ , 自帶遊戲引擎,物理引擎就是Bullet
導出三角形數據,Blender有個插件專門導出三角形數據 文件後綴名爲raw,它是文本格式的,
導出時首先要讓模型旋轉一定角度,座標系不是opengl的座標系,cocos2dx採用的就是opengl的座標系
raw文件格式非常簡單:n行,每行9個浮點數據(描述一個三角形),每三個浮點爲一個頂點
3.自定義格式
。。。。
來實現數據的載入吧
首先讀取raw文件,實現一個簡單的PhysicsHelper3D
- #ifndef __PHYSICS_HELPER_3D_H__
- #define __PHYSICS_HELPER_3D_H__
- #include <cocos2d.h>
- USING_NS_CC;
- class PhysicsHelper3D
- {
- public:
- static std::vector<float> loadRaw(const char* fileName);
- static bool loadRaw(const char* fileName, std::vector<float>& verts);
- };
- #endif // !__PHYSICS_HELPER_3D_H__
- #include "PhysicsHelper3D.h"
- std::vector<float> PhysicsHelper3D::loadRaw(const char* fileName)
- {
- std::vector<float> data;
- if (loadRaw(fileName, data))
- {
- return data;
- }
- return std::vector<float>(0);
- }
- bool PhysicsHelper3D::loadRaw(const char* fileName, std::vector<float>& verts)
- {
- char line[1024];
- float oneData;
- auto rawData = FileUtils::getInstance()->getStringFromFile(fileName); // 利用cocos2dx載入文件
- std::stringstream ss, ssLine;
- ss << rawData;
- while (ss.getline(line, 1024)) // 讀取一行
- {
- ssLine << line;
- for (int i = 0; i < 9; i++) // 獲取9個浮點數
- {
- ssLine >> oneData;
- verts.push_back(oneData);
- }
- }
- return true;
- }
並不是很難吧,載入文件辦法不好,不過先將就着用吧
- _indexVertexArrays = new btTriangleIndexVertexArray(_verts.size() / 9, &_verIndices[0], 3 * sizeof(int),
- _verts.size() / 3, (btScalar*)&_verts[0], 3 * sizeof(float));
- _meshShape = new btBvhTriangleMeshShape(_indexVertexArrays, true);
- _verts是vector<float> 三角形的個數 =_verts.size() / 9
- 爲了構建方便實現PhysicsMesh3D
- #ifndef __PHYSICS_MESH_3D_H__
- #define __PHYSICS_MESH_3D_H__
- #include "Bullet/btBulletDynamicsCommon.h"
- #include "cocos2d.h"
- USING_NS_CC;
- class PhysicsMesh3D
- {
- public:
- static PhysicsMesh3D* constuct(const char* fileName);
- void destroy();
- bool initWithFile(const char* fileName);
- private:
- std::vector<float> _verts; // 存放頂點
- std::vector<int> _verIndices; // 頂點索引
- btTriangleIndexVertexArray* _indexVertexArrays; // 三角形數據
- CC_SYNTHESIZE_READONLY(btBvhTriangleMeshShape*, _meshShape, MeshShape); // shape
- };
- #endif
- CC_SYNTHESIZE_READONLY 爲cocos2dx提供的宏
- bool PhysicsMesh3D::initWithFile(const char* fileName)
- {
- _indexVertexArrays = nullptr;
- _verts.clear();
- _verIndices.clear();
- if (PhysicsHelper3D::loadRaw(fileName, _verts)) // 載入數據
- {
- _verIndices.resize(_verts.size()); // 頂點的位置就是索引
- for (int i=0; i < _verts.size(); ++i)
- {
- _verIndices[i] = i;
- }
- _indexVertexArrays = new btTriangleIndexVertexArray(
- _verts.size() / 9, // 三角形個數
- &_verIndices[0], // 三角數據數組首地址
- 3 * sizeof(int), // 一個三角索引大小
- _verts.size() / 3, // 頂點個數
- (btScalar*)&_verts[0], // 頂點數組首地址
- 3 * sizeof(float)); // 一個頂點大小
- // 獲取shape
- _meshShape = new btBvhTriangleMeshShape(_indexVertexArrays, true);
- return true;
- }
- return false;
- }
釋放申請的內存
- void PhysicsMesh3D::destroy()
- {
- _verts.clear();
- _verIndices.clear();
- delete _indexVertexArrays;
- delete this;
- }
在PhysicsWorld3D建立一個添加Mesh的方法
- btRigidBody* addTriangleMesh(PhysicsMesh3D* mesh3D, const btVector3& position,
- const PhysicsMaterial3D& material = PHYSICS_MATERIAL3D_PLANE);
- btRigidBody* PhysicsWorld3D::addTriangleMeshShape(PhysicsMesh3D* mesh3D, const btVector3& position, const PhysicsMaterial3D& material)
- {
- CCAssert(material.mass == 0.f, "body's mass must be 0.");
- auto body = getBody(mesh3D->getMeshShape(), position, material);
- _world->addRigidBody(body);
- return body;
- }
測試
HelloWorld 添加變量
- PhysicsMesh3D* _phyMesh3D; // mesh shape
- _phyMesh3D = PhysicsMesh3D::constuct("heightmap.raw");
- _world->addTriangleMesh(_phyMesh3D, btVector3(0, 0, 0));
- // 載入plane模型
- auto spPlane = Sprite3D::create("model/heightmap.c3b");
- this->addChild(spPlane);
- spPlane->setPosition3D(Vec3(0, 0, 0));
- spPlane->setRotation3D(Vec3(0, 180, 0));
onExit()不要忘了
- _phyMesh3D->destroy();