Skip to content

Archive

Category: M3G/JSR184

Nothing, however simple, goes without saying

My girlfriend borrowed a couple of cables from her office today, so I finally managed to wire the E61 to my netbook. As a first best, I hoped, Eclipse Pulsar would recognize my device. As a second best, I was thinking using the new and shiny Ovi suite to install my test app. To the average noob, Pulsar is the usual rhizomy mess of menus and panels, so after toying with the spider awhile I decided to try my luck online, indeed landing a nice Wiki page Entitled “How to install Java ME application in mobile phone“. Among numerous equally-unattractive-to-a-developer ways to install java apps on a nokia phone, I quote:

“If the device has a serial cable port and connectivity software for a PC, the MIDlet can be installed on the device over a serial cable”

Which I edited to:

To install the MIDlet, double-click on either the *.jar or *.jad file and follow on-screen instructions. On a PC, if jar/jad files are not associated with PC suite or Ovi suite by default, you can right click and select “open with” then choose “Ovi suite” or “PC suite” as applicable.

I always thought that PC suite associating jar files to itself in this way is somehow of a pre-emptive way to suggest that java doesn’t have a life beyond Nokia phones. Now I will modify my file associations, as I happen to have tens of thousands of lines of code on my PC that are nothing MIDlet.

I don’t care whether this goes without saying or not. I wasted another hour on this. I’m in a bad mood.

If it doesn’t work, downgrade CLDC and/or MIDP

In Pulsar, open the M folder labeled Application Descriptor. Then select create package to actually get a deployable jar/jad file. Since my device is 5 years old, I had a friendly ‘can’t run application’ or such message when I tried installing. By default, Pulsar is configuring to MIDP 2.1 (at the time of writing this, using the latest SDK). Until I find a reason not to, I’ll just downgrade CLDC and MIDP (in the ‘M panel’) to the oldest version that I can run something against.

Pingoo goes live

After I downgraded MIDP to 2.0, (note the advice on Nokia wiki if it doesn’t work for you), the Pingoo displayed at a decent FPS on my E61. I really need to come up with something chunkier, right?

So I used fractal subdivision in Blender to bump the poly count to ~10,000. This slows down the M3G exporter to a crawl because it spits out console output for every face. The M3G file inflated from 21kb to 565kb. FPS dropped to 2-3 frames per second, flat shaded, with 2 point lights enabled (no textures).

My first attempt at loading my sample 3D scene on an actual device didn’t work out too well.

I have two smartphones handy and one connection cable. Bluetooth died on my netbook long ago anyway. Both devices are 5 years old. Although my E61 is listed as Ovi compatible (the other isn’t), it doesn’t connect. Maybe I just need the right cable :)

Now that I have the Ovi suite installed, I had a quick look and… the prospect isn’t great. I hope they will understand someday that a web browser (versus native software) is just a let-down way to buy apps. So you open the Ovi suite, click on an app and expect pressing a download or buy button. Instead they fire up the default web browser. The UI and store presentation feel unpolished, like they didn’t have UI designers, or CSS enabled coders. Maybe it looks better on-device…

The Ovi suite itself seems like a half baked flash app made by a high school kid. They didn’t even manage a decent scroll bar.

I’m losing interest. And this may be the right time to explain why I’m interested after all. So why should an iPhone dev studio consider other devices?

  1. Workflow and release schedule. There are tons (literally tons) of devices out there that have nothing like the screen definition or horse power of an iPod 2nd gen. Meaning you simply can’t put as much content or smart code. Meaning low end devices are a good market for an early release. So with de-spec-ed devices available, you can release a ‘lite’ version earlier, thus abiding to ‘release early, release often’. Meaning that finally you can release a better app (benefiting early feedback etc…) on higher spec devices.
    Just from a technical point of view, java code is safer than compiled code, so even targeting comparable devices, the release cycle is bound to be shorter (that is, unless you mean to release on all and every Java enabled device at once)
  2. Not everybody will ever have an iDevice. In my company many people are carrying HTCs running Android. A (happy) few are carrying an iPhone. In the ‘real world’ around us, iPhones are visible exceptions, nothing like the norm. Now don’t tell me everybody will finally get an iPhone. Even taking aside pricing considerations, just imagine what it would be like? A mobile phone is something users carry with them. Consciously or not, their choice of a device reflects their personality as much as their means. One size fits all may be good for OSes – well that’s because an OS is something people don’t care about, as long as it works (and the more it’s like anybody else’s, the more ‘it works’).
  3. Anybody can enjoy a nice app. A quick look at the Ovi store shows that people didn’t wait for the iPhone to make mobile games (!). No matter what the store(s) may look like, there is an app market beyond iTunes, and I’m confident this market will grow, part because people see iPhone users enjoy apps on the go, part because manufacturers are making efforts to catch up.

