Repository: ThePhysicsGuys/Physics3D
Branch: master
Commit: 00691dffa552
Files: 548
Total size: 2.1 MB
Directory structure:
gitextract_pl6iqfhu/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── msvcBuild.yml
│ └── ubuntuBuild.yml
├── .gitignore
├── CMakeLists.txt
├── LICENSE.md
├── Physics3D/
│ ├── CMakeLists.txt
│ ├── Physics3D.vcxproj
│ ├── boundstree/
│ │ ├── boundsTree.cpp
│ │ ├── boundsTree.h
│ │ ├── boundsTreeAVX.cpp
│ │ ├── boundsTreeSSE.cpp
│ │ └── filters/
│ │ ├── outOfBoundsFilter.h
│ │ ├── rayIntersectsBoundsFilter.h
│ │ ├── visibilityFilter.cpp
│ │ └── visibilityFilter.h
│ ├── colissionBuffer.h
│ ├── constraints/
│ │ ├── ballConstraint.cpp
│ │ ├── ballConstraint.h
│ │ ├── barConstraint.cpp
│ │ ├── barConstraint.h
│ │ ├── constraint.cpp
│ │ ├── constraint.h
│ │ ├── constraintGroup.cpp
│ │ ├── constraintGroup.h
│ │ ├── constraintImpl.h
│ │ ├── hingeConstraint.cpp
│ │ └── hingeConstraint.h
│ ├── datastructures/
│ │ ├── alignedPtr.h
│ │ ├── aligned_alloc.cpp
│ │ ├── aligned_alloc.h
│ │ ├── buffers.h
│ │ ├── compactPtrDataPair.h
│ │ ├── iteratorEnd.h
│ │ ├── iteratorFactory.h
│ │ ├── monotonicTree.h
│ │ ├── parallelArray.h
│ │ ├── sharedArray.h
│ │ ├── smartPointers.h
│ │ ├── uniqueArrayPtr.h
│ │ ├── unmanagedArray.h
│ │ └── unorderedVector.h
│ ├── externalforces/
│ │ ├── directionalGravity.cpp
│ │ ├── directionalGravity.h
│ │ ├── externalForce.cpp
│ │ ├── externalForce.h
│ │ ├── magnetForce.cpp
│ │ └── magnetForce.h
│ ├── geometry/
│ │ ├── builtinShapeClasses.cpp
│ │ ├── builtinShapeClasses.h
│ │ ├── computationBuffer.cpp
│ │ ├── computationBuffer.h
│ │ ├── convexShapeBuilder.cpp
│ │ ├── convexShapeBuilder.h
│ │ ├── genericCollidable.h
│ │ ├── genericIntersection.cpp
│ │ ├── genericIntersection.h
│ │ ├── indexedShape.cpp
│ │ ├── indexedShape.h
│ │ ├── intersection.cpp
│ │ ├── intersection.h
│ │ ├── polyhedron.cpp
│ │ ├── polyhedron.h
│ │ ├── scalableInertialMatrix.h
│ │ ├── shape.cpp
│ │ ├── shape.h
│ │ ├── shapeBuilder.cpp
│ │ ├── shapeBuilder.h
│ │ ├── shapeClass.cpp
│ │ ├── shapeClass.h
│ │ ├── shapeCreation.cpp
│ │ ├── shapeCreation.h
│ │ ├── shapeLibrary.cpp
│ │ ├── shapeLibrary.h
│ │ ├── triangleMesh.cpp
│ │ ├── triangleMesh.h
│ │ ├── triangleMeshAVX.cpp
│ │ ├── triangleMeshCommon.h
│ │ ├── triangleMeshSSE.cpp
│ │ └── triangleMeshSSE4.cpp
│ ├── hardconstraints/
│ │ ├── constraintTemplates.h
│ │ ├── controller/
│ │ │ ├── constController.h
│ │ │ ├── sineWaveController.cpp
│ │ │ └── sineWaveController.h
│ │ ├── fixedConstraint.cpp
│ │ ├── fixedConstraint.h
│ │ ├── hardConstraint.cpp
│ │ ├── hardConstraint.h
│ │ ├── hardPhysicalConnection.cpp
│ │ ├── hardPhysicalConnection.h
│ │ ├── motorConstraint.cpp
│ │ ├── motorConstraint.h
│ │ └── sinusoidalPistonConstraint.h
│ ├── inertia.cpp
│ ├── inertia.h
│ ├── layer.cpp
│ ├── layer.h
│ ├── math/
│ │ ├── boundingBox.h
│ │ ├── bounds.h
│ │ ├── cframe.h
│ │ ├── constants.h
│ │ ├── fix.h
│ │ ├── globalCFrame.h
│ │ ├── globalTransform.h
│ │ ├── linalg/
│ │ │ ├── commonMatrices.h
│ │ │ ├── eigen.cpp
│ │ │ ├── eigen.h
│ │ │ ├── largeMatrix.h
│ │ │ ├── largeMatrixAlgorithms.h
│ │ │ ├── mat.h
│ │ │ ├── quat.h
│ │ │ ├── trigonometry.cpp
│ │ │ ├── trigonometry.h
│ │ │ └── vec.h
│ │ ├── mathUtil.h
│ │ ├── position.h
│ │ ├── predefinedTaylorExpansions.h
│ │ ├── ray.h
│ │ ├── rotation.h
│ │ ├── taylorExpansion.h
│ │ ├── transform.h
│ │ └── utils.h
│ ├── misc/
│ │ ├── catchable_assert.h
│ │ ├── cpuid.cpp
│ │ ├── cpuid.h
│ │ ├── debug.cpp
│ │ ├── debug.h
│ │ ├── physicsProfiler.cpp
│ │ ├── physicsProfiler.h
│ │ ├── profiling.h
│ │ ├── serialization/
│ │ │ ├── dynamicSerialize.h
│ │ │ ├── serialization.cpp
│ │ │ ├── serialization.h
│ │ │ ├── serializeBasicTypes.cpp
│ │ │ ├── serializeBasicTypes.h
│ │ │ └── sharedObjectSerializer.h
│ │ ├── toString.h
│ │ ├── unreachable.h
│ │ ├── validityHelper.cpp
│ │ └── validityHelper.h
│ ├── motion.h
│ ├── part.cpp
│ ├── part.h
│ ├── physical.cpp
│ ├── physical.h
│ ├── relativeMotion.h
│ ├── rigidBody.cpp
│ ├── rigidBody.h
│ ├── softlinks/
│ │ ├── alignmentLink.cpp
│ │ ├── alignmentLink.h
│ │ ├── elasticLink.cpp
│ │ ├── elasticLink.h
│ │ ├── magneticLink.cpp
│ │ ├── magneticLink.h
│ │ ├── softLink.cpp
│ │ ├── softLink.h
│ │ ├── springLink.cpp
│ │ └── springLink.h
│ ├── threading/
│ │ ├── physicsThread.cpp
│ │ ├── physicsThread.h
│ │ ├── sharedLockGuard.h
│ │ ├── threadPool.h
│ │ ├── upgradeableMutex.cpp
│ │ └── upgradeableMutex.h
│ ├── world.cpp
│ ├── world.h
│ ├── worldIteration.h
│ ├── worldPhysics.cpp
│ └── worldPhysics.h
├── Physics3D.sln
├── README.md
├── _config.yml
├── application/
│ ├── application.cpp
│ ├── application.h
│ ├── application.rc
│ ├── application.vcxproj
│ ├── builtinWorlds.cpp
│ ├── builtinWorlds.h
│ ├── core.cpp
│ ├── core.h
│ ├── debugData.natvis
│ ├── ecs/
│ │ ├── components.h
│ │ └── entityBuilder.h
│ ├── eventHandler.cpp
│ ├── eventHandler.h
│ ├── extendedPart.cpp
│ ├── extendedPart.h
│ ├── input/
│ │ ├── playerController.cpp
│ │ ├── playerController.h
│ │ ├── standardInputHandler.cpp
│ │ └── standardInputHandler.h
│ ├── io/
│ │ ├── saveDialog.cpp
│ │ ├── saveDialog.h
│ │ ├── serialization.cpp
│ │ └── serialization.h
│ ├── layer/
│ │ ├── cameraLayer.cpp
│ │ ├── cameraLayer.h
│ │ ├── constraintLayer.cpp
│ │ ├── constraintLayer.h
│ │ ├── debugLayer.cpp
│ │ ├── debugLayer.h
│ │ ├── debugOverlay.cpp
│ │ ├── debugOverlay.h
│ │ ├── guiLayer.cpp
│ │ ├── guiLayer.h
│ │ ├── imguiLayer.cpp
│ │ ├── imguiLayer.h
│ │ ├── modelLayer.cpp
│ │ ├── modelLayer.h
│ │ ├── pickerLayer.cpp
│ │ ├── pickerLayer.h
│ │ ├── postprocessLayer.cpp
│ │ ├── postprocessLayer.h
│ │ ├── shadowLayer.cpp
│ │ ├── shadowLayer.h
│ │ ├── skyboxLayer.cpp
│ │ ├── skyboxLayer.h
│ │ ├── testLayer.cpp
│ │ └── testLayer.h
│ ├── legacy/
│ │ └── frames.h
│ ├── math.natvis
│ ├── picker/
│ │ ├── ray.cpp
│ │ ├── ray.h
│ │ ├── selection.cpp
│ │ ├── selection.h
│ │ └── tools/
│ │ ├── alignmentLinkTool.cpp
│ │ ├── alignmentLinkTool.h
│ │ ├── attachmentTool.cpp
│ │ ├── attachmentTool.h
│ │ ├── elasticLinkTool.cpp
│ │ ├── elasticLinkTool.h
│ │ ├── fixedConstraintTool.cpp
│ │ ├── fixedConstraintTool.h
│ │ ├── magneticLinkTool.cpp
│ │ ├── magneticLinkTool.h
│ │ ├── motorConstraintTool.cpp
│ │ ├── motorConstraintTool.h
│ │ ├── pathTool.cpp
│ │ ├── pathTool.h
│ │ ├── pistonConstraintTool.cpp
│ │ ├── pistonConstraintTool.h
│ │ ├── regionSelectionTool.cpp
│ │ ├── regionSelectionTool.h
│ │ ├── rotationTool.cpp
│ │ ├── rotationTool.h
│ │ ├── scaleTool.cpp
│ │ ├── scaleTool.h
│ │ ├── selectionTool.cpp
│ │ ├── selectionTool.h
│ │ ├── springLinkTool.cpp
│ │ ├── springLinkTool.h
│ │ ├── toolSpacing.h
│ │ ├── translationTool.cpp
│ │ └── translationTool.h
│ ├── resource.h
│ ├── resources.cpp
│ ├── resources.h
│ ├── shader/
│ │ ├── basicShader.cpp
│ │ ├── basicShader.h
│ │ ├── shaderBase.cpp
│ │ ├── shaderBase.h
│ │ ├── shaders.cpp
│ │ └── shaders.h
│ ├── view/
│ │ ├── camera.cpp
│ │ ├── camera.h
│ │ ├── debugFrame.cpp
│ │ ├── debugFrame.h
│ │ ├── ecsFrame.cpp
│ │ ├── ecsFrame.h
│ │ ├── environmentFrame.cpp
│ │ ├── environmentFrame.h
│ │ ├── frames.cpp
│ │ ├── frames.h
│ │ ├── layerFrame.cpp
│ │ ├── layerFrame.h
│ │ ├── propertiesFrame.cpp
│ │ ├── propertiesFrame.h
│ │ ├── resourceFrame.cpp
│ │ ├── resourceFrame.h
│ │ ├── screen.cpp
│ │ ├── screen.h
│ │ ├── toolbarFrame.cpp
│ │ └── toolbarFrame.h
│ ├── view.natvis
│ ├── worldBuilder.cpp
│ ├── worldBuilder.h
│ ├── worlds.cpp
│ └── worlds.h
├── benchmarks/
│ ├── basicWorld.cpp
│ ├── benchmark.cpp
│ ├── benchmark.h
│ ├── benchmarks.vcxproj
│ ├── complexObjectBenchmark.cpp
│ ├── ecsBenchmark.cpp
│ ├── getBoundsPerformance.cpp
│ ├── manyCubesBenchmark.cpp
│ ├── rotationBenchmark.cpp
│ ├── threadResponseTime.cpp
│ ├── worldBenchmark.cpp
│ └── worldBenchmark.h
├── engine/
│ ├── core.cpp
│ ├── core.h
│ ├── ecs/
│ │ └── registry.h
│ ├── engine.vcxproj
│ ├── event/
│ │ ├── event.h
│ │ ├── keyEvent.h
│ │ ├── mouseEvent.h
│ │ └── windowEvent.h
│ ├── input/
│ │ ├── inputHandler.cpp
│ │ ├── inputHandler.h
│ │ ├── keyboard.cpp
│ │ ├── keyboard.h
│ │ ├── modifiers.cpp
│ │ ├── modifiers.h
│ │ ├── mouse.cpp
│ │ └── mouse.h
│ ├── io/
│ │ ├── export.cpp
│ │ ├── export.h
│ │ ├── import.cpp
│ │ └── import.h
│ ├── layer/
│ │ ├── layer.h
│ │ ├── layerStack.cpp
│ │ └── layerStack.h
│ ├── options/
│ │ ├── keyboardOptions.cpp
│ │ └── keyboardOptions.h
│ ├── resource/
│ │ ├── meshResource.cpp
│ │ └── meshResource.h
│ └── tool/
│ ├── buttonTool.h
│ ├── stateTool.h
│ ├── tool.h
│ ├── toolManager.cpp
│ └── toolManager.h
├── examples/
│ ├── CMakeLists.txt
│ ├── examples.vcxproj
│ └── openglBasic.cpp
├── graphics/
│ ├── batch/
│ │ ├── batch.h
│ │ ├── batchConfig.h
│ │ ├── commandBatch.h
│ │ ├── guiBatch.h
│ │ ├── instanceBatch.h
│ │ └── instanceBatchManager.h
│ ├── bindable.cpp
│ ├── bindable.h
│ ├── buffers/
│ │ ├── bufferLayout.cpp
│ │ ├── bufferLayout.h
│ │ ├── frameBuffer.cpp
│ │ ├── frameBuffer.h
│ │ ├── indexBuffer.cpp
│ │ ├── indexBuffer.h
│ │ ├── renderBuffer.cpp
│ │ ├── renderBuffer.h
│ │ ├── vertexArray.cpp
│ │ ├── vertexArray.h
│ │ ├── vertexBuffer.cpp
│ │ └── vertexBuffer.h
│ ├── component.natvis
│ ├── core.cpp
│ ├── core.h
│ ├── debug/
│ │ ├── guiDebug.cpp
│ │ ├── guiDebug.h
│ │ ├── profilerUI.cpp
│ │ ├── profilerUI.h
│ │ ├── threePhaseBuffer.h
│ │ ├── visualDebug.cpp
│ │ └── visualDebug.h
│ ├── ecs/
│ │ ├── components.h
│ │ └── materials.h
│ ├── extendedTriangleMesh.cpp
│ ├── extendedTriangleMesh.h
│ ├── font.cpp
│ ├── font.h
│ ├── glfwUtils.cpp
│ ├── glfwUtils.h
│ ├── graphics.vcxproj
│ ├── gui/
│ │ ├── color.h
│ │ ├── gui.cpp
│ │ ├── gui.h
│ │ ├── guiUtils.cpp
│ │ ├── guiUtils.h
│ │ ├── imgui/
│ │ │ ├── imguiExtension.h
│ │ │ ├── imguiStyle.cpp
│ │ │ ├── imguiStyle.h
│ │ │ ├── legacy_imgui_impl_glfw.cpp
│ │ │ ├── legacy_imgui_impl_glfw.h
│ │ │ ├── legacy_imgui_impl_opengl3.cpp
│ │ │ └── legacy_imgui_impl_opengl3.h
│ │ └── orderedVector.h
│ ├── legacy/
│ │ ├── button.cpp
│ │ ├── button.h
│ │ ├── checkBox.cpp
│ │ ├── checkBox.h
│ │ ├── colorPicker.cpp
│ │ ├── colorPicker.h
│ │ ├── component.h
│ │ ├── container.cpp
│ │ ├── container.h
│ │ ├── cshader.cpp
│ │ ├── cshader.h
│ │ ├── directionEditor.cpp
│ │ ├── directionEditor.h
│ │ ├── frame.cpp
│ │ ├── frame.h
│ │ ├── gshader.cpp
│ │ ├── gshader.h
│ │ ├── image.cpp
│ │ ├── image.h
│ │ ├── label.cpp
│ │ ├── label.h
│ │ ├── layout.cpp
│ │ ├── layout.h
│ │ ├── panel.cpp
│ │ ├── panel.h
│ │ ├── shader.cpp
│ │ ├── shader.h
│ │ ├── shaderLexer.cpp
│ │ ├── shaderLexer.h
│ │ ├── shaderParser.cpp
│ │ ├── shaderParser.h
│ │ ├── slider.cpp
│ │ ├── slider.h
│ │ ├── text.cpp
│ │ └── text.h
│ ├── mesh/
│ │ ├── abstractMesh.cpp
│ │ ├── abstractMesh.h
│ │ ├── arrayMesh.cpp
│ │ ├── arrayMesh.h
│ │ ├── indexedMesh.cpp
│ │ ├── indexedMesh.h
│ │ ├── pointMesh.cpp
│ │ ├── pointMesh.h
│ │ ├── primitive.h
│ │ ├── vectorMesh.cpp
│ │ └── vectorMesh.h
│ ├── meshRegistry.cpp
│ ├── meshRegistry.h
│ ├── path/
│ │ ├── path.cpp
│ │ ├── path.h
│ │ ├── path3D.cpp
│ │ └── path3D.h
│ ├── renderer.cpp
│ ├── renderer.h
│ ├── resource/
│ │ ├── fontResource.cpp
│ │ ├── fontResource.h
│ │ ├── shaderResource.cpp
│ │ ├── shaderResource.h
│ │ ├── textureResource.cpp
│ │ └── textureResource.h
│ ├── shader/
│ │ ├── lexer.cpp
│ │ ├── lexer.h
│ │ ├── parser.cpp
│ │ ├── parser.h
│ │ ├── propertiesParser.cpp
│ │ ├── propertiesParser.h
│ │ ├── shader.cpp
│ │ ├── shader.h
│ │ ├── shaders.cpp
│ │ └── shaders.h
│ ├── texture.cpp
│ └── texture.h
├── install/
│ ├── clean.bat
│ ├── clean.sh
│ ├── setup.bat
│ ├── setup.sh
│ ├── setupBuild.bat
│ ├── setupBuild.sh
│ ├── setupBuildUbuntu.sh
│ ├── setupDependencies.bat
│ ├── setupDependencies.sh
│ ├── setupDependenciesUbuntu.sh
│ └── setupUbuntu.sh
├── res/
│ ├── default_imgui.ini
│ └── shaders/
│ ├── basic.shader
│ ├── blur.shader
│ ├── compute.shader
│ ├── debug.shader
│ ├── depth.shader
│ ├── depthbuffer.shader
│ ├── font.shader
│ ├── gui.shader
│ ├── instance.shader
│ ├── lighting.shader
│ ├── line.shader
│ ├── mask.shader
│ ├── origin.shader
│ ├── point.shader
│ ├── postprocess.shader
│ ├── quad.shader
│ ├── sky.shader
│ ├── skybox.shader
│ ├── test.shader
│ └── vector.shader
├── tests/
│ ├── boundsTree2Tests.cpp
│ ├── compare.h
│ ├── constraintTests.cpp
│ ├── ecsTests.cpp
│ ├── estimateMotion.cpp
│ ├── estimateMotion.h
│ ├── estimationTests.cpp
│ ├── generators.cpp
│ ├── generators.h
│ ├── geometryTests.cpp
│ ├── guiTests.cpp
│ ├── indexedShapeTests.cpp
│ ├── inertiaTests.cpp
│ ├── jointTests.cpp
│ ├── lexerTests.cpp
│ ├── mathTests.cpp
│ ├── motionTests.cpp
│ ├── physicalStructureTests.cpp
│ ├── physicsTests.cpp
│ ├── randomValues.h
│ ├── rotationTests.cpp
│ ├── simulation.h
│ ├── testFrameworkConsistencyTests.cpp
│ ├── testValues.cpp
│ ├── testValues.h
│ ├── tests.vcxproj
│ ├── testsMain.cpp
│ └── testsMain.h
├── util/
│ ├── cmdParser.h
│ ├── fileUtils.cpp
│ ├── fileUtils.h
│ ├── iteratorUtils.h
│ ├── log.cpp
│ ├── log.h
│ ├── parseCPUIDArgs.h
│ ├── properties.cpp
│ ├── properties.h
│ ├── resource/
│ │ ├── resource.cpp
│ │ ├── resource.h
│ │ ├── resourceDescriptor.h
│ │ ├── resourceLoader.cpp
│ │ ├── resourceLoader.h
│ │ ├── resourceManager.cpp
│ │ └── resourceManager.h
│ ├── stringUtil.cpp
│ ├── stringUtil.h
│ ├── systemVariables.cpp
│ ├── systemVariables.h
│ ├── terminalColor.cpp
│ ├── terminalColor.h
│ ├── tracker.h
│ ├── typetraits.h
│ ├── util.vcxproj
│ ├── valueCycle.cpp
│ └── valueCycle.h
└── world.grammar
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/msvcBuild.yml
================================================
name: MSVC
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
buildMSVC:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: setup
run: install\setup.bat
- name: make
run: cmake --build build --parallel
================================================
FILE: .github/workflows/ubuntuBuild.yml
================================================
name: Ubuntu
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
buildUbuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: setup
run: sh install/setupUbuntu.sh
- name: make
run: cmake --build build --parallel
================================================
FILE: .gitignore
================================================
# Files possibly generated by Physics3D
res/.properties
imgui.ini
res/imgui.ini
*.parts
*.nativeParts
*.world
*.obj
*.bobj
# Visual Studio
**.vcxproj.filters
**.vcxproj.user
**.aps
*.db
*.opendb
*.TMP
.vs/
Debug/
Release/
x64/
x86/
lib/
ipch/
# Visual studio code
.vscode/
# CLion
.idea/
cmake-build-debug/
# Other build systems
include/
vcpkg/
build/
build2/
build_debug/
*.log
SHADERed/
res/xcf/
*.ruleset
Hexinator.lnk
Physics3D.sln.DotSettings.user
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.10)
project(Physics3D-application VERSION 1.0)
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Building with: ${CMAKE_CXX_COMPILER_ID}")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Oi /ot /GL")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -fsanitize=address")
set(OpenGL_GL_PREFERENCE "GLVND")
#running benchmarks showed this to be a pessimization
#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION True)
#surprisingly, also a pessimization
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffast-math")
endif()
find_package(glfw3 3.2 REQUIRED)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(Freetype REQUIRED)
find_package(Threads REQUIRED)
include_directories(PRIVATE "${GLFW_DIR}/include")
include_directories(PRIVATE "${GLEW_DIR}/include")
include_directories(PRIVATE "${FREETYPE_INCLUDE_DIRS}")
include_directories(PRIVATE "include")
add_library(util STATIC
util/terminalColor.cpp
util/log.cpp
util/properties.cpp
util/stringUtil.cpp
util/fileUtils.cpp
util/valueCycle.cpp
util/systemVariables.cpp
util/resource/resource.cpp
util/resource/resourceLoader.cpp
util/resource/resourceManager.cpp
)
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
target_link_libraries(util stdc++fs)
endif()
# Adds the Physics3D library
add_subdirectory(Physics3D)
add_executable(benchmarks
benchmarks/benchmark.cpp
benchmarks/basicWorld.cpp
benchmarks/complexObjectBenchmark.cpp
benchmarks/getBoundsPerformance.cpp
benchmarks/manyCubesBenchmark.cpp
benchmarks/worldBenchmark.cpp
benchmarks/rotationBenchmark.cpp
benchmarks/ecsBenchmark.cpp
benchmarks/threadResponseTime.cpp
)
add_library(imguiInclude STATIC
include/imgui/imgui.cpp
include/imgui/imgui_demo.cpp
include/imgui/imgui_draw.cpp
include/imgui/imgui_impl_glfw.cpp
include/imgui/imgui_impl_opengl3.cpp
include/imgui/imgui_widgets.cpp
)
add_library(graphics STATIC
graphics/glfwUtils.cpp
graphics/bindable.cpp
graphics/core.cpp
graphics/font.cpp
graphics/renderer.cpp
graphics/texture.cpp
graphics/extendedTriangleMesh.cpp
graphics/meshRegistry.cpp
graphics/buffers/bufferLayout.cpp
graphics/buffers/frameBuffer.cpp
graphics/buffers/indexBuffer.cpp
graphics/buffers/renderBuffer.cpp
graphics/buffers/vertexArray.cpp
graphics/buffers/vertexBuffer.cpp
graphics/debug/guiDebug.cpp
graphics/debug/profilerUI.cpp
graphics/debug/visualDebug.cpp
graphics/gui/gui.cpp
graphics/gui/guiUtils.cpp
graphics/gui/imgui/imguiStyle.cpp
graphics/mesh/abstractMesh.cpp
graphics/mesh/arrayMesh.cpp
graphics/mesh/indexedMesh.cpp
graphics/mesh/pointMesh.cpp
graphics/mesh/vectorMesh.cpp
graphics/path/path.cpp
graphics/path/path3D.cpp
graphics/resource/fontResource.cpp
graphics/resource/shaderResource.cpp
graphics/resource/textureResource.cpp
graphics/shader/shader.cpp
graphics/shader/lexer.cpp
graphics/shader/parser.cpp
graphics/shader/shaders.cpp
graphics/shader/propertiesParser.cpp
)
add_library(engine STATIC
engine/core.cpp
engine/input/inputHandler.cpp
engine/input/keyboard.cpp
engine/input/modifiers.cpp
engine/input/mouse.cpp
engine/io/export.cpp
engine/io/import.cpp
engine/layer/layerStack.cpp
engine/tool/toolManager.cpp
engine/options/keyboardOptions.cpp
engine/resource/meshResource.cpp
)
add_executable(tests
tests/testsMain.cpp
tests/estimateMotion.cpp
tests/testValues.cpp
tests/generators.cpp
tests/mathTests.cpp
tests/rotationTests.cpp
tests/motionTests.cpp
tests/geometryTests.cpp
tests/estimationTests.cpp
tests/constraintTests.cpp
tests/jointTests.cpp
tests/boundsTree2Tests.cpp
tests/guiTests.cpp
tests/indexedShapeTests.cpp
tests/physicalStructureTests.cpp
tests/physicsTests.cpp
tests/inertiaTests.cpp
tests/testFrameworkConsistencyTests.cpp
tests/ecsTests.cpp
tests/lexerTests.cpp
)
add_executable(application
application/core.cpp
application/application.cpp
application/eventHandler.cpp
application/extendedPart.cpp
application/resources.cpp
application/worldBuilder.cpp
application/builtinWorlds.cpp
application/worlds.cpp
application/input/standardInputHandler.cpp
application/input/playerController.cpp
application/io/saveDialog.cpp
application/io/serialization.cpp
application/layer/constraintLayer.cpp
application/layer/debugLayer.cpp
application/layer/debugOverlay.cpp
application/layer/guiLayer.cpp
application/layer/modelLayer.cpp
application/layer/pickerLayer.cpp
application/layer/postprocessLayer.cpp
application/layer/skyboxLayer.cpp
application/layer/testLayer.cpp
application/layer/cameraLayer.cpp
application/layer/shadowLayer.cpp
application/layer/imguiLayer.cpp
application/picker/ray.cpp
application/picker/selection.cpp
application/picker/tools/selectionTool.cpp
application/picker/tools/translationTool.cpp
application/picker/tools/rotationTool.cpp
application/picker/tools/scaleTool.cpp
application/picker/tools/regionSelectionTool.cpp
application/picker/tools/attachmentTool.cpp
application/picker/tools/fixedConstraintTool.cpp
application/picker/tools/motorConstraintTool.cpp
application/picker/tools/pistonConstraintTool.cpp
application/picker/tools/elasticLinkTool.cpp
application/picker/tools/magneticLinkTool.cpp
application/picker/tools/springLinkTool.cpp
application/picker/tools/alignmentLinkTool.cpp
application/picker/tools/pathTool.cpp
application/shader/shaders.cpp
application/shader/basicShader.cpp
application/shader/shaderBase.cpp
application/view/camera.cpp
application/view/frames.cpp
application/view/screen.cpp
application/view/debugFrame.cpp
application/view/layerFrame.cpp
application/view/ecsFrame.cpp
application/view/propertiesFrame.cpp
application/view/resourceFrame.cpp
application/view/environmentFrame.cpp
application/view/toolbarFrame.cpp
)
target_include_directories(tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(benchmarks PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(graphics PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(application PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(graphics PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/graphics")
target_include_directories(engine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/engine")
target_include_directories(application PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/application")
target_link_libraries(tests util)
target_link_libraries(tests Physics3D)
target_link_libraries(tests graphics)
target_link_libraries(tests engine)
target_link_libraries(tests Threads::Threads)
target_link_libraries(benchmarks util)
target_link_libraries(benchmarks Physics3D)
target_link_libraries(benchmarks Threads::Threads)
target_link_libraries(graphics imguiInclude)
target_link_libraries(graphics Physics3D)
target_link_libraries(engine graphics)
target_link_libraries(application util)
target_link_libraries(application Physics3D)
target_link_libraries(application graphics)
target_link_libraries(application engine)
#target_link_libraries(application ${GLFW_LIBRARIES})
target_link_libraries(application glfw)
#target_link_libraries(application ${OPENGL_LIBRARIES})
target_link_libraries(application OpenGL::GL)
#target_link_libraries(application ${GLEW_LIBRARIES})
target_link_libraries(application GLEW::GLEW)
target_link_libraries(application ${FREETYPE_LIBRARIES})
target_link_libraries(application Threads::Threads)
# install(TARGETS benchmarks DESTINATION bin)
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2018 ThePhysicsGuys
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Physics3D/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.10)
project(Physics3D VERSION 0.9)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Oi /ot /GL")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native -fno-math-errno")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Ofast")
#running benchmarks showed this to be a pessimization
#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION True)
#surprisingly, also a pessimization
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffast-math")
endif()
add_library(Physics3D STATIC
part.cpp
physical.cpp
rigidBody.cpp
layer.cpp
world.cpp
worldPhysics.cpp
inertia.cpp
math/linalg/eigen.cpp
math/linalg/trigonometry.cpp
geometry/computationBuffer.cpp
geometry/convexShapeBuilder.cpp
geometry/genericIntersection.cpp
geometry/indexedShape.cpp
geometry/intersection.cpp
geometry/triangleMesh.cpp
geometry/triangleMeshSSE.cpp
geometry/triangleMeshSSE4.cpp
geometry/triangleMeshAVX.cpp
geometry/polyhedron.cpp
geometry/shape.cpp
geometry/shapeBuilder.cpp
geometry/shapeClass.cpp
geometry/shapeCreation.cpp
geometry/builtinShapeClasses.cpp
geometry/shapeLibrary.cpp
datastructures/aligned_alloc.cpp
boundstree/boundsTree.cpp
boundstree/boundsTreeAVX.cpp
boundstree/filters/visibilityFilter.cpp
hardconstraints/fixedConstraint.cpp
hardconstraints/hardConstraint.cpp
hardconstraints/hardPhysicalConnection.cpp
hardconstraints/motorConstraint.cpp
hardconstraints/controller/sineWaveController.cpp
constraints/constraint.cpp
constraints/constraintGroup.cpp
constraints/ballConstraint.cpp
constraints/hingeConstraint.cpp
constraints/barConstraint.cpp
softlinks/softLink.cpp
softlinks/springLink.cpp
softlinks/elasticLink.cpp
softlinks/magneticLink.cpp
softlinks/alignmentLink.cpp
externalforces/directionalGravity.cpp
externalforces/externalForce.cpp
externalforces/magnetForce.cpp
threading/upgradeableMutex.cpp
threading/physicsThread.cpp
misc/debug.cpp
misc/cpuid.cpp
misc/validityHelper.cpp
misc/physicsProfiler.cpp
misc/serialization/serialization.cpp
misc/serialization/serializeBasicTypes.cpp
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set_source_files_properties(geometry/triangleMeshSSE.cpp PROPERTIES COMPILE_FLAGS /arch:SSE2)
set_source_files_properties(geometry/triangleMeshSSE4.cpp PROPERTIES COMPILE_FLAGS /arch:SSE2)
set_source_files_properties(geometry/triangleMeshAVX.cpp PROPERTIES COMPILE_FLAGS /arch:AVX2)
else()
set_source_files_properties(geometry/triangleMeshSSE.cpp PROPERTIES COMPILE_FLAGS -msse2) # Up to SSE2
set_source_files_properties(geometry/triangleMeshSSE4.cpp PROPERTIES COMPILE_FLAGS -msse4.1) # Up to SSE4_1
set_source_files_properties(geometry/triangleMeshAVX.cpp PROPERTIES COMPILE_FLAGS -mfma) # Includes AVX, AVX2 and FMA
endif()
================================================
FILE: Physics3D/Physics3D.vcxproj
================================================
Debug
Win32
Release
Win32
Debug
x64
Release
x64
15.0
{DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}
physics
10.0
Physics3D
DynamicLibrary
true
v142
MultiByte
Application
false
v142
true
MultiByte
StaticLibrary
true
v142
MultiByte
StaticLibrary
false
v142
true
MultiByte
$(ProjectName)
$(ProjectName)
Level3
Disabled
true
true
Level3
Disabled
true
true
NotSet
stdcpp17
_MBCS;%(PreprocessorDefinitions)
true
Level3
MaxSpeed
true
true
true
true
true
true
Level3
MaxSpeed
true
true
true
true
NotSet
_MBCS;NDEBUG;CATCH_INTERSECTION_ERRORS;%(PreprocessorDefinitions)
stdcpp17
true
true
true
AdvancedVectorExtensions2
AdvancedVectorExtensions2
StreamingSIMDExtensions2
StreamingSIMDExtensions2
StreamingSIMDExtensions2
StreamingSIMDExtensions2
================================================
FILE: Physics3D/boundstree/boundsTree.cpp
================================================
#include "boundsTree.h"
#include "../datastructures/aligned_alloc.h"
namespace P3D {
// naive implementation, to be optimized
BoundsTemplate TrunkSIMDHelperFallback::getTotalBounds(const TreeTrunk& trunk, int upTo) {
assert(upTo >= 1 && upTo <= BRANCH_FACTOR);
BoundsTemplate totalBounds = trunk.getBoundsOfSubNode(0);
for(int i = 1; i < upTo; i++) {
totalBounds = unionOfBounds(totalBounds, trunk.getBoundsOfSubNode(i));
}
return totalBounds;
}
// naive implementation, to be optimized
BoundsTemplate TrunkSIMDHelperFallback::getTotalBoundsWithout(const TreeTrunk& trunk, int upTo, int without) {
assert(upTo >= 2 && upTo <= BRANCH_FACTOR); // size must be at least 2, can't compute otherwise
assert(without >= 0 && without < BRANCH_FACTOR);
if(without == 0) {
BoundsTemplate totalBounds = trunk.getBoundsOfSubNode(1);
for(int i = 2; i < upTo; i++) {
totalBounds = unionOfBounds(totalBounds, trunk.getBoundsOfSubNode(i));
}
return totalBounds;
} else {
BoundsTemplate totalBounds = trunk.getBoundsOfSubNode(0);
for(int i = 1; i < upTo; i++) {
if(i == without) continue;
totalBounds = unionOfBounds(totalBounds, trunk.getBoundsOfSubNode(i));
}
return totalBounds;
}
}
BoundsArray TrunkSIMDHelperFallback::getAllTotalBoundsWithout(const TreeTrunk& trunk, int upTo) {
assert(upTo >= 2 && upTo <= BRANCH_FACTOR); // size must be at least 2, can't compute otherwise
BoundsArray result;
{
BoundsTemplate totalBounds0 = trunk.getBoundsOfSubNode(1);
for(int i = 2; i < upTo; i++) {
totalBounds0 = unionOfBounds(totalBounds0, trunk.getBoundsOfSubNode(i));
}
result.setBounds(0, totalBounds0);
}
for(int without = 1; without < upTo; without++) {
BoundsTemplate totalBounds = trunk.getBoundsOfSubNode(0);
for(int i = 2; i < upTo; i++) {
if(i == without) continue;
totalBounds = unionOfBounds(totalBounds, trunk.getBoundsOfSubNode(i));
}
result.setBounds(without, totalBounds);
}
return result;
}
std::array TrunkSIMDHelperFallback::getAllContainsBounds(const TreeTrunk& trunk, const BoundsTemplate& boundsToContain) {
std::array contained;
for(int i = 0; i < BRANCH_FACTOR; i++) {
BoundsTemplate subNodeBounds = trunk.getBoundsOfSubNode(i);
contained[i] = subNodeBounds.contains(boundsToContain);
}
return contained;
}
std::array TrunkSIMDHelperFallback::computeAllCosts(const TreeTrunk& trunk) {
std::array costs;
for(int i = 0; i < BRANCH_FACTOR; i++) {
BoundsTemplate subNodeBounds = trunk.getBoundsOfSubNode(i);
costs[i] = computeCost(subNodeBounds);
}
return costs;
}
std::array TrunkSIMDHelperFallback::computeAllCombinationCosts(const BoundsArray& boundsArr, const BoundsTemplate& boundsExtention) {
std::array costs;
for(int i = 0; i < BRANCH_FACTOR; i++) {
BoundsTemplate subNodeBounds = boundsArr.getBounds(i);
costs[i] = computeCost(unionOfBounds(boundsExtention, subNodeBounds));
}
return costs;
}
std::pair TrunkSIMDHelperFallback::computeFurthestObjects(const BoundsArray& boundsArray, int size) {
std::pair furthestObjects{0, 1};
float biggestCost = -std::numeric_limits::infinity();
for(int i = 0; i < size - 1; i++) {
BoundsTemplate iBounds = boundsArray.getBounds(i);
for(int j = i + 1; j < size; j++) {
BoundsTemplate jBounds = boundsArray.getBounds(j);
float cost = computeCost(unionOfBounds(iBounds, jBounds));
if(cost > biggestCost) {
biggestCost = cost;
furthestObjects.first = i;
furthestObjects.second = j;
}
}
}
return furthestObjects;
}
int TrunkSIMDHelperFallback::getLowestCombinationCost(const TreeTrunk& trunk, const BoundsTemplate& boundsExtention, int nodeSize) {
std::array costs = TrunkSIMDHelperFallback::computeAllCombinationCosts(trunk.subNodeBounds, boundsExtention);
float bestCost = costs[0];
int bestIndex = 0;
for(int i = 1; i < nodeSize; i++) {
if(costs[i] < bestCost) {
bestIndex = i;
bestCost = costs[i];
}
}
return bestIndex;
}
std::array TrunkSIMDHelperFallback::computeOverlapsWith(const TreeTrunk& trunk, int trunkSize, const BoundsTemplate& bounds) {
std::array result;
for(int i = 0; i < trunkSize; i++) {
result[i] = intersects(trunk.getBoundsOfSubNode(i), bounds);
}
return result;
}
OverlapMatrix TrunkSIMDHelperFallback::computeBoundsOverlapMatrix(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize) {
OverlapMatrix result;
for(int a = 0; a < trunkASize; a++) {
BoundsTemplate aBounds = trunkA.getBoundsOfSubNode(a);
for(int b = 0; b < trunkBSize; b++) {
BoundsTemplate bBounds = trunkB.getBoundsOfSubNode(b);
result[a][b] = intersects(aBounds, bBounds);
}
}
return result;
}
OverlapMatrix TrunkSIMDHelperFallback::computeInternalBoundsOverlapMatrix(const TreeTrunk& trunk, int trunkSize) {
OverlapMatrix result;
for(int a = 0; a < trunkSize; a++) {
BoundsTemplate aBounds = trunk.getBoundsOfSubNode(a);
for(int b = a+1; b < trunkSize; b++) {
BoundsTemplate bBounds = trunk.getBoundsOfSubNode(b);
result[a][b] = intersects(aBounds, bBounds);
}
}
return result;
}
std::array TrunkSIMDHelperFallback::computeAllExtentionCosts(const TreeTrunk& trunk, int trunkSize, const BoundsTemplate& extraBounds) {
std::array resultingCosts;
for(int i = 0; i < trunkSize; i++) {
resultingCosts[i] = computeAdditionCost(trunk.getBoundsOfSubNode(i), extraBounds);
}
return resultingCosts;
}
int TrunkSIMDHelperFallback::transferNodes(TreeTrunk& srcTrunk, int srcTrunkStart, int srcTrunkEnd, TreeTrunk& destTrunk, int destTrunkSize) {
for(int i = srcTrunkStart; i < srcTrunkEnd; i++) {
destTrunk.setSubNode(destTrunkSize, std::move(srcTrunk.subNodes[i]), srcTrunk.getBoundsOfSubNode(i));
destTrunkSize++;
}
return destTrunkSize;
}
BoundsArray TrunkSIMDHelperFallback::combineBoundsArrays(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize) {
BoundsArray result;
for(int i = 0; i < trunkASize; i++) {
result.setBounds(i, trunkA.getBoundsOfSubNode(i));
}
for(int i = 0; i < trunkBSize; i++) {
result.setBounds(trunkASize + i, trunkB.getBoundsOfSubNode(i));
}
return result;
}
// returns true if modified
bool TrunkSIMDHelperFallback::exchangeNodesBetween(TreeTrunk& trunkA, int& trunkASize, TreeTrunk& trunkB, int& trunkBSize) {
int totalSize = trunkASize + trunkBSize;
// if this is not the case, we could've just merged the nodes, this is handled in another function
// if totalSize was BRANCH_FACTOR + 1 we could've moved all but one node from one trunk to the other, also handled elsewhere
assert(totalSize >= BRANCH_FACTOR + 2);
BoundsArray allBounds = TrunkSIMDHelperFallback::combineBoundsArrays(trunkA, trunkASize, trunkB, trunkBSize);
std::pair furthestObjects = TrunkSIMDHelperFallback::computeFurthestObjects(allBounds, totalSize);
BoundsTemplate aBounds = allBounds.getBounds(furthestObjects.first);
BoundsTemplate bBounds = allBounds.getBounds(furthestObjects.second);
int aResultSize = 0;
int bResultSize = 0;
int aResult[BRANCH_FACTOR * 2];
int bResult[BRANCH_FACTOR * 2];
// positive if costA < costB
float allDeltaCosts[BRANCH_FACTOR * 2];
for(int i = 0; i < totalSize; i++) {
BoundsTemplate bounds = allBounds.getBounds(i);
float costA = computeCost(unionOfBounds(aBounds, bounds));
float costB = computeCost(unionOfBounds(bBounds, bounds));
allDeltaCosts[i] = costB - costA;
}
for(int i = 0; i < totalSize; i++) {
if(allDeltaCosts[i] >= 0.0) {
aResult[aResultSize++] = i;
} else {
bResult[bResultSize++] = i;
}
}
if(aResultSize > BRANCH_FACTOR) {
// move least costly elements from a to b (least costly cost is closest to 0)
do {
int worstIndex = 0;
float worstCost = allDeltaCosts[aResult[0]];
for(int i = 1; i < aResultSize; i++) {
float cost = allDeltaCosts[aResult[i]];
if(cost < worstCost) {
worstIndex = i;
worstCost = cost;
}
}
bResult[bResultSize++] = aResult[worstIndex];
aResult[worstIndex] = aResult[--aResultSize];
} while(aResultSize > BRANCH_FACTOR);
} else if(bResultSize > BRANCH_FACTOR) {
// move least costly elements from b to a (least costly cost is closest to 0)
do {
int worstIndex = 0;
float worstCost = allDeltaCosts[bResult[0]];
for(int i = 1; i < bResultSize; i++) {
float cost = allDeltaCosts[bResult[i]];
if(cost > worstCost) { // these costs are all negative
worstIndex = i;
worstCost = cost;
}
}
aResult[aResultSize++] = bResult[worstIndex];
bResult[worstIndex] = bResult[--bResultSize];
} while(bResultSize > BRANCH_FACTOR);
}
assert(aResultSize + bResultSize == totalSize);
assert(aResultSize <= BRANCH_FACTOR && bResultSize <= BRANCH_FACTOR);
assert(aResultSize >= 2 && bResultSize >= 2); // Guaranteed by totalSize >= BRANCH_FACTOR + 2 && aResultSize <= BRANCH_FACTOR && bResultSize <= BRANCH_FACTOR
// checks to see if a change needs to be made
bool a0ComesFromA = aResult[0] < trunkASize;
bool b0ComesFromA = bResult[0] < trunkASize;
if(a0ComesFromA != b0ComesFromA) {// they differ in origin
for(int i = 1; i < aResultSize; i++) {
bool comesFromA = aResult[i] < trunkASize;
if(comesFromA != a0ComesFromA) {
goto wasChanged;
}
}
for(int i = 1; i < bResultSize; i++) {
bool comesFromA = bResult[i] < trunkASize;
if(comesFromA != b0ComesFromA) {
goto wasChanged;
}
}
return false;
}
wasChanged:;
TreeNodeRef allSubNodes[BRANCH_FACTOR * 2];
for(int i = 0; i < trunkASize; i++) {
allSubNodes[i] = std::move(trunkA.subNodes[i]);
}
for(int i = 0; i < trunkBSize; i++) {
allSubNodes[i+trunkASize] = std::move(trunkB.subNodes[i]);
}
for(int i = 0; i < aResultSize; i++) {
int from = aResult[i];
trunkA.setSubNode(i, std::move(allSubNodes[from]), allBounds.getBounds(from));
}
for(int i = 0; i < bResultSize; i++) {
int from = bResult[i];
trunkB.setSubNode(i, std::move(allSubNodes[from]), allBounds.getBounds(from));
}
trunkASize = aResultSize;
trunkBSize = bResultSize;
return true;
}
// returns the new size of this node, to be applied to the caller
int addRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, TreeNodeRef&& newNode, const BoundsTemplate& bounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
if(curTrunkSize == BRANCH_FACTOR) {
int chosenNode = TrunkSIMDHelperFallback::getLowestCombinationCost(curTrunk, bounds, curTrunkSize);
TreeNodeRef& chosen = curTrunk.subNodes[chosenNode];
BoundsTemplate oldSubNodeBounds = curTrunk.getBoundsOfSubNode(chosenNode);
// can be inserted into the trunkNode
if(chosen.isTrunkNode() && !chosen.isGroupHead()) {
int newSize = addRecursive(allocator, chosen.asTrunk(), chosen.getTrunkSize(), std::move(newNode), bounds);
chosen.setTrunkSize(newSize);
} else {
TreeTrunk* newTrunk = allocator.allocTrunk();
newTrunk->setSubNode(0, std::move(chosen), oldSubNodeBounds);
newTrunk->setSubNode(1, std::move(newNode), bounds);
chosen = TreeNodeRef(newTrunk, 2, false);
}
curTrunk.setBoundsOfSubNode(chosenNode, unionOfBounds(oldSubNodeBounds, bounds));
return BRANCH_FACTOR;
} else {
curTrunk.setSubNode(curTrunkSize, std::move(newNode), bounds);
return curTrunkSize + 1;
}
}
bool containsObjectRecursive(const TreeTrunk& trunk, int trunkSize, const void* object, const BoundsTemplate& bounds) {
assert(trunkSize >= 0 && trunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(trunk, bounds);
for(int i = 0; i < trunkSize; i++) {
if(!couldContain[i]) continue;
const TreeNodeRef& subNode = trunk.subNodes[i];
if(subNode.isTrunkNode()) {
if(containsObjectRecursive(subNode.asTrunk(), subNode.getTrunkSize(), object, bounds)) {
return true;
}
} else {
if(subNode.asObject() == object) {
return true;
}
}
}
return false;
}
// returns new trunkSize if removed, -1 if not removed
int removeRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, const void* objectToRemove, const BoundsTemplate& bounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, bounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subNodeTrunk = subNode.asTrunk();
assert(subNode.getTrunkSize() >= 2);
int newSubNodeTrunkSize = removeRecursive(allocator, subNodeTrunk, subNode.getTrunkSize(), objectToRemove, bounds);
assert(newSubNodeTrunkSize >= 1 || newSubNodeTrunkSize == -1);
if(newSubNodeTrunkSize != -1) {
if(newSubNodeTrunkSize == 1) {
// remove reduntant trunk
bool wasGroupHead = subNode.isGroupHead();
subNode = std::move(subNodeTrunk.subNodes[0]);
curTrunk.setBoundsOfSubNode(i, subNodeTrunk.getBoundsOfSubNode(0));
if(wasGroupHead && subNode.isTrunkNode()) {
subNode.makeGroupHead(); // in the rare case that it removes Z in a node that is a group like this: GH[TN[X,Y],Z]. Without this the grouping would be lost
}
allocator.freeTrunk(&subNodeTrunk);
} else {
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subNodeTrunk, newSubNodeTrunkSize));
subNode.setTrunkSize(newSubNodeTrunkSize);
}
return curTrunkSize;
}
} else {
if(subNode.asObject() == objectToRemove) {
curTrunk.moveSubNode(curTrunkSize - 1, i);
return curTrunkSize - 1;
}
}
}
return -1;
}
struct TreeGrab {
int resultingGroupSize;
TreeNodeRef nodeRef;
BoundsTemplate nodeBounds;
TreeGrab() : resultingGroupSize(-1) {}
TreeGrab(int resultingGroupSize, TreeNodeRef&& nodeRef, const BoundsTemplate& nodeBounds) :
resultingGroupSize(resultingGroupSize),
nodeRef(std::move(nodeRef)),
nodeBounds(nodeBounds) {}
};
// returns new trunkSize if removed, -1 if not removed
static TreeGrab grabGroupRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, const void* groupRepresentative, const BoundsTemplate& representativeBounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, representativeBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subNodeTrunk = subNode.asTrunk();
size_t trunkSize = subNode.getTrunkSize();
assert(trunkSize >= 2);
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subNodeTrunk, trunkSize, groupRepresentative, representativeBounds)) {
// found group, now remove it
TreeNodeRef subNodeCopy = std::move(subNode);
curTrunk.moveSubNode(curTrunkSize - 1, i);
return TreeGrab(curTrunkSize - 1, std::move(subNodeCopy), curTrunk.getBoundsOfSubNode(i));
}
} else {
// try
TreeGrab recursiveResult = grabGroupRecursive(allocator, subNodeTrunk, subNode.getTrunkSize(), groupRepresentative, representativeBounds);
int newSubNodeTrunkSize = recursiveResult.resultingGroupSize;
assert(newSubNodeTrunkSize >= 1 || newSubNodeTrunkSize == -1);
if(newSubNodeTrunkSize != -1) {
if(newSubNodeTrunkSize == 1) {
// remove reduntant trunk
curTrunk.setSubNode(i, std::move(subNodeTrunk.subNodes[0]), subNodeTrunk.getBoundsOfSubNode(0));
allocator.freeTrunk(&subNodeTrunk);
} else {
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subNodeTrunk, newSubNodeTrunkSize));
subNode.setTrunkSize(newSubNodeTrunkSize);
}
recursiveResult.resultingGroupSize = curTrunkSize;
return recursiveResult;
}
}
} else {
if(subNode.asObject() == groupRepresentative) {
TreeNodeRef subNodeCopy = std::move(subNode);
BoundsTemplate subNodeBounds = curTrunk.getBoundsOfSubNode(i);
curTrunk.moveSubNode(curTrunkSize - 1, i);
return TreeGrab(curTrunkSize - 1, std::move(subNodeCopy), subNodeBounds);
}
}
}
return TreeGrab();
}
const TreeNodeRef* getGroupRecursive(const TreeTrunk& curTrunk, int curTrunkSize, const void* groupRepresentative, const BoundsTemplate& representativeBounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, representativeBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
const TreeTrunk& subNodeTrunk = subNode.asTrunk();
size_t subTrunkSize = subNode.getTrunkSize();
assert(subTrunkSize >= 2);
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subNodeTrunk, subTrunkSize, groupRepresentative, representativeBounds)) {
return &subNode;
}
} else {
const TreeNodeRef* recursiveFound = getGroupRecursive(subNodeTrunk, subTrunkSize, groupRepresentative, representativeBounds);
if(recursiveFound != nullptr) {
return recursiveFound;
}
}
} else {
if(subNode.asObject() == groupRepresentative) {
return &subNode;
}
}
}
return nullptr;
}
// also frees starting trunk
static void freeTrunksRecursive(TrunkAllocator& alloc, TreeTrunk& curTrunk, int curTrunkSize) {
for(int i = 0; i < curTrunkSize; i++) {
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
freeTrunksRecursive(alloc, subTrunk, subNode.getTrunkSize());
}
}
alloc.freeTrunk(&curTrunk);
}
// expects a function of the form void(void* object, const BoundsTemplate& bounds)
template
static void forEachRecurseWithBounds(const TreeTrunk& curTrunk, int curTrunkSize, const Func& func) {
for(int i = 0; i < curTrunkSize; i++) {
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
forEachRecurseWithBounds(subNode.asTrunk(), subNode.getTrunkSize(), func);
} else {
func(subNode.asObject(), curTrunk.getBoundsOfSubNode(i));
}
}
}
// returns true if the group that is inserted into is found
// deletes all trunknodes of the destroyed group with the provided allocator
static bool insertGroupIntoGroup(TrunkAllocator& sourceAlloc, TrunkAllocator& destinationAlloc, TreeTrunk& baseTrunk, int baseTrunkSize, const void* groupToAddToRep, const BoundsTemplate& groupToAddToRepBounds, TreeNodeRef&& groupToDestroy, const BoundsTemplate& groupToDestroyBounds) {
bool groupWasFound = modifyGroupRecursive(destinationAlloc, baseTrunk, baseTrunkSize, groupToAddToRep, groupToAddToRepBounds, [&sourceAlloc, &destinationAlloc, &groupToDestroy, &groupToDestroyBounds](TreeNodeRef& group, const BoundsTemplate& groupBounds) {
TreeTrunk* trunk;
int trunkSize;
if(group.isLeafNode()) {
trunk = destinationAlloc.allocTrunk();
trunk->setSubNode(0, std::move(group), groupBounds);
trunkSize = 1;
} else {
trunk = &group.asTrunk();
trunkSize = group.getTrunkSize();
}
if(groupToDestroy.isTrunkNode()) {
forEachRecurseWithBounds(groupToDestroy.asTrunk(), groupToDestroy.getTrunkSize(), [&](void* object, const BoundsTemplate& bounds) {
trunkSize = addRecursive(destinationAlloc, *trunk, trunkSize, TreeNodeRef(object), bounds);
});
} else {
trunkSize = addRecursive(destinationAlloc, *trunk, trunkSize, TreeNodeRef(groupToDestroy.asObject()), groupToDestroyBounds);
}
group = TreeNodeRef(trunk, trunkSize, true);
if(groupToDestroy.isTrunkNode()) {
TreeTrunk& destroyTrunk = groupToDestroy.asTrunk();
freeTrunksRecursive(sourceAlloc, destroyTrunk, groupToDestroy.getTrunkSize());
}
return TrunkSIMDHelperFallback::getTotalBounds(*trunk, trunkSize);
});
return groupWasFound;
}
TrunkAllocator::TrunkAllocator() : allocationCount(0) {}
TrunkAllocator::~TrunkAllocator() {
assert(this->allocationCount == 0);
}
TrunkAllocator::TrunkAllocator(TrunkAllocator&& other) noexcept : allocationCount(other.allocationCount) {
other.allocationCount = 0;
}
TrunkAllocator& TrunkAllocator::operator=(TrunkAllocator&& other) noexcept {
std::swap(this->allocationCount, other.allocationCount);
return *this;
}
TreeTrunk* TrunkAllocator::allocTrunk() {
this->allocationCount++;
std::cout << "allocTrunk " << this->allocationCount << std::endl;
return static_cast(aligned_malloc(sizeof(TreeTrunk), alignof(TreeTrunk)));
}
void TrunkAllocator::freeTrunk(TreeTrunk* trunk) {
this->allocationCount--;
std::cout << "freeTrunk " << this->allocationCount << std::endl;
aligned_free(trunk);
}
void TrunkAllocator::freeAllTrunks(TreeTrunk& baseTrunk, int baseTrunkSize) {
for(int i = 0; i < baseTrunkSize; i++) {
TreeNodeRef& subNode = baseTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
freeTrunksRecursive(*this, subNode.asTrunk(), subNode.getTrunkSize());
}
}
}
BoundsTreePrototype::BoundsTreePrototype() : baseTrunk(), baseTrunkSize(0) {}
BoundsTreePrototype::~BoundsTreePrototype() {
this->clear();
}
void BoundsTreePrototype::addGroupTrunk(TreeTrunk* newTrunk, int newTrunkSize) {
this->baseTrunkSize = addRecursive(allocator, baseTrunk, baseTrunkSize, TreeNodeRef(newTrunk, newTrunkSize, true), TrunkSIMDHelperFallback::getTotalBounds(*newTrunk, newTrunkSize));
}
void BoundsTreePrototype::add(void* newObject, const BoundsTemplate& bounds) {
this->baseTrunkSize = addRecursive(allocator, baseTrunk, baseTrunkSize, TreeNodeRef(newObject), bounds);
}
void BoundsTreePrototype::addToGroup(void* newObject, const BoundsTemplate& newObjectBounds, const void* groupRepresentative, const BoundsTemplate& groupRepBounds) {
bool foundGroup = modifyGroupRecursive(allocator, baseTrunk, baseTrunkSize, groupRepresentative, groupRepBounds, [this, newObject, &newObjectBounds](TreeNodeRef& groupNode, const BoundsTemplate& groupNodeBounds) {
assert(groupNode.isGroupHeadOrLeaf());
if(groupNode.isTrunkNode()) {
TreeTrunk& groupTrunk = groupNode.asTrunk();
int resultingSize = addRecursive(allocator, groupTrunk, groupNode.getTrunkSize(), TreeNodeRef(newObject), newObjectBounds);
groupNode.setTrunkSize(resultingSize);
return TrunkSIMDHelperFallback::getTotalBounds(groupTrunk, resultingSize);
} else {
TreeTrunk* newTrunkNode = allocator.allocTrunk();
newTrunkNode->setSubNode(0, std::move(groupNode), groupNodeBounds);
newTrunkNode->setSubNode(1, TreeNodeRef(newObject), newObjectBounds);
groupNode = TreeNodeRef(newTrunkNode, 2, true);
return TrunkSIMDHelperFallback::getTotalBounds(*newTrunkNode, 2);
}
});
if(!foundGroup) {
throw "Group not found!";
}
}
void BoundsTreePrototype::mergeGroups(const void* groupRepA, const BoundsTemplate& repABounds, const void* groupRepB, const BoundsTemplate& repBBounds) {
TreeGrab grabbed = grabGroupRecursive(this->allocator, this->baseTrunk, this->baseTrunkSize, groupRepA, repABounds);
if(grabbed.resultingGroupSize == -1) {
throw "groupRepA not found!";
}
this->baseTrunkSize = grabbed.resultingGroupSize;
bool groupBWasFound = insertGroupIntoGroup(this->allocator, this->allocator, this->baseTrunk, this->baseTrunkSize, groupRepB, repBBounds, std::move(grabbed.nodeRef), grabbed.nodeBounds);
if(!groupBWasFound) {
throw "groupRepB not found!";
}
}
void BoundsTreePrototype::transferGroupTo(const void* groupRep, const BoundsTemplate& groupRepBounds, BoundsTreePrototype& destinationTree) {
TreeGrab grabbed = grabGroupRecursive(this->allocator, this->baseTrunk, this->baseTrunkSize, groupRep, groupRepBounds);
if(grabbed.resultingGroupSize == -1) {
throw "groupRep not found!";
}
this->baseTrunkSize = grabbed.resultingGroupSize;
bool groupBWasFound = addRecursive(destinationTree.allocator, destinationTree.baseTrunk, destinationTree.baseTrunkSize, std::move(grabbed.nodeRef), grabbed.nodeBounds);
assert(groupBWasFound);
}
void BoundsTreePrototype::remove(const void* objectToRemove, const BoundsTemplate& bounds) {
int resultingBaseSize = removeRecursive(allocator, baseTrunk, baseTrunkSize, objectToRemove, bounds);
if(resultingBaseSize != -1) {
this->baseTrunkSize = resultingBaseSize;
} else {
throw "Object not found!";
}
}
bool BoundsTreePrototype::contains(const void* object, const BoundsTemplate& bounds) const {
return containsObjectRecursive(baseTrunk, baseTrunkSize, object, bounds);
}
bool BoundsTreePrototype::groupContains(const void* object, const BoundsTemplate& bounds, const void* groupRep, const BoundsTemplate& groupRepBounds) const {
const TreeNodeRef* groupFound = getGroupRecursive(this->baseTrunk, this->baseTrunkSize, groupRep, groupRepBounds);
if(!groupFound) {
throw "Group not found!";
}
if(groupFound->isTrunkNode()) {
return containsObjectRecursive(groupFound->asTrunk(), groupFound->getTrunkSize(), object, bounds);
} else {
return groupFound->asObject() == object;
}
}
static bool updateObjectBoundsRecurive(TreeTrunk& curTrunk, int curTrunkSize, const void* object, const BoundsTemplate& originalBounds, const BoundsTemplate& newBounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, originalBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(updateObjectBoundsRecurive(subTrunk, subTrunkSize, object, originalBounds, newBounds)) {
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize));
return true;
}
} else {
if(subNode.asObject() == object) {
curTrunk.setBoundsOfSubNode(i, newBounds);
return true;
}
}
}
return false;
}
void BoundsTreePrototype::updateObjectBounds(const void* object, const BoundsTemplate& originalBounds, const BoundsTemplate& newBounds) {
bool wasFound = updateObjectBoundsRecurive(this->baseTrunk, this->baseTrunkSize, object, originalBounds, newBounds);
if(!wasFound) throw "Object was not found!";
}
static bool findAndReplaceObjectRecursive(TreeTrunk& curTrunk, int curTrunkSize, const void* oldObject, void* newObject, const BoundsTemplate& bounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, bounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(findAndReplaceObjectRecursive(subTrunk, subTrunkSize, oldObject, newObject, bounds)) {
return true;
}
} else {
if(subNode.asObject() == oldObject) {
subNode.setObject(newObject);
return true;
}
}
}
return false;
}
void BoundsTreePrototype::findAndReplaceObject(const void* oldObject, void* newObject, const BoundsTemplate& bounds) {
bool found = findAndReplaceObjectRecursive(this->baseTrunk, this->baseTrunkSize, oldObject, newObject, bounds);
if(!found) throw "Object to rename not found!";
}
static bool disbandGroupRecursive(TreeTrunk& curTrunk, int curTrunkSize, const void* groupRep, const BoundsTemplate& groupRepBounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, groupRepBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subTrunk, subTrunkSize, groupRep, groupRepBounds)) {
subNode.removeGroupHead();
return true;
}
} else {
if(disbandGroupRecursive(subTrunk, subTrunkSize, groupRep, groupRepBounds)) {
return true;
}
}
} else {
if(subNode.asObject() == groupRep) {
return true;
}
}
}
return false;
}
void BoundsTreePrototype::disbandGroup(const void* groupRep, const BoundsTemplate& groupRepBounds) {
disbandGroupRecursive(this->baseTrunk, this->baseTrunkSize, groupRep, groupRepBounds);
}
static size_t getSizeRecursive(const TreeTrunk& curTrunk, int curTrunkSize) {
size_t total = 0;
for(int i = 0; i < curTrunkSize; i++) {
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
total += getSizeRecursive(subNode.asTrunk(), subNode.getTrunkSize());
} else {
total++;
}
}
return total;
}
size_t BoundsTreePrototype::size() const {
return getSizeRecursive(baseTrunk, baseTrunkSize);
}
size_t BoundsTreePrototype::groupSize(const void* groupRep, const BoundsTemplate& groupRepBounds) const {
const TreeNodeRef* groupFound = getGroupRecursive(this->baseTrunk, this->baseTrunkSize, groupRep, groupRepBounds);
if(!groupFound) {
throw "Group not found!";
}
if(groupFound->isTrunkNode()) {
return getSizeRecursive(groupFound->asTrunk(), groupFound->getTrunkSize());
} else {
return 1;
}
}
void BoundsTreePrototype::clear() {
this->allocator.freeAllTrunks(this->baseTrunk, this->baseTrunkSize);
this->baseTrunkSize = 0;
}
// means that this trunk and it's subtrunks cannot be improved
static bool isLeafTrunk(TreeTrunk& trunk, int trunkSize) {
for(int subNodeI = 0; subNodeI < trunkSize; subNodeI++) {
TreeNodeRef& subNode = trunk.subNodes[subNodeI];
if(!subNode.isGroupHeadOrLeaf()) return false;
}
return true;
}
static void improveTrunkHorizontal(TrunkAllocator& alloc, TreeTrunk& trunk) {
constexpr int trunkSize = BRANCH_FACTOR; // required by the interface, allows for small SIMD optimizations
assert(!isLeafTrunk(trunk, trunkSize));
// indexed overlaps[i][j] with j >= i+1
OverlapMatrix overlaps = TrunkSIMDHelperFallback::computeInternalBoundsOverlapMatrix(trunk, trunkSize);
for(int subNodeAI = 0; subNodeAI < trunkSize-1; subNodeAI++) {
TreeNodeRef& subNodeA = trunk.subNodes[subNodeAI];
if(subNodeA.isGroupHeadOrLeaf()) continue; // no breaking up groups
TreeTrunk& subTrunkA = subNodeA.asTrunk();
int subTrunkSizeA = subNodeA.getTrunkSize();
for(int subNodeBI = subNodeAI+1; subNodeBI < trunkSize - 1; subNodeBI++) {
TreeNodeRef& subNodeB = trunk.subNodes[subNodeBI];
if(subNodeB.isGroupHeadOrLeaf()) continue; // no breaking up groups
TreeTrunk& subTrunkB = subNodeB.asTrunk();
int subTrunkSizeB = subNodeB.getTrunkSize();
if(!overlaps[subNodeAI][subNodeBI]) continue;
if(subTrunkSizeA + subTrunkSizeB >= BRANCH_FACTOR + 2) {
// if true, this updates subTrunkSizeA and subTrunkSizeB
if(TrunkSIMDHelperFallback::exchangeNodesBetween(subTrunkA, subTrunkSizeA, subTrunkB, subTrunkSizeB)) {
// update treenoderefs
subNodeA.setTrunkSize(subTrunkSizeA);
subNodeB.setTrunkSize(subTrunkSizeB);
trunk.setBoundsOfSubNode(subNodeAI, TrunkSIMDHelperFallback::getTotalBounds(subTrunkA, subTrunkSizeA));
trunk.setBoundsOfSubNode(subNodeBI, TrunkSIMDHelperFallback::getTotalBounds(subTrunkB, subTrunkSizeB));
// just return, changing trunkNodes invalidated overlaps matrix, more improvements can be done in future calls
return;
}
} else {
// can just merge out a Trunk
// move all but first element from B to A
subTrunkSizeA = TrunkSIMDHelperFallback::transferNodes(subTrunkB, 1, subTrunkSizeB, subTrunkA, subTrunkSizeA);
subNodeA.setTrunkSize(subTrunkSizeA);
trunk.setBoundsOfSubNode(subNodeAI, TrunkSIMDHelperFallback::getTotalBounds(subTrunkA, subTrunkSizeA));
trunk.setSubNode(subNodeBI, std::move(subTrunkB.subNodes[0]), subTrunkB.getBoundsOfSubNode(0));
alloc.freeTrunk(&subTrunkB);
// just return, changing trunkNodes invalidated overlaps matrix, more improvements can be done in future calls
return;
}
}
}
}
inline static void swapNodesBetweenTrunks(TreeTrunk& trunkA, int indexInTrunkA, TreeTrunk& trunkB, int indexInTrunkB) {
BoundsTemplate boundsA = trunkA.getBoundsOfSubNode(indexInTrunkA);
BoundsTemplate boundsB = trunkB.getBoundsOfSubNode(indexInTrunkB);
trunkA.setBoundsOfSubNode(indexInTrunkA, boundsB);
trunkB.setBoundsOfSubNode(indexInTrunkB, boundsA);
std::swap(trunkA.subNodes[indexInTrunkA], trunkB.subNodes[indexInTrunkB]);
}
static void improveTrunkVertical(TreeTrunk& trunk) {
constexpr int trunkSize = BRANCH_FACTOR; // required by the interface, allows for small SIMD optimizations
if(isLeafTrunk(trunk, trunkSize)) return; // no improvement possible
std::array allExistingSizes = TrunkSIMDHelperFallback::computeAllCosts(trunk);
int smallestSubNodeI = 0;
float smallestSubNodeSize = allExistingSizes[0];
for(int i = 1; i < trunkSize; i++) {
if(allExistingSizes[i] < smallestSubNodeSize) {
smallestSubNodeSize = allExistingSizes[i];
smallestSubNodeI = i;
}
}
int largestItemSubTrunkI = -1;
int largestItemI = -1;
float largestItemInSubTrunkISize = smallestSubNodeSize; // try to beat this, if we can't then there's no swap
for(int subTrunkI = 0; subTrunkI < trunkSize; subTrunkI++) {
if(subTrunkI == smallestSubNodeI) continue; // the smallest node cannot contain a node larger than itself, also, trying to swap these would store this node in itself, leading to memory leak and objects disappearing
TreeNodeRef& subNode = trunk.subNodes[subTrunkI];
// find a node, and try to swap it with an element from a group
if(subNode.isGroupHeadOrLeaf()) continue;
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
std::array subTrunkSizes = TrunkSIMDHelperFallback::computeAllCosts(subTrunk);
for(int i = 0; i < subTrunkSize; i++) {
if(subTrunkSizes[i] > largestItemInSubTrunkISize) {
largestItemInSubTrunkISize = subTrunkSizes[i];
largestItemSubTrunkI = subTrunkI;
largestItemI = i;
}
}
}
if(largestItemI != -1) {
// an improvement can be made by swapping
TreeTrunk& chosenSubTrunk = trunk.subNodes[largestItemSubTrunkI].asTrunk();
int chosenSubTrunkSize = trunk.subNodes[largestItemSubTrunkI].getTrunkSize();
swapNodesBetweenTrunks(trunk, smallestSubNodeI, chosenSubTrunk, largestItemI);
trunk.setBoundsOfSubNode(largestItemSubTrunkI, TrunkSIMDHelperFallback::getTotalBounds(chosenSubTrunk, chosenSubTrunkSize));
}
}
static int moveElementsOutOfGroup(TrunkAllocator& alloc, TreeTrunk& trunk, int trunkSize) {
assert(!isLeafTrunk(trunk, trunkSize));
if(trunkSize >= BRANCH_FACTOR) return trunkSize;
int freeSlots = BRANCH_FACTOR - trunkSize;
for(int subNodeI = 0; subNodeI < trunkSize; subNodeI++) {
TreeNodeRef& subNode = trunk.subNodes[subNodeI];
// find a node, and try to swap it with an element from a group
if(!subNode.isGroupHeadOrLeaf()) { // no breaking up groups
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(subTrunkSize <= freeSlots + 1) {
// whole trunk is consumed, can use an extra slot since
trunk.setSubNode(subNodeI, std::move(subTrunk.subNodes[0]), subTrunk.getBoundsOfSubNode(0));
trunkSize = TrunkSIMDHelperFallback::transferNodes(subTrunk, 1, subTrunkSize, trunk, trunkSize);
alloc.freeTrunk(&subTrunk);
if(trunkSize >= BRANCH_FACTOR) return trunkSize;
freeSlots = BRANCH_FACTOR - trunkSize;
} else {
// not consuming the whole trunk add what we can
int resultingSubTrunkSize = subTrunkSize - freeSlots;
int resultingSize = TrunkSIMDHelperFallback::transferNodes(subTrunk, resultingSubTrunkSize, subTrunkSize, trunk, trunkSize);
subNode.setTrunkSize(resultingSubTrunkSize);
trunk.setBoundsOfSubNode(subNodeI, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, resultingSubTrunkSize));
assert(resultingSize == BRANCH_FACTOR);
return resultingSize;
}
}
}
return trunkSize;
}
static int improveStructureRecursive(TrunkAllocator& alloc, TreeTrunk& trunk, int trunkSize) {
bool givenTrunkIsLeafTrunk = true;
for(int i = 0; i < trunkSize; i++) {
TreeNodeRef& subNode = trunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
subTrunkSize = improveStructureRecursive(alloc, subTrunk, subTrunkSize);
subNode.setTrunkSize(subTrunkSize);
if(!subNode.isGroupHead()) givenTrunkIsLeafTrunk = false;
}
}
if(givenTrunkIsLeafTrunk) return trunkSize;
trunkSize = moveElementsOutOfGroup(alloc, trunk, trunkSize);
if(trunkSize != BRANCH_FACTOR) return trunkSize;
if(isLeafTrunk(trunk, trunkSize)) return trunkSize;
improveTrunkVertical(trunk); // trunkSize == BRANCH_FACTOR
improveTrunkHorizontal(alloc, trunk); // trunkSize == BRANCH_FACTOR
return trunkSize;
}
void BoundsTreePrototype::improveStructure() {
improveStructureRecursive(this->allocator, this->baseTrunk, this->baseTrunkSize);
}
void BoundsTreePrototype::maxImproveStructure() {
for(int i = 0; i < 5; i++) {
this->improveStructure();
}
}
};
================================================
FILE: Physics3D/boundstree/boundsTree.h
================================================
#pragma once
#include "../math/fix.h"
#include "../math/position.h"
#include "../math/bounds.h"
#include "../datastructures/iteratorEnd.h"
#include "../datastructures/iteratorFactory.h"
#include
#include
#include
#include
#include
#include
#include
#include
namespace P3D {
constexpr int BRANCH_FACTOR = 8;
static_assert((BRANCH_FACTOR & (BRANCH_FACTOR - 1)) == 0, "Branch factor must be power of 2");
struct TreeTrunk;
inline float computeCost(const BoundsTemplate& bounds) {
Vec3f d = bounds.getDiagonal();
return d.x + d.y + d.z;
}
inline float computeAdditionCost(const BoundsTemplate& oldBounds, const BoundsTemplate& extraBounds) {
return computeCost(unionOfBounds(oldBounds, extraBounds)) - computeCost(oldBounds);
}
class TreeNodeRef {
friend struct TreeTrunk;
static constexpr std::uintptr_t SIZE_DATA_MASK = BRANCH_FACTOR - 1;
static constexpr std::uintptr_t GROUP_HEAD_MASK = BRANCH_FACTOR;
static constexpr std::uintptr_t PTR_MASK = ~(SIZE_DATA_MASK | GROUP_HEAD_MASK);
static constexpr std::uintptr_t INVALID_REF = 0xADADADADADADADAD;
/* encoding:
0b...pppppgsss
(this is if BRANCH_FACTOR == 8)
Last 3 bits specify type:
0b000: leaf node -> ptr points to object
else : trunk node -> ptr points to TreeTrunk
g bit specifies 'isGroupHead'
s bits specify size of this trunk node - 1. 0b111 is a trunknode of size 8
*/
std::uintptr_t ptr;
inline int getSizeData() const {
return static_cast(ptr & SIZE_DATA_MASK);
}
public:
#ifndef NDEBUG
TreeNodeRef() noexcept : ptr(INVALID_REF) {}
#else
TreeNodeRef() = default;
#endif
inline explicit TreeNodeRef(TreeTrunk* trunk, int trunkSize, bool isGroupHead) noexcept;
inline explicit TreeNodeRef(void* ptr) noexcept : ptr(reinterpret_cast(ptr)) {
assert((this->ptr & SIZE_DATA_MASK) == 0);
}
TreeNodeRef(const TreeNodeRef&) = delete;
TreeNodeRef& operator=(const TreeNodeRef&) = delete;
#ifndef NDEBUG
TreeNodeRef(TreeNodeRef&& other) noexcept : ptr(other.ptr) { other.ptr = INVALID_REF; }
TreeNodeRef& operator=(TreeNodeRef&& other) noexcept { this->ptr = other.ptr; other.ptr = INVALID_REF; return *this; }
#else
TreeNodeRef(TreeNodeRef&& other) = default;
TreeNodeRef& operator=(TreeNodeRef&& other) = default;
#endif
inline bool isLeafNode() const {
assert(this->ptr != INVALID_REF);
return getSizeData() == 0;
}
inline bool isTrunkNode() const {
assert(this->ptr != INVALID_REF);
return getSizeData() != 0;
}
inline int getTrunkSize() const {
assert(isTrunkNode());
return getSizeData() + 1;
}
inline void setTrunkSize(int newSize) {
assert(isTrunkNode());
assert(newSize >= 2 && newSize <= BRANCH_FACTOR);
this->ptr = (this->ptr & ~SIZE_DATA_MASK) | (newSize - 1);
}
inline void setObject(void* newObject) {
assert(isLeafNode());
this->ptr = reinterpret_cast(newObject);
assert((this->ptr & SIZE_DATA_MASK) == 0);
}
inline void makeGroupHead() {
assert(isTrunkNode());
this->ptr |= GROUP_HEAD_MASK;
}
inline void removeGroupHead() {
assert(isTrunkNode());
this->ptr &= ~GROUP_HEAD_MASK;
}
inline bool isGroupHead() const {
assert(isTrunkNode());
return (ptr & GROUP_HEAD_MASK) != 0;
}
inline bool isGroupHeadOrLeaf() const {
if(isTrunkNode()) {
return (ptr & GROUP_HEAD_MASK) != 0;
} else {
return true;
}
}
inline const TreeTrunk& asTrunk() const;
inline TreeTrunk& asTrunk();
inline void* asObject() const;
// expects a function of the form BoundsTemplate(const CastTo&)
template
inline BoundsTemplate recalculateBoundsRecursive(const GetObjectBoundsFunc& getObjBounds);
};
struct TrunkSIMDHelperFallback;
template
struct alignas(64) BoundsArray {
float xMin[Size];
float yMin[Size];
float zMin[Size];
float xMax[Size];
float yMax[Size];
float zMax[Size];
inline BoundsTemplate getBounds(int index) const {
assert(index >= 0 && index < Size);
BoundsTemplate result;
result.min.x = xMin[index];
result.min.y = yMin[index];
result.min.z = zMin[index];
result.max.x = xMax[index];
result.max.y = yMax[index];
result.max.z = zMax[index];
return result;
}
inline void setBounds(int index, const BoundsTemplate& newBounds) {
assert(index >= 0 && index < Size);
xMin[index] = newBounds.min.x;
yMin[index] = newBounds.min.y;
zMin[index] = newBounds.min.z;
xMax[index] = newBounds.max.x;
yMax[index] = newBounds.max.y;
zMax[index] = newBounds.max.z;
}
inline void move(int from, int to) {
assert(from >= 0 && from < Size);
assert(to >= 0 && to < Size);
xMin[to] = xMin[from];
yMin[to] = yMin[from];
zMin[to] = zMin[from];
xMax[to] = xMax[from];
yMax[to] = yMax[from];
zMax[to] = zMax[from];
}
};
struct alignas(64) TreeTrunk {
BoundsArray subNodeBounds;
TreeNodeRef subNodes[BRANCH_FACTOR];
inline BoundsTemplate getBoundsOfSubNode(int subNode) const {
return this->subNodeBounds.getBounds(subNode);
}
inline void setBoundsOfSubNode(int subNode, const BoundsTemplate& newBounds) {
this->subNodeBounds.setBounds(subNode, newBounds);
}
inline void moveSubNode(int from, int to) {
this->subNodeBounds.move(from, to);
this->subNodes[to] = std::move(this->subNodes[from]);
}
inline void setSubNode(int subNode, TreeNodeRef&& newNode, const BoundsTemplate& newBounds) {
this->subNodeBounds.setBounds(subNode, newBounds);
subNodes[subNode] = std::move(newNode);
}
};
TreeNodeRef::TreeNodeRef(TreeTrunk* trunk, int trunkSize, bool isGroupHead) noexcept : ptr(reinterpret_cast(trunk) | (static_cast(trunkSize) - 1) | (isGroupHead ? BRANCH_FACTOR : 0)) {
assert(trunkSize >= 2 && trunkSize <= BRANCH_FACTOR); // trunkSize must be between 2-BRANCH_FACTOR
assert((reinterpret_cast(trunk) & (BRANCH_FACTOR * sizeof(int64_t) - 1)) == 0); // check trunk is aligned correctly
}
const TreeTrunk& TreeNodeRef::asTrunk() const {
assert(this->ptr != INVALID_REF);
assert(isTrunkNode());
return *reinterpret_cast(ptr & PTR_MASK);
}
TreeTrunk& TreeNodeRef::asTrunk() {
assert(this->ptr != INVALID_REF);
assert(isTrunkNode());
return *reinterpret_cast(ptr & PTR_MASK);
}
void* TreeNodeRef::asObject() const {
assert(this->ptr != INVALID_REF);
assert(isLeafNode());
return reinterpret_cast(ptr & ~SIZE_DATA_MASK);
}
struct OverlapMatrix {
bool overlapData[BRANCH_FACTOR*BRANCH_FACTOR]{};
inline bool* operator[](size_t idx) {return overlapData+BRANCH_FACTOR*idx;}
inline const bool* operator[](size_t idx) const {return overlapData+BRANCH_FACTOR*idx;}
};
struct TrunkSIMDHelperFallback {
static BoundsTemplate getTotalBounds(const TreeTrunk& trunk, int upTo);
static BoundsTemplate getTotalBoundsWithout(const TreeTrunk& trunk, int upTo, int without);
// returns a list of bounds, every index contains the total bounds without that subNode
static BoundsArray getAllTotalBoundsWithout(const TreeTrunk& trunk, int upTo);
static std::array getAllContainsBounds(const TreeTrunk& trunk, const BoundsTemplate& boundsToContain);
static std::array computeAllCosts(const TreeTrunk& trunk);
static std::array computeAllCombinationCosts(const BoundsArray& boundsArr, const BoundsTemplate& boundsExtention);
static std::pair computeFurthestObjects(const BoundsArray& boundsArray, int size);
static int getLowestCombinationCost(const TreeTrunk& trunk, const BoundsTemplate& boundsExtention, int nodeSize);
static std::array computeOverlapsWith(const TreeTrunk& trunk, int trunkSize, const BoundsTemplate& bounds);
// indexed result[a][b]
static OverlapMatrix computeBoundsOverlapMatrix(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize);
static OverlapMatrix computeBoundsOverlapMatrixAVX(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize);
static OverlapMatrix computeBoundsOverlapMatrixSSE(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize);
// indexed result[i][j] with j >= i+1
static OverlapMatrix computeInternalBoundsOverlapMatrix(const TreeTrunk& trunk, int trunkSize);
static std::array computeAllExtentionCosts(const TreeTrunk& trunk, int trunkSize, const BoundsTemplate& extraBounds);
// returns resulting destTrunk size
static int transferNodes(TreeTrunk& srcTrunk, int srcTrunkStart, int srcTrunkEnd, TreeTrunk& destTrunk, int destTrunkSize);
static BoundsArray combineBoundsArrays(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize);
// returns true if modified
static bool exchangeNodesBetween(TreeTrunk& trunkA, int& trunkASize, TreeTrunk& trunkB, int& trunkBSize);
};
template
inline BoundsTemplate TreeNodeRef::recalculateBoundsRecursive(const GetObjectBoundsFunc& getObjBounds) {
int sizeData = getSizeData();
if(sizeData == 0) { // is leaf node
return getObjBounds(*reinterpret_cast(this->asObject()));
} else {
int trunkSize = sizeData + 1;
TreeTrunk* trunk = this->asTrunk();
for(int i = 0; i < trunkSize; i++) {
trunk->setBoundsOfSubNode(i, trunk->subNodes[i].recalculateBoundsRecursive(getObjBounds));
}
return TrunkSIMDHelperFallback::getTotalBounds(*trunk, trunkSize);
}
}
class TrunkAllocator {
size_t allocationCount;
public:
TrunkAllocator();
~TrunkAllocator();
TrunkAllocator(const TrunkAllocator&) = delete;
TrunkAllocator& operator=(const TrunkAllocator&) = delete;
TrunkAllocator(TrunkAllocator&& other) noexcept;
TrunkAllocator& operator=(TrunkAllocator&& other) noexcept;
TreeTrunk* allocTrunk();
void freeTrunk(TreeTrunk* trunk);
void freeAllTrunks(TreeTrunk& baseTrunk, int baseTrunkSize);
};
int addRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, TreeNodeRef&& newNode, const BoundsTemplate& bounds);
int removeRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, const void* objectToRemove, const BoundsTemplate& bounds);
bool containsObjectRecursive(const TreeTrunk& trunk, int trunkSize, const void* object, const BoundsTemplate& bounds);
const TreeNodeRef* getGroupRecursive(const TreeTrunk& curTrunk, int curTrunkSize, const void* groupRepresentative, const BoundsTemplate& representativeBounds);
/*
Expects a function of the form BoundsTemplate(TreeNodeRef& groupNode, const BoundsTemplate& groupNodeBounds)
Should return the new bounds of the node.
The input group and resulting group should be in the normal form of a group:
if Trunk node => isGroupHead is enabled, no trunk node below has isGroupHead
or Leaf node
*/
template
inline bool modifyGroupRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, const void* groupRepresentative, const BoundsTemplate& groupRepresentativeBounds, const Func& modification) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, groupRepresentativeBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subTrunk, subTrunkSize, groupRepresentative, groupRepresentativeBounds)) {
BoundsTemplate newTrunkBounds = modification(subNode, curTrunk.getBoundsOfSubNode(i));
assert(subNode.isGroupHeadOrLeaf());
curTrunk.setBoundsOfSubNode(i, newTrunkBounds);
return true;
}
} else {
if(modifyGroupRecursive(allocator, subTrunk, subTrunkSize, groupRepresentative, groupRepresentativeBounds, modification)) {
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize));
return true;
}
}
} else {
if(subNode.asObject() == groupRepresentative) {
BoundsTemplate newTrunkBounds = modification(subNode, curTrunk.getBoundsOfSubNode(i));
assert(subNode.isGroupHeadOrLeaf());
curTrunk.setBoundsOfSubNode(i, newTrunkBounds);
return true;
}
}
}
return false;
}
/*
Expects a function of the form std::optional>(TreeNodeRef& groupNode, const BoundsTemplate& groupNodeBounds)
Should return the new bounds of the node.
The input group and resulting group should be in the normal form of a group:
if Trunk node => isGroupHead is enabled, no trunk node below has isGroupHead
or Leaf node
*/
template
inline int contractGroupRecursive(TrunkAllocator& allocator, TreeTrunk& curTrunk, int curTrunkSize, const void* groupRepresentative, const BoundsTemplate& groupRepresentativeBounds, const Func& modification) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, groupRepresentativeBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subTrunk, subTrunkSize, groupRepresentative, groupRepresentativeBounds)) {
std::optional> newTrunkBounds = modification(subNode, curTrunk.getBoundsOfSubNode(i));
if(newTrunkBounds.has_value()) {
assert(subNode.isGroupHeadOrLeaf());
curTrunk.setBoundsOfSubNode(i, newTrunkBounds.value());
return curTrunkSize;
} else {
curTrunk.moveSubNode(curTrunkSize - 1, i);
return curTrunkSize - 1;
}
}
} else {
int newSubSize = contractGroupRecursive(allocator, subTrunk, subTrunkSize, groupRepresentative, groupRepresentativeBounds, modification);
if(newSubSize != -1) {
assert(newSubSize >= 1);
if(newSubSize == 1) {
curTrunk.setSubNode(i, std::move(subTrunk.subNodes[0]), subTrunk.getBoundsOfSubNode(0));
allocator.freeTrunk(&subTrunk);
} else {
subNode.setTrunkSize(newSubSize);
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, newSubSize));
}
return curTrunkSize;
}
}
} else {
if(subNode.asObject() == groupRepresentative) {
std::optional> newTrunkBounds = modification(subNode, curTrunk.getBoundsOfSubNode(i));
if(newTrunkBounds.has_value()) {
assert(subNode.isGroupHeadOrLeaf());
curTrunk.setBoundsOfSubNode(i, newTrunkBounds.value());
return curTrunkSize;
} else {
curTrunk.moveSubNode(curTrunkSize - 1, i);
return curTrunkSize - 1;
}
}
}
}
return -1;
}
// expects a function of the form void(Boundable& object)
template
inline void forEachRecurse(const TreeTrunk& curTrunk, int curTrunkSize, const Func& func) {
for(int i = 0; i < curTrunkSize; i++) {
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
forEachRecurse(subNode.asTrunk(), subNode.getTrunkSize(), func);
} else {
func(*static_cast(subNode.asObject()));
}
}
}
// expects a filter of the form std::array(const TreeTrunk& trunk, int trunkSize)
// expects a function of the form void(Boundable& object)
template
inline void forEachFilteredRecurse(const TreeTrunk& curTrunk, int curTrunkSize, const Filter& filter, const Func& func) {
std::array passes = filter(curTrunk, curTrunkSize);
for(int i = 0; i < curTrunkSize; i++) {
if(passes[i]) {
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
forEachFilteredRecurse(subNode.asTrunk(), subNode.getTrunkSize(), filter, func);
} else {
func(*static_cast(subNode.asObject()));
}
}
}
}
// expects a function of the form void(Boundable*, Boundable*)
// Calls the given function for each pair of leaf nodes from the two trunks
template
void forEachColissionWithRecursive(Boundable* obj, const BoundsTemplate& objBounds, const TreeTrunk& trunk, int trunkSize, const Func& func) {
std::array collidesWith = SIMDHelper::computeOverlapsWith(trunk, trunkSize, objBounds);
for(int i = 0; i < trunkSize; i++) {
if(!collidesWith[i]) continue;
const TreeNodeRef& subNode = trunk.subNodes[i];
if(subNode.isTrunkNode()) {
forEachColissionWithRecursive(obj, objBounds, subNode.asTrunk(), subNode.getTrunkSize(), func);
} else {
func(obj, static_cast(subNode.asObject()));
}
}
}
// expects a function of the form void(Boundable*, Boundable*)
// Calls the given function for each pair of leaf nodes from the two trunks
template
void forEachColissionWithRecursive(const TreeTrunk& trunk, int trunkSize, Boundable* obj, const BoundsTemplate& objBounds, const Func& func) {
std::array collidesWith = SIMDHelper::computeOverlapsWith(trunk, trunkSize, objBounds);
for(int i = 0; i < trunkSize; i++) {
if(!collidesWith[i]) continue;
const TreeNodeRef& subNode = trunk.subNodes[i];
if(subNode.isTrunkNode()) {
forEachColissionWithRecursive(subNode.asTrunk(), subNode.getTrunkSize(), obj, objBounds, func);
} else {
func(static_cast(subNode.asObject()), obj);
}
}
}
// expects a function of the form void(Boundable*, Boundable*)
// Calls the given function for each pair of leaf nodes from the two trunks
template
void forEachColissionBetweenRecursive(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize, const Func& func) {
OverlapMatrix overlapBetween = SIMDHelper::computeBoundsOverlapMatrix(trunkA, trunkASize, trunkB, trunkBSize);
for(int a = 0; a < trunkASize; a++) {
const TreeNodeRef& aNode = trunkA.subNodes[a];
bool aIsTrunk = aNode.isTrunkNode();
for(int b = 0; b < trunkBSize; b++) {
if(!overlapBetween[a][b]) continue;
const TreeNodeRef& bNode = trunkB.subNodes[b];
bool bIsTrunk = bNode.isTrunkNode();
if(aIsTrunk) {
if(bIsTrunk) {
// both trunk nodes
// split both
forEachColissionBetweenRecursive(aNode.asTrunk(), aNode.getTrunkSize(), bNode.asTrunk(), bNode.getTrunkSize(), func);
} else {
// a is trunk, b is not
// split a
forEachColissionWithRecursive(aNode.asTrunk(), aNode.getTrunkSize(), static_cast(bNode.asObject()), trunkB.getBoundsOfSubNode(b), func);
}
} else {
if(bIsTrunk) {
// b is trunk, a is not
// split b
forEachColissionWithRecursive(static_cast(aNode.asObject()), trunkA.getBoundsOfSubNode(a), bNode.asTrunk(), bNode.getTrunkSize(), func);
} else {
// both leaf nodes
func(static_cast(aNode.asObject()), static_cast(bNode.asObject()));
}
}
}
}
}
// expects a function of the form void(Boundable*, Boundable*)
// Calls the given function for each pair of leaf nodes that are not in the same group and have overlapping bounds
template
void forEachColissionInternalRecursive(const TreeTrunk& curTrunk, int curTrunkSize, const Func& func) {
OverlapMatrix internalOverlap = SIMDHelper::computeInternalBoundsOverlapMatrix(curTrunk, curTrunkSize);
for(int a = 0; a < curTrunkSize; a++) {
const TreeNodeRef& aNode = curTrunk.subNodes[a];
bool aIsTrunk = aNode.isTrunkNode();
for(int b = a+1; b < curTrunkSize; b++) {
if(!internalOverlap[a][b]) continue;
const TreeNodeRef& bNode = curTrunk.subNodes[b];
bool bIsTrunk = bNode.isTrunkNode();
if(aIsTrunk) {
if(bIsTrunk) {
// both trunk nodes
// split both
forEachColissionBetweenRecursive(aNode.asTrunk(), aNode.getTrunkSize(), bNode.asTrunk(), bNode.getTrunkSize(), func);
} else {
// a is trunk, b is not
// split a
forEachColissionWithRecursive(aNode.asTrunk(), aNode.getTrunkSize(), static_cast(bNode.asObject()), curTrunk.getBoundsOfSubNode(b), func);
}
} else {
if(bIsTrunk) {
// b is trunk, a is not
// split b
forEachColissionWithRecursive(static_cast(aNode.asObject()), curTrunk.getBoundsOfSubNode(a), bNode.asTrunk(), bNode.getTrunkSize(), func);
} else {
// both leaf nodes
func(static_cast(aNode.asObject()), static_cast(bNode.asObject()));
}
}
}
}
for(int i = 0; i < curTrunkSize; i++) {
const TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode() && !subNode.isGroupHead()) {
forEachColissionInternalRecursive(subNode.asTrunk(), subNode.getTrunkSize(), func);
}
}
}
class BoundsTreeIteratorPrototype {
struct StackElement {
const TreeTrunk* trunk;
int trunkSize;
int curIndex;
};
std::stack trunkStack;
public:
BoundsTreeIteratorPrototype() = default;
BoundsTreeIteratorPrototype(const TreeTrunk& baseTrunk, int baseTrunkSize) : trunkStack() {
if(baseTrunkSize == 0) return;
trunkStack.push(StackElement{&baseTrunk, baseTrunkSize, 0});
while(trunkStack.top().trunk->subNodes[0].isTrunkNode()) {
const TreeNodeRef& nextNode = trunkStack.top().trunk->subNodes[0];
trunkStack.push(StackElement{&nextNode.asTrunk(), nextNode.getTrunkSize(), 0});
}
}
void* operator*() const {
const StackElement& top = trunkStack.top();
return top.trunk->subNodes[top.curIndex].asObject();
}
BoundsTreeIteratorPrototype& operator++() {
StackElement& top = trunkStack.top();
top.curIndex++;
if(top.curIndex < top.trunkSize) {
const TreeNodeRef& nextNode = trunkStack.top().trunk->subNodes[top.curIndex];
if(nextNode.isTrunkNode()) {
// rise until hitting non trunk node
trunkStack.push(StackElement{&nextNode.asTrunk(), nextNode.getTrunkSize(), 0});
while(trunkStack.top().trunk->subNodes[0].isTrunkNode()) {
const TreeNodeRef& nextNode = trunkStack.top().trunk->subNodes[0];
trunkStack.push(StackElement{&nextNode.asTrunk(), nextNode.getTrunkSize(), 0});
}
}
} else {
// drop down until next available
do {
trunkStack.pop();
if(trunkStack.size() == 0) break; // iteration done
StackElement& top = trunkStack.top();
top.curIndex++;
assert(top.curIndex <= top.trunkSize);
} while(top.curIndex == top.trunkSize); // keep popping
}
return *this;
}
bool operator!=(IteratorEnd) const {
return trunkStack.size() != 0;
}
};
template
class FilteredTreeIteratorPrototype {
struct StackElement {
const TreeTrunk* trunk;
int trunkSize;
int curIndex;
std::array filterResults;
};
Filter filter;
std::stack trunkStack;
public:
FilteredTreeIteratorPrototype() = default;
void delveDown() {
while(true) {
again:
StackElement& top = trunkStack.top();
for(; top.curIndex < top.trunkSize; top.curIndex++) {
if(top.filterResults[top.curIndex]) {
const TreeNodeRef& subNode = top.trunk->subNodes[top.curIndex];
if(!subNode.isTrunkNode()) break;
StackElement newElem{&subNode.asTrunk(), subNode.getTrunkSize(), 0};
newElem.filterResults = this->filter(*newElem.trunk, newElem.trunkSize);
trunkStack.push(newElem);
goto again;
}
}
// no available node found, go back up a level
trunkStack.pop();
if(trunkStack.empty()) return; // end of iteration
StackElement& newTop = trunkStack.top();
newTop.curIndex++;
}
}
FilteredTreeIteratorPrototype(const TreeTrunk& baseTrunk, int baseTrunkSize, const Filter& filter) : trunkStack(), filter(filter) {
if(baseTrunkSize == 0) return;
StackElement newElement{&baseTrunk, baseTrunkSize};
newElement.filterResults = this->filter(baseTrunk, baseTrunkSize);
for(int i = 0; i < baseTrunkSize; i++){
if(newElement.filterResults[i]) {
newElement.curIndex = i;
trunkStack.push(newElement);
goto found;
}
}
return; // none of the nodes are suitable
found:;
this->delveDown();
}
void* operator*() const {
const StackElement& top = trunkStack.top();
return top.trunk->subNodes[top.curIndex].asObject();
}
FilteredTreeIteratorPrototype& operator++() {
StackElement& top = trunkStack.top();
top.curIndex++;
this->delveDown();
return *this;
}
bool operator!=(IteratorEnd) const {
return trunkStack.size() != 0;
}
};
class BoundsTreePrototype {
TreeTrunk baseTrunk;
int baseTrunkSize;
TrunkAllocator allocator;
template
friend class BoundsTree;
public:
BoundsTreePrototype();
~BoundsTreePrototype();
inline BoundsTreePrototype(BoundsTreePrototype&& other) noexcept : baseTrunk(std::move(other.baseTrunk)), baseTrunkSize(other.baseTrunkSize), allocator(std::move(other.allocator)) {
other.baseTrunkSize = 0;
}
inline BoundsTreePrototype& operator=(BoundsTreePrototype&& other) noexcept {
this->baseTrunk = std::move(other.baseTrunk);
this->baseTrunkSize = other.baseTrunkSize;
this->allocator = std::move(other.allocator);
other.baseTrunkSize = 0;
return *this;
}
void add(void* newObject, const BoundsTemplate& bounds);
void remove(const void* objectToRemove, const BoundsTemplate& bounds);
void addToGroup(void* newObject, const BoundsTemplate& newObjectBounds, const void* groupRepresentative, const BoundsTemplate& groupRepBounds);
void mergeGroups(const void* groupRepA, const BoundsTemplate& repABounds, const void* groupRepB, const BoundsTemplate& repBBounds);
void transferGroupTo(const void* groupRep, const BoundsTemplate& groupRepBounds, BoundsTreePrototype& destinationTree);
void updateObjectBounds(const void* object, const BoundsTemplate& originalBounds, const BoundsTemplate& newBounds);
// oldObject and newObject should have the same bounds
void findAndReplaceObject(const void* oldObject, void* newObject, const BoundsTemplate& bounds);
void disbandGroup(const void* groupRep, const BoundsTemplate& groupRepBounds);
bool contains(const void* object, const BoundsTemplate& bounds) const;
bool groupContains(const void* groupRep, const BoundsTemplate& groupRepBounds, const void* object, const BoundsTemplate& bounds) const;
size_t size() const;
size_t groupSize(const void* groupRep, const BoundsTemplate& groupRepBounds) const;
inline bool isEmpty() const {
return this->baseTrunkSize == 0;
}
void clear();
template
void removeSubGroup(GroupIter iter, const GroupIterEnd& iterEnd) {
if(!(iter != iterEnd)) return;
std::pair> firstObject = *iter;
++iter;
if(!(iter != iterEnd)) { // just one item
this->remove(firstObject.first, firstObject.second);
} else {
int newMainSize = contractGroupRecursive(this->allocator, this->baseTrunk, this->baseTrunkSize, firstObject.first, firstObject.second, [this, &iter, &iterEnd, &firstObject](TreeNodeRef& group, const BoundsTemplate& groupNodeBounds) {
if(!group.isTrunkNode()) {
throw "Attempting to remove more than one object from a single-object group!";
}
TreeTrunk& groupTrunk = group.asTrunk();
int groupTrunkSize = group.getTrunkSize();
groupTrunkSize = removeRecursive(this->allocator, groupTrunk, groupTrunkSize, firstObject.first, firstObject.second);
assert(groupTrunkSize != -1); // should have been verified
do {
std::pair> currentlyMoving = *iter;
groupTrunkSize = removeRecursive(this->allocator, groupTrunk, groupTrunkSize, currentlyMoving.first, currentlyMoving.second);
if(groupTrunkSize == -1) {
throw "Iterator provided an object which could not be found in this group!";
}
++iter;
} while(iter != iterEnd);
if(groupTrunkSize >= 2) {
group.setTrunkSize(groupTrunkSize);
return std::optional>(TrunkSIMDHelperFallback::getTotalBounds(groupTrunk, groupTrunkSize));
} else if(groupTrunkSize == 1) {
BoundsTemplate resultingBounds = groupTrunk.getBoundsOfSubNode(0);
group = std::move(groupTrunk.subNodes[0]);
if(group.isTrunkNode()) {
group.makeGroupHead(); // make sure to preserve the group head if the trunk is overwritten
}
this->allocator.freeTrunk(&groupTrunk);
return std::optional>(resultingBounds);
} else {
this->allocator.freeTrunk(&groupTrunk);
return std::optional>();
}
});
this->baseTrunkSize = newMainSize;
}
}
// the given iterator should return objects of type std::pair>
template
void transferSplitGroupTo(GroupIter iter, const GroupIterEnd& iterEnd, BoundsTreePrototype& destinationTree) {
if(!(iter != iterEnd)) return; // no items
this->removeSubGroup(iter, iterEnd);
std::pair> firstObject = *iter;
++iter;
if(iter != iterEnd) {
TreeTrunk* newGroupTrunk = destinationTree.allocator.allocTrunk();
newGroupTrunk->setSubNode(0, TreeNodeRef(const_cast(firstObject.first)), firstObject.second);
int newGroupTrunkSize = 1;
do {
std::pair> obj = *iter;
newGroupTrunkSize = addRecursive(destinationTree.allocator, *newGroupTrunk, newGroupTrunkSize, TreeNodeRef(const_cast(obj.first)), obj.second);
++iter;
} while(iter != iterEnd);
destinationTree.baseTrunkSize = addRecursive(destinationTree.allocator, destinationTree.baseTrunk, destinationTree.baseTrunkSize, TreeNodeRef(newGroupTrunk, newGroupTrunkSize, true), TrunkSIMDHelperFallback::getTotalBounds(*newGroupTrunk, newGroupTrunkSize));
} else {
destinationTree.add(const_cast(firstObject.first), firstObject.second);
}
}
// the given iterator should return objects of type std::pair>
template
void splitGroup(GroupIter iter, const GroupIterEnd& iterEnd) {
this->transferSplitGroupTo(iter, iterEnd, *this);
}
void improveStructure();
void maxImproveStructure();
BoundsTreeIteratorPrototype begin() const { return BoundsTreeIteratorPrototype(baseTrunk, baseTrunkSize); }
IteratorEnd end() const { return IteratorEnd(); }
template
IteratorFactoryWithEnd> iterFiltered(const Filter& filter) const {
return IteratorFactoryWithEnd>{FilteredTreeIteratorPrototype(baseTrunk, baseTrunkSize, filter)};
}
// unsafe functions
inline std::pair getBaseTrunk() { return std::pair(this->baseTrunk, this->baseTrunkSize); }
inline std::pair getBaseTrunk() const { return std::pair(this->baseTrunk, this->baseTrunkSize); }
inline TrunkAllocator& getAllocator() { return allocator; }
inline const TrunkAllocator& getAllocator() const { return allocator; }
void addGroupTrunk(TreeTrunk* newNode, int newNodeSize);
};
template
void recalculateBoundsRecursive(TreeTrunk& curTrunk, int curTrunkSize) {
for(int i = 0; i < curTrunkSize; i++) {
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
recalculateBoundsRecursive(subTrunk, subTrunkSize);
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize));
} else {
Boundable* object = static_cast(subNode.asObject());
curTrunk.setBoundsOfSubNode(i, object->getBounds());
}
}
}
template
bool updateGroupBoundsRecursive(TreeTrunk& curTrunk, int curTrunkSize, const Boundable* groupRep, const BoundsTemplate& originalGroupRepBounds) {
assert(curTrunkSize >= 0 && curTrunkSize <= BRANCH_FACTOR);
std::array couldContain = TrunkSIMDHelperFallback::getAllContainsBounds(curTrunk, originalGroupRepBounds);
for(int i = 0; i < curTrunkSize; i++) {
if(!couldContain[i]) continue;
TreeNodeRef& subNode = curTrunk.subNodes[i];
if(subNode.isTrunkNode()) {
TreeTrunk& subTrunk = subNode.asTrunk();
int subTrunkSize = subNode.getTrunkSize();
if(subNode.isGroupHead()) {
if(containsObjectRecursive(subTrunk, subTrunkSize, groupRep, originalGroupRepBounds)) {
recalculateBoundsRecursive(subTrunk, subTrunkSize);
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize));
return true;
}
} else {
if(updateGroupBoundsRecursive(subTrunk, subTrunkSize, groupRep, originalGroupRepBounds)) {
curTrunk.setBoundsOfSubNode(i, TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize));
return true;
}
}
} else {
if(subNode.asObject() == groupRep) {
curTrunk.setBoundsOfSubNode(i, groupRep->getBounds());
return true;
}
}
}
return false;
}
template
class BoundableCastIterator {
UnderlyingIter iter;
public:
BoundableCastIterator() = default;
BoundableCastIterator(const UnderlyingIter& iter) : iter(iter) {}
BoundableCastIterator(const TreeTrunk& baseTrunk, int baseTrunkSize) : iter(baseTrunk, baseTrunkSize) {}
BoundableCastIterator& operator++() { ++iter; return *this; }
Boundable& operator*() const { return *static_cast(*iter); }
bool operator!=(IteratorEnd) const { return iter != IteratorEnd{}; }
};
template
class BoundsTree {
BoundsTreePrototype tree;
public:
inline const BoundsTreePrototype& getPrototype() const { return tree; }
inline BoundsTreePrototype& getPrototype() { return tree; }
template
struct BoundableIteratorAdapter {
BoundableIter iter;
std::pair