From 22b99e0526dbae254076a98881688c126521b4f5 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Thu, 14 Mar 2024 09:19:08 +0100 Subject: [PATCH] Physx integration (#244) --- examples/AddExample.cmake | 11 +- examples/LibConfig.cmake | 1 + examples/extras/CMakeLists.txt | 1 + examples/extras/physics/CMakeLists.txt | 2 + examples/extras/physics/PxEngine.hpp | 576 ++++++++++++++++++++++ examples/extras/physics/RigidbodyInfo.hpp | 140 ++++++ examples/extras/physics/physx_demo.cpp | 263 ++++++++++ include/threepp/core/Object3D.hpp | 3 + vcpkg.json | 6 + 9 files changed, 1002 insertions(+), 1 deletion(-) create mode 100644 examples/extras/physics/CMakeLists.txt create mode 100644 examples/extras/physics/PxEngine.hpp create mode 100644 examples/extras/physics/RigidbodyInfo.hpp create mode 100644 examples/extras/physics/physx_demo.cpp diff --git a/examples/AddExample.cmake b/examples/AddExample.cmake index 01fe5eeb..0e6084ae 100644 --- a/examples/AddExample.cmake +++ b/examples/AddExample.cmake @@ -1,7 +1,7 @@ function(add_example) - set(flags TRY_LINK_IMGUI LINK_IMGUI LINK_ASSIMP LINK_XML WEB) + set(flags TRY_LINK_IMGUI LINK_IMGUI LINK_ASSIMP LINK_XML LINK_PHYSX WEB) set(oneValueArgs NAME) set(multiValueArgs SOURCES WEB_EMBED) @@ -21,6 +21,11 @@ function(add_example) return() endif () + if (arg_LINK_PHYSX AND NOT unofficial-omniverse-physx-sdk_FOUND) + message(AUTHOR_WARNING "physx not found, skipping '${arg_NAME}' example..") + return() + endif () + if (NOT arg_SOURCES) add_executable("${arg_NAME}" "${arg_NAME}.cpp") @@ -38,6 +43,10 @@ function(add_example) target_link_libraries("${arg_NAME}" PRIVATE assimp::assimp) endif () + if (arg_LINK_PHYSX AND unofficial-omniverse-physx-sdk_FOUND) + target_link_libraries("${arg_NAME}" PRIVATE unofficial::omniverse-physx-sdk::sdk) + endif () + if (DEFINED EMSCRIPTEN) set(LINK_FLAGS " --bind -sUSE_GLFW=3 -sGL_DEBUG=1 -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -sFULL_ES3 -sASSERTIONS -sALLOW_MEMORY_GROWTH -sNO_DISABLE_EXCEPTION_CATCHING -sWASM=1") diff --git a/examples/LibConfig.cmake b/examples/LibConfig.cmake index d2aaf26b..48508fd3 100644 --- a/examples/LibConfig.cmake +++ b/examples/LibConfig.cmake @@ -1,5 +1,6 @@ find_package(imgui CONFIG QUIET) find_package(assimp CONFIG QUIET) +find_package(unofficial-omniverse-physx-sdk CONFIG QUIET) if (imgui_FOUND) set_property(TARGET imgui::imgui APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS HAS_IMGUI) diff --git a/examples/extras/CMakeLists.txt b/examples/extras/CMakeLists.txt index 5a346921..8d9a801b 100644 --- a/examples/extras/CMakeLists.txt +++ b/examples/extras/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(core) add_subdirectory(curves) +add_subdirectory(physics) diff --git a/examples/extras/physics/CMakeLists.txt b/examples/extras/physics/CMakeLists.txt new file mode 100644 index 00000000..4656ebc7 --- /dev/null +++ b/examples/extras/physics/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_example(NAME "physx_demo" LINK_PHYSX) diff --git a/examples/extras/physics/PxEngine.hpp b/examples/extras/physics/PxEngine.hpp new file mode 100644 index 00000000..864efb94 --- /dev/null +++ b/examples/extras/physics/PxEngine.hpp @@ -0,0 +1,576 @@ + +#ifndef THREEPP_PXENGINE_HPP +#define THREEPP_PXENGINE_HPP + +#include "PxPhysicsAPI.h" + +#include "RigidbodyInfo.hpp" + +#include "threepp/core/BufferGeometry.hpp" +#include "threepp/core/Object3D.hpp" +#include "threepp/geometries/BoxGeometry.hpp" +#include "threepp/geometries/CapsuleGeometry.hpp" +#include "threepp/geometries/CylinderGeometry.hpp" +#include "threepp/geometries/SphereGeometry.hpp" +#include "threepp/objects/InstancedMesh.hpp" +#include "threepp/objects/LineSegments.hpp" +#include "threepp/objects/Mesh.hpp" +#include "threepp/objects/Points.hpp" + +#include +#include + +namespace { + + using JointCreate = std::function; + + physx::PxVec3 toPxVector3(const threepp::Vector3& v) { + return {v.x, v.y, v.z}; + } + + physx::PxQuat toPxQuat(const threepp::Quaternion& q) { + return {q.x, q.y, q.z, q.w}; + } + + physx::PxTransform toPxTransform(const threepp::Matrix4& m) { + return physx::PxTransform(physx::PxMat44((float*) m.elements.data())); + } + +}// namespace + +class PxEngine: public threepp::Object3D { + +public: + bool debugVisualisation = true; + + explicit PxEngine(float timeStep = 1.f / 100) + : timeStep(timeStep), + sceneDesc(physics->getTolerancesScale()), + onMeshRemovedListener(this) { + + sceneDesc.gravity = physx::PxVec3(0.0f, -9.81f, 0.0f); + sceneDesc.flags |= physx::PxSceneFlag::eENABLE_PCM; + + if (!sceneDesc.cpuDispatcher) { + cpuDispatcher = physx::PxDefaultCpuDispatcherCreate(1); + sceneDesc.cpuDispatcher = cpuDispatcher; + } + if (!sceneDesc.filterShader) { + sceneDesc.filterShader = physx::PxDefaultSimulationFilterShader; + } + + sceneDesc.solverType = physx::PxSolverType::eTGS; + + scene = physics->createScene(sceneDesc); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eACTOR_AXES, 1.0f); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LOCAL_FRAMES, 0.5f); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eCONTACT_POINT, 2.0f); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LIMITS, 1.0f); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eCONTACT_FORCE, 1.0f); + scene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES, 1.0f); + + materials.emplace_back(physics->createMaterial(0.5f, 0.5f, 0.6f)); + + debugLines->material()->vertexColors = true; + debugPoints->material()->vertexColors = true; + debugTriangles->material()->vertexColors = true; + add(debugLines); + add(debugPoints); + add(debugTriangles); + } + + PxEngine(const PxEngine&) = delete; + PxEngine(PxEngine&&) = delete; + PxEngine& operator=(const PxEngine&) = delete; + PxEngine& operator=(PxEngine&&) = delete; + + void setup(threepp::Object3D& obj) { + + obj.traverse([this](threepp::Object3D& obj) { + if (obj.userData.count("rigidbodyInfo")) { + auto info = std::any_cast(obj.userData.at("rigidbodyInfo")); + registerObject(obj, info); + } + }); + + obj.traverse([this](threepp::Object3D& obj) { + if (obj.userData.count("rigidbodyInfo")) { + const auto info = std::any_cast(obj.userData.at("rigidbodyInfo")); + if (info._joint) { + physx::PxJoint* joint = nullptr; + switch (info._joint->type) { + case threepp::JointInfo::Type::HINGE: + joint = createJoint(physx::PxRevoluteJointCreate, &obj, info._joint->connectedBody, info._joint->anchor, info._joint->axis); + if (info._joint->limits) { + joint->is()->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, true); + joint->is()->setLimit({info._joint->limits->x, info._joint->limits->y}); + } + break; + case threepp::JointInfo::Type::PRISMATIC: + joint = createJoint(physx::PxDistanceJointCreate, &obj, info._joint->connectedBody, info._joint->anchor, info._joint->axis); + break; + case threepp::JointInfo::Type::BALL: + joint = createJoint(physx::PxSphericalJointCreate, &obj, info._joint->connectedBody, info._joint->anchor, info._joint->axis); + break; + case threepp::JointInfo::Type::LOCK: + joint = createJoint(physx::PxFixedJointCreate, &obj, info._joint->connectedBody, info._joint->anchor, info._joint->axis); + break; + } + if (joint) { + joints[&obj].push_back(joint); + } + } + } + }); + } + + void step(float dt) { + + internalTime += dt; + + while (internalTime >= timeStep) { + + scene->simulate(timeStep); + scene->fetchResults(true); + + internalTime -= timeStep; + } + + static threepp::Matrix4 tmpMat; + for (auto& [obj, rb] : bodies) { + + obj->updateMatrixWorld(); + + if (auto instanced = obj->as()) { + + tmpMat.copy(*obj->matrix).invert(); + auto& array = instanced->instanceMatrix()->array(); + for (int i = 0; i < instanced->count(); i++) { + auto pose = threepp::Matrix4().fromArray(physx::PxMat44(rb[i]->getGlobalPose()).front()); + pose.premultiply(tmpMat); + for (auto j = 0; j < 16; j++) { + array[i * 16 + j] = pose[j]; + } + } + + instanced->instanceMatrix()->needsUpdate(); + + } else { + + const auto pose = physx::PxMat44(rb.front()->getGlobalPose()); + obj->matrix->fromArray(pose.front()); + } + obj->matrix->premultiply(tmpMat.copy(*obj->parent->matrixWorld).invert()); + } + + if (debugVisualisation) { + visible = true; + scene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE, 1.0f); + debugRender(); + } else { + scene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE, 0); + visible = false; + } + } + + void registerObject(threepp::Object3D& obj, const threepp::RigidBodyInfo& info) { + + if (!info._useVisualGeometryAsCollider && info._colliders.empty()) { + return; + } + if (info._useVisualGeometryAsCollider && !obj.geometry()) { + return; + } + + obj.updateMatrixWorld(); + + const auto material = toPxMaterial(info._material); + + std::vector shapes; + + if (info._useVisualGeometryAsCollider) { + auto shape = toPxShape(obj.geometry(), material); + if (!shape) { + shape = physics->createShape(physx::PxSphereGeometry(0.1), *material, true);//dummy + } + shapes.emplace_back(shape); + } + + for (const auto& [collider, offset] : info._colliders) { + auto shape = toPxShape(collider, material); + shape->setLocalPose(toPxTransform(offset)); + shapes.emplace_back(shape); + } + + + if (info._type == threepp::RigidBodyInfo::Type::STATIC) { + auto staticActor = PxCreateStatic(*physics, toPxTransform(*obj.matrixWorld), *shapes.front()); + + for (unsigned i = 1; i < shapes.size(); i++) { + staticActor->attachShape(*shapes[i]); + } + + bodies[&obj].emplace_back(staticActor); + } else { + + auto createSingle = [&](float* data) { + auto rigidActor = PxCreateDynamic(*physics, physx::PxTransform(physx::PxMat44(data)), *shapes.front(), 1.f); + rigidActor->setSolverIterationCounts(30, 2); + + for (unsigned i = 1; i < shapes.size(); i++) { + rigidActor->attachShape(*shapes[i]); + } + + if (info._mass) { + physx::PxRigidBodyExt::setMassAndUpdateInertia(*rigidActor, *info._mass); + } else { + physx::PxRigidBodyExt::updateMassAndInertia(*rigidActor, 1.f); + } + return rigidActor; + }; + + if (auto instanced = obj.as()) { + + const auto& array = instanced->instanceMatrix()->array(); + + threepp::Matrix4 tmp; + for (unsigned i = 0; i < instanced->count(); i++) { + unsigned index = i * 16; + tmp.fromArray(array, index); + tmp.premultiply(*obj.matrixWorld); + auto body = createSingle(tmp.elements.data()); + bodies[&obj].emplace_back(body); + } + } else { + bodies[&obj].emplace_back(createSingle(obj.matrixWorld->elements.data())); + } + } + + for (auto shape : shapes) { + shape->release(); + } + + for (auto actor : bodies[&obj]) { + scene->addActor(*actor); + } + + obj.matrixAutoUpdate = false; + obj.addEventListener("remove", &onMeshRemovedListener); + } + + physx::PxJoint* createJoint(const JointCreate& create, threepp::Object3D* o1, threepp::Object3D* o2, threepp::Vector3 anchor, threepp::Vector3 axis) { + + o1->updateMatrixWorld(); + if (o2) o2->updateMatrixWorld(); + + auto rb1 = getBody(*o1); + auto rb2 = o2 ? getBody(*o2) : nullptr; + + threepp::Matrix4 f1; + f1.makeRotationFromQuaternion(threepp::Quaternion().setFromUnitVectors({1, 0, 0}, axis)); + f1.setPosition(anchor); + + threepp::Matrix4 f2 = o2 ? *o2->matrixWorld : threepp::Matrix4(); + f2.invert().multiply(*o1->matrixWorld).multiply(f1); + + physx::PxTransform frame1 = toPxTransform(f1); + physx::PxTransform frame2 = toPxTransform(f2); + + auto* joint = create(*physics, rb2, frame2, rb1, frame1); + joint->setConstraintFlag(physx::PxConstraintFlag::eVISUALIZATION, true); + + return joint; + } + + physx::PxRigidActor* getBody(threepp::Object3D& mesh, size_t index = 0) { + if (!bodies.count(&mesh)) return nullptr; + return bodies.at(&mesh)[index]; + } + + template + JointType* getJoint(threepp::Object3D& obj, int index = 0) { + if (!joints.count(&obj)) return nullptr; + auto* joint = joints.at(&obj).at(index); + return joint->is(); + } + + [[nodiscard]] physx::PxScene* getScene() const { + return scene; + } + + [[nodiscard]] physx::PxPhysics* getPhysics() const { + return physics; + } + + ~PxEngine() override { + + for (auto [_, list] : bodies) { + for (auto rb : list) rb->release(); + } + + for (auto material : materials) material->release(); + if (cpuDispatcher) cpuDispatcher->release(); + scene->release(); + physics->release(); + foundation->release(); + } + +private: + float timeStep; + float internalTime{}; + + // Initialize the Physics SDK + physx::PxDefaultAllocator allocator; + physx::PxDefaultErrorCallback errorCallback; + physx::PxFoundation* foundation = PxCreateFoundation(PX_PHYSICS_VERSION, allocator, errorCallback); + physx::PxPhysics* physics = PxCreatePhysics(PX_PHYSICS_VERSION, *foundation, physx::PxTolerancesScale()); + + physx::PxDefaultCpuDispatcher* cpuDispatcher; + physx::PxSceneDesc sceneDesc; + physx::PxScene* scene; + std::vector materials; + + std::unordered_map> bodies; + std::unordered_map> joints; + + std::shared_ptr debugTriangles = threepp::Mesh::create(); + std::shared_ptr debugLines = threepp::LineSegments::create(); + std::shared_ptr debugPoints = threepp::Points::create(); + + physx::PxMaterial* toPxMaterial(const std::optional material) { + if (!material) { + return materials.front(); + } + return physics->createMaterial(material->friction, material->friction, material->restitution); + } + + physx::PxShape* toPxShape(const threepp::Collider& collider, physx::PxMaterial* material) { + + switch (collider.index()) { + case 0:// Sphere + { + threepp::SphereCollider sphere = std::get(collider); + return physics->createShape(physx::PxSphereGeometry(sphere.radius), *material, false); + } + case 1:// Box + { + threepp::BoxCollider box = std::get(collider); + return physics->createShape(physx::PxBoxGeometry(box.halfWidth, box.halfHeight, box.halfDepth), *material, false); + } + case 2:// Capsule + { + threepp::CapsuleCollider box = std::get(collider); + return physics->createShape(physx::PxCapsuleGeometry(box.radius, box.halfHeight), *material, false); + } + default: + return nullptr; + } + } + + physx::PxShape* toPxShape(const threepp::BufferGeometry* geometry, physx::PxMaterial* material) { + + if (!geometry) return nullptr; + + const auto type = geometry->type(); + if (type == "BoxGeometry") { + const auto box = dynamic_cast(geometry); + return physics->createShape(physx::PxBoxGeometry(physx::PxVec3{box->width / 2, box->height / 2, box->depth / 2}), *material, false); + } else if (type == "SphereGeometry") { + const auto sphere = dynamic_cast(geometry); + return physics->createShape(physx::PxSphereGeometry(sphere->radius), *material, false); + } else if (type == "CapsuleGeometry") { + const auto cap = dynamic_cast(geometry); + auto capShape = physics->createShape(physx::PxCapsuleGeometry(cap->radius, cap->length / 2), *material, false); + capShape->setLocalPose(physx::PxTransform(physx::PxQuat(physx::PxHalfPi, {0, 0, 1}))); + return capShape; + } else { + + if (auto pos = geometry->getAttribute("position")) { + physx::PxConvexMeshDesc convexDesc; + convexDesc.points.count = pos->count() / 2; + convexDesc.points.stride = sizeof(physx::PxVec3); + convexDesc.points.data = pos->array().data(); + convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX; + + physx::PxTolerancesScale scale; + physx::PxCookingParams params(scale); + + physx::PxDefaultMemoryOutputStream buf; + physx::PxConvexMeshCookingResult::Enum result; + if (!PxCookConvexMesh(params, convexDesc, buf, &result)) { + return nullptr; + } + physx::PxDefaultMemoryInputData input(buf.getData(), buf.getSize()); + auto convexMesh = physics->createConvexMesh(input); + + return physics->createShape(physx::PxConvexMeshGeometry(convexMesh), *material, true); + } + + return nullptr; + } + } + + void debugRender() { + const auto& buffer = scene->getRenderBuffer(); + + // lines + + std::vector lineVertices(buffer.getNbLines() * 6); + std::vector lineColors(buffer.getNbLines() * 6); + for (auto i = 0; i < buffer.getNbLines(); i++) { + const auto& line = buffer.getLines()[i]; + + lineVertices.emplace_back(line.pos0.x); + lineVertices.emplace_back(line.pos0.y); + lineVertices.emplace_back(line.pos0.z); + lineVertices.emplace_back(line.pos1.x); + lineVertices.emplace_back(line.pos1.y); + lineVertices.emplace_back(line.pos1.z); + + threepp::Color c1 = line.color0; + threepp::Color c2 = line.color1; + lineColors.emplace_back(c1.r); + lineColors.emplace_back(c1.g); + lineColors.emplace_back(c1.b); + lineColors.emplace_back(c2.r); + lineColors.emplace_back(c2.g); + lineColors.emplace_back(c2.b); + } + + debugLines->visible = !lineVertices.empty(); + if (!lineVertices.empty()) { + auto pos = debugLines->geometry()->getAttribute("position"); + auto col = debugLines->geometry()->getAttribute("color"); + if (pos && pos->array().size() >= lineVertices.size()) {//re-use geometry + std::copy(lineVertices.begin(), lineVertices.end(), pos->array().begin()); + std::copy(lineColors.begin(), lineColors.end(), col->array().begin()); + pos->needsUpdate(); + col->needsUpdate(); + debugLines->geometry()->setDrawRange(0, lineVertices.size() / 3); + } else { + auto geom = threepp::BufferGeometry::create(); + geom->setAttribute("position", threepp::FloatBufferAttribute::create(lineVertices, 3)); + geom->setAttribute("color", threepp::FloatBufferAttribute::create(lineColors, 3)); + debugLines->setGeometry(geom); + } + } + + // triangles + + std::vector triangleVertices(buffer.getNbTriangles() * 9); + std::vector triangleColors(buffer.getNbTriangles() * 9); + for (auto i = 0; i < buffer.getNbTriangles(); i++) { + const auto& point = buffer.getTriangles()[i]; + + auto pos1 = point.pos0; + auto pos2 = point.pos1; + auto pos3 = point.pos2; + + triangleVertices.emplace_back(pos1.x); + triangleVertices.emplace_back(pos1.y); + triangleVertices.emplace_back(pos1.z); + + triangleVertices.emplace_back(pos2.x); + triangleVertices.emplace_back(pos2.y); + triangleVertices.emplace_back(pos2.z); + + triangleVertices.emplace_back(pos3.x); + triangleVertices.emplace_back(pos3.y); + triangleVertices.emplace_back(pos3.z); + + threepp::Color color1 = point.color0; + threepp::Color color2 = point.color1; + threepp::Color color3 = point.color2; + + triangleColors.emplace_back(color1.r); + triangleColors.emplace_back(color1.g); + triangleColors.emplace_back(color1.b); + + triangleColors.emplace_back(color2.r); + triangleColors.emplace_back(color2.g); + triangleColors.emplace_back(color2.b); + + triangleColors.emplace_back(color3.r); + triangleColors.emplace_back(color3.g); + triangleColors.emplace_back(color3.b); + } + + debugTriangles->visible = !triangleVertices.empty(); + if (!triangleVertices.empty()) { + auto pos = debugTriangles->geometry()->getAttribute("position"); + auto col = debugTriangles->geometry()->getAttribute("color"); + if (pos && pos->array().size() >= triangleVertices.size()) {//re-use geometry + std::copy(triangleVertices.begin(), triangleVertices.end(), pos->array().begin()); + std::copy(triangleColors.begin(), triangleColors.end(), col->array().begin()); + pos->needsUpdate(); + col->needsUpdate(); + debugTriangles->geometry()->setDrawRange(0, triangleVertices.size() / 3); + } else { + auto geom = threepp::BufferGeometry::create(); + geom->setAttribute("position", threepp::FloatBufferAttribute::create(triangleVertices, 3)); + geom->setAttribute("color", threepp::FloatBufferAttribute::create(triangleColors, 3)); + debugTriangles->setGeometry(geom); + } + } + + // points + + std::vector pointVertices(buffer.getNbPoints() * 3); + std::vector pointColors(buffer.getNbPoints() * 3); + for (auto i = 0; i < buffer.getNbPoints(); i++) { + const auto& point = buffer.getPoints()[i]; + + auto pos = point.pos; + pointVertices.emplace_back(pos.x); + pointVertices.emplace_back(pos.y); + pointVertices.emplace_back(pos.z); + + threepp::Color color = point.color; + pointColors.emplace_back(color.r); + pointColors.emplace_back(color.g); + pointColors.emplace_back(color.b); + } + + debugPoints->visible = !pointVertices.empty(); + if (!pointVertices.empty()) { + auto pos = debugPoints->geometry()->getAttribute("position"); + auto col = debugPoints->geometry()->getAttribute("color"); + if (pos && pos->array().size() >= pointVertices.size()) {//re-use geometry + std::copy(pointVertices.begin(), pointVertices.end(), pos->array().begin()); + std::copy(pointColors.begin(), pointColors.end(), col->array().begin()); + pos->needsUpdate(); + col->needsUpdate(); + debugPoints->geometry()->setDrawRange(0, pointVertices.size() / 3); + } else { + auto geom = threepp::BufferGeometry::create(); + geom->setAttribute("position", threepp::FloatBufferAttribute::create(pointVertices, 3)); + geom->setAttribute("color", threepp::FloatBufferAttribute::create(pointColors, 3)); + debugPoints->setGeometry(geom); + } + } + } + + struct MeshRemovedListener: threepp::EventListener { + + explicit MeshRemovedListener(PxEngine* scope): scope(scope) {} + + void onEvent(threepp::Event& event) override { + if (event.type == "remove") { + auto m = static_cast(event.target); + if (scope->bodies.count(m)) { + auto rb = scope->bodies.at(m); + scope->scene->removeActor(*rb.front()); + scope->bodies.erase(m); + } + m->removeEventListener("remove", this); + } + } + + private: + PxEngine* scope; + }; + + MeshRemovedListener onMeshRemovedListener; +}; + +#endif//THREEPP_PXENGINE_HPP diff --git a/examples/extras/physics/RigidbodyInfo.hpp b/examples/extras/physics/RigidbodyInfo.hpp new file mode 100644 index 00000000..34134db5 --- /dev/null +++ b/examples/extras/physics/RigidbodyInfo.hpp @@ -0,0 +1,140 @@ + + +#ifndef THREEPP_RIGIDBODYINFO_HPP +#define THREEPP_RIGIDBODYINFO_HPP + +#include +#include +#include +#include +#include + +#include "threepp/math/Matrix4.hpp" +#include "threepp/math/Vector2.hpp" +#include "threepp/math/Vector3.hpp" + +namespace threepp { + + class Object3D; + + struct JointInfo { + + enum class Type { + LOCK, + HINGE, + PRISMATIC, + BALL + }; + + threepp::Vector3 anchor; + threepp::Vector3 axis{0, 1, 0}; + threepp::Object3D* connectedBody = nullptr; + std::optional limits; + + Type type = Type::LOCK; + + JointInfo& setConnectedBody(threepp::Object3D& body) { + connectedBody = &body; + return *this; + } + + JointInfo& setAxis(Vector3 axis) { + this->axis = axis; + return *this; + } + + JointInfo& setLimits(Vector2 limits) { + this->limits = limits; + return *this; + } + + JointInfo& setAnchor(Vector3 anchor) { + this->anchor = anchor; + return *this; + } + + JointInfo& setType(Type type) { + this->type = type; + return *this; + } + }; + + struct CapsuleCollider { + float halfHeight; + float radius; + + CapsuleCollider(float radius, float halfHeight) + : radius(radius), halfHeight(halfHeight) {} + }; + + struct SphereCollider { + float radius; + + explicit SphereCollider(float radius) + : radius(radius) {} + }; + + struct BoxCollider { + float halfWidth = 0.5; + float halfHeight = 0.5; + float halfDepth = 0.5; + + BoxCollider(float halfWidth, float halfHeight, float halfDepth) + : halfWidth(halfWidth), halfHeight(halfHeight), halfDepth(halfDepth) {} + }; + + struct MaterialInfo { + float friction; + float restitution ; + + MaterialInfo(float friction, float restitution) + : friction(friction), restitution(restitution) {} + }; + + using Collider = std::variant; + + struct RigidBodyInfo { + + enum class Type { + STATIC, + DYNAMIC + }; + + Type _type; + std::optional _mass; + std::optional _joint; + std::vector> _colliders; + std::optional _material; + bool _useVisualGeometryAsCollider{true}; + + explicit RigidBodyInfo(Type type = Type::DYNAMIC): _type(type) {} + + RigidBodyInfo& setMass(float mass) { + this->_mass = mass; + return *this; + } + + RigidBodyInfo& addCollider(Collider collider, Matrix4 offset = Matrix4()) { + this->_colliders.emplace_back(collider, offset); + return *this; + } + + RigidBodyInfo& useVisualGeometryAsCollider(bool flag) { + this->_useVisualGeometryAsCollider = flag; + return *this; + } + + RigidBodyInfo& setMaterialProperties(float friction, float restitution) { + this->_material = MaterialInfo(friction, restitution); + return *this; + } + + JointInfo& addJoint() { + this->_joint = JointInfo(); + return *_joint; + } + }; + +}// namespace threepp + +#endif//THREEPP_RIGIDBODYINFO_HPP diff --git a/examples/extras/physics/physx_demo.cpp b/examples/extras/physics/physx_demo.cpp new file mode 100644 index 00000000..12a658f2 --- /dev/null +++ b/examples/extras/physics/physx_demo.cpp @@ -0,0 +1,263 @@ + +#include "PxEngine.hpp" + +#include "threepp/threepp.hpp" + +#include + +using namespace threepp; + +namespace { + + struct KeyController: KeyListener { + + explicit KeyController(std::vector joints) + : joints(std::move(joints)) { + + for (auto j : this->joints) { + j->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eDRIVE_ENABLED, true); + } + } + + void onKeyPressed(KeyEvent evt) override; + void onKeyReleased(KeyEvent evt) override; + + private: + float speed = 2; + std::vector joints; + }; + + void KeyController::onKeyPressed(KeyEvent evt) { + if (evt.key == Key::NUM_1) { + joints[0]->setDriveVelocity(-speed); + } else if (evt.key == Key::NUM_2) { + joints[0]->setDriveVelocity(speed); + } else if (evt.key == Key::NUM_3) { + joints[1]->setDriveVelocity(-speed); + } else if (evt.key == Key::NUM_4) { + joints[1]->setDriveVelocity(speed); + } else if (evt.key == Key::NUM_5) { + joints[2]->setDriveVelocity(-speed); + } else if (evt.key == Key::NUM_6) { + joints[2]->setDriveVelocity(speed); + } + } + + void KeyController::onKeyReleased(KeyEvent evt) { + switch (evt.key) { + case Key::NUM_1: + case Key::NUM_2: { + joints[0]->setDriveVelocity(0); + } break; + case Key::NUM_3: + case Key::NUM_4: { + joints[1]->setDriveVelocity(0); + } break; + case Key::NUM_5: + case Key::NUM_6: { + joints[2]->setDriveVelocity(0); + } break; + default: + break; + } + } + + void setupInstancedMesh(InstancedMesh& mesh) { + + Matrix4 matrix; + size_t index = 0; + int amount = static_cast(std::cbrt(mesh.count())); + float offset = static_cast(amount - 1) / 2; + for (int x = 0; x < amount; x++) { + for (int y = 0; y < amount; y++) { + for (int z = 0; z < amount; z++) { + matrix.setPosition(offset - float(x), offset - float(y), offset - float(z)); + mesh.setMatrixAt(index, matrix); + ++index; + } + } + } + } + + auto spawnObject() { + + auto geometry = SphereGeometry::create(0.1); + auto material = MeshPhongMaterial::create(); + + auto mesh = Mesh::create(geometry, material); + mesh->castShadow = true; + + RigidBodyInfo info; + info.setMass(10).setMaterialProperties(0.5, 0.9); + mesh->userData["rigidbodyInfo"] = info; + + return mesh; + } + +}// namespace + +int main() { + + PxEngine engine; + + Canvas canvas("PhysX", {{"aa", 6}}); + GLRenderer renderer(canvas.size()); + renderer.shadowMap().enabled = true; + + Scene scene; + scene.background = Color::aliceblue; + + PerspectiveCamera camera(60, canvas.aspect()); + camera.position.set(0, 2.5, -8); + + TextureLoader tl; + auto tex = tl.load("data/textures/checker.png"); + auto defaultMaterial = MeshPhongMaterial::create({{"color", Color::darkblue}}); + + auto box1 = Mesh::create(BoxGeometry::create(0.5, 1, 0.5), defaultMaterial); + box1->position.y = 0.5; + box1->castShadow = true; + scene.add(box1); + + auto box2 = Mesh::create(BoxGeometry::create(0.25, 2, 0.25), defaultMaterial); + box2->position.y = 1.5; + box2->castShadow = true; + box1->add(box2); + + auto box3 = Mesh::create(CylinderGeometry::create(0.15, 0.15, 0.5), defaultMaterial); + box3->position.y = 1.25; + box3->castShadow = true; + box2->add(box3); + + STLLoader loader; + auto geometry = loader.load("data/models/stl/pr2_head_pan.stl"); + geometry->scale(5, 5, 5); + auto sphereMaterial = MeshPhongMaterial::create({{"color", Color::red}}); + auto mesh = Mesh::create(geometry, sphereMaterial); + mesh->castShadow = true; + mesh->position.y = 10; + scene.add(mesh); + + int groundSize = 60; + auto groundMaterial = MeshPhongMaterial::create({{"color", Color::gray}}); + auto ground = Mesh::create(BoxGeometry::create(groundSize, 0.1, groundSize), groundMaterial); + ground->position.y = -0.05; + ground->receiveShadow = true; + scene.add(ground); + + auto instanceMaterial = MeshPhongMaterial::create(); + instanceMaterial->color = Color::gray; + auto instanced = InstancedMesh::create(SphereGeometry::create(0.25), instanceMaterial, static_cast(std::pow(3, 3))); + setupInstancedMesh(*instanced); + instanced->position.y = 50; + instanced->castShadow = true; + scene.add(instanced); + + auto light1 = AmbientLight::create(Color::white, 0.3f); + scene.add(light1); + + auto light2 = DirectionalLight::create(); + light2->position.set(100, 100, -100); + light2->castShadow = true; + auto shadowCamera = light2->shadow->camera->as(); + shadowCamera->left = shadowCamera->bottom = -10; + shadowCamera->right = shadowCamera->top = 10; + scene.add(light2); + + auto box1Body = RigidBodyInfo{}; + box1Body.addJoint() + .setType(JointInfo::Type::HINGE) + .setAnchor({0, -0.5, 0}) + .setAxis({0, 1, 0}) + .setLimits({math::degToRad(-120), math::degToRad(120)}); + box1->userData["rigidbodyInfo"] = box1Body; + + auto box2Body = RigidBodyInfo{}; + box2Body.addJoint() + .setType(JointInfo::Type::HINGE) + .setAnchor({0, -1, 0}) + .setAxis({0, 0, 1}) + .setConnectedBody(*box1) + .setLimits({math::degToRad(0), math::degToRad(120)}); + box2->userData["rigidbodyInfo"] = box2Body; + + auto box3Body = RigidBodyInfo{}; + box3Body.addJoint() + .setType(JointInfo::Type::HINGE) + .setAnchor({0, -0.25, 0}) + .setAxis({0, 1, 0}) + .setConnectedBody(*box2) + .setLimits({math::degToRad(-90), math::degToRad(90)}); + box3->userData["rigidbodyInfo"] = box3Body; + + auto groundBody = RigidBodyInfo{RigidBodyInfo::Type::STATIC} + .addCollider(BoxCollider(0.5, 2, groundSize / 2), Matrix4().setPosition(groundSize / 2, 2, 0)) + .addCollider(BoxCollider(0.5, 2, groundSize / 2), Matrix4().setPosition(-groundSize / 2, 2, 0)) + .addCollider(BoxCollider(0.5, 2, groundSize / 2), Matrix4().makeRotationY(math::PI / 2).setPosition(0, 2, groundSize / 2)) + .addCollider(BoxCollider(0.5, 2, groundSize / 2), Matrix4().makeRotationY(math::PI / 2).setPosition(0, 2, -groundSize / 2)); + ground->userData["rigidbodyInfo"] = groundBody; + + auto meshBody = RigidBodyInfo{}.setMass(20).setMaterialProperties(1, 0); + mesh->userData["rigidbodyInfo"] = meshBody; + + auto instancedBody = RigidBodyInfo(); + instanced->userData["rigidbodyInfo"] = instancedBody; + + engine.setup(scene); + scene.add(engine); + + auto* j1 = engine.getJoint(*box1); + auto* j2 = engine.getJoint(*box2); + auto* j3 = engine.getJoint(*box3); + + KeyController keyListener({j1, j2, j3}); + canvas.addKeyListener(keyListener); + + OrbitControls controls(camera, canvas); + + bool run = false; + KeyAdapter adapter(KeyAdapter::Mode::KEY_PRESSED | KeyAdapter::KEY_REPEAT, [&](KeyEvent evt) { + run = true; + + if (evt.key == Key::SPACE) { + auto obj = spawnObject(); + obj->position = camera.position; + scene.add(obj); + engine.setup(*obj); + auto rb = engine.getBody(*obj)->is(); + Vector3 world; + camera.getWorldDirection(world); + rb->addForce(toPxVector3(world * 10000)); + + canvas.invokeLater([&, obj] { + scene.remove(*obj); + }, + 2);// remove after 2 seconds + } else if (evt.key == Key::D) { + engine.debugVisualisation = !engine.debugVisualisation; + } + }); + canvas.addKeyListener(adapter); + + canvas.onWindowResize([&](WindowSize size) { + camera.aspect = size.aspect(); + camera.updateProjectionMatrix(); + renderer.setSize(size); + }); + + std::cout << "Press any key to start.\n" + "\nPress 'd' to toggle debug visualization." + "\nPress 'space' to spawn spheres." + << std::endl; + + Clock clock; + canvas.animate([&] { + auto dt = clock.getDelta(); + + renderer.render(scene, camera); + + if (run) { + engine.step(dt); + } + }); +} diff --git a/include/threepp/core/Object3D.hpp b/include/threepp/core/Object3D.hpp index 6ccfde66..93deb635 100644 --- a/include/threepp/core/Object3D.hpp +++ b/include/threepp/core/Object3D.hpp @@ -14,6 +14,7 @@ #include "misc.hpp" +#include #include #include #include @@ -99,6 +100,8 @@ namespace threepp { // When this property is set for an instance of Group, all descendants objects will be sorted and rendered together. Sorting is from lowest to highest renderOrder. Default value is 0. unsigned int renderOrder = 0; + std::unordered_map userData; + std::optional onBeforeRender; std::optional onAfterRender; diff --git a/vcpkg.json b/vcpkg.json index d299e57e..4794c26a 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -29,6 +29,12 @@ ] } ] + }, + "physx": { + "description": "Enable physx in examples", + "dependencies": [ + "physx" + ] } } }