]> gerrit.simantics Code Review - simantics/3d.git/blobdiff - org.simantics.g3d/src/org/simantics/proconf/g3d/base/ScenegraphAdapterImpl.java
git-svn-id: https://www.simantics.org/svn/simantics/3d/trunk@22280 ac1ea38d-2e2b...
[simantics/3d.git] / org.simantics.g3d / src / org / simantics / proconf / g3d / base / ScenegraphAdapterImpl.java
diff --git a/org.simantics.g3d/src/org/simantics/proconf/g3d/base/ScenegraphAdapterImpl.java b/org.simantics.g3d/src/org/simantics/proconf/g3d/base/ScenegraphAdapterImpl.java
new file mode 100644 (file)
index 0000000..f9687f5
--- /dev/null
@@ -0,0 +1,820 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007- VTT Technical Research Centre of Finland.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.proconf.g3d.base;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Queue;\r
+import java.util.Set;\r
+import java.util.concurrent.ConcurrentLinkedQueue;\r
+\r
+import org.simantics.db.AbstractQuery;\r
+import org.simantics.db.Graph;\r
+import org.simantics.db.GraphRequestAdapter;\r
+import org.simantics.db.GraphRequestStatus;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.queries.IGraphQuery;\r
+import org.simantics.db.queries.IQuery;\r
+import org.simantics.db.queries.IQueryListener;\r
+import org.simantics.db.utils.transaction.MergingTransactionRunner;\r
+import org.simantics.layer0.utils.EntityFactory;\r
+import org.simantics.layer0.utils.IEntity;\r
+import org.simantics.layer0.utils.Property;\r
+import org.simantics.proconf.g3d.Resources;\r
+import org.simantics.proconf.g3d.scenegraph.IGeometryNode;\r
+import org.simantics.proconf.g3d.scenegraph.IGraphicsNode;\r
+import org.simantics.proconf.g3d.scenegraph.RootGraphicsNode;\r
+import org.simantics.proconf.g3d.stubs.G3DNode;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+import org.simantics.utils.datastructures.BijectionMap;\r
+\r
+import com.jme.scene.Node;\r
+\r
+\r
+/**\r
+ * Scene-graph adapter :\r
+ * 1. Adapts graph change events into changes in actual scene-graph nodes.\r
+ * 2. Handles instantiating and disposing of Scene-graph nodes.\r
+ * 3.  \r
+ * \r
+ * @author Marko Luukkainen\r
+ *\r
+ */\r
+public abstract class ScenegraphAdapterImpl implements ScenegraphAdapter {\r
+       \r
+       protected static boolean DEBUG = false;\r
+       \r
+       private RootGraphicsNode root;\r
+       private HashMap<Resource,ScenegraphQuery> scenegraphQueries = new HashMap<Resource, ScenegraphQuery>();\r
+    private HashMap<Resource,NodePropertyQuery> propertyQueries = new HashMap<Resource, NodePropertyQuery>();\r
+    private HashMap<Resource,NodeTransformationQuery> transformationQueries = new HashMap<Resource, NodeTransformationQuery>();\r
+       private HashMap<Resource, IGraphicsNode> abstractGraphicsNodes = new HashMap<Resource, IGraphicsNode>();\r
+    protected Queue<IGeometryNode> geometryUpdates = new ConcurrentLinkedQueue<IGeometryNode>();\r
+       \r
+    private BijectionMap<Resource, String> nameMap = new BijectionMap<Resource, String>();\r
+    \r
+    protected JmeRenderingComponent component;\r
+    \r
+    protected boolean viewChanged = false;\r
+    \r
+    protected Session session;\r
+    \r
+    private MergingTransactionRunner transactionRunner;\r
+    \r
+    public ScenegraphAdapterImpl(Session session, JmeRenderingComponent component) {\r
+       this.component = component;\r
+       this.session = session;\r
+       transactionRunner = new MergingTransactionRunner(session,true);\r
+    }\r
+    \r
+    \r
+    @Override\r
+    public JmeRenderingComponent getRenderingComponent() {\r
+       return component;\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#setRootNode(fi.vtt.simantics.g3d.stubs.G3DNode)\r
+        */\r
+    public void setRootNode(G3DNode rootNode) {\r
+       addSubnodeListener(rootNode);\r
+        root = new RootGraphicsNode(component,rootNode.getResource());\r
+        abstractGraphicsNodes.put(rootNode.getResource(),root);\r
+        G3DTools.reloadCache(rootNode.getGraph(),root.getResource());\r
+        addRootPropertyListener(rootNode.getGraph());\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#needsUpdateGeometry()\r
+        */\r
+    public boolean needsUpdateGeometry() {\r
+       return geometryUpdates.size() > 0;\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#updateGeometry(fi.vtt.simantics.g3d.scenegraph.IGeometryNode)\r
+        */\r
+    public void updateGeometry(IGeometryNode node) {\r
+       geometryUpdates.add(node);\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#updateGeometry(fi.vtt.simantics.db.connection.Resource)\r
+        */\r
+    public void updateGeometry(Resource nodeResource) {\r
+       geometryUpdates.add((IGeometryNode)abstractGraphicsNodes.get(nodeResource));\r
+    }\r
+    \r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#updateGeometry(fi.vtt.simantics.db.connection.Graph)\r
+        */\r
+    public synchronized void updateGeometry(Graph graph) {\r
+       if (geometryUpdates.size() > 0) {\r
+                       for (IGeometryNode n : geometryUpdates) {\r
+                               try {\r
+                                       n.updateGeometry(graph);\r
+                                       if (DEBUG) System.out.println("ScenegraphAdapterImpl: geometryUpdated " + n.getResource());\r
+                               } catch (Exception e) {\r
+                                       ErrorLogger.defaultLogError("Failed to update geometry of node" + n.getResource(), e);\r
+                               }\r
+                       }\r
+                       geometryUpdates.clear();\r
+                       viewChanged = true;\r
+                       //if (DEBUG) System.out.println("ScenegraphAdapterImpl: geometryUpdated");\r
+               }\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getNodes()\r
+        */\r
+    public Collection<IGraphicsNode> getNodes() {\r
+        return abstractGraphicsNodes.values();\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getNode(fi.vtt.simantics.db.connection.Resource)\r
+        */\r
+    public IGraphicsNode getNode(Resource resource) {\r
+        return abstractGraphicsNodes.get(resource);\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getRootNode()\r
+        */\r
+    public IGraphicsNode getRootNode() {\r
+       return getNode(getRootResource());\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getNode(fi.vtt.simantics.layer0.utils.IEntity)\r
+        */\r
+    public IGraphicsNode getNode(IEntity IEntity) {\r
+       return abstractGraphicsNodes.get(IEntity.getResource());\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getNodeResource(java.lang.String)\r
+        */\r
+    public Resource getNodeResource(String uid) {\r
+       return nameMap.getLeft(uid);\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getNodeUID(fi.vtt.simantics.db.connection.Resource)\r
+        */\r
+    public String getNodeUID(Resource nodeResource) {\r
+       String name = nameMap.getRight(nodeResource);\r
+       if (name == null) {\r
+               //name = UUID.randomUUID().toString();\r
+               name = Long.toString(nodeResource.getResourceId());\r
+               nameMap.map(nodeResource, name);\r
+       }\r
+       return name;\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#hasNode(fi.vtt.simantics.db.connection.Resource)\r
+        */\r
+    public boolean hasNode(Resource resource) {\r
+        return abstractGraphicsNodes.containsKey(resource);\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getRootResource()\r
+        */\r
+    public Resource getRootResource() {\r
+       if (root == null)\r
+               return null;\r
+        return root.getResource();\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#getRoot()\r
+        */\r
+    public Node getRoot() {\r
+       return root.getGroup();\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#dispose()\r
+        */\r
+    public void dispose() {\r
+       Set<Resource> shapes = new HashSet<Resource>(abstractGraphicsNodes.keySet());\r
+        for (Resource r : shapes) {\r
+            removeNode(r);\r
+        }\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#isChanged()\r
+        */\r
+    public boolean isChanged() {\r
+       return viewChanged;\r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+        * @see fi.vtt.simantics.g3d.scenegraph.IScenegraphAdapter#setChanged(boolean)\r
+        */\r
+    public void setChanged(boolean changed) {\r
+       viewChanged = changed;\r
+    }\r
+    \r
+    \r
+    /**\r
+     * Instantiates Listener that listens hierarchy changes in the node (subnode\r
+     * added and/or removed)\r
+     * \r
+     * @param node\r
+     * @return\r
+     */\r
+    protected abstract ScenegraphQuery newSubnodeListener(G3DNode node);\r
+    \r
+    protected void addSubnodeListener(G3DNode node) {\r
+       if (DEBUG) System.out.println("ScenegraphAdapter.addSubnodeListener( " + node.getResource() + " )");\r
+       ScenegraphQuery q = newSubnodeListener(node);\r
+       node.getGraph().performQuery(q);\r
+        scenegraphQueries.put(node.getResource(), q);\r
+    }\r
+    \r
+    /**\r
+     * Returns propertyQuery for a single scene-graph node.\r
+     * @param node\r
+     * @return\r
+     */\r
+    protected abstract NodePropertyQuery newPropertyListener(G3DNode node);\r
+    \r
+    /**\r
+     * Returns transformationQuery for a single scene-graph node\r
+     * @param root\r
+     * @return\r
+     */\r
+    protected abstract NodeTransformationQuery newTransformationListener(G3DNode node);\r
+    \r
+    /**\r
+     * Returns propertyQuery for the root node.\r
+     * May return null if root node has no interesting properties.\r
+     * \r
+     * Potentially root node could contain lighting settings, and so on...\r
+     * \r
+     * @param root\r
+     * @return\r
+     */\r
+    protected abstract NodePropertyQuery newRootPropertyListener(G3DNode root);\r
+    \r
+    protected void addPropertyListener(G3DNode node) {\r
+       if (DEBUG) System.out.println("ScenegraphAdapter.addPropertyListener( " + node.getResource() + " )");\r
+       NodePropertyQuery q = newPropertyListener(node);\r
+       node.getGraph().performQuery(q);\r
+       propertyQueries.put(node.getResource(),q);\r
+    }\r
+    \r
+    \r
+    protected void addTransformationListener(G3DNode node) {\r
+       if (DEBUG) System.out.println("ScenegraphAdapter.addPropertyListener( " + node.getResource() + " )");\r
+       NodeTransformationQuery q = newTransformationListener(node);\r
+       node.getGraph().performQuery(q);\r
+       transformationQueries.put(node.getResource(),q);\r
+    }\r
+    \r
+    protected void addRootPropertyListener(Graph g) {\r
+       G3DNode node = root.getG3DNode(g);\r
+       if (DEBUG) System.out.println("ScenegraphAdapter.addRootPropertyListener( " + node.getResource() + " )");       \r
+       NodePropertyQuery q = newRootPropertyListener(node);\r
+       if (q == null)\r
+               return;\r
+       node.getGraph().performQuery(q);\r
+       propertyQueries.put(node.getResource(),q);\r
+    }\r
+    \r
+    /**\r
+     * Instantiates a new scene-graph node\r
+     * \r
+     * @param parent the parent of the new node.\r
+     * @param node the new node.\r
+     * @return\r
+     */\r
+    protected abstract IGraphicsNode instantiateNode(IGraphicsNode parent, G3DNode node);\r
+    \r
+\r
+    /**\r
+     * Adds node into scene-graph\r
+     * @param parent the parent of the node\r
+     * @param r resource of the node\r
+     * @return created scene-graph node\r
+     */\r
+    protected IGraphicsNode addNode(IEntity parent, IEntity r) {\r
+        if (!r.isInstanceOf(Resources.g3dResource.G3DNode)) {\r
+            ErrorLogger.defaultLogError("Trying to add node into scenegraph that is not instance of G3DNode " + r,new Exception("ASSERT!"));\r
+            return null;\r
+        }\r
+        if (parent.equals(r)) {\r
+               if (DEBUG) System.out.println("ThreeDimensionalEditorBase.addNodeP(" + r.getResource().getResourceId() + ") adding node to itself?!");\r
+            ErrorLogger.defaultLogError("Adding scnegraphnode " + r.getResource().getResourceId() + " to itself!", new Exception("ASSERT!"));\r
+            return abstractGraphicsNodes.get(r);\r
+        }\r
+        \r
+        if (abstractGraphicsNodes.containsKey(r)) {\r
+               \r
+            IGraphicsNode inView = abstractGraphicsNodes.get(r);\r
+            if (inView.getParent() == null) {\r
+                //if graphicsNode has no parent it must be the root node\r
+                ErrorLogger.defaultLogError("Trying to add rootnode into scenegraph " + r, null);\r
+                return null;\r
+            }\r
+            if (parent.equals(inView.getParent().getResource())) {\r
+               if (DEBUG) System.out.println("ThreeDimensionalEditorBase.addNodeP(" + r.getResource().getResourceId() + ") already in view");\r
+                return inView;\r
+            } else {\r
+               if (DEBUG) System.out.println("ThreeDimensionalEditorBase.addNodeP(" + r.getResource().getResourceId() + ") already in view, but has different parent, current parent is ("+inView.getParent().getResource().getResourceId()+") and node is added to  ("+parent+") -> removing from old parent and inserting to new");\r
+                removeNode(inView.getParent().getResource(),r.getResource());\r
+            }\r
+        }\r
+        \r
+        G3DNode node = new G3DNode(r);\r
+\r
+        IGraphicsNode mo;\r
+        IGraphicsNode parentNode = abstractGraphicsNodes.get(parent);\r
+        if (parentNode == null) {\r
+               if (DEBUG) System.out.println("No graphicsnode for (" + parent.getResource().getResourceId() + ")");\r
+            return null;\r
+        } else {\r
+            mo = instantiateNode(parentNode, node);\r
+            if (mo == null) {\r
+                ErrorLogger.defaultLogError("Could not instantiate scenegraph node for " + r.getResource().getResourceId(), null);\r
+                return null;\r
+            }\r
+            if (DEBUG) System.out.println("ThreeDimensionalEditorBase.addNodeP(" + r.getResource().getResourceId() + ") added to parent (" + parent.getResource().getResourceId() + ") " + mo.getClass());\r
+        }\r
+        addSubnodeListener(node);\r
+        addPropertyListener(node);\r
+        addTransformationListener(node);\r
+\r
+        abstractGraphicsNodes.put(r.getResource(), mo);\r
+\r
+        \r
+        \r
+        // FIXME : this is a hack to fix transformations of instantiated nodes\r
+//             if (graph.getCurrentTransaction() != null) {\r
+//                     try {\r
+//                             G3DTools.propagateWorldTransformChange(parentNode\r
+//                                             .getG3DNode());\r
+//                             graph.commitChanges(CommitMessage.CHANGE_MESSAGE);\r
+//                             // G3DNodeTools.transformationUpdate(graph, r.getId());\r
+//                     } catch (Exception e) {\r
+//                             ErrorLogger.defaultLogError(e);\r
+//                     }\r
+//             }\r
+\r
+        \r
+        return mo;\r
+    }\r
+\r
+    /**\r
+        * This is used only when view is disposed!\r
+        * \r
+        * @param r\r
+        */\r
+    private void removeNode(Resource r) {\r
+       NodeTransformationQuery tq = transformationQueries.get(r);\r
+        //StructuralChangeMonitor monitor = monitors.getLeft(r);\r
+        if (tq == null) {\r
+               if (abstractGraphicsNodes.containsKey(r)) {\r
+                // root node has no monitor (no transformation to monitor)\r
+                //System.out.println("ThreeDimensionalEditorBase.removeNode(" + r + ") node has no monitor, but has node in scenegraph");  \r
+                abstractGraphicsNodes.remove(r);\r
+                if(scenegraphQueries.get(r) != null) {\r
+                       scenegraphQueries.get(r).dispose();\r
+                       scenegraphQueries.remove(r);\r
+                }\r
+            } else {\r
+                //System.out.println("ThreeDimensionalEditorBase.removeNode(" + r + ") not in view");\r
+            }\r
+               return;\r
+        }\r
+        // remove listeners\r
+        propertyQueries.remove(r).dispose();     \r
+        transformationQueries.remove(r).dispose();\r
+        scenegraphQueries.get(r);\r
+        scenegraphQueries.remove(r).dispose();\r
+        // remove children\r
+        IGraphicsNode node = abstractGraphicsNodes.get(r);\r
+        ArrayList<IGraphicsNode> children = new ArrayList<IGraphicsNode>(node.getChildren());\r
+        for (IGraphicsNode n : children) {\r
+            removeNode(n.getResource());\r
+        }\r
+        // remove the node\r
+        if (DEBUG) System.out.println("ThreeDimensionalEditorBase.removeNode(" + r + ") removed");\r
+        \r
+        node.dispose();\r
+        abstractGraphicsNodes.remove(r);\r
+\r
+    }\r
+    \r
+    /**\r
+     * Removes a scene-graph node.\r
+     * @param parent the parent of the node\r
+     * @param r the node.\r
+     */\r
+    protected void removeNode(Resource parent,Resource r) {\r
+       NodePropertyQuery q = propertyQueries.get(r);\r
+        if (q == null) {\r
+            assert(!abstractGraphicsNodes.containsKey(r));\r
+            if (DEBUG) System.out.println("ThreeDimensionalEditorBase.removeNodeP(" + r + ") not in view");\r
+            return;\r
+        }\r
+        \r
+        IGraphicsNode node = abstractGraphicsNodes.get(r);\r
+        Resource rParent = node.getParent().getResource();\r
+        if (!rParent.equals(parent)) {\r
+            // this event may happen, depending of the order of events in transaction\r
+               if (DEBUG) System.out.println("ThreeDimensionalEditorBase.removeNodeP(" + r + ") trying to remove from wrong parent current ("+rParent+") remove parentnode null("+parent+")");    \r
+            return;\r
+        }\r
+        // removing listeners\r
+        propertyQueries.remove(r).dispose();\r
+        transformationQueries.remove(r).dispose();\r
+        scenegraphQueries.remove(r).dispose();\r
+        // remove node's children\r
+        ArrayList<IGraphicsNode> children = new ArrayList<IGraphicsNode>(node.getChildren());\r
+        for (IGraphicsNode n : children) {\r
+            removeNode(r,n.getResource());\r
+        }       \r
+        if (DEBUG) System.out.println("ThreeDimensionalEditorBase.removeNodeP(" + r + ") from ("+parent+")");    \r
+        // remove the node\r
+        \r
+        abstractGraphicsNodes.remove(r);\r
+        if (geometryUpdates.contains(node)) {\r
+               geometryUpdates.remove(node);\r
+        }\r
+        node.dispose();\r
+    }\r
+\r
+    \r
+    /**\r
+     * Query that tracks changes in resources.\r
+     * \r
+     * @author Marko Luukkainen\r
+     *\r
+     */\r
+    public abstract class NodeQuery extends AbstractQuery<Object>{\r
+        protected Resource nodeResource;\r
+        private boolean disposed = false;\r
+        \r
+        private IQueryListener listener = null;\r
+       \r
+       public NodeQuery(Resource r) {\r
+               this.nodeResource = r;\r
+       }\r
+       \r
+       protected abstract Object compute2(Graph graph);\r
+       \r
+       @Override\r
+        public Object performQuery(Graph graph) {\r
+               if (disposed) return null;\r
+               return compute2(graph);\r
+        }\r
+       \r
+       /**\r
+        *  \r
+        * @param oldResult result of the query before the change.\r
+        * @param newResult result of the query after the change.\r
+        */\r
+       public abstract boolean updated(Graph graph, Object oldResult, Object newResult);\r
+       \r
+       @Override\r
+       public int getType() {\r
+               return IQuery.SCHEDULED_UPDATE;\r
+       }\r
+       \r
+       @Override\r
+       public void resultChangedRaw(final Object oldResult, final Object newResult) {\r
+               if (disposed)\r
+                       throw new RuntimeException("Updating disposed query"); //return;\r
+               transactionRunner.run(new GraphRequestAdapter() {\r
+                       @Override\r
+                       public GraphRequestStatus perform(Graph g) throws Exception {\r
+                               if (!disposed) {\r
+                                       if (oldResult == IQueryListener.NO_VALUE)\r
+                                               updated(g, null, newResult);\r
+                                       else\r
+                                               updated(g, oldResult, newResult);\r
+                               } \r
+                               return GraphRequestStatus.transactionComplete();\r
+                       }\r
+               });\r
+\r
+       }\r
+       \r
+       \r
+       @Override\r
+       public boolean equalsQuery(IGraphQuery<Object> other) {\r
+               return nodeResource.equals(((NodeQuery)other).nodeResource);\r
+       }\r
+       \r
+        @Override\r
+        final public int hash() {\r
+            return nodeResource.hashCode();\r
+        }\r
+       \r
+       \r
+       /**\r
+        * Disposes the query\r
+        */\r
+       public void dispose() {\r
+               disposed = true;\r
+               if (DEBUG) System.out.println("NodeQuery " +  nodeResource +  " disposed()" + " " + this.getClass());\r
+       }\r
+       \r
+        @Override\r
+        public boolean isDisposed() {\r
+            return disposed;\r
+        }\r
+        \r
+        // without separate listener, this query would work only once\r
+        @Override\r
+        public IQueryListener getListener() {\r
+               if (listener == null) {\r
+                       listener = new IQueryListener() {\r
+                               @Override\r
+                               public boolean isDisposed() {\r
+                                       return NodeQuery.this.disposed;\r
+                               }\r
+                               \r
+                               @Override\r
+                               public void resultChangedRaw(Object oldResult, Object newResult) {\r
+                                       NodeQuery.this.resultChangedRaw(oldResult, newResult);\r
+                               }\r
+                       };\r
+               }\r
+               return listener;\r
+        }\r
+    }\r
+    \r
+    /**\r
+     * \r
+     * Query that tracks changes in scene-graph structure (parent/child relationships).\r
+     * \r
+     * @author Marko Luukkainen\r
+     *\r
+     */\r
+    public abstract class ScenegraphQuery extends NodeQuery {\r
+       \r
+       List<Resource> added = new ArrayList<Resource>();\r
+       List<Resource> removed = new ArrayList<Resource>();\r
+       \r
+       private boolean initialized;\r
+       \r
+       public ScenegraphQuery(Resource nodeResource) {\r
+                       super(nodeResource);\r
+                       initialized = false;\r
+                       if(DEBUG)System.out.println("ScenegraphQuery created for " + nodeResource);\r
+               }\r
+       \r
+       @Override\r
+       public List<Resource> compute2(Graph g) {\r
+               IEntity node = EntityFactory.create(g,nodeResource);\r
+               Collection<IEntity> children = node.getRelatedObjects(Resources.g3dResource.HasChild);\r
+               List<Resource> list = new ArrayList<Resource>();\r
+               for (IEntity n: children)\r
+                       list.add(n.getResource());\r
+               if (DEBUG) System.out.println("ScenegraphQuery " + nodeResource +   " has " + list.size() + " children");\r
+               return list;\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+               @Override\r
+       public boolean updated(Graph graph, Object oldResult, Object newResult) {\r
+               List<Resource> oldChildren;\r
+               if (oldResult != null)\r
+                       oldChildren = (List<Resource>)oldResult;\r
+               else\r
+                       oldChildren = new ArrayList<Resource>();\r
+               List<Resource> newChildren      = (List<Resource>)newResult;\r
+               \r
+               if (DEBUG) System.out.println("ScenegraphQuery " + nodeResource +  " updated: had " + oldChildren.size() + " children, but now has " + newChildren.size() + " children");\r
+               added.clear();\r
+               removed.clear();\r
+               if (initialized) {\r
+                               for (Resource r : oldChildren)\r
+                                       if (!newChildren.contains(r))\r
+                                               removed.add(r);\r
+                               for (Resource r : newChildren)\r
+                                       if (!oldChildren.contains(r))\r
+                                               added.add(r);\r
+                               for (Resource r : removed) {\r
+                                       if (DEBUG)\r
+                                               System.out.println("ScenegraphQuery " + nodeResource\r
+                                                               + " removed " + r);\r
+                                       removeNode(nodeResource, r);\r
+                               }\r
+                               if (added.size() > 0) {\r
+                                       G3DNode parent = new G3DNode(graph, nodeResource);\r
+                                       /*\r
+                                        * try {\r
+                                        * \r
+                                        * G3DTools.propagateTransformChange(parent); } catch\r
+                                        * (Exception e) { ErrorLogger.defaultLogError(e); }\r
+                                        */\r
+                                       for (Resource r : added) {\r
+                                               IEntity e = EntityFactory.create(graph, r);\r
+                                               G3DTools.propagateLocalTransformChange(parent, e);\r
+                                               IGraphicsNode n = addNode(parent, e);\r
+                                               shapeAdded(graph, n);\r
+                                       }\r
+                               }\r
+                               return (added.size() > 0 || removed.size() > 0);\r
+               } else {\r
+                       // when query is run for the first time, we can assume that transformations are correct.\r
+                       initialized = true;\r
+                       for (Resource r : newChildren)\r
+                               added.add(r);\r
+                       if (added.size() > 0) {\r
+                                       G3DNode parent = new G3DNode(graph, nodeResource);\r
+                                       for (Resource r : added) {\r
+                                               IEntity e = EntityFactory.create(graph, r);\r
+                                               IGraphicsNode n = addNode(parent, e);\r
+                                               shapeAdded(graph, n);\r
+                                       }\r
+                                       return true;\r
+                               }\r
+                       return false;\r
+               }\r
+               \r
+       }\r
+       \r
+       \r
+       /**\r
+        * This method is run after a node is added to scene-graph.\r
+        * \r
+        * @param graph Graph of the current transaction.\r
+        * @param node the newly added scene-graph node\r
+        */\r
+       public abstract void shapeAdded(Graph graph,IGraphicsNode node);\r
+       \r
+//     @Override\r
+//     public void attach() {\r
+//             scenegraphQueries.put(nodeResource, this);\r
+//     }\r
+       \r
+       \r
+    }\r
+    \r
+    /**\r
+     * Tracks changes in scene-graph nodes' properties\r
+     * \r
+     * @author Marko Luukkainen\r
+     *\r
+     */\r
+    public abstract class NodePropertyQuery extends NodeQuery {\r
\r
+        private boolean initialized;\r
+        \r
+       public NodePropertyQuery(Resource nodeResource) {\r
+               super(nodeResource);\r
+               initialized = false;\r
+               if(DEBUG)System.out.println("NodePropertyQuery created for " + nodeResource);\r
+       }\r
+       \r
+       @Override\r
+       public List<Object> compute2(Graph g) {\r
+               IEntity t = EntityFactory.create(g,nodeResource);\r
+               \r
+               Collection<Property> properties = t.getRelatedProperties(Resources.g3dResource.HasNonTransformation);\r
+               List<Object> propertyValues = new ArrayList<Object>();\r
+               p(properties,propertyValues);\r
+\r
+               return propertyValues;\r
+       }\r
+       \r
+       private void p(Collection<Property> properties, List<Object> propertyValues) {\r
+               for (Property p : properties) {\r
+                       Collection<Property> subProperties = p.getRelatedProperties(p.getGraph().getBuiltins().HasProperty);\r
+                       if (subProperties.size() != 0) {\r
+                               p(subProperties,propertyValues);\r
+                       } \r
+                       if (p.hasValue()){\r
+                               propertyValues.add(p.getValue());\r
+                       }\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public boolean updated(Graph graph, Object oldResult, Object newResult) {\r
+               if (initialized) {\r
+                       if (DEBUG) System.out.println("NodePropertyQuery changed " + nodeResource + " " + abstractGraphicsNodes.size());\r
+               IGraphicsNode mo = abstractGraphicsNodes.get(nodeResource);\r
+               if (mo == null) {\r
+                       if (DEBUG) System.out.println("NodePropertyQuery invalid change " + nodeResource + " " + abstractGraphicsNodes.size());\r
+                       ErrorLogger.defaultLogError("Got update from resource " + nodeResource + " but its not part of the scenegraph", null);\r
+                       dispose();\r
+                       return false;\r
+               }\r
+               shapeUpdated(graph,mo);\r
+               return true;\r
+               } else {\r
+                       initialized = true;\r
+                       return false;\r
+               }\r
+       }\r
+\r
+       \r
+       /**\r
+        * This method is run when a scene-graph node is changed.\r
+        * \r
+        * @param shape the changed node\r
+        */\r
+       public abstract void shapeUpdated(Graph graph,IGraphicsNode shape);\r
+       \r
+//     @Override\r
+//     public void attach() {\r
+//             propertyQueries.put(nodeResource, this);\r
+//     }\r
+       \r
+    }\r
+    \r
+    public abstract class NodeTransformationQuery extends NodeQuery {\r
+       \r
+       private boolean initialized;\r
+       \r
+       public NodeTransformationQuery(Resource nodeResource) {\r
+               super(nodeResource);\r
+               initialized = false;\r
+               if(DEBUG)System.out.println("NodeTransformationQuery created for " + nodeResource);\r
+       }\r
+       \r
+       @Override\r
+       public List<Object> compute2(Graph g) {\r
+               IEntity t = EntityFactory.create(g,nodeResource);\r
+               \r
+               Collection<Property> properties = t.getRelatedProperties(Resources.g3dResource.HasTransformation);\r
+               \r
+               List<Object> propertyValues = new ArrayList<Object>();\r
+               p(properties,propertyValues);\r
+               return propertyValues;\r
+\r
+       }\r
+       \r
+       private void p(Collection<Property> properties, List<Object> propertyValues) {\r
+               for (Property p : properties) {\r
+                       Collection<Property> subProperties = p.getRelatedProperties(p.getGraph().getBuiltins().HasProperty);\r
+                       if (subProperties.size() != 0) {\r
+                               p(subProperties,propertyValues);\r
+                       } \r
+                       if (p.hasValue()){\r
+                               propertyValues.add(p.getValue());\r
+                       }\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public boolean updated(Graph graph,Object oldResult, Object newResult) {\r
+               if (initialized) {\r
+                       if (DEBUG) System.out.println("NodeTransformationQuery changed " + nodeResource + " " + abstractGraphicsNodes.size());\r
+\r
+                       G3DTools.transformationUpdate(graph, nodeResource);\r
+\r
+                       IGraphicsNode mo = abstractGraphicsNodes.get(nodeResource);\r
+               if (mo == null) {\r
+                       if (DEBUG) System.out.println("NodeTransformationQuery invalid change " + nodeResource + " " + abstractGraphicsNodes.size());\r
+                       ErrorLogger.defaultLogError("Got update from resource " + nodeResource + " but its not part of the scenegraph", null);\r
+                       dispose();\r
+                       return false;\r
+               }\r
+               shapeUpdated(graph,mo);\r
+               return true;\r
+               } else {\r
+                       initialized = true;\r
+                       return false;\r
+               }\r
+       }\r
+\r
+       \r
+       /**\r
+        * This method is run when a scene-graph node is changed.\r
+        * \r
+        * @param shape the changed node\r
+        */\r
+       public abstract void shapeUpdated(Graph graph,IGraphicsNode shape);\r
+       \r
+//     @Override\r
+//     public void attach() {\r
+//             transformationQueries.put(nodeResource, this);\r
+//     }\r
+       \r
+    }\r
+    \r
+\r
+}\r