Midtown Madness2 road plugin

ZModeler2 Software Development Kit forum. Programming questions and answers.

Moderator: Oleg

Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Midtown Madness2 road plugin

Post by Midas »

Hi Oleg and other members of this forum

For ZModeler 1 series I made a plugin which generates roads from splines and exports them to the Midtown Madness 2 PSDL format. This works very well so far.

What I would like to do next is to make a renderer which will render the roads like they would look after the export. Preferably this would happen in realtime, while drawing the splines. I will also need a toolbox which lists the splines and highlights them in the 3D views when you select them from the list. Can I do these things with the ZModeler SDK?

Since splines aren't fully documented and I didn't really understand the rendering functions, I was hoping to get a reply here..

Thanks in advance

Midas
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Making a render plugin

Post by Oleg »

Hi.

Unfortunately, I don't have any materials on ZModeler1 beside the backup copy of SDK, so I can't be of much help on a subject. ZModeler 1 is pretty old and very limited in terms of addons and their features. I can't remember how it draws scene in viewports, but if i'm not mistaken, it was possible to assign custom renderer onto an object. E.g. the "probe.z3d" sample file has a "smoke generator" assigned onto a dummy node. But once again, I don't have any source code on any related materials. :(
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Making a render plugin

Post by Midas »

Thanks for your quick reply, Oleg.

Do you think I can do this in ZModeler 2 instead? I actually got ZModeler 2 running for the first time just now (couldn't get it working before). The spline editing seems to be much better in it than version 1, also with bezier curves. It would be nice to get that to work with the road generator too.

Can I ask you a favor, and give me a quick start of the features I'm gonna need to implement if I make this as a ZModeler 2 plugin? If you know of similar plugins, that would be useful too.

Thanks,

Midas

Image
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Making a render plugin

Post by Oleg »

Sure, zmodeler2 is way more preferable to deal with. It's core and overal workflow is very alike to ZModeler3 that I currently working on, so I could be of much help in context of ZModeler2.

As I see, the SDK is for version 2.2.4 while the most recently released is 2.2.6. I guess you can create and test plugin on version 2.2.4 (shipped with SDK) and later put it into 2.2.6, it should work too.

There are projects+source for some original plugins available, you should be able to compile them prior to start your own plugin (just to ensure you have all required libraries and headers).

ZModeler2 is based on interfaces and their implementation. It's very-very alike to COM/ATL but resides on my own simplified implementation hidden inside macros.

Concerning your goal, I would recommend to do it in several steps:

1. create a plugin that will export a "tool" (say "Road Service") resided in a floater window (Source\Services\NodesService\Browser.* is something very similar: a tree control inside a floater, you will have a list control and, probably, some buttons).
2. subscribe your RoadService to handle scene changes (when some node is added/deleted/changed), so you can reflect availability of new splines and show them in a list instantly. "Subscription" code is also available in Browser.* files.
3. Create and implement your own kind of "Node" (scene object) that would be both Spline and Mesh at a time. Your implementation will always ignore changes in "Mesh", but will respect changes in "Spline". Thus, your node will rebuild road mesh when spline is changed. ZModeler will let other tools to deal with the spline (allow editing) and viewports will be able to render your node's mesh (generated road).

The #3 might look complex, but you can request ZModeler to create default spline/mesh instance for you, so you can delegate 95% of implementation code to an instance of default spline/mesh.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Making a render plugin

Post by Midas »

Thanks, that got me started. :)

I decided to use the 'built-in' nodes browser, and instead use a context menu action to select whether or not to generate roads on the selected spline. The float window will only show some properties for the currently selected spline.

I got a context menu tool to work, but it should only be visible on spline objects. Can I determine the type of object inside queryLayout()?

Also, should this topic be moved because it is no longer about ZModeler 1?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Making a render plugin

Post by Oleg »

sure, just а couple of rows of code:

Code: Select all

#include <scene/ISplineNode.h>


ZRESULT CMyRoadTool::queryLayout(scene::INode* pNode, core::eLevel level, core::ui::IViewport* pViewport)
{
  ZPtr<scene::ISplineNode> pSplineNode;
  if (ZRESULT_OK != pNode->QueryInterface(IID_(scene::ISplineNode), (void**)&pSplineNode))
    return ZRESULT_FALSE;//not suitable node for "road tool", don't include it in context menu;

  //
  // may be add some extra verification code here. e.g. check, whether this spline node is
  // already "under control" of your road tool;

  return ZRESULT_OK; //allow road tool to appear in context menu;
}

