This lesson is the fourth in an ongoinging series. It's designed to follow.

ZScript Lesson I: IButtons and Formatting,

ZScript Lesson II: ISliders, and

ZScript Lesson III: CanvasStroke and Loops

<hr>

In the last lesson, you learned how to paint on the canvas using theCanvasStrokecommand. In this lesson, you'll learn how to place 3D objects in the canvas with theTransformSetcommand.

You'll create a ZScript that draws the selected 3D object a number of times in a grid. InteractiveISlidersset the number of rows, columns, and "turbulence". By adding a command to mark each object as it's drawn, you can later use the MultiMarker tool to draw all objects in another orientation, like the gravestones in the above picture.

You'll use theLoopcommand (featured in Lesson III) to create the grid. You'll also learn how to add random elements to your ZScripts, to account for the "turbulence". And by learning how to center the entire grid inside the canvas, you'll get a little more familiar with different ways of expressing calculations.

<hr>

<font color="#ffa000" size="3">1) About the X, Y and Z directions</font>

Here's a little refresher on 3-dimensional drawing. Chances are good this is a familiar concept to you; if you don't need a refresher, skip to the next section.

By now you know the ZBrush canvas isn't a flat canvas, it's more like a window into 3D space. To get to any point in 3D space, you can start somewhere in the canvas and move a certain amount left or right (X direction), a certain amount up or down (Y direction), and a certain amount nearer or farther (Z direction). Here's a visual helper:

(Note: Ordinarily, ZBrush doesn't draw using perspective foreshortening -- this effect is exaggerated in the above drawing.)

All 3D objects have components which correspond to these 3 directions -- a width (X), a height (Y) and a depth (Z). When you draw a 3D object, such as a cube, in a blank ZBrush canvas, the object's Z side is facing you:

You can rotate the object, and the other sides become visible:

ZBrush's Canvas Gyro provides a convenient way to move, resize, and rotate any 3D object without having to think about its X, Y and Z components. Internally, ZBrush assigns a number to each component: each component's size (width = X component, height = Y component, depth = Z component), each component's direction (what degree it's "spun" around each of the three axes), and the placement of the object in each of the three canvas' directions (left/right, up/down, near/far).

That makes a total of 9 numbers to determine exactly where the object is on the canvas, how big it is, and which way it's facing. If you ever need to determine (or change!) what these numbers are, you can visit the Transform palette, click each of the Move, Scale and Rotate buttons, and view the numbers in the sliders within the Info sub-palette:

(Note: in a later lesson, you'll learn about theTransformGetcommand, which provides a means of determining these numbers within a ZScript.)

<hr>

<font color="#ffa000" size="3">2) The TransformSet Command</font>

TheTransformSetcommand simply sets all 9 components for any 3D object that's drawn.

TheTransformSetcommand acts on the most-recently-drawn 3D object, as long as it's still "active". You should already know when a 3D object can be edited or transformed within ZBrush -- right after it's been drawn. The same rule applies for issuing theTransformSetcommand -- it won't have any meaning unless a 3D object has been drawn recently, before another tool has been selected.

Note: it isn't necessary to activate a Transform Mode (Move, Scale, or Rotate) before issuing theTransformSetcommand.

<hr>

<font color="#ffa000" size="3">3) Drawing 3D Objects</font>

Before you can change the 9 attributes of a 3D object, you first need to draw it on the canvas.

Remember theCanvasStrokecommand from the last lesson? There's a similar command calledCanvasClick, which applies a kind of "mini-stroke" to the canvas. This "mini-stroke" is limited because it can only have up to 8 points. It looks like this:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[CanvasClick,</font></td></tr></table></blockquote>

First point X , First point Y ,

Second point X , Second point Y ,

Third point X , Third point Y ,

Fourth point X , Fourth point Y ,

Fifth point X , Fifth point Y ,

Sixth point X , Sixth point Y ,

Seventh point X , Seventh point Y ,

Eighth point X , Eighth point Y

]

You can use theCanvasClickcommand to draw a brushstroke consisting of one to eight points, omitting any points you don't need to draw. The effect will be a brushstroke that begins with a mouse/tablet-pen click at the first point, dragging through the remaining points, and releasing at the last point.

