Copying geometry mid-animation

EDIT: Solved! See the solution at the end of this post.

So, in an attempt to implement a way to highlight a model, I’ve set up a system where I clone all the geometry in the node, enlarge it slightly, cull camera-facing surfaces, and then apply a different material. The result is that the highlighted model has sort of an aura effect around it. It works passably well, until a model starts animating. When that happens the cloned geometry retains the default shape of the model. The model was imported via OgreXML from Blender, if that makes a difference. So, I’m looking for an answer to one of the following questions:

  • Is there a better way to highlight a model that animates?

OR

  • How do I clone the model in such a way that it also is in the right animation pose?

Here’s the code where I create the highlight:

	private Node createHighlightNode(Node toHighlight, Material howToHighlight) {
		Node out = new Node();
		for (Spatial s : toHighlight.getChildren()) {
			Spatial clone = s.clone(false);
			out.attachChild(clone);
		}
		out.setLocalRotation(toHighlight.getLocalRotation());
		howToHighlight.clearParam("BoneMatrices"); // This is to prevent multiple BoneMatrices getting attached to this one Material.
		out.setMaterial(howToHighlight);
		out.setLocalScale(toHighlight.getLocalScale().mult(1.05f));
		out.setLocalTranslation(toHighlight.getLocalTranslation().add(0f, 0f, 0.05f));
		return out;
	}

Here’s an example of how it should look (roughly):

Here’s an example of how it looks when an animation is playing:

EDIT:
SOLUTION TO How do I clone the model in such a way that it also is in the right animation pose?

package net.bithaven.jme;

import com.jme3.animation.SkeletonControl;
import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

/**
 * Highlights a {@link Spatial}. Has been coded to work for both {@link Node}s and {@link Geometry}s, but has not 
 * been tested on a {@code Geometry}.
 * 
 * @author Alweth
 * 
 * @see #highlight(Spatial, Material)
 * @see #stopHighlight()
 *
 */
public class Highlighter {
	Node subject = null;
	
	/**
	 * Creates {@link Geometry}s attached to {@code toHighlight} so that it is highlighted by {@link Material} 
	 * {@code howToHighlight}. If {@code toHighlight} is not a {@link Node}, a new {@code Node} will be placed 
	 * between it and its {@link Spatial#getParent() parent}.
	 * 
	 * @param toHighlight The {@link Spatial} to highlight.
	 * @param howToHighlight The {@link Material} (eg. color) to highlight it with.
	 */
	public void highlight(Spatial toHighlight, Material howToHighlight) {
		if (toHighlight == subject)
			return;
		
		if (toHighlight instanceof Geometry) {
			Node n = new Node();
			n.attachChild(toHighlight);
			toHighlight.getParent().attachChild(n);
			highlight(n, howToHighlight);
			return;
		} else if (toHighlight instanceof Node) {
			if (subject != null) {
				for (Geometry g : subject.descendantMatches(Geometry.class, "highlight")) {
					g.removeFromParent();
				}
			}
			subject = (Node)toHighlight;
			hardwareSkinningOff(subject);
			for (Spatial s : subject.getChildren()) {
				highlightInternal(s, howToHighlight, subject);
			}
		}
	}
	
	/**
	 * Stops highlighting the highlighted {@link Spatial}, if any.
	 */
	public void stopHighlight() {
		if (subject != null) {
			for (Geometry g : subject.descendantMatches(Geometry.class, "highlight")) {
				g.removeFromParent();
			}
			subject = null;
		}
	}
	
	private void highlightInternal (Spatial s, Material howToHighlight, Node parent) {
		hardwareSkinningOff(s);
		if (s instanceof Geometry) {
			Geometry n = new Geometry("highlight", ((Geometry)s).getMesh());
			n.setLocalRotation(s.getLocalRotation());
			n.setMaterial(howToHighlight);
			n.setLocalScale(s.getLocalScale().mult(1.05f));
			n.setLocalTranslation(s.getLocalTranslation().add(0f, 0f, -0.02f));
			parent.attachChild(n);
			return;
		} else if (s instanceof Node) {
			for (Spatial s2 : ((Node)s).getChildren()) {
				highlightInternal(s2, howToHighlight, (Node)s);
			}
		}
	}
	
