ScriptMonkey

So, this is a little project I’ve been working on… (the initial reasons are less relevant now but they still affect my prime use-case)

ScriptMonkey is a Groovy shell window that can be used to control a JME scene. So the app starts up with a standard JME window with a few boxes in it, some default lights, etc… but it also opens up this separate Groovy Console window.

It might look something like this:

It’s still pretty basic but already pretty powerful. Mostly the theory is to make JME groovy by augmenting the existing interfaces just enough to be useful without making the user relearn too many things. So app refers to the app, rootNode refers to the rootNode, and most of the Java-isms you know and love will still work.

However, some things become much easier for experimenting and tweaking. My original use-case was to use this as a model importer because the SDK importer was causing me some pain and because the models I was loading had many meshes and materials and it was becoming tedious to adjust them all by hand. So I wanted some automation.

Here is what is currently available…

General Features
The script console remembers what you have entered from one run to the next. Whatever was there when you closed the app down (presuming no crash) will be there when you start up again. Useful for experimenting with whole scene setup.

Furthermore, if you put .groovy scripts in a local “scripts” directory they will be run automatically when the tool starts up.

As a test, here is the startup script that automatically vies me a sky:
sky = createSky( “galaxy+Z.png”, “galaxy-Z.png”,
“galaxy+X.png”, “galaxy-X.png”,
“galaxy+Y.png”, “galaxy-Y.png” );
rootNode += sky

The screen shot app state is included by default and can be invoked in the usual “SysRq” way. Debug stats can be toggled with F5 as usual. The setup also includes a (currently unused) Lemur-based HUD that can be toggled on/off with F3… but there is nothing in it right now. (coming soon)

Camera
Currently there is a fly-style camera in the 3D view that can be toggled on and off with the space bar. On the scripting side, you can access “camera” or you can use some convenience functions:
go x,y,z
…will move to that location
look Facing (where Facing is either North, South, East, West, Up, or Down)
…will look in that direction.
For example:
go 10, 0, 10
look North

Future: fly (to move over time), lookAt (to look at a location)

Assets and Models

loadTexture(String) or loadTexture(textureKey) will load a texture from the asset manager. Coming soon: loadTexture(File)

loadModel(String) loads a model from the asset manager in the conventional way.

loadModel(File root, String model) will temporarily set an asset root for loading the specified model.

loadModel(File model) will temporarily set an expanding asset locator that can search the nearby directories for the asset’s dependencies. This right here makes all the difference.

Files

chooseFile(extension) will open a file selector dialog and return the file that the user selects.

Future: choose file for saving, etc.

An example usage where the chooseFile and loadModel can be combined to add a model to the scene:
f = chooseFile “j3o”
model = loadModel f
rootNode += model

Materials
Materials have been augmented to behave more like maps. material.params will return a Map of all possible values that the material can have (set to null if they aren’t set) including the ones in the material definition itself. What this means is that you can do things like:

// See all available parameter names on a material
println mat.params.collect{ it.key }

// See all of the set parameters on a material
println mat.params.findAll{ it.value != null }

// Or just generally set and get material values
mat.params.Diffuse = ColorRGBA.Red
println mat.params.Ambient

Scene Stuff
As you saw earlier the sky factory is exposed through a convenience function. Also, Node has been enhanced to support some operator overloads:

// Add a child to a node
node += child

// Get a child at index 2
println node[2]

// Find the child with a particular name
println node[“Box”]

Spatial itself has also been enhanced to support a ‘visit’ method:

// Visit every spatial in someSpatial’s hierarchy in depth-first order
// and print it to the console
someSpatial.visit { println it }

And as mentioned, so far two sky functions (more to come, I guess):
Spatial createSky( Texture west, Texture east, Texture north, Texture south, Texture up, Texture down )
Spatial createSky( String west, String east, String north, String south, String up, String down )

Math
So far, I’ve enhanced Vector3f, Vector2f, and Vector4f to support various operator overloading. I also added some glsl style constructors for them.

v1 = vec3(1,2,3)
v2 = vec3(1)
v3 = v1 + v2 * 3
v4 = vec4(1,2,3,4)
v5 = v4.xyz

Support for ColorRGBA coming soon as well as some kind of support for Quaternions.