Personally I think plurality is all good, and everybody enjoying the same thing on the same device at the same time is… terrifying. So the sooner competitors put their act together, the happier I’ll be.

But if you stay away from this s**t, I won’t blame you.

In order to get started porting my engine to JME, understanding this example seems like a very good start. It’s also a quick, hands on way to understand M3G and the blender exporter. So I’ll replace the Pogoroo by one of the sample files associated with the blender exporter. This will break the app and I’ll learn how it works by fixing it.

Please refer to my last article for links to this example — won’t be much use if we’re not looking at the code together.

I really liked the Pogoroo example. Apparently this was originally provided by Superscape, and somehow bundle in Sun’s edition of the JME before landing in the Samsung SDK.

I also like the blender M3G exporter. So I did this…

myWorld = (World)Loader.load(“/com/superscape/m3g/wtksamples/pogoroo/content/bebe.m3g”)[0];
//myWorld = (World)Loader.load(“/com/superscape/m3g/wtksamples/pogoroo/content/pogoroo.m3g”)[0];

…after trying this:

myWorld = (World)Loader.load(“c:/bebe.m3g”)[0]; // replace by sample model exported from blender.

Since an absolute path doesn’t work, we can tell that there’s a configuration bit somewhere setting the path for resources.

Now, you’d figure that we can’t replace the original model by another one, and you’re almost right. Except instead of a black screen we get a very cute 3D rendering of a Penguin (Pingoo?) and an engaging exception.

So I think we’re set. We have a working JME project manager, a working simulator and a working assets exporter. Time to understand some of this stuff. Now, this is what triggers an exception:

tRoo = (Group)myWorld.find(POGOROO_MOVE_GROUP_TRANSFORM_ID);
tCams = (Group)myWorld.find(CAMERA_GROUP_TRANSFORM_ID);
acRoo = (Group)myWorld.find(POGOROO_TRANSFORM_ID);
animRoo = (AnimationController)myWorld.find(ROO_BOUNCE_ID);

I haven’t looked at JSR184 in the past 5 years or more, and I never actually used JSR184. The beauty of it is that it doesn’t require an explanation(?).

Here’s what I’m finding:

  • I moved the camera in the original blender file and exported again. The camera updates in the MIDlet, so this camera is actually in use.
  • None of the above calls (tRoo = etc…) does actually trigger an exception, so they’ll just be returning null (I don’t have a debugger setup and line numbers seem wrong, so I’m tracing)

Surely this would trigger a NullPointerException at this point.

AnimationTrack track=null; // changed this way because there is already code to provide defaults instead.
//AnimationTrack track = acRoo.getAnimationTrack(0); // exception!

Once modified as above, we don’t have an exception anymore. We can use the phone simulator’s controls to try to move the Pingoo and it obviously doesn’t budge. More worrying, we don’t get an exception either.

I want to reassign tRoo, tCams, cRoo, animRoo. If we can do that, we’ll know that we know for sure how the exporter generates IDs. But before that I’m really curious to find out why trying to operate the controls doesn’t throw an exception.

So I started tracing this stuff again, and I found that exceptions raised within RefreshTask‘s run() method (Oh my… they use a timer for that!?) aren’t displayed. We can catch them though.

So what do we need?

This is what gets extracted first:

private AnimationController animRoo; // 5, 18
private Group tRoo;
private Group tCams;
private Group acRoo;

In my last article I mentionned that the blender M3G exporter allows the user to specify a user ID for scene nodes – scene nodes are retrieved using an integer – a (hopefully) unique identifier. The Pogoroo example demonstrates the same idea.

I missed several interesting facts about the exporter and the provided example last time:

  1. The exporter assigns ID 0 to anything that doesn’t have a user defined ID (so much for unique IDs?)
  2. In the provided example 2 nodes have ID 1. One is the armature controlling the Pingoo, the other is an animation controller.

