/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit.animations; import com.sun.lwuit.animations.*; import com.sun.lwuit.Component; import com.sun.lwuit.Dialog; import com.sun.lwuit.Display; import com.sun.lwuit.Graphics; import com.sun.lwuit.Image; import com.sun.lwuit.M3G; import javax.microedition.m3g.AnimationController; import javax.microedition.m3g.AnimationTrack; import javax.microedition.m3g.Appearance; import javax.microedition.m3g.Background; import javax.microedition.m3g.Camera; import javax.microedition.m3g.CompositingMode; import javax.microedition.m3g.Graphics3D; import javax.microedition.m3g.Group; import javax.microedition.m3g.Image2D; import javax.microedition.m3g.IndexBuffer; import javax.microedition.m3g.KeyframeSequence; import javax.microedition.m3g.Mesh; import javax.microedition.m3g.Node; import javax.microedition.m3g.PolygonMode; import javax.microedition.m3g.Texture2D; import javax.microedition.m3g.Transform; import javax.microedition.m3g.TriangleStripArray; import javax.microedition.m3g.VertexArray; import javax.microedition.m3g.VertexBuffer; import javax.microedition.m3g.World; /** * Transitions utilizing the M3G API for 3D effects, this transition requires * M3G (JSR 184) support on the device in order to work properly. Currently * none of these transitions work with dialogs or any component type that * is not a form * * @author Shai Almog */ public final class Transition3D extends Transition implements M3G.Callback { private static final int FLY_IN = 2; private static final int ROTATION = 1; private static final int CUBE_ROTATION = 3; private static final int STATIC_ROTATION = 4; private static final int SWING_IN = 5; private static final int VERTICAL_CUBE_ROTATION = 6; private int transitionType; private World world; //private int rotation; private long startTime; private int duration = 1000; private static int maxTextureResolution = -1; private boolean rotateRight; private boolean highQualityMode; private boolean firstFinished; private int clipX, clipY, clipW, clipH; private boolean firstTime = true; private Transition3D(int transitionType) { this.transitionType = transitionType; } /** * @inheritDoc */ public void cleanup() { super.cleanup(); world = null; try { Graphics3D.getInstance().setCamera(null, null); Graphics3D.getInstance().resetLights(); } catch(Throwable err) { } firstTime = true; } private static void initMaxTexture() { maxTextureResolution = M3G.getInstance().getMaxTextureDimension(); } /** * Allows performance/memory sensitive devices to define a maximum size for the * texture thus increasing performance on the expense of quality. * @param size */ public static void setMaxTextureDimension(int size) { maxTextureResolution = size; } /** * Creates a rotation transition from the current form to the next form * * @param duration duration in milliseconds of the transition * @param rotateRight indicates rotating towards the right side or the left side true == right * @return newly created transition object */ public static Transition3D createRotation(int duration, boolean rotateRight) { initMaxTexture(); Transition3D t3d = new Transition3D(ROTATION); t3d.duration = duration; t3d.rotateRight = rotateRight; return t3d; } /** * Creates a rotation transition from the current form to the next dialog, in this rotation only * the dialog will rotate and the form will remain static * * @param duration duration in milliseconds of the transition * @param rotateRight indicates rotating towards the right side or the left side true == right * @return newly created transition object */ public static Transition3D createStaticRotation(int duration, boolean rotateRight) { initMaxTexture(); Transition3D t3d = new Transition3D(STATIC_ROTATION); t3d.duration = duration; t3d.rotateRight = rotateRight; return t3d; } /** * Creates a rotation transition from the top to the bottom giving a feeling of Swinging into place * * @param duration duration in milliseconds of the transition * @return new transtion object */ public static Transition3D createSwingIn(int duration) { return createSwingIn(duration, true); } /** * Creates a rotation transition from the top to the bottom giving a feeling of Swinging into place * * @param duration duration in milliseconds of the transition * @param topDown indicates rotating downwards or upwards * @return new transtion object */ public static Transition3D createSwingIn(int duration, boolean topDown) { initMaxTexture(); Transition3D t3d = new Transition3D(SWING_IN); t3d.duration = duration; t3d.rotateRight = topDown; return t3d; } /** * Creates a cube rotation transition from the current form to the next form * * @param duration duration in milliseconds of the transition * @param rotateRight indicates rotating towards the right side or the left side true == right * @return newly created transition object */ public static Transition3D createCube(int duration, boolean rotateRight) { initMaxTexture(); Transition3D t3d = new Transition3D(CUBE_ROTATION); t3d.duration = duration; t3d.rotateRight = rotateRight; return t3d; } /** * Creates a cube rotation transition from the current form to the next form * * @param duration duration in milliseconds of the transition * @param rotateDown indicates rotating towards the upper side when true * @return newly created transition object */ public static Transition3D createVerticalCube(int duration, boolean rotateDown) { initMaxTexture(); Transition3D t3d = new Transition3D(VERTICAL_CUBE_ROTATION); t3d.duration = duration; t3d.rotateRight = rotateDown; return t3d; } /** * Creates a fly in transition object. * * @param duration duration in milliseconds of the transition * @return newly created transition object */ public static Transition3D createFlyIn(int duration) { initMaxTexture(); Transition3D t3d = new Transition3D(FLY_IN); t3d.duration = duration; return t3d; } /** * @inheritDoc */ public Transition copy(boolean reverse) { Transition3D t3d = new Transition3D(transitionType); t3d.duration = duration; if(reverse) { t3d.rotateRight = !rotateRight; } else { t3d.rotateRight = rotateRight; } return t3d; } /** * @inheritDoc */ public boolean animate() { if(world == null) { return false; } long time = System.currentTimeMillis(); int current = (int)(time - startTime); world.animate(current); // after the motion finished we need to paint one last time otherwise // there will be a "bump" in sliding if(firstFinished) { return false; } boolean finished = current >= duration; if(finished) { if(!firstFinished) { firstFinished = true; } } return true; } /** * @inheritDoc */ public void paint(Graphics g) { // prevents painting when the components are too small if(world == null) { return; } // paint the finished component if the animation completed if(firstFinished) { getDestination().paintComponent(g); return; } Component c = getSource(); int titleHeight = 0; if(c instanceof Dialog) { Dialog dlg = (Dialog)c; c = dlg.getContentPane(); titleHeight = dlg.getTitleComponent().getHeight(); } else { if(getDestination() instanceof Dialog) { Dialog dlg = (Dialog)getDestination(); c = dlg.getContentPane(); titleHeight = dlg.getTitleComponent().getHeight(); } else { if(firstTime) { startTime = System.currentTimeMillis(); firstTime = false; } } } if(firstTime) { startTime = System.currentTimeMillis(); firstTime = false; // paint the whole source form once to make sure we have the proper // tinting behavior if(getDestination() instanceof Dialog) { getSource().paintComponent(g); } else { getDestination().paintComponent(g); } } clipY = c.getAbsoluteY() - titleHeight; clipX = c.getAbsoluteX(); clipW = c.getWidth(); clipH = c.getHeight() + titleHeight; g.setClip(clipX, clipY, clipW, clipH); M3G.getInstance().renderM3G(g, false, Graphics3D.ANTIALIAS, this); } /** * @inheritDoc */ public void initTransition() { try { Component source = getSource(); Component dest = getDestination(); // prevent painting when the components are too small if(source == null || dest == null || source.getWidth() < 4 || source.getHeight() < 4 || dest.getWidth() < 4 || dest.getHeight() < 4) { return; } firstFinished = false; world = new World(); Camera camera = new Camera(); camera.setPerspective(30.0f, 1, 1.0f, 45.0f); Transform cameraTransform = new Transform(); Graphics3D g3d = Graphics3D.getInstance(); g3d.setCamera(camera, cameraTransform); // the whole world is within a single group in the scene graph tree Group group = new Group(); world.addChild(group); world.setActiveCamera(camera); group.addChild(camera); Background background3D = new Background(); background3D.setColorClearEnable(true); background3D.setDepthClearEnable(true); background3D.setColor(0xff000000 | source.getStyle().getBgColor()); world.setBackground(background3D); switch(transitionType) { case FLY_IN: { Mesh destMesh = createMesh(dest); group.addChild(destMesh); createFlyIn(destMesh, background3D); break; } case CUBE_ROTATION: { Mesh destMesh = createMesh(dest); createCubeRotation(group, destMesh, false); break; } case VERTICAL_CUBE_ROTATION: { Mesh destMesh = createMesh(dest); createCubeRotation(group, destMesh, true); break; } case ROTATION: { Mesh destMesh = createMesh(dest); group.addChild(destMesh); createRotation(group, destMesh); break; } case SWING_IN: case STATIC_ROTATION: { Mesh destMesh; if(source instanceof Dialog) { destMesh = createMesh(source); source = dest; } else { destMesh = createMesh(dest); } createStaticRotation(group, destMesh, background3D, source, transitionType == SWING_IN); break; } default: throw new IllegalArgumentException("Bad 3D mode : " + transitionType); } world.animate(0); startTime = System.currentTimeMillis(); } catch(Throwable t) { t.printStackTrace(); cleanup(); } } private void createFlyIn(Mesh destMesh, Background background3D) { background3D.setImage(createImage2D(getSource())); KeyframeSequence trans = new KeyframeSequence(10, 3, KeyframeSequence.LINEAR); KeyframeSequence alpha = new KeyframeSequence(2, 1, KeyframeSequence.LINEAR); alpha.setDuration(duration); trans.setDuration(duration); alpha.setKeyframe(0, 0, new float[] {0.1f}); alpha.setKeyframe(1, duration, new float[] {1}); int frac = duration / 10; int time = 0; float position = -31; for(int iter = 0 ; iter < 10 ; iter++) { float[] pos = new float[] {0, 0, position}; trans.setKeyframe(iter, time, pos); position += 3; time += frac; } AnimationController animation = new AnimationController(); AnimationTrack track = new AnimationTrack(trans, AnimationTrack.TRANSLATION); destMesh.addAnimationTrack(track); track.setController(animation); track = new AnimationTrack(alpha, AnimationTrack.ALPHA); destMesh.addAnimationTrack(track); track.setController(animation); } private void createCubeRotation(Group parent, Mesh destMesh, boolean vertical) { Group group = new Group(); parent.addChild(group); Mesh sourceMesh = createMesh(getSource()); group.addChild(destMesh); group.addChild(sourceMesh); sourceMesh.setTranslation(0f, 0, 1.0f); group.translate(0f, 0, -4.7f); KeyframeSequence rotation = new KeyframeSequence(3, 4, KeyframeSequence.SPLINE); rotation.setDuration(duration); if(!vertical) { if(rotateRight) { destMesh.setOrientation(270, 0, 1, 0); destMesh.setTranslation(-1.0f, 0, 0f); } else { destMesh.setOrientation(90, 0, 1, 0); destMesh.setTranslation(1.0f, 0, 0f); } rotation.setKeyframe(0, 0, getYRoation(0)); if(rotateRight) { rotation.setKeyframe(1, duration / 2, getYRoation(20)); rotation.setKeyframe(2, duration, getYRoation(45)); } else { rotation.setKeyframe(1, duration / 2, getYRoation(-20)); rotation.setKeyframe(2, duration, getYRoation(-45)); } } else { if(rotateRight) { destMesh.setOrientation(270, 1, 0, 0); destMesh.setTranslation(0.0f, 1.0f, 0f); } else { destMesh.setOrientation(90, 1, 0, 0); destMesh.setTranslation(0.0f, -1.0f, 0f); } rotation.setKeyframe(0, 0, getXRoationTop(0)); if(rotateRight) { rotation.setKeyframe(1, duration / 2, getXRoationTop(20)); rotation.setKeyframe(2, duration, getXRoationTop(45)); } else { rotation.setKeyframe(1, duration / 2, getXRoationTop(-20)); rotation.setKeyframe(2, duration, getXRoationTop(-45)); } } AnimationController animation = new AnimationController(); AnimationTrack track = new AnimationTrack(rotation, AnimationTrack.ORIENTATION); group.addAnimationTrack(track); track.setController(animation); } private void createRotation(Group group, Mesh destMesh) { Node sourceMesh = (Node)createMesh(getSource()); group.addChild(sourceMesh); sourceMesh.setTranslation(0, 0, -3.79f); destMesh.setTranslation(0, 0, -3.8f); KeyframeSequence rotationSource = new KeyframeSequence(3, 4, KeyframeSequence.SPLINE); KeyframeSequence rotationDest = new KeyframeSequence(3, 4, KeyframeSequence.SPLINE); rotationSource.setDuration(duration); rotationDest.setDuration(duration); int half = 45; int full = 90; // prevent the rotation from staying too long in 90 degrees int halfDest = 150; int fullDest = 180; if(rotateRight) { half = -half; full = -full; halfDest = -halfDest; fullDest = -fullDest; } rotationSource.setKeyframe(0, 0, getYRoation(0)); rotationSource.setKeyframe(1, duration / 4, getYRoation(half)); rotationSource.setKeyframe(2, duration / 2, getYRoation(full)); rotationDest.setKeyframe(0, 0, getYRoation(full)); rotationDest.setKeyframe(1, duration / 2, getYRoation(halfDest)); rotationDest.setKeyframe(2, duration, getYRoation(fullDest)); AnimationController animation = new AnimationController(); AnimationTrack track = new AnimationTrack(rotationSource, AnimationTrack.ORIENTATION); sourceMesh.addAnimationTrack(track); track.setController(animation); track = new AnimationTrack(rotationDest, AnimationTrack.ORIENTATION); destMesh.addAnimationTrack(track); track.setController(animation); } private void createStaticRotation(Group group, Mesh destMesh, Background background3D, Component source, boolean swingIn) { background3D.setImage(createImage2D(source)); KeyframeSequence rotationDest; // prevent the rotation from staying too long in 90 degrees int halfDest = 90; int fullDest = 180; int mid = 135; AnimationController animation = new AnimationController(); Node rotatingNode; if(swingIn) { rotationDest = new KeyframeSequence(5, 4, KeyframeSequence.SPLINE); rotationDest.setDuration(duration); Group rotation = new Group(); group.addChild(rotation); rotation.addChild(destMesh); if(rotateRight) { rotation.translate(0, 0.8f, -4.0f); destMesh.translate(0, -0.8f, 0); } else { rotation.translate(0, -0.8f, -4.0f); destMesh.translate(0, 0.8f, 0); } int quarter = duration / 4; if(source == getSource()) { rotationDest.setKeyframe(0, 0, getXRoationTop(halfDest)); rotationDest.setKeyframe(1, quarter, getXRoationTop(mid)); rotationDest.setKeyframe(2, quarter * 2, getXRoationTop(fullDest)); rotationDest.setKeyframe(3, quarter * 3, getXRoationTop(fullDest + halfDest / 6)); rotationDest.setKeyframe(4, duration, getXRoationTop(fullDest)); } else { rotationDest.setKeyframe(0, 0, getXRoationTop(fullDest)); rotationDest.setKeyframe(1, quarter, getXRoationTop(fullDest + halfDest / 6)); rotationDest.setKeyframe(2, quarter * 2, getXRoationTop(fullDest)); rotationDest.setKeyframe(3, quarter * 3, getXRoationTop(mid)); rotationDest.setKeyframe(4, duration, getXRoationTop(halfDest)); } rotatingNode = rotation; } else { if(rotateRight) { halfDest = -halfDest; fullDest = -fullDest; mid = -mid; } destMesh.setTranslation(0, 0, -3.8f); rotationDest = new KeyframeSequence(3, 4, KeyframeSequence.SPLINE); rotationDest.setDuration(duration); group.addChild(destMesh); if(source == getSource()) { rotationDest.setKeyframe(0, 0, getYRoation(halfDest)); rotationDest.setKeyframe(1, duration / 2, getYRoation(mid)); rotationDest.setKeyframe(2, duration, getYRoation(fullDest)); } else { rotationDest.setKeyframe(0, 0, getYRoation(fullDest)); rotationDest.setKeyframe(1, duration / 2, getYRoation(mid)); rotationDest.setKeyframe(2, duration, getYRoation(halfDest)); } rotatingNode = destMesh; } AnimationTrack track = new AnimationTrack(rotationDest, AnimationTrack.ORIENTATION); rotatingNode.addAnimationTrack(track); track.setController(animation); } /** * Creates a rotation matrix on the Y axis */ private float[] getYRoation(float angle) { angle = (float)Math.toRadians(angle); return new float[] {0, (float)Math.sin(angle), 0, (float)Math.cos(angle)}; } /** * Creates a rotation matrix on the top X axis */ private float[] getXRoationTop(float angle) { angle = (float)Math.toRadians(angle); return new float[] {(float)Math.sin(angle), 0, 0, (float)Math.cos(angle)}; } private Image2D createImage2D(Component c) { int w = c.getWidth(); int h = c.getHeight(); Dialog dlg = null; if(getSource() instanceof Dialog) { dlg = (Dialog)getSource(); w = dlg.getContentPane().getWidth(); h = dlg.getContentPane().getHeight() + dlg.getTitleComponent().getHeight(); } else { if(getDestination() instanceof Dialog) { dlg = (Dialog)getDestination(); w = dlg.getContentPane().getWidth(); h = dlg.getContentPane().getHeight() + dlg.getTitleComponent().getHeight(); } } int textureW; int textureH; if(highQualityMode) { // use the true texture maximum resolution ignoring light mode... int max = M3G.getInstance().getMaxTextureDimension(); textureW = Math.min(M3G.closestHigherPowerOf2(w - 1), max); textureH = Math.min(M3G.closestHigherPowerOf2(h - 1), max); } else { textureW = Math.min(M3G.closestLowerPowerOf2(w + 1), maxTextureResolution); textureH = Math.min(M3G.closestLowerPowerOf2(h + 1), maxTextureResolution); } Image mutable = Image.createImage(w, h); Graphics g = mutable.getGraphics(); if(c instanceof Dialog) { c = dlg.getContentPane(); g.translate(-c.getAbsoluteX(), -c.getAbsoluteY() + dlg.getTitleComponent().getHeight()); dlg.getContentPane().paintComponent(g, false); dlg.getTitleComponent().paintComponent(g, false); //g.setClip(c.getAbsoluteX(), c.getAbsoluteY() + dlg.getTitleComponent().getHeight(), mutable.getWidth(), mutable.getHeight()); } else { if(dlg != null) { Component content = dlg.getContentPane(); g.translate(-content.getAbsoluteX(), -content.getAbsoluteY() + dlg.getTitleComponent().getHeight()); } else { g.translate(-c.getAbsoluteX(), -c.getAbsoluteY()); } c.paintComponent(g); } mutable = mutable.scaled(textureW, textureH); return M3G.getInstance().createImage2D(Image2D.RGB, mutable); } private Mesh createMesh(Component c) { Texture2D tex = createTexture(createImage2D(c)); CompositingMode cm = new CompositingMode(); cm.setBlending(CompositingMode.ALPHA); cm.setAlphaWriteEnable(true); cm.setDepthTestEnable(true); Appearance appearance = new Appearance(); PolygonMode polyMode = new PolygonMode(); polyMode.setPerspectiveCorrectionEnable(true); appearance.setPolygonMode(polyMode); appearance.setCompositingMode(cm); appearance.setTexture(0, tex); VertexBuffer vb = makeGeometry(); int[] indicies = {1,2,0,3}; // one quad int[] stripLens = {4}; IndexBuffer ib = new TriangleStripArray(indicies, stripLens); Mesh m = new Mesh(vb, ib, appearance); return m; } private VertexBuffer makeGeometry() { // create vertices short[] verts = { -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0 }; VertexArray va = new VertexArray(verts.length / 3, 3, 2); va.set(0, verts.length / 3, verts); // create texture coordinates short[] tcs = { 0, 1, 1, 1, 1, 0, 0, 0 }; VertexArray texArray = new VertexArray(tcs.length / 2, 2, 2); texArray.set(0, tcs.length / 2, tcs); VertexBuffer vb = new VertexBuffer(); vb.setPositions(va, 1.0f, null); // no scale, bias vb.setTexCoords(0, texArray, 1.0f, null); return vb; } /** * Creates a texture making */ private Texture2D createTexture(Image2D img) { Texture2D tex = new Texture2D(img); tex.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST); tex.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP); tex.setBlending(Texture2D.FUNC_REPLACE); return tex; } /** * @inheritDoc */ public void paintM3G(Graphics3D g) { g.render(world); } /** * High quality mode renders the transition using smoother graphics but can * take a whole lot more memory per texture and bitmap resulting in a likely * out of memory error on high resolution/low memory devices with complex UI * elements. * * @return whether this is high quality rendering mode */ public boolean isHighQualityMode() { return highQualityMode; } /** * High quality mode renders the transition using smoother graphics but can * take a whole lot more memory per texture and bitmap resulting in a likely * out of memory error on high resolution/low memory devices with complex UI * elements. * * @param highQualityMode indicates whether this is the high quality mode */ public void setHighQualityMode(boolean highQualityMode) { this.highQualityMode = highQualityMode; } }