User Tools

Site Tools


volumetric_procedural_models

Procedural Shape Modeling Language (PSML) Syntax

Operator Precedence


Precedence and syntax for expressions is modeled after (but not equivalent to) expressions in Java.

Precedence rules from lowest to highest :

  1. &&, ||
  2. ==, !=, < , <=, >, >=, instanceof
  3. &, |, ^
  4. +, -
  5. *, /, %
  6. ** (exponentiation, e.g., 2^3 ==> 2**3)
  7. unary -, unary !


Keywords


grammar
method
float
String
Shape
if
else
instanceof
print
rules
new
return

Types


float
String
float[]
String[]
Shape[]
Multidimensional Arrays are supported, i.e., float[][]…[] and String[][]…[]

Geometric Booleans

One can perform boolean operations on both terminal symbols as well as non-terminal symbols. This is accomplished as part of the sequential code after a rule block.

There are two access methods that provide references to symbols from grammatical code, i.e., code within rule blocks.

Shape[] a = instances("<SymbolName>");
Shape[] b = terminals("<SymbolName>");

The instances() function returns an array of Shapes. The array contains the shapes associated with each created instance of the NonTerminal symbol having name <SymbolName> generated when the grammar was executed. Boolean operations performed on these objects are applied directly to the geometry of the NonTerminal symbol, i.e., they are applied to the primitive shape provided as the predecessor when the rules block was executed.

The terminals() function returns an array of Shapes. The array contains the shapes associated with each created instance of the NonTerminal symbol having the name <SymbolName> generated when the grammar was executed. In contrast to the instances() function, boolean operations performed on these objects are not applied to the geometry of the NonTerminal. Instead, the operation is applied to all terminal elements that were generated as children of the referenced NonTerminal symbol when the rules block was executed.

TODO: Explain how to use paths to reference specific subsets of symbols generated in the rules of the grammar or in any method invoked within the rules.

A boolean operation can then be performed by invoking the special function as follows:

for (float idxA=0; idxA < a.length(); idxA = idxA+1) {
   for (float idxB=0;  idxB < b.length(); idxB = idxB+1) {
 
       a[idxA].geometricBoolean(b[idxB],"<op>","<mode>");
 
   }
}

Where <op> may be '+' for union, '-' for difference, or '&&' for intersection and <mode> may be 'show' to show boolean operations that would otherwise be invisible or 'inherit' to enforce appearance inheritance between objects that are being merged as a result of the boolean.

Boolean operations have the format

cyl1Shape[0].geometricBoolean(cyl2Shape[0],"-","inherit");

where cyl1Shape[0] and cyl2Shape[0] are Shapes within a Shape array and the geometricBoolean() operates on the two Shapes.

If the operation is either a union ('+') or a difference ('&&'), the boolean operation merges the 2nd operand into the first operand, i.e., the 2nd operand is destroyed in the process of the boolean operation. For the operation in the code above, if the 2nd operand, cyl2Shape[0], is a terminal symbol, the reference to this Shape is destroyed in the process of the boolean operation.

If the operation is an intersection ('&&'), the boolean operation takes the difference of the two operand and any Shape variables generated by a call to terminals() are replaced with the result of the difference operation. For the operation in the code above, if the 2nd operand, cyl2Shape[0], is a non-terminal symbol, the shape associated with the non-terminal Symbol is generated and subsequently used to perform the geometricBoolean() operation and then discarded.

Booleans also are unique in that they generate a return value which may be captured from the geometricBoolean() operation. For example:

        Shape[] cyl1Shape = instances("cyl1");
        Shape[] cyl2Shape = instances("cyl2");
        Shape cyl3 = cyl1Shape[0].geometricBoolean(cyl2Shape[0],"-","inherit");

The resulting shape, cyl3, in the above code may then be used in subsequent boolean operations or perhaps as a source Shape for a new rules block.

Geometric booleans on Shape[] variables generated by a call to terminals() replace the first argument with the result of the boolean operation for the union and intersection ('+' and '&&') operations and replace both the first and second arguments with the result for the difference ('-') operation.

