Haskell 中的 SceneGraph 遍历

发布于 2024-09-11 04:00:00 字数 1126 浏览 10 评论 0原文

我想使用 Data.TreeTransformShape 节点组成。在 SceneGraph 中,空间变换在遍历时累积并应用于形状以进行渲染。

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)是否有一个更短的版本来定义这棵树?除了第一个 Node 定义和所有空 subForest 之外的所有内容实际上都是多余的。

谢谢你!

I want to implement a simple SceneGraph in Haskell using Data.Tree consisting of Transform and Shape nodes. In a SceneGraph the spatial transformation is accumulated while traversing and applied to the shape for rendering.

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

Say we have a scene with an object which is moved to the right and consisting of a Square at bottom and a Circle on top

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

I came up with this tree definition:

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

The rendering would be something like this:

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

My questions are:

1) How do you define a traverse function, which accumulates the transformation and calls the render tasks?

2) How do you avoid making the traverse IO?

3) Is there a shorter version to define this tree? All but the first Node definition and all empty subForests are actually superfluous.

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

酒与心事 2024-09-18 04:00:00

矛盾的是,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,但它们更“统一”。简而言之,在树上使用递归是完全没问题的。

关于第 2 点,一旦您决定应该在 IO monad 中渲染圆形和正方形,您就无能为力了。

Paradoxically, Data.Tree is not used very often in Haskell because defining a custom tree type is so easy. In your case, I would implement the scene graph (tree) as follows:

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

Your example becomes

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

This answers point 3.

To traverse the tree, use recursion:

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

There are more generic traversals available, for instance in Data.Traversable, but they are more "uniform". In short, using recursion on trees is perfectly fine.

Concering point 2, there is nothing much you can do once you decide that circles and squares should be rendered in the IO monad.

流云如水 2024-09-18 04:00:00

我喜欢将树表示为带有底层列表单子的单子列表。如果这句话令人困惑,只需看看代码:

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 中的树不同之处在于:

  • 您可以使用 monad 和列表的现有函数(例如 scanl)。

  • 它们可以是一元的

  • 叶节点和具有 0 个子节点的节点是不同的东西。这在某些情况下可能有意义(例如区分空目录和文件)
    • cons x mempty 是叶子,cons x (joinL []) 是具有 0 个子节点的节点。

I like to represent trees as monadic lists with an underlying list monad. if this sentence is confusing, just look at the code:

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))
]

These trees differ from those in Data.Tree in that:

  • You can use the existing functions for monads and lists (like scanl) for these.

  • They can be monadic

  • Leafs and nodes with 0 children are different things. This might make sense in some situations (for example to differentiate an empty directory from a file)
    • cons x mempty is a leaf, and cons x (joinL []) is a node with 0 children.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文