Showing posts with label groovy. Show all posts
Showing posts with label groovy. Show all posts

Monday, February 4, 2008

Handling a call to a missing method in different languages, Part 1

This is the first of a two-part series of posts about the way different languages allow you to handle a call to method that doesn't exist. I'll try to implement a little example with each of these languages. For this part I'm going to show Ruby, Smalltalk, Groovy and Boo.

The doesNotUnderstand message


Although this feature is called by many names, the origin may be the doesNotUnderstand message of Smalltalk. From its Wikipedia entry:


When an object is sent a message that it does not implement, the virtual machine sends the object the doesNotUnderstand: message with a reification of the message as an argument. The message (another object, an instance of Message) contains the selector of the message and an Array of its arguments...


This mecanism could be used for many tasks, from debugging to creating proxies. A nice reflection on this is The Best of method_missing and also in the reflection section of Smalltalk Wikipedia entry.

One of the most interesting uses is in the implementation of Ruby on Rails's ActiveRecord. In Under the hood: ActiveRecord::Base.find there's a nice description on how the find_... method is implemented using method_missing.


The example


For these posts I'm going to implement a little example of two classes that provides access to a CSV file by using this mecanism. For example given that we have the following file called "cars.csv" (taken from here):


Year,Make,Model
1997,Ford,E350
2000,Mercury,Cougar


and another called "scores.csv"


Name,Age,Score
Luis,31,1.8
John,35,1.9
Paul,25,1.6


For simplicity we assume that not double quotes are used.

Given that the first row are the headers, we can use this class to have access to the data of each record using the name of the header as if it was a member of the class. For example:


cs = CsvFile.new("cars.csv")
ps = CsvFile.new("scores.csv")

cs.each do|c|
puts c.Model
end

ps.each do|p|
puts p.Name
end


The basic strategy to create the example is to create two classes CsvFile and CsvFileEntry. One to represent the file and the other to represent each entry.

In general, the contents of the file will be loaded as an list of lists. The headers will be loading in a dictionary associated with its position.


The languages


For these posts I tried to find programming languages that have direct support for this feature. For most of these programming languages, the presented snippet is my first program in it, so please let me know if I missed something.

Ruby

In Ruby the method_missing method provides this functionality.

The method signature is the following:


obj.method_missing( aSymbol [, *args ] ) -> anObject


In our little example the:


class CsvFileEntry
def initialize(headers,content)
@headers = headers
@content = content
end
def method_missing(method_name,*args)
if (@headers.has_key? method_name.to_s) then
return @content[@headers[method_name.to_s]]
else
raise "Method not found #{method_name}"
end
end
end


Implementation for the CsvFile class is the following(without the file loading code):


class CsvFile
attr_reader :headers
attr_reader :content
def initialize(filename)
@filename = filename
@headers = Hash.new
@content = []
...
end

def entries
return content.collect {|c| CsvFileEntry.new(@headers,c)}
end
end


In the implementation of method_missing, the method_name argument is converted to string and used to lookup the name of the requested field. The headers instance variable contains a Hash with the positions of each header and the contents variable contains the contents of the entry.

Now we can use these classes as follows:


f = CsvFile.new("testfile.csv")
cars = CsvFile.new("cars.csv")

f.entries.each { |e| puts "#{e.Name} #{e.Age}" }

f.entries.each do |e|
puts e.Name
end

print "-------\n"

cars.entries.each do |c|
puts c.Model
puts c.Year
end


Smalltalk

As described above, in Smalltalk we have to implement the doesNotUnderstand method. This method receives a an instance of the Message class . This class contains information about the requested method and arguments.

The implementation of the CsvFileEntry class looks like this:


Object subclass: #CsvFileEntry
instanceVariableNames: 'headers contents'
classVariableNames: ''
poolDictionaries: ''
category: 'Langexplr-Classes'!


doesNotUnderstand: aMessage
| index |
^(headers includesKey: aMessage selector)
ifTrue:
[index := headers at: (aMessage selector).
contents at: index.]
ifFalse: [super doesNotUnderstand: aMessage].



