commit 11adbb57bd35716bc7731ee6550e15bdddb8dd13 Author: Joshua Ashton Date: Sun Aug 28 22:31:01 2022 +0100 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4fd074c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +# This file is here so GitHub uses 4-space tabs... + +root = true + +[*] +indent_style = tab +indent_size = 4 diff --git a/.github/workflows/artifacts-windows.yml b/.github/workflows/artifacts-windows.yml new file mode 100644 index 0000000..ff60b5d --- /dev/null +++ b/.github/workflows/artifacts-windows.yml @@ -0,0 +1,52 @@ +name: Build Artifacts for Windows + +on: [push, pull_request, workflow_dispatch] + +jobs: + build-set-windows: + runs-on: windows-2022 + + steps: + - name: Checkout Mini Source SDK 2013 + id: checkout-minisdk + uses: actions/checkout@v3 + with: + repository: 'Joshua-Ashton/mini-source-sdk-2013' + + - name: Checkout VPhysics Jolt + id: checkout-code + uses: actions/checkout@v3 + with: + path: 'mp/src/vphysics_jolt' + submodules: recursive + + - name: Find Visual Studio + id: find-vs + shell: pwsh + run: | + $installationPath = Get-VSSetupInstance ` + | Select-VSSetupInstance -Require Microsoft.VisualStudio.Workload.NativeDesktop -Latest ` + | Select-Object -ExpandProperty InstallationPath + Write-Output "VSDEVCMD=${installationPath}\Common7\Tools\VsDevCmd.bat" ` + | Out-File -FilePath "${Env:GITHUB_ENV}" -Append + + - name: Build MSVC x86 + id: build + shell: pwsh + working-directory: 'mp/src' + run: | + & "${Env:COMSPEC}" /s /c "`"${Env:VSDEVCMD}`" -arch=x86 -host_arch=x64 -no_logo && set" ` + | % { , ($_ -Split '=', 2) } ` + | % { [System.Environment]::SetEnvironmentVariable($_[0], $_[1]) } + .\fix_registry.bat + .\createjoltprojects.bat + devenv jolt.sln /upgrade + msbuild jolt.sln /nodeReuse:false /t:Rebuild /p:Configuration=Release /p:Platform=x86 /m /v:minimal + + - name: Upload artifacts + id: upload-artifacts + uses: actions/upload-artifact@v2 + with: + name: vphysics_jolt_sdk2013_win32 + path: mp/game + if-no-files-found: error diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e55d9e9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "joltphysics/src"] + path = joltphysics/src + url = https://github.com/jrouwe/JoltPhysics/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6e847f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2022 Joshua Ashton and Josh Dowell (Slartibarty) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fea3f73 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +![VPhysics Jolt Logo](assets/cube_base_nobg.png "VPhysics Jolt") + +## What is Volt? ⚑ + +Volt (VPhysics Jolt) is a replacement for Source's VPhysics which uses IVP/Havok
+Created by [Joshua Ashton (🐸✨)](https://github.com/Joshua-Ashton) [@phys_ballsocket](https://twitter.com/phys_ballsocket) and [Josh Dowell (Slartibarty)](https://github.com/Slartibarty) [@Slartbarty](https://twitter.com/Slartbarty). + +Volt is designed to be incredibly high performance, supporting thousands of objects at once without bringing tick/framerate down to a crawl. + +In our testing, the performance overhead from having thousands of objects moving at once, now comes from the client code needing to update/render, as going out of the PVS of these objects will still cause them to be simulated, but will no longer be rendered. + +## Features + +Volt is mostly feature complete, but is missing some things such as support for raycast vehicles, breakable constraints, and perhaps other things we've missed! + +It is our goal to implement any missing features at some point. + +Below is a feature table of Volt vs VPhysics and the Bullet VPhysics project. +If we missed anything we don't support or we do, feel free to add to it. +It is not meant to be a bash on anyone elses work however, the Bullet VPhysics project was a great inspiration to us! + +| Feature | VPhysics | Volt (VPhysics Jolt) | Bullet VPhysics | +|:--------------|:--------:|:-------------:|:---------------:| +| Constraints (except Pulleys) | βœ”οΈ | βœ”οΈ | βœ”οΈ | +| Pulleys | βœ”οΈ | ❌ | ❌ | +| Breakable constraints | βœ”οΈ | ❌ | ❌ | +| Motors (Motion Controllers) | βœ”οΈ | βœ”οΈ | βœ”οΈ | +| Motors (Constraint) | βœ”οΈ | βœ”οΈ | ❌ | +| Ragdolls | βœ”οΈ | βœ”οΈ (some bugs) | βœ”οΈ | +| Triggers | βœ”οΈ | βœ”οΈ | ❌ | +| Object touch callbacks | βœ”οΈ | βœ”οΈ | ❌ | +| Prop damage/breaking | βœ”οΈ | βœ”οΈ | ❌ | +| Fluid events | βœ”οΈ | βœ”οΈ | ❌ | +| Prop splashing effects | βœ”οΈ | βœ”οΈ | ❌ | +| Wheeled Vehicles | βœ”οΈ | βœ”οΈ | βœ”οΈ | +| Raycast Vehicles (ie. Airboat) | βœ”οΈ | ❌ | 〰️ (janky) | +| NPCs/Doors (Shadow Controllers) | βœ”οΈ | βœ”οΈ | βœ”οΈ | +| Save/Restore Support | βœ”οΈ | βœ”οΈ | ❌ | +| Portal Support | βœ”οΈ | βœ”οΈ | ❌ | +| Game per-object collide callback support
eg. no-collide | βœ”οΈ | βœ”οΈ | ❌ | +| Crash-resistant solver | ❌ | βœ”οΈ | (no data) | +| Supports thousands of objects without lag | ❌ | βœ”οΈ | ❌ | +| Multithreaded | ❌ | βœ”οΈ | βœ”οΈ (partially) | +| Proper player controller | βœ”οΈ | ❌ (needs work!) | βœ”οΈ (partially) | + +## Bugs + +VPhysics Jolt is not without its flaws, however. See the [issue tracker](https://github.com/Joshua-Ashton/VPhysics-Jolt/issues) for bugs that are known. + +There are definitely going to be bugs that we don't know about or haven't encountered, or different quirks across engine branches. + +## How to build + +The Volt code is provided as-is, it is up to you to build it for your SDK, etc. + +Volt should build fine against Source SDK 2013 and Alien Swarm SDK on a MSVC or GCC compiler with at least C++20 support. + +*If you are building directly against the public SDK 2013 and Alien Swarm SDKs, you will need to do minor work to memoverride.cpp to make it compatible with the newer compilers and newer Windows SDKs.* + +Unfortunately we cannot redistribute the additional code/headers needed to build Volt for games such as Garry's Mod which uses a different VPhysics interface (CS:GO's) to what is found in the Valve-provided public SDKs. + +## Download + +For each release, binary builds are provided for Garry's Mod and Source SDK 2013 on the [Releases](https://github.com/Joshua-Ashton/VPhysics-Jolt/releases/) page. + +## Media + +### Lots of Melons + Dumpster +[![Lots of Melons + Dumpster](https://img.youtube.com/vi/gPDQkmfQCsc/0.jpg)](https://www.youtube.com/watch?v=gPDQkmfQCsc "Lots of Melons + Dumpster") + +### Physically Simulated Chain +[![Physically Simulated Chain](https://img.youtube.com/vi/tVmQTmbSJM0/0.jpg)](https://www.youtube.com/watch?v=tVmQTmbSJM0 "Physically Simulated Chain") + +### Lots of Balls Test +[![Lots of Balls Test](https://img.youtube.com/vi/tYfiTyRtmz8/0.jpg)](https://www.youtube.com/watch?v=tYfiTyRtmz8 "Lots of Balls Test") + +### Wheels + Weld Car Dupe Test +[![Weld Car Dupe Test](https://img.youtube.com/vi/5_QbbXbIrg8/0.jpg)](https://www.youtube.com/watch?v=5_QbbXbIrg8 "Weld Car Dupe Test") + +### Door + NPC (Physics Shadowed Objects) Test +[![Door + NPC (Physics Shadowed Objects) Test](https://img.youtube.com/vi/SdEj7HTuJmU/0.jpg)](https://www.youtube.com/watch?v=SdEj7HTuJmU "Door + NPC (Physics Shadowed Objects) Test") + +### Lots of Cubes + Ragdolls + Funnel +[![Lots of Cubes + Ragdolls + Funnel](https://img.youtube.com/vi/CLVnSwg33Dk/0.jpg)](https://www.youtube.com/watch?v=CLVnSwg33Dk "Lots of Cubes + Ragdolls + Funnel") + +### Slow Mo Cubes +[![Slow Mo Cubes](https://img.youtube.com/vi/GzW_4bufwEk/0.jpg)](https://www.youtube.com/watch?v=GzW_4bufwEk "Slow Mo Cubes") + +### Propane in Dumpster +[![Propane in Dumpster](https://img.youtube.com/vi/10vvRJVHGQc/0.jpg)](https://www.youtube.com/watch?v=10vvRJVHGQc "Propane in Dumpster") + +*Have some cool media of stuff going on in Volt you'd like to add? Feel free to make a pull request!* + +## Projects using Volt + +### [Portal 2: Desolation](https://emberspark.games/desolation/) + +### [Prelude Online](https://prelude.online/) + +# Have fun! 🐸⚑ \ No newline at end of file diff --git a/assets/cube_base_nobg.png b/assets/cube_base_nobg.png new file mode 100644 index 0000000..bd850b3 Binary files /dev/null and b/assets/cube_base_nobg.png differ diff --git a/joltphysics/joltphysics.vpc b/joltphysics/joltphysics.vpc new file mode 100644 index 0000000..b973d3f --- /dev/null +++ b/joltphysics/joltphysics.vpc @@ -0,0 +1,416 @@ +//----------------------------------------------------------------------------- +// JOLTPHYSICS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTLIBNAME "joltphysics" + +$Include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Include "joltphysics_settings.vpc" + +$Configuration +{ + $Compiler + { + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "Jolt/Jolt.h" + } +} + +$Project "JoltPhysics" +{ + $Folder "Precompiled Header" + { + $File "pch.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + } + + $Folder "Source Files" + { + $File "src\Jolt\AABBTree\AABBTreeBuilder.cpp" + $File "src\Jolt\AABBTree\AABBTreeBuilder.h" + $File "src\Jolt\AABBTree\AABBTreeToBuffer.h" + $File "src\Jolt\AABBTree\NodeCodec\NodeCodecQuadTreeHalfFloat.h" + $File "src\Jolt\AABBTree\TriangleCodec\TriangleCodecIndexed8BitPackSOA4Flags.h" + $File "src\Jolt\Core\Atomics.h" + $File "src\Jolt\Core\ByteBuffer.h" + $File "src\Jolt\Core\Color.cpp" + $File "src\Jolt\Core\Color.h" + $File "src\Jolt\Core\Core.h" + $File "src\Jolt\Core\Factory.cpp" + $File "src\Jolt\Core\Factory.h" + $File "src\Jolt\Core\FixedSizeFreeList.h" + $File "src\Jolt\Core\FixedSizeFreeList.inl" + $File "src\Jolt\Core\FPControlWord.h" + $File "src\Jolt\Core\FPException.h" + $File "src\Jolt\Core\FPFlushDenormals.h" + $File "src\Jolt\Core\HashCombine.h" + $File "src\Jolt\Core\IssueReporting.cpp" + $File "src\Jolt\Core\IssueReporting.h" + $File "src\Jolt\Core\JobSystem.h" + $File "src\Jolt\Core\JobSystem.inl" + $File "src\Jolt\Core\JobSystemThreadPool.cpp" + $File "src\Jolt\Core\JobSystemThreadPool.h" + $File "src\Jolt\Core\LinearCurve.cpp" + $File "src\Jolt\Core\LinearCurve.h" + $File "src\Jolt\Core\LockFreeHashMap.h" + $File "src\Jolt\Core\LockFreeHashMap.inl" + $File "src\Jolt\Core\Memory.cpp" + $File "src\Jolt\Core\Memory.h" + $File "src\Jolt\Core\Mutex.h" + $File "src\Jolt\Core\MutexArray.h" + $File "src\Jolt\Core\NonCopyable.h" + $File "src\Jolt\Core\Profiler.cpp" + $File "src\Jolt\Core\Profiler.h" + $File "src\Jolt\Core\Profiler.inl" + $File "src\Jolt\Core\Reference.h" + $File "src\Jolt\Core\Result.h" + $File "src\Jolt\Core\RTTI.cpp" + $File "src\Jolt\Core\RTTI.h" + $File "src\Jolt\Core\StaticArray.h" + $File "src\Jolt\Core\StreamIn.h" + $File "src\Jolt\Core\StreamOut.h" + $File "src\Jolt\Core\StreamWrapper.h" + $File "src\Jolt\Core\StringTools.cpp" + $File "src\Jolt\Core\StringTools.h" + $File "src\Jolt\Core\TempAllocator.h" + $File "src\Jolt\Core\TickCounter.cpp" + $File "src\Jolt\Core\TickCounter.h" + $File "src\Jolt\Geometry\AABox.h" + $File "src\Jolt\Geometry\AABox4.h" + $File "src\Jolt\Geometry\ClipPoly.h" + $File "src\Jolt\Geometry\ClosestPoint.h" + $File "src\Jolt\Geometry\ConvexHullBuilder.cpp" + $File "src\Jolt\Geometry\ConvexHullBuilder.h" + $File "src\Jolt\Geometry\ConvexHullBuilder2D.cpp" + $File "src\Jolt\Geometry\ConvexHullBuilder2D.h" + $File "src\Jolt\Geometry\ConvexSupport.h" + $File "src\Jolt\Geometry\Ellipse.h" + $File "src\Jolt\Geometry\EPAConvexHullBuilder.h" + $File "src\Jolt\Geometry\EPAPenetrationDepth.h" + $File "src\Jolt\Geometry\GJKClosestPoint.h" + $File "src\Jolt\Geometry\IndexedTriangle.h" + $File "src\Jolt\Geometry\Indexify.cpp" + $File "src\Jolt\Geometry\Indexify.h" + $File "src\Jolt\Geometry\MortonCode.h" + $File "src\Jolt\Geometry\OrientedBox.cpp" + $File "src\Jolt\Geometry\OrientedBox.h" + $File "src\Jolt\Geometry\Plane.h" + $File "src\Jolt\Geometry\RayAABox.h" + $File "src\Jolt\Geometry\RayAABox8.h" + $File "src\Jolt\Geometry\RayCapsule.h" + $File "src\Jolt\Geometry\RayCylinder.h" + $File "src\Jolt\Geometry\RaySphere.h" + $File "src\Jolt\Geometry\RayTriangle.h" + $File "src\Jolt\Geometry\RayTriangle8.h" + $File "src\Jolt\Geometry\Sphere.h" + $File "src\Jolt\Geometry\Triangle.h" + $File "src\Jolt\Jolt.h" + $File "src\Jolt\Math\DVec3.h" + $File "src\Jolt\Math\DVec3.inl" + $File "src\Jolt\Math\EigenValueSymmetric.h" + $File "src\Jolt\Math\FindRoot.h" + $File "src\Jolt\Math\Float2.h" + $File "src\Jolt\Math\Float3.h" + $File "src\Jolt\Math\Float4.h" + $File "src\Jolt\Math\GaussianElimination.h" + $File "src\Jolt\Math\HalfFloat.h" + $File "src\Jolt\Math\Mat44.h" + $File "src\Jolt\Math\Mat44.inl" + $File "src\Jolt\Math\Math.h" + $File "src\Jolt\Math\MathTypes.h" + $File "src\Jolt\Math\Matrix.h" + $File "src\Jolt\Math\Quat.h" + $File "src\Jolt\Math\Quat.inl" + $File "src\Jolt\Math\Swizzle.h" + $File "src\Jolt\Math\UVec4.cpp" + $File "src\Jolt\Math\UVec4.h" + $File "src\Jolt\Math\UVec4.inl" + $File "src\Jolt\Math\UVec8.h" + $File "src\Jolt\Math\UVec8.inl" + $File "src\Jolt\Math\Vec3.cpp" + $File "src\Jolt\Math\Vec3.h" + $File "src\Jolt\Math\Vec3.inl" + $File "src\Jolt\Math\Vec4.h" + $File "src\Jolt\Math\Vec4.inl" + $File "src\Jolt\Math\Vec8.h" + $File "src\Jolt\Math\Vec8.inl" + $File "src\Jolt\Math\Vector.h" + $File "src\Jolt\ObjectStream\GetPrimitiveTypeOfType.h" + $File "src\Jolt\ObjectStream\ObjectStream.cpp" + $File "src\Jolt\ObjectStream\ObjectStream.h" + $File "src\Jolt\ObjectStream\ObjectStreamBinaryIn.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamBinaryIn.h" + $File "src\Jolt\ObjectStream\ObjectStreamBinaryOut.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamBinaryOut.h" + $File "src\Jolt\ObjectStream\ObjectStreamIn.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamIn.h" + $File "src\Jolt\ObjectStream\ObjectStreamOut.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamOut.h" + $File "src\Jolt\ObjectStream\ObjectStreamTextIn.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamTextIn.h" + $File "src\Jolt\ObjectStream\ObjectStreamTextOut.cpp" + $File "src\Jolt\ObjectStream\ObjectStreamTextOut.h" + $File "src\Jolt\ObjectStream\ObjectStreamTypes.h" + $File "src\Jolt\ObjectStream\SerializableAttribute.h" + $File "src\Jolt\ObjectStream\SerializableAttributeEnum.h" + $File "src\Jolt\ObjectStream\SerializableAttributeTyped.h" + $File "src\Jolt\ObjectStream\SerializableObject.cpp" + $File "src\Jolt\ObjectStream\SerializableObject.h" + $File "src\Jolt\ObjectStream\TypeDeclarations.cpp" + $File "src\Jolt\ObjectStream\TypeDeclarations.h" + $File "src\Jolt\Physics\Body\Body.cpp" + $File "src\Jolt\Physics\Body\Body.h" + $File "src\Jolt\Physics\Body\Body.inl" + $File "src\Jolt\Physics\Body\BodyAccess.cpp" + $File "src\Jolt\Physics\Body\BodyAccess.h" + $File "src\Jolt\Physics\Body\BodyActivationListener.h" + $File "src\Jolt\Physics\Body\BodyCreationSettings.cpp" + $File "src\Jolt\Physics\Body\BodyCreationSettings.h" + $File "src\Jolt\Physics\Body\BodyFilter.h" + $File "src\Jolt\Physics\Body\BodyID.h" + $File "src\Jolt\Physics\Body\BodyInterface.cpp" + $File "src\Jolt\Physics\Body\BodyInterface.h" + $File "src\Jolt\Physics\Body\BodyLock.h" + $File "src\Jolt\Physics\Body\BodyLockInterface.h" + $File "src\Jolt\Physics\Body\BodyLockMulti.h" + $File "src\Jolt\Physics\Body\BodyManager.cpp" + $File "src\Jolt\Physics\Body\BodyManager.h" + $File "src\Jolt\Physics\Body\BodyPair.h" + $File "src\Jolt\Physics\Body\MassProperties.cpp" + $File "src\Jolt\Physics\Body\MassProperties.h" + $File "src\Jolt\Physics\Body\MotionProperties.cpp" + $File "src\Jolt\Physics\Body\MotionProperties.h" + $File "src\Jolt\Physics\Body\MotionProperties.inl" + $File "src\Jolt\Physics\Body\MotionQuality.h" + $File "src\Jolt\Physics\Body\MotionType.h" + $File "src\Jolt\Physics\Character\Character.cpp" + $File "src\Jolt\Physics\Character\Character.h" + $File "src\Jolt\Physics\Collision\AABoxCast.h" + $File "src\Jolt\Physics\Collision\ActiveEdgeMode.h" + $File "src\Jolt\Physics\Collision\ActiveEdges.h" + $File "src\Jolt\Physics\Collision\BackFaceMode.h" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhase.cpp" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhase.h" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseBruteForce.cpp" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseBruteForce.h" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseLayer.h" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseQuadTree.cpp" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseQuadTree.h" + $File "src\Jolt\Physics\Collision\BroadPhase\BroadPhaseQuery.h" + $File "src\Jolt\Physics\Collision\BroadPhase\QuadTree.cpp" + $File "src\Jolt\Physics\Collision\BroadPhase\QuadTree.h" + $File "src\Jolt\Physics\Collision\CastConvexVsTriangles.cpp" + $File "src\Jolt\Physics\Collision\CastConvexVsTriangles.h" + $File "src\Jolt\Physics\Collision\CastSphereVsTriangles.cpp" + $File "src\Jolt\Physics\Collision\CastSphereVsTriangles.h" + $File "src\Jolt\Physics\Collision\CastResult.h" + $File "src\Jolt\Physics\Collision\CollectFacesMode.h" + $File "src\Jolt\Physics\Collision\CollideConvexVsTriangles.cpp" + $File "src\Jolt\Physics\Collision\CollideConvexVsTriangles.h" + $File "src\Jolt\Physics\Collision\CollidePointResult.h" + $File "src\Jolt\Physics\Collision\CollideShape.h" + $File "src\Jolt\Physics\Collision\CollideSphereVsTriangles.cpp" + $File "src\Jolt\Physics\Collision\CollideSphereVsTriangles.h" + $File "src\Jolt\Physics\Collision\CollisionCollector.h" + $File "src\Jolt\Physics\Collision\CollisionCollectorImpl.h" + $File "src\Jolt\Physics\Collision\CollisionDispatch.cpp" + $File "src\Jolt\Physics\Collision\CollisionDispatch.h" + $File "src\Jolt\Physics\Collision\CollisionGroup.cpp" + $File "src\Jolt\Physics\Collision\CollisionGroup.h" + $File "src\Jolt\Physics\Collision\ContactListener.h" + $File "src\Jolt\Physics\Collision\GroupFilter.cpp" + $File "src\Jolt\Physics\Collision\GroupFilter.h" + $File "src\Jolt\Physics\Collision\GroupFilterTable.cpp" + $File "src\Jolt\Physics\Collision\GroupFilterTable.h" + $File "src\Jolt\Physics\Collision\ManifoldBetweenTwoFaces.cpp" + $File "src\Jolt\Physics\Collision\ManifoldBetweenTwoFaces.h" + $File "src\Jolt\Physics\Collision\NarrowPhaseQuery.cpp" + $File "src\Jolt\Physics\Collision\NarrowPhaseQuery.h" + $File "src\Jolt\Physics\Collision\NarrowPhaseStats.cpp" + $File "src\Jolt\Physics\Collision\NarrowPhaseStats.h" + $File "src\Jolt\Physics\Collision\ObjectLayer.h" + $File "src\Jolt\Physics\Collision\PhysicsMaterial.cpp" + $File "src\Jolt\Physics\Collision\PhysicsMaterial.h" + $File "src\Jolt\Physics\Collision\PhysicsMaterialSimple.cpp" + $File "src\Jolt\Physics\Collision\PhysicsMaterialSimple.h" + $File "src\Jolt\Physics\Collision\RayCast.h" + $File "src\Jolt\Physics\Collision\Shape\BoxShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\BoxShape.h" + $File "src\Jolt\Physics\Collision\Shape\CapsuleShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\CapsuleShape.h" + $File "src\Jolt\Physics\Collision\Shape\CompoundShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\CompoundShape.h" + $File "src\Jolt\Physics\Collision\Shape\CompoundShapeVisitors.h" + $File "src\Jolt\Physics\Collision\Shape\ConvexHullShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\ConvexHullShape.h" + $File "src\Jolt\Physics\Collision\Shape\ConvexShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\ConvexShape.h" + $File "src\Jolt\Physics\Collision\Shape\CylinderShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\CylinderShape.h" + $File "src\Jolt\Physics\Collision\Shape\DecoratedShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\DecoratedShape.h" + $File "src\Jolt\Physics\Collision\Shape\GetTrianglesContext.h" + $File "src\Jolt\Physics\Collision\Shape\HeightFieldShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\HeightFieldShape.h" + $File "src\Jolt\Physics\Collision\Shape\MeshShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\MeshShape.h" + $File "src\Jolt\Physics\Collision\Shape\MutableCompoundShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\MutableCompoundShape.h" + $File "src\Jolt\Physics\Collision\Shape\OffsetCenterOfMassShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\OffsetCenterOfMassShape.h" + $File "src\Jolt\Physics\Collision\Shape\PolyhedronSubmergedVolumeCalculator.h" + $File "src\Jolt\Physics\Collision\Shape\RotatedTranslatedShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\RotatedTranslatedShape.h" + $File "src\Jolt\Physics\Collision\Shape\ScaledShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\ScaledShape.h" + $File "src\Jolt\Physics\Collision\Shape\ScaleHelpers.h" + $File "src\Jolt\Physics\Collision\Shape\Shape.cpp" + $File "src\Jolt\Physics\Collision\Shape\Shape.h" + $File "src\Jolt\Physics\Collision\Shape\SphereShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\SphereShape.h" + $File "src\Jolt\Physics\Collision\Shape\StaticCompoundShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\StaticCompoundShape.h" + $File "src\Jolt\Physics\Collision\Shape\SubShapeID.h" + $File "src\Jolt\Physics\Collision\Shape\SubShapeIDPair.h" + $File "src\Jolt\Physics\Collision\Shape\TaperedCapsuleShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\TaperedCapsuleShape.gliffy" + $File "src\Jolt\Physics\Collision\Shape\TaperedCapsuleShape.h" + $File "src\Jolt\Physics\Collision\Shape\TriangleShape.cpp" + $File "src\Jolt\Physics\Collision\Shape\TriangleShape.h" + $File "src\Jolt\Physics\Collision\ShapeCast.h" + $File "src\Jolt\Physics\Collision\ShapeFilter.h" + $File "src\Jolt\Physics\Collision\SortReverseAndStore.h" + $File "src\Jolt\Physics\Collision\TransformedShape.cpp" + $File "src\Jolt\Physics\Collision\TransformedShape.h" + $File "src\Jolt\Physics\Constraints\ConeConstraint.cpp" + $File "src\Jolt\Physics\Constraints\ConeConstraint.h" + $File "src\Jolt\Physics\Constraints\Constraint.cpp" + $File "src\Jolt\Physics\Constraints\Constraint.h" + $File "src\Jolt\Physics\Constraints\ConstraintManager.cpp" + $File "src\Jolt\Physics\Constraints\ConstraintManager.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\AngleConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\AxisConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\DualAxisConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\GearConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\HingeRotationConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\PointConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\RackAndPinionConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\RotationEulerConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\RotationQuatConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\SpringPart.h" + $File "src\Jolt\Physics\Constraints\ConstraintPart\SwingTwistConstraintPart.h" + $File "src\Jolt\Physics\Constraints\ContactConstraintManager.cpp" + $File "src\Jolt\Physics\Constraints\ContactConstraintManager.h" + $File "src\Jolt\Physics\Constraints\DistanceConstraint.cpp" + $File "src\Jolt\Physics\Constraints\DistanceConstraint.h" + $File "src\Jolt\Physics\Constraints\FixedConstraint.cpp" + $File "src\Jolt\Physics\Constraints\FixedConstraint.h" + $File "src\Jolt\Physics\Constraints\GearConstraint.cpp" + $File "src\Jolt\Physics\Constraints\GearConstraint.h" + $File "src\Jolt\Physics\Constraints\HingeConstraint.cpp" + $File "src\Jolt\Physics\Constraints\HingeConstraint.h" + $File "src\Jolt\Physics\Constraints\MotorSettings.cpp" + $File "src\Jolt\Physics\Constraints\MotorSettings.h" + $File "src\Jolt\Physics\Constraints\PathConstraint.cpp" + $File "src\Jolt\Physics\Constraints\PathConstraint.h" + $File "src\Jolt\Physics\Constraints\PathConstraintPath.cpp" + $File "src\Jolt\Physics\Constraints\PathConstraintPath.h" + $File "src\Jolt\Physics\Constraints\PathConstraintPathHermite.cpp" + $File "src\Jolt\Physics\Constraints\PathConstraintPathHermite.h" + $File "src\Jolt\Physics\Constraints\PointConstraint.cpp" + $File "src\Jolt\Physics\Constraints\PointConstraint.h" + $File "src\Jolt\Physics\Constraints\RackAndPinionConstraint.cpp" + $File "src\Jolt\Physics\Constraints\RackAndPinionConstraint.h" + $File "src\Jolt\Physics\Constraints\SixDOFConstraint.cpp" + $File "src\Jolt\Physics\Constraints\SixDOFConstraint.h" + $File "src\Jolt\Physics\Constraints\SliderConstraint.cpp" + $File "src\Jolt\Physics\Constraints\SliderConstraint.h" + $File "src\Jolt\Physics\Constraints\SwingTwistConstraint.cpp" + $File "src\Jolt\Physics\Constraints\SwingTwistConstraint.h" + $File "src\Jolt\Physics\Constraints\TwoBodyConstraint.cpp" + $File "src\Jolt\Physics\Constraints\TwoBodyConstraint.h" + $File "src\Jolt\Physics\EActivation.h" + $File "src\Jolt\Physics\IslandBuilder.cpp" + $File "src\Jolt\Physics\IslandBuilder.h" + $File "src\Jolt\Physics\PhysicsLock.cpp" + $File "src\Jolt\Physics\PhysicsLock.h" + $File "src\Jolt\Physics\PhysicsScene.cpp" + $File "src\Jolt\Physics\PhysicsScene.h" + $File "src\Jolt\Physics\PhysicsSettings.h" + $File "src\Jolt\Physics\PhysicsStepListener.h" + $File "src\Jolt\Physics\PhysicsSystem.cpp" + $File "src\Jolt\Physics\PhysicsSystem.h" + $File "src\Jolt\Physics\PhysicsUpdateContext.cpp" + $File "src\Jolt\Physics\PhysicsUpdateContext.h" + $File "src\Jolt\Physics\Ragdoll\Ragdoll.cpp" + $File "src\Jolt\Physics\Ragdoll\Ragdoll.h" + $File "src\Jolt\Physics\StateRecorder.h" + $File "src\Jolt\Physics\StateRecorderImpl.cpp" + $File "src\Jolt\Physics\StateRecorderImpl.h" + $File "src\Jolt\Physics\Vehicle\TrackedVehicleController.cpp" + $File "src\Jolt\Physics\Vehicle\TrackedVehicleController.h" + $File "src\Jolt\Physics\Vehicle\VehicleAntiRollBar.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleAntiRollBar.h" + $File "src\Jolt\Physics\Vehicle\VehicleCollisionTester.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleCollisionTester.h" + $File "src\Jolt\Physics\Vehicle\VehicleConstraint.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleConstraint.h" + $File "src\Jolt\Physics\Vehicle\VehicleController.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleController.h" + $File "src\Jolt\Physics\Vehicle\VehicleDifferential.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleDifferential.h" + $File "src\Jolt\Physics\Vehicle\VehicleEngine.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleEngine.h" + $File "src\Jolt\Physics\Vehicle\VehicleTrack.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleTrack.h" + $File "src\Jolt\Physics\Vehicle\VehicleTransmission.cpp" + $File "src\Jolt\Physics\Vehicle\VehicleTransmission.h" + $File "src\Jolt\Physics\Vehicle\Wheel.cpp" + $File "src\Jolt\Physics\Vehicle\Wheel.h" + $File "src\Jolt\Physics\Vehicle\WheeledVehicleController.cpp" + $File "src\Jolt\Physics\Vehicle\WheeledVehicleController.h" + $File "src\Jolt\RegisterTypes.cpp" + $File "src\Jolt\RegisterTypes.h" + $File "src\Jolt\Renderer\DebugRenderer.cpp" + $File "src\Jolt\Renderer\DebugRenderer.h" + $File "src\Jolt\Renderer\DebugRendererPlayback.cpp" + $File "src\Jolt\Renderer\DebugRendererPlayback.h" + $File "src\Jolt\Renderer\DebugRendererRecorder.cpp" + $File "src\Jolt\Renderer\DebugRendererRecorder.h" + $File "src\Jolt\Skeleton\SkeletalAnimation.cpp" + $File "src\Jolt\Skeleton\SkeletalAnimation.h" + $File "src\Jolt\Skeleton\Skeleton.cpp" + $File "src\Jolt\Skeleton\Skeleton.h" + $File "src\Jolt\Skeleton\SkeletonPose.cpp" + $File "src\Jolt\Skeleton\SkeletonPose.h" + $File "src\Jolt\TriangleGrouper\TriangleGrouper.h" + $File "src\Jolt\TriangleGrouper\TriangleGrouperClosestCentroid.cpp" + $File "src\Jolt\TriangleGrouper\TriangleGrouperClosestCentroid.h" + $File "src\Jolt\TriangleGrouper\TriangleGrouperMorton.cpp" + $File "src\Jolt\TriangleGrouper\TriangleGrouperMorton.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitter.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitter.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitterBinning.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitterBinning.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitterFixedLeafSize.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitterFixedLeafSize.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitterLongestAxis.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitterLongestAxis.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitterMean.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitterMean.h" + $File "src\Jolt\TriangleSplitter\TriangleSplitterMorton.cpp" + $File "src\Jolt\TriangleSplitter\TriangleSplitterMorton.h" + } +} diff --git a/joltphysics/joltphysics_settings.vpc b/joltphysics/joltphysics_settings.vpc new file mode 100644 index 0000000..65af408 --- /dev/null +++ b/joltphysics/joltphysics_settings.vpc @@ -0,0 +1,22 @@ +// +// Configuration settings for Jolt Physics +// + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\vphysics_jolt\joltphysics\src" + + $PreprocessorDefinitions "$BASE;JPH_DISABLE_CUSTOM_ALLOCATOR;JPH_DEBUG_RENDERER" + $PreprocessorDefinitions "$BASE;JPH_ENABLE_ASSERTS" [$DEVELOPMENT_ONLY] + //$PreprocessorDefinitions "$BASE;JPH_PROFILE_ENABLED" [$DEVELOPMENT_ONLY] + + // CPU feature macros, we may want to tune these before release. + $PreprocessorDefinitions "$BASE;JPH_USE_SSE4_1;JPH_USE_SSE4_2;JPH_USE_LZCNT;JPH_USE_TZCNT;JPH_USE_F16C;JPH_USE_FMADD" + + // We DO want to comment this line out for release, AVX 1 & 2 adoption isn't reliable enough yet + // $EnableEnhancedInstructionSet "Advanced Vector Extensions 2 (/arch:AVX2)" [$DEVELOPMENT_ONLY] + } +} + diff --git a/joltphysics/pch.cpp b/joltphysics/pch.cpp new file mode 100644 index 0000000..faec159 --- /dev/null +++ b/joltphysics/pch.cpp @@ -0,0 +1 @@ +#include "Jolt/Jolt.h" diff --git a/joltphysics/src b/joltphysics/src new file mode 160000 index 0000000..22d6754 --- /dev/null +++ b/joltphysics/src @@ -0,0 +1 @@ +Subproject commit 22d675437edc441329ed244f632807a8789fa5a6 diff --git a/vphysics_jolt/cbase.cpp b/vphysics_jolt/cbase.cpp new file mode 100644 index 0000000..10f5a8d --- /dev/null +++ b/vphysics_jolt/cbase.cpp @@ -0,0 +1,2 @@ + +#include "cbase.h" diff --git a/vphysics_jolt/cbase.h b/vphysics_jolt/cbase.h new file mode 100644 index 0000000..cb8266b --- /dev/null +++ b/vphysics_jolt/cbase.h @@ -0,0 +1,114 @@ +//================================================================================================= +// +// This is the precompiled header +// +//================================================================================================= + +#pragma once + +// Tier0 +#include "tier0/basetypes.h" +#include "tier0/dbg.h" +#ifndef GAME_SDK2013 +#include "tier0/logging.h" +#endif + +#ifdef GAME_SDK2013 +#include "compat/compat_sdk2013.h" +#endif + +#include "compat/branch_overrides.h" + +// STD +// Ensure cmath is included everywhere +// so we get those sweet overloaded maths functions +#include +#include + +// STL +#include +#include +#include +#include +#include +#include + +// Mathlib +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" + +// Tier1 +#include "tier1/tier1.h" +#include "tier1/strtools.h" +#include "tier1/interface.h" +#include "tier1/keyvalues.h" +#include "tier1/UtlStringMap.h" +#include "tier1/utlbuffer.h" + +// Misc +#ifdef JPH_DEBUG_RENDERER +#include "engine/ivdebugoverlay.h" +#endif +#include "bspfile.h" +#include "cmodel.h" +#include "const.h" +#include "isaverestore.h" +#include "vcollide_parse.h" + +// VPhysics Interface +#include "vphysics_interface.h" +#include "vphysics/collision_set.h" +#include "vphysics/constraints.h" +#include "vphysics/friction.h" +#include "vphysics/object_hash.h" +#include "vphysics/performance.h" +#include "vphysics/player_controller.h" +#include "vphysics/stats.h" +#include "vphysics/vehicles.h" +#include "vphysics/virtualmesh.h" + +// Jolt +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Ourselves +#include "vjolt_interface.h" +#include "vjolt_util.h" diff --git a/vphysics_jolt/compat/better_winlite.h b/vphysics_jolt/compat/better_winlite.h new file mode 100644 index 0000000..86fa8d3 --- /dev/null +++ b/vphysics_jolt/compat/better_winlite.h @@ -0,0 +1,39 @@ + +#pragma once + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN + +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#define NOCRYPT +#define NOMETAFILE +#define NOMINMAX +#define MMNOSOUND + +#include + +#undef PostMessage +#undef CreateEvent +#undef PlaySound +#undef SetCursor +#undef ProgressBox +#undef RegisterClass +#undef AddJob +#undef GetJob +#undef Yield +#undef SetPort +#undef CreateFont +#undef ShellExecute +#undef ShellExecuteEx +#undef GetCurrentTime +#undef GetTickCount +#undef GetCurrentDirectory +#undef GetObject +#undef LoadCursorFromFile +#undef GetCharABCWidths + +#endif diff --git a/vphysics_jolt/compat/branch_overrides.h b/vphysics_jolt/compat/branch_overrides.h new file mode 100644 index 0000000..ea4a62b --- /dev/null +++ b/vphysics_jolt/compat/branch_overrides.h @@ -0,0 +1,38 @@ + +#pragma once + +#if defined( GAME_CSGO ) || defined( GAME_DESOLATION ) +#define GAME_CSGO_OR_NEWER +#define override_csgo override +#define override_not_csgo +#else +#define override_csgo +#define override_not_csgo override +#endif + +#if defined( GAME_CSGO ) || defined( GAME_DESOLATION ) || defined( GAME_PORTAL2 ) +#define GAME_PORTAL2_OR_NEWER +#define override_portal2 override +#define override_not_portal2 +#else +#define override_portal2 +#define override_not_portal2 override +#endif + +#if defined( GAME_CSGO ) || defined( GAME_DESOLATION ) || defined( GAME_PORTAL2 ) || defined( GAME_L4D2 ) || defined( GAME_ASW ) +#define GAME_ASW_OR_NEWER +#define override_asw override +#define override_not_asw +#else +#define override_asw +#define override_not_asw override +#endif + +#ifndef GAME_DESOLATION +using strlen_t = int; +#endif + +#ifndef GAME_CSGO_OR_NEWER +#define FastASCIIToUpper( c ) ( ( ( (c) >= 'a' ) && ( (c) <= 'z' ) ) ? ( (c) - 32 ) : (c) ) +#define FastASCIIToLower( c ) ( ( ( (c) >= 'A' ) && ( (c) <= 'Z' ) ) ? ( (c) + 32 ) : (c) ) +#endif diff --git a/vphysics_jolt/compat/compat_sdk2013.h b/vphysics_jolt/compat/compat_sdk2013.h new file mode 100644 index 0000000..3d1fb15 --- /dev/null +++ b/vphysics_jolt/compat/compat_sdk2013.h @@ -0,0 +1,75 @@ + +#pragma once + +// This file provides compatibility with stuff used from the Alien Swarm SDK and above +// Source Engine branches for SDK 2013. + +#include "Color.h" + +enum LoggingSeverity_t +{ + LS_MESSAGE = 0, + LS_WARNING = 1, + LS_ASSERT = 2, + LS_ERROR = 3, + LS_HIGHEST_SEVERITY = 4, +}; + +struct LoggingChannelInfo_t +{ + const char *pszName; + int nFlags; + LoggingSeverity_t nSeverity; + Color color = Color(255, 255, 255); +}; + +#define DECLARE_LOGGING_CHANNEL( Channel ) extern LoggingChannelInfo_t g_LoggingInfo##Channel; + +#define DEFINE_LOGGING_CHANNEL_NO_TAGS( Channel, ChannelName, ... ) LoggingChannelInfo_t g_LoggingInfo##Channel = { ChannelName, __VA_ARGS__ }; + +static const int MAX_LOGGING_MESSAGE_LENGTH = 2048; + +// TODO +#define DevAssert( ... ) +#define DevAssertMsg( ... ) +#define AssertMsg_Internal( ... ) +#define InternalMsg( Channel, Fmt, ... ) ConColorMsg( g_LoggingInfo##Channel.color, "[%s]" Fmt, g_LoggingInfo##Channel.pszName, ##__VA_ARGS__ ) +#define Log_Msg( Channel, Fmt, ... ) InternalMsg( Channel, Fmt, ##__VA_ARGS__ ) +#define Log_Warning( Channel, Fmt, ... ) InternalMsg( Channel, Fmt, ##__VA_ARGS__ ) +#define Log_Error( Channel, Fmt, ... ) InternalMsg( Channel, Fmt, ##__VA_ARGS__ ) + +// Unused, just makes stuff cleaner to not have ifdef spam. +enum collisionhints +{ + COLLISION_HINT_DEBRIS = 0x0001, + COLLISION_HINT_STATICSOLID = 0x0002, +}; + +class IPhysicsCollisionSet; +class IPhysics; + +struct ragdollcollisionrules_t +{ + void Defaults( IPhysics *pPhysics, IPhysicsCollisionSet *pSetIn ) + { + pCollisionSet = pSetIn; + bSelfCollisions = true; + } + int bSelfCollisions; + IPhysicsCollisionSet *pCollisionSet; +}; + +struct ragdollanimatedfriction_t +{ + float minFriction; + float maxFriction; + float timeIn; + float timeOut; + float timeHold; +}; + +enum PlayerContactState_t +{ + PLAYER_CONTACT_PHYSICS = 1, + PLAYER_CONTACT_GAMEOBJECT = 2, +}; diff --git a/vphysics_jolt/vjolt_callstack.h b/vphysics_jolt/vjolt_callstack.h new file mode 100644 index 0000000..3f5ec03 --- /dev/null +++ b/vphysics_jolt/vjolt_callstack.h @@ -0,0 +1,25 @@ + +#pragma once + +#include "compat/better_winlite.h" + +#ifdef _MSC_VER +#define VJOLT_RETURN_ADDRESS() _ReturnAddress() +#else +#define VJOLT_RETURN_ADDRESS() __builtin_return_address(0) +#endif + +FORCEINLINE void GetCallingFunctionModulePath( void *pReturnAddress, char *pszModulePath, size_t len ) +{ +#ifdef _WIN32 + MEMORY_BASIC_INFORMATION mbi; + if ( ::VirtualQuery( pReturnAddress, &mbi, sizeof(mbi)) ) { + HMODULE module = reinterpret_cast< HMODULE >( mbi.AllocationBase ); + + ::GetModuleFileNameA( module, pszModulePath, DWORD( len ) ); + return; + } +#else + V_strncpy( pszModulePath, "Unknown", len); +#endif +} diff --git a/vphysics_jolt/vjolt_collide.cpp b/vphysics_jolt/vjolt_collide.cpp new file mode 100644 index 0000000..37078c1 --- /dev/null +++ b/vphysics_jolt/vjolt_collide.cpp @@ -0,0 +1,927 @@ + +#include "cbase.h" + +#include "coordsize.h" +#include "mathlib/polyhedron.h" + +#include "vjolt_parse.h" +#include "vjolt_querymodel.h" + +#include "vjolt_collide.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +// Also in vjolt_collide_trace.cpp, should unify or just remove entirely +static constexpr float kMaxConvexRadius = JPH::cDefaultConvexRadius; + +JoltPhysicsCollision JoltPhysicsCollision::s_PhysicsCollision; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( JoltPhysicsCollision, IPhysicsCollision, VPHYSICS_COLLISION_INTERFACE_VERSION, JoltPhysicsCollision::GetInstance() ); + +//------------------------------------------------------------------------------------------------- + +// Creates a shape from shape settings, resolving the reference +template < typename ShapeType, typename T > +ShapeType *ShapeSettingsToShape( const T& settings ) +{ + auto result = settings.Create(); + if ( !result.IsValid() ) + { + // Josh: + // Need to handle degenerate convexes and stuff. + const char *error = result.HasError() + ? result.GetError().c_str() + : "Unknown"; + + Log_Warning( LOG_VJolt, "Failed to create shape: %s.\n", error ); + return nullptr; + } + + return static_cast< ShapeType * >( ToDanglingRef( result.Get() ) ); +} + +// Creates a JPH::ConvexShape from shape settings, and casts to a CPhysConvex +template < typename T > +CPhysConvex *ShapeSettingsToPhysConvex( const T& settings ) +{ + return CPhysConvex::FromConvexShape( ShapeSettingsToShape< JPH::ConvexShape >( settings ) ); +} + +// Creates a JPH::Shape from shape settings, and casts to a CPhysCollide +template < typename T > +CPhysCollide *ShapeSettingsToPhysCollide( const T& settings ) +{ + return CPhysCollide::FromShape( ShapeSettingsToShape< JPH::Shape >( settings ) ); +} + +//------------------------------------------------------------------------------------------------- + +CPhysConvex *JoltPhysicsCollision::ConvexFromVerts( Vector **pVerts, int vertCount ) +{ + // This uses this and not a CUtlVector or std::vector or whatever for two reasons: + // 1: This is a single allocation we can then toss away instead of growing + // 2: We do not want to initialize these vectors on allocation + // 3: Automatic deletion when out of scope + // Please do not change me to either. + std::unique_ptr< JPH::Vec3[] > verts = std::make_unique< JPH::Vec3[] >( vertCount ); + for ( int i = 0; i < vertCount; i++ ) + verts[ i ] = SourceToJolt::Distance( *pVerts[ i ] ); + + JPH::ConvexHullShapeSettings settings( verts.get(), vertCount, kMaxConvexRadius, nullptr /* material */ ); + settings.mHullTolerance = 0.0f; + return ShapeSettingsToPhysConvex( settings ); +} + +CPhysConvex *JoltPhysicsCollision::ConvexFromPlanes( float *pPlanes, int planeCount, float mergeDistance ) +{ + Log_Stub( LOG_VJolt ); + return nullptr; +} + +float JoltPhysicsCollision::ConvexVolume( CPhysConvex *pConvex ) +{ + return JoltToSource::Volume( pConvex->ToConvexShape()->GetVolume() ); +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsCollision::ConvexSurfaceArea( CPhysConvex *pConvex ) +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +void JoltPhysicsCollision::SetConvexGameData( CPhysConvex *pConvex, unsigned int gameData ) +{ + pConvex->ToConvexShape()->SetUserData( gameData ); +} + +void JoltPhysicsCollision::ConvexFree( CPhysConvex *pConvex ) +{ + pConvex->ToConvexShape()->Release(); +} + +CPhysConvex *JoltPhysicsCollision::BBoxToConvex( const Vector &mins, const Vector &maxs ) +{ + JPH::AABox aabox = SourceToJolt::AABBBounds( mins, maxs ); + + JPH::BoxShape *pBoxShape = new JPH::BoxShape( aabox.GetExtent(), kMaxConvexRadius, nullptr /* material */ ); + + JPH::RotatedTranslatedShapeSettings rotatedSettings( aabox.mMin + aabox.GetExtent(), JPH::Quat::sIdentity(), pBoxShape ); + return ShapeSettingsToPhysConvex( rotatedSettings ); +} + +CPhysConvex *JoltPhysicsCollision::ConvexFromConvexPolyhedron( const CPolyhedron &ConvexPolyhedron ) +{ + std::unique_ptr< JPH::Vec3[] > pPoints = std::make_unique< JPH::Vec3[] >( ConvexPolyhedron.iVertexCount ); + + // This loop fills me with rage + for ( unsigned short i = 0; i < ConvexPolyhedron.iVertexCount; ++i ) + pPoints[i] = SourceToJolt::Distance( ConvexPolyhedron.pVertices[i] ); + + JPH::ConvexHullShapeSettings settings( pPoints.get(), ConvexPolyhedron.iVertexCount, kMaxConvexRadius, nullptr /* material */); + settings.mHullTolerance = 0.0f; // Slart: Otherwise some polyhedrons crash :( + CPhysConvex *pPhysConvex = ShapeSettingsToPhysConvex( settings ); + + return pPhysConvex; +} + +void JoltPhysicsCollision::ConvexesFromConvexPolygon( const Vector &vPolyNormal, const Vector *pPoints, int iPointCount, CPhysConvex **pOutput ) +{ + // Slart: Unused + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +CPhysPolysoup *JoltPhysicsCollision::PolysoupCreate() +{ + return new CPhysPolysoup; +} + +void JoltPhysicsCollision::PolysoupDestroy( CPhysPolysoup *pSoup ) +{ + delete pSoup; +} + +void JoltPhysicsCollision::PolysoupAddTriangle( CPhysPolysoup *pSoup, const Vector &a, const Vector &b, const Vector &c, int materialIndex7bits ) +{ + // Add both windings to make this two-faced. + pSoup->Triangles.push_back( JPH::Triangle( SourceToJolt::DistanceFloat3( c ), SourceToJolt::DistanceFloat3( b ), SourceToJolt::DistanceFloat3( a ) ) ); + pSoup->Triangles.push_back( JPH::Triangle( SourceToJolt::DistanceFloat3( a ), SourceToJolt::DistanceFloat3( b ), SourceToJolt::DistanceFloat3( c ) ) ); +} + +CPhysCollide *JoltPhysicsCollision::ConvertPolysoupToCollide( CPhysPolysoup *pSoup, bool useMOPP ) +{ + VJoltAssertMsg( !useMOPP, "MOPPs not supported\n" ); + + if ( useMOPP ) + return nullptr; + + // ConvertPolysoupToCollide does NOT free the Polysoup. + JPH::MeshShapeSettings settings( pSoup->Triangles ); + return ShapeSettingsToPhysCollide( settings ); +} + +//------------------------------------------------------------------------------------------------- + +CPhysCollide *JoltPhysicsCollision::ConvertConvexToCollide( CPhysConvex **pConvex, int convexCount ) +{ + // If we only have one convex shape, we can just use that directly, + // without making a compound shape. + if ( convexCount == 1 ) + return pConvex[0]->ToPhysCollide(); + + JPH::StaticCompoundShapeSettings settings; + for ( int i = 0; i < convexCount; i++ ) + settings.AddShape( JPH::Vec3::sZero(), JPH::Quat::sIdentity(), pConvex[i]->ToConvexShape() ); + + CPhysCollide *pCollide = ShapeSettingsToPhysCollide( settings ); + + // This function also 'frees' the convexes. + for ( int i = 0; i < convexCount; i++ ) + pConvex[i]->ToConvexShape()->Release(); + + return pCollide; +} + +CPhysCollide *JoltPhysicsCollision::ConvertConvexToCollideParams( CPhysConvex **pConvex, int convexCount, const convertconvexparams_t &convertParams ) +{ + // Slart: The parameters are just IVP crap and the only one that isn't is never ever used. HA! + return ConvertConvexToCollide( pConvex, convexCount ); +} + +void JoltPhysicsCollision::DestroyCollide( CPhysCollide *pCollide ) +{ + if ( !pCollide ) + return; + + pCollide->ToShape()->Release(); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsCollision::CollideSize( CPhysCollide *pCollide ) +{ + Log_Stub( LOG_VJolt ); + return 0; +} + +int JoltPhysicsCollision::CollideWrite( char *pDest, CPhysCollide *pCollide, bool bSwap /*= false*/ ) +{ + Log_Stub( LOG_VJolt ); + return 0; +} + +CPhysCollide *JoltPhysicsCollision::UnserializeCollide( char *pBuffer, int size, int index ) +{ + Log_Stub( LOG_VJolt ); + return nullptr; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsCollision::CollideVolume( CPhysCollide *pCollide ) +{ + return JoltToSource::Volume( pCollide->ToShape()->GetVolume() ); +} + +float JoltPhysicsCollision::CollideSurfaceArea( CPhysCollide *pCollide ) +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- + +Vector JoltPhysicsCollision::CollideGetExtent( const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, const Vector &direction ) +{ + if ( !pCollide ) + return collideOrigin; + + const JPH::Shape *pShape = pCollide->ToShape(); + + JPH::Vec3 vecDirection = JPH::Vec3( direction.x, direction.y, direction.z ); + + JPH::Mat44 matCollideTransform = JPH::Mat44::sRotationTranslation( + SourceToJolt::Angle( collideAngles ), SourceToJolt::Distance( collideOrigin ) - pShape->GetCenterOfMass() ); + + float flMaxDot = -FLT_MAX; + JPH::Vec3 vecMaxExtent = JPH::Vec3::sZero(); + ActOnSubShapes< JPH::ConvexShape >( pShape, [&]( const JPH::ConvexShape* pConvexShape, JPH::Mat44Arg matSubShapeTransform ) + { + JPH::ConvexShape::SupportingFace supportingFace; + pConvexShape->GetSupportingFace( vecDirection, JPH::Vec3::sReplicate( 1.0f ), supportingFace ); + + for ( const JPH::Vec3 &vecVertex : supportingFace ) + { + JPH::Vec3 vecTransformedVertex = matCollideTransform * matSubShapeTransform * vecVertex; + + const float flDot = vecTransformedVertex.Dot( vecDirection ); + if ( flDot > flMaxDot ) + { + vecMaxExtent = vecTransformedVertex; + flMaxDot = flDot; + } + } + }); + + return JoltToSource::Distance( vecMaxExtent ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsCollision::CollideGetAABB( Vector *pMins, Vector *pMaxs, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles ) +{ + JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); + JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); + + const JPH::Shape *pShape = pCollide->ToShape(); + + const JPH::Mat44 translation = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() ); + const JPH::AABox worldBounds = pShape->GetWorldSpaceBounds( translation, JPH::Vec3::sReplicate( 1.0f ) ); + + JoltToSource::AABBBounds( worldBounds, *pMins, *pMaxs ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsCollision::CollideGetMassCenter( CPhysCollide *pCollide, Vector *pOutMassCenter ) +{ + *pOutMassCenter = JoltToSource::Distance( pCollide->ToShape()->GetCenterOfMass() ); +} + +void JoltPhysicsCollision::CollideSetMassCenter( CPhysCollide *pCollide, const Vector &massCenter ) +{ + // Slart: Only used in studiomdl + Log_Stub( LOG_VJolt ); +} + +Vector JoltPhysicsCollision::CollideGetOrthographicAreas( const CPhysCollide *pCollide ) +{ + // Slart: Only used in studiomdl... In a part that is #if 0'd out... + Log_Stub( LOG_VJolt ); + return vec3_origin; +} + +void JoltPhysicsCollision::CollideSetOrthographicAreas( CPhysCollide *pCollide, const Vector &areas ) +{ + // Slart: Never used + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsCollision::CollideIndex( const CPhysCollide *pCollide ) +{ + // Slart: Only used by code behind #ifdef _DEBUG + Log_Stub( LOG_VJolt ); + return 0; +} + +//------------------------------------------------------------------------------------------------- + +CPhysCollide *JoltPhysicsCollision::BBoxToCollide( const Vector &mins, const Vector &maxs ) +{ + return BBoxToConvex( mins, maxs )->ToPhysCollide(); +} + +int JoltPhysicsCollision::GetConvexesUsedInCollideable( const CPhysCollide *pCollideable, CPhysConvex **pOutputArray, int iOutputArrayLimit ) +{ + const JPH::Shape *pShape = pCollideable->ToShape(); + JPH::EShapeType shapeType = pShape->GetType(); + if ( shapeType != JPH::EShapeType::Compound ) + { + pOutputArray[0] = const_cast( CPhysConvex::FromConvexShape( static_cast( pShape ) ) ); + return 1; + } + + const JPH::StaticCompoundShape *pCompoundShape = static_cast( pShape ); + const JPH::StaticCompoundShape::SubShapes &subShapes = pCompoundShape->GetSubShapes(); + + const uint maxNumShapes = Min( pCompoundShape->GetNumSubShapes(), iOutputArrayLimit ); + for ( uint i = 0; i < maxNumShapes; ++i ) + pOutputArray[i] = const_cast( CPhysConvex::FromConvexShape( static_cast( subShapes[i].mShape.GetPtr() ) ) ); + + return maxNumShapes; +} + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsCollision::IsBoxIntersectingCone( const Vector &boxAbsMins, const Vector &boxAbsMaxs, const truncatedcone_t &cone ) +{ + // Slart: Never used + Log_Stub( LOG_VJolt ); + return false; +} + +//------------------------------------------------------------------------------------------------- + +// +// See studiobyteswap from the public 2013 SDK for more info about these defines. +// https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/common/studiobyteswap.cpp +// + +namespace ivp_compat +{ + struct collideheader_t + { + int size; + int vphysicsID; + short version; + short modelType; + }; + + struct compactsurfaceheader_t + { + int surfaceSize; + Vector dragAxisAreas; + int axisMapSize; + }; + + struct moppsurfaceheader_t + { + int moppSize; + }; + + struct compactsurface_t + { + float mass_center[3]; + float rotation_inertia[3]; + float upper_limit_radius; + + unsigned int max_factor_surface_deviation : 8; + int byte_size : 24; + int offset_ledgetree_root; + int dummy[3]; + }; + + struct compactmopp_t + { + float mass_center[3]; + float rotation_inertia[3]; + float upper_limit_radius; + + unsigned int max_factor_surface_deviation : 8; + int byte_size : 24; + int offset_ledgetree_root; + int offset_ledges; + int size_convex_hull; + int dummy; + }; + + struct compactledge_t + { + int c_point_offset; + + union + { + int ledgetree_node_offset; + int client_data; + }; + + struct + { + uint has_children_flag : 2; + int is_compact_flag : 2; + uint dummy : 4; + uint size_div_16 : 24; + }; + + short n_triangles; + short for_future_use; + }; + + struct compactedge_t + { + uint start_point_index : 16; + int opposite_index : 15; + uint is_virtual : 1; + }; + + struct compacttriangle_t + { + uint tri_index : 12; + uint pierce_index : 12; + uint material_index : 7; + uint is_virtual : 1; + compactedge_t c_three_edges[3]; + }; + + struct compactledgenode_t + { + int offset_right_node; + int offset_compact_ledge; + float center[3]; + float radius; + unsigned char box_sizes[3]; + unsigned char free_0; + + const compactledge_t *GetCompactLedge() const + { + VJoltAssert( this->offset_right_node == 0 ); + return ( compactledge_t * )( ( char * )this + this->offset_compact_ledge ); + } + + const compactledgenode_t *GetLeftChild() const + { + VJoltAssert( this->offset_right_node ); + return this + 1; + } + + const compactledgenode_t *GetRightChild() const + { + VJoltAssert( this->offset_right_node ); + return ( compactledgenode_t * )( ( char * )this + this->offset_right_node ); + } + + bool IsTerminal() const + { + return this->offset_right_node == 0; + } + + const compactledge_t *GetCompactHull() const + { + if ( this->offset_compact_ledge ) + return ( compactledge_t * )( ( char * )this + this->offset_compact_ledge ); + else + return nullptr; + } + }; + + static constexpr int IVP_COMPACT_SURFACE_ID = MAKEID('I','V','P','S'); + static constexpr int IVP_COMPACT_SURFACE_ID_SWAPPED = MAKEID('S','P','V','I'); + static constexpr int IVP_COMPACT_MOPP_ID = MAKEID('M','O','P','P'); + static constexpr int VPHYSICS_COLLISION_ID = MAKEID('V','P','H','Y'); + static constexpr short VPHYSICS_COLLISION_VERSION = 0x0100; + + enum + { + COLLIDE_POLY = 0, + COLLIDE_MOPP = 1, + COLLIDE_BALL = 2, + COLLIDE_VIRTUAL = 3, + }; + + JPH::ConvexShape *IVPLedgeToConvexShape( const compactledge_t *pLedge ) + { + if ( !pLedge->n_triangles ) + return nullptr; + + const char *pVertices = reinterpret_cast< const char * >( pLedge ) + pLedge->c_point_offset; + const compacttriangle_t *pTriangles = reinterpret_cast< const compacttriangle_t * >( pLedge + 1 ); + + const int nVertCount = pLedge->n_triangles * 3; + + std::unique_ptr< JPH::Vec3[] > verts = std::make_unique< JPH::Vec3[] >( nVertCount ); + + // Each triangle + for ( int i = 0; i < pLedge->n_triangles; i++ ) + { + // For each point of the current triangle + for ( int j = 0; j < 3; j++ ) + { + static constexpr size_t IVPAlignedVectorSize = 16; + + const int nIndex = pTriangles[ i ].c_three_edges[ j ].start_point_index; + const float *pVertex = reinterpret_cast< const float * >( pVertices + ( nIndex * IVPAlignedVectorSize ) ); + + verts[ ( i * 3 ) + j ] = JPH::Vec3( pVertex[ 0 ], pVertex[ 2 ], -pVertex[ 1 ] ); + } + } + + JPH::ConvexHullShapeSettings settings{ verts.get(), nVertCount, kMaxConvexRadius, nullptr /* material */ }; + settings.mHullTolerance = 0.0f; + JPH::ConvexShape *pConvexShape = ShapeSettingsToShape< JPH::ConvexShape >( settings ); + if ( !pConvexShape ) + return nullptr; + + pConvexShape->SetUserData( pLedge->client_data ); + return pConvexShape; + } + + void GetAllIVPEdges( const compactledgenode_t *pNode, CUtlVector< const compactledge_t * >& vecOut ) + { + if ( !pNode ) + return; + + if ( !pNode->IsTerminal() ) + { + GetAllIVPEdges( pNode->GetRightChild(), vecOut ); + GetAllIVPEdges( pNode->GetLeftChild(), vecOut ); + } + else + vecOut.AddToTail( pNode->GetCompactLedge() ); + } + + CPhysCollide *DeserializeIVP_Poly( const collideheader_t *pCollideHeader ) + { + const compactsurfaceheader_t *pSurfaceHeader = reinterpret_cast< const compactsurfaceheader_t* >( pCollideHeader + 1 ); + const compactsurface_t *pSurface = reinterpret_cast< const compactsurface_t* >( pSurfaceHeader + 1 ); + + if ( pSurface->dummy[2] != IVP_COMPACT_SURFACE_ID ) + return nullptr; + + const compactledgenode_t *pFirstLedgeNode = reinterpret_cast< const compactledgenode_t * >( + reinterpret_cast< const char * >( pSurface ) + pSurface->offset_ledgetree_root ); + + CUtlVector< const compactledge_t * > ledges; + GetAllIVPEdges( pFirstLedgeNode, ledges ); + + VJoltAssert( !ledges.IsEmpty() ); + + if ( ledges.Count() != 1 ) + { + JPH::StaticCompoundShapeSettings settings{}; + // One compound convex per ledge. + for ( int i = 0; i < ledges.Count(); i++ ) + { + const JPH::Shape* pShape = IVPLedgeToConvexShape( ledges[i] ); + // Josh: + // Some models have degenerate convexes which fails to make + // a subshape in Jolt, so we need to ignore those ledges. + if ( pShape ) + settings.AddShape( JPH::Vec3::sZero(), JPH::Quat::sIdentity(), pShape ); + } + CPhysCollide* pCollide = ShapeSettingsToPhysCollide( settings ); + return pCollide; + } + else + { + JPH::ConvexShape *pShape = IVPLedgeToConvexShape( ledges[ 0 ] ); + return CPhysConvex::FromConvexShape( pShape )->ToPhysCollide(); + } + } +} + +void JoltPhysicsCollision::VCollideLoad( vcollide_t *pOutput, int solidCount, const char *pBuffer, int size, bool swap /*= false*/ ) +{ + if ( swap ) + { + Log_Error( LOG_VJolt, "If you got here. Tell me what you did!\n" ); + return; + } + + pOutput->solidCount = solidCount; + pOutput->solids = new CPhysCollide*[ solidCount ]; + + const char *pCursor = pBuffer; + for ( int i = 0; i < solidCount; i++ ) + { + // Be safe ahead of time as so much can go wrong with + // this mess! :p + pOutput->solids[ i ] = nullptr; + + const ivp_compat::collideheader_t *pCollideHeader = reinterpret_cast( pCursor ); + + if ( pCollideHeader->vphysicsID != ivp_compat::VPHYSICS_COLLISION_ID || + pCollideHeader->version != ivp_compat::VPHYSICS_COLLISION_VERSION ) + { + Log_Warning( LOG_VJolt, "Skipped solid %d due to invalid header (id: %.4s, version: 0x%x)\n", i, &pCollideHeader->vphysicsID, pCollideHeader->version ); + continue; + } + + switch ( pCollideHeader->modelType ) + { + case ivp_compat::COLLIDE_POLY: + pOutput->solids[ i ] = DeserializeIVP_Poly( pCollideHeader ); + break; + + case ivp_compat::COLLIDE_MOPP: + Log_Warning( LOG_VJolt, "Unsupported solid type COLLIDE_MOPP on solid %d. Skipping...\n", i ); + break; + + case ivp_compat::COLLIDE_BALL: + Log_Warning( LOG_VJolt, "Unsupported solid type COLLIDE_BALL on solid %d. Skipping...\n", i ); + break; + + case ivp_compat::COLLIDE_VIRTUAL: + Log_Warning( LOG_VJolt, "Unsupported solid type COLLIDE_VIRTUAL on solid %d. Skipping...\n", i ); + break; + + default: + Log_Warning( LOG_VJolt, "Unsupported solid type 0x%x on solid %d. Skipping...\n", (int)pCollideHeader->modelType, i ); + break; + } + + // Size does not include the size of "size", ironically! + // add sizeof( int ) for that. + pCursor += pCollideHeader->size + sizeof( int ); + } + + // The rest of the buffer is KV. + const int keyValuesSize = size - ( uintp( pCursor ) - uintp( pBuffer ) ); + + pOutput->pKeyValues = new char[ keyValuesSize + 1 ]; + V_memcpy( pOutput->pKeyValues, pCursor, keyValuesSize ); + pOutput->pKeyValues[ keyValuesSize ] = '\0'; + + pOutput->descSize = keyValuesSize; + pOutput->isPacked = false; +#ifdef GAME_ASW_OR_NEWER + pOutput->pUserData = nullptr; +#endif + +} + +void JoltPhysicsCollision::VCollideUnload( vcollide_t *pVCollide ) +{ + VCollideFreeUserData( pVCollide ); + for ( int i = 0; i < pVCollide->solidCount; i++ ) + delete pVCollide->solids[ i ]; + + delete[] pVCollide->solids; + delete[] pVCollide->pKeyValues; + V_memset( pVCollide, 0, sizeof( *pVCollide ) ); +} + +//------------------------------------------------------------------------------------------------- + +IVPhysicsKeyParser *JoltPhysicsCollision::VPhysicsKeyParserCreate( const char *pKeyData ) +{ + return CreateVPhysicsKeyParser( pKeyData, false ); +} + +IVPhysicsKeyParser *JoltPhysicsCollision::VPhysicsKeyParserCreate( vcollide_t *pVCollide ) +{ + return CreateVPhysicsKeyParser( pVCollide->pKeyValues, pVCollide->isPacked ); +} + +void JoltPhysicsCollision::VPhysicsKeyParserDestroy( IVPhysicsKeyParser *pParser ) +{ + DestroyVPhysicsKeyParser( pParser ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsCollision::CreateDebugMesh( CPhysCollide const *pCollisionModel, Vector **outVerts ) +{ + const JPH::Shape *pShape = pCollisionModel->ToShape(); + JPH::Shape::VisitedShapes visitedShapes; + JPH::Shape::Stats stats = pShape->GetStatsRecursive( visitedShapes ); + + const int nMaxTriCount = int( stats.mNumTriangles ); + const int nRequestCount = Max( nMaxTriCount, JPH::Shape::cGetTrianglesMinTrianglesRequested ); + + const int nRequestVertCount = nRequestCount * 3; + + Vector *pVerts = new Vector[ nRequestVertCount ]; + + JPH::AllHitCollisionCollector collector; + JPH::ShapeFilter filter; + pShape->CollectTransformedShapes( JPH::AABox::sBiggest(), pShape->GetCenterOfMass() * JoltToSource::Factor, JPH::Quat::sIdentity(), JPH::Vec3::sReplicate( JoltToSource::Factor ), JPH::SubShapeIDCreator(), collector, filter ); + + int nAccumTris = 0; + for ( auto &shape : collector.mHits ) + { + JPH::Shape::GetTrianglesContext ctx; + shape.GetTrianglesStart( ctx, JPH::AABox::sBiggest() ); + for ( ;; ) + { + int nSubShapeTriCount = shape.GetTrianglesNext( ctx, nRequestCount, reinterpret_cast( &pVerts[ nAccumTris * 3 ] ), nullptr /* materials */); + if ( nSubShapeTriCount == 0 || nAccumTris + nSubShapeTriCount > nMaxTriCount ) + break; + + nAccumTris += nSubShapeTriCount; + } + } + + *outVerts = pVerts; + return nAccumTris * 3; +} + +void JoltPhysicsCollision::DestroyDebugMesh( int vertCount, Vector *outVerts ) +{ + delete[] outVerts; +} + +//------------------------------------------------------------------------------------------------- + +ICollisionQuery *JoltPhysicsCollision::CreateQueryModel( CPhysCollide *pCollide ) +{ + return new JoltCollisionQuery( pCollide->ToShape() ); +} + +void JoltPhysicsCollision::DestroyQueryModel( ICollisionQuery *pQuery ) +{ + delete pQuery; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsCollision *JoltPhysicsCollision::ThreadContextCreate() +{ + return this; +} + +void JoltPhysicsCollision::ThreadContextDestroy( IPhysicsCollision *pThreadContex ) +{ + // Does nothing in VPhysics. +} + +//------------------------------------------------------------------------------------------------- + +CPhysCollide *JoltPhysicsCollision::CreateVirtualMesh( const virtualmeshparams_t ¶ms ) +{ + IVirtualMeshEvent *event = params.pMeshEventHandler; + + virtualmeshlist_t meshList; + + event->GetVirtualMesh( params.userData, &meshList ); + + JPH::VertexList vertexList; + vertexList.resize( meshList.vertexCount ); + for ( int i = 0; i < meshList.vertexCount; ++i ) + vertexList[i] = SourceToJolt::DistanceFloat3( meshList.pVerts[i] ); + + JPH::IndexedTriangleList indexedTriangleList; + indexedTriangleList.resize( meshList.indexCount * 2 ); + + for ( int i = 0; i < meshList.triangleCount; ++i ) + { + // Add both windings to make this two-faced. + // Probably doesn't matter too much but matches what used to happen. + indexedTriangleList[i*2+0].mIdx[0] = meshList.indices[i*3+0]; + indexedTriangleList[i*2+0].mIdx[1] = meshList.indices[i*3+1]; + indexedTriangleList[i*2+0].mIdx[2] = meshList.indices[i*3+2]; + + indexedTriangleList[i*2+1].mIdx[2] = meshList.indices[i*3+0]; + indexedTriangleList[i*2+1].mIdx[1] = meshList.indices[i*3+1]; + indexedTriangleList[i*2+1].mIdx[0] = meshList.indices[i*3+2]; + } + + JPH::MeshShapeSettings settings( vertexList, indexedTriangleList ); + + return ShapeSettingsToPhysCollide( settings ); +} + +bool JoltPhysicsCollision::SupportsVirtualMesh() +{ + return true; +} + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsCollision::GetBBoxCacheSize( int *pCachedSize, int *pCachedCount ) +{ + // Josh: We don't use a bbox cache as we have box shapes directly, + // and this is only used for debug stats. + *pCachedSize = 0; + *pCachedCount = 0; + return true; +} + +//------------------------------------------------------------------------------------------------- + +CPolyhedron *JoltPhysicsCollision::PolyhedronFromConvex( CPhysConvex * const pConvex, bool bUseTempPolyhedron ) +{ + // This is vile + Log_Stub( LOG_VJolt ); + return nullptr; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsCollision::OutputDebugInfo( const CPhysCollide *pCollide ) +{ + Log_Stub( LOG_VJolt ); +} + +unsigned int JoltPhysicsCollision::ReadStat( int statID ) +{ + // Josh: + // This always returns 0 in VPhysics. + // It was used by the HL2 Beta physgun at one point for... + // something... + return 0; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsCollision::CollideGetRadius( const CPhysCollide *pCollide ) +{ + return pCollide->ToShape()->GetInnerRadius(); +} + +//------------------------------------------------------------------------------------------------- + +void *JoltPhysicsCollision::VCollideAllocUserData( vcollide_t *pVCollide, size_t userDataSize ) +{ +#ifdef GAME_ASW_OR_NEWER + VCollideFreeUserData( pVCollide ); + + if ( userDataSize ) + pVCollide->pUserData = malloc( userDataSize ); + + return pVCollide->pUserData; +#else + return nullptr; +#endif +} + +void JoltPhysicsCollision::VCollideFreeUserData( vcollide_t *pVCollide ) +{ +#ifdef GAME_ASW_OR_NEWER + if ( pVCollide->pUserData ) + { + free( pVCollide->pUserData ); + pVCollide->pUserData = nullptr; + } +#endif +} + +void JoltPhysicsCollision::VCollideCheck( vcollide_t *pVCollide, const char *pName ) +{ + // Josh: + // A thing to spew warnings about non-optimal solids in IVP. + // Entirely useless for us. +} + +bool JoltPhysicsCollision::TraceBoxAA( const Ray_t &ray, const CPhysCollide *pCollide, trace_t *ptr ) +{ + TraceBox( ray, pCollide, vec3_origin, vec3_angle, ptr ); + return true; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsCollision::DuplicateAndScale( vcollide_t *pOut, const vcollide_t *pIn, float flScale ) +{ + CPhysCollide **pSolids = new CPhysCollide * [pIn->solidCount]; + for ( unsigned short i = 0; i < pIn->solidCount; i++ ) + { + const JPH::Shape* pShape = pIn->solids[i]->ToShape(); + + pSolids[i] = CPhysCollide::FromShape( ToDanglingRef( pShape->ScaleShape( JPH::Vec3::sReplicate( flScale ) ).Get() ) ); + } + + char *pKeyValues = new char[ pIn->descSize ]; + V_memcpy( pKeyValues, pIn->pKeyValues, pIn->descSize ); + + *pOut = vcollide_t + { + .solidCount = pIn->solidCount, + .isPacked = pIn->isPacked, + .descSize = pIn->descSize, + .solids = pSolids, + .pKeyValues = pKeyValues, +#ifdef GAME_ASW_OR_NEWER + .pUserData = nullptr, +#endif + }; +} + +//------------------------------------------------------------------------------------------------- + +const JPH::Shape *CreateCOMOverrideShape( const JPH::Shape* pShape, JPH::Vec3Arg comOverride ) +{ + JPH::Vec3 comOffset = comOverride - pShape->GetCenterOfMass(); + + if ( comOffset.IsNearZero() ) + return pShape; + + JPH::OffsetCenterOfMassShapeSettings settings( comOffset, pShape ); + return ShapeSettingsToShape< JPH::OffsetCenterOfMassShape >( settings ); +} diff --git a/vphysics_jolt/vjolt_collide.h b/vphysics_jolt/vjolt_collide.h new file mode 100644 index 0000000..ebdbec1 --- /dev/null +++ b/vphysics_jolt/vjolt_collide.h @@ -0,0 +1,185 @@ +//================================================================================================= +// +// CPhysCollide and friends! +// +//================================================================================================= + +#pragma once + +//------------------------------------------------------------------------------------------------- + +// Dummy helper class to go back and forth. +// Does not and will not contain *any* data. +class CPhysCollide +{ +public: + JPH::Shape* ToShape() + { + return reinterpret_cast(this); + } + + const JPH::Shape *ToShape() const + { + return reinterpret_cast< const JPH::Shape * >( this ); + } + + //------------------------------------------------------------------------------------------------- + + static CPhysCollide *FromShape( JPH::Shape *pCollide ) + { + return reinterpret_cast< CPhysCollide * >( pCollide ); + } + + static const CPhysCollide *FromShape( const JPH::Shape *pCollide ) + { + return reinterpret_cast< const CPhysCollide * >( pCollide ); + } +}; + +//------------------------------------------------------------------------------------------------- + +// Dummy helper class to go back and forth. +// Does not and will not contain *any* data. +class CPhysConvex +{ +public: + JPH::ConvexShape* ToConvexShape() + { + return reinterpret_cast< JPH::ConvexShape* >( this ); + } + + const JPH::ConvexShape *ToConvexShape() const + { + return reinterpret_cast< const JPH::ConvexShape * >( this ); + } + + //------------------------------------------------------------------------------------------------- + + static CPhysConvex *FromConvexShape( JPH::ConvexShape *pCollide ) + { + return reinterpret_cast< CPhysConvex * >( pCollide ); + } + + static const CPhysConvex *FromConvexShape( const JPH::ConvexShape *pCollide ) + { + return reinterpret_cast< const CPhysConvex * >( pCollide ); + } + + //------------------------------------------------------------------------------------------------- + + CPhysCollide *ToPhysCollide() + { + return reinterpret_cast< CPhysCollide * >( this ); + } +}; + +//------------------------------------------------------------------------------------------------- + +class CPhysPolysoup +{ +public: + JPH::TriangleList Triangles; +}; + +//------------------------------------------------------------------------------------------------- + +// Josh: Suprise! This is not an app system! Just an interface... +class JoltPhysicsCollision final : public IPhysicsCollision +{ +public: + CPhysConvex *ConvexFromVerts( Vector **pVerts, int vertCount ) override; + CPhysConvex *ConvexFromPlanes( float *pPlanes, int planeCount, float mergeDistance ) override; + float ConvexVolume( CPhysConvex *pConvex ) override; + + float ConvexSurfaceArea( CPhysConvex *pConvex ) override; + void SetConvexGameData( CPhysConvex *pConvex, unsigned int gameData ) override; + void ConvexFree( CPhysConvex *pConvex ) override; + CPhysConvex *BBoxToConvex( const Vector &mins, const Vector &maxs ) override; + CPhysConvex *ConvexFromConvexPolyhedron( const CPolyhedron &ConvexPolyhedron ) override; + void ConvexesFromConvexPolygon( const Vector &vPolyNormal, const Vector *pPoints, int iPointCount, CPhysConvex **pOutput ) override; + + CPhysPolysoup *PolysoupCreate() override; + void PolysoupDestroy( CPhysPolysoup *pSoup ) override; + void PolysoupAddTriangle( CPhysPolysoup *pSoup, const Vector &a, const Vector &b, const Vector &c, int materialIndex7bits ) override; + CPhysCollide *ConvertPolysoupToCollide( CPhysPolysoup *pSoup, bool useMOPP ) override; + + CPhysCollide *ConvertConvexToCollide( CPhysConvex **pConvex, int convexCount ) override; + CPhysCollide *ConvertConvexToCollideParams( CPhysConvex **pConvex, int convexCount, const convertconvexparams_t &convertParams ) override; + void DestroyCollide( CPhysCollide *pCollide ) override; + + int CollideSize( CPhysCollide *pCollide ) override; + int CollideWrite( char *pDest, CPhysCollide *pCollide, bool bSwap = false ) override; + CPhysCollide *UnserializeCollide( char *pBuffer, int size, int index ) override; + + float CollideVolume( CPhysCollide *pCollide ) override; + float CollideSurfaceArea( CPhysCollide *pCollide ) override; + + Vector CollideGetExtent( const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, const Vector &direction ) override; + + void CollideGetAABB( Vector *pMins, Vector *pMaxs, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles ) override; + + void CollideGetMassCenter( CPhysCollide *pCollide, Vector *pOutMassCenter ) override; + void CollideSetMassCenter( CPhysCollide *pCollide, const Vector &massCenter ) override; + Vector CollideGetOrthographicAreas( const CPhysCollide *pCollide ) override; + void CollideSetOrthographicAreas( CPhysCollide *pCollide, const Vector &areas ) override; + + int CollideIndex( const CPhysCollide *pCollide ) override; + + CPhysCollide *BBoxToCollide( const Vector &mins, const Vector &maxs ) override; + int GetConvexesUsedInCollideable( const CPhysCollide *pCollideable, CPhysConvex **pOutputArray, int iOutputArrayLimit ) override; + + + void TraceBox( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) override; + void TraceBox( const Ray_t &ray, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) override; + void TraceBox( const Ray_t &ray, unsigned int contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) override; + + void TraceCollide( const Vector &start, const Vector &end, const CPhysCollide *pSweepCollide, const QAngle &sweepAngles, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) override; + + bool IsBoxIntersectingCone( const Vector &boxAbsMins, const Vector &boxAbsMaxs, const truncatedcone_t &cone ) override; + + void VCollideLoad( vcollide_t *pOutput, int solidCount, const char *pBuffer, int size, bool swap = false ) override; + void VCollideUnload( vcollide_t *pVCollide ) override; + + IVPhysicsKeyParser *VPhysicsKeyParserCreate( const char *pKeyData ) override; + IVPhysicsKeyParser *VPhysicsKeyParserCreate( vcollide_t *pVCollide ) override_asw; + void VPhysicsKeyParserDestroy( IVPhysicsKeyParser *pParser ) override; + + int CreateDebugMesh( CPhysCollide const *pCollisionModel, Vector **outVerts ) override; + void DestroyDebugMesh( int vertCount, Vector *outVerts ) override; + + ICollisionQuery *CreateQueryModel( CPhysCollide *pCollide ) override; + void DestroyQueryModel( ICollisionQuery *pQuery ) override; + + IPhysicsCollision *ThreadContextCreate() override; + void ThreadContextDestroy( IPhysicsCollision *pThreadContex ) override; + + CPhysCollide *CreateVirtualMesh( const virtualmeshparams_t ¶ms ) override; + bool SupportsVirtualMesh() override; + + + bool GetBBoxCacheSize( int *pCachedSize, int *pCachedCount ) override; + + + CPolyhedron *PolyhedronFromConvex( CPhysConvex * const pConvex, bool bUseTempPolyhedron ) override; + + void OutputDebugInfo( const CPhysCollide *pCollide ) override; + unsigned int ReadStat( int statID ) override; + + float CollideGetRadius( const CPhysCollide *pCollide ) override_asw; + + void *VCollideAllocUserData( vcollide_t *pVCollide, size_t userDataSize ) override_asw; + void VCollideFreeUserData( vcollide_t *pVCollide ) override_asw; + void VCollideCheck( vcollide_t *pVCollide, const char *pName ) override_asw; + + bool TraceBoxAA( const Ray_t &ray, const CPhysCollide *pCollide, trace_t *ptr ) override_csgo; + + void DuplicateAndScale( vcollide_t *pOut, const vcollide_t *pIn, float flScale ) override_csgo; + +public: + static JoltPhysicsCollision& GetInstance() { return s_PhysicsCollision; } + +private: + static JoltPhysicsCollision s_PhysicsCollision; +}; + +const JPH::Shape *CreateCOMOverrideShape( const JPH::Shape* pShape, JPH::Vec3Arg comOverride ); diff --git a/vphysics_jolt/vjolt_collide_trace.cpp b/vphysics_jolt/vjolt_collide_trace.cpp new file mode 100644 index 0000000..0c72ef4 --- /dev/null +++ b/vphysics_jolt/vjolt_collide_trace.cpp @@ -0,0 +1,781 @@ +//================================================================================================= +// +// Trace Logic +// +// Split out from vjolt_collide.cpp due to being a can of worms +// +//================================================================================================= + +#include "cbase.h" + +#include "coordsize.h" // DIST_EPSILON + +#include "vjolt_debugrender.h" +#include "vjolt_util.h" + +#include "vjolt_collide.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +namespace VJoltTrace +{ + +static constexpr float kCollisionTolerance = 1.0e-3f; +static constexpr float kCharacterPadding = 0.02f; + +// Also in vjolt_collide.cpp, should unify or just remove entirely +static constexpr float kMaxConvexRadius = JPH::cDefaultConvexRadius; + +static ConVar vjolt_trace_debug( "vjolt_trace_debug", "0", FCVAR_CHEAT ); +static ConVar vjolt_trace_debug_castray( "vjolt_trace_debug_castray", "0", FCVAR_CHEAT ); +static ConVar vjolt_trace_debug_collidepoint( "vjolt_trace_debug_collidepoint", "0", FCVAR_CHEAT ); +static ConVar vjolt_trace_debug_castbox( "vjolt_trace_debug_castbox", "0", FCVAR_CHEAT ); +static ConVar vjolt_trace_debug_collidebox( "vjolt_trace_debug_collidebox", "0", FCVAR_CHEAT ); + +// Josh: Enables a hack to make portals work. For some reason when we enable colliding with +// backfaces, the player gets easily stuck in all sorts of things! +// Slart and I have not been able to determine the root cause of this problem and have tried for a long time... +// +// Slart: Portal 2 probably passes in a bad winding order in the polyhedron or something, dunno if it affects Portal 1 +static ConVar vjolt_trace_portal_hack( "vjolt_trace_portal_hack", "0", FCVAR_NONE ); + +//------------------------------------------------------------------------------------------------- +// +// Collectors and helpers +// +//------------------------------------------------------------------------------------------------- + +#ifdef JPH_DEBUG_RENDERER + +static void PrintTrace( const trace_t *tr, JoltPhysicsDebugRenderer &debugRenderer ) +{ + Vector textPos = tr->endpos; + textPos.z += 4.0f; + + constexpr float dur = 0.1f; + constexpr int r = 255; + constexpr int g = 255; + constexpr int b = 255; + constexpr int a = 255; + + IVJoltDebugOverlay *pDebugOverlay = debugRenderer.GetDebugOverlay(); + + pDebugOverlay->AddTextOverlayRGB( textPos, 0, dur, r,g,b,a, "startpos : [ %g %g %g ]", tr->startpos.x, tr->startpos.y, tr->startpos.z ); + pDebugOverlay->AddTextOverlayRGB( textPos, 1, dur, r,g,b,a, "endpos : [ %g %g %g ]", tr->endpos.x, tr->endpos.y, tr->endpos.z ); + pDebugOverlay->AddTextOverlayRGB( textPos, 2, dur, r,g,b,a, "fraction : %g", tr->fraction ); + pDebugOverlay->AddTextOverlayRGB( textPos, 3, dur, r,g,b,a, "contents : %d", tr->contents ); + pDebugOverlay->AddTextOverlayRGB( textPos, 4, dur, r,g,b,a, "allsolid : %d", (int)tr->allsolid ); + pDebugOverlay->AddTextOverlayRGB( textPos, 5, dur, r,g,b,a, "startsolid : %d", (int)tr->startsolid ); + pDebugOverlay->AddTextOverlayRGB( textPos, 6, dur, r,g,b,a, "normal : [ %g %g %g ]", tr->plane.normal.x, tr->plane.normal.y, tr->plane.normal.z ); + pDebugOverlay->AddTextOverlayRGB( textPos, 7, dur, r,g,b,a, "dist : %g", tr->plane.dist ); +} + +#endif + +// +// Collector for VJolt_CastRay +// Returns the closest hit within the contents mask +// +class ContentsCollector_CastRay final : public JPH::CastRayCollector +{ +public: + ContentsCollector_CastRay( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo ) + : m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {} + + void AddHit( const JPH::RayCastResult &inResult ) override + { + // Test if this collision is closer than the previous one + const float theirEarlyOut = inResult.GetEarlyOutFraction(); + const float ourEarlyOut = GetEarlyOutFraction(); + if ( !m_DidHit || theirEarlyOut < ourEarlyOut ) + { + const uint32 gameData = static_cast( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) ); + const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID; + + if ( contents & m_ContentsMask ) + { + // Update our early out fraction + UpdateEarlyOutFraction( theirEarlyOut ); + + // Store hit info locally + m_Fraction = inResult.mFraction; + m_SubShapeID = inResult.mSubShapeID2; + m_ResultContents = contents; + + m_DidHit = true; + } + } + } + +private: + // Inputs + const JPH::Shape * m_pShape = nullptr; + uint32 m_ContentsMask = 0; + IConvexInfo * m_pConvexInfo = nullptr; + +public: + // Outputs (only use if m_DidHit is true) + float m_Fraction = 1.0f; // Use this instead of GetEarlyOutFraction + JPH::SubShapeID m_SubShapeID; // Subshape hit + uint32 m_ResultContents = 0; // Contents of hit subshape + + bool m_DidHit = false; +}; + +// +// Collector for VJolt_CollidePoint +// Returns the first hit within the contents mask +// +class ContentsCollector_CollidePoint final : public JPH::CollidePointCollector +{ +public: + ContentsCollector_CollidePoint( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo ) + : m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {} + + void AddHit( const JPH::CollidePointResult &inResult ) override + { + // Return the first hit only + if ( m_DidHit ) + return; + + const uint32 gameData = static_cast( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) ); + const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID; + + if ( contents & m_ContentsMask ) + { + // Store hit info locally + m_SubShapeID = inResult.mSubShapeID2; + m_ResultContents = contents; + + m_DidHit = true; + } + } + +private: + // Inputs + const JPH::Shape * m_pShape = nullptr; + uint32 m_ContentsMask = 0; + IConvexInfo * m_pConvexInfo = nullptr; + +public: + // Outputs (only use if m_DidHit is true) + JPH::SubShapeID m_SubShapeID; // Subshape hit + uint32 m_ResultContents = 0; // Contents of hit subshape + + bool m_DidHit = false; +}; + +// +// Filter for generic shape casts or collides +// Ensures that objects which do not fit within the contents mask are excluded +// +class ContentsFilter_Shape final : public JPH::ShapeFilter +{ +public: + ContentsFilter_Shape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo ) + : m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {} + + bool ShouldCollide( const JPH::SubShapeID& inSubShapeID2 ) const override + { + const uint32 gameData = static_cast( m_pShape->GetSubShapeUserData( inSubShapeID2 ) ); + const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID; + + return !!( contents & m_ContentsMask ); + } + + bool ShouldCollide( const JPH::SubShapeID &inSubShapeID1, const JPH::SubShapeID &inSubShapeID2 ) const override + { + return ShouldCollide( inSubShapeID2 ); + } + +public: + // Input + const JPH::Shape * m_pShape = nullptr; + uint32 m_ContentsMask = 0; + IConvexInfo * m_pConvexInfo = nullptr; +}; + +// +// Collector for generic shape casts +// +class ContentsCollector_CastShape final : public JPH::CastShapeCollector +{ +public: + ContentsCollector_CastShape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo ) + : m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {} + + void AddHit( const JPH::ShapeCastResult &inResult ) override + { + const uint32 gameData = static_cast( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) ); + const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID; + + // Ensure that the contents filter was used + VJoltAssert( contents & m_ContentsMask ); + + // Test if this collision is closer than the previous one + const float theirEarlyOut = inResult.GetEarlyOutFraction(); + const float ourEarlyOut = GetEarlyOutFraction(); + if ( !m_DidHit || theirEarlyOut < ourEarlyOut ) + { + // Update our early out fraction + UpdateEarlyOutFraction( theirEarlyOut ); + + m_Fraction = inResult.mFraction; + m_ResultContents = contents; + m_SubShapeID = inResult.mSubShapeID2; + m_ContactPoint = inResult.mContactPointOn2; + m_PenetrationAxis = inResult.mPenetrationAxis; + m_PenetrationDepth = inResult.mPenetrationDepth; + + m_DidHit = true; + m_HitBackFace = inResult.mIsBackFaceHit; + } + } + +private: + // Input + const JPH::Shape * m_pShape = nullptr; + uint32 m_ContentsMask = 0; + IConvexInfo * m_pConvexInfo = nullptr; + +public: + // Outputs (only use if m_DidHit is true) + float m_Fraction = 1.0f; + uint32 m_ResultContents = 0; // Contents of the subshape that we hit + JPH::SubShapeID m_SubShapeID; // SubShapeID that we hit + JPH::Vec3 m_ContactPoint; // Point of impact relative to the COM of the object we hit + JPH::Vec3 m_PenetrationAxis; + float m_PenetrationDepth = 0.0f; + + bool m_DidHit = false; // Set to true if we hit anything + bool m_HitBackFace = false; // Set to true if the hit was against a backface +}; + +// +// Collector for generic shape collides +// +class ContentsCollector_CollideShape final : public JPH::CollideShapeCollector +{ +public: + ContentsCollector_CollideShape( const JPH::Shape *pShape, uint32 contentsMask, IConvexInfo *pConvexInfo ) + : m_pShape( pShape ), m_ContentsMask( contentsMask ), m_pConvexInfo( pConvexInfo ) {} + + // Called whenever a hit occurs, for compound objects this can be called multiple times + void AddHit( const JPH::CollideShapeResult &inResult ) override + { + // Get the contents of the subshape that we hit + const uint32 gameData = static_cast( m_pShape->GetSubShapeUserData( inResult.mSubShapeID2 ) ); + const uint32 contents = m_pConvexInfo ? m_pConvexInfo->GetContents( gameData ) : CONTENTS_SOLID; + + VJoltAssert( contents & m_ContentsMask ); + + if ( inResult.GetEarlyOutFraction() < GetEarlyOutFraction() ) + { + UpdateEarlyOutFraction( inResult.GetEarlyOutFraction() ); + + m_ResultContents = contents; + m_SubShapeID = inResult.mSubShapeID2; + m_ContactPoint = inResult.mContactPointOn2; + m_PenetrationAxis = inResult.mPenetrationAxis; + m_PenetrationDepth = inResult.mPenetrationDepth; + + m_DidHit = true; + } + } + +private: + // Input + const JPH::Shape * m_pShape = nullptr; + uint32 m_ContentsMask = 0; + IConvexInfo * m_pConvexInfo = nullptr; + +public: + // Output, only valid if m_didHit is true + uint32 m_ResultContents = 0; // Contents of the subshape that we hit + JPH::SubShapeID m_SubShapeID; // SubShapeID that we hit + JPH::Vec3 m_ContactPoint; // Point of impact relative to the COM of the object we hit + JPH::Vec3 m_PenetrationAxis; + float m_PenetrationDepth = 0.0f; + + bool m_DidHit = false; // Set to true if we hit anything +}; + +// +// Contents collector for simple collide operations (no contents) +// +class ContentsCollector_SimpleCollide final : public JPH::CollideShapeCollector +{ +public: + void AddHit( const ResultType &inResult ) override + { + intersection = true; + } + + bool intersection = false; +}; + +//------------------------------------------------------------------------------------------------- +// +// Tracing functions +// +//------------------------------------------------------------------------------------------------- + +// +// Casts a ray against a shape +// +static void CastRay( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + const JPH::Shape *pShape = pCollide->ToShape(); + + JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); + JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); + JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() ); + + // Create ray + JPH::RayCast joltRay = { SourceToJolt::Distance( ray.m_Start ), SourceToJolt::Distance( ray.m_Delta ) }; + // Transform the ray by the inverse of the position/rotation + joltRay = joltRay.Transformed( queryTransform.InversedRotationTranslation() ); + + // Set-up the settings + JPH::RayCastSettings settings; + settings.mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces; + settings.mTreatConvexAsSolid = true; + + // + // Hello! + // This code mimics IVP's behaviour where solids are solid, and the ray is totally clipped when inside. + // the BSP code (the gold standard) allows traces that start inside of solid objects to pass through backfaces on the convex. + // BSP sets traces that start inside of objects as startsolid 1, IVP doesn't do this :( + // So in the future, to mimic the BSP logic, we should make traces that begin inside of objects startsolid and only allsolid + // if they never leave the object + // + + // Create our collector and cast away! + ContentsCollector_CastRay collector( pShape, contentsMask, pConvexInfo ); + pShape->CastRay( joltRay, settings, JPH::SubShapeIDCreator(), collector ); + + if ( !collector.m_DidHit ) + { + VJoltAssert( collector.m_Fraction == 1.0f ); + } + + // Populate pTrace's members + pTrace->fraction = collector.m_Fraction; + pTrace->startpos = ray.m_Start + ray.m_StartOffset; + pTrace->endpos = pTrace->startpos + ( ray.m_Delta * pTrace->fraction ); + pTrace->allsolid = pTrace->fraction == 0.0f; + pTrace->startsolid = pTrace->fraction == 0.0f; + + // If we hit, we fill in the plane and contents too + if ( collector.m_DidHit ) + { + JPH::Vec3 normal = queryTransform.GetRotation() * pShape->GetSurfaceNormal( collector.m_SubShapeID, joltRay.GetPointOnRay( collector.m_Fraction ) ); + pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() ); + pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); + + pTrace->contents = collector.m_ResultContents; + } + +#if defined JPH_DEBUG_RENDERER + + // Debug trace visualizing + IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_castray.GetBool() && pOverlay ) + { + JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance(); + + JPH::Color rayColor( 255, 0, 0, 255 ); + + if ( collector.m_DidHit ) + { + rayColor.r = 0; + rayColor.g = 255; + + JPH::Vec3 normal = queryTransform.GetRotation() * pShape->GetSurfaceNormal( collector.m_SubShapeID, joltRay.GetPointOnRay( collector.m_Fraction ) ); + debugRenderer.DrawArrow( queryTransform * ( joltRay.mOrigin + ( joltRay.mDirection * collector.GetEarlyOutFraction() ) ), queryTransform * ( joltRay.mOrigin + joltRay.mDirection * collector.GetEarlyOutFraction() ) + normal, JPH::Color::sRed, 0.3f ); + } + + debugRenderer.DrawArrow( queryTransform * joltRay.mOrigin, queryTransform * ( joltRay.mOrigin + ( joltRay.mDirection * collector.GetEarlyOutFraction() ) ), rayColor, 0.3f ); + } + +#endif +} + +// +// Collides a point against a shape +// +static void CollidePoint( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + const JPH::Shape *pShape = pCollide->ToShape(); + + JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); + JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); + JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() ); + + JPH::Vec3 point = queryTransform.InversedRotationTranslation() * SourceToJolt::Distance(ray.m_Start); + + ContentsCollector_CollidePoint collector( pShape, contentsMask, pConvexInfo ); + pShape->CollidePoint( point, JPH::SubShapeIDCreator(), collector ); + + // Populate pTrace's members + pTrace->fraction = collector.m_DidHit ? 0.0f : 1.0f; + pTrace->startpos = ray.m_Start + ray.m_StartOffset; + pTrace->endpos = pTrace->startpos; + pTrace->allsolid = collector.m_DidHit; + pTrace->startsolid = collector.m_DidHit; + + // This will be zero if we didn't hit + pTrace->contents = collector.m_ResultContents; + +#if defined JPH_DEBUG_RENDERER + + // Debug trace visualizing + IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_collidepoint.GetBool() && pOverlay ) + { + JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance(); + + JPH::Color rayColor( 0, 255, 0, 0 ); + + if ( collector.m_DidHit ) + { + rayColor.a = 255; + } + + debugRenderer.DrawMarker( point, rayColor, 0.3f ); + } + +#endif +} + +static float CalculateSourceFraction( const Vector &rayDelta, float fraction, const Vector &normal ) +{ + const float rayLength = rayDelta.Length(); + float invBaseLength = 0.0f; + Vector rayDir = vec3_origin; + if ( rayLength ) + { + rayDir = rayDelta.Normalized(); + invBaseLength = 1.0f / rayLength; + } + + float hitLength = rayLength * fraction; + float dot = DotProduct( rayDir, normal ); + if ( dot < 0.0f ) + hitLength += DIST_EPSILON / dot; + + hitLength = Max( hitLength, 0.0f ); + + return hitLength * invBaseLength; +} + +// +// Casts a box against a shape +// +static void CastBoxVsShape( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + const JPH::Shape *pShape = pCollide->ToShape(); + + JPH::Vec3 origin = SourceToJolt::Distance( ray.m_Start ); // Origin of the box or ray we're casting + JPH::Vec3 direction = SourceToJolt::Distance( ray.m_Delta ); // Direction and distance of the cast + JPH::Vec3 halfExtent = SourceToJolt::Distance( ray.m_Extents ); + + JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); // Position of the object we're casting against + JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); // Rotation of the object we're casting against + JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() ); + + JPH::BoxShape boxShape( halfExtent, kMaxConvexRadius ); + JPH::ShapeCast shapeCast( &boxShape, JPH::Vec3::sReplicate( 1.0f ), JPH::Mat44::sTranslation( origin ), direction ); + + JPH::ShapeCastSettings settings; + //settings.mBackFaceModeTriangles = JPH::EBackFaceMode::CollideWithBackFaces; + // Josh: Had to re-enable CollideWithBackFaces to allow triggers for the Portal Environment to work. + // Come back here if we start getting stuck on things again... + if ( vjolt_trace_portal_hack.GetBool() ) + settings.mBackFaceModeConvex = JPH::EBackFaceMode::CollideWithBackFaces; + //settings.mCollisionTolerance = kCollisionTolerance; + settings.mUseShrunkenShapeAndConvexRadius = true; + settings.mReturnDeepestPoint = true; + + ContentsFilter_Shape filter( pShape, contentsMask, pConvexInfo ); + ContentsCollector_CastShape collector( pShape, contentsMask, pConvexInfo ); + JPH::CollisionDispatch::sCastShapeVsShapeWorldSpace( shapeCast, settings, pShape, JPH::Vec3::sReplicate( 1.0f ), filter, queryTransform, JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(), collector ); + + if ( collector.m_DidHit ) + { + JPH::Vec3 normal = -( collector.m_PenetrationAxis.Normalized() ); + pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() ); + + pTrace->fraction = CalculateSourceFraction( ray.m_Delta, collector.m_Fraction, pTrace->plane.normal ); + + //Log_Msg( LOG_VJolt, "Depth: %g, InitialFraction = %g, NewFraction = %g\n", collector.m_PenetrationDepth, flInitialFraction, pTrace->fraction ); + + pTrace->startpos = ray.m_Start + ray.m_StartOffset; + pTrace->endpos = pTrace->startpos + ( ray.m_Delta * pTrace->fraction ); + + pTrace->endpos -= pTrace->plane.normal * collector.m_PenetrationDepth; + + pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); + pTrace->contents = collector.m_ResultContents; + + // If penetrating more than DIST_EPSILON, consider it an intersection + //constexpr float PenetrationEpsilon = DIST_EPSILON; + static constexpr float kMinRequiredPenetration = 0.005f + kCharacterPadding; + + pTrace->allsolid = collector.m_PenetrationDepth > kMinRequiredPenetration && pTrace->fraction == 0.0f; + pTrace->startsolid = collector.m_PenetrationDepth > kMinRequiredPenetration && pTrace->fraction == 0.0f; + } + else + { + pTrace->fraction = 1.0f; + pTrace->startpos = ray.m_Start + ray.m_StartOffset; + pTrace->endpos = pTrace->startpos + ray.m_Delta; + + // We didn't hit anything, so we must be completely free right? + pTrace->allsolid = false; + pTrace->startsolid = false; + } + +#if defined JPH_DEBUG_RENDERER + + // Debug trace visualizing + IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_castbox.GetBool() && pOverlay ) + { + JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance(); + + JPH::Color color( 255, 64, 64, 255 ); + + if ( collector.m_DidHit ) + { + color.r = 64; + color.g = 255; + + JPH::Vec3 hitPos = origin + ( direction * collector.m_Fraction ); + debugRenderer.DrawArrow( hitPos, hitPos + collector.m_PenetrationAxis, JPH::Color::sRed, 0.3f ); + } + + boxShape.Draw( &debugRenderer, queryTransform, JPH::Vec3::sReplicate( 1.0f ), color, false, false ); + } + +#endif +} + +// +// Collides a box against a shape +// +static void CollideBoxVsShape( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + const JPH::Shape *pShape = pCollide->ToShape(); + + JPH::Vec3 origin = SourceToJolt::Distance( ray.m_Start ); // Origin of the box or ray we're casting + JPH::Vec3 direction = SourceToJolt::Distance( ray.m_Delta ); // Direction and distance of the cast + JPH::Vec3 halfExtent = SourceToJolt::Distance( ray.m_Extents ); + + JPH::Vec3 position = SourceToJolt::Distance( collideOrigin ); // Position of the object we're casting against + JPH::Quat rotation = SourceToJolt::Angle( collideAngles ); // Rotation of the object we're casting against + JPH::Mat44 queryTransform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pShape->GetCenterOfMass() ); + + JPH::BoxShape boxShape( halfExtent, kMaxConvexRadius ); + + JPH::CollideShapeSettings settings; + //settings.mMaxSeparationDistance = DIST_EPSILON; + //settings.mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces; + + ContentsCollector_CollideShape collector( pShape, contentsMask, pConvexInfo ); + JPH::CollisionDispatch::sCollideShapeVsShape( + &boxShape, pShape, + JPH::Vec3::sReplicate( 1.0f ), JPH::Vec3::sReplicate( 1.0f ), + JPH::Mat44::sIdentity(), queryTransform, + JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(), + settings, collector ); + + pTrace->fraction = collector.m_DidHit ? 0.0f : 1.0f; + pTrace->startpos = ray.m_Start + ray.m_StartOffset; + pTrace->endpos = pTrace->startpos; + pTrace->allsolid = collector.m_DidHit; + pTrace->startsolid = collector.m_DidHit; + + if ( collector.m_DidHit ) + { + JPH::Vec3 normal = -( ( queryTransform.GetRotation() * collector.m_PenetrationAxis ).Normalized() ); + pTrace->plane.normal = Vector( normal.GetX(), normal.GetY(), normal.GetZ() ); + pTrace->plane.dist = DotProduct( pTrace->endpos, pTrace->plane.normal ); + + pTrace->contents = collector.m_ResultContents; + } + +#if defined JPH_DEBUG_RENDERER + + // Debug trace visualizing + IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_trace_debug.GetBool() && vjolt_trace_debug_collidebox.GetBool() && pOverlay ) + { + JoltPhysicsDebugRenderer &debugRenderer = JoltPhysicsDebugRenderer::GetInstance(); + + JPH::Color color( 255, 64, 64, 255 ); + + if ( collector.m_DidHit ) + { + color.r = 64; + color.g = 255; + } + + boxShape.Draw( &debugRenderer, queryTransform, JPH::Vec3::sReplicate( 1.0f ), color, false, false ); + } + +#endif +} + +// +// Collides a shape against a shape +// +// Slart: This sucks, it could be implemented better in the environment interface using a bool and two physics objects instead... Basically all the code using this wants it to see +// if a shape is inside another shape. +// +static void CollideShapeVsShape( const Vector &start, const Vector &end, const CPhysCollide *pSweepCollide, const QAngle &sweepAngles, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + ClearTrace( pTrace ); + + // Are we sweeping, or just doing a point test? + bool isSweeping = false; + + if ( !VectorCompare( start, end ) ) + { + isSweeping = true; + + // Slart: Exactly one piece of code "may" want a sweeping trace, ffs + Log_Warning( LOG_VJolt, "Attemping an unsupported TraceCollide\n" ); + return; + } + + // Glue variable + const Vector &sweepOrigin = start; + + const JPH::Shape *pSweepShape = pSweepCollide->ToShape(); + JPH::Vec3 sweepJoltPosition = SourceToJolt::Distance( sweepOrigin ); + JPH::Quat sweepJoltRotation = SourceToJolt::Angle( sweepAngles ); + JPH::Mat44 sweepTransform = JPH::Mat44::sRotationTranslation( sweepJoltRotation, sweepJoltPosition + sweepJoltRotation * pSweepShape->GetCenterOfMass() ); + + const JPH::Shape *pCollideShape = pCollide->ToShape(); + JPH::Vec3 collideJoltPosition = SourceToJolt::Distance( collideOrigin ); + JPH::Quat collideJoltRotation = SourceToJolt::Angle( collideAngles ); + JPH::Mat44 collideTransform = JPH::Mat44::sRotationTranslation( collideJoltRotation, collideJoltPosition + collideJoltRotation * pCollideShape->GetCenterOfMass() ); + //JPH::Mat44 collideTransform = JPH::Mat44::sTranslation( collideJoltPosition ); + + if ( !isSweeping ) + { + JPH::AABox sweepAABB = pSweepShape->GetWorldSpaceBounds( sweepTransform, JPH::Vec3::sReplicate( 1.0f ) ); + JPH::AABox collideAABB = pCollideShape->GetWorldSpaceBounds( collideTransform, JPH::Vec3::sReplicate( 1.0f ) ); + + // Debug trace visualizing + IVJoltDebugOverlay *pOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_trace_debug.GetBool() && pOverlay && !pTrace->allsolid ) + { + Vector mins, maxs; + + mins = JoltToSource::Distance( sweepAABB.mMin ); + maxs = JoltToSource::Distance( sweepAABB.mMax ); + pOverlay->AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 64, 64, 255, -1.0f ); + + mins = JoltToSource::Distance( collideAABB.mMin ); + maxs = JoltToSource::Distance( collideAABB.mMax ); + pOverlay->AddBoxOverlay( vec3_origin, mins, maxs, vec3_angle, 255, 64, 64, 255, -1.0f ); + } + + // Don't bother doing any work if we don't contain the thing to be tested + if ( !collideAABB.Contains( sweepAABB ) ) + { + // TODO(Slart): Uncomment this + //return; + } + + ContentsCollector_SimpleCollide collector; + + JPH::CollisionDispatch::sCollideShapeVsShape( + pSweepShape, pCollideShape, + JPH::Vec3::sReplicate( 1.0f ), JPH::Vec3::sReplicate( 1.0f ), + sweepTransform, collideTransform, + JPH::SubShapeIDCreator(), JPH::SubShapeIDCreator(), + JPH::CollideShapeSettings(), collector ); + + if ( collector.intersection ) + { + pTrace->allsolid = true; + pTrace->startsolid = true; + } + } +} + +//------------------------------------------------------------------------------------------------- +// +// Where we figure out which type this trace actually is +// +//------------------------------------------------------------------------------------------------- + +static void TraceBase( const Ray_t &ray, uint32 contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + VJoltAssert( pCollide && pTrace ); + + // Default out our trace + ClearTrace( pTrace ); + + // We can't trust Ray_t's settings because after conversion from Source > Jolt the coordinates might become tiny + bool isPoint = SourceToJolt::Distance( ray.m_Extents ).ReduceMin() < kMaxConvexRadius; + bool isCollide = !ray.m_IsSwept; + + if ( isPoint ) + { + if ( isCollide ) + { + CollidePoint( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace ); + } + else + { + CastRay( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace ); + } + } + else + { + if ( isCollide ) + { + // TODO(Slart): This should be CollideBoxVsShape, but I can't remember why it wasn't good enough... + CastBoxVsShape( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace ); + } + else + { + CastBoxVsShape( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, pTrace ); + } + } +} + +} // namespace VJoltTrace + +//------------------------------------------------------------------------------------------------- +// +// IPhysicsCollision Interface +// +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsCollision::TraceBox( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) +{ + Ray_t ray; + ray.Init( start, end, mins, maxs ); + VJoltTrace::TraceBase( ray, MASK_ALL, nullptr, pCollide, collideOrigin, collideAngles, ptr ); +} + +void JoltPhysicsCollision::TraceBox( const Ray_t &ray, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) +{ + VJoltTrace::TraceBase( ray, MASK_ALL, nullptr, pCollide, collideOrigin, collideAngles, ptr ); +} + +void JoltPhysicsCollision::TraceBox( const Ray_t &ray, unsigned int contentsMask, IConvexInfo *pConvexInfo, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *ptr ) +{ + VJoltTrace::TraceBase( ray, contentsMask, pConvexInfo, pCollide, collideOrigin, collideAngles, ptr ); +} + +void JoltPhysicsCollision::TraceCollide( const Vector &start, const Vector &end, const CPhysCollide *pSweepCollide, const QAngle &sweepAngles, const CPhysCollide *pCollide, const Vector &collideOrigin, const QAngle &collideAngles, trace_t *pTrace ) +{ + VJoltTrace::CollideShapeVsShape( start, end, pSweepCollide, sweepAngles, pCollide, collideOrigin, collideAngles, pTrace ); +} diff --git a/vphysics_jolt/vjolt_constraints.cpp b/vphysics_jolt/vjolt_constraints.cpp new file mode 100644 index 0000000..047ba55 --- /dev/null +++ b/vphysics_jolt/vjolt_constraints.cpp @@ -0,0 +1,625 @@ +//================================================================================================= +// +// Constraints +// +//================================================================================================= + +#include "cbase.h" + +#include "vjolt_environment.h" +#include "vjolt_layers.h" +#include "vjolt_object.h" + +#include "vjolt_constraints.h" + +#include "vjolt_layers.h" + +//------------------------------------------------------------------------------------------------- + +static ConVar vjolt_ragdoll_hinge_optimization( "vjolt_ragdoll_hinge_optimization", "1", FCVAR_REPLICATED, + "Optimizes ragdolls to use hinge constraints for joints with 1 degree of freedom. Additionally fixes legs going back on themselves. Currently breaks ragdolls of NPCs killed in a pose (they inherit the pose)."); + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsConstraintGroup::JoltPhysicsConstraintGroup() +{ +} + +JoltPhysicsConstraintGroup::~JoltPhysicsConstraintGroup() +{ +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraintGroup::Activate() +{ + for ( JoltPhysicsConstraint *pConstraint : m_pConstraints ) + pConstraint->Activate(); +} + +bool JoltPhysicsConstraintGroup::IsInErrorState() +{ + return false; +} + +void JoltPhysicsConstraintGroup::ClearErrorState() +{ +} + +void JoltPhysicsConstraintGroup::GetErrorParams( constraint_groupparams_t *pParams ) +{ + if ( pParams ) + *pParams = m_ErrorParams; +} + +void JoltPhysicsConstraintGroup::SetErrorParams( const constraint_groupparams_t ¶ms ) +{ + m_ErrorParams = params; +} + +void JoltPhysicsConstraintGroup::SolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1 ) +{ +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraintGroup::AddConstraint( JoltPhysicsConstraint *pConstraint ) +{ + m_pConstraints.push_back( pConstraint ); +} + +void JoltPhysicsConstraintGroup::RemoveConstraint( JoltPhysicsConstraint *pConstraint ) +{ + m_pConstraints.erase( + std::remove_if( m_pConstraints.begin(), m_pConstraints.end(), [&]( JoltPhysicsConstraint *pOther ) { return pOther == pConstraint; } ) ); +} + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsConstraint::JoltPhysicsConstraint( JoltPhysicsEnvironment *pPhysicsEnvironment, IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, constraintType_t Type, JPH::Constraint* pConstraint, void *pGameData ) + : m_pPhysicsEnvironment( pPhysicsEnvironment ) + , m_pPhysicsSystem( pPhysicsEnvironment->GetPhysicsSystem() ) + , m_pObjReference( static_cast( pReferenceObject ) ) + , m_pObjAttached( static_cast( pAttachedObject ) ) + , m_ConstraintType( Type ) + , m_pConstraint( pConstraint ) + , m_pGameData( pGameData ) +{ + m_pObjReference->AddDestroyedListener( this ); + m_pObjAttached->AddDestroyedListener( this ); +} + +JoltPhysicsConstraint::~JoltPhysicsConstraint() +{ + if ( m_pGroup ) + { + m_pGroup->RemoveConstraint( this ); + m_pGroup = nullptr; + } + + DestroyConstraint(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::Activate() +{ + if ( m_pConstraint ) + m_pConstraint->SetEnabled( true ); +} + +void JoltPhysicsConstraint::Deactivate() +{ + if ( m_pConstraint ) + m_pConstraint->SetEnabled( false ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::SetGameData( void *gameData ) +{ + m_pGameData = gameData; +} + +void *JoltPhysicsConstraint::GetGameData() const +{ + return m_pGameData; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsObject *JoltPhysicsConstraint::GetReferenceObject() const +{ + return m_pObjReference; +} + +IPhysicsObject *JoltPhysicsConstraint::GetAttachedObject() const +{ + return m_pObjAttached; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::SetLinearMotor( float speed, float maxLinearImpulse ) +{ + if ( !m_pConstraint ) + return; + + speed = SourceToJolt::Distance( speed ); + maxLinearImpulse = SourceToJolt::Distance( maxLinearImpulse ); + + switch ( m_ConstraintType ) + { + case CONSTRAINT_SLIDING: + { + JPH::SliderConstraint *pConstraint = static_cast( m_pConstraint ); + pConstraint->SetMotorState( speed ? JPH::EMotorState::Velocity : JPH::EMotorState::Off ); + pConstraint->SetTargetVelocity( speed ); + + JPH::MotorSettings &motorSettings = pConstraint->GetMotorSettings(); + motorSettings.SetForceLimits( -maxLinearImpulse, maxLinearImpulse ); + + break; + } + } +} + +void JoltPhysicsConstraint::SetAngularMotor( float rotSpeed, float maxAngularImpulse ) +{ + if ( !m_pConstraint ) + return; + + rotSpeed = DEG2RAD( rotSpeed ); + maxAngularImpulse = DEG2RAD( maxAngularImpulse ); + + switch ( m_ConstraintType ) + { + case CONSTRAINT_RAGDOLL: + { + // Josh: + // If you change the hinge optimization stuff, remember to + // check this! m_ConstraintType is CONSTRAINT_HINGE for that! (same with normal vphysics) + // + // Something else to note is... does the below code for friction vs angular impulse work on + // ragdolls -> hinges correctly? This happens in Source, but this may not necessarily be correct. + // :/ + VJoltAssert( m_pConstraint->GetSubType() == JPH::EConstraintSubType::SixDOF ); + + JPH::SixDOFConstraint *pConstraint = static_cast( m_pConstraint ); + pConstraint->SetTargetAngularVelocityCS( JPH::Vec3( rotSpeed, rotSpeed, rotSpeed ) ); + pConstraint->SetMaxFriction( JPH::SixDOFConstraint::EAxis::RotationX, maxAngularImpulse ); + pConstraint->SetMaxFriction( JPH::SixDOFConstraint::EAxis::RotationY, maxAngularImpulse ); + pConstraint->SetMaxFriction( JPH::SixDOFConstraint::EAxis::RotationZ, maxAngularImpulse ); + break; + } + + case CONSTRAINT_HINGE: + { + JPH::HingeConstraint *pConstraint = static_cast( m_pConstraint ); + pConstraint->SetMotorState( rotSpeed ? JPH::EMotorState::Velocity : JPH::EMotorState::Off ); + pConstraint->SetTargetAngularVelocity( rotSpeed ); + + JPH::MotorSettings &motorSettings = pConstraint->GetMotorSettings(); + motorSettings.SetForceLimits( -fabsf( maxAngularImpulse ), fabsf( maxAngularImpulse ) ); + + break; + } + } +} + +//------------------------------------------------------------------------------------------------- + +// Slart: This is never called anywhere in our codebase +void JoltPhysicsConstraint::UpdateRagdollTransforms( const matrix3x4_t &constraintToReference, const matrix3x4_t &constraintToAttached ) +{ +} + +// Slart: This is only used for visual debugging, which we don't *really* need since we have Jolt's debugger +bool JoltPhysicsConstraint::GetConstraintTransform( matrix3x4_t *pConstraintToReference, matrix3x4_t *pConstraintToAttached ) const +{ + if ( m_pObjReference && pConstraintToReference ) + m_pObjReference->GetPositionMatrix( pConstraintToReference ); + if ( m_pObjAttached && pConstraintToAttached ) + m_pObjAttached->GetPositionMatrix( pConstraintToAttached ); + return true; +} + +// Slart: Yet another debugging thing +bool JoltPhysicsConstraint::GetConstraintParams( constraint_breakableparams_t *pParams ) const +{ + return false; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::OutputDebugInfo() +{ + +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + DestroyConstraint(); + + // Normal VPhysics calls ConstraintBroken when an object being killed destroys the constraint. + m_pPhysicsEnvironment->NotifyConstraintDisabled( this ); +} + +//------------------------------------------------------------------------------------------------- +// Ragdoll +//------------------------------------------------------------------------------------------------- + +static Vector DOFToAxis( uint32 uDOF ) +{ + return Vector( + uDOF == 0 ? 1.0f : 0.0f, + uDOF == 1 ? 1.0f : 0.0f, + uDOF == 2 ? 1.0f : 0.0f ); +} + +static uint32 NextDOF( uint32 uDOF ) +{ + return ( uDOF + 1 ) % 3; +} + +static bool IsFixedAxis( const constraint_axislimit_t &axis ) +{ + return axis.minRotation == axis.maxRotation; +} + +// Returns a bitmask of degrees of freedom for a ragdoll. +static uint32 GetDegreesOfFreedom( const constraint_ragdollparams_t &ragdoll ) +{ + uint32 uDOFMask = 0; + + for ( int i = 0; i < 3; i++ ) + { + if ( !IsFixedAxis( ragdoll.axes[i] ) ) + uDOFMask |= 1u << i; + } + + return uDOFMask; +} + +bool JoltPhysicsConstraint::InitialiseHingeFromRagdoll( IPhysicsConstraintGroup* pGroup, const constraint_ragdollparams_t& ragdoll ) +{ + const uint32 uDOFMask = GetDegreesOfFreedom( ragdoll ); + const uint32 uDOFCount = popcnt( uDOFMask ); + + if ( uDOFCount != 1 ) + return false; + + const uint32 uDOF = tzcnt( uDOFMask ); + const Vector vecNextDOFAxis = DOFToAxis( NextDOF( uDOF ) ); + + matrix3x4_t refObjToWorld; + m_pObjReference->GetPositionMatrix( &refObjToWorld ); + + matrix3x4_t constraintToWorld; + ConcatTransforms( refObjToWorld, ragdoll.constraintToReference, constraintToWorld ); + + Vector perpAxisDir; + VectorIRotate( vecNextDOFAxis, ragdoll.constraintToReference, perpAxisDir ); + + constraint_limitedhingeparams_t hinge; + hinge.Defaults(); + hinge.constraint = ragdoll.constraint; + hinge.worldPosition = GetColumn( constraintToWorld, MatrixAxis::Origin ); + hinge.worldAxisDirection = GetColumn( constraintToWorld, static_cast< JoltMatrixAxes >( uDOF ) ); + hinge.referencePerpAxisDirection = vecNextDOFAxis; + hinge.attachedPerpAxisDirection = Rotate( perpAxisDir, ragdoll.constraintToAttached ); + hinge.hingeAxis = ragdoll.axes[ uDOF ]; + + if ( !ragdoll.useClockwiseRotations ) + { + const float minLimit = hinge.hingeAxis.minRotation; + const float maxLimit = hinge.hingeAxis.maxRotation; + + hinge.hingeAxis.minRotation = -maxLimit; + hinge.hingeAxis.maxRotation = -minLimit; + } + + InitialiseHinge( pGroup, hinge ); + + return true; +} + +void JoltPhysicsConstraint::InitialiseRagdoll( IPhysicsConstraintGroup *pGroup, const constraint_ragdollparams_t &ragdoll ) +{ + // Josh: + // Optimize to a hinge constraint if we can -- avoids a bunch of useless computation + // and additionally fixes the fact that we can only specify disjoint min/max rotations + // on the X axis with Jolt 6DOF + // Currently breaks killing NPCs that are in a pose for some reason -- they stay in the pose. Needs investigation. + if ( vjolt_ragdoll_hinge_optimization.GetBool() && InitialiseHingeFromRagdoll( pGroup, ragdoll ) ) + return; + + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_RAGDOLL; + + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::Mat44 constraintToReference = SourceToJolt::Matrix( ragdoll.constraintToReference ); + JPH::Mat44 constraintToAttached = SourceToJolt::Matrix( ragdoll.constraintToAttached ); + + JPH::SixDOFConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + + settings.mPosition1 = constraintToReference.GetTranslation() - refBody->GetShape()->GetCenterOfMass(); + settings.mAxisX1 = constraintToReference.GetAxisX(); + settings.mAxisY1 = constraintToReference.GetAxisY(); + + settings.mPosition2 = constraintToAttached.GetTranslation() - attBody->GetShape()->GetCenterOfMass(); + settings.mAxisX2 = constraintToAttached.GetAxisX(); + settings.mAxisY2 = constraintToAttached.GetAxisY(); + + + for ( int i = 0; i < 3; i++ ) + { + JPH::SixDOFConstraintSettings::EAxis positionalAxis = static_cast< JPH::SixDOFConstraintSettings::EAxis >( + JPH::SixDOFConstraintSettings::EAxis::TranslationX + i ); + + JPH::SixDOFConstraintSettings::EAxis rotationalAxis = static_cast< JPH::SixDOFConstraintSettings::EAxis >( + JPH::SixDOFConstraintSettings::EAxis::RotationX + i ); + + // Make positional axes fixed, unless otherwise stated; the airboat needs them unlocked for a funny hack. + if ( !ragdoll.onlyAngularLimits ) + settings.MakeFixedAxis( positionalAxis ); + + if ( ragdoll.axes[i].minRotation == ragdoll.axes[i].maxRotation ) + { + //Log_Msg( LOG_VJolt, "Creating ragdoll-fixed constraint\n" ); + settings.MakeFixedAxis( rotationalAxis ); + } + else + { + if ( i == 0 ) + { + //Log_Msg( LOG_VJolt, "Creating X ragdoll constraint with %g min and %g max\n", -ragdoll.axes[i].maxRotation, -ragdoll.axes[i].minRotation ); + settings.SetLimitedAxis( rotationalAxis, DEG2RAD( ragdoll.axes[i].minRotation ), DEG2RAD( ragdoll.axes[i].maxRotation ) ); + } + else + { + // Josh: + // Jolt uses a Swing Twist for part of this, which means we have to find the max movement allowed + // to contrain it by, I think. This results in legs that kind of flop both ways... From the Jolt code + // "The swing twist constraint part requires symmetrical rotations around Y and Z" + // + // This is kind of 'worked around' by the code above that converts 1DOF -> hinges. + + const float maxMovement = Max( fabsf( ragdoll.axes[i].maxRotation ), fabsf( ragdoll.axes[i].minRotation ) ); + //Log_Msg( LOG_VJolt, "Creating Y/Z ragdoll constraint with %g min and %g max\n", -maxMovement, maxMovement ); + settings.SetLimitedAxis( rotationalAxis, -DEG2RAD( maxMovement ), DEG2RAD( maxMovement ) ); + } + } + + // Swap the limits if we are using clockwise rotations, + // this is only not true if we are saving/loading. + if ( ragdoll.useClockwiseRotations ) + { + const float minLimit = settings.mLimitMin[rotationalAxis]; + const float maxLimit = settings.mLimitMax[rotationalAxis]; + + settings.mLimitMin[rotationalAxis] = -maxLimit; + settings.mLimitMax[rotationalAxis] = -minLimit; + } + + // TODO(Josh): What is .torque on a ragdoll in Source? I want to understand what it is + // before setting random values on the Jolt side. + // + //if ( ragdoll.axes[ i ].torque != 0.0f ) + // settings.mMotorSettings[ rotationalAxis ].SetTorqueLimit( ragdoll.axes[ i ].torque ); + } + + m_pConstraint = settings.Create( *refBody, *attBody ); + m_pConstraint->SetEnabled( !pGroup && ragdoll.constraint.isActive ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- +// Hinge +//------------------------------------------------------------------------------------------------- + +static JPH::Vec3 HingePerpendicularVector( JPH::Vec3Arg dir ) +{ + return fabsf( dir.GetX() ) < 0.57f + ? JPH::Vec3::sAxisX().Cross( dir ).Normalized() + : JPH::Vec3::sAxisY().Cross( dir ).Normalized(); +} + +void JoltPhysicsConstraint::InitialiseHinge( IPhysicsConstraintGroup *pGroup, const constraint_hingeparams_t &hinge ) +{ + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_HINGE; + + // Get our bodies + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::HingeConstraintSettings settings; + settings.mPoint1 = SourceToJolt::Distance( hinge.worldPosition ); + settings.mPoint2 = SourceToJolt::Distance( hinge.worldPosition ); + + settings.mHingeAxis1 = JPH::Vec3( hinge.worldAxisDirection.x, hinge.worldAxisDirection.y, hinge.worldAxisDirection.z ); + settings.mHingeAxis2 = JPH::Vec3( hinge.worldAxisDirection.x, hinge.worldAxisDirection.y, hinge.worldAxisDirection.z ); + + settings.mNormalAxis1 = HingePerpendicularVector( settings.mHingeAxis1 ); + settings.mNormalAxis2 = HingePerpendicularVector( settings.mHingeAxis2 ); + + if ( hinge.hingeAxis.minRotation != hinge.hingeAxis.maxRotation ) + { + settings.mLimitsMin = DEG2RAD( -hinge.hingeAxis.maxRotation ); + settings.mLimitsMax = DEG2RAD( -hinge.hingeAxis.minRotation ); + } + + // TODO(Josh): Fix this... I have no idea what this should be. + //settings.mMaxFrictionTorque = hinge.hingeAxis.torque; + + m_pConstraint = settings.Create( *refBody, *attBody ); + m_pConstraint->SetEnabled( !pGroup && hinge.constraint.isActive ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- +// Sliding +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::InitialiseSliding( IPhysicsConstraintGroup *pGroup, const constraint_slidingparams_t &sliding ) +{ + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_SLIDING; + + // Get our bodies + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::SliderConstraintSettings settings; + settings.SetPoint( *refBody, *attBody ); + settings.SetSliderAxis( JPH::Vec3( sliding.slideAxisRef.x, sliding.slideAxisRef.y, sliding.slideAxisRef.z ) ); + + if ( sliding.limitMin != sliding.limitMax ) + { + settings.mLimitsMin = SourceToJolt::Distance( sliding.limitMin ); + settings.mLimitsMax = SourceToJolt::Distance( sliding.limitMax ); + } + + settings.mMaxFrictionForce = sliding.friction; + + m_pConstraint = settings.Create( *refBody, *attBody ); + m_pConstraint->SetEnabled( !pGroup && sliding.constraint.isActive ); + + if ( sliding.velocity ) + { + JPH::SliderConstraint *pConstraint = static_cast( m_pConstraint ); + pConstraint->SetMotorState( JPH::EMotorState::Velocity ); + pConstraint->SetTargetVelocity( SourceToJolt::Distance( sliding.velocity ) ); + } + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- +// Ballsocket +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::InitialiseBallsocket( IPhysicsConstraintGroup *pGroup, const constraint_ballsocketparams_t &ballsocket ) +{ + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_BALLSOCKET; + + // Get our bodies + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::PointConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = SourceToJolt::Distance( ballsocket.constraintPosition[0] ) - refBody->GetShape()->GetCenterOfMass(); + settings.mPoint2 = SourceToJolt::Distance( ballsocket.constraintPosition[1] ) - attBody->GetShape()->GetCenterOfMass(); + + m_pConstraint = settings.Create( *refBody, *attBody ); + m_pConstraint->SetEnabled( !pGroup && ballsocket.constraint.isActive ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- +// Fixed +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::InitialiseFixed( IPhysicsConstraintGroup *pGroup, const constraint_fixedparams_t &fixed ) +{ + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_FIXED; + + // Get our bodies + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::FixedConstraintSettings settings; + settings.SetPoint( *refBody, *attBody ); + + m_pConstraint = settings.Create( *refBody, *attBody ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- +// Length +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::InitialiseLength( IPhysicsConstraintGroup *pGroup, const constraint_lengthparams_t &length ) +{ + SetGroup( pGroup ); + m_ConstraintType = CONSTRAINT_LENGTH; + + // Get our bodies + JPH::Body *refBody = m_pObjReference->GetBody(); + JPH::Body *attBody = m_pObjAttached->GetBody(); + + JPH::DistanceConstraintSettings settings; + settings.mSpace = JPH::EConstraintSpace::LocalToBodyCOM; + settings.mPoint1 = SourceToJolt::Distance( length.objectPosition[0] ) - refBody->GetShape()->GetCenterOfMass(); + settings.mPoint2 = SourceToJolt::Distance( length.objectPosition[1] ) - attBody->GetShape()->GetCenterOfMass(); + + settings.mMinDistance = SourceToJolt::Distance( length.minLength ); + settings.mMaxDistance = SourceToJolt::Distance( length.totalLength ); + + // Josh: UNDONE! Nothing seems to use strength on length ever + // after analysing the codebase. + // + //settings.mFrequency = 1.0f - length.constraint.strength; + //if ( settings.mFrequency ) + // settings.mDamping = 1.0f; + + m_pConstraint = settings.Create( *refBody, *attBody ); + m_pConstraint->SetEnabled( !pGroup && length.constraint.isActive ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::SaveConstraintSettings( JPH::StateRecorder &recorder ) +{ + recorder.Write( m_ConstraintType ); + auto settings = m_pConstraint->GetConstraintSettings(); + settings->SaveBinaryState( recorder ); + m_pConstraint->SaveState( recorder ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsConstraint::SetGroup( IPhysicsConstraintGroup *pGroup ) +{ + if ( m_pGroup ) + m_pGroup->RemoveConstraint( this ); + m_pGroup = static_cast< JoltPhysicsConstraintGroup * >( pGroup ); + if ( m_pGroup ) + m_pGroup->AddConstraint( this ); +} + +void JoltPhysicsConstraint::DestroyConstraint() +{ + if ( m_pObjAttached ) + { + m_pObjAttached->RemoveDestroyedListener( this ); + m_pObjAttached = nullptr; + } + if ( m_pObjReference ) + { + m_pObjReference->RemoveDestroyedListener( this ); + m_pObjReference = nullptr; + } + + if ( m_pConstraint ) + { + m_pPhysicsSystem->RemoveConstraint( m_pConstraint ); + m_pConstraint->Release(); + m_pConstraint = nullptr; + } +} diff --git a/vphysics_jolt/vjolt_constraints.h b/vphysics_jolt/vjolt_constraints.h new file mode 100644 index 0000000..8821cbc --- /dev/null +++ b/vphysics_jolt/vjolt_constraints.h @@ -0,0 +1,101 @@ +//================================================================================================= +// +// Constraints +// +//================================================================================================= + +#pragma once + +#include "vjolt_internal_listeners.h" + +enum constraintType_t +{ + CONSTRAINT_UNKNOWN = 0, + CONSTRAINT_RAGDOLL, + CONSTRAINT_HINGE, + CONSTRAINT_FIXED, + CONSTRAINT_BALLSOCKET, + CONSTRAINT_SLIDING, + CONSTRAINT_PULLEY, + CONSTRAINT_LENGTH, +}; + +class JoltPhysicsConstraint; +class JoltPhysicsEnvironment; + +class JoltPhysicsConstraintGroup final : public IPhysicsConstraintGroup +{ +public: + JoltPhysicsConstraintGroup(); + ~JoltPhysicsConstraintGroup() override; + + void Activate() override; + bool IsInErrorState() override; + void ClearErrorState() override; + void GetErrorParams( constraint_groupparams_t *pParams ) override; + void SetErrorParams( const constraint_groupparams_t ¶ms ) override; + void SolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1 ) override; + + void AddConstraint( JoltPhysicsConstraint *pConstraint ); + void RemoveConstraint( JoltPhysicsConstraint *pConstraint ); + +private: + std::vector< JoltPhysicsConstraint * > m_pConstraints; + constraint_groupparams_t m_ErrorParams = {}; +}; + +class JoltPhysicsConstraint final : public IPhysicsConstraint, public IJoltObjectDestroyedListener +{ +public: + JoltPhysicsConstraint( JoltPhysicsEnvironment *pPhysicsEnvironment, IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, constraintType_t Type = CONSTRAINT_UNKNOWN, JPH::Constraint* pConstraint = nullptr, void *pGameData = nullptr ); + ~JoltPhysicsConstraint() override; + + void Activate() override; + void Deactivate() override; + + void SetGameData( void *gameData ) override; + void * GetGameData() const override; + + IPhysicsObject *GetReferenceObject() const override; + IPhysicsObject *GetAttachedObject() const override; + + void SetLinearMotor( float speed, float maxLinearImpulse ) override; + void SetAngularMotor( float rotSpeed, float maxAngularImpulse ) override; + + void UpdateRagdollTransforms( const matrix3x4_t &constraintToReference, const matrix3x4_t &constraintToAttached ) override; + bool GetConstraintTransform( matrix3x4_t *pConstraintToReference, matrix3x4_t *pConstraintToAttached ) const override; + bool GetConstraintParams( constraint_breakableparams_t *pParams ) const override; + + void OutputDebugInfo() override; + + // IJoltObjectDestroyedListener + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) override; + +public: + bool InitialiseHingeFromRagdoll( IPhysicsConstraintGroup* pGroup, const constraint_ragdollparams_t& ragdoll ); + void InitialiseRagdoll( IPhysicsConstraintGroup *pGroup, const constraint_ragdollparams_t &ragdoll ); + void InitialiseHinge( IPhysicsConstraintGroup *pGroup, const constraint_hingeparams_t &hinge ); + void InitialiseSliding( IPhysicsConstraintGroup *pGroup, const constraint_slidingparams_t &sliding ); + void InitialiseBallsocket( IPhysicsConstraintGroup *pGroup, const constraint_ballsocketparams_t &ballsocket ); + void InitialiseFixed( IPhysicsConstraintGroup *pGroup, const constraint_fixedparams_t &fixed ); + void InitialiseLength( IPhysicsConstraintGroup *pGroup, const constraint_lengthparams_t &length ); + + void SaveConstraintSettings( JPH::StateRecorder &recorder ); + +private: + + void SetGroup( IPhysicsConstraintGroup *pGroup ); + + void DestroyConstraint(); + + JoltPhysicsObject *m_pObjReference = nullptr; + JoltPhysicsObject *m_pObjAttached = nullptr; + JPH::Constraint *m_pConstraint = nullptr; + constraintType_t m_ConstraintType = CONSTRAINT_UNKNOWN; + + JoltPhysicsConstraintGroup *m_pGroup = nullptr; + + void *m_pGameData = nullptr; + JoltPhysicsEnvironment *m_pPhysicsEnvironment = nullptr; + JPH::PhysicsSystem *m_pPhysicsSystem = nullptr; +}; diff --git a/vphysics_jolt/vjolt_controller_fluid.cpp b/vphysics_jolt/vjolt_controller_fluid.cpp new file mode 100644 index 0000000..2514e10 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_fluid.cpp @@ -0,0 +1,227 @@ + +#include "cbase.h" + +#include "vjolt_layers.h" +#include "vjolt_collide.h" +#include "vjolt_object.h" +#include "vjolt_environment.h" +#include "vjolt_surfaceprops.h" + +#include "vjolt_controller_fluid.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +// Josh: The surfacePlane in fluidparams_t is specified in world-space, but we need to translate +// that to the pFluidObject's local-space so we can handle the fluid object moving +// for eg. func_water_analog. +static cplane_t PlaneToLocalSpace( JoltPhysicsObject* pFluidObject, Vector4D worldSurfacePlane ) +{ + const cplane_t worldPlane = { worldSurfacePlane.AsVector3D(), worldSurfacePlane[3] }; + + matrix3x4_t objectToWorld; + pFluidObject->GetPositionMatrix( &objectToWorld ); + + cplane_t localPlane; + MatrixITransformPlane( objectToWorld, worldPlane, localPlane ); + return localPlane; +} + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsFluidController::JoltPhysicsFluidController( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pFluidObject, const fluidparams_t *pParams ) + : m_pPhysicsSystem( pPhysicsSystem ) + , m_pFluidObject( pFluidObject ) + , m_Params( *pParams ) + , m_LocalPlane( PlaneToLocalSpace( pFluidObject, pParams->surfacePlane ) ) +{ + m_pFluidObject->BecomeTrigger(); + m_pFluidObject->SetFluidController( this ); + m_pFluidObject->AddDestroyedListener( this ); +} + +JoltPhysicsFluidController::~JoltPhysicsFluidController() +{ + ClearCachedObjectsInShape(); + + if ( m_pFluidObject ) + { + m_pFluidObject->RemoveDestroyedListener( this ); + m_pFluidObject->SetFluidController( nullptr ); + m_pFluidObject->RemoveTrigger(); + } +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFluidController::SetGameData( void *pGameData ) +{ + m_Params.pGameData = pGameData; +} + +void *JoltPhysicsFluidController::GetGameData() const +{ + return m_Params.pGameData; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFluidController::GetSurfacePlane( Vector *pNormal, float *pDist ) const +{ + const cplane_t worldPlane = GetSurfacePlane(); + + if ( pNormal ) + *pNormal = worldPlane.normal; + + if ( pDist ) + *pDist = worldPlane.dist; +} + +float JoltPhysicsFluidController::GetDensity() const +{ + // TODO(Josh): We know the material density, but what units should this be in? + return 1.0f; +} + +void JoltPhysicsFluidController::WakeAllSleepingObjects() +{ + for ( JoltPhysicsObject *pObject : m_ObjectsInShape ) + pObject->Wake(); +} + +int JoltPhysicsFluidController::GetContents() const +{ + return m_Params.contents; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFluidController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + if ( pObject == m_pFluidObject ) + m_pFluidObject = nullptr; + + std::erase_if( m_ObjectsInShape, [pObject]( JoltPhysicsObject *pCachedObject ) { return pObject == pCachedObject; } ); +} + +//------------------------------------------------------------------------------------------------- + +// Applies buoyancy to any body that intersects with the water shape +class SourceFluidCollector : public JPH::CollideShapeCollector +{ +public: + SourceFluidCollector( JPH::PhysicsSystem *inSystem, std::vector &objectsInShape, const JPH::Plane &inSurface, float inDeltaTime, const fluidparams_t ¶ms, float flDensity ) + : m_pPhysicsSystem ( inSystem ) + , m_ObjectsInShape ( objectsInShape ) + , m_Surface ( inSurface ) + , m_DeltaTime ( inDeltaTime ) + , m_Params ( params ) + , m_flDensity ( flDensity ) {} + + void AddHit( const ResultType &inResult ) override + { + const JPH::BodyID inBodyID = inResult.mBodyID2; + + JPH::BodyLockWrite lock( m_pPhysicsSystem->GetBodyLockInterface(), inBodyID ); + JPH::Body &body = lock.GetBody(); + JoltPhysicsObject *pObject = reinterpret_cast( body.GetUserData() ); + + // Don't do fluid simulation for these. + if ( pObject->GetShadowController() || !( pObject->GetCallbackFlags() & CALLBACK_DO_FLUID_SIMULATION ) ) + return; + + m_ObjectsInShape.push_back( pObject ); + + // The original VPhysics ignores m_Params.torqueFactor and always has 0.01. + // But I think 0.05 looks better. + static constexpr float flTorqueFactor = 0.05f; + + // Josh: + // The buoyancy ratio in Source works like this: + // fluid_density = m_flDensity * pObject->GetBuoyancyRatio() + // but in Jolt, it gets the fluid density like this (you pass in inBuoyancy) + // fluid_density = inBuoyancy / (total_volume * inverse_mass); + // so with some rearranging... + // inBuoyancy = fluid_density * (total_volume * inverse_mass); + const float flFluidDensity = m_flDensity * pObject->GetBuoyancyRatio(); + float inBuoyancy = flFluidDensity * pObject->GetBody()->GetShape()->GetVolume() * pObject->GetInvMass(); + if ( body.IsActive() ) + body.ApplyBuoyancyImpulse( m_Surface, inBuoyancy, 0.3f /* m_Params.damping */, flTorqueFactor, SourceToJolt::Distance( m_Params.currentVelocity ), m_pPhysicsSystem->GetGravity(), m_DeltaTime); + } + +private: + JPH::PhysicsSystem * m_pPhysicsSystem; + std::vector &m_ObjectsInShape; + JPH::Plane m_Surface; + float m_DeltaTime; + const fluidparams_t &m_Params; + float m_flDensity; +}; + +void JoltPhysicsFluidController::OnPreSimulate( float deltaTime ) +{ + // Clear out our last list of items + ClearCachedObjectsInShape(); + + if ( !m_pFluidObject ) + return; + + const cplane_t surfacePlane = GetSurfacePlane(); + + JPH::Plane surface = JPH::Plane( + SourceToJolt::Unitless( surfacePlane.normal ), + SourceToJolt::Distance( -surfacePlane.dist ) ); + + const float flDensity = m_pFluidObject->GetMaterialDensity(); + + SourceFluidCollector collector( m_pPhysicsSystem, m_ObjectsInShape, surface, deltaTime, m_Params, flDensity ); + + // Apply buoyancy to all bodies that intersect with the water + JPH::CollideShapeSettings collideSettings; + collideSettings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideOnlyWithActive; + + // Ignore my own body + // TODO(Josh): Should we use the SourceHitFilter from the player controller here? + // May make more sense and respect ShouldCollide. Not sure if regular VPhysics does here. + JPH::IgnoreSingleBodyFilter body_filter( m_pFluidObject->GetBodyID() ); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + JPH::Mat44 queryTransform = bodyInterface.GetCenterOfMassTransform( m_pFluidObject->GetBodyID() ); + + const JPH::Shape *pShape = m_pFluidObject->GetCollide()->ToShape(); + + m_pPhysicsSystem->GetNarrowPhaseQueryNoLock().CollideShape( + pShape, JPH::Vec3::sReplicate( 1.0f ), queryTransform, collideSettings, collector, + JPH::SpecifiedBroadPhaseLayerFilter( BroadPhaseLayers::MOVING ), JPH::SpecifiedObjectLayerFilter( Layers::MOVING ), body_filter ); + + for ( JoltPhysicsObject *pObject : m_ObjectsInShape ) + pObject->AddDestroyedListener( this ); +} + +//------------------------------------------------------------------------------------------------- + +cplane_t JoltPhysicsFluidController::GetSurfacePlane() const +{ + if ( !m_pFluidObject ) + return cplane_t{}; + + matrix3x4_t objectToWorld; + m_pFluidObject->GetPositionMatrix( &objectToWorld ); + + cplane_t worldPlane; + MatrixTransformPlane( objectToWorld, m_LocalPlane, worldPlane ); + + return worldPlane; +} + +void JoltPhysicsFluidController::ClearCachedObjectsInShape() +{ + // TODO(Josh): This could maybe be made more efficient by having two vectors + // and only updating the listeners on the ones we need to, then std::move-ing. + for ( JoltPhysicsObject *pObject : m_ObjectsInShape ) + pObject->RemoveDestroyedListener( this ); + + m_ObjectsInShape.clear(); +} diff --git a/vphysics_jolt/vjolt_controller_fluid.h b/vphysics_jolt/vjolt_controller_fluid.h new file mode 100644 index 0000000..01617a4 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_fluid.h @@ -0,0 +1,38 @@ + +#pragma once + +#include "vjolt_internal_listeners.h" + +class JoltPhysicsFluidController final : public IPhysicsFluidController, public IJoltObjectDestroyedListener, public IJoltPhysicsController +{ +public: + JoltPhysicsFluidController( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pFluidObject, const fluidparams_t *pParams ); + ~JoltPhysicsFluidController() override; + + void SetGameData( void *pGameData ) override; + void * GetGameData() const override; + + void GetSurfacePlane( Vector *pNormal, float *pDist ) const override; + float GetDensity() const override; + void WakeAllSleepingObjects() override; + int GetContents() const override; + +public: + + // IJoltObjectDestroyedListener + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) override; + // IJoltPhysicsController + void OnPreSimulate( float flDeltaTime ) override; + +private: + + cplane_t GetSurfacePlane() const; + void ClearCachedObjectsInShape(); + + JPH::PhysicsSystem * m_pPhysicsSystem; + JoltPhysicsObject * m_pFluidObject; + std::vector m_ObjectsInShape; + + fluidparams_t m_Params; + cplane_t m_LocalPlane; +}; diff --git a/vphysics_jolt/vjolt_controller_motion.cpp b/vphysics_jolt/vjolt_controller_motion.cpp new file mode 100644 index 0000000..bbe6428 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_motion.cpp @@ -0,0 +1,152 @@ + +#include "cbase.h" + +#include "vjolt_controller_motion.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsMotionController::JoltPhysicsMotionController( IMotionEvent *pHandler ) + : m_pMotionEvent( pHandler ) +{ +} + +JoltPhysicsMotionController::~JoltPhysicsMotionController() +{ + for ( JoltPhysicsObject *pObject : m_pObjects ) + pObject->RemoveDestroyedListener( this ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsMotionController::SetEventHandler( IMotionEvent *pHandler ) +{ + m_pMotionEvent = pHandler; +} + +void JoltPhysicsMotionController::AttachObject( IPhysicsObject *pObject, bool bCheckIfAlreadyAttached ) +{ + if ( !pObject || pObject->IsStatic() ) + return; + + JoltPhysicsObject *pPhysicsObject = static_cast< JoltPhysicsObject * >( pObject ); + if ( bCheckIfAlreadyAttached && m_pObjects.HasElement( pPhysicsObject ) ) + return; + + pPhysicsObject->AddDestroyedListener( this ); + m_pObjects.AddToTail( pPhysicsObject ); +} + +void JoltPhysicsMotionController::DetachObject( IPhysicsObject *pObject ) +{ + if ( !pObject ) + return; + + JoltPhysicsObject *pPhysicsObject = static_cast< JoltPhysicsObject * >( pObject ); + m_pObjects.FindAndRemove( pPhysicsObject ); + pPhysicsObject->RemoveDestroyedListener( this ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsMotionController::CountObjects( void ) +{ + return m_pObjects.Count(); +} + +void JoltPhysicsMotionController::GetObjects( IPhysicsObject **pObjectList ) +{ + for ( int i = 0; i < m_pObjects.Count(); i++ ) + pObjectList[ i ] = m_pObjects[ i ]; +} + +void JoltPhysicsMotionController::ClearObjects( void ) +{ + m_pObjects.RemoveAll(); +} + +void JoltPhysicsMotionController::WakeObjects( void ) +{ + for ( JoltPhysicsObject *pObject : m_pObjects ) + pObject->Wake(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsMotionController::SetPriority( priority_t priority ) +{ + // Not relevant to us. +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsMotionController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + JoltPhysicsObject *pPhysicsObject = static_cast< JoltPhysicsObject * >( pObject ); + m_pObjects.FindAndRemove( pPhysicsObject ); +} + +void JoltPhysicsMotionController::OnPreSimulate( float flDeltaTime ) +{ + if ( !m_pMotionEvent ) + return; + + for ( JoltPhysicsObject *pObject : m_pObjects ) + { + if ( !pObject->IsMoveable() ) + return; + + Vector vecLocalVelocity = vec3_origin; + Vector vecAngularVelocity = vec3_origin; + IMotionEvent::simresult_e simResult = m_pMotionEvent->Simulate( this, pObject, flDeltaTime, vecLocalVelocity, vecAngularVelocity ); + + vecLocalVelocity *= flDeltaTime; + vecAngularVelocity *= flDeltaTime; + + // Convert linear velocity to world space + Vector vecWorldLinearVelocity = vec3_origin; + pObject->LocalToWorldVector( &vecWorldLinearVelocity, vecLocalVelocity ); + + switch ( simResult ) + { + case IMotionEvent::SIM_NOTHING: + { + break; + } + + case IMotionEvent::SIM_LOCAL_ACCELERATION: + { + pObject->AddVelocity( &vecWorldLinearVelocity, &vecAngularVelocity ); + break; + } + + case IMotionEvent::SIM_LOCAL_FORCE: + { + pObject->ApplyForceCenter( vecWorldLinearVelocity ); + pObject->ApplyTorqueCenter( vecAngularVelocity ); + break; + } + + case IMotionEvent::SIM_GLOBAL_ACCELERATION: + { + pObject->AddVelocity( &vecLocalVelocity, &vecAngularVelocity ); + break; + } + + case IMotionEvent::SIM_GLOBAL_FORCE: + { + pObject->ApplyForceCenter( vecLocalVelocity ); + pObject->ApplyTorqueCenter( vecAngularVelocity ); + break; + } + + default: + { + Log_Warning( LOG_VJolt, "Invalid motion event\n" ); + break; + } + } + } +} diff --git a/vphysics_jolt/vjolt_controller_motion.h b/vphysics_jolt/vjolt_controller_motion.h new file mode 100644 index 0000000..3caac28 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_motion.h @@ -0,0 +1,33 @@ + +#pragma once + +#include "vjolt_object.h" +#include "vjolt_environment.h" + +class JoltPhysicsMotionController : public IPhysicsMotionController, public IJoltObjectDestroyedListener, public IJoltPhysicsController +{ +public: + JoltPhysicsMotionController( IMotionEvent *pHandler ); + ~JoltPhysicsMotionController() override; + + void SetEventHandler( IMotionEvent *handler ) override; + void AttachObject( IPhysicsObject *pObject, bool checkIfAlreadyAttached ) override; + void DetachObject( IPhysicsObject *pObject ) override; + + int CountObjects( void ) override; + void GetObjects( IPhysicsObject **pObjectList ) override; + void ClearObjects( void ) override; + void WakeObjects( void ) override; + + void SetPriority( priority_t priority ) override; + +public: + + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) override; + void OnPreSimulate( float flDeltaTime ) override; + +private: + IMotionEvent *m_pMotionEvent; + + CUtlVector< JoltPhysicsObject * > m_pObjects; +}; diff --git a/vphysics_jolt/vjolt_controller_player.cpp b/vphysics_jolt/vjolt_controller_player.cpp new file mode 100644 index 0000000..206cf0f --- /dev/null +++ b/vphysics_jolt/vjolt_controller_player.cpp @@ -0,0 +1,437 @@ + +#include "cbase.h" + +#include "vjolt_layers.h" + +#include "vjolt_controller_player.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +static ConVar vjolt_player_collision_tolerance( "vjolt_player_collision_tolerance", "0.05" ); + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsPlayerController::JoltPhysicsPlayerController( JoltPhysicsObject *pObject ) +{ + SetObjectInternal( pObject ); +} + +JoltPhysicsPlayerController::~JoltPhysicsPlayerController() +{ + SetObjectInternal( nullptr ); + SetGround( nullptr ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsPlayerController::Update( const Vector &position, const Vector &velocity, float secondsToArrival, bool onground, IPhysicsObject *ground ) +{ + // timeOffset == secondsToArrival + + const JPH::Vec3 targetPosition = SourceToJolt::Distance( position ); + + if ( targetPosition.IsClose( m_targetPosition ) ) + return; + + const JPH::Vec3 targetVelocity = SourceToJolt::Distance( velocity ); + + m_targetPosition = targetPosition; + m_targetVelocity = targetVelocity; + m_secondsToArrival = secondsToArrival; + + // Bogus assertion: onground can be true and ground can be null when touching the world. That is okay + //VJoltAssert( ( onground && ground ) || ( !onground && !ground ) ); + + SetGround( static_cast( ground ) ); +} + +void JoltPhysicsPlayerController::SetEventHandler( IPhysicsPlayerControllerEvent *handler ) +{ + m_pHandler = handler; +} + +bool JoltPhysicsPlayerController::IsInContact() +{ + uint32 nState = GetContactState( 0 ); + return !!( nState & PLAYER_CONTACT_PHYSICS ); +} + +void JoltPhysicsPlayerController::MaxSpeed( const Vector &velocity ) +{ + // Do we have to care about this? IVP used a rigid body for the player shadow because it didn't + // have the concept of kinematic objects, our Jolt shadow follows the player exactly and + // is kinematic so if the game follows this max speed limit we don't need to care. +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsPlayerController::SetObject( IPhysicsObject *pObject ) +{ + SetObjectInternal( static_cast( pObject ) ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsPlayerController::GetShadowPosition( Vector *position, QAngle *angles ) +{ + return m_pObject->GetShadowPosition( position, angles ); +} + +void JoltPhysicsPlayerController::StepUp( float height ) +{ + if ( height == 0.0f ) + return; + + // Since the player is a kinematic object that slides around the world using velocity, when + // stepping up onto a platform we need to go there instantly, AddToPosition does that. + + m_pObject->AddToPosition( JPH::Vec3( 0.0f, 0.0f, SourceToJolt::Distance( height ) ) ); +} + +void JoltPhysicsPlayerController::Jump() +{ + // This does nothing in VPhysics. +} + +void JoltPhysicsPlayerController::GetShadowVelocity( Vector *velocity ) +{ + if ( !velocity ) + return; + + JPH::Vec3 jphVelocity = m_pObject->GetBody()->GetLinearVelocity(); + if ( m_pGround ) + { + jphVelocity -= m_pGround->GetBody()->GetPointVelocity( m_groundPos ); + } + + *velocity = JoltToSource::Distance( jphVelocity ); +} + +IPhysicsObject *JoltPhysicsPlayerController::GetObject() +{ + return m_pObject; +} + +void JoltPhysicsPlayerController::GetLastImpulse( Vector *pOut ) +{ + VectorClear( *pOut ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsPlayerController::SetPushMassLimit( float maxPushMass ) +{ + m_flPushableMassLimit = maxPushMass; +} + +void JoltPhysicsPlayerController::SetPushSpeedLimit( float maxPushSpeed ) +{ + m_flPushableSpeedLimit = maxPushSpeed; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsPlayerController::GetPushMassLimit() +{ + return m_flPushableMassLimit; +} + +float JoltPhysicsPlayerController::GetPushSpeedLimit() +{ + return m_flPushableSpeedLimit; +} + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsPlayerController::WasFrozen() +{ + // I think here the code is referring to IVP freezing objects after inactivity (sleeping), + // our objects are forced to never sleep, so we don't need to care? + return false; +} + +//------------------------------------------------------------------------------------------------- + +static void CheckCollision( JoltPhysicsObject *pObject, JPH::CollideShapeCollector &ioCollector, JPH::BodyFilter &ioFilter ) +{ + JPH::PhysicsSystem *pSystem = pObject->GetEnvironment()->GetPhysicsSystem(); + + // TODO(Josh): Make a PLAYER ONLY layer that will only collide with MOVING ONLY annd + // NOTHING ELSE tomorrow. + + // Create query broadphase layer filter + JPH::DefaultBroadPhaseLayerFilter broadphase_layer_filter = pSystem->GetDefaultBroadPhaseLayerFilter( Layers::MOVING ); + + // Create query object layer filter + JPH::DefaultObjectLayerFilter object_layer_filter = pSystem->GetDefaultLayerFilter( Layers::MOVING ); + + // Determine position to test + JPH::Vec3 position; + JPH::Quat rotation; + JPH::BodyInterface &bi = pSystem->GetBodyInterfaceNoLock(); + bi.GetPositionAndRotation( pObject->GetBodyID(), position, rotation ); + JPH::Mat44 query_transform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pObject->GetBody()->GetShape()->GetCenterOfMass() ); + + // Settings for collide shape + JPH::CollideShapeSettings settings; + settings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideOnlyWithActive; + settings.mActiveEdgeMovementDirection = bi.GetLinearVelocity( pObject->GetBodyID() ); + settings.mBackFaceMode = JPH::EBackFaceMode::IgnoreBackFaces; + settings.mMaxSeparationDistance = vjolt_player_collision_tolerance.GetFloat(); + + pSystem->GetNarrowPhaseQueryNoLock().CollideShape( pObject->GetBody()->GetShape(), JPH::Vec3::sReplicate( 1.0f ), query_transform, settings, ioCollector, broadphase_layer_filter, object_layer_filter, ioFilter ); +} + +// Slart: This is a version of CheckCollision that projects the player by their velocity, to attempt to push objects that we'll walk into soon +#if 0 +static void CheckCollision2( JoltPhysicsObject *pObject, JPH::CollideShapeCollector &ioCollector, const JPH::Vec3Arg targetVelocity, float flDeltaTime ) +{ + JPH::PhysicsSystem *pSystem = pObject->GetEnvironment()->GetPhysicsSystem(); + + // TODO(Josh): Make a PLAYER ONLY layer that will only collide with MOVING ONLY annd + // NOTHING ELSE tomorrow. + + // Create query broadphase layer filter + JPH::DefaultBroadPhaseLayerFilter broadphase_layer_filter = pSystem->GetDefaultBroadPhaseLayerFilter( Layers::MOVING ); + + // Create query object layer filter + JPH::DefaultObjectLayerFilter object_layer_filter = pSystem->GetDefaultLayerFilter( Layers::MOVING ); + + // Ignore my own body + JPH::IgnoreSingleBodyFilter body_filter( pObject->GetBodyID() ); + + // Determine position to test + JPH::Vec3 position; + JPH::Quat rotation; + JPH::BodyInterface &bi = pSystem->GetBodyInterfaceNoLock(); + bi.GetPositionAndRotation( pObject->GetBodyID(), position, rotation ); + position += targetVelocity * ( flDeltaTime * 2.0f ); + JPH::Mat44 query_transform = JPH::Mat44::sRotationTranslation( rotation, position + rotation * pObject->GetBody()->GetShape()->GetCenterOfMass() ); + + // Settings for collide shape + JPH::CollideShapeSettings settings; + settings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideOnlyWithActive; + settings.mActiveEdgeMovementDirection = bi.GetLinearVelocity( pObject->GetBodyID() ); + settings.mBackFaceMode = JPH::EBackFaceMode::IgnoreBackFaces; + settings.mMaxSeparationDistance = vjolt_player_collision_tolerance.GetFloat(); + + pSystem->GetNarrowPhaseQueryNoLock().CollideShape( pObject->GetBody()->GetShape(), JPH::Vec3::sReplicate( 1.0f ), query_transform, settings, ioCollector, broadphase_layer_filter, object_layer_filter, body_filter ); +} +#endif + +template +class SourceHitFilter : public JPH::BodyFilter +{ +public: + SourceHitFilter( JPH::PhysicsSystem* pPhysicsSystem, JoltPhysicsObject* pSelfObject ) + : m_pPhysicsSystem( pPhysicsSystem ) + , m_pSelfObject( pSelfObject ) + { + } + + bool ShouldCollideLocked( const JPH::Body &inBody ) const override + { + JoltPhysicsObject* pObject = reinterpret_cast( inBody.GetUserData() ); + + // Ignore self if specified. This can be nullptr if you don't want this. + if ( pObject == m_pSelfObject ) + return false; + + if constexpr ( MoveablesOnly ) + { + if ( pObject->IsTrigger() || !pObject->IsMoveable() ) + return false; + } + + if ( !pObject->GetEnvironment()->GetContactListener()->ShouldCollide( m_pSelfObject, pObject ) ) + return false; + + return true; + } + +private: + JPH::PhysicsSystem *m_pPhysicsSystem; + JoltPhysicsObject *m_pSelfObject; +}; + +uint32 JoltPhysicsPlayerController::GetContactState( uint16 nGameFlags ) +{ + // This does not seem to affect much, we should aspire to have our physics be as 1:1 to brush collisions as possible anyway +#ifdef GAME_PORTAL2_OR_NEWER + if ( !m_pObject->IsCollisionEnabled() ) + return 0; + + // Collector that finds the hit with the normal that is the most 'up' + class ContactStateCollector : public JPH::CollideShapeCollector + { + public: + ContactStateCollector( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pPlayerObject, uint16 nGameFlags ) + : m_pPhysicsSystem( pPhysicsSystem ) + , m_pPlayerObject( pPlayerObject ) + , m_nGameFlags( nGameFlags ) + { + } + + void AddHit( const JPH::CollideShapeResult &inResult ) override + { + JPH::BodyLockRead lock( m_pPhysicsSystem->GetBodyLockInterfaceNoLock(), inResult.mBodyID2 ); + const JPH::Body &body = lock.GetBody(); + + JoltPhysicsObject *pObject = reinterpret_cast( body.GetUserData() ); + + if ( !pObject->IsControlledByGame() ) + m_nFlagsOut |= PLAYER_CONTACT_PHYSICS; + + if ( pObject->GetGameFlags() & m_nGameFlags ) + m_nFlagsOut |= PLAYER_CONTACT_GAMEOBJECT; + } + + uint32 m_nFlagsOut = 0; + + private: + JPH::PhysicsSystem *m_pPhysicsSystem; + JoltPhysicsObject *m_pPlayerObject; + uint16 m_nGameFlags; + }; + + JPH::PhysicsSystem *pSystem = m_pObject->GetEnvironment()->GetPhysicsSystem(); + ContactStateCollector collector( pSystem, m_pObject, nGameFlags ); + SourceHitFilter filter( pSystem, m_pObject ); + CheckCollision( m_pObject, collector, filter ); + + return collector.m_nFlagsOut; +#else + return 0; +#endif +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsPlayerController::OnPreSimulate( float flDeltaTime ) +{ + VJoltAssertMsg( m_pObject->GetBody()->GetMotionType() == JPH::EMotionType::Kinematic, "Shadow controllers must be kinematic!" ); + +#if 0 + if ( m_pGround ) + { + JPH::Mat44 matrix = JPH::Mat44::sRotationTranslation( m_pObject->GetBody()->GetRotation(), m_pObject->GetBody()->GetPosition() ).Transposed3x3(); + m_groundPos = -matrix.Multiply3x3( m_targetPosition ); + + matrix3x4_t mat; + m_pGround->GetPositionMatrix( &mat ); + m_targetPosition = mat.TransformVector( m_GroundPos ); + + m_pGround->GetVelocityAtPoint( m_GroundPos, &groundVelocity ); + m_pObject->AddVelocity( -groundVelocity ); + } +#else + /*if ( m_pGround ) + { + JPH::Mat44 matrix = JPH::Mat44::sRotationTranslation( m_pObject->GetBody()->GetRotation(), m_pObject->GetBody()->GetPosition() ).Transposed3x3(); + JPH::Vec3 groundPos = -matrix.Multiply3x3( m_targetPosition ); + int g = 5; + m_targetPosition = matrix * groundPos; + }*/ +#endif + + // Apply downwards force to the ground + // This code mimics JoltObject::ApplyForceOffset but without Source > Jolt conversions + /*if ( m_pGround && m_pGround->IsMoveable() ) + { + JPH::PhysicsSystem *pPhysicsSystem = m_pGround->GetEnvironment()->GetPhysicsSystem(); + + JPH::BodyInterface &bodyInterface = pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.AddImpulse( m_pGround->GetBodyID(), pPhysicsSystem->GetGravity() * m_pObject->GetMass() * flDeltaTime, m_pObject->GetBody()->GetPosition() ); + }*/ + + JPH::PhysicsSystem *pPhysicsSystem = m_pObject->GetEnvironment()->GetPhysicsSystem(); + JPH::BodyInterface &bodyInterface = pPhysicsSystem->GetBodyInterfaceNoLock(); + + // Project ourselves towards our velocity + JPH::AnyHitCollisionCollector collector; + SourceHitFilter filter( pPhysicsSystem, m_pObject ); + CheckCollision( m_pObject, collector, filter ); + + if ( collector.HadHit() ) + { + JPH::BodyID otherID = collector.mHit.mBodyID2; + + //bodyInterface.AddImpulse( otherID, m_pObject->GetMass() * m_targetVelocity * flDeltaTime, m_pObject->GetBody()->GetPosition() ); + bodyInterface.AddImpulse( otherID, m_pObject->GetMass() * pPhysicsSystem->GetGravity() * flDeltaTime, m_pObject->GetBody()->GetPosition()); + } + + if ( m_secondsToArrival > 0.0f ) + bodyInterface.MoveKinematic( m_pObject->GetBodyID(), m_targetPosition, JPH::Quat::sIdentity(), m_secondsToArrival ); + else + { + bodyInterface.SetPositionAndRotation( m_pObject->GetBodyID(), m_targetPosition, JPH::Quat::sIdentity(), JPH::EActivation::Activate ); + bodyInterface.SetLinearAndAngularVelocity( m_pObject->GetBodyID(), JPH::Vec3::sZero(), JPH::Vec3::sZero() ); + } + + m_secondsToArrival = Max( m_secondsToArrival - flDeltaTime, 0.0f ); +} + +void JoltPhysicsPlayerController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + if ( pObject == m_pObject ) + { + SetObjectInternal( nullptr ); + } + if ( pObject == m_pGround ) + { + SetGround( nullptr ); + } +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsPlayerController::SetObjectInternal( JoltPhysicsObject *pObject ) +{ + if ( m_pObject == pObject ) + return; + + // Reset the last object + if ( m_pObject ) + { + // Don't bother resetting kinematic or sleep state, it does not matter because + // any object tied to a player controller was created to be a player object + m_pObject->RemoveDestroyedListener( this ); + m_pObject->RemoveCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER ); + } + + // Set our new object + m_pObject = pObject; + + // Adjust the new object + if ( m_pObject ) + { + // Set kinematic + m_pObject->GetBody()->SetMotionType( JPH::EMotionType::Kinematic ); + m_pObject->GetBody()->SetAllowSleeping( false ); + + m_pObject->AddDestroyedListener( this ); + m_pObject->AddCallbackFlags( CALLBACK_IS_PLAYER_CONTROLLER ); + } +} + +void JoltPhysicsPlayerController::SetGround( JoltPhysicsObject *pGround ) +{ + if ( m_pGround == pGround ) + return; + + if ( m_pGround ) + { + m_pGround->RemoveDestroyedListener( this ); + } + + // Set our new ground + m_pGround = pGround; + + if ( m_pGround ) + { + m_pGround->AddDestroyedListener( this ); + } +} diff --git a/vphysics_jolt/vjolt_controller_player.h b/vphysics_jolt/vjolt_controller_player.h new file mode 100644 index 0000000..b27cbdf --- /dev/null +++ b/vphysics_jolt/vjolt_controller_player.h @@ -0,0 +1,70 @@ + +#pragma once + +#include "vjolt_object.h" +#include "vjolt_environment.h" + +class JoltPhysicsPlayerController : public IPhysicsPlayerController, public IJoltObjectDestroyedListener, public IJoltPhysicsController +{ +public: + JoltPhysicsPlayerController( JoltPhysicsObject *pObject ); + ~JoltPhysicsPlayerController() override; + + void Update( const Vector &position, const Vector &velocity, float secondsToArrival, bool onground, IPhysicsObject *ground ) override; + void SetEventHandler( IPhysicsPlayerControllerEvent *handler ) override; + bool IsInContact( void ) override; + void MaxSpeed( const Vector &maxVelocity ) override; + + void SetObject( IPhysicsObject *pObject ) override; + + int GetShadowPosition( Vector *position, QAngle *angles ) override; + void StepUp( float height ) override; + void Jump() override; + void GetShadowVelocity( Vector *velocity ) override; + IPhysicsObject *GetObject() override; + void GetLastImpulse( Vector *pOut ) override; + + void SetPushMassLimit( float maxPushMass ) override; + void SetPushSpeedLimit( float maxPushSpeed ) override; + + float GetPushMassLimit() override; + float GetPushSpeedLimit() override; + + bool WasFrozen() override; + + uint32 GetContactState( uint16 nGameFlags ) override_portal2; + + // IJoltObjectDestroyedListener + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) override; + // IJoltPhysicsController + void OnPreSimulate( float flDeltaTime ) override; + +private: + void SetObjectInternal( JoltPhysicsObject *pObject ); + void SetGround( JoltPhysicsObject *pObject ); + +private: + JoltPhysicsObject *m_pObject = nullptr; + IPhysicsPlayerControllerEvent *m_pHandler = nullptr; + + JoltPhysicsObject *m_pGround = nullptr; + JPH::Vec3 m_groundPos = JPH::Vec3::sZero(); + + JPH::Vec3 m_targetPosition = JPH::Vec3::sZero(); // Where we want to be + JPH::Vec3 m_targetVelocity = JPH::Vec3::sZero(); // How we want to be + float m_secondsToArrival = FLT_EPSILON; // When we want to be + + float m_maxSpeed = 0.0f; + float m_maxDampSpeed = 0.0f; + float m_maxAngular = 0.0f; + float m_maxDampAngular = 0.0f; + float m_teleportDistance = 0.0f; + bool m_isPhysicallyControlled = false; // If true we're a bone follower on an NPC or something... + bool m_allowTranslation = false; // Should we translate? + bool m_allowRotation = false; // Should we rotate? + + float m_flPushableMassLimit = 1e4f; + float m_flPushableSpeedLimit = 1e4f; + + uint16 m_savedMaterialIndex = 0; +}; diff --git a/vphysics_jolt/vjolt_controller_shadow.cpp b/vphysics_jolt/vjolt_controller_shadow.cpp new file mode 100644 index 0000000..81c422a --- /dev/null +++ b/vphysics_jolt/vjolt_controller_shadow.cpp @@ -0,0 +1,171 @@ + +#include "cbase.h" + +#include "vjolt_controller_shadow.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsShadowController::JoltPhysicsShadowController( JoltPhysicsObject *pObject, bool allowTranslation, bool allowRotation ) + : m_pObject( pObject ), m_allowTranslation( allowTranslation ), m_allowRotation( allowRotation ) +{ + // Make our object kinematic + m_pObject->GetBody()->SetMotionType( JPH::EMotionType::Kinematic ); + + m_savedCallbackFlags = m_pObject->GetCallbackFlags(); + m_pObject->SetCallbackFlags( m_savedCallbackFlags | CALLBACK_SHADOW_COLLISION ); +} + +JoltPhysicsShadowController::~JoltPhysicsShadowController() +{ + if ( !( m_pObject->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE ) ) + { + m_pObject->SetCallbackFlags( m_savedCallbackFlags ); + } +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsShadowController::Update( const Vector &position, const QAngle &angles, float timeOffset ) +{ + // timeOffset == secondsToArrival + + JPH::Vec3 targetPosition = SourceToJolt::Distance( position ); + JPH::Quat targetRotation = SourceToJolt::Angle( angles ); + + if ( targetPosition.IsClose( m_targetPosition, 1e-8f ) && targetRotation.IsClose( m_targetRotation, 1e-8f ) ) + return; + + m_targetPosition = targetPosition; + m_targetRotation = targetRotation; + m_secondsToArrival = Max( timeOffset, 0.0f ); + m_enabled = true; +} + +void JoltPhysicsShadowController::MaxSpeed( float maxSpeed, float maxAngularSpeed ) +{ + m_maxSpeed = maxSpeed; + m_maxDampSpeed = maxSpeed; + m_maxAngular = maxAngularSpeed; + m_maxDampAngular = maxAngularSpeed; +} + +void JoltPhysicsShadowController::StepUp( float height ) +{ + if ( height == 0.0f ) + return; + + m_pObject->AddToPosition( JPH::Vec3( 0.0f, 0.0f, SourceToJolt::Distance( height ) ) ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsShadowController::SetTeleportDistance( float teleportDistance ) +{ + m_teleportDistance = SourceToJolt::Distance( teleportDistance ); +} + +bool JoltPhysicsShadowController::AllowsTranslation() +{ + return m_allowTranslation; +} + +bool JoltPhysicsShadowController::AllowsRotation() +{ + return m_allowRotation; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsShadowController::SetPhysicallyControlled( bool isPhysicallyControlled ) +{ + m_isPhysicallyControlled = isPhysicallyControlled; +} + +bool JoltPhysicsShadowController::IsPhysicallyControlled() +{ + return m_isPhysicallyControlled; +} + +void JoltPhysicsShadowController::GetLastImpulse( Vector *pOut ) +{ + //*pOut = JoltToSource::Distance( m_lastImpulse ); + VectorClear( *pOut ); +} + +// HACK HACK HACK WE MIGHT WANT TO CHANGE THIS +// IMPLEMENT ME! +static constexpr int ShadowMaterialIndex = 0xF000; + +void JoltPhysicsShadowController::UseShadowMaterial( bool bUseShadowMaterial ) +{ + if ( !m_pObject ) + return; + +#if 0 + int current = m_pObject->GetMaterialIndex(); + int target = bUseShadowMaterial ? ShadowMaterialIndex : m_savedMaterialIndex; + if ( target != current ) + m_pObject->SetMaterialIndex( target ); +#endif +} + +void JoltPhysicsShadowController::ObjectMaterialChanged( int materialIndex ) +{ + if ( !m_pObject ) + return; + + m_savedMaterialIndex = materialIndex; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsShadowController::GetTargetPosition( Vector *pPositionOut, QAngle *pAnglesOut ) +{ + if ( pPositionOut ) + *pPositionOut = JoltToSource::Distance( m_targetPosition ); + if ( pAnglesOut ) + *pAnglesOut = JoltToSource::Angle( m_targetRotation ); + + return m_secondsToArrival; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsShadowController::GetTeleportDistance() +{ + return JoltToSource::Distance( m_teleportDistance ); +} + +void JoltPhysicsShadowController::GetMaxSpeed( float *pMaxSpeedOut, float *pMaxAngularSpeedOut ) +{ + if ( pMaxSpeedOut ) + *pMaxSpeedOut = m_maxSpeed; + + if ( pMaxAngularSpeedOut ) + *pMaxAngularSpeedOut = m_maxAngular; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsShadowController::OnPreSimulate( float flDeltaTime ) +{ + if (!m_enabled) + return; + + VJoltAssertMsg( m_pObject->GetBody()->GetMotionType() == JPH::EMotionType::Kinematic, "Shadow controllers must be kinematic!" ); + + JPH::BodyInterface &bodyInterface = m_pObject->GetEnvironment()->GetPhysicsSystem()->GetBodyInterfaceNoLock(); + if ( m_secondsToArrival > 0.0f ) + bodyInterface.MoveKinematic( m_pObject->GetBodyID(), m_targetPosition, m_targetRotation, m_secondsToArrival ); + else + { + bodyInterface.SetPositionAndRotation( m_pObject->GetBodyID(), m_targetPosition, m_targetRotation, JPH::EActivation::Activate ); + bodyInterface.SetLinearAndAngularVelocity( m_pObject->GetBodyID(), JPH::Vec3::sZero(), JPH::Vec3::sZero() ); + m_enabled = false; + } + + m_secondsToArrival = Max( m_secondsToArrival - flDeltaTime, 0.0f ); +} diff --git a/vphysics_jolt/vjolt_controller_shadow.h b/vphysics_jolt/vjolt_controller_shadow.h new file mode 100644 index 0000000..107da80 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_shadow.h @@ -0,0 +1,55 @@ + +#pragma once + +#include "vjolt_object.h" +#include "vjolt_environment.h" + +class JoltPhysicsShadowController final : public IPhysicsShadowController, public IJoltPhysicsController +{ +public: + JoltPhysicsShadowController( JoltPhysicsObject *pObject, bool allowTranslation, bool allowRotation ); + ~JoltPhysicsShadowController() override; + + void Update( const Vector &position, const QAngle &angles, float timeOffset ) override; + void MaxSpeed( float maxSpeed, float maxAngularSpeed ) override; + void StepUp( float height ) override; + + void SetTeleportDistance( float teleportDistance ) override; + bool AllowsTranslation() override; + bool AllowsRotation() override; + + void SetPhysicallyControlled( bool isPhysicallyControlled ) override; + bool IsPhysicallyControlled() override; + void GetLastImpulse( Vector *pOut ) override; + void UseShadowMaterial( bool bUseShadowMaterial ) override; + void ObjectMaterialChanged( int materialIndex ) override; + + float GetTargetPosition( Vector *pPositionOut, QAngle *pAnglesOut ) override; + + float GetTeleportDistance() override; + void GetMaxSpeed( float *pMaxSpeedOut, float *pMaxAngularSpeedOut ) override; + + // IJoltPhysicsController + void OnPreSimulate( float flDeltaTime ) override; + +private: + JoltPhysicsObject *m_pObject = nullptr; + + JPH::Vec3 m_targetPosition = JPH::Vec3::sZero(); // Where we want to be + JPH::Quat m_targetRotation = JPH::Quat::sIdentity(); // How we want to be + float m_secondsToArrival = 0; // When we want to be + + float m_maxSpeed = 0.0f; + float m_maxDampSpeed = 0.0f; + float m_maxAngular = 0.0f; + float m_maxDampAngular = 0.0f; + float m_teleportDistance = 0.0f; + bool m_isPhysicallyControlled = false; // If true we're a bone follower on an NPC or something... + bool m_allowTranslation = false; // Should we translate? + bool m_allowRotation = false; // Should we rotate? + + bool m_enabled = false; + + uint16 m_savedMaterialIndex = 0; + uint16 m_savedCallbackFlags = 0; +}; diff --git a/vphysics_jolt/vjolt_controller_vehicle.cpp b/vphysics_jolt/vjolt_controller_vehicle.cpp new file mode 100644 index 0000000..a53e0c7 --- /dev/null +++ b/vphysics_jolt/vjolt_controller_vehicle.cpp @@ -0,0 +1,534 @@ +ο»Ώ +#include "cbase.h" + +#include "vjolt_layers.h" + +#include "vjolt_controller_vehicle.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------ + +static ConVar vjolt_vehicle_wheel_debug( "vjolt_vehicle_wheel_debug", "0", FCVAR_CHEAT ); + +static ConVar vjolt_vehicle_throttle_opposition_limit( "vjolt_vehicle_throttle_opposition_limit", "5", FCVAR_NONE, + "Below what speed should we be attempting to drive/climb with handbrake on to avoid falling down." ); + +//------------------------------------------------------------------------------------------------ + +static const JPH::Vec3 VehicleUpVector = JPH::Vec3( 0, 0, 1 ); +static const JPH::Vec3 VehicleForwardVector = JPH::Vec3( 0, 1, 0 ); + +static const char *VehicleTypeToName( unsigned int VehicleType ) +{ + switch ( VehicleType ) + { + case VEHICLE_TYPE_CAR_WHEELS: return "Car Wheels"; + case VEHICLE_TYPE_CAR_RAYCAST: return "Car Raycast"; + case VEHICLE_TYPE_JETSKI_RAYCAST: return "Jetski Raycast"; + case VEHICLE_TYPE_AIRBOAT_RAYCAST: return "Airboat Raycast"; + default: return "Unknown"; + } +} + +JPH::Ref< JPH::VehicleCollisionTester > CreateVehicleCollisionTester( unsigned int VehicleType, float LargestWheelRadius ) +{ + switch ( VehicleType ) + { + default: + Log_Warning( LOG_VJolt, "Don't know how to make vehicle type: %s (%u).\n", VehicleTypeToName( VehicleType ), VehicleType ); + [[ fallthrough ]]; + case VEHICLE_TYPE_CAR_WHEELS: + return new JPH::VehicleCollisionTesterCastSphere( Layers::MOVING, LargestWheelRadius, VehicleUpVector ); + } +} + +//------------------------------------------------------------------------------------------------ + +JoltPhysicsVehicleController::JoltPhysicsVehicleController( JoltPhysicsEnvironment* pEnvironment, JPH::PhysicsSystem* pPhysicsSystem, JoltPhysicsObject* pVehicleBodyObject, const vehicleparams_t& params, unsigned int nVehicleType, IPhysicsGameTrace* pGameTrace ) + : m_pEnvironment( pEnvironment ) + , m_pPhysicsSystem( pPhysicsSystem ) + , m_pCarBodyObject( pVehicleBodyObject ) + , m_VehicleType( nVehicleType ) + , m_VehicleParams( params ) +{ + JPH::VehicleConstraintSettings vehicle; + vehicle.mUp = VehicleUpVector; + vehicle.mForward = VehicleForwardVector; + vehicle.mDrawConstraintSize = 0.1f; + CreateWheels( vehicle ); + vehicle.mController = CreateVehicleController(); + + m_Tester = CreateVehicleCollisionTester( nVehicleType, m_InternalState.LargestWheelRadius ); + + m_pCarBodyObject->AddDestroyedListener( this ); + m_VehicleConstraint = new JPH::VehicleConstraint( *m_pCarBodyObject->GetBody(), vehicle ); + m_pPhysicsSystem->AddConstraint( m_VehicleConstraint ); + m_pPhysicsSystem->AddStepListener( m_VehicleConstraint ); +} + +JoltPhysicsVehicleController::~JoltPhysicsVehicleController() +{ + DetachObject(); + + for ( auto &wheel : m_Wheels ) + m_pEnvironment->DestroyObject( wheel.pObject ); + m_Wheels.clear(); +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::Update( float dt, vehicle_controlparams_t &controls ) +{ + m_ControlParams = controls; + + UpdateBooster( dt ); + + HandleBoostKey(); +} + +const vehicle_operatingparams_t &JoltPhysicsVehicleController::GetOperatingParams() +{ + return m_OperatingParams; +} + +const vehicleparams_t &JoltPhysicsVehicleController::GetVehicleParams() +{ + return m_VehicleParams; +} + +vehicleparams_t &JoltPhysicsVehicleController::GetVehicleParamsForChange() +{ + return m_VehicleParams; +} + +float JoltPhysicsVehicleController::UpdateBooster( float dt ) +{ + m_InternalState.BoostDelay = Max( m_InternalState.BoostDelay - dt, 0.0f ); + m_InternalState.BoosterRemainingTime = Max( m_InternalState.BoosterRemainingTime - dt, 0.0f ); + + return m_InternalState.BoostDelay; +} + +int JoltPhysicsVehicleController::GetWheelCount() +{ + return int( m_Wheels.size() ); +} + +IPhysicsObject *JoltPhysicsVehicleController::GetWheel( int index ) +{ + if ( index >= int( m_Wheels.size() ) ) + return nullptr; + + return m_Wheels[ index ].pObject; +} + +bool JoltPhysicsVehicleController::GetWheelContactPoint( int index, Vector *pContactPoint, int *pSurfaceProps ) +{ + if ( index < int( m_Wheels.size() ) && m_VehicleConstraint->GetWheels()[ index ]->HasContact() ) + { + if ( pContactPoint ) + *pContactPoint = JoltToSource::Distance( m_VehicleConstraint->GetWheels()[ index ]->GetContactPosition() ); + + // TODO(Josh): This! + if ( pSurfaceProps ) + *pSurfaceProps = 0; + + return true; + } + else + { + if ( pContactPoint ) + *pContactPoint = vec3_origin; + + if ( pSurfaceProps ) + *pSurfaceProps = 0; + + return false; + } +} + +void JoltPhysicsVehicleController::SetSpringLength( int wheelIndex, float length ) +{ + +} + +void JoltPhysicsVehicleController::SetWheelFriction( int wheelIndex, float friction ) +{ + +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsVehicleController::OnVehicleEnter() +{ + // Undo any damping we may have set to slow the boat when + // we got out. + if ( m_VehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + float flDampSpeed = 0.0f; + float flDampRotSpeed = 0.0f; + m_pCarBodyObject->SetDamping( &flDampSpeed, &flDampRotSpeed ); + } +} + +void JoltPhysicsVehicleController::OnVehicleExit() +{ + // If we are an airboat, set a bunch of damping to slow us down. + if ( m_VehicleType == VEHICLE_TYPE_AIRBOAT_RAYCAST ) + { + float flDampSpeed = 1.0f; + float flDampRotSpeed = 1.0f; + m_pCarBodyObject->SetDamping( &flDampSpeed, &flDampRotSpeed ); + } + + SetEngineDisabled( false ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsVehicleController::SetEngineDisabled( bool bDisable ) +{ + m_InternalState.EngineDisabled = bDisable; +} + +bool JoltPhysicsVehicleController::IsEngineDisabled() +{ + return m_InternalState.EngineDisabled; +} + +void JoltPhysicsVehicleController::GetCarSystemDebugData( vehicle_debugcarsystem_t &debugCarSystem ) +{ + +} + +void JoltPhysicsVehicleController::VehicleDataReload() +{ + +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsVehicleController::GetSpeed() +{ + const Vector orientation = GetColumn( GetBodyMatrix(), MatrixAxis::Left ); + return orientation.Dot( m_pCarBodyObject->GetVelocity() ); +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::HandleBoostKey() +{ + // Handle triggering boosting if the key is pressed and we aren't currently boosting or in cooldown. + if ( m_ControlParams.boost && !m_InternalState.BoostDelay && !m_InternalState.BoosterRemainingTime ) + { + m_InternalState.BoosterRemainingTime = m_VehicleParams.engine.boostDuration; + m_InternalState.BoostDelay = m_VehicleParams.engine.boostDuration + m_VehicleParams.engine.boostDelay; + } +} + +void JoltPhysicsVehicleController::HandleBoostDecay() +{ + // Decay the boost time if we are currently boosting or have a delay. + if ( m_VehicleParams.engine.boostDuration || m_VehicleParams.engine.boostDelay ) + { + m_OperatingParams.boostTimeLeft = m_InternalState.BoostDelay + ? 100.0f - ( 100.0f * ( m_InternalState.BoostDelay / ( m_VehicleParams.engine.boostDuration + m_VehicleParams.engine.boostDelay ) ) ) + : 100.0f; + } +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::OnPreSimulate( float flDeltaTime ) +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + // With any user input, assure that the car is active + if ( m_ControlParams.steering != 0.0f || m_ControlParams.throttle != 0.0f || m_ControlParams.brake != 0.0f || m_ControlParams.handbrake ) + bodyInterface.ActivateBody( m_pCarBodyObject->GetBodyID() ); + + bool bHandbrake = m_ControlParams.handbrake; + + // Don't throttle when holding handbrake (like Source) + float flThrottle = bHandbrake ? 0.0f : m_ControlParams.throttle; + + // Apply a little brake without throttle to stop the vehicle from coasting (like Source). + const bool bCoasting = flThrottle == 0.0f && m_ControlParams.brake == 0.0f && !bHandbrake; + const float flBrake = bCoasting ? 0.1f : m_ControlParams.brake; + + const float ThrottleOpositionSpeed = vjolt_vehicle_throttle_opposition_limit.GetFloat(); + + // Enable the handbrake when going at low speeds to avoid slipping when going up hill. + if ( ( flThrottle < 0.0f && m_OperatingParams.speed > ThrottleOpositionSpeed ) || + ( flThrottle > 0.0f && m_OperatingParams.speed < -ThrottleOpositionSpeed ) ) + bHandbrake = true; + + // Are we boosting? + float flTotalTorqueMultiplier = 1.0f; + if ( m_InternalState.BoosterRemainingTime != 0.0f ) + { + GetWheeledVehicleController()->GetEngine().SetCurrentRPM(m_VehicleParams.engine.maxRPM); + // Slam the throttle to 1, neeeowm! + m_ControlParams.throttle = 1.0f; + flThrottle = 1.0f; + + const float flSpeedFactor = RemapValClamped( fabsf( m_OperatingParams.speed ), 0, m_VehicleParams.engine.maxSpeed, 0.1f, 1.0f ); + const float flTurnFactor = 1.0f - ( fabsf( m_ControlParams.steering ) * 0.95f ); + // Josh: * 2 as the original torque stuff in Source was based around 0.5 being the max, and 1.0 being boost. + const float flDampedBoost = 2.0f * m_VehicleParams.engine.boostForce * flSpeedFactor * flTurnFactor; + + if ( flDampedBoost > flTotalTorqueMultiplier ) + flTotalTorqueMultiplier = flDampedBoost; + } + + // Update the torque factors as we may be boosting and be > 1. + // TODO(Josh): More than 2 wheels per axle. + VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 ); + for ( int i = 0; i < m_VehicleParams.axleCount; i++ ) + GetWheeledVehicleController()->GetDifferentials()[i].mEngineTorqueRatio = flTotalTorqueMultiplier * m_VehicleParams.axles[i].torqueFactor; + + // Pass the input on to the constraint + GetWheeledVehicleController()->SetDriverInput( flThrottle, m_ControlParams.steering, flBrake, bHandbrake ? 1.0f : 0.0f ); + + // Set the collision tester + m_VehicleConstraint->SetVehicleCollisionTester( m_Tester ); +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::OnPostSimulate( float flDeltaTime ) +{ + // Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step) + + float flSteeringAngle = 0.0f; + m_OperatingParams.wheelsInContact = 0; + m_OperatingParams.wheelsNotInContact = 0; + for ( int w = 0; w < GetWheelCount(); w++ ) + { + const JPH::WheelSettings *settings = m_VehicleConstraint->GetWheels()[w]->GetSettings(); + // The cyclinder we draw is aligned with Y so we specify that as rotational axis + JPH::Mat44 wheelTransform = m_VehicleConstraint->GetWheelWorldTransform( w, JPH::Vec3( 1, 0, 0 ), JPH::Vec3( 0, 0, 1 ) ); + + // Find our greatest steering angle. + float flWheelSteeringAngle = JoltToSource::Angle( m_VehicleConstraint->GetWheels()[w]->GetSteerAngle() ); + if ( fabsf( flWheelSteeringAngle ) > fabsf( flSteeringAngle ) ) + flSteeringAngle = flWheelSteeringAngle; + + Vector newPos = JoltToSource::Distance( wheelTransform.GetTranslation() ); + // TODO(Josh): This triggers JPH_ASSERT(mCol[3] == Vec4(0, 0, 0, 1)); + // what to do about that?.. + // We just want the local rotation, and this seems to work (?) + QAngle newQuat = JoltToSource::Angle( wheelTransform.GetQuaternion() ); + m_Wheels[ w ].pObject->EnableCollisions( false ); + // Set dummy wheel object pos/angles so the game code can update pose positions for wheels. + m_Wheels[ w ].pObject->SetPosition( newPos, newQuat, true ); + // Wake it up so that the game bothers to do pose positions. + m_Wheels[ w ].pObject->Wake(); + + if ( m_VehicleConstraint->GetWheels()[w]->HasContact() ) + m_OperatingParams.wheelsInContact++; + else + m_OperatingParams.wheelsNotInContact++; + + IVJoltDebugOverlay *pDebugOverlay = JoltPhysicsInterface::GetInstance().GetDebugOverlay(); + if ( vjolt_vehicle_wheel_debug.GetBool() && pDebugOverlay ) + { + const Vector vecWheelPos = JoltToSource::Distance( wheelTransform.GetTranslation() ); + const Vector vecWheelSize = JoltToSource::Distance( JPH::Vec3( settings->mWidth / 2.0f, settings->mRadius, settings->mRadius ) ); + + pDebugOverlay->AddBoxOverlay( + vecWheelPos, + -vecWheelSize, vecWheelSize, + newQuat, + 255, 0, 255, 100, + -1.0f ); + } + } + + m_OperatingParams.gear = GetWheeledVehicleController()->GetTransmission().GetCurrentGear(); + m_OperatingParams.engineRPM = GetWheeledVehicleController()->GetEngine().GetCurrentRPM(); + m_OperatingParams.speed = GetSpeed(); + m_OperatingParams.steeringAngle = -flSteeringAngle; + m_OperatingParams.boostDelay = m_InternalState.BoostDelay; + HandleBoostDecay(); +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::CreateWheel( JPH::VehicleConstraintSettings &vehicleSettings, matrix3x4_t& bodyMatrix, int axleIdx, int wheelIdx ) +{ + const vehicle_axleparams_t &axle = m_VehicleParams.axles[ axleIdx ]; + + const Vector wheelPositionLocal = axle.offset + + ( ( wheelIdx % 2 == 1 ) ? axle.wheelOffset : -axle.wheelOffset ); + + Vector wheelPositionWorld; + VectorTransform( wheelPositionLocal, bodyMatrix, wheelPositionWorld ); + + // Josh: Good enough heuristic. + const float wheelRadius = axle.wheels.radius; + const float wheelWidth = wheelRadius / 2.0f; + + // Josh: Area of a cylinder = Ο€.h.r^2 + // Using radius in terms of Source units as we pass this to CreateSphereObject. + const float wheelVolume = M_PI * wheelWidth * Cube( wheelRadius ); + + { + objectparams_t wheelParams = + { + .mass = axle.wheels.mass, + .inertia = axle.wheels.inertia, + .damping = axle.wheels.damping, + .rotdamping = axle.wheels.rotdamping, + .pName = "VehicleWheel", + .pGameData = m_pCarBodyObject->GetGameData(), + .volume = wheelVolume, + }; + IPhysicsObject *pWheelObject = m_pEnvironment->CreateSphereObject( + wheelRadius, axle.wheels.materialIndex, + wheelPositionWorld, QAngle(), + &wheelParams, false ); + + JoltPhysicsObject *pJoltWheelObject = static_cast< JoltPhysicsObject * >( pWheelObject ); + + pJoltWheelObject->SetGameFlags( m_pCarBodyObject->GetGameFlags() ); + pJoltWheelObject->SetCallbackFlags( CALLBACK_IS_VEHICLE_WHEEL ); + // Josh: The wheel is a fake object, so disable collisions on it. + pJoltWheelObject->EnableCollisions( false ); + + m_Wheels.push_back( JoltPhysicsWheel{ .pObject = pJoltWheelObject } ); + } + + const float steeringAngle = DEG2RAD( Max( m_VehicleParams.steering.degreesSlow, m_VehicleParams.steering.degreesFast ) ); + const float additionalLength = SourceToJolt::Distance( axle.wheels.springAdditionalLength ); + + JPH::WheelSettingsWV *wheelSettings = new JPH::WheelSettingsWV; + wheelSettings->mPosition = SourceToJolt::Distance( wheelPositionLocal ); + wheelSettings->mDirection = JPH::Vec3( 0, 0, -1 ); + wheelSettings->mAngularDamping = axle.wheels.rotdamping; + // TODO(Josh): What about more than 4 wheels? + wheelSettings->mMaxSteerAngle = axleIdx == 0 ? steeringAngle : 0.0f; + wheelSettings->mRadius = SourceToJolt::Distance( axle.wheels.radius ); + wheelSettings->mWidth = SourceToJolt::Distance( wheelWidth ); + wheelSettings->mInertia = 0.5f * axle.wheels.mass * ( wheelSettings->mRadius * wheelSettings->mRadius ); + wheelSettings->mSuspensionMinLength = 0; + wheelSettings->mSuspensionMaxLength = additionalLength; + wheelSettings->mSuspensionDamping = axle.suspension.springDamping; + // Josh: + // so to go from K (Spring Constant) -> freq we do + // sqrtf( K / Mass ) / ( 2.0f * PI ) + // but it seems like it already has mass divided in Source so... + // sqrtf( K ) / ( 2.0f * PI ) + wheelSettings->mSuspensionFrequency = sqrtf( axle.suspension.springConstant ) / ( 2.0f * M_PI_F ); + // Josh: I don't know why but it looks and feels really wrong without this: + // TODO(Josh): Investigate more later, doesn't make much sense. + // May be related to mass of wheel or something. + wheelSettings->mSuspensionFrequency *= M_PI_F; + if ( axle.wheels.frictionScale ) + { + wheelSettings->mLateralFriction.AddPoint( 1.0f, axle.wheels.frictionScale ); + wheelSettings->mLongitudinalFriction.AddPoint( 1.0f, axle.wheels.frictionScale ); + } + + vehicleSettings.mWheels.push_back( wheelSettings ); + m_InternalState.LargestWheelRadius = Max( m_InternalState.LargestWheelRadius, SourceToJolt::Distance( wheelWidth ) ); +} + +void JoltPhysicsVehicleController::CreateWheels( JPH::VehicleConstraintSettings &vehicleSettings ) +{ + matrix3x4_t carBodyMtx = GetBodyMatrix(); + + m_Wheels.reserve( m_VehicleParams.axleCount * m_VehicleParams.wheelsPerAxle ); + vehicleSettings.mAntiRollBars.reserve( m_VehicleParams.axleCount ); + + for ( int axle = 0; axle < m_VehicleParams.axleCount; axle++ ) + { + for ( int wheel = 0; wheel < m_VehicleParams.wheelsPerAxle; wheel++ ) + CreateWheel( vehicleSettings, carBodyMtx, axle, wheel ); + + // TODO(Josh): More than 2 wheels per axle. + VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 ); + JPH::VehicleAntiRollBar rollbar; + rollbar.mLeftWheel = ( axle * m_VehicleParams.wheelsPerAxle ); + rollbar.mRightWheel = ( axle * m_VehicleParams.wheelsPerAxle ) + 1; + vehicleSettings.mAntiRollBars.push_back( rollbar ); + } +} + +JPH::WheeledVehicleControllerSettings *JoltPhysicsVehicleController::CreateVehicleController() +{ + static constexpr float HorsePowerToWatts = 745.7f; + + JPH::WheeledVehicleControllerSettings *pController = new JPH::WheeledVehicleControllerSettings; + // Josh: + // T = ( 745.7 * P ) / ( 2 * PI * ( RPM / 60 ) ) + pController->mEngine.mMaxTorque = ( HorsePowerToWatts * m_VehicleParams.engine.horsepower ) / ( 2.0f * M_PI * ( m_VehicleParams.engine.maxRPM / 60.0f ) ); + // Josh: Fudge + pController->mEngine.mMinRPM = Max( m_VehicleParams.engine.shiftDownRPM - 300, 0.0f ); + pController->mEngine.mMaxRPM = m_VehicleParams.engine.maxRPM; + pController->mEngine.mAngularDamping = 0.0f; + + pController->mTransmission.mMode = m_VehicleParams.engine.isAutoTransmission ? JPH::ETransmissionMode::Auto : JPH::ETransmissionMode::Manual; + pController->mTransmission.mGearRatios.clear(); + for ( int i = 0; i < m_VehicleParams.engine.gearCount; i++ ) + pController->mTransmission.mGearRatios.push_back( m_VehicleParams.engine.gearRatio[ i ] ); + + pController->mTransmission.mReverseGearRatios.clear(); + pController->mTransmission.mReverseGearRatios.push_back( -m_VehicleParams.engine.gearRatio[0] ); + + pController->mTransmission.mShiftUpRPM = m_VehicleParams.engine.shiftUpRPM; + pController->mTransmission.mShiftDownRPM = m_VehicleParams.engine.shiftDownRPM; + + pController->mDifferentials.reserve( m_VehicleParams.axleCount ); + for ( int i = 0; i < m_VehicleParams.axleCount; i++ ) + { + // TODO(Josh): More than 2 wheels per axle. + VJoltAssert( m_VehicleParams.wheelsPerAxle == 2 ); + JPH::VehicleDifferentialSettings differential; + differential.mLeftWheel = ( i * m_VehicleParams.wheelsPerAxle ); + differential.mRightWheel = ( i * m_VehicleParams.wheelsPerAxle ) + 1; + differential.mEngineTorqueRatio = m_VehicleParams.axles[ i ].torqueFactor; + + pController->mDifferentials.push_back( differential ); + } + + return pController; +} + +JPH::WheeledVehicleController *JoltPhysicsVehicleController::GetWheeledVehicleController() +{ + return static_cast( m_VehicleConstraint->GetController() ); +} + +//------------------------------------------------------------------------------------------------ + +matrix3x4_t JoltPhysicsVehicleController::GetBodyMatrix() const +{ + matrix3x4_t value; + m_pCarBodyObject->GetPositionMatrix( &value ); + return value; +} + +//------------------------------------------------------------------------------------------------ + +void JoltPhysicsVehicleController::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + if ( m_pCarBodyObject == pObject ) + DetachObject(); +} + +void JoltPhysicsVehicleController::DetachObject() +{ + if ( m_pCarBodyObject ) + { + m_pCarBodyObject->RemoveDestroyedListener( this ); + + // Remove the listeners and constraint now, we can never + // attach to another body. + m_pPhysicsSystem->RemoveConstraint( m_VehicleConstraint ); + m_pPhysicsSystem->RemoveStepListener( m_VehicleConstraint ); + + m_pCarBodyObject = nullptr; + } +} diff --git a/vphysics_jolt/vjolt_controller_vehicle.h b/vphysics_jolt/vjolt_controller_vehicle.h new file mode 100644 index 0000000..b9cd03f --- /dev/null +++ b/vphysics_jolt/vjolt_controller_vehicle.h @@ -0,0 +1,92 @@ + +#pragma once + +#include "vjolt_object.h" // IJoltObjectDestroyedListener +#include "vjolt_environment.h" // IJoltPhysicsController + +struct JoltPhysicsWheel +{ + JoltPhysicsObject* pObject = nullptr; + bool InWater = false; + float Depth = 0.0f; +}; + +struct JoltPhysicsInternalVehicleState +{ + bool EngineDisabled = false; + float BoostDelay = 0.0f; + float BoosterRemainingTime = 0.0f; + float LargestWheelRadius = 0.0f; +}; + +class JoltPhysicsVehicleController final : public IPhysicsVehicleController, public IJoltObjectDestroyedListener, public IJoltPhysicsController +{ +public: + static constexpr int MaxWheels = VEHICLE_MAX_WHEEL_COUNT; + + JoltPhysicsVehicleController( JoltPhysicsEnvironment *pEnvironment, JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pVehicleBodyObject, const vehicleparams_t ¶ms, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace ); + ~JoltPhysicsVehicleController() override; + + void Update( float dt, vehicle_controlparams_t &controls ) override; + const vehicle_operatingparams_t & GetOperatingParams() override; + const vehicleparams_t & GetVehicleParams() override; + vehicleparams_t & GetVehicleParamsForChange() override; + float UpdateBooster( float dt ) override; + int GetWheelCount( void ) override; + IPhysicsObject * GetWheel( int index ) override; + bool GetWheelContactPoint( int index, Vector *pContactPoint, int *pSurfaceProps ) override; + void SetSpringLength( int wheelIndex, float length ) override; + void SetWheelFriction( int wheelIndex, float friction ) override; + + void OnVehicleEnter( void ) override; + void OnVehicleExit( void ) override; + + void SetEngineDisabled( bool bDisable ) override; + bool IsEngineDisabled( void ) override; + + void GetCarSystemDebugData( vehicle_debugcarsystem_t &debugCarSystem ) override; + void VehicleDataReload() override; + +public: + // IJoltObjectDestroyedListener + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) override; + + float GetSpeed(); + + // IJoltPhysicsController + void OnPreSimulate( float flDeltaTime ) override; + void OnPostSimulate( float flDeltaTime ) override; + +private: + + void HandleBoostKey(); + void HandleBoostDecay(); + + void CreateWheel( JPH::VehicleConstraintSettings &vehicleSettings, matrix3x4_t &bodyMatrix, int axleIdx, int wheelIdx ); + void CreateWheels( JPH::VehicleConstraintSettings& vehicleSettings ); + + JPH::WheeledVehicleControllerSettings *CreateVehicleController(); + JPH::WheeledVehicleController *GetWheeledVehicleController(); + + matrix3x4_t GetBodyMatrix() const; + + void DetachObject(); + + JoltPhysicsEnvironment *m_pEnvironment = nullptr; + JPH::PhysicsSystem *m_pPhysicsSystem = nullptr; + JoltPhysicsObject *m_pCarBodyObject = nullptr; + vehicleparams_t m_VehicleParams = {}; + unsigned int m_VehicleType = 0u; + + vehicle_operatingparams_t m_OperatingParams = {}; + vehicle_controlparams_t m_ControlParams = {}; + + std::vector< JoltPhysicsWheel > m_Wheels; + + JoltPhysicsInternalVehicleState m_InternalState; + + JPH::Ref< JPH::VehicleConstraint > m_VehicleConstraint; + JPH::Ref< JPH::VehicleCollisionTester > m_Tester; + + +}; diff --git a/vphysics_jolt/vjolt_debugrender.cpp b/vphysics_jolt/vjolt_debugrender.cpp new file mode 100644 index 0000000..558d3a0 --- /dev/null +++ b/vphysics_jolt/vjolt_debugrender.cpp @@ -0,0 +1,289 @@ +//================================================================================================= +// +// Jolt Debug Renderer Implementation +// +//================================================================================================= + +#include "cbase.h" + +#ifdef JPH_DEBUG_RENDERER + +#include + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" + +#include "vjolt_debugrender.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Slart: Not sure if this is still relevant, +// it's the amount of time a debugoverlay element should stay on-screen +#define ONE_SINGLE_FRAME 0.0f + +#define JOLT_VERTEX_BUFFER_NAME "Jolt Debug Renderer Vertices" +#define JOLT_INDEX_BUFFER_NAME "Jolt Debug Renderer Indices" + +static ConVar vjolt_debugrender( "vjolt_debugrender", "0", FCVAR_CHEAT ); +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY +static ConVar vjolt_debugrender_picture_in_picture( "vjolt_debugrender_picture_in_picture", "1" ); +static ConVar vjolt_debugrender_clear_rt( "vjolt_debugrender_clear_rt", "1" ); +static ConVar vjolt_debugrender_clear_depth( "vjolt_debugrender_clear_depth", "1" ); +static ConVar vjolt_debugrender_wireframe( "vjolt_debugrender_wireframe", "0" ); +static ConVar vjolt_debugrender_color_mode( "vjolt_debugrender_color_mode", "instance", 0, "One of instance, shape_type, motion_type, sleep, island, material." ); +#endif + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsDebugRenderer::JoltPhysicsDebugRenderer() +{ + DebugRenderer::Initialize(); +} + +JoltPhysicsDebugRenderer::~JoltPhysicsDebugRenderer() +{ +} + +void JoltPhysicsDebugRenderer::DrawLine( const JPH::Float3& inFrom, const JPH::Float3& inTo, JPH::ColorArg inColor ) +{ + Vector v1 = JoltToSource::Distance( inFrom ); + Vector v2 = JoltToSource::Distance( inTo ); + +#ifdef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + GetDebugOverlay()->AddLineOverlay( v1, v2, inColor.r, inColor.g, inColor.b, true, ONE_SINGLE_FRAME ); +#else + GetDebugOverlay()->AddLineOverlay( v1, v2, inColor.r, inColor.g, inColor.b, inColor.a, true, 0.2f ); +#endif +} + +void JoltPhysicsDebugRenderer::DrawTriangle( JPH::Vec3Arg inV1, JPH::Vec3Arg inV2, JPH::Vec3Arg inV3, JPH::ColorArg inColor ) +{ + //DrawTriangle_Internal( JPH::Float3( inV1.GetX(), inV1.GetY(), inV1.GetZ() ), JPH::Float3( inV2.GetX(), inV2.GetY(), inV2.GetZ() ), JPH::Float3( inV3.GetX(), inV3.GetY(), inV3.GetZ() ), inColor ); +} + +JoltPhysicsDebugRenderer::Batch JoltPhysicsDebugRenderer::CreateTriangleBatch( const Triangle* inTriangles, int inTriangleCount ) +{ +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + const Vertex* inVertices = reinterpret_cast( inTriangles ); + int inVertexCount = inTriangleCount * 3; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + constexpr VertexFormat_t fmt = VERTEX_POSITION | VERTEX_NORMAL | VERTEX_COLOR | VERTEX_TEXCOORD_SIZE(0, 2); + + IMesh* pMesh = pRenderContext->CreateStaticMesh( fmt, JOLT_VERTEX_BUFFER_NAME ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, inTriangleCount ); + { + for (int i = 0; i < inVertexCount; ++i) + { + meshBuilder.Position3f ( inVertices[i].mPosition.x * JoltToSource::Factor, inVertices[i].mPosition.y * JoltToSource::Factor, inVertices[i].mPosition.z * JoltToSource::Factor ); + meshBuilder.Normal3f ( inVertices[i].mNormal.x, inVertices[i].mNormal.y, inVertices[i].mNormal.z ); + meshBuilder.TexCoord2f ( 0, inVertices[i].mUV.x, inVertices[i].mUV.y ); + meshBuilder.Color4Packed( inVertices[i].mColor.mU32 ); + meshBuilder.AdvanceVertex(); + } + } + meshBuilder.End(); + + return new BatchImpl( pMesh ); +#else + return nullptr; +#endif +} + +JoltPhysicsDebugRenderer::Batch JoltPhysicsDebugRenderer::CreateTriangleBatch( const Vertex* inVertices, int inVertexCount, const uint32* inIndices, int inIndexCount ) +{ +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + constexpr VertexFormat_t fmt = VERTEX_POSITION | VERTEX_NORMAL | VERTEX_COLOR | VERTEX_TEXCOORD_SIZE(0, 2); + + IMesh* pMesh = pRenderContext->CreateStaticMesh( fmt, JOLT_VERTEX_BUFFER_NAME ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, inVertexCount, inIndexCount ); + { + for (int i = 0; i < inVertexCount; ++i) + { + meshBuilder.Position3f ( inVertices[i].mPosition.x * JoltToSource::Factor, inVertices[i].mPosition.y * JoltToSource::Factor, inVertices[i].mPosition.z * JoltToSource::Factor ); + meshBuilder.Normal3f ( inVertices[i].mNormal.x, inVertices[i].mNormal.y, inVertices[i].mNormal.z ); + meshBuilder.TexCoord2f ( 0, inVertices[i].mUV.x, inVertices[i].mUV.y ); + meshBuilder.Color4Packed( inVertices[i].mColor.mU32 ); + meshBuilder.AdvanceVertex(); + } + + for (int i = 0; i < inIndexCount; ++i) + { + meshBuilder.Index( static_cast( inIndices[i] ) ); + meshBuilder.AdvanceIndex(); + } + } + meshBuilder.End(); + + return new BatchImpl( pMesh ); +#else + return nullptr; +#endif +} + +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY +MaterialCullMode_t ConvertCullMode( JPH::DebugRenderer::ECullMode mode ) +{ + switch (mode) + { + case JPH::DebugRenderer::ECullMode::CullFrontFace: + return MATERIAL_CULLMODE_CCW; + case JPH::DebugRenderer::ECullMode::CullBackFace: + return MATERIAL_CULLMODE_CW; + default: // case JPH::DebugRenderer::ECullMode::Off: + return MATERIAL_CULLMODE_NONE; + } +} +#endif + +void JoltPhysicsDebugRenderer::DrawGeometry( JPH::Mat44Arg inModelMatrix, const JPH::AABox& inWorldSpaceBounds, float inLODScaleSq, JPH::ColorArg inModelColor, const GeometryRef& inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode ) +{ +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + matrix3x4_t sourceMatrix = JoltToSource::Matrix( inModelMatrix ); + + const BatchImpl* batch = reinterpret_cast< const BatchImpl * >( inGeometry->mLODs[0].mTriangleBatch.GetPtr() ); + + color32 clearColor = { .r = 20, .g = 20, .b = 20, .a = 255 }; + + color32 modulateColor = JoltToSource::Color( inModelColor ).ToColor32(); + + bool bForceWireFrame = vjolt_debugrender_wireframe.GetInt() == 2; + + DebugOverlayMeshDesc_t desc = + { + .pMesh = batch->GetMesh(), + .matTransform = sourceMatrix, + .flDuration = -1.0f, + .bIgnoreZ = false, + .bWireframe = inDrawMode == EDrawMode::Wireframe || bForceWireFrame, + .bClearRT = vjolt_debugrender_clear_rt.GetBool(), + .bClearDepth = vjolt_debugrender_clear_depth.GetBool(), + .colClearColor = clearColor, + .colModulateColor = modulateColor, + .eCullMode = ConvertCullMode( inCullMode ), + .bPip = vjolt_debugrender_picture_in_picture.GetBool() + }; + + GetDebugOverlay()->DrawMesh( desc ); +#endif +} + +void JoltPhysicsDebugRenderer::DrawText3D( JPH::Vec3Arg inPosition, const std::string_view &inString, JPH::ColorArg inColor, float inHeight ) +{ + // Josh: + // Doing a copy of 1024, the max size allowed by a debug overlay + // because AddTextOverlayRGB takes in a c_str and a string view + // is not necessarily null terminated. + char text[1024]; + V_strncpy( text, inString.data(), sizeof( text ) ); + + Vector origin = JoltToSource::Distance( inPosition ); + + GetDebugOverlay()->AddTextOverlayRGB( origin, 0, 0.5f, inColor.r, inColor.g, inColor.b, inColor.a, "%s", text ); +} + +void JoltPhysicsDebugRenderer::DrawJoltTVText() +{ + GetDebugOverlay()->AddScreenTextOverlay( 0.75f, 0.55f, 0.5f, 255, 0, 255, 255, "Live Jolt Reaction" ); // "Jolt TV" +} + +//------------------------------------------------------------------------------------------------- + +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + +static JPH::BodyManager::EShapeColor GetColorMode() +{ + const char *pszString = vjolt_debugrender_color_mode.GetString(); + + if ( !V_stricmp( pszString, "instance" ) ) + return JPH::BodyManager::EShapeColor::InstanceColor; + else if ( !V_stricmp( pszString, "shape_type" ) ) + return JPH::BodyManager::EShapeColor::ShapeTypeColor; + else if ( !V_stricmp( pszString, "motion_type" ) ) + return JPH::BodyManager::EShapeColor::MotionTypeColor; + else if ( !V_stricmp( pszString, "sleep" ) ) + return JPH::BodyManager::EShapeColor::SleepColor; + else if ( !V_stricmp( pszString, "island" ) ) + return JPH::BodyManager::EShapeColor::IslandColor; + else if ( !V_stricmp( pszString, "material" ) ) + return JPH::BodyManager::EShapeColor::MaterialColor; + + Log_Msg( LOG_VJolt, "Unknown color mode: %s\n", pszString ); + return JPH::BodyManager::EShapeColor::InstanceColor; +} + +#endif + +void JoltPhysicsDebugRenderer::RenderPhysicsSystem( JPH::PhysicsSystem &physicsSystem ) +{ + if ( !GetDebugOverlay() || !vjolt_debugrender.GetBool()) + return; + +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + const bool wireframe = vjolt_debugrender_wireframe.GetBool(); + + JPH::BodyManager::DrawSettings drawSettings + { + .mDrawGetSupportFunction = false, ///< Draw the GetSupport() function, used for convex collision detection + .mDrawSupportDirection = false, ///< When drawing the support function, also draw which direction mapped to a specific support point + .mDrawGetSupportingFace = false, ///< Draw the faces that were found colliding during collision detection + .mDrawShape = true, ///< Draw the shapes of all bodies + .mDrawShapeWireframe = wireframe, ///< When mDrawShape is true and this is true, the shapes will be drawn in wireframe instead of solid. + .mDrawShapeColor = GetColorMode(), ///< Coloring scheme to use for shapes + .mDrawBoundingBox = false, ///< Draw a bounding box per body + .mDrawCenterOfMassTransform = false, ///< Draw the center of mass for each body + .mDrawWorldTransform = false, ///< Draw the world transform (which can be different than the center of mass) for each body + .mDrawVelocity = false, ///< Draw the velocity vector for each body + .mDrawMassAndInertia = false, ///< Draw the mass and inertia (as the box equivalent) for each body + .mDrawSleepStats = false, ///< Draw stats regarding the sleeping algorithm of each body + }; +#else + JPH::BodyManager::DrawSettings drawSettings + { + .mDrawGetSupportFunction = false, ///< Draw the GetSupport() function, used for convex collision detection + .mDrawSupportDirection = false, ///< When drawing the support function, also draw which direction mapped to a specific support point + .mDrawGetSupportingFace = false, ///< Draw the faces that were found colliding during collision detection + .mDrawShape = false, ///< Draw the shapes of all bodies + .mDrawShapeWireframe = false, ///< When mDrawShape is true and this is true, the shapes will be drawn in wireframe instead of solid. + .mDrawBoundingBox = true, ///< Draw a bounding box per body + .mDrawCenterOfMassTransform = false, ///< Draw the center of mass for each body + .mDrawWorldTransform = false, ///< Draw the world transform (which can be different than the center of mass) for each body + .mDrawVelocity = true, ///< Draw the velocity vector for each body + .mDrawMassAndInertia = false, ///< Draw the mass and inertia (as the box equivalent) for each body + .mDrawSleepStats = false, ///< Draw stats regarding the sleeping algorithm of each body + }; +#endif + + physicsSystem.DrawBodies( drawSettings, this ); + +#ifndef VJOLT_USE_PHYSICS_DEBUG_OVERLAY + // :frog: + if ( vjolt_debugrender_picture_in_picture.GetBool() ) + DrawJoltTVText(); +#endif +} + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsDebugRenderer &JoltPhysicsDebugRenderer::GetInstance() +{ + static JoltPhysicsDebugRenderer s_DebugRenderer; + return s_DebugRenderer; +} + +IVJoltDebugOverlay *JoltPhysicsDebugRenderer::GetDebugOverlay() +{ + return JoltPhysicsInterface::GetInstance().GetDebugOverlay(); +} + +//------------------------------------------------------------------------------------------------- + +#endif // JPH_DEBUG_RENDERER diff --git a/vphysics_jolt/vjolt_debugrender.h b/vphysics_jolt/vjolt_debugrender.h new file mode 100644 index 0000000..4c1fdbf --- /dev/null +++ b/vphysics_jolt/vjolt_debugrender.h @@ -0,0 +1,65 @@ + +#pragma once + +class IMesh; + +#ifdef JPH_DEBUG_RENDERER + +class JoltPhysicsDebugRenderer final : public JPH::DebugRenderer +{ +public: + JoltPhysicsDebugRenderer(); + ~JoltPhysicsDebugRenderer() override; + + /////////////////////////////////////////// + // JPH::DebugRenderer + Draw Implementation + /////////////////////////////////////////// + + void DrawLine( const JPH::Float3 &inFrom, const JPH::Float3 &inTo, JPH::ColorArg inColor ) override; + + void DrawTriangle( JPH::Vec3Arg inV1, JPH::Vec3Arg inV2, JPH::Vec3Arg inV3, JPH::ColorArg inColor ) override; + + Batch CreateTriangleBatch( const Triangle *inTriangles, int inTriangleCount ) override; + Batch CreateTriangleBatch( const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount ) override; + + // This parameter list sucks + void DrawGeometry( JPH::Mat44Arg inModelMatrix, const JPH::AABox &inWorldSpaceBounds, float inLODScaleSq, JPH::ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode = ECullMode::CullBackFace, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid ) override; + + void DrawText3D( JPH::Vec3Arg inPosition, const std::string_view &inString, JPH::ColorArg inColor = JPH::Color::sWhite, float inHeight = 0.5f ) override; + + /////////////////////////////////////////// + // Hehe + /////////////////////////////////////////// + + void DrawJoltTVText(); + + /////////////////////////////////////////// + // Main Interface + /////////////////////////////////////////// + + void RenderPhysicsSystem( JPH::PhysicsSystem &physicsSystem ); + + static JoltPhysicsDebugRenderer& GetInstance(); + + static IVJoltDebugOverlay *GetDebugOverlay(); + +private: + class BatchImpl final : public JPH::RefTargetVirtual, public JPH::RefTarget + { + public: + BatchImpl( IMesh *pMesh ) + : m_pMesh( pMesh ) { } + + void AddRef() override { JPH::RefTarget::AddRef(); } + void Release() override { JPH::RefTarget::Release(); } + + IMesh* GetMesh() const { return m_pMesh; } + + private: + IMesh *m_pMesh; + }; + + bool m_bShouldClear = false; +}; + +#endif diff --git a/vphysics_jolt/vjolt_environment.cpp b/vphysics_jolt/vjolt_environment.cpp new file mode 100644 index 0000000..616f0a0 --- /dev/null +++ b/vphysics_jolt/vjolt_environment.cpp @@ -0,0 +1,1458 @@ +//================================================================================================= +// +// Interface to a physics scene +// Physics environments are implemented as discrete JPH::PhysicsSystems. +// Portal and Portal 2 have two of these. +// +// Notes: +// Josh: We always use BodyInterfaceNoLock and deal with unlocked bodies now. +// Jolt starts to get very angry and asserts at us for doing that but we are never simulating +// or having it do things while the bodies are technically unlocked -- but it doesn't matter. +// We need to do this so we can assume the lock during the callbacks for collisions and stuff. +// +//================================================================================================= + +#include "cbase.h" + +#include "vjolt_callstack.h" +#include "vjolt_collide.h" +#include "vjolt_constraints.h" +#include "vjolt_controller_fluid.h" +#include "vjolt_controller_motion.h" +#include "vjolt_controller_player.h" +#include "vjolt_controller_shadow.h" +#include "vjolt_controller_vehicle.h" +#include "vjolt_debugrender.h" +#include "vjolt_layers.h" +#include "vjolt_object.h" +#include "vjolt_state_recorder_file.h" + +#include "vjolt_environment.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +// This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error. +static constexpr uint kMaxBodies = 16384; + +// This determines how many mutexes to allocate to protect rigid bodies from concurrent access. Set it to 0 for the default settings. +static constexpr uint kNumBodyMutexes = 0; + +// This is the max amount of body pairs that can be queued at any time (the broad phase will detect overlapping +// body pairs based on their bounding boxes and will insert them into a queue for the narrowphase). If you make this buffer +// too small the queue will fill up and the broad phase jobs will start to do narrow phase work. This is slightly less efficient. +static constexpr uint kMaxBodyPairs = kMaxBodies; + +// This is the maximum size of the contact constraint buffer. If more contacts (collisions between bodies) are detected than this +// number then these contacts will be ignored and bodies will start interpenetrating / fall through the world. +static constexpr uint kMaxContactConstraints = kMaxBodies; + +static ConVar vjolt_linearcast( "vjolt_linearcast", "1", FCVAR_NONE, "Whether bodies will be created with linear cast motion quality (only takes effect after map restart)." ); +static ConVar vjolt_initial_simulation( "vjolt_initial_simulation", "0", FCVAR_NONE, "Whether to pre-settle physics objects on map load." ); + +static ConVar vjolt_substeps_collision( "vjolt_substeps_collision", "1", FCVAR_NONE, "Number of collision steps to perform.", true, 0.0f, true, 4.0f ); +static ConVar vjolt_substeps_integration( "vjolt_substeps_integration", "1", FCVAR_NONE, "Number of integration substeps to perform.", true, 0.0f, true, 4.0f ); + +static ConVar vjolt_baumgarte_factor( "vjolt_baumgarte_factor", "0.2", FCVAR_NONE, "Baumgarte stabilization factor (how much of the position error to 'fix' in 1 update). Changing this may help with constraint stability. Requires a map restart to change.", true, 0.0f, true, 1.0f ); + +//------------------------------------------------------------------------------------------------- + +// Function that determines if two object layers can collide +static bool JoltObjectCanCollide( JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2 ) +{ + switch ( inObject1 ) + { + // NO_COLLIDE collides with nothing. + case Layers::NO_COLLIDE: + return false; + // NON_MOVING collides with moving objects and debris. + case Layers::NON_MOVING_WORLD: + case Layers::NON_MOVING_OBJECT: + return inObject2 == Layers::MOVING || + inObject2 == Layers::DEBRIS; + // MOVING collides with moving and non-moving objects. + case Layers::MOVING: + return inObject2 == Layers::MOVING || + inObject2 == Layers::NON_MOVING_WORLD || + inObject2 == Layers::NON_MOVING_OBJECT; + + // DEBRIS only collides with non-moving objects. + case Layers::DEBRIS: + return inObject2 == Layers::NON_MOVING_WORLD || inObject2 == Layers::NON_MOVING_OBJECT; + default: + VJoltAssert( false ); + return false; + } +}; + +// BroadPhaseLayerInterface implementation +// This defines a mapping between object and broadphase layers. +class JoltBPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface +{ +public: + JoltBPLayerInterfaceImpl() + { + // Create a mapping table from object to broad phase layer + mObjectToBroadPhase[Layers::NON_MOVING_WORLD] = BroadPhaseLayers::NON_MOVING_WORLD; + mObjectToBroadPhase[Layers::NON_MOVING_OBJECT] = BroadPhaseLayers::NON_MOVING_OBJECT; + mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING; + mObjectToBroadPhase[Layers::NO_COLLIDE] = BroadPhaseLayers::NO_COLLIDE; + mObjectToBroadPhase[Layers::DEBRIS] = BroadPhaseLayers::DEBRIS; + } + + uint GetNumBroadPhaseLayers() const override + { + return Layers::NUM_LAYERS; + } + + JPH::BroadPhaseLayer GetBroadPhaseLayer( JPH::ObjectLayer inLayer ) const override + { + VJoltAssert( inLayer < Layers::NUM_LAYERS ); + return mObjectToBroadPhase[inLayer]; + } + +#if defined( JPH_EXTERNAL_PROFILE ) || defined( JPH_PROFILE_ENABLED ) + const char *GetBroadPhaseLayerName( JPH::BroadPhaseLayer inLayer ) const override + { + switch ( (JPH::BroadPhaseLayer::Type)inLayer ) + { + case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return "NON_MOVING"; + case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return "MOVING"; + default: VJoltAssert( false ); return "INVALID"; + } + } +#endif + +private: + JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS]; +}; + +// Function that determines if two broadphase layers can collide +static bool JoltBroadPhaseCanCollide( JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2 ) +{ + switch (inLayer1) + { + // NO_COLLIDE collides with nothing. + case Layers::NO_COLLIDE: + return false; + // NON_MOVING collides with moving objects and debris. + case Layers::NON_MOVING_WORLD: + case Layers::NON_MOVING_OBJECT: + return inLayer2 == BroadPhaseLayers::MOVING || + inLayer2 == BroadPhaseLayers::DEBRIS; + // MOVING collides with moving and non-moving objects. + case Layers::MOVING: + return inLayer2 == BroadPhaseLayers::MOVING || + inLayer2 == BroadPhaseLayers::NON_MOVING_WORLD || + inLayer2 == BroadPhaseLayers::NON_MOVING_OBJECT; + + // DEBRIS only collides with non-moving objects. + case Layers::DEBRIS: + return inLayer2 == BroadPhaseLayers::NON_MOVING_WORLD || inLayer2 == BroadPhaseLayers::NON_MOVING_OBJECT; + default: + VJoltAssert( false ); + return false; + } +} + +//------------------------------------------------------------------------------------------------- + +static char s_szNextEnvironmentDumpPath[ MAX_PATH ]; +static bool s_bShouldDumpEnvironmentClient = false; +static bool s_bShouldDumpEnvironmentServer = false; + +CON_COMMAND( vjolt_environment_dump_client, "Dumps the next simulated environment to a .bin file" ) +{ + s_bShouldDumpEnvironmentClient = true; + V_strncpy( s_szNextEnvironmentDumpPath, args.Arg( 1 ), MAX_PATH ); +} + +CON_COMMAND( vjolt_environment_dump_server, "Dumps the next simulated environment to a .bin file" ) +{ + s_bShouldDumpEnvironmentServer = true; + V_strncpy( s_szNextEnvironmentDumpPath, args.Arg( 1 ), MAX_PATH ); +} + +//------------------------------------------------------------------------------------------------- + +JoltBPLayerInterfaceImpl JoltPhysicsEnvironment::s_BPLayerInterface; + +JoltPhysicsEnvironment::JoltPhysicsEnvironment() + : m_ContactListener( m_PhysicsSystem ) +{ + m_PhysicsSystem.Init( + kMaxBodies, kNumBodyMutexes, kMaxBodyPairs, kMaxContactConstraints, + s_BPLayerInterface, JoltBroadPhaseCanCollide, JoltObjectCanCollide ); + + { + JPH::PhysicsSettings settings = m_PhysicsSystem.GetPhysicsSettings(); + settings.mBaumgarte = vjolt_baumgarte_factor.GetFloat(); + m_PhysicsSystem.SetPhysicsSettings( settings ); + } + + // A body activation listener gets notified when bodies activate and go to sleep + // Note that this is called from a job so whatever you do here needs to be thread safe. + // Registering one is entirely optional. + //m_PhysicsSystem.SetBodyActivationListener( &vars.bodyActivationListener ); + + // A contact listener gets notified when bodies (are about to) collide, and when they separate again. + // Note that this is called from a job so whatever you do here needs to be thread safe. + // Registering one is entirely optional. + m_PhysicsSystem.SetContactListener( &m_ContactListener ); + + // Source clamps friction from 0 -> 1, so lets do that. + m_PhysicsSystem.SetCombineFriction( []( const JPH::Body &inBody1, const JPH::SubShapeID &inSubShapeID1, const JPH::Body &inBody2, const JPH::SubShapeID &inSubShapeID2 ) -> float + { + return Clamp( inBody1.GetFriction() * inBody2.GetFriction(), 0.0f, 1.0f ); + } ); + + // Jolt normally does max( x, y ) for resitution, but + // Source's values expect them to be multiplied and clamped. + m_PhysicsSystem.SetCombineRestitution( []( const JPH::Body &inBody1, const JPH::SubShapeID& inSubShapeID1, const JPH::Body &inBody2, const JPH::SubShapeID& inSubShapeID2 ) -> float + { + return Clamp( inBody1.GetRestitution() * inBody2.GetRestitution(), 0.0f, 1.0f ); + } ); + + // Set our linear cast member + m_bUseLinearCast = vjolt_linearcast.GetBool(); +} + +JoltPhysicsEnvironment::~JoltPhysicsEnvironment() +{ + // Clear any pending dead bodies. + DeleteDeadObjects(); + + // Clear out all our bodies. + m_PhysicsSystem.GetBodies( m_CachedBodies ); + + const int nCount = int ( m_CachedBodies.size() ); + for ( int i = 0; i < nCount; i++ ) + { + JPH::Body *pBody = m_PhysicsSystem.GetBodyLockInterfaceNoLock().TryGetBody( m_CachedBodies[ i ] ); + JoltPhysicsObject *pObject = reinterpret_cast< JoltPhysicsObject * >( pBody->GetUserData() ); + RemoveBodyAndDeleteObject( pObject ); + } +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetDebugOverlay( CreateInterfaceFn debugOverlayFactory ) +{ + m_pDebugOverlay = nullptr; + if ( debugOverlayFactory ) + { + m_pDebugOverlay = (IVJoltDebugOverlay *)debugOverlayFactory( VJOLT_DEBUG_OVERLAY_VERSION, nullptr ); + + JoltPhysicsInterface::GetInstance().SetDebugOverlay( m_pDebugOverlay ); + } +} + +IVPhysicsDebugOverlay *JoltPhysicsEnvironment::GetDebugOverlay() +{ + // Slart: For some reason this is part of the vphysics interface, nothing ever uses it + // outside of vphysics and we shouldn't either, we want to be able to pick between the + // full debugoverlay or the vphysics one based on a compile-time parameter + return nullptr; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetGravity( const Vector &gravityVector ) +{ + JPH::Vec3 gravity = SourceToJolt::Distance( gravityVector ); + m_PhysicsSystem.SetGravity( gravity ); +} + +void JoltPhysicsEnvironment::GetGravity( Vector *pGravityVector ) const +{ + VJoltAssert( pGravityVector ); + *pGravityVector = JoltToSource::Distance( m_PhysicsSystem.GetGravity() ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetAirDensity( float density ) +{ + // Josh: This is linear damping there is also angular damping... + // Slart: Maybe we should set both to this value + m_flAirDensity = density; + Log_Stub( LOG_VJolt ); +} + +float JoltPhysicsEnvironment::GetAirDensity() const +{ + Log_Stub( LOG_VJolt ); + return m_flAirDensity; +} + +//------------------------------------------------------------------------------------------------- + +static objectparams_t NormalizeObjectParams( objectparams_t* pParams ) +{ + objectparams_t params = *pParams; + params.mass = Clamp( pParams->mass, VPHYSICS_MIN_MASS, VPHYSICS_MAX_MASS ); + + return params; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsObject *JoltPhysicsEnvironment::CreatePolyObject( const CPhysCollide *pCollisionModel, int materialIndex, const Vector &position, const QAngle &angles, objectparams_t *pParams ) +{ + objectparams_t params = NormalizeObjectParams( pParams ); + + const JPH::Shape* pShape = pCollisionModel->ToShape(); + if ( params.massCenterOverride ) + { + JPH::Vec3 massCenterOverride = SourceToJolt::Distance( *params.massCenterOverride ); + pShape = CreateCOMOverrideShape( pShape, massCenterOverride ); + } + + JPH::BodyCreationSettings settings( pShape, SourceToJolt::Distance( position ), SourceToJolt::Angle( angles ), JPH::EMotionType::Dynamic, Layers::MOVING ); + settings.mMassPropertiesOverride.mMass = params.mass; + //settings.mMassPropertiesOverride.mInertia = JPH::Mat44::sIdentity() * params.inertia; + settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia; // JPH::EOverrideMassProperties::MassAndInertiaProvided; + + if ( m_bUseLinearCast ) + settings.mMotionQuality = JPH::EMotionQuality::LinearCast; + + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + JPH::Body *pBody = bodyInterface.CreateBody( settings ); + bodyInterface.AddBody( pBody->GetID(), JPH::EActivation::DontActivate ); + + return new JoltPhysicsObject( pBody, this, false, materialIndex, ¶ms ); +} + +IPhysicsObject *JoltPhysicsEnvironment::CreatePolyObjectStatic( const CPhysCollide *pCollisionModel, int materialIndex, const Vector &position, const QAngle &angles, objectparams_t *pParams ) +{ + objectparams_t params = NormalizeObjectParams( pParams ); + + JPH::BodyCreationSettings settings( pCollisionModel->ToShape(), SourceToJolt::Distance( position ), SourceToJolt::Angle( angles ), JPH::EMotionType::Static, Layers::NON_MOVING_WORLD ); + + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + JPH::Body *pBody = bodyInterface.CreateBody( settings ); + bodyInterface.AddBody( pBody->GetID(), JPH::EActivation::DontActivate ); + + return new JoltPhysicsObject( pBody, this, true, materialIndex, ¶ms ); +} + +IPhysicsObject *JoltPhysicsEnvironment::CreateSphereObject( float radius, int materialIndex, const Vector &position, const QAngle &angles, objectparams_t *pParams, bool isStatic ) +{ + objectparams_t params = NormalizeObjectParams( pParams ); + + const JPH::Shape *pShape = new JPH::SphereShape( SourceToJolt::Distance( radius ) ); + if ( params.massCenterOverride ) + { + JPH::Vec3 massCenterOverride = SourceToJolt::Distance( *params.massCenterOverride ); + pShape = CreateCOMOverrideShape( pShape, massCenterOverride ); + } + + JPH::EMotionType motionType = isStatic ? JPH::EMotionType::Static : JPH::EMotionType::Dynamic; + JPH::ObjectLayer objectLayer = isStatic ? Layers::NON_MOVING_WORLD : Layers::MOVING; + + JPH::BodyCreationSettings settings( pShape, SourceToJolt::Distance( position ), SourceToJolt::Angle( angles ), motionType, objectLayer ); + + if ( !isStatic ) + { + if ( m_bUseLinearCast ) + settings.mMotionQuality = JPH::EMotionQuality::LinearCast; + + settings.mMassPropertiesOverride.mMass = params.mass; + //settings.mMassPropertiesOverride.mInertia = JPH::Mat44::sIdentity() * params.inertia; + settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;//JPH::EOverrideMassProperties::MassAndInertiaProvided; + } + + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + JPH::Body *pBody = bodyInterface.CreateBody( settings ); + bodyInterface.AddBody( pBody->GetID(), JPH::EActivation::DontActivate ); + + return new JoltPhysicsObject( pBody, this, isStatic, materialIndex, ¶ms ); +} + +void JoltPhysicsEnvironment::DestroyObject( IPhysicsObject *pObject ) +{ + if ( !pObject ) + return; + + JoltPhysicsObject *pJoltObject = static_cast( pObject ); + + if ( pJoltObject->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE ) + { + // Object deleted twice. + VJoltAssertMsg( 0, "Object deleted twice.\n" ); + return; + } + + pJoltObject->AddCallbackFlags( CALLBACK_MARKED_FOR_DELETE ); + + // If we are simulating or the delete queue is enabled, add it to the delete queue. + // Otherwise, just delete it now. + if ( m_bSimulating || m_bEnableDeleteQueue ) + m_pDeadObjects.push_back( pJoltObject ); + else + RemoveBodyAndDeleteObject( pJoltObject ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsFluidController *JoltPhysicsEnvironment::CreateFluidController( IPhysicsObject *pFluidObject, fluidparams_t *pParams ) +{ + JoltPhysicsObject *pJoltObject = static_cast< JoltPhysicsObject * >( pFluidObject ); + JoltPhysicsFluidController *pFluidController = new JoltPhysicsFluidController( &m_PhysicsSystem, pJoltObject, pParams ); + m_pPhysicsControllers.AddToTail( pFluidController ); + return pFluidController; +} + +void JoltPhysicsEnvironment::DestroyFluidController( IPhysicsFluidController *pFluidController ) +{ + JoltPhysicsFluidController *pInternalFluidController = static_cast( pFluidController ); + m_pPhysicsControllers.FindAndRemove( pInternalFluidController ); + delete pInternalFluidController; +} + +//------------------------------------------------------------------------------------------------- + +class JoltPhysicsSpring final : public IPhysicsSpring, public IJoltObjectDestroyedListener +{ +public: + JoltPhysicsSpring( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pObjectStart, JoltPhysicsObject *pObjectEnd, springparams_t *pParams ); + ~JoltPhysicsSpring() override; + + void GetEndpoints( Vector *worldPositionStart, Vector *worldPositionEnd ) override; + void SetSpringConstant( float flSpringConstant ) override; + void SetSpringDamping( float flSpringDamping ) override; + void SetSpringLength( float flSpringLength ) override; + + IPhysicsObject *GetStartObject() override; + IPhysicsObject *GetEndObject() override; + + void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ); + +private: + JPH::PhysicsSystem *m_pPhysicsSystem = nullptr; + + JoltPhysicsObject *m_pObjectStart = nullptr; + JoltPhysicsObject *m_pObjectEnd = nullptr; + + JPH::DistanceConstraint *m_pConstraint = nullptr; + bool m_OnlyStretch = false; +}; + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsSpring::JoltPhysicsSpring( JPH::PhysicsSystem *pPhysicsSystem, JoltPhysicsObject *pObjectStart, JoltPhysicsObject *pObjectEnd, springparams_t *pParams ) + : m_pPhysicsSystem( pPhysicsSystem ) + , m_pObjectStart( pObjectStart ) + , m_pObjectEnd( pObjectEnd ) + , m_OnlyStretch( pParams->onlyStretch ) +{ + JPH::Body *refBody = m_pObjectStart->GetBody(); + JPH::Body *attBody = m_pObjectEnd->GetBody(); + + JPH::DistanceConstraintSettings settings; + settings.mSpace = pParams->useLocalPositions ? JPH::EConstraintSpace::LocalToBodyCOM : JPH::EConstraintSpace::WorldSpace; + settings.mPoint1 = SourceToJolt::Distance( pParams->startPosition ); + settings.mPoint2 = SourceToJolt::Distance( pParams->endPosition ); + settings.mMinDistance = m_OnlyStretch ? 0.0f : SourceToJolt::Distance( pParams->naturalLength ); + settings.mMaxDistance = SourceToJolt::Distance( pParams->naturalLength ); + + settings.mFrequency = GetSpringFrequency( pParams->constant, m_pObjectStart, m_pObjectEnd ); + // TODO(Josh): The damping values are normally fucking crazy like 5500 from Source... wtf is going on here. + settings.mDamping = 0.0f; + + m_pConstraint = static_cast< JPH::DistanceConstraint * >( settings.Create( *refBody, *attBody ) ); + m_pConstraint->SetEnabled( true ); + + m_pPhysicsSystem->AddConstraint( m_pConstraint ); + + m_pObjectStart->AddDestroyedListener( this ); + m_pObjectEnd->AddDestroyedListener( this ); +} + +JoltPhysicsSpring::~JoltPhysicsSpring() +{ + if ( m_pObjectStart ) + m_pObjectStart->RemoveDestroyedListener( this ); + + if ( m_pObjectEnd ) + m_pObjectEnd->RemoveDestroyedListener( this ); + + m_pPhysicsSystem->RemoveConstraint( m_pConstraint ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsSpring::GetEndpoints( Vector *worldPositionStart, Vector *worldPositionEnd ) +{ + // TODO(Josh): Implement this. + Log_Stub( LOG_VJolt ); + + if ( worldPositionStart ) + *worldPositionStart = vec3_origin; + + if ( worldPositionEnd ) + *worldPositionEnd = vec3_origin; +} + +void JoltPhysicsSpring::SetSpringConstant( float flSpringConstant ) +{ + m_pObjectStart->Wake(); + m_pObjectEnd->Wake(); + + m_pConstraint->SetFrequency( GetSpringFrequency( flSpringConstant, m_pObjectStart, m_pObjectEnd ) ); +} + +void JoltPhysicsSpring::SetSpringDamping( float flSpringDamping ) +{ + m_pObjectStart->Wake(); + m_pObjectEnd->Wake(); + + //m_pConstraint->SetDamping( flSpringDamping ); +} + +void JoltPhysicsSpring::SetSpringLength( float flSpringLength ) +{ + m_pObjectStart->Wake(); + m_pObjectEnd->Wake(); + + float flLength = SourceToJolt::Distance( flSpringLength ); + m_pConstraint->SetDistance( m_OnlyStretch ? 0.0f : flLength, flLength ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsObject *JoltPhysicsSpring::GetStartObject() +{ + return m_pObjectStart; +} + +IPhysicsObject *JoltPhysicsSpring::GetEndObject() +{ + return m_pObjectEnd; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsSpring::OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) +{ + if ( pObject == m_pObjectStart ) + m_pObjectStart = nullptr; + + if ( pObject == m_pObjectEnd ) + m_pObjectEnd = nullptr; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsSpring *JoltPhysicsEnvironment::CreateSpring( IPhysicsObject *pObjectStart, IPhysicsObject *pObjectEnd, springparams_t *pParams ) +{ + JoltPhysicsObject *pJoltObjectStart = static_cast< JoltPhysicsObject *>( pObjectStart ); + JoltPhysicsObject *pJoltObjectEnd = static_cast< JoltPhysicsObject *>( pObjectEnd ); + + return new JoltPhysicsSpring( &m_PhysicsSystem, pJoltObjectStart, pJoltObjectEnd, pParams ); +} + +void JoltPhysicsEnvironment::DestroySpring( IPhysicsSpring *pSpring ) +{ + JoltPhysicsSpring *pJoltSpring = static_cast< JoltPhysicsSpring * >( pSpring ); + delete pJoltSpring; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateRagdollConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_ragdollparams_t &ragdoll ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseRagdoll( pGroup, ragdoll ); + return pConstraint; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateHingeConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_hingeparams_t &hinge ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseHinge( pGroup, hinge ); + return pConstraint; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateFixedConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_fixedparams_t &fixed ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseFixed( pGroup, fixed ); + return pConstraint; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateSlidingConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_slidingparams_t &sliding ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseSliding( pGroup, sliding ); + return pConstraint; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateBallsocketConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_ballsocketparams_t &ballsocket ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseBallsocket( pGroup, ballsocket ); + return pConstraint; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreatePulleyConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_pulleyparams_t &pulley ) +{ + Log_Stub( LOG_VJolt ); + return nullptr; +} + +IPhysicsConstraint *JoltPhysicsEnvironment::CreateLengthConstraint( IPhysicsObject *pReferenceObject, IPhysicsObject *pAttachedObject, IPhysicsConstraintGroup *pGroup, const constraint_lengthparams_t &length ) +{ + JoltPhysicsConstraint *pConstraint = new JoltPhysicsConstraint( this, pReferenceObject, pAttachedObject ); + pConstraint->InitialiseLength( pGroup, length ); + return pConstraint; +} + +void JoltPhysicsEnvironment::DestroyConstraint( IPhysicsConstraint *pConstraint ) +{ + if ( !pConstraint ) + return; + + JoltPhysicsConstraint *pJoltConstraint = static_cast< JoltPhysicsConstraint * >( pConstraint ); + if ( m_bWakeObjectsOnConstraintDeletion ) + { + IPhysicsObject *pObjectRef = pJoltConstraint->GetReferenceObject(); + if ( pObjectRef ) + pObjectRef->Wake(); + + IPhysicsObject *pObjectAtt = pJoltConstraint->GetAttachedObject(); + if ( pObjectAtt ) + pObjectAtt->Wake(); + } + + // Suprisingly, the quick delete thing only affects whether we wake + // constraints or not. + // It does not affect whether it goes in the delete queue. + if ( m_bSimulating ) + { + pJoltConstraint->Deactivate(); + m_pDeadConstraints.push_back( pJoltConstraint ); + } + else + { + delete pJoltConstraint; + } +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsConstraintGroup *JoltPhysicsEnvironment::CreateConstraintGroup( const constraint_groupparams_t &groupParams ) +{ + return new JoltPhysicsConstraintGroup; +} + +void JoltPhysicsEnvironment::DestroyConstraintGroup( IPhysicsConstraintGroup *pGroup ) +{ + delete static_cast( pGroup ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsShadowController *JoltPhysicsEnvironment::CreateShadowController( IPhysicsObject *pObject, bool allowTranslation, bool allowRotation ) +{ + JoltPhysicsShadowController *pController = new JoltPhysicsShadowController( static_cast( pObject ), allowTranslation, allowRotation ); + m_pPhysicsControllers.AddToTail( pController ); + return pController; +} + +void JoltPhysicsEnvironment::DestroyShadowController( IPhysicsShadowController *pShadowController ) +{ + JoltPhysicsShadowController *pController = static_cast< JoltPhysicsShadowController * >( pShadowController ); + m_pPhysicsControllers.FindAndRemove( pController ); + delete pController; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsPlayerController *JoltPhysicsEnvironment::CreatePlayerController( IPhysicsObject *pObject ) +{ + JoltPhysicsPlayerController *pController = new JoltPhysicsPlayerController( static_cast( pObject ) ); + m_pPhysicsControllers.AddToTail( pController ); + return pController; +} + +void JoltPhysicsEnvironment::DestroyPlayerController( IPhysicsPlayerController *pPlayerController ) +{ + JoltPhysicsPlayerController *pController = static_cast< JoltPhysicsPlayerController * >( pPlayerController ); + m_pPhysicsControllers.FindAndRemove( pController ); + delete pController; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsMotionController *JoltPhysicsEnvironment::CreateMotionController( IMotionEvent *pHandler ) +{ + JoltPhysicsMotionController *pController = new JoltPhysicsMotionController( pHandler ); + m_pPhysicsControllers.AddToTail( pController ); + return pController; +} + +void JoltPhysicsEnvironment::DestroyMotionController( IPhysicsMotionController *pController ) +{ + JoltPhysicsMotionController *pJoltController = static_cast< JoltPhysicsMotionController * >( pController ); + m_pPhysicsControllers.FindAndRemove( pJoltController ); + delete pJoltController; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsVehicleController *JoltPhysicsEnvironment::CreateVehicleController( IPhysicsObject *pVehicleBodyObject, const vehicleparams_t ¶ms, unsigned int nVehicleType, IPhysicsGameTrace *pGameTrace ) +{ + JoltPhysicsObject *pJoltCarBodyObject = static_cast< JoltPhysicsObject * >( pVehicleBodyObject ); + + JoltPhysicsVehicleController *pController = new JoltPhysicsVehicleController( this, &m_PhysicsSystem, pJoltCarBodyObject, params, nVehicleType, pGameTrace ); + m_pPhysicsControllers.AddToTail( pController ); + return pController; +} + +void JoltPhysicsEnvironment::DestroyVehicleController( IPhysicsVehicleController *pVehicleController ) +{ + JoltPhysicsVehicleController *pJoltController = static_cast( pVehicleController ); + m_pPhysicsControllers.FindAndRemove( pJoltController ); + delete pJoltController; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetCollisionSolver( IPhysicsCollisionSolver *pSolver ) +{ + m_ContactListener.SetGameSolver( pSolver ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::Simulate( float deltaTime ) +{ + // Handle pausing the game... + if ( deltaTime == 0.0f ) + return; + + // Grab our shared assets from the interface + JPH::TempAllocator *tempAllocator = JoltPhysicsInterface::GetInstance().GetTempAllocator(); + JPH::JobSystem *jobSystem = JoltPhysicsInterface::GetInstance().GetJobSystem(); + + // Clear any dead objects before running the simulation. + DeleteDeadObjects(); + + HandleDebugDumpingEnvironment( VJOLT_RETURN_ADDRESS() ); + + m_bSimulating = true; + + // Funnily enough, VPhysics calls this BEFORE + // doing the simulation... + m_ContactListener.PostSimulationFrame(); + + // Run pre-simulation controllers + for ( IJoltPhysicsController *pController : m_pPhysicsControllers ) + pController->OnPreSimulate( deltaTime ); + + const int nIntegrationSubSteps = vjolt_substeps_integration.GetInt(); + const int nCollisionSubSteps = vjolt_substeps_collision.GetInt(); + + // If we haven't already, optimize the broadphase, currently this can only happen once per-environment + if ( !m_bOptimizedBroadPhase ) + { + m_PhysicsSystem.OptimizeBroadPhase(); + m_bOptimizedBroadPhase = true; + + if ( vjolt_initial_simulation.GetBool() ) + { + // Do an initial simulation to settle objects down. + static constexpr float InitialIterationTimescale = 1.0f / 60.0f; + static constexpr int MaxInitialIterations = 1024; + static constexpr int InitialSubSteps = 4; + + int nIterCount = 0; + while ( m_PhysicsSystem.GetNumActiveBodies() && nIterCount < MaxInitialIterations ) + { + m_PhysicsSystem.Update( InitialIterationTimescale, 1, InitialSubSteps, tempAllocator, jobSystem ); + nIterCount++; + } + } + else + { + // Move things around! + m_PhysicsSystem.Update( deltaTime, nCollisionSubSteps, nIntegrationSubSteps, tempAllocator, jobSystem ); + } + } + else + { + // Move things around! + m_PhysicsSystem.Update( deltaTime, nCollisionSubSteps, nIntegrationSubSteps, tempAllocator, jobSystem ); + } + m_ContactListener.FlushCallbacks(); + + // Run post-simulation controllers + for ( IJoltPhysicsController *pController : m_pPhysicsControllers ) + pController->OnPostSimulate( deltaTime ); + + m_bSimulating = false; + + // If the delete queue is disabled, we only added to it during the simulation + // ie. callbacks etc. So flush that now. + if ( !m_bEnableDeleteQueue ) + DeleteDeadObjects(); + +#ifdef JPH_DEBUG_RENDERER + JoltPhysicsDebugRenderer::GetInstance().RenderPhysicsSystem( m_PhysicsSystem ); +#endif +} + +bool JoltPhysicsEnvironment::IsInSimulation() const +{ + return m_bSimulating; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsEnvironment::GetSimulationTimestep() const +{ + return m_flStepTime; +} + +void JoltPhysicsEnvironment::SetSimulationTimestep( float timestep ) +{ + m_flStepTime = timestep; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsEnvironment::GetSimulationTime() const +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +void JoltPhysicsEnvironment::ResetSimulationClock() +{ + Log_Stub( LOG_VJolt ); +} + +float JoltPhysicsEnvironment::GetNextFrameTime() const +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetCollisionEventHandler( IPhysicsCollisionEvent *pCollisionEvents ) +{ + m_ContactListener.SetGameListener( pCollisionEvents ); +} + +void JoltPhysicsEnvironment::SetObjectEventHandler( IPhysicsObjectEvent *pObjectEvents ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsEnvironment::SetConstraintEventHandler( IPhysicsConstraintEvent *pConstraintEvents ) +{ + m_pConstraintListener = pConstraintEvents; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetQuickDelete( bool bQuick ) +{ + // Josh: + // What a weird function, all this does is determine whether + // to wake objects when the constraint gets deleted or not. + m_bWakeObjectsOnConstraintDeletion = !bQuick; +} + +//------------------------------------------------------------------------------------------------- + +// GetActiveObjectCount and GetActiveObjects are always called in pairs +// and outside of the simulation. +// +// If there is a weird case where this is different (GMod lua on collision callbacks maybe?). +// Uncomment the locking code below. + +int JoltPhysicsEnvironment::GetActiveObjectCount() const +{ + // If this is the first call, then some objects may have become + // asleep from the initial simulation have their visuals not match where they are. + if ( m_bActiveObjectCountFirst ) + m_PhysicsSystem.GetBodies( m_CachedActiveBodies ); + else + m_PhysicsSystem.GetActiveBodies( m_CachedActiveBodies ); + + m_bActiveObjectCountFirst = false; + const int nCount = int ( m_CachedActiveBodies.size() ); + return nCount; +} + +void JoltPhysicsEnvironment::GetActiveObjects( IPhysicsObject **pOutputObjectList ) const +{ + const int nCount = int ( m_CachedActiveBodies.size() ); + for ( int i = 0; i < nCount; i++ ) + { + JPH::Body *pBody = m_PhysicsSystem.GetBodyLockInterfaceNoLock().TryGetBody( m_CachedActiveBodies[ i ] ); + pOutputObjectList[ i ] = reinterpret_cast( pBody->GetUserData() ); + } +} + +const IPhysicsObject **JoltPhysicsEnvironment::GetObjectList( int *pOutputObjectCount ) const +{ + m_PhysicsSystem.GetBodies( m_CachedBodies ); + + const int nCount = int ( m_CachedBodies.size() ); + + if ( pOutputObjectCount ) + *pOutputObjectCount = nCount; + + m_CachedObjects.reserve( nCount ); + for ( int i = 0; i < nCount; i++ ) + { + JPH::Body *pBody = m_PhysicsSystem.GetBodyLockInterfaceNoLock().TryGetBody( m_CachedBodies[ i ] ); + m_CachedObjects[ i ] = reinterpret_cast< IPhysicsObject * >( pBody->GetUserData() ); + } + + return m_CachedObjects.data(); +} + +bool JoltPhysicsEnvironment::TransferObject( IPhysicsObject *pObject, IPhysicsEnvironment *pDestinationEnvironment ) +{ + JoltPhysicsObject *pJoltObject = static_cast< JoltPhysicsObject * >( pObject ); + JoltPhysicsEnvironment *pJoltEnv = static_cast< JoltPhysicsEnvironment * >( pDestinationEnvironment ); + + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + bodyInterface.RemoveBody( pJoltObject->GetBodyID() ); + + pJoltEnv->ObjectTransferHandOver( pJoltObject ); + pJoltObject->UpdateEnvironment( pJoltEnv ); + + return true; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::CleanupDeleteList() +{ + DeleteDeadObjects(); +} + +void JoltPhysicsEnvironment::EnableDeleteQueue( bool enable ) +{ + m_bEnableDeleteQueue = enable; +} + +//------------------------------------------------------------------------------------------------- + +class JoltStateRecorderSave final : public JPH::StateRecorder +{ +public: + JoltStateRecorderSave( ISave *pSave ) + : m_pSave( pSave ) + { + } + + JoltStateRecorderSave( IRestore *pRestore ) + : m_pRestore( pRestore ) + { + } + + void WriteBytes( const void* inData, size_t inNumBytes ) override + { + m_pSave->WriteData( reinterpret_cast< const char * >( inData ), int( inNumBytes ) ); + } + + void ReadBytes( void* outData, size_t inNumBytes ) override + { + m_pRestore->ReadData( reinterpret_cast< char * >( outData ), int( inNumBytes ), 0 ); + } + + bool IsEOF() const override { return false; } + bool IsFailed() const override { return false; } + +private: + union + { + ISave *m_pSave; + IRestore *m_pRestore; + }; +}; + +bool JoltPhysicsEnvironment::Save( const physsaveparams_t ¶ms ) +{ + JoltStateRecorderSave recorder( params.pSave ); + + switch ( params.type ) + { + default: + case PIID_UNKNOWN: + Log_Warning( LOG_VJolt, "Saving PIID_UNKNOWN is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSOBJECT: + { + JoltPhysicsObject *pObject = reinterpret_cast< JoltPhysicsObject * >( params.pObject ); + JPH::BodyCreationSettings bodyCreationSettings = pObject->GetBody()->GetBodyCreationSettings(); + + recorder.Write( reinterpret_cast( pObject ) ); + bodyCreationSettings.SaveBinaryState( recorder ); + pObject->SaveObjectState( recorder ); + return true; + } + case PIID_IPHYSICSFLUIDCONTROLLER: + // This just returns false in regular VPhysics. + return false; + case PIID_IPHYSICSSPRING: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSSPRING is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSCONSTRAINTGROUP: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSCONSTRAINTGROUP is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSCONSTRAINT: + { + JoltPhysicsConstraint *pConstraint = reinterpret_cast< JoltPhysicsConstraint * >( params.pObject ); + JoltPhysicsObject *pRefObject = reinterpret_cast< JoltPhysicsObject * >( pConstraint->GetReferenceObject() ); + JoltPhysicsObject *pAttObject = reinterpret_cast< JoltPhysicsObject * >( pConstraint->GetAttachedObject() ); + + recorder.Write( reinterpret_cast( pConstraint ) ); + recorder.Write( reinterpret_cast( pRefObject ) ); + recorder.Write( reinterpret_cast( pAttObject ) ); + pConstraint->SaveConstraintSettings( recorder ); + return true; + } + case PIID_IPHYSICSSHADOWCONTROLLER: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSSHADOWCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSPLAYERCONTROLLER: + // This just returns false in regular VPhysics. + return false; + case PIID_IPHYSICSMOTIONCONTROLLER: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSMOTIONCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSVEHICLECONTROLLER: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSVEHICLECONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSGAMETRACE: + Log_Warning( LOG_VJolt, "Saving PIID_IPHYSICSGAMETRACE is unsupported right now.\n" ); + return false; + } +} + +void JoltPhysicsEnvironment::PreRestore( const physprerestoreparams_t ¶ms ) +{ + m_SaveRestorePointerMap.clear(); + + for ( int i = 0; i < params.recreatedObjectCount; i++ ) + AddPhysicsSaveRestorePointer( + reinterpret_cast< uintp >( params.recreatedObjectList[ i ].pOldObject ), + params.recreatedObjectList[ i ].pNewObject ); +} + +bool JoltPhysicsEnvironment::Restore( const physrestoreparams_t ¶ms ) +{ + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + JoltStateRecorderSave recorder( params.pRestore ); + + switch ( params.type ) + { + default: + case PIID_UNKNOWN: + Log_Warning( LOG_VJolt, "Restoring PIID_UNKNOWN is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSOBJECT: + { + const JPH::Shape* pShape = params.pCollisionModel->ToShape(); + JPH::BodyCreationSettings bodyCreationSettings; + uintp originalPtr; + + recorder.Read( originalPtr ); + bodyCreationSettings.RestoreBinaryState( recorder ); + bodyCreationSettings.SetShape( pShape ); + JPH::Body *pBody = bodyInterface.CreateBody( bodyCreationSettings ); + bodyInterface.AddBody( pBody->GetID(), JPH::EActivation::DontActivate ); + JoltPhysicsObject *pJoltObject = new JoltPhysicsObject( pBody, this, params.pGameData, recorder ); + + *params.ppObject = reinterpret_cast< void * >( pJoltObject ); + AddPhysicsSaveRestorePointer( originalPtr, pJoltObject ); + return true; + } + case PIID_IPHYSICSFLUIDCONTROLLER: + // Given saving this just returns false, this should never happen. + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSFLUIDCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSSPRING: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSSPRING is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSCONSTRAINTGROUP: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSCONSTRAINTGROUP is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSCONSTRAINT: + { + uintp constraintPtr, refObjectPtr, attObjectPtr; + constraintType_t type; + + recorder.Read( constraintPtr ); + recorder.Read( refObjectPtr ); + recorder.Read( attObjectPtr ); + + recorder.Read( type ); + JPH::ConstraintSettings::ConstraintResult result = JPH::ConstraintSettings::sRestoreFromBinaryState( recorder ); + + if ( result.HasError() ) + { + Log_Warning( LOG_VJolt, "Error restoring constraint: %s.\n", result.GetError().c_str() ); + return false; + } + + JoltPhysicsObject* pRefObject = LookupPhysicsSaveRestorePointer< JoltPhysicsObject >( refObjectPtr ); + JoltPhysicsObject* pAttObject = LookupPhysicsSaveRestorePointer< JoltPhysicsObject >( attObjectPtr ); + + const JPH::TwoBodyConstraintSettings *pSettings = static_cast< const JPH::TwoBodyConstraintSettings * >( result.Get().GetPtr() ); + JPH::Constraint *pConstraint = bodyInterface.CreateConstraint( pSettings, pRefObject->GetBodyID(), pAttObject->GetBodyID() ); + pConstraint->RestoreState( recorder ); + m_PhysicsSystem.AddConstraint( pConstraint ); + JoltPhysicsConstraint* pJoltConstraint = new JoltPhysicsConstraint( this, pRefObject, pAttObject, type, pConstraint, params.pGameData ); + *params.ppObject = reinterpret_cast( pJoltConstraint ); + AddPhysicsSaveRestorePointer( constraintPtr, pJoltConstraint ); + return true; + } + case PIID_IPHYSICSSHADOWCONTROLLER: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSSHADOWCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSPLAYERCONTROLLER: + // Given saving this just returns false, this should never happen. + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSPLAYERCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSMOTIONCONTROLLER: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSMOTIONCONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSVEHICLECONTROLLER: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSVEHICLECONTROLLER is unsupported right now.\n" ); + return false; + case PIID_IPHYSICSGAMETRACE: + Log_Warning( LOG_VJolt, "Restoring PIID_IPHYSICSGAMETRACE is unsupported right now.\n" ); + return false; + } +} + +void JoltPhysicsEnvironment::PostRestore() +{ + Log_Stub( LOG_VJolt ); + m_SaveRestorePointerMap.clear(); +} + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsEnvironment::IsCollisionModelUsed( CPhysCollide *pCollide ) const +{ + // Josh: This is only used in debug code. + return false; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::TraceRay( const Ray_t &ray, unsigned int fMask, IPhysicsTraceFilter *pTraceFilter, trace_t *pTrace ) +{ + // Josh: This does nothing in regular vphysics. +} + +void JoltPhysicsEnvironment::SweepCollideable( const CPhysCollide *pCollide, const Vector &vecAbsStart, const Vector &vecAbsEnd, + const QAngle &vecAngles, unsigned int fMask, IPhysicsTraceFilter *pTraceFilter, trace_t *pTrace ) +{ + // Josh: This does nothing in regular vphysics. +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::GetPerformanceSettings( physics_performanceparams_t *pOutput ) const +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsEnvironment::SetPerformanceSettings( const physics_performanceparams_t *pSettings ) +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::ReadStats( physics_stats_t *pOutput ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsEnvironment::ClearStats() +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +// StateRecorder implementation that gathers the size required to save a body's state +class VJoltStateSizeRecorder final : public JPH::StateRecorder +{ +public: + // StreamIn + void ReadBytes( void *outData, size_t inNumBytes ) override {} + bool IsEOF() const override { return false; } + + // StreamOut + void WriteBytes( const void *inData, size_t inNumBytes ) + { + saveSize += inNumBytes; + } + + // Both + bool IsFailed() const override { return false; } + +public: + size_t saveSize = 0; +}; + +unsigned int JoltPhysicsEnvironment::GetObjectSerializeSize( IPhysicsObject *pObject ) const +{ + JoltPhysicsObject *pJoltObject = static_cast( pObject ); + const JPH::Body *pBody = pJoltObject->GetBody(); + + VJoltStateSizeRecorder recorder; + + pBody->SaveState( recorder ); + + pJoltObject->SaveObjectState( recorder ); + + // Larger than we actually need, but it doesn't matter + return static_cast( sizeof( void * ) + recorder.saveSize + sizeof( JPH::BodyCreationSettings ) ); +} + +void JoltPhysicsEnvironment::SerializeObjectToBuffer( IPhysicsObject *pObject, unsigned char *pBuffer, unsigned int bufferSize ) +{ + JoltPhysicsObject *pJoltObject = static_cast( pObject ); + const JPH::Body *pBody = pJoltObject->GetBody(); + + VJoltStateRecorder recorder( pBuffer, bufferSize ); + + // Write shape + recorder.Write( const_cast( reinterpret_cast( pBody->GetShape() ) ) ); + + // Write body creation settings + JPH::BodyCreationSettings bodyCreationSettings = pBody->GetBodyCreationSettings(); + bodyCreationSettings.SaveBinaryState( recorder ); + + pJoltObject->SaveObjectState( recorder ); +} + +IPhysicsObject *JoltPhysicsEnvironment::UnserializeObjectFromBuffer( void *pGameData, unsigned char *pBuffer, unsigned int bufferSize, bool enableCollisions ) +{ + VJoltStateRecorder recorder( pBuffer, bufferSize, CUtlBuffer::READ_ONLY ); + + // Read shape + JPH::Shape *pShape; + recorder.Read< JPH::Shape* >( pShape ); + + // Read body creation settings + JPH::BodyCreationSettings bodyCreationSettings; + bodyCreationSettings.RestoreBinaryState( recorder ); + + bodyCreationSettings.SetShape( pShape ); + + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + JPH::Body *pBody = bodyInterface.CreateBody( bodyCreationSettings ); + bodyInterface.AddBody( pBody->GetID(), JPH::EActivation::Activate ); + + JoltPhysicsObject *pJoltObject = new JoltPhysicsObject( pBody, this, pGameData, recorder ); + pJoltObject->EnableCollisions( enableCollisions ); + return pJoltObject; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::EnableConstraintNotify( bool bEnable ) +{ + m_EnableConstraintNotify = bEnable; +} + +void JoltPhysicsEnvironment::DebugCheckContacts() +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetAlternateGravity( const Vector &gravityVector ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsEnvironment::GetAlternateGravity( Vector *pGravityVector ) const +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsEnvironment::GetDeltaFrameTime( int maxTicks ) const +{ + Log_Stub( LOG_VJolt ); + // TODO(Josh): wtf to do here? + return m_flStepTime * maxTicks; +} + +void JoltPhysicsEnvironment::ForceObjectsToSleep( IPhysicsObject **pList, int listCount ) +{ + for ( int i = 0; i < listCount; i++ ) + pList[ i ]->Sleep(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::SetPredicted( bool bPredicted ) +{ + Log_Stub( LOG_VJolt ); +} + +bool JoltPhysicsEnvironment::IsPredicted() +{ + Log_Stub( LOG_VJolt ); + return false; +} + +void JoltPhysicsEnvironment::SetPredictionCommandNum( int iCommandNum ) +{ + Log_Stub( LOG_VJolt ); +} + +int JoltPhysicsEnvironment::GetPredictionCommandNum() +{ + Log_Stub( LOG_VJolt ); + return 0; +} + +void JoltPhysicsEnvironment::DoneReferencingPreviousCommands( int iCommandNum ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsEnvironment::RestorePredictedSimulation() +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::DestroyCollideOnDeadObjectFlush( CPhysCollide *pCollide ) +{ + // If this collide is part of a dead object, add it to a queue to delete. + for ( JoltPhysicsObject *pObject : m_pDeadObjects ) + { + if ( pObject->GetCollide() == pCollide ) + { + if ( !VectorContains( m_pDeadObjectCollides, pCollide ) ) + m_pDeadObjectCollides.push_back( pCollide ); + return; + } + } + + // Otherwise, just delete it now. + JoltPhysicsCollision::GetInstance().DestroyCollide( pCollide ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::ObjectTransferHandOver( JoltPhysicsObject *pObject ) +{ + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + bodyInterface.AddBody( pObject->GetBodyID(), JPH::EActivation::Activate ); +} + +void JoltPhysicsEnvironment::NotifyConstraintDisabled( JoltPhysicsConstraint* pConstraint ) +{ + if ( m_pConstraintListener && m_EnableConstraintNotify ) + m_pConstraintListener->ConstraintBroken( pConstraint ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::RemoveBodyAndDeleteObject( JoltPhysicsObject *pObject ) +{ + JPH::BodyInterface &bodyInterface = m_PhysicsSystem.GetBodyInterfaceNoLock(); + bodyInterface.RemoveBody( pObject->GetBodyID() ); + delete pObject; +} + +void JoltPhysicsEnvironment::DeleteDeadObjects() +{ + for ( JoltPhysicsObject *pObject : m_pDeadObjects ) + RemoveBodyAndDeleteObject( pObject ); + m_pDeadObjects.clear(); + + for ( JoltPhysicsConstraint *pConstraint : m_pDeadConstraints ) + delete pConstraint; + m_pDeadConstraints.clear(); + + for ( CPhysCollide *pCollide : m_pDeadObjectCollides ) + JoltPhysicsCollision::GetInstance().DestroyCollide( pCollide ); + m_pDeadObjectCollides.clear(); +} + +//------------------------------------------------------------------------------------------------- + +template < typename T > +void JoltPhysicsEnvironment::AddPhysicsSaveRestorePointer( uintp oldPtr, T* newPtr ) +{ + VJoltAssert( oldPtr != 0 ); + VJoltAssert( newPtr != 0 ); + + m_SaveRestorePointerMap[ oldPtr ] = reinterpret_cast< void * >( newPtr ); +} + +template < typename T > +T *JoltPhysicsEnvironment::LookupPhysicsSaveRestorePointer( uintp oldPtr ) +{ + if ( !oldPtr ) + return nullptr; + + auto iter = m_SaveRestorePointerMap.find( oldPtr ); + if ( iter == m_SaveRestorePointerMap.end() ) + return nullptr; + + return reinterpret_cast< T * >( iter->second ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsEnvironment::HandleDebugDumpingEnvironment( void *pReturnAddress ) +{ + if ( !s_bShouldDumpEnvironmentClient && !s_bShouldDumpEnvironmentServer ) + return; + + // Josh: + // Check if we were called by either server.dll or client.dll + // so we get the right environment. + // (Client will only have ragdolls etc) + char szModulePath[MAX_PATH]; + GetCallingFunctionModulePath( pReturnAddress, szModulePath, MAX_PATH ); + const char* pszModuleFileName = V_UnqualifiedFileName( szModulePath ); + + bool bIsServerDumping = StringHasPrefix( pszModuleFileName, "server" ) && s_bShouldDumpEnvironmentServer; + bool bIsClientDumping = StringHasPrefix( pszModuleFileName, "client" ) && s_bShouldDumpEnvironmentClient; + + VJoltAssertMsg( !StringHasPrefix( pszModuleFileName, "vphysics" ), "Should never get vphysics as the calling module, this only looks up 1 call in the stack." ); + + if ( !bIsServerDumping && !bIsClientDumping ) + return; + + JoltStateRecorderFile recorder( s_szNextEnvironmentDumpPath, false ); + if ( recorder.IsValid() ) + { + JPH::PhysicsScene scene; + scene.FromPhysicsSystem( &m_PhysicsSystem ); + scene.SaveBinaryState( recorder, true, true ); + } + else + Log_Warning( LOG_VJolt, "Failed to open stream to dump environment to file %s!\n", s_szNextEnvironmentDumpPath ); + + s_bShouldDumpEnvironmentClient = false; + s_bShouldDumpEnvironmentServer = false; +} diff --git a/vphysics_jolt/vjolt_environment.h b/vphysics_jolt/vjolt_environment.h new file mode 100644 index 0000000..ea3f517 --- /dev/null +++ b/vphysics_jolt/vjolt_environment.h @@ -0,0 +1,217 @@ +//================================================================================================= +// +// Interface to a physics scene +// +//================================================================================================= + +#pragma once + +#include "vjolt_interface.h" +#include "vjolt_object.h" +#include "vjolt_constraints.h" +#include "vjolt_listener_contact.h" + +class JoltBPLayerInterfaceImpl; + +// StateRecorder implementation that saves to a fixed buffer +class VJoltStateRecorder final : public JPH::StateRecorder, public CUtlBuffer +{ +public: + using CUtlBuffer::CUtlBuffer; + + // StreamIn + void ReadBytes( void* outData, size_t inNumBytes ) override + { + Get( outData, static_cast( inNumBytes ) ); + } + + bool IsEOF() const override { return false; } + + // StreamOut + void WriteBytes( const void* inData, size_t inNumBytes ) + { + Put( inData, static_cast( inNumBytes ) ); + } + + // Both + bool IsFailed() const override { return false; } +}; + +class JoltPhysicsEnvironment final : public IPhysicsEnvironment +{ +public: + JoltPhysicsEnvironment(); + ~JoltPhysicsEnvironment() override; + + void SetDebugOverlay( CreateInterfaceFn debugOverlayFactory ) override; + IVPhysicsDebugOverlay* GetDebugOverlay( void ) override; + + void SetGravity( const Vector& gravityVector ) override; + void GetGravity( Vector* pGravityVector ) const override; + + void SetAirDensity( float density ) override; + float GetAirDensity() const override; + + IPhysicsObject* CreatePolyObject( const CPhysCollide* pCollisionModel, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t* pParams ) override; + IPhysicsObject* CreatePolyObjectStatic( const CPhysCollide* pCollisionModel, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t* pParams ) override; + IPhysicsObject* CreateSphereObject( float radius, int materialIndex, const Vector& position, const QAngle& angles, objectparams_t* pParams, bool isStatic ) override; + void DestroyObject( IPhysicsObject* ) override; + + IPhysicsFluidController* CreateFluidController( IPhysicsObject* pFluidObject, fluidparams_t* pParams ) override; + void DestroyFluidController( IPhysicsFluidController* ) override; + + IPhysicsSpring* CreateSpring( IPhysicsObject* pObjectStart, IPhysicsObject* pObjectEnd, springparams_t* pParams ) override; + void DestroySpring( IPhysicsSpring* ) override; + + IPhysicsConstraint* CreateRagdollConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_ragdollparams_t& ragdoll ) override; + IPhysicsConstraint* CreateHingeConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_hingeparams_t& hinge ) override; + IPhysicsConstraint* CreateFixedConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_fixedparams_t& fixed ) override; + IPhysicsConstraint* CreateSlidingConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_slidingparams_t& sliding ) override; + IPhysicsConstraint* CreateBallsocketConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_ballsocketparams_t& ballsocket ) override; + IPhysicsConstraint* CreatePulleyConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_pulleyparams_t& pulley ) override; + IPhysicsConstraint* CreateLengthConstraint( IPhysicsObject* pReferenceObject, IPhysicsObject* pAttachedObject, IPhysicsConstraintGroup* pGroup, const constraint_lengthparams_t& length ) override; + + void DestroyConstraint( IPhysicsConstraint* ) override; + + IPhysicsConstraintGroup* CreateConstraintGroup( const constraint_groupparams_t& groupParams ) override; + void DestroyConstraintGroup( IPhysicsConstraintGroup* pGroup ) override; + + IPhysicsShadowController* CreateShadowController( IPhysicsObject* pObject, bool allowTranslation, bool allowRotation ) override; + void DestroyShadowController( IPhysicsShadowController* ) override; + + IPhysicsPlayerController* CreatePlayerController( IPhysicsObject* pObject ) override; + void DestroyPlayerController( IPhysicsPlayerController* ) override; + + IPhysicsMotionController* CreateMotionController( IMotionEvent* pHandler ) override; + void DestroyMotionController( IPhysicsMotionController* pController ) override; + + IPhysicsVehicleController* CreateVehicleController( IPhysicsObject* pVehicleBodyObject, const vehicleparams_t& params, unsigned int nVehicleType, IPhysicsGameTrace* pGameTrace ) override; + void DestroyVehicleController( IPhysicsVehicleController* ) override; + + void SetCollisionSolver( IPhysicsCollisionSolver* pSolver ) override; + + void Simulate( float deltaTime ) override; + bool IsInSimulation() const override; + + float GetSimulationTimestep() const override; + void SetSimulationTimestep( float timestep ) override; + + float GetSimulationTime() const override; + void ResetSimulationClock() override; + float GetNextFrameTime() const override; + + void SetCollisionEventHandler( IPhysicsCollisionEvent* pCollisionEvents ) override; + void SetObjectEventHandler( IPhysicsObjectEvent* pObjectEvents ) override; + virtual void SetConstraintEventHandler( IPhysicsConstraintEvent* pConstraintEvents ) override; + + void SetQuickDelete( bool bQuick ) override; + + int GetActiveObjectCount() const override; + void GetActiveObjects( IPhysicsObject** pOutputObjectList ) const override; + const IPhysicsObject** GetObjectList( int* pOutputObjectCount ) const override; + bool TransferObject( IPhysicsObject* pObject, IPhysicsEnvironment* pDestinationEnvironment ) override; + + void CleanupDeleteList() override; + void EnableDeleteQueue( bool enable ) override; + + bool Save( const physsaveparams_t& params ) override; + void PreRestore( const physprerestoreparams_t& params ) override; + bool Restore( const physrestoreparams_t& params ) override; + void PostRestore() override; + + bool IsCollisionModelUsed( CPhysCollide* pCollide ) const override; + + void TraceRay( const Ray_t& ray, unsigned int fMask, IPhysicsTraceFilter* pTraceFilter, trace_t* pTrace ) override; + void SweepCollideable( const CPhysCollide* pCollide, const Vector& vecAbsStart, const Vector& vecAbsEnd, + const QAngle& vecAngles, unsigned int fMask, IPhysicsTraceFilter* pTraceFilter, trace_t* pTrace ) override; + + void GetPerformanceSettings( physics_performanceparams_t* pOutput ) const override; + void SetPerformanceSettings( const physics_performanceparams_t* pSettings ) override; + + void ReadStats( physics_stats_t* pOutput ) override; + void ClearStats() override; + + unsigned int GetObjectSerializeSize( IPhysicsObject* pObject ) const override; + void SerializeObjectToBuffer( IPhysicsObject* pObject, unsigned char* pBuffer, unsigned int bufferSize ) override; + IPhysicsObject* UnserializeObjectFromBuffer( void* pGameData, unsigned char* pBuffer, unsigned int bufferSize, bool enableCollisions ) override; + + void EnableConstraintNotify( bool bEnable ) override; + void DebugCheckContacts() override; + + void SetAlternateGravity( const Vector& gravityVector ) override_asw; + void GetAlternateGravity( Vector* pGravityVector ) const override_asw; + + float GetDeltaFrameTime( int maxTicks ) const override_asw; + void ForceObjectsToSleep( IPhysicsObject** pList, int listCount ) override_asw; + + void SetPredicted( bool bPredicted ) override_portal2; + bool IsPredicted() override_portal2; + void SetPredictionCommandNum( int iCommandNum ) override_portal2; + int GetPredictionCommandNum() override_portal2; + void DoneReferencingPreviousCommands( int iCommandNum ) override_portal2; + void RestorePredictedSimulation() override_portal2; + + void DestroyCollideOnDeadObjectFlush( CPhysCollide* ) override_portal2; + +public: + JPH::PhysicsSystem* GetPhysicsSystem() { return &m_PhysicsSystem; } + + void ObjectTransferHandOver( JoltPhysicsObject* pObject ); + + JoltPhysicsContactListener* GetContactListener() { return &m_ContactListener; } + + IPhysicsConstraintEvent* GetConstraintEvents() { return m_pConstraintListener; } + + void NotifyConstraintDisabled( JoltPhysicsConstraint* pConstraint ); + +private: + + void RemoveBodyAndDeleteObject( JoltPhysicsObject* pObject ); + void DeleteDeadObjects(); + + template + void AddPhysicsSaveRestorePointer( uintp oldPtr, T* newPtr ); + + template + T* LookupPhysicsSaveRestorePointer( uintp oldPtr ); + + void HandleDebugDumpingEnvironment( void* pReturnAddress ); + + bool m_bSimulating = false; + bool m_bEnableDeleteQueue = false; + bool m_bWakeObjectsOnConstraintDeletion = false; + bool m_bOptimizedBroadPhase = false; + bool m_bUseLinearCast = true; + float m_flStepTime = 1.0f / 60.0f; + float m_flAirDensity = 2.0f; + + static JoltBPLayerInterfaceImpl s_BPLayerInterface; + + // For GetObjectList + mutable JPH::BodyIDVector m_CachedBodies; + mutable std::vector< const IPhysicsObject * > m_CachedObjects; + + // For GetActiveObjectCount and GetActiveObjects + mutable JPH::BodyIDVector m_CachedActiveBodies; + + JPH::PhysicsSystem m_PhysicsSystem; + + std::vector< JoltPhysicsObject * > m_pDeadObjects; + std::vector< JoltPhysicsConstraint * > m_pDeadConstraints; + std::vector< CPhysCollide * > m_pDeadObjectCollides; + + CUtlVector< IJoltPhysicsController * > m_pPhysicsControllers; + + std::unordered_map< uintp, void * > m_SaveRestorePointerMap; + + // The physics system that simulates the world + // The debug overlay to render with (if it was ever passed to us) + IVJoltDebugOverlay *m_pDebugOverlay = nullptr; + + JoltPhysicsContactListener m_ContactListener; + IPhysicsConstraintEvent *m_pConstraintListener = nullptr; + + bool m_EnableConstraintNotify = false; + + mutable bool m_bActiveObjectCountFirst = true; +}; diff --git a/vphysics_jolt/vjolt_friction.cpp b/vphysics_jolt/vjolt_friction.cpp new file mode 100644 index 0000000..983d497 --- /dev/null +++ b/vphysics_jolt/vjolt_friction.cpp @@ -0,0 +1,99 @@ + +#include "cbase.h" + +#include "vjolt_friction.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Josh: +// Friction snapshots are used for the following: +// - Making objects on NPCs phase through them after some time +// - Something something physics damage, but that seems to work anyway... :think: +// - Game code callback for freezing objects if too many objects (IVP performance backdoor) -- not relevant for us +// - Disabling last portal stuff for objects after teleporting, but our trigger callbacks are robust so this game code hack doesn't seem to matter *touch wood* +// - Something to do with changing paint powers +// - Train physics blockers +// All in all, it doesn't seem to be *that* important, most things still work +// without it implemented. +// Right now, we do not have an efficient way to implement this with Jolt. +// Hence it is just stubby. + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsFrictionSnapshot::IsValid() +{ + return false; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsObject *JoltPhysicsFrictionSnapshot::GetObject( int index ) +{ + return nullptr; +} + +int JoltPhysicsFrictionSnapshot::GetMaterial( int index ) +{ + return 0; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFrictionSnapshot::GetContactPoint( Vector &out ) +{ + out.Zero(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFrictionSnapshot::GetSurfaceNormal( Vector &out ) +{ + out.Zero(); +} + +float JoltPhysicsFrictionSnapshot::GetNormalForce() +{ + return 0.0f; +} + +float JoltPhysicsFrictionSnapshot::GetEnergyAbsorbed() +{ + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFrictionSnapshot::RecomputeFriction() +{ + +} + +void JoltPhysicsFrictionSnapshot::ClearFrictionForce() +{ + +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFrictionSnapshot::MarkContactForDelete() +{ + +} + +void JoltPhysicsFrictionSnapshot::DeleteAllMarkedContacts( bool wakeObjects ) +{ + +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsFrictionSnapshot::NextFrictionData() +{ + +} + +float JoltPhysicsFrictionSnapshot::GetFrictionCoefficient() +{ + return 0.0f; +} diff --git a/vphysics_jolt/vjolt_friction.h b/vphysics_jolt/vjolt_friction.h new file mode 100644 index 0000000..6e77167 --- /dev/null +++ b/vphysics_jolt/vjolt_friction.h @@ -0,0 +1,26 @@ + +#pragma once + +class JoltPhysicsFrictionSnapshot final : public IPhysicsFrictionSnapshot +{ +public: + bool IsValid() override; + + IPhysicsObject *GetObject( int index ) override; + int GetMaterial( int index ) override; + + void GetContactPoint( Vector &out ) override; + + void GetSurfaceNormal( Vector &out ) override; + float GetNormalForce() override; + float GetEnergyAbsorbed() override; + + void RecomputeFriction() override; + void ClearFrictionForce() override; + + void MarkContactForDelete() override; + void DeleteAllMarkedContacts( bool wakeObjects ) override; + + void NextFrictionData() override; + float GetFrictionCoefficient() override; +}; diff --git a/vphysics_jolt/vjolt_interface.cpp b/vphysics_jolt/vjolt_interface.cpp new file mode 100644 index 0000000..49c6db0 --- /dev/null +++ b/vphysics_jolt/vjolt_interface.cpp @@ -0,0 +1,201 @@ +//================================================================================================= +// +// The base physics DLL interface +// +//================================================================================================= + +#include "cbase.h" + +#include "vjolt_environment.h" +#include "vjolt_collide.h" +#include "vjolt_surfaceprops.h" +#include "vjolt_objectpairhash.h" + +#include "vjolt_interface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +// Slart: +// Pre-allocate 64 megabytes for physics allocations. +// I don't think we've tuned this value. It's just a big number that we probably won't ever hit. +static constexpr uint kTempAllocSize = 64 * 1024 * 1024; + +// Josh: +// We cannot support more than 64 threads doing physics work because +// of the code I wrote in vjolt_listener_contact to dispatch events. +// It uses a single uint64_t bitmask that is iterated on for the thread-local +// event vectors. +// This isn't an issue, the benefits of more threads tends to trail off between +// 8-16 threads anyway. +static constexpr uint kMaxPhysicsThreads = 64; + +DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_VJolt, "VJolt", 0, LS_MESSAGE, Color( 205, 142, 212, 255 ) ); +DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_JoltInternal, "Jolt" ); + +JoltPhysicsInterface JoltPhysicsInterface::s_PhysicsInterface; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( JoltPhysicsInterface, IPhysics, VPHYSICS_INTERFACE_VERSION, JoltPhysicsInterface::GetInstance() ); + +//------------------------------------------------------------------------------------------------- + +// Slart: +// Instead of using Jolt's allocator override functionality, we disable it and just define the +// functions here, all of Jolt's memory allocation goes through here, besides new and delete +// which use the Valve overrides in memoverride.cpp. +// For Desolation we use mi-malloc rather than dlmalloc, that also gets built into the statically +// linked releases for gmod (along with all of tier0 and vstdlib). +namespace JPH { + + void *Allocate( size_t inSize ) + { + return MemAlloc_Alloc( inSize ); + } + + void Free( void *inBlock ) + { + MemAlloc_Free( inBlock ); + } + + void *AlignedAllocate( size_t inSize, size_t inAlignment ) + { + return MemAlloc_AllocAligned( inSize, inAlignment ); + } + + void AlignedFree( void *inBlock ) + { + MemAlloc_FreeAligned( inBlock ); + } +} + +//------------------------------------------------------------------------------------------------- + +InitReturnVal_t JoltPhysicsInterface::Init() +{ + const InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + { + return nRetVal; + } + + MathLib_Init(); + + // Install callbacks + JPH::Trace = JoltPhysicsInterface::OnTrace; + JPH_IF_ENABLE_ASSERTS( JPH::AssertFailed = JoltPhysicsInterface::OnAssert; ) + + // Create a factory + JPH::Factory::sInstance = new JPH::Factory(); + + // Register all Jolt physics types + JPH::RegisterTypes(); + + // Create an allocator for temporary allocations during physics simulations + m_pTempAllocator = new JPH::TempAllocatorImpl( kTempAllocSize ); + + // Josh: + // We may want to replace this with a better heuristic, or add a launch arg for this in future. + // Right now, this does what -1 does in Jolt, but limits it to 64 threads, as we cannot support + // more than this (see above). + const uint32 threadCount = Min( std::thread::hardware_concurrency() - 1, kMaxPhysicsThreads ); + m_pJobSystem = new JPH::JobSystemThreadPool( JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, threadCount ); + + return INIT_OK; +} + +void JoltPhysicsInterface::Shutdown() +{ + delete m_pJobSystem; + delete m_pTempAllocator; + delete JPH::Factory::sInstance; + + BaseClass::Shutdown(); +} + +void *JoltPhysicsInterface::QueryInterface( const char *pInterfaceName ) +{ + CreateInterfaceFn factory = Sys_GetFactoryThis(); + return factory( pInterfaceName, NULL ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsEnvironment *JoltPhysicsInterface::CreateEnvironment() +{ + return new JoltPhysicsEnvironment(); +} + +void JoltPhysicsInterface::DestroyEnvironment( IPhysicsEnvironment *pEnvironment ) +{ + delete static_cast( pEnvironment ); +} + +IPhysicsEnvironment *JoltPhysicsInterface::GetActiveEnvironmentByIndex( int index ) +{ + // Josh: Nothing uses this... ever. + Log_Stub( LOG_VJolt ); + return nullptr; +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsObjectPairHash *JoltPhysicsInterface::CreateObjectPairHash() +{ + return new JoltPhysicsObjectPairHash; +} + +void JoltPhysicsInterface::DestroyObjectPairHash( IPhysicsObjectPairHash *pHash ) +{ + delete static_cast( pHash ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsCollisionSet *JoltPhysicsInterface::FindOrCreateCollisionSet( unsigned int id, int maxElementCount ) +{ + if ( maxElementCount > 32 ) + return nullptr; + + if ( IPhysicsCollisionSet *pSet = FindCollisionSet( id ) ) + return pSet; + + auto result = m_CollisionSets.emplace( id, JoltPhysicsCollisionSet{} ); + return &result.first->second; +} + +IPhysicsCollisionSet *JoltPhysicsInterface::FindCollisionSet( unsigned int id ) +{ + auto iter = m_CollisionSets.find( id ); + if ( iter != m_CollisionSets.end() ) + return &iter->second; + + return nullptr; +} + +void JoltPhysicsInterface::DestroyAllCollisionSets() +{ + m_CollisionSets.clear(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsInterface::OnTrace( const char *fmt, ... ) +{ + va_list args; + char msg[MAX_LOGGING_MESSAGE_LENGTH]; + + va_start( args, fmt ); + V_vsnprintf( msg, sizeof( msg ), fmt, args ); + va_end( args ); + + Log_Msg( LOG_JoltInternal, "%s\n", msg ); +} + +bool JoltPhysicsInterface::OnAssert( const char *inExpression, const char *inMessage, const char *inFile, uint inLine ) +{ + const char *message = inMessage ? inMessage : inExpression; + (void) message; + AssertMsg_Internal( false, inLine, inFile, message ); + return false; +} diff --git a/vphysics_jolt/vjolt_interface.h b/vphysics_jolt/vjolt_interface.h new file mode 100644 index 0000000..d6fe58f --- /dev/null +++ b/vphysics_jolt/vjolt_interface.h @@ -0,0 +1,130 @@ +//================================================================================================= +// +// The base physics DLL interface +// +//================================================================================================= + +#pragma once + +//------------------------------------------------------------------------------------------------- + +// Whether to use the VPhysics debug overlay (legacy compatible) or the fully featured IVDebugOverlay +// In Desolation, IVDebugOverlay has new members that allow us to draw IMesh objects, without this +// debugoverlay rendering is incredibly inefficient (and may run the materialsystem mempool out of memory) +#if !defined( GAME_DESOLATION ) || defined( BUILD_FOR_EXTERNAL_GAME ) +#define VJOLT_USE_PHYSICS_DEBUG_OVERLAY +#endif + +#ifdef VJOLT_USE_PHYSICS_DEBUG_OVERLAY +class IVPhysicsDebugOverlay; +using IVJoltDebugOverlay = IVPhysicsDebugOverlay; +#define VJOLT_DEBUG_OVERLAY_VERSION VPHYSICS_DEBUG_OVERLAY_INTERFACE_VERSION +#else +class IVDebugOverlay; +using IVJoltDebugOverlay = IVDebugOverlay; +#define VJOLT_DEBUG_OVERLAY_VERSION VDEBUG_OVERLAY_INTERFACE_VERSION +#endif + +// Call this in stubbed functions to spew when they're hit +#if 1 // DEVELOPMENT_ONLY +#define Log_Stub( Channel ) +#else +#define Log_Stub( Channel ) \ + Log_Warning( Channel, "Stub: %s\n", __FUNCTION__ ) +#endif + +// So we can toggle assertions in this module at our discretion +#if DEVELOPMENT_ONLY +#define VJoltAssert DevAssert +#define VJoltAssertMsg DevAssertMsg +#else +#define VJoltAssert Assert +#define VJoltAssertMsg AssertMsg +#endif + +DECLARE_LOGGING_CHANNEL( LOG_VJolt ); // For our vphysics_jolt code +DECLARE_LOGGING_CHANNEL( LOG_JoltInternal ); // For Jolt's traces/assertions. Do NOT use for our code. + +//------------------------------------------------------------------------------------------------- + +class JoltPhysicsCollisionSet final : public IPhysicsCollisionSet +{ +public: + + void EnableCollisions( int index0, int index1 ) override + { + m_Bits[ index0 ] |= 1u << index1; + m_Bits[ index1 ] |= 1u << index0; + } + + void DisableCollisions( int index0, int index1 ) override + { + m_Bits[ index0 ] &= ~( 1u << index1 ); + m_Bits[ index1 ] &= ~( 1u << index0 ); + } + + bool ShouldCollide( int index0, int index1 ) override + { + return !!( m_Bits[ index0 ] & ( 1u << index1 ) ); + } + +private: + + std::array< uint32, 32 > m_Bits = {}; + +}; + +//------------------------------------------------------------------------------------------------- + +class JoltPhysicsInterface final : public CTier1AppSystem +{ +private: + using BaseClass = CTier1AppSystem; + +public: + InitReturnVal_t Init() override; + void Shutdown() override; + void *QueryInterface( const char *pInterfaceName ) override; + + IPhysicsEnvironment *CreateEnvironment() override; + void DestroyEnvironment( IPhysicsEnvironment *pEnvironment ) override; + IPhysicsEnvironment *GetActiveEnvironmentByIndex( int index ) override; + + IPhysicsObjectPairHash *CreateObjectPairHash() override; + void DestroyObjectPairHash( IPhysicsObjectPairHash *pHash ) override; + + IPhysicsCollisionSet *FindOrCreateCollisionSet( unsigned int id, int maxElementCount ) override; + IPhysicsCollisionSet *FindCollisionSet( unsigned int id ) override; + void DestroyAllCollisionSets() override; + +public: + static JoltPhysicsInterface &GetInstance() { return s_PhysicsInterface; } + JPH::TempAllocator *GetTempAllocator() { return m_pTempAllocator; } + JPH::JobSystem *GetJobSystem() { return m_pJobSystem; } + + void SetDebugOverlay( IVJoltDebugOverlay *pOverlay ) { if ( m_pDebugOverlay != pOverlay ) m_pDebugOverlay = pOverlay; } + IVJoltDebugOverlay *GetDebugOverlay() { return m_pDebugOverlay; } + +private: + static void OnTrace( const char *fmt, ... ); + static bool OnAssert( const char *inExpression, const char *inMessage, const char *inFile, uint inLine ); + + std::unordered_map< unsigned int, JoltPhysicsCollisionSet > m_CollisionSets; + + // We need a temp allocator for temporary allocations during the physics update. We're + // pre-allocating 10 MB to avoid having to do allocations during the physics update. + // B.t.w. 10 MB is way too much for this example but it is a typical value you can use. + // If you don't want to pre-allocate you can also use TempAllocatorMalloc to fall back to + // malloc / free. + JPH::TempAllocator *m_pTempAllocator; + + // We need a job system that will execute physics jobs on multiple threads. Typically + // you would implement the JobSystem interface yourself and let Jolt Physics run on top + // of your own job scheduler. JobSystemThreadPool is an example implementation. + JPH::JobSystem *m_pJobSystem; + + // For debugging stuff in collide and such. + IVJoltDebugOverlay *m_pDebugOverlay = nullptr; + + static JoltPhysicsInterface s_PhysicsInterface; +}; diff --git a/vphysics_jolt/vjolt_internal_listeners.h b/vphysics_jolt/vjolt_internal_listeners.h new file mode 100644 index 0000000..ed3cc4f --- /dev/null +++ b/vphysics_jolt/vjolt_internal_listeners.h @@ -0,0 +1,24 @@ + +#pragma once + +class JoltPhysicsObject; + +abstract_class IJoltPhysicsController +{ +public: + virtual ~IJoltPhysicsController() {} + + // Called before the simulation is run + virtual void OnPreSimulate( float flDeltaTime ) {}; + // Called after the simulation is run + virtual void OnPostSimulate( float flDeltaTime ) {}; +}; + +abstract_class IJoltObjectDestroyedListener +{ +public: + virtual ~IJoltObjectDestroyedListener() {} + + // Called whenever a physics object is destroyed + virtual void OnJoltPhysicsObjectDestroyed( JoltPhysicsObject *pObject ) = 0; +}; diff --git a/vphysics_jolt/vjolt_keyvalues_schema.cpp b/vphysics_jolt/vjolt_keyvalues_schema.cpp new file mode 100644 index 0000000..d0b9cf2 --- /dev/null +++ b/vphysics_jolt/vjolt_keyvalues_schema.cpp @@ -0,0 +1,238 @@ + +#include "cbase.h" + +#include "vjolt_surfaceprops.h" + +#include "vjolt_keyvalues_schema.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +const JoltKVSchemaFunc_t FillBaseProp = +{ + sizeof( surfacedata_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + surfacedata_t *pSurfaceDataPtr = reinterpret_cast< surfacedata_t * >( pPtr ); + + int nSurfaceIndex = JoltPhysicsSurfaceProps::GetInstance().GetSurfaceIndex( pProp->GetString() ); + if ( nSurfaceIndex == -1 ) + { + Log_Warning( LOG_VJolt, "You must specify the base material %s before it can be used. Defaulting to 'default' as a base.\n", pProp->GetString() ); + // Always the default material. + nSurfaceIndex = 0; + } + + *pSurfaceDataPtr = *JoltPhysicsSurfaceProps::GetInstance().GetSurfaceData( nSurfaceIndex ); + } +}; + +const JoltKVSchemaFunc_t FillStringProp = +{ + 0, // Varies. + []( KeyValues *pProp, void *pPtr, size_t size ) + { + char *pszStringPtr = reinterpret_cast< char * >( pPtr ); + V_strncpy( pszStringPtr, pProp->GetString(), static_cast< strlen_t >( size ) ); + } +}; + +const JoltKVSchemaFunc_t FillIntProp = +{ + sizeof( int ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + int *pIntPtr = reinterpret_cast< int * >( pPtr ); + *pIntPtr = pProp->GetInt(); + } +}; + +const JoltKVSchemaFunc_t FillFloatProp = +{ + sizeof( float ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + float *pFloatPtr = reinterpret_cast< float * >( pPtr ); + *pFloatPtr = pProp->GetFloat(); + } +}; + +const JoltKVSchemaFunc_t FillUnsignedCharProp = +{ + sizeof( unsigned char ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + unsigned char *pCharPtr = reinterpret_cast< unsigned char * >( pPtr ); + *pCharPtr = static_cast< unsigned char >( pProp->GetInt() ); + } +}; + +const JoltKVSchemaFunc_t FillBoolProp = +{ + sizeof( bool ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + bool *pBoolPtr = reinterpret_cast< bool * >( pPtr ); + *pBoolPtr = pProp->GetBool(); + } +}; + +const JoltKVSchemaFunc_t FillVectorProp = +{ + sizeof( Vector ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + Vector *pVectorPtr = reinterpret_cast< Vector * >( pPtr ); + sscanf( pProp->GetString(), "%f %f %f", &pVectorPtr->x, &pVectorPtr->y, &pVectorPtr->z ); + } +}; + +const JoltKVSchemaFunc_t FillVector4DProp = +{ + sizeof( Vector4D ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + Vector4D *pVector4DPtr = reinterpret_cast< Vector4D * >( pPtr ); + sscanf( pProp->GetString(), "%f %f %f %f", &pVector4DPtr->x, &pVector4DPtr->y, &pVector4DPtr->z, &pVector4DPtr->w ); + } +}; + +const JoltKVSchemaFunc_t FillQAngleProp = +{ + sizeof( QAngle ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + QAngle *pQAnglePtr = reinterpret_cast< QAngle * >( pPtr ); + sscanf( pProp->GetString(), "%f %f %f", &pQAnglePtr->x, &pQAnglePtr->y, &pQAnglePtr->z ); + } +}; + +const JoltKVSchemaFunc_t FillIntPairProp = +{ + sizeof( JoltPhysicsIntPair ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + JoltPhysicsIntPair *pIntPairPtr = reinterpret_cast< JoltPhysicsIntPair * >( pPtr ); + sscanf( pProp->GetString(), "%d,%d", &pIntPairPtr->Index0, &pIntPairPtr->Index1 ); + } +}; + +const JoltKVSchemaFunc_t FillGameMaterialProp = +{ + sizeof( unsigned short ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + const char *pValue = pProp->GetString(); + unsigned short *pShortPtr = reinterpret_cast< unsigned short * >( pPtr ); + + if ( V_strlen( pValue ) == 1 && !V_isdigit( pValue[ 0 ] ) ) + *pShortPtr = FastASCIIToUpper( pValue[ 0 ] ); + else + *pShortPtr = pProp->GetInt(); + } +}; + +const JoltKVSchemaFunc_t FillSoundProp = +{ + sizeof( unsigned short ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + const char *pValue = pProp->GetString(); + unsigned short *pShortPtr = reinterpret_cast< unsigned short * >( pPtr ); + + *pShortPtr = JoltPhysicsSurfaceProps::GetInstance().RegisterSound( pValue ); + } +}; + +const JoltKVSchemaFunc_t FillSurfaceProp = +{ + sizeof( int ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + const char* pValue = pProp->GetString(); + int *pIntPtr = reinterpret_cast< int * >( pPtr ); + + *pIntPtr = JoltPhysicsSurfaceProps::GetInstance().GetSurfaceIndex( pValue ); + } +}; + +//------------------------------------------------------------------------------------------------- + +void ParseJoltKVSchema( KeyValues *pKV, const JoltKVSchemaProp_t *pDescs, uint count, void *pObj, void *pUnknownKeyObj, IVPhysicsKeyHandler *pUnknownKeyHandler ) +{ + bool bHandled = false; + + for ( KeyValues* pProp = pKV->GetFirstSubKey(); pProp != nullptr; pProp = pProp->GetNextKey() ) + { + const char *pName = pProp->GetName(); + + for ( uint i = 0; i < count; i++ ) + { + const JoltKVSchemaProp_t &desc = pDescs[ i ]; + + if ( !V_stricmp( pName, desc.pszName ) ) + { + VJoltAssertMsg( desc.func.ptr_size == 0 || desc.size == desc.func.ptr_size, "Desc element size does not match function size"); + + int *pArraySize = nullptr; + if ( desc.arrayOffset != static_cast( ~0llu ) ) + pArraySize = reinterpret_cast< int * >( reinterpret_cast< char * >( pObj ) + desc.arrayOffset ); + + char* pElement = reinterpret_cast< char * >( pObj ) + desc.offset; + if ( pArraySize ) + pElement += *pArraySize * desc.size; + + desc.func.ReadFunc( pProp, reinterpret_cast< void * >( pElement ), desc.size); + if ( desc.fixupFunc ) + desc.fixupFunc( pObj ); + if ( pArraySize ) + ( *pArraySize )++; + bHandled = true; + } + } + + if ( !bHandled && pUnknownKeyHandler ) + pUnknownKeyHandler->ParseKeyValue( pUnknownKeyObj, pName, pProp->GetString() ); + } +} + +void ParseJoltKVCustom( KeyValues *pKV, void *pUnknownKeyObj, IVPhysicsKeyHandler* pUnknownKeyHandler ) +{ + // Josh: + // Parse out custom KV entries like "vehicle_sounds" etc + // out recursively. + + for ( KeyValues* pProp = pKV->GetFirstSubKey(); pProp != nullptr; pProp = pProp->GetNextKey() ) + { + if ( pUnknownKeyHandler ) + pUnknownKeyHandler->ParseKeyValue( pUnknownKeyObj, pProp->GetName(), pProp->GetString()); + + ParseJoltKVCustom( pProp, pUnknownKeyObj, pUnknownKeyHandler ); + } +} + +//------------------------------------------------------------------------------------------------- + +KeyValues *HeaderlessKVBufferToKeyValues( const char *pszBuffer, const char *pszSetName ) +{ + CUtlBuffer buffer; + buffer.SetBufferType( true, true ); + + buffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + buffer.PutString( "\"PhysProps\"\r\n{" ); + buffer.PutString( pszBuffer ); + buffer.PutString( "\r\n}" ); + buffer.PutChar( '\0' ); + + KeyValues *pszKV = new KeyValues( pszSetName ); + + if ( !pszKV->LoadFromBuffer( pszSetName, buffer ) ) + { + pszKV->deleteThis(); + pszKV = nullptr; + } + + return pszKV; +} diff --git a/vphysics_jolt/vjolt_keyvalues_schema.h b/vphysics_jolt/vjolt_keyvalues_schema.h new file mode 100644 index 0000000..a8c59c5 --- /dev/null +++ b/vphysics_jolt/vjolt_keyvalues_schema.h @@ -0,0 +1,56 @@ + +#pragma once + +// This function fixes up the base object +// after loading a single KV value. +using JoltKVSchemaFixupFunc_t = void (*)( void *pBaseObject ); + +struct JoltKVSchemaFunc_t +{ + size_t ptr_size; + void ( *ReadFunc )( KeyValues *pProp, void *pPtr, size_t size ); +}; + +struct JoltKVSchemaProp_t +{ + const char *pszName; + size_t offset; + size_t size; + size_t arrayOffset; + JoltKVSchemaFunc_t func; + + JoltKVSchemaFixupFunc_t fixupFunc; +}; + +struct JoltPhysicsIntPair +{ + int Index0; + int Index1; +}; + +extern const JoltKVSchemaFunc_t FillBaseProp; +extern const JoltKVSchemaFunc_t FillStringProp; +extern const JoltKVSchemaFunc_t FillIntProp; +extern const JoltKVSchemaFunc_t FillFloatProp; +extern const JoltKVSchemaFunc_t FillUnsignedCharProp; +extern const JoltKVSchemaFunc_t FillBoolProp; +extern const JoltKVSchemaFunc_t FillVectorProp; +extern const JoltKVSchemaFunc_t FillVector4DProp; +extern const JoltKVSchemaFunc_t FillQAngleProp; +extern const JoltKVSchemaFunc_t FillIntPairProp; +extern const JoltKVSchemaFunc_t FillGameMaterialProp; +extern const JoltKVSchemaFunc_t FillSurfaceProp; +extern const JoltKVSchemaFunc_t FillSoundProp; + +#define KVSCHEMA_DESC_ARRAY( type, x, len ) \ + offsetof( type, x ), sizeof( *type::x ), offsetof( type, len ) + +#define KVSCHEMA_DESC( type, x ) \ + offsetof( type, x ), sizeof( type::x ), static_cast(~0llu) + +#define KVSCHEMA_DESC_NO_OFFSET( type ) \ + 0, sizeof( type ), static_cast(~0llu) + +void ParseJoltKVSchema( KeyValues *pKV, const JoltKVSchemaProp_t *pDescs, uint count, void *pObj, void *pUnknownKeyObj = nullptr, IVPhysicsKeyHandler* pUnknownKeyHandler = nullptr ); +void ParseJoltKVCustom( KeyValues *pKV, void *pUnknownKeyObj, IVPhysicsKeyHandler* pUnknownKeyHandler ); +KeyValues *HeaderlessKVBufferToKeyValues( const char *pszBuffer, const char *pszSetName ); diff --git a/vphysics_jolt/vjolt_layers.h b/vphysics_jolt/vjolt_layers.h new file mode 100644 index 0000000..3b24ee6 --- /dev/null +++ b/vphysics_jolt/vjolt_layers.h @@ -0,0 +1,30 @@ + +#pragma once + +// Layer that objects can be in, determines which other objects it can collide with +// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more +// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation +// but only if you do collision testing). +namespace Layers +{ + inline constexpr uint8 NON_MOVING_WORLD = 0; + inline constexpr uint8 NON_MOVING_OBJECT = 1; + inline constexpr uint8 MOVING = 2; + inline constexpr uint8 NO_COLLIDE = 3; // Disables collisions with everything, including the world. + inline constexpr uint8 DEBRIS = 4; // Disables collisions with everything except the world + inline constexpr uint8 NUM_LAYERS = 5; +}; + +// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have +// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame. +// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have +// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune +// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY. +namespace BroadPhaseLayers +{ + inline constexpr JPH::BroadPhaseLayer NON_MOVING_WORLD( 0 ); + inline constexpr JPH::BroadPhaseLayer NON_MOVING_OBJECT( 1 ); + inline constexpr JPH::BroadPhaseLayer MOVING( 2 ); + inline constexpr JPH::BroadPhaseLayer NO_COLLIDE( 3 ); + inline constexpr JPH::BroadPhaseLayer DEBRIS( 4 ); +}; diff --git a/vphysics_jolt/vjolt_listener_contact.h b/vphysics_jolt/vjolt_listener_contact.h new file mode 100644 index 0000000..99af5d3 --- /dev/null +++ b/vphysics_jolt/vjolt_listener_contact.h @@ -0,0 +1,572 @@ + +#pragma once + +#include "vjolt_controller_fluid.h" + +struct JoltPhysicsContactPair +{ + JoltPhysicsObject *pObject1 = nullptr; + JoltPhysicsObject *pObject2 = nullptr; +}; + +enum VPhysicsGameFlags : uint32 +{ + FVPHYSICS_DMG_SLICE = 0x0001, + FVPHYSICS_CONSTRAINT_STATIC = 0x0002, + FVPHYSICS_PLAYER_HELD = 0x0004, + FVPHYSICS_PART_OF_RAGDOLL = 0x0008, + FVPHYSICS_MULTIOBJECT_ENTITY = 0x0010, + FVPHYSICS_HEAVY_OBJECT = 0x0020, + FVPHYSICS_PENETRATING = 0x0040, + FVPHYSICS_NO_PLAYER_PICKUP = 0x0080, + FVPHYSICS_WAS_THROWN = 0x0100, + FVPHYSICS_DMG_DISSOLVE = 0x0200, + FVPHYSICS_NO_IMPACT_DMG = 0x0400, + FVPHYSICS_NO_NPC_IMPACT_DMG = 0x0800, + FVPHYSICS_PUSH_PLAYER = 0x1000, + FVPHYSICS_NO_SELF_COLLISIONS = 0x8000, +}; + +class JoltPhysicsContactListener final : public JPH::ContactListener +{ +public: + JoltPhysicsContactListener( JPH::PhysicsSystem &physicsSystem ) + : m_PhysicsSystem( physicsSystem ) + { + } + + JPH::ValidateResult OnContactValidate( const JPH::Body &inBody1, const JPH::Body &inBody2, const JPH::CollideShapeResult &inCollisionResult ) override + { + return JPH::ValidateResult::AcceptAllContactsForThisBodyPair; + } + + void OnContactAdded( const JPH::Body &inBody1, const JPH::Body &inBody2, const JPH::ContactManifold &inManifold, JPH::ContactSettings &ioSettings ) override + { + JoltPhysicsObject* pObject1 = reinterpret_cast( inBody1.GetUserData() ); + JoltPhysicsObject* pObject2 = reinterpret_cast( inBody2.GetUserData() ); + + bool bShouldCollide = ShouldCollide( pObject1, pObject2 ); + // If the game says we shouldn't collide, we will treat this as a sensor + // to satisfy the StartTouch/EndTouch events. + ioSettings.mIsSensor = !bShouldCollide || ioSettings.mIsSensor; + + if ( !m_pGameListener ) + return; + + if ( pObject1->IsFluid() || pObject2->IsFluid() ) + { + const uint32 uThreadId = GetThreadId(); + + if ( pObject1->IsFluid() && ( pObject2->GetCallbackFlags() & CALLBACK_FLUID_TOUCH ) ) + m_FluidStartTouchEvents.EmplaceBack( uThreadId, pObject1, pObject2 ); + + if ( pObject2->IsFluid() && ( pObject1->GetCallbackFlags() & CALLBACK_FLUID_TOUCH ) ) + m_FluidStartTouchEvents.EmplaceBack( uThreadId, pObject2, pObject1 ); + + return; + } + + if ( pObject1->IsTrigger() || pObject2->IsTrigger() ) + { + const uint32 uThreadId = GetThreadId(); + + if ( pObject1->IsTrigger() ) + m_EnterTriggerEvents.EmplaceBack( uThreadId, pObject1, pObject2 ); + + if ( pObject2->IsTrigger() ) + m_EnterTriggerEvents.EmplaceBack( uThreadId, pObject2, pObject1 ); + + return; + } + + const bool bIsCollision = bShouldCollide && JoltPhysicsCollisionEvent::IsCollision ( pObject1, pObject2 ); + const bool bIsShadowCollision = bShouldCollide && JoltPhysicsCollisionEvent::IsShadowCollision( pObject1, pObject2 ); + + if ( bIsCollision || bIsShadowCollision ) + { + // Josh: + // We know ahead of time what this is used for (playing sounds and such) + // and it is not easily threadable + // (unlike the StartTouch objects which we can get away as long as the objects themselves aren't concurrent it seems) + // To avoid this causing locks and therefore lagging with many objects, + // we can just know ahead of time what is going to cause a sound to play, which is + // hardcoded at speed > 70.0f and deltaTime < 0.05 (the latter of which we don't track) + // So we can just avoid sending these PreCollision in this case. + + const Vector vecCollideNormal = Vector( inManifold.mWorldSpaceNormal.GetX(), inManifold.mWorldSpaceNormal.GetY(), inManifold.mWorldSpaceNormal.GetZ() ); + const float flCollisionSpeed = JoltPhysicsCollisionEvent::GetCollisionSpeed( pObject1, pObject2, vecCollideNormal ); + + const bool bHasSound = + flCollisionSpeed >= 70.0f && + pObject1->GetGameMaterialAllowsSounds() && + pObject2->GetGameMaterialAllowsSounds(); + + const bool bSane = m_GlobalCollisionEventCount < MaxCollisionEvents; + + const bool bSendCollisionCallback = ( bHasSound && bSane ) || bIsShadowCollision; + + if ( bSendCollisionCallback ) + { + m_CollisionEvents.EmplaceBack( GetThreadId(), JoltPhysicsCollisionInfo( pObject1, pObject2, inManifold ) ); + m_GlobalCollisionEventCount++; + } + } + + if ( ShouldTouchCallback( pObject1, pObject2 ) ) + m_StartTouchEvents.EmplaceBack( GetThreadId(), JoltPhysicsCollisionInfo( pObject1, pObject2, inManifold ) ); + } + + void OnContactPersisted( const JPH::Body &inBody1, const JPH::Body &inBody2, const JPH::ContactManifold &inManifold, JPH::ContactSettings &ioSettings ) override + { + JoltPhysicsObject* pObject1 = reinterpret_cast( inBody1.GetUserData() ); + JoltPhysicsObject* pObject2 = reinterpret_cast( inBody2.GetUserData() ); + + bool bShouldCollide = ShouldCollide( pObject1, pObject2 ); + // If the game says we shouldn't collide, we will treat this as a sensor + // to satisfy the StartTouch/EndTouch events. + ioSettings.mIsSensor = !bShouldCollide || ioSettings.mIsSensor; + + if ( !m_pGameListener ) + return; + + // TODO(Josh): Need a way to calculate the energy to send friction callbacks. + // + //JoltPhysicsObject *pObject1 = reinterpret_cast< JoltPhysicsObject * >( inBody1.GetUserData() ); + //JoltPhysicsObject *pObject2 = reinterpret_cast< JoltPhysicsObject * >( inBody2.GetUserData() ); + // + //if ( !ShouldFrictionCallback( pObject1, pObject2 ) ) + // return; + // + //JoltPhysicsCollisionData data( inManifold ); + //std::unique_lock lock( m_CallbackMutex ); + //m_pGameListener->Friction( pObject1, 15500.0f, pObject1->GetMaterialIndex(), pObject2->GetMaterialIndex(), &data ); + //m_pGameListener->Friction( pObject2, 15500.0f, pObject2->GetMaterialIndex(), pObject1->GetMaterialIndex(), &data ); + } + + void OnContactRemoved( const JPH::SubShapeIDPair &inSubShapePair ) + { + if ( !m_pGameListener ) + return; + + // This is always called with all bodies locked. + const JPH::BodyLockInterfaceNoLock &bodyInterface = m_PhysicsSystem.GetBodyLockInterfaceNoLock(); + + JPH::Body *pBody1 = bodyInterface.TryGetBody( inSubShapePair.GetBody1ID() ); + JPH::Body *pBody2 = bodyInterface.TryGetBody( inSubShapePair.GetBody2ID() ); + + // One of the bodies may have been deleted. + // TODO(Josh): Handle calling end touch when we delete a body. + if ( !pBody1 || !pBody2 ) + return; + + JoltPhysicsObject *pObject1 = reinterpret_cast< JoltPhysicsObject * >( pBody1->GetUserData() ); + JoltPhysicsObject *pObject2 = reinterpret_cast< JoltPhysicsObject * >( pBody2->GetUserData() ); + + if ( pObject1->IsFluid() || pObject2->IsFluid() ) + { + const uint32 uThreadId = GetThreadId(); + + if ( pObject1->IsFluid() && ( pObject2->GetCallbackFlags() & CALLBACK_FLUID_TOUCH ) ) + m_FluidEndTouchEvents.EmplaceBack( uThreadId, pObject1, pObject2 ); + + if ( pObject2->IsFluid() && ( pObject1->GetCallbackFlags() & CALLBACK_FLUID_TOUCH ) ) + m_FluidEndTouchEvents.EmplaceBack( uThreadId, pObject2, pObject1 ); + + return; + } + + if ( pObject1->IsTrigger() || pObject2->IsTrigger() ) + { + const uint32 uThreadId = GetThreadId(); + + if ( pObject1->IsTrigger() ) + m_LeaveTriggerEvents.EmplaceBack( uThreadId, pObject1, pObject2 ); + + if ( pObject2->IsTrigger() ) + m_LeaveTriggerEvents.EmplaceBack( uThreadId, pObject2, pObject1 ); + + return; + } + + if ( !ShouldTouchCallback( pObject1, pObject2 ) ) + return; + + const uint32 uThreadId = GetThreadId(); + + // Josh: + // We don't have any collision data here + // and caching it would be annoying and expensive. + // + // Lucky for us though, the game simply just calls the stuff + // to retrieve the contact point and normal, then just never uses it + // so we can return anything we want and it will change *nothing*! + m_EndTouchEvents.EmplaceBack( uThreadId, JoltPhysicsCollisionInfo( pObject1, pObject2 ) ); + } + + bool ShouldCollide( JoltPhysicsObject *pObject0, JoltPhysicsObject *pObject1 ) + { + VJoltAssert( pObject0 != pObject1 ); + + if ( !pObject0 || !pObject1 ) + return false; + + if ( ( pObject0->GetCallbackFlags() & CALLBACK_ENABLING_COLLISION ) && ( pObject1->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE ) ) + return false; + + if ( ( pObject1->GetCallbackFlags() & CALLBACK_ENABLING_COLLISION ) && ( pObject0->GetCallbackFlags() & CALLBACK_MARKED_FOR_DELETE ) ) + return false; + + if ( !m_pGameSolver ) + return true; + + // Josh: + // Do some work the game does ahead of time to + // avoid needless locking. + if ( !PreEmptGameShouldCollide( pObject0, pObject1 ) ) + return false; + + // Actually ask the game now, locking both bodies so they cannot have + // concurrent ShouldCollide calls. + //JoltPhysicsObjectPairLock lock( pObject0->GetCollisionTestLock(), pObject1->GetCollisionTestLock() ); + std::unique_lock lock( m_ShouldCollideLock ); + return m_pGameSolver->ShouldCollide( pObject0, pObject1, pObject0->GetGameData(), pObject1->GetGameData() ); + } + + bool PreEmptGameShouldCollide( JoltPhysicsObject *pObject0, JoltPhysicsObject *pObject1 ) + { + // This function pre-empts the result of the + // game's ShouldCollide implementation to avoid needless locking. + + // Check if the entities are the same and self-collisions are disabled. + if ( pObject0->GetGameData() == pObject1->GetGameData() ) + { + if ( ( pObject0->GetGameFlags() | pObject1->GetGameFlags() ) & FVPHYSICS_NO_SELF_COLLISIONS ) + return false; + } + + // If both of these are constrained to the world, they shouldn't collide. + if ( pObject0->GetGameFlags() & pObject1->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC ) + return false; + + // We do wheels separately, the IS_VEHICLE_WHEEL is just to have some dummy object to return to the game. + if ( ( pObject0->GetCallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL ) || ( pObject1->GetCallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL ) ) + return false; + + // Two shadow controlled objects should not collide. + if ( pObject0->GetShadowController() && pObject1->GetShadowController() ) + return false; + + return true; + } + + bool ShouldFrictionCallback( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2 ) + { + if ( !( pObject1->GetCallbackFlags() & CALLBACK_GLOBAL_FRICTION ) ) + return false; + + if ( !( pObject2->GetCallbackFlags() & CALLBACK_GLOBAL_FRICTION ) ) + return false; + + return true; + } + + bool ShouldTouchCallback( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2 ) + { + uint32 uFlags = 0; + uFlags |= pObject1->GetCallbackFlags(); + uFlags |= pObject2->GetCallbackFlags(); + + if ( !( uFlags & CALLBACK_GLOBAL_TOUCH ) ) + return false; + + if ( !( uFlags & CALLBACK_GLOBAL_TOUCH_STATIC ) && ( pObject1->IsStatic() || pObject2->IsStatic() ) ) + return false; + + return true; + } + + IPhysicsCollisionEvent *GetGameListener() + { + return m_pGameListener; + } + + void SetGameListener( IPhysicsCollisionEvent *pListener ) + { + m_pGameListener = pListener; + } + + IPhysicsCollisionSolver *GetGameSolver() + { + return m_pGameSolver; + } + + void SetGameSolver( IPhysicsCollisionSolver *pSolver ) + { + m_pGameSolver = pSolver; + } + + void FlushCallbacks() + { + if ( !m_pGameListener ) + return; + + // Send PreCollision events + // + // Don't clear the collision events the first time around as we have + // the post-collisde event to send too! + m_CollisionEvents.ForEach< false >( [ this ]( JoltPhysicsCollisionEvent& event ) + { + // Fake the velocities for the objects during the PreCollision callback so + // we get a proper delta velocity between Pre/Post for damage callbacks to work. + JPH::Vec3 object1Vel = event.m_Data.GetPair().pObject1->FakeJoltLinearVelocity( event.m_Data.GetObject1PreCollisionVelocity() ); + JPH::Vec3 object2Vel = event.m_Data.GetPair().pObject2->FakeJoltLinearVelocity( event.m_Data.GetObject2PreCollisionVelocity() ); + m_pGameListener->PreCollision( &event.m_Event ); + event.m_Data.GetPair().pObject1->RestoreJoltLinearVelocity( object1Vel ); + event.m_Data.GetPair().pObject2->RestoreJoltLinearVelocity( object2Vel ); + }); + + // Send StartTouch events + m_StartTouchEvents.ForEach< true >( [ this ]( JoltPhysicsCollisionData& event ) + { + m_pGameListener->StartTouch( event.GetPair().pObject1, event.GetPair().pObject2, &event ); + }); + + // Send EnterTrigger events + m_EnterTriggerEvents.ForEach< true >( [ this ]( JoltPhysicsContactPair& event ) + { + m_pGameListener->ObjectEnterTrigger( event.pObject1, event.pObject2 ); + }); + + // Send FluidStartTouch events + m_FluidStartTouchEvents.ForEach< true >( [ this ]( JoltPhysicsContactPair& event ) + { + m_pGameListener->FluidStartTouch( event.pObject2, event.pObject1->GetFluidController() ); + }); + + // Send PostCollision events + // + // Clear it this time as we are done with these! + m_CollisionEvents.ForEach< true >( [ this ]( JoltPhysicsCollisionEvent& event ) + { + m_pGameListener->PostCollision( &event.m_Event ); + }); + + // Send EndTouch events + m_EndTouchEvents.ForEach< true >( [ this ]( JoltPhysicsCollisionData& event ) + { + m_pGameListener->EndTouch( event.GetPair().pObject1, event.GetPair().pObject2, &event ); + }); + + // Send LeaveTrigger events + m_LeaveTriggerEvents.ForEach< true >( [ this ]( JoltPhysicsContactPair& event ) + { + m_pGameListener->ObjectLeaveTrigger( event.pObject1, event.pObject2 ); + }); + + // Send FluidEndTouch events + m_FluidEndTouchEvents.ForEach< true >( [ this ]( JoltPhysicsContactPair& event ) + { + m_pGameListener->FluidEndTouch( event.pObject2, event.pObject1->GetFluidController() ); + }); + + // Reset the collision event counter. + m_GlobalCollisionEventCount = 0u; + } + + void PostSimulationFrame() + { + if ( m_pGameListener ) + m_pGameListener->PostSimulationFrame(); + } + +private: + + static uint32 GetThreadId() + { + static thread_local uint32 s_ThreadId = ~0u; + static std::atomic< uint32 > s_ThreadCtr = { 0u }; + if ( s_ThreadId == ~0u ) + s_ThreadId = s_ThreadCtr++; + return s_ThreadId; + } + + const JPH::PhysicsSystem &m_PhysicsSystem; + + IPhysicsCollisionEvent *m_pGameListener = nullptr; + IPhysicsCollisionSolver *m_pGameSolver = nullptr; + + std::mutex m_ShouldCollideLock; + + class JoltPhysicsCollisionInfo + { + public: + JoltPhysicsCollisionInfo( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2 ) + : m_CollisionPair{ pObject1, pObject2 } + { + } + + JoltPhysicsCollisionInfo( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2, const JPH::ContactManifold &inManifold ) + : m_CollisionPair{ pObject1, pObject2 } + // Slart: Note this negated vector, it is important, Portal 2 bouncy paint needs it negated otherwise things fly into the surface they hit + , m_SurfaceNormal( -Vector( inManifold.mWorldSpaceNormal.GetX(), inManifold.mWorldSpaceNormal.GetY(), inManifold.mWorldSpaceNormal.GetZ() ) ) + , m_ContactPoint( JoltToSource::Distance( inManifold.mWorldSpaceContactPointsOn1[0] ) ) + // Unused... + , m_ContactSpeed( vec3_origin ) + , m_Velocity0( pObject1->GetBody()->GetLinearVelocity() ) + , m_Velocity1( pObject1->GetBody()->GetLinearVelocity() ) + { + } + + JoltPhysicsContactPair m_CollisionPair; + + Vector m_SurfaceNormal = vec3_origin; + Vector m_ContactPoint = vec3_origin; + Vector m_ContactSpeed = vec3_origin; + + JPH::Vec3 m_Velocity0 = JPH::Vec3::sZero(); + JPH::Vec3 m_Velocity1 = JPH::Vec3::sZero(); + }; + + class JoltPhysicsCollisionData final : public IPhysicsCollisionData + { + public: + JoltPhysicsCollisionData( const JoltPhysicsCollisionInfo &info ) + : m_CollisionData{ info } + { + } + + void GetSurfaceNormal( Vector &out ) override + { + out = m_CollisionData.m_SurfaceNormal; + } + + void GetContactPoint( Vector &out ) override + { + out = m_CollisionData.m_ContactPoint; + } + + void GetContactSpeed( Vector &out ) override + { + out = m_CollisionData.m_ContactSpeed; + } + + JoltPhysicsContactPair GetPair() const + { + return m_CollisionData.m_CollisionPair; + } + + JPH::Vec3 GetObject1PreCollisionVelocity() const + { + return m_CollisionData.m_Velocity0; + } + + JPH::Vec3 GetObject2PreCollisionVelocity() const + { + return m_CollisionData.m_Velocity1; + } + private: + JoltPhysicsCollisionInfo m_CollisionData; + }; + + class JoltPhysicsCollisionEvent + { + public: + JoltPhysicsCollisionEvent( const JoltPhysicsCollisionInfo &info ) + : m_Data{ info } + { + JoltPhysicsObject *pObject1 = m_Data.GetPair().pObject1; + JoltPhysicsObject* pObject2 = m_Data.GetPair().pObject2; + + m_Event.pObjects[0] = pObject1; + m_Event.pObjects[1] = pObject2; + m_Event.surfaceProps[0] = pObject1->GetMaterialIndex(); + m_Event.surfaceProps[1] = pObject2->GetMaterialIndex(); + m_Event.isCollision = IsCollision( pObject1, pObject2 ); + m_Event.isShadowCollision = IsShadowCollision( pObject1, pObject2 ); + m_Event.deltaCollisionTime = 100.0f; + m_Event.collisionSpeed = GetCollisionSpeed( pObject1, pObject2, info.m_SurfaceNormal ); + m_Event.pInternalData = &m_Data; + } + + static bool IsCollision( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2 ) + { + bool bIsCollision = ( pObject1->GetCallbackFlags() & pObject2->GetCallbackFlags() ) & CALLBACK_GLOBAL_COLLISION; + + if ( pObject1->IsStatic() && !( pObject2->GetCallbackFlags() & CALLBACK_GLOBAL_COLLIDE_STATIC ) ) + bIsCollision = false; + if ( pObject2->IsStatic() && !( pObject1->GetCallbackFlags() & CALLBACK_GLOBAL_COLLIDE_STATIC ) ) + bIsCollision = false; + + return bIsCollision; + } + + static bool IsShadowCollision( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2 ) + { + return ( pObject1->GetCallbackFlags() ^ pObject2->GetCallbackFlags() ) & CALLBACK_SHADOW_COLLISION; + } + + static float GetCollisionSpeed( JoltPhysicsObject *pObject1, JoltPhysicsObject *pObject2, Vector vecNormal ) + { + const Vector vecCollisionSpeed = pObject1->GetVelocity() - pObject2->GetVelocity(); + return fabsf( vecCollisionSpeed.Dot( vecNormal ) ); + } + + vcollisionevent_t m_Event = {}; + JoltPhysicsCollisionData m_Data; + }; + + template < typename Data > + struct JoltPhysicsEventTracker + { + public: + template < typename... T > + void EmplaceBack( uint32 uThreadId, T&&... val) + { + m_Mask |= 1ull << uThreadId; + m_Events[ uThreadId ].emplace_back( std::forward< T >( val )... ); + } + + template < bool bClear, typename FuncType > + void ForEach( FuncType func ) + { + for ( uint32 thread = m_Mask; thread; thread &= thread - 1 ) + { + const uint32 i = tzcnt( thread ); + for ( auto &event : m_Events[ i ] ) + func( event ); + + if constexpr ( bClear ) + m_Events[ i ].clear(); + } + + if constexpr ( bClear ) + m_Mask = 0ull; + } + + private: + static constexpr uint32 kMaxThreads = 64; + std::atomic< uint64_t > m_Mask = { 0ull }; + std::vector< Data > m_Events[ kMaxThreads ]; + }; + + // The maximum number of sent collision events to send per-frame. + // This is used to play stuff like sounds and physics fx. + // This is quite expensive to do so, we rate-limit this quite aggressively. + static constexpr uint32_t MaxCollisionEvents = 4; + std::atomic< uint32 > m_GlobalCollisionEventCount = { 0u }; + + JoltPhysicsEventTracker< JoltPhysicsCollisionEvent > m_CollisionEvents; + + JoltPhysicsEventTracker< JoltPhysicsCollisionData > m_StartTouchEvents; + JoltPhysicsEventTracker< JoltPhysicsCollisionData > m_EndTouchEvents; + + JoltPhysicsEventTracker< JoltPhysicsContactPair > m_EnterTriggerEvents; + JoltPhysicsEventTracker< JoltPhysicsContactPair > m_LeaveTriggerEvents; + + // For the fluid events: + // Object1 = the fluid + // Object2 = the object + JoltPhysicsEventTracker< JoltPhysicsContactPair > m_FluidStartTouchEvents; + JoltPhysicsEventTracker< JoltPhysicsContactPair > m_FluidEndTouchEvents; + +}; diff --git a/vphysics_jolt/vjolt_object.cpp b/vphysics_jolt/vjolt_object.cpp new file mode 100644 index 0000000..5f3b5fd --- /dev/null +++ b/vphysics_jolt/vjolt_object.cpp @@ -0,0 +1,1243 @@ +ο»Ώ//================================================================================================= +// +// A physics object, implemented as a wrapper over JPH::Body +// Every tangible object in the game has one of these +// +//================================================================================================= + +#include "cbase.h" + +#include "vjolt_collide.h" +#include "vjolt_surfaceprops.h" +#include "vjolt_friction.h" +#include "vjolt_environment.h" +#include "vjolt_layers.h" +#include "vjolt_controller_shadow.h" + +#include "vjolt_object.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsObject::JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, bool bStatic, int nMaterialIndex, const objectparams_t *pParams ) + : m_pBody( pBody ) + , m_pEnvironment( pEnvironment ) + , m_pPhysicsSystem( pEnvironment->GetPhysicsSystem() ) + , m_bStatic( bStatic ) + , m_flCachedMass( pParams->mass ) + , m_flCachedInvMass( m_flCachedMass ? 1.0f / m_flCachedMass : 0.0f ) + , m_pGameData( pParams->pGameData ) + , m_materialIndex( Max( nMaterialIndex, 0 ) ) // Sometimes we get passed -1. + , m_flVolume( pParams->volume ) +{ + // Josh: + // Assert that m_pGameData is the first element, some games + // decide to just read this directly by offsetting by the vtable size + // instead of calling GetGameData(). + static_assert( offsetof( JoltPhysicsObject, m_pGameData ) == sizeof( void * ) ); + + // Set the body's userdata as ourselves + pBody->SetUserData( reinterpret_cast( this ) ); + if ( !m_pBody->IsStatic() ) + { + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + pMotionProperties->SetLinearDamping( pParams->damping ); + pMotionProperties->SetAngularDamping( pParams->rotdamping ); + } + + UpdateMaterialProperties(); +} + +JoltPhysicsObject::JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, void *pGameData, JPH::StateRecorder &recorder ) + : m_pBody( pBody ) + , m_pEnvironment( pEnvironment ) + , m_pPhysicsSystem( pEnvironment->GetPhysicsSystem() ) + , m_pGameData( pGameData ) +{ + RestoreObjectState( recorder ); +} + +JoltPhysicsObject::~JoltPhysicsObject() +{ + RemoveShadowController(); + + // Josh: + // Iterate over this in reverse as we could remove a listener from inside this callback + for ( int i = m_destroyedListeners.Count() - 1; i >= 0; i-- ) + m_destroyedListeners[ i ]->OnJoltPhysicsObjectDestroyed( this ); + + JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.DestroyBody( GetBodyID() ); +} + +bool JoltPhysicsObject::IsStatic() const +{ + // Whether this a static body to VPhysics and not + // to Jolt (static or motion disabled) itself. + return m_bStatic; +} + +bool JoltPhysicsObject::IsAsleep() const +{ + return !m_pBody->IsActive(); +} + +bool JoltPhysicsObject::IsTrigger() const +{ + return m_pBody->IsSensor(); +} + +bool JoltPhysicsObject::IsFluid() const +{ + return m_pFluidController != nullptr; +} + +bool JoltPhysicsObject::IsHinged() const +{ + Log_Stub( LOG_VJolt ); + return false; +} + +bool JoltPhysicsObject::IsCollisionEnabled() const +{ + return m_bCachedCollisionEnabled; +} + +bool JoltPhysicsObject::IsGravityEnabled() const +{ + if ( !m_pBody->IsStatic() ) + { + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + return pMotionProperties->GetGravityFactor() != 0.0f; + } + + return false; +} + +bool JoltPhysicsObject::IsDragEnabled() const +{ + Log_Stub( LOG_VJolt ); + return false; +} + +bool JoltPhysicsObject::IsMotionEnabled() const +{ + return !m_bPinned; +} + +bool JoltPhysicsObject::IsMoveable() const +{ + return IsMotionEnabled() && !IsStatic(); +} + +bool JoltPhysicsObject::IsAttachedToConstraint( bool bExternalOnly ) const +{ + Log_Stub( LOG_VJolt ); + return false; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::EnableCollisions( bool enable ) +{ + // Josh: + // When collisions are disabled, they disable for EVERYTHING, including against the world. + m_bCachedCollisionEnabled = enable; + UpdateLayer(); +} + +void JoltPhysicsObject::EnableGravity( bool enable ) +{ + if ( !m_pBody->IsStatic() ) + { + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + pMotionProperties->SetGravityFactor( enable ? 1.0f : 0.0f ); + } +} + +void JoltPhysicsObject::EnableDrag( bool enable ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsObject::EnableMotion( bool enable ) +{ + if ( IsStatic() ) + return; + + const bool bPinned = !enable; + + if ( m_bPinned == bPinned ) + return; + + m_bPinned = bPinned; + UpdateLayer(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetGameData( void *pGameData ) +{ + m_pGameData = pGameData; +} + +void *JoltPhysicsObject::GetGameData() const +{ + return m_pGameData; +} + +void JoltPhysicsObject::SetGameFlags( unsigned short userFlags ) +{ + m_gameFlags = userFlags; +} + +unsigned short JoltPhysicsObject::GetGameFlags() const +{ + return m_gameFlags; +} + +void JoltPhysicsObject::SetGameIndex( unsigned short gameIndex ) +{ + m_gameIndex = gameIndex; +} + +unsigned short JoltPhysicsObject::GetGameIndex() const +{ + return m_gameIndex; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetCallbackFlags( unsigned short callbackflags ) +{ + m_callbackFlags = callbackflags; +} + +unsigned short JoltPhysicsObject::GetCallbackFlags() const +{ + return m_callbackFlags; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::Wake() +{ + if ( !m_pBody->IsStatic() ) + { + JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.ActivateBody( m_pBody->GetID() ); + } +} + +void JoltPhysicsObject::Sleep() +{ + if ( !m_pBody->IsStatic() ) + { + JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.DeactivateBody( m_pBody->GetID() ); + } +} + +void JoltPhysicsObject::RecheckCollisionFilter() +{ + RecheckContactPoints(); +} + +void JoltPhysicsObject::RecheckContactPoints( bool bSearchForNewContacts /*= false*/ ) +{ + JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.InvalidateContactCache( GetBodyID() ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetMass( float mass ) +{ + // To match regular VPhysics, allow 0 here but not at init. + mass = Clamp( mass, 0.0f, VPHYSICS_MAX_MASS ); + + m_flCachedMass = mass; + m_flCachedInvMass = mass ? 1.0f / mass : 0.0f; + + if ( !IsStatic() ) + { + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + // Mass is already in KG. IVP is weird. + + // Josh: This is what we used to do and it was giving VERY whacky results + // when moving objects around after calling SetMass. + // pMotionProperties->SetInverseMass( m_flCachedInvMass ); + // This method below seems to work properly because it deals with all of the inertia crap. + JPH::MassProperties massProperties = m_pBody->GetShape()->GetMassProperties(); + massProperties.ScaleToMass( mass ); + massProperties.mInertia( 3, 3 ) = 1.0f; + pMotionProperties->SetMassProperties( massProperties ); + + CalculateBuoyancy(); + } +} + +float JoltPhysicsObject::GetMass() const +{ + return m_flCachedMass; +} + +float JoltPhysicsObject::GetInvMass() const +{ + return m_flCachedInvMass; +} + +Vector JoltPhysicsObject::GetInertia() const +{ + Vector inv = GetInvInertia(); + return Vector( 1.0f / inv.x, 1.0f / inv.y, 1.0f / inv.z ); +} + +Vector JoltPhysicsObject::GetInvInertia() const +{ + if ( IsStatic() ) + return Vector( 1.0f, 1.0f, 1.0f ); + + //const JPH::Vec3 inertia = m_pBody->GetMotionProperties()->GetInverseInertiaDiagonal(); + const JPH::Vec3 inertia = m_pBody->GetInverseInertia() * JPH::Vec3::sReplicate( 1.0f ); + return Abs( JoltToSource::Unitless( inertia ) ); +} + +void JoltPhysicsObject::SetInertia( const Vector &inertia ) +{ + // TODO(Josh): Does anything use this? + // and if so, does this specify the local diagonal or rotated diagonal? + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetDamping( const float *speed, const float *rot ) +{ + if ( IsStatic() ) + return; + + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + if ( speed ) + pMotionProperties->SetLinearDamping( *speed ); + if ( rot ) + pMotionProperties->SetAngularDamping( *rot ); +} + +void JoltPhysicsObject::GetDamping( float *speed, float *rot ) const +{ + if ( IsStatic() ) + return; + + JPH::MotionProperties* pMotionProperties = m_pBody->GetMotionProperties(); + if ( speed ) + *speed = pMotionProperties->GetLinearDamping(); + if ( rot ) + *rot = pMotionProperties->GetAngularDamping(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetDragCoefficient( float *pDrag, float *pAngularDrag ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsObject::SetBuoyancyRatio( float ratio ) +{ + m_flBuoyancyRatio = ratio; +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsObject::GetMaterialIndex() const +{ + return m_materialIndex; +} + +void JoltPhysicsObject::SetMaterialIndex( int materialIndex ) +{ + // Gotta clamp it, because we get -1 sometimes + materialIndex = Max( 0, materialIndex ); + + if ( m_materialIndex != materialIndex ) + { + m_materialIndex = materialIndex; + UpdateMaterialProperties(); + } +} + +//------------------------------------------------------------------------------------------------- + +unsigned int JoltPhysicsObject::GetContents() const +{ + return m_contents; +} + +void JoltPhysicsObject::SetContents( unsigned int contents ) +{ + m_contents = contents; +} + +//------------------------------------------------------------------------------------------------- + +float JoltPhysicsObject::GetSphereRadius() const +{ + if ( m_pBody->GetShape()->GetSubType() != JPH::EShapeSubType::Sphere ) + return 0.0f; + + const JPH::SphereShape *pSphereShape = static_cast< const JPH::SphereShape * >( m_pBody->GetShape() ); + return pSphereShape->GetRadius(); +} + +void JoltPhysicsObject::SetSphereRadius( float radius ) +{ + if ( m_pBody->GetShape()->GetSubType() != JPH::EShapeSubType::Sphere ) + return; + + // Can't get this shape non-const... urg... + Log_Stub( LOG_VJolt ); +} + +float JoltPhysicsObject::GetEnergy() const +{ + // 1/2 * mv^2 + const float flKineticEnergy = 0.5f * m_flCachedMass * m_pBody->GetLinearVelocity().LengthSq(); + // TODO(Josh): We need to factor in inertia or something here to get this right. + // as this AngularVelocity is in rads/s... + // I guess it's a good enough approximation for now. + // Right now the equation is 1/2ww where we probably want 1/2wIw. + // Intertia in Jolt is weird... Not sure what's going on with its matrix thingy. + const float flAngularEnergy = 0.5f * m_flCachedMass * m_pBody->GetAngularVelocity().LengthSq(); + + return JoltToSource::Energy( flKineticEnergy + flAngularEnergy ); +} + +Vector JoltPhysicsObject::GetMassCenterLocalSpace() const +{ + return JoltToSource::Distance( m_pBody->GetShape()->GetCenterOfMass() ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetPosition( const Vector &worldPosition, const QAngle &angles, bool isTeleport ) +{ + JPH::Vec3 joltPosition = SourceToJolt::Distance( worldPosition ); + JPH::Quat joltRotation = SourceToJolt::Angle( angles ); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + bodyInterface.SetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation, JPH::EActivation::DontActivate ); +} + +void JoltPhysicsObject::SetPositionMatrix( const matrix3x4_t &matrix, bool isTeleport ) +{ + SetPosition( GetColumn( matrix, MatrixAxis::Origin ), ToQAngle( matrix ), isTeleport ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::GetPosition( Vector *worldPosition, QAngle *angles ) const +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + JPH::Vec3 joltPosition; + JPH::Quat joltRotation; + bodyInterface.GetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation ); + + if ( worldPosition ) + *worldPosition = JoltToSource::Distance( joltPosition ); + + if ( angles ) + *angles = JoltToSource::Angle( joltRotation ); +} + +void JoltPhysicsObject::GetPositionMatrix( matrix3x4_t *positionMatrix ) const +{ + matrix3x4_t matrix; + SetIdentityMatrix( matrix ); + AngleMatrix( JoltToSource::Angle( m_pBody->GetRotation() ), JoltToSource::Distance( m_pBody->GetPosition() ), matrix ); + *positionMatrix = matrix; +} + +void JoltPhysicsObject::SetVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) +{ + JPH::Vec3 joltLinearVelocity = velocity ? SourceToJolt::Distance( *velocity ) : JPH::Vec3{}; + JPH::Vec3 joltAngularVelocity = angularVelocity ? SourceToJolt::AngularImpulse( *angularVelocity ) : JPH::Vec3{}; + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + if ( velocity && angularVelocity ) + bodyInterface.SetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); + else if ( velocity ) + bodyInterface.SetLinearVelocity( m_pBody->GetID(), joltLinearVelocity ); + else if ( angularVelocity ) + bodyInterface.SetAngularVelocity( m_pBody->GetID(), joltAngularVelocity ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetVelocityInstantaneous( const Vector *velocity, const AngularImpulse *angularVelocity ) +{ + SetVelocity( velocity, angularVelocity ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::GetVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + JPH::Vec3 joltLinearVelocity; + JPH::Vec3 joltAngularVelocity; + bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); + + if ( velocity ) + *velocity = JoltToSource::Distance( joltLinearVelocity ); + + if ( angularVelocity ) + *angularVelocity = JoltToSource::AngularImpulse( joltAngularVelocity ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::AddVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) +{ + if ( !IsMoveable() ) + return; + + // Do this longer method do set velocity and angular velocity + // in the same lock. + const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); + + JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); + if ( lock.Succeeded() ) + { + JPH::Body &body = lock.GetBody(); + + if ( velocity ) + body.SetLinearVelocityClamped( body.GetLinearVelocity() + SourceToJolt::Distance( *velocity ) ); + + if ( angularVelocity ) + body.SetAngularVelocityClamped( body.GetAngularVelocity() + SourceToJolt::AngularImpulse( *angularVelocity ) ); + + if ( !body.IsActive() && ( !body.GetLinearVelocity().IsNearZero() || !body.GetAngularVelocity().IsNearZero() ) ) + m_pPhysicsSystem->GetBodyInterfaceNoLock().ActivateBodies( &m_pBody->GetID(), 1 ); + } +} + +void JoltPhysicsObject::GetVelocityAtPoint( const Vector &worldPosition, Vector *pVelocity ) const +{ + VJoltAssert( pVelocity ); + + *pVelocity = JoltToSource::Distance( m_pPhysicsSystem->GetBodyInterfaceNoLock().GetPointVelocity( m_pBody->GetID(), SourceToJolt::Distance( worldPosition ) ) ); +} + +void JoltPhysicsObject::GetImplicitVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const +{ + Log_Stub( LOG_VJolt ); + if ( velocity ) + *velocity = vec3_origin; + + if ( angularVelocity ) + *angularVelocity = vec3_origin; +} + +void JoltPhysicsObject::LocalToWorld( Vector *worldPosition, const Vector &localPosition ) const +{ + matrix3x4_t matrix; + GetPositionMatrix( &matrix ); + // Copy in case src == dest + VectorTransform( Vector( localPosition ), matrix, *worldPosition ); +} + +void JoltPhysicsObject::WorldToLocal( Vector *localPosition, const Vector &worldPosition ) const +{ + matrix3x4_t matrix; + GetPositionMatrix( &matrix ); + // Copy in case src == dest + VectorITransform( Vector( worldPosition ), matrix, *localPosition ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::LocalToWorldVector( Vector *worldVector, const Vector &localVector ) const +{ + matrix3x4_t matrix; + GetPositionMatrix( &matrix ); + // Copy in case src == dest + VectorRotate( Vector( localVector ), matrix, *worldVector ); +} + +void JoltPhysicsObject::WorldToLocalVector( Vector *localVector, const Vector &worldVector ) const +{ + matrix3x4_t matrix; + GetPositionMatrix( &matrix ); + // Copy in case src == dest + VectorIRotate( Vector( worldVector ), matrix, *localVector ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::ApplyForceCenter( const Vector &forceVector ) +{ + if ( !IsMoveable() ) + return; + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.AddImpulse( m_pBody->GetID(), SourceToJolt::Distance( forceVector ) ); +} + +void JoltPhysicsObject::ApplyForceOffset( const Vector &forceVector, const Vector &worldPosition ) +{ + if ( !IsMoveable() ) + return; + + JPH::Vec3 impulse = SourceToJolt::Distance( forceVector ); + JPH::Vec3 point = SourceToJolt::Distance( worldPosition ); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.AddImpulse( m_pBody->GetID(), impulse, point ); +} + +void JoltPhysicsObject::ApplyTorqueCenter( const AngularImpulse &torque ) +{ + if ( !IsMoveable() ) + return; + + // Do this longer method do set velocity and angular velocity + // in the same lock. + const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); + + JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); + if ( lock.Succeeded() ) + { + JPH::Body &body = lock.GetBody(); + + body.AddAngularImpulse( SourceToJolt::AngularImpulse( torque ) ); + + if ( !body.IsActive() && ( !body.GetAngularVelocity().IsNearZero() ) ) + m_pPhysicsSystem->GetBodyInterfaceNoLock().ActivateBodies( &m_pBody->GetID(), 1 ); + } +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::CalculateForceOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerForce, AngularImpulse *centerTorque ) const +{ + JPH::Vec3 pos = SourceToJolt::Distance( worldPosition ); + JPH::Vec3 force = SourceToJolt::Distance( forceVector ); + + JPH::Vec3 com = pos - m_pBody->GetCenterOfMassPosition(); + JPH::Vec3 cross = com.Cross( force ); + + if ( centerForce ) + *centerForce = JoltToSource::Distance( force ); + + if ( centerTorque ) + *centerTorque = JoltToSource::AngularImpulse( cross ); +} + +void JoltPhysicsObject::CalculateVelocityOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerVelocity, AngularImpulse *centerAngularVelocity ) const +{ + // Convert force to SI units to multiply by mass for impulse. + JPH::Vec3 siForce = SourceToJolt::Distance( forceVector ); + + if ( centerVelocity ) + *centerVelocity = JoltToSource::Distance( siForce * m_flCachedInvMass ); + + if ( centerAngularVelocity ) + { + JPH::Vec3 siPosition = SourceToJolt::Distance( worldPosition ); + + // TODO(Josh): Check this math. + JPH::Vec3 siRelativePosition = siPosition - m_pBody->GetCenterOfMassPosition(); + JPH::Vec3 cross = siRelativePosition.Cross( siForce ); + cross = m_pBody->GetWorldTransform().Transposed3x3() * cross; + + *centerAngularVelocity = JoltToSource::AngularImpulse( cross ); + } +} + +float JoltPhysicsObject::CalculateLinearDrag( const Vector &unitDirection ) const +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +float JoltPhysicsObject::CalculateAngularDrag( const Vector &objectSpaceRotationAxis ) const +{ + Log_Stub( LOG_VJolt ); + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- + +bool JoltPhysicsObject::GetContactPoint( Vector *contactPoint, IPhysicsObject **contactObject ) const +{ + Log_Stub( LOG_VJolt ); + return false; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetShadow( float maxSpeed, float maxAngularSpeed, bool allowPhysicsMovement, bool allowPhysicsRotation ) +{ + if ( m_pShadowController ) + { + m_pShadowController->MaxSpeed( maxSpeed, maxAngularSpeed ); + } + else + { +#if 1 + m_bShadowTemporarilyDisableGravity = false; +#endif + + m_pShadowController = static_cast( m_pEnvironment->CreateShadowController( this, allowPhysicsMovement, allowPhysicsRotation ) ); + m_pShadowController->MaxSpeed( maxSpeed, maxAngularSpeed ); + } +} + +void JoltPhysicsObject::UpdateShadow( const Vector &targetPosition, const QAngle &targetAngles, bool tempDisableGravity, float timeOffset ) +{ + if ( m_pShadowController ) + { +#if 1 + if ( tempDisableGravity != m_bShadowTemporarilyDisableGravity ) + { + m_bShadowTemporarilyDisableGravity = tempDisableGravity; + if ( !m_pShadowController || m_pShadowController->AllowsTranslation() ) + EnableGravity( !m_bShadowTemporarilyDisableGravity ); + } +#endif + m_pShadowController->Update( targetPosition, targetAngles, timeOffset ); + } +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsObject::GetShadowPosition( Vector *position, QAngle *angles ) const +{ + // Josh: + // If func_door_rotating, func_tracktrains are moving slowly, + // check this function out... + // + // Interpolates to the next timestep + float flTimeStep = m_pEnvironment->GetSimulationTimestep(); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + JPH::Vec3 joltPosition, joltLinearVelocity, joltAngularVelocity; + JPH::Quat joltRotation; + + bodyInterface.GetPositionAndRotation( m_pBody->GetID(), joltPosition, joltRotation ); + bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), joltLinearVelocity, joltAngularVelocity ); + + if ( position ) + { + *position = JoltToSource::Distance( joltPosition + ( joltLinearVelocity * flTimeStep ) ); + } + if ( angles ) + { + // From Jolt's AddRotationStep. + + JPH::Vec3 joltAngularVelocityTimesDT = joltAngularVelocity * flTimeStep; + float len = joltAngularVelocityTimesDT.Length(); + + JPH::Quat newQuat = joltRotation; + if ( len > 1.0e-6f ) + newQuat = ( JPH::Quat::sRotation( joltAngularVelocityTimesDT / len, len ) * joltRotation ).Normalized(); + *angles = JoltToSource::Angle( newQuat ); + } + + return 1; +} + +IPhysicsShadowController *JoltPhysicsObject::GetShadowController() const +{ + return m_pShadowController; +} + +void JoltPhysicsObject::RemoveShadowController() +{ + if ( m_pShadowController ) + { + m_pEnvironment->DestroyShadowController(m_pShadowController); + m_pShadowController = nullptr; + } +} + +static void ComputeController( JPH::Vec3 &vecCurrentVelocity, const JPH::Vec3 &vecDeltaPos, float flMaxSpeed, float flMaxDampSpeed, float flScaleDelta, float flDamping, JPH::Vec3 *pOutImpulse = nullptr ) +{ + float flCurrentSpeedSq = vecCurrentVelocity.LengthSq(); + if ( flCurrentSpeedSq < 1e-6f ) + { + vecCurrentVelocity = JPH::Vec3::sZero(); + } + else if ( flMaxDampSpeed > 0 ) + { + JPH::Vec3 vecAccelDampening = vecCurrentVelocity * -flDamping; + float flSpeed = sqrtf( flCurrentSpeedSq ) * fabsf( flDamping ); + if ( flSpeed > flMaxDampSpeed ) + { + flSpeed = flMaxDampSpeed / flSpeed; + vecAccelDampening *= flSpeed; + } + vecCurrentVelocity += vecAccelDampening; + } + + JPH::Vec3 vecAcceleration = JPH::Vec3::sZero(); + if ( flMaxSpeed > 0.0f ) + { + vecAcceleration = vecDeltaPos * flScaleDelta; + float flSpeed = vecDeltaPos.Length() * flScaleDelta; + if ( flSpeed > flMaxSpeed ) + { + flSpeed = flMaxSpeed / flSpeed; + vecAcceleration *= flSpeed; + } + vecCurrentVelocity += vecAcceleration; + } + + if ( pOutImpulse ) + *pOutImpulse = vecAcceleration; +} + +// hlshadowcontrol_params_t but in Jolt space. +struct JoltShadowControlParams +{ + JPH::Vec3 TargetPosition; + JPH::Quat TargetRotation; + JPH::Vec3 LastPosition; + JPH::Vec3 LastImpulse; + float MaxAngular; + float MaxDampAngular; + float MaxSpeed; + float MaxDampSpeed; + float DampFactor; + float TeleportDistance; +}; + +static float ComputeShadowController( JoltShadowControlParams ¶ms, JPH::Vec3 &position, JPH::Quat &rotation, JPH::Vec3 &linearVelocity, JPH::Vec3& angularVelocity, float flSecondsToArrival, float flDeltaTime ) +{ + const float flFraction = flSecondsToArrival > 0.0f + ? Min( flDeltaTime / flSecondsToArrival, 1.0f ) + : 1.0f; + + flSecondsToArrival = Max( flSecondsToArrival - flDeltaTime, 0.0f ); + + if ( flFraction <= 0.0f ) + return flSecondsToArrival; + + JPH::Vec3 deltaPosition = params.TargetPosition - position; + + if ( params.TeleportDistance > 0.0f && deltaPosition.LengthSq() > Square( params.TeleportDistance ) ) + { + position = params.TargetPosition; + rotation = params.TargetRotation; + deltaPosition = JPH::Vec3::sZero(); + } + + const float flInvDeltaTime = 1.0f / flDeltaTime; + const float flFractionTime = flFraction * flInvDeltaTime; + + ComputeController( linearVelocity, deltaPosition, params.MaxSpeed, params.MaxDampSpeed, flFractionTime, params.DampFactor, ¶ms.LastImpulse); + + params.LastPosition = position + linearVelocity * flDeltaTime; + + JPH::Quat deltaRotation = params.TargetRotation * rotation.Inversed(); + + JPH::Vec3 axis; + float angle; + deltaRotation.GetAxisAngle( axis, angle ); + + JPH::Vec3 deltaAngles = axis * angle; + ComputeController( angularVelocity, deltaAngles, params.MaxAngular, params.MaxDampAngular, flFractionTime, params.DampFactor ); + + return flSecondsToArrival; +} + + +float JoltPhysicsObject::ComputeShadowControl( const hlshadowcontrol_params_t ¶ms, float flSecondsToArrival, float flDeltaTime ) +{ + JoltShadowControlParams joltParams = + { + .TargetPosition = SourceToJolt::Distance( params.targetPosition ), + .TargetRotation = SourceToJolt::Angle( params.targetRotation ), + .MaxAngular = SourceToJolt::Angle( params.maxAngular ), + .MaxDampAngular = SourceToJolt::Angle( params.maxDampAngular ), + .MaxSpeed = SourceToJolt::Distance( params.maxSpeed ), + .MaxDampSpeed = SourceToJolt::Distance( params.maxDampSpeed ), + .DampFactor = params.dampFactor, + .TeleportDistance = SourceToJolt::Distance( params.teleportDistance ), + }; + + JPH::BodyInterface& bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + JPH::Vec3 position; + JPH::Quat rotation; + bodyInterface.GetPositionAndRotation( m_pBody->GetID(), position, rotation ); + JPH::Vec3 linearVelocity; + JPH::Vec3 angularVelocity; + bodyInterface.GetLinearAndAngularVelocity( m_pBody->GetID(), linearVelocity, angularVelocity ); + + JPH::Vec3 scratchPosition = position; + JPH::Quat scratchRotation = rotation; + JPH::Vec3 scratchLinearVelocity = linearVelocity; + JPH::Vec3 scratchAngularVelocity = angularVelocity; + float flNewSecondsToArrival = + ComputeShadowController( joltParams, scratchPosition, scratchRotation, scratchLinearVelocity, scratchAngularVelocity, flSecondsToArrival, flDeltaTime ); + + if ( scratchPosition != position || scratchRotation != rotation ) + bodyInterface.SetPositionAndRotation( m_pBody->GetID(), scratchPosition, scratchRotation, JPH::EActivation::Activate ); + + if ( scratchLinearVelocity != linearVelocity || scratchAngularVelocity != angularVelocity ) + bodyInterface.SetLinearAndAngularVelocity( m_pBody->GetID(), scratchLinearVelocity, scratchAngularVelocity ); + + return flNewSecondsToArrival; +} + +//------------------------------------------------------------------------------------------------- + +const CPhysCollide *JoltPhysicsObject::GetCollide() const +{ + const CPhysCollide *pCollide = CPhysCollide::FromShape( m_pBody->GetShape() ); + return pCollide; +} + +const char *JoltPhysicsObject::GetName() const +{ + // Slart: Jolt used to store debug names in JPH::Body, but it was removed. So now everybody's NoName. + return "NoName"; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::BecomeTrigger() +{ + m_pBody->SetIsSensor( true ); +} + +void JoltPhysicsObject::RemoveTrigger() +{ + if ( !IsTrigger() ) + return; + + // Josh: + // All this logic below is to trigger ObjectLeaveTrigger + // when the trigger is deleted. + IPhysicsCollisionEvent *pEventListener = m_pEnvironment->GetContactListener()->GetGameListener(); + if ( pEventListener && IsTrigger() ) + { + class SourceTriggerCollector : public JPH::CollideShapeCollector + { + public: + SourceTriggerCollector( JPH::PhysicsSystem *pPhysicsSystem, IPhysicsCollisionEvent *pGameListener, JoltPhysicsObject *pTrigger ) + : m_pPhysicsSystem( pPhysicsSystem ) + , m_pGameListener ( pGameListener ) + , m_pTrigger ( pTrigger ) {} + + void AddHit( const ResultType &inResult ) override + { + const JPH::BodyID inBodyID = inResult.mBodyID2; + + JPH::BodyLockWrite lock( m_pPhysicsSystem->GetBodyLockInterface(), inBodyID ); + JPH::Body &body = lock.GetBody(); + JoltPhysicsObject *pObject = reinterpret_cast( body.GetUserData() ); + + if ( !pObject ) + return; + + m_pGameListener->ObjectLeaveTrigger( m_pTrigger, pObject ); + } + + private: + JPH::PhysicsSystem *m_pPhysicsSystem; + IPhysicsCollisionEvent *m_pGameListener; + JoltPhysicsObject *m_pTrigger; + }; + + SourceTriggerCollector collector( m_pPhysicsSystem, pEventListener, this ); + + JPH::IgnoreSingleBodyFilter body_filter( GetBodyID() ); + + JPH::CollideShapeSettings collideSettings; + collideSettings.mActiveEdgeMode = JPH::EActiveEdgeMode::CollideWithAll; + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + JPH::Mat44 queryTransform = bodyInterface.GetCenterOfMassTransform( GetBodyID() ); + + const JPH::Shape *pShape = GetCollide()->ToShape(); + + m_pPhysicsSystem->GetNarrowPhaseQueryNoLock().CollideShape( + pShape, JPH::Vec3::sReplicate( 1.0f ), queryTransform, collideSettings, collector, + JPH::SpecifiedBroadPhaseLayerFilter( BroadPhaseLayers::MOVING ), JPH::SpecifiedObjectLayerFilter( Layers::MOVING ), body_filter ); + } + + m_pBody->SetIsSensor( false ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::BecomeHinged( int localAxis ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsObject::RemoveHinged() +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +IPhysicsFrictionSnapshot *JoltPhysicsObject::CreateFrictionSnapshot() +{ + return new JoltPhysicsFrictionSnapshot; +} + +void JoltPhysicsObject::DestroyFrictionSnapshot( IPhysicsFrictionSnapshot *pSnapshot ) +{ + delete pSnapshot; +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::OutputDebugInfo() const +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::SetUseAlternateGravity( bool bSet ) +{ + Log_Stub( LOG_VJolt ); +} + +void JoltPhysicsObject::SetCollisionHints( uint32 collisionHints ) +{ + m_collisionHints = collisionHints; + UpdateLayer(); +} + +uint32 JoltPhysicsObject::GetCollisionHints() const +{ + return m_collisionHints; +} + +//------------------------------------------------------------------------------------------------- + +IPredictedPhysicsObject *JoltPhysicsObject::GetPredictedInterface() const +{ + Log_Stub( LOG_VJolt ); + return nullptr; +} + +void JoltPhysicsObject::SyncWith( IPhysicsObject *pOther ) +{ + if ( this->IsCollisionEnabled() != pOther->IsCollisionEnabled() ) + EnableCollisions( pOther->IsCollisionEnabled() ); + + if ( this->IsGravityEnabled() != pOther->IsGravityEnabled() ) + EnableGravity( pOther->IsGravityEnabled() ); + + if ( this->IsDragEnabled() != pOther->IsDragEnabled() ) + EnableDrag(pOther->IsDragEnabled() ); + + if ( this->IsMotionEnabled() != pOther->IsMotionEnabled() ) + EnableMotion(pOther->IsMotionEnabled() ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::UpdateEnvironment( JoltPhysicsEnvironment *pEnvironment ) +{ + m_pEnvironment = pEnvironment; + m_pPhysicsSystem = pEnvironment->GetPhysicsSystem(); +} + +void JoltPhysicsObject::AddDestroyedListener( IJoltObjectDestroyedListener *pListener ) +{ + m_destroyedListeners.AddToTail( pListener ); +} + +void JoltPhysicsObject::RemoveDestroyedListener( IJoltObjectDestroyedListener *pListener ) +{ + m_destroyedListeners.FindAndRemove( pListener ); +} + +void JoltPhysicsObject::AddToPosition( JPH::Vec3Arg addPos ) +{ + const JPH::BodyLockInterfaceNoLock &bodyLockInterface = m_pPhysicsSystem->GetBodyLockInterfaceNoLock(); + + JPH::BodyLockWrite lock( bodyLockInterface, m_pBody->GetID() ); + if ( lock.Succeeded() ) + { + JPH::Body &body = lock.GetBody(); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.SetPosition( m_pBody->GetID(), body.GetPosition() + addPos, JPH::EActivation::DontActivate ); + } +} + +void JoltPhysicsObject::SetPosition( const Vector &worldPosition ) +{ + JPH::Vec3 joltPosition = SourceToJolt::Distance( worldPosition ); + + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + bodyInterface.SetPosition( m_pBody->GetID(), joltPosition, JPH::EActivation::DontActivate ); +} + +void JoltPhysicsObject::AddVelocity( const Vector &worldPosition ) +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + bodyInterface.AddLinearVelocity( m_pBody->GetID(), SourceToJolt::Distance( worldPosition ) ); +} + +Vector JoltPhysicsObject::GetVelocity() +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + return JoltToSource::Distance( bodyInterface.GetLinearVelocity( m_pBody->GetID() ) ); +} + +void JoltPhysicsObject::CalculateBuoyancy() +{ + if ( m_flVolume != 0.0f ) + { + float flVolume = SourceToJolt::Volume( Max( m_flVolume, 5.0f ) ); + float flDensity = m_flCachedMass / flVolume; + m_flBuoyancyRatio = flDensity / m_flMaterialDensity; + } + else + { + m_flBuoyancyRatio = 1.0f; + } +} + +float JoltPhysicsObject::GetMaterialDensity() const +{ + return m_flMaterialDensity; +} + +float JoltPhysicsObject::GetBuoyancyRatio() const +{ + return m_flBuoyancyRatio; +} + +bool JoltPhysicsObject::IsControlledByGame() const +{ + if ( m_pShadowController && !m_pShadowController->IsPhysicallyControlled() ) + return true; + + if ( m_callbackFlags & CALLBACK_IS_PLAYER_CONTROLLER ) + return true; + + return false; +} + +void JoltPhysicsObject::SaveObjectState( JPH::StateRecorder &recorder ) +{ + m_pBody->SaveState( recorder ); + + // Josh: Do not write m_pGameData, as this is passed in, in UnserializeObjectFromBuffer. + //recorder.Write( m_pGameData ); + recorder.Write( m_gameFlags ); + recorder.Write( m_gameIndex ); + recorder.Write( m_callbackFlags ); + recorder.Write( m_bStatic ); + recorder.Write( m_bPinned ); + recorder.Write( m_materialIndex ); + recorder.Write( m_contents ); + recorder.Write( m_flCachedMass ); + recorder.Write( m_flCachedInvMass ); + recorder.Write( m_bCachedCollisionEnabled ); + recorder.Write( m_flMaterialDensity ); + recorder.Write( m_flBuoyancyRatio ); + recorder.Write( m_flVolume ); + recorder.Write( m_GameMaterial ); + + // Josh: + // In regular VPhysics, shadows are serialized but then forced to never be read. + // Lets just not bother serializing these. +} + +void JoltPhysicsObject::RestoreObjectState( JPH::StateRecorder &recorder ) +{ + // Restore the body's state. + m_pBody->RestoreState( recorder ); + + // Set the body's userdata as ourselves + m_pBody->SetUserData( reinterpret_cast( this ) ); + + // Josh: Do not read m_pGameData, as this is not serialized. + //recorder.Read( m_pGameData ); + recorder.Read( m_gameFlags ); + recorder.Read( m_gameIndex ); + recorder.Read( m_callbackFlags ); + recorder.Read( m_bStatic ); + recorder.Read( m_bPinned ); + recorder.Read( m_materialIndex ); + recorder.Read( m_contents ); + recorder.Read( m_flCachedMass ); + recorder.Read( m_flCachedInvMass ); + recorder.Read( m_bCachedCollisionEnabled ); + recorder.Read( m_flMaterialDensity ); + recorder.Read( m_flBuoyancyRatio ); + recorder.Read( m_flVolume ); + recorder.Read( m_GameMaterial ); + + // Recompute states. + UpdateMaterialProperties(); + UpdateLayer(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObject::UpdateMaterialProperties() +{ + const surfacedata_t *pSurface = JoltPhysicsSurfaceProps::GetInstance().GetSurfaceData( m_materialIndex ); + + m_pBody->SetRestitution( pSurface->physics.elasticity ); + m_pBody->SetFriction( pSurface->physics.friction ); + m_flMaterialDensity = pSurface->physics.density; + m_GameMaterial = pSurface->game.material; + CalculateBuoyancy(); +} + +void JoltPhysicsObject::UpdateLayer() +{ + JPH::BodyInterface &bodyInterface = m_pPhysicsSystem->GetBodyInterfaceNoLock(); + + const bool bCollisionsEnabled = m_bCachedCollisionEnabled; + const bool bStatic = IsStatic(); + const bool bPinned = m_bPinned; + const bool bDebris = m_collisionHints & COLLISION_HINT_DEBRIS; + const bool bStaticSolid = m_collisionHints & COLLISION_HINT_STATICSOLID; + + // Update motion type if not made as a complete solid. + if ( !bStatic && !IsControlledByGame() ) + { + bool bStaticMotionType = bStaticSolid || bPinned; + bodyInterface.SetMotionType( m_pBody->GetID(), bStaticMotionType ? JPH::EMotionType::Static : JPH::EMotionType::Dynamic, JPH::EActivation::Activate ); + } + + // Update layer + uint8 layer = Layers::MOVING; + + if ( bDebris ) + layer = Layers::DEBRIS; + + if ( bStatic || bStaticSolid ) + layer = Layers::NON_MOVING_WORLD; + else if ( bPinned ) + layer = Layers::NON_MOVING_OBJECT; + + if ( !bCollisionsEnabled ) + layer = Layers::NO_COLLIDE; + + bodyInterface.SetObjectLayer( m_pBody->GetID(), layer ); +} diff --git a/vphysics_jolt/vjolt_object.h b/vphysics_jolt/vjolt_object.h new file mode 100644 index 0000000..8134ab5 --- /dev/null +++ b/vphysics_jolt/vjolt_object.h @@ -0,0 +1,289 @@ +//================================================================================================= +// +// A physics object +// +//================================================================================================= + +#pragma once + +class IPredictedPhysicsObject; + +class IJoltObjectDestroyedListener; +class JoltPhysicsShadowController; +class JoltPhysicsFluidController; +class JoltPhysicsEnvironment; +class JoltPhysicsObject; + +#if defined( GAME_CSGO_OR_NEWER ) +using IPhysicsObjectInterface = IPredictedPhysicsObject; +#else +using IPhysicsObjectInterface = IPhysicsObject; +#endif + +class JoltPhysicsObject final : public IPhysicsObjectInterface +{ +public: + JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, bool bStatic, int nMaterialIndex, const objectparams_t *pParams ); + JoltPhysicsObject( JPH::Body *pBody, JoltPhysicsEnvironment *pEnvironment, void *pGameData, JPH::StateRecorder &recorder ); + ~JoltPhysicsObject() override; + + bool IsStatic() const override; + bool IsAsleep() const override; + bool IsTrigger() const override; + bool IsFluid() const override; + bool IsHinged() const override; + bool IsCollisionEnabled() const override; + bool IsGravityEnabled() const override; + bool IsDragEnabled() const override; + bool IsMotionEnabled() const override; + bool IsMoveable() const override; // legacy: IsMotionEnabled() && !IsStatic() + bool IsAttachedToConstraint( bool bExternalOnly ) const override; + + void EnableCollisions( bool enable ) override; + void EnableGravity( bool enable ) override; + void EnableDrag( bool enable ) override; + void EnableMotion( bool enable ) override; + + void SetGameData( void *pGameData ) override; + void * GetGameData() const override; + void SetGameFlags( unsigned short userFlags ) override; + unsigned short GetGameFlags() const override; + void SetGameIndex( unsigned short gameIndex ) override; + unsigned short GetGameIndex() const override; + + void SetCallbackFlags( unsigned short callbackflags ) override; + unsigned short GetCallbackFlags() const override; + + void Wake() override; + void Sleep() override; + void RecheckCollisionFilter() override; + void RecheckContactPoints( bool bSearchForNewContacts ) override_portal2; + void RecheckContactPoints() override_not_portal2 { RecheckContactPoints( false ); } + + void SetMass( float mass ) override; + float GetMass() const override; + float GetInvMass() const override; + Vector GetInertia() const override; + Vector GetInvInertia() const override; + void SetInertia( const Vector &inertia ) override; + + void SetDamping( const float *speed, const float *rot ) override; + void GetDamping( float *speed, float *rot ) const override; + + void SetDragCoefficient( float *pDrag, float *pAngularDrag ) override; + void SetBuoyancyRatio( float ratio ) override; + + int GetMaterialIndex() const override; + void SetMaterialIndex( int materialIndex ) override; + + unsigned int GetContents() const override; + void SetContents( unsigned int contents ) override; + + float GetSphereRadius() const override; + void SetSphereRadius( float radius ) override_asw; + float GetEnergy() const override; + Vector GetMassCenterLocalSpace() const override; + + void SetPosition( const Vector &worldPosition, const QAngle &angles, bool isTeleport ) override; + void SetPositionMatrix( const matrix3x4_t &matrix, bool isTeleport ) override; + + void GetPosition( Vector *worldPosition, QAngle *angles ) const override; + void GetPositionMatrix( matrix3x4_t *positionMatrix ) const override; + void SetVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) override; + + void SetVelocityInstantaneous( const Vector *velocity, const AngularImpulse *angularVelocity ) override; + + void GetVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const override; + + void AddVelocity( const Vector *velocity, const AngularImpulse *angularVelocity ) override; + void GetVelocityAtPoint( const Vector &worldPosition, Vector *pVelocity ) const override; + void GetImplicitVelocity( Vector *velocity, AngularImpulse *angularVelocity ) const override; + void LocalToWorld( Vector *worldPosition, const Vector &localPosition ) const override; + void WorldToLocal( Vector *localPosition, const Vector &worldPosition ) const override; + + void LocalToWorldVector( Vector *worldVector, const Vector &localVector ) const override; + void WorldToLocalVector( Vector *localVector, const Vector &worldVector ) const override; + + void ApplyForceCenter( const Vector &forceVector ) override; + void ApplyForceOffset( const Vector &forceVector, const Vector &worldPosition ) override; + void ApplyTorqueCenter( const AngularImpulse &torque ) override; + + void CalculateForceOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerForce, AngularImpulse *centerTorque ) const override; + void CalculateVelocityOffset( const Vector &forceVector, const Vector &worldPosition, Vector *centerVelocity, AngularImpulse *centerAngularVelocity ) const override; + float CalculateLinearDrag( const Vector &unitDirection ) const override; + float CalculateAngularDrag( const Vector &objectSpaceRotationAxis ) const override; + + bool GetContactPoint( Vector *contactPoint, IPhysicsObject **contactObject ) const override; + + void SetShadow( float maxSpeed, float maxAngularSpeed, bool allowPhysicsMovement, bool allowPhysicsRotation ) override; + void UpdateShadow( const Vector &targetPosition, const QAngle &targetAngles, bool tempDisableGravity, float timeOffset ) override; + + int GetShadowPosition( Vector *position, QAngle *angles ) const override; + IPhysicsShadowController * GetShadowController() const override; + void RemoveShadowController() override; + float ComputeShadowControl( const hlshadowcontrol_params_t ¶ms, float secondsToArrival, float dt ) override; + + + const CPhysCollide * GetCollide() const override; + const char * GetName() const override; + + void BecomeTrigger() override; + void RemoveTrigger() override; + + void BecomeHinged( int localAxis ) override; + void RemoveHinged() override; + + IPhysicsFrictionSnapshot *CreateFrictionSnapshot() override; + void DestroyFrictionSnapshot( IPhysicsFrictionSnapshot *pSnapshot ) override; + + void OutputDebugInfo() const override; + +#if OBJECT_WELDING + void WeldToObject( IPhysicsObject *pParent ) override; + void RemoveWeld( IPhysicsObject *pOther ) override; + void RemoveAllWelds() override; +#endif + + void SetUseAlternateGravity( bool bSet ) override_asw; + void SetCollisionHints( uint32 collisionHints ) override_asw; + uint32 GetCollisionHints() const override_asw; + + IPredictedPhysicsObject * GetPredictedInterface() const override_csgo; + void SyncWith( IPhysicsObject *pOther ) override_csgo; + + void SetErrorDelta_Position( const Vector& vPosition ) override_csgo {} + void SetErrorDelta_Velocity( const Vector& vVelocity ) override_csgo {} + +public: + JoltPhysicsEnvironment *GetEnvironment() { return m_pEnvironment; } + + JPH::BodyID GetBodyID() { return m_pBody->GetID(); } + JPH::Body *GetBody() { return m_pBody; } + + void UpdateEnvironment( JoltPhysicsEnvironment *pEnvironment ); + + void AddDestroyedListener( IJoltObjectDestroyedListener *pListener ); + void RemoveDestroyedListener( IJoltObjectDestroyedListener *pListener ); + + // Grabs the position, adds addPos and teleports the object + void AddToPosition( JPH::Vec3Arg addPos ); + + // Only sets the position, and nothing else. + void SetPosition( const Vector &worldPosition ); + + // Adds to the velocity (Source space) + void AddVelocity( const Vector &worldPosition ); + + Vector GetVelocity(); + + void CalculateBuoyancy(); + + float GetMaterialDensity() const; + float GetBuoyancyRatio() const; + float GetVolume() const { return m_flVolume; } + + bool IsControlledByGame() const; + + void AddCallbackFlags( uint16 flags ) { m_callbackFlags |= flags; } + void RemoveCallbackFlags( uint16 flags ) { m_callbackFlags &= ~flags; } + + void SaveObjectState( JPH::StateRecorder &recorder ); + void RestoreObjectState( JPH::StateRecorder &recorder ); + + unsigned short GetGameMaterial() const + { + return m_GameMaterial; + } + + bool GetGameMaterialAllowsSounds() const + { + return m_GameMaterial != 'X'; + } + + JoltPhysicsFluidController *GetFluidController() + { + return m_pFluidController; + } + + void SetFluidController( JoltPhysicsFluidController *pFluidController ) + { + m_pFluidController = pFluidController; + } + + // Fakes a linear velocity so we can have correct before/after velocity + // when going between PreCollision and PostCollision callbacks. + JPH::Vec3 FakeJoltLinearVelocity( JPH::Vec3Arg fakeVelocity ) + { + if ( m_pBody->IsStatic() ) + return JPH::Vec3::sZero(); + + JPH::Vec3 oldVel = m_pBody->GetLinearVelocity(); + m_pBody->SetLinearVelocity( fakeVelocity ); + return oldVel; + } + + void RestoreJoltLinearVelocity( JPH::Vec3Arg realVelocity ) + { + if ( m_pBody->IsStatic() ) + return; + + m_pBody->SetLinearVelocity( realVelocity ); + } + +private: + void UpdateMaterialProperties(); + void UpdateLayer(); + + // Josh: + // Always put m_pGameData first. Some games that will + // remain un-named offset by the vtable to get to this + // instead of calling GetGameData(). + void *m_pGameData = nullptr; + + uint16 m_gameFlags = 0; + uint16 m_gameIndex = 0; + uint16 m_callbackFlags = CALLBACK_GLOBAL_COLLISION|CALLBACK_GLOBAL_FRICTION|CALLBACK_FLUID_TOUCH|CALLBACK_GLOBAL_TOUCH|CALLBACK_GLOBAL_COLLIDE_STATIC|CALLBACK_DO_FLUID_SIMULATION; + uint32 m_collisionHints = 0; + + bool m_bStatic = false; + bool m_bPinned = false; + + int m_materialIndex = 0; + uint m_contents = CONTENTS_SOLID; + + // Need this as Jolt gets very unhappy about reading motion + // properties of static objects. + float m_flCachedMass = 0.0f; + float m_flCachedInvMass = 0.0f; + bool m_bCachedCollisionEnabled = true; + + float m_flMaterialDensity = 1.0f; // Material density in Jolt space. + float m_flBuoyancyRatio = 0.0f; + float m_flVolume = 0.0f; + + unsigned short m_GameMaterial = 0; + + + CUtlVector< IJoltObjectDestroyedListener * > m_destroyedListeners; + + // Shadow variables + JoltPhysicsShadowController *m_pShadowController = nullptr; + JoltPhysicsFluidController *m_pFluidController = nullptr; + bool m_bShadowTemporarilyDisableGravity = false; + + JPH::Body *m_pBody = nullptr; // Underlying Jolt body + JoltPhysicsEnvironment *m_pEnvironment = nullptr; // Physics environment this body belongs to + JPH::PhysicsSystem *m_pPhysicsSystem = nullptr; // Physics system this body belongs to +}; + +// Josh: This doesn't handle mass change and is kind of a hack and sliightly wrong. +// Would be nice to just specify spring constant directly in Jolt. +inline float GetInvEffectiveMass( JoltPhysicsObject *pObject0, JoltPhysicsObject *pObject1 ) +{ + return ( pObject0->IsStatic() ? 0.0f : pObject0->GetInvMass() ) + ( pObject1->IsStatic() ? 0.0f : pObject1->GetInvMass() ); +} + +inline float GetSpringFrequency( float flConstant, JoltPhysicsObject *pObject0, JoltPhysicsObject *pObject1 ) +{ + return sqrt( flConstant * GetInvEffectiveMass( pObject0, pObject1 ) ) / ( 2.0f * M_PI_F ); +} diff --git a/vphysics_jolt/vjolt_objectpairhash.cpp b/vphysics_jolt/vjolt_objectpairhash.cpp new file mode 100644 index 0000000..f54a696 --- /dev/null +++ b/vphysics_jolt/vjolt_objectpairhash.cpp @@ -0,0 +1,96 @@ + +#include "cbase.h" + +#include "vjolt_objectpairhash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +static std::pair< void *, void * > CreateSortedPair( void *pObject0, void *pObject1 ) +{ + return std::make_pair( + pObject0 <= pObject1 ? pObject0 : pObject1, + pObject0 <= pObject1 ? pObject1 : pObject0 ); +} + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsObjectPairHash::JoltPhysicsObjectPairHash() +{ +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsObjectPairHash::AddObjectPair( void *pObject0, void *pObject1 ) +{ + auto pair = CreateSortedPair( pObject0, pObject1 ); + + if ( IsObjectPairInHash( pObject0, pObject1 ) ) + return; + + m_PairHashes[ GetHashArrayIndex( PointerHasher{}( pair ) ) ].emplace( pair ); + m_ObjectHashes[ GetHashArrayIndex( std::hash< void* >()( pObject0 ) ) ].emplace( pair ); + m_ObjectHashes[ GetHashArrayIndex( std::hash< void* >()( pObject1 ) ) ].emplace( pair ); + m_Objects.emplace( pObject0 ); + m_Objects.emplace( pObject1 ); +} + +void JoltPhysicsObjectPairHash::RemoveObjectPair( void *pObject0, void *pObject1 ) +{ + auto pair = CreateSortedPair( pObject0, pObject1 ); + + if ( !IsObjectPairInHash( pObject0, pObject1 ) ) + return; + + m_PairHashes[ GetHashArrayIndex( PointerHasher{}( pair ) ) ].erase( pair ); + m_ObjectHashes[ GetHashArrayIndex( std::hash< void* >()( pObject0 ) ) ].erase( pair ); + m_ObjectHashes[ GetHashArrayIndex( std::hash< void* >()( pObject1 ) ) ].erase( pair ); + m_Objects.erase( pObject0 ); + m_Objects.erase( pObject1 ); +} + +bool JoltPhysicsObjectPairHash::IsObjectPairInHash( void *pObject0, void *pObject1 ) +{ + auto pair = CreateSortedPair( pObject0, pObject1 ); + return m_PairHashes[ GetHashArrayIndex( PointerHasher{}( pair ) ) ].contains( pair ); +} + +void JoltPhysicsObjectPairHash::RemoveAllPairsForObject( void *pObject0 ) +{ + auto &objectHashes = m_ObjectHashes[ GetHashArrayIndex( std::hash< void* >()( pObject0 ) ) ]; + + for ( auto it = objectHashes.begin(); it != objectHashes.end(); ) + { + auto pair = *it++; + + RemoveObjectPair( pair.first, pair.second ); + } +} + +bool JoltPhysicsObjectPairHash::IsObjectInHash( void *pObject0 ) +{ + return m_Objects.contains( pObject0 ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsObjectPairHash::GetPairCountForObject( void *pObject0 ) +{ + return int( m_Objects.count( pObject0 ) ); +} + +int JoltPhysicsObjectPairHash::GetPairListForObject( void *pObject0, int nMaxCount, void **ppObjectList ) +{ + auto& objectHashes = m_ObjectHashes[GetHashArrayIndex( std::hash< void* >()( pObject0 ) )]; + + int nCount = 0; + for ( auto it = objectHashes.begin(); it != objectHashes.end() && nCount < nMaxCount; ++it, ++nCount ) + { + auto pair = *it; + ppObjectList[ nCount ] = pair.second != pObject0 ? pair.second : pair.first; + } + + return nCount; +} diff --git a/vphysics_jolt/vjolt_objectpairhash.h b/vphysics_jolt/vjolt_objectpairhash.h new file mode 100644 index 0000000..e887b10 --- /dev/null +++ b/vphysics_jolt/vjolt_objectpairhash.h @@ -0,0 +1,49 @@ + +#pragma once + +class JoltPhysicsObjectPairHash : public IPhysicsObjectPairHash +{ +public: + JoltPhysicsObjectPairHash(); + + void AddObjectPair( void *pObject0, void *pObject1 ) override; + void RemoveObjectPair( void *pObject0, void *pObject1 ) override; + bool IsObjectPairInHash( void *pObject0, void *pObject1 ) override; + void RemoveAllPairsForObject( void *pObject0 ) override; + bool IsObjectInHash( void *pObject0 ) override; + + int GetPairCountForObject( void *pObject0 ) override; + int GetPairListForObject( void *pObject0, int nMaxCount, void **ppObjectList ) override; + +private: + + struct PointerHasher + { + template < typename T > + static void HashCombine( size_t& seed, const T& v ) + { + std::hash< T > hasher; + seed ^= hasher( v ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 ); + } + + size_t operator() ( const std::pair< void*, void* >& val ) const + { + size_t hash = 0; + HashCombine( hash, val.first ); + HashCombine( hash, val.second ); + return hash; + } + }; + + static constexpr size_t HashSize = 1024; + static constexpr size_t GetHashArrayIndex( size_t hash ) + { + return hash & ( HashSize - 1u ); + } + + using HashEntries = std::unordered_set< std::pair< void*, void* >, PointerHasher >; + + std::array< HashEntries, HashSize > m_PairHashes; + std::array< HashEntries, HashSize > m_ObjectHashes; + std::unordered_multiset< void* > m_Objects; +}; diff --git a/vphysics_jolt/vjolt_parse.cpp b/vphysics_jolt/vjolt_parse.cpp new file mode 100644 index 0000000..9e26dfd --- /dev/null +++ b/vphysics_jolt/vjolt_parse.cpp @@ -0,0 +1,496 @@ +ο»Ώ +#include "cbase.h" + +#include "vjolt_keyvalues_schema.h" +#include "vjolt_surfaceprops.h" + +#include "vjolt_parse.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +class JoltPhysicsParseKV final : public IVPhysicsKeyParser +{ +public: + JoltPhysicsParseKV( KeyValues *pKV ); + ~JoltPhysicsParseKV() override; + + const char* GetCurrentBlockName() override; + bool Finished() override; + void ParseSolid( solid_t *pSolid, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void ParseFluid( fluid_t *pFluid, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void ParseRagdollConstraint( constraint_ragdollparams_t *pConstraint, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void ParseSurfaceTable( int *table, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void ParseCustom( void *pCustom, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void ParseVehicle( vehicleparams_t *pVehicle, IVPhysicsKeyHandler *unknownKeyHandler ) override; + void SkipBlock() override; + void ParseCollisionRules( ragdollcollisionrules_t *pRules, IVPhysicsKeyHandler *unknownKeyHandler ) override_asw; + void ParseRagdollAnimatedFriction( ragdollanimatedfriction_t *pFriction, IVPhysicsKeyHandler *unknownKeyHandler ) override_asw; + +private: + + void NextBlock(); + + KeyValues *m_pKV; + KeyValues *m_pCurrentBlock; +}; + +//------------------------------------------------------------------------------------------------- + +struct JoltPhysicsCollisionRulesHelper +{ + ragdollcollisionrules_t Rules; + IVPhysicsKeyHandler *pUnknownKeyHandler; + + JoltPhysicsIntPair CollisionPair; +}; + +//------------------------------------------------------------------------------------------------- + +static const JoltKVSchemaProp_t kSolidDescs[] = +{ + { "Index", KVSCHEMA_DESC( solid_t, index ), FillIntProp }, +#ifdef GAME_ASW_OR_NEWER + { "Contents", KVSCHEMA_DESC( solid_t, contents ), FillIntProp }, +#endif + { "Name", KVSCHEMA_DESC( solid_t, name ), FillStringProp }, + { "Parent", KVSCHEMA_DESC( solid_t, parent ), FillStringProp }, + { "Mass", KVSCHEMA_DESC( solid_t, params.mass ), FillFloatProp }, + { "SurfaceProp", KVSCHEMA_DESC( solid_t, surfaceprop ), FillStringProp }, + { "MassCenterOverride", KVSCHEMA_DESC( solid_t, massCenterOverride ), FillVectorProp, + []( void *pBaseObject ) + { + // Josh: + // If we ended up setting this, we need to update the pointer + // of the params to point to our Vector. + solid_t *pSolid = reinterpret_cast< solid_t * >( pBaseObject ); + pSolid->params.massCenterOverride = &pSolid->massCenterOverride; + } + }, + { "Damping", KVSCHEMA_DESC( solid_t, params.damping ), FillFloatProp }, + { "RotDamping", KVSCHEMA_DESC( solid_t, params.rotdamping ), FillFloatProp }, + { "Drag", KVSCHEMA_DESC( solid_t, params.dragCoefficient ), FillFloatProp }, + //{ "RollingDrag", KVSCHEMA_DESC( solid_t, params.rollingDrag ), FillFloatProp }, + { "Inertia", KVSCHEMA_DESC( solid_t, params.inertia ), FillFloatProp }, + { "RotInertiaLimit", KVSCHEMA_DESC( solid_t, params.rotInertiaLimit ), FillFloatProp }, + { "Volume", KVSCHEMA_DESC( solid_t, params.volume ), FillFloatProp }, +}; + +static const JoltKVSchemaProp_t kFluidDescs[] = +{ + { "Index", KVSCHEMA_DESC( fluid_t, index ), FillIntProp }, + { "SurfaceProp", KVSCHEMA_DESC( fluid_t, surfaceprop ), FillStringProp }, + { "Damping", KVSCHEMA_DESC( fluid_t, params.damping ), FillFloatProp }, + { "SurfacePlane", KVSCHEMA_DESC( fluid_t, params.surfacePlane ), FillVector4DProp }, + { "CurrentVelocity", KVSCHEMA_DESC( fluid_t, params.currentVelocity ), FillVectorProp }, + { "Contents", KVSCHEMA_DESC( fluid_t, params.contents ), FillIntProp }, +}; + +static const JoltKVSchemaProp_t kRagdollDescs[] = +{ + { "Parent", KVSCHEMA_DESC( constraint_ragdollparams_t, parentIndex ), FillIntProp }, + { "Child", KVSCHEMA_DESC( constraint_ragdollparams_t, childIndex ), FillIntProp }, + + { "XMin", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[0].minRotation ), FillFloatProp }, + { "YMin", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[1].minRotation ), FillFloatProp }, + { "ZMin", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[2].minRotation ), FillFloatProp }, + + { "XMax", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[0].maxRotation ), FillFloatProp }, + { "YMax", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[1].maxRotation ), FillFloatProp }, + { "ZMax", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[2].maxRotation ), FillFloatProp }, + + { "XFriction", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[0].torque ), FillFloatProp, + []( void *pBaseObject ) + { + constraint_ragdollparams_t *pParams = reinterpret_cast< constraint_ragdollparams_t * >( pBaseObject ); + pParams->axes[0].angularVelocity = 0; + }, + }, + { "YFriction", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[1].torque ), FillFloatProp, + []( void *pBaseObject ) + { + constraint_ragdollparams_t *pParams = reinterpret_cast< constraint_ragdollparams_t * >( pBaseObject ); + pParams->axes[1].angularVelocity = 0; + }, + }, + { "ZFriction", KVSCHEMA_DESC( constraint_ragdollparams_t, axes[2].torque ), FillFloatProp, + []( void *pBaseObject ) + { + constraint_ragdollparams_t *pParams = reinterpret_cast< constraint_ragdollparams_t * >( pBaseObject ); + pParams->axes[2].angularVelocity = 0; + }, + }, +}; + +static const JoltKVSchemaProp_t kVehicleAxleWheelDescs[] = +{ + { "Radius", KVSCHEMA_DESC( vehicle_wheelparams_t, radius ), FillFloatProp }, + { "Mass", KVSCHEMA_DESC( vehicle_wheelparams_t, mass ), FillFloatProp }, + { "Intertia", KVSCHEMA_DESC( vehicle_wheelparams_t, inertia ), FillFloatProp }, + { "Damping", KVSCHEMA_DESC( vehicle_wheelparams_t, damping ), FillFloatProp }, + { "RotDamping", KVSCHEMA_DESC( vehicle_wheelparams_t, rotdamping ), FillFloatProp }, + { "FrictionScale", KVSCHEMA_DESC( vehicle_wheelparams_t, frictionScale ), FillFloatProp }, + { "Material", KVSCHEMA_DESC( vehicle_wheelparams_t, materialIndex ), FillSurfaceProp }, + { "SkidMaterial", KVSCHEMA_DESC( vehicle_wheelparams_t, skidMaterialIndex ), FillSurfaceProp }, + { "BrakeMaterial", KVSCHEMA_DESC( vehicle_wheelparams_t, brakeMaterialIndex ), FillSurfaceProp }, +}; + +static const JoltKVSchemaProp_t kVehicleAxleSuspensionDescs[] = +{ + { "SpringConstant", KVSCHEMA_DESC( vehicle_suspensionparams_t, springConstant ), FillFloatProp }, + { "SpringDamping", KVSCHEMA_DESC( vehicle_suspensionparams_t, springDamping ), FillFloatProp }, + { "StabilizerConstant", KVSCHEMA_DESC( vehicle_suspensionparams_t, stabilizerConstant ), FillFloatProp }, + { "SpringDampingCompression", KVSCHEMA_DESC( vehicle_suspensionparams_t, springDampingCompression ), FillFloatProp }, + { "MaxBodyForce", KVSCHEMA_DESC( vehicle_suspensionparams_t, maxBodyForce ), FillFloatProp }, +}; + +static const JoltKVSchemaProp_t kVehicleAxleDescs[] = +{ + { "Wheel", KVSCHEMA_DESC( vehicle_axleparams_t, wheels ), + { + sizeof( vehicle_wheelparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_wheelparams_t *pWheelParams = reinterpret_cast< vehicle_wheelparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleAxleWheelDescs, ARRAYSIZE( kVehicleAxleWheelDescs ), pWheelParams ); + } + } + }, + { "Suspension", KVSCHEMA_DESC( vehicle_axleparams_t, suspension ), + { + sizeof( vehicle_suspensionparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_suspensionparams_t *pSuspensionParams = reinterpret_cast< vehicle_suspensionparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleAxleSuspensionDescs, ARRAYSIZE( kVehicleAxleSuspensionDescs ), pSuspensionParams ); + } + } + }, + { "Offset", KVSCHEMA_DESC( vehicle_axleparams_t, offset ), FillVectorProp }, + { "WheelOffset", KVSCHEMA_DESC( vehicle_axleparams_t, wheelOffset ), FillVectorProp }, + { "TorqueFactor", KVSCHEMA_DESC( vehicle_axleparams_t, torqueFactor ), FillFloatProp }, + { "BrakeFactor", KVSCHEMA_DESC( vehicle_axleparams_t, brakeFactor ), FillFloatProp }, +}; + +static const JoltKVSchemaProp_t kVehicleBodyDescs[] = +{ + { "MassCenterOverride", KVSCHEMA_DESC( vehicle_bodyparams_t, massCenterOverride ), FillVectorProp }, + { "AddGravity", KVSCHEMA_DESC( vehicle_bodyparams_t, addGravity ), FillFloatProp }, + { "MaxAngularVelocity", KVSCHEMA_DESC( vehicle_bodyparams_t, maxAngularVelocity ), FillFloatProp }, + { "MassOverride", KVSCHEMA_DESC( vehicle_bodyparams_t, massOverride ), FillFloatProp }, + { "TiltForce", KVSCHEMA_DESC( vehicle_bodyparams_t, tiltForce ), FillFloatProp }, + { "TiltForceHeight", KVSCHEMA_DESC( vehicle_bodyparams_t, tiltForceHeight ), FillFloatProp }, + { "CounterTorqueFactor", KVSCHEMA_DESC( vehicle_bodyparams_t, counterTorqueFactor ), FillFloatProp }, + { "KeepUprightTorque", KVSCHEMA_DESC( vehicle_bodyparams_t, keepUprightTorque ), FillFloatProp }, +}; + +static const JoltKVSchemaProp_t kVehicleEngineBoostDescs[] = +{ + { "Force", KVSCHEMA_DESC( vehicle_engineparams_t, boostForce ), FillFloatProp }, + { "Duration", KVSCHEMA_DESC( vehicle_engineparams_t, boostDuration ), FillFloatProp }, + { "Delay", KVSCHEMA_DESC( vehicle_engineparams_t, boostDelay ), FillFloatProp }, + { "MaxSpeed", KVSCHEMA_DESC( vehicle_engineparams_t, boostMaxSpeed ), FillFloatProp }, + { "TorqueBoost", KVSCHEMA_DESC( vehicle_engineparams_t, torqueBoost ), FillBoolProp }, +}; + +static const JoltKVSchemaProp_t kVehicleEngineDescs[] = +{ + { "Boost", KVSCHEMA_DESC_NO_OFFSET( vehicle_engineparams_t ), + { + sizeof( vehicle_engineparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_engineparams_t *pEngineParams = reinterpret_cast< vehicle_engineparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleEngineBoostDescs, ARRAYSIZE( kVehicleEngineBoostDescs ), pEngineParams ); + } + } + }, + { "Gear", KVSCHEMA_DESC_ARRAY( vehicle_engineparams_t, gearRatio, gearCount ), FillFloatProp }, + { "Horsepower", KVSCHEMA_DESC( vehicle_engineparams_t, horsepower ), FillFloatProp }, + { "MaxSpeed", KVSCHEMA_DESC( vehicle_engineparams_t, maxSpeed ), FillFloatProp }, + { "MaxReverseSpeed", KVSCHEMA_DESC( vehicle_engineparams_t, maxRevSpeed ), FillFloatProp }, + { "AxleRatio", KVSCHEMA_DESC( vehicle_engineparams_t, axleRatio ), FillFloatProp }, + { "MaxRPM", KVSCHEMA_DESC( vehicle_engineparams_t, maxRPM ), FillFloatProp }, + { "ThrottleTime", KVSCHEMA_DESC( vehicle_engineparams_t, throttleTime ), FillFloatProp }, + { "AutoTransmission", KVSCHEMA_DESC( vehicle_engineparams_t, isAutoTransmission ), FillBoolProp }, + { "ShiftUpRPM", KVSCHEMA_DESC( vehicle_engineparams_t, shiftUpRPM ), FillFloatProp }, + { "ShiftDownRPM", KVSCHEMA_DESC( vehicle_engineparams_t, shiftDownRPM ), FillFloatProp }, + { "AutobrakeSpeedGain", KVSCHEMA_DESC( vehicle_engineparams_t, autobrakeSpeedGain ), FillFloatProp }, + { "AutobrakeSpeedFactor", KVSCHEMA_DESC( vehicle_engineparams_t, autobrakeSpeedFactor ), FillFloatProp }, +}; + +static const JoltKVSchemaProp_t kVehicleSteeringDescs[] = +{ + { "DegreesSlow", KVSCHEMA_DESC( vehicle_steeringparams_t, degreesSlow ), FillFloatProp }, + { "DegreesFast", KVSCHEMA_DESC( vehicle_steeringparams_t, degreesFast ), FillFloatProp }, + { "DegreesBoost", KVSCHEMA_DESC( vehicle_steeringparams_t, degreesBoost ), FillFloatProp }, + + { "SlowCarSpeed", KVSCHEMA_DESC( vehicle_steeringparams_t, speedSlow ), FillFloatProp }, + { "FastCarSpeed", KVSCHEMA_DESC( vehicle_steeringparams_t, speedFast ), FillFloatProp }, + + { "SlowSteeringRate", KVSCHEMA_DESC( vehicle_steeringparams_t, steeringRateSlow ), FillFloatProp }, + { "FastSteeringRate", KVSCHEMA_DESC( vehicle_steeringparams_t, steeringRateFast ), FillFloatProp }, + + { "SteeringRestRateSlow", KVSCHEMA_DESC( vehicle_steeringparams_t, steeringRestRateSlow ), FillFloatProp }, + { "SteeringRestRateFast", KVSCHEMA_DESC( vehicle_steeringparams_t, steeringRestRateFast ), FillFloatProp }, + + { "ThrottleSteeringRestRateFactor", KVSCHEMA_DESC( vehicle_steeringparams_t, throttleSteeringRestRateFactor ), FillFloatProp }, + { "BoostSteeringRestRateFactor", KVSCHEMA_DESC( vehicle_steeringparams_t, boostSteeringRestRateFactor ), FillFloatProp }, + { "BoostSteeringRateFactor", KVSCHEMA_DESC( vehicle_steeringparams_t, boostSteeringRateFactor ), FillFloatProp }, + + { "SteeringExponent", KVSCHEMA_DESC( vehicle_steeringparams_t, steeringExponent ), FillFloatProp }, + { "TurnThrottleReduceSlow", KVSCHEMA_DESC( vehicle_steeringparams_t, turnThrottleReduceSlow ), FillFloatProp }, + { "TurnThrottleReduceFast", KVSCHEMA_DESC( vehicle_steeringparams_t, turnThrottleReduceFast ), FillFloatProp }, + + { "BrakeSteeringRateFactor", KVSCHEMA_DESC( vehicle_steeringparams_t, brakeSteeringRateFactor ), FillFloatProp }, + { "PowerSlideAccel", KVSCHEMA_DESC( vehicle_steeringparams_t, powerSlideAccel ), FillFloatProp }, + + { "SkidAllowed", KVSCHEMA_DESC( vehicle_steeringparams_t, isSkidAllowed ), FillBoolProp }, + { "DustCloud", KVSCHEMA_DESC( vehicle_steeringparams_t, dustCloud ), FillBoolProp }, +}; + +static const JoltKVSchemaProp_t kVehicleDescs[] = +{ + { "Axle", KVSCHEMA_DESC_ARRAY( vehicleparams_t, axles, axleCount ), + { + sizeof( vehicle_axleparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_axleparams_t *pAxleParams = reinterpret_cast< vehicle_axleparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleAxleDescs, ARRAYSIZE( kVehicleAxleDescs ), pAxleParams ); + } + } + }, + { "Body", KVSCHEMA_DESC( vehicleparams_t, body ), + { + sizeof( vehicle_bodyparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_bodyparams_t *pBodyParams = reinterpret_cast< vehicle_bodyparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleBodyDescs, ARRAYSIZE( kVehicleBodyDescs ), pBodyParams ); + } + } + }, + { "Engine", KVSCHEMA_DESC( vehicleparams_t, engine ), + { + sizeof( vehicle_engineparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_engineparams_t *pEngineParams = reinterpret_cast< vehicle_engineparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleEngineDescs, ARRAYSIZE( kVehicleEngineDescs ), pEngineParams ); + } + } + }, + { "Steering", KVSCHEMA_DESC( vehicleparams_t, steering ), + { + sizeof( vehicle_steeringparams_t ), + []( KeyValues *pProp, void *pPtr, size_t size ) + { + vehicle_steeringparams_t *pSteeringParams = reinterpret_cast< vehicle_steeringparams_t * >( pPtr ); + ParseJoltKVSchema( pProp, kVehicleSteeringDescs, ARRAYSIZE( kVehicleSteeringDescs ), pSteeringParams ); + } + } + }, + { "WheelsPerAxle", KVSCHEMA_DESC( vehicleparams_t, wheelsPerAxle ), FillIntProp }, +}; + +static const JoltKVSchemaProp_t kCollisionRulesDescs[] = +{ + { "SelfCollisions", KVSCHEMA_DESC( JoltPhysicsCollisionRulesHelper, Rules.bSelfCollisions ), FillIntProp }, + { "CollisionPair", KVSCHEMA_DESC( JoltPhysicsCollisionRulesHelper, CollisionPair ), FillIntPairProp, + []( void *pBaseObject ) + { + // Josh: + // Now that it's been parsed, set it on the collision set. + JoltPhysicsCollisionRulesHelper* pHelper = reinterpret_cast( pBaseObject ); + if ( pHelper->Rules.bSelfCollisions ) + pHelper->Rules.pCollisionSet->EnableCollisions( pHelper->CollisionPair.Index0, pHelper->CollisionPair.Index1 ); + } + }, +}; + +static const JoltKVSchemaProp_t kRagdollAnimatedFrictionDescs[] = +{ + { "AnimFrictionMin", KVSCHEMA_DESC( ragdollanimatedfriction_t, minFriction ), FillFloatProp }, + { "AnimFrictionMax", KVSCHEMA_DESC( ragdollanimatedfriction_t, maxFriction ), FillFloatProp }, + { "AnimFrictionTimeIn", KVSCHEMA_DESC( ragdollanimatedfriction_t, timeIn ), FillFloatProp }, + { "AnimFrictionTimeOut", KVSCHEMA_DESC( ragdollanimatedfriction_t, timeOut ), FillFloatProp }, + { "AnimFrictionTimeHold", KVSCHEMA_DESC( ragdollanimatedfriction_t, timeHold ), FillFloatProp }, +}; + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsParseKV::JoltPhysicsParseKV( KeyValues *pKV ) + : m_pKV( pKV ) + , m_pCurrentBlock( m_pKV->GetFirstSubKey() ) +{ +} + +JoltPhysicsParseKV::~JoltPhysicsParseKV() +{ +} + +//------------------------------------------------------------------------------------------------- + +const char* JoltPhysicsParseKV::GetCurrentBlockName() +{ + if ( !m_pCurrentBlock ) + return nullptr; + + return m_pCurrentBlock->GetName(); +} + +bool JoltPhysicsParseKV::Finished() +{ + return !m_pCurrentBlock; +} + +void JoltPhysicsParseKV::ParseSolid( solid_t *pSolid, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pSolid ); + else + V_memset( pSolid, 0, sizeof( *pSolid ) ); + + ParseJoltKVSchema( m_pCurrentBlock, kSolidDescs, ARRAYSIZE( kSolidDescs ), pSolid, pSolid, unknownKeyHandler ); + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseFluid( fluid_t *pFluid, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pFluid ); + else + { + V_memset( pFluid, 0, sizeof( *pFluid ) ); + V_strncpy( pFluid->surfaceprop, "water", sizeof( pFluid->surfaceprop ) ); + } + + ParseJoltKVSchema( m_pCurrentBlock, kFluidDescs, ARRAYSIZE( kFluidDescs ), pFluid, pFluid, unknownKeyHandler ); + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseRagdollConstraint( constraint_ragdollparams_t *pConstraint, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pConstraint ); + else + { + V_memset( pConstraint, 0, sizeof( *pConstraint ) ); + pConstraint->childIndex = -1; + pConstraint->parentIndex = -1; + } + + // Josh: The KV specifies clockwise rotations. + pConstraint->useClockwiseRotations = true; + + ParseJoltKVSchema( m_pCurrentBlock, kRagdollDescs, ARRAYSIZE( kRagdollDescs ), pConstraint, pConstraint, unknownKeyHandler ); + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseSurfaceTable( int *table, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + for ( KeyValues* pProp = m_pCurrentBlock->GetFirstSubKey(); pProp != nullptr; pProp = pProp->GetNextKey() ) + { + int nPropIdx = JoltPhysicsSurfaceProps::GetInstance().GetSurfaceIndex( pProp->GetName() ); + int nTableIdx = pProp->GetInt(); + + if ( nTableIdx < 128 ) + table[nTableIdx] = nPropIdx; + } + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseCustom( void *pCustom, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pCustom ); + + ParseJoltKVCustom( m_pCurrentBlock, pCustom, unknownKeyHandler ); + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseVehicle( vehicleparams_t *pVehicle, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pVehicle ); + else + V_memset( pVehicle, 0, sizeof( *pVehicle ) ); + + ParseJoltKVSchema( m_pCurrentBlock, kVehicleDescs, ARRAYSIZE( kVehicleDescs ), pVehicle, pVehicle, unknownKeyHandler); + + NextBlock(); +} + +void JoltPhysicsParseKV::SkipBlock() +{ + NextBlock(); +} + +void JoltPhysicsParseKV::ParseCollisionRules( ragdollcollisionrules_t *pRules, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pRules ); + + JoltPhysicsCollisionRulesHelper helper = + { + .Rules = pRules ? *pRules : ragdollcollisionrules_t{}, + .pUnknownKeyHandler = unknownKeyHandler, + }; + + ParseJoltKVSchema( m_pCurrentBlock, kCollisionRulesDescs, ARRAYSIZE( kCollisionRulesDescs ), &helper, pRules, unknownKeyHandler ); + + if ( pRules ) + *pRules = helper.Rules; + + NextBlock(); +} + +void JoltPhysicsParseKV::ParseRagdollAnimatedFriction( ragdollanimatedfriction_t *pFriction, IVPhysicsKeyHandler *unknownKeyHandler ) +{ + if ( unknownKeyHandler ) + unknownKeyHandler->SetDefaults( pFriction ); + else + V_memset( pFriction, 0, sizeof( *pFriction ) ); + + ParseJoltKVSchema( m_pCurrentBlock, kRagdollAnimatedFrictionDescs, ARRAYSIZE( kRagdollAnimatedFrictionDescs ), pFriction, pFriction, unknownKeyHandler ); + + NextBlock(); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsParseKV::NextBlock() +{ + if ( m_pCurrentBlock ) + m_pCurrentBlock = m_pCurrentBlock->GetNextKey(); +} + +//------------------------------------------------------------------------------------------------- + +IVPhysicsKeyParser *CreateVPhysicsKeyParser( const char *pKeyData, bool bIsPacked ) +{ + VJoltAssertMsg( !bIsPacked, "Packed VPhysics KV not supported. You should not get here anyway as we do not emit it." ); + if ( bIsPacked ) + return nullptr; + + KeyValues *pszKV = HeaderlessKVBufferToKeyValues( pKeyData, "VPhysicsKeyParse" ); + + if ( !pszKV ) + return nullptr; + + return new JoltPhysicsParseKV( pszKV ); +} + +void DestroyVPhysicsKeyParser( IVPhysicsKeyParser *pParser ) +{ + JoltPhysicsParseKV *pJoltParser = static_cast< JoltPhysicsParseKV * >( pParser ); + delete pJoltParser; +} diff --git a/vphysics_jolt/vjolt_parse.h b/vphysics_jolt/vjolt_parse.h new file mode 100644 index 0000000..374815c --- /dev/null +++ b/vphysics_jolt/vjolt_parse.h @@ -0,0 +1,9 @@ + +#pragma once + +//------------------------------------------------------------------------------------------------- + +class IVPhysicsKeyParser; + +IVPhysicsKeyParser* CreateVPhysicsKeyParser( const char* pKeyData, bool bIsPacked ); +void DestroyVPhysicsKeyParser( IVPhysicsKeyParser* pParser ); diff --git a/vphysics_jolt/vjolt_querymodel.cpp b/vphysics_jolt/vjolt_querymodel.cpp new file mode 100644 index 0000000..1a32ba9 --- /dev/null +++ b/vphysics_jolt/vjolt_querymodel.cpp @@ -0,0 +1,96 @@ + +#include "cbase.h" + +#include "vjolt_querymodel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +JoltCollisionQuery::JoltCollisionQuery( JPH::Shape *pShape ) + : m_pShape ( pShape ) +{ +} + +//------------------------------------------------------------------------------------------------- + +int JoltCollisionQuery::ConvexCount() +{ + const JPH::StaticCompoundShape *pCompoundShape = GetCompoundShape( m_pShape ); + if ( pCompoundShape ) + return int( pCompoundShape->GetNumSubShapes() ); + + // If we aren't a compound shape, then we have one. + return 1; +} + +int JoltCollisionQuery::TriangleCount( int convexIndex ) +{ + return ActOnSubShape( m_pShape, convexIndex, []( const JPH::Shape* pShape ) -> int + { + JPH::Shape::Stats stats = pShape->GetStats(); + return stats.mNumTriangles; + } ); +} + +//------------------------------------------------------------------------------------------------- + +unsigned int JoltCollisionQuery::GetGameData( int convexIndex ) +{ + return static_cast< unsigned int >( m_pShape->GetUserData() ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltCollisionQuery::GetTriangleVerts( int convexIndex, int triangleIndex, Vector *verts ) +{ + ActOnSubShape( m_pShape, convexIndex, [&]( const JPH::Shape* pShape ) -> int + { + static constexpr int kRequestedTriangles = 2048; + + JPH::Shape::GetTrianglesContext ctx; + pShape->GetTrianglesStart( ctx, JPH::AABox::sBiggest(), JPH::Vec3::sZero(), JPH::Quat::sIdentity(), JPH::Vec3( 1.0f, 1.0f, 1.0f ) ); + int i = 0; + for ( ;; ) + { + JPH::Float3 vertices[ kRequestedTriangles * 3 ]; + int count = pShape->GetTrianglesNext( ctx, kRequestedTriangles, vertices, nullptr /* materials */ ); + if ( count == 0 ) + break; + + if ( triangleIndex >= i && triangleIndex < i + count ) + { + verts[ 0 ] = JoltToSource::Distance( vertices[ ( triangleIndex % kRequestedTriangles ) * 3 + 0 ] ); + verts[ 1 ] = JoltToSource::Distance( vertices[ ( triangleIndex % kRequestedTriangles ) * 3 + 1 ] ); + verts[ 2 ] = JoltToSource::Distance( vertices[ ( triangleIndex % kRequestedTriangles ) * 3 + 2 ] ); + return 0; + } + + i += kRequestedTriangles; + } + + return 1; + } ); + +} + +void JoltCollisionQuery::SetTriangleVerts( int convexIndex, int triangleIndex, const Vector *verts ) +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltCollisionQuery::GetTriangleMaterialIndex( int convexIndex, int triangleIndex ) +{ + Log_Stub( LOG_VJolt ); + return 0; +} + +void JoltCollisionQuery::SetTriangleMaterialIndex( int convexIndex, int triangleIndex, int index7bits ) +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- diff --git a/vphysics_jolt/vjolt_querymodel.h b/vphysics_jolt/vjolt_querymodel.h new file mode 100644 index 0000000..6388242 --- /dev/null +++ b/vphysics_jolt/vjolt_querymodel.h @@ -0,0 +1,23 @@ + +#pragma once + +class JoltCollisionQuery final : public ICollisionQuery +{ +public: + JoltCollisionQuery( JPH::Shape *pShape ); + + int ConvexCount() override; + int TriangleCount( int convexIndex ) override; + + unsigned int GetGameData( int convexIndex ) override; + + void GetTriangleVerts( int convexIndex, int triangleIndex, Vector *verts ) override; + void SetTriangleVerts( int convexIndex, int triangleIndex, const Vector *verts ) override; + + int GetTriangleMaterialIndex( int convexIndex, int triangleIndex ) override; + void SetTriangleMaterialIndex( int convexIndex, int triangleIndex, int index7bits ) override; + +private: + + JPH::Ref m_pShape; +}; diff --git a/vphysics_jolt/vjolt_state_recorder_file.h b/vphysics_jolt/vjolt_state_recorder_file.h new file mode 100644 index 0000000..27b19e5 --- /dev/null +++ b/vphysics_jolt/vjolt_state_recorder_file.h @@ -0,0 +1,35 @@ + +#pragma once + +class JoltStateRecorderFile final : public JPH::StateRecorder +{ +public: + JoltStateRecorderFile( const char* pszPath, bool input ) + : m_Stream( pszPath, std::ios::binary | ( input ? std::ios::in : std::ios::out ) ) + { + } + + JoltStateRecorderFile( JoltStateRecorderFile &&other ) + : StateRecorder( other ) + , m_Stream( move( other.m_Stream ) ) + { + } + + void WriteBytes( const void* inData, size_t inNumBytes ) override + { + m_Stream.write( reinterpret_cast< const char * >( inData ), inNumBytes ); + } + + void ReadBytes( void* outData, size_t inNumBytes ) override + { + m_Stream.read( reinterpret_cast< char * >( outData ), inNumBytes ); + } + + bool IsEOF() const override { return m_Stream.eof(); } + bool IsFailed() const override { return m_Stream.fail(); } + + bool IsValid() const { return !m_Stream.bad(); } + +private: + std::fstream m_Stream; +}; diff --git a/vphysics_jolt/vjolt_surfaceprops.cpp b/vphysics_jolt/vjolt_surfaceprops.cpp new file mode 100644 index 0000000..2dea9af --- /dev/null +++ b/vphysics_jolt/vjolt_surfaceprops.cpp @@ -0,0 +1,272 @@ + +#include "cbase.h" + +#include "vjolt_interface.h" +#include "vjolt_keyvalues_schema.h" + +#include "vjolt_surfaceprops.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsSurfaceProps JoltPhysicsSurfaceProps::s_PhysicsSurfaceProps; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( JoltPhysicsSurfaceProps, IPhysicsSurfaceProps, VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, JoltPhysicsSurfaceProps::GetInstance() ); + +//------------------------------------------------------------------------------------------------- + +static const JoltKVSchemaProp_t kSurfacePropDescs[] = +{ + // Base Property + { "Base", KVSCHEMA_DESC( JoltSurfaceProp, data ), FillBaseProp }, + + // Physics Properties + { "Friction", KVSCHEMA_DESC( JoltSurfaceProp, data.physics.friction ), FillFloatProp }, + { "Elasticity", KVSCHEMA_DESC( JoltSurfaceProp, data.physics.elasticity ), FillFloatProp }, + { "Density", KVSCHEMA_DESC( JoltSurfaceProp, data.physics.density ), FillFloatProp }, + { "Thickness", KVSCHEMA_DESC( JoltSurfaceProp, data.physics.thickness ), FillFloatProp }, + { "Dampening", KVSCHEMA_DESC( JoltSurfaceProp, data.physics.dampening ), FillFloatProp }, + + // Audio Properties + { "AudioReflectivity", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.reflectivity ), FillFloatProp }, + { "AudioHardnessFactor", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.hardnessFactor ), FillFloatProp }, + { "AudioRoughnessFactor", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.roughnessFactor ), FillFloatProp }, + + { "ScrapeRoughThreshold", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.roughThreshold ), FillFloatProp }, + { "ImpactHardThreshold", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.hardThreshold ), FillFloatProp }, + { "AudioHardMinVelocity", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.hardVelocityThreshold ), FillFloatProp }, + +#ifdef GAME_CSGO_OR_NEWER + { "HighPitchOcclusion", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.highPitchOcclusion ), FillFloatProp }, + { "MidPitchOcclusion", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.midPitchOcclusion ), FillFloatProp }, + { "LowPitchOcclusion", KVSCHEMA_DESC( JoltSurfaceProp, data.audio.lowPitchOcclusion ), FillFloatProp }, +#endif + + // Sound Properties +#ifdef GAME_ASW_OR_NEWER + { "StepLeft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.walkStepLeft ), FillSoundProp }, + { "StepLeft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.runStepLeft ), FillSoundProp }, + { "StepRight", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.walkStepRight ), FillSoundProp }, + { "StepRight", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.runStepRight ), FillSoundProp }, + + { "WalkLeft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.walkStepLeft ), FillSoundProp }, + { "WalkRight", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.walkStepRight ), FillSoundProp }, + + { "RunLeft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.runStepLeft ), FillSoundProp }, + { "RunRight", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.runStepRight ), FillSoundProp }, +#else + { "StepLeft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.stepleft ), FillSoundProp }, + { "StepRight", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.stepright ), FillSoundProp }, +#endif + + { "ImpactSoft", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.impactSoft ), FillSoundProp }, + { "ImpactHard", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.impactHard ), FillSoundProp }, + + { "ScrapeSmooth", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.scrapeSmooth ), FillSoundProp }, + { "ScrapeRough", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.scrapeRough ), FillSoundProp }, + + { "BulletImpact", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.bulletImpact ), FillSoundProp }, + { "Rolling", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.rolling ), FillSoundProp }, + + { "Break", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.breakSound ), FillSoundProp }, + { "Strain", KVSCHEMA_DESC( JoltSurfaceProp, data.sounds.strainSound ), FillSoundProp }, + + // Game Properties + { "MaxSpeedFactor", KVSCHEMA_DESC( JoltSurfaceProp, data.game.maxSpeedFactor ), FillFloatProp }, + { "JumpFactor", KVSCHEMA_DESC( JoltSurfaceProp, data.game.jumpFactor ), FillFloatProp }, +#ifdef GAME_CSGO_OR_NEWER + { "PenetrationModifier", KVSCHEMA_DESC( JoltSurfaceProp, data.game.penetrationModifier ), FillFloatProp }, + { "DamageModifier", KVSCHEMA_DESC( JoltSurfaceProp, data.game.damageModifier ), FillFloatProp }, +#endif + { "GameMaterial", KVSCHEMA_DESC( JoltSurfaceProp, data.game.material ), FillGameMaterialProp }, + { "Climbable", KVSCHEMA_DESC( JoltSurfaceProp, data.game.climbable ), FillUnsignedCharProp }, +#ifdef GAME_CSGO_OR_NEWER + { "HideTargetID", KVSCHEMA_DESC( JoltSurfaceProp, data.game.hidetargetid ), FillBoolProp }, + { "DamageLossPercentPerPenetration", KVSCHEMA_DESC( JoltSurfaceProp, data.game.damageLossPercentPerPenetration ), FillFloatProp }, +#endif +}; + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsSurfaceProps::JoltPhysicsSurfaceProps() +{ + JoltSurfaceProp prop = {}; + prop.data.physics.friction = 0.8f; + prop.data.physics.elasticity = 0.25f; + prop.data.physics.density = 2000.0f; + prop.data.physics.thickness = 0.0f; + prop.data.physics.dampening = 0.0f; + m_SurfaceProps[ "default" ] = prop; +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsSurfaceProps::ParseSurfaceData( const char *pFilename, const char *pTextfile ) +{ + KeyValues::AutoDelete kv( SurfacePropsToKeyValues( pTextfile ) ); + + for ( KeyValues* pSurface = kv->GetFirstSubKey(); pSurface != nullptr; pSurface = pSurface->GetNextKey() ) + { + const char *pSurfaceName = pSurface->GetName(); + + JoltSurfaceProp values = {}; + // Try to find it if we already have a material with this name, + // so we can update the values on it, otherwise take the base material. + UtlSymId_t id = m_SurfaceProps.Find( pSurfaceName ); + if ( id != m_SurfaceProps.InvalidIndex() ) + values = m_SurfaceProps[ id ]; + else + values = m_SurfaceProps[ BaseMaterialIdx ]; + + ParseJoltKVSchema( pSurface, kSurfacePropDescs, uint( std::size( kSurfacePropDescs ) ), &values ); + + // If we don't have this already, add it, + // otherwise update the values. + if ( id == m_SurfaceProps.InvalidIndex() ) + m_SurfaceProps[ pSurfaceName ] = values; + else + m_SurfaceProps[ id ] = values; + } + + return m_SurfaceProps.GetNumStrings(); +} + +int JoltPhysicsSurfaceProps::SurfacePropCount( void ) const +{ + return int ( m_SurfaceProps.GetNumStrings() ); +} + +//------------------------------------------------------------------------------------------------- + +int JoltPhysicsSurfaceProps::GetSurfaceIndex( const char *pSurfacePropName ) const +{ + // TODO(Josh): Something about reserved props for $MATERIAL_INDEX_SHADOW + + UtlSymId_t nIndex = m_SurfaceProps.Find( pSurfacePropName ); + if ( nIndex != m_SurfaceProps.InvalidIndex() ) + return int( nIndex ); + + return -1; +} + +void JoltPhysicsSurfaceProps::GetPhysicsProperties( int surfaceDataIndex, float *density, float *thickness, float *friction, float *elasticity ) const +{ + const UtlSymId_t id = surfaceDataIndex >= 0 && surfaceDataIndex < int( m_SurfaceProps.GetNumStrings() ) + ? UtlSymId_t( surfaceDataIndex ) + : BaseMaterialIdx; + + const JoltSurfaceProp& prop = m_SurfaceProps[ id ]; + if ( density ) *density = prop.data.physics.density; + if ( thickness ) *thickness = prop.data.physics.thickness; + if ( friction ) *friction = prop.data.physics.friction; + if ( elasticity ) *elasticity = prop.data.physics.elasticity; +} + +//------------------------------------------------------------------------------------------------- + +surfacedata_t *JoltPhysicsSurfaceProps::GetSurfaceData( int surfaceDataIndex ) +{ + const UtlSymId_t id = surfaceDataIndex >= 0 && surfaceDataIndex < int( m_SurfaceProps.GetNumStrings() ) + ? UtlSymId_t( surfaceDataIndex ) + : BaseMaterialIdx; + + JoltSurfaceProp& prop = m_SurfaceProps[ id ]; + return &prop.data; +} + +const char *JoltPhysicsSurfaceProps::GetString( unsigned short stringTableIndex ) const +{ + return m_SoundStrings.String( stringTableIndex ); +} + +//------------------------------------------------------------------------------------------------- + +const char *JoltPhysicsSurfaceProps::GetPropName( int surfaceDataIndex ) const +{ + if ( surfaceDataIndex < 0 || surfaceDataIndex >= int ( m_SurfaceProps.GetNumStrings() ) ) + return nullptr; + return m_SurfaceProps.String( surfaceDataIndex ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsSurfaceProps::SetWorldMaterialIndexTable( int *pMapArray, int mapSize ) +{ + Log_Stub( LOG_VJolt ); +} + +//------------------------------------------------------------------------------------------------- + +void JoltPhysicsSurfaceProps::GetPhysicsParameters( int surfaceDataIndex, surfacephysicsparams_t *pParamsOut ) const +{ + if ( !pParamsOut ) + return; + + const UtlSymId_t id = surfaceDataIndex >= 0 && surfaceDataIndex < int( m_SurfaceProps.GetNumStrings() ) + ? UtlSymId_t( surfaceDataIndex ) + : BaseMaterialIdx; + + const JoltSurfaceProp& prop = m_SurfaceProps[ id ]; + *pParamsOut = prop.data.physics; +} + +//------------------------------------------------------------------------------------------------- + +ISaveRestoreOps *JoltPhysicsSurfaceProps::GetMaterialIndexDataOps() const +{ + return &JoltPhysicsMaterialIndexSaveOps::GetInstance(); +} + +//------------------------------------------------------------------------------------------------- + +unsigned short JoltPhysicsSurfaceProps::RegisterSound( const char *pName ) +{ + return m_SoundStrings.AddString( pName ); +} + +//------------------------------------------------------------------------------------------------- + +KeyValues *JoltPhysicsSurfaceProps::SurfacePropsToKeyValues( const char *pszBuffer ) +{ + return HeaderlessKVBufferToKeyValues( pszBuffer, "PhysProps" ); +} + +//------------------------------------------------------------------------------------------------- + +JoltPhysicsMaterialIndexSaveOps JoltPhysicsMaterialIndexSaveOps::s_Instance; + +void JoltPhysicsMaterialIndexSaveOps::Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) +{ + int *pMaterialIdx = reinterpret_cast( fieldInfo.pField ); + + const char *pMaterialName = JoltPhysicsSurfaceProps::GetInstance().GetPropName( *pMaterialIdx ); + if ( !pMaterialName ) + pMaterialName = JoltPhysicsSurfaceProps::GetInstance().GetPropName( 0 ); + + int nMaterialNameLength = V_strlen( pMaterialName ) + 1; + pSave->WriteInt( &nMaterialNameLength ); + pSave->WriteString( pMaterialName ); +} + +void JoltPhysicsMaterialIndexSaveOps::Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) +{ + int nMaterialNameLength = pRestore->ReadInt(); + char szMaterialName[ 2048 ]; + pRestore->ReadString( szMaterialName, sizeof( szMaterialName ), nMaterialNameLength ); + + int *pMaterialIdx = reinterpret_cast( fieldInfo.pField ); + *pMaterialIdx = Max( JoltPhysicsSurfaceProps::GetInstance().GetSurfaceIndex( szMaterialName ), 0 ); +} + +bool JoltPhysicsMaterialIndexSaveOps::IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) +{ + int *pMaterialIdx = reinterpret_cast( fieldInfo.pField ); + return !*pMaterialIdx; +} + +void JoltPhysicsMaterialIndexSaveOps::MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) +{ + int* pMaterialIdx = reinterpret_cast( fieldInfo.pField ); + *pMaterialIdx = 0; +} diff --git a/vphysics_jolt/vjolt_surfaceprops.h b/vphysics_jolt/vjolt_surfaceprops.h new file mode 100644 index 0000000..a4dc6d7 --- /dev/null +++ b/vphysics_jolt/vjolt_surfaceprops.h @@ -0,0 +1,60 @@ + +#pragma once + +struct JoltSurfaceProp +{ + surfacedata_t data; +}; + +class JoltPhysicsMaterialIndexSaveOps : public CDefSaveRestoreOps +{ +public: + void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) override; + void Restore( const SaveRestoreFieldInfo_t& fieldInfo, IRestore* pRestore ) override; + + bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) override; + void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) override; + + static JoltPhysicsMaterialIndexSaveOps& GetInstance() { return s_Instance; } + +private: + static JoltPhysicsMaterialIndexSaveOps s_Instance; +}; + +class JoltPhysicsSurfaceProps final : public IPhysicsSurfaceProps +{ +public: + JoltPhysicsSurfaceProps(); + + int ParseSurfaceData( const char *pFilename, const char *pTextfile ) override; + int SurfacePropCount( void ) const override; + + int GetSurfaceIndex( const char *pSurfacePropName ) const override; + void GetPhysicsProperties( int surfaceDataIndex, float *density, float *thickness, float *friction, float *elasticity ) const override; + + surfacedata_t *GetSurfaceData( int surfaceDataIndex ) override; + const char *GetString( unsigned short stringTableIndex ) const override; + + const char *GetPropName( int surfaceDataIndex ) const override; + + void SetWorldMaterialIndexTable( int *pMapArray, int mapSize ) override; + + void GetPhysicsParameters( int surfaceDataIndex, surfacephysicsparams_t *pParamsOut ) const override; + + ISaveRestoreOps *GetMaterialIndexDataOps() const override_portal2; + +public: + static JoltPhysicsSurfaceProps& GetInstance() { return s_PhysicsSurfaceProps; } + + unsigned short RegisterSound( const char *pName ); + +private: + static JoltPhysicsSurfaceProps s_PhysicsSurfaceProps; + + CUtlStringMap< JoltSurfaceProp > m_SurfaceProps; + CUtlSymbolTable m_SoundStrings; + + static constexpr UtlSymId_t BaseMaterialIdx = UtlSymId_t( 0 ); + + KeyValues *SurfacePropsToKeyValues( const char *pszBuffer ); +}; diff --git a/vphysics_jolt/vjolt_util.h b/vphysics_jolt/vjolt_util.h new file mode 100644 index 0000000..52f4ef3 --- /dev/null +++ b/vphysics_jolt/vjolt_util.h @@ -0,0 +1,364 @@ +//================================================================================================= +// +// Source / Jolt utilities +// +//================================================================================================= + +#pragma once + +inline constexpr float InchesToMetres = 0.0254f; +inline constexpr float MetresToInches = 1.0f / 0.0254f; + +// TODO! Remove loam_expr -> constexpr when mathlib stuff is sorted. +#define loam_expr inline + +loam_expr Vector VectorHalfExtent( Vector mins, Vector maxs ) +{ + return 0.5f * ( maxs - mins ); +} + +loam_expr Quaternion ToQuaternion( const QAngle& angles ) +{ + Quaternion result; + AngleQuaternion( angles, result ); + return result; +} + +loam_expr QAngle ToQAngle( const Quaternion &q ) +{ + QAngle result; + QuaternionAngles( q, result ); + return result; +} + +loam_expr QAngle ToQAngle( const matrix3x4_t& m ) +{ + QAngle result; + MatrixAngles( m, result ); + return result; +} + +loam_expr Vector Abs( const Vector &v ) +{ + Vector result; + VectorAbs( v, result ); + return result; +} + +loam_expr Vector Rotate( const Vector &vector, const Quaternion &angle ) +{ + Vector out; + VectorRotate( vector, angle, out ); + return out; +} + +loam_expr Vector Rotate( const Vector& vector, const matrix3x4_t &matrix ) +{ + Vector out; + VectorRotate( vector, matrix, out ); + return out; +} + +template < typename T > +constexpr T Cube( T x ) +{ + return x * x * x; +} + +namespace MatrixAxis +{ + enum JoltMatrixAxes + { + Forward = 0, + Left = 1, + Up = 2, + + X = 0, + Y = 1, + Z = 2, + + Origin = 3, + Projective = 3, + }; +} +using JoltMatrixAxes = MatrixAxis::JoltMatrixAxes; + +loam_expr Vector GetColumn( const matrix3x4_t& m, JoltMatrixAxes axis ) +{ + Vector value; + MatrixGetColumn( m, (int)axis, value ); + return value; +} + +// +// SourceToJolt: +// +// Type conversions from: +// - JPH::Vec3 -> Vector, +// - JPH::Float3 -> Vector, +// - JPH::Quat-> Quaternion, +// - JPH::Quat -> QAngle, +// Unit conversions from: +// - metres -> inches (distance, area, volume, energy) +// - radians -> degrees +namespace JoltToSource +{ + inline constexpr float Factor = MetresToInches; + inline constexpr float InvFactor = InchesToMetres; + + // This is for doing direct type conversions + // ie. normals, directions, scale factors, coefficients and + // certain dimensionless quantities. + loam_expr float Unitless( float value ) { return value; } + loam_expr Vector Unitless( JPH::Vec3Arg value ) { return Vector( value[0], value[1], value[2] ); } + loam_expr Vector Unitless( JPH::Float3 value ) { return Vector( value[0], value[1], value[2] ); } + + // This is used for any unit that has a singular metre factor + // ie. distance (m), velocity (m/s), acceleration (m/s^2), force (kg m/s^2). + loam_expr float Distance( float value ) { return value * Factor; } + loam_expr Vector Distance( JPH::Vec3Arg value ) { return Vector( Distance( value[0] ), Distance( value[1] ), Distance( value[2] ) ); } + loam_expr Vector Distance( JPH::Float3 value ) { return Vector( Distance( value[0] ), Distance( value[1] ), Distance( value[2] ) ); } + + // m^2 -> in^2 + loam_expr float Area( float value ) { return value * Factor * Factor; } + // m^3 -> in^3 + loam_expr float Volume( float value ) { return value * Factor * Factor * Factor; } + + // These handle converting Quaternions/QAngles -> JPH::Quat, + // which is a direct type passthrough. + // They also handles converting scalar angle units, which is rad -> deg. + loam_expr Quaternion Quat( JPH::QuatArg value ) { return Quaternion( value.GetX(), value.GetY(), value.GetZ(), value.GetW() ); } + loam_expr float Angle( float value ) { return RAD2DEG( value ); } + loam_expr QAngle Angle( JPH::QuatArg value ) { return ToQAngle( Quat( value ) ); } + + loam_expr float Energy( float value ) { return value / ( InvFactor * InvFactor ); } + + // Converts types and handles the angle (rad -> deg) unit conversion. + loam_expr float AngularImpulse( float value ) { return Angle( value ); } + loam_expr Vector AngularImpulse( JPH::Vec3Arg value ) { return Vector( AngularImpulse( value[0] ), AngularImpulse( value[1] ), AngularImpulse( value[2] ) ); } + + // Misc. AABB helpers. + loam_expr Vector AABBCenter( const JPH::AABox &aabox ) { return Distance( aabox.mMin + aabox.GetExtent() ); } + loam_expr Vector AABBHalfExtent( const JPH::AABox &aabox ) { return Distance( aabox.GetExtent() ); } + loam_expr void AABBBounds( const JPH::AABox &aabox, Vector &outMins, Vector &outMaxs ) + { + outMins = AABBCenter( aabox ) - AABBHalfExtent( aabox ); + outMaxs = AABBCenter( aabox ) + AABBHalfExtent( aabox ); + } + + loam_expr matrix3x4_t Matrix( JPH::Mat44Arg matrix ) + { + return matrix3x4_t + { + Vector( matrix.GetAxisX().GetX(), matrix.GetAxisX().GetY(), matrix.GetAxisX().GetZ() ), + Vector( matrix.GetAxisY().GetX(), matrix.GetAxisY().GetY(), matrix.GetAxisY().GetZ() ), + Vector( matrix.GetAxisZ().GetX(), matrix.GetAxisZ().GetY(), matrix.GetAxisZ().GetZ() ), + Distance( matrix.GetTranslation() ), + }; + } + + loam_expr ::Color Color( JPH::ColorArg color ) + { + return ::Color( color.r, color.g, color.b, color.a ); + } +} + +// +// SourceToJolt: +// +// Type conversions from: +// - Vector -> JPH::Vec3, +// - Vector -> JPH::Float3, +// - Quaternion -> JPH::Quat, +// - QAngle -> JPH::Quat, +// Unit conversions from: +// - inches -> metres (distance, area, volume, energy) +// - degrees -> radians +namespace SourceToJolt +{ + inline constexpr float Factor = InchesToMetres; + inline constexpr float InvFactor = MetresToInches; + + // This is for doing direct type conversions + // ie. normals, directions, scale factors, coefficients and + // certain dimensionless quantities. + loam_expr float Unitless( float value ) { return value; } + loam_expr JPH::Vec3 Unitless( Vector value ) { return JPH::Vec3( value[0], value[1], value[2] ); } + loam_expr JPH::Float3 UnitlessFloat3( Vector value ) { return JPH::Float3( value[0], value[1], value[2] ); } + + // This is used for any unit that has a singular Source Unit(tm) "inch" factor + // ie. distance (in), velocity (in/s), acceleration (in/s^2), force (kg in/s^2). + loam_expr float Distance( float value ) { return value * Factor; } + loam_expr JPH::Vec3 Distance( Vector value ) { return JPH::Vec3( Distance( value[0] ), Distance( value[1] ), Distance( value[2] ) ); } + loam_expr JPH::Float3 DistanceFloat3( Vector value ) { return JPH::Float3( Distance( value[0] ), Distance( value[1] ), Distance( value[2] ) ); } + + // in^2 -> m^2 + loam_expr float Area( float value ) { return value * Factor * Factor; } + // in^3 -> m^3 + loam_expr float Volume( float value ) { return value * Factor * Factor * Factor; } + + // These handle converting JPH::Quat -> Quaternions/QAngles, + // which is a direct type passthrough. + // They also handles converting scalar angle units, which is deg -> rad. + loam_expr JPH::Quat Quat( Quaternion value ) { return JPH::Quat( value.x, value.y, value.z, value.w ); } + loam_expr float Angle( float value ) { return DEG2RAD( value ); } + loam_expr JPH::Quat Angle( QAngle value ) { return Quat( ToQuaternion( value ) ); } + + loam_expr float Energy( float value ) { return value / ( InvFactor * InvFactor ); } + + // Converts types and handles the angle (deg -> rad) unit conversion. + loam_expr float AngularImpulse( float value ) { return Angle( value ); } + loam_expr JPH::Vec3 AngularImpulse( Vector value ) { return JPH::Vec3( AngularImpulse( value[0] ), AngularImpulse( value[1] ), AngularImpulse( value[2] ) ); } + + // Misc. AABB helpers. + loam_expr JPH::Vec3 AABBCenter( Vector mins, Vector maxs ) { return Distance( mins + VectorHalfExtent( mins, maxs ) ); } + loam_expr JPH::Vec3 AABBHalfExtent( Vector mins, Vector maxs ) { return Distance( VectorHalfExtent( mins, maxs ) ); } + loam_expr JPH::AABox AABBBounds( Vector mins, Vector maxs ) + { + return JPH::AABox + { + AABBCenter( mins, maxs ) - AABBHalfExtent( mins, maxs ), + AABBCenter( mins, maxs ) + AABBHalfExtent( mins, maxs ), + }; + } + + loam_expr JPH::Mat44 Matrix( const matrix3x4_t &m ) + { + return JPH::Mat44 + { + JPH::Vec4( GetColumn( m, MatrixAxis::X ).x, GetColumn( m, MatrixAxis::X ).y, GetColumn( m, MatrixAxis::X ).z, 0.0f ), + JPH::Vec4( GetColumn( m, MatrixAxis::Y ).x, GetColumn( m, MatrixAxis::Y ).y, GetColumn( m, MatrixAxis::Y ).z, 0.0f ), + JPH::Vec4( GetColumn( m, MatrixAxis::Z ).x, GetColumn( m, MatrixAxis::Z ).y, GetColumn( m, MatrixAxis::Z ).z, 0.0f ), + JPH::Vec4( Distance( GetColumn( m, MatrixAxis::Origin ) ), 1.0f ), + }; + } +} + +// Traces + +// Same as CM_ClearTrace +inline void ClearTrace( trace_t *trace ) +{ + memset( trace, 0, sizeof( *trace ) ); + trace->fraction = 1.0f; + trace->fractionleftsolid = 0.0f; + trace->surface.name = "**empty**"; +} + +// Converts a JoltPhysics smart ref-counted pointer to a raw pointer with a dangling +// reference that we can clean up explicitly explcitly later in eg. ConvexFree. +// +// The main reason is to avoids trailing class pointers that just have a single instance +// of this class that then get deleted, when we can just pass the raw pointer around ourselves +// and explicitly dereference on ourside when the game calls delete. +template < typename T > +T *ToDanglingRef( const JPH::Ref< T >& ref ) +{ + T *pPtr = ref.GetPtr(); + pPtr->AddRef(); + return pPtr; +} + +template < typename T > +bool VectorContains( const std::vector< T >& vector, const T &object ) +{ + return std::find(vector.begin(), vector.end(), object) != vector.end(); +} + +inline const JPH::Shape* UndecorateShape( const JPH::Shape *pShape ) +{ + if ( pShape->GetType() == JPH::EShapeType::Decorated ) + pShape = static_cast< const JPH::DecoratedShape * >( pShape )->GetInnerShape(); + + return pShape; +} + +inline const JPH::StaticCompoundShape *GetCompoundShape( const JPH::Shape *pShape ) +{ + pShape = UndecorateShape( pShape ); + + return pShape->GetType() == JPH::EShapeType::Compound + ? static_cast< const JPH::StaticCompoundShape * >( pShape ) + : nullptr; +} + +template < typename T, typename ShapeType, typename Func > +T ActOnSubShape( const JPH::Shape *pShape, int nIndex, Func ShapeFunc ) +{ + const JPH::StaticCompoundShape *pCompoundShape = GetCompoundShape( pShape ); + if ( pCompoundShape ) + { + const JPH::CompoundShape::SubShape& subShape = pCompoundShape->GetSubShape( nIndex ); + return ShapeFunc( static_cast< const ShapeType * >( UndecorateShape( subShape.mShape.GetPtr() ) ) ); + } + + return ShapeFunc( static_cast< const ShapeType * >( UndecorateShape( pShape ) ) ); +} + +template < typename ShapeType, typename Func > +void ActOnSubShapes( const JPH::Shape *pShape, Func ShapeFunc ) +{ + const JPH::StaticCompoundShape *pCompoundShape = GetCompoundShape( pShape ); + if ( pCompoundShape ) + { + for ( const JPH::CompoundShape::SubShape& subShape : pCompoundShape->GetSubShapes() ) + { + JPH::Mat44 matLocalTranslation = JPH::Mat44::sRotationTranslation( subShape.GetRotation(), subShape.GetPositionCOM() ); + ShapeFunc( static_cast< const ShapeType * >( UndecorateShape( subShape.mShape.GetPtr() ) ), matLocalTranslation ); + } + return; + } + + ShapeFunc( static_cast< const ShapeType * >( UndecorateShape( pShape ) ), JPH::Mat44::sIdentity() ); +} + +inline uint32 popcntStep( uint32 n, uint32 mask, uint32 shift ) +{ + return ( n & mask ) + ( ( n & ~mask ) >> shift ); +} + +inline uint32 popcnt( uint32 n ) +{ + n = popcntStep(n, 0x55555555, 1); + n = popcntStep(n, 0x33333333, 2); + n = popcntStep(n, 0x0F0F0F0F, 4); + n = popcntStep(n, 0x00FF00FF, 8); + n = popcntStep(n, 0x0000FFFF, 16); + return n; +} + +inline uint32 tzcnt( uint32 n ) +{ +#if defined(_MSC_VER) && !defined(__clang__) + return _tzcnt_u32( n ); +#elif defined(__BMI__) + return __tzcnt_u32( n ); +#elif defined(__GNUC__) || defined(__clang__) + // tzcnt is encoded as rep bsf, so we can use it on all + // processors, but the behaviour of zero inputs differs: + // - bsf: zf = 1, cf = ?, result = ? + // - tzcnt: zf = 0, cf = 1, result = 32 + // We'll have to handle this case manually. + uint32 res; + uint32 tmp; + asm ( + "tzcnt %2, %0;" + "mov $32, %1;" + "test %2, %2;" + "cmovz %1, %0;" + : "=&r" (res), "=&r" (tmp) + : "r" (n) + : "cc"); + return res; +#else + uint32 r = 31; + n &= -n; + r -= ( n & 0x0000FFFF ) ? 16 : 0; + r -= ( n & 0x00FF00FF ) ? 8 : 0; + r -= ( n & 0x0F0F0F0F ) ? 4 : 0; + r -= ( n & 0x33333333 ) ? 2 : 0; + r -= ( n & 0x55555555 ) ? 1 : 0; + return n != 0 ? r : 32; +#endif +} + diff --git a/vphysics_jolt/vphysics_jolt.vpc b/vphysics_jolt/vphysics_jolt.vpc new file mode 100644 index 0000000..b7f9456 --- /dev/null +++ b/vphysics_jolt/vphysics_jolt.vpc @@ -0,0 +1,9 @@ +//----------------------------------------------------------------------------- +// VPHYSICS_JOLT.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro PROJNAME "vphysics_jolt" + +$Include "vphysics_jolt_inc.vpc" diff --git a/vphysics_jolt/vphysics_jolt_inc.vpc b/vphysics_jolt/vphysics_jolt_inc.vpc new file mode 100644 index 0000000..09e659c --- /dev/null +++ b/vphysics_jolt/vphysics_jolt_inc.vpc @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// VPHYSICS_JOLT_INC.INC +// +// Project Script +//----------------------------------------------------------------------------- + +$MacroRequired "PROJNAME" + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\$GAMEDIR\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Include "..\joltphysics\joltphysics_settings.vpc" + +$Configuration +{ + $Compiler + { + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "cbase.h" + } +} + +$Project "$PROJNAME" +{ + $Folder "Precompiled Header" + { + $File "cbase.h" + $File "cbase.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + } + + $Folder "Source Files" + { + $Folder "Public Files" + { + $File "$SRCDIR\public\collisionutils.cpp" \ + "$SRCDIR\public\filesystem_helpers.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Not Using Precompiled Headers" + } + } + } + } + + $File "vjolt_collide.cpp" + $File "vjolt_collide_trace.cpp" + $File "vjolt_constraints.cpp" + $File "vjolt_controller_fluid.cpp" + $File "vjolt_controller_motion.cpp" + $File "vjolt_controller_player.cpp" + $File "vjolt_controller_vehicle.cpp" + $File "vjolt_controller_shadow.cpp" + $File "vjolt_debugrender.cpp" + $File "vjolt_environment.cpp" + $File "vjolt_friction.cpp" + $File "vjolt_interface.cpp" + $File "vjolt_keyvalues_schema.cpp" + $File "vjolt_object.cpp" + $File "vjolt_objectpairhash.cpp" + $File "vjolt_parse.cpp" + $File "vjolt_querymodel.cpp" + $File "vjolt_surfaceprops.cpp" + } + + $Folder "Header Files" + { + $Folder "Public Files" + { + $File "$SRCDIR\public\collisionutils.h" + $File "$SRCDIR\public\datamap.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\vcollide_parse.h" + $File "$SRCDIR\public\vcollide.h" + } + + $Folder "Compatibility" + { + $File "compat\better_winlite.h" + $File "compat\branch_overrides.h" + $File "compat\compat_sdk2013.h" + } + + $File "vjolt_callstack.h" + $File "vjolt_collide.h" + $File "vjolt_constraints.h" + $File "vjolt_controller_fluid.h" + $File "vjolt_controller_motion.h" + $File "vjolt_controller_player.h" + $File "vjolt_controller_vehicle.h" + $File "vjolt_controller_shadow.h" + $File "vjolt_debugrender.h" + $File "vjolt_environment.h" + $File "vjolt_friction.h" + $File "vjolt_interface.h" + $File "vjolt_internal_listeners.h" + $File "vjolt_keyvalues_schema.h" + $File "vjolt_layers.h" + $File "vjolt_listener_contact.h" + $File "vjolt_object.h" + $File "vjolt_objectpairhash.h" + $File "vjolt_parse.h" + $File "vjolt_querymodel.h" + $File "vjolt_state_recorder_file.h" + $File "vjolt_surfaceprops.h" + $File "vjolt_util.h" + } + + $Folder "Interface" + { + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\vphysics\collision_set.h" + $File "$SRCDIR\public\vphysics\constraints.h" + $File "$SRCDIR\public\vphysics\friction.h" + $File "$SRCDIR\public\vphysics\object_hash.h" + $File "$SRCDIR\public\vphysics\performance.h" + $File "$SRCDIR\public\vphysics\player_controller.h" + $File "$SRCDIR\public\vphysics\stats.h" + $File "$SRCDIR\public\vphysics\vehicles.h" + } + + $Folder "Link Libraries" + { + $Lib "joltphysics" + $Lib "mathlib" + $Lib "tier2" + } +} diff --git a/vphysics_jolt/vphysics_jolt_static.vpc b/vphysics_jolt/vphysics_jolt_static.vpc new file mode 100644 index 0000000..6e41647 --- /dev/null +++ b/vphysics_jolt/vphysics_jolt_static.vpc @@ -0,0 +1,9 @@ +//----------------------------------------------------------------------------- +// VPHYSICS_JOLT_STATIC.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro PROJNAME "vphysics_jolt_static" + +$Include "vphysics_jolt_inc.vpc"