AI - FlowField(流场寻路)

FlowField流场寻路,利用网格存储每个点对目标点的推力,网格上的单位根据对于推力进行移动。用于大量单位进行寻路对于同一目的地的寻路,常用于rts游戏等。

对应一张网格地图(图中黑块是不可行走区域)
在这里插入图片描述

生成热度图

计算所有网格对于目标点(图中红点)网格的路径距离。(每个格子的移动距离算作1)。
通过dijkstra算法遍历出每个格子的路径距离.(a *算法启发函数结果为0就是dijkstra算法。之前NavMesh寻路有说明过a *算法)

void FlowFieldScene::createHeadMap() {unordered_map<int, float> openList;unordered_map<int, float> closeList;_distNode->removeAllChildren();_dist.clear();//FlowFieldMathHelper::mapHeight 地图高度,即地图在y轴上的格子数for (int i = 0; i <= FlowFieldMathHelper::mapHeight; i++) {vector<float> d;d.resize(FlowFieldMathHelper::mapWidth + 1, 0);_dist.push_back(d);}//转换实际位置到网格坐标的位置Vec2 gridPos = FlowFieldMathHelper::getGridPos(_touchBeganPosition);//每个网格都有个唯一idint gridId = FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y);openList.emplace(gridId, 0);while (!openList.empty()) {pair<int, float> node = *openList.begin();for (auto n : openList) {if (node.second > n.second) node = n;}openList.erase(node.first);closeList.insert(node);Vec2 gridPos = FlowFieldMathHelper::getGridPosById(node.first);_dist[gridPos.y][gridPos.x] = node.second;//FlowFieldMathHelper::getNeighbor获取周边的格子,计算热度图四向就够auto neighbors = FlowFieldMathHelper::getNeighbor(node.first);for (auto neighbor : neighbors) {//isBlock 判断是否是阻挡格if (isBlock(neighbor.first)) continue;if (closeList.find(neighbor.first) != closeList.end()) continue;if (openList.find(neighbor.first) == openList.end()) {openList.emplace(neighbor.first, neighbor.second + node.second);}else {if (openList[neighbor.first] > neighbor.second + node.second) {openList[neighbor.first] = neighbor.second + node.second;}}}}
}

在这里插入图片描述

生成向量图

生成热度图之后,遍历每个网格,查找他所有相邻的网格(8向),选择到目标点路径距离最小的网格(阻挡网格的路径距离无穷大),把当前网格的向量指向对应网格,最终生成矢量图
(注意,当周围有阻挡网格时,要判断不能斜向穿过阻挡格)
在这里插入图片描述
当矢量是左上(-1,1)左下(-1,-1)右上(1,1)右下(1,-1),判断目标格子周围的阻挡格。静止矢量斜向穿过障碍物

//静止倾斜穿过障碍物
bool FlowFieldScene::checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY) {if (offsetX * offsetY == 0) return false;if (isBlock(gridX + offsetX, gridY) || isBlock(gridX, gridY + offsetY)) return true;return false;
}

生成向量图

void FlowFieldScene::createVectorMap() {_vectorNode->clear();_vectorMap.clear();for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {if (_dist[y][x] == 0) continue;Vec2 direct;float neighborDist = -1;for (int offsetX = -1; offsetX <= 1; offsetX++) {for (int offsetY = -1; offsetY <= 1; offsetY++) {int toX = x + offsetX;int toY = y + offsetY;if (isBlock(toX, toY)) continue;else if (x == toX && y == toY) continue;else if (toX < 0 || toX > FlowFieldMathHelper::mapWidth) continue;else if (toY < 0 || toY > FlowFieldMathHelper::mapHeight) continue;if ( neighborDist == -1 || neighborDist > _dist[toY][toX] ) {if (checkObliqueAngleBlock(x, y, offsetX, offsetY)) continue;neighborDist = _dist[toY][toX];direct = Vec2(offsetX, offsetY);}}}_vectorMap.emplace(FlowFieldMathHelper::getGridIdByGridPos(x, y), direct);}}
}

