Thing I’m trying to solve
I’ve always liked board-game-token like objects. Ones that are kind of 2D but have some depth to them (a Minecraft sword is a good example of this). When I’ve created these in the past I’ve turned the 2D image into voxels and let my voxel builder turn them 3D. This works but has a couple of downsides:
- You get a jagged edge (it follows the pixel edges)
- The number of vertexes is INSANE; 8 per pixel. So its only really suitable for very small images.
So I decided to do it properly:
- Take an image where transparent means “not present”
- Do edge detection on it
- Simpilify the edge
- Triangularize it
Deep tokens Library
I’ve created a library to do just that. It produces things like this:
(real thing on left, triangles representation on right)
Starting from images like this
Here you can see that the traditional jMonkeyEngine logo has been turned into a 3D shape.
Features
Performance
The library creates these images pretty fast. Using the sun and clouds (512 by 362 px) as a test case the time it takes from png image finished loading to geometry created is on average; 45 milliseconds. That’s pretty fast but if you really care about start up time you could create the geometry ahead of time then save it as a j3o. (I have plans to improve this performance, so it will probably get better in a new release)
Edit; got it down to 10ms for version 1.0.2
No transparency
No transparency; holes really are holes. This means no worrying about render order.
Normal calculation
The library automatically calculates appropriate normals, so the tokens look good when lit. Sharp edges are automatically detected (by default a greater than 30 degree change at a vertex is “sharp”) leading to sharp changes in normals at corners whereas curved surfaces get gentle changes in normals.
See in the below test shape the difference between the corners (sharp normal change) and the curved sections
Edge tinting
If you aren’t using lighting (or you want to make the edge more distinct) vertex colours can be used to darken it.
DeepTokenBuilder deepTokenBuilder = new DeepTokenBuilder(1, 0.1f);
deepTokenBuilder.setEdgeTint(new ColorRGBA(0.75f, 0.75f, 0.75f, 1));
Holes, islands, concave shapes
There are very few limitations on the starting images it can be given. They can be concave, convex, have holes, have islands in the holes (and holes in those islands etc).
E.g. this complex image (white is actually transparent and becomes “hole”)
Which outputs as this
As a fairly complex shape this has a decent number of triangles, but the amount of simplification of the edge is a configurable parameter so this could be reduced (with obvious downsides)
Limitations
There are a couple of things that can give bad results in the output
Single pixel wide regions
This is the big one, the library hates single pixel regions (like long thin hairs). Triangularisation will fail if DeepTokens encounters any of these. The jMonkeyEngine logo has one region where this happens (the hairs come out to the very thin, long, point) and I had to give it a hair cut. I might be able to pre-process the image to avoid that but it’s a job for another day.
Dirty edges
Often 2D images fade to transparent over a few pixels (anti aliasing). This won’t prevent a deep token being generated but tend to look a bit rubbish. DeepTokens likes sharp aliased edges; where a fully transparent pixel is next to a fully opaque pixel. After all DeepTokens is going to create a sharp edge in 3D and doesn’t know what to do with these partially transparent pixels
Solid edge
This isn’t a limitation exactly just an FYI; the edge is coloured by the last pixel, so if (cartoon style) you have a solid border round the image that will be the colour of the edge.
Example
Create a DeepTokenBuilder and specify its width and depth (height is implicit from the image shape)
DeepTokenBuilder deepTokenBuilder = new DeepTokenBuilder(1, 0.1f);
Load an image
BufferedImage tokenImage = ImageIO.read(TestApplication.class.getResourceAsStream("/pathToImage.png"));
Request this be turned into a deep token
Geometry deepToken = deepTokenBuilder.bufferedImageToLitGeometry(tokenImage, assetManager);
Or just get the mesh and texture and put it together yourself (if you want to use an unusual material)
Mesh mesh = deepTokenBuilder.bufferedImageToMesh(tokenImage);
Texture texture = deepTokenBuilder.imageToTexture(tokenImage);
Release
This library is on Maven Central, current dependency is
implementation "com.onemillionworlds:deeptokens:1.0.2"
But keep an eye on maven central for new versions [there is some caching, so 24 hour delay on new versions]