Download
So, if you are interesting in playing around, you can download it here:
http://code.google.com/p/jmonkeyplatform-contributions/source/browse/#svn%2Ftrunk%2FLemur%2Fextensions%2FScriptMonkey%2Frelease

Next Steps

Saving scenes/models. That’s the big one… no point in tweaking stuff if you can save it out again for later.

Now that I have the basic system in place, I plan to start building out the HUD-related scripting. I will add the ability to hook scripts on buttons in the hud and mouse events, etc… with another set of buttons to switch modes. I also have started for another project some blender-style Lemur-based manipulators that I will make available for clicked objects for moving/rotating.

My goal is to use this as a super-importer where a lot of scene management has to be done and having access to scripts and loops would be beneficial. It also helps me get around the fact that right now the only way to update the SDK blender support is to build the SDK from source… and for the blender importer that’s kind of crucial.

Note: some of my specific enhancements will be add-on scripts that I will put in the “scripts” startup folder and some will be enhancements to ScriptMonkey itself. I plan to make all of it available and hopefully such script snippets become a “thing” anyway.

Edit: removed the code blocks because they totally screw up formatting.

12 Likes

Totally awesome mr speed! great write up, really excited for this feature!

Kewl!
I can see several use cases where it will save me a lot of time.

Is there a way to get it into the sdk? The groovy console is something “standard” or did you make the ui yourself?

@nehon said: Kewl! I can see several use cases where it will save me a lot of time.

Is there a way to get it into the sdk? The groovy console is something “standard” or did you make the ui yourself?

The groovy console comes with groovy. I didn’t make it. I just inject a little into it.

nice one, here have your 2000th like :slight_smile: congrats

1 Like

This is pretty cool. The crucial part would now be determining the asset paths for the actual project later so you can store them in the j3o, else you won’t be able to load the model without your special loader. The SDK already does this, it uses a similar loader to find external files (including letting the user specify where the file is), then it replaces the asset paths of the external files with the actual paths in the project. I agree with nehon that it would be cool to have this as an extension to the SDK model importer, like an additional checkbox “scripted post processing” where you can even save script presets or so :slight_smile:

@normen said: This is pretty cool. The crucial part would now be determining the asset paths for the actual project later so you can store them in the j3o, else you won't be able to load the model without your special loader. The SDK already does this, it uses a similar loader to find external files (including letting the user specify where the file is), then it replaces the asset paths of the external files with the actual paths in the project. I agree with nehon that it would be cool to have this as an extension to the SDK model importer, like an additional checkbox "scripted post processing" where you can even save script presets or so :-)

Yeah, it would be cool.

re: Asset saving… yes this is always the harder part. The advantage here (over the importer) is that all of the models are in memory and so there is the potential to do more advanced things to save the data. Instead of having to decide during the load process how everything will get stored, the user has the chance to make changes, load additional models, setup new hierarchies, share textures that weren’t shared before, etc… and then possibly save all textures to one directory, all models to another, and so on. Or to just let every saved thing be self-contained in its own directory. Heck, for the shared texture case you could even script it to write out little manifest files for each model on the textures it uses.

Not the kind of thing that’s easy to precan into a UI and anyway we have the SDK already for those easier import cases.

One of the example use-cases I’m working from is the case where I have this big mother ship that’s actually made from a library of other parts. But loaded from blender it’s a bunch of separate meshes (or at least separate objects) with no relation to one another. I’d really like to save this out as a library of separate parts, sharing a common set of textures/materials, and then write the whole ship out just as a bunch of references to those parts. But even aside from that, just managing 100 different materials was about to kill me.

Now I can change things about every material in a hierarchy with just a few lines of script.

Note: In Script Monkey, all of the Groovy Console part is controlled in just one App State and the majority of that app state is managing the groovy shell that the Groovy Console uses. I imagine that it wouldn’t be difficult to replace the Groovy Console part with “SDK editory stuff” that delegates to the groovy shell inside. All of the other DSL-like features are in separate .groovy files and they do not rely on the Groovy Console bit at all.

@wezrule said: nice one, here have your 2000th like :) congrats

Heheheh… Thanks! I wouldn’t have noticed if you hadn’t pointed it out. Pretty cool.

