Populating a scenegraph and Dynamism

Member
Posts: 153
Joined: 2004.12
Post: #1
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 Blink

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.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #2
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.
Quote this message in a reply
Member
Posts: 153
Joined: 2004.12
Post: #3
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 Cry

There was a long silence...
'I claim them all,' said the Savage at last.
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #4
why couldn't you use a string-based class identifier?
Quote this message in a reply
Member
Posts: 153
Joined: 2004.12
Post: #5
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.
Quote this message in a reply
Member
Posts: 153
Joined: 2004.12
Post: #6
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:
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: 0

My 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.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #7
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:
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.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Scenegraph newbie Falcor 2 2,350 Jul 15, 2005 04:34 PM
Last Post: Falcor