SyntaxHighlighter

Monday 17 June 2013

Day 6: Adding objects

Procedure to add object models

  1. Download .fbx file (these tend to work seamlessly with XNA), mine was called Diskette.fbx (it's a real doozy)
  2. Update configs and markers: two things, make an ALVARConfig.txt matching your marker card; and update config.txt to map the model to the marker (so XNA knows which marker card applies to which model). For this example I reused ALVARConfig7.txt, it can be used as is so all I did renaming the line inside config.txt to say "diskette" and not "gingerbread". See below for details.
  3. Import the model and all its dependencies into AR program (LowryDemo): in Visual C# 2008, within Solution Explorer window (right hand side), right click Content -> add -> existing item, browse to your .fbx. Now you can reference the model within the code. Don't forget to repeat this step to add textures jpgs etc that came with it too!
  4. Update global variables to initialize a MarkerNode variable you will use for your demo.
  5. Create a method to add your model: add[modelname]demo (see below for details of this method)
  6. Add call to your addDemo method from step 4, into createObjects() method

Notes on Step 2: How to update configs


ALVARConfig.txt

Typically looks like this:
Sample ALVARConfig.txt file
Each ALVARConfig refers to a marker card. The first line is the number of markers on your marker card and the 4 numbers below tell ALVAR which markers they are. The third set of numbers (four of them again) represent the ratio of sizes of the markers. The bottom bunch of numbers are for scaling, but don't worry about them - the same values can be reused in all your ALVARConfigs.

Config.txt layout

Sample config.txt file
The first three numbers represent the camera settings. The framerate (15 in this case), X resolution, and Y resolution (so here the resolution would be standard 720p, aka 960x720). Every line apart from these three is a mapping of an ALVARConfig file to a model name - assigning marker cards to models. You can then reference the model-marker pair via the model name when setting up markers.

Step 4: Making a global MarkerNode variable for your demo

Edit the existing line to include a MarkerNode of your own:
MarkerNode sofaMarkerNode, dragonMarkerNode, ... , disketteMarkerNode;

Remember what you called it, it will be used in the addDemo method below (step 5)

Step 5: Method addDemo

Every demo needs a method that adds it to the scene graph. All these methods are called in turn from within the CreateObjects() method. Here I'll explain how to make an addDemo method for your chosen model demo.

The aim is to make appropriate nodes and add them to the scene graph. You'll have a marker node on top, and some geometry and transform nodes below.
  1. Initialize marker node. This code is identical for any demo, can copy-paste and change the name of the variable.
  2. Add marker node to scene graph. Remember the marker node is always at the top of the nodes for a single demo (model transform and geometry nodes all fall under a single marker node, for each and every demo)
  3. Make geometry and transform nodes to manage the models in the demo
  4. Add geometry and transform nodes to the scene graph under the marker node, in whichever order gives the desired result.
Here's how its done:

Initialize marker node and add to scene graph (steps 1 and 2)

If you recall the global MarkerNode variable for our demo has already been initalized, so we assign it here. Similar code can be reused for every demo.
        private void addDisketteDemo()
        {
            int[] ids = new int[4];
            for (int i = 0; i < ids.Length; i++)
                ids[i] = i;
            modelConfig _modelConfig = new modelConfig();
            for (int i = 0; i < modelConfigArray.Length; i++)
            {
                if (modelConfigArray[i].name == "diskette")
                    _modelConfig = modelConfigArray[i];
            }
            if (_modelConfig.isMarkerArray)
            {
                disketteMarkerNode = new MarkerNode(scene.MarkerTracker, _modelConfig.markerAddress, ids);
            }
            else
            {
                disketteMarkerNode = new MarkerNode(scene.MarkerTracker, _modelConfig.markerNumber);
            }
            scene.RootNode.AddChild(disketteMarkerNode);

The string within the if statement of the second for loop is important:
               if (modelConfigArray[i].name == "diskette")
Here, the string must be the same as the name given in the config.txt file that links the demo to the marker array, i.e. a line similar to:
diskette,1,ALVARConfig7.txt

Geometry and Transform nodes (steps 3 and 4)

A geometry node basically holds a model itself, and some information pertaining to it. You load the model into XNA via a ModelLoader object, than assign it to a geometry node as such:
            ModelLoader loader = new ModelLoader();
            Model diskette = (Model)loader.Load("", "diskette");
            GeometryNode diskettenode = new GeometryNode("diskette");
            diskettenode.Model = diskette;
            diskette.UseInternalMaterials = true;
            diskette.CastShadows = true;
            diskette.ReceiveShadows = true;

The features UseInternalMaterials, CastShadows and ReceiveShadows can be set as desired.

Transform nodes are similar, but obviously you don't need to load a model:
            TransformNode mosiTrans = new TransformNode();
            mosiTrans.Scale = new Vector3(0.001f * _modelConfig.scale,
                                          0.001f * _modelConfig.scale,
                                          0.001f * _modelConfig.scale);
            //mosiTrans.Translation = new Vector3(0, 100, 0);
            mosiTrans.Rotation = Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0),
                                                                MathHelper.ToRadians(-90));

            disketteMarkerNode.AddChild(mosiTrans);
            mosiTrans.AddChild(diskettenode);
        }