initializeWith: theContents headers: theHeaders
headers := theHeaders.
contents := theContents.
^self.


The implementation of the CsvFile class(without the file loading code) looks like this:


Object subclass: #CsvFile
instanceVariableNames: 'fileName contents headers'
classVariableNames: ''
poolDictionaries: ''
category: 'Langexplr-Classes'


getContents
^contents.


getEntries
^(contents collect:[:e | (CsvFileEntry new) initializeWith: e headers: headers ]).

initializeWithFileName: aFileName
...
^ self.


An example of a use of these classes is the following:


scores := CsvFile new initializeWithFileName: 'testfile.csv'.

cars := CsvFile new initializeWithFileName: 'cars.csv'.

cars getEntries do:
[:entry|
Transcript show: (entry Model)].

scores getEntries do:
[:entry|
Transcript show: (entry Name)] .


Groovy

Groovy provides this feature for methods and properties. A description of this mecanism can be found in Using methodMissing and propertyMissing
. A nice example of a use of this mecanism is the GORM's Domain Class Querying.

In Groovy we need to implement the def propertyMissing(String name) method or the def methodMissing(String name,args) to handle a property or method access. The following CsvFileEntry implementation uses overrides both methods to provide property access and getter method(getXXXXX) access.


public class CsvFileEntry {
private HashMap headers;
private String[] contents;

CsvFileEntry(HashMap headers,String[] contents) {
this.headers = headers;
this.contents = contents;
}
def propertyMissing(String name) {
if (headers[name] != null) {
return this.contents[this.headers[name]];
} else {
throw new RuntimeException("Property not found ${name}");
}
}
def methodMissing(String name,args) {
def getterNameResult = name =~ /get(.*$)/;
if (getterNameResult.matches() &&
headers[getterNameResult.group(1)] != null) {
return this.contents[this.headers[getterNameResult.group(1)]];
} else {
throw new RuntimeException("Method not found ${name}");
}
}
}


The implementation for the CsvFile class is the following(without the file loading part):


span class="srckeyw">public class CsvFile {
def HashMap headers;
def contents;

CsvFile(String fileName) {
...
}

def entries() {
def result = []
for ( c in contents) {
result.add(new CsvFileEntry(headers,c));
}
return result
}
}


A use of this code looks like this:


def scores = new CsvFile("testfile.csv")
def cars = new CsvFile("cars.csv")


for ( e in scores.entries() ) {
println("${e.Name} --> ${e.getAge()}")
}

for ( c in cars.entries()) {
println(c.getModel());
println(c.Year)
}


Boo

In Boo this functionality is available by implementing the IQuackFu interface. A nice explanation of this feature is available from: If it walks like a duck and it quacks like a duck . Also Dynamic Inheritance - fun with IQuackFu provides more information . A very nice example called XmlObject.boo (available from Boo source distribution) shows how IQuackFu is used to easy the access to Xml trees.

The IQuackFu interface contains the following members:

  • def QuackInvoke(name as string, args as (object)) as object: for method calls

  • def QuackGet(name as string) as object: for property access

  • def QuackSet(name as string, value) as object: for property assignment




Since Boo allowed handling both method and property access I created two ways to access the data: by using the name of the header as a property or a call to a method called GetXXXXX.

The CsvFileEntry implementation is the following:


class CsvEntry(IQuackFu):
content as (string)
headers as Hash
public virtual def constructor(aContent as (string),aHeaders as Hash):
content = aContent
headers = aHeaders

def QuackInvoke(name as string, args as (object)) as object:
r = /Get(?<name>.*$)/.Match(name)

if (r.Success and headers.ContainsKey(r.Groups["name"].Value)):
index = headers[r.Groups["name"].Value]
return content[index]
else:
raise InvalidOperationException("Method ${name} not found")


def QuackSet(name as string, value) as object:
pass

def QuackGet(name as string) as object:
if(headers.ContainsKey(name)):
index = headers[name]
return content[index]
else:
raise InvalidOperationException("Property ${name} not found")


The CsvFile implementation (without the file loading part) :


class CsvFile():
headers = {}
content = []
public virtual def constructor(fileName as string):
...

public def Entries():
for e as (string) in content:
yield CsvEntry(e,headers)



A use of this class:


csvF = CsvFile("testfile.csv")
cars = CsvFile("cars.csv")


for e in csvF.Entries():
print e.Name


for c in cars.Entries():
print c.GetModel()



Code for this post can be found here.

For the next post the Python, Objective-C, Haxe, ActionScript and Perl examples will be presented.

Saturday, March 31, 2007

Plotting 3D functions with Groovy and Java3D

In this post I'm going to show a little demo that plots 3D functions by using Groovy and Java3D.

For this demo I'm going to use the Java3D builder created for previous posts.

The TriangleArray class will be used to create the grid to plot the function . Two triangles will be create for each set of 4 points. For future posts I'm going to try to change this to support TriangleStripArray or QuadArray.

In order to create each coordinate of the plane, a function that maps between the iteration number and the real coordinate to be plotted is required. In order to do this the createMapFunc was created, this function returns a function that maps between two coordinates.


Closure createMapFunc(int x1,int x2,double y1,double y2) {
double m = (y2 - y1)/(x2 - x1);
double b = y1 - m*x1;
return { x -> m*x + b}
}

Closure createFloatMapFunc(int x1,int x2,double y1,double y2) {
Closure f = createMapFunc(x1,x2,y1,y2);
return { (float)f(it)};
}



Given this we can now create the TriangleArray data required to plot the function:



Geometry createGeometry(Closure f,double min,double max,int gridSize) {
Closure fInter = createFloatMapFunc(0,gridSize-1,min,max);
TriangleArray ta = new TriangleArray(((gridSize-1)**2)*6,
TriangleArray.COORDINATES
|TriangleArray.NORMALS
);
int idx = 0;
float x,y,z;

for ( iY in (0..(gridSize-2))) {
for( iX in (0..(gridSize-2))) {
// First triangle
x = fInter(iX);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx,new Point3f(x,y,z));

x = fInter(iX+1);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx+1,new Point3f(x,y,z));

x = fInter(iX);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+2,new Point3f(x,y,z));

idx += 3;

// Second triangle
x = fInter(iX+1);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx,new Point3f(x,y,z));

x = fInter(iX+1);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+1,new Point3f(x,y,z));

x = fInter(iX);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+2,new Point3f(x,y,z));

idx += 3;
}
}
return ta;
}


The function to be plotted is a parameter to the createGeometry function.

For this example the following function will be used:


def f = { x,y -> Math.cos(x+y)*Math.sin(x-y)}


Now that we have the code to generate the 3D function geometry we can create the Java3D/Swing required elements by using the Swing, Java3D and SimpleUniverse builders.




SwingBuilder sb = new SwingBuilder();
SimpleUniverseBuilder sub = new SimpleUniverseBuilder();
Java3dBuilder jb = new Java3dBuilder();


JFrame aFrame = sb.frame(title:"Function",size:[500,500]){
panel(layout: new FlowLayout()) {
thePanel = panel(preferredSize:[500,500],
layout:new BorderLayout())
}
}

SimpleUniverse univ =
sub.simpleUniverse() {
viewingPlatform(nominalViewingTransform:true) {
orbitBehavior(
flags:OrbitBehavior.REVERSE_ALL,
bounds:new BoundingSphere(
new Point3d(0.0,0.0,0.0),
100.0))
}
viewer() {
view(minFrameCycleTime:5)
}
}

thePanel.add(univ.getCanvas(),BorderLayout.CENTER);

BranchGroup bg =
jb.branchGroup() {
transformGroup(
capability:
TransformGroup.ALLOW_TRANSFORM_WRITE) {
shape3d(geometry:createGeometry(f,-2.5,2.5,20),
appearance:polyAppearance())
}
}
bg.compile();

univ.addBranchGraph(bg);
aFrame.show()





The result of running the program looks like this:




As a last detail, the appearance of the surface is created with the createPolyAppearance function:



Appearance polyAppearance() {
Appearance app = new Appearance();
PolygonAttributes pa = new PolygonAttributes();
pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
app.setPolygonAttributes(pa);
return app;
}



Code for this experiment can be found here.

In future posts I'm going to try to add more features, like materials applied to the surface geometry, axis lines, etc.

Thursday, March 8, 2007

More Java3D and Groovy Builders

In the previous post, a basic Builder for Java3D was shown. For this post I've decided to take one another example from the Java3D distribution and try to implement the scene creation code by using the builder. Also I've received nice suggestions that and I'm going to implement.


The example that I'm going to use is SphereMotion.java which is a nice demonstration of spheres, animation and lights.

The scene creation code of this example can be found in the createSceneGraph method of the SphereMotion class in SphereMotion.java. I'm not going to show the code in this post.

This example contains the following elements:


  1. Uses the Background class to set the background color.

  2. Creates a big Sphere which is the central element of the scene

  3. Conditionally creates a PointLight, DirectionalLight or SpotLight for the Sphere illumination.

  4. Uses a PositionInterpolator and RotationInterpolator to perform an animations

  5. Uses AmbientLight for general scene illumination.




Helpers for Background, Sphere, PointLight, DirectionalLight, SpotLight, PositionInterpolator and AmbientLight were created.

According to the suggestions, we can use Groovy array literals to specify attribute values that require classes. For example instead of using "alpha: new Alpha(-1,4000)" use "alpha: [-1,4000]". This makes the code more readable.

The flexibility of having the control over attribute and element creation let's you support many ways of doing the same thing. For example, I wanted to allow the "position" attribute of the PointLight class to be specified as an instance of the Point3f class(for example "position:new Point(0.0f,0.0f,0.0f)) or as an array of three elements (for example "position:[0.0f, 0.0f, 0.0f]"). To implement this in the builder, the PointLightHelper code for the position attribute looks like this:


