这是我的毕业论文题目,两年前的东西了,来出来纪念下吧。后面附演讲pdf。

这讲的是在一个三维模型上手绘纹理的故事,纹理是用户选择的样本纹理。因此,实际上我的毕业论文就是做了一个上纹理的工具。用户选择不同的模型,同时选择不同的样本纹理(符合马尔科夫随机场的样本纹理),就可以用鼠标在模型上刷三维纹理了。

看起来挺好玩的,实际上整个系统的实现过程非常繁杂。这个系统主要用了两个算法,一个是扩展指数映射计算笔画的局部参数化,另外一个是根据样本纹理动态合成新的大纹理(要贴到笔画区域),这个用了并行可控制纹理合成算法,在约束条件下做了特殊实现。由于扩展指数映射用的是他人的实现代码,严重影响了整个系统的速度,也影响了我后面做实验的效果,非常恶心,所以不到无奈时候,千万不要用所谓的牛人的资源。

系统流程:

最终效果:

PDF下载:三维表面上基于笔画的纹理交互式合成

预览如下:

记得以前学汇编和PE文件的时候知道,系统不会直接调用我们编写的main,而是调用指定的入口地址。实际上这个入口地址,是在链接时候指定的,MS C++中使用链接命令/entry:function可以修改默认设置。

那么,默认情况下,我们使用VC编写的应用程序使用的是什么入口函数了?

函数 默认
mainCRTStartup (or wmainCRTStartup) An application using /SUBSYSTEM:CONSOLE; calls main (or wmain)
WinMainCRTStartup (or wWinMainCRTStartup) An application using /SUBSYSTEM:WINDOWS; calls WinMain (or wWinMain), which must be defined with __stdcall

注意,区分入口函数和主函数(main,WinMain)。

默认情况下,控制台程序使用mainCRTStartup作为入口函数,窗口程序使用WinMainCRTStartup作为入口函数。同时,这两个函数都有对应的Unicode版本(前缀加w)。

现在要考虑的是,这些启动函数都做了什么事情?

在crtexe.c文件中可以找到这几个启动函数的定义,如下:

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
#ifdef _WINMAIN_

