用Cocos2d-x實現2D光線效果

 

2015.3.23優化修改,如今已經能達到穩定60幀了。。ide

 

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

創意來自於:http://ncase.me/sight-and-light/優化

 

我要介紹的,就是這樣的效果:(創意和素材都來自於上文的網址)this

7

因爲原文介紹的過於簡練,致使像我這樣的小白根本看不懂,因此我想要介紹的更易懂一點。。spa

1、畫線段

在Cocos2d-x中,已經封裝了經過Opengl ES的畫線函數,只須要建立一個DrawNode對象,就能夠畫線了,畫幾條線段,就像這樣:3d

2

2、畫射線和線段的交點及軌跡。

這裏須要一點點幾何知識了。(我也是惡補的)code

     直線的參數表示:

直線能夠用直線上的一點P0和方向向量v表示,直線上的全部點P知足 P = P0 + tv。對象

參數方程最方便的地方在於直線、射線、線段的方程形式是同樣的,區別在於參數t。直線的t沒有範圍限制,射線的t>0,線段的t在0~1之間(t >=0 && t <= 1)。blog

     直線交點:

設直線分別爲 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 }
射線與線段的交點

 

3、以鼠標爲光源,畫射向線段端點的光線

改動一下剛纔的代碼,以鼠標做爲光源,畫射向每一個端點的光線,在每條光線的兩側同時畫出極角偏移1e-4的兩條光線,用來穿過線段端點,與端點後面的線段相交。看起來就像這樣:

4

4、畫多邊形,標記出光亮區域

上一步畫的光線表示出了光亮區域,還須要畫出填充多邊形來標記一下,可是opengl只能畫凸多邊形。因此爲了畫出須要的不規則多邊形,要分割成三角形來畫。

容易看出,任意相鄰的兩個交點與光源,能夠組成一個三角形,接下來就是找相鄰的點。因此極角排序一下,依次取相鄰的點就能夠了。畫完三角形後的效果就像這樣:

5

 

5、實現本文開頭的效果

Cocos2d-x提供了ClippingNode類,能夠作出不規則的裁剪圖形,以上一步畫的多邊形爲模板裁剪就能夠了,很少贅述,代碼中有詳細。可是目前還有兩個問題:一、沒有實現出原文中的陰影效果 二、編譯到安卓看不到效果。

但願有大牛能指教一下。

6、附上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
相關文章
相關標籤/搜索