BAScene is a 3D scene graph library. It is unusual in its support for fully editable and persistent objects. It is written in pure Objective-C for Mac OS X 10.5 and higher. An experimental version exists with support for iOS 4.0 and higher. It has been in sporadic development since 2008.
BAScene relies on BAFoundation, also available on Github.
Two sample projects, Blocks and PerlinTerrain, demonstrate usage of BAScene. They are available as separately. The demos include compiled versions of BAScene (and BAFoundation), so it's possible to try them on their own.
The simplest way to get started with BAScene is to make your application delegate a subclass of the BAScene class. BAScene is, in turn, a subclass of the BACoreDataManager class, found in BAFoundation. BACoreDataManager combines a number of conveniences, and takes care of managing your model, persistent store coordinator, and managed object context. It offers numerous hooks to override so you can customize the behaviour.
It's not necessary to use the persistence features of BAScene. Various classes, including BASceneView, BACamera, BACameraSetup, and the various utility types and functions can be used on their own. Just add them directly to your project. The BAScene class provides some conveniences along with Core Data stack management (inherited from BACoreDataManager), but the latter can be ignored. If you never access the managed object context or other Core Data-related properties, you don't need to worry about them.
The scene graph classes are all persistent, but if you never save the object context, you don't need to worry so much about that, either. You could probably even just use an in-memory store.
The library is not designed for high-performance, although some parts have been optimized. It is primarily meant as a way to access 3D and scene graph features directly from Objective-C. It is ideally suited for use in small or medium-sized projects for creative and toy apps, demonstrations, and simple games. It is also useful for adding small amounts of 3D content to projects where 3D rendering or editing is not the primary purpose.
Enabling support for the OpenGL Core 3.2 profile (for Mac OS X 10.7 and higher), and OpenGL ES 2.0, is in progress.
You can watch a presentation about BAScene on SlideShare. As you will see in the slides, it was at one time going to be named "Scene Kit", until Apple took that name.
BAScene and SceneKit actually have very little overlap in their specific purpose and usefulness. Ensuring they are compatible is a pending task for improving BAScene.
Scene Graph Classes
All of the proper scene graph classes represent Core Data entities. The scene graph entities provide extremely fine-grained structure to your 3D content, right down to the vertex attributes like points, texture coordinates and colours. This is to ensure that everything can be edited, and no information is lost.
Props, Prototypes and Meshes
The "objects" in a 3D scene are each represented by a
Prop. Props are lightweight objects which represent concrete realizations of another entity, the
Prototype. A prototype is a collection of one or more
Mesh objects. A mesh is a collection of vertex data representing a rigid body. A prototype is thus a grouping of one or more rigid bodies, which can be composed to make a more complex object model. A prototype is abstract, while a prop is concrete. (I am trying to be careful not to use Object Oriented terminology. A Prop is not a kind of Prototype. A Prop has a Prototype.)
In order to facilitate re-use of geometry in multiple prototypes, prototypes and peshes are related, indirectly, through the intermediate
PrototypeMesh object. There is one
PrototypeMesh for each use of a
Mesh in a
PrototypeMesh adds additional properties to the mesh that are unique to its use in that prototype: specifically
Resource objects. Note that this means that the same
Mesh object can be used multiple times in one
Prototype - each use will be facilitated by a unique
PrototypeMesh, so there is no conflict.
Polygons, Points and Tuples
Meshes are built out of
Polygon objects. In most cases, that means triangles—polygons with three coplanar vertices—but a polygon can have any number of points (instances of
Point is, in turn, a collection of
Tuple objects representing vertices, normals and texture coordinates.
To optimize performance of mesh drawing, meshes can be "compiled" (using
-[Mesh compile]). This generates an OpenGL Vertex Buffer Object, built from the mesh's points. The VBO is then attached as a geometry
Resource object, and from then on, that gets drawn, instead of drawing the various points using immediate mode.
Prop and every
PrototypeMesh has a
Transform: the current transformation matrix for that object, relative to its parent. For the
PrototypeMesh, the transform is relative to the
Prototype. For the
Prop, the transform is relative to the world origin.
Groups and Partitions
Returning to the
Prop and moving in the other direction, props are collected together in groups, represented by
Group objects. A custom kind of group is the
Partition, designed for building octrees. Groups and partitions are not fully realized, and could use more work. In an earlier version of the library, they had a Transform of their own. I forget why I took it away. Probably I thought that the model diagram was too complicated. Maybe I should bring it back.
The basics of rendering involves a view, a camera, and a collection of props. The
BASceneView class is a subclass of
NSOpenGLView. It provides basic support for mouse and WSAD-based keyboard control, so you can fly around your scene as though you were in a game. Use the middle mouse button to access enable X/Y plane mouse control (parallel to the screen, or left/right and up/down), and the right mouse button to enable X/Z control (parallel to the "ground", or left/right and forward/back).
BASceneView uses CVDisplayLink to manage drawing updates, if enabled with
-enableDisplayLink. Otherwise, it will perform drawing in
-drawRect:. You may manually disable display link drawing with
disableDisplayLink. You can also configure
BASceneView to automatically disable drawing while in a background window using the
-refresh to update the view frustum. You can change properties on
BASceneView to alter some aspects of the frustum, but the class will be updated to provide more control of viewing options, and support for parallel projection. (However, note that a number of OpenGL state settings for view rendering are already found on
BASceneView relies on
BACamera for actually drawing any scene content. The view will create its own camera automatically.
BACamera represents the viewer's point of view in the scene. It has very primitive support for animating the camera, in order to make the mouse and keyboard controls work. It also manages other aspects of OpenGL drawing state, like the background colour, back face culling, rendering mode (fill, line, point), and more. (Which settings are default is in flux, so always set all the ones that matter to you.)
Similar in purpose to the GLTexture class in Apple's GLKit, this provides a wrapper for OpenGL texture objects.
Categories to support drawing
The way you actually get stuff on-screen is to provide the camera with a "draw delegate", an object which conforms to the very simple
BACameraDrawDelegate protocol. The draw delegate should respond to two methods:
-sortedPropsForCamera:, which returns an array of objects to be drawn (optionally sorted relative to the camera's location), and
-paintForCamera:, which is useful for drawing overlay content (it is invoked after the scene content is drawn).
The props returned to the camera do not have to be
BAProp objects—they can be any object which conforms to the
BAVisible protocol, which includes only one mandatory method:
BAProp conforms to this protocol. The camera iterates over the array returned from the delegate in response to
-sortedPropsForCamera:, asking each prop to draw itself, one by one.
Once you have a view and a camera, you can start regular drawing updates by sending
BASceneView. Alternately, just send
-setNeedsDisplay, and the view will be redrawn using the standard Cocoa drawing system. (Note that in the former case, drawing is performed on the display link's dedicated thread. Also, the two update techniques are mutually exclusive.)
BAScene class is a subclass of
BACoreDataManager, a convenience for managing various aspects of Core Data which are basically boilerplate and otherwise clutter up your application delegate class. It also adds support for scheduling regular updates to your model on a dedicated dispatch queue. The simplest approach is to make your application delegate a subclass of
BAScene, but it could also be used without subclasses as an instance variable in your delegate or your document class.
A subclass of
BABitArray, this provides a simple class for storing 1-bit 3D volume data. It is primarily useful for testing and experiments. It is demonstrated in the
PerlinTerrain project. It needs to be re-written as a subclass of
BASampleArray to provide support for more flexible data. (
BABitArray, is part of the BAFoundation framework.
BASampleArray is an sort of Objective-C equivalent to a Vector in C++: a dynamic, variable-sized sequence of data types whose size (but not type) is specified at creation. Since using NSArray for non-objects is clumsy—even more so before the addition of literal syntax to Objective-c, and Objective-C has no support for templates—they aren't really necessary—
BASampleArray is a workable substitute for vector-style handling of primitive and structure data types.)
In order to facilitate simulation and animation, you can use the built in update queue provided by the
BAScene class. (Remember how I suggested using it has your application delegate's superclass?) When your app starts, just send a
-startUpdates: message, including a block that will invoke an update cycle on your model. You can pause and resume updates at will, with
resumeUpdates, or cancel updates altogether with
-cancelUpdates, which will remove the update block and its associated timer. You must cancel the existing update timer before setting a new one (multiple invocations of of
-startUpdates: will have no effect).
User Interface Tools
BACameraSetup class is a view controller which will create a window for changing the properties of any
BACamera object which is assigned to its
The project includes all its own custom types and its own basic library of mathematical routines designed to work with them. Types are declared in
BASceneTypes.h. Routines are found in
BASceneUtilities.h, along with useful macros and functions for random number generation, simple object drawing, and extensions to
NSColor (which should be moved to a new file).
There are custom types for points, sizes, ranges, vectors and matrices. I have attempted to design these types to be as similar as possible to the types defined in NSGeometry.h. I have provide integer and 3D and homogeneous variants. There are some inconsistencies to the naming which still need to be addressed. Also, these types are based on OpenGL types, not Core Foundation types (which vary depending on the target architecture in ways where are not in sync with types supported by OpenGL).
There are also functions for converting types into strings and back. This is marginally useful for archiving values if you don't mind truncation. A proper category on NSValue has yet to be written.
In addition, there are a number of union types which allow for easy conversion between named structure elements and array-based elements, for easier compatibility with OpenGL functions.
Note on the API
The API for the persistent classes follows a pattern that I have since grown disenchanted with.
The current version relies on the existence of an "active" managed object context. You can create new objects, in most cases, using class convenience methods which insert new objects, or find existing ones, in the active context. This is set automatically when you first create the BAScene object. It can also be changed, using class methods on
NSManagedObjectContext, defined in a custom category implemented as part of BAFoundation.
For example, to make a new mesh, you might invoke
-[BAMesh meshWithTriangles:count:], which accepts a
BATriangle (a struct type defined in
BASceneTypes.h) and an integer. This create the necessary Polygon, Point and Tuple objects.
I intend to replace this design with something slightly different that I have used elsewhere, and much prefer. (Briefly, it instead defines new instance methods on
NSManagedObjectContext using a category. But the methods are still defined in the custom ("human") class file for each given entity, as with the current design.)