用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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM