////////////////////////////////////////////////////////////////////////////////
// Mesh.cpp
// Class Implementation
// Copyright Bjoern Ganster
// Developed since 2001
////////////////////////////////////////////////////////////////////////////////

#include "Mesh.h"
#include "Matrix.h"
#include "FileCache.h"
#include "BGString.h"
#include "BGCPString.h"

#include <assert.h>

#define FILE_NUMBER FILE_NO_MESH_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

Mesh::Mesh (RenderableObject* _parent)
: RenderableObject("Mesh", MeshID, _parent),
  texture(NULL),
  points(),
  polygons(),
  poliesDirty(true),
  transformedVerticesDirty(false) // empty cache is not dirty ...
{
	SET_SCOPE;
	DefaultPolygonColor = Color (1.0, 1.0, 1.0);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

Mesh::~Mesh ()
{
	SET_SCOPE;
	for (size_t i = 0; i < polygons.size(); i++)
	{
		MeshPolygon* poly = polygons[i];
		if (poly != NULL) {
			delete poly;
		}
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Add a point

size_t Mesh::addPoint (double x, double y, double z)
{
	SET_SCOPE;
   size_t base = points.size();
   points.push_back(x);
   points.push_back(y);
   points.push_back(z);
   RETURN base/3;
}

////////////////////////////////////////////////////////////////////////////////

bool Mesh::getPoint (size_t index, Point3D& p) const
{
	SET_SCOPE;
	size_t base = 3 * index;
	if (base + 2 < points.size()) {
		p.x = points[base];
		p.y = points[++base];
		p.z = points[++base];
		p.w = 1.0;
		RETURN true; 
	} else {
		RETURN false;
	}
}

void Mesh::setPoint (size_t index, const Point3D& p) 
{
	SET_SCOPE;
	size_t base = 3*index;
	points[base] = p.x;
	points[++base] = p.y;
	points[++base] = p.z;
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Render a polygon set

void Mesh::renderSelf ()
{
	SET_SCOPE;
   if (visible) {
      bool textured;
      if (texture != nullptr && texture->getWidth() > 0) {
         glEnable (GL_TEXTURE_2D);
         glEnableClientState (GL_TEXTURE_COORD_ARRAY);
         texture->useMipmap();
         //texture.use();
         textured = true;
      } else {
         glDisable (GL_TEXTURE_2D);
         textured = false;
      }

      glEnableClientState (GL_VERTEX_ARRAY);

      color.glColor();

      if (poliesDirty) {
        triangleVertices.clear();
        triangleTexCoords.clear();
        quadVertices.clear();
        quadTexCoords.clear();
      }

      for (size_t i = 0; i < polygons.size(); ++i) {
          MeshPolygon& poly = *polygons[i];
          if (poly.getPointCount() == 3) {
              if (poliesDirty) {
                  for (size_t j = 0; j < 3; ++j) {
                      Point3D p = poly.getVertexCoords(j);
                      double u, v;
                      auto ti = poly.getTexCoordIndex(j);
                      getTexCoords(ti, u, v);
                      triangleVertices.push_back((float) p.x);
                      triangleVertices.push_back((float) p.y);
                      triangleVertices.push_back((float) p.z);
                      triangleTexCoords.push_back((float) u);
                      triangleTexCoords.push_back((float) v);
                      triangleNormals.push_back((float) poly.normal.x);
                      triangleNormals.push_back((float) poly.normal.y);
                      triangleNormals.push_back((float) poly.normal.z);
                  }
              }
          } else if (poly.getPointCount() == 4) {
                  if (poliesDirty) {
                      for (size_t j = 0; j < 4; ++j) {
                          Point3D p = poly.getVertexCoords(j);
                          double u, v;
                          auto ti = poly.getTexCoordIndex(j);
                          getTexCoords(ti, u, v);
                          quadVertices.push_back((float)p.x);
                          quadVertices.push_back((float)p.y);
                          quadVertices.push_back((float)p.z);
                          quadTexCoords.push_back((float)u);
                          quadTexCoords.push_back((float)v);
                          //Point3D n = poly.getNormal(j);
                          quadNormals.push_back((float)poly.normal.x);
                          quadNormals.push_back((float)poly.normal.y);
                          quadNormals.push_back((float)poly.normal.z);
                      }
                  }
          } else {
             poly.render(textured);
          }
      }

	  glEnableClientState(GL_NORMAL_ARRAY);

      poliesDirty = false;
      glVertexPointer (3, GL_FLOAT, 0, triangleVertices.data());
      glTexCoordPointer (2, GL_FLOAT, 0, triangleTexCoords.data());
      glNormalPointer(GL_FLOAT, 0, triangleNormals.data());
      glDrawArrays(GL_TRIANGLES, 0, triangleVertices.size()/3);

      glVertexPointer (3, GL_FLOAT, 0, quadVertices.data());
      glTexCoordPointer (2, GL_FLOAT, 0, quadTexCoords.data());
      glNormalPointer(GL_FLOAT, 0, quadNormals.data());
      glDrawArrays(GL_QUADS, 0, quadVertices.size()/3);

      glDisableClientState (GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);

      if (textured) {
         glDisable (GL_TEXTURE_2D);
         glDisableClientState (GL_TEXTURE_COORD_ARRAY);
      }

  		// Render normals for debugging
		glDisable (GL_TEXTURE_2D);
		glColor3d(1.0, 0.0, 0.0);
		glDisable (GL_LIGHT0);
		/*for (size_t i = 0; i < polygons.size(); ++i) {
			MeshPolygon& poly = *polygons[i];
			if (poly.getPointCount() > 0) {
				
				glBegin(GL_LINES);
				Point3D p = poly.getVertexCoords(0);
				for (size_t j = 1; j < poly.getPointCount(); ++j) {
					p += poly.getVertexCoords(j);
				}
				p = 1.0 / poly.getPointCount() * p;

				Point3D q = p + poly.normal;
				p.glVertex();
				q.glVertex();
				glEnd();
			}
		}*/

		// Render wireframe
		/*for (size_t i = 0; i < polygons.size(); ++i) {
			MeshPolygon& poly = *polygons[i];
			if (poly.getPointCount() > 0) {
				glColor3d(1.0, 1.0, 1.0);
				glBegin(GL_LINES);
				for (size_t j = 0; j < poly.getPointCount(); ++j) {
					size_t k = (j+1) % poly.getPointCount();
					Point3D p = poly.getVertexCoords(j);
					Point3D q = poly.getVertexCoords(k);
					p.glVertex();
					q.glVertex();
				}
				glEnd();
			}
		}*/

		glEnable (GL_LIGHT0);
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Get min/max mesh coords to decide about grid sizes

Point3D Mesh::getMinCoords (bool transform) const 
{
	SET_SCOPE;
	if (poliesDirty) {
		Point3D result, candidate;

		if (points.size() != 0) {
			getPoint(0, result);
			for (unsigned int i = 1; i < points.size(); i++) {
				getPoint(i, candidate);
				if (transform)
					candidate = M.Transform(candidate);
				result = MinCoords (result, candidate);
			}
		}
		RETURN result;
	}
	RETURN mMinCoords;
}

Point3D Mesh::getMaxCoords (bool transform) const 
{
	SET_SCOPE;
	if (poliesDirty) {
		Point3D result, candidate;

		if (points.size() != 0) {
			getPoint(0, result);
			for (unsigned int i = 1; i < points.size(); i++) {
				getPoint(i, candidate);
				if (transform)
					candidate = M.Transform(candidate);
				result = MaxCoords (result, candidate);
			}
		}
		RETURN result;
	}
	RETURN mMaxCoords;
}

////////////////////////////////////////////////////////////////////////////////
// Mark front-facing polygons

void Mesh::markFrontFacingPolygons (const Point3D& ls)
{
/*	for (unsigned int i = 0; i < polygons.size(); i++) {
		MeshPolygon& poly = *polygons[i];
		Point3D p0 = poly.getVertexCoords(0);
		Point3D p1 = poly.getVertexCoords(1);
		Point3D p2 = poly.getVertexCoords(2);
		Point3D dir1 = p0 - p1;
		Point3D dir2 = p0 - p2;
		dir1.unitLength();
		dir2.unitLength();
		Point3D normal = (dir1).crossMultiply(dir2);

		if (normal * (ls - p0) > 0.0) {
			poly.mark = 1;
		} else {
			poly.mark = -1;
		}
	}*/
}

////////////////////////////////////////////////////////////////////////////////
// Render shadow volume

void Mesh::renderShadowVolumes (const Point3D& ls) const
{
/*   for (unsigned int i = 0; i < polygons.getSize (); i++) {
      MeshPolygon& poly = *polygons[i];
      for (unsigned int j = 0; j < poly.getPointCount (); j++) {
         //MeshPolygonEdge& edge = poly.getEdge (j);

         // A shadow polygon is created for this edge if either there is no
         // opposed edge or the edge and the opposed edge differ in lighting
         bool RenderShadowEdge = false;
         if (edge.opposedEdge != NULL) {
            if (poly.mark > edge.opposedEdge->poly->mark) {
               RenderShadowEdge = true;
            }
         } else {
            RenderShadowEdge = true;
         }

         if (RenderShadowEdge) {
            // p1, q1 represent the edge
            Point3D& p1 = *edge.point;
            Point3D& q1 = *poly.getPoint ((j+1) % poly.getPointCount ());

            // p2, q2 are the directions from ls to p1, q1, at infinity
            Point3D p2 = p1-ls;
            Point3D q2 = q1-ls;

            // Render shadow polygon
            glBegin (GL_QUADS);
               p1.glVertex ();
               q1.glVertex ();
               glVertex4d (q2.x, q2.y, q2.z, 0.0);
               glVertex4d (p2.x, p2.y, p2.z, 0.0);
            glEnd ();
         }
      }
   }*/
}

///////////////////////////////////////////////////////////////////////////////
// Load a mesh from a .obj

bool Mesh::loadFromObj (const PathChar* FileName, Texture* textureToUse)
{
	SET_SCOPE;
	if (!fileExists (FileName)) {
		BGCPString msg = BGCPString("Failed to open") + BGCPString(FileName, CP_SYSTEM);
		msg.printLn();
		RETURN false;
	}

   FileCache file;
   file.openReadOnly (FileName);
   bool KeepReading = file.getFileSize() > 0;

   if (!KeepReading) {
	   BGCPString msg = BGCPString("Empty file? ") + BGCPString(FileName, CP_SYSTEM);
	   msg.printLn();
      RETURN false;
   }

   // Read file
   FileOffset offset = 0;
   BGString line;
   file.readLine (offset, line);

   // Index 0 in obj refers to the origin
   addPoint(0.0, 0.0, 0.0);
   addTexCoords(0.0, 0.0);

   while (KeepReading) {
      if (line.getLength() > 2) {
         if (line.getChars()[0] == 'v'
         &&  line.getChars()[1] == ' ') {
            // Read three coordinates from the file
            double x, y, z;
            BGString xStr, yStr, zStr;
            xStr.assignWord (line.getChars(), 1, ' ', line.getLength());
            yStr.assignWord (line.getChars(), 2, ' ', line.getLength());
            zStr.assignWord (line.getChars(), 3, ' ', line.getLength());

			if (xStr.toDouble(x) && yStr.toDouble(y) && zStr.toDouble(z)) {
				addPoint (x, y, z);
			}
         } else 
         if (line.getChars()[0] == 'f'
         &&  line.getChars()[1] == ' ') 
         {
            // Count words
            size_t offset = 2;
            size_t pointCount = 0;
            while (offset < line.getLength()) {
               BGString num;
               offset = num.assignWord (line.getChars(), (int) pointCount+1, 
                                        ' ', line.getLength());
               size_t j = num.toInt();
               if (j > 0)
                  pointCount++;
            }

            // Set polygon points
            MeshPolygon* newPoly = addPolygon(pointCount);
            offset = 2;
            size_t word = 1;
            size_t pnum = 0;
            
            while (pnum < pointCount && word < line.getLength()) {
               BGString vertexData, vnum, tnum;
               offset = vertexData.assignWord (line.getChars(), (int) word, ' ', 
                                        line.getLength());
               vnum.assignWord(vertexData.getChars(), 0, '/', vertexData.getLength());
               tnum.assignWord(vertexData.getChars(), 1, '/', vertexData.getLength());
               unsigned int newV = vnum.toInt();
               word++;

               // Check if vertex number is legal
               if (newV <= getPointCount() && newV != 0) {
                  unsigned int t = tnum.toInt();
                  newPoly->setVertexIndex(pnum, newV);
                  newPoly->setTexCoordIndex(pnum, t);
                  pnum++;
               }
            }
         } else
         if (line.getChars()[0] == 'v'
         &&  line.getChars()[1] == 't') {
            BGString ustr, vstr;
            double u, v;
            ustr.assignWord(line.getChars(), 1, ' ', line.getLength());
            vstr.assignWord(line.getChars(), 2, ' ', line.getLength());
            ustr.toDouble(u);
            vstr.toDouble(v);
            textureCoords.push_back(u);
            textureCoords.push_back(v);
         }
      }

      KeepReading = (offset < file.getFileSize());
      if (KeepReading) {
         file.readLine (offset, line);
      } else {
         line = "#";
      }
   }

   calculateNormals();
   RETURN true;
}

///////////////////////////////////////////////////////////////////////////////
// Calculate normals for polygons and vertices

void Mesh::calculateNormals()
{
	SET_SCOPE;
   // Calculate normals for polygons
   for (size_t i = 0; i < polygons.size(); ++i) {
	   MeshPolygon* poly = polygons[i];
      if (poly->getPointCount() > 2) {
          auto p0i = poly->getVertexIndex(0);
          auto p1i = poly->getVertexIndex(1);
          auto p2i = poly->getVertexIndex(2);
          Point3D p0, p1, p2;
          getPoint(p0i, p0);
          getPoint(p1i, p1);
          getPoint(p2i, p2);
		  Point3D d1 = p0-p1;
		  Point3D d2 = p0-p2;
          poly->normal  = d1.crossMultiply (d2);
          poly->normal.unitLength();
      }
   }

   // Calculate normals for the vertices from surrounding triangles
/*   for (i = 0; i < points.getSize (); i++) {
      Point3D& p = *points[i];
      p.normal = Point3D (0.0, 0.0, 0.0);
      for (j = 0; j < p.triangles.size (); j++) {
         p.normal = p.normal + p.triangles[j]->normal;
      }

      if (p.normal.length () == 0.0)
         p.normal = Point3D (1.0, 0.0, 0.0);
      else
         p.normal.UnitLength ();
   }*/

   RETURN;
}

///////////////////////////////////////////////////////////////////////////////
// Save to obj

bool Mesh::saveToObj (const PathChar* FileName, bool saveUVs)
{
	SET_SCOPE;
   BGCPString lines = BGCPString("g ") + getName() + BGCPString("\n");
   size_t uvBaseForPoly = 1;

   // Write vertices
   for (size_t i = 0; i < points.size(); i += 3) {
	   BGCPString line = BGCPString("v ") + BGCPString::fromDouble(points[i]) + BGCPString(" ") + 
		   BGCPString::fromDouble(points[i+1]) + BGCPString(" ") +
		   BGCPString::fromDouble(points[i+2]) + BGCPString("\n");
		lines += line;
   }

   for (size_t i = 0; i < polygons.size(); i++) {
      MeshPolygon& poly = *polygons[i];

      // Write UVs - optional
      if (saveUVs) { // && poly.hasTextureCoords()) {
         for (size_t j = 0; j < poly.getPointCount(); j++) {
            double u, v;
            auto ti = poly.getTexCoordIndex(j);
            getTexCoords(ti, u, v);
            BGCPString line = BGCPString("vt ") + BGCPString::fromDouble (u) + BGCPString(" ") + 
				BGCPString::fromDouble(v) + BGCPString("\n");
			lines += line;
         }
      }

      // Write faces
      BGCPString line("f");
      for (size_t j = 0; j < poly.getPointCount(); j++) {
         line += BGCPString(" ");
         auto vertexNum = poly.getVertexIndex(j);
		 line += BGCPString::fromInt(vertexNum+1);
         if (saveUVs) {
            line += BGCPString("/");
            auto texCoordNum = poly.getTexCoordIndex(j);
            line += BGCPString::fromInt(uvBaseForPoly + j);
         }
      }
      line += BGCPString("\n");
      lines += line;
	  uvBaseForPoly += poly.getPointCount();
   }

   lines.saveToFile(FileName, CP_ASCII);
   RETURN true;
}

///////////////////////////////////////////////////////////////////////////////
// Reduce polygons to triangles

// Not all algorithms can cope with polygons consisting of more than 3 vertices
// (that is, non-triangles). Therefore, this method reduces such polygons to 
// triangles. This is done by connecting all edges to the first vertex of the 
// polygon for polygons consisting of four points. For other polygons, a new
// point is created in the middle of the polygon and connected to the edges of 
// the polygon.

void Mesh::reducePolygonsToTriangles ()
{
/*   unsigned int i = 0;
   while (i < polygons.size ()) {
      MeshPolygon* poly = polygons[i];
      if (poly->getEdgeCount () == 4) {
         addTriangle (poly->getPoint (0)->num,
                      poly->getPoint (1)->num,
                      poly->getPoint (2)->num);
         addTriangle (poly->getPoint (0)->num,
                      poly->getPoint (2)->num,
                      poly->getPoint (3)->num);

         // Delete old triangle and remove it from the vector
         removePolygon (poly);
      } else if (poly->getEdgeCount () > 4) {
         // Compute center of polygon
         unsigned int j;
         Point3D center = *poly->getPoint (0);
         size_t polysize = poly->getEdgeCount ();
         for (j = 1; j < polysize; j++) {
            center = center + *poly->getPoint (j);
         }
         center = center / polysize;
         Point3D* centerpoint = addPoint (center.x, center.y, center.z);
         size_t centernum = centerpoint->num;

         // Create new triangles from polygon
         for (j = 0; j < polysize; j++) {
            unsigned int k = (j+1) % polysize;
            addTriangle (centernum, 
                         poly->getPoint (j)->num,
                         poly->getPoint (k)->num);
         }

         // Remove old triangle
         removePolygon (poly);
      } else {
         // Process next polygon
         i++;
      }
   }
   calculateNormals ();*/
}

///////////////////////////////////////////////////////////////////////////////
// Set per vertex lighting

void Mesh::setPerVertexLighting (bool newVal)
{
	SET_SCOPE;
	for (unsigned int i = 0; i < polygons.size(); i++) {
		polygons[i]->PerVertexLighting = newVal;
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Recursive Part of mesh size calculation

void Mesh::getSizeRecursive (Point3D& minCoords, Point3D& maxCoords, 
    size_t& vertices, size_t& polygons, const Matrix& m) const
{
	SET_SCOPE;
    Matrix m2 = m * getMatrix();
    if (vertices == 0 && points.size() > 0) {
        Point3D p = getVertexCoords(0);
        p = m2.Transform(p);
        maxCoords = p;
        minCoords = p;
    }

   for (size_t i = 0; i < getPointCount(); i++) {
      Point3D p;
      getPoint(i,p);
      p = m2.Transform(p);
      minCoords = MinCoords (minCoords, p);
      maxCoords = MaxCoords (maxCoords, p);
   }
   polygons += this->getPolygonCount();
   vertices += this->getPointCount();
   RenderableObject::getSizeRecursive(minCoords, maxCoords, vertices, polygons, m);
   RETURN;
}

// Transform all vertices using m
void Mesh::applyMatrix(const Matrix& m)
{
	SET_SCOPE;
	Matrix m2 = M * m;

    // Update vertices stored directly in this object
    for (size_t i = 0; i < getPointCount(); ++i) {
        Point3D p;
        getPoint(i, p);
        Point3D q = m2.Transform(p);
        setPoint(i, q);
    }

	M = Matrix();

    // Update vertices in the polygons
    /*for (size_t i = 0; i < getPolygonCount(); ++i) {
        MeshPolygon* poly = polygons[i];
        for (size_t j = 0; j < poly->getPointCount(); ++j) {
            size_t k = poly->getPoint(j);
            poly->setPoint(j, k);
        }
    }*/

	RETURN;
}

// Set texture coordinates for a vertex
void Mesh::setTexCoords (size_t i, double u, double v)
{
	SET_SCOPE;
    if (2*i+1 < textureCoords.size()) {
        textureCoords[2*i] = u;
        textureCoords[2*i+1] = v;
    }
	RETURN;
}

// Add new texture coordinates to this mesh and return their index
size_t Mesh::addTexCoords (double u, double v)
{
	SET_SCOPE;
    size_t index = textureCoords.size() / 2;
    textureCoords.push_back(u);
    textureCoords.push_back(v);
    RETURN index;
}

// Get texture coordinates
void Mesh::getTexCoords (size_t i, double& u, double& v) const
{
	SET_SCOPE;
    if (2*i+1 < textureCoords.size()) {
        u = textureCoords[2*i];
        v = textureCoords[2*i+1];
    } else {
		u = 0.0;
		v = 0.0;
	}
	RETURN;
}

void Mesh::setVertexCoords(size_t polyBaseIndex, size_t indexInPoly, double x, double y, double z)
{
	SET_SCOPE;
    size_t index = 3*(polyBaseIndex+indexInPoly);
    //if (index+2 < points.getSize()) {
        points[index]   = x;
        points[index+1] = y;
        points[index+2] = z;
    //}
	RETURN;
}

// Get vertex coordinates
Point3D Mesh::getVertexCoords(size_t i) const 
{
	SET_SCOPE;
    Point3D result;
    size_t index = 3*i;
    if (index+2 < points.size()) {
        result.x = points[index];
        result.y = points[index+1];
        result.z = points[index+2];
    }
    RETURN result;
}

MeshPolygon* Mesh::addPolygon(size_t vertexCount)
{
	SET_SCOPE;
    size_t oldSize = points.size();
    points.reserve(oldSize + 3 * vertexCount);
    textureCoords.reserve(oldSize + 2 * vertexCount);
    size_t newIndex = oldSize/3;
    MeshPolygon* newPoly = new MeshPolygon(vertexCount, this, newIndex);
    polygons.push_back(newPoly);
    setPoliesDirty();
    RETURN newPoly;
}

// Prepare to render polygon
/*void Mesh::setRenderingBuffers(size_t mIndexInMesh, size_t vertexCount, bool textured)
{
    glVertexPointer(3, GL_DOUBLE, 0, &points[3*mIndexInMesh]);
    if (textured) {
        glTexCoordPointer(2, GL_DOUBLE, 0, &textureCoords[2*mIndexInMesh]);
    }
    glDrawArrays (GL_POLYGON, 0, vertexCount);
}*/

// Copy data from other mesh (not including childs)
void Mesh::copyMesh(const Mesh* other)
{
	SET_SCOPE;
	if (other == nullptr) {
		RETURN;
	}

	texture = other->texture;
	DefaultPolygonColor = other->DefaultPolygonColor;
	points = other->points;
	textureCoords = other->textureCoords;
	triangleVertices = other->triangleVertices;
	triangleTexCoords = other->triangleTexCoords;
	triangleNormals = other->triangleNormals;
	quadVertices = other->quadVertices;
	quadTexCoords = other->quadTexCoords;
	quadNormals = other->quadNormals;
	poliesDirty = other->poliesDirty;
	setName(other->getName());

	for (size_t i = 0; i < other->polygons.size(); ++i) {
		MeshPolygon* otherPoly = other->polygons[i];
		MeshPolygon* newPoly = MeshPolygon::copy(otherPoly, this);
		polygons.push_back(newPoly);
	}

	RETURN;
}

// Transform all vertices in the scene and cache them
void Mesh::cacheTransformedVertices(const Matrix& m)
{
	if (!transformedVerticesDirty || getPointCount() == 0) {
		return;
	}

	mTransformedVertices.resize(getPointCount());

	// Cache transformed vertices
	for (size_t j = 0; j < getPointCount(); ++j) {
		Point3D p = getVertexCoords(j);
		p = m.Transform(p);
		mTransformedVertices[j] = p;
	}

	// Check childs
	RenderableObject::cacheTransformedVertices(m);
	transformedVerticesDirty = false;

	mMinCoords = mTransformedVertices[0];
	mMaxCoords = mTransformedVertices[0];
	for (size_t i = 1; i < getPointCount(); ++i) {
		mMinCoords.x = std::min(mMinCoords.x, mTransformedVertices[i].x);
		mMinCoords.y = std::min(mMinCoords.y, mTransformedVertices[i].y);
		mMinCoords.z = std::min(mMinCoords.z, mTransformedVertices[i].z);
		mMaxCoords.x = std::max(mMaxCoords.x, mTransformedVertices[i].x);
		mMaxCoords.y = std::max(mMaxCoords.y, mTransformedVertices[i].y);
		mMaxCoords.z = std::max(mMaxCoords.z, mTransformedVertices[i].z);
	}
}

Point3D Mesh::getTransformedVertex(size_t index) const
{
	if (index < mTransformedVertices.size()) {
		return mTransformedVertices[index];
	} else {
		return Point3D();
	}
}

void Mesh::setTransformedVerticesCacheDirty()
{
	transformedVerticesDirty = true;
	RenderableObject::setTransformedVerticesCacheDirty();
}
