The aim of the Haskell Scene Graph project is to provide a solid basis for the development of 3D applications. Other work which has 3D content has focused on an application or a research topic. Whilst these projects have a scene graph and/or 3D component, it is not easy to extract a useful library. The project's aim is to provide such a library.
Current Status: Initial 0.1 version is available on Hackage
The library draws inspiration from the following Haskell projects:
and from the following non-Haskell work:
A scene is constructed using a set of combinators. The following creates a green cube
cube 0.5 `colour` Green
To scale the cube we use the 'scale' function. This takes a vector indicating by how much to scale in X,Y and Z directions:
cube 0.5 `colour` Green `scale` v1x 40
v1x is a function that given a number n, returns the vector (n,1,1). This contributes to a concise specification of the scene.So here the cube is scale by 40 in the X direction.
To translate the scene we use a similar function to scale:
cube 0.5 `colour` Green `scale` v1x 40 `translate` vy 2
Finally our scene requires a camera and light to be specified:
cube 0.5 `colour` Green `scale` v1x 40 `translate` vy 2 <+> camera `translate` vy (-10) <+> light `translate` vz 10
In order to run the scene we use runScene. As the above scene has been constructed in a monad, we need to run the monad, to extract the actual Scene data structure.
runScene $ osgt $ cube 0.5 `colour` Green `scale` v1x 40 `translate` vy 2 <+> camera `translate` vy (-10) <+> light `translate` vz 10
To navigate the scene use the following:
- W - Move forward
- S - Move back
- A - Move left
- D - Move right
- Z - Move down
- X - Move up
- ESC - Exit
The module Graphics.SceneGraph.Reactive uses Reactive to enliven a scene. ExampleReactive.hs provides an example of this.This uses a calculator defined independantly of the GUI and is made tangible by SceneGraph.
calc = runSceneReactive calculator sceneButtonReactive
calculator is the Scene representing the calculator. The button and display objects are labelled to allow event hanlders to be applied during a second pass.
sceneButtonReactive is where the second pass is performed and this wires up the calculator.
The Scene Graph is built using FGL. So it remains to define a scene node to get things started:
data SceneData = Group | Geode Geometry | LOD | MatrixTransform (MatrixD) | Switch Int | Material Phong | Handler (Maybe (ClickHandler, Sink ())) (Maybe (DragHandler, Sink GLdouble)) | Light | Camera | Texture String | Text String
Group - Supports grouping of nodes. Since the Graph will provide the links to children we don't need to have anything more.
Geode - Is where geometries shapes are specified. In theory these nodes should not have children. Geometries can be basic shapes (provided in GLUT.Objects) or meshes of some form.
LOD - Level of detail. Children of this would be representations of the same sub-scene at different level of detail. Choice of which child which depend on current viewing scale. (Currently not implemented).
MatrixTransform - Transform node.
Switch - Provides the ability to choose the sub-tree based on the integer value. One application of this is to provide a representation to be drawn when the object is selected. Some implementations suggest using a bit map across all the children.
Material - Sets the material for the tree starting at this node. Currently just Phong is shown.
Handler - Some form of event handler can be specified at this node. Two types are available - ClickHandler to handle mouse clicks on the object, and DragHandler to handle drag events on the object.
Light - Specifies a light at the origin.
Camera - Specifies a camera at the origin.
Texture- Named texture is applied to the sub-tree.
Text - Text object.
Note that the scene is defined as a graph but when the monad is run a tree is created.
Scene Graph Monad
Wrapping in Monad enables us to define a simple DSL for building Scene Graphs:
type OSGT m = (StateT OSGState m) -- Append the two graphs parented at the supplied nodes (<+>) :: Monad m => OSGT m SceneNode -> OSGT m SceneNode -> OSGT m SceneNode -- Translate a node (and child graph) -- Rotate, scale and colour can be defined in similar ways translate :: Monad m => OSGT m SceneNode -> Vector3 Float -> OSGT m SceneNode -- Example ferris :: Float -> OSG SceneNode ferris theta = do let ring = torus 0.5 `colour` White --Blue axial1 = cube 0.5 `scale` v1x 40 `colour` Green axial2 = cube 0.5 `scale` v1z 40 `colour` Green axial = axial1 <+> axial2 root <- (ring `translate` vy 2 <+> ring `translate` vy (-2) <+> axial `translate` vy (-2) <+> axial `translate` vy 2 <+> crossbeams) `rotate` (theta , (vector3 0 1 0 )) return root
The Monad can be run to extract the actual scene graph and then sent off for viewing.
Currently, system is very fickle about light and camera. These need to be specified and need to be translated immediately.
Build an extended example that uses the library. A puzzle game of some sort.
Texture loading requires program to be run in root directory of package.