Populating a scenegraph and Dynamism
Iv come to a point in my project where a significant amount of time is going into creating levels. These levels consist of bezier patches, vertex data, height maps, lights, physics objects, and emitters 
Im loading all the information for each type of object from a text file in a manner similar to:
step 1.)Define my object in my application (HeightMap *myHeightMap = [[HeightMap alloc] init])
step 2.)load a text file that describes each object ([myHeightMap load:@"someFile.LC"])
step 3.)add the object to my scene graph ([sceneGraph addObject:heightMap])
This obviously makes for some really long initialization code. And anytime i want to add/remove or fundamentally change my level i have to recompile my app (which isn't a big deal but annoying.)
Its also becoming harder and harder to refine or add new features to my different objects. Im seriously thinking about creating a new loading system that relies on dynamism. I was wondering if someone has attempted something like this already? And if so wether or not revamping my entire engine to rely on such technology is really going to help things out.

Im loading all the information for each type of object from a text file in a manner similar to:
step 1.)Define my object in my application (HeightMap *myHeightMap = [[HeightMap alloc] init])
step 2.)load a text file that describes each object ([myHeightMap load:@"someFile.LC"])
step 3.)add the object to my scene graph ([sceneGraph addObject:heightMap])
This obviously makes for some really long initialization code. And anytime i want to add/remove or fundamentally change my level i have to recompile my app (which isn't a big deal but annoying.)
Its also becoming harder and harder to refine or add new features to my different objects. Im seriously thinking about creating a new loading system that relies on dynamism. I was wondering if someone has attempted something like this already? And if so wether or not revamping my entire engine to rely on such technology is really going to help things out.
There was a long silence...
'I claim them all,' said the Savage at last.
Couldn't you just have a text file that lists the objects, their types, and the files it should reference? Then it takes each object, dynamically allocates memory for it, initializes it with the correct type, and initialization file. That seems like it would work and prevent you from needed to compile when changing the level.
Seems like that would pretty much be exactly what i would do except instead of doing something like:
Class namedClass = NSClassFromString(className);
I would have to use some type of switch. Which *almost* goes against the principle of it. Just one more place where i would have to keep track of some integer based class identifier
Class namedClass = NSClassFromString(className);
I would have to use some type of switch. Which *almost* goes against the principle of it. Just one more place where i would have to keep track of some integer based class identifier
There was a long silence...
'I claim them all,' said the Savage at last.
why couldn't you use a string-based class identifier?
I could. I don't see how that improves the situation though. It just seems easier to use NSClassFromString instead of
Code:
switch(type)
{
case LC_LIGHT:
{
class = [[LCLight alloc] init];
}
case LC_HEIGHT_MAP:
{
class = [[LCHeightMap alloc] init];
}
break;
etc...
}There was a long silence...
'I claim them all,' said the Savage at last.
This is what iv come up with so far:
My scene graph is populated by LCNode's. Each LCNode can have many children and one parent. Every object (Light, Camera, etc..) that can go on my scene graph is a child class of LCNode.
Given a scenegraph like:
My loading code is:
My scene graph is populated by LCNode's. Each LCNode can have many children and one parent. Every object (Light, Camera, etc..) that can go on my scene graph is a child class of LCNode.
Given a scenegraph like:
Code:
SceneGraph:LevelOne
1
Node: LCCamera:1.LCCamera
Name: Camera
Position: 0 0 0
Rotation: 0 0 0 0
Scale: 1 1 1
Children: 2
Node: LCLight:1.LCLight
Name: Light1
Position: 0 0 0
Rotation: 0 0 0
Scale: 1 1 1
Children: 0
Node: LCCurvedSurface:1.LCCurvedSurface
Name: Surface0
Position: 0 0 0
Rotation: 0 0 0 0
Scale: 1 1 1
Children: 0My loading code is:
Code:
- (void)loadSceneGraph:(NSString *)string
{
NSString *name;
int nNodes,i;
NSString *file = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], string];
NSLog(@"Loading: %@", file);
NSString *contents = [NSString stringWithContentsOfFile:file];
NSScanner *scan = [[NSScanner alloc] initWithString:contents];
[scan scanString:@"SceneGraph:" intoString:nil];
[scan scanUpToString:@"\n" intoString:&name];
[scan scanInt:&nNodes];
NSLog(@"%@ %d Nodes", name, nNodes);
LCNode *node;
for(i = 0; i < nNodes; i++)
{
node = [self scanNode:scan];
[self addObject:node];
}
}
- (LCNode *)scanNode:(NSScanner *)scan
{
NSString *class, *filename, *name;
float fx,fy,fz;
float rw,rx,ry,rz;
float sx,sy,sz;
int nChildren, i;
[scan scanString:@"Node: " intoString:nil];
[scan scanUpToString:@":" intoString:&class];
[scan scanString:@":" intoString:nil];
[scan scanUpToString:@"\n" intoString:&filename];
[scan scanString:@"Name: " intoString:nil];
[scan scanUpToString:@"\n" intoString:&name];
[scan scanString:@"Position: " intoString:nil];
[scan scanFloat:&fx]; [scan scanFloat:&fy]; [scan scanFloat:&fz];
[scan scanString:@"Rotation: " intoString:nil];
[scan scanFloat:&rw]; [scan scanFloat:&rx]; [scan scanFloat:&ry]; [scan scanFloat:&rz];
[scan scanString:@"Scale: " intoString:nil];
[scan scanFloat:&sx]; [scan scanFloat:&sy]; [scan scanFloat:&sz];
[scan scanString:@"Children: " intoString:nil];
[scan scanInt:&nChildren];
NSLog(@"%@ - (%@ : %@)",name, class, filename);
NSLog(@"{");
Class object = NSClassFromString(class);
LCNode *node = [[object alloc] initFromFile:filename];
[node setName:name];
[[node nodePosition] set:fx:fy:fz];
[[node nodeScale] set:sx:sy:sz];
LCNode *child;
for(i = 0; i < nChildren; i++)
{
NSLog(@"Child:");
child = [self scanNode:scan];
[node addChild:child];
}
NSLog(@"}");
return node;
}
- (void)addObject:(LCNode *)tModel
{
[objects addObject:tModel];
}There was a long silence...
'I claim them all,' said the Savage at last.
I use a mix of classloading and XML-like DOM tree design for loading my scenes.
Here's what one of my "world" files looks like:
I use a classloader to load the objects by name. The objects all implement the interface:
So as I descend the DOM tree for the file, I create objects by name and pass them the DOM node for initialization. Objects defined *inside* of other objects ( see CelestialBody, which is a child of Sky ) become logical children of the parent, so the parent "owns" it, and acts as it's proxy in the scene graph.
DOn't pass up classloadin and deferral of configuration to the class. It'll keep your code very clean.
One note: The file format above is a custom file format I came up with a few years ago because at the time I didn't know about TinyXML. Had I, I wouldn't have come up with my own... but what's done is done, and my APIs for the file format are pretty easy to work with.
Here's what one of my "world" files looks like:
Code:
[begin:Manifest dtd=org.zakariya.worldengine.world]
[begin:World name="Test 0"]
#!
World parameters
!#
size:real=16000
fogColor:reallist=0.7,0.7,0.75
ambientLight:reallist=0.4,0.4,0.4,1
gravity:reallist=0,0,-9.8
wind:reallist=0,80,0
defaultMu:real=100
seed:int=1
environmentMap:string=Textures/Clouds/2.png
[begin:Lights]
#!
If only one light is specified it will default
to be the primary light.
!#
[begin:Light primary=true]
position:reallist=1200,900,400
#primary defaults to directional anyhow
directional:boolean=true
ambient:reallist=0.0,0.0,0.0,1.0
diffuse:reallist=0.9,0.9,0.9,1
specular:reallist=0.9,0.9,0.9,1
constantAttenuation:real=1
linearAttenuation:real=0
quadraticAttenuation:real=0
[end]
[end]
[begin:GraphEntities]
[begin:SkyDome]
name:string=Sky
insertInto:stringlist=Global,FinalPass
primaryColor:reallist=0.3, 0.3, 0.5, 1
horizonColor:reallist=1, 0.95, 0.9, 1
colorRamp:real=1.5
cloudTexture:string=Textures/Clouds/2.png
cloudColor:reallist=1,1,1,0.2
numStars:int=750
starParticle:string=Sun
starColorA:reallist=1,1,1,1
starColorB:reallist=1,1,1,0.5
starMinArc:real=0.05
starMaxArc:real=0.1
[begin:CelestialBody attachToPrimaryLight=true]
name:string=Sun
#attachToPrimaryLight obviates this
direction:reallist=1200,900,1000
arcSize:real=2
# one or the other
particleTexture:string=Sun
color:reallist=1,0.5,0.3,1
blendSrcFunc:string=GL_SRC_ALPHA
blendDstFunc:string=GL_ONE
haloArcSize:real=30
haloColor:real=1,1,0,1
[end]
[end]
[begin:Terrain]
name:string=Terrain
insertInto:stringlist=Global
heightmap:string=Maps/0/Heightmap.png
maxHeight:real=1800
variance:int=5
pixelsPerPatch:int=32
patchesPerSide:int=64
primaryTexture:string=Textures/Terrain/Grass/1.jpg
primaryTextureScale:real=200
secondaryTexture:string=Textures/Terrain/Rocks/7.jpg
secondaryTextureScale:real=220
detailTexture:string=Textures/Terrain/Detail/4.jpg
detailTextureScale:real=4
[begin:Shader class="NormalModulationTerrainShader"]
resolution:int=512
# primary and secondary colors to be
# selected based on modulation
primaryColor:reallist=1,1,1,1
secondaryColor:reallist=1,0.95,0.9,1
# color for low z values
lowColor:reallist=0.8,0.8,0.8,1
# color for high z values
highColor:reallist=1,1,1,1
# color of light hitting terrain
lightColor:reallist=1,1,0.9,1
# color of shadows on terrain
shadowColor:reallist=0.4,0.4,0.6,1
# thresholding values for modulation; the tighter, the more
# severe modulation you'll see between primary & secondary
mixMin:real=0.25
mixMax:real=0.4
#!
contrast and brightening, applied as a post-processing
effect on the mixmap.
mixBrighten is an offset, pushing the mix value up or down.
valid values are from -1 to +1, will be clamped if out
of range. Default value is zero, which has no effect.
mixContrast is a power the mix value ( after brightening )
will be raised to. Where valid values are from 0 and up.
Values from [0, 1) will result in a raising of contrast,
with 0 resulting in absolute contrast.
Values from (1,infinity) will result in a dampening of
contrast, to gray. Default value is 1 which has no effect.
!#
mixBrighten:real=0.6
mixContrast:real=0.2
#!
We can add an image to the mixmap, to add some noise,
which adds realism.
modes are
Add : dst = src + dst
AddSigned : dst = (( src - 128 ) * 2 ) + dst;
!#
#applyImage:string=Maps/0/Noise.png
#applyImageMode:string=AddSigned
#!
if true, resulting colormap and mixmap will be written
to disk where the app is located as the
two files "Colormap_RGB.png" and "Colormap_Alpha.png",
where the RGB file is the colormap, and the Alpha file
is the mixmap. This is useful for tuning.
!#
dumpOutput:boolean=true
[end]
[end]
[begin:Foliage]
name:string=Foliage
insertInto:stringlist=Ornamentation
terrain:string=Terrain
cellsPerSide:int=9
cellSize:int=70
texture:string=Textures/Foliage/Foliage-Green.png
orientationTypeZero:string=Standing
orientationTypeOne:string=Standing
orientationTypeTwo:string=Standing
orientationTypeThree:string=Standing
sizeTypeZero:reallist=2,3
sizeTypeOne:reallist=2,3
sizeTypeTwo:reallist=2,3
sizeTypeThree:reallist=2,3
zOffsetTypeZero:real=-0.15
zOffsetTypeOne:real=-0.15
zOffsetTypeTwo:real=-0.15
zOffsetTypeThree:real=-0.1
color:reallist=0.4,0.5,0.2
[begin:OrnamentationGenerator]
numTypeZero:int=350
numTypeOne:int=250
numTypeTwo:int=100
numTypeThree:int=4
[end]
[end]
[begin:Debris]
name:string=Rocks
insertInto:stringlist=Ornamentation
terrain:string=Terrain
cellsPerSide:int=7
cellSize:int=500
modelZero:string=Ornamentation/Slab.obj
modelZeroScale:real=10
modelZeroRandomRotationAxis:reallist=1,1,1
modelZeroOffset:real=0
modelOne:string=Ornamentation/Slab.obj
modelOneScale:real=7
modelOneRandomRotationAxis:reallist=1,1,1
modelOneOffset:real=0
modelTwo:string=Ornamentation/Slab.obj
modelTwoScale:real=4
modelTwoRandomRotationAxis:reallist=1,1,1
modelTwoOffset:real=2
modelThree:string=Ornamentation/Slab.obj
modelThreeScale:real=2
modelThreeRandomRotationAxis:reallist=1,1,1
modelThreeOffset:real=0
#texture:string=Ornamentation/Rock.png
texture:string=Textures/Terrain/Rocks/7.jpg
color:reallist=0.75,0.75,0.75
[begin:OrnamentationGenerator]
numTypeZero:int=10
numTypeOne:int=10
numTypeTwo:int=10
numTypeThree:int=20
[end]
[end]
#!
[begin:RainEnvironmentalParticleSystem]
name:string=Rain
insertInto:stringlist=FinalPass
particleDensity:real=1e-8
color:reallist=1,1,1,0.3
length:real=10
speed:real=1000
[end]
!#
[begin:FogEnvironmentalParticleSystem]
name:string=Fog
insertInto:stringlist=FinalPass
particleDensity:real=2.0e-9
tex:string=Smoke2
color:reallist=0.9,0.9,0.9,0.5
radius:real=400
[end]
[end]
[begin:GraphProperties]
[begin:OrderedGraph]
[begin:Global]
order:stringlist=Sky,Terrain
[end]
[begin:Ornamentation]
order:stringlist=Rocks,Foliage
[end]
[begin:FinalPass]
order:stringlist=Sky,Rain,Fog
[end]
[end]
[begin:SpatialGraph]
#nothing, for now
[end]
[begin:EffectGraph]
#nothing, for now
[end]
[end]
[end]
[end]I use a classloader to load the objects by name. The objects all implement the interface:
Code:
class DOMInitializable
{
public:
DOMInitializable( void ){}
virtual ~DOMInitializable( void ){}
/**
represents the two states for initialization -- Success and Failure.
*/
enum status { Failure = 0, Success = 1 };
/**
initialize this object from a ManifestDOMObject node. @return Success
on success and Fialure otherwise. Failures should be logged
to PANSICore::Logger
*/
virtual status initialize( PANSICore::ManifestDOMObject *node ) = 0;
};So as I descend the DOM tree for the file, I create objects by name and pass them the DOM node for initialization. Objects defined *inside* of other objects ( see CelestialBody, which is a child of Sky ) become logical children of the parent, so the parent "owns" it, and acts as it's proxy in the scene graph.
DOn't pass up classloadin and deferral of configuration to the class. It'll keep your code very clean.
One note: The file format above is a custom file format I came up with a few years ago because at the time I didn't know about TinyXML. Had I, I wouldn't have come up with my own... but what's done is done, and my APIs for the file format are pretty easy to work with.
Possibly Related Threads...
| Thread: | Author | Replies: | Views: | Last Post | |
| Scenegraph newbie | Falcor | 2 | 2,153 |
Jul 15, 2005 04:34 PM Last Post: Falcor |
|

