package textureSync;

import java.applet.Applet;
import java.awt.event.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.color.*;
import javax.swing.*;
import javax.swing.event.*;

public class TextureSync extends Applet {
    double alpha, prevAlpha;
    double dAlpha = 0;
    int w = 256, h = 256;
    DrawingTexture tex;
    Appearance appearance;
    public boolean sync = false;
    final TransformGroup TG = new TransformGroup();{
        TG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE );
    }
    
    public BranchGroup createSceneGraph() {
        tex = new DrawingTexture( w, h );
        
        appearance = new Appearance();
        appearance.setTexture( tex );
        
        BranchGroup objRoot = new BranchGroup();
        Cylinder c = new Cylinder( 0.5f, 1f, Primitive.GENERATE_TEXTURE_COORDS, appearance );
        objRoot.addChild( TG );
        TG.addChild( c );
        Behavior animate = new Behavior(){
            WakeupCondition wakeup = new WakeupOnElapsedFrames( 0 );
            public void initialize(){
                wakeupOn( wakeup );
            }
            public void processStimulus( java.util.Enumeration criteria ){
                change();
                wakeupOn( wakeup );
            }
        };
        animate.setSchedulingBounds( new BoundingSphere() );
        objRoot.addChild( animate );
        objRoot.compile();
        return objRoot;
    }
    public TextureSync() {
        setLayout(new BorderLayout());
        final JLabel dAlphaLabel = new JLabel( "0 degrees per frame", SwingConstants.CENTER );
        final JSlider slider = new JSlider( 0, 360, 0 );
        JPanel northPanel = new JPanel();
        northPanel.setLayout( new GridLayout( 2, 1 ) );
        slider.addChangeListener( new ChangeListener(){
            public void stateChanged( ChangeEvent e ){
                dAlpha = slider.getValue()/360.0*Math.PI*2;
                dAlphaLabel.setText( String.valueOf( slider.getValue() ) + " degrees per frame" );
            }
        });
        add( "North", northPanel );
        northPanel.add( slider );
        northPanel.add( dAlphaLabel );
        Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
        add("Center", c);
        final JButton button = new JButton( sync ? "texture update delayed by one frame" : "this is the default" );
        button.addActionListener( new ActionListener(){
            public void actionPerformed( ActionEvent e ){
                sync = ! sync;
                button. setText( String.valueOf( sync ? "texture update delayed by one frame" : "this is the default" ) );
            }
        });
        add( "South", button );
        BranchGroup scene = createSceneGraph();
        SimpleUniverse u = new SimpleUniverse(c);
        u.getViewingPlatform().setNominalViewingTransform();
        u.addBranchGraph(scene);
    }
    public void change(){
        prevAlpha = alpha;
        alpha += dAlpha;
        if( alpha > Math.PI*2 ){
            alpha -= Math.PI*2;
        }
        updateTransform( alpha );
        updateTexture( sync ? prevAlpha : alpha );
    }
    public void updateTransform( double a ){
        Transform3D T = new Transform3D();
        T.rotY( a );
        TG.setTransform( T );
    }
    public void updateTexture( double a ){
        Graphics g = tex.getGraphics();
        g.setColor( Color.yellow );
        g.fillRect( 0, 0, w, h );
        g.setColor( Color.blue );
        int left = (int)( w - ( a*w )/( Math.PI*2 ) + w/2 - 5 );
        int top = h/2 - 15;
        g.fillRect( left, top, 10, 30 );
        g.fillRect( left - w, top, 10, 30 );
        g.fillRect( left + w, top, 10, 30 );
        tex.update();
    }
    public static void main(String[] args) {
        new MainFrame(new TextureSync(), 256, 256);
    }
    
    static class DrawingTexture extends Texture2D{
        private ImageComponent2D frontImcomp, backImcomp;
        private BufferedImage frontImage, backImage;
        
        public DrawingTexture( int w, int h ){
            //w : width
            //h : height
            super( Texture.BASE_LEVEL, Texture.RGB, w, h );
            setCapability( Texture.ALLOW_IMAGE_WRITE );
            
            frontImage = createBufferedImage( w, h );
            frontImcomp = new ImageComponent2D( ImageComponent.FORMAT_RGB,      w, h, true, true );
            frontImcomp.setCapability( ImageComponent.ALLOW_IMAGE_READ );
            frontImcomp.set( frontImage );
            
            backImage = createBufferedImage( w, h );
            backImcomp = new ImageComponent2D( ImageComponent.FORMAT_RGB, w,   h, true, true );
            backImcomp.setCapability( ImageComponent.ALLOW_IMAGE_READ );
            backImcomp.set( frontImage );
            
            setImage( 0, frontImcomp );
        }
        public static BufferedImage createBufferedImage( int w, int h ){
            //utility to simplify the creation of the right format image
            //w : width
            //h : height
            BufferedImage bi = null;
            ColorSpace cs = ColorSpace.getInstance( ColorSpace.CS_sRGB );
            int[] nbits = {
                8,8,8
            };
            ColorModel cm = new ComponentColorModel( cs, nbits, false, false, java.awt.Transparency.OPAQUE, 0 );
            int[] bandoffset = {
                0,1,2
            };
            WritableRaster wr = java.awt.image.Raster.createInterleavedRaster( DataBuffer.TYPE_BYTE, w, h,      w*3, 3, bandoffset, null );
            bi = new BufferedImage( cm, wr, false, null );
            return bi;
        }
        public Graphics2D getGraphics(){
            /*backImage must not be referenced by an ImageComponent*/
            if( frontImcomp.getImage() == backImage || backImcomp.getImage() == backImage ){
                System.out.println( "backImage is referenced" );
            }
            if( !( frontImcomp.getImage() == frontImage && backImcomp.getImage() == frontImage ) ){
                System.out.println( "frontImage is not referenced" );
            }
            return (Graphics2D)backImage.getGraphics();
        }
        public void update(){
            backImcomp.set( backImage );
            setImage( 0, backImcomp );
            frontImcomp.set( backImage );
            BufferedImage tempImage = frontImage; frontImage = backImage; backImage = tempImage;
            ImageComponent2D tempImcomp = frontImcomp; frontImcomp = backImcomp; backImcomp = tempImcomp;
        }
    }
}
