/*
 * Author: Andrew Mann
 *
 * Copyright (C) 2004 PlaneShift Team (info@planeshift.it,
 * http://www.planeshift.it)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation (version 2 of the License)
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */





#include "cssysdef.h"
#include "iutil/objreg.h"
#include "iutil/plugin.h"
#include "iutil/vfs.h"
#include "iutil/virtclk.h"
#include "iutil/csinput.h"
#include "iutil/eventq.h"
#include "iutil/event.h"
#include "iutil/document.h"
#include "ivideo/graph3d.h"
#include "ivideo/graph2d.h"
#include "imap/ldrctxt.h"
#include "imap/loader.h"
#include "imap/reader.h"
//#include "csutil/sysfunc.h"
//#include "ivaria/reporter.h"
//#include "ivaria/stdrep.h"

#include "iengine/engine.h"
#include "iengine/camera.h"
#include "iengine/sector.h"
#include "iengine/material.h"
#include "iengine/texture.h"
#include "ivideo/material.h"
#include "cstool/csview.h"
#include "imesh/thing.h"
#include "iengine/mesh.h"
#include "imesh/object.h"
#include "imesh/particles.h"
#include "igraphic/imageio.h"

#include "cstool/initapp.h"
#include "csutil/cmdhelp.h"
#include "csutil/event.h"

// PAWS stuff
#include "paws/pawsmanager.h"
#include "paws/pawsmainwidget.h"

// Application paws widgets

#include "paws/pawsfilenavigation.h"
#include "gui_menubar.h"
#include "pawsscrollpane.h"
#include "pawscollapsablewidget.h"
#include "gui_material.h"

#include "gui_particlesystem.h"
#include "gui_pslist.h"
#include "gui_colorrow.h"

// NR stuff
#include "ivideo/shader/shader.h"


#include "pv_helpers.h"
#include "pv_resmgr.h"
//#include "heightgrid.h"
#include "partview.h"

CS_IMPLEMENT_APPLICATION


// Initialize globals/statics
PartViewApp *PartViewApp::g_pApp=NULL;


//
//  Startup function
//

int main(int argc, char *argv[])
{
    // CreateEnvironment prepares CS for use. 
    // Part of preparation is creating the object registry, which is returned upon success
    iObjectRegistry *object_reg = csInitializer::CreateEnvironment(argc,argv);

    // Instanciate the application class
    PartViewApp::g_pApp = new PartViewApp(object_reg);

    // Initialize and run the application
    if (PartViewApp::g_pApp && PartViewApp::g_pApp->Initialize())
        PartViewApp::g_pApp->Run();

    delete PartViewApp::g_pApp;
    PartViewApp::g_pApp=NULL;

    // Clean up CS stuff
    csInitializer::DestroyApplication(object_reg);
    return 0;
}




PartViewApp::PartViewApp(iObjectRegistry *object_reg)
{
    // Set our local pointer to the object registry
    this->object_reg=object_reg;


    rotX=rotY=0.0f;

    // Default to showing the window
    window_visible=true;
    last_int_x=last_int_z=0xFFFFFFFF;

    mouse_left_down=mouse_right_down=false;

    menubar=NULL;
    matlistgui=NULL;
    pslistgui=NULL;

    resource_manager=NULL;
}


PartViewApp::~PartViewApp()
{
}


