Search ProofOfProgress Blog

Monday, March 28, 2011

MaxScript Rig Exporter for GoTeamGo

The rig exporter I made in maxscript finally got my rig into the game correctly.
What it does:
1. Converts all links to skinning.
2. Puts all geometry pivots at the origin.

...Step2 took me at least 7 hours of work today.
The entire exporter is 387 lines of code long.

The exported rig looks pretty trippy. As because of the math involved, all bones start at [0,0,0] at the beginning of the animation cycle.

Solution: Symmetry Modifier Gizmo Pivot not aligned to Object Pivot

Solution in max when you think you think you fixed the pivot of an object, yet the modifier's gizmo still won't line up correctly:

1. Select Object.
2. Run This Line: ResetXform($)
3. Select Object > Convert To Editable Poly.
4. Now try modifier.

Solution: Align Pivot To Another Object Without Affecting Geometry in MaxScript

This code can be used to Align a pivot of an object to another object or
some other arbitrary matrix without affecting the position of the original geometry.

Potential Usage: programmatically moving pivots before export so your model
will look right in your game engine.


Function ReAssignPivot
inputTransform --New Transform Matrix To Be Pivot
&theObject --The Object to Act on.
--Function Description:
--Gives the object a new pivot, defined by a transform matrix.
--Does so WITHOUT affecting the world position of the original geometry.
--
--**************************************************************
-- More Free Scripts: www.ProofOfProgress.BlogSpot.com ***
--**************************************************************
--
=(
--VLM = Visible Local Matrix.
--The matrix/pivot you see when selecting object and "Local" axis is selected as viewable.
VLM = theObject.Transform;
IP_LocalRot = theObject.objectOffsetRot; --Rotation to be used later.
IP_LOCAL = theObject.objectOffsetPos; --Invisible Pivot Local coordinates
--In relation to VLM matrix.
IP_WORLD = IP_LOCAL * VLM; --World Coordinates of Invisible Pivot. [Local To World Transform]
VLM_0 = inputTransform; --Reset Visible Local matrix coordinates.

NEW_IP_LOCAL = IP_WORLD * inverse(VLM_0); --[World To local Transform]

theObject.Transform = VLM_0;
theObject.objectOffsetPos = NEW_IP_LOCAL;

--Now Handle Rotation:
--Since rotation of visible local matrix has been zeroed out,
--You must add that loss to the invisible pivot rotation.

RotationLoss = VLM.RotationPart - VLM_0.RotationPart;
GeomWorldRot = RotationLoss + IP_LocalRot;
theObject.objectOffsetRot = GeomWorldRot;

)--[FN:ReAssignPivot]

--Test Code;
--This test code will align SomeCircle's pivot to PivotObject's Pivot.
inputTransform = $PivotObject.Transform;
affectedObject = $SomeCircle;
ReAssignPivot inputTransform &OBJ;

Solution: Align Pivot To World Without Affecting Geometry in Maxscript


This script moves the pivot of an object to the world and aligns it without
affecting the original location of the object geometry.

Some things I learned about max's internals from doing this:
1. theObject.objectOffsetPos is the LOCAL coordinates of the objects
"Invisible" axis. Plug those Local coordinates into the objects
transform matrix to get the WORLD coordinates of the objects
"Invisible" axis.

Example:
LocalCoords = theObject.objectOffsetPos;
TheMatrix = theObject.Transform;
WorldSpaceCoords = LocalCoords * TheMatrix;

2. The Objects VISIBLE pivot is a Matrix.
The Objects INVISIBLE pivot is a quaternion and point3 position.

This is self-Evident when you mess around.


Function alignPivotToWorld
&theObject
--Aligns the pivot to the world WITHOUT affecting the geometry.
--
--*****************************************************
--More Scripts at: www.ProofOfProgress.BlogSpot.com **
--*****************************************************
--
=(
--VLM = Visible Local Matrix.
--The matrix/pivot you see when selecting object and "Local" axis is selected as viewable.
VLM = theObject.Transform;
IP_LocalRot = theObject.objectOffsetRot; --Rotation to be used later.
IP_LOCAL = theObject.objectOffsetPos; --Invisible Pivot Local coordinates
--In relation to VLM matrix.
IP_WORLD = IP_LOCAL * VLM; --World Coordinates of Invisible Pivot. [Local To World Transform]
VLM_0 = matrix3 1; --Reset Visible Local matrix coordinates.

NEW_IP_LOCAL = IP_WORLD * inverse(VLM_0); --[World To local Transform]

theObject.Transform = VLM_0;
theObject.objectOffsetPos = NEW_IP_LOCAL;

--Now Handle Rotation:
--Since rotation of visible local matrix has been zeroed out,
--You must add that loss to the invisible pivot rotation.
GeomWorldRot = VLM.RotationPart + IP_LocalRot;
theObject.objectOffsetRot = GeomWorldRot;

)--[FN:alignPivotToWorld]

