两个文件VRML环境之间互操作实例
来源:第三维度
在VRML中,将不同物体放置在不同的wrl文件中的最大好处是,使VRML场景的层次结构更加清晰,代码可读性强,维护修改容易。
通常,我们可以将一个场景里的不同物体写在不同的wrl文件中,最后在主场景wrl文件里将各个物体通过“Inline”节点导入,形成一个完整场景。
这种方式对于用户交互和动画比较少并且形式单纯的方式比较合适,但是在一些复杂情况下,用“Inline”节点是无法实现的。其原因在于“Inline”节点没有用于交互的输入输出事件。
举一个最简单的例子。场景中有两个物体A和B,A具有插值动画,可以旋转,B具有一个touchSensor(触摸感应器)。
现在要求实现:
1.A物体的绘制和动画代码写在a.wrl文件中,B物体的绘制和触发器代码写在b.wrl文件中(这样做的目的是为了实现前面描述的好处。)\
2.鼠标点击物体B后,触发A物体的动画。
显然,使用“Inline”节点是无法同时满足上面两个条件的。
但是两个条件在VRML中并非无法满足。
这个时候,我们需要使用PROTO节点和EXTERNPROTO进行原型声明和外部引用。下面对代码编写进行详细说明。
首先,将a.wrl和b.wrl的完整代码贴上来:
a.wrl:
#VRML V2.0 utf8
#Cosmo Worlds V2.0
PROTO KfaPositionInterpolator [
eventIn SFFloat set_fraction
eventOut SFVec3f value_changed
exposedField MFFloat key 0
exposedField MFInt32 keyTypes 2
exposedField MFVec3f keyValue 0 0 0
field MFFloat authorKey 0
field MFVec3f authorKeyValue 0 0 0
]
{
PositionInterpolator {
key IS key
set_fraction IS set_fraction
keyValue IS keyValue
value_changed IS value_changed
}
}
PROTO KfaAnimation [
field SFFloat framesPerSecond 10
field SFFloat zoom 1
field SFBool snap TRUE
field SFBool viewInFrames TRUE
field SFBool showEmptyFieldLines FALSE
field SFFloat playRangeStart 0
field SFFloat playRangeEnd 1
field SFBool usePlayRange TRUE
field SFNode timeSensor NULL
field MFNode fieldInterps []
field MFNode actors []
]
{
Group {
}
}
PROTO AnimationInA [
eventIn SFTime TouchTime
]
{
DEF _0 Transform {
children [
Shape {
appearance Appearance {
material Material {
}
}
geometry Box {
}
}
DEF UnnamedAnimation0 KfaAnimation {
framesPerSecond 25
snap TRUE
viewInFrames TRUE
playRangeStart 0
playRangeEnd 1
timeSensor DEF UnnamedAnimation0Time TimeSensor {
startTime IS TouchTime
cycleInterval 4
}
actors USE _0
fieldInterps [
DEF UnnamedTransformScaleInterp KfaPositionInterpolator {
key [ 0, 0.01, 0.02, 0.03,
0.04, 0.05, 0.06, 0.07,
0.08, 0.09, 0.1, 0.11,
0.12, 0.13, 0.14, 0.15,
0.16, 0.17, 0.18, 0.19,
0.2, 0.21, 0.22, 0.23,
0.24, 0.25, 0.26, 0.27,
0.28, 0.29, 0.3, 0.31,
0.32, 0.33, 0.34, 0.35,
0.36, 0.37, 0.38, 0.39,
0.4, 0.41, 0.42, 0.43,
0.44, 0.45, 0.46, 0.47,
0.48, 0.49, 0.5, 0.51,
0.52, 0.53, 0.54, 0.55,
0.56, 0.57, 0.58, 0.59,
0.6, 0.61, 0.62, 0.63,
0.64, 0.65, 0.66, 0.67,
0.68, 0.69, 0.7, 0.71,
0.72, 0.73, 0.74, 0.75,
0.76, 0.77, 0.779999, 0.789999,
0.799999, 0.809999, 0.819999, 0.829999,
0.839999, 0.849999, 0.859999, 0.869999,
0.879999, 0.889999, 0.899999, 0.909999,
0.919999, 0.929999, 0.939999, 0.949999,
0.959999, 0.969999, 0.979999, 0.989999,
1 ]
keyTypes [ 2, 2, 2 ]
authorKey [ 0, 0.25, 1 ]
keyValue [ 1 1 1,
1.05987 1.05987 1.05987,
1.12379 1.12379 1.12379,
1.19122 1.19122 1.19122,
1.26161 1.26161 1.26161,
1.33439 1.33439 1.33439,
1.40902 1.40902 1.40902,
1.48493 1.48493 1.48493,
1.56159 1.56159 1.56159,
1.63843 1.63843 1.63843,
1.7149 1.7149 1.7149,
1.79045 1.79045 1.79045,
1.86452 1.86452 1.86452,
1.93657 1.93657 1.93657,
2.00603 2.00603 2.00603,
2.07235 2.07235 2.07235,
2.13498 2.13498 2.13498,
2.19338 2.19338 2.19338,
2.24697 2.24697 2.24697,
2.29521 2.29521 2.29521,
2.33756 2.33756 2.33756,
2.37344 2.37344 2.37344,
2.40231 2.40231 2.40231,
2.42362 2.42362 2.42362,
2.43681 2.43681 2.43681,
2.44133 2.44133 2.44133,
2.44082 2.44082 2.44082,
2.43931 2.43931 2.43931,
2.43681 2.43681 2.43681,
2.43335 2.43335 2.43335,
2.42895 2.42895 2.42895,
2.42362 2.42362 2.42362,
2.41739 2.41739 2.41739,
2.41028 2.41028 2.41028,
2.40231 2.40231 2.40231,
2.3935 2.3935 2.3935,
2.38387 2.38387 2.38387,
2.37344 2.37344 2.37344,
2.36223 2.36223 2.36223,
2.35026 2.35026 2.35026,
2.33756 2.33756 2.33756,
2.32413 2.32413 2.32413,
2.31001 2.31001 2.31001,
2.29521 2.29521 2.29521,
2.27976 2.27976 2.27976,
2.26367 2.26367 2.26367,
2.24697 2.24697 2.24697,
2.22967 2.22967 2.22967,
2.2118 2.2118 2.2118,
2.19338 2.19338 2.19338,
2.17442 2.17442 2.17442,
2.15495 2.15495 2.15495,
2.13499 2.13499 2.13499,
2.11455 2.11455 2.11455,
2.09367 2.09367 2.09367,
2.07235 2.07235 2.07235,
2.05063 2.05063 2.05063,
2.02851 2.02851 2.02851,
2.00603 2.00603 2.00603,
1.98319 1.98319 1.98319,
1.96003 1.96003 1.96003,
1.93657 1.93657 1.93657,
1.91281 1.91281 1.91281,
1.88879 1.88879 1.88879,
1.86452 1.86452 1.86452,
1.84003 1.84003 1.84003,
1.81533 1.81533 1.81533,
1.79045 1.79045 1.79045,
1.7654 1.7654 1.7654,
1.74021 1.74021 1.74021,
1.7149 1.7149 1.7149,
1.68949 1.68949 1.68949,
1.66399 1.66399 1.66399,
1.63843 1.63843 1.63843,
1.61283 1.61283 1.61283,
1.58721 1.58721 1.58721,
1.56159 1.56159 1.56159,
1.53599 1.53599 1.53599,
1.51043 1.51043 1.51043,
1.48493 1.48493 1.48493,
1.45952 1.45952 1.45952,
1.43421 1.43421 1.43421,
1.40902 1.40902 1.40902,
1.38397 1.38397 1.38397,
1.35909 1.35909 1.35909,
1.33439 1.33439 1.33439,
1.3099 1.3099 1.3099,
1.28563 1.28563 1.28563,
1.26161 1.26161 1.26161,
1.23785 1.23785 1.23785,
1.21439 1.21439 1.21439,
1.19123 1.19123 1.19123,
1.16839 1.16839 1.16839,
1.14591 1.14591 1.14591,
1.12379 1.12379 1.12379,
1.10207 1.10207 1.10207,
1.08075 1.08075 1.08075,
1.05987 1.05987 1.05987,
1.03943 1.03943 1.03943,
1.01947 1.01947 1.01947,
1 1 1 ]
authorKeyValue [ 1 1 1,
2.44133 2.44133 2.44133,
1 1 1 ]
}
]
}
]
translation 4 1 0
rotation 0 1 0 3.14159
scale 1 1 1
}
ROUTE UnnamedAnimation0Time.fraction_changed TO UnnamedTransformScaleInterp.set_fraction
ROUTE UnnamedTransformScaleInterp.value_changed TO _0.set_scale
}
b.wrl:
#VRML V2.0 utf8
#Cosmo Worlds V2.0
Transform {
children [
DEF TS TouchSensor {}
Shape {
appearance Appearance {
material Material {
}
}
geometry Sphere {
}
}
]
translation -3 1 0
}
EXTERNPROTO AnimationInB [
eventIn SFTime TouchTime
]
"a.wrl#AnimationInA"
DEF animation AnimationInB {}
ROUTE TS.touchTime TO animation.TouchTime
分析一下。代码很简单,a.wrl中,画了一个正方体,并使用一个TimeSensor和PositionInterpolator实现插值动画。b.wrl中,画了一个球体,并在球题上安装了一个touchSensor作为鼠标点击的感应器。
注意,在a.wrl文件中,我们将A物体的绘制和动画代码使用PROTO括起来,使它成为一个原型(相当于一个自定义的节点。如果不知原型为何物,请参阅任意关于VRML的教材)。
原型的域中,我们加入一个时间型入事件:
eventIn SFTime TouchTime
这个入事件的作用,后面再详细说。
接下来,在时间感应器TimeSensor中,我们这样写:
timeSensor DEF UnnamedAnimation0Time TimeSensor {
startTime IS TouchTime
cycleInterval 4
}
注意里面的“startTime IS TouchTime”一句,表明将前面定义的时间型入事件 TouchTime作为这个TimeSensor的开始时间。
最后,注意动画的路由:
ROUTE UnnamedAnimation0Time.fraction_changed TO UnnamedTransformScaleInterp.set_fraction
ROUTE UnnamedTransformScaleInterp.value_changed TO _0.set_scale
也要包括在原型中。
好了,再来分析b.wrl。
b.wrl在绘制完一个球体后,使用:
EXTERNPROTO Animation [
eventIn SFTime TouchTime
]
"1.wrl#Animation"
进行外部原型引用,引用路径之所以写成:"1.wrl#Animation",是因为1.wrl中定义了好几个原型,而我们这里需要引用的是Animation这个原型。
然后,我们使用
DEF animation Animation {}
生成一个实际的对象。在这里,Animation实际上已经相当于VRML中已经有的节点。如果这里看不懂,同样,请参阅任何一本VRML书籍中,关于PROTO的介绍。
最后,我们生成路由:
ROUTE TS.touchTime TO animation.TouchTime
大功告成!
说了这么多,代码虽然简单,但是也有不少(其实主要的代码量来自于插值动画的中间值,真正的功能代码非常少),下面进行一下总结:
思路其实十分简单。用户点击球体后,相当于触发了球体上的touchSensor,产生一个时间型的事件,将此事件通过ROUTE TS.touchTime TO animation.TouchTime,传递给我们定义的原型AnimationInB,从而传到了所引用的外部原型AnimationInA中(也就是说,传到了a.wrl文件中),而由于AnimationInA的TimeSensor中有一个“startTime IS TouchTime”,所以这个时间型事件最终传到了时间触发器TimeSensor里,触发了动画。
原型定义PROTO和外部原型引用EXTERNPROTO是一对非常有用的节点,人和我们自己写的扩展节点都要用到他们。灵活使用不仅能够使我们的VRML场景更加丰富多彩,还能提高效率,减少代码复写率等。