【Unity3D】关于Vector3向量与Quaternion四元数的转换与应用

四月 7th, 2015 9 views

在Unity中大家对Vector3应该说是很熟悉了,这里就简单带过一下,Vector3表示一个三维向量(x,y,z),例如Vector3.forward等价于new Vectory(0,0,1),即x=0,y=0,z=1的一个向量。图就不画了,大家高中都学过。
而Quaternion表示一个四元数,何为四元数,例如ai+bj+ck+d这样一个超复数,篇幅有限高数这里也不多说了,我们只关注一下Unity的四元数类的使用就好。在Unity中Quaternion(四元数类)主要用来处理物体的旋转,其实数变量由x,y,z,w四个参数构成,区间[-1,1]。
例如绕y轴旋转180°写做(0,1,0,0),调用公式计算过程如下:

由欧拉旋转(X,Y,Z)转换为四元数(x,y,z,w)
—————————————————->
x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2)
y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2)
z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2)
w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2)
q = (x, y, z, w)
—————————————————->
X = 0
Y = 180
Z = 0
—————————————————->
x = 0
y = 1
z = 0
w = 0

在Unity中这一套转换可以直接调用方法Quaternion.Euler(0, 180, 0)或者transform.Rotate(0, 180, 0)完成。euler即欧拉角的意思,欧拉角可以用Vector进行表示,表示在x,y,z三轴上的旋转角度。
同时transform.rotation即是一个四元数,表示物体的旋转角度方向,我们可以通过Quaternion.Euler和Quaternion.eulerAngles(但这个方法在Unity4之后的版本已经过时)将欧拉角转换为四元数。

因此假设需要让一个物体的顺时针方向以y轴旋转90°有以下几种方法:

1
2
3
4
5
6
7
8
//用四元数的欧拉方法 旋转到90°
transform.rotation = Quaternion.Euler(new Vector3(0, 90, 0));

//用四元数的向量方法 Vector3(1, 0, 0) 表示1个90°方向的向量
transform.rotation = Quaternion.LookRotation(new Vector3(1, 0, 0));

//每一帧自旋转90°
transform.Rotate(new Vector3(0, 90, 0));

以上方法均可在一帧之内旋转顺时针绕y轴旋转90°。但通常游戏中有一个缓慢转动的过程,如果需要一步到位,而是慢慢旋转到90°应该如何去做呢。我们可以使用以下方法:

1
2
3
4
5
//使用四元数的球形差值方法 在每一帧中调整与目标四元数的差值
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(new Vector3(1, 0, 0)), 10 * Time.deltaTime);

//使用Vector3的RotateTowards方法将一个向量转向另一个向量
transform.rotation = Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, new Vector3(1, 0, 0), Time.deltaTime, Time.deltaTime));

从理论上来说球形差值Quaternion.Slerp的转向要平滑一些但相应的运算量大,每一帧都在出现误差值所以每一帧都在校调,而RotateTowards的方法要简洁但粗暴一些,但转向的平滑性不如前者好,特别是在多方向同时操作上,会出现一些不平滑的问题。
综上,建议在设计转向模块的时候,保留两种方案,在实际操作中对比两种方案的显示效果,择优选用。

本篇到此,欢迎大家补充新的方法,用以达到以上两端代码块同样的效果。

BeiTown
2015.04.07

Protobuf 动态反射填充机制(JAVA)

十月 23rd, 2014 79 views

利用此方案可进行自动化Protobuf的功能拓展,核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
com.google.protobuf.GeneratedMessage.Builder builder;
Object object;

/* 遍历protobuf builder体 取出字段 */
for (int i = 0; i < builder.getDescriptorForType().getFields().size(); i++) {

    FieldDescriptor pf = builder.getDescriptorForType().getFields().get(i);// 获取proto字段
    Field f = object.getClass().getDeclaredField(pf.getName());// 获取同名成员变量
    Method m = object.getClass().getMethod("get" + f.getName());// 获取该成员变量的get方法
    Object value = m.invoke(object);
    builder.setField(pf, value);

}

builder.build();