bool PartViewApp::Initialize()
{
    // Perform any initialization that might be able to fail here

    // The config file manager for CS pre-loads the VFS plugin and then reads a specified config file 
    //  which may specify some engine settings
    if ( !csInitializer::SetupConfigManager(object_reg, "/this/partview.cfg"))
    {
        // Initialization of config file failed
#ifdef _RPTF0
        _RPTF0(_CRT_WARN,"PartViewApp::Initialize() - csInitializer::SetupConfigManager() failed!");
#endif
        return false;
    }



    // Request the plugins that we need to function
    if (!csInitializer::RequestPlugins(object_reg,
        CS_REQUEST_VFS, // VFS - The Virtual File System plugin.  This allows CS to map a standard format of resource paths and names onto the 
                        // differing filesystems it can run on (windows and linux for example).  If something needs to be loaded from disk, VFS is needed.
                        // Pretty much every application will make use of VFS
        CS_REQUEST_OPENGL3D, // Need the OpenGL 3D renderer to display output to screen
        CS_REQUEST_ENGINE, // The CS engine tracks things including sectors, meshes, materials, etc
        CS_REQUEST_IMAGELOADER, // The imageloader will let us load images like textures for our walls automatically
        CS_REQUEST_LEVELLOADER,   // LevelLoader must be specified along with imageloader
        CS_REQUEST_REPORTER,
        CS_REQUEST_REPORTERLISTENER, // The reporter handles receiving reports (warning/error output) and the listener handles outputting it
        CS_REQUEST_PLUGIN("crystalspace.documentsystem.xmlread", iDocumentSystem),
        CS_REQUEST_END // Mark the end of requests
        ))
    {
        // Initialization of plugins failed - probably not all of them are present / able to be found
#ifdef _RPTF0
        _RPTF0(_CRT_WARN,"PartViewApp::Initialize() - Could not initialize plugins!");
#endif
        return false;
    }


    // Setup static event handler callback
    if (!csInitializer::SetupEventHandler(object_reg,StaticEventHandler))
    {
        // The reporter is setup now, so using that instead of the RPT debug macros
        csReport(object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Could not initialize event handler.");
        return false;
    }

    // Check to see if the --help option was specified on the command line
    if (csCommandLineHelper::CheckHelp(object_reg))
    {
        csReport(object_reg, CS_REPORTER_SEVERITY_NOTIFY,"PartViewApp::Initialize()","This is a MainApp application. There is no help, dolt!");
        // Display the CS options anyway
        csCommandLineHelper::Help (object_reg);
        return false;
    }

    csRef<iPluginManager> pluginmgr = CS_QUERY_REGISTRY(object_reg, iPluginManager);
    if (!pluginmgr.IsValid())
    {
        csReport(object_reg, CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iPluginManager interface in the available plugins!");
        return false;
    }



    // Start finding the interfaces to some of the plugins we loaded
    // The virtual clock.
    vc = CS_QUERY_REGISTRY (object_reg, iVirtualClock);
    if (!vc.IsValid()) 
    {
        // Crystal Space uses SmartPointers for almost every pointer return.
        //  SmartPointers are a small wrapper template class around a pointer that attempts to function as transparently as possible.
        //  SmartPointers automatically adjust a reference count based on the number of SmartPointers to a given object that are in existance.
        //  When the count reaches 0, the object is deleted.
        // In this way, some memory leaks are avoided, and reference counting does not have to be performed manually.
        //
        csReport(object_reg, CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iVirtualClock interface in the available plugins!");
        return false;
    }

    // The CS graphics engine
    engine = CS_QUERY_REGISTRY (object_reg, iEngine);
    if (!engine.IsValid())
    {
        csReport(object_reg, CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iEngine interface in the available plugins!");
        return false;
    }

    // The 3D Renderer.
    g3d = CS_QUERY_REGISTRY (object_reg, iGraphics3D);
    if (!g3d.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iGraphics3D interface in the available plugins!");
        return false;
    }

    // The keyboard input driver.
    kbd = CS_QUERY_REGISTRY (object_reg, iKeyboardDriver);
    if (!kbd.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iKeyboardDriver interface in the available plugins!");
        return false;
    }

    // The Crystal Space file parser plugin interface - called 'iLoader'.
    loader = CS_QUERY_REGISTRY (object_reg, iLoader);
    if (!loader.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iLoader interface in the available plugins!");
        return false;
    }

    // The Crystal Space Virtual File System interface
    vfs = CS_QUERY_REGISTRY (object_reg, iVFS);
    if (!vfs.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iVFS interface in the available plugins!");
        return false;
    }

    // The shader plugins make heavy use of string sets.  Stringsets are registered lists of strings converted into integer indexes
    //  for fast comparison
    csRef<iStringSet> strings = CS_QUERY_REGISTRY_TAG_INTERFACE (object_reg, "crystalspace.shared.stringset", iStringSet);
    if (!strings.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iStringSet interface in the available plugins!");
        return false;
    }

    // We also need a iDocumentSystem and an iDocument to parse the shader definition file for us
    csRef<iDocumentSystem> docsys (CS_QUERY_REGISTRY(object_reg, iDocumentSystem));
    if (!docsys.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iDocumentSystem interface in the available plugins!");
        return false;
    }
    

    // Get the 2D rendering interface from the 3D rendering interface
    g2d = g3d->GetDriver2D();
    if (!g2d.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not retrieve iGraphics2D interface from g3d->GetDriver2D()!");
        return false;
    }

    // The plugin manager, which allows us to have better control of loading plugins.  We need this to load a plugin and get a pointer to
    // an interface which many different plugins support
    plgmgr = CS_QUERY_REGISTRY (object_reg, iPluginManager);
    if (!plgmgr.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iPluginManager interface in the available plugins!");
        return false;
    }

    // The iLoaderPlugin interface for the particles loader
    particlesloader=CS_LOAD_PLUGIN(pluginmgr,"crystalspace.mesh.loader.factory.particles",iLoaderPlugin);
    if (!particlesloader.IsValid())
    {
        csReport(object_reg, CS_REPORTER_SEVERITY_ERROR, "MainApp::Initialize()","Could not find an iLoaderPlugin interface for 'crystalspace.mesh.loader.factory.particles'!");
        return false;
    }


    // OpenApplication performs some CS startup tasks such as initializing all the previously requested plugins
    if (!csInitializer::OpenApplication (object_reg))
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","csInitializer::OpenApplication() failed!");
        return false;
    }

    // PAWS window manager creation
    paws = new PawsManager(object_reg);
    if (!paws)
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","PawsManager could not be instanciated!");
        return false;
    }

    // Create the top level paws interface 'widget' (window)
    mainWidget = new pawsMainWidget(paws);
    if (!mainWidget)
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","pawsMainWidget could not be instanciated!");
        return false;
    }
    
    // Assign this widget as the current top level widget
    paws->SetMainWidget(mainWidget);

    // Helper function which registers any custom paws widgets that are used in this application
    //  defined below
    RegisterPawsFactories();

    // Load all the registered widgets
    if (!LoadPawsWidgets())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","LoadWidgets() signaled bad return.");
        return false;
    }

    // Setup standard PAWS mouse cursor
    paws->GetMouse()->ChangeImage("Standard Mouse Pointer");


    // Create the resource manager
    resource_manager=new PVResourceManager(object_reg);


    ////
    // Basic initialization is complete.  Begin to setup the render environment
    ////

    // Don't use any lighting cache at this time.
    engine->SetLightingCacheMode(0);
  

    // Create the sector - a logical area where our walls, lights, and camera will be placed
    // The name can be anything, but must be unique.  Since we only have 1 sector we don't need to worry about that.
    the_room = engine->CreateSector("sector0");


    CreateDefaultRoom();
/*  LoadMapFileAsRoom("/this/art/world/hydlaa.zip");

    iSectorList *sectorlist=engine->GetSectors();

    if (sectorlist)
    {
        if (sectorlist->GetCount() > 0)
            the_room=sectorlist->Get(0);

        the_room->
    }
    */

    // Now that we have a room, create our camera and put it inside.

    // When constructing a view, the CS engine interface and 3d rendering interface must be passed.
    view = csPtr<iView> (new csView (engine, g3d));

    // Put the camera inside of our sector
    view->GetCamera ()->SetSector (the_room);

    // Set the position of the camera to 0,5,-3 - not quite in the middle of the room
    view->GetCamera ()->GetTransform ().SetOrigin (csVector3 (0, 5, -3));

    // The view dimensions should match the dimensions of the viewport on your monitor.
    //  To do that, we need to get the 2d rendering interface and see what the dimensions are
    iGraphics2D* g2d = g3d->GetDriver2D ();
    view->SetRectangle (0, 0, g2d->GetWidth (), g2d->GetHeight ());

    
/*
    ////
    //  Test object(s)
    //
    // The objects created from this point on are testing various pieces of the engine
    //
    ////

    csRef<iImage> img = loader->LoadImage("/lib/std/stone4.gif");
    if (!img.IsValid())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error loading '/lib/std/stone4.gif' as image!");
        return false;
    }
    csRef<iImage> selimg = loader->LoadImage("/lib/std/white128.gif");
    if (!selimg.IsValid())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error loading '/lib/std/white128.gif' as image!");
        return false;
    }


    // Find the heightgrid plugin
//    csRef<iHeightGridPlugin> hg_plugin = CS_QUERY_REGISTRY_TAG_INTERFACE (object_reg, "crystalspace.mesh.object.rhad.heightgrid", iHeightGridPlugin);
    csRef<iHeightGridPlugin> hg_plugin = CS_QUERY_REGISTRY(object_reg, iHeightGridPlugin);
    if (!hg_plugin.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iHeightGridPlugin interface for heightgrid in the available plugins!");
        return false;
    }

    csRef<iMeshObjectFactory> mesh_fact = hg_plugin->NewFactory();
    if (!mesh_fact.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not create a heightgrid factory from the plugin!");
        return false;
    }

    hg_fact=SCF_QUERY_INTERFACE(mesh_fact,iHeightGridFactory);
    if (!hg_fact.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not retrieve an iHeightGridFactory interface from the factory!");
        return false;
    }

    // Initialize the factory
    hg_fact->SetColoringImage(img);
    hg_fact->SetMapScalingFactorX(4.0);
    hg_fact->SetMapScalingFactorZ(4.0);
    hg_fact->SetHeightBiasFactor(1.0f);
    if (gui_menu_widget)
    {
        gui_menu_widget->SetScaleVal(1.0f);
        gui_menu_widget->SetBiasVal(1.0f);
    }


    hg_mesh=mesh_fact->NewInstance();


    hg_meshwrapper=engine->CreateMeshWrapper(hg_mesh,"HeightGrid",the_room);

*/


    /*
    
    heightgrid=new HeightGrid(object_reg,g3d,engine);
    heightgrid->InitFromImage(img,selimg,the_room,6.0f);

   // heightgrid->HeightFromImage(img,1.0f,0.0f);
    heightgrid->Place(csVector3(-3.0f,2.0f,-3.0f));



    /*
    // A cube in the center of the room - with a shader

    // Create an empty document to read the shader specification into
    csRef<iDocument> shaderDoc = docsys->CreateDocument ();

    // Now we open the shader file using VFS
    csRef<iFile> shaderFile = vfs->Open ("/shader/cg_parallax.xml", VFS_FILE_READ);

    // And parse the XML formatted shader into the iDocument
    shaderDoc->Parse (shaderFile);

    // Now we need to get an interface to the shader manager which will allow us to compile and register this shader
    csRef<iShaderManager> shmgr (CS_QUERY_REGISTRY(object_reg, iShaderManager));

    // And get the XMLShader compiler, which is used for all the shader top level definitions
    csRef<iShaderCompiler> shcom (shmgr->GetCompiler ("XMLShader"));
    
    // Compile the shader XML into a shader object which can later be applied to the material
    csRef<iShader> shader = shcom->CompileShader (shaderDoc->GetRoot ()->GetNode ("shader"));




    // Create a material using a shader to apply to the faces of the cube

    csRef<iTextureHandle> shadertex_handle = loader->LoadTexture("/lib/std/stone4.gif");
    if (!shadertex_handle.IsValid())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error loading '/lib/std/stone4.gif' as texture");
        return false;
    }
    
    csRef<iTextureWrapper> shadertex_wrapper = engine->GetTextureList()->NewTexture(shadertex_handle);
    if (!shadertex_wrapper.IsValid())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error adding '/lib/std/stone4.gif' texture to the engine texture list");
        return false;
    }

    csRef<iMaterial> shadermat = engine->CreateBaseMaterial(shadertex_wrapper);
    if (!shadermat.IsValid())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error creating material using '/lib/std/stone4.gif' as texture");
        return false;
    }

    // The shader uses a single texture, which must be specified as a ShaderVariable
    csRef<csShaderVariable> attributevar (csPtr<csShaderVariable> (
        new csShaderVariable (strings->Request ("tex diffuse"))));
    attributevar->SetValue (shadertex_wrapper->GetTextureHandle());
    shadermat->AddVariable (attributevar);  

    attributevar = csPtr<csShaderVariable> (new csShaderVariable(strings->Request("heightmap")));
    attributevar->SetValue (shadertex_wrapper->GetTextureHandle());
    shadermat->AddVariable (attributevar);  


    // Apply the shader to this material
    shadermat->SetShader(strings->Request ("OR compatibility"),shader);

    // Add this material to the engine's list of materials.   This creates a wrapper which the engine
    //  uses to access the material.
    csRef<iMaterialWrapper> shadermat_wrapper = engine->GetMaterialList ()->NewMaterial (shadermat, "shadermaterial");

    // Create the cube object
    csRef<iMeshWrapper> cube (engine->CreateThingMesh(the_room, "cube"));
    csRef<iThingState> cube_thingstate =
        SCF_QUERY_INTERFACE (cube->GetMeshObject (), iThingState);
    csRef<iThingFactoryState> cube_state = cube_thingstate->GetFactory ();

    cube_state->AddOutsideBox(csVector3(-1,4,-1),csVector3(1,6,1));

    cube_state->SetPolygonMaterial(CS_POLYRANGE_LAST,shadermat_wrapper);

    cube_state->SetPolygonTextureMapping(CS_POLYRANGE_LAST,3);
*/


    // Create a particle system
/*
    csRef<iMeshFactoryWrapper> ps_fact_wrapper = engine->CreateMeshFactory("crystalspace.mesh.object.particles","crystalspace.mesh.object.particles");
    if (!ps_fact_wrapper.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not create an iMeshFactoryWrapper for crystalspace.mesh.object.particles!");
        return false;
    }

    

    csRef<iMeshObjectFactory> ps_factory = ps_fact_wrapper->GetMeshObjectFactory();
    csRef<iParticlesFactoryState> ps_factory_state = SCF_QUERY_INTERFACE(ps_factory,iParticlesFactoryState);


    iMaterialWrapper* stone_wrapper = engine->GetMaterialList ()->FindByName ("stone");


    ps_factory_state->SetMaterial(stone_wrapper);
    ps_factory_state->SetAutoStart(true);
    ps_factory_state->SetParticlesPerSecond(15);
    ps_factory_state->SetPointEmitType();
    ps_factory_state->SetEmitTime(1000.0);



    csRef<iMeshObject> particle_system = ps_factory->NewInstance();

    csRef<iMeshWrapper> ps_wrapper = engine->CreateMeshWrapper(particle_system,"partsys",the_room,csVector3(0,5,0));


    PartSystemTracker *ps_track = new PartSystemTracker(particle_system,"Initial System");

    particle_systems.Push(ps_track);
    */

    // Load some default materials
    loader->LoadTexture ("raindrop", "/lib/std/raindrop.png");
    loader->LoadTexture ("portaloverlay", "/lib/std/portaloverlay.jpg");
    loader->LoadTexture ("snow", "/lib/std/snow.png");
    loader->LoadTexture ("spark", "/lib/std/spark.png");




    // Now we tell the engine that we've finished creating our world geometry and lights and that it should
    //  calculate its lightmaps and release any interim loading formats.
    engine->Prepare ();

    /*
    testarrow=new DirectionArrow(object_reg,the_room,csVector3(0,5,0),csVector3(0,1,1),0.1f);
    if (!testarrow->Initialize())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error creating test arrow!");
        return false;
    }
    CoordinateArrows *coord_arrows=new CoordinateArrows(object_reg,the_room,csVector3(0,5,0),csVector3(0,1,0),csVector3(1,0,0),1.0f,0.1f);
    if (!coord_arrows->Initialize())
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error creating coordinate arrows!");
        return false;
    }
    */




    resource_manager->Material_AddAllMaterialsInEngine();

    matlistgui=(guiMaterialList *)paws->FindWidget("materiallist");
    if (matlistgui)
        matlistgui->UpdateMaterialList();

    menubar=(guiMenuBar *)paws->FindWidget("menubar");
    pslistgui=(guiPSList *)paws->FindWidget("particlesystems");

//    resource_manager->LoadLibraryFile("/this/data/partview/libs/test","material_library.xml");


    NewParticleSystem("Initial System0",csVector3(0,5,0));


    // Initialized ok!
    return true;
}


