/**
 */

#include <aws/dynamodb/model/ConsumedCapacity.h>
#include <aws/core/utils/json/JsonSerializer.h>

#include <utility>

using namespace Aws::Utils::Json;
using namespace Aws::Utils;

namespace Aws
{
namespace DynamoDB
{
namespace Model
{

ConsumedCapacity::ConsumedCapacity() :
    m_tableNameHasBeenSet(false),
    m_capacityUnits(0.0),
    m_capacityUnitsHasBeenSet(false),
    m_readCapacityUnits(0.0),
    m_readCapacityUnitsHasBeenSet(false),
    m_writeCapacityUnits(0.0),
    m_writeCapacityUnitsHasBeenSet(false),
    m_tableHasBeenSet(false),
    m_localSecondaryIndexesHasBeenSet(false),
    m_globalSecondaryIndexesHasBeenSet(false)

ConsumedCapacity::ConsumedCapacity(JsonView jsonValue) :
    m_tableNameHasBeenSet(false),
    m_capacityUnits(0.0),
    m_capacityUnitsHasBeenSet(false),
    m_readCapacityUnits(0.0),
    m_readCapacityUnitsHasBeenSet(false),
    m_writeCapacityUnits(0.0),
    m_writeCapacityUnitsHasBeenSet(false),
    m_tableHasBeenSet(false),
    m_localSecondaryIndexesHasBeenSet(false),
    m_globalSecondaryIndexesHasBeenSet(false)
{
  *this = jsonValue;
}

ConsumedCapacity& ConsumedCapacity::operator =(JsonView jsonValue)
{
  if(jsonValue.ValueExists("TableName"))
  {
    m_tableName = jsonValue.GetString("TableName");

    m_tableNameHasBeenSet = true;
  }

  if(jsonValue.ValueExists("CapacityUnits"))
  {
    m_capacityUnits = jsonValue.GetDouble("CapacityUnits");

    m_capacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("ReadCapacityUnits"))
  {
    m_readCapacityUnits = jsonValue.GetDouble("ReadCapacityUnits");

    m_readCapacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("WriteCapacityUnits"))
  {
    m_writeCapacityUnits = jsonValue.GetDouble("WriteCapacityUnits");

    m_writeCapacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("Table"))
  {
    m_table = jsonValue.GetObject("Table");

    m_tableHasBeenSet = true;
  }

  if(jsonValue.ValueExists("LocalSecondaryIndexes"))
  {
    Aws::Map<Aws::String, JsonView> localSecondaryIndexesJsonMap = jsonValue.GetObject("LocalSecondaryIndexes").GetAllObjects();
    for(auto& localSecondaryIndexesItem : localSecondaryIndexesJsonMap)
    {
      m_localSecondaryIndexes[localSecondaryIndexesItem.first] = localSecondaryIndexesItem.second.AsObject();
    }
    m_localSecondaryIndexesHasBeenSet = true;
  }

  if(jsonValue.ValueExists("GlobalSecondaryIndexes"))
  {
    Aws::Map<Aws::String, JsonView> globalSecondaryIndexesJsonMap = jsonValue.GetObject("GlobalSecondaryIndexes").GetAllObjects();
    for(auto& globalSecondaryIndexesItem : globalSecondaryIndexesJsonMap)
    {
      m_globalSecondaryIndexes[globalSecondaryIndexesItem.first] = globalSecondaryIndexesItem.second.AsObject();
    }
    m_globalSecondaryIndexesHasBeenSet = true;
  }

  return *this;
}

JsonValue ConsumedCapacity::Jsonize() const
{
  JsonValue payload;

  if(m_tableNameHasBeenSet)
  {
   payload.WithString("TableName", m_tableName);

  }

  if(m_capacityUnitsHasBeenSet)
  {
   payload.WithDouble("CapacityUnits", m_capacityUnits);

  }

  if(m_readCapacityUnitsHasBeenSet)
  {
   payload.WithDouble("ReadCapacityUnits", m_readCapacityUnits);

  }

  if(m_writeCapacityUnitsHasBeenSet)
  {
   payload.WithDouble("WriteCapacityUnits", m_writeCapacityUnits);

  }

  if(m_tableHasBeenSet)
  {
   payload.WithObject("Table", m_table.Jsonize());

  }

  if(m_localSecondaryIndexesHasBeenSet)
  {
   JsonValue localSecondaryIndexesJsonMap;
   for(auto& localSecondaryIndexesItem : m_localSecondaryIndexes)
   {
     localSecondaryIndexesJsonMap.WithObject(localSecondaryIndexesItem.first, localSecondaryIndexesItem.second.Jsonize());
   }
   payload.WithObject("LocalSecondaryIndexes", std::move(localSecondaryIndexesJsonMap));

  }

  if(m_globalSecondaryIndexesHasBeenSet)
  {
   JsonValue globalSecondaryIndexesJsonMap;
   for(auto& globalSecondaryIndexesItem : m_globalSecondaryIndexes)
   {
     globalSecondaryIndexesJsonMap.WithObject(globalSecondaryIndexesItem.first, globalSecondaryIndexesItem.second.Jsonize());
   }
   payload.WithObject("GlobalSecondaryIndexes", std::move(globalSecondaryIndexesJsonMap));

  }

  return payload;
}

} // namespace Model
} // namespace DynamoDB
} // namespace Aws


/**
 */

#include <aws/dynamodb/model/ConsumedCapacity.h>
#include <aws/core/utils/json/JsonSerializer.h>

#include <utility>

