戻る

直線エンティティを自作してみよう (1)

〜 OpenGLプラットフォーム「ヒスイ」 チュートリアル 〜

2007年9月
HixUT の追加に合わせて加筆修正 2008年8月

これまでは Hisui.Spatial に含まれている定義済みのエンティティを使用してきました。ここでは直線エンティティを自分で定義してみます。次の3つのチュートリアルを理解していることを前提とします。

このチュートリアルのゴール

まず次のようなコマンドを作りましょう。

  [Hisui.Ctrl.Command( "直線作図" )]
  static void PutMyLine( Hisui.Ctrl.IContext con )
  {
    Hisui.Geom.Point3d p1 = new Hisui.Geom.Point3d( 0, 0, 0 );
    Hisui.Geom.Point3d p2 = new Hisui.Geom.Point3d( 1, 2, 3 );
    con.ActiveEntries.Put( new MyLine( p1, p2 ) ); 
  }

このコマンドを動かすと次の結果が得られることをゴールとします。

直線エンティティの作成

特にエンティティだからといって何か特別なクラスを継承する必要ありません。ごく普通に直線クラスを定義すればOKです。

class MyLine
{
  Hisui.Geom.Point3d _p1;
  Hisui.Geom.Point3d _p2;

  public Hisui.Geom.Point3d P1
  {
    get { return _p1; }
    set { _p1 = value; }
  }

  public Hisui.Geom.Point3d P2
  {
    get { return _p2; }
    set { _p2 = value; }
  }

  public MyLine( Hisui.Geom.Point3d p1, Hisui.Geom.Point3d p2 )
  {
    _p1 = p1;
    _p2 = p2;
  }
}

これで PutMyLine コマンドを起動してみてください。次のようにツリービューに登録されたら成功です。

[図07-01: ツリービュー]

しかし、ビューには何も表示されません。次は MyLine を描画するシーンクラスを作成することにします。

直線を描画するシーンクラスの作成

チュートリアル「OpenGL 命令で直線を描画してみよう」では LineScene という直線を描画するシーンクラスを作成しました。それと同じ要領で MyLine エンティティを描画する MyLineScene クラスを作成することにします。

ポイントは次の3つです。

IScene インターフェイスの実装

次のように IScene インターフェイスを実装した MyLineScene クラスを作成します。とりあえず Draw() メソッドの中身は空のままにしておきます。

class MyLineScene : Hisui.Graphics.IScene
{
  public void Draw( Hisui.Graphics.ISceneContext sc )
  {
  }
}

Scene 属性の付与

MyLineScene クラスに Scene 属性を付与します。ヒスイのプラグイン機構は Scene 属性の付いたクラスを見つけるとそれをエンティティにバインドされたシーンとして Hisui.Graphics.SceneFactory に登録します。Scene 属性の引数にはバインドするエンティティの型を指定します。

[Hisui.Graphics.Scene( typeof( MyLine ) )]
class MyLineScene : Hisui.Graphics.IScene
{
  public void Draw( Hisui.Graphics.ISceneContext sc )
  {
  }
}
このように、typeof(MyLine) を引数に渡して Scene 属性をつけます。こうすることで MyLineScene クラスが MyLine エンティティに対応するシーンとしてバインドされます。

コンストラクタ引数で MyLine を受け取る

シーンをエンティティにバインドするには、Scene 属性だけでなく、以下のようにコンストラクタ引数でエンティティを受け取るようにする必要があります。

[Hisui.Graphics.Scene( typeof( MyLine ) )]
class MyLineScene : Hisui.Graphics.IScene
{
  readonly MyLine _line;

  public MyLineScene( MyLine line )
  {
    _line = line;
  }
  
  public void Draw( Hisui.Graphics.ISceneContext sc )
  {
  }
}

Draw メソッドの実装

あとは Draw メソッドの実装だけです。