To draw any 3D object, you need only two points -- the first click and the final release. Since you can use theTransformSetcommand to set the object's placement, size and orientation, it doesn't matter where these two points are (as long as they're different from each other).

So you can combine theCanvasClickandTransformSetcommands to draw any 3D object. Here's a simple ZScript that draws a cube on the canvas:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[IButton,"Draw a Cube","This button draws a cube on the canvas",</font></td></tr></table></blockquote>

[IPress,Tool:Cube3D]

[CanvasClick,2,4,7,9]

[TransformSet,

320,240,0,

100,120,140,

60,0,45

]

]

Load this ZScript and press the button -- if you're quick, you'll notice the cube is drawn near the upper left-hand corner of the canvas (clicked at 2,4 and dragged to 7,9). Then it is immediately given this information:

X position(on the canvas) of 320 pixels, from the left edge of the canvas.

Y position(on the canvas) of 240 pixels, from the top of the canvas.

Z position(on the canvas) of 0 -- this is right in the middle of the canvas space.

X Size(width) of 100 (actual width is twice this many pixels; more on this below.)

Y Size(height) of 120

Z Size(depth) of 140

X Angleof 60 (degrees)

Y Angleof 0

Z Angleof 45

No matter how the 3D object is drawn using theCanvasClickcommand, you can precisely set the object's placement, size and orientation using theTransformSetcommand.

Important:Unlike most ZScript commands, you shouldn't omit arguments from theTransformSetcommand. ZBrush assumes each omitted argument is zero -- what good is an object with zero width, height and depth?

<hr>

<font color="#ffa000" size="3">4) Setting Up the ZScript</font>

Now that you know how to draw 3D objects on the canvas, you can combine this knowledge with commands you've already learned to draw a grid full of them.

Start by creating theISlidersandIButtonto look like this:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[PageSetWidth,401]</font></td></tr></table></blockquote>

[ISlider,"Rows",4,1,1,15,

"Number of horizontal rows in the grid",

,,195]

[PenMove,10]

[ISlider,"Columns",4,1,1,15,

"Number of vertical columns in the grid",

,,195]

[PenMoveDown]

[ISlider,"Draw Size",10,1,1,99,

"Width, height and depth of each individual element",

,,130]

[PenMove,5]

[ISlider,"Space between",5,1,0,99,

"Space between each individual element in the grid",

,,130]

[PenMove,5]

[ISlider,"Turbulence",0,1,0,90,

"Degree of random rotation for each object",

,,130]

[PenMoveDown]

[iButton,"Draw the Grid","Draw the grid using the above settings",

// Commands group goes here

,,400]

The remainder of this lesson focuses on the Commands group inside theIButton.

Next, use theVarSetcommand to set a variable to each of theISlidervalues (replace the comment "// Commands group goes here"):

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,Rows,[IGet,-5]]</font></td></tr></table></blockquote>

[VarSet,Columns,[IGet,-4]]

[VarSet,Size,[IGet,-3]]

[VarSet,Space,[IGet,-2]]

[VarSet,Turb,[IGet,-1]]

<hr>

<font color="#ffa000" size="3">5) Some Comments About Size</font>

As mentioned earlier, you'd expect the three size values (X, Y and Z components) to be the object's width, height and depth. You'd be half-right.

Each of the X, Y and Z components reflect the distance from the middle of the object to its "outside edge" (this distance is labeled "Draw Size" in this illustration):

Looking at this example, you might wonder why ZBrush wasn't designed to simply measure the object's total width, depth and height. The reason is that most 3D objects aren't cubes -- they're complex shapes for which width, depth and height aren't as easy to visualize. Where would you measure the width of a twisted crescent, or a lopsided tree branch, for example?

To further complicate matters, a 3D object can be enlarged or shrunken using its Deformation modifiers:

In this case, the "outside edge" is moved, and has little to do with the object's X, Y and Z Size components. Pressing the Unify button in the Deformation sub-palette can help, because it shrinks or enlarges the object to fit inside a cube with these dimensions.

<hr>

<font color="#ffa000" size="3">6) Drawing a Row</font>

