home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Maximum 3D 3
/
m3d-p3.iso
/
3DS_MAX
/
3DSMAX.2_0
/
SCRIPTS
/
DEMO.MS
< prev
next >
Wrap
Text File
|
1997-10-19
|
39KB
|
1,015 lines
--
-- demo.ms - MAXScript demo
--
-- John Wainwright
--
-- This script is intended to give an interactive walk-through of
-- some of MAXScript's main features. The best way to view this
-- file is to step through it in the MAXScript listener window.
-- Here are the instructions:
--
-- 1. when MAX is running, choose the Utility command panel and
-- select MAXScript from the utility list
-- 2. click [Open listener] in the MAXScript rollout panel and the
-- interactive listener window opens
-- 3. in the listener window, type:
-- include "demo.ms"
-- and and hit <enter> and this file should start filling up the
-- listener window. The include command just inserts the named
-- text file into the listener as though you'd typed it all in.
-- 4. you can scroll around the text in the listener (unlike the
-- standard DOS prompt window) and re-execute any of the code
-- that's there, so, scroll the window back to the top
-- 5. position the cursor on the "2 + 2 * 3" line below by clicking
-- on the line and then hit the number-pad <enter> key. The
-- expression is evaluated and the result printed out. You can
-- put the cursor anywhere on a line, so long as it is not selecting
-- any run of characters, and the line will be executed when you
-- hit number-pad <enter>. If you use the main <enter> key, it
-- will just insert a newline at the cursor, just like a text
-- editor. This difference is so you can both edit & re-evaluate
-- existing code. (Note that this is not the normal way of
-- editing permanent scripts, you use separate script editor
-- windows for this, which are demonstrated later in the walk
-- through). You can also select a run of characters in a line
-- and hit enter and that sub-expression will be evaluated &
-- the result printed out on the next line. If you don't have
-- a number-pad on your machine, you can use shift <enter> as an
-- alternative to number-pad <enter>.
-- 6. step through each example placing the cursor somewhere on each
-- line and hitting number-pad <enter> to see what it does.
-- You can select several lines at once and hit enter and it will
-- evaluate each complete statement in the selection and print
-- it's value. Some code fragments extend over several lines, so
-- you select them all and hit number-pad <enter>. For the
-- first few instances of this, the comments (introduced by
-- a '--') tell you what to select.
-- 7. if you want to experiment, you can type new code into the
-- listener at any point and hit number-pad <enter> to evaluate
-- it.
-- 8. have fun.
--
-- first, some language basics...
2 + 2 * 3 -- infix algebraic expressions, standard precedence
x = 1 -- assignment to automatically create variables
x = x + 1
x = "foo" -- variables can contain any kind of object
x * 2 -- but you can't make type mistakes; this prints an error
-- MAXScript has all the standard linear algebra types & operators
x = [5, 10, 15] -- 3D vector
x + [3, 2, 1] -- vector addition
x * 1.2 -- scalar multiplication
q = Quat 45 x_axis -- quaternions, matrices, euler angles, etc.
m = q as Matrix3 -- conversion between appropriate types
x * m -- vector transformed by a matrix
-- many values in MAXScript have subcomponents or
-- properties which you get at with the '.' operator
x.y -- the y component of the vector
q.angle -- the quaternion's (derived) angle
x.y = 23 -- set properties through assignment
x
m.row2.y -- and cascade for nested access
-- MAXScript also has a general purpose dynamic array value that
-- can hold values of any kind
a = #(1, 2, [5,3,2], "foo") -- #(...) defines an array literal
a[2] -- an array element, indexes start at 1
a[4] = "baz" -- assignment to an element
a[5] = 1.5 -- arrays extend automatically
append a "more" -- or build them by appending
-- memory management is automatic in MAXScript. It detects
-- when some value is no longer referenced and reclaims its
-- memory, so, for example, if you evaluated:
a = 23
-- memory taken up by the array that was in the variable 'a' will
-- be automatically recycled since nothing else refers to it.
-- This makes life easy.
-- there are several distinguished values in MAXScript
true -- booleans...
false
4 > 5 -- which are the results of comparisons
on -- synonyms for true &
off -- false
undefined -- the undefined value which you get...
foo -- if you access an as yet unused variable
b = #(1, 2, 3)
b[23] -- or an array element that isn't there
-- MAXScript has all the standard control structures:
if a > 5 then print "yes" else print "no"
for i = 1 to 10 do print i
while a > 20 do print (a -= 1)
-- you'll notice that some of the command output duplicates
-- the last line. This is because MAXScript is an
-- "expression-based" language and all of the constructs,
-- including control structures, yield a value. Every
-- control structure doubles as an expression, for example
-- the if-statement works as an if-expression, so you can say
-- things like:
x = if a > 5 then "yes" else "no"
-- or even:
x = (if a > 5 then sin else cos) a
-- in which an if-expression chooses the function to
-- call. MAXScript is also known as a
-- "fully first class" language, and entities like
-- functions are values that can be computed and
-- passed around as in this example.
-- the for-loop also automatically iterates over any of
-- the collections in MAXScript:
a = #(1, "two", 3.3, [4,4,4])
for i in a do print i
-- MAXScript is block-structured. Blocks of code are
-- surrounded by parentheses, as in the next example. This is
-- a multi-line expression, so you have to drag-select
-- the whole thing and hit number-pad <enter>.
for i = 1 to 5 do
(
local x = sqrt i -- you can declare locals inside blocks
print x -- they are lexically scoped
)
-- you can also put several expressions on one line
-- with ';' separators:
for i = 1 to 5 do ( local x = sqrt i; print x )
-- the for-loop has a variant that collects things
-- into arrays. So,
sqrts = for i in 1 to 10 collect sqrt i
-- generates an array of square roots. This form
-- is handy for procedurally gathering sets of
-- scene objects to work on.
-- Function definition is simple. The following defines
-- a function called 'add' that takes two parameters 'a' & 'b'.
-- The body (after the '=') can be a block expression,
-- and can contain locals, etc.
function add a b = a + b
add 2 3.5 -- call it
-- the syntax function calls is command-language-like,
-- there are no parentheses or commas, you just line up the
-- arguments one after the other. This simplifies the syntax a
-- lot, and makes it easy to have optional keyword arguments
-- functions can also be recursive, the 'fn' here, is short just
-- for 'function'
fn fac n = if n <= 0 then 1 else n * fac (n - 1)
fac 6
-- now some MAX stuff...
-- first, simple object creation and animation. These early examples
-- are things you could do more quickly in the UI, but show the building
-- blocks that will be used to do the hard stuff later
-- create a box and a sphere:
b = box length:20 width:20 height:20
s = sphere radius:12 segs:20 position:[75,20,0]
-- they turn up in the scene, and you've got hold of them in variables b & s
-- the keyword arguments you can specify are
-- 1. any of the creation params for the object type
-- 2. any transforms as .pos, .rotation, .scale
-- 3. general node properties like .name, .wireColor, etc.
-- they are all optional and have useful defaults
-- you can get at MAX object properties as you do other properties
-- in MAXScript:
b.pos -- get position
b.pos = [0,-120,10] -- or set it and the box jumps forward
-- any time you change a property of a MAX object like this,
-- MAXScript immediately reflects the change in the scene, as though
-- you had adjusted the object in the UI
-- you can also create modifiers and add them to an object.
-- The following creates a twist modifier set at 30 degrees and
-- applies it to the box which is in variable b
addModifier b (twist angle:30)
-- bump up the segs
b.heightsegs = 10; b.lengthsegs = b.widthsegs = 5
-- and change the modifier
b.twist.angle = 60
-- modifiers turn up as named properties on the object
b.scale *= 0.5 -- scale the box down a bit
-- in the last example, one of the "math-assignment" operators
-- has been used to modify a property in place. The above
-- line is equivalent to: b.scale = b.scale * 0.5
-- there are math-assignment operators for +,*,-,/
-- Again, all the parameters you can get at in the MAX rollouts
-- are accessible as properties in MAXScript
-- Now some animation.
--
-- MAXScript has several syntax forms that mirror the main MAX UI tools,
-- like the animate button and the time slider and the working coordinate
-- system. The next example 'turns on' the animate button
-- for a block of code and 'sets the slider' twice and moves
-- the objects around, mirroring what you'd do in the UI.
-- (remember to drag-select all the lines & hit numberpad-<enter>)
animate on
(
at time 0 (b.pos = [-100, 0, 0]; s.scale = [1, 1, 0.25])
at time 100 (b.pos = [100, 0, 0]; s.scale = [1, 1, 3])
)
-- now drag the slider around and see that it's made some animation
-- and put keyframes at 0 & 100
-- when you use animate & time prefixes like this, the actual UI
-- animate button doesn't come on and the slider
-- doesn't move, it's virtual within MAXScript.
-- in these examples, the time was given as a simple number. This
-- is interpreted as a frame number by MASXScript. You can also
-- use one of the various time literals that look like this:
2.5s -- 2.5 seconds (all times print out as frames)
20f -- 20 frames
4800t -- 4800 ticks = 1 sec = 30f
1m3s5f -- 1 min, 3 secs, 5 frames
1:15.5 -- SMPTE 1 min, 15 secs, 5 frames
-- you can put the 'context prefixes' on simple expressions to affect
-- their evaluation:
p1 = at time 10 b.pos -- grab pos at time 10 into a variable
p2 = at time 50 b.pos -- and pos at time 50
distance p2 p1 -- and do some across-time computation
-- the following adds a little 4 seg sphere and makes it do a wobbly chase
-- of the box (again, select all the lines at once)
s2 = sphere radius:3 segs:4
animate on for t in 0 to 100 by 5 do
at time t s2.pos = b.pos + random [-11,-11,-11] [11,11,11]
-- this turns on animate, loops over time in t every
-- 5 frames, sets the current time each time through the loop
-- and sets the follower's pos to the box's pos + a random
-- vector. Note that the animate command is split into
-- two lines; MAXScript has a kind of free-form layout and
-- there are some simple rules in the online docs that
-- tell where you can split commands into multiple lines.
-- drag the time slider about to see the small sphere
-- chasing the box. Leave the slider at about frame 50.
-- the "animate" and "at time" contexts are one of several
-- that are in MAXScript. Others can be use to set
-- working coordinate systems, rotation centers,
-- working levels in the scene hierarchy, etc. An important
-- context is "undo" for controlling whether or not
-- scripted operations are undoable in MAX. By default,
-- scripted operations in the Listener are undoable, but those in
-- utility & controller scripts are not since it is very
-- easy to flood the undo buffers if you script a large number
-- of changes. You turn on or off undo for a sequence of
-- expressions like this:
undo on
(
delete s
b.height += 30
s2.radius = 25
)
-- you can then undo this set of changes, *as a whole*, either
-- using the undo button in MAX or with the command:
max undo
-- All the contexts, including undo, can be set permanently while
-- working in the listener, using the 'set' construct, for example:
set undo on
-- now each individual evaluation in the listener will
-- add an entry to the undo stack
-- Up until now you've worked with freshly created objects that
-- were kept in MAXScript variables. You can also name
-- existing objects in the MAX scene.
-- first, open a scene that's got some objects:
loadMaxFile "msdemo1.max"
-- this is the tutorial ikwalk.max file and has a
-- hierarchy of objects in the robot.
-- <you can load & merge & save using built-in MAXScript
-- functions>
-- To name a scene object & work with it in MAXScript, you
-- use a 'pathname'. It starts with a '$', like this:
select $torso
-- this calls the MAXScript function 'select' to select the object
-- named 'torso' in the scene
--
-- since MAX objects are arranged in a hierarchy and they
-- can have non-unique names, you can use a 'path' through
-- the hierarchy to help name an exact object:
select $torso/leftupperleg/leftknee/leftlowerleg
-- the slashes navigate through the heirarchy to a particular spot,
-- much like file pathnames in Windows
--
-- you can also use wild cards patterns in pathnames to
-- name sets of related objects:
select $torso/* -- all immediate children
select $torso...* -- the whole robot
select $*lower*...* -- the lower extremities
select $left* -- the left side
select $*leg* -- both legs
-- once you've named a set you can work on it as a whole
-- like you can in MAX:
selection.radius = 1 -- make all the legs skinny
-- this used one of MAXScript's built in 'object sets', in
-- this case the current selection. They act like
-- pathname sets. You could also have used the pathname
-- in the assignment directly:
$*leg*.radius += 1 -- make him healthy again
scale $*foot* [2,1,1] -- give him clown feet
rotate $*leftupper* -35 y_axis -- and exercise
select geometry -- here are some more object sets
select lights -- all the categories in MAX are available
hide objects -- 'objects' gives you everything in the scene
-- you can also invoke most of the MAX menu and toolbar functions
-- using the 'max' command, the list is in the manual.
-- answer NO to the file save question that pops up and yes to
-- the reset question
max select none
max reset file
-- now create some geometry that's a bit more tedious to do
-- by hand:
for i in 1 to 10 do
sphere radius: (i*10) position: [0, (10*(1.0*i)^2) - 10, 0]
-- this makes 10 spheres of increasing radius that touch exactly,
-- a bit more of a chore to do interactively in MAX
-- do it again, but this time grab the spheres into a MAXScript
-- array:
hide geometry -- clear the scene first
spheres = for i in 1 to 10 collect
sphere radius: (i*10) position: [(10*(1.0*i)^2) - 10, 0, 0]
-- the 'collect' form of the for-loop gathers all the
-- results into an array. You can then index them or
-- loop over them:
spheres[3]
for i in spheres do print i.radius
-- now make a variant of the 'array layout' tool in MAX. Here
-- you want the line of touching spheres to be instance-cloned
-- and rotated in an increasing angle as it clones.
-- <give it a second or so to run - MAX takes a little time
-- creating lots of objects>
inc = 10
for angle in 20 to 360 by inc do
(
instance spheres rotation:(Quat angle z_axis)
inc += 10
)
-- <you can zoom extents and move the listener to see the results
-- better>
-- this loops over an increasing angle and instances the whole
-- array's worth each time, setting each sphere's rotation
-- You can use an array of objects anywhere you use a pathname
-- and it does the 'automatic' mapping of the function over all the
-- array elements for you
-- Making a selection based on some geometric property is handy:
for s in $sph* where s.radius > 30 and s.radius < 70 do selectmore s
-- this looped over all the spheres and added the ones in a certain
-- radius range to the current selection, now operate on them:
selection.radius = 30
-- you can see it's possible to use MAXScript casually to help your
-- direct manipulation work in MAX.
max select none
-- some more neat tricks... you don't want the spheres too neatly
-- lined up; the following shuggles each one about a bit:
coordsys local for s in $sph* do s.pos = random [-40,-40,0] [40,40,0]
-- this shows the use of a coordinate system context. Such context
-- prefixes effect transforms in MAXScript exactly the way they do
-- in MAX. This example loops over the spheres and in each's local
-- coordinate system, sets its position to a small random vector, so
-- jiggling them all about. You can click back on the command
-- above & eval it (by hitting numpad-<enter> several times and
-- each time the positions are randomized a bit more.
-- How about a radial scatter. Here's a function to do this:
--
-- (drag-select the whole thing & numpad-<enter> to define the
-- function)
mapped function radial_scatter obj centre amount =
(
local delta = obj.pos - centre,
stretch = length delta * amount/100.0,
stretchlow = stretch - stretch/4,
stretchhigh = stretch + stretch/4
obj.pos = centre + delta * random stretchlow stretchhigh
)
-- this takes an object, a center and a 'scatter' amount. Notice
-- that the computations are all on vectors - vector math is
-- symbolic in MAXScript. It computes a random radial move
-- proportional to the distance from the center and shifts
-- the object. Now apply it to all spheres:
radial_scatter $sph* [0,0,0] 0.2
-- you can make scripted functions that are automatically mapped
-- over a collection by preceding their definition with the word
-- 'mapped', so here the scatter function gets called many times,
-- each time being given the next sphere in $sph*
max reset file -- choose no and yes.
max utility mode
-- Everyone wants a starfield. Here's a function to generate one
-- out of geometry. It takes a star count and optional keyword
-- arguments for position & extent.
fn starfield count extent:[200,200,200] pos:[0,0,0] =
(
local f = pos - extent / 2,
t = pos + extent / 2
for i = 1 to count do
sphere name:"star" radius:(random 0.1 2.0) \
position:(random f t) \
segs:4 smooth:false
)
-- this makes a bunch of 4-sided, non-smooth spheres scattered
-- throughout the extent.
--
-- The optional keyword arguments defined above have values
-- after them which are used as defaults if the arguments are
-- not supplied by the caller.
starfield 250 -- 250 stars, default bounds, please wait.
-- again, most of the time here is in the actual object creation
-- inside MAX.
-- if you wanted a spherical ball of stars you could select the
-- stars further than some distance from the center of the extent:
for s in $star* where distance s.position [0,0,0] > 100 do selectmore s
-- and trim them away
max delete
-- now some more complicated animation:
loadMaxFile "msdemo2.max"
-- this file has two objects, a traveling sphere and a tall
-- box that has a bend modifier on it. If you wave the time slider
-- about, you can see the ball move. The following animates the bend
-- so the box sways around, following the sphere, like a look-at
-- controller but deforming the top of the box and keeping
-- its base still.
--
-- here's a function that takes a target and something to bend at it:
fn bendtracker bender target =
(
animate on
for t = 0 to 100 do at time t
(
d = target.pos - bender.pos
bender.bend.angle = atan2 d.z d.x
bender.bend.direction = -(atan2 d.y d.x)
)
)
-- the function turns on animate and loops over t over animation time.
-- it gets the delta vector by substracting the bender pos from the
-- target pos. Since this is in a changing time context,
-- each time it is computed it will use the animated
-- properties of the objects at that point in time.
-- It does some trig to figure bend angle & direction and plants
-- keyframes on the bend modifier. Call it on the two scene objects,
-- and then wave the time slider to see the result:
bendtracker $bender $target
-- Something even trickier: floating teapots.
loadMaxFile "msdemo3.max"
-- this scene has a teapot, an animated ripply surface and
-- a tape helper. The following makes the teapot float on the
-- rippling surface at the point that the tape instersects
-- the surface, moving & rotating to follow the surface
-- as it ripples about. There was a brilliant, 20 or 30 step
-- tutorial by Randy Kreitzman about doing this in MAX r1 using
-- several dummies & expression controllers. And, of course,
-- the Attach controller in MAX r2 does it all for you, but
-- here it is in MAXScript:
fn sail surface item tape =
(
local ir = intersectRay surface (tape as ray)
item.dir = ir.dir
item.pos = ir.pos
)
animate on
for t = 0 to 100 by 5 do at time t sail $box01 $teapot01 $tape01
-- <play the animation to see the results>
-- the function takes a surface, a floater and a
-- tape helper (it can be anything with a target)
-- and uses a built-in function to compute the normal
-- ray at the surface/ray intersection, then sets
-- the floaters direction & position to those
-- components of the normal ray. The 'dir' property
-- is one of the handy 'virtual' properties that MAXScript provides;
-- it sets the direction of the object's local z-axis to point
-- along the given direction vector.
--
-- the animate should be clear by now. Note that the context
-- clauses, like time & animate are 'dynamic' in their effect.
-- this means any functions you call and any they call, and so on,
-- observe the current context.
-- the surface floater is sufficiently neat to warrant turning into
-- a utility with a user interface (at least it was in MAX r1), so...
-- the following command opens a script editor window on the named
-- file. It contains a variant of the sail function and a utility
-- panel rollout written in MAXScript. When the window is open,
-- check the code out, then evaluate it by choosing "Evalute All" in
-- the File menu (or hitting ctrl-e) and come back here.
-- <script editors let you write larger scripts
-- that can be saved to & got from files and let you <enter> eval
-- selections in them, the results printing in the listener>
edit "floater.ms"
-- the "Surface Floater" utility definition has been added to the
-- utilities drop-down list in the MAXScript panel. (There are some
-- others there also that we're loaded from the startup script that
-- we'll look at later.) When you select it, the scripted panel opens
-- and you can pick the surface, floater & intersector, do an
-- immediate float or animate it over chosen frames and adjust
-- the position of the floater relative to the surface. The
-- x,y,z & roll spinners are live, as soon as you pick a floater you
-- can move it with them.
-- Set the z offset to -4 and the roll to -90 and click animate
-- again. The teapot looks like it as some weight now and
-- settles into the surface a bit.
-- Close the utility
-- If you study the script file you'll see the floater function
-- has been enhanced with a roll and a local offset parameter
-- for the spinners to use. The local coordsys code
-- at the end of the function does the work. Note that it
-- backscales the offset by the object's scale in case it
-- isn't unity, so the offset coord is always in world-space units.
--
-- A utility definition is made up of a set of locals that
-- stay alive until the panel is closed, a set of panel UI
-- controls (buttons, spinners, etc.) with names &
-- captions & setup parameters, and a set of handler functions
-- (the 'on' things) that get called as the user works with
-- the panel. Each kind of control has associated 'events'
-- (like pressed, picked, changed) and call the associated
-- handlers with appropriate arguments when those events happen.
-- Because a panel rollout is narrow, MAXScript can compute a
-- reasonable layout automatically, so you don't need a
-- layout editor.
max reset file
-- another use of the intersecting ray technique is for scattering
-- objects over a surface, such as growing hair.
-- (Of course, this has now been provided for with the
-- scatter compound object in r2!). The following function
-- takes a surface and a collection of prototype hairs and instances
-- clones of randomly chosen proto-hairs randomly over the surface.
-- open a test file:
loadMaxFile "msdemo9.max"
-- define the function...
fn grow_hair surf hairs count =
(
progressStart "Growing hair..."
local r = ray [0,0,0] x_axis,
len = length (surf.max - surf.min) * 2
for i in 1 to count do
(
-- place the ray at a random position outside the
-- surface pointing into the center & intersect
r.dir = random [-1,-1,-1] [1,1,1]
r.pos = surf.center + -r.dir * len
local ir = intersectRay surf r
-- instance a randomly chosen hair on the intersect ray
if ir != undefined do
instance hairs[random 1 hairs.count] name:"hairs" \
pos:ir.pos dir:ir.dir
-- update the prograss bar, exit loop if user canceled
if progressUpdate (i * 100 / count) == false then exit
)
-- close the progress bar
progressEnd ()
)
-- this function also demonstrates how to display a progress
-- bar and handle the cancel button.
grow_hair $surf1 $hair* 500 -- 500 hairs, please
-- notice how you can pass a wild-card pathname around as a
-- function argument and it acts like a collection of objects.
-- this function is actually pretty crude, there are better ways
-- to do this by iterating over the mesh itself and attaching
-- geometry onto one MAX object, but it highlights some
-- simple and powerful techniques
delete $hairs* -- delete the generated geometry
max reset file
-- you can construct and access bezier splines in MAXScript,
-- using them to help object layout and animation.
-- The following places spheres evenly-spaced along one curve,
-- using the second curve as a 'scaling' envelope
-- to control the diameter of each sphere as it is placed.
-- For each sphere, it uses the nearestpath() function
-- to compute the closest point on the envelope curve and
-- sets the sphere radius using the distance from it to that
-- point on the envelope
loadMaxFile "msdemo6.max"
unhide $line01
unhide $line04
len = curvelength $line01
for u = 0.0 to 1.0 by (12.0/len) do
(
local s = sphere radius:4 pos:(lengthinterp $line01 u)
local p = pathinterp $line04 (nearestpathparam $line04 s.pos)
s.radius = distance s.pos p
)
-- you can also make a curve act like a 'spine' for a
-- set of objects (like metaballs) and as you animate the
-- spine the balls move with it.
delete $sph* -- get rid of the balls
unhide $spine -- and show a spine and
unhide $ball* -- some balls along it
hide $line* -- hide the other stuff
-- wave the time slider about to see the spine curve animating. The
-- balls are placed along it at time zero.
-- the following computes 2 arrays with elements in it for each ball.
-- one has the nearest point parameters at time 0 for each ball
-- and the other has a distance from the nearest points to each ball
-- at time 0:
nus = at time 0
for b in $ball* collect nearestpathparam $spine b.pos
dists = at time 0
for i in 1 to nus.count collect
(
local b = $ball*[i],
d = distance b.pos (pathinterp $spine nus[i])
if b.pos.z > (pathinterp $spine nus[i]).z then d else -d
)
-- if you wave the time line around, you'll see that the spine is
-- a morphing curve. the following places a keyframe every 5 frames
-- for each ball, placing them at a point that maintains their original
-- orientation & distance from the curve's nearest point as the
-- curve moves
animate on
for t in 0 to 100 by 5 do at time t
for i in 1 to nus.count do
$ball*[i].pos = pathinterp $spine nus[i] +
((pathtangent $spine nus[i]) * dists[i]) * quat 90 y_axis
-- now wave the time slider about. the balls move in concert with the
-- spine. (there is a limitation here with only one spline, that
-- you can't control any twisting. a generalization would have 2
-- splines you could use as 'rail' to control twist over time over
-- the length of the spine.
-- the next thing to look at is mesh creation at the vertex level.
m = mesh vertices: #([0,0,65], [0,-25,0], [-10,-10,0], [-25,0,0], [-10,10,0], [0,25,0], [10,10,0], [25,0,0], [10,-10,0]) \
faces: #([1,3,2], [1,4,3], [1,5,4], [1,6,5], [1,7,6], [1,8,7], [1,9,8], [1,2,9])
-- the 'mesh' function takes several forms, in this case arrays of vertices
-- & faces (you can also add arrays of materialIDs, texture vertices,
-- etc.)
-- another variant generates a simple square mesh which you can then run
-- over moving the vertices around and do other stuff to it.
--
-- the following example generates surface meshes based on 3D math
-- surface functions.
--
-- first, some z functions of x & y:
fn saddle x y = sin(x * y) -- saddle
fn sombrero x y = (cos(x^2 + y^2)) / (1 + (x^2 + y^2)/45) -- sombrero
-- now a surface builder that will use the 3D surface function
-- you give it to build a mesh:
fn math_mesh func length:100 width:100
lengthSegs:16 widthSegs:16
zscale:10 xyrange:22.5 old_mesh: =
(
local m, vert_count = (lengthsegs + 1) * (widthsegs + 1),
xscale = xyrange as float / width,
yscale = xyrange as float / length
if old_mesh == unsupplied then
m = mesh length:length width:width lengthsegs:lengthsegs widthsegs:widthsegs
else
m = old_mesh
m.position = [-width/2, -length/2, 0]
for i = 1 to vert_count do
(
vertex = getvert m i
vertex.z = zscale * func (vertex.x * xscale) (vertex.y * yscale)
setvert m i vertex
)
update m
m
)
-- this takes some optional geometry & topology arguments and a 3D surface
-- function (MAXScript lets you pass functions around as parameters),
-- builds a mesh (or modifies one you optionally supply) and runs
-- across its vertices computing a new z for each using the
-- surface function. For those of you who have done SDK-level
-- mesh programming, vertex coordinates in MAXScript are
-- always given in the working coordinate system, in this case 'world'.
--
-- Select the definition and hit <enter> it to define the
-- function if you haven't already.
--
-- First move the existing mesh in the scene off to the side then
-- make some surfaces...
-- <move each aside as you make them, they all get built at [0,0,0]>
math_mesh saddle
math_mesh saddle lengthsegs:32 widthsegs:32 xyrange:45
math_mesh sombrero xyrange:45 zscale:40 lengthsegs:32 widthsegs:32
max reset file
-- The next example illustrates how a tech department in
-- a large house might set up a job-specific tool for the
-- artists and wrap it in a utility panel UI...
--
-- Here's the scenario:
-- You have generated some text files from an Excel spreadsheet;
-- one contains a list of bitmap file names in pairs, a diffuse
-- map and an opacity, for some background trees in a scene
edit "mapdef.dat"
-- the other file has some generated names & positions & rotations
-- for the backdrops
edit "place.dat"
-- you want a tool that let's the artist choose the map & position
-- files through a standard file open dialog and that will then
-- read the files and build materials from the map file info.
-- It then constructs a cross mesh and instances it at the locations
-- given in the placement file and randomly applies one of the
-- backdrop materials.
-- The following file defines the 'backdrop loading' function
-- and a utility panel to drive it. Open the file and evaluate
-- it by choosing "Evalute All" in the File menu (or hitting ctrl-e)
-- and come back here.
edit "backdrop.ms"
-- here's a simple scene file to load them into:
loadMaxFile "msdemo4.max"
-- Shrink or close the data file windows to clean up some space.
-- Select the "Load Backdrop" utility in the MAXScript panel
-- utility drop-down. Click the file chooser buttons in order and
-- locate the mapdef.dat & place.dat files in the scripts directory.
-- click "Load backdrops" and in come the backdrops. Render the
-- scene for a check. The adjustment spinners are active and
-- apply to all the backdrops so move them, -100 in y,
-- and scale them up, 1.5 for x,y,z and re-render.
-- The "Shuffle materials" button randomly reassigns the materials.
-- Click it a try another render.
-- The utility script file has some comments in it about how it works.
-- While still in this scene lets make some cameras and
-- access the renderer. First some cameras:
targetcamera pos:[3.5, -320, 50] target:(targetobject pos:[0, -100, 40])
targetcamera pos:[85, 30, 20] target:(targetobject pos:[-50, 1, 20])
targetcamera pos:[-2, -8.6, 450] target:(targetobject pos:[0, 0.5, 17])
-- note you have to make a target object explicitly for target cameras
-- (and target spots and tape helpers). The cameras provide
-- different angles on the scene. You can get a snapshot through each like
-- this:
for c in cameras do render camera:c outputwidth:320 outputheight:240
-- it loops over each camera in the scene and renders the views into
-- a separate bitmap object shown in its own virtual frame buffer.
-- Other optional keyword args let you specify outputfile and all
-- the common renderer parameters. See the docs.
-- There's another utility in the MAXScript panel called "Trail Maker".
-- Open it up and load this file to play with:
loadMaxFile "msdemo5.max"
-- The trail maker lets you select a master animated object
-- and a trailer object (or selection) and places copies
-- of the trailers along the path of the master at intervals
-- controlled by the appropriate spinners. Have a play. Hit
-- the delete button each time to trash the last trail.
-- Here's one interesting use:
-- 1. select all the trees that are over to the side
-- 2. pick the big yellow box as master (the one that
-- moves but doesn't bank)
-- 3. choose "use selection" for the trailer - for
-- each trailer copy, this will randomly choose
-- an object in the current selection
-- 4. set offset y to 25
-- 5. select "offset is exclusion zone"
-- (this means leave a space that wide along the
-- path and put trail copies outside it)
-- 6. set randomize y to 15, frame step to 5
-- copies to 4 & hit "Make trail" - instant
-- road-side grove.
--
-- MAXScript also lets you log listener activity to a file:
openlog "foo.log" -- log on
$box01.pos.z += 20 -- do stuff
print objects
closelog () -- log closed
edit "foo.log" -- look at it
-- note the '()' above after the closelog. This function
-- takes no arguments, but you can't just write the function
-- name to call it; this would just reference the variable holding
-- the function so you'd just get the function value and not
-- call it. The () is a 'nullary' call - it calls the
-- function with no arguments.
-- You can also do formatted ouput to a text file, useful for
-- exporting scene info for artists or to drive motion-control
-- cameras, etc.
-- For this example, first create some keyframed objects....
b = box (); s = sphere ()
animate on
(
at time 0 (move b [-100, 0, 0]; scale s [1, 1, 0.25])
at time 35 move b [0, 100, 0]
at time 100 (move b [200, 0, 0]; scale s [1, 1, 3])
)
-- The following code creates an ouptut file and loops over the
-- objects in a scene dumping out name & bounds. It checks to
-- see which have keyframes and dumps out the keyframe info...
--
-- <select this whole piece of code and eval it>
f = createfile "foo.dmp"
for o in objects do
(
format "object %, % - %\n" o.name o.min o.max to:f
local keys
if o.pos.isAnimated and (keys = o.pos.keys) != undefined do
(
format " % position keys\n" keys.count to:f
for k in keys do
format " %: %\n" k.time k.value to:f
)
if o.scale.isAnimated and (keys = o.scale.keys) != undefined do
(
format " % scale keys\n" keys.count to:f
for k in keys do
format " %: %\n" k.time k.value to:f
)
)
close f
edit "foo.dmp" -- look at the results.
-- and MAXScript has all the linear algebra you could want...
foo = box height:100
foo.rotation = quat 45 x_axis * quat 90 y_axis -- quaternions
m = $box01.transform -- object's node TM
mi = inverse m -- matrix math...
[1,2,3] * mi
for i in 0 to 1 by 0.1 do -- quat. interpolation
print (slerp (quat 0 x_axis) (quat 45 y_axis) i)
-- if you have access to the SDK help files, the notes on the linear
-- algebra types are relevant if you can decode them - MAXScript
-- exposes all these types and their operations.
-- the end