blob: 8fae3414877010b4361f49f3199b757236868554 [file] [log] [blame]
/**
* Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) 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:
* Martin Fluegge - initial API and implementation
*/
package org.eclipse.emf.cdo.threedee.ui;
import org.eclipse.emf.cdo.threedee.Frontend;
import org.eclipse.emf.cdo.threedee.Session;
import org.eclipse.emf.cdo.threedee.common.Element;
import org.eclipse.emf.cdo.threedee.common.ElementDescriptor;
import org.eclipse.emf.cdo.threedee.common.ElementProvider;
import org.eclipse.emf.cdo.threedee.ui.bundle.OM;
import org.eclipse.emf.cdo.threedee.ui.nodes.CallShape;
import org.eclipse.emf.cdo.threedee.ui.nodes.ElementGroup;
import org.eclipse.emf.cdo.threedee.ui.nodes.IntroPlanet;
import org.eclipse.emf.cdo.threedee.ui.nodes.ReferenceShape;
import org.eclipse.emf.cdo.threedee.ui.nodes.RootElement;
import org.eclipse.emf.cdo.threedee.ui.nodes.ThreeDeeNode;
import org.eclipse.net4j.util.ObjectUtil;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
import org.eclipse.net4j.util.concurrent.QueueRunner;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.net4j.util.ui.UIQueueRunner;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import com.sun.j3d.utils.behaviors.keyboard.KeyNavigatorBehavior;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.picking.PickCanvas;
import com.sun.j3d.utils.picking.PickResult;
import com.sun.j3d.utils.picking.PickTool;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Geometry;
import javax.media.j3d.Group;
import javax.media.j3d.LineArray;
import javax.media.j3d.Node;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.View;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import java.awt.Frame;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* @author Martin Fluegge
*/
public class ThreeDeeWorld implements ISelectionProvider
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, ThreeDeeWorld.class);
private static boolean PRODUCTION = false;
private Map<Element, ElementGroup> elementGroups = new HashMap<Element, ElementGroup>();
private Map<Element, Map<Element, ReferenceShape>> referenceShapes = new HashMap<Element, Map<Element, ReferenceShape>>();
private Composite composite;
private QueueRunner runner;
private SimpleUniverse universe;
private BranchGroup scene;
private TransformGroup sphereTransformGroup;
private Canvas3D canvas;
private boolean showCrossReferences;
private Transform3D viewingTransform;
private RootElement root;
private StructuredSelection selection;
private List<ISelectionChangedListener> selectionChangeListeners = new ArrayList<ISelectionChangedListener>();
private CallThread callThread;
public ThreeDeeWorld(Composite parent)
{
composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND);
composite.setLayout(new FillLayout());
runner = new UIQueueRunner(parent.getDisplay());
runner.activate();
runner.addWork(new Runnable()
{
public void run()
{
init();
}
});
for (Session session : Frontend.INSTANCE.getElements())
{
Element rootElement = session.getRootElement();
if (rootElement != null && !elementGroups.containsKey(rootElement))
{
addElement(rootElement);
}
}
}
private void init()
{
Frame frame = SWT_AWT.new_Frame(composite);
canvas = ThreeDeeUtil.createCanvas(frame);
universe = new SimpleUniverse(canvas);
scene = createScene(canvas);
universe.addBranchGraph(scene);
if (!PRODUCTION)
{
universe.addBranchGraph(createCoordinateSystem());
}
setNominalViewingTransform();
frame.add(canvas);
createPicking(canvas, scene);
if (PRODUCTION)
{
IntroPlanet planet = new IntroPlanet(this);
universe.addBranchGraph(planet);
planet.start();
}
}
@SuppressWarnings("unused")
private void mouseTest()
{
Thread t = new Thread(new Runnable()
{
public void run()
{
ConcurrencyUtil.sleep(10000);
final Display display = composite.getDisplay();
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
Event event = new Event();
event.type = SWT.MouseDown;
event.button = 1;
display.post(event);
}
});
for (int i = 0; i < 100; i += 10)
{
final int a = i;
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
Event event = new Event();
event.type = SWT.MouseMove;
event.x = composite.toDisplay(a, a).x;
event.y = composite.toDisplay(a + 10, a + 10).y;
display.post(event);
try
{
Thread.sleep(500);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
});
}
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
Event event = new Event();
event.type = SWT.MouseUp;
event.button = 1;
display.post(event);
}
});
}
});
t.start();
}
private BranchGroup createScene(Canvas3D canvas)
{
BranchGroup scene = new BranchGroup();
scene.setCapability(Group.ALLOW_CHILDREN_EXTEND);
scene.setCapability(Group.ALLOW_CHILDREN_WRITE);
createLights(scene);
createNavigation(scene);
sphereTransformGroup = createTransformGroup();
scene.addChild(sphereTransformGroup);
root = new RootElement(canvas);
scene.addChild(root);
return scene;
}
private void createLights(BranchGroup group)
{
DirectionalLight directionalLight = new DirectionalLight();
directionalLight.setColor(new Color3f(0.7f, 0.8f, 0.8f));
directionalLight.setDirection(new Vector3f(15.0f, -20.0f, -6.0f));
directionalLight.setInfluencingBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0));
group.addChild(directionalLight);
AmbientLight ambientLight = new AmbientLight();
ambientLight.setColor(new Color3f(0.4f, 0.4f, 0.4f));
ambientLight.setInfluencingBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0));
group.addChild(ambientLight);
}
private void createNavigation(BranchGroup branchGroup)
{
TransformGroup viewTransformGroup = universe.getViewingPlatform().getViewPlatformTransform();
KeyNavigatorBehavior keyInteractor = new KeyNavigatorBehavior(viewTransformGroup);
BoundingSphere movingBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);
keyInteractor.setSchedulingBounds(movingBounds);
branchGroup.addChild(keyInteractor);
BoundingSphere mouseZone = new BoundingSphere(new Point3d(), Double.MAX_VALUE);
OrbitBehavior ob = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_TRANSLATE | OrbitBehavior.REVERSE_ROTATE);
ob.setSchedulingBounds(mouseZone);
universe.getViewingPlatform().setViewPlatformBehavior(ob);
}
private void createPicking(final Canvas3D canvas, final BranchGroup scene)
{
final PickCanvas pickCanvas = new PickCanvas(canvas, scene);
pickCanvas.setMode(PickTool.BOUNDS);
canvas.addMouseListener(new MouseAdapter()
{
@SuppressWarnings("unchecked")
@Override
public void mouseClicked(MouseEvent e)
{
pickCanvas.setShapeLocation(e);
PickResult result = pickCanvas.pickClosest();
if (result != null)
{
Node node = result.getNode(PickResult.PRIMITIVE | PickResult.SHAPE3D);
if (node != null)
{
ThreeDeeNode<Element> threeDeeNode = ThreeDeeUtil.getThreeDeeNode(node);
Element element = threeDeeNode.getModel();
setSelection(new StructuredSelection(element));
return;
}
}
setSelection(StructuredSelection.EMPTY);
}
});
}
private BranchGroup createCoordinateSystem()
{
BranchGroup group = new BranchGroup();
// X axis made of spheres
for (float x = -1.0f; x <= 1.0f; x = x + 0.1f)
{
Sphere sphere = new Sphere(0.02f);
TransformGroup tg = new TransformGroup();
Transform3D transform = new Transform3D();
Vector3f vector = new Vector3f(x, .0f, .0f);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(sphere);
group.addChild(tg);
}
// Y axis made of cones
for (float y = -1.0f; y <= 1.0f; y = y + 0.1f)
{
TransformGroup tg = new TransformGroup();
Transform3D transform = new Transform3D();
Cone cone = new Cone(0.02f, 0.02f);
Vector3f vector = new Vector3f(.0f, y, .0f);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(cone);
group.addChild(tg);
}
// Z axis made of cylinders
for (float z = -1.0f; z <= 1.0f; z = z + 0.1f)
{
TransformGroup tg = new TransformGroup();
Transform3D transform = new Transform3D();
Cylinder cylinder = new Cylinder(0.02f, 0.02f);
Vector3f vector = new Vector3f(.0f, .0f, z);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(cylinder);
group.addChild(tg);
}
return group;
}
public void setNominalViewingTransform()
{
ViewingPlatform viewingPlatform = universe.getViewingPlatform();
View view = viewingPlatform.getViewers()[0].getView();
view.setBackClipDistance(100.0d);
double fieldOfView = view.getFieldOfView();
double viewDistance = 12.0d / Math.tan(fieldOfView / 2.0d);
viewingTransform = new Transform3D();
viewingTransform.set(new Vector3d(0.0, 0.0, viewDistance));
viewingPlatform.getViewPlatformTransform().setTransform(viewingTransform);
}
public static ContextTracer getTracer()
{
return TRACER;
}
public SimpleUniverse getUniverse()
{
return universe;
}
public BranchGroup getScene()
{
return scene;
}
public TransformGroup getSphereTransformGroup()
{
return sphereTransformGroup;
}
public Canvas3D getCanvas()
{
return canvas;
}
public Transform3D getViewingTransform()
{
return viewingTransform;
}
public Composite getComposite()
{
return composite;
}
// public synchronized Element[] getElements()
// {
// return
// }
public void addElement(Element element)
{
if (!elementGroups.containsKey(element))
{
ElementGroup group = createElementGroup(element);
ElementProvider provider = element.getProvider();
Map<Integer, Boolean> references = element.getReferences();
createChildren(group, provider, references);
ElementGroup containerGroup = getContainerElementGroup(element);
addNode(group, containerGroup);
}
}
private void addNode(final ElementGroup node, final ElementGroup parent)
{
schedule(new Runnable()
{
public void run()
{
if (parent == null)
{
root.addChild(node);
}
else
{
ThreeDeeUtil.enablePicking(node);
parent.addChild(node);
}
layout();
Element element = node.getModel();
Element containerElement = getContainerElement(element);
if (containerElement != null)
{
updateReferences(containerElement);
}
else
{
updateReferences(element);
}
}
});
}
public void removeElement(Element element)
{
Element containerElement = getContainerElement(element);
if (containerElement != null)
{
containerElement.getReferences().remove(element.getID());
}
ElementGroup containmentGroup = elementGroups.remove(element);
if (containmentGroup != null)
{
ElementGroup containerContainmentGroup = getContainerElementGroup(element);
removeNode(containmentGroup, containerContainmentGroup);
clearReferenceNodes(element);
if (containerContainmentGroup != null)
{
clearReferenceNodes(element.getProvider().getElement(element.getContainerID()));
updateReferences(containerElement);
// layout(null, containerContainmentGroup);
}
}
layout();
}
private void removeNode(final ElementGroup containmentGroup, final ElementGroup containerContainmentGroup)
{
schedule(new Runnable()
{
public void run()
{
if (containerContainmentGroup != null)
{
containerContainmentGroup.removeChild(containmentGroup);
}
else
{
universe.getLocale().removeBranchGraph(containmentGroup);
}
root.layout();
}
});
}
private Element getContainerElement(Element element)
{
return element.getProvider().getElement(element.getContainerID());
}
private void clearReferenceNodes(Element element)
{
Map<Element, ReferenceShape> map = referenceShapes.get(element);
if (map != null)
{
for (ReferenceShape shape : map.values())
{
universe.getLocale().removeBranchGraph((BranchGroup)shape.getParent().getParent());
}
referenceShapes.remove(element);
}
}
private ElementGroup getContainerElementGroup(Element element)
{
Element containerElement = getContainerElement(element);
return elementGroups.get(containerElement);
}
private void createChildren(ElementGroup containerGroup, ElementProvider provider, Map<Integer, Boolean> references)
{
for (int targetID : references.keySet())
{
Element target = provider.getElement(targetID);
ElementGroup targetGroup = elementGroups.get(target);
if (targetGroup == null)
{
targetGroup = createElementGroup(target);
containerGroup.addChild(targetGroup);
createChildren(targetGroup, provider, target.getReferences());
}
}
}
private ElementGroup createElementGroup(Element element)
{
ElementGroup group = new ElementGroup(element, canvas);
elementGroups.put(element, group);
return group;
}
public void filter(Set<ElementDescriptor> toBeHidden)
{
for (ElementGroup elementGroup : elementGroups.values())
{
ElementDescriptor descriptor = elementGroup.getModel().getDescriptor();
elementGroup.setVisible(!toBeHidden.contains(descriptor));
}
}
public void dispose()
{
runner.deactivate();
composite.dispose();
}
public boolean schedule(Runnable runnable)
{
return runner.addWork(runnable);
}
public void addReferenceShape(final Node shapeLine)
{
schedule(new Runnable()
{
public void run()
{
TransformGroup transformGroupLine = new TransformGroup();
addChild(transformGroupLine, shapeLine);
BranchGroup branchGroup = new BranchGroup();
branchGroup.setCapability(BranchGroup.ALLOW_DETACH);
branchGroup.setCapability(Node.ALLOW_PICKABLE_WRITE);
branchGroup.addChild(transformGroupLine);
universe.addBranchGraph(branchGroup);
}
});
}
public void layout()
{
// schedule(new Runnable()
// {
// public void run()
// {
// for (Session session : Frontend.INSTANCE.getElements())
// {
// Element rootElement = session.getRootElement();
// if (rootElement != null)
// {
// ElementGroup elementGroup = elementGroups.get(rootElement);
// if (elementGroup != null)
// {
// layout(elementGroup, null);
// }
// }
// }
// }
// });
root.layout();
}
private void updateReferences(Element element)
{
ElementProvider provider = element.getProvider();
Map<Integer, Boolean> references = element.getReferences();
Integer[] elementIDs = references.keySet().toArray(new Integer[references.size()]);
for (int elementID : elementIDs)
{
Element referenceElement = provider.getElement(elementID);
if (references.get(elementID) || isShowCrossReferences())
{
Map<Element, ReferenceShape> map = referenceShapes.get(element);
ReferenceShape referenceShape;
if (map == null)
{
map = new HashMap<Element, ReferenceShape>();
referenceShapes.put(element, map);
referenceShape = createAndSetReferenceShape(element, references, elementID, map, referenceElement);
}
else
{
referenceShape = map.get(referenceElement);
if (referenceShape == null)
{
referenceShape = createAndSetReferenceShape(element, references, elementID, map, referenceElement);
}
}
referenceShape.setGeometry(getLineGeometry(element, referenceElement));
}
updateReferences(referenceElement);
}
}
private ReferenceShape createAndSetReferenceShape(Element element, Map<Integer, Boolean> references, int elementID,
Map<Element, ReferenceShape> map, Element referenceElement)
{
boolean containment = references.get(elementID);
ReferenceShape referenceShape = new ReferenceShape(element, referenceElement, containment);
addReferenceShape(referenceShape);
map.put(referenceElement, referenceShape);
return referenceShape;
}
private Geometry getLineGeometry(Element from, Element to)
{
ElementGroup fromGroup = elementGroups.get(from);
if (fromGroup == null)
{
return null;
}
ElementGroup toGroup = elementGroups.get(to);
if (toGroup == null)
{
return null;
}
Node fromShape = fromGroup.getShape();
Assert.isNotNull(fromShape);
Node toShape = toGroup.getShape();
Assert.isNotNull(toShape);
Point3f fromPosition = ThreeDeeUtil.getPositionAsPoint3f(fromShape);
Point3f toPosition = ThreeDeeUtil.getPositionAsPoint3f(toShape);
Point3f[] points = new Point3f[2];
points[0] = fromPosition;
points[1] = toPosition;
LineArray lineArray = new LineArray(2, LineArray.COORDINATES);
lineArray.setCoordinates(0, points);
return lineArray;
}
private TransformGroup createTransformGroup()
{
TransformGroup transformGroup = new TransformGroup();
transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
transformGroup.setCapability(Node.ENABLE_PICK_REPORTING);
transformGroup.setPickable(true);
return transformGroup;
}
private void addChild(Group parent, Node child)
{
parent.addChild(child);
}
public void showCall(Element source, Element target, boolean transmission)
{
if (source == null)
{
// Can happen for call events
return;
}
getCallThread().add(source, target, transmission);
}
public void setShowCrossReferences(boolean showCrossReferences)
{
this.showCrossReferences = showCrossReferences;
}
public boolean isShowCrossReferences()
{
return showCrossReferences;
}
public void addSelectionChangedListener(ISelectionChangedListener listener)
{
selectionChangeListeners.add(listener);
}
public void removeSelectionChangedListener(ISelectionChangedListener listener)
{
selectionChangeListeners.remove(listener);
}
public StructuredSelection getSelection()
{
if (selection == null)
{
return StructuredSelection.EMPTY;
}
return selection;
}
public void setSelection(ISelection selection)
{
if (selection instanceof StructuredSelection)
{
StructuredSelection ssel = (StructuredSelection)selection;
if (!ObjectUtil.equals(this.selection, ssel))
{
updateSelection(false);
this.selection = ssel;
updateSelection(true);
SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
for (ISelectionChangedListener listener : selectionChangeListeners)
{
listener.selectionChanged(event);
}
}
}
}
private void updateSelection(boolean select)
{
for (Iterator<?> it = getSelection().iterator(); it.hasNext();)
{
Object object = it.next();
ElementGroup elementGroup = elementGroups.get(object);
if (elementGroup != null)
{
elementGroup.select(select);
}
}
}
private synchronized CallThread getCallThread()
{
if (callThread == null)
{
callThread = new CallThread();
callThread.start();
}
return callThread;
}
/**
* @author Eike Stepper
*/
private final class CallThread extends Thread
{
private Map<Pair<Element, Element>, CallShape> calls = new HashMap<Pair<Element, Element>, CallShape>();
public CallThread()
{
setDaemon(true);
}
public synchronized void add(Element source, Element target, boolean transmission)
{
Pair<Element, Element> key = new Pair<Element, Element>(source, target);
CallShape call = calls.get(key);
if (call == null)
{
call = new CallShape(source, target, transmission);
call.setGeometry(getLineGeometry(source, target));
universe.addBranchGraph(call);
calls.put(key, call);
}
else
{
call.getAppearance().getTransparencyAttributes().setTransparency(0f);
}
}
@Override
public void run()
{
while (!interrupted())
{
for (Entry<Pair<Element, Element>, CallShape> entry : getEntries())
{
CallShape call = entry.getValue();
TransparencyAttributes transparencyAttributes = call.getAppearance().getTransparencyAttributes();
float transparency = transparencyAttributes.getTransparency();
if (transparency >= 1.0f)
{
remove(entry.getKey());
call.detach();
}
else
{
transparencyAttributes.setTransparency(transparency + 0.01f);
}
}
ConcurrencyUtil.sleep(15);
}
}
private synchronized Entry<Pair<Element, Element>, CallShape>[] getEntries()
{
@SuppressWarnings("unchecked")
Entry<Pair<Element, Element>, CallShape>[] array = calls.entrySet().toArray(new Entry[calls.size()]);
return array;
}
private synchronized void remove(Pair<Element, Element> key)
{
calls.remove(key);
}
}
}