/*
A not so simple example simulating a swarm of bees (in fact:
little spheres) which follow the queen (a bigger sphere).
Unfortunately I have no knowledge about the behaviour of bees
therefore it doesn't look very realistic.

The motion of the queen is done by conventional animation
tracks.
*/


  /** Creates the initial state from information in the current scene
    and/or displays a dialog to allow configuring the script.

    @return the initial state or null if the state could not be created.
            in the latter case the script should have displayed a dialog
            to inform the user about the reason of the failure.
  */
  public Serializable createInitialState(window)
  {
    scene = window.getScene();
    result = new Vector();   // Consisting of: time of state, positions of the bees, motion vectors of the bees
    idx = -1;
    result.add(scene.getTime());
    nullmove = new Vec3();

    while (true)
    {
      idx = scene.indexOf("Bee", idx + 1);
      if (idx == -1)
        break;

      beepos = new Vec3(scene.getObject(idx).getCoordinates().getOrigin());
      result.add(beepos);
      result.add(nullmove);
    }

    return result;
  }


  public void applyTime(obj, time)
  {
    orig = obj.coords.getOrigin();
    orig.set(0.0, 0.0, 0.0);
    obj.coords.setOrigin(orig);

    obj.clearDistortion();
    for (int j = obj.tracks.length-1; j >= 0; j--)
      if (obj.tracks[j].isEnabled())
        obj.tracks[j].apply(time);
  }

  public double clip(value, min, max)
  {
    if (value < min)
      return min;
    if (value > max)
      return max;
    return value;
 }


  /** Take <CODE>initial</CODE> state and one <CODE>previous</CODE> state
    and create a new state which is appropriate if time goes the
    <CODE>timestep</CODE> interval further.

    <P>The <CODE>timestep</CODE> may be negative iff
    {@link #isBackwardTimeAllowed()} returns
    true for the same combination of parameters.<BR>
    <CODE>initial</CODE> and <CODE>previous</CODE> may be identical (if this
    is the first step).<BR>

    <P>The <CODE>initial</CODE> state is given because it may contain
    additional configuration data which does not need to be stored in
    every state object.

    <P>The current implementation always uses a timestep of 1/fps or
    -1/fps where fps are the frames per second of the current scene. (TODO(MB)!)

    @return a new (or recycled) state object or null if no state could be
            created.
  */
  public Object executeTimeStep(initial, previous, timestep, scene)
  {
    result = new Vector();
    newtime = previous.get(0) + timestep;
    result.add(newtime);

    queencopy = scene.getObject("Queen").duplicate(new NullObject());

    applyTime(queencopy, newtime);
    queenpos = queencopy.getCoordinates().getOrigin();

    // Calculate swarm position
    beecount = (previous.size()-1)/2;
    swarmpos = new Vec3();

    for (i = 0; i < beecount; ++i)
      swarmpos.add(previous.get(i*2 + 1));

    swarmpos.scale(1.0/beecount);

    for (i = 0; i < beecount; ++i)
    {  // For each bee:

      beepos = previous.get(i*2 + 1);
      oldbeemove = previous.get(i*2 + 2);
      beedist = queenpos.distance(beepos);
      swarmdist = swarmpos.distance(beepos);

      // Move directly towards queen
      beemove = queenpos.minus(beepos);
      beemove.scale(DIRACC * beedist * beedist);

      // Move directly towards swarm(or away from it)
      beemove.add((swarmpos.minus(beepos)).times(SWARMACC * clip(1/swarmdist, 0, 1)));

      // Random move
      beemove.add((new Vec3(Math.random()-.5, Math.random()-.5, Math.random()-.5)).times(RANDOMWEIGHT));

      // "Inertia"
      beemove.scale(1-OLDMOVEWEIGHT);
      beemove.add(oldbeemove.times(OLDMOVEWEIGHT));

      beemove.scale(timestep);

      result.add(beepos.plus(beemove));
      result.add(beemove);
    }

    return result;
  }


  /** Returns true iff a new state can be created from an old state which
    lies in the future of the new one.<BR>
    This is only needed in interactive mode. During animation rendering
    time always goes forward.<BR>
    The parameters have the same meaning as for {@link #executeTimeStep()}.
  */
  public boolean isBackwardTimeAllowed(initial, previous, timestep, scene)
  { return false; }


  /** Returns the minimal timestep allowed for these states.
    The parameters have the same meaning as for {@link #executeTimeStep()}.
    <P>Ignored by current implementation!  (TODO(MB)!)
  */
  public double getMinimalTimeStep(initial, previous, scene)
  {return 0;}


  /** Returns the maximal timestep allowed for these states.
    The parameters have the same meaning as for {@link #executeTimeStep()}.
    <P>Ignored by current implementation!  (TODO(MB)!)
  */
  public double getMaximalTimeStep(initial, previous, scene)
  {return -1;}


  /** Realizes the state, meaning to set the scene according to the state
    information in <CODE>state</CODE> and <CODE>initial</CODE>.
  */
  public void realizeState(initial, state, scene)
  {
    int idx = -1;

    for (int i = 0; i < (state.size()-1)/2; ++i)
    {  // For each bee:

      beepos = state.get(i*2 + 1);

      idx = scene.indexOf("Bee", idx + 1);
      if (idx == -1)
        break;

      scene.getObject(idx).getCoordinates().setOrigin(new Vec3(beepos));
    }
  }


  public double DIRACC = .5; //.5;
  public double SWARMACC = -2;

  public double OLDMOVEWEIGHT = 0.4;
  public double RANDOMWEIGHT = .9;