using namespace Aws::Utils::Json;
using namespace Aws::Utils;

namespace Aws
{
namespace DynamoDB
{
namespace Model
{

ConsumedCapacity::ConsumedCapacity() :
    m_tableNameHasBeenSet(false),
    m_capacityUnits(0.0),
    m_capacityUnitsHasBeenSet(false),
    m_readCapacityUnits(0.0),
    m_readCapacityUnitsHasBeenSet(false),
    m_writeCapacityUnits(0.0),
    m_writeCapacityUnitsHasBeenSet(false),
    m_tableHasBeenSet(false),
    m_localSecondaryIndexesHasBeenSet(false),
    m_globalSecondaryIndexesHasBeenSet(false)
{
}

ConsumedCapacity::ConsumedCapacity(JsonView jsonValue) :
    m_tableNameHasBeenSet(false),
    m_capacityUnits(0.0),
    m_capacityUnitsHasBeenSet(false),
    m_readCapacityUnits(0.0),
    m_readCapacityUnitsHasBeenSet(false),
    m_writeCapacityUnits(0.0),
    m_writeCapacityUnitsHasBeenSet(false),
    m_tableHasBeenSet(false),
    m_localSecondaryIndexesHasBeenSet(false),
    m_globalSecondaryIndexesHasBeenSet(false)
{
  *this = jsonValue;
}

ConsumedCapacity& ConsumedCapacity::operator =(JsonView jsonValue)
{
  if(jsonValue.ValueExists("TableName"))
  {
    m_tableName = jsonValue.GetString("TableName");

    m_tableNameHasBeenSet = true;
  }

  if(jsonValue.ValueExists("CapacityUnits"))
  {
    m_capacityUnits = jsonValue.GetDouble("CapacityUnits");

    m_capacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("ReadCapacityUnits"))
  {
    m_readCapacityUnits = jsonValue.GetDouble("ReadCapacityUnits");

    m_readCapacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("WriteCapacityUnits"))
  {
    m_writeCapacityUnits = jsonValue.GetDouble("WriteCapacityUnits");

    m_writeCapacityUnitsHasBeenSet = true;
  }

  if(jsonValue.ValueExists("Table"))
  {
    m_table = jsonValue.GetObject("Table");

    m_tableHasBeenSet = true;
  }

  if(jsonValue.ValueExists("LocalSecondaryIndexes"))
  {
    Aws::Map<Aws::String, JsonView> localSecondaryIndexesJsonMap = jsonValue.GetObject("LocalSecondaryIndexes").GetAllObjects();
    for(auto& localSecondaryIndexesItem : localSecondaryIndexesJsonMap)
    {
      m_localSecondaryIndexes[localSecondaryIndexesItem.first] = localSecondaryIndexesItem.second.AsObject();
    }
    m_localSecondaryIndexesHasBeenSet = true;
  }

  if(jsonValue.ValueExists("GlobalSecondaryIndexes"))
  {
    Aws::Map<Aws::String, JsonView> globalSecondaryIndexesJsonMap = jsonValue.GetObject("GlobalSecondaryIndexes").GetAllObjects();
    for(auto& globalSecondaryIndexesItem : globalSecondaryIndexesJsonMap)
    {
      m_globalSecondaryIndexes[globalSecondaryIndexesItem.first] = globalSecondaryIndexesItem.second.AsObject();
    }
    m_globalSecondaryIndexesHasBeenSet = true;
  }

  return *this;
}

JsonValue ConsumedCapacity::Jsonize() const
{
  JsonValue payload;

  if(m_tableNameHasBeenSet)
  {
   payload.WithString("TableName", m_tableName);

  }

  if(m_capacityUnitsHasBeenSet)
  {
   payload.WithDouble("CapacityUnits", m_capacityUnits);

  }

  if(m_readCapacityUnitsHasBeenSet)
  {
   payload.WithDouble("ReadCapacityUnits", m_readCapacityUnits);

  }

  if(m_writeCapacityUnitsHasBeenSet)
  {
   payload.WithDouble("WriteCapacityUnits", m_writeCapacityUnits);

  }

  if(m_tableHasBeenSet)
  {
   payload.WithObject("Table", m_table.Jsonize());

  }

  if(m_localSecondaryIndexesHasBeenSet)
  {
   JsonValue localSecondaryIndexesJsonMap;
   for(auto& localSecondaryIndexesItem : m_localSecondaryIndexes)
   {
     localSecondaryIndexesJsonMap.WithObject(localSecondaryIndexesItem.first, localSecondaryIndexesItem.second.Jsonize());
   }
   payload.WithObject("LocalSecondaryIndexes", std::move(localSecondaryIndexesJsonMap));

  }

  if(m_globalSecondaryIndexesHasBeenSet)
  {
   JsonValue globalSecondaryIndexesJsonMap;
   for(auto& globalSecondaryIndexesItem : m_globalSecondaryIndexes)
   {
     globalSecondaryIndexesJsonMap.WithObject(globalSecondaryIndexesItem.first, globalSecondaryIndexesItem.second.Jsonize());
   }
   payload.WithObject("GlobalSecondaryIndexes", std::move(globalSecondaryIndexesJsonMap));

  }

  return payl

}

} // namespace Model
} // namespace DynamoDB
} // namespace Aws


/* bzflag
 *
 */

#include "bzfsAPI.h"

class rabbitTimer : public bz_Plugin
{
public:
    virtual const char* Name ()
    {
        return "Rabit Timer";
    }
    virtual void Init ( const char* config );

