--- /dev/null
+package org.simantics.proconf.g3d.shapeeditor.tools;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.List;\r
+\r
+import javax.vecmath.AxisAngle4d;\r
+import javax.vecmath.Quat4d;\r
+\r
+import org.eclipse.jface.action.IMenuManager;\r
+import org.eclipse.jface.action.IToolBarManager;\r
+import org.eclipse.jface.dialogs.Dialog;\r
+import org.eclipse.jface.dialogs.InputDialog;\r
+import org.eclipse.jface.dialogs.MessageDialog;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.custom.CCombo;\r
+import org.eclipse.swt.events.FocusAdapter;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.KeyAdapter;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.SelectionAdapter;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.layout.FillLayout;\r
+import org.eclipse.swt.layout.FormAttachment;\r
+import org.eclipse.swt.layout.FormData;\r
+import org.eclipse.swt.layout.FormLayout;\r
+import org.eclipse.swt.layout.GridData;\r
+import org.eclipse.swt.layout.GridLayout;\r
+import org.eclipse.swt.widgets.Button;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Slider;\r
+import org.eclipse.swt.widgets.Text;\r
+import org.simantics.db.ContextGraph;\r
+import org.simantics.db.Graph;\r
+import org.simantics.db.GraphRequestAdapter;\r
+import org.simantics.db.GraphRequestStatus;\r
+import org.simantics.db.GraphRequestWithResult;\r
+import org.simantics.db.Resource;\r
+import org.simantics.g2d.stubs.anim.Animation;\r
+import org.simantics.g2d.stubs.anim.Interpolator;\r
+import org.simantics.g2d.stubs.anim.ScalarInterpolator;\r
+import org.simantics.g2d.stubs.anim.SlerpInterpolator;\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.animation.curve.SlerpCurve;\r
+import org.simantics.proconf.animation.curve.TCBCurve;\r
+import org.simantics.proconf.g3d.actions.ContextAction;\r
+import org.simantics.proconf.g3d.actions.RotateAction;\r
+import org.simantics.proconf.g3d.actions.TranslateAction;\r
+import org.simantics.proconf.g3d.animation.Animatable;\r
+import org.simantics.proconf.g3d.base.EditorContribution;\r
+import org.simantics.proconf.g3d.base.G3DTools;\r
+import org.simantics.proconf.g3d.base.ThreeDimensionalEditorBase;\r
+import org.simantics.proconf.g3d.base.VisualizationScheduler;\r
+import org.simantics.proconf.g3d.common.StructuredResourceSelection;\r
+import org.simantics.proconf.g3d.scenegraph.IGraphicsNode;\r
+import org.simantics.proconf.g3d.scenegraph.ISelectableNode;\r
+import org.simantics.proconf.g3d.shapeeditor.ShapeEditorResources;\r
+import org.simantics.proconf.g3d.shapeeditor.dialogs.PropertySelectionDialog;\r
+import org.simantics.proconf.g3d.shapeeditor.views.ShapeEditorBase;\r
+import org.simantics.proconf.g3d.stubs.G3DModel;\r
+import org.simantics.proconf.g3d.stubs.Orientation;\r
+import org.simantics.utils.ErrorLogger;\r
+\r
+public class AnimationContribution implements EditorContribution {\r
+ List<ContextAction> actions = new ArrayList<ContextAction>();\r
+ \r
+ private ShapeEditorBase parent;\r
+ \r
+ private final String NO_ANIMATION = "None";\r
+\r
+ private Slider timeSlider;\r
+ private double key = 0;\r
+ private Composite buttonComposite;\r
+ private Button insertKeyFrameButton;\r
+ private Button clearKeyFrameButton;\r
+ private CCombo animationCombo;\r
+ private Button addAnimationButton;\r
+ private Button removeAnimationButton;\r
+ private Button clearAnimationButton;\r
+ private Button animateButton;\r
+ private Button usePrecalculation;\r
+ private Text timeText;\r
+ private Composite infoComposite;\r
+ private Text infoText;\r
+\r
+ private Resource animationResource;\r
+ \r
+ private ContextAction translateAction;\r
+ private ContextAction rotateAction;\r
+ \r
+ public AnimationContribution(ThreeDimensionalEditorBase parent) {\r
+ this.parent = (ShapeEditorBase)parent;\r
+ }\r
+ \r
+ @Override\r
+ public String getName() {\r
+ return "Animator";\r
+ }\r
+ \r
+ @Override\r
+ public void createControl(Composite parent) {\r
+ FormLayout flayout = new FormLayout();\r
+ parent.setLayout(flayout);\r
+ infoComposite = new Composite(parent, SWT.BORDER);\r
+ FormData data = new FormData();\r
+ data.top = new FormAttachment(0, 0);\r
+ data.left = new FormAttachment(0, 0);\r
+ data.right = new FormAttachment(100, 0);\r
+ data.bottom = new FormAttachment(infoComposite, 0, SWT.TOP);\r
+ this.parent.getRenderingComposite().setLayoutData(data);\r
+\r
+ infoComposite.setLayout(new FillLayout(SWT.VERTICAL));\r
+ infoText = new Text(infoComposite, SWT.NONE);\r
+ GridLayout layout = new GridLayout(2, false);\r
+ layout.horizontalSpacing = 2;\r
+ layout.verticalSpacing = 2;\r
+ layout.marginWidth = 1;\r
+ layout.marginHeight = 1;\r
+ infoComposite.setLayout(layout);\r
+ GridData gdata = new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1);\r
+ // FIXME : allows text widget to take all available space (horizontal / width)\r
+ gdata.widthHint = 2000;\r
+ infoText.setLayoutData(gdata);\r
+ timeSlider = new Slider(infoComposite, SWT.NONE);\r
+ timeSlider.setValues(0, 0, 100, 1, 1, 1);\r
+ timeSlider.addSelectionListener(new SelectionAdapter() {\r
+ @Override\r
+ public void widgetSelected(SelectionEvent e) {\r
+ key = ((double) timeSlider.getSelection()) / 100.0;\r
+ updateTime();\r
+ }\r
+ });\r
+ timeSlider.setLayoutData(new GridData(SWT.FILL, 1, true, false));\r
+ timeText = new Text(infoComposite, SWT.SINGLE);\r
+ timeText.addFocusListener(new FocusAdapter() {\r
+ @Override\r
+ public void focusLost(FocusEvent e) {\r
+ updateTime();\r
+ }\r
+ });\r
+ timeText.addKeyListener(new KeyAdapter() {\r
+ @Override\r
+ public void keyReleased(KeyEvent e) {\r
+ if (e.keyCode == SWT.CR)\r
+ updateTime();\r
+ }\r
+ });\r
+\r
+ buttonComposite = new Composite(infoComposite, SWT.NONE);\r
+ buttonComposite.setLayout(new FillLayout(SWT.HORIZONTAL));\r
+ buttonComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1));\r
+ insertKeyFrameButton = new Button(buttonComposite, SWT.PUSH);\r
+ insertKeyFrameButton.setText("Insert Keyframe");\r
+ insertKeyFrameButton.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ insertKeyFrame();\r
+ }\r
+ });\r
+\r
+ clearKeyFrameButton = new Button(buttonComposite, SWT.PUSH);\r
+ clearKeyFrameButton.setText("Clear Keyframe");\r
+\r
+ animationCombo = new CCombo(buttonComposite, SWT.NONE);\r
+ animationCombo.add(NO_ANIMATION);\r
+ animationCombo.select(0);\r
+ animationCombo.setEditable(false);\r
+ animationCombo.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ selectAnimation();\r
+ }\r
+ });\r
+\r
+ addAnimationButton = new Button(buttonComposite, SWT.PUSH);\r
+ addAnimationButton.setText("New Animation");\r
+ addAnimationButton.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ addAnimation();\r
+ }\r
+ });\r
+\r
+ clearAnimationButton = new Button(buttonComposite, SWT.PUSH);\r
+ clearAnimationButton.setText("Clear Animation");\r
+ clearAnimationButton.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ clearAnimation();\r
+ }\r
+ });\r
+\r
+ removeAnimationButton = new Button(buttonComposite, SWT.PUSH);\r
+ removeAnimationButton.setText("Remove Animation");\r
+ removeAnimationButton.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ removeAnimation();\r
+ }\r
+ });\r
+\r
+ animateButton = new Button(buttonComposite, SWT.TOGGLE);\r
+ animateButton.setText("Animate");\r
+ animateButton.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ animate();\r
+ }\r
+ });\r
+\r
+ usePrecalculation = new Button(buttonComposite, SWT.CHECK);\r
+ usePrecalculation.setText("Precalc");\r
+\r
+ data = new FormData();\r
+ data.left = new FormAttachment(0, 0);\r
+ data.right = new FormAttachment(100, 0);\r
+ data.bottom = new FormAttachment(100, 0);\r
+ // FIXME : take account font size\r
+ data.height = 20 * 3;\r
+ infoComposite.setLayoutData(data);\r
+\r
+ this.parent.getSelectionAdapter().addSelectionChangedListener(new ISelectionChangedListener() {\r
+ public void selectionChanged(SelectionChangedEvent event) {\r
+ updateUI();\r
+ }\r
+ });\r
+ updateUI();\r
+ \r
+ this.parent.getSession().asyncRead(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ G3DModel m = new G3DModel(g,AnimationContribution.this.parent.getModelResource());\r
+ Collection<Animation> animations = m.getAnimation();\r
+\r
+ final List<String> animationNames = new ArrayList<String>();\r
+ for (Animation a : animations) {\r
+ animationNames.add(a.getName());\r
+ }\r
+ AnimationContribution.this.parent.getRenderingComposite().getDisplay().asyncExec(new Runnable() {\r
+ public void run() {\r
+ for (String s : animationNames)\r
+ animationCombo.add(s);\r
+ }\r
+ });\r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ \r
+ });\r
+ }\r
+ \r
+ @Override\r
+ public void disposeControl() {\r
+ if (animateRunnable != null)\r
+ VisualizationScheduler.getInstance().removeVisualization(animateRunnable);\r
+ animateRunnable = null;\r
+ \r
+ infoComposite.dispose();\r
+\r
+ }\r
+ \r
+ @Override\r
+ public void fillContextMenu(Graph graph, IMenuManager manager, StructuredResourceSelection selection) {\r
+\r
+ }\r
+ \r
+ \r
+ \r
+ @Override\r
+ public Collection<ContextAction> getActions() {\r
+ return actions;\r
+ }\r
+ \r
+ @Override\r
+ public void initialize(Graph graph) {\r
+ actions.add(translateAction = new TranslateAction(parent) {\r
+ @Override\r
+ public void setInfoText(String text) {\r
+ infoText.setText(text);\r
+ }\r
+ });\r
+ actions.add(rotateAction = new RotateAction(parent){\r
+ @Override\r
+ public void setInfoText(String text) {\r
+ infoText.setText(text);\r
+ }\r
+ });\r
+ }\r
+ \r
+ private double getCurrentKey() {\r
+ return key;\r
+ }\r
+ \r
+ private void updateTime() {\r
+ final double t = getCurrentKey();\r
+ timeText.setText(Double.toString(t));\r
+ if (animationResource == null)\r
+ return;\r
+ if (usePrecalculation.getSelection()) {\r
+ for (IGraphicsNode n : parent.getScenegraphAdapter().getNodes()) {\r
+ if (n instanceof Animatable) {\r
+ // TODO : frame-rate dependent animations\r
+ ((Animatable)n).animate(t,0.0);\r
+ }\r
+ parent.setViewChanged(true);\r
+ } \r
+ } else {\r
+ parent.getSession().asyncWrite(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ Collection<IEntity> interpolators = getAnimation(g).getRelatedObjects(ShapeEditorResources.animationResource.HasInterpolator);\r
+ for (IEntity i : interpolators) {\r
+ if (i.isInstanceOf(ShapeEditorResources.animationResource.ScalarInterpolator)) {\r
+ // TODO : creating curve each time when time is set is\r
+ // slow. Curve should be cached\r
+ TCBCurve c = (TCBCurve) ShapeEditorResources.curveBuilder.loadInterpolator(i);\r
+ double out = c.evaluate(t);\r
+ g.setScalarDouble(i.getSingleRelatedObject(ShapeEditorResources.animationResource.HasTarget).getResource(), out);\r
+ } else if (i.isInstanceOf(ShapeEditorResources.animationResource.SlerpInterpolator)) {\r
+ // TODO : creating curve each time when time is set is slow.\r
+ // Curve should be cached\r
+ SlerpCurve c = (SlerpCurve) ShapeEditorResources.curveBuilder.loadInterpolator(i);\r
+ Quat4d out = c.evaluate(t);\r
+ Orientation r = new Orientation(i.getSingleRelatedObject(ShapeEditorResources.animationResource.HasTarget));\r
+ AxisAngle4d aa = new AxisAngle4d();\r
+ aa.set(out);\r
+ G3DTools.setOrientation(r, aa);\r
+ }\r
+ }\r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ \r
+ @Override\r
+ public void requestCompleted(GraphRequestStatus status) {\r
+ parent.getScenegraphAdapter().setChanged(true);\r
+ }\r
+ }); \r
+ }\r
+ }\r
+ \r
+ private IEntity findPropertyInterpolator(Graph g, Resource property) {\r
+ Collection<IEntity> interpolators = getAnimation(g).getRelatedObjects(ShapeEditorResources.animationResource.HasInterpolator);\r
+ for (IEntity i : interpolators) {\r
+ IEntity e = i.getAtMostOneRelatedObject(ShapeEditorResources.animationResource.HasTarget);\r
+ if (e == null)\r
+ continue;\r
+ if (e.getResource().equals(property)) {\r
+ return i;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ private void insertKeyFrame() {\r
+ ArrayList<Resource> instances = new ArrayList<Resource>();\r
+ for (Resource rs : parent.getSelectionAdapter().getCurrentSelection().getSelectionList()) {\r
+ instances.add(rs);\r
+ }\r
+ PropertySelectionDialog dialog = new PropertySelectionDialog(parent.getRenderingComposite().getShell(),"Select property","Select animated property",parent.getSession(),instances);\r
+ if (dialog.open() == Dialog.CANCEL) {\r
+ return;\r
+ }\r
+ final List<Resource> properties = dialog.getSelectedPropertyInstances(); \r
+ parent.getSession().asyncWrite(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ for (Resource r : properties) {\r
+ IEntity t = EntityFactory.create(g,r);\r
+ IEntity current = findPropertyInterpolator(g, r);\r
+ if (t.isInstanceOf(ShapeEditorResources.g3dResource.Position)) {\r
+ \r
+ for (int i = 0; i < 3; i++) {\r
+ IEntity d = null;\r
+ switch (i) {\r
+ case 0:\r
+ d = t.getSingleRelatedObject(ShapeEditorResources.g3dResource.HasX);\r
+ break;\r
+ case 1:\r
+ d = t.getSingleRelatedObject(ShapeEditorResources.g3dResource.HasY);\r
+ break;\r
+ case 2:\r
+ d = t.getSingleRelatedObject(ShapeEditorResources.g3dResource.HasZ);\r
+ break;\r
+ }\r
+ current = findPropertyInterpolator(g, d.getResource());\r
+ addScalarKey(current,d);\r
+ }\r
+ \r
+ } else if (t.isInstanceOf(ShapeEditorResources.g3dResource.Orientation)) {\r
+ Orientation rot = new Orientation(t);\r
+ addSlerpKey(current, rot);\r
+ } else if (t.isInstanceOf(ShapeEditorResources.g3dResource.Color)) {\r
+ for (int i = 0; i < 3; i++) {\r
+ IEntity d = null;\r
+ switch (i) {\r
+ case 0:\r
+ d = t.getSingleRelatedObject(ShapeEditorResources.g3dResource.HasRed);\r
+ break;\r
+ case 1:\r
+ break;\r
+ case 2:\r
+ break;\r
+ }\r
+ current = findPropertyInterpolator(g, d.getResource());\r
+ addScalarKey(current,d);\r
+ }\r
+ } else if (t.isInstanceOf(g.getBuiltins().Double)) {\r
+ addScalarKey(current,t);\r
+ } else {\r
+ // TODO: basic cases are handled, only way to support\r
+ // is to find all doubles from property structure and\r
+ // interpolators attached to them, if there's any\r
+ ErrorLogger.getDefault().logWarning("Default keyframe adding has not been implemented", null);\r
+ }\r
+ }\r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void addScalarKey(IEntity current, IEntity d) {\r
+ \r
+ Graph graph = d.getGraph();\r
+ if (current == null) {\r
+ current = ScalarInterpolator.createDefault(graph).toInterpolator(); // FIXME : stubcast\r
+ getAnimation(graph).addStatement(ShapeEditorResources.animationResource.HasInterpolator, current);\r
+ current.addStatement(ShapeEditorResources.animationResource.HasTarget, d);\r
+ }\r
+ ShapeEditorResources.curveBuilder.addKey(current,getCurrentKey(),new double[]{d.getGraph().getScalarDouble(d.getResource()),0.0,0.0,0.0});\r
+ }\r
+ \r
+ private void addSlerpKey(IEntity current, Orientation r) {\r
+ Graph graph = r.getGraph();\r
+ if (current == null) {\r
+ current = SlerpInterpolator.createDefault(graph).toInterpolator(); // FIXME : stubcast\r
+ getAnimation(graph).addStatement(ShapeEditorResources.animationResource.HasInterpolator, current);\r
+ current.addStatement(ShapeEditorResources.animationResource.HasTarget, r);\r
+ \r
+ }\r
+ AxisAngle4d aa = G3DTools.getOrientation(r);\r
+ Quat4d q = new Quat4d();\r
+ q.set(aa);\r
+ ShapeEditorResources.curveBuilder.addKey(current,getCurrentKey(),new double[]{q.w,q.x,q.y,q.z}); \r
+ }\r
+ \r
+ private void selectAnimation() {\r
+ if (animationCombo.getSelectionIndex() == 0) {\r
+ animationResource = null;\r
+ updateUI();\r
+ } else {\r
+ final String name = animationCombo.getItem(animationCombo.getSelectionIndex());\r
+ parent.getSession().asyncRead(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ \r
+ Resource modelResource = parent.getModelResource();\r
+ G3DModel m = new G3DModel(g, modelResource);\r
+ Collection<Animation> animations = m.getAnimation();\r
+ boolean found = false;\r
+ for (Animation a : animations) {\r
+ if (a.getName().startsWith(name) && a.getName().length() == name.length()) {\r
+ animationResource = a.getResource();\r
+ found = true;\r
+ break;\r
+ }\r
+ }\r
+ if (!found) {\r
+ ErrorLogger.defaultLogError("Could find animation " + name + " for model " + m.getResource(), null);\r
+ animationResource = null;\r
+ }\r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ @Override\r
+ public void requestCompleted(GraphRequestStatus status) {\r
+ parent.getRenderingComposite().getDisplay().asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ updateUI(); \r
+ }\r
+ });\r
+ }\r
+ });\r
+ } \r
+ }\r
+ \r
+ private Animation getAnimation(Graph graph) {\r
+ return new Animation(graph, animationResource);\r
+ }\r
+\r
+ private void addAnimation() {\r
+ InputDialog d = new InputDialog(parent.getRenderingComposite().getShell(),"Animation name","Animation name","",null);\r
+ if (d.open() == InputDialog.CANCEL) {\r
+ return;\r
+ }\r
+ final String name = d.getValue();\r
+ if (name == null || name.length() == 0) {\r
+ return;\r
+ }\r
+ parent.getSession().asyncWrite(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ G3DModel m = parent.getModel(g);\r
+ Collection<IEntity> animations = m.getRelatedObjects(ShapeEditorResources.animationResource.HasAnimation);\r
+ for (IEntity a : animations) {\r
+ if (a.getName().startsWith(name) && a.getName().length() == name.length()) {\r
+ ErrorLogger.getDefault().logWarning("Cannot add animation with the same name " + name, null);\r
+ return GraphRequestStatus.transactionCancel();\r
+ }\r
+ }\r
+\r
+ Animation newAnimation = Animation.createDefault(g);\r
+ newAnimation.setName(name);\r
+ m.addStatement(ShapeEditorResources.animationResource.HasAnimation, newAnimation);\r
+ \r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+\r
+ @Override\r
+ public void requestCompleted(GraphRequestStatus status) {\r
+ parent.getRenderingComposite().getDisplay().asyncExec(new Runnable() {\r
+ public void run() {\r
+ animationCombo.add(name);\r
+ animationCombo.select(animationCombo.indexOf(name));\r
+ selectAnimation();\r
+ }\r
+ });\r
+ }\r
+ }); \r
+ }\r
+ \r
+ private void removeAnimation() {\r
+ assert(animationResource != null);\r
+ GraphRequestWithResult<String> r = new GraphRequestWithResult<String>() {\r
+ public String performWithResult(Graph g) throws Exception {\r
+ return getAnimation(g).getName();\r
+ };\r
+ };\r
+ parent.getSession().syncRead(r);\r
+ MessageDialog dialog = new MessageDialog(parent.getRenderingComposite().getShell(),"Confirm",null,"Do you want to remove animation " + r.getResult() ,MessageDialog.QUESTION,new String[]{"Yes","No"},1);\r
+ if (dialog.open() == 1)\r
+ return;\r
+ parent.getSession().asyncWrite(new GraphRequestAdapter() {\r
+ String name;\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ \r
+ Animation currentAnimation = getAnimation(g);\r
+ name = currentAnimation.getName();\r
+ parent.getModel(g).removeStatement(ShapeEditorResources.animationResource.HasAnimation, currentAnimation);\r
+ \r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ \r
+ @Override\r
+ public void requestCompleted(GraphRequestStatus status) {\r
+ parent.getRenderingComposite().getDisplay().asyncExec(new Runnable() {\r
+ public void run() {\r
+ animationCombo.remove(animationCombo.indexOf(name));\r
+ }\r
+ }); \r
+ }\r
+ });\r
+ }\r
+ \r
+ private void clearAnimation() {\r
+ assert(animationResource != null); \r
+ \r
+ parent.getSession().asyncWrite(new GraphRequestAdapter() {\r
+ \r
+ boolean proceed;\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ int size = getAnimation(g).getInterpolator().size();\r
+ if (size == 0)\r
+ return GraphRequestStatus.transactionCancel();\r
+ final String name = getAnimation(g).getName();\r
+ parent.getRenderingComposite().getDisplay().syncExec(new Runnable() {\r
+ public void run() {\r
+ MessageDialog dialog = new MessageDialog(parent.getRenderingComposite().getShell(),"Confirm",null,"Do you want to clear animation " + name ,MessageDialog.QUESTION,new String[]{"Yes","No"},1);\r
+ proceed = (dialog.open() != MessageDialog.CANCEL);\r
+ }\r
+ });\r
+ if (proceed) {\r
+ //getAnimation(g).getInterpolator().clear();\r
+ getAnimation(g).removeRelatedStatements(ShapeEditorResources.animationResource.HasInterpolator);\r
+ return GraphRequestStatus.transactionComplete();\r
+ } else {\r
+ return GraphRequestStatus.transactionCancel();\r
+ }\r
+ }\r
+ });\r
+ }\r
+ \r
+ public Graph createAnimationParameterization(Graph g) {\r
+ if (g.getObjects(parent.getModelResource(), ShapeEditorResources.g3dResource.HasSizingParameter).size() > 0) {\r
+ ContextGraph graph;\r
+ if (!(g instanceof ContextGraph)) {\r
+ graph = new ContextGraph(g);\r
+ graph.setContext(parent.getModelResource());\r
+ } else {\r
+ graph = (ContextGraph)g;\r
+ }\r
+ Animation animation = getAnimation(graph);\r
+ Collection<Interpolator> interpolators = animation.getInterpolator();\r
+ for (org.simantics.g2d.stubs.anim.Interpolator interpolator : interpolators) {\r
+ IEntity target = interpolator.getTarget();\r
+ // check all model properties\r
+ G3DModel model = parent.getModel(graph);\r
+ Collection<Property> modelProperties = model.getRelatedProperties(ShapeEditorResources.g3dResource.HasSizingParameter);\r
+ for (Property p : modelProperties) {\r
+ IEntity t = EntityFactory.create(graph,p.getResource());\r
+ // get parameterization equations\r
+ Collection<IEntity> equations = t.getRelatedObjects(ShapeEditorResources.equationResource.HasTarget);\r
+ // get parameterized values\r
+ Collection<IEntity> parameterTargets = new ArrayList<IEntity>();\r
+ for (IEntity eq : equations) {\r
+ Collection<IEntity> tgts = eq.getRelatedObjects(ShapeEditorResources.equationResource.HasTarget);\r
+ assert(tgts.size() == 1);\r
+ parameterTargets.add(tgts.iterator().next());\r
+ }\r
+ // do matching between interpolator targets and parameterized values\r
+ // TODO : old system did not have inverse relations but current system does.\r
+ // it is possible to take interpolation target and find if it is connected to an equation\r
+ // this would make code much faster (no more stupid loops over everything)\r
+ for (IEntity d : parameterTargets) {\r
+ if (d.getResource().equals(target.getResource())) {\r
+ // get default value for sizing property\r
+ Collection<IEntity> prop = t.getRelatedObjects(ShapeEditorResources.g3dResource.HasDefaultDoubleValue);\r
+ if (prop.size() == 1) {\r
+ ShapeEditorResources.curveBuilder.parameterize(interpolator, prop.iterator().next().toProperty().getDoubleArray(), p.getDoubleArray());\r
+ } else {\r
+ ErrorLogger.defaultLogError("Cannot parameterize interpolator " + interpolator.getResource() + " of animation " + animation.getResource() + " since parameter " + p.getResource() + " has no default value", null);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ return graph;\r
+ } else {\r
+ return g;\r
+ }\r
+ }\r
+ \r
+ private AnimateRunnable animateRunnable = null;\r
+ \r
+ private void animate() {\r
+ updateUI();\r
+ if (animateButton.getSelection()) {\r
+ if (animateRunnable != null)\r
+ return;\r
+ if (usePrecalculation.getSelection()) {\r
+ parent.getSession().asyncRead(new GraphRequestAdapter() {\r
+ @Override\r
+ public GraphRequestStatus perform(Graph g) throws Exception {\r
+ Graph graph = parent.createParameterization(g);\r
+ createAnimationParameterization(graph);\r
+ for (IGraphicsNode n : parent.getScenegraphAdapter().getNodes()) {\r
+ if (n instanceof ISelectableNode) {\r
+ if (!((ISelectableNode)n).isVisible())\r
+ continue;\r
+ }\r
+ if (n instanceof Animatable) {\r
+ ((Animatable)n).setAnimation(graph,animationResource);\r
+ }\r
+ }\r
+ return GraphRequestStatus.transactionComplete();\r
+ }\r
+ \r
+ @Override\r
+ public void requestCompleted(GraphRequestStatus status) {\r
+ animateRunnable = new AnimateRunnable();\r
+ VisualizationScheduler.getInstance().addVisualization(animateRunnable);\r
+ }\r
+ });\r
+ } else {\r
+ animateRunnable = new AnimateRunnable();\r
+ VisualizationScheduler.getInstance().addVisualization(animateRunnable);\r
+ }\r
+ \r
+ } else {\r
+ if (animateRunnable == null)\r
+ return; \r
+ VisualizationScheduler.getInstance().removeVisualization(animateRunnable);\r
+ animateRunnable = null;\r
+ if (usePrecalculation.getSelection()) {\r
+ // updateTime updates values to graph if precalculation is not used.\r
+ // we must store current values from animation to synchronize view and graph\r
+ // information so that use can modify animation properly.\r
+ usePrecalculation.setSelection(false);\r
+ updateTime();\r
+ usePrecalculation.setSelection(true);\r
+ for (IGraphicsNode n : parent.getScenegraphAdapter().getNodes()) {\r
+ if (n instanceof Animatable) {\r
+ ((Animatable)n).setAnimation(null,null);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void updateUI() {\r
+ if (animationResource != null) {\r
+ animateButton.setEnabled(true);\r
+ timeSlider.setEnabled(true);\r
+ if (animateButton.getSelection()) {\r
+ addAnimationButton.setEnabled(false);\r
+ insertKeyFrameButton.setEnabled(false);\r
+ removeAnimationButton.setEnabled(false);\r
+ clearKeyFrameButton.setEnabled(false);\r
+ animationCombo.setEnabled(false);\r
+ usePrecalculation.setEnabled(false);\r
+ clearAnimationButton.setEnabled(false);\r
+ } else {\r
+ addAnimationButton.setEnabled(true);\r
+ insertKeyFrameButton.setEnabled(!parent.getSelectionAdapter().getCurrentSelection().isEmpty());\r
+ removeAnimationButton.setEnabled(true);\r
+ clearKeyFrameButton.setEnabled(false); //FIXME : detect keyframes\r
+ animationCombo.setEnabled(true);\r
+ usePrecalculation.setEnabled(true);\r
+ clearAnimationButton.setEnabled(true);\r
+ } \r
+ } else {\r
+ timeSlider.setEnabled(false);\r
+ addAnimationButton.setEnabled(true);\r
+ insertKeyFrameButton.setEnabled(false);\r
+ removeAnimationButton.setEnabled(false);\r
+ clearKeyFrameButton.setEnabled(false);\r
+ animateButton.setEnabled(false);\r
+ animateButton.setSelection(false);\r
+ animationCombo.setEnabled(true);\r
+ usePrecalculation.setEnabled(false);\r
+ clearAnimationButton.setEnabled(false);\r
+ }\r
+ }\r
+ \r
+ private void updateKey(double k) {\r
+ key = k;\r
+ if (key >= 1.0)\r
+ key = 0.0;\r
+ else if (key < 0.0)\r
+ key = 1.0;\r
+ timeSlider.setSelection((int)(key*100.0));\r
+ }\r
+ \r
+ private class AnimateRunnable implements Runnable {\r
+ public void run() {\r
+ try {\r
+ updateKey(key + 0.01);\r
+ updateTime();\r
+ } catch (Exception e) {\r
+ VisualizationScheduler.getInstance().removeVisualization(animateRunnable);\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void fillLocalToolBar(IToolBarManager manager) {\r
+\r
+ }\r
+ \r
+ @Override\r
+ public void fillLocalPullDown(IMenuManager manager) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void dispose() {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void run() {\r
+\r
+ }\r
+\r
+}\r