/// The main application runloop
void PartViewApp::Run()
{
    // We use the CS runloop entirely
    csDefaultRunLoop(object_reg);
}

bool PartViewApp::CreateDefaultRoom()
{
    ////
    //  The room we're creating will be a box from -5, 0 , -5 to 5, 20, 5.
    //    A cube would be harder to distiguish orientation in.
    //
    ////

    // We use a convenience function to create an empty mesh that has certain parameters set appropriately for a bounding box
    // (the outer walls of the room).  This mesh is set to zbuffer fill and draw with the 'wall' priority which ensures it 
    // will be rendered in the correct order.
    csRef<iMeshWrapper> walls (engine->CreateSectorWallsMesh (the_room, "outer_walls"));

    // CreateSectorWallsMesh creates a mesh using the ambiguously named 'thing' plugin.
    // Thing meshes and genmeshes are two types of general purpose meshes used in Crystal Space.  Each has its own
    //  limitations, but thing meshes are supposed to be more flexible.
    // Here we use the SCF_QUERY_INTERFACE() macro to safely retrieve the iThingState interface from the meshobject retrieved from the meshwrapper returned above.
    csRef<iThingState> walls_thingstate =
        SCF_QUERY_INTERFACE (walls->GetMeshObject (), iThingState);
    // Now we can use the Thing state to get the factory.  Although we have created an instance of a mesh, the mesh has no polygons.
    //  Each instance of a mesh shares its geometry data with all other instances of the same mesh.  This data is stored in the 'factory'
    //  which is named such because you can generate new instances from the factory at any time.
    // For our purposes, we need to add some polygons (the outer walls) so we first have to get access to the factory.
    csRef<iThingFactoryState> walls_state = walls_thingstate->GetFactory ();


    // iThingFactoryState::AddInsideBox is a convenience function that creates a box with all 6 sides facing inward.
    //  Here we specify the minimum and maximum dimensions.
    walls_state->AddInsideBox (csVector3 (-5, 0, -5), csVector3 (5, 20, 5));


    // We've defined the box, facing inward, but a texture needs to be applied to the surfaces or else we wont really see anything.

    // Load a texture to apply to the inside of our room
    // We'll load it from the standard library included with CS in data/standard.zip - which is mounted at /lib/std/ 
    // We'll load a stone texture named stone4.gif and call it 'stone'
    // In this case we don't want to do anything fancy with the material, so we'll use the form of LoadTexture that creates a material wrapper
    //  for us and registers it automatically.
    if (!loader->LoadTexture ("stone", "/lib/std/stone4.gif"))
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::Initialize()","Error loading '/lib/std/stone4.gif' as texture/material 'stone'");
        return false;
    }


    // We'll retrieve a pointer to the interface for the material wrapper to assign to the wall polygons
    iMaterialWrapper* stone_wrapper = engine->GetMaterialList ()->FindByName ("stone");
    
    // The first parameter of iThingFactoryState::SetPolygonMaterial is a range of polygons which will be affected.
    //  CS_POLYRANGE_LAST is a macro encompasing all polygons.
    walls_state->SetPolygonMaterial (CS_POLYRANGE_LAST, stone_wrapper);

    // This form of SetPolygonTextureMapping performs implicit mapping of the texture across the polygons.
    //  The method is better described in the interface of iThingFactoryState.  Briefly, the texture will
    //  be repeated approximated 3 times over each polygon.
    walls_state->SetPolygonTextureMapping (CS_POLYRANGE_LAST, 3);
    


    // We need some lights to be able to see anything.  Add a few:

    // We'll reuse this for creating each light
    csRef<iLight> light;

    // A lightlist is a simple list of lights.  In this case we'll start out with the complete list of
    //  lights in our sector (empty list) and add our lights to that list.
    // This doesn't actually make the lights affect the objects in the room yet.
    iLightList* ll = the_room->GetLights ();

    // Unnamed light, at -3,5,10 with 10 light radius and red color
    light = engine->CreateLight (0, csVector3 (-3, 5, 0), 10,
        csColor (1, 0, 0));
    ll->Add (light);

    // Unnamed light, at 3,5,0 with 10 light radius and blue color
    light = engine->CreateLight (0, csVector3 (3, 5,  0), 10,
        csColor (0, 0, 1));
    ll->Add (light);

    // Unnamed light, at 0,5,-3 with 10 light radius and green color
    light = engine->CreateLight (0, csVector3 (0, 5, -3), 10,
        csColor (0, 1, 0));
    ll->Add (light);

    return true;
}



