|
May 1st, 2008
Hooray Interpolation!
If you ever really want to confuse someone that is looking at your script controllers, just use cubic polynomials to control imaginary splines. All kidding aside, scripted bez. interp. is awesome in 3ds Max for several reasons:
- Its faster than using native Max splines
- It gives full control over the in and out vectors, which animators love
- It really breaks rigs out of the inorganic box of right angles and linear blends
The first point was the real shocker for me. The first time I noticed this was while rigging some bendy cartoon arms that were driven by actual splines. Stretchy bones were strung between helpers that were path constrained along certain percentages of this spline, with constant velocity turned on for appropriate behavior. There was a noticeable slowdown that really took the fun out of animating the rig, so I had to look into other methods. It turns out that this formula saves the day:
(1-t)^3*p0 + 3*t*(1-t)^2*p1 + 3*t^2*(1-t)*p2 + t^3*p3
Where: t is a value between 0 and 1, and p0, p1, p2, p3 are the four points to interpolate through. p0 and p3 are the start and end, while p1 and p2 are the out and in vectors to control the interpolation. Really these vectors are what you are manipulating anytime you work with paths in Photoshop, or manipulate type in any vector-based illustration software (bezier handles). I just plug in the appropriate values into a script controller and I get magically fast interpolation. And since p1 and p2 are the out and in vectors, controlling these values with some control objects that feel like bezier handles lets animators express themselves more intuitively. Features like these are really what makes rigs interesting to create, animate with, and to watch in performance. The audience might not know it, but deep inside I believe they are bored with watching static linear interps. occuring between nearly every relationship in the hierarchy. This definitely isn’t something new, but it is something that should be implemented more often by more technical artists. If only Max splines were fast enough to begin with… but I suppose we have to work within the package the project is maintained in, or get fed up enough to write some C++.
Posted in Skinning, MAXScript | 1 Comment »
March 16th, 2008
Choices, choices
There really are quite a few options when it comes to packaging scripts for deployment. You have the completely MXS-written installer, third party install packages (like NSIS), shell scripts, or deployment via another language like Python. I suppose it depends on the environment, but the most important decision breaker for me is how fast the package can be updated and repackaged. Ideally, any necessary updates should be able to be integrated instantly.
The simplest way to get this fully automated scheme running is just a specified location on the network that all scripts are saved at, and loading these when Max starts with a startup (in /stdplugs for first execution) script. The script directory on the network has a folder structure similar to the Max installation so the load script can simply copy everything directly. This would be completely transparent to the artist, but has a drawback: if you have a set of macroscripts with icons, those icons will fail to load. The macroscripts will still appear in their correct categories (as long as you fileIn), but will lack icons. I believe there may be a method in the colorMan interface in Max 9 that reloads icons, but I haven’t had a chance to look. Anyways, this is a decent method nonetheless for deployment in terms of time to update/repackage/reinstall.
Aside from the auto-load approach, you have various installer scripts and executables that can run outside or from within Max, but all must be loaded explicitly by the user. I’ve used NSIS to package my scripts into a nice .exe with guided installation, but I really find this too tedious for production. It takes far too much time to update the install script, copy files, build the .exe, etc. The process can be automated somewhat by having a script that generates the install script according to a faux-Max directory structure, but this is just another thing to “deal with.” This is generally how I feel about using a third party language like C# or Python to perform the installation. Unless there is already a studio-wide distribution of a particular language, its a hassle to install packages on people’s machines for the sole purpose of installing MAXScripts.
Recently what I’ve been favoring are shell scripts. They can be run at computer startup, or explicitly. What makes them particularly nice is the ability to control a VCS like Perforce to grab the latest scripts, and then perform a looped file copy for the installation. Since its just copying all files in all folders in a certain directory, it never has to be updated. I simply write my scripts, check them in, and keep working. When someone needs scripts, they just run the shell script.
This is what’s working for me at the moment, but if anyone has any alternate methods or points to add I’d love to hear them!
Posted in MAXScript | No Comments »
March 15th, 2008
Ever since I saw XSI’s sweet skin transfer tools (I forget the exact name), I knew 3ds Max needed something similar. We do have the Skin Utilities panel that will bake skin data into a mesh for transferring to another mesh, but sometimes this doesn’t cut it. One example of this is when particular vertices must inherit the exact weight and bone list of their source vertices. This sort of situation arises when meshes are separate but need to look continuous, like in games that require meshes to be split up for model swaps. There are just so many cases where a smarter skin transfer utility would save loads of time. The only problem has been finding the need for such a tool, since I am unlikely to need it unless I have a ton of similarly rigged situations that could benefit from such a system (like in a game or cinematic pipeline). Luckily, the other day I had an opportunity to write such a tool, and I’m very excited about it.
The skin transfer tool that ships with Max basically has a single drawback: the inability to perform a 1:1 transfer. Sure, you can use vertex snap to put verts very close to each other for a more optimal transfer, but in the end the tool will interpolate a tiny error value into the weight and the match won’t be perfect. Not to mention the fact that Max’s vertex snap isn’t actually perfect. This blew my mind when I first found it out. Try snapping a vert to another vert, select the object and single vertex, and run this code on each to compare (be sure to use Editable Poly objects for this code):
print (polyop.getvert $ (polyop.getvertselection $ as array)[1])
The script will print the selected vertex’s position for comparison. You’ll notice that they aren’t exactly the same! This just doesn’t cut it. When using Skin Utilities, at this point you would have to go in and manually type in the correct weights/bones for each vertex you wanted, and promptly explode in anger. No more! The tool will automatically and perfectly snap a selection of vertices to their nearest source vertices. Snapping a group of vertices is just one click. When the skin transfer routine is running, any vertices that are perfectly coincident will inherit a 1:1 weight/bone relationship from its source mesh. And what about the vertices you don’t want perfectly snapped?
For those verts, I have routine that will collect the three nearest source vertices and grab a distance-modulated weight/bone assignment. This means that completely different meshes can inherit as much or as little skin data from each other as the artist defines.
Posted in Skinning, MAXScript | No Comments »
March 15th, 2008
I get this question semi-often, so I thought I’d make a post about this name and its history…
Around 1999 I used to play a lot of Counter-Strike, and d3coy was my handle. Of course the name also served me well in Day of Defeat and TFC, among others. If you played with a d3coy that favored the AWP/Desert Eagle back during that time period, chances are it was me. When I first started playing, in anticipation to how much I knew I was going to get my ass handed to me, I named myself decoy. Somewhere along the line the “3″ made its way in there, either on accident or in an attempt to be “l33t.”
Anyways, that was a long time ago, and its the only handle I’ve used extensively. I’ve seen plenty of other d3coys out there, including d3koy, dekoy, etc. I’m not sure who had the name first, although I assume it probably has been used by someone or other since the 70’s, or something similarly ridiculous. The only way I’ve ever been able to connect the name to something even moderately professional has been the idea that the transpose of the first two letters in matrix form would create “3d.” But that’s one hell of a stretch.
Posted in Site Related | No Comments »
March 15th, 2008
Why try this?
This has always been something I wanted to try to code, so I checked out some of the research papers that Jos Stam has published, and started working on it. This initial test was done in two dimensions, but the nature of fluid simulation enables easy expansion into the third dimension. Maybe sometime in the future I will try to branch this code into 3D. But basically, the way it works is through a general approximation of the Navier-Stokes equations, which are a set of interdependent differential equations. To solve one, you need to solve the other, which depends on the first, etc. Obviously we need to transform this into a process that a computer can solve for, and various methods of doing so exist.
Stam’s method uses the finite space method. In the finite space method the simulation area is divided up into voxels, and during each step the various equations are applied to each voxel to move the fluids around. Whats nice about working in graphics is that we don’t have to make our simulations absolutely physically accurate; if it looks good, its right. So his approach is aware of things like the conservation of energy, but as you’ll see there are various steps that “fudge” the values into simply looking correct. This example only models densities and velocities, but more complex fluid sims will also compute temperature, pressure, and other terms.
The rundown
So the first step is to inject some densities into our flow field. Lets only think of densities for now, and add velocities after. If you picture a glass of water in your mind, and drop a droplet of ink in, what happens? Well, many things, but we can summarize its movement in two ways: it seems to diffuse outward in every direction, and it seems to move faster downwards since that is the initial force of the droplet (gravity). Since we are ignoring that velocity force for now, we concentrate on diffusion. We set our simulation up so that during each step, N voxels get injected with densities (imagine some density entering the fluid of your program from another dimension, Flatland anyone?) at a constant rate. During each step we also diffuse densities from voxels that have densities to diffuse into the neighboring voxels. Some pseudocode:
if has density:
diffuse density by diffusionRate to neighbors
So we have some scalar amount to influence the diffusion rate. At this point the simulation just has a few constant voxels that are injected with density, while the neighboring voxels slowly get darker and darker through diffusion.
So now we can address the second way diffusion propagates: by its velocity. Up to this point, each voxel only had a density property, but now we are going to need a 2D vector velocity property on each voxel. This will effectively turn our sim field into a true flow field, where velocities propagate densities and so forth. If we think only of the velocities, we can also say that the velocities tend to diffuse into their neighbors. So we can write a function that handles scalars and vectors to diffuse densities and velocities. Now that we have velocities diffusing, we can move densities around according to the velocities. What you’ll see at this point is the velocities being strong when they first enter the system, but die quickly due to diffusion. It is as if the densities are moving through a super dense fluid that immediately diffuse perfectly. What Stam has done to remedy this is to move the velocities according to themselves. In this way, the velocity field actually advects the density field and itself. Once this is done, you get a much more intuitively fluid-seeming simulation, since the velocities don’t seem to die off as quickly. Some pseudocode to summarize:
for each step:
for each voxel:
diffuse densities
diffuse velocities
advect velocities /*velocities advect themselves*/
advect densities
My simulation didn’t get beyond this point, but the last step involved gradient fields and some tricky vector calculus to really enhance the vortices in the sim. If you’ve gotten this far, I’d love to hear how you implemented this vortex confinement! This prototype was written in Processing.
You can see my results below. Left click to affect the density field, right click to affect the velocity field, ‘R’ to reset.
|
Posted in Physics | No Comments »
March 15th, 2008
So we had this crazy idea to have sweat droplets drip down someone’s face convincingly at a previous studio. When a droplet cascades down a surface, it will leave a darker trail behind it due to the drop in the lambertian reflectance when an object is wet. You can view a prototype using these methods here, however in this example I show the texture map being used as a displacement to fake a reaction to the single particle. Using this technique it is possible to record the exact path that an object or particle takes while it travels within a certain threshold proximity to an object’s surface. Let me preface my description of the process by acknowledging the fact that many more advanced systems exist for obtaining mesh deformations and/or material changes on an object’s surface that are driven by a separate entity. However, in 3ds Max there is no easy way to achieve this effect (although there are numerous workarounds and “cheats,” as there always are), so we’ll be using MAXScript to obtain it. Instead of altering the mesh of the object when another object is detected, we will be accessing the UVW coordinates of the object so that we can “paint” a trace onto the object’s surface. Having a dynamic mesh that reacts to other objects is a topic much too complex for a scripting language to handle with any respectable speed, so this will be our preferred method. The essential idea behind the entire process is to shoot rays out of the object, extract information about the ray collisions, access the UVW information, and draw pixels to a bitmap. I’ll outline each of these processes in the coming sections.
Distribution of Samples
I’ll be referring to the object that draws on the surface object as the “tracer.” Whatever the tracer is, a single object or particle flow, we will need a flexible method to distribute samples from that object. From frame to frame, we need to get the object’s position and retrieve a collection of vectors to shoot our rays along from this object’s position. For my prototype, I wrote this function to generate the vectors quickly:
fn setSamples iterations =
(
local segLength = 360.0 / (iterations * 2)
local origLength = segLength
append theVectors [0,0,1]
append theVectors [0,0,-1]
for i = 1 to iterations do
for x = 1 to (iterations * 2) do
(
offset = [0,0,(((i - .5) / iterations) - .5) * 2]
tempVector = [(cos segLength),(sin segLength),offset.z]
sphereDist = (distance tempVector offset) - (sqrt (1 - (pow offset.z 2)))
theVector = (((normalize (tempVector - offset)) * sphereDist) - tempVector)
segLength += origLength
append theVectors theVector
)
)
It distributes more samples laterally than from its “poles,” but it serves its purpose here. The premise was to first create several rings of vectors in a cylindrical pattern, and then translate them to reside at unit length from the central point, thus creating a sphere. If anyone has other ideas about efficiently distributing samples from a single point I’d love to hear it (maybe something using a random distribution into spherical coordinates). For visualization purposes, here’s a script that will construct a series of cones and cylinders so one can see exactly how the vectors are affected when the iterations are changed.
(
fn makeCones vectorArray =
for x in vectorArray do
(
theCyl = cylinder heightsegs:1 sides:3 height:1 radius:.02
theCyl.rotation.controller = lookat_constraint()
theCyl.rotation.controller.appendtarget x 100
theCyl.rotation.controller.target_axis = 2
theCyl.rotation.controller.upnode_axis = 1
theCyl.rotation.controller.StoUP_axis = 1
theCyl.rotation.controller.lookat_vector_length = 0
theCyl.wirecolor = white
x.radius *= 2
theCyl.material = x.material = meditmaterials[2]
)
local iterations = 5 –2 to 10 spinner range
local segLength = 360 / (iterations * 2)
local origLength = segLength
local theVectors = #()
local theColor = [0,0,0]
/* This method of ray distribution first creates a cylindrical array of
vectors, and translates them into a spherical array surrounding a point
(the particle) with the radius equaling the unit length. It provides more
ray detection horizontally than vertically. In the function below, spheres
are appended to theVectors array, which is obviously unnecessary in practice.
You will want to adapt this script to only append the vectors derived from
the function. This script is for visualization purposes only. */
local polN = sphere pos:[0,0,1] radius:.025 wirecolor:red
local polS = sphere pos:[0,0,-1] radius:.025 wirecolor:red
append theVectors polN
append theVectors polS
for i = 1 to iterations do
(
for x = 1 to (iterations * 2) do
(
offset = [0,0,(((i - .5) / iterations) - .5) * 2]
theVector = [(cos segLength),(sin segLength),offset.z]
sphereDist = (distance theVector offset) - (sqrt (1 - (pow offset.z 2)))
local theSphere = sphere pos:(((normalize (theVector - offset)) * sphereDist) - theVector) radius:.025 wirecolor:theColor
segLength += origLength
append theVectors theSphere
)
theColor = [(random 1 255),(random 1 255),(random 1 255)]
)
makeCones theVectors
)
Painting

