0%

skynet.call 在设计的时候,是没有考虑超时的问题的。云风的解释是,加入之后会使得在其构建的系统增加不必要的复杂度。这个解释也是合理的,但是还是免不了有需求要使用到超时的机制。

举个简单例子,skynet实现的web服务,一些http请求的时候,如果没有超时机制,http请求就会一直占用。

云风给出了一种解决方案,就是增加一个代理服务,让其负责转发,然后代理服务增加超时的检查。也是一种不错的方式。

skynet 是支持协程的,应该说snlua服务,都是基于协程实现的。基于协程的特性,本身就能实现超时检测,逻辑也很简单。

执行函数的时候,调用 do_func_or_timeout 传入 msecfunc 。函数就会启用(fork)两个协程,并且让当前线程wait:

  • 第一个协程,sleep msec,之后检测 is_wait 如果为false,结束;否则wakeup原本协程;
  • 第二个线程则是执行 func函数,正常返回后,检查is_wait 如果为false,结束;否则wakeup原本协程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
local Skynet = require "skynet"
local M = {}

local STATUS_SUCC = 0
local STATUS_TIMEOUT = 1

function M.do_func_or_timeout(msec, func, ...)
local args = ...
local ctx = {
is_wait = true,
corout = coroutine.running(),
}

Skynet.fork(function()
Skynet.sleep(msec) -- 超时等待msec 1/00秒
if not ctx.is_wait then
return
end

ctx.is_wait = false
ctx.status = STATUS_TIMEOUT
Skynet.wakeup(ctx.corout)
end)

Skynet.fork(function()
local status = STATUS_SUCC
local ret = func(args)

if not ctx.is_wait then
return
end

ctx.is_wait = false
ctx.status = status
ctx.ret = ret

Skynet.wakeup(ctx.corout)
end)
Skynet.wait()
return ctx.status, ctx.ret
end

print("== start test ==", Skynet.time())
local status, ret = M.do_func_or_timeout(200, function()
Skynet.sleep(300)
return "ok"
end)
print("== status ret 1 ==", Skynet.time(), status, ret)

local status, ret = M.do_func_or_timeout(600, function()
Skynet.sleep(300)
return "ok"
end)
print("== status ret 2 ==", Skynet.time(), status, ret)

return M

输出:

1
2
3
== start test ==        1631807810.97
== status ret 1 == 1631807812.97 1 nil
== status ret 2 == 1631807815.97 0 ok

基本原理

将场景区域划分为小格子,然后将玩家的视野统一设定为玩家所在的格子和周边的八个格子。这样在同步的时候就只需要同步九宫格内的数据。为此需要维护玩家进入和离开格子的数据。

基本接口

主要有四个接口,其中进入场景(enter)和场景内移动(move)在这里合并为set接口。

  • new_area: 新建aoi场景区域
  • set: 进入场景或者场景内移动
  • leave: 离开场景
  • get_ids_by_grid: 根据格子id获取格子内对象id

实现

数据结构

所有数据都挂在场景内,此处称为area。area内包含了场景范围,对象列表,格子列表。new_area 接口也就是创建场景数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local tb_area = {
min_x = args.min_x, -- 场景范围
min_y = args.min_y,
max_x = args.max_x,
max_y = args.max_y,

grid_x = grid_x, -- x轴有几段
grid_y = grid_y, -- y轴有几段

grid_max = grid_max, -- 总格子数量
grid_size = grid_size, -- 格子长度

map_actor = {}, -- 对象列表
lst_grid = {}, -- 格子
}

进入场景和移动

进入场景非常简单,根据坐标计算出目标格子,然后分别加入 map_actor 列表和 lst_grid 列表即可。

场景内移动分2种情况:

第一种在格子内移动,这种情况,aoi没有变化。

第二种跨格子移动,出现了离开旧格子加入新格子的情况。这里稍微注意的是,有可能旧的九宫格和新的九宫格存在重叠的区域,这样的情况,对于重叠的区域的对象视野来说,目标对象并没有离开过视野。

离开场景

离开也非常简单,从 map_actor 列表和 lst_grid 列表删除即可。

完整代码

GitHub - rondsny/lua_aoi

数据结构

lua的内存结构最主要有三大块,lua_State、 CallInfo、 lua_TValue。

  • lua_State里面的 stack (栈)是主要的内存结构,类型是 lua_TValue;
  • lua_TValue 主要是Value,是一个 uion,存的内容根据 lua_TValue.tt_ 标记;
  • CallInfo 用于记录函数调用信息:作用的栈区间,返回数量,调用链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

typedef union lua_Value {
void * p; // LUA_TLIGHTUSERDATA
int b; // bool
lua_Integer i; // integer
lua_Number n; // number
lua_CFunction f; // function
} Value;

typedef struct lua_TValue {
Value value_;
int tt_;
} TValue;

typedef TValue* StkId;

struct CallInfo {
StkId func; // 被调用函数在栈中的位置
StkId top; // 被调用函数的栈顶位置
int nresult; // 又多少个返回值
int callstatus; // 调用状态
struct CallInfo* next; // 下一个调用
struct CallInfo* previous; // 上一个调用
};

typedef struct lua_State {
StkId stack; // 栈
StkId stack_last; // 从这里开始,栈不能被使用
StkId top; // 栈顶
int stack_size; // 大小
struct lua_longjmp * errorjmp;
int status; // 状态
struct lua_State * next; // 下一个lua_State,通常创建协程时会产生
struct lua_State * previous;
struct CallInfo base_ci; // 和lua_State生命周期一致的函数调用信息
struct CallInfo* ci; // 当前运作的CallInfo
struct global_State* l_G; // global_state指针
ptrdiff_t errorfunc; // 错误函数位于栈的那个位置
int ncalls; // 进行了多少次函数调用
} lua_State;

主要操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// lua_State的创建和销毁
struct lua_State* lua_newstate(lua_Alloc alloc, void* ud);
void lua_close(struct lua_State* L);
// 入栈
void increase_top(struct lua_State* L);
void lua_pushinteger(struct lua_State* L, int integer);

// 出栈
void lua_settop(struct lua_State* L, int idx);
int lua_gettop(struct lua_State* L);
void lua_pop(struct lua_State* L);

// 取栈上的值
lua_Integer lua_tointegerx(struct lua_State* L, int idx, int* isnum);

// 创建新的函数对象CallInfo
static struct CallInfo* next_ci(struct lua_State* L, StkId func, int nresult);

背景

在项目使用中,出现了以下报错:

1
2
3
Error Code: 1118 - Row size too large (> 8126).
Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help.
In current row format, BLOB prefix of 768 bytes is stored inline.

上面报错,这就涉及到了row size的上限,也有可能会涉及到file format的设置。

一些上限

  • 创建表报错:maximum row size > 65535
  • 创建表报错:row size too large > 8126
  • 插入数据报错:row size too larget > 8126

这里主要讲第三种报错,插入数据的时候触发的报错。先要理清file format和row format两个概念。
个人理解修改这两个参数都有可能解决掉问题。为啥是有可能,因为file format只是部分长字段类型的设置。

File format

先解释下file forma。这是用于varchat或者text字段存储数据的方案。file format有两种,分别是Antelope和Barracuda。

Antelope模式

在MySQL 5.6之前的版本,默认file format是Antelope。
意思是,对于varchar和text字段,会将内容的前768字段放置在index record中,后面再接20个字节的指针。
剩下内容会放置在BLOB page中。

假设有11个text字段,每个字段都是1000字节。那么在插入数据的时候,row size = (768+20)* 11 = 8668 > 8126,将会触发row size too larget > 8126报错。
如果使用Antelope模式,不建议使用超过10个text或varchar字段。

