2007年12月20日星期四

数组与指针---都是"退化"惹的祸

转自:http://bbs.chinaunix.net/thread-1031622-1-1.html

个人的浅显认识, 欢迎批评指正.

1. 什么是数组类型?

下面是C99中原话:
An array type describes a contiguously allocated nonempty set of objects with a
particular member object type, called the element type.36) Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T , the array type is sometimes called ‘‘array of T ’’. The construction of an array type from an element type is called ‘‘array type derivation’’.

很显然, 数组类型也是一种数据类型, 其本质功能和其他类型无异:定义该类型的数据所占内存空间的大小以及可以对该类型数据进行的操作(及如何操作).

2. 数组类型定义的数据是什么?它是变量还是常量?
char s[10] = "china";
在这个例子中, 数组类型为 array of 10 chars(姑且这样写), 定义的数据显然是一个数组s.

下面是C99中原话:
An lvalue is an expression with an object type or an incomplete type other than void; if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.

看了上面的定义, 大家应该明白了modifiable lvalue和lvalue的区别, 大家也应该注意到array type定义的是lvalue而不是modifiable lvalue.所以说s是lvalue.

s指代的是整个数组, s的内容显然是指整个数组中的数据, 它是china\0****(这里*表示任意别的字符).s的内容是可以改变的, 从这个意义上来说, s显然是个变量.

3. 数组什么时候会"退化"

下面是C99中原话:
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.

上面这句话说的很清楚了, 数组在除了3种情况外, 其他时候都要"退化"成指向首元素的指针.
比如对 char s[10] = "china";
这3中例外情况是:
(1) sizeof(s)
(2) &s;
(3) 用来初始化s的"china";

除了上述3种情况外,s都会退化成&s[0], 这就是数组变量的操作方式

4. 数组与指针有什么不同?
4.1 初始化的不同
char s[] = "china";
char *p = "china";

在第一句中,以&s[0]开始的连续6个字节内存分别被赋值为:
'c', 'h', 'i', 'n', 'a', '\0'

第二句中,p被初始化为程序data段的某个地址,该地址是字符串"china"的首地址

4.2 sizeof的不同

sizeof就是要求一种数据(类型)所占内存的字节数. 对于4.1中的s和p
sizeof(s)应为6, 而sizeof(p)应为一个"指针"的大小.

这个结果可以从1中对于数组类型的定义和3中数组什么时候不会"退化"中得出来.

4.3 &操作符

对于&操作符, 数组同样不会退化.
4.1中的s和p分别取地址后,其意义为:
&s的类型为pointer to array of 6 chars.
&p的类型为pointer to pointer to char.

4.4 s退化后为什么不可修改

除3种情况外,数组s在表达式中都会退化为"指向数组首元素的指针", 既&s[0]

举个例子
int a;
(&a)++; //你想对谁++? 这显然是不对的

对(&s[0])++操作犹如(&a)++, 同样是不对的,这就导致退化后的s变成不可修改的了.

4.5 二维数组与二级指针

char s[10];与char *p;
char s2[10][8];与char **p2;

s与p的关系,s2与p2的关系,两者相同吗?
紧扣定义的时候又到了.
除3种情况外,数组在表达式中都会退化为"指向数组首元素的指针".

s退化后称为&s[0], 类型为pointer to char, 与p相同
s2退化后称为&s2[0], 类型为pointer to array of 8 chars, 与p2不同

4.6 数组作为函数参数

毫无疑问, 数组还是会退化.

void func(char s[10]); <===> void func(char *s);

void func(char s[10][8]); <===> void func(char (*s)[8]);

4.7 在一个文件中定义char s[8], 在另外一个文件中声明extern char *s. 这样可以吗?

---------file1.c---------
char s[8];

---------file2.c---------
extern char *s;


答案是不可以. 一般来说,在file2.c中使用*s会引起core dump, 这是为什么呢?

先考虑int的例子.
---------file1.c---------
int a;

---------file2.c---------
extern int a;

file1.c和file2.c经过编译后, 在file2.o的符号表中, a的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中a的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern int a;进行的,即0xbf8eafae会被认为是整形a的地址
比如 a = 2; 其伪代码会对应为 *((int *)0xbf8eafae) = 2;

现在再看原来的例子.

---------file1.c---------
char s[8];

---------file2.c---------
extern char *s;