在这里插入图片描述

设置转向力

根据设置的流场向量获得转向力

//_flowFieldDirect为设置的当前格子流场转向力方向
Vec2 MoveNode::flowField() {if (_flowFieldDirect == Vec2::ZERO) return Vec2::ZERO;Vec2 desiredVelocity = _flowFieldDirect * _dtSpeed;Vec2 steering;if (MoveSmooth) steering = desiredVelocity - _velocity;else steering = desiredVelocity;return steering;
}

加入之前的steering系统,转向系统,集群模拟

void MoveNode::update(float dt)
{findNeighbourObjs();_dtSpeed = _speed * dt;Vec2 steering = Vec2::ZERO;steering += seek(_tarPos);steering += flee();steering += wander();steering += pursuit();steering += cohesion();steering += separation();steering += alignment();steering += flowField();steering = turncate(steering, _maxForce);steering *= ( 1 / (float)_mass );_velocity += steering;_velocity += wallAvoid();_velocity = turncate(_velocity, _maxSpeed * dt);updatePos();
}

此时如果直接取当前所在网格的向量作流场力的方向,会出现两个问题

void FlowFieldScene::update(float dt) {for (auto node : _moveNodes) {int gridId = FlowFieldMathHelper::getGridId(node->getPosition());Vec2 direct = _vectorMap[gridId];node->setFlowFieldDirect(direct);}
}

1.如果转向力所占的权重不够大,会导致物体转向不及时,并且可能插入阻挡格的情况
请添加图片描述

2.如果增大转向力所占权重,又容易导致物体直接贴着网格边缘行走,而非沿着中线行走
请添加图片描述

为了改善这种情况,根据所处当前格的不同位置,选取不同方向的三个相邻网格,加上自身网格的4个向量进行线插值
在这里插入图片描述
进行双线性插值

双线性插值

在这里插入图片描述
获取4个向量后,先把网格y值相同的向量,进行两两线性插值,再把求出的两个新向量进行线性插值即可

注意,双线性插值的话,如果目标点再阻挡格旁边,而阻挡格又没有向量(取0)的话,目标会直接穿过阻挡格
请添加图片描述

因此如果获取的网格是阻挡格,则直接取阻挡网格指向当前格的方向做插值。
这个是只有流场力的单独优化,实际项目中,不同力的权重可能不同,真正避免与阻挡物碰撞。还是要加上专门的碰撞避免处理(如阻挡物周围加力场,ORCA等)
在这里插入图片描述
在这里插入图片描述

