According to Wikipedia, a Gizmo is " ... a bounding box used for manipulating objects in 3D modeling computer programs".
Well ... not necessary a box ... and not always used for manipulating objects - couldn't a Gizmo be used just as an onscreen informative tool?
In the following post I invite you to explore with me the visual customization of modifiers and how gizmos or as 3ds Max call them modifier apparatus are drawn on the screen. All this will be examined by creating a simple modifier plugin that will serve as an on-screen informative tool for the object it modifies.
Of course, to implement a modifier we have to rely on the Modifier class, and its always worth starting class analysis by having a look at inheritance diagram of the class we have under the scope:
Image may be NSFW.
Clik here to view.
According to class description for the BaseObject Class
"Anything with a representation in the 3D viewports is derived from BaseObject (including modifiers and controllers whose gizmos appear in the viewports)".
Ok, so this is where it all starts and why we have the following requirement:
A modifier plug-in must implement a method to display the modifier's gizmo in the 3D viewport. This is done by implementing a member function of the BaseObject class called BaseObject::Display(). The GraphicsWindow class provides a number of drawing routines for this purpose.
Having defined our starting point, let us continue by gathering the implementation pieces that we will need for our project.
Implementation
As already stated above, to have something displayed, we have to inherit from the BaseObject class, but for our particular need we are going to take the Modifier branch.
Image may be NSFW.
Clik here to view.
Within the BaseObject class, we have a special interest in the BaseObject::Display() method:
virtual int Display(TimeValue t, INode* inode, ViewExp* vpt, int flags);
This method is called by 3ds Max when it is time to draw the object. It will "point" you to the viewport information given by ViewExp class and provides other information. The ViewExp class contains the information on GraphicWindow associated with the given viewport:
virtual ViewExp::GraphicsWindow* getGW();
Now comes the real fun:
The GraphicsWindow class “provides low-level access to 3ds Max's graphics system“, that we will be using to explicitly draw lines, texts, markers, etc. to the viewport - everything we need for drawing our Gizmo. To make the drawing task easier, we will use the DrawLineProc Class, that"... provides a simplified way to draw a connected series of lines to the GraphicsWindow":
int DrawLineProc::proc(Point3 *p, int n);
However, for the text draw, we still are going to use the "raw" approach with:
void GraphicsWindow::text(Point3* xyz,const MCHAR* s);
We have all the details we need to create an interesting gozmo, so let's pass from theory to practice by building something.
Hands-on
We will start by setting up a project and creating a Modifier type plugin. For simplicity, (to avoid having implementing a lot of methods uninteresting to our current task), it will be based on SimpleMod2, but the same approach is also applicable to modifiers directly based on the Modifier Class. We can use the Plugin Wizard to start the project as seen below. In this example, we will call the plugin “Informix”.
Image may be NSFW.
Clik here to view.
Before firing up the plugin, don't forget to make the necessary adjustments and switch to the Hybrid configuration:
Image may be NSFW.
Clik here to view.
To start with the basic implementation, if we run our “Informix” plugin on the iconic Teapot, we will get a displayed bounding box. For example:
Image may be NSFW.
Clik here to view.
This bounding box is given by the default implementation of the Display within the SimpleMod class, but we should not have problems in overriding it:
int Display(TimeValue t, INode* inode, ViewExp* vpt, int flagst, ModContext* mc) override;
Here is a basic implementation that will start our task of providing a more interesting gizmo:
int Informix::Display(TimeValue t, INode* inode, ViewExp* vpt, int flagst, ModContext* mc)
{
GraphicsWindow *gw = vpt->getGW();
DrawLineProc lp(gw);
lp.SetLineColor(1, 0, 0);
Point3 my_gismo_points[5] = { Point3(0, 0, 0), Point3(-80, 0, 0),
Point3(-60, 10, 0), Point3(-80, 0, 0),
Point3(-60, -10, 0) };
lp.proc(my_gismo_points, 5);
gw->setColor(TEXT_COLOR, 1, 1, 1);
gw->text(&my_gismo_points[1], L"Here's the tip of my GIZMO!!!");
return 0;
}
Now, if you run the plugin again, you should see something like this:
Image may be NSFW.
Clik here to view.
Quite simple, right? A bunch of points and some calls to draw lines.
Well ... easy things get complicated very fast when you draw something custom and parametric.
Now we can start by creating a method responsible for drawing a double arrowhead segment with a label in the center of the segment:
void Informix::drawMyArrows(Point3 p1, Point3 p2, const wchar_t* my_text, ViewExp* vpt)
{
float arrow_scale_factor = 0.05;
float arrow_wing_factor = 10.0f; // max(max(p1.z + p2.z, p1.x + p2.x)*arrow_scale_factor, 1);
pblock2->GetValue(pb_wing_spin, GetCOREInterface()->GetTime(), arrow_wing_factor, FOREVER);
Point3 wing_side_point(0,0,0);
Point3 wing_other_side_point(0,0,0);
Point3 ref_point((p1.x + p2.x), (p1.y + p2.y), (p1.z + p2.z));
Point3 midpoint = ref_point / 2;
//prepare point for left arrow
Point3 left_reference_point = p1*(1-arrow_scale_factor)+p2*arrow_scale_factor;
makeWingsidePoints(left_reference_point, wing_side_point, wing_other_side_point, arrow_wing_factor, p1);
Point3 my_gizmo_point_left[5] = { p2, p1, wing_side_point, p1, wing_other_side_point };
//prepare point for right arrow
Point3 right_reference_point = p1*arrow_scale_factor + p2*(1 - arrow_scale_factor);
makeWingsidePoints(right_reference_point, wing_side_point, wing_other_side_point, arrow_wing_factor, p1);
Point3 my_gizmo_point_right[5] = { p1, p2, wing_side_point, p2, wing_other_side_point };
//draw points and labels
GraphicsWindow *gw = vpt->getGW();
DrawLineProc lp(gw);
lp.SetLineColor(0, 0, 0);
lp.proc(my_gizmo_point_left, 5);
lp.proc(my_gizmo_point_right, 5);
gw->setColor(TEXT_COLOR, 0, 0, 0);
gw->text(&midpoint, my_text);
}
Now we can organize some repetitive code into a helper method:
void Informix::makeWingsidePoints( Point3 reference_point,
Point3 &wing_side_point,
Point3 &wing_other_side_point,
float arrow_wing_factor,
const Point3 source)
{
wing_side_point = reference_point;
wing_other_side_point = wing_side_point;
if(reference_point.x != source.x)
{
wing_side_point.z -= arrow_wing_factor;
wing_other_side_point.z += arrow_wing_factor;
}
else
{
wing_side_point.x -= arrow_wing_factor;
wing_other_side_point.x += arrow_wing_factor;
}
wing_side_point.y -= arrow_wing_factor;
wing_other_side_point.y += arrow_wing_factor;
}
Now our updated Modifier::Display() method is easier to read:
int Informix::Display(TimeValue t, INode* inode, ViewExp* vpt, int flagst, ModContext* mc)
{
TimeValue current_time = GetCOREInterface()->GetTime();
Box3 bounding_box;
inode->EvalWorldState(current_time).obj->GetLocalBoundBox(current_time, inode,vpt,bounding_box);
Point3 min = bounding_box.Min();
Point3 max = bounding_box.Max();
Point3 x_max(max.x, min.y, min.z);
Point3 y_max(min.x, max.y, min.z);
Point3 z_max(min.x, min.y, max.z);
std::wstring label_x = std::to_wstring(max.x - min.x) + L" units";
std::wstring label_y = std::to_wstring(max.y - min.y) + L" units";
std::wstring label_z = std::to_wstring(max.z - min.z) + L" units";
drawMyArrows(min, x_max, label_x.c_str(), vpt);
drawMyArrows(min, y_max, label_y.c_str(), vpt);
drawMyArrows(min, z_max, label_z.c_str(), vpt);
return 0;
}
Basically this is it.
Having this coded in a simple combination, we end up having the following result when applying our modifier to an object:
Image may be NSFW.
Clik here to view.
Conclusion
Although this example is not terribly useful, I hope the sample code shows you enough to implement your own cool gizmos!
Image may be NSFW.
Clik here to view.
and coming back to Wikipedia definition ...
Is it a box?
I think not.
Is it used to manipulate objects?
Not really.
Is it useful?
Wait a minute ... the definition doesn't say that it should be useful.
Then, is it a Gizmo?
I have no idea, but somewhere along the way of its creation, it was.
If you are interested in the source code, it is available here, which is great for a beginner, but for those who found it boring, you can look at the Symmetry plugin (Symmetry.cpp), available in the SDK samples within the .\maxsdk\samples\modifiers\BasicOps\ folder.
Clik here to view.