源码分析使用Cocos2d-x实现2D光线效果

来源:互联网 发布:tensorflow fcn 微调 编辑:程序博客网 时间:2024/05/22 09:06

我要介绍的,就是这样的效果:(创意和素材都来自于原文:http://ncase.me/sight-and-light/)

2D1.gif


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


一、画线段

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

1414983435199442.png

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

这里需要一点点几何知识了。


直线的参数表示:

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

1414983624751931.png

主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool HelloWorld::getIntersection(const Line& ray, const Line& segment,
                                Point& point, float& distance)
{
    Vec2 v1(ray.p2 - ray.p1);
    Vec2 v2(segment.p2 - segment.p1);
    floatcross = getCross(v1, v2);
    if(cross == 0) {
        returnfalse;
    }
    Vec2 u(ray.p1 - segment.p1);
    floatt1 = getCross(v2, u) / cross;
    floatt2 = getCross(v1, u) / cross;
    if(t1 < 0 || t2 < 0 || t2 > 1) {
        returnfalse;
    }
    point = v1 * t1 + ray.p1;
    distance = t1;
    returntrue;
}
射线与线段的交点


三、以鼠标为光源,画射向线段端点的光线

改动一下刚才的代码,以鼠标作为光源,画射向每个端点的光线,在每条光线的两侧同时画出极角偏移1e-4的两条光线,用来穿过线段端点,与端点后面的线段相交。看起来就像这样:

1414983731818023.jpg


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

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

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

1414983796292207.jpg


五、实现本文开头的效果

Cocos2d-x提供了ClippingNode类,可以做出不规则的裁剪图形,以上一步画的多边形为模板裁剪就可以了,不多赘述,代码中有详细。


六、附上Cocos2d-x写的主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include "HelloWorldScene.h"
USING_NS_CC;
Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    returnscene;
}
boolHelloWorld::init()
{
    if(!Layer::init()) {
        returnfalse;
    }
    // 添加背景图
    auto visSize = Director::getInstance()->getVisibleSize();
    auto background = Sprite::create("background.png");
    background->setPosition(visSize.width / 2, visSize.height / 2);
    addChild(background, 1);
    // 创建两个DrawNode,一个用来画静态线段,一个画动态线段
    _staticDraw = DrawNode::create();
    addChild(_staticDraw, 100);
    _touchDraw = DrawNode::create();
    //addChild(_touchDraw, 100);
    // 创建ClippingNode,设置底板和模板
    _clip = ClippingNode::create();
    _clip->setInverted(false);
    _clip->setAlphaThreshold(255.0f);
    auto foreground = Sprite::create("foreground.png");
    foreground->setPosition(visSize.width / 2, visSize.height / 2);
    _clip->addChild(foreground, 1);
    _clip->setStencil(_touchDraw);
    addChild(_clip, 101);
   
    // 画线段,并保存所有不重复的端点
    initSegments();
    initPoints();
    // 触摸监听
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = [=](Touch* touch, Event* event) {
        onTouchMoved(touch, event);
        returntrue;
    };
    listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved,this);
    getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener,this);
    returntrue;
}
voidHelloWorld::onTouchMoved(Touch* touch, Event* event)
{
    Point tar(0, 0);// 光线的端点
    Point cur(0, 0);// 光线与线段的交点
    floatdistance = 0; // 光源与交点的距离
    _touchDraw->clear();
    auto pos = touch->getLocation();
    //_touchDraw->drawDot(pos, 5, Color4F::RED);
    // 计算极角,并添加两个偏移1e-4的极角
    initAngles(pos);
    // 极角排序
    std::sort(_angles.begin(), _angles.end(), [](floatx, float y) {
        returnx < y;
    });
    // 找最近的交点
    std::vector<Point> vertex;
    for(auto angle : _angles) {
        Vec2 dlt(cos(angle),sin(angle));
        floatclosest = -1;
        for(auto s : _segments) {
            if (getIntersection(Line(pos, pos + dlt), s, cur, distance)) {
                if (closest == -1 || closest > distance) {
                   closest = distance;
                   tar = cur;
                }
            }
        }
        if(closest != -1) {
            vertex.push_back(tar);
        }
    }
   
    // 画三角形
    // 下面2个循环第3个参数可以写为 vertex[(i+1) % vertex.size()],合并成1个循环
    // 但是显然,取余操作效率太低,分开写更好一些
    intlimit = vertex.size() - 1;
    for(inti = 0; i < limit; i++) {
        _touchDraw->drawTriangle(pos, vertex[i], vertex[i+1], Color4F::WHITE);
    }
    if(limit > 0) {
        _touchDraw->drawTriangle(pos, vertex[limit], vertex[0], Color4F::WHITE);
    }
    //画三角形的边,Debug用
    /*for(auto v : vertex) {
        _touchDraw->drawSegment(pos, v, 0.5f, Color4F::RED);
        _touchDraw->drawDot(v, 3, Color4F::RED);
    }*/
}
// 画线段
voidHelloWorld::initSegments()
{
    _segments.clear();
    _segments.push_back(Line(Point(0, 360), Point(840, 360)));
    _segments.push_back(Line(Point(840, 360), Point(840, 0)));
    _segments.push_back(Line(Point(840, 0), Point(0, 0)));
    _segments.push_back(Line(Point(0, 0), Point(0, 360)));
    _segments.push_back(Line(Point(100, 210), Point(120, 310)));
    _segments.push_back(Line(Point(120, 310), Point(200, 280)));
    _segments.push_back(Line(Point(200, 280), Point(140, 150)));
    _segments.push_back(Line(Point(140, 150), Point(100, 210)));
    _segments.push_back(Line(Point(100, 160), Point(120, 110)));
    _segments.push_back(Line(Point(120, 110), Point(60, 60)));
    _segments.push_back(Line(Point(60, 60), Point(100, 160)));
    _segments.push_back(Line(Point(200, 100), Point(220, 210)));
    _segments.push_back(Line(Point(220, 210), Point(300, 160)));
    _segments.push_back(Line(Point(300, 160), Point(350, 40)));
    _segments.push_back(Line(Point(350, 40), Point(200, 100)));
    _segments.push_back(Line(Point(540, 300), Point(560, 320)));
    _segments.push_back(Line(Point(560, 320), Point(570, 290)));
    _segments.push_back(Line(Point(570, 290), Point(540, 300)));
    _segments.push_back(Line(Point(650, 170), Point(760, 190)));
    _segments.push_back(Line(Point(760, 190), Point(740, 90)));
    _segments.push_back(Line(Point(740, 90), Point(630, 70)));
    _segments.push_back(Line(Point(630, 70), Point(650, 170)));
    _segments.push_back(Line(Point(600, 265), Point(780, 310)));
    _segments.push_back(Line(Point(780, 310), Point(680, 210)));
    _segments.push_back(Line(Point(680, 210), Point(600, 265)));
    for(auto s : _segments) {
        _staticDraw->drawSegment(s.p1, s.p2, 0.5f, Color4F::WHITE);
    }
}
// 找不重复端点
voidHelloWorld::initPoints()
{
    for(auto segment : _segments) {
        if(_points.find(segment.p1) == _points.end()) {
            _points.insert(segment.p1);
        }
        if(_points.find(segment.p2) == _points.end()) {
            _points.insert(segment.p2);
        }
    }
}
// 初始化极角
voidHelloWorld::initAngles(constPoint& touchPos)
{
    _angles.clear();
    constfloat eps = static_cast<float>(1e-4);
    for(auto p : _points) {
        auto angle = atan2(p.y - touchPos.y, p.x - touchPos.x);
        _angles.push_back(angle);
        _angles.push_back(angle - eps);
        _angles.push_back(angle + eps);
    }
}
// 向量的叉积
floatHelloWorld::getCross(constVec2& v1, constVec2& v2)
{
    return(v1.x * v2.y - v1.y * v2.x);
}
// 射线与线段的交点
boolHelloWorld::getIntersection(constLine& ray, constLine& segment,
                                Point& point, float& distance)
{
    Vec2 v1(ray.p2 - ray.p1);
    Vec2 v2(segment.p2 - segment.p1);
    floatcross = getCross(v1, v2);
    if(cross == 0) {
        return false;
    }
    Vec2 u(ray.p1 - segment.p1);
    floatt1 = getCross(v2, u) / cross;
    floatt2 = getCross(v1, u) / cross;
    if(t1 < 0 || t2 < 0 || t2 > 1) {
        return false;
    }
    point = v1 * t1 + ray.p1;
    distance = t1;
    returntrue;
}

PS. 目前还有两个问题:1、没有实现出原文中的阴影效果 2、编译到安卓看不到效果。还希望大家与原作者交流讨论

0 0