public class PointLightHelper extends LightHelper {

protected void applyAttributes(Node aNode,
Map attributes) {
super.applyAttributes(aNode, attributes);
PointLight pl = (PointLight)aNode;

// Check the position attribute
if (attributes.get("position") != null) {
Object value = attributes.get("position");
if (AttributeHelper.isArrayList3fTuple(value)) {
pl.setPosition(
AttributeHelper.arrayListToPoint3f(
(ArrayList)value));
} else
if (value instanceof Point3f) {
pl.setPosition((Point3f)value);
}
}
...


where


public static Point3f arrayListToPoint3f(ArrayList a) {
Vector3f result;
return
new Point3f(
((Number)a.get(0)).floatValue(),
((Number)a.get(1)).floatValue(),
((Number)a.get(2)).floatValue());
}

public static boolean isArrayList3fTuple(Object value) {
boolean result = false;
if (value instanceof ArrayList &&
((ArrayList)value).size() == 3 ) {
ArrayList a = ((ArrayList)value);
result =
a.get(0) instanceof Number &&
a.get(1) instanceof Number &&
a.get(2) instanceof Number;
}
return result;
}




The TransformGroupHelper class was also modified so more specific transformations can be specified. For example a I wanted to create a transform group to rotate on Y axis only. To solve this an attribute rotY(and rotX and rotZ) was created to support this directly on the TransformGroupHelper. This attribute was implemented as:



if (key.matches("rot(X|Y|Z)") &&
((value instanceof Number) )) {

double dValue = ((Number)value).doubleValue();

Transform3D theRotationTransform =
new Transform3D();
tg.getTransform(theRotationTransform);

switch(Character.toUpperCase(
key.charAt(key.length() - 1))) {
case 'X':
theRotationTransform.rotX(dValue);
break;
case 'Y':
theRotationTransform.rotY(dValue);
break;
case 'Z':
theRotationTransform.rotZ(dValue);
break;
}
tg.setTransform(theRotationTransform);
}




Once having the helpers for the new elements implemented, the only thing missing to resolve was to translate the scene creation code to the builder. One of the major issues was the creation of the Light object. In the original code, the Light was created like this:



switch (lightType) {
case DIRECTIONAL_LIGHT:
lgt1 = new DirectionalLight(lColor1, lDirect1);
lgt2 = new DirectionalLight(lColor2, lDirect2);
break;
case POINT_LIGHT:
lgt1 = new PointLight(lColor1, lPoint, atten);
lgt2 = new PointLight(lColor2, lPoint, atten);
break;
case SPOT_LIGHT:
lgt1 = new SpotLight(lColor1, lPoint, atten, lDirect1,
25.0f * (float)Math.PI / 180.0f, 10.0f);
lgt2 = new SpotLight(lColor2, lPoint, atten, lDirect2,
25.0f * (float)Math.PI / 180.0f, 10.0f);
break;
}

...

l1Trans.addChild(lgt1);
l2Trans.addChild(lgt2);




This means that the creation of the instance of the desired light is a parameter of the program. Also another issue was that two lights were created and different parents are assigned to each one. In order to support this we split the creation of both lights into two different switch statements.

The final code for the creation of this scene looks like this:



public BranchGroup createSceneGraph() {
Color3f eColor = new Color3f(0.0f, 0.0f, 0.0f);
Color3f sColor = new Color3f(1.0f, 1.0f, 1.0f);
Color3f objColor = new Color3f(0.6f, 0.6f, 0.6f);
Color3f lColor1= new Color3f(1.0f, 0.0f, 0.0f);
Color3f lColor2= new Color3f(0.0f, 1.0f, 0.0f);
Color3f alColor= new Color3f(0.2f, 0.2f, 0.2f);
Color3f bgColor= new Color3f(0.05f, 0.05f, 0.2f);

TransformGroup t1;
TransformGroup t2;

Java3dBuilder j3b = new Java3dBuilder()

BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

// Create the root of the branch graph
BranchGroup objRoot =
j3b.branchGroup() {
transformGroup(scale:0.4) {
background(color:new Color3f(0.05f, 0.05f, 0.2f),bounds:bounds)
sphere(radius:1.0f,
flags:Sphere.GENERATE_NORMALS,
divisions:80,
material:new Material(objColor,
eColor,
objColor,
sColor,
100.0f),
lighting:true
)
t1 = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE){
transformGroup(
translate:[0.0, 0.0, 2.0])
{
sphere(radius:0.05f,coloringAttributes:lColor1)
switch(lightType) {
case DIRECTIONAL_LIGHT:
directionalLight(color:lColor1,
direction:[0.0, 0.0, -2.0],
influencingBounds:bounds)
break;
case POINT_LIGHT:
pointLight(color:lColor1,
position:[0, 0, 0],
attenuation:[1,0,0],
influencingBounds:bounds)
break;
case SPOT_LIGHT:
spotLight(color:lColor1,
position:[0, 0, 0],
attenuation:[1,0,0],
direction:[0.0, 0.0, -2.0],
spreadAngle:25.0f * (float)Math.PI / 180.0f,
concentration:10.0,
influencingBounds:bounds)
break;
}
}
}
t2 = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE){
transformGroup(
translate:[0.5, 0.8, 2.0])
{
sphere(radius:0.05f,coloringAttributes:lColor2)
switch(lightType) {
case DIRECTIONAL_LIGHT:
directionalLight(color:lColor2,
direction:[-0.5, -0.8, -2.0],
influencingBounds:bounds)
break;
case POINT_LIGHT:
pointLight(color:lColor2,
position:[0, 0, 0],
attenuation:[1,0,0],
influencingBounds:bounds)
break;
case SPOT_LIGHT:
spotLight(color:lColor2,
position:[0, 0, 0],
attenuation:[1,0,0],
direction:[-0.5, -0.8, -2.0],
spreadAngle:25.0f * (float)Math.PI / 180.0f,
concentration:10,
influencingBounds:bounds)
break;
}
}
}
ambientLight(color:alColor,
influencingBounds:bounds)
rotationInterpolator(alpha:new Alpha(-1, Alpha.INCREASING_ENABLE,
0, 0,
4000, 0, 0,
0, 0, 0),
target:t1,
axisOfTransform:new Transform3D(),
schedulingBounds:bounds,
minAngle:0.0f,
maxAngle:Math.PI*2.0f);
rotationInterpolator(alpha:new Alpha(-1, Alpha.INCREASING_ENABLE,
0, 0,
1000, 0, 0,
0, 0, 0),
target:t2,
axisOfTransform:new Transform3D(),
schedulingBounds:bounds,
minAngle:0.0f,
maxAngle:0.0f);
positionInterpolator(
alpha:new Alpha(-1,
Alpha.INCREASING_ENABLE |
Alpha.DECREASING_ENABLE,
0, 0,
5000, 0, 0,
5000, 0, 0),
target:univ.getViewingPlatform().getViewPlatformTransform(),
yAxisOfTransform:(-1*Math.PI/2.0),
schedulingBounds:bounds,
start:2.0f,
end:3.5f);


}
}
objRoot.compile();

return objRoot;
}





The result is the same as the Java example:




If someone wants to look at the code of this experiment, it can be found here.

In future posts I'm going to try to implement more Java3D functionality into the builder.

Friday, February 23, 2007

Using Groovy Builders to create Java3D scenes

While listening to Java Posse episode 101 I found out about a nice feature of the Groovy language called Builders.

Groovy Builders provide a unified syntax for tree-like structures. The following example shows the use of the MarkupBuilder to create an XML document.


import groovy.xml.*;

writer = new StringWriter()
builder = new MarkupBuilder(writer)

builder.document(name:"a document") {
chapter(name:"First one") {
page1("first page")
page2("second page")
}
chapter(name:"First two") {
page1("page 1")

page2("page 2" )

}
}

println(writer.toString())



This program generates the following output:


<document name='a document'>
<chapter name='First one'>
<page1>first page</page1>
<page2>second page</page2>
</chapter>
<chapter name='First two'>
<page1>page 1</page1>
<page2>page 2</page2>
</chapter>
</document>


The nice thing about Groovy builders is that can also be used to create other kinds of tree structures such as Swing GUIs(SwingBuilder) or Ant tasks(AntBuilder) among others. For example, using the Swing builder you can create a simple form:



import java.awt.*
import javax.swing.*
import groovy.swing.SwingBuilder


sb = new SwingBuilder()

aFrame = sb.frame(title:"Hello World",size:[200,200]){
panel(layout: new FlowLayout()) {
scrollPane(preferredSize:[200,130]) {
tree()
}
panel(layout:new GridLayout(1,2,15,15)){
button(text:"Ok")
button(text:"Cancel")
}
}
}
aFrame.show()



Will generate:




All this is possible because Groovy provides an extensible factory mechanism for this syntax. I think this makes Groovy builders a much better feature than having a just XML literals (like in VB9) because it gives the developer freedom to select other data formats (objects, JSON,binary files, S-Expressions!,etc).

In other to create a new builder you have to inherit from groovy.util.BuilderSupport and implement the following methods:



protected abstract void setParent(Object parent, Object child);
protected abstract Object createNode(Object name);
protected abstract Object createNode(Object name, Object value);
protected abstract Object createNode(Object name, Map attributes);
protected abstract Object createNode(Object name, Map attributes, Object value);



The createNode methods are used to create one node of the tree with the given name,attributes and value. The setParent method is used to connect parent and child nodes.

In this post I'm going to create the first approach of a new builder for Groovy that can be used to build Java3D scenes.

Java3D scenes are represented as a scene graph. The objects in the scene graph can be arranged in a tree structure (although references between sibling nodes can exists, hence the name). For example:



This tree represents a scene with tree objects two spheres (sp) and one cube (cb) and one transformation group (tg) and a branch group (bg). Transformation groups, as the name says, applies a transformation (rotate,scale,etc) to all of its child elements. Java3D is a huge topic which is not intended to be covered in this post, for more information check here and here.