bool PartViewApp::LoadMapFileAsRoom(const char *mapfile)
{
    // Not fully implemented yet
    if (!mapfile)
        return false;
    if (strlen(mapfile) > 4 && !strcmp(mapfile+strlen(mapfile)-4,".zip"))
    {
        csRef<iDataBuffer> realpath=vfs->GetRealPath(mapfile);
        vfs->Unmount("/map/",NULL);
        if (vfs->Mount("/map/",realpath->GetData()))
        {
            int i;
            csRef<iStringArray> files;

#ifdef _RPTF0
            _RPTF0(_CRT_WARN,"Mount succeeded, iterating files...\n");
#endif

            vfs->ChDir("/map/");
            files=vfs->FindFiles("/map/");
            for (i=0;i<files->Length();i++)
            {
#ifdef _RPTF1
                _RPTF1(_CRT_WARN,"Found file:%s\n",files->Get(i));
#endif
            }
            
            return loader->LoadMapFile("/map/world");
        }
    }
    return loader->LoadMapFile(mapfile);
}


PartSystemTracker *PartViewApp::NewParticleSystem(const char *system_name,csVector3 position)
{
   // Create a particle system
    csRef<iMeshFactoryWrapper> ps_fact_wrapper = engine->CreateMeshFactory("crystalspace.mesh.object.particles",system_name);
    if (!ps_fact_wrapper.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::NewParticleSystem()","Could not create an iMeshFactoryWrapper for %s!",system_name);
        return NULL;
    }

    
    csRef<iMeshObjectFactory> ps_factory = ps_fact_wrapper->GetMeshObjectFactory();
    csRef<iParticlesFactoryState> ps_factory_state = SCF_QUERY_INTERFACE(ps_factory,iParticlesFactoryState);


    iMaterialWrapper* mat_wrapper = engine->GetMaterialList ()->FindByName ("stone");
    ps_factory_state->SetMaterial(mat_wrapper);

    ps_factory_state->SetAutoStart(true);
    ps_factory_state->SetParticlesPerSecond(15);
    ps_factory_state->SetPointEmitType();
    ps_factory_state->SetEmitTime(1000.0);

    csRef<iMeshObject> particle_system = ps_factory->NewInstance();
    csRef<iMeshWrapper> ps_wrapper = engine->CreateMeshWrapper(particle_system,system_name,the_room,position);


    PartSystemTracker *ps_track = new PartSystemTracker(ps_wrapper,system_name,ps_fact_wrapper);
    ps_track->ShowOrientationArrows(true);


    if (pslistgui)
        pslistgui->AddParticleSystemInterface(ps_track);

    if (menubar && pslistgui)
        menubar->RefreshPSCount(pslistgui->GetParticleSystemCount());

    return ps_track;
}

void PartViewApp::PointFromCenterOfCamera(float distance,csVector3 &world_point)
{
    csRef<iCamera> camera=view->GetCamera();
    csVector3 camera_unitclick;

    if (!camera)
    {
        world_point.Set(0.0f,0.0f,0.0f);
        return;
    }
    csVector2 perspective(camera->GetShiftX(),camera->GetShiftY());
    camera->InvPerspective(perspective,distance,camera_unitclick);
    world_point=camera->GetTransform().This2Other(camera_unitclick);
}