Barracuda模式

Barracude会将所有数据放在BLOB page中,在index record里面只放20个字节的指针。

设置切换

查询File format设置:

1
show variables like "%innodb_file%";

my.cnf 设置

1
2
3
innodb_file_format = Barracuda #
innodb_file_per_table = 1
innodb_large_prefix = 1

Row format

innodb引擎的行格式(row format)有两种。分别是compact和dynamic/compressed。

1
2
3
ALTER TABLE test ROW_FORMAT=COMPRESSED;

SHOW TABLE STATUS IN test_db;

这里还是之说Antelope模式下使用text的问题。普通的数值类型,其实很难超8126的长度限制,就不说了。在Antelope模式下,text字段的前768会存储在index record中,会占用row size的768+2个字节。所以text/varchar字段不能太多。除非确定不会太长。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 验证测试的sql

create table text2
(
text1 longtext,
text2 longtext,
text3 longtext,
text4 longtext,
text5 longtext,
text6 longtext,
text7 longtext,
text8 longtext,
text9 longtext,
text10 longtext,
text11 longtext
) ENGINE=InnoDB DEFAULT CHARSET=ascii;



insert into text2 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000)
);

insert into text2 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',197) # repeat('y',198) error
);


--------


create table text3
(
text1 longtext,
text2 longtext,
text3 longtext,
text4 longtext,
text5 longtext,
text6 longtext,
text7 longtext,
text8 longtext,
text9 longtext,
text10 longtext,
text11 longtext
) ENGINE=INNODB DEFAULT CHARSET=ascii ROW_FORMAT=COMPRESSED;


insert into text3 values(
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000),
repeat('y',1000)
);

1. 插件扩展

1.1. 命名空间

1
2
using UnityEditor;
using UnityEngine; //非必需,常用到

1.2. 使用语法

1
2
3
4
5
[MenuItem("Assets/My Test")]
private static void Test()
{
Debug.Log("Hello World!")
}

语法说明

1
2
3
4
5
6
7
[MenuItem("path $t", Is, Priority)]
private static void Test1()
{}

[MenuItem("path _t", Is, Priority)]
private static void Test2()
{}
  • path 是菜单路径;
    • 一级菜单名称不支持中文
  • $t 是一个快捷键实例,在路径后面接空格,然后加上快捷键表示,单纯的一个按键快捷键按键字符前带下划线。该项非必需
    • % 表示 ctrl
    • # 表示 shift
    • & 表示 alt
  • Is 设置为true的时候,如果没有选中游戏对象,会显示不可用状态,该选项非必需
  • Priority 是优先级,数值越小优先级越高,非必需,其默认值为1000。

下面表示快捷键为”ctrl+h” 的实例。

1
2
3
4
5
[MenuItem("Assets/My Test %h")]
private static void Test()
{
Debug.Log("Hello World!")
}

1.3. Selection类

https://docs.unity3d.com/ScriptReference/Selection.html

1.3.1. 获取选中物体

Selection.Objects可以获得选中的物品。

1.3.2. 获取选中目录

1
2
3
4
5
6
7
8
9
10
string[] guids = Selection.assetGUIDs;
foreach ( var id in guids )
{
string path = AssetDatabase.GUIDToAssetPath(id);
if(Directory.Exists(path))
{
Debug.Log(string.format("Directory {0} exist", path))
}
}

1.4. 给控件添加右上角齿轮菜单增加功能

1
2
3
4
5
[MenuItem("CONTEXT/Rigidbody/wyg_test")]
private static TestRightButton()
{
Debug.Log("右键测试")
}
  • CONTEXT 为固定写法;
  • Rigidbody 是控件名称,可以修改为其他控件;
  • 我使用中文的时候不知道为什么没有显示出来。

1.5. 弹窗

编辑器的弹窗类需要继承EditorWindow

使用方法与GUI的使用方法基本一致,有Init,Awake,OnGUI等函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class TestWindow : EditorWindow
{
public void Init()
{
}

publick void OnGUI()
{
GUILayout.Label("Test Window");
if (GUILayout.Button("Button"))
{
Debug.Log("On Button")
}
}
}

2. 组件属性展示

以下效果都是组件的显示,所以是属于using UnityEngine;的。

2.1. Range

可以将数值的展示效果变成滑动条效果。

1
2
[Range(1, 12)]
public int month;

2.2. Multiline/TextArea

控制占用多行显示。

1
2
3
4
5
[Multiline(5)]
public string msg1;

[TextArea(3,5)]
public string msg2; // 最少显示三行,最多显示五行

2.3. ContextMenuItem/ContextMenu

添加右键小菜单,添加一些小功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
[ContextMenuItem("Random", "RandomNumber")]
[ContextMenuItem("Reset", "ResetNumber")]
public int number;

void RandomNumber()
{
number = Random.Range(0, 100);
}

void ResetNumber()
{
number = 0;
}

还有一个ContextMenu的特性,用法类似。

1
2
3
4
5
6
7
public string Name;

[ContextMenu("Reset Name")]
private void ResetName()
{
Name = "";
}

2.4. ColorUsage

设置颜色选择器。

1
2
3
4
5
6
7
public Color color1;

[ColorUsage(false)]
public Color color2; // 没有alpha

[ColorUsage(true, true, 0, 8, 0.125f, 3)]
public Color color3; // HDR

Unity常见API

本文主要为了方便查阅

1. MonoBehaviour 生命周期

  • Awake 对象创建的时候调用,类似构造函数
  • Start 在Awake之后执行,区别在于,如果组件不可用(在Inspector没有勾选该组件),是不会执行Start
  • Update 主函数循环每帧调用
  • FixedUpdate 每次固定帧调用,在物理计算的时候,应该使用该方法,而不是Update
  • OnDestroy 对象销毁时调用

2. MonoBehaviour 常见回调函数

  • OnMouseEnter 鼠标移入GUI控件或者碰撞体时调用
  • OnMouseOver 鼠标停留在GUI控件或者碰撞体时调用
  • OnMouseExit 鼠标移出GUI控件或者碰撞体时调用
  • OnMouseDown 鼠标在GUI控件或者碰撞体上按下时调用
  • OnMouseUp 鼠标在GUI控件或者碰撞体上释放时调用
  • OnTriggerEnter 当其他碰撞体进入触发器时调用
  • OnTriggerExit 当其他碰撞体离开触发器时调用
  • OnCollisionEnter 当碰撞体或者刚体与其他碰撞体或刚体接触时调用
    • OnCollisionEnter2D 其它2D函数类似
  • OnCollisionExit 当碰撞体或者刚体与其他碰撞体或刚体停止接触时调用
  • OnCollisionStay 当碰撞体或者刚体与其他碰撞体或刚体保持接触时调用
  • OnContollerColliderHit 当控制器移动时与碰撞体发生碰撞时调用
  • OnBecameVisible 对于任意一个相机可见时调用
  • OnBecameInVisible 对于任意一个相机不可见时调用
  • OnEnable 对象启用或者激活时调用
  • OnDisable 对象禁用或者取消激活时调用
  • OnDestroy 脚本销毁时调用
  • OnGUI 渲染GUI和处理GUI消息时调用

3. 访问游戏对象

  • GameObject.Find 多个时返回第一个
  • GameObject.FindWithTag 多个时返回第一个
  • GameObject.FindGameObjectsWithTag 返回数组

以上函数比较耗时,尽量避免在update函数中使用。

4. 访问组件

