A SimpleMod plug-in is a deformation-type modifier that moves vertices around, but does not change the topology (adding or deleting vertices, faces, surfaces, lines, and so on).
Examples of similar 3ds Max modifiers are Bend, Stretch, and Taper.
These kinds of modifiers require only a single map
handler to be provided for moving vertices around and they automatically supply 3D box deformation gizmo and center sub-objects.
A scripted Simple Modifier plug-in is declared by specifying the <superclass>
as simpleMod
.
SCRIPT
plugin simpleMod saddle
name:"SaddleDeform"
classID:#(685325,452281)
version:1
(
parameters main rollout:params
(
amount type:#integer ui:amtSpin default:20
)
rollout params "Saddle Parameters"
(
spinner amtSpin "Amount: " type:#integer range:[0,1000,20]
)
on map i p do
(
p.z += amount * sin((p.x * 22.5/extent.x) * (p.y * 22.5/extent.y))
p
)
)
This script defines a new SimpleMod plug-in named SaddleDeform. It applies a saddle-type deformation to an object, curving up two opposite corners, and curving down the other two opposite corners (try it on a plane object to see this best). There is a single parameter spinner, amount
that specifies how much deformation to apply. The key component of a SimpleMod plug-in is the map
handler.
Its form is:
on map <index> <point> do <expr>
This event handler is called once for every point in the object (or current point selection in the object) being modified. These points might be mesh vertices, spline vertices, NURBS CVs, and so on. The <index>
argument gives the number of the point, but this index might not correspond to a vertex number in a mesh or a spline. The <point>
argument supplied to the map
handler gives the current object-space coordinates of the point as a Point3. The function must modify the supplied point value as appropriate and return it as the result of the map
handler call. In the example above, the map
handler applies a Z-coordinate deformation using the equation z = sin(x * y)
, so all the points retain their X and Y coordinates and the Z is moved up or down to follow the saddle function. Note that all coordinates are object-local.
The <index>
value is 1-based. However, the scripted plug-in is called with an <index>
value of 0 to signal that this particular map call is being used by the gizmo bounding box drawing code to compute points in the gizmo box to draw. This hit testing is performed on the object the SimpleMod modifier is applied to and the gizmo bounding box is displayed. This will be the case if the object is selected, the Modifier panel is active, and the SimpleMod modifier is selected in the object’s modifier stack.
It is basically up to the object being modified to decide what index value to pass. For example, for meshes it is the actual mesh vertex index. For patches, it is the control points first all in one sequence, followed by the in and out vectors for each control point in control point order. For splines, it is the control point, in vector, and out vector in control point sequence within the curve sequence. This ordering might change in later versions of 3ds Max.
Because the map
handler can be called many times, even on simple objects, it is highly recommended that you minimize the number of values created in the map
handler. This will reduce the need for garbage collection to be run. If you need several local variables in the map handler, it is recommended that you declare one or more Point2 or Point3 values in the scripted plug-in definition, and then store values to the component values of the Point2 or Point3 values. This will prevent new local variables from being allocated in the map
handler.
You must not try to access the object being modified from within the map
handler as this will attempt to evaluate the scripted modifier again. This will result in an infinite loop and hang 3ds Max.
There are also pre-defined plug-in locals you can access in the map
handler as follows:
min -- the modifier context's bounding box min coordinate
max -- the modifier context's bounding box max coordinate
center -- the modifier context's bounding box center coordinate
extent -- the modifier context's bounding box extent or size
In the example above, the map handler uses the extent
pre-defined local variable to compute a scaling for the saddle function, so that it gets a 1/8th cycle of the function (22.5 degrees) across the whole object. These bounding box locals relate to the modifier's context, either the whole object or group of objects if applied to a scene node selection, or sub-object selection if there is a sub-object selection modifier below it on the stack (such as, a Mesh Select).
You can also use some other built-in capabilities of SimpleMod to implement modifier limits display. To do this, implement handlers for the following (you must implement all if you implement any):
on modLimitZMin do <expr>
on modLimitZMax do <expr>
on modLimitAxis do <expr>
If called, the on modLimitZMin
and on modLimitZMax
handler expressions must evaluate to float values corresponding to the minimum and maximum limits and the on modLimitAxis
handler expression must return #x
, #y
, or #z
to specify the limit axis. The MAXScript global variable currentTime
contains the time at which these values must be computed. Generally, simple access to parameter values will yield the correct currentTime
value automatically. The simplest way to implement these handlers is to maintain limit parameters and associated spinners and checkboxes, and simply pass these parameter values back.
Note that these handlers do not implement the actual limiting of the effect to the limit values, only the gizmo display of the limits. To actually apply the limits, you must provide the necessary logic and calculations within the on map ... do()
event handler.
The following example implements a radial Bulge effect relative to the Z axis, and provides limits expressed as Percentage of the bounding box Z height. When enabled, the p.z coordinate normalized by the height of the bounding box is compared to the lower and upper limits and the effect is only applied if the value is between the limits. As the effect and limits are always applied using the Z axis, the on modLimitAxis do() handler always returns #z.
SCRIPT
plugin simpleMod bulgeZ
name:"BulgeZ"
classID:#(0xa4741be, 0x953eaf3)
version:1
(
parameters main rollout:params
(
amplitude type:#float ui:spn_amplitude default:1
wavecount type:#float ui:spn_wavecount default:1
phase type:#float ui:spn_phase default:0
limit type:#boolean ui:chk_limit default:false
maxLimit type:#float ui:spn_maxLimit default:100
minLimit type:#float ui:spn_minLimit default:0
)
rollout params "BulgeZ Parameters"
(
spinner spn_amplitude "Amplitude: " type:#float range:[0,1000,1]
spinner spn_wavecount "Wave Count: " type:#float range:[0,1000,1]
spinner spn_phase "Phase:" type:#float range:[-100000,100000,0] scale:0.01
group "Limits"
(
checkbox chk_limit "Limit Effect" align:#right offset:[8,-5]
spinner spn_maxLimit "Upper Limit %:" range:[-1000,1000,100]
spinner spn_minLimit "Lower Limit %:" range:[-1000,1000,0]
)
)
on map i p do
(
local v = normalize [p.x-center.x, p.y-center.y,0]
local pzn = p.z/(max.z-min.z)
if not limit or (pzn >= minLimit*0.01 and pzn <= maxLimit*0.01) do
p += v*amplitude*(cos (360*(pzn-phase)*wavecount))
p
)
on modLimitZMin do if limit then minLimit*0.01*(max.z-min.z) else 0.0
on modLimitZMax do if limit then maxLimit*0.01*(max.z-min.z) else 0.0
on modLimitAxis do #z
)
The Modify panel actually creates a fresh instance of every modifier when it is to be shown in the Modifier list or the buttons in the Modifiers rollout. This will cause the create
handler to be called for a scripted SimpleMod plug-in. No special handling in the create
handler is required for this case.