Device::GetLayoutUI

Hi,

I’m a games programmer and I need to create a better road layout tool for our gfx team - preferably as a plugin to world machine. I’ve looked at the PDK and I think I have to somehow provide my own layout manipulator, in order to be able to control splines and how they are applied to the terrain.
Does anyone know how I write my own layout? Alternatively how do I access an existing layout in order to get hold of the splines?

I’ve tried overriding the SimpleGenerator::GetLayoutUI, but the method is never called.

Thanks in advance,
Henning

Hi Henning,

Essentially there are 2 steps to introducing Layout View UI code:

  1. Subclass from DeviceLayoutUIClient (defined in LayoutUIClient.h) a new class that represents the view code for the device to be interacted with. For example:
class MyDeviceUIClient : public DeviceLayoutUIClient
{
public:
        MyDeviceUIClient(Device *mydevice) { setOwner(mydevice); };
...
        /// Draw the device into Layout View
	virtual bool OnDraw(LV_GUIData &style);
... and more stuff.. see header for full list of supported function calls
};
  1. In your device class, create the following function:
class MyCustomDevice : public Filter {
public:
... other stuff...
LayoutUIClient *GetLayoutUI() { return &my_layout_ui; };
... more
private:
  MyDeviceUIClient my_layout_ui;

It must return a pointer to an instance of the layout UI view class defined in Step 1.

The above should get you up and running and have WM call into your layout view code. As soon as GetLayoutUI() is not returning NULL, WM will call on the Draw and Manipulator functions defined in that interface.

However, note that the above allows you to create Overview-type device interactions in Layout Mode. You cannot access WM’s Layout Generator spline layouts and such, as they are done through a related but more tightly integrated codebase. You could create similar functionality from scratch but you are currently unable to leverage the Layout Generator itself.

Allowing plugins into the Layout Generator code itself is a desirable feature but one that is currently not possible as of WM 2.2.

Hi Remnant,

Thanks for the explanation. However as I’m a total newbie to world-machine I’m still at a loss on how I trigger a call to the GetLayoutUI() method.
What I do:

[ul][li]Start world machine through dev studio.[/li]
[li]Place a breakpoint in Inverter::GetLayoutUI.[/li]
[li]insert my modified version of the Inverter filter.[/li]
[li]Goto Layout view.[/li]
[li]Select the Inverter in the device navigation.[/li]
[li]Select new layout…[/li][/ul]

But alas the breakpoint is not fired. I’ve checked with the Inverter::Activate to be sure I get breakpoints, and they work fine there…

What am I doing wrong?
Henning

Ah, OK.

Here’s the trick: Your device’s Layout UI will only be active when you are in the Overview tab of the layout view… AND you have changed the mode to Device Edit mode. (That’s the icon of the hand grabbing a box) in the layout view toolbar.

For an example of this, drop a File Input device and load a terrain in it, then hit Layout View and go to the overview->device edit tab mode. You will see a box around your imported terrain, and you can move and resize the imported terrain by dragging the box or its handles around. This directly uses the LayoutUI() methods described previously.

Your device must either be selected, or you must have “Show Only Selected” unchecked in the overview bar. Unchecking that can be handy as it allows you to manipulate all devices in your network at once rather than one at a time.

Thanks, I’ve gotten a bit further now…

I’m now able to do custom drawing using opengl and the interaction with manipulators seems to be working - somewhat…

I’ve created 2 manipulators during construction, both are points, and I return those through numManipulator and getManipulator but

I only ever get LVM_DRAG in actionManipulator(…)
the first argument (int i - I assume its manipulator index) is always 0, even though I select the other point.

I’m also at a loss to how I add new manipulator points? I could of course use a rectangle manipulator and make all selection, dragging etc by myself. Is this the intention?

Thanks,
Henning

Hi,

Are you able to help me out on this or should I find another solution?

Thanks,
Henning

Hi there,

Sorry for the delay. I had to check into the codebase to make sure I wasn’t going to give you erroneous advice.

To make sure you’re getting the manipulators setup, I’ve included some example code below formed from the FileInput’s DeviceUI class.
Important things to note is that the manipulators are returned by value, so that they do not have to have any kind of lifespan – you can create them on the fly. The only important thing is that the manipulator
ID codes mean something coherent to your plugin.

You should certainly be getting different indices in your actionManipulator code.

One thing you are quickly running into is that the Device Layout-UI interface has been implemented only to the minimalist set of operations necessary for internal use.
For example, inside World Machine the interface is used by the File Input and Tiled File Input devices, and also used by the Render Extents manipulation page in the layout view.
Ultimately, I feel like many devices could benefit from exposing graphical manipulation tools, but I ran out of time in the previous major release doing so.

With that in mind… after a double-check of the internal code, the only two manipulator actions that are implemented at the moment are LVM_DRAG and LVM_DOUBLECLICK. These were simply the only parts needed by the internal systems. (LVM_SIMPLE manipulators are essentially those that only receive LVM_DRAG messages).


// critical method implementations

int FileInUIClient::numManipulator() { return 5; };

LV_Manipulator  FileInUIClient::getManipulator(int i) {
	LV_Manipulator manip;
	FileInput *owner = (FileInput*)base_owner;

	Rect4F &rq = owner->GetRenderExtents();

// all manipulators are simple and framework-draw-managed (we draw some extras in OnDraw as well)
	manip.flags = LVM_FLAG_AUTODRAW | LVM_FLAG_SIMPLE;

	switch ( i ) { 
		case 0:			// The main area manipulator
			manip.flags = 0;
			manip.type = LVM_AREA;
			manip.area = rq;
			manip.code = 0;
			break;
		case 1:
			manip.type = LVM_POINT;	// Low-left corner
			manip.point = CoordF(rq.x0, rq.y0);
			manip.code = 1;
			break;
		case 2:
			manip.type = LVM_POINT;	// Low-right corner
			manip.point = CoordF(rq.x1, rq.y0);
			manip.code = 2;
			break;
		case 3:
			manip.type = LVM_POINT;	// Upper-right corner
			manip.point = CoordF(rq.x1, rq.y1);
			manip.code = 3;
			break;
		case 4:
			manip.type = LVM_POINT;	// Upper-left corner
			manip.point = CoordF(rq.x0, rq.y1);
			manip.code = 4;
			break;
		default:
			break;
	};
	return manip;
};

bool FileInUIClient::actionManipulator(int i, LVM_ACTION act, Vector2 wpoint, int flags) {
	FileInput *owner = (FileInput*)base_owner;

	Rect4F &area = owner->GetRenderExtents();

	switch ( i ) { 
		case 0:			// The main area manipulator
			if (act == LVM_DBLCLICK) {
				owner->RunUI();
				return true;
			}
			else if (act == LVM_DRAG) {
				owner->setOrigin(offset);
				return true;
			}
			break;
		case 1:	// Low-left
			area.x0 = wpoint.x;
			area.y0 = wpoint.y;
			break;
		case 2:// Low-right corner
			area.x1 = wpoint.x;
			area.y0 = wpoint.y;
			break;
		case 3:// Upper-right corner
			area.x1 = wpoint.x;
			area.y1 = wpoint.y;
			break;
		case 4:// Upper-left corner
			area.x0 = wpoint.x;
			area.y1 = wpoint.y;
		default:
			break;
	};

	area.Validate();
	offset.x = area.x0; offset.y = area.y0;
	owner->setOrigin(offset);
	return true;
};

bool FileInUIClient::OnDraw(LV_GUIData &style) {
	Rect4F &rq = owner->GetRenderExtents();

	glLineWidth(3.0f);

// paint a transparent overlay of file input area
	glBegin(GL_QUADS);
	glColor4f(0.4f, 0.4f, 0.4f, 0.25f);
	glVertex3f(x1,y1, 0.0f);
	glVertex3f(x2,y1, 0.0f);
	glVertex3f(x2,y2, 0.0f);
	glVertex3f(x1,y2, 0.0f);
	glEnd();

	return true;
};

char* FileInUIClient::getManipulatorHelp(int i) {
	if (i == 0)
		return "Drag to move the file input extents, or Double-click to edit";
	else {
		return "Drag manipulator to adjust extents. +SHIFT constrains to square";
	}
};

Thanks for the sample code, it did explain a lot to me, and I’ve got the most basic editing up and running.

However, I’m at a loss to where I should keep my data? Apparently the Device is created and destroyed on the fly, so I can’t keep my control points for the road in this class. Should I somehow add these as parameters (hidden maybe?).

Secondly when I double click and add a new manipulator, it doesn’t seem to react on it first. When I hoover over the new manipulator I can see that wm2 doesn’t react to it. If I then click on a previous manipulator and move the cursor back to the new manipulator wm2 reacts to it.

Henning

I’m not sure what you mean by created and destroyed on the fly – your device has a lifetime equivelent to when you first place it into the world (or the world is loaded), and it ends when the world is cleared or the device deleted. This usually corresponds to the data lifetime you would want.

Thus I would keep data in the device class instance. (You could keep it in your LayoutUI-derived view class, but I’d recommend keeping important data in the actual device responsible for it instead) This is how the Layout Generator does things – each layout is stored in a Layout Generator device. This will give you a lifetime equivelent to the lifetime of the device in the world, and lets you store per-instance data.

If you for some reason have data that needs to be common to all instances of the your device class, then I would implement a singleton
internal to your device class that all instances would then share – although then you have to decide which device will be responsible for serializing your data on load/save. On the whole, unless you have a very good reason I would go for the first method.

Your second issue is a bug on my side. actionManipulator’s return value was not being used to invalidate the manipulators like it should. This is now in the bugtracker and fixed.

Great to hear you fixed issue 2. Do you have any release schedule? Or do you do private patches?

If it doesn’t create the device on the fly, how come I get this?

The address is the pointer of the inverter:

000000000A6C8EF0 Inverter::Activate
000000000A6CA5D0 Inverter::Activate
000000000A6C9690 Inverter::Activate
000000000A6C8EF0 Inverter::Activate
000000000A6C9E30 Inverter::Activate
000000000A6C9690 Inverter::Activate
000000000A6CA5D0 Inverter::Activate
000000000A6C8EF0 Inverter::Activate
000000000A6C7FB0 Inverter::Activate
000000000A6C8750 Inverter::Activate
000000000A6C9E30 Inverter::Activate

Henning

That does take an explanation:

Essentially, your device has a lifetime of the world, but sometimes World Machine will create copies of the world for its own purposes. In particular, Layout View will grant each of its render threads a copy of the world so that they can work in parallel with no worries about contention issues. Thus you’ll see extra copies of your device be spawned and activated, but the master world is the “real” copy.

From the device perspective, none of that matters. And if your device is “typical” (simply has parameters, etc) then nothing special needs to be done.

If you store more exotic data in your device, as I believe you are aiming to do, then you must implement the function doClone as shown below:

class MyDevice : public Device {
protected:
virtual void doClone(Device *other);			// Copy data from other to this
std::vector<CoordF> vertices;
}

void MyDevice::doClone(Device *other) {
// call parent class
   Device::doClone(other);
// copy my data
  vertices = other->vertices;
..

}

Hope that clears it up!

Excellent, now it works! Thanks for the help!