Hyperscene
TOC »
Hyperscene is a scene library – made for placing objects in a shared world for the purpose of rendering – for CHICKEN Scheme. Hyperscene features a scene graph, cameras with a variety of movement types, frustum culling based on an configurable spatial partitioning system, and a lighting extension. Hyperscene is target agnostic: it is not bound to any particular rendering target and should work equally well for curses, OpenGL, and anything in between.
Hyperscene is a set of bindings to the Hyperscene C library. It’s fairly rough around the edges for general use in Scheme. It should generally be wrapped in order to make it more palatable. Hypergiant is one such library that wraps Hyperscene for use with OpenGL.
Some rendering options are defined at compile time. See render-camera for details. Important note, in version 0.4.0 (for CHICKEN 5), these options aren't available anymore and will be added back when CHICKEN 5.1 is out
Requirements
- miscmacros
Documentation
Hyperscene’s scenes rely on a number of elements to be in place before a scene can be rendered. First is the scene itself. A scene could be thought of as the world or coordinate system that serves as the base for all the rendering operations. Second is a node. A node is the “physical” thing that can being rendered. Nodes can be added to scenes, or can be added to each other if you want a node to be defined in terms of its relation to another (hence the scene “graph”). Third is a camera. Cameras have a position and orientation in a scene, as well as a projection. Cameras can be rendered, which renders the part of the scene that they are pointing at. The fourth element that must be present is a pipeline. Pipelines are the collection of functions that explain how to render a node. If a node is to be rendered, it must have a pipeline associated with it.
These four elements are all represented as c-pointers. Passing the wrong pointer to the wrong function will result in bad things happening. Sorry.
The basic use of Hyperscene is as follows: First you create pipelines and a scene. Then you add nodes to that scene (or to nodes that are already in the scene). These nodes are assigned a pipeline that knows how it can draw them. Then you create a camera associated with that scene, that has a particular position, orientation, and projection. Cameras are then called upon to render the scene.
Scenes
Scenes can be either active or inactive. The only difference is that active scenes are updated with a call to update-scenes.
- make-sceneprocedure
Create a new scene. The scene’s space is partitioned with the partition interface given by partition-interface. New scenes are automatically activated.
- delete-scene SCENEprocedure
Delete the given scene.
- activate-scene SCENEprocedure
Activate the given scene. If the scene is already active, this has no effect.
- deactivate-scene SCENEprocedure
Deactivate the given scene.
- update-scenesprocedure
Update all active scenes. This must be called every frame in order to make sure all nodes are positioned correctly.
Nodes
Nodes are the elements that are rendered in Hyperscene. They have five primary properties:
- Their parent: which can either be a scene, or another node
- Their position and orientation: how they are positioned relative to their parent and the world
- Their data: user-specified data
- Their pipeline: the set of functions that accepts the data, and renders the node
- Their bounding sphere radius: the radius that defines a sphere that encloses the entire visible space of the node
The following functions are used create, delete, and work with nodes:
- add-node PARENT DATA PIPELINE DELETEprocedure
Create a new node with the given parent. PARENT can either be a scene or another node. DATA is a user supplied pointer to some data which is then passed to the PIPELINE functions as well as to DELETE when the node is deleted.
- delete-node NODEprocedure
- unsafe-delete-node NODEprocedure
Delete the given node, removing it from the scene and calling DELETE on its DATA. unsafe-delete-node should only be called if the DELETE function is a pure C function that not call back into Scheme.
- node-scene NODEprocedure
Return the scene that the node belongs to.
- set-node-bounding-sphere! NODE RADIUSprocedure
Set the radius of the node’s bounding sphere. This is important to set so that Hyperscene knows when the node is inside a camera’s bounding volume or not. When a node is created, the bounding sphere radius is initially set to 1.
- node-bounding-sphere NODEprocedure
Return the #f32(x y z radius) bounding sphere of the node. The bounding sphere is positioned in world coordinates. Modifying the returned f32vector will have no effect.
- set-node-position! NODE POINTprocedure
Set the position of the node, relative to its parent, to be the #f32(x y z) POINT.
- move-node! NODE VECTORprocedure
Move the node by the #f32(x y z) VECTOR.
- node-position NODEprocedure
Return the #f32(x y z) position of the node relative to its parent. Modifying this value will not change the node’s position.
- node-rotation NODEprocedure
Return a pointer to the node’s quaternion (x y z w) that describes the rotation of the node relative to its parent. Modifying this quaternion (e.g. with gl-math’s imperative quaternion functions) will rotate the node. Make sure to call node-needs-update! after modifying the returned quaternion.
- node-needs-update! NODEprocedure
Nodes need to be informed when they have been modified in such a way that they need to be updated. Most node modification functions (set-node-position!, move-node!, set-node-bounding-sphere!) call this automatically, but Hyperscene cannot tell when a node’s rotation quaternion has been modified. Make sure to call node-needs-update! after modifying node-rotation’s return value.
- node-transform NODEprocedure
Return a pointer to the 4x4 transform matrix that describes the position and orientation of the node in world space. Consecutive elements of the matrix represent columns. Any modifications to the transform matrix will be lost when the scene is updated.
- node-data NODEprocedure
Return a pointer to the node’s user supplied data.
Memory management
- set-node-pool-size! SIZEprocedure
Hyperscene uses memory pools to store its data relating to nodes, which makes creation and deletion of nodes and scenes quick. For best performance, set the node pool size to be as large as the greatest number of nodes that will be needed for a scene. When a scene is created with make-scene its node pool is set to this size. Defaults to 4096.
Pipelines
Pipelines are structures consisting of three functions: a pre-render function, a render function, and a post-render function. When a scene (camera) is rendered, the visible nodes are sorted by their pipelines before they are drawn. Then, for every group of pipelines, the pre-render function is called with the first node as an argument. Every node is then passed to the render function. Finally, the post-render function is called to clean up. The sorting is done – and the pre/post-render functions are only called once – in order to minimize the amount of state changes that need to occur during rendering.
The exception to this is when a pipeline represents an element that could be partially transparent. “Alpha” pipelines get drawn after all the other ones, and the nodes that are associate with alpha pipelines are always drawn in order of decreasing distance from the camera. This ensures that the transparent parts can be rendered correctly.
When targeting OpenGL, one pipeline per shader program is generally desirable. The pre-render function should therefore call gl:use-program, while the render function should not. The post-render function can reset any state (e.g. (gl:use-program 0), etc).
- add-pipeline PRE-RENDER RENDER POST-RENDER #!optional ALPHAprocedure
Create a new pipeline with the given callbacks (or pointers to pure C functions). ALPHA? indicates whether or not the pipeline can render any transparent elements (defaults to #f).
- delete-pipeline PIPELINEprocedure
Delete the given pipeline.
Cameras
Cameras, aside from having an orientation and position within a given scene, have two main properties. Their type is the sort of projection that the camera uses: either orthographic or perspective. The style of the camera indicates the way in which the camera can be moved (see Movement and rotation for details of each function):
- A position camera is one where the position and rotation of the camera is explicitly set. Movement functions: move-camera!, set-camera-position!, camera-rotation.
- A look-at camera is given a position, an up-vector, and a point that it is looking at. The rotation of the camera is determined from these vectors. Movement functions: move-camera!, set-camera-position!, set-camera-up!, camera-look-at!.
- An orbit camera is given a point to look at, a distance from that object, and a yaw (rotation around the vertical axis), pitch (rotation around the side-to-side axis), and roll (rotation around the front-to-back axis). Movement functions: camera-look-at!, yaw-camera!, set-camera-yaw!, pitch-camera!, set-camera-pitch!, roll-camera!, set-camera-roll!, zoom-camera!, set-camera-zoom!.
- A first-person camera has a position, yaw, pitch, and roll, and has special functions to move it forward and back, left and right, and up and down. Movement functions: move-camera!, move-camera-forward!, set-camera-position!, yaw-camera!, set-camera-yaw!, pitch-camera!, set-camera-pitch!, roll-camera!, set-camera-roll!, move-camera-forward!, move-camera-up!, strafe-camera!.
The following functions are used create, delete, and work with cameras:
- (make-camera TYPE STYLE SCENE [near: NEAR] [far: FAR] [angle: ANGLE] [width: WIDTH] [height: HEIGHT] [viewport-width-ratio: VIEWPORT-WIDTH-RATIO] [viewport-height-ratio: VIEWPORT-HEIGHT-RATIO] [static-viewport?: STATIC-VIEWPORT?])procedure
Create a new camera associated with the given scene. TYPE must be one of #:ortho or #:perspective for an orthographic or a perspective camera, respectively. STYLE must be one of #:position, #:look-at, #:orbit, or #:first-person. New cameras are automatically activated. NEAR is the near plane of the camera, defaulting to 1. FAR is the far plane of the camera, defaulting to 10000. ANGLE is the view-angle, in degrees, for perspective cameras, defaulting to 70. WIDTH and HEIGHT should be initialized to the size of camera’s viewport. VIEWPORT-WIDTH-RATIO and VIEWPORT-HEIGHT-RATIO scale the camera’s viewport (its view frustum’s near plane) in the width and height direction. The effects of the scaling persist after resize-cameras is called. If STATIC-VIEWPORT? is #t, the camera’s viewport dimensions will be fixed such that they won’t be changed by resize-cameras, although VIEWPORT-WIDTH-RATIO and VIEWPORT-HEIGHT-RATIO still effect the final viewport size.
- delete-camera CAMERAprocedure
Delete the given camera.
- render-camera CAMERAprocedure
Render the given camera. When cameras are rendered, all of the visible nodes are sorted: first into groups of nodes that have an alpha pipline or that don’t.
Alpha nodes are sorted by decreasing distance from the camera and rendered last. There are two sorting schemes that may be employed. The first, and default, scheme is useful when working with one-dimensional alpha objects. It sorts the distance of nodes based only on their origin, not taking into account their bounding sphere. The second scheme, enabled by defining #:volumetric-alpha, is useful when working with three-dimensional alpha objects, and sorts distance while taking the bounding sphere into account.
Non-alpha nodes are sorted by pipeline. Each pipeline is then sorted again by increasing distance from the camera before they are rendered. By doing so, the things that are closest to the camera are drawn first (“reverse painter” sorting) which can help graphics hardware determine when later bits of the scene are hidden, thus saving some rendering time. Not all applications will benefit from this extra step, though, and it can be disabled by defining #:no-reverse-painter at compilation time.
- update-camera CAMERAprocedure
Update the given camera. This updates the view matrix of the camera to reflect any changes that may have occurred. This should always be done before rendering.
- activate-camera CAMERAprocedure
Add the camera to the list of active cameras (or push it to the back of the list, thus setting it to be rendered last). New cameras are automatically activated.
- deactivate-camera CAMERAprocedure
Remove the camera from the list of active cameras.
- render-camerasprocedure
Render all the active cameras.
- update-camerasprocedure
Update all the active cameras.
- resize-cameras WIDTH HEIGHTprocedure
Modify the projection matrix of all cameras, based on the viewport dimensions WIDTH and HEIGHT. Should be called whenever the window is resized. The viewport dimensions of a camera are scaled by any values passed to set-camera-viewport-ratio! or the viewport-*-ratio keywords of make-camera. If static-viewport? was set to #t when a camera was created, this function has no effect on it.
- set-camera-clip-planes! CAMERA NEAR FARprocedure
Set the near and far clip planes of the camera. Nodes closer to or further away from these plans will not be visible.
- set-camera-view-angle! CAMERA ANGLEprocedure
Set the viewing angle of the perspective camera to angle degrees. This doesn’t have any effect on orthographic cameras.
- set-camera-viewport-ratio! CAMERA WIDTH HEIGHTprocedure
Scale CAMERA’s viewport (its view frustum’s near plane) in the width and height direction by WIDTH and HEIGHT. The effects of the scaling persist after resize-cameras is called. This is equivalent to setting the viewport-*-ratio keywords of make-camera.
- set-camera-viewport-dimensions! CAMERA WIDTH HEIGHTprocedure
Set CAMERA’s viewport (its view frustum’s near plane) dimensions to WIDTH and HEIGHT, and fixes these dimensions such that they will not be changed when resize-cameras is called.
- set-camera-viewport-screen-position! CAMERA LEFT RIGHT BOTTOM TOPprocedure
Set the area of the screen that CAMERA renders to, defined by the rectangle of LEFT, RIGHT, BOTTOM, TOP. These default to the full screen, which is represented by values of -1, 1, -1, 1, respectively.
- set-camera-viewport-offset! CAMERA X Yprocedure
Move CAMERA’s viewport (its view frustum’s near plane) by (X, Y) expressed as a fraction of the viewport’s width and height. This is generally only useful for a perspective projection, when lines should converge not to the middle of the screen, but to another point. Setting x to 0.5, for example, moves the focal centre to the right edge of the viewport.
Movement and rotation
- move-camera! CAMERA VECTORprocedure
Move the position of the camera by the vector #f32(x y z). Cannot be called with an orbit camera.
- set-camera-position! CAMERA POINTprocedure
Set the position of the camera to the #f32(x y z) point . Cannot be called with an orbit camera.
- camera-position CAMERAprocedure
Return the #f32(x y z) position of the camera. Modifying this vector will not affect the camera.
- camera-rotation CAMERAprocedure
Return a pointer to the node’s quaternion (x y z w) that describes the rotation of the camera. Modifying this quaternion (e.g. with gl-math’s imperative quaternion functions) will rotate position cameras. The returned quaternion must not be modified for any other camera styles.
- camera-look-at! CAMERA POINTprocedure
Set the #f32(x y z) point the look-at or orbit cameras are looking at.
- set-camera-up! CAMERA UPprocedure
Set the camera’s #f32(x y z) up-vector. Cannot be called with a non-look-at camera.
- yaw-camera! CAMERA ANGLEprocedure
Add ANGLE radians to the orbit or first-person camera’s yaw.
- set-camera-yaw! CAMERA ANGLEprocedure
Set the yaw of the orbit or first-person camera to ANGLE radians.
- pitch-camera! CAMERA ANGLEprocedure
Add ANGLE radians to the orbit or first-person camera’s pitch.
- set-camera-pitch! CAMERA ANGLEprocedure
Set the pitch of the orbit or first-person camera to ANGLE radians.
- roll-camera! CAMERA ANGLEprocedure
Add ANGLE radians to the orbit or first-person camera’s roll.
- set-camera-roll! CAMERA ANGLEprocedure
Set the roll of the orbit or first-person camera to ANGLE radians.
- zoom-camera CAMERA DISTANCEprocedure
Add DISTANCE to the orbit camera’s zoom.
- set-camera-zoom CAMERA DISTANCEprocedure
Set the zoom of the orbit camera to DISTANCE.
- move-camera-forward! CAMERA DISTANCEprocedure
Move the first-person camera forward by DISTANCE. Only the camera’s yaw is taken into account for the movement.
- move-camera-up! CAMERA DISTANCEprocedure
Move the first-person camera up by DISTANCE.
- strafe-camera! CAMERA DISTANCEprocedure
Move the first-person camera to the right by DISTANCE. Only the camera’s yaw is taken into account for the movement.
Camera matrix and position access
- camera-projection CAMERAprocedure
Returns a pointer to the projection matrix of the camera.
- camera-view CAMERAprocedure
Returns a pointer to the view matrix of the camera.
- camera-view-projection CAMERAprocedure
Returns a pointer to the projection * view matrix of the camera.
Currently rendering camera
While rendering, it can be desirable to have pointers to various matrices relating to the camera and node being rendered (e.g. to be used as uniform values). These pointers always point to the relevant value of the camera currently being rendered.
- current-camera-positionprocedure
Returns a pointer to the (x y z) position of the camera currently being rendered.
- current-camera-viewprocedure
Returns a pointer to the view matrix of the camera currently being rendered.
- current-camera-projectionprocedure
Returns a pointer to the projection matrix of the camera currently being rendered.
- current-camera-view-projectionprocedure
Returns a pointer to the projection * view matrix of the camera currently being rendered.
- current-camera-model-view-projectionprocedure
Returns a pointer to the projection * view * model matrix of the node currently being rendered.
- current-inverse-transpose-modelprocedure
Returns a pointer to the inverse transpose model matrix of the node currently being rendered. This matrix is useful for lighting. If it is not wanted, the calculation of this value can be omitted by defining the feature #:no-inverse-transpose at compile time.
Spatial Partitioning
Hyperscene only renders nodes that are within the bounds of a camera (i.e. it performs view frustum culling). In order for it to efficiently sort through the nodes, a spatial partitioning system is used. Different spatial partitioning systems can be used on a per-scene basis.
If you wish to write a new partition interface, see the Hyperscene documentation.
- partition-interfaceprocedure
- set-partition-interface! INTERFACEprocedure
Returns/sets the partition interface that scenes are initialized with. Defaults to aabb-tree-interface.
- aabb-tree-interfaceprocedure
aabb-tree-interface is a hybrid AABB ternary tree/nonatree/isoceptree inspired heavily by Dynamic Spatial Partitioning for Real-Time Visibility Determination. When trees split, they try to split only along those axis where the nodes are most well dispersed. For example, if you have a 2D game, chances are nodes will be arranged along the X and Y axes, with little separation on the Z axis. In this situation, when enough nodes are added to a given AABB tree, it will only split along those two axes. In doing so, it avoids extraneous tree creation. This can be taken advantage of most in 2D situations by not using too much (Z) distance between layers.
- set-aabb-tree-pool-size! SIZEprocedure
Set the memory pool size of the aabb-tree-interface. This pool sets the number of trees in the partition interface’s pool, which is initialized for each scene when make-scene is called. Defaults to 4096.
Extensions
Hyperscene features an extension system, so that the rendering of a scene can be augmented in new and exciting ways.
Extensions can add special nodes to scenes. If a node is created that is given a pointer to an extension in place of a pipeline, that node will not be rendered but will instead be handled by its extension during rendering and updating.
- activate-extension SCENE EXTENSIONprocedure
Before an extension can be used in a given scene, it must be activated.
Lights
Hyperscene supplies an extension that provides a generic lighting system:
- lightingprocedure
Before lights can be used in a scene, lighting must be activated with activate-extension.
- max-lightsprocedure
Return the maximum number of lights that may be visible at once. Defaults to 8.
- set-max-lights! Nprocedure
Set the maximum lights that may be visible at once. Must not be called after the first scene with lighting is initialized.
- set-ambient-light! SCENE COLORprocedure
Scenes that use the lighting extension have an #f32(r g b) ambient light associated with them, set by this function.
- (add-light PARENT COLOR INTENSITY [direction: direction] [spot-angle: SPOT-ANGLE])procedure
Adds a new light to the given PARENT node (or scene) with #f32(r g b) COLOR. INTENSITY is the floating point value associated with the brightness of the light. DIRECTION is an #f32(x y z) vector that indicates the direction that the light is pointing, defaulting to #f32(0 0 0). SPOT-ANGLE indicates the angle in radians that the light is spread over (defaulting to 0, representing a non-spotlight source). A node is returned that can be moved, rotated, and sized like any other node.
- set-light-color! LIGHT COLORprocedure
Sets the #f32(r g b) color of the light.
- light-color LIGHTprocedure
Returns the #f32(r g b) color of the light.
- set-light-intensity! LIGHT INTENSITYprocedure
Sets the intensity of the light.
- light-intensity LIGHTprocedure
Returns the intensity of the light.
- set-light-direction! LIGHT DIRECTIONprocedure
Sets the #f32(x y z) direction of the light.
- light-direction LIGHTprocedure
Returns the #f32(x y z) direction of the light.
- set-light-spot-angle! LIGHT ANGLEprocedure
Sets the angle, in radians, over which the light is spread.
- light-spot-angle LIGHTprocedure
Returns the angle over which the light is spread.
- n-current-lightsprocedure
Returns a pointer to the number of visible lights in the scene currently being rendered.
- current-ambient-lightprocedure
Returns a pointer to the (r g b) color of ambient light in the scene currently being rendered.
- current-light-positionsprocedure
Returns a pointer to the array of packed (x y z) positions of the visible lights in the scene currently being rendered.
- current-light-colorsprocedure
Returns a pointer to the array of packed (r g b) colors of the visible lights in the scene currently being rendered.
- current-light-intensitiesprocedure
Returns a pointer to the array of intensities of the visible lights in the scene currently being rendered.
- current-light-directionsprocedure
Returns a pointer to the array of packed (x y z spotAngle) directions and angles of the visible lights in the scene currently being rendered.
- make-material R G B SHININESSprocedure
Make a simple material definition consisting of an RGB color (specular color, perhaps) and shininess value. Returns a four element f32vector, located in non-garbage-collected memory. Intended for use as a uniform value.
- set-material-specular-color! MATERIAL R G Bprocedure
Set the RGB values of the given material.
- set-material-shininess! MATERIAL SHININESSprocedure
Set the shininess value of the given material.
- set-light-pool-size! SIZEprocedure
Every scene is given a pool from which to allocate lights, the size of which can be modified by calling this function before initialization (defaults to 1024).
Writing your own extensions
The Hyperscene C library is extensible in C. See its documentation for details.
Examples
See the examples directory to see Hyperscene in action.
Version history
Version 0.4.0
19 March 2019
- Maintenance given to Kooda
- Port to CHICKEN 5
Version 0.3.0
20 January 2014
- Add control over camera viewports
- Improved alpha sorting
Version 0.2.0
20 December 2014
- Separate camera updating from rendering
- Remove unsafe-render-camera* functions
- current-ambient-light now takes no argument
- add-light takes keyword args, not optional
Version 0.1.0
- Initial release
Source repository
Source available here.
Bug reports and patches welcome! Bugs can be reported to kooda@upyum.com
Authors
Alex Charlton
Adrien (Kooda) Ramos
License
BSD