--Test Code;
OBJ = $;
thePos = [0,0,0];
alignPivotToWorld &OBJ;


I don't have the time, but if anyone would like to edit this
So that the pivot can be adjusted to be put ANYWHERE rather than aligned to the
World, let me know. I will re-post it and credit will be given.

Sunday, March 27, 2011

Solution To: Bone Animation Fails to Export in 3DSmax

Scenario:
1. You have a bone linked to a dummy.
2. You animate the dummy.
3. You export the bone animation.

Problem:
The bone animation does not seem to be in your FBX file when you test it
in your engine.

Why:
Linking moves the bone, but does not change the position controller value.

Solution:
This script to switch out your regular linking with this controller.



Function ConvertLinkedChildrenToPuppets
LOBA --Linked [Child] Objects array.
--Array of objects to have their link
--Constraint converted to script controller.
SGEO --Script Controller geometry object.
--The geometry object that will house the
--SINGLE script controller responsible for
--Controlling the position of all LOBA objects.
--
--*****************************************************
--More Scripts at: www.ProofOfProgress.BlogSpot.com **
--*****************************************************
--
=(

SCON = Scale_Script(); --Create Empty Scale Script.

--Go through geometry objects and add them to lists of exposed nodes.
--Also, add constants to the script controller that are matrices
--Of the original offset position between parent and child.
for g = 1 to LOBA.count do(
gstring = g as string;

--Add Constant
matrixName = "offsetMatrix" + gstring;
parentMatrix = LOBA[g].Parent.Transform;
childMatrix = LOBA[g].Transform;
offsetMatrix = childMatrix*Inverse(parentMatrix);
matrixValue = offsetMatrix;
SCON.AddConstant matrixName matrixValue

--Add Parent
NodeName = "ParentNo" + gstring;
theNodeObj = LOBA[g].Parent;
SCON.AddNode NodeName theNodeObj;

--Add Child
NodeName = "ChildNo" + gstring;
theNodeObj = LOBA[g];
SCON.AddNode NodeName theNodeObj;

--Un-Parent the arrangement:
LOBA[g].Parent = Undefined;
)--[Next g]


--Must add controller LAST or script code will be erased.
SGEO.Scale.Controller = SCON; --Add script controller to object.

--Create The Script:
script_code = "";
for g = 1 to LOBA.count do(
gg = g as string;
--cs = "childNo" + gg + ".Transform = offsetMatrix" + gg + "*ParentNo" + gg +".Transform;" + "\n";
cs = "childNo" + gg + ".Transform = offsetMatrix" + gg + "*ParentNo" + gg +".Transform;" + "\n";
script_code = script_code +cs;
)--[x]
script_code = script_code + "[1,1,1]" + "\n";
SCON.Script = script_code;

)--[FN:ConvertLinkedChildrenToPuppets]

LOBA = #();
LOBA[1] = $Circle01;
LOBA[2] = $Circle02;
LOBA[3] = $Circle03;
LOBA[4] = $Circle04;

SGEO = $SGEO;

ConvertLinkedChildrenToPuppets LOBA SGEO;

Link By Hand / Code in MaxScript

--The math behind linking AAA to BBB.
--If AAA and BBB are objects in 3DSmax.
offsetMatrix = $AAA.Transform*Inverse($BBB.Transform);
$AAA.Transform = offsetMatrix*$BBB.Transform;


--To Unlink The Original Parenting:
$.Parent = Undefined;

Dynamically Created Rollout For Storing Matrices in Maxscript

I just found out I don't need to do this because I can place
matrices as constants within script controllers.
Doing that dynamically would be better than doing this.
But here it is anyways.