Using the samples, the script can draw to our surface object using its UVW coordinates. Every cycle where a tracer’s ray hits the surface object, the script will draw a single pixel to a bitmap stored in memory. At the end of every cycle, the script will output this image to a directory so that they can be played back or mapped back onto our object. Since during every cycle we draw to the same bitmap, a trail slowly forms as the script iterates.
The core of the code that accesses an object’s UV’s is done for us, thanks to Bobo. He wrote a tutorial in the MAXScript reference on writing to an object’s texture. This is the simplest possible example of drawing to our bitmap. Taking this further, we can vary the intensity of the pixel drawn by the distance the ray had to travel to hit the surface object. This is where threshold values come in handy, to offer a greater degree of intensity changes in our outputted image sequence. Instead of drawing a single pixel, we can draw a circle of pixels, and even apply some feathering to this circle using sine, gauss, or similar methods. In my opinion this is the most intriguing and exciting part about this process, namely, using all the information gathered from the samples to vary the application of our pixels. The radius of the pixel circle drawn can also be varied by the distance the ray traveled to the surface, almost yielding a sumi-e effect. The image to the right illustrates some simple manipulation that can be achieved when painting to the bitmap using the information gathered from ray sampling. As you can see, this is an area for much fruitful exploration.
Sub-sampling and Interpolators
Sampling frame-by-frame will obviously yield spotty results in the outputted image sequence if the tracer objects are moving too quickly, so we will need a way to sub-sample between each frame. When dealing with single objects, this isn’t a problem at all; you simply supply a float value for the “at time” construct that returns the object’s position, but it poses much more of a problem when distributing samples from particles. The nature of particle flow is frame-by-frame interpolation, so the methods that particle flow has exposed to MAXScript are limited in this same manner. We’ll have to use our own interpolator on the dataset to achieve a sub-sampling effect. Bezier interpolation does this well enough, but remember that this sort of interpolation is only useful when the particles are moving extremely quickly. Generally, you can get by with a simple linear interpolation since many post-processes will be applied to the information that is drawn, and that leads us into the next section.
Post-Processes and Other Thoughts
While in the drawing phase, using the rich data provided to us by the samples, we’re in a powerful position to alter the output, but many times this is not enough. What is so convenient about this method is that we’re dealing with an image sequence, so whatever we create can be loaded into any major compositing software and all of the tools and effects therein are at our disposal. The possibilities are nearly endless. This is one major point where this method is superior to direct mesh-to-mesh alterations as mentioned at the beginning of this article, since those methods simply do not yield the flexibility that image files do.
A great example of this method’s usefulness would be of a boot stomping into mud. By spawning particles on the boot and carefully manipulating the threshold values, it should be possible to achieve a perfect imprint of the boot’s treads in the mud. Using a post-process, you could even make the surrounding mud displace upwards as though the boot actually had weight. Unfortunately, at this time I cannot release my entire prototype, but I think a shot of the UI and the primary painting function should give the inspired all they need to finish the job. An image of the UI can be seen here. I feel that this is a process that is probably better suited for the SDK, and it would be interesting to see how much farther this or a related method could be taken.
fn putPixels radius theBmp thePos strength theDepth gaussian:false dSize:true dStr:true =
(
local theColor = [0,0,0] + ((1 - strength) * [255,255,255])
local theRadius = radius
if dStr do theColor = (theDepth * [255,255,255]) + ((1 - strength) * [255,255,255])
if dSize do theRadius = ((1 - theDepth) * theRadius)
for w = -theRadius to theRadius do
for h = -theRadius to theRadius do
if (theVar = distance ([w,h] + thePos) thePos) <= theRadius do
if gaussian then
(
theVar /= theRadius
theVar = sin(90.0 * theVar)
thePixel = getpixels theBmp ([w,h] + thePos) 1
if thePixel[1] != undefined do
(
thePixel[1] = (thePixel[1] * theVar) + (theColor * (1.0 - theVar))
setpixels theBmp ([w,h] + thePos) thePixel
)
)else setpixels theBmp ([w,h] + thePos) #(theColor)
)
Posted in MAXScript | 1 Comment »
March 15th, 2008
Matrices and Vectors
There are quite a few key methods that are helpful when dealing with matrices and vectors. This may seem like esoteric knowledge if you’ve never coded, but it is these building blocks that drive all of the transforms under the hood of 3ds Max (all 3d apps really). For instance, there are occasions where one needs a “Look At” behavior without actually employing the built-in lookat_constraint (maybe you need additional control on the rotational controller). So the way a lookat_constraint works is by taking the vector received by subtracting the target object’s position from the lookat object’s position, and constructing a matrix off that vector. As a side note, to retrieve a vector that points from object A to object B, you subtract B’s position from A’s.
A matrix in 3ds Max is composed of three vectors and a translation (which is another vector). An object is scaled when any of the first three vectors aren’t “normal” vectors, also known as “unit vectors.” The definition of a normal vector is a vector who’s length is equal to 1. So, treating the vector that we just acquired from our subtraction as the Z direction, we can take the cross product of it and the world “Up” direction (which is [0,0,1] in 3ds Max). Cross products yield a vector that is perpendicular to the plane created by the two supplied vectors, so we essentially just received our X vector. Using cross product again, we can take our Z and X vector and receive our Y vector. From these three vectors, we can now construct our matrix. The code below describes the operations I just explained, making sure to normalize all vectors received so that we construct a non-scaled matrix:
(
a = $main_obj.pos —-assuming these are the names of your objects
b = $target_obj.pos
zVect = normalize (b - a)
xVect = normalize (cross zVect [0,0,1])
yVect = normalize (cross zVect xVect)
theMatrix = matrix3 xVect yVect zVect [0,0,0] —-the resultant matrix
)
Please note that the “normalize” function is one of the most computationally expensive methods to invoke. In long hand, to normalize a vector you divide it by its length, which can be demonstrated in MAXScript quite easily using the “length” function. The length of a vector equals the square root of the sum of the square terms of the vector, demonstrated here:

Essentially, the fewer normalizations you can perform, the better. When computing a simple “Look At” rotation (which, I forgot to add, is the rotation part of the matrix that we calculated in the above code, accessible using theMatrix.rotation), it is usually not necessary to normalize the vectors during the computation. This is because the non-normal vectors will yield the correct rotation value anyways. However, if you were computing the entire transform of an object, you would want to normalize the vectors before constructing the matrix, otherwise the object would exhibit strange scaling behaviors due to the resultant scaled matrix. In this way, one can observe how rotation and scale are intrinsically tied together in 3ds Max. This is an important point.
Many times I see users relying solely on the “ExposeTM” helper object in 3ds Max to retrieve local rotations of an object in relation to its parent, or other objects. Generally just one of the values from the helper is needed, so this may or may not be overkill. The transform of a child in local space equals the inverse of the child’s transform multiplied by the parent’s transform. Check out the following code snippet:
(
local child = $ —-assuming the child object is selected
local theMatrix = inverse child.transform * child.parent.transform
format “Local rotation: %\n” theMatrix.rotation
)
This method gives you just another way to extract local rotations from an object, without having to rely on the “ExposeTM” helper overly much. Of course, local rotations can be extracted from two objects regardless of a parent/child relationship. Comparing the values derived from this extraction method with the values of the “ExposeTM” helper will show that they are one and the same. One question open for speculation, however, is whether a script controller performing this operation is faster or slower than a parameter referenced to the SDK-written “ExposeTM” helper. Personally, I notice no difference even in fairly complex rigs, and I prefer to not have an extraordinary amount of helpers cluttering up the scene.
Quaternions
It is very difficult to describe a quaternion value; its four terms simply describe a value which describes a rotation. Being a four-dimensional construct, it is impossible to describe it in three-dimensional terms. However, a way to think about them to save everyone a headache is as an angle/axis form. Using this mode of thinking, a quaternion can be derived from an axis of rotation, and an amount of rotation about that axis in degrees. In MAXScript, we can create a quaternion value that rotates 90 degrees about the Z axis like so:
quat 90 [0,0,1]
Under the hood, 3ds Max will immediately convert this value to…
(quat 0 0 0.707107 0.707107)
…which saves us the headache of working in four dimensions. The four values of the quat (x, y, z, and w) describe the x, y, and z terms of its vector, and the angle of rotation around this vector (w). Actually, the last value isn’t the angle itself, but the cosine of half of the angle. The first three terms are also not what they seem; they are multiplied by the sine of half of the angle value. So in long form:

Where a = angle of rotation. You can code out this expression in MAXScript and receive the same value that you would using the quat angle axis constructor we used above. This concept alone should open a floodgate of scripting possibilities. Using this method, one can calculate automatic rotation of a ball as it rolls across the floor (see the Portfolio section, under Script Demonstrations, for a video example of this employed in the Dynamic Ball Rig). To calculate the total rotation of the spherical object at any point in time, it is necessary to know the circumference of the sphere, which we all know as c = 2pr. For the sphere to have traveled one full rotation, it will have traveled its circumference. So if we obtain the total distance traveled by the sphere up to the current time (by looping through its positions at every frame up until the current one and adding the vector lengths between each position), we can divide its distance traveled by its circumference, and multiply that quotient by 360 to retrieve the number of degrees it has rotated to travel to that location. Simply subtracting the previous position from the current position will yield a vector representing the direction of travel, which we can then use in a cross product against the world up direction ([0,0,1], assuming the sphere is on a flat surface) to get the axis the sphere will rotate about. These two numbers can be used to construct the quaternion value representative of this object’s rotation. All of this can be done in real-time using scripted controllers, however, being a history-dependent solver poses all of the obvious problems. Most, if not all, of these problems can be alleviated by separating separate shots into separate scene files, or better yet, having the rotation only being calculated for the current time segment.
Quaternion principles can also be employed when scripting gears that need to rotate against each other in a precise manner. Using some algebra we can reverse engineer the angle/axis quaternion representation and retrieve the rotation appropriate for gears of differing sizes rotating against each other, assuming you can reliably obtain the radius of each gear. In this example the smaller gear is being driven by the larger. The small gear’s rotation is driven by a script controller, and in that script controller a variable named “bigGear” holds a reference to the larger gear’s rotation (as a quat value). There are also two constants, “bigGearRadius” and “smGearRadius” that hold the respective radii.
(
rotAxis = -1 * bigGear.axis
proportion = bigGear.angle * (bigGearRadius * 2 * pi)
angle = proportion / (smGearRadius * 2 * pi)
quat angle rotAxis
)
Its a simple concept, but by having the product of the big gear’s total rotational angle multiplied by its circumference, and dividing it by the smaller gear’s circumference, we get the precise amount of rotation for the smaller gear. The variable “rotAxis” is multiplied by -1 simply so that the smaller gear rotates against the larger.
Posted in MAXScript | No Comments »
March 11th, 2008
So I’ve gone with integrating WordPress as the new format for articles, blogs, and other meanderings. It was just too tedious to update under the previous format, hence the lack of entries. Hopefully these nice publishing tools will give me less incentive for procrastination, and get me talking.
Aside from the new blog, I think I’ll take down the Projects page over the next week and roll any relevant topics into blog entries. Interests are whimsical, and so is good research that fuel projects. Currently I write every little idea I get down into a little black book, and few of these ideas ever become anything more than ink on a page. When ideas arise, they can just be jotted down and I can move on. Having a whole Projects page is just so anti-creation! Why create a system that rigidifies the creation process? Free-form is the way to go. Hence the blog. Bam.
Posted in Site Related | No Comments »
|
|
|
|