4.1. 常见组件

  • Transform 位置、旋转、缩放
  • Rigidbody 刚体
  • Renderer 渲染物体模型
  • Light 灯光属性
  • Camera 相机属性
  • Collider 碰撞体
  • Animation 动画
  • Audio 声音
  • Mesh
    • Mesh Filter 网格过滤器
    • Text Mesh 文本
    • Mesh Renderer 网格渲染器
    • Skinned Mesh Renderer 蒙皮渲染器,用于骨骼动画
  • Particle Sysyem 粒子系统
  • Physics 物理
  • Image Effects
  • Scripts 自定义组件

4.2. 获取组件

  • GetComponent<>() 获取组件
  • GetComponents<>()
  • GetComponentInChildren<>() 得到对象或者对象子物体上的组件
  • GetComponentsInChildren<>()

以上函数比较耗时,尽量避免在update函数中使用。

4.3. Transform 组件

4.3.1. 成员变量
  • position 世界坐标系
  • localPosition 相对坐标系(父对象)
  • eulerAngles 世界坐标系中以欧拉角表示的旋转
  • localEulerAngles 相对旋转
  • right 右方向
  • up 上方向
  • forward 前方向
  • rotation 旋转四元数
  • localRotation 相对旋转四元数
  • localScale 相对缩放比例
  • parent 父对象的Transform组件
  • worldToLocalMatrix 世界坐标系到局部坐标系的变换矩阵(只读)
  • localToWorldMatrix 局部坐标系到世界坐标系的变换矩阵(只读)
  • root 根对象的Transform组件
  • childCount 子孙对象的数量
  • lossyScale 全局缩放比例(只读)
4.3.2. 成员函数
  • Translate 按指定方向和距离平移
  • Rotate 按指定的欧拉角旋转
  • RotateAround 按给定旋转轴和旋转角度进行旋转
  • LookAt 旋转使得自身的前方向指向目标的位置
  • TransformDirection 将一个方向从局部坐标系变换到世界坐标系
  • InverseTansformDirection 将一个方向从世界坐标系变化到局部坐标系
  • DetachChildren 与所有子物体接触父子关系
  • Find 按名称查找子对象
  • IsChildOf 判断是否是指定对象的子对象

5. Time 时间类

5.1. 成员变量

  • time 游戏从开始到现在经历的时间(秒)(只读)
  • timeSinceLevelLoad 此帧开始时间(秒)(只读),从关卡加载完成开始计算
  • dateTime 上一帧耗费的时间(秒)(只读)
  • fixedTime 最近FixedUpdate的时间。该时间从游戏开始计算
  • fixedDateTime 物理引擎和FixedUpdate的更新时间间隔
  • maximunDateTime 一帧的最大耗时时间
  • smoothDeltaTime Time.deltaTime的平滑淡出
  • timeScale 时间流逝速度的比例。可以制作慢动作特效
  • frameCount 已渲染的帧的总数(只读)
  • realtimeSinceStartup 游戏从开始到现在的真实时间(秒),该事件不受timeScale影响
  • captureFramerate 固定帧率设置

6. Random 随机数类

6.1. 成员变量

  • seed 随机数生成器种子
  • value 0~1随机数,包含0和1
  • insideUnitSphere 半径为1的球体内的一个随机点
  • insideUnitCircle 半径为1的圆内的一个随机点
  • onUnitSphere 半径为1的球面上的一个随机点
  • rotation 随机旋转
  • rotationUnitform 均匀分布的随机旋转

6.2. 成员函数

  • Range 返回(min,max)直接的随机浮点数,包含min和max

7. Mathf 数学类

7.1. 成员变量

  • PI
  • Infinity 正无穷大
  • NegativeInfinity 负无穷大
  • Deg2Rad 度到弧度的转换系数
  • Rad2Deg 弧度到度的转换系数
  • Epsilon 一个很小的浮点数

7.2. 成员函数

  • Sin 弧度
  • Cos 弧度
  • Tan 弧度
  • Asin 角度
  • ACos 角度
  • Atan 角度
  • Sqrt 计算平方根
  • Abs 绝对值
  • Min
  • Max
  • Pow Pow(f,p) f的p次方
  • Exp Exp(p) e的p次方
  • Log 计算对数
  • Log10 计算基为10的对数
  • Ceil 向上取整
  • Floor 向下取整
  • Round 四舍五入
  • Clamp 将数值限制在min和max之间
  • Clamp01 将数值限制在0和1之间

8. Coroutine 协同程序函数

  • StartCoroutine 启动一个协同程序
  • StopCoroutine 终止一个协同程序
  • StopAllCoroutines 终止所有协同程序
  • WaitForSeconds 等到若干秒
  • WaitForFixedUpdates 等待直到下一次FiexedUpdate调用

官方文档:http://bulletphysics.org
开源代码:https://github.com/bulletphysics/bullet3/releases
API文档:http://bulletphysics.org/Bullet/BulletFull/annotated.html

封装bullet碰撞检测

该碰撞检测包含场景创建、场景物体添加、射线击中检测、胶囊体碰撞检测。

0. 几个文件说明

  1. 将所有场景管理碰撞检测封装到ColScene类中;
  2. Erlang的入口为 t_bullet.erl, C++的入口为 t_bullet.cpp;
  3. data_scene.erl 为场景地形存储文件;
  4. t_btest.erl 为测试场景。

1. ColScene类

1.1. ColScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#ifndef COL_SCENE
#define COL_SCENE

#include "btBulletDynamicsCommon.h"

class ColScene
{
private:
btDefaultCollisionConfiguration* m_colConfig;
btCollisionDispatcher* m_dispatcher;
btBroadphaseInterface* m_broadInterface;
btSequentialImpulseConstraintSolver* m_solver;
btDynamicsWorld* m_world; // 场景信息,退出的时候需要delete

// add collision object
btRigidBody* createAddRigidBody(btScalar mass, const btTransform& startTransform, btCollisionShape* shape);
void deleteColObj(btCollisionObject* obj);
void setColPos(btVector3 p);

public:
btVector3* m_colPos; // 用于记录每次碰撞位置

ColScene();
~ColScene();

// for add 增加长方体、球体、胶囊体、三角网格到场景中
btRigidBody* addBox(btVector3 boxHalf, btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addSphere(btScalar radius, btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addCupsule(
btScalar radius,
btScalar height,
btVector3 bpos, btVector4 rota, btScalar mass);
btRigidBody* addTriMesh(
int vtxCount,
int idxCount,
btScalar vtx[],
unsigned short idx[],
btVector3 bpos, btVector4 rota, btScalar mass);

// for check 射线检查、碰撞检测
bool rayHit(btVector3 from, btVector3 to);
bool checkPos(btCollisionObject* obj);
bool checkFirstCupsule();

// for debug
btCollisionObject* createCapsule(btVector3 posA, btVector3 posB, double radius);
};
#endif

1.2. ColScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#include <list>
#include <math.h>
#include "ColScene.h"
#include "btBulletDynamicsCommon.h"

using namespace std;

// 碰撞检测回调
struct MyColCallBack : btCollisionWorld::ContactResultCallback
{
btVector3 m_colPos;
bool is_col = false;

btScalar addSingleResult(
btManifoldPoint & cp,
const btCollisionObjectWrapper * colObj0Wrap,
int partId0,
int index0,
const btCollisionObjectWrapper * colObj1Wrap,
int partId1,
int index1)
{
m_colPos = cp.getPositionWorldOnB();
is_col = true;
printf("col pos {%f, %f, %f}\n", m_colPos.getX(), m_colPos.getY(), m_colPos.getZ());
return btScalar(0.f);
};
};

// -------------------------------
// 构造函数和析构函数
// -------------------------------
ColScene::ColScene()
{
m_colPos = new btVector3();
m_colConfig = new btDefaultCollisionConfiguration();
m_dispatcher = new btCollisionDispatcher(m_colConfig);
m_broadInterface = new btDbvtBroadphase();

m_solver = new btSequentialImpulseConstraintSolver;
m_world = new btDiscreteDynamicsWorld(m_dispatcher, m_broadInterface, m_solver, m_colConfig);
}

//
ColScene::~ColScene()
{
for (int i = 0; i < m_world->getNumCollisionObjects(); ++i)
{
btCollisionObject* obj = m_world->getCollisionObjectArray()[i];
deleteColObj(obj);
}
delete m_world;
delete m_solver;
delete m_broadInterface;
delete m_dispatcher;
delete m_colConfig;
delete m_colPos;
}

// -------------------------------
// 私有函数
// -------------------------------
void ColScene::deleteColObj(btCollisionObject* obj)
{
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState())
{
delete body->getMotionState();
delete body->getCollisionShape();
}
m_world->removeCollisionObject(obj);
delete obj;
}

void ColScene::setColPos(btVector3 p)
{
printf("pos = {%f, %f, %f}\n", p.getX(), p.getY(), p.getZ());
m_colPos->setX(p.getX());
m_colPos->setY(p.getY());
m_colPos->setZ(p.getZ());
}

btRigidBody* ColScene::createAddRigidBody(float mass, const btTransform& startTransform, btCollisionShape* shape)
{
bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo);

m_world->addRigidBody(body);
return body;
};