I just uploaded a new release to: http://code.google.com/p/jmonkeyplatform-contributions/source/browse/#svn%2Ftrunk%2FLemur%2Fextensions%2FScriptMonkey%2Frelease

Details:

  • fixed a bug that would cause node += child to set node to an int.
  • Added initial “mode” support as a global app state.
  • Added “modeHook” scripting support
  • Added ColorRGBA math add-ons like for the Vector classes.
  • Added camera lookAt convenience functions. Takes Spatial, Vector3f, or x,y,z
  • Fixed a legacy bug in the console state where binding variables set through
    callbacks were getting wiped out.
  • Added a SelectionState to keep track of the selected spatial and provide click-through
    support.
  • Exposed the selected spatial through a “selected” binding and allow easy script-based
    selection changing with setSelected()
  • Removed the Node.plus() overload because it works strangely in the case of node += child
    and doesn’t look right as just node + child. Replaced it with leftShift, ie: node << child
  • Added Node.leftShift to add a child to a node.
  • Added ability to Node to do .each{}, .findAll{}, etc.
  • Added Spatial.flatten() to flatten the spatial into a single collection of itself and all
    of its children.
  • Added visit(Class) version of the visit method for visiting only nodes of a specific
    type. ie: rootNode.visit(Geometry) { println it }

An example of advanced groovy usage combining some of the above… Let’s say you wanted to find all of the unique “Color” material parameters:
[java]
colors = rootNode.flatten().findAll{ it instanceof Geometry }.collect{ it.material.params?.Color }.unique{ c1, c2 → c1.is(c2) ? 0 : 1 } - null
[/java]

Then you could do something like, increase all of the red values:
colors.each{ it.r *= 1.1 }

For the curious… here is is broken down:
rootNode.flatten()
-flattens the scene hierarchy into one collection. I could have started with any node but I chose the root for this example.

findAll{ it instanceof Geometry }
-filter it down to just a collection of Geometry

collect{ it.material.params.Color }
-for each of those Geometry objects, convert it to the “Color” material parameter. So we are left with a collection of color instances.

…now, some of those colors might be duplicate instances and we wouldn’t want to multiply their red twice, so we use the “unique” method…

unique{ c1, c2 → c1.is(c2) ? 0 : 1 }
-but we have to give it a custom closure because by default unique will do a .equals() check… and we want unique instances not unique values. c1.is(c2) is groovy’s way of doing an == check… in this case the closure needs to return 0, 1, or -1 like a comparator. I cheat and just return 0 for == and 1 for !=.

Finally, because some materials might not have a “Color” parameter, we end up with one null in the list… so I remove it with:

  • null

The nice thing is that in the shell, I could build that up element by element, experimenting along the way with what worked and what didn’t. I probably wouldn’t have gotten all of that right the first time.

3 Likes

Pretty nice project. If i understand correctly you have to write groovy ‘interfaces’ for all the java objects. If that is still the case i personally still prefer plain java.

Your example in java 8 would be: (Beside the unique operator since i don’t get what it does)

[java]
rootNode.flatten().stream().filter(f->f instanceof Geometry).map(f->f.getMaterial().getParam(“Color”)).filter(f->f!=null).forEach(f->f.setR(f.getR()*1.1));
[/java]

Don’t know about groovy, but with this i have full autocompletation from the ide which helps a lot. Sure, most of the java-groovy interfaces could be generated (at least all the delegater’s) but still this looks to me like a lot of ‘coding it twice’. In addition the java version offers an easy way to paralellize that thing.

But it also might be only my personal thing to be sceptical when it comes to merging languages in one application.

@zzuegg said: Pretty nice project. If i understand correctly you have to write groovy 'interfaces' for all the java objects. If that is still the case i personally still prefer plain java.

You never have to write groovy interfaces. Groovy is Java, basically. I add some operator overloading and some additional methods (though the expando metaclass) to existing classes to make them more powerful.