Function StoreMatrices
MatrixArray --An array of matrices to go into the "MatrixHolder" custom attribute.
HoldingObject --The object the custom attribute will be attached to.
--
--*****************************************************
--More Scripts at: www.ProofOfProgress.BlogSpot.com **
--*****************************************************
--
--Function Summary:
--Dynamically creates a custom attribute with MatrixArray.Count number of Matrices.
--Can then be accessed by: TheObject.BaseObject.MatrixHolder.Matrix5
=(
--MUST use global or execute function will not create attribute correctly.
global MatrixHolderDEF_586_214_3958;

DQ = "\""; --Double quote character.

--Header Assemble:
H01 = "MatrixHolderDEF_586_214_3958 = attributes MatrixHolder(" + "\n";
H02 = "parameters main rollout:ro_howMany(" + "\n";
H_X = H01 + H02;

--Body Code Assemble:
matrixMAXstring = MatrixArray.Count as string;
varList = "";
For i = 1 to MatrixArray.count do(
mn = i as string; --Matrix Number.
coreCode = "Matrix" + mn + " type:#matrix3;" + "\n";
varList = varList + coreCode;
)--Next i

--Footer Assemble:
F01 = ")--[params]" + "\n";
F02 = "rollout ro_howMany "+DQ+"MatrixHolder"+DQ+" (" + "\n";
F03 = "label lab1 "+DQ+"MatrixCount=="+matrixMaxString+DQ+ "\n";
F04 =")--[x]" + "\n";
F05 =")--[End Custom Attribute Def]" + "\n";
F_X = F01+F02+F03+F04+F05;

--Assemble and Execute Definition:
exe_def = H_X+varList+F_X;
Execute(exe_def);

--Add the Compiled Definition to the Object:
custAttributes.add HoldingObject MatrixHolderDEF_586_214_3958 Unique:True BaseObject:True; --*******************<<<<

--Now that your custom attribute is created with the correct number of matrix slots,
--Fill the values in.
--global global_m;

global global_HoldingObj;
global global_MatrixArray;
For m = 1 to MatrixArray.count do(
gm = m as string;
global_HoldingObj = HoldingObject.BaseObject.MatrixHolder;
global_MatrixArray = MatrixArray;
--eCode = "HoldingObject.BaseObject.MatrixHolder.Matrix" + gm +"= MatrixArray["+gm+"];"
eCode = "global_HoldingObj.Matrix" + gm + "=global_MatrixArray["+gm+"];";
Execute(eCode);
)--[next m]


--Edit: Put the adding custom attribute code into the actual EXE-string.
--The EXE-string is executing on a different thread than the rest of the code.
--So if you tried "custAttributes.add HoldingObject MatrixHolderDEF Unique:True BaseObject:True"
--After using the EXE string, it would not work. Even SLEEP would not fix the problem.

--UPDATE: It appears to be executing on a different thread..
--But it is actually a scope issue that can be fixed by using a GLOBAL.
--Look at: "Why does Execute return 'undefined' when accessing a loop variable?"
--In maxscript help for more information.

)--[FN:StoreMatrices]


--Example Code:
MatrixArray = #();
TheObject = $;
MatrixArray[1] = Matrix3 [1,1,1] [1,1,1] [1,1,1] [1,1,1];
MatrixArray[2] = Matrix3 [2,2,2] [2,2,2] [2,2,2] [2,2,2];
MatrixArray[3] = Matrix3 [3,3,3] [3,3,3] [3,3,3] [3,3,3];
MatrixArray[4] = Matrix3 [4,4,4] [4,4,4] [4,4,4] [4,4,4];
MatrixArray[5] = Matrix3 [5,5,5] [5,5,5] [5,5,5] [5,5,5];

--Test Code:
theOBJ = $;
StoreMatrices MatrixArray theOBJ;

--Test to see if one of the matrices exist.
theOBJ.BaseObject.MatrixHolder.Matrix5

Saturday, March 26, 2011

Align Tool Does not Work in 3dsMax

Step1: Aligned one object's pivot to another object.
Step2: Orientation constrain them together.
Step3: Wonder why they popped out of place anyways.

Honestly, I am not sure why the align tool does not always properly align objects.
But a quick solution:

--Align Object 1 to object 2, Rotation ONLY.
Pos01 = $Object01.pos;
$Object01.Transform = $Object02.Transform;
$Object01.pos = Pos01;

Friday, March 25, 2011

Animation Export Solution Found

BACK STORY: -------------------------------
If you link say... A bone to a circle..
And then animate that circle...
And then bake out the keyframes of the BONE... You will NOT get any animation
to show up in the engine when you export the mesh and the bone.

Why? Because when an object is linked in 3DSmax, the controller values never change.

Try this:
Put a bone at [0,0,0] and then link it to something. Animate that something.
Then select the bone and execute the script:

