SplineAnim.java Source code

Java tutorial

Introduction

Here is the source code for SplineAnim.java

Source

/*
 * @(#)SplineAnim.java 1.13 02/10/21 13:55:30
 * 
 * Copyright (c) 1996-2002 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *  - Redistribution in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

import java.applet.Applet;
import java.awt.Button;
import java.awt.Choice;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.ItemSelectable;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Scrollbar;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.media.j3d.Alpha;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Light;
import javax.media.j3d.Material;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.behaviors.interpolators.KBKeyFrame;
import com.sun.j3d.utils.behaviors.interpolators.KBRotPosScaleSplinePathInterpolator;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

/*
 * This program demonstrates the use of KBRotPosScaleSplinePathInterpolator in
 * order to to do spline animation paths using Kochanek-Bartels (also known as
 * TCB or Tension-Continuity-Bias ) splines. A cone red cone is animated along a
 * spline path specified by 5 knot points, which are showns as cyan spheres.
 * 
 * Use the left mouse button to changes orientation of scene. Use the middle
 * mouse button to zoom in/out Use the right mouse button to pan the scene
 */
public class SplineAnim extends Applet implements ActionListener, AdjustmentListener, ItemListener {

    // 3D Canvas
    Canvas3D canvas;

    // UI Components
    Panel controlPanel;

    Panel canvasPanel;

    Button animateButton;

    Choice interpChoice;

    Scrollbar speedSlider;

    Label speedLabel;

    Label interpLabel;

    // Scene Graph
    BoundingSphere bounds;

    BranchGroup root;

    BranchGroup behaviorBranch;

    Transform3D sceneTransform;

    TransformGroup sceneTransformGroup;

    Transform3D objTransform;

    TransformGroup objTransformGroup;

    Transform3D lightTransform1;

    Transform3D lightTransform2;

    TransformGroup light1TransformGroup;

    TransformGroup light2TransformGroup;

    // Key Frames & Interpolator
    int duration = 5000;

    Alpha animAlpha;

    Transform3D yAxis;

    KBKeyFrame[] linearKeyFrames = new KBKeyFrame[6];

    KBKeyFrame[] splineKeyFrames = new KBKeyFrame[6];

    KBRotPosScaleSplinePathInterpolator splineInterpolator;

    KBRotPosScaleSplinePathInterpolator linearInterpolator;

    // Data: Knot positions & transform groups
    Vector3f pos0 = new Vector3f(-5.0f, -5.0f, 0.0f);

    Vector3f pos1 = new Vector3f(-5.0f, 5.0f, 0.0f);

    Vector3f pos2 = new Vector3f(0.0f, 5.0f, 0.0f);

    Vector3f pos3 = new Vector3f(0.0f, -5.0f, 0.0f);

    Vector3f pos4 = new Vector3f(5.0f, -5.0f, 0.0f);

    Vector3f pos5 = new Vector3f(5.0f, 5.0f, 0.0f);

    TransformGroup k0TransformGroup;

    TransformGroup k1TransformGroup;

    TransformGroup k2TransformGroup;

    TransformGroup k3TransformGroup;

    TransformGroup k4TransformGroup;

    TransformGroup k5TransformGroup;

    // Flags
    boolean animationOn = true;

    boolean linear = false;

    private SimpleUniverse u = null;

    public SplineAnim() {
    }