PartSystemTracker *PartViewApp::NewParticleSystemAtCamera(const char *system_name)
{
    csVector3 world_unitclick;

    PointFromCenterOfCamera(1.0f,world_unitclick);

    // Create a new particle system 1 unit out from the camera
    return NewParticleSystem(system_name,world_unitclick);
}

void PartViewApp::RemoveParticleSystem(PartSystemTracker *system)
{
    if (pslistgui)
        pslistgui->RemoveParticleSystemInterface(system);
    delete system;
}




//
// This is a static callback wrapper for the event handler.
//
// Callbacks must be static, so this uses a global pointer to a single instance of the MainApp class
//  to redirect events to the class member function.
//
//
bool PartViewApp::StaticEventHandler(iEvent& ev)
{
    // If the pointer is not valid, don't crash 
    if (g_pApp)
        return g_pApp->EventHandler(ev);
    else
    {
#ifdef _RPTF0
        _RPTF0(_CRT_WARN,"PartViewApp::StaticEventHandler() - Warning! Static PartViewApp::g_pApp is NULL!\n");
#endif
        return false;
    }
}


bool PartViewApp::EventHandler(iEvent &ev)
{
    // Give PAWS the first shot at handling events.
    if (paws->HandleEvent(ev))
        return true;

    switch(ev.Type)
    {
        case csevBroadcast:
            switch (ev.Command.Code)
            {
                case cscmdProcess:
                    // Only spend time drawing if the window is visible
                    if (window_visible)
                    {
                        // cscmdProcess indicates the begining of processing for a frame
                        StartFrame ();
                    }
                    // Return true for handled
                    return true;
                case cscmdFinalProcess:
                    FinishFrame();
                    return true;
                case cscmdPostProcess:
                {
                    if (window_visible)
                    {
                        // Draw PAWS interface only if the window is visible
                        g3d->BeginDraw(engine->GetBeginDrawFlags() | CSDRAW_2DGRAPHICS);
                        paws->Draw();
                    }
                    else
                    {
                        csSleep(150);
                    }
                    break;
                }
                case cscmdCanvasHidden:
                    // The window was hidden
                    window_visible=false;
                    break;
                case cscmdCanvasExposed:
                    // The window was made visible
                    window_visible=true;
                    break;
            }
            break;
        case csevMouseDown:
            switch (ev.Mouse.Button)
            {
                case 1:
                    if (!mouse_left_down)
                    {
                        // Start grid selection
                        
                        csVector3 pt,vec;
                        unsigned int x,z;
                        ScreenToWorld(ev.Mouse.x,ev.Mouse.y,vec,pt);
//                        csReport(object_reg, CS_REPORTER_SEVERITY_WARNING,"MainApp - Mouseclick","World coords: (pt)x=%f y=%f z=%f  (vec) x=%f y=%f z=%f",
//                            pt.x,pt.y,pt.z,vec.x,vec.y,vec.z);

                    }
                    mouse_left_down=true;
                    break;
                case 2:
                    if (!mouse_right_down)
                    {
                        // Start height adjustment
                        last_mouseright_y=ev.Mouse.y;
                    }
                    mouse_right_down=true;
                    break;
                default:
                    break;
            }
            break;
        case csevMouseUp:
            switch (ev.Mouse.Button)
            {
                case 1:
                    if (mouse_left_down)
                    {
                        // End grid selection
                    }
                    mouse_left_down=false;
                    break;
                case 2:
                    if (mouse_right_down)
                    {
                        // End height adjustment
                    }
                    mouse_right_down=false;
                    break;
                default:
                    break;
            }
            break;
        case csevMouseMove:
            if (mouse_left_down)
            {
                // Continue grid selection
                csVector3 pt,vec;
                unsigned int x,z;
                ScreenToWorld(ev.Mouse.x,ev.Mouse.y,vec,pt);
            }
            if (mouse_right_down)
            {
            }
            break;
        case csevKeyboard:
            if (csKeyEventHelper::GetEventType (&ev) == csKeyEventTypeDown &&
                (csKeyEventHelper::GetCookedCode (&ev) == CSKEY_ESC))
            {
                // Handle the ESC key to quit the application
                csRef<iEventQueue> q (CS_QUERY_REGISTRY (object_reg, iEventQueue));
                if (q) q->GetEventOutlet()->Broadcast (cscmdQuit);
                // Return true for handled
                return true;
            }
            break;
    }
    return false;
}


//
// Drawing occurs in what are called 'frames'.  One frame is one image displayed to the screen - usually considered a single point in time
//  StartFrame() performs setup that needs to be done at the begining of drawing a frame.
//  FinishFrame() performs steps to complete drawing the frame

void PartViewApp::StartFrame ()
{
  // First get elapsed time from the virtual clock.
  csTicks elapsed_time = vc->GetElapsedTicks ();

  // Perform any timed processing that the app needs.  This is done only once per frame
  // on the assumption that once per frame is the fastest anything is output, so it's likely the most frequently
  // things will need processing
  TimedProcessing(elapsed_time);

  // Now rotate the camera according to keyboard state
  float speed = (elapsed_time / 1000.0) * (0.06 * 20);

  iCamera* c = view->GetCamera();

  if (kbd->GetKeyState (CSKEY_SHIFT))
  {
    // If the user is holding down shift, the arrow keys will cause
    // the camera to strafe up, down, left or right from it's
    // current position.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      c->Move (CS_VEC_RIGHT * 4 * speed);
    if (kbd->GetKeyState (CSKEY_LEFT))
      c->Move (CS_VEC_LEFT * 4 * speed);
    if (kbd->GetKeyState (CSKEY_UP))
      c->Move (CS_VEC_UP * 4 * speed);
    if (kbd->GetKeyState (CSKEY_DOWN))
      c->Move (CS_VEC_DOWN * 4 * speed);
  }
  else
  {
    // left and right cause the camera to rotate on the global Y
    // axis; page up and page down cause the camera to rotate on the
    // _camera's_ X axis (more on this in a second) and up and down
    // arrows cause the camera to go forwards and backwards.
    if (kbd->GetKeyState (CSKEY_RIGHT))
      rotY += speed;
    if (kbd->GetKeyState (CSKEY_LEFT))
        rotY -= speed;
    if (kbd->GetKeyState (CSKEY_PGUP))
        rotX += speed;
    if (kbd->GetKeyState (CSKEY_PGDN))
        rotX -= speed;
    if (kbd->GetKeyState (CSKEY_UP))
        c->Move (CS_VEC_FORWARD * 4 * speed);
    if (kbd->GetKeyState (CSKEY_DOWN))
        c->Move (CS_VEC_BACKWARD * 4 * speed);
  }

  // We now assign a new rotation transformation to the camera.  You
  // can think of the rotation this way: starting from the zero
  // position, you first rotate "rotY" radians on your Y axis to get
  // the first rotation.  From there you rotate "rotX" radians on the
  // your X axis to get the final rotation.  We multiply the
  // individual rotations on each axis together to get a single
  // rotation matrix.  The rotations are applied in right to left
  // order .
  csMatrix3 rot = csXRotMatrix3 (rotX) * csYRotMatrix3 (rotY);
  csOrthoTransform ot (rot, c->GetTransform().GetOrigin ());
  c->SetTransform (ot);


  // Tell 3D driver we're going to display 3D things.
  if (!g3d->BeginDraw (engine->GetBeginDrawFlags () | CSDRAW_3DGRAPHICS))
    return;

  // Tell the camera to render into the frame buffer.
  view->Draw ();
}


