Midtown Madness2 road plugin

Post a reply

Smilies
:D :) :( :o :shock: :? 8) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen:

BBCode is ON
[img] is ON
[url] is ON
Smilies are ON

Topic review
   

Expand view Topic review: Midtown Madness2 road plugin

Re: Midtown Madness2 road plugin

by Oleg » Tue Mar 04, 2014 1:40 pm

yes.

Code: Select all

  pSpline->reset();
  ZPtr<scene::IControlPoint>  pStart, pEnd;
  pSpline->next(&pStart);
  if (!pStart)
    return ZRESULT_FALSE;
  ZRESULT zr;
  do
  {
    ZPoint3D pt;
    zr = pSpline->getSectionControls(pStart, &pEnd);
    if (pStart && pEnd)
    {
      //
      // valid section
      long nDetail;
      pStart->getDetalization(nDetail);
      for (int i = 0; i < nDetail+1; i++)
      {
        //get point
        pSpline->getSectionPoint(pStart, pEnd, (float)i/(float)(nDetail+1), pt);
        // use pt;
      }
    }
    if (ZRESULT_FALSE == zr && pEnd)
    {
      //the very last point of spline:
      pEnd->getControlPosition(pt);
      //use pt;
    }
    pStart = pEnd;
    pEnd   = NULL;
  }
  while(ZRESULT_OK == zr);


Re: Midtown Madness2 road plugin

by Midas » Tue Mar 04, 2014 1:19 pm

Thanks Oleg!

Also, since I would like to use bézier curves, I will have to calculate a point on the curve for each cross section of the road by means of a precission value. Would getSectionPoint() be the way to go?

Re: Midtown Madness2 road plugin

by Oleg » Tue Mar 04, 2014 12:40 pm

Hi.

Code: Select all

#include <helpers/sceneNavigation.h>

// pSplineNode is scene::ISplineNode;

ZMatrix matToWorld, matToWorldNT;
scene::getNodeToWorldTransform(pSplineNode, matToWorld);
matToWorldNT = matToWorld;
matToWorldNT.noTranslation();//keep "rotation" part of the matrix;

// matToWorld can be used to convert node-space position to world-space;
// matToWorldNT can be used to convert node-space directions (or normals) to world-space (if needed)

ZPoint3D pos;
pSpline->getControlPosition(...., pos);

pos = pos*matToWorld;

//pos is now in world space;

Re: Midtown Madness2 road plugin

by Midas » Tue Mar 04, 2014 10:51 am

Oleg,

So I got back to this for a while..

The plugin is working, it can generate roads to PSDL, based on the settings in the user properties. This is the result:

Image

The only problem, as you can see, is that the roads are all placed on the origin. This is because I used getControlPosition() to get the position for every point on a spline, and for splines this is relative to the spline's "coordinate space". Is it possible to get the absolute coordinates instead?

Regards,

Midas

Re: Midtown Madness2 road plugin

by Oleg » Fri Jan 17, 2014 9:49 am

Looks like a bug. There was something like that in ZMod2, I remember this kind of a problem. I guess you can reside on a "slower" method of querying option names one by one and getting the value of the matching: you can write a helper function that will read and provide a value out of IUserDefinedProperites given an option name.

Re: Midtown Madness2 road plugin

by Midas » Fri Jan 17, 2014 8:00 am

The returned string is "A" and the returned number is 3. When I open "Selection Properties", it shows a single "test" property with empty value.

Doing the same for normal meshes gives the same result.

Re: Midtown Madness2 road plugin

by Oleg » Thu Jan 16, 2014 1:39 pm

what would this test code produce at run:

Code: Select all

  ZPtr<core::ui::controls::IUserDefinedOptions> pUserOpt;
  ZString strValue;
  if (ZRESULT_OK == pNode->QueryInterface(IID_(core::ui::controls::IUserDefinedOptions), (void**)&pUserOpt))
  {
    pUserOpt->addOption("test", core::ui::controls::editBox, "A");
    pUserOpt->addOption("test", core::ui::controls::editBox, "B");
    pUserOpt->addOption("test", core::ui::controls::editBox, "C");
    pUserOpt->getOptionValueByName("test", strValue);
    AfxMessageBox(strValue);
    AfxMessageBox(ZString::string(pUserOpt->getOptionsCount()));
  }
?

Re: Midtown Madness2 road plugin

by Midas » Thu Jan 16, 2014 1:24 pm