You already know you'll be assigning 9 values to each 3D object with theTransformSetcommand. Think about the 9 values for a moment, keeping in mind the way each object will be drawn in the grid:

X Positionwill be different for each object.

Y Positionwill be different for each object.

Z Positionwill be the same for each object.

X Size: will be the same for each object (equal to the "Size"ISlider).

Y Size: same (also equal to the "Size"ISlider).

Z Size: same (also equal to the "Size"ISlider).

X Angle: different.

Y Angle: different.

Z Angle: different.

This looks like a job for variables, doesn't it? The ones that stay the same can be set once, before theLoopcommand is encountered; the ones that change can be set to different values inside theLoopcommand.

Start by setting the variables which are the same for each object. The Z Position doesn't matter too much; you might as well make it 0. Put these right after the otherVarSetcommands:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,ZPosition,0]</font></td></tr></table></blockquote>

[VarSet,XSize,Size]

[VarSet,YSize,Size]

[VarSet,ZSize,Size]

Create two more variables called StartX and StartY -- these are the starting positions for each row and column. The X and Y Positions will change during each loop, but they'll need to come back to these starting values now and then. We'll take another look at StartX and StartY later; for now set them equal to the "Size"ISlider:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,StartX,Size]</font></td></tr></table></blockquote>

[VarSet,StartY,Size]

Set the X Position and Y Position equal to these starting values:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,XPosition,StartX]</font></td></tr></table></blockquote>

[VarSet,YPosition,StartY]

Now you can begin building yourLoopcommand. This will be aLoopthat creates just one row of 3D objects. It will draw several "columns" of one object each, this means it will execute the number of times determined in the "Columns"ISlider.

Think it through first (or sketch it out first -- whichever is more comfortable for you). In this loop, here's the sequence of events:

<blockquote>1. Determine this object's orientation. (You already know its starting position and its size)

2. Draw the object usingCanvasClick.

3. UseTransformSetto set its position, size and orientation.

4. "Jump" to the next position.</blockquote>

As for step 1, we'll cover this in the next section -- you'll use 0's (zeroes) for now.

As for step 4, take a look at this illustration:

You need to know how big the "jump" is between the center of each object. As you can see, the "jump" is equal to two "draw sizes" plus one "space". This can be expressed like this (using XSize instead of "draw size"):XSize * 2 + Space.

Now you have enough information to fill in the firstLoop:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[Loop,Columns,</font></td></tr></table></blockquote>

[VarSet,XAngle,0]

[VarSet,YAngle,0]

[VarSet,ZAngle,0]

[CanvasClick,0,0,0,4]// Remember, these are arbitrary numbers

[TransformSet,

XPosition, YPosition, ZPosition,

XSize, YSize, ZSize,

XAngle, YAngle, ZAngle

]

[VarAdd,XPosition,XSize*2 + Space]

]

Select a Cube3D, and try out your ZScript. You should get a row of cubes in the upper-left corner of your canvas.

<hr>

<font color="#ffa000" size="3">7) Drawing the Grid</font>

To draw an entire grid, you basically change the commands which draw one object, into anotherLoopwhich draws an entire column.

The sequence for drawing a column is the same:

<blockquote>1. Determine the orientation (zeroes for now).

2. Draw the object usingCanvasClick.

3. UseTransformSetto set its position, size and orientation.

4. "Jump" to the next (vertical) position.</blockquote>

Most of the commands to carry out this sequence are already there:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,XAngle,0]</font></td></tr></table></blockquote>

[VarSet,YAngle,0]

[VarSet,ZAngle,0]

[CanvasClick,0,0,0,4]

[TransformSet,

XPosition, YPosition, ZPosition,

XSize, YSize, ZSize,

XAngle, YAngle, ZAngle

]

Add to this the fourth step, the vertical "jump":

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarAdd,YPosition,YSize*2 + Space]</font></td></tr></table></blockquote>

Put aLoopcommand around this entire block, which repeats for the number of rows:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[Loop,Rows,</font></td></tr></table></blockquote>

[VarSet,XAngle,0]

[VarSet,YAngle,0]

[VarSet,ZAngle,0]

[CanvasClick,0,0,0,4]

[TransformSet,

XPosition, YPosition, ZPosition,

XSize, YSize, ZSize,

XAngle, YAngle, ZAngle

]

[VarAdd,YPosition,YSize*2 + Space]

]// The final bracket for the Loop command

One more thing: you should re-set the Y Position to the value of the StartY variable, before each column begins. Move theVarSetcommand which does this, to just before this newLoop.

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,YPosition,StartY]</font></td></tr></table></blockquote>

[Loop,Rows,

Now you have aLoopcommand inside anotherLoopcommand. Select another Cube3D and try the ZScript again. You should get an entire grid in the upper left-hand corner of your canvas.

<hr>

<font color="#ffa000" size="3">8) Adding Turbulence</font>

Turbulence simply means that each object is rotated by a random amount when it's drawn.

You can tell ZBrush to "pick any number" by expressing it this way:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">Rand(</font></td></tr></table></blockquote>number)

Notice this isn't a command -- it's a mathematical function, meaning you can simply use this expression anywhere you'd use an actual number.

Thenumberin this expression is the largest value you'd like the random number to be. So the expression

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">Rand( 10 )</font></td></tr></table></blockquote>

means "pick any number from 0 to 10". The random number picked won't usually be a whole number (it'll be some number like 3.72 or 8.16), and that's okay in this case. (If you ever find you need random whole numbers, you can use the functionIRand()in exactly the same way.)

What if you need random numbers that aren't as low as zero? Suppose you wish to pick random numbers from 10 to 15. You can simply pick random numbers from 0 to 5, and add 10:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">Rand( 5 ) + 10</font></td></tr></table></blockquote>

Turning back to this ZScript, your task is an easy one: replace the zeroes in each of the AngleVarSets to an expression using the Rand function:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,XAngle,Rand(Turb)]</font></td></tr></table></blockquote>

[VarSet,YAngle,Rand(Turb)]

[VarSet,ZAngle,Rand(Turb)]

This way, each of the angles will be set to some number between 0 and the value set in the TurbulenceISlider.

<hr>

<font color="#ffa000" size="3">9) Centering the Grid</font>

Right now, this ZScript draws grids in the upper left-hand corner of the canvas:

Let's add some finesse by centering the grid in the canvas. In the process, you'll get a little more familiar with different ways of expressing calculations.

You probably have some idea how you'd center the grid in this illustration: you'd move it to the right and down by half the "leftover" space.

You can figure out the "leftover" width and height in advance. Do this by determining the width of the grid, and subtracting it from the canvas width. Determine the grid's height and subtract it from the canvas height. Dividing these results by two gives you a good idea where to start.

For our purposes, the width and height of the grid are really the horizontal and vertical distances between the first object's center and the last object's center:

You already know how big the distance between two objects is -- the "jump" size determined earlier. The width of a grid of 3-columns would be 2 "jump" sizes; the width of a 4-column grid would be 3 "jump" sizes ... are you seeing a pattern here?

The width of any grid is the number of columns minus one, times the "jump" size. Similarly, the height of any grid is the number of rows minus one, times the "jump" size.

So, in theIButton's Commands group, after the variables have been set toISlidervalues, you can set a variable for the "jump" size:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet, Jump, XSize*2 + Space]</font></td></tr></table></blockquote>

Then, you can set variables for the grid width and height:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet, GridWidth , (Columns-1) * Jump]</font></td></tr></table></blockquote>

[VarSet, GridHeight , (Rows-1) * Jump]

The parentheses in the above expressions mean "do this part first before continuing with the rest of the calculation". So the value of Columns-1 is calculated first, then that value is multiplied by Jump.

Now you'll find the values of StartX and StartY, the initial position of the first object drawn. Find theVarSetcommands which set these two values in the ZScript -- you'll replace them with expressions described below.

You can find the canvas width and height by reading the corresponding sliders in the Document:Modifiers sub-palette, withIGetcommands. Remarkably, you can use theseIGetcommands within calculations, freely mixed with variables and numbers.