    virtual void Event ( bz_EventData *eventData );

    float rabbitKillTimeLimit;    // the max time the rabbit has to kill someone
    bool rollOver;        // whether or not to roll over leftover seconds

    float rabbitDeathTime;

    int currentRabbit;
};

BZ_PLUGIN(rabbitTimer)

{
    if (eventData->eventType == bz_eTickEvent)
    {
        bz_TickEventData_V1* tickdata = (bz_TickEventData_V1*)eventData;

        if (currentRabbit != -1 && tickdata->eventTime >= rabbitDeathTime)
        {
            // kill the wabbit!
            bz_killPlayer(currentRabbit, false, BZ_SERVER);

            // stopgap. the kill event should do this, really...
            currentRabbit = -1;
            rabbitDeathTime = (float)tickdata->eventTime + rabbitKillTimeLimit;

            bz_sendTextMessage (BZ_SERVER, BZ_ALLUSERS, "Time's up! Selecting new rabbit.");
        }
        else if (currentRabbit == -1
                 && bz_getTeamCount(eHunterTeam) >= 3) // make sure we've got enough people before reactivating the timer
        {
            // find the new rabbit
            bz_APIIntList pl;
            bz_getPlayerIndexList(&pl);

            for (unsigned int i = 0; i < pl.size() && currentRabbit == -1; i++)
            {
                bz_BasePlayerRecord* pr = bz_getPlayerByIndex(pl.get(i));

                if (pr != NULL)
                {
                    if (pr->team == eRabbitTeam)
                    {
                        currentRabbit = pr->playerID;
                        int limit = (int)rabbitKillTimeLimit;
                        bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("You have %d seconds to make a kill!", limit));
                    }
                    bz_freePlayerRecord(pr);
                }
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerDieEvent)
    {

        bz_PlayerDieEventData_V1* killdata = (bz_PlayerDieEventData_V1*)eventData;

        if (killdata->team == eRabbitTeam)
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;
        }
        else if (killdata->killerTeam == eRabbitTeam && currentRabbit != -1)
        {
            if (rollOver)
            {
                rabbitDeathTime += rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                int timeremaining = (int)(rabbitDeathTime - killdata->eventTime);

                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("+%d seconds: %d seconds remaining.", limit, timeremaining));
            }
            else
            {
                rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("%d seconds remaining.", limit));
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerPartEvent)
    {
        bz_PlayerJoinPartEventData_V1* partdata = (bz_PlayerJoinPartEventData_V1*)eventData;

        if (partdata->record->team == eRabbitTeam) // we need to select a new rabbit if the rabbit leaves.
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)partdata->eventTime + rabbitKillTimeLimit;
        }
    }
}

void rabbitTimer::Init(const char* commandLine)
{
    rabbitKillTimeLimit = 30.0;
    rollOver = false;
    currentRabbit = -1;
    rabbitDeathTime = 3600.0; // something large

    std::string param = commandLine;

    if (param.size() > 0 && param.at(0) == '+')
    {
        rollOver = true;
        param  = param.erase(0,1);
    }

    int newtime = atoi(param.c_str());

    if (newtime > 0)
        rabbitKillTimeLimit = (float)newtime;

    Register(bz_ePlayerDieEvent);
    Register(bz_ePlayerPartEvent);
    Register(bz_eTickEvent);
}


/* bzflag
 *
 */

#include "bzfsAPI.h"

class rabbitTimer : public bz_Plugin
{
public:
    virtual const char* Name ()

    virtual void Init ( const char* config );

    virtual void Event ( bz_EventData *eventData );

    float rabbitKillTimeLimit;    // the max time the rabbit has to kill someone
    bool rollOver;        // whether or not to roll over leftover seconds

    float rabbitDeathTime;

    int currentRabbit;
};

BZ_PLUGIN(rabbitTimer)

void rabbitTimer::Event(bz_EventData *eventData)
{
    if (eventData->eventType == bz_eTickEvent)
    {
        bz_TickEventData_V1* tickdata = (bz_TickEventData_V1*)eventData;

        if (currentRabbit != -1 && tickdata->eventTime >= rabbitDeathTime)
        {
            // kill the wabbit!
            bz_killPlayer(currentRabbit, false, BZ_SERVER);

            // stopgap. the kill event should do this, really...
            currentRabbit = -1;
            rabbitDeathTime = (float)tickdata->eventTime + rabbitKillTimeLimit;

            bz_sendTextMessage (BZ_SERVER, BZ_ALLUSERS, "Time's up! Selecting new rabbit.");
        }
        else if (currentRabbit == -1
                 && bz_getTeamCount(eHunterTeam) >= 3) // make sure we've got enough people before reactivating the timer
        {
            // find the new rabbit
            bz_APIIntList pl;
            bz_getPlayerIndexList(&pl);

            for (unsigned int i = 0; i < pl.size() && currentRabbit == -1; i++)
            {
                bz_BasePlayerRecord* pr = bz_getPlayerByIndex(pl.get(i));

                if (pr != NULL)
                {
                    if (pr->team == eRabbitTeam)
                    {
                        currentRabbit = pr->playerID;
                        int limit = (int)rabbitKillTimeLimit;
                        bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("You have %d seconds to make a kill!", limit));
                    }
                    bz_freePlayerRecord(pr);
                }
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerDieEvent)
    {

        bz_PlayerDieEventData_V1* killdata = (bz_PlayerDieEventData_V1*)eventData;

        if (killdata->team == eRabbitTeam)
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;
        }
        else if (killdata->killerTeam == eRabbitTeam && currentRabbit != -1)
        {
            if (rollOver)
            {
                rabbitDeathTime += rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                int timeremaining = (int)(rabbitDeathTime - killdata->eventTime);

                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("+%d seconds: %d seconds remaining.", limit, timeremaining));
            }
            else
            {
                rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("%d seconds remaining.", limit));
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerPartEvent)
    {
        bz_PlayerJoinPartEventData_V1* partdata = (bz_PlayerJoinPartEventData_V1*)eventData;

        if (partdata->record->team == eRabbitTeam) // we need to select a new rabbit if the rabbit leaves.
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)partdata->eventTime + rabbitKillTimeLimit;
        }
    }
}

