Class Expr

3DS Max Plug-In SDK

Class Expr

See Also: Class Point3, List of Expression Types, List of Expression Variable Types, List of Expression Return Codes, Character Strings.

class Expr

Description:

This class may be used by developers to parse mathematical expressions. The expression is created as a character string using a straightforward syntax. Expressions consist of operators (+, -, *, /, etc.), literal constants (numbers like 180, 2.718, etc.), variables (single floating point values or vector (Point3) values), and functions (mathematical functions that take one ore more arguments and return a result). The return value from the expression may be a floating point value or a vector. There are many built in functions, operators and constants available for use.

All methods of this class are implemented by the system.

Developers wishing to use these APIs should #include \MAXSDK\INCLUDE\EXPRLIB.H and should link to \MAXSDK\LIB\EXPR.LIB.

Sample code using these APIs is shown below, and is also available as part of the expression controller in \MAXSDK\SAMPLES\CONTROLLERS\EXPRCTRL.CPP.

Variables may be defined and used in expressions. Variable names are case sensitive, and must begin with a letter of the alphabet, but may include numbers. They may be any length. To create a named variable, you use the method defVar(). This takes a name and returns a register number. Defining the variable creates storage space in a list of variables maintained by the parser, and the register number is used as an array index into the variable value arrays passed into the expression evaluation method (eval()).

To use the variable in an expression just use its name. For example if you define a variable named radius, you can use it in an expression like: 2*pi*radius. To give the variable a value, you define two arrays of variables and pass them to the evaluation method (eval()). There is one array for scalar variables, and one for vector variables. You pass these arrays along with the number of variables in each list. See the sample code below for an example.

The order of calling the methods of this class to evaluate an expression is as follows:

Declare an expression instance (Expr expr;)

Define the expression (char e1[] = "2*pi*radius";).

Define any variables (expr.defVar(SCALAR_VAR, _T("radius"));)

Load the expression (expr.load(e1);)

Evaluate the expression (expr.eval(...);)

There are no restrictions on the use of white space in expressions -- it may be used freely to make expressions more readable. In certain instances, white space should be used to ensure non-ambiguous parsing. For example, the x operator is used for to compute the cross product of two vectors. If a developer has several vectors: Vec, Axis and xAxis and wanted to compute the cross product, VecxAxis is ambiguous while Vec x Axis is not.

All the necessary information to evaluate an expression is completely stored within an expression object. For example, if you are passed a pointer to an expression object for which some variables have been defined that you knew the value of, you could get all the information you needed from the expression object to completely evaluate the expression. This includes the expression string, variable names, variable types, and variable register indices.

For complete documentation of the built in functions please refer to the 3ds max User's Guide under Using Expression Controllers. Below is an overview of the operators, constants and functions that are available:

Expression Operators:

Scalar Operators

 Operator Use Meaning

 + p+q addition

 - p-q subtraction

 - -p additive inverse

 * p*q multiplication

 / p/q division

 ^ p^q power (p to the power of q)

 ** p**q same as p^q

Boolean Operators

 = p=q equal to

 < p<q less than

 > p>q greater than

 <= p<=q less than or equal to

 >= p>=q greater than or equal to

 | p|q logical OR

 & p&q logical AND

Vector Operators

 + V+W addition

 - V-W subtraction

 * p*V scalar multiplication

  V*p "

 * V*W dot product

 x VxW cross product

 / V/p scalar division

 . V.x first component (X)

 . V.y second component (Y)

 . V.z third component (Z)

Built-In Constants:

 pi 3.1415...

 e 2.7182...

 TPS 4800 (ticks per second)

Expression Functions:

Trigonometric Functions

The angles are specified and returned in degrees.

 sin(p) sine

 cos(p) cosine

 tan(p) tangent

 asin(p) arc sine

 acos(p) arc cosine

 atan(p) arc tangent

Hyperbolic Functions

 sinh(p) hyperbolic sine

 cosh(p) hyperbolic cosine

 tanh(p) hyperbolic tangent

Conversion between Radians and Degrees

 radToDeg(p) takes p in radians and returns the same angle in degrees

 degToRad(p) takes p in degrees and returns the same angle in radians

Rounding Functions

 ceil(p) smallest integer greater than or equal to p.

 floor(p) largest integer less than or equal to p.

Standard Calculations

 ln(p) natural (base e) logarithm

 log(p) common (base 10) logarithm

 exp(p) exponential function -- exp(e) = e^p

 pow(p, q) p to the power of q -- p^q

 sqrt(p) square root

 abs(p) absolute value

 min(p, q) minimum -- returns p or q depending on which is smaller

 max(p, q) maximum -- returns p or q depending on which is larger

 mod(p, q) remainder of p divided by q

Conditional

 if (p, q, r) works like the common spreadsheet "if" -- if p is nonzero

  then "if" returns q, otherwise r.