The TransformNode object has methods Scale(Vector3), Translation(Vector3) and Rotation(Quaternion).

The final two lines add the new nodes to the scene graph where they need to be .

Note on textures

All textures must be in dimensions that are multiples of four, and dependent on your graphics card they may need to be powers of two. The error that pops up starts:
Invalid texture. Face 0 is sized (x by y)...

Making new markers to add more models

Currently we can have up to 12 marker arrays. 8 are in use so far, the other 4 can be printed out and assigned to models as and when required. Files ALVARArrayx.gif (where x =  nothing, or 2 - 12) hold the marker images to be printed.

Adding markers

The following has been taken from the ALVAR documentation, and describes how to generate sample markers for use with some sample programs:
Stuff about generating new markers
As and when we need more marker arrays (e.g. when we want over 12 objects simultaneously), this should be worth looking into.

My first new static model :-)

Trawling the net for models turned up a good 'un

Dynamic models, manual and inbuilt animation

The procedure for using animated models and animating them is similar to dealing with static models, but there are some differences: you use an AnimatedModelLoader instead of a ModelLoader, and you require some extra steps to perform the actual animation.

Note there are two types of animations, some models have their own animations inbuilt, and they may for example come with a skinningdata tag. The other type of animation is where you cause models to move about or update themselves by growing/shrinking etc manually by using the scene graph (e.g. use a TransformNode that is updated in the draw method). This second type of animation can be done to any model, static or otherwise, and is entirely dependent on the scene graph you craft.

The entire procedure is as follows (changes highlighted in red):

  1. Manage configs and markers as before, import the new model into Content as before
  2. If your has an inbuilt animation and Skinningdata: right click on the newly added model under Content, -> click properties, under Content Processor change to SkinnedModelProcessor.
  3. Initialize a global MarkerNode variable as before, but (if you want to manually animate a model) also initialize a global TransformNode, and/or (if your model has an inbuilt animation) initialize a global AnimatedModel variable.
  4. Create addDemo, but (if your model has an inbuilt animationusing AnimatedModelLoader instead of ModelLoader
  5. Add call to your addDemo inside CreateObjects() as before.
As mentioned, an inbuilt animation makes the model do stuff. This will result in a model animating in one place. And if we want to manually animate a model so that it moves across and about the view, we can use the scene graph (a TransformNode that is updated on every frame to make the model look like it's moving).

In theory this will work, at the moment I downloaded an animated model and tried to load its animations but I'm getting a Skinningdata tag not found exception that keeps crashing the program. Either I haven't found a model that has skinningdata yet or something is seriously wrong!

Investigations will continue tomorrow

No comments:

Post a Comment

Note: only a member of this blog may post a comment.