Yes, I'm sure. I also tested the value of the string in the output window. I'm not getting duplicates either. After the property is added for the first time, it won't change after the following addOption() calls. I'm also not doing anything with the return value (index). (I didn't change anything to your code.)

Re: Midtown Madness2 road plugin

by Oleg » Thu Jan 16, 2014 12:25 pm

Midas wrote: There is a problem though. IUserDefinedOptions::addOption() doesn't seem to overwrite the current value if it already exists.
are you sure? the IUserDefinedOptions::addOption implementation starts with:

tOptionInfo& info = getOptionByNameOrCreateNew(strName);

(see API\Helpers\userDefined.h)

and changes value of the given info slot. So it have to alter existing property value when name matches. There is a bug that returned property index is always the index of last property (even if it was not added and existing was found), may be this has confused you?

Do you actually get duplicate option names in properties of your spline then?

Re: Midtown Madness2 road plugin

by Midas » Thu Jan 16, 2014 11:57 am

Excellent, that fixed it nicely.

Yes, it does produce an error, but it's fixed easily. You forgot to declare a variable in the loadData() function in SplinesSerialization.cpp:

Code: Select all

core::io::tChunkInfo chunkInfo;
There is a problem though. IUserDefinedOptions::addOption() doesn't seem to overwrite the current value if it already exists. To fix this, it would be useful to have a function called "setOptionValueByName" just like there is "getOptionValueByName". Another way would be to find the index of the user option by iterating over getOptionName(). Then I can use the iterator value on setOptionValue() which takes an index. What do you think?

Re: Midtown Madness2 road plugin

by Oleg » Thu Jan 16, 2014 10:13 am

ohh... my bad, the ISplineNode has no user-defined options in ZModeler2...

Since I don't have an environment to compile zmod2 stuff, I hope you can do it for your project. Here's an original source code attached, it has all required changes to support user-defined options on spline nodes. Just compile it and use instead of original Splines.zmp plugin.

Let me know if it does not compile or produce any errors.

Concerning your question about it.queryNode(...), it's a must, since ZModeler does not guarantee you that "apply" will be called only after "queryLayout". This is the default behavior of context menu service, but ITool::apply method could be accessed externally by plugins. For example, hotkeys service can call "apply" on any tool if user assigns hot-key for such a tool.
Attachments
Splines_source.zip
(47.11 KiB) Downloaded 874 times

Re: Midtown Madness2 road plugin

by Midas » Thu Jan 16, 2014 9:06 am

Oleg wrote:Your tool is a floater that shows a width and/or other road-specific options for a spline you select in scene, right?
Yes. I will clarify with a screenshot:

Image

The collectSceneSplineNodes function looks like a good idea (as long as it doesn't have performance 'drawbacks'). I will implement this.

However, the user properties, like I said, don't work for me in ZModeler. I changed the apply and queryLayout as you suggested, but the properties won't appear in the "Selection Properties" window after they were added. Am I missing something?

Thanks for your notes about my code also; will take them in account.

I also have a small remark about the while loop in apply(). It's only a detail, but I was just wondering:

Code: Select all

while (it.next())
{
	if (!it.isAllowEditing())
	  continue;

	pNode = NULL;
	// Is it strictly necessary to check for a spline node here? Doesn't queryLayout() already ensure this?
	if (ZRESULT_OK == it.queryNode(IID_(scene::ISplineNode), (void**)&pNode))
		break;
}

Re: Midtown Madness2 road plugin

by Oleg » Thu Jan 16, 2014 5:33 am

I don't have zmod2 source to compile and/or test your plugin, but hope I see the overal logic behind.

Your tool is a floater that shows a width and/or other road-specific options for a spline you select in scene, right? I guess you don't actually need to collect a set of splines to store that much of information in your global CPSDLNodes. Well, you can actually use such a thing, but you can create it dynamically using a scene content when this info is needed. The key feature I would like to suggest is to move your per-spline options into user-defined options of the spline. You can give them specific names like

PSDL.RoadPlugin.Convert
('1' = convert, any other value = do not convert

PSDL.RoadPlugin.WidthRoad
(if value successfully converts to float, and float is in correct range (0.0f to 10.0f), consider it to be correct width specified;

PSDL.RoadPlugin.WidthSidewalk
...

and so on.

User will not need to create these options (yes, user could see them in regular "properties" window of zmodeler, can change or delete them), but the node itself will maintain them across through ZModeler logic. The spline will save and load these properties into .z3d, so you don't bother with this stuff.

Your tool can give a quick spot on scene nodes, collect splines that match criteria into array, check their user-defined options where applies (read given width values). You can move this into a routine like that:

collectSceneSplineNodes(core::IProcParams* pParams, bool bSelectedOnly, bool bConvertOnly, CPSDLNodes& arrOut);

It should not be a member of any class, so it can be accessed from any point of your code.
The routine will collect either selected only splines (e.g., when some scene node is selected or deselected, it will collect correct array of "still selected" scene splines, so your floater window can show properties for selected splines), and/or it can also check availability of non-zero "PSDL.RoadPlugin.Convert" user-defined property on a spline, so it will find only those splines that user wants to convert (e.g. before an export).

Code: Select all

ZRESULT collectSceneSplineNodes(core::IProcParams* pParams, bool bSelectedOnly, bool bConvertOnly, CPSDLNodes& arrOut);
{
  //out array is empty by default; if it's not a case in your implementation for some reason, clear it:
  //arrOut.deleteAll(); // add this method into CPSDLNodes too;

  // scene navigator walks entire scene nodes, including
  // hidden and disabled;
  scene::CSceneNavigator nav;
  if (ZFAILED(nav.initialize(pParams)))
    return ZRESULT_FALSE;

  //
  // loop on nodes
  while (nav.next())
  {
    if (!nav.isAccessible())
      continue;

    ZPtr<scene::ISplineNode> pSNode;
    if (ZRESULT_OK == nav.queryNode(IID_(scene::ISplineNode), (void**)&pSNode))
    {
      //got a spline;
      DWORD dwNodeStatus = nav.getNodeStatus();
      float fWidth1 = -1.0f; //not available/not known;
      float fWidth2 = -1.0f; //not available/not known;
      bool  bConvert = false;
      long  nType   = -1;
      ZPtr<core::ui::controls::IUserDefinedProperty>  pUserProp;
      ZString strValue;
      if (ZRESULT_OK == pSNode->QueryInterface(IID_(core::ui::controls::IUserDefinedProperty), (void**)&pUserProp))
      {
        bool bConvert = ZRESULT_OK == pUserProp->getValueByName("PSDL.RoadPlugin.Convert", strValue) &&
                        strValue.length() > 0 && ((LPCTSTR)strValue)[0] != '0';
        if (ZRESULT_OK != pUserProp->getValueByName("PSDL.RoadPlugin.WidthRoad", strValue) ||
                        strValue.length() == 0 || 1 != _stscanf(strValue, "%f", &fWidth1))
          fWidth1 = -1.0f;//consider as not available;
        if (ZRESULT_OK != pUserProp->getValueByName("PSDL.RoadPlugin.WidthSidewalk", strValue) ||
                        strValue.length() == 0 || 1 != _stscanf(strValue, "%f", &fWidth2))
          fWidth2 = -1.0f;//consider as not available;
        if (ZRESULT_OK != pUserProp->getValueByName("PSDL.RoadPlugin.Type", strValue) ||
                        strValue.length() == 0 || 1 != _stscanf(strValue, "%li", &nType))
          nType = -1;//consider as not available;
      }
      //
      // determine whether this spline shold go into [out] array:
      if ((!bConvertOnly || bConvertOnly && bConvert) &&
          (!bSelectedOnly || bSelectedOnly && (0 != (dwNodeStatus & STATUS_SELECTED))))
        // refine "add" routine to accept initial parameters: "convert", "type", "width1", "width2":
        // let it refine values, since -1.0f could appear as width (not defined), type could
        // be -1 as "not defined" too)))
        arrOut.add(pSNode, bConvert, nType, fWidth1, fWidth2);
    }//is spline

  }//while nodes
  return ZRESULT_OK;
}
Your context tool will not deal with CPSDLNodes, but will set on/off user-defined property "PSDL.RoadPlugin.Convert" on a given spline(s):

Code: Select all

ZRESULT CPSDLRoadsContextTool::apply(core::IProcParams *pParams, DWORD *pRetVal)
{
  resetError();
  core::eLevel lev;
  pParams->getLevel(lev);

  if (SUBLEVEL(lev))
    return ZRESULT_FALSE;

  scene::tNodeSeq seq;
  scene::CSubsetIterator it;
  ZPtr<scene::ISplineNode> pNode;

  if (!it.initialize(pParams))
    return ZRESULT_FALSE;

  while (it.next())
  {
    if (!it.isAllowEditing())
      continue;

    pNode = NULL;
    // stop on spline node only:
    if (ZRESULT_OK == it.queryNode(IID_(scene::ISplineNode), (void**)&pNode))
      break;
  }

  if (!pNode)
    return ZRESULT_FALSE;

  //
  // query current user-defined value:
  ZPtr<core::ui::controls::IUserDefinedOptions> pUserOpt;
  if (ZRESULT_OK == pNode->QueryInterface(IID_(core::ui::controls::IUserDefinedOptions), (void**)&pUserOpt))
  {
    ZString strValue;
    ZString strNewValue("1");//by default your tool will enable conversion;
    if (ZRESULT_OK == pUserOpt->getOptionValueByName("PSDL.RoadPlugin.Convert", strValue) &&
        strValue.length() > 0 && ((LPCTSTR)strValue)[0] != '0')
    {
      // convert property is already available and it's On; so context tool action
      // would be to turn if off:
      strNewValue[0] = '0';
    }
    // adding option that matches by name to any existing option will "update" value
    // on existing, so it's safe to use "add" in both cases: option is already available
    // and option is not available yet;
    pUserOpt->addOption("PSDL.RoadPlugin.Convert", core::ui::controls::checkBox, strNewValue);
  }

  //
  // ...you can also force your floater window to update, so it can reflect changes
  //
  return ZRESULT_OK;
}
The respective queryLayout will change, sine it will check for a property on a spline node:

Code: Select all

ZRESULT CPSDLRoadsContextTool::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;

  // given node is a splien node; check for conversion property:
  ZPtr<core::ui::controls::IUserDefinedOptions> pUserOpt;
  ZString strValue;
  if (ZRESULT_OK == pNode->QueryInterface(IID_(core::ui::controls::IUserDefinedOptions), (void**)&pUserOpt) &&
      ZRESULT_OK == pUserOpt->getOptionValueByName("PSDL.RoadPlugin.Convert", strValue) &&
      strValue.length() > 0 && ((LPCTSTR)strValue)[0] != '0')
    // valid property with valid 'on' value:
    g_bCheckState = true;
  else
    g_bCheckState = false;

  return ZRESULT_OK;
}
and your context tool does not need setStatus and changeStatus. the checkStatus(DWORD nBit) should look like this:
return (nBit == STATUS_ACTIVE && g_bCheckState) ? ZRESULT_OK : ZRESULT_FALSE;


Concerning your tool, there are some notes to mention:
1. event handler should always return ZRESULT_FALSE. Returning ZRESULT_OK will stop processing and other event handlers will not get notified.
2. "scene" event handing will need onNodeSelection only, all other can simply return ZRESULT_FALSE;
3. handling "zm" events becomes not necessary.

all of your

Code: Select all

			void Update(void);
			void OnCheckRadio(UINT nID);
			void OnEnChange(UINT nID);
should start with a rows of code like that:

Code: Select all

CPSDLNodes arrSplines;
ZPtr<core::IProcParams> pParmas;
g_pZModeler->getProcParams(&pParams);
if (ZRESULT_OK == collectSceneSplineNodes(pParams, true, false, arrSplines))
{
  //
  // the rest of implementation follows here;
}

Re: Midtown Madness2 road plugin

by Midas » Wed Jan 15, 2014 8:56 am

Ok, I'm totally lost now. :|

I don't know how I can implement the 'service' class and initGlobalPointer() function in my project in its current state. I will add a link to the source code. Can you please take a look at it?

I'm sorry for not understanding, nevertheless the effort you put into this..

PSDLFunctions2_src.zip

Re: Midtown Madness2 road plugin

by Oleg » Wed Jan 15, 2014 5:45 am

I've made a separate service out of the splines storage, since this storage should reflect scene changes, and should be able to open/save spline pointers. So, it looks like preferable to put splines management into the "service" and extract user-interation methods into a tool. The tool could request your service from ZModeler and call methods you need.

g_pSplineNodes should be IRoadSplineNodes, it's my mistake. Thus, if your tool needs any specific logic of spline storage to be accessible, respective methods should be available in an interface part.

A tool will not handle any events, the "storage service" will do it.

However, this solution will not work, if certain scene changes require some UI appearance like enabling/disabling controls or selecting/deselecting items in a list control, especially when these UI control are a part of the "Tool" (the case when your tool is a floater window with some controls).

Currently, this is usually resolved by moving all-and-everything into the "service", so the "tool" is just a button in toolbar that requests "service" to show a dialog box. (Materials service and textures service are an example).

The problem is with serialization. When reading .z3d file, the reading-service facing a declaration inside a .z3d file, it has to find (or create) an instance that will deal with respective saved data. It can handle "services" easily, as they are available directly in repository, but it can't handle tools, since tools instances are spread all around the ZModeler (some are in menu, some are in context menu, some are in commands bar and so on). Thus, most of tools are not serializeable.

If the "UI controls are in a Tool" is your case, and the event handling inside a "service" can not change UI state of controls (it can't reach them to enable/disable or whatsoever), I would recommend

the service as:

- splines vector;
- serialization support;

the tool (with UI controls) as:

- event handler (scene, zm)
- required logic of spline->road conversion

the context tool as
- user-interaction for adding under control / removing from control of a given spline (interacts with service)

If your UI elements are not a floater window, a regular dialog box could be a choice. In such a case, an implementation of "everything inside a service" (including dialog itself) can be preferable.


P.S. if you chose a "dialog-and-service-and-handler" implementation, the SDK\services\MaterialsService\MaterialsService.* is your helper. It's important to derive your class from CDialog (CWnd) first, and then the rest of interfaces can follow:

class CMyService : public CDialog, public services::IRoadSplineNode, public core::events::IZMEventHandler

and a very specific row inside interfaces map should be present: it should point to the very-very first ZModeler interface of your class:

INTERFACE_BEGINMAP(CMyService)
INTERFACE_CONTROLLING_UNKNOWN(services::IService)
...

where "services::IService" is the first, since it's the first in derivation of "services::IRoadSplineNode".
Specifying wrong "controlling unknown" interface in a macro will cause app crash on attempt to instantiate a class (crash during CWnd::Create or at the attempt to invoke any method of zmodeler interface on a given instance).

Re: Midtown Madness2 road plugin

by Midas » Wed Jan 15, 2014 5:18 am

I'm not really sure why you made a seperate service for the splines storage class. Can't I just make CRoadSplineNodes into a service?

Why is g_pSplineNodes declared as CRoadSplineNodes and initialized as IRoadSplineNodes in the startup function? Is that an error?

What I was thinking was to make CRoadSplineNodes into a service which handles scene events, and declares its own virtual functions for events, which can be implemented in a class that inherits from this service. For example, CPSDLRoadTool would derive from CRoadSplineNodes and implement these functions to update its dialog controls when needed, so that CPSDLRoadTool does not inherit from ISceneEventHandler and IZMEventHandler, but just from IRoadSplineNodes. I don't know if something like this is possible..?

I'm sorry if this doesn't make sense. :|

Re: Midtown Madness2 road plugin

by Oleg » Tue Jan 14, 2014 10:37 am

A small remark: you can get rid of all stuff I've added in "DllMain" code sample (except the row of ZM_LIBRARY_REG(services::CRoadSplineNodes, ZM_CLASS_SERVICE)

But your tool will need to obtain service instance explicitly with some extra routine:

Code: Select all

ZRESULT initGlobalPointer()
{
  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;
    }
  }
  return ZRESULT_FALSE;
}
each of your code routine, that requires the service, would start with a row like this:

Code: Select all

ZRESULT myRoutine()
{
  if (NULL == services::g_pSplineNodes && ZRESULT_OK != initGlobalPointer())
    return ZRESULT_FAIL;//can not operate;

  //
  // do what you need with service pointer services::g_pSplineNodes, it's safe now
}
What is the benefit? The instance of your service will be created when some of PSDL-specific tools is used. On that moment the service is created and subscribed for events. So, if user does not use your tools in ZModeler, an instance is not created and no related stuff is saved into .z3d file (just like no extra plugin is available at all). As soon as user applies your tool or does anything that causes a "getService(....)" to be called, your service is created and ready-to-go-and-to-save :)

Re: Midtown Madness2 road plugin

by Oleg » Tue Jan 14, 2014 10:21 am

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.

Re: Midtown Madness2 road plugin

by Midas » Tue Jan 14, 2014 2:57 am

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?

Re: Midtown Madness2 road plugin

by Oleg » Mon Jan 13, 2014 9:27 pm

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) ?

Re: Midtown Madness2 road plugin

by Midas » Mon Jan 13, 2014 10:27 am

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?

Re: Midtown Madness2 road plugin

by Oleg » Sun Jan 12, 2014 9:59 am

ohh... my mistake, this method is avail in zmodeler3 API only, so reside on method onNodeSelection() instead.

Re: Midtown Madness2 road plugin

by Midas » Sun Jan 12, 2014 9:40 am

Ok. Can you tell me which parameters this function has and how I can implement it?

Re: Midtown Madness2 road plugin

by Oleg » Sun Jan 12, 2014 9:31 am

yup. It's an updated SDK for 2.2.6. The help file is not updated so it does not reflect changes.

Re: Midtown Madness2 road plugin

by Midas » Sun Jan 12, 2014 9:13 am

Are you sure the link you posted is a diferent version, since it seems to be exactly the same as 2.2.4?

Top