In this case, you're determining the position of the first 3D object drawn (the StartX and StartY variables). To find the value of StartX, you need to subtract the Grid Width from the Document Width and divide by two. You can express it like this:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,StartX, ( [IGet,Document:Modifiers:Width] - GridWidth ) / 2 ]</font></td></tr></table></blockquote>

This might look a little heavy on the math, but it's really simple: Set StartX to the value of the Document Width (theIGetcommand) minus the Grid Width (the variable) divided by 2 (the number).

Again, the parentheses mean "do this part of the calculation first". They're not absolutely necessary, but I find they make it a bit easier to break up longer expressions into comprehensible groups.

Set the value of StartY the same way:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[VarSet,StartY, ( [IGet,Document:Modifiers:Height] - GridHeight ) / 2]</font></td></tr></table></blockquote>

<hr>

<font color="#ffa000" size="3">10) Marking the Objects</font>

Nothing new here. Simply use twoIPresscommands: one to clear all markers before drawing anything, the other to mark each object as soon as it's drawn.

At the top of theIButton's Commands group, clear all the markers:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[IPress,Marker:Inventory: Delete all]</font></td></tr></table></blockquote>

After theTransformSetcommand which displays each object, set a new marker:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[IPress,Transform:Mark Object Position]</font></td></tr></table></blockquote>

<hr>

This lesson has been a long one (hopefully not too complex). Here is the entire ZScript, presented in one block in case you've missed anything:

<blockquote><table border="0" cellpadding="6" cellspacing="0" bgcolor="#cccccc"><tr><td bgcolor="#cccccc"><font color="black" size="2">[PageSetWidth,401]</font></td></tr></table></blockquote>

[ISlider,"Rows",4,1,1,15,

"Number of horizontal rows in the grid",

,,195]

[PenMove,10]

[ISlider,"Columns",4,1,1,15,

"Number of vertical columns in the grid",

,,195]

[PenMoveDown]

[ISlider,"Draw Size",10,1,1,99,

"Width, height and depth of each individual element",

,,130]

[PenMove,5]

[ISlider,"Space between",5,1,0,99,

"Space between each individual element in the grid",

,,130]

[PenMove,5]

[ISlider,"Turbulence",0,1,0,90,

"Degree of random rotation for each object",

,,130]

[PenMoveDown]

[iButton,"Draw the Grid","Draw the grid using the above settings",

[IPress,Marker:Inventory: Delete all]

[VarSet,Rows,[IGet,-5]]

[VarSet,Columns,[IGet,-4]]

[VarSet,Size,[IGet,-3]]

[VarSet,Space,[IGet,-2]]

[VarSet,Turb,[IGet,-1]]

[VarSet,ZPosition,0]

[VarSet,XSize,Size]

[VarSet,YSize,Size]

[VarSet,ZSize,Size]

[VarSet, Jump, XSize*2 + Space]

[VarSet, GridWidth , (Columns-1) * Jump]

[VarSet, GridHeight , (Rows-1) * Jump]

[VarSet,StartX, ( [IGet,Document:Modifiers:Width] - GridWidth ) / 2 ]

[VarSet,StartY, ( [IGet,Document:Modifiers:Height] - GridHeight ) / 2]

[VarSet,XPosition,StartX]

[Loop,Columns,

[VarSet,YPosition,StartY]

[Loop,Rows,

[VarSet,XAngle,Rand(Turb)]

[VarSet,YAngle,Rand(Turb)]

[VarSet,ZAngle,Rand(Turb)]

[CanvasClick,0,0,0,4]

[TransformSet,

XPosition, YPosition, ZPosition,

XSize, YSize, ZSize,

XAngle, YAngle, ZAngle

]

[IPress,Transform:Mark Object Position]

[VarAdd,YPosition,YSize*2 + Space]

]

[VarAdd,XPosition,XSize*2 + Space]

]

,,400]

<hr><font color="#ffa000" size="3">Exercises</font>

To get a little more practice, try these challenges on your own:

Try adding random variances in size and position to each object drawn, similar to the "turbulence" in each object's orientation.

Instead of a simple grid, try creating a ZScript that draws 3D objects in a "brick" pattern, where each successive row is offset horizontally from the one above it.

Happy ZScripting!

dave