Vec2 FlowFieldScene::bilinearInterpolation(Vec2 curPosition) {Vec2 gridPos = FlowFieldMathHelper::getGridPos(curPosition);int offsetX, offsetY;if (curPosition.x < gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = -1;else if (curPosition.x == gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = 0;else offsetX = 1;if (curPosition.y < gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = -1;else if (curPosition.y == gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = 0;else offsetY = 1;Vec2 v1, v2, v3, v4;v1 = getVectorByGridPos(gridPos);bool noX = gridPos.x == FlowFieldMathHelper::mapWidth || isBlock(gridPos.x + offsetX, gridPos.y);//if (noX) v2 = getVectorByGridPos(gridPos);if (noX) v2 = Vec2(-offsetX, 0);else v2 = getVectorByGridPos(gridPos + Vec2(offsetX, 0));bool noY = gridPos.y == FlowFieldMathHelper::mapHeight || isBlock(gridPos.x, gridPos.y + offsetY);//if (noY) v3 = getVectorByGridPos(gridPos);if (noY) v3 = Vec2(0, -offsetY);else v3 = getVectorByGridPos(gridPos + Vec2(0, offsetY));//if (noX || noY) v4 = getVectorByGridPos(gridPos);if (noX || noY) Vec2(-offsetX, -offsetY);else v4 = getVectorByGridPos(gridPos + Vec2(offsetX, offsetY));;float xWeight = abs(curPosition.x - gridPos.x * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;v1 = v1.lerp(v2, xWeight);v3 = v3.lerp(v4, xWeight);float yWeight = abs(curPosition.y - gridPos.y * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;Vec2 direct = v1.lerp(v3, yWeight);direct.normalize();return direct;
}
void FlowFieldScene::update(float dt) {for (auto node : _moveNodes) {int gridId = FlowFieldMathHelper::getGridId(node->getPosition());Vec2 direct = bilinearInterpolation(node->getPosition());
//            Vec2 direct = _vectorMap[gridId];node->setFlowFieldDirect(direct);}
}

通过双线性插值的方式,物体的行动轨迹会更加靠近与正中路线。
请添加图片描述

流场力和一些其他力结合的效果

请添加图片描述

源码

FlowField.h

#ifndef __FLOW_FIELD_SCENE_H__
#define __FLOW_FIELD_SCENE_H__#include "cocos2d.h"
#include "CrowdSimulation/MoveNodeManager.h"
USING_NS_CC;
using namespace std;class FlowFieldScene : public Scene
{
public:static Scene* createScene();virtual bool init();virtual bool onTouchBegan(Touch* touch, Event* unused_event);// implement the "static create()" method manuallyCREATE_FUNC(FlowFieldScene);void createHeadMap();void showHeatInfo();void createVectorMap();bool checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY);bool isBlock(int gridX, int gridY);bool isBlock(int gridId);void resetMoveNode();Vec2 getVectorByGridPos(Vec2 gridPos);Vec2 bilinearInterpolation(Vec2 curPosition);void update(float dt);protected:EventListenerTouchOneByOne* _touchListener;Vec2 _touchBeganPosition;DrawNode* _mapDrawNode;Node* _distNode;DrawNode* _vectorNode;DrawNode* _tarDot;unordered_map<int, bool> _blockGridIdMap;vector<vector<float>> _dist;unordered_map<int, Vec2> _vectorMap;MoveNodeManager* _manager;vector<MoveNode*> _moveNodes;
};#endif

FlowField.cpp

#include "FlowFieldScene.h"
#include "FlowFieldMathHelper.h"Scene* FlowFieldScene::createScene()
{return FlowFieldScene::create();
}vector<pair<Vec2, Vec2>> blockLine = {make_pair(Vec2(200,400), Vec2(200,300)),make_pair(Vec2(220,400), Vec2(220,300)),make_pair(Vec2(240,400), Vec2(240,300)),make_pair(Vec2(260,550), Vec2(260,250)),make_pair(Vec2(280,350), Vec2(600,350)),make_pair(Vec2(700, 280), Vec2(1100, 280)),make_pair(Vec2(750, 330), Vec2(1150, 330)),make_pair(Vec2(115, 115), Vec2(115, 150)),make_pair(Vec2(115, 115), Vec2(215, 115)),make_pair(Vec2(270, 87), Vec2(666, 87)),make_pair(Vec2(777, 575), Vec2(1233, 575)),make_pair(Vec2(500, 400), Vec2(500, 466)),make_pair(Vec2(370, 222), Vec2(577, 222)),make_pair(Vec2(888, 124), Vec2(1277, 124)),
};static void problemLoading(const char* filename)
{printf("Error while loading: %s\n", filename);printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in FlowFieldScene.cpp\n");
}// on "init" you need to initialize your instance
bool FlowFieldScene::init()
{//// 1. super init firstif (!Scene::init()){return false;}auto visibleSize = Director::getInstance()->getVisibleSize();Vec2 origin = Director::getInstance()->getVisibleOrigin();auto layer = LayerColor::create(Color4B(255, 255, 255, 255));layer:setContentSize(visibleSize);this->addChild(layer);auto node = DrawNode::create();this->addChild(node);
//    for (int i = FlowFieldMathHelper::gridLen; i < visibleSize.width; i += FlowFieldMathHelper::gridLen) {
//        node->drawSegment(Vec2(i, 0), Vec2(i, 640), 1, Color4F(0, 0, 0, 0.3));
//    }
//    for (int i = FlowFieldMathHelper::gridLen; i < visibleSize.height; i += FlowFieldMathHelper::gridLen) {
//        node->drawSegment(Vec2(0, i), Vec2(1400, i), 1, Color4F(0, 0, 0, 0.3));
//    }for (auto line : blockLine) {if (line.first.x == line.second.x) {auto minY = min(line.first.y, line.second.y);auto maxY = max(line.first.y, line.second.y);int gridMinY = minY / FlowFieldMathHelper::gridLen;int gridMaxY = maxY / FlowFieldMathHelper::gridLen + 1;int gridX = line.first.x / FlowFieldMathHelper::gridLen;for (auto y = gridMinY; y <= gridMaxY; y ++) {int id = FlowFieldMathHelper::getGridIdByGridPos(gridX, y);_blockGridIdMap.emplace(id, true);}Vec2 v1 = Vec2(gridX, gridMinY);Vec2 v2 = Vec2(gridX, gridMaxY);vector<Vec2> v = {Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),Vec2(v1.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),Vec2(v1.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v2.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),Vec2(v1.x * FlowFieldMathHelper::gridLen, v2.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),};node->drawPolygon(reinterpret_cast<Vec2*>(v.data()), v.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));}else if (line.first.y == line.second.y) {auto minX = min(line.first.x, line.second.x);auto maxX = max(line.first.x, line.second.x);int gridMinX = minX / FlowFieldMathHelper::gridLen;int gridMaxX = maxX / FlowFieldMathHelper::gridLen + 1;int gridY = line.first.y / FlowFieldMathHelper::gridLen;for (auto x = gridMinX; x <= gridMaxX; x++) {int id = FlowFieldMathHelper::getGridIdByGridPos(x, gridY);_blockGridIdMap.emplace(id, true);}Vec2 v1 = Vec2(gridMinX, gridY);Vec2 v2 = Vec2(gridMaxX, gridY);vector<Vec2> v = {Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),Vec2(v2.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),Vec2(v2.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),};node->drawPolygon(reinterpret_cast<Vec2*>(v.data()), v.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));}}_mapDrawNode = DrawNode::create();this->addChild(_mapDrawNode);_distNode = Node::create();this->addChild(_distNode);_vectorNode = DrawNode::create();this->addChild(_vectorNode);_tarDot = DrawNode::create();this->addChild(_tarDot);_tarDot->drawDot(Vec2::ZERO, 5, Color4F(1, 0, 0, 1));_tarDot->setPosition(Vec2(777, 444));_touchListener = EventListenerTouchOneByOne::create();_touchListener->setSwallowTouches(true);_touchListener->onTouchBegan = CC_CALLBACK_2(FlowFieldScene::onTouchBegan, this);this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, layer);_manager = new MoveNodeManager();this->scheduleUpdate();return true;
}bool FlowFieldScene::onTouchBegan(Touch* touch, Event* event)
{_touchBeganPosition = touch->getLocation();CCLOG("==========》 %f, %f", _touchBeganPosition.x, _touchBeganPosition.y);if (FlowFieldMathHelper::getGridId(_touchBeganPosition) == -1) {resetMoveNode();return true; }_tarDot->setPosition(_touchBeganPosition);createHeadMap();createVectorMap();
//    resetMoveNode();return true;
}void FlowFieldScene::update(float dt) {for (auto node : _moveNodes) {int gridId = FlowFieldMathHelper::getGridId(node->getPosition());Vec2 direct = bilinearInterpolation(node->getPosition());
//            Vec2 direct = _vectorMap[gridId];node->setFlowFieldDirect(direct);}
}Vec2 FlowFieldScene::getVectorByGridPos(Vec2 gridPos) {return _vectorMap[FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y)];
}Vec2 FlowFieldScene::bilinearInterpolation(Vec2 curPosition) {Vec2 gridPos = FlowFieldMathHelper::getGridPos(curPosition);int offsetX, offsetY;if (curPosition.x < gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = -1;else if (curPosition.x == gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = 0;else offsetX = 1;if (curPosition.y < gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = -1;else if (curPosition.y == gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = 0;else offsetY = 1;Vec2 v1, v2, v3, v4;v1 = getVectorByGridPos(gridPos);bool noX = gridPos.x == FlowFieldMathHelper::mapWidth || isBlock(gridPos.x + offsetX, gridPos.y);//if (noX) v2 = getVectorByGridPos(gridPos);if (noX) v2 = Vec2(-offsetX, 0);else v2 = getVectorByGridPos(gridPos + Vec2(offsetX, 0));bool noY = gridPos.y == FlowFieldMathHelper::mapHeight || isBlock(gridPos.x, gridPos.y + offsetY);//if (noY) v3 = getVectorByGridPos(gridPos);if (noY) v3 = Vec2(0, -offsetY);else v3 = getVectorByGridPos(gridPos + Vec2(0, offsetY));//if (noX || noY) v4 = getVectorByGridPos(gridPos);if (noX || noY) Vec2(-offsetX, -offsetY);else v4 = getVectorByGridPos(gridPos + Vec2(offsetX, offsetY));;float xWeight = abs(curPosition.x - gridPos.x * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;v1 = v1.lerp(v2, xWeight);v3 = v3.lerp(v4, xWeight);float yWeight = abs(curPosition.y - gridPos.y * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;Vec2 direct = v1.lerp(v3, yWeight);direct.normalize();return direct;
}void FlowFieldScene::createHeadMap() {unordered_map<int, float> openList;unordered_map<int, float> closeList;_distNode->removeAllChildren();_dist.clear();for (int i = 0; i <= FlowFieldMathHelper::mapHeight; i++) {vector<float> d;d.resize(FlowFieldMathHelper::mapWidth + 1, 0);_dist.push_back(d);}Vec2 gridPos = FlowFieldMathHelper::getGridPos(_touchBeganPosition);int gridId = FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y);openList.emplace(gridId, 0);while (!openList.empty()) {pair<int, float> node = *openList.begin();for (auto n : openList) {if (node.second > n.second) node = n;}openList.erase(node.first);closeList.insert(node);Vec2 gridPos = FlowFieldMathHelper::getGridPosById(node.first);_dist[gridPos.y][gridPos.x] = node.second;auto neighbors = FlowFieldMathHelper::getNeighbor(node.first);for (auto neighbor : neighbors) {if (isBlock(neighbor.first)) continue;if (closeList.find(neighbor.first) != closeList.end()) continue;if (openList.find(neighbor.first) == openList.end()) {openList.emplace(neighbor.first, neighbor.second + node.second);}else {if (openList[neighbor.first] > neighbor.second + node.second) {openList[neighbor.first] = neighbor.second + node.second;}}}}
//    showHeatInfo();
}void FlowFieldScene::showHeatInfo() {_distNode->removeAllChildren();for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {if (!isBlock(x, y)) {auto num = Label::create();num->setColor(Color3B(0, 255, 0));//num->setSystemFontSize(20);num->setSystemFontSize(20);_distNode->addChild(num);auto s = to_string(_dist[y][x]);//num->setString(s.substr(0, s.find(".") + 2));num->setString(s.substr(0, s.find(".")));num->setPosition(Vec2(x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2));}}}
}bool FlowFieldScene::isBlock(int gridX, int gridY) {return isBlock(FlowFieldMathHelper::getGridIdByGridPos(gridX, gridY));
}
bool FlowFieldScene::isBlock(int gridId) {return _blockGridIdMap.find(gridId) != _blockGridIdMap.end();
}//静止倾斜穿过障碍物
bool FlowFieldScene::checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY) {if (offsetX * offsetY == 0) return false;if (isBlock(gridX + offsetX, gridY) || isBlock(gridX, gridY + offsetY)) return true;return false;
}void FlowFieldScene::createVectorMap() {_vectorNode->clear();_vectorMap.clear();for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {if (_dist[y][x] == 0) continue;Vec2 direct;float neighborDist = -1;for (int offsetX = -1; offsetX <= 1; offsetX++) {for (int offsetY = -1; offsetY <= 1; offsetY++) {int toX = x + offsetX;int toY = y + offsetY;if (isBlock(toX, toY)) continue;else if (x == toX && y == toY) continue;else if (toX < 0 || toX > FlowFieldMathHelper::mapWidth) continue;else if (toY < 0 || toY > FlowFieldMathHelper::mapHeight) continue;if ( neighborDist == -1 || neighborDist > _dist[toY][toX] ) {if (checkObliqueAngleBlock(x, y, offsetX, offsetY)) continue;neighborDist = _dist[toY][toX];direct = Vec2(offsetX, offsetY);}}}
//            Vec2 v1 = Vec2(x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2);
//            Vec2 v2 = v1 + (direct * FlowFieldMathHelper::gridLen / 2);
//            _vectorNode->drawSegment(v1, v2, 1, Color4F(0, 0, 1, 1));_vectorMap.emplace(FlowFieldMathHelper::getGridIdByGridPos(x, y), direct);}}
}void FlowFieldScene::resetMoveNode() {if (_moveNodes.empty()) {for (int i = 0; i < 100; i++) {auto moveNode = _manager->getFlowFieldNode();this->addChild(moveNode);_moveNodes.push_back(moveNode);}}float width = FlowFieldMathHelper::mapWidth * FlowFieldMathHelper::gridLen;float height = FlowFieldMathHelper::mapHeight * FlowFieldMathHelper::gridLen;for (auto node : _moveNodes) {Vec2 v;do {v.x = RandomHelper::random_real<float>(10, width-10);v.y = RandomHelper::random_real<float>(10, height-10);} while (isBlock(FlowFieldMathHelper::getGridId(v)));node->setPos(v);//Vec2 vg = FlowFieldMathHelper::getGridPos(v);//node->setPos(Vec2(vg.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, vg.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2));}
}

FlowFieldMathHelper.h

#pragma once
#ifndef __FLOWFIELD_MATH_H__
#define __FLOWFIELD_MATH_H__#include "cocos2d.h"
USING_NS_CC;
using namespace std;class FlowFieldMathHelper
{
public:static Vec2 getGridPos(Vec2 pos);static int getGridId(Vec2 pos);static int getGridIdByGridPos(int gridX, int gridY);static vector<pair<int, float>> getNeighbor(int gridId);static Vec2 getGridPosById(int gridId);static const int gridLen = 24;static const int mapWidth = 1400 / gridLen - 1;static const int mapHeight = 640 / gridLen - 1;};#endif

FlowFieldMathHelper.cpp

#include "FlowFieldMathHelper.h"Vec2 FlowFieldMathHelper::getGridPos(Vec2 pos) {return Vec2(int(pos.x / gridLen), int(pos.y / gridLen));
}int FlowFieldMathHelper::getGridId(Vec2 pos) {Vec2 v = getGridPos(pos);return getGridIdByGridPos(v.x, v.y);
}int FlowFieldMathHelper::getGridIdByGridPos(int gridX, int gridY) {if (gridX < 0 || gridX > mapWidth) return -1;if (gridY < 0 || gridY > mapHeight) return -1;return gridX + gridY * (mapWidth + 1);
}Vec2 FlowFieldMathHelper::getGridPosById(int gridId) {int x = gridId % (mapWidth + 1);int y = (gridId - x) / (mapWidth + 1);return Vec2(x, y);
}vector<pair<int, float>> FlowFieldMathHelper::getNeighbor(int gridId) {Vec2 pos = getGridPosById(gridId);vector<pair<int, float>> ret;/*for (int offsetX = -1; offsetX <= 1; offsetX++) {for (int offsetY = -1; offsetY <= 1; offsetY++) {int x = pos.x + offsetX;int y = pos.y + offsetY;if (x == pos.x && y == pos.y) continue;else if (x < 0 || x > mapWidth) continue;else if (y < 0 || y > mapHeight) continue;else if (x == pos.x || y == pos.y) ret.push_back(make_pair(getGridIdByGridPos(x, y), 1));else ret.push_back(make_pair(getGridIdByGridPos(x, y), 1.5));}}*/for (int offsetX = -1; offsetX <= 1; offsetX++) {for (int offsetY = -1; offsetY <= 1; offsetY++) {int x = pos.x + offsetX;int y = pos.y + offsetY;if (x == pos.x && y == pos.y) continue;else if (x < 0 || x > mapWidth) continue;else if (y < 0 || y > mapHeight) continue;else if (x == pos.x || y == pos.y) ret.push_back(make_pair(getGridIdByGridPos(x, y), 1));}}return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/211255.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java零基础——docker篇

1.【熟悉】docker简介 1.1 什么是docker Docker是一个开源项目&#xff0c;诞生于2013年初&#xff0c;最初是dotCloud公司内部的一个业余项目。它基于Google公司推出的Go语言实现。项目后来加入了Linux基金会&#xff0c;遵从了Apache2.0协议&#xff0c;项目代码在GitHub上进…

iptables防火墙之SNAT与DNET

NAT 1.SNAT&#xff1a;让内网可以访问外网 2.DNAT&#xff1a;让外网可以访问到内网的机器 网关服务器&#xff0c;要开启路由功能 内核功能&#xff1a; sysctl -a 列出所有参数 内核参数&#xff0c;然后grep可以查看到默认的内核参数 内核参数配置文件 /etc/sysctl.…

TLS、对称/非对称加密、CA认证

1. SSL与TLS SSL/TLS是一种密码通信框架&#xff0c;他是世界上使用最广泛的密码通信方法。SSL/TLS综合运用了密码学中的对称密码&#xff0c;消息认证码&#xff0c;公钥密码&#xff0c;数字签名&#xff0c;伪随机数生成器等&#xff0c;可以说是密码学中的集大成者。 TLS…

【android开发-16】android中文件和sharedpreferences数据存储详解

1&#xff0c;文件读写方式的数据存储 下面是一个简单的示例&#xff0c;演示如何在Android中使用内部存储来保存和读取文件&#xff1a; 保存文件&#xff1a; try { String data "这是要保存的数据"; FileOutputStream fos openFileOutput("myFile"…

在微信小程序中如何改变默认打开的页面

在微信小程序中&#xff0c;在我们编写页面的时候&#xff0c;可能会在重新渲染的时候导致页面跳转到默认打开的页面上&#xff0c;为了提升用户的一个体验&#xff0c;我们可以设置一些内容来修改小程序默认打开的页面&#xff0c;提升开发者的开发体验。 当我们打开一个微信…

linux之buildroot(3)配置软件包

Linux之buildroot(3)配置软件包 Author&#xff1a;Onceday Date&#xff1a;2023年11月30日 漫漫长路&#xff0c;才刚刚开始… 全系列文章请查看专栏: buildroot编译框架_Once_day的博客-CSDN博客。 参考文档&#xff1a; Buildroot - Making Embedded Linux Easymdev.t…

陀螺仪LSM6DSV16X与AI集成(2)----姿态解算

陀螺仪LSM6DSV16X与AI集成.2--姿态解算 概述视频教学样品申请完整代码下载欧拉角万向节死锁四元数法姿态解算双环PI控制器偏航角陀螺仪解析代码上位机通讯加速度演示陀螺仪工作方式主程序演示 概述 LSM6DSV16X包含三轴陀螺仪与三轴加速度计。 姿态有多种数学表示方式&#xff…

go-fastfds部署心得

我是windows系统安装 Docker Desktop部署 docker run --name go-fastdfs&#xff08;任意的一个名称&#xff09; --privilegedtrue -t -p 3666:8080 -v /data/fasttdfs_data:/data -e GO_FASTDFS_DIR/data sjqzhang/go-fastdfs:lastest docker run&#xff1a;该命令用于运…

二手物品交易系统详细功能介绍

这是二手物品交易系统的详细功能介绍&#xff1a; 二手交易系统&#xff1a;该系统主要服务于二手交易市场&#xff0c;允许卖家发布二手商品信息&#xff0c;买家可以询价、购买支付&#xff0c;同时支持发布高价回收信息。多城市切换&#xff1a;用户可以切换到不同城市&…

LLM推理部署(四):一个用于训练、部署和评估基于大型语言模型的聊天机器人的开放平台FastChat

FastChat是用于对话机器人模型训练、部署、评估的开放平台。体验地址为&#xff1a;https://chat.lmsys.org/&#xff0c;该体验平台主要是为了收集人类的真实反馈&#xff0c;目前已经支持30多种大模型&#xff0c;已经收到500万的请求&#xff0c;收集了10万调人类对比大模型…

力扣题:字符的统计-12.2

力扣题-12.2 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;423. 从英文中重建数字 解题思想&#xff1a;有的单词通过一个字母就可以确定&#xff0c;依次确定即可 class Solution(object):def originalDigits(self, s):""":typ…

Linux部署HDFS集群

&#xff08;一&#xff09;VMware虚拟机中部署 ps、其中node1、node2、node3替换为自己相应节点的IP地址&#xff0c;或者host文件中配置过的主机名&#xff0c;或者看前置准备 或者查看前置准备&#xff1a;Linux部署HDFS集群前置准备 1.下载压缩包 https://www.apache.or…

STM32L051使用HAL库操作实例(13)- 读取IAQ-CORE-C传感器实例

目录 一、前言 二、传感器参数 三、STM32CubeMX配置&#xff08;本文使用的STM32CubeMX版本为6.1.2&#xff09;例程使用模拟I2C进行数据读取 1.MCU选型 2.使能时钟 3.时钟配置 4.GPIO口配置 四、配置STM32CubeMX生成工程文件 五、点击GENERATE CODE生成工程文件 六、…

IT外包服务内容有哪些?

在信息技术迅猛发展的今天&#xff0c;越来越多的企业为了提高效率、降低成本以及更专注于核心业务&#xff0c;选择将信息技术工作外包给专业的IT服务公司。IT外包包含很多不同的服务&#xff0c;以下是对主要服务内容的简要概述。 1. 网络建设与维护 网络是现代企业信息系统…

同调群的维度 和 同调群的秩

同调群的维度是指同调群中非零元素的最小阶数。与线性代数中对向量空间的维度的理解类似。对同调群&#xff0c;k维同调群的维度是k。 同调群的秩是指同调群中的自由部分的维度。同调群通常包含自由部分和挠部分。同调群的秩是指同调群中自由部分的维度。对同调群&#xff0c;…

Hadoop学习笔记(HDP)-Part.14 安装YARN+MR

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

【SpringCloud系列】@FeignClient微服务轻舞者

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

RocketMQ-核心编程模型

RocketMQ的消息模型 深入理解RocketMQ的消息模型 RocketMQ客户端基本流程 RocketMQ基于Maven提供了客户端的核心依赖&#xff1a; <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><version&…

unity学习笔记19

一、角色动画的使用练习 从资源商店导入的动画资源&#xff08;Character Pack: Free Sample&#xff09;中将资源中的角色创建在场景里&#xff0c;现在场景里存在的角色并没有任何动画。 在资源中找到Animations文件夹&#xff0c;在这个文件有很多模型文件&#xff08;.FBX…

什么牌子的开放式耳机好?开放式耳机选购指南来了!

在当今音频科技不断演进的时代&#xff0c;开放式耳机作为一种受欢迎的音频设备&#xff0c;吸引着越来越多的消费者&#xff0c;与封闭式耳机相比&#xff0c;开放式耳机在音质表现和舒适度上都具有独特的优势&#xff0c;在众多品牌和型号中选择一款满足个人需求的开放式耳机…