// -----------------------
// 射线检测、碰撞检测
// -----------------------
bool ColScene::rayHit(btVector3 from, btVector3 to)
{
btCollisionWorld::ClosestRayResultCallback callback(from, to);
m_world->rayTest(from, to, callback);
if(callback.hasHit())
{
btVector3 p = callback.m_hitPointWorld;
setColPos(p);
return true;
}
return false;
}

bool ColScene::checkPos(btCollisionObject* obj)
{
MyColCallBack cb = MyColCallBack();
m_world->contactTest(obj, cb);
btVector3 pos = cb.m_colPos;
setColPos(pos);
return cb.is_col;
}

bool ColScene::checkFirstCupsule()
{
bool is_col = false;

m_world->performDiscreteCollisionDetection();
// m_world->stepSimulation(1.f/60.f);

list<btCollisionObject*> m_collisionObjects;
int numManifolds = m_world->getDispatcher()->getNumManifolds();

for(int i=0; i<numManifolds; i++)
{
btPersistentManifold* contactManifold = m_world->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = (btCollisionObject*)(contactManifold->getBody0());
btCollisionObject* obB = (btCollisionObject*)(contactManifold->getBody1());

int numContacts = contactManifold->getNumContacts();
for(int j=0; j<numContacts; j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if(pt.getDistance()<0.f)
{
m_collisionObjects.push_back(obA);
m_collisionObjects.push_back(obB);
btVector3 posA = pt.getPositionWorldOnA();
btVector3 posB = pt.getPositionWorldOnB();
}
}
}

if(m_collisionObjects.size()>0)
{
m_collisionObjects.sort();
m_collisionObjects.unique();
for(list<btCollisionObject*>::iterator itr = m_collisionObjects.begin(); itr != m_collisionObjects.end(); ++itr) {
btCollisionObject* colObj = *itr;

if(colObj->getCollisionShape()->getShapeType()==CAPSULE_SHAPE_PROXYTYPE) // 如果是胶囊体刚体
{
btTransform trans = colObj->getWorldTransform();
setColPos(trans.getOrigin());
is_col = true;
break;
}
}
m_collisionObjects.clear();
}
return is_col;
}

// -------------------------------
// 增加长方体、球体、胶囊体、三角网格(注意,这里会添加到场景中)
// -------------------------------
btRigidBody* ColScene::addBox(btVector3 boxHalf, btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btBoxShape(boxHalf);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addSphere(btScalar radius, btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btSphereShape(radius);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addCupsule(
btScalar radius,
btScalar height,
btVector3 bpos, btVector4 rota, btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btCapsuleShape((btScalar)radius, height);
return createAddRigidBody(mass, trans, shape);
};

btRigidBody* ColScene::addTriMesh(
int vtxCount,
int idxCount,
btScalar vtx[],
unsigned short idx[],
btVector3 bpos,
btVector4 rota,
btScalar mass)
{
btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setOrigin(btVector3(0,-25,0));
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));

btTriangleIndexVertexArray* meshInterface = new btTriangleIndexVertexArray();
btIndexedMesh part;

part.m_vertexBase = (const unsigned char*)vtx;
part.m_vertexStride = sizeof(btScalar) * 3;
part.m_numVertices = vtxCount;
part.m_triangleIndexBase = (const unsigned char*)idx;
part.m_triangleIndexStride = sizeof(short) * 3;
part.m_numTriangles = idxCount/3;
part.m_indexType = PHY_SHORT;

meshInterface->addIndexedMesh(part, PHY_SHORT);

bool useQuantizedAabbCompression = true;
btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression);

return createAddRigidBody(mass, trans, shape);
};

// for debug
// -------------------------
// 创建一个胶囊体(但是不添加到场景)
btCollisionObject* ColScene::createCapsule(btVector3 posA, btVector3 posB, double radius)
{
btScalar lenX = posB.getX() - posA.getX();
btScalar lenY = posB.getY() - posA.getY();
btScalar lenZ = posB.getZ() - posA.getZ();
btScalar height = sqrt(lenX*lenX + lenY*lenY + lenZ*lenZ);

btScalar posX = (posA.getX()+posB.getX())/2.f;
btScalar posY = (posA.getY()+posB.getY())/2.f;
btScalar posZ = (posA.getZ()+posB.getZ())/2.f;

printf("lenX -> %f\n", lenX );
printf("lenY -> %f\n", lenY );
printf("lenZ -> %f\n", lenZ );
printf("heigh -> %f\n", height);

printf("posX -> %f\n", posX );
printf("posY -> %f\n", posY );
printf("posZ -> %f\n", posZ );

btVector3 bpos(posX, posY, posZ);
btVector4 rota(lenX, lenY, lenZ, 1.f);
btScalar mass(1.f);

btTransform trans;
trans.setIdentity();
trans.setOrigin(bpos);
trans.setRotation(btQuaternion(rota.getX(), rota.getY(), rota.getZ(), rota.getW()));
btCollisionShape* shape = new btCapsuleShape((btScalar)radius, height);

bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo);
return body;
}

2. 入口

2.1. Erlang项目rebar配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

{deps,[
{bullet3, ".*", {git, "git@github.com:bulletphysics/bullet3.git", {tag, "2.85"}}, [raw]}
]}.

{port_env, [{"linux|darwin", "CFLAGS", "$CFLAGS "},
{"win32", "CFLAGS", "/Ox /fp:fast"}]}.

{port_specs, [
{"win32", "priv/t_bullet.dll", [
"c_src/t_bullet.cpp",
"deps/bullet3/src/BulletDynamics/Character/*.cpp",
"deps/bullet3/src/BulletDynamics/ConstraintSolver/*.cpp",
"deps/bullet3/src/BulletDynamics/Dynamics/*.cpp",
"deps/bullet3/src/BulletDynamics/Featherstone/*.cpp",
"deps/bullet3/src/BulletDynamics/MLCPSolvers/*.cpp",
"deps/bullet3/src/BulletDynamics/Vehicle/*.cpp",
"deps/bullet3/src/BulletCollision/BroadphaseCollision/*.cpp",
"deps/bullet3/src/BulletCollision/CollisionDispatch/*.cpp",
"deps/bullet3/src/BulletCollision/CollisionShapes/*.cpp",
"deps/bullet3/src/BulletCollision/Gimpact/*.cpp",
"deps/bullet3/src/BulletCollision/NarrowPhaseCollision/*.cpp",
"deps/bullet3/src/LinearMath/*.cpp"
],
[{env, [{"CXXFLAGS", "/Ox /fp:fast -I\"deps\\bullet3\\src\" "}]}]}

]}.