void PartViewApp::FinishFrame ()
{
    // Tell the 3d rendering layer we are done drawing
    g3d->FinishDraw ();
    // And tell it to put the total results of drawing out to the screen
    g3d->Print (0);
}

bool PartViewApp::RegisterPawsFactories()
{
    pawsWidgetFactory* factory;
    
    // register the factories of all other custom made paws widgets here
    factory = new guiMenuBarFactory(paws);
    factory = new guiMaterialListFactory(paws);
    factory = new pawsScrollPaneFactory(paws);
    factory = new pawsCollapsableWidgetFactory(paws);

    factory = new guiParticleSystemFactory(paws);
    factory = new guiPSListFactory(paws);
    factory = new guiColorRowFactory(paws);


    return true;
}

bool PartViewApp::LoadPawsWidgets()
{
    bool success=true;
    
    success=success & TryLoadPawsWidget("data/partview/menubar.xml");
    success=success & TryLoadPawsWidget("data/partview/materiallist.xml");
    success=success & TryLoadPawsWidget("data/partview/pslist.xml");



    // load all other custom made paws widgets here
    return success;
}

bool PartViewApp::TryLoadPawsWidget(const char *xml_filename)
{
    if (!paws->LoadWidget(xml_filename))
    {
        csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,"PartViewApp::TryLoadPawsWidget()","Error loading widget '%s'",xml_filename);
        return false;
    }
    return true;
}


void PartViewApp::ScreenToWorld(int x, int y,csVector3 &click_vector,csVector3 &camerapos)
{
    // Converts the location of the mouse pointer into a point and vector in world space
    csRef<iCamera> camera=view->GetCamera();
    csVector3 camera_unitclick,world_unitclick;

    if (!camera.IsValid())
        return;

    csVector2 perspective(x,camera->GetShiftY()*2-y);
    camera->InvPerspective(perspective,1.0f,camera_unitclick);
    world_unitclick=camera->GetTransform().This2Other(camera_unitclick);

    camerapos=camera->GetTransform().GetO2TTranslation();
    click_vector=world_unitclick - camerapos;
}


void PartViewApp::TimedProcessing(csTicks elapsed)
{
    // Check the paws GUI for outstanding load or save requests if need be
//    if (gui_menu_widget)
//        gui_menu_widget->ProcessTimedEvents(elapsed);
//    if (broadgui)
//        broadgui->MatchSelectedSystemRunningState();
    if (matlistgui)
        matlistgui->DoTimedChecks();
    if (menubar)
        menubar->DoTimedChecks();
    if (pslistgui)
        pslistgui->DoTimedChecks(elapsed);
}

bool PartViewApp::LoadParticleSystem(const char *filename)
{
    csRef<iFile> ps_file;
    csRef<iDocument> ps_document;
    csRef<iDocumentNode> ps_rootnode,ps_meshfactnode,meshfact_pluginnode,ps_params;
    csRef<iDocumentNodeIterator> ps_nodeit,ps_paramit;
    int loaded_systems=0;
    csString system_name;
    static int global_load_count=0;

    if (!particlesloader)
        return false;

    ps_file=vfs->Open(filename,VFS_FILE_READ);
    if (!ps_file)
    {
        // TODO: Display a notice about failure
        return false;
    }
    csRef<iDocumentSystem> docsys (CS_QUERY_REGISTRY(object_reg, iDocumentSystem));
    if (!docsys.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iDocumentSystem interface in the available plugins!");
        return false;
    }

    ps_document=docsys->CreateDocument();
    ps_document->Parse(ps_file);

    ps_rootnode=ps_document->GetRoot();
    ps_nodeit=ps_rootnode->GetNodes("meshfact");


    ps_nodeit=ps_rootnode->GetNodes("meshfact");
    while (ps_nodeit->HasNext())
    {
        ps_meshfactnode=ps_nodeit->Next();
        meshfact_pluginnode=ps_meshfactnode->GetNode("plugin");
        // No plugin specified
        if (!meshfact_pluginnode)
            continue;
        // Not using the plugin we support
        if (!meshfact_pluginnode->GetContentsValue() ||
            strcmp("crystalspace.mesh.loader.factory.particles",meshfact_pluginnode->GetContentsValue()))
            continue;

        csRef<iDocumentAttribute> attrib=ps_meshfactnode->GetAttribute("name");
        if (attrib)
            system_name=attrib->GetValue();
        else
            system_name.Format("Loaded System%d",global_load_count++);

        ps_params=ps_meshfactnode->GetNode("params");
        if (!ps_params)
            continue;

        

        csRef<iLoaderContext> ldr_context = engine->CreateLoaderContext();
        csRef<iBase> fact_ibase=particlesloader->Parse(ps_params,ldr_context,NULL);
        if (!fact_ibase)
            continue;


        csRef<iMeshObjectFactory> ps_fact = SCF_QUERY_INTERFACE(fact_ibase,iMeshObjectFactory);
        if (!ps_fact)
            continue;

        csRef<iMeshFactoryWrapper> ps_fact_wrapper = engine->CreateMeshFactory(ps_fact,system_name);
        if (!ps_fact_wrapper)
            continue;
    
        csRef<iMeshObjectFactory> ps_factory = ps_fact_wrapper->GetMeshObjectFactory();
        csRef<iParticlesFactoryState> ps_factory_state = SCF_QUERY_INTERFACE(ps_factory,iParticlesFactoryState);


        csRef<iMeshObject> particle_system = ps_factory->NewInstance();
        if (!particle_system->GetMaterialWrapper())
        {
            // Factory did not load a default material, assign one
            iMaterialWrapper* mat_wrapper = engine->GetMaterialList ()->FindByName ("stone");
            ps_factory_state->SetMaterial(mat_wrapper);
            particle_system->SetMaterialWrapper(mat_wrapper);
        }

        csVector3 position;
        PointFromCenterOfCamera(1.0f,position);
        csRef<iMeshWrapper> ps_wrapper = engine->CreateMeshWrapper(particle_system,system_name,the_room,position);


        PartSystemTracker *ps_track = new PartSystemTracker(ps_wrapper,system_name,ps_fact_wrapper);
        ps_track->ShowOrientationArrows(true);

        if (pslistgui)
            pslistgui->AddParticleSystemInterface(ps_track);

        loaded_systems++;
    }
    if (loaded_systems>0)
    {
        if (menubar && pslistgui)
            menubar->RefreshPSCount(pslistgui->GetParticleSystemCount());
        return true;
    }

    return false;
}