void rabbitTimer::Init(const char* commandLine)
{
    rabbitKillTimeLimit = 30.0;
    rollOver = false;
    currentRabbit = -1;
    rabbitDeathTime = 3600.0; // something large

    std::string param = commandLine;

    if (param.size() > 0 && param.at(0) == '+')
    {
        rollOver = true;
        param  = param.erase(0,1);
    }

    int newtime = atoi(param.c_str());

    if (newtime > 0)
        rabbitKillTimeLimit = (float)newtime;

    Register(bz_ePlayerDieEvent);
    Register(bz_ePlayerPartEvent);
    Register(bz_eTickEvent);
}


/* bzflag
 *
 */

#include "bzfsAPI.h"

class rabbitTimer : public bz_Plugin
{
public:
    virtual const char* Name ()
    {
        return "Rabit Timer";
    }
    virtual void Init ( const char* config );

    virtual void Event ( bz_EventData *eventData );

    float rabbitKillTimeLimit;    // the max time the rabbit has to kill someone
    bool rollOver;        // whether or not to roll over leftover seconds

    float rabbitDeathTime;

    int currentRabbit;
};

BZ_PLUGIN(rabbitTimer)

void rabbitTimer::Event(bz_EventData *eventData)
{
    if (eventData->eventType == bz_eTickEvent)
    {
        bz_TickEventData_V1* tickdata = (bz_TickEventData_V1*)eventData;

        if (currentRabbit != -1 && tickdata->eventTime >= rabbitDeathTime)
        {
            // kill the wabbit!
            bz_killPlayer(currentRabbit, false, BZ_SERVER);

            // stopgap. the kill event should do this, really...
            currentRabbit = -1;
            rabbitDeathTime = (float)tickdata->eventTime + rabbitKillTimeLimit;

            bz_sendTextMessage (BZ_SERVER, BZ_ALLUSERS, "Time's up! Selecting new rabbit.");
        }
        else if (currentRabbit == -1
                 && bz_getTeamCount(eHunterTeam) >= 3) // make sure we've got enough people before reactivating the timer
        {
            // find the new rabbit
            bz_APIIntList pl;
            bz_getPlayerIndexList(&pl);

            for (unsigned int i = 0; i < pl.size() && currentRabbit == -1; i++)
            {
                bz_BasePlayerRecord* pr = bz_getPlayerByIndex(pl.get(i));

                if (pr != NULL)
                {
                    if (pr->team == eRabbitTeam)
                    {
                        currentRabbit = pr->playerID;
                        int limit = (int)rabbitKillTimeLimit;
                        bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("You have %d seconds to make a kill!", limit));
                    }
                    bz_freePlayerRecord(pr);
                }
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerDieEvent)
    {

        bz_PlayerDieEventData_V1* killdata = (bz_PlayerDieEventData_V1*)eventData;

        if (killdata->team == eRabbitTeam)
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;
        }
        else if (killdata->killerTeam == eRabbitTeam && currentRabbit != -1)
        {
            if (rollOver)
            {
                rabbitDeathTime += rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                int timeremaining = (int)(rabbitDeathTime - killdata->eventTime);

                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("+%d seconds: %d seconds remaining.", limit, timeremaining));
            }
            else
            {
                rabbitDeathTime = (float)killdata->eventTime + rabbitKillTimeLimit;

                int limit = (int)rabbitKillTimeLimit;
                bz_sendTextMessage(BZ_SERVER, currentRabbit, bz_format("%d seconds remaining.", limit));
            }
        }
    }
    else if (eventData->eventType == bz_ePlayerPartEvent)
    {
        bz_PlayerJoinPartEventData_V1* partdata = (bz_PlayerJoinPartEventData_V1*)eventData;

        if (partdata->record->team == eRabbitTeam) // we need to select a new rabbit if the rabbit leaves.
        {
            currentRabbit = -1; // we will sort this out on the next tick

            rabbitDeathTime = (float)partdata->eventTime + rabbitKillTimeLimit;
        }
    }
}