@zzuegg said: Your example in java 8 would be: (Beside the unique operator since i don't get what it does)

[java]
rootNode.flatten().stream().filter(f->f instanceof Geometry).map(f->f.getMaterial().getParam(“Color”)).filter(f->f!=null).forEach(f->f.setX(f.getX()*1.1));
[/java]

Don’t know about groovy, but with this i have full autocompletation from the ide which helps a lot. Sure, most of the java-groovy interfaces could be generated (at least all the delegater’s) but still this looks to me like a lot of ‘coding it twice’.

There is nothing to generate. Groovy is Java. It compiles to Java byte code. It uses Java classes.

Your Java8 example would require heavy modification to existing classes… like actually modifying the Java code for Node, Material, etc… In Groovy I was able to add this functionality at runtime.

@pspeed said: You never have to write groovy interfaces. Groovy is Java, basically. I add some operator overloading and some additional methods (though the expando metaclass) to existing classes to make them more powerful.

There is nothing to generate. Groovy is Java. It compiles to Java byte code. It uses Java classes.

Your Java8 example would require heavy modification to existing classes… like actually modifying the Java code for Node, Material, etc… In Groovy I was able to add this functionality at runtime.

Not really. Java 8 has default methods on interfaces, so most of this things are already there. Flatten method is not there, probably would have to be written externally - but rest is pure java 8. Almost… I don’t think that filter to map would understand that f suddenly becomes Geometry type. In your groovy example, you silently switch to reflection-based calls with dynamic typing, java 8 doesn’t allow that.

Just for comparison, here is the xtend code, without any extensions to current jme3 or expansions methods (node.flatten would have to be coded as expansion method, then it could be written as rootNode.flatten - unfortunately Spatial is not Iterable, so built-in flatten in xtend cannot be used)

[java]
val colors = nodes.filter(Geometry).map[material.params.findFirst[name ==“Color”]?.value as ColorRGBA].toSet
[/java]

This has some effect as your groovy code. You could do colors.forEach[r=r*1.1] as well (of course, when doing unique/set first, it would mean only one instance of each unique color would get modified, but thats beside the point).

Main benefit I see for groovy is that you can do all that on the fly, dynamically compiling, mixing with console-typed commands etc. For IDE-level stuff, I will take xtend over groovy any time - all the stuff above is strongly typed with full autocompletion etc (I don’t think that it.material.params was autocompleting for you in groovy?).

Now, if we allow adding extension methods in the equation (which I think you are doing with groovy), sky is the limit :wink: I’m using it for example in my entity system, where I write things like
[java]
findEntity(all(Camera,Visual) && Owner.where[player == myPlayer])
[/java]
when I want to create a filter for finding entity with Camera and Visual components, plus Owner component with given predicate - and yes, ‘player’ field of Owner component is autocompleting as well (Camera, Visual and Owner are class names)

But anyway, discussion should be about groovy, rather than xtend or java 8. I’m also using groovy for dynamic scripts and I’m thinking if they could be somehow made into usable console-level command language without preparsing. You know,
[java]
setCameraPosition(vec3(1,5,4),vec3(0,0,0))
[/java]
is not very sexy, but
[java]
cameraPosition at:1,5,4 look:0,0,0
//or just
cameraPosition 1,5,4 0,0,0
[/java]
looks already console-acceptable

I know groovy has some syntax for parentless methods calls and named parameters - there might be some way to trick it into reasonably smart processing I hope.

@abies said: Just for comparison, here is the xtend code, without any extensions to current jme3 or expansions methods (node.flatten would have to be coded as expansion method, then it could be written as rootNode.flatten - unfortunately Spatial is not Iterable, so built-in flatten in xtend cannot be used)

[java]
val colors = nodes.filter(Geometry).map[material.params.findFirst[name ==“Color”]?.value as ColorRGBA].toSet
[/java]

So how would this work? You used “nodes” but I used “node”. Node is not a collection but I made it seem as one for Groovy.

Anyway, I’d heard Java8 was adopting a lot of Groovy syntax… seems like it has.

For the record, I’ve written almost no code to support this. Any claims of “coding it twice” are greatly exaggerated.

For example, here is the entirety of what I’ve ‘added’ to Node:
[java]
Node.metaClass {

// Support node[12] style syntax
getAt { int index ->
    delegate.getChild(index);
}

// Support node["MyNode"] style syntax
getAt { String name ->
    delegate.getChild(name);
}

// Support node << child for adding chilren
leftShift { Spatial s ->
    delegate.attachChild(s);
}

// This will make it work nice with each, findAll, etc.
iterator {
    delegate.children.iterator();
}

flatten {
    def result = [delegate];
    children.each{ result.addAll(it.flatten()) }
    return result;
}

}
[/java]

I’ve written very little code to support this integration and most of it is like that up there… just to make things more Groovy friendly. All of the Java ways are still available and for example, even without the above you could still do node.children.each{} instead of node.each{}

Material took the most work because it is a map that isn’t a Map and because I wanted to expose all of the material properties to inspection, including the ones from the MaterialDef.

I mean, I only started the project Saturday night after all. Not bad for 48 hours calendar time and probably only 20 man hours at most: Google Code Archive - Long-term storage for Google Code Project Hosting.

1 Like
@abies said: But anyway, discussion should be about groovy, rather than xtend or java 8. I'm also using groovy for dynamic scripts and I'm thinking if they could be somehow made into usable console-level command language without preparsing. You know, [java] setCameraPosition(vec3(1,5,4),vec3(0,0,0)) [/java] is not very sexy, but [java] cameraPosition at:1,5,4 look:0,0,0 //or just cameraPosition 1,5,4 0,0,0 [/java] looks already console-acceptable

I know groovy has some syntax for parentless methods calls and named parameters - there might be some way to trick it into reasonably smart processing I hope.

The way I have camera stuff now is that I wrote root level helper functions, so:
go 1,5,4
lookAt 0,0,0
or:
go vec3(1,5,4)
…if you like.

Or…
go 1,5,4
lookAt rootNode[“MyObject”]

…which reminds me I need to add a go(Spatial) version.

Note: the camera “API”… the license header is longer than the code I think:
http://code.google.com/p/jmonkeyplatform-contributions/source/browse/trunk/Lemur/extensions/ScriptMonkey/src/com/simsilica/script/CameraApi.groovy

Tonight’s project will be to hook up the UI elements so scripts can easily add their own panels and buttons. Useful for custom setup of prebuilt scripts and/or adding buttons for repetitive tasks that require flying around and selecting objects before running the task.

@pspeed said: So how would this work? You used "nodes" but I used "node". Node is not a collection but I made it seem as one for Groovy.

Anyway, I’d heard Java8 was adopting a lot of Groovy syntax… seems like it has.

I think that there is small confusion. Zzuegg gave java 8 example, I gave xtend (Xtend - Modernized Java) example. These are different things. Java 8 has failed big time in my opinion, because they tried to get too close to scala rather than groovy, but have no language power inherent in scala. Xtend is in sweet spot for me - it is still very much strongly typed java, just with all syntax sugar you may want, but without trying to convert everything into parallel stream with verbose syntax for lambdas.

Regarding nodes/versus rootNode.flatten - yes, I have noted that I gave example without any extensions methods. You can add extension method as

[java]
class JmeExtensions {
def static flatten(Spatial spatial) {
val nodes = #
spatial.breadthFirstTraversal[nodes += it]
return nodes
}
}
[/java]

and then be able to call spatial.flatten on any spatial. BTW, in your example, I suppose that you have another .flatten definition for Geometry.metaClass returning just geometry itself?

Generally, groovy is a lot more powerful. Unfortunately, it is also sometimes slow in unexpected places (very small syntax change might suddenly fool it’s static optimizer and throw you into fully dynamic dispatch mode, which is very expensive there, lot more than plain reflection) and it is very easy to confuse it’s autocomplete support and go into ‘python’ ducktyping mode. I’m currently mixing both, using groovy for dynamic scripts which can be provided by mod writers/console-like typing and xtend for actual coding instead of java.

BTW, if anybody is interested, this is the monstrosity which xtend has done behind the scenes to expand that one-liner
[java]
Iterable<Geometry> _filter = Iterables.<Geometry>filter(nodes, Geometry.class);
final Function1<Geometry,ColorRGBA> _function = new Function1<Geometry,ColorRGBA>() {
public ColorRGBA apply(final Geometry it) {
Material _material = it.getMaterial();
List<MatParam> _params = _material.getParams();
final Function1<MatParam,Boolean> _function = new Function1<MatParam,Boolean>() {
public Boolean apply(final MatParam it) {
String _name = it.getName();
boolean _equals = Objects.equal(_name, “Color”);
return Boolean.valueOf(_equals);
}
};
MatParam _findFirst = IterableExtensions.<MatParam>findFirst(_params, _function);
Object _value = null;
if (_findFirst!=null) {
_value=_findFirst.getValue();
}
return ((ColorRGBA) _value);
}
};
Iterable<ColorRGBA> _map = IterableExtensions.<Geometry, ColorRGBA>map(_filter, _function);
final Set<ColorRGBA> x = IterableExtensions.<ColorRGBA>toSet(_map);
[/java]
It is converting everything to java and then from java to bytecode. It is transparent and allows to inspect generated things to learn possible performance gotchas (not that there are many).

@abies said: BTW, in your example, I suppose that you have another .flatten definition for Geometry.metaClass returning just geometry itself?

Yes, actually for any spatial. Here are the spatial enhancements in their entirety:
[java]
// Some general spatial enhancements
Spatial.metaClass {
visit { Closure visitor →
delegate.depthFirstTraversal( visitor as SceneGraphVisitor );
}

visit { Class type, Closure visitor ->
    delegate.depthFirstTraversal( {
        if( type.isInstance(it) ) {
            visitor(it);
        }
    } as SceneGraphVisitor);
}

flatten {
    return delegate;
}

}
[/java]

@abies said: I don't think that filter to map would understand that f suddenly becomes Geometry type. In your groovy example, you silently switch to reflection-based calls with dynamic typing, java 8 doesn't allow that.

Beside the missing ‘flatten’ the java 8 example is fully working. After the map the predicate is of course of the type geometry. The only thing not possible without modifications in other classes is the flatten thing, where the best solution would be writing a static FlattenService.

If you have the necessary classes declared you can even make all the changes needed at runtime with stock netbeans. Or if you are using JRebel you don’t even need the classes declared at startup.

@pspeed: i might have exaggerated with ‘coding it twice’. When i browse trough the .groovy files i see a lot of delegates:

For example:

[java]
Vector3f.metaClass{
multiply { Number scale ->
return delegate.mult(scale.floatValue());
}
}
[/java]

If you have to write such delegate code for every method you use on ‘java objects’ than that would be overhead (in terms of lines of code) from my point of view. But i have to admit i don’t know anything about groovy and so i might oversee the benefits of dooing this.

Add: @abies was right. Of course there is a cast missing
[java]
Flatten.flattenNode(rootNode).stream().filter(f->f instanceof Geometry).map(f->((Geometry)f).getMaterial().getParam(“Color”)).filter(f->f!=null).forEach(f->f.setR(f.getR()*1.1));
[/java]
would be my solution to the example

Flatter would be a simple
[java]
public class Flatten{
public static ArrayList<Node> flattenNode(Node node){
return Flatten.flattenNode(node,new ArrayList<Node>());
}
private static ArrayList<Node> flattenNode(Node node,ArrayList<Node> collection){
collection.add(node);
node.getChildren().forEach(f->Flatten.flattenNode(f,collection));
return collection;
}
}
[/java]

@zzuegg said: Beside the missing 'flatten' the java 8 example is fully working. After the map the predicate is of course of the type geometry.

assuming that flatten returns List of <Spatial>, are you saying that at

[java]
flatten().stream().filter(f->f instanceof Geometry).map(x->…
[/java]
x is of type Geometry ? How it is possible? at stream(), it is of type Stream<Spatial>, then we have
[java]
Stream<T> filter(Predicate<? super T> predicate);
[/java]
which has to return T, not specific subclass of T… What kind of magic make it working?

@abies said: assuming that flatten returns List of <Spatial>, are you saying that at

[java]
flatten().stream().filter(f->f instanceof Geometry).map(x->…
[/java]
x is of type Geometry ? How it is possible? at stream(), it is of type Stream<Spatial>, then we have
[java]
Stream<T> filter(Predicate<? super T> predicate);
[/java]
which has to return T, not specific subclass of T… What kind of magic make it working?

Yeah, i have edited the post above, but you was faster. It requires a cast, or an additional map