[ブログに戻る](/ja/blog) # Visualize SDK: アニメーションの操作

Egor Slupko 5月 21, 2021 [\#Visualize](/ja/blog/tag/visualize) [\#animation](/ja/blog/tag/animation)

<a class="a2a_button_facebook"></a><a class="a2a_button_twitter"></a><a class="a2a_button_linkedin"></a>

Visualize SDKを使用すると、エンティティ、サブエンティティ、カメラのアニメーションを実装できます。アニメーションAPIには、移動、回転、スケーリングの3つの変換が可能です。

#### アニメーションの作成

##### アニメーションコンテナ

アニメーション作成の最初のステップは、アニメーションコンテナの作成です。アニメーションコンテナにはペアが含まれ、それらをタイムラインにバインドします。ここでActorはアニメーション化されるオブジェクトを定義し、Actionは変更ルールを定義します。Visualize SDKのアニメーションコンテナは、OdTvAnimationContainerオブジェクトによって表されます。これは、対応するOdTvDatabaseメソッドを使用して作成できます。

```
OdTvAnimationContainerId contId = pDb->createAnimationContainer( OD_T( "LineAnimation" ) );
OdTvAnimationContainerPtr pCont = contId.openObject( OdTv::kForWrite );
```

##### アクター

アクターは、アニメーションプロセス中に変更されるオブジェクトを定義します。Visualize SDKでは、アクターはOdTvSubItemPathクラスによって表され、エンティティ、サブエンティティ、またはカメラオブジェクトを参照できます。

##### アクション

アクションは変更ルールを定義し、アニメーション開発において最も複雑なフェーズです。アニメーションコンテナはアクションをタイムラインにバインドするため、アクションは変更ルールにフレーム表現を使用します。アクションにはフレームが含まれます。これらのフレームの一部は、指定する必要があるキーフレームです。その他のフレームは、キーフレームと指定された補間方法を使用して補間されます。Visualize SDKのアクションは、OdTvAnimationActionオブジェクトによって表されます。これは、対応するOdTvDatabaseメソッドを使用して作成できます。

```
OdTvAnimationActionId actionId = pDb->createAnimationAction( OD_T( "LineRotation" ) );
OdTvAnimationActionPtr pAction = actionId.openObject( OdTv::kForWrite );
```

アクションが作成されたら、設定する必要があります。たとえば、フレーム数を指定できます。

```
pAction->setNumFrames( 361 );
```

また、アクションの速度を定義するために、1秒あたりのフレーム数を指定できます。設定が完了したら、次のOdTvAnimationActionメソッドを使用してアクションのキーフレームを指定する必要があります。

```
virtual OdTvResult setKeypoint( OdUInt32 nFrame, Keydata kd, double keyval, Interpolator ipl = kCubic ) = 0;
```

- nFrame — フレーム番号
- kd — 変更されるオブジェクトデータ
- keyval — このフレームでの実際のオブジェクトデータ値
- ipl — キーフレーム間で使用される補間メカニズム

例えば、次のコードは、361フレームを含み、Z軸を中心にオブジェクトを360度回転させるアクションを作成します（10フレームごとにキーフレームがあります）。

```
OdTvAnimationActionId actionId = pDb->createAnimationAction( OD_T( "LineRotation" ) );
OdTvAnimationActionPtr pAction = actionId.openObject( OdTv::kForWrite );
pAction->setNumFrames( 361 );
for( OdUInt32 i = 0; i <= 360; i+=10 )
{
  double angle = (double)(i)*OdaPI / 180.0;
  pAction->setKeypoint( i, OdTvAnimationAction::kRotationZ, angle, OdTvAnimationAction::kLinear );
}
```

キーフレームは、複数のキーデータチャネルを変更できます。例：

```
pAction->setKeypoint( 25, OdTvAnimationAction::kTranslationX, 10, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 25, OdTvAnimationAction::kTranslationY, -10, OdTvAnimationAction::kLinear );
```

このサンプルでは、キーフレームはオブジェクトをX軸とY軸の両方に沿って移動させます。

##### キーデータ

アニメーションアクションは、移動、回転、スケーリングの3つの変換をサポートしています。それぞれは、OdTvAnimationAction::Keydata 列挙型で3つのチャネルとして表現されます。

```
enum Keydata
{
  kTranslationX,  //Translation by the x-axis.
  kTranslationY,  //Translation by the y-axis.
  kTranslationZ,  //Translation by the z-axis.
  kRotationX,     //Rotation by the x-axis.
  kRotationY,     //Rotation by the y-axis.
  kRotationZ,     //Rotation by the z-axis.
  kScaleX,        //Scaling by the x-axis.
  kScaleY,        //Scaling by the y-axis.
  kScaleZ,        //Scaling by the z-axis.

  kNumKeydata     //Service type: number of actual data types.
};
```

##### 補間

\[Keyframe1;Keyframe2\] の間隔にあるフレームは、次の3つの方法で補間できます。

- しきい値補間: 値 = 区間 \[Keyframe1; Keyframe2) での Keyframe1 の値、およびフレーム Keyframe2 での Keyframe2 の値
- 線形補間
- 3次スプライン補間

補間の種類は、OdTvAnimationAction::Interpolator 列挙型によって記述されます。

```
enum Interpolator
{
  kThreshold,     //No interpolation: value is equal to the previous keypoint value.
  kLinear,        //Linear interpolation.
  kCubic          //Cubic spline interpolation.
};
```

##### アクションとアクターの結合

アクションとアクターが指定された後、次の OdTvAnimationContainer メソッドを使用してアニメーションコンテナに追加できます。

```
virtual OdTvResult addAnimation( const OdTvSubItemPath& actor, const OdTvAnimationActionId& actionId, OdInt32 nRepeats = 0, OdUInt32 timeStart = 0, OdTvActorBasis* pCustomBasis = NULL ) = 0;
};
```

- nRepeats — アニメーションの繰り返し回数 (0 – 繰り返しなし、アニメーションを1回のみ再生; 1 – 1回再生して繰り返す; -1 – 無限に繰り返す)
- timeStart — アニメーションの開始時間 (ミリ秒単位)
- pCustomBasis — カスタムアクター基底へのポインタ

同じアニメーションアクションを、同じまたは異なるアニメーションコンテナ内の異なるアクターで使用できます（各アクターに対して「360度回転」アクションを作成する必要はありません）。また、同じアクターを、同じまたは異なるアニメーションコンテナ内の異なるアクションで使用できます。

##### カスタムアクター基底

アクターと、アクターをX軸を中心に360度回転させるアニメーションアクションがあると仮定します。

```
OdTvAnimationActionPtr pAction = actionId.openObject( OdTv::kForWrite );
pAction->setNumFrames( 360 );
pAction->setKeypoint( 0, OdTvAnimationAction::kRotationX, 0.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 359, OdTvAnimationAction::kRotationX, 359.0 * OdaPI / 180.0, OdTvAnimationAction::kLinear );
```

例えば、OdTvVector( 1.0, 1.0, 1.0 ) のような異なるベクトルを中心にアクターを回転させたいとします。上記のコードのアクションを使用して、カスタムアクター基底を指定できます。

```
OdTvAnimationContainer::OdTvActorBasis* pBasis = new OdTvAnimationContainer::OdTvActorBasis();
pBasis->origin() = actorExtents.center();
pBasis->xAxis() = OdTvVector( 1.0, 1.0, 1.0 );
pAction->addAnimation( actor, actionId, -1, 0, pBasis );
```

このサンプルでは、アクターの範囲の中心が原点として機能します。これはエンティティアニメーションのデフォルトの原点です。OdTvActorBasis には制限がないため、異なる軸は共線的で非正規化されていてもかまいません。

#### アニメーションの再生

アニメーションコンテナは、次のメソッドを使用してアニメーションを再生するためにも使用する必要があります。

```
virtual OdTvResult setCurrentTime( OdUInt32 msec )
```

このメソッドはコンテナの現在時刻を指定するため、時刻を、アクターによってアクションを含み実行する各 &lt;アクター、アクション&gt; ペアの適切なフレーム番号に変換します。例えば、Microsoft® Windows® オペレーティングシステム用の次のコードは、アニメーションコンテナの現在時刻を0として使用し、次に保存された現在時刻と clock() メソッドの結果との差を使用してオブジェクトをアニメーション化します。

```
clock_t start = clock();
clock_t curTime = start;
OdUInt32 nTotalTime = 0;
Bool bInfinite = !pCont->totalTime( nTotalTime );
while( curTime - start < nTotalTime || bInfinite )
{
  pCont->setCurrentTime( curTime – start );
  pDevice->update();
  curTime = clock();
}
```

#### アニメーションを検証する

アニメーションコンテナに&lt;Actor, Action&gt;のペアが追加されると、いくつかのキャッシュが計算されます。しかし、このアクターが変更された（例えば、移動された）後、計算されたキャッシュは無効になります。アニメーションコンテナはアクターやアクションの変更を追跡しないため、以下のメソッドを呼び出してキャッシュを手動で検証する必要があります。

```
virtual OdTvResult validateAnimations() = 0;
```

#### アニメーションの例

以下のサンプルコードは、3つの線を含むモデルを持つVSFファイルを生成します。このファイルには、これらの線に対する無限アニメーションも含まれています。そのうち2つは中心点を中心にZ軸周りに回転し、1つの線は繰り返される「8の字」パターンで移動します。この例は、OdVisualizeViewerアプリケーションの「Examples &gt; Animation &gt; EntityAnimation」から起動できます。

```
//1. Initial step
//1.1 Create database and model
OdTvDatabaseId dbId;
OdTvFactoryId factId = odTvGetFactory();
dbId = factId.createDatabase();
OdTvDatabasePtr pDb = dbId.openObject( OdTv::kForWrite );
OdTvModelId modelId = pDb->createModel( OD_T( "SimpleModel" ) );
OdTvModelPtr pModel = modelId.openObject( OdTv::kForWrite );
//1.2 Create entity with single line
OdTvEntityId entId = pModel->appendEntity();
OdTvEntityPtr pEnt = entId.openObject( OdTv::kForWrite );
pEnt->appendPolyline( OdTvPoint( -1.0, -1.0, 0.0 ), OdTvPoint( 1.0, 1.0, 0.0 ) );
//2. Create animation
//2.1 Create animation container
OdTvAnimationContainerId contId = pDb->createAnimationContainer( OD_T( "LineAnimation" ) );
OdTvAnimationContainerPtr pCont = contId.openObject( OdTv::kForWrite );
//2.2 Create animation action: rotation on 360 degrees around Z axis
//2.2.1 Create action
OdTvAnimationActionId actionId = pDb->createAnimationAction( OD_T( "LineRotation" ) );
OdTvAnimationActionPtr pAction = actionId.openObject( OdTv::kForWrite );
//2.2.2 Specify number of frames
pAction->setNumFrames( 361 );
//2.2.3 Each 10 frame is a key frame
for( OdUInt32 i = 0; i <= 360; i+=10 )
{
  double angle = (double)(i)*OdaPI / 180.0;
  pAction->setKeypoint( i, OdTvAnimationAction::kRotationZ, angle, OdTvAnimationAction::kLinear );
}
//2.3 Specify actor
OdTvSubItemPath actor;
actor.entitiesIds().push_back( entId );
//2.4 Add pair <Actor, Action> to the container with infinite repeats number
pCont->addAnimation( actor, actionId, -1 );

//Another animation with the same action
entId = pModel->appendEntity( OD_T( "RotatingLine2" ) );
pEnt = entId.openObject( OdTv::kForWrite );
pEnt->appendPolyline( OdTvPoint( -1.0, -1.0, 0.0 ), OdTvPoint( 1.0, 1.0, 0.0 ) );
pEnt->setModelingMatrix( OdTvMatrix::translation( OdTvVector( 3.0, 0.0, 0.0 ) ) );
contId = pDb->createAnimationContainer( OD_T( "SecondLineAnimation" ) );
pCont = contId.openObject( OdTv::kForWrite );
OdTvSubItemPath actor1;
actor1.entitiesIds().push_back( entId );
pCont->addAnimation( actor1, actionId, -1 );

//Third animation - translation
entId = pModel->appendEntity( OD_T( "MovingLine" ) );
pEnt = entId.openObject( OdTv::kForWrite );
pEnt->appendPolyline( OdTvPoint( -3.0, 1.0, 0.0 ), OdTvPoint( -3.0, -1.0, 0.0 ) );
contId = pDb->createAnimationContainer( OD_T( "ThirdLineAnimation" ) );
pCont = contId.openObject( OdTv::kForWrite );
OdTvSubItemPath actor2;
actor2.entitiesIds().push_back( entId );
actionId = pDb->createAnimationAction( OD_T( "LineTranslation" ) );
pAction = actionId.openObject( OdTv::kForWrite );
pAction->setNumFrames( 41 );
pAction->setKeypoint( 0, OdTvAnimationAction::kTranslationX, 0.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 0, OdTvAnimationAction::kTranslationY, 0.0, OdTvAnimationAction::kLinear );

pAction->setKeypoint( 10, OdTvAnimationAction::kTranslationX, -1.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 10, OdTvAnimationAction::kTranslationY, -1.0, OdTvAnimationAction::kLinear );

pAction->setKeypoint( 20, OdTvAnimationAction::kTranslationX, -1.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 20, OdTvAnimationAction::kTranslationY, 0.0, OdTvAnimationAction::kLinear );

pAction->setKeypoint( 30, OdTvAnimationAction::kTranslationX, 0.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 30, OdTvAnimationAction::kTranslationY, -1.0, OdTvAnimationAction::kLinear );

pAction->setKeypoint( 40, OdTvAnimationAction::kTranslationX, 0.0, OdTvAnimationAction::kLinear );
pAction->setKeypoint( 40, OdTvAnimationAction::kTranslationY, 0.0, OdTvAnimationAction::kLinear );

pCont->addAnimation( actor2, actionId, -1 );

//3 Create device and view, save file
{
  OdTvGsDeviceId devId = pDb->createDevice( OD_T( "SimpleDevice)" ) );
  OdTvGsDevicePtr pDev = devId.openObject( OdTv::kForWrite );
  OdTvGsViewId viewId = pDev->createView( OD_T( "SimpleView)" ) );
  OdTvGsViewPtr pView = viewId.openObject( OdTv::kForWrite );
  pView->addModel( modelId );
  pDev->addView( viewId );
  pDb->writeFile( OD_T( "D:\\LineAnimation.vsf" ) );
}
```

![animation](/files/inline-images/tv_animation_result.gif)別のカメラアニメーションの例は、OdVisualizeViewerアプリケーションの「Examples &gt; Animation &gt; CameraAnimation」から起動できます。カメラアニメーションの例：

![animation example](/files/inline-images/tv_animation_example_0.gif)両方の例のソースコードの場所: Visualize/Examples/VisualizeModelsGenerator/VisualizeModelsGeneratorImpl.cpp。

#### カメラアニメーションに関する注意点

カメラは5つの異なるアクターとして表現できます。

1\. カメラはエンティティとしてアニメーション化できます。この場合、アクターはカメラ全体である必要があります。

```
OdTvSubItemPath cameraActor;
cameraActor.entitiesIds().push_back( cameraId );
```

2\. カメラの位置のみをアニメーション化できます。この場合、正しいアクターを指定するにはOdTvCamera::positionId()を使用する必要があります。

```
OdTvSubItemPath cameraPositionActor;
cameraPositionActor.entitiesIds().push_back( cameraId );
cameraPositionActor.geometryDatasIds().push_back( pCamera->positionId() );
```

3\. カメラのターゲットのみをアニメーション化できます。この場合、正しいアクターを指定するにはOdTvCamera::targetId()を使用する必要があります。

```
OdTvSubItemPath cameraTargetActor;
cameraTargetActor.entitiesIds().push_back( cameraId );
cameraTargetActor.geometryDatasIds().push_back( pCamera->targetId() );
```

4\. カメラのアップベクトルのみをアニメーション化できます。この場合、正しいアクターを指定するにはOdTvCamera::upId()を使用する必要があります。

```
OdTvSubItemPath cameraUpActor;
cameraUpActor.entitiesIds().push_back( cameraId );
cameraUpActor.geometryDatasIds().push_back( pCamera->upId() );
```

5\. カメラの視野幅と高さのみをアニメーション化できます。この場合、正しいアクターを指定するにはOdTvCamera::fieldsId()を使用する必要があります。

```
OdTvSubItemPath cameraFieldsActor;
cameraFieldsActor.entitiesIds().push_back( cameraId );
cameraFieldsActor.geometryDatasIds().push_back( pCamera->fieldsId() );
```

この場合、kScaleXとkScaleYのアクションフレームチャネルのみがカメラの視野幅と視野高さをスケーリングするために使用され、アニメーションズーム操作に役立ちます。

これら5つのアクターを組み合わせることで、すべてのアクターに共通の変換ルールを適用することなく、まったく新しいカメラパラメータを指定できます。この場合、カメラパラメータの自動調整を無効にすると役立つことがあります。たとえば、自動調整が有効になっている場合、新しいターゲットを指定すると、視線方向とアップベクトルが垂直である必要があるため、カメラのアップベクトルが変更される可能性があります。しかし、アップベクトルアニメーションはアップベクトルのキャッシュされた値に適用されます。したがって、自動調整を無効にすることで、曖昧さを回避するのに役立ちます。

## 今すぐ始める

**ODAソフトウェアを60日間無料でお試しください。  
リスクなし、クレジットカード不要。**

[無料で試す](https://www.opendesign.com/ja/free-trial)
