text: /*=========================================================================
  
    Program:   Visualization Toolkit
    Module:    vtkLODProp3D.cxx
  
    Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
    All rights reserved.
    See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
  
       This software is distributed WITHOUT ANY WARRANTY; without even
       the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
       PURPOSE.  See the above copyright notice for more information.
  
  =========================================================================*/
  #include "vtkLODProp3D.h"
  
  #include "vtkAbstractVolumeMapper.h"
  #include "vtkActor.h"
  #include "vtkCommand.h"
  #include "vtkImageMapper3D.h"
  #include "vtkImageSlice.h"
  #include "vtkLinearTransform.h"
  #include "vtkMapper.h"
  #include "vtkMatrix4x4.h"
  #include "vtkObjectFactory.h"
  #include "vtkPropCollection.h"
  #include "vtkVolume.h"
  
  #include <cmath>
  
  vtkStandardNewMacro(vtkLODProp3D);
  
  #define VTK_INDEX_NOT_IN_USE (-1)
  
  #define VTK_INVALID_LOD_INDEX (-2)
  
  #define VTK_LOD_ACTOR_TYPE 1
  #define VTK_LOD_VOLUME_TYPE 2
  #define VTK_LOD_IMAGE_TYPE 3
  
  class vtkLODProp3DCallback : public vtkCommand
  {
  public:
    // generic new method
    static vtkLODProp3DCallback* New() { return new vtkLODProp3DCallback; }
  
    // the execute
    void Execute(vtkObject* caller, unsigned long event, void* vtkNotUsed(v)) override
    {
      vtkProp* po = vtkProp::SafeDownCast(caller);
      if (event == vtkCommand::PickEvent && po)
      {
        this->Self->InvokeEvent(vtkCommand::PickEvent, nullptr);
      }
    }
  
    // some ivars that should be set
    vtkLODProp3D* Self;
  };
  
  // Construct a new vtkLODProp3D. Automatic LOD selection is on, there are
  // no LODs.
  vtkLODProp3D::vtkLODProp3D()
  {
    this->NumberOfEntries = 0;
    this->NumberOfLODs = 0;
    this->LODs = nullptr;
    this->CurrentIndex = 1000;
    this->AutomaticLODSelection = 1;
    this->SelectedLODID = 1000;
    this->SelectedLODIndex = -1;
    this->SelectedPickLODID = 1000;
    this->AutomaticPickLODSelection = 1;
    this->PickCallback = vtkLODProp3DCallback::New();
    this->PickCallback->Self = this;
  }
  
  // Destruct the vtkLODProp3D. Delete the vtkProp3Ds that were created
  // for the LODs. Delete the array of LODs.
  vtkLODProp3D::~vtkLODProp3D()
  {
    // Delete the vtkProp3D objects that were created
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE)
      {
        this->LODs[i].Prop3D->RemoveConsumer(this);
        this->LODs[i].Prop3D->RemoveObserver(this->PickCallback);
        this->LODs[i].Prop3D->Delete();
      }
    }
  
    // Delete the array of LODs
    if (this->NumberOfEntries > 0)
    {
      delete[] this->LODs;
    }
  
    this->PickCallback->Delete();
  }
  
  int vtkLODProp3D::ConvertIDToIndex(int id)
  {
    int index = 0;
  
    while (index < this->NumberOfEntries && this->LODs[index].ID != id)
    {
      index++;
    }
    if (index == this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Could not locate ID: " << id);
      index = VTK_INVALID_LOD_INDEX;
    }
  
    return index;
  }
  
  // Get the next available entry index
  int vtkLODProp3D::GetNextEntryIndex()
  {
    // Search for an available index
    int index = 0;
    while (index < this->NumberOfEntries && this->LODs[index].ID != VTK_INDEX_NOT_IN_USE)
    {
      index++;
    }
  
    // If an available index was not found, we need more entries
    if (index >= this->NumberOfEntries)
    {
      // If we have no entries, create 10. If we already have some, create
      // twice as many as we already have.
      int amount = (this->NumberOfEntries) ? (this->NumberOfEntries * 2) : (10);
  
      // Make the new array
      vtkLODProp3DEntry* newLODs = new vtkLODProp3DEntry[amount];
  
      int i;
      // Copy the old entries into the new array
      for (i = 0; i < this->NumberOfEntries; i++)
      {
        newLODs[i].Prop3D = this->LODs[i].Prop3D;
        newLODs[i].Prop3DType = this->LODs[i].Prop3DType;
        newLODs[i].ID = this->LODs[i].ID;
        newLODs[i].EstimatedTime = this->LODs[i].EstimatedTime;
        newLODs[i].Level = this->LODs[i].Level;
        newLODs[i].State = this->LODs[i].State;
      }
  
      // This is the index that we will return - one past the old entries
      index = i;
  
      // Initialize the new entries to default values
      for (; i < amount; i++)
      {
        newLODs[i].Prop3D = nullptr;
        newLODs[i].ID = VTK_INDEX_NOT_IN_USE;
      }
  
      // Delete the old array and set the pointer to the new one
      delete[] this->LODs;
      this->LODs = newLODs;
  
      // Set the new number of entries that we have
      this->NumberOfEntries = amount;
    }
    return index;
  }
  
  // Get the bounds of this prop. This is just the max bounds of all LODs
  double* vtkLODProp3D::GetBounds()
  {
    double newBounds[6];
    int first = 1;
  
    // Loop through all valid entries
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE)
      {
        vtkProp3D* p = this->LODs[i].Prop3D;
        if (p->GetMTime() < this->GetMTime())
        {
          p->SetUserMatrix(this->GetMatrix());
        }
  
        // Get the bounds of this entry
        p->GetBounds(newBounds);
  
        // If this is the first entry, this is the current bounds
        if (first)
        {
          memcpy(this->Bounds, newBounds, 6 * sizeof(double));
          first = 0;
        }
        // If this is not the first entry, compare these bounds with the
        // current bounds expanding the current ones as necessary
        else
        {
          this->Bounds[0] = (newBounds[0] < this->Bounds[0]) ? (newBounds[0]) : (this->Bounds[0]);
          this->Bounds[1] = (newBounds[1] > this->Bounds[1]) ? (newBounds[1]) : (this->Bounds[1]);
          this->Bounds[2] = (newBounds[2] < this->Bounds[2]) ? (newBounds[2]) : (this->Bounds[2]);
          this->Bounds[3] = (newBounds[3] > this->Bounds[3]) ? (newBounds[3]) : (this->Bounds[3]);
          this->Bounds[4] = (newBounds[4] < this->Bounds[4]) ? (newBounds[4]) : (this->Bounds[4]);
          this->Bounds[5] = (newBounds[5] > this->Bounds[5]) ? (newBounds[5]) : (this->Bounds[5]);
        }
      }
    }
  
    return this->Bounds;
  }
  
  // Method to remove a LOD based on an ID
  void vtkLODProp3D::RemoveLOD(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    this->LODs[index].Prop3D->RemoveConsumer(this);
    this->LODs[index].Prop3D->RemoveObserver(this->PickCallback);
    this->LODs[index].Prop3D->Delete();
    this->LODs[index].ID = VTK_INDEX_NOT_IN_USE;
    this->NumberOfLODs--;
  }
  
  // Convenience method to get the ID of the LOD that was used
  // during the last render
  int vtkLODProp3D::GetLastRenderedLODID()
  {
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      return -1;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      return -1;
    }
  
    return this->LODs[this->SelectedLODIndex].ID;
  }
  
  // Convenience method to get the estimated render time for a given LOD
  // based on an ID (the number returned when the LOD was added)
  double vtkLODProp3D::GetLODEstimatedRenderTime(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    return (index != VTK_INVALID_LOD_INDEX) ? this->GetLODIndexEstimatedRenderTime(index) : 0.0;
  }
  
  double vtkLODProp3D::GetLODIndexEstimatedRenderTime(int index)
  {
    return (index < 0 || index >= this->NumberOfEntries) ? 0 : this->LODs[index].EstimatedTime;
  }
  
  // Convenience method to set an actor LOD without a texture, or a
  // backface property.  Needed from wrappers where null pointers
  // are not possible
  int vtkLODProp3D::AddLOD(vtkMapper* m, vtkProperty* p, double time)
  {
    return this->AddLOD(m, p, nullptr, nullptr, time);
  }
  
  // Convenience method to set an actor LOD without a texture.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkMapper* m, vtkProperty* p, vtkProperty* back, double time)
  {
    return this->AddLOD(m, p, back, nullptr, time);
  }
  
  // Convenience method to set an actor LOD without a backface property.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkMapper* m, vtkProperty* p, vtkTexture* t, double time)
  {
    return this->AddLOD(m, p, nullptr, t, time);
  }
  
  // Convenience method to set an actor LOD without a property.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkMapper* m, vtkTexture* t, double time)
  {
    return this->AddLOD(m, nullptr, nullptr, t, time);
  }
  
  // Convenience method to set an actor LOD without a texture or a property.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkMapper* m, double time)
  {
    return this->AddLOD(m, nullptr, nullptr, nullptr, time);
  }
  
  // The real method for adding an actor LOD.
  int vtkLODProp3D::AddLOD(
    vtkMapper* m, vtkProperty* p, vtkProperty* back, vtkTexture* t, double time)
  {
    int index = this->GetNextEntryIndex();
    vtkActor* actor = vtkActor::New();
    vtkMatrix4x4* matrix = vtkMatrix4x4::New();
    this->GetMatrix(matrix);
    actor->SetUserMatrix(matrix);
    matrix->Delete();
    actor->SetMapper(m);
    if (p)
    {
      actor->SetProperty(p);
    }
  
    if (back)
    {
      actor->SetBackfaceProperty(back);
    }
  
    if (t)
    {
      actor->SetTexture(t);
    }
  
    actor->AddConsumer(this);
  
    this->LODs[index].Prop3D = actor;
    this->LODs[index].Prop3DType = VTK_LOD_ACTOR_TYPE;
    this->LODs[index].ID = this->CurrentIndex++;
    this->LODs[index].EstimatedTime = time;
    this->LODs[index].Level = 0.0;
    this->LODs[index].State = 1;
    this->LODs[index].Prop3D->AddObserver(vtkCommand::PickEvent, this->PickCallback);
    this->NumberOfLODs++;
  
    actor->SetEstimatedRenderTime(time);
  
    return this->LODs[index].ID;
  }
  
  // Convenience method to set a volume LOD without a property.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkAbstractVolumeMapper* m, double time)
  {
    return this->AddLOD(m, nullptr, time);
  }
  
  // The real method for adding a volume LOD.
  int vtkLODProp3D::AddLOD(vtkAbstractVolumeMapper* m, vtkVolumeProperty* p, double time)
  {
    int index = this->GetNextEntryIndex();
    vtkVolume* volume = vtkVolume::New();
    vtkMatrix4x4* matrix = vtkMatrix4x4::New();
    this->GetMatrix(matrix);
    volume->SetUserMatrix(matrix);
    matrix->Delete();
    volume->SetMapper(m);
    if (p)
    {
      volume->SetProperty(p);
    }
  
    volume->AddConsumer(this);
  
    this->LODs[index].Prop3D = volume;
    this->LODs[index].Prop3DType = VTK_LOD_VOLUME_TYPE;
    this->LODs[index].ID = this->CurrentIndex++;
    this->LODs[index].EstimatedTime = time;
    this->LODs[index].Level = 0.0;
    this->LODs[index].State = 1;
    this->LODs[index].Prop3D->AddObserver(vtkCommand::PickEvent, this->PickCallback);
    this->NumberOfLODs++;
  
    volume->SetEstimatedRenderTime(time);
  
    return this->LODs[index].ID;
  }
  
  // Convenience method to set a volume LOD without a property.
  // Needed from wrappers where null pointers are not possible
  int vtkLODProp3D::AddLOD(vtkImageMapper3D* m, double time)
  {
    return this->AddLOD(m, nullptr, time);
  }
  
  // The real method for adding a volume LOD.
  int vtkLODProp3D::AddLOD(vtkImageMapper3D* m, vtkImageProperty* p, double time)
  {
    int index = this->GetNextEntryIndex();
    vtkImageSlice* image = vtkImageSlice::New();
    vtkMatrix4x4* matrix = vtkMatrix4x4::New();
    this->GetMatrix(matrix);
    image->SetUserMatrix(matrix);
    matrix->Delete();
    image->SetMapper(m);
    if (p)
    {
      image->SetProperty(p);
    }
  
    image->AddConsumer(this);
  
    this->LODs[index].Prop3D = image;
    this->LODs[index].Prop3DType = VTK_LOD_IMAGE_TYPE;
    this->LODs[index].ID = this->CurrentIndex++;
    this->LODs[index].EstimatedTime = time;
    this->LODs[index].Level = 0.0;
    this->LODs[index].State = 1;
    this->LODs[index].Prop3D->AddObserver(vtkCommand::PickEvent, this->PickCallback);
    this->NumberOfLODs++;
  
    image->SetEstimatedRenderTime(time);
  
    return this->LODs[index].ID;
  }
  
  // Set the mapper for an LOD that is an actor
  void vtkLODProp3D::SetLODMapper(int id, vtkMapper* m)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an actor mapper on a non-actor!");
      return;
    }
  
    static_cast<vtkActor*>(this->LODs[index].Prop3D)->SetMapper(m);
  }
  
  // Get the mapper for an LOD that is an actor
  void vtkLODProp3D::GetLODMapper(int id, vtkMapper** m)
  {
    *m = nullptr;
  
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an actor mapper on a non-actor!");
  
      return;
    }
  
    *m = static_cast<vtkActor*>(this->LODs[index].Prop3D)->GetMapper();
  }
  
  // Set the mapper for an LOD that is a volume
  void vtkLODProp3D::SetLODMapper(int id, vtkAbstractVolumeMapper* m)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_VOLUME_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set a volume mapper on a non-volume!");
      return;
    }
  
    static_cast<vtkVolume*>(this->LODs[index].Prop3D)->SetMapper(m);
  }
  
  // Get the mapper for an LOD that is an actor
  void vtkLODProp3D::GetLODMapper(int id, vtkAbstractVolumeMapper** m)
  {
    *m = nullptr;
  
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_VOLUME_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get a volume mapper on a non-volume!");
      return;
    }
  
    *m = static_cast<vtkVolume*>(this->LODs[index].Prop3D)->GetMapper();
  }
  
  // Set the mapper for an LOD that is an image
  void vtkLODProp3D::SetLODMapper(int id, vtkImageMapper3D* m)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_IMAGE_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an image mapper on a non-image!");
      return;
    }
  
    static_cast<vtkImageSlice*>(this->LODs[index].Prop3D)->SetMapper(m);
  }
  
  // Get the mapper for an LOD that is an image
  void vtkLODProp3D::GetLODMapper(int id, vtkImageMapper3D** m)
  {
    *m = nullptr;
  
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_IMAGE_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an image mapper on a non-image!");
      return;
    }
  
    *m = static_cast<vtkImageSlice*>(this->LODs[index].Prop3D)->GetMapper();
  }
  
  // Get the mapper for an LOD that is an AbstractMapper3D
  vtkAbstractMapper3D* vtkLODProp3D::GetLODMapper(int id)
  {
    vtkAbstractMapper3D* m = nullptr;
  
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return m;
    }
  
    if (this->LODs[index].Prop3DType == VTK_LOD_ACTOR_TYPE)
    {
      m = static_cast<vtkActor*>(this->LODs[index].Prop3D)->GetMapper();
    }
    else if (this->LODs[index].Prop3DType == VTK_LOD_VOLUME_TYPE)
    {
      m = static_cast<vtkVolume*>(this->LODs[index].Prop3D)->GetMapper();
    }
    else if (this->LODs[index].Prop3DType == VTK_LOD_IMAGE_TYPE)
    {
      m = static_cast<vtkImageSlice*>(this->LODs[index].Prop3D)->GetMapper();
    }
  
    return m;
  }
  
  // Set the property for an LOD that is an actor
  void vtkLODProp3D::SetLODProperty(int id, vtkProperty* p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an actor property on a non-actor!");
      return;
    }
  
    static_cast<vtkActor*>(this->LODs[index].Prop3D)->SetProperty(p);
  }
  
  // Get the property for an LOD that is an actor
  void vtkLODProp3D::GetLODProperty(int id, vtkProperty** p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an actor property on a non-actor!");
      return;
    }
  
    *p = static_cast<vtkActor*>(this->LODs[index].Prop3D)->GetProperty();
  }
  
  // Set the property for an LOD that is a volume
  void vtkLODProp3D::SetLODProperty(int id, vtkVolumeProperty* p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_VOLUME_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set a volume property on a non-volume!");
      return;
    }
  
    static_cast<vtkVolume*>(this->LODs[index].Prop3D)->SetProperty(p);
  }
  
  // Get the property for an LOD that is an actor
  void vtkLODProp3D::GetLODProperty(int id, vtkVolumeProperty** p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_VOLUME_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get a volume property on a non-volume!");
      return;
    }
  
    *p = static_cast<vtkVolume*>(this->LODs[index].Prop3D)->GetProperty();
  }
  
  // Set the property for an LOD that is an image
  void vtkLODProp3D::SetLODProperty(int id, vtkImageProperty* p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_IMAGE_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an image property on a non-image!");
      return;
    }
  
    static_cast<vtkImageSlice*>(this->LODs[index].Prop3D)->SetProperty(p);
  }
  
  // Get the property for an LOD that is an image
  void vtkLODProp3D::GetLODProperty(int id, vtkImageProperty** p)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_IMAGE_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an image property on a non-image!");
      return;
    }
  
    *p = static_cast<vtkImageSlice*>(this->LODs[index].Prop3D)->GetProperty();
  }
  
  // Set the texture for an LOD that is an actor
  void vtkLODProp3D::SetLODTexture(int id, vtkTexture* t)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an actor texture on a non-actor!");
      return;
    }
  
    static_cast<vtkActor*>(this->LODs[index].Prop3D)->SetTexture(t);
  }
  
  // Get the texture for an LOD that is an actor
  void vtkLODProp3D::GetLODTexture(int id, vtkTexture** t)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an actor texture on a non-actor!");
      return;
    }
  
    *t = static_cast<vtkActor*>(this->LODs[index].Prop3D)->GetTexture();
  }
  
  // Set the backface property for an LOD that is an actor
  void vtkLODProp3D::SetLODBackfaceProperty(int id, vtkProperty* t)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot set an actor backface property on a non-actor!");
      return;
    }
  
    static_cast<vtkActor*>(this->LODs[index].Prop3D)->SetBackfaceProperty(t);
  }
  
  // Get the backface property for an LOD that is an actor
  void vtkLODProp3D::GetLODBackfaceProperty(int id, vtkProperty** t)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (this->LODs[index].Prop3DType != VTK_LOD_ACTOR_TYPE)
    {
      vtkErrorMacro(<< "Error: Cannot get an actor backface property on a non-actor!");
      return;
    }
  
    *t = static_cast<vtkActor*>(this->LODs[index].Prop3D)->GetBackfaceProperty();
  }
  
  void vtkLODProp3D::EnableLOD(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return;
    }
  
    this->LODs[index].State = 1;
  }
  
  void vtkLODProp3D::DisableLOD(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return;
    }
  
    this->LODs[index].State = 0;
  }
  
  int vtkLODProp3D::IsLODEnabled(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return 0;
    }
  
    return this->LODs[index].State;
  }
  
  void vtkLODProp3D::SetLODLevel(int id, double level)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return;
    }
  
    this->LODs[index].Level = level;
  }
  
  double vtkLODProp3D::GetLODLevel(int id)
  {
    int index = this->ConvertIDToIndex(id);
  
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return -1;
    }
  
    return this->LODs[index].Level;
  }
  
  double vtkLODProp3D::GetLODIndexLevel(int index)
  {
    if (index == VTK_INVALID_LOD_INDEX || index == VTK_INDEX_NOT_IN_USE)
    {
      return -1;
    }
    return this->LODs[index].Level;
  }
  
  // Release any graphics resources that any of the LODs might be using
  // for a particular window (such as display lists).
  void vtkLODProp3D::ReleaseGraphicsResources(vtkWindow* w)
  {
    // Loop through all LODs and pass this message along
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE)
      {
        this->LODs[i].Prop3D->ReleaseGraphicsResources(w);
      }
    }
  }
  
  // Standard render method - render any opaque geometry in the selected LOD
  int vtkLODProp3D::RenderOpaqueGeometry(vtkViewport* viewport)
  {
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Index out of range!");
      return 0;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      vtkErrorMacro(<< "Index not valid!");
      return 0;
    }
  
    // Actually do the rendering
    this->UpdateKeysForSelectedProp();
    int retval = this->LODs[this->SelectedLODIndex].Prop3D->RenderOpaqueGeometry(viewport);
  
    this->EstimatedRenderTime += this->LODs[this->SelectedLODIndex].Prop3D->GetEstimatedRenderTime();
  
    return retval;
  }
  
  // Standard render method - render any translucent geometry in the selected LOD
  int vtkLODProp3D::RenderTranslucentPolygonalGeometry(vtkViewport* viewport)
  {
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Index out of range!");
      return 0;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      vtkErrorMacro(<< "Index not valid!");
      return 0;
    }
  
    // Actually do the rendering
    this->UpdateKeysForSelectedProp();
    int retval =
      this->LODs[this->SelectedLODIndex].Prop3D->RenderTranslucentPolygonalGeometry(viewport);
  
    this->EstimatedRenderTime += this->LODs[this->SelectedLODIndex].Prop3D->GetEstimatedRenderTime();
  
    return retval;
  }
  
  // Description:
  // Does this prop have some translucent polygonal geometry?
  vtkTypeBool vtkLODProp3D::HasTranslucentPolygonalGeometry()
  {
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Index out of range!");
      return 0;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      vtkErrorMacro(<< "Index not valid!");
      return 0;
    }
  
    return this->LODs[this->SelectedLODIndex].Prop3D->HasTranslucentPolygonalGeometry();
  }
  
  // Standard render method - render any translucent geometry in the selected LOD
  int vtkLODProp3D::RenderVolumetricGeometry(vtkViewport* viewport)
  {
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Index out of range!");
      return 0;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      vtkErrorMacro(<< "Index not valid!");
      return 0;
    }
  
    // Actually do the rendering
    this->UpdateKeysForSelectedProp();
    int retval = this->LODs[this->SelectedLODIndex].Prop3D->RenderVolumetricGeometry(viewport);
  
    this->EstimatedRenderTime += this->LODs[this->SelectedLODIndex].Prop3D->GetEstimatedRenderTime();
  
    return retval;
  }
  
  // Override the method from vtkProp - add to both this prop and the prop of
  // the selected LOD
  void vtkLODProp3D::AddEstimatedRenderTime(double t, vtkViewport* vp)
  {
    // Add to this prop's estimated render time
    this->EstimatedRenderTime += t;
  
    // Check if the selected index is in range
    if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
    {
      vtkErrorMacro(<< "Index out of range!");
      return;
    }
  
    // Check if the selected index is valid
    if (this->LODs[this->SelectedLODIndex].ID == VTK_INDEX_NOT_IN_USE)
    {
      vtkErrorMacro(<< "Index not valid!");
      return;
    }
  
    // Now that error checking is done, add to the estimated render time
    // of the selected LOD
    this->LODs[this->SelectedLODIndex].Prop3D->AddEstimatedRenderTime(t, vp);
  }
  
  void vtkLODProp3D::RestoreEstimatedRenderTime()
  {
    // restore the EstimatedTime of the last LOD to be rendered
    if (this->SelectedLODIndex >= 0 && this->SelectedLODIndex < this->NumberOfEntries)
    {
      this->LODs[this->SelectedLODIndex].Prop3D->RestoreEstimatedRenderTime();
    }
  }
  
  // Set the allocated render time - this is where the decision is made
  // as to which LOD to select
  void vtkLODProp3D::SetAllocatedRenderTime(double t, vtkViewport* vp)
  {
    int i;
    int index = -1;
    double bestTime;
    double bestLevel = 0;
    double targetTime;
    double estimatedTime;
    double newTime;
  
    // update the EstimatedTime of the last LOD to be rendered
    if (this->SelectedLODIndex >= 0 && this->SelectedLODIndex < this->NumberOfEntries &&
      this->LODs[this->SelectedLODIndex].ID != VTK_INDEX_NOT_IN_USE)
    {
      // For stability, blend in the new time - 25% old + 75% new
      newTime = this->LODs[this->SelectedLODIndex].Prop3D->GetEstimatedRenderTime(vp);
      this->LODs[this->SelectedLODIndex].EstimatedTime =
        0.25 * this->LODs[this->SelectedLODIndex].EstimatedTime + 0.75 * newTime;
    }
  
    this->SavedEstimatedRenderTime = this->EstimatedRenderTime;
  
    if (this->AutomaticLODSelection)
    {
      bestTime = -1.0;
  
      targetTime = t;
  
      for (i = 0; i < this->NumberOfEntries; i++)
      {
        if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE && this->LODs[i].State == 1)
        {
          // Gather some information
          estimatedTime = this->GetLODIndexEstimatedRenderTime(i);
  
          // If we've never rendered this LOD and we have no info on it,
          // then try it out
          if (estimatedTime == 0.0)
          {
            index = i;
            bestTime = 0.0;
            bestLevel = this->GetLODIndexLevel(i);
            break;
          }
  
          // If we do have at least a guess as to the render time, and
          // this seems like the best we have so far, pick it.
          // It is the best we have if
          //
          // 1) our estimated time is less than what we are looking for,
          //    but greater than any we have selected so far.
          //
          // 2) we have not selected anything else yet
          //    (regardless of what the estimated time is)
          //
          // 3) it is less than the time of the currently selected LOD
          //    if that LOD's time is greater than the time we are targeting.
          //
          if (estimatedTime > 0.0 &&
            ((estimatedTime > bestTime && estimatedTime < targetTime) || (bestTime == -1.0) ||
              (estimatedTime < bestTime && bestTime > targetTime)))
          {
            index = i;
            bestTime = estimatedTime;
            bestLevel = this->GetLODIndexLevel(i);
          }
        }
      }
  
      // If we aren't trying some level for the first time with 0.0 bestTime,
      // make sure there isn't a LOD that can be rendered faster and has a
      // higher level
      double level;
      if (bestTime != 0.0)
      {
        for (i = 0; i < this->NumberOfEntries; i++)
        {
          if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE && this->LODs[i].State == 1)
          {
            // Gather some information
            estimatedTime = this->GetLODIndexEstimatedRenderTime(i);
            level = this->GetLODIndexLevel(i);
  
            // Update the index and the level, but not the time. This is
            // so that we find the best level that can be rendered
            // faster than the LOD selected above.
            if (estimatedTime <= bestTime && level < bestLevel)
            {
              index = i;
              bestLevel = level;
            }
          }
        }
      }
    }
    else
    {
      index = 0;
      while (index < this->NumberOfEntries && this->LODs[index].ID != this->SelectedLODID)
      {
        index++;
      }
      if (index == this->NumberOfEntries)
      {
        vtkErrorMacro(<< "Could not render selected LOD ID: " << this->SelectedLODID);
        index = 0;
        while (index < this->NumberOfEntries && this->LODs[index].ID != VTK_INDEX_NOT_IN_USE)
        {
          index++;
        }
      }
    }
  
    this->EstimatedRenderTime = 0.0;
    this->AllocatedRenderTime = t;
    if (index == -1)
    {
      return;
    }
  
    this->SelectedLODIndex = index;
    this->LODs[this->SelectedLODIndex].Prop3D->SetAllocatedRenderTime(t, vp);
  
    // Push the matrix down into the selected LOD
    vtkProp3D* p = this->LODs[this->SelectedLODIndex].Prop3D;
    // Getting our matrix here triggers a ComputeMatrix, if necessary,
    // which updates our MatrixMTime
    vtkMatrix4x4* mat = this->GetMatrix();
    if (p->GetUserTransformMatrixMTime() < this->MatrixMTime)
    {
      p->SetUserMatrix(mat);
    }
  }
  
  void vtkLODProp3D::PrintSelf(ostream& os, vtkIndent indent)
  {
    this->Superclass::PrintSelf(os, indent);
  
    os << indent << "Number Of LODs: " << this->NumberOfLODs << endl;
  
    os << indent << "Selected LOD ID: " << this->SelectedLODID << endl;
  
    os << indent << "AutomaticLODSelection: " << (this->AutomaticLODSelection ? "On\n" : "Off\n");
  
    os << indent
       << "AutomaticPickLODSelection: " << (this->AutomaticPickLODSelection ? "On\n" : "Off\n");
  
    os << indent << "SelectedPickLODID: " << this->SelectedPickLODID << endl;
  
    os << indent << "CurrentIndex: " << this->CurrentIndex << endl;
  }
  
  void vtkLODProp3D::GetActors(vtkPropCollection* ac)
  {
  #if 0
    // I don't get that 1999 code, why is it limiting the actors to the one
    // picked...
  
    vtkDebugMacro(<< "vtkLODProp3D::GetActors");
    int index;
    int lodID;
  
    lodID = this->GetPickLODID();
    index = this->ConvertIDToIndex(lodID);
  
    if (index == VTK_INVALID_LOD_INDEX)
    {
      return;
    }
  
    if (! this->LODs[index].Prop3D->IsA("vtkVolume"))
    {
      ac->AddItem(this->LODs[index].Prop3D);
    }
  #else
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE && vtkActor::SafeDownCast(this->LODs[i].Prop3D))
      {
        ac->AddItem(this->LODs[i].Prop3D);
      }
    }
  #endif
  }
  
  void vtkLODProp3D::GetVolumes(vtkPropCollection* ac)
  {
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE && vtkVolume::SafeDownCast(this->LODs[i].Prop3D))
      {
        ac->AddItem(this->LODs[i].Prop3D);
      }
    }
  }
  
  int vtkLODProp3D::GetAutomaticPickPropIndex()
  {
    double bestTime = -1.0;
    int index = 0;
    double targetTime = 0;
  
    for (int i = 0; i < this->NumberOfEntries; i++)
    {
      if (this->LODs[i].ID != VTK_INDEX_NOT_IN_USE)
      {
        // Gather some information
        double estimatedTime = this->GetLODIndexEstimatedRenderTime(i);
  
        // If we've never rendered this LOD and we have no info on it,
        // then try it out
        if (estimatedTime == 0.0)
        {
          index = i;
          break;
        }
  
        // If we do have at least a guess as to the render time, and
        // this seems like the best we have so far, pick it.
        // It is the best we have if
        //
        // 1) our estimated time is less than what we are looking for,
        //    but greater than any we have selected so far.
        //
        // 2) we have not selected anything else yet
        //    (regardless of what the estimated time is)
        //
        // 3) it is less than the time of the currently selected LOD
        //    if that LOD's time is greater than the time we are targeting.
        //
        if (estimatedTime > 0.0 &&
          ((estimatedTime > bestTime && estimatedTime < targetTime) || (bestTime == -1.0) ||
            (estimatedTime < bestTime && bestTime > targetTime)))
        {
          index = i;
          bestTime = estimatedTime;
        }
      }
    }
    return index;
  }
  
  void vtkLODProp3D::UpdateKeysForSelectedProp()
  {
    this->LODs[this->SelectedLODIndex].Prop3D->SetPropertyKeys(this->GetPropertyKeys());
  }
  
  int vtkLODProp3D::GetPickLODID()
  {
    vtkDebugMacro(<< "vtkLODProp3D::GetPickLODID");
    int lodID, index;
    if (this->AutomaticPickLODSelection)
    {
      if (this->SelectedLODIndex < 0 || this->SelectedLODIndex >= this->NumberOfEntries)
      {
        index = this->GetAutomaticPickPropIndex();
      }
      else
      {
        index = this->SelectedLODIndex;
      }
      lodID = this->LODs[index].ID;
    }
    else
    {
      lodID = this->SelectedPickLODID;
    }
    return lodID;
  }
  
  void vtkLODProp3D::SetSelectedPickLODID(int id)
  {
    this->SelectedPickLODID = id;
    this->Modified();
  }
  
  void vtkLODProp3D::ShallowCopy(vtkProp* prop)
  {
    vtkLODProp3D* a = vtkLODProp3D::SafeDownCast(prop);
  
    if (a)
    {
      this->SetAutomaticLODSelection(a->GetAutomaticLODSelection());
      this->SetAutomaticPickLODSelection(a->GetAutomaticPickLODSelection());
      this->SetSelectedLODID(a->GetSelectedLODID());
      this->NumberOfLODs = a->NumberOfLODs;
      // for(int i = 0; i < this->NumberOfLODs; i++) {}
    }
  
    // Now do superclass
    this->vtkProp3D::ShallowCopy(prop);
  }
  