In this post I'll only cover a couple of Java3D classes, in future posts in I'm going to try to add more elements to the Groovy builder.

The main strategy is to create a new builder class (Java3dBuidler) which creates Java3D nodes by using helpers that map attributes to Node properties and constructor arguments. This mapping is done using helpers for each node. All the code for the builder support will be created using Java although Groovy could also be used.

First of all we create the Java3dBuilder class:


public class Java3dBuilder extends BuilderSupport {

HashMap<String,J3dNodeHelper> objects;

public Java3dBuilder() {
objects = new HashMap();

objects.put("branchGroup",new BranchGroupHelper());
objects.put("transformGroup",new TransformGroupHelper());
objects.put("colorCube",new ColorCubeHelper());
objects.put("rotationInterpolator",new RotatorInterpolatorHelper());
}
...

The Java3dBuilder constructor creates a map between the names of the elements to be used in the scene and the node creation helpers.

The J3dNodeHelper is the base class of the node creation helpers which are used to create instances of individual kinds of Java3D nodes. For example BranchGroupHelper will be used to create BranchGroup instances using the "branchGroup" name.

The setParent method as described above, creates a link between parent and child. In this case a parent must be a Java3D Group and the child a Java3D Node.


protected void setParent(Object parent, Object child) {
if (parent instanceof Group ) {
Group gParent = (Group)parent;
Node nChild = (Node)child;
gParent.addChild(nChild);
}
}


The createNode methods use the required node helper to create the required node .


protected SceneGraphObject createObject(String name) {
J3dNodeHelper c = objects.get(name);
return (SceneGraphObject)c.create(new HashMap());
}
protected SceneGraphObject createObject(String name,Map attributes) {
J3dNodeHelper c = objects.get(name);
return (SceneGraphObject)c.create(attributes);
}

protected Object createNode(Object name, Map atts, Object value) {
SceneGraphObject result = null;
String sName = (String)name;
SceneGraphObject current = (SceneGraphObject)getCurrent();
result = createObject(sName,atts);
return result;
}

protected Object createNode(Object object, Map atts) {
SceneGraphObject result = null;
String sName = (String)object;
return createObject(sName,atts);
}

protected Object createNode(Object name, Object value) {
SceneGraphObject result = null;
SceneGraphObject current = (SceneGraphObject)getCurrent();
String sName = (String)name;
result = createObject(sName);
return result;
}

protected Object createNode(Object name) {
SceneGraphObject result = null;
SceneGraphObject current = (SceneGraphObject)getCurrent();
String sName = (String)name;
result = createObject(sName);
return result;
}






The J3dNodeHelper class is implemented as:



public abstract class J3dNodeHelper {
public J3dNodeHelper() {
}

abstract protected Node createNode();

protected void applyAttributes(Node aNode,Map attributes) {
}

public Node create(Map attributes) {
Node result = createNode();
applyAttributes(result,attributes);
return result;
}
}




An example of a J3dNodeHelper used for TransformGroup is the following:



public class TransformGroupHelper extends J3dNodeHelper{
protected void applyAttributes(Node aNode,Map attributes) {
TransformGroup tg = (TransformGroup)aNode;
Set<Map.Entry<Object,Object>> s = attributes.entrySet();
for (Map.Entry<Object,Object> e : s) {
if (e.getKey().toString().equals("capability") &&
e.getValue() instanceof Integer) {
tg.setCapability(((Integer)e.getValue()).intValue());
} else
if (e.getKey().toString().equals("transform") &&
e.getValue() instanceof Transform3D) {
tg.setTransform(((Transform3D)e.getValue()));
}else {
System.out.println("Could not set property "+e.getKey()+
" ,"+e.getValue()+","+e.getValue().getClass());
}
}
}
protected Node createNode() {
return new TransformGroup();
}
}




The TransformGroupHelper class maps two TransformGroup attributes: capability and transform.

Also there're some cases where attributes have to be mapped to constructor arguments, because of properties that cannot be changed after object creation. In the case the create method must extract this values before creating the actual node . For example the helper for ColorCube class looks like this:



public class ColorCubeHelper extends J3dNodeHelper {
protected Node createNode() {
return new ColorCube();
}
protected Node createNode(double scale) {
return new ColorCube(scale);
}

public Node create(Map attributes) {
Node retValue;
if (attributes.get("scale") != null) {
retValue =
createNode(
((BigDecimal)attributes.get("scale")).doubleValue());
} else {
retValue = createNode();
}
applyAttributes(retValue,attributes);
return retValue;
}

}



Now that we have all the infrastructure ready, we can start using our builder. The following example show the creation of the scene graph using our builder:



public BranchGroup createSceneGraph() {
Java3dBuilder j3b = new Java3dBuilder()
BranchGroup objRoot ;
objRoot =
j3b.branchGroup() {
transformGroup(
transform:new Transform3D(
new Quat4f(
3.4f,
4.0f,
1.5f,
1.0f),
new Vector3d(),
1.0)) {
colorCube(scale:0.5)
}
};
objRoot.compile();
return objRoot;
}



Which generates the scene:



The code for scene creation of the HelloUniverse.java, included with Java3D, looks like this (removing the comments):



public BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();

TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(objTrans);

objTrans.addChild(new ColorCube(0.4));

Transform3D yAxis = new Transform3D();
Alpha rotationAlpha = new Alpha(-1, 4000);

RotationInterpolator rotator =
new RotationInterpolator(rotationAlpha, objTrans, yAxis,
0.0f, (float) (Math.PI*2.0f));
BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
rotator.setSchedulingBounds(bounds);
objRoot.addChild(rotator);

objRoot.compile();

return objRoot;
}