void rabbitTimer::Init(const char* commandLine)
{
    rabbitKillTimeLimit = 30.0;
    rollOver = false;
    currentRabbit = -1;
    rabbitDeathTime = 3600.0; // something large

    std::string param = commandLine;

    if (param.size() > 0 && param.at(0) == '+')
    {
        rollOver = true;
        param  = param.erase(0,1);
    }

    int newtime = atoi(param.c_str());

    if (newtime > 0)
        rabbitKillTimeLimit = (float)newtime;

    Register(bz_ePlayerDieEvent);
    Register(bz_ePlayerPartEvent);
    Register(bz_eTi


#include "UnitTestsUtils.hpp"
#include "gtest/gtest.h"

#include <Core/Geometry/TriangleMesh3.hpp>

using namespace CubbyFlow;

TEST(TriangleMesh3, Constructors)
{
    TriangleMesh3 mesh1;
    EXPECT_EQ(0u, mesh1.NumberOfPoints());
    EXPECT_EQ(0u, mesh1.NumberOfNormals());
    EXPECT_EQ(0u, mesh1.NumberOfUVs());
    EXPECT_EQ(0u, mesh1.NumberOfTriangles());
}

TEST(TriangleMesh3, ReadObj)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_EQ(56u, mesh.NumberOfPoints());
    EXPECT_EQ(96u, mesh.NumberOfNormals());
    EXPECT_EQ(76u, mesh.NumberOfUVs());
    EXPECT_EQ(108u, mesh.NumberOfTriangles());
}

TEST(TriangleMesh3, ClosestPoint)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(localResult);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestPoint(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestNormal)
{
    std::string objStr = GetSphereTriMesh5x5Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestNormal(pt);
            auto closestPt = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(closestPt);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestNormal(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_NEAR(expected, actual, 1e-9);
    }
}

TEST(TriangleMesh3, ClosestDistance)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist = std::numeric_limits<double>::max();

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestDistance(pt);

            if (localResult < minDist)
            {
                minDist = localResult;
            }
        }

        return minDist;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestDistance(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_DOUBLE_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, Intersects)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            if (tri.Intersects(ray))
            {
                return true;
            }
        }

        return false;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        bool actual = mesh.Intersects(ray);
        bool expected = bruteForceTest(ray);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestIntersection)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        SurfaceRayIntersection3 result{};

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestIntersection(ray);

            if (localResult.distance < result.distance)
            {
                result = localResult;
            }
        }

        return result;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        auto actual = mesh.ClosestIntersection(ray);
        auto expected = bruteForceTest(ray);
        EXPECT_DOUBLE_EQ(expected.distance, actual.distance);
        EXPECT_VECTOR3_EQ(expected.point, actual.point);
        EXPECT_VECTOR3_EQ(expected.normal, actual.normal);
        EXPECT_EQ(expected.isIntersecting, actual.isIntersecting);
    }
}

{
    const std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const size_t numSamples = GetNumberOfSamplePoints3();

    for (size_t i = 0; i < numSamples; ++i)
    {
        Vector3D p = GetSamplePoints3()[i];
        auto actual = mesh.IsInside(p);
        auto expected = mesh.GetBoundingBox().Contains(p);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, BoundingBox)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_BOUNDING_BOX3_EQ(
        BoundingBox3D({ -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 }),
        mesh.GetBoundingBox());
}

TEST(TriangleMesh3, Builder)
{
    TriangleMesh3::PointArray points = { Vector3D(1, 2, 3), Vector3D(4, 5, 6),
                                         Vector3D(7, 8, 9),
                                         Vector3D(10, 11, 12) };

    TriangleMesh3::NormalArray normals = { Vector3D(10, 11, 12),
                                           Vector3D(7, 8, 9), Vector3D(4, 5, 6),
                                           Vector3D(1, 2, 3) };

    TriangleMesh3::UVArray uvs = { Vector2D(13, 14), Vector2D(15, 16) };

    TriangleMesh3::IndexArray pointIndices = { Vector3UZ(0, 1, 2),
                                               Vector3UZ(0, 1, 3) };

    TriangleMesh3::IndexArray normalIndices = { Vector3UZ(1, 2, 3),
                                                Vector3UZ(2, 1, 0) };

    TriangleMesh3::IndexArray uvIndices = { Vector3UZ(1, 0, 2),
                                            Vector3UZ(3, 1, 0) };

    TriangleMesh3 mesh = TriangleMesh3::GetBuilder()
                             .WithPoints(points)
                             .WithNormals(normals)
                             .WithUVs(uvs)
                             .WithPointIndices(pointIndices)
                             .WithNormalIndices(normalIndices)
                             .WithUVIndices(uvIndices)
                             .Build();

    EXPECT_EQ(4u, mesh.NumberOfPoints());
    EXPECT_EQ(4u, mesh.NumberOfNormals());
    EXPECT_EQ(2u, mesh.NumberOfUVs());
    EXPECT_EQ(2u, mesh.NumberOfTriangles());

    for (size_t i = 0; i < mesh.NumberOfPoints(); ++i)
    {
        EXPECT_EQ(points[i], mesh.Point(i));
    }

    for (size_t i = 0; i < mesh.NumberOfNormals(); ++i)
    {
        EXPECT_EQ(normals[i], mesh.Normal(i));
    }

    for (size_t i = 0; i < mesh.NumberOfUVs(); ++i)
    {
        EXPECT_EQ(uvs[i], mesh.UV(i));
    }

    for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
    {
        EXPECT_EQ(pointIndices[i], mesh.PointIndex(i));
        EXPECT_EQ(normalIndices[i], mesh.NormalIndex(i));
        EXPECT_EQ(uvIndices[i], mesh.UVIndex(i));
    }
}


{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_BOUNDING_BOX3_EQ(
        BoundingBox3D({ -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 }),
        mesh.GetBoundingBox());
}

#include "UnitTestsUtils.hpp"
#include "gtest/gtest.h"

#include <Core/Geometry/TriangleMesh3.hpp>

using namespace CubbyFlow;

TEST(TriangleMesh3, Constructors)
{
    TriangleMesh3 mesh1;
    EXPECT_EQ(0u, mesh1.NumberOfPoints());
    EXPECT_EQ(0u, mesh1.NumberOfNormals());
    EXPECT_EQ(0u, mesh1.NumberOfUVs());
    EXPECT_EQ(0u, mesh1.NumberOfTriangles());
}