2.2. Erlang代码入口t_bullet.erl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
% @Author: weiyanguang
% @Email: rondsny@gmail.com
% @Date: 2017-01-22 18:04:28
% @Last Modified by: weiyanguang
% @Last Modified time: 2017-02-10 11:55:43
% @Desc:

-module(t_bullet).
-export([
open_scene/0
,close_scene/0

,add_box/3
,add_sphere/3
,add_mesh/6

,col_check_capsule/3
,ray_hit/2
]).

-on_load(init/0).

init() ->
Code = filename:join("./priv", atom_to_list(?MODULE)),
erlang:load_nif(Code, 0).

%% 初始化场景
open_scene() ->
erlang:nif_error(undef).

%% 删除场景内资源,回收内存
close_scene() ->
erlang:nif_error(undef).

add_box(_BoxHalf, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_sphere(_Radius, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_cupsule(_Radius, _Height, _Bpos, _Rotation) ->
erlang:nif_error(undef).

add_mesh(_VtxCount, _IdxCount, _VtxList, _IdxList, _Bpos, _Rotation) ->
erlang:nif_error(undef).

%% 检查胶囊体与场景内是否发生碰撞
%% 胶囊体两端坐标,半径
%% @return {1, {x,y,z}}| {0,{A.x, A.y, A.z}}
col_check_capsule(_PosA, _PosB, _Raduis) ->
erlang:nif_error(undef).

%% 射线击中检测
ray_hit(_From, _To) ->
erlang:nif_error(undef).

2.3. C++代码入口t_bullet.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#include <stdio.h>
#include "erl_nif.h"
#include "btBulletDynamicsCommon.h"
#include "ColScene.h"

ColScene* g_scene;

// ---------------------------
static int get_number_f(ErlNifEnv *env, ERL_NIF_TERM eterm, double *f)
{
if (enif_get_double(env, eterm, f)) {
return 1;
}
else {
long n;
if (enif_get_long(env, eterm, &n)){
*f = (double)n;
return 1;
}
else {
return 0;
}
}
}

static int get_vector3(ErlNifEnv *env, ERL_NIF_TERM eterm, btVector3 *vector)
{
double x,y,z;
int arity;
const ERL_NIF_TERM *array;
if (enif_get_tuple(env, eterm, &arity, &array)
&& 3 == arity
&& get_number_f(env, array[0], &x)
&& get_number_f(env, array[1], &y)
&& get_number_f(env, array[2], &z)) {

vector->setX((btScalar)x);
vector->setY((btScalar)y);
vector->setZ((btScalar)z);

return 1;
}
else {
return 0;
}
}

static int get_vector4(ErlNifEnv *env, ERL_NIF_TERM eterm, btVector4 *vector)
{
double x,y,z,w;
int arity;
const ERL_NIF_TERM *array;
if (enif_get_tuple(env, eterm, &arity, &array)
&& 4 == arity
&& get_number_f(env, array[0], &x)
&& get_number_f(env, array[1], &y)
&& get_number_f(env, array[2], &z)
&& get_number_f(env, array[3], &w)) {

vector->setX((btScalar)x);
vector->setY((btScalar)y);
vector->setZ((btScalar)z);
vector->setW((btScalar)w);

return 1;
}
else {
return 0;
}
}

// ---------------------------

static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
return 0;
}

static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
{
return 0;
}

static ERL_NIF_TERM open_scene(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
if (!g_scene)
{
g_scene = new ColScene();
printf("init scene done\n");
}
return enif_make_atom(env, "ok");
}

static ERL_NIF_TERM close_scene(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
delete g_scene;
return enif_make_atom(env, "ok");
}

// ---------------------------------------------------------

static ERL_NIF_TERM add_box(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 boxHalf;
btVector3 bpos;
btVector4 rota;

if(
get_vector3(env, argv[0], &boxHalf) &&
get_vector3(env, argv[1], &bpos) &&
get_vector4(env, argv[2], &rota)
){
g_scene->addBox(boxHalf, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_sphere(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double radius;
btVector3 bpos;
btVector4 rota;

if(
enif_get_double(env, argv[0], &radius) &&
get_vector3(env, argv[1], &bpos) &&
get_vector4(env, argv[2], &rota)
){
g_scene->addSphere(radius, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_cupsule(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
double radius;
double height;
btVector3 bpos;
btVector4 rota;

if(
enif_get_double(env, argv[0], &radius) &&
enif_get_double(env, argv[1], &height) &&
get_vector3(env, argv[2], &bpos) &&
get_vector4(env, argv[3], &rota)
){
g_scene->addCupsule(radius, height, bpos, rota, 0.);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

static ERL_NIF_TERM add_mesh(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
int vtxCount;
int idxCount;
btVector3 bpos;
btVector4 rota;

double vt;
int id;
btScalar vtx[10240];
unsigned short idx[20480];

ERL_NIF_TERM head;
ERL_NIF_TERM tail;

tail = argv[2];
int i = 0;
while(enif_get_list_cell(env, tail, &head, &tail))
{
enif_get_double(env, head, &vt);
vtx[i] = (float)vt;
i++;
}

tail = argv[3];
i = 0;
while(enif_get_list_cell(env, tail, &head, &tail)){
enif_get_int(env, head, &id);
idx[i] = (unsigned short)id;
i++;
}

if(
enif_get_int(env, argv[0], &vtxCount) &&
enif_get_int(env, argv[1], &idxCount) &&
get_vector3(env, argv[4], &bpos) &&
get_vector4(env, argv[5], &rota)
){
g_scene->addTriMesh(vtxCount, idxCount,
vtx, idx,
bpos, rota, 0.f);
return enif_make_atom(env, "ok");
}
return enif_make_atom(env, "undef");
}

// ---------------------------------------------------------

// @return {1, {x,y,z}}| {0, {A.x, A.y, A.z}}
static ERL_NIF_TERM col_check_capsule(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 posA;
btVector3 posB;
if (!get_vector3(env, argv[0], &posA))
return enif_make_badarg(env);

else
{
if (!get_vector3(env, argv[1], &posB))
return enif_make_badarg(env);

else
{
double radius;
enif_get_double(env, argv[3], &radius);
btCollisionObject* obj = g_scene->createCapsule(posA, posB, radius);
int shapType = g_scene->checkPos(obj);
if(shapType>0)
{
ERL_NIF_TERM termPos = enif_make_tuple3(env,
enif_make_double(env, g_scene->m_colPos->getX()),
enif_make_double(env, g_scene->m_colPos->getY()),
enif_make_double(env, g_scene->m_colPos->getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env,
enif_make_int(env, 1),
termPos);
return terms;
}

ERL_NIF_TERM termA = enif_make_tuple3(env,
enif_make_double(env, posA.getX()),
enif_make_double(env, posA.getY()),
enif_make_double(env, posA.getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 0), termA);
return terms;
}
}
}


// check raycast hit
static ERL_NIF_TERM ray_hit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
btVector3 from;
btVector3 to;
if (!get_vector3(env, argv[0], &from))
return enif_make_badarg(env);

else
{
if (!get_vector3(env, argv[1], &to))
return enif_make_badarg(env);

else
{
if(g_scene->rayHit(from, to))
{
ERL_NIF_TERM termPos = enif_make_tuple3(env,
enif_make_double(env, g_scene->m_colPos->getX()),
enif_make_double(env, g_scene->m_colPos->getY()),
enif_make_double(env, g_scene->m_colPos->getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 1), termPos);
return terms;
}

ERL_NIF_TERM termA = enif_make_tuple3(env,
enif_make_double(env, from.getX()),
enif_make_double(env, from.getY()),
enif_make_double(env, from.getZ()));

ERL_NIF_TERM terms = enif_make_tuple2(env, enif_make_int(env, 0), termA);
return terms;
}
}
}


static ErlNifFunc nif_funcs[] = {
{"open_scene", 0, open_scene},
{"close_scene", 0, close_scene},

{"add_box", 3, add_box},
{"add_sphere", 3, add_sphere},
{"add_cupsule", 4, add_cupsule},
{"add_mesh", 6, add_mesh},

{"col_check_capsule", 3, col_check_capsule},
{"ray_hit", 2, ray_hit}
};


ERL_NIF_INIT(t_bullet, nif_funcs, load, NULL, upgrade, NULL)

3. 地形数据的erlang处理

3.1. 定义数据格式

数据record定义

1
2
3
4
5
6
7
8
9
10
11
-record(data_scene, {
id = 0,
type = undef, % box|sphere|mesh
position = {0,0,0}, % 位置
rotation = {0,0,0,1}, % 旋转
% 以下玩为box|sphere的内容
scale = {0,0,0}, % 形状,当为sphere的时候取第一个数为半径
% 以下为mesh的内容
vtx = [], % 顶点坐标
idx = [] % 三角形坐标索引
}).

一个测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-module(data_scene).
-include("data.hrl").
-compile([export_all, {no_auto_import, [{get, 1}]}]).
gets() ->
[
1,
2,
3].

get(1) -> #data_scene{
id = 1,
type = box,
position = {1,1,1},
rotation = {0,0,0,1},
scale = {1,1,1}
};

get(2) -> #data_scene{
id = 2,
type = sphere,
position = {1,1,1},
rotation = {0,0,0,1},
scale = {1,1,1}
};

get(3) -> #data_scene{
id = 3,
type = mesh,
position = {0,-25,0},
rotation = {0,0,0,1},
vtx = [
-250.0,2.99192,113.281,
-250.0,2.18397,117.188,
-246.094,1.62262,113.281,
-246.094,1.51628,117.188,
-242.188,0.847411,113.281,
...],
idx = [
0,1,2,
3,2,1,
2,3,4,
...]

...

3.2. 初始化场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-module(t_btest).
-include("ts_global.hrl").
-include("data.hrl").
-compile([export_all]).

init() ->
t_bullet:open_scene(),

Ids = data_scene:gets(),
Cfsg = [data_scene:get(Id)||Id<-Ids],
lists:foreach(fun add_obj/1, Cfsg),
ok.

add_obj(#data_scene{
type = box,
position = Bpos,
rotation = Rota,
scale = Scal
}) ->
t_bullet:add_box(Scal, Bpos, Rota);

add_obj(#data_scene{
type = sphere,
position = Bpos,
rotation = Rota,
scale = {Radius, _, _}
}) ->
t_bullet:add_sphere(Radius, Bpos, Rota);
add_obj(#data_scene{
type = sphere,
position = Bpos,
rotation = Rota,
scale = Radius
}) when is_number(Radius) ->
t_bullet:add_sphere(Radius, Bpos, Rota);
add_obj(#data_scene{
type = mesh,
position = Bpos,
rotation = Rota,
vtx = Vts,
idx = Idx
}) ->
t_bullet:add_mesh(length(Vts)/3, length(Idx), Vts, Idx, Bpos, Rota);
add_obj(_Unk) ->
skip.

3.3. 测试结果

官方文档:http://bulletphysics.org
开源代码:https://github.com/bulletphysics/bullet3/releases
API文档:http://bulletphysics.org/Bullet/BulletFull/annotated.html

bullet3的三种碰撞检测

以下三种方式都是可以达到碰撞检测的效果:

  1. btCollisionWorld::contactTest 检测指定对象是否与场景发生碰撞;
  2. btCollisionWorld::performDiscreteCollisionDetection 检测场景中所有的碰撞;
  3. btDynamicsWorld::stepSimulation 模拟运动。

还有一种射线检测,但是与这里的物体碰撞稍微有些区别,这里就不展开来讲了。

0. 准备工作

先创建一个场景,增加一个地板(box)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
btDefaultCollisionConfiguration* g_colConfig;
btCollisionDispatcher* g_dispatcher;
btBroadphaseInterface* g_broadInterface;
btSequentialImpulseConstraintSolver* g_solver;
btDynamicsWorld* g_world; // 场景信息,退出的时候需要delete

g_colConfig = new btDefaultCollisionConfiguration();
g_dispatcher = new btCollisionDispatcher(g_colConfig);
g_broadInterface = new btDbvtBroadphase();
g_solver = new btSequentialImpulseConstraintSolver;
g_world = new btDiscreteDynamicsWorld(g_dispatcher, g_broadInterface, g_solver, g_colConfig);

g_world->setGravity(btVector3(0,-10,0)); // 设置重力加速度

// add a test box
{
btCollisionShape* shape = new btBoxShape(btVector3(btScalar(1000.),btScalar(10.),btScalar(1000.)));
btTransform trans;
trans.setIdentity();
trans.setOrigin(btVector3(0, -10, 0));

btScalar mass=0.f;
btVector3 localInertia(0, 0, 0);
bool isDynamic = (mass != 0.f);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo);
g_world->addRigidBody(body);
}

1. btCollisionWorld::contactTest

完整函数内容为

1
void btCollisionWorld::contactTest(btCollisionObject * colObj, ContactResultCallback & resultCallback)

contactTest会对确定的colObj对象与btCollisionWorld中的所有对象进行接触检测,并调用ContactResultCallBack回调。
其实这个函数不算碰撞检测,只是算接触检测,如果距离为0,是会触发回调的。

1.1. 继承回调的结构体

ContactResultCallback结构体有一个名为addSingleResult的纯虚函数,在继承的时候一定要实现addSingleResult函数。这个也是碰撞的时候执行的回调函数。是这个结构体的核心。碰撞信息会存储在btManifoldPoint & cp中,使用方法也比较简单,可以参考API文档的接口。其它地方的碰撞,也是用这个对象存储,处理方法是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 碰撞检测回调
struct MyColCallBack : btCollisionWorld::ContactResultCallback
{
public:
btScalar addSingleResult(
btManifoldPoint & cp,
const btCollisionObjectWrapper * colObj0Wrap,
int partId0,
int index0,
const btCollisionObjectWrapper * colObj1Wrap,
int partId1,
int index1)
{
btVector3 posA = cp.getPositionWorldOnA();
btVector3 posB = cp.getPositionWorldOnB();
printf("col pos for A {%f, %f, %f}\n", posA.getX(), posA.getY(), posA.getZ());
printf("col pos for B {%f, %f, %f}\n", posB.getX(), posB.getY(), posB.getZ());

return btScalar(0.f);
};
};

1.2. 碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建一个球体,并加入到场景中
btCollisionShape* shape = new btSphereShape(btScalar(1.f));
btTransform trans;
trans.setIdentity();
trans.setOrigin(btVector3(0, 1, 0));

btScalar mass=1.f;
btVector3 localInertia(0, 0, 0);
bool isDynamic = (mass != 0.f);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* g_body = new btRigidBody(cInfo);
g_world->addRigidBody(g_body);

// 创建回调并碰撞检测
MyColCallBack callBack;
g_world->contactTest(g_body, callBack);

// todo delete

运行结果:
result

2. btCollisionWorld::performDiscreteCollisionDetection

performDiscreteCollisionDetection会对场景中的所有物体进行一次碰撞检测。而contactTest是对确定的物体进行碰撞检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
g_world->performDiscreteCollisionDetection();

list<btCollisionObject*> m_collisionObjects;
int numManifolds = g_world->getDispatcher()->getNumManifolds();

for(int i=0; i<numManifolds; i++)
{
btPersistentManifold* contactManifold = g_world->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = (btCollisionObject*)(contactManifold->getBody0());
btCollisionObject* obB = (btCollisionObject*)(contactManifold->getBody1());

int numContacts = contactManifold->getNumContacts();
for(int j=0; j<numContacts; j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if(pt.getDistance()<=0.f)
{
m_collisionObjects.push_back(obA);
m_collisionObjects.push_back(obB);
btVector3 posA = pt.getPositionWorldOnA();
btVector3 posB = pt.getPositionWorldOnB();
printf("%d A -> {%f, %f, %f}\n", i, posA.getX(), posA.getY(), posA.getZ()); // 碰撞点
printf("%d B -> {%f, %f, %f}\n", i, posB.getX(), posB.getY(), posB.getZ());
}
}
}

这里需要注意一下,多个物体两两碰撞的时候,列表m_collisionObjects内是存在重复的可能的,往往需要去重一下。

1
2
m_collisionObjects.sort();
m_collisionObjects.unique();

运行结果:
这里我多加了一个半径为1,位置为{1,1,0}的求,然后基本上两个球和地板发生了两两碰撞。
result

3. btDynamicsWorld::stepSimulation

完整的函数内容为:

1
2
3
4
virtual int btDynamicsWorld::stepSimulation(
btScalar timeStep,
int maxSubSteps = 1,
btScalar fixedTimeStep = btScalar(1.)/btScalar(60.))

stepSimulation其实不是用来做碰撞检测的,而是用来做物理运动模拟的。既然能做运动模拟,那肯定也能够做碰撞检测了。

3.1. 模拟运动

设置场景的重力加速为btVector3(0,-10,0),增加一个半径为1,位置为{0,100,0}的球体,并设置其质量为1,冲量为{2,0,0},即球体会以x轴速度为2,Y轴以-10的加速度做抛物线运动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 设置重力加速度
g_world->setGravity(btVector3(0,-10,0));

// 创建一个球体,并加入到场景中
btCollisionShape* shape = new btSphereShape(btScalar(1.f));
btTransform trans;
trans.setIdentity();
trans.setOrigin(btVector3(0, 100, 0));

btScalar mass=1.f;
btVector3 localInertia(0, 0, 0);
bool isDynamic = (mass != 0.f);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia);

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* g_body = new btRigidBody(cInfo);
g_body->applyCentralImpulse(btVector3(2,0,0)); // 设置冲量
g_world->addRigidBody(g_body);


for (i=0;i<10;i++)
{
g_world->stepSimulation(1.f/60.f,10); // 模拟运动

trans = g_body->getWorldTransform();
printf("world pos = %f,%f,%f\n", trans.getOrigin().getX(),
trans.getOrigin().getY(),
trans.getOrigin().getZ());
}
}

