| #include "CPhysicsEngine.h"
|
| #include "CEngine.h"
|
| #include "CGame.h"
|
| #include "CResourcesManager.h"
|
| #include "CConVarManager.h"
|
| #include "CBinaryFile.h"
|
| #include "U_Files.h"
|
| #include "CBufferWrapper.h"
|
| #include "CScopeExit.h"
|
| #include "U_JSON.h"
|
|
|
| #include "U_General.h"
|
| #include "VHACD.h"
|
|
|
| CPhysicsWorld* CPhysicsEngine::CreateWorld()
|
| {
|
| auto vworld = V_CreateWorld();
|
| if(!vworld) { return nullptr; }
|
|
|
| vworld->OverridePersistent(this); //TODO should we do it here?
|
|
|
| glm::vec3 Gravity = DefaultGravity;
|
| COMPONENT_CALL_GET(Gravity, CConVarManager, GetConVarValue<glm::vec3>("phys.gravity", DefaultGravity));
|
|
|
| m_Worlds.push_back(std::move(vworld));
|
| auto& world = m_Worlds.back();
|
|
|
| world->SetGravity(Gravity);
|
|
|
| //mgr->AddConVar("phys.gravity", new CWrapable<glm::vec3>(DefaultGravity));
|
| //mgr->AddConVar("phys.substeps", new CWrapable<int>(10));
|
| //mgr->AddConVar("phys.fixedtimestep", new CWrapable<int>(60));
|
|
|
| return world.get();
|
| }
|
|
|
| void PhysicsShapes::CBasic::Load(const nlohmann::json& _json) { V_Load(_json); Build(); }
|
| void PhysicsShapes::CBasic::V_Load(const nlohmann::json& _json) {}
|
|
|
| void PhysicsShapes::CBox::V_Load(const nlohmann::json& _json)
|
| {
|
| HalfExtents = GetFromJson(_json, "halfextents", glm::vec3(1.0f, 1.0f, 1.0f));
|
| }
|
|
|
| void PhysicsShapes::CCapsule::V_Load(const nlohmann::json& _json)
|
| {
|
| Radius = GetFromJson(_json, "radius", 1.0f);
|
| Height = GetFromJson(_json, "height", 2.0f);
|
| }
|
|
|
| void PhysicsShapes::CSphere::V_Load(const nlohmann::json& _json)
|
| {
|
| Radius = GetFromJson(_json, "radius", 1.0f);
|
| }
|
|
|
| void PhysicsShapes::CTriMesh::LoadTrimesh(const std::filesystem::path& path)
|
| {
|
| Log::Instance() << "Loading trimesh " << path.string() << Log::Endl;
|
|
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
| auto _trimesh = resman->GetOrCreate("trimesh", path.string());
|
|
|
| _trimesh->Wait();
|
| auto trimesh = dynamic_cast<CTriangleMesh*>(_trimesh.get());
|
|
|
| for(auto& v : trimesh->Vertices)
|
| {
|
| Vertices.push_back(v);
|
|
|
| if(v.x >= 10000.0f || v.y >= 10000.0f || v.z >= 10000.0f)
|
| {
|
| Log::ErrInstance() << "AT LOADTRIMESH TOO FAR AWAY " << v << Log::Endl;
|
| }
|
| }
|
|
|
| for(auto& tri : trimesh->Triangles)
|
| {
|
| Triangles.push_back
|
| (
|
| {
|
| static_cast<glm::uvec3::value_type>(tri.Index.x),
|
| static_cast<glm::uvec3::value_type>(tri.Index.y),
|
| static_cast<glm::uvec3::value_type>(tri.Index.z)
|
| }
|
| );
|
| }
|
| Log::Instance() << "Loaded" << Log::Endl;
|
| }
|
|
|
| void PhysicsShapes::CTriMesh::V_Load(const nlohmann::json& _json)
|
| {
|
| std::string path;
|
| path = GetFromJson<std::string>(_json, "mesh_path", "");
|
|
|
| LoadTrimesh(path);
|
| }
|
|
|
| void PhysicsShapes::CConvex::V_Load(const nlohmann::json& _json)
|
| {
|
| std::string path;
|
| path = GetFromJson<std::string>(_json, "mesh_path", "");
|
|
|
| Log::Instance() << "Loading " << path << " for convex\n";
|
| LoadTrimesh(path);
|
| }
|
|
|
| void PhysicsShapes::CCompound::V_Load(const nlohmann::json& _json)
|
| {
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| if(!game) { return; }
|
|
|
| if(_json.contains("shapes"))
|
| {
|
| auto& val = _json["shapes"];
|
| if(val.is_array())
|
| {
|
| for(auto& kv : val.items())
|
| {
|
| if(kv.value().is_object())
|
| {
|
| auto& obj = kv.value();
|
|
|
| CTransform trans;
|
| auto _shape = CCollision::ParseShape(obj, &trans);
|
| auto shape = std::shared_ptr<PhysicsShapes::CBasic>(_shape.release()); //TODO why shared
|
|
|
| Childs.push_back({ trans, shape });
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| void CWorldUnit::SetCollision(std::shared_ptr<CCollision> _col)
|
| {
|
| V_SetCollision(_col);
|
| m_Collision = _col;
|
| }
|
|
|
| std::shared_ptr<CCollision> CWorldUnit::GetCollision() const { return m_Collision; }
|
| void CWorldUnit::V_SetCollision(std::shared_ptr<CCollision> _col) {}
|
|
|
| void CRigidBody::SetMass(float mass)
|
| {
|
| V_SetMass(mass);
|
| m_Mass = mass;
|
| }
|
|
|
| float CRigidBody::GetMass() const { return m_Mass; }
|
| void CRigidBody::V_SetMass(float mass) {}
|
|
|
| void CWorldUnit::SetTransform(const CTransform& trans)
|
| {
|
| Transform = trans;
|
| }
|
|
|
| bool CWorldUnit::IsUpdatingInternalTransform() const
|
| {
|
| return m_updatingInternalTransform;
|
| }
|
|
|
| void CWorldUnit::UpdateInternalTransform()
|
| {
|
| auto newTrans = GetInternalTransform();
|
|
|
| auto newMatrix = newTrans.GetModelMatrix();
|
| auto matrix = Transform.GetModelMatrix();
|
|
|
| if(newMatrix != matrix) //TODO may be inefficient
|
| {
|
| m_updatingInternalTransform = true;
|
| Transform = GetInternalTransform();
|
| m_updatingInternalTransform = false;
|
| }
|
| }
|
|
|
| void CWorldUnit::m_setCollisionPtr(std::shared_ptr<CCollision> _col)
|
| {
|
| m_Collision = _col;
|
| }
|
|
|
| CTransform CWorldUnit::GetInternalTransform()
|
| {
|
| return GetTransform();
|
| }
|
|
|
| CTransform CWorldUnit::GetTransform() const
|
| {
|
| return Transform;
|
| }
|
|
|
| void CRigidBody::SetLinearVelocity(const glm::vec3& vel) {}
|
| void CRigidBody::SetAngularVelocity(const glm::vec3& ang_vel) {}
|
|
|
| glm::vec3 CRigidBody::GetLinearVelocity() const { return { 0.0f, 0.0f, 0.0f }; }
|
| glm::vec3 CRigidBody::GetAngularVelocity() const { return { 0.0f, 0.0f, 0.0f }; }
|
|
|
| void CRigidBody::Activate() {}
|
|
|
| CCallbackHandler<void, CTransformBase::CMeasurePack, CTransformBase*>& CWorldUnit::GetTransformCallback()
|
| {
|
| return Transform.OnTransformChanged;
|
| }
|
|
|
| std::unique_ptr<CPhysicsWorld> CPhysicsEngine::V_CreateWorld() { return nullptr; }
|
|
|
| std::unique_ptr<CRigidBody> CPhysicsWorld::V_CreateRigidBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform, float mass)
|
| {
|
| return nullptr;
|
| }
|
|
|
| std::unique_ptr<CKinematicBody> CPhysicsWorld::V_CreateKinematicBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform)
|
| {
|
| return nullptr;
|
| }
|
|
|
| std::unique_ptr<CGhostBody> CPhysicsWorld::V_CreateGhostBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform)
|
| {
|
| return nullptr;
|
| }
|
|
|
| float PhysicsShapes::CBasic::GetMargin() const { return 0.0f; }
|
| void PhysicsShapes::CBasic::SetMargin(float _margin) {}
|
|
|
| void CPhysicsWorld::V_AddRigidBody(CRigidBody* rgBody) {}
|
| void CPhysicsWorld::V_AddKinematicBody(CKinematicBody* knBody) {}
|
| void CPhysicsWorld::V_AddGhostBody(CGhostBody* ghBody) {}
|
|
|
| CRigidBody* CPhysicsWorld::CreateRigidBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform, float mass)
|
| {
|
| auto rgBody = V_CreateRigidBody(_collision, startTransform, mass);
|
| auto ret = rgBody.get();
|
|
|
| V_AddRigidBody(rgBody.get());
|
| rgBody->OverridePersistent(this);
|
|
|
| Units.push_back(std::move(rgBody));
|
| return ret;
|
| }
|
|
|
| CKinematicBody* CPhysicsWorld::CreateKinematicBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform)
|
| {
|
| auto knBody = V_CreateKinematicBody(_collision, startTransform);
|
| auto ret = knBody.get();
|
|
|
| V_AddKinematicBody(knBody.get());
|
| knBody->OverridePersistent(this);
|
|
|
| Units.push_back(std::move(knBody));
|
| return ret;
|
| }
|
|
|
| CGhostBody* CPhysicsWorld::CreateGhostBody(std::shared_ptr<CCollision> _collision, const CTransform& startTransform)
|
| {
|
| auto ghBody = V_CreateGhostBody(_collision, startTransform);
|
| auto ret = ghBody.get();
|
|
|
| V_AddGhostBody(ghBody.get());
|
| ghBody->OverridePersistent(this);
|
|
|
| Units.push_back(std::move(ghBody));
|
| return ret;
|
| }
|
|
|
| void CPhysicsEngine::Trace(std::function<void(CPhysicsWorld*)> func)
|
| {
|
| for(auto& world : m_Worlds)
|
| {
|
| func(world.get());
|
| }
|
| }
|
|
|
| void CPhysicsWorld::SetGravity(const glm::vec3& _gravity)
|
| {
|
| V_SetGravity(_gravity);
|
| }
|
|
|
| glm::vec3 CPhysicsWorld::GetGravity() const { return { 0.0f, 0.0f, 0.0f }; }
|
|
|
| void CPhysicsWorld::V_SetGravity(const glm::vec3& _gravity)
|
| {
|
|
|
| }
|
|
|
| void CPhysicsWorld::OverridePersistent(CPhysicsEngine* _engine) { m_linkedEngine = _engine; }
|
| void CPhysicsWorld::V_Step(float deltaTime) {}
|
|
|
| void CPhysicsWorld::Step(float deltaTime)
|
| {
|
| V_Step(deltaTime);
|
| for(auto& unit : Units)
|
| {
|
| unit->UpdateInternalTransform(); //TODO may be ineffecient
|
|
|
| //update internal ->
|
| //transformable onchange
|
| }
|
| }
|
|
|
| void PhysicsShapes::CBasic::V_BaseBuild() {}
|
| void PhysicsShapes::CBasic::V_Build() {}
|
|
|
| void PhysicsShapes::CTriMesh::Merge(float threshold)
|
| {
|
| /*std::vector<glm::vec3> merged;
|
| std::vector<bool> processed(Vertices.size(), false);
|
|
|
| for(size_t i1 = 0; i1 < Vertices.size(); i1++)
|
| {
|
| if(processed[i1]) { continue; }
|
|
|
| auto& v1 = Vertices[i1];
|
| auto accum = v1;
|
| size_t times = 1;
|
| processed[i1] = true;
|
|
|
| for(size_t i2 = i1 + 1; i2 < Vertices.size(); i2++)
|
| {
|
| if(processed[i2]) continue;
|
|
|
| auto& v2 = Vertices[i2];
|
| auto dist = glm::distance(v1, v2);
|
| if(dist <= threshold)
|
| {
|
| accum += v2;
|
| times++;
|
| processed[i2] = true;
|
| }
|
| }
|
|
|
| auto avg = accum / static_cast<float>(times);
|
| merged.push_back(avg);
|
| }*/
|
|
|
| Build();
|
| }
|
|
|
| void PhysicsShapes::CBasic::Build()
|
| {
|
| V_BaseBuild();
|
| V_Build();
|
| }
|
|
|
| void PhysicsShapes::CConvex::V_BaseBuild()
|
| {
|
| if(NeedVHACD)
|
| {
|
| BuildConvex();
|
| }
|
| }
|
|
|
| void PhysicsShapes::CCompound::V_BaseBuild()
|
| {
|
| for(auto& ch : Childs)
|
| {
|
| //ch.Shape->Build(); //TODO WHY BUILD
|
| }
|
| }
|
|
|
| CPhysicsEngine::CPhysicsEngine()
|
| {
|
| ShapesFactory.add<PhysicsShapes::CTriMesh>("trimesh");
|
| ShapesFactory.add<PhysicsShapes::CConvex>("convex");
|
| ShapesFactory.add<PhysicsShapes::CCompound>("compound");
|
| ShapesFactory.add<PhysicsShapes::CCapsule>("capsule");
|
| ShapesFactory.add<PhysicsShapes::CSphere>("sphere");
|
| ShapesFactory.add<PhysicsShapes::CBox>("box");
|
| }
|
|
|
| class VHACDLogger : public VHACD::IVHACD::IUserLogger
|
| {
|
| public:
|
| void Log(const char* const msg) override
|
| {
|
| printf("[VHACD] %s\n", msg);
|
| }
|
| };
|
|
|
| class VHACDCallback : public VHACD::IVHACD::IUserCallback
|
| {
|
| public:
|
| void Update(const double overallProgress,
|
| const double stageProgress,
|
| const char* const stage,
|
| const char* operation) override
|
| {
|
| printf("[VHACD] %s (%s): %.1f%% (overall %.1f%%)\n",
|
| stage,
|
| operation,
|
| stageProgress * 100.0,
|
| overallProgress * 100.0);
|
| }
|
| };
|
|
|
| void PhysicsShapes::CConvex::BuildConvex()
|
| {
|
| VHACD::IVHACD* vhacd = VHACD::CreateVHACD();
|
| VHACD::IVHACD::Parameters params;
|
|
|
| VHACDLogger myLogger;
|
| VHACDCallback myCallback;
|
|
|
| Log::Instance() << "glm::vec3 sizeof is " << sizeof(glm::vec3) << Log::Endl;
|
| Log::Instance() << "glm::uvec3 sizeof is " << sizeof(glm::uvec3) << Log::Endl;
|
|
|
| const size_t vcount = Vertices.size();
|
|
|
| for (size_t i = 0; i < Triangles.size(); ++i)
|
| {
|
| const auto& t = Triangles[i];
|
|
|
| //Log::Instance() << "Triangle(" << t << ") as in (" << Vertices.at(t.x) << "); (" << Vertices.at(t.y) << "); (" << Vertices.at(t.z) << ")\n";
|
|
|
| if (t.x >= vcount || t.y >= vcount || t.z >= vcount)
|
| {
|
| printf("INVALID TRI INDEX at %zu: %u %u %u (vcount=%zu)\n",
|
| i, t.x, t.y, t.z, vcount);
|
| }
|
|
|
| if (t.x == t.y || t.y == t.z || t.x == t.z)
|
| {
|
| printf("DEGENERATE TRI (duplicate index) at %zu: %u %u %u\n",
|
| i, t.x, t.y, t.z);
|
| }
|
| }
|
|
|
| // 2) Check vertex memory layout
|
| static_assert(sizeof(glm::vec3) == sizeof(float) * 3,
|
| "Vec3 must be exactly 3 floats");
|
| static_assert(sizeof(glm::uvec3) == sizeof(uint32_t) * 3,
|
| "UVec3 must be exactly 3 floats");
|
|
|
|
|
|
|
| // 3) Check vertex values
|
| for (size_t i = 0; i < Vertices.size(); ++i)
|
| {
|
| const auto& v = Vertices[i];
|
|
|
| if(v.x >= 10000.0f || v.y >= 10000.0f || v.z >= 10000.0f)
|
| {
|
| Log::ErrInstance() << "TOO FAR AWAY " << v << Log::Endl;
|
| }
|
|
|
| if (!std::isfinite(v.x) || !std::isfinite(v.y) || !std::isfinite(v.z))
|
| {
|
| printf("INVALID VERTEX at %zu: %f %f %f\n", i, v.x, v.y, v.z);
|
| }
|
| }
|
|
|
| params.m_callback = &myCallback; // progress reporting
|
| params.m_logger = &myLogger; // diagnostic output
|
| params.m_taskRunner = nullptr; // unless you have your own job system
|
|
|
| // --- Core behavior ---
|
| params.m_maxConvexHulls = 1; // EXACTLY one hull
|
| params.m_maxRecursionDepth = 1; // MUST be >= 1 to emit a hull
|
| params.m_resolution = 100000; // 50k–200k sweet spot
|
| params.m_minimumVolumePercentErrorAllowed = 1.0; // default is fine
|
|
|
| // --- Geometry handling ---
|
| params.m_fillMode = VHACD::FillMode::SURFACE_ONLY;
|
| // Use FLOOD_FILL only if mesh is guaranteed watertight
|
|
|
| params.m_shrinkWrap = true;
|
| // Now safe since your mesh is valid and closed-ish
|
|
|
| params.m_minEdgeLength = 0; // DO NOT block splitting
|
| params.m_findBestPlane = false; // experimental; keep off
|
|
|
| // --- Output hull quality ---
|
| params.m_maxNumVerticesPerCH = 256; // Bullet stable limit
|
| params.m_asyncACD = false; // deterministic behavior
|
|
|
| Log::Instance() << "VHACD computing with " << Vertices.size() << " vertices and " << Triangles.size() << " triangles\n";
|
|
|
| bool succ = vhacd->Compute((float*)Vertices.data(), Vertices.size(), (std::uint32_t*)Triangles.data(), Triangles.size(), params);
|
|
|
| Log::Instance() << "Result is " << succ << Log::Endl;
|
|
|
| //TODO error handling: assert(hullCount == 1)
|
| //assert(vhacd->GetNConvexHulls() == 1);
|
|
|
| Vertices.clear();
|
| Triangles.clear();
|
|
|
| VHACD::IVHACD::ConvexHull hull;
|
| vhacd->GetConvexHull(0, hull);
|
|
|
| Log::Instance() << "vhacd->GetNConvexHulls() is " << vhacd->GetNConvexHulls() << Log::Endl;
|
| Log::Instance() << "hull.m_points.size() is " << hull.m_points.size() << Log::Endl;
|
| Log::Instance() << "hull.m_triangles.size() is " << hull.m_triangles.size() << Log::Endl;
|
|
|
| Vertices.reserve(hull.m_points.size());
|
| Triangles.reserve(hull.m_triangles.size());
|
|
|
| for (const VHACD::Vertex& v : hull.m_points)
|
| {
|
| Vertices.push_back({ v.mX, v.mY, v.mZ });
|
| }
|
|
|
| for (const VHACD::Triangle& t : hull.m_triangles)
|
| {
|
| Triangles.push_back({ t.mI0, t.mI1, t.mI2 });
|
| }
|
|
|
| Log::Instance() << "VHACD got " << Vertices.size() << " vertices and " << Triangles.size() << " triangles\n";
|
|
|
| vhacd->Release();
|
| //vhacd->Clean();
|
| }
|
|
|
| std::string CPhysicsEngine::GetType() const
|
| {
|
| return {};
|
| }
|
|
|
| void CTriangleMesh::V_SetupLoadPipeline(CLoadPipeline& pipeline)
|
| {
|
| pipeline.push_back //load image (async)
|
| (
|
| {
|
| [](std::shared_ptr<CLoadingContext> context) -> bool
|
| {
|
| auto thisResource = dynamic_cast<CTriangleMesh*>(context->CurrentResource);
|
|
|
| auto path = context->LoadPath;
|
| auto found_path = FileUtils::find_first_by_name(FileUtils::get_executable_path() / "resources" / "shapes", path.string());
|
|
|
| if(!found_path.has_value()) { return false; }
|
|
|
| CBinaryFile _bin;
|
| _bin.OpenRead(found_path.value());
|
|
|
| if(!_bin.IsOpen()) { return false; }
|
|
|
| auto filesize = _bin.Read<std::uint64_t>();
|
|
|
| Log::Instance() << "filesize is " << filesize << Log::Endl;
|
|
|
| auto vec = _bin.ReadDataVec(filesize);
|
| Log::Instance() << "allocd\n";
|
| _bin.Close();
|
|
|
| CBufferWrapper bin(vec);
|
|
|
| auto VerticesCount = bin.Read<std::uint64_t>();
|
| auto MaterialsCount = bin.Read<std::uint64_t>();
|
|
|
| Log::Instance() << "VerticesCount is " << VerticesCount << Log::Endl;
|
| Log::Instance() << "MaterialsCount is " << MaterialsCount << Log::Endl;
|
|
|
| for(std::uint64_t i = 0; i < VerticesCount; i++)
|
| {
|
| auto v = bin.Read<glm::vec3>();
|
| thisResource->Vertices.push_back(v);
|
| }
|
|
|
| for(std::uint64_t m = 0; m < MaterialsCount; m++)
|
| {
|
| auto TrianglesCount = bin.Read<std::uint64_t>();
|
| auto matName = bin.ReadLenString<std::uint8_t, std::string>();
|
|
|
| for(std::uint64_t i = 0; i < TrianglesCount; i++)
|
| {
|
| CTriangle tri;
|
| tri.Index = bin.Read<glm::u64vec3>();
|
| tri.Material = 0; //TODO by matname
|
|
|
| thisResource->Triangles.push_back(std::move(tri));
|
| }
|
| }
|
| return true;
|
| },
|
| true //async (is_async)
|
| }
|
| );
|
| }
|
|
|
| void LoadSingleConvexBinary(CBinaryFile& file, std::vector<glm::vec3>& vertices, std::vector<glm::uvec3>& triangles)
|
| {
|
| auto VerticesCount = file.Read<std::uint32_t>();
|
| auto TrianglesCount = file.Read<std::uint32_t>();
|
|
|
| for(std::uint32_t i = 0; i < VerticesCount; i++)
|
| {
|
| vertices.push_back(file.Read<glm::vec3>());
|
| }
|
|
|
| for(std::uint32_t i = 0; i < TrianglesCount; i++)
|
| {
|
| triangles.push_back(file.Read<glm::uvec3>());
|
| }
|
| }
|
|
|
| void ApplyVhacdConfig(const std::filesystem::path& _config_path, VHACD::IVHACD::Parameters& params)
|
| {
|
| using json = nlohmann::json;
|
| if(!_config_path.empty())
|
| {
|
| std::ifstream file;
|
| file.open(FileUtils::get_executable_path() / _config_path);
|
|
|
| if(file.is_open())
|
| {
|
| json data;
|
| bool valid = true;
|
|
|
| try
|
| {
|
| data = json::parse(file);
|
| }
|
| catch (const json::parse_error& e)
|
| {
|
| valid = false;
|
| }
|
| catch (const std::exception& e)
|
| {
|
| valid = false;
|
| }
|
| file.close();
|
|
|
| if(valid)
|
| {
|
| JsonToVHACD(data, params);
|
| }
|
| }
|
| }
|
| }
|
|
|
| void CCollision::CompileNativeConvex(const std::filesystem::path& _mesh_path, std::vector<std::filesystem::path> _configs_paths, bool vhacd)
|
| {
|
| VHACD::IVHACD::Parameters params;
|
|
|
| if(vhacd)
|
| {
|
| for(auto& p : _configs_paths)
|
| {
|
| ApplyVhacdConfig(p, params);
|
| }
|
| }
|
|
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
| auto _trimesh = resman->GetOrCreate("trimesh", _mesh_path.string());
|
|
|
| _trimesh->Wait();
|
| auto trimesh = dynamic_cast<CTriangleMesh*>(_trimesh.get());
|
|
|
| struct WritePair
|
| {
|
| std::vector<glm::vec3> Vertices;
|
| std::vector<glm::uvec3> Triangles;
|
|
|
| glm::vec3 Center = glm::vec3(0.0f, 0.0f, 0.0f);
|
| };
|
|
|
| std::vector<glm::vec3> CurrentMeshVertices;
|
| std::vector<glm::uvec3> CurrentMeshTriangles;
|
|
|
| for(auto& v : trimesh->Vertices)
|
| {
|
| CurrentMeshVertices.push_back(v);
|
| }
|
|
|
| for(auto& tri : trimesh->Triangles)
|
| {
|
| CurrentMeshTriangles.push_back
|
| (
|
| {
|
| static_cast<glm::uvec3::value_type>(tri.Index.x),
|
| static_cast<glm::uvec3::value_type>(tri.Index.y),
|
| static_cast<glm::uvec3::value_type>(tri.Index.z)
|
| }
|
| );
|
| }
|
|
|
| std::vector<WritePair> ToWrite;
|
| if(!vhacd)
|
| {
|
| WritePair wr;
|
|
|
| wr.Vertices = std::move(CurrentMeshVertices);
|
| wr.Triangles = std::move(CurrentMeshTriangles);
|
|
|
| ToWrite.push_back(std::move(wr));
|
| }
|
| else
|
| {
|
| VHACD::IVHACD* vhacd = VHACD::CreateVHACD();
|
| CScopeExit scopeExit([vhacd]() { vhacd->Release(); });
|
|
|
| VHACDLogger myLogger;
|
| VHACDCallback myCallback;
|
|
|
| params.m_callback = &myCallback;
|
| params.m_logger = &myLogger;
|
| params.m_taskRunner = nullptr;
|
|
|
| bool succ = vhacd->Compute((float*)CurrentMeshVertices.data(), CurrentMeshVertices.size(), (std::uint32_t*)CurrentMeshTriangles.data(), CurrentMeshTriangles.size(), params);
|
|
|
| uint32_t nconv = vhacd->GetNConvexHulls();
|
| for(uint32_t i = 0; i < nconv; i++)
|
| {
|
| VHACD::IVHACD::ConvexHull hull;
|
| vhacd->GetConvexHull(i, hull);
|
|
|
| std::vector<glm::vec3> vert;
|
| std::vector<glm::uvec3> tri;
|
|
|
| for (const VHACD::Vertex& v : hull.m_points)
|
| {
|
| vert.push_back({ v.mX, v.mY, v.mZ });
|
| }
|
|
|
| for (const VHACD::Triangle& t : hull.m_triangles)
|
| {
|
| tri.push_back({ t.mI0, t.mI1, t.mI2 });
|
| }
|
|
|
| WritePair wr;
|
|
|
| wr.Vertices = std::move(vert);
|
| wr.Triangles = std::move(tri);
|
| wr.Center = { hull.m_center.GetX(), hull.m_center.GetY(), hull.m_center.GetZ() };
|
|
|
| ToWrite.push_back(std::move(wr));
|
| }
|
| }
|
|
|
| auto outPath = FileUtils::get_executable_path() / "resources" / "convex" / (trimesh->Name + ".ecnv");
|
| CBinaryFile outFile;
|
|
|
| outFile.OpenWrite(outPath);
|
| outFile.Write<std::uint32_t>(ToWrite.size());
|
|
|
| for(std::uint32_t i = 0; i < ToWrite.size(); i++)
|
| {
|
| auto& wr = ToWrite.at(i);
|
|
|
| outFile.Write<glm::vec3>(wr.Center);
|
|
|
| outFile.Write<std::uint32_t>(wr.Vertices.size());
|
| outFile.Write<std::uint32_t>(wr.Triangles.size());
|
|
|
| for(auto& v : wr.Vertices) { outFile.Write<glm::vec3>(v); }
|
| for(auto& t : wr.Triangles) { outFile.Write<glm::uvec3>(t); }
|
| }
|
|
|
| outFile.Close();
|
| Log::Instance() << "Compiled convex with " << ToWrite.size() << " parts\n";
|
| }
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> CCollision::LoadNativeConvex(const nlohmann::json& _json)
|
| {
|
| std::string path;
|
| path = GetFromJson<std::string>(_json, "mesh_path", "");
|
| if(path.empty()) { path = GetFromJson<std::string>(_json, "path", ""); }
|
| if(path.empty()) { path = GetFromJson<std::string>(_json, "convex_path", ""); }
|
|
|
| CBinaryFile file;
|
| auto found_path = FileUtils::find_first_by_name(FileUtils::get_executable_path() / "resources" / "convex", path);
|
|
|
| if(!found_path.has_value()) { return nullptr; }
|
| file.OpenRead(found_path.value());
|
|
|
| auto partsCount = file.Read<std::uint32_t>(); //1 - convex, >1 - compound
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> mainShape;
|
| std::string mainShapeType = (partsCount > 1) ? "compound" : "convex";
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| mainShape = game->PhysicsEngine->ShapesFactory.create<std::unique_ptr<PhysicsShapes::CBasic>>(mainShapeType);
|
|
|
| for(std::uint32_t i = 0; i < partsCount; i++)
|
| {
|
| std::vector<glm::vec3> Vertices;
|
| std::vector<glm::uvec3> Triangles;
|
|
|
| glm::vec3 Center = glm::vec3(0.0f, 0.0f, 0.0f);
|
|
|
| Center = file.Read<glm::vec3>();
|
| LoadSingleConvexBinary(file, Vertices, Triangles);
|
|
|
| if(partsCount == 1)
|
| {
|
| auto mainConvex = dynamic_cast<PhysicsShapes::CConvex*>(mainShape.get());
|
|
|
| mainConvex->Vertices = std::move(Vertices);
|
| mainConvex->Triangles = std::move(Triangles);
|
|
|
| mainConvex->NeedVHACD = false;
|
| mainConvex->Build();
|
| return mainShape;
|
| }
|
| else
|
| {
|
| auto mainCompound = dynamic_cast<PhysicsShapes::CCompound*>(mainShape.get());
|
|
|
| std::shared_ptr<PhysicsShapes::CBasic> _conv = game->PhysicsEngine->ShapesFactory.create<std::shared_ptr<PhysicsShapes::CBasic>>("convex");
|
| auto conv = dynamic_cast<PhysicsShapes::CConvex*>(_conv.get());
|
|
|
| conv->Vertices = std::move(Vertices);
|
| conv->Triangles = std::move(Triangles);
|
|
|
| conv->NeedVHACD = false;
|
| conv->Build();
|
| CTransform trans;
|
|
|
| trans.SetPosition(Center);
|
| mainCompound->Childs.push_back({ trans, _conv });
|
| }
|
| }
|
|
|
| file.Close();
|
| mainShape->Build();
|
| return mainShape;
|
| }
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> CCollision::BuildConvexCompound(const nlohmann::json& _json)
|
| {
|
| std::string path;
|
| path = GetFromJson<std::string>(_json, "mesh_path", "");
|
|
|
| bool withHoles = false;
|
| withHoles = GetFromJson<bool>(_json, "with_holes", false);
|
|
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
| auto _trimesh = resman->GetOrCreate("trimesh", path);
|
|
|
| _trimesh->Wait();
|
| auto trimesh = dynamic_cast<CTriangleMesh*>(_trimesh.get());
|
|
|
| std::vector<glm::vec3> Vertices;
|
| std::vector<glm::uvec3> Triangles;
|
|
|
| for(auto& v : trimesh->Vertices)
|
| {
|
| Vertices.push_back(v);
|
| }
|
|
|
| for(auto& tri : trimesh->Triangles)
|
| {
|
| Triangles.push_back
|
| (
|
| {
|
| static_cast<glm::uvec3::value_type>(tri.Index.x),
|
| static_cast<glm::uvec3::value_type>(tri.Index.y),
|
| static_cast<glm::uvec3::value_type>(tri.Index.z)
|
| }
|
| );
|
| }
|
|
|
| VHACD::IVHACD* vhacd = VHACD::CreateVHACD();
|
| CScopeExit scopeExit([vhacd]() { vhacd->Release(); });
|
|
|
| VHACD::IVHACD::Parameters params;
|
|
|
| VHACDLogger myLogger;
|
| VHACDCallback myCallback;
|
|
|
| params.m_callback = &myCallback;
|
| params.m_logger = &myLogger;
|
| params.m_taskRunner = nullptr;
|
|
|
| if(withHoles)
|
| {
|
| params.m_maxConvexHulls = 128;
|
| params.m_resolution = 400000;
|
| params.m_minimumVolumePercentErrorAllowed = 2.0;
|
| params.m_maxRecursionDepth = 12;
|
| params.m_shrinkWrap = true;
|
| params.m_fillMode = VHACD::FillMode::SURFACE_ONLY;
|
| params.m_maxNumVerticesPerCH = 64;
|
| params.m_asyncACD = true;
|
| params.m_minEdgeLength = 2;
|
| params.m_findBestPlane = false;
|
| }
|
| else
|
| {
|
| params.m_maxConvexHulls = 64;
|
| params.m_resolution = 200000;
|
| params.m_minimumVolumePercentErrorAllowed = 1.0;
|
| params.m_maxRecursionDepth = 10;
|
| params.m_shrinkWrap = true;
|
| params.m_fillMode = VHACD::FillMode::FLOOD_FILL;
|
| params.m_maxNumVerticesPerCH = 64;
|
| params.m_asyncACD = true;
|
| params.m_minEdgeLength = 2;
|
| params.m_findBestPlane = false;
|
| }
|
|
|
| JsonToVHACD(_json, params);
|
| bool succ = vhacd->Compute((float*)Vertices.data(), Vertices.size(), (std::uint32_t*)Triangles.data(), Triangles.size(), params);
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| uint32_t nconv = vhacd->GetNConvexHulls();
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> mainShape;
|
| std::string mainShapeType = "compound";
|
|
|
| if(nconv == 1)
|
| {
|
| mainShapeType = "convex";
|
| }
|
| mainShape = game->PhysicsEngine->ShapesFactory.create<std::unique_ptr<PhysicsShapes::CBasic>>(mainShapeType);
|
|
|
| for(uint32_t i = 0; i < nconv; i++)
|
| {
|
| VHACD::IVHACD::ConvexHull hull;
|
| vhacd->GetConvexHull(i, hull);
|
|
|
| std::vector<glm::vec3> vert;
|
| std::vector<glm::uvec3> tri;
|
|
|
| for (const VHACD::Vertex& v : hull.m_points)
|
| {
|
| vert.push_back({ v.mX, v.mY, v.mZ });
|
| }
|
|
|
| for (const VHACD::Triangle& t : hull.m_triangles)
|
| {
|
| tri.push_back({ t.mI0, t.mI1, t.mI2 });
|
| }
|
|
|
| if(nconv == 1)
|
| {
|
| auto mainConvex = dynamic_cast<PhysicsShapes::CConvex*>(mainShape.get());
|
|
|
| mainConvex->Vertices = std::move(vert);
|
| mainConvex->Triangles = std::move(tri);
|
|
|
| mainConvex->NeedVHACD = false;
|
| mainConvex->Build();
|
| return mainShape;
|
| }
|
| else
|
| {
|
| auto mainCompound = dynamic_cast<PhysicsShapes::CCompound*>(mainShape.get());
|
|
|
| std::shared_ptr<PhysicsShapes::CBasic> _conv = game->PhysicsEngine->ShapesFactory.create<std::shared_ptr<PhysicsShapes::CBasic>>("convex");
|
| auto conv = dynamic_cast<PhysicsShapes::CConvex*>(_conv.get());
|
|
|
| conv->Vertices = std::move(vert);
|
| conv->Triangles = std::move(tri);
|
|
|
| conv->NeedVHACD = false;
|
| conv->Build();
|
| CTransform trans;
|
|
|
| trans.SetPosition({ hull.m_center.GetX(), hull.m_center.GetY(), hull.m_center.GetZ() });
|
| mainCompound->Childs.push_back({ trans, _conv });
|
| }
|
| }
|
|
|
| mainShape->Build();
|
| return mainShape;
|
| }
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> CCollision::ParseShape(const nlohmann::json& _json, CTransformBase* _trans)
|
| {
|
| using json = nlohmann::json;
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| if(!game) { return nullptr; }
|
|
|
| if(_json.contains("type"))
|
| {
|
| if(_json.contains("transform") && _trans)
|
| {
|
| auto& transj = _json["transform"];
|
| CTransform trans;
|
|
|
| if(transj.is_object())
|
| {
|
| if(transj.contains("matrix"))
|
| {
|
| auto& matrix = transj["matrix"];
|
| if(matrix.is_array())
|
| {
|
| glm::mat4 matr;
|
| std::vector<float> nums = matrix.get<std::vector<float>>();
|
| for(size_t i = 0; i < nums.size() && i < (matr.length() * matr.length()); i++)
|
| {
|
| matr[i % 4][i / 4] = nums.at(i);
|
| }
|
| trans.SetFromMatrix(matr);
|
| }
|
| }
|
|
|
| if(transj.contains("position"))
|
| {
|
| auto& position = transj["position"];
|
| if(position.is_array())
|
| {
|
| glm::vec3 pos;
|
| std::vector<float> nums = position.get<std::vector<float>>();
|
|
|
| for(size_t i = 0; i < nums.size() && i < pos.length(); i++)
|
| {
|
| pos[i] = nums.at(i);
|
| }
|
|
|
| trans.SetPosition(pos);
|
| }
|
| }
|
|
|
| if(transj.contains("rotation"))
|
| {
|
| auto& rotation = transj["rotation"];
|
| if(rotation.is_array())
|
| {
|
| glm::quat quat;
|
| std::vector<float> nums = rotation.get<std::vector<float>>();
|
|
|
| for(size_t i = 0; i < nums.size() && i < quat.length(); i++)
|
| {
|
| quat[i] = nums.at(i);
|
| }
|
|
|
| trans.SetRotation(quat);
|
| }
|
| }
|
|
|
| if(transj.contains("euler"))
|
| {
|
| auto& euler = transj["euler"];
|
| if(euler.is_array())
|
| {
|
| CAngles angles;
|
| std::vector<float> nums = euler.get<std::vector<float>>();
|
|
|
| for(size_t i = 0; i < nums.size() && i < angles.length(); i++)
|
| {
|
| angles[i] = CAngle::degrees(nums.at(i));
|
| }
|
|
|
| trans.GetEulerRotation().SetRotation(angles);
|
| }
|
| }
|
|
|
| if(transj.contains("scale"))
|
| {
|
| auto& scale = transj["scale"];
|
| if(scale.is_array())
|
| {
|
| glm::vec3 scl;
|
| std::vector<float> nums = scale.get<std::vector<float>>();
|
|
|
| for(size_t i = 0; i < nums.size() && i < scl.length(); i++)
|
| {
|
| scl[i] = nums.at(i);
|
| }
|
|
|
| trans.SetScale(scl);
|
| }
|
| }
|
| }
|
| _trans->SetPRS(trans.GetPRS());
|
| }
|
|
|
| const json& typej = _json["type"];
|
|
|
| float jsonMargin = -1.0f;
|
| if(_json.contains("margin"))
|
| {
|
| const json& marginj = _json["margin"];
|
| jsonMargin = marginj.get<float>();
|
| }
|
|
|
| PhysicsShapes::CBasic* shapeToSetParam = nullptr;
|
| CScopeExit setParams([&shapeToSetParam, jsonMargin]()
|
| {
|
| if(shapeToSetParam && jsonMargin != 1.0f)
|
| {
|
| shapeToSetParam->SetMargin(jsonMargin);
|
| }
|
| });
|
|
|
| std::string typestr;
|
| if(typej.is_string())
|
| {
|
| typestr = typej.get<std::string>();
|
| }
|
| if(typestr.empty()) { return nullptr; }
|
|
|
| if(typestr == "convex_compound")
|
| {
|
| auto ret = BuildConvexCompound(_json);
|
| shapeToSetParam = ret.get();
|
| return ret;
|
| }
|
| else if(typestr == "convex_native")
|
| {
|
| auto ret = LoadNativeConvex(_json);
|
| shapeToSetParam = ret.get();
|
| return ret;
|
| }
|
|
|
| std::unique_ptr<PhysicsShapes::CBasic> ret = game->PhysicsEngine->ShapesFactory.create<std::unique_ptr<PhysicsShapes::CBasic>>(typestr);
|
| if(!ret) { return nullptr; }
|
|
|
| ret->Load(_json);
|
| shapeToSetParam = ret.get();
|
| return ret;
|
| }
|
| return nullptr;
|
| }
|
|
|
| void CCollision::V_SetupLoadPipeline(CLoadPipeline& pipeline)
|
| {
|
| pipeline.push_back //load image (async)
|
| (
|
| {
|
| [](std::shared_ptr<CLoadingContext> context) -> bool
|
| {
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| if(!game) { return false; }
|
|
|
| using namespace nlohmann;
|
| auto thisResource = dynamic_cast<CCollision*>(context->CurrentResource);
|
|
|
| auto path = context->LoadPath;
|
| auto found_path = FileUtils::find_first_by_name(FileUtils::get_executable_path() / "resources" / "collision", path.string());
|
|
|
| if(!found_path.has_value()) { return false; }
|
|
|
| std::ifstream file(found_path.value());
|
|
|
| if (!file.is_open())
|
| {
|
| return false;
|
| }
|
|
|
| json data;
|
| try
|
| {
|
| data = json::parse(file);
|
| }
|
| catch (const json::parse_error& e)
|
| {
|
| return false;
|
| }
|
| catch (const std::exception& e)
|
| {
|
| return false;
|
| }
|
| file.close();
|
|
|
| Log::Instance() << "Parsing collision " << found_path.value().string() << Log::Endl;
|
|
|
| thisResource->Shape = ParseShape(data);
|
| if(!thisResource->Shape) { return false; }
|
|
|
| //return false;
|
| return true;
|
| },
|
| true //async (is_async)
|
| }
|
| );
|
| }
|
|
|
| CPhysicsWorld* CPhysicsEngine::GetWorld(int id)
|
| {
|
| if(id < 0 || id >= m_Worlds.size()) { return nullptr; }
|
| return m_Worlds.at(id).get();
|
| }
|
|
|
| std::string PhysicsShapes::CBasic::GetType() const { return "basic"; }
|
| std::string PhysicsShapes::CTriMesh::GetType() const { return "trimesh"; }
|
| std::string PhysicsShapes::CConvex::GetType() const { return "convex"; }
|
| std::string PhysicsShapes::CCompound::GetType() const { return "compound"; }
|
| std::string PhysicsShapes::CCapsule::GetType() const { return "capsule"; }
|
| std::string PhysicsShapes::CSphere::GetType() const { return "sphere"; }
|
| std::string PhysicsShapes::CBox::GetType() const { return "box"; }
|
|
|
| void CWorldUnit::OverridePersistent(CPhysicsWorld* _world)
|
| {
|
| m_World = _world;
|
| }
|
|
|
| CPhysicsWorld* CWorldUnit::GetWorld() const
|
| {
|
| return m_World;
|
| }
|
|
|
| bool CPhysicsEngine::V_BaseScriptInit(std::shared_ptr<sol::state> state, sol::table table)
|
| {
|
| table.set_function("getWorld", [this](int id, sol::this_state ts) -> sol::table
|
| {
|
| auto world = GetWorld(id);
|
| return world ? world->GetScriptTable(ts) : sol::lua_nil;
|
| });
|
| return true;
|
| }
|
|
|
| bool CPhysicsWorld::V_BaseScriptInit(std::shared_ptr<sol::state> state, sol::table table)
|
| {
|
| table.set_function("getGravity", [this]() -> glm::vec3 { return this->GetGravity(); });
|
| table.set_function("setGravity", [this](const glm::vec3& grav) { return this->SetGravity(grav); });
|
|
|
| table.set_function("createRigidBody", [this](const std::string& colname, float mass, sol::table _trans, sol::this_state ts) -> sol::object
|
| {
|
| if(colname.empty()) { return sol::lua_nil; }
|
|
|
| CTransform start_trans;
|
| if(_trans.valid())
|
| {
|
| start_trans = ScriptUtils::FromObject<CTransform>(_trans);
|
| }
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
|
|
| auto _col = resman->GetOrCreate("collision", colname);
|
| auto col = std::dynamic_pointer_cast<CCollision>(_col);
|
|
|
| if(!col) { return sol::lua_nil; }
|
|
|
| auto ret = this->CreateRigidBody(col, start_trans, mass);
|
| return ret->GetScriptTable(ts);
|
| });
|
|
|
| table.set_function("createKinematicBody", [this](const std::string& colname, sol::table _trans, sol::this_state ts) -> sol::object
|
| {
|
| if(colname.empty()) { return sol::lua_nil; }
|
|
|
| CTransform start_trans;
|
| if(_trans.valid())
|
| {
|
| start_trans = ScriptUtils::FromObject<CTransform>(_trans);
|
| }
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
|
|
| auto _col = resman->GetOrCreate("collision", colname);
|
| auto col = std::dynamic_pointer_cast<CCollision>(_col);
|
|
|
| if(!col) { return sol::lua_nil; }
|
|
|
| auto ret = this->CreateKinematicBody(col, start_trans);
|
| return ret->GetScriptTable(ts);
|
| });
|
|
|
| table.set_function("createGhostBody", [this](const std::string& colname, sol::table _trans, sol::this_state ts) -> sol::object
|
| {
|
| if(colname.empty()) { return sol::lua_nil; }
|
|
|
| CTransform start_trans;
|
| if(_trans.valid())
|
| {
|
| start_trans = ScriptUtils::FromObject<CTransform>(_trans);
|
| }
|
|
|
| auto game = CEngine::GetInstance()->Components.GetComponentTyped<CGame>();
|
| auto resman = CEngine::GetInstance()->Components.GetComponentTyped<CResourcesManager>();
|
|
|
| auto _col = resman->GetOrCreate("collision", colname);
|
| auto col = std::dynamic_pointer_cast<CCollision>(_col);
|
|
|
| if(!col) { return sol::lua_nil; }
|
|
|
| auto ret = this->CreateGhostBody(col, start_trans);
|
| return ret->GetScriptTable(ts);
|
| });
|
| return true;
|
| }
|
|
|
| bool CWorldUnit::V_BaseScriptInit(std::shared_ptr<sol::state> state, sol::table table)
|
| {
|
| return true;
|
| }
|
|
|
| EUnitType CRigidBody::GetType() const { return EUnitType::Rigid; }
|
| EUnitType CKinematicBody::GetType() const { return EUnitType::Kinematic; }
|
| EUnitType CGhostBody::GetType() const { return EUnitType::Ghost; }
|
|
|
| LINK_RESOURCE_TO_CLASS(CTriangleMesh, trimesh);
|
| LINK_RESOURCE_TO_CLASS(CCollision, collision); |