2

とノードData.Treeで構成される単純なSceneGraphをHaskellに実装したいと思います。シーングラフでは、空間変換はトラバース中に蓄積され、レンダリングのためにシェイプに適用されます。TransformShape

type Transform = Vector2 Double
data Shape     = Circle Double | Square Double
data SceneNode = XFormNode Transform | ShapeNode Shape

右に移動し、下部に正方形、上部に円で構成されるオブジェクトがあるシーンがあるとします。

^
|
|  ()
|  []
0----->

私はこのツリーの定義を思いついた:

let tree = Node (XFormNode (vector2 10 0))
                [Node (ShapeNode (Square 10)) []
                ,Node (XFormNode (vector2 0 10))
                      [Node (ShapeNode (Circle 10)) []]
                ]

レンダリングは次のようになります。

render :: Position2 -> Shape -> IO ()
render p (Circle r) = drawCircle p r
render p (Square a) = drawSquare p a

私の質問は次のとおりです。

1)traverse変換を累積し、レンダリングタスクを呼び出す関数をどのように定義しますか?

2)トラバースIOを作成しないようにするにはどうすればよいですか?

3)このツリーを定義するためのより短いバージョンはありますか?最初のノード定義とすべての空のsubForestを除くすべては、実際には不要です。

ありがとうございました!

4

2 に答える 2

2

逆説的Data.Treeですが、カスタムツリータイプの定義はとても簡単なので、Haskellではあまり使用されません。あなたの場合、シーングラフ(ツリー)を次のように実装します。

type Transform = Vector2 Double
data Shape     = Circle Double | Square Double
data Scene     = Transform Transform [Scene] | Shape Shape

あなたの例は

example :: Scene
example = Transform (vector2 10 0)
                    [ Shape (Square 10)
                    , Transform (vector2 0 10) [Shape (Circle 10)]
                    ]

これはポイント3に答えます。

ツリーをトラバースするには、再帰を使用します。

render :: Position2 -> Scene -> IO ()
render p (Transform v scenes) = mapM_ (render (p+v)) scenes
render p (Shape (Circle r))   = drawCircle p r
render p (Shape (Square a))   = drawSquare p a

たとえば、で利用できるより一般的なトラバーサルがありますがData.Traversable、それらはより「均一」です。つまり、ツリーで再帰を使用することはまったく問題ありません。

IOポイント2に関しては、円と正方形をモナドにレンダリングすることを決定したら、できることはほとんどありません。

于 2010-07-29T08:34:18.457 に答える
2

私は、ツリーを、基になるリストモナドを持つモナドリストとして表すのが好きです。この文が紛らわしい場合は、コードを見てください。

import Control.Applicative (liftA2)
import Control.Monad.ListT (ListT) -- "List" in hackage
import Data.List.Class (cons, joinL, lastL, scanl) -- "List" in hackage
import Data.Monoid (mempty)
import Data.Tensor (Vector2 (..)) -- "Tensor" in hackage
import Prelude hiding (scanl)

type Transform = Vector2 Double
data Shape     = Circle Double | Square Double deriving Show
data SceneNode = XFormNode Transform | ShapeNode Shape deriving Show

tree :: ListT [] SceneNode
tree
    = cons (XFormNode (Vector2 10 0))
    $ joinL
        [ cons (ShapeNode (Square 10)) mempty
        , cons (XFormNode (Vector2 0 10)) $ cons (ShapeNode (Circle 10)) mempty
        ]

traverseStep :: (Transform, SceneNode) -> SceneNode -> (Transform, SceneNode)
traverseStep (ta, _) n@(XFormNode tb) = (liftA2 (+) ta tb, n)
traverseStep (t, _) n = (t, n)

ghci> lastL $ scanl traverseStep (Vector2 0 0, XFormNode (Vector2 0 0)) tree
[ (Vector2 10.0 0.0,  ShapeNode (Square 10.0))
, (Vector2 10.0 10.0, ShapeNode (Circle 10.0))
]

これらの木は、次の点でそれらとは異なりますData.Tree

  • これらのモナドとリスト(のようなscanl)の既存の関数を使用できます。

  • 彼らはモナディックになることができます

  • 子が0のリーフとノードは別のものです。これは、状況によっては意味がある場合があります(たとえば、空のディレクトリとファイルを区別するため)。
    • cons x memptyは葉でありcons x (joinL [])、子が0のノードです。
于 2010-07-25T13:15:59.273 に答える