Vector Handling

 length(V) the length of V

 unit(V) returns a unit vector in the same direction as V.

 comp(V, I) i-th component, where I=0, 1, or 2.

  comp([5,6,7],1) = 6

Special Animation Functions

 noise(p, q, r) 3D noise -- returns a randomly generated position.

  p, q, and r are random values used as a seed.

Sample Code:

The following code shows how the expression parser can be used. This code evaluates several expressions and displays the results in a dialog box. Both scalar and vector variables are used. One expression contains an error to show how error handling is done.

void Utility::TestExpr() {

 // Declare an expression instance and variable storage

 Expr expr;

 float sRegs[2]; // Must be at least getVarCount(SCALAR_VAR);

 Point3 vRegs[2]; // Must be at least getVarCount(VECTOR_VAR);

 float ans[3];

 int status;

 

 // Define a few expressions

 char e0[] = "2+2";

 char e1[] = "2.0 * pi * radius";

 char e2[] = "[1,1,0] + axis";

 char e3[] = "[sin(90.0), sin(radToDeg(0.5*pi)), axis.z]";

 char e4[] = "2+2*!@#$%"; // Bad expression

 

 // Define variables

 int radiusReg = expr.defVar(SCALAR_VAR, _T("radius"));

 int axisReg = expr.defVar(VECTOR_VAR, _T("axis"));

 // Set the variable values

 sRegs[radiusReg] = 50.0f;

 vRegs[axisReg] = Point3(0.0f, 0.0f, 1.0f);

 // Get the number of each we have defined so far

 int sCount = expr.getVarCount(SCALAR_VAR);

 int vCount = expr.getVarCount(VECTOR_VAR);

 

 // Load and evaluate expression "e0"

 if (status = expr.load(e0))

  HandleLoadError(status, expr);

 else {

  status = expr.eval(ans, sCount, sRegs, vCount, vRegs);

  if (status != EXPR_NORMAL)

   HandleEvalError(status, expr);

  else

   DisplayExprResult(expr, ans);

 }

 // Load and evaluate expression "e1"

 if (status = expr.load(e1))

  HandleLoadError(status, expr);

 else {

  status = expr.eval(ans, sCount, sRegs, vCount, vRegs);

  if (status != EXPR_NORMAL)

   HandleEvalError(status, expr);

  else

   DisplayExprResult(expr, ans);

 }

 // Load and evaluate expression "e2"

 if (status = expr.load(e2))

  HandleLoadError(status, expr);

 else {

  status = expr.eval(ans, sCount, sRegs, vCount, vRegs);

  if (status != EXPR_NORMAL)

   HandleEvalError(status, expr);

  else

   DisplayExprResult(expr, ans);

 }

 // Load and evaluate expression "e3"

 if (status = expr.load(e3))

  HandleLoadError(status, expr);

 else {

  status = expr.eval(ans, sCount, sRegs, vCount, vRegs);

  if (status != EXPR_NORMAL)

   HandleEvalError(status, expr);

  else

   DisplayExprResult(expr, ans);

 }

 // Load and evaluate expression "e4"

 if (status = expr.load(e4))

  HandleLoadError(status, expr);

 else {

  status = expr.eval(ans, sCount, sRegs, vCount, vRegs);

  if (status != EXPR_NORMAL)

   HandleEvalError(status, expr);

  else

   DisplayExprResult(expr, ans);

 }

}

 

// Display the expression and the result

void Utility::DisplayExprResult(Expr expr, float *ans) {

 TCHAR msg[128];

 

 if (expr.getExprType() == SCALAR_EXPR) {

  _stprintf(msg, _T("Answer to \"%s\" is %.1f"),

   expr.getExprStr(), *ans);

  Message(msg, _T("Expression Result"));

 }

 else {

  _stprintf(msg, _T("Answer to \"%s\" is [%.1f, %.1f, %.1f]"),

   expr.getExprStr(), ans[0], ans[1], ans[2]);

  Message(msg, _T("Expression Result"));

 }

}

 

// Display the load error message

void Utility::HandleLoadError(int status, Expr expr) {

 TCHAR msg[128];

 

 if(status == EXPR_INST_OVERFLOW) {

  _stprintf(_T("Inst stack overflow: %s"),

   expr.getProgressStr());

  Message(msg, _T("Error"));

 }

 else if (status == EXPR_UNKNOWN_TOKEN) {

  _stprintf(msg, _T("Unknown token: %s"),

   expr.getProgressStr());

  Message(msg, _T("Error"));

 }

 else {

  _stprintf(msg,

   _T("Cannot parse \"%s\". Error begins at last char of: %s"),

   expr.getExprStr(), expr.getProgressStr());

  Message(msg, _T("Error"));

 }

}

 

// Display the evaluation error message 

