用Cocos2d-x实现2D光线效果
2015.3.23优化修改,现在已经能达到稳定60帧了。。
本博客地址:http://www.cnblogs.com/wolfred7464/
创意来自于:http://ncase.me/sight-and-light/
我要介绍的,就是这样的效果:(创意和素材都来自于上文的网址)
由于原文介绍的过于简练,导致像我这样的小白根本看不懂,所以我想要介绍的更易懂一点。。
一、画线段
在Cocos2d-x中,已经封装了通过Opengl ES的画线函数,只需要创建一个DrawNode对象,就可以画线了,画几条线段,就像这样:
二、画射线和线段的交点及轨迹。
这里需要一点点几何知识了。(我也是恶补的)
直线的参数表示:
直线可以用直线上的一点P0和方向向量v表示,直线上的所有点P满足 P = P0 + tv。
参数方程最方便的地方在于直线、射线、线段的方程形式是一样的,区别在于参数t。直线的t没有范围限制,射线的t>0,线段的t在0~1之间(t >=0 && t <= 1)。
直线交点:
设直线分别为 P+t1v 和 Q+t2w,设向量u=QP,设cross(x, y)为向量x和y的叉积,则:
t1 = cross(w, u) / cross(v, w)
t2 = cross(v, u) / cross(v, w)
当cross(v, w) == 0时,两直线平行,无交点。
所以把屏幕中心作为光源,方向指向鼠标所在的位置,画一条射线,t即是光源与交点的距离,选一个最近的交点(即t最小),连接光源和这个点,就会得到这样的效果:
主要代码:
1 bool HelloWorld::getIntersection(const Line& ray, const Line& segment, 2 Point& point, float& distance) 3 { 4 Vec2 v1(ray.p2 - ray.p1); 5 Vec2 v2(segment.p2 - segment.p1); 6 float cross = getCross(v1, v2); 7 if(cross == 0) { 8 return false; 9 } 10 Vec2 u(ray.p1 - segment.p1); 11 float t1 = getCross(v2, u) / cross; 12 float t2 = getCross(v1, u) / cross; 13 if(t1 < 0 || t2 < 0 || t2 > 1) { 14 return false; 15 } 16 point = v1 * t1 + ray.p1; 17 distance = t1; 18 return true; 19 }
三、以鼠标为光源,画射向线段端点的光线
改动一下刚才的代码,以鼠标作为光源,画射向每个端点的光线,在每条光线的两侧同时画出极角偏移1e-4的两条光线,用来穿过线段端点,与端点后面的线段相交。看起来就像这样:
四、画多边形,标记出光亮区域
上一步画的光线表示出了光亮区域,还需要画出填充多边形来标记一下,但是opengl只能画凸多边形。所以为了画出需要的不规则多边形,要分割成三角形来画。
容易看出,任意相邻的两个交点与光源,可以组成一个三角形,接下来就是找相邻的点。所以极角排序一下,依次取相邻的点就可以了。画完三角形后的效果就像这样:
五、实现本文开头的效果
Cocos2d-x提供了ClippingNode类,可以做出不规则的裁剪图形,以上一步画的多边形为模板裁剪就可以了,不多赘述,代码中有详细。但是目前还有两个问题:1、没有实现出原文中的阴影效果 2、编译到安卓看不到效果。
希望有大牛能指教一下。
六、附上Cocos2d-x写的主要代码
1 #ifndef __LIGHTSCENE_H__ 2 #define __LIGHTSCENE_H__ 3 4 #include "cocos2d.h" 5 6 class Line 7 { 8 public: 9 cocos2d::Point p1; 10 cocos2d::Point p2; 11 Line(const cocos2d::Point& p1, const cocos2d::Point& p2) { 12 this->p1 = p1; 13 this->p2 = p2; 14 } 15 }; 16 17 class LightScene : public cocos2d::Layer 18 { 19 public: 20 static cocos2d::Scene* createScene(); 21 virtual bool init(); 22 CREATE_FUNC(LightScene); 23 private: 24 void initVertexs(); 25 void drawSegments(); 26 void initSingleVertexs(); 27 void calcAngles(const cocos2d::Point& touchPos); 28 29 float getCross(const cocos2d::Vec2& v1, const cocos2d::Vec2& v2); 30 bool getIntersection(const Line& ray, const Line& segment, 31 cocos2d::Point& point, float& distance); 32 33 void drawLight(const cocos2d::Vec2& pos); 34 35 cocos2d::DrawNode* _staticDraw; 36 cocos2d::DrawNode* _touchDraw; 37 cocos2d::ClippingNode* _clip; 38 39 std::vector<cocos2d::Point> _vertexs; 40 std::vector<Line> _segments; 41 std::vector<float> _angles; 42 }; 43 44 #endif
1 #include "LightScene.h" 2 3 USING_NS_CC; 4 5 Scene* LightScene::createScene() 6 { 7 auto scene = Scene::create(); 8 auto layer = LightScene::create(); 9 scene->addChild(layer); 10 return scene; 11 } 12 13 bool LightScene::init() 14 { 15 if (!Layer::init()) { 16 return false; 17 } 18 19 // 添加背景图 20 auto visSize = Director::getInstance()->getVisibleSize(); 21 auto background = Sprite::create("background.png"); 22 background->setPosition(visSize.width / 2, visSize.height / 2); 23 addChild(background, 1); 24 25 _staticDraw = DrawNode::create(); 26 addChild(_staticDraw, 100); 27 _touchDraw = DrawNode::create(); 28 addChild(_touchDraw, 100); 29 30 _clip = ClippingNode::create(); 31 _clip->setInverted(false); 32 _clip->setAlphaThreshold(255.0f); 33 auto foreground = Sprite::create("foreground.png"); 34 foreground->setPosition(visSize.width / 2, visSize.height / 2); 35 _clip->addChild(foreground, 1); 36 _clip->setStencil(_touchDraw); 37 addChild(_clip, 101); 38 39 initVertexs(); 40 drawSegments(); 41 initSingleVertexs(); 42 43 // 触摸监听 44 auto listener = EventListenerTouchOneByOne::create(); 45 listener->onTouchBegan = [=](Touch* touch, Event* event) { 46 drawLight(touch->getLocation()); 47 return true; 48 }; 49 listener->onTouchMoved = [=](Touch* touch, Event* event) { 50 drawLight(touch->getLocation()); 51 }; 52 getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); 53 return true; 54 } 55 56 void LightScene::drawLight(const cocos2d::Vec2& pos) 57 { 58 Point tar(0, 0); // 光线的端点 59 Point cur(0, 0); // 光线与线段的交点 60 float distance = 0; // 光源与交点的距离 61 62 _touchDraw->clear(); 63 64 // 计算极角,添加两个偏移1e-4的极角 65 calcAngles(pos); 66 67 // 极角排序 68 std::sort(_angles.begin(), _angles.end(), [](float x, float y) { 69 return x < y; 70 }); 71 72 // 找最近的交点 73 static std::vector<Point> vertex; 74 vertex.clear(); 75 for (auto angle : _angles) { 76 Vec2 dlt(cos(angle), sin(angle)); 77 float closest = -1; 78 for (auto s : _segments) { 79 if (getIntersection(Line(pos, pos + dlt), s, cur, distance)) { 80 if (closest == -1 || closest > distance) { 81 closest = distance; 82 tar = cur; 83 } 84 } 85 } 86 if (closest != -1) { 87 vertex.push_back(tar); 88 } 89 } 90 91 // 画三角形 92 int limit = vertex.size() - 1; 93 for (int i = 0; i < limit; i++) { 94 _touchDraw->drawTriangle(pos, vertex[i], vertex[i + 1], Color4F::WHITE); 95 } 96 if (limit > 0) { 97 _touchDraw->drawTriangle(pos, vertex[limit], vertex[0], Color4F::WHITE); 98 } 99 } 100 101 void LightScene::initVertexs() { 102 int crd[] = { 103 0, 360, 840, 360, 104 840, 360, 840, 0, 105 840, 0, 0, 0, 106 0, 0, 0, 360, 107 100, 210, 120, 310, 108 120, 310, 200, 280, 109 200, 280, 140, 150, 110 140, 150, 100, 210, 111 100, 160, 120, 110, 112 120, 110, 60, 60, 113 60, 60, 100, 160, 114 200, 100, 220, 210, 115 220, 210, 300, 160, 116 300, 160, 350, 40, 117 350, 40, 200, 100, 118 540, 300, 560, 320, 119 560, 320, 570, 290, 120 570, 290, 540, 300, 121 650, 170, 760, 190, 122 760, 190, 740, 90, 123 740, 90, 630, 70, 124 630, 70, 650, 170, 125 600, 265, 780, 310, 126 780, 310, 680, 210, 127 680, 210, 600, 265 128 }; 129 for (int i = 0; i < 100; i += 2) { 130 _vertexs.push_back(Point(crd[i], crd[i + 1])); 131 } 132 } 133 134 // 画线段 135 void LightScene::drawSegments() 136 { 137 for (int i = 0; i < _vertexs.size(); i += 2) { 138 _segments.push_back(Line(_vertexs[i], _vertexs[i + 1])); 139 _staticDraw->drawSegment(_vertexs[i], _vertexs[i + 1], 0.5f, Color4F::WHITE); 140 } 141 } 142 143 // 找不重复端点 144 void LightScene::initSingleVertexs() 145 { 146 std::vector<Point> singleVertexs; 147 for (int i = 0; i < _vertexs.size(); i++) { 148 bool has = false; 149 for (int j = 0; j < singleVertexs.size(); j++) { 150 if (_vertexs[i] == singleVertexs[j]) { 151 has = true; 152 break; 153 } 154 } 155 if (!has) { 156 singleVertexs.push_back(_vertexs[i]); 157 } 158 } 159 _vertexs.clear(); 160 for (auto v : singleVertexs) { 161 _vertexs.push_back(v); 162 } 163 } 164 165 // 计算极角 166 void LightScene::calcAngles(const Point& touchPos) 167 { 168 _angles.clear(); 169 const float eps = static_cast<float>(1e-4); 170 for (auto p : _vertexs) { 171 auto angle = atan2(p.y - touchPos.y, p.x - touchPos.x); 172 //_angles.push_back(angle); 173 _angles.push_back(angle - eps); 174 _angles.push_back(angle + eps); 175 } 176 } 177 178 // 向量的叉积 179 float LightScene::getCross(const Vec2& v1, const Vec2& v2) 180 { 181 return (v1.x * v2.y - v1.y * v2.x); 182 } 183 184 // 射线与线段的交点 185 bool LightScene::getIntersection(const Line& ray, const Line& segment, 186 Point& point, float& distance) 187 { 188 Vec2 v1(ray.p2 - ray.p1); 189 Vec2 v2(segment.p2 - segment.p1); 190 float cross = getCross(v1, v2); 191 if (cross == 0) { 192 return false; 193 } 194 Vec2 u(ray.p1 - segment.p1); 195 float t1 = getCross(v2, u) / cross; 196 float t2 = getCross(v1, u) / cross; 197 if (t1 < 0 || t2 < 0 || t2 > 1) { 198 return false; 199 } 200 point = v1 * t1 + ray.p1; 201 distance = t1; 202 return true; 203 }