using Hisui.OpenGL;
...
  public void Draw( Hisui.Graphics.ISceneContext sc )
  {
    GL.glBegin( GL.GL_LINES );
    GL.glVertex3d( _line.P1.x, _line.P1.y, _line.P1.z );
    GL.glVertex3d( _line.P2.x, _line.P2.y, _line.P2.z );
    GL.glEnd();
  }

これでコマンドを実行してみましょう。直線が描画されるはずです。

画面フィットに対応

フィットボタンを押してみよう

このエンティティクラスには一つ問題があります。

ツールバーにこんなアイコン があります。これはビューの拡大率などをオブジェクトにフィットさせるコマンドです。試しに、ビューを拡大・縮小してからこのボタンをクリックしてみてください。描画されている直線にビューがフィットするはずなのですが、残念ながら直線が画面からはみ出してしまいます。

フィットさせるためには、描画しているエンティティの「大きさ」をフレームワーク側に伝える必要があります。正確にはエンティティの境界ボックスや境界球が必要です。エンティティの大きさが分からないと、画面フィットをしようにも情報が足らないのです。

フィットを正しく動作させるには、エンティティに IBoundary3d インターフェイスを実装する必要があります。

エンティティに IBoundary3d を実装

次のようにエンティティに IBoundary3d インターフェイスを実装します。

class MyLine : Hisui.Geom.IBoundary3d
{
  ...

  public Hisui.Geom.Box3d BoundingBox
  {
    get { return new Hisui.Geom.Box3d(P1, P2); }
  }

  public Hisui.Geom.Sphere3d BoundingSphere
  {
    get
    {
      return new Hisui.Geom.Sphere3d(
          Hisui.Geom.Point3d.Interpolate(P1, P2),
          0.5 * (P2 - P1).Length
      );
    }
  }
}

これでもう一度フィットボタンを押してみましょう。きちんとフィットされましたか?

直線エンティティのシリアライズ

概要

ヒスイは、ドキュメントを XML 形式で保存する機能を備えています。このファイル形式を HiX (Hisui XML) と呼びます。HiX ではシリアライズ機能を各エンティティごとにプラグインできるようになっています。

Hisui.Hix.dll には、Hisui.Hix.ISerializer というインターフェイスが用意されています。このインターフェイスを実装したシリアライザを作成することで、MyLine エンティティをシリアライズすることが可能です。

ポイントは次の2つです。

ISerializerインターフェイス

ISerializer インターフェイスは次のように定義されています。

namespace Hisui.Hix
{
  public interface ISerializer
  {
    object Target { get; set; }
    IEnumerable<object> References { get; }
    void Write( XmlWriter writer, INameResolver names );
    void Read( XmlReader reader, IReferenceResolver refs );
  }
}

このインターフェイスを実装して、次のように MyLineSerializer クラスを作成します。ここでは各メソッドの実装は空になっています。

class MyLineSerializer : Hisui.Hix.ISerializer
{
  public object Target
  {
    get { ... }
    set { ... }
  }

  public IEnumerable<object> References
  {
    get { yield break; }
  }

  public void Write( XmlWriter writer, Hisui.Hix.INameResolver names )
  {
  }

  public void Read( XmlReader reader, Hisui.Hix.IReferenceResolver refs )
  {
  }
}

ここに Serializer 属性を付与します。引数に MyLine 型を指定することで、シリアライザとエンティティがバインドされます。

[Hisui.Hix.Serializer( typeof( MyLine ) )]
class MyLineSerializer : Hisui.Hix.ISerializer
{
  ...
}

Target プロパティの実装

Target プロパティはシリアライズの対象となっているエンティティを get/set するプロパティです。 次のように MyLine エンティティを get/set します。

[Hisui.Hix.Serializer( typeof( MyLine ) )]
class MyLineSerializer : Hisui.Hix.ISerializer
{
  MyLine _line;

  public object Target
  {
    get { return _line; }
    set { _line = (MyLine)value; }
  }
  ...
}

Rerefences プロパティの実装

ここでは References プロパティは使用しません。従って、実装は yield break するだけでOKです。

  public IEnumerable<object> References
  {
    get { yield break; }
  }