Geometric booleans on Shape[] variables generated by a call to instances() generate new Shapes which, if they are needed in subsequent code, must be captured as a return value from the geometricBoolean() function.

Hence, in the code above, cyl3 is a new shape which represents the result of the specified geometricBoolean() operation.

However, in the code below cyl3 is a reference, i.e., completely equivalent to, cyl1Shape[0].

        Shape[] cyl1Shape = terminals("cyl1");
        Shape[] cyl2Shape = instances("cyl2");
        Shape cyl3 = cyl1Shape[0].geometricBoolean(cyl2Shape[0],"-","inherit");

A complete example of a boolean operation is shown below: Contents of file: boxtest.psm

grammar boxtest {
	method main(){
            rules {
                Axiom::I("Box", {1,2,3})T(0,0,0.5)I("Box", {3,1,1}){cyl1, cyl2};
                cyl1::appearance("texture","textures/brick3.jpg"){j3d.terminal};
                cyl2::void(){j3d.terminal};
            }
 
        Shape[] cyl1Shape = terminals("cyl1");
        Shape[] cyl2Shape = instances("cyl2");
        cyl1Shape[0].geometricBoolean(cyl2Shape[0],"-","inherit");
    }
}

Namespace objects


Shape3D.Box
Shape3D.Cylinder
Shape3D.Cone
Shape3D.Sphere
Shape3D.Ramp
Shape3D.Tetrahedron

Shape access variables


myShape is a special variable that accesses the primitive shape associated with a non-terminal or non-terminal method.

Shape Variables

All shapes:
myShape.tx, myShape.ty, myShape.tz – position of the primitive

Shape3D.Box, Shape3D.Ramp
myShape.sx, myShape.sy, myShape.sz – (x, y, z) size of the primitive

Shape3D.Cylinder, Shape3D.Cone
myShape.r, myShape.h – (radius, height) size of the primitive

Shape3D.Sphere
myShape.r – radius of the primitive

Shape3D.Tetrahedron
myShape.edge1, myShape.edge2, myShape.deltaY, myShape.theta – edge1, edge2, height, and angle (theta) between edge1 and edge2 in the (x,y) plane

Example:

  if (myShape instanceof Shape3D.Box) {
     float[] dims = {myShape.sx, myShape.sy, myShape.sz};
     print( "(x,y,z) dims = (" + dims[0] + "," + dims[1] + "," + dims[2] + ")" );
  }

Constants


Math.PI The value PI (approximately 3.14159265).
Math.E The value of the natural exponential (approximately 2.7182818).

Built-In Functions


Math.random(x) – generates a random real-valued number between 0 and x.
Math.round(x) – truncates the value of X to the nearest integer.
Math.floor(x) – truncates the value of X to the nearest integer Z < x.
Math.ceil(x) – truncates the value of X to the nearest integer Z > x.
Math.sin(x) – returns the sine of x.
Math.cos(x) – returns the cosine of x.
Math.tan(x) – returns the tangent of x.
Math.atan(x) – returns the arc-tangent of x.
Math.sqrt(x) – returns the square-root of x.
Math.max(x,y) – returns the max of the set {x,y}.
Math.min(x,y) – returns the min of the set {x,y}.
Math.abs(x) – returns the absolute value |x|.
Math.len(x) – returns the string length of x.
Math.log(x) – returns the natural log of x.
Math.sgn(x) – returns the sign (+1 or -1) of the passed variable x.
Math.val(x) – converts the passed String x to a float.

Grammar Declaration


grammar <ID> {
Variable definitions
Method definitions
main() method is required if this program is executable as an independent element.
}

Method Declaration


method <ID> (<type> <ID1>, <type> <ID2>, …)
{
Variable definitions
Variable assignments
Rule Blocks
}

If Statement


if (condition) { }


IF Statement Example