Do not hold the supplied node reference; queryLayout is just to check whether your tool is suitable for a given conditions: "node+level+viewport". If/when user actually picks the tool in context menu, an apply method is called:


Code: Select all

#include <helpers/sceneNavigation.h>


ZRESULT CMyRoadTool::apply(core::IProcParams* pParams, DWORD* pRetVal)
{
  resetError();

  ZPtr<scene::IScene> pScene;
  g_pZModeler->getScene(&pScene);
  //
  // loop on nodes
  scene::CSubsetIterator it;
  if (ZFAILED(it.initialize(pParams)))
    return REPORT_ERROR(NULL, _T("CMyRoadTool::apply -> Missing or empty subset."));
  // core::undo::beginUndoBranch(_T("my tool"));
  while (it.next())
  {
    if (!it.isAllowEditing())
      continue;
    ZPtr<scene::ISplineNode>  pSplineNode;
    if (ZRESULT_OK != it.queryNode(IID_(scene::ISplineNode), (void**)&pSplineNode))
      continue;

    //
    // pSplineNode is the spline in "subset" (the item the context menu was fired for)

    // :TODO: add your code here;

  }//while

  //core::undo::commitUndoBranch();
  return ZRESULT_OK;
}
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

That worked!

For checking whether a spline is 'under control' of the road service, can I define my own node properties?

I tried to copy CDummyNodeProperties to my project and change some of the properties, but that didn't work. Also, can I use this only in a 'shared'-plugin (.zmc), or will this work in 'service'-plugins (.zms) as well?

Thanks once again
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

node properties - it depends on purpose of these properties. If you need some properties in a node to be used by your implementation (inside zmodeler, for realtime purpose), you should probably implement your own "INode" and replace the given node with your custom node in scene. It's a tricky task and should be chosen when your node need to behave in a specific way, something that default spline or mesh implementation does not allow to do.

If you need to store some properties on a node (and probably let the user edit these properties), you can deal with "user-defined" properties. each scene node have them, and you can access them via "IUserDefinedOptions" interface:

Code: Select all

#include <core/ui/controls/IUserDefinedOptions.h>

{
  // inside some routine
  // pSplineNode is scene::ISplineNode (or any scene::INode you have)
  ZPtr<core::ui::controls::IUserDefinedOptions> pUserOpt;
  if (ZRESULT_OK == pSplineNode->QueryInterface(IID_(core::ui::controls::IUserDefinedOptions), (void**)&pUserOpt))
  {
    // use pUserOpt interface to add/query/edit options on a node:
    pUserOpt->addOption("Convert", core::ui::controls::checkBox, "1"); //spline should be converted
    pUserOpt->addOption("Width", core::ui::controls::floatBox2, "6.5");// width of road to generate from spline
  }
}

and very-alike code will let you read properties on export:

Code: Select all


{
  // inside some export routine, given pSplineNode found in scene:
  ZPtr<core::ui::controls::IUserDefinedOptions> pUserOpt;
  ZString str;
  if (ZRESULT_OK == pSplineNode->QueryInterface(IID_(core::ui::controls::IUserDefinedOptions), (void**)&pUserOpt) &&
      ZRESULT_OK == pUserOpt->getOptionValueByName("Convert", str) && str.length() > 0 &&
      ((LPCTSTR)str)[0] == '1')
  {
    // option of type "checkbox" named "Convert" was found, and checkmark was set on it
    // (user-defined option value is '1' when checkmark is set, and '0' when not set)
    float fWidth = 3.0f; //default width
    if (ZRESULT_OK == pUserOpt->getOptionValueByName("Width", str) && str.length() > 0 &&
       1 == _stscanf(str, "%f", &fWidth))
      zmath::clamp(fWidth, 1.0f, 12.0f); //ensure user has specified correct width: clamp to min/max range;

    //
    // ok, pSplineNode should be converted into a road with a known width in meters.
   
  }
}