このプロパティは、ターゲットエンティティが他のエンティティを参照している場合に、その参照先のエンティティを返すものです。MyLine エンティティは他のエンティティを参照していないので、yield break でOKなのです。

Write メソッドの実装 - XML書き出し

XMLにシリアライズするコードを Write() に記述します。

  using Hisui;
  ...
  public void Write( XmlWriter writer, Hisui.Hix.INameResolver names )
  {
    writer.WriteHixData( "p1", _line.P1 );
    writer.WriteHixData( "p2", _line.P2 );
  }

このコードで使用している WriteHixData() という関数は、XmlWriter に対する拡張メソッドです。この関数は HixUT というクラスに下記のように定義されています。

namespace Hisui
{
  public static class HixUT
  {
    public static void WriteHixData( this XmlWriter writer, string localName, T data ) { ... }
  }
}

この HixUT クラスは Hisui ネームスペースに定義されています。したがって、この拡張メソッドを有効にするには using Hisui が必要です。HixUT には他にもシリアライズに必要なユーティリティが幾つか定義されています。

ここでは引数の names は使用しません。この引数が必要になるのは、ターゲットエンティティが他のエンティティを参照している場合のみです。

上記のコードでは次のような出力結果が得られます。

  <object name="1" type="Tutorial.MyLine">
    <p1>0 0 0</p1>
    <p2>1 2 3</p2>
  </object>

Read メソッドの実装 - XML読み込み

次に読み込み側 Read() を作ります。

  using Hisui;
  ...
  public void Read(XmlReader reader, Hisui.Hix.IReferenceResolver refs)
  {
    var p1 = reader.ReadHixData<Hisui.Geom.Point3d>( "p1" );
    var p2 = reader.ReadHixData<Hisui.Geom.Point3d>( "p2" );
    _line = new MyLine(p1, p2);
  }

引数の refs は使用しません。この引数が必要になるのは、ターゲットエンティティが他のエンティティを参照している場合のみです。

確認してみよう

では PutMyLine コマンドを起動して MyLine エンティティをドキュメントに登録し、その状態で HiX ファイルを出力してみましょう。また、そのファイルが正しく読み込めるか確認してみましょう。

AbstractSerializer の利用

上記では ISerializer インターフェイスを全て自分で実装しましたが、Hisui.Hix には AbstractSerializer<T> という抽象クラスが用意されており、これを利用するとより簡潔にシリアライザを実装することが出来ます。以下に AbstractSerializer<T> の定義を示します。

namespace Hisui.Hix
{
  public abstract class AbstractSerializer<T> : ISerializer
  {
    T _target;

    protected T Target
    {
      get { return _target; }
      set { _target = value; }
    }

    #region ISerializer メンバ

    object ISerializer.Target
    {
      get { return _target; }
      set { _target = (T)value; }
    }

    public virtual IEnumerable<object> References
    {
      get { yield break; }
    }

    public abstract void Write( System.Xml.XmlWriter writer, INameResolver names );
    public abstract void Read( System.Xml.XmlReader reader, IReferenceResolver refs );

    #endregion
  }
}

これを継承すれば MyLineSerializer は下記のようにより簡潔に実装することが出来ます。

using Hisui;
...
[Hisui.Hix.Serializer( typeof( MyLine ) )]
class MyLineSerializer : Hisui.Hix.AbstractSerializer<MyLine>
{
  public override void Write( XmlWriter writer, Hisui.Hix.INameResolver names )
  {
    writer.WriteHixData( "p1", base.Target.P1 );
    writer.WriteHixData( "p2", base.Target.P2 );
  }

  public override void Read( XmlReader reader, Hisui.Hix.IReferenceResolver refs )
  {
    var p1 = reader.ReadHixData<Hisui.Geom.Point3d>( "p1" );
    var p2 = reader.ReadHixData<Hisui.Geom.Point3d>( "p2" );
    base.Target = new MyLine( p1, p2 );
  }
}

戻る

Copyright © 2007-2008, 株式会社カタッチ
http://www.quatouch.com