用Cocos2d-x实现2D光线效果

 

2015.3.23优化修改,现在已经能达到稳定60帧了。。

 

本博客地址:http://www.cnblogs.com/wolfred7464/

创意来自于:http://ncase.me/sight-and-light/

 

我要介绍的,就是这样的效果:(创意和素材都来自于上文的网址)

7

由于原文介绍的过于简练,导致像我这样的小白根本看不懂,所以我想要介绍的更易懂一点。。

一、画线段

在Cocos2d-x中,已经封装了通过Opengl ES的画线函数,只需要创建一个DrawNode对象,就可以画线了,画几条线段,就像这样:

2

二、画射线和线段的交点及轨迹。

这里需要一点点几何知识了。(我也是恶补的)

     直线的参数表示:

直线可以用直线上的一点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最小),连接光源和这个点,就会得到这样的效果:

3

主要代码:

 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的两条光线,用来穿过线段端点,与端点后面的线段相交。看起来就像这样:

4

四、画多边形,标记出光亮区域

上一步画的光线表示出了光亮区域,还需要画出填充多边形来标记一下,但是opengl只能画凸多边形。所以为了画出需要的不规则多边形,要分割成三角形来画。

容易看出,任意相邻的两个交点与光源,可以组成一个三角形,接下来就是找相邻的点。所以极角排序一下,依次取相邻的点就可以了。画完三角形后的效果就像这样:

5

 

五、实现本文开头的效果

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
LightScene.h
  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 }
LightScene.cpp

 

posted @ 2014-11-01 14:11  Anti-Magic  阅读(3481)  评论(3编辑  收藏  举报