The exporter displays export related data (includings IDs) in the blender console. Rudimentary, but it helps.

Bound animRoo

The first item I managed to rebind was animRoo. I used the second animation in the blender file, and this works easily because it has ID 2 by default, and that is a unique ID.

Bound a transform group for the camera (tCams)

In the blender scene browser (I’ll post a screenshot), any object seems to be living under a transform group. However, although tCams expects a transform group, the exporter doesn’t create one for the camera. Instead the transform and the camera seem collapsed to one node.

So I added an ‘Empty’ in blender and parented it from the camera (so the camera is a child of the empty). I named this Empty#25 (25 is an arbitrary int I came up with and voila I managed to bind my camera group.

tRoo and acRoo

I have these two left. Now I have nodes for both the armature and the Pingoo, but they’re not groups. Since I already got into this trap, I thought OK, why should we have two transforms for the Pingoo. Not diving deeper into the code, I’m just guessing there’s an inner transform for the rotation, and an outer transform for the translation. So I made two empties, further complicating the transform hierarchy, but maybe necessary to get this to work. I ‘named’ them 200, 201 and updated the static ints in the java file.

I wouldn’t say this feels like it works as I expected. However…

At the next run, several interesting things happen:

  1. I can see the little Pingoo moving it’s beak. So our animation is working out, big time!
  2. Left/Right keys appear to rotate the Pingoo
  3. Top/Down appear to move the Pingoo

I write ‘appear to’ because these behaviors seem somehow incorrect. I put the camera back in place (facing the pingoo) and moved the transforms (aka tRoo, acRoo) to coincide with the Pingoo’s vertical axis.

Interlude

My cute project manager seems to be getting moody. Also, when I check my process my manager, I find 4 (yes, four!) instances of java running simultaneously. Admittingly they don’t seem very busy, but that’s just a cherry on top of weird taskbar widgets that I didn’t have before installing the JME SDKs (three of these).

Guess I’ll try a ‘cold start’.

Finally

I checked things up after restarting the IDE. The Pingoo is moving along the wrong axis (got bold enough to actually add some terrain so I could get a fair idea). It’s rotating too, but somehow I’m not convinced the camera is following as it should. This seems to be down to axis orientations (and assumptions the ‘game logic’ makes about it).

There’s a lot of good stuff in here. JSR184 clearly supports bone animation, textures and lighting (looks horrible in the simulator) out of the box, never mind a well formed scene-graph. Plus, the exporter gets it right too, although I really need to do something about these IDs.

My engine maybe implements 10% of all this stuff, not to say anything about my exporter. So porting the engine ‘facade’ and reimplementing my game logic on top of that may be much easier than I anticipated. Proof also, than 10% of a game engine can run a pretty nice game. OK… I do have quite a bit of stuff in there that’s clearly not scoped under “3D rendering”  :)

The frame rate is defeating in the simulator, but it doesn’t say much about how it would look on a phone.

I think the sample *.m3g files (and the methods used to load and display them) look neat. I also noticed that Blender has an exporter for *.m3g with support for textures, armatures and animation. Gerhard Völkl clearly put a lot of work in this exporter, topping 3000 lines of code in a single file (a couple dozen classes in there, it’s actually quite readable).

(note: on a PC you can find the source for blender py scripts under Documents and Settings\User\Application Data\Blender Foundation\Blender\.blender\scripts)

The linked page also provides samples files to help us understand how the exporter interprets blender files. There are two things I immediately want to know:

  • How are animations exported? In Blender, bone animations are not directly connected to armatures. Export plugins typically use a kind of naming convention, and this one makes no exception, for example Talk#A1E10#2 is an action, where A1 refers the target armature, E10 means the animation ends at frame 10, and 2 appears to be the node ID (see below).
  • How are node IDs selected? From sample M3G code, it’s fairly obvious that M3G files, for better and for worse, do not contain named entities. Unfortunately the blender M3G exporter uses a naming convention that requires the artist to insert IDs. Maybe this can be improved a little – for example, we could have a java source file with named constants remapping blender object names from arbitrary IDs.

The M3G exporter also allows direct export to java code (in other words, it can generate code which, when run, will produce a scenegraph).

Useful resources