执行结果
result

官方文档:http://bulletphysics.org
开源代码:https://github.com/bulletphysics/bullet3/releases
API文档:http://bulletphysics.org/Bullet/BulletFull/annotated.html

1. 初始化物体

  1. 物体的形状由btCollisionShape对象维护;
  2. 物体的位置,旋转状态由btTransform对象维护;
  3. 最终需要将物体封装成btRigidBodybtSoftBody或其它对象;
  4. 然后将步骤3的对象加入到场景中。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
btCollisionShape* shape = new btBoxShape(btVector3(btScalar(1000.),btScalar(10.),btScalar(1000.)));
btTransform trans; // 位置、旋转维护对象
trans.setIdentity();
trans.setOrigin(btVector3(0, -10, 0)); // 设置位置

btScalar mass=0.f;
btVector3 localInertia(0, 0, 0);
bool isDynamic = (mass != 0.f);
if (isDynamic)
shape->calculateLocalInertia(mass, localInertia); // 设置惯性

btDefaultMotionState* myMotionState = new btDefaultMotionState(trans);
btRigidBody::btRigidBodyConstructionInfo cInfo(mass, myMotionState, shape, localInertia);
btRigidBody* body = new btRigidBody(cInfo); // 封装成刚体
g_world->addRigidBody(body); // 将物体添加到场景

2. 常见物体对象

  • btCollisionObject 基类
  • btRigidBody 刚体
  • btSoftBody 流体

2.1. 物体对象常用函数

  • btCollisionShape* btCollisionObject::getCollisionShape()
    • btCollisionObject对象中获取形状维护对象
  • void btCollisionObject::setFriction(btScalar frict)
    • 设置摩擦力
    • 默认值:0
  • void btCollisionObject::setRestitution(btScalar rest)
    • 设置碰撞反弹系数
    • 默认值:0
  • void btRigidBody::applyImpulse(const btVector3 & impulse, const btVector3 & rel_pos)
    • 设置冲量/动量(通过这个设置初始速度)
  • void btRigidBody::applyCentralImpulse(const btVector3 & impulse)
    • 设置冲量/动量(通过这个设置初始速度)
    • 默认值:0

3. 初始化常见物体形状

http://bulletphysics.org/Bullet/BulletFull/classbtCollisionShape.html
常见的物体有长方体、球体、胶囊体、三角网格集合。

  • btCollisionShap
    • 基类
  • btBoxShape
    • 长方体
    • BOX_SHAPE_PROXYTYPE
  • btSphereShape
    • 球体
    • SPHERE_SHAPE_PROXYTYPE
  • btCapsuleShape
    • 胶囊体
    • CAPSULE_SHAPE_PROXYTYPE
  • btBvhTriangleMeshShap
    • 三角网格
    • TRIANGLE_MESH_SHAPE_PROXYTYPE
  • btMultiSphereShape
    • 凸球体集合
    • MULTI_SPHERE_SHAPE_PROXYTYPE

3.1. 物体对象常用函数

  • int btCollisionShape::getShapeType() const
    • 获取物品类型,类型参考以下枚举
    • #include "BulletCollision/BroadphaseCollision/btBroadphaseProxy.h" //for the shape types

3.2. 三角网格btBvhTriangleMeshShape

  • 构造函数btBvhTriangleMeshShape::btBvhTriangleMeshShape(btStridingMeshInterface* meshInterface,bool useQuantizedAabbCompression)
  • 构造函数btBvhTriangleMeshShape::btBvhTriangleMeshShape(btStridingMeshInterface* meshInterface,bool useQuantizedAabbCompression, bool buildBvh = true)
  • btTriangleIndexVertexArray类集成于 btStridingMeshInterface接口。
  • btIndexedMesh 三角网格顶点列表和索引列表维护类
3.2.1. 三角网格数据假设格式如下
  • 顶点表 Vertex Buff
  • 三角形表 Index Buff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define Landscape03.txCount 1980      // 顶点数量
#define Landscape03.dxCount 11310 // 三角形数量
#include "LinearMath/btScalar.h"

btScalar Landscape03.tx[] = { // 顶点坐标列表(三维)
-3.0.0f,3.99193.,113.3.1f,
-3.0.0f,3.18397f,117.188f,
-3.6.094f,1.63.63.,113.3.1f,
...};

