Lemur Gems #4 : Simple Mesh Deformation

In this Lemur Gem I will show an example of using the DMesh class to deform a standard Mesh on the fly. Lemur Gem #3 showed how to easily turn regular 3D scene objects into clickable things. I will extend that to turn those boxes into swaying beams.

Some random introductions…

First meet MBox. MBox behaves similarly to a JME box except it can be ‘split’ along any of the major axes to provide simple subdivision. I replace the old Box initialization code with MBox so that there are more triangles to morph.

Next there is DMesh and Deformation. A DMesh is a proper JME Mesh extension that wraps any JME Mesh and modifies it with a specified Deformation function. The deformation function is passed each the position and normal Vector3fs for each vertex and can modify them as desired.

A Deformations utility class provides some default deformation functions. (Just two right now.) Of these, I will be using the Cylindrical deformation in this demo. The Cylindrical deformation conceptually warps space around some origin at some radius. For example, a flat plane/grid could be warped to a specific curve by placing the origin somewhere off the plane and providing an appropriate radius for the curve.

I think it’s probably best if seen. Explanations are hard without a picture.

In the last demo, I created 5 regular JME Box meshes. In this one I create 5 MBox meshes as follows:

    // MBox is like a JME box except that it can be split along
    // the three axes.  In this case, we have no splits in x or z
    // and 5 splits in y.  This means that each long side will be
    // 6 quads vertically.    
    MBox b = new MBox(0.5f, 3, 0.5f, 0, 5, 0);

To that a deformation is applied as follows:

    // Create a deformation function from the standard
    // built-in deformations.  
    // The "cylindrical" deformation warps space such that 
    // the mesh will curve about some 'origin' and radius.
    //    cylindrical( int majorAxis, int minorAxis,
    //                 Vector3f origin, float radius,
    //                 float start, float limit )  
    // The start and limit control the range of the effect along the major
    // axis. 
    final Vector3f curveOrigin = new Vector3f(3, -3, 0) ;
    final float radius = 3;
    final Cylindrical cylDeform = Deformations.cylindrical( 1, 0, curveOrigin, radius, 0, 0 );
            
    // DMesh takes any source mesh and applies a deformation function producing
    // new mesh data.  The function parameters can be updated later to animate them.
    final DMesh mesh = new DMesh(b, cylDeform);            

…and that DMesh is passed to the Geometry instead of the box in gem 3’s version. After that we have a slightly different mouse listener setup:

    MouseEventControl.addListenersToSpatial(geom,
            new DefaultMouseListener() {
 
                private boolean dragging;       
                private float xLast;
                private float limit = 0;
 
                @Override
                public void mouseButtonEvent( MouseButtonEvent event, Spatial target, Spatial capture ) {
                    event.setConsumed();

                    if( event.isPressed() ) {
                        xLast = event.getX();
                        dragging = true;
                    } else {
                        dragging = false;
                        mesh.createCollisionData();
                        geom.updateModelBound();                                
                    }
                }

                @Override
                public void mouseMoved( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    if( !dragging ) {
                        return;
                    }
                    event.setConsumed();
                            
                    float xDelta = event.getX() - xLast;
                    xLast = event.getX();
                            
                    // The limit sets the 'range' of the effect from origin.
                    // So a limit of 6 would be the maximum because our boxes are
                    // only 6 units long and origin is at the base.
                    // However, limit starts to have a small effect at 5 or so.
                    // At that point we switch to moving the curve radius in.
                    limit += xDelta / 100;
                            
                    // When the limit it 7 or more then the curve radius becomes
                    // 1.0.  Anything much smaller than that and the object starts
                    // wrapping back upon itself.  So we'll hard-clamp the max
                    // of limit to 7.
                    if( limit > 7 ) {
                        limit = 7;
                    }
                                                        
                    if( limit > 5 ) {
                        // Shift the curve origin closer to the box base...
                        // ie: a tighter curve
                        // A radius smaller than 1 starts to wrap back upon
                        // itself.                             
                        curveOrigin.x = 3 - (limit - 5);
                        cylDeform.setRadius(curveOrigin.x);
                    } else {
                        // Reset the curve origin and radius back to normal.
                        curveOrigin.x = 3;
                        cylDeform.setRadius(curveOrigin.x);
                    }
                    cylDeform.setLimit(Math.min(6, limit)); 
                    mesh.updateMesh();                            
                }
                   
                @Override
                public void mouseEntered( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    Material m = ((Geometry)target).getMaterial();
                    m.setColor("Color", ColorRGBA.Yellow);
                }

                @Override
                public void mouseExited( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    Material m = ((Geometry)target).getMaterial();
                    m.setColor("Color", ColorRGBA.Blue);
                }                        
            });

Here we’ve kept the existing enter/exit behavior of setting the mesh yellow/blue respectively. To it we’ve added button down and up detection to setup dragging. The x delta from one dragged mouse move to the next is tracked and used to adjust the Cylindrical deformations parameters. The Cylindrical deformation is a kind of complicated morph but it makes cool results so I used it. However, it consequently has a lot of parameters.

This is the exact same deformation I use to curve the animated pages of my 3D Book UIs.

Here is the full demo class: http://code.google.com/p/jmonkeyplatform-contributions/source/browse/trunk/Lemur/examples/LemurGems/src/gems/DeformationDemo.java

And a video…

12 Likes

Awesome! :slight_smile:

1 Like

Very nice!

1 Like