    public void init() {
        this.setLayout(new FlowLayout());

        // Create the canvas and the UI
        canvasPanel = new Panel();
        controlPanel = new Panel();
        createCanvasPanel(canvasPanel);
        this.add(canvasPanel);
        createControlPanel(controlPanel);
        this.add(controlPanel);

        // Create the scene.
        BranchGroup scene = createSceneGraph();

        // Setup keyframe data for our animation
        setupSplineKeyFrames();
        setupLinearKeyFrames();

        // Setup alpha, create the interpolators and start them. We
        // create both a linear and a spline interpolator and turn on
        // one depending on user selection. The default is spline.
        setupAnimationData();
        createInterpolators();
        startInterpolator();

        // Add viewing platform
        u = new SimpleUniverse(canvas);

        // add mouse behaviors to ViewingPlatform
        ViewingPlatform viewingPlatform = u.getViewingPlatform();

        viewingPlatform.setNominalViewingTransform();

        // add orbit behavior to the ViewingPlatform
        OrbitBehavior orbit = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL);
        BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);
        orbit.setSchedulingBounds(bounds);
        viewingPlatform.setViewPlatformBehavior(orbit);

        u.addBranchGraph(scene);
    }

    public void destroy() {
        u.cleanup();
    }

    /*
     * This creates the control panel which contains a choice menu to toggle
     * between spline and linear interpolation, a slider to adjust the speed of
     * the animation and a animation start/stop button.
     */
    private void createControlPanel(Panel p) {

        GridBagLayout gl = new GridBagLayout();
        GridBagConstraints gbc = new GridBagConstraints();

        p.setLayout(gl);
        gbc.weightx = 100;
        gbc.weighty = 100;
        gbc.fill = GridBagConstraints.BOTH;

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        interpLabel = new Label("Interpolation Type", Label.LEFT);
        p.add(interpLabel, gbc);

        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        interpChoice = new Choice();
        interpChoice.add("Spline");
        interpChoice.add("Linear");
        p.add(interpChoice, gbc);
        interpChoice.addItemListener(this);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        gbc.gridheight = 1;
        speedSlider = new Scrollbar(Scrollbar.HORIZONTAL, 2, 1, 0, 11);
        speedSlider.setUnitIncrement(1);
        p.add(speedSlider, gbc);
        speedSlider.addAdjustmentListener(this);

        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.gridwidth = 2;
        gbc.gridheight = 1;
        speedLabel = new Label(" - Animation Speed +", Label.CENTER);
        p.add(speedLabel, gbc);

        gbc.gridx = 0;
        gbc.gridy = 5;
        gbc.gridwidth = 2;
        gbc.gridheight = 1;
        animateButton = new Button("Stop Animation");
        p.add(animateButton, gbc);
        animateButton.addActionListener(this);

    }

    /*
     * This creates the Java3D canvas
     */
    private void createCanvasPanel(Panel p) {

        GridBagLayout gl = new GridBagLayout();
        GridBagConstraints gbc = new GridBagConstraints();

        p.setLayout(gl);
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = 5;
        gbc.gridheight = 5;
        GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();

        canvas = new Canvas3D(config);
        canvas.setSize(490, 490);
        p.add(canvas, gbc);

    }

    /*
     * This creates the scene with 5 knot points represented by cyan spheres, a
     * cone obejct that will be transformed, and two directional lights + and
     * ambient light.
     */
    public BranchGroup createSceneGraph() {

        // Colors for lights and objects
        Color3f aColor = new Color3f(0.2f, 0.2f, 0.2f);
        Color3f eColor = new Color3f(0.0f, 0.0f, 0.0f);
        Color3f sColor = new Color3f(1.0f, 1.0f, 1.0f);
        Color3f coneColor = new Color3f(0.9f, 0.1f, 0.1f);
        Color3f sphereColor = new Color3f(0.1f, 0.7f, 0.9f);
        Color3f bgColor = new Color3f(0.0f, 0.0f, 0.0f);
        Color3f lightColor = new Color3f(1.0f, 1.0f, 1.0f);

        // Root of the branch grsph
        BranchGroup root = new BranchGroup();

        // Create transforms such that all objects appears in the scene
        sceneTransform = new Transform3D();
        sceneTransform.setScale(0.14f);
        Transform3D yrot = new Transform3D();
        yrot.rotY(-Math.PI / 5.0d);
        sceneTransform.mul(yrot);
        sceneTransformGroup = new TransformGroup(sceneTransform);
        sceneTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        sceneTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        root.addChild(sceneTransformGroup);

        // Create bounds for the background and lights
        bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0f);

        // Set up the background
        Background bg = new Background(bgColor);
        bg.setApplicationBounds(bounds);
        sceneTransformGroup.addChild(bg);

        // Create the transform group node for the lights
        lightTransform1 = new Transform3D();
        lightTransform2 = new Transform3D();
        Vector3d lightPos1 = new Vector3d(0.0, 0.0, 2.0);
        Vector3d lightPos2 = new Vector3d(1.0, 0.0, -2.0);
        lightTransform1.set(lightPos1);
        lightTransform2.set(lightPos2);
        light1TransformGroup = new TransformGroup(lightTransform1);
        light2TransformGroup = new TransformGroup(lightTransform2);
        sceneTransformGroup.addChild(light1TransformGroup);
        sceneTransformGroup.addChild(light2TransformGroup);

        // Create lights
        AmbientLight ambLight = new AmbientLight(aColor);
        Light dirLight1;
        Light dirLight2;

        Vector3f lightDir1 = new Vector3f(lightPos1);
        Vector3f lightDir2 = new Vector3f(lightPos2);
        lightDir1.negate();
        lightDir2.negate();
        dirLight1 = new DirectionalLight(lightColor, lightDir1);
        dirLight2 = new DirectionalLight(lightColor, lightDir2);

        // Set the influencing bounds
        ambLight.setInfluencingBounds(bounds);
        dirLight1.setInfluencingBounds(bounds);
        dirLight2.setInfluencingBounds(bounds);

        // Add the lights into the scene graph
        sceneTransformGroup.addChild(ambLight);
        sceneTransformGroup.addChild(dirLight1);
        sceneTransformGroup.addChild(dirLight2);

        // Create a cone and add it to the scene graph.
        objTransform = new Transform3D();
        objTransformGroup = new TransformGroup(objTransform);
        objTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        sceneTransformGroup.addChild(objTransformGroup);

        Material m = new Material(coneColor, eColor, coneColor, sColor, 100.0f);
        Appearance a = new Appearance();
        m.setLightingEnable(true);
        a.setMaterial(m);
        Cone cone = new Cone(0.4f, 1.0f);
        cone.setAppearance(a);
        objTransformGroup.addChild(cone);

        // Create transform groups for each knot point
        // knot point 0
        Transform3D t3dKnot = new Transform3D();
        t3dKnot.set(pos0);
        TransformGroup k0TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k0TransformGroup);

        // knot point 1
        t3dKnot = new Transform3D();
        t3dKnot.set(pos1);
        TransformGroup k1TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k1TransformGroup);

        // knot point 2
        t3dKnot = new Transform3D();
        t3dKnot.set(pos2);
        TransformGroup k2TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k2TransformGroup);

        // knot point 3
        t3dKnot = new Transform3D();
        t3dKnot.set(pos3);
        TransformGroup k3TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k3TransformGroup);

        // knot point 4
        t3dKnot = new Transform3D();
        t3dKnot.set(pos4);
        TransformGroup k4TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k4TransformGroup);

        // knot point 5
        t3dKnot = new Transform3D();
        t3dKnot.set(pos5);
        TransformGroup k5TransformGroup = new TransformGroup(t3dKnot);
        sceneTransformGroup.addChild(k5TransformGroup);

        // Create spheres for each knot point's transform group
        ColoringAttributes sphereColorAttr = new ColoringAttributes();
        sphereColorAttr.setColor(sphereColor);
        Appearance sphereAppearance = new Appearance();
        sphereAppearance.setColoringAttributes(sphereColorAttr);
        k0TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));
        k1TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));
        k2TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));
        k3TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));
        k4TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));
        k5TransformGroup.addChild(new Sphere(0.10f, sphereAppearance));

        return root;
    }

    /*
     * This sets up the key frame data for the spline interpolator. Each knot
     * point has a scale and rotation component specified. The second argument
     * to KBKeyFrame (in this case 0) tells the interpolator that this is to be
     * interpolated using splines. The last three arguments to KBKeyFrame are
     * Tension, Continuity, and Bias components for each key frame.
     */
    private void setupSplineKeyFrames() {
        // Prepare spline keyframe data
        Point3f p = new Point3f(pos0); // position
        float head = (float) Math.PI / 2.0f; // heading
        float pitch = 0.0f; // pitch
        float bank = 0.0f; // bank
        Point3f s = new Point3f(1.0f, 1.0f, 1.0f); // uniform scale
        splineKeyFrames[0] = new KBKeyFrame(0.0f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos1);
        head = 0.0f; // heading
        pitch = 0.0f; // pitch
        bank = (float) -Math.PI / 2.0f; // bank
        s = new Point3f(1.0f, 1.0f, 1.0f); // uniform scale
        splineKeyFrames[1] = new KBKeyFrame(0.2f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos2);
        head = 0.0f; // heading
        pitch = 0.0f; // pitch
        bank = 0.0f; // bank
        s = new Point3f(0.7f, 0.7f, 0.7f); // uniform scale
        splineKeyFrames[2] = new KBKeyFrame(0.4f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos3);
        head = (float) Math.PI / 2.0f; // heading
        pitch = 0.0f; // pitch
        bank = (float) Math.PI / 2.0f; // bank
        s = new Point3f(0.5f, 0.5f, 0.5f); // uniform scale
        splineKeyFrames[3] = new KBKeyFrame(0.6f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos4);
        head = (float) -Math.PI / 2.0f; // heading
        pitch = (float) -Math.PI / 2.0f; // pitch
        bank = (float) Math.PI / 2.0f; // bank
        s = new Point3f(0.4f, 0.4f, 0.4f); // uniform scale
        splineKeyFrames[4] = new KBKeyFrame(0.8f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos5);
        head = 0.0f; // heading
        pitch = 0.0f; // pitch
        bank = 0.0f; // bank
        s = new Point3f(1.0f, 1.0f, 1.0f); // uniform scale
        splineKeyFrames[5] = new KBKeyFrame(1.0f, 0, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);
    }

    /*
     * This sets up the key frame data for the linear interpolator. Each knot
     * point has a scale and rotation component specified. The second argument
     * to KBKeyFrame (in this case 1) tells the interpolator that this is to be
     * interpolated linearly. The last three arguments to TCBKeyFrame are
     * Tension, Continuity, and Bias components for each key frame.
     */
    private void setupLinearKeyFrames() {
        // Prepare linear keyframe data
        Point3f p = new Point3f(pos0);
        float head = 0.0f; // heading
        float pitch = 0.0f; // pitch
        float bank = 0.0f; // bank
        Point3f s = new Point3f(1.0f, 1.0f, 1.0f); // uniform scale
        linearKeyFrames[0] = new KBKeyFrame(0.0f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos1);
        linearKeyFrames[1] = new KBKeyFrame(0.2f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos2);
        linearKeyFrames[2] = new KBKeyFrame(0.4f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos3);
        linearKeyFrames[3] = new KBKeyFrame(0.6f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos4);
        linearKeyFrames[4] = new KBKeyFrame(0.8f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);

        p = new Point3f(pos5);
        linearKeyFrames[5] = new KBKeyFrame(1.0f, 1, p, head, pitch, bank, s, 0.0f, 0.0f, 0.0f);
    }

    /*
     * This sets up alpha for the interpolator
     */
    private void setupAnimationData() {
        yAxis = new Transform3D();
        animAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0, duration, 0, 0, 0, 0, 0);
    }

    /*
     * create a spline and a linear interpolator, but we will activate only one
     * in startInterpolator()
     */
    private void createInterpolators() {

        behaviorBranch = new BranchGroup();

        // create spline interpolator
        splineInterpolator = new KBRotPosScaleSplinePathInterpolator(animAlpha, objTransformGroup, yAxis,
                splineKeyFrames);
        splineInterpolator.setSchedulingBounds(bounds);
        behaviorBranch.addChild(splineInterpolator);

        // create linear interpolator
        linearInterpolator = new KBRotPosScaleSplinePathInterpolator(animAlpha, objTransformGroup, yAxis,
                linearKeyFrames);
        linearInterpolator.setSchedulingBounds(bounds);
        behaviorBranch.addChild(linearInterpolator);
        objTransformGroup.addChild(behaviorBranch);

    }

    /*
     * This activates one of the interpolators depending on the state of the
     * linear boolean flag which may be toggled by the user using the choice
     * menu.
     */
    public void startInterpolator() {
        if (animationOn) {
            if (linear) {
                splineInterpolator.setEnable(false);
                linearInterpolator.setEnable(true);
            } else {
                linearInterpolator.setEnable(false);
                splineInterpolator.setEnable(true);
            }
        }
    }

    /*
     * Toggle animation
     */
    public void actionPerformed(ActionEvent event) {
        Object source = event.getSource();
        if (source == animateButton) {
            try {
                // toggle animation
                if (animationOn) {
                    animationOn = false;
                    splineInterpolator.setEnable(false);
                    linearInterpolator.setEnable(false);
                    animateButton.setLabel("Start Animation");
                } else {
                    animationOn = true;
                    startInterpolator();
                    animateButton.setLabel("Stop Animation");
                }
            } catch (Exception e) {
                System.err.println("Exception " + e);
            }
        }
    }

    /*
     * Toggle the interpolators
     */
    public void itemStateChanged(ItemEvent event) {
        Object source = event.getSource();
        ItemSelectable ie = event.getItemSelectable();
        if (source == interpChoice) {
            try {
                if (ie.getSelectedObjects()[0] == "Spline") {
                    linear = false;
                }
                if (ie.getSelectedObjects()[0] == "Linear") {
                    linear = true;
                }
                startInterpolator();
            } catch (Exception e) {
                System.err.println("Exception " + e);
            }
        }
    }

    /*
     * Adjust the speed of the animations
     */
    public void adjustmentValueChanged(AdjustmentEvent e) {
        int value = e.getValue();
        duration = 6000 - (500 * value);
        animAlpha.setIncreasingAlphaDuration(duration);
    }

    public static void main(String[] args) {
        Frame frame = new MainFrame(new SplineAnim(), 500, 600);
    }
}