/** * */ package cz.cuni.mff.peckam.java.origamist.gui.common; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import javax.media.j3d.Alpha; import javax.media.j3d.Appearance; import javax.media.j3d.Behavior; import javax.media.j3d.BoundingSphere; import javax.media.j3d.Canvas3D; import javax.media.j3d.GeometryArray; import javax.media.j3d.Group; import javax.media.j3d.ImageComponent2D; import javax.media.j3d.LineArray; import javax.media.j3d.Morph; import javax.media.j3d.OrderedGroup; import javax.media.j3d.Screen3D; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.TriangleArray; import javax.media.j3d.WakeupCondition; import javax.media.j3d.WakeupOnElapsedFrames; import javax.vecmath.Point3d; import org.apache.log4j.Level; import org.apache.log4j.Logger; import com.sun.j3d.utils.universe.SimpleUniverse; import cz.cuni.mff.peckam.java.origamist.exceptions.InvalidOperationException; import cz.cuni.mff.peckam.java.origamist.exceptions.PaperStructureException; import cz.cuni.mff.peckam.java.origamist.model.Origami; import cz.cuni.mff.peckam.java.origamist.model.Step; import cz.cuni.mff.peckam.java.origamist.modelstate.ModelSegment; import cz.cuni.mff.peckam.java.origamist.modelstate.ModelState; import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedCallable; /** * A canvas controller that implements model folding animations. * * @author Martin Pecka */ @SuppressWarnings("deprecation") public class StepMorphingCanvasController extends StepViewingCanvasController { /** The morph nodes on the current step. */ protected final Set<Morph> morphs = new HashSet<Morph>(); /** The number of animation frames per a step. */ protected final int framesPerStep; /** The number of frames elapsed since the last step change. */ protected int currentStepElapsedFrames = 0; /** Transforms of this and the next step. */ protected Transform3D fromTrans = new Transform3D(), toTrans = new Transform3D(); { // screen dimensions need to be set before rendering SimpleUniverse u = new SimpleUniverse(new Canvas3D(SimpleUniverse.getPreferredConfiguration())); Screen3D screen = u.getViewer().getCanvas3D().getScreen3D(); Screen3D screen2 = canvas.getScreen3D(); screen2.setSize(screen.getSize()); screen2.setPhysicalScreenWidth(screen.getPhysicalScreenWidth()); screen2.setPhysicalScreenHeight(screen.getPhysicalScreenHeight()); if (canvas.getOffScreenBuffer() == null) { ImageComponent2D image = new ImageComponent2D(ImageComponent2D.FORMAT_RGBA, 800, 600); image.setCapability(ImageComponent2D.ALLOW_IMAGE_READ); canvas.setOffScreenBuffer(image); } canvas.setDoubleBufferEnable(false); canvas.setVisible(true); canvas.renderOffScreenBuffer(); setStep(step); } /** * @param canvas * @param origami * @param step * @param framesPerStep The number of animation frames per a step. */ public StepMorphingCanvasController(Canvas3D canvas, Origami origami, Step step, int framesPerStep) { super(canvas, origami, step); this.framesPerStep = framesPerStep; } /** * @param canvas * @param framesPerStep The number of animation frames per a step. */ public StepMorphingCanvasController(Canvas3D canvas, int framesPerStep) { super(canvas); this.framesPerStep = framesPerStep; } @Override protected TransformGroup setupTGroup() throws InvalidOperationException { try { // we will need to edit the model state, so create a safe copy of it ModelState state = step.getModelState(true).clone(); TriangleArray[] trianglesWithDelayed = state.getTrianglesArrays(); LineArray[] lineArraysWithDelayed = state.getLineArrays(); state.revertDelayedOperations(); TriangleArray[] trianglesWithoutDelayed = state.getTrianglesArrays(); LineArray[] lineArraysWithoutDelayed = state.getLineArraysAfterDelayedRevertion(); assert trianglesWithDelayed.length == trianglesWithoutDelayed.length; assert lineArraysWithDelayed.length == lineArraysWithoutDelayed.length; tGroup = new TransformGroup(); tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); if (morphs == null) return tGroup; OrderedGroup og = new OrderedGroup(); model = new TransformGroup(); morphs.clear(); Morph top, bottom; Appearance appearance; Appearance appearance2; for (int i = 0; i < trianglesWithDelayed.length; i++) { appearance = createNormalTrianglesAppearance(); appearance2 = createInverseTrianglesAppearance(); top = new Morph(new GeometryArray[] { trianglesWithoutDelayed[i], trianglesWithDelayed[i] }, appearance); bottom = new Morph(new GeometryArray[] { trianglesWithoutDelayed[i], trianglesWithDelayed[i] }, appearance2); top.setCapability(Morph.ALLOW_WEIGHTS_WRITE); bottom.setCapability(Morph.ALLOW_WEIGHTS_WRITE); morphs.add(top); morphs.add(bottom); model.addChild(top); model.addChild(bottom); } Group lines = new TransformGroup(); Appearance appearance3; Morph morph; for (int i = 0; i < lineArraysWithDelayed.length; i++) { ModelSegment segment = (ModelSegment) lineArraysWithDelayed[i].getUserData(); appearance3 = createBasicLinesAppearance(); getLineAppearanceManager().alterBasicAppearance(appearance3, segment.getDirection(), step.getId() - segment.getOriginatingStepId()); morph = new Morph(new GeometryArray[] { lineArraysWithoutDelayed[i], lineArraysWithDelayed[i] }, appearance3); morph.setCapability(Morph.ALLOW_WEIGHTS_WRITE); morphs.add(morph); lines.addChild(morph); } model.addChild(lines); og.addChild(model); setupTransform(); tGroup.setTransform(transform); og.addChild(getMarkerGroups()); tGroup.addChild(og); return tGroup; } catch (InvalidOperationException e) { tGroup = new TransformGroup(); tGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // TODO create an ErrorTransformGroup that would signalize to the user that an operation is invalid throw e; } } @Override protected void createAndAddBranchGraphChildren() throws InvalidOperationException { setupTGroup(); branchGraph.addChild(tGroup); if (!canvas.isOffScreen()) { // onscreen canvases will be controlled by this behavior Behavior morph = new Behavior() { private Alpha alpha; private WakeupCondition trigger = new WakeupOnElapsedFrames(0); public void initialize() { alpha = new Alpha(-1, (framesPerStep / 30) * 1000); // play at 30 fps this.setSchedulingBounds(new BoundingSphere(new Point3d(), 1000)); this.wakeupOn(trigger); } public void processStimulus(@SuppressWarnings("rawtypes") Enumeration criteria) { // don't need to decode event since there is only one trigger updateMorphs(alpha.value()); this.wakeupOn(trigger); // set next wakeup condition } }; branchGraph.addChild(morph); } } // @Override // protected Transform3D computeInitialViewTransform(Origami origami) // { // return new Transform3D(); // } @Override public void setStep(Step step, Runnable afterSetCallback, ParametrizedCallable<?, ? super Exception> exceptionCallback) { final Step oldStep = this.step; this.step = step; currentStepElapsedFrames = 0; if (step != null && step.getAttachedTo() == null) { return; } Exception exception = null; synchronized (this) { try { if (step != null) setupUniverse(); } catch (InvalidOperationException e) { Logger.getLogger("application").l7dlog(Level.ERROR, "StepRenderer.InvalidOperationException", new Object[] { this.step.getId(), e.getOperation().toString() }, e); exception = e; } catch (PaperStructureException e) { Logger.getLogger("application").error(e.getMessage(), e); exception = e; } finally { topTexture = null; bottomTexture = null; } } if (oldStep != null && step != null && step.getNext() != null) { fromTrans = new Transform3D(baseTransform); this.step = step.getNext(); setupTransform(); toTrans = new Transform3D(baseTransform); this.step = step; setupTransform(); } support.firePropertyChange(STEP_PROPERTY, null, step); setZoom(50); // TODO just a workaround, will need to find out why the view is badly centered without this if (exception == null) { afterSetStep(); if (afterSetCallback != null) afterSetCallback.run(); } else if (exceptionCallback != null) { exceptionCallback.call(exception); } } /** * Update morph nodes depending on the given alpha value. * * @param alpha The alpha value in 0.0 - 1.0. */ protected void updateMorphs(float alpha) { final double[] weights = new double[2]; // don't need to decode event since there is only one trigger Arrays.fill(weights, 0); weights[0] = 1 - alpha; weights[1] = alpha; for (Morph morph : morphs) morph.setWeights(weights); } /** * @return The next animation frame. If this would be the last frame of the step's animation, the next step is * automatically set. If this is the last step, return <code>null</code> */ public BufferedImage getNextFrame() { if (step == null) return null; float alphaValue = ((float) currentStepElapsedFrames / framesPerStep); // get alpha updateMorphs(alphaValue); adjustSize(); // Transform3D tr = new Transform3D(fromTrans); // tr.invert(); // tr.mul(toTrans, tr); // tr.mul(alphaValue); //TODO will need another kind of interpolation // // transform.mul(fromTrans, tr); // tGroup.setTransform(transform); canvas.waitForOffScreenRendering(); canvas.renderOffScreenBuffer(); canvas.waitForOffScreenRendering(); BufferedImage image = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.drawImage(canvas.getOffScreenBuffer().getImage(), 0, 0, origami.getPaper().getBackgroundColor(), null); g.dispose(); currentStepElapsedFrames++; if (currentStepElapsedFrames > framesPerStep) { currentStepElapsedFrames = 0; if (step.getNext() != null) setStep(step.getNext()); } return image; } }