/*
 * $RCSfile: Background.java,v $
 *
 *      @(#)Background.java 1.33 99/02/20 16:05:38
 *
 * Copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * 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.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 *
 * $Revision: 1.2 $
 * $Date: 2005/02/03 23:06:51 $
 * $State: Exp $
 */
/*
 *
 * @Author: Rick Goldberg
 * @Author: Doug Gehringer
 */
package org.jdesktop.j3d.loaders.vrml97.impl;
import java.awt.*;
import java.awt.image.*;
//import javax.media.j3d.BoundingSphere;
//import javax.media.j3d.Bounds;
//import javax.media.j3d.BranchGroup;
//import javax.media.j3d.ImageComponent;
//import javax.media.j3d.ImageComponent2D;
//import javax.media.j3d.PolygonAttributes;
//import javax.media.j3d.Texture;
//import javax.media.j3d.Texture2D;

//import javax.vecmath.Point3d;
import javax.vecmath.*;
import javax.media.j3d.*;
import java.net.URL;
import com.sun.j3d.utils.image.TextureLoader;

/**  Description of the Class */
public class Background extends BindableNode {

    // from BindableNode
    // eventIn  SFBool bind
    // eventOut SFTime bindTime;
    // eventOut SFBool isBound;

    // exposedField
    MFFloat groundAngle;
    MFColor groundColor;
    MFString backUrl;
    MFString bottomUrl;
    MFString frontUrl;
    MFString leftUrl;
    MFString rightUrl;
    MFString topUrl;
    MFFloat skyAngle;
    MFColor skyColor;

    // spherical mapping divisions per linear space pixel [0..63] -> [0..Pi]
    // generated by
    // for (p = 0; p<64; p ++) {
    //     theta[p] = Math.acos( 1.0 - ((double)p)/31.5) ) ;
    // }

    double thetas[] = {0.0, 0.2526477262845635, 0.3582612185126168, 0.4399759547909189, 0.509443848811893, 0.5711684985655375, 0.6274557729231908, 0.6796738189082439, 0.7287134507434055, 0.7751933733103613, 0.8195643276682608, 0.8621670552325126, 0.9032668821590636, 0.9430755091369839, 0.9817653565786227, 1.019479357663014, 1.0563378512337098, 1.092443562145639, 1.1278852827212575, 1.162740649493618, 1.1970782758519827, 1.2309594173407747, 1.2644392922379775, 1.2975681442542588, 1.3303921100256373, 1.3629539374415498, 1.3952935892191738, 1.4274487578895312, 1.4594553124539327, 1.4913476927097593, 1.5231592641704934, 1.5549226443049409, 1.5866700092848522, 1.6184333894192997, 1.6502449608800338, 1.6821373411358607, 1.714143895700262, 1.7462990643706193, 1.7786387161482433, 1.811200543564156, 1.8440245093355343, 1.8771533613518159, 1.9106332362490184, 1.9445143777378107, 1.978852004096175, 2.0137073708685356, 2.0491490914441544, 2.0852548023560833, 2.122113295926779, 2.1598272970111703, 2.198517144452809, 2.2383257714307296, 2.2794255983572804, 2.3220283259215324, 2.366399280279432, 2.412879202846388, 2.461918834681549, 2.5141368806666025, 2.5704241550242557, 2.6321488047779003, 2.7016166987988743, 2.7833314350771765, 2.8889449273052294, 3.141592653589793};

    private final static boolean GROUND = true;
    private final static boolean SKY = false;

    // This node is not added to the J3D graph directly, so implNode=null
    // When the background is bound, it's backgroundImpl is attached to
    // the browserRoot
    javax.media.j3d.BranchGroup backgroundImpl;
    javax.media.j3d.Background background;
    javax.media.j3d.Bounds bound;