If we want to create this scene using our builder we write:



public BranchGroup createSceneGraph() {
TransformGroup tg;
Java3dBuilder j3b = new Java3dBuilder()
BranchGroup objRoot ;

objRoot =
j3b.branchGroup() {
tg = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE) {
colorCube(scale:0.4)
};
rotationInterpolator(
alpha:new Alpha(-1,4000),
target:tg,
axisOfTransform:new Transform3D(),
minAngle:0.0,
maxAngle:Math.PI*2.0,
schedulingBounds:new BoundingSphere(
new Point3d(0.0,0.0,0.0), 100.0)
);
}

objRoot.compile();
return objRoot;
}



More complex scenes can be created, for example:



public BranchGroup createSceneGraph() {
TransformGroup tg;
Java3dBuilder j3b = new Java3dBuilder()

BranchGroup objRoot;
objRoot =
j3b.branchGroup() {
tg = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE) {
colorCube(scale:0.1)
transformGroup(transform:new Transform3D(
new Quat4f(),
new Vector3d(0.0f,0.5f,0.5f),
1.0)) {
colorCube(scale:0.1)
}
transformGroup(transform:new Transform3D(
new Quat4f(),
new Vector3d(0.0f,-0.5f,0.5f),
1.0)) {
colorCube(scale:0.1)
}
transformGroup(transform:new Transform3D(
new Quat4f(),
new Vector3d(0.5f,0.0f,0.5f),
1.0)) {
colorCube(scale:0.1)
}
transformGroup(transform:new Transform3D(
new Quat4f(),
new Vector3d(-0.5f,0.0f,0.5f),
1.0)) {
colorCube(scale:0.1)
}
};
rotationInterpolator(
alpha:new Alpha(-1,4000),
target:tg,
axisOfTransform:new Transform3D(),
minAngle:0.0,
maxAngle:Math.PI*2.0,
schedulingBounds:new BoundingSphere(
new Point3d(0.0,0.0,0.0), 100.0)
);
}

objRoot.compile();
return objRoot;
}





Will generate:



In future posts I'm going to elaborate more on this topic. In this post only four Java3D Node classes were used, more helpers need to be created in order to support Materials, Lights, Primitives,etc.

Also in the future it'll be very interesting to see what F3 is doing for Swing/Java2D and soon for 3D graphics.