$.pos.controller.value;

At different intervals along the timeline.

You will get the same value of [0,0,0] no matter where the bone is.

Solution:_________________________________
Put a script controller in a dummy object that controls ALL the linking constraints
for all the objects you want to export. The animation will now export properly.

Currently working on a script that will replace all linked child object's controllers
with a script controller.

I am calling it "ConvertLinkedChildrenToPuppets".

I already did a quick "by hand" test of my solution, so I know it works.
Time to start coding.

Tuesday, March 22, 2011

MaxScript Snippet: BitArray To Array


theBITS = #{2..5,7}
theARRAY = theBITS as array;

for i = 1 to theARRAY.count do(
ist = i as string;
print("[" + ist + "]==" + theARRAY[i] as string);
)--[x]

Number Of Texture Verticies in MaxScript UVW unwrap

Getting Number of Texture Vertices:

This:
UVertCount = GEOM.modifiers[#unwrap_uvw].NumberVertices()

NOT This:
MapCN = GEOM.modifiers[#unwrap_uvw].unwrap.getMapChannel();
UVertCount = polyop.getNumMapFaces GEOM MapCN;

Image Tube Source Code

Used to Automatically unwrap UVWS of segments of box or plane in order to make
a scrolling marque from a video of a scrolling marquee.
This is done by assigning each segment of the plane a different time offset of the video. We then then make a 3d scrolling marquee of any shape we desire from this setup created by the script.

Current Location of script on my system:
E:\TECH_ROOT\MaxScript\MyScripts\In_Developement\Image_Tube\Image_Tube_V5.ms





Get UVW's of Geometry Face in Maxscript

Note to self:
This code currently in:
E:\TECH_ROOT\MaxScript\MyScripts\In_Developement\Image_Tube\Research\Gvert_To_Uvert_Plan.ms



--Code to get UVWvert ID from face.
Xnode = $;
faceIndex = 18;
ithVertex = 1;


--Step1: --Get UVW vertex index from face selection.
$.modifiers[#unwrap_uvw].getVertexIndexFromFace faceIndex ithVertex

--Step2: --Turn the index of the Uvert into a selection.
$.modifiers[#unwrap_uvw].selectVerticesByNode bitarray Xnode

--Step3: --Turn selection into Uvert index ID.
$.modifiers[#unwrap_uvw].getSelectedVerticesByNode Xnode


--How would you get faces from selected Uvert?

--Gvert to Uvert algorithm:

--Step1:
--Take geometry vert. Use geometry vert to get the 4 faces connected to that vert.

--Step2:
----------using getVertexIndexFromFace
----------[$.modifiers[#unwrap_uvw].getVertexIndexFromFace faceIndex ithVertex]
----------Collect all of the Uverts associated with each face.

--Step3:
--Return the Uverts that the all the faces have in common.
--Will be 1 to N number of u-verts.


--Simpler Way:
--1: setSelectedGeomVertsByNode #{Gvert_Index} Xnode
--2: getSelectedVerticesByNode Xnode

Monday, March 21, 2011

Effect And Todo

Not as cool as I thought it would be.
Going home.

Todo Tomorrow:
Make script that splits UVWs into 4 quadrants Via Resizing unwrap.
The quadrant you get in will be based on the color of the wireframe.

Reason:
Will be a quick way to texture my model for game when script is complete.


Convert to Editable Poly with Maxscript


If only the max script listener worked all the time:

GeomButNotPoly = $
ConvertTo GeomButNotPoly Editable_Poly

-John Mark

Wednesday, March 9, 2011

Expose Nodes To Script Controllers in MaxScript



--Example of how to use a script controller: March2011

ScriptHolderOBJ = Sphere(); --Create object to hold script controller.
TestNode = Box(); --Object to expose to script controller

assembled_EXE_string = "quat 0 0 0 1 --I like Pie. I would eat it every day if I could.";

--*********************************************************************
--MUST ADD SCRIPT CONTROLLER TO OBJECT BEFORE YOU EDIT IT!!
--If you don't, none of your edits to the controller will stick and you will
--Be left with an empty script controller.
--*********************************************************************
SCON = Rotation_Script() --Script Controller for Rotation. (Position is "Float_Script" )
ScriptHolderOBJ.rotation.controller = SCON;

--Add The Hard Coded Nodes to script Controller.
SCON.AddNode "ExposedNode_01" TestNode --Equivalent of AddNode button.

--This step must
SCON.Script = assembled_EXE_string;