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* addTriangleMeshShape(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->addTriangleMeshShape(_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();
为了方便测试, 实现了一个漫游摄像机,有空讲解一下。