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