源码分析使用Cocos2d-x实现2D光线效果
来源:互联网 发布:tensorflow fcn 微调 编辑:程序博客网 时间:2024/05/22 09:06
我要介绍的,就是这样的效果:(创意和素材都来自于原文: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 == 0) {
return
false
;
}
Vec2 u(ray.p1 - segment.p1);
float
t1 = getCross(v2, u) / cross;
float
t2 = getCross(v1, u) / cross;
if
(t1 < 0 || t2 < 0 || t2 > 1) {
return
false
;
}
point = v1 * t1 + ray.p1;
distance = t1;
return
true
;
}
射线与线段的交点
三、以鼠标为光源,画射向线段端点的光线
改动一下刚才的代码,以鼠标作为光源,画射向每个端点的光线,在每条光线的两侧同时画出极角偏移1e-4的两条光线,用来穿过线段端点,与端点后面的线段相交。看起来就像这样:
四、画多边形,标记出光亮区域
上一步画的光线表示出了光亮区域,还需要画出填充多边形来标记一下,但是opengl只能画凸多边形。所以为了画出需要的不规则多边形,要分割成三角形来画。
容易看出,任意相邻的两个交点与光源,可以组成一个三角形,接下来就是找相邻的点。所以极角排序一下,依次取相邻的点就可以了。画完三角形后的效果就像这样:
五、实现本文开头的效果
Cocos2d-x提供了ClippingNode类,可以做出不规则的裁剪图形,以上一步画的多边形为模板裁剪就可以了,不多赘述,代码中有详细。
六、附上Cocos2d-x写的主要代码:
#include "HelloWorldScene.h"
USING_NS_CC;
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();
auto layer = HelloWorld::create();
scene->addChild(layer);
return
scene;
}
bool
HelloWorld::init()
{
if
(!Layer::init()) {
return
false
;
}
// 添加背景图
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);
return
true
;
};
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved,
this
);
getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener,
this
);
return
true
;
}
void
HelloWorld::onTouchMoved(Touch* touch, Event* event)
{
Point tar(0, 0);
// 光线的端点
Point cur(0, 0);
// 光线与线段的交点
float
distance = 0;
// 光源与交点的距离
_touchDraw->clear();
auto pos = touch->getLocation();
//_touchDraw->drawDot(pos, 5, Color4F::RED);
// 计算极角,并添加两个偏移1e-4的极角
initAngles(pos);
// 极角排序
std::sort(_angles.begin(), _angles.end(), [](
float
x,
float
y) {
return
x < y;
});
// 找最近的交点
std::vector<Point> vertex;
for
(auto angle : _angles) {
Vec2 dlt(
cos
(angle),
sin
(angle));
float
closest = -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个循环
// 但是显然,取余操作效率太低,分开写更好一些
int
limit = vertex.size() - 1;
for
(
int
i = 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);
}*/
}
// 画线段
void
HelloWorld::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);
}
}
// 找不重复端点
void
HelloWorld::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);
}
}
}
// 初始化极角
void
HelloWorld::initAngles(
const
Point& touchPos)
{
_angles.clear();
const
float
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);
}
}
// 向量的叉积
float
HelloWorld::getCross(
const
Vec2& v1,
const
Vec2& v2)
{
return
(v1.x * v2.y - v1.y * v2.x);
}
// 射线与线段的交点
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 == 0) {
return
false
;
}
Vec2 u(ray.p1 - segment.p1);
float
t1 = getCross(v2, u) / cross;
float
t2 = getCross(v1, u) / cross;
if
(t1 < 0 || t2 < 0 || t2 > 1) {
return
false
;
}
point = v1 * t1 + ray.p1;
distance = t1;
return
true
;
}
PS. 目前还有两个问题:1、没有实现出原文中的阴影效果 2、编译到安卓看不到效果。还希望大家与原作者交流讨论。
- 源码分析使用Cocos2d-x实现2D光线效果
- 基于Cocos2d-x的2D光线效果雏形
- Cocos2d-x 跟随光线效果实现
- 【Cocos2d-x】视线和光线:如何创建 2D 视觉范围效果
- 【Cocos2d-x】视线和光线:如何创建 2D 视觉范围效果
- 【Cocos2d-x】视线和光线:如何创建 2D 视觉范围效果
- 【Cocos2d-x】水面效果的2D实现(一)
- 【Cocos2d-x】源码分析之 2d/ui/Widget
- 【Cocos2d-x】源码分析之 2d/ui/UILayoutDefine.h
- 【Cocos2d-x】源码分析之 2d/ui/UILayout
- [Cocos2d-x]源码分析之 2d/ui/UILayout
- 【Cocos2d-x】源码分析之 2d/ui/Widget
- cocos2d-x 源码分析
- cocos2d-x 源码分析
- COCOS2D-3.X LUA 动画效果实现源码
- 【Cocos2d-x】实现翻牌效果
- cocos2d-x实现亮片效果
- cocos2d-x实现打字机效果
- 第十周项目6-贪财的富翁
- [c] 单链表创建,打印和反转
- log4j配置springMvc例子
- Apache的工作模式和最大并发请求数设置
- 免费JSON格式的服务接口
- 源码分析使用Cocos2d-x实现2D光线效果
- HDOJ 5099 Comparison of Android versions 坑题
- 第十周项目一(3)
- tn air max 2014 pas cher so the surgery begins."We picked up the small latitude mother 140 grams of
- christian louboutin pas cher after running away from home 29 hours
- rac2fs脚本
- scarpe hogan but also attracted a large number of stars
- SQL Server 日期格式转换
- hollister driver's license