volumetric_procedural_models

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

Precedence rules from lowest to highest :

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

grammar

method

float

String

Shape

if

else

instanceof

print

rules

new

return

float

String

float[]

String[]

Shape[]

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

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"); } }

Shape3D.Box

Shape3D.Cylinder

Shape3D.Cone

Shape3D.Sphere

Shape3D.Ramp

Shape3D.Tetrahedron

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

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] + ")" ); }

**Math.PI** The value PI (approximately 3.14159265).

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

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 <ID> {

Variable definitions

Method definitions

main() method is required if this program is executable as an independent element.

}

method <ID> (<type> <ID1>, <type> <ID2>, …)

{

Variable definitions

Variable assignments

Rule Blocks

}

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] ); } }

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