TEST(TriangleMesh3, ReadObj)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_EQ(56u, mesh.NumberOfPoints());
    EXPECT_EQ(96u, mesh.NumberOfNormals());
    EXPECT_EQ(76u, mesh.NumberOfUVs());
    EXPECT_EQ(108u, mesh.NumberOfTriangles());
}

TEST(TriangleMesh3, ClosestPoint)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(localResult);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestPoint(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestNormal)
{
    std::string objStr = GetSphereTriMesh5x5Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestNormal(pt);
            auto closestPt = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(closestPt);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestNormal(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_NEAR(expected, actual, 1e-9);
    }
}

TEST(TriangleMesh3, ClosestDistance)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist = std::numeric_limits<double>::max();

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestDistance(pt);

            if (localResult < minDist)
            {
                minDist = localResult;
            }
        }

        return minDist;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestDistance(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_DOUBLE_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, Intersects)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            if (tri.Intersects(ray))
            {
                return true;
            }
        }

        return false;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        bool actual = mesh.Intersects(ray);
        bool expected = bruteForceTest(ray);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestIntersection)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        SurfaceRayIntersection3 result{};

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestIntersection(ray);

            if (localResult.distance < result.distance)
            {
                result = localResult;
            }
        }

        return result;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        auto actual = mesh.ClosestIntersection(ray);
        auto expected = bruteForceTest(ray);
        EXPECT_DOUBLE_EQ(expected.distance, actual.distance);
        EXPECT_VECTOR3_EQ(expected.point, actual.point);
        EXPECT_VECTOR3_EQ(expected.normal, actual.normal);
        EXPECT_EQ(expected.isIntersecting, actual.isIntersecting);
    }
}

TEST(TriangleMesh3, IsInside)
{
    const std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const size_t numSamples = GetNumberOfSamplePoints3();

    for (size_t i = 0; i < numSamples; ++i)
    {
        Vector3D p = GetSamplePoints3()[i];
        auto actual = mesh.IsInside(p);
        auto expected = mesh.GetBoundingBox().Contains(p);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, BoundingBox)

TEST(TriangleMesh3, Builder)
{
    TriangleMesh3::PointArray points = { Vector3D(1, 2, 3), Vector3D(4, 5, 6),
                                         Vector3D(7, 8, 9),
                                         Vector3D(10, 11, 12) };

    TriangleMesh3::NormalArray normals = { Vector3D(10, 11, 12),
                                           Vector3D(7, 8, 9), Vector3D(4, 5, 6),
                                           Vector3D(1, 2, 3) };

    TriangleMesh3::UVArray uvs = { Vector2D(13, 14), Vector2D(15, 16) };

    TriangleMesh3::IndexArray pointIndices = { Vector3UZ(0, 1, 2),
                                               Vector3UZ(0, 1, 3) };

    TriangleMesh3::IndexArray normalIndices = { Vector3UZ(1, 2, 3),
                                                Vector3UZ(2, 1, 0) };

    TriangleMesh3::IndexArray uvIndices = { Vector3UZ(1, 0, 2),
                                            Vector3UZ(3, 1, 0) };

    TriangleMesh3 mesh = TriangleMesh3::GetBuilder()
                             .WithPoints(points)
                             .WithNormals(normals)
                             .WithUVs(uvs)
                             .WithPointIndices(pointIndices)
                             .WithNormalIndices(normalIndices)
                             .WithUVIndices(uvIndices)
                             .Build();

    EXPECT_EQ(4u, mesh.NumberOfPoints());
    EXPECT_EQ(4u, mesh.NumberOfNormals());
    EXPECT_EQ(2u, mesh.NumberOfUVs());
    EXPECT_EQ(2u, mesh.NumberOfTriangles());

    for (size_t i = 0; i < mesh.NumberOfPoints(); ++i)
    {
        EXPECT_EQ(points[i], mesh.Point(i));
    }

    for (size_t i = 0; i < mesh.NumberOfNormals(); ++i)
    {
        EXPECT_EQ(normals[i], mesh.Normal(i));
    }

    for (size_t i = 0; i < mesh.NumberOfUVs(); ++i)
    {
        EXPECT_EQ(uvs[i], mesh.UV(i));
    }

    for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
    {
        EXPECT_EQ(pointIndices[i], mesh.PointIndex(i));
        EXPECT_EQ(normalIndices[i], mesh.NormalIndex(i));
        EXPECT_EQ(uvIndices[i], mesh.UVIndex(i));
    }
}


#include "UnitTestsUtils.hpp"
#include "gtest/gtest.h"

#include <Core/Geometry/TriangleMesh3.hpp>

using namespace CubbyFlow;

TEST(TriangleMesh3, Constructors)
{
    TriangleMesh3 mesh1;
    EXPECT_EQ(0u, mesh1.NumberOfPoints());
    EXPECT_EQ(0u, mesh1.NumberOfNormals());
    EXPECT_EQ(0u, mesh1.N

    EXPECT_EQ(0u, mesh1.NumberOfTriangles());
}

TEST(TriangleMesh3, ReadObj)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_EQ(56u, mesh.NumberOfPoints());
    EXPECT_EQ(96u, mesh.NumberOfNormals());
    EXPECT_EQ(76u, mesh.NumberOfUVs());
    EXPECT_EQ(108u, mesh.NumberOfTriangles());
}

TEST(TriangleMesh3, ClosestPoint)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(localResult);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestPoint(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestNormal)
{
    std::string objStr = GetSphereTriMesh5x5Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestNormal(pt);
            auto closestPt = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(closestPt);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestNormal(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_NEAR(expected, actual, 1e-9);
    }
}

TEST(TriangleMesh3, ClosestDistance)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist = std::numeric_limits<double>::max();

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestDistance(pt);

            if (localResult < minDist)
            {
                minDist = localResult;
            }
        }

        return minDist;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestDistance(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_DOUBLE_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, Intersects)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            if (tri.Intersects(ray))
            {
                return true;
            }
        }

        return false;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        bool actual = mesh.Intersects(ray);
        bool expected = bruteForceTest(ray);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestIntersection)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        SurfaceRayIntersection3 result{};

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestIntersection(ray);

            if (localResult.distance < result.distance)
            {
                result = localResult;
            }
        }

        return result;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        auto actual = mesh.ClosestIntersection(ray);
        auto expected = bruteForceTest(ray);
        EXPECT_DOUBLE_EQ(expected.distance, actual.distance);
        EXPECT_VECTOR3_EQ(expected.point, actual.point);
        EXPECT_VECTOR3_EQ(expected.normal, actual.normal);
        EXPECT_EQ(expected.isIntersecting, actual.isIntersecting);
    }
}