grammar IfTest {
 method main() {
  float[] a;
  a = new float[5];
  a[1] = 1;
  if (a[1] == 1) {
    rules {
      1:Axiom::I("Box"){BoxShape};
      2:BoxShape::{j3d.Box};
    }
  }
  else if (a[1] == 2) {
  rules {
    1:Axiom::T(3,0,0)I("Cylinder"){RoundShape};
    2:RoundShape::{j3d.Cylinder};
  }
  }
  else {
  rules {
    1:Axiom::T(5,0,0)I("Cylinder"){RoundShape};
    2:RoundShape::{j3d.Cylinder};
  }
  }
  rules {
    1:Axiom::T(10,0,0)I("Cylinder"){RoundShape};
    2:RoundShape::{j3d.Cylinder};
  }
 }
}

print(<exp1> + <exp2> + … + <expN>);

grammar PrintTest {
 method main() {
  float[] a = {1,0};
  float idx=0;
  print( "a[" + idx + "] = " + a[idx] );
  }
}

Example

This example demonstrates how this grammar is capable of representing procedures which can now be independent of the geometry upon which the procedure operates. In the case below, we seek to build walls. The procedure of building a wall does not change for either a cylindrical or a linear (rectangular) wall. Current approaches require different procedures based on the geometry of the underlying wall. The example below highlights that this is no longer necessary for PSML. Here, the cylinder and the wall differ in only one rule (the ParentShape rule in the makewall method). Here, the cylinder must have a hollow center for an interior wall of the cylinder to exist which is distinct from rectangular walls which have no distinct inside/outside. Hence, the rectangular wall does not require the “space” non-terminal symbol that is present in the same rule for the cylinder.

grammar Wall {
 
 method makewall( String[] dirs) {
  if (myShape instanceof Shape3D.Box) {
   float[] dims = {myShape.sy, myShape.sx, myShape.sz};
   rules {
    1:ParentShape :: split( dirs[2], {dims[2]/5,3*dims[2]/5,dims[2]/5} ) 
	{ wallFace(dims,dirs), wallMiddle(dims,dirs), wallFace(dims,dirs) };
   }
  }
 
  if (myShape instanceof Shape3D.Cylinder) {
   float[] dims = {myShape.h, 2*Math.PI, myShape.r};
   rules {
    1:ParentShape :: split( dirs[2], {dims[2]-5,1,3,1} ) 
	{ space, wallFace(dims,dirs), wallMiddle(dims,dirs), wallFace(dims,dirs) };
    2:space::void(){j3d.myShape};
   }
  }
 }
 
 
 method wallMiddle(float[] dims, String[] dirs) {
  rules {
   1:WallMiddle :: split( dirs[0], {2*dims[0]/5,dims[0]/5,2*dims[0]/5} ) {walkway,mud,space};
   8:space::void(){j3d.myShape};
   9:walkway::{j3d.myShape};
   10:mud::{j3d.myShape};
  }
 }
 
 method wallFace(float[] dims, String[] dirs) {
  float[] dimsbrick = {dims[1]/10, 2*dims[1]/100};
  rules {
   1:WallFace :: repeat( dirs[0], {dims[0]/10,2*dims[0]/100,dims[0]/10,2*dims[0]/100} , 0) {brick_course_even,mortar,brick_course_odd,mortar};
   4:brick_course_even :: repeat( dirs[1], dimsbrick, 1/2) {brick,mortar};
   5:brick_course_odd :: repeat( dirs[1], dimsbrick, 0) {brick,mortar};
   6:brick::{j3d.myShape};
   7:mortar::{j3d.myShape};
  }
 }
 
 method main() {
  String[] dirsYXZ = {"y", "x", "z"};
  String[] dirsRTZ = {"height", "theta", "r"};
  if (1 == 1) {
   rules {
    1:Axiom::T(10,0,0)S(20,10,3)I("Box"){Wall};
    2:Wall::{makewall(dirsYXZ)};  
   }  
  }
  float[] dimsRTZ = {20,Math.PI/2,20};
  rules {
   1:Axiom::T(-dimsRTZ[0],0,0)I("Cylinder",{dimsRTZ[0],dimsRTZ[2]}){Wall};
   2:Wall::{makewall(dirsRTZ)};
  }
 }
} 
volumetric_procedural_models.txt · Last modified: 2012/01/11 21:56 by arwillis