Plugin extension (zmc, zms, zmf) is not important, ZModeler will load any *.zm? files and consider them as "plugin". The extension or folder is not important.

Dummy node properties source code is an extension for properties service, that adds a "user-friendly" options into "Properties" window to allow user access some features of dummy nodes. It does not add "features" to dummy nodes, it just provide a way to edit those that already available.

For example, "user-defined" options that I was talking about (above) are also a "feature" that is available on nodes, and a similar implementation adds "user-friendly" way to edit them into "Properties" window of properties service.

You can not add any "features" to existing spline nodes that could be edited in your own "user-friendly" way via add-on extension to Properties service, unless you actually replace nodes with your own implementation (that will provide the "features" you need). If you do, you can also create an addon for properties service too.

As you see, a lot of things could be done within a scope of your task, you just need to decide whether you chose "easy to implement" or "easy to use". :)
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

I've got an idea, probably, the optimal for your task.

1. you create a "tool" that resides in a floating window (or where ever you like, it might be a button in toolbar that will bring a settings window, or anything else).
2. your tool can create a mesh node (regular 3d mesh node) and will interactively set up it's vertices and quads to represent the road (build on splines "under control"). The node can be added to scene, so it's drawn in 3D view. See remark below;
3. your tool is subscribed for "scene events" and get notified when scene node is added/removed/changed
4. your tool holds a set of spline node references that user decided to convert to roads.
5. when "node removed" event is fired, you should get rid of respective node from your set, so you don't hold nodes that are no longer available in scene, and don't generate roads for splines that were deleted by user.
6. when "node has changed" event is fired, your check whether given node is spline, whether it's one of your "under control" nodes, and if it is, you can re-generate roads 3D geometry mesh (vertices and quads) + request redraw of 3D viewport;

Remark:
The best practice would be not to add your "roads" mesh node into scene, so user will not see it in hierarchy, will not try to edit mesh geometry and will not be able to access this mesh at all. In such a case, your "tool" can subscribe to zmodeler's "render" events too, and request your "mesh node" to "draw itself" in 3D viewport, when the rest of scene is drawn already.

The benefit of this method is that you don't create your own implementation of nodes, you fully reside on default implementation of splines and 3d meshes.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

I guess the easy way will do. However, user-defined options don't seem to work for me. When I add them, they are gone whenever I reopen the 'Properties' dialog. :?
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Yes, that looks like a good idea. :)

Am I going to need a class that holds an array of the splines, which is accessible for both the floater window and the context menu tool, from a global instance of that class? Something like:

Code: Select all

class CRoadSplineNodes
{
public:
	void addSpline(scene::ISplineNode *pNode)
		{ m_aSplines.push_back(pNode); }

	scene::ISplineNode *getSpline(size_t nIndex)
	{
		if (m_aSplines[nIndex])
			return m_aSplines[nIndex];
		return 0;
	}

	size_t numSplines(void)
		{ return m_aSplines.size(); }

	void removeSpline(scene::ISplineNode *pNode)
	{
		std::vector<scene::ISplineNode*>::iterator it = m_aSplines.begin();
		while (it != m_aSplines.end())
		{
			if (*it == pNode)
			{
				m_aSplines.erase(it);
				break;
			}
		}
	}

private:

	std::vector<scene::ISplineNode*> m_aSplines;
};

extern CRoadSplineNodes g_roadSplines;
I also couldn't find how to make the context menu item have a checkbox.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Ok, my implementation for adding/removing splines to the road tool seems to work. I also got a working checkbox in the context menu.

Can I use onNodeSelection() to get all currently selected items in the scene, or does this work for single nodes only? I will have to do this because the floater window should be either enabled or disabled, depending on the selection.
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

onNodeSelection() is fired for each node that was selected/deselected. you should better use onSceneChanges(). It's fired only once when something is changed in scene (e.g. a lot of nodes was selected, hidden, deleted - your event handler will get only one invocation of onSceneChanges().

One remark on your code above: it's not very safe to hold just a pointer to interface, since an instance of an object might get deleted. If you need to control a node, hold a reference to it via AddRef and Release() calls too. It could be something like

std::vector< ZPtr<scene::ISplineNode> > m_aSplines;

since ZPtr will automatically AddRef() on assignment and Release() on deletion; it's very alike to CComPtr<>