bool PartViewApp::SaveParticleSystem(PartSystemTracker *ps_track, const char *filename)
{
    // TODO: vfs->Exists() confirm popup
    csRef<iFile> ps_file;
    csRef<iDocument> ps_document;
    csRef<iDocumentNode> ps_rootnode,ps_meshfactnode,meshfact_pluginnode,ps_params,work_node,node_text;
    csRef<iDocumentNodeIterator> ps_nodeit,ps_paramit;
    iMeshObject *ps_meshobj;
    csRef<iParticlesObjectState> ps_obj_state;

    if (!ps_track)
        return false;

    ps_meshobj=ps_track->particle_system_wrapper->GetMeshObject();
    if (!ps_meshobj)
        return false;

    ps_obj_state=SCF_QUERY_INTERFACE(ps_meshobj,iParticlesObjectState);
    if (!ps_obj_state)
        return false;


    ps_file=vfs->Open(filename,VFS_FILE_WRITE);
    if (!ps_file)
    {
        // TODO: Display a notice about failure
        return false;
    }
    csRef<iDocumentSystem> docsys (CS_QUERY_REGISTRY(object_reg, iDocumentSystem));
    if (!docsys.IsValid())
    {
        csReport(object_reg,CS_REPORTER_SEVERITY_ERROR, "PartViewApp::Initialize()","Could not find an iDocumentSystem interface in the available plugins!");
        return false;
    }

    ps_document=docsys->CreateDocument();


    ps_rootnode=ps_document->CreateRoot();
    if (!ps_rootnode)
        return false;
    ps_meshfactnode=ps_rootnode->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!ps_meshfactnode)
        return false;
    ps_meshfactnode->SetValue("meshfact");
    ps_meshfactnode->SetAttribute("name",ps_track->name);
    meshfact_pluginnode=ps_meshfactnode->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!meshfact_pluginnode)
        return false;
    meshfact_pluginnode->SetValue("plugin");
    work_node=meshfact_pluginnode->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!work_node)
        return false;
    work_node->SetValue("crystalspace.mesh.loader.factory.particles");
    
    ps_params=ps_meshfactnode->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!ps_params)
        return false;
    ps_params->SetValue("params");
    // Write emitter
    {
        csRef<iDocumentNode> node_emit,node_force;
        node_emit=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!node_emit)
            return false;
        node_emit->SetValue("emitter");
        switch (ps_obj_state->GetEmitType())
        {
            case CS_PART_EMIT_SPHERE:
                node_emit->SetAttribute("type","sphere");
                // time
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("time");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetEmitTime());
                // innerradius
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("innerradius");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetSphereEmitInnerRadius());
                // outerradius
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("outerradius");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetSphereEmitOuterRadius());
            break;
            case CS_PART_EMIT_PLANE:
                node_emit->SetAttribute("type","plane");
                // time
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("time");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetEmitTime());
                // size
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("size");
                work_node->SetAttributeAsFloat("x",ps_obj_state->GetEmitXSize());
                work_node->SetAttributeAsFloat("y",ps_obj_state->GetEmitYSize());
            break;
            case CS_PART_EMIT_BOX:
                node_emit->SetAttribute("type","box");
                // time
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("time");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetEmitTime());
                // size
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("size");
                work_node->SetAttributeAsFloat("x",ps_obj_state->GetEmitXSize());
                work_node->SetAttributeAsFloat("y",ps_obj_state->GetEmitYSize());
                work_node->SetAttributeAsFloat("z",ps_obj_state->GetEmitZSize());
            break;
            case CS_PART_EMIT_CYLINDER:
                node_emit->SetAttribute("type","cylinder");
                // time
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("time");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetEmitTime());
                // size
                work_node=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("size");
                // x_size and y_size are used for radius and height
                work_node->SetAttributeAsFloat("x",ps_obj_state->GetEmitXSize());
                work_node->SetAttributeAsFloat("y",ps_obj_state->GetEmitYSize());
            break;
            default:
                // Unsupported emitter type
                return false;
            break;
        }
        // Write force
        csParticleFalloffType falloff_force,falloff_cone;
        ps_obj_state->GetFalloffType(falloff_force,falloff_cone);

        node_force=node_emit->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!node_force)
            return false;
        // Force amount
        work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!work_node)
            return false;
        work_node->SetValue("amount");
        node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
        if (!node_text)
            return false;
        node_text->SetValueAsFloat(ps_obj_state->GetForce());
        // range
        work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!work_node)
            return false;
        work_node->SetValue("range");
        node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
        if (!node_text)
            return false;
        node_text->SetValueAsFloat(ps_obj_state->GetForceRange());
        // Falloff
        work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!work_node)
            return false;
        work_node->SetValue("falloff");
        node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
        if (!node_text)
            return false;
        switch (falloff_force)
        {
            case CS_PART_FALLOFF_CONSTANT:
                node_text->SetValue("constant");
            break;
            case CS_PART_FALLOFF_LINEAR:
                node_text->SetValue("linear");
            break;
            case CS_PART_FALLOFF_PARABOLIC:
                node_text->SetValue("parabolic");
            break;
            default:
                return false;
            break;
        }
        node_force->SetValue("force");

        switch (ps_obj_state->GetForceType())
        {
            case CS_PART_FORCE_RADIAL:
                node_force->SetAttribute("type","radial");
                // No additional properties
            break;
            case CS_PART_FORCE_LINEAR:
            {
                csVector3 direction;
                node_force->SetAttribute("type","linear");
                ps_obj_state->GetForceDirection(direction);
                work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("direction");
                work_node->SetAttributeAsFloat("x",direction.x);
                work_node->SetAttributeAsFloat("y",direction.y);
                work_node->SetAttributeAsFloat("z",direction.z);
            }
            break;
            case CS_PART_FORCE_CONE:
            {
                csVector3 direction;
                node_force->SetAttribute("type","cone");
                ps_obj_state->GetForceDirection(direction);
                work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("direction");
                work_node->SetAttributeAsFloat("x",direction.x);
                work_node->SetAttributeAsFloat("y",direction.y);
                work_node->SetAttributeAsFloat("z",direction.z);

                work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("coneradius");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetForceConeRadius());


                work_node=node_force->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("conefalloff");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                switch (falloff_cone)
                {
                    case CS_PART_FALLOFF_CONSTANT:
                        node_text->SetValue("constant");
                    break;
                    case CS_PART_FALLOFF_LINEAR:
                        node_text->SetValue("linear");
                    break;
                    case CS_PART_FALLOFF_PARABOLIC:
                        node_text->SetValue("parabolic");
                    break;
                    default:
                        return false;
                    break;
                }
            }
            break;
            default:
                // Unsupported force type
                return false;
            break;
        } // Force done
    } // Emitter done

    // dampener
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("dampener");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetDampener());

    // mass
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("mass");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetMass());

    // massvariation
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("massvariation");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetMassVariation());
    
    // particles per second
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("pps");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsInt(ps_obj_state->GetParticlesPerSecond());

    // initial particles
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("initial");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsInt(ps_obj_state->GetInitialParticleCount());

    // gravity
    {
        csVector3 gravity;
        ps_obj_state->GetGravity(gravity);

        work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!work_node)
            return false;
        work_node->SetValue("gravity");
        work_node->SetAttributeAsFloat("x",gravity.x);
        work_node->SetAttributeAsFloat("y",gravity.y);
        work_node->SetAttributeAsFloat("z",gravity.z);
    }

    // diffusion
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("diffusion");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetDiffusion());

    // particle radius
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("radius");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetParticleRadius());

    // particle lifetime
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("ttl");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetTimeToLive());

    // particle lifetime variation
    work_node=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
    if (!work_node)
        return false;
    work_node->SetValue("timevariation");
    node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
    if (!node_text)
        return false;
    node_text->SetValueAsFloat(ps_obj_state->GetTimeVariation());

    // Color method
    {
        csRef<iDocumentNode> node_color;
        node_color=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!node_color)
            return false;
        node_color->SetValue("colormethod");
        switch (ps_obj_state->GetParticleColorMethod())
        {
            case CS_PART_COLOR_CONSTANT:
            {
        csColor4 color;
                ps_obj_state->GetConstantColor(color);

                node_color->SetAttribute("type","constant");
                work_node=node_color->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("color");
                work_node->SetAttributeAsFloat("red",color.red);
                work_node->SetAttributeAsFloat("green",color.green);
                work_node->SetAttributeAsFloat("blue",color.blue);
            }
            break;
            case CS_PART_COLOR_LINEAR:
            {
                csRef<iDocumentNode> node_gradient;
                int c_i,c_l;
                csArray<csColor4> gradient_colors=ps_obj_state->GetGradient();
                c_l=gradient_colors.Length();

                node_color->SetAttribute("type","linear");

                node_gradient=node_color->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!node_gradient)
                    return false;
                node_gradient->SetValue("gradient");
                for (c_i=0;c_i<c_l;c_i++)
                {
                    work_node=node_gradient->CreateNodeBefore(CS_NODE_ELEMENT,0);
                    if (!work_node)
                        return false;
                    work_node->SetValue("color");
                    work_node->SetAttributeAsFloat("red",gradient_colors[c_i].red);
                    work_node->SetAttributeAsFloat("green",gradient_colors[c_i].green);
                    work_node->SetAttributeAsFloat("blue",gradient_colors[c_i].blue);
                }
            }
            break;
            case CS_PART_COLOR_LOOPING:
            {
                csRef<iDocumentNode> node_gradient;
                int c_i,c_l;
                csArray<csColor4> gradient_colors=ps_obj_state->GetGradient();
                c_l=gradient_colors.Length();

                node_color->SetAttribute("type","looping");

                work_node->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetColorLoopTime());

                node_gradient=node_color->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!node_gradient)
                    return false;
                node_gradient->SetValue("gradient");
                for (c_i=0;c_i<c_l;c_i++)
                {
                    work_node=node_gradient->CreateNodeBefore(CS_NODE_ELEMENT,0);
                    if (!work_node)
                        return false;
                    work_node->SetValue("color");
                    work_node->SetAttributeAsFloat("red",gradient_colors[c_i].red);
                    work_node->SetAttributeAsFloat("green",gradient_colors[c_i].green);
                    work_node->SetAttributeAsFloat("blue",gradient_colors[c_i].blue);
                }
            }
            break;
            case CS_PART_COLOR_HEAT:
                node_color->SetAttribute("type","heat");
                work_node=node_color->CreateNodeBefore(CS_NODE_ELEMENT,0);
                if (!work_node)
                    return false;
                work_node->SetValue("temp");
                node_text=work_node->CreateNodeBefore(CS_NODE_TEXT,0);
                if (!node_text)
                    return false;
                node_text->SetValueAsFloat(ps_obj_state->GetBaseHeat());
            break;
            default:
                // Cannot save callback color methods... how did you set a callback in this app anyway?!
                return false;
            break;
        }
    } // Color Method done

    // Material selection
    {
        iObject *mat_obj;
        iMeshObject *ps_obj;
        iMaterialWrapper *ps_mat_wrapper;
        const char *mat_name;
        csRef<iDocumentNode> node_material;
        node_material=ps_params->CreateNodeBefore(CS_NODE_ELEMENT,0);
        if (!node_material)
            return false;
        node_material->SetValue("material");

        ps_obj=ps_track->particle_system_wrapper->GetMeshObject();
        if (!ps_obj)
            return false;
        ps_mat_wrapper=ps_obj->GetMaterialWrapper();
        if (!ps_mat_wrapper)
            return false;

        mat_obj=ps_mat_wrapper->QueryObject();
        if (!mat_obj)
            return false;
        mat_name=mat_obj->GetName();
        if (!mat_name)
            return false;

        work_node=node_material->CreateNodeBefore(CS_NODE_TEXT,0);
        if (!work_node)
            return false;
        work_node->SetValue(mat_name);
    }



    return (ps_document->Write(ps_file)==0);
}




