用Cocos2d-x实现2D光线效果
2015.3.23优化修改,现在已经能达到稳定60帧了。。
本博客地址:http://www.cnblogs.com/wolfred7464/
创意来自于:http://ncase.me/sight-and-light/
我要介绍的,就是这样的效果:(创意和素材都来自于上文的网址)

由于原文介绍的过于简练,导致像我这样的小白根本看不懂,所以我想要介绍的更易懂一点。。
一、画线段
在Cocos2d-x中,已经封装了通过Opengl ES的画线函数,只需要创建一个DrawNode对象,就可以画线了,画几条线段,就像这样:

二、画射线和线段的交点及轨迹。
这里需要一点点几何知识了。(我也是恶补的)
直线的参数表示:
直线可以用直线上的一点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最小),连接光源和这个点,就会得到这样的效果:

主要代码:
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);
float cross = getCross(v1, v2);
if(cross == ) {
return false;
}
Vec2 u(ray.p1 - segment.p1);
float t1 = getCross(v2, u) / cross;
float t2 = getCross(v1, u) / cross;
if(t1 < || t2 < || t2 > ) {
return false;
}
point = v1 * t1 + ray.p1;
distance = t1;
return true;
}
射线与线段的交点
三、以鼠标为光源,画射向线段端点的光线
改动一下刚才的代码,以鼠标作为光源,画射向每个端点的光线,在每条光线的两侧同时画出极角偏移1e-4的两条光线,用来穿过线段端点,与端点后面的线段相交。看起来就像这样:

四、画多边形,标记出光亮区域
上一步画的光线表示出了光亮区域,还需要画出填充多边形来标记一下,但是opengl只能画凸多边形。所以为了画出需要的不规则多边形,要分割成三角形来画。
容易看出,任意相邻的两个交点与光源,可以组成一个三角形,接下来就是找相邻的点。所以极角排序一下,依次取相邻的点就可以了。画完三角形后的效果就像这样:

五、实现本文开头的效果
Cocos2d-x提供了ClippingNode类,可以做出不规则的裁剪图形,以上一步画的多边形为模板裁剪就可以了,不多赘述,代码中有详细。但是目前还有两个问题:1、没有实现出原文中的阴影效果 2、编译到安卓看不到效果。
希望有大牛能指教一下。
六、附上Cocos2d-x写的主要代码
#ifndef __LIGHTSCENE_H__
#define __LIGHTSCENE_H__ #include "cocos2d.h" class Line
{
public:
cocos2d::Point p1;
cocos2d::Point p2;
Line(const cocos2d::Point& p1, const cocos2d::Point& p2) {
this->p1 = p1;
this->p2 = p2;
}
}; class LightScene : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(LightScene);
private:
void initVertexs();
void drawSegments();
void initSingleVertexs();
void calcAngles(const cocos2d::Point& touchPos); float getCross(const cocos2d::Vec2& v1, const cocos2d::Vec2& v2);
bool getIntersection(const Line& ray, const Line& segment,
cocos2d::Point& point, float& distance); void drawLight(const cocos2d::Vec2& pos); cocos2d::DrawNode* _staticDraw;
cocos2d::DrawNode* _touchDraw;
cocos2d::ClippingNode* _clip; std::vector<cocos2d::Point> _vertexs;
std::vector<Line> _segments;
std::vector<float> _angles;
}; #endif
LightScene.h
#include "LightScene.h" USING_NS_CC; Scene* LightScene::createScene()
{
auto scene = Scene::create();
auto layer = LightScene::create();
scene->addChild(layer);
return scene;
} bool LightScene::init()
{
if (!Layer::init()) {
return false;
} // 添加背景图
auto visSize = Director::getInstance()->getVisibleSize();
auto background = Sprite::create("background.png");
background->setPosition(visSize.width / , visSize.height / );
addChild(background, ); _staticDraw = DrawNode::create();
addChild(_staticDraw, );
_touchDraw = DrawNode::create();
addChild(_touchDraw, ); _clip = ClippingNode::create();
_clip->setInverted(false);
_clip->setAlphaThreshold(255.0f);
auto foreground = Sprite::create("foreground.png");
foreground->setPosition(visSize.width / , visSize.height / );
_clip->addChild(foreground, );
_clip->setStencil(_touchDraw);
addChild(_clip, ); initVertexs();
drawSegments();
initSingleVertexs(); // 触摸监听
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = [=](Touch* touch, Event* event) {
drawLight(touch->getLocation());
return true;
};
listener->onTouchMoved = [=](Touch* touch, Event* event) {
drawLight(touch->getLocation());
};
getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
return true;
} void LightScene::drawLight(const cocos2d::Vec2& pos)
{
Point tar(, ); // 光线的端点
Point cur(, ); // 光线与线段的交点
float distance = ; // 光源与交点的距离 _touchDraw->clear(); // 计算极角,添加两个偏移1e-4的极角
calcAngles(pos); // 极角排序
std::sort(_angles.begin(), _angles.end(), [](float x, float y) {
return x < y;
}); // 找最近的交点
static std::vector<Point> vertex;
vertex.clear();
for (auto angle : _angles) {
Vec2 dlt(cos(angle), sin(angle));
float closest = -;
for (auto s : _segments) {
if (getIntersection(Line(pos, pos + dlt), s, cur, distance)) {
if (closest == - || closest > distance) {
closest = distance;
tar = cur;
}
}
}
if (closest != -) {
vertex.push_back(tar);
}
} // 画三角形
int limit = vertex.size() - ;
for (int i = ; i < limit; i++) {
_touchDraw->drawTriangle(pos, vertex[i], vertex[i + ], Color4F::WHITE);
}
if (limit > ) {
_touchDraw->drawTriangle(pos, vertex[limit], vertex[], Color4F::WHITE);
}
} void LightScene::initVertexs() {
int crd[] = {
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , ,
};
for (int i = ; i < ; i += ) {
_vertexs.push_back(Point(crd[i], crd[i + ]));
}
} // 画线段
void LightScene::drawSegments()
{
for (int i = ; i < _vertexs.size(); i += ) {
_segments.push_back(Line(_vertexs[i], _vertexs[i + ]));
_staticDraw->drawSegment(_vertexs[i], _vertexs[i + ], 0.5f, Color4F::WHITE);
}
} // 找不重复端点
void LightScene::initSingleVertexs()
{
std::vector<Point> singleVertexs;
for (int i = ; i < _vertexs.size(); i++) {
bool has = false;
for (int j = ; j < singleVertexs.size(); j++) {
if (_vertexs[i] == singleVertexs[j]) {
has = true;
break;
}
}
if (!has) {
singleVertexs.push_back(_vertexs[i]);
}
}
_vertexs.clear();
for (auto v : singleVertexs) {
_vertexs.push_back(v);
}
} // 计算极角
void LightScene::calcAngles(const Point& touchPos)
{
_angles.clear();
const float eps = static_cast<float>(1e-);
for (auto p : _vertexs) {
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);
}
} // 向量的叉积
float LightScene::getCross(const Vec2& v1, const Vec2& v2)
{
return (v1.x * v2.y - v1.y * v2.x);
} // 射线与线段的交点
bool LightScene::getIntersection(const Line& ray, const Line& segment,
Point& point, float& distance)
{
Vec2 v1(ray.p2 - ray.p1);
Vec2 v2(segment.p2 - segment.p1);
float cross = getCross(v1, v2);
if (cross == ) {
return false;
}
Vec2 u(ray.p1 - segment.p1);
float t1 = getCross(v2, u) / cross;
float t2 = getCross(v1, u) / cross;
if (t1 < || t2 < || t2 > ) {
return false;
}
point = v1 * t1 + ray.p1;
distance = t1;
return true;
}
LightScene.cpp
用Cocos2d-x实现2D光线效果的更多相关文章
- [原创]cocos2d-x研习录-第一阶 背景介绍 之 cocos2d家族史
Cocos2D是一个2D开源游戏引擎,它最早是由Ricardo Quesada(阿根廷人,社区简称Riq)和他的朋友们用Python开发的,用于开发2D游戏和基于2D图形的任何应用.最早引擎的名字源自 ...
- Cocos2D研究院之CCNode详解(三)
http://www.xuanyusong.com/archives/950 上一章我们了解了cocos2d的项目路径以及工作原理,这次作者要真刀真枪地讲解代码了,咱们先来看看cocos2d最常用.也 ...
- Index
我主要在研究.NET/C# 实现 PC IMERP 和 Android IMERP ,目的在解决企业通信中遇到的各类自动化问题 分布式缓存框架: Microsoft Velocity:微软自家分布 ...
- GitHub上整理的一些工具
技术站点 Hacker News:非常棒的针对编程的链接聚合网站 Programming reddit:同上 MSDN:微软相关的官方技术集中地,主要是文档类 infoq:企业级应用,关注软件开发领域 ...
- GitHub上整理的一些工具[转载]
Source:http://segmentfault.com/q/1010000002404545 技术站点 Hacker News:非常棒的针对编程的链接聚合网站 Programming reddi ...
- Mac下cocos2dx-3.0打包Android时,提示"SimpleAudioEngine.h"not found的解决方法
前段时间触控公布cocos2dx-3.0,在升级之后试过之后,在最初的不习惯之后,感觉比之前的好用了不少,在下之前一直是用xCode模板创建,这回算是一口气升到顶了. 之后再一次编程时须要用到Sima ...
- GitHub 开源工具整理
技术站点 Hacker News:非常棒的针对编程的链接聚合网站 Programming reddit:同上 MSDN:微软相关的官方技术集中地,主要是文档类 infoq:企业级应用,关注软件开发领域 ...
- GitHub上整理的一些工具,求补充
http://segmentfault.com/q/1010000002404545 技术站点 Hacker News:非常棒的针对编程的链接聚合网站 Programming reddit:同上 MS ...
- GitHub上整理的一些资料(转)
技术站点 Hacker News:非常棒的针对编程的链接聚合网站 Programming reddit:同上 MSDN:微软相关的官方技术集中地,主要是文档类 infoq:企业级应用,关注软件开发领域 ...
随机推荐
- iOS开发之静态库.a的制作教程
第一种方法:直接新建一个工程. 1.新建项目-> 选择 “Cocoa Touch Static Library” 2.添加库需要包含的源代码,将你工程里的代码添加到打静态库工程里: 3.配置一下 ...
- 用Windows Server 2003搭建企业内部邮件服务器
公司要搭建一个邮件服务器,方便内部邮件的发送.而且要求每位员工都可以使用自己的账号和密码.领导将这份工作交给我,不过,这可难不倒我.只要借助Windows Server 2003就可以轻松建起内部邮件 ...
- hdu 1094 A+B for Input-Output Practice (VI)
A+B for Input-Output Practice (VI) Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/327 ...
- MySQL - 启停服务
Windows 环境 命令行方式 启动 MySQL 服务: net start mysql停止 MySQL 服务: net stop mysql 注:需要以管理员身份启动 cmd 后再执行上述命令. ...
- 基于spark实现表的join操作
1. 自连接 假设存在如下文件: [root@bluejoe0 ~]# cat categories.csv 1,生活用品,0 2,数码用品,1 3,手机,2 4,华为Mate7,3 每一行的格式为: ...
- 使用jsonp跨域请求
一.异步对象,不能实现跨域请求 在站点A中访问站点B的数据: 站点A代码: window.onload = function () { document.getElementById("bt ...
- ASP.NET5/MVC6 下生成Helppage
https://github.com/domaindrivendev/Ahoy 打开nuget包管理器,搜索Swashbuckle 打开Startup.cs文件在ConfigureServices方法 ...
- Android调用系统相机以及自定义相机
0.综述 自定义相机,此处展示简单的相机功能,官方文档中还有相应关于视频拍摄的内容,此处不提 1.添加权限 <!--相机权限,数据存储--> <uses-permission and ...
- Sql 基于列的Case表达式
Case表达式可以用在 Select,update ,delete ,set,in,where ,order by,having子句之后, 只是case表达式不能控制sql程序的流程,只能作为基于列的 ...
- 谈谈asp.net中的<% %>,<%= %>,<%# %><%$ %>的使用
学而不思则罔,思而不学则殆,每天坚持一小步,则成功一大步 asp.net中的<% %>,<%= %>,<%#eval("") %><%$ ...