And a note on comparison of interfaces: your "removeSpline" code compares pointers to interfaces scene::ISplineNode as (*it == pNode). It is nearly safe in case of "scene::ISplineNode", since only one of this interface could be available in a node, but this might not in some cases when you need to compare scene::INode interfaces (there could be several scene::INode interfaces inside one instance of an object, so addresses on scene::INode* could be slightly different). The safe way to ensure instances are matching is to compare them via GetControllingUnknown():

Code: Select all

     ZUnknown* pNodeUnk = pNode->GetControllingUnknown(); //this method does not AddRef();     
     while (it != m_aSplines.end())
      {
         if ((*it)->GetControllingUnknown() == pNodeUnk) //comparing on "ZUnknwon" address is safe
         {
          ....
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Thank you, I have changed the class to work with ZPtr now.

Is onNodeSelection() included in the SDK? I could not find it in the help file, nor the include files.
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

it's a part of "core::events::ISceneEventHandler". The Source\Services\NodesService\Browser.* uses this method to know when nodes are shown/hidden externally.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

My bad, I meant onSceneChanges().. Is this available in the SDK?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

...my bad, it's not avail in 2.2.4 :(

I will zip and attach headers+libs for 2.2.6 (the latest version). I think it would be a better choice. Some example plugins from SDK might fail to compile, but I'm up here to help if something would fail to work.

Edit:
http://www.zmodeler2.com/files/ZModeler2_SDK_full.rar
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Are you sure the link you posted is a diferent version, since it seems to be exactly the same as 2.2.4?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

yup. It's an updated SDK for 2.2.6. The help file is not updated so it does not reflect changes.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Ok. Can you tell me which parameters this function has and how I can implement it?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

ohh... my mistake, this method is avail in zmodeler3 API only, so reside on method onNodeSelection() instead.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Ok, I got the multiple selection to work with onNodeSelection() only. Basicly the whole plugin is finished now (except the rendering which I'll skip for now). The next thing I have to do is implement write/read functionality for the Z3D file. How is this going to work with the splines, since the road tool refers to them by pointers?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

I'll send you an example implementation later evening. Can you show your implementation of "DllMain" module (the one with ZM_LIBRARY_*** and ZM_DLLMAIN_*** macros) ?
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

Yes, sure. I can also send the full source if you want. Maybe you find some errors.

Code: Select all

#include "Tool.h"
#include "ContextTool.h"

ZM_LIBRARY_DEFAULT_DECLARE()
	ZM_LIBRARY_NO_COPYRIGHT_INFO()
	ZM_LIBRARY_COLLECTION_BEGIN()
		ZM_LIBRARY_REG(core::tools::CPSDLRoadTool, ZM_CLASS_TOOL)
		ZM_LIBRARY_REG(core::tools::CPSDLRoadsContextTool, ZM_CLASS_TOOL)
	ZM_LIBRARY_COLLECTION_END()
ZM_LIBRARY_DEFAULT_END()

ZM_DLLMAIN_DEFAULT_BEGIN()
	ZM_LIBRARY_DEFAULT()
	ZM_LIBRARY_DESCRIPTION(_T("PSDL Functions for ZModeler 2"))
	ZM_LIBRARY_REGISTER()
ZM_DLLMAIN_DEFAULT_END()
I am also planning an intersection generator (which will generate intersections at points where two splines intersect). Does ZModeler have helper functions, for example, for calculating intersection points, and distance between two vertices?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

In general, to perform correct open/save of your data with .z3d files, your implementation should fit the overal logic of ZModeler addons. The most suitable solution for this case would be, probably, convert your extra storage of splines into "freely-available" service. You will export it from your plugin as a derivation of "services::IService", so ZModeler will know where to keep it or how to create an instance. You will not (or at least you don't need to) keep a global variable of your service, since you will be able to ask ZModeler to provide (and initially create) a shared instance of this service.

The benefit is that ZModeler will be able to create an instance (or find if already available) during loading of .z3d file.

Ok, here's how it could look:

Code: Select all


//
// interface part:
#include <services/IService.h>
#include <core::events::IZMEventHandler.h>
#include <core::events::ISceneEventHandler.h>
#include <scene::ISplineNode.h>
#include <core::io::ISerializeable.h>


typedef std::vector<ZPtr<scene::ISplineNode> > tSplineNodeList;
namespace services
{
  class IRoadSplineNodes : public IService
  {
  public:
     virtual void addSpline(scene::ISplineNode *pNode) = 0;
     virtual scene::ISplineNode *getSpline(size_t nIndex) = 0;
     virtual size_t numSplines(void) = 0;
     virtual void removeSpline(scene::ISplineNode *pNode) = 0;
  };
};//services

//
// implementation part:
namespace services
{

class CRoadSplineNodes : public services::IRoadSplineNodes,
                         public core::events::IZMEventHandler,
                         // quite suitable to be a "scene handler" too
                         // public core::events::ISceneEventHandler,
                         public core::io::ISerializeable
{
public:
  CRoadSplineNodes()
  {
  }
  virtual ~CRoadSplineNodes();

  INTERFACE_DECLAREMAP(CRoadSplineNodes)

public:
  //-----------------------
  // services::IService
  //-----------------------
  ZRESULT init(void); //will need to subscribe to "ZM events"

public:
  //-----------------------
  // IRoadSplineNodes
  //-----------------------
   void addSpline(scene::ISplineNode *pNode)
      { m_aSplines.push_back(pNode); }

   scene::ISplineNode *getSpline(size_t nIndex)
   {
      if (m_aSplines[nIndex])
         return m_aSplines[nIndex];
      return 0;
   }

   size_t numSplines(void)
      { return m_aSplines.size(); }

   void removeSpline(scene::ISplineNode *pNode)
   {
      tSplineNodeList::iterator it = m_aSplines.begin();
      ZUnknwon pUnk = pNode ? pNode->GetControllingUnknown() : NULL;
      while (it != m_aSplines.end())
      {
         if ((*it)->GetControllingUnknown() == pUnk)
         {
            m_aSplines.erase(it);
            break;
         }
      }
   }

public:
  //-----------------------
  // IZMEventHandler
  //-----------------------
  
  //get rid of controller pointers when closing;
  virtual ZRESULT onClose(void) { m_aSplines.clear(); return ZRESULT_FALSE; }
  //... and when scene is reset:
  virtual ZRESULT onNew(void) { return onClose(); }
  //do nothing on other events:
  virtual ZRESULT onStart(bool bDone=false) { return ZRESULT_FALSE;}
  virtual ZRESULT onOpen(LPCTSTR psz, bool bDone=false) { return ZRESULT_FALSE;}
  virtual ZRESULT onMerge(LPCTSTR psz, bool bDone=false, scene::CNodeCol* pNodes=NULL) { return ZRESULT_FALSE;}
  virtual ZRESULT onSave(LPCTSTR psz, bool bDone=false)  { return ZRESULT_FALSE;}
  virtual ZRESULT onImport(LPCTSTR psz, bool bDone=false){ return ZRESULT_FALSE;}
  virtual ZRESULT onExport(LPCTSTR psz, bool bDone=false){ return ZRESULT_FALSE;}
public:
  //-----------------------
  // core::io::ISerializeable
  //-----------------------
  virtual ZRESULT saveDeclaration(core::io::IStream* pStream, IOpenSaveService* pSrv, LPCTSTR pszResolver=NULL);
  virtual ZRESULT saveData(core::io::IStream* pStream, DWORD dwID, IOpenSaveService* pSrv);
  virtual ZRESULT loadData(core::io::IStream* pStream, IOpenSaveService* pSrv, DWORD dwSize, DWORD dwVersion=0, bool bMerge=false);
  virtual ZRESULT getReCreationMode(core::io::eReCreationMode& mode);

private:
  //-----------------------
  // data members; the splines we control:
  //-----------------------
  tSplineNodeList m_aSplines;
};


//
// global pointer to your service;
extern CRoadSplineNodes* g_pSplineNodes;

};//serivces


There's not that much stuff I've added, or at least there is no too much to implement.
Here's how this can be implemented (note, I don't test it in any way, so I can't be sure whether it complies and/or works correctly)

Code: Select all

//
// example implementation
#include <helpers/serializeable.h>

namespace services
{

INTERFACE_BEGINMAP(CRoadSplineNodes)
  INTERFACE_ENTRY(services::IService)
  INTERFACE_RELATIVE_ENTRY(core::events::IEventHandler, core::events::IZMEventHandler)
  INTERFACE_ENTRY(core::events::IZMEventHandler)
  INTERFACE_ENTRY(core::io::ISerializeable)
INTERFACE_ENDMAP()

CRoadSplineNodes* g_pSplineNodes = NULL;

CRoadSplineNodes::~CRoadSplineNodes()
{

  //
  // detach from event handling:
  ZPtr<core::events::IEventDispatcher>  pEvntDsp;
  ZPtr<core::events::IEventHandler> pHandler;
  if (ZRESULT_OK == QueryInterface(IID_(core::events::IEventHandler), (void**)&pHandler))
  {
    if (ZRESULT_OK == g_pZModeler->getEventDispatcher(core::events::EVENT_ZM, &pEvntDsp))
      pEvntDsp->setEventHandler(core::events::EVENT_ZM, pHandler, core::BO_SUBTRACT); //detach
    pEvntDsp = NULL;
    // and in case of scene event handling:
    //if (ZRESULT_OK == g_pZModeler->getEventDispatcher(core::events::EVENT_SCENE, &pEvntDsp))
    //  pEvntDsp->setEventHandler(core::events::EVENT_SCENE, pHandler, core::BO_SUBTRACT); //detach
  }
}

ZRESULT CRoadSplineNodes::init(void)
{
  //
  // register service in _zm_ events;
  ZPtr<core::events::IEventDispatcher>  pEvntDsp;
  ZPtr<core::events::IEventHandler>     pHandler;
  QueryInterface(IID_(core::events::IEventHandler), (void**)&pHandler);
  if (pHandler)
  {
    if (ZRESULT_OK == g_pZModeler->getEventDispatcher(core::events::EVENT_ZM, &pEvntDsp))
      pEvntDsp->setEventHandler(core::events::EVENT_ZM, pHandler, core::BO_UNITE);
    pEvntDsp = NULL;
    // and in case of scene event handling:
    //if (ZRESULT_OK == g_pZModeler->getEventDispatcher(core::events::EVENT_SCENE, &pEvntDsp))
    //  pEvntDsp->setEventHandler(core::events::EVENT_SCENE, pHandler, core::BO_UNITE);
  }

  return ZRESULT_OK;
}

//-----------------------
// core::io::ISerializeable
//-----------------------
ZRESULT CRoadSplineNodes::saveDeclaration(core::io::IStream* pStream, IOpenSaveService* pSrv, LPCTSTR pszResolver)
{
  resetError();
  if (NULL == pStream || NULL == pSrv)
    return ZRESULT_INVALID_ARG;
  core::io::writeDeclaration(pStream, _T("services::CRoadSplineNodes"), this, pSrv, 0, pszResolver);
  //
  // actually, scene nodes are registered (already or will be), so
  // you don't need to register them. However, the code below will
  // not harm at all:
  ZPtr<core::io::ISerializeable>  pSerial;
  tSplineNodeList::iterator it = m_aSplines.begin();
  while (it != m_aSplines.end())
    if (ZRESULT_OK == (*it)->QueryInterface(IID_(core::io::ISerializeable), (void**)&pSerial))
    {
      pSrv->addPersistentSerializeable(pSerial, false);
      pSerial = NULL;
    }

  return ZRESULT_OK;
}


ZRESULT CRoadSplineNodes::saveData(core::io::IStream* pStream, DWORD dwID, IOpenSaveService* pSrv)
{
  // saving data includes saving of array length and spline pointers. 
  // pointers are saved using unique ID of respective interface. These
  // IDs are maintained (assigned) by open/save service:
  CDataChunk splines(pStream, dwID, 0x02020600);  //a block of data, version 2.2.6;
  // amount of pointers followed:
  writeLong(pStream, (long)m_aSplines.size());
  tSplineNodeList::iterator it = m_aSplines.begin();
  while (it != m_aSplines.end())
  {
    ZUnknwon* pInterface = (*it)->GetControllingUnknown();
    core::io::writeInterface(pStream, pSrv, pUnk);//will write ID of respective ZUnknown;
  }
  return ZRESULT_OK;
}

ZRESULT CRoadSplineNodes::loadData(core::io::IStream* pStream, IOpenSaveService* pSrv, DWORD dwSize, DWORD dwVersion, bool bMerge)
{
  if (!bMerge)
    m_aSplines.clear();

  // loading data is as follows:
  // 1. read amount of interfaces first:
  long num = 0;
  pStream->read(&num, 4);
  for (long t = 0; t < num; t++)
  {
    // read interfaces and push_back em:
    // interface is read and resolved using the service (a helper routine does it in one call):
    ZPtr<scene::ISplineNode> pSNode;
    core::io::readInterface(pStream, pSrv, IID_(scene::ISplineNode), (void**)&pSNode);
    if (pSNode)
      addSpline(pSNode);
  }
  return ZRESULT_OK;
}

ZRESULT CRoadSplineNodes::getReCreationMode(core::io::eReCreationMode& mode)
{
  //
  // persisten service is the IService instance, which is already available, so "saver"
  // already has an instance and will call "saveDeclaration" and then "saveData", and
  // opener will use "getService" logic with a class name specified as
  // "services::CRoadSplineNodes" in a row of code in ::saveDeclaration (see above).

  mode = ERC_PERSISTENT_SERVICE;
  return ZRESULT_OK;
}

Your library will require extra startup-specific code too:

Code: Select all

//
// and a fragment of DllMain:
#include <helpers/serializeable.h>

ZM_LIBRARY_DEFAULT_DECLARE()
  ZM_LIBRARY_NO_COPYRIGHT_INFO()
  ZM_LIBRARY_COLLECTION_BEGIN()
    ZM_LIBRARY_REG(services::CRoadSplineNodes,       ZM_CLASS_SERVICE)
    ZM_LIBRARY_REG(core::tools::CPSDLRoadTool, ZM_CLASS_TOOL)
    ZM_LIBRARY_REG(core::tools::CPSDLRoadsContextTool, ZM_CLASS_TOOL)
  ZM_LIBRARY_COLLECTION_END()
  ZM_LIBRARY_REQUIRES(ZM_LIBREQUIRES_STARTUP)
  ZM_LIBRARY_STARTUP(startupLibrary)
ZM_LIBRARY_DEFAULT_END()

ZM_DLLMAIN_DEFAULT_BEGIN()
   ZM_LIBRARY_DEFAULT()
   ZM_LIBRARY_DESCRIPTION(_T("PSDL Functions for ZModeler 2"))
   ZM_LIBRARY_REGISTER()
ZM_DLLMAIN_DEFAULT_END()

ZRESULT startupLibrary()
{
  //
  // you don't create an instance explicity; let ZModeler create a service
  // for your correctly; your services is correctly exported from your plugin
  // and repository will call "init" on an instance for you too.
  ZPtr<core::IRepository>   pRepos;
  ZPtr<services::IService>  pService;
  if (ZRESULT_OK == g_pZModeler->getRepository(&pRepos) &&
      ZRESULT_OK == pRepos->getService(_T("services::CRoadSplineNodes"), &pService))
  {
    // service instantated; it's "init" was called, so this instance is alreay
    // subscribed to event handling; all you need to do, is to add this instance
    // to open/save service, so it's used during save operation:

    core::io::registerSerializeable(pService);
    
    // and let the global variable save the pointer to an instance.
    // it's safe to keep a pointer, since an instance of your service will not
    // get released until ZModeler closes (it's subscribed to event handling, so
    // at least event dispatcher guarantees an instance exists). Your code is safe
    // to access service via "g_pSplineNodes", just ensure it's not NULL and go on.
    ZPtr<services::IRoadSplineNodes>  pMyService;
    if (ZRESULT_OK == pService->QueryInterface(IID_(services::IRoadSplineNodes), (void**)&pMyService))
      services::g_pSplineNodes = pMyService;
  }
  
  return ZRESULT_OK;
}

You can add a lot of logic into services::IRoadSplineNodes inteface and give it a more reflecting name like "services::IPSDLRoadSplineService". Then it could be extracted into a separate header file that could be spread with your plugin. Thus, anyone else will be able to create additional tools or plugins and use your service (with "getService" and "QueryInterface" as shown above).

Let me know if you need more detailed explanation or if something is not clear or not working as it should.
Post Reply