PartSystemTracker::PartSystemTracker(csRef<iMeshWrapper> psystem_wrapper,const char *pname,csRef<iMeshFactoryWrapper> psfactory) :
    name(pname), particle_system_wrapper(psystem_wrapper), ps_factory_wrapper(psfactory) 
{
    system_stopping=false;
    show_arrows=false;
    orientation_arrows=NULL;
}

PartSystemTracker::~PartSystemTracker() 
{
    csRef<iEngine> engine = CS_QUERY_REGISTRY (PartViewApp::g_pApp->object_reg, iEngine);
    if (engine)
    {
        engine->RemoveObject(particle_system_wrapper);
        engine->RemoveObject(ps_factory_wrapper);
    }
    particle_system_wrapper=NULL;
    ps_factory_wrapper=NULL;
    delete orientation_arrows;
}

void PartSystemTracker::ShowOrientationArrows(bool show)
{
    if (!particle_system_wrapper)
        return;

    if (!orientation_arrows)
    {
        iMovable *movable;
        iSector *existing_sector;
        iSectorList *sec_list;
        csVector3 system_pos;
        csVector3 x_vec,y_vec;
        csReversibleTransform movable_trans;
        csMatrix3 t2o;

        movable=particle_system_wrapper->GetMovable();
        if (!movable)
            return;

        sec_list=movable->GetSectors();
        if (sec_list->GetCount() < 1)
            return;
        existing_sector=sec_list->Get(0);

        system_pos=movable->GetPosition();
        movable_trans=movable->GetTransform();
        t2o=movable_trans.GetT2O();
        x_vec=t2o.Col1();
        y_vec=t2o.Col2();

        orientation_arrows=new CoordinateArrows(PartViewApp::g_pApp->object_reg,existing_sector,system_pos,y_vec,x_vec,0.5f,0.2f);
        if (!orientation_arrows || !orientation_arrows->Initialize())
            return;

        movable->AddListener(orientation_arrows);
    }
    
    if (show)
        orientation_arrows->Show();
    else
        orientation_arrows->Hide();

    show_arrows=show;
}

bool PartSystemTracker::OrientationArrowsVisible()
{
    return show_arrows;
}


