Flocking算法海王的鱼塘是怎样炼成的
Posted 知心宝贝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flocking算法海王的鱼塘是怎样炼成的相关的知识,希望对你有一定的参考价值。
目录
一、引言
正式开始之前,我们先来搞懂一下究竟什么叫Flocking算法?
Flocking algorithm 国内一般称为蜂拥算法,由许多离散的动物形成,但群体整体上是流动的,这是个体行为的综合结果。
典型的自然现象包括:蜂群、鸟群、鱼群、兽群等,这些动物聚集的现象(包括人类)可以帮助生物更好的躲避天敌、迁徙、获取食物......
下面放几张图片:
野兽迁徙
狼群捕食
蜂群制造蜂蜜
大雁南飞
鱼群移动
二、发展
1987 年 7 月,Craig Reynolds这位老先生率先提出了经典的Flocking模型,该模型要求群体行为满足三个规则:
-
聚合:独立的个体逐渐加入到群体
-
速度匹配:个体与群体的航向保持一致,不要脱离
-
分离:避免群体内的个体相互碰撞
三、鱼群
下面主要在Unity3D中,实现一个简易的鱼群模拟,实现了生成鱼群、聚合鱼群、速度匹配、捕食、分离等功能模块,下面来介绍一下。
1.组件
为了,更好的管理鱼群,我们在脚本中定义了一个组件。
[Header("Fish Setting")]//控制面板
[Range(0.0f,5.0f)]
public float min;//速度最小值
[Range(0.0f, 5.0f)]
public float max;//速度最小值
[Range(1.0f, 10.0f)]
public float neighborDistance;//聚合的距离
[Range(0.0f, 5.0f)]
public float RotationSpeed;//转速
2.生成鱼群
在脚本Create里面要定义一个范围,让数组里面的鱼群在这个范围内生成、移动。
public GameObject prefab1;//🐟种类1
public GameObject prefab2;//🐟种类2
public int fishnum=50;//初始化🐟数量
public GameObject[] fish;//数组存储
public Vector3 swimlimt = new Vector3(5, 5, 5);//边界10*10*10
生成的话,我们采取随机生成,范围还是固定在边界范围内部。
public void Start()
fish = new GameObject[fishnum];
for(int i=0;i<fishnum;i++)
Vector3 pos = this.transform.position + new Vector3(Random.Range(-swimlimt.x,swimlimt.x),
Random.Range(0, swimlimt.y),
Random.Range(-swimlimt.z, swimlimt.z));
if(i%2==0)
fish[i] = (GameObject)Instantiate(prefab1,pos,Quaternion.identity);//克隆🐟
else
fish[i] = (GameObject)Instantiate(prefab2, pos, Quaternion.identity);
fish[i].GetComponent<FlockSpeed>().sp = this;//两个脚本间联系
3.鱼群运动
对于鱼群的移动,要在FlockSpeed脚本里面添加速度、方向。
private void Update()
speed = Random.Range(sp.min, sp.max);//速度范围
this.transform.Translate(0, 0, speed * Time.deltaTime);//开始移动
这时候,鱼群只会朝向Z轴移动,其他的什么也完成不了,下面的步骤才是Flocking算法应用的关键。
四、聚合
聚合是Flocking算法的关键,这个算法会把鱼聚集到一起。
还记得我们之前组件定义的变量neighborDistance,这个是聚合的距离。假如两只鱼之间的距离<=neighborDistance,那么它就属于这个集群,我们要想办法把这只鱼加到集群里面来。
那如何让加入的鱼满足整体,不至于脱离呢?这时候就需要鱼群的中心位置,也叫平均位置。
对于一个鱼群来说,每一个🐟都有一个特定的位置,不可能出现两只🐟重合的情况,所以:
平均位置=鱼群位置相加的总和/鱼群的数量
红色星星就是计算的中心位置,最下面的🐟neighborDistance,但满足如果还不修正位置,它就会跑出集群了。向量都学过吧,对于一个三维的坐标,上图红色箭头的向量等于坐标:
average-desired
。
所以,这只鱼需要转向中心位置,转向的时候还需要注意不要碰到别的鱼,这部分留到分离的时候细讲。
void Community()
GameObject[] gos=sp.fish;
float Distance;
Vector3 center = Vector3.zero;
Vector3 avoid = Vector3.zero;
int size=0;
float goSpeed=0.01f;
foreach(GameObject go in gos)
if(go!=this.gameObject)//遍历数组内除掉本身所有的鱼
Distance = Vector3.Distance(this.transform.position, go.transform.position);//相邻两条鱼距离
if (Distance<=sp.neighborDistance)//满足集群规则
size++;
center+= go.transform.position;
if (Distance < limt)//避免碰撞
avoid += this.transform.position - go.transform.position;
FlockSpeed flockSpeed = go.GetComponent<FlockSpeed>();
goSpeed += flockSpeed.speed;
if(size>0)
center = center / size+(sp.goal- this.transform.position );//后面加上的是目标位置,朝着目标移动
goSpeed = goSpeed / size;//平均速度
Vector3 direction = (center + avoid) - transform.position;
if(direction!=Vector3.zero)//需要转向
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), sp.RotationSpeed * Time.deltaTime);//from to 平滑旋转 Quaternion.LookRotation注视旋转
五、速度匹配
如上图所示,每一条🐟都有一个前进的方向。我们要保障所有鱼的速度最终和虚拟领航者一致,那么,对于整个集群来说,虚拟领航者的速度是什么?
虚拟领航者的速度=总体速度和/鱼群数目
如上图所示,current heading和 average heading朝向有偏差,所以要调整方向。
调整之后,两个方向保持一致。
最后,集群内部的每条鱼要达到这样的效果。
速度匹配是goSpeed
变量,代码部分也在上面的Community()函数里面了。
六、捕食
鱼群捕食的实现,首先定义一个目标,这个目标有10%的概率移动,鱼群会跟随目标移动。
private void Update()
//10%的几率更换目标
if(Random.Range(0,100)<10)
goal = this.transform.position + new Vector3(Random.Range(-swimlimt.x, swimlimt.x),
Random.Range(0, swimlimt.y),
Random.Range(-swimlimt.z, swimlimt.z));
为了更加清晰的看见鱼群的移动,我关闭了随机移动,手动更换目标物体的位置。可以看到,鱼群跟随目标的移动而移动。
七、分离
1.躲避🐟
为了避免鱼群内部的鱼相互碰撞,要指定一个分离的原则。
如上图所示,鱼本来要向中心位置聚合的,但由于会碰撞到其他的鱼,所以需要转向。
🐟和🐟之间的躲避变量是avoid
,代码部分也在上面的Community()函数里面了。
2.躲避边界
一开始,我们定义了一个10*10*10的边界还记得吗?
为了防止鱼游动超出边界,我们要定义一个转向,让鱼即将超出边界时,掉头向着中心方向移动。
Bounds b = new Bounds(sp.transform.position,sp.swimlimt);//边界的盒子
Vector3 direction = Vector3.zero;
RaycastHit hit = new RaycastHit();
if (!b.Contains(transform.position))//即将超出边界
turning = true;//开始转向
direction = sp.transform.position - transform.position;//转向方向指向中心位置
else
turning = false;
3.躲避障碍
鱼群在移动的过程中,可以会遇到障碍物、捕食者,这时候需要鱼群做出快速避障的功能。
初中的物理知识,入射角=反射角
,我们需要鱼的脑子上面向前发射一条长长的射线,碰到障碍物的时候。
原本的方向等于反射的光线。
判断转向
if(Physics.Raycast(this.transform.position,transform.forward*5,out hit))//遇到障碍物
//Debug.DrawRay(this.transform.position,this.transform.forward*5,Color.red);红色射线
direction = Vector3.Reflect(this.transform.forward, hit.normal);//反射角 = 入射角 转向
turning = true;
开始转向
if(turning)
this.transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation(direction), sp.RotationSpeed * Time.deltaTime);
else
Community();//聚合函数
八、效果展示
这里gif图片太大了,放不了!!的gif图片,我特意把鱼群的目标放到了柱子里面,看看鱼会不会直直的撞向柱子?
鱼一般可以看清12m以内的物体,这里我将鱼的视野范围特意扩大了5倍。事实上,当鱼脑袋上面发射的红线,碰到海底的这个大柱子时就会发生转向。
但由于目标没有移动,所以鱼群只好围绕着柱子旋转(团队合作捕猎),直到我将目标移出柱子,鱼群才开始远离。
九、总结
Flocking算法是群居动物的合作行为,通过个体间的相互作用实现整体的目标。本文实现的只是一个简单的🐟集群模拟,仅仅是模式识别领域数据聚类的一个算法分支,转向角度、视野、领队方面还没有实现。
总之,非常感谢大家耐着性子阅读这篇长文( ̄︶ ̄)。
【参考资料】
数据库是怎样炼成的:查询处理优化篇
“数据库系统中最复杂的地方有两个,一是查询优化,二是数据库引擎,著名的DDIA很大篇幅讲了存储引擎和分布式数据的概念,但对于sql数据库系统并没有侧重,所以又翻出了大学课本数据库系统概论,回顾下一些基础概念。”
本文主要介绍关系型数据库的查询处理和查询优化技术,查询优化主要分为代数优化和物理优化,代数优化指关系代数表达式的优化,物理优化指存取路径和底层操作算法的优化
01
—
关系型数据库的查询处理
查询处理的任务是把用户提交给RDBMS的查询语句转化为高效的执行计划,查询处理可以分为四个阶段:查询分析、查询检查、查询优化和查询执行。
1、查询分析
首先对查询语句进行扫描、词法分析和语法分析。识别出关键字、属性、关系名等,进行语法检查,判断语句是否符合SQL的语法规则。(词法、语法、语义分析)
2、查询检查
对合法的查询语句进行语义检查,检查语句中的数据库对象、属性、关系是否存在和有效,还要根据数据字典中的用户权限和完整性约束定义的用户的存取权限进行检查,检查过后便把SQL转换为等价的关系代数表达式,一般使用查询树(语法分析树),来表示扩展的关系代数表达式。(这部分主要是安全性检查和完整性检查)
3、查询优化
每个查询都会有许多可供选择的执行策略和操作算法,查询优化就是选择一个高效的处理策略,按照操作的层次一般可以分为代数优化和物理优化,代数优化指按照一定的规则改变代数表达式中的操作次序和组合(关系表达式等价变换规则);物理优化则是存取路径和底层操作算法的选择。其依据可以是基于规则、代价、语义等。
4、 查询执行
依据优化器得到的执行策略生成执行计划,由代码生成器生成执行这个查询计划的代码。
02
—
查询操作的算法实例
本节简单举例几种选择和连接操作的实现算法思想,每一种都有多种具体实现。
一、选择操作的实现
1、简单的全表扫描
对查询的基本表顺序扫描,逐一检查每个元组是否满足。
2、索引扫描方法
如果选择的属性上有索引(b树或hash),可以用索引扫描,用过先找到满足条件的元组主码,直接在查询的基本表中找到元组。
如果是大于小于的范围操作,先找到阈值处的索引,以此为入口点在B+树的顺序集上找到所有元组指针,之后在回表。
如果是多个条件,一是先找到这些条件的元组交集,然后回表,二是先找到一个条件的元组,回表,再继续在得到的元组中判断下一组条件。
二、连接操作的实现(只说了自然连接)
1、嵌套循环方法
对外层循环的每一个元组,检索内存循环的每一个元组,并检查两个元组在连接属性上是否相等,直到外层的处理完。
2、排序-合并方法
先对两个表的连接属性进行排序,然后循环外层循环表做处理,两个表都只要扫描一遍。
3、索引连接 Index Join 方法
首先在内层循环表的连接属性上建立索引,然后循环外层表通过索引找到对应的内层表并连接起来,循环处理直到外层的元组处理完成
4、Hash Join方法
把连接属性作为Hash码,用同一个Hash函数将两个元组集散列到同一个文件中。第一步划分阶段,对包含较少元组的表分散到Hash表的桶中。第二步试探阶段,把另一个表处理一遍,把桶中另一张表的元组连过来。
03
—
关系型数据库的查询优化
示例语句:
SELECT Student.name FROM Student, Class WHERE Student.Sno = Class.Sno AND Class.Cno = '2';
判断三种情况:
1、计算广义笛卡尔积,然后在中间结果上做选择,最后在name属性上投影
2、计算自然连接,然后再中间结果上做选择,最后同样投影
3、先对Class表做选择,使用选择的结果做连接,最后投影
这三种对应不同的代数表达式,体现出代数优化的重要性,在此基础上如果Cno和Sno上有索引的话,连接过程也就不必连接基本表,而是可以使用上节提到的index join方法,这就是物理优化。
04
—
尾语
代数优化和物理优化非常多,后面在续,跳过了很多基础部分,比如完整性、安全性和关系理论,着重在查询优化、数据库恢复(checkpoint)和并发控制三节,理论篇就这些了。
以上是关于Flocking算法海王的鱼塘是怎样炼成的的主要内容,如果未能解决你的问题,请参考以下文章