#ifdef WPRFLAG
int wWinMainCRTStartup(
#else /* WPRFLAG */
int WinMainCRTStartup(
#endif /* WPRFLAG */

#else /* _WINMAIN_ */

#ifdef WPRFLAG
int wmainCRTStartup(
#else /* WPRFLAG */
int mainCRTStartup(
#endif /* WPRFLAG */

#endif /* _WINMAIN_ */
void
)
{
/*
* The /GS security cookie must be initialized before any exception
* handling targetting the current image is registered. No function
* using exception handling can be called in the current image until
* after __security_init_cookie has been called.
*/
__security_init_cookie();

return __tmainCRTStartup();
}

因此,实际上是根据平台(Windows或者Console),多字节还是Unicode,生成不同的默认入口函数。

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
__declspec(noinline)
int
__tmainCRTStartup(
void
)
{
int initret;
int mainret=0;
int managedapp;
#ifdef _WINMAIN_
_TUCHAR *lpszCommandLine;
STARTUPINFO StartupInfo;

__try {
/*
Note: MSDN specifically notes that GetStartupInfo returns no error, and throws unspecified SEH if it fails, so
the very general exception handler below is appropriate
*/
GetStartupInfo( amp;StartupInfo );
} __except(EXCEPTION_EXECUTE_HANDLER) {
return 255;
}
#endif /* _WINMAIN_ */
/*
* Determine if this is a managed application
*/
managedapp = check_managed_app();

if ( !_heap_init(1) ) /* initialize heap */
fast_error_exit(_RT_HEAPINIT); /* write message and die */

if( !_mtinit() ) /* initialize multi-thread */
fast_error_exit(_RT_THREAD); /* write message and die */

/* Enable buffer count checking if linking against static lib */
_CrtSetCheckCount(TRUE);

/*
* Initialize the Runtime Checks stuff
*/
#ifdef _RTC
_RTC_Initialize();
#endif /* _RTC */
/*
* Guard the remainder of the initialization code and the call
* to user's main, or WinMain, function in a __try/__except
* statement.
*/

__try {

if ( _ioinit() lt; 0 ) /* initialize lowio */
_amsg_exit(_RT_LOWIOINIT);

/* get wide cmd line info */
_tcmdln = (_TSCHAR *)GetCommandLineT();

/* get wide environ info */
_tenvptr = (_TSCHAR *)GetEnvironmentStringsT();

if ( _tsetargv() lt; 0 )
_amsg_exit(_RT_SPACEARG);
if ( _tsetenvp() lt; 0 )
_amsg_exit(_RT_SPACEENV);

initret = _cinit(TRUE); /* do C data initialize */
if (initret != 0)
_amsg_exit(initret);

#ifdef _WINMAIN_

lpszCommandLine = _twincmdln();
mainret = _tWinMain( (HINSTANCE)amp;__ImageBase,
NULL,
lpszCommandLine,
StartupInfo.dwFlags amp; STARTF_USESHOWWINDOW
? StartupInfo.wShowWindow
: SW_SHOWDEFAULT
);
#else /* _WINMAIN_ */
_tinitenv = _tenviron;
mainret = _tmain(__argc, _targv, _tenviron);
#endif /* _WINMAIN_ */

if ( !managedapp )
exit(mainret);

_cexit();

}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
/*
* Should never reach here
*/

mainret = GetExceptionCode();

if ( !managedapp )
_exit(mainret);

_c_exit();

} /* end of try - except */

return mainret;
}

从上面代码,可以很清晰了解启动函数到底做了什么事情。

Console版本

1.初始化C的堆申请(_heap_init(1))

2.初始化多线程(_mtinit())

3.获取命令行(GetCommandLineT())

4.获取环境变量(GetEnvironmentStringsT())

5.初始化C和C++的全局变量(_cinit(TRUE))

6.调用main函数(_tmain(__argc, _targv, _tenviron))

Windows版本

1.获取StartupInfo(GetStartupInfo( StartupInfo ))

2.初始化C的堆申请(_heap_init(1))

3.初始化多线程(_mtinit())

4.获取命令行(GetCommandLineT())

5.获取环境变量(GetEnvironmentStringsT())

6.初始化C和C++的全局变量(_cinit(TRUE))

7.调用WinMain函数

最后再看看_cinit(TRUE)到底做了什么事情?

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
int __cdecl _cinit (
int initFloatingPrecision
)
{
int initret;

/*
* initialize floating point package, if present
*/
#ifdef CRTDLL
_fpmath(initFloatingPrecision);
#else /* CRTDLL */
if (_FPinit != NULL amp;amp;
_IsNonwritableInCurrentImage((PBYTE)amp;_FPinit))
{
(*_FPinit)(initFloatingPrecision);
}
_initp_misc_cfltcvt_tab();
#endif /* CRTDLL */

/*
* do initializations
*/
initret = _initterm_e( __xi_a, __xi_z );
if ( initret != 0 )
return initret;

#ifdef _RTC
atexit(_RTC_Terminate);
#endif /* _RTC */
/*
* do C++ initializations
*/
_initterm( __xc_a, __xc_z );

#ifndef CRTDLL
/*
* If we have any dynamically initialized __declspec(thread)
* variables, then invoke their initialization for the thread on
* which the DLL is being loaded, by calling __dyn_tls_init through
* a callback defined in tlsdyn.obj. We can't rely on the OS
* calling __dyn_tls_init with DLL_PROCESS_ATTACH because, on
* Win2K3 and before, that call happens before the CRT is
* initialized.
*/
if (__dyn_tls_init_callback != NULL amp;amp;
_IsNonwritableInCurrentImage((PBYTE)amp;__dyn_tls_init_callback))
{
__dyn_tls_init_callback(NULL, DLL_THREAD_ATTACH, NULL);
}
#endif /* CRTDLL */

return 0;
}

最重要的两行,initret = _initterm_e( xi_a, xi_z )和_initterm( xc_a, xc_z )。这两行的作用分别是初始化C标准库中的全局变量和初始化C++的全局变量。

至此,对C++程序的默认启动函数的基本过程有个了解了。

1.发布博客软件:Windows Live Write,可以参考我的WindowsLiveWrite博文

2.Windows7/Xp上显示TGA图片的缩略图:MysticThumbs,可以参考我的MysticThumbs博文

3.记笔记:印象笔记(Evernote),不用介绍了,任何设备上都可以用(电脑,手机,ipad…)。

4.注册表浏览器:registryworkshop,比Windows默认的注册表舒服很多。

5.破解工具:TrialReset。该软件可以清除很多软件的保护信息,比如visual assist X,我就用它清楚过VA的保护信息。搜索该软件会得到一些介绍,单纯的清除信息操作很简单,清除后相当于无限延长试用期。试试会得到你意想不到的结果。

6.阅读软件:calibre。calibre是一个非常优秀的书籍管理软件。当然,我找到它的原因是它可以非常方便的转mobi,epub等。这样就可以将任意格式的书籍放到kindle或者多看阅读器(支持多种设备,手机,ipad,kindle等)上面去看了。这对于用阅读打法无聊时光的我,非常有用。calibre还可以在PC上当做书籍管理和阅读软件,我正在用它看epub格式的书籍了,因为多看阅读器不支持Windows平台。更多功能我就不知道了,有待挖掘。

7.文件内容搜索工具:FileSeek。这款软件能够下载到破解版,我现在主要用来搜索as代码(逆战的界面使用的是ScaleForm开发的,需要写as代码)。Windows自带的搜索功能对于文件内容搜索确实不方便额。

持续更新…

我使用的是windows下的最新版CTex,版本号是CTeX_2.9.2.164_Full.exe。注意使用full版本,如果不是,可能不支持beamer。beamer是一个流行的演讲文档模板,有多种主题,效果也还不错。

我在尝试beamer的过程中,发现网上大部分例子都不支持中文,或者不支持我使用的环境下的中文,即windows下的最新版CTex。折腾了几天,试了很多个模板,发现xeCJK能够完美解决这个问题,因为很多老的模板都是用的CJK,我将其中一个模板改成xeCJK就支持中文了。
下面给出这个模板的设置。

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
\documentclass{beamer}
\usetheme{Warsaw}
\usepackage{fontspec,xunicode,xltxtra}
\usepackage[slantfont,boldfont]{xeCJK} % 允许斜体和粗体
\setbeamercovered{transparent}
\usepackage[english]{babel}
% or whatever
\usepackage{hyperref}
\usepackage[T1]{fontenc}
% or whatever
\usefonttheme{professionalfonts}
\usepackage{times}
\usepackage{mathptmx}
\usepackage{tabularx}
% Or whatever. Note that the encoding and the font should match. If T1
% does not look nice, try deleting the line with the fontenc.
\usepackage{xcolor}
\usepackage{booktabs, multirow, enumerate}
\usepackage{animate}
\usepackage{multimedia}

% ... or whatever. Note that the encoding and the font should match.
% If T1 does not look nice, try deleting the line with the fontenc.
\usepackage{lmodern} %optional
\usepackage{listings}

% Delete this, if you do not want the table of contents to pop up at
% the beginning of each subsection:
\AtBeginSection[]
{
\begin{frame}<beamer>
\frametitle{内容大纲}
\tableofcontents[currentsection]
\end{frame}
}

\setCJKmainfont{Microsoft YaHei} % 设置缺省中文字体
\setCJKmonofont{SimSun} % 设置等宽字体
\setmainfont{TeX Gyre Pagella} % 英文衬线字体
\setmonofont{Microsoft YaHei} % 英文等宽字体
\setsansfont{Trebuchet MS} % 英文无衬线字体

\begin{document}

\title[***纹理***]% optional, use only with long paper titles
{ ***纹理***\\[2ex]}

%\subtitle[malloc] %optional
%{malloc\ 实现}

\author[yx] % optional, use only with lots of authors
{
\textcolor[rgb]{0.00, 0.41, 0.66}{yx}
}

% the titlepage
% the plain option removes the sidebar and header from the title page
\begin{frame}[plain]
\titlepage
\end{frame}
%%%%%%%%%%%%%%%%

\begin{frame}
\frametitle{内容大纲}
\tableofcontents
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{研究背景和意义}
%%%%%%%%%%%%%%%%
\begin{frame}{纹理的作用}{}
\begin{block}<1->{}
纹理是图形学中增强真实性的重要手段。
\end{block}
\begin{block}<2->{}
应用纹理到三维表面能够有效的表示物体表面的颜色,材质,几何等属性。
\end{block}
\end{frame}
%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\end{document}

值得注意的是,请用utf8来保存该文档,并且在WinEdit中用utf8来打开,否则还是可能看到乱码的。
boats against the current, borne back ceaselessly into the past.

今天逆战发布新版了。我做的新军衔系统和转生系统在这次上线了。哈哈,好有成就感。第一次写的代码能够影响这么多人的生活。听说玩家都挤在主城转生NPC处了。

新军衔系统:

转生系统:

在逆战的界面开发中,一般只需要从uc往flash中单向传输数据,因此这里只总结从uc->flash的数据流动方法。flash->uc的数据流动,其实有相对应的方式,比如SetVariableBool函数有对应的GetVariableBool函数。

在unrealscript中,使用scaleform的界面类,都继承自GfxMoviePlayer。

一、传输变量

native function SetVariableString(string path, string s);

native function SetVariableBool(string path, bool b);

native function SetVariableNumber(string path, float f);

我在GfxUIMovie中没找到专门设置int的函数,估计可以用SetVariableNumber替代。关键是理解path的设置,比如对应flash文件的名称是ReBornGuide。那么path可能是ReBornGuide.str或者_root.ReBornGuide.str,具体跟ue3项目的一些设置有关。

这几个函数对应的cpp声明如下:

String:SetVariableString(const FString path, const FString s);

Bool:SetVariableBool(const FString path, UBool b);

Float:SetVariableNumber(const FString path, float f);

有趣的是,这几个函数的字符串参数都是引用,这样恰恰说明了在uc中,字符串是引用类型。

例子:

1
2
3
4
5
6
test.uc中:
String strTest = "测试";
String path = "_root.TestFlash.strTest";
SetVariableString(path,strTest);
TestFlash.as中:
var strTest:String;

二、传输数组

native function bool SetVariableStringArray(string path, int index, array Arg);

native function bool SetVariableFloatArray(string path, int index, array Arg);

native function bool SetVariableIntArray(string path, int index, array Arg);

其对应的cpp实现

String:SetVariableStringArray(const FString path, INT Index,const TArray arg);

Float:SetVariableFloatArray(const FString path, INT Index,const TArray arg);

Int:SetVariableFloatArray(const FString path, INT Index,const TArray arg);

这里Index表示从数组的第几个元素开始,0表示传输全部,arg是要传输的数组。

例子:

1
2
3
4
5
6
7
8
test.uc中:
local array<String> strArray;
strArray.AddItem("0");
strArray.AddItem("1");
String path = "_root.TestFlash.strTest";
SetVariableStringArray(path,strArray);
TestFlash.as中:
var strArray:Array = new Array();

三、传输复合对象(结构体)

当需要传输整体的数据,或者传输多个数据的时候,可以将数据组合成一个结构体。假设在uc中定义了如下结构体,

1
2
3
4
5
6
7
8
9
struct RankData
{
var string Name;
var int Rank;
var double d;
var float f;
var bool b;
};
RankData RD;

我们想将其一次性传入as中定义的Object对象,var RD:Object。这里我们可以使用SetVariableObject函数。其定义如下:

native function SetVariableObject(string path, GFxObject Object);

下面的例子很好说明了如何使用该函数。

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
uc中:
struct RankData
{
var string Name;
var int Rank;
var double d;
var float f;
var bool b;
};

function SetFlashVariables()
{
local GFxObject GfxObj;
local RankData RD;
local string path;

RD.Name = "test";
RD.Rank = 1;
RD.d = 1.0;
RD.f = 1.0;
RD.b = True;
GfxObj = CreateObject("Object");
GfxObj.SetString("Name", RD.Name);
GfxObj.SetInt("Rank", RD.Rank);
GfxObj.SetDouble("d", RD.d);
GfxObj.SetFloat("f", RD.f);
GfxObj.SetBool("b", RD.b);

path = "_root.TestFlash.RD"
SetVariableObject(path, GfxObj);
GfxObj.DestroyObject();
}

TestFlash.as中:
var RD:Object = new Object();
//在uc中调用了SetFlashVariables函数后,as中就能获得RD的值。
trace(RD.Name);
trace(RD.Name);
trace(RD.f);
trace(RD.d);
trace(RD.b);

从上面代表可以看出,GFxObject可以代表一个符号变量,可以设置一个GFxObject的多个属性,然后一次性将这个对象传入flash。GFxObject.uc中有如下Set变量的函数:

1
2
3
4
5
6
native final function SetBool(string Member, bool b);
native final function SetFloat(string Member, float f);
native final function SetDouble(string Member,double d);
native final function SetInt(string Member,int i);
native final function SetString(string Member, string s);
native final function SetObject(string Member, GFxObject val);

因此,可以看出可以设置GFxObject各种类型的属性。关键的是还可以设置其GFxObject类型的属性,从而可以嵌套定义。比如结构体中定义另一结构体成员。当需要将多次传输组合为一次时候,也可以使用这种方式,并不需要一定定义结构体。

四、传输结构体数组

假设as中定义如下数组,var RankListDatas:Array = new Array();那么可以用下面代码传输:

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
//军衔列表每项的数据
struct RankListData
{
var int Level;//等级
var string Name;//军衔名
};
var array<RankListData> RankListDatas;
var string path;
function SetFlashRankListDatas()
{
local int Index;
local GFxObject GfxRankListDatas;
local GFxObject GfxTempObj;

GfxRankListDatas = CreateArray();
for (Index = 0; Index < RankDatas.Length; Index++)//第0项是多余的
{
GfxTempObj = CreateObject("Object");
GfxTempObj.SetInt("Level", RankListDatas[Index].Level);
GfxTempObj.SetString("Name", RankListDatas[Index].Name);
GfxRankListDatas.SetElementObject(Index, GfxTempObj);
GfxTempObj.DestroyObject();
}

SetVariableObject(path, GfxRankListDatas);
GfxRankListDatas.DestroyObject();
}

从代码里面可以看到,还是用SetVariableObject传输数据。不过,需要用CreateArray()创建GFxObject对象。在as代码中RankListDatas[0].Name就可以访问第一个元素的Name属性了。

GfxRankListDatas.SetElementObject(Index, GfxTempObj)则是将GfxTempObj设置为GfxRankListDatas代表的数组的第Index个元素。

五、传输结构体数组的数组(结构体内部复合结构体数组)

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
struct AwardData
{
var string IconSource;
};

struct RankAwardData
{
var string RankAwardTitle;
var array<AwardData> AwardDatas;
};
var array<RankAwardData> RankAwardDatas;
var string path;

function SetFlashRankAwardDatas()
{
local int Index;
local int i;
local GFxObject GfxRankAwardDatas;
local GFxObject GfxTempObj;
local GFxObject GfxAwardDatas;
local GFxObject GfxAwardTempObj;

GfxRankAwardDatas = CreateArray();
for (Index = 0; Index < RankAwardDatas.Length; Index++)
{
GfxTempObj = CreateObject("Object");
GfxTempObj.SetString("RankAwardTitle", RankAwardDatas[Index].RankAwardTitle);

GfxAwardDatas = CreateArray();
for (i = 0; i < RankAwardDatas[Index].AwardDatas.Length; ++i)
{
GfxAwardTempObj = CreateObject("Object");
GfxAwardTempObj.SetString("IconSource", RankAwardDatas[Index].AwardDatas[i].IconSource);
GfxAwardDatas.SetElementObject(i, GfxAwardTempObj);
GfxAwardTempObj.DestroyObject();
}
GfxTempObj.SetObject("RankAward", GfxAwardDatas);
GfxRankAwardDatas.SetElementObject(Index, GfxTempObj);
GfxAwardDatas.DestroyObject();
GfxTempObj.DestroyObject();
}

SetVariableObject(path, GfxRankAwardDatas);
GfxRankAwardDatas.DestroyObject();
}

从上面代码可以看出,调用GfxTempObj.SetObject(“RankAward”, GfxAwardDatas)可以设置GFxObject对象的GFxObject属性,从而能够传输复合结构体类型的数组。假设as中对应的数组定义为var RankAwardDatas:Array=new Array();那么,在as代码中RankAwardDatas[0].RankAward.IconSource则能访问该数组第一个元素的结构体成员的IconSource属性。

我不知道现在的win8和win10系统上是不是自动显示tga格式的缩略图的。我的win7系统确实有时候显示不了。为什么有这样的需求了,因为我现在的工作是开发游戏,虽然这几个月在写界面。在网上搜到有一个这样的软件MysticThumbs。这显然是外国友人做的东西,肯定是要注册的,官网上面下载的肯定是的,要交钱的。伟大的天朝人民肯定有破解版本了。

下面分享下我下载到的破解版本,里面有使用说明,MysticThumbs破解版本。虽然说里面有破解说明,既然写了这篇文章,我就是想更简略的说明下如何使用。

1.打开之后,直接运行MysticThumbs3.0.0.Multi+Pt FULL。假设安装到默认路径。

2.打开安装路径C:\Program Files\MysticCoder\MysticThumbs,你会看到里面有几个dll。

3.把安装文件里面的patch目录下的文件,复制到2所在的目录即可。

破解完毕,现在不用担心使用一个月后失效了。

这个破解版本在2个多月后失效了,慎用。

7月26号封闭培训,到部门来已经是8月10号了。刚开始懵懵懂懂的,不知道干啥。部门上课的那几天,把新人手册的步骤做了一遍。悲伤的是,做完之后不少忘记了。编译脚本的addin插件至今没有显示出来,虽然有几天它显示过。除此之外,还有不少纠结过的问题。让我一一道来吧。配置vs字体和颜色,va快捷键,查找快捷键无效,F12查看定义无效,uc断点无效,调试uc时候游戏进程掉了。。。下面来说说成功解决掉的几个问题吧,非常感谢jerry哥和vivin姐的帮忙。

1.vs的字体和颜色。默认字体是新宋体,这个字体据说不是很好,推荐consolas字体。颜色的话,有个网站叫做https://studiostyl.es/
可以去里面选自己喜欢的风格,设置好就行。值得注意的是,安装nfringe后,字体颜色设置面板里面会出现unrealscript相关的设置,注意修改。
如图所示,

另一个需要注意的地方是,与.uc文件不同,.cpp中的代码颜色是在visual assist x中修改的,这个我找了半天才反应过来。

2.visaul assist x的open file in solution快捷键shift+alt+o没掉了,按这个快捷键打开的一个网页模板窗口。方式是配置一个新的快捷键。tools->options->environment->keyboard,打开面板后选中VAssistX.OpenFileInSolutionDialog。再将鼠标焦点切换到Press shortcut keys下面的输入框里面,按下shift+alt+o,就添加一个全局快捷键,点击assign按钮即可。如图,

3.vs的查找文件快捷键,shift+ctrl+f无效。这是因为安装了搜狗输入法造成了。搜狗输入法在更上层截断了这个快捷键,设置下搜狗输入法取消这个快捷键即可。如图,

4.调试uc时候游戏进程脱离vs。这个debug->attach to process即可。注意最上面的tansport可以筛选unrealscript,筛选后再用下面的refresh按钮刷新一下就出来了。

5.f12查找代码定义的问题并没有彻底解决。在uc文件中,只能用f12查找定义。对于f12失效的情况下,使用3在整个解决方案搜索,不过比较麻烦,但是能解决问题。在cpp文件中,就能够使用va的快捷键alt+g查找定义了。我居然忘记了这茬,以前从没接触过脚本编写,现在项目里面cpp和uc都有,所以混淆了。

6.uc断点无效,据说是nfringe的bug。

7.另外项目组的addin插件一直点击没反应,也不知道原因。不过,我在解决问题3之后,addin插件按钮点击后不会造成vs崩溃,但是还是无效的,也就是彻底没反应,我是服了。。。不过,今天前辈在我机子上面调试那个插件的代码,修复了bug,可以使用了,开心。。。

早上悲催的被发现昨晚上传代码没传全,差点请全组喝饮料了。。。

vector是一直在用的东西,太常用了,数组的替代品。大多数时候,都想不出理由不用vector,改用list,deque之类的。从功能来看,vector就是一个动态的数组而已。使用vector时候,唯一需要注意的是,当重新申请更大空间时候,是去了一块新内存,而不是直接在原地址增加,所有迭代器是都会失效的。当然还有其它操作造成某些迭代器失效。

下面来看看vector的部分定义。全部定义过于冗长了。

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
template <class T, class Alloc = alloc>  // 預設使用 alloc 為配置器
class vector {
public:
// 以下標示 (1),(2),(3),(4),(5),代表 iterator_traits<I> 所服務的5個型別。
typedef T value_type; // (1)
typedef value_type* pointer; // (2)
typedef const value_type* const_pointer;
typedef const value_type* const_iterator;
typedef value_type reference; // (3)
typedef const value_type const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type; // (4)
// 以下,由於vector 所維護的是一個連續線性空間,所以不論其元素型別為何,
// 原生指標都可以做為其迭代器而滿足所有需求。
typedef value_type* iterator;

protected:
// 專屬之空間配置器,每次配置一個元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;

// vector採用簡單的線性連續空間。以兩個迭代器start和end分別指向頭尾,
// 並以迭代器end_of_storage指向容量尾端。容量可能比(尾-頭)還大,
// 多餘即備用空間。
iterator start;
iterator finish;
iterator end_of_storage;

void insert_aux(iterator position, const T x);
void deallocate() {
if (start)
data_allocator::deallocate(start, end_of_storage - start);
}

void fill_initialize(size_type n, const T value) {
start = allocate_and_fill(n, value); // 配置空間並設初值
finish = start + n; // 調整水位
end_of_storage = finish; // 調整水位
}

public:
iterator begin() { return start; }
const_iterator begin() const { return start; }
iterator end() { return finish; }
const_iterator end() const { return finish; }
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
size_type size() const { return size_type(end() - begin()); }
size_type max_size() const { return size_type(-1) / sizeof(T); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
reference operator[](size_type n) { return *(begin() + n); }
const_reference operator[](size_type n) const { return *(begin() + n); }

vector() : start(0), finish(0), end_of_storage(0) {}
// 以下建構式,允許指定大小 n 和初值 value
vector(size_type n, const T value) { fill_initialize(n, value); }
vector(int n, const T value) { fill_initialize(n, value); }
vector(long n, const T value) { fill_initialize(n, value); }
explicit vector(size_type n) { fill_initialize(n, T()); }

vector(const vector<T, Alloc> x) {
start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());
finish = start + (x.end() - x.begin());
end_of_storage = finish;
}

vector(const_iterator first, const_iterator last) {
size_type n = 0;
distance(first, last, n);
start = allocate_and_copy(n, first, last);
finish = start + n;
end_of_storage = finish;
}

~vector() {
destroy(start, finish); // 全域函式,建構/解構基本工具。
deallocate(); // 先前定義好的成員函式
}
vector<T, Alloc> operator=(const vector<T, Alloc> x);
void reserve(size_type n) {
if (capacity() < n) {
const size_type old_size = size();
iterator tmp = allocate_and_copy(n, start, finish);
destroy(start, finish);
deallocate();
start = tmp;
finish = tmp + old_size;
end_of_storage = start + n;
}
}

// 取出第一個元素內容
reference front() { return *begin(); }
const_reference front() const { return *begin(); }
// 取出最後一個元素內容
reference back() { return *(end() - 1); }
const_reference back() const { return *(end() - 1); }
// 增加一個元素,做為最後元素
void push_back(const T x) {
if (finish != end_of_storage) { // 還有備用空間
construct(finish, x); // 直接在備用空間中建構元素。
++finish; // 調整水位高度
}
else // 已無備用空間
insert_aux(end(), x);
}

void pop_back() {
--finish;
destroy(finish); // 全域函式,建構/解構基本工具。
}
// 將迭代器 position 所指之元素移除
iterator erase(iterator position) {
if (position + 1 != end()) // 如果 p 不是指向最後一個元素
// 將 p 之後的元素一一向前遞移
copy(position + 1, finish, position);

--finish; // 調整水位
destroy(finish); // 全域函式,建構/解構基本工具。
return position;
}
iterator erase(iterator first, iterator last) {
iterator i = copy(last, finish, first);
destroy(i, finish); // 全域函式,建構/解構基本工具。
finish = finish - (last - first);
return first;
}
void resize(size_type new_size, const T x) {
if (new_size < size())
erase(begin() + new_size, end());
else
insert(end(), new_size - size(), x);
}
void resize(size_type new_size) { resize(new_size, T()); }
// 清除全部元素。注意,並未釋放空間,以備可能未來還會新加入元素。
void clear() { erase(begin(), end()); }

protected:
iterator allocate_and_fill(size_type n, const T x) {
iterator result = data_allocator::allocate(n); // 配置n個元素空間
// 全域函式,記憶體低階工具,將result所指之未初始化空間設定初值為 x,n個
// 定義於 <stl_uninitialized.h>。
uninitialized_fill_n(result, n, x);
return result;
}
}

下面分几部分来讲述vector的定义。首先,模板参数一个是类型T作为vector的内含物类型,class Alloc = alloc则是使用默认的内存申请器。这一句typedef simple_alloc data_allocator;则是重定义相应的内存申请器类型。
然后,vector的迭代器类型其实就是原始的指针类型重定义了一下。以下这几句重定义,

1
2
3
4
5
6
7
8
typedef T value_type;             // (1)
typedef value_type* pointer; // (2)
typedef const value_type* const_pointer;
typedef const value_type* const_iterator;
typedef value_type reference; // (3)
typedef const value_type const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type; // (4)

在list,deque,map,set等支持迭代器操作的容器定义中都会出现。其中,vector中的定义是最简单的。每个容器都有值类型,指针类型,迭代器类型,引用类型,以及大小类型,difference_type。知道这些后,写代码时候,可以避免一些错误,或者说代码更规范,就能自然而然的使用容易内置类型,比如vector::size_type而不是int来进行一些操作。原始指针支持随机存储,因此vector的迭代器就是功能最强的随机迭代器。
vector采用的数据结构很简单,就是一段线性内存。不过,有三个指针分别指向内存的头,已使用的尾部,尾。为了避免过多的重新分配内存,vector的容量永远比大小大,而且每次重新配置内存,大小翻倍。所以,vector的很多操作,比如,begin,end,front,back,size等的实现就很直观了,具体参看源码。下图很清楚的说明了这一情况。

接着要讲的是vector如何管理空间的。先看下面三个函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
vector(size_type n, const T value) { fill_initialize(n, value); }
void fill_initialize(size_type n, const T value) {
start = allocate_and_fill(n, value); // 配置空間並設初值
finish = start + n; // 調整水位
end_of_storage = finish; // 調整水位
}
iterator allocate_and_fill(size_type n, const T x) {
iterator result = data_allocator::allocate(n); // 配置n個元素空間
// 全域函式,記憶體低階工具,將result所指之未初始化空間設定初值為 x,n個
// 定義於 <stl_uninitialized.h>。
uninitialized_fill_n(result, n, x);
return result;
}

从源码中可以看出,最终都调用了函数uninitialized_fill_n初始化数据。这个stl的全局函数保证申请内存时候不会调用构造函数做额外的事情,也就是申请内存后,再用数据初始化。下面再看看其余几个操作。

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
void push_back(const T x) {
if (finish != end_of_storage) { // 還有備用空間
construct(finish, x); // 直接在備用空間中建構元素。
++finish; // 調整水位高度
}
else // 已無備用空間
insert_aux(end(), x);
}

void pop_back() {
--finish;
destroy(finish); // 全域函式,建構/解構基本工具。
}
// 將迭代器 position 所指之元素移除
iterator erase(iterator position) {
if (position + 1 != end()) // 如果 p 不是指向最後一個元素
// 將 p 之後的元素一一向前遞移
copy(position + 1, finish, position);

--finish; // 調整水位
destroy(finish); // 全域函式,建構/解構基本工具。
return position;
}
iterator erase(iterator first, iterator last) {
iterator i = copy(last, finish, first);
destroy(i, finish); // 全域函式,建構/解構基本工具。
finish = finish - (last - first);
return first;
}

push_back在还有空间的情况下,也只需要调用construct函数构造数据,否则就需要调用insert_aux申请新的内存,再添加数据了。pop_back相对就简单很多。erase函数也涉及到一些移动内存块的操作,思路基本是先移动内存,然后把末尾多出来的数据析构掉(调用destroy函数)。

很久没更新博客了。额,一直想写这个来着。记得以前写过绘制三维标量场的文章,方法是找的渐变颜色纹理图片,然后用一维纹理坐标映射贴上去的。后面发现根本不需要去找图片,这种渐变色本来就是一种颜色空间的定义。请看hsv颜色模型,这种颜色模型类似于rgb,也是三个分量h(色调),s(饱和度),v(亮度)。h表示的绕圆锥的一周,也就是纯颜色的渐变,正是我们需要的东西。s表示从圆心到圆边缘的半径,0-1的范围,表示颜色纯度。v则是垂直的轴,表示亮度。如下图,我们要的就是绕圈一周的颜色渐变。

总体思路就是数值0-1,映射到hsv颜色空间的(0-360,1,255)。实现标量场的方法是,首先,将hsv的(0-360,1,255)转换为rgb,保存为一维数组。以该数组为数据,生成一维纹理,数值0-1作为纹理坐标,再贴图即可生成标量场。下面是用该方法生成标量场的相关代码。首先是构造一维纹理数组,然后用其生成1d纹理。

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
int CTexture::BuildColorMapTexture()
{
const int COLOR_SIZE = 240;//红到蓝
ImageRGBA colorMap(COLOR_SIZE, 1);

for (int i = 0; i < COLOR_SIZE; ++i)
{
colorMap.pixel(i, 0) = Hsv2Rgb(i, 1, 255);
}

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, m_uTexName);
glBindTexture(GL_TEXTURE_1D, m_uTexName);

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

//生成2d的Mipmap纹理
int nRet = gluBuild1DMipmaps(GL_TEXTURE_1D, 4, COLOR_SIZE, GL_RGBA, GL_UNSIGNED_BYTE,
colorMap.raw());

if (nRet != 0)
{
return 0;
}

return 1;
}

hsv2rgb的代码如下,从网上可以随便找个修改。

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
PixelRGBA Hsv2Rgb(float H, float S, float V)
{
float R, G, B;

int i;
float f, p, q, t;
if(S == 0)
{
// achromatic (grey)
R = G = B = V;
return V4B(R, G, B, 255);
}

H /= 60; // sector 0 to 5
i = floor( H );
f = H - i; // factorial part of h
p = V * ( 1 - S );
q = V * ( 1 - S * f );
t = V * ( 1 - S * ( 1 - f ) );

switch( i )
{
case 0:
R = V;
G = t;
B = p;
break;
case 1:
R = q;
G = V;
B = p;
break;
case 2:
R = p;
G = V;
B = t;
break;
case 3:
R = p;
G = q;
B = V;
break;
case 4:
R = t;
G = p;
B = V;
break;
default: // case 5:
R = V;
G = p;
B = q;
break;
}

return V4B(R, G, B, 255);
}

剩下的事情就是使用BuildColorMapTexture生成1d纹理,然后将数值作为1d纹理坐标设置好就行了。下面是相关的效果图:

颜色空间转换生成纹理:

渐变纹理贴图:

不同的方法效果有区别,原因可能是贴图的数据更精细,而且同一范围比如0-0.3对应的颜色就可能不一致。马上要毕业了,工作内容是游戏开发,以后估计不会再更新读研期间类似的文章的了。