明者自明,不做过多解释了,本篇到此,谢谢关注。

BeiTown
2014-10-23

QT拖拽文件、文件夹及遍历文件夹简单案例

九月 19th, 2014 9 views

首先是实现QT的窗体拖拽功能将文件拖拽进入Mainwindow,需要实现代码如下:

1
2
3
4
5
6
7
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setAcceptDrops(true);//将Mainwindow设置成可相应拖拽事件
}

这里的 setAcceptDrops(true); 将Mainwindow设置成可相应拖拽事件,接下来,重载dragEnterEvent和dropEvent方法,实现对拖拽内容的判断和操作,如下:

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
//拖入Mainwindow时触发
void MainWindow::dragEnterEvent(QDragEnterEvent  *event){
    if(event->mimeData()->hasUrls())
    {
        foreach(QUrl url, event->mimeData()->urls())
        {
            if(!url.isEmpty())
                event->accept();//界面反馈表示可拖拽
        }
        return;
    }
    else
    {
        event->ignore();//界面反馈表示不可拖拽
    }
}

//松开鼠标后触发
void MainWindow::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls())
    {
        foreach(QUrl url, event->mimeData()->urls())
        {
            QFileInfo fileInfo(url.toString());
            QString filePath = fileInfo.filePath().remove(0, QDir::currentPath().size() + 9);

            if(fileInfo.fileName().contains(".")){//根据是否带"."判断是否是文件夹
                //属于文件
            }else{
                //属于文件夹
                FindFile(filePath);//遍历文件夹递归函数
            }
        }
        return;
    }
    return;

}

Read the rest of this entry »

QT实现启动画面

九月 10th, 2014 3 views

代码如下,详解见注释,效果请自行编译运行

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
#include "mainwindow.h"
#include <QApplication>
#include <QSplashScreen>
#include <QPixmap>
#include <QTextCodec>
#include <QElapsedTimer>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);


    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(codec);


    QSplashScreen splash(QPixmap("./images/logo.png"));
    splash.setDisabled(true); //禁用用户的输入事件响应 否则点击会消失
    splash.show();

    splash.showMessage(QString::fromUtf8("检测版本信息中..."),
                       Qt::AlignCenter|Qt::AlignBottom,Qt::black);


    //实现延时2s
    int delayTime = 2;
    QElapsedTimer timer;
    timer.start();
    while(timer.elapsed() < (delayTime * 1000))
    {
        a.processEvents();
    }

    MainWindow w;
    splash.finish(&w);//关闭启动画面
    w.show();

    return a.exec();
}

BeiTown
2014.09.10

QWebView保存帐号密码功能实现

八月 13th, 2014 27 views

以下用QT模拟一下常规浏览器对帐号密码的保存

首先是获取QWebview中的表单内容,需要保存的表单中的两个input如下:

1
2
帐号:<input name="username" type="text"/>
密码:<input name="password" type="password"/>

在QT中获取这两个input的值的需要使用带JS的方法,首先通过findFirstElement()方法找到目标标签,然后调用js方法获取其值,如下:

1
2
3
//获取帐号密码
qDebug()< <ui->webView->page()->mainFrame()->findFirstElement("#username").evaluateJavaScript("this.value").toString();
qDebug()< <ui->webView->page()->mainFrame()->findFirstElement("#password").evaluateJavaScript("this.value").toString();

具体在何处调用以上方法视应用设计的情况而定,这里我使用了QWebview::loadStarted()信号来触发,因为提交表单时必会触发这个信号。
Read the rest of this entry »

QT使用单例模式获取主窗体Mainwindow指针

七月 22nd, 2014 25 views

最近一直在用QT做项目,信号和槽机制甚是好用,但是前提需求是在connect时必须同时传入Object1和Object2的指针(见《QT 多线程(UI线程)间的signal/slot(信号与槽)通信》),这样一来,我们如果需要在其他类中绑定Mainwindow的槽函数,则必须在该类初始化时传入Mainwindow的指针,甚是麻烦。甚至有时是间隔了好几个类。
查询了网络上的一些方法,稍微可行一些的是使用parentWidget()方法,但是还是没有从根本上解决全局获取Mainwindow指针的问题。
既然是全局,我们的基本解决思路就是static化,实现起来其实也非常简单,使Mainwindow变成单例即可,代码如下:

首先是Mainwindow.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <qmainwindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    static MainWindow *GetInstance();//获取单例方法

private:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    Ui::MainWindow *ui;
    static MainWindow *m_pInstance;//单例指针

};

#endif // MAINWINDOW_H
</qmainwindow>

Read the rest of this entry »

QT5读取及修改cookie简单样例

七月 3rd, 2014 23 views

介于网上(中文网页)没有一篇完整的关于QT5读写cookie的简单案例,特有此篇。

①准备工作,定义cookie控制器
在开始获取cookie之前我们需要做一些准备工作,先在mainwindow下拖出一个webview,这里不上图了,以后会比较少上图,有些图华而不实,文字看得懂的自然都看得懂。
之后给这个webview定义一个networkAccessManager,#include什么的这些常识问题大家自己F1。

1
2
3
4
5
6
7
8
9
10
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    _cookieJar = new MyCookieJar();
    //这里设置networkAccessManager
    ui->webView->page()->networkAccessManager()->setCookieJar( _cookieJar );

}

上文中的_cookieJar是我们自己定义的一个MyCookieJar类,继承了QT自带的QNetworkCookieJar。
这里我自己封装了一些方法也放出来吧:

MyCookieJar.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef MYCOOKIEJAR_H
#define MYCOOKIEJAR_H
#include <qnetworkcookiejar>
#include <qnetworkcookie>
#include <qnetworkrequest>
class MyCookieJar : public QNetworkCookieJar
{
    Q_OBJECT
public:
    explicit MyCookieJar(QObject *parent = 0);

    QList<qnetworkcookie> getCookies();
    void setCookies(const QList</qnetworkcookie><qnetworkcookie>& cookieList);
    void addCookies(QString name,QString value);
    void changeCookie(QString name,QString value);
};
#endif // MYCOOKIEJAR_H
</qnetworkcookie></qnetworkrequest></qnetworkcookie></qnetworkcookiejar>

MyCookieJar.cpp

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
#include "mycookiejar.h"

MyCookieJar::MyCookieJar(QObject *parent) :
    QNetworkCookieJar(parent)
{
}


QList<qnetworkcookie> MyCookieJar::getCookies()

{
    return  this->allCookies();
}

void MyCookieJar::setCookies(const QList</qnetworkcookie><qnetworkcookie>& cookieList)
{
    if(this == NULL)
        return;
    this->setAllCookies(cookieList);
}

void MyCookieJar::addCookies(QString name,QString value)
{
    QNetworkCookie cookie(name.toUtf8(),value.toUtf8());
    this->insertCookie(cookie);
}

void MyCookieJar::changeCookie(QString name,QString value)
{
    QNetworkCookie cookie(name.toUtf8(),value.toUtf8());
    this->updateCookie(cookie);

}
</qnetworkcookie>

Read the rest of this entry »

Redis异常JedisConnectionException:Read timed out解决笔记

六月 25th, 2014 458 views

笔记①

异常如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
    at redis.clients.jedis.Protocol.process(Protocol.java:131)
    at redis.clients.jedis.Protocol.read(Protocol.java:187)
    at redis.clients.jedis.Connection.getIntegerReply(Connection.java:200)
    at redis.clients.jedis.BinaryJedis.incrBy(BinaryJedis.java:633)
    at java.util.TimerThread.mainLoop(Unknown Source)
    at java.util.TimerThread.run(Unknown Source)
Caused by: java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at redis.clients.util.RedisInputStream.fill(RedisInputStream.java:109)
    at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:45)
    at redis.clients.jedis.Protocol.process(Protocol.java:116)
    ... 8 more

在服务器运行中突发异常,当时对Redis的操作并不频繁,大概一分钟一次的写数据,初步分析排除了资源竞争照成的Redis超时异常。
之后ping -t 远程Redis主机,发现当出现Read timed out异常时经常伴有ping超时情况,初步判定为网络不稳定照成。