    /**
     *Constructor for the Background object
     *
     *@param  loader Description of the Parameter
     */
    public Background(Loader loader) {
        super(loader, loader.getBackgroundStack());
        groundAngle = new MFFloat();
        groundColor = new MFColor();
        backUrl = new MFString();
        bottomUrl = new MFString();
        frontUrl = new MFString();
        leftUrl = new MFString();
        rightUrl = new MFString();
        topUrl = new MFString();
        skyAngle = new MFFloat();
        float[] color = new float[3];
        color[0] = 0.3f;
        color[1] = 0.3f;
        color[2] = 0.3f;
        skyColor = new MFColor(color);
        loader.addBackground(this);
        initFields();
    }

    /**
     *Constructor for the Background object
     *
     *@param  loader Description of the Parameter
     *@param  bind Description of the Parameter
     *@param  bindTime Description of the Parameter
     *@param  isBound Description of the Parameter
     *@param  groundAngle Description of the Parameter
     *@param  groundColor Description of the Parameter
     *@param  backUrl Description of the Parameter
     *@param  bottomUrl Description of the Parameter
     *@param  frontUrl Description of the Parameter
     *@param  leftUrl Description of the Parameter
     *@param  rightUrl Description of the Parameter
     *@param  topUrl Description of the Parameter
     *@param  skyAngle Description of the Parameter
     *@param  skyColor Description of the Parameter
     */
    public Background(Loader loader, SFBool bind, SFTime bindTime,
            SFBool isBound, MFFloat groundAngle, MFColor groundColor,
            MFString backUrl, MFString bottomUrl, MFString frontUrl,
            MFString leftUrl, MFString rightUrl, MFString topUrl,
            MFFloat skyAngle, MFColor skyColor) {
        super(loader, loader.getBackgroundStack(), bind, bindTime, isBound);
        this.groundAngle = groundAngle;
        this.groundColor = groundColor;
        this.backUrl = backUrl;
        this.bottomUrl = bottomUrl;
        this.frontUrl = frontUrl;
        this.leftUrl = leftUrl;
        this.rightUrl = rightUrl;
        this.topUrl = topUrl;
        this.skyAngle = skyAngle;
        this.skyColor = skyColor;
        loader.addBackground(this);
        initFields();
    }

    /**  Description of the Method */
    void initImpl() {
        //どうしてもgroundColorなどの色指定の背景と
        //backUrlなどの画像指定の背景を共存させられ
        //なかったので，画像が一枚でもあれば，そちらを
        //優先ということで処理することにする．
        //javax.media.j3d.BackgroundのAPIにある
        //「pre-tessellated onto a unit sphere」というのが
        //ポイントなのかも．

        int i=backUrl.getSize()+bottomUrl.getSize()+frontUrl.getSize()
            +leftUrl.getSize()+rightUrl.getSize()+topUrl.getSize();

        if (i==0)
            addColorBackground();
        else
            addImageBackground();

        background.setApplicationBounds(loader.infiniteBounds);
        backgroundImpl = new RGroup();
        backgroundImpl.addChild(background);

        implReady = true;
    }

    void addColorBackground() {
        if (skyAngle.mfloat.length > 0) {
            int[] bkg = createBkgGrad(skyColor, skyAngle, Background.SKY);
            if (groundAngle.mfloat.length > 0) {
                int[] grndBkg = createBkgGrad(groundColor, groundAngle, Background.GROUND);
                for (int i = 0; i < 32; i++) {
                    bkg[i + 31] = grndBkg[31 - i];
                }
            }
            Texture2D img = getImageBkg(bkg);
            background = new javax.media.j3d.Background();

            javax.media.j3d.Appearance app = new javax.media.j3d.Appearance();
            app.setTexture(img);
            PolygonAttributes pa = new PolygonAttributes();
            pa.setCullFace(javax.media.j3d.PolygonAttributes.CULL_NONE);
            pa.setBackFaceNormalFlip(true);
            app.setPolygonAttributes(pa);
            javax.media.j3d.Group sphere = new com.sun.j3d.utils.geometry.Sphere(1.0f,
                    com.sun.j3d.utils.geometry.Sphere.GENERATE_TEXTURE_COORDS, 20,
                    app);
            BranchGroup bkgGeom = new BranchGroup();
            bkgGeom.addChild(sphere);
            background.setGeometry(bkgGeom);
        }
        else {
            background = new javax.media.j3d.Background(skyColor.vals[0],
                    skyColor.vals[1], skyColor.vals[2]);
        }
    }

    void addImageBackground() {
        background = new javax.media.j3d.Background();
        BranchGroup bkgGeom = new BranchGroup();
        if (backUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d(-0.577, -0.577, -0.577); // 左下  3+-----+2
            vertices[1] = new Point3d( 0.577, -0.577, -0.577); // 右下   |     |
            vertices[2] = new Point3d( 0.577,  0.577, -0.577); // 右上   |     |
            vertices[3] = new Point3d(-0.577,  0.577, -0.577); // 左上  0+-----+1
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f); // 左下  3+-----+2
            txcoords[1] = new TexCoord2f(0.0f, 1.0f); // 右下   |     |
            txcoords[2] = new TexCoord2f(0.0f, 0.0f); // 右上   |     |
            txcoords[3] = new TexCoord2f(1.0f, 0.0f); // 左上  0+-----+1
            addImage(vertices,txcoords,backUrl,bkgGeom);
        }
        if (bottomUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d(-0.577, -0.577,  0.577);
            vertices[1] = new Point3d( 0.577, -0.577,  0.577);
            vertices[2] = new Point3d( 0.577, -0.577, -0.577);
            vertices[3] = new Point3d(-0.577, -0.577, -0.577);
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f);
            txcoords[1] = new TexCoord2f(0.0f, 1.0f);
            txcoords[2] = new TexCoord2f(0.0f, 0.0f);
            txcoords[3] = new TexCoord2f(1.0f, 0.0f);
            addImage(vertices,txcoords,bottomUrl,bkgGeom);
        }
        if (frontUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d( 0.577, -0.577,  0.577);
            vertices[1] = new Point3d(-0.577, -0.577,  0.577);
            vertices[2] = new Point3d(-0.577,  0.577,  0.577);
            vertices[3] = new Point3d( 0.577,  0.577,  0.577);
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f);
            txcoords[1] = new TexCoord2f(0.0f, 1.0f);
            txcoords[2] = new TexCoord2f(0.0f, 0.0f);
            txcoords[3] = new TexCoord2f(1.0f, 0.0f);
            addImage(vertices,txcoords,frontUrl,bkgGeom);
        }
        if (leftUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d(-0.577, -0.577,  0.577);
            vertices[1] = new Point3d(-0.577, -0.577, -0.577);
            vertices[2] = new Point3d(-0.577,  0.577, -0.577);
            vertices[3] = new Point3d(-0.577,  0.577,  0.577);
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f);
            txcoords[1] = new TexCoord2f(0.0f, 1.0f);
            txcoords[2] = new TexCoord2f(0.0f, 0.0f);
            txcoords[3] = new TexCoord2f(1.0f, 0.0f);
            addImage(vertices,txcoords,leftUrl,bkgGeom);
        }
        if (rightUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d( 0.577, -0.577, -0.577);
            vertices[1] = new Point3d( 0.577, -0.577,  0.577);
            vertices[2] = new Point3d( 0.577,  0.577,  0.577);
            vertices[3] = new Point3d( 0.577,  0.577, -0.577);
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f);
            txcoords[1] = new TexCoord2f(0.0f, 1.0f);
            txcoords[2] = new TexCoord2f(0.0f, 0.0f);
            txcoords[3] = new TexCoord2f(1.0f, 0.0f);
            addImage(vertices,txcoords,rightUrl,bkgGeom);
        }
        if (topUrl.getSize()>0) {
            Point3d[] vertices = new Point3d[4];
            vertices[0] = new Point3d( 0.577,  0.577,  0.577);
            vertices[1] = new Point3d(-0.577,  0.577,  0.577);
            vertices[2] = new Point3d(-0.577,  0.577, -0.577);
            vertices[3] = new Point3d( 0.577,  0.577, -0.577);
            TexCoord2f[] txcoords = new TexCoord2f[4];
            txcoords[0] = new TexCoord2f(1.0f, 1.0f);
            txcoords[1] = new TexCoord2f(0.0f, 1.0f);
            txcoords[2] = new TexCoord2f(0.0f, 0.0f);
            txcoords[3] = new TexCoord2f(1.0f, 0.0f);
            addImage(vertices,txcoords,topUrl,bkgGeom);
        }
        background.setGeometry(bkgGeom);
    }

    void addImage(Point3d[] vertices,TexCoord2f[] txcoords,MFString url,BranchGroup bkgGeom) {
        QuadArray geom = new QuadArray(vertices.length,
            GeometryArray.COORDINATES |
            GeometryArray.NORMALS |
            GeometryArray.TEXTURE_COORDINATE_2);
        geom.setCoordinates(0, vertices);
        geom.setTextureCoordinates(0, 0, txcoords);

        javax.media.j3d.Appearance app = new javax.media.j3d.Appearance();
        URL bgImage = null;
        try {
            bgImage = loader.stringToURL(url.get1Value(0));
        }catch(Exception e) {
            e.printStackTrace();
        }
        TextureLoader tex = new TextureLoader(bgImage,"RGB",
            TextureLoader.BY_REFERENCE | TextureLoader.Y_UP,
            null);
        if (tex!=null)
            app.setTexture(tex.getTexture());

        Shape3D shape = new Shape3D(geom,app);

        bkgGeom.addChild(shape);
    }

    // create full "external sky" sphere gradient map to a 64x1 "grid" of pixels
    // knowing theta and color samples. these will be used to create a texture to
    // apply to the infinite background sphere.

    /**
     *  Description of the Method
     *
     *@param  colors Description of the Parameter
     *@param  angles Description of the Parameter
     *@param  dome Description of the Parameter
     *@return  Description of the Return Value
     *@exception  a3vrml.InvalidVRMLSyntaxException Description of the Exception
     */
    int[] createBkgGrad(MFColor colors, MFFloat angles, boolean dome)
             throws a3vrml.InvalidVRMLSyntaxException {
        float[] clrs0 = new float[3];
        float[] clrs1 = new float[3];
        int c0dex = 0;
        int c1dex = 1;
        int maxdex = angles.mfloat.length;
        int mindex = 0;
        int span = dome ? 32 : 64;
        int[] gradients = new int[span];
//System.out.println(maxdex);

        if (maxdex != (colors.vals.length / 3) - 1) {
            throw new a3vrml.InvalidVRMLSyntaxException(
                    "Background: there shall be one less angle than colors"
                    );
        }

        colors.get1Value(0, clrs0);
        gradients[0] = ((int) (clrs0[0] * 255.0f)) << 24 |
                ((int) (clrs0[1] * 255.0f)) << 16 |
                ((int) (clrs0[2] * 255.0f)) << 8 |
                (int) 0xff;
        for (int i = 1; i < span; i++) {
            if (c1dex <= maxdex) {
                int r;
                int g;
                int b;
                float i0;
                float i1;
//System.out.println("c0dex "+c0dex+ " c1dex "+c1dex);
                if (c1dex != maxdex) {
//System.out.println( i+ " " +thetas[i] + " >= " + angles.get1Value(c1dex));
                    if (thetas[i] >= angles.get1Value(c1dex)) {
                        c0dex = c1dex;
                        c1dex++;
                    }
                }
                colors.get1Value(c0dex, clrs0);
                colors.get1Value(c1dex, clrs1);
                i0 = (1.0f - (float) Math.cos(thetas[i])) * 0.5f;
                i1 = 1.0f - i0;
                r = (int) ((clrs0[0] * i0 + clrs1[0] * i1) * 255.0f);
                g = (int) ((clrs0[1] * i0 + clrs1[1] * i1) * 255.0f);
                b = (int) ((clrs0[2] * i0 + clrs1[2] * i1) * 255.0f);
//System.out.println("r "+r+" g "+g+" b "+b);
                gradients[i] = (r << 24) | (g << 16) | (b << 8) | (int) 0xff;
            }
            else {
                gradients[i] = gradients[i - 1];
            }
//System.out.println(gradients[i]);

        }

        return gradients;
    }


    /**
     *  Gets the imageBkg attribute of the Background object
     *
     *@param  gradients Description of the Parameter
     *@return  The imageBkg value
     */
    Texture2D getImageBkg(int[] gradients) {
        ImageComponent2D component2D;
        BufferedImage bufIm;
        ColorModel cm = new DirectColorModel(32, 0xff000000, 0xff0000, 0xff00, 0xff);
        WritableRaster raster = cm.createCompatibleWritableRaster(1, 64);
        int[] data = ((DataBufferInt) raster.getDataBuffer()).getData();
        System.arraycopy(gradients, 0, data, 0, gradients.length);
        bufIm = new BufferedImage(cm, raster, false, null);
        component2D = new ImageComponent2D(ImageComponent.FORMAT_RGBA, bufIm);

        Texture2D tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGB, 1, 64);
        // do these need to be for at infinity?
        tex.setMinFilter(Texture.BASE_LEVEL_LINEAR);
        tex.setMagFilter(Texture.BASE_LEVEL_LINEAR);
        tex.setImage(0, component2D);
        tex.setEnable(true);
        return tex;
    }


    /**
     *  Gets the backgroundImpl attribute of the Background object
     *
     *@return  The backgroundImpl value
     */
    public javax.media.j3d.BranchGroup getBackgroundImpl() {
        return backgroundImpl;
    }


    /**
     *  Description of the Method
     *
     *@param  eventInName Description of the Parameter
     *@param  time Description of the Parameter
     */
    public void notifyMethod(String eventInName, double time) {
        if (eventInName.equals("bind")) {
            super.notifyMethod("bind", time);
        }
        else if (eventInName.equals("skyColor")) {
            if (skyColor.vals.length == 3) {
                background.setColor(skyColor.vals[0], skyColor.vals[1],
                        skyColor.vals[2]);
            }
            else {
                System.err.println("Background: unexpected number of colors");
            }
        }
        else if (eventInName.equals("route_skyColor")) {
            background.setCapability(
                    javax.media.j3d.Background.ALLOW_COLOR_WRITE);
        }
        else if (eventInName.equals("route_bind")) {
            // no-op
        }
        else {
            System.err.println("Background: unexpected notify " + eventInName);
        }
    }

    /**
     *  Gets the type attribute of the Background object
     *
     *@return  The type value
     */
    public String getType() {
        return "Background";
    }

    /**
     *  Description of the Method
     *
     *@return  Description of the Return Value
     */
    public Object clone() {
        return new Background(loader,
                (SFBool) bind.clone(),
                (SFTime) bindTime.clone(),
                (SFBool) isBound.clone(),
                (MFFloat) groundAngle.clone(),
                (MFColor) groundColor.clone(),
                (MFString) backUrl.clone(),
                (MFString) bottomUrl.clone(),
                (MFString) frontUrl.clone(),
                (MFString) leftUrl.clone(),
                (MFString) rightUrl.clone(),
                (MFString) topUrl.clone(),
                (MFFloat) skyAngle.clone(),
                (MFColor) skyColor.clone());
    }

    /**  Description of the Method */
    void initFields() {
        initBindableFields();
        groundAngle.init(this, FieldSpec, Field.EXPOSED_FIELD, "groundAngle");
        groundColor.init(this, FieldSpec, Field.EXPOSED_FIELD, "groundColor");
        backUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "backUrl");
        bottomUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "bottomUrl");
        frontUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "frontUrl");
        leftUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "leftUrl");
        rightUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "rightUrl");
        topUrl.init(this, FieldSpec, Field.EXPOSED_FIELD, "topUrl");
        skyAngle.init(this, FieldSpec, Field.EXPOSED_FIELD, "skyAngle");
        skyColor.init(this, FieldSpec, Field.EXPOSED_FIELD, "skyColor");

    }

    /**
     *  Description of the Method
     *
     *@return  Description of the Return Value
     */
    public a3vrml.BaseNode wrap() {
        return new org.jdesktop.j3d.loaders.vrml97.node.Background(this);
    }
}