TEST(TriangleMesh3, IsInside)
{
    const std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const size_t numSamples = GetNumberOfSamplePoints3();

    for (size_t i = 0; i < numSamples; ++i)
    {
        Vector3D p = GetSamplePoints3()[i];
        auto actual = mesh.IsInside(p);
        auto expected = mesh.GetBoundingBox().Contains(p);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, BoundingBox)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_BOUNDING_BOX3_EQ(
        BoundingBox3D({ -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 }),
        mesh.GetBoundingBox());
}

TEST(TriangleMesh3, Builder)
{
    TriangleMesh3::PointArray points = { Vector3D(1, 2, 3), Vector3D(4, 5, 6),
                                         Vector3D(7, 8, 9),
                                         Vector3D(10, 11, 12) };

    TriangleMesh3::NormalArray normals = { Vector3D(10, 11, 12),
                                           Vector3D(7, 8, 9), Vector3D(4, 5, 6),
                                           Vector3D(1, 2, 3) };

    TriangleMesh3::UVArray uvs = { Vector2D(13, 14), Vector2D(15, 16) };

    TriangleMesh3::IndexArray pointIndices = { Vector3UZ(0, 1, 2),
                                               Vector3UZ(0, 1, 3) };

    TriangleMesh3::IndexArray normalIndices = { Vector3UZ(1, 2, 3),
                                                Vector3UZ(2, 1, 0) };

    TriangleMesh3::IndexArray uvIndices = { Vector3UZ(1, 0, 2),
                                            Vector3UZ(3, 1, 0) };

    TriangleMesh3 mesh = TriangleMesh3::GetBuilder()
                             .WithPoints(points)
                             .WithNormals(normals)
                             .WithUVs(uvs)
                             .WithPointIndices(pointIndices)
                             .WithNormalIndices(normalIndices)
                             .WithUVIndices(uvIndices)
                             .Build();

    EXPECT_EQ(4u, mesh.NumberOfPoints());
    EXPECT_EQ(4u, mesh.NumberOfNormals());
    EXPECT_EQ(2u, mesh.NumberOfUVs());
    EXPECT_EQ(2u, mesh.NumberOfTriangles());

    for (size_t i = 0; i < mesh.NumberOfPoints(); ++i)
    {
        EXPECT_EQ(points[i], mesh.Point(i));
    }

    for (size_t i = 0; i < mesh.NumberOfNormals(); ++i)
    {
        EXPECT_EQ(normals[i], mesh.Normal(i));
    }

    for (size_t i = 0; i < mesh.NumberOfUVs(); ++i)
    {
        EXPECT_EQ(uvs[i], mesh.UV(i));
    }

    for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
    {
        EXPECT_EQ(pointIndices[i], mesh.PointIndex(i));
        EXPECT_EQ(normalIndices[i], mesh.NormalIndex(i));
        EXPECT_EQ(uvIndices[i], mesh.UVIndex(i));
    }
}


for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestPoint(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_EQ(expected, actual);
    }

#include "UnitTestsUtils.hpp"
#include "gtest/gtest.h"

#include <Core/Geometry/TriangleMesh3.hpp>

using namespace CubbyFlow;

TEST(TriangleMesh3, Constructors)
{
    TriangleMesh3 mesh1;
    EXPECT_EQ(0u, mesh1.NumberOfPoints());
    EXPECT_EQ(0u, mesh1.NumberOfNormals());
    EXPECT_EQ(0u, mesh1.NumberOfUVs());
    EXPECT_EQ(0u, mesh1.NumberOfTriangles());
}

TEST(TriangleMesh3, ReadObj)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_EQ(56u, mesh.NumberOfPoints());
    EXPECT_EQ(96u, mesh.NumberOfNormals());
    EXPECT_EQ(76u, mesh.NumberOfUVs());
    EXPECT_EQ(108u, mesh.NumberOfTriangles());
}

TEST(TriangleMesh3, ClosestPoint)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(localResult);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();

}