临时解决办法是在new JedisPool(config, host, port, timeout) 初始化时,将timeout设置成更大的值(默认是2000)以便应对极端网络情况下的读取流异常情况。更进一步的分析还有待发掘,本篇未完待续。

笔记②
之前Read timed out的异常并未在修改了配置文件后有太大的改善,在外网环境下,当用户基数增长到一定量级时,再次出现Read timed out异常。

这种现象太容易让服务端程序将焦点集中在Redis的并发处理能力及连接池数量上,因此特意打印了Redis的慢查询日志,发现并无明显异常,之后观察了Redis的连接数,也不过几十个client而已,并未超过设置上限,在Jedis方面,按照网上其他朋友的建议修改了maxIdle、maxActive和maxWait的值,但问题依旧。

因此开始将排查重点放到了网络底层上,检查了与Redis主机的线路ping值,出现Read timed out的时候,并未出现延迟情况,网络ping值一直<1ms。

继续观察,这个时候注意到偶尔出现了带宽峰值突破30MB/s的情况,多亏了服务器上开启的流量监控浮动,否则很难捕捉到这一瞬间。

通过与机房确认,确定两台机器走的是外网IP,因此访问Redis时走的带宽为外网带宽,那么势必会受到机房防火墙的限制,因此出现了Jedis无法读取到后续数据流的问题。

找到根源之后,问题就很好解决了,在业务机与Redis服务器上各加了一块千兆网卡,通过内网进行直连。重新开机,导用户,测试,加大用户量,一切正常。同时实时流量显示最高峰值达到了100MB/s。

因为Redis是基于TCP连接的,而海量的数据反复交互相当于远程读写内存的操作,势必会造成带宽的使用紧张,那么在带宽吃紧的情况下,Redis客户端即Jedis拿不到连接或拿不到后续数据包也是很正常的了。

但考虑到峰值交互量有点高的可怕,针对这一问题,对并发交互数据的频率进行调整,对数据量进行精减才是解决这一问题的最佳方案。

关于redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out的这一问题我们暂且解决到这里,目前暂未再次出现。
大家如果遇到此类问题,在修改Jedis参数的情况下依旧无法解决,可以考虑一下是否是硬件及网络问题造成的。

本篇到此,欢迎交流讨论。谢谢。

Beitown
2015.04.07

在IE8下DIV margin:0 auto不居中的解决办法

六月 5th, 2014 3 views

RT,用<center>标签完美解决

1
2
3
4
5
6
7
8
9
 <center>
      <div style="margin:0 auto;">
        <p>[ 我为痴狂 ]</br>
        </p>
        <p><a href="http://creater.beitown.com/?cat=3" >Creater 创世记</a></p>
        <p><a href="http://coder.beitown.com" >Coder 编码之源</a></p>
        <p style="text-align:right">—— BeiTown-北呈-2014-星夜河殿下</p>
      </div>
 </center>

BeiTown
2014-06-05

Java正则表达式取目标字符串简单范例

六月 3rd, 2014 17 views

本篇只讲述JAVA正则表达式类java.util.regex的取字符串的使用方法,关于正则表达式的语法及使用不在本篇的描述之中,若有需要可参看《正则表达式30分钟入门教程》

首先做完范例需要一个待解析字符串,这里顺带把最近一个项目中的jdbcUrl拿来解析好了,字符串如下

1
jdbc:mysql://127.0.0.1:3306/test

这里说下目标需求,即把这段jdbcUrl分解成IP(127.0.0.1)、端口号(3306)以及数据库名(test),现将Java中的操作分为以下几步:

①书写正则表达式

正则表达式分别如下:

IP:(?<=//).*(?=:)取”//”和” :”之间的内容
PORT:(?<=\d:).*(?=/)取”数字+:”和” /”之间的内容
DBNAME:(?<=\d/).*”)取”数字+/”后面的内容

这里用正则表达式测试工具可以实时看到效果。
Read the rest of this entry »