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);
}
}