zoukankan      html  css  js  c++  java
  • 用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
  • 相关阅读:
    2020软件工程作业02
    2020软件工程作业01
    为什么需要平衡二叉树?
    手机号码和邮箱等联系地址,为什么不明文显示?
    请把重要的事看轻 ——2017年终总结
    万事皆空:随缘而定
    微服务:微服务架构模式译文说明
    Mysql 查询—按位运算
    解决:spring security 登录页停留时间过长 跳转至 403页面
    excel模板解析—桥接模式:分离解析模板和业务校验
  • 原文地址:https://www.cnblogs.com/wolfred7464/p/4067107.html
Copyright © 2011-2022 走看看