unsigned short Landscape03.dx[] = { // 三角形列表
0,1,3.
3,3.1,
3.3,4,
5,4,3,
4,5,6,
...};

3.2.3. btStridingMeshInterface接口

通用高性能三角网格访问接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
btStridingMeshInterface* meshInterface = new btTriangleIndexVertexArray();
btIndexedMesh part;

part.m_vertexBase = (const unsigned char*)LandscapeVtx[i];
part.m_vertexStride = sizeof(btScalar) * 3;
part.m_numVertices = LandscapeVtxCount[i];
part.m_triangleIndexBase = (const unsigned char*)LandscapeIdx[i];
part.m_triangleIndexStride = sizeof( short) * 3;
part.m_numTriangles = LandscapeIdxCount[i]/3;
part.m_indexType = PHY_SHORT;

meshInterface->addIndexedMesh(part,PHY_SHORT);

bool useQuantizedAabbCompression = true;
btBvhTriangleMeshShape* trimeshShape = new btBvhTriangleMeshShape(meshInterface,useQuantizedAabbCompression);

3.3. 长方体btBoxShape

  • 构造函数btBoxShape::btBoxShape(const btVector3 & boxHalfExtents)
  • 长宽高,封装成btVector3对象

3.4. 球btSphereShape

  • 构造函数btSphereShape::btSphereShape(btScalar radius)
  • radius xyz轴的半径,可以设置为椭圆球

3.5. 胶囊体btCapsuleShape

  • 构造函数btCapsuleShape::btCapsuleShape()
  • 构造函数btCapsuleShape::btCapsuleShape(btScalar radius, btScalar height)
  • radius 胶囊体半径,可以设置为椭圆球
  • height 胶囊体长度,height为圆心之间的距离
  • 胶囊体的aabb的边的长度为 {radius2, radius2, radius*2+height}

3.6. 凸球体集合btMultiSphereShape

  • 构造函数btMultiSphereShape (const btVector3* positions,const btScalar* radi,int numSpheres)
  • positions 球心位置集合(第一个数组地址)
  • radi 球半径集合(第一个数组地址)
  • numSpheres 球体数量

举例和效果

1
2
3
4
5
6
7
8
9
10
11
12
13
btVector3 vectors[4];
vectors[0] = btVector3(10,10,10);
vectors[1] = btVector3(20,20,20);
vectors[2] = btVector3(30,20,20);
vectors[3] = btVector3(30,10,40);

btScalar radi[4];
radi[0] = 5.f;
radi[1] = 5.f;
radi[2] = 5.f;
radi[3] = 10.f;

btCollisionShape* shape = new btMultiSphereShape(vectors, radi, 4);

example

4. 射线Raycast

  • btCollisionWorld::rayTest(const btVector3 &from, const btVector3 &to, RayResultCallback &callback)
    • 射线检测
  • btCollisionWorld::ClosestRayResultCallback
    • 射线回调
  • 回调的m_flags
    • 用于设置对物体的正反面是否生效
  • btVector3 btVector3::lerp(btVector3& v, btScalar& t)
    • 从当前坐标往v坐标方向距离t处的坐标
1
2
3
4
5
6
7
8
9
10
11
12
// 获取第一个击中坐标
btVector3 from(0,0,0);
btVector3 to(10,10,10);
btCollisionWorld::ClosestRayResultCallback callback(from, to);
callback.m_flags = 0; // (位运算) 击中面 标识符

world->rayTest(from, to, callback); // 射线击中检测
if(callback.hasHit()) // 但射线击中物体
{
// 获取击中位置坐标
btVector3 p = from.lerp(to, callback.m_closestHitFraction);
}
1
2
3
4
5
6
7
8
9
10
11
12
// 获取所有击中目标
btVector3 from(0,0,0);
btVector3 to(10,10,10);
btCollisionWorld::AllHitsRayResultCallback allCallback(from, to);
allCallback.m_flags = 0; // (位运算) 击中面 标识符

world->rayTest(from, to, allCallback);
for(int i=0; i<allCallback.m_hitFractions.size(); i++)
{
btVector3 p = from.lerp(to, allCallback.m_hitFractions[i]);
}

example

创建世界(场景)及常见函数

官方文档:http://bulletphysics.org
开源代码:https://github.com/bulletphysics/bullet3/releases
API文档:http://bulletphysics.org/Bullet/BulletFull/annotated.html

0. 世界的继承类

  • btCollisionWorld
    • 基类
  • btDynamicsWorld
    • 继承于btCollisionWorld
    • 基础的动力学实现
  • btDiscreteDynamicsWorld
    • 继承于btDynamicsWorld
    • 刚体的运动模拟

1. 创建世界(场景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化场景

// 用于配置碰撞检测堆栈大小,默认碰撞算法,接触副本池的大小
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();

// 用于计算重叠对象(碰撞检测,接触点计算)(接触点会被封装成 btPersistentManifold 对象)
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

// 提供成对的aabb重叠监测(重叠对象的管理,存储,增加删除等)
btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase();

btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
btDynamicsWorld* world = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);

2. 场景函数

  • void btDynamicsWorld::addRigidBody(btRigidBody* body)
    • 添加一个刚体
  • void btCollisionWorld::removeCollisionObject(btCollisionObject* collisionObject)
    • 删除对象
  • int btCollisionWorld::getNumCollisionObjects()
    • 获取数量
  • btAlignedObjectArray<btCollisionObject*> btCollisionWorld::getCollisionObjectArray()
    • 获取场景所有对象
  • void btDynamicsWorld::setGravity(const btVector3& gravity)
    • 设置重力
    • 默认值:btVector3(0,0,0)
  • void btDynamicsWorld::performDiscreteCollisionDetection()
    • 碰撞检测
  • int btDynamicsWorld::stepSimulation(btScalar timeStep, int maxSubSteps = 1, btScalar fixedTimeStep = btScalar(1.)/btScalar(60.))
    • 模拟运动

3. 举例

  1. 场景场景;
  2. 添加一个刚体;
  3. 释放内存退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "btBulletDynamicsCommon.h"
#include <stdio.h>

int main(int argc, char** argv)
{
int i;
///-----initialization_start-----

///collision configuration contains default setup for memory, collision setup. Advanced users can create their own configuration.
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();

///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

///btDbvtBroadphase is a good general purpose broadphase. You can also try out btAxis3Sweep.
btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase();

///the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded)
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,overlappingPairCache,solver,collisionConfiguration);

dynamicsWorld->setGravity(btVector3(0,-10,0));

///-----initialization_end-----

//keep track of the shapes, we release memory at exit.
//make sure to re-use collision shapes among rigid bodies whenever possible!
btAlignedObjectArray<btCollisionShape*> collisionShapes;


///create a few basic rigid bodies
btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(50.),btScalar(50.),btScalar(50.)));

btTransform groundTransform;
groundTransform.setIdentity();
groundTransform.setOrigin(btVector3(0,-56,0));

btScalar mass(0.);

//rigidbody is dynamic if and only if mass is non zero, otherwise static
bool isDynamic = (mass != 0.f);

btVector3 localInertia(0,0,0);
if (isDynamic)
groundShape->calculateLocalInertia(mass,localInertia);

//using motionstate is optional, it provides interpolation capabilities, and only synchronizes 'active' objects
btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,groundShape,localInertia);
btRigidBody* body = new btRigidBody(rbInfo);

//add the body to the dynamics world
dynamicsWorld->addRigidBody(body);

//cleanup in the reverse order of creation/initialization
delete body;
delete myMotionState;
delete groundShape;

delete dynamicsWorld;
delete solver;
delete overlappingPairCache;
delete dispatcher;
delete collisionConfiguration;
}