upgrading zphysics
This commit is contained in:
parent
22f65c4fb0
commit
fe2e5b6e42
|
|
@ -0,0 +1,13 @@
|
||||||
|
const c = @import("miniaudio").c;
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
test "miniaudio-compile-check" {
|
||||||
|
try std.testing.expect(c.MA_VERSION_MAJOR == 0);
|
||||||
|
try std.testing.expect(c.MA_VERSION_MINOR == 11);
|
||||||
|
|
||||||
|
var engine: c.ma_engine = undefined;
|
||||||
|
const result = c.ma_engine_init(null, &engine);
|
||||||
|
c.ma_engine_uninit(&engine);
|
||||||
|
|
||||||
|
try std.testing.expect(result == c.MA_SUCCESS);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
orig_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
dryRun = False
|
||||||
|
|
||||||
|
def run(cmd, cwd=None):
|
||||||
|
global dryRun
|
||||||
|
print(" ".join(cmd))
|
||||||
|
c = os.getcwd()
|
||||||
|
|
||||||
|
if cwd is not None:
|
||||||
|
c = cwd
|
||||||
|
|
||||||
|
if not dryRun:
|
||||||
|
print(subprocess.check_output(" ".join(cmd), shell=True, cwd=c).decode(), file=sys.stderr)
|
||||||
|
|
||||||
|
def launchTests():
|
||||||
|
run(['zig', 'env'])
|
||||||
|
|
||||||
|
dirs = list(filter(None, [x if os.path.isdir(x) else None for x in os.listdir(orig_dir)]))
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for d in dirs:
|
||||||
|
print(">>>>> testing " , d)
|
||||||
|
try:
|
||||||
|
run(['zig', 'build', 'test', '-fincremental'], cwd=os.path.join(orig_dir, d))
|
||||||
|
results[d] = "success 🍆💦😏"
|
||||||
|
except:
|
||||||
|
results[d] = "test failed. ❌️🤡🍿🍦🎪🎈"
|
||||||
|
|
||||||
|
for e in results:
|
||||||
|
print(e, results[e])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description='run automation tasks for the repo')
|
||||||
|
|
||||||
|
parser.add_argument('--dry', action="store_true", help='dont run anything')
|
||||||
|
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.dry:
|
||||||
|
dryRun = True
|
||||||
|
|
||||||
|
|
||||||
|
launchTests()
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub const Gltf = @import("Gltf.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Ignore some special directories
|
||||||
|
.zig-cache
|
||||||
|
zig-out
|
||||||
|
|
||||||
|
# Ignore some special OS files
|
||||||
|
*.DS_Store
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
# zphysics v0.1.0 - Build package, [C API](libs/JoltC) and bindings for Jolt Physics
|
# [zphysics](https://github.com/zig-gamedev/zphysics)
|
||||||
|
|
||||||
[Jolt Physics](https://github.com/jrouwe/JoltPhysics) is a fast and modern physics library written in C++.
|
Zig build package, bindings and [C API](libs/JoltC) for [Jolt Physics](https://github.com/jrouwe/JoltPhysics).
|
||||||
|
|
||||||
This project aims to provide high-performance, consistent and roboust [C API](libs/JoltC) and Zig API for Jolt.
|
For a simple sample applications please see [here](https://github.com/zig-gamedev/zig-gamedev/tree/main/samples/physics_test_wgpu/src/physics_test_wgpu.zig).
|
||||||
|
|
||||||
For a simple sample applications please see [here](https://github.com/michal-z/zig-gamedev/tree/main/samples/physics_test_wgpu/src/physics_test_wgpu.zig).
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Copy `zphysics` to a subdirectory of your project and add the following to your `build.zig.zon` .dependencies:
|
Example `build.zig`:
|
||||||
```zig
|
|
||||||
.zphysics = .{ .path = "libs/zphysics" },
|
|
||||||
```
|
|
||||||
|
|
||||||
Then in your `build.zig` add:
|
|
||||||
```zig
|
```zig
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const exe = b.addExecutable(.{ ... });
|
const exe = b.addExecutable(.{ ... });
|
||||||
|
|
@ -106,3 +99,20 @@ pub fn main() !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage in a shared library
|
||||||
|
|
||||||
|
The `joltc` artifact can be built as a shared library by specifying the `shared` build option:
|
||||||
|
|
||||||
|
```
|
||||||
|
const zphysics = b.dependency("zphysics", .{
|
||||||
|
.shared = true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
If your zig module uses `zphysics` and is itself part of a shared library that is reloaded at runtime, then some additional steps are required:
|
||||||
|
|
||||||
|
- Before unloading the shared library, call `preUnload` to export the internal global state
|
||||||
|
- After reloading the shared library, call `postReload` to import the internal state and update allocator vtables
|
||||||
|
|
||||||
|
If you use `registerTrace` or `registerAssertFailed`, these must also be called again to update their function pointers.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn addMacros(module: *std.Build.Module, options: anytype) void {
|
||||||
|
if (options.enable_cross_platform_determinism)
|
||||||
|
module.addCMacro("JPH_CROSS_PLATFORM_DETERMINISTIC", "");
|
||||||
|
if (options.enable_debug_renderer)
|
||||||
|
module.addCMacro("JPH_DEBUG_RENDERER", "");
|
||||||
|
if (options.use_double_precision)
|
||||||
|
module.addCMacro("JPH_DOUBLE_PRECISION", "");
|
||||||
|
if (options.enable_asserts)
|
||||||
|
module.addCMacro("JPH_ENABLE_ASSERTS", "");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false;
|
|
||||||
|
|
||||||
const options = .{
|
const options = .{
|
||||||
.use_double_precision = b.option(
|
.use_double_precision = b.option(
|
||||||
|
|
@ -26,8 +36,24 @@ pub fn build(b: *std.Build) void {
|
||||||
"enable_debug_renderer",
|
"enable_debug_renderer",
|
||||||
"Enable debug renderer",
|
"Enable debug renderer",
|
||||||
) orelse false,
|
) orelse false,
|
||||||
|
.shared = b.option(
|
||||||
|
bool,
|
||||||
|
"shared",
|
||||||
|
"Build JoltC as shared lib",
|
||||||
|
) orelse false,
|
||||||
|
.no_exceptions = b.option(
|
||||||
|
bool,
|
||||||
|
"no_exceptions",
|
||||||
|
"Disable C++ Exceptions",
|
||||||
|
) orelse true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const user_extensions = b.option(
|
||||||
|
[]const std.Build.LazyPath,
|
||||||
|
"user_extensions",
|
||||||
|
"List of user source files to add to the joltc library",
|
||||||
|
) orelse &.{};
|
||||||
|
|
||||||
const options_step = b.addOptions();
|
const options_step = b.addOptions();
|
||||||
inline for (std.meta.fields(@TypeOf(options))) |field| {
|
inline for (std.meta.fields(@TypeOf(options))) |field| {
|
||||||
options_step.addOption(field.type, field.name, @field(options, field.name));
|
options_step.addOption(field.type, field.name, @field(options, field.name));
|
||||||
|
|
@ -43,24 +69,38 @@ pub fn build(b: *std.Build) void {
|
||||||
});
|
});
|
||||||
zjolt.addIncludePath(b.path("libs/JoltC"));
|
zjolt.addIncludePath(b.path("libs/JoltC"));
|
||||||
|
|
||||||
const joltc = if (static_build) b.addStaticLibrary(.{
|
const joltc = b.addLibrary(.{
|
||||||
.name = "joltc",
|
.name = "joltc",
|
||||||
.target = target,
|
.linkage = if (options.shared) .dynamic else .static,
|
||||||
.optimize = optimize,
|
.root_module = b.createModule(.{
|
||||||
}) else b.addSharedLibrary(.{
|
.target = target,
|
||||||
.name = "joltc",
|
.optimize = optimize,
|
||||||
.target = target,
|
}),
|
||||||
.optimize = optimize,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options.shared and target.result.os.tag == .windows)
|
||||||
|
joltc.root_module.addCMacro("JPC_API", "extern __declspec(dllexport)");
|
||||||
|
|
||||||
b.installArtifact(joltc);
|
b.installArtifact(joltc);
|
||||||
|
|
||||||
joltc.addIncludePath(b.path("libs"));
|
joltc.addIncludePath(b.path("libs"));
|
||||||
joltc.addIncludePath(b.path("libs/JoltC"));
|
joltc.addIncludePath(b.path("libs/JoltC"));
|
||||||
joltc.linkLibC();
|
joltc.linkLibC();
|
||||||
if (target.result.abi != .msvc)
|
if (target.result.abi != .msvc) {
|
||||||
joltc.linkLibCpp();
|
joltc.linkLibCpp();
|
||||||
|
} else {
|
||||||
|
joltc.linkSystemLibrary("advapi32");
|
||||||
|
}
|
||||||
|
|
||||||
const src_dir = "libs/Jolt";
|
const src_dir = "libs/Jolt";
|
||||||
|
const c_flags = &.{
|
||||||
|
"-std=c++17",
|
||||||
|
if (options.no_exceptions) "-fno-exceptions" else "",
|
||||||
|
"-fno-access-control",
|
||||||
|
"-fno-sanitize=undefined",
|
||||||
|
};
|
||||||
|
|
||||||
|
addMacros(joltc.root_module, options);
|
||||||
joltc.addCSourceFiles(.{
|
joltc.addCSourceFiles(.{
|
||||||
.files = &.{
|
.files = &.{
|
||||||
"libs/JoltC/JoltPhysicsC.cpp",
|
"libs/JoltC/JoltPhysicsC.cpp",
|
||||||
|
|
@ -69,6 +109,7 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Core/Color.cpp",
|
src_dir ++ "/Core/Color.cpp",
|
||||||
src_dir ++ "/Core/Factory.cpp",
|
src_dir ++ "/Core/Factory.cpp",
|
||||||
src_dir ++ "/Core/IssueReporting.cpp",
|
src_dir ++ "/Core/IssueReporting.cpp",
|
||||||
|
src_dir ++ "/Core/JobSystemSingleThreaded.cpp",
|
||||||
src_dir ++ "/Core/JobSystemThreadPool.cpp",
|
src_dir ++ "/Core/JobSystemThreadPool.cpp",
|
||||||
src_dir ++ "/Core/JobSystemWithBarrier.cpp",
|
src_dir ++ "/Core/JobSystemWithBarrier.cpp",
|
||||||
src_dir ++ "/Core/LinearCurve.cpp",
|
src_dir ++ "/Core/LinearCurve.cpp",
|
||||||
|
|
@ -82,7 +123,6 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Geometry/ConvexHullBuilder2D.cpp",
|
src_dir ++ "/Geometry/ConvexHullBuilder2D.cpp",
|
||||||
src_dir ++ "/Geometry/Indexify.cpp",
|
src_dir ++ "/Geometry/Indexify.cpp",
|
||||||
src_dir ++ "/Geometry/OrientedBox.cpp",
|
src_dir ++ "/Geometry/OrientedBox.cpp",
|
||||||
src_dir ++ "/Math/UVec4.cpp",
|
|
||||||
src_dir ++ "/Math/Vec3.cpp",
|
src_dir ++ "/Math/Vec3.cpp",
|
||||||
src_dir ++ "/ObjectStream/ObjectStream.cpp",
|
src_dir ++ "/ObjectStream/ObjectStream.cpp",
|
||||||
src_dir ++ "/ObjectStream/ObjectStreamBinaryIn.cpp",
|
src_dir ++ "/ObjectStream/ObjectStreamBinaryIn.cpp",
|
||||||
|
|
@ -94,7 +134,6 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/ObjectStream/SerializableObject.cpp",
|
src_dir ++ "/ObjectStream/SerializableObject.cpp",
|
||||||
src_dir ++ "/ObjectStream/TypeDeclarations.cpp",
|
src_dir ++ "/ObjectStream/TypeDeclarations.cpp",
|
||||||
src_dir ++ "/Physics/Body/Body.cpp",
|
src_dir ++ "/Physics/Body/Body.cpp",
|
||||||
src_dir ++ "/Physics/Body/BodyAccess.cpp",
|
|
||||||
src_dir ++ "/Physics/Body/BodyCreationSettings.cpp",
|
src_dir ++ "/Physics/Body/BodyCreationSettings.cpp",
|
||||||
src_dir ++ "/Physics/Body/BodyInterface.cpp",
|
src_dir ++ "/Physics/Body/BodyInterface.cpp",
|
||||||
src_dir ++ "/Physics/Body/BodyManager.cpp",
|
src_dir ++ "/Physics/Body/BodyManager.cpp",
|
||||||
|
|
@ -113,6 +152,7 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Physics/Collision/CollideSphereVsTriangles.cpp",
|
src_dir ++ "/Physics/Collision/CollideSphereVsTriangles.cpp",
|
||||||
src_dir ++ "/Physics/Collision/CollisionDispatch.cpp",
|
src_dir ++ "/Physics/Collision/CollisionDispatch.cpp",
|
||||||
src_dir ++ "/Physics/Collision/CollisionGroup.cpp",
|
src_dir ++ "/Physics/Collision/CollisionGroup.cpp",
|
||||||
|
src_dir ++ "/Physics/Collision/EstimateCollisionResponse.cpp",
|
||||||
src_dir ++ "/Physics/Collision/GroupFilter.cpp",
|
src_dir ++ "/Physics/Collision/GroupFilter.cpp",
|
||||||
src_dir ++ "/Physics/Collision/GroupFilterTable.cpp",
|
src_dir ++ "/Physics/Collision/GroupFilterTable.cpp",
|
||||||
src_dir ++ "/Physics/Collision/ManifoldBetweenTwoFaces.cpp",
|
src_dir ++ "/Physics/Collision/ManifoldBetweenTwoFaces.cpp",
|
||||||
|
|
@ -127,16 +167,19 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Physics/Collision/Shape/ConvexShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/ConvexShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/CylinderShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/CylinderShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/DecoratedShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/DecoratedShape.cpp",
|
||||||
|
src_dir ++ "/Physics/Collision/Shape/EmptyShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/HeightFieldShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/HeightFieldShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/MeshShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/MeshShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/MutableCompoundShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/MutableCompoundShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp",
|
||||||
|
src_dir ++ "/Physics/Collision/Shape/PlaneShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/RotatedTranslatedShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/RotatedTranslatedShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/ScaledShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/ScaledShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/Shape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/Shape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/SphereShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/SphereShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/StaticCompoundShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/StaticCompoundShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/TaperedCapsuleShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/TaperedCapsuleShape.cpp",
|
||||||
|
src_dir ++ "/Physics/Collision/Shape/TaperedCylinderShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/Shape/TriangleShape.cpp",
|
src_dir ++ "/Physics/Collision/Shape/TriangleShape.cpp",
|
||||||
src_dir ++ "/Physics/Collision/TransformedShape.cpp",
|
src_dir ++ "/Physics/Collision/TransformedShape.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/ConeConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/ConeConstraint.cpp",
|
||||||
|
|
@ -152,21 +195,26 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Physics/Constraints/PathConstraintPath.cpp",
|
src_dir ++ "/Physics/Constraints/PathConstraintPath.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/PathConstraintPathHermite.cpp",
|
src_dir ++ "/Physics/Constraints/PathConstraintPathHermite.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/PointConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/PointConstraint.cpp",
|
||||||
|
src_dir ++ "/Physics/Constraints/PulleyConstraint.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/RackAndPinionConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/RackAndPinionConstraint.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/SixDOFConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/SixDOFConstraint.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/SliderConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/SliderConstraint.cpp",
|
||||||
|
src_dir ++ "/Physics/Constraints/SpringSettings.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/SwingTwistConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/SwingTwistConstraint.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/TwoBodyConstraint.cpp",
|
src_dir ++ "/Physics/Constraints/TwoBodyConstraint.cpp",
|
||||||
src_dir ++ "/Physics/Constraints/PulleyConstraint.cpp",
|
|
||||||
src_dir ++ "/Physics/DeterminismLog.cpp",
|
src_dir ++ "/Physics/DeterminismLog.cpp",
|
||||||
src_dir ++ "/Physics/IslandBuilder.cpp",
|
src_dir ++ "/Physics/IslandBuilder.cpp",
|
||||||
src_dir ++ "/Physics/LargeIslandSplitter.cpp",
|
src_dir ++ "/Physics/LargeIslandSplitter.cpp",
|
||||||
src_dir ++ "/Physics/PhysicsScene.cpp",
|
src_dir ++ "/Physics/PhysicsScene.cpp",
|
||||||
src_dir ++ "/Physics/PhysicsSystem.cpp",
|
src_dir ++ "/Physics/PhysicsSystem.cpp",
|
||||||
src_dir ++ "/Physics/PhysicsUpdateContext.cpp",
|
src_dir ++ "/Physics/PhysicsUpdateContext.cpp",
|
||||||
src_dir ++ "/Physics/PhysicsLock.cpp",
|
|
||||||
src_dir ++ "/Physics/Ragdoll/Ragdoll.cpp",
|
src_dir ++ "/Physics/Ragdoll/Ragdoll.cpp",
|
||||||
|
src_dir ++ "/Physics/SoftBody/SoftBodyCreationSettings.cpp",
|
||||||
|
src_dir ++ "/Physics/SoftBody/SoftBodyMotionProperties.cpp",
|
||||||
|
src_dir ++ "/Physics/SoftBody/SoftBodyShape.cpp",
|
||||||
|
src_dir ++ "/Physics/SoftBody/SoftBodySharedSettings.cpp",
|
||||||
src_dir ++ "/Physics/StateRecorderImpl.cpp",
|
src_dir ++ "/Physics/StateRecorderImpl.cpp",
|
||||||
|
src_dir ++ "/Physics/Vehicle/MotorcycleController.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/TrackedVehicleController.cpp",
|
src_dir ++ "/Physics/Vehicle/TrackedVehicleController.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/VehicleAntiRollBar.cpp",
|
src_dir ++ "/Physics/Vehicle/VehicleAntiRollBar.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/VehicleCollisionTester.cpp",
|
src_dir ++ "/Physics/Vehicle/VehicleCollisionTester.cpp",
|
||||||
|
|
@ -178,43 +226,38 @@ pub fn build(b: *std.Build) void {
|
||||||
src_dir ++ "/Physics/Vehicle/VehicleTransmission.cpp",
|
src_dir ++ "/Physics/Vehicle/VehicleTransmission.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/Wheel.cpp",
|
src_dir ++ "/Physics/Vehicle/Wheel.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/WheeledVehicleController.cpp",
|
src_dir ++ "/Physics/Vehicle/WheeledVehicleController.cpp",
|
||||||
src_dir ++ "/Physics/Vehicle/MotorcycleController.cpp",
|
|
||||||
src_dir ++ "/RegisterTypes.cpp",
|
src_dir ++ "/RegisterTypes.cpp",
|
||||||
src_dir ++ "/Renderer/DebugRenderer.cpp",
|
src_dir ++ "/Renderer/DebugRenderer.cpp",
|
||||||
src_dir ++ "/Renderer/DebugRendererPlayback.cpp",
|
src_dir ++ "/Renderer/DebugRendererPlayback.cpp",
|
||||||
src_dir ++ "/Renderer/DebugRendererRecorder.cpp",
|
src_dir ++ "/Renderer/DebugRendererRecorder.cpp",
|
||||||
|
src_dir ++ "/Renderer/DebugRendererSimple.cpp",
|
||||||
src_dir ++ "/Skeleton/SkeletalAnimation.cpp",
|
src_dir ++ "/Skeleton/SkeletalAnimation.cpp",
|
||||||
src_dir ++ "/Skeleton/Skeleton.cpp",
|
src_dir ++ "/Skeleton/Skeleton.cpp",
|
||||||
src_dir ++ "/Skeleton/SkeletonMapper.cpp",
|
src_dir ++ "/Skeleton/SkeletonMapper.cpp",
|
||||||
src_dir ++ "/Skeleton/SkeletonPose.cpp",
|
src_dir ++ "/Skeleton/SkeletonPose.cpp",
|
||||||
src_dir ++ "/TriangleGrouper/TriangleGrouperClosestCentroid.cpp",
|
|
||||||
src_dir ++ "/TriangleGrouper/TriangleGrouperMorton.cpp",
|
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitter.cpp",
|
src_dir ++ "/TriangleSplitter/TriangleSplitter.cpp",
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitterBinning.cpp",
|
src_dir ++ "/TriangleSplitter/TriangleSplitterBinning.cpp",
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitterFixedLeafSize.cpp",
|
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitterLongestAxis.cpp",
|
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitterMean.cpp",
|
src_dir ++ "/TriangleSplitter/TriangleSplitterMean.cpp",
|
||||||
src_dir ++ "/TriangleSplitter/TriangleSplitterMorton.cpp",
|
|
||||||
},
|
|
||||||
.flags = &.{
|
|
||||||
"-std=c++17",
|
|
||||||
if (@import("builtin").abi != .msvc) "-DJPH_COMPILER_MINGW" else "",
|
|
||||||
if (options.enable_cross_platform_determinism) "-DJPH_CROSS_PLATFORM_DETERMINISTIC" else "",
|
|
||||||
if (options.enable_debug_renderer) "-DJPH_DEBUG_RENDERER" else "",
|
|
||||||
if (options.use_double_precision) "-DJPH_DOUBLE_PRECISION" else "",
|
|
||||||
if (options.enable_asserts) "-DJPH_ENABLE_ASSERTS" else "",
|
|
||||||
"-fno-access-control",
|
|
||||||
"-fno-sanitize=undefined",
|
|
||||||
},
|
},
|
||||||
|
.flags = c_flags,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (user_extensions) |user_extension| {
|
||||||
|
joltc.addCSourceFile(.{
|
||||||
|
.file = user_extension,
|
||||||
|
.flags = c_flags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const test_step = b.step("test", "Run zphysics tests");
|
const test_step = b.step("test", "Run zphysics tests");
|
||||||
|
|
||||||
const tests = b.addTest(.{
|
const tests = b.addTest(.{
|
||||||
.name = "zphysics-tests",
|
.name = "zphysics-tests",
|
||||||
.root_source_file = b.path("src/zphysics.zig"),
|
.root_module = b.createModule(.{
|
||||||
.target = target,
|
.root_source_file = b.path("src/zphysics.zig"),
|
||||||
.optimize = optimize,
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
b.installArtifact(tests);
|
b.installArtifact(tests);
|
||||||
|
|
||||||
|
|
@ -223,18 +266,17 @@ pub fn build(b: *std.Build) void {
|
||||||
tests.want_lto = false;
|
tests.want_lto = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMacros(tests.root_module, options);
|
||||||
tests.addCSourceFile(.{
|
tests.addCSourceFile(.{
|
||||||
.file = b.path("libs/JoltC/JoltPhysicsC_Tests.c"),
|
.file = b.path("libs/JoltC/JoltPhysicsC_Tests.c"),
|
||||||
.flags = &.{
|
.flags = &.{
|
||||||
if (@import("builtin").abi != .msvc) "-DJPH_COMPILER_MINGW" else "",
|
|
||||||
if (options.enable_cross_platform_determinism) "-DJPH_CROSS_PLATFORM_DETERMINISTIC" else "",
|
|
||||||
if (options.enable_debug_renderer) "-DJPH_DEBUG_RENDERER" else "",
|
|
||||||
if (options.use_double_precision) "-DJPH_DOUBLE_PRECISION" else "",
|
|
||||||
if (options.enable_asserts) "-DJPH_ENABLE_ASSERTS" else "",
|
|
||||||
"-fno-sanitize=undefined",
|
"-fno-sanitize=undefined",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (b.option(bool, "verbose", "Print verbose test debug output to stderr") orelse false)
|
||||||
|
tests.root_module.addCMacro("PRINT_OUTPUT", "");
|
||||||
|
|
||||||
tests.root_module.addImport("zphysics_options", options_module);
|
tests.root_module.addImport("zphysics_options", options_module);
|
||||||
tests.addIncludePath(b.path("libs/JoltC"));
|
tests.addIncludePath(b.path("libs/JoltC"));
|
||||||
tests.linkLibrary(joltc);
|
tests.linkLibrary(joltc);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
.{
|
.{
|
||||||
.name = .zphysics,
|
.name = .zphysics,
|
||||||
.version = "0.1.0",
|
.fingerprint = 0x1def6aac00c4909d,
|
||||||
|
.version = "0.2.0-dev",
|
||||||
|
.minimum_zig_version = "0.15.1",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
"libs",
|
"libs",
|
||||||
"src",
|
"src",
|
||||||
"README.md",
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
},
|
},
|
||||||
.fingerprint = 0x1def6aace4359182,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,84 +8,72 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
AABBTreeBuilder::Node::Node()
|
uint AABBTreeBuilder::Node::GetMinDepth(const Array<Node> &inNodes) const
|
||||||
{
|
|
||||||
mChild[0] = nullptr;
|
|
||||||
mChild[1] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
AABBTreeBuilder::Node::~Node()
|
|
||||||
{
|
|
||||||
delete mChild[0];
|
|
||||||
delete mChild[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint AABBTreeBuilder::Node::GetMinDepth() const
|
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
{
|
{
|
||||||
uint left = mChild[0]->GetMinDepth();
|
uint left = inNodes[mChild[0]].GetMinDepth(inNodes);
|
||||||
uint right = mChild[1]->GetMinDepth();
|
uint right = inNodes[mChild[1]].GetMinDepth(inNodes);
|
||||||
return min(left, right) + 1;
|
return min(left, right) + 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint AABBTreeBuilder::Node::GetMaxDepth() const
|
uint AABBTreeBuilder::Node::GetMaxDepth(const Array<Node> &inNodes) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
{
|
{
|
||||||
uint left = mChild[0]->GetMaxDepth();
|
uint left = inNodes[mChild[0]].GetMaxDepth(inNodes);
|
||||||
uint right = mChild[1]->GetMaxDepth();
|
uint right = inNodes[mChild[1]].GetMaxDepth(inNodes);
|
||||||
return max(left, right) + 1;
|
return max(left, right) + 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint AABBTreeBuilder::Node::GetNodeCount() const
|
uint AABBTreeBuilder::Node::GetNodeCount(const Array<Node> &inNodes) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
return mChild[0]->GetNodeCount() + mChild[1]->GetNodeCount() + 1;
|
return inNodes[mChild[0]].GetNodeCount(inNodes) + inNodes[mChild[1]].GetNodeCount(inNodes) + 1;
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint AABBTreeBuilder::Node::GetLeafNodeCount() const
|
uint AABBTreeBuilder::Node::GetLeafNodeCount(const Array<Node> &inNodes) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
return mChild[0]->GetLeafNodeCount() + mChild[1]->GetLeafNodeCount();
|
return inNodes[mChild[0]].GetLeafNodeCount(inNodes) + inNodes[mChild[1]].GetLeafNodeCount(inNodes);
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint AABBTreeBuilder::Node::GetTriangleCountInTree() const
|
uint AABBTreeBuilder::Node::GetTriangleCountInTree(const Array<Node> &inNodes) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
return mChild[0]->GetTriangleCountInTree() + mChild[1]->GetTriangleCountInTree();
|
return inNodes[mChild[0]].GetTriangleCountInTree(inNodes) + inNodes[mChild[1]].GetTriangleCountInTree(inNodes);
|
||||||
else
|
else
|
||||||
return GetTriangleCount();
|
return GetTriangleCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AABBTreeBuilder::Node::GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const
|
void AABBTreeBuilder::Node::GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const
|
||||||
{
|
{
|
||||||
outMin = INT_MAX;
|
outMin = INT_MAX;
|
||||||
outMax = 0;
|
outMax = 0;
|
||||||
outAverage = 0;
|
outAverage = 0;
|
||||||
uint avg_divisor = 0;
|
uint avg_divisor = 0;
|
||||||
GetTriangleCountPerNodeInternal(outAverage, avg_divisor, outMin, outMax);
|
GetTriangleCountPerNodeInternal(inNodes, outAverage, avg_divisor, outMin, outMax);
|
||||||
if (avg_divisor > 0)
|
if (avg_divisor > 0)
|
||||||
outAverage /= avg_divisor;
|
outAverage /= avg_divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
float AABBTreeBuilder::Node::CalculateSAHCost(float inCostTraversal, float inCostLeaf) const
|
float AABBTreeBuilder::Node::CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const
|
||||||
{
|
{
|
||||||
float surface_area = mBounds.GetSurfaceArea();
|
float surface_area = mBounds.GetSurfaceArea();
|
||||||
return surface_area > 0.0f? CalculateSAHCostInternal(inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f;
|
return surface_area > 0.0f? CalculateSAHCostInternal(inNodes, inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChildren) const
|
void AABBTreeBuilder::Node::GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node*> &outChildren) const
|
||||||
{
|
{
|
||||||
JPH_ASSERT(outChildren.empty());
|
JPH_ASSERT(outChildren.empty());
|
||||||
|
|
||||||
|
|
@ -94,8 +82,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Start with the children of this node
|
// Start with the children of this node
|
||||||
outChildren.push_back(mChild[0]);
|
outChildren.push_back(&inNodes[mChild[0]]);
|
||||||
outChildren.push_back(mChild[1]);
|
outChildren.push_back(&inNodes[mChild[1]]);
|
||||||
|
|
||||||
size_t next = 0;
|
size_t next = 0;
|
||||||
bool all_triangles = true;
|
bool all_triangles = true;
|
||||||
|
|
@ -106,7 +94,7 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
|
||||||
{
|
{
|
||||||
// If there only triangle nodes left, we have to terminate
|
// If there only triangle nodes left, we have to terminate
|
||||||
if (all_triangles)
|
if (all_triangles)
|
||||||
return;
|
return;
|
||||||
next = 0;
|
next = 0;
|
||||||
all_triangles = true;
|
all_triangles = true;
|
||||||
}
|
}
|
||||||
|
|
@ -116,8 +104,8 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
|
||||||
if (to_expand->HasChildren())
|
if (to_expand->HasChildren())
|
||||||
{
|
{
|
||||||
outChildren.erase(outChildren.begin() + next);
|
outChildren.erase(outChildren.begin() + next);
|
||||||
outChildren.push_back(to_expand->mChild[0]);
|
outChildren.push_back(&inNodes[to_expand->mChild[0]]);
|
||||||
outChildren.push_back(to_expand->mChild[1]);
|
outChildren.push_back(&inNodes[to_expand->mChild[1]]);
|
||||||
all_triangles = false;
|
all_triangles = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -127,22 +115,22 @@ void AABBTreeBuilder::Node::GetNChildren(uint inN, Array<const Node *> &outChild
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float AABBTreeBuilder::Node::CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const
|
float AABBTreeBuilder::Node::CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea()
|
return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea()
|
||||||
+ mChild[0]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea)
|
+ inNodes[mChild[0]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea)
|
||||||
+ mChild[1]->CalculateSAHCostInternal(inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea);
|
+ inNodes[mChild[1]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea);
|
||||||
else
|
else
|
||||||
return inCostLeafDivSurfaceArea * mBounds.GetSurfaceArea() * GetTriangleCount();
|
return inCostLeafDivSurfaceArea * mBounds.GetSurfaceArea() * GetTriangleCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const
|
void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const
|
||||||
{
|
{
|
||||||
if (HasChildren())
|
if (HasChildren())
|
||||||
{
|
{
|
||||||
mChild[0]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax);
|
inNodes[mChild[0]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
|
||||||
mChild[1]->GetTriangleCountPerNodeInternal(outAverage, outAverageDivisor, outMin, outMax);
|
inNodes[mChild[1]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -153,37 +141,45 @@ void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(float &outAverage, u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf) :
|
AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf) :
|
||||||
mTriangleSplitter(inSplitter),
|
mTriangleSplitter(inSplitter),
|
||||||
mMaxTrianglesPerLeaf(inMaxTrianglesPerLeaf)
|
mMaxTrianglesPerLeaf(inMaxTrianglesPerLeaf)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
AABBTreeBuilder::Node *AABBTreeBuilder::Build(AABBTreeBuilderStats &outStats)
|
AABBTreeBuilder::Node *AABBTreeBuilder::Build(AABBTreeBuilderStats &outStats)
|
||||||
{
|
{
|
||||||
TriangleSplitter::Range initial = mTriangleSplitter.GetInitialRange();
|
TriangleSplitter::Range initial = mTriangleSplitter.GetInitialRange();
|
||||||
Node *root = BuildInternal(initial);
|
|
||||||
|
|
||||||
|
// Worst case for number of nodes: 1 leaf node per triangle. At each level above, the number of nodes is half that of the level below.
|
||||||
|
// This means that at most we'll be allocating 2x the number of triangles in nodes.
|
||||||
|
mNodes.reserve(2 * initial.Count());
|
||||||
|
mTriangles.reserve(initial.Count());
|
||||||
|
|
||||||
|
// Build the tree
|
||||||
|
Node &root = mNodes[BuildInternal(initial)];
|
||||||
|
|
||||||
|
// Collect stats
|
||||||
float avg_triangles_per_leaf;
|
float avg_triangles_per_leaf;
|
||||||
uint min_triangles_per_leaf, max_triangles_per_leaf;
|
uint min_triangles_per_leaf, max_triangles_per_leaf;
|
||||||
root->GetTriangleCountPerNode(avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf);
|
root.GetTriangleCountPerNode(mNodes, avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf);
|
||||||
|
|
||||||
mTriangleSplitter.GetStats(outStats.mSplitterStats);
|
mTriangleSplitter.GetStats(outStats.mSplitterStats);
|
||||||
|
|
||||||
outStats.mSAHCost = root->CalculateSAHCost(1.0f, 1.0f);
|
outStats.mSAHCost = root.CalculateSAHCost(mNodes, 1.0f, 1.0f);
|
||||||
outStats.mMinDepth = root->GetMinDepth();
|
outStats.mMinDepth = root.GetMinDepth(mNodes);
|
||||||
outStats.mMaxDepth = root->GetMaxDepth();
|
outStats.mMaxDepth = root.GetMaxDepth(mNodes);
|
||||||
outStats.mNodeCount = root->GetNodeCount();
|
outStats.mNodeCount = root.GetNodeCount(mNodes);
|
||||||
outStats.mLeafNodeCount = root->GetLeafNodeCount();
|
outStats.mLeafNodeCount = root.GetLeafNodeCount(mNodes);
|
||||||
outStats.mMaxTrianglesPerLeaf = mMaxTrianglesPerLeaf;
|
outStats.mMaxTrianglesPerLeaf = mMaxTrianglesPerLeaf;
|
||||||
outStats.mTreeMinTrianglesPerLeaf = min_triangles_per_leaf;
|
outStats.mTreeMinTrianglesPerLeaf = min_triangles_per_leaf;
|
||||||
outStats.mTreeMaxTrianglesPerLeaf = max_triangles_per_leaf;
|
outStats.mTreeMaxTrianglesPerLeaf = max_triangles_per_leaf;
|
||||||
outStats.mTreeAvgTrianglesPerLeaf = avg_triangles_per_leaf;
|
outStats.mTreeAvgTrianglesPerLeaf = avg_triangles_per_leaf;
|
||||||
|
|
||||||
return root;
|
return &root;
|
||||||
}
|
}
|
||||||
|
|
||||||
AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles)
|
uint AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles)
|
||||||
{
|
{
|
||||||
// Check if there are too many triangles left
|
// Check if there are too many triangles left
|
||||||
if (inTriangles.Count() > mMaxTrianglesPerLeaf)
|
if (inTriangles.Count() > mMaxTrianglesPerLeaf)
|
||||||
|
|
@ -192,7 +188,21 @@ AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Ra
|
||||||
TriangleSplitter::Range left, right;
|
TriangleSplitter::Range left, right;
|
||||||
if (!mTriangleSplitter.Split(inTriangles, left, right))
|
if (!mTriangleSplitter.Split(inTriangles, left, right))
|
||||||
{
|
{
|
||||||
JPH_IF_DEBUG(Trace("AABBTreeBuilder: Doing random split for %d triangles (max per node: %d)!", (int)inTriangles.Count(), mMaxTrianglesPerLeaf);)
|
// When the trace below triggers:
|
||||||
|
//
|
||||||
|
// This code builds a tree structure to accelerate collision detection.
|
||||||
|
// At top level it will start with all triangles in a mesh and then divides the triangles into two batches.
|
||||||
|
// This process repeats until until the batch size is smaller than mMaxTrianglePerLeaf.
|
||||||
|
//
|
||||||
|
// It uses a TriangleSplitter to find a good split. When this warning triggers, the splitter was not able
|
||||||
|
// to create a reasonable split for the triangles. This usually happens when the triangles in a batch are
|
||||||
|
// intersecting. They could also be overlapping when projected on the 3 coordinate axis.
|
||||||
|
//
|
||||||
|
// To solve this issue, you could try to pass your mesh through a mesh cleaning / optimization algorithm.
|
||||||
|
// You could also inspect the triangles that cause this issue and see if that part of the mesh can be fixed manually.
|
||||||
|
//
|
||||||
|
// When you do not fix this warning, the tree will be less efficient for collision detection, but it will still work.
|
||||||
|
JPH_IF_DEBUG(Trace("AABBTreeBuilder: Doing random split for %d triangles (max per node: %u)!", (int)inTriangles.Count(), mMaxTrianglesPerLeaf);)
|
||||||
int half = inTriangles.Count() / 2;
|
int half = inTriangles.Count() / 2;
|
||||||
JPH_ASSERT(half > 0);
|
JPH_ASSERT(half > 0);
|
||||||
left = TriangleSplitter::Range(inTriangles.mBegin, inTriangles.mBegin + half);
|
left = TriangleSplitter::Range(inTriangles.mBegin, inTriangles.mBegin + half);
|
||||||
|
|
@ -200,26 +210,33 @@ AABBTreeBuilder::Node *AABBTreeBuilder::BuildInternal(const TriangleSplitter::Ra
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively build
|
// Recursively build
|
||||||
Node *node = new Node();
|
const uint node_index = (uint)mNodes.size();
|
||||||
node->mChild[0] = BuildInternal(left);
|
mNodes.push_back(Node());
|
||||||
node->mChild[1] = BuildInternal(right);
|
uint left_index = BuildInternal(left);
|
||||||
node->mBounds = node->mChild[0]->mBounds;
|
uint right_index = BuildInternal(right);
|
||||||
node->mBounds.Encapsulate(node->mChild[1]->mBounds);
|
Node &node = mNodes[node_index];
|
||||||
return node;
|
node.mChild[0] = left_index;
|
||||||
|
node.mChild[1] = right_index;
|
||||||
|
node.mBounds = mNodes[node.mChild[0]].mBounds;
|
||||||
|
node.mBounds.Encapsulate(mNodes[node.mChild[1]].mBounds);
|
||||||
|
return node_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create leaf node
|
// Create leaf node
|
||||||
Node *node = new Node();
|
const uint node_index = (uint)mNodes.size();
|
||||||
node->mTriangles.reserve(inTriangles.Count());
|
mNodes.push_back(Node());
|
||||||
|
Node &node = mNodes.back();
|
||||||
|
node.mTrianglesBegin = (uint)mTriangles.size();
|
||||||
|
node.mNumTriangles = inTriangles.mEnd - inTriangles.mBegin;
|
||||||
|
const VertexList &v = mTriangleSplitter.GetVertices();
|
||||||
for (uint i = inTriangles.mBegin; i < inTriangles.mEnd; ++i)
|
for (uint i = inTriangles.mBegin; i < inTriangles.mEnd; ++i)
|
||||||
{
|
{
|
||||||
const IndexedTriangle &t = mTriangleSplitter.GetTriangle(i);
|
const IndexedTriangle &t = mTriangleSplitter.GetTriangle(i);
|
||||||
const VertexList &v = mTriangleSplitter.GetVertices();
|
mTriangles.push_back(t);
|
||||||
node->mTriangles.push_back(t);
|
node.mBounds.Encapsulate(v, t);
|
||||||
node->mBounds.Encapsulate(v, t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -32,66 +32,66 @@ struct AABBTreeBuilderStats
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper class to build an AABB tree
|
/// Helper class to build an AABB tree
|
||||||
class AABBTreeBuilder
|
class JPH_EXPORT AABBTreeBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// A node in the tree, contains the AABox for the tree and any child nodes or triangles
|
/// A node in the tree, contains the AABox for the tree and any child nodes or triangles
|
||||||
class Node : public NonCopyable
|
class Node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
/// Constructor
|
/// Indicates that there is no child
|
||||||
Node();
|
static constexpr uint cInvalidNodeIndex = ~uint(0);
|
||||||
~Node();
|
|
||||||
|
|
||||||
/// Get number of triangles in this node
|
/// Get number of triangles in this node
|
||||||
inline uint GetTriangleCount() const { return uint(mTriangles.size()); }
|
inline uint GetTriangleCount() const { return mNumTriangles; }
|
||||||
|
|
||||||
/// Check if this node has any children
|
/// Check if this node has any children
|
||||||
inline bool HasChildren() const { return mChild[0] != nullptr || mChild[1] != nullptr; }
|
inline bool HasChildren() const { return mChild[0] != cInvalidNodeIndex || mChild[1] != cInvalidNodeIndex; }
|
||||||
|
|
||||||
/// Min depth of tree
|
/// Min depth of tree
|
||||||
uint GetMinDepth() const;
|
uint GetMinDepth(const Array<Node> &inNodes) const;
|
||||||
|
|
||||||
/// Max depth of tree
|
/// Max depth of tree
|
||||||
uint GetMaxDepth() const;
|
uint GetMaxDepth(const Array<Node> &inNodes) const;
|
||||||
|
|
||||||
/// Number of nodes in tree
|
/// Number of nodes in tree
|
||||||
uint GetNodeCount() const;
|
uint GetNodeCount(const Array<Node> &inNodes) const;
|
||||||
|
|
||||||
/// Number of leaf nodes in tree
|
/// Number of leaf nodes in tree
|
||||||
uint GetLeafNodeCount() const;
|
uint GetLeafNodeCount(const Array<Node> &inNodes) const;
|
||||||
|
|
||||||
/// Get triangle count in tree
|
/// Get triangle count in tree
|
||||||
uint GetTriangleCountInTree() const;
|
uint GetTriangleCountInTree(const Array<Node> &inNodes) const;
|
||||||
|
|
||||||
/// Calculate min and max triangles per node
|
/// Calculate min and max triangles per node
|
||||||
void GetTriangleCountPerNode(float &outAverage, uint &outMin, uint &outMax) const;
|
void GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const;
|
||||||
|
|
||||||
/// Calculate the total cost of the tree using the surface area heuristic
|
/// Calculate the total cost of the tree using the surface area heuristic
|
||||||
float CalculateSAHCost(float inCostTraversal, float inCostLeaf) const;
|
float CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const;
|
||||||
|
|
||||||
/// Recursively get children (breadth first) to get in total inN children (or less if there are no more)
|
/// Recursively get children (breadth first) to get in total inN children (or less if there are no more)
|
||||||
void GetNChildren(uint inN, Array<const Node *> &outChildren) const;
|
void GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node *> &outChildren) const;
|
||||||
|
|
||||||
/// Bounding box
|
/// Bounding box
|
||||||
AABox mBounds;
|
AABox mBounds;
|
||||||
|
|
||||||
/// Triangles (if no child nodes)
|
/// Triangles (if no child nodes)
|
||||||
IndexedTriangleList mTriangles;
|
uint mTrianglesBegin; // Index into mTriangles
|
||||||
|
uint mNumTriangles = 0;
|
||||||
|
|
||||||
/// Child nodes (if no triangles)
|
/// Child node indices (if no triangles)
|
||||||
Node * mChild[2];
|
uint mChild[2] = { cInvalidNodeIndex, cInvalidNodeIndex };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class AABBTreeBuilder;
|
friend class AABBTreeBuilder;
|
||||||
|
|
||||||
/// Recursive helper function to calculate cost of the tree
|
/// Recursive helper function to calculate cost of the tree
|
||||||
float CalculateSAHCostInternal(float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const;
|
float CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const;
|
||||||
|
|
||||||
/// Recursive helper function to calculate min and max triangles per node
|
/// Recursive helper function to calculate min and max triangles per node
|
||||||
void GetTriangleCountPerNodeInternal(float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const;
|
void GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -100,11 +100,19 @@ public:
|
||||||
/// Recursively build tree, returns the root node of the tree
|
/// Recursively build tree, returns the root node of the tree
|
||||||
Node * Build(AABBTreeBuilderStats &outStats);
|
Node * Build(AABBTreeBuilderStats &outStats);
|
||||||
|
|
||||||
|
/// Get all nodes
|
||||||
|
const Array<Node> & GetNodes() const { return mNodes; }
|
||||||
|
|
||||||
|
/// Get all triangles
|
||||||
|
const Array<IndexedTriangle> &GetTriangles() const { return mTriangles; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Node * BuildInternal(const TriangleSplitter::Range &inTriangles);
|
uint BuildInternal(const TriangleSplitter::Range &inTriangles);
|
||||||
|
|
||||||
TriangleSplitter & mTriangleSplitter;
|
TriangleSplitter & mTriangleSplitter;
|
||||||
const uint mMaxTrianglesPerLeaf;
|
const uint mMaxTrianglesPerLeaf;
|
||||||
|
Array<Node> mNodes;
|
||||||
|
Array<IndexedTriangle> mTriangles;
|
||||||
};
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,8 @@
|
||||||
#include <Jolt/Core/ByteBuffer.h>
|
#include <Jolt/Core/ByteBuffer.h>
|
||||||
#include <Jolt/Geometry/IndexedTriangle.h>
|
#include <Jolt/Geometry/IndexedTriangle.h>
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
|
||||||
#include <deque>
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
template <class T> using Deque = std::deque<T, STLAllocator<T>>;
|
|
||||||
|
|
||||||
/// Conversion algorithm that converts an AABB tree to an optimized binary buffer
|
/// Conversion algorithm that converts an AABB tree to an optimized binary buffer
|
||||||
template <class TriangleCodec, class NodeCodec>
|
template <class TriangleCodec, class NodeCodec>
|
||||||
class AABBTreeToBuffer
|
class AABBTreeToBuffer
|
||||||
|
|
@ -37,20 +31,89 @@ public:
|
||||||
static const int TriangleHeaderSize = TriangleCodec::TriangleHeaderSize;
|
static const int TriangleHeaderSize = TriangleCodec::TriangleHeaderSize;
|
||||||
|
|
||||||
/// Convert AABB tree. Returns false if failed.
|
/// Convert AABB tree. Returns false if failed.
|
||||||
bool Convert(const VertexList &inVertices, const AABBTreeBuilder::Node *inRoot, const char *&outError)
|
bool Convert(const Array<IndexedTriangle> &inTriangles, const Array<AABBTreeBuilder::Node> &inNodes, const VertexList &inVertices, const AABBTreeBuilder::Node *inRoot, bool inStoreUserData, const char *&outError)
|
||||||
{
|
{
|
||||||
const typename NodeCodec::EncodingContext node_ctx;
|
typename NodeCodec::EncodingContext node_ctx;
|
||||||
typename TriangleCodec::EncodingContext tri_ctx(inVertices);
|
typename TriangleCodec::EncodingContext tri_ctx(inVertices);
|
||||||
|
|
||||||
// Estimate the amount of memory required
|
// Child nodes out of loop so we don't constantly realloc it
|
||||||
uint tri_count = inRoot->GetTriangleCountInTree();
|
Array<const AABBTreeBuilder::Node *> child_nodes;
|
||||||
uint node_count = inRoot->GetNodeCount();
|
child_nodes.reserve(NumChildrenPerNode);
|
||||||
uint nodes_size = node_ctx.GetPessimisticMemoryEstimate(node_count);
|
|
||||||
uint total_size = HeaderSize + TriangleHeaderSize + nodes_size + tri_ctx.GetPessimisticMemoryEstimate(tri_count);
|
|
||||||
mTree.reserve(total_size);
|
|
||||||
|
|
||||||
// Reset counters
|
// First calculate how big the tree is going to be.
|
||||||
mNodesSize = 0;
|
// Since the tree can be huge for very large meshes, we don't want
|
||||||
|
// to reallocate the buffer as it may cause out of memory situations.
|
||||||
|
// This loop mimics the construction loop below.
|
||||||
|
uint64 total_size = HeaderSize + TriangleHeaderSize;
|
||||||
|
size_t node_count = 1; // Start with root node
|
||||||
|
size_t to_process_max_size = 1; // Track size of queues so we can do a single reserve below
|
||||||
|
size_t to_process_triangles_max_size = 0;
|
||||||
|
{ // A scope to free the memory associated with to_estimate and to_estimate_triangles
|
||||||
|
Array<const AABBTreeBuilder::Node *> to_estimate;
|
||||||
|
Array<const AABBTreeBuilder::Node *> to_estimate_triangles;
|
||||||
|
to_estimate.push_back(inRoot);
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
while (!to_estimate.empty())
|
||||||
|
{
|
||||||
|
// Get the next node to process
|
||||||
|
const AABBTreeBuilder::Node *node = to_estimate.back();
|
||||||
|
to_estimate.pop_back();
|
||||||
|
|
||||||
|
// Update total size
|
||||||
|
node_ctx.PrepareNodeAllocate(node, total_size);
|
||||||
|
|
||||||
|
if (node->HasChildren())
|
||||||
|
{
|
||||||
|
// Collect the first NumChildrenPerNode sub-nodes in the tree
|
||||||
|
child_nodes.clear(); // Won't free the memory
|
||||||
|
node->GetNChildren(inNodes, NumChildrenPerNode, child_nodes);
|
||||||
|
|
||||||
|
// Increment the number of nodes we're going to store
|
||||||
|
node_count += child_nodes.size();
|
||||||
|
|
||||||
|
// Insert in reverse order so we estimate left child first when taking nodes from the back
|
||||||
|
for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx)
|
||||||
|
{
|
||||||
|
// Store triangles in separate list so we process them last
|
||||||
|
const AABBTreeBuilder::Node *child = child_nodes[idx];
|
||||||
|
if (child->HasChildren())
|
||||||
|
{
|
||||||
|
to_estimate.push_back(child);
|
||||||
|
to_process_max_size = max(to_estimate.size(), to_process_max_size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
to_estimate_triangles.push_back(child);
|
||||||
|
to_process_triangles_max_size = max(to_estimate_triangles.size(), to_process_triangles_max_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Update total size
|
||||||
|
tri_ctx.PreparePack(&inTriangles[node->mTrianglesBegin], node->mNumTriangles, inStoreUserData, total_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've got triangles to estimate, loop again with just the triangles
|
||||||
|
if (to_estimate_triangles.empty())
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
to_estimate.swap(to_estimate_triangles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize the prepare stage for the triangle context
|
||||||
|
tri_ctx.FinalizePreparePack(total_size);
|
||||||
|
|
||||||
|
// Reserve the buffer
|
||||||
|
if (size_t(total_size) != total_size)
|
||||||
|
{
|
||||||
|
outError = "AABBTreeToBuffer: Out of memory!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mTree.reserve(size_t(total_size));
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
NodeHeader *header = HeaderSize > 0? mTree.Allocate<NodeHeader>() : nullptr;
|
NodeHeader *header = HeaderSize > 0? mTree.Allocate<NodeHeader>() : nullptr;
|
||||||
|
|
@ -61,21 +124,22 @@ public:
|
||||||
const AABBTreeBuilder::Node * mNode = nullptr; // Node that this entry belongs to
|
const AABBTreeBuilder::Node * mNode = nullptr; // Node that this entry belongs to
|
||||||
Vec3 mNodeBoundsMin; // Quantized node bounds
|
Vec3 mNodeBoundsMin; // Quantized node bounds
|
||||||
Vec3 mNodeBoundsMax;
|
Vec3 mNodeBoundsMax;
|
||||||
uint mNodeStart = uint(-1); // Start of node in mTree
|
size_t mNodeStart = size_t(-1); // Start of node in mTree
|
||||||
uint mTriangleStart = uint(-1); // Start of the triangle data in mTree
|
size_t mTriangleStart = size_t(-1); // Start of the triangle data in mTree
|
||||||
|
size_t mChildNodeStart[NumChildrenPerNode]; // Start of the children of the node in mTree
|
||||||
|
size_t mChildTrianglesStart[NumChildrenPerNode]; // Start of the triangle data in mTree
|
||||||
|
size_t * mParentChildNodeStart = nullptr; // Where to store mNodeStart (to patch mChildNodeStart of my parent)
|
||||||
|
size_t * mParentTrianglesStart = nullptr; // Where to store mTriangleStart (to patch mChildTrianglesStart of my parent)
|
||||||
uint mNumChildren = 0; // Number of children
|
uint mNumChildren = 0; // Number of children
|
||||||
uint mChildNodeStart[NumChildrenPerNode]; // Start of the children of the node in mTree
|
|
||||||
uint mChildTrianglesStart[NumChildrenPerNode]; // Start of the triangle data in mTree
|
|
||||||
uint * mParentChildNodeStart = nullptr; // Where to store mNodeStart (to patch mChildNodeStart of my parent)
|
|
||||||
uint * mParentTrianglesStart = nullptr; // Where to store mTriangleStart (to patch mChildTrianglesStart of my parent)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Deque<NodeData *> to_process;
|
|
||||||
Deque<NodeData *> to_process_triangles;
|
|
||||||
Array<NodeData> node_list;
|
|
||||||
|
|
||||||
|
Array<NodeData *> to_process;
|
||||||
|
to_process.reserve(to_process_max_size);
|
||||||
|
Array<NodeData *> to_process_triangles;
|
||||||
|
to_process_triangles.reserve(to_process_triangles_max_size);
|
||||||
|
Array<NodeData> node_list;
|
||||||
node_list.reserve(node_count); // Needed to ensure that array is not reallocated, so we can keep pointers in the array
|
node_list.reserve(node_count); // Needed to ensure that array is not reallocated, so we can keep pointers in the array
|
||||||
|
|
||||||
NodeData root;
|
NodeData root;
|
||||||
root.mNode = inRoot;
|
root.mNode = inRoot;
|
||||||
root.mNodeBoundsMin = inRoot->mBounds.mMin;
|
root.mNodeBoundsMin = inRoot->mBounds.mMin;
|
||||||
|
|
@ -83,10 +147,6 @@ public:
|
||||||
node_list.push_back(root);
|
node_list.push_back(root);
|
||||||
to_process.push_back(&node_list.back());
|
to_process.push_back(&node_list.back());
|
||||||
|
|
||||||
// Child nodes out of loop so we don't constantly realloc it
|
|
||||||
Array<const AABBTreeBuilder::Node *> child_nodes;
|
|
||||||
child_nodes.reserve(NumChildrenPerNode);
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
while (!to_process.empty())
|
while (!to_process.empty())
|
||||||
|
|
@ -100,7 +160,7 @@ public:
|
||||||
|
|
||||||
// Collect the first NumChildrenPerNode sub-nodes in the tree
|
// Collect the first NumChildrenPerNode sub-nodes in the tree
|
||||||
child_nodes.clear(); // Won't free the memory
|
child_nodes.clear(); // Won't free the memory
|
||||||
node_data->mNode->GetNChildren(NumChildrenPerNode, child_nodes);
|
node_data->mNode->GetNChildren(inNodes, NumChildrenPerNode, child_nodes);
|
||||||
node_data->mNumChildren = (uint)child_nodes.size();
|
node_data->mNumChildren = (uint)child_nodes.size();
|
||||||
|
|
||||||
// Fill in default child bounds
|
// Fill in default child bounds
|
||||||
|
|
@ -118,47 +178,41 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a new node
|
// Start a new node
|
||||||
uint old_size = (uint)mTree.size();
|
|
||||||
node_data->mNodeStart = node_ctx.NodeAllocate(node_data->mNode, node_data->mNodeBoundsMin, node_data->mNodeBoundsMax, child_nodes, child_bounds_min, child_bounds_max, mTree, outError);
|
node_data->mNodeStart = node_ctx.NodeAllocate(node_data->mNode, node_data->mNodeBoundsMin, node_data->mNodeBoundsMax, child_nodes, child_bounds_min, child_bounds_max, mTree, outError);
|
||||||
if (node_data->mNodeStart == uint(-1))
|
if (node_data->mNodeStart == size_t(-1))
|
||||||
return false;
|
return false;
|
||||||
mNodesSize += (uint)mTree.size() - old_size;
|
|
||||||
|
|
||||||
if (node_data->mNode->HasChildren())
|
if (node_data->mNode->HasChildren())
|
||||||
{
|
{
|
||||||
// Insert in reverse order so we process left child first when taking nodes from the back
|
// Insert in reverse order so we process left child first when taking nodes from the back
|
||||||
for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx)
|
for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx)
|
||||||
{
|
{
|
||||||
|
const AABBTreeBuilder::Node *child_node = child_nodes[idx];
|
||||||
|
|
||||||
// Due to quantization box could have become bigger, not smaller
|
// Due to quantization box could have become bigger, not smaller
|
||||||
JPH_ASSERT(AABox(child_bounds_min[idx], child_bounds_max[idx]).Contains(child_nodes[idx]->mBounds), "AABBTreeToBuffer: Bounding box became smaller!");
|
JPH_ASSERT(AABox(child_bounds_min[idx], child_bounds_max[idx]).Contains(child_node->mBounds), "AABBTreeToBuffer: Bounding box became smaller!");
|
||||||
|
|
||||||
// Add child to list of nodes to be processed
|
// Add child to list of nodes to be processed
|
||||||
NodeData child;
|
NodeData child;
|
||||||
child.mNode = child_nodes[idx];
|
child.mNode = child_node;
|
||||||
child.mNodeBoundsMin = child_bounds_min[idx];
|
child.mNodeBoundsMin = child_bounds_min[idx];
|
||||||
child.mNodeBoundsMax = child_bounds_max[idx];
|
child.mNodeBoundsMax = child_bounds_max[idx];
|
||||||
child.mParentChildNodeStart = &node_data->mChildNodeStart[idx];
|
child.mParentChildNodeStart = &node_data->mChildNodeStart[idx];
|
||||||
child.mParentTrianglesStart = &node_data->mChildTrianglesStart[idx];
|
child.mParentTrianglesStart = &node_data->mChildTrianglesStart[idx];
|
||||||
NodeData *old = &node_list[0];
|
|
||||||
node_list.push_back(child);
|
node_list.push_back(child);
|
||||||
if (old != &node_list[0])
|
|
||||||
{
|
|
||||||
outError = "Internal Error: Array reallocated, memory corruption!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store triangles in separate list so we process them last
|
// Store triangles in separate list so we process them last
|
||||||
if (node_list.back().mNode->HasChildren())
|
if (child_node->HasChildren())
|
||||||
to_process.push_back(&node_list.back());
|
to_process.push_back(&node_list.back());
|
||||||
else
|
else
|
||||||
to_process_triangles.push_back(&node_list.back());
|
to_process_triangles.push_back(&node_list.back());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Add triangles
|
// Add triangles
|
||||||
node_data->mTriangleStart = tri_ctx.Pack(node_data->mNode->mTriangles, mTree, outError);
|
node_data->mTriangleStart = tri_ctx.Pack(&inTriangles[node_data->mNode->mTrianglesBegin], node_data->mNode->mNumTriangles, inStoreUserData, mTree, outError);
|
||||||
if (node_data->mTriangleStart == uint(-1))
|
if (node_data->mTriangleStart == size_t(-1))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,35 +230,33 @@ public:
|
||||||
else
|
else
|
||||||
to_process.swap(to_process_triangles);
|
to_process.swap(to_process_triangles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert that our reservation was correct (we don't know if we swapped the arrays or not)
|
||||||
|
JPH_ASSERT(to_process_max_size == to_process.capacity() || to_process_triangles_max_size == to_process.capacity());
|
||||||
|
JPH_ASSERT(to_process_max_size == to_process_triangles.capacity() || to_process_triangles_max_size == to_process_triangles.capacity());
|
||||||
|
|
||||||
// Finalize all nodes
|
// Finalize all nodes
|
||||||
for (NodeData &n : node_list)
|
for (NodeData &n : node_list)
|
||||||
if (!node_ctx.NodeFinalize(n.mNode, n.mNodeStart, n.mNumChildren, n.mChildNodeStart, n.mChildTrianglesStart, mTree, outError))
|
if (!node_ctx.NodeFinalize(n.mNode, n.mNodeStart, n.mNumChildren, n.mChildNodeStart, n.mChildTrianglesStart, mTree, outError))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Finalize the triangles
|
// Finalize the triangles
|
||||||
tri_ctx.Finalize(inVertices, triangle_header, mTree);
|
tri_ctx.Finalize(inVertices, triangle_header, mTree);
|
||||||
|
|
||||||
// Validate that we reserved enough memory
|
// Validate that our reservations were correct
|
||||||
if (nodes_size < mNodesSize)
|
if (node_count != node_list.size())
|
||||||
{
|
{
|
||||||
outError = "Internal Error: Not enough memory reserved for nodes!";
|
outError = "Internal Error: Node memory estimate was incorrect, memory corruption!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (total_size < (uint)mTree.size())
|
if (total_size != mTree.size())
|
||||||
{
|
{
|
||||||
outError = "Internal Error: Not enough memory reserved for triangles!";
|
outError = "Internal Error: Tree memory estimate was incorrect, memory corruption!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize the nodes
|
// Finalize the nodes
|
||||||
if (!node_ctx.Finalize(header, inRoot, node_list[0].mNodeStart, node_list[0].mTriangleStart, outError))
|
return node_ctx.Finalize(header, inRoot, node_list[0].mNodeStart, node_list[0].mTriangleStart, outError);
|
||||||
return false;
|
|
||||||
|
|
||||||
// Shrink the tree, this will invalidate the header and triangle_header variables
|
|
||||||
mTree.shrink_to_fit();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get resulting data
|
/// Get resulting data
|
||||||
|
|
@ -236,10 +288,9 @@ public:
|
||||||
{
|
{
|
||||||
return mTree.Get<void>(HeaderSize + TriangleHeaderSize);
|
return mTree.Get<void>(HeaderSize + TriangleHeaderSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ByteBuffer mTree; ///< Resulting tree structure
|
ByteBuffer mTree; ///< Resulting tree structure
|
||||||
uint mNodesSize; ///< Size in bytes of the nodes in the buffer
|
|
||||||
};
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
template <int Alignment>
|
|
||||||
class NodeCodecQuadTreeHalfFloat
|
class NodeCodecQuadTreeHalfFloat
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -23,11 +22,13 @@ public:
|
||||||
Float3 mRootBoundsMin;
|
Float3 mRootBoundsMin;
|
||||||
Float3 mRootBoundsMax;
|
Float3 mRootBoundsMax;
|
||||||
uint32 mRootProperties;
|
uint32 mRootProperties;
|
||||||
|
uint8 mBlockIDBits; ///< Number of bits to address a triangle block
|
||||||
|
uint8 mPadding[3] = { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)
|
/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)
|
||||||
static constexpr int HeaderSize = sizeof(Header);
|
static constexpr int HeaderSize = sizeof(Header);
|
||||||
|
|
||||||
/// Stack size to use during DecodingContext::sWalkTree
|
/// Stack size to use during DecodingContext::sWalkTree
|
||||||
static constexpr int StackSize = 128;
|
static constexpr int StackSize = 128;
|
||||||
|
|
||||||
|
|
@ -54,33 +55,37 @@ public:
|
||||||
HalfFloat mBoundsMaxZ[4];
|
HalfFloat mBoundsMaxZ[4];
|
||||||
uint32 mNodeProperties[4]; ///< 4 child node properties
|
uint32 mNodeProperties[4]; ///< 4 child node properties
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Node) == 64, "Node should be 64 bytes");
|
static_assert(sizeof(Node) == 64, "Node should be 64 bytes");
|
||||||
|
|
||||||
/// This class encodes and compresses quad tree nodes
|
/// This class encodes and compresses quad tree nodes
|
||||||
class EncodingContext
|
class EncodingContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Get an upper bound on the amount of bytes needed for a node tree with inNodeCount nodes
|
/// Mimics the size a call to NodeAllocate() would add to the buffer
|
||||||
uint GetPessimisticMemoryEstimate(uint inNodeCount) const
|
void PrepareNodeAllocate(const AABBTreeBuilder::Node *inNode, uint64 &ioBufferSize) const
|
||||||
{
|
|
||||||
return inNodeCount * (sizeof(Node) + Alignment - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocate a new node for inNode.
|
|
||||||
/// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed
|
|
||||||
/// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax
|
|
||||||
/// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node
|
|
||||||
/// Returns uint(-1) on error and reports the error in outError
|
|
||||||
uint NodeAllocate(const AABBTreeBuilder::Node *inNode, Vec3Arg inNodeBoundsMin, Vec3Arg inNodeBoundsMax, Array<const AABBTreeBuilder::Node *> &ioChildren, Vec3 outChildBoundsMin[NumChildrenPerNode], Vec3 outChildBoundsMax[NumChildrenPerNode], ByteBuffer &ioBuffer, const char *&outError) const
|
|
||||||
{
|
{
|
||||||
// We don't emit nodes for leafs
|
// We don't emit nodes for leafs
|
||||||
if (!inNode->HasChildren())
|
if (!inNode->HasChildren())
|
||||||
return (uint)ioBuffer.size();
|
return;
|
||||||
|
|
||||||
// Align the buffer
|
// Add size of node
|
||||||
ioBuffer.Align(Alignment);
|
ioBufferSize += sizeof(Node);
|
||||||
uint node_start = (uint)ioBuffer.size();
|
}
|
||||||
|
|
||||||
|
/// Allocate a new node for inNode.
|
||||||
|
/// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed
|
||||||
|
/// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax
|
||||||
|
/// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node
|
||||||
|
/// Returns size_t(-1) on error and reports the error in outError
|
||||||
|
size_t NodeAllocate(const AABBTreeBuilder::Node *inNode, Vec3Arg inNodeBoundsMin, Vec3Arg inNodeBoundsMax, Array<const AABBTreeBuilder::Node *> &ioChildren, Vec3 outChildBoundsMin[NumChildrenPerNode], Vec3 outChildBoundsMax[NumChildrenPerNode], ByteBuffer &ioBuffer, const char *&outError) const
|
||||||
|
{
|
||||||
|
// We don't emit nodes for leafs
|
||||||
|
if (!inNode->HasChildren())
|
||||||
|
return ioBuffer.size();
|
||||||
|
|
||||||
|
// Remember the start of the node
|
||||||
|
size_t node_start = ioBuffer.size();
|
||||||
|
|
||||||
// Fill in bounds
|
// Fill in bounds
|
||||||
Node *node = ioBuffer.Allocate<Node>();
|
Node *node = ioBuffer.Allocate<Node>();
|
||||||
|
|
@ -104,13 +109,13 @@ public:
|
||||||
if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
|
if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
|
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
|
||||||
return uint(-1);
|
return size_t(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Make this an invalid triangle node
|
// Make this an invalid triangle node
|
||||||
node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT;
|
node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT;
|
||||||
|
|
||||||
// Make bounding box invalid
|
// Make bounding box invalid
|
||||||
node->mBoundsMinX[i] = HALF_FLT_MAX;
|
node->mBoundsMinX[i] = HALF_FLT_MAX;
|
||||||
|
|
@ -133,7 +138,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Once all nodes have been added, this call finalizes all nodes by patching in the offsets of the child nodes (that were added after the node itself was added)
|
/// Once all nodes have been added, this call finalizes all nodes by patching in the offsets of the child nodes (that were added after the node itself was added)
|
||||||
bool NodeFinalize(const AABBTreeBuilder::Node *inNode, uint inNodeStart, uint inNumChildren, const uint *inChildrenNodeStart, const uint *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError) const
|
bool NodeFinalize(const AABBTreeBuilder::Node *inNode, size_t inNodeStart, uint inNumChildren, const size_t *inChildrenNodeStart, const size_t *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError)
|
||||||
{
|
{
|
||||||
if (!inNode->HasChildren())
|
if (!inNode->HasChildren())
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -141,46 +146,64 @@ public:
|
||||||
Node *node = ioBuffer.Get<Node>(inNodeStart);
|
Node *node = ioBuffer.Get<Node>(inNodeStart);
|
||||||
for (uint i = 0; i < inNumChildren; ++i)
|
for (uint i = 0; i < inNumChildren; ++i)
|
||||||
{
|
{
|
||||||
// If there are triangles, use the triangle offset otherwise use the node offset
|
size_t offset;
|
||||||
uint offset = node->mNodeProperties[i] != 0? inChildrenTrianglesStart[i] : inChildrenNodeStart[i];
|
if (node->mNodeProperties[i] != 0)
|
||||||
|
{
|
||||||
|
// This is a triangle block
|
||||||
|
offset = inChildrenTrianglesStart[i];
|
||||||
|
|
||||||
|
// Store highest block with triangles so we can count the number of bits we need
|
||||||
|
mHighestTriangleBlock = max(mHighestTriangleBlock, offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is a node block
|
||||||
|
offset = inChildrenNodeStart[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store offset of next node / triangles
|
||||||
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
|
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set";
|
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
|
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
|
||||||
if (offset & ~OFFSET_MASK)
|
if (offset > OFFSET_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
|
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
node->mNodeProperties[i] |= uint32(offset);
|
||||||
// Store offset of next node / triangles
|
|
||||||
node->mNodeProperties[i] |= offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Once all nodes have been finalized, this will finalize the header of the nodes
|
/// Once all nodes have been finalized, this will finalize the header of the nodes
|
||||||
bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, uint inRootNodeStart, uint inRootTrianglesStart, const char *&outError) const
|
bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, size_t inRootNodeStart, size_t inRootTrianglesStart, const char *&outError) const
|
||||||
{
|
{
|
||||||
uint offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart;
|
// Check if we can address the root node
|
||||||
|
size_t offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart;
|
||||||
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
|
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-signifiant bits set";
|
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
|
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
|
||||||
if (offset & ~OFFSET_MASK)
|
if (offset > OFFSET_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
|
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the root has triangles, we need to take that offset instead since the mHighestTriangleBlock will be zero
|
||||||
|
size_t highest_triangle_block = inRootTrianglesStart != size_t(-1)? inRootTrianglesStart : mHighestTriangleBlock;
|
||||||
|
highest_triangle_block >>= OFFSET_NON_SIGNIFICANT_BITS;
|
||||||
|
|
||||||
inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin);
|
inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin);
|
||||||
inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax);
|
inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax);
|
||||||
outHeader->mRootProperties = offset + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT);
|
outHeader->mRootProperties = uint32(offset) + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT);
|
||||||
|
outHeader->mBlockIDBits = uint8(32 - CountLeadingZeros(uint32(highest_triangle_block)));
|
||||||
if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
|
if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
|
||||||
{
|
{
|
||||||
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
|
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
|
||||||
|
|
@ -188,7 +211,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t mHighestTriangleBlock = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This class decodes and decompresses quad tree nodes
|
/// This class decodes and decompresses quad tree nodes
|
||||||
|
|
@ -196,9 +222,9 @@ public:
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Get the amount of bits needed to store an ID to a triangle block
|
/// Get the amount of bits needed to store an ID to a triangle block
|
||||||
inline static uint sTriangleBlockIDBits(const ByteBuffer &inTree)
|
inline static uint sTriangleBlockIDBits(const Header *inHeader)
|
||||||
{
|
{
|
||||||
return 32 - CountLeadingZeros((uint32)inTree.size()) - OFFSET_NON_SIGNIFICANT_BITS;
|
return inHeader->mBlockIDBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a triangle block ID to the start of the triangle buffer
|
/// Convert a triangle block ID to the start of the triangle buffer
|
||||||
|
|
@ -228,10 +254,19 @@ public:
|
||||||
const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));
|
const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));
|
||||||
|
|
||||||
// Unpack bounds
|
// Unpack bounds
|
||||||
|
#ifdef JPH_CPU_BIG_ENDIAN
|
||||||
|
Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0));
|
||||||
|
Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0));
|
||||||
|
Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0));
|
||||||
|
|
||||||
|
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0));
|
||||||
|
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0));
|
||||||
|
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0));
|
||||||
|
#else
|
||||||
UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));
|
UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));
|
||||||
Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);
|
Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);
|
||||||
Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
||||||
|
|
||||||
UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinZ[0]));
|
UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinZ[0]));
|
||||||
Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx);
|
Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx);
|
||||||
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
||||||
|
|
@ -239,6 +274,7 @@ public:
|
||||||
UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));
|
UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));
|
||||||
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);
|
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);
|
||||||
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
|
||||||
|
#endif
|
||||||
|
|
||||||
// Load properties for 4 children
|
// Load properties for 4 children
|
||||||
UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);
|
UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);
|
||||||
|
|
@ -252,7 +288,7 @@ public:
|
||||||
mTop += num_results;
|
mTop += num_results;
|
||||||
}
|
}
|
||||||
else if (tri_count != TRIANGLE_COUNT_MASK) // TRIANGLE_COUNT_MASK indicates a padding node, normally we shouldn't visit these nodes but when querying with a big enough box you could touch HALF_FLT_MAX (about 65K)
|
else if (tri_count != TRIANGLE_COUNT_MASK) // TRIANGLE_COUNT_MASK indicates a padding node, normally we shouldn't visit these nodes but when querying with a big enough box you could touch HALF_FLT_MAX (about 65K)
|
||||||
{
|
{
|
||||||
// Node contains triangles, do individual tests
|
// Node contains triangles, do individual tests
|
||||||
uint32 triangle_block_id = node_properties & OFFSET_MASK;
|
uint32 triangle_block_id = node_properties & OFFSET_MASK;
|
||||||
const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);
|
const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);
|
||||||
|
|
@ -265,7 +301,7 @@ public:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Fetch next node until we find one that the visitor wants to see
|
// Fetch next node until we find one that the visitor wants to see
|
||||||
do
|
do
|
||||||
--mTop;
|
--mTop;
|
||||||
while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop));
|
while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// TriangleBlockHeader,
|
/// TriangleBlockHeader,
|
||||||
/// TriangleBlock (4 triangles and their flags in 16 bytes),
|
/// TriangleBlock (4 triangles and their flags in 16 bytes),
|
||||||
/// TriangleBlock...
|
/// TriangleBlock...
|
||||||
|
/// [Optional] UserData (4 bytes per triangle)
|
||||||
///
|
///
|
||||||
/// Vertices are stored:
|
/// Vertices are stored:
|
||||||
///
|
///
|
||||||
|
|
@ -34,7 +35,7 @@ public:
|
||||||
static constexpr int TriangleHeaderSize = sizeof(TriangleHeader);
|
static constexpr int TriangleHeaderSize = sizeof(TriangleHeader);
|
||||||
|
|
||||||
/// If this codec could return a different offset than the current buffer size when calling Pack()
|
/// If this codec could return a different offset than the current buffer size when calling Pack()
|
||||||
static constexpr bool ChangesOffsetOnPack = false;
|
static constexpr bool ChangesOffsetOnPack = false;
|
||||||
|
|
||||||
/// Amount of bits per component
|
/// Amount of bits per component
|
||||||
enum EComponentData : uint32
|
enum EComponentData : uint32
|
||||||
|
|
@ -67,7 +68,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(VertexData) == 8, "Compiler added padding");
|
static_assert(sizeof(VertexData) == 8, "Compiler added padding");
|
||||||
|
|
||||||
/// A block of 4 triangles
|
/// A block of 4 triangles
|
||||||
struct TriangleBlock
|
struct TriangleBlock
|
||||||
{
|
{
|
||||||
|
|
@ -77,59 +78,168 @@ public:
|
||||||
|
|
||||||
static_assert(sizeof(TriangleBlock) == 16, "Compiler added padding");
|
static_assert(sizeof(TriangleBlock) == 16, "Compiler added padding");
|
||||||
|
|
||||||
|
enum ETriangleBlockHeaderFlags : uint32
|
||||||
|
{
|
||||||
|
OFFSET_TO_VERTICES_BITS = 29, ///< Offset from current block to start of vertices in bytes
|
||||||
|
OFFSET_TO_VERTICES_MASK = (1 << OFFSET_TO_VERTICES_BITS) - 1,
|
||||||
|
OFFSET_NON_SIGNIFICANT_BITS = 2, ///< The offset from the current block to the start of the vertices must be a multiple of 4 bytes
|
||||||
|
OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1,
|
||||||
|
OFFSET_TO_USERDATA_BITS = 3, ///< When user data is stored, this is the number of blocks to skip to get to the user data (0 = no user data)
|
||||||
|
OFFSET_TO_USERDATA_MASK = (1 << OFFSET_TO_USERDATA_BITS) - 1,
|
||||||
|
};
|
||||||
|
|
||||||
/// A triangle header, will be followed by one or more TriangleBlocks
|
/// A triangle header, will be followed by one or more TriangleBlocks
|
||||||
struct TriangleBlockHeader
|
struct TriangleBlockHeader
|
||||||
{
|
{
|
||||||
const VertexData * GetVertexData() const { return reinterpret_cast<const VertexData *>(reinterpret_cast<const uint8 *>(this) + mOffsetToVertices); }
|
const VertexData * GetVertexData() const { return reinterpret_cast<const VertexData *>(reinterpret_cast<const uint8 *>(this) + ((mFlags & OFFSET_TO_VERTICES_MASK) << OFFSET_NON_SIGNIFICANT_BITS)); }
|
||||||
const TriangleBlock * GetTriangleBlock() const { return reinterpret_cast<const TriangleBlock *>(reinterpret_cast<const uint8 *>(this) + sizeof(TriangleBlockHeader)); }
|
const TriangleBlock * GetTriangleBlock() const { return reinterpret_cast<const TriangleBlock *>(reinterpret_cast<const uint8 *>(this) + sizeof(TriangleBlockHeader)); }
|
||||||
|
const uint32 * GetUserData() const { uint32 offset = mFlags >> OFFSET_TO_VERTICES_BITS; return offset == 0? nullptr : reinterpret_cast<const uint32 *>(GetTriangleBlock() + offset); }
|
||||||
|
|
||||||
uint32 mOffsetToVertices; ///< Offset from current block to start of vertices in bytes
|
uint32 mFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(TriangleBlockHeader) == 4, "Compiler added padding");
|
static_assert(sizeof(TriangleBlockHeader) == 4, "Compiler added padding");
|
||||||
|
|
||||||
|
/// This class is used to validate that the triangle data will not be degenerate after compression
|
||||||
|
class ValidationContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
ValidationContext(const IndexedTriangleList &inTriangles, const VertexList &inVertices) :
|
||||||
|
mVertices(inVertices)
|
||||||
|
{
|
||||||
|
// Only used the referenced triangles, just like EncodingContext::Finalize does
|
||||||
|
for (const IndexedTriangle &i : inTriangles)
|
||||||
|
for (uint32 idx : i.mIdx)
|
||||||
|
mBounds.Encapsulate(Vec3(inVertices[idx]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if a triangle will be degenerate after quantization
|
||||||
|
bool IsDegenerate(const IndexedTriangle &inTriangle) const
|
||||||
|
{
|
||||||
|
// Quantize the triangle in the same way as EncodingContext::Finalize does
|
||||||
|
UVec4 quantized_vertex[3];
|
||||||
|
Vec3 compress_scale = Vec3::sReplicate(COMPONENT_MASK) / Vec3::sMax(mBounds.GetSize(), Vec3::sReplicate(1.0e-20f));
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
quantized_vertex[i] = ((Vec3(mVertices[inTriangle.mIdx[i]]) - mBounds.mMin) * compress_scale + Vec3::sReplicate(0.5f)).ToInt();
|
||||||
|
return quantized_vertex[0] == quantized_vertex[1] || quantized_vertex[1] == quantized_vertex[2] || quantized_vertex[0] == quantized_vertex[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const VertexList & mVertices;
|
||||||
|
AABox mBounds;
|
||||||
|
};
|
||||||
|
|
||||||
/// This class is used to encode and compress triangle data into a byte buffer
|
/// This class is used to encode and compress triangle data into a byte buffer
|
||||||
class EncodingContext
|
class EncodingContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// Indicates a vertex hasn't been seen yet in the triangle list
|
||||||
|
static constexpr uint32 cNotFound = 0xffffffff;
|
||||||
|
|
||||||
/// Construct the encoding context
|
/// Construct the encoding context
|
||||||
explicit EncodingContext(const VertexList &inVertices) :
|
explicit EncodingContext(const VertexList &inVertices) :
|
||||||
mVertexMap(inVertices.size(), 0xffffffff) // Fill vertex map with 'not found'
|
mVertexMap(inVertices.size(), cNotFound)
|
||||||
{
|
{
|
||||||
// Reserve for worst case to avoid allocating in the inner loop
|
|
||||||
mVertices.reserve(inVertices.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an upper bound on the amount of bytes needed to store inTriangleCount triangles
|
/// Mimics the size a call to Pack() would add to the buffer
|
||||||
uint GetPessimisticMemoryEstimate(uint inTriangleCount) const
|
void PreparePack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, uint64 &ioBufferSize)
|
||||||
{
|
{
|
||||||
// Worst case each triangle is alone in a block, none of the vertices are shared and we need to add 3 bytes to align the vertices
|
// Add triangle block header
|
||||||
return inTriangleCount * (sizeof(TriangleBlockHeader) + sizeof(TriangleBlock) + 3 * sizeof(VertexData)) + 3;
|
ioBufferSize += sizeof(TriangleBlockHeader);
|
||||||
|
|
||||||
|
// Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared)
|
||||||
|
uint start_vertex = Clamp((int)mVertexCount - 256 + (int)inNumTriangles * 3, 0, (int)mVertexCount);
|
||||||
|
|
||||||
|
// Pack vertices
|
||||||
|
uint padded_triangle_count = AlignUp(inNumTriangles, 4);
|
||||||
|
for (uint t = 0; t < padded_triangle_count; t += 4)
|
||||||
|
{
|
||||||
|
// Add triangle block header
|
||||||
|
ioBufferSize += sizeof(TriangleBlock);
|
||||||
|
|
||||||
|
for (uint vertex_nr = 0; vertex_nr < 3; ++vertex_nr)
|
||||||
|
for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx)
|
||||||
|
{
|
||||||
|
// Fetch vertex index. Create degenerate triangles for padding triangles.
|
||||||
|
bool triangle_available = t + block_tri_idx < inNumTriangles;
|
||||||
|
uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0];
|
||||||
|
|
||||||
|
// Check if we've seen this vertex before and if it is in the range that we can encode
|
||||||
|
uint32 &vertex_index = mVertexMap[src_vertex_index];
|
||||||
|
if (vertex_index == cNotFound || vertex_index < start_vertex)
|
||||||
|
{
|
||||||
|
// Add vertex
|
||||||
|
vertex_index = mVertexCount;
|
||||||
|
mVertexCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user data
|
||||||
|
if (inStoreUserData)
|
||||||
|
ioBufferSize += inNumTriangles * sizeof(uint32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mimics the size the Finalize() call would add to ioBufferSize
|
||||||
|
void FinalizePreparePack(uint64 &ioBufferSize)
|
||||||
|
{
|
||||||
|
// Remember where the vertices are going to start in the output buffer
|
||||||
|
JPH_ASSERT(IsAligned(ioBufferSize, 4));
|
||||||
|
mVerticesStartIdx = size_t(ioBufferSize);
|
||||||
|
|
||||||
|
// Add vertices to buffer
|
||||||
|
ioBufferSize += uint64(mVertexCount) * sizeof(VertexData);
|
||||||
|
|
||||||
|
// Reserve the amount of memory we need for the vertices
|
||||||
|
mVertices.reserve(mVertexCount);
|
||||||
|
|
||||||
|
// Set vertex map back to 'not found'
|
||||||
|
for (uint32 &v : mVertexMap)
|
||||||
|
v = cNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pack the triangles in inContainer to ioBuffer. This stores the mMaterialIndex of a triangle in the 8 bit flags.
|
/// Pack the triangles in inContainer to ioBuffer. This stores the mMaterialIndex of a triangle in the 8 bit flags.
|
||||||
/// Returns uint(-1) on error.
|
/// Returns size_t(-1) on error.
|
||||||
uint Pack(const IndexedTriangleList &inTriangles, ByteBuffer &ioBuffer, const char *&outError)
|
size_t Pack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, ByteBuffer &ioBuffer, const char *&outError)
|
||||||
{
|
{
|
||||||
// Determine position of triangles start
|
JPH_ASSERT(inNumTriangles > 0);
|
||||||
uint offset = (uint)ioBuffer.size();
|
|
||||||
|
|
||||||
// Update stats
|
// Determine position of triangles start
|
||||||
uint tri_count = (uint)inTriangles.size();
|
size_t triangle_block_start = ioBuffer.size();
|
||||||
mNumTriangles += tri_count;
|
|
||||||
|
|
||||||
// Allocate triangle block header
|
// Allocate triangle block header
|
||||||
TriangleBlockHeader *header = ioBuffer.Allocate<TriangleBlockHeader>();
|
TriangleBlockHeader *header = ioBuffer.Allocate<TriangleBlockHeader>();
|
||||||
|
|
||||||
// Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared)
|
// Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared)
|
||||||
uint start_vertex = Clamp((int)mVertices.size() - 256 + (int)tri_count * 3, 0, (int)mVertices.size());
|
uint start_vertex = Clamp((int)mVertices.size() - 256 + (int)inNumTriangles * 3, 0, (int)mVertices.size());
|
||||||
|
|
||||||
// Store the start vertex offset, this will later be patched to give the delta offset relative to the triangle block
|
// Store the start vertex offset relative to TriangleBlockHeader
|
||||||
mOffsetsToPatch.push_back(uint((uint8 *)&header->mOffsetToVertices - &ioBuffer[0]));
|
size_t offset_to_vertices = mVerticesStartIdx - triangle_block_start + size_t(start_vertex) * sizeof(VertexData);
|
||||||
header->mOffsetToVertices = start_vertex * sizeof(VertexData);
|
if (offset_to_vertices & OFFSET_NON_SIGNIFICANT_MASK)
|
||||||
|
{
|
||||||
|
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Internal Error: Offset has non-significant bits set";
|
||||||
|
return size_t(-1);
|
||||||
|
}
|
||||||
|
offset_to_vertices >>= OFFSET_NON_SIGNIFICANT_BITS;
|
||||||
|
if (offset_to_vertices > OFFSET_TO_VERTICES_MASK)
|
||||||
|
{
|
||||||
|
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset to vertices doesn't fit. Too much data.";
|
||||||
|
return size_t(-1);
|
||||||
|
}
|
||||||
|
header->mFlags = uint32(offset_to_vertices);
|
||||||
|
|
||||||
|
// When we store user data we need to store the offset to the user data in TriangleBlocks
|
||||||
|
uint padded_triangle_count = AlignUp(inNumTriangles, 4);
|
||||||
|
if (inStoreUserData)
|
||||||
|
{
|
||||||
|
uint32 num_blocks = padded_triangle_count >> 2;
|
||||||
|
JPH_ASSERT(num_blocks <= OFFSET_TO_USERDATA_MASK);
|
||||||
|
header->mFlags |= num_blocks << OFFSET_TO_VERTICES_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
// Pack vertices
|
// Pack vertices
|
||||||
uint padded_triangle_count = AlignUp(tri_count, 4);
|
|
||||||
for (uint t = 0; t < padded_triangle_count; t += 4)
|
for (uint t = 0; t < padded_triangle_count; t += 4)
|
||||||
{
|
{
|
||||||
TriangleBlock *block = ioBuffer.Allocate<TriangleBlock>();
|
TriangleBlock *block = ioBuffer.Allocate<TriangleBlock>();
|
||||||
|
|
@ -137,12 +247,12 @@ public:
|
||||||
for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx)
|
for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx)
|
||||||
{
|
{
|
||||||
// Fetch vertex index. Create degenerate triangles for padding triangles.
|
// Fetch vertex index. Create degenerate triangles for padding triangles.
|
||||||
bool triangle_available = t + block_tri_idx < tri_count;
|
bool triangle_available = t + block_tri_idx < inNumTriangles;
|
||||||
uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[tri_count - 1].mIdx[0];
|
uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0];
|
||||||
|
|
||||||
// Check if we've seen this vertex before and if it is in the range that we can encode
|
// Check if we've seen this vertex before and if it is in the range that we can encode
|
||||||
uint32 &vertex_index = mVertexMap[src_vertex_index];
|
uint32 &vertex_index = mVertexMap[src_vertex_index];
|
||||||
if (vertex_index == 0xffffffff || vertex_index < start_vertex)
|
if (vertex_index == cNotFound || vertex_index < start_vertex)
|
||||||
{
|
{
|
||||||
// Add vertex
|
// Add vertex
|
||||||
vertex_index = (uint32)mVertices.size();
|
vertex_index = (uint32)mVertices.size();
|
||||||
|
|
@ -154,7 +264,7 @@ public:
|
||||||
if (vertex_offset > 0xff)
|
if (vertex_offset > 0xff)
|
||||||
{
|
{
|
||||||
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset doesn't fit in 8 bit";
|
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset doesn't fit in 8 bit";
|
||||||
return uint(-1);
|
return size_t(-1);
|
||||||
}
|
}
|
||||||
block->mIndices[vertex_nr][block_tri_idx] = (uint8)vertex_offset;
|
block->mIndices[vertex_nr][block_tri_idx] = (uint8)vertex_offset;
|
||||||
|
|
||||||
|
|
@ -163,29 +273,34 @@ public:
|
||||||
if (flags > 0xff)
|
if (flags > 0xff)
|
||||||
{
|
{
|
||||||
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Material index doesn't fit in 8 bit";
|
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Material index doesn't fit in 8 bit";
|
||||||
return uint(-1);
|
return size_t(-1);
|
||||||
}
|
}
|
||||||
block->mFlags[block_tri_idx] = (uint8)flags;
|
block->mFlags[block_tri_idx] = (uint8)flags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return offset;
|
// Store user data
|
||||||
|
if (inStoreUserData)
|
||||||
|
{
|
||||||
|
uint32 *user_data = ioBuffer.Allocate<uint32>(inNumTriangles);
|
||||||
|
for (uint t = 0; t < inNumTriangles; ++t)
|
||||||
|
user_data[t] = inTriangles[t].mUserData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return triangle_block_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After all triangles have been packed, this finalizes the header and triangle buffer
|
/// After all triangles have been packed, this finalizes the header and triangle buffer
|
||||||
void Finalize(const VertexList &inVertices, TriangleHeader *ioHeader, ByteBuffer &ioBuffer) const
|
void Finalize(const VertexList &inVertices, TriangleHeader *ioHeader, ByteBuffer &ioBuffer) const
|
||||||
{
|
{
|
||||||
|
// Assert that our reservations were correct
|
||||||
|
JPH_ASSERT(mVertices.size() == mVertexCount);
|
||||||
|
JPH_ASSERT(ioBuffer.size() == mVerticesStartIdx);
|
||||||
|
|
||||||
// Check if anything to do
|
// Check if anything to do
|
||||||
if (mVertices.empty())
|
if (mVertices.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Align buffer to 4 bytes
|
|
||||||
uint vertices_idx = (uint)ioBuffer.Align(4);
|
|
||||||
|
|
||||||
// Patch the offsets
|
|
||||||
for (uint o : mOffsetsToPatch)
|
|
||||||
*ioBuffer.Get<uint32>(o) += vertices_idx - o;
|
|
||||||
|
|
||||||
// Calculate bounding box
|
// Calculate bounding box
|
||||||
AABox bounds;
|
AABox bounds;
|
||||||
for (uint32 v : mVertices)
|
for (uint32 v : mVertices)
|
||||||
|
|
@ -213,17 +328,17 @@ public:
|
||||||
private:
|
private:
|
||||||
using VertexMap = Array<uint32>;
|
using VertexMap = Array<uint32>;
|
||||||
|
|
||||||
uint mNumTriangles = 0;
|
uint32 mVertexCount = 0; ///< Number of vertices calculated during PreparePack
|
||||||
Array<uint32> mVertices; ///< Output vertices as an index into the original vertex list (inVertices), sorted according to occurrence
|
size_t mVerticesStartIdx = 0; ///< Start of the vertices in the output buffer, calculated during PreparePack
|
||||||
VertexMap mVertexMap; ///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices)
|
Array<uint32> mVertices; ///< Output vertices as an index into the original vertex list (inVertices), sorted according to occurrence
|
||||||
Array<uint> mOffsetsToPatch; ///< Offsets to the vertex buffer that need to be patched in once all nodes have been packed
|
VertexMap mVertexMap; ///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This class is used to decode and decompress triangle data packed by the EncodingContext
|
/// This class is used to decode and decompress triangle data packed by the EncodingContext
|
||||||
class DecodingContext
|
class DecodingContext
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/// Private helper functions to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.)
|
/// Private helper function to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.)
|
||||||
JPH_INLINE void Unpack(const VertexData *inVertices, UVec4Arg inIndex, Vec4 &outX, Vec4 &outY, Vec4 &outZ) const
|
JPH_INLINE void Unpack(const VertexData *inVertices, UVec4Arg inIndex, Vec4 &outX, Vec4 &outY, Vec4 &outZ) const
|
||||||
{
|
{
|
||||||
// Get compressed data
|
// Get compressed data
|
||||||
|
|
@ -241,6 +356,28 @@ public:
|
||||||
outZ = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ);
|
outZ = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private helper function to unpack 4 triangles from a triangle block
|
||||||
|
JPH_INLINE void Unpack(const TriangleBlock *inBlock, const VertexData *inVertices, Vec4 &outX1, Vec4 &outY1, Vec4 &outZ1, Vec4 &outX2, Vec4 &outY2, Vec4 &outZ2, Vec4 &outX3, Vec4 &outY3, Vec4 &outZ3) const
|
||||||
|
{
|
||||||
|
// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
|
||||||
|
UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&inBlock->mIndices[0]));
|
||||||
|
UVec4 iv1 = indices.Expand4Byte0();
|
||||||
|
UVec4 iv2 = indices.Expand4Byte4();
|
||||||
|
UVec4 iv3 = indices.Expand4Byte8();
|
||||||
|
|
||||||
|
#ifdef JPH_CPU_BIG_ENDIAN
|
||||||
|
// On big endian systems we need to reverse the bytes
|
||||||
|
iv1 = iv1.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
|
||||||
|
iv2 = iv2.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
|
||||||
|
iv3 = iv3.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Decompress the triangle data
|
||||||
|
Unpack(inVertices, iv1, outX1, outY1, outZ1);
|
||||||
|
Unpack(inVertices, iv2, outX2, outY2, outZ2);
|
||||||
|
Unpack(inVertices, iv3, outX3, outY3, outZ3);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JPH_INLINE explicit DecodingContext(const TriangleHeader *inHeader) :
|
JPH_INLINE explicit DecodingContext(const TriangleHeader *inHeader) :
|
||||||
mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)),
|
mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)),
|
||||||
|
|
@ -257,25 +394,17 @@ public:
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNumTriangles > 0);
|
JPH_ASSERT(inNumTriangles > 0);
|
||||||
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
||||||
const VertexData *vertices = header->GetVertexData();
|
const VertexData *vertices = header->GetVertexData();
|
||||||
const TriangleBlock *t = header->GetTriangleBlock();
|
const TriangleBlock *t = header->GetTriangleBlock();
|
||||||
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
||||||
|
|
||||||
int triangles_left = inNumTriangles;
|
int triangles_left = inNumTriangles;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
|
// Unpack the vertices for 4 triangles
|
||||||
UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&t->mIndices[0]));
|
|
||||||
UVec4 iv1 = indices.Expand4Byte0();
|
|
||||||
UVec4 iv2 = indices.Expand4Byte4();
|
|
||||||
UVec4 iv3 = indices.Expand4Byte8();
|
|
||||||
|
|
||||||
// Decompress the triangle data
|
|
||||||
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
|
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
|
||||||
Unpack(vertices, iv1, v1x, v1y, v1z);
|
Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
|
||||||
Unpack(vertices, iv2, v2x, v2y, v2z);
|
|
||||||
Unpack(vertices, iv3, v3x, v3y, v3z);
|
|
||||||
|
|
||||||
// Transpose it so we get normal vectors
|
// Transpose it so we get normal vectors
|
||||||
Mat44 v1 = Mat44(v1x, v1y, v1z, Vec4::sZero()).Transposed();
|
Mat44 v1 = Mat44(v1x, v1y, v1z, Vec4::sZero()).Transposed();
|
||||||
|
|
@ -291,7 +420,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
++t;
|
++t;
|
||||||
}
|
}
|
||||||
while (t < end);
|
while (t < end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +429,7 @@ public:
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNumTriangles > 0);
|
JPH_ASSERT(inNumTriangles > 0);
|
||||||
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
||||||
const VertexData *vertices = header->GetVertexData();
|
const VertexData *vertices = header->GetVertexData();
|
||||||
const TriangleBlock *t = header->GetTriangleBlock();
|
const TriangleBlock *t = header->GetTriangleBlock();
|
||||||
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
||||||
|
|
||||||
|
|
@ -310,17 +439,9 @@ public:
|
||||||
UVec4 start_triangle_idx = UVec4::sZero();
|
UVec4 start_triangle_idx = UVec4::sZero();
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
|
// Unpack the vertices for 4 triangles
|
||||||
UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&t->mIndices[0]));
|
|
||||||
UVec4 iv1 = indices.Expand4Byte0();
|
|
||||||
UVec4 iv2 = indices.Expand4Byte4();
|
|
||||||
UVec4 iv3 = indices.Expand4Byte8();
|
|
||||||
|
|
||||||
// Decompress the triangle data
|
|
||||||
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
|
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
|
||||||
Unpack(vertices, iv1, v1x, v1y, v1z);
|
Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
|
||||||
Unpack(vertices, iv2, v2x, v2y, v2z);
|
|
||||||
Unpack(vertices, iv3, v3x, v3y, v3z);
|
|
||||||
|
|
||||||
// Perform ray vs triangle test
|
// Perform ray vs triangle test
|
||||||
Vec4 distance = RayTriangle4(inRayOrigin, inRayDirection, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
|
Vec4 distance = RayTriangle4(inRayOrigin, inRayDirection, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
|
||||||
|
|
@ -336,7 +457,7 @@ public:
|
||||||
// Next block
|
// Next block
|
||||||
++t;
|
++t;
|
||||||
start_triangle_idx += UVec4::sReplicate(4);
|
start_triangle_idx += UVec4::sReplicate(4);
|
||||||
}
|
}
|
||||||
while (t < end);
|
while (t < end);
|
||||||
|
|
||||||
// Get the smallest component
|
// Get the smallest component
|
||||||
|
|
@ -379,14 +500,22 @@ public:
|
||||||
outV3 = trans.GetAxisZ();
|
outV3 = trans.GetAxisZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get user data for a triangle
|
||||||
|
JPH_INLINE uint32 GetUserData(const void *inTriangleStart, uint32 inTriangleIdx) const
|
||||||
|
{
|
||||||
|
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
||||||
|
const uint32 *user_data = header->GetUserData();
|
||||||
|
return user_data != nullptr? user_data[inTriangleIdx] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get flags for entire triangle block
|
/// Get flags for entire triangle block
|
||||||
JPH_INLINE static void sGetFlags(const void *inTriangleStart, uint32 inNumTriangles, uint8 *outTriangleFlags)
|
JPH_INLINE static void sGetFlags(const void *inTriangleStart, uint32 inNumTriangles, uint8 *outTriangleFlags)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNumTriangles > 0);
|
JPH_ASSERT(inNumTriangles > 0);
|
||||||
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
|
||||||
const TriangleBlock *t = header->GetTriangleBlock();
|
const TriangleBlock *t = header->GetTriangleBlock();
|
||||||
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
|
||||||
|
|
||||||
int triangles_left = inNumTriangles;
|
int triangles_left = inNumTriangles;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +523,7 @@ public:
|
||||||
*outTriangleFlags++ = t->mFlags[i];
|
*outTriangleFlags++ = t->mFlags[i];
|
||||||
|
|
||||||
++t;
|
++t;
|
||||||
}
|
}
|
||||||
while (t < end);
|
while (t < end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,7 +535,7 @@ public:
|
||||||
return first_block[inTriangleIndex >> 2].mFlags[inTriangleIndex & 0b11];
|
return first_block[inTriangleIndex >> 2].mFlags[inTriangleIndex & 0b11];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpacks triangles and flags, convencience function
|
/// Unpacks triangles and flags, convenience function
|
||||||
JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const
|
JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const
|
||||||
{
|
{
|
||||||
Unpack(inTriangleStart, inNumTriangles, outTriangles);
|
Unpack(inTriangleStart, inNumTriangles, outTriangles);
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,23 @@ inline const char *GetConfigurationString()
|
||||||
"x86 "
|
"x86 "
|
||||||
#elif defined(JPH_CPU_ARM)
|
#elif defined(JPH_CPU_ARM)
|
||||||
"ARM "
|
"ARM "
|
||||||
#elif defined(JPH_PLATFORM_WASM)
|
#elif defined(JPH_CPU_RISCV)
|
||||||
|
"RISC-V "
|
||||||
|
#elif defined(JPH_CPU_PPC)
|
||||||
|
"PowerPC "
|
||||||
|
#ifdef JPH_CPU_BIG_ENDIAN
|
||||||
|
"(Big Endian) "
|
||||||
|
#else
|
||||||
|
"(Little Endian) "
|
||||||
|
#endif
|
||||||
|
#elif defined(JPH_CPU_LOONGARCH)
|
||||||
|
"LoongArch "
|
||||||
|
#elif defined(JPH_CPU_E2K)
|
||||||
|
"E2K "
|
||||||
|
#elif defined(JPH_CPU_WASM)
|
||||||
"WASM "
|
"WASM "
|
||||||
|
#else
|
||||||
|
#error Unknown CPU architecture
|
||||||
#endif
|
#endif
|
||||||
#if JPH_CPU_ADDRESS_BITS == 64
|
#if JPH_CPU_ADDRESS_BITS == 64
|
||||||
"64-bit "
|
"64-bit "
|
||||||
|
|
@ -62,8 +77,31 @@ inline const char *GetConfigurationString()
|
||||||
#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
|
#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
|
||||||
"(FP Exceptions) "
|
"(FP Exceptions) "
|
||||||
#endif
|
#endif
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG_RENDERER
|
||||||
|
"(Debug Renderer) "
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_PROFILE_ENABLED
|
||||||
|
"(Profile) "
|
||||||
|
#endif
|
||||||
|
#if defined(JPH_OBJECT_LAYER_BITS) && JPH_OBJECT_LAYER_BITS == 32
|
||||||
|
"(32-bit ObjectLayer) "
|
||||||
|
#else
|
||||||
|
"(16-bit ObjectLayer) "
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
|
"(Assertions) "
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
|
"(ObjectStream) "
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_DEBUG
|
||||||
"(Debug) "
|
"(Debug) "
|
||||||
|
#endif
|
||||||
|
#if defined(__cpp_rtti) && __cpp_rtti
|
||||||
|
"(C++ RTTI) "
|
||||||
|
#endif
|
||||||
|
#if defined(__cpp_exceptions) && __cpp_exceptions
|
||||||
|
"(C++ Exceptions) "
|
||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,22 @@
|
||||||
|
|
||||||
#ifdef JPH_USE_NEON
|
#ifdef JPH_USE_NEON
|
||||||
|
|
||||||
|
// Constructing NEON values
|
||||||
#ifdef JPH_COMPILER_MSVC
|
#ifdef JPH_COMPILER_MSVC
|
||||||
JPH_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
// Constructing NEON values
|
|
||||||
#define JPH_NEON_INT32x4(v1, v2, v3, v4) { int64_t(v1) + (int64_t(v2) << 32), int64_t(v3) + (int64_t(v4) << 32) }
|
#define JPH_NEON_INT32x4(v1, v2, v3, v4) { int64_t(v1) + (int64_t(v2) << 32), int64_t(v3) + (int64_t(v4) << 32) }
|
||||||
#define JPH_NEON_UINT32x4(v1, v2, v3, v4) { uint64_t(v1) + (uint64_t(v2) << 32), uint64_t(v3) + (uint64_t(v4) << 32) }
|
#define JPH_NEON_UINT32x4(v1, v2, v3, v4) { uint64_t(v1) + (uint64_t(v2) << 32), uint64_t(v3) + (uint64_t(v4) << 32) }
|
||||||
#define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { int64_t(v1) + (int64_t(v2) << 8) + (int64_t(v3) << 16) + (int64_t(v4) << 24) + (int64_t(v5) << 32) + (int64_t(v6) << 40) + (int64_t(v7) << 48) + (int64_t(v8) << 56), int64_t(v9) + (int64_t(v10) << 8) + (int64_t(v11) << 16) + (int64_t(v12) << 24) + (int64_t(v13) << 32) + (int64_t(v14) << 40) + (int64_t(v15) << 48) + (int64_t(v16) << 56) }
|
#define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { int64_t(v1) + (int64_t(v2) << 8) + (int64_t(v3) << 16) + (int64_t(v4) << 24) + (int64_t(v5) << 32) + (int64_t(v6) << 40) + (int64_t(v7) << 48) + (int64_t(v8) << 56), int64_t(v9) + (int64_t(v10) << 8) + (int64_t(v11) << 16) + (int64_t(v12) << 24) + (int64_t(v13) << 32) + (int64_t(v14) << 40) + (int64_t(v15) << 48) + (int64_t(v16) << 56) }
|
||||||
|
#define JPH_NEON_UINT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { uint64_t(v1) + (uint64_t(v2) << 8) + (uint64_t(v3) << 16) + (uint64_t(v4) << 24) + (uint64_t(v5) << 32) + (uint64_t(v6) << 40) + (uint64_t(v7) << 48) + (uint64_t(v8) << 56), uint64_t(v9) + (uint64_t(v10) << 8) + (uint64_t(v11) << 16) + (uint64_t(v12) << 24) + (uint64_t(v13) << 32) + (uint64_t(v14) << 40) + (uint64_t(v15) << 48) + (uint64_t(v16) << 56) }
|
||||||
|
#else
|
||||||
|
#define JPH_NEON_INT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 }
|
||||||
|
#define JPH_NEON_UINT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 }
|
||||||
|
#define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 }
|
||||||
|
#define JPH_NEON_UINT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MSVC and GCC prior to version 12 don't define __builtin_shufflevector
|
||||||
|
#if defined(JPH_COMPILER_MSVC) || (defined(JPH_COMPILER_GCC) && __GNUC__ < 12)
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// Generic shuffle vector template
|
// Generic shuffle vector template
|
||||||
template <unsigned I1, unsigned I2, unsigned I3, unsigned I4>
|
template <unsigned I1, unsigned I2, unsigned I3, unsigned I4>
|
||||||
|
|
@ -30,13 +39,13 @@
|
||||||
template <>
|
template <>
|
||||||
JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 2, 2>(float32x4_t inV1, float32x4_t inV2)
|
JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 2, 2>(float32x4_t inV1, float32x4_t inV2)
|
||||||
{
|
{
|
||||||
return vcombine_f32(vget_low_f32(inV1), vdup_lane_s32(vget_high_f32(inV1), 0));
|
return vcombine_f32(vget_low_f32(inV1), vdup_lane_f32(vget_high_f32(inV1), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 3, 3>(float32x4_t inV1, float32x4_t inV2)
|
JPH_INLINE float32x4_t NeonShuffleFloat32x4<0, 1, 3, 3>(float32x4_t inV1, float32x4_t inV2)
|
||||||
{
|
{
|
||||||
return vcombine_f32(vget_low_f32(inV1), vdup_lane_s32(vget_high_f32(inV1), 1));
|
return vcombine_f32(vget_low_f32(inV1), vdup_lane_f32(vget_high_f32(inV1), 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
@ -48,13 +57,13 @@
|
||||||
template <>
|
template <>
|
||||||
JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 0, 3, 2>(float32x4_t inV1, float32x4_t inV2)
|
JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 0, 3, 2>(float32x4_t inV1, float32x4_t inV2)
|
||||||
{
|
{
|
||||||
return vcombine_f32(vrev64_f32(vget_low_f32(inV1)), vrev64_f32(vget_high_f32(inV1)));
|
return vcombine_f32(vrev64_f32(vget_low_f32(inV1)), vrev64_f32(vget_high_f32(inV1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
JPH_INLINE float32x4_t NeonShuffleFloat32x4<2, 2, 1, 0>(float32x4_t inV1, float32x4_t inV2)
|
JPH_INLINE float32x4_t NeonShuffleFloat32x4<2, 2, 1, 0>(float32x4_t inV1, float32x4_t inV2)
|
||||||
{
|
{
|
||||||
return vcombine_f32(vdup_lane_s32(vget_high_f32(inV1), 0), vrev64_f32(vget_low_f32(inV1)));
|
return vcombine_f32(vdup_lane_f32(vget_high_f32(inV1), 0), vrev64_f32(vget_low_f32(inV1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
@ -67,22 +76,19 @@
|
||||||
template <>
|
template <>
|
||||||
JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 2, 0, 0>(float32x4_t inV1, float32x4_t inV2)
|
JPH_INLINE float32x4_t NeonShuffleFloat32x4<1, 2, 0, 0>(float32x4_t inV1, float32x4_t inV2)
|
||||||
{
|
{
|
||||||
static int8x16_t table = JPH_NEON_INT8x16(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03);
|
static uint8x16_t table = JPH_NEON_UINT8x16(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03);
|
||||||
return vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(inV1), table));
|
return vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(inV1), table));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle a vector
|
// Shuffle a vector
|
||||||
#define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) NeonShuffleFloat32x4<index1, index2, index3, index4>(vec1, vec2)
|
#define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) NeonShuffleFloat32x4<index1, index2, index3, index4>(vec1, vec2)
|
||||||
|
#define JPH_NEON_SHUFFLE_U32x4(vec1, vec2, index1, index2, index3, index4) vreinterpretq_u32_f32((NeonShuffleFloat32x4<index1, index2, index3, index4>(vreinterpretq_f32_u32(vec1), vreinterpretq_f32_u32(vec2))))
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
#else
|
#else
|
||||||
// Constructing NEON values
|
|
||||||
#define JPH_NEON_INT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 }
|
|
||||||
#define JPH_NEON_UINT32x4(v1, v2, v3, v4) { v1, v2, v3, v4 }
|
|
||||||
#define JPH_NEON_INT8x16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 }
|
|
||||||
|
|
||||||
// Shuffle a vector
|
// Shuffle a vector
|
||||||
#define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) __builtin_shufflevector(vec1, vec2, index1, index2, index3, index4)
|
#define JPH_NEON_SHUFFLE_F32x4(vec1, vec2, index1, index2, index3, index4) __builtin_shufflevector(vec1, vec2, index1, index2, index3, index4)
|
||||||
|
#define JPH_NEON_SHUFFLE_U32x4(vec1, vec2, index1, index2, index3, index4) __builtin_shufflevector(vec1, vec2, index1, index2, index3, index4)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // JPH_USE_NEON
|
#endif // JPH_USE_NEON
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,713 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/STLAllocator.h>
|
||||||
|
#include <Jolt/Core/HashCombine.h>
|
||||||
|
|
||||||
|
#ifdef JPH_USE_STD_VECTOR
|
||||||
|
|
||||||
|
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
|
#include <vector>
|
||||||
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
template <class T, class Allocator = STLAllocator<T>> using Array = std::vector<T, Allocator>;
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Simple replacement for std::vector
|
||||||
|
///
|
||||||
|
/// Major differences:
|
||||||
|
/// - Memory is not initialized to zero (this was causing a lot of page faults when deserializing large MeshShapes / HeightFieldShapes)
|
||||||
|
/// - Iterators are simple pointers (for now)
|
||||||
|
/// - No exception safety
|
||||||
|
/// - No specialization like std::vector<bool> has
|
||||||
|
/// - Not all functions have been implemented
|
||||||
|
template <class T, class Allocator = STLAllocator<T>>
|
||||||
|
class [[nodiscard]] Array : private Allocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
using allocator_type = Allocator;
|
||||||
|
using size_type = size_t;
|
||||||
|
using difference_type = typename Allocator::difference_type;
|
||||||
|
using pointer = T *;
|
||||||
|
using const_pointer = const T *;
|
||||||
|
using reference = T &;
|
||||||
|
using const_reference = const T &;
|
||||||
|
|
||||||
|
using const_iterator = const T *;
|
||||||
|
using iterator = T *;
|
||||||
|
|
||||||
|
/// An iterator that traverses the array in reverse order
|
||||||
|
class rev_it
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
rev_it() = default;
|
||||||
|
explicit rev_it(T *inValue) : mValue(inValue) { }
|
||||||
|
|
||||||
|
/// Copying
|
||||||
|
rev_it(const rev_it &) = default;
|
||||||
|
rev_it & operator = (const rev_it &) = default;
|
||||||
|
|
||||||
|
/// Comparison
|
||||||
|
bool operator == (const rev_it &inRHS) const { return mValue == inRHS.mValue; }
|
||||||
|
bool operator != (const rev_it &inRHS) const { return mValue != inRHS.mValue; }
|
||||||
|
|
||||||
|
/// Arithmetics
|
||||||
|
rev_it & operator ++ () { --mValue; return *this; }
|
||||||
|
rev_it operator ++ (int) { return rev_it(mValue--); }
|
||||||
|
rev_it & operator -- () { ++mValue; return *this; }
|
||||||
|
rev_it operator -- (int) { return rev_it(mValue++); }
|
||||||
|
|
||||||
|
rev_it operator + (int inValue) { return rev_it(mValue - inValue); }
|
||||||
|
rev_it operator - (int inValue) { return rev_it(mValue + inValue); }
|
||||||
|
|
||||||
|
rev_it & operator += (int inValue) { mValue -= inValue; return *this; }
|
||||||
|
rev_it & operator -= (int inValue) { mValue += inValue; return *this; }
|
||||||
|
|
||||||
|
/// Access
|
||||||
|
T & operator * () const { return *mValue; }
|
||||||
|
T & operator -> () const { return *mValue; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T * mValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A const iterator that traverses the array in reverse order
|
||||||
|
class crev_it
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
crev_it() = default;
|
||||||
|
explicit crev_it(const T *inValue) : mValue(inValue) { }
|
||||||
|
|
||||||
|
/// Copying
|
||||||
|
crev_it(const crev_it &) = default;
|
||||||
|
explicit crev_it(const rev_it &inValue) : mValue(inValue.mValue) { }
|
||||||
|
crev_it & operator = (const crev_it &) = default;
|
||||||
|
crev_it & operator = (const rev_it &inRHS) { mValue = inRHS.mValue; return *this; }
|
||||||
|
|
||||||
|
/// Comparison
|
||||||
|
bool operator == (const crev_it &inRHS) const { return mValue == inRHS.mValue; }
|
||||||
|
bool operator != (const crev_it &inRHS) const { return mValue != inRHS.mValue; }
|
||||||
|
|
||||||
|
/// Arithmetics
|
||||||
|
crev_it & operator ++ () { --mValue; return *this; }
|
||||||
|
crev_it operator ++ (int) { return crev_it(mValue--); }
|
||||||
|
crev_it & operator -- () { ++mValue; return *this; }
|
||||||
|
crev_it operator -- (int) { return crev_it(mValue++); }
|
||||||
|
|
||||||
|
crev_it operator + (int inValue) { return crev_it(mValue - inValue); }
|
||||||
|
crev_it operator - (int inValue) { return crev_it(mValue + inValue); }
|
||||||
|
|
||||||
|
crev_it & operator += (int inValue) { mValue -= inValue; return *this; }
|
||||||
|
crev_it & operator -= (int inValue) { mValue += inValue; return *this; }
|
||||||
|
|
||||||
|
/// Access
|
||||||
|
const T & operator * () const { return *mValue; }
|
||||||
|
const T & operator -> () const { return *mValue; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T * mValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
using reverse_iterator = rev_it;
|
||||||
|
using const_reverse_iterator = crev_it;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Move elements from one location to another
|
||||||
|
inline void move(pointer inDestination, pointer inSource, size_type inCount)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_trivially_copyable<T>())
|
||||||
|
memmove(inDestination, inSource, inCount * sizeof(T));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inDestination < inSource)
|
||||||
|
{
|
||||||
|
for (T *destination_end = inDestination + inCount; inDestination < destination_end; ++inDestination, ++inSource)
|
||||||
|
{
|
||||||
|
new (inDestination) T(std::move(*inSource));
|
||||||
|
inSource->~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (T *destination = inDestination + inCount - 1, *source = inSource + inCount - 1; destination >= inDestination; --destination, --source)
|
||||||
|
{
|
||||||
|
new (destination) T(std::move(*source));
|
||||||
|
source->~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reallocate the data block to inNewCapacity
|
||||||
|
inline void reallocate(size_type inNewCapacity)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inNewCapacity > 0 && inNewCapacity >= mSize);
|
||||||
|
|
||||||
|
pointer ptr;
|
||||||
|
if constexpr (AllocatorHasReallocate<Allocator>::sValue)
|
||||||
|
{
|
||||||
|
// Reallocate data block
|
||||||
|
ptr = get_allocator().reallocate(mElements, mCapacity, inNewCapacity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Copy data to a new location
|
||||||
|
ptr = get_allocator().allocate(inNewCapacity);
|
||||||
|
if (mElements != nullptr)
|
||||||
|
{
|
||||||
|
move(ptr, mElements, mSize);
|
||||||
|
get_allocator().deallocate(mElements, mCapacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mElements = ptr;
|
||||||
|
mCapacity = inNewCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destruct elements [inStart, inEnd - 1]
|
||||||
|
inline void destruct(size_type inStart, size_type inEnd)
|
||||||
|
{
|
||||||
|
if constexpr (!std::is_trivially_destructible<T>())
|
||||||
|
if (inStart < inEnd)
|
||||||
|
for (T *element = mElements + inStart, *element_end = mElements + inEnd; element < element_end; ++element)
|
||||||
|
element->~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Reserve array space
|
||||||
|
inline void reserve(size_type inNewSize)
|
||||||
|
{
|
||||||
|
if (mCapacity < inNewSize)
|
||||||
|
reallocate(inNewSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resize array to new length
|
||||||
|
inline void resize(size_type inNewSize)
|
||||||
|
{
|
||||||
|
destruct(inNewSize, mSize);
|
||||||
|
reserve(inNewSize);
|
||||||
|
|
||||||
|
if constexpr (!std::is_trivially_constructible<T>())
|
||||||
|
for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
|
||||||
|
new (element) T;
|
||||||
|
mSize = inNewSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resize array to new length and initialize all elements with inValue
|
||||||
|
inline void resize(size_type inNewSize, const T &inValue)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to resize");
|
||||||
|
|
||||||
|
destruct(inNewSize, mSize);
|
||||||
|
reserve(inNewSize);
|
||||||
|
|
||||||
|
for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
|
||||||
|
new (element) T(inValue);
|
||||||
|
mSize = inNewSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destruct all elements and set length to zero
|
||||||
|
inline void clear()
|
||||||
|
{
|
||||||
|
destruct(0, mSize);
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Grow the array by at least inAmount elements
|
||||||
|
inline void grow(size_type inAmount = 1)
|
||||||
|
{
|
||||||
|
size_type min_size = mSize + inAmount;
|
||||||
|
if (min_size > mCapacity)
|
||||||
|
{
|
||||||
|
size_type new_capacity = max(min_size, mCapacity * 2);
|
||||||
|
reserve(new_capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free memory
|
||||||
|
inline void free()
|
||||||
|
{
|
||||||
|
get_allocator().deallocate(mElements, mCapacity);
|
||||||
|
mElements = nullptr;
|
||||||
|
mCapacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy all elements and free memory
|
||||||
|
inline void destroy()
|
||||||
|
{
|
||||||
|
if (mElements != nullptr)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Replace the contents of this array with inBegin .. inEnd
|
||||||
|
template <class Iterator>
|
||||||
|
inline void assign(Iterator inBegin, Iterator inEnd)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
reserve(size_type(std::distance(inBegin, inEnd)));
|
||||||
|
|
||||||
|
for (Iterator element = inBegin; element != inEnd; ++element)
|
||||||
|
new (&mElements[mSize++]) T(*element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace the contents of this array with inList
|
||||||
|
inline void assign(std::initializer_list<T> inList)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
reserve(size_type(inList.size()));
|
||||||
|
|
||||||
|
for (const T &v : inList)
|
||||||
|
new (&mElements[mSize++]) T(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default constructor
|
||||||
|
Array() = default;
|
||||||
|
|
||||||
|
/// Constructor with allocator
|
||||||
|
explicit inline Array(const Allocator &inAllocator) :
|
||||||
|
Allocator(inAllocator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor with length
|
||||||
|
explicit inline Array(size_type inLength, const Allocator &inAllocator = { }) :
|
||||||
|
Allocator(inAllocator)
|
||||||
|
{
|
||||||
|
resize(inLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor with length and value
|
||||||
|
inline Array(size_type inLength, const T &inValue, const Allocator &inAllocator = { }) :
|
||||||
|
Allocator(inAllocator)
|
||||||
|
{
|
||||||
|
resize(inLength, inValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor from initializer list
|
||||||
|
inline Array(std::initializer_list<T> inList, const Allocator &inAllocator = { }) :
|
||||||
|
Allocator(inAllocator)
|
||||||
|
{
|
||||||
|
assign(inList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor from iterator
|
||||||
|
inline Array(const_iterator inBegin, const_iterator inEnd, const Allocator &inAllocator = { }) :
|
||||||
|
Allocator(inAllocator)
|
||||||
|
{
|
||||||
|
assign(inBegin, inEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
inline Array(const Array<T, Allocator> &inRHS) :
|
||||||
|
Allocator(inRHS.get_allocator())
|
||||||
|
{
|
||||||
|
assign(inRHS.begin(), inRHS.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move constructor
|
||||||
|
inline Array(Array<T, Allocator> &&inRHS) noexcept :
|
||||||
|
Allocator(std::move(inRHS.get_allocator())),
|
||||||
|
mSize(inRHS.mSize),
|
||||||
|
mCapacity(inRHS.mCapacity),
|
||||||
|
mElements(inRHS.mElements)
|
||||||
|
{
|
||||||
|
inRHS.mSize = 0;
|
||||||
|
inRHS.mCapacity = 0;
|
||||||
|
inRHS.mElements = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destruct all elements
|
||||||
|
inline ~Array()
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the allocator
|
||||||
|
inline Allocator & get_allocator()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const Allocator &get_allocator() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add element to the back of the array
|
||||||
|
inline void push_back(const T &inValue)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to push_back");
|
||||||
|
|
||||||
|
grow();
|
||||||
|
|
||||||
|
T *element = mElements + mSize++;
|
||||||
|
new (element) T(inValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void push_back(T &&inValue)
|
||||||
|
{
|
||||||
|
grow();
|
||||||
|
|
||||||
|
T *element = mElements + mSize++;
|
||||||
|
new (element) T(std::move(inValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct element at the back of the array
|
||||||
|
template <class... A>
|
||||||
|
inline T & emplace_back(A &&... inValue)
|
||||||
|
{
|
||||||
|
grow();
|
||||||
|
|
||||||
|
T *element = mElements + mSize++;
|
||||||
|
new (element) T(std::forward<A>(inValue)...);
|
||||||
|
return *element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove element from the back of the array
|
||||||
|
inline void pop_back()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mSize > 0);
|
||||||
|
mElements[--mSize].~T();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if there are no elements in the array
|
||||||
|
inline bool empty() const
|
||||||
|
{
|
||||||
|
return mSize == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns amount of elements in the array
|
||||||
|
inline size_type size() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns maximum amount of elements the array can hold
|
||||||
|
inline size_type capacity() const
|
||||||
|
{
|
||||||
|
return mCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce the capacity of the array to match its size
|
||||||
|
void shrink_to_fit()
|
||||||
|
{
|
||||||
|
if (mElements != nullptr)
|
||||||
|
{
|
||||||
|
if (mSize == 0)
|
||||||
|
free();
|
||||||
|
else if (mCapacity > mSize)
|
||||||
|
reallocate(mSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swap the contents of two arrays
|
||||||
|
void swap(Array<T, Allocator> &inRHS) noexcept
|
||||||
|
{
|
||||||
|
std::swap(get_allocator(), inRHS.get_allocator());
|
||||||
|
std::swap(mSize, inRHS.mSize);
|
||||||
|
std::swap(mCapacity, inRHS.mCapacity);
|
||||||
|
std::swap(mElements, inRHS.mElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Iterator>
|
||||||
|
void insert(const_iterator inPos, Iterator inBegin, Iterator inEnd)
|
||||||
|
{
|
||||||
|
size_type num_elements = size_type(std::distance(inBegin, inEnd));
|
||||||
|
if (num_elements > 0)
|
||||||
|
{
|
||||||
|
// After grow() inPos may be invalid
|
||||||
|
size_type first_element = inPos - mElements;
|
||||||
|
|
||||||
|
grow(num_elements);
|
||||||
|
|
||||||
|
T *element_begin = mElements + first_element;
|
||||||
|
T *element_end = element_begin + num_elements;
|
||||||
|
move(element_end, element_begin, mSize - first_element);
|
||||||
|
|
||||||
|
for (T *element = element_begin; element < element_end; ++element, ++inBegin)
|
||||||
|
new (element) T(*inBegin);
|
||||||
|
|
||||||
|
mSize += num_elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const_iterator inPos, const T &inValue)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(&inValue < mElements || &inValue >= mElements + mSize, "Can't pass an element from the array to insert");
|
||||||
|
|
||||||
|
// After grow() inPos may be invalid
|
||||||
|
size_type first_element = inPos - mElements;
|
||||||
|
|
||||||
|
grow();
|
||||||
|
|
||||||
|
T *element = mElements + first_element;
|
||||||
|
move(element + 1, element, mSize - first_element);
|
||||||
|
|
||||||
|
new (element) T(inValue);
|
||||||
|
mSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove one element from the array
|
||||||
|
iterator erase(const_iterator inIter)
|
||||||
|
{
|
||||||
|
size_type p = size_type(inIter - begin());
|
||||||
|
JPH_ASSERT(p < mSize);
|
||||||
|
mElements[p].~T();
|
||||||
|
if (p + 1 < mSize)
|
||||||
|
move(mElements + p, mElements + p + 1, mSize - p - 1);
|
||||||
|
--mSize;
|
||||||
|
return const_cast<iterator>(inIter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove multiple element from the array
|
||||||
|
iterator erase(const_iterator inBegin, const_iterator inEnd)
|
||||||
|
{
|
||||||
|
size_type p = size_type(inBegin - begin());
|
||||||
|
size_type n = size_type(inEnd - inBegin);
|
||||||
|
JPH_ASSERT(inEnd <= end());
|
||||||
|
destruct(p, p + n);
|
||||||
|
if (p + n < mSize)
|
||||||
|
move(mElements + p, mElements + p + n, mSize - p - n);
|
||||||
|
mSize -= n;
|
||||||
|
return const_cast<iterator>(inBegin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterators
|
||||||
|
inline const_iterator begin() const
|
||||||
|
{
|
||||||
|
return mElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator end() const
|
||||||
|
{
|
||||||
|
return mElements + mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline crev_it rbegin() const
|
||||||
|
{
|
||||||
|
return crev_it(mElements + mSize - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline crev_it rend() const
|
||||||
|
{
|
||||||
|
return crev_it(mElements - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator cbegin() const
|
||||||
|
{
|
||||||
|
return begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator cend() const
|
||||||
|
{
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline crev_it crbegin() const
|
||||||
|
{
|
||||||
|
return rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline crev_it crend() const
|
||||||
|
{
|
||||||
|
return rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline iterator begin()
|
||||||
|
{
|
||||||
|
return mElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline iterator end()
|
||||||
|
{
|
||||||
|
return mElements + mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline rev_it rbegin()
|
||||||
|
{
|
||||||
|
return rev_it(mElements + mSize - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline rev_it rend()
|
||||||
|
{
|
||||||
|
return rev_it(mElements - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const T * data() const
|
||||||
|
{
|
||||||
|
return mElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline T * data()
|
||||||
|
{
|
||||||
|
return mElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access element
|
||||||
|
inline T & operator [] (size_type inIdx)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return mElements[inIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const T & operator [] (size_type inIdx) const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return mElements[inIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access element
|
||||||
|
inline T & at(size_type inIdx)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return mElements[inIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const T & at(size_type inIdx) const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return mElements[inIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First element in the array
|
||||||
|
inline const T & front() const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mSize > 0);
|
||||||
|
return mElements[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline T & front()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mSize > 0);
|
||||||
|
return mElements[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Last element in the array
|
||||||
|
inline const T & back() const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mSize > 0);
|
||||||
|
return mElements[mSize - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline T & back()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mSize > 0);
|
||||||
|
return mElements[mSize - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assignment operator
|
||||||
|
Array<T, Allocator> & operator = (const Array<T, Allocator> &inRHS)
|
||||||
|
{
|
||||||
|
if (static_cast<const void *>(this) != static_cast<const void *>(&inRHS))
|
||||||
|
assign(inRHS.begin(), inRHS.end());
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assignment move operator
|
||||||
|
Array<T, Allocator> & operator = (Array<T, Allocator> &&inRHS) noexcept
|
||||||
|
{
|
||||||
|
if (static_cast<const void *>(this) != static_cast<const void *>(&inRHS))
|
||||||
|
{
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
get_allocator() = std::move(inRHS.get_allocator());
|
||||||
|
|
||||||
|
mSize = inRHS.mSize;
|
||||||
|
mCapacity = inRHS.mCapacity;
|
||||||
|
mElements = inRHS.mElements;
|
||||||
|
|
||||||
|
inRHS.mSize = 0;
|
||||||
|
inRHS.mCapacity = 0;
|
||||||
|
inRHS.mElements = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assignment operator
|
||||||
|
Array<T, Allocator> & operator = (std::initializer_list<T> inRHS)
|
||||||
|
{
|
||||||
|
assign(inRHS);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Comparing arrays
|
||||||
|
bool operator == (const Array<T, Allocator> &inRHS) const
|
||||||
|
{
|
||||||
|
if (mSize != inRHS.mSize)
|
||||||
|
return false;
|
||||||
|
for (size_type i = 0; i < mSize; ++i)
|
||||||
|
if (!(mElements[i] == inRHS.mElements[i]))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator != (const Array<T, Allocator> &inRHS) const
|
||||||
|
{
|
||||||
|
if (mSize != inRHS.mSize)
|
||||||
|
return true;
|
||||||
|
for (size_type i = 0; i < mSize; ++i)
|
||||||
|
if (mElements[i] != inRHS.mElements[i])
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get hash for this array
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
// Hash length first
|
||||||
|
uint64 ret = Hash<uint32> { } (uint32(size()));
|
||||||
|
|
||||||
|
// Then hash elements
|
||||||
|
for (const T *element = mElements, *element_end = mElements + mSize; element < element_end; ++element)
|
||||||
|
HashCombine(ret, *element);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_type mSize = 0;
|
||||||
|
size_type mCapacity = 0;
|
||||||
|
T * mElements = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
||||||
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
|
JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
/// Declare std::hash for Array
|
||||||
|
template <class T, class Allocator>
|
||||||
|
struct hash<JPH::Array<T, Allocator>>
|
||||||
|
{
|
||||||
|
size_t operator () (const JPH::Array<T, Allocator> &inRHS) const
|
||||||
|
{
|
||||||
|
return std::size_t(inRHS.GetHash());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
JPH_SUPPRESS_WARNING_POP
|
||||||
|
|
||||||
|
#endif // JPH_USE_STD_VECTOR
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Push a new element into a binary max-heap.
|
||||||
|
/// [inBegin, inEnd - 1) must be a a valid heap. Element inEnd - 1 will be inserted into the heap. The heap will be [inBegin, inEnd) after this call.
|
||||||
|
/// inPred is a function that returns true if the first element is less or equal than the second element.
|
||||||
|
/// See: https://en.wikipedia.org/wiki/Binary_heap
|
||||||
|
template <typename Iterator, typename Pred>
|
||||||
|
void BinaryHeapPush(Iterator inBegin, Iterator inEnd, Pred inPred)
|
||||||
|
{
|
||||||
|
using diff_t = typename std::iterator_traits<Iterator>::difference_type;
|
||||||
|
using elem_t = typename std::iterator_traits<Iterator>::value_type;
|
||||||
|
|
||||||
|
// New heap size
|
||||||
|
diff_t count = std::distance(inBegin, inEnd);
|
||||||
|
|
||||||
|
// Start from the last element
|
||||||
|
diff_t current = count - 1;
|
||||||
|
while (current > 0)
|
||||||
|
{
|
||||||
|
// Get current element
|
||||||
|
elem_t ¤t_elem = *(inBegin + current);
|
||||||
|
|
||||||
|
// Get parent element
|
||||||
|
diff_t parent = (current - 1) >> 1;
|
||||||
|
elem_t &parent_elem = *(inBegin + parent);
|
||||||
|
|
||||||
|
// Sort them so that the parent is larger than the child
|
||||||
|
if (inPred(parent_elem, current_elem))
|
||||||
|
{
|
||||||
|
std::swap(parent_elem, current_elem);
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// When there's no change, we're done
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop an element from a binary max-heap.
|
||||||
|
/// [inBegin, inEnd) must be a valid heap. The largest element will be removed from the heap. The heap will be [inBegin, inEnd - 1) after this call.
|
||||||
|
/// inPred is a function that returns true if the first element is less or equal than the second element.
|
||||||
|
/// See: https://en.wikipedia.org/wiki/Binary_heap
|
||||||
|
template <typename Iterator, typename Pred>
|
||||||
|
void BinaryHeapPop(Iterator inBegin, Iterator inEnd, Pred inPred)
|
||||||
|
{
|
||||||
|
using diff_t = typename std::iterator_traits<Iterator>::difference_type;
|
||||||
|
|
||||||
|
// Begin by moving the highest element to the end, this is the popped element
|
||||||
|
std::swap(*(inEnd - 1), *inBegin);
|
||||||
|
|
||||||
|
// New heap size
|
||||||
|
diff_t count = std::distance(inBegin, inEnd) - 1;
|
||||||
|
|
||||||
|
// Start from the root
|
||||||
|
diff_t largest = 0;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Get first child
|
||||||
|
diff_t child = (largest << 1) + 1;
|
||||||
|
|
||||||
|
// Check if we're beyond the end of the heap, if so the 2nd child is also beyond the end
|
||||||
|
if (child >= count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Remember the largest element from the previous iteration
|
||||||
|
diff_t prev_largest = largest;
|
||||||
|
|
||||||
|
// Check if first child is bigger, if so select it
|
||||||
|
if (inPred(*(inBegin + largest), *(inBegin + child)))
|
||||||
|
largest = child;
|
||||||
|
|
||||||
|
// Switch to the second child
|
||||||
|
++child;
|
||||||
|
|
||||||
|
// Check if second child is bigger, if so select it
|
||||||
|
if (child < count && inPred(*(inBegin + largest), *(inBegin + child)))
|
||||||
|
largest = child;
|
||||||
|
|
||||||
|
// If there was no change, we're done
|
||||||
|
if (prev_largest == largest)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Swap element
|
||||||
|
std::swap(*(inBegin + prev_largest), *(inBegin + largest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Underlying data type for ByteBuffer
|
/// Underlying data type for ByteBuffer
|
||||||
using ByteBufferVector = std::vector<uint8, STLAlignedAllocator<uint8, JPH_CACHE_LINE_SIZE>>;
|
using ByteBufferVector = Array<uint8, STLAlignedAllocator<uint8, JPH_CACHE_LINE_SIZE>>;
|
||||||
|
|
||||||
/// Simple byte buffer, aligned to a cache line
|
/// Simple byte buffer, aligned to a cache line
|
||||||
class ByteBuffer : public ByteBufferVector
|
class ByteBuffer : public ByteBufferVector
|
||||||
|
|
@ -23,7 +23,7 @@ public:
|
||||||
|
|
||||||
// Calculate new size and resize buffer
|
// Calculate new size and resize buffer
|
||||||
size_t s = AlignUp(size(), inSize);
|
size_t s = AlignUp(size(), inSize);
|
||||||
resize(s);
|
resize(s, 0);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ public:
|
||||||
|
|
||||||
// Construct elements
|
// Construct elements
|
||||||
for (Type *d = data, *d_end = data + inSize; d < d_end; ++d)
|
for (Type *d = data, *d_end = data + inSize; d < d_end; ++d)
|
||||||
::new (d) Type;
|
new (d) Type;
|
||||||
|
|
||||||
// Return pointer
|
// Return pointer
|
||||||
return data;
|
return data;
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,21 @@ class Color;
|
||||||
using ColorArg = Color;
|
using ColorArg = Color;
|
||||||
|
|
||||||
/// Class that holds an RGBA color with 8-bits per component
|
/// Class that holds an RGBA color with 8-bits per component
|
||||||
class [[nodiscard]] Color
|
class JPH_EXPORT_GCC_BUG_WORKAROUND [[nodiscard]] Color
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructors
|
/// Constructors
|
||||||
Color() = default; ///< Intentionally not initialized for performance reasons
|
Color() = default; ///< Intentionally not initialized for performance reasons
|
||||||
Color(const Color &inRHS) = default;
|
Color(const Color &inRHS) = default;
|
||||||
|
Color & operator = (const Color &inRHS) = default;
|
||||||
explicit constexpr Color(uint32 inColor) : mU32(inColor) { }
|
explicit constexpr Color(uint32 inColor) : mU32(inColor) { }
|
||||||
constexpr Color(uint8 inRed, uint8 inGreen, uint8 inBlue, uint8 inAlpha = 255) : r(inRed), g(inGreen), b(inBlue), a(inAlpha) { }
|
constexpr Color(uint8 inRed, uint8 inGreen, uint8 inBlue, uint8 inAlpha = 255) : r(inRed), g(inGreen), b(inBlue), a(inAlpha) { }
|
||||||
constexpr Color(ColorArg inRHS, uint8 inAlpha) : r(inRHS.r), g(inRHS.g), b(inRHS.b), a(inAlpha) { }
|
constexpr Color(ColorArg inRHS, uint8 inAlpha) : r(inRHS.r), g(inRHS.g), b(inRHS.b), a(inAlpha) { }
|
||||||
|
|
||||||
/// Comparison
|
/// Comparison
|
||||||
inline bool operator == (ColorArg inRHS) const { return mU32 == inRHS.mU32; }
|
inline bool operator == (ColorArg inRHS) const { return mU32 == inRHS.mU32; }
|
||||||
inline bool operator != (ColorArg inRHS) const { return mU32 != inRHS.mU32; }
|
inline bool operator != (ColorArg inRHS) const { return mU32 != inRHS.mU32; }
|
||||||
|
|
||||||
/// Convert to uint32
|
/// Convert to uint32
|
||||||
uint32 GetUInt32() const { return mU32; }
|
uint32 GetUInt32() const { return mU32; }
|
||||||
|
|
||||||
|
|
@ -33,6 +34,12 @@ public:
|
||||||
inline uint8 operator () (uint inIdx) const { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
|
inline uint8 operator () (uint inIdx) const { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
|
||||||
inline uint8 & operator () (uint inIdx) { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
|
inline uint8 & operator () (uint inIdx) { JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
|
||||||
|
|
||||||
|
/// Multiply two colors
|
||||||
|
inline Color operator * (const Color &inRHS) const { return Color(uint8((uint32(r) * inRHS.r) >> 8), uint8((uint32(g) * inRHS.g) >> 8), uint8((uint32(b) * inRHS.b) >> 8), uint8((uint32(a) * inRHS.a) >> 8)); }
|
||||||
|
|
||||||
|
/// Multiply color with intensity in the range [0, 1]
|
||||||
|
inline Color operator * (float inIntensity) const { return Color(uint8(r * inIntensity), uint8(g * inIntensity), uint8(b * inIntensity), a); }
|
||||||
|
|
||||||
/// Convert to Vec4 with range [0, 1]
|
/// Convert to Vec4 with range [0, 1]
|
||||||
inline Vec4 ToVec4() const { return Vec4(r, g, b, a) / 255.0f; }
|
inline Vec4 ToVec4() const { return Vec4(r, g, b, a) / 255.0f; }
|
||||||
|
|
||||||
|
|
@ -72,6 +79,6 @@ public:
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(is_trivial<Color>(), "Is supposed to be a trivial type!");
|
static_assert(std::is_trivial<Color>(), "Is supposed to be a trivial type!");
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,72 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// Jolt library version
|
||||||
|
#define JPH_VERSION_MAJOR 5
|
||||||
|
#define JPH_VERSION_MINOR 3
|
||||||
|
#define JPH_VERSION_PATCH 1
|
||||||
|
|
||||||
|
// Determine which features the library was compiled with
|
||||||
|
#ifdef JPH_DOUBLE_PRECISION
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_1 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_1 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_CROSS_PLATFORM_DETERMINISTIC
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_2 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_2 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_3 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_3 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_PROFILE_ENABLED
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_4 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_4 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_EXTERNAL_PROFILE
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_5 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_5 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_DEBUG_RENDERER
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_6 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_6 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_DISABLE_TEMP_ALLOCATOR
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_7 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_7 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_8 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_8 0
|
||||||
|
#endif
|
||||||
|
#if defined(JPH_OBJECT_LAYER_BITS) && JPH_OBJECT_LAYER_BITS == 32
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_9 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_9 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_10 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_10 0
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_11 1
|
||||||
|
#else
|
||||||
|
#define JPH_VERSION_FEATURE_BIT_11 0
|
||||||
|
#endif
|
||||||
|
#define JPH_VERSION_FEATURES (uint64(JPH_VERSION_FEATURE_BIT_1) | (JPH_VERSION_FEATURE_BIT_2 << 1) | (JPH_VERSION_FEATURE_BIT_3 << 2) | (JPH_VERSION_FEATURE_BIT_4 << 3) | (JPH_VERSION_FEATURE_BIT_5 << 4) | (JPH_VERSION_FEATURE_BIT_6 << 5) | (JPH_VERSION_FEATURE_BIT_7 << 6) | (JPH_VERSION_FEATURE_BIT_8 << 7) | (JPH_VERSION_FEATURE_BIT_9 << 8) | (JPH_VERSION_FEATURE_BIT_10 << 9) | (JPH_VERSION_FEATURE_BIT_11 << 10))
|
||||||
|
|
||||||
|
// Combine the version and features in a single ID
|
||||||
|
#define JPH_VERSION_ID ((JPH_VERSION_FEATURES << 24) | (JPH_VERSION_MAJOR << 16) | (JPH_VERSION_MINOR << 8) | JPH_VERSION_PATCH)
|
||||||
|
|
||||||
// Determine platform
|
// Determine platform
|
||||||
#if defined(JPH_PLATFORM_BLUE)
|
#if defined(JPH_PLATFORM_BLUE)
|
||||||
// Correct define already defined, this overrides everything else
|
// Correct define already defined, this overrides everything else
|
||||||
|
|
@ -17,13 +83,15 @@
|
||||||
#define JPH_PLATFORM_ANDROID
|
#define JPH_PLATFORM_ANDROID
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
#define JPH_PLATFORM_LINUX
|
#define JPH_PLATFORM_LINUX
|
||||||
|
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||||
|
#define JPH_PLATFORM_BSD
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#include <TargetConditionals.h>
|
#include <TargetConditionals.h>
|
||||||
#if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
|
#if defined(TARGET_OS_IPHONE) && !TARGET_OS_IPHONE
|
||||||
#define JPH_PLATFORM_MACOS
|
#define JPH_PLATFORM_MACOS
|
||||||
#else
|
#else
|
||||||
#define JPH_PLATFORM_IOS
|
#define JPH_PLATFORM_IOS
|
||||||
#endif
|
#endif
|
||||||
#elif defined(__EMSCRIPTEN__)
|
#elif defined(__EMSCRIPTEN__)
|
||||||
#define JPH_PLATFORM_WASM
|
#define JPH_PLATFORM_WASM
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -112,25 +180,128 @@
|
||||||
#define JPH_VECTOR_ALIGNMENT 8 // 32-bit ARM does not support aligning on the stack on 16 byte boundaries
|
#define JPH_VECTOR_ALIGNMENT 8 // 32-bit ARM does not support aligning on the stack on 16 byte boundaries
|
||||||
#define JPH_DVECTOR_ALIGNMENT 8
|
#define JPH_DVECTOR_ALIGNMENT 8
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(__riscv)
|
||||||
|
// RISC-V CPU architecture
|
||||||
|
#define JPH_CPU_RISCV
|
||||||
|
#if __riscv_xlen == 64
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 64
|
||||||
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
|
#define JPH_DVECTOR_ALIGNMENT 32
|
||||||
|
#else
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 32
|
||||||
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
|
#define JPH_DVECTOR_ALIGNMENT 8
|
||||||
|
#endif
|
||||||
#elif defined(JPH_PLATFORM_WASM)
|
#elif defined(JPH_PLATFORM_WASM)
|
||||||
// WebAssembly CPU architecture
|
// WebAssembly CPU architecture
|
||||||
#define JPH_CPU_WASM
|
#define JPH_CPU_WASM
|
||||||
#define JPH_CPU_ADDRESS_BITS 32
|
#if defined(__wasm64__)
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 64
|
||||||
|
#else
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 32
|
||||||
|
#endif
|
||||||
#define JPH_VECTOR_ALIGNMENT 16
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
#define JPH_DVECTOR_ALIGNMENT 32
|
#define JPH_DVECTOR_ALIGNMENT 32
|
||||||
#define JPH_DISABLE_CUSTOM_ALLOCATOR
|
#ifdef __wasm_simd128__
|
||||||
|
#define JPH_USE_SSE
|
||||||
|
#define JPH_USE_SSE4_1
|
||||||
|
#define JPH_USE_SSE4_2
|
||||||
|
#endif
|
||||||
|
#elif defined(__powerpc__) || defined(__powerpc64__)
|
||||||
|
// PowerPC CPU architecture
|
||||||
|
#define JPH_CPU_PPC
|
||||||
|
#if defined(__powerpc64__)
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 64
|
||||||
|
#else
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 32
|
||||||
|
#endif
|
||||||
|
#ifdef _BIG_ENDIAN
|
||||||
|
#define JPH_CPU_BIG_ENDIAN
|
||||||
|
#endif
|
||||||
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
|
#define JPH_DVECTOR_ALIGNMENT 8
|
||||||
|
#elif defined(__loongarch__)
|
||||||
|
// LoongArch CPU architecture
|
||||||
|
#define JPH_CPU_LOONGARCH
|
||||||
|
#if defined(__loongarch64)
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 64
|
||||||
|
#else
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 32
|
||||||
|
#endif
|
||||||
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
|
#define JPH_DVECTOR_ALIGNMENT 8
|
||||||
|
#elif defined(__e2k__)
|
||||||
|
// E2K CPU architecture (MCST Elbrus 2000)
|
||||||
|
#define JPH_CPU_E2K
|
||||||
|
#define JPH_CPU_ADDRESS_BITS 64
|
||||||
|
#define JPH_VECTOR_ALIGNMENT 16
|
||||||
|
#define JPH_DVECTOR_ALIGNMENT 32
|
||||||
|
|
||||||
|
// Compiler flags on e2k arch determine CPU features
|
||||||
|
#if defined(__SSE__) && !defined(JPH_USE_SSE)
|
||||||
|
#define JPH_USE_SSE
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#error Unsupported CPU architecture
|
#error Unsupported CPU architecture
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// If this define is set, Jolt is compiled as a shared library
|
||||||
|
#ifdef JPH_SHARED_LIBRARY
|
||||||
|
#ifdef JPH_BUILD_SHARED_LIBRARY
|
||||||
|
// While building the shared library, we must export these symbols
|
||||||
|
#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW)
|
||||||
|
#define JPH_EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define JPH_EXPORT __attribute__ ((visibility ("default")))
|
||||||
|
#if defined(JPH_COMPILER_GCC)
|
||||||
|
// Prevents an issue with GCC attribute parsing (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69585)
|
||||||
|
#define JPH_EXPORT_GCC_BUG_WORKAROUND [[gnu::visibility("default")]]
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
// When linking against Jolt, we must import these symbols
|
||||||
|
#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW)
|
||||||
|
#define JPH_EXPORT __declspec(dllimport)
|
||||||
|
#else
|
||||||
|
#define JPH_EXPORT __attribute__ ((visibility ("default")))
|
||||||
|
#if defined(JPH_COMPILER_GCC)
|
||||||
|
// Prevents an issue with GCC attribute parsing (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69585)
|
||||||
|
#define JPH_EXPORT_GCC_BUG_WORKAROUND [[gnu::visibility("default")]]
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
// If the define is not set, we use static linking and symbols don't need to be imported or exported
|
||||||
|
#define JPH_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JPH_EXPORT_GCC_BUG_WORKAROUND
|
||||||
|
#define JPH_EXPORT_GCC_BUG_WORKAROUND JPH_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Macro used by the RTTI macros to not export a function
|
||||||
|
#define JPH_NO_EXPORT
|
||||||
|
|
||||||
// Pragmas to store / restore the warning state and to disable individual warnings
|
// Pragmas to store / restore the warning state and to disable individual warnings
|
||||||
#ifdef JPH_COMPILER_CLANG
|
#ifdef JPH_COMPILER_CLANG
|
||||||
#define JPH_PRAGMA(x) _Pragma(#x)
|
#define JPH_PRAGMA(x) _Pragma(#x)
|
||||||
#define JPH_SUPPRESS_WARNING_PUSH JPH_PRAGMA(clang diagnostic push)
|
#define JPH_SUPPRESS_WARNING_PUSH JPH_PRAGMA(clang diagnostic push)
|
||||||
#define JPH_SUPPRESS_WARNING_POP JPH_PRAGMA(clang diagnostic pop)
|
#define JPH_SUPPRESS_WARNING_POP JPH_PRAGMA(clang diagnostic pop)
|
||||||
#define JPH_CLANG_SUPPRESS_WARNING(w) JPH_PRAGMA(clang diagnostic ignored w)
|
#define JPH_CLANG_SUPPRESS_WARNING(w) JPH_PRAGMA(clang diagnostic ignored w)
|
||||||
|
#if __clang_major__ >= 13
|
||||||
|
#define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w) JPH_CLANG_SUPPRESS_WARNING(w)
|
||||||
|
#else
|
||||||
|
#define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w)
|
||||||
|
#endif
|
||||||
|
#if __clang_major__ >= 16
|
||||||
|
#define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w) JPH_CLANG_SUPPRESS_WARNING(w)
|
||||||
|
#else
|
||||||
|
#define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w)
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define JPH_CLANG_SUPPRESS_WARNING(w)
|
#define JPH_CLANG_SUPPRESS_WARNING(w)
|
||||||
|
#define JPH_CLANG_13_PLUS_SUPPRESS_WARNING(w)
|
||||||
|
#define JPH_CLANG_16_PLUS_SUPPRESS_WARNING(w)
|
||||||
#endif
|
#endif
|
||||||
#ifdef JPH_COMPILER_GCC
|
#ifdef JPH_COMPILER_GCC
|
||||||
#define JPH_PRAGMA(x) _Pragma(#x)
|
#define JPH_PRAGMA(x) _Pragma(#x)
|
||||||
|
|
@ -176,17 +347,24 @@
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wgnu-zero-variadic-macro-arguments") \
|
JPH_CLANG_SUPPRESS_WARNING("-Wgnu-zero-variadic-macro-arguments") \
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wdocumentation-unknown-command") \
|
JPH_CLANG_SUPPRESS_WARNING("-Wdocumentation-unknown-command") \
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wctad-maybe-unsupported") \
|
JPH_CLANG_SUPPRESS_WARNING("-Wctad-maybe-unsupported") \
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wdeprecated-copy") \
|
JPH_CLANG_SUPPRESS_WARNING("-Wswitch-default") \
|
||||||
|
JPH_CLANG_13_PLUS_SUPPRESS_WARNING("-Wdeprecated-copy") \
|
||||||
|
JPH_CLANG_13_PLUS_SUPPRESS_WARNING("-Wdeprecated-copy-with-dtor") \
|
||||||
|
JPH_CLANG_16_PLUS_SUPPRESS_WARNING("-Wunsafe-buffer-usage") \
|
||||||
JPH_IF_NOT_ANDROID(JPH_CLANG_SUPPRESS_WARNING("-Wimplicit-int-float-conversion")) \
|
JPH_IF_NOT_ANDROID(JPH_CLANG_SUPPRESS_WARNING("-Wimplicit-int-float-conversion")) \
|
||||||
\
|
\
|
||||||
JPH_GCC_SUPPRESS_WARNING("-Wcomment") \
|
JPH_GCC_SUPPRESS_WARNING("-Wcomment") \
|
||||||
JPH_GCC_SUPPRESS_WARNING("-Winvalid-offsetof") \
|
JPH_GCC_SUPPRESS_WARNING("-Winvalid-offsetof") \
|
||||||
JPH_GCC_SUPPRESS_WARNING("-Wclass-memaccess") \
|
JPH_GCC_SUPPRESS_WARNING("-Wclass-memaccess") \
|
||||||
|
JPH_GCC_SUPPRESS_WARNING("-Wpedantic") \
|
||||||
|
JPH_GCC_SUPPRESS_WARNING("-Wunused-parameter") \
|
||||||
|
JPH_GCC_SUPPRESS_WARNING("-Wmaybe-uninitialized") \
|
||||||
\
|
\
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4619) /* #pragma warning: there is no warning number 'XXXX' */ \
|
JPH_MSVC_SUPPRESS_WARNING(4619) /* #pragma warning: there is no warning number 'XXXX' */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4514) /* 'X' : unreferenced inline function has been removed */ \
|
JPH_MSVC_SUPPRESS_WARNING(4514) /* 'X' : unreferenced inline function has been removed */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4710) /* 'X' : function not inlined */ \
|
JPH_MSVC_SUPPRESS_WARNING(4710) /* 'X' : function not inlined */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4711) /* function 'X' selected for automatic inline expansion */ \
|
JPH_MSVC_SUPPRESS_WARNING(4711) /* function 'X' selected for automatic inline expansion */ \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4714) /* function 'X' marked as __forceinline not inlined */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4820) /* 'X': 'Y' bytes padding added after data member 'Z' */ \
|
JPH_MSVC_SUPPRESS_WARNING(4820) /* 'X': 'Y' bytes padding added after data member 'Z' */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4100) /* 'X' : unreferenced formal parameter */ \
|
JPH_MSVC_SUPPRESS_WARNING(4100) /* 'X' : unreferenced formal parameter */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4626) /* 'X' : assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted */ \
|
JPH_MSVC_SUPPRESS_WARNING(4626) /* 'X' : assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted */ \
|
||||||
|
|
@ -204,22 +382,26 @@
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5219) /* implicit conversion from 'X' to 'Y', possible loss of data */ \
|
JPH_MSVC_SUPPRESS_WARNING(5219) /* implicit conversion from 'X' to 'Y', possible loss of data */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4826) /* Conversion from 'X *' to 'JPH::uint64' is sign-extended. This may cause unexpected runtime behavior. (32-bit) */ \
|
JPH_MSVC_SUPPRESS_WARNING(4826) /* Conversion from 'X *' to 'JPH::uint64' is sign-extended. This may cause unexpected runtime behavior. (32-bit) */ \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5264) /* 'X': 'const' variable is not used */ \
|
JPH_MSVC_SUPPRESS_WARNING(5264) /* 'X': 'const' variable is not used */ \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4251) /* class 'X' needs to have DLL-interface to be used by clients of class 'Y' */ \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4738) /* storing 32-bit float result in memory, possible loss of performance */ \
|
||||||
JPH_MSVC2019_SUPPRESS_WARNING(5246) /* the initialization of a subobject should be wrapped in braces */
|
JPH_MSVC2019_SUPPRESS_WARNING(5246) /* the initialization of a subobject should be wrapped in braces */
|
||||||
|
|
||||||
// OS-specific includes
|
// OS-specific includes
|
||||||
#if defined(JPH_PLATFORM_WINDOWS)
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
#define JPH_BREAKPOINT __debugbreak()
|
#define JPH_BREAKPOINT __debugbreak()
|
||||||
#elif defined(JPH_PLATFORM_BLUE)
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
// Configuration for a popular game console.
|
// Configuration for a popular game console.
|
||||||
// This file is not distributed because it would violate an NDA.
|
// This file is not distributed because it would violate an NDA.
|
||||||
// Creating one should only be a couple of minutes of work if you have the documentation for the platform
|
// Creating one should only be a couple of minutes of work if you have the documentation for the platform
|
||||||
// (you only need to define JPH_BREAKPOINT, JPH_PLATFORM_BLUE_GET_TICKS and JPH_PLATFORM_BLUE_GET_TICK_FREQUENCY and include the right header).
|
// (you only need to define JPH_BREAKPOINT, JPH_PLATFORM_BLUE_GET_TICKS, JPH_PLATFORM_BLUE_MUTEX*, JPH_PLATFORM_BLUE_RWLOCK*, JPH_PLATFORM_BLUE_SEMAPHORE* and include the right header).
|
||||||
#include <Jolt/Core/PlatformBlue.h>
|
#include <Jolt/Core/PlatformBlue.h>
|
||||||
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
|
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) || defined(JPH_PLATFORM_BSD)
|
||||||
#if defined(JPH_CPU_X86)
|
#if defined(JPH_CPU_X86)
|
||||||
#define JPH_BREAKPOINT __asm volatile ("int $0x3")
|
#define JPH_BREAKPOINT __asm volatile ("int $0x3")
|
||||||
#elif defined(JPH_CPU_ARM)
|
#elif defined(JPH_CPU_ARM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_E2K) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
|
||||||
#define JPH_BREAKPOINT __builtin_trap()
|
#define JPH_BREAKPOINT __builtin_trap()
|
||||||
|
#else
|
||||||
|
#error Unknown CPU architecture
|
||||||
#endif
|
#endif
|
||||||
#elif defined(JPH_PLATFORM_WASM)
|
#elif defined(JPH_PLATFORM_WASM)
|
||||||
#define JPH_BREAKPOINT do { } while (false) // Not supported
|
#define JPH_BREAKPOINT do { } while (false) // Not supported
|
||||||
|
|
@ -227,9 +409,6 @@
|
||||||
#error Unknown platform
|
#error Unknown platform
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Crashes the application
|
|
||||||
#define JPH_CRASH do { int *ptr = nullptr; *ptr = 0; } while (false)
|
|
||||||
|
|
||||||
// Begin the JPH namespace
|
// Begin the JPH namespace
|
||||||
#define JPH_NAMESPACE_BEGIN \
|
#define JPH_NAMESPACE_BEGIN \
|
||||||
JPH_SUPPRESS_WARNING_PUSH \
|
JPH_SUPPRESS_WARNING_PUSH \
|
||||||
|
|
@ -244,29 +423,34 @@
|
||||||
// Suppress warnings generated by the standard template library
|
// Suppress warnings generated by the standard template library
|
||||||
#define JPH_SUPPRESS_WARNINGS_STD_BEGIN \
|
#define JPH_SUPPRESS_WARNINGS_STD_BEGIN \
|
||||||
JPH_SUPPRESS_WARNING_PUSH \
|
JPH_SUPPRESS_WARNING_PUSH \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4365) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4619) \
|
JPH_MSVC_SUPPRESS_WARNING(4619) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4710) \
|
JPH_MSVC_SUPPRESS_WARNING(4710) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4711) \
|
JPH_MSVC_SUPPRESS_WARNING(4711) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4820) \
|
JPH_MSVC_SUPPRESS_WARNING(4820) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(4514) \
|
JPH_MSVC_SUPPRESS_WARNING(4514) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5262) \
|
JPH_MSVC_SUPPRESS_WARNING(5262) \
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5264)
|
JPH_MSVC_SUPPRESS_WARNING(5264) \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4738) \
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(5045)
|
||||||
|
|
||||||
#define JPH_SUPPRESS_WARNINGS_STD_END \
|
#define JPH_SUPPRESS_WARNINGS_STD_END \
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_SUPPRESS_WARNING_POP
|
||||||
|
|
||||||
// Standard C++ includes
|
// Standard C++ includes
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
#include <vector>
|
#include <float.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
#include <cstdint>
|
||||||
#include <limits.h>
|
#ifdef JPH_COMPILER_MSVC
|
||||||
#include <float.h>
|
#include <malloc.h> // for alloca
|
||||||
#include <string.h>
|
#endif
|
||||||
#if defined(JPH_USE_SSE)
|
#if defined(JPH_USE_SSE)
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
#elif defined(JPH_USE_NEON)
|
#elif defined(JPH_USE_NEON)
|
||||||
|
|
@ -277,11 +461,11 @@ JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// Commonly used STL types
|
// Commonly used STL types
|
||||||
using std::pair;
|
|
||||||
using std::min;
|
using std::min;
|
||||||
using std::max;
|
using std::max;
|
||||||
using std::abs;
|
using std::abs;
|
||||||
|
|
@ -291,26 +475,20 @@ using std::floor;
|
||||||
using std::trunc;
|
using std::trunc;
|
||||||
using std::round;
|
using std::round;
|
||||||
using std::fmod;
|
using std::fmod;
|
||||||
using std::swap;
|
|
||||||
using std::size;
|
|
||||||
using std::string;
|
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
using std::function;
|
using std::function;
|
||||||
using std::numeric_limits;
|
using std::numeric_limits;
|
||||||
using std::isfinite;
|
using std::isfinite;
|
||||||
using std::isnan;
|
using std::isnan;
|
||||||
using std::is_trivial;
|
|
||||||
using std::is_trivially_constructible;
|
|
||||||
using std::is_trivially_destructible;
|
|
||||||
using std::ostream;
|
using std::ostream;
|
||||||
using std::istream;
|
using std::istream;
|
||||||
|
|
||||||
// Standard types
|
// Standard types
|
||||||
using uint = unsigned int;
|
using uint = unsigned int;
|
||||||
using uint8 = uint8_t;
|
using uint8 = std::uint8_t;
|
||||||
using uint16 = uint16_t;
|
using uint16 = std::uint16_t;
|
||||||
using uint32 = uint32_t;
|
using uint32 = std::uint32_t;
|
||||||
using uint64 = uint64_t;
|
using uint64 = std::uint64_t;
|
||||||
|
|
||||||
// Assert sizes of types
|
// Assert sizes of types
|
||||||
static_assert(sizeof(uint) >= 4, "Invalid size of uint");
|
static_assert(sizeof(uint) >= 4, "Invalid size of uint");
|
||||||
|
|
@ -320,9 +498,24 @@ static_assert(sizeof(uint32) == 4, "Invalid size of uint32");
|
||||||
static_assert(sizeof(uint64) == 8, "Invalid size of uint64");
|
static_assert(sizeof(uint64) == 8, "Invalid size of uint64");
|
||||||
static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid size of pointer" );
|
static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid size of pointer" );
|
||||||
|
|
||||||
|
// Determine if we want extra debugging code to be active
|
||||||
|
#if !defined(NDEBUG) && !defined(JPH_NO_DEBUG)
|
||||||
|
#define JPH_DEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
// Define inline macro
|
// Define inline macro
|
||||||
#if defined(JPH_COMPILER_CLANG) || defined(JPH_COMPILER_GCC)
|
#if defined(JPH_NO_FORCE_INLINE)
|
||||||
|
#define JPH_INLINE inline
|
||||||
|
#elif defined(JPH_COMPILER_CLANG)
|
||||||
#define JPH_INLINE __inline__ __attribute__((always_inline))
|
#define JPH_INLINE __inline__ __attribute__((always_inline))
|
||||||
|
#elif defined(JPH_COMPILER_GCC)
|
||||||
|
// On gcc 14 using always_inline in debug mode causes error: "inlining failed in call to 'always_inline' 'XXX': function not considered for inlining"
|
||||||
|
// See: https://github.com/jrouwe/JoltPhysics/issues/1096
|
||||||
|
#if __GNUC__ >= 14 && defined(JPH_DEBUG)
|
||||||
|
#define JPH_INLINE inline
|
||||||
|
#else
|
||||||
|
#define JPH_INLINE __inline__ __attribute__((always_inline))
|
||||||
|
#endif
|
||||||
#elif defined(JPH_COMPILER_MSVC)
|
#elif defined(JPH_COMPILER_MSVC)
|
||||||
#define JPH_INLINE __forceinline
|
#define JPH_INLINE __forceinline
|
||||||
#else
|
#else
|
||||||
|
|
@ -345,9 +538,9 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si
|
||||||
|
|
||||||
// Stack allocation
|
// Stack allocation
|
||||||
#define JPH_STACK_ALLOC(n) alloca(n)
|
#define JPH_STACK_ALLOC(n) alloca(n)
|
||||||
|
|
||||||
// Shorthand for #ifdef _DEBUG / #endif
|
// Shorthand for #ifdef JPH_DEBUG / #endif
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
#define JPH_IF_DEBUG(...) __VA_ARGS__
|
#define JPH_IF_DEBUG(...) __VA_ARGS__
|
||||||
#define JPH_IF_NOT_DEBUG(...)
|
#define JPH_IF_NOT_DEBUG(...)
|
||||||
#else
|
#else
|
||||||
|
|
@ -391,12 +584,18 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si
|
||||||
#define JPH_PRECISE_MATH_ON
|
#define JPH_PRECISE_MATH_ON
|
||||||
#define JPH_PRECISE_MATH_OFF
|
#define JPH_PRECISE_MATH_OFF
|
||||||
#elif defined(JPH_COMPILER_CLANG)
|
#elif defined(JPH_COMPILER_CLANG)
|
||||||
// We compile without -ffast-math because it cannot be turned off for a single compilation unit
|
// We compile without -ffast-math because pragma float_control(precise, on) doesn't seem to actually negate all of the -ffast-math effects and causes the unit tests to fail (even if the pragma is added to all files)
|
||||||
// On clang 14 and later we can turn off float contraction through a pragma, so if FMA is on we can disable it through this macro
|
// On clang 14 and later we can turn off float contraction through a pragma (before it was buggy), so if FMA is on we can disable it through this macro
|
||||||
#if __clang_major__ >= 14 && defined(JPH_USE_FMADD)
|
#if (defined(JPH_CPU_ARM) && !defined(JPH_PLATFORM_ANDROID) && __clang_major__ >= 16) || (defined(JPH_CPU_X86) && __clang_major__ >= 14)
|
||||||
#define JPH_PRECISE_MATH_ON \
|
#define JPH_PRECISE_MATH_ON \
|
||||||
|
_Pragma("float_control(precise, on, push)") \
|
||||||
_Pragma("clang fp contract(off)")
|
_Pragma("clang fp contract(off)")
|
||||||
#define JPH_PRECISE_MATH_OFF \
|
#define JPH_PRECISE_MATH_OFF \
|
||||||
|
_Pragma("float_control(pop)")
|
||||||
|
#elif __clang_major__ >= 14 && (defined(JPH_USE_FMADD) || defined(FP_FAST_FMA))
|
||||||
|
#define JPH_PRECISE_MATH_ON \
|
||||||
|
_Pragma("clang fp contract(off)")
|
||||||
|
#define JPH_PRECISE_MATH_OFF \
|
||||||
_Pragma("clang fp contract(on)")
|
_Pragma("clang fp contract(on)")
|
||||||
#else
|
#else
|
||||||
#define JPH_PRECISE_MATH_ON
|
#define JPH_PRECISE_MATH_ON
|
||||||
|
|
@ -404,14 +603,32 @@ static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid si
|
||||||
#endif
|
#endif
|
||||||
#elif defined(JPH_COMPILER_MSVC)
|
#elif defined(JPH_COMPILER_MSVC)
|
||||||
// Unfortunately there is no way to push the state of fp_contract, so we have to assume it was turned on before JPH_PRECISE_MATH_ON
|
// Unfortunately there is no way to push the state of fp_contract, so we have to assume it was turned on before JPH_PRECISE_MATH_ON
|
||||||
#define JPH_PRECISE_MATH_ON \
|
#define JPH_PRECISE_MATH_ON \
|
||||||
__pragma(float_control(precise, on, push)) \
|
__pragma(float_control(precise, on, push)) \
|
||||||
__pragma(fp_contract(off))
|
__pragma(fp_contract(off))
|
||||||
#define JPH_PRECISE_MATH_OFF \
|
#define JPH_PRECISE_MATH_OFF \
|
||||||
__pragma(fp_contract(on)) \
|
__pragma(fp_contract(on)) \
|
||||||
__pragma(float_control(pop))
|
__pragma(float_control(pop))
|
||||||
#else
|
#else
|
||||||
#error Undefined
|
#error Undefined
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Check if Thread Sanitizer is enabled
|
||||||
|
#ifdef __has_feature
|
||||||
|
#if __has_feature(thread_sanitizer)
|
||||||
|
#define JPH_TSAN_ENABLED
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef __SANITIZE_THREAD__
|
||||||
|
#define JPH_TSAN_ENABLED
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Attribute to disable Thread Sanitizer for a particular function
|
||||||
|
#ifdef JPH_TSAN_ENABLED
|
||||||
|
#define JPH_TSAN_NO_SANITIZE __attribute__((no_sanitize("thread")))
|
||||||
|
#else
|
||||||
|
#define JPH_TSAN_NO_SANITIZE
|
||||||
|
#endif
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
#ifdef JPH_USE_SSE
|
#if defined(JPH_CPU_WASM)
|
||||||
|
|
||||||
|
// Not supported
|
||||||
|
|
||||||
|
#elif defined(JPH_USE_SSE)
|
||||||
|
|
||||||
/// Helper class that needs to be put on the stack to update the state of the floating point control word.
|
/// Helper class that needs to be put on the stack to update the state of the floating point control word.
|
||||||
/// This state is kept per thread.
|
/// This state is kept per thread.
|
||||||
|
|
@ -28,7 +32,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint mPrevState;
|
uint mPrevState;
|
||||||
};
|
};
|
||||||
|
|
||||||
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
||||||
|
|
@ -71,11 +75,11 @@ public:
|
||||||
FPControlWord()
|
FPControlWord()
|
||||||
{
|
{
|
||||||
uint64 val;
|
uint64 val;
|
||||||
asm volatile("mrs %0, fpcr" : "=r" (val));
|
asm volatile("mrs %0, fpcr" : "=r" (val));
|
||||||
mPrevState = val;
|
mPrevState = val;
|
||||||
val &= ~Mask;
|
val &= ~Mask;
|
||||||
val |= Value;
|
val |= Value;
|
||||||
asm volatile("msr fpcr, %0" : /* no output */ : "r" (val));
|
asm volatile("msr fpcr, %0" : /* no output */ : "r" (val));
|
||||||
}
|
}
|
||||||
|
|
||||||
~FPControlWord()
|
~FPControlWord()
|
||||||
|
|
@ -122,9 +126,13 @@ private:
|
||||||
uint32 mPrevState;
|
uint32 mPrevState;
|
||||||
};
|
};
|
||||||
|
|
||||||
#elif defined(JPH_CPU_WASM)
|
#elif defined(JPH_CPU_RISCV)
|
||||||
|
|
||||||
// Not supported
|
// RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions.
|
||||||
|
|
||||||
|
#elif defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
|
||||||
|
|
||||||
|
// Not implemented right now
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,18 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
|
#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
|
||||||
|
|
||||||
#if defined(JPH_USE_SSE)
|
#if defined(JPH_CPU_WASM)
|
||||||
|
|
||||||
/// Enable floating point divide by zero exception and exceptions on invalid numbers
|
// Not supported
|
||||||
class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID> { };
|
class FPExceptionsEnable { };
|
||||||
|
class FPExceptionDisableInvalid { };
|
||||||
|
class FPExceptionDisableDivByZero { };
|
||||||
|
class FPExceptionDisableOverflow { };
|
||||||
|
|
||||||
|
#elif defined(JPH_USE_SSE)
|
||||||
|
|
||||||
|
/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
|
||||||
|
class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID | _MM_MASK_OVERFLOW> { };
|
||||||
|
|
||||||
/// Disable invalid floating point value exceptions
|
/// Disable invalid floating point value exceptions
|
||||||
class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MASK_INVALID> { };
|
class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MASK_INVALID> { };
|
||||||
|
|
@ -21,10 +29,13 @@ class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MAS
|
||||||
/// Disable division by zero floating point exceptions
|
/// Disable division by zero floating point exceptions
|
||||||
class FPExceptionDisableDivByZero : public FPControlWord<_MM_MASK_DIV_ZERO, _MM_MASK_DIV_ZERO> { };
|
class FPExceptionDisableDivByZero : public FPControlWord<_MM_MASK_DIV_ZERO, _MM_MASK_DIV_ZERO> { };
|
||||||
|
|
||||||
|
/// Disable floating point overflow exceptions
|
||||||
|
class FPExceptionDisableOverflow : public FPControlWord<_MM_MASK_OVERFLOW, _MM_MASK_OVERFLOW> { };
|
||||||
|
|
||||||
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
||||||
|
|
||||||
/// Enable floating point divide by zero exception and exceptions on invalid numbers
|
/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
|
||||||
class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE> { };
|
class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE | _EM_OVERFLOW> { };
|
||||||
|
|
||||||
/// Disable invalid floating point value exceptions
|
/// Disable invalid floating point value exceptions
|
||||||
class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> { };
|
class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> { };
|
||||||
|
|
@ -32,6 +43,9 @@ class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID>
|
||||||
/// Disable division by zero floating point exceptions
|
/// Disable division by zero floating point exceptions
|
||||||
class FPExceptionDisableDivByZero : public FPControlWord<_EM_ZERODIVIDE, _EM_ZERODIVIDE> { };
|
class FPExceptionDisableDivByZero : public FPControlWord<_EM_ZERODIVIDE, _EM_ZERODIVIDE> { };
|
||||||
|
|
||||||
|
/// Disable floating point overflow exceptions
|
||||||
|
class FPExceptionDisableOverflow : public FPControlWord<_EM_OVERFLOW, _EM_OVERFLOW> { };
|
||||||
|
|
||||||
#elif defined(JPH_CPU_ARM)
|
#elif defined(JPH_CPU_ARM)
|
||||||
|
|
||||||
/// Invalid operation exception bit
|
/// Invalid operation exception bit
|
||||||
|
|
@ -40,8 +54,11 @@ static constexpr uint64 FP_IOE = 1 << 8;
|
||||||
/// Enable divide by zero exception bit
|
/// Enable divide by zero exception bit
|
||||||
static constexpr uint64 FP_DZE = 1 << 9;
|
static constexpr uint64 FP_DZE = 1 << 9;
|
||||||
|
|
||||||
/// Enable floating point divide by zero exception and exceptions on invalid numbers
|
/// Enable floating point overflow bit
|
||||||
class FPExceptionsEnable : public FPControlWord<FP_IOE | FP_DZE, FP_IOE | FP_DZE> { };
|
static constexpr uint64 FP_OFE = 1 << 10;
|
||||||
|
|
||||||
|
/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
|
||||||
|
class FPExceptionsEnable : public FPControlWord<FP_IOE | FP_DZE | FP_OFE, FP_IOE | FP_DZE | FP_OFE> { };
|
||||||
|
|
||||||
/// Disable invalid floating point value exceptions
|
/// Disable invalid floating point value exceptions
|
||||||
class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
|
class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
|
||||||
|
|
@ -49,12 +66,16 @@ class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
|
||||||
/// Disable division by zero floating point exceptions
|
/// Disable division by zero floating point exceptions
|
||||||
class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
|
class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
|
||||||
|
|
||||||
#elif defined(JPH_CPU_WASM)
|
/// Disable floating point overflow exceptions
|
||||||
|
class FPExceptionDisableOverflow : public FPControlWord<0, FP_OFE> { };
|
||||||
|
|
||||||
// Not supported
|
#elif defined(JPH_CPU_RISCV)
|
||||||
class FPExceptionsEnable { };
|
|
||||||
class FPExceptionDisableInvalid { };
|
#error "RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled."
|
||||||
class FPExceptionDisableDivByZero { };
|
|
||||||
|
#elif defined(JPH_CPU_PPC)
|
||||||
|
|
||||||
|
#error PowerPC floating point exception handling to be implemented. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled.
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
@ -68,6 +89,7 @@ class FPExceptionDisableDivByZero { };
|
||||||
class FPExceptionsEnable { };
|
class FPExceptionsEnable { };
|
||||||
class FPExceptionDisableInvalid { };
|
class FPExceptionDisableInvalid { };
|
||||||
class FPExceptionDisableDivByZero { };
|
class FPExceptionDisableDivByZero { };
|
||||||
|
class FPExceptionDisableOverflow { };
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,12 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
#if defined(JPH_USE_SSE)
|
#if defined(JPH_CPU_WASM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
|
||||||
|
|
||||||
|
// Not supported
|
||||||
|
class FPFlushDenormals { };
|
||||||
|
|
||||||
|
#elif defined(JPH_USE_SSE)
|
||||||
|
|
||||||
/// Helper class that needs to be put on the stack to enable flushing denormals to zero
|
/// Helper class that needs to be put on the stack to enable flushing denormals to zero
|
||||||
/// This can make floating point operations much faster when working with very small numbers
|
/// This can make floating point operations much faster when working with very small numbers
|
||||||
|
|
@ -16,6 +21,8 @@ class FPFlushDenormals : public FPControlWord<_MM_FLUSH_ZERO_ON, _MM_FLUSH_ZERO_
|
||||||
|
|
||||||
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
#elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
|
||||||
|
|
||||||
|
/// Helper class that needs to be put on the stack to enable flushing denormals to zero
|
||||||
|
/// This can make floating point operations much faster when working with very small numbers
|
||||||
class FPFlushDenormals : public FPControlWord<_DN_FLUSH, _MCW_DN> { };
|
class FPFlushDenormals : public FPControlWord<_DN_FLUSH, _MCW_DN> { };
|
||||||
|
|
||||||
#elif defined(JPH_CPU_ARM)
|
#elif defined(JPH_CPU_ARM)
|
||||||
|
|
@ -27,11 +34,6 @@ static constexpr uint64 FP_FZ = 1 << 24;
|
||||||
/// This can make floating point operations much faster when working with very small numbers
|
/// This can make floating point operations much faster when working with very small numbers
|
||||||
class FPFlushDenormals : public FPControlWord<FP_FZ, FP_FZ> { };
|
class FPFlushDenormals : public FPControlWord<FP_FZ, FP_FZ> { };
|
||||||
|
|
||||||
#elif defined(JPH_CPU_WASM)
|
|
||||||
|
|
||||||
// Not supported
|
|
||||||
class FPFlushDenormals { };
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#error Unsupported CPU architecture
|
#error Unsupported CPU architecture
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,25 @@ JPH_NAMESPACE_BEGIN
|
||||||
Factory *Factory::sInstance = nullptr;
|
Factory *Factory::sInstance = nullptr;
|
||||||
|
|
||||||
void *Factory::CreateObject(const char *inName)
|
void *Factory::CreateObject(const char *inName)
|
||||||
{
|
{
|
||||||
const RTTI *ci = Find(inName);
|
const RTTI *ci = Find(inName);
|
||||||
return ci != nullptr? ci->CreateObject() : nullptr;
|
return ci != nullptr? ci->CreateObject() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RTTI *Factory::Find(const char *inName)
|
const RTTI *Factory::Find(const char *inName)
|
||||||
{
|
{
|
||||||
ClassNameMap::iterator c = mClassNameMap.find(inName);
|
ClassNameMap::iterator c = mClassNameMap.find(inName);
|
||||||
return c != mClassNameMap.end()? c->second : nullptr;
|
return c != mClassNameMap.end()? c->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RTTI *Factory::Find(uint32 inHash)
|
const RTTI *Factory::Find(uint32 inHash)
|
||||||
{
|
{
|
||||||
ClassHashMap::iterator c = mClassHashMap.find(inHash);
|
ClassHashMap::iterator c = mClassHashMap.find(inHash);
|
||||||
return c != mClassHashMap.end()? c->second : nullptr;
|
return c != mClassHashMap.end()? c->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Factory::Register(const RTTI *inRTTI)
|
bool Factory::Register(const RTTI *inRTTI)
|
||||||
{
|
{
|
||||||
// Check if we already know the type
|
// Check if we already know the type
|
||||||
if (Find(inRTTI->GetName()) != nullptr)
|
if (Find(inRTTI->GetName()) != nullptr)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -49,6 +49,7 @@ bool Factory::Register(const RTTI *inRTTI)
|
||||||
if (!Register(inRTTI->GetBaseClass(i)))
|
if (!Register(inRTTI->GetBaseClass(i)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
// Register attribute classes
|
// Register attribute classes
|
||||||
for (int i = 0; i < inRTTI->GetAttributeCount(); ++i)
|
for (int i = 0; i < inRTTI->GetAttributeCount(); ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -56,12 +57,16 @@ bool Factory::Register(const RTTI *inRTTI)
|
||||||
if (rtti != nullptr && !Register(rtti))
|
if (rtti != nullptr && !Register(rtti))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif // JPH_OBJECT_STREAM
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Factory::Register(const RTTI **inRTTIs, uint inNumber)
|
bool Factory::Register(const RTTI **inRTTIs, uint inNumber)
|
||||||
{
|
{
|
||||||
|
mClassHashMap.reserve(mClassHashMap.size() + inNumber);
|
||||||
|
mClassNameMap.reserve(mClassNameMap.size() + inNumber);
|
||||||
|
|
||||||
for (const RTTI **rtti = inRTTIs; rtti < inRTTIs + inNumber; ++rtti)
|
for (const RTTI **rtti = inRTTIs; rtti < inRTTIs + inNumber; ++rtti)
|
||||||
if (!Register(*rtti))
|
if (!Register(*rtti))
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Factory, to create RTTI objects
|
/// This class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
|
||||||
class Factory
|
class JPH_EXPORT Factory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,6 @@ private:
|
||||||
const ObjectStorage & GetStorage(uint32 inObjectIndex) const { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; }
|
const ObjectStorage & GetStorage(uint32 inObjectIndex) const { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; }
|
||||||
ObjectStorage & GetStorage(uint32 inObjectIndex) { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; }
|
ObjectStorage & GetStorage(uint32 inObjectIndex) { return mPages[inObjectIndex >> mPageShift][inObjectIndex & mObjectMask]; }
|
||||||
|
|
||||||
/// Number of objects that we currently have in the free list / new pages
|
|
||||||
#ifdef JPH_ENABLE_ASSERTS
|
|
||||||
atomic<uint32> mNumFreeObjects;
|
|
||||||
#endif // JPH_ENABLE_ASSERTS
|
|
||||||
|
|
||||||
/// Simple counter that makes the first free object pointer update with every CAS so that we don't suffer from the ABA problem
|
|
||||||
atomic<uint32> mAllocationTag;
|
|
||||||
|
|
||||||
/// Index of first free object, the first 32 bits of an object are used to point to the next free object
|
|
||||||
atomic<uint64> mFirstFreeObjectAndTag;
|
|
||||||
|
|
||||||
/// Size (in objects) of a single page
|
/// Size (in objects) of a single page
|
||||||
uint32 mPageSize;
|
uint32 mPageSize;
|
||||||
|
|
||||||
|
|
@ -60,14 +49,27 @@ private:
|
||||||
/// Total number of objects that have been allocated
|
/// Total number of objects that have been allocated
|
||||||
uint32 mNumObjectsAllocated;
|
uint32 mNumObjectsAllocated;
|
||||||
|
|
||||||
/// The first free object to use when the free list is empty (may need to allocate a new page)
|
|
||||||
atomic<uint32> mFirstFreeObjectInNewPage;
|
|
||||||
|
|
||||||
/// Array of pages of objects
|
/// Array of pages of objects
|
||||||
ObjectStorage ** mPages = nullptr;
|
ObjectStorage ** mPages = nullptr;
|
||||||
|
|
||||||
/// Mutex that is used to allocate a new page if the storage runs out
|
/// Mutex that is used to allocate a new page if the storage runs out
|
||||||
Mutex mPageMutex;
|
/// This variable is aligned to the cache line to prevent false sharing with
|
||||||
|
/// the constants used to index into the list via `Get()`.
|
||||||
|
alignas(JPH_CACHE_LINE_SIZE) Mutex mPageMutex;
|
||||||
|
|
||||||
|
/// Number of objects that we currently have in the free list / new pages
|
||||||
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
|
atomic<uint32> mNumFreeObjects;
|
||||||
|
#endif // JPH_ENABLE_ASSERTS
|
||||||
|
|
||||||
|
/// Simple counter that makes the first free object pointer update with every CAS so that we don't suffer from the ABA problem
|
||||||
|
atomic<uint32> mAllocationTag;
|
||||||
|
|
||||||
|
/// Index of first free object, the first 32 bits of an object are used to point to the next free object
|
||||||
|
atomic<uint64> mFirstFreeObjectAndTag;
|
||||||
|
|
||||||
|
/// The first free object to use when the free list is empty (may need to allocate a new page)
|
||||||
|
atomic<uint32> mFirstFreeObjectInNewPage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Invalid index
|
/// Invalid index
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ uint32 FixedSizeFreeList<Object>::ConstructObject(Parameters &&... inParameters)
|
||||||
// Allocation successful
|
// Allocation successful
|
||||||
JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
|
JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
|
||||||
ObjectStorage &storage = GetStorage(first_free);
|
ObjectStorage &storage = GetStorage(first_free);
|
||||||
::new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
|
new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
|
||||||
storage.mNextFreeObject.store(first_free, memory_order_release);
|
storage.mNextFreeObject.store(first_free, memory_order_release);
|
||||||
return first_free;
|
return first_free;
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +97,7 @@ uint32 FixedSizeFreeList<Object>::ConstructObject(Parameters &&... inParameters)
|
||||||
// Allocation successful
|
// Allocation successful
|
||||||
JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
|
JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
|
||||||
ObjectStorage &storage = GetStorage(first_free);
|
ObjectStorage &storage = GetStorage(first_free);
|
||||||
::new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
|
new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
|
||||||
storage.mNextFreeObject.store(first_free, memory_order_release);
|
storage.mNextFreeObject.store(first_free, memory_order_release);
|
||||||
return first_free;
|
return first_free;
|
||||||
}
|
}
|
||||||
|
|
@ -108,9 +108,13 @@ uint32 FixedSizeFreeList<Object>::ConstructObject(Parameters &&... inParameters)
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
void FixedSizeFreeList<Object>::AddObjectToBatch(Batch &ioBatch, uint32 inObjectIndex)
|
void FixedSizeFreeList<Object>::AddObjectToBatch(Batch &ioBatch, uint32 inObjectIndex)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(GetStorage(inObjectIndex).mNextFreeObject.load(memory_order_relaxed) == inObjectIndex, "Trying to add a object to the batch that is already in a free list");
|
|
||||||
JPH_ASSERT(ioBatch.mNumObjects != uint32(-1), "Trying to reuse a batch that has already been freed");
|
JPH_ASSERT(ioBatch.mNumObjects != uint32(-1), "Trying to reuse a batch that has already been freed");
|
||||||
|
|
||||||
|
// Reset next index
|
||||||
|
atomic<uint32> &next_free_object = GetStorage(inObjectIndex).mNextFreeObject;
|
||||||
|
JPH_ASSERT(next_free_object.load(memory_order_relaxed) == inObjectIndex, "Trying to add a object to the batch that is already in a free list");
|
||||||
|
next_free_object.store(cInvalidObjectIndex, memory_order_release);
|
||||||
|
|
||||||
// Link object in batch to free
|
// Link object in batch to free
|
||||||
if (ioBatch.mFirstObjectIndex == cInvalidObjectIndex)
|
if (ioBatch.mFirstObjectIndex == cInvalidObjectIndex)
|
||||||
ioBatch.mFirstObjectIndex = inObjectIndex;
|
ioBatch.mFirstObjectIndex = inObjectIndex;
|
||||||
|
|
@ -126,7 +130,7 @@ void FixedSizeFreeList<Object>::DestructObjectBatch(Batch &ioBatch)
|
||||||
if (ioBatch.mFirstObjectIndex != cInvalidObjectIndex)
|
if (ioBatch.mFirstObjectIndex != cInvalidObjectIndex)
|
||||||
{
|
{
|
||||||
// Call destructors
|
// Call destructors
|
||||||
if constexpr (!is_trivially_destructible<Object>())
|
if constexpr (!std::is_trivially_destructible<Object>())
|
||||||
{
|
{
|
||||||
uint32 object_idx = ioBatch.mFirstObjectIndex;
|
uint32 object_idx = ioBatch.mFirstObjectIndex;
|
||||||
do
|
do
|
||||||
|
|
@ -161,7 +165,7 @@ void FixedSizeFreeList<Object>::DestructObjectBatch(Batch &ioBatch)
|
||||||
// Mark the batch as freed
|
// Mark the batch as freed
|
||||||
#ifdef JPH_ENABLE_ASSERTS
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
ioBatch.mNumObjects = uint32(-1);
|
ioBatch.mNumObjects = uint32(-1);
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +178,7 @@ void FixedSizeFreeList<Object>::DestructObject(uint32 inObjectIndex)
|
||||||
JPH_ASSERT(inObjectIndex != cInvalidObjectIndex);
|
JPH_ASSERT(inObjectIndex != cInvalidObjectIndex);
|
||||||
|
|
||||||
// Call destructor
|
// Call destructor
|
||||||
ObjectStorage &storage = GetStorage(inObjectIndex);
|
ObjectStorage &storage = GetStorage(inObjectIndex);
|
||||||
storage.mObject.~Object();
|
storage.mObject.~Object();
|
||||||
|
|
||||||
// Add to object free list
|
// Add to object free list
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,21 @@ inline uint64 HashBytes(const void *inData, uint inSize, uint64 inSeed = 0xcbf29
|
||||||
uint64 hash = inSeed;
|
uint64 hash = inSeed;
|
||||||
for (const uint8 *data = reinterpret_cast<const uint8 *>(inData); data < reinterpret_cast<const uint8 *>(inData) + inSize; ++data)
|
for (const uint8 *data = reinterpret_cast<const uint8 *>(inData); data < reinterpret_cast<const uint8 *>(inData) + inSize; ++data)
|
||||||
{
|
{
|
||||||
hash = hash ^ uint64(*data);
|
hash ^= uint64(*data);
|
||||||
hash = hash * 0x100000001b3UL;
|
hash *= 0x100000001b3UL;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the FNV-1a hash of inString.
|
||||||
|
/// @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||||
|
constexpr uint64 HashString(const char *inString, uint64 inSeed = 0xcbf29ce484222325UL)
|
||||||
|
{
|
||||||
|
uint64 hash = inSeed;
|
||||||
|
for (const char *c = inString; *c != 0; ++c)
|
||||||
|
{
|
||||||
|
hash ^= uint64(*c);
|
||||||
|
hash *= 0x100000001b3UL;
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
@ -40,58 +53,182 @@ inline uint64 Hash64(uint64 inValue)
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Helper function that hashes a single value into ioSeed
|
/// Fallback hash function that calls T::GetHash()
|
||||||
/// Taken from: https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
|
template <class T>
|
||||||
template <typename T>
|
struct Hash
|
||||||
inline void HashCombineHelper(size_t &ioSeed, const T &inValue)
|
|
||||||
{
|
{
|
||||||
std::hash<T> hasher;
|
uint64 operator () (const T &inValue) const
|
||||||
ioSeed ^= hasher(inValue) + 0x9e3779b9 + (ioSeed << 6) + (ioSeed >> 2);
|
{
|
||||||
|
return inValue.GetHash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A hash function for floats
|
||||||
|
template <>
|
||||||
|
struct Hash<float>
|
||||||
|
{
|
||||||
|
uint64 operator () (float inValue) const
|
||||||
|
{
|
||||||
|
float value = inValue == 0.0f? 0.0f : inValue; // Convert -0.0f to 0.0f
|
||||||
|
return HashBytes(&value, sizeof(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A hash function for doubles
|
||||||
|
template <>
|
||||||
|
struct Hash<double>
|
||||||
|
{
|
||||||
|
uint64 operator () (double inValue) const
|
||||||
|
{
|
||||||
|
double value = inValue == 0.0? 0.0 : inValue; // Convert -0.0 to 0.0
|
||||||
|
return HashBytes(&value, sizeof(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A hash function for character pointers
|
||||||
|
template <>
|
||||||
|
struct Hash<const char *>
|
||||||
|
{
|
||||||
|
uint64 operator () (const char *inValue) const
|
||||||
|
{
|
||||||
|
return HashString(inValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A hash function for std::string_view
|
||||||
|
template <>
|
||||||
|
struct Hash<std::string_view>
|
||||||
|
{
|
||||||
|
uint64 operator () (const std::string_view &inValue) const
|
||||||
|
{
|
||||||
|
return HashBytes(inValue.data(), uint(inValue.size()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A hash function for String
|
||||||
|
template <>
|
||||||
|
struct Hash<String>
|
||||||
|
{
|
||||||
|
uint64 operator () (const String &inValue) const
|
||||||
|
{
|
||||||
|
return HashBytes(inValue.data(), uint(inValue.size()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A fallback function for generic pointers
|
||||||
|
template <class T>
|
||||||
|
struct Hash<T *>
|
||||||
|
{
|
||||||
|
uint64 operator () (T *inValue) const
|
||||||
|
{
|
||||||
|
return HashBytes(&inValue, sizeof(inValue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper macro to define a hash function for trivial types
|
||||||
|
#define JPH_DEFINE_TRIVIAL_HASH(type) \
|
||||||
|
template <> \
|
||||||
|
struct Hash<type> \
|
||||||
|
{ \
|
||||||
|
uint64 operator () (const type &inValue) const \
|
||||||
|
{ \
|
||||||
|
return HashBytes(&inValue, sizeof(inValue)); \
|
||||||
|
} \
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Commonly used types
|
||||||
|
JPH_DEFINE_TRIVIAL_HASH(char)
|
||||||
|
JPH_DEFINE_TRIVIAL_HASH(int)
|
||||||
|
JPH_DEFINE_TRIVIAL_HASH(uint32)
|
||||||
|
JPH_DEFINE_TRIVIAL_HASH(uint64)
|
||||||
|
|
||||||
|
/// Helper function that hashes a single value into ioSeed
|
||||||
|
/// Based on https://github.com/jonmaiga/mx3 by Jon Maiga
|
||||||
|
template <typename T>
|
||||||
|
inline void HashCombine(uint64 &ioSeed, const T &inValue)
|
||||||
|
{
|
||||||
|
constexpr uint64 c = 0xbea225f9eb34556dUL;
|
||||||
|
|
||||||
|
uint64 h = ioSeed;
|
||||||
|
uint64 x = Hash<T> { } (inValue);
|
||||||
|
|
||||||
|
// See: https://github.com/jonmaiga/mx3/blob/master/mx3.h
|
||||||
|
// mix_stream(h, x)
|
||||||
|
x *= c;
|
||||||
|
x ^= x >> 39;
|
||||||
|
h += x * c;
|
||||||
|
h *= c;
|
||||||
|
|
||||||
|
// mix(h)
|
||||||
|
h ^= h >> 32;
|
||||||
|
h *= c;
|
||||||
|
h ^= h >> 29;
|
||||||
|
h *= c;
|
||||||
|
h ^= h >> 32;
|
||||||
|
h *= c;
|
||||||
|
h ^= h >> 29;
|
||||||
|
|
||||||
|
ioSeed = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hash combiner to use a custom struct in an unordered map or set
|
/// Hash combiner to use a custom struct in an unordered map or set
|
||||||
///
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
///
|
///
|
||||||
/// struct SomeHashKey
|
/// struct SomeHashKey
|
||||||
/// {
|
/// {
|
||||||
/// std::string key1;
|
/// std::string key1;
|
||||||
/// std::string key2;
|
/// std::string key2;
|
||||||
/// bool key3;
|
/// bool key3;
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// JPH_MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
|
/// JPH_MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
|
||||||
template <typename... Values>
|
template <typename FirstValue, typename... Values>
|
||||||
inline void HashCombine(std::size_t &ioSeed, Values... inValues)
|
inline uint64 HashCombineArgs(const FirstValue &inFirstValue, Values... inValues)
|
||||||
{
|
{
|
||||||
// Hash all values together using a fold expression
|
// Prime the seed by hashing the first value
|
||||||
(HashCombineHelper(ioSeed, inValues), ...);
|
uint64 seed = Hash<FirstValue> { } (inFirstValue);
|
||||||
|
|
||||||
|
// Hash all remaining values together using a fold expression
|
||||||
|
(HashCombine(seed, inValues), ...);
|
||||||
|
|
||||||
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
|
|
||||||
|
|
||||||
#define JPH_MAKE_HASH_STRUCT(type, name, ...) \
|
#define JPH_MAKE_HASH_STRUCT(type, name, ...) \
|
||||||
struct [[nodiscard]] name \
|
struct [[nodiscard]] name \
|
||||||
{ \
|
{ \
|
||||||
std::size_t operator()(const type &t) const \
|
::JPH::uint64 operator()(const type &t) const \
|
||||||
{ \
|
{ \
|
||||||
std::size_t ret = 0; \
|
return ::JPH::HashCombineArgs(__VA_ARGS__); \
|
||||||
::JPH::HashCombine(ret, __VA_ARGS__); \
|
} \
|
||||||
return ret; \
|
};
|
||||||
} \
|
|
||||||
};
|
#define JPH_MAKE_STD_HASH(type) \
|
||||||
|
JPH_SUPPRESS_WARNING_PUSH \
|
||||||
|
JPH_SUPPRESS_WARNINGS \
|
||||||
|
namespace std \
|
||||||
|
{ \
|
||||||
|
template<> \
|
||||||
|
struct [[nodiscard]] hash<type> \
|
||||||
|
{ \
|
||||||
|
size_t operator()(const type &t) const \
|
||||||
|
{ \
|
||||||
|
return size_t(::JPH::Hash<type>{ }(t)); \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
} \
|
||||||
|
JPH_SUPPRESS_WARNING_POP
|
||||||
|
|
||||||
#define JPH_MAKE_HASHABLE(type, ...) \
|
#define JPH_MAKE_HASHABLE(type, ...) \
|
||||||
JPH_SUPPRESS_WARNING_PUSH \
|
JPH_SUPPRESS_WARNING_PUSH \
|
||||||
JPH_SUPPRESS_WARNINGS \
|
JPH_SUPPRESS_WARNINGS \
|
||||||
namespace std \
|
namespace JPH \
|
||||||
{ \
|
{ \
|
||||||
template<> \
|
template<> \
|
||||||
JPH_MAKE_HASH_STRUCT(type, hash<type>, __VA_ARGS__) \
|
JPH_MAKE_HASH_STRUCT(type, Hash<type>, __VA_ARGS__) \
|
||||||
} \
|
} \
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_SUPPRESS_WARNING_POP \
|
||||||
|
JPH_MAKE_STD_HASH(type)
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,872 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Math/BVec16.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Helper class for implementing an UnorderedSet or UnorderedMap
|
||||||
|
/// Based on CppCon 2017: Matt Kulukundis "Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step"
|
||||||
|
/// See: https://www.youtube.com/watch?v=ncHmEUmJZf4
|
||||||
|
template <class Key, class KeyValue, class HashTableDetail, class Hash, class KeyEqual>
|
||||||
|
class HashTable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Properties
|
||||||
|
using value_type = KeyValue;
|
||||||
|
using size_type = uint32;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Base class for iterators
|
||||||
|
template <class Table, class Iterator>
|
||||||
|
class IteratorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Properties
|
||||||
|
using difference_type = typename Table::difference_type;
|
||||||
|
using value_type = typename Table::value_type;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
IteratorBase(const IteratorBase &inRHS) = default;
|
||||||
|
|
||||||
|
/// Assignment operator
|
||||||
|
IteratorBase & operator = (const IteratorBase &inRHS) = default;
|
||||||
|
|
||||||
|
/// Iterator at start of table
|
||||||
|
explicit IteratorBase(Table *inTable) :
|
||||||
|
mTable(inTable),
|
||||||
|
mIndex(0)
|
||||||
|
{
|
||||||
|
while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0)
|
||||||
|
++mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator at specific index
|
||||||
|
IteratorBase(Table *inTable, size_type inIndex) :
|
||||||
|
mTable(inTable),
|
||||||
|
mIndex(inIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefix increment
|
||||||
|
Iterator & operator ++ ()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(IsValid());
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
++mIndex;
|
||||||
|
}
|
||||||
|
while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0);
|
||||||
|
|
||||||
|
return static_cast<Iterator &>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Postfix increment
|
||||||
|
Iterator operator ++ (int)
|
||||||
|
{
|
||||||
|
Iterator result(mTable, mIndex);
|
||||||
|
++(*this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access to key value pair
|
||||||
|
const KeyValue & operator * () const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(IsValid());
|
||||||
|
return mTable->mData[mIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access to key value pair
|
||||||
|
const KeyValue * operator -> () const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(IsValid());
|
||||||
|
return mTable->mData + mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Equality operator
|
||||||
|
bool operator == (const Iterator &inRHS) const
|
||||||
|
{
|
||||||
|
return mIndex == inRHS.mIndex && mTable == inRHS.mTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inequality operator
|
||||||
|
bool operator != (const Iterator &inRHS) const
|
||||||
|
{
|
||||||
|
return !(*this == inRHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the iterator is valid
|
||||||
|
bool IsValid() const
|
||||||
|
{
|
||||||
|
return mIndex < mTable->mMaxSize
|
||||||
|
&& (mTable->mControl[mIndex] & cBucketUsed) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Table * mTable;
|
||||||
|
size_type mIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the maximum number of elements that we can support given a number of buckets
|
||||||
|
static constexpr size_type sGetMaxLoad(size_type inBucketCount)
|
||||||
|
{
|
||||||
|
return uint32((cMaxLoadFactorNumerator * inBucketCount) / cMaxLoadFactorDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the control value for a bucket
|
||||||
|
JPH_INLINE void SetControlValue(size_type inIndex, uint8 inValue)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIndex < mMaxSize);
|
||||||
|
mControl[inIndex] = inValue;
|
||||||
|
|
||||||
|
// Mirror the first 15 bytes to the 15 bytes beyond mMaxSize
|
||||||
|
// Note that this is equivalent to:
|
||||||
|
// if (inIndex < 15)
|
||||||
|
// mControl[inIndex + mMaxSize] = inValue
|
||||||
|
// else
|
||||||
|
// mControl[inIndex] = inValue
|
||||||
|
// Which performs a needless write if inIndex >= 15 but at least it is branch-less
|
||||||
|
mControl[((inIndex - 15) & (mMaxSize - 1)) + 15] = inValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the index and control value for a particular key
|
||||||
|
JPH_INLINE void GetIndexAndControlValue(const Key &inKey, size_type &outIndex, uint8 &outControl) const
|
||||||
|
{
|
||||||
|
// Calculate hash
|
||||||
|
uint64 hash_value = Hash { } (inKey);
|
||||||
|
|
||||||
|
// Split hash into index and control value
|
||||||
|
outIndex = size_type(hash_value >> 7) & (mMaxSize - 1);
|
||||||
|
outControl = cBucketUsed | uint8(hash_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate space for the hash table
|
||||||
|
void AllocateTable(size_type inMaxSize)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(mData == nullptr);
|
||||||
|
|
||||||
|
mMaxSize = inMaxSize;
|
||||||
|
mLoadLeft = sGetMaxLoad(inMaxSize);
|
||||||
|
size_t required_size = size_t(mMaxSize) * (sizeof(KeyValue) + 1) + 15; // Add 15 bytes to mirror the first 15 bytes of the control values
|
||||||
|
if constexpr (cNeedsAlignedAllocate)
|
||||||
|
mData = reinterpret_cast<KeyValue *>(AlignedAllocate(required_size, alignof(KeyValue)));
|
||||||
|
else
|
||||||
|
mData = reinterpret_cast<KeyValue *>(Allocate(required_size));
|
||||||
|
mControl = reinterpret_cast<uint8 *>(mData + mMaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy the contents of another hash table
|
||||||
|
void CopyTable(const HashTable &inRHS)
|
||||||
|
{
|
||||||
|
if (inRHS.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
AllocateTable(inRHS.mMaxSize);
|
||||||
|
|
||||||
|
// Copy control bytes
|
||||||
|
memcpy(mControl, inRHS.mControl, mMaxSize + 15);
|
||||||
|
|
||||||
|
// Copy elements
|
||||||
|
uint index = 0;
|
||||||
|
for (const uint8 *control = mControl, *control_end = mControl + mMaxSize; control != control_end; ++control, ++index)
|
||||||
|
if (*control & cBucketUsed)
|
||||||
|
new (mData + index) KeyValue(inRHS.mData[index]);
|
||||||
|
mSize = inRHS.mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grow the table to a new size
|
||||||
|
void GrowTable(size_type inNewMaxSize)
|
||||||
|
{
|
||||||
|
// Move the old table to a temporary structure
|
||||||
|
size_type old_max_size = mMaxSize;
|
||||||
|
KeyValue *old_data = mData;
|
||||||
|
const uint8 *old_control = mControl;
|
||||||
|
mData = nullptr;
|
||||||
|
mControl = nullptr;
|
||||||
|
mSize = 0;
|
||||||
|
mMaxSize = 0;
|
||||||
|
mLoadLeft = 0;
|
||||||
|
|
||||||
|
// Allocate new table
|
||||||
|
AllocateTable(inNewMaxSize);
|
||||||
|
|
||||||
|
// Reset all control bytes
|
||||||
|
memset(mControl, cBucketEmpty, mMaxSize + 15);
|
||||||
|
|
||||||
|
if (old_data != nullptr)
|
||||||
|
{
|
||||||
|
// Copy all elements from the old table
|
||||||
|
for (size_type i = 0; i < old_max_size; ++i)
|
||||||
|
if (old_control[i] & cBucketUsed)
|
||||||
|
{
|
||||||
|
size_type index;
|
||||||
|
KeyValue *element = old_data + i;
|
||||||
|
JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey</* InsertAfterGrow= */ true>(HashTableDetail::sGetKey(*element), index);
|
||||||
|
JPH_ASSERT(inserted);
|
||||||
|
new (mData + index) KeyValue(std::move(*element));
|
||||||
|
element->~KeyValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free memory
|
||||||
|
if constexpr (cNeedsAlignedAllocate)
|
||||||
|
AlignedFree(old_data);
|
||||||
|
else
|
||||||
|
Free(old_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Get an element by index
|
||||||
|
KeyValue & GetElement(size_type inIndex) const
|
||||||
|
{
|
||||||
|
return mData[inIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a key into the map, returns true if the element was inserted, false if it already existed.
|
||||||
|
/// outIndex is the index at which the element should be constructed / where it is located.
|
||||||
|
template <bool InsertAfterGrow = false>
|
||||||
|
bool InsertKey(const Key &inKey, size_type &outIndex)
|
||||||
|
{
|
||||||
|
// Ensure we have enough space
|
||||||
|
if (mLoadLeft == 0)
|
||||||
|
{
|
||||||
|
// Should not be growing if we're already growing!
|
||||||
|
if constexpr (InsertAfterGrow)
|
||||||
|
JPH_ASSERT(false);
|
||||||
|
|
||||||
|
// Decide if we need to clean up all tombstones or if we need to grow the map
|
||||||
|
size_type num_deleted = sGetMaxLoad(mMaxSize) - mSize;
|
||||||
|
if (num_deleted * cMaxDeletedElementsDenominator > mMaxSize * cMaxDeletedElementsNumerator)
|
||||||
|
rehash(0);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Grow by a power of 2
|
||||||
|
size_type new_max_size = max<size_type>(mMaxSize << 1, 16);
|
||||||
|
if (new_max_size < mMaxSize)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(false, "Overflow in hash table size, can't grow!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GrowTable(new_max_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split hash into index and control value
|
||||||
|
size_type index;
|
||||||
|
uint8 control;
|
||||||
|
GetIndexAndControlValue(inKey, index, control);
|
||||||
|
|
||||||
|
// Keeps track of the index of the first deleted bucket we found
|
||||||
|
constexpr size_type cNoDeleted = ~size_type(0);
|
||||||
|
size_type first_deleted_index = cNoDeleted;
|
||||||
|
|
||||||
|
// Linear probing
|
||||||
|
KeyEqual equal;
|
||||||
|
size_type bucket_mask = mMaxSize - 1;
|
||||||
|
BVec16 control16 = BVec16::sReplicate(control);
|
||||||
|
BVec16 bucket_empty = BVec16::sZero();
|
||||||
|
BVec16 bucket_deleted = BVec16::sReplicate(cBucketDeleted);
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Read 16 control values (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
||||||
|
BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);
|
||||||
|
|
||||||
|
// Check if we must find the element before we can insert
|
||||||
|
if constexpr (!InsertAfterGrow)
|
||||||
|
{
|
||||||
|
// Check for the control value we're looking for
|
||||||
|
// Note that when deleting we can create empty buckets instead of deleted buckets.
|
||||||
|
// This means we must unconditionally check all buckets in this batch for equality
|
||||||
|
// (also beyond the first empty bucket).
|
||||||
|
uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());
|
||||||
|
|
||||||
|
// Index within the 16 buckets
|
||||||
|
size_type local_index = index;
|
||||||
|
|
||||||
|
// Loop while there's still buckets to process
|
||||||
|
while (control_equal != 0)
|
||||||
|
{
|
||||||
|
// Get the first equal bucket
|
||||||
|
uint first_equal = CountTrailingZeros(control_equal);
|
||||||
|
|
||||||
|
// Skip to the bucket
|
||||||
|
local_index += first_equal;
|
||||||
|
|
||||||
|
// Make sure that our index is not beyond the end of the table
|
||||||
|
local_index &= bucket_mask;
|
||||||
|
|
||||||
|
// We found a bucket with same control value
|
||||||
|
if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))
|
||||||
|
{
|
||||||
|
// Element already exists
|
||||||
|
outIndex = local_index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip past this bucket
|
||||||
|
control_equal >>= first_equal + 1;
|
||||||
|
local_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're still scanning for deleted buckets
|
||||||
|
if (first_deleted_index == cNoDeleted)
|
||||||
|
{
|
||||||
|
// Check if any buckets have been deleted, if so store the first one
|
||||||
|
uint32 control_deleted = uint32(BVec16::sEquals(control_bytes, bucket_deleted).GetTrues());
|
||||||
|
if (control_deleted != 0)
|
||||||
|
first_deleted_index = index + CountTrailingZeros(control_deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for empty buckets
|
||||||
|
uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
||||||
|
if (control_empty != 0)
|
||||||
|
{
|
||||||
|
// If we found a deleted bucket, use it.
|
||||||
|
// It doesn't matter if it is before or after the first empty bucket we found
|
||||||
|
// since we will always be scanning in batches of 16 buckets.
|
||||||
|
if (first_deleted_index == cNoDeleted || InsertAfterGrow)
|
||||||
|
{
|
||||||
|
index += CountTrailingZeros(control_empty);
|
||||||
|
--mLoadLeft; // Using an empty bucket decreases the load left
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = first_deleted_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that our index is not beyond the end of the table
|
||||||
|
index &= bucket_mask;
|
||||||
|
|
||||||
|
// Update control byte
|
||||||
|
SetControlValue(index, control);
|
||||||
|
++mSize;
|
||||||
|
|
||||||
|
// Return index to newly allocated bucket
|
||||||
|
outIndex = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next batch of 16 buckets
|
||||||
|
index = (index + 16) & bucket_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Non-const iterator
|
||||||
|
class iterator : public IteratorBase<HashTable, iterator>
|
||||||
|
{
|
||||||
|
using Base = IteratorBase<HashTable, iterator>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Properties
|
||||||
|
using reference = typename Base::value_type &;
|
||||||
|
using pointer = typename Base::value_type *;
|
||||||
|
|
||||||
|
/// Constructors
|
||||||
|
explicit iterator(HashTable *inTable) : Base(inTable) { }
|
||||||
|
iterator(HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { }
|
||||||
|
iterator(const iterator &inIterator) : Base(inIterator) { }
|
||||||
|
|
||||||
|
/// Assignment
|
||||||
|
iterator & operator = (const iterator &inRHS) { Base::operator = (inRHS); return *this; }
|
||||||
|
|
||||||
|
using Base::operator *;
|
||||||
|
|
||||||
|
/// Non-const access to key value pair
|
||||||
|
KeyValue & operator * ()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(this->IsValid());
|
||||||
|
return this->mTable->mData[this->mIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
using Base::operator ->;
|
||||||
|
|
||||||
|
/// Non-const access to key value pair
|
||||||
|
KeyValue * operator -> ()
|
||||||
|
{
|
||||||
|
JPH_ASSERT(this->IsValid());
|
||||||
|
return this->mTable->mData + this->mIndex;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Const iterator
|
||||||
|
class const_iterator : public IteratorBase<const HashTable, const_iterator>
|
||||||
|
{
|
||||||
|
using Base = IteratorBase<const HashTable, const_iterator>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Properties
|
||||||
|
using reference = const typename Base::value_type &;
|
||||||
|
using pointer = const typename Base::value_type *;
|
||||||
|
|
||||||
|
/// Constructors
|
||||||
|
explicit const_iterator(const HashTable *inTable) : Base(inTable) { }
|
||||||
|
const_iterator(const HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { }
|
||||||
|
const_iterator(const const_iterator &inRHS) : Base(inRHS) { }
|
||||||
|
const_iterator(const iterator &inIterator) : Base(inIterator.mTable, inIterator.mIndex) { }
|
||||||
|
|
||||||
|
/// Assignment
|
||||||
|
const_iterator & operator = (const iterator &inRHS) { this->mTable = inRHS.mTable; this->mIndex = inRHS.mIndex; return *this; }
|
||||||
|
const_iterator & operator = (const const_iterator &inRHS) { Base::operator = (inRHS); return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Default constructor
|
||||||
|
HashTable() = default;
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
HashTable(const HashTable &inRHS)
|
||||||
|
{
|
||||||
|
CopyTable(inRHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move constructor
|
||||||
|
HashTable(HashTable &&ioRHS) noexcept :
|
||||||
|
mData(ioRHS.mData),
|
||||||
|
mControl(ioRHS.mControl),
|
||||||
|
mSize(ioRHS.mSize),
|
||||||
|
mMaxSize(ioRHS.mMaxSize),
|
||||||
|
mLoadLeft(ioRHS.mLoadLeft)
|
||||||
|
{
|
||||||
|
ioRHS.mData = nullptr;
|
||||||
|
ioRHS.mControl = nullptr;
|
||||||
|
ioRHS.mSize = 0;
|
||||||
|
ioRHS.mMaxSize = 0;
|
||||||
|
ioRHS.mLoadLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assignment operator
|
||||||
|
HashTable & operator = (const HashTable &inRHS)
|
||||||
|
{
|
||||||
|
if (this != &inRHS)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
CopyTable(inRHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move assignment operator
|
||||||
|
HashTable & operator = (HashTable &&ioRHS) noexcept
|
||||||
|
{
|
||||||
|
if (this != &ioRHS)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
mData = ioRHS.mData;
|
||||||
|
mControl = ioRHS.mControl;
|
||||||
|
mSize = ioRHS.mSize;
|
||||||
|
mMaxSize = ioRHS.mMaxSize;
|
||||||
|
mLoadLeft = ioRHS.mLoadLeft;
|
||||||
|
|
||||||
|
ioRHS.mData = nullptr;
|
||||||
|
ioRHS.mControl = nullptr;
|
||||||
|
ioRHS.mSize = 0;
|
||||||
|
ioRHS.mMaxSize = 0;
|
||||||
|
ioRHS.mLoadLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destructor
|
||||||
|
~HashTable()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reserve memory for a certain number of elements
|
||||||
|
void reserve(size_type inMaxSize)
|
||||||
|
{
|
||||||
|
// Calculate max size based on load factor
|
||||||
|
size_type max_size = GetNextPowerOf2(max<uint32>((cMaxLoadFactorDenominator * inMaxSize) / cMaxLoadFactorNumerator, 16));
|
||||||
|
if (max_size <= mMaxSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GrowTable(max_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy the entire hash table
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
// Delete all elements
|
||||||
|
if constexpr (!std::is_trivially_destructible<KeyValue>())
|
||||||
|
if (!empty())
|
||||||
|
for (size_type i = 0; i < mMaxSize; ++i)
|
||||||
|
if (mControl[i] & cBucketUsed)
|
||||||
|
mData[i].~KeyValue();
|
||||||
|
|
||||||
|
if (mData != nullptr)
|
||||||
|
{
|
||||||
|
// Free memory
|
||||||
|
if constexpr (cNeedsAlignedAllocate)
|
||||||
|
AlignedFree(mData);
|
||||||
|
else
|
||||||
|
Free(mData);
|
||||||
|
|
||||||
|
// Reset members
|
||||||
|
mData = nullptr;
|
||||||
|
mControl = nullptr;
|
||||||
|
mSize = 0;
|
||||||
|
mMaxSize = 0;
|
||||||
|
mLoadLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy the entire hash table but keeps the memory allocated
|
||||||
|
void ClearAndKeepMemory()
|
||||||
|
{
|
||||||
|
// Destruct elements
|
||||||
|
if constexpr (!std::is_trivially_destructible<KeyValue>())
|
||||||
|
if (!empty())
|
||||||
|
for (size_type i = 0; i < mMaxSize; ++i)
|
||||||
|
if (mControl[i] & cBucketUsed)
|
||||||
|
mData[i].~KeyValue();
|
||||||
|
mSize = 0;
|
||||||
|
|
||||||
|
// If there are elements that are not marked cBucketEmpty, we reset them
|
||||||
|
size_type max_load = sGetMaxLoad(mMaxSize);
|
||||||
|
if (mLoadLeft != max_load)
|
||||||
|
{
|
||||||
|
// Reset all control bytes
|
||||||
|
memset(mControl, cBucketEmpty, mMaxSize + 15);
|
||||||
|
mLoadLeft = max_load;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to first element
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
return iterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to one beyond last element
|
||||||
|
iterator end()
|
||||||
|
{
|
||||||
|
return iterator(this, mMaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to first element
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return const_iterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to one beyond last element
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return const_iterator(this, mMaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to first element
|
||||||
|
const_iterator cbegin() const
|
||||||
|
{
|
||||||
|
return const_iterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator to one beyond last element
|
||||||
|
const_iterator cend() const
|
||||||
|
{
|
||||||
|
return const_iterator(this, mMaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of buckets in the table
|
||||||
|
size_type bucket_count() const
|
||||||
|
{
|
||||||
|
return mMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max number of buckets that the table can have
|
||||||
|
constexpr size_type max_bucket_count() const
|
||||||
|
{
|
||||||
|
return size_type(1) << (sizeof(size_type) * 8 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if there are no elements in the table
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mSize == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of elements in the table
|
||||||
|
size_type size() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max number of elements that the table can hold
|
||||||
|
constexpr size_type max_size() const
|
||||||
|
{
|
||||||
|
return size_type((uint64(max_bucket_count()) * cMaxLoadFactorNumerator) / cMaxLoadFactorDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the max load factor for this table (max number of elements / number of buckets)
|
||||||
|
constexpr float max_load_factor() const
|
||||||
|
{
|
||||||
|
return float(cMaxLoadFactorNumerator) / float(cMaxLoadFactorDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new element, returns iterator and if the element was inserted
|
||||||
|
std::pair<iterator, bool> insert(const value_type &inValue)
|
||||||
|
{
|
||||||
|
size_type index;
|
||||||
|
bool inserted = InsertKey(HashTableDetail::sGetKey(inValue), index);
|
||||||
|
if (inserted)
|
||||||
|
new (mData + index) KeyValue(inValue);
|
||||||
|
return std::make_pair(iterator(this, index), inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find an element, returns iterator to element or end() if not found
|
||||||
|
const_iterator find(const Key &inKey) const
|
||||||
|
{
|
||||||
|
// Check if we have any data
|
||||||
|
if (empty())
|
||||||
|
return cend();
|
||||||
|
|
||||||
|
// Split hash into index and control value
|
||||||
|
size_type index;
|
||||||
|
uint8 control;
|
||||||
|
GetIndexAndControlValue(inKey, index, control);
|
||||||
|
|
||||||
|
// Linear probing
|
||||||
|
KeyEqual equal;
|
||||||
|
size_type bucket_mask = mMaxSize - 1;
|
||||||
|
BVec16 control16 = BVec16::sReplicate(control);
|
||||||
|
BVec16 bucket_empty = BVec16::sZero();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Read 16 control values
|
||||||
|
// (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
||||||
|
BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);
|
||||||
|
|
||||||
|
// Check for the control value we're looking for
|
||||||
|
// Note that when deleting we can create empty buckets instead of deleted buckets.
|
||||||
|
// This means we must unconditionally check all buckets in this batch for equality
|
||||||
|
// (also beyond the first empty bucket).
|
||||||
|
uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());
|
||||||
|
|
||||||
|
// Index within the 16 buckets
|
||||||
|
size_type local_index = index;
|
||||||
|
|
||||||
|
// Loop while there's still buckets to process
|
||||||
|
while (control_equal != 0)
|
||||||
|
{
|
||||||
|
// Get the first equal bucket
|
||||||
|
uint first_equal = CountTrailingZeros(control_equal);
|
||||||
|
|
||||||
|
// Skip to the bucket
|
||||||
|
local_index += first_equal;
|
||||||
|
|
||||||
|
// Make sure that our index is not beyond the end of the table
|
||||||
|
local_index &= bucket_mask;
|
||||||
|
|
||||||
|
// We found a bucket with same control value
|
||||||
|
if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))
|
||||||
|
{
|
||||||
|
// Element found
|
||||||
|
return const_iterator(this, local_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip past this bucket
|
||||||
|
control_equal >>= first_equal + 1;
|
||||||
|
local_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for empty buckets
|
||||||
|
uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
||||||
|
if (control_empty != 0)
|
||||||
|
{
|
||||||
|
// An empty bucket was found, we didn't find the element
|
||||||
|
return cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next batch of 16 buckets
|
||||||
|
index = (index + 16) & bucket_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Erase an element by iterator
|
||||||
|
void erase(const const_iterator &inIterator)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIterator.IsValid());
|
||||||
|
|
||||||
|
// Read 16 control values before and after the current index
|
||||||
|
// (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
||||||
|
BVec16 control_bytes_before = BVec16::sLoadByte16(mControl + ((inIterator.mIndex - 16) & (mMaxSize - 1)));
|
||||||
|
BVec16 control_bytes_after = BVec16::sLoadByte16(mControl + inIterator.mIndex);
|
||||||
|
BVec16 bucket_empty = BVec16::sZero();
|
||||||
|
uint32 control_empty_before = uint32(BVec16::sEquals(control_bytes_before, bucket_empty).GetTrues());
|
||||||
|
uint32 control_empty_after = uint32(BVec16::sEquals(control_bytes_after, bucket_empty).GetTrues());
|
||||||
|
|
||||||
|
// If (this index including) there exist 16 consecutive non-empty slots (represented by a bit being 0) then
|
||||||
|
// a probe looking for some element needs to continue probing so we cannot mark the bucket as empty
|
||||||
|
// but must mark it as deleted instead.
|
||||||
|
// Note that we use: CountLeadingZeros(uint16) = CountLeadingZeros(uint32) - 16.
|
||||||
|
uint8 control_value = CountLeadingZeros(control_empty_before) - 16 + CountTrailingZeros(control_empty_after) < 16? cBucketEmpty : cBucketDeleted;
|
||||||
|
|
||||||
|
// Mark the bucket as empty/deleted
|
||||||
|
SetControlValue(inIterator.mIndex, control_value);
|
||||||
|
|
||||||
|
// Destruct the element
|
||||||
|
mData[inIterator.mIndex].~KeyValue();
|
||||||
|
|
||||||
|
// If we marked the bucket as empty we can increase the load left
|
||||||
|
if (control_value == cBucketEmpty)
|
||||||
|
++mLoadLeft;
|
||||||
|
|
||||||
|
// Decrease size
|
||||||
|
--mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Erase an element by key
|
||||||
|
size_type erase(const Key &inKey)
|
||||||
|
{
|
||||||
|
const_iterator it = find(inKey);
|
||||||
|
if (it == cend())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
erase(it);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swap the contents of two hash tables
|
||||||
|
void swap(HashTable &ioRHS) noexcept
|
||||||
|
{
|
||||||
|
std::swap(mData, ioRHS.mData);
|
||||||
|
std::swap(mControl, ioRHS.mControl);
|
||||||
|
std::swap(mSize, ioRHS.mSize);
|
||||||
|
std::swap(mMaxSize, ioRHS.mMaxSize);
|
||||||
|
std::swap(mLoadLeft, ioRHS.mLoadLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In place re-hashing of all elements in the table. Removes all cBucketDeleted elements
|
||||||
|
/// The std version takes a bucket count, but we just re-hash to the same size.
|
||||||
|
void rehash(size_type)
|
||||||
|
{
|
||||||
|
// Update the control value for all buckets
|
||||||
|
for (size_type i = 0; i < mMaxSize; ++i)
|
||||||
|
{
|
||||||
|
uint8 &control = mControl[i];
|
||||||
|
switch (control)
|
||||||
|
{
|
||||||
|
case cBucketDeleted:
|
||||||
|
// Deleted buckets become empty
|
||||||
|
control = cBucketEmpty;
|
||||||
|
break;
|
||||||
|
case cBucketEmpty:
|
||||||
|
// Remains empty
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Mark all occupied as deleted, to indicate it needs to move to the correct place
|
||||||
|
control = cBucketDeleted;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replicate control values to the last 15 entries
|
||||||
|
for (size_type i = 0; i < 15; ++i)
|
||||||
|
mControl[mMaxSize + i] = mControl[i];
|
||||||
|
|
||||||
|
// Loop over all elements that have been 'deleted' and move them to their new spot
|
||||||
|
BVec16 bucket_used = BVec16::sReplicate(cBucketUsed);
|
||||||
|
size_type bucket_mask = mMaxSize - 1;
|
||||||
|
uint32 probe_mask = bucket_mask & ~uint32(0b1111); // Mask out lower 4 bits because we test 16 buckets at a time
|
||||||
|
for (size_type src = 0; src < mMaxSize; ++src)
|
||||||
|
if (mControl[src] == cBucketDeleted)
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Split hash into index and control value
|
||||||
|
size_type src_index;
|
||||||
|
uint8 src_control;
|
||||||
|
GetIndexAndControlValue(HashTableDetail::sGetKey(mData[src]), src_index, src_control);
|
||||||
|
|
||||||
|
// Linear probing
|
||||||
|
size_type dst = src_index;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Check if any buckets are free
|
||||||
|
BVec16 control_bytes = BVec16::sLoadByte16(mControl + dst);
|
||||||
|
uint32 control_free = uint32(BVec16::sAnd(control_bytes, bucket_used).GetTrues()) ^ 0xffff;
|
||||||
|
if (control_free != 0)
|
||||||
|
{
|
||||||
|
// Select this bucket as destination
|
||||||
|
dst += CountTrailingZeros(control_free);
|
||||||
|
dst &= bucket_mask;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next batch of 16 buckets
|
||||||
|
dst = (dst + 16) & bucket_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we stay in the same probe group
|
||||||
|
if (((dst - src_index) & probe_mask) == ((src - src_index) & probe_mask))
|
||||||
|
{
|
||||||
|
// We stay in the same group, we can stay where we are
|
||||||
|
SetControlValue(src, src_control);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (mControl[dst] == cBucketEmpty)
|
||||||
|
{
|
||||||
|
// There's an empty bucket, move us there
|
||||||
|
SetControlValue(dst, src_control);
|
||||||
|
SetControlValue(src, cBucketEmpty);
|
||||||
|
new (mData + dst) KeyValue(std::move(mData[src]));
|
||||||
|
mData[src].~KeyValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There's an element in the bucket we want to move to, swap them
|
||||||
|
JPH_ASSERT(mControl[dst] == cBucketDeleted);
|
||||||
|
SetControlValue(dst, src_control);
|
||||||
|
std::swap(mData[src], mData[dst]);
|
||||||
|
// Iterate again with the same source bucket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize load left
|
||||||
|
mLoadLeft = sGetMaxLoad(mMaxSize) - mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// If this allocator needs to fall back to aligned allocations because the type requires it
|
||||||
|
static constexpr bool cNeedsAlignedAllocate = alignof(KeyValue) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16);
|
||||||
|
|
||||||
|
/// Max load factor is cMaxLoadFactorNumerator / cMaxLoadFactorDenominator
|
||||||
|
static constexpr uint64 cMaxLoadFactorNumerator = 7;
|
||||||
|
static constexpr uint64 cMaxLoadFactorDenominator = 8;
|
||||||
|
|
||||||
|
/// If we can recover this fraction of deleted elements, we'll reshuffle the buckets in place rather than growing the table
|
||||||
|
static constexpr uint64 cMaxDeletedElementsNumerator = 1;
|
||||||
|
static constexpr uint64 cMaxDeletedElementsDenominator = 8;
|
||||||
|
|
||||||
|
/// Values that the control bytes can have
|
||||||
|
static constexpr uint8 cBucketEmpty = 0;
|
||||||
|
static constexpr uint8 cBucketDeleted = 0x7f;
|
||||||
|
static constexpr uint8 cBucketUsed = 0x80; // Lowest 7 bits are lowest 7 bits of the hash value
|
||||||
|
|
||||||
|
/// The buckets, an array of size mMaxSize
|
||||||
|
KeyValue * mData = nullptr;
|
||||||
|
|
||||||
|
/// Control bytes, an array of size mMaxSize + 15
|
||||||
|
uint8 * mControl = nullptr;
|
||||||
|
|
||||||
|
/// Number of elements in the table
|
||||||
|
size_type mSize = 0;
|
||||||
|
|
||||||
|
/// Max number of elements that can be stored in the table
|
||||||
|
size_type mMaxSize = 0;
|
||||||
|
|
||||||
|
/// Number of elements we can add to the table before we need to grow
|
||||||
|
size_type mLoadLeft = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -4,15 +4,11 @@
|
||||||
|
|
||||||
#include <Jolt/Jolt.h>
|
#include <Jolt/Jolt.h>
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
|
||||||
#include <fstream>
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
static void DummyTrace([[maybe_unused]] const char *inFMT, ...)
|
static void DummyTrace([[maybe_unused]] const char *inFMT, ...)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(false);
|
JPH_ASSERT(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
TraceFunction Trace = DummyTrace;
|
TraceFunction Trace = DummyTrace;
|
||||||
|
|
@ -20,7 +16,7 @@ TraceFunction Trace = DummyTrace;
|
||||||
#ifdef JPH_ENABLE_ASSERTS
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
|
|
||||||
static bool DummyAssertFailed(const char *inExpression, const char *inMessage, const char *inFile, uint inLine)
|
static bool DummyAssertFailed(const char *inExpression, const char *inMessage, const char *inFile, uint inLine)
|
||||||
{
|
{
|
||||||
return true; // Trigger breakpoint
|
return true; // Trigger breakpoint
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Trace function, needs to be overridden by application. This should output a line of text to the log / TTY.
|
/// Trace function, needs to be overridden by application. This should output a line of text to the log / TTY.
|
||||||
using TraceFunction = void (*)(const char *inFMT, ...);
|
using TraceFunction = void (*)(const char *inFMT, ...);
|
||||||
extern TraceFunction Trace;
|
JPH_EXPORT extern TraceFunction Trace;
|
||||||
|
|
||||||
// Always turn on asserts in Debug mode
|
// Always turn on asserts in Debug mode
|
||||||
#if defined(_DEBUG) && !defined(JPH_ENABLE_ASSERTS)
|
#if defined(JPH_DEBUG) && !defined(JPH_ENABLE_ASSERTS)
|
||||||
#define JPH_ENABLE_ASSERTS
|
#define JPH_ENABLE_ASSERTS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JPH_ENABLE_ASSERTS
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
/// Function called when an assertion fails. This function should return true if a breakpoint needs to be triggered
|
/// Function called when an assertion fails. This function should return true if a breakpoint needs to be triggered
|
||||||
using AssertFailedFunction = bool(*)(const char *inExpression, const char *inMessage, const char *inFile, uint inLine);
|
using AssertFailedFunction = bool(*)(const char *inExpression, const char *inMessage, const char *inFile, uint inLine);
|
||||||
extern AssertFailedFunction AssertFailed;
|
JPH_EXPORT extern AssertFailedFunction AssertFailed;
|
||||||
|
|
||||||
// Helper functions to pass message on to failed function
|
// Helper functions to pass message on to failed function
|
||||||
struct AssertLastParam { };
|
struct AssertLastParam { };
|
||||||
|
|
@ -26,13 +26,13 @@ extern TraceFunction Trace;
|
||||||
inline bool AssertFailedParamHelper(const char *inExpression, const char *inFile, uint inLine, const char *inMessage, AssertLastParam) { return AssertFailed(inExpression, inMessage, inFile, inLine); }
|
inline bool AssertFailedParamHelper(const char *inExpression, const char *inFile, uint inLine, const char *inMessage, AssertLastParam) { return AssertFailed(inExpression, inMessage, inFile, inLine); }
|
||||||
|
|
||||||
/// Main assert macro, usage: JPH_ASSERT(condition, message) or JPH_ASSERT(condition)
|
/// Main assert macro, usage: JPH_ASSERT(condition, message) or JPH_ASSERT(condition)
|
||||||
#define JPH_ASSERT(inExpression, ...) do { if (!(inExpression) && AssertFailedParamHelper(#inExpression, __FILE__, uint(__LINE__), ##__VA_ARGS__, AssertLastParam())) JPH_BREAKPOINT; } while (false)
|
#define JPH_ASSERT(inExpression, ...) do { if (!(inExpression) && AssertFailedParamHelper(#inExpression, __FILE__, JPH::uint(__LINE__), ##__VA_ARGS__, JPH::AssertLastParam())) JPH_BREAKPOINT; } while (false)
|
||||||
|
|
||||||
#define JPH_IF_ENABLE_ASSERTS(...) __VA_ARGS__
|
#define JPH_IF_ENABLE_ASSERTS(...) __VA_ARGS__
|
||||||
#else
|
#else
|
||||||
#define JPH_ASSERT(...) ((void)0)
|
#define JPH_ASSERT(...) ((void)0)
|
||||||
|
|
||||||
#define JPH_IF_ENABLE_ASSERTS(...)
|
#define JPH_IF_ENABLE_ASSERTS(...)
|
||||||
#endif // JPH_ENABLE_ASSERTS
|
#endif // JPH_ENABLE_ASSERTS
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -15,29 +15,29 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// A class that allows units of work (Jobs) to be scheduled across multiple threads.
|
/// A class that allows units of work (Jobs) to be scheduled across multiple threads.
|
||||||
/// It allows dependencies between the jobs so that the jobs form a graph.
|
/// It allows dependencies between the jobs so that the jobs form a graph.
|
||||||
///
|
///
|
||||||
/// The pattern for using this class is:
|
/// The pattern for using this class is:
|
||||||
///
|
///
|
||||||
/// // Create job system
|
/// // Create job system
|
||||||
/// JobSystem *job_system = new JobSystemThreadPool(...);
|
/// JobSystem *job_system = new JobSystemThreadPool(...);
|
||||||
///
|
///
|
||||||
/// // Create some jobs
|
/// // Create some jobs
|
||||||
/// JobHandle second_job = job_system->CreateJob("SecondJob", Color::sRed, []() { ... }, 1); // Create a job with 1 dependency
|
/// JobHandle second_job = job_system->CreateJob("SecondJob", Color::sRed, []() { ... }, 1); // Create a job with 1 dependency
|
||||||
/// JobHandle first_job = job_system->CreateJob("FirstJob", Color::sGreen, [second_job]() { ....; second_job.RemoveDependency(); }, 0); // Job can start immediately, will start second job when it's done
|
/// JobHandle first_job = job_system->CreateJob("FirstJob", Color::sGreen, [second_job]() { ....; second_job.RemoveDependency(); }, 0); // Job can start immediately, will start second job when it's done
|
||||||
/// JobHandle third_job = job_system->CreateJob("ThirdJob", Color::sBlue, []() { ... }, 0); // This job can run immediately as well and can run in parallel to job 1 and 2
|
/// JobHandle third_job = job_system->CreateJob("ThirdJob", Color::sBlue, []() { ... }, 0); // This job can run immediately as well and can run in parallel to job 1 and 2
|
||||||
///
|
///
|
||||||
/// // Add the jobs to the barrier so that we can execute them while we're waiting
|
/// // Add the jobs to the barrier so that we can execute them while we're waiting
|
||||||
/// Barrier *barrier = job_system->CreateBarrier();
|
/// Barrier *barrier = job_system->CreateBarrier();
|
||||||
/// barrier->AddJob(first_job);
|
/// barrier->AddJob(first_job);
|
||||||
/// barrier->AddJob(second_job);
|
/// barrier->AddJob(second_job);
|
||||||
/// barrier->AddJob(third_job);
|
/// barrier->AddJob(third_job);
|
||||||
/// job_system->WaitForJobs(barrier);
|
/// job_system->WaitForJobs(barrier);
|
||||||
///
|
///
|
||||||
/// // Clean up
|
/// // Clean up
|
||||||
/// job_system->DestroyBarrier(barrier);
|
/// job_system->DestroyBarrier(barrier);
|
||||||
/// delete job_system;
|
/// delete job_system;
|
||||||
///
|
///
|
||||||
/// Jobs are guaranteed to be started in the order that their dependency counter becomes zero (in case they're scheduled on a background thread)
|
/// Jobs are guaranteed to be started in the order that their dependency counter becomes zero (in case they're scheduled on a background thread)
|
||||||
/// or in the order they're added to the barrier (when dependency count is zero and when executing on the thread that calls WaitForJobs).
|
/// or in the order they're added to the barrier (when dependency count is zero and when executing on the thread that calls WaitForJobs).
|
||||||
///
|
///
|
||||||
/// If you want to implement your own job system, inherit from JobSystem and implement:
|
/// If you want to implement your own job system, inherit from JobSystem and implement:
|
||||||
|
|
@ -51,22 +51,22 @@ JPH_NAMESPACE_BEGIN
|
||||||
///
|
///
|
||||||
/// JobSystem::Barrier is used to track the completion of a set of jobs. Jobs will be created by other jobs and added to the barrier while it is being waited on. This means that you cannot
|
/// JobSystem::Barrier is used to track the completion of a set of jobs. Jobs will be created by other jobs and added to the barrier while it is being waited on. This means that you cannot
|
||||||
/// create a dependency graph beforehand as the graph changes while jobs are running. Implement the following functions:
|
/// create a dependency graph beforehand as the graph changes while jobs are running. Implement the following functions:
|
||||||
///
|
///
|
||||||
/// * Barrier::AddJob/AddJobs - Add a job to the barrier, any call to WaitForJobs will now also wait for this job to complete.
|
/// * Barrier::AddJob/AddJobs - Add a job to the barrier, any call to WaitForJobs will now also wait for this job to complete.
|
||||||
/// If you store the job in a data structure in the Barrier you need to call AddRef() on the job to keep it alive and Release() after you're done with it.
|
/// If you store the job in a data structure in the Barrier you need to call AddRef() on the job to keep it alive and Release() after you're done with it.
|
||||||
/// * Barrier::OnJobFinished - This function is called when a job has finished executing, you can use this to track completion and remove the job from the list of jobs to wait on.
|
/// * Barrier::OnJobFinished - This function is called when a job has finished executing, you can use this to track completion and remove the job from the list of jobs to wait on.
|
||||||
///
|
///
|
||||||
/// The functions on JobSystem that need to be implemented to support barriers are:
|
/// The functions on JobSystem that need to be implemented to support barriers are:
|
||||||
///
|
///
|
||||||
/// * JobSystem::CreateBarrier - Create a new barrier.
|
/// * JobSystem::CreateBarrier - Create a new barrier.
|
||||||
/// * JobSystem::DestroyBarrier - Destroy a barrier.
|
/// * JobSystem::DestroyBarrier - Destroy a barrier.
|
||||||
/// * JobSystem::WaitForJobs - This is the main function that is used to wait for all jobs that have been added to a Barrier. WaitForJobs can execute jobs that have
|
/// * JobSystem::WaitForJobs - This is the main function that is used to wait for all jobs that have been added to a Barrier. WaitForJobs can execute jobs that have
|
||||||
/// been added to the barrier while waiting. It is not wise to execute other jobs that touch physics structures as this can cause race conditions and deadlocks. Please keep in mind that the barrier is
|
/// been added to the barrier while waiting. It is not wise to execute other jobs that touch physics structures as this can cause race conditions and deadlocks. Please keep in mind that the barrier is
|
||||||
/// only intended to wait on the completion of the Jolt jobs added to it, if you scheduled any jobs in your engine's job system to execute the Jolt jobs as part of QueueJob/QueueJobs, you might still need
|
/// only intended to wait on the completion of the Jolt jobs added to it, if you scheduled any jobs in your engine's job system to execute the Jolt jobs as part of QueueJob/QueueJobs, you might still need
|
||||||
/// to wait for these in this function after the barrier is finished waiting.
|
/// to wait for these in this function after the barrier is finished waiting.
|
||||||
///
|
///
|
||||||
/// An example implementation is JobSystemThreadPool. If you don't want to write the Barrier class you can also inherit from JobSystemWithBarrier.
|
/// An example implementation is JobSystemThreadPool. If you don't want to write the Barrier class you can also inherit from JobSystemWithBarrier.
|
||||||
class JobSystem : public NonCopyable
|
class JPH_EXPORT JobSystem : public NonCopyable
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
class Job;
|
class Job;
|
||||||
|
|
@ -79,7 +79,7 @@ public:
|
||||||
class JobHandle : private Ref<Job>
|
class JobHandle : private Ref<Job>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline JobHandle() = default;
|
inline JobHandle() = default;
|
||||||
inline JobHandle(const JobHandle &inHandle) = default;
|
inline JobHandle(const JobHandle &inHandle) = default;
|
||||||
inline JobHandle(JobHandle &&inHandle) noexcept : Ref<Job>(std::move(inHandle)) { }
|
inline JobHandle(JobHandle &&inHandle) noexcept : Ref<Job>(std::move(inHandle)) { }
|
||||||
|
|
@ -97,7 +97,7 @@ public:
|
||||||
/// Check if this job has finished executing
|
/// Check if this job has finished executing
|
||||||
inline bool IsDone() const { return GetPtr() != nullptr && GetPtr()->IsDone(); }
|
inline bool IsDone() const { return GetPtr() != nullptr && GetPtr()->IsDone(); }
|
||||||
|
|
||||||
/// Add to the dependency counter.
|
/// Add to the dependency counter.
|
||||||
inline void AddDependency(int inCount = 1) const { GetPtr()->AddDependency(inCount); }
|
inline void AddDependency(int inCount = 1) const { GetPtr()->AddDependency(inCount); }
|
||||||
|
|
||||||
/// Remove from the dependency counter. Job will start whenever the dependency counter reaches zero
|
/// Remove from the dependency counter. Job will start whenever the dependency counter reaches zero
|
||||||
|
|
@ -105,7 +105,7 @@ public:
|
||||||
inline void RemoveDependency(int inCount = 1) const { GetPtr()->RemoveDependencyAndQueue(inCount); }
|
inline void RemoveDependency(int inCount = 1) const { GetPtr()->RemoveDependencyAndQueue(inCount); }
|
||||||
|
|
||||||
/// Remove a dependency from a batch of jobs at once, this can be more efficient than removing them one by one as it requires less locking
|
/// Remove a dependency from a batch of jobs at once, this can be more efficient than removing them one by one as it requires less locking
|
||||||
static inline void sRemoveDependencies(JobHandle *inHandles, uint inNumHandles, int inCount = 1);
|
static inline void sRemoveDependencies(const JobHandle *inHandles, uint inNumHandles, int inCount = 1);
|
||||||
|
|
||||||
/// Helper function to remove dependencies on a static array of job handles
|
/// Helper function to remove dependencies on a static array of job handles
|
||||||
template <uint N>
|
template <uint N>
|
||||||
|
|
@ -173,15 +173,15 @@ protected:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) :
|
Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) :
|
||||||
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||||
mJobName(inJobName),
|
mJobName(inJobName),
|
||||||
mColor(inColor),
|
mColor(inColor),
|
||||||
#endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
#endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||||
mJobSystem(inJobSystem),
|
mJobSystem(inJobSystem),
|
||||||
mJobFunction(inJobFunction),
|
mJobFunction(inJobFunction),
|
||||||
mNumDependencies(inNumDependencies)
|
mNumDependencies(inNumDependencies)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the jobs system to which this job belongs
|
/// Get the jobs system to which this job belongs
|
||||||
|
|
@ -192,19 +192,25 @@ protected:
|
||||||
{
|
{
|
||||||
// Adding a reference can use relaxed memory ordering
|
// Adding a reference can use relaxed memory ordering
|
||||||
mReferenceCount.fetch_add(1, memory_order_relaxed);
|
mReferenceCount.fetch_add(1, memory_order_relaxed);
|
||||||
}
|
}
|
||||||
inline void Release()
|
inline void Release()
|
||||||
{
|
{
|
||||||
|
#ifndef JPH_TSAN_ENABLED
|
||||||
// Releasing a reference must use release semantics...
|
// Releasing a reference must use release semantics...
|
||||||
if (mReferenceCount.fetch_sub(1, memory_order_release) == 1)
|
if (mReferenceCount.fetch_sub(1, memory_order_release) == 1)
|
||||||
{
|
{
|
||||||
// ... so that we can use aquire to ensure that we see any updates from other threads that released a ref before freeing the job
|
// ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before freeing the job
|
||||||
atomic_thread_fence(memory_order_acquire);
|
atomic_thread_fence(memory_order_acquire);
|
||||||
mJobSystem->FreeJob(this);
|
mJobSystem->FreeJob(this);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// But under TSAN, we cannot use atomic_thread_fence, so we use an acq_rel operation unconditionally instead
|
||||||
|
if (mReferenceCount.fetch_sub(1, memory_order_acq_rel) == 1)
|
||||||
|
mJobSystem->FreeJob(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add to the dependency counter.
|
/// Add to the dependency counter.
|
||||||
inline void AddDependency(int inCount);
|
inline void AddDependency(int inCount);
|
||||||
|
|
||||||
/// Remove from the dependency counter. Returns true whenever the dependency counter reaches zero
|
/// Remove from the dependency counter. Returns true whenever the dependency counter reaches zero
|
||||||
|
|
@ -217,10 +223,10 @@ protected:
|
||||||
|
|
||||||
/// Set the job barrier that this job belongs to and returns false if this was not possible because the job already finished
|
/// Set the job barrier that this job belongs to and returns false if this was not possible because the job already finished
|
||||||
inline bool SetBarrier(Barrier *inBarrier)
|
inline bool SetBarrier(Barrier *inBarrier)
|
||||||
{
|
{
|
||||||
intptr_t barrier = 0;
|
intptr_t barrier = 0;
|
||||||
if (mBarrier.compare_exchange_strong(barrier, reinterpret_cast<intptr_t>(inBarrier), memory_order_relaxed))
|
if (mBarrier.compare_exchange_strong(barrier, reinterpret_cast<intptr_t>(inBarrier), memory_order_relaxed))
|
||||||
return true;
|
return true;
|
||||||
JPH_ASSERT(barrier == cBarrierDoneState, "A job can only belong to 1 barrier");
|
JPH_ASSERT(barrier == cBarrierDoneState, "A job can only belong to 1 barrier");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +272,11 @@ protected:
|
||||||
/// Test if the job finished executing
|
/// Test if the job finished executing
|
||||||
inline bool IsDone() const { return mNumDependencies.load(memory_order_relaxed) == cDoneState; }
|
inline bool IsDone() const { return mNumDependencies.load(memory_order_relaxed) == cDoneState; }
|
||||||
|
|
||||||
|
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||||
|
/// Get the name of the job
|
||||||
|
const char * GetName() const { return mJobName; }
|
||||||
|
#endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||||
|
|
||||||
static constexpr uint32 cExecutingState = 0xe0e0e0e0; ///< Value of mNumDependencies when job is executing
|
static constexpr uint32 cExecutingState = 0xe0e0e0e0; ///< Value of mNumDependencies when job is executing
|
||||||
static constexpr uint32 cDoneState = 0xd0d0d0d0; ///< Value of mNumDependencies when job is done executing
|
static constexpr uint32 cDoneState = 0xd0d0d0d0; ///< Value of mNumDependencies when job is done executing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ void JobSystem::Job::RemoveDependencyAndQueue(int inCount)
|
||||||
mJobSystem->QueueJob(this);
|
mJobSystem->QueueJob(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JobSystem::JobHandle::sRemoveDependencies(JobHandle *inHandles, uint inNumHandles, int inCount)
|
void JobSystem::JobHandle::sRemoveDependencies(const JobHandle *inHandles, uint inNumHandles, int inCount)
|
||||||
{
|
{
|
||||||
JPH_PROFILE_FUNCTION();
|
JPH_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include <Jolt/Jolt.h>
|
||||||
|
|
||||||
|
#include <Jolt/Core/JobSystemSingleThreaded.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::Init(uint inMaxJobs)
|
||||||
|
{
|
||||||
|
mJobs.Init(inMaxJobs, inMaxJobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
JobHandle JobSystemSingleThreaded::CreateJob(const char *inJobName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies)
|
||||||
|
{
|
||||||
|
// Construct an object
|
||||||
|
uint32 index = mJobs.ConstructObject(inJobName, inColor, this, inJobFunction, inNumDependencies);
|
||||||
|
JPH_ASSERT(index != AvailableJobs::cInvalidObjectIndex);
|
||||||
|
Job *job = &mJobs.Get(index);
|
||||||
|
|
||||||
|
// Construct handle to keep a reference, the job is queued below and will immediately complete
|
||||||
|
JobHandle handle(job);
|
||||||
|
|
||||||
|
// If there are no dependencies, queue the job now
|
||||||
|
if (inNumDependencies == 0)
|
||||||
|
QueueJob(job);
|
||||||
|
|
||||||
|
// Return the handle
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::FreeJob(Job *inJob)
|
||||||
|
{
|
||||||
|
mJobs.DestructObject(inJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::QueueJob(Job *inJob)
|
||||||
|
{
|
||||||
|
inJob->Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::QueueJobs(Job **inJobs, uint inNumJobs)
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < inNumJobs; ++i)
|
||||||
|
QueueJob(inJobs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
JobSystem::Barrier *JobSystemSingleThreaded::CreateBarrier()
|
||||||
|
{
|
||||||
|
return &mDummyBarrier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::DestroyBarrier(Barrier *inBarrier)
|
||||||
|
{
|
||||||
|
// There's nothing to do here, the barrier is just a dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobSystemSingleThreaded::WaitForJobs(Barrier *inBarrier)
|
||||||
|
{
|
||||||
|
// There's nothing to do here, the barrier is just a dummy, we just execute the jobs immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/JobSystem.h>
|
||||||
|
#include <Jolt/Core/FixedSizeFreeList.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Implementation of a JobSystem without threads, runs jobs as soon as they are added
|
||||||
|
class JPH_EXPORT JobSystemSingleThreaded final : public JobSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
JobSystemSingleThreaded() = default;
|
||||||
|
explicit JobSystemSingleThreaded(uint inMaxJobs) { Init(inMaxJobs); }
|
||||||
|
|
||||||
|
/// Initialize the job system
|
||||||
|
/// @param inMaxJobs Max number of jobs that can be allocated at any time
|
||||||
|
void Init(uint inMaxJobs);
|
||||||
|
|
||||||
|
// See JobSystem
|
||||||
|
virtual int GetMaxConcurrency() const override { return 1; }
|
||||||
|
virtual JobHandle CreateJob(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies = 0) override;
|
||||||
|
virtual Barrier * CreateBarrier() override;
|
||||||
|
virtual void DestroyBarrier(Barrier *inBarrier) override;
|
||||||
|
virtual void WaitForJobs(Barrier *inBarrier) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Dummy implementation of Barrier, all jobs are executed immediately
|
||||||
|
class BarrierImpl : public Barrier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
|
// See Barrier
|
||||||
|
virtual void AddJob(const JobHandle &inJob) override { /* We don't need to track jobs */ }
|
||||||
|
virtual void AddJobs(const JobHandle *inHandles, uint inNumHandles) override { /* We don't need to track jobs */ }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Called by a Job to mark that it is finished
|
||||||
|
virtual void OnJobFinished(Job *inJob) override { /* We don't need to track jobs */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// See JobSystem
|
||||||
|
virtual void QueueJob(Job *inJob) override;
|
||||||
|
virtual void QueueJobs(Job **inJobs, uint inNumJobs) override;
|
||||||
|
virtual void FreeJob(Job *inJob) override;
|
||||||
|
|
||||||
|
/// Shared barrier since the barrier implementation does nothing
|
||||||
|
BarrierImpl mDummyBarrier;
|
||||||
|
|
||||||
|
/// Array of jobs (fixed size)
|
||||||
|
using AvailableJobs = FixedSizeFreeList<Job>;
|
||||||
|
AvailableJobs mJobs;
|
||||||
|
};
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
#ifndef JPH_COMPILER_MINGW
|
#ifndef JPH_COMPILER_MINGW
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#else
|
#else
|
||||||
|
|
@ -20,6 +22,9 @@
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_SUPPRESS_WARNING_POP
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef JPH_PLATFORM_LINUX
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
|
@ -43,8 +48,9 @@ JobSystemThreadPool::JobSystemThreadPool(uint inMaxJobs, uint inMaxBarriers, int
|
||||||
Init(inMaxJobs, inMaxBarriers, inNumThreads);
|
Init(inMaxJobs, inMaxBarriers, inNumThreads);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JobSystemThreadPool::StartThreads(int inNumThreads)
|
void JobSystemThreadPool::StartThreads([[maybe_unused]] int inNumThreads)
|
||||||
{
|
{
|
||||||
|
#if !defined(JPH_CPU_WASM) || defined(__EMSCRIPTEN_PTHREADS__) // If we're running without threads support we cannot create threads and we ignore the inNumThreads parameter
|
||||||
// Auto detect number of threads
|
// Auto detect number of threads
|
||||||
if (inNumThreads < 0)
|
if (inNumThreads < 0)
|
||||||
inNumThreads = thread::hardware_concurrency() - 1;
|
inNumThreads = thread::hardware_concurrency() - 1;
|
||||||
|
|
@ -66,6 +72,7 @@ void JobSystemThreadPool::StartThreads(int inNumThreads)
|
||||||
mThreads.reserve(inNumThreads);
|
mThreads.reserve(inNumThreads);
|
||||||
for (int i = 0; i < inNumThreads; ++i)
|
for (int i = 0; i < inNumThreads; ++i)
|
||||||
mThreads.emplace_back([this, i] { ThreadMain(i); });
|
mThreads.emplace_back([this, i] { ThreadMain(i); });
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JobSystemThreadPool::~JobSystemThreadPool()
|
JobSystemThreadPool::~JobSystemThreadPool()
|
||||||
|
|
@ -125,10 +132,10 @@ JobHandle JobSystemThreadPool::CreateJob(const char *inJobName, ColorArg inColor
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||||
}
|
}
|
||||||
Job *job = &mJobs.Get(index);
|
Job *job = &mJobs.Get(index);
|
||||||
|
|
||||||
// Construct handle to keep a reference, the job is queued below and may immediately complete
|
// Construct handle to keep a reference, the job is queued below and may immediately complete
|
||||||
JobHandle handle(job);
|
JobHandle handle(job);
|
||||||
|
|
||||||
// If there are no dependencies, queue the job now
|
// If there are no dependencies, queue the job now
|
||||||
if (inNumDependencies == 0)
|
if (inNumDependencies == 0)
|
||||||
QueueJob(job);
|
QueueJob(job);
|
||||||
|
|
@ -170,12 +177,12 @@ void JobSystemThreadPool::QueueJobInternal(Job *inJob)
|
||||||
// We calculated the head outside of the loop, update head (and we also need to update tail to prevent it from passing head)
|
// We calculated the head outside of the loop, update head (and we also need to update tail to prevent it from passing head)
|
||||||
head = GetHead();
|
head = GetHead();
|
||||||
old_value = mTail;
|
old_value = mTail;
|
||||||
|
|
||||||
// Second check if there's space in the queue
|
// Second check if there's space in the queue
|
||||||
if (old_value - head >= cQueueLength)
|
if (old_value - head >= cQueueLength)
|
||||||
{
|
{
|
||||||
// Wake up all threads in order to ensure that they can clear any nullptrs they may not have processed yet
|
// Wake up all threads in order to ensure that they can clear any nullptrs they may not have processed yet
|
||||||
mSemaphore.Release((uint)mThreads.size());
|
mSemaphore.Release((uint)mThreads.size());
|
||||||
|
|
||||||
// Sleep a little (we have to wait for other threads to update their head pointer in order for us to be able to continue)
|
// Sleep a little (we have to wait for other threads to update their head pointer in order for us to be able to continue)
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||||
|
|
@ -187,7 +194,7 @@ void JobSystemThreadPool::QueueJobInternal(Job *inJob)
|
||||||
Job *expected_job = nullptr;
|
Job *expected_job = nullptr;
|
||||||
bool success = mQueue[old_value & (cQueueLength - 1)].compare_exchange_strong(expected_job, inJob);
|
bool success = mQueue[old_value & (cQueueLength - 1)].compare_exchange_strong(expected_job, inJob);
|
||||||
|
|
||||||
// Regardless of who wrote the slot, we will update the tail (if the successful thread got scheduled out
|
// Regardless of who wrote the slot, we will update the tail (if the successful thread got scheduled out
|
||||||
// after writing the pointer we still want to be able to continue)
|
// after writing the pointer we still want to be able to continue)
|
||||||
mTail.compare_exchange_strong(old_value, old_value + 1);
|
mTail.compare_exchange_strong(old_value, old_value + 1);
|
||||||
|
|
||||||
|
|
@ -230,39 +237,74 @@ void JobSystemThreadPool::QueueJobs(Job **inJobs, uint inNumJobs)
|
||||||
mSemaphore.Release(min(inNumJobs, (uint)mThreads.size()));
|
mSemaphore.Release(min(inNumJobs, (uint)mThreads.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
|
|
||||||
// Sets the current thread name in MSVC debugger
|
#if !defined(JPH_COMPILER_MINGW) // MinGW doesn't support __try/__except)
|
||||||
static void SetThreadName(const char *inName)
|
// Sets the current thread name in MSVC debugger
|
||||||
{
|
static void RaiseThreadNameException(const char *inName)
|
||||||
#pragma pack(push, 8)
|
|
||||||
|
|
||||||
struct THREADNAME_INFO
|
|
||||||
{
|
{
|
||||||
DWORD dwType; // Must be 0x1000.
|
#pragma pack(push, 8)
|
||||||
LPCSTR szName; // Pointer to name (in user addr space).
|
|
||||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
|
||||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma pack(pop)
|
struct THREADNAME_INFO
|
||||||
|
{
|
||||||
|
DWORD dwType; // Must be 0x1000.
|
||||||
|
LPCSTR szName; // Pointer to name (in user addr space).
|
||||||
|
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||||
|
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||||
|
};
|
||||||
|
|
||||||
THREADNAME_INFO info;
|
#pragma pack(pop)
|
||||||
info.dwType = 0x1000;
|
|
||||||
info.szName = inName;
|
|
||||||
info.dwThreadID = (DWORD)-1;
|
|
||||||
info.dwFlags = 0;
|
|
||||||
|
|
||||||
__try
|
THREADNAME_INFO info;
|
||||||
{
|
info.dwType = 0x1000;
|
||||||
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
|
info.szName = inName;
|
||||||
|
info.dwThreadID = (DWORD)-1;
|
||||||
|
info.dwFlags = 0;
|
||||||
|
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
|
||||||
|
}
|
||||||
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
#endif // !JPH_COMPILER_MINGW
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW
|
static void SetThreadName(const char* inName)
|
||||||
|
{
|
||||||
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
|
|
||||||
|
// Suppress casting warning, it's fine here as GetProcAddress doesn't really return a FARPROC
|
||||||
|
JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type
|
||||||
|
JPH_CLANG_SUPPRESS_WARNING("-Wcast-function-type-strict") // error : cast from 'FARPROC' (aka 'long long (*)()') to 'SetThreadDescriptionFunc' (aka 'long (*)(void *, const wchar_t *)') converts to incompatible function type
|
||||||
|
JPH_MSVC_SUPPRESS_WARNING(4191) // reinterpret_cast' : unsafe conversion from 'FARPROC' to 'SetThreadDescriptionFunc'. Calling this function through the result pointer may cause your program to fail
|
||||||
|
|
||||||
|
using SetThreadDescriptionFunc = HRESULT(WINAPI*)(HANDLE hThread, PCWSTR lpThreadDescription);
|
||||||
|
static SetThreadDescriptionFunc SetThreadDescription = reinterpret_cast<SetThreadDescriptionFunc>(GetProcAddress(GetModuleHandleW(L"Kernel32.dll"), "SetThreadDescription"));
|
||||||
|
|
||||||
|
JPH_SUPPRESS_WARNING_POP
|
||||||
|
|
||||||
|
if (SetThreadDescription)
|
||||||
|
{
|
||||||
|
wchar_t name_buffer[64] = { 0 };
|
||||||
|
if (MultiByteToWideChar(CP_UTF8, 0, inName, -1, name_buffer, sizeof(name_buffer) / sizeof(wchar_t) - 1) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetThreadDescription(GetCurrentThread(), name_buffer);
|
||||||
|
}
|
||||||
|
#if !defined(JPH_COMPILER_MINGW)
|
||||||
|
else if (IsDebuggerPresent())
|
||||||
|
RaiseThreadNameException(inName);
|
||||||
|
#endif // !JPH_COMPILER_MINGW
|
||||||
|
}
|
||||||
|
#elif defined(JPH_PLATFORM_LINUX)
|
||||||
|
static void SetThreadName(const char *inName)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(strlen(inName) < 16); // String will be truncated if it is longer
|
||||||
|
prctl(PR_SET_NAME, inName, 0, 0, 0);
|
||||||
|
}
|
||||||
|
#endif // JPH_PLATFORM_LINUX
|
||||||
|
|
||||||
void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -270,7 +312,7 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
||||||
char name[64];
|
char name[64];
|
||||||
snprintf(name, sizeof(name), "Worker %d", int(inThreadIndex + 1));
|
snprintf(name, sizeof(name), "Worker %d", int(inThreadIndex + 1));
|
||||||
|
|
||||||
#if defined(JPH_PLATFORM_WINDOWS) && !defined(JPH_COMPILER_MINGW)
|
#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_PLATFORM_LINUX)
|
||||||
SetThreadName(name);
|
SetThreadName(name);
|
||||||
#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW
|
#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW
|
||||||
|
|
||||||
|
|
@ -280,6 +322,9 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
||||||
|
|
||||||
JPH_PROFILE_THREAD_START(name);
|
JPH_PROFILE_THREAD_START(name);
|
||||||
|
|
||||||
|
// Call the thread init function
|
||||||
|
mThreadInitFunction(inThreadIndex);
|
||||||
|
|
||||||
atomic<uint> &head = mHeads[inThreadIndex];
|
atomic<uint> &head = mHeads[inThreadIndex];
|
||||||
|
|
||||||
while (!mQuit)
|
while (!mQuit)
|
||||||
|
|
@ -310,6 +355,9 @@ void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call the thread exit function
|
||||||
|
mThreadExitFunction(inThreadIndex);
|
||||||
|
|
||||||
JPH_PROFILE_THREAD_END();
|
JPH_PROFILE_THREAD_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ JPH_NAMESPACE_BEGIN
|
||||||
using std::thread;
|
using std::thread;
|
||||||
|
|
||||||
/// Implementation of a JobSystem using a thread pool
|
/// Implementation of a JobSystem using a thread pool
|
||||||
///
|
///
|
||||||
/// Note that this is considered an example implementation. It is expected that when you integrate
|
/// Note that this is considered an example implementation. It is expected that when you integrate
|
||||||
/// the physics engine into your own project that you'll provide your own implementation of the
|
/// the physics engine into your own project that you'll provide your own implementation of the
|
||||||
/// JobSystem built on top of whatever job system your project uses.
|
/// JobSystem built on top of whatever job system your project uses.
|
||||||
class JobSystemThreadPool final : public JobSystemWithBarrier
|
class JPH_EXPORT JobSystemThreadPool final : public JobSystemWithBarrier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -33,10 +33,15 @@ public:
|
||||||
JobSystemThreadPool() = default;
|
JobSystemThreadPool() = default;
|
||||||
virtual ~JobSystemThreadPool() override;
|
virtual ~JobSystemThreadPool() override;
|
||||||
|
|
||||||
|
/// Functions to call when a thread is initialized or exits, must be set before calling Init()
|
||||||
|
using InitExitFunction = function<void(int)>;
|
||||||
|
void SetThreadInitFunction(const InitExitFunction &inInitFunction) { mThreadInitFunction = inInitFunction; }
|
||||||
|
void SetThreadExitFunction(const InitExitFunction &inExitFunction) { mThreadExitFunction = inExitFunction; }
|
||||||
|
|
||||||
/// Initialize the thread pool
|
/// Initialize the thread pool
|
||||||
/// @param inMaxJobs Max number of jobs that can be allocated at any time
|
/// @param inMaxJobs Max number of jobs that can be allocated at any time
|
||||||
/// @param inMaxBarriers Max number of barriers that can be allocated at any time
|
/// @param inMaxBarriers Max number of barriers that can be allocated at any time
|
||||||
/// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to autodetect the amount of CPU's.
|
/// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more because the main thread will also run jobs while waiting for a barrier to complete). Use -1 to auto detect the amount of CPU's.
|
||||||
void Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1);
|
void Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1);
|
||||||
|
|
||||||
// See JobSystem
|
// See JobSystem
|
||||||
|
|
@ -45,7 +50,7 @@ public:
|
||||||
|
|
||||||
/// Change the max concurrency after initialization
|
/// Change the max concurrency after initialization
|
||||||
void SetNumThreads(int inNumThreads) { StopThreads(); StartThreads(inNumThreads); }
|
void SetNumThreads(int inNumThreads) { StopThreads(); StartThreads(inNumThreads); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// See JobSystem
|
// See JobSystem
|
||||||
virtual void QueueJob(Job *inJob) override;
|
virtual void QueueJob(Job *inJob) override;
|
||||||
|
|
@ -56,7 +61,7 @@ private:
|
||||||
/// Start/stop the worker threads
|
/// Start/stop the worker threads
|
||||||
void StartThreads(int inNumThreads);
|
void StartThreads(int inNumThreads);
|
||||||
void StopThreads();
|
void StopThreads();
|
||||||
|
|
||||||
/// Entry point for a thread
|
/// Entry point for a thread
|
||||||
void ThreadMain(int inThreadIndex);
|
void ThreadMain(int inThreadIndex);
|
||||||
|
|
||||||
|
|
@ -66,6 +71,10 @@ private:
|
||||||
/// Internal helper function to queue a job
|
/// Internal helper function to queue a job
|
||||||
inline void QueueJobInternal(Job *inJob);
|
inline void QueueJobInternal(Job *inJob);
|
||||||
|
|
||||||
|
/// Functions to call when initializing or exiting a thread
|
||||||
|
InitExitFunction mThreadInitFunction = [](int) { };
|
||||||
|
InitExitFunction mThreadExitFunction = [](int) { };
|
||||||
|
|
||||||
/// Array of jobs (fixed size)
|
/// Array of jobs (fixed size)
|
||||||
using AvailableJobs = FixedSizeFreeList<Job>;
|
using AvailableJobs = FixedSizeFreeList<Job>;
|
||||||
AvailableJobs mJobs;
|
AvailableJobs mJobs;
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ void JobSystemWithBarrier::BarrierImpl::Wait()
|
||||||
|
|
||||||
// Loop through the jobs and erase jobs from the beginning of the list that are done
|
// Loop through the jobs and erase jobs from the beginning of the list that are done
|
||||||
while (mJobReadIndex < mJobWriteIndex)
|
while (mJobReadIndex < mJobWriteIndex)
|
||||||
{
|
{
|
||||||
atomic<Job *> &job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
atomic<Job *> &job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
||||||
Job *job_ptr = job.load();
|
Job *job_ptr = job.load();
|
||||||
if (job_ptr == nullptr || !job_ptr->IsDone())
|
if (job_ptr == nullptr || !job_ptr->IsDone())
|
||||||
|
|
@ -146,15 +146,18 @@ void JobSystemWithBarrier::BarrierImpl::Wait()
|
||||||
} while (has_executed);
|
} while (has_executed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for another thread to wake us when either there is more work to do or when all jobs have completed
|
// Wait for another thread to wake us when either there is more work to do or when all jobs have completed.
|
||||||
int num_to_acquire = max(1, mSemaphore.GetValue()); // When there have been multiple releases, we acquire them all at the same time to avoid needlessly spinning on executing jobs
|
// When there have been multiple releases, we acquire them all at the same time to avoid needlessly spinning on executing jobs.
|
||||||
|
// Note that using GetValue is inherently unsafe since we can read a stale value, but this is not an issue here as this is the only
|
||||||
|
// place where we acquire the semaphore. Other threads only release it, so we can only read a value that is lower or equal to the actual value.
|
||||||
|
int num_to_acquire = max(1, mSemaphore.GetValue());
|
||||||
mSemaphore.Acquire(num_to_acquire);
|
mSemaphore.Acquire(num_to_acquire);
|
||||||
mNumToAcquire -= num_to_acquire;
|
mNumToAcquire -= num_to_acquire;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All jobs should be done now, release them
|
// All jobs should be done now, release them
|
||||||
while (mJobReadIndex < mJobWriteIndex)
|
while (mJobReadIndex < mJobWriteIndex)
|
||||||
{
|
{
|
||||||
atomic<Job *> &job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
atomic<Job *> &job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
||||||
Job *job_ptr = job.load();
|
Job *job_ptr = job.load();
|
||||||
JPH_ASSERT(job_ptr != nullptr && job_ptr->IsDone());
|
JPH_ASSERT(job_ptr != nullptr && job_ptr->IsDone());
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// * JobSystem::QueueJob/QueueJobs
|
/// * JobSystem::QueueJob/QueueJobs
|
||||||
///
|
///
|
||||||
/// See instructions in JobSystem for more information on how to implement these.
|
/// See instructions in JobSystem for more information on how to implement these.
|
||||||
class JobSystemWithBarrier : public JobSystem
|
class JPH_EXPORT JobSystemWithBarrier : public JobSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -70,7 +70,7 @@ private:
|
||||||
/// Jobs queue for the barrier
|
/// Jobs queue for the barrier
|
||||||
static constexpr uint cMaxJobs = 2048;
|
static constexpr uint cMaxJobs = 2048;
|
||||||
static_assert(IsPowerOf2(cMaxJobs)); // We do bit operations and require max jobs to be a power of 2
|
static_assert(IsPowerOf2(cMaxJobs)); // We do bit operations and require max jobs to be a power of 2
|
||||||
atomic<Job *> mJobs[cMaxJobs]; ///< List of jobs that are part of this barrier, nullptrs for empty slots
|
atomic<Job *> mJobs[cMaxJobs]; ///< List of jobs that are part of this barrier, nullptrs for empty slots
|
||||||
alignas(JPH_CACHE_LINE_SIZE) atomic<uint> mJobReadIndex { 0 }; ///< First job that could be valid (modulo cMaxJobs), can be nullptr if other thread is still working on adding the job
|
alignas(JPH_CACHE_LINE_SIZE) atomic<uint> mJobReadIndex { 0 }; ///< First job that could be valid (modulo cMaxJobs), can be nullptr if other thread is still working on adding the job
|
||||||
alignas(JPH_CACHE_LINE_SIZE) atomic<uint> mJobWriteIndex { 0 }; ///< First job that can be written (modulo cMaxJobs)
|
alignas(JPH_CACHE_LINE_SIZE) atomic<uint> mJobWriteIndex { 0 }; ///< First job that can be written (modulo cMaxJobs)
|
||||||
atomic<int> mNumToAcquire { 0 }; ///< Number of times the semaphore has been released, the barrier should acquire the semaphore this many times (written at the same time as mJobWriteIndex so ok to put in same cache line)
|
atomic<int> mNumToAcquire { 0 }; ///< Number of times the semaphore has been released, the barrier should acquire the semaphore this many times (written at the same time as mJobWriteIndex so ok to put in same cache line)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ float LinearCurve::GetValue(float inX) const
|
||||||
if (mPoints.empty())
|
if (mPoints.empty())
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
|
|
||||||
Points::const_iterator i2 = lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; });
|
Points::const_iterator i2 = std::lower_bound(mPoints.begin(), mPoints.end(), inX, [](const Point &inPoint, float inValue) { return inPoint.mX < inValue; });
|
||||||
|
|
||||||
if (i2 == mPoints.begin())
|
if (i2 == mPoints.begin())
|
||||||
return mPoints.front().mY;
|
return mPoints.front().mY;
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ class StreamOut;
|
||||||
class StreamIn;
|
class StreamIn;
|
||||||
|
|
||||||
// A set of points (x, y) that form a linear curve
|
// A set of points (x, y) that form a linear curve
|
||||||
class LinearCurve
|
class JPH_EXPORT LinearCurve
|
||||||
{
|
{
|
||||||
public:
|
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, LinearCurve)
|
||||||
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(LinearCurve)
|
|
||||||
|
|
||||||
|
public:
|
||||||
/// A point on the curve
|
/// A point on the curve
|
||||||
class Point
|
class Point
|
||||||
{
|
{
|
||||||
public:
|
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(JPH_EXPORT, Point)
|
||||||
JPH_DECLARE_SERIALIZABLE_NON_VIRTUAL(Point)
|
|
||||||
|
|
||||||
|
public:
|
||||||
float mX = 0.0f;
|
float mX = 0.0f;
|
||||||
float mY = 0.0f;
|
float mY = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public:
|
||||||
inline T * FromOffset(uint32 inOffset) const;
|
inline T * FromOffset(uint32 inOffset) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8 * mObjectStore = nullptr; ///< This contains a contigous list of objects (possibly of varying size)
|
uint8 * mObjectStore = nullptr; ///< This contains a contiguous list of objects (possibly of varying size)
|
||||||
uint32 mObjectStoreSizeBytes = 0; ///< The size of mObjectStore in bytes
|
uint32 mObjectStoreSizeBytes = 0; ///< The size of mObjectStore in bytes
|
||||||
atomic<uint32> mWriteOffset { 0 }; ///< Next offset to write to in mObjectStore
|
atomic<uint32> mWriteOffset { 0 }; ///< Next offset to write to in mObjectStore
|
||||||
};
|
};
|
||||||
|
|
@ -84,7 +84,7 @@ public:
|
||||||
/// Remove all elements.
|
/// Remove all elements.
|
||||||
/// Note that this cannot happen simultaneously with adding new elements.
|
/// Note that this cannot happen simultaneously with adding new elements.
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
/// Get the current amount of buckets that the map is using
|
/// Get the current amount of buckets that the map is using
|
||||||
uint32 GetNumBuckets() const { return mNumBuckets; }
|
uint32 GetNumBuckets() const { return mNumBuckets; }
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ public:
|
||||||
/// Multiple threads can be inserting in the map at the same time.
|
/// Multiple threads can be inserting in the map at the same time.
|
||||||
template <class... Params>
|
template <class... Params>
|
||||||
inline KeyValue * Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams);
|
inline KeyValue * Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams);
|
||||||
|
|
||||||
/// Find an element, returns null if not found
|
/// Find an element, returns null if not found
|
||||||
inline const KeyValue * Find(const Key &inKey, uint64 inKeyHash) const;
|
inline const KeyValue * Find(const Key &inKey, uint64 inKeyHash) const;
|
||||||
|
|
||||||
|
|
@ -145,22 +145,22 @@ public:
|
||||||
bool operator != (const Iterator &inRHS) const { return !(*this == inRHS); }
|
bool operator != (const Iterator &inRHS) const { return !(*this == inRHS); }
|
||||||
|
|
||||||
/// Convert to key value pair
|
/// Convert to key value pair
|
||||||
KeyValue & operator * ();
|
KeyValue & operator * ();
|
||||||
|
|
||||||
/// Next item
|
/// Next item
|
||||||
Iterator & operator ++ ();
|
Iterator & operator ++ ();
|
||||||
|
|
||||||
MapType * mMap;
|
MapType * mMap;
|
||||||
uint32 mBucket;
|
uint32 mBucket;
|
||||||
uint32 mOffset;
|
uint32 mOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Iterate over the map, note that it is not safe to do this in parallel to Clear().
|
/// Iterate over the map, note that it is not safe to do this in parallel to Clear().
|
||||||
/// It is safe to do this while adding elements to the map, but newly added elements may or may not be returned by the iterator.
|
/// It is safe to do this while adding elements to the map, but newly added elements may or may not be returned by the iterator.
|
||||||
Iterator begin();
|
Iterator begin();
|
||||||
Iterator end();
|
Iterator end();
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
/// Output stats about this map to the log
|
/// Output stats about this map to the log
|
||||||
void TraceStats() const;
|
void TraceStats() const;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,10 @@ inline T *LFHMAllocator::FromOffset(uint32 inOffset) const
|
||||||
// LFHMAllocatorContext
|
// LFHMAllocatorContext
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
inline LFHMAllocatorContext::LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize) :
|
inline LFHMAllocatorContext::LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize) :
|
||||||
mAllocator(inAllocator),
|
mAllocator(inAllocator),
|
||||||
mBlockSize(inBlockSize)
|
mBlockSize(inBlockSize)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset)
|
inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset)
|
||||||
|
|
@ -89,7 +89,7 @@ inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, ui
|
||||||
JPH_ASSERT(IsPowerOf2(inAlignment));
|
JPH_ASSERT(IsPowerOf2(inAlignment));
|
||||||
uint32 alignment_mask = inAlignment - 1;
|
uint32 alignment_mask = inAlignment - 1;
|
||||||
uint32 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
|
uint32 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
|
||||||
|
|
||||||
// Check if we have space
|
// Check if we have space
|
||||||
if (mEnd - mBegin < inSize + alignment)
|
if (mEnd - mBegin < inSize + alignment)
|
||||||
{
|
{
|
||||||
|
|
@ -98,7 +98,7 @@ inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, ui
|
||||||
|
|
||||||
// Update alignment
|
// Update alignment
|
||||||
alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
|
alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
|
||||||
|
|
||||||
// Check if we have space again
|
// Check if we have space again
|
||||||
if (mEnd - mBegin < inSize + alignment)
|
if (mEnd - mBegin < inSize + alignment)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -163,7 +163,7 @@ void LockFreeHashMap<Key, Value>::SetNumBuckets(uint32 inNumBuckets)
|
||||||
JPH_ASSERT(inNumBuckets <= mMaxBuckets);
|
JPH_ASSERT(inNumBuckets <= mMaxBuckets);
|
||||||
JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets));
|
JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets));
|
||||||
|
|
||||||
mNumBuckets = inNumBuckets;
|
mNumBuckets = inNumBuckets;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Value>
|
template <class Key, class Value>
|
||||||
|
|
@ -189,7 +189,7 @@ inline typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Valu
|
||||||
// Construct the key/value pair
|
// Construct the key/value pair
|
||||||
KeyValue *kv = mAllocator.template FromOffset<KeyValue>(write_offset);
|
KeyValue *kv = mAllocator.template FromOffset<KeyValue>(write_offset);
|
||||||
JPH_ASSERT(intptr_t(kv) % alignof(KeyValue) == 0);
|
JPH_ASSERT(intptr_t(kv) % alignof(KeyValue) == 0);
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
memset(kv, 0xcd, size);
|
memset(kv, 0xcd, size);
|
||||||
#endif
|
#endif
|
||||||
kv->mKey = inKey;
|
kv->mKey = inKey;
|
||||||
|
|
@ -280,7 +280,7 @@ typename LockFreeHashMap<Key, Value>::KeyValue &LockFreeHashMap<Key, Value>::Ite
|
||||||
JPH_ASSERT(mOffset != cInvalidHandle);
|
JPH_ASSERT(mOffset != cInvalidHandle);
|
||||||
|
|
||||||
return *mMap->mAllocator.template FromOffset<KeyValue>(mOffset);
|
return *mMap->mAllocator.template FromOffset<KeyValue>(mOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Key, class Value>
|
template <class Key, class Value>
|
||||||
typename LockFreeHashMap<Key, Value>::Iterator &LockFreeHashMap<Key, Value>::Iterator::operator++ ()
|
typename LockFreeHashMap<Key, Value>::Iterator &LockFreeHashMap<Key, Value>::Iterator::operator++ ()
|
||||||
|
|
@ -311,7 +311,7 @@ typename LockFreeHashMap<Key, Value>::Iterator &LockFreeHashMap<Key, Value>::Ite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
|
|
||||||
template <class Key, class Value>
|
template <class Key, class Value>
|
||||||
void LockFreeHashMap<Key, Value>::TraceStats() const
|
void LockFreeHashMap<Key, Value>::TraceStats() const
|
||||||
|
|
@ -339,8 +339,8 @@ void LockFreeHashMap<Key, Value>::TraceStats() const
|
||||||
histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++;
|
histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace("max_objects_per_bucket = %d, num_buckets = %d, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects);
|
Trace("max_objects_per_bucket = %d, num_buckets = %u, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects);
|
||||||
|
|
||||||
for (int i = 0; i < cMaxPerBucket; ++i)
|
for (int i = 0; i < cMaxPerBucket; ++i)
|
||||||
if (histogram[i] != 0)
|
if (histogram[i] != 0)
|
||||||
Trace("%d: %d", i, histogram[i]);
|
Trace("%d: %d", i, histogram[i]);
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,16 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Allocate)(size_t inSize)
|
JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Allocate)(size_t inSize)
|
||||||
{
|
{
|
||||||
|
JPH_ASSERT(inSize > 0);
|
||||||
return malloc(inSize);
|
return malloc(inSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(Reallocate)(void *inBlock, [[maybe_unused]] size_t inOldSize, size_t inNewSize)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inNewSize > 0);
|
||||||
|
return realloc(inBlock, inNewSize);
|
||||||
|
}
|
||||||
|
|
||||||
JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock)
|
JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock)
|
||||||
{
|
{
|
||||||
free(inBlock);
|
free(inBlock);
|
||||||
|
|
@ -31,13 +38,19 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(Free)(void *inBlock)
|
||||||
|
|
||||||
JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(AlignedAllocate)(size_t inSize, size_t inAlignment)
|
JPH_ALLOC_SCOPE void *JPH_ALLOC_FN(AlignedAllocate)(size_t inSize, size_t inAlignment)
|
||||||
{
|
{
|
||||||
|
JPH_ASSERT(inSize > 0 && inAlignment > 0);
|
||||||
|
|
||||||
#if defined(JPH_PLATFORM_WINDOWS)
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
// Microsoft doesn't implement C++17 std::aligned_alloc
|
// Microsoft doesn't implement posix_memalign
|
||||||
return _aligned_malloc(inSize, inAlignment);
|
return _aligned_malloc(inSize, inAlignment);
|
||||||
#elif defined(JPH_PLATFORM_ANDROID)
|
|
||||||
return memalign(inAlignment, AlignUp(inSize, inAlignment));
|
|
||||||
#else
|
#else
|
||||||
return std::aligned_alloc(inAlignment, AlignUp(inSize, inAlignment));
|
void *block = nullptr;
|
||||||
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
|
JPH_GCC_SUPPRESS_WARNING("-Wunused-result")
|
||||||
|
JPH_CLANG_SUPPRESS_WARNING("-Wunused-result")
|
||||||
|
posix_memalign(&block, inAlignment, inSize);
|
||||||
|
JPH_SUPPRESS_WARNING_POP
|
||||||
|
return block;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,16 +58,15 @@ JPH_ALLOC_SCOPE void JPH_ALLOC_FN(AlignedFree)(void *inBlock)
|
||||||
{
|
{
|
||||||
#if defined(JPH_PLATFORM_WINDOWS)
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
_aligned_free(inBlock);
|
_aligned_free(inBlock);
|
||||||
#elif defined(JPH_PLATFORM_ANDROID)
|
|
||||||
free(inBlock);
|
|
||||||
#else
|
#else
|
||||||
std::free(inBlock);
|
free(inBlock);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
|
||||||
AllocateFunction Allocate = nullptr;
|
AllocateFunction Allocate = nullptr;
|
||||||
|
ReallocateFunction Reallocate = nullptr;
|
||||||
FreeFunction Free = nullptr;
|
FreeFunction Free = nullptr;
|
||||||
AlignedAllocateFunction AlignedAllocate = nullptr;
|
AlignedAllocateFunction AlignedAllocate = nullptr;
|
||||||
AlignedFreeFunction AlignedFree = nullptr;
|
AlignedFreeFunction AlignedFree = nullptr;
|
||||||
|
|
@ -62,6 +74,7 @@ AlignedFreeFunction AlignedFree = nullptr;
|
||||||
void RegisterDefaultAllocator()
|
void RegisterDefaultAllocator()
|
||||||
{
|
{
|
||||||
Allocate = AllocateImpl;
|
Allocate = AllocateImpl;
|
||||||
|
Reallocate = ReallocateImpl;
|
||||||
Free = FreeImpl;
|
Free = FreeImpl;
|
||||||
AlignedAllocate = AlignedAllocateImpl;
|
AlignedAllocate = AlignedAllocateImpl;
|
||||||
AlignedFree = AlignedFreeImpl;
|
AlignedFree = AlignedFreeImpl;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// Normal memory allocation, must be at least 8 byte aligned on 32 bit platform and 16 byte aligned on 64 bit platform
|
// Normal memory allocation, must be at least 8 byte aligned on 32 bit platform and 16 byte aligned on 64 bit platform
|
||||||
using AllocateFunction = void *(*)(size_t inSize);
|
using AllocateFunction = void *(*)(size_t inSize);
|
||||||
|
using ReallocateFunction = void *(*)(void *inBlock, size_t inOldSize, size_t inNewSize);
|
||||||
using FreeFunction = void (*)(void *inBlock);
|
using FreeFunction = void (*)(void *inBlock);
|
||||||
|
|
||||||
// Aligned memory allocation
|
// Aligned memory allocation
|
||||||
|
|
@ -17,13 +18,14 @@ using AlignedAllocateFunction = void *(*)(size_t inSize, size_t inAlignment);
|
||||||
using AlignedFreeFunction = void (*)(void *inBlock);
|
using AlignedFreeFunction = void (*)(void *inBlock);
|
||||||
|
|
||||||
// User defined allocation / free functions
|
// User defined allocation / free functions
|
||||||
extern AllocateFunction Allocate;
|
JPH_EXPORT extern AllocateFunction Allocate;
|
||||||
extern FreeFunction Free;
|
JPH_EXPORT extern ReallocateFunction Reallocate;
|
||||||
extern AlignedAllocateFunction AlignedAllocate;
|
JPH_EXPORT extern FreeFunction Free;
|
||||||
extern AlignedFreeFunction AlignedFree;
|
JPH_EXPORT extern AlignedAllocateFunction AlignedAllocate;
|
||||||
|
JPH_EXPORT extern AlignedFreeFunction AlignedFree;
|
||||||
|
|
||||||
/// Register platform default allocation / free functions
|
/// Register platform default allocation / free functions
|
||||||
void RegisterDefaultAllocator();
|
JPH_EXPORT void RegisterDefaultAllocator();
|
||||||
|
|
||||||
/// Macro to override the new and delete functions
|
/// Macro to override the new and delete functions
|
||||||
#define JPH_OVERRIDE_NEW_DELETE \
|
#define JPH_OVERRIDE_NEW_DELETE \
|
||||||
|
|
@ -32,17 +34,22 @@ void RegisterDefaultAllocator();
|
||||||
JPH_INLINE void *operator new[] (size_t inCount) { return JPH::Allocate(inCount); } \
|
JPH_INLINE void *operator new[] (size_t inCount) { return JPH::Allocate(inCount); } \
|
||||||
JPH_INLINE void operator delete[] (void *inPointer) noexcept { JPH::Free(inPointer); } \
|
JPH_INLINE void operator delete[] (void *inPointer) noexcept { JPH::Free(inPointer); } \
|
||||||
JPH_INLINE void *operator new (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
|
JPH_INLINE void *operator new (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
|
||||||
JPH_INLINE void operator delete (void *inPointer, std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \
|
JPH_INLINE void operator delete (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \
|
||||||
JPH_INLINE void *operator new[] (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
|
JPH_INLINE void *operator new[] (size_t inCount, std::align_val_t inAlignment) { return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
|
||||||
JPH_INLINE void operator delete[] (void *inPointer, std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); }
|
JPH_INLINE void operator delete[] (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept { JPH::AlignedFree(inPointer); } \
|
||||||
|
JPH_INLINE void *operator new ([[maybe_unused]] size_t inCount, void *inPointer) noexcept { return inPointer; } \
|
||||||
|
JPH_INLINE void operator delete ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ } \
|
||||||
|
JPH_INLINE void *operator new[] ([[maybe_unused]] size_t inCount, void *inPointer) noexcept { return inPointer; } \
|
||||||
|
JPH_INLINE void operator delete[] ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ }
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// Directly define the allocation functions
|
// Directly define the allocation functions
|
||||||
void *Allocate(size_t inSize);
|
JPH_EXPORT void *Allocate(size_t inSize);
|
||||||
void Free(void *inBlock);
|
JPH_EXPORT void *Reallocate(void *inBlock, size_t inOldSize, size_t inNewSize);
|
||||||
void *AlignedAllocate(size_t inSize, size_t inAlignment);
|
JPH_EXPORT void Free(void *inBlock);
|
||||||
void AlignedFree(void *inBlock);
|
JPH_EXPORT void *AlignedAllocate(size_t inSize, size_t inAlignment);
|
||||||
|
JPH_EXPORT void AlignedFree(void *inBlock);
|
||||||
|
|
||||||
// Don't implement allocator registering
|
// Don't implement allocator registering
|
||||||
inline void RegisterDefaultAllocator() { }
|
inline void RegisterDefaultAllocator() { }
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ using SharedMutexBase = shared_mutex;
|
||||||
|
|
||||||
#if defined(JPH_ENABLE_ASSERTS) || defined(JPH_PROFILE_ENABLED) || defined(JPH_EXTERNAL_PROFILE)
|
#if defined(JPH_ENABLE_ASSERTS) || defined(JPH_PROFILE_ENABLED) || defined(JPH_EXTERNAL_PROFILE)
|
||||||
|
|
||||||
/// Very simple wrapper around MutexBase which tracks lock contention in the profiler
|
/// Very simple wrapper around MutexBase which tracks lock contention in the profiler
|
||||||
/// and asserts that locks/unlocks take place on the same thread
|
/// and asserts that locks/unlocks take place on the same thread
|
||||||
class Mutex : public MutexBase
|
class Mutex : public MutexBase
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,13 @@ public:
|
||||||
|
|
||||||
/// Initialization
|
/// Initialization
|
||||||
/// @param inNumMutexes The amount of mutexes to allocate
|
/// @param inNumMutexes The amount of mutexes to allocate
|
||||||
void Init(uint inNumMutexes)
|
void Init(uint inNumMutexes)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(mMutexStorage == nullptr);
|
JPH_ASSERT(mMutexStorage == nullptr);
|
||||||
JPH_ASSERT(inNumMutexes > 0 && IsPowerOf2(inNumMutexes));
|
JPH_ASSERT(inNumMutexes > 0 && IsPowerOf2(inNumMutexes));
|
||||||
|
|
||||||
mMutexStorage = new MutexStorage[inNumMutexes];
|
mMutexStorage = new MutexStorage[inNumMutexes];
|
||||||
mNumMutexes = inNumMutexes;
|
mNumMutexes = inNumMutexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the number of mutexes that were allocated
|
/// Get the number of mutexes that were allocated
|
||||||
|
|
@ -45,7 +45,7 @@ public:
|
||||||
/// Convert an object index to a mutex index
|
/// Convert an object index to a mutex index
|
||||||
inline uint32 GetMutexIndex(uint32 inObjectIndex) const
|
inline uint32 GetMutexIndex(uint32 inObjectIndex) const
|
||||||
{
|
{
|
||||||
std::hash<uint32> hasher;
|
Hash<uint32> hasher;
|
||||||
return hasher(inObjectIndex) & (mNumMutexes - 1);
|
return hasher(inObjectIndex) & (mNumMutexes - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Class that makes another class non-copyable. Usage: Inherit from NonCopyable.
|
/// Class that makes another class non-copyable. Usage: Inherit from NonCopyable.
|
||||||
class NonCopyable
|
class JPH_EXPORT NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NonCopyable() = default;
|
NonCopyable() = default;
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,56 @@ JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
#ifdef JPH_PROFILE_ENABLED
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#if defined(JPH_EXTERNAL_PROFILE) && defined(JPH_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
ProfileStartMeasurementFunction ProfileStartMeasurement = [](const char *, uint32, uint8 *) { };
|
||||||
|
ProfileEndMeasurementFunction ProfileEndMeasurement = [](uint8 *) { };
|
||||||
|
|
||||||
|
#elif defined(JPH_PROFILE_ENABLED)
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Profiler
|
// Profiler
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
Profiler *Profiler::sInstance = nullptr;
|
Profiler *Profiler::sInstance = nullptr;
|
||||||
thread_local ProfileThread *ProfileThread::sInstance = nullptr;
|
|
||||||
|
#ifdef JPH_SHARED_LIBRARY
|
||||||
|
static thread_local ProfileThread *sInstance = nullptr;
|
||||||
|
|
||||||
|
ProfileThread *ProfileThread::sGetInstance()
|
||||||
|
{
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileThread::sSetInstance(ProfileThread *inInstance)
|
||||||
|
{
|
||||||
|
sInstance = inInstance;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
thread_local ProfileThread *ProfileThread::sInstance = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool ProfileMeasurement::sOutOfSamplesReported = false;
|
bool ProfileMeasurement::sOutOfSamplesReported = false;
|
||||||
|
|
||||||
|
void Profiler::UpdateReferenceTime()
|
||||||
|
{
|
||||||
|
mReferenceTick = GetProcessorTickCount();
|
||||||
|
mReferenceTime = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 Profiler::GetProcessorTicksPerSecond() const
|
||||||
|
{
|
||||||
|
uint64 ticks = GetProcessorTickCount();
|
||||||
|
std::chrono::high_resolution_clock::time_point time = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
return (ticks - mReferenceTick) * 1000000000ULL / std::chrono::duration_cast<std::chrono::nanoseconds>(time - mReferenceTime).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function assumes that none of the threads are active while we're dumping the profile,
|
||||||
|
// otherwise there will be a race condition on mCurrentSample and the profile data.
|
||||||
|
JPH_TSAN_NO_SANITIZE
|
||||||
void Profiler::NextFrame()
|
void Profiler::NextFrame()
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mLock);
|
std::lock_guard lock(mLock);
|
||||||
|
|
@ -38,6 +75,8 @@ void Profiler::NextFrame()
|
||||||
|
|
||||||
for (ProfileThread *t : mThreads)
|
for (ProfileThread *t : mThreads)
|
||||||
t->mCurrentSample = 0;
|
t->mCurrentSample = 0;
|
||||||
|
|
||||||
|
UpdateReferenceTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::Dump(const string_view &inTag)
|
void Profiler::Dump(const string_view &inTag)
|
||||||
|
|
@ -46,20 +85,20 @@ void Profiler::Dump(const string_view &inTag)
|
||||||
mDumpTag = inTag;
|
mDumpTag = inTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::AddThread(ProfileThread *inThread)
|
void Profiler::AddThread(ProfileThread *inThread)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mLock);
|
std::lock_guard lock(mLock);
|
||||||
|
|
||||||
mThreads.push_back(inThread);
|
mThreads.push_back(inThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::RemoveThread(ProfileThread *inThread)
|
void Profiler::RemoveThread(ProfileThread *inThread)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mLock);
|
std::lock_guard lock(mLock);
|
||||||
|
|
||||||
Array<ProfileThread *>::iterator i = find(mThreads.begin(), mThreads.end(), inThread);
|
Array<ProfileThread *>::iterator i = std::find(mThreads.begin(), mThreads.end(), inThread);
|
||||||
JPH_ASSERT(i != mThreads.end());
|
JPH_ASSERT(i != mThreads.end());
|
||||||
mThreads.erase(i);
|
mThreads.erase(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator)
|
void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator)
|
||||||
|
|
@ -75,7 +114,6 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample,
|
||||||
|
|
||||||
// Start accumulating totals
|
// Start accumulating totals
|
||||||
uint64 cycles_this_with_children = ioSample->mEndCycle - ioSample->mStartCycle;
|
uint64 cycles_this_with_children = ioSample->mEndCycle - ioSample->mStartCycle;
|
||||||
uint64 cycles_in_children = 0;
|
|
||||||
|
|
||||||
// Loop over following samples until we find a sample that starts on or after our end
|
// Loop over following samples until we find a sample that starts on or after our end
|
||||||
ProfileSample *sample;
|
ProfileSample *sample;
|
||||||
|
|
@ -85,9 +123,6 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample,
|
||||||
JPH_ASSERT(sample->mStartCycle >= ioSample->mStartCycle);
|
JPH_ASSERT(sample->mStartCycle >= ioSample->mStartCycle);
|
||||||
JPH_ASSERT(sample->mEndCycle <= ioSample->mEndCycle);
|
JPH_ASSERT(sample->mEndCycle <= ioSample->mEndCycle);
|
||||||
|
|
||||||
// This is a direct child of us, accumulate time
|
|
||||||
cycles_in_children += sample->mEndCycle - sample->mStartCycle;
|
|
||||||
|
|
||||||
// Recurse and skip over the children of this child
|
// Recurse and skip over the children of this child
|
||||||
sAggregate(inDepth + 1, inColor, sample, inEnd, ioAggregators, ioKeyToAggregator);
|
sAggregate(inDepth + 1, inColor, sample, inEnd, ioAggregators, ioKeyToAggregator);
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +144,7 @@ void Profiler::sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the measurement to the aggregator
|
// Add the measurement to the aggregator
|
||||||
aggregator->AccumulateMeasurement(cycles_this_with_children, cycles_in_children);
|
aggregator->AccumulateMeasurement(cycles_this_with_children);
|
||||||
|
|
||||||
// Update ioSample to the last child of ioSample
|
// Update ioSample to the last child of ioSample
|
||||||
JPH_ASSERT(sample[-1].mStartCycle <= ioSample->mEndCycle);
|
JPH_ASSERT(sample[-1].mStartCycle <= ioSample->mEndCycle);
|
||||||
|
|
@ -126,7 +161,7 @@ void Profiler::DumpInternal()
|
||||||
Threads threads;
|
Threads threads;
|
||||||
for (ProfileThread *t : mThreads)
|
for (ProfileThread *t : mThreads)
|
||||||
threads.push_back({ t->mThreadName, t->mSamples, t->mSamples + t->mCurrentSample });
|
threads.push_back({ t->mThreadName, t->mSamples, t->mSamples + t->mCurrentSample });
|
||||||
|
|
||||||
// Shift all samples so that the first sample is at zero
|
// Shift all samples so that the first sample is at zero
|
||||||
uint64 min_cycle = 0xffffffffffffffffUL;
|
uint64 min_cycle = 0xffffffffffffffffUL;
|
||||||
for (const ThreadSamples &t : threads)
|
for (const ThreadSamples &t : threads)
|
||||||
|
|
@ -162,9 +197,6 @@ void Profiler::DumpInternal()
|
||||||
for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
|
for (ProfileSample *s = t.mSamplesBegin, *end = t.mSamplesEnd; s < end; ++s)
|
||||||
sAggregate(0, Color::sGetDistinctColor(0).GetUInt32(), s, end, aggregators, key_to_aggregators);
|
sAggregate(0, Color::sGetDistinctColor(0).GetUInt32(), s, end, aggregators, key_to_aggregators);
|
||||||
|
|
||||||
// Dump as list
|
|
||||||
DumpList(tag.c_str(), aggregators);
|
|
||||||
|
|
||||||
// Dump as chart
|
// Dump as chart
|
||||||
DumpChart(tag.c_str(), threads, key_to_aggregators, aggregators);
|
DumpChart(tag.c_str(), threads, key_to_aggregators, aggregators);
|
||||||
}
|
}
|
||||||
|
|
@ -177,92 +209,12 @@ static String sHTMLEncode(const char *inString)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profiler::DumpList(const char *inTag, const Aggregators &inAggregators)
|
|
||||||
{
|
|
||||||
// Open file
|
|
||||||
std::ofstream f;
|
|
||||||
f.open(StringFormat("profile_list_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
|
|
||||||
if (!f.is_open())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Write header
|
|
||||||
f << R"(<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Profile List</title>
|
|
||||||
<link rel="stylesheet" href="WebIncludes/semantic.min.css">
|
|
||||||
<script type="text/javascript" src="WebIncludes/jquery-3.6.4.min.js"></script>
|
|
||||||
<script type="text/javascript" src="WebIncludes/semantic.min.js"></script>
|
|
||||||
<script type="text/javascript" src="WebIncludes/tablesort.js"></script>
|
|
||||||
<script type="text/javascript">$(document).ready(function() { $('table').tablesort({ compare: function(a, b) { return isNaN(a) || isNaN(b)? a.localeCompare(b) : Number(a) - Number(b); } }); });</script>
|
|
||||||
</head>
|
|
||||||
<body class="minimal pushable">
|
|
||||||
<table id="profile" class="ui sortable celled striped table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Description</th>
|
|
||||||
<th class="sorted descending">Total time with children (%)</th>
|
|
||||||
<th>Total time (%)</th>
|
|
||||||
<th>Calls</th>
|
|
||||||
<th>µs / call with children</th>
|
|
||||||
<th>µs / call</th>
|
|
||||||
<th>Min. µs / call</th>
|
|
||||||
<th>Max. µs / call</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody style="text-align: right;">
|
|
||||||
)";
|
|
||||||
|
|
||||||
// Get total time
|
|
||||||
uint64 total_time = 0;
|
|
||||||
for (const Aggregator &item : inAggregators)
|
|
||||||
total_time += item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren;
|
|
||||||
|
|
||||||
// Get cycles per second
|
|
||||||
uint64 cycles_per_second = GetProcessorTicksPerSecond();
|
|
||||||
|
|
||||||
// Sort the list
|
|
||||||
Aggregators aggregators = inAggregators;
|
|
||||||
QuickSort(aggregators.begin(), aggregators.end());
|
|
||||||
|
|
||||||
// Write all aggregators
|
|
||||||
for (const Aggregator &item : aggregators)
|
|
||||||
{
|
|
||||||
uint64 cycles_in_call_no_children = item.mTotalCyclesInCallWithChildren - item.mTotalCyclesInChildren;
|
|
||||||
|
|
||||||
char str[2048];
|
|
||||||
snprintf(str, sizeof(str), R"(<tr>
|
|
||||||
<td style="text-align: left;">%s</td>
|
|
||||||
<td>%.1f</td>
|
|
||||||
<td>%.1f</td>
|
|
||||||
<td>%u</td>
|
|
||||||
<td>%.2f</td>
|
|
||||||
<td>%.2f</td>
|
|
||||||
<td>%.2f</td>
|
|
||||||
<td>%.2f</td>
|
|
||||||
</tr>)",
|
|
||||||
sHTMLEncode(item.mName).c_str(), // Description
|
|
||||||
100.0 * item.mTotalCyclesInCallWithChildren / total_time, // Total time with children
|
|
||||||
100.0 * cycles_in_call_no_children / total_time, // Total time no children
|
|
||||||
item.mCallCounter, // Calls
|
|
||||||
1000000.0 * item.mTotalCyclesInCallWithChildren / cycles_per_second / item.mCallCounter, // us / call with children
|
|
||||||
1000000.0 * cycles_in_call_no_children / cycles_per_second / item.mCallCounter, // us / call no children
|
|
||||||
1000000.0 * item.mMinCyclesInCallWithChildren / cycles_per_second, // Min. us / call with children
|
|
||||||
1000000.0 * item.mMaxCyclesInCallWithChildren / cycles_per_second); // Max. us / call with children
|
|
||||||
|
|
||||||
f << str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// End table
|
|
||||||
f << R"(</tbody></table></body></html>)";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators)
|
void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators)
|
||||||
{
|
{
|
||||||
// Open file
|
// Open file
|
||||||
std::ofstream f;
|
std::ofstream f;
|
||||||
f.open(StringFormat("profile_chart_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
|
f.open(StringFormat("profile_chart_%s.html", inTag).c_str(), std::ofstream::out | std::ofstream::trunc);
|
||||||
if (!f.is_open())
|
if (!f.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Write header
|
// Write header
|
||||||
|
|
@ -270,8 +222,331 @@ void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyT
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Profile Chart</title>
|
<title>Profile Chart</title>
|
||||||
<link rel="stylesheet" href="WebIncludes/profile_chart.css">
|
<style>
|
||||||
<script type="text/javascript" src="WebIncludes/profile_chart.js"></script>
|
html, body {
|
||||||
|
padding: 0px;
|
||||||
|
border: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
padding: 0px;
|
||||||
|
border: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tooltip {
|
||||||
|
font: Courier New;
|
||||||
|
position: absolute;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: black;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 5px;
|
||||||
|
font: 14px Arial;
|
||||||
|
visibility: hidden;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
color: blue;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var canvas;
|
||||||
|
var ctx;
|
||||||
|
var tooltip;
|
||||||
|
var min_scale;
|
||||||
|
var scale;
|
||||||
|
var offset_x = 0;
|
||||||
|
var offset_y = 0;
|
||||||
|
var size_y;
|
||||||
|
var dragging = false;
|
||||||
|
var previous_x = 0;
|
||||||
|
var previous_y = 0;
|
||||||
|
var bar_height = 15;
|
||||||
|
var line_height = bar_height + 2;
|
||||||
|
var thread_separation = 6;
|
||||||
|
var thread_font_size = 12;
|
||||||
|
var thread_font = thread_font_size + "px Arial";
|
||||||
|
var bar_font_size = 10;
|
||||||
|
var bar_font = bar_font_size + "px Arial";
|
||||||
|
var end_cycle = 0;
|
||||||
|
|
||||||
|
function drawChart()
|
||||||
|
{
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
var y = offset_y;
|
||||||
|
|
||||||
|
for (var t = 0; t < threads.length; t++)
|
||||||
|
{
|
||||||
|
// Check if thread has samples
|
||||||
|
var thread = threads[t];
|
||||||
|
if (thread.start.length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Draw thread name
|
||||||
|
y += thread_font_size;
|
||||||
|
ctx.font = thread_font;
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
ctx.fillText(thread.thread_name, 0, y);
|
||||||
|
y += thread_separation;
|
||||||
|
|
||||||
|
// Draw outlines for each bar of samples
|
||||||
|
ctx.fillStyle = "#c0c0c0";
|
||||||
|
for (var d = 0; d <= thread.max_depth; d++)
|
||||||
|
ctx.fillRect(0, y + d * line_height, canvas.width, bar_height);
|
||||||
|
|
||||||
|
// Draw samples
|
||||||
|
ctx.font = bar_font;
|
||||||
|
for (var s = 0; s < thread.start.length; s++)
|
||||||
|
{
|
||||||
|
// Cull bar
|
||||||
|
var rx = scale * (offset_x + thread.start[s]);
|
||||||
|
if (rx > canvas.width) // right of canvas
|
||||||
|
break;
|
||||||
|
var rw = scale * thread.cycles[s];
|
||||||
|
if (rw < 0.5) // less than half pixel, skip
|
||||||
|
continue;
|
||||||
|
if (rx + rw < 0) // left of canvas
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Draw bar
|
||||||
|
var ry = y + line_height * thread.depth[s];
|
||||||
|
ctx.fillStyle = thread.color[s];
|
||||||
|
ctx.fillRect(rx, ry, rw, bar_height);
|
||||||
|
ctx.strokeStyle = thread.darkened_color[s];
|
||||||
|
ctx.strokeRect(rx, ry, rw, bar_height);
|
||||||
|
|
||||||
|
// Get index in aggregated list
|
||||||
|
var a = thread.aggregator[s];
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
if (rw > aggregated.name_width[a])
|
||||||
|
{
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
ctx.fillText(aggregated.name[a], rx + (rw - aggregated.name_width[a]) / 2, ry + bar_height - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next line
|
||||||
|
y += line_height * (1 + thread.max_depth) + thread_separation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update size
|
||||||
|
size_y = y - offset_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTooltip(mouse_x, mouse_y)
|
||||||
|
{
|
||||||
|
var y = offset_y;
|
||||||
|
|
||||||
|
for (var t = 0; t < threads.length; t++)
|
||||||
|
{
|
||||||
|
// Check if thread has samples
|
||||||
|
var thread = threads[t];
|
||||||
|
if (thread.start.length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Thead name
|
||||||
|
y += thread_font_size + thread_separation;
|
||||||
|
|
||||||
|
// Draw samples
|
||||||
|
for (var s = 0; s < thread.start.length; s++)
|
||||||
|
{
|
||||||
|
// Cull bar
|
||||||
|
var rx = scale * (offset_x + thread.start[s]);
|
||||||
|
if (rx > mouse_x)
|
||||||
|
break;
|
||||||
|
var rw = scale * thread.cycles[s];
|
||||||
|
if (rx + rw < mouse_x)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var ry = y + line_height * thread.depth[s];
|
||||||
|
if (mouse_y >= ry && mouse_y < ry + bar_height)
|
||||||
|
{
|
||||||
|
// Get index into aggregated list
|
||||||
|
var a = thread.aggregator[s];
|
||||||
|
|
||||||
|
// Found bar, fill in tooltip
|
||||||
|
tooltip.style.left = (canvas.offsetLeft + mouse_x) + "px";
|
||||||
|
tooltip.style.top = (canvas.offsetTop + mouse_y) + "px";
|
||||||
|
tooltip.style.visibility = "visible";
|
||||||
|
tooltip.innerHTML = aggregated.name[a] + "<br>"
|
||||||
|
+ "<table>"
|
||||||
|
+ "<tr><td>Time:</td><td class=\"stat\">" + (1000000 * thread.cycles[s] / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Start:</td><td class=\"stat\">" + (1000000 * thread.start[s] / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>End:</td><td class=\"stat\">" + (1000000 * (thread.start[s] + thread.cycles[s]) / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Avg. Time:</td><td class=\"stat\">" + (1000000 * aggregated.cycles_per_frame[a] / cycles_per_second / aggregated.calls[a]).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Min Time:</td><td class=\"stat\">" + (1000000 * aggregated.min_cycles[a] / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Max Time:</td><td class=\"stat\">" + (1000000 * aggregated.max_cycles[a] / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Time / Frame:</td><td class=\"stat\">" + (1000000 * aggregated.cycles_per_frame[a] / cycles_per_second).toFixed(2) + " µs</td></tr>"
|
||||||
|
+ "<tr><td>Calls:</td><td class=\"stat\">" + aggregated.calls[a] + "</td></tr>"
|
||||||
|
+ "</table>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next line
|
||||||
|
y += line_height * (1 + thread.max_depth) + thread_separation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No bar found, hide tooltip
|
||||||
|
tooltip.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseDown(evt)
|
||||||
|
{
|
||||||
|
dragging = true;
|
||||||
|
previous_x = evt.clientX, previous_y = evt.clientY;
|
||||||
|
tooltip.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(evt)
|
||||||
|
{
|
||||||
|
dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampMotion()
|
||||||
|
{
|
||||||
|
// Clamp horizontally
|
||||||
|
var min_offset_x = canvas.width / scale - end_cycle;
|
||||||
|
if (offset_x < min_offset_x)
|
||||||
|
offset_x = min_offset_x;
|
||||||
|
if (offset_x > 0)
|
||||||
|
offset_x = 0;
|
||||||
|
|
||||||
|
// Clamp vertically
|
||||||
|
var min_offset_y = canvas.height - size_y;
|
||||||
|
if (offset_y < min_offset_y)
|
||||||
|
offset_y = min_offset_y;
|
||||||
|
if (offset_y > 0)
|
||||||
|
offset_y = 0;
|
||||||
|
|
||||||
|
// Clamp scale
|
||||||
|
if (scale < min_scale)
|
||||||
|
scale = min_scale;
|
||||||
|
var max_scale = 1000 * min_scale;
|
||||||
|
if (scale > max_scale)
|
||||||
|
scale = max_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove(evt)
|
||||||
|
{
|
||||||
|
if (dragging)
|
||||||
|
{
|
||||||
|
// Calculate new offset
|
||||||
|
offset_x += (evt.clientX - previous_x) / scale;
|
||||||
|
offset_y += evt.clientY - previous_y;
|
||||||
|
|
||||||
|
clampMotion();
|
||||||
|
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
drawTooltip(evt.clientX - canvas.offsetLeft, evt.clientY - canvas.offsetTop);
|
||||||
|
|
||||||
|
previous_x = evt.clientX, previous_y = evt.clientY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScroll(evt)
|
||||||
|
{
|
||||||
|
tooltip.style.visibility = "hidden";
|
||||||
|
|
||||||
|
var old_scale = scale;
|
||||||
|
if (evt.deltaY > 0)
|
||||||
|
scale /= 1.1;
|
||||||
|
else
|
||||||
|
scale *= 1.1;
|
||||||
|
|
||||||
|
clampMotion();
|
||||||
|
|
||||||
|
// Ensure that event under mouse stays under mouse
|
||||||
|
var x = previous_x - canvas.offsetLeft;
|
||||||
|
offset_x += x / scale - x / old_scale;
|
||||||
|
|
||||||
|
clampMotion();
|
||||||
|
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function darkenColor(color)
|
||||||
|
{
|
||||||
|
var i = parseInt(color.slice(1), 16);
|
||||||
|
|
||||||
|
var r = i >> 16;
|
||||||
|
var g = (i >> 8) & 0xff;
|
||||||
|
var b = i & 0xff;
|
||||||
|
|
||||||
|
r = Math.round(0.8 * r);
|
||||||
|
g = Math.round(0.8 * g);
|
||||||
|
b = Math.round(0.8 * b);
|
||||||
|
|
||||||
|
i = (r << 16) + (g << 8) + b;
|
||||||
|
|
||||||
|
return "#" + i.toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startChart()
|
||||||
|
{
|
||||||
|
// Fetch elements
|
||||||
|
canvas = document.getElementById('canvas');
|
||||||
|
ctx = canvas.getContext("2d");
|
||||||
|
tooltip = document.getElementById('tooltip');
|
||||||
|
|
||||||
|
// Resize canvas to fill screen
|
||||||
|
canvas.width = document.body.offsetWidth - 20;
|
||||||
|
canvas.height = document.body.offsetHeight - 20;
|
||||||
|
|
||||||
|
// Register mouse handlers
|
||||||
|
canvas.onmousedown = onMouseDown;
|
||||||
|
canvas.onmouseup = onMouseUp;
|
||||||
|
canvas.onmouseout = onMouseUp;
|
||||||
|
canvas.onmousemove = onMouseMove;
|
||||||
|
canvas.onwheel = onScroll;
|
||||||
|
|
||||||
|
for (var t = 0; t < threads.length; t++)
|
||||||
|
{
|
||||||
|
var thread = threads[t];
|
||||||
|
|
||||||
|
// Calculate darkened colors
|
||||||
|
thread.darkened_color = new Array(thread.color.length);
|
||||||
|
for (var s = 0; s < thread.color.length; s++)
|
||||||
|
thread.darkened_color[s] = darkenColor(thread.color[s]);
|
||||||
|
|
||||||
|
// Calculate max depth and end cycle
|
||||||
|
thread.max_depth = 0;
|
||||||
|
for (var s = 0; s < thread.start.length; s++)
|
||||||
|
{
|
||||||
|
thread.max_depth = Math.max(thread.max_depth, thread.depth[s]);
|
||||||
|
end_cycle = Math.max(end_cycle, thread.start[s] + thread.cycles[s]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate width of name strings
|
||||||
|
ctx.font = bar_font;
|
||||||
|
aggregated.name_width = new Array(aggregated.name.length);
|
||||||
|
for (var a = 0; a < aggregated.name.length; a++)
|
||||||
|
aggregated.name_width[a] = ctx.measureText(aggregated.name[a]).width;
|
||||||
|
|
||||||
|
// Store scale properties
|
||||||
|
min_scale = canvas.width / end_cycle;
|
||||||
|
scale = min_scale;
|
||||||
|
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="startChart();">
|
<body onload="startChart();">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
@ -397,6 +672,6 @@ void Profiler::DumpChart(const char *inTag, const Threads &inThreads, const KeyT
|
||||||
</tbody></table></body></html>)";
|
</tbody></table></body></html>)";
|
||||||
}
|
}
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif // JPH_PROFILE_ENABLED
|
#endif // JPH_PROFILE_ENABLED
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
#include <Jolt/Core/NonCopyable.h>
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
|
|
@ -16,16 +17,31 @@ JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#ifdef JPH_SHARED_LIBRARY
|
||||||
|
/// Functions called when a profiler measurement starts or stops, need to be overridden by the user.
|
||||||
|
using ProfileStartMeasurementFunction = void (*)(const char *inName, uint32 inColor, uint8 *ioUserData);
|
||||||
|
using ProfileEndMeasurementFunction = void (*)(uint8 *ioUserData);
|
||||||
|
|
||||||
|
JPH_EXPORT extern ProfileStartMeasurementFunction ProfileStartMeasurement;
|
||||||
|
JPH_EXPORT extern ProfileEndMeasurementFunction ProfileEndMeasurement;
|
||||||
|
#endif // JPH_SHARED_LIBRARY
|
||||||
|
|
||||||
/// Create this class on the stack to start sampling timing information of a particular scope.
|
/// Create this class on the stack to start sampling timing information of a particular scope.
|
||||||
///
|
///
|
||||||
/// Left unimplemented intentionally. Needs to be implemented by the user of the library.
|
/// For statically linked builds, this is left unimplemented intentionally. Needs to be implemented by the user of the library.
|
||||||
/// On construction a measurement should start, on destruction it should be stopped.
|
/// On construction a measurement should start, on destruction it should be stopped.
|
||||||
|
/// For dynamically linked builds, the user should override the ProfileStartMeasurement and ProfileEndMeasurement functions.
|
||||||
class alignas(16) ExternalProfileMeasurement : public NonCopyable
|
class alignas(16) ExternalProfileMeasurement : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
#ifdef JPH_SHARED_LIBRARY
|
||||||
|
JPH_INLINE ExternalProfileMeasurement(const char *inName, uint32 inColor = 0) { ProfileStartMeasurement(inName, inColor, mUserData); }
|
||||||
|
JPH_INLINE ~ExternalProfileMeasurement() { ProfileEndMeasurement(mUserData); }
|
||||||
|
#else
|
||||||
ExternalProfileMeasurement(const char *inName, uint32 inColor = 0);
|
ExternalProfileMeasurement(const char *inName, uint32 inColor = 0);
|
||||||
~ExternalProfileMeasurement();
|
~ExternalProfileMeasurement();
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8 mUserData[64];
|
uint8 mUserData[64];
|
||||||
|
|
@ -34,18 +50,20 @@ private:
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Macros to do the actual profiling
|
// Macros to do the actual profiling
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
|
JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
|
||||||
|
|
||||||
// Dummy implementations
|
// Dummy implementations
|
||||||
#define JPH_PROFILE_THREAD_START(name)
|
#define JPH_PROFILE_START(name)
|
||||||
#define JPH_PROFILE_THREAD_END()
|
#define JPH_PROFILE_END()
|
||||||
#define JPH_PROFILE_NEXTFRAME()
|
#define JPH_PROFILE_THREAD_START(name)
|
||||||
#define JPH_PROFILE_DUMP(...)
|
#define JPH_PROFILE_THREAD_END()
|
||||||
|
#define JPH_PROFILE_NEXTFRAME()
|
||||||
|
#define JPH_PROFILE_DUMP(...)
|
||||||
|
|
||||||
// Scope profiling measurement
|
// Scope profiling measurement
|
||||||
#define JPH_PROFILE_TAG2(line) profile##line
|
#define JPH_PROFILE_TAG2(line) profile##line
|
||||||
#define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
|
#define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
|
||||||
|
|
@ -74,11 +92,14 @@ class ProfileSample;
|
||||||
class ProfileThread;
|
class ProfileThread;
|
||||||
|
|
||||||
/// Singleton class for managing profiling information
|
/// Singleton class for managing profiling information
|
||||||
class Profiler : public NonCopyable
|
class JPH_EXPORT Profiler : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
Profiler() { UpdateReferenceTime(); }
|
||||||
|
|
||||||
/// Increments the frame counter to provide statistics per frame
|
/// Increments the frame counter to provide statistics per frame
|
||||||
void NextFrame();
|
void NextFrame();
|
||||||
|
|
||||||
|
|
@ -94,7 +115,7 @@ public:
|
||||||
|
|
||||||
/// Singleton instance
|
/// Singleton instance
|
||||||
static Profiler * sInstance;
|
static Profiler * sInstance;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Helper class to freeze ProfileSamples per thread while processing them
|
/// Helper class to freeze ProfileSamples per thread while processing them
|
||||||
struct ThreadSamples
|
struct ThreadSamples
|
||||||
|
|
@ -105,18 +126,17 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper class to aggregate ProfileSamples
|
/// Helper class to aggregate ProfileSamples
|
||||||
class Aggregator
|
class Aggregator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
Aggregator(const char *inName) : mName(inName) { }
|
Aggregator(const char *inName) : mName(inName) { }
|
||||||
|
|
||||||
/// Accumulate results for a measurement
|
/// Accumulate results for a measurement
|
||||||
void AccumulateMeasurement(uint64 inCyclesInCallWithChildren, uint64 inCyclesInChildren)
|
void AccumulateMeasurement(uint64 inCyclesInCallWithChildren)
|
||||||
{
|
{
|
||||||
mCallCounter++;
|
mCallCounter++;
|
||||||
mTotalCyclesInCallWithChildren += inCyclesInCallWithChildren;
|
mTotalCyclesInCallWithChildren += inCyclesInCallWithChildren;
|
||||||
mTotalCyclesInChildren += inCyclesInChildren;
|
|
||||||
mMinCyclesInCallWithChildren = min(inCyclesInCallWithChildren, mMinCyclesInCallWithChildren);
|
mMinCyclesInCallWithChildren = min(inCyclesInCallWithChildren, mMinCyclesInCallWithChildren);
|
||||||
mMaxCyclesInCallWithChildren = max(inCyclesInCallWithChildren, mMaxCyclesInCallWithChildren);
|
mMaxCyclesInCallWithChildren = max(inCyclesInCallWithChildren, mMaxCyclesInCallWithChildren);
|
||||||
}
|
}
|
||||||
|
|
@ -127,16 +147,15 @@ private:
|
||||||
return mTotalCyclesInCallWithChildren > inRHS.mTotalCyclesInCallWithChildren;
|
return mTotalCyclesInCallWithChildren > inRHS.mTotalCyclesInCallWithChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identification
|
/// Identification
|
||||||
const char * mName; ///< User defined name of this item
|
const char * mName; ///< User defined name of this item
|
||||||
|
|
||||||
/// Statistics
|
/// Statistics
|
||||||
uint32 mCallCounter = 0; ///< Number of times AccumulateMeasurement was called
|
uint32 mCallCounter = 0; ///< Number of times AccumulateMeasurement was called
|
||||||
uint64 mTotalCyclesInCallWithChildren = 0; ///< Total amount of cycles spent in this scope
|
uint64 mTotalCyclesInCallWithChildren = 0; ///< Total amount of cycles spent in this scope
|
||||||
uint64 mTotalCyclesInChildren = 0; ///< Total amount of cycles spent in children of this scope
|
|
||||||
uint64 mMinCyclesInCallWithChildren = 0xffffffffffffffffUL; ///< Minimum amount of cycles spent per call
|
uint64 mMinCyclesInCallWithChildren = 0xffffffffffffffffUL; ///< Minimum amount of cycles spent per call
|
||||||
uint64 mMaxCyclesInCallWithChildren = 0; ///< Maximum amount of cycles spent per call
|
uint64 mMaxCyclesInCallWithChildren = 0; ///< Maximum amount of cycles spent per call
|
||||||
};
|
};
|
||||||
|
|
||||||
using Threads = Array<ThreadSamples>;
|
using Threads = Array<ThreadSamples>;
|
||||||
using Aggregators = Array<Aggregator>;
|
using Aggregators = Array<Aggregator>;
|
||||||
|
|
@ -145,19 +164,26 @@ private:
|
||||||
/// Helper function to aggregate profile sample data
|
/// Helper function to aggregate profile sample data
|
||||||
static void sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator);
|
static void sAggregate(int inDepth, uint32 inColor, ProfileSample *&ioSample, const ProfileSample *inEnd, Aggregators &ioAggregators, KeyToAggregator &ioKeyToAggregator);
|
||||||
|
|
||||||
|
/// We measure the amount of ticks per second, this function resets the reference time point
|
||||||
|
void UpdateReferenceTime();
|
||||||
|
|
||||||
|
/// Get the amount of ticks per second, note that this number will never be fully accurate as the amount of ticks per second may vary with CPU load, so this number is only to be used to give an indication of time for profiling purposes
|
||||||
|
uint64 GetProcessorTicksPerSecond() const;
|
||||||
|
|
||||||
/// Dump profiling statistics
|
/// Dump profiling statistics
|
||||||
void DumpInternal();
|
void DumpInternal();
|
||||||
void DumpList(const char *inTag, const Aggregators &inAggregators);
|
|
||||||
void DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators);
|
void DumpChart(const char *inTag, const Threads &inThreads, const KeyToAggregator &inKeyToAggregators, const Aggregators &inAggregators);
|
||||||
|
|
||||||
std::mutex mLock; ///< Lock that protects mThreads
|
std::mutex mLock; ///< Lock that protects mThreads
|
||||||
|
uint64 mReferenceTick; ///< Tick count at the start of the frame
|
||||||
|
std::chrono::high_resolution_clock::time_point mReferenceTime; ///< Time at the start of the frame
|
||||||
Array<ProfileThread *> mThreads; ///< List of all active threads
|
Array<ProfileThread *> mThreads; ///< List of all active threads
|
||||||
bool mDump = false; ///< When true, the samples are dumped next frame
|
bool mDump = false; ///< When true, the samples are dumped next frame
|
||||||
String mDumpTag; ///< When not empty, this overrides the auto incrementing number of the dump filename
|
String mDumpTag; ///< When not empty, this overrides the auto incrementing number of the dump filename
|
||||||
};
|
};
|
||||||
|
|
||||||
// Class that contains the information of a single scoped measurement
|
// Class that contains the information of a single scoped measurement
|
||||||
class alignas(16) ProfileSample : public NonCopyable
|
class alignas(16) JPH_EXPORT_GCC_BUG_WORKAROUND ProfileSample : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -186,17 +212,26 @@ public:
|
||||||
ProfileSample mSamples[cMaxSamples]; ///< Buffer of samples
|
ProfileSample mSamples[cMaxSamples]; ///< Buffer of samples
|
||||||
uint mCurrentSample = 0; ///< Next position to write a sample to
|
uint mCurrentSample = 0; ///< Next position to write a sample to
|
||||||
|
|
||||||
|
#ifdef JPH_SHARED_LIBRARY
|
||||||
|
JPH_EXPORT static void sSetInstance(ProfileThread *inInstance);
|
||||||
|
JPH_EXPORT static ProfileThread *sGetInstance();
|
||||||
|
#else
|
||||||
|
static inline void sSetInstance(ProfileThread *inInstance) { sInstance = inInstance; }
|
||||||
|
static inline ProfileThread *sGetInstance() { return sInstance; }
|
||||||
|
|
||||||
|
private:
|
||||||
static thread_local ProfileThread *sInstance;
|
static thread_local ProfileThread *sInstance;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create this class on the stack to start sampling timing information of a particular scope
|
/// Create this class on the stack to start sampling timing information of a particular scope
|
||||||
class ProfileMeasurement : public NonCopyable
|
class JPH_EXPORT ProfileMeasurement : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline ProfileMeasurement(const char *inName, uint32 inColor = 0);
|
inline ProfileMeasurement(const char *inName, uint32 inColor = 0);
|
||||||
inline ~ProfileMeasurement();
|
inline ~ProfileMeasurement();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ProfileSample * mSample;
|
ProfileSample * mSample;
|
||||||
ProfileSample mTemp;
|
ProfileSample mTemp;
|
||||||
|
|
@ -209,7 +244,7 @@ JPH_NAMESPACE_END
|
||||||
#include "Profiler.inl"
|
#include "Profiler.inl"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Macros to do the actual profiling
|
// Macros to do the actual profiling
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
|
|
@ -222,11 +257,11 @@ JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
|
||||||
#define JPH_PROFILE_END() do { JPH_PROFILE_THREAD_END(); delete Profiler::sInstance; Profiler::sInstance = nullptr; } while (false)
|
#define JPH_PROFILE_END() do { JPH_PROFILE_THREAD_END(); delete Profiler::sInstance; Profiler::sInstance = nullptr; } while (false)
|
||||||
|
|
||||||
/// Start instrumenting a thread
|
/// Start instrumenting a thread
|
||||||
#define JPH_PROFILE_THREAD_START(name) do { if (Profiler::sInstance) ProfileThread::sInstance = new ProfileThread(name); } while (false)
|
#define JPH_PROFILE_THREAD_START(name) do { if (Profiler::sInstance) ProfileThread::sSetInstance(new ProfileThread(name)); } while (false)
|
||||||
|
|
||||||
/// End instrumenting a thread
|
/// End instrumenting a thread
|
||||||
#define JPH_PROFILE_THREAD_END() do { delete ProfileThread::sInstance; ProfileThread::sInstance = nullptr; } while (false)
|
#define JPH_PROFILE_THREAD_END() do { delete ProfileThread::sGetInstance(); ProfileThread::sSetInstance(nullptr); } while (false)
|
||||||
|
|
||||||
/// Scope profiling measurement
|
/// Scope profiling measurement
|
||||||
#define JPH_PROFILE_TAG2(line) profile##line
|
#define JPH_PROFILE_TAG2(line) profile##line
|
||||||
#define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
|
#define JPH_PROFILE_TAG(line) JPH_PROFILE_TAG2(line)
|
||||||
|
|
@ -234,8 +269,8 @@ JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
|
||||||
|
|
||||||
/// Scope profiling for function
|
/// Scope profiling for function
|
||||||
#define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME)
|
#define JPH_PROFILE_FUNCTION() JPH_PROFILE(JPH_FUNCTION_NAME)
|
||||||
|
|
||||||
/// Update frame counter
|
/// Update frame counter
|
||||||
#define JPH_PROFILE_NEXTFRAME() Profiler::sInstance->NextFrame()
|
#define JPH_PROFILE_NEXTFRAME() Profiler::sInstance->NextFrame()
|
||||||
|
|
||||||
/// Dump profiling info
|
/// Dump profiling info
|
||||||
|
|
|
||||||
|
|
@ -23,17 +23,19 @@ ProfileThread::~ProfileThread()
|
||||||
// ProfileMeasurement
|
// ProfileMeasurement
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ProfileMeasurement::ProfileMeasurement(const char *inName, uint32 inColor)
|
JPH_TSAN_NO_SANITIZE // TSAN reports a race on sOutOfSamplesReported, however the worst case is that we report the out of samples message multiple times
|
||||||
|
ProfileMeasurement::ProfileMeasurement(const char *inName, uint32 inColor)
|
||||||
{
|
{
|
||||||
if (ProfileThread::sInstance == nullptr)
|
ProfileThread *current_thread = ProfileThread::sGetInstance();
|
||||||
|
if (current_thread == nullptr)
|
||||||
{
|
{
|
||||||
// Thread not instrumented
|
// Thread not instrumented
|
||||||
mSample = nullptr;
|
mSample = nullptr;
|
||||||
}
|
}
|
||||||
else if (ProfileThread::sInstance->mCurrentSample < ProfileThread::cMaxSamples)
|
else if (current_thread->mCurrentSample < ProfileThread::cMaxSamples)
|
||||||
{
|
{
|
||||||
// Get pointer to write data to
|
// Get pointer to write data to
|
||||||
mSample = &ProfileThread::sInstance->mSamples[ProfileThread::sInstance->mCurrentSample++];
|
mSample = ¤t_thread->mSamples[current_thread->mCurrentSample++];
|
||||||
|
|
||||||
// Start constructing sample (will end up on stack)
|
// Start constructing sample (will end up on stack)
|
||||||
mTemp.mName = inName;
|
mTemp.mName = inName;
|
||||||
|
|
@ -47,8 +49,8 @@ ProfileMeasurement::ProfileMeasurement(const char *inName, uint32 inColor)
|
||||||
// Out of samples
|
// Out of samples
|
||||||
if (!sOutOfSamplesReported)
|
if (!sOutOfSamplesReported)
|
||||||
{
|
{
|
||||||
Trace("ProfileMeasurement: Too many samples, some data will be lost!");
|
|
||||||
sOutOfSamplesReported = true;
|
sOutOfSamplesReported = true;
|
||||||
|
Trace("ProfileMeasurement: Too many samples, some data will be lost!");
|
||||||
}
|
}
|
||||||
mSample = nullptr;
|
mSample = nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,16 @@ template <typename Iterator, typename Compare>
|
||||||
inline void QuickSortMedianOfThree(Iterator inFirst, Iterator inMiddle, Iterator inLast, Compare inCompare)
|
inline void QuickSortMedianOfThree(Iterator inFirst, Iterator inMiddle, Iterator inLast, Compare inCompare)
|
||||||
{
|
{
|
||||||
// This should be guaranteed because we switch over to insertion sort when there's 32 or less elements
|
// This should be guaranteed because we switch over to insertion sort when there's 32 or less elements
|
||||||
JPH_ASSERT(inFirst != inMiddle && inMiddle != inLast);
|
JPH_ASSERT(inFirst != inMiddle && inMiddle != inLast);
|
||||||
|
|
||||||
if (inCompare(*inMiddle, *inFirst))
|
if (inCompare(*inMiddle, *inFirst))
|
||||||
swap(*inFirst, *inMiddle);
|
std::swap(*inFirst, *inMiddle);
|
||||||
|
|
||||||
if (inCompare(*inLast, *inFirst))
|
if (inCompare(*inLast, *inFirst))
|
||||||
swap(*inFirst, *inLast);
|
std::swap(*inFirst, *inLast);
|
||||||
|
|
||||||
if (inCompare(*inLast, *inMiddle))
|
if (inCompare(*inLast, *inMiddle))
|
||||||
swap(*inMiddle, *inLast);
|
std::swap(*inMiddle, *inLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for QuickSort using the Ninther method, will move the pivot element to inMiddle.
|
/// Helper function for QuickSort using the Ninther method, will move the pivot element to inMiddle.
|
||||||
|
|
@ -94,7 +94,7 @@ inline void QuickSort(Iterator inBegin, Iterator inEnd, Compare inCompare)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Swap the elements
|
// Swap the elements
|
||||||
swap(*i, *j);
|
std::swap(*i, *j);
|
||||||
|
|
||||||
// Note that the first while loop in this function should
|
// Note that the first while loop in this function should
|
||||||
// have been do i++ while (...) but since we cannot decrement
|
// have been do i++ while (...) but since we cannot decrement
|
||||||
|
|
|
||||||
|
|
@ -34,51 +34,53 @@ RTTI::RTTI(const char *inName, int inSize, pCreateObjectFunction inCreateObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
int RTTI::GetBaseClassCount() const
|
int RTTI::GetBaseClassCount() const
|
||||||
{
|
{
|
||||||
return (int)mBaseClasses.size();
|
return (int)mBaseClasses.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
const RTTI *RTTI::GetBaseClass(int inIdx) const
|
const RTTI *RTTI::GetBaseClass(int inIdx) const
|
||||||
{
|
{
|
||||||
return mBaseClasses[inIdx].mRTTI;
|
return mBaseClasses[inIdx].mRTTI;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 RTTI::GetHash() const
|
uint32 RTTI::GetHash() const
|
||||||
{
|
{
|
||||||
// Perform diffusion step to get from 64 to 32 bits (see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function)
|
// Perform diffusion step to get from 64 to 32 bits (see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function)
|
||||||
uint64 hash = HashString(mName);
|
uint64 hash = HashString(mName);
|
||||||
return (uint32)(hash ^ (hash >> 32));
|
return (uint32)(hash ^ (hash >> 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
void *RTTI::CreateObject() const
|
void *RTTI::CreateObject() const
|
||||||
{
|
{
|
||||||
return IsAbstract()? nullptr : mCreate();
|
return IsAbstract()? nullptr : mCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTTI::DestructObject(void *inObject) const
|
void RTTI::DestructObject(void *inObject) const
|
||||||
{
|
{
|
||||||
mDestruct(inObject);
|
mDestruct(inObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTTI::AddBaseClass(const RTTI *inRTTI, int inOffset)
|
void RTTI::AddBaseClass(const RTTI *inRTTI, int inOffset)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inOffset >= 0 && inOffset < mSize, "Base class not contained in derived class");
|
JPH_ASSERT(inOffset >= 0 && inOffset < mSize, "Base class not contained in derived class");
|
||||||
|
|
||||||
// Add base class
|
// Add base class
|
||||||
BaseClass base;
|
BaseClass base;
|
||||||
base.mRTTI = inRTTI;
|
base.mRTTI = inRTTI;
|
||||||
base.mOffset = inOffset;
|
base.mOffset = inOffset;
|
||||||
mBaseClasses.push_back(base);
|
mBaseClasses.push_back(base);
|
||||||
|
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
// Add attributes of base class
|
// Add attributes of base class
|
||||||
for (const SerializableAttribute &a : inRTTI->mAttributes)
|
for (const SerializableAttribute &a : inRTTI->mAttributes)
|
||||||
mAttributes.push_back(SerializableAttribute(a, inOffset));
|
mAttributes.push_back(SerializableAttribute(a, inOffset));
|
||||||
|
#endif // JPH_OBJECT_STREAM
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RTTI::operator == (const RTTI &inRHS) const
|
bool RTTI::operator == (const RTTI &inRHS) const
|
||||||
{
|
{
|
||||||
// Compare addresses
|
// Compare addresses
|
||||||
if (this == &inRHS)
|
if (this == &inRHS)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Check that the names differ (if that is the case we probably have two instances
|
// Check that the names differ (if that is the case we probably have two instances
|
||||||
|
|
@ -104,7 +106,7 @@ bool RTTI::IsKindOf(const RTTI *inRTTI) const
|
||||||
const void *RTTI::CastTo(const void *inObject, const RTTI *inRTTI) const
|
const void *RTTI::CastTo(const void *inObject, const RTTI *inRTTI) const
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inObject != nullptr);
|
JPH_ASSERT(inObject != nullptr);
|
||||||
|
|
||||||
// Check if this is the same type
|
// Check if this is the same type
|
||||||
if (this == inRTTI)
|
if (this == inRTTI)
|
||||||
return inObject;
|
return inObject;
|
||||||
|
|
@ -125,19 +127,23 @@ const void *RTTI::CastTo(const void *inObject, const RTTI *inRTTI) const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
|
|
||||||
void RTTI::AddAttribute(const SerializableAttribute &inAttribute)
|
void RTTI::AddAttribute(const SerializableAttribute &inAttribute)
|
||||||
{
|
{
|
||||||
mAttributes.push_back(inAttribute);
|
mAttributes.push_back(inAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
int RTTI::GetAttributeCount() const
|
int RTTI::GetAttributeCount() const
|
||||||
{
|
{
|
||||||
return (int)mAttributes.size();
|
return (int)mAttributes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SerializableAttribute &RTTI::GetAttribute(int inIdx) const
|
const SerializableAttribute &RTTI::GetAttribute(int inIdx) const
|
||||||
{
|
{
|
||||||
return mAttributes[inIdx];
|
return mAttributes[inIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // JPH_OBJECT_STREAM
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// class)
|
/// class)
|
||||||
///
|
///
|
||||||
/// Notes:
|
/// Notes:
|
||||||
/// - An extra virtual member function is added. This adds 8 bytes to the size of
|
/// - An extra virtual member function is added. This adds 8 bytes to the size of
|
||||||
/// an instance of the class (unless you are already using virtual functions).
|
/// an instance of the class (unless you are already using virtual functions).
|
||||||
///
|
///
|
||||||
/// To use RTTI on a specific class use:
|
/// To use RTTI on a specific class use:
|
||||||
|
|
@ -43,7 +43,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// JPH_IMPLEMENT_RTTI_VIRTUAL(Bar)
|
/// JPH_IMPLEMENT_RTTI_VIRTUAL(Bar)
|
||||||
/// {
|
/// {
|
||||||
/// JPH_ADD_BASE_CLASS(Bar, Foo) // Multiple inheritance is allowed, just do JPH_ADD_BASE_CLASS for every base class
|
/// JPH_ADD_BASE_CLASS(Bar, Foo) // Multiple inheritance is allowed, just do JPH_ADD_BASE_CLASS for every base class
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
|
@ -74,7 +74,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// JPH_IMPLEMENT_RTTI_VIRTUAL(Bar)
|
/// JPH_IMPLEMENT_RTTI_VIRTUAL(Bar)
|
||||||
/// {
|
/// {
|
||||||
/// JPH_ADD_BASE_CLASS(Bar, Foo)
|
/// JPH_ADD_BASE_CLASS(Bar, Foo)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
|
@ -90,21 +90,21 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// IsKindOf(bar_ptr, RTTI(Foo)) returns true
|
/// IsKindOf(bar_ptr, RTTI(Foo)) returns true
|
||||||
/// IsKindOf(bar_ptr, RTTI(Bar)) returns true
|
/// IsKindOf(bar_ptr, RTTI(Bar)) returns true
|
||||||
///
|
///
|
||||||
/// StaticCast<Bar>(foo_ptr) asserts and returns foo_ptr casted to pBar
|
/// StaticCast<Bar>(foo_ptr) asserts and returns foo_ptr casted to Bar *
|
||||||
/// StaticCast<Bar>(bar_ptr) returns bar_ptr casted to pBar
|
/// StaticCast<Bar>(bar_ptr) returns bar_ptr casted to Bar *
|
||||||
///
|
///
|
||||||
/// DynamicCast<Bar>(foo_ptr) returns nullptr
|
/// DynamicCast<Bar>(foo_ptr) returns nullptr
|
||||||
/// DynamicCast<Bar>(bar_ptr) returns bar_ptr casted to pBar
|
/// DynamicCast<Bar>(bar_ptr) returns bar_ptr casted to Bar *
|
||||||
///
|
///
|
||||||
/// Other feature of DynamicCast:
|
/// Other feature of DynamicCast:
|
||||||
///
|
///
|
||||||
/// class A { int data[5]; };
|
/// class A { int data[5]; };
|
||||||
/// class B { int data[7]; };
|
/// class B { int data[7]; };
|
||||||
/// class C : public A, public B { int data[9]; };
|
/// class C : public A, public B { int data[9]; };
|
||||||
///
|
///
|
||||||
/// C *c = new C;
|
/// C *c = new C;
|
||||||
/// A *a = c;
|
/// A *a = c;
|
||||||
///
|
///
|
||||||
/// Note that:
|
/// Note that:
|
||||||
///
|
///
|
||||||
/// B *b = (B *)a;
|
/// B *b = (B *)a;
|
||||||
|
|
@ -116,9 +116,9 @@ JPH_NAMESPACE_BEGIN
|
||||||
/// doesn't compile, and
|
/// doesn't compile, and
|
||||||
///
|
///
|
||||||
/// B *b = DynamicCast<B>(a);
|
/// B *b = DynamicCast<B>(a);
|
||||||
///
|
///
|
||||||
/// does the correct cast
|
/// does the correct cast
|
||||||
class RTTI
|
class JPH_EXPORT RTTI
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Function to create an object
|
/// Function to create an object
|
||||||
|
|
@ -151,7 +151,7 @@ public:
|
||||||
|
|
||||||
/// Add base class
|
/// Add base class
|
||||||
void AddBaseClass(const RTTI *inRTTI, int inOffset);
|
void AddBaseClass(const RTTI *inRTTI, int inOffset);
|
||||||
|
|
||||||
/// Equality operators
|
/// Equality operators
|
||||||
bool operator == (const RTTI &inRHS) const;
|
bool operator == (const RTTI &inRHS) const;
|
||||||
bool operator != (const RTTI &inRHS) const { return !(*this == inRHS); }
|
bool operator != (const RTTI &inRHS) const { return !(*this == inRHS); }
|
||||||
|
|
@ -162,10 +162,12 @@ public:
|
||||||
/// Cast inObject of this type to object of type inRTTI, returns nullptr if the cast is unsuccessful
|
/// Cast inObject of this type to object of type inRTTI, returns nullptr if the cast is unsuccessful
|
||||||
const void * CastTo(const void *inObject, const RTTI *inRTTI) const;
|
const void * CastTo(const void *inObject, const RTTI *inRTTI) const;
|
||||||
|
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
/// Attribute access
|
/// Attribute access
|
||||||
void AddAttribute(const SerializableAttribute &inAttribute);
|
void AddAttribute(const SerializableAttribute &inAttribute);
|
||||||
int GetAttributeCount() const;
|
int GetAttributeCount() const;
|
||||||
const SerializableAttribute & GetAttribute(int inIdx) const;
|
const SerializableAttribute & GetAttribute(int inIdx) const;
|
||||||
|
#endif // JPH_OBJECT_STREAM
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Base class information
|
/// Base class information
|
||||||
|
|
@ -180,7 +182,9 @@ protected:
|
||||||
StaticArray<BaseClass, 4> mBaseClasses; ///< Names of base classes
|
StaticArray<BaseClass, 4> mBaseClasses; ///< Names of base classes
|
||||||
pCreateObjectFunction mCreate; ///< Pointer to a function that will create a new instance of this class
|
pCreateObjectFunction mCreate; ///< Pointer to a function that will create a new instance of this class
|
||||||
pDestructObjectFunction mDestruct; ///< Pointer to a function that will destruct an object of this class
|
pDestructObjectFunction mDestruct; ///< Pointer to a function that will destruct an object of this class
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
StaticArray<SerializableAttribute, 32> mAttributes; ///< All attributes of this class
|
StaticArray<SerializableAttribute, 32> mAttributes; ///< All attributes of this class
|
||||||
|
#endif // JPH_OBJECT_STREAM
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -188,11 +192,11 @@ protected:
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_NON_VIRTUAL
|
// JPH_DECLARE_RTTI_NON_VIRTUAL
|
||||||
#define JPH_DECLARE_RTTI_NON_VIRTUAL(class_name) \
|
#define JPH_DECLARE_RTTI_NON_VIRTUAL(linkage, class_name) \
|
||||||
public: \
|
public: \
|
||||||
JPH_OVERRIDE_NEW_DELETE \
|
JPH_OVERRIDE_NEW_DELETE \
|
||||||
friend RTTI * GetRTTIOfType(class_name *); \
|
friend linkage RTTI * GetRTTIOfType(class_name *); \
|
||||||
friend inline const RTTI * GetRTTI(const class_name *inObject) { return GetRTTIOfType((class_name *)nullptr); }\
|
friend inline const RTTI * GetRTTI([[maybe_unused]] const class_name *inObject) { return GetRTTIOfType(static_cast<class_name *>(nullptr)); }\
|
||||||
static void sCreateRTTI(RTTI &inRTTI); \
|
static void sCreateRTTI(RTTI &inRTTI); \
|
||||||
|
|
||||||
// JPH_IMPLEMENT_RTTI_NON_VIRTUAL
|
// JPH_IMPLEMENT_RTTI_NON_VIRTUAL
|
||||||
|
|
@ -210,8 +214,8 @@ public: \
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_OUTSIDE_CLASS
|
// JPH_DECLARE_RTTI_OUTSIDE_CLASS
|
||||||
#define JPH_DECLARE_RTTI_OUTSIDE_CLASS(class_name) \
|
#define JPH_DECLARE_RTTI_OUTSIDE_CLASS(linkage, class_name) \
|
||||||
RTTI * GetRTTIOfType(class_name *); \
|
linkage RTTI * GetRTTIOfType(class_name *); \
|
||||||
inline const RTTI * GetRTTI(const class_name *inObject) { return GetRTTIOfType((class_name *)nullptr); }\
|
inline const RTTI * GetRTTI(const class_name *inObject) { return GetRTTIOfType((class_name *)nullptr); }\
|
||||||
void CreateRTTI##class_name(RTTI &inRTTI); \
|
void CreateRTTI##class_name(RTTI &inRTTI); \
|
||||||
|
|
||||||
|
|
@ -228,22 +232,22 @@ public: \
|
||||||
// Same as above, but for classes that have virtual functions
|
// Same as above, but for classes that have virtual functions
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define JPH_DECLARE_RTTI_HELPER(class_name, modifier) \
|
#define JPH_DECLARE_RTTI_HELPER(linkage, class_name, modifier) \
|
||||||
public: \
|
public: \
|
||||||
JPH_OVERRIDE_NEW_DELETE \
|
JPH_OVERRIDE_NEW_DELETE \
|
||||||
friend RTTI * GetRTTIOfType(class_name *); \
|
friend linkage RTTI * GetRTTIOfType(class_name *); \
|
||||||
friend inline const RTTI * GetRTTI(const class_name *inObject) { return inObject->GetRTTI(); } \
|
friend inline const RTTI * GetRTTI(const class_name *inObject) { return inObject->GetRTTI(); } \
|
||||||
virtual const RTTI * GetRTTI() const modifier; \
|
virtual const RTTI * GetRTTI() const modifier; \
|
||||||
virtual const void * CastTo(const RTTI *inRTTI) const modifier; \
|
virtual const void * CastTo(const RTTI *inRTTI) const modifier; \
|
||||||
static void sCreateRTTI(RTTI &inRTTI); \
|
static void sCreateRTTI(RTTI &inRTTI); \
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_VIRTUAL - for derived classes with RTTI
|
// JPH_DECLARE_RTTI_VIRTUAL - for derived classes with RTTI
|
||||||
#define JPH_DECLARE_RTTI_VIRTUAL(class_name) \
|
#define JPH_DECLARE_RTTI_VIRTUAL(linkage, class_name) \
|
||||||
JPH_DECLARE_RTTI_HELPER(class_name, override)
|
JPH_DECLARE_RTTI_HELPER(linkage, class_name, override)
|
||||||
|
|
||||||
// JPH_IMPLEMENT_RTTI_VIRTUAL
|
// JPH_IMPLEMENT_RTTI_VIRTUAL
|
||||||
#define JPH_IMPLEMENT_RTTI_VIRTUAL(class_name) \
|
#define JPH_IMPLEMENT_RTTI_VIRTUAL(class_name) \
|
||||||
RTTI * GetRTTIOfType(class_name *) \
|
RTTI * GetRTTIOfType(class_name *) \
|
||||||
{ \
|
{ \
|
||||||
static RTTI rtti(#class_name, sizeof(class_name), []() -> void * { return new class_name; }, [](void *inObject) { delete (class_name *)inObject; }, &class_name::sCreateRTTI); \
|
static RTTI rtti(#class_name, sizeof(class_name), []() -> void * { return new class_name; }, [](void *inObject) { delete (class_name *)inObject; }, &class_name::sCreateRTTI); \
|
||||||
return &rtti; \
|
return &rtti; \
|
||||||
|
|
@ -259,16 +263,16 @@ public: \
|
||||||
void class_name::sCreateRTTI(RTTI &inRTTI) \
|
void class_name::sCreateRTTI(RTTI &inRTTI) \
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_VIRTUAL_BASE - for concrete base class that has RTTI
|
// JPH_DECLARE_RTTI_VIRTUAL_BASE - for concrete base class that has RTTI
|
||||||
#define JPH_DECLARE_RTTI_VIRTUAL_BASE(class_name) \
|
#define JPH_DECLARE_RTTI_VIRTUAL_BASE(linkage, class_name) \
|
||||||
JPH_DECLARE_RTTI_HELPER(class_name, )
|
JPH_DECLARE_RTTI_HELPER(linkage, class_name, )
|
||||||
|
|
||||||
// JPH_IMPLEMENT_RTTI_VIRTUAL_BASE
|
// JPH_IMPLEMENT_RTTI_VIRTUAL_BASE
|
||||||
#define JPH_IMPLEMENT_RTTI_VIRTUAL_BASE(class_name) \
|
#define JPH_IMPLEMENT_RTTI_VIRTUAL_BASE(class_name) \
|
||||||
JPH_IMPLEMENT_RTTI_VIRTUAL(class_name)
|
JPH_IMPLEMENT_RTTI_VIRTUAL(class_name)
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_ABSTRACT - for derived abstract class that have RTTI
|
// JPH_DECLARE_RTTI_ABSTRACT - for derived abstract class that have RTTI
|
||||||
#define JPH_DECLARE_RTTI_ABSTRACT(class_name) \
|
#define JPH_DECLARE_RTTI_ABSTRACT(linkage, class_name) \
|
||||||
JPH_DECLARE_RTTI_HELPER(class_name, override)
|
JPH_DECLARE_RTTI_HELPER(linkage, class_name, override)
|
||||||
|
|
||||||
// JPH_IMPLEMENT_RTTI_ABSTRACT
|
// JPH_IMPLEMENT_RTTI_ABSTRACT
|
||||||
#define JPH_IMPLEMENT_RTTI_ABSTRACT(class_name) \
|
#define JPH_IMPLEMENT_RTTI_ABSTRACT(class_name) \
|
||||||
|
|
@ -288,8 +292,8 @@ public: \
|
||||||
void class_name::sCreateRTTI(RTTI &inRTTI) \
|
void class_name::sCreateRTTI(RTTI &inRTTI) \
|
||||||
|
|
||||||
// JPH_DECLARE_RTTI_ABSTRACT_BASE - for abstract base class that has RTTI
|
// JPH_DECLARE_RTTI_ABSTRACT_BASE - for abstract base class that has RTTI
|
||||||
#define JPH_DECLARE_RTTI_ABSTRACT_BASE(class_name) \
|
#define JPH_DECLARE_RTTI_ABSTRACT_BASE(linkage, class_name) \
|
||||||
JPH_DECLARE_RTTI_HELPER(class_name, )
|
JPH_DECLARE_RTTI_HELPER(linkage, class_name, )
|
||||||
|
|
||||||
// JPH_IMPLEMENT_RTTI_ABSTRACT_BASE
|
// JPH_IMPLEMENT_RTTI_ABSTRACT_BASE
|
||||||
#define JPH_IMPLEMENT_RTTI_ABSTRACT_BASE(class_name) \
|
#define JPH_IMPLEMENT_RTTI_ABSTRACT_BASE(class_name) \
|
||||||
|
|
@ -299,20 +303,20 @@ public: \
|
||||||
// Declare an RTTI class for registering with the factory
|
// Declare an RTTI class for registering with the factory
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define JPH_DECLARE_RTTI_FOR_FACTORY(class_name) \
|
#define JPH_DECLARE_RTTI_FOR_FACTORY(linkage, class_name) \
|
||||||
RTTI * GetRTTIOfType(class class_name *);
|
linkage RTTI * GetRTTIOfType(class class_name *);
|
||||||
|
|
||||||
#define JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(name_space, class_name) \
|
#define JPH_DECLARE_RTTI_WITH_NAMESPACE_FOR_FACTORY(linkage, name_space, class_name) \
|
||||||
namespace name_space { \
|
namespace name_space { \
|
||||||
class class_name; \
|
class class_name; \
|
||||||
RTTI * GetRTTIOfType(class class_name *); \
|
linkage RTTI * GetRTTIOfType(class class_name *); \
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Find the RTTI of a class
|
// Find the RTTI of a class
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define JPH_RTTI(class_name) GetRTTIOfType((class_name *)nullptr)
|
#define JPH_RTTI(class_name) GetRTTIOfType(static_cast<class_name *>(nullptr))
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Macro to rename a class, useful for embedded classes:
|
// Macro to rename a class, useful for embedded classes:
|
||||||
|
|
@ -333,7 +337,7 @@ public: \
|
||||||
/// Define very dirty macro to get the offset of a baseclass into a class
|
/// Define very dirty macro to get the offset of a baseclass into a class
|
||||||
#define JPH_BASE_CLASS_OFFSET(inClass, inBaseClass) ((int(uint64((inBaseClass *)((inClass *)0x10000))))-0x10000)
|
#define JPH_BASE_CLASS_OFFSET(inClass, inBaseClass) ((int(uint64((inBaseClass *)((inClass *)0x10000))))-0x10000)
|
||||||
|
|
||||||
// JPH_ADD_BASE_CLASS
|
// JPH_ADD_BASE_CLASS
|
||||||
#define JPH_ADD_BASE_CLASS(class_name, base_class_name) \
|
#define JPH_ADD_BASE_CLASS(class_name, base_class_name) \
|
||||||
inRTTI.AddBaseClass(JPH_RTTI(base_class_name), JPH_BASE_CLASS_OFFSET(class_name, base_class_name));
|
inRTTI.AddBaseClass(JPH_RTTI(base_class_name), JPH_BASE_CLASS_OFFSET(class_name, base_class_name));
|
||||||
|
|
||||||
|
|
@ -380,31 +384,27 @@ inline bool IsKindOf(const Ref<Type> &inObject, const RTTI *inRTTI)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast inObject to DstType, asserts on failure
|
/// Cast inObject to DstType, asserts on failure
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType, std::enable_if_t<std::is_base_of_v<DstType, SrcType> || std::is_base_of_v<SrcType, DstType>, bool> = true>
|
||||||
inline const DstType *StaticCast(const SrcType *inObject)
|
inline const DstType *StaticCast(const SrcType *inObject)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(IsKindOf(inObject, JPH_RTTI(DstType)), "Invalid cast");
|
|
||||||
return static_cast<const DstType *>(inObject);
|
return static_cast<const DstType *>(inObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType, std::enable_if_t<std::is_base_of_v<DstType, SrcType> || std::is_base_of_v<SrcType, DstType>, bool> = true>
|
||||||
inline DstType *StaticCast(SrcType *inObject)
|
inline DstType *StaticCast(SrcType *inObject)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(IsKindOf(inObject, JPH_RTTI(DstType)), "Invalid cast");
|
|
||||||
return static_cast<DstType *>(inObject);
|
return static_cast<DstType *>(inObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType, std::enable_if_t<std::is_base_of_v<DstType, SrcType> || std::is_base_of_v<SrcType, DstType>, bool> = true>
|
||||||
inline RefConst<DstType> StaticCast(RefConst<SrcType> &inObject)
|
inline const DstType *StaticCast(const RefConst<SrcType> &inObject)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(IsKindOf(inObject, JPH_RTTI(DstType)), "Invalid cast");
|
|
||||||
return static_cast<const DstType *>(inObject.GetPtr());
|
return static_cast<const DstType *>(inObject.GetPtr());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType, std::enable_if_t<std::is_base_of_v<DstType, SrcType> || std::is_base_of_v<SrcType, DstType>, bool> = true>
|
||||||
inline Ref<DstType> StaticCast(Ref<SrcType> &inObject)
|
inline DstType *StaticCast(const Ref<SrcType> &inObject)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(IsKindOf(inObject, JPH_RTTI(DstType)), "Invalid cast");
|
|
||||||
return static_cast<DstType *>(inObject.GetPtr());
|
return static_cast<DstType *>(inObject.GetPtr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,13 +422,13 @@ inline DstType *DynamicCast(SrcType *inObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType>
|
||||||
inline RefConst<DstType> DynamicCast(RefConst<SrcType> &inObject)
|
inline const DstType *DynamicCast(const RefConst<SrcType> &inObject)
|
||||||
{
|
{
|
||||||
return inObject != nullptr? reinterpret_cast<const DstType *>(inObject->CastTo(JPH_RTTI(DstType))) : nullptr;
|
return inObject != nullptr? reinterpret_cast<const DstType *>(inObject->CastTo(JPH_RTTI(DstType))) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class DstType, class SrcType>
|
template <class DstType, class SrcType>
|
||||||
inline Ref<DstType> DynamicCast(Ref<SrcType> &inObject)
|
inline DstType *DynamicCast(const Ref<SrcType> &inObject)
|
||||||
{
|
{
|
||||||
return inObject != nullptr? const_cast<DstType *>(reinterpret_cast<const DstType *>(inObject->CastTo(JPH_RTTI(DstType)))) : nullptr;
|
return inObject != nullptr? const_cast<DstType *>(reinterpret_cast<const DstType *>(inObject->CastTo(JPH_RTTI(DstType)))) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ template <class T> class RefConst;
|
||||||
/// some responsibility to the programmer. The most notable point is that you cannot
|
/// some responsibility to the programmer. The most notable point is that you cannot
|
||||||
/// have one object reference another and have the other reference the first one
|
/// have one object reference another and have the other reference the first one
|
||||||
/// back, because this way the reference count of both objects will never become
|
/// back, because this way the reference count of both objects will never become
|
||||||
/// lower than 1, resulting in a memory leak. By carefully designing your classses
|
/// lower than 1, resulting in a memory leak. By carefully designing your classes
|
||||||
/// (and particularly identifying who owns who in the class hierarchy) you can avoid
|
/// (and particularly identifying who owns who in the class hierarchy) you can avoid
|
||||||
/// these problems.
|
/// these problems.
|
||||||
template <class T>
|
template <class T>
|
||||||
class RefTarget
|
class RefTarget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline RefTarget() = default;
|
inline RefTarget() = default;
|
||||||
|
|
@ -59,13 +59,19 @@ public:
|
||||||
|
|
||||||
inline void Release() const
|
inline void Release() const
|
||||||
{
|
{
|
||||||
|
#ifndef JPH_TSAN_ENABLED
|
||||||
// Releasing a reference must use release semantics...
|
// Releasing a reference must use release semantics...
|
||||||
if (mRefCount.fetch_sub(1, memory_order_release) == 1)
|
if (mRefCount.fetch_sub(1, memory_order_release) == 1)
|
||||||
{
|
{
|
||||||
// ... so that we can use aquire to ensure that we see any updates from other threads that released a ref before deleting the object
|
// ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before deleting the object
|
||||||
atomic_thread_fence(memory_order_acquire);
|
atomic_thread_fence(memory_order_acquire);
|
||||||
delete static_cast<const T *>(this);
|
delete static_cast<const T *>(this);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// But under TSAN, we cannot use atomic_thread_fence, so we use an acq_rel operation unconditionally instead
|
||||||
|
if (mRefCount.fetch_sub(1, memory_order_acq_rel) == 1)
|
||||||
|
delete static_cast<const T *>(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
||||||
|
|
@ -75,10 +81,10 @@ protected:
|
||||||
static constexpr uint32 cEmbedded = 0x0ebedded; ///< A large value that gets added to the refcount to mark the object as embedded
|
static constexpr uint32 cEmbedded = 0x0ebedded; ///< A large value that gets added to the refcount to mark the object as embedded
|
||||||
|
|
||||||
mutable atomic<uint32> mRefCount = 0; ///< Current reference count
|
mutable atomic<uint32> mRefCount = 0; ///< Current reference count
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Pure virtual version of RefTarget
|
/// Pure virtual version of RefTarget
|
||||||
class RefTargetVirtual
|
class JPH_EXPORT RefTargetVirtual
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Virtual destructor
|
/// Virtual destructor
|
||||||
|
|
@ -106,19 +112,17 @@ public:
|
||||||
inline Ref(const Ref<T> &inRHS) : mPtr(inRHS.mPtr) { AddRef(); }
|
inline Ref(const Ref<T> &inRHS) : mPtr(inRHS.mPtr) { AddRef(); }
|
||||||
inline Ref(Ref<T> &&inRHS) noexcept : mPtr(inRHS.mPtr) { inRHS.mPtr = nullptr; }
|
inline Ref(Ref<T> &&inRHS) noexcept : mPtr(inRHS.mPtr) { inRHS.mPtr = nullptr; }
|
||||||
inline ~Ref() { Release(); }
|
inline ~Ref() { Release(); }
|
||||||
|
|
||||||
/// Assignment operators
|
/// Assignment operators
|
||||||
inline Ref<T> & operator = (T *inRHS) { if (mPtr != inRHS) { Release(); mPtr = inRHS; AddRef(); } return *this; }
|
inline Ref<T> & operator = (T *inRHS) { if (mPtr != inRHS) { Release(); mPtr = inRHS; AddRef(); } return *this; }
|
||||||
inline Ref<T> & operator = (const Ref<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
inline Ref<T> & operator = (const Ref<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
||||||
inline Ref<T> & operator = (Ref<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
inline Ref<T> & operator = (Ref<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
||||||
|
|
||||||
/// Casting operators
|
/// Casting operators
|
||||||
inline operator T * const () const { return mPtr; }
|
inline operator T *() const { return mPtr; }
|
||||||
inline operator T *() { return mPtr; }
|
|
||||||
|
|
||||||
/// Access like a normal pointer
|
/// Access like a normal pointer
|
||||||
inline T * const operator -> () const { return mPtr; }
|
inline T * operator -> () const { return mPtr; }
|
||||||
inline T * operator -> () { return mPtr; }
|
|
||||||
inline T & operator * () const { return *mPtr; }
|
inline T & operator * () const { return *mPtr; }
|
||||||
|
|
||||||
/// Comparison
|
/// Comparison
|
||||||
|
|
@ -128,8 +132,13 @@ public:
|
||||||
inline bool operator != (const Ref<T> &inRHS) const { return mPtr != inRHS.mPtr; }
|
inline bool operator != (const Ref<T> &inRHS) const { return mPtr != inRHS.mPtr; }
|
||||||
|
|
||||||
/// Get pointer
|
/// Get pointer
|
||||||
inline T * GetPtr() const { return mPtr; }
|
inline T * GetPtr() const { return mPtr; }
|
||||||
inline T * GetPtr() { return mPtr; }
|
|
||||||
|
/// Get hash for this object
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
return Hash<T *> { } (mPtr);
|
||||||
|
}
|
||||||
|
|
||||||
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
||||||
void ** InternalGetPointer() { return reinterpret_cast<void **>(&mPtr); }
|
void ** InternalGetPointer() { return reinterpret_cast<void **>(&mPtr); }
|
||||||
|
|
@ -140,9 +149,9 @@ private:
|
||||||
/// Use "variable = nullptr;" to release an object, do not call these functions
|
/// Use "variable = nullptr;" to release an object, do not call these functions
|
||||||
inline void AddRef() { if (mPtr != nullptr) mPtr->AddRef(); }
|
inline void AddRef() { if (mPtr != nullptr) mPtr->AddRef(); }
|
||||||
inline void Release() { if (mPtr != nullptr) mPtr->Release(); }
|
inline void Release() { if (mPtr != nullptr) mPtr->Release(); }
|
||||||
|
|
||||||
T * mPtr; ///< Pointer to object that we are reference counting
|
T * mPtr; ///< Pointer to object that we are reference counting
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Class for automatic referencing, this is the equivalent of a CONST pointer to type T
|
/// Class for automatic referencing, this is the equivalent of a CONST pointer to type T
|
||||||
/// if you assign a value to this class it will increment the reference count by one
|
/// if you assign a value to this class it will increment the reference count by one
|
||||||
|
|
@ -161,19 +170,19 @@ public:
|
||||||
inline RefConst(const Ref<T> &inRHS) : mPtr(inRHS.mPtr) { AddRef(); }
|
inline RefConst(const Ref<T> &inRHS) : mPtr(inRHS.mPtr) { AddRef(); }
|
||||||
inline RefConst(Ref<T> &&inRHS) noexcept : mPtr(inRHS.mPtr) { inRHS.mPtr = nullptr; }
|
inline RefConst(Ref<T> &&inRHS) noexcept : mPtr(inRHS.mPtr) { inRHS.mPtr = nullptr; }
|
||||||
inline ~RefConst() { Release(); }
|
inline ~RefConst() { Release(); }
|
||||||
|
|
||||||
/// Assignment operators
|
/// Assignment operators
|
||||||
inline RefConst<T> & operator = (const T * inRHS) { if (mPtr != inRHS) { Release(); mPtr = inRHS; AddRef(); } return *this; }
|
inline RefConst<T> & operator = (const T * inRHS) { if (mPtr != inRHS) { Release(); mPtr = inRHS; AddRef(); } return *this; }
|
||||||
inline RefConst<T> & operator = (const RefConst<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
inline RefConst<T> & operator = (const RefConst<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
||||||
inline RefConst<T> & operator = (RefConst<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
inline RefConst<T> & operator = (RefConst<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
||||||
inline RefConst<T> & operator = (const Ref<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
inline RefConst<T> & operator = (const Ref<T> &inRHS) { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; AddRef(); } return *this; }
|
||||||
inline RefConst<T> & operator = (Ref<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
inline RefConst<T> & operator = (Ref<T> &&inRHS) noexcept { if (mPtr != inRHS.mPtr) { Release(); mPtr = inRHS.mPtr; inRHS.mPtr = nullptr; } return *this; }
|
||||||
|
|
||||||
/// Casting operators
|
/// Casting operators
|
||||||
inline operator const T * () const { return mPtr; }
|
inline operator const T * () const { return mPtr; }
|
||||||
|
|
||||||
/// Access like a normal pointer
|
/// Access like a normal pointer
|
||||||
inline const T * operator -> () const { return mPtr; }
|
inline const T * operator -> () const { return mPtr; }
|
||||||
inline const T & operator * () const { return *mPtr; }
|
inline const T & operator * () const { return *mPtr; }
|
||||||
|
|
||||||
/// Comparison
|
/// Comparison
|
||||||
|
|
@ -185,7 +194,13 @@ public:
|
||||||
inline bool operator != (const Ref<T> &inRHS) const { return mPtr != inRHS.mPtr; }
|
inline bool operator != (const Ref<T> &inRHS) const { return mPtr != inRHS.mPtr; }
|
||||||
|
|
||||||
/// Get pointer
|
/// Get pointer
|
||||||
inline const T * GetPtr() const { return mPtr; }
|
inline const T * GetPtr() const { return mPtr; }
|
||||||
|
|
||||||
|
/// Get hash for this object
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
return Hash<const T *> { } (mPtr);
|
||||||
|
}
|
||||||
|
|
||||||
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
/// INTERNAL HELPER FUNCTION USED BY SERIALIZATION
|
||||||
void ** InternalGetPointer() { return const_cast<void **>(reinterpret_cast<const void **>(&mPtr)); }
|
void ** InternalGetPointer() { return const_cast<void **>(reinterpret_cast<const void **>(&mPtr)); }
|
||||||
|
|
@ -194,9 +209,9 @@ private:
|
||||||
/// Use "variable = nullptr;" to release an object, do not call these functions
|
/// Use "variable = nullptr;" to release an object, do not call these functions
|
||||||
inline void AddRef() { if (mPtr != nullptr) mPtr->AddRef(); }
|
inline void AddRef() { if (mPtr != nullptr) mPtr->AddRef(); }
|
||||||
inline void Release() { if (mPtr != nullptr) mPtr->Release(); }
|
inline void Release() { if (mPtr != nullptr) mPtr->Release(); }
|
||||||
|
|
||||||
const T * mPtr; ///< Pointer to object that we are reference counting
|
const T * mPtr; ///< Pointer to object that we are reference counting
|
||||||
};
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
||||||
|
|
@ -206,22 +221,22 @@ JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
|
||||||
namespace std
|
namespace std
|
||||||
{
|
{
|
||||||
/// Declare std::hash for Ref
|
/// Declare std::hash for Ref
|
||||||
template <class T>
|
template <class T>
|
||||||
struct hash<JPH::Ref<T>>
|
struct hash<JPH::Ref<T>>
|
||||||
{
|
{
|
||||||
size_t operator () (const JPH::Ref<T> &inRHS) const
|
size_t operator () (const JPH::Ref<T> &inRHS) const
|
||||||
{
|
{
|
||||||
return hash<T *> { }(inRHS.GetPtr());
|
return size_t(inRHS.GetHash());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Declare std::hash for RefConst
|
/// Declare std::hash for RefConst
|
||||||
template <class T>
|
template <class T>
|
||||||
struct hash<JPH::RefConst<T>>
|
struct hash<JPH::RefConst<T>>
|
||||||
{
|
{
|
||||||
size_t operator () (const JPH::RefConst<T> &inRHS) const
|
size_t operator () (const JPH::RefConst<T> &inRHS) const
|
||||||
{
|
{
|
||||||
return hash<const T *> { }(inRHS.GetPtr());
|
return size_t(inRHS.GetHash());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// GCC doesn't properly detect that mState is used to ensure that mResult is initialized
|
|
||||||
JPH_GCC_SUPPRESS_WARNING("-Wmaybe-uninitialized")
|
|
||||||
|
|
||||||
/// Helper class that either contains a valid result or an error
|
/// Helper class that either contains a valid result or an error
|
||||||
template <class Type>
|
template <class Type>
|
||||||
class Result
|
class Result
|
||||||
|
|
@ -16,7 +13,7 @@ class Result
|
||||||
public:
|
public:
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
Result() { }
|
Result() { }
|
||||||
|
|
||||||
/// Copy constructor
|
/// Copy constructor
|
||||||
Result(const Result<Type> &inRHS) :
|
Result(const Result<Type> &inRHS) :
|
||||||
mState(inRHS.mState)
|
mState(inRHS.mState)
|
||||||
|
|
@ -24,11 +21,11 @@ public:
|
||||||
switch (inRHS.mState)
|
switch (inRHS.mState)
|
||||||
{
|
{
|
||||||
case EState::Valid:
|
case EState::Valid:
|
||||||
::new (&mResult) Type (inRHS.mResult);
|
new (&mResult) Type (inRHS.mResult);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Error:
|
case EState::Error:
|
||||||
::new (&mError) String(inRHS.mError);
|
new (&mError) String(inRHS.mError);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Invalid:
|
case EState::Invalid:
|
||||||
|
|
@ -43,18 +40,18 @@ public:
|
||||||
switch (inRHS.mState)
|
switch (inRHS.mState)
|
||||||
{
|
{
|
||||||
case EState::Valid:
|
case EState::Valid:
|
||||||
::new (&mResult) Type (std::move(inRHS.mResult));
|
new (&mResult) Type (std::move(inRHS.mResult));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Error:
|
case EState::Error:
|
||||||
::new (&mError) String(std::move(inRHS.mError));
|
new (&mError) String(std::move(inRHS.mError));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Invalid:
|
case EState::Invalid:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
inRHS.mState = EState::Invalid;
|
// Don't reset the state of inRHS, the destructors still need to be called after a move operation
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destructor
|
/// Destructor
|
||||||
|
|
@ -70,11 +67,11 @@ public:
|
||||||
switch (inRHS.mState)
|
switch (inRHS.mState)
|
||||||
{
|
{
|
||||||
case EState::Valid:
|
case EState::Valid:
|
||||||
::new (&mResult) Type (inRHS.mResult);
|
new (&mResult) Type (inRHS.mResult);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Error:
|
case EState::Error:
|
||||||
::new (&mError) String(inRHS.mError);
|
new (&mError) String(inRHS.mError);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Invalid:
|
case EState::Invalid:
|
||||||
|
|
@ -94,30 +91,30 @@ public:
|
||||||
switch (inRHS.mState)
|
switch (inRHS.mState)
|
||||||
{
|
{
|
||||||
case EState::Valid:
|
case EState::Valid:
|
||||||
::new (&mResult) Type (std::move(inRHS.mResult));
|
new (&mResult) Type (std::move(inRHS.mResult));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Error:
|
case EState::Error:
|
||||||
::new (&mError) String(std::move(inRHS.mError));
|
new (&mError) String(std::move(inRHS.mError));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Invalid:
|
case EState::Invalid:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
inRHS.mState = EState::Invalid;
|
// Don't reset the state of inRHS, the destructors still need to be called after a move operation
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear result or error
|
/// Clear result or error
|
||||||
void Clear()
|
void Clear()
|
||||||
{
|
{
|
||||||
switch (mState)
|
switch (mState)
|
||||||
{
|
{
|
||||||
case EState::Valid:
|
case EState::Valid:
|
||||||
mResult.~Type();
|
mResult.~Type();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EState::Error:
|
case EState::Error:
|
||||||
mError.~String();
|
mError.~String();
|
||||||
|
|
@ -140,10 +137,10 @@ public:
|
||||||
const Type & Get() const { JPH_ASSERT(IsValid()); return mResult; }
|
const Type & Get() const { JPH_ASSERT(IsValid()); return mResult; }
|
||||||
|
|
||||||
/// Set the result value
|
/// Set the result value
|
||||||
void Set(const Type &inResult) { Clear(); ::new (&mResult) Type(inResult); mState = EState::Valid; }
|
void Set(const Type &inResult) { Clear(); new (&mResult) Type(inResult); mState = EState::Valid; }
|
||||||
|
|
||||||
/// Set the result value (move value)
|
/// Set the result value (move value)
|
||||||
void Set(const Type &&inResult) { Clear(); ::new (&mResult) Type(std::move(inResult)); mState = EState::Valid; }
|
void Set(Type &&inResult) { Clear(); new (&mResult) Type(std::move(inResult)); mState = EState::Valid; }
|
||||||
|
|
||||||
/// Check if we had an error
|
/// Check if we had an error
|
||||||
bool HasError() const { return mState == EState::Error; }
|
bool HasError() const { return mState == EState::Error; }
|
||||||
|
|
@ -152,9 +149,9 @@ public:
|
||||||
const String & GetError() const { JPH_ASSERT(HasError()); return mError; }
|
const String & GetError() const { JPH_ASSERT(HasError()); return mError; }
|
||||||
|
|
||||||
/// Set an error value
|
/// Set an error value
|
||||||
void SetError(const char *inError) { Clear(); ::new (&mError) String(inError); mState = EState::Error; }
|
void SetError(const char *inError) { Clear(); new (&mError) String(inError); mState = EState::Error; }
|
||||||
void SetError(const string_view &inError) { Clear(); ::new (&mError) String(inError); mState = EState::Error; }
|
void SetError(const string_view &inError) { Clear(); new (&mError) String(inError); mState = EState::Error; }
|
||||||
void SetError(String &&inError) { Clear(); ::new (&mError) String(std::move(inError)); mState = EState::Error; }
|
void SetError(String &&inError) { Clear(); new (&mError) String(std::move(inError)); mState = EState::Error; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
union
|
union
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ public:
|
||||||
using size_type = size_t;
|
using size_type = size_t;
|
||||||
using difference_type = ptrdiff_t;
|
using difference_type = ptrdiff_t;
|
||||||
|
|
||||||
|
/// The allocator is stateless
|
||||||
|
using is_always_equal = std::true_type;
|
||||||
|
|
||||||
|
/// Allocator supports moving
|
||||||
|
using propagate_on_container_move_assignment = std::true_type;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline STLAlignedAllocator() = default;
|
inline STLAlignedAllocator() = default;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Default implementation of AllocatorHasReallocate which tells if an allocator has a reallocate function
|
||||||
|
template <class T> struct AllocatorHasReallocate { static constexpr bool sValue = false; };
|
||||||
|
|
||||||
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
|
||||||
/// STL allocator that forwards to our allocation functions
|
/// STL allocator that forwards to our allocation functions
|
||||||
|
|
@ -27,6 +30,12 @@ public:
|
||||||
using size_type = size_t;
|
using size_type = size_t;
|
||||||
using difference_type = ptrdiff_t;
|
using difference_type = ptrdiff_t;
|
||||||
|
|
||||||
|
/// The allocator is stateless
|
||||||
|
using is_always_equal = std::true_type;
|
||||||
|
|
||||||
|
/// Allocator supports moving
|
||||||
|
using propagate_on_container_move_assignment = std::true_type;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline STLAllocator() = default;
|
inline STLAllocator() = default;
|
||||||
|
|
||||||
|
|
@ -34,19 +43,33 @@ public:
|
||||||
template <typename T2>
|
template <typename T2>
|
||||||
inline STLAllocator(const STLAllocator<T2> &) { }
|
inline STLAllocator(const STLAllocator<T2> &) { }
|
||||||
|
|
||||||
|
/// If this allocator needs to fall back to aligned allocations because the type requires it
|
||||||
|
static constexpr bool needs_aligned_allocate = alignof(T) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16);
|
||||||
|
|
||||||
/// Allocate memory
|
/// Allocate memory
|
||||||
inline pointer allocate(size_type inN)
|
inline pointer allocate(size_type inN)
|
||||||
{
|
{
|
||||||
if constexpr (alignof(T) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16))
|
if constexpr (needs_aligned_allocate)
|
||||||
return (pointer)AlignedAllocate(inN * sizeof(value_type), alignof(T));
|
return pointer(AlignedAllocate(inN * sizeof(value_type), alignof(T)));
|
||||||
else
|
else
|
||||||
return (pointer)Allocate(inN * sizeof(value_type));
|
return pointer(Allocate(inN * sizeof(value_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should we expose a reallocate function?
|
||||||
|
static constexpr bool has_reallocate = std::is_trivially_copyable<T>() && !needs_aligned_allocate;
|
||||||
|
|
||||||
|
/// Reallocate memory
|
||||||
|
template <bool has_reallocate_v = has_reallocate, typename = std::enable_if_t<has_reallocate_v>>
|
||||||
|
inline pointer reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inNewSize > 0); // Reallocating to zero size is implementation dependent, so we don't allow it
|
||||||
|
return pointer(Reallocate(inOldPointer, inOldSize * sizeof(value_type), inNewSize * sizeof(value_type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Free memory
|
/// Free memory
|
||||||
inline void deallocate(pointer inPointer, size_type)
|
inline void deallocate(pointer inPointer, size_type)
|
||||||
{
|
{
|
||||||
if constexpr (alignof(T) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16))
|
if constexpr (needs_aligned_allocate)
|
||||||
AlignedFree(inPointer);
|
AlignedFree(inPointer);
|
||||||
else
|
else
|
||||||
Free(inPointer);
|
Free(inPointer);
|
||||||
|
|
@ -71,6 +94,9 @@ public:
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The STLAllocator implements the reallocate function if the alignment of the class is smaller or equal to the default alignment for the platform
|
||||||
|
template <class T> struct AllocatorHasReallocate<STLAllocator<T>> { static constexpr bool sValue = STLAllocator<T>::has_reallocate; };
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
template <typename T> using STLAllocator = std::allocator<T>;
|
template <typename T> using STLAllocator = std::allocator<T>;
|
||||||
|
|
@ -78,7 +104,6 @@ template <typename T> using STLAllocator = std::allocator<T>;
|
||||||
#endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
|
#endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
|
||||||
// Declare STL containers that use our allocator
|
// Declare STL containers that use our allocator
|
||||||
template <class T> using Array = std::vector<T, STLAllocator<T>>;
|
|
||||||
using String = std::basic_string<char, std::char_traits<char>, STLAllocator<char>>;
|
using String = std::basic_string<char, std::char_traits<char>, STLAllocator<char>>;
|
||||||
using IStringStream = std::basic_istringstream<char, std::char_traits<char>, STLAllocator<char>>;
|
using IStringStream = std::basic_istringstream<char, std::char_traits<char>, STLAllocator<char>>;
|
||||||
|
|
||||||
|
|
@ -89,7 +114,7 @@ JPH_NAMESPACE_END
|
||||||
namespace std
|
namespace std
|
||||||
{
|
{
|
||||||
/// Declare std::hash for String, for some reason on Linux based platforms template deduction takes the wrong variant
|
/// Declare std::hash for String, for some reason on Linux based platforms template deduction takes the wrong variant
|
||||||
template <>
|
template <>
|
||||||
struct hash<JPH::String>
|
struct hash<JPH::String>
|
||||||
{
|
{
|
||||||
inline size_t operator () (const JPH::String &inRHS) const
|
inline size_t operator () (const JPH::String &inRHS) const
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/STLAllocator.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#ifndef JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
|
||||||
|
/// STL allocator that keeps N elements in a local buffer before falling back to regular allocations
|
||||||
|
template <typename T, size_t N>
|
||||||
|
class STLLocalAllocator : private STLAllocator<T>
|
||||||
|
{
|
||||||
|
using Base = STLAllocator<T>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// General properties
|
||||||
|
using value_type = T;
|
||||||
|
using pointer = T *;
|
||||||
|
using const_pointer = const T *;
|
||||||
|
using reference = T &;
|
||||||
|
using const_reference = const T &;
|
||||||
|
using size_type = size_t;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
|
||||||
|
/// The allocator is not stateless (has local buffer)
|
||||||
|
using is_always_equal = std::false_type;
|
||||||
|
|
||||||
|
/// We cannot copy, move or swap allocators
|
||||||
|
using propagate_on_container_copy_assignment = std::false_type;
|
||||||
|
using propagate_on_container_move_assignment = std::false_type;
|
||||||
|
using propagate_on_container_swap = std::false_type;
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
STLLocalAllocator() = default;
|
||||||
|
STLLocalAllocator(const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
|
||||||
|
STLLocalAllocator(STLLocalAllocator &&) = delete; // Can't move an allocator as the buffer is local to the original
|
||||||
|
STLLocalAllocator & operator = (const STLLocalAllocator &) = delete; // Can't copy an allocator as the buffer is local to the original
|
||||||
|
|
||||||
|
/// Constructor used when rebinding to another type. This expects the allocator to use the original memory pool from the first allocator,
|
||||||
|
/// but in our case we cannot use the local buffer of the original allocator as it has different size and alignment rules.
|
||||||
|
/// To solve this we make this allocator fall back to the heap immediately.
|
||||||
|
template <class T2>
|
||||||
|
explicit STLLocalAllocator(const STLLocalAllocator<T2, N> &) : mNumElementsUsed(N) { }
|
||||||
|
|
||||||
|
/// Check if inPointer is in the local buffer
|
||||||
|
inline bool is_local(const_pointer inPointer) const
|
||||||
|
{
|
||||||
|
ptrdiff_t diff = inPointer - reinterpret_cast<const_pointer>(mElements);
|
||||||
|
return diff >= 0 && diff < ptrdiff_t(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate memory
|
||||||
|
inline pointer allocate(size_type inN)
|
||||||
|
{
|
||||||
|
// If we allocate more than we have, fall back to the heap
|
||||||
|
if (mNumElementsUsed + inN > N)
|
||||||
|
return Base::allocate(inN);
|
||||||
|
|
||||||
|
// Allocate from our local buffer
|
||||||
|
pointer result = reinterpret_cast<pointer>(mElements) + mNumElementsUsed;
|
||||||
|
mNumElementsUsed += inN;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Always implements a reallocate function as we can often reallocate in place
|
||||||
|
static constexpr bool has_reallocate = true;
|
||||||
|
|
||||||
|
/// Reallocate memory
|
||||||
|
inline pointer reallocate(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inNewSize > 0); // Reallocating to zero size is implementation dependent, so we don't allow it
|
||||||
|
|
||||||
|
// If there was no previous allocation, we can go through the regular allocate function
|
||||||
|
if (inOldPointer == nullptr)
|
||||||
|
return allocate(inNewSize);
|
||||||
|
|
||||||
|
// If the pointer is outside our local buffer, fall back to the heap
|
||||||
|
if (!is_local(inOldPointer))
|
||||||
|
{
|
||||||
|
if constexpr (AllocatorHasReallocate<Base>::sValue)
|
||||||
|
return Base::reallocate(inOldPointer, inOldSize, inNewSize);
|
||||||
|
else
|
||||||
|
return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we happen to have space left, we only need to update our bookkeeping
|
||||||
|
pointer base_ptr = reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inOldSize;
|
||||||
|
if (inOldPointer == base_ptr
|
||||||
|
&& mNumElementsUsed - inOldSize + inNewSize <= N)
|
||||||
|
{
|
||||||
|
mNumElementsUsed += inNewSize - inOldSize;
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't reallocate in place, fall back to the heap
|
||||||
|
return ReallocateImpl(inOldPointer, inOldSize, inNewSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Free memory
|
||||||
|
inline void deallocate(pointer inPointer, size_type inN)
|
||||||
|
{
|
||||||
|
// If the pointer is not in our local buffer, fall back to the heap
|
||||||
|
if (!is_local(inPointer))
|
||||||
|
return Base::deallocate(inPointer, inN);
|
||||||
|
|
||||||
|
// Else we can only reclaim memory if it was the last allocation
|
||||||
|
if (inPointer == reinterpret_cast<pointer>(mElements) + mNumElementsUsed - inN)
|
||||||
|
mNumElementsUsed -= inN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocators are not-stateless, assume if allocator address matches that the allocators are the same
|
||||||
|
inline bool operator == (const STLLocalAllocator<T, N> &inRHS) const
|
||||||
|
{
|
||||||
|
return this == &inRHS;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator != (const STLLocalAllocator<T, N> &inRHS) const
|
||||||
|
{
|
||||||
|
return this != &inRHS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converting to allocator for other type
|
||||||
|
template <typename T2>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = STLLocalAllocator<T2, N>;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Implements reallocate when the base class doesn't or when we go from local buffer to heap
|
||||||
|
inline pointer ReallocateImpl(pointer inOldPointer, size_type inOldSize, size_type inNewSize)
|
||||||
|
{
|
||||||
|
pointer new_pointer = Base::allocate(inNewSize);
|
||||||
|
size_type n = min(inOldSize, inNewSize);
|
||||||
|
if constexpr (std::is_trivially_copyable<T>())
|
||||||
|
{
|
||||||
|
// Can use mem copy
|
||||||
|
memcpy(new_pointer, inOldPointer, n * sizeof(T));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Need to actually move the elements
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
new (new_pointer + i) T(std::move(inOldPointer[i]));
|
||||||
|
inOldPointer[i].~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deallocate(inOldPointer, inOldSize);
|
||||||
|
return new_pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
alignas(T) uint8 mElements[N * sizeof(T)];
|
||||||
|
size_type mNumElementsUsed = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The STLLocalAllocator always implements a reallocate function as it can often reallocate in place
|
||||||
|
template <class T, size_t N> struct AllocatorHasReallocate<STLLocalAllocator<T, N>> { static constexpr bool sValue = STLLocalAllocator<T, N>::has_reallocate; };
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
template <typename T, size_t N> using STLLocalAllocator = std::allocator<T>;
|
||||||
|
|
||||||
|
#endif // !JPH_DISABLE_CUSTOM_ALLOCATOR
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -27,6 +27,9 @@ public:
|
||||||
using size_type = size_t;
|
using size_type = size_t;
|
||||||
using difference_type = ptrdiff_t;
|
using difference_type = ptrdiff_t;
|
||||||
|
|
||||||
|
/// The allocator is not stateless (depends on the temp allocator)
|
||||||
|
using is_always_equal = std::false_type;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
inline STLTempAllocator(TempAllocator &inAllocator) : mAllocator(inAllocator) { }
|
inline STLTempAllocator(TempAllocator &inAllocator) : mAllocator(inAllocator) { }
|
||||||
|
|
||||||
|
|
@ -37,7 +40,7 @@ public:
|
||||||
/// Allocate memory
|
/// Allocate memory
|
||||||
inline pointer allocate(size_type inN)
|
inline pointer allocate(size_type inN)
|
||||||
{
|
{
|
||||||
return (pointer)mAllocator.Allocate(uint(inN * sizeof(value_type)));
|
return pointer(mAllocator.Allocate(uint(inN * sizeof(value_type))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Free memory
|
/// Free memory
|
||||||
|
|
@ -46,15 +49,15 @@ public:
|
||||||
mAllocator.Free(inPointer, uint(inN * sizeof(value_type)));
|
mAllocator.Free(inPointer, uint(inN * sizeof(value_type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocators are stateless so assumed to be equal
|
/// Allocators are not-stateless, assume if allocator address matches that the allocators are the same
|
||||||
inline bool operator == (const STLTempAllocator<T> &) const
|
inline bool operator == (const STLTempAllocator<T> &inRHS) const
|
||||||
{
|
{
|
||||||
return true;
|
return &mAllocator == &inRHS.mAllocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator != (const STLTempAllocator<T> &) const
|
inline bool operator != (const STLTempAllocator<T> &inRHS) const
|
||||||
{
|
{
|
||||||
return false;
|
return &mAllocator != &inRHS.mAllocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converting to allocator for other type
|
/// Converting to allocator for other type
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// Class that calls a function when it goes out of scope
|
||||||
|
template <class F>
|
||||||
|
class ScopeExit : public NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor specifies the exit function
|
||||||
|
JPH_INLINE explicit ScopeExit(F &&inFunction) : mFunction(std::move(inFunction)) { }
|
||||||
|
|
||||||
|
/// Destructor calls the exit function
|
||||||
|
JPH_INLINE ~ScopeExit() { if (!mInvoked) mFunction(); }
|
||||||
|
|
||||||
|
/// Call the exit function now instead of when going out of scope
|
||||||
|
JPH_INLINE void Invoke()
|
||||||
|
{
|
||||||
|
if (!mInvoked)
|
||||||
|
{
|
||||||
|
mFunction();
|
||||||
|
mInvoked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No longer call the exit function when going out of scope
|
||||||
|
JPH_INLINE void Release()
|
||||||
|
{
|
||||||
|
mInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
F mFunction;
|
||||||
|
bool mInvoked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define JPH_SCOPE_EXIT_TAG2(line) scope_exit##line
|
||||||
|
#define JPH_SCOPE_EXIT_TAG(line) JPH_SCOPE_EXIT_TAG2(line)
|
||||||
|
|
||||||
|
/// Usage: JPH_SCOPE_EXIT([]{ code to call on scope exit });
|
||||||
|
#define JPH_SCOPE_EXIT(...) ScopeExit JPH_SCOPE_EXIT_TAG(__LINE__)(__VA_ARGS__)
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -9,13 +9,14 @@
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
#ifndef JPH_COMPILER_MINGW
|
#ifndef JPH_COMPILER_MINGW
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#else
|
#else
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_SUPPRESS_WARNING_POP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -25,6 +26,31 @@ Semaphore::Semaphore()
|
||||||
{
|
{
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
mSemaphore = CreateSemaphore(nullptr, 0, INT_MAX, nullptr);
|
mSemaphore = CreateSemaphore(nullptr, 0, INT_MAX, nullptr);
|
||||||
|
if (mSemaphore == nullptr)
|
||||||
|
{
|
||||||
|
Trace("Failed to create semaphore");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
#elif defined(JPH_USE_PTHREADS)
|
||||||
|
int ret = sem_init(&mSemaphore, 0, 0);
|
||||||
|
if (ret == -1)
|
||||||
|
{
|
||||||
|
Trace("Failed to create semaphore");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
#elif defined(JPH_USE_GRAND_CENTRAL_DISPATCH)
|
||||||
|
mSemaphore = dispatch_semaphore_create(0);
|
||||||
|
if (mSemaphore == nullptr)
|
||||||
|
{
|
||||||
|
Trace("Failed to create semaphore");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
if (!JPH_PLATFORM_BLUE_SEMAPHORE_INIT(mSemaphore))
|
||||||
|
{
|
||||||
|
Trace("Failed to create semaphore");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,6 +58,12 @@ Semaphore::~Semaphore()
|
||||||
{
|
{
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
CloseHandle(mSemaphore);
|
CloseHandle(mSemaphore);
|
||||||
|
#elif defined(JPH_USE_PTHREADS)
|
||||||
|
sem_destroy(&mSemaphore);
|
||||||
|
#elif defined(JPH_USE_GRAND_CENTRAL_DISPATCH)
|
||||||
|
dispatch_release(mSemaphore);
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
JPH_PLATFORM_BLUE_SEMAPHORE_DESTROY(mSemaphore);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,17 +71,27 @@ void Semaphore::Release(uint inNumber)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNumber > 0);
|
JPH_ASSERT(inNumber > 0);
|
||||||
|
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_USE_PTHREADS) || defined(JPH_USE_GRAND_CENTRAL_DISPATCH) || defined(JPH_PLATFORM_BLUE)
|
||||||
int old_value = mCount.fetch_add(inNumber);
|
int old_value = mCount.fetch_add(inNumber, std::memory_order_release);
|
||||||
if (old_value < 0)
|
if (old_value < 0)
|
||||||
{
|
{
|
||||||
int new_value = old_value + (int)inNumber;
|
int new_value = old_value + (int)inNumber;
|
||||||
int num_to_release = min(new_value, 0) - old_value;
|
int num_to_release = min(new_value, 0) - old_value;
|
||||||
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
::ReleaseSemaphore(mSemaphore, num_to_release, nullptr);
|
::ReleaseSemaphore(mSemaphore, num_to_release, nullptr);
|
||||||
|
#elif defined(JPH_USE_PTHREADS)
|
||||||
|
for (int i = 0; i < num_to_release; ++i)
|
||||||
|
sem_post(&mSemaphore);
|
||||||
|
#elif defined(JPH_USE_GRAND_CENTRAL_DISPATCH)
|
||||||
|
for (int i = 0; i < num_to_release; ++i)
|
||||||
|
dispatch_semaphore_signal(mSemaphore);
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
JPH_PLATFORM_BLUE_SEMAPHORE_SIGNAL(mSemaphore, num_to_release);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
std::lock_guard lock(mLock);
|
std::lock_guard lock(mLock);
|
||||||
mCount += (int)inNumber;
|
mCount.fetch_add(inNumber, std::memory_order_relaxed);
|
||||||
if (inNumber > 1)
|
if (inNumber > 1)
|
||||||
mWaitVariable.notify_all();
|
mWaitVariable.notify_all();
|
||||||
else
|
else
|
||||||
|
|
@ -61,19 +103,31 @@ void Semaphore::Acquire(uint inNumber)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNumber > 0);
|
JPH_ASSERT(inNumber > 0);
|
||||||
|
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_USE_PTHREADS) || defined(JPH_USE_GRAND_CENTRAL_DISPATCH) || defined(JPH_PLATFORM_BLUE)
|
||||||
int old_value = mCount.fetch_sub(inNumber);
|
int old_value = mCount.fetch_sub(inNumber, std::memory_order_acquire);
|
||||||
int new_value = old_value - (int)inNumber;
|
int new_value = old_value - (int)inNumber;
|
||||||
if (new_value < 0)
|
if (new_value < 0)
|
||||||
{
|
{
|
||||||
int num_to_acquire = min(old_value, 0) - new_value;
|
int num_to_acquire = min(old_value, 0) - new_value;
|
||||||
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
for (int i = 0; i < num_to_acquire; ++i)
|
for (int i = 0; i < num_to_acquire; ++i)
|
||||||
WaitForSingleObject(mSemaphore, INFINITE);
|
WaitForSingleObject(mSemaphore, INFINITE);
|
||||||
|
#elif defined(JPH_USE_PTHREADS)
|
||||||
|
for (int i = 0; i < num_to_acquire; ++i)
|
||||||
|
sem_wait(&mSemaphore);
|
||||||
|
#elif defined(JPH_USE_GRAND_CENTRAL_DISPATCH)
|
||||||
|
for (int i = 0; i < num_to_acquire; ++i)
|
||||||
|
dispatch_semaphore_wait(mSemaphore, DISPATCH_TIME_FOREVER);
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
JPH_PLATFORM_BLUE_SEMAPHORE_WAIT(mSemaphore, num_to_acquire);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
std::unique_lock lock(mLock);
|
std::unique_lock lock(mLock);
|
||||||
mCount -= (int)inNumber;
|
mWaitVariable.wait(lock, [this, inNumber]() {
|
||||||
mWaitVariable.wait(lock, [this]() { return mCount >= 0; });
|
return mCount.load(std::memory_order_relaxed) >= int(inNumber);
|
||||||
|
});
|
||||||
|
mCount.fetch_sub(inNumber, std::memory_order_relaxed);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,47 +4,64 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/Atomics.h>
|
||||||
|
|
||||||
|
// Determine which platform specific construct we'll use
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
#include <atomic>
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
#include <mutex>
|
// We include windows.h in the cpp file, the semaphore itself is a void pointer
|
||||||
#include <condition_variable>
|
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_BSD) || defined(JPH_PLATFORM_WASM)
|
||||||
|
#include <semaphore.h>
|
||||||
|
#define JPH_USE_PTHREADS
|
||||||
|
#elif defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
|
||||||
|
#include <dispatch/dispatch.h>
|
||||||
|
#define JPH_USE_GRAND_CENTRAL_DISPATCH
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
// Jolt/Core/PlatformBlue.h should have defined everything that is needed below
|
||||||
|
#else
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#endif
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// Things we're using from STL
|
|
||||||
using std::atomic;
|
|
||||||
using std::mutex;
|
|
||||||
using std::condition_variable;
|
|
||||||
|
|
||||||
/// Implements a semaphore
|
/// Implements a semaphore
|
||||||
/// When we switch to C++20 we can use counting_semaphore to unify this
|
/// When we switch to C++20 we can use counting_semaphore to unify this
|
||||||
class Semaphore
|
class JPH_EXPORT Semaphore
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
Semaphore();
|
Semaphore();
|
||||||
~Semaphore();
|
~Semaphore();
|
||||||
|
|
||||||
/// Release the semaphore, signalling the thread waiting on the barrier that there may be work
|
/// Release the semaphore, signaling the thread waiting on the barrier that there may be work
|
||||||
void Release(uint inNumber = 1);
|
void Release(uint inNumber = 1);
|
||||||
|
|
||||||
/// Acquire the semaphore inNumber times
|
/// Acquire the semaphore inNumber times
|
||||||
void Acquire(uint inNumber = 1);
|
void Acquire(uint inNumber = 1);
|
||||||
|
|
||||||
/// Get the current value of the semaphore
|
/// Get the current value of the semaphore
|
||||||
inline int GetValue() const { return mCount; }
|
inline int GetValue() const { return mCount.load(std::memory_order_relaxed); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_USE_PTHREADS) || defined(JPH_USE_GRAND_CENTRAL_DISPATCH) || defined(JPH_PLATFORM_BLUE)
|
||||||
#ifdef JPH_PLATFORM_WINDOWS
|
#ifdef JPH_PLATFORM_WINDOWS
|
||||||
// On windows we use a semaphore object since it is more efficient than a lock and a condition variable
|
using SemaphoreType = void *;
|
||||||
alignas(JPH_CACHE_LINE_SIZE) atomic<int> mCount { 0 }; ///< We increment mCount for every release, to acquire we decrement the count. If the count is negative we know that we are waiting on the actual semaphore.
|
#elif defined(JPH_USE_PTHREADS)
|
||||||
void * mSemaphore; ///< The semaphore is an expensive construct so we only acquire/release it if we know that we need to wait/have waiting threads
|
using SemaphoreType = sem_t;
|
||||||
|
#elif defined(JPH_USE_GRAND_CENTRAL_DISPATCH)
|
||||||
|
using SemaphoreType = dispatch_semaphore_t;
|
||||||
|
#elif defined(JPH_PLATFORM_BLUE)
|
||||||
|
using SemaphoreType = JPH_PLATFORM_BLUE_SEMAPHORE;
|
||||||
|
#endif
|
||||||
|
alignas(JPH_CACHE_LINE_SIZE) atomic<int> mCount { 0 }; ///< We increment mCount for every release, to acquire we decrement the count. If the count is negative we know that we are waiting on the actual semaphore.
|
||||||
|
SemaphoreType mSemaphore { }; ///< The semaphore is an expensive construct so we only acquire/release it if we know that we need to wait/have waiting threads
|
||||||
#else
|
#else
|
||||||
// Other platforms: Emulate a semaphore using a mutex, condition variable and count
|
// Other platforms: Emulate a semaphore using a mutex, condition variable and count
|
||||||
mutex mLock;
|
std::mutex mLock;
|
||||||
condition_variable mWaitVariable;
|
std::condition_variable mWaitVariable;
|
||||||
int mCount = 0;
|
atomic<int> mCount { 0 };
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/HashCombine.h>
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Simple variable length array backed by a fixed size buffer
|
/// Simple variable length array backed by a fixed size buffer
|
||||||
|
|
@ -24,8 +26,8 @@ public:
|
||||||
explicit StaticArray(std::initializer_list<T> inList)
|
explicit StaticArray(std::initializer_list<T> inList)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inList.size() <= N);
|
JPH_ASSERT(inList.size() <= N);
|
||||||
for (typename std::initializer_list<T>::iterator i = inList.begin(); i != inList.end(); ++i)
|
for (const T &v : inList)
|
||||||
::new (reinterpret_cast<T *>(&mElements[mSize++])) T(*i);
|
new (reinterpret_cast<T *>(&mElements[mSize++])) T(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy constructor
|
/// Copy constructor
|
||||||
|
|
@ -33,7 +35,7 @@ public:
|
||||||
{
|
{
|
||||||
while (mSize < inRHS.mSize)
|
while (mSize < inRHS.mSize)
|
||||||
{
|
{
|
||||||
::new (&mElements[mSize]) T(inRHS[mSize]);
|
new (&mElements[mSize]) T(inRHS[mSize]);
|
||||||
++mSize;
|
++mSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +43,7 @@ public:
|
||||||
/// Destruct all elements
|
/// Destruct all elements
|
||||||
~StaticArray()
|
~StaticArray()
|
||||||
{
|
{
|
||||||
if constexpr (!is_trivially_destructible<T>())
|
if constexpr (!std::is_trivially_destructible<T>())
|
||||||
for (T *e = reinterpret_cast<T *>(mElements), *end = e + mSize; e < end; ++e)
|
for (T *e = reinterpret_cast<T *>(mElements), *end = e + mSize; e < end; ++e)
|
||||||
e->~T();
|
e->~T();
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +51,7 @@ public:
|
||||||
/// Destruct all elements and set length to zero
|
/// Destruct all elements and set length to zero
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
if constexpr (!is_trivially_destructible<T>())
|
if constexpr (!std::is_trivially_destructible<T>())
|
||||||
for (T *e = reinterpret_cast<T *>(mElements), *end = e + mSize; e < end; ++e)
|
for (T *e = reinterpret_cast<T *>(mElements), *end = e + mSize; e < end; ++e)
|
||||||
e->~T();
|
e->~T();
|
||||||
mSize = 0;
|
mSize = 0;
|
||||||
|
|
@ -59,15 +61,15 @@ public:
|
||||||
void push_back(const T &inElement)
|
void push_back(const T &inElement)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(mSize < N);
|
JPH_ASSERT(mSize < N);
|
||||||
::new (&mElements[mSize++]) T(inElement);
|
new (&mElements[mSize++]) T(inElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct element at the back of the array
|
/// Construct element at the back of the array
|
||||||
template <class... A>
|
template <class... A>
|
||||||
void emplace_back(A &&... inElement)
|
void emplace_back(A &&... inElement)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(mSize < N);
|
JPH_ASSERT(mSize < N);
|
||||||
::new (&mElements[mSize++]) T(std::forward<A>(inElement)...);
|
new (&mElements[mSize++]) T(std::forward<A>(inElement)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove element from the back of the array
|
/// Remove element from the back of the array
|
||||||
|
|
@ -99,10 +101,10 @@ public:
|
||||||
void resize(size_type inNewSize)
|
void resize(size_type inNewSize)
|
||||||
{
|
{
|
||||||
JPH_ASSERT(inNewSize <= N);
|
JPH_ASSERT(inNewSize <= N);
|
||||||
if constexpr (!is_trivially_constructible<T>())
|
if constexpr (!std::is_trivially_constructible<T>())
|
||||||
for (T *element = reinterpret_cast<T *>(mElements) + mSize, *element_end = reinterpret_cast<T *>(mElements) + inNewSize; element < element_end; ++element)
|
for (T *element = reinterpret_cast<T *>(mElements) + mSize, *element_end = reinterpret_cast<T *>(mElements) + inNewSize; element < element_end; ++element)
|
||||||
::new (element) T;
|
new (element) T;
|
||||||
if constexpr (!is_trivially_destructible<T>())
|
if constexpr (!std::is_trivially_destructible<T>())
|
||||||
for (T *element = reinterpret_cast<T *>(mElements) + inNewSize, *element_end = reinterpret_cast<T *>(mElements) + mSize; element < element_end; ++element)
|
for (T *element = reinterpret_cast<T *>(mElements) + inNewSize, *element_end = reinterpret_cast<T *>(mElements) + mSize; element < element_end; ++element)
|
||||||
element->~T();
|
element->~T();
|
||||||
mSize = inNewSize;
|
mSize = inNewSize;
|
||||||
|
|
@ -156,6 +158,19 @@ public:
|
||||||
return reinterpret_cast<const T &>(mElements[inIdx]);
|
return reinterpret_cast<const T &>(mElements[inIdx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access element
|
||||||
|
T & at(size_type inIdx)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return reinterpret_cast<T &>(mElements[inIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const T & at(size_type inIdx) const
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inIdx < mSize);
|
||||||
|
return reinterpret_cast<const T &>(mElements[inIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
/// First element in the array
|
/// First element in the array
|
||||||
const T & front() const
|
const T & front() const
|
||||||
{
|
{
|
||||||
|
|
@ -211,13 +226,13 @@ public:
|
||||||
{
|
{
|
||||||
size_type rhs_size = inRHS.size();
|
size_type rhs_size = inRHS.size();
|
||||||
|
|
||||||
if ((void *)this != (void *)&inRHS)
|
if (static_cast<const void *>(this) != static_cast<const void *>(&inRHS))
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
while (mSize < rhs_size)
|
while (mSize < rhs_size)
|
||||||
{
|
{
|
||||||
::new (&mElements[mSize]) T(inRHS[mSize]);
|
new (&mElements[mSize]) T(inRHS[mSize]);
|
||||||
++mSize;
|
++mSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -232,20 +247,20 @@ public:
|
||||||
size_type rhs_size = inRHS.size();
|
size_type rhs_size = inRHS.size();
|
||||||
JPH_ASSERT(rhs_size <= N);
|
JPH_ASSERT(rhs_size <= N);
|
||||||
|
|
||||||
if ((void *)this != (void *)&inRHS)
|
if (static_cast<const void *>(this) != static_cast<const void *>(&inRHS))
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
while (mSize < rhs_size)
|
while (mSize < rhs_size)
|
||||||
{
|
{
|
||||||
::new (&mElements[mSize]) T(inRHS[mSize]);
|
new (&mElements[mSize]) T(inRHS[mSize]);
|
||||||
++mSize;
|
++mSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comparing arrays
|
/// Comparing arrays
|
||||||
bool operator == (const StaticArray<T, N> &inRHS) const
|
bool operator == (const StaticArray<T, N> &inRHS) const
|
||||||
{
|
{
|
||||||
|
|
@ -266,7 +281,20 @@ public:
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get hash for this array
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
// Hash length first
|
||||||
|
uint64 ret = Hash<uint32> { } (uint32(size()));
|
||||||
|
|
||||||
|
// Then hash elements
|
||||||
|
for (const T *element = reinterpret_cast<const T *>(mElements), *element_end = reinterpret_cast<const T *>(mElements) + mSize; element < element_end; ++element)
|
||||||
|
HashCombine(ret, *element);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct alignas(T) Storage
|
struct alignas(T) Storage
|
||||||
{
|
{
|
||||||
|
|
@ -293,16 +321,7 @@ namespace std
|
||||||
{
|
{
|
||||||
size_t operator () (const JPH::StaticArray<T, N> &inRHS) const
|
size_t operator () (const JPH::StaticArray<T, N> &inRHS) const
|
||||||
{
|
{
|
||||||
std::size_t ret = 0;
|
return std::size_t(inRHS.GetHash());
|
||||||
|
|
||||||
// Hash length first
|
|
||||||
JPH::HashCombine(ret, inRHS.size());
|
|
||||||
|
|
||||||
// Then hash elements
|
|
||||||
for (const T &t : inRHS)
|
|
||||||
JPH::HashCombine(ret, t);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Simple binary input stream
|
/// Simple binary input stream
|
||||||
class StreamIn
|
class JPH_EXPORT StreamIn : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Virtual destructor
|
/// Virtual destructor
|
||||||
|
|
@ -16,30 +18,40 @@ public:
|
||||||
/// Read a string of bytes from the binary stream
|
/// Read a string of bytes from the binary stream
|
||||||
virtual void ReadBytes(void *outData, size_t inNumBytes) = 0;
|
virtual void ReadBytes(void *outData, size_t inNumBytes) = 0;
|
||||||
|
|
||||||
/// Returns true when an attempt has been made to read past the end of the file
|
/// Returns true when an attempt has been made to read past the end of the file.
|
||||||
|
/// Note that this follows the convention of std::basic_ios::eof which only returns true when an attempt is made to read past the end, not when the read pointer is at the end.
|
||||||
virtual bool IsEOF() const = 0;
|
virtual bool IsEOF() const = 0;
|
||||||
|
|
||||||
/// Returns true if there was an IO failure
|
/// Returns true if there was an IO failure
|
||||||
virtual bool IsFailed() const = 0;
|
virtual bool IsFailed() const = 0;
|
||||||
|
|
||||||
/// Read a primitive (e.g. float, int, etc.) from the binary stream
|
/// Read a primitive (e.g. float, int, etc.) from the binary stream
|
||||||
template <class T>
|
template <class T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
|
||||||
void Read(T &outT)
|
void Read(T &outT)
|
||||||
{
|
{
|
||||||
ReadBytes(&outT, sizeof(outT));
|
ReadBytes(&outT, sizeof(outT));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a vector of primitives from the binary stream
|
/// Read a vector of primitives from the binary stream
|
||||||
template <class T, class A>
|
template <class T, class A, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
|
||||||
void Read(std::vector<T, A> &outT)
|
void Read(Array<T, A> &outT)
|
||||||
{
|
{
|
||||||
typename Array<T>::size_type len = outT.size(); // Initialize to previous array size, this is used for validation in the StateRecorder class
|
uint32 len = uint32(outT.size()); // Initialize to previous array size, this is used for validation in the StateRecorder class
|
||||||
Read(len);
|
Read(len);
|
||||||
if (!IsEOF() && !IsFailed())
|
if (!IsEOF() && !IsFailed())
|
||||||
{
|
{
|
||||||
outT.resize(len);
|
outT.resize(len);
|
||||||
for (typename Array<T>::size_type i = 0; i < len; ++i)
|
if constexpr (std::is_same_v<T, Vec3> || std::is_same_v<T, DVec3> || std::is_same_v<T, DMat44>)
|
||||||
Read(outT[i]);
|
{
|
||||||
|
// These types have unused components that we don't want to read
|
||||||
|
for (typename Array<T, A>::size_type i = 0; i < len; ++i)
|
||||||
|
Read(outT[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Read all elements at once
|
||||||
|
ReadBytes(outT.data(), len * sizeof(T));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
outT.clear();
|
outT.clear();
|
||||||
|
|
@ -49,7 +61,7 @@ public:
|
||||||
template <class Type, class Traits, class Allocator>
|
template <class Type, class Traits, class Allocator>
|
||||||
void Read(std::basic_string<Type, Traits, Allocator> &outString)
|
void Read(std::basic_string<Type, Traits, Allocator> &outString)
|
||||||
{
|
{
|
||||||
typename std::basic_string<Type, Traits, Allocator>::size_type len = 0;
|
uint32 len = 0;
|
||||||
Read(len);
|
Read(len);
|
||||||
if (!IsEOF() && !IsFailed())
|
if (!IsEOF() && !IsFailed())
|
||||||
{
|
{
|
||||||
|
|
@ -60,6 +72,22 @@ public:
|
||||||
outString.clear();
|
outString.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a vector of primitives from the binary stream using a custom function to read the elements
|
||||||
|
template <class T, class A, typename F>
|
||||||
|
void Read(Array<T, A> &outT, const F &inReadElement)
|
||||||
|
{
|
||||||
|
uint32 len = uint32(outT.size()); // Initialize to previous array size, this is used for validation in the StateRecorder class
|
||||||
|
Read(len);
|
||||||
|
if (!IsEOF() && !IsFailed())
|
||||||
|
{
|
||||||
|
outT.resize(len);
|
||||||
|
for (typename Array<T, A>::size_type i = 0; i < len; ++i)
|
||||||
|
inReadElement(*this, outT[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
outT.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// Read a Vec3 (don't read W)
|
/// Read a Vec3 (don't read W)
|
||||||
void Read(Vec3 &outVec)
|
void Read(Vec3 &outVec)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Simple binary output stream
|
/// Simple binary output stream
|
||||||
class StreamOut
|
class JPH_EXPORT StreamOut : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Virtual destructor
|
/// Virtual destructor
|
||||||
|
|
@ -20,33 +22,55 @@ public:
|
||||||
virtual bool IsFailed() const = 0;
|
virtual bool IsFailed() const = 0;
|
||||||
|
|
||||||
/// Write a primitive (e.g. float, int, etc.) to the binary stream
|
/// Write a primitive (e.g. float, int, etc.) to the binary stream
|
||||||
template <class T>
|
template <class T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
|
||||||
void Write(const T &inT)
|
void Write(const T &inT)
|
||||||
{
|
{
|
||||||
WriteBytes(&inT, sizeof(inT));
|
WriteBytes(&inT, sizeof(inT));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a vector of primitives from the binary stream
|
/// Write a vector of primitives to the binary stream
|
||||||
template <class T, class A>
|
template <class T, class A, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
|
||||||
void Write(const std::vector<T, A> &inT)
|
void Write(const Array<T, A> &inT)
|
||||||
{
|
{
|
||||||
typename Array<T>::size_type len = inT.size();
|
uint32 len = uint32(inT.size());
|
||||||
Write(len);
|
Write(len);
|
||||||
if (!IsFailed())
|
if (!IsFailed())
|
||||||
for (typename Array<T>::size_type i = 0; i < len; ++i)
|
{
|
||||||
Write(inT[i]);
|
if constexpr (std::is_same_v<T, Vec3> || std::is_same_v<T, DVec3> || std::is_same_v<T, DMat44>)
|
||||||
|
{
|
||||||
|
// These types have unused components that we don't want to write
|
||||||
|
for (typename Array<T, A>::size_type i = 0; i < len; ++i)
|
||||||
|
Write(inT[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Write all elements at once
|
||||||
|
WriteBytes(inT.data(), len * sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a string to the binary stream (writes the number of characters and then the characters)
|
/// Write a string to the binary stream (writes the number of characters and then the characters)
|
||||||
template <class Type, class Traits, class Allocator>
|
template <class Type, class Traits, class Allocator>
|
||||||
void Write(const std::basic_string<Type, Traits, Allocator> &inString)
|
void Write(const std::basic_string<Type, Traits, Allocator> &inString)
|
||||||
{
|
{
|
||||||
typename std::basic_string<Type, Traits, Allocator>::size_type len = inString.size();
|
uint32 len = uint32(inString.size());
|
||||||
Write(len);
|
Write(len);
|
||||||
if (!IsFailed())
|
if (!IsFailed())
|
||||||
WriteBytes(inString.data(), len * sizeof(Type));
|
WriteBytes(inString.data(), len * sizeof(Type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a vector of primitives to the binary stream using a custom write function
|
||||||
|
template <class T, class A, typename F>
|
||||||
|
void Write(const Array<T, A> &inT, const F &inWriteElement)
|
||||||
|
{
|
||||||
|
uint32 len = uint32(inT.size());
|
||||||
|
Write(len);
|
||||||
|
if (!IsFailed())
|
||||||
|
for (typename Array<T, A>::size_type i = 0; i < len; ++i)
|
||||||
|
inWriteElement(inT[i], *this);
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a Vec3 (don't write W)
|
/// Write a Vec3 (don't write W)
|
||||||
void Write(const Vec3 &inVec)
|
void Write(const Vec3 &inVec)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Jolt/Core/Result.h>
|
||||||
|
#include <Jolt/Core/StreamIn.h>
|
||||||
|
#include <Jolt/Core/StreamOut.h>
|
||||||
|
#include <Jolt/Core/UnorderedMap.h>
|
||||||
|
#include <Jolt/Core/Factory.h>
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
namespace StreamUtils {
|
||||||
|
|
||||||
|
template <class Type>
|
||||||
|
using ObjectToIDMap = UnorderedMap<const Type *, uint32>;
|
||||||
|
|
||||||
|
template <class Type>
|
||||||
|
using IDToObjectMap = Array<Ref<Type>>;
|
||||||
|
|
||||||
|
// Restore a single object by reading the hash of the type, constructing it and then calling the restore function
|
||||||
|
template <class Type>
|
||||||
|
Result<Ref<Type>> RestoreObject(StreamIn &inStream, void (Type::*inRestoreBinaryStateFunction)(StreamIn &))
|
||||||
|
{
|
||||||
|
Result<Ref<Type>> result;
|
||||||
|
|
||||||
|
// Read the hash of the type
|
||||||
|
uint32 hash;
|
||||||
|
inStream.Read(hash);
|
||||||
|
if (inStream.IsEOF() || inStream.IsFailed())
|
||||||
|
{
|
||||||
|
result.SetError("Failed to read type hash");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the RTTI for the type
|
||||||
|
const RTTI *rtti = Factory::sInstance->Find(hash);
|
||||||
|
if (rtti == nullptr)
|
||||||
|
{
|
||||||
|
result.SetError("Failed to create instance of type");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct and read the data of the type
|
||||||
|
Ref<Type> object = reinterpret_cast<Type *>(rtti->CreateObject());
|
||||||
|
(object->*inRestoreBinaryStateFunction)(inStream);
|
||||||
|
if (inStream.IsEOF() || inStream.IsFailed())
|
||||||
|
{
|
||||||
|
result.SetError("Failed to restore object");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Set(object);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save an object reference to a stream. Uses a map to map objects to IDs which is also used to prevent writing duplicates.
|
||||||
|
template <class Type>
|
||||||
|
void SaveObjectReference(StreamOut &inStream, const Type *inObject, ObjectToIDMap<Type> *ioObjectToIDMap)
|
||||||
|
{
|
||||||
|
if (ioObjectToIDMap == nullptr || inObject == nullptr)
|
||||||
|
{
|
||||||
|
// Write null ID
|
||||||
|
inStream.Write(~uint32(0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typename ObjectToIDMap<Type>::const_iterator id = ioObjectToIDMap->find(inObject);
|
||||||
|
if (id != ioObjectToIDMap->end())
|
||||||
|
{
|
||||||
|
// Existing object, write ID
|
||||||
|
inStream.Write(id->second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// New object, write the ID
|
||||||
|
uint32 new_id = uint32(ioObjectToIDMap->size());
|
||||||
|
(*ioObjectToIDMap)[inObject] = new_id;
|
||||||
|
inStream.Write(new_id);
|
||||||
|
|
||||||
|
// Write the object
|
||||||
|
inObject->SaveBinaryState(inStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore an object reference from stream.
|
||||||
|
template <class Type>
|
||||||
|
Result<Ref<Type>> RestoreObjectReference(StreamIn &inStream, IDToObjectMap<Type> &ioIDToObjectMap)
|
||||||
|
{
|
||||||
|
Result<Ref<Type>> result;
|
||||||
|
|
||||||
|
// Read id
|
||||||
|
uint32 id = ~uint32(0);
|
||||||
|
inStream.Read(id);
|
||||||
|
|
||||||
|
// Check null
|
||||||
|
if (id == ~uint32(0))
|
||||||
|
{
|
||||||
|
result.Set(nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it already exists
|
||||||
|
if (id >= ioIDToObjectMap.size())
|
||||||
|
{
|
||||||
|
// New object, restore it
|
||||||
|
result = Type::sRestoreFromBinaryState(inStream);
|
||||||
|
if (result.HasError())
|
||||||
|
return result;
|
||||||
|
JPH_ASSERT(id == ioIDToObjectMap.size());
|
||||||
|
ioIDToObjectMap.push_back(result.Get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Existing object filter
|
||||||
|
result.Set(ioIDToObjectMap[id].GetPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save an array of objects to a stream.
|
||||||
|
template <class ArrayType, class ValueType>
|
||||||
|
void SaveObjectArray(StreamOut &inStream, const ArrayType &inArray, ObjectToIDMap<ValueType> *ioObjectToIDMap)
|
||||||
|
{
|
||||||
|
uint32 len = uint32(inArray.size());
|
||||||
|
inStream.Write(len);
|
||||||
|
for (const ValueType *value: inArray)
|
||||||
|
SaveObjectReference(inStream, value, ioObjectToIDMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore an array of objects from a stream.
|
||||||
|
template <class ArrayType, class ValueType>
|
||||||
|
Result<ArrayType> RestoreObjectArray(StreamIn &inStream, IDToObjectMap<ValueType> &ioIDToObjectMap)
|
||||||
|
{
|
||||||
|
Result<ArrayType> result;
|
||||||
|
|
||||||
|
uint32 len;
|
||||||
|
inStream.Read(len);
|
||||||
|
if (inStream.IsEOF() || inStream.IsFailed())
|
||||||
|
{
|
||||||
|
result.SetError("Failed to read stream");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayType values;
|
||||||
|
values.reserve(len);
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
{
|
||||||
|
Result value = RestoreObjectReference(inStream, ioIDToObjectMap);
|
||||||
|
if (value.HasError())
|
||||||
|
{
|
||||||
|
result.SetError(value.GetError());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
values.push_back(std::move(value.Get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Set(values);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // StreamUtils
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/// A strided pointer behaves exactly like a normal pointer except that the
|
||||||
|
/// elements that the pointer points to can be part of a larger structure.
|
||||||
|
/// The stride gives the number of bytes from one element to the next.
|
||||||
|
template <class T>
|
||||||
|
class JPH_EXPORT StridedPtr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
/// Constructors
|
||||||
|
StridedPtr() = default;
|
||||||
|
StridedPtr(const StridedPtr &inRHS) = default;
|
||||||
|
StridedPtr(T *inPtr, int inStride = sizeof(T)) : mPtr(const_cast<uint8 *>(reinterpret_cast<const uint8 *>(inPtr))), mStride(inStride) { }
|
||||||
|
|
||||||
|
/// Assignment
|
||||||
|
inline StridedPtr & operator = (const StridedPtr &inRHS) = default;
|
||||||
|
|
||||||
|
/// Incrementing / decrementing
|
||||||
|
inline StridedPtr & operator ++ () { mPtr += mStride; return *this; }
|
||||||
|
inline StridedPtr & operator -- () { mPtr -= mStride; return *this; }
|
||||||
|
inline StridedPtr operator ++ (int) { StridedPtr old_ptr(*this); mPtr += mStride; return old_ptr; }
|
||||||
|
inline StridedPtr operator -- (int) { StridedPtr old_ptr(*this); mPtr -= mStride; return old_ptr; }
|
||||||
|
inline StridedPtr operator + (int inOffset) const { StridedPtr new_ptr(*this); new_ptr.mPtr += inOffset * mStride; return new_ptr; }
|
||||||
|
inline StridedPtr operator - (int inOffset) const { StridedPtr new_ptr(*this); new_ptr.mPtr -= inOffset * mStride; return new_ptr; }
|
||||||
|
inline void operator += (int inOffset) { mPtr += inOffset * mStride; }
|
||||||
|
inline void operator -= (int inOffset) { mPtr -= inOffset * mStride; }
|
||||||
|
|
||||||
|
/// Distance between two pointers in elements
|
||||||
|
inline int operator - (const StridedPtr &inRHS) const { JPH_ASSERT(inRHS.mStride == mStride); return (mPtr - inRHS.mPtr) / mStride; }
|
||||||
|
|
||||||
|
/// Comparison operators
|
||||||
|
inline bool operator == (const StridedPtr &inRHS) const { return mPtr == inRHS.mPtr; }
|
||||||
|
inline bool operator != (const StridedPtr &inRHS) const { return mPtr != inRHS.mPtr; }
|
||||||
|
inline bool operator <= (const StridedPtr &inRHS) const { return mPtr <= inRHS.mPtr; }
|
||||||
|
inline bool operator >= (const StridedPtr &inRHS) const { return mPtr >= inRHS.mPtr; }
|
||||||
|
inline bool operator < (const StridedPtr &inRHS) const { return mPtr < inRHS.mPtr; }
|
||||||
|
inline bool operator > (const StridedPtr &inRHS) const { return mPtr > inRHS.mPtr; }
|
||||||
|
|
||||||
|
/// Access value
|
||||||
|
inline T & operator * () const { return *reinterpret_cast<T *>(mPtr); }
|
||||||
|
inline T * operator -> () const { return reinterpret_cast<T *>(mPtr); }
|
||||||
|
inline T & operator [] (int inOffset) const { uint8 *ptr = mPtr + inOffset * mStride; return *reinterpret_cast<T *>(ptr); }
|
||||||
|
|
||||||
|
/// Explicit conversion
|
||||||
|
inline T * GetPtr() const { return reinterpret_cast<T *>(mPtr); }
|
||||||
|
|
||||||
|
/// Get stride in bytes
|
||||||
|
inline int GetStride() const { return mStride; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8 * mPtr = nullptr; /// Pointer to element
|
||||||
|
int mStride = 0; /// Stride (number of bytes) between elements
|
||||||
|
};
|
||||||
|
|
||||||
|
JPH_NAMESPACE_END
|
||||||
|
|
@ -31,7 +31,7 @@ void StringReplace(String &ioString, const string_view &inSearch, const string_v
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
index = ioString.find(inSearch, index);
|
index = ioString.find(inSearch, index);
|
||||||
if (index == String::npos)
|
if (index == String::npos)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
ioString.replace(index, inSearch.size(), inReplace);
|
ioString.replace(index, inSearch.size(), inReplace);
|
||||||
|
|
@ -46,7 +46,7 @@ void StringToVector(const string_view &inString, Array<String> &outVector, const
|
||||||
|
|
||||||
// Ensure vector empty
|
// Ensure vector empty
|
||||||
if (inClearVector)
|
if (inClearVector)
|
||||||
outVector.clear();
|
outVector.clear();
|
||||||
|
|
||||||
// No string? no elements
|
// No string? no elements
|
||||||
if (inString.empty())
|
if (inString.empty())
|
||||||
|
|
|
||||||
|
|
@ -8,44 +8,31 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Create a formatted text string for debugging purposes.
|
/// Create a formatted text string for debugging purposes.
|
||||||
/// Note that this function has an internal buffer of 1024 characters, so long strings will be trimmed.
|
/// Note that this function has an internal buffer of 1024 characters, so long strings will be trimmed.
|
||||||
String StringFormat(const char *inFMT, ...);
|
JPH_EXPORT String StringFormat(const char *inFMT, ...);
|
||||||
|
|
||||||
/// Convert type to string
|
/// Convert type to string
|
||||||
template<typename T>
|
template<typename T>
|
||||||
String ConvertToString(const T &inValue)
|
String ConvertToString(const T &inValue)
|
||||||
{
|
{
|
||||||
using OStringStream = std::basic_ostringstream<char, std::char_traits<char>, STLAllocator<char>>;
|
using OStringStream = std::basic_ostringstream<char, std::char_traits<char>, STLAllocator<char>>;
|
||||||
OStringStream oss;
|
OStringStream oss;
|
||||||
oss << inValue;
|
oss << inValue;
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the FNV-1a hash of inString.
|
|
||||||
/// @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
|
||||||
constexpr uint64 HashString(const char *inString)
|
|
||||||
{
|
|
||||||
uint64 hash = 14695981039346656037UL;
|
|
||||||
for (const char *c = inString; *c != 0; ++c)
|
|
||||||
{
|
|
||||||
hash ^= *c;
|
|
||||||
hash = hash * 1099511628211UL;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace substring with other string
|
/// Replace substring with other string
|
||||||
void StringReplace(String &ioString, const string_view &inSearch, const string_view &inReplace);
|
JPH_EXPORT void StringReplace(String &ioString, const string_view &inSearch, const string_view &inReplace);
|
||||||
|
|
||||||
/// Convert a delimited string to an array of strings
|
/// Convert a delimited string to an array of strings
|
||||||
void StringToVector(const string_view &inString, Array<String> &outVector, const string_view &inDelimiter = ",", bool inClearVector = true);
|
JPH_EXPORT void StringToVector(const string_view &inString, Array<String> &outVector, const string_view &inDelimiter = ",", bool inClearVector = true);
|
||||||
|
|
||||||
/// Convert an array strings to a delimited string
|
/// Convert an array strings to a delimited string
|
||||||
void VectorToString(const Array<String> &inVector, String &outString, const string_view &inDelimiter = ",");
|
JPH_EXPORT void VectorToString(const Array<String> &inVector, String &outString, const string_view &inDelimiter = ",");
|
||||||
|
|
||||||
/// Convert a string to lower case
|
/// Convert a string to lower case
|
||||||
String ToLower(const string_view &inString);
|
JPH_EXPORT String ToLower(const string_view &inString);
|
||||||
|
|
||||||
/// Converts the lower 4 bits of inNibble to a string that represents the number in binary format
|
/// Converts the lower 4 bits of inNibble to a string that represents the number in binary format
|
||||||
const char *NibbleToBinary(uint32 inNibble);
|
JPH_EXPORT const char *NibbleToBinary(uint32 inNibble);
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Allocator for temporary allocations.
|
/// Allocator for temporary allocations.
|
||||||
/// This allocator works as a stack: The blocks must always be freed in the reverse order as they are allocated.
|
/// This allocator works as a stack: The blocks must always be freed in the reverse order as they are allocated.
|
||||||
/// Note that allocations and frees can take place from different threads, but the order is guaranteed though
|
/// Note that allocations and frees can take place from different threads, but the order is guaranteed though
|
||||||
/// job dependencies, so it is not needed to use any form of locking.
|
/// job dependencies, so it is not needed to use any form of locking.
|
||||||
class TempAllocator : public NonCopyable
|
class JPH_EXPORT TempAllocator : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -28,13 +28,13 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Default implementation of the temp allocator that allocates a large block through malloc upfront
|
/// Default implementation of the temp allocator that allocates a large block through malloc upfront
|
||||||
class TempAllocatorImpl final : public TempAllocator
|
class JPH_EXPORT TempAllocatorImpl final : public TempAllocator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
/// Constructs the allocator with a maximum allocatable size of inSize
|
/// Constructs the allocator with a maximum allocatable size of inSize
|
||||||
explicit TempAllocatorImpl(uint inSize) :
|
explicit TempAllocatorImpl(size_t inSize) :
|
||||||
mBase(static_cast<uint8 *>(AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT))),
|
mBase(static_cast<uint8 *>(AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT))),
|
||||||
mSize(inSize)
|
mSize(inSize)
|
||||||
{
|
{
|
||||||
|
|
@ -56,9 +56,12 @@ public:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uint new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
|
size_t new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
|
||||||
if (new_top > mSize)
|
if (new_top > mSize)
|
||||||
JPH_CRASH; // Out of memory
|
{
|
||||||
|
Trace("TempAllocator: Out of memory trying to allocate %u bytes", inSize);
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
void *address = mBase + mTop;
|
void *address = mBase + mTop;
|
||||||
mTop = new_top;
|
mTop = new_top;
|
||||||
return address;
|
return address;
|
||||||
|
|
@ -76,25 +79,52 @@ public:
|
||||||
{
|
{
|
||||||
mTop -= AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
|
mTop -= AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
|
||||||
if (mBase + mTop != inAddress)
|
if (mBase + mTop != inAddress)
|
||||||
JPH_CRASH; // Freeing in the wrong order
|
{
|
||||||
|
Trace("TempAllocator: Freeing in the wrong order");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if no allocations have been made
|
/// Check if no allocations have been made
|
||||||
bool IsEmpty() const
|
bool IsEmpty() const
|
||||||
{
|
{
|
||||||
return mTop == 0;
|
return mTop == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the total size of the fixed buffer
|
||||||
|
size_t GetSize() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current usage in bytes of the buffer
|
||||||
|
size_t GetUsage() const
|
||||||
|
{
|
||||||
|
return mTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if an allocation of inSize can be made in this fixed buffer allocator
|
||||||
|
bool CanAllocate(uint inSize) const
|
||||||
|
{
|
||||||
|
return mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT) <= mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if memory block at inAddress is owned by this allocator
|
||||||
|
bool OwnsMemory(const void *inAddress) const
|
||||||
|
{
|
||||||
|
return inAddress >= mBase && inAddress < mBase + mSize;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8 * mBase; ///< Base address of the memory block
|
uint8 * mBase; ///< Base address of the memory block
|
||||||
uint mSize; ///< Size of the memory block
|
size_t mSize; ///< Size of the memory block
|
||||||
uint mTop = 0; ///< Current top of the stack
|
size_t mTop = 0; ///< End of currently allocated area
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Implementation of the TempAllocator that just falls back to malloc/free
|
/// Implementation of the TempAllocator that just falls back to malloc/free
|
||||||
/// Note: This can be quite slow when running in the debugger as large memory blocks need to be initialized with 0xcd
|
/// Note: This can be quite slow when running in the debugger as large memory blocks need to be initialized with 0xcd
|
||||||
class TempAllocatorMalloc final : public TempAllocator
|
class JPH_EXPORT TempAllocatorMalloc final : public TempAllocator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -102,14 +132,57 @@ public:
|
||||||
// See: TempAllocator
|
// See: TempAllocator
|
||||||
virtual void * Allocate(uint inSize) override
|
virtual void * Allocate(uint inSize) override
|
||||||
{
|
{
|
||||||
return AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT);
|
return inSize > 0? AlignedAllocate(inSize, JPH_RVECTOR_ALIGNMENT) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See: TempAllocator
|
||||||
|
virtual void Free(void *inAddress, [[maybe_unused]] uint inSize) override
|
||||||
|
{
|
||||||
|
if (inAddress != nullptr)
|
||||||
|
AlignedFree(inAddress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Implementation of the TempAllocator that tries to allocate from a large preallocated block, but falls back to malloc when it is exhausted
|
||||||
|
class JPH_EXPORT TempAllocatorImplWithMallocFallback final : public TempAllocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
||||||
|
/// Constructs the allocator with an initial fixed block if inSize
|
||||||
|
explicit TempAllocatorImplWithMallocFallback(uint inSize) :
|
||||||
|
mAllocator(inSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// See: TempAllocator
|
||||||
|
virtual void * Allocate(uint inSize) override
|
||||||
|
{
|
||||||
|
if (mAllocator.CanAllocate(inSize))
|
||||||
|
return mAllocator.Allocate(inSize);
|
||||||
|
else
|
||||||
|
return mFallbackAllocator.Allocate(inSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See: TempAllocator
|
// See: TempAllocator
|
||||||
virtual void Free(void *inAddress, uint inSize) override
|
virtual void Free(void *inAddress, uint inSize) override
|
||||||
{
|
{
|
||||||
AlignedFree(inAddress);
|
if (inAddress == nullptr)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(inSize == 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mAllocator.OwnsMemory(inAddress))
|
||||||
|
mAllocator.Free(inAddress, inSize);
|
||||||
|
else
|
||||||
|
mFallbackAllocator.Free(inAddress, inSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TempAllocatorImpl mAllocator;
|
||||||
|
TempAllocatorMalloc mFallbackAllocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,15 @@
|
||||||
#if defined(JPH_PLATFORM_WINDOWS)
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
JPH_SUPPRESS_WARNING_PUSH
|
||||||
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
JPH_MSVC_SUPPRESS_WARNING(5039) // winbase.h(13179): warning C5039: 'TpSetCallbackCleanupGroup': pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception.
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
#ifndef JPH_COMPILER_MINGW
|
#ifndef JPH_COMPILER_MINGW
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#else
|
#else
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
JPH_SUPPRESS_WARNING_POP
|
JPH_SUPPRESS_WARNING_POP
|
||||||
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID)
|
|
||||||
#include <fstream>
|
|
||||||
#elif defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/sysctl.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
@ -36,85 +33,4 @@ uint64 GetProcessorTickCount()
|
||||||
|
|
||||||
#endif // JPH_PLATFORM_WINDOWS_UWP || (JPH_PLATFORM_WINDOWS && JPH_CPU_ARM)
|
#endif // JPH_PLATFORM_WINDOWS_UWP || (JPH_PLATFORM_WINDOWS && JPH_CPU_ARM)
|
||||||
|
|
||||||
static const uint64 sProcessorTicksPerSecond = []() {
|
|
||||||
#if defined(JPH_PLATFORM_WINDOWS_UWP) || (defined(JPH_PLATFORM_WINDOWS) && defined(JPH_CPU_ARM))
|
|
||||||
LARGE_INTEGER frequency { };
|
|
||||||
QueryPerformanceFrequency(&frequency);
|
|
||||||
return uint64(frequency.QuadPart);
|
|
||||||
#elif defined(JPH_PLATFORM_WINDOWS)
|
|
||||||
// Open the key where the processor speed is stored
|
|
||||||
HKEY hkey;
|
|
||||||
RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, 1, &hkey);
|
|
||||||
|
|
||||||
// Query the speed in MHz
|
|
||||||
uint mhz = 0;
|
|
||||||
DWORD mhz_size = sizeof(uint);
|
|
||||||
RegQueryValueExA(hkey, "~MHz", nullptr, nullptr, (LPBYTE)&mhz, &mhz_size);
|
|
||||||
|
|
||||||
// Close key
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
|
|
||||||
// Initialize amount of cycles per second
|
|
||||||
return uint64(mhz) * 1000000UL;
|
|
||||||
#elif defined(JPH_PLATFORM_BLUE)
|
|
||||||
return JPH_PLATFORM_BLUE_GET_TICK_FREQUENCY();
|
|
||||||
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID)
|
|
||||||
// Open /proc/cpuinfo
|
|
||||||
std::ifstream ifs("/proc/cpuinfo");
|
|
||||||
if (ifs.is_open())
|
|
||||||
{
|
|
||||||
// Read all lines
|
|
||||||
while (ifs.good())
|
|
||||||
{
|
|
||||||
// Get next line
|
|
||||||
string line;
|
|
||||||
getline(ifs, line);
|
|
||||||
|
|
||||||
#if defined(JPH_CPU_X86)
|
|
||||||
const char *cpu_str = "cpu MHz";
|
|
||||||
#elif defined(JPH_CPU_ARM)
|
|
||||||
const char *cpu_str = "BogoMIPS";
|
|
||||||
#else
|
|
||||||
#error Unsupported CPU architecture
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check if line starts with correct string
|
|
||||||
const size_t num_chars = strlen(cpu_str);
|
|
||||||
if (strncmp(line.c_str(), cpu_str, num_chars) == 0)
|
|
||||||
{
|
|
||||||
// Find ':'
|
|
||||||
string::size_type pos = line.find(':', num_chars);
|
|
||||||
if (pos != String::npos)
|
|
||||||
{
|
|
||||||
// Convert to number
|
|
||||||
string freq = line.substr(pos + 1);
|
|
||||||
return uint64(stod(freq) * 1000000.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JPH_ASSERT(false);
|
|
||||||
return uint64(0);
|
|
||||||
#elif defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
|
|
||||||
// Use sysctl to get the processor frequency
|
|
||||||
int mib[2];
|
|
||||||
mib[0] = CTL_HW;
|
|
||||||
mib[1] = HW_CPU_FREQ;
|
|
||||||
uint64 freq = 1;
|
|
||||||
size_t len = sizeof(freq);
|
|
||||||
sysctl(mib, 2, &freq, &len, nullptr, 0);
|
|
||||||
return freq;
|
|
||||||
#elif defined(JPH_PLATFORM_WASM)
|
|
||||||
return 1; // Not supported
|
|
||||||
#else
|
|
||||||
#error Undefined
|
|
||||||
#endif
|
|
||||||
}();
|
|
||||||
|
|
||||||
uint64 GetProcessorTicksPerSecond()
|
|
||||||
{
|
|
||||||
return sProcessorTicksPerSecond;
|
|
||||||
}
|
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
// Include for __rdtsc
|
// Include for __rdtsc
|
||||||
#if defined(JPH_PLATFORM_WINDOWS)
|
#if defined(JPH_PLATFORM_WINDOWS)
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#elif defined(JPH_CPU_X86) && defined(JPH_COMPILER_GCC)
|
#elif defined(JPH_CPU_X86) && defined(JPH_COMPILER_GCC)
|
||||||
#include <x86intrin.h>
|
#include <x86intrin.h>
|
||||||
|
#elif defined(JPH_CPU_E2K)
|
||||||
|
#include <x86intrin.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
@ -27,13 +29,13 @@ JPH_INLINE uint64 GetProcessorTickCount()
|
||||||
return JPH_PLATFORM_BLUE_GET_TICKS();
|
return JPH_PLATFORM_BLUE_GET_TICKS();
|
||||||
#elif defined(JPH_CPU_X86)
|
#elif defined(JPH_CPU_X86)
|
||||||
return __rdtsc();
|
return __rdtsc();
|
||||||
|
#elif defined(JPH_CPU_E2K)
|
||||||
|
return __rdtsc();
|
||||||
#elif defined(JPH_CPU_ARM) && defined(JPH_USE_NEON)
|
#elif defined(JPH_CPU_ARM) && defined(JPH_USE_NEON)
|
||||||
uint64 val;
|
uint64 val;
|
||||||
asm volatile("mrs %0, cntvct_el0" : "=r" (val));
|
asm volatile("mrs %0, cntvct_el0" : "=r" (val));
|
||||||
return val;
|
return val;
|
||||||
#elif defined(JPH_CPU_ARM)
|
#elif defined(JPH_CPU_ARM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_WASM) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
|
||||||
return 0; // Not supported
|
|
||||||
#elif defined(JPH_CPU_WASM)
|
|
||||||
return 0; // Not supported
|
return 0; // Not supported
|
||||||
#else
|
#else
|
||||||
#error Undefined
|
#error Undefined
|
||||||
|
|
@ -42,7 +44,4 @@ JPH_INLINE uint64 GetProcessorTickCount()
|
||||||
|
|
||||||
#endif // JPH_PLATFORM_WINDOWS_UWP || (JPH_PLATFORM_WINDOWS && JPH_CPU_ARM)
|
#endif // JPH_PLATFORM_WINDOWS_UWP || (JPH_PLATFORM_WINDOWS && JPH_CPU_ARM)
|
||||||
|
|
||||||
/// Get the amount of ticks per second, note that this number will never be fully accurate as the amound of ticks per second may vary with CPU load, so this number is only to be used to give an indication of time for profiling purposes
|
|
||||||
uint64 GetProcessorTicksPerSecond();
|
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,80 @@
|
||||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
#include <Jolt/Core/HashTable.h>
|
||||||
#include <unordered_map>
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
template <class Key, class T, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>> using UnorderedMap = std::unordered_map<Key, T, Hash, KeyEqual, STLAllocator<pair<const Key, T>>>;
|
/// Internal helper class to provide context for UnorderedMap
|
||||||
|
template <class Key, class Value>
|
||||||
|
class UnorderedMapDetail
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Get key from key value pair
|
||||||
|
static const Key & sGetKey(const std::pair<Key, Value> &inKeyValue)
|
||||||
|
{
|
||||||
|
return inKeyValue.first;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Hash Map class
|
||||||
|
/// @tparam Key Key type
|
||||||
|
/// @tparam Value Value type
|
||||||
|
/// @tparam Hash Hash function (note should be 64-bits)
|
||||||
|
/// @tparam KeyEqual Equality comparison function
|
||||||
|
template <class Key, class Value, class Hash = JPH::Hash<Key>, class KeyEqual = std::equal_to<Key>>
|
||||||
|
class UnorderedMap : public HashTable<Key, std::pair<Key, Value>, UnorderedMapDetail<Key, Value>, Hash, KeyEqual>
|
||||||
|
{
|
||||||
|
using Base = HashTable<Key, std::pair<Key, Value>, UnorderedMapDetail<Key, Value>, Hash, KeyEqual>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using size_type = typename Base::size_type;
|
||||||
|
using iterator = typename Base::iterator;
|
||||||
|
using const_iterator = typename Base::const_iterator;
|
||||||
|
using value_type = typename Base::value_type;
|
||||||
|
|
||||||
|
Value & operator [] (const Key &inKey)
|
||||||
|
{
|
||||||
|
size_type index;
|
||||||
|
bool inserted = this->InsertKey(inKey, index);
|
||||||
|
value_type &key_value = this->GetElement(index);
|
||||||
|
if (inserted)
|
||||||
|
new (&key_value) value_type(inKey, Value());
|
||||||
|
return key_value.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
std::pair<iterator, bool> try_emplace(const Key &inKey, Args &&...inArgs)
|
||||||
|
{
|
||||||
|
size_type index;
|
||||||
|
bool inserted = this->InsertKey(inKey, index);
|
||||||
|
if (inserted)
|
||||||
|
new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(inKey), std::forward_as_tuple(std::forward<Args>(inArgs)...));
|
||||||
|
return std::make_pair(iterator(this, index), inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
std::pair<iterator, bool> try_emplace(Key &&inKey, Args &&...inArgs)
|
||||||
|
{
|
||||||
|
size_type index;
|
||||||
|
bool inserted = this->InsertKey(inKey, index);
|
||||||
|
if (inserted)
|
||||||
|
new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(std::move(inKey)), std::forward_as_tuple(std::forward<Args>(inArgs)...));
|
||||||
|
return std::make_pair(iterator(this, index), inserted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Const version of find
|
||||||
|
using Base::find;
|
||||||
|
|
||||||
|
/// Non-const version of find
|
||||||
|
iterator find(const Key &inKey)
|
||||||
|
{
|
||||||
|
const_iterator it = Base::find(inKey);
|
||||||
|
return iterator(this, it.mIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,32 @@
|
||||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
#include <Jolt/Core/HashTable.h>
|
||||||
#include <unordered_set>
|
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
template <class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>> using UnorderedSet = std::unordered_set<Key, Hash, KeyEqual, STLAllocator<Key>>;
|
/// Internal helper class to provide context for UnorderedSet
|
||||||
|
template <class Key>
|
||||||
|
class UnorderedSetDetail
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// The key is the key, just return it
|
||||||
|
static const Key & sGetKey(const Key &inKey)
|
||||||
|
{
|
||||||
|
return inKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Hash Set class
|
||||||
|
/// @tparam Key Key type
|
||||||
|
/// @tparam Hash Hash function (note should be 64-bits)
|
||||||
|
/// @tparam KeyEqual Equality comparison function
|
||||||
|
template <class Key, class Hash = JPH::Hash<Key>, class KeyEqual = std::equal_to<Key>>
|
||||||
|
class UnorderedSet : public HashTable<Key, Key, UnorderedSetDetail<Key>, Hash, KeyEqual>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,19 @@ public:
|
||||||
/// Create box from 2 points
|
/// Create box from 2 points
|
||||||
static AABox sFromTwoPoints(Vec3Arg inP1, Vec3Arg inP2) { return AABox(Vec3::sMin(inP1, inP2), Vec3::sMax(inP1, inP2)); }
|
static AABox sFromTwoPoints(Vec3Arg inP1, Vec3Arg inP2) { return AABox(Vec3::sMin(inP1, inP2), Vec3::sMax(inP1, inP2)); }
|
||||||
|
|
||||||
/// Get bounding box of size 2 * FLT_MAX
|
/// Create box from indexed triangle
|
||||||
|
static AABox sFromTriangle(const VertexList &inVertices, const IndexedTriangle &inTriangle)
|
||||||
|
{
|
||||||
|
AABox box = sFromTwoPoints(Vec3(inVertices[inTriangle.mIdx[0]]), Vec3(inVertices[inTriangle.mIdx[1]]));
|
||||||
|
box.Encapsulate(Vec3(inVertices[inTriangle.mIdx[2]]));
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get bounding box of size FLT_MAX
|
||||||
static AABox sBiggest()
|
static AABox sBiggest()
|
||||||
{
|
{
|
||||||
return AABox(Vec3::sReplicate(-FLT_MAX), Vec3::sReplicate(FLT_MAX));
|
/// Max half extent of AABox is 0.5 * FLT_MAX so that GetSize() remains finite
|
||||||
|
return AABox(Vec3::sReplicate(-0.5f * FLT_MAX), Vec3::sReplicate(0.5f * FLT_MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comparison operators
|
/// Comparison operators
|
||||||
|
|
@ -50,15 +59,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encapsulate point in bounding box
|
/// Encapsulate point in bounding box
|
||||||
void Encapsulate(Vec3Arg inPos)
|
void Encapsulate(Vec3Arg inPos)
|
||||||
{
|
{
|
||||||
mMin = Vec3::sMin(mMin, inPos);
|
mMin = Vec3::sMin(mMin, inPos);
|
||||||
mMax = Vec3::sMax(mMax, inPos);
|
mMax = Vec3::sMax(mMax, inPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encapsulate bounding box in bounding box
|
/// Encapsulate bounding box in bounding box
|
||||||
void Encapsulate(const AABox &inRHS)
|
void Encapsulate(const AABox &inRHS)
|
||||||
{
|
{
|
||||||
mMin = Vec3::sMin(mMin, inRHS.mMin);
|
mMin = Vec3::sMin(mMin, inRHS.mMin);
|
||||||
mMax = Vec3::sMax(mMax, inRHS.mMax);
|
mMax = Vec3::sMax(mMax, inRHS.mMax);
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +102,7 @@ public:
|
||||||
Vec3 min_length = Vec3::sReplicate(inMinEdgeLength);
|
Vec3 min_length = Vec3::sReplicate(inMinEdgeLength);
|
||||||
mMax = Vec3::sSelect(mMax, mMin + min_length, Vec3::sLess(mMax - mMin, min_length));
|
mMax = Vec3::sSelect(mMax, mMin + min_length, Vec3::sLess(mMax - mMin, min_length));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widen the box on both sides by inVector
|
/// Widen the box on both sides by inVector
|
||||||
void ExpandBy(Vec3Arg inVector)
|
void ExpandBy(Vec3Arg inVector)
|
||||||
{
|
{
|
||||||
|
|
@ -120,8 +129,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get surface area of bounding box
|
/// Get surface area of bounding box
|
||||||
float GetSurfaceArea() const
|
float GetSurfaceArea() const
|
||||||
{
|
{
|
||||||
Vec3 extent = mMax - mMin;
|
Vec3 extent = mMax - mMin;
|
||||||
return 2.0f * (extent.GetX() * extent.GetY() + extent.GetX() * extent.GetZ() + extent.GetY() * extent.GetZ());
|
return 2.0f * (extent.GetX() * extent.GetY() + extent.GetX() * extent.GetZ() + extent.GetY() * extent.GetZ());
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +195,7 @@ public:
|
||||||
// Start with the translation of the matrix
|
// Start with the translation of the matrix
|
||||||
Vec3 new_min, new_max;
|
Vec3 new_min, new_max;
|
||||||
new_min = new_max = inMatrix.GetTranslation();
|
new_min = new_max = inMatrix.GetTranslation();
|
||||||
|
|
||||||
// Now find the extreme points by considering the product of the min and max with each column of inMatrix
|
// Now find the extreme points by considering the product of the min and max with each column of inMatrix
|
||||||
for (int c = 0; c < 3; ++c)
|
for (int c = 0; c < 3; ++c)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,11 @@ JPH_INLINE UVec4 AABox4VsBox(Mat44Arg inOrientation, Vec3Arg inHalfExtents, Vec4
|
||||||
// Note that the code is swapped around: A is the aabox and B is the oriented box (this saves us from having to invert the orientation of the oriented box)
|
// Note that the code is swapped around: A is the aabox and B is the oriented box (this saves us from having to invert the orientation of the oriented box)
|
||||||
|
|
||||||
// Compute translation vector t (the translation of B in the space of A)
|
// Compute translation vector t (the translation of B in the space of A)
|
||||||
Vec4 t[3] {
|
Vec4 t[3] {
|
||||||
inOrientation.GetTranslation().SplatX() - 0.5f * (inBoxMinX + inBoxMaxX),
|
inOrientation.GetTranslation().SplatX() - 0.5f * (inBoxMinX + inBoxMaxX),
|
||||||
inOrientation.GetTranslation().SplatY() - 0.5f * (inBoxMinY + inBoxMaxY),
|
inOrientation.GetTranslation().SplatY() - 0.5f * (inBoxMinY + inBoxMaxY),
|
||||||
inOrientation.GetTranslation().SplatZ() - 0.5f * (inBoxMinZ + inBoxMaxZ) };
|
inOrientation.GetTranslation().SplatZ() - 0.5f * (inBoxMinZ + inBoxMaxZ) };
|
||||||
|
|
||||||
// Compute common subexpressions. Add in an epsilon term to
|
// Compute common subexpressions. Add in an epsilon term to
|
||||||
// counteract arithmetic errors when two edges are parallel and
|
// counteract arithmetic errors when two edges are parallel and
|
||||||
// their cross product is (near) null (see text for details)
|
// their cross product is (near) null (see text for details)
|
||||||
|
|
@ -104,9 +104,9 @@ JPH_INLINE UVec4 AABox4VsBox(Mat44Arg inOrientation, Vec3Arg inHalfExtents, Vec4
|
||||||
Vec3 abs_r[3] { inOrientation.GetAxisX().Abs() + epsilon, inOrientation.GetAxisY().Abs() + epsilon, inOrientation.GetAxisZ().Abs() + epsilon };
|
Vec3 abs_r[3] { inOrientation.GetAxisX().Abs() + epsilon, inOrientation.GetAxisY().Abs() + epsilon, inOrientation.GetAxisZ().Abs() + epsilon };
|
||||||
|
|
||||||
// Half extents for a
|
// Half extents for a
|
||||||
Vec4 a_half_extents[3] {
|
Vec4 a_half_extents[3] {
|
||||||
0.5f * (inBoxMaxX - inBoxMinX),
|
0.5f * (inBoxMaxX - inBoxMinX),
|
||||||
0.5f * (inBoxMaxY - inBoxMinY),
|
0.5f * (inBoxMaxY - inBoxMinY),
|
||||||
0.5f * (inBoxMaxZ - inBoxMinZ) };
|
0.5f * (inBoxMaxZ - inBoxMinZ) };
|
||||||
|
|
||||||
// Half extents of b
|
// Half extents of b
|
||||||
|
|
@ -119,7 +119,7 @@ JPH_INLINE UVec4 AABox4VsBox(Mat44Arg inOrientation, Vec3Arg inHalfExtents, Vec4
|
||||||
|
|
||||||
// Test axes L = A0, L = A1, L = A2
|
// Test axes L = A0, L = A1, L = A2
|
||||||
Vec4 ra, rb;
|
Vec4 ra, rb;
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = a_half_extents[i];
|
ra = a_half_extents[i];
|
||||||
rb = b_half_extents_x * abs_r[0][i] + b_half_extents_y * abs_r[1][i] + b_half_extents_z * abs_r[2][i];
|
rb = b_half_extents_x * abs_r[0][i] + b_half_extents_y * abs_r[1][i] + b_half_extents_z * abs_r[2][i];
|
||||||
|
|
@ -127,7 +127,7 @@ JPH_INLINE UVec4 AABox4VsBox(Mat44Arg inOrientation, Vec3Arg inHalfExtents, Vec4
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test axes L = B0, L = B1, L = B2
|
// Test axes L = B0, L = B1, L = B2
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = a_half_extents[0] * abs_r[i][0] + a_half_extents[1] * abs_r[i][1] + a_half_extents[2] * abs_r[i][2];
|
ra = a_half_extents[0] * abs_r[i][0] + a_half_extents[1] * abs_r[i][1] + a_half_extents[2] * abs_r[i][2];
|
||||||
rb = Vec4::sReplicate(inHalfExtents[i]);
|
rb = Vec4::sReplicate(inHalfExtents[i]);
|
||||||
|
|
@ -151,34 +151,34 @@ JPH_INLINE UVec4 AABox4VsBox(Mat44Arg inOrientation, Vec3Arg inHalfExtents, Vec4
|
||||||
|
|
||||||
// Test axis L = A1 x B0
|
// Test axis L = A1 x B0
|
||||||
ra = a_half_extents[0] * abs_r[0][2] + a_half_extents[2] * abs_r[0][0];
|
ra = a_half_extents[0] * abs_r[0][2] + a_half_extents[2] * abs_r[0][0];
|
||||||
rb = b_half_extents_y * abs_r[2][1] + b_half_extents_z * abs_r[1][1];
|
rb = b_half_extents_y * abs_r[2][1] + b_half_extents_z * abs_r[1][1];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 0) - t[2] * inOrientation(0, 0)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 0) - t[2] * inOrientation(0, 0)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Test axis L = A1 x B1
|
// Test axis L = A1 x B1
|
||||||
ra = a_half_extents[0] * abs_r[1][2] + a_half_extents[2] * abs_r[1][0];
|
ra = a_half_extents[0] * abs_r[1][2] + a_half_extents[2] * abs_r[1][0];
|
||||||
rb = b_half_extents_x * abs_r[2][1] + b_half_extents_z * abs_r[0][1];
|
rb = b_half_extents_x * abs_r[2][1] + b_half_extents_z * abs_r[0][1];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 1) - t[2] * inOrientation(0, 1)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 1) - t[2] * inOrientation(0, 1)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Test axis L = A1 x B2
|
// Test axis L = A1 x B2
|
||||||
ra = a_half_extents[0] * abs_r[2][2] + a_half_extents[2] * abs_r[2][0];
|
ra = a_half_extents[0] * abs_r[2][2] + a_half_extents[2] * abs_r[2][0];
|
||||||
rb = b_half_extents_x * abs_r[1][1] + b_half_extents_y * abs_r[0][1];
|
rb = b_half_extents_x * abs_r[1][1] + b_half_extents_y * abs_r[0][1];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 2) - t[2] * inOrientation(0, 2)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[0] * inOrientation(2, 2) - t[2] * inOrientation(0, 2)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Test axis L = A2 x B0
|
// Test axis L = A2 x B0
|
||||||
ra = a_half_extents[0] * abs_r[0][1] + a_half_extents[1] * abs_r[0][0];
|
ra = a_half_extents[0] * abs_r[0][1] + a_half_extents[1] * abs_r[0][0];
|
||||||
rb = b_half_extents_y * abs_r[2][2] + b_half_extents_z * abs_r[1][2];
|
rb = b_half_extents_y * abs_r[2][2] + b_half_extents_z * abs_r[1][2];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 0) - t[0] * inOrientation(1, 0)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 0) - t[0] * inOrientation(1, 0)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Test axis L = A2 x B1
|
// Test axis L = A2 x B1
|
||||||
ra = a_half_extents[0] * abs_r[1][1] + a_half_extents[1] * abs_r[1][0];
|
ra = a_half_extents[0] * abs_r[1][1] + a_half_extents[1] * abs_r[1][0];
|
||||||
rb = b_half_extents_x * abs_r[2][2] + b_half_extents_z * abs_r[0][2];
|
rb = b_half_extents_x * abs_r[2][2] + b_half_extents_z * abs_r[0][2];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 1) - t[0] * inOrientation(1, 1)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 1) - t[0] * inOrientation(1, 1)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Test axis L = A2 x B2
|
// Test axis L = A2 x B2
|
||||||
ra = a_half_extents[0] * abs_r[2][1] + a_half_extents[1] * abs_r[2][0];
|
ra = a_half_extents[0] * abs_r[2][1] + a_half_extents[1] * abs_r[2][0];
|
||||||
rb = b_half_extents_x * abs_r[1][2] + b_half_extents_y * abs_r[0][2];
|
rb = b_half_extents_x * abs_r[1][2] + b_half_extents_y * abs_r[0][2];
|
||||||
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 2) - t[0] * inOrientation(1, 2)).Abs(), ra + rb));
|
overlaps = UVec4::sAnd(overlaps, Vec4::sLessOrEqual((t[1] * inOrientation(0, 2) - t[0] * inOrientation(1, 2)).Abs(), ra + rb));
|
||||||
|
|
||||||
// Return if the OBB vs AABBs are intersecting
|
// Return if the OBB vs AABBs are intersecting
|
||||||
return overlaps;
|
return overlaps;
|
||||||
}
|
}
|
||||||
|
|
@ -189,16 +189,29 @@ JPH_INLINE UVec4 AABox4VsBox(const OrientedBox &inBox, Vec4Arg inBoxMinX, Vec4Ar
|
||||||
return AABox4VsBox(inBox.mOrientation, inBox.mHalfExtents, inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ, inEpsilon);
|
return AABox4VsBox(inBox.mOrientation, inBox.mHalfExtents, inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ, inEpsilon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the squared distance between 4 AABoxes and a point
|
||||||
|
JPH_INLINE Vec4 AABox4DistanceSqToPoint(Vec4Arg inPointX, Vec4Arg inPointY, Vec4Arg inPointZ, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ)
|
||||||
|
{
|
||||||
|
// Get closest point on box
|
||||||
|
Vec4 closest_x = Vec4::sMin(Vec4::sMax(inPointX, inBoxMinX), inBoxMaxX);
|
||||||
|
Vec4 closest_y = Vec4::sMin(Vec4::sMax(inPointY, inBoxMinY), inBoxMaxY);
|
||||||
|
Vec4 closest_z = Vec4::sMin(Vec4::sMax(inPointZ, inBoxMinZ), inBoxMaxZ);
|
||||||
|
|
||||||
|
// Return the squared distance between the box and point
|
||||||
|
return Square(closest_x - inPointX) + Square(closest_y - inPointY) + Square(closest_z - inPointZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the squared distance between 4 AABoxes and a point
|
||||||
|
JPH_INLINE Vec4 AABox4DistanceSqToPoint(Vec3 inPoint, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ)
|
||||||
|
{
|
||||||
|
return AABox4DistanceSqToPoint(inPoint.SplatX(), inPoint.SplatY(), inPoint.SplatZ(), inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ);
|
||||||
|
}
|
||||||
|
|
||||||
/// Test 4 AABoxes vs a sphere
|
/// Test 4 AABoxes vs a sphere
|
||||||
JPH_INLINE UVec4 AABox4VsSphere(Vec4Arg inCenterX, Vec4Arg inCenterY, Vec4Arg inCenterZ, Vec4Arg inRadiusSq, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ)
|
JPH_INLINE UVec4 AABox4VsSphere(Vec4Arg inCenterX, Vec4Arg inCenterY, Vec4Arg inCenterZ, Vec4Arg inRadiusSq, Vec4Arg inBoxMinX, Vec4Arg inBoxMinY, Vec4Arg inBoxMinZ, Vec4Arg inBoxMaxX, Vec4Arg inBoxMaxY, Vec4Arg inBoxMaxZ)
|
||||||
{
|
{
|
||||||
// Get closest point on box
|
|
||||||
Vec4 closest_x = Vec4::sMin(Vec4::sMax(inCenterX, inBoxMinX), inBoxMaxX);
|
|
||||||
Vec4 closest_y = Vec4::sMin(Vec4::sMax(inCenterY, inBoxMinY), inBoxMaxY);
|
|
||||||
Vec4 closest_z = Vec4::sMin(Vec4::sMax(inCenterZ, inBoxMinZ), inBoxMaxZ);
|
|
||||||
|
|
||||||
// Test the distance from the center of the sphere to the box is smaller than the radius
|
// Test the distance from the center of the sphere to the box is smaller than the radius
|
||||||
Vec4 distance_sq = Square(closest_x - inCenterX) + Square(closest_y - inCenterY) + Square(closest_z - inCenterZ);
|
Vec4 distance_sq = AABox4DistanceSqToPoint(inCenterX, inCenterY, inCenterZ, inBoxMinX, inBoxMinY, inBoxMinZ, inBoxMaxX, inBoxMaxY, inBoxMaxZ);
|
||||||
return Vec4::sLessOrEqual(distance_sq, inRadiusSq);
|
return Vec4::sLessOrEqual(distance_sq, inRadiusSq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ void ClipPolyVsPlane(const VERTEX_ARRAY &inPolygonToClip, Vec3Arg inPlaneOrigin,
|
||||||
// Determine state of last point
|
// Determine state of last point
|
||||||
Vec3 e1 = inPolygonToClip[inPolygonToClip.size() - 1];
|
Vec3 e1 = inPolygonToClip[inPolygonToClip.size() - 1];
|
||||||
float prev_num = (inPlaneOrigin - e1).Dot(inPlaneNormal);
|
float prev_num = (inPlaneOrigin - e1).Dot(inPlaneNormal);
|
||||||
bool prev_inside = prev_num < 0.0f;
|
bool prev_inside = prev_num < 0.0f;
|
||||||
|
|
||||||
// Loop through all vertices
|
// Loop through all vertices
|
||||||
for (typename VERTEX_ARRAY::size_type j = 0; j < inPolygonToClip.size(); ++j)
|
for (typename VERTEX_ARRAY::size_type j = 0; j < inPolygonToClip.size(); ++j)
|
||||||
|
|
@ -73,13 +73,13 @@ void ClipPolyVsPoly(const VERTEX_ARRAY &inPolygonToClip, const VERTEX_ARRAY &inC
|
||||||
Vec3 clip_e1 = inClippingPolygon[i];
|
Vec3 clip_e1 = inClippingPolygon[i];
|
||||||
Vec3 clip_e2 = inClippingPolygon[(i + 1) % inClippingPolygon.size()];
|
Vec3 clip_e2 = inClippingPolygon[(i + 1) % inClippingPolygon.size()];
|
||||||
Vec3 clip_normal = inClippingPolygonNormal.Cross(clip_e2 - clip_e1); // Pointing inward to the clipping polygon
|
Vec3 clip_normal = inClippingPolygonNormal.Cross(clip_e2 - clip_e1); // Pointing inward to the clipping polygon
|
||||||
|
|
||||||
// Get source and target polygon
|
// Get source and target polygon
|
||||||
const VERTEX_ARRAY &src_polygon = (i == 0)? inPolygonToClip : tmp_vertices[tmp_vertices_idx];
|
const VERTEX_ARRAY &src_polygon = (i == 0)? inPolygonToClip : tmp_vertices[tmp_vertices_idx];
|
||||||
tmp_vertices_idx ^= 1;
|
tmp_vertices_idx ^= 1;
|
||||||
VERTEX_ARRAY &tgt_polygon = (i == inClippingPolygon.size() - 1)? outClippedPolygon : tmp_vertices[tmp_vertices_idx];
|
VERTEX_ARRAY &tgt_polygon = (i == inClippingPolygon.size() - 1)? outClippedPolygon : tmp_vertices[tmp_vertices_idx];
|
||||||
tgt_polygon.clear();
|
tgt_polygon.clear();
|
||||||
|
|
||||||
// Clip against the edge
|
// Clip against the edge
|
||||||
ClipPolyVsPlane(src_polygon, clip_e1, clip_normal, tgt_polygon);
|
ClipPolyVsPlane(src_polygon, clip_e1, clip_normal, tgt_polygon);
|
||||||
|
|
||||||
|
|
@ -115,8 +115,8 @@ void ClipPolyVsEdge(const VERTEX_ARRAY &inPolygonToClip, Vec3Arg inEdgeVertex1,
|
||||||
// Determine state of last point
|
// Determine state of last point
|
||||||
Vec3 e1 = inPolygonToClip[inPolygonToClip.size() - 1];
|
Vec3 e1 = inPolygonToClip[inPolygonToClip.size() - 1];
|
||||||
float prev_num = (inEdgeVertex1 - e1).Dot(edge_normal);
|
float prev_num = (inEdgeVertex1 - e1).Dot(edge_normal);
|
||||||
bool prev_inside = prev_num < 0.0f;
|
bool prev_inside = prev_num < 0.0f;
|
||||||
|
|
||||||
// Loop through all vertices
|
// Loop through all vertices
|
||||||
for (typename VERTEX_ARRAY::size_type j = 0; j < inPolygonToClip.size(); ++j)
|
for (typename VERTEX_ARRAY::size_type j = 0; j < inPolygonToClip.size(); ++j)
|
||||||
{
|
{
|
||||||
|
|
@ -128,10 +128,10 @@ void ClipPolyVsEdge(const VERTEX_ARRAY &inPolygonToClip, Vec3Arg inEdgeVertex1,
|
||||||
// In -> Out or Out -> In: Add point on clipping plane
|
// In -> Out or Out -> In: Add point on clipping plane
|
||||||
if (cur_inside != prev_inside)
|
if (cur_inside != prev_inside)
|
||||||
{
|
{
|
||||||
// Solve: (X - inPlaneOrigin) . inPlaneNormal = 0 and X = e1 + t * (e2 - e1) for X
|
// Solve: (inEdgeVertex1 - X) . edge_normal = 0 and X = e1 + t * (e2 - e1) for X
|
||||||
Vec3 e12 = e2 - e1;
|
Vec3 e12 = e2 - e1;
|
||||||
float denom = e12.Dot(edge_normal);
|
float denom = e12.Dot(edge_normal);
|
||||||
Vec3 clipped_point = e1 + (prev_num / denom) * e12;
|
Vec3 clipped_point = denom != 0.0f? e1 + (prev_num / denom) * e12 : e1;
|
||||||
|
|
||||||
// Project point on line segment v1, v2 so see if it falls outside if the edge
|
// Project point on line segment v1, v2 so see if it falls outside if the edge
|
||||||
float projection = (clipped_point - v1).Dot(v12);
|
float projection = (clipped_point - v1).Dot(v12);
|
||||||
|
|
@ -142,7 +142,7 @@ void ClipPolyVsEdge(const VERTEX_ARRAY &inPolygonToClip, Vec3Arg inEdgeVertex1,
|
||||||
else
|
else
|
||||||
outClippedPolygon.push_back(clipped_point);
|
outClippedPolygon.push_back(clipped_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update previous state
|
// Update previous state
|
||||||
prev_num = num;
|
prev_num = num;
|
||||||
prev_inside = cur_inside;
|
prev_inside = cur_inside;
|
||||||
|
|
@ -159,7 +159,7 @@ void ClipPolyVsAABox(const VERTEX_ARRAY &inPolygonToClip, const AABox &inAABox,
|
||||||
|
|
||||||
VERTEX_ARRAY tmp_vertices[2];
|
VERTEX_ARRAY tmp_vertices[2];
|
||||||
int tmp_vertices_idx = 0;
|
int tmp_vertices_idx = 0;
|
||||||
|
|
||||||
for (int coord = 0; coord < 3; ++coord)
|
for (int coord = 0; coord < 3; ++coord)
|
||||||
for (int side = 0; side < 2; ++side)
|
for (int side = 0; side < 2; ++side)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ namespace ClosestPoint
|
||||||
{
|
{
|
||||||
/// Compute barycentric coordinates of closest point to origin for infinite line defined by (inA, inB)
|
/// Compute barycentric coordinates of closest point to origin for infinite line defined by (inA, inB)
|
||||||
/// Point can then be computed as inA * outU + inB * outV
|
/// Point can then be computed as inA * outU + inB * outV
|
||||||
inline void GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, float &outU, float &outV)
|
/// Returns false if the points inA, inB do not form a line (are at the same point)
|
||||||
|
inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, float &outU, float &outV)
|
||||||
{
|
{
|
||||||
Vec3 ab = inB - inA;
|
Vec3 ab = inB - inA;
|
||||||
float denominator = ab.LengthSq();
|
float denominator = ab.LengthSq();
|
||||||
|
|
@ -33,17 +34,20 @@ namespace ClosestPoint
|
||||||
outU = 0.0f;
|
outU = 0.0f;
|
||||||
outV = 1.0f;
|
outV = 1.0f;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outV = -inA.Dot(ab) / denominator;
|
outV = -inA.Dot(ab) / denominator;
|
||||||
outU = 1.0f - outV;
|
outU = 1.0f - outV;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute barycentric coordinates of closest point to origin for plane defined by (inA, inB, inC)
|
/// Compute barycentric coordinates of closest point to origin for plane defined by (inA, inB, inC)
|
||||||
/// Point can then be computed as inA * outU + inB * outV + inC * outW
|
/// Point can then be computed as inA * outU + inB * outV + inC * outW
|
||||||
inline void GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, float &outU, float &outV, float &outW)
|
/// Returns false if the points inA, inB, inC do not form a plane (are on the same line or at the same point)
|
||||||
|
inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, float &outU, float &outV, float &outW)
|
||||||
{
|
{
|
||||||
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Barycentric Coordinates)
|
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Barycentric Coordinates)
|
||||||
// With p = 0
|
// With p = 0
|
||||||
|
|
@ -55,16 +59,18 @@ namespace ClosestPoint
|
||||||
Vec3 v2 = inC - inB;
|
Vec3 v2 = inC - inB;
|
||||||
|
|
||||||
// Make sure that the shortest edge is included in the calculation to keep the products a * b - c * d as small as possible to preserve accuracy
|
// Make sure that the shortest edge is included in the calculation to keep the products a * b - c * d as small as possible to preserve accuracy
|
||||||
float d00 = v0.Dot(v0);
|
float d00 = v0.LengthSq();
|
||||||
float d11 = v1.Dot(v1);
|
float d11 = v1.LengthSq();
|
||||||
float d22 = v2.Dot(v2);
|
float d22 = v2.LengthSq();
|
||||||
if (d00 <= d22)
|
if (d00 <= d22)
|
||||||
{
|
{
|
||||||
// Use v0 and v1 to calculate barycentric coordinates
|
// Use v0 and v1 to calculate barycentric coordinates
|
||||||
float d01 = v0.Dot(v1);
|
float d01 = v0.Dot(v1);
|
||||||
|
|
||||||
float denominator = d00 * d11 - d01 * d01;
|
// Denominator must be positive:
|
||||||
if (abs(denominator) < FLT_EPSILON)
|
// |v0|^2 * |v1|^2 - (v0 . v1)^2 = |v0|^2 * |v1|^2 * (1 - cos(angle)^2) >= 0
|
||||||
|
float denominator = d00 * d11 - d01 * d01;
|
||||||
|
if (denominator < 1.0e-12f)
|
||||||
{
|
{
|
||||||
// Degenerate triangle, return coordinates along longest edge
|
// Degenerate triangle, return coordinates along longest edge
|
||||||
if (d00 > d11)
|
if (d00 > d11)
|
||||||
|
|
@ -77,23 +83,24 @@ namespace ClosestPoint
|
||||||
GetBaryCentricCoordinates(inA, inC, outU, outW);
|
GetBaryCentricCoordinates(inA, inC, outU, outW);
|
||||||
outV = 0.0f;
|
outV = 0.0f;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float a0 = inA.Dot(v0);
|
float a0 = inA.Dot(v0);
|
||||||
float a1 = inA.Dot(v1);
|
float a1 = inA.Dot(v1);
|
||||||
outV = (d01 * a1 - d11 * a0) / denominator;
|
outV = (d01 * a1 - d11 * a0) / denominator;
|
||||||
outW = (d01 * a0 - d00 * a1) / denominator;
|
outW = (d01 * a0 - d00 * a1) / denominator;
|
||||||
outU = 1.0f - outV - outW;
|
outU = 1.0f - outV - outW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Use v1 and v2 to calculate barycentric coordinates
|
// Use v1 and v2 to calculate barycentric coordinates
|
||||||
float d12 = v1.Dot(v2);
|
float d12 = v1.Dot(v2);
|
||||||
|
|
||||||
float denominator = d11 * d22 - d12 * d12;
|
float denominator = d11 * d22 - d12 * d12;
|
||||||
if (abs(denominator) < FLT_EPSILON)
|
if (denominator < 1.0e-12f)
|
||||||
{
|
{
|
||||||
// Degenerate triangle, return coordinates along longest edge
|
// Degenerate triangle, return coordinates along longest edge
|
||||||
if (d11 > d22)
|
if (d11 > d22)
|
||||||
|
|
@ -106,21 +113,23 @@ namespace ClosestPoint
|
||||||
GetBaryCentricCoordinates(inB, inC, outV, outW);
|
GetBaryCentricCoordinates(inB, inC, outV, outW);
|
||||||
outU = 0.0f;
|
outU = 0.0f;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float c1 = inC.Dot(v1);
|
float c1 = inC.Dot(v1);
|
||||||
float c2 = inC.Dot(v2);
|
float c2 = inC.Dot(v2);
|
||||||
outU = (d22 * c1 - d12 * c2) / denominator;
|
outU = (d22 * c1 - d12 * c2) / denominator;
|
||||||
outV = (d11 * c2 - d12 * c1) / denominator;
|
outV = (d11 * c2 - d12 * c1) / denominator;
|
||||||
outW = 1.0f - outU - outV;
|
outW = 1.0f - outU - outV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the closest point to the origin of line (inA, inB)
|
/// Get the closest point to the origin of line (inA, inB)
|
||||||
/// outSet describes which features are closest: 1 = a, 2 = b, 3 = line segment ab
|
/// outSet describes which features are closest: 1 = a, 2 = b, 3 = line segment ab
|
||||||
inline Vec3 GetClosestPointOnLine(Vec3Arg inA, Vec3Arg inB, uint32 &outSet)
|
inline Vec3 GetClosestPointOnLine(Vec3Arg inA, Vec3Arg inB, uint32 &outSet)
|
||||||
{
|
{
|
||||||
float u, v;
|
float u, v;
|
||||||
GetBaryCentricCoordinates(inA, inB, u, v);
|
GetBaryCentricCoordinates(inA, inB, u, v);
|
||||||
|
|
@ -174,7 +183,7 @@ namespace ClosestPoint
|
||||||
float n_len_sq = n.LengthSq();
|
float n_len_sq = n.LengthSq();
|
||||||
|
|
||||||
// Check degenerate
|
// Check degenerate
|
||||||
if (n_len_sq < 1.0e-11f) // Square(FLT_EPSILON) was too small and caused numerical problems, see test case TestCollideParallelTriangleVsCapsule
|
if (n_len_sq < 1.0e-10f) // Square(FLT_EPSILON) was too small and caused numerical problems, see test case TestCollideParallelTriangleVsCapsule
|
||||||
{
|
{
|
||||||
// Degenerate, fallback to vertices and edges
|
// Degenerate, fallback to vertices and edges
|
||||||
|
|
||||||
|
|
@ -184,6 +193,7 @@ namespace ClosestPoint
|
||||||
float best_dist_sq = inC.LengthSq();
|
float best_dist_sq = inC.LengthSq();
|
||||||
|
|
||||||
// If the closest point must include C then A or B cannot be closest
|
// If the closest point must include C then A or B cannot be closest
|
||||||
|
// Note that we test vertices first because we want to prefer a closest vertex over a closest edge (this results in an outSet with fewer bits set)
|
||||||
if constexpr (!MustIncludeC)
|
if constexpr (!MustIncludeC)
|
||||||
{
|
{
|
||||||
// Try vertex A
|
// Try vertex A
|
||||||
|
|
@ -203,21 +213,6 @@ namespace ClosestPoint
|
||||||
closest_point = inB;
|
closest_point = inB;
|
||||||
best_dist_sq = b_len_sq;
|
best_dist_sq = b_len_sq;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge AB
|
|
||||||
float ab_len_sq = ab.LengthSq();
|
|
||||||
if (ab_len_sq > Square(FLT_EPSILON))
|
|
||||||
{
|
|
||||||
float v = Clamp(-a.Dot(ab) / ab_len_sq, 0.0f, 1.0f);
|
|
||||||
Vec3 q = a + v * ab;
|
|
||||||
float dist_sq = q.LengthSq();
|
|
||||||
if (dist_sq < best_dist_sq)
|
|
||||||
{
|
|
||||||
closest_set = swap_ac.GetX()? 0b0110 : 0b0011;
|
|
||||||
closest_point = q;
|
|
||||||
best_dist_sq = dist_sq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge AC
|
// Edge AC
|
||||||
|
|
@ -236,7 +231,7 @@ namespace ClosestPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge BC
|
// Edge BC
|
||||||
Vec3 bc = c - inB;
|
Vec3 bc = inC - inB;
|
||||||
float bc_len_sq = bc.LengthSq();
|
float bc_len_sq = bc.LengthSq();
|
||||||
if (bc_len_sq > Square(FLT_EPSILON))
|
if (bc_len_sq > Square(FLT_EPSILON))
|
||||||
{
|
{
|
||||||
|
|
@ -245,77 +240,97 @@ namespace ClosestPoint
|
||||||
float dist_sq = q.LengthSq();
|
float dist_sq = q.LengthSq();
|
||||||
if (dist_sq < best_dist_sq)
|
if (dist_sq < best_dist_sq)
|
||||||
{
|
{
|
||||||
closest_set = swap_ac.GetX()? 0b0011 : 0b0110;
|
closest_set = 0b0110;
|
||||||
closest_point = q;
|
closest_point = q;
|
||||||
best_dist_sq = dist_sq;
|
best_dist_sq = dist_sq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the closest point must include C then AB cannot be closest
|
||||||
|
if constexpr (!MustIncludeC)
|
||||||
|
{
|
||||||
|
// Edge AB
|
||||||
|
ab = inB - inA;
|
||||||
|
float ab_len_sq = ab.LengthSq();
|
||||||
|
if (ab_len_sq > Square(FLT_EPSILON))
|
||||||
|
{
|
||||||
|
float v = Clamp(-inA.Dot(ab) / ab_len_sq, 0.0f, 1.0f);
|
||||||
|
Vec3 q = inA + v * ab;
|
||||||
|
float dist_sq = q.LengthSq();
|
||||||
|
if (dist_sq < best_dist_sq)
|
||||||
|
{
|
||||||
|
closest_set = 0b0011;
|
||||||
|
closest_point = q;
|
||||||
|
best_dist_sq = dist_sq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
outSet = closest_set;
|
outSet = closest_set;
|
||||||
return closest_point;
|
return closest_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in vertex region outside A
|
// Check if P in vertex region outside A
|
||||||
Vec3 ap = -a;
|
Vec3 ap = -a;
|
||||||
float d1 = ab.Dot(ap);
|
float d1 = ab.Dot(ap);
|
||||||
float d2 = ac.Dot(ap);
|
float d2 = ac.Dot(ap);
|
||||||
if (d1 <= 0.0f && d2 <= 0.0f)
|
if (d1 <= 0.0f && d2 <= 0.0f)
|
||||||
{
|
{
|
||||||
outSet = swap_ac.GetX()? 0b0100 : 0b0001;
|
outSet = swap_ac.GetX()? 0b0100 : 0b0001;
|
||||||
return a; // barycentric coordinates (1,0,0)
|
return a; // barycentric coordinates (1,0,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in vertex region outside B
|
// Check if P in vertex region outside B
|
||||||
Vec3 bp = -inB;
|
Vec3 bp = -inB;
|
||||||
float d3 = ab.Dot(bp);
|
float d3 = ab.Dot(bp);
|
||||||
float d4 = ac.Dot(bp);
|
float d4 = ac.Dot(bp);
|
||||||
if (d3 >= 0.0f && d4 <= d3)
|
if (d3 >= 0.0f && d4 <= d3)
|
||||||
{
|
{
|
||||||
outSet = 0b0010;
|
outSet = 0b0010;
|
||||||
return inB; // barycentric coordinates (0,1,0)
|
return inB; // barycentric coordinates (0,1,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in edge region of AB, if so return projection of P onto AB
|
// Check if P in edge region of AB, if so return projection of P onto AB
|
||||||
if (d1 * d4 <= d3 * d2 && d1 >= 0.0f && d3 <= 0.0f)
|
if (d1 * d4 <= d3 * d2 && d1 >= 0.0f && d3 <= 0.0f)
|
||||||
{
|
{
|
||||||
float v = d1 / (d1 - d3);
|
float v = d1 / (d1 - d3);
|
||||||
outSet = swap_ac.GetX()? 0b0110 : 0b0011;
|
outSet = swap_ac.GetX()? 0b0110 : 0b0011;
|
||||||
return a + v * ab; // barycentric coordinates (1-v,v,0)
|
return a + v * ab; // barycentric coordinates (1-v,v,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in vertex region outside C
|
// Check if P in vertex region outside C
|
||||||
Vec3 cp = -c;
|
Vec3 cp = -c;
|
||||||
float d5 = ab.Dot(cp);
|
float d5 = ab.Dot(cp);
|
||||||
float d6 = ac.Dot(cp);
|
float d6 = ac.Dot(cp);
|
||||||
if (d6 >= 0.0f && d5 <= d6)
|
if (d6 >= 0.0f && d5 <= d6)
|
||||||
{
|
{
|
||||||
outSet = swap_ac.GetX()? 0b0001 : 0b0100;
|
outSet = swap_ac.GetX()? 0b0001 : 0b0100;
|
||||||
return c; // barycentric coordinates (0,0,1)
|
return c; // barycentric coordinates (0,0,1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in edge region of AC, if so return projection of P onto AC
|
// Check if P in edge region of AC, if so return projection of P onto AC
|
||||||
if (d5 * d2 <= d1 * d6 && d2 >= 0.0f && d6 <= 0.0f)
|
if (d5 * d2 <= d1 * d6 && d2 >= 0.0f && d6 <= 0.0f)
|
||||||
{
|
{
|
||||||
float w = d2 / (d2 - d6);
|
float w = d2 / (d2 - d6);
|
||||||
outSet = 0b0101;
|
outSet = 0b0101;
|
||||||
return a + w * ac; // barycentric coordinates (1-w,0,w)
|
return a + w * ac; // barycentric coordinates (1-w,0,w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if P in edge region of BC, if so return projection of P onto BC
|
// Check if P in edge region of BC, if so return projection of P onto BC
|
||||||
float d4_d3 = d4 - d3;
|
float d4_d3 = d4 - d3;
|
||||||
float d5_d6 = d5 - d6;
|
float d5_d6 = d5 - d6;
|
||||||
if (d3 * d6 <= d5 * d4 && d4_d3 >= 0.0f && d5_d6 >= 0.0f)
|
if (d3 * d6 <= d5 * d4 && d4_d3 >= 0.0f && d5_d6 >= 0.0f)
|
||||||
{
|
{
|
||||||
float w = d4_d3 / (d4_d3 + d5_d6);
|
float w = d4_d3 / (d4_d3 + d5_d6);
|
||||||
outSet = swap_ac.GetX()? 0b0011 : 0b0110;
|
outSet = swap_ac.GetX()? 0b0011 : 0b0110;
|
||||||
return inB + w * (c - inB); // barycentric coordinates (0,1-w,w)
|
return inB + w * (c - inB); // barycentric coordinates (0,1-w,w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// P inside face region.
|
// P inside face region.
|
||||||
// Here we deviate from Christer Ericson's article to improve accuracy.
|
// Here we deviate from Christer Ericson's article to improve accuracy.
|
||||||
// Determine distance between triangle and origin: distance = (centroid - origin) . normal / |normal|
|
// Determine distance between triangle and origin: distance = (centroid - origin) . normal / |normal|
|
||||||
// Closest point to origin is then: distance . normal / |normal|
|
// Closest point to origin is then: distance . normal / |normal|
|
||||||
// Note that this way of calculating the closest point is much more accurate than first calculating barycentric coordinates
|
// Note that this way of calculating the closest point is much more accurate than first calculating barycentric coordinates
|
||||||
// and then calculating the closest point based on those coordinates.
|
// and then calculating the closest point based on those coordinates.
|
||||||
outSet = 0b0111;
|
outSet = 0b0111;
|
||||||
return n * (a + inB + c).Dot(n) / (3.0f * n_len_sq);
|
return n * (a + inB + c).Dot(n) / (3.0f * n_len_sq);
|
||||||
|
|
@ -327,22 +342,22 @@ namespace ClosestPoint
|
||||||
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)
|
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)
|
||||||
// With p = 0
|
// With p = 0
|
||||||
|
|
||||||
// Test if point p and d lie on opposite sides of plane through abc
|
// Test if point p and d lie on opposite sides of plane through abc
|
||||||
Vec3 n = (inB - inA).Cross(inC - inA);
|
Vec3 n = (inB - inA).Cross(inC - inA);
|
||||||
float signp = inA.Dot(n); // [AP AB AC]
|
float signp = inA.Dot(n); // [AP AB AC]
|
||||||
float signd = (inD - inA).Dot(n); // [AD AB AC]
|
float signd = (inD - inA).Dot(n); // [AD AB AC]
|
||||||
|
|
||||||
// Points on opposite sides if expression signs are the same
|
// Points on opposite sides if expression signs are the same
|
||||||
// Note that we left out the minus sign in signp so we need to check > 0 instead of < 0 as in Christer's book
|
// Note that we left out the minus sign in signp so we need to check > 0 instead of < 0 as in Christer's book
|
||||||
// We compare against a small negative value to allow for a little bit of slop in the calculations
|
// We compare against a small negative value to allow for a little bit of slop in the calculations
|
||||||
return signp * signd > -FLT_EPSILON;
|
return signp * signd > -FLT_EPSILON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns for each of the planes of the tetrahedron if the origin is inside it
|
/// Returns for each of the planes of the tetrahedron if the origin is inside it
|
||||||
/// Roughly equivalent to:
|
/// Roughly equivalent to:
|
||||||
/// [OriginOutsideOfPlane(inA, inB, inC, inD),
|
/// [OriginOutsideOfPlane(inA, inB, inC, inD),
|
||||||
/// OriginOutsideOfPlane(inA, inC, inD, inB),
|
/// OriginOutsideOfPlane(inA, inC, inD, inB),
|
||||||
/// OriginOutsideOfPlane(inA, inD, inB, inC),
|
/// OriginOutsideOfPlane(inA, inD, inB, inC),
|
||||||
/// OriginOutsideOfPlane(inB, inD, inC, inA)]
|
/// OriginOutsideOfPlane(inB, inD, inC, inA)]
|
||||||
inline UVec4 OriginOutsideOfTetrahedronPlanes(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, Vec3Arg inD)
|
inline UVec4 OriginOutsideOfTetrahedronPlanes(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, Vec3Arg inD)
|
||||||
{
|
{
|
||||||
|
|
@ -356,7 +371,7 @@ namespace ClosestPoint
|
||||||
Vec3 ac_cross_ad = ac.Cross(ad);
|
Vec3 ac_cross_ad = ac.Cross(ad);
|
||||||
Vec3 ad_cross_ab = ad.Cross(ab);
|
Vec3 ad_cross_ab = ad.Cross(ab);
|
||||||
Vec3 bd_cross_bc = bd.Cross(bc);
|
Vec3 bd_cross_bc = bd.Cross(bc);
|
||||||
|
|
||||||
// For each plane get the side on which the origin is
|
// For each plane get the side on which the origin is
|
||||||
float signp0 = inA.Dot(ab_cross_ac); // ABC
|
float signp0 = inA.Dot(ab_cross_ac); // ABC
|
||||||
float signp1 = inA.Dot(ac_cross_ad); // ACD
|
float signp1 = inA.Dot(ac_cross_ad); // ACD
|
||||||
|
|
@ -400,15 +415,15 @@ namespace ClosestPoint
|
||||||
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)
|
// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)
|
||||||
// With p = 0
|
// With p = 0
|
||||||
|
|
||||||
// Start out assuming point inside all halfspaces, so closest to itself
|
// Start out assuming point inside all halfspaces, so closest to itself
|
||||||
uint32 closest_set = 0b1111;
|
uint32 closest_set = 0b1111;
|
||||||
Vec3 closest_point = Vec3::sZero();
|
Vec3 closest_point = Vec3::sZero();
|
||||||
float best_dist_sq = FLT_MAX;
|
float best_dist_sq = FLT_MAX;
|
||||||
|
|
||||||
// Determine for each of the faces of the tetrahedron if the origin is in front of the plane
|
// Determine for each of the faces of the tetrahedron if the origin is in front of the plane
|
||||||
UVec4 origin_out_of_planes = OriginOutsideOfTetrahedronPlanes(inA, inB, inC, inD);
|
UVec4 origin_out_of_planes = OriginOutsideOfTetrahedronPlanes(inA, inB, inC, inD);
|
||||||
|
|
||||||
// If point outside face abc then compute closest point on abc
|
// If point outside face abc then compute closest point on abc
|
||||||
if (origin_out_of_planes.GetX()) // OriginOutsideOfPlane(inA, inB, inC, inD)
|
if (origin_out_of_planes.GetX()) // OriginOutsideOfPlane(inA, inB, inC, inD)
|
||||||
{
|
{
|
||||||
if constexpr (MustIncludeD)
|
if constexpr (MustIncludeD)
|
||||||
|
|
@ -421,18 +436,18 @@ namespace ClosestPoint
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Test the face normally
|
// Test the face normally
|
||||||
closest_point = GetClosestPointOnTriangle<false>(inA, inB, inC, closest_set);
|
closest_point = GetClosestPointOnTriangle<false>(inA, inB, inC, closest_set);
|
||||||
}
|
}
|
||||||
best_dist_sq = closest_point.LengthSq();
|
best_dist_sq = closest_point.LengthSq();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repeat test for face acd
|
// Repeat test for face acd
|
||||||
if (origin_out_of_planes.GetY()) // OriginOutsideOfPlane(inA, inC, inD, inB)
|
if (origin_out_of_planes.GetY()) // OriginOutsideOfPlane(inA, inC, inD, inB)
|
||||||
{
|
{
|
||||||
uint32 set;
|
uint32 set;
|
||||||
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inC, inD, set);
|
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inC, inD, set);
|
||||||
float dist_sq = q.LengthSq();
|
float dist_sq = q.LengthSq();
|
||||||
if (dist_sq < best_dist_sq)
|
if (dist_sq < best_dist_sq)
|
||||||
{
|
{
|
||||||
best_dist_sq = dist_sq;
|
best_dist_sq = dist_sq;
|
||||||
closest_point = q;
|
closest_point = q;
|
||||||
|
|
@ -440,39 +455,39 @@ namespace ClosestPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repeat test for face adb
|
// Repeat test for face adb
|
||||||
if (origin_out_of_planes.GetZ()) // OriginOutsideOfPlane(inA, inD, inB, inC)
|
if (origin_out_of_planes.GetZ()) // OriginOutsideOfPlane(inA, inD, inB, inC)
|
||||||
{
|
{
|
||||||
// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward
|
// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward
|
||||||
// and it improves consistency for GJK which will always add a new vertex D and keep the closest
|
// and it improves consistency for GJK which will always add a new vertex D and keep the closest
|
||||||
// feature from the previous iteration in ABC
|
// feature from the previous iteration in ABC
|
||||||
uint32 set;
|
uint32 set;
|
||||||
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inB, inD, set);
|
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inB, inD, set);
|
||||||
float dist_sq = q.LengthSq();
|
float dist_sq = q.LengthSq();
|
||||||
if (dist_sq < best_dist_sq)
|
if (dist_sq < best_dist_sq)
|
||||||
{
|
{
|
||||||
best_dist_sq = dist_sq;
|
best_dist_sq = dist_sq;
|
||||||
closest_point = q;
|
closest_point = q;
|
||||||
closest_set = (set & 0b0011) + ((set & 0b0100) << 1);
|
closest_set = (set & 0b0011) + ((set & 0b0100) << 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repeat test for face bdc
|
// Repeat test for face bdc
|
||||||
if (origin_out_of_planes.GetW()) // OriginOutsideOfPlane(inB, inD, inC, inA)
|
if (origin_out_of_planes.GetW()) // OriginOutsideOfPlane(inB, inD, inC, inA)
|
||||||
{
|
{
|
||||||
// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward
|
// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward
|
||||||
// and it improves consistency for GJK which will always add a new vertex D and keep the closest
|
// and it improves consistency for GJK which will always add a new vertex D and keep the closest
|
||||||
// feature from the previous iteration in ABC
|
// feature from the previous iteration in ABC
|
||||||
uint32 set;
|
uint32 set;
|
||||||
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inB, inC, inD, set);
|
Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inB, inC, inD, set);
|
||||||
float dist_sq = q.LengthSq();
|
float dist_sq = q.LengthSq();
|
||||||
if (dist_sq < best_dist_sq)
|
if (dist_sq < best_dist_sq)
|
||||||
{
|
{
|
||||||
closest_point = q;
|
closest_point = q;
|
||||||
closest_set = set << 1;
|
closest_set = set << 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outSet = closest_set;
|
outSet = closest_set;
|
||||||
return closest_point;
|
return closest_point;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@
|
||||||
#include <Jolt/Core/StringTools.h>
|
#include <Jolt/Core/StringTools.h>
|
||||||
#include <Jolt/Core/UnorderedSet.h>
|
#include <Jolt/Core/UnorderedSet.h>
|
||||||
|
|
||||||
|
#ifdef JPH_CONVEX_BUILDER_DUMP_SHAPE
|
||||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
JPH_SUPPRESS_WARNINGS_STD_END
|
JPH_SUPPRESS_WARNINGS_STD_END
|
||||||
|
#endif // JPH_CONVEX_BUILDER_DUMP_SHAPE
|
||||||
|
|
||||||
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
||||||
#include <Jolt/Renderer/DebugRenderer.h>
|
#include <Jolt/Renderer/DebugRenderer.h>
|
||||||
|
|
@ -26,8 +28,8 @@ ConvexHullBuilder::Face::~Face()
|
||||||
Edge *e = mFirstEdge;
|
Edge *e = mFirstEdge;
|
||||||
if (e != nullptr)
|
if (e != nullptr)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
Edge *next = e->mNextEdge;
|
Edge *next = e->mNextEdge;
|
||||||
delete e;
|
delete e;
|
||||||
e = next;
|
e = next;
|
||||||
|
|
@ -218,17 +220,16 @@ bool ConvexHullBuilder::AssignPointToFace(int inPositionIdx, const Faces &inFace
|
||||||
// This point is in front of the face, add it to the conflict list
|
// This point is in front of the face, add it to the conflict list
|
||||||
if (best_dist_sq > best_face->mFurthestPointDistanceSq)
|
if (best_dist_sq > best_face->mFurthestPointDistanceSq)
|
||||||
{
|
{
|
||||||
// This point is futher away than any others, update the distance and add point as last point
|
// This point is further away than any others, update the distance and add point as last point
|
||||||
best_face->mFurthestPointDistanceSq = best_dist_sq;
|
best_face->mFurthestPointDistanceSq = best_dist_sq;
|
||||||
best_face->mConflictList.push_back(inPositionIdx);
|
best_face->mConflictList.push_back(inPositionIdx);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Not the furthest point, add it as the before last point
|
// Not the furthest point, add it as the before last point
|
||||||
best_face->mConflictList.push_back(best_face->mConflictList.back());
|
best_face->mConflictList.insert(best_face->mConflictList.begin() + best_face->mConflictList.size() - 1, inPositionIdx);
|
||||||
best_face->mConflictList[best_face->mConflictList.size() - 2] = inPositionIdx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,11 +249,12 @@ float ConvexHullBuilder::DetermineCoplanarDistance() const
|
||||||
int ConvexHullBuilder::GetNumVerticesUsed() const
|
int ConvexHullBuilder::GetNumVerticesUsed() const
|
||||||
{
|
{
|
||||||
UnorderedSet<int> used_verts;
|
UnorderedSet<int> used_verts;
|
||||||
|
used_verts.reserve(UnorderedSet<int>::size_type(mPositions.size()));
|
||||||
for (Face *f : mFaces)
|
for (Face *f : mFaces)
|
||||||
{
|
{
|
||||||
Edge *e = f->mFirstEdge;
|
Edge *e = f->mFirstEdge;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
used_verts.insert(e->mStartIdx);
|
used_verts.insert(e->mStartIdx);
|
||||||
e = e->mNextEdge;
|
e = e->mNextEdge;
|
||||||
} while (e != f->mFirstEdge);
|
} while (e != f->mFirstEdge);
|
||||||
|
|
@ -265,20 +267,20 @@ bool ConvexHullBuilder::ContainsFace(const Array<int> &inIndices) const
|
||||||
for (Face *f : mFaces)
|
for (Face *f : mFaces)
|
||||||
{
|
{
|
||||||
Edge *e = f->mFirstEdge;
|
Edge *e = f->mFirstEdge;
|
||||||
Array<int>::const_iterator index = find(inIndices.begin(), inIndices.end(), e->mStartIdx);
|
Array<int>::const_iterator index = std::find(inIndices.begin(), inIndices.end(), e->mStartIdx);
|
||||||
if (index != inIndices.end())
|
if (index != inIndices.end())
|
||||||
{
|
{
|
||||||
size_t matches = 0;
|
size_t matches = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Check if index matches
|
// Check if index matches
|
||||||
if (*index != e->mStartIdx)
|
if (*index != e->mStartIdx)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Increment number of matches
|
// Increment number of matches
|
||||||
matches++;
|
matches++;
|
||||||
|
|
||||||
// Next index in list of inIndices
|
// Next index in list of inIndices
|
||||||
index++;
|
index++;
|
||||||
if (index == inIndices.end())
|
if (index == inIndices.end())
|
||||||
|
|
@ -287,7 +289,7 @@ bool ConvexHullBuilder::ContainsFace(const Array<int> &inIndices) const
|
||||||
// Next edge
|
// Next edge
|
||||||
e = e->mNextEdge;
|
e = e->mNextEdge;
|
||||||
} while (e != f->mFirstEdge);
|
} while (e != f->mFirstEdge);
|
||||||
|
|
||||||
if (matches == inIndices.size())
|
if (matches == inIndices.size())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +315,7 @@ ConvexHullBuilder::EResult ConvexHullBuilder::Initialize(int inMaxVertices, floa
|
||||||
|
|
||||||
// Increase desired tolerance if accuracy doesn't allow it
|
// Increase desired tolerance if accuracy doesn't allow it
|
||||||
float tolerance_sq = max(coplanar_tolerance_sq, Square(inTolerance));
|
float tolerance_sq = max(coplanar_tolerance_sq, Square(inTolerance));
|
||||||
|
|
||||||
// Find point furthest from the origin
|
// Find point furthest from the origin
|
||||||
int idx1 = -1;
|
int idx1 = -1;
|
||||||
float max_dist_sq = -1.0f;
|
float max_dist_sq = -1.0f;
|
||||||
|
|
@ -408,7 +410,7 @@ ConvexHullBuilder::EResult ConvexHullBuilder::Initialize(int inMaxVertices, floa
|
||||||
Array<Vec3> positions_2d;
|
Array<Vec3> positions_2d;
|
||||||
positions_2d.reserve(mPositions.size());
|
positions_2d.reserve(mPositions.size());
|
||||||
for (Vec3 v : mPositions)
|
for (Vec3 v : mPositions)
|
||||||
positions_2d.push_back(Vec3(base1.Dot(v), base2.Dot(v), 0));
|
positions_2d.emplace_back(base1.Dot(v), base2.Dot(v), 0.0f);
|
||||||
|
|
||||||
// Build hull
|
// Build hull
|
||||||
Array<int> edges_2d;
|
Array<int> edges_2d;
|
||||||
|
|
@ -466,7 +468,7 @@ ConvexHullBuilder::EResult ConvexHullBuilder::Initialize(int inMaxVertices, floa
|
||||||
|
|
||||||
// Ensure the planes are facing outwards
|
// Ensure the planes are facing outwards
|
||||||
if (max_dist < 0.0f)
|
if (max_dist < 0.0f)
|
||||||
swap(idx2, idx3);
|
std::swap(idx2, idx3);
|
||||||
|
|
||||||
// Create tetrahedron
|
// Create tetrahedron
|
||||||
Face *t1 = CreateTriangle(idx1, idx2, idx4);
|
Face *t1 = CreateTriangle(idx1, idx2, idx4);
|
||||||
|
|
@ -552,7 +554,7 @@ ConvexHullBuilder::EResult ConvexHullBuilder::Initialize(int inMaxVertices, floa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap it to the end
|
// Swap it to the end
|
||||||
swap(mCoplanarList[best_idx], mCoplanarList.back());
|
std::swap(mCoplanarList[best_idx], mCoplanarList.back());
|
||||||
|
|
||||||
// Remove it
|
// Remove it
|
||||||
furthest_point_idx = mCoplanarList.back().mPositionIdx;
|
furthest_point_idx = mCoplanarList.back().mPositionIdx;
|
||||||
|
|
@ -646,7 +648,7 @@ void ConvexHullBuilder::AddPoint(Face *inFacingFace, int inIdx, float inCoplanar
|
||||||
Face *f = CreateTriangle(e.mStartIdx, e.mEndIdx, inIdx);
|
Face *f = CreateTriangle(e.mStartIdx, e.mEndIdx, inIdx);
|
||||||
outNewFaces.push_back(f);
|
outNewFaces.push_back(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link edges
|
// Link edges
|
||||||
for (Faces::size_type i = 0; i < outNewFaces.size(); ++i)
|
for (Faces::size_type i = 0; i < outNewFaces.size(); ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -722,8 +724,8 @@ void ConvexHullBuilder::FreeFace(Face *inFace)
|
||||||
// Make sure that this face is not connected
|
// Make sure that this face is not connected
|
||||||
Edge *e = inFace->mFirstEdge;
|
Edge *e = inFace->mFirstEdge;
|
||||||
if (e != nullptr)
|
if (e != nullptr)
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
JPH_ASSERT(e->mNeighbourEdge == nullptr);
|
JPH_ASSERT(e->mNeighbourEdge == nullptr);
|
||||||
e = e->mNextEdge;
|
e = e->mNextEdge;
|
||||||
} while (e != inFace->mFirstEdge);
|
} while (e != inFace->mFirstEdge);
|
||||||
|
|
@ -753,8 +755,8 @@ void ConvexHullBuilder::sUnlinkFace(Face *inFace)
|
||||||
{
|
{
|
||||||
// Unlink from neighbours
|
// Unlink from neighbours
|
||||||
Edge *e = inFace->mFirstEdge;
|
Edge *e = inFace->mFirstEdge;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (e->mNeighbourEdge != nullptr)
|
if (e->mNeighbourEdge != nullptr)
|
||||||
{
|
{
|
||||||
// Validate that neighbour points to us
|
// Validate that neighbour points to us
|
||||||
|
|
@ -958,7 +960,7 @@ void ConvexHullBuilder::MergeDegenerateFace(Face *inFace, Faces &ioAffectedFaces
|
||||||
max_length_sq = length_sq;
|
max_length_sq = length_sq;
|
||||||
longest_edge = e;
|
longest_edge = e;
|
||||||
}
|
}
|
||||||
p1 = p2;
|
p1 = p2;
|
||||||
e = next;
|
e = next;
|
||||||
} while (e != inFace->mFirstEdge);
|
} while (e != inFace->mFirstEdge);
|
||||||
|
|
||||||
|
|
@ -990,7 +992,7 @@ void ConvexHullBuilder::MergeCoplanarOrConcaveFaces(Face *inFace, float inCoplan
|
||||||
float signed_dist_face_centroid_sq = abs(dist_face_centroid) * dist_face_centroid;
|
float signed_dist_face_centroid_sq = abs(dist_face_centroid) * dist_face_centroid;
|
||||||
float face_normal_len_sq = inFace->mNormal.LengthSq();
|
float face_normal_len_sq = inFace->mNormal.LengthSq();
|
||||||
float other_face_normal_len_sq = other_face->mNormal.LengthSq();
|
float other_face_normal_len_sq = other_face->mNormal.LengthSq();
|
||||||
if ((signed_dist_other_face_centroid_sq > -inCoplanarToleranceSq * face_normal_len_sq
|
if ((signed_dist_other_face_centroid_sq > -inCoplanarToleranceSq * face_normal_len_sq
|
||||||
|| signed_dist_face_centroid_sq > -inCoplanarToleranceSq * other_face_normal_len_sq)
|
|| signed_dist_face_centroid_sq > -inCoplanarToleranceSq * other_face_normal_len_sq)
|
||||||
&& inFace->mNormal.Dot(other_face->mNormal) > 0.0f) // Never merge faces that are back to back
|
&& inFace->mNormal.Dot(other_face->mNormal) > 0.0f) // Never merge faces that are back to back
|
||||||
{
|
{
|
||||||
|
|
@ -1007,13 +1009,13 @@ void ConvexHullBuilder::MergeCoplanarOrConcaveFaces(Face *inFace, float inCoplan
|
||||||
|
|
||||||
void ConvexHullBuilder::sMarkAffected(Face *inFace, Faces &ioAffectedFaces)
|
void ConvexHullBuilder::sMarkAffected(Face *inFace, Faces &ioAffectedFaces)
|
||||||
{
|
{
|
||||||
if (find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())
|
if (std::find(ioAffectedFaces.begin(), ioAffectedFaces.end(), inFace) == ioAffectedFaces.end())
|
||||||
ioAffectedFaces.push_back(inFace);
|
ioAffectedFaces.push_back(inFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConvexHullBuilder::RemoveInvalidEdges(Face *inFace, Faces &ioAffectedFaces)
|
void ConvexHullBuilder::RemoveInvalidEdges(Face *inFace, Faces &ioAffectedFaces)
|
||||||
{
|
{
|
||||||
// This marks that the plane needs to be recalculated (we delay this until the end of the
|
// This marks that the plane needs to be recalculated (we delay this until the end of the
|
||||||
// function since we don't use the plane and we want to avoid calculating it multiple times)
|
// function since we don't use the plane and we want to avoid calculating it multiple times)
|
||||||
bool recalculate_plane = false;
|
bool recalculate_plane = false;
|
||||||
|
|
||||||
|
|
@ -1174,8 +1176,8 @@ void ConvexHullBuilder::DumpFace(const Face *inFace) const
|
||||||
Trace("f:0x%p", inFace);
|
Trace("f:0x%p", inFace);
|
||||||
|
|
||||||
const Edge *e = inFace->mFirstEdge;
|
const Edge *e = inFace->mFirstEdge;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
Trace("\te:0x%p { i:%d e:0x%p f:0x%p }", e, e->mStartIdx, e->mNeighbourEdge, e->mNeighbourEdge->mFace);
|
Trace("\te:0x%p { i:%d e:0x%p f:0x%p }", e, e->mStartIdx, e->mNeighbourEdge, e->mNeighbourEdge->mFace);
|
||||||
e = e->mNextEdge;
|
e = e->mNextEdge;
|
||||||
} while (e != inFace->mFirstEdge);
|
} while (e != inFace->mFirstEdge);
|
||||||
|
|
@ -1196,8 +1198,8 @@ void ConvexHullBuilder::ValidateFace(const Face *inFace) const
|
||||||
{
|
{
|
||||||
const Edge *e = inFace->mFirstEdge;
|
const Edge *e = inFace->mFirstEdge;
|
||||||
if (e != nullptr)
|
if (e != nullptr)
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
JPH_ASSERT(e->mNeighbourEdge == nullptr);
|
JPH_ASSERT(e->mNeighbourEdge == nullptr);
|
||||||
e = e->mNextEdge;
|
e = e->mNextEdge;
|
||||||
} while (e != inFace->mFirstEdge);
|
} while (e != inFace->mFirstEdge);
|
||||||
|
|
@ -1207,8 +1209,8 @@ void ConvexHullBuilder::ValidateFace(const Face *inFace) const
|
||||||
int edge_count = 0;
|
int edge_count = 0;
|
||||||
|
|
||||||
const Edge *e = inFace->mFirstEdge;
|
const Edge *e = inFace->mFirstEdge;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Count edge
|
// Count edge
|
||||||
++edge_count;
|
++edge_count;
|
||||||
|
|
||||||
|
|
@ -1275,7 +1277,7 @@ void ConvexHullBuilder::GetCenterOfMassAndVolume(Vec3 &outCenterOfMass, float &o
|
||||||
Vec3 v2 = mPositions[e->mStartIdx];
|
Vec3 v2 = mPositions[e->mStartIdx];
|
||||||
|
|
||||||
for (e = e->mNextEdge; e != f->mFirstEdge; e = e->mNextEdge)
|
for (e = e->mNextEdge; e != f->mFirstEdge; e = e->mNextEdge)
|
||||||
{
|
{
|
||||||
// Fetch the last point of the triangle
|
// Fetch the last point of the triangle
|
||||||
Vec3 v3 = mPositions[e->mStartIdx];
|
Vec3 v3 = mPositions[e->mStartIdx];
|
||||||
|
|
||||||
|
|
@ -1290,7 +1292,7 @@ void ConvexHullBuilder::GetCenterOfMassAndVolume(Vec3 &outCenterOfMass, float &o
|
||||||
|
|
||||||
// Update v2 for next triangle
|
// Update v2 for next triangle
|
||||||
v2 = v3;
|
v2 = v3;
|
||||||
} while (e != f->mFirstEdge);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate center of mass, fall back to average point in case there is no volume (everything is on a plane in this case)
|
// Calculate center of mass, fall back to average point in case there is no volume (everything is on a plane in this case)
|
||||||
|
|
@ -1423,7 +1425,7 @@ void ConvexHullBuilder::DrawWireFace(const Face *inFace, ColorArg inColor) const
|
||||||
const Edge *e = inFace->mFirstEdge;
|
const Edge *e = inFace->mFirstEdge;
|
||||||
RVec3 prev = cDrawScale * (mOffset + mPositions[e->mStartIdx]);
|
RVec3 prev = cDrawScale * (mOffset + mPositions[e->mStartIdx]);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
const Edge *next = e->mNextEdge;
|
const Edge *next = e->mNextEdge;
|
||||||
RVec3 cur = cDrawScale * (mOffset + mPositions[next->mStartIdx]);
|
RVec3 cur = cDrawScale * (mOffset + mPositions[next->mStartIdx]);
|
||||||
DebugRenderer::sInstance->DrawArrow(prev, cur, inColor, 0.01f);
|
DebugRenderer::sInstance->DrawArrow(prev, cur, inColor, 0.01f);
|
||||||
|
|
@ -1451,7 +1453,7 @@ void ConvexHullBuilder::DumpShape() const
|
||||||
|
|
||||||
std::ofstream f;
|
std::ofstream f;
|
||||||
f.open(StringFormat("dumped_shape%d.cpp", shape_no).c_str(), std::ofstream::out | std::ofstream::trunc);
|
f.open(StringFormat("dumped_shape%d.cpp", shape_no).c_str(), std::ofstream::out | std::ofstream::trunc);
|
||||||
if (!f.is_open())
|
if (!f.is_open())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
f << "{\n";
|
f << "{\n";
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// A convex hull builder that tries to create hulls as accurately as possible. Used for offline processing.
|
/// A convex hull builder that tries to create hulls as accurately as possible. Used for offline processing.
|
||||||
class ConvexHullBuilder : public NonCopyable
|
class JPH_EXPORT ConvexHullBuilder : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Forward declare
|
// Forward declare
|
||||||
|
|
@ -75,7 +75,7 @@ public:
|
||||||
Vec3 mCentroid; ///< Center of the face
|
Vec3 mCentroid; ///< Center of the face
|
||||||
ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). The last position in the list is the point that is furthest away from the face.
|
ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). The last position in the list is the point that is furthest away from the face.
|
||||||
Edge * mFirstEdge = nullptr; ///< First edge of this face
|
Edge * mFirstEdge = nullptr; ///< First edge of this face
|
||||||
float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furtest point from the conflict list to the face
|
float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furthest point from the conflict list to the face
|
||||||
bool mRemoved = false; ///< Flag that indicates that face has been removed (face will be freed later)
|
bool mRemoved = false; ///< Flag that indicates that face has been removed (face will be freed later)
|
||||||
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
||||||
int mIteration; ///< Iteration that this face was created
|
int mIteration; ///< Iteration that this face was created
|
||||||
|
|
@ -144,7 +144,7 @@ private:
|
||||||
public:
|
public:
|
||||||
Edge * mNeighbourEdge; ///< Edge that this edge is connected to
|
Edge * mNeighbourEdge; ///< Edge that this edge is connected to
|
||||||
int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge
|
int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge
|
||||||
int mEndIdx; ///< Vertex index in mPosition that indicats the end vertex of this edge
|
int mEndIdx; ///< Vertex index in mPosition that indicates the end vertex of this edge
|
||||||
};
|
};
|
||||||
|
|
||||||
// Private typedefs
|
// Private typedefs
|
||||||
|
|
@ -207,7 +207,7 @@ private:
|
||||||
/// Merges inFace with a neighbour if it is degenerate (a sliver)
|
/// Merges inFace with a neighbour if it is degenerate (a sliver)
|
||||||
void MergeDegenerateFace(Face *inFace, Faces &ioAffectedFaces);
|
void MergeDegenerateFace(Face *inFace, Faces &ioAffectedFaces);
|
||||||
|
|
||||||
/// Merges any coplanar as well as neighbours that form a non-convex edge into inFace.
|
/// Merges any coplanar as well as neighbours that form a non-convex edge into inFace.
|
||||||
/// Faces are considered coplanar if the distance^2 of the other face's centroid is smaller than inToleranceSq.
|
/// Faces are considered coplanar if the distance^2 of the other face's centroid is smaller than inToleranceSq.
|
||||||
void MergeCoplanarOrConcaveFaces(Face *inFace, float inToleranceSq, Faces &ioAffectedFaces);
|
void MergeCoplanarOrConcaveFaces(Face *inFace, float inToleranceSq, Faces &ioAffectedFaces);
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ private:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const Positions & mPositions; ///< List of positions (some of them are part of the hull)
|
const Positions & mPositions; ///< List of positions (some of them are part of the hull)
|
||||||
Faces mFaces; ///< List of faces that are part of the hull (if !mRemoved)
|
Faces mFaces; ///< List of faces that are part of the hull (if !mRemoved)
|
||||||
|
|
||||||
struct Coplanar
|
struct Coplanar
|
||||||
{
|
{
|
||||||
|
|
@ -267,7 +267,7 @@ private:
|
||||||
CoplanarList mCoplanarList; ///< List of positions that are coplanar to a face but outside of the face, these are added to the hull at the end
|
CoplanarList mCoplanarList; ///< List of positions that are coplanar to a face but outside of the face, these are added to the hull at the end
|
||||||
|
|
||||||
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
#ifdef JPH_CONVEX_BUILDER_DEBUG
|
||||||
int mIteration; ///< Number of iterations we've had so far (for debug purposes)
|
int mIteration; ///< Number of iterations we've had so far (for debug purposes)
|
||||||
mutable RVec3 mOffset; ///< Offset to use for state drawing
|
mutable RVec3 mOffset; ///< Offset to use for state drawing
|
||||||
Vec3 mDelta; ///< Delta offset between next states
|
Vec3 mDelta; ///< Delta offset between next states
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ void ConvexHullBuilder2D::Edge::CalculateNormalAndCenter(const Vec3 *inPositions
|
||||||
|
|
||||||
// Center of edge
|
// Center of edge
|
||||||
mCenter = 0.5f * (p1 + p2);
|
mCenter = 0.5f * (p1 + p2);
|
||||||
|
|
||||||
// Create outward pointing normal.
|
// Create outward pointing normal.
|
||||||
// We have two choices for the normal (which satisfies normal . edge = 0):
|
// We have two choices for the normal (which satisfies normal . edge = 0):
|
||||||
// normal1 = (-edge.y, edge.x, 0)
|
// normal1 = (-edge.y, edge.x, 0)
|
||||||
// normal2 = (edge.y, -edge.x, 0)
|
// normal2 = (edge.y, -edge.x, 0)
|
||||||
// We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.
|
// We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.
|
||||||
Vec3 edge = p2 - p1;
|
Vec3 edge = p2 - p1;
|
||||||
|
|
@ -99,7 +99,7 @@ void ConvexHullBuilder2D::ValidateEdges() const
|
||||||
|
|
||||||
++count;
|
++count;
|
||||||
edge = edge->mNextEdge;
|
edge = edge->mNextEdge;
|
||||||
} while (edge != mFirstEdge);
|
} while (edge != mFirstEdge);
|
||||||
|
|
||||||
// Validate that count matches
|
// Validate that count matches
|
||||||
JPH_ASSERT(count == mNumEdges);
|
JPH_ASSERT(count == mNumEdges);
|
||||||
|
|
@ -135,15 +135,14 @@ void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge
|
||||||
{
|
{
|
||||||
if (best_dist_sq > best_edge->mFurthestPointDistanceSq)
|
if (best_dist_sq > best_edge->mFurthestPointDistanceSq)
|
||||||
{
|
{
|
||||||
// This point is futher away than any others, update the distance and add point as last point
|
// This point is further away than any others, update the distance and add point as last point
|
||||||
best_edge->mFurthestPointDistanceSq = best_dist_sq;
|
best_edge->mFurthestPointDistanceSq = best_dist_sq;
|
||||||
best_edge->mConflictList.push_back(inPositionIdx);
|
best_edge->mConflictList.push_back(inPositionIdx);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Not the furthest point, add it as the before last point
|
// Not the furthest point, add it as the before last point
|
||||||
best_edge->mConflictList.push_back(best_edge->mConflictList.back());
|
best_edge->mConflictList.insert(best_edge->mConflictList.begin() + best_edge->mConflictList.size() - 1, inPositionIdx);
|
||||||
best_edge->mConflictList[best_edge->mConflictList.size() - 2] = inPositionIdx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +169,7 @@ ConvexHullBuilder2D::EResult ConvexHullBuilder2D::Initialize(int inIdx1, int inI
|
||||||
// Start with the initial indices in counter clockwise order
|
// Start with the initial indices in counter clockwise order
|
||||||
float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();
|
float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();
|
||||||
if (z < 0.0f)
|
if (z < 0.0f)
|
||||||
swap(inIdx1, inIdx2);
|
std::swap(inIdx1, inIdx2);
|
||||||
|
|
||||||
// Create and link edges
|
// Create and link edges
|
||||||
Edge *e1 = new Edge(inIdx1);
|
Edge *e1 = new Edge(inIdx1);
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,16 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// A convex hull builder that tries to create 2D hulls as accurately as possible. Used for offline processing.
|
/// A convex hull builder that tries to create 2D hulls as accurately as possible. Used for offline processing.
|
||||||
class ConvexHullBuilder2D : public NonCopyable
|
class JPH_EXPORT ConvexHullBuilder2D : public NonCopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Positions = Array<Vec3>;
|
using Positions = Array<Vec3>;
|
||||||
using Edges = Array<int>;
|
using Edges = Array<int>;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// @param inPositions Positions used to make the hull. Uses X and Y component of Vec3 only!
|
/// @param inPositions Positions used to make the hull. Uses X and Y component of Vec3 only!
|
||||||
explicit ConvexHullBuilder2D(const Positions &inPositions);
|
explicit ConvexHullBuilder2D(const Positions &inPositions);
|
||||||
|
|
||||||
/// Destructor
|
/// Destructor
|
||||||
~ConvexHullBuilder2D();
|
~ConvexHullBuilder2D();
|
||||||
|
|
@ -86,10 +86,10 @@ private:
|
||||||
Vec3 mNormal; ///< Normal of the edge (not normalized)
|
Vec3 mNormal; ///< Normal of the edge (not normalized)
|
||||||
Vec3 mCenter; ///< Center of the edge
|
Vec3 mCenter; ///< Center of the edge
|
||||||
ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). Last entry is the one furthest away from the edge, remainder is unsorted.
|
ConflictList mConflictList; ///< Positions associated with this edge (that are closest to this edge). Last entry is the one furthest away from the edge, remainder is unsorted.
|
||||||
Edge * mPrevEdge = nullptr; ///< Previous edge in cicular list
|
Edge * mPrevEdge = nullptr; ///< Previous edge in circular list
|
||||||
Edge * mNextEdge = nullptr; ///< Next edge in circular list
|
Edge * mNextEdge = nullptr; ///< Next edge in circular list
|
||||||
int mStartIdx; ///< Position index of start of this edge
|
int mStartIdx; ///< Position index of start of this edge
|
||||||
float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furtest point from the conflict list to the edge
|
float mFurthestPointDistanceSq = 0.0f; ///< Squared distance of furthest point from the conflict list to the edge
|
||||||
};
|
};
|
||||||
|
|
||||||
const Positions & mPositions; ///< List of positions (some of them are part of the hull)
|
const Positions & mPositions; ///< List of positions (some of them are part of the hull)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ struct AddConvexRadius
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the support vector for this convex shape.
|
/// Calculate the support vector for this convex shape.
|
||||||
Vec3 GetSupport(Vec3Arg inDirection) const
|
Vec3 GetSupport(Vec3Arg inDirection) const
|
||||||
{
|
{
|
||||||
float length = inDirection.Length();
|
float length = inDirection.Length();
|
||||||
|
|
@ -71,7 +71,7 @@ struct MinkowskiDifference
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the support vector for this convex shape.
|
/// Calculate the support vector for this convex shape.
|
||||||
Vec3 GetSupport(Vec3Arg inDirection) const
|
Vec3 GetSupport(Vec3Arg inDirection) const
|
||||||
{
|
{
|
||||||
return mObjectA.GetSupport(inDirection) - mObjectB.GetSupport(-inDirection);
|
return mObjectA.GetSupport(inDirection) - mObjectB.GetSupport(-inDirection);
|
||||||
|
|
@ -159,7 +159,7 @@ struct PolygonConvexSupport
|
||||||
{
|
{
|
||||||
Vec3 support_point = mVertices[0];
|
Vec3 support_point = mVertices[0];
|
||||||
float best_dot = mVertices[0].Dot(inDirection);
|
float best_dot = mVertices[0].Dot(inDirection);
|
||||||
|
|
||||||
for (typename VERTEX_ARRAY::const_iterator v = mVertices.begin() + 1; v < mVertices.end(); ++v)
|
for (typename VERTEX_ARRAY::const_iterator v = mVertices.begin() + 1; v < mVertices.end(); ++v)
|
||||||
{
|
{
|
||||||
float dot = v->Dot(inDirection);
|
float dot = v->Dot(inDirection);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
//#define JPH_EPA_CONVEX_BUILDER_DRAW
|
//#define JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
|
||||||
#include <Jolt/Core/NonCopyable.h>
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
|
#include <Jolt/Core/BinaryHeap.h>
|
||||||
|
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
#include <Jolt/Renderer/DebugRenderer.h>
|
#include <Jolt/Renderer/DebugRenderer.h>
|
||||||
|
|
@ -38,7 +39,7 @@ public:
|
||||||
// Constants
|
// Constants
|
||||||
static constexpr int cMaxEdgeLength = 128; ///< Max number of edges in FindEdge
|
static constexpr int cMaxEdgeLength = 128; ///< Max number of edges in FindEdge
|
||||||
static constexpr float cMinTriangleArea = 1.0e-10f; ///< Minimum area of a triangle before, if smaller than this it will not be added to the priority queue
|
static constexpr float cMinTriangleArea = 1.0e-10f; ///< Minimum area of a triangle before, if smaller than this it will not be added to the priority queue
|
||||||
static constexpr float cBarycentricEpsilon = 1.0e-3f; ///< Epsilon value used to determine if a point is in the interior of a triangle
|
static constexpr float cBarycentricEpsilon = 1.0e-3f; ///< Epsilon value used to determine if a point is in the interior of a triangle
|
||||||
|
|
||||||
// Forward declare
|
// Forward declare
|
||||||
class Triangle;
|
class Triangle;
|
||||||
|
|
@ -50,7 +51,7 @@ public:
|
||||||
/// Information about neighbouring triangle
|
/// Information about neighbouring triangle
|
||||||
Triangle * mNeighbourTriangle; ///< Triangle that neighbours this triangle
|
Triangle * mNeighbourTriangle; ///< Triangle that neighbours this triangle
|
||||||
int mNeighbourEdge; ///< Index in mEdge that specifies edge that this Edge is connected to
|
int mNeighbourEdge; ///< Index in mEdge that specifies edge that this Edge is connected to
|
||||||
|
|
||||||
int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge
|
int mStartIdx; ///< Vertex index in mPositions that indicates the start vertex of this edge
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -152,7 +153,7 @@ public:
|
||||||
{
|
{
|
||||||
// Destruct triangle
|
// Destruct triangle
|
||||||
inT->~Triangle();
|
inT->~Triangle();
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
memset(inT, 0xcd, sizeof(Triangle));
|
memset(inT, 0xcd, sizeof(Triangle));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -197,7 +198,7 @@ public:
|
||||||
inT->mInQueue = true;
|
inT->mInQueue = true;
|
||||||
|
|
||||||
// Resort heap
|
// Resort heap
|
||||||
std::push_heap(begin(), end(), sTriangleSorter);
|
BinaryHeapPush(begin(), end(), sTriangleSorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peek the next closest triangle without removing it
|
/// Peek the next closest triangle without removing it
|
||||||
|
|
@ -209,8 +210,8 @@ public:
|
||||||
/// Get next closest triangle
|
/// Get next closest triangle
|
||||||
Triangle * PopClosest()
|
Triangle * PopClosest()
|
||||||
{
|
{
|
||||||
// Move largest to end
|
// Move closest to end
|
||||||
std::pop_heap(begin(), end(), sTriangleSorter);
|
BinaryHeapPop(begin(), end(), sTriangleSorter);
|
||||||
|
|
||||||
// Remove last triangle
|
// Remove last triangle
|
||||||
Triangle *t = back();
|
Triangle *t = back();
|
||||||
|
|
@ -309,7 +310,7 @@ public:
|
||||||
|
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
// Draw new support point
|
// Draw new support point
|
||||||
DrawMarker(pos, Color::sYellow, 1.0f);
|
DrawMarker(pos, Color::sYellow, 1.0f);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE
|
#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE
|
||||||
|
|
@ -337,7 +338,7 @@ public:
|
||||||
|| nt->mClosestLenSq < 0.0f) // For when the origin is not inside the hull yet
|
|| nt->mClosestLenSq < 0.0f) // For when the origin is not inside the hull yet
|
||||||
mTriangleQueue.push_back(nt);
|
mTriangleQueue.push_back(nt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link edges
|
// Link edges
|
||||||
for (int i = 0; i < num_edges; ++i)
|
for (int i = 0; i < num_edges; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -555,19 +556,19 @@ private:
|
||||||
DrawState();
|
DrawState();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// When we start with two triangles facing away from each other and adding a point that is on the plane,
|
// When we start with two triangles facing away from each other and adding a point that is on the plane,
|
||||||
// sometimes we consider the point in front of both causing both triangles to be removed resulting in an empty edge list.
|
// sometimes we consider the point in front of both causing both triangles to be removed resulting in an empty edge list.
|
||||||
// In this case we fail to add the point which will result in no collision reported (the shapes are contacting in 1 point so there's 0 penetration)
|
// In this case we fail to add the point which will result in no collision reported (the shapes are contacting in 1 point so there's 0 penetration)
|
||||||
return outEdges.size() >= 3;
|
return outEdges.size() >= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE
|
#ifdef JPH_EPA_CONVEX_BUILDER_VALIDATE
|
||||||
/// Check consistency of 1 triangle
|
/// Check consistency of 1 triangle
|
||||||
void ValidateTriangle(const Triangle *inT) const
|
void ValidateTriangle(const Triangle *inT) const
|
||||||
{
|
{
|
||||||
if (inT->mRemoved)
|
if (inT->mRemoved)
|
||||||
{
|
{
|
||||||
// Valdiate that removed triangles are not connected to anything
|
// Validate that removed triangles are not connected to anything
|
||||||
for (const Edge &my_edge : inT->mEdge)
|
for (const Edge &my_edge : inT->mEdge)
|
||||||
JPH_ASSERT(my_edge.mNeighbourTriangle == nullptr);
|
JPH_ASSERT(my_edge.mNeighbourTriangle == nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -649,6 +650,23 @@ public:
|
||||||
mOffset += Vec3(max_x - min_x + 0.5f, 0.0f, 0.0f);
|
mOffset += Vec3(max_x - min_x + 0.5f, 0.0f, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw a label to indicate the next stage in the algorithm
|
||||||
|
void DrawLabel(const string_view &inText)
|
||||||
|
{
|
||||||
|
DebugRenderer::sInstance->DrawText3D(cDrawScale * mOffset, inText, Color::sWhite, 0.1f * cDrawScale);
|
||||||
|
|
||||||
|
mOffset += Vec3(5.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw geometry for debugging purposes
|
||||||
|
void DrawGeometry(const DebugRenderer::GeometryRef &inGeometry, ColorArg inColor)
|
||||||
|
{
|
||||||
|
RMat44 origin = RMat44::sScale(Vec3::sReplicate(cDrawScale)) * RMat44::sTranslation(mOffset);
|
||||||
|
DebugRenderer::sInstance->DrawGeometry(origin, inGeometry->mBounds.Transformed(origin), inGeometry->mBounds.GetExtent().LengthSq(), inColor, inGeometry);
|
||||||
|
|
||||||
|
mOffset += Vec3(inGeometry->mBounds.GetSize().GetX(), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw a triangle for debugging purposes
|
/// Draw a triangle for debugging purposes
|
||||||
void DrawWireTriangle(const Triangle &inTriangle, ColorArg inColor)
|
void DrawWireTriangle(const Triangle &inTriangle, ColorArg inColor)
|
||||||
{
|
{
|
||||||
|
|
@ -675,16 +693,16 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TriangleFactory mFactory; ///< Factory to create new triangles and remove old ones
|
TriangleFactory mFactory; ///< Factory to create new triangles and remove old ones
|
||||||
const Points & mPositions; ///< List of positions (some of them are part of the hull)
|
const Points & mPositions; ///< List of positions (some of them are part of the hull)
|
||||||
TriangleQueue mTriangleQueue; ///< List of triangles that are part of the hull that still need to be checked (if !mRemoved)
|
TriangleQueue mTriangleQueue; ///< List of triangles that are part of the hull that still need to be checked (if !mRemoved)
|
||||||
|
|
||||||
#if defined(JPH_EPA_CONVEX_BUILDER_VALIDATE) || defined(JPH_EPA_CONVEX_BUILDER_DRAW)
|
#if defined(JPH_EPA_CONVEX_BUILDER_VALIDATE) || defined(JPH_EPA_CONVEX_BUILDER_DRAW)
|
||||||
Triangles mTriangles; ///< The list of all triangles in this hull (for debug purposes)
|
Triangles mTriangles; ///< The list of all triangles in this hull (for debug purposes)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
int mIteration; ///< Number of iterations we've had so far (for debug purposes)
|
int mIteration; ///< Number of iterations we've had so far (for debug purposes)
|
||||||
RVec3 mOffset; ///< Offset to use for state drawing
|
RVec3 mOffset; ///< Offset to use for state drawing
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
@ -767,7 +785,7 @@ EPAConvexHullBuilder::Triangle::Triangle(int inIdx0, int inIdx1, int inIdx2, con
|
||||||
mLambda[0] = l0;
|
mLambda[0] = l0;
|
||||||
mLambda[1] = l1;
|
mLambda[1] = l1;
|
||||||
mLambdaRelativeTo0 = true;
|
mLambdaRelativeTo0 = true;
|
||||||
|
|
||||||
// Check if closest point is interior to the triangle. For a convex hull which contains the origin each face must contain the origin, but because
|
// Check if closest point is interior to the triangle. For a convex hull which contains the origin each face must contain the origin, but because
|
||||||
// our faces are triangles, we can have multiple coplanar triangles and only 1 will have the origin as an interior point. We want to use this triangle
|
// our faces are triangles, we can have multiple coplanar triangles and only 1 will have the origin as an interior point. We want to use this triangle
|
||||||
// to calculate the contact points because it gives the most accurate results, so we will only add these triangles to the priority queue.
|
// to calculate the contact points because it gives the most accurate results, so we will only add these triangles to the priority queue.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
#include <Jolt/Geometry/GJKClosestPoint.h>
|
#include <Jolt/Geometry/GJKClosestPoint.h>
|
||||||
#include <Jolt/Geometry/EPAConvexHullBuilder.h>
|
#include <Jolt/Geometry/EPAConvexHullBuilder.h>
|
||||||
|
|
||||||
|
//#define JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Implementation of Expanding Polytope Algorithm as described in:
|
/// Implementation of Expanding Polytope Algorithm as described in:
|
||||||
|
|
@ -45,6 +47,11 @@ private:
|
||||||
/// The GJK algorithm, used to start the EPA algorithm
|
/// The GJK algorithm, used to start the EPA algorithm
|
||||||
GJKClosestPoint mGJK;
|
GJKClosestPoint mGJK;
|
||||||
|
|
||||||
|
#ifdef JPH_ENABLE_ASSERTS
|
||||||
|
/// Tolerance as passed to the GJK algorithm, used for asserting.
|
||||||
|
float mGJKTolerance = 0.0f;
|
||||||
|
#endif // JPH_ENABLE_ASSERTS
|
||||||
|
|
||||||
/// A list of support points for the EPA algorithm
|
/// A list of support points for the EPA algorithm
|
||||||
class SupportPoints
|
class SupportPoints
|
||||||
{
|
{
|
||||||
|
|
@ -68,11 +75,11 @@ private:
|
||||||
mY.push_back(w);
|
mY.push_back(w);
|
||||||
mP[outIndex] = p;
|
mP[outIndex] = p;
|
||||||
mQ[outIndex] = q;
|
mQ[outIndex] = q;
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Return code for GetPenetrationDepthStepGJK
|
/// Return code for GetPenetrationDepthStepGJK
|
||||||
enum class EStatus
|
enum class EStatus
|
||||||
|
|
@ -81,7 +88,7 @@ public:
|
||||||
Colliding, ///< Returned if the objects penetrate
|
Colliding, ///< Returned if the objects penetrate
|
||||||
Indeterminate ///< Returned if the objects penetrate further than the convex radius. In this case you need to call GetPenetrationDepthStepEPA to get the actual penetration depth.
|
Indeterminate ///< Returned if the objects penetrate further than the convex radius. In this case you need to call GetPenetrationDepthStepEPA to get the actual penetration depth.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Calculates penetration depth between two objects, first step of two (the GJK step)
|
/// Calculates penetration depth between two objects, first step of two (the GJK step)
|
||||||
///
|
///
|
||||||
/// @param inAExcludingConvexRadius Object A without convex radius.
|
/// @param inAExcludingConvexRadius Object A without convex radius.
|
||||||
|
|
@ -91,14 +98,20 @@ public:
|
||||||
/// @param ioV Pass in previously returned value or (1, 0, 0). On return this value is changed to direction to move B out of collision along the shortest path (magnitude is meaningless).
|
/// @param ioV Pass in previously returned value or (1, 0, 0). On return this value is changed to direction to move B out of collision along the shortest path (magnitude is meaningless).
|
||||||
/// @param inTolerance Minimal distance before A and B are considered colliding.
|
/// @param inTolerance Minimal distance before A and B are considered colliding.
|
||||||
/// @param outPointA Position on A that has the least amount of penetration.
|
/// @param outPointA Position on A that has the least amount of penetration.
|
||||||
/// @param outPointB Position on B that has the least amount of penetration.
|
/// @param outPointB Position on B that has the least amount of penetration.
|
||||||
/// Use |outPointB - outPointA| to get the distance of penetration.
|
/// Use |outPointB - outPointA| to get the distance of penetration.
|
||||||
template <typename AE, typename BE>
|
template <typename AE, typename BE>
|
||||||
EStatus GetPenetrationDepthStepGJK(const AE &inAExcludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, float inConvexRadiusB, float inTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
|
EStatus GetPenetrationDepthStepGJK(const AE &inAExcludingConvexRadius, float inConvexRadiusA, const BE &inBExcludingConvexRadius, float inConvexRadiusB, float inTolerance, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)
|
||||||
{
|
{
|
||||||
JPH_PROFILE_FUNCTION();
|
JPH_PROFILE_FUNCTION();
|
||||||
|
|
||||||
// Don't supply a zero ioV, we only want to get points on the hull of the Minkowsky sum and not internal points
|
JPH_IF_ENABLE_ASSERTS(mGJKTolerance = inTolerance;)
|
||||||
|
|
||||||
|
// Don't supply a zero ioV, we only want to get points on the hull of the Minkowsky sum and not internal points.
|
||||||
|
//
|
||||||
|
// Note that if the assert below triggers, it is very likely that you have a MeshShape that contains a degenerate triangle (e.g. a sliver).
|
||||||
|
// Go up a couple of levels in the call stack to see if we're indeed testing a triangle and if it is degenerate.
|
||||||
|
// If this is the case then fix the triangles you supply to the MeshShape.
|
||||||
JPH_ASSERT(!ioV.IsNearZero());
|
JPH_ASSERT(!ioV.IsNearZero());
|
||||||
|
|
||||||
// Get closest points
|
// Get closest points
|
||||||
|
|
@ -108,7 +121,7 @@ public:
|
||||||
if (closest_points_dist_sq > combined_radius_sq)
|
if (closest_points_dist_sq > combined_radius_sq)
|
||||||
{
|
{
|
||||||
// No collision
|
// No collision
|
||||||
return EStatus::NotColliding;
|
return EStatus::NotColliding;
|
||||||
}
|
}
|
||||||
if (closest_points_dist_sq > 0.0f)
|
if (closest_points_dist_sq > 0.0f)
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +165,7 @@ public:
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
// 1 vertex, which must be at the origin, which is useless for our purpose
|
// 1 vertex, which must be at the origin, which is useless for our purpose
|
||||||
JPH_ASSERT(support_points.mY[0].IsNearZero(1.0e-8f));
|
JPH_ASSERT(support_points.mY[0].IsNearZero(Square(mGJKTolerance)));
|
||||||
support_points.mY.pop_back();
|
support_points.mY.pop_back();
|
||||||
|
|
||||||
// Add support points in 4 directions to form a tetrahedron around the origin
|
// Add support points in 4 directions to form a tetrahedron around the origin
|
||||||
|
|
@ -195,6 +208,12 @@ public:
|
||||||
// Create hull out of the initial points
|
// Create hull out of the initial points
|
||||||
JPH_ASSERT(support_points.mY.size() >= 3);
|
JPH_ASSERT(support_points.mY.size() >= 3);
|
||||||
EPAConvexHullBuilder hull(support_points.mY);
|
EPAConvexHullBuilder hull(support_points.mY);
|
||||||
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
hull.DrawLabel("Build initial hull");
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("Init: num_points = %u", (uint)support_points.mY.size());
|
||||||
|
#endif
|
||||||
hull.Initialize(0, 1, 2);
|
hull.Initialize(0, 1, 2);
|
||||||
for (typename Points::size_type i = 3; i < support_points.mY.size(); ++i)
|
for (typename Points::size_type i = 3; i < support_points.mY.size(); ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -205,13 +224,24 @@ public:
|
||||||
EPAConvexHullBuilder::NewTriangles new_triangles;
|
EPAConvexHullBuilder::NewTriangles new_triangles;
|
||||||
if (!hull.AddPoint(t, i, FLT_MAX, new_triangles))
|
if (!hull.AddPoint(t, i, FLT_MAX, new_triangles))
|
||||||
{
|
{
|
||||||
// We can't recover from a failure to add a point to the hull because the old triangles have been unlinked already.
|
// We can't recover from a failure to add a point to the hull because the old triangles have been unlinked already.
|
||||||
// Assume no collision. This can happen if the shapes touch in 1 point (or plane) in which case the hull is degenerate.
|
// Assume no collision. This can happen if the shapes touch in 1 point (or plane) in which case the hull is degenerate.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
hull.DrawLabel("Complete hull");
|
||||||
|
|
||||||
|
// Generate the hull of the Minkowski difference for visualization
|
||||||
|
MinkowskiDifference diff(inAIncludingConvexRadius, inBIncludingConvexRadius);
|
||||||
|
DebugRenderer::GeometryRef geometry = DebugRenderer::sInstance->CreateTriangleGeometryForConvex([&diff](Vec3Arg inDirection) { return diff.GetSupport(inDirection); });
|
||||||
|
hull.DrawGeometry(geometry, Color::sYellow);
|
||||||
|
|
||||||
|
hull.DrawLabel("Ensure origin in hull");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Loop until we are sure that the origin is inside the hull
|
// Loop until we are sure that the origin is inside the hull
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
|
@ -235,6 +265,17 @@ public:
|
||||||
if (t->mClosestLenSq >= 0.0f)
|
if (t->mClosestLenSq >= 0.0f)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
hull.DrawLabel("Next iteration");
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("EncapsulateOrigin: verts = (%d, %d, %d), closest_dist_sq = %g, centroid = (%g, %g, %g), normal = (%g, %g, %g)",
|
||||||
|
t->mEdge[0].mStartIdx, t->mEdge[1].mStartIdx, t->mEdge[2].mStartIdx,
|
||||||
|
t->mClosestLenSq,
|
||||||
|
t->mCentroid.GetX(), t->mCentroid.GetY(), t->mCentroid.GetZ(),
|
||||||
|
t->mNormal.GetX(), t->mNormal.GetY(), t->mNormal.GetZ());
|
||||||
|
#endif
|
||||||
|
|
||||||
// Remove the triangle from the queue before we start adding new ones (which may result in a new closest triangle at the front of the queue)
|
// Remove the triangle from the queue before we start adding new ones (which may result in a new closest triangle at the front of the queue)
|
||||||
hull.PopClosestTriangleFromQueue();
|
hull.PopClosestTriangleFromQueue();
|
||||||
|
|
||||||
|
|
@ -254,21 +295,28 @@ public:
|
||||||
if (!t->IsFacing(w) || !hull.AddPoint(t, new_index, FLT_MAX, new_triangles))
|
if (!t->IsFacing(w) || !hull.AddPoint(t, new_index, FLT_MAX, new_triangles))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If the triangle was removed we can free it now
|
// The triangle is facing the support point "w" and can now be safely removed
|
||||||
if (t->mRemoved)
|
JPH_ASSERT(t->mRemoved);
|
||||||
hull.FreeTriangle(t);
|
hull.FreeTriangle(t);
|
||||||
|
|
||||||
// If we run out of triangles or points, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
|
// If we run out of triangles or points, we couldn't include the origin in the hull so there must be very little penetration and we report no collision.
|
||||||
if (!hull.HasNextTriangle() || support_points.mY.size() >= cMaxPointsToIncludeOriginInHull)
|
if (!hull.HasNextTriangle() || support_points.mY.size() >= cMaxPointsToIncludeOriginInHull)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
hull.DrawLabel("Main algorithm");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Current closest distance to origin
|
// Current closest distance to origin
|
||||||
float closest_dist_sq = FLT_MAX;
|
float closest_dist_sq = FLT_MAX;
|
||||||
|
|
||||||
// Remember last good triangle
|
// Remember last good triangle
|
||||||
Triangle *last = nullptr;
|
Triangle *last = nullptr;
|
||||||
|
|
||||||
|
// If we want to flip the penetration depth
|
||||||
|
bool flip_v_sign = false;
|
||||||
|
|
||||||
// Loop until closest point found
|
// Loop until closest point found
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
@ -282,6 +330,16 @@ public:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
|
hull.DrawLabel("Next iteration");
|
||||||
|
#endif
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("FindClosest: verts = (%d, %d, %d), closest_len_sq = %g, centroid = (%g, %g, %g), normal = (%g, %g, %g)",
|
||||||
|
t->mEdge[0].mStartIdx, t->mEdge[1].mStartIdx, t->mEdge[2].mStartIdx,
|
||||||
|
t->mClosestLenSq,
|
||||||
|
t->mCentroid.GetX(), t->mCentroid.GetY(), t->mCentroid.GetZ(),
|
||||||
|
t->mNormal.GetX(), t->mNormal.GetY(), t->mNormal.GetZ());
|
||||||
|
#endif
|
||||||
// Check if next triangle is further away than closest point, we've found the closest point
|
// Check if next triangle is further away than closest point, we've found the closest point
|
||||||
if (t->mClosestLenSq >= closest_dist_sq)
|
if (t->mClosestLenSq >= closest_dist_sq)
|
||||||
break;
|
break;
|
||||||
|
|
@ -296,7 +354,7 @@ public:
|
||||||
// and this way we do less calculations and lose less precision
|
// and this way we do less calculations and lose less precision
|
||||||
int new_index;
|
int new_index;
|
||||||
Vec3 w = support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, t->mNormal, new_index);
|
Vec3 w = support_points.Add(inAIncludingConvexRadius, inBIncludingConvexRadius, t->mNormal, new_index);
|
||||||
|
|
||||||
// Project w onto the triangle normal
|
// Project w onto the triangle normal
|
||||||
float dot = t->mNormal.Dot(w);
|
float dot = t->mNormal.Dot(w);
|
||||||
|
|
||||||
|
|
@ -306,8 +364,13 @@ public:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Get the distance squared (along normal) to the support point
|
// Get the distance squared (along normal) to the support point
|
||||||
float dist_sq = dot * dot / t->mNormal.LengthSq();
|
float dist_sq = Square(dot) / t->mNormal.LengthSq();
|
||||||
|
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("FindClosest: w = (%g, %g, %g), dot = %g, dist_sq = %g",
|
||||||
|
w.GetX(), w.GetY(), w.GetZ(),
|
||||||
|
dot, dist_sq);
|
||||||
|
#endif
|
||||||
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
// Draw the point that we're adding
|
// Draw the point that we're adding
|
||||||
hull.DrawMarker(w, Color::sPurple, 1.0f);
|
hull.DrawMarker(w, Color::sPurple, 1.0f);
|
||||||
|
|
@ -317,19 +380,34 @@ public:
|
||||||
|
|
||||||
// If the error became small enough, we've converged
|
// If the error became small enough, we've converged
|
||||||
if (dist_sq - t->mClosestLenSq < t->mClosestLenSq * inTolerance)
|
if (dist_sq - t->mClosestLenSq < t->mClosestLenSq * inTolerance)
|
||||||
|
{
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("Converged");
|
||||||
|
#endif // JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep track of the minimum distance
|
// Keep track of the minimum distance
|
||||||
closest_dist_sq = min(closest_dist_sq, dist_sq);
|
closest_dist_sq = min(closest_dist_sq, dist_sq);
|
||||||
|
|
||||||
// If the triangle thinks this point is not front facing, we've reached numerical precision and we're done
|
// If the triangle thinks this point is not front facing, we've reached numerical precision and we're done
|
||||||
if (!t->IsFacing(w))
|
if (!t->IsFacing(w))
|
||||||
|
{
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("Not facing triangle");
|
||||||
|
#endif // JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Add point to hull
|
// Add point to hull
|
||||||
EPAConvexHullBuilder::NewTriangles new_triangles;
|
EPAConvexHullBuilder::NewTriangles new_triangles;
|
||||||
if (!hull.AddPoint(t, new_index, closest_dist_sq, new_triangles))
|
if (!hull.AddPoint(t, new_index, closest_dist_sq, new_triangles))
|
||||||
|
{
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("Could not add point");
|
||||||
|
#endif // JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// If the hull is starting to form defects then we're reaching numerical precision and we have to stop
|
// If the hull is starting to form defects then we're reaching numerical precision and we have to stop
|
||||||
bool has_defect = false;
|
bool has_defect = false;
|
||||||
|
|
@ -340,16 +418,32 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (has_defect)
|
if (has_defect)
|
||||||
|
{
|
||||||
|
#ifdef JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
Trace("Has defect");
|
||||||
|
#endif // JPH_EPA_PENETRATION_DEPTH_DEBUG
|
||||||
|
// When the hull has defects it is possible that the origin has been classified on the wrong side of the triangle
|
||||||
|
// so we do an additional check to see if the penetration in the -triangle normal direction is smaller than
|
||||||
|
// the penetration in the triangle normal direction. If so we must flip the sign of the penetration depth.
|
||||||
|
Vec3 w2 = inAIncludingConvexRadius.GetSupport(-t->mNormal) - inBIncludingConvexRadius.GetSupport(t->mNormal);
|
||||||
|
float dot2 = -t->mNormal.Dot(w2);
|
||||||
|
if (dot2 < dot)
|
||||||
|
flip_v_sign = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (hull.HasNextTriangle() && support_points.mY.size() < cMaxPoints);
|
while (hull.HasNextTriangle() && support_points.mY.size() < cMaxPoints);
|
||||||
|
|
||||||
// Determine closest points, if last == null it means the hull was a plane so there's no penetration
|
// Determine closest points, if last == null it means the hull was a plane so there's no penetration
|
||||||
if (last == nullptr)
|
if (last == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Should be an interior point
|
#ifdef JPH_EPA_CONVEX_BUILDER_DRAW
|
||||||
JPH_ASSERT(last->mClosestPointInterior);
|
hull.DrawLabel("Closest found");
|
||||||
|
hull.DrawWireTriangle(*last, Color::sWhite);
|
||||||
|
hull.DrawArrow(last->mCentroid, last->mCentroid + last->mNormal.NormalizedOr(Vec3::sZero()), Color::sWhite, 0.1f);
|
||||||
|
hull.DrawState();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Calculate penetration by getting the vector from the origin to the closest point on the triangle:
|
// Calculate penetration by getting the vector from the origin to the closest point on the triangle:
|
||||||
// distance = (centroid - origin) . normal / |normal|, closest = origin + distance * normal / |normal|
|
// distance = (centroid - origin) . normal / |normal|, closest = origin + distance * normal / |normal|
|
||||||
|
|
@ -359,6 +453,10 @@ public:
|
||||||
if (outV.IsNearZero())
|
if (outV.IsNearZero())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Check if we have to flip the sign of the penetration depth
|
||||||
|
if (flip_v_sign)
|
||||||
|
outV = -outV;
|
||||||
|
|
||||||
// Use the barycentric coordinates for the closest point to the origin to find the contact points on A and B
|
// Use the barycentric coordinates for the closest point to the origin to find the contact points on A and B
|
||||||
Vec3 p0 = support_points.mP[last->mEdge[0].mStartIdx];
|
Vec3 p0 = support_points.mP[last->mEdge[0].mStartIdx];
|
||||||
Vec3 p1 = support_points.mP[last->mEdge[1].mStartIdx];
|
Vec3 p1 = support_points.mP[last->mEdge[1].mStartIdx];
|
||||||
|
|
@ -407,7 +505,7 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB
|
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB
|
||||||
///
|
///
|
||||||
/// @param inStart Start position and orientation of the convex object
|
/// @param inStart Start position and orientation of the convex object
|
||||||
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
||||||
|
|
@ -417,32 +515,34 @@ public:
|
||||||
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
|
/// @param inB The convex object B, must support the GetSupport(Vec3) function.
|
||||||
/// @param inConvexRadiusA The convex radius of A, this will be added on all sides to pad A.
|
/// @param inConvexRadiusA The convex radius of A, this will be added on all sides to pad A.
|
||||||
/// @param inConvexRadiusB The convex radius of B, this will be added on all sides to pad B.
|
/// @param inConvexRadiusB The convex radius of B, this will be added on all sides to pad B.
|
||||||
/// @param inReturnDeepestPoint If the shapes are initially interesecting this determines if the EPA algorithm will run to find the deepest point
|
/// @param inReturnDeepestPoint If the shapes are initially intersecting this determines if the EPA algorithm will run to find the deepest point
|
||||||
/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.
|
/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.
|
||||||
/// @param outPointA is the contact point on A
|
/// @param outPointA is the contact point on A
|
||||||
/// @param outPointB is the contact point on B
|
/// @param outPointB is the contact point on B
|
||||||
/// @param outContactNormal is either the contact normal when the objects are touching or the penetration axis when the objects are penetrating at the start of the sweep (pointing from A to B, length will not be 1)
|
/// @param outContactNormal is either the contact normal when the objects are touching or the penetration axis when the objects are penetrating at the start of the sweep (pointing from A to B, length will not be 1)
|
||||||
///
|
///
|
||||||
/// @return true if the a hit was found, in which case ioLambda, outPointA, outPointB and outSurfaceNormal are updated.
|
/// @return true if the a hit was found, in which case ioLambda, outPointA, outPointB and outSurfaceNormal are updated.
|
||||||
template <typename A, typename B>
|
template <typename A, typename B>
|
||||||
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inCollisionTolerance, float inPenetrationTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, bool inReturnDeepestPoint, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outContactNormal)
|
bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inCollisionTolerance, float inPenetrationTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, bool inReturnDeepestPoint, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outContactNormal)
|
||||||
{
|
{
|
||||||
|
JPH_IF_ENABLE_ASSERTS(mGJKTolerance = inCollisionTolerance;)
|
||||||
|
|
||||||
// First determine if there's a collision at all
|
// First determine if there's a collision at all
|
||||||
if (!mGJK.CastShape(inStart, inDirection, inCollisionTolerance, inA, inB, inConvexRadiusA, inConvexRadiusB, ioLambda, outPointA, outPointB, outContactNormal))
|
if (!mGJK.CastShape(inStart, inDirection, inCollisionTolerance, inA, inB, inConvexRadiusA, inConvexRadiusB, ioLambda, outPointA, outPointB, outContactNormal))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// When our contact normal is too small, we don't have an accurate result
|
// When our contact normal is too small, we don't have an accurate result
|
||||||
bool contact_normal_invalid = outContactNormal.IsNearZero(Square(inCollisionTolerance));
|
bool contact_normal_invalid = outContactNormal.IsNearZero(Square(inCollisionTolerance));
|
||||||
|
|
||||||
if (inReturnDeepestPoint
|
if (inReturnDeepestPoint
|
||||||
&& ioLambda == 0.0f // Only when lambda = 0 we can have the bodies overlap
|
&& ioLambda == 0.0f // Only when lambda = 0 we can have the bodies overlap
|
||||||
&& (inConvexRadiusA + inConvexRadiusB == 0.0f // When no convex radius was provided we can never trust contact points at lambda = 0
|
&& (inConvexRadiusA + inConvexRadiusB == 0.0f // When no convex radius was provided we can never trust contact points at lambda = 0
|
||||||
|| contact_normal_invalid))
|
|| contact_normal_invalid))
|
||||||
{
|
{
|
||||||
// If we're initially intersecting, we need to run the EPA algorithm in order to find the deepest contact point
|
// If we're initially intersecting, we need to run the EPA algorithm in order to find the deepest contact point
|
||||||
AddConvexRadius<A> add_convex_a(inA, inConvexRadiusA);
|
AddConvexRadius add_convex_a(inA, inConvexRadiusA);
|
||||||
AddConvexRadius<B> add_convex_b(inB, inConvexRadiusB);
|
AddConvexRadius add_convex_b(inB, inConvexRadiusB);
|
||||||
TransformedConvexObject<AddConvexRadius<A>> transformed_a(inStart, add_convex_a);
|
TransformedConvexObject transformed_a(inStart, add_convex_a);
|
||||||
if (!GetPenetrationDepthStepEPA(transformed_a, add_convex_b, inPenetrationTolerance, outContactNormal, outPointA, outPointB))
|
if (!GetPenetrationDepthStepEPA(transformed_a, add_convex_b, inPenetrationTolerance, outContactNormal, outPointA, outPointB))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public:
|
||||||
/// Construct ellipse with radius A along the X-axis and B along the Y-axis
|
/// Construct ellipse with radius A along the X-axis and B along the Y-axis
|
||||||
Ellipse(float inA, float inB) : mA(inA), mB(inB) { JPH_ASSERT(inA > 0.0f); JPH_ASSERT(inB > 0.0f); }
|
Ellipse(float inA, float inB) : mA(inA), mB(inB) { JPH_ASSERT(inA > 0.0f); JPH_ASSERT(inB > 0.0f); }
|
||||||
|
|
||||||
/// Check if inPoint is inside the ellipsse
|
/// Check if inPoint is inside the ellipse
|
||||||
bool IsInside(const Float2 &inPoint) const
|
bool IsInside(const Float2 &inPoint) const
|
||||||
{
|
{
|
||||||
return Square(inPoint.x / mA) + Square(inPoint.y / mB) <= 1.0f;
|
return Square(inPoint.x / mA) + Square(inPoint.y / mB) <= 1.0f;
|
||||||
|
|
@ -26,7 +26,7 @@ public:
|
||||||
|
|
||||||
/// Get the closest point on the ellipse to inPoint
|
/// Get the closest point on the ellipse to inPoint
|
||||||
/// Assumes inPoint is outside the ellipse
|
/// Assumes inPoint is outside the ellipse
|
||||||
/// @see Rotation Joint Limits in Quaterion Space by Gino van den Bergen, section 10.1 in Game Engine Gems 3.
|
/// @see Rotation Joint Limits in Quaternion Space by Gino van den Bergen, section 10.1 in Game Engine Gems 3.
|
||||||
Float2 GetClosestPoint(const Float2 &inPoint) const
|
Float2 GetClosestPoint(const Float2 &inPoint) const
|
||||||
{
|
{
|
||||||
float a_sq = Square(mA);
|
float a_sq = Square(mA);
|
||||||
|
|
@ -38,7 +38,7 @@ public:
|
||||||
// <=> (x', y') = (a^2 x / (t + a^2), b^2 y / (t + b^2))
|
// <=> (x', y') = (a^2 x / (t + a^2), b^2 y / (t + b^2))
|
||||||
// Requiring point to be on ellipse (substituting into [1]): g(t) = (a x / (t + a^2))^2 + (b y / (t + b^2))^2 - 1 = 0
|
// Requiring point to be on ellipse (substituting into [1]): g(t) = (a x / (t + a^2))^2 + (b y / (t + b^2))^2 - 1 = 0
|
||||||
|
|
||||||
// Newton raphson iteration, starting at t = 0
|
// Newton Raphson iteration, starting at t = 0
|
||||||
float t = 0.0f;
|
float t = 0.0f;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
|
@ -52,13 +52,13 @@ public:
|
||||||
return Float2(a_sq * inPoint.x / t_plus_a_sq, b_sq * inPoint.y / t_plus_b_sq);
|
return Float2(a_sq * inPoint.x / t_plus_a_sq, b_sq * inPoint.y / t_plus_b_sq);
|
||||||
|
|
||||||
// Get derivative dg/dt = g'(t) = -2 (b^2 y^2 / (t + b^2)^3 + a^2 x^2 / (t + a^2)^3)
|
// Get derivative dg/dt = g'(t) = -2 (b^2 y^2 / (t + b^2)^3 + a^2 x^2 / (t + a^2)^3)
|
||||||
float gt_accent = -2.0f *
|
float gt_accent = -2.0f *
|
||||||
(a_sq * Square(inPoint.x) / Cubed(t_plus_a_sq)
|
(a_sq * Square(inPoint.x) / Cubed(t_plus_a_sq)
|
||||||
+ b_sq * Square(inPoint.y) / Cubed(t_plus_b_sq));
|
+ b_sq * Square(inPoint.y) / Cubed(t_plus_b_sq));
|
||||||
|
|
||||||
// Calculate t for next iteration: tn+1 = tn - g(t) / g'(t)
|
// Calculate t for next iteration: tn+1 = tn - g(t) / g'(t)
|
||||||
float tn = t - gt / gt_accent;
|
float tn = t - gt / gt_accent;
|
||||||
t = tn;
|
t = tn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Jolt/Core/NonCopyable.h>
|
#include <Jolt/Core/NonCopyable.h>
|
||||||
#include <Jolt/Core/FPException.h>
|
|
||||||
#include <Jolt/Geometry/ClosestPoint.h>
|
#include <Jolt/Geometry/ClosestPoint.h>
|
||||||
#include <Jolt/Geometry/ConvexSupport.h>
|
#include <Jolt/Geometry/ConvexSupport.h>
|
||||||
|
|
||||||
|
|
@ -28,7 +27,7 @@ private:
|
||||||
/// @param outV Closest point
|
/// @param outV Closest point
|
||||||
/// @param outVLenSq |outV|^2
|
/// @param outVLenSq |outV|^2
|
||||||
/// @param outSet Set of points that form the new simplex closest to the origin (bit 1 = mY[0], bit 2 = mY[1], ...)
|
/// @param outSet Set of points that form the new simplex closest to the origin (bit 1 = mY[0], bit 2 = mY[1], ...)
|
||||||
///
|
///
|
||||||
/// If LastPointPartOfClosestFeature is true then the last point added will be assumed to be part of the closest feature and the function will do less work.
|
/// If LastPointPartOfClosestFeature is true then the last point added will be assumed to be part of the closest feature and the function will do less work.
|
||||||
///
|
///
|
||||||
/// @return True if new closest point was found.
|
/// @return True if new closest point was found.
|
||||||
|
|
@ -73,7 +72,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
Trace("GetClosest: set = 0b%s, v = [%s], |v| = %g", NibbleToBinary(set), ConvertToString(v).c_str(), (double)v.Length());
|
Trace("GetClosest: set = 0b%s, v = [%s], |v| = %g", NibbleToBinary(set), ConvertToString(v).c_str(), (double)v.Length());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
float v_len_sq = v.LengthSq();
|
float v_len_sq = v.LengthSq();
|
||||||
|
|
@ -115,10 +114,6 @@ private:
|
||||||
mNumPoints = num_points;
|
mNumPoints = num_points;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCC 11.3 thinks the assignments to mP, mQ and mY below may use uninitialized variables
|
|
||||||
JPH_SUPPRESS_WARNING_PUSH
|
|
||||||
JPH_GCC_SUPPRESS_WARNING("-Wmaybe-uninitialized")
|
|
||||||
|
|
||||||
// Remove points that are not in the set, only updates mP
|
// Remove points that are not in the set, only updates mP
|
||||||
void UpdatePointSetP(uint32 inSet)
|
void UpdatePointSetP(uint32 inSet)
|
||||||
{
|
{
|
||||||
|
|
@ -161,15 +156,13 @@ private:
|
||||||
mNumPoints = num_points;
|
mNumPoints = num_points;
|
||||||
}
|
}
|
||||||
|
|
||||||
JPH_SUPPRESS_WARNING_POP
|
|
||||||
|
|
||||||
// Calculate closest points on A and B
|
// Calculate closest points on A and B
|
||||||
void CalculatePointAAndB(Vec3 &outPointA, Vec3 &outPointB) const
|
void CalculatePointAAndB(Vec3 &outPointA, Vec3 &outPointB) const
|
||||||
{
|
{
|
||||||
switch (mNumPoints)
|
switch (mNumPoints)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
outPointA = mP[0];
|
outPointA = mP[0];
|
||||||
outPointB = mQ[0];
|
outPointB = mQ[0];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -192,14 +185,14 @@ private:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
memset(&outPointA, 0xcd, sizeof(outPointA));
|
memset(&outPointA, 0xcd, sizeof(outPointA));
|
||||||
memset(&outPointB, 0xcd, sizeof(outPointB));
|
memset(&outPointB, 0xcd, sizeof(outPointB));
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Test if inA and inB intersect
|
/// Test if inA and inB intersect
|
||||||
///
|
///
|
||||||
|
|
@ -244,7 +237,7 @@ public:
|
||||||
{
|
{
|
||||||
// Separating axis found
|
// Separating axis found
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
Trace("Seperating axis");
|
Trace("Separating axis");
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +286,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next seperation axis to test is the negative of the closest point of the Minkowski sum to the origin
|
// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin
|
||||||
// Note: This must be done before terminating as converged since the separating axis is -v
|
// Note: This must be done before terminating as converged since the separating axis is -v
|
||||||
ioV = -ioV;
|
ioV = -ioV;
|
||||||
|
|
||||||
|
|
@ -393,7 +386,7 @@ public:
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
Trace("Distance bigger than max");
|
Trace("Distance bigger than max");
|
||||||
#endif
|
#endif
|
||||||
#ifdef _DEBUG
|
#ifdef JPH_DEBUG
|
||||||
memset(&outPointA, 0xcd, sizeof(outPointA));
|
memset(&outPointA, 0xcd, sizeof(outPointA));
|
||||||
memset(&outPointB, 0xcd, sizeof(outPointB));
|
memset(&outPointB, 0xcd, sizeof(outPointB));
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -456,7 +449,7 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next seperation axis to test is the negative of the closest point of the Minkowski sum to the origin
|
// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin
|
||||||
// Note: This must be done before terminating as converged since the separating axis is -v
|
// Note: This must be done before terminating as converged since the separating axis is -v
|
||||||
ioV = -ioV;
|
ioV = -ioV;
|
||||||
|
|
||||||
|
|
@ -508,7 +501,7 @@ public:
|
||||||
outNumPoints = mNumPoints;
|
outNumPoints = mNumPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> instersects inA
|
/// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> intersects inA
|
||||||
///
|
///
|
||||||
/// Code based upon: Ray Casting against General Convex Objects with Application to Continuous Collision Detection - Gino van den Bergen
|
/// Code based upon: Ray Casting against General Convex Objects with Application to Continuous Collision Detection - Gino van den Bergen
|
||||||
///
|
///
|
||||||
|
|
@ -517,7 +510,7 @@ public:
|
||||||
/// @param inTolerance The minimal distance between the ray and A before it is considered colliding
|
/// @param inTolerance The minimal distance between the ray and A before it is considered colliding
|
||||||
/// @param inA A convex object that has the GetSupport(Vec3) function
|
/// @param inA A convex object that has the GetSupport(Vec3) function
|
||||||
/// @param ioLambda The max fraction along the ray, on output updated with the actual collision fraction.
|
/// @param ioLambda The max fraction along the ray, on output updated with the actual collision fraction.
|
||||||
///
|
///
|
||||||
/// @return true if a hit was found, ioLambda is the solution for lambda.
|
/// @return true if a hit was found, ioLambda is the solution for lambda.
|
||||||
template <typename A>
|
template <typename A>
|
||||||
bool CastRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, float inTolerance, const A &inA, float &ioLambda)
|
bool CastRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, float inTolerance, const A &inA, float &ioLambda)
|
||||||
|
|
@ -532,7 +525,7 @@ public:
|
||||||
Vec3 v = x - inA.GetSupport(Vec3::sZero());
|
Vec3 v = x - inA.GetSupport(Vec3::sZero());
|
||||||
float v_len_sq = FLT_MAX;
|
float v_len_sq = FLT_MAX;
|
||||||
bool allow_restart = false;
|
bool allow_restart = false;
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
|
|
@ -558,7 +551,7 @@ public:
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
Trace("v . r = %g", (double)v_dot_r);
|
Trace("v . r = %g", (double)v_dot_r);
|
||||||
#endif
|
#endif
|
||||||
if (v_dot_r >= 0.0f)
|
if (v_dot_r >= -1.0e-18f) // Instead of checking >= 0, check with epsilon as we don't want the division below to overflow to infinity as it can cause a float exception
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Update the lower bound for lambda
|
// Update the lower bound for lambda
|
||||||
|
|
@ -579,7 +572,7 @@ public:
|
||||||
|
|
||||||
// Update x to new closest point on the ray
|
// Update x to new closest point on the ray
|
||||||
x = inRayOrigin + lambda * inRayDirection;
|
x = inRayOrigin + lambda * inRayDirection;
|
||||||
|
|
||||||
// We've shifted x, so reset v_len_sq so that it is not used as early out for GetClosest
|
// We've shifted x, so reset v_len_sq so that it is not used as early out for GetClosest
|
||||||
v_len_sq = FLT_MAX;
|
v_len_sq = FLT_MAX;
|
||||||
|
|
||||||
|
|
@ -598,7 +591,6 @@ public:
|
||||||
mY[i] = x - mP[i];
|
mY[i] = x - mP[i];
|
||||||
|
|
||||||
// Determine the new closest point from Y to origin
|
// Determine the new closest point from Y to origin
|
||||||
bool needs_restart = false;
|
|
||||||
uint32 set; // Set of points that form the new simplex
|
uint32 set; // Set of points that form the new simplex
|
||||||
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
|
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
|
||||||
{
|
{
|
||||||
|
|
@ -606,27 +598,7 @@ public:
|
||||||
Trace("Failed to converge");
|
Trace("Failed to converge");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// We failed to converge, restart
|
// Only allow 1 restart, if we still can't get a closest point
|
||||||
needs_restart = true;
|
|
||||||
}
|
|
||||||
else if (set == 0xf)
|
|
||||||
{
|
|
||||||
#ifdef JPH_GJK_DEBUG
|
|
||||||
Trace("Full simplex");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If there are 4 points, x is inside the tetrahedron and we've found a hit
|
|
||||||
// Double check if this is indeed the case
|
|
||||||
if (v_len_sq <= tolerance_sq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// We failed to converge, restart
|
|
||||||
needs_restart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needs_restart)
|
|
||||||
{
|
|
||||||
// Only allow 1 restart, if we still can't get a closest point
|
|
||||||
// we're so close that we return this as a hit
|
// we're so close that we return this as a hit
|
||||||
if (!allow_restart)
|
if (!allow_restart)
|
||||||
break;
|
break;
|
||||||
|
|
@ -642,6 +614,16 @@ public:
|
||||||
v_len_sq = FLT_MAX;
|
v_len_sq = FLT_MAX;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (set == 0xf)
|
||||||
|
{
|
||||||
|
#ifdef JPH_GJK_DEBUG
|
||||||
|
Trace("Full simplex");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
|
||||||
|
JPH_ASSERT(v_len_sq == 0.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the points P to form the new simplex
|
// Update the points P to form the new simplex
|
||||||
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
||||||
|
|
@ -662,7 +644,7 @@ public:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB
|
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB
|
||||||
///
|
///
|
||||||
/// @param inStart Start position and orientation of the convex object
|
/// @param inStart Start position and orientation of the convex object
|
||||||
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
||||||
|
|
@ -686,7 +668,7 @@ public:
|
||||||
return CastRay(Vec3::sZero(), inDirection, inTolerance, difference, ioLambda);
|
return CastRay(Vec3::sZero(), inDirection, inTolerance, difference, ioLambda);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> instersects inB
|
/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB
|
||||||
///
|
///
|
||||||
/// @param inStart Start position and orientation of the convex object
|
/// @param inStart Start position and orientation of the convex object
|
||||||
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)
|
||||||
|
|
@ -699,7 +681,7 @@ public:
|
||||||
/// @param outPointA is the contact point on A (if outSeparatingAxis is near zero, this may not be not the deepest point)
|
/// @param outPointA is the contact point on A (if outSeparatingAxis is near zero, this may not be not the deepest point)
|
||||||
/// @param outPointB is the contact point on B (if outSeparatingAxis is near zero, this may not be not the deepest point)
|
/// @param outPointB is the contact point on B (if outSeparatingAxis is near zero, this may not be not the deepest point)
|
||||||
/// @param outSeparatingAxis On return this will contain a vector that points from A to B along the smallest distance of separation.
|
/// @param outSeparatingAxis On return this will contain a vector that points from A to B along the smallest distance of separation.
|
||||||
/// The length of this vector indicates the separation of A and B without their convex radius.
|
/// The length of this vector indicates the separation of A and B without their convex radius.
|
||||||
/// If it is near zero, the direction may not be accurate as the bodies may overlap when lambda = 0.
|
/// If it is near zero, the direction may not be accurate as the bodies may overlap when lambda = 0.
|
||||||
///
|
///
|
||||||
/// @return true if a hit was found, ioLambda is the solution for lambda and outPoint and outSeparatingAxis are valid.
|
/// @return true if a hit was found, ioLambda is the solution for lambda and outPoint and outSeparatingAxis are valid.
|
||||||
|
|
@ -708,7 +690,7 @@ public:
|
||||||
{
|
{
|
||||||
float tolerance_sq = Square(inTolerance);
|
float tolerance_sq = Square(inTolerance);
|
||||||
|
|
||||||
// Calculate how close A and B (without their convex radius) need to be to eachother in order for us to consider this a collision
|
// Calculate how close A and B (without their convex radius) need to be to each other in order for us to consider this a collision
|
||||||
float sum_convex_radius = inConvexRadiusA + inConvexRadiusB;
|
float sum_convex_radius = inConvexRadiusA + inConvexRadiusB;
|
||||||
|
|
||||||
// Transform the shape to be cast to the starting position
|
// Transform the shape to be cast to the starting position
|
||||||
|
|
@ -762,7 +744,7 @@ public:
|
||||||
#ifdef JPH_GJK_DEBUG
|
#ifdef JPH_GJK_DEBUG
|
||||||
Trace("v . r = %g", (double)v_dot_r);
|
Trace("v . r = %g", (double)v_dot_r);
|
||||||
#endif
|
#endif
|
||||||
if (v_dot_r >= 0.0f)
|
if (v_dot_r >= -1.0e-18f) // Instead of checking >= 0, check with epsilon as we don't want the division below to overflow to infinity as it can cause a float exception
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Update the lower bound for lambda
|
// Update the lower bound for lambda
|
||||||
|
|
@ -783,7 +765,7 @@ public:
|
||||||
|
|
||||||
// Update x to new closest point on the ray
|
// Update x to new closest point on the ray
|
||||||
x = lambda * inDirection;
|
x = lambda * inDirection;
|
||||||
|
|
||||||
// We've shifted x, so reset v_len_sq so that it is not used as early out when GetClosest returns false
|
// We've shifted x, so reset v_len_sq so that it is not used as early out when GetClosest returns false
|
||||||
v_len_sq = FLT_MAX;
|
v_len_sq = FLT_MAX;
|
||||||
|
|
||||||
|
|
@ -807,7 +789,6 @@ public:
|
||||||
mY[i] = x - (mQ[i] - mP[i]);
|
mY[i] = x - (mQ[i] - mP[i]);
|
||||||
|
|
||||||
// Determine the new closest point from Y to origin
|
// Determine the new closest point from Y to origin
|
||||||
bool needs_restart = false;
|
|
||||||
uint32 set; // Set of points that form the new simplex
|
uint32 set; // Set of points that form the new simplex
|
||||||
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
|
if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))
|
||||||
{
|
{
|
||||||
|
|
@ -815,27 +796,7 @@ public:
|
||||||
Trace("Failed to converge");
|
Trace("Failed to converge");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// We failed to converge, restart
|
// Only allow 1 restart, if we still can't get a closest point
|
||||||
needs_restart = true;
|
|
||||||
}
|
|
||||||
else if (set == 0xf)
|
|
||||||
{
|
|
||||||
#ifdef JPH_GJK_DEBUG
|
|
||||||
Trace("Full simplex");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If there are 4 points, x is inside the tetrahedron and we've found a hit
|
|
||||||
// Double check that A and B are indeed touching according to our tolerance
|
|
||||||
if (v_len_sq <= tolerance_sq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// We failed to converge, restart
|
|
||||||
needs_restart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needs_restart)
|
|
||||||
{
|
|
||||||
// Only allow 1 restart, if we still can't get a closest point
|
|
||||||
// we're so close that we return this as a hit
|
// we're so close that we return this as a hit
|
||||||
if (!allow_restart)
|
if (!allow_restart)
|
||||||
break;
|
break;
|
||||||
|
|
@ -852,6 +813,16 @@ public:
|
||||||
v_len_sq = FLT_MAX;
|
v_len_sq = FLT_MAX;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (set == 0xf)
|
||||||
|
{
|
||||||
|
#ifdef JPH_GJK_DEBUG
|
||||||
|
Trace("Full simplex");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// We're inside the tetrahedron, we have a hit (verify that length of v is 0)
|
||||||
|
JPH_ASSERT(v_len_sq == 0.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the points P and Q to form the new simplex
|
// Update the points P and Q to form the new simplex
|
||||||
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration
|
||||||
|
|
@ -881,10 +852,10 @@ public:
|
||||||
|
|
||||||
// Get the contact point
|
// Get the contact point
|
||||||
// Note that A and B will coincide when lambda > 0. In this case we calculate only B as it is more accurate as it contains less terms.
|
// Note that A and B will coincide when lambda > 0. In this case we calculate only B as it is more accurate as it contains less terms.
|
||||||
switch (mNumPoints)
|
switch (mNumPoints)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
outPointB = mQ[0] + convex_radius_b;
|
outPointB = mQ[0] + convex_radius_b;
|
||||||
outPointA = lambda > 0.0f? outPointB : mP[0] - convex_radius_a;
|
outPointA = lambda > 0.0f? outPointB : mP[0] - convex_radius_a;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -908,7 +879,7 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store separating axis, in case we have a convex radius we can just return v,
|
// Store separating axis, in case we have a convex radius we can just return v,
|
||||||
// otherwise v will be very small and we resort to returning previous v as an approximation.
|
// otherwise v will be very small and we resort to returning previous v as an approximation.
|
||||||
outSeparatingAxis = sum_convex_radius > 0.0f? -v : -prev_v;
|
outSeparatingAxis = sum_convex_radius > 0.0f? -v : -prev_v;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public:
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
IndexedTriangleNoMaterial() = default;
|
IndexedTriangleNoMaterial() = default;
|
||||||
IndexedTriangleNoMaterial(uint32 inI1, uint32 inI2, uint32 inI3) { mIdx[0] = inI1; mIdx[1] = inI2; mIdx[2] = inI3; }
|
constexpr IndexedTriangleNoMaterial(uint32 inI1, uint32 inI2, uint32 inI3) : mIdx { inI1, inI2, inI3 } { }
|
||||||
|
|
||||||
/// Check if two triangles are identical
|
/// Check if two triangles are identical
|
||||||
bool operator == (const IndexedTriangleNoMaterial &inRHS) const
|
bool operator == (const IndexedTriangleNoMaterial &inRHS) const
|
||||||
|
|
@ -41,9 +41,13 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if triangle is degenerate
|
/// Check if triangle is degenerate
|
||||||
bool IsDegenerate() const
|
bool IsDegenerate(const VertexList &inVertices) const
|
||||||
{
|
{
|
||||||
return mIdx[0] == mIdx[1] || mIdx[1] == mIdx[2] || mIdx[2] == mIdx[0];
|
Vec3 v0(inVertices[mIdx[0]]);
|
||||||
|
Vec3 v1(inVertices[mIdx[1]]);
|
||||||
|
Vec3 v2(inVertices[mIdx[2]]);
|
||||||
|
|
||||||
|
return (v1 - v0).Cross(v2 - v0).IsNearZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotate the vertices so that the second vertex becomes first etc. This does not change the represented triangle.
|
/// Rotate the vertices so that the second vertex becomes first etc. This does not change the represented triangle.
|
||||||
|
|
@ -61,6 +65,13 @@ public:
|
||||||
return (Vec3(inVertices[mIdx[0]]) + Vec3(inVertices[mIdx[1]]) + Vec3(inVertices[mIdx[2]])) / 3.0f;
|
return (Vec3(inVertices[mIdx[0]]) + Vec3(inVertices[mIdx[1]]) + Vec3(inVertices[mIdx[2]])) / 3.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the hash value of this structure
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
static_assert(sizeof(IndexedTriangleNoMaterial) == 3 * sizeof(uint32), "Class should have no padding");
|
||||||
|
return HashBytes(this, sizeof(IndexedTriangleNoMaterial));
|
||||||
|
}
|
||||||
|
|
||||||
uint32 mIdx[3];
|
uint32 mIdx[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,12 +82,12 @@ public:
|
||||||
using IndexedTriangleNoMaterial::IndexedTriangleNoMaterial;
|
using IndexedTriangleNoMaterial::IndexedTriangleNoMaterial;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
IndexedTriangle(uint32 inI1, uint32 inI2, uint32 inI3, uint32 inMaterialIndex) : IndexedTriangleNoMaterial(inI1, inI2, inI3), mMaterialIndex(inMaterialIndex) { }
|
constexpr IndexedTriangle(uint32 inI1, uint32 inI2, uint32 inI3, uint32 inMaterialIndex, uint inUserData = 0) : IndexedTriangleNoMaterial(inI1, inI2, inI3), mMaterialIndex(inMaterialIndex), mUserData(inUserData) { }
|
||||||
|
|
||||||
/// Check if two triangles are identical
|
/// Check if two triangles are identical
|
||||||
bool operator == (const IndexedTriangle &inRHS) const
|
bool operator == (const IndexedTriangle &inRHS) const
|
||||||
{
|
{
|
||||||
return mMaterialIndex == inRHS.mMaterialIndex && IndexedTriangleNoMaterial::operator==(inRHS);
|
return mMaterialIndex == inRHS.mMaterialIndex && mUserData == inRHS.mUserData && IndexedTriangleNoMaterial::operator==(inRHS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotate the vertices so that the lowest vertex becomes the first. This does not change the represented triangle.
|
/// Rotate the vertices so that the lowest vertex becomes the first. This does not change the represented triangle.
|
||||||
|
|
@ -85,20 +96,28 @@ public:
|
||||||
if (mIdx[0] < mIdx[1])
|
if (mIdx[0] < mIdx[1])
|
||||||
{
|
{
|
||||||
if (mIdx[0] < mIdx[2])
|
if (mIdx[0] < mIdx[2])
|
||||||
return IndexedTriangle(mIdx[0], mIdx[1], mIdx[2], mMaterialIndex); // 0 is smallest
|
return IndexedTriangle(mIdx[0], mIdx[1], mIdx[2], mMaterialIndex, mUserData); // 0 is smallest
|
||||||
else
|
else
|
||||||
return IndexedTriangle(mIdx[2], mIdx[0], mIdx[1], mMaterialIndex); // 2 is smallest
|
return IndexedTriangle(mIdx[2], mIdx[0], mIdx[1], mMaterialIndex, mUserData); // 2 is smallest
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (mIdx[1] < mIdx[2])
|
if (mIdx[1] < mIdx[2])
|
||||||
return IndexedTriangle(mIdx[1], mIdx[2], mIdx[0], mMaterialIndex); // 1 is smallest
|
return IndexedTriangle(mIdx[1], mIdx[2], mIdx[0], mMaterialIndex, mUserData); // 1 is smallest
|
||||||
else
|
else
|
||||||
return IndexedTriangle(mIdx[2], mIdx[0], mIdx[1], mMaterialIndex); // 2 is smallest
|
return IndexedTriangle(mIdx[2], mIdx[0], mIdx[1], mMaterialIndex, mUserData); // 2 is smallest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the hash value of this structure
|
||||||
|
uint64 GetHash() const
|
||||||
|
{
|
||||||
|
static_assert(sizeof(IndexedTriangle) == 5 * sizeof(uint32), "Class should have no padding");
|
||||||
|
return HashBytes(this, sizeof(IndexedTriangle));
|
||||||
|
}
|
||||||
|
|
||||||
uint32 mMaterialIndex = 0;
|
uint32 mMaterialIndex = 0;
|
||||||
|
uint32 mUserData = 0; ///< User data that can be used for anything by the application, e.g. for tracking the original index of the triangle
|
||||||
};
|
};
|
||||||
|
|
||||||
using IndexedTriangleNoMaterialList = Array<IndexedTriangleNoMaterial>;
|
using IndexedTriangleNoMaterialList = Array<IndexedTriangleNoMaterial>;
|
||||||
|
|
@ -107,5 +126,5 @@ using IndexedTriangleList = Array<IndexedTriangle>;
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
||||||
// Create a std::hash for IndexedTriangleNoMaterial and IndexedTriangle
|
// Create a std::hash for IndexedTriangleNoMaterial and IndexedTriangle
|
||||||
JPH_MAKE_HASHABLE(JPH::IndexedTriangleNoMaterial, t.mIdx[0], t.mIdx[1], t.mIdx[2])
|
JPH_MAKE_STD_HASH(JPH::IndexedTriangleNoMaterial)
|
||||||
JPH_MAKE_HASHABLE(JPH::IndexedTriangle, t.mIdx[0], t.mIdx[1], t.mIdx[2], t.mMaterialIndex)
|
JPH_MAKE_STD_HASH(JPH::IndexedTriangle)
|
||||||
|
|
|
||||||
|
|
@ -4,59 +4,203 @@
|
||||||
|
|
||||||
#include <Jolt/Jolt.h>
|
#include <Jolt/Jolt.h>
|
||||||
|
|
||||||
#include <Jolt/Core/UnorderedMap.h>
|
|
||||||
#include <Jolt/Geometry/Indexify.h>
|
#include <Jolt/Geometry/Indexify.h>
|
||||||
|
#include <Jolt/Geometry/AABox.h>
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
void Indexify(const TriangleList &inTriangles, VertexList &outVertices, IndexedTriangleList &outTriangles, float inVertexWeldDistance)
|
static JPH_INLINE const Float3 &sIndexifyGetFloat3(const TriangleList &inTriangles, uint32 inVertexIndex)
|
||||||
|
{
|
||||||
|
return inTriangles[inVertexIndex / 3].mV[inVertexIndex % 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static JPH_INLINE Vec3 sIndexifyGetVec3(const TriangleList &inTriangles, uint32 inVertexIndex)
|
||||||
|
{
|
||||||
|
return Vec3::sLoadFloat3Unsafe(sIndexifyGetFloat3(inTriangles, inVertexIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sIndexifyVerticesBruteForce(const TriangleList &inTriangles, const uint32 *inVertexIndices, const uint32 *inVertexIndicesEnd, Array<uint32> &ioWeldedVertices, float inVertexWeldDistance)
|
||||||
{
|
{
|
||||||
float weld_dist_sq = Square(inVertexWeldDistance);
|
float weld_dist_sq = Square(inVertexWeldDistance);
|
||||||
|
|
||||||
// Ensure that output vertices are empty before we begin
|
// Compare every vertex
|
||||||
outVertices.clear();
|
for (const uint32 *v1_idx = inVertexIndices; v1_idx < inVertexIndicesEnd; ++v1_idx)
|
||||||
|
{
|
||||||
|
Vec3 v1 = sIndexifyGetVec3(inTriangles, *v1_idx);
|
||||||
|
|
||||||
// Find unique vertices
|
// with every other vertex...
|
||||||
UnorderedMap<Float3, uint32> vertex_map;
|
for (const uint32 *v2_idx = v1_idx + 1; v2_idx < inVertexIndicesEnd; ++v2_idx)
|
||||||
for (const Triangle &t : inTriangles)
|
|
||||||
for (const Float3 &v : t.mV)
|
|
||||||
{
|
{
|
||||||
// Try to insert element
|
Vec3 v2 = sIndexifyGetVec3(inTriangles, *v2_idx);
|
||||||
auto insert = vertex_map.insert(pair<Float3, uint32>(v, 0));
|
|
||||||
if (insert.second)
|
// If they're weldable
|
||||||
|
if ((v2 - v1).LengthSq() <= weld_dist_sq)
|
||||||
{
|
{
|
||||||
// Newly inserted, see if we can share
|
// Find the lowest indices both indices link to
|
||||||
bool found = false;
|
uint32 idx1 = *v1_idx;
|
||||||
for (size_t i = 0; i < outVertices.size(); ++i)
|
for (;;)
|
||||||
{
|
{
|
||||||
const Float3 &other = outVertices[i];
|
uint32 new_idx1 = ioWeldedVertices[idx1];
|
||||||
if (Square(other.x - v.x) + Square(other.y - v.y) + Square(other.z - v.z) <= weld_dist_sq)
|
if (new_idx1 >= idx1)
|
||||||
{
|
|
||||||
insert.first->second = (uint32)i;
|
|
||||||
found = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
idx1 = new_idx1;
|
||||||
|
}
|
||||||
|
uint32 idx2 = *v2_idx;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
uint32 new_idx2 = ioWeldedVertices[idx2];
|
||||||
|
if (new_idx2 >= idx2)
|
||||||
|
break;
|
||||||
|
idx2 = new_idx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found)
|
// Order the vertices
|
||||||
{
|
uint32 lowest = min(idx1, idx2);
|
||||||
// Can't share, add vertex
|
uint32 highest = max(idx1, idx2);
|
||||||
insert.first->second = (uint32)outVertices.size();
|
|
||||||
outVertices.push_back(v);
|
// Link highest to lowest
|
||||||
}
|
ioWeldedVertices[highest] = lowest;
|
||||||
|
|
||||||
|
// Also update the vertices we started from to avoid creating long chains
|
||||||
|
ioWeldedVertices[*v1_idx] = lowest;
|
||||||
|
ioWeldedVertices[*v2_idx] = lowest;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sIndexifyVerticesRecursively(const TriangleList &inTriangles, uint32 *ioVertexIndices, uint inNumVertices, uint32 *ioScratch, Array<uint32> &ioWeldedVertices, float inVertexWeldDistance, uint inMaxRecursion)
|
||||||
|
{
|
||||||
|
// Check if we have few enough vertices to do a brute force search
|
||||||
|
// Or if we've recursed too deep (this means we chipped off a few vertices each iteration because all points are very close)
|
||||||
|
if (inNumVertices <= 8 || inMaxRecursion == 0)
|
||||||
|
{
|
||||||
|
sIndexifyVerticesBruteForce(inTriangles, ioVertexIndices, ioVertexIndices + inNumVertices, ioWeldedVertices, inVertexWeldDistance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate bounds
|
||||||
|
AABox bounds;
|
||||||
|
for (const uint32 *v = ioVertexIndices, *v_end = ioVertexIndices + inNumVertices; v < v_end; ++v)
|
||||||
|
bounds.Encapsulate(sIndexifyGetVec3(inTriangles, *v));
|
||||||
|
|
||||||
|
// Determine split plane
|
||||||
|
int split_axis = bounds.GetExtent().GetHighestComponentIndex();
|
||||||
|
float split_value = bounds.GetCenter()[split_axis];
|
||||||
|
|
||||||
|
// Partition vertices
|
||||||
|
uint32 *v_read = ioVertexIndices, *v_write = ioVertexIndices, *v_end = ioVertexIndices + inNumVertices;
|
||||||
|
uint32 *scratch = ioScratch;
|
||||||
|
while (v_read < v_end)
|
||||||
|
{
|
||||||
|
// Calculate distance to plane
|
||||||
|
float distance_to_split_plane = sIndexifyGetFloat3(inTriangles, *v_read)[split_axis] - split_value;
|
||||||
|
if (distance_to_split_plane < -inVertexWeldDistance)
|
||||||
|
{
|
||||||
|
// Vertex is on the right side
|
||||||
|
*v_write = *v_read;
|
||||||
|
++v_read;
|
||||||
|
++v_write;
|
||||||
|
}
|
||||||
|
else if (distance_to_split_plane > inVertexWeldDistance)
|
||||||
|
{
|
||||||
|
// Vertex is on the wrong side, swap with the last vertex
|
||||||
|
--v_end;
|
||||||
|
std::swap(*v_read, *v_end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Vertex is too close to the split plane, it goes on both sides
|
||||||
|
*scratch++ = *v_read++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we made any progress
|
||||||
|
uint num_vertices_on_both_sides = (uint)(scratch - ioScratch);
|
||||||
|
if (num_vertices_on_both_sides == inNumVertices)
|
||||||
|
{
|
||||||
|
sIndexifyVerticesBruteForce(inTriangles, ioVertexIndices, ioVertexIndices + inNumVertices, ioWeldedVertices, inVertexWeldDistance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate how we classified the vertices
|
||||||
|
uint num_vertices_left = (uint)(v_write - ioVertexIndices);
|
||||||
|
uint num_vertices_right = (uint)(ioVertexIndices + inNumVertices - v_end);
|
||||||
|
JPH_ASSERT(num_vertices_left + num_vertices_right + num_vertices_on_both_sides == inNumVertices);
|
||||||
|
memcpy(v_write, ioScratch, num_vertices_on_both_sides * sizeof(uint32));
|
||||||
|
|
||||||
|
// Recurse
|
||||||
|
uint max_recursion = inMaxRecursion - 1;
|
||||||
|
sIndexifyVerticesRecursively(inTriangles, ioVertexIndices, num_vertices_left + num_vertices_on_both_sides, ioScratch, ioWeldedVertices, inVertexWeldDistance, max_recursion);
|
||||||
|
sIndexifyVerticesRecursively(inTriangles, ioVertexIndices + num_vertices_left, num_vertices_right + num_vertices_on_both_sides, ioScratch, ioWeldedVertices, inVertexWeldDistance, max_recursion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexify(const TriangleList &inTriangles, VertexList &outVertices, IndexedTriangleList &outTriangles, float inVertexWeldDistance)
|
||||||
|
{
|
||||||
|
uint num_triangles = (uint)inTriangles.size();
|
||||||
|
uint num_vertices = num_triangles * 3;
|
||||||
|
|
||||||
|
// Create a list of all vertex indices
|
||||||
|
Array<uint32> vertex_indices;
|
||||||
|
vertex_indices.resize(num_vertices);
|
||||||
|
for (uint i = 0; i < num_vertices; ++i)
|
||||||
|
vertex_indices[i] = i;
|
||||||
|
|
||||||
|
// Link each vertex to itself
|
||||||
|
Array<uint32> welded_vertices;
|
||||||
|
welded_vertices.resize(num_vertices);
|
||||||
|
for (uint i = 0; i < num_vertices; ++i)
|
||||||
|
welded_vertices[i] = i;
|
||||||
|
|
||||||
|
// A scope to free memory used by the scratch array
|
||||||
|
{
|
||||||
|
// Some scratch memory, used for the vertices that fall in both partitions
|
||||||
|
Array<uint32> scratch;
|
||||||
|
scratch.resize(num_vertices);
|
||||||
|
|
||||||
|
// Recursively split the vertices
|
||||||
|
sIndexifyVerticesRecursively(inTriangles, vertex_indices.data(), num_vertices, scratch.data(), welded_vertices, inVertexWeldDistance, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a pass to complete the welding, linking each vertex to the vertex it is welded to
|
||||||
|
// (and since we're going from 0 to N we can be sure that the vertex we're linking to is already linked to the lowest vertex)
|
||||||
|
uint num_resulting_vertices = 0;
|
||||||
|
for (uint i = 0; i < num_vertices; ++i)
|
||||||
|
{
|
||||||
|
JPH_ASSERT(welded_vertices[welded_vertices[i]] <= welded_vertices[i]);
|
||||||
|
welded_vertices[i] = welded_vertices[welded_vertices[i]];
|
||||||
|
if (welded_vertices[i] == i)
|
||||||
|
++num_resulting_vertices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the vertices
|
||||||
|
outVertices.clear();
|
||||||
|
outVertices.reserve(num_resulting_vertices);
|
||||||
|
for (uint i = 0; i < num_vertices; ++i)
|
||||||
|
if (welded_vertices[i] == i)
|
||||||
|
{
|
||||||
|
// New vertex
|
||||||
|
welded_vertices[i] = (uint32)outVertices.size();
|
||||||
|
outVertices.push_back(sIndexifyGetFloat3(inTriangles, i));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Reused vertex, remap index
|
||||||
|
welded_vertices[i] = welded_vertices[welded_vertices[i]];
|
||||||
|
}
|
||||||
|
|
||||||
// Create indexed triangles
|
// Create indexed triangles
|
||||||
outTriangles.clear();
|
outTriangles.clear();
|
||||||
outTriangles.reserve(inTriangles.size());
|
outTriangles.reserve(num_triangles);
|
||||||
for (const Triangle &t : inTriangles)
|
for (uint t = 0; t < num_triangles; ++t)
|
||||||
{
|
{
|
||||||
IndexedTriangle it;
|
IndexedTriangle it;
|
||||||
it.mMaterialIndex = t.mMaterialIndex;
|
it.mMaterialIndex = inTriangles[t].mMaterialIndex;
|
||||||
for (int j = 0; j < 3; ++j)
|
it.mUserData = inTriangles[t].mUserData;
|
||||||
it.mIdx[j] = vertex_map[t.mV[j]];
|
for (int v = 0; v < 3; ++v)
|
||||||
if (!it.IsDegenerate())
|
it.mIdx[v] = welded_vertices[t * 3 + v];
|
||||||
|
if (!it.IsDegenerate(outVertices))
|
||||||
outTriangles.push_back(it);
|
outTriangles.push_back(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,8 +209,14 @@ void Deindexify(const VertexList &inVertices, const IndexedTriangleList &inTrian
|
||||||
{
|
{
|
||||||
outTriangles.resize(inTriangles.size());
|
outTriangles.resize(inTriangles.size());
|
||||||
for (size_t t = 0; t < inTriangles.size(); ++t)
|
for (size_t t = 0; t < inTriangles.size(); ++t)
|
||||||
|
{
|
||||||
|
const IndexedTriangle &in = inTriangles[t];
|
||||||
|
Triangle &out = outTriangles[t];
|
||||||
|
out.mMaterialIndex = in.mMaterialIndex;
|
||||||
|
out.mUserData = in.mUserData;
|
||||||
for (int v = 0; v < 3; ++v)
|
for (int v = 0; v < 3; ++v)
|
||||||
outTriangles[t].mV[v] = inVertices[inTriangles[t].mIdx[v]];
|
out.mV[v] = inVertices[in.mIdx[v]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Take a list of triangles and get the unique set of vertices and use them to create indexed triangles.
|
/// Take a list of triangles and get the unique set of vertices and use them to create indexed triangles.
|
||||||
/// Vertices that are less than inVertexWeldDistance apart will be combined to a single vertex.
|
/// Vertices that are less than inVertexWeldDistance apart will be combined to a single vertex.
|
||||||
void Indexify(const TriangleList &inTriangles, VertexList &outVertices, IndexedTriangleList &outTriangles, float inVertexWeldDistance = 1.0e-4f);
|
JPH_EXPORT void Indexify(const TriangleList &inTriangles, VertexList &outVertices, IndexedTriangleList &outTriangles, float inVertexWeldDistance = 1.0e-4f);
|
||||||
|
|
||||||
/// Take a list of indexed triangles and unpack them
|
/// Take a list of indexed triangles and unpack them
|
||||||
void Deindexify(const VertexList &inVertices, const IndexedTriangleList &inTriangles, TriangleList &outTriangles);
|
JPH_EXPORT void Deindexify(const VertexList &inVertices, const IndexedTriangleList &inTriangles, TriangleList &outTriangles);
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
JPH_NAMESPACE_END
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
#include <Jolt/Jolt.h>
|
#include <Jolt/Jolt.h>
|
||||||
|
|
||||||
#include <Jolt/Geometry/AABox.h>
|
|
||||||
#include <Jolt/Geometry/OrientedBox.h>
|
#include <Jolt/Geometry/OrientedBox.h>
|
||||||
|
#include <Jolt/Geometry/AABox.h>
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ bool OrientedBox::Overlaps(const AABox &inBox, float inEpsilon) const
|
||||||
|
|
||||||
// Test axes L = A0, L = A1, L = A2
|
// Test axes L = A0, L = A1, L = A2
|
||||||
float ra, rb;
|
float ra, rb;
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = a_half_extents[i];
|
ra = a_half_extents[i];
|
||||||
rb = mHalfExtents[0] * abs_r[0][i] + mHalfExtents[1] * abs_r[1][i] + mHalfExtents[2] * abs_r[2][i];
|
rb = mHalfExtents[0] * abs_r[0][i] + mHalfExtents[1] * abs_r[1][i] + mHalfExtents[2] * abs_r[2][i];
|
||||||
|
|
@ -38,7 +38,7 @@ bool OrientedBox::Overlaps(const AABox &inBox, float inEpsilon) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test axes L = B0, L = B1, L = B2
|
// Test axes L = B0, L = B1, L = B2
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = a_half_extents.Dot(abs_r[i]);
|
ra = a_half_extents.Dot(abs_r[i]);
|
||||||
rb = mHalfExtents[i];
|
rb = mHalfExtents[i];
|
||||||
|
|
@ -62,34 +62,34 @@ bool OrientedBox::Overlaps(const AABox &inBox, float inEpsilon) const
|
||||||
|
|
||||||
// Test axis L = A1 x B0
|
// Test axis L = A1 x B0
|
||||||
ra = a_half_extents[0] * abs_r[0][2] + a_half_extents[2] * abs_r[0][0];
|
ra = a_half_extents[0] * abs_r[0][2] + a_half_extents[2] * abs_r[0][0];
|
||||||
rb = mHalfExtents[1] * abs_r[2][1] + mHalfExtents[2] * abs_r[1][1];
|
rb = mHalfExtents[1] * abs_r[2][1] + mHalfExtents[2] * abs_r[1][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 0) - rot(2, 3) * rot(0, 0)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 0) - rot(2, 3) * rot(0, 0)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A1 x B1
|
// Test axis L = A1 x B1
|
||||||
ra = a_half_extents[0] * abs_r[1][2] + a_half_extents[2] * abs_r[1][0];
|
ra = a_half_extents[0] * abs_r[1][2] + a_half_extents[2] * abs_r[1][0];
|
||||||
rb = mHalfExtents[0] * abs_r[2][1] + mHalfExtents[2] * abs_r[0][1];
|
rb = mHalfExtents[0] * abs_r[2][1] + mHalfExtents[2] * abs_r[0][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 1) - rot(2, 3) * rot(0, 1)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 1) - rot(2, 3) * rot(0, 1)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A1 x B2
|
// Test axis L = A1 x B2
|
||||||
ra = a_half_extents[0] * abs_r[2][2] + a_half_extents[2] * abs_r[2][0];
|
ra = a_half_extents[0] * abs_r[2][2] + a_half_extents[2] * abs_r[2][0];
|
||||||
rb = mHalfExtents[0] * abs_r[1][1] + mHalfExtents[1] * abs_r[0][1];
|
rb = mHalfExtents[0] * abs_r[1][1] + mHalfExtents[1] * abs_r[0][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 2) - rot(2, 3) * rot(0, 2)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 2) - rot(2, 3) * rot(0, 2)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B0
|
// Test axis L = A2 x B0
|
||||||
ra = a_half_extents[0] * abs_r[0][1] + a_half_extents[1] * abs_r[0][0];
|
ra = a_half_extents[0] * abs_r[0][1] + a_half_extents[1] * abs_r[0][0];
|
||||||
rb = mHalfExtents[1] * abs_r[2][2] + mHalfExtents[2] * abs_r[1][2];
|
rb = mHalfExtents[1] * abs_r[2][2] + mHalfExtents[2] * abs_r[1][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 0) - rot(0, 3) * rot(1, 0)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 0) - rot(0, 3) * rot(1, 0)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B1
|
// Test axis L = A2 x B1
|
||||||
ra = a_half_extents[0] * abs_r[1][1] + a_half_extents[1] * abs_r[1][0];
|
ra = a_half_extents[0] * abs_r[1][1] + a_half_extents[1] * abs_r[1][0];
|
||||||
rb = mHalfExtents[0] * abs_r[2][2] + mHalfExtents[2] * abs_r[0][2];
|
rb = mHalfExtents[0] * abs_r[2][2] + mHalfExtents[2] * abs_r[0][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 1) - rot(0, 3) * rot(1, 1)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 1) - rot(0, 3) * rot(1, 1)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B2
|
// Test axis L = A2 x B2
|
||||||
ra = a_half_extents[0] * abs_r[2][1] + a_half_extents[1] * abs_r[2][0];
|
ra = a_half_extents[0] * abs_r[2][1] + a_half_extents[1] * abs_r[2][0];
|
||||||
rb = mHalfExtents[0] * abs_r[1][2] + mHalfExtents[1] * abs_r[0][2];
|
rb = mHalfExtents[0] * abs_r[1][2] + mHalfExtents[1] * abs_r[0][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 2) - rot(0, 3) * rot(1, 2)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 2) - rot(0, 3) * rot(1, 2)) > ra + rb) return false;
|
||||||
|
|
||||||
// Since no separating axis is found, the OBB and AAB must be intersecting
|
// Since no separating axis is found, the OBB and AAB must be intersecting
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ bool OrientedBox::Overlaps(const OrientedBox &inBox, float inEpsilon) const
|
||||||
|
|
||||||
// Test axes L = A0, L = A1, L = A2
|
// Test axes L = A0, L = A1, L = A2
|
||||||
float ra, rb;
|
float ra, rb;
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = mHalfExtents[i];
|
ra = mHalfExtents[i];
|
||||||
rb = inBox.mHalfExtents[0] * abs_r[0][i] + inBox.mHalfExtents[1] * abs_r[1][i] + inBox.mHalfExtents[2] * abs_r[2][i];
|
rb = inBox.mHalfExtents[0] * abs_r[0][i] + inBox.mHalfExtents[1] * abs_r[1][i] + inBox.mHalfExtents[2] * abs_r[2][i];
|
||||||
|
|
@ -119,7 +119,7 @@ bool OrientedBox::Overlaps(const OrientedBox &inBox, float inEpsilon) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test axes L = B0, L = B1, L = B2
|
// Test axes L = B0, L = B1, L = B2
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
ra = mHalfExtents.Dot(abs_r[i]);
|
ra = mHalfExtents.Dot(abs_r[i]);
|
||||||
rb = inBox.mHalfExtents[i];
|
rb = inBox.mHalfExtents[i];
|
||||||
|
|
@ -143,34 +143,34 @@ bool OrientedBox::Overlaps(const OrientedBox &inBox, float inEpsilon) const
|
||||||
|
|
||||||
// Test axis L = A1 x B0
|
// Test axis L = A1 x B0
|
||||||
ra = mHalfExtents[0] * abs_r[0][2] + mHalfExtents[2] * abs_r[0][0];
|
ra = mHalfExtents[0] * abs_r[0][2] + mHalfExtents[2] * abs_r[0][0];
|
||||||
rb = inBox.mHalfExtents[1] * abs_r[2][1] + inBox.mHalfExtents[2] * abs_r[1][1];
|
rb = inBox.mHalfExtents[1] * abs_r[2][1] + inBox.mHalfExtents[2] * abs_r[1][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 0) - rot(2, 3) * rot(0, 0)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 0) - rot(2, 3) * rot(0, 0)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A1 x B1
|
// Test axis L = A1 x B1
|
||||||
ra = mHalfExtents[0] * abs_r[1][2] + mHalfExtents[2] * abs_r[1][0];
|
ra = mHalfExtents[0] * abs_r[1][2] + mHalfExtents[2] * abs_r[1][0];
|
||||||
rb = inBox.mHalfExtents[0] * abs_r[2][1] + inBox.mHalfExtents[2] * abs_r[0][1];
|
rb = inBox.mHalfExtents[0] * abs_r[2][1] + inBox.mHalfExtents[2] * abs_r[0][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 1) - rot(2, 3) * rot(0, 1)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 1) - rot(2, 3) * rot(0, 1)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A1 x B2
|
// Test axis L = A1 x B2
|
||||||
ra = mHalfExtents[0] * abs_r[2][2] + mHalfExtents[2] * abs_r[2][0];
|
ra = mHalfExtents[0] * abs_r[2][2] + mHalfExtents[2] * abs_r[2][0];
|
||||||
rb = inBox.mHalfExtents[0] * abs_r[1][1] + inBox.mHalfExtents[1] * abs_r[0][1];
|
rb = inBox.mHalfExtents[0] * abs_r[1][1] + inBox.mHalfExtents[1] * abs_r[0][1];
|
||||||
if (abs(rot(0, 3) * rot(2, 2) - rot(2, 3) * rot(0, 2)) > ra + rb) return false;
|
if (abs(rot(0, 3) * rot(2, 2) - rot(2, 3) * rot(0, 2)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B0
|
// Test axis L = A2 x B0
|
||||||
ra = mHalfExtents[0] * abs_r[0][1] + mHalfExtents[1] * abs_r[0][0];
|
ra = mHalfExtents[0] * abs_r[0][1] + mHalfExtents[1] * abs_r[0][0];
|
||||||
rb = inBox.mHalfExtents[1] * abs_r[2][2] + inBox.mHalfExtents[2] * abs_r[1][2];
|
rb = inBox.mHalfExtents[1] * abs_r[2][2] + inBox.mHalfExtents[2] * abs_r[1][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 0) - rot(0, 3) * rot(1, 0)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 0) - rot(0, 3) * rot(1, 0)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B1
|
// Test axis L = A2 x B1
|
||||||
ra = mHalfExtents[0] * abs_r[1][1] + mHalfExtents[1] * abs_r[1][0];
|
ra = mHalfExtents[0] * abs_r[1][1] + mHalfExtents[1] * abs_r[1][0];
|
||||||
rb = inBox.mHalfExtents[0] * abs_r[2][2] + inBox.mHalfExtents[2] * abs_r[0][2];
|
rb = inBox.mHalfExtents[0] * abs_r[2][2] + inBox.mHalfExtents[2] * abs_r[0][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 1) - rot(0, 3) * rot(1, 1)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 1) - rot(0, 3) * rot(1, 1)) > ra + rb) return false;
|
||||||
|
|
||||||
// Test axis L = A2 x B2
|
// Test axis L = A2 x B2
|
||||||
ra = mHalfExtents[0] * abs_r[2][1] + mHalfExtents[1] * abs_r[2][0];
|
ra = mHalfExtents[0] * abs_r[2][1] + mHalfExtents[1] * abs_r[2][0];
|
||||||
rb = inBox.mHalfExtents[0] * abs_r[1][2] + inBox.mHalfExtents[1] * abs_r[0][2];
|
rb = inBox.mHalfExtents[0] * abs_r[1][2] + inBox.mHalfExtents[1] * abs_r[0][2];
|
||||||
if (abs(rot(1, 3) * rot(0, 2) - rot(0, 3) * rot(1, 2)) > ra + rb) return false;
|
if (abs(rot(1, 3) * rot(0, 2) - rot(0, 3) * rot(1, 2)) > ra + rb) return false;
|
||||||
|
|
||||||
// Since no separating axis is found, the OBBs must be intersecting
|
// Since no separating axis is found, the OBBs must be intersecting
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ JPH_NAMESPACE_BEGIN
|
||||||
class AABox;
|
class AABox;
|
||||||
|
|
||||||
/// Oriented box
|
/// Oriented box
|
||||||
class [[nodiscard]] OrientedBox
|
class JPH_EXPORT_GCC_BUG_WORKAROUND [[nodiscard]] OrientedBox
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
JPH_OVERRIDE_NEW_DELETE
|
JPH_OVERRIDE_NEW_DELETE
|
||||||
|
|
@ -26,12 +26,12 @@ public:
|
||||||
/// Construct from axis aligned box and transform. Only works for rotation/translation matrix (no scaling / shearing).
|
/// Construct from axis aligned box and transform. Only works for rotation/translation matrix (no scaling / shearing).
|
||||||
OrientedBox(Mat44Arg inOrientation, const AABox &inBox) : OrientedBox(inOrientation.PreTranslated(inBox.GetCenter()), inBox.GetExtent()) { }
|
OrientedBox(Mat44Arg inOrientation, const AABox &inBox) : OrientedBox(inOrientation.PreTranslated(inBox.GetCenter()), inBox.GetExtent()) { }
|
||||||
|
|
||||||
/// Test if oriented boxe overlaps with axis aligned box eachother
|
/// Test if oriented box overlaps with axis aligned box each other
|
||||||
bool Overlaps(const AABox &inBox, float inEpsilon = 1.0e-6f) const;
|
bool Overlaps(const AABox &inBox, float inEpsilon = 1.0e-6f) const;
|
||||||
|
|
||||||
/// Test if two oriented boxes overlap eachother
|
/// Test if two oriented boxes overlap each other
|
||||||
bool Overlaps(const OrientedBox &inBox, float inEpsilon = 1.0e-6f) const;
|
bool Overlaps(const OrientedBox &inBox, float inEpsilon = 1.0e-6f) const;
|
||||||
|
|
||||||
Mat44 mOrientation; ///< Transform that positions and rotates the local space axis aligned box into world space
|
Mat44 mOrientation; ///< Transform that positions and rotates the local space axis aligned box into world space
|
||||||
Vec3 mHalfExtents; ///< Half extents (half the size of the edge) of the local space axis aligned box
|
Vec3 mHalfExtents; ///< Half extents (half the size of the edge) of the local space axis aligned box
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,27 @@ public:
|
||||||
/// Offset the plane (positive value means move it in the direction of the plane normal)
|
/// Offset the plane (positive value means move it in the direction of the plane normal)
|
||||||
Plane Offset(float inDistance) const { return Plane(mNormalAndConstant - Vec4(Vec3::sZero(), inDistance)); }
|
Plane Offset(float inDistance) const { return Plane(mNormalAndConstant - Vec4(Vec3::sZero(), inDistance)); }
|
||||||
|
|
||||||
|
/// Transform the plane by a matrix
|
||||||
|
inline Plane GetTransformed(Mat44Arg inTransform) const
|
||||||
|
{
|
||||||
|
Vec3 transformed_normal = inTransform.Multiply3x3(GetNormal());
|
||||||
|
return Plane(transformed_normal, GetConstant() - inTransform.GetTranslation().Dot(transformed_normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale the plane, can handle non-uniform and negative scaling
|
||||||
|
inline Plane Scaled(Vec3Arg inScale) const
|
||||||
|
{
|
||||||
|
Vec3 scaled_normal = GetNormal() / inScale;
|
||||||
|
float scaled_normal_length = scaled_normal.Length();
|
||||||
|
return Plane(scaled_normal / scaled_normal_length, GetConstant() / scaled_normal_length);
|
||||||
|
}
|
||||||
|
|
||||||
/// Distance point to plane
|
/// Distance point to plane
|
||||||
float SignedDistance(Vec3Arg inPoint) const { return inPoint.Dot(GetNormal()) + GetConstant(); }
|
float SignedDistance(Vec3Arg inPoint) const { return inPoint.Dot(GetNormal()) + GetConstant(); }
|
||||||
|
|
||||||
|
/// Project inPoint onto the plane
|
||||||
|
Vec3 ProjectPointOnPlane(Vec3Arg inPoint) const { return inPoint - GetNormal() * SignedDistance(inPoint); }
|
||||||
|
|
||||||
/// Returns intersection point between 3 planes
|
/// Returns intersection point between 3 planes
|
||||||
static bool sIntersectPlanes(const Plane &inP1, const Plane &inP2, const Plane &inP3, Vec3 &outPoint)
|
static bool sIntersectPlanes(const Plane &inP1, const Plane &inP2, const Plane &inP3, Vec3 &outPoint)
|
||||||
{
|
{
|
||||||
|
|
@ -63,7 +81,7 @@ public:
|
||||||
// [aw*(bz*cy-by*cz)+ay*(bw*cz-bz*cw)+az*(by*cw-bw*cy)]
|
// [aw*(bz*cy-by*cz)+ay*(bw*cz-bz*cw)+az*(by*cw-bw*cy)]
|
||||||
// [aw*(bx*cz-bz*cx)+ax*(bz*cw-bw*cz)+az*(bw*cx-bx*cw)]
|
// [aw*(bx*cz-bz*cx)+ax*(bz*cw-bw*cz)+az*(bw*cx-bx*cw)]
|
||||||
// [aw*(by*cx-bx*cy)+ax*(bw*cy-by*cw)+ay*(bx*cw-bw*cx)]
|
// [aw*(by*cx-bx*cy)+ax*(bw*cy-by*cw)+ay*(bx*cw-bw*cx)]
|
||||||
Vec4 numerator =
|
Vec4 numerator =
|
||||||
a.SplatW() * (b.Swizzle<SWIZZLE_Z, SWIZZLE_X, SWIZZLE_Y, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_X, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_X, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Z, SWIZZLE_X, SWIZZLE_Y, SWIZZLE_UNUSED>())
|
a.SplatW() * (b.Swizzle<SWIZZLE_Z, SWIZZLE_X, SWIZZLE_Y, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_X, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_X, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Z, SWIZZLE_X, SWIZZLE_Y, SWIZZLE_UNUSED>())
|
||||||
+ a.Swizzle<SWIZZLE_Y, SWIZZLE_X, SWIZZLE_X, SWIZZLE_UNUSED>() * (b.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_Y, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_Y, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED>())
|
+ a.Swizzle<SWIZZLE_Y, SWIZZLE_X, SWIZZLE_X, SWIZZLE_UNUSED>() * (b.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_Y, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_Y, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED>())
|
||||||
+ a.Swizzle<SWIZZLE_Z, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_UNUSED>() * (b.Swizzle<SWIZZLE_Y, SWIZZLE_W, SWIZZLE_X, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_W, SWIZZLE_X, SWIZZLE_W, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_W, SWIZZLE_X, SWIZZLE_W, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Y, SWIZZLE_W, SWIZZLE_X, SWIZZLE_UNUSED>());
|
+ a.Swizzle<SWIZZLE_Z, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_UNUSED>() * (b.Swizzle<SWIZZLE_Y, SWIZZLE_W, SWIZZLE_X, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_W, SWIZZLE_X, SWIZZLE_W, SWIZZLE_UNUSED>() - b.Swizzle<SWIZZLE_W, SWIZZLE_X, SWIZZLE_W, SWIZZLE_UNUSED>() * c.Swizzle<SWIZZLE_Y, SWIZZLE_W, SWIZZLE_X, SWIZZLE_UNUSED>());
|
||||||
|
|
@ -73,6 +91,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef JPH_OBJECT_STREAM
|
||||||
|
friend void CreateRTTIPlane(class RTTI &); // For JPH_IMPLEMENT_SERIALIZABLE_OUTSIDE_CLASS
|
||||||
|
#endif
|
||||||
|
|
||||||
Vec4 mNormalAndConstant; ///< XYZ = normal, W = constant, plane: x . normal + constant = 0
|
Vec4 mNormalAndConstant; ///< XYZ = normal, W = constant, plane: x . normal + constant = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public:
|
||||||
mIsParallel = Vec3::sLessOrEqual(inDirection.Abs(), Vec3::sReplicate(1.0e-20f));
|
mIsParallel = Vec3::sLessOrEqual(inDirection.Abs(), Vec3::sReplicate(1.0e-20f));
|
||||||
|
|
||||||
// Calculate 1 / direction while avoiding division by zero
|
// Calculate 1 / direction while avoiding division by zero
|
||||||
mInvDirection = Vec3::sSelect(inDirection, Vec3::sReplicate(1.0f), mIsParallel).Reciprocal();
|
mInvDirection = Vec3::sSelect(inDirection, Vec3::sOne(), mIsParallel).Reciprocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 mInvDirection; ///< 1 / ray direction
|
Vec3 mInvDirection; ///< 1 / ray direction
|
||||||
|
|
@ -36,7 +36,7 @@ JPH_INLINE float RayAABox(Vec3Arg inOrigin, const RayInvDirection &inInvDirectio
|
||||||
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
||||||
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
||||||
|
|
||||||
// Test against all three axii simultaneously.
|
// Test against all three axes simultaneously.
|
||||||
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
||||||
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
||||||
|
|
||||||
|
|
@ -89,8 +89,8 @@ JPH_INLINE Vec4 RayAABox4(Vec3Arg inOrigin, const RayInvDirection &inInvDirectio
|
||||||
Vec4 invdirx = inInvDirection.mInvDirection.SplatX();
|
Vec4 invdirx = inInvDirection.mInvDirection.SplatX();
|
||||||
Vec4 invdiry = inInvDirection.mInvDirection.SplatY();
|
Vec4 invdiry = inInvDirection.mInvDirection.SplatY();
|
||||||
Vec4 invdirz = inInvDirection.mInvDirection.SplatZ();
|
Vec4 invdirz = inInvDirection.mInvDirection.SplatZ();
|
||||||
|
|
||||||
// Test against all three axii simultaneously.
|
// Test against all three axes simultaneously.
|
||||||
Vec4 t1x = (inBoundsMinX - originx) * invdirx;
|
Vec4 t1x = (inBoundsMinX - originx) * invdirx;
|
||||||
Vec4 t1y = (inBoundsMinY - originy) * invdiry;
|
Vec4 t1y = (inBoundsMinY - originy) * invdiry;
|
||||||
Vec4 t1z = (inBoundsMinZ - originz) * invdirz;
|
Vec4 t1z = (inBoundsMinZ - originz) * invdirz;
|
||||||
|
|
@ -139,7 +139,7 @@ JPH_INLINE void RayAABox(Vec3Arg inOrigin, const RayInvDirection &inInvDirection
|
||||||
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
||||||
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
||||||
|
|
||||||
// Test against all three axii simultaneously.
|
// Test against all three axes simultaneously.
|
||||||
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
||||||
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ JPH_INLINE bool RayAABoxHits(Vec3Arg inOrigin, const RayInvDirection &inInvDirec
|
||||||
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
|
||||||
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
|
||||||
|
|
||||||
// Test against all three axii simultaneously.
|
// Test against all three axes simultaneously.
|
||||||
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
|
||||||
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ JPH_INLINE bool RayAABoxHits(Vec3Arg inOrigin, Vec3Arg inDirection, Vec3Arg inBo
|
||||||
|
|
||||||
Vec3 diff = 2.0f * inOrigin - inBoundsMin - inBoundsMax;
|
Vec3 diff = 2.0f * inOrigin - inBoundsMin - inBoundsMax;
|
||||||
Vec3 abs_diff = diff.Abs();
|
Vec3 abs_diff = diff.Abs();
|
||||||
|
|
||||||
UVec4 no_intersection = UVec4::sAnd(Vec3::sGreater(abs_diff, extents), Vec3::sGreaterOrEqual(diff * inDirection, Vec3::sZero()));
|
UVec4 no_intersection = UVec4::sAnd(Vec3::sGreater(abs_diff, extents), Vec3::sGreaterOrEqual(diff * inDirection, Vec3::sZero()));
|
||||||
|
|
||||||
Vec3 abs_dir = inDirection.Abs();
|
Vec3 abs_dir = inDirection.Abs();
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
|
||||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Jolt/Math/Vec8.h>
|
|
||||||
#include <Jolt/Geometry/RayAABox.h>
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
/// Intersect 8 AABBs with ray, returns minimal distance along ray or FLT_MAX if no hit
|
|
||||||
/// Note: Can return negative value if ray starts in box
|
|
||||||
JPH_INLINE Vec8 RayAABox8(Vec3Arg inOrigin, const RayInvDirection &inInvDirection, Vec8Arg inBoundsMinX, Vec8Arg inBoundsMinY, Vec8Arg inBoundsMinZ, Vec8Arg inBoundsMaxX, Vec8Arg inBoundsMaxY, Vec8Arg inBoundsMaxZ)
|
|
||||||
{
|
|
||||||
// Constants
|
|
||||||
Vec8 flt_min = Vec8::sReplicate(-FLT_MAX);
|
|
||||||
Vec8 flt_max = Vec8::sReplicate(FLT_MAX);
|
|
||||||
|
|
||||||
// Origin
|
|
||||||
Vec8 originx = Vec8::sSplatX(Vec4(inOrigin));
|
|
||||||
Vec8 originy = Vec8::sSplatY(Vec4(inOrigin));
|
|
||||||
Vec8 originz = Vec8::sSplatZ(Vec4(inOrigin));
|
|
||||||
|
|
||||||
// Parallel
|
|
||||||
UVec8 parallelx = UVec8::sSplatX(inInvDirection.mIsParallel);
|
|
||||||
UVec8 parallely = UVec8::sSplatY(inInvDirection.mIsParallel);
|
|
||||||
UVec8 parallelz = UVec8::sSplatZ(inInvDirection.mIsParallel);
|
|
||||||
|
|
||||||
// Inverse direction
|
|
||||||
Vec8 invdirx = Vec8::sSplatX(Vec4(inInvDirection.mInvDirection));
|
|
||||||
Vec8 invdiry = Vec8::sSplatY(Vec4(inInvDirection.mInvDirection));
|
|
||||||
Vec8 invdirz = Vec8::sSplatZ(Vec4(inInvDirection.mInvDirection));
|
|
||||||
|
|
||||||
// Test against all three axii simultaneously.
|
|
||||||
Vec8 t1x = (inBoundsMinX - originx) * invdirx;
|
|
||||||
Vec8 t1y = (inBoundsMinY - originy) * invdiry;
|
|
||||||
Vec8 t1z = (inBoundsMinZ - originz) * invdirz;
|
|
||||||
Vec8 t2x = (inBoundsMaxX - originx) * invdirx;
|
|
||||||
Vec8 t2y = (inBoundsMaxY - originy) * invdiry;
|
|
||||||
Vec8 t2z = (inBoundsMaxZ - originz) * invdirz;
|
|
||||||
|
|
||||||
// Compute the max of min(t1,t2) and the min of max(t1,t2) ensuring we don't
|
|
||||||
// use the results from any directions parallel to the slab.
|
|
||||||
Vec8 t_minx = Vec8::sSelect(Vec8::sMin(t1x, t2x), flt_min, parallelx);
|
|
||||||
Vec8 t_miny = Vec8::sSelect(Vec8::sMin(t1y, t2y), flt_min, parallely);
|
|
||||||
Vec8 t_minz = Vec8::sSelect(Vec8::sMin(t1z, t2z), flt_min, parallelz);
|
|
||||||
Vec8 t_maxx = Vec8::sSelect(Vec8::sMax(t1x, t2x), flt_max, parallelx);
|
|
||||||
Vec8 t_maxy = Vec8::sSelect(Vec8::sMax(t1y, t2y), flt_max, parallely);
|
|
||||||
Vec8 t_maxz = Vec8::sSelect(Vec8::sMax(t1z, t2z), flt_max, parallelz);
|
|
||||||
|
|
||||||
// t_min.xyz = maximum(t_min.x, t_min.y, t_min.z);
|
|
||||||
Vec8 t_min = Vec8::sMax(Vec8::sMax(t_minx, t_miny), t_minz);
|
|
||||||
|
|
||||||
// t_max.xyz = minimum(t_max.x, t_max.y, t_max.z);
|
|
||||||
Vec8 t_max = Vec8::sMin(Vec8::sMin(t_maxx, t_maxy), t_maxz);
|
|
||||||
|
|
||||||
// if (t_min > t_max) return FLT_MAX;
|
|
||||||
UVec8 no_intersection = Vec8::sGreater(t_min, t_max);
|
|
||||||
|
|
||||||
// if (t_max < 0.0f) return FLT_MAX;
|
|
||||||
no_intersection = UVec8::sOr(no_intersection, Vec8::sLess(t_max, Vec8::sZero()));
|
|
||||||
|
|
||||||
// if bounds are invalid return FLOAT_MAX;
|
|
||||||
UVec8 bounds_invalid = UVec8::sOr(UVec8::sOr(Vec8::sGreater(inBoundsMinX, inBoundsMaxX), Vec8::sGreater(inBoundsMinY, inBoundsMaxY)), Vec8::sGreater(inBoundsMinZ, inBoundsMaxZ));
|
|
||||||
no_intersection = UVec8::sOr(no_intersection, bounds_invalid);
|
|
||||||
|
|
||||||
// if (inInvDirection.mIsParallel && !(Min <= inOrigin && inOrigin <= Max)) return FLT_MAX; else return t_min;
|
|
||||||
UVec8 no_parallel_overlapx = UVec8::sAnd(parallelx, UVec8::sOr(Vec8::sLess(originx, inBoundsMinX), Vec8::sGreater(originx, inBoundsMaxX)));
|
|
||||||
UVec8 no_parallel_overlapy = UVec8::sAnd(parallely, UVec8::sOr(Vec8::sLess(originy, inBoundsMinY), Vec8::sGreater(originy, inBoundsMaxY)));
|
|
||||||
UVec8 no_parallel_overlapz = UVec8::sAnd(parallelz, UVec8::sOr(Vec8::sLess(originz, inBoundsMinZ), Vec8::sGreater(originz, inBoundsMaxZ)));
|
|
||||||
no_intersection = UVec8::sOr(no_intersection, UVec8::sOr(UVec8::sOr(no_parallel_overlapx, no_parallel_overlapy), no_parallel_overlapz));
|
|
||||||
return Vec8::sSelect(t_min, flt_max, no_intersection);
|
|
||||||
}
|
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
JPH_NAMESPACE_BEGIN
|
||||||
|
|
||||||
/// Tests a ray starting at inRayOrigin and extending infinitely in inRayDirection against a sphere,
|
/// Tests a ray starting at inRayOrigin and extending infinitely in inRayDirection against a sphere,
|
||||||
/// @return FLT_MAX if there is no intersection, otherwise the fraction along the ray.
|
/// @return FLT_MAX if there is no intersection, otherwise the fraction along the ray.
|
||||||
/// @param inRayOrigin Ray origin. If the ray starts inside the sphere, the returned fraction will be 0.
|
/// @param inRayOrigin Ray origin. If the ray starts inside the sphere, the returned fraction will be 0.
|
||||||
/// @param inRayDirection Ray direction. Does not need to be normalized.
|
/// @param inRayDirection Ray direction. Does not need to be normalized.
|
||||||
|
|
@ -27,7 +27,7 @@ JPH_INLINE float RaySphere(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, Vec3Arg
|
||||||
|
|
||||||
// Sort so that the smallest is first
|
// Sort so that the smallest is first
|
||||||
if (fraction1 > fraction2)
|
if (fraction1 > fraction2)
|
||||||
swap(fraction1, fraction2);
|
std::swap(fraction1, fraction2);
|
||||||
|
|
||||||
// Test solution with lowest fraction, this will be the ray entering the sphere
|
// Test solution with lowest fraction, this will be the ray entering the sphere
|
||||||
if (fraction1 >= 0.0f)
|
if (fraction1 >= 0.0f)
|
||||||
|
|
@ -41,7 +41,7 @@ JPH_INLINE float RaySphere(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, Vec3Arg
|
||||||
return FLT_MAX;
|
return FLT_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests a ray starting at inRayOrigin and extending infinitely in inRayDirection against a sphere.
|
/// Tests a ray starting at inRayOrigin and extending infinitely in inRayDirection against a sphere.
|
||||||
/// Outputs entry and exit points (outMinFraction and outMaxFraction) along the ray (which could be negative if the hit point is before the start of the ray).
|
/// Outputs entry and exit points (outMinFraction and outMaxFraction) along the ray (which could be negative if the hit point is before the start of the ray).
|
||||||
/// @param inRayOrigin Ray origin. If the ray starts inside the sphere, the returned fraction will be 0.
|
/// @param inRayOrigin Ray origin. If the ray starts inside the sphere, the returned fraction will be 0.
|
||||||
/// @param inRayDirection Ray direction. Does not need to be normalized.
|
/// @param inRayDirection Ray direction. Does not need to be normalized.
|
||||||
|
|
@ -85,7 +85,7 @@ JPH_INLINE int RaySphere(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, Vec3Arg in
|
||||||
|
|
||||||
// Sort so that the smallest is first
|
// Sort so that the smallest is first
|
||||||
if (fraction1 > fraction2)
|
if (fraction1 > fraction2)
|
||||||
swap(fraction1, fraction2);
|
std::swap(fraction1, fraction2);
|
||||||
|
|
||||||
outMinFraction = fraction1;
|
outMinFraction = fraction1;
|
||||||
outMaxFraction = fraction2;
|
outMaxFraction = fraction2;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ JPH_INLINE float RayTriangle(Vec3Arg inOrigin, Vec3Arg inDirection, Vec3Arg inV0
|
||||||
|
|
||||||
// Zero & one
|
// Zero & one
|
||||||
Vec3 zero = Vec3::sZero();
|
Vec3 zero = Vec3::sZero();
|
||||||
Vec3 one = Vec3::sReplicate(1.0f);
|
Vec3 one = Vec3::sOne();
|
||||||
|
|
||||||
// Find vectors for two edges sharing inV0
|
// Find vectors for two edges sharing inV0
|
||||||
Vec3 e1 = inV1 - inV0;
|
Vec3 e1 = inV1 - inV0;
|
||||||
|
|
@ -31,7 +31,7 @@ JPH_INLINE float RayTriangle(Vec3Arg inOrigin, Vec3Arg inDirection, Vec3Arg inV0
|
||||||
UVec4 det_near_zero = Vec3::sLess(det.Abs(), epsilon);
|
UVec4 det_near_zero = Vec3::sLess(det.Abs(), epsilon);
|
||||||
|
|
||||||
// When the determinant is near zero, set it to one to avoid dividing by zero
|
// When the determinant is near zero, set it to one to avoid dividing by zero
|
||||||
det = Vec3::sSelect(det, Vec3::sReplicate(1.0f), det_near_zero);
|
det = Vec3::sSelect(det, Vec3::sOne(), det_near_zero);
|
||||||
|
|
||||||
// Calculate distance from inV0 to ray origin
|
// Calculate distance from inV0 to ray origin
|
||||||
Vec3 s = inOrigin - inV0;
|
Vec3 s = inOrigin - inV0;
|
||||||
|
|
@ -49,7 +49,7 @@ JPH_INLINE float RayTriangle(Vec3Arg inOrigin, Vec3Arg inDirection, Vec3Arg inV0
|
||||||
Vec3 t = Vec3::sReplicate(e2.Dot(q)) / det;
|
Vec3 t = Vec3::sReplicate(e2.Dot(q)) / det;
|
||||||
|
|
||||||
// Check if there is an intersection
|
// Check if there is an intersection
|
||||||
UVec4 no_intersection =
|
UVec4 no_intersection =
|
||||||
UVec4::sOr
|
UVec4::sOr
|
||||||
(
|
(
|
||||||
UVec4::sOr
|
UVec4::sOr
|
||||||
|
|
@ -61,10 +61,10 @@ JPH_INLINE float RayTriangle(Vec3Arg inOrigin, Vec3Arg inDirection, Vec3Arg inV0
|
||||||
),
|
),
|
||||||
UVec4::sOr
|
UVec4::sOr
|
||||||
(
|
(
|
||||||
Vec3::sLess(v, zero),
|
Vec3::sLess(v, zero),
|
||||||
Vec3::sGreater(u + v, one)
|
Vec3::sGreater(u + v, one)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Vec3::sLess(t, zero)
|
Vec3::sLess(t, zero)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ JPH_INLINE Vec4 RayTriangle4(Vec3Arg inOrigin, Vec3Arg inDirection, Vec4Arg inV0
|
||||||
UVec4 det_near_zero = Vec4::sLess(det, epsilon);
|
UVec4 det_near_zero = Vec4::sLess(det, epsilon);
|
||||||
|
|
||||||
// Set components of the determinant to 1 that are near zero to avoid dividing by zero
|
// Set components of the determinant to 1 that are near zero to avoid dividing by zero
|
||||||
det = Vec4::sSelect(det, Vec4::sReplicate(1.0f), det_near_zero);
|
det = Vec4::sSelect(det, Vec4::sOne(), det_near_zero);
|
||||||
|
|
||||||
// Calculate distance from inV0 to ray origin
|
// Calculate distance from inV0 to ray origin
|
||||||
Vec4 sx = inOrigin.SplatX() - inV0X;
|
Vec4 sx = inOrigin.SplatX() - inV0X;
|
||||||
|
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
|
||||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Jolt/Math/Vec8.h>
|
|
||||||
|
|
||||||
JPH_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
/// Intersect ray with 8 triangles in SOA format, returns 8 vector of closest points or FLT_MAX if no hit
|
|
||||||
JPH_INLINE Vec8 RayTriangle8(Vec3Arg inOrigin, Vec3Arg inDirection, Vec8Arg inV0X, Vec8Arg inV0Y, Vec8Arg inV0Z, Vec8Arg inV1X, Vec8Arg inV1Y, Vec8Arg inV1Z, Vec8Arg inV2X, Vec8Arg inV2Y, Vec8Arg inV2Z)
|
|
||||||
{
|
|
||||||
// Epsilon
|
|
||||||
Vec8 epsilon = Vec8::sReplicate(1.0e-12f);
|
|
||||||
|
|
||||||
// Zero & one
|
|
||||||
Vec8 zero = Vec8::sZero();
|
|
||||||
Vec8 one = Vec8::sReplicate(1.0f);
|
|
||||||
|
|
||||||
// Find vectors for two edges sharing inV0
|
|
||||||
Vec8 e1x = inV1X - inV0X;
|
|
||||||
Vec8 e1y = inV1Y - inV0Y;
|
|
||||||
Vec8 e1z = inV1Z - inV0Z;
|
|
||||||
Vec8 e2x = inV2X - inV0X;
|
|
||||||
Vec8 e2y = inV2Y - inV0Y;
|
|
||||||
Vec8 e2z = inV2Z - inV0Z;
|
|
||||||
|
|
||||||
// Get direction vector components
|
|
||||||
Vec8 dx = Vec8::sSplatX(Vec4(inDirection));
|
|
||||||
Vec8 dy = Vec8::sSplatY(Vec4(inDirection));
|
|
||||||
Vec8 dz = Vec8::sSplatZ(Vec4(inDirection));
|
|
||||||
|
|
||||||
// Begin calculating determinant - also used to calculate u parameter
|
|
||||||
Vec8 px = dy * e2z - dz * e2y;
|
|
||||||
Vec8 py = dz * e2x - dx * e2z;
|
|
||||||
Vec8 pz = dx * e2y - dy * e2x;
|
|
||||||
|
|
||||||
// if determinant is near zero, ray lies in plane of triangle
|
|
||||||
Vec8 det = e1x * px + e1y * py + e1z * pz;
|
|
||||||
|
|
||||||
// Check which determinants are near zero
|
|
||||||
UVec8 det_near_zero = Vec8::sLess(det.Abs(), epsilon);
|
|
||||||
|
|
||||||
// Set components of the determinant to 1 that are near zero to avoid dividing by zero
|
|
||||||
det = Vec8::sSelect(det, Vec8::sReplicate(1.0f), det_near_zero);
|
|
||||||
|
|
||||||
// Calculate distance from inV0 to ray origin
|
|
||||||
Vec8 sx = Vec8::sSplatX(Vec4(inOrigin)) - inV0X;
|
|
||||||
Vec8 sy = Vec8::sSplatY(Vec4(inOrigin)) - inV0Y;
|
|
||||||
Vec8 sz = Vec8::sSplatZ(Vec4(inOrigin)) - inV0Z;
|
|
||||||
|
|
||||||
// Calculate u parameter and flip sign if determinant was negative
|
|
||||||
Vec8 u = (sx * px + sy * py + sz * pz) / det;
|
|
||||||
|
|
||||||
// Prepare to test v parameter
|
|
||||||
Vec8 qx = sy * e1z - sz * e1y;
|
|
||||||
Vec8 qy = sz * e1x - sx * e1z;
|
|
||||||
Vec8 qz = sx * e1y - sy * e1x;
|
|
||||||
|
|
||||||
// Calculate v parameter and flip sign if determinant was negative
|
|
||||||
Vec8 v = (dx * qx + dy * qy + dz * qz) / det;
|
|
||||||
|
|
||||||
// Get intersection point and flip sign if determinant was negative
|
|
||||||
Vec8 t = (e2x * qx + e2y * qy + e2z * qz) / det;
|
|
||||||
|
|
||||||
// Check if there is an intersection
|
|
||||||
UVec8 no_intersection =
|
|
||||||
UVec8::sOr
|
|
||||||
(
|
|
||||||
UVec8::sOr
|
|
||||||
(
|
|
||||||
UVec8::sOr
|
|
||||||
(
|
|
||||||
det_near_zero,
|
|
||||||
Vec8::sLess(u, zero)
|
|
||||||
),
|
|
||||||
UVec8::sOr
|
|
||||||
(
|
|
||||||
Vec8::sLess(v, zero),
|
|
||||||
Vec8::sGreater(u + v, one)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Vec8::sLess(t, zero)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Select intersection point or FLT_MAX based on if there is an intersection or not
|
|
||||||
return Vec8::sSelect(t, Vec8::sReplicate(FLT_MAX), no_intersection);
|
|
||||||
}
|
|
||||||
|
|
||||||
JPH_NAMESPACE_END
|
|
||||||
|
|
@ -26,13 +26,13 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
inline Vec3 GetCenter() const { return Vec3::sLoadFloat3Unsafe(mCenter); }
|
inline Vec3 GetCenter() const { return Vec3::sLoadFloat3Unsafe(mCenter); }
|
||||||
inline float GetRadius() const { return mRadius; }
|
inline float GetRadius() const { return mRadius; }
|
||||||
|
|
||||||
/// Test if two spheres overlap
|
/// Test if two spheres overlap
|
||||||
inline bool Overlaps(const Sphere &inB) const
|
inline bool Overlaps(const Sphere &inB) const
|
||||||
{
|
{
|
||||||
return (Vec3::sLoadFloat3Unsafe(mCenter) - Vec3::sLoadFloat3Unsafe(inB.mCenter)).LengthSq() <= Square(mRadius + inB.mRadius);
|
return (Vec3::sLoadFloat3Unsafe(mCenter) - Vec3::sLoadFloat3Unsafe(inB.mCenter)).LengthSq() <= Square(mRadius + inB.mRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this sphere overlaps with a box
|
/// Check if this sphere overlaps with a box
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue