/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * 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.
 *
 * * Redistributions 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 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jme3test.scene.instancing;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.Node;
import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.scene.instancing.InstancedNode;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;

public class TestInstanceNode extends SimpleApplication  {

    private Mesh mesh1;
    private Mesh mesh2;
    private final Material[] materials = new Material[6];
    private Node instancedNode;
    private float time = 0;
    private boolean INSTANCING = true;
    
    public static void main(String[] args){
        TestInstanceNode app = new TestInstanceNode();
        AppSettings settings = new AppSettings(true);
        settings.setVSync(false);
        app.setSettings(settings);
        app.start();
    }

    private Geometry createInstance(float x, float z) {
        Mesh mesh; 
        if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
        else mesh = mesh1;
        Geometry geometry = new Geometry("randomGeom", mesh);
        geometry.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);
        geometry.setLocalTranslation(x, 0, z);
        return geometry;
    }
    
    @Override
    public void simpleInitApp() {
        mesh1 = new Sphere(13, 13, 0.4f, true, false);
        mesh2 = new Box(0.4f, 0.4f, 0.4f);
        
        materials[0] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[0].setBoolean("UseInstancing", INSTANCING);
        materials[0].setColor("Color", ColorRGBA.Red);
        
        materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[1].setBoolean("UseInstancing", INSTANCING);
        materials[1].setColor("Color", ColorRGBA.Green);
        
        materials[2] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[2].setBoolean("UseInstancing", INSTANCING);
        materials[2].setColor("Color", ColorRGBA.Blue);
        
        materials[3] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[3].setBoolean("UseInstancing", INSTANCING);
        materials[3].setColor("Color", ColorRGBA.Cyan);
        
        materials[4] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[4].setBoolean("UseInstancing", INSTANCING);
        materials[4].setColor("Color", ColorRGBA.Magenta);
        
        materials[5] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        materials[5].setBoolean("UseInstancing", INSTANCING);
        materials[5].setColor("Color", ColorRGBA.Yellow);
       
        instancedNode = new InstancedNode("instanced_node");
        
        rootNode.attachChild(instancedNode);
        
        int extent = 30;
        
        for (int y = -extent; y < extent; y++) {
            for (int x = -extent; x < extent; x++) {
                Geometry instance = createInstance(x, y);
                
                float height = (smoothstep(0, 1, FastMath.nextRandomFloat()) * 2.5f) - 1.25f;
                instance.setUserData("height", height);
                instance.setUserData("dir", 1f);
                
                instancedNode.attachChild(instance);
            }
        }
        
        if (INSTANCING) {
            ((InstancedNode)instancedNode).instance();
        }
        
        //instancedNode = (InstancedNode) instancedNode.clone();
        //instancedNode.move(0, 5, 0);
        //rootNode.attachChild(instancedNode);
        
        cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
        cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
        flyCam.setMoveSpeed(15);
        flyCam.setEnabled(false);
    }
    
    private float smoothstep(float edge0, float edge1, float x) {
        // Scale, bias and saturate x to 0..1 range
        x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
        // Evaluate polynomial
        return x * x * (3 - 2 * x);
    }
    
    
    @Override
    public void simpleUpdate(float tpf) {
        time += tpf;

        if (time > 1f) {
            time = 0f;
            
            for (Spatial instance : instancedNode.getChildren()) {
                if (!(instance instanceof InstancedGeometry)) {
                    Geometry geom = (Geometry) instance;
                    geom.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);

                    Mesh mesh; 
                    if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
                    else mesh = mesh1;
                    geom.setMesh(mesh);
                }
            }
        }
        
        for (Spatial child : instancedNode.getChildren()) {
            if (!(child instanceof InstancedGeometry)) {
                float val = ((Float)child.getUserData("height")).floatValue();
                float dir = ((Float)child.getUserData("dir")).floatValue();

                val += (dir + ((FastMath.nextRandomFloat() * 0.5f) - 0.25f)) * tpf;

                if (val > 1f) {
                    val = 1f;
                    dir = -dir;
                } else if (val < 0f) {
                    val = 0f;
                    dir = -dir;
                }

                Vector3f translation = child.getLocalTranslation();
                translation.y = (smoothstep(0, 1, val) * 2.5f) - 1.25f;

                child.setUserData("height", val);
                child.setUserData("dir", dir);

                child.setLocalTranslation(translation);
            }
        }
    }
}
