/* This abstract class represents an Object3D which is actually composed of
   other objects.  Typically, these objects are procedurally generated,
   such as by a script. */

/* Copyright (C) 2002 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */

package artofillusion.object;

import artofillusion.*;
import artofillusion.math.*;
import java.io.*;
import java.util.*;

public abstract class ObjectCollection extends Object3D
{
  protected Vector cachedObjects;
  protected BoundingBox cachedBounds;
  protected double lastTime;
  protected CoordinateSystem lastCoords;
  protected Scene lastScene;
  protected boolean usesTime, usesCoords;
  
  public ObjectCollection()
  {
    super();
  }

  public ObjectCollection(DataInputStream in, Scene theScene) throws IOException, InvalidObjectException
  {
    super(in, theScene);
  }
  
//  /** For compatibility with TaPD. */
//  public synchronized Enumeration getObjects(ObjectInfo info,
//      boolean interactive, Scene scene)
//  {
//    return getObjects(info, interactive, (SceneRenderInfoProvider) scene);
//  }
  
  /** Get an enumeration of ObjectInfos listing the objects which this object
      is composed of.  This simply calls the protected method enumerateObjects()
      (which must be provided by subclasses), while caching objects for
      interactive previews. */
  
// TODO (MB) Parameter type should be changed back from SceneRend... to Scene
  public synchronized Enumeration getObjects(ObjectInfo info,
      boolean interactive, SceneRenderInfoProvider sceneinfo)
  {
    if (!interactive)
      return enumerateObjects(info, interactive, sceneinfo);
    if (cachedObjects == null)
      {
        cachedObjects = new Vector();
        Enumeration enum = enumerateObjects(info, interactive, sceneinfo);
        while (enum.hasMoreElements())
          cachedObjects.addElement(enum.nextElement());
      }
    return cachedObjects.elements();
  }

  /** Get an enumeration of ObjectInfos listing the objects which this object
      is composed of. */
// TODO (MB) Parameter type should be changed back from SceneRend... to Scene
  protected abstract Enumeration enumerateObjects(ObjectInfo info, boolean interactive, SceneRenderInfoProvider sceneinfo);

  /* Get a BoundingBox which just encloses the object. */

  public BoundingBox getBounds()
  {
    return cachedBounds;
  }
  
  /** Since object collections are generally procedurally generated, they may
      depend explicitly on time.  If so, this should be called with the value true. */

  public void setUsesTime(boolean b)
  {
    usesTime = b;
  }
  
  /** Since object collections are generally procedurally generated, they may
      depend explicitly on position.  If so, this should be called with the value true. */

  public void setUsesCoords(boolean b)
  {
    usesCoords = b;
  }

  /** Determine whether the object is closed. */

  public boolean isClosed()
  {
    for (int i = 0; i < cachedObjects.size(); i++)
      {
        ObjectInfo info = (ObjectInfo) cachedObjects.elementAt(i);
        if (!info.object.isClosed())
          return false;
      }
    return true;
  }

  /** Assume that a material can be set for the	object collection (though actually
      setting one may or may not have any effect). */

  public boolean canSetMaterial()
  {
    return true;
  }

  /** Get a mesh representing the union of all objects in the collection. */
  
  public RenderingMesh getRenderingMesh(double tol, boolean interactive, ObjectInfo info)
  {
    return convertToTriangleMesh(tol).getRenderingMesh(tol, interactive, info);
  }

  /** An object collection is never drawn directly.  Instead, its component objects
      are enumerated and drawn individually. */
  
  public WireframeMesh getWireframeMesh()
  {
    return new WireframeMesh(new Vec3 [0], new int [0], new int [0]);
  }

  /** For simplicity, just assume that the object can be converted approximately. */
  
  public int canConvertToTriangleMesh()
  {
    return APPROXIMATELY;   // TODO(MB) Less simple answer
  }
  
  /** Create a triangle mesh which is the union of all the objects in this collection. */

  public TriangleMesh convertToTriangleMesh(double tol)
  {
    Vector allVert = new Vector();
    Vector allFace = new Vector();
    int start = 0;
    Enumeration enum = getObjects(new ObjectInfo(this, lastCoords, ""), false, lastScene);
    while (enum.hasMoreElements())
      {
        ObjectInfo obj = (ObjectInfo) enum.nextElement();
        if (obj.object.canConvertToTriangleMesh() == CANT_CONVERT)
          continue;
        Mat4 trans = obj.coords.fromLocal();
        TriangleMesh tri = obj.object.convertToTriangleMesh(tol);
        MeshVertex vert[] = tri.getVertices();
        for (int i = 0; i < vert.length; i++)
          allVert.addElement(trans.times(vert[i].r));
        TriangleMesh.Face face[] = tri.getFaces();
        for (int i = 0; i < face.length; i++)
          allFace.addElement(new int [] {face[i].v1+start, face[i].v2+start, face[i].v3+start});
        start += vert.length;
      }
    if (allVert.size() == 0)
      allVert.addElement(new Vec3());
    Vec3 vert[] = new Vec3 [allVert.size()];
    allVert.copyInto(vert);
    int face[][] = new int [allFace.size()][];
    allFace.copyInto(face);
    TriangleMesh mesh = new TriangleMesh(vert, face);
    mesh.copyTextureAndMaterial(this);
    mesh.copyModelEvent(this);
    return mesh;
  }
  
  /** If this object explicitly references time or position, the cached objects and
      bounding box may need to be reevaluated. */

  public void sceneChanged(ObjectInfo info, Scene scene)
  {
    if (cachedBounds == null || (usesTime && lastTime != scene.getTime()) ||
        (usesCoords && !lastCoords.equals(info.coords)))
      {
        lastScene = scene;
        lastTime = scene.getTime();
        lastCoords = info.coords.duplicate();
        cachedObjects = null;
        Enumeration enum = getObjects(info, true, scene);
        if (!enum.hasMoreElements())
          cachedBounds = new BoundingBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
        else
          while (enum.hasMoreElements())
            {
              ObjectInfo obj = (ObjectInfo) enum.nextElement();
              BoundingBox bounds = obj.getBounds();
              bounds = bounds.transformAndOutset(obj.coords.fromLocal());
              if (cachedBounds == null)
                cachedBounds = bounds;
              else
                cachedBounds = cachedBounds.merge(bounds);
            }
        info.clearCachedMeshes();
      }
  }
}