void Utility::HandleEvalError(int status, Expr expr) {

 TCHAR msg[128];

 

 _stprintf(msg, _T("Can't parse expression \"%s\""), expr.getExprStr());

 Message(msg, _T("Error"));

}

 

// Display the specified message and title in a dialog box

void Utility::Message(TCHAR *msg, TCHAR *title) {

 MessageBox(ip->GetMAXHWnd(),

  (LPCTSTR) msg, (LPCTSTR) title, MB_ICONINFORMATION|MB_OK);

}

Methods:

Prototype:

Expr()

Remarks:

Constructor. Internal data structures are initialized as empty.

Prototype:

~Expr()

Remarks:

Destructor. Any currently defined variables are deleted.

Prototype:

int load(char *s);

Remarks:

This method is used to load an expression for parsing. An error code is returned indicating if the expression was loaded. A successfully loaded expression is then ready for evaluation with the eval() method.

Parameters:

char *s

The expression to load.

Return Value:

See List of Expression Return Codes.

Prototype:

int eval(float *ans, int sRegCt, float *sRegs, int vRegCt=0, Point3 *vRegs=NULL);

Remarks:

This method is used to evaluate the expression loaded using load(). It returns either a scalar or vector result.

Parameters:

float *ans

The numeric result of the expression is returned here, i.e. the answer . For scalar values this is a pointer to a single float. For vector values, ans[0] is x, ans[1] = y, ans[2] = z. You can determine which type of result is returned using the method getExprType().

int sRegCt

The number of items in the sRegs array of scalar variables.

float *sRegs

Array of scalar variables.

int vRegCt=0

The number of items in the vRegs array of vector variables.

Point3 *vRegs=NULL

Array of vector variables.

Return Value:

See List of Expression Return Codes.

Prototype:

int getExprType();

Remarks:

Returns the type of expression. See List of Expression Types.

Prototype:

TCHAR *getExprStr();

Remarks:

Returns a pointer to the currently loaded expression string.

Prototype:

TCHAR *getProgressStr();

Remarks:

If there was an error parsing the expression, this method returns a string showing what portion of the expression was parsed before the error occurred.

Prototype:

int defVar(int type, TCHAR *name);

Remarks:

Defines a named variable that may be used in an expression.

Parameters:

int type

The type of variable. See List of Expression Variable Types.

TCHAR *name

The name of the variable. This name must begin with a letter, may include numbers and may be any length.

Return Value:

The register number (into the sRegs or vRegs array passed to eval()) of the variable.

Prototype:

int getVarCount(int type);

Remarks:

This method returns the number of variables defined of the specified type. When you call eval() on an expression, you must make sure that the variable arrays (sRegs and vRegs) are at least the size returned from this method.

Parameters:

int type

See List of Expression Variable Types.

Prototype:

TCHAR *getVarName(int type, int i);

Remarks:

Returns the name of the variable whose index is passed, or NULL if the variable could not be found.

Parameters:

int type

The type the variable. See List of Expression Variable Types.

int i

The register number of the variable.

Prototype:

int getVarRegNum(int type, int i);

Remarks:

When you define a variable with defVar(), you get a back a register number. If your code is set up in such a way that saving that register number is not convenient in the block of code that defines it, you can use this method later on to find out what that return value had been. For example, one piece of code might have:

expr->defVar(SCALAR_VAR, "a"); // not saving return value...

expr->defVar(SCALAR_VAR, "b");

and then right before evaluating the expression, you might have some code such as:

for(i = 0; i < expr->getVarCount(SCALAR_VAR); i++)

if(_tcscmp("a", expr->getVarName(SCALAR_VAR, i) == 0)

aRegNum = expr->getVarRegNum(SCALAR_VAR, i);

Of course, this is a bit contrived -- most real examples would probably have tables to store the variable names, register numbers, etc. and thus would not need to call this method. It is available however, and this makes the expression object self-contained in that everything you need to evaluate an expression with variables (other than the variable values themselves) is stored by the expression object.

Parameters:

int type

See List of Expression Variable Types.

int i

The variable index returned from the method defVar().

Return Value:

The register index for the variable whose type and index are passed.

Prototype:

BOOL deleteAllVars();

Remarks:

Deletes all the variables from the list maintained by the expression.

Return Value:

TRUE if the variables were deleted; otherwise FALSE.

Prototype:

BOOL deleteVar(TCHAR *name);

Remarks:

Deletes the variable whose name is passed from the list maintained by the expression. Register numbers never get reassigned, even if a variable gets deleted. For example, if you delete variables 0-9, and keep variable 10, you're going to need to pass in an array of size at least 11 to the eval() method, even though the first 10 slots are unused.

Parameters:

TCHAR *name

The name of the variable to delete.

Return Value:

TRUE if the variable was deleted; otherwise FALSE (the name was not found).