同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char *s;进行的,即0xbf8eafae会被认为是指针s的地址
比如 *s = 2; 其伪代码会对应为 *(*((char **)0xbf8eafae)) = 2;

*((char **)0xbf8eafae)会是什么结果呢?
这个操作的意思是:将0xbf8eafae做为一个二级字符指针, 将0xbf8eafae为始址的4个字节(32位机)作为一级字符指针
也就是将file1.o中的s[0], s[1], s[2], s[3]拼接成一个字符指针.


那么*(*((char **)0xbf8eafae)) = 2;的结果就是对file1.o中s[0], s[1], s[2], s[3]拼接成的这个地址对应
的内存赋值为2.
这样怎么会正确呢?


下面看看正确的写法:

---------file1.c---------
char s[8];

---------file2.c---------
extern char s[];


同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char s[];进行的,即0xbf8eafae会被认为是数组s的地址
比如 *s = 2; 其伪代码会对应为 *(*((char (*)[])0xbf8eafae)) = 2;

*((char (*)[])0xbf8eafae)会是什么结果呢?
这个操作的意思是:将0xbf8eafae做为一个指向字符数组的指针, 然后对该指针进行*操作.
这就用到了数组的一个重要性质:
对于数组 char aaa[10];来说, &aaa[0], &aaa, *(&aaa)在数值上是相同的(其实, *(&aaa)之所以在程序中
会在值上等于&aaa[0], 这也是退化的结果: *(&aaa)就是数组名aaa, aaa退化为&aaa[0]).
所以, *((char (*)[])0xbf8eafae)的结果在值上还是0xbf8eafae, 在类型上退化成"指向数组首元素的指针"


那么*(*((char (*)[])0xbf8eafae)) = 2;
其伪代码就成为*((char *)0xbf8eafae) = 2; 即将数组s的第一个元素设为2


5. 小结论

(a). 数组类型是一种特殊类型, 它定义的是数组变量, 是lvalue但不是modifiable lvalue
(b). 除了3种情况外(sizeof, &, 用做数组初始化的字符串数组), 数组会退化成"指向数组首元素的指针"
(c). 不要将数组名简单的看作不可修改的相应的指针, 它们还是有很多不同的

2007年12月14日星期五

qWrap包裹变形


http://www.qarl.com/qLab/?p=29



不是很好用,貌似只适合高模。自己试了试,效果不尽人意。

Maya 2008 API Template for Xcode

http://www.thomascraigen.com/tools/index.html
开发maya插件的xcode模板,不多说了。

2007年12月12日星期三

不同拓扑结构的模型之间的变换1

写在前面:
这只是一些想法,目前我还没有办法验证,可能跟本就行不通,现在只希望能把这些想法记载下来。

shapeA和shapeB的拓扑结构不同,简单的说就是顶点数和面数不一样。如果要由shapeA平滑的变换到shapeB,目前来说还没有很好办法,至少这些主流的3d软件还都无法做到。siggraph的paper可能有解决方案,有空的时候翻翻看。


cmiVFX的免费教程

最近cmi有提供了一篇免费的blender教程,主要说的是用node来做合成。用blender做合成,看起来很有nuke的感觉,只是功能和稳定性比起nuke差些。

http://cgchannel.com/news/viewfeature.jsp?newsid=6763

现在的blender真是一个怪物,各个方面都可以做,3D、后期、剪辑通吃,只是做的都不是很好。
差点忘了,cmi的网站要穿墙才能访问到。

Automatic Rigging and Animation of 3D Characters



http://www.mit.edu/~ibaran/autorig/


以前曾经考虑过自动装配角色的问题,现在已经有人做出来了。大概看了一下paper和视频,对于复杂的模型,这样rig恐怕还不行,不过动画转换的功能却非常不错。

2007年12月3日星期一

TopMod-拓扑建模软件



在blender的论坛里冒泡的时候,无意中发现了这个软件。是一个google code上的项目,基于QT4,opensource,支持osx和windows,貌似linux也在支持之列。下载之后试了试,不会用。

http://www.topmod3d.org

2007年12月1日星期六

Blender中的Fluid

http://www.blendernation.com/2007/11/21/guide-on-blender-fluid-simulators-parameters/

A Blender artist called Antonio Gessi, has published a very cool guide about the Fluid Simulator options. To make this guide even more interesting, all parameters are illustrated with animations. For instance, when he explains how the Domain option works, there is an animation showing how the fluid will behave at different resolutions.