TEST(TriangleMesh3, ClosestNormal)
{
    std::string objStr = GetSphereTriMesh5x5Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist2 = std::numeric_limits<double>::max();
        Vector3D result;

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestNormal(pt);
            auto closestPt = tri.ClosestPoint(pt);
            double localDist2 = pt.DistanceSquaredTo(closestPt);

            if (localDist2 < minDist2)
            {
                minDist2 = localDist2;
                result = localResult;
            }
        }

        return result;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestNormal(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_VECTOR3_NEAR(expected, actual, 1e-9);
    }
}

TEST(TriangleMesh3, ClosestDistance)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const auto bruteForceSearch = [&](const Vector3D& pt) {
        double minDist = std::numeric_limits<double>::max();

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestDistance(pt);

            if (localResult < minDist)
            {
                minDist = localResult;
            }
        }

        return minDist;
    };

    size_t numSamples = GetNumberOfSamplePoints3();
    for (size_t i = 0; i < numSamples; ++i)
    {
        auto actual = mesh.ClosestDistance(GetSamplePoints3()[i]);
        auto expected = bruteForceSearch(GetSamplePoints3()[i]);
        EXPECT_DOUBLE_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, Intersects)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            if (tri.Intersects(ray))
            {
                return true;
            }
        }

        return false;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        bool actual = mesh.Intersects(ray);
        bool expected = bruteForceTest(ray);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, ClosestIntersection)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    size_t numSamples = GetNumberOfSamplePoints3();

    const auto bruteForceTest = [&](const Ray3D& ray) {
        SurfaceRayIntersection3 result{};

        for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
        {
            Triangle3 tri = mesh.Triangle(i);
            auto localResult = tri.ClosestIntersection(ray);

            if (localResult.distance < result.distance)
            {
                result = localResult;
            }
        }

        return result;
    };

    for (size_t i = 0; i < numSamples; ++i)
    {
        Ray3D ray(GetSamplePoints3()[i], GetSampleDirs3()[i]);
        auto actual = mesh.ClosestIntersection(ray);
        auto expected = bruteForceTest(ray);
        EXPECT_DOUBLE_EQ(expected.distance, actual.distance);
        EXPECT_VECTOR3_EQ(expected.point, actual.point);
        EXPECT_VECTOR3_EQ(expected.normal, actual.normal);
        EXPECT_EQ(expected.isIntersecting, actual.isIntersecting);
    }
}

TEST(TriangleMesh3, IsInside)
{
    const std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    const size_t numSamples = GetNumberOfSamplePoints3();

    for (size_t i = 0; i < numSamples; ++i)
    {
        Vector3D p = GetSamplePoints3()[i];
        auto actual = mesh.IsInside(p);
        auto expected = mesh.GetBoundingBox().Contains(p);
        EXPECT_EQ(expected, actual);
    }
}

TEST(TriangleMesh3, BoundingBox)
{
    std::string objStr = GetCubeTriMesh3x3x3Obj();
    std::istringstream objStream(objStr);

    TriangleMesh3 mesh;
    [[maybe_unused]] bool isLoaded = mesh.ReadObj(&objStream);

    EXPECT_BOUNDING_BOX3_EQ(
        BoundingBox3D({ -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 }),
        mesh.GetBoundingBox());
}

TEST(TriangleMesh3, Builder)
{
    TriangleMesh3::PointArray points = { Vector3D(1, 2, 3), Vector3D(4, 5, 6),
                                         Vector3D(7, 8, 9),
                                         Vector3D(10, 11, 12) };

    TriangleMesh3::NormalArray normals = { Vector3D(10, 11, 12),
                                           Vector3D(7, 8, 9), Vector3D(4, 5, 6),
                                           Vector3D(1, 2, 3) };

    TriangleMesh3::UVArray uvs = { Vector2D(13, 14), Vector2D(15, 16) };

    TriangleMesh3::IndexArray pointIndices = { Vector3UZ(0, 1, 2),
                                               Vector3UZ(0, 1, 3) };

    TriangleMesh3::IndexArray normalIndices = { Vector3UZ(1, 2, 3),
                                                Vector3UZ(2, 1, 0) };

    TriangleMesh3::IndexArray uvIndices = { Vector3UZ(1, 0, 2),
                                            Vector3UZ(3, 1, 0) };

    TriangleMesh3 mesh = TriangleMesh3::GetBuilder()
                             .WithPoints(points)
                             .WithNormals(normals)
                             .WithUVs(uvs)
                             .WithPointIndices(pointIndices)
                             .WithNormalIndices(normalIndices)
                             .WithUVIndices(uvIndices)
                             .Build();

    EXPECT_EQ(4u, mesh.NumberOfPoints());
    EXPECT_EQ(4u, mesh.NumberOfNormals());
    EXPECT_EQ(2u, mesh.NumberOfUVs());
    EXPECT_EQ(2u, mesh.NumberOfTriangles());

    for (size_t i = 0; i < mesh.NumberOfPoints(); ++i)
    {
        EXPECT_EQ(points[i], mesh.Point(i));
    }

    for (size_t i = 0; i < mesh.NumberOfNormals(); ++i)
    {
        EXPECT_EQ(normals[i], mesh.Normal(i));
    }

    for (size_t i = 0; i < mesh.NumberOfUVs(); ++i)
    {
        EXPECT_EQ(uvs[i], mesh.UV(i));
    }

    for (size_t i = 0; i < mesh.NumberOfTriangles(); ++i)
    {
        EXPECT_EQ(pointIndices[i], mesh.PointIndex(i));
        EXPECT_EQ(normalIndices[i], mesh.NormalIndex(i));
        EXPECT_EQ(uvIndices[i], mesh.UVIndex(i));
    }
}