@Refresh flags are not updated upward, refreshFlags == 0 does not guarantee that the subgraph didn’t change.
Im not really sure about this. From my observation:
SetTransformRefresh always includes SetBoundRefresh which updates flags upward to parent.
Adding child to a node calls setTransformRefresh(), which calls setBoundRefresh which updates flags upward to parent.
Detaching also calls setTransformRefresh() and so on.
I do agree with your statement since setLightListRefresh() does not update upward to parent. I assumed I could ignore this because I do not use light.
Thus I assume DynNode can be used for scenes with no lighting, which is common on Android. I said ‘assume’ because so far I have not encountered any problems with it yet. Maybe it should have been called NoLightNode or something.
Regarding a general case solution for updating Controls. What about one of these:
- I guess one can create two rootNodes, one for objects with controls and call updateLogicalState on it and another rootNode with objects without controls not calling updateLogical state. That really seems to be an easy way to do things.
- What if there was one list of Contols. When attaching an object to rootNode or its subNodes, all objects controls were added to this list, when detaching removed. Also when adding a Control to an object and it was attached to the rootNode, that Control would be added. This would definitely eliminate the updateLogicalState overhead. Eg. Following is the sample code how this could be done:
For convenience I extended Node and Spatial classes, Thus NewNode would become Node, and NewSpatial would become Spatial. RootNode would be a special kind of node used as the rootNode.
public abstract class NewSpatial extends Spatial {
public void addControl(Control control) {
controls.add(control);
control.setSpatial(this);
/*Only required to call this method now, since if list had
controls it would already be in added in the list*/
if(controls.size() == 1) addControlsToList(controls);
}
public void removeControl(Class<? extends Control> controlType) {
for (int i = 0; i < controls.size(); i++) {
if (controlType.isAssignableFrom(controls.get(i).getClass())) {
Control control = controls.remove(i);
control.setSpatial(null);
}
}
if(controls.isEmpty()) removeControlsFromList(controls);
}
public boolean removeControl(Control control) {
boolean result = controls.remove(control);
if (result) {
control.setSpatial(null);
}
if(controls.isEmpty()) removeControlsFromList(controls);
return result;
}
protected void addControlsToList(SafeArrayList<Control> list) {
if(parent != null)
((NewNode)parent).addControlsToList(list);
}
protected void removeControlsFromList(SafeArrayList<Control> list) {
if(parent != null)
((NewNode)parent).removeControlsFromList(list);
}
}
public class NewNode extends Node {
private static final Logger logger = Logger.getLogger(NewNode.class.getName());
public int attachChild(Spatial child) {
return attachChildAt(child, children.size());
}
public int attachChildAt(Spatial child, int index) {
if (child == null)
throw new NullPointerException();
if (child.getParent() != this && child != this) {
if (child.getParent() != null) {
child.getParent().detachChild(child);
}
child.setParent(this);
children.add(index, child);
if(parent != null || this instanceof RootNode) {
if(child instanceof Node) {
Node n = (Node)child;
n.depthFirstTraversal(new SceneGraphVisitor() {
public void visit(Spatial s) {
if(!s.controls.isEmpty())
addControlsToList(s.controls);
}
});
}
else {
if(!child.controls.isEmpty())
addControlsToList(child.controls);
}
}
child.setTransformRefresh();
child.setLightListRefresh();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
new Object[]{child.getName(), getName()});
}
}
return children.size();
}
public Spatial detachChildAt(int index) {
Spatial child = children.remove(index);
if ( child != null ) {
child.setParent( null );
logger.log(Level.FINE, "{0}: Child removed.", this.toString());
if(parent != null || this instanceof RootNode) {
if(child instanceof Node) {
Node n = (Node)child;
n.depthFirstTraversal(new SceneGraphVisitor() {
public void visit(Spatial s) {
if(!s.controls.isEmpty())
removeControlsFromList(s.controls);
}
});
}
else {
if(!child.controls.isEmpty())
removeControlsFromList(child.controls);
}
}
// since a child with a bound was detached;
// our own bound will probably change.
setBoundRefresh();
// our world transform no longer influences the child.
// XXX: Not neccessary? Since child will have transform updated
// when attached anyway.
child.setTransformRefresh();
// lights are also inherited from parent
child.setLightListRefresh();
}
return child;
}
protected void addControlsToList(SafeArrayList<Control> list) {
if(parent != null)
((NewNode)parent).addControlsToList(list);
}
protected void removeControlsFromList(SafeArrayList<Control> list) {
if(parent != null)
((NewNode)parent).removeControlsFromList(list);
}
}
public class RootNode extends NewNode {
SafeArrayList<SafeArrayList<Control>> controlsList;
protected void addControlsToList(SafeArrayList<Control> list) {
controlsList.add(list);
}
protected void removeControlsFromList(SafeArrayList<Control> list) {
controlsList.remove(list);
}
public void updateLogicalState(float tpf) {
SafeArrayList<Control> cList[];
Control[] c;
cList = controlsList.getArray();
for(int i = 0; i < cList.length; i++) {
c = cList[i].getArray();
for(int j = 0; j < c.length; j++)
c[i].update(tpf);
}
}
}
I hope I did not miss any important thing. If not, this should alleviate the updateLogicalState overhead for spatials with no controls. The price to pay for this change is travering subnodes when attaching detaching nodes.
Tell me what you think!