2007年10月16日星期二

传闻是EMC的一道笔试题

http://bbs.chinaunix.net/thread-918458-1-1.html

函数原形已经给出:int p(int i, int N);
功能:调用该函数,打印如下格式的输出,例p(1, 7);
1
2
3
4
5
6
7
6
5
4
3
2
1
即每行一个数字。(注意:N只打印一次)
要求:
函数中唯一能够调用的函数就是printf。
只使用一条语句,如果你真的不能用一条语句,每增加一条语句扣1分。
不准使用如下的关键字:typedef, enum, do, while, for, switch, case, break, continue, goto,
until, if, ..具体很多我也忘了,反正能用的不多。
不能使用逗号表达式和?:表达式。
标准:(总分10分)
1. 每多一条语句扣1分,即每多一个;就扣1分
2. 每使用一次if或?:扣2分
3. 每使用一次for,while, swith各扣4分
大体上就是如上的要求了。


水牛们给出的答案:
1.
int p( int i, int N ){
return ( printf( "%d\n", i ) && N > 1 && p( i+1, N-1 ) && printf( "%d\n", i ) ) + 1;
}

2.
如果不递归的话,能不能做出来呢?
我觉得还是有可能的。
只需要用 flwlibc 不要用 glibc 就可以。
printf( "%*v", N, i );

3.非递归
int p (int i, int N)
{
return i < 2*N
&& printf ("%d\n", N - (N-i) * (2*(i
&& (*((int volatile *)&i - 1) -= 5) && ++(*(int volatile *)&i);
}

4.正确的
这个是通用的正确的代码:
int p(int i, int n)
{
return i<=n && printf("%d\n", i) &&( !p(i+1, n) || printf("%d\n", i));
}
p(1,7),p(2,9)... i

2007年8月20日星期一

Autodesk area的blog,duncan's corner和mayalicious

流体spec
http://area.autodesk.com/blogs/blog/7/

Mel spec
http://area.autodesk.com/blogs/blog/4/

2007年7月27日星期五

maya笔记 2007-7-27

添加和删除粒子

添加粒子:
emit -o particle1;
emit -object particle1;

删除粒子:
在maya中,已经产生的粒子是无法被删除的,在文档中提到,如果要“删除”一个粒子,只能把生命值设置成0,同时粒子的ID不会被回收。

particle -e -or 2 -at lifespanPP -fv 1.0 particleShape1 ;
particle -edit -order 2 -attribute lifespanPP -floatValue 1.0 particleShape1 ;

per-particle属性都是在数组中保存的,但是却不能通过a[n]的方式直接操作。

2007年5月27日星期日

stretchy bone

也就是可以伸长和的骨骼系统,也叫toony rig。

j1-j2-j3
locator1点约束到j1,ik点约束到locator2
创建如下表达式:

vector $l1p,$l3p;
$l1p=<>;
$l3p=<>;
float $diff=(mag($l1p-$l3p)-18)/2;
if($diff>0.001){
joint2.translateX=10+$diff;
joint3.translateX=8+$diff;
}
else{
joint2.translateX=10;
joint3.translateX=8;
}

其中有几个常量,10是j2的tx,8是j3的tx,也就是每段骨骼的长度。

尝试用rotatepivot,结果发现得到的坐标是0,最后只能让locator保持没有父物体的状态用translate。

2007年5月26日星期六

MEL笔记 2007-05-26

joint/ik旋转轴向+/- 180度的限制

如果使用ik handle来控制joint的旋转,会有一个+/-180度限制的问题。一般来说,这不是什么问题,如果通过表达式用这个旋转的角度来控制其他的物体的时候,很可能就会在某个转角出现“跳动(flip)”。
比如j1-j2是一个joint层级,j1是父,用ikhandle(sc/pr均可)来控制j1的转向。有如下表达式:
locator1.rx=j1.rx/4;

拖动ikhandle,j1的旋转不会出现跳动,但是locator1在j1旋转过某些位置(j1.rx超过180或者小于-180)的时候,就会发生跳动。

如何绕过这个问题呢?

j1-j2同上,locator1是j1-j2要指向的位置,locator2-locator3是旋转的驱动。
将j1 aim约束到locator1上,只约束y和z,在j1.rx创建如下表达式,
j1.rx=locator2.rx+locator3.rx;
这样基本上就可以了。


05年的maya master classes,starwar3的note里,介绍了一个prop工具,可以非常方便的制作手持物体的动画,可惜在没有更多的说明,除了一段屏幕录像和pdf中的2页简述,几乎不涉及任何实现。根据一些猜测和试验,现在差不多已经清楚如何实现,这个joint的问题就是在prop之后,控制手的旋转时遇到的问题之一。

2007年5月24日星期四

MEL笔记 2007-05-24

joint & locator
joint的属性:
创建joint的时候,会有这么一句命令: joint -e -zso -oj xyz -sao yup joint13;
作用是将orient按照xyz的优先指向第一个子节点,会改变orientation的值,这句命令有一个条件,就是joint13的Rotate必须为0。channelbox中显示为0并不一定是0,可能是个很小的数。

orient也可以用直接用命令joint -e -orientation来修改,在设置新的值之后,joint会旋转,但是rotate的值却不会改变。

给locator添加joint子物体的时候,如果locator的scale不为1,那么joint的translate将保持原来的值。

2007年5月16日星期三

MEL笔记 3

maya master 2005中有starwar3的note中介绍了一个UNIQUE CHARACTER USER
INTERFACES,其中有个创建带分区位图界面的要求。我发现用formLayout可以满足这个要求。

拿mel的例子改一改,貌似位图要最后加载才能正常显示。


string $window = `window`;
string $form = `formLayout -numberOfDivisions 100`;
string $b1 = `button -label "b1"`;
string $b2 = `button -label "b2"`;
string $column = `columnLayout -adjustableColumn true`;
button; button; button;

setParent ..;
formLayout;
picture -image "MayaStartupImage.xpm";

formLayout -edit
-attachForm $b1 "top" 5
-attachForm $b1 "left" 5
-attachControl $b1 "bottom" 5 $b2
-attachPosition $b1 "right" 5 75

-attachNone $b2 "top"
-attachForm $b2 "left" 5
-attachForm $b2 "bottom" 5
-attachForm $b2 "right" 5

-attachForm $column "top" 5
-attachPosition $column "left" 0 75
-attachControl $column "bottom" 5 $b2
-attachForm $column "right" 5
$form;

showWindow $window;

2007年5月13日星期日

Maya8.5 linux 32bitbug

Bug多多。
1. q/w/e..marking menu,如果在lmb放开之前把键盘放开的话,crash...
2. 如果选中一个物体,然后打开channel editor,crash...

2007年5月2日星期三

Maya笔记 1

projection贴图的alpha通道问题。
在使用layeredTexture的时候发现的这个问题。projection的image参数只接受图像的RGB参数,然而在输出的时候,却提供alpha的输出,如此,在制作复合贴图的时候,图像的alpha参数就无法正确传递。
workround:把图像的alpha连接到projection节点的alpha offset上,projection的alpha gain设置为0。如果图像没有warp的话,图像的default color和projection的default color都要设置为0,避免“膏药”的效果。

Maya8.5的文档中有一处错误,transferAttribute的sampleSpace参数,文档中说3是component-based,实际上应该是4,3会导致程序崩溃。

2007年4月23日星期一

ffmpeg,libquicktime,nuke像素格式笔记

NUKE:
nuke的图像数据保存在channel中,我们只需要用到其中的r,g,b三个channel。这三个channel是相互独立的,可以分别从中读取数据,每一次可以读取图像的一行像素。这样看来,最合理的线性存储方式就是每个channel的数据作为一组来保存,即:RRRR...RRRRGGGG...GGGGBBBB...BBBB的形似。

FFMPEG:
ffmpeg支持的编码比较多,如果按照像素格式来分,主要有两类,一类是是RGB,另一类是YUV。对于影像类编码,比如xvid/mpeg4之类,颜色模式只能是YUV,甚至仅仅支持yuv420p这一种。因为要考虑到与nuke相结合,这里只看rgb像素格式中的rgb24。rgb24的线性格式为:RRRR...RRRRGGGG...GGGGBBBB...BBBB,可以很方便的和nuke中的channel数据结合。

ffmpeg提供了一个函数可以在各种像素格式之间转换。yuv420p的Cr和Cb分量只有Y分量大小的一半,因此需要的空间只有rgb24的2/3。

libquicktime:
libquicktime中没有与上面RGB24一致的原始rgb格式,有一种rgb888格式。其线性格式为:RGBRGBRGB...。libquicktime在保存帧的时候也要先将rgb转换为yuv,不过这个过程是自动完成的。

从rgb24到rgb888的转换:


pFrameBufferLinear[y*w*3+3*x] = pFrameRGB_r[Y*w+i]; //R
pFrameBufferLinear[y*w*3+3*x+1] = pFrameRGB_g[Y*w+i]; //G
pFrameBufferLinear[y*w*3+3*x+2] = pFrameRGB_b[Y*w+i]; //B

2007年4月20日星期五

mel笔记 2

把一个locator约束到一个cv点上。
vector $cvpoint=`xform -query -worldSpace -translation curve1.cv[1]`;
locator1.translateX=$cvpoint.x;
locator1.translateY=$cvpoint.y;
locator1.translateZ=$cvpoint.z;
存在的问题:
只有在播放的时候表达式的值才会被更新。

2007年4月17日星期二

Nuke videoWriter参考资源列表

libquicktime:
http://libquicktime.sourceforge.net/doc/apiref/index.html

Multi-Process:
http://linux.chinaunix.net/doc/program/2005-01-27/957.shtml
http://www.linuxforum.net/books/upfaq/book1.htm
http://www.ibm.com/developerworks/cn/views/linux/articles.jsp

FIFO:
http://www.linuxhq.com/guides/LPG/node11.html#SECTION00722000000000000000

Nuke4.6 for linux的video输出插件

第一个测试版终于完成了。
一共两个 程序,videoWriter是nuke的插件,lqt_server是mov编码程序。
依赖关系:
lqt_server依赖libquicktime,而libquicktime依赖ffmpeg,要编译的话,需要先编译安装ffmpeg和libquicktime。
我的libquicktime和ffmpeg都在/opt里,zip里包含的预编译版本都是链接/opt里的libquicktime和ffmpeg。
安装:
编译完成后,将videoWriter.linuxfc4_intel复制到~/.nuke里,lqtEncodeServer复制到/usr/local/bin里。
使用:
创建一个write节点,文件类型选择video,".mov"的输出留给foundry去实现吧。在Total frames设置要输出的总帧数,framerate是帧率。在execute的时候还要输入帧范围,务必使这个帧数和Total frames中设置的相等,否则程序会出错。最后输出的文件是是由ffmpeg_mpeg4编码的mov格式的文件。用libquicktime的qtinfo工具可以看到格式的信息,用mplayer和vlc可以正常播放,但是xine不能。

其它:
videoWriter是在NDK中yuvWriter的基础上改写的,因此继承了yuvWriter类的局限,Total frames参数就是其一。在yuvWriter无法获得序列的总帧数,而视频输出的过程中,这个帧数必须是知道的。在Write类中是可以知道总帧数, yuvWriter并未继承Write类,而是从FileWriter类里继承过来的,也就是说需要从Write类派生出一个新的类才能解决帧数未知的问题。




文件:
http://cobranail.googlepages.com/videoWriter_Nuke.zip

MEL笔记 1

source命令:
Maya的文档上说:“ It is important for advanced MEL users to understand that "source" is not a MEL command.”
在文档中举的几个例子似乎对此也有暗示。根据我的体会,source不接受变量做参数。比如source "mel_file.mel"是可以的,但是如果用这种形式:
string $mfile = "mel_file.mel";
source $mfile;
就会提示语法错误。使用eval似乎也不可以trick。

$gShelfTopLevel变量:
global string $gShelfTopLevel;
这个变量似乎是工具架的默认父级部件,createNewShelf等创建shelf的命令都会将新的shelf添加到这个变量所指的部件之下。

shelf文件:
SaveAllShelves之后,各个工具架上的button都会被保存到mel文件中,里面是一个函数,函数命即为工具架的名称。函数的内容就是一些shelfButton命令。
例如:
global proc Polys () {
global string $gBuffStr;
global string $gBuffStr0;
global string $gBuffStr1;


shelfButton
-enableCommandRepeat 1
-enable 1
-width 38
-height 38
-manage 1
-visible 1
-preventOverride 0
-align "center"
-label "Sphere"
-labelOffset 0
-font "tinyBoldLabelFont"
-image "polySphere.xpm"
-image1 "polySphere.xpm"
-style "iconOnly"
-marginWidth 1
-marginHeight 1
-command "polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -cuv 2 -ch 1; objectMoveCommand;"
-sourceType "mel"
-actionIsSubstitute 0
;
}