Export objects with Marmalade Studio
This tutorial walks you through a simple example of how to build and render a world using instanced geometry in Marmalade Studio. You will learn how to export a world from your 3D art package and how to render it from a Marmalade code project.
Building and exporting the assets
Building and exporting the assets
The art files for this tutorial are found in the sdk install\examples\games\kartz\art\tracks\track_01\, files for each 3D modelling package are within appropriately named folders.
Exporting the Models
The first step is to Export all the different models that will appear in the world. All these separate 'pieces' are stored in a single file. Each piece only occurs once in this file, and the positioning is not relevant. This allows the pieces to only be Exported once. The scene should be Exported as a Model Export, with local transforms.
- Open the file track_01_world_ModelExport in your preferred 3D Modelling Package
- Open the Exporter Dialog
- Choose an appropriate project (if required create a new project for this tutorial and add a data directory)
- Choose a Model Export if not already selected
- Leave all the other options as they are already set
- Click Export
Exporting the World
The next step is to decide on the placements for all the objects within the world. Each object has a class type and the name of the model that it is a copy of. This means that if the world has 100 trees, only 1 tree model will be exported, and this will be used everywhere a tree is required. This saves space and makes it easy to alter the world if the original model changes.
- Open the file track_01_ModelExport in your preferred 3D Modelling Package
- Open the Exporter Dialog
- Select the same project as before
- Choose a World Export
- Leave all the other options as set
- Click Export
Rendering the world
Now we need to write the code to render the world.
We include the headers of the modules we will be using.
- IwGx provides low-level rendering API, abstracting software and hardware rendering.
- IwGraphics provides a higher-level API that renders models.
- IwResManager provides resource management - the loading and binarising of assets.
#include "s3e.h" #include "IwGraphics.h" #include "IwGx.h" #include "IwResManager.h" #include "IwTextParserITX.h"
The basic building block of our world will be an entity.
Entities are read out of a text-based .world file. The exact format of the world file is application-definable using the expmetatemplate system. This example uses a very simple default format where each entity looks like:
CStatic { name "myInstance" pos { 100 0 100 } rot { 0 0 0 1 } model "myModel" }
The world file is a standard itx file. As it is read in, the itx parser will feed each token it encounters to the ParseAttribute function of the current object (if any). If this is not recognised the class factory will attempt to instantiate an object of that class.
So for this sample, an object of type "CStatic" will be created. Then ParseAttribute will be called with "name", then "pos", etc.
We will create a CStatic object for each instance in the world file.
class CStatic : public CIwResource {
The position and orientation of the entity are held in m_WorldMat.
CIwMat m_WorldMat;
The entity holds a pointer to a model. A model is a bundle of streams and primitives which are held in an extensible format based on a number of blocks. For example, a model will have a vertex block that contains the model space positions of its vertices.
CIwModel* m_Model;
IwGraphics allows the user to override some types of blocks on a per-instance basis. For example, the world might contain two tables, one in light and one in shade. The application can use model block override to display the two tables with different lighting.
CIwManagedList m_Blocks;
The IW_MANAGED_DECLARE/IW_MANAGED_IMPLEMENT macros define a set of convenience functionality for this class, for example being able to query the classname.
public: IW_MANAGED_DECLARE(CStatic); CStatic() { m_WorldMat = CIwMat::g_Identity; m_Model = NULL; } ~CStatic() { m_Blocks.Delete(); }
The ParseAttribute function is called for each line in the entity's itx entry.
virtual bool ParseAttribute(CIwTextParserITX* pParser, const char* pAttrName) {
The "pos" attribute determines the entity's translation in world space.
if( strcmp(pAttrName, "pos") == 0 ) { float fpos[3]; pParser->ReadFloatArray(fpos, 3); m_WorldMat.t.x = (int32)(fpos[0]); m_WorldMat.t.y = (int32)(fpos[1]); m_WorldMat.t.z = (int32)(fpos[2]); }
The "rot" attribute determines the entity's orientation in world space.
else if( strcmp(pAttrName, "rot") == 0 ) { float fquat[4]; pParser->ReadFloatArray(fquat, 4); CIwQuat q( (int32)(fquat[0] * IW_GEOM_QONE), (int32)(fquat[1] * IW_GEOM_QONE), (int32)(fquat[2] * IW_GEOM_QONE), (int32)(fquat[3] * IW_GEOM_QONE) ); m_WorldMat.PreRotate((CIwMat)q); }
The "scale" attribute determines the entity's size in world space.
else if( strcmp(pAttrName, "scale") == 0 ) { float size[3]; pParser->ReadFloatArray(size,3); for(uint32 x = 0; x < 3; x++) { for(uint32 y = 0; y< 3; y++) { m_WorldMat.m[x][y] = IW_FIXED_MUL(m_WorldMat.m[x][y], (int32)(size[x]*IW_GEOM_ONE)); } } }
The "model" attribute determines the entity's appearance. The model will have been previously loaded from a .geo file via a .group file (in this case, the same one that contains the world file).
else if( strcmp(pAttrName, "model") == 0 ) { CIwStringL s; pParser->ReadString(s); m_Model = (CIwModel*)IwGetResManager()->GetResNamed(s.c_str(), "CIwModel"); }
The base class will handle the "name" attribute (or return false if it doesn't recognise the token)
else return CIwResource::ParseAttribute(pParser, pAttrName);
We return true to indicate we recognised and consumed the token correctly.
return true; }
When we've finished being read in, we add ourselves to the resource manager. This resource will now be owned by the current group and will be binarised along with the rest of the contents.
virtual void ParseClose(CIwTextParserITX* pParser) { IwGetResManager()->AddRes("CStatic", this); }
If a block definition was embedded in our definition this call be made as that block is closed, so we just add the block.
virtual void ParseCloseChild(CIwTextParserITX* pParser, CIwManaged* pChild) { m_Blocks.Add(pChild); }
The Serialise function is called by the resource manager to serialise a resource into or from a .group.bin file. We must ensure that our entire state is securely serialised, otherwise the resource may not function correctly on reload.
virtual void Serialise() { CIwManaged::Serialise(); IwSerialiseInt32(m_WorldMat.m[0][0], 12); IwSerialiseManagedHash(&m_Model); if( g_IwSerialiseContext.read ) IwResolveManagedHash(&m_Model, IW_GRAPHICS_RESTYPE_MODEL); m_Blocks.Serialise(); }
The Render function is called to render each instance.
void Render() {
IwGxSetModelMatrix transforms model space to the entity's world space position/orientation
IwGxSetModelMatrix(&m_WorldMat);
Each model block belonging to this instance is rendered, signalling to IwGraphics to use this block instead of the corresponding block in the model itself.
uint32 i; for(i = 0; i < m_Blocks.GetSize(); i++) { ((CIwModelBlock*)m_Blocks[i])->Render(m_Model, 0); }
Now the model is rendered.
m_Model->Render();
The PostRender method returns IwGraphics to using the blocks inside the models
for(i = 0; i < m_Blocks.GetSize(); i++) { ((CIwModelBlock*)m_Blocks[i])->PostRender(m_Model, 0); } } }; IW_MANAGED_IMPLEMENT(CStatic);
The IW_CLASS_FACTORY macro adds this class to the data-creatable set. This allows itx files to instantiate classes of this type.
IW_CLASS_FACTORY(CStatic);
Since we are adding a custom resource type (the .world file) we need to add a custom resource handler. This is the simplest possible resource handler - it hands off building to the ITX parser and returns no individual resource.
class CWorldHandler : public CIwResHandler { public: CWorldHandler() : CIwResHandler("world", "World") {}; virtual CIwResource* Build(const CIwStringL& pathname) { IwGetTextParserITX()->ParseFile(pathname.c_str()); return NULL; } };
The entry point (where the code starts executing) is the standard c main function.
int main() {
Marmalade Studio modules must be explicitly initialised before use. Modules may implicitly initialise other modules they depend on. In this example IwGx will initialise IwResManager.
IwGxInit(); IwGraphicsInit();
Before we load our world file we need to make IwResManager aware of our .world files and our CStatic class.
IwGetResManager()->AddHandler(new CWorldHandler); IW_CLASS_REGISTER(CStatic);
Now we load our world. Marmalade Studio resources are held in atomic units called resource groups (.group files). A resource group contains one or more resources.
A .group file is a human readable text file specifying which resource are contained in the group.
CIwResGroup
{
name "track"
// the pieces file
environment/track_01/track_01.group
// the world file
tracks/track_01/track_01.world
}
As you can see, the group first loads a child group. This contains the models that will be instanced. Next it includes the world file. This contains our instances.
To load a group we simply call the LoadGroup method of the resource manager singleton.
IwGetResManager()->LoadGroup("track.group");
Once the group file has been loaded, the resource manager singleton can retrieve resources by name and type.
A resource group contains lists of resources by type. For this example we will get the list of all instances and render them. IwGraphics includes a simple bounding sphere test, but a real application would likely put the instances into a hierarchical structure for higher level culling.
CIwResList* pInstances = IwGetResManager()->GetCurrentGroup()->GetListNamed("CStatic");
We keep track of how many milliseconds elapse every frame to move the camera at a constant speed independent of the framerate.
uint64 lastFrame = s3eTimerGetMs();
In this example we will use the keyboard to navigate the world. To this end we keep track of the camera translation.
CIwVec3 cameraTrans(0,0,0);
Now we enter the main loop. The exit condition is s3eDeviceCheckQuitRequest. This will return true if the OS requires us to exit. In the Marmalade Simulator this will be when the close icon is clicked.
while(!s3eDeviceCheckQuitRequest()) {
First of all, move the camera according to the keys pressed and how long the last frame took.
uint64 now = s3eTimerGetMs(); iwfixed speed = (int32)(now - lastFrame); lastFrame = now; if( s3eKeyboardGetState(s3eKeyAbsUp) & S3E_KEY_STATE_DOWN ) cameraTrans.z -= speed; if( s3eKeyboardGetState(s3eKeyAbsDown) & S3E_KEY_STATE_DOWN ) cameraTrans.z += speed; if( s3eKeyboardGetState(s3eKeyAbsLeft) & S3E_KEY_STATE_DOWN ) cameraTrans.x -= speed; if( s3eKeyboardGetState(s3eKeyAbsRight) & S3E_KEY_STATE_DOWN ) cameraTrans.x += speed;
Now we begin rendering.
First of all we clear the screen.
IwGxClear(IW_GX_COLOUR_BUFFER_F | IW_GX_DEPTH_BUFFER_F);
Set a simple camera matrix.
We use the LookAt function to set up a diagonal view of the world, then move to the current cameraTrans.
CIwMat view; view.LookAt(CIwVec3(IW_GEOM_ONE,IW_GEOM_ONE,IW_GEOM_ONE), CIwVec3(0,0,0), -CIwVec3::g_AxisY); view.t = cameraTrans; view.t.y += 1000; IwGxSetViewMatrix(&view);
Now we walk the list of instances, rendering each one.
for(uint32 i = 0; i < pInstances->m_Resources.GetSize(); i++) { ((CStatic*)pInstances->m_Resources[i])->Render(); }
IwGxFlush commits all the drawing commands to the renderer.
IwGxFlush();
Finally we call IwGxSwapBuffers to present the rendered output to the screen.
Marmalade applications are co-operatively threaded. This means they must yield frequently to the system so background processes can continue to run. This is done using s3eDeviceYield. In this example we want to run as fast as possible, so we pass 0 to s3eDeviceYield. This will return control to our application as soon as possible.
s3eDeviceYield(0);
To update the state of the keyboard we must call s3eKeyboardUpdate() once a frame.
s3eKeyboardUpdate(); }
Once the main loop has ended, we must free up any resources we created. Note that the resource allocations in the resource manager are owned by that module. Resource manager resources are destroyed using DestroyGroup. However, any groups still loaded when resource manager terminates will be freed automatically, so we will not explicitly free them here.
Finally, we terminate the modules we created for a clean exit. Marmalade Studio will assert if any allocations are leaked. There are various useful debugging options for identifying leaks.
main() should return 0 to indicate no errors occurred.
return 0; }
Export objects with Marmalade Studio
Reviewed by Unknown
on
17:43
Rating:
No hay comentarios: