Midtown Madness2 road plugin

ZModeler2 Software Development Kit forum. Programming questions and answers.

Moderator: Oleg

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

Re: Midtown Madness2 road plugin

Post by Oleg »

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 :)
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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. :|
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 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).
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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
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 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;
}
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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;
}
Last edited by Midas on Tue Mar 04, 2014 10:39 am, edited 1 time in total.
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 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
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

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?
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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.)
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

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()));
  }
?
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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.
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

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.
Midas
Posts: 21
Joined: Fri Jun 11, 2010 9:20 pm
Location: Belgium

Re: Midtown Madness2 road plugin

Post by Midas »

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
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

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;

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

Re: Midtown Madness2 road plugin

Post by Midas »

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?
User avatar
Oleg
Site Admin
Posts: 14044
Joined: Fri Feb 06, 2004 3:54 am
Contact:

Re: Midtown Madness2 road plugin

Post by Oleg »

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


Post Reply