虚拟现实VRML简明教程
第四节 连续动画
  小结:本节实现连续动画,动画由接触检测器启动,由时间检测器驱动,动画本身比较简单,就是不断地旋转。产生不断变化的旋转值的方法有两种:自己编写脚本,或者利用插补器节点。

#VRML V2.0 utf8
DEF cube Transform {
 rotation 1 1 1 0
  children [
   Shape {
    appearance Appearance {
     material Material {
      diffuseColor 0 1 0
      }
     }
    geometry Box {}
    }
   DEF TouchS TouchSensor {}
   ]
 }
DEF revolver OrientationInterpolator {
 key [0,1]
 keyValue [ 0.5 0.5 0.5 0,0.5 0.5 0.5 3.14159]
 }
DEF ticker TimeSensor {
 cycleInterval 2
 loop TRUE
 enabled FALSE
 }
ROUTE TouchS.isOver TO ticker.set_enabled
ROUTE ticker.fraction_changed TO revolver.set_fraction
ROUTE revolver.value_changed TO cube.set_rotation

3。 朝向插补器

  插补器节点可认为是VRML内置的脚本节点,它们执行简单的动态计算,通常和时间检测器或者能够使对象产生动作的节点结合在一起使用,生成线性关键帧动画。插补器节点实际上是一个由关键点和对应关键值定义的分段线形函数。根据插值类型的不同,VRML共定义六个插补器节点:

  ColorInterpolator(颜色插补器)、

  CoordinateInterpolator(坐标插补器)、

  NormalInterpolator(法线插补器)、

  OrientationInterpolator(朝向插补器)、

  positionInterpolator(位置插补器)、

  ScalarInterpolator(标量插补器)。

  所有插补器的域和事件都是类似的:

  eventIn SFFloat set_fruction

  exposedField MFFloat key [...]

  exposedField MF[type] keyValue [.....]

  eventOut [S|M]F[type] keyValue_changed

  关键值域keyValue的类型决定了插补器的类型(例如,OrientationInterpolator的keyValue域的类型是MFFloat).入事件set_fraction接收SFFloat型的事件,插补器随即根据它进行插值,并通过出事件value_changed送出插值结果。

  这里我们把时间检测器的fraction_changed事件作为插补器的输入,这个事件是一个[0,1]区间的值,每个时间步都送出一次,表示当前周期内已过去的时间相对于整个周期的比例,是插补器常用的输入源之一。与此对应,我们把插补器关键帧的取值也定义在[0,1]范围内。与0和1这两个关键帧对应的关键值的旋转轴是相同的,只是旋转角度不同(0,3.14159),这样方位插补器输出的旋转值的旋转轴固定不变,旋转角从0递增到3.14159,然后不断重复。

  上述脚本节点的功能比较简单,只是不断送出调整的旋转值,它是关键帧动画的一种。由于关键帧动画十分常用,故VRML专门定义了插补器节点来实现它。

#VRML V2.0 utf8
DEF cube Transform {
 rotation 1 1 1 0
 children [
  Shape {
   appearance Appearance {
    material Material {
     diffuseColor 1 0 0
     }
    }
   geoemtry Box {}
   }
  DEF TouchS TouchSensor {}
  ]
 }
DEF revolver Script {
 eventIn SFTime startRevolving
 eventOut SFRotation revolve
 field SFFloat angle 0
 url "vrmlscript :
   function startRevolving () {
    revolve[0]=1;
    revolve[1]=1;
    revolve[2]=1;
    revolve[3]=angle;
    angle+=0.1;
    }"
 }
DEF ticker TimeSensor {
 cycleInterval 0.1
 loop TRUE
 enabled FALSE
 }
ROUTE TouchS.isOver TO ticker.set_enabled
ROUTE ticker.cycleTime TO revolver.startRevolving
ROUTE revolver.revolve TO cube.set_rotation

  enabled用于启用和停用时间检测器,开始时它处于停用状态,以后由接触检测器的isOver事件修改这一状态。启用的时间检测器每隔0.1秒送出一个cycleTime事件,并用它来触发revolver的startRevolving事件,注意,cycleTime事件的类型为SFTime,而路由两端事件的类型必须匹配,所以尽管这里我们不关心这个事件表示的具体时刻,还是把revolver的startRevolving事件类型也改为SFTime.这样,revolver的函数startRevolving()就会每0.1秒调用一次,从而驱动方块连续旋转。完整的代码是:

  DEF ticker TimeSensor {

   cleInterval 0.1

   loop TRUE

   enabled FALSE

   }

  ROUTE TouchS.isOver TO ticker.set_enabled

  ROUTE ticker.cycleTime TO revolver.startRevolving

  ROUTE revolver.revolve TO cube.set_rotation

2。时间检测器

  为了使方块能够连续旋转,需要引进等间隔连续发送的时间序列,这正是时间检测器的用武之地。时间检测器随着时间推移不断产生事件,可用于多种目的,包括:

  a. 驱动连续性的仿真和动画

  b. 控制周期性的活动(如每分钟一次)

  c. 初始化单独事件,如报警钟

  下面是我们要用的时间检测器和修改后的路由关系:

  Script节点revolver的核心是内联的ECMAScript脚本函数。它给定一个不断变化的旋转值。当鼠标指针移动到方块之上时,接触检测器发出isOver,和第一节中采用的isActive事件不同,isOver不需鼠标左钮按下时即可发出。isOver事件通过路由传递给脚本节点的事件入口startRevolving,从而启动函数startRevolving,函数将一个新的旋转值发往事件出口revolve,这个旋转值通过路由进入cube的外露域rotation,修改了方块的旋转角,引起它的朝向变化。鼠标指针在cube上面的每次方位变化都引起isOver事件发送一次,从而导致方块旋转一次。
  其中,方块cube包含两个子节点,前者定义了它的形态(红色的单位立方体),后者把它定义成接触检测器。注意,cube的类型是Transform节点,它的rotation 域是外露域,指定本组相对于上层坐标系的旋转值,这里指定的初始值是“1 1 1 0 ”,其中前三个数值定义旋转轴,最后一个值定义旋转角。由于它是外露域,因而可以通过入事件(名为set_rotation)进行修改,下面定义的动态行为就是这样实现的。

1。接触检测器

  作为开始的基本代码是:

  #VRML V2.0 utf8
  DEF cube Transform {
   rotation 1 1 1 0
   children [
    Shape {
     appearance Appearance {
      material Material {
       diffuseColor 1 0 0
       }
      }
     geometry Box {}
     }
    DEF TouchS TouchSensor {}
    ]
   }
  DEF revolver Script {
   eventIn SFBool startRevolving
   eventOut SFRotation revolve
   field SFFloat angle 0
   url "javascript :
    function startRevolving () {
     revolve[0]=1;
     revolve[1]=1;
     revolve[2]=1;
     revolve[3]=angle;
     angle+=0.1;
     }"
    }
  ROUTE TouchS.isOver TO revolver.startRevolving
  ROUTE revolver.revolve TO cube.set_rotation

  在第二节中我们已经使用过接触检测器,当我们把鼠标指针放到方块(这个几何节点包含接触检测器)上面时,指针形状发生变化,这意味着我们已经进入检测区,如果按下鼠标左钮,则按照我们的定义,当前视点会发生变化。

  这一节仍然制作这样一个对接触有反应的方块,只是接触后它会连续不断地转动,动画行为可以用时间检测器(TimeSensor)驱动,而不断变化的旋转值可用脚本节点或朝向插补器(orientationInterpolator)给出。