	private static void hardwareSkinningOff (Spatial s) {
		SkeletonControl sc = s.getControl(SkeletonControl.class);
		if (sc != null) sc.setHardwareSkinningPreferred(false);
	}
}

You need to grab the skeletonControl and call

skeletonControl.setHardwareSkinningPreferred(false);

This way the highlight mesh will also be transformed when the animation is played.

When you clone a model the mesh’s buffers are not cloned. So basically both of the highlighted model and the real model will share the same buffers.

Disabling hardware skinning then will ensure the animation transforms are applied to the buffer on the CPU (instead of transformed at render time on the GPU).

Edit: another way could be to attach the highlighted model to the same node as the actual model (at least to the node containing the skeletonControl)… it might also work and allow you to keep hardware skining

1 Like

Hi, nehon. Thanks for your great response. Strangely, the behavior doesn’t change when I turn hardware skinning off. I’m doing it on the Model’s SkeletonControl right after I load the Model. Any ideas?

mhhh, did you try the other way?
I’m pretty sure the first way works I use it myself…something else must be wrong

1 Like

You may try mesh.prepareForAnim(true);

http://javadoc.jmonkeyengine.org/com/jme3/scene/Mesh.html#prepareForAnim-boolean-

This is called internally on the first frame if the mesh is animated, or when switching from hardware to software.

1 Like

I’ll try the other way. Would you mind posting the code that you use so I can compare it with mine?

Well it’s spread along several classes…
One thing that I do though is to disable HW skinning before cloning, it may be important…

1 Like

Disabling hardware skinning before cloning doesn’t make a difference.

Regarding your second suggestion, here’s how I’ve tried to implement it. It also copies the model and changes the material, but still doesn’t animate at all.

for (Spatial s : toHighlight.getChildren()) {
	if (s instanceof Node) {
		if (DEVMODE)
			logger.log(INFO, "Is a NODE!!!!");
		for (Spatial s2 : ((Node)s).getChildren()) {
			if (s2.getName().equals("highlight")) s2.removeFromParent();
		}
		s.getControl(SkeletonControl.class).setHardwareSkinningPreferred(false);
		Node n = (Node)s.clone(false);
		n.setName("highlight");
		SkeletonControl sc = s.getControl(SkeletonControl.class);
		if (sc != null) {
			sc.setHardwareSkinningPreferred(false); //TODO: Might slow things down.
		}
		n.setLocalRotation(toHighlight.getLocalRotation());
		n.setMaterial(howToHighlight);
		n.setLocalScale(s.getLocalScale().mult(1.05f));
		n.setLocalTranslation(s.getLocalTranslation().add(0f, 0f, -0.02f));
		((Node)s).attachChild(n);
	}
}

EDIT: I should clarify, that I’m doing all of this every update.

I should clarify, that I’m doing all of this every update.

oh?? why?
Once is enough

Because the highlighted object changes dynamically.

EDIT: When I originally wrote this, I thought I’d be cloning the geometry in the shape that it was for animation.

Yeah but I guess you pick the geom in some way, am I right?
So that’s when this event occur that you have to do it. Cloning the Geom on every frame is a bad idea and is unnecessary.

1 Like

Did you mean for that to be n.getControl()… else it’s redundant with the one three lines up.

1 Like

Yes, I did. Thanks! Unfortunately, that doesn’t make a difference. :disappointed:

OK instead of cloning the geometry try to create a new one but with the target geom mesh :

Geometry higlight = new Geometry("highlight", model.getMesh());

Then assign a material to it and disable HW skinning on the skeleton control

1 Like

Thanks, nehon! That seems to have done it, in combination with your second suggestion! I’ll post my code here again after I’ve cleaned it up a little.

add your hilight mesh in a geometry shader? It doesn’t lag like the above can, it worked for me.

1 Like

I’ve updated the original post with my current solution.

thetoucher, I am a jMonkeyEngine noob and have very little experience working with rasterizers. Could you explain how you did it? Preferably with example code?