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> operator*() const { const Boundable* item = *iter; return std::pair>(static_cast(item), item->getBounds()); } BoundableIteratorAdapter& operator++() { ++iter; return *this; } template bool operator!=(const BoundableIterEnd& end) const { return this->iter != end; } }; void add(Boundable* newObject) { tree.add(static_cast(newObject), newObject->getBounds()); } void remove(const Boundable* objectToRemove) { tree.remove(static_cast(objectToRemove), objectToRemove->getBounds()); } void addToGroup(Boundable* newObject, const Boundable* groupRepresentative) { tree.addToGroup(static_cast(newObject), newObject->getBounds(), static_cast(groupRepresentative), groupRepresentative->getBounds()); } template void addAllToGroup(BoundableIter iter, const BoundableIterEnd& iterEnd, const Boundable* groupRep) { if(!(iter != iterEnd)) return; modifyGroupRecursive(tree.allocator, tree.baseTrunk, tree.baseTrunkSize, groupRep, groupRep->getBounds(), [this, &iter, &iterEnd](TreeNodeRef& groupNode, BoundsTemplate groupBounds) { if(groupNode.isLeafNode()) { TreeTrunk* newTrunk = this->tree.allocator.allocTrunk(); newTrunk->setSubNode(0, std::move(groupNode), groupBounds); Boundable* newObj = *iter; BoundsTemplate newObjBounds = newObj->getBounds(); newTrunk->setSubNode(1, TreeNodeRef(newObj), newObjBounds); groupNode = TreeNodeRef(newTrunk, 2, true); ++iter; } TreeTrunk& trunk = groupNode.asTrunk(); int curTrunkSize = groupNode.getTrunkSize(); while(iter != iterEnd) { Boundable* curObj = *iter; BoundsTemplate curObjBounds = curObj->getBounds(); curTrunkSize = addRecursive(this->tree.allocator, trunk, curTrunkSize, TreeNodeRef(curObj), curObjBounds); ++iter; } return TrunkSIMDHelperFallback::getTotalBounds(trunk, curTrunkSize); }); } void mergeGroups(const Boundable* groupRepA, const Boundable* groupRepB) { tree.mergeGroups(static_cast(groupRepA), groupRepA->getBounds(), static_cast(groupRepB), groupRepB->getBounds()); } bool contains(const Boundable* object) const { return tree.contains(static_cast(object), object->getBounds()); } bool groupContains(const Boundable* groupRep, const Boundable* object) const { assert(this->contains(groupRep)); return tree.groupContains(static_cast(groupRep), groupRep->getBounds(), static_cast(object), object->getBounds()); } void updateObjectBounds(const Boundable* object, const BoundsTemplate& originalBounds) { tree.updateObjectBounds(static_cast(object), originalBounds, object->getBounds()); } void updateObjectGroupBounds(const Boundable* groupRep, const BoundsTemplate& originalGroupRepBounds) { bool success = updateGroupBoundsRecursive(tree.baseTrunk, tree.baseTrunkSize, groupRep, originalGroupRepBounds); if(!success) throw "groupRep was not found in tree!"; } // oldObject and newObject should have the same bounds void findAndReplaceObject(const Boundable* oldObject, Boundable* newObject, const BoundsTemplate& bounds) { assert(this->tree.contains(oldObject, bounds)); tree.findAndReplaceObject(static_cast(oldObject), static_cast(newObject), bounds); } void disbandGroup(const Boundable* groupRep) { assert(this->contains(groupRep)); tree.disbandGroup(static_cast(groupRep), groupRep->getBounds()); } size_t size() const { return tree.size(); } size_t groupSize(const Boundable* groupRep) const { return tree.groupSize(static_cast(groupRep), groupRep->getBounds()); } bool isEmpty() const { return tree.isEmpty(); } void clear() { tree.clear(); } void transferGroupTo(const Boundable* groupRep, BoundsTree& destinationTree) { tree.transferGroupTo(static_cast(groupRep), groupRep->getBounds(), destinationTree.tree); } // the given iterator should return objects of type Boundable* template void transferSplitGroupTo(GroupIter&& iter, const GroupIterEnd& iterEnd, BoundsTree& destinationTree) { tree.transferSplitGroupTo(BoundableIteratorAdapter{std::move(iter)}, iterEnd, destinationTree.tree); } void transferTo(Boundable* obj, BoundsTree& destinationTree) { BoundsTemplate bounds = obj->getBounds(); tree.remove(static_cast(obj), bounds); destinationTree.tree.add(static_cast(obj), bounds); } void moveOutOfGroup(Boundable* obj) { BoundsTemplate bounds = obj->getBounds(); tree.remove(static_cast(obj), bounds); tree.add(static_cast(obj), bounds); } // the given iterator should return objects of type Boundable* template void splitGroup(GroupIter iter, const GroupIterEnd& iterEnd) { tree.splitGroup(BoundableIteratorAdapter{std::move(iter)}, iterEnd); } // expects a function of the form void(Boundable& object) template void forEach(const Func& func) const { forEachRecurse(this->tree.baseTrunk, this->tree.baseTrunkSize, func); } // expects a function of the form void(Boundable& object) template void forEachFiltered(const Filter& filter, const Func& func) const { forEachFilteredRecurse(this->tree.baseTrunk, this->tree.baseTrunkSize, filter, func); } // expects a function of the form void(Boundable& object) template void forEachInGroup(const void* groupRepresentative, const BoundsTemplate& groupRepBounds, const Func& func) const { const TreeNodeRef* group = getGroupRecursive(this->tree.baseTrunk, this->tree.baseTrunkSize, groupRepresentative, groupRepBounds); if(group == nullptr) { throw "Group not found!"; } if(group->isTrunkNode()) { forEachRecurse(group->asTrunk(), group->getTrunkSize(), func); } else { func(*static_cast(group->asObject())); } } BoundableCastIterator begin() const { return BoundableCastIterator(this->tree.begin()); } IteratorEnd end() const { return IteratorEnd(); } template IteratorFactoryWithEnd>> iterFiltered(const Filter& filter) const { return IteratorFactoryWithEnd>>{ BoundableCastIterator>( FilteredTreeIteratorPrototype(this->tree.baseTrunk, this->tree.baseTrunkSize, filter) ) }; } template void forEachColission(const Func& func) const { if(this->tree.baseTrunkSize == 0) return; forEachColissionInternalRecursive(this->tree.baseTrunk, this->tree.baseTrunkSize, func); } template void forEachColissionWith(const BoundsTree& other, const Func& func) const { if(this->tree.baseTrunkSize == 0 || other.tree.baseTrunkSize == 0) return; forEachColissionBetweenRecursive(this->tree.baseTrunk, this->tree.baseTrunkSize, other.tree.baseTrunk, other.tree.baseTrunkSize, func); } void recalculateBounds() { recalculateBoundsRecursive(this->tree.baseTrunk, this->tree.baseTrunkSize); } void improveStructure() { tree.improveStructure(); } void maxImproveStructure() { tree.maxImproveStructure(); } }; struct BasicBounded { BoundsTemplate bounds; BoundsTemplate getBounds() const { return bounds; } }; }; ================================================ FILE: Physics3D/boundstree/boundsTreeAVX.cpp ================================================ #include "boundsTree.h" #include "../datastructures/alignedPtr.h" #include namespace P3D { OverlapMatrix TrunkSIMDHelperFallback::computeBoundsOverlapMatrixAVX(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize) { OverlapMatrix result; __m256 rx0 = _mm256_set1_ps(0); __m256 ry0 = _mm256_set1_ps(0); __m256 rz0 = _mm256_set1_ps(0); __m256 rx1 = _mm256_set1_ps(0); __m256 ry1 = _mm256_set1_ps(0); __m256 rz1 = _mm256_set1_ps(0); alignas(32) float tempBuff[8]; __m256 bxmin = _mm256_load_ps(trunkB.subNodeBounds.xMin); __m256 bymin = _mm256_load_ps(trunkB.subNodeBounds.yMin); __m256 bzmin = _mm256_load_ps(trunkB.subNodeBounds.zMin); __m256 bxmax = _mm256_load_ps(trunkB.subNodeBounds.xMax); __m256 bymax = _mm256_load_ps(trunkB.subNodeBounds.yMax); __m256 bzmax = _mm256_load_ps(trunkB.subNodeBounds.zMax); for(int a = 0; a < trunkASize; a++) { BoundsTemplate aBounds0 = trunkA.getBoundsOfSubNode(a); __m256 axmin = _mm256_set1_ps(aBounds0.min.x); __m256 aymin = _mm256_set1_ps(aBounds0.min.y); __m256 azmin = _mm256_set1_ps(aBounds0.min.z); __m256 axmax = _mm256_set1_ps(aBounds0.max.x); __m256 aymax = _mm256_set1_ps(aBounds0.max.y); __m256 azmax = _mm256_set1_ps(aBounds0.max.z); rx0 = _mm256_cmp_ps(axmax, bxmin, _CMP_GE_OQ); ry0 = _mm256_cmp_ps(aymax, bymin, _CMP_GE_OQ); rz0 = _mm256_cmp_ps(azmax, bzmin, _CMP_GE_OQ); rx1 = _mm256_cmp_ps(axmin, bxmax, _CMP_LE_OQ); ry1 = _mm256_cmp_ps(aymin, bymax, _CMP_LE_OQ); rz1 = _mm256_cmp_ps(azmin, bzmax, _CMP_LE_OQ); __m256 resultBool = _mm256_and_ps( _mm256_and_ps(_mm256_and_ps(rx0, ry0), rz0), _mm256_and_ps( _mm256_and_ps(rx1, ry1), rz1) ); _mm256_store_ps(tempBuff, resultBool); result[a][0] = tempBuff[0]; result[a][1] = tempBuff[1]; result[a][2] = tempBuff[2]; result[a][3] = tempBuff[3]; result[a][4] = tempBuff[4]; result[a][5] = tempBuff[5]; result[a][6] = tempBuff[6]; result[a][7] = tempBuff[7]; } return result; } } ================================================ FILE: Physics3D/boundstree/boundsTreeSSE.cpp ================================================ #include "boundsTree.h" #include "../datastructures/alignedPtr.h" #include namespace P3D { OverlapMatrix TrunkSIMDHelperFallback::computeBoundsOverlapMatrixSSE(const TreeTrunk& trunkA, int trunkASize, const TreeTrunk& trunkB, int trunkBSize) { OverlapMatrix result; __m128 rx0 = _mm_set1_ps(0); __m128 ry0 = _mm_set1_ps(0); __m128 rz0 = _mm_set1_ps(0); __m128 rx1 = _mm_set1_ps(0); __m128 ry1 = _mm_set1_ps(0); __m128 rz1 = _mm_set1_ps(0); alignas(32) float tempBuff[4]; for(int a = 0; a < trunkASize; a++) { BoundsTemplate aBounds0 = trunkA.getBoundsOfSubNode(a); __m128 axmin = _mm_set1_ps(aBounds0.min.x); __m128 aymin = _mm_set1_ps(aBounds0.min.y); __m128 azmin = _mm_set1_ps(aBounds0.min.z); __m128 axmax = _mm_set1_ps(aBounds0.max.x); __m128 aymax = _mm_set1_ps(aBounds0.max.y); __m128 azmax = _mm_set1_ps(aBounds0.max.z); for(unsigned short int i=0; i<2; i++){ __m128 bxmin = _mm_load_ps(trunkB.subNodeBounds.xMin + i * 4); __m128 bymin = _mm_load_ps(trunkB.subNodeBounds.yMin + i * 4); __m128 bzmin = _mm_load_ps(trunkB.subNodeBounds.zMin + i * 4); __m128 bxmax = _mm_load_ps(trunkB.subNodeBounds.xMax + i * 4); __m128 bymax = _mm_load_ps(trunkB.subNodeBounds.yMax + i * 4); __m128 bzmax = _mm_load_ps(trunkB.subNodeBounds.zMax + i * 4); rx0 = _mm_cmp_ps(axmax, bxmin, _CMP_GE_OQ); ry0 = _mm_cmp_ps(aymax, bymin, _CMP_GE_OQ); rz0 = _mm_cmp_ps(azmax, bzmin, _CMP_GE_OQ); rx1 = _mm_cmp_ps(axmin, bxmax, _CMP_LE_OQ); ry1 = _mm_cmp_ps(aymin, bymax, _CMP_LE_OQ); rz1 = _mm_cmp_ps(azmin, bzmax, _CMP_LE_OQ); __m128 resultBool = _mm_and_ps( _mm_and_ps(_mm_and_ps(rx0, ry0), rz0), _mm_and_ps( _mm_and_ps(rx1, ry1), rz1) ); _mm_store_ps(tempBuff, resultBool); result[a][0 + i * 4] = tempBuff[0]; result[a][1 + i * 4] = tempBuff[1]; result[a][2 + i * 4] = tempBuff[2]; result[a][3 + i * 4] = tempBuff[3]; } } return result; } } ================================================ FILE: Physics3D/boundstree/filters/outOfBoundsFilter.h ================================================ #pragma once #include "../../math/bounds.h" #include "../boundsTree.h" #include "../../part.h" namespace P3D { struct OutOfBoundsFilter { Bounds bounds; OutOfBoundsFilter() = default; OutOfBoundsFilter(const Bounds& bounds) : bounds(bounds) {} std::array operator()(const TreeTrunk& trunk, int trunkSize) const { std::array results; for(int i = 0; i < trunkSize; i++) { results[i] = !this->bounds.contains(trunk.getBoundsOfSubNode(i)); } return results; } bool operator()(const Part& part) const { return true; } }; }; ================================================ FILE: Physics3D/boundstree/filters/rayIntersectsBoundsFilter.h ================================================ #pragma once #include "../../math/bounds.h" #include "../../math/ray.h" #include "../../part.h" #include "../boundsTree.h" namespace P3D { struct RayIntersectBoundsFilter { Ray ray; RayIntersectBoundsFilter() = default; RayIntersectBoundsFilter(const Ray& ray) : ray(ray) {} std::array operator()(const TreeTrunk& trunk, int trunkSize) const { std::array results; for(int i = 0; i < trunkSize; i++) { results[i] = doRayAndBoundsIntersect(trunk.getBoundsOfSubNode(i), this->ray); } return results; } bool operator()(const Part& part) const { return true; } }; }; ================================================ FILE: Physics3D/boundstree/filters/visibilityFilter.cpp ================================================ #include "visibilityFilter.h" #include "../../math/linalg/trigonometry.h" #include "../../math/position.h" #include "../../math/bounds.h" #include "../../part.h" namespace P3D { VisibilityFilter::VisibilityFilter(const Position& origin, Vec3 normals[5], double maxDepth) : origin(origin), up(normals[0]), down(normals[1]), left(normals[2]), right(normals[3]), forward(normals[4]), maxDepth(maxDepth) {} VisibilityFilter VisibilityFilter::fromSteps(const Position& origin, const Vec3& stepForward, const Vec3& stepUp, const Vec3& stepRight, double maxDepth) { Vec3 upNormal = (stepForward + stepUp) % stepRight; Vec3 downNormal = -(stepForward - stepUp) % stepRight; Vec3 leftNormal = -(stepForward + stepRight) % stepUp; Vec3 rightNormal = (stepForward - stepRight) % stepUp; Vec3 normals[5]{upNormal, downNormal, leftNormal, rightNormal, stepForward}; return VisibilityFilter(origin, normals, maxDepth); } VisibilityFilter VisibilityFilter::fromSteps(const Position& origin, const Vec3& stepForward, const Vec3& stepUp, const Vec3& stepRight, double maxDepth, double left, double right, double down, double up) { Vec3 upNormal = (stepForward + stepUp * up) % stepRight; Vec3 downNormal = -(stepForward + stepUp * down) % stepRight; Vec3 leftNormal = -(stepForward - stepRight * left) % stepUp; Vec3 rightNormal = (stepForward - stepRight * right) % stepUp; Vec3 normals[5]{upNormal, downNormal, leftNormal, rightNormal, stepForward}; return VisibilityFilter(origin, normals, maxDepth); } VisibilityFilter VisibilityFilter::forWindow(const Position& origin, const Vec3& cameraForward, const Vec3& cameraUp, double fov, double aspect, double maxDepth) { double tanFov = tan(fov / 2); Vec3 stepForward = normalize(cameraForward); Vec3 stepUp = normalize(cameraUp) * tanFov; Vec3 stepRight = normalize(cameraUp % cameraForward) * tanFov * aspect; return fromSteps( origin, stepForward, stepUp, stepRight, maxDepth ); } VisibilityFilter VisibilityFilter::forSubWindow(const Position& origin, const Vec3& cameraForward, const Vec3& cameraUp, double fov, double aspect, double maxDepth, double left, double right, double down, double up) { double tanFov = tan(fov / 2); Vec3 stepForward = normalize(cameraForward); Vec3 stepUp = normalize(cameraUp) * tanFov; Vec3 stepRight = normalize(cameraUp % cameraForward) * tanFov * aspect; return fromSteps( origin, stepForward, stepUp, stepRight, maxDepth, left, right, down, up ); } bool VisibilityFilter::operator()(const Position& point) const { double offsets[5]{0,0,0,0,maxDepth}; Vec3 normals[5]{up, down, left, right, forward}; for(int i = 0; i < 5; i++) { Vec3& normal = normals[i]; Vec3 relativePos = point - origin; if(relativePos * normal > offsets[i]) return false; } return true; } bool VisibilityFilter::operator()(const Bounds& bounds) const { double offsets[5]{0,0,0,0,maxDepth}; Vec3 normals[5]{up, down, left, right, forward}; for(int i = 0; i < 5; i++) { Vec3& normal = normals[i]; // we're checking that *a* corner of the TreeNode's bounds is within the viewport, basically similar to rectangle-rectangle colissions, google it! // cornerOfInterest is the corner that is the furthest positive corner relative to the normal, so if it is not visible (eg above the normal) then the whole box must be invisible Position cornerOfInterest( (normal.x >= 0) ? bounds.min.x : bounds.max.x, (normal.y >= 0) ? bounds.min.y : bounds.max.y, // let's look at the top of the viewport, if the bottom of the box is above this then the whole box must be above it. (normal.z >= 0) ? bounds.min.z : bounds.max.z ); Vec3 relativePos = cornerOfInterest - origin; if(relativePos * normal > offsets[i]) return false; } return true; } bool VisibilityFilter::operator()(const Part& part) const { return true; } }; /* A / / o---o <-- cornerOfInterest for B / | | / | | o o---o <-- cornerOfInterest for A \ \ \ \ B A B \ COI B / \ v / \ o---o <- COI A \ |/ | \ | | \ /o---o \ / o */ ================================================ FILE: Physics3D/boundstree/filters/visibilityFilter.h ================================================ #pragma once #include "../../math/linalg/vec.h" #include "../../math/bounds.h" #include "../../part.h" namespace P3D { class VisibilityFilter { public: Position origin; private: // normals of the viewPort, facing outward Vec3 up; Vec3 down; Vec3 left; Vec3 right; Vec3 forward; double maxDepth; public: VisibilityFilter() : up(), down(), left(), right(), forward(), maxDepth() {}; VisibilityFilter(const Position& origin, Vec3 normals[5], double maxDepth); /* Creates a VisibilityFilter from the values derived from usual camera parameters. stepForward: the direction of the camera stepUp: should be perpendicular to stepForward & stepRight, furthest visible point, starting from stepForward, in the up direction. stepRight: should be perpendicular to stepForward & stepUp, furthest visible point, starting from stepForward, in the right direction. */ static VisibilityFilter fromSteps(const Position& origin, const Vec3& stepForward, const Vec3& stepUp, const Vec3& stepRight, double maxDepth); /* Creates a VisibilityFilter from the values derived from common camera parameters. cameraForward: the direction of the camera cameraUp: should be perpendicular to cameraForward & cameraRight, furthest visible point, starting from cameraForward, in the up direction. cameraRight: should be perpendicular to cameraForward & cameraUp, furthest visible point, starting from cameraForward, in the right direction. Also accepts subWindow parameters: left-right goes from -1..1 down-up goes from -1..1 */ static VisibilityFilter fromSteps(const Position& origin, const Vec3& stepForward, const Vec3& stepUp, const Vec3& stepRight, double maxDepth, double left, double right, double down, double up); /* Creates a VisibilityFilter from the values derived from usual camera parameters. cameraForward: the direction of the camera cameraUp: should be perpendicular to cameraForward, up direction relative to the camera fov: Field Of View of the camera, expressed in radians aspect: aspect ratio of the camera, == width / height */ static VisibilityFilter forWindow(const Position& origin, const Vec3& cameraForward, const Vec3& cameraUp, double fov, double aspect, double maxDepth); /* Creates a VisibilityFilter for a subregion of the screen. Useful for selection and stuff Create it by giving the same arguments as forWindow, but also pass which portion of the screen to select. left-right goes from -1..1 down-up goes from -1..1 */ static VisibilityFilter forSubWindow(const Position& origin, const Vec3& cameraForward, const Vec3& cameraUp, double fov, double aspect, double maxDepth, double left, double right, double down, double up); bool operator()(const Position& point) const; bool operator()(const Part& part) const; bool operator()(const Bounds& bounds) const; Vec3 getForwardStep() const { return forward; } Vec3 getTopOfViewPort() const { return projectToPlaneNormal(forward, up); } Vec3 getBottomOfViewPort() const { return projectToPlaneNormal(forward, down); } Vec3 getLeftOfViewPort() const { return projectToPlaneNormal(forward, left); } Vec3 getRightOfViewPort() const { return projectToPlaneNormal(forward, right); } }; }; ================================================ FILE: Physics3D/colissionBuffer.h ================================================ #pragma once #include namespace P3D { struct Colission { Part* p1; Part* p2; Position intersection; Vec3 exitVector; }; struct ColissionBuffer { std::vector freePartColissions; std::vector freeTerrainColissions; inline void addFreePartColission(Part* a, Part* b, Position intersection, Vec3 exitVector) { freePartColissions.push_back(Colission{a, b, intersection, exitVector}); } inline void addTerrainColission(Part* freePart, Part* terrainPart, Position intersection, Vec3 exitVector) { freeTerrainColissions.push_back(Colission{freePart, terrainPart, intersection, exitVector}); } inline void clear() { freePartColissions.clear(); freeTerrainColissions.clear(); } }; }; ================================================ FILE: Physics3D/constraints/ballConstraint.cpp ================================================ #include "ballConstraint.h" #include "constraintImpl.h" namespace P3D { int BallConstraint::maxNumberOfParameters() const { return 3; } static ConstraintMatrixPair<3> makeMatrices(const PhysicalInfo& phys, const Vec3& attach) { Vec3 attachRelativeToCOM = phys.cframe.localToRelative(attach) - phys.relativeCenterOfMass; Mat3 crossEquivAttach = createCrossProductEquivalent(attachRelativeToCOM); Matrix parameterToMotion = joinVertical(Mat3::DIAGONAL(phys.forceResponse), phys.momentResponse * crossEquivAttach); Matrix motionToEquation = joinHorizontal(Mat3::IDENTITY(), -crossEquivAttach); return ConstraintMatrixPair<3>{parameterToMotion, motionToEquation}; } ConstraintMatrixPack BallConstraint::getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const { ConstraintMatrixPair<3> cA = makeMatrices(physA, attachA); ConstraintMatrixPair<3> cB = makeMatrices(physB, attachB); Vec3 error0 = physB.cframe.localToGlobal(attachB) - physA.cframe.localToGlobal(attachA); Vec3 velocityA = cA.motionToEquation * physA.motion.getDerivAsVec6(0); Vec3 velocityB = cB.motionToEquation * physB.motion.getDerivAsVec6(0); Vec3 error1 = velocityB - velocityA; Matrix error = Matrix::fromColumns({error0, error1}); /*inMemoryMatrixVectorMultiply(motionToEquationMatrixA, getMotionVecForDeriv(physA.mainPhysical->motionOfCenterOfMass, 1), mA); inMemoryMatrixVectorMultiply(motionToEquationMatrixB, getMotionVecForDeriv(physB.mainPhysical->motionOfCenterOfMass, 1), mB); mB -= mA; errorValue.setCol(2, mB);*/ return ConstraintMatrixPack(matrixBuf, errorBuf, cA, cB, error); } }; ================================================ FILE: Physics3D/constraints/ballConstraint.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "constraint.h" namespace P3D { struct BallConstraint : public Constraint { Vec3 attachA; Vec3 attachB; BallConstraint() = default; inline BallConstraint(Vec3 attachA, Vec3 attachB) : attachA(attachA), attachB(attachB) {} virtual int maxNumberOfParameters() const override; virtual ConstraintMatrixPack getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const override; }; }; ================================================ FILE: Physics3D/constraints/barConstraint.cpp ================================================ #include "barConstraint.h" #include "constraintImpl.h" namespace P3D { int BarConstraint::maxNumberOfParameters() const { return 1; } static ConstraintMatrixPair<1> makeMatrices(const PhysicalInfo& phys, const Vec3& attach, const Vec3& barAxis) { Vec3 attachRelativeToCOM = phys.cframe.localToRelative(attach) - phys.relativeCenterOfMass; Mat3 crossEquivAttach = createCrossProductEquivalent(attachRelativeToCOM); Matrix parameterToMotion = Matrix::fromColumns({join(phys.forceResponse * barAxis, phys.momentResponse * crossEquivAttach * barAxis)}); Matrix motionToEquation = Matrix::fromRows({join(barAxis, (attachRelativeToCOM % barAxis))}); return ConstraintMatrixPair<1>{parameterToMotion, motionToEquation}; } ConstraintMatrixPack BarConstraint::getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const { Vec3 barAxis = physB.cframe.localToGlobal(attachB) - physA.cframe.localToGlobal(attachA); ConstraintMatrixPair<1> cA = makeMatrices(physA, attachA, barAxis); ConstraintMatrixPair<1> cB = makeMatrices(physB, attachB, barAxis); double error0 = length(barAxis) - this->barLength; double velocityA = cA.motionToEquation * physA.motion.getDerivAsVec6(0); double velocityB = cB.motionToEquation * physB.motion.getDerivAsVec6(0); double error1 = velocityB - velocityA; Matrix error = Matrix::fromColumns({error0, error1}); /*inMemoryMatrixVectorMultiply(motionToEquationMatrixA, getMotionVecForDeriv(physA.mainPhysical->motionOfCenterOfMass, 1), mA); inMemoryMatrixVectorMultiply(motionToEquationMatrixB, getMotionVecForDeriv(physB.mainPhysical->motionOfCenterOfMass, 1), mB); mB -= mA; errorValue.setCol(2, mB);*/ return ConstraintMatrixPack(matrixBuf, errorBuf, cA, cB, error); } }; ================================================ FILE: Physics3D/constraints/barConstraint.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "constraint.h" namespace P3D { struct BarConstraint : public Constraint { Vec3 attachA; Vec3 attachB; double barLength; BarConstraint() = default; inline BarConstraint(Vec3 attachA, Vec3 attachB, double barLength) : attachA(attachA), attachB(attachB), barLength(barLength) {} virtual int maxNumberOfParameters() const override; virtual ConstraintMatrixPack getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const override; }; }; ================================================ FILE: Physics3D/constraints/constraint.cpp ================================================ #include "constraint.h" namespace P3D { Constraint::~Constraint() {} } ================================================ FILE: Physics3D/constraints/constraint.h ================================================ #pragma once namespace P3D { class ConstraintMatrixPack; struct PhysicalInfo; struct Constraint { virtual int maxNumberOfParameters() const = 0; virtual ConstraintMatrixPack getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const = 0; virtual ~Constraint(); }; } ================================================ FILE: Physics3D/constraints/constraintGroup.cpp ================================================ #include "constraintGroup.h" #include "constraintImpl.h" #include "../math/linalg/largeMatrix.h" #include "../math/linalg/largeMatrixAlgorithms.h" #include "../math/linalg/mat.h" #include "../physical.h" #include "../math/mathUtil.h" #include "../misc/validityHelper.h" #include #include #include namespace P3D { int PhysicalConstraint::maxNumberOfParameters() const { return constraint->maxNumberOfParameters(); } static PhysicalInfo getInfo(const Physical& phys) { GlobalCFrame cf = phys.getCFrame(); Vec3 relativeCOM = phys.mainPhysical->totalCenterOfMass - (cf.getPosition() - phys.mainPhysical->getCFrame().getPosition()); return PhysicalInfo{cf, phys.getMotion(), 1 / phys.mainPhysical->totalMass, phys.mainPhysical->momentResponse, relativeCOM}; } ConstraintMatrixPack PhysicalConstraint::getMatrices(double* matrixBuf, double* errorBuf) const { PhysicalInfo infoA = getInfo(*physA); PhysicalInfo infoB = getInfo(*physB); return constraint->getMatrices(infoA, infoB, matrixBuf, errorBuf); } void ConstraintGroup::add(Physical* first, Physical* second, Constraint* constraint) { this->constraints.push_back(PhysicalConstraint(first, second, constraint)); } void ConstraintGroup::add(Part* first, Part* second, Constraint* constraint) { this->constraints.push_back(PhysicalConstraint(first->ensureHasPhysical(), second->ensureHasPhysical(), constraint)); } void ConstraintGroup::apply() const { std::size_t maxNumberOfParameters = 0; ConstraintMatrixPack* constraintMatrices = new ConstraintMatrixPack[constraints.size()]; for(std::size_t i = 0; i < constraints.size(); i++) { maxNumberOfParameters += constraints[i].constraint->maxNumberOfParameters(); } double* matrixBuffer = new double[std::size_t(24) * maxNumberOfParameters]; double* errorBuffer = new double[std::size_t(NUMBER_OF_ERROR_DERIVATIVES) * maxNumberOfParameters]; std::size_t numberOfParams = 0; for(std::size_t i = 0; i < constraints.size(); i++) { constraintMatrices[i] = constraints[i].getMatrices(matrixBuffer + std::size_t(24) * numberOfParams, errorBuffer + std::size_t(NUMBER_OF_ERROR_DERIVATIVES) * numberOfParams); numberOfParams += constraintMatrices[i].getSize(); } UnmanagedHorizontalFixedMatrix vectorToSolve(errorBuffer, maxNumberOfParameters); LargeMatrix systemToSolve(numberOfParams, numberOfParams); { std::size_t curColIndex = 0; for(std::size_t blockCol = 0; blockCol < constraints.size(); blockCol++) { int colSize = constraintMatrices[blockCol].getSize(); MotorizedPhysical* mPhysA = constraints[blockCol].physA->mainPhysical; MotorizedPhysical* mPhysB = constraints[blockCol].physB->mainPhysical; const UnmanagedHorizontalFixedMatrix motionToEq1 = constraintMatrices[blockCol].getMotionToEquationMatrixA(); const UnmanagedHorizontalFixedMatrix motionToEq2 = constraintMatrices[blockCol].getMotionToEquationMatrixB(); std::size_t curRowIndex = 0; for(std::size_t blockRow = 0; blockRow < constraints.size(); blockRow++) { int rowSize = constraintMatrices[blockRow].getSize(); MotorizedPhysical* cPhysA = constraints[blockRow].physA->mainPhysical; MotorizedPhysical* cPhysB = constraints[blockRow].physB->mainPhysical; const UnmanagedVerticalFixedMatrix paramToMotion1 = constraintMatrices[blockRow].getParameterToMotionMatrixA(); const UnmanagedVerticalFixedMatrix paramToMotion2 = constraintMatrices[blockRow].getParameterToMotionMatrixB(); double resultBuf1[6 * 6]; UnmanagedLargeMatrix resultMat1(resultBuf1, rowSize, colSize); for(double& d : resultMat1) d = 0.0; double resultBuf2[6 * 6]; UnmanagedLargeMatrix resultMat2(resultBuf2, rowSize, colSize); for(double& d : resultMat2) d = 0.0; if(mPhysA == cPhysA) { inMemoryMatrixMultiply(motionToEq1, paramToMotion1, resultMat1); } else if(mPhysA == cPhysB) { inMemoryMatrixMultiply(motionToEq1, paramToMotion2, resultMat1); inMemoryMatrixNegate(resultMat1); } if(mPhysB == cPhysA) { inMemoryMatrixMultiply(motionToEq2, paramToMotion1, resultMat2); inMemoryMatrixNegate(resultMat2); } else if(mPhysB == cPhysB) { inMemoryMatrixMultiply(motionToEq2, paramToMotion2, resultMat2); } resultMat1 += resultMat2; systemToSolve.setSubMatrix(curColIndex, curRowIndex, resultMat1); curRowIndex += rowSize; } curColIndex += colSize; } } assert(isMatValid(vectorToSolve)); destructiveSolve(systemToSolve, vectorToSolve); assert(isMatValid(vectorToSolve)); { std::size_t curParameterIndex = 0; for(std::size_t i = 0; i < constraints.size(); i++) { const UnmanagedVerticalFixedMatrix curP2MA = constraintMatrices[i].getParameterToMotionMatrixA(); const UnmanagedVerticalFixedMatrix curP2MB = constraintMatrices[i].getParameterToMotionMatrixB(); std::size_t curSize = curP2MA.cols; UnmanagedHorizontalFixedMatrix parameterVec = vectorToSolve.subRows(curParameterIndex, curSize); Matrix effectOnA = curP2MA * parameterVec; Matrix effectOnB = -(curP2MB * parameterVec); // TODO add moving correction Vector offsetAngularEffectOnA = effectOnA.getCol(0); Vector offsetAngularEffectOnB = effectOnB.getCol(0); assert(isVecValid(offsetAngularEffectOnA)); assert(isVecValid(offsetAngularEffectOnB)); GlobalCFrame& mainPACF = constraints[i].physA->mainPhysical->rigidBody.mainPart->cframe; GlobalCFrame& mainPBCF = constraints[i].physB->mainPhysical->rigidBody.mainPart->cframe; mainPACF.position += offsetAngularEffectOnA.getSubVector<3>(0); mainPACF.rotation = Rotation::fromRotationVector(offsetAngularEffectOnA.getSubVector<3>(3)) * mainPACF.rotation; mainPBCF.position += offsetAngularEffectOnB.getSubVector<3>(0); mainPBCF.rotation = Rotation::fromRotationVector(offsetAngularEffectOnB.getSubVector<3>(3)) * mainPBCF.rotation; Vector velAngularEffectOnA = effectOnA.getCol(1); Vector velAngularEffectOnB = effectOnB.getCol(1); assert(isVecValid(velAngularEffectOnA)); assert(isVecValid(velAngularEffectOnB)); constraints[i].physA->mainPhysical->motionOfCenterOfMass.translation.translation[0] += velAngularEffectOnA.getSubVector<3>(0); constraints[i].physA->mainPhysical->motionOfCenterOfMass.rotation.rotation[0] += velAngularEffectOnA.getSubVector<3>(3); constraints[i].physB->mainPhysical->motionOfCenterOfMass.translation.translation[0] += velAngularEffectOnB.getSubVector<3>(0); constraints[i].physB->mainPhysical->motionOfCenterOfMass.rotation.rotation[0] += velAngularEffectOnB.getSubVector<3>(3); /*Vector accelAngularEffectOnA = effectOnA.getCol(2); Vector accelAngularEffectOnB = effectOnB.getCol(2); constraints[i].physA->mainPhysical->totalForce += constraints[i].physA->mainPhysical->totalMass * velAngularEffectOnA.getSubVector<3>(0); constraints[i].physA->mainPhysical->totalMoment += ~constraints[i].physA->mainPhysical->momentResponse * velAngularEffectOnA.getSubVector<3>(3); constraints[i].physB->mainPhysical->totalForce += constraints[i].physB->mainPhysical->totalMass * velAngularEffectOnB.getSubVector<3>(0); constraints[i].physB->mainPhysical->totalMoment += ~constraints[i].physB->mainPhysical->momentResponse * velAngularEffectOnB.getSubVector<3>(3);*/ curParameterIndex += curSize; } assert(curParameterIndex == numberOfParams); } } }; ================================================ FILE: Physics3D/constraints/constraintGroup.h ================================================ #pragma once #include #include "constraint.h" namespace P3D { class Physical; class Part; class PhysicalConstraint { public: Physical* physA; Physical* physB; Constraint* constraint; inline PhysicalConstraint(Physical* physA, Physical* physB, Constraint* constraint) : physA(physA), physB(physB), constraint(constraint) {} int maxNumberOfParameters() const; ConstraintMatrixPack getMatrices(double* matrixBuf, double* errorBuf) const; }; class ConstraintGroup { public: std::vector constraints; //std::vector physicals; void add(Physical* first, Physical* second, Constraint* constraint); void add(Part* first, Part* second, Constraint* constraint); void apply() const; }; } ================================================ FILE: Physics3D/constraints/constraintImpl.h ================================================ #pragma once #include "constraint.h" #include "../math/linalg/mat.h" #include "../math/linalg/largeMatrix.h" #include "../math/globalCFrame.h" #include "../motion.h" namespace P3D { #define NUMBER_OF_ERROR_DERIVATIVES 2 template struct ConstraintMatrixPair { Matrix paramToMotion; Matrix motionToEquation; ConstraintMatrixPair operator-() const { return ConstraintMatrixPair{-paramToMotion, -motionToEquation}; } }; struct PhysicalInfo { GlobalCFrame cframe; Motion motion; double forceResponse; // 1/mass Mat3 momentResponse; Vec3 relativeCenterOfMass; }; class ConstraintMatrixPack { double* matrixData; double* errorData; int size; // size * 6 for matrix size, size * 6 * 4 for total size public: ConstraintMatrixPack() = default; template ConstraintMatrixPack(double* matrixBuf, double* errorBuf, const Matrix& paramToMotionA, const Matrix& paramToMotionB, const Matrix& motionToEqA, const Matrix& motionToEqB, const Matrix& errorMat) : matrixData(matrixBuf), errorData(errorBuf), size(Size) { paramToMotionA.toColMajorData(matrixData); paramToMotionB.toColMajorData(matrixData + 6 * Size); motionToEqA.toRowMajorData(matrixData + 12 * Size); motionToEqB.toRowMajorData(matrixData + 18 * Size); errorMat.toRowMajorData(errorData); } template ConstraintMatrixPack(double* matrixBuf, double* errorBuf, const ConstraintMatrixPair& mA, const ConstraintMatrixPair& mB, const Matrix& errorMat) : matrixData(matrixBuf), errorData(errorBuf), size(Size) { mA.paramToMotion.toColMajorData(matrixData); mB.paramToMotion.toColMajorData(matrixData + 6 * Size); mA.motionToEquation.toRowMajorData(matrixData + 12 * Size); mB.motionToEquation.toRowMajorData(matrixData + 18 * Size); errorMat.toRowMajorData(errorData); } int getSize() const { return size; } UnmanagedVerticalFixedMatrix getParameterToMotionMatrixA() const { return UnmanagedVerticalFixedMatrix(matrixData, size); } UnmanagedVerticalFixedMatrix getParameterToMotionMatrixB() const { return UnmanagedVerticalFixedMatrix(matrixData + size_t(6) * size, size); } UnmanagedHorizontalFixedMatrix getMotionToEquationMatrixA() const { return UnmanagedHorizontalFixedMatrix(matrixData + size_t(12) * size, size); } UnmanagedHorizontalFixedMatrix getMotionToEquationMatrixB() const { return UnmanagedHorizontalFixedMatrix(matrixData + size_t(18) * size, size); } UnmanagedHorizontalFixedMatrix getErrorMatrix() const { return UnmanagedHorizontalFixedMatrix(errorData, size); } }; } ================================================ FILE: Physics3D/constraints/hingeConstraint.cpp ================================================ #include "hingeConstraint.h" #include "constraintImpl.h" namespace P3D { int HingeConstraint::maxNumberOfParameters() const { return 5; } static ConstraintMatrixPair<5> makeMatrices(const PhysicalInfo& phys, const Vec3& attach, const Vec3& p1, const Vec3& p2) { Vec3 attachRelativeToCOM = phys.cframe.localToRelative(attach) - phys.relativeCenterOfMass; Mat3 crossEquivAttach = createCrossProductEquivalent(attachRelativeToCOM); Matrix impulseToMotion = joinHorizontal(Mat3::DIAGONAL(phys.forceResponse), Matrix::ZEROS()); Matrix angularEffect = joinHorizontal(crossEquivAttach, Matrix::fromColumns({p1, p2})); Matrix parameterToMotion = joinVertical(impulseToMotion, phys.momentResponse * angularEffect); Matrix motionToEquation = join(Mat3::IDENTITY(), -crossEquivAttach, Matrix::ZEROS(), Matrix::fromRows({p1, p2})); return ConstraintMatrixPair<5>{parameterToMotion, motionToEquation}; } ConstraintMatrixPack HingeConstraint::getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const { Vec3 localMainAxis = normalize(this->axisA); Vec3 localP1 = normalize(getPerpendicular(localMainAxis)); Vec3 localP2 = normalize(localMainAxis % localP1); Vec3 localMainOffsetAxis = normalize(this->axisB); Vec3 mainAxis = physA.cframe.localToRelative(localMainAxis); Vec3 p1 = physA.cframe.localToRelative(localP1); Vec3 p2 = physA.cframe.localToRelative(localP2); Vec3 mainOffsetAxis = physB.cframe.localToRelative(localMainOffsetAxis); // rotationOffset will be in the plane defined by p1 and p2, as it is perpendicular to mainAxis Vec3 rotationOffset = mainAxis % mainOffsetAxis; double rotationOffsetP1 = p1 * rotationOffset; double rotationOffsetP2 = p2 * rotationOffset; ConstraintMatrixPair<5> cA = makeMatrices(physA, attachA, p1, p2); ConstraintMatrixPair<5> cB = makeMatrices(physB, attachB, p1, p2); Vec5 error0 = join(Vec3(physB.cframe.localToGlobal(attachB) - physA.cframe.localToGlobal(attachA)), Vec2(rotationOffsetP1, rotationOffsetP2)); Vec5 velocityA = cA.motionToEquation * physA.motion.getDerivAsVec6(0); Vec5 velocityB = cB.motionToEquation * physB.motion.getDerivAsVec6(0); Vec5 error1 = velocityB - velocityA; Matrix error = Matrix::fromColumns({error0, error1}); return ConstraintMatrixPack(matrixBuf, errorBuf, cA, cB, error); } }; ================================================ FILE: Physics3D/constraints/hingeConstraint.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "constraint.h" namespace P3D { struct HingeConstraint : public Constraint { Vec3 attachA; Vec3 axisA; Vec3 attachB; Vec3 axisB; HingeConstraint() = default; inline HingeConstraint(Vec3 attachA, Vec3 axisA, Vec3 attachB, Vec3 axisB) : attachA(attachA), axisA(axisA), attachB(attachB), axisB(axisB) {} virtual int maxNumberOfParameters() const override; virtual ConstraintMatrixPack getMatrices(const PhysicalInfo& physA, const PhysicalInfo& physB, double* matrixBuf, double* errorBuf) const override; }; }; ================================================ FILE: Physics3D/datastructures/alignedPtr.h ================================================ #pragma once #include #include #include "aligned_alloc.h" namespace P3D { template class UniqueAlignedPointer { T* data; public: UniqueAlignedPointer() : data(nullptr) {} UniqueAlignedPointer(std::size_t size, std::size_t align = alignof(T)) : data(static_cast(aligned_malloc(sizeof(T)* size, align))) {} ~UniqueAlignedPointer() { aligned_free(static_cast(data)); } inline T* get() const { return data; } operator T* () const { return data; } UniqueAlignedPointer(const UniqueAlignedPointer& other) = delete; UniqueAlignedPointer& operator=(const UniqueAlignedPointer& other) = delete; UniqueAlignedPointer(UniqueAlignedPointer&& other) noexcept : data(other.data) { other.data = nullptr; } UniqueAlignedPointer& operator=(UniqueAlignedPointer&& other) noexcept { std::swap(this->data, other.data); return *this; } }; template class SharedAlignedPointer { T* data; std::size_t* refCount; public: SharedAlignedPointer() : data(nullptr), refCount(nullptr) {} SharedAlignedPointer(std::size_t size, std::size_t align = alignof(T)) : data(static_cast(aligned_malloc(sizeof(T)* size, align))), refCount(new std::size_t(1)) {} ~SharedAlignedPointer() { if(refCount != nullptr && --(*refCount) == 0) { aligned_free(static_cast(data)); delete refCount; } } inline T* get() const { return data; } operator T* () const { return data; } SharedAlignedPointer(const SharedAlignedPointer& other) : data(other.data), refCount(other.refCount) { (*refCount)++; } SharedAlignedPointer& operator=(const SharedAlignedPointer& other) { this->~SharedAlignedPointer(); this->data = other.data; this->refCount = other.refCount; (*refCount)++; return *this; } SharedAlignedPointer(SharedAlignedPointer&& other) noexcept : data(other.data), refCount(other.refCount) { other.data = nullptr; other.refCount = nullptr; } SharedAlignedPointer& operator=(SharedAlignedPointer&& other) noexcept { std::swap(this->data, other.data); std::swap(this->refCount, other.refCount); return *this; } }; }; ================================================ FILE: Physics3D/datastructures/aligned_alloc.cpp ================================================ #include "aligned_alloc.h" #include #include namespace P3D { void* aligned_malloc(size_t size, size_t align) { #ifdef _MSC_VER return _aligned_malloc(size, align); #else return aligned_alloc(align, size); #endif } void aligned_free(void* ptr) { #ifdef _MSC_VER _aligned_free(ptr); #else free(ptr); #endif } }; ================================================ FILE: Physics3D/datastructures/aligned_alloc.h ================================================ #pragma once #include namespace P3D { void* aligned_malloc(std::size_t size, std::size_t align); void aligned_free(void* ptr); }; ================================================ FILE: Physics3D/datastructures/buffers.h ================================================ #pragma once #include #include "iteratorEnd.h" namespace P3D { inline unsigned long long nextPowerOf2(unsigned long long v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v |= v >> 32; return v + 1; } template struct ListIter { T* start; T* fin; T* begin() const { return start; } T* end() const { return fin; } }; /*template struct ConstListIter { const T* start; const T* fin; const T* begin() const { return start; } const T* end() const { return fin; } };*/ template struct ListOfPtrIter { T* const* cur; ListOfPtrIter() = default; ListOfPtrIter(T* const* cur) : cur(cur) {} T& operator*() const { return **cur; } ListOfPtrIter& operator++() { cur++; return *this; } ListOfPtrIter operator++(int) { ListOfPtrIter prevSelf = *this; cur++; return prevSelf; } bool operator==(const ListOfPtrIter& other) const { return this->cur == other.cur; } bool operator!=(const ListOfPtrIter& other) const { return this->cur != other.cur; } T* operator->() const { return *cur; } operator T* () const { return *cur; } }; template struct ListOfPtrIterFactory { T* const* start; T* const* fin; ListOfPtrIterFactory() {} ListOfPtrIterFactory(T* const* start, T* const* fin) : start(start), fin(fin) {} ListOfPtrIterFactory(const ListIter& iter) : start(iter.start), fin(iter.fin) {} ListOfPtrIterFactory(const ListIter& iter) : start(iter.start), fin(iter.fin) {} ListOfPtrIter begin() const { return ListOfPtrIter{start}; } ListOfPtrIter end() const { return ListOfPtrIter{fin}; } }; template struct BufferWithCapacity { T* data; size_t capacity; BufferWithCapacity() : data(nullptr), capacity(0) {}; BufferWithCapacity(size_t initialCapacity) : data(new T[initialCapacity]), capacity(initialCapacity) {} BufferWithCapacity(BufferWithCapacity&& other) noexcept { this->data = other.data; this->capacity = other.capacity; other.data = nullptr; other.capacity = 0; } ~BufferWithCapacity() { delete[] data; } BufferWithCapacity(const BufferWithCapacity& other) = delete; void operator=(const BufferWithCapacity& other) = delete; BufferWithCapacity& operator=(BufferWithCapacity&& other) noexcept { std::swap(this->data, other.data); std::swap(this->capacity, other.capacity); return *this; } void resize(size_t newCapacity, size_t sizeToCopy) { T* newBuf = new T[newCapacity]; for(size_t i = 0; i < sizeToCopy; i++) newBuf[i] = std::move(data[i]); delete[] data; data = newBuf; capacity = newCapacity; } size_t ensureCapacity(size_t newCapacity, size_t sizeToCopy) { if(newCapacity > this->capacity) { size_t nextPower = nextPowerOf2(newCapacity); resize(nextPower, sizeToCopy); return nextPower; } return this->capacity; } T& operator[](size_t index) { return data[index]; } const T& operator[](size_t index) const { return data[index]; } }; template struct AddableBuffer : public BufferWithCapacity { size_t size = 0; AddableBuffer() : BufferWithCapacity() {} AddableBuffer(size_t initialCapacity) : BufferWithCapacity(initialCapacity) {} AddableBuffer(T* data, size_t dataSize, size_t initialCapacity) : BufferWithCapacity(initialCapacity), size(dataSize) { assert(data != nullptr); for(size_t i = 0; i < dataSize; i++) this->data[i] = data[i]; } ~AddableBuffer() {} AddableBuffer(const AddableBuffer&) = delete; AddableBuffer& operator=(const AddableBuffer&) = delete; AddableBuffer(AddableBuffer&& other) noexcept : BufferWithCapacity(std::move(other)), size(other.size) { other.size = 0; } AddableBuffer& operator=(AddableBuffer&& other) noexcept { BufferWithCapacity::operator=(std::move(other)); size_t tmpSize = this->size; this->size = other.size; other.size = tmpSize; return *this; } inline void resize(size_t newCapacity) { BufferWithCapacity::resize(newCapacity, this->size); } inline size_t ensureCapacity(size_t newCapacity) { size_t newSize = BufferWithCapacity::ensureCapacity(newCapacity, this->size); return newSize; } inline T* add(const T& obj) { this->ensureCapacity(this->size + 1); this->data[this->size] = obj; return &this->data[this->size++]; } inline T* add(T&& obj) { this->ensureCapacity(this->size + 1); this->data[this->size] = std::move(obj); return &this->data[this->size++]; } inline void clear() { size = 0; } inline T* begin() { return this->data; } inline const T* begin() const { return this->data; } inline T* end() { return this->data + size; } inline const T* end() const { return this->data + this->size; } inline bool liesInList(const T* obj) const { return obj >= this->data && obj < this->data + this->size; } }; template struct FixedLocalBuffer { T buf[N]{}; int size = 0; T& operator[](int index) { return buf[index]; } const T& operator[](int index) const { return buf[index]; } const T* begin() const { return &buf; } T* begin() { return &buf; } const T* end() const { return buf + size; } T* end() { return buf + size; } void add(const T& obj) { buf[size++] = obj; } }; template struct CircularIter { T* iter; T* bufStart; T* bufEnd; size_t itemsLeft; void operator++() { iter++; if(iter == bufEnd) { iter = bufStart; } itemsLeft--; } T& operator*() const { return *iter; } bool operator==(IteratorEnd) const { return this->itemsLeft == 0; } bool operator!=(IteratorEnd) const { return this->itemsLeft != 0; } }; template struct CircularBuffer { private: T* buf; size_t curI = 0; size_t capacity; bool hasComeAround = false; public: CircularBuffer() : buf(nullptr), capacity(0) {} CircularBuffer(size_t capacity) : buf(new T[capacity + 1]), capacity(capacity) {} ~CircularBuffer() { delete[] buf; } inline size_t size() const { if(hasComeAround) return capacity; else return curI; } CircularBuffer(const CircularBuffer& other) : buf(new T[other.capacity + 1]), curI(other.curI), capacity(other.capacity), hasComeAround(other.hasComeAround) { for(size_t i = 0; i < other.capacity; i++) { this->buf[i] = other.buf[i]; } } CircularBuffer& operator=(const CircularBuffer& other) { if(this->capacity != other.capacity) { delete[] this->buf; this->buf = new T[other.capacity]; } this->curI = other.curI; this->capacity = other.capacity; this->hasComeAround = other.hasComeAround; for(size_t i = 0; i < other.capacity; i++) { this->buf[i] = other.buf[i]; } return *this; } CircularBuffer(CircularBuffer&& other) : buf(other.buf), curI(other.curI), capacity(other.capacity), hasComeAround(other.hasComeAround) { other.buf = nullptr; other.capacity = 0; other.curI = 0; other.hasComeAround = false; } CircularBuffer& operator=(CircularBuffer&& other) { std::swap(this->buf, other.buf); std::swap(this->curI, other.curI); std::swap(this->capacity, other.capacity); std::swap(this->hasComeAround, other.hasComeAround); return *this; } inline void add(const T& newObj) { buf[curI] = newObj; curI++; if(curI >= capacity) { curI = 0; hasComeAround = true; } } inline T sum() const { size_t limit = size(); if(limit == 0) return T(); T total = buf[0]; for(size_t i = 1; i < limit; i++) total += buf[i]; return total; } inline T avg() const { size_t limit = size(); if(limit == 0) return T(); T total = sum(); return T(total / limit); } inline void resize(size_t newCapacity) { T* newBuf = new T[newCapacity]; T* cur = newBuf; size_t elementsForgotten = (this->capacity > newCapacity) ? this->capacity - newCapacity : 0; if(hasComeAround) { for(size_t i = curI + elementsForgotten; i < capacity; i++) { *cur = buf[i]; cur++; } if(elementsForgotten > capacity - curI) { elementsForgotten -= (capacity - curI); } } for(size_t i = elementsForgotten; i < curI; i++) { *cur = buf[i]; cur++; } delete[] buf; this->buf = newBuf; } T& front() { return (curI == 0) ? buf[capacity - 1] : buf[curI - 1]; } const T& front() const { return (curI == 0) ? buf[capacity - 1] : buf[curI - 1]; } T& tail() { return hasComeAround ? buf[curI] : buf[0]; } const T& tail() const { return hasComeAround ? buf[curI] : buf[0]; } inline CircularIter begin() { return CircularIter{hasComeAround ? buf + curI : buf, buf, buf + capacity, size()}; } inline CircularIter begin() const { return CircularIter{hasComeAround ? buf + curI : buf, buf, buf + capacity, size()}; } inline IteratorEnd end() { return IteratorEnd(); } }; }; ================================================ FILE: Physics3D/datastructures/compactPtrDataPair.h ================================================ #pragma once #include #include /* Stores a little extra data in the lower bits of the given ptr type. This is usually only 3 bits, but on stronger aligned types this may be more Pointers are aligned on 4 or 8 bytes usually, this means we can use these few bits for storing some extra small data For example, the length of an array pointed to by the pointer if that array will always be very small. 0bPPP...PPPPP000 | 0b000...00000DDD = 0bPPP...PPPPPDDD */ namespace P3D { template class CompactPtrDataPair { protected: std::uintptr_t value; CompactPtrDataPair(std::uintptr_t value) : value(value) {} public: static constexpr std::uintptr_t dataMask = (std::uintptr_t(1) << BitWidth) - std::uintptr_t(1); static constexpr std::uintptr_t ptrMask = ~dataMask; CompactPtrDataPair() noexcept : value(reinterpret_cast(nullptr)) {} CompactPtrDataPair(PtrT* ptr, DataType data) noexcept : value(reinterpret_cast(ptr) | data) { assert(reinterpret_cast(ptr) & dataMask == std::uintptr_t(0)); assert(data & ptrMask == std::uintptr_t(0)); } PtrT* getPtr() const noexcept { return reinterpret_cast(value & ptrMask); } bool isNullptr() const noexcept { return value & ptrMask == reinterpret_cast(nullptr) & ptrMask; } DataType getData() const noexcept { return value & dataMask; } void setPtr(PtrT* ptr) noexcept { assert(reinterpret_cast(ptr) & dataMask == std::uintptr_t(0)); value = reinterpret_cast(ptr) | (value & dataMask); } void setData(DataType data) noexcept { assert(data & ptrMask == std::uintptr_t(0)); value = data | (value & ptrMask) } CompactPtrDataPair& operator++() { assert(getData() < dataMask); ++value; return *this; } CompactPtrDataPair& operator--() { assert(getData() != 0); --value; return *this; } CompactPtrDataPair operator++(int) { CompactPtrDataPair result = *this; ++(*this); return result; } CompactPtrDataPair operator--(int) { CompactPtrDataPair result = *this; --(*this); return result; } }; template class CompactPtrDataPair { protected: std::uintptr_t value; CompactPtrDataPair(std::uintptr_t value) : value(value) {} public: static constexpr std::uintptr_t dataMask = std::uintptr_t(1); static constexpr std::uintptr_t ptrMask = ~dataMask; CompactPtrDataPair() noexcept : value(reinterpret_cast(nullptr)) {} CompactPtrDataPair(PtrT* ptr, bool data) noexcept : value(reinterpret_cast(ptr) | (data ? std::uintptr_t(1) : std::uintptr_t(0))) { assert(reinterpret_cast(ptr) & dataMask == std::uintptr_t(0)); } PtrT* getPtr() const noexcept { return reinterpret_cast(value & ptrMask); } bool isNullptr() const noexcept { return value & ptrMask == reinterpret_cast(nullptr) & ptrMask; } bool getData() const noexcept { return value & dataMask != std::uintptr_t(0); } void setPtr(PtrT* ptr) noexcept { assert(reinterpret_cast(ptr) & dataMask == std::uintptr_t(0)); value = reinterpret_cast(ptr) | (value & dataMask); } void setData(bool data) noexcept { if(data) { value |= std::uintptr_t(1); } else { value &= ptrMask; } } void setDataTrue() noexcept { value |= std::uintptr_t(1); } void setDataFalse() noexcept { value &= ptrMask; } void invertData() noexcept { value ^= std::uintptr_t(1); } }; template bool operator==(CompactPtrDataPair first, CompactPtrDataPair second) { return first.value == second.value; } template bool operator!=(CompactPtrDataPair first, CompactPtrDataPair second) { return first.value != second.value; } template class CompactUniquePtrDataPair : public CompactPtrDataPair { public: CompactUniquePtrDataPair() noexcept : CompactPtrDataPair() {} CompactUniquePtrDataPair(PtrT* ptr, DataType data) noexcept : CompactPtrDataPair(ptr, data) {} CompactUniquePtrDataPair(const CompactUniquePtrDataPair&) = delete; CompactUniquePtrDataPair& operator=(const CompactUniquePtrDataPair&) = delete; CompactUniquePtrDataPair(CompactUniquePtrDataPair&& other) noexcept : CompactPtrDataPair(other.value) { other.value = reinterpret_cast(nullptr); } CompactUniquePtrDataPair& operator=(CompactUniquePtrDataPair&& other) noexcept { std::uintptr_t tmpValue = this->value; this->value = other.value; other.value = tmpValue; } ~CompactPtrDataPair() { if(!this->isNullptr()) delete this->getPtr(); } }; template using CompactPtrBoolPair = CompactPtrDataPair; template using CompactUniquePtrBoolPair = CompactUniquePtrDataPair; } ================================================ FILE: Physics3D/datastructures/iteratorEnd.h ================================================ #pragma once namespace P3D { struct IteratorEnd {}; }; ================================================ FILE: Physics3D/datastructures/iteratorFactory.h ================================================ #pragma once #include #include "iteratorEnd.h" namespace P3D { template class IteratorFactory { BeginType start; EndType fin; public: IteratorFactory() = default; IteratorFactory(const BeginType& start, const EndType& fin) : start(start), fin(fin) {} IteratorFactory(BeginType&& start, EndType&& fin) : start(std::move(start)), fin(std::move(fin)) {} BeginType begin() const { return start; } EndType end() const { return fin; } }; template class IteratorFactoryWithEnd { BeginType iter; public: IteratorFactoryWithEnd() = default; IteratorFactoryWithEnd(const BeginType& iter) : iter(iter) {} IteratorFactoryWithEnd(BeginType&& iter) : iter(std::move(iter)) {} BeginType begin() const { return iter; } IteratorEnd end() const { return IteratorEnd(); } }; }; ================================================ FILE: Physics3D/datastructures/monotonicTree.h ================================================ #pragma once #include #include #include #include "unmanagedArray.h" namespace P3D { template struct MonotonicTreeNode { MonotonicTreeNode* children; T value; }; template class MonotonicTree; /* Warning: This is an unmanaged object, does not automatically free memory on delete */ template class MonotonicTreeBuilder { friend class MonotonicTree; UnmanagedArray> allocatedMemory; MonotonicTreeNode* currentAlloc; public: MonotonicTreeBuilder() : allocatedMemory(), currentAlloc(nullptr) {} MonotonicTreeBuilder(UnmanagedArray>&& memory) : allocatedMemory(std::move(memory)), currentAlloc(allocatedMemory.get()) {} MonotonicTreeNode* alloc(std::size_t size) { MonotonicTreeNode* claimedBlock = currentAlloc; currentAlloc += size; assert(currentAlloc <= allocatedMemory.getEnd()); return claimedBlock; } T* getPtrToFree() { return allocatedMemory.getPtrToFree(); } }; /* Warning: This is an unmanaged object, does not automatically free memory on delete */ template class MonotonicTree { UnmanagedArray> allocatedMemory; public: MonotonicTree() : allocatedMemory() {} MonotonicTree(MonotonicTreeBuilder&& builder) : allocatedMemory(std::move(builder.allocatedMemory)) { assert(builder.currentAlloc == this->allocatedMemory.getEnd()); } MonotonicTree& operator=(MonotonicTreeBuilder&& builder) { assert(builder.currentAlloc == builder.allocatedMemory.getEnd()); this->allocatedMemory = std::move(builder.allocatedMemory); } MonotonicTreeNode& operator[](std::size_t index) { return allocatedMemory[index]; } const MonotonicTreeNode& operator[](std::size_t index) const { return allocatedMemory[index]; } MonotonicTreeNode* begin() { return allocatedMemory.begin(); } const MonotonicTreeNode* begin() const { return allocatedMemory.begin(); } MonotonicTreeNode* end() { return allocatedMemory.end(); } const MonotonicTreeNode* end() const { return allocatedMemory.end(); } MonotonicTreeNode* getPtrToFree() { return allocatedMemory.getPtrToFree(); } }; }; ================================================ FILE: Physics3D/datastructures/parallelArray.h ================================================ #pragma once #include namespace P3D { template struct ParallelArray { T values[C]; ParallelArray() = default; ParallelArray(T* values) : values{} { for(std::size_t i = 0; i < C; i++) { this->values[i] = values[i]; } } ParallelArray operator+(const ParallelArray& other) const { T newValues[C]; for(std::size_t i = 0; i < C; i++) { newValues[i] = values[i] + other.values[i]; } return ParallelArray{newValues}; } ParallelArray operator-(const ParallelArray& other) const { T newValues[C]; for(std::size_t i = 0; i < C; i++) { newValues[i] = values[i] - other.values[i]; } return ParallelArray{newValues}; } template ParallelArray operator*(T2& other) const { T newValues[C]; for(std::size_t i = 0; i < C; i++) { newValues[i] = values[i] / other; } return ParallelArray{newValues}; } template ParallelArray operator/(T2& other) const { T newValues[C]; for(std::size_t i = 0; i < C; i++) { newValues[i] = values[i] / other; } return ParallelArray{newValues}; } void operator+=(const ParallelArray& other) { for(std::size_t i = 0; i < C; i++) { values[i] += other.values[i]; } } void operator-=(const ParallelArray& other) { for(std::size_t i = 0; i < C; i++) { values[i] -= other.values[i]; } } template void operator*=(T2& other) { for(std::size_t i = 0; i < C; i++) { values[i] *= other; } } template void operator/=(T2& other) { for(std::size_t i = 0; i < C; i++) { values[i] /= other; } } T& operator[](std::size_t index) { return values[index]; } T sum() const { T total = values[0]; for(std::size_t i = 1; i < C; i++) { total += values[i]; } return total; } }; }; ================================================ FILE: Physics3D/datastructures/sharedArray.h ================================================ #pragma once namespace P3D { template class SharedArrayPtr { T* ptr; size_t* refCount; inline SharedArrayPtr(T* data, size_t* refCount) : ptr(data) , refCount(refCount) {} public: SharedArrayPtr() : ptr(nullptr) , refCount(nullptr) {} explicit SharedArrayPtr(T* data) : ptr(data) , refCount((data == nullptr) ? nullptr : new size_t(0)) {} SharedArrayPtr(const SharedArrayPtr& other) : ptr(other.ptr) , refCount(other.refCount) { if (refCount != nullptr) ++(*refCount); } SharedArrayPtr& operator=(const SharedArrayPtr& other) { this->~SharedArrayPtr(); this->ptr = other.ptr; this->refCount = other.refCount; if (refCount != nullptr) ++(*refCount); return *this; } SharedArrayPtr(SharedArrayPtr&& other) noexcept : ptr(other.ptr) , refCount(other.refCount) { other.ptr = nullptr; other.refCount = nullptr; } SharedArrayPtr& operator=(SharedArrayPtr&& other) noexcept { this->~SharedArrayPtr(); this->ptr = other.ptr; this->refCount = other.refCount; other.ptr = nullptr; other.refCount = nullptr; return *this; } static SharedArrayPtr staticSharedArrayPtr(T* data) { return SharedArrayPtr(data, nullptr); } ~SharedArrayPtr() { if (refCount != nullptr) { if (*refCount == 0) { delete refCount; delete[] ptr; } else { --(*refCount); } } } T& operator*() const { return *ptr; } T& operator[](size_t index) const { return ptr[index]; } T* operator+(size_t offset) const { return ptr + offset; } T* operator-(size_t offset) const { return ptr - offset; } T* operator->() const { return ptr; } T* get() const { return ptr; } bool operator==(T* other) const { return ptr == other; } bool operator!=(T* other) const { return ptr != other; } bool operator<=(T* other) const { return ptr <= other; } bool operator>=(T* other) const { return ptr >= other; } bool operator<(T* other) const { return ptr < other; } bool operator>(T* other) const { return ptr > other; } }; }; ================================================ FILE: Physics3D/datastructures/smartPointers.h ================================================ #pragma once #include #include namespace P3D { struct RC { std::size_t refCount = 0; virtual ~RC() {} }; struct AtomicRC { std::atomic refCount = 0; virtual ~AtomicRC() {} }; template void intrusive_ptr_add_ref(T* iptr) noexcept { iptr->refCount++; } template void intrusive_ptr_release(T* iptr) noexcept { // retrieve resulting count right from the --count operator, so that std::atomic is properly used if iptr->refCount is atomic. auto resultingCount = --iptr->refCount; if (resultingCount == 0) delete iptr; } /* Defines a managed pointer to a shared object, of which unlike shared_ptr, the refCount is stored in the object itself. Requires the managed object to define a public (optionally atomic) integer-like field called 'refCount'. */ template class intrusive_ptr { protected: template friend class intrusive_ptr; T* ptr; public: intrusive_ptr() noexcept : ptr(nullptr) {} explicit intrusive_ptr(T* ptr, bool addRef = true) noexcept : ptr(ptr) { if (ptr && addRef) intrusive_ptr_add_ref(ptr); } intrusive_ptr(const intrusive_ptr& iptr) noexcept : ptr(iptr.ptr) { if (ptr) intrusive_ptr_add_ref(ptr); } intrusive_ptr(intrusive_ptr&& iptr) noexcept : ptr(iptr.ptr) { iptr.ptr = nullptr; } ~intrusive_ptr() noexcept { if (ptr) intrusive_ptr_release(ptr); } intrusive_ptr& operator=(const intrusive_ptr& iptr) noexcept { return operator=(iptr.ptr); } intrusive_ptr& operator=(intrusive_ptr&& iptr) noexcept { swap(iptr); return *this; } template intrusive_ptr& operator=(const intrusive_ptr& iptr) noexcept { return operator=(iptr.ptr); } intrusive_ptr& operator=(T* ptr) noexcept { if (ptr != this->ptr) { const T* tmp = this->ptr; if (ptr) intrusive_ptr_add_ref(ptr); this->ptr = ptr; if (tmp) intrusive_ptr_release(tmp); } return *this; } [[nodiscard]] T& operator*() const noexcept { return *ptr; } [[nodiscard]] T* get() const noexcept { return ptr; } [[nodiscard]] T* operator->() const noexcept { return ptr; } [[nodiscard]] bool valid() const noexcept { return ptr != nullptr; } [[nodiscard]] bool invalid() const noexcept { return ptr == nullptr; } void reset() noexcept { T* const tmp = ptr; ptr = nullptr; if (tmp) intrusive_ptr_release(tmp); } void swap(intrusive_ptr& iptr) noexcept { T* const tmp = ptr; ptr = iptr.ptr; iptr.ptr = tmp; } void attach(T* ptr) noexcept { T* const tmp = this->ptr; this->ptr = ptr; if (tmp) intrusive_ptr_release(tmp); } [[nodiscard]] T* detach() noexcept { T* const tmp = ptr; ptr = nullptr; return tmp; } [[nodiscard]] operator bool() const noexcept { return ptr != nullptr; } [[nodiscard]] bool operator!() const noexcept { return ptr == nullptr; } }; template bool operator==(const intrusive_ptr& iptr1, const intrusive_ptr& iptr2) noexcept { return (iptr1.get() == iptr2.get()); } template bool operator!=(const intrusive_ptr& iptr1, const intrusive_ptr& iptr2) noexcept { return (iptr1.get() != iptr2.get()); } template bool operator==(const intrusive_ptr& iptr, T* ptr) noexcept { return (iptr.get() == ptr); } template bool operator!=(const intrusive_ptr& iptr, T* ptr) noexcept { return (iptr.get() != ptr); } template bool operator==(T* ptr, const intrusive_ptr& iptr) noexcept { return (ptr == iptr.get()); } template bool operator!=(T* ptr, const intrusive_ptr& iptr) noexcept { return (ptr != iptr.get()); } template bool operator==(std::nullptr_t nptr, const intrusive_ptr& iptr) noexcept { return (nullptr == iptr.get()); } template bool operator!=(std::nullptr_t nptr, const intrusive_ptr& iptr) noexcept { return (nullptr != iptr.get()); } template bool operator==(const intrusive_ptr& iptr, std::nullptr_t nptr) noexcept { return (iptr.get() == nullptr); } template bool operator!=(const intrusive_ptr& iptr, std::nullptr_t nptr) noexcept { return (iptr.get() != nullptr); } template intrusive_ptr make_intrusive(Args&&... args) noexcept { return intrusive_ptr(new T(std::forward(args)...)); } template intrusive_ptr intrusive_cast(const intrusive_ptr& iptr) noexcept { return intrusive_ptr(static_cast(iptr.get())); } template using IRef = intrusive_ptr; template using SRef = std::shared_ptr; template using URef = std::unique_ptr; } ================================================ FILE: Physics3D/datastructures/uniqueArrayPtr.h ================================================ #include #include #include namespace P3D { template struct DefaultArrayDelete { void operator()(T* arr) const { delete[] arr; } }; struct DoNothingDelete { void operator()(void* arr) const noexcept {} }; template> class UniqueArrayPtr { T* ptr; T* endPtr; Deleter deleter; public: // ignore endPtr, endPtr is only valid if ptr is not nullptr UniqueArrayPtr() : ptr(nullptr) {} UniqueArrayPtr(T* ptr, std::size_t size, Deleter deleter = {}) : ptr(ptr), endPtr(ptr + size), deleter(deleter) {} ~UniqueArrayPtr() { deleter(this->ptr); } UniqueArrayPtr(const UniqueArrayPtr&) = delete; UniqueArrayPtr& operator=(const UniqueArrayPtr&) = delete; UniqueArrayPtr(UniqueArrayPtr&& other) noexcept : ptr(other.ptr), endPtr(other.endPtr), deleter(std::move(other.deleter)) { other.ptr = nullptr; } UniqueArrayPtr& operator=(UniqueArrayPtr&& other) noexcept { std::swap(this->ptr, other.ptr); std::swap(this->endPtr, other.endPtr); std::swap(this->deleter, other.deleter); return *this; } T& operator[](std::size_t index) { assert(ptr != nullptr); assert(index >= 0 && ptr + index < endPtr); return ptr[index]; } const T& operator[](std::size_t index) const { assert(ptr != nullptr); assert(index >= 0 && ptr + index < endPtr); return ptr[index]; } T& operator*() { assert(ptr != nullptr); return *ptr; } const T& operator*() const { assert(ptr != nullptr); return *ptr; } T* begin() { assert(ptr != nullptr); return ptr; } const T* begin() const { assert(ptr != nullptr); return ptr; } T* end() { assert(ptr != nullptr); return endPtr; } const T* end() const { assert(ptr != nullptr); return endPtr; } T* get() { return ptr; } const T* get() const { return ptr; } T* getEnd() { assert(ptr != nullptr); return endPtr; } const T* getEnd() const { assert(ptr != nullptr); return endPtr; } }; }; ================================================ FILE: Physics3D/datastructures/unmanagedArray.h ================================================ #include #include #include /* Warning: This is an unmanaged object, does not automatically free memory on delete */ namespace P3D { template class UnmanagedArray { T* ptr; T* endPtr; public: // ignore endPtr, endPtr is only valid if ptr is not nullptr UnmanagedArray() : ptr(nullptr) {} UnmanagedArray(T* ptr, std::size_t size) : ptr(ptr), endPtr(ptr + size) {} T& operator[](std::size_t index) { assert(ptr != nullptr); assert(index >= 0 && ptr + index < endPtr); return ptr[index]; } const T& operator[](std::size_t index) const { assert(ptr != nullptr); assert(index >= 0 && ptr + index < endPtr); return ptr[index]; } T& operator*() { assert(ptr != nullptr); return *ptr; } const T& operator*() const { assert(ptr != nullptr); return *ptr; } T* begin() { assert(ptr != nullptr); return ptr; } const T* begin() const { assert(ptr != nullptr); return ptr; } T* end() { assert(ptr != nullptr); return endPtr; } const T* end() const { assert(ptr != nullptr); return endPtr; } T* get() { return ptr; } const T* get() const { return ptr; } T* getEnd() { assert(ptr != nullptr); return endPtr; } const T* getEnd() const { assert(ptr != nullptr); return endPtr; } T* getPtrToFree() { return ptr; } }; }; ================================================ FILE: Physics3D/datastructures/unorderedVector.h ================================================ #pragma once #include #include namespace P3D { template class UnorderedVector : public std::vector { public: inline void remove(T&& element) { T* el = &element; assert(el >= &std::vector::front()); T* bck = &std::vector::back(); assert(el <= bck); if(el != bck) { *el = std::move(*bck); } std::vector::pop_back(); } }; }; ================================================ FILE: Physics3D/externalforces/directionalGravity.cpp ================================================ #include "directionalGravity.h" #include "../math/position.h" #include "../part.h" #include "../physical.h" #include "../world.h" namespace P3D { void DirectionalGravity::apply(WorldPrototype* world) { for(MotorizedPhysical* p : world->physicals) { p->applyForceAtCenterOfMass(gravity * p->totalMass); } } double DirectionalGravity::getPotentialEnergyForObject(const WorldPrototype* world, const Part& part) const { return Vec3(Position() - part.getCenterOfMass()) * gravity * part.getMass(); } double DirectionalGravity::getPotentialEnergyForObject(const WorldPrototype* world, const MotorizedPhysical& phys) const { return Vec3(Position() - phys.getCenterOfMass()) * gravity * phys.totalMass; } }; ================================================ FILE: Physics3D/externalforces/directionalGravity.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "externalForce.h" namespace P3D { class WorldPrototype; class Part; class MotorizedPhysical; class DirectionalGravity : public ExternalForce { public: Vec3 gravity; DirectionalGravity(Vec3 gravity) : gravity(gravity) {} virtual void apply(WorldPrototype* world) override; virtual double getPotentialEnergyForObject(const WorldPrototype* world, const Part& part) const override; virtual double getPotentialEnergyForObject(const WorldPrototype* world, const MotorizedPhysical& phys) const override; }; }; ================================================ FILE: Physics3D/externalforces/externalForce.cpp ================================================ #include "externalForce.h" #include "../world.h" #include "../part.h" #include "../physical.h" namespace P3D { double ExternalForce::getPotentialEnergyForObject(const WorldPrototype* world, const Part& part) const { return 0.0; } double ExternalForce::getPotentialEnergyForObject(const WorldPrototype* world, const MotorizedPhysical& phys) const { double total = 0.0; for(const Part& p : phys.rigidBody) { total += this->getPotentialEnergyForObject(world, p); } return total; } double ExternalForce::getTotalPotentialEnergyForThisForce(const WorldPrototype* world) const { double total = 0.0; for(MotorizedPhysical* p : world->physicals) { total += this->getPotentialEnergyForObject(world, *p); } return total; } }; ================================================ FILE: Physics3D/externalforces/externalForce.h ================================================ #pragma once namespace P3D { class WorldPrototype; class Part; class MotorizedPhysical; class ExternalForce { public: virtual void apply(WorldPrototype* world) = 0; // These do not necessarity have to be implemented. Used for world potential energy computation virtual double getPotentialEnergyForObject(const WorldPrototype* world, const Part& part) const; // default implementation sums potential energies of constituent parts virtual double getPotentialEnergyForObject(const WorldPrototype* world, const MotorizedPhysical& phys) const; virtual double getTotalPotentialEnergyForThisForce(const WorldPrototype* world) const; }; }; ================================================ FILE: Physics3D/externalforces/magnetForce.cpp ================================================ #include "magnetForce.h" #include "../world.h" namespace P3D { MagnetForce::MagnetForce(double pickerStrength, double pickerSpeedStrength) : selectedPart(nullptr), localSelectedPoint(), magnetPoint(), pickerStrength(pickerStrength), pickerSpeedStrength(pickerSpeedStrength) {} MagnetForce::MagnetForce(Part& selectedPart, Vec3 localSelectedPoint, Position magnetPoint, double pickerStrength, double pickerSpeedStrength) : selectedPart(&selectedPart), localSelectedPoint(localSelectedPoint), magnetPoint(magnetPoint), pickerStrength(pickerStrength), pickerSpeedStrength(pickerSpeedStrength) {} void MagnetForce::apply(WorldPrototype* world) { if(selectedPart != nullptr) { Physical* selectedPartPhys = selectedPart->getPhysical(); if(selectedPartPhys != nullptr) { MotorizedPhysical* selectedPhysical = selectedPartPhys->mainPhysical; // Magnet force Position absoluteSelectedPoint = selectedPart->getCFrame().localToGlobal(localSelectedPoint); Position centerOfmass = selectedPhysical->getCenterOfMass(); Vec3 delta = magnetPoint - absoluteSelectedPoint; Vec3 relativeSelectedPointSpeed = selectedPart->getMotion().getVelocityOfPoint(absoluteSelectedPoint - centerOfmass); Vec3 force = selectedPhysical->totalMass * (delta * pickerStrength - relativeSelectedPointSpeed * pickerSpeedStrength); selectedPhysical->applyForceToPhysical(absoluteSelectedPoint - centerOfmass, force); selectedPhysical->motionOfCenterOfMass.rotation.rotation[0] *= 0.8; } } } }; ================================================ FILE: Physics3D/externalforces/magnetForce.h ================================================ #pragma once #include "externalForce.h" #include "../part.h" #include "../math/linalg/vec.h" #include "../math/position.h" namespace P3D { class MagnetForce : public ExternalForce { public: Part* selectedPart; Vec3 localSelectedPoint; Position magnetPoint; double pickerStrength; double pickerSpeedStrength; MagnetForce() = default; MagnetForce(double pickerStrength, double pickerSpeedStrength); MagnetForce(Part& selectedPart, Vec3 localSelectedPoint, Position magnetPoint, double pickerStrength, double pickerSpeedStrength); virtual void apply(WorldPrototype* world) override; }; }; ================================================ FILE: Physics3D/geometry/builtinShapeClasses.cpp ================================================ #include "builtinShapeClasses.h" #include "shapeCreation.h" #include "shapeLibrary.h" #include "../math/constants.h" namespace P3D { #pragma region CubeClass CubeClass::CubeClass() : ShapeClass(8, Vec3(0, 0, 0), ScalableInertialMatrix(Vec3(8.0 / 3.0, 8.0 / 3.0, 8.0 / 3.0), Vec3(0, 0, 0)), CUBE_CLASS_ID) { // CubeClass is a singleton instance, starting refCount >= 1 ensures it is never deleted this->refCount = 1; } bool CubeClass::containsPoint(Vec3 point) const { return std::abs(point.x) <= 1.0 && std::abs(point.y) <= 1.0 && std::abs(point.z) <= 1.0; } double CubeClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { if(origin.x < 0) { origin.x = -origin.x; direction.x = -direction.x; } if(origin.y < 0) { origin.y = -origin.y; direction.y = -direction.y; } if(origin.z < 0) { origin.z = -origin.z; direction.z = -direction.z; } //origin + t * direction = x1z double tx = (1 - origin.x) / direction.x; Vec3 intersX = origin + tx * direction; if(std::abs(intersX.y) <= 1.0 && std::abs(intersX.z) <= 1.0) return tx; double ty = (1 - origin.y) / direction.y; Vec3 intersY = origin + ty * direction; if(std::abs(intersY.x) <= 1.0 && std::abs(intersY.z) <= 1.0) return ty; double tz = (1 - origin.z) / direction.z; Vec3 intersZ = origin + tz * direction; if(std::abs(intersZ.x) <= 1.0 && std::abs(intersZ.y) <= 1.0) return tz; return std::numeric_limits::infinity(); } BoundingBox CubeClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { Mat3 referenceFrame = rotation.asRotationMatrix() * scale; double x = std::abs(referenceFrame(0, 0)) + std::abs(referenceFrame(0, 1)) + std::abs(referenceFrame(0, 2)); double y = std::abs(referenceFrame(1, 0)) + std::abs(referenceFrame(1, 1)) + std::abs(referenceFrame(1, 2)); double z = std::abs(referenceFrame(2, 0)) + std::abs(referenceFrame(2, 1)) + std::abs(referenceFrame(2, 2)); BoundingBox result{-x,-y,-z,x,y,z}; return result; } double CubeClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return scale[0] * scale[0] + scale[1] * scale[1] + scale[2] * scale[2]; } Vec3f CubeClass::furthestInDirection(const Vec3f& direction) const { return Vec3f(direction.x < 0 ? -1.0f : 1.0f, direction.y < 0 ? -1.0f : 1.0f, direction.z < 0 ? -1.0f : 1.0f); } Polyhedron CubeClass::asPolyhedron() const { return ShapeLibrary::createCube(2.0); } #pragma endregion #pragma region SphereClass SphereClass::SphereClass() : ShapeClass(4.0 / 3.0 * PI, Vec3(0, 0, 0), ScalableInertialMatrix(Vec3(4.0 / 15.0 * PI, 4.0 / 15.0 * PI, 4.0 / 15.0 * PI), Vec3(0, 0, 0)), SPHERE_CLASS_ID) { // SphereClass is a singleton instance, starting refCount >= 1 ensures it is never deleted this->refCount = 1; } bool SphereClass::containsPoint(Vec3 point) const { return lengthSquared(point) <= 1.0; } double SphereClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { //o*o + o*d*t + o*d*t + t*t*d*d double c = origin * origin - 1; double b = origin * direction; double a = direction * direction; // solve a*t^2 + 2*b*t + c double D = b * b - a * c; if(D >= 0) { return (-b + -sqrt(D)) / a; } else { return std::numeric_limits::infinity(); } } BoundingBox SphereClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { double s = scale[0]; return BoundingBox{-s, -s, -s, s, s, s}; } double SphereClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return scale[0] * scale[0]; } double SphereClass::getScaledMaxRadius(DiagonalMat3 scale) const { return scale[0]; } Vec3f SphereClass::furthestInDirection(const Vec3f& direction) const { float lenSq = lengthSquared(direction); if(lenSq == 0.0f) return Vec3f(1.0f, 0.0f, 0.0f); return direction / std::sqrt(lenSq); } Polyhedron SphereClass::asPolyhedron() const { return ShapeLibrary::createSphere(1.0, 3); } void SphereClass::setScaleX(double newX, DiagonalMat3& scale) const { scale[0] = newX; scale[1] = newX; scale[2] = newX; } void SphereClass::setScaleY(double newY, DiagonalMat3& scale) const { scale[0] = newY; scale[1] = newY; scale[2] = newY; } void SphereClass::setScaleZ(double newZ, DiagonalMat3& scale) const { scale[0] = newZ; scale[1] = newZ; scale[2] = newZ; } #pragma endregion #pragma region CylinderClass /* Inertia of cyllinder: z = V * 1/2 x, y = V * 7/12 X = y + z = 7/12 Y = x + z = 7/12 Z = x + y = 1/2 x = 1/4 * PI * 2 y = 1/4 * PI * 2 z = 1/3 * PI * 2 */ CylinderClass::CylinderClass() : ShapeClass(PI * 2.0, Vec3(0, 0, 0), ScalableInertialMatrix(Vec3(PI / 2.0, PI / 2.0, PI * 2.0 / 3.0), Vec3(0, 0, 0)), CYLINDER_CLASS_ID) { // CylinderClass is a singleton instance, starting refCount >= 1 ensures it is never deleted this->refCount = 1; } bool CylinderClass::containsPoint(Vec3 point) const { return std::abs(point.z) <= 1.0 && point.x * point.x + point.y + point.y <= 1.0; } double CylinderClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { Vec2 xyOrigin(origin.x, origin.y); Vec2 xyDirection(direction.x, direction.y); //o*o + o*d*t + o*d*t + t*t*d*d double c = xyOrigin * xyOrigin - 1; double b = xyOrigin * xyDirection; double a = xyDirection * xyDirection; // solve a*t^2 + 2*b*t + c double D = b * b - a * c; if(D >= 0) { double t = (-b + -sqrt(D)) / a; double z = origin.z + t * direction.z; if(std::abs(z) <= 1.0) { return t; } else { // origin + t * direction = 1 => t = (1-origin)/direction double t2 = (((origin.z >= 0) ? 1 : -1) - origin.z) / direction.z; double x = origin.x + t2 * direction.x; double y = origin.y + t2 * direction.y; if(x * x + y * y <= 1.0) { return t2; } else { return std::numeric_limits::infinity(); } } } else { return std::numeric_limits::infinity(); } } BoundingBox CylinderClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { double height = scale[2]; double radius = scale[0]; Vec3 normalizedZVector = rotation.getZ(); Vec3 zVector = normalizedZVector * height; double extraX = std::hypot(normalizedZVector.y, normalizedZVector.z); double extraY = std::hypot(normalizedZVector.x, normalizedZVector.z); double extraZ = std::hypot(normalizedZVector.x, normalizedZVector.y); double x = std::abs(zVector.x) + extraX * radius; double y = std::abs(zVector.y) + extraY * radius; double z = std::abs(zVector.z) + extraZ * radius; return BoundingBox{-x, -y, -z, x, y, z}; } double CylinderClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return scale[0] * scale[0] + scale[2] * scale[2]; } Vec3f CylinderClass::furthestInDirection(const Vec3f& direction) const { float z = (direction.z >= 0.0f) ? 1.0f : -1.0f; float lenSq = direction.x * direction.x + direction.y * direction.y; if(lenSq == 0.0) return Vec3f(1.0f, 0.0f, z); float length = sqrt(lenSq); return Vec3f(direction.x / length, direction.y / length, z); } Polyhedron CylinderClass::asPolyhedron() const { return ShapeLibrary::createPrism(64, 1.0, 2.0); } void CylinderClass::setScaleX(double newX, DiagonalMat3& scale) const { scale[0] = newX; scale[1] = newX; } void CylinderClass::setScaleY(double newY, DiagonalMat3& scale) const { scale[0] = newY; scale[1] = newY; } #pragma endregion #pragma region WedgeClass WedgeClass::WedgeClass() : ShapeClass(4.0, Vec3(-1 / 3, 0, 1 / 3), ScalableInertialMatrix(Vec3(0.9030, 0.6032, 0.8411), Vec3(0.3517, 0.0210, 0.1667)), WEDGE_CLASS_ID) { // WedgeClass is a singleton instance, starting refCount >= 1 ensures it is never deleted this->refCount = 1; } bool WedgeClass::containsPoint(Vec3 point) const { return std::abs(point.x) <= 1.0 && std::abs(point.y) <= 1.0 && std::abs(point.z) <= 1.0 && point.x + point.y <= 0.0; } double WedgeClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { double currMin = std::numeric_limits::max(); double ty = (-1 - origin.y) / direction.y; Vec3 intersY = origin + ty * direction; if(std::abs(intersY.x) <= 1.0 && std::abs(intersY.z) <= 1.0 && intersY.x <= 1.0 && ty > 0) { if(ty < currMin) { currMin = ty; } } double tx = (-1 - origin.x) / direction.x; Vec3 intersX = origin + tx * direction; if(std::abs(intersX.y) <= 1.0 && std::abs(intersX.z) <= 1.0 && intersX.y <= 1.0 && tx > 0) { if(tx < currMin) { currMin = tx; } } { double tz = (-1 - origin.z) / direction.z; Vec3 intersZ = origin + tz * direction; if(std::abs(intersZ.x) <= 1.0 && std::abs(intersZ.y) <= 1.0 && intersZ.x + intersZ.y <= 0.0 && tz > 0) { if(tz < currMin) { currMin = tz; } } } { double tz = (1 - origin.z) / direction.z; Vec3 intersZ = origin + tz * direction; if(std::abs(intersZ.x) <= 1.0 && std::abs(intersZ.y) <= 1.0 && intersZ.x + intersZ.y <= 0.0 && tz > 0) { if(tz < currMin) { currMin = tz; } } } const double dn = direction.x + direction.y; double t = (-origin.x - origin.y) / dn; Vec3 point = origin + t * direction; if(std::abs(point.x) <= 1.0 && std::abs(point.y) <= 1.0 && std::abs(point.z) <= 1.0 && t > 0) { if(t < currMin) { currMin = t; } } return currMin; } BoundingBox WedgeClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { const Mat3& rotMat = rotation.asRotationMatrix(); Vec3 scaleRot[3] = {}; for(int i = 0; i < 3; i++) { scaleRot[i] = scale[i] * rotMat.getCol(i); } const Vec3 vertices[] = { -scaleRot[0] - scaleRot[1] - scaleRot[2], -scaleRot[0] - scaleRot[1] + scaleRot[2], scaleRot[0] - scaleRot[1] + scaleRot[2], scaleRot[0] - scaleRot[1] - scaleRot[2], -scaleRot[0] + scaleRot[1] - scaleRot[2], -scaleRot[0] + scaleRot[1] + scaleRot[2], }; double xmin = vertices[0].x; double xmax = vertices[0].x; double ymin = vertices[0].y; double ymax = vertices[0].y; double zmin = vertices[0].z; double zmax = vertices[0].z; for(int i = 1; i < ShapeLibrary::wedgeVertexCount; i++) { const Vec3& current = vertices[i]; if(current.x < xmin) xmin = current.x; if(current.x > xmax) xmax = current.x; if(current.y < ymin) ymin = current.y; if(current.y > ymax) ymax = current.y; if(current.z < zmin) zmin = current.z; if(current.z > zmax) zmax = current.z; } return BoundingBox{xmin, ymin, zmin, xmax, ymax, zmax}; } double WedgeClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return scale[0] * scale[0] + scale[1] * scale[1] + scale[2] * scale[2]; } Vec3f WedgeClass::furthestInDirection(const Vec3f& direction) const { float best = ShapeLibrary::wedgeVertices[0] * direction; Vec3f bestVertex = ShapeLibrary::wedgeVertices[0]; for(int i = 1; i < ShapeLibrary::wedgeVertexCount; i++) { float current = ShapeLibrary::wedgeVertices[i] * direction; if(current > best) { best = current; bestVertex = ShapeLibrary::wedgeVertices[i]; } } return bestVertex; } Polyhedron WedgeClass::asPolyhedron() const { return ShapeLibrary::wedge; } #pragma endregion #pragma region CornerClass CornerClass::CornerClass() : ShapeClass(4 / 3, Vec3(-2 / 3, -2 / 3, -2 / 3), ScalableInertialMatrix(Vec3(0.2000, 0.2000, 0.2000), Vec3(0.0667, 0.0667, 0.0667)), CORNER_CLASS_ID) { // CornerClass is a singleton instance, starting refCount >= 1 ensures it is never deleted this->refCount = 1; } bool CornerClass::containsPoint(Vec3 point) const { return point.x >= -1.0 && point.y >= -1.0 && point.z >= -1.0 && point.x + point.y + point.z <= -1.0; } double CornerClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { /* -x - 1 = 0; -y - 1 = 0; -z - 1 = 0; x + y + z + 1 = 0; */ double currMin = std::numeric_limits::max(); double tx = (-1 - origin.x) / direction.x; Vec3 intersX = origin + tx * direction; if(intersX.y >= -1.0 && intersX.z >= -1.0 && intersX.y + intersX.z <= 0.0 && tx > 0) { if(tx < currMin) { currMin = tx; } } double ty = (-1 - origin.y) / direction.y; Vec3 intersY = origin + ty * direction; if(intersY.x >= -1.0 && intersY.z >= -1.0 && intersY.x + intersY.z <= 0.0 && ty > 0) { if(ty < currMin) { currMin = ty; } } double tz = (-1 - origin.z) / direction.z; Vec3 intersZ = origin + tz * direction; if(intersZ.x >= -1.0 && intersZ.y >= -1.0 && intersZ.x + intersZ.y <= 0.0 && tz > 0) { if(tz < currMin) { currMin = tz; } } const double dn = direction.x + direction.y + direction.z; double t = (-1 - origin.x - origin.y - origin.z) / dn; Vec3 point = origin + t * direction; if(point.x >= -1.0 && point.y >= -1.0 && point.z >= -1.0 && t > 0) { if(t < currMin) { currMin = t; } } return currMin; } BoundingBox CornerClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { const Mat3f& rot = rotation.asRotationMatrix(); Vec3f scaleRot[3] = {}; for(int i = 0; i < 3; i++) { scaleRot[i] = scale[i] * rot.getCol(i); } const Vec3f vertices[] = { -scaleRot[0] - scaleRot[1] - scaleRot[2], -scaleRot[0] - scaleRot[1] + scaleRot[2], -scaleRot[0] + scaleRot[1] - scaleRot[2], scaleRot[0] - scaleRot[1] - scaleRot[2], }; double xmin = vertices[0].x; double xmax = vertices[0].x; double ymin = vertices[0].y; double ymax = vertices[0].y; double zmin = vertices[0].z; double zmax = vertices[0].z; for(int i = 1; i < ShapeLibrary::cornerVertexCount; i++) { const Vec3f& current = vertices[i]; if(current.x < xmin) xmin = current.x; if(current.x > xmax) xmax = current.x; if(current.y < ymin) ymin = current.y; if(current.y > ymax) ymax = current.y; if(current.z < zmin) zmin = current.z; if(current.z > zmax) zmax = current.z; } return BoundingBox{xmin, ymin, zmin, xmax, ymax, zmax}; } double CornerClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return scale[0] * scale[0] + scale[1] * scale[1] + scale[2] * scale[2]; } Vec3f CornerClass::furthestInDirection(const Vec3f& direction) const { float bestDir = ShapeLibrary::cornerVertices[0] * direction; Vec3f bestVertex = ShapeLibrary::cornerVertices[0]; for(int i = 1; i < ShapeLibrary::cornerVertexCount; i++) { float currBest = ShapeLibrary::cornerVertices[i] * direction; if(currBest > bestDir) { bestDir = currBest; bestVertex = ShapeLibrary::cornerVertices[i]; } } return bestVertex; } Polyhedron CornerClass::asPolyhedron() const { return ShapeLibrary::corner; } #pragma endregion #pragma region PolyhedronShapeClass PolyhedronShapeClass::PolyhedronShapeClass(Polyhedron&& poly) noexcept : poly(std::move(poly)), ShapeClass(poly.getVolume(), poly.getCenterOfMass(), poly.getScalableInertiaAroundCenterOfMass(), CONVEX_POLYHEDRON_CLASS_ID) {} bool PolyhedronShapeClass::containsPoint(Vec3 point) const { return poly.containsPoint(point); } double PolyhedronShapeClass::getIntersectionDistance(Vec3 origin, Vec3 direction) const { return poly.getIntersectionDistance(origin, direction); } BoundingBox PolyhedronShapeClass::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { return poly.getBounds(Mat3f(rotation.asRotationMatrix() * scale)); } double PolyhedronShapeClass::getScaledMaxRadius(DiagonalMat3 scale) const { return poly.getScaledMaxRadius(scale); } double PolyhedronShapeClass::getScaledMaxRadiusSq(DiagonalMat3 scale) const { return poly.getScaledMaxRadiusSq(scale); } Vec3f PolyhedronShapeClass::furthestInDirection(const Vec3f& direction) const { return poly.furthestInDirection(direction); } Polyhedron PolyhedronShapeClass::asPolyhedron() const { return poly; } #pragma endregion #pragma region PolyhedronShapeClassAVX BoundingBox PolyhedronShapeClassAVX::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { return poly.getBoundsAVX(Mat3f(rotation.asRotationMatrix() * scale)); } Vec3f PolyhedronShapeClassAVX::furthestInDirection(const Vec3f& direction) const { return poly.furthestInDirectionAVX(direction); } BoundingBox PolyhedronShapeClassSSE::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { return poly.getBoundsSSE(Mat3f(rotation.asRotationMatrix() * scale)); } Vec3f PolyhedronShapeClassSSE::furthestInDirection(const Vec3f& direction) const { return poly.furthestInDirectionSSE(direction); } BoundingBox PolyhedronShapeClassSSE4::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { return poly.getBoundsSSE(Mat3f(rotation.asRotationMatrix() * scale)); } Vec3f PolyhedronShapeClassSSE4::furthestInDirection(const Vec3f& direction) const { return poly.furthestInDirectionSSE4(direction); } BoundingBox PolyhedronShapeClassFallback::getBounds(const Rotation& rotation, const DiagonalMat3& scale) const { return poly.getBoundsFallback(Mat3f(rotation.asRotationMatrix() * scale)); } Vec3f PolyhedronShapeClassFallback::furthestInDirection(const Vec3f& direction) const { return poly.furthestInDirectionFallback(direction); } #pragma endregion const CubeClass CubeClass::instance; const SphereClass SphereClass::instance; const CylinderClass CylinderClass::instance; const WedgeClass WedgeClass::instance; const CornerClass CornerClass::instance; }; ================================================ FILE: Physics3D/geometry/builtinShapeClasses.h ================================================ #pragma once #include "polyhedron.h" #include "shapeClass.h" namespace P3D { #define CUBE_CLASS_ID 0 #define SPHERE_CLASS_ID 1 #define CYLINDER_CLASS_ID 2 #define WEDGE_CLASS_ID 3 #define CORNER_CLASS_ID 4 #define CONVEX_POLYHEDRON_CLASS_ID 10 class CubeClass : public ShapeClass { CubeClass(); public: virtual bool containsPoint(Vec3 point) const; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const; virtual Vec3f furthestInDirection(const Vec3f& direction) const; virtual Polyhedron asPolyhedron() const; static const CubeClass instance; }; class SphereClass : public ShapeClass { SphereClass(); public: virtual bool containsPoint(Vec3 point) const; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const; virtual double getScaledMaxRadius(DiagonalMat3 scale) const; virtual Vec3f furthestInDirection(const Vec3f& direction) const; virtual Polyhedron asPolyhedron() const; void setScaleX(double newX, DiagonalMat3& scale) const override; void setScaleY(double newY, DiagonalMat3& scale) const override; void setScaleZ(double newZ, DiagonalMat3& scale) const override; static const SphereClass instance; }; class CylinderClass : public ShapeClass { CylinderClass(); public: virtual bool containsPoint(Vec3 point) const; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const; virtual Vec3f furthestInDirection(const Vec3f& direction) const; virtual Polyhedron asPolyhedron() const; void setScaleX(double newX, DiagonalMat3& scale) const override; void setScaleY(double newY, DiagonalMat3& scale) const override; static const CylinderClass instance; }; class WedgeClass : public ShapeClass { WedgeClass(); public: virtual bool containsPoint(Vec3 point) const override; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const override; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; virtual Polyhedron asPolyhedron() const override; static const WedgeClass instance; }; class CornerClass : public ShapeClass { CornerClass(); public: virtual bool containsPoint(Vec3 point) const override; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const override; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; virtual Polyhedron asPolyhedron() const override; static const CornerClass instance; }; class PolyhedronShapeClass : public ShapeClass { protected: Polyhedron poly; public: PolyhedronShapeClass(Polyhedron&& poly) noexcept; virtual bool containsPoint(Vec3 point) const override; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const override; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual double getScaledMaxRadius(DiagonalMat3 scale) const override; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; virtual Polyhedron asPolyhedron() const override; }; class PolyhedronShapeClassAVX : public PolyhedronShapeClass { public: using PolyhedronShapeClass::PolyhedronShapeClass; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; }; class PolyhedronShapeClassSSE : public PolyhedronShapeClass { public: using PolyhedronShapeClass::PolyhedronShapeClass; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; }; class PolyhedronShapeClassSSE4 : public PolyhedronShapeClass { public: using PolyhedronShapeClass::PolyhedronShapeClass; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; }; class PolyhedronShapeClassFallback : public PolyhedronShapeClass { public: using PolyhedronShapeClass::PolyhedronShapeClass; virtual BoundingBox getBounds(const Rotation& rotation, const DiagonalMat3& scale) const override; virtual Vec3f furthestInDirection(const Vec3f& direction) const override; }; }; ================================================ FILE: Physics3D/geometry/computationBuffer.cpp ================================================ #include "computationBuffer.h" #include "../misc/debug.h" #include "genericIntersection.h" namespace P3D { ComputationBuffers::ComputationBuffers(int initialVertCount, int initialTriangleCount) : vertexCapacity(initialVertCount), triangleCapacity(initialTriangleCount) { createVertexBuffersUnsafe(initialVertCount); createTriangleBuffersUnsafe(initialTriangleCount); } void ComputationBuffers::ensureCapacity(int vertCapacity, int triangleCapacity) { if(this->vertexCapacity < vertCapacity) { Debug::log("Increasing vertex buffer capacity from %d to %d", this->vertexCapacity, vertCapacity); deleteVertexBuffers(); createVertexBuffersUnsafe(vertCapacity); } if(this->triangleCapacity < triangleCapacity) { Debug::log("Increasing vertex buffer capacity from %d to %d", this->triangleCapacity, triangleCapacity); deleteTriangleBuffers(); createTriangleBuffersUnsafe(triangleCapacity); } } ComputationBuffers::~ComputationBuffers() { deleteVertexBuffers(); deleteTriangleBuffers(); } void ComputationBuffers::createVertexBuffersUnsafe(int newVertexCapacity) { vertBuf = new Vec3f[newVertexCapacity]; knownVecs = new MinkowskiPointIndices[newVertexCapacity]; this->vertexCapacity = newVertexCapacity; } void ComputationBuffers::createTriangleBuffersUnsafe(int newTriangleCapacity) { triangleBuf = new Triangle[newTriangleCapacity]; neighborBuf = new TriangleNeighbors[newTriangleCapacity]; edgeBuf = new EdgePiece[newTriangleCapacity]; removalBuf = new int[newTriangleCapacity]; this->triangleCapacity = newTriangleCapacity; } void ComputationBuffers::deleteVertexBuffers() { delete[] vertBuf; delete[] knownVecs; } void ComputationBuffers::deleteTriangleBuffers() { delete[] triangleBuf; delete[] neighborBuf; delete[] edgeBuf; delete[] removalBuf; } }; ================================================ FILE: Physics3D/geometry/computationBuffer.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "convexShapeBuilder.h" namespace P3D { struct MinkowskiPointIndices; struct ComputationBuffers { Vec3f* vertBuf; Triangle* triangleBuf; TriangleNeighbors* neighborBuf; EdgePiece* edgeBuf; int* removalBuf; MinkowskiPointIndices* knownVecs; int vertexCapacity; int triangleCapacity; ComputationBuffers(int initialVertCount, int initialTriangleCount); void ensureCapacity(int vertCapacity, int triangleCapacity); ~ComputationBuffers(); ComputationBuffers(ComputationBuffers& other) = delete; ComputationBuffers(ComputationBuffers&& other) = delete; void operator=(ComputationBuffers& other) = delete; void operator=(ComputationBuffers&& other) = delete; private: void createVertexBuffersUnsafe(int vertexCapacity); void createTriangleBuffersUnsafe(int triangleCapacity); void deleteVertexBuffers(); void deleteTriangleBuffers(); }; }; ================================================ FILE: Physics3D/geometry/convexShapeBuilder.cpp ================================================ #include "convexShapeBuilder.h" #include #include #include #include "../misc/validityHelper.h" #include "../misc/catchable_assert.h" namespace P3D { ConvexShapeBuilder::ConvexShapeBuilder(Vec3f* vertBuf, Triangle* triangleBuf, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf, int* removalBuffer, EdgePiece* newTriangleBuffer) : vertexBuf(vertBuf), triangleBuf(triangleBuf), vertexCount(vertexCount), triangleCount(triangleCount), neighborBuf(neighborBuf), removalBuffer(removalBuffer), newTriangleBuffer(newTriangleBuffer) { fillNeighborBuf(triangleBuf, triangleCount, neighborBuf); } ConvexShapeBuilder::ConvexShapeBuilder(const Polyhedron& s, Vec3f* vertBuf, Triangle* triangleBuf, TriangleNeighbors* neighborBuf, int* removalBuffer, EdgePiece* newTriangleBuffer) : vertexBuf(vertBuf), triangleBuf(triangleBuf), vertexCount(s.vertexCount), triangleCount(s.triangleCount), neighborBuf(neighborBuf), removalBuffer(removalBuffer), newTriangleBuffer(newTriangleBuffer) { for(int i = 0; i < s.vertexCount; i++) { vertBuf[i] = s.getVertex(i); } for(int i = 0; i < s.triangleCount; i++) { triangleBuf[i] = s.getTriangle(i); } fillNeighborBuf(triangleBuf, triangleCount, neighborBuf); } void moveTriangle(Triangle* triangleBuf, TriangleNeighbors* neighborBuf, int oldIndex, int newIndex) { triangleBuf[newIndex] = triangleBuf[oldIndex]; TriangleNeighbors neighbors = neighborBuf[oldIndex]; neighborBuf[newIndex] = neighbors; // update neighbors of moved triangle for(int neighborIndex : neighbors.neighbors) { neighborBuf[neighborIndex].replaceNeighbor(oldIndex, newIndex); } } void ConvexShapeBuilder::removeTriangle(int triangleIndex) { Triangle oldT = triangleBuf[triangleIndex]; TriangleNeighbors& oldN = neighborBuf[triangleIndex]; // update neighbors of deleted triangle for(int neighbor : oldN.neighbors) { oldN.replaceNeighbor(triangleIndex, -1); } // move last triangle in the list to the spot of the deleted triangle triangleCount--; moveTriangle(triangleBuf, neighborBuf, triangleCount, triangleIndex); } bool ConvexShapeBuilder::isAbove(const Vec3f& point, Triangle t) { Vec3f* verts = vertexBuf; Vec3f v0 = verts[t[0]]; Vec3f v1 = verts[t[1]]; Vec3f v2 = verts[t[2]]; Vec3f normalVec = (v1 - v0) % (v2 - v0); return (point - v0) * normalVec > 0; } struct ConvexTriangleIterator { int* removalList; EdgePiece* newTrianglesList; int newTriangleCount = 0; int removalCount = 0; Vec3f point; ConvexShapeBuilder& shapeBuilder; ConvexTriangleIterator(const Vec3f& point, ConvexShapeBuilder& shapeBuilder, int* removalBuffer, EdgePiece* newTrianglesBuffer) : point(point), shapeBuilder(shapeBuilder), removalList(removalBuffer), newTrianglesList(newTrianglesBuffer) {} void markTriangleRemoved(int triangle) { shapeBuilder.neighborBuf[triangle].AB_Neighbor = -1; removalList[removalCount++] = triangle; } bool isRemoved(int triangle) { return shapeBuilder.neighborBuf[triangle].AB_Neighbor == -1; } bool isAbove(Triangle t) { Vec3f* verts = shapeBuilder.vertexBuf; Vec3f v0 = verts[t[0]]; Vec3f v1 = verts[t[1]]; Vec3f v2 = verts[t[2]]; Vec3f normalVec = (v1 - v0) % (v2 - v0); return (point - v0) * normalVec > 0; } // returned value is whether the last triangle checked was an edgePiece, in that case add it to the list void recurseTriangle(int currentTriangle, int previousTriangle, int previousTriangleSide) { if(isRemoved(currentTriangle)) return; Triangle curTriangle = shapeBuilder.triangleBuf[currentTriangle]; TriangleNeighbors currentNeighbors = shapeBuilder.neighborBuf[currentTriangle]; int arrivingNeighborIndex = currentNeighbors.getNeighborIndex(previousTriangle); if(isAbove(curTriangle)) { // remove this triangle, check neighbors markTriangleRemoved(currentTriangle); int rightSide = (arrivingNeighborIndex + 1) % 3; int leftSide = (arrivingNeighborIndex + 2) % 3; recurseTriangle(currentNeighbors[rightSide], currentTriangle, rightSide); recurseTriangle(currentNeighbors[leftSide], currentTriangle, leftSide); } else { // this triangle is an edge, add it to the list int vertexInTriangleIndex = (previousTriangleSide + 2) % 3; int vertex = shapeBuilder.triangleBuf[previousTriangle][vertexInTriangleIndex]; newTrianglesList[newTriangleCount++] = EdgePiece{vertex, currentTriangle, arrivingNeighborIndex}; } } void linkTipTriangles(int next, int previous) { shapeBuilder.neighborBuf[previous].CA_Neighbor = next; shapeBuilder.neighborBuf[next].AB_Neighbor = previous; } void createTipTriangle(int newPointVertex, int previousVertex, int triangleToBeReplaced, EdgePiece replacingInfo) { // int toBeReplaced = removalList[i]; // EdgePiece replacingInfo = newTrianglesList[i]; int edgeTriangle = replacingInfo.edgeTriangle; shapeBuilder.triangleBuf[triangleToBeReplaced] = Triangle{newPointVertex, previousVertex, replacingInfo.vertexIndex}; shapeBuilder.neighborBuf[triangleToBeReplaced].BC_Neighbor = edgeTriangle; shapeBuilder.neighborBuf[edgeTriangle][replacingInfo.neighborIndexOfEdgeTriangle] = triangleToBeReplaced; } void applyUpdates(int newPointVertex) { int easilyMovableTriangles = std::min(newTriangleCount, removalCount); int previousVertex = newTrianglesList[newTriangleCount - 1].vertexIndex; int previousTriangle = -1; { int toBeReplaced = removalList[0]; EdgePiece replacingInfo = newTrianglesList[0]; createTipTriangle(newPointVertex, previousVertex, toBeReplaced, replacingInfo); previousVertex = replacingInfo.vertexIndex; previousTriangle = toBeReplaced; } for(int i = 1; i < easilyMovableTriangles; i++) { int toBeReplaced = removalList[i]; EdgePiece replacingInfo = newTrianglesList[i]; createTipTriangle(newPointVertex, previousVertex, toBeReplaced, replacingInfo); linkTipTriangles(toBeReplaced, previousTriangle); previousVertex = replacingInfo.vertexIndex; previousTriangle = toBeReplaced; } if(newTriangleCount >= removalCount) { // extra triangles, add at the end for(int i = removalCount; i < newTriangleCount; i++) { int toBeReplaced = shapeBuilder.triangleCount++; EdgePiece replacingInfo = newTrianglesList[i]; createTipTriangle(newPointVertex, previousVertex, toBeReplaced, replacingInfo); linkTipTriangles(toBeReplaced, previousTriangle); previousVertex = replacingInfo.vertexIndex; previousTriangle = toBeReplaced; } linkTipTriangles(removalList[0], previousTriangle); } else { // not enough triangles to fill the gaps, move stuff back linkTipTriangles(removalList[0], previousTriangle); // The shape is entirely valid at this point, we just need to remove the triangles that are no longer part of it int resultingPolygonSize = shapeBuilder.triangleCount + newTriangleCount - removalCount; int replacingTriangleCursor = resultingPolygonSize; for(int i = newTriangleCount; i < removalCount; i++) { int triangleToRemove = removalList[i]; if(triangleToRemove < resultingPolygonSize) { for(; replacingTriangleCursor < shapeBuilder.triangleCount; replacingTriangleCursor++) { if(!isRemoved(replacingTriangleCursor)) { moveTriangle(shapeBuilder.triangleBuf, shapeBuilder.neighborBuf, replacingTriangleCursor, triangleToRemove); replacingTriangleCursor++; goto nextTriangle; } } throw "Bad state in convexShapeBuilder! could not find non removed triangle to fill the gap!"; } nextTriangle:; } shapeBuilder.triangleCount = resultingPolygonSize; } } }; void ConvexShapeBuilder::addPoint(const Vec3f& point, int oldTriangleIndex) { catchable_assert(isVecValid(point)); ConvexTriangleIterator iter(point, *this, this->removalBuffer, this->newTriangleBuffer); TriangleNeighbors neighbors = neighborBuf[oldTriangleIndex]; iter.markTriangleRemoved(oldTriangleIndex); iter.recurseTriangle(neighbors[0], oldTriangleIndex, 0); iter.recurseTriangle(neighbors[1], oldTriangleIndex, 1); iter.recurseTriangle(neighbors[2], oldTriangleIndex, 2); vertexBuf[vertexCount++] = point; iter.applyUpdates(vertexCount - 1); } bool ConvexShapeBuilder::addPoint(const Vec3f& point) { for(int i = 0; i < triangleCount; i++) { if(isAbove(point, triangleBuf[i])) { addPoint(point, i); return true; } } return false; } Polyhedron ConvexShapeBuilder::toPolyhedron() const { return Polyhedron(stripUnusedVertices(vertexBuf, triangleBuf, vertexCount, triangleCount)); } IndexedShape ConvexShapeBuilder::toIndexedShape() const { return IndexedShape(this->toPolyhedron(), neighborBuf); } }; ================================================ FILE: Physics3D/geometry/convexShapeBuilder.h ================================================ #pragma once #include "indexedShape.h" #include "shapeBuilder.h" #include "../datastructures/sharedArray.h" namespace P3D { // used in building shape struct EdgePiece { int vertexIndex; int edgeTriangle; int neighborIndexOfEdgeTriangle; }; /* No checks for capacity are done, make sure that vertBuf, triangleBuf and neighborBuf are large enough */ class ConvexShapeBuilder { public: Vec3f* vertexBuf; Triangle* triangleBuf; int vertexCount; int triangleCount; TriangleNeighbors* neighborBuf; // temporary buffers int* removalBuffer; EdgePiece* newTriangleBuffer; ConvexShapeBuilder(Vec3f* vertBuf, Triangle* triangleBuf, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf, int* removalBuffer, EdgePiece* newTriangleBuffer); ConvexShapeBuilder(const Polyhedron& s, Vec3f* newVertBuf, Triangle* newTriangleBuf, TriangleNeighbors* neighborBuf, int* removalBuffer, EdgePiece* newTriangleBuffer); void addPoint(const Vec3f& point, int oldTriangleIndex); // returns true if successful bool addPoint(const Vec3f& point); void removeTriangle(int triangleIndex); bool isAbove(const Vec3f& point, Triangle t); Polyhedron toPolyhedron() const; IndexedShape toIndexedShape() const; }; }; ================================================ FILE: Physics3D/geometry/genericCollidable.h ================================================ #pragma once #include "../math/linalg/vec.h" namespace P3D { struct GenericCollidable { virtual Vec3f furthestInDirection(const Vec3f& direction) const = 0; }; }; ================================================ FILE: Physics3D/geometry/genericIntersection.cpp ================================================ #include "genericIntersection.h" #include "../math/linalg/vec.h" #include "convexShapeBuilder.h" #include "computationBuffer.h" #include "../math/utils.h" #include "../misc/debug.h" #include "../misc/physicsProfiler.h" #include "../misc/profiling.h" #include "polyhedron.h" #include "../misc/validityHelper.h" #include "../misc/catchable_assert.h" #include #define GJK_MAX_ITER 200 #define EPA_MAX_ITER 200 namespace P3D { inline static void incDebugTally(HistoricTally& tally, int iterTime) { if(iterTime >= GJK_MAX_ITER) { tally.addToTally(IterationTime::LIMIT_REACHED, 1); } else if(iterTime >= 15) { tally.addToTally(IterationTime::TOOMANY, 1); } else { tally.addToTally(static_cast(iterTime), 1); } } static Vec3f getNormalVec(Triangle t, Vec3f* vertices) { Vec3f v0 = vertices[t[0]]; Vec3f v1 = vertices[t[1]]; Vec3f v2 = vertices[t[2]]; return (v1 - v0) % (v2 - v0); } static double getDistanceOfTriangleToOriginSquared(Triangle t, Vec3f* vertices) { return pointToPlaneDistanceSquared(getNormalVec(t, vertices), vertices[t[0]]); } struct NearestSurface { int triangleIndex; double distanceSquared; }; static NearestSurface getNearestSurface(const ConvexShapeBuilder& builder) { int best = 0; double bestDistSq = getDistanceOfTriangleToOriginSquared(builder.triangleBuf[0], builder.vertexBuf); for(int i = 1; i < builder.triangleCount; i++) { Triangle t = builder.triangleBuf[i]; double distSq = getDistanceOfTriangleToOriginSquared(t, builder.vertexBuf); if(distSq < bestDistSq) { best = i; bestDistSq = distSq; } } return NearestSurface{best, bestDistSq}; } static MinkPoint getSupport(const ColissionPair& info, const Vec3f& searchDirection) { Vec3f furthest1 = info.scaleFirst * info.first.furthestInDirection(info.scaleFirst * searchDirection); // in local space of first Vec3f transformedSearchDirection = -info.transform.relativeToLocal(searchDirection); Vec3f furthest2 = info.scaleSecond * info.second.furthestInDirection(info.scaleSecond * transformedSearchDirection); // in local space of second Vec3f secondVertex = info.transform.localToGlobal(furthest2); // converted to local space of first /*catchable_assert(isVecValid(furthest1)); catchable_assert(isVecValid(transformedSearchDirection)); catchable_assert(isVecValid(furthest2)); catchable_assert(isVecValid(secondVertex));*/ return MinkPoint{ furthest1 - secondVertex, furthest1, secondVertex }; // local to first } std::optional runGJKTransformed(const ColissionPair& info, Vec3f searchDirection) { MinkPoint A(getSupport(info, searchDirection)); MinkPoint B, C, D; // set new searchdirection to be straight at the origin searchDirection = -A.p; // GJK 2 // s.A is B.p // s.B is A.p // line segment, check if line, or either point closer // B can't be closer since we picked a point towards the origin // Just one test, to see if the line segment or A is closer B = getSupport(info, searchDirection); if (B.p * searchDirection < 0) { incDebugTally(GJKNoCollidesIterationStatistics, 0); return std::optional(); } Vec3f AO = -B.p; Vec3f AB = A.p - B.p; searchDirection = -(AO % AB) % AB; C = getSupport(info, searchDirection); if (C.p * searchDirection < 0) { incDebugTally(GJKNoCollidesIterationStatistics, 1); return std::optional(); } // s.A is C.p newest // s.B is B.p // s.C is A.p // triangle, check if closest to one of the edges, point, or face for(int iter = 0; iter < GJK_MAX_ITER; iter++) { Vec3f AO = -C.p; Vec3f AB = B.p - C.p; Vec3f AC = A.p - C.p; Vec3f normal = AB % AC; Vec3f nAB = AB % normal; Vec3f nAC = normal % AC; if(AO * nAB > 0) { // edge of AB is closest, searchDirection perpendicular to AB towards O A = B; B = C; searchDirection = -(AO % AB) % AB; C = getSupport(info, searchDirection); if(C.p * searchDirection < 0) { incDebugTally(GJKNoCollidesIterationStatistics, iter+2); return std::optional(); } } else { if(AO*nAC > 0) { // edge of AC is closest, searchDirection perpendicular to AC towards O B = C; searchDirection = -(AO % AC) % AC; C = getSupport(info, searchDirection); if(C.p * searchDirection < 0) { incDebugTally(GJKNoCollidesIterationStatistics, iter + 2); return std::optional(); } } else { // hurray! best shape is tetrahedron // just find which direction to look in if(normal * AO > 0) { searchDirection = normal; } else { searchDirection = -normal; // invert triangle MinkPoint tmp(A); A = B; B = tmp; } // GJK 4 // s.A is D.p // s.B is C.p // s.C is B.p // s.D is A.p D = getSupport(info, searchDirection); if(D.p * searchDirection < 0) { incDebugTally(GJKNoCollidesIterationStatistics, iter + 2); return std::optional(); } Vec3f AO = -D.p; // A is new point, at the top of the tetrahedron Vec3f AB = C.p - D.p; Vec3f AC = B.p - D.p; Vec3f AD = A.p - D.p; Vec3f nABC = AB % AC; Vec3f nACD = AC % AD; Vec3f nADB = AD % AB; if(nACD * AO > 0) { // remove B and continue with triangle // s = Simplex(s.A, s.C, s.D, s.At, s.Ct, s.Dt); C = D; } else { if(nABC * AO > 0) { // remove D and continue with triangle // s = Simplex(s.A, s.B, s.C, s.At, s.Bt, s.Ct); A = B; B = C; C = D; } else { if(nADB * AO > 0) { // remove C and continue with triangle //s = Simplex(s.A, s.D, s.B, s.At, s.Dt, s.Bt); B = C; C = D; } else { // GOTCHA! TETRAHEDRON COVERS THE ORIGIN! incDebugTally(GJKCollidesIterationStatistics, iter + 2); return std::optional(Tetrahedron{D, C, B, A}); } } } } } } Debug::logWarn("GJK iteration limit reached!"); incDebugTally(GJKNoCollidesIterationStatistics, GJK_MAX_ITER + 2); return std::optional(); } void initializeBuffer(const Tetrahedron& s, ComputationBuffers& b) { b.vertBuf[0] = s.A.p; b.vertBuf[1] = s.B.p; b.vertBuf[2] = s.C.p; b.vertBuf[3] = s.D.p; b.triangleBuf[0] = {0,1,2}; b.triangleBuf[1] = {0,2,3}; b.triangleBuf[2] = {0,3,1}; b.triangleBuf[3] = {3,2,1}; b.knownVecs[0] = MinkowskiPointIndices{s.A.originFirst, s.A.originSecond}; b.knownVecs[1] = MinkowskiPointIndices{s.B.originFirst, s.B.originSecond}; b.knownVecs[2] = MinkowskiPointIndices{s.C.originFirst, s.C.originSecond}; b.knownVecs[3] = MinkowskiPointIndices{s.D.originFirst, s.D.originSecond}; } bool runEPATransformed(const ColissionPair& info, const Tetrahedron& s, Vec3f& intersection, Vec3f& exitVector, ComputationBuffers& bufs) { initializeBuffer(s, bufs); ConvexShapeBuilder builder(bufs.vertBuf, bufs.triangleBuf, 4, 4, bufs.neighborBuf, bufs.removalBuf, bufs.edgeBuf); for(int iter = 0; iter < EPA_MAX_ITER; iter++) { NearestSurface ns = getNearestSurface(builder); int closestTriangleIndex = ns.triangleIndex; double distSq = ns.distanceSquared; Triangle closestTriangle = builder.triangleBuf[closestTriangleIndex]; Vec3f a = builder.vertexBuf[closestTriangle[0]]; Vec3f b = builder.vertexBuf[closestTriangle[1]]; Vec3f c = builder.vertexBuf[closestTriangle[2]]; catchable_assert(isVecValid(a)); catchable_assert(isVecValid(b)); catchable_assert(isVecValid(c)); Vec3f closestTriangleNormal = getNormalVec(closestTriangle, builder.vertexBuf); MinkPoint point(getSupport(info, closestTriangleNormal)); catchable_assert(isVecValid(point.p)); // point is the new point to be added, check if it's past the current triangle double newPointDistSq = pow(point.p * closestTriangleNormal, 2) / lengthSquared(closestTriangleNormal); MinkowskiPointIndices curIndices{point.originFirst, point.originSecond}; // Do not remove! The inversion catches NaN as well! if(!(newPointDistSq <= distSq * 1.01)) { bufs.knownVecs[builder.vertexCount] = curIndices; builder.addPoint(point.p, closestTriangleIndex); } else { // closestTriangle is an edge triangle, so our best direction is towards this triangle. RayIntersection ri = rayTriangleIntersection(Vec3f(), closestTriangleNormal, a, b, c); exitVector = ri.d * closestTriangleNormal; catchable_assert(isVecValid(exitVector)); MinkowskiPointIndices inds[3]{bufs.knownVecs[closestTriangle[0]], bufs.knownVecs[closestTriangle[1]], bufs.knownVecs[closestTriangle[2]]}; Vec3f v0 = b - a, v1 = c - a, v2 = exitVector - a; float d00 = v0 * v0; float d01 = v0 * v1; float d11 = v1 * v1; float d20 = v2 * v0; float d21 = v2 * v1; float denom = d00 * d11 - d01 * d01; float v = (d11 * d20 - d01 * d21) / denom; float w = (d00 * d21 - d01 * d20) / denom; float u = 1.0f - v - w; Vec3f A0 = inds[0][0], A1 = inds[1][0], A2 = inds[2][0]; Vec3f B0 = inds[0][1], B1 = inds[1][1], B2 = inds[2][1]; Vec3f avgFirst = A0 * u + A1 * v + A2 * w; Vec3f avgSecond = B0 * u + B1 * v + B2 * w; // intersection = (avgFirst + relativeCFrame.localToGlobal(avgSecond)) / 2; intersection = (avgFirst + avgSecond) * 0.5f; incDebugTally(EPAIterationStatistics, iter); return true; } } Debug::logWarn("EPA iteration limit exceeded! "); incDebugTally(EPAIterationStatistics, EPA_MAX_ITER); return false; } }; ================================================ FILE: Physics3D/geometry/genericIntersection.h ================================================ #pragma once #include #include "../math/linalg/vec.h" #include "../math/transform.h" #include "genericCollidable.h" namespace P3D { struct ComputationBuffers; struct Simplex; struct MinkowskiPointIndices { Vec3f indices[2]; Vec3f& operator[](int i) { return indices[i]; } }; struct Simplex { Vec3 A, B, C, D; MinkowskiPointIndices At, Bt, Ct, Dt; int order; Simplex() = default; Simplex(Vec3 A, MinkowskiPointIndices At) : A(A), At(At), order(1) {} Simplex(Vec3 A, Vec3 B, MinkowskiPointIndices At, MinkowskiPointIndices Bt) : A(A), B(B), At(At), Bt(Bt), order(2) {} Simplex(Vec3 A, Vec3 B, Vec3 C, MinkowskiPointIndices At, MinkowskiPointIndices Bt, MinkowskiPointIndices Ct) : A(A), B(B), C(C), At(At), Bt(Bt), Ct(Ct), order(3) {} Simplex(Vec3 A, Vec3 B, Vec3 C, Vec3 D, MinkowskiPointIndices At, MinkowskiPointIndices Bt, MinkowskiPointIndices Ct, MinkowskiPointIndices Dt) : A(A), B(B), C(C), D(D), At(At), Bt(Bt), Ct(Ct), Dt(Dt), order(4) {} void insert(Vec3 newA, MinkowskiPointIndices newAt) { D = C; C = B; B = A; A = newA; Dt = Ct; Ct = Bt; Bt = At; At = newAt; order++; } }; struct MinkPoint { Vec3f p; Vec3f originFirst; Vec3f originSecond; }; struct Tetrahedron { MinkPoint A, B, C, D; }; struct ColissionPair { const GenericCollidable& first; const GenericCollidable& second; CFramef transform; DiagonalMat3f scaleFirst; DiagonalMat3f scaleSecond; }; std::optional runGJKTransformed(const ColissionPair& colissionPair, Vec3f initialSearchDirection); bool runEPATransformed(const ColissionPair& colissionPair, const Tetrahedron& s, Vec3f& intersection, Vec3f& exitVector, ComputationBuffers& bufs); }; ================================================ FILE: Physics3D/geometry/indexedShape.cpp ================================================ #include "indexedShape.h" #include #include "../misc/validityHelper.h" namespace P3D { int& TriangleNeighbors::operator[](int index) { return this->neighbors[index]; } bool TriangleNeighbors::hasNeighbor(int triangleIndex) const { return triangleIndex == neighbors[0] || triangleIndex == neighbors[1] || triangleIndex == neighbors[2]; } int TriangleNeighbors::replaceNeighbor(int oldNeighbor, int newNeighbor) { for(int i = 0; i < 3; i++) { if(neighbors[i] == oldNeighbor) { neighbors[i] = newNeighbor; return i; } } throw std::runtime_error("Neighbor not found in replaceNeighbor"); } int TriangleNeighbors::getNeighborIndex(int neighbor) { for(int i = 0; i < 3; i++) { if(neighbors[i] == neighbor) { return i; } } throw std::runtime_error("Neighbor not found in getNeighborIndex"); } IndexedShape::IndexedShape(Polyhedron&& poly, TriangleNeighbors* neighborBuf) : Polyhedron(std::move(poly)), neighbors(neighborBuf) {} IndexedShape::IndexedShape(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf) : Polyhedron(vertices, triangles, vertexCount, triangleCount), neighbors(neighborBuf) {} void fillNeighborBuf(const Triangle* triangles, int triangleCount, TriangleNeighbors* neighborBuf) { for(int i = 0; i < triangleCount; i++) { for(int j = i + 1; j < triangleCount; j++) { const Triangle& ti = triangles[i]; const Triangle& tj = triangles[j]; for(int index1 = 0; index1 < 3; index1++) { for(int index2 = 0; index2 < 3; index2++) { if(ti[index1] == tj[index2]) { if(ti[(index1 + 1) % 3] == tj[(index2 + 2) % 3]) { // Triangles are joined at edges: index1 - index1+1 <-> index2-1 - index2 neighborBuf[i][(index1 + 2) % 3] = j; neighborBuf[j][(index2 + 1) % 3] = i; } else if(ti[(index1 + 2) % 3] == tj[(index2 + 1) % 3]) { // Triangles are joined at edges: index1-1 - index1 <-> index2 - index2+1 neighborBuf[i][(index1 + 1) % 3] = j; neighborBuf[j][(index2 + 2) % 3] = i; } goto nextTriangle; } } } nextTriangle: continue; } } } }; ================================================ FILE: Physics3D/geometry/indexedShape.h ================================================ #pragma once #include "polyhedron.h" #include "../datastructures/sharedArray.h" namespace P3D { struct TriangleNeighbors { union { struct { int BC_Neighbor, CA_Neighbor, AB_Neighbor; }; int neighbors[3]; }; int& operator[](int sideIndex); bool hasNeighbor(int triangleIndex) const; // replaces neighbor, returns index of replaced neighbor, throws BadNeighborException if not found int replaceNeighbor(int oldNeighbor, int newNeighbor); int getNeighborIndex(int neighbor); }; /*struct TriangleIndex { TriangleNeighbors * neighbors; TriangleIndex(Triangle* triangles, int triangleCount, TriangleNeighbors * neighborBuf); };*/ struct IndexedShape : Polyhedron { TriangleNeighbors* neighbors; IndexedShape(Polyhedron&& poly, TriangleNeighbors* neighborBuf); IndexedShape(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf); }; void fillNeighborBuf(const Triangle* triangles, int triangleCount, TriangleNeighbors* neighborBuf); }; ================================================ FILE: Physics3D/geometry/intersection.cpp ================================================ #include "intersection.h" #include "genericIntersection.h" #include "../misc/physicsProfiler.h" #include "../misc/profiling.h" #include "computationBuffer.h" #include "shape.h" #include "polyhedron.h" #include "../misc/validityHelper.h" #include "shapeClass.h" #include "../misc/catchable_assert.h" #include namespace P3D { std::optional intersectsTransformed(const Shape& first, const Shape& second, const CFrame& relativeTransform) { return intersectsTransformed(*first.baseShape, *second.baseShape, relativeTransform, first.scale, second.scale); } thread_local ComputationBuffers buffers(1000, 2000); std::optional intersectsTransformed(const GenericCollidable& first, const GenericCollidable& second, const CFrame& relativeTransform, const DiagonalMat3& scaleFirst, const DiagonalMat3& scaleSecond) { ColissionPair info{first, second, relativeTransform, scaleFirst, scaleSecond}; physicsMeasure.mark(PhysicsProcess::GJK_COL); std::optional collides = runGJKTransformed(info, -relativeTransform.position); if(collides) { Tetrahedron& result = collides.value(); physicsMeasure.mark(PhysicsProcess::EPA); Vec3f intersection; Vec3f exitVector; if(!std::isfinite(result.A.p.x) || !std::isfinite(result.A.p.y) || !std::isfinite(result.A.p.z)) { intersection = Vec3f(0.0f, 0.0f, 0.0f); float minOfScaleFirst = float(std::min(scaleFirst[0], std::min(scaleFirst[1], scaleFirst[2]))); float minOfScaleSecond = float(std::min(scaleSecond[0], std::min(scaleSecond[1], scaleSecond[2]))); exitVector = Vec3f(std::min(minOfScaleFirst, minOfScaleSecond), 0.0f, 0.0f); return Intersection(intersection, exitVector); } catchable_assert(isVecValid(result.A.p)); catchable_assert(isVecValid(result.A.originFirst)); catchable_assert(isVecValid(result.A.originSecond)); catchable_assert(isVecValid(result.B.p)); catchable_assert(isVecValid(result.B.originFirst)); catchable_assert(isVecValid(result.B.originSecond)); catchable_assert(isVecValid(result.C.p)); catchable_assert(isVecValid(result.C.originFirst)); catchable_assert(isVecValid(result.C.originSecond)); catchable_assert(isVecValid(result.D.p)); catchable_assert(isVecValid(result.D.originFirst)); catchable_assert(isVecValid(result.D.originSecond)); bool epaResult = runEPATransformed(info, result, intersection, exitVector, buffers); catchable_assert(isVecValid(exitVector)); if(!epaResult) { return std::optional(); } else { return std::optional(Intersection(intersection, exitVector)); } } else { physicsMeasure.mark(PhysicsProcess::OTHER, PhysicsProcess::GJK_NO_COL); return std::optional(); } } }; ================================================ FILE: Physics3D/geometry/intersection.h ================================================ #pragma once #include #include "../math/linalg/vec.h" #include "../math/cframe.h" #include "genericCollidable.h" namespace P3D { class Shape; class Polyhedron; struct Intersection { // Local to first Vec3 intersection; // Local to first Vec3 exitVector; Intersection(const Vec3& intersection, const Vec3& exitVector) : intersection(intersection), exitVector(exitVector) {} }; std::optional intersectsTransformed(const Shape& first, const Shape& second, const CFrame& relativeTransform); std::optional intersectsTransformed(const GenericCollidable& first, const GenericCollidable& second, const CFrame& relativeTransform, const DiagonalMat3& scaleFirst, const DiagonalMat3& scaleSecond); }; ================================================ FILE: Physics3D/geometry/polyhedron.cpp ================================================ #include "polyhedron.h" #include "../math/linalg/vec.h" #include "../math/utils.h" #include "../misc/debug.h" #include "../misc/validityHelper.h" namespace P3D { Polyhedron::Polyhedron(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount) : TriangleMesh(vertexCount, triangleCount, vertices, triangles) { assert(isValid(*this)); } Polyhedron::Polyhedron(const TriangleMesh& mesh) : TriangleMesh(mesh) { assert(isValid(*this)); } Polyhedron::Polyhedron(TriangleMesh&& mesh) noexcept : TriangleMesh(std::move(mesh)) { assert(isValid(*this)); } Polyhedron::Polyhedron(const MeshPrototype& mesh) : TriangleMesh(mesh) { assert(isValid(*this)); } Polyhedron::Polyhedron(MeshPrototype&& mesh) noexcept : TriangleMesh(std::move(mesh)) { assert(isValid(*this)); } Polyhedron Polyhedron::translated(Vec3f offset) const { return Polyhedron(TriangleMesh::translated(offset)); } Polyhedron Polyhedron::rotated(Rotationf rotation) const { return Polyhedron(TriangleMesh::rotated(rotation)); } Polyhedron Polyhedron::localToGlobal(CFramef frame) const { return Polyhedron(TriangleMesh::localToGlobal(frame)); } Polyhedron Polyhedron::globalToLocal(CFramef frame) const { return Polyhedron(TriangleMesh::globalToLocal(frame)); } Polyhedron Polyhedron::scaled(float scaleX, float scaleY, float scaleZ) const { return Polyhedron(TriangleMesh::scaled(scaleX, scaleY, scaleZ)); } Polyhedron Polyhedron::scaled(DiagonalMat3f scale) const { return Polyhedron(TriangleMesh::scaled(scale)); } Polyhedron Polyhedron::translatedAndScaled(Vec3f translation, DiagonalMat3f scale) const { return Polyhedron(TriangleMesh::translatedAndScaled(translation, scale)); } //TODO parallelize bool Polyhedron::containsPoint(Vec3f point) const { Vec3f ray(1, 0, 0); bool isExiting = false; double bestD = std::numeric_limits::infinity(); for(const Triangle& tri : iterTriangles()) { RayIntersection r = rayTriangleIntersection(point, ray, this->getVertex(tri.firstIndex), this->getVertex(tri.secondIndex), this->getVertex(tri.thirdIndex)); if(r.d >= 0 && r.lineIntersectsTriangle()) { if(r.d < bestD) { bestD = r.d; isExiting = (getNormalVecOfTriangle(tri) * ray >= 0); } } } return isExiting; } double Polyhedron::getVolume() const { double total = 0; for(Triangle triangle : iterTriangles()) { Vec3 v0 = this->getVertex(triangle.firstIndex); Vec3 v1 = this->getVertex(triangle.secondIndex); Vec3 v2 = this->getVertex(triangle.thirdIndex); double D1x = v1.x - v0.x; double D1y = v1.y - v0.y; double D2x = v2.x - v0.x; double D2y = v2.y - v0.y; double nz = D1x * D2y - D1y * D2x; total += nz * (v0.z + v1.z + v2.z); } return total / 6; } Vec3 Polyhedron::getCenterOfMass() const { Vec3 total(0, 0, 0); for(Triangle triangle : iterTriangles()) { Vec3 v0 = this->getVertex(triangle.firstIndex); Vec3 v1 = this->getVertex(triangle.secondIndex); Vec3 v2 = this->getVertex(triangle.thirdIndex); Vec3 normalVec = (v1 - v0) % (v2 - v0); Vec3 vFactor = elementWiseSquare(v0) + elementWiseSquare(v1) + elementWiseSquare(v2) + elementWiseMul(v0, v1) + elementWiseMul(v1, v2) + elementWiseMul(v2, v0); total += elementWiseMul(normalVec, vFactor); } return total / (24 * getVolume()); } /* The total inertial matrix is given by the integral over the volume of the shape of the following matrix: [[ [y^2+z^2, xy, xz], [xy, x^2+z^2, yz], [xz, yz, x^2+y^2] ]] This has been reworked to a surface integral resulting in the given formulae This function returns an intermediary step which can easily be converted to scaled versions of the inertial matrix */ ScalableInertialMatrix Polyhedron::getScalableInertia(const CFrame& reference) const { Vec3 totalDiagElementParts(0, 0, 0); Vec3 totalOffDiag(0, 0, 0); for(Triangle triangle : iterTriangles()) { Vec3 v0 = reference.globalToLocal(Vec3(this->getVertex(triangle.firstIndex))); Vec3 v1 = reference.globalToLocal(Vec3(this->getVertex(triangle.secondIndex))); Vec3 v2 = reference.globalToLocal(Vec3(this->getVertex(triangle.thirdIndex))); Vec3 normalVec = (v1 - v0) % (v2 - v0); // scales x: sy*sz, y: sx*sz z: sx*sy // Diagonal Elements // sx*sx*sx, sy*sy*sy, sz*sz*sz Vec3 squaredIntegral = elementWiseCube(v0) + elementWiseCube(v1) + elementWiseCube(v2) + elementWiseMul(elementWiseSquare(v0), v1 + v2) + elementWiseMul(elementWiseSquare(v1), v0 + v2) + elementWiseMul(elementWiseSquare(v2), v0 + v1) + elementWiseMul(elementWiseMul(v0, v1), v2); Vec3 diagonalElementParts = elementWiseMul(normalVec, squaredIntegral); // (sx^3)*sy*sz, sx*(sy^3)*sz, sx*sy*(sz^3) totalDiagElementParts += diagonalElementParts; //total[0][0] += diagonalElementParts.y + diagonalElementParts.z; // sx*sy*sz*(sy^2+sz^2) //total[1][1] += diagonalElementParts.z + diagonalElementParts.x; // sx*sy*sz*(sz^2+sx^2) //total[2][2] += diagonalElementParts.x + diagonalElementParts.y; // sx*sy*sz*(sx^2+sy^2) // Other Elements double selfProducts = v0.x * v0.y * v0.z + v1.x * v1.y * v1.z + v2.x * v2.y * v2.z; double twoSames = v0.x * v0.y * v1.z + v0.x * v1.y * v0.z + v0.x * v1.y * v1.z + v0.x * v0.y * v2.z + v0.x * v2.y * v0.z + v0.x * v2.y * v2.z + v1.x * v0.y * v0.z + v1.x * v1.y * v0.z + v1.x * v0.y * v1.z + v1.x * v1.y * v2.z + v1.x * v2.y * v1.z + v1.x * v2.y * v2.z + v2.x * v0.y * v0.z + v2.x * v1.y * v2.z + v2.x * v0.y * v2.z + v2.x * v1.y * v1.z + v2.x * v2.y * v0.z + v2.x * v2.y * v1.z; double allDifferents = v0.x * v1.y * v2.z + v0.x * v2.y * v1.z + v1.x * v0.y * v2.z + v1.x * v2.y * v0.z + v2.x * v0.y * v1.z + v2.x * v1.y * v0.z; double xyzIntegral = -(3.0 * selfProducts + twoSames + 0.5 * allDifferents); // scales linearly by sx*sy*sz totalOffDiag += normalVec * xyzIntegral; //total[1][0] += dFactor.z * xyzIntegral; // sx*sy*sz* sx*sy //total[2][0] += dFactor.y * xyzIntegral; // sx*sy*sz* sx*sz //total[2][1] += dFactor.x * xyzIntegral; // sx*sy*sz* sz*sy } return ScalableInertialMatrix(totalDiagElementParts * (1.0 / 60.0), totalOffDiag * (1.0 / 60.0)); } ScalableInertialMatrix Polyhedron::getScalableInertiaAroundCenterOfMass() const { return getScalableInertia(CFrame(getCenterOfMass())); } /* The total inertial matrix is given by the integral over the volume of the shape of the following matrix: [[ [y^2+z^2, xy, xz], [xy, x^2+z^2, yz], [xz, yz, x^2+y^2] ]] This has been reworked to a surface integral resulting in the given formulae */ SymmetricMat3 Polyhedron::getInertia(const CFrame& reference) const { return getScalableInertia(reference).toMatrix(); } SymmetricMat3 Polyhedron::getInertiaAroundCenterOfMass() const { return getScalableInertiaAroundCenterOfMass().toMatrix(); } }; ================================================ FILE: Physics3D/geometry/polyhedron.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" #include "../math/rotation.h" #include "../math/cframe.h" #include "scalableInertialMatrix.h" #include "triangleMesh.h" namespace P3D { class Polyhedron : public TriangleMesh { public: Polyhedron() : TriangleMesh() {} explicit Polyhedron(const TriangleMesh& mesh); explicit Polyhedron(TriangleMesh&& mesh) noexcept; explicit Polyhedron(const MeshPrototype& mesh); explicit Polyhedron(MeshPrototype&& mesh) noexcept; Polyhedron(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount); Polyhedron translated(Vec3f offset) const; Polyhedron rotated(Rotationf rotation) const; Polyhedron localToGlobal(CFramef frame) const; Polyhedron globalToLocal(CFramef frame) const; Polyhedron scaled(float scaleX, float scaleY, float scaleZ) const; Polyhedron scaled(DiagonalMat3f scale) const; Polyhedron translatedAndScaled(Vec3f translation, DiagonalMat3f scale) const; bool containsPoint(Vec3f point) const; double getVolume() const; Vec3 getCenterOfMass() const; SymmetricMat3 getInertiaAroundCenterOfMass() const; SymmetricMat3 getInertia(const CFrame& reference) const; ScalableInertialMatrix getScalableInertia(const CFrame& reference) const; ScalableInertialMatrix getScalableInertiaAroundCenterOfMass() const; }; }; ================================================ FILE: Physics3D/geometry/scalableInertialMatrix.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" namespace P3D { class ScalableInertialMatrix { // not the exact diagonal components, they must still be added like: m[0][0] = diagonal.y + diagonal.z Vec3 diagonal; // scales (sx^3)*sy*sz, sx*(sy^3)*sz, sx*sy*(sz^3) Vec3 offDiagonal; // scales z = sx*sy*sz* sx*sy // scales y = sx*sy*sz* sx*sz // scales x = sx*sy*sz* sz*sy public: ScalableInertialMatrix(Vec3 diagonalConstructors, Vec3 offDiagonal) : diagonal(diagonalConstructors), offDiagonal(offDiagonal) {} SymmetricMat3 toMatrix() const { return SymmetricMat3{ diagonal.y + diagonal.z, offDiagonal.z, diagonal.x + diagonal.z, offDiagonal.y, offDiagonal.x, diagonal.x + diagonal.y }; } SymmetricMat3 toMatrix(double scaleX, double scaleY, double scaleZ) const { double xyz = scaleX * scaleY * scaleZ; Vec3 scaledDC = xyz * elementWiseMul(diagonal, Vec3(scaleX * scaleX, scaleY * scaleY, scaleZ * scaleZ)); Vec3 scaledOD = xyz * elementWiseMul(offDiagonal, Vec3(scaleY * scaleZ, scaleX * scaleZ, scaleX * scaleY)); return SymmetricMat3{ scaledDC.y + scaledDC.z, scaledOD.z, scaledDC.x + scaledDC.z, scaledOD.y, scaledOD.x, scaledDC.x + scaledDC.y }; } SymmetricMat3 toMatrix(DiagonalMat3 scale) const { return toMatrix(scale[0], scale[1], scale[2]); } }; }; ================================================ FILE: Physics3D/geometry/shape.cpp ================================================ #include "shape.h" #include "polyhedron.h" #include "shapeClass.h" namespace P3D { Shape::Shape() : baseShape(nullptr), scale{1,1,1} {} Shape::Shape(intrusive_ptr baseShape, DiagonalMat3 scale) : baseShape(std::move(baseShape)), scale(scale) { assert(this->baseShape); } Shape::Shape(intrusive_ptr baseShape) : baseShape(std::move(baseShape)), scale{1,1,1} { assert(this->baseShape); } Shape::Shape(intrusive_ptr baseShape, double width, double height, double depth) : baseShape(std::move(baseShape)), scale{width / 2, height / 2, depth / 2} { assert(this->baseShape); } // defined here so that ShapeClass destructor does not need to be called externally Shape::~Shape() {} Shape::Shape(Shape&&) = default; Shape& Shape::operator=(Shape&&) = default; Shape::Shape(const Shape&) = default; Shape& Shape::operator=(const Shape&) = default; bool Shape::containsPoint(Vec3 point) const { return baseShape->containsPoint(~scale * point); } double Shape::getIntersectionDistance(Vec3 origin, Vec3 direction) const { return baseShape->getIntersectionDistance(~scale * origin, ~scale * direction); } double Shape::getVolume() const { return baseShape->volume * det(scale); } Shape Shape::scaled(const DiagonalMat3& scale) const { return Shape(baseShape, this->scale * scale); } Shape Shape::scaled(double scaleX, double scaleY, double scaleZ) const { return Shape(baseShape, scale * DiagonalMat3{scaleX, scaleY, scaleZ}); } BoundingBox Shape::getBounds() const { return BoundingBox{-scale[0], -scale[1], -scale[2], scale[0], scale[1], scale[2]}; } BoundingBox Shape::getBounds(const Rotation& referenceFrame) const { return baseShape->getBounds(referenceFrame, scale); } Vec3 Shape::getCenterOfMass() const { return scale * baseShape->centerOfMass; } SymmetricMat3 Shape::getInertia() const { return baseShape->inertia.toMatrix(scale); } double Shape::getMaxRadius() const { return baseShape->getScaledMaxRadius(scale); } double Shape::getMaxRadiusSq() const { return baseShape->getScaledMaxRadiusSq(scale); } Vec3f Shape::furthestInDirection(const Vec3f& direction) const { return baseShape->furthestInDirection(direction); } Polyhedron Shape::asPolyhedron() const { return baseShape->asPolyhedron().scaled(scale); } void Shape::setWidth(double newWidth) { baseShape->setScaleX(newWidth / 2, scale); } void Shape::setHeight(double newHeight) { baseShape->setScaleY(newHeight / 2, scale); } void Shape::setDepth(double newDepth) { baseShape->setScaleZ(newDepth / 2, scale); } }; ================================================ FILE: Physics3D/geometry/shape.h ================================================ #pragma once #include "../math/boundingBox.h" #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" #include "../math/cframe.h" #include "../math/transform.h" #include "../datastructures/smartPointers.h" namespace P3D { class ShapeClass; class Polyhedron; class Shape { public: intrusive_ptr baseShape; DiagonalMat3 scale; Shape(); Shape(intrusive_ptr baseShape); Shape(intrusive_ptr baseShape, DiagonalMat3 scale); Shape(intrusive_ptr baseShape, double width, double height, double depth); ~Shape(); Shape(Shape&&); Shape& operator=(Shape&&); Shape(const Shape&); Shape& operator=(const Shape&); [[nodiscard]] bool containsPoint(Vec3 point) const; [[nodiscard]] double getIntersectionDistance(Vec3 origin, Vec3 direction) const; [[nodiscard]] double getVolume() const; [[nodiscard]] double getWidth() const { return scale[0] * 2; } [[nodiscard]] double getHeight() const { return scale[1] * 2; } [[nodiscard]] double getDepth() const { return scale[2] * 2; } void setWidth(double newWidth); void setHeight(double newHeight); void setDepth(double newDepth); [[nodiscard]] Shape scaled(const DiagonalMat3& scale) const; [[nodiscard]] Shape scaled(double scaleX, double scaleY, double scaleZ) const; [[nodiscard]] BoundingBox getBounds() const; [[nodiscard]] BoundingBox getBounds(const Rotation& referenceFrame) const; [[nodiscard]] Vec3 getCenterOfMass() const; // defined around the object's Center Of Mass [[nodiscard]] SymmetricMat3 getInertia() const; [[nodiscard]] double getMaxRadius() const; [[nodiscard]] double getMaxRadiusSq() const; [[nodiscard]] Vec3f furthestInDirection(const Vec3f& direction) const; [[nodiscard]] Polyhedron asPolyhedron() const; }; }; ================================================ FILE: Physics3D/geometry/shapeBuilder.cpp ================================================ #include "shapeBuilder.h" namespace P3D { ShapeBuilder::ShapeBuilder(Vec3f* vertBuf, Triangle* triangleBuf, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf) : vertexBuf(vertBuf), triangleBuf(triangleBuf), vertexCount(vertexCount), triangleCount(triangleCount), neighborBuf(neighborBuf) { fillNeighborBuf(triangleBuf, triangleCount, neighborBuf); } void ShapeBuilder::addPoint(Vec3f point, int oldTriangleIndex) { /* To add point: Add point to vertex buffer Remove triangle that point replaces Add 3 new triangles connecting point to oldTriangle's neighbors - ABP - BCP - CAP With ABC the old triangle and P the new point update neighbors */ int newPointIndex = vertexCount; // add extra point vertexBuf[newPointIndex] = point; int t0Index = oldTriangleIndex; int t1Index = triangleCount; int t2Index = triangleCount + 1; Triangle oldT = triangleBuf[oldTriangleIndex]; TriangleNeighbors oldNeighbors = neighborBuf[oldTriangleIndex]; triangleBuf[t0Index] = Triangle{oldT[0], oldT[1], newPointIndex}; triangleBuf[t1Index] = Triangle{oldT[1], oldT[2], newPointIndex}; triangleBuf[t2Index] = Triangle{oldT[2], oldT[0], newPointIndex}; vertexCount++; triangleCount += 2; neighborBuf[t0Index].BC_Neighbor = t1Index; neighborBuf[t1Index].CA_Neighbor = t0Index; neighborBuf[t1Index].BC_Neighbor = t2Index; neighborBuf[t2Index].CA_Neighbor = t1Index; neighborBuf[t2Index].BC_Neighbor = t0Index; neighborBuf[t0Index].CA_Neighbor = t2Index; neighborBuf[t0Index].AB_Neighbor = oldNeighbors.AB_Neighbor; neighborBuf[t1Index].AB_Neighbor = oldNeighbors.BC_Neighbor; neighborBuf[t2Index].AB_Neighbor = oldNeighbors.CA_Neighbor; // update neighbors next to replaced triangle neighborBuf[oldNeighbors.AB_Neighbor].replaceNeighbor(oldTriangleIndex, t0Index); neighborBuf[oldNeighbors.BC_Neighbor].replaceNeighbor(oldTriangleIndex, t1Index); neighborBuf[oldNeighbors.CA_Neighbor].replaceNeighbor(oldTriangleIndex, t2Index); } Polyhedron ShapeBuilder::toPolyhedron() const { return Polyhedron(vertexBuf, triangleBuf, vertexCount, triangleCount); } IndexedShape ShapeBuilder::toIndexedShape() const { return IndexedShape(vertexBuf, triangleBuf, vertexCount, triangleCount, neighborBuf); } }; ================================================ FILE: Physics3D/geometry/shapeBuilder.h ================================================ #pragma once #include "shape.h" #include "indexedShape.h" namespace P3D { class ShapeBuilder { Vec3f* vertexBuf; Triangle* triangleBuf; int vertexCount; int triangleCount; TriangleNeighbors* neighborBuf; public: ShapeBuilder(Vec3f* vertBuf, Triangle* triangleBuf, int vertexCount, int triangleCount, TriangleNeighbors* neighborBuf); void addPoint(Vec3f point, int replacingTriangleIndex); Polyhedron toPolyhedron() const; IndexedShape toIndexedShape() const; }; }; ================================================ FILE: Physics3D/geometry/shapeClass.cpp ================================================ #include "shapeClass.h" namespace P3D { ShapeClass::ShapeClass(double volume, Vec3 centerOfMass, ScalableInertialMatrix inertia, std::size_t intersectionClassID) : volume(volume), centerOfMass(centerOfMass), inertia(inertia), intersectionClassID(intersectionClassID), refCount(0) {} ShapeClass::~ShapeClass() {} double ShapeClass::getScaledMaxRadius(DiagonalMat3 scale) const { return sqrt(this->getScaledMaxRadiusSq(scale)); } void ShapeClass::setScaleX(double newX, DiagonalMat3& scale) const { scale[0] = newX; } void ShapeClass::setScaleY(double newY, DiagonalMat3& scale) const { scale[1] = newY; } void ShapeClass::setScaleZ(double newZ, DiagonalMat3& scale) const { scale[2] = newZ; } }; ================================================ FILE: Physics3D/geometry/shapeClass.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" #include "../math/rotation.h" #include "../math/boundingBox.h" #include "genericCollidable.h" #include "scalableInertialMatrix.h" #include namespace P3D { class Polyhedron; // a ShapeClass is defined as a shape with dimentions -1..1 in all axes. All functions work on scaled versions of the shape. // examples include: // Sphere of radius=1 // Cylinder of radius=1, height=2 // Cube of 2x2x2 // Custom polygon bounded by a 2x2x2 box class ShapeClass : public GenericCollidable { public: // For intrusive_ptr. MUST be named refCount, see datastructures/smartPointers.h mutable std::atomic refCount; std::size_t intersectionClassID; double volume; Vec3 centerOfMass; ScalableInertialMatrix inertia; ShapeClass(double volume, Vec3 centerOfMass, ScalableInertialMatrix inertia, std::size_t intersectionClassID); virtual ~ShapeClass(); virtual bool containsPoint(Vec3 point) const = 0; virtual double getIntersectionDistance(Vec3 origin, Vec3 direction) const = 0; virtual BoundingBox getBounds(const Rotation& referenceFrame, const DiagonalMat3& scale) const = 0; virtual double getScaledMaxRadius(DiagonalMat3 scale) const; virtual double getScaledMaxRadiusSq(DiagonalMat3 scale) const = 0; /* This must return a valid Vec3f on the surface of the shape, even for 0,0,0 Does not need to take account of NaN or infinities in the input argument */ virtual Vec3f furthestInDirection(const Vec3f& direction) const = 0; virtual Polyhedron asPolyhedron() const = 0; // these functions determine the relations between the axes, for example, for Sphere, all axes must be equal virtual void setScaleX(double newX, DiagonalMat3& scale) const; virtual void setScaleY(double newY, DiagonalMat3& scale) const; virtual void setScaleZ(double newZ, DiagonalMat3& scale) const; }; }; ================================================ FILE: Physics3D/geometry/shapeCreation.cpp ================================================ #include "shapeCreation.h" #include "shapeClass.h" #include "polyhedron.h" #include "builtinShapeClasses.h" #include "../misc/cpuid.h" #include "../datastructures/smartPointers.h" namespace P3D { Shape boxShape(double width, double height, double depth) { return Shape(intrusive_ptr(&CubeClass::instance), width, height, depth); } Shape wedgeShape(double width, double height, double depth) { return Shape(intrusive_ptr(&WedgeClass::instance), width, height, depth); } Shape cornerShape(double width, double height, double depth) { return Shape(intrusive_ptr(&CornerClass::instance), width, height, depth); } Shape sphereShape(double radius) { return Shape(intrusive_ptr(&SphereClass::instance), radius * 2, radius * 2, radius * 2); } Shape cylinderShape(double radius, double height) { return Shape(intrusive_ptr(&CylinderClass::instance), radius * 2, radius * 2, height); } Shape polyhedronShape(const Polyhedron& poly) { BoundingBox bounds = poly.getBounds(); Vec3 center = bounds.getCenter(); DiagonalMat3 scale{2 / bounds.getWidth(), 2 / bounds.getHeight(), 2 / bounds.getDepth()}; PolyhedronShapeClass* shapeClass; if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { shapeClass = new PolyhedronShapeClassAVX(poly.translatedAndScaled(-center, scale)); } else if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE4_1)) { shapeClass = new PolyhedronShapeClassSSE4(poly.translatedAndScaled(-center, scale)); } else { shapeClass = new PolyhedronShapeClassSSE(poly.translatedAndScaled(-center, scale)); } } else { shapeClass = new PolyhedronShapeClassFallback(poly.translatedAndScaled(-center, scale)); } return Shape(intrusive_ptr(shapeClass), bounds.getWidth(), bounds.getHeight(), bounds.getDepth()); } }; ================================================ FILE: Physics3D/geometry/shapeCreation.h ================================================ #pragma once #include "shape.h" namespace P3D { class Polyhedron; Shape boxShape(double width, double height, double depth); Shape wedgeShape(double width, double height, double depth); Shape cornerShape(double width, double height, double depth); Shape sphereShape(double radius); Shape cylinderShape(double radius, double height); Shape polyhedronShape(const Polyhedron& poly); } ================================================ FILE: Physics3D/geometry/shapeLibrary.cpp ================================================ #include "shapeLibrary.h" #include #include #include "../datastructures/buffers.h" #include "../math/linalg/trigonometry.h" #include "../math/constants.h" namespace P3D::ShapeLibrary { Vec3f octahedronVertices[]{ Vec3f(-1.0, 0.0, 0.0), Vec3f(1.0, 0.0, 0.0), Vec3f(0.0, -1.0, 0.0), Vec3f(0.0, 1.0, 0.0), Vec3f(0.0, 0.0, -1.0), Vec3f(0.0, 0.0, 1.0), }; Triangle octahedronTriangles[]{ {1,2,4}, {1,3,5}, {1,4,3}, {1,5,2}, {0,2,5}, {0,3,4}, {0,4,2}, {0,5,3} }; Vec3f icosahedronVertices[]{ Vec3f(0, 0.5, g() / 2), Vec3f(0, 0.5, -g() / 2), Vec3f(0, -0.5, -g() / 2), Vec3f(0, -0.5, g() / 2), Vec3f(0.5, g() / 2, 0), Vec3f(0.5, -g() / 2, 0), Vec3f(-0.5, -g() / 2, 0), Vec3f(-0.5, g() / 2, 0), Vec3f(g() / 2, 0, 0.5), Vec3f(-g() / 2, 0, 0.5), Vec3f(-g() / 2, 0, -0.5), Vec3f(g() / 2, 0, -0.5), }; Triangle icosahedronTriangles[]{ {0 , 3 , 8}, {0 , 8, 4 }, {0, 4 , 7}, {0, 7 , 9}, {0, 9, 3 }, {2 , 10, 1}, {2 , 1, 11}, {2, 11, 5}, {2, 5 , 6}, {2, 6, 10}, {8 , 3 , 5}, {8 , 5, 11}, {8, 11, 4}, {4, 11, 1}, {4, 1, 7 }, {10, 7 , 1}, {10, 9, 7 }, {9, 10, 6}, {9, 6 , 3}, {3, 6, 5 }, }; Vec3f tetrahedronVertices[]{ Vec3f(1.0f, 1.0f, 1.0f), Vec3f(1.0f, -1.0f, -1.0f), Vec3f(-1.0f, -1.0f, 1.0f), Vec3f(-1.0f, 1.0f, -1.0f) }; Triangle tetrahedronTriangles[]{ {0, 2, 1}, {0, 3, 2}, {0, 1, 3}, {1, 2, 3} }; Vec3f trianglePyramidVertices[]{ Vec3f(0.0f, 0.0f, 0.0f), Vec3f(1.0f, 0.0f, 0.0f), Vec3f(0.0f, 1.0f, 0.0f), Vec3f(0.0f, 0.0f, 1.0f) }; Vec3f houseVertices[]{ Vec3f(-0.5f, 0.0f, -0.5f), Vec3f(-0.5f, 0.0f, 0.5f), Vec3f(0.5f, 0.0f, 0.5f), Vec3f(0.5f, 0.0f, -0.5f), Vec3f(-0.5f, 1.0f, -0.5f), Vec3f(-0.5f, 1.0f, 0.5f), Vec3f(0.5f, 1.0f, 0.5f), Vec3f(0.5f, 1.0f, -0.5f), Vec3f(0.0f, 1.5f, -5.0f), Vec3f(0.0f, 1.5f, 0.5f) }; Triangle houseTriangles[]{ {0, 1, 4}, {5, 4, 1}, // left {0, 2, 1}, {0, 3, 2}, // bottom {0, 4, 3}, {3, 4, 7}, // front {3, 7, 2}, {2, 7, 6}, // right {1, 2, 5}, {5, 2, 6}, // back {7, 4, 8}, {5, 6, 9}, // roof {4, 5, 9}, {4, 9, 8}, {7, 8, 9}, {7, 9, 6} //roof2 }; Vec3f cornerVertices[]{ Vec3f(-1.0f, -1.0f, -1.0f), Vec3f(-1.0f, -1.0f, 1.0f), Vec3f(-1.0f, 1.0f, -1.0f), Vec3f(1.0f, -1.0f, -1.0f) }; Triangle cornerTriangles[]{ {0, 1, 2}, {2, 3, 0}, {0, 3, 1}, {2, 1, 3} }; Vec3f wedgeVertices[]{ Vec3f(-1.0f, -1.0f, -1.0f), Vec3f(-1.0f, -1.0f, 1.0f), Vec3f(1.0f, -1.0f, 1.0f), Vec3f(1.0f, -1.0f, -1.0f), Vec3f(-1.0f, 1.0f, -1.0f), Vec3f(-1.0f, 1.0f, 1.0f) }; Triangle wedgeTriangles[]{ {0, 2, 1}, {0, 3, 2}, // bottom face {0, 5, 4}, {0, 1, 5}, // back face {0, 4, 3}, // left face {1, 2, 5}, // right face {2, 3, 4}, {2, 4, 5} // diagonal face }; Triangle boxTriangles[12] = { {1,0,2},{3,2,0}, // BOTTOM {1,5,0},{4,0,5}, // FRONT {1,2,5},{6,5,2}, // RIGHT {6,2,7},{3,7,2}, // BACK {3,0,7},{4,7,0}, // LEFT {4,5,7},{6,7,5}, // TOP }; const int wedgeVertexCount = 6; const int cornerVertexCount = 4; const Polyhedron tetrahedron(tetrahedronVertices, tetrahedronTriangles, 4, 4); const Polyhedron octahedron(octahedronVertices, octahedronTriangles, 6, 8); const Polyhedron icosahedron(icosahedronVertices, icosahedronTriangles, 12, 20); const Polyhedron trianglePyramid(trianglePyramidVertices, tetrahedronTriangles, 4, 4); const Polyhedron house(houseVertices, houseTriangles, 10, 16); const Polyhedron wedge{wedgeVertices, wedgeTriangles, wedgeVertexCount, 8}; const Polyhedron corner{cornerVertices, cornerTriangles, cornerVertexCount, 4}; Polyhedron createBox(float width, float height, float depth) { float dx = float(width / 2.0); float dy = float(height / 2.0); float dz = float(depth / 2.0); Vec3f vertBuf[8]{ Vec3f(-dx, -dy, -dz), Vec3f(+dx, -dy, -dz), Vec3f(+dx, +dy, -dz), Vec3f(-dx, +dy, -dz), Vec3f(-dx, -dy, +dz), Vec3f(+dx, -dy, +dz), Vec3f(+dx, +dy, +dz), Vec3f(-dx, +dy, +dz) }; return Polyhedron(vertBuf, boxTriangles, 8, 12); } Polyhedron createCube(float side) { return createBox(side, side, side); } static void createTriangleRing(int ring1Start, int ring2Start, int size, EditableMesh& mesh, int offset) { for(int i = 0; i < size - 1; i++) { mesh.setTriangle(offset + i * 2, ring1Start + i, ring1Start + i + 1, ring2Start + i); mesh.setTriangle(offset + i * 2 + 1, ring1Start + i + 1, ring2Start + i + 1, ring2Start + i); } int last = size - 1; mesh.setTriangle(offset + last * 2, ring1Start + last, ring1Start, ring2Start + last); mesh.setTriangle(offset + last * 2 + 1, ring1Start, ring2Start, ring2Start + last); } static void createTriangleRingReverse(int ring1Start, int ring2Start, int size, EditableMesh& mesh, int offset) { for(int i = 0; i < size - 1; i++) { mesh.setTriangle(offset + i * 2, ring1Start + i + 1, ring1Start + i, ring2Start + i); mesh.setTriangle(offset + i * 2 + 1, ring1Start + i + 1, ring2Start + i, ring2Start + i + 1); } int last = size - 1; mesh.setTriangle(offset + last * 2, ring1Start, ring1Start + last, ring2Start + last); mesh.setTriangle(offset + last * 2 + 1, ring1Start, ring2Start + last, ring2Start); } static void createTriangleFan(int topIndex, int startFan, int size, EditableMesh& mesh, int offset) { for(int i = 0; i < size - 1; i++) { mesh.setTriangle(offset + i, topIndex, startFan + i, startFan + i + 1); } } static void createTriangleFanReverse(int topIndex, int startFan, int size, EditableMesh& mesh, int offset) { for(int i = 0; i < size - 1; i++) { mesh.setTriangle(offset + i, topIndex, startFan + i + 1, startFan + i); } } static void createTriangleCone(int topIndex, int startFan, int size, EditableMesh& mesh, int offset) { createTriangleFan(topIndex, startFan, size, mesh, offset); mesh.setTriangle(offset + size - 1, topIndex, startFan + size - 1, startFan); } static void createTriangleConeReverse(int topIndex, int startFan, int size, EditableMesh& mesh, int offset) { createTriangleFanReverse(topIndex, startFan, size, mesh, offset); mesh.setTriangle(offset + size - 1, topIndex, startFan, startFan + size - 1); } static void createFlatSurface(int startFan, int size, EditableMesh& mesh, int offset) { createTriangleFan(startFan, startFan + 1, size - 1, mesh, offset); } static void createFlatSurfaceReverse(int startFan, int size, EditableMesh& mesh, int offset) { createTriangleFanReverse(startFan, startFan + 1, size - 1, mesh, offset); } Polyhedron createPrism(int sides, float radius, float height) { EditableMesh result(sides * 2, (sides - 1) * 4); // vertices for(int i = 0; i < sides; i++) { float angle = i * pi() * 2 / sides; float h = height / 2; float cr = cos(angle) * radius; float sr = sin(angle) * radius; result.setVertex(i * 2, cr, sr, h); result.setVertex(i * 2 + 1, cr, sr, -h); } // triangles // sides for(int i = 0; i < sides; i++) { int botLeft = i * 2; int botRight = ((i + 1) % sides) * 2; result.setTriangle(i * 2, botLeft, botLeft + 1, botRight); // botLeft, botRight, topLeft result.setTriangle(i * 2 + 1, botRight + 1, botRight, botLeft + 1); // topRight, topLeft, botRight } // top and bottom for(int i = 0; i < sides - 2; i++) { // common corner is i=0 result.setTriangle(i + sides * 2, 0, (i + 1) * 2, (i + 2) * 2); result.setTriangle(i + (sides - 2) + sides * 2, 1, (i + 2) * 2 + 1, (i + 1) * 2 + 1); } return Polyhedron(std::move(result)); } Polyhedron createPointyPrism(int sides, float radius, float height, float topOffset, float bottomOffset) { EditableMesh result(sides * 2 + 2, sides * 4); // vertices for(int i = 0; i < sides; i++) { float angle = i * pi() * 2 / sides; result.setVertex(i * 2, cos(angle) * radius, -height / 2, sin(angle) * radius); result.setVertex(i * 2 + 1, cos(angle) * radius, height / 2, sin(angle) * radius); } int bottomIndex = sides * 2; int topIndex = sides * 2 + 1; result.setVertex(bottomIndex, 0, -height / 2 - bottomOffset, 0); result.setVertex(topIndex, 0, height / 2 + topOffset, 0); // triangles // sides for(int i = 0; i < sides; i++) { int botLeft = i * 2; int botRight = ((i + 1) % sides) * 2; result.setTriangle(i * 2, botLeft, botLeft + 1, botRight); // botLeft, botRight, topLeft result.setTriangle(i * 2 + 1, botRight + 1, botRight, botLeft + 1); // topRight, topLeft, botRight } // top and bottom for(int i = 0; i < sides; i++) { // common corner is i=0 result.setTriangle(i + sides * 2, bottomIndex, i * 2, ((i + 1) % sides) * 2); result.setTriangle(i + sides + sides * 2, topIndex, ((i + 1) % sides) * 2 + 1, i * 2 + 1); } return Polyhedron(std::move(result)); } // divides every triangle into 4 smaller triangles static std::pair tesselate(Vec3f* vecBuf, Triangle* triangleBuf, int vertexCount, int triangleCount) { assert(triangleCount % 2 == 0); int newVecBufSize = vertexCount + triangleCount * 3 / 2; int newTriIndex = triangleCount * 4; int curMapIndex = vertexCount; std::map, int> newPoints; for(int i = triangleCount; i-- > 0;) { // first collect the 6 new points Triangle& curT = triangleBuf[i]; Triangle newCenterTriangle; for(int j = 0; j < 3; j++) { int a = curT[j]; int b = curT[(j + 1) % 3]; if(b > a) { std::swap(a, b); } std::pair edge(a, b); auto result = newPoints.find(edge); int index; if(result == newPoints.end()) { index = curMapIndex++; newPoints[edge] = index; vecBuf[index] = (vecBuf[a] + vecBuf[b]) * 0.5f; } else { index = newPoints[edge]; } newCenterTriangle[j] = index; } triangleBuf[--newTriIndex] = Triangle{newCenterTriangle[1], newCenterTriangle[0], curT[1]}; triangleBuf[--newTriIndex] = Triangle{newCenterTriangle[2], newCenterTriangle[1], curT[2]}; triangleBuf[--newTriIndex] = Triangle{newCenterTriangle[0], newCenterTriangle[2], curT[0]}; triangleBuf[--newTriIndex] = newCenterTriangle; } assert(curMapIndex == newVecBufSize); return std::pair(vecBuf, triangleBuf); } Polyhedron createSphere(float radius, int steps) { int vertices = 12; int triangles = 20; for(int i = 0; i < steps; i++) { vertices = vertices + triangles * 3 / 2; triangles = triangles * 4; } Polyhedron curSphere = icosahedron; Vec3f* vecBuf = new Vec3f[vertices]; Triangle* triBuf = new Triangle[triangles]; curSphere.getVertices(vecBuf); curSphere.getTriangles(triBuf); vertices = 12; triangles = 20; for(int i = 0; i < steps; i++) { tesselate(vecBuf, triBuf, vertices, triangles); vertices = vertices + triangles * 3 / 2; triangles = triangles * 4; } // size 42 x 80 // size 162 x 320 // size 642 x 1280 for(int i = 0; i < vertices; i++) { vecBuf[i] = normalize(vecBuf[i]) * radius; } Polyhedron poly(vecBuf, triBuf, vertices, triangles); //Polyhedron poly(vecBuf, triBuf, 12, 20); delete[] vecBuf; delete[] triBuf; return poly; } Polyhedron createSpikeBall(float internalRadius, float spikeRadius, int steps, int spikeSteps) { int vertices = 12; int triangles = 20; for(int i = 0; i < steps; i++) { vertices = vertices + triangles * 3 / 2; triangles = triangles * 4; } Polyhedron curSphere = icosahedron; Vec3f* vecBuf = new Vec3f[vertices]; Triangle* triBuf = new Triangle[triangles]; curSphere.getVertices(vecBuf); curSphere.getTriangles(triBuf); vertices = 12; triangles = 20; for(int i = 0; i < steps; i++) { tesselate(vecBuf, triBuf, vertices, triangles); vertices = vertices + triangles * 3 / 2; triangles = triangles * 4; } // size 42 x 80 // size 162 x 320 // size 642 x 1280 int spikeVerts = 12; int spikeTris = 20; for(int i = 0; i < spikeSteps; i++) { spikeVerts = spikeVerts + spikeTris * 3 / 2; spikeTris = spikeTris * 4; } for(int i = 0; i < spikeVerts; i++) { vecBuf[i] = normalize(vecBuf[i]) * spikeRadius; } for(int i = spikeVerts; i < vertices; i++) { vecBuf[i] = normalize(vecBuf[i]) * internalRadius; } Polyhedron poly(vecBuf, triBuf, vertices, triangles); //Polyhedron poly(vecBuf, triBuf, 12, 20); delete[] vecBuf; delete[] triBuf; return poly; } Polyhedron createTorus(float ringRadius, float radiusOfTube, int radialFidelity, int tubeFidelity) { EditableMesh result(radialFidelity * tubeFidelity, 2 * radialFidelity * tubeFidelity); for(int segment = 0; segment < radialFidelity; segment++) { float angle = (2 * pi() * segment) / radialFidelity; float s = sin(angle); float c = cos(angle); for(int partInSegment = 0; partInSegment < tubeFidelity; partInSegment++) { float tubeAngle = (2 * pi() * partInSegment) / tubeFidelity; float height = radiusOfTube * sin(tubeAngle); float radius = ringRadius + radiusOfTube * cos(tubeAngle); result.setVertex(segment * tubeFidelity + partInSegment, Vec3f(s * radius, c * radius, height)); } } // triangles for(int segment = 0; segment < radialFidelity - 1; segment++) { createTriangleRing(segment * tubeFidelity, (segment + 1) * tubeFidelity, tubeFidelity, result, 2 * segment * tubeFidelity); } createTriangleRing((radialFidelity - 1) * tubeFidelity, 0, tubeFidelity, result, 2 * (radialFidelity - 1) * tubeFidelity); return Polyhedron(std::move(result)); } Polyhedron createRevolvedShape(float startZ, Vec2f* inbetweenPoints, int inbetweenPointCount, float endZ, int segmentCount) { EditableMesh result(segmentCount * inbetweenPointCount + 2, segmentCount * 2 * (inbetweenPointCount)); result.setVertex(0, 0.0f, 0.0f, startZ); int lastVertex = segmentCount * inbetweenPointCount + 1; result.setVertex(lastVertex, 0.0f, 0.0f, endZ); for(int segmentI = 0; segmentI < segmentCount; segmentI++) { float angle = (2 * pi() * segmentI) / segmentCount; float s = sin(angle); float c = cos(angle); for(int inbetweenI = 0; inbetweenI < inbetweenPointCount; inbetweenI++) { float zValue = inbetweenPoints[inbetweenI].x; float radius = inbetweenPoints[inbetweenI].y; result.setVertex(inbetweenI * segmentCount + segmentI + 1, -s * radius, c * radius, zValue); } } // triangles createTriangleConeReverse(0, 1, segmentCount, result, 0); for(int inbetweenI = 0; inbetweenI < inbetweenPointCount - 1; inbetweenI++) { createTriangleRing(1 + segmentCount * inbetweenI, 1 + segmentCount * (inbetweenI + 1), segmentCount, result, segmentCount + 2 * segmentCount * inbetweenI); } createTriangleCone(lastVertex, 1 + segmentCount * (inbetweenPointCount - 1), segmentCount, result, segmentCount + 2 * segmentCount * (inbetweenPointCount - 1)); return Polyhedron(std::move(result)); } } ================================================ FILE: Physics3D/geometry/shapeLibrary.h ================================================ #pragma once #include "../geometry/polyhedron.h" namespace P3D::ShapeLibrary { extern const Polyhedron tetrahedron; extern const Polyhedron octahedron; extern const Polyhedron icosahedron; extern const Polyhedron trianglePyramid; extern const Polyhedron house; extern const Polyhedron wedge; extern const Polyhedron corner; extern Vec3f wedgeVertices[6]; extern Vec3f cornerVertices[4]; extern const int wedgeVertexCount; extern const int cornerVertexCount; Polyhedron createBox(float width, float height, float depth); Polyhedron createCube(float side); /* Creates a prism oriented along the Z axis with the given number of sides result.vertices will contain all the corners, grouped 2 by 2 rotating counterclockwise as seen from the top result.triangles will contain all the triangles: - sides*2 triangles for the sides - (sides - 2) triangles for the top cap - (sides - 2) triangles for the bottom cap sides must be >= 3 */ Polyhedron createPrism(int sides, float radius, float height); /* Creates a pointed prism oriented along the Y axis with the given number of sides result.vertices will contain all the corners: - vertices grouped 2 by 2 rotating counterclockwise as seen from the top - top vertex - bottom vertex result.triangles will contain all the triangles: - sides*2 triangles for the sides - sides triangles for the top point - sides triangles for the bottom point sides must be >= 3 */ Polyhedron createPointyPrism(int sides, float radius, float height, float topOffset, float bottomOffset); Polyhedron createSphere(float radius, int steps = 1); Polyhedron createSpikeBall(float internalRadius, float spikeRadius, int steps, int spikeSteps); /* Creates a torus around the z axis. The tube will have a diameter of 2xradiusOfTube ringRadius gives the radius of the centerline of the ring radialFidelity is the number of segments tubeFidelity is the number of vertices that make up each segment */ Polyhedron createTorus(float ringRadius, float radiusOfTube, int radialFidelity, int tubeFidelity); /* Creates a swept shape around the z axis, where it meets in two points on the axis. startZ denotes the starting z-coordinate, a triangle fan will be made starting from this index endZ denotes the ending z-coordinate inbetweenPoints is a list of length inbetweenPointsCount, which contains the 2D outline to be swept around. The x-value denotes the z-position alogn the axis, the y value is the radius at that point sweepFidelity denotes the number of steps of rotation. The triangles are structured as follows: [0 .. sweepFidelity-1] is the triangleFan for startZ [sweepFidelity .. sweepFidelity + 2*(inbetweenPointCount-1)*sweepFidelity-1] are the rings comprising the inbetweenPoints. Each of these rings is 2*sweepFidelity triangles [sweepFidelity + 2*(inbetweenPointCount-1)*sweepFidelity .. 2*inbetweenPointCount*sweepFidelity - 1] is the triangleFan for endZ */ Polyhedron createRevolvedShape(float startZ, Vec2f* inbetweenPoints, int inbetweenPointCount, float endZ, int sweepFidelity); }; ================================================ FILE: Physics3D/geometry/triangleMesh.cpp ================================================ #include "triangleMesh.h" #include "../misc/validityHelper.h" #include "../misc/cpuid.h" #include #include #include #include #include #include namespace P3D { #pragma region bufManagement static size_t getOffset(size_t size) { return (size + 7) & 0xFFFFFFFFFFFFFFF8; } static UniqueAlignedPointer createParallelVecBuf(size_t size) { return UniqueAlignedPointer(getOffset(size) * 3, 32); } static UniqueAlignedPointer createParallelTriangleBuf(size_t size) { return UniqueAlignedPointer(getOffset(size) * 3, 32); } template static UniqueAlignedPointer copy(const UniqueAlignedPointer& buf, size_t size) { size_t totalBufSize = getOffset(size) * 3; UniqueAlignedPointer result(totalBufSize, 32); memcpy(result.get(), buf, sizeof(T) * totalBufSize); return result; } template static void fixFinalBlock(T* buf, size_t size) { unsigned int totalValues = getOffset(size) * 3; unsigned int unusableValues_n = ((getOffset(size) * 3 - size * 3) / 3); unsigned int offset = totalValues - unusableValues_n; T* xValues = buf + offset - 8 * 2; T* yValues = buf + offset - 8; T* zValues = buf + offset; unsigned int index = 0; for(size_t i = index; i < unusableValues_n; i++) { xValues[i] = xValues[-1]; yValues[i] = yValues[-1]; zValues[i] = zValues[-1]; } } #pragma endregion #pragma region triangle bool Triangle::sharesEdgeWith(Triangle other) const { return firstIndex == other.secondIndex && secondIndex == other.firstIndex || firstIndex == other.thirdIndex && secondIndex == other.secondIndex || firstIndex == other.firstIndex && secondIndex == other.thirdIndex || secondIndex == other.secondIndex && thirdIndex == other.firstIndex || secondIndex == other.thirdIndex && thirdIndex == other.secondIndex || secondIndex == other.firstIndex && thirdIndex == other.thirdIndex || thirdIndex == other.secondIndex && firstIndex == other.firstIndex || thirdIndex == other.thirdIndex && firstIndex == other.secondIndex || thirdIndex == other.firstIndex && firstIndex == other.thirdIndex; } bool Triangle::operator==(const Triangle& other) const { return firstIndex == other.firstIndex && secondIndex == other.secondIndex && thirdIndex == other.thirdIndex || firstIndex == other.secondIndex && secondIndex == other.thirdIndex && thirdIndex == other.firstIndex || firstIndex == other.thirdIndex && secondIndex == other.firstIndex && thirdIndex == other.secondIndex; } #pragma endregion #pragma region MeshPrototype MeshPrototype::MeshPrototype(const MeshPrototype& mesh) : vertices(copy(mesh.vertices, mesh.vertexCount)), triangles(copy(mesh.triangles, mesh.triangleCount)), vertexCount(mesh.vertexCount), triangleCount(mesh.triangleCount) { } MeshPrototype& MeshPrototype::operator=(const MeshPrototype& mesh) { this->vertices = copy(mesh.vertices, mesh.vertexCount); this->triangles = copy(mesh.triangles, mesh.triangleCount); this->vertexCount = mesh.vertexCount; this->triangleCount = mesh.triangleCount; return *this; } MeshPrototype::MeshPrototype() : vertices(), triangles(), vertexCount(0), triangleCount(0) {} MeshPrototype::MeshPrototype(int vertexCount, int triangleCount) : vertices(getOffset(vertexCount) * 3, 32), triangles(getOffset(triangleCount) * 3, 32), vertexCount(vertexCount), triangleCount(triangleCount) {} MeshPrototype::MeshPrototype(int vertexCount, int triangleCount, UniqueAlignedPointer&& triangles) : vertices(getOffset(vertexCount) * 3, 32), triangles(std::move(triangles)), vertexCount(vertexCount), triangleCount(triangleCount) {} MeshPrototype::MeshPrototype(int vertexCount, int triangleCount, UniqueAlignedPointer&& vertices, UniqueAlignedPointer&& triangles) : vertices(std::move(vertices)), triangles(std::move(triangles)), vertexCount(vertexCount), triangleCount(triangleCount) {} Vec3f MeshPrototype::getVertex(int index) const { // assert(index >= 0 && index < vertexCount); size_t currect_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; return Vec3f(this->vertices[currect_index], this->vertices[currect_index + BLOCK_WIDTH], this->vertices[currect_index + BLOCK_WIDTH * 2]); } Triangle MeshPrototype::getTriangle(int index) const { assert(index >= 0 && index < triangleCount); size_t currect_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; return Triangle{triangles[currect_index], triangles[currect_index + BLOCK_WIDTH], triangles[currect_index + BLOCK_WIDTH * 2]}; } #pragma endregion #pragma region EditableMesh EditableMesh::EditableMesh(int vertexCount, int triangleCount) : MeshPrototype(vertexCount, triangleCount) {} EditableMesh::EditableMesh(int vertexCount, int triangleCount, const UniqueAlignedPointer& triangles) : MeshPrototype(vertexCount, triangleCount, copy(triangles, triangleCount)) {} EditableMesh::EditableMesh(int vertexCount, int triangleCount, UniqueAlignedPointer&& triangles) : MeshPrototype(vertexCount, triangleCount, std::move(triangles)) {} EditableMesh::EditableMesh(const MeshPrototype& mesh) : MeshPrototype(mesh) {} EditableMesh::EditableMesh(MeshPrototype&& mesh) noexcept : MeshPrototype(std::move(mesh)) {} void EditableMesh::setVertex(int index, Vec3f newVertex) { assert(index >= 0 && index < vertexCount); size_t correct_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; this->vertices[correct_index] = newVertex.x; this->vertices[correct_index + BLOCK_WIDTH] = newVertex.y; this->vertices[correct_index + 2 * BLOCK_WIDTH] = newVertex.z; } void EditableMesh::setVertex(int index, float x, float y, float z) { assert(index >= 0 && index < vertexCount); size_t correct_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; this->vertices[correct_index] = x; this->vertices[correct_index + BLOCK_WIDTH] = y; this->vertices[correct_index + 2 * BLOCK_WIDTH] = z; } void EditableMesh::setTriangle(int index, Triangle newTriangle) { assert(index >= 0 && index < triangleCount); assert(isValidTriangle(newTriangle, vertexCount)); size_t correct_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; this->triangles[correct_index] = newTriangle.firstIndex; this->triangles[correct_index + BLOCK_WIDTH] = newTriangle.secondIndex; this->triangles[correct_index + 2 * BLOCK_WIDTH] = newTriangle.thirdIndex; } void EditableMesh::setTriangle(int index, int a, int b, int c) { assert(index >= 0 && index < triangleCount); assert(isValidTriangle(Triangle{a,b,c}, vertexCount)); size_t correct_index = (index / BLOCK_WIDTH) * BLOCK_WIDTH * 2 + index; this->triangles[correct_index] = a; this->triangles[correct_index + BLOCK_WIDTH] = b; this->triangles[correct_index + 2 * BLOCK_WIDTH] = c; } #pragma endregion #pragma region TriangleMesh TriangleMesh::TriangleMesh(UniqueAlignedPointer&& vertices, UniqueAlignedPointer&& triangles, int vertexCount, int triangleCount) : MeshPrototype(vertexCount, triangleCount, std::move(vertices), std::move(triangles)) { assert(isValid(*this)); } TriangleMesh::TriangleMesh(int vertexCount, int triangleCount, const Vec3f* vertices, const Triangle* triangles) : MeshPrototype(vertexCount, triangleCount) { float* xValues = this->vertices.get(); float* yValues = xValues + BLOCK_WIDTH; float* zValues = yValues + BLOCK_WIDTH; unsigned int index = 0; for(size_t i = 0; i < vertexCount; i++) { xValues[index] = vertices[i].x; yValues[index] = vertices[i].y; zValues[index] = vertices[i].z; index++; if((index % BLOCK_WIDTH) == 0) index += BLOCK_WIDTH * 2; } if((index % BLOCK_WIDTH) == 0) index -= BLOCK_WIDTH * 2; unsigned int lastIndex = index - 1; unsigned int sizeLeft = (getOffset(vertexCount) * 3 - vertexCount * 3) / 3; for(unsigned int i = index; i < index + sizeLeft; i++){ xValues[i] = xValues[lastIndex]; yValues[i] = yValues[lastIndex]; zValues[i] = zValues[lastIndex]; } int* aValues = this->triangles.get(); int* bValues = aValues + BLOCK_WIDTH; int* cValues = bValues + BLOCK_WIDTH; index = 0; for(size_t i = 0; i < triangleCount; i++) { aValues[index] = triangles[i].firstIndex; bValues[index] = triangles[i].secondIndex; cValues[index] = triangles[i].thirdIndex; index++; if((index % BLOCK_WIDTH) == 0) index += BLOCK_WIDTH * 2; } if((index % BLOCK_WIDTH) == 0) index -= BLOCK_WIDTH * 2; lastIndex = index - 1; sizeLeft = (getOffset(triangleCount) * 3 - triangleCount * 3) / 3; for(unsigned int i=index; i < index + sizeLeft; i++){ aValues[i] = aValues[lastIndex]; bValues[i] = bValues[lastIndex]; cValues[i] = cValues[lastIndex]; } assert(isValid(*this)); } TriangleMesh::TriangleMesh(const MeshPrototype& mesh) : MeshPrototype(mesh) { fixFinalBlock(this->vertices.get(), vertexCount); fixFinalBlock(this->triangles.get(), triangleCount); assert(isValid(*this)); } TriangleMesh::TriangleMesh(MeshPrototype&& mesh) noexcept : MeshPrototype(std::move(mesh)) { fixFinalBlock(this->vertices.get(), vertexCount); fixFinalBlock(this->triangles.get(), triangleCount); assert(isValid(*this)); } IteratorFactory TriangleMesh::iterVertices() const { unsigned int totalVertices = getOffset(vertexCount) * 3; unsigned int unusableVertices_n = ((getOffset(vertexCount) * 3 - vertexCount * 3) / 3); bool isMulOfBlockWidth = (vertexCount % BLOCK_WIDTH == 0); return IteratorFactory(ShapeVertexIter{vertices, 0}, ShapeVertexIter{&vertices[totalVertices - unusableVertices_n - BLOCK_WIDTH * 2 + BLOCK_WIDTH * 2 * isMulOfBlockWidth], 0}); } IteratorFactory TriangleMesh::iterTriangles() const { unsigned int totalTriangles = getOffset(triangleCount) * 3; unsigned int unusableTriangles_n = ((getOffset(triangleCount) * 3 - triangleCount * 3) / 3); bool isMulOfBlockWidth = (triangleCount % BLOCK_WIDTH == 0); return IteratorFactory(ShapeTriangleIter{triangles, 0}, ShapeTriangleIter{&triangles[totalTriangles - unusableTriangles_n - BLOCK_WIDTH * 2 + BLOCK_WIDTH * 2 * isMulOfBlockWidth], 0}); } void TriangleMesh::getTriangles(Triangle* triangleBuf) const { size_t i = 0; for(Triangle triangle : iterTriangles()) { triangleBuf[i++] = triangle; } } void TriangleMesh::getVertices(Vec3f* vertexBuf) const { size_t i = 0; for(Vec3f vertex : iterVertices()) { vertexBuf[i++] = vertex; } } TriangleMesh TriangleMesh::translated(Vec3f offset) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { result.setVertex(i, this->getVertex(i) + offset); } return TriangleMesh(std::move(result)); } TriangleMesh TriangleMesh::rotated(Rotationf rotation) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { result.setVertex(i, rotation * this->getVertex(i)); } return TriangleMesh(std::move(result)); } TriangleMesh TriangleMesh::localToGlobal(CFramef frame) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { result.setVertex(i, frame.localToGlobal(this->getVertex(i))); } return TriangleMesh(std::move(result)); } TriangleMesh TriangleMesh::globalToLocal(CFramef frame) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { result.setVertex(i, frame.globalToLocal(this->getVertex(i))); } return TriangleMesh(std::move(result)); } TriangleMesh TriangleMesh::scaled(float scaleX, float scaleY, float scaleZ) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { Vec3f v = this->getVertex(i); result.setVertex(i, Vec3f(scaleX * v.x, scaleY * v.y, scaleZ * v.z)); } return TriangleMesh(std::move(result)); } TriangleMesh TriangleMesh::scaled(DiagonalMat3f scale) const { return scaled(scale[0], scale[1], scale[2]); } TriangleMesh TriangleMesh::translatedAndScaled(Vec3f translation, DiagonalMat3f scale) const { EditableMesh result(this->vertexCount, this->triangleCount, this->triangles); for(int i = 0; i < this->vertexCount; i++) { Vec3f cur = this->getVertex(i); result.setVertex(i, scale * (cur + translation)); } return TriangleMesh(std::move(result)); } Vec3f TriangleMesh::getNormalVecOfTriangle(Triangle triangle) const { Vec3f v0 = this->getVertex(triangle.firstIndex); return (this->getVertex(triangle.secondIndex) - v0) % (this->getVertex(triangle.thirdIndex) - v0); } void TriangleMesh::computeNormals(Vec3f* buffer) const { // TODO parallelize for (Triangle triangle : iterTriangles()) { Vec3f v0 = this->getVertex(triangle.firstIndex); Vec3f v1 = this->getVertex(triangle.secondIndex); Vec3f v2 = this->getVertex(triangle.thirdIndex); Vec3f D10 = normalize(v1 - v0); Vec3f D20 = normalize(v2 - v0); Vec3f D21 = normalize(v2 - v1); buffer[triangle.firstIndex] += D10 % D20; buffer[triangle.secondIndex] += D10 % D21; buffer[triangle.thirdIndex] += D20 % D21; } for (int i = 0; i < vertexCount; i++) { buffer[i] = normalize(buffer[i]); } } CircumscribingSphere TriangleMesh::getCircumscribingSphere() const { BoundingBox bounds = getBounds(); Vec3 center = bounds.getCenter(); double radius = getMaxRadius(center); return CircumscribingSphere{center, radius}; } double TriangleMesh::getMaxRadiusSq() const { double bestDistSq = 0; for(Vec3f vertex : iterVertices()) { double distSq = lengthSquared(vertex); if(distSq > bestDistSq) { bestDistSq = distSq; } } return bestDistSq; } double TriangleMesh::getMaxRadiusSq(Vec3f reference) const { double bestDistSq = 0; for(Vec3f vertex : iterVertices()) { double distSq = lengthSquared(vertex - reference); if(distSq > bestDistSq) { bestDistSq = distSq; } } return bestDistSq; } double TriangleMesh::getMaxRadius() const { return sqrt(getMaxRadiusSq()); } double TriangleMesh::getMaxRadius(Vec3f reference) const { return sqrt(getMaxRadiusSq(reference)); } double TriangleMesh::getScaledMaxRadiusSq(DiagonalMat3 scale) const { double bestDistSq = 0; for(Vec3f vertex : iterVertices()) { double distSq = lengthSquared(scale * Vec3(vertex)); if(distSq > bestDistSq) { bestDistSq = distSq; } } return bestDistSq; } double TriangleMesh::getScaledMaxRadius(DiagonalMat3 scale) const { return sqrt(getScaledMaxRadiusSq(scale)); } double TriangleMesh::getIntersectionDistance(const Vec3& origin, const Vec3& direction) const { const double EPSILON = 0.0000001; double t = std::numeric_limits::max(); for(Triangle triangle : iterTriangles()) { Vec3 v0 = this->getVertex(triangle.firstIndex); Vec3 v1 = this->getVertex(triangle.secondIndex); Vec3 v2 = this->getVertex(triangle.thirdIndex); Vec3 edge1 = v1 - v0; Vec3 edge2 = v2 - v0; Vec3 h = direction % edge2; double a = edge1 * h; if(a > -EPSILON && a < EPSILON) continue; Vec3 s = origin - v0; double f = 1.0 / a; double u = f * (s * h); if(u < 0.0 || u > 1.0) continue; Vec3 q = s % edge1; double v = direction * f * q; if(v < 0.0 || u + v > 1.0) continue; double r = edge2 * f * q; if(r > EPSILON) { if(r < t) t = r; } else { //Log::debug("Line intersection but not a ray intersection"); continue; } } return t; } TriangleMesh stripUnusedVertices(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount) { bool* vertexIsReferenced = new bool[vertexCount]; for(int i = 0; i < vertexCount; i++) { vertexIsReferenced[i] = false; } for(int i = 0; i < triangleCount; i++) { Triangle t = triangles[i]; vertexIsReferenced[t.firstIndex] = true; vertexIsReferenced[t.secondIndex] = true; vertexIsReferenced[t.thirdIndex] = true; } int totalUnReferencedCount = 0; for(int i = 0; i < vertexCount; i++) { if(vertexIsReferenced[i] == false) { totalUnReferencedCount++; } } int lastValidVertex = vertexCount - 1; while(vertexIsReferenced[lastValidVertex] == false) { lastValidVertex--; } std::vector> substitutions(totalUnReferencedCount); // substitute first with second in triangles EditableMesh result(vertexCount - totalUnReferencedCount, triangleCount); // fix vertices for(int i = 0; i <= lastValidVertex; i++) { if(vertexIsReferenced[i] == false) { result.setVertex(i, vertices[lastValidVertex]); substitutions.push_back(std::make_pair(lastValidVertex, i)); do { lastValidVertex--; } while(vertexIsReferenced[lastValidVertex] == false && lastValidVertex > i); // the second condition is for the case that the first n vertices are all invalid, which would make lastValidVertex run off 0 } else { result.setVertex(i, vertices[i]); } } delete[] vertexIsReferenced; for(int i = 0; i < triangleCount; i++) { Triangle t = triangles[i]; for(std::pair& sub : substitutions) { if(t.firstIndex == sub.first) t.firstIndex = sub.second; if(t.secondIndex == sub.first) t.secondIndex = sub.second; if(t.thirdIndex == sub.first) t.thirdIndex = sub.second; } result.setTriangle(i, t); } return TriangleMesh(std::move(result)); } int TriangleMesh::furthestIndexInDirectionFallback(const Vec3f& direction) const { float bestDot = this->getVertex(0) * direction; int bestVertexIndex = 0; for(int i = 1; i < vertexCount; i++) { float newD = this->getVertex(i) * direction; if(newD > bestDot) { bestDot = newD; bestVertexIndex = i; } } return bestVertexIndex; } Vec3f TriangleMesh::furthestInDirectionFallback(const Vec3f& direction) const { float bestDot = this->getVertex(0) * direction; Vec3f bestVertex = this->getVertex(0); for(int i = 1; i < vertexCount; i++) { float newD = this->getVertex(i) * direction; if(newD > bestDot) { bestDot = newD; bestVertex = this->getVertex(i); } } return bestVertex; } BoundingBox TriangleMesh::getBoundsFallback() const { double xmin = this->getVertex(0).x, xmax = this->getVertex(0).x; double ymin = this->getVertex(0).y, ymax = this->getVertex(0).y; double zmin = this->getVertex(0).z, zmax = this->getVertex(0).z; for(int i = 1; i < vertexCount; i++) { const Vec3f current = this->getVertex(i); if(current.x < xmin) xmin = current.x; if(current.x > xmax) xmax = current.x; if(current.y < ymin) ymin = current.y; if(current.y > ymax) ymax = current.y; if(current.z < zmin) zmin = current.z; if(current.z > zmax) zmax = current.z; } return BoundingBox{xmin, ymin, zmin, xmax, ymax, zmax}; } BoundingBox TriangleMesh::getBoundsFallback(const Mat3f& referenceFrame) const { Mat3f transp = referenceFrame.transpose(); double xmax = (referenceFrame * this->furthestInDirection(transp * Vec3f(1, 0, 0))).x; double xmin = (referenceFrame * this->furthestInDirection(transp * Vec3f(-1, 0, 0))).x; double ymax = (referenceFrame * this->furthestInDirection(transp * Vec3f(0, 1, 0))).y; double ymin = (referenceFrame * this->furthestInDirection(transp * Vec3f(0, -1, 0))).y; double zmax = (referenceFrame * this->furthestInDirection(transp * Vec3f(0, 0, 1))).z; double zmin = (referenceFrame * this->furthestInDirection(transp * Vec3f(0, 0, -1))).z; return BoundingBox(xmin, ymin, zmin, xmax, ymax, zmax); } int TriangleMesh::furthestIndexInDirection(const Vec3f& direction) const { if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { return furthestIndexInDirectionAVX(direction); } else if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE4_1)) { return furthestIndexInDirectionSSE4(direction); } else { return furthestIndexInDirectionSSE(direction); } } else { return furthestIndexInDirectionFallback(direction); } } Vec3f TriangleMesh::furthestInDirection(const Vec3f& direction) const { if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { return furthestInDirectionAVX(direction); } else if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE4_1)) { return furthestInDirectionSSE4(direction); } else { return furthestInDirectionSSE(direction); } } else { return furthestInDirectionFallback(direction); } } BoundingBox TriangleMesh::getBounds() const { if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { return getBoundsAVX(); } else if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { return getBoundsSSE(); } else { return getBoundsFallback(); } } BoundingBox TriangleMesh::getBounds(const Mat3f& referenceFrame) const { if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { return getBoundsAVX(referenceFrame); } else if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { return getBoundsSSE(referenceFrame); } else { return getBoundsFallback(referenceFrame); } } #pragma endregion }; ================================================ FILE: Physics3D/geometry/triangleMesh.h ================================================ #pragma once #define BLOCK_WIDTH 8 #include "../math/linalg/vec.h" #include "../math/cframe.h" #include "../math/boundingBox.h" #include "../datastructures/alignedPtr.h" #include "../datastructures/iteratorFactory.h" #include namespace P3D { struct Triangle { union { struct { int firstIndex, secondIndex, thirdIndex; }; int indexes[3]; }; [[nodiscard]] bool sharesEdgeWith(Triangle other) const; [[nodiscard]] Triangle rightShift() const { return Triangle{thirdIndex, firstIndex, secondIndex}; } [[nodiscard]] Triangle leftShift() const { return Triangle{secondIndex, thirdIndex, firstIndex}; } Triangle operator~() const { return Triangle{firstIndex, thirdIndex, secondIndex}; } bool operator==(const Triangle& other) const; int& operator[](int i) { return indexes[i]; } const int& operator[](int i) const { return indexes[i]; } }; struct ShapeVertexIter { float* curVertex; size_t index; Vec3f operator*() const { return Vec3f{curVertex[index], (curVertex[index + BLOCK_WIDTH]), (curVertex[index + 2 * BLOCK_WIDTH])}; } void operator++() { index++; if((index % BLOCK_WIDTH) == 0) index += BLOCK_WIDTH * 2; } bool operator!=(const ShapeVertexIter& other) const { // size_t correct_index = (index/offset)*offset+8*2+index; return &curVertex[index] != other.curVertex; } }; struct ShapeTriangleIter { int* curTriangle; size_t index; Triangle operator*() const { return Triangle{curTriangle[index], (curTriangle[index + BLOCK_WIDTH]), (curTriangle[index + 2 * BLOCK_WIDTH])}; } void operator++() { index++; if((index % BLOCK_WIDTH) == 0) index += BLOCK_WIDTH * 2; } bool operator!=(const ShapeTriangleIter& other) const { //size_t correct_index = (index/offset)*offset*2+index; return &curTriangle[index] != other.curTriangle; } bool operator==(const ShapeTriangleIter& other) const { // size_t correct_index = (index/offset)*offset*2+index; return &curTriangle[index] == other.curTriangle; } }; class TriangleMesh; class MeshPrototype { protected: UniqueAlignedPointer vertices; UniqueAlignedPointer triangles; public: int vertexCount; int triangleCount; MeshPrototype(); MeshPrototype(int vertexCount, int triangleCount); MeshPrototype(int vertexCount, int triangleCount, UniqueAlignedPointer&& triangles); MeshPrototype(int vertexCount, int triangleCount, UniqueAlignedPointer&& vertices, UniqueAlignedPointer&& triangles); MeshPrototype(const MeshPrototype& mesh); MeshPrototype& operator=(const MeshPrototype& mesh); MeshPrototype(MeshPrototype&&) noexcept = default; MeshPrototype& operator=(MeshPrototype&&) noexcept = default; [[nodiscard]] Vec3f getVertex(int index) const; [[nodiscard]] Triangle getTriangle(int index) const; }; class EditableMesh : public MeshPrototype { public: EditableMesh(int vertexCount, int triangleCount); EditableMesh(int vertexCount, int triangleCount, const UniqueAlignedPointer& triangles); EditableMesh(int vertexCount, int triangleCount, UniqueAlignedPointer&& triangles); explicit EditableMesh(const MeshPrototype& mesh); explicit EditableMesh(MeshPrototype&&) noexcept; void setVertex(int index, Vec3f vertex); void setVertex(int index, float x, float y, float z); void setTriangle(int index, Triangle triangle); void setTriangle(int index, int a, int b, int c); }; class TriangleMesh : public MeshPrototype { protected: TriangleMesh(UniqueAlignedPointer&& vertices, UniqueAlignedPointer&& triangles, int vertexCount, int triangleCount); public: TriangleMesh() = default; TriangleMesh(int vertexCount, int triangleCount, const Vec3f* vertices, const Triangle* triangles); ~TriangleMesh() = default; TriangleMesh(TriangleMesh&&) noexcept = default; TriangleMesh& operator=(TriangleMesh&&) noexcept = default; TriangleMesh(const TriangleMesh&) = default; TriangleMesh& operator=(const TriangleMesh&) = default; explicit TriangleMesh(MeshPrototype&& mesh) noexcept; explicit TriangleMesh(const MeshPrototype& mesh); [[nodiscard]] IteratorFactory iterVertices() const; [[nodiscard]] IteratorFactory iterTriangles() const; void getTriangles(Triangle* triangleBuf) const; void getVertices(Vec3f* vertexBuf) const; [[nodiscard]] TriangleMesh translated(Vec3f offset) const; [[nodiscard]] TriangleMesh rotated(Rotationf rotation) const; [[nodiscard]] TriangleMesh localToGlobal(CFramef frame) const; [[nodiscard]] TriangleMesh globalToLocal(CFramef frame) const; [[nodiscard]] TriangleMesh scaled(float scaleX, float scaleY, float scaleZ) const; [[nodiscard]] TriangleMesh scaled(DiagonalMat3f scale) const; [[nodiscard]] TriangleMesh translatedAndScaled(Vec3f translation, DiagonalMat3f scale) const; [[nodiscard]] Vec3f getNormalVecOfTriangle(Triangle triangle) const; void computeNormals(Vec3f* buffer) const; [[nodiscard]] CircumscribingSphere getCircumscribingSphere() const; [[nodiscard]] double getMaxRadius() const; [[nodiscard]] double getMaxRadius(Vec3f reference) const; [[nodiscard]] double getMaxRadiusSq() const; [[nodiscard]] double getMaxRadiusSq(Vec3f reference) const; [[nodiscard]] double getScaledMaxRadius(DiagonalMat3 scale) const; [[nodiscard]] double getScaledMaxRadiusSq(DiagonalMat3 scale) const; [[nodiscard]] BoundingBox getBoundsFallback() const; [[nodiscard]] BoundingBox getBoundsFallback(const Mat3f& referenceFrame) const; [[nodiscard]] int furthestIndexInDirectionFallback(const Vec3f& direction) const; [[nodiscard]] Vec3f furthestInDirectionFallback(const Vec3f& direction) const; [[nodiscard]] BoundingBox getBoundsSSE() const; [[nodiscard]] BoundingBox getBoundsSSE(const Mat3f& referenceFrame) const; [[nodiscard]] int furthestIndexInDirectionSSE(const Vec3f& direction) const; [[nodiscard]] Vec3f furthestInDirectionSSE(const Vec3f& direction) const; [[nodiscard]] int furthestIndexInDirectionSSE4(const Vec3f& direction) const; [[nodiscard]] Vec3f furthestInDirectionSSE4(const Vec3f& direction) const; [[nodiscard]] BoundingBox getBoundsAVX() const; [[nodiscard]] BoundingBox getBoundsAVX(const Mat3f& referenceFrame) const; [[nodiscard]] int furthestIndexInDirectionAVX(const Vec3f& direction) const; [[nodiscard]] Vec3f furthestInDirectionAVX(const Vec3f& direction) const; [[nodiscard]] BoundingBox getBounds() const; [[nodiscard]] BoundingBox getBounds(const Mat3f& referenceFrame) const; [[nodiscard]] int furthestIndexInDirection(const Vec3f& direction) const; [[nodiscard]] Vec3f furthestInDirection(const Vec3f& direction) const; [[nodiscard]] double getIntersectionDistance(const Vec3& origin, const Vec3& direction) const; }; TriangleMesh stripUnusedVertices(const Vec3f* vertices, const Triangle* triangles, int vertexCount, int triangleCount); }; ================================================ FILE: Physics3D/geometry/triangleMeshAVX.cpp ================================================ #include "triangleMesh.h" #include "triangleMeshCommon.h" #include // AVX2 implementation for TriangleMesh functions namespace P3D { inline __m256i _mm256_blendv_epi32(__m256i a, __m256i b, __m256 mask) { return _mm256_castps_si256( _mm256_blendv_ps( _mm256_castsi256_ps(a), _mm256_castsi256_ps(b), mask ) ); } inline uint32_t mm256_extract_epi32_var_indx(__m256i vec, int i) { __m128i indx = _mm_cvtsi32_si128(i); __m256i val = _mm256_permutevar8x32_epi32(vec, _mm256_castsi128_si256(indx)); return _mm_cvtsi128_si32(_mm256_castsi256_si128(val)); } int TriangleMesh::furthestIndexInDirectionAVX(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m256 dx = _mm256_set1_ps(direction.x); __m256 dy = _mm256_set1_ps(direction.y); __m256 dz = _mm256_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; const float* yValues = this->vertices + 8; const float* zValues = this->vertices + 2 * 8; __m256 bestDot = _mm256_fmadd_ps(dz, _mm256_load_ps(zValues), _mm256_fmadd_ps(dy, _mm256_load_ps(yValues), _mm256_mul_ps(dx, _mm256_load_ps(xValues)))); __m256i bestIndices = _mm256_set1_epi32(0); for(size_t blockI = 1; blockI < (vertexCount + 7) / 8; blockI++) { __m256i indices = _mm256_set1_epi32(int(blockI)); __m256 dot = _mm256_fmadd_ps(dz, _mm256_load_ps(zValues + blockI * 24), _mm256_fmadd_ps(dy, _mm256_load_ps(yValues + blockI * 24), _mm256_mul_ps(dx, _mm256_load_ps(xValues + blockI * 24)))); __m256 whichAreMax = _mm256_cmp_ps(dot, bestDot, _CMP_GT_OQ); // Greater than, false if dot == NaN bestDot = _mm256_blendv_ps(bestDot, dot, whichAreMax); bestIndices = _mm256_blendv_epi32(bestIndices, indices, whichAreMax); // TODO convert to _mm256_blendv_epi8 } // find max of our 8 left candidates __m256 swap4x4 = _mm256_permute2f128_ps(bestDot, bestDot, 1); __m256 bestDotInternalMax = _mm256_max_ps(bestDot, swap4x4); __m256 swap2x2 = _mm256_permute_ps(bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm256_max_ps(bestDotInternalMax, swap2x2); __m256 swap1x1 = _mm256_permute_ps(bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm256_max_ps(bestDotInternalMax, swap1x1); __m256 compare = _mm256_cmp_ps(bestDotInternalMax, bestDot, _CMP_EQ_UQ); uint32_t mask = _mm256_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); uint32_t block = mm256_extract_epi32_var_indx(bestIndices, index); return block * 8 + index; } Vec3f TriangleMesh::furthestInDirectionAVX(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m256 dx = _mm256_set1_ps(direction.x); __m256 dy = _mm256_set1_ps(direction.y); __m256 dz = _mm256_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; const float* yValues = this->vertices + 8; const float* zValues = this->vertices + 16; __m256 bestX = _mm256_load_ps(xValues); __m256 bestY = _mm256_load_ps(yValues); __m256 bestZ = _mm256_load_ps(zValues); __m256 bestDot = _mm256_fmadd_ps(dz, bestZ, _mm256_fmadd_ps(dy, bestY, _mm256_mul_ps(dx, bestX))); for(size_t blockI = 1; blockI < (vertexCount + 7) / 8; blockI++) { //__m256i indices = _mm256_set1_epi32(int(blockI)); __m256 xVal = _mm256_load_ps(xValues + blockI * 24); __m256 yVal = _mm256_load_ps(yValues + blockI * 24); __m256 zVal = _mm256_load_ps(zValues + blockI * 24); __m256 dot = _mm256_fmadd_ps(dz, zVal, _mm256_fmadd_ps(dy, yVal, _mm256_mul_ps(dx, xVal))); __m256 whichAreMax = _mm256_cmp_ps(bestDot, dot, _CMP_GT_OQ); // Greater than, false if dot == NaN bestDot = _mm256_blendv_ps(dot, bestDot, whichAreMax); bestX = _mm256_blendv_ps(xVal, bestX, whichAreMax); bestY = _mm256_blendv_ps(yVal, bestY, whichAreMax); bestZ = _mm256_blendv_ps(zVal, bestZ, whichAreMax); } // now we find the max of the remaining 8 elements __m256 swap4x4 = _mm256_permute2f128_ps(bestDot, bestDot, 1); __m256 bestDotInternalMax = _mm256_max_ps(bestDot, swap4x4); __m256 swap2x2 = _mm256_permute_ps(bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm256_max_ps(bestDotInternalMax, swap2x2); __m256 swap1x1 = _mm256_permute_ps(bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm256_max_ps(bestDotInternalMax, swap1x1); __m256 compare = _mm256_cmp_ps(bestDotInternalMax, bestDot, _CMP_EQ_UQ); uint32_t mask = _mm256_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); // a bug occurs here, when mask == 0 the resulting index is undefined return Vec3f(GET_AVX_ELEM(bestX, index), GET_AVX_ELEM(bestY, index), GET_AVX_ELEM(bestZ, index)); } // compare the remaining 8 elements inline static BoundingBox toBounds(__m256 xMin, __m256 xMax, __m256 yMin, __m256 yMax, __m256 zMin, __m256 zMax) { // now we compare the remaining 8 elements __m256 xyMin = _mm256_min_ps(_mm256_permute2f128_ps(xMin, yMin, 0x20), _mm256_permute2f128_ps(xMin, yMin, 0x31)); __m256 xyMax = _mm256_max_ps(_mm256_permute2f128_ps(xMax, yMax, 0x20), _mm256_permute2f128_ps(xMax, yMax, 0x31)); zMin = _mm256_min_ps(zMin, _mm256_permute2f128_ps(zMin, zMin, 1)); zMax = _mm256_max_ps(zMax, _mm256_permute2f128_ps(zMax, zMax, 1)); xyMin = _mm256_min_ps(xyMin, _mm256_permute_ps(xyMin, SWAP_2x2)); xyMax = _mm256_max_ps(xyMax, _mm256_permute_ps(xyMax, SWAP_2x2)); zMin = _mm256_min_ps(zMin, _mm256_permute_ps(zMin, SWAP_2x2)); zMax = _mm256_max_ps(zMax, _mm256_permute_ps(zMax, SWAP_2x2)); __m256 zxzyMin = _mm256_blend_ps(xyMin, zMin, 0b00110011); // stored as xxyyzzzz zxzyMin = _mm256_min_ps(zxzyMin, _mm256_permute_ps(zxzyMin, SWAP_1x1)); __m256 zxzyMax = _mm256_blend_ps(xyMax, zMax, 0b00110011); zxzyMax = _mm256_max_ps(zxzyMax, _mm256_permute_ps(zxzyMax, SWAP_1x1)); // reg structure zzxxzzyy return BoundingBox{GET_AVX_ELEM(zxzyMin,2), GET_AVX_ELEM(zxzyMin, 6), GET_AVX_ELEM(zxzyMin, 0), GET_AVX_ELEM(zxzyMax, 2), GET_AVX_ELEM(zxzyMax, 6), GET_AVX_ELEM(zxzyMax, 0)}; } BoundingBox TriangleMesh::getBoundsAVX() const { size_t vertexCount = this->vertexCount; size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; const float* yValues = this->vertices + 8; const float* zValues = this->vertices + 2 * 8; __m256 xMax = _mm256_load_ps(xValues); __m256 xMin = xMax; __m256 yMax = _mm256_load_ps(yValues); __m256 yMin = yMax; __m256 zMax = _mm256_load_ps(zValues); __m256 zMin = zMax; for(size_t blockI = 1; blockI < (vertexCount + 7) / 8; blockI++) { __m256 xVal = _mm256_load_ps(xValues + blockI * 24); __m256 yVal = _mm256_load_ps(yValues + blockI * 24); __m256 zVal = _mm256_load_ps(zValues + blockI * 24); xMax = _mm256_max_ps(xMax, xVal); yMax = _mm256_max_ps(yMax, yVal); zMax = _mm256_max_ps(zMax, zVal); xMin = _mm256_min_ps(xMin, xVal); yMin = _mm256_min_ps(yMin, yVal); zMin = _mm256_min_ps(zMin, zVal); } return toBounds(xMin, xMax, yMin, yMax, zMin, zMax); } BoundingBox TriangleMesh::getBoundsAVX(const Mat3f& referenceFrame) const { size_t vertexCount = this->vertexCount; size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; const float* yValues = this->vertices + 8; const float* zValues = this->vertices + 2 * 8; float mxx = referenceFrame(0, 0); float mxy = referenceFrame(0, 1); float mxz = referenceFrame(0, 2); float myx = referenceFrame(1, 0); float myy = referenceFrame(1, 1); float myz = referenceFrame(1, 2); float mzx = referenceFrame(2, 0); float mzy = referenceFrame(2, 1); float mzz = referenceFrame(2, 2); __m256 xVal = _mm256_load_ps(xValues); __m256 yVal = _mm256_load_ps(yValues); __m256 zVal = _mm256_load_ps(zValues); __m256 xMin = _mm256_fmadd_ps(_mm256_set1_ps(mxz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(mxy), yVal, _mm256_mul_ps(_mm256_set1_ps(mxx), xVal))); __m256 yMin = _mm256_fmadd_ps(_mm256_set1_ps(myz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(myy), yVal, _mm256_mul_ps(_mm256_set1_ps(myx), xVal))); __m256 zMin = _mm256_fmadd_ps(_mm256_set1_ps(mzz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(mzy), yVal, _mm256_mul_ps(_mm256_set1_ps(mzx), xVal))); __m256 xMax = xMin; __m256 yMax = yMin; __m256 zMax = zMin; for(size_t blockI = 1; blockI < (vertexCount + 7) / 8; blockI++) { __m256 xVal = _mm256_load_ps(xValues + blockI * 24); __m256 yVal = _mm256_load_ps(yValues + blockI * 24); __m256 zVal = _mm256_load_ps(zValues + blockI * 24); __m256 dotX = _mm256_fmadd_ps(_mm256_set1_ps(mxz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(mxy), yVal, _mm256_mul_ps(_mm256_set1_ps(mxx), xVal))); xMin = _mm256_min_ps(xMin, dotX); xMax = _mm256_max_ps(xMax, dotX); __m256 dotY = _mm256_fmadd_ps(_mm256_set1_ps(myz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(myy), yVal, _mm256_mul_ps(_mm256_set1_ps(myx), xVal))); yMin = _mm256_min_ps(yMin, dotY); yMax = _mm256_max_ps(yMax, dotY); __m256 dotZ = _mm256_fmadd_ps(_mm256_set1_ps(mzz), zVal, _mm256_fmadd_ps(_mm256_set1_ps(mzy), yVal, _mm256_mul_ps(_mm256_set1_ps(mzx), xVal))); zMin = _mm256_min_ps(zMin, dotZ); zMax = _mm256_max_ps(zMax, dotZ); } return toBounds(xMin, xMax, yMin, yMax, zMin, zMax); } }; ================================================ FILE: Physics3D/geometry/triangleMeshCommon.h ================================================ #pragma once #include "triangleMesh.h" #include namespace P3D { #ifdef _MSC_VER #define GET_SSE_ELEM(reg, index) reg.m128_f32[index] #define GET_SSE_ELEMi(reg, index) reg.m128i_i32[index] #define GET_AVX_ELEM(reg, index) reg.m256_f32[index] #else #define GET_SSE_ELEM(reg, index) reg[index] #define GET_AVX_ELEM(reg, index) reg[index] inline static int mm_extractv_epi32(__m128i a, int b) { alignas(16) int buf[4]; _mm_storeu_si128(reinterpret_cast<__m128i*>(buf), a); return buf[b]; } #define GET_SSE_ELEMi(reg, index) mm_extractv_epi32(reg, index) #endif #define SWAP_2x2 0b01001110 #define SWAP_1x1 0b10110001 inline static size_t getOffset(size_t size) { return (size + 7) & 0xFFFFFFFFFFFFFFF8; } #ifdef _MSC_VER inline static uint32_t countZeros(uint32_t mask) { unsigned long ret = 0; _BitScanForward(&ret, mask); return static_cast(ret); } #else inline static uint32_t countZeros(uint32_t mask) { return __builtin_ctz(mask); } #endif static __m128 custom_fmadd_ps(__m128 a, __m128 b, __m128 c) { return _mm_add_ps(_mm_mul_ps(a, b), c); } }; ================================================ FILE: Physics3D/geometry/triangleMeshSSE.cpp ================================================ #include "triangleMesh.h" #include "triangleMeshCommon.h" #include // SSE2 implementation for TriangleMesh functions namespace P3D { // used if SSE4_1 is not available, emulates _mm_blendv_ps static __m128 custom_blendv_ps(__m128 a, __m128 b, __m128 mask) { return _mm_or_ps(_mm_andnot_ps(mask, a), _mm_and_ps(mask, b)); } // used if SSE4_1 is not available, emulates _mm_blendv_epi32 static __m128i custom_blendv_epi32(__m128i a, __m128i b, __m128i mask) { return _mm_or_si128(_mm_andnot_si128(mask, a), _mm_and_si128(mask, b)); } static BoundingBox toBounds(__m128 xMin, __m128 xMax, __m128 yMin, __m128 yMax, __m128 zMin, __m128 zMax) { for(int i = 0; i < 3; i++) { __m128 xShuf = _mm_shuffle_ps(xMax, xMax, 0x93); xMax = _mm_max_ps(xMax, xShuf); } for(int i = 0; i < 3; i++) { __m128 yShuf = _mm_shuffle_ps(yMax, yMax, 0x93); yMax = _mm_max_ps(yMax, yShuf); } for(int i = 0; i < 3; i++) { __m128 zShuf = _mm_shuffle_ps(zMax, zMax, 0x93); zMax = _mm_max_ps(zMax, zShuf); } for(int i = 0; i < 3; i++) { __m128 xShuf = _mm_shuffle_ps(xMin, xMin, 0x93); xMin = _mm_min_ps(xMin, xShuf); } for(int i = 0; i < 3; i++) { __m128 yShuf = _mm_shuffle_ps(yMin, yMin, 0x93); yMin = _mm_min_ps(yMin, yShuf); } for(int i = 0; i < 3; i++) { __m128 zShuf = _mm_shuffle_ps(zMin, zMin, 0x93); zMin = _mm_min_ps(zMin, zShuf); } return BoundingBox{GET_SSE_ELEM(xMin, 0), GET_SSE_ELEM(yMin, 0), GET_SSE_ELEM(zMin, 0), GET_SSE_ELEM(xMax, 0), GET_SSE_ELEM(yMax, 0), GET_SSE_ELEM(zMax, 0)}; } BoundingBox TriangleMesh::getBoundsSSE() const { size_t vertexCount = this->vertexCount; size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; __m128 xMax = _mm_load_ps(xValues); __m128 xMin = xMax; __m128 yMax = _mm_load_ps(xValues + BLOCK_WIDTH); __m128 yMin = yMax; __m128 zMax = _mm_load_ps(xValues + BLOCK_WIDTH * 2); __m128 zMin = zMax; __m128 xVal = _mm_load_ps(xValues + 4); __m128 yVal = _mm_load_ps(xValues + BLOCK_WIDTH + 4); __m128 zVal = _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4); xMax = _mm_max_ps(xMax, xVal); yMax = _mm_max_ps(yMax, yVal); zMax = _mm_max_ps(zMax, zVal); xMin = _mm_min_ps(xMin, xVal); yMin = _mm_min_ps(yMin, yVal); zMin = _mm_min_ps(zMin, zVal); const float *verticesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verticesEnd; verticesPointer += BLOCK_WIDTH * 3) { __m128 xVal = _mm_load_ps(verticesPointer); __m128 yVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH); __m128 zVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2); xMax = _mm_max_ps(xMax, xVal); yMax = _mm_max_ps(yMax, yVal); zMax = _mm_max_ps(zMax, zVal); xMin = _mm_min_ps(xMin, xVal); yMin = _mm_min_ps(yMin, yVal); zMin = _mm_min_ps(zMin, zVal); xVal = _mm_load_ps(verticesPointer + 4); yVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4); zVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4); xMax = _mm_max_ps(xMax, xVal); yMax = _mm_max_ps(yMax, yVal); zMax = _mm_max_ps(zMax, zVal); xMin = _mm_min_ps(xMin, xVal); yMin = _mm_min_ps(yMin, yVal); zMin = _mm_min_ps(zMin, zVal); } return toBounds(xMin, xMax, yMin, yMax, zMin, zMax); } BoundingBox TriangleMesh::getBoundsSSE(const Mat3f& referenceFrame) const { size_t vertexCount = this->vertexCount; size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; float mxx = referenceFrame(0, 0); float mxy = referenceFrame(0, 1); float mxz = referenceFrame(0, 2); float myx = referenceFrame(1, 0); float myy = referenceFrame(1, 1); float myz = referenceFrame(1, 2); float mzx = referenceFrame(2, 0); float mzy = referenceFrame(2, 1); float mzz = referenceFrame(2, 2); __m128 xVal = _mm_load_ps(xValues); __m128 yVal = _mm_load_ps(xValues + BLOCK_WIDTH); __m128 zVal = _mm_load_ps(xValues + BLOCK_WIDTH * 2); __m128 xMin = custom_fmadd_ps(_mm_set1_ps(mxz), zVal, custom_fmadd_ps(_mm_set1_ps(mxy), yVal, _mm_mul_ps(_mm_set1_ps(mxx), xVal))); __m128 yMin = custom_fmadd_ps(_mm_set1_ps(myz), zVal, custom_fmadd_ps(_mm_set1_ps(myy), yVal, _mm_mul_ps(_mm_set1_ps(myx), xVal))); __m128 zMin = custom_fmadd_ps(_mm_set1_ps(mzz), zVal, custom_fmadd_ps(_mm_set1_ps(mzy), yVal, _mm_mul_ps(_mm_set1_ps(mzx), xVal))); __m128 xMax = xMin; __m128 yMax = yMin; __m128 zMax = zMin; xVal = _mm_load_ps(xValues + 4); yVal = _mm_load_ps(xValues + BLOCK_WIDTH + 4); zVal = _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4); __m128 dotX = custom_fmadd_ps(_mm_set1_ps(mxz), zVal, custom_fmadd_ps(_mm_set1_ps(mxy), yVal, _mm_mul_ps(_mm_set1_ps(mxx), xVal))); xMin = _mm_min_ps(xMin, dotX); xMax = _mm_max_ps(xMax, dotX); __m128 dotY = custom_fmadd_ps(_mm_set1_ps(myz), zVal, custom_fmadd_ps(_mm_set1_ps(myy), yVal, _mm_mul_ps(_mm_set1_ps(myx), xVal))); yMin = _mm_min_ps(yMin, dotY); yMax = _mm_max_ps(yMax, dotY); __m128 dotZ = custom_fmadd_ps(_mm_set1_ps(mzz), zVal, custom_fmadd_ps(_mm_set1_ps(mzy), yVal, _mm_mul_ps(_mm_set1_ps(mzx), xVal))); zMin = _mm_min_ps(zMin, dotZ); zMax = _mm_max_ps(zMax, dotZ); const float *verticesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verticesEnd; verticesPointer += BLOCK_WIDTH * 3) { __m128 xVal = _mm_load_ps(verticesPointer); __m128 yVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH); __m128 zVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2); __m128 dotX = custom_fmadd_ps(_mm_set1_ps(mxz), zVal, custom_fmadd_ps(_mm_set1_ps(mxy), yVal, _mm_mul_ps(_mm_set1_ps(mxx), xVal))); xMin = _mm_min_ps(xMin, dotX); xMax = _mm_max_ps(xMax, dotX); __m128 dotY = custom_fmadd_ps(_mm_set1_ps(myz), zVal, custom_fmadd_ps(_mm_set1_ps(myy), yVal, _mm_mul_ps(_mm_set1_ps(myx), xVal))); yMin = _mm_min_ps(yMin, dotY); yMax = _mm_max_ps(yMax, dotY); __m128 dotZ = custom_fmadd_ps(_mm_set1_ps(mzz), zVal, custom_fmadd_ps(_mm_set1_ps(mzy), yVal, _mm_mul_ps(_mm_set1_ps(mzx), xVal))); zMin = _mm_min_ps(zMin, dotZ); zMax = _mm_max_ps(zMax, dotZ); xVal = _mm_load_ps(verticesPointer + 4); yVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4); zVal = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4); dotX = custom_fmadd_ps(_mm_set1_ps(mxz), zVal, custom_fmadd_ps(_mm_set1_ps(mxy), yVal, _mm_mul_ps(_mm_set1_ps(mxx), xVal))); xMin = _mm_min_ps(xMin, dotX); xMax = _mm_max_ps(xMax, dotX); dotY = custom_fmadd_ps(_mm_set1_ps(myz), zVal, custom_fmadd_ps(_mm_set1_ps(myy), yVal, _mm_mul_ps(_mm_set1_ps(myx), xVal))); yMin = _mm_min_ps(yMin, dotY); yMax = _mm_max_ps(yMax, dotY); dotZ = custom_fmadd_ps(_mm_set1_ps(mzz), zVal, custom_fmadd_ps(_mm_set1_ps(mzy), yVal, _mm_mul_ps(_mm_set1_ps(mzx), xVal))); zMin = _mm_min_ps(zMin, dotZ); zMax = _mm_max_ps(zMax, dotZ); } return toBounds(xMin, xMax, yMin, yMax, zMin, zMax); } int TriangleMesh::furthestIndexInDirectionSSE(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m128 dx = _mm_set1_ps(direction.x); __m128 dy = _mm_set1_ps(direction.y); __m128 dz = _mm_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; __m128 bestDot = custom_fmadd_ps(dz, _mm_load_ps(xValues + BLOCK_WIDTH * 2), custom_fmadd_ps(dy, _mm_load_ps(xValues + BLOCK_WIDTH), _mm_mul_ps(dx, _mm_load_ps(xValues)))); __m128i bestIndices = _mm_set1_epi32(0); unsigned int blockI = 1; __m128i indices = _mm_set1_epi32(static_cast(blockI)); __m128 dot = custom_fmadd_ps(dz, _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4), custom_fmadd_ps(dy, _mm_load_ps(xValues + BLOCK_WIDTH + 4), _mm_mul_ps(dx, _mm_load_ps(xValues + 4)))); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = custom_blendv_ps(bestDot, dot, whichAreMax); bestIndices = custom_blendv_epi32(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; const float *verticesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verticesEnd; verticesPointer += BLOCK_WIDTH * 3) { __m128i indices = _mm_set1_epi32(static_cast(blockI)); __m128 dot = custom_fmadd_ps(dz, _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2), custom_fmadd_ps(dy, _mm_load_ps(verticesPointer + BLOCK_WIDTH), _mm_mul_ps(dx, _mm_load_ps(verticesPointer)))); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = custom_blendv_ps(bestDot, dot, whichAreMax); bestIndices = custom_blendv_epi32(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; indices = _mm_set1_epi32(static_cast(blockI)); dot = custom_fmadd_ps(dz, _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4), custom_fmadd_ps(dy, _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4), _mm_mul_ps(dx, _mm_load_ps(verticesPointer + 4)))); //Compare greater than, returns false if either operand is NaN. whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = custom_blendv_ps(bestDot, dot, whichAreMax); bestIndices = custom_blendv_epi32(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; } __m128 swap4x4 = _mm_shuffle_ps(bestDot, bestDot, 1); __m128 bestDotInternalMax = _mm_max_ps(bestDot, swap4x4); __m128 swap2x2 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap2x2); __m128 swap1x1 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap1x1); //Compare equality, returns true if either operand is NaN. __m128 compare = _mm_cmpeq_ps(bestDotInternalMax, bestDot); compare = _mm_cmpunord_ps(compare, compare); uint32_t mask = _mm_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); auto block = GET_SSE_ELEMi(bestIndices, index); return block * 4 + index; } Vec3f TriangleMesh::furthestInDirectionSSE(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m128 dx = _mm_set1_ps(direction.x); __m128 dy = _mm_set1_ps(direction.y); __m128 dz = _mm_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; __m128 bestX1 = _mm_load_ps(xValues); __m128 bestY1 = _mm_load_ps(xValues + BLOCK_WIDTH); __m128 bestZ1 = _mm_load_ps(xValues + BLOCK_WIDTH * 2); __m128 bestX2 = bestX1; __m128 bestY2 = bestY1; __m128 bestZ2 = bestZ1; __m128 bestDot1 = custom_fmadd_ps(dz, bestZ1, custom_fmadd_ps(dy, bestY1, _mm_mul_ps(dx, bestX1))); __m128 bestDot2 = bestDot1; __m128 xVal1 = _mm_load_ps(xValues + 4); __m128 yVal1 = _mm_load_ps(xValues + BLOCK_WIDTH + 4); __m128 zVal1 = _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4); __m128 xVal2 = xVal1; __m128 yVal2 = yVal1; __m128 zVal2 = zVal1; __m128 dot1 = custom_fmadd_ps(dz, zVal1, custom_fmadd_ps(dy, yVal1, _mm_mul_ps(dx, xVal1))); __m128 dot2 = dot1; //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax1 = _mm_cmpgt_ps(dot1, bestDot1); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax2 = whichAreMax1; bestDot1 = custom_blendv_ps(bestDot1, dot1, whichAreMax1); bestX1 = custom_blendv_ps(bestX1, xVal1, whichAreMax1); bestY1 = custom_blendv_ps(bestY1, yVal1, whichAreMax1); bestZ1 = custom_blendv_ps(bestZ1, zVal1, whichAreMax1); const float *verteciesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verteciesEnd; verticesPointer += BLOCK_WIDTH * 3) { //__m128i indices = _mm_set1_epi32(static_cast(blockI)); xVal1 = _mm_load_ps(verticesPointer); yVal1 = _mm_load_ps(verticesPointer + BLOCK_WIDTH); zVal1 = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2); xVal2 = _mm_load_ps(verticesPointer + 4); yVal2 = _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4); zVal2 = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4); dot1 = custom_fmadd_ps(dz, zVal1, custom_fmadd_ps(dy, yVal1, _mm_mul_ps(dx, xVal1))); dot2 = custom_fmadd_ps(dz, zVal2, custom_fmadd_ps(dy, yVal2, _mm_mul_ps(dx, xVal2))); //Compare greater than, returns false if either operand is NaN. whichAreMax1 = _mm_cmpgt_ps(bestDot1, dot1); whichAreMax2 = _mm_cmpgt_ps(bestDot2, dot2); bestDot1 = custom_blendv_ps(dot1, bestDot1, whichAreMax1); bestDot2 = custom_blendv_ps(dot2, bestDot2, whichAreMax2); bestX1 = custom_blendv_ps(xVal1, bestX1, whichAreMax1); bestX2 = custom_blendv_ps(xVal2, bestX2, whichAreMax2); bestY1 = custom_blendv_ps(yVal1, bestY1, whichAreMax1); bestY2 = custom_blendv_ps(yVal2, bestY2, whichAreMax2); bestZ1 = custom_blendv_ps(zVal1, bestZ1, whichAreMax1); bestZ2 = custom_blendv_ps(zVal2, bestZ2, whichAreMax2); } //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(bestDot1, bestDot2); __m128 bestDot = custom_blendv_ps(bestDot2, bestDot1, whichAreMax); __m128 bestX = custom_blendv_ps(bestX2, bestX1, whichAreMax); __m128 bestY = custom_blendv_ps(bestY2, bestY1, whichAreMax); __m128 bestZ = custom_blendv_ps(bestZ2, bestZ1, whichAreMax); __m128 swap4x4 = _mm_shuffle_ps(bestDot, bestDot, 1); __m128 bestDotInternalMax = _mm_max_ps(bestDot, swap4x4); __m128 swap2x2 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap2x2); __m128 swap1x1 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap1x1); //Compare equality, return true if either operand is NaN. __m128 compare = _mm_cmpeq_ps(bestDotInternalMax, bestDot); compare = _mm_cmpunord_ps(compare, compare); uint32_t mask = _mm_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); return Vec3f(GET_SSE_ELEM(bestX, index), GET_SSE_ELEM(bestY, index), GET_SSE_ELEM(bestZ, index)); } }; ================================================ FILE: Physics3D/geometry/triangleMeshSSE4.cpp ================================================ #include "triangleMesh.h" #include "triangleMeshCommon.h" #include // SSE2 implementation for TriangleMesh functions namespace P3D { // SSE4-specific implementation, the _mm_blendv_ps instruction was the bottleneck int TriangleMesh::furthestIndexInDirectionSSE4(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m128 dx = _mm_set1_ps(direction.x); __m128 dy = _mm_set1_ps(direction.y); __m128 dz = _mm_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; __m128 bestDot = custom_fmadd_ps(dz, _mm_load_ps(xValues + BLOCK_WIDTH * 2), custom_fmadd_ps(dy, _mm_load_ps(xValues + BLOCK_WIDTH), _mm_mul_ps(dx, _mm_load_ps(xValues)))); __m128i bestIndices = _mm_set1_epi32(0); unsigned int blockI = 1; __m128i indices = _mm_set1_epi32(static_cast(blockI)); __m128 dot = custom_fmadd_ps(dz, _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4), custom_fmadd_ps(dy, _mm_load_ps(xValues + BLOCK_WIDTH + 4), _mm_mul_ps(dx, _mm_load_ps(xValues + 4)))); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = _mm_blendv_ps(bestDot, dot, whichAreMax); bestIndices = _mm_blendv_epi8(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; const float *verticesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verticesEnd; verticesPointer += BLOCK_WIDTH * 3) { __m128i indices = _mm_set1_epi32(static_cast(blockI)); __m128 dot = custom_fmadd_ps(dz, _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2), custom_fmadd_ps(dy, _mm_load_ps(verticesPointer + BLOCK_WIDTH), _mm_mul_ps(dx, _mm_load_ps(verticesPointer)))); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = _mm_blendv_ps(bestDot, dot, whichAreMax); bestIndices = _mm_blendv_epi8(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; indices = _mm_set1_epi32(static_cast(blockI)); dot = custom_fmadd_ps(dz, _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4), custom_fmadd_ps(dy, _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4), _mm_mul_ps(dx, _mm_load_ps(verticesPointer + 4)))); //Compare greater than, returns false if either operand is NaN. whichAreMax = _mm_cmpgt_ps(dot, bestDot); bestDot = _mm_blendv_ps(bestDot, dot, whichAreMax); bestIndices = _mm_blendv_epi8(bestIndices, indices, _mm_castps_si128(whichAreMax)); blockI++; } __m128 swap4x4 = _mm_shuffle_ps(bestDot, bestDot, 1); __m128 bestDotInternalMax = _mm_max_ps(bestDot, swap4x4); __m128 swap2x2 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap2x2); __m128 swap1x1 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap1x1); //Compare equality, returns true if either operand is NaN. __m128 compare = _mm_cmpeq_ps(bestDotInternalMax, bestDot); compare = _mm_cmpunord_ps(compare, compare); uint32_t mask = _mm_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); auto block = GET_SSE_ELEMi(bestIndices, index); return block * 4 + index; } Vec3f TriangleMesh::furthestInDirectionSSE4(const Vec3f& direction) const { size_t vertexCount = this->vertexCount; __m128 dx = _mm_set1_ps(direction.x); __m128 dy = _mm_set1_ps(direction.y); __m128 dz = _mm_set1_ps(direction.z); size_t offset = getOffset(vertexCount); const float* xValues = this->vertices; __m128 bestX1 = _mm_load_ps(xValues); __m128 bestY1 = _mm_load_ps(xValues + BLOCK_WIDTH); __m128 bestZ1 = _mm_load_ps(xValues + BLOCK_WIDTH * 2); __m128 bestX2 = bestX1; __m128 bestY2 = bestY1; __m128 bestZ2 = bestZ1; __m128 bestDot1 = custom_fmadd_ps(dz, bestZ1, custom_fmadd_ps(dy, bestY1, _mm_mul_ps(dx, bestX1))); __m128 bestDot2 = bestDot1; __m128 xVal1 = _mm_load_ps(xValues + 4); __m128 yVal1 = _mm_load_ps(xValues + BLOCK_WIDTH + 4); __m128 zVal1 = _mm_load_ps(xValues + BLOCK_WIDTH * 2 + 4); __m128 dot1 = custom_fmadd_ps(dz, zVal1, custom_fmadd_ps(dy, yVal1, _mm_mul_ps(dx, xVal1))); __m128 dot2 = dot1; //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax1 = _mm_cmpgt_ps(bestDot1, dot1); //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax2 = whichAreMax1; bestDot1 = _mm_blendv_ps(dot1, bestDot1, whichAreMax1); bestX1 = _mm_blendv_ps(xVal1, bestX1, whichAreMax1); bestY1 = _mm_blendv_ps(yVal1, bestY1, whichAreMax1); bestZ1 = _mm_blendv_ps(zVal1, bestZ1, whichAreMax1); const float *verticesEnd = xValues + offset * 3; for(const float *verticesPointer = xValues + BLOCK_WIDTH * 3; verticesPointer != verticesEnd; verticesPointer += BLOCK_WIDTH * 3) { //__m128i indices = _mm_set1_epi32(static_cast(blockI)); __m128 xVal1 = _mm_load_ps(verticesPointer); __m128 yVal1 = _mm_load_ps(verticesPointer + BLOCK_WIDTH); __m128 zVal1 = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2); __m128 xVal2 = _mm_load_ps(verticesPointer + 4); __m128 yVal2 = _mm_load_ps(verticesPointer + BLOCK_WIDTH + 4); __m128 zVal2 = _mm_load_ps(verticesPointer + BLOCK_WIDTH * 2 + 4); dot1 = custom_fmadd_ps(dz, zVal1, custom_fmadd_ps(dy, yVal1, _mm_mul_ps(dx, xVal1))); dot2 = custom_fmadd_ps(dz, zVal2, custom_fmadd_ps(dy, yVal2, _mm_mul_ps(dx, xVal2))); //Compare greater than, returns false if either operand is NaN. whichAreMax1 = _mm_cmpgt_ps(bestDot1, dot1); //Compare greater than, returns false if either operand is NaN. whichAreMax2 = _mm_cmpgt_ps(bestDot2, dot2); bestDot1 = _mm_blendv_ps(dot1, bestDot1, whichAreMax1); bestDot2 = _mm_blendv_ps(dot2, bestDot2, whichAreMax2); bestX1 = _mm_blendv_ps(xVal1, bestX1, whichAreMax1); bestX2 = _mm_blendv_ps(xVal2, bestX2, whichAreMax2); bestY1 = _mm_blendv_ps(yVal1, bestY1, whichAreMax1); bestY2 = _mm_blendv_ps(yVal2, bestY2, whichAreMax2); bestZ1 = _mm_blendv_ps(zVal1, bestZ1, whichAreMax1); bestZ2 = _mm_blendv_ps(zVal2, bestZ2, whichAreMax2); } //Compare greater than, returns false if either operand is NaN. __m128 whichAreMax = _mm_cmpgt_ps(bestDot1, bestDot2); __m128 bestDot = _mm_blendv_ps(bestDot2, bestDot1, whichAreMax); __m128 bestX = _mm_blendv_ps(bestX2, bestX1, whichAreMax); __m128 bestY = _mm_blendv_ps(bestY2, bestY1, whichAreMax); __m128 bestZ = _mm_blendv_ps(bestZ2, bestZ1, whichAreMax); __m128 swap4x4 = _mm_shuffle_ps(bestDot, bestDot, 1); __m128 bestDotInternalMax = _mm_max_ps(bestDot, swap4x4); __m128 swap2x2 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_2x2); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap2x2); __m128 swap1x1 = _mm_shuffle_ps(bestDotInternalMax, bestDotInternalMax, SWAP_1x1); bestDotInternalMax = _mm_max_ps(bestDotInternalMax, swap1x1); //Compare equality, return true if either operand is NaN. __m128 compare = _mm_cmpeq_ps(bestDotInternalMax, bestDot); compare = _mm_cmpunord_ps(compare, compare); uint32_t mask = _mm_movemask_ps(compare); assert(mask != 0); uint32_t index = countZeros(mask); return Vec3f(GET_SSE_ELEM(bestX, index), GET_SSE_ELEM(bestY, index), GET_SSE_ELEM(bestZ, index)); } }; ================================================ FILE: Physics3D/hardconstraints/constraintTemplates.h ================================================ #pragma once #include "hardConstraint.h" /* Requires a SpeedController argument, this object must provide the following methods: void update(double deltaT); double getValue() const; // returns the current speed of the motor FullTaylor getFullTaylorExpansion() const; // returns the current speed and it's derivatives, being acceleration, jerk etc */ namespace P3D { template class MotorConstraintTemplate : public HardConstraint, public SpeedController { public: using SpeedController::SpeedController; virtual void update(double deltaT) override { SpeedController::update(deltaT); } virtual CFrame getRelativeCFrame() const override { return CFrame(Rotation::rotZ(SpeedController::getValue())); } virtual RelativeMotion getRelativeMotion() const override { FullTaylor speedDerivatives = SpeedController::getFullTaylorExpansion(); RotationalMotion rotationMotion(speedDerivatives.getDerivatives().transform([](double v) {return Vec3(0, 0, v); })); return RelativeMotion(Motion(TranslationalMotion(), rotationMotion), CFrame(Rotation::rotZ(speedDerivatives.getConstantValue()))); } virtual ~MotorConstraintTemplate() override {} }; /* Requires a LengthController argument, this object must provide the following methods: void update(double deltaT); double getValue() const; // The current length of the piston FullTaylor getFullTaylorExpansion() const; // The current length and its derivatives of the piston */ template class PistonConstraintTemplate : public HardConstraint, public LengthController { public: using LengthController::LengthController; virtual void update(double deltaT) override { LengthController::update(deltaT); } virtual CFrame getRelativeCFrame() const override { return CFrame(0.0, 0.0, LengthController::getValue()); } virtual RelativeMotion getRelativeMotion() const override { FullTaylor speedDerivatives = LengthController::getFullTaylorExpansion(); TranslationalMotion translationMotion(speedDerivatives.getDerivatives().transform([](double v) {return Vec3(0, 0, v); })); return RelativeMotion(Motion(translationMotion, RotationalMotion()), CFrame(0.0, 0.0, speedDerivatives.getConstantValue())); } virtual ~PistonConstraintTemplate() override {} }; }; ================================================ FILE: Physics3D/hardconstraints/controller/constController.h ================================================ #pragma once #include "../../math/taylorExpansion.h" namespace P3D { class ConstController { public: double value; inline ConstController(double value) : value(value) {} inline void update(double deltaT) const {} inline double getValue() const { return value; } inline FullTaylor getFullTaylorExpansion() const { return FullTaylor{value}; } }; }; ================================================ FILE: Physics3D/hardconstraints/controller/sineWaveController.cpp ================================================ #include "sineWaveController.h" #include "../../math/predefinedTaylorExpansions.h" #include "../../math/constants.h" namespace P3D { SineWaveController::SineWaveController(double minValue, double maxValue, double period) : minValue(minValue), maxValue(maxValue), period(period), currentStepInPeriod(0) { assert(maxValue >= minValue); } void SineWaveController::update(double deltaT) { currentStepInPeriod += deltaT; if(currentStepInPeriod >= period) currentStepInPeriod -= period; } double SineWaveController::getValue() const { double multiplier = (maxValue - minValue) / 2; double offset = minValue + multiplier; double currentAngle = currentStepInPeriod * (2 * PI / period); return sin(currentAngle) * multiplier + offset; } FullTaylor SineWaveController::getFullTaylorExpansion() const { double multiplier = (maxValue - minValue) / 2; double offset = minValue + multiplier; double divPeriodToRadians = 2 * PI / period; FullTaylor result = generateFullTaylorForSinWave(currentStepInPeriod, (2 * PI / period)) * multiplier; result += offset; return result; } }; ================================================ FILE: Physics3D/hardconstraints/controller/sineWaveController.h ================================================ #pragma once #include "../../math/taylorExpansion.h" #include "../../motion.h" namespace P3D { class SineWaveController { public: double currentStepInPeriod; double minValue; double maxValue; double period; SineWaveController(double minValue, double maxValue, double period); void update(double deltaT); double getValue() const; FullTaylor getFullTaylorExpansion() const; }; }; ================================================ FILE: Physics3D/hardconstraints/fixedConstraint.cpp ================================================ #include "fixedConstraint.h" namespace P3D { void FixedConstraint::update(double deltaT) {} void FixedConstraint::invert() {} CFrame FixedConstraint::getRelativeCFrame() const { return CFrame(0.0, 0.0, 0.0); } RelativeMotion FixedConstraint::getRelativeMotion() const { return RelativeMotion(Motion(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0)), CFrame(0.0, 0.0, 0.0)); } }; ================================================ FILE: Physics3D/hardconstraints/fixedConstraint.h ================================================ #pragma once #include "hardConstraint.h" namespace P3D { // Mostly used for debugging. You should instead use plain part attachments. // This constraint should behave exactly the same as a plain part attachment, but being a HardConstraint, it is less performant. class FixedConstraint : public HardConstraint { public: virtual void update(double deltaT) override; virtual void invert() override; virtual CFrame getRelativeCFrame() const override; virtual RelativeMotion getRelativeMotion() const override; }; }; ================================================ FILE: Physics3D/hardconstraints/hardConstraint.cpp ================================================ #include "hardConstraint.h" namespace P3D { CFrame HardConstraint::getRelativeCFrame() const { return this->getRelativeMotion().locationOfRelativeMotion; } HardConstraint::~HardConstraint() {} }; ================================================ FILE: Physics3D/hardconstraints/hardConstraint.h ================================================ #pragma once #include "../math/cframe.h" #include "../motion.h" #include "../relativeMotion.h" /* A HardConstraint is a constraint that fully defines one object in terms of another */ namespace P3D { class HardConstraint { public: bool isInverted = false; virtual void update(double deltaT) = 0; virtual void invert() { isInverted = !isInverted; } virtual RelativeMotion getRelativeMotion() const = 0; virtual CFrame getRelativeCFrame() const = 0; virtual ~HardConstraint(); }; }; ================================================ FILE: Physics3D/hardconstraints/hardPhysicalConnection.cpp ================================================ #include "hardPhysicalConnection.h" namespace P3D { HardPhysicalConnection::HardPhysicalConnection(std::unique_ptr constraintWithParent, const CFrame& attachOnChild, const CFrame& attachOnParent) : attachOnChild(attachOnChild), attachOnParent(attachOnParent), constraintWithParent(std::move(constraintWithParent)) {} CFrame HardPhysicalConnection::getRelativeCFrameToParent() const { return attachOnParent.localToGlobal(constraintWithParent->getRelativeCFrame().localToGlobal(~attachOnChild)); } RelativeMotion HardPhysicalConnection::getRelativeMotion() const { RelativeMotion relMotion = constraintWithParent->getRelativeMotion(); return relMotion.extendBegin(this->attachOnParent).extendEnd(~this->attachOnChild); } void HardPhysicalConnection::update(double deltaT) { constraintWithParent->update(deltaT); } HardPhysicalConnection HardPhysicalConnection::inverted()&& { this->constraintWithParent->invert(); return HardPhysicalConnection(std::move(this->constraintWithParent), this->attachOnParent, this->attachOnChild); } }; ================================================ FILE: Physics3D/hardconstraints/hardPhysicalConnection.h ================================================ #pragma once #include #include "hardConstraint.h" #include "../math/cframe.h" #include "../relativeMotion.h" namespace P3D { struct HardPhysicalConnection { CFrame attachOnChild; CFrame attachOnParent; std::unique_ptr constraintWithParent; HardPhysicalConnection(std::unique_ptr constraintWithParent, const CFrame& attachOnChild, const CFrame& attachOnParent); CFrame getRelativeCFrameToParent() const; RelativeMotion getRelativeMotion() const; void update(double deltaT); HardPhysicalConnection inverted()&&; }; } ================================================ FILE: Physics3D/hardconstraints/motorConstraint.cpp ================================================ #include "motorConstraint.h" #include "../math/linalg/trigonometry.h" #include "../math/constants.h" #include "../misc/debug.h" namespace P3D { void ConstantMotorTurner::update(double deltaT) { this->currentAngle = fmod(fmod(this->currentAngle + deltaT * speed, 2 * PI) + 2 * PI, 2 * PI); } void ConstantMotorTurner::invert() { Debug::logError("ConstantSpeedMotorConstraint::invert is not implemented!"); assert(false); } double ConstantMotorTurner::getValue() const { return currentAngle; } FullTaylor ConstantMotorTurner::getFullTaylorExpansion() const { return FullTaylor::fromConstantAndDerivatives(this->currentAngle, Taylor{speed}); } }; ================================================ FILE: Physics3D/hardconstraints/motorConstraint.h ================================================ #pragma once #include "hardConstraint.h" #include "constraintTemplates.h" namespace P3D { class ConstantMotorTurner { public: double speed; double currentAngle; inline ConstantMotorTurner(double motorSpeed, double currentAngle) : speed(motorSpeed), currentAngle(currentAngle) {} inline ConstantMotorTurner(double motorSpeed) : speed(motorSpeed), currentAngle(0.0) {} void update(double deltaT); void invert(); double getValue() const; FullTaylor getFullTaylorExpansion() const; }; typedef MotorConstraintTemplate ConstantSpeedMotorConstraint; }; ================================================ FILE: Physics3D/hardconstraints/sinusoidalPistonConstraint.h ================================================ #include "hardConstraint.h" #include "constraintTemplates.h" #include "controller/sineWaveController.h" namespace P3D { typedef PistonConstraintTemplate SinusoidalPistonConstraint; }; ================================================ FILE: Physics3D/inertia.cpp ================================================ #include "inertia.h" #include "math/linalg/trigonometry.h" #include "math/predefinedTaylorExpansions.h" namespace P3D { SymmetricMat3 getRotatedInertia(const SymmetricMat3& originalInertia, const Rotation& rotation) { return rotation.localToGlobal(originalInertia); } SymmetricMat3 getTranslatedInertia(const SymmetricMat3& originalInertia, double mass, const Vec3& translation, const Vec3& centerOfMass) { SymmetricMat3 translationFactor = skewSymmetricSquared(translation + centerOfMass) - skewSymmetricSquared(centerOfMass); return originalInertia - translationFactor * mass; } /* computes a translated inertial matrix, COMOffset is the offset of the object's center of mass from the resulting center of mass == localCenterOfMass - totalCenterOfMass */ SymmetricMat3 getTranslatedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& COMOffset) { SymmetricMat3 translationFactor = skewSymmetricSquared(COMOffset); return originalInertia - translationFactor * mass; } SymmetricMat3 getTransformedInertia(const SymmetricMat3& originalInertia, double mass, const CFrame& cframe, const Vec3& centerOfMass) { return getTranslatedInertia(cframe.getRotation().localToGlobal(originalInertia), mass, cframe.getPosition(), centerOfMass); } /* computes a translated inertial matrix, and it's derivatives COMOffset is the offset of the object's center of mass from the resulting center of mass motionOfOffset is the change of COMOffset over time, this is relative to the motion of the COM towards which this is computed */ FullTaylor getTranslatedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& COMOffset, const TranslationalMotion& motionOfOffset) { FullTaylor result = -generateFullTaylorForSkewSymmetricSquared(FullTaylor::fromConstantAndDerivatives(COMOffset, motionOfOffset.translation)); result *= mass; result += originalInertia; return result; } /* computes a rotated inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object rotation is the starting rotation, and rotationMotion gives the change in rotation, both expressed in global space */ FullTaylor getRotatedInertiaTaylor(const SymmetricMat3& originalInertia, const Rotation& rotation, const RotationalMotion& rotationMotion) { Mat3 rotationMat = rotation.asRotationMatrix(); Taylor rotationDerivs = generateTaylorForRotationMatrix(rotationMotion.rotation, rotationMat); // rotation(t) * originalInertia * rotation(t).transpose() // diff => rotation(t) * originalInertia * rotation'(t).transpose() + rotation'(t) * originalInertia * rotation(t).transpose() // diff2 => 2 * rotation'(t) * originalInertia * rotation'(t).transpose() + rotation(t) * originalInertia * rotation''(t).transpose() + rotation''(t) * originalInertia * rotation(t).transpose() return FullTaylor{ rotation.localToGlobal(originalInertia), addTransposed(rotationMat * originalInertia * rotationDerivs[0].transpose()), addTransposed(rotationMat * originalInertia * rotationDerivs[1].transpose()) + 2.0 * mulSymmetricLeftRightTranspose(originalInertia, rotationDerivs[0])}; } /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object localCenterOfMass is the center of mass of the transformed object offsetCFrame is the offset of the object to it's new position relative to the total Center Of Mass */ SymmetricMat3 getTransformedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& localCenterOfMass, const CFrame& offsetCFrame) { Vec3 resultingOffset = offsetCFrame.localToGlobal(localCenterOfMass); return getTransformedInertiaAroundCenterOfMass(originalInertia, mass, CFrame(resultingOffset, offsetCFrame.getRotation())); } /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object offsetCFrame is the offset of the object's center of mass and rotation relative to the COM of it's parent. */ SymmetricMat3 getTransformedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const CFrame& offsetCFrame) { SymmetricMat3 translationFactor = skewSymmetricSquared(offsetCFrame.getPosition()); return getRotatedInertia(originalInertia, offsetCFrame.getRotation()) - translationFactor * mass; } /* computes the derivatives of a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object localCenterOfMass is the local center of mass in the local coordinate system of startingCFrame startingCFrame is the current relative position motion is the relative motion of the offset object's center of mass relative to the total center of mass, in the coordinate system of the total center of mass. */ FullTaylor getTransformedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& localCenterOfMass, const CFrame& startingCFrame, const Motion& motion) { Vec3 relativeOffset = startingCFrame.localToRelative(localCenterOfMass); Vec3 resultingOffset = startingCFrame.getPosition() + relativeOffset; Motion resultingMotion = motion.getMotionOfPoint(relativeOffset); return getTransformedInertiaDerivativesAroundCenterOfMass(originalInertia, mass, CFrame(resultingOffset, startingCFrame.getRotation()), resultingMotion); } /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object startingCFrame is the current relative position motion is the relative motion of the offset object's center of mass relative to the total center of mass, in the coordinate system of the total center of mass. */ FullTaylor getTransformedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const CFrame& startingCFrame, const Motion& motion) { FullTaylor translation = FullTaylor::fromConstantAndDerivatives(startingCFrame.getPosition(), motion.translation.translation); Rotation originalRotation = startingCFrame.getRotation(); RotationalMotion rotationMotion = motion.rotation; FullTaylor translationFactor = -generateFullTaylorForSkewSymmetricSquared(translation) * mass; //translationFactor.constantValue += originalInertia; FullTaylor rotationFactor = getRotatedInertiaTaylor(originalInertia, originalRotation, rotationMotion); return translationFactor + rotationFactor; } }; ================================================ FILE: Physics3D/inertia.h ================================================ #pragma once #include "math/linalg/mat.h" #include "math/cframe.h" #include "math/taylorExpansion.h" #include "motion.h" #include "relativeMotion.h" namespace P3D { SymmetricMat3 getRotatedInertia(const SymmetricMat3& originalInertia, const Rotation& rotation); SymmetricMat3 getTranslatedInertia(const SymmetricMat3& originalInertia, double mass, const Vec3& translation, const Vec3& centerOfMass); SymmetricMat3 getTransformedInertia(const SymmetricMat3& originalInertia, double mass, const CFrame& cframe, const Vec3& centerOfMass); /* computes a translated inertial matrix, COMOffset is the offset of the object's center of mass from the resulting center of mass == localCenterOfMass - totalCenterOfMass */ SymmetricMat3 getTranslatedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& COMOffset); /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object offsetCFrame is the offset of the object's center of mass and rotation relative to the COM of it's parent. */ SymmetricMat3 getTransformedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const CFrame& offsetCFrame); /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object localCenterOfMass is the center of mass of the transformed object offsetCFrame is the offset of the object to it's new position relative to the total Center Of Mass */ SymmetricMat3 getTransformedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& localCenterOfMass, const CFrame& offsetCFrame); /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object offsetCFrame is the offset of the object's center of mass and rotation relative to the COM of it's parent. */ SymmetricMat3 getTransformedInertiaAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const CFrame& offsetCFrame); /* computes a rotated inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object rotation is the starting rotation, and rotationMotion gives the change in rotation, both expressed in global space */ FullTaylor getRotatedInertiaTaylor(const SymmetricMat3& originalInertia, const Rotation& rotation, const RotationalMotion& rotationMotion); /* computes a translated inertial matrix, and it's derivatives COMOffset is the offset of the object's center of mass from the resulting center of mass motionOfOffset is the change of COMOffset over time, this is relative to the motion of the COM towards which this is computed */ FullTaylor getTranslatedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& COMOffset, const TranslationalMotion& motionOfOffset); /* computes a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object startingCFrame is the current relative position motion is the relative motion of the offset object's center of mass relative to the total center of mass, in the coordinate system of the total center of mass. */ FullTaylor getTransformedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const CFrame& startingCFrame, const Motion& motion); /* computes the derivatives of a transformed inertial matrix, where originalInertia is the inertia around the center of mass of the transformed object localCenterOfMass is the local center of mass in the local coordinate system of startingCFrame startingCFrame is the current relative position motion is the relative motion of the offset object's center of mass relative to the total center of mass, in the coordinate system of the total center of mass. */ FullTaylor getTransformedInertiaDerivativesAroundCenterOfMass(const SymmetricMat3& originalInertia, double mass, const Vec3& localCenterOfMass, const CFrame& startingCFrame, const Motion& motion); inline Vec3 getAngularMomentumFromOffsetOnlyVelocity(const Vec3& offset, const Vec3& velocity, double mass) { return offset % velocity * mass; } // offsetInertia = originalInertia - [ComOffset]x ^ 2 * mass // angularMomemtum = (offsetInertia - [ComOffset]x ^ 2 * mass) * angularVel // == offsetInertia * angularVel - ComOffset % (ComOffset % angularVel) * mass // == offsetInertia * (velocity % ComOffset)/|ComOffset|^2 + ComOffset % velocity * mass // leftOverAngularMomentum = inline Vec3 getAngularMomentumFromOffset(const Vec3& offset, const Vec3& velocity, const Vec3& angularVelocity, const SymmetricMat3& inertia, double mass) { Vec3 velocityAngularMomentum = getAngularMomentumFromOffsetOnlyVelocity(offset, velocity, mass); Vec3 rotationAngularMomentum = inertia * angularVelocity; return velocityAngularMomentum + rotationAngularMomentum; } }; ================================================ FILE: Physics3D/layer.cpp ================================================ #include "layer.h" #include "world.h" #include "misc/validityHelper.h" #include "misc/debug.h" #include "misc/physicsProfiler.h" #include namespace P3D { WorldLayer::WorldLayer(ColissionLayer* parent) : parent(parent) {} WorldLayer::~WorldLayer() { tree.forEach([](Part& p) { p.layer = nullptr; }); } WorldLayer::WorldLayer(WorldLayer&& other) noexcept : tree(std::move(other.tree)), parent(other.parent) { tree.forEach([this, &other](Part& p) { assert(p.layer = &other); p.layer = this; }); } WorldLayer& WorldLayer::operator=(WorldLayer&& other) noexcept { std::swap(tree, other.tree); std::swap(parent, other.parent); tree.forEach([this, &other](Part& p) { assert(p.layer = &other); p.layer = this; }); other.tree.forEach([this, &other](Part& p) { assert(p.layer = this); p.layer = &other; }); return *this; } void WorldLayer::refresh() { physicsMeasure.mark(PhysicsProcess::UPDATE_TREE_BOUNDS); tree.recalculateBounds(); physicsMeasure.mark(PhysicsProcess::UPDATE_TREE_STRUCTURE); tree.improveStructure(); } void WorldLayer::addPart(Part* newPart) { tree.add(newPart); } static void addMotorPhysToGroup(BoundsTree& tree, MotorizedPhysical* phys, Part* group) { auto base = tree.getPrototype().getBaseTrunk(); TrunkAllocator& alloc = tree.getPrototype().getAllocator(); modifyGroupRecursive(alloc, base.first, base.second, group, group->getBounds(), [&](TreeNodeRef& groupNode, const BoundsTemplate& groupNodeBounds) { Part* mp = phys->getMainPart(); if(groupNode.isLeafNode()) { TreeTrunk* newTrunk = alloc.allocTrunk(); newTrunk->setSubNode(0, std::move(groupNode), groupNodeBounds); newTrunk->setSubNode(1, TreeNodeRef(mp), mp->getBounds()); groupNode = TreeNodeRef(newTrunk, 2, true); } else { addRecursive(alloc, groupNode.asTrunk(), groupNode.getTrunkSize(), TreeNodeRef(mp), mp->getBounds()); } TreeTrunk& curTrunk = groupNode.asTrunk(); int curTrunkSize = groupNode.getTrunkSize(); phys->forEachPartExceptMainPart([&](Part& part) { curTrunkSize = addRecursive(alloc, curTrunk, curTrunkSize, TreeNodeRef(&part), part.getBounds()); }); groupNode.setTrunkSize(curTrunkSize); return TrunkSIMDHelperFallback::getTotalBounds(curTrunk, curTrunkSize); }); } void WorldLayer::addIntoGroup(Part* newPart, Part* group) { assert(newPart->layer == nullptr); assert(group->layer == this); Physical* partPhys = newPart->getPhysical(); if(partPhys != nullptr) { MotorizedPhysical* mainPhys = partPhys->mainPhysical; addMotorPhysToGroup(tree, mainPhys, group); mainPhys->forEachPart([this](Part& p) {p.layer = this; }); } else { tree.addToGroup(newPart, group); newPart->layer = this; } } void WorldLayer::moveOutOfGroup(Part* part) { this->tree.moveOutOfGroup(part); } void WorldLayer::removePart(Part* partToRemove) { assert(partToRemove->layer == this); tree.remove(partToRemove); parent->world->onPartRemoved(partToRemove); partToRemove->layer = nullptr; } void WorldLayer::notifyPartBoundsUpdated(const Part* updatedPart, const Bounds& oldBounds) { tree.updateObjectBounds(updatedPart, oldBounds); } void WorldLayer::notifyPartGroupBoundsUpdated(const Part* mainPart, const Bounds& oldMainPartBounds) { tree.updateObjectGroupBounds(mainPart, oldMainPartBounds); } void WorldLayer::notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept { tree.findAndReplaceObject(oldPartPtr, newPartPtr, newPartPtr->getBounds()); } void WorldLayer::mergeGroups(Part* first, Part* second) { this->tree.mergeGroups(first, second); } // TODO can be optimized, this only needs to move the single partToMove node void WorldLayer::moveIntoGroup(Part* partToMove, Part* group) { this->tree.mergeGroups(partToMove, group); } // TODO can be optimized, this only needs to move the single part nodes void WorldLayer::joinPartsIntoNewGroup(Part* p1, Part* p2) { this->tree.mergeGroups(p1, p2); } int WorldLayer::getID() const { return (parent->getID() * ColissionLayer::NUMBER_OF_SUBLAYERS) + static_cast(this - parent->subLayers); } WorldLayer* getLayerByID(std::vector& knownLayers, int id) { return &knownLayers[id / ColissionLayer::NUMBER_OF_SUBLAYERS].subLayers[id % ColissionLayer::NUMBER_OF_SUBLAYERS]; } const WorldLayer* getLayerByID(const std::vector& knownLayers, int id) { return &knownLayers[id / ColissionLayer::NUMBER_OF_SUBLAYERS].subLayers[id % ColissionLayer::NUMBER_OF_SUBLAYERS]; } int getMaxLayerID(const std::vector& knownLayers) { return static_cast(knownLayers.size()) * ColissionLayer::NUMBER_OF_SUBLAYERS; } int ColissionLayer::getID() const { return static_cast(this - &world->layers[0]); } ColissionLayer::ColissionLayer() : world(nullptr), collidesInternally(true), subLayers{WorldLayer(this), WorldLayer(this)} {} ColissionLayer::ColissionLayer(WorldPrototype* world, bool collidesInternally) : world(world), collidesInternally(collidesInternally), subLayers{WorldLayer(this), WorldLayer(this)} {} ColissionLayer::ColissionLayer(ColissionLayer&& other) noexcept : world(other.world), collidesInternally(other.collidesInternally), subLayers{std::move(other.subLayers[0]), std::move(other.subLayers[1])} { other.world = nullptr; for(WorldLayer& l : subLayers) { l.parent = this; } } ColissionLayer& ColissionLayer::operator=(ColissionLayer&& other) noexcept { std::swap(this->world, other.world); std::swap(this->subLayers, other.subLayers); std::swap(this->collidesInternally, other.collidesInternally); for(WorldLayer& l : subLayers) { l.parent = this; } for(WorldLayer& l : other.subLayers) { l.parent = &other; } return *this; } void ColissionLayer::refresh() { subLayers[FREE_PARTS_LAYER].refresh(); } static bool boundsSphereEarlyEnd(const DiagonalMat3& scale, const Vec3& sphereCenter, double sphereRadius) { return std::abs(sphereCenter.x) > scale[0] + sphereRadius || std::abs(sphereCenter.y) > scale[1] + sphereRadius || std::abs(sphereCenter.z) > scale[2] + sphereRadius; } static bool runColissionPreTests(const Part& p1, const Part& p2) { Vec3 offset = p1.getPosition() - p2.getPosition(); if(isLongerThan(offset, p1.maxRadius + p2.maxRadius)) { intersectionStatistics.addToTally(IntersectionResult::PART_DISTANCE_REJECT, 1); return false; } if(boundsSphereEarlyEnd(p1.hitbox.scale, p1.getCFrame().globalToLocal(p2.getPosition()), p2.maxRadius)) { intersectionStatistics.addToTally(IntersectionResult::PART_BOUNDS_REJECT, 1); return false; } if(boundsSphereEarlyEnd(p2.hitbox.scale, p2.getCFrame().globalToLocal(p1.getPosition()), p1.maxRadius)) { intersectionStatistics.addToTally(IntersectionResult::PART_BOUNDS_REJECT, 1); return false; } return true; } static void findColissionsBetween(std::vector& colissions, const BoundsTree& treeA, const BoundsTree& treeB) { treeA.forEachColissionWith(treeB, [&colissions](Part* a, Part* b) { colissions.push_back(Colission{a, b}); }); } static void findColissionsInternal(std::vector& colissions, const BoundsTree& tree) { tree.forEachColission([&colissions](Part* a, Part* b) { colissions.push_back(Colission{a, b}); }); } void ColissionLayer::getInternalColissions(ColissionBuffer& curColissions) const { findColissionsInternal(curColissions.freePartColissions, subLayers[0].tree); findColissionsBetween(curColissions.freeTerrainColissions, subLayers[0].tree, subLayers[1].tree); } void getColissionsBetween(const ColissionLayer& a, const ColissionLayer& b, ColissionBuffer& curColissions) { findColissionsBetween(curColissions.freePartColissions, a.subLayers[0].tree, b.subLayers[0].tree); findColissionsBetween(curColissions.freeTerrainColissions, a.subLayers[0].tree, b.subLayers[1].tree); findColissionsBetween(curColissions.freeTerrainColissions, b.subLayers[0].tree, a.subLayers[1].tree); } }; ================================================ FILE: Physics3D/layer.h ================================================ #pragma once #include #include "boundstree/boundsTree.h" #include "part.h" #include "colissionBuffer.h" namespace P3D { class WorldPrototype; class ColissionLayer; class WorldLayer { public: BoundsTree tree; ColissionLayer* parent; explicit WorldLayer(ColissionLayer* parent); WorldLayer(WorldLayer&& other) noexcept; WorldLayer& operator=(WorldLayer&& other) noexcept; WorldLayer(const WorldLayer&) = delete; WorldLayer& operator=(const WorldLayer&) = delete; ~WorldLayer(); void refresh(); void addPart(Part* newPart); void removePart(Part* partToRemove); void addIntoGroup(Part* newPart, Part* group); template void addAllToGroup(PartIterBegin begin, PartIterEnd end, Part* group) { tree.addAllToGroup(begin, end, group); } //void addIntoGroup(MotorizedPhysical* newPhys, Part* group); void notifyPartBoundsUpdated(const Part* updatedPart, const Bounds& oldBounds); void notifyPartGroupBoundsUpdated(const Part* mainPart, const Bounds& oldMainPartBounds); /* When a part is std::move'd to a different location, this function is called to update any pointers This is something that in general should not be performed when the part is already in a world, but this function is provided for completeness */ void notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept; void mergeGroups(Part* first, Part* second); void moveIntoGroup(Part* partToMove, Part* group); void moveOutOfGroup(Part* part); void joinPartsIntoNewGroup(Part* p1, Part* p2); template void splitGroup(PartIterBegin begin, PartIterEnd end) { tree.splitGroup(begin, end); } void optimize() { tree.maxImproveStructure(); } template void forEach(const Func& funcToRun) const { tree.forEach(funcToRun); } template void forEachFiltered(const Filter& filter, const Func& funcToRun) const { tree.forEachFiltered(filter, funcToRun); } int getID() const; }; WorldLayer* getLayerByID(std::vector& knownLayers, int id); const WorldLayer* getLayerByID(const std::vector& knownLayers, int id); int getMaxLayerID(const std::vector& knownLayers); class ColissionLayer { public: static constexpr int FREE_PARTS_LAYER = 0; static constexpr int TERRAIN_PARTS_LAYER = 1; static constexpr int NUMBER_OF_SUBLAYERS = 2; WorldLayer subLayers[NUMBER_OF_SUBLAYERS]; // freePartsLayer // terrainLayer WorldPrototype* world; bool collidesInternally; ColissionLayer(); ColissionLayer(WorldPrototype* world, bool collidesInternally); ColissionLayer(ColissionLayer&& other) noexcept; ColissionLayer& operator=(ColissionLayer&& other) noexcept; void refresh(); void getInternalColissions(ColissionBuffer& curColissions) const; template void forEach(const Func& funcToRun) const { for(const WorldLayer& subLayer : this->subLayers) { subLayer.forEach(funcToRun); } } template void forEachFiltered(const Filter& filter, const Func& funcToRun) const { for(const WorldLayer& subLayer : this->subLayers) { subLayer.forEachFiltered(filter, funcToRun); } } int getID() const; }; void getColissionsBetween(const ColissionLayer& a, const ColissionLayer& b, ColissionBuffer& curColissions); }; ================================================ FILE: Physics3D/math/boundingBox.h ================================================ #pragma once #include "linalg/vec.h" namespace P3D { template struct BoundingBoxTemplate { Vector min; Vector max; BoundingBoxTemplate() : min(), max() {} BoundingBoxTemplate(T x, T y, T z) : min(-x / 2, -y / 2, -z / 2), max(x / 2, y / 2, z / 2) {} BoundingBoxTemplate(T xmin, T ymin, T zmin, T xmax, T ymax, T zmax) : min(xmin, ymin, zmin), max(xmax, ymax, zmax) {} BoundingBoxTemplate(Vector min, Vector max) : min(min), max(max) {} [[nodiscard]] bool intersects(const BoundingBoxTemplate& o) const { return min.x <= o.max.x && max.x >= o.min.x && min.y <= o.max.y && max.y >= o.min.y && min.z <= o.max.z && max.z >= o.min.z; } [[nodiscard]] bool containsPoint(const Vector& point) const { return point.x >= min.x && point.x <= max.x && point.y >= min.y && point.y <= max.y && point.z >= min.z && point.z <= max.z; } [[nodiscard]] T getWidth() const { return max.x - min.x; } [[nodiscard]] T getHeight() const { return max.y - min.y; } [[nodiscard]] T getDepth() const { return max.z - min.z; } [[nodiscard]] Vector getCenter() const { return (min + max) * 0.5; } [[nodiscard]] BoundingBoxTemplate expanded(T amount) const { return BoundingBoxTemplate(min.x - amount, min.y - amount, min.z - amount, max.x + amount, max.y + amount, max.z + amount); } [[nodiscard]] BoundingBoxTemplate expanded(const Vector& p) { return BoundingBoxTemplate( p.x < min.x ? p.x : min.x, p.y < min.y ? p.y : min.y, p.z < min.z ? p.z : min.z, p.x > max.x ? p.x : max.x, p.y > max.y ? p.y : max.y, p.z > max.z ? p.z : max.z ); } [[nodiscard]] BoundingBoxTemplate expanded(const BoundingBoxTemplate& o) { return BoundingBoxTemplate( o.min.x < min.x ? o.min.x : min.x, o.min.y < min.y ? o.min.y : min.y, o.min.z < min.z ? o.min.z : min.z, o.max.x > max.x ? o.max.x : max.x, o.max.y > max.y ? o.max.y : max.y, o.max.z > max.z ? o.max.z : max.z ); } [[nodiscard]] BoundingBoxTemplate scaled(T scaleX, T scaleY, T scaleZ) { return BoundingBoxTemplate{min.x * scaleX, min.y * scaleY, min.z * scaleZ, max.x * scaleX, max.y * scaleY, max.z * scaleZ}; } }; typedef BoundingBoxTemplate BoundingBox; struct CircumscribingSphere { Vec3 origin = Vec3(); double radius = 0; }; }; ================================================ FILE: Physics3D/math/bounds.h ================================================ #pragma once #include "position.h" #include "boundingBox.h" #include namespace P3D { template struct BoundsTemplate { PositionTemplate min; PositionTemplate max; constexpr BoundsTemplate() = default; constexpr BoundsTemplate(const PositionTemplate& min, const PositionTemplate& max) noexcept : min(min), max(max) {} BoundsTemplate(const BoundsTemplate&) = default; BoundsTemplate& operator=(const BoundsTemplate&) = default; // conversion constructors. Bounds should expand with rounding errors, bounds must always be at least as large as the object they're bounding template constexpr BoundsTemplate(const BoundsTemplate& from) noexcept : min(), max() { int curRound = fegetround(); fesetround(FE_UPWARD); this->min.x = static_cast(from.min.x); this->min.y = static_cast(from.min.y); this->min.z = static_cast(from.min.z); fesetround(FE_DOWNWARD); this->max.x = static_cast(from.max.x); this->max.y = static_cast(from.max.y); this->max.z = static_cast(from.max.z); fesetround(curRound); } template constexpr BoundsTemplate& operator=(const BoundsTemplate& from) noexcept { int curRound = fegetround(); fesetround(FE_UPWARD); this->min.x = static_cast(from.min.x); this->min.y = static_cast(from.min.y); this->min.z = static_cast(from.min.z); fesetround(FE_DOWNWARD); this->max.x = static_cast(from.max.x); this->max.y = static_cast(from.max.y); this->max.z = static_cast(from.max.z); fesetround(curRound); return *this; } constexpr Vector getDiagonal() const noexcept { return max - min; } constexpr bool contains(const PositionTemplate& p) const noexcept { return p >= min && p <= max; } constexpr bool contains(const BoundsTemplate& other) const noexcept { return other.min >= this->min && other.max <= this->max; } constexpr PositionTemplate getCenter() const noexcept { return avg(min, max); } constexpr BoundsTemplate expanded(T amount) const noexcept { return BoundsTemplate(min - Vector(amount, amount, amount), max + Vector(amount, amount, amount)); } constexpr BoundsTemplate expanded(Vector amount) const noexcept { return BoundsTemplate(min - amount, max + amount); } constexpr T getWidth() const noexcept { return max.x - min.x; } constexpr T getHeight() const noexcept { return max.y - min.y; } constexpr T getDepth() const noexcept { return max.z - min.z; } constexpr T getVolume() const noexcept { Vector diag = max - min; return diag.x * diag.y * diag.z; } }; template constexpr bool intersects(const BoundsTemplate& first, const BoundsTemplate& second) noexcept { return first.max >= second.min && first.min <= second.max; } template constexpr BoundsTemplate unionOfBounds(const BoundsTemplate& first, const BoundsTemplate& second) noexcept { return BoundsTemplate(min(first.min, second.min), max(first.max, second.max)); } template constexpr bool operator==(const BoundsTemplate& first, const BoundsTemplate& second) noexcept { return first.min == second.min && first.max == second.max; } template constexpr bool operator!=(const BoundsTemplate& first, const BoundsTemplate& second) noexcept { return first.min != second.min || first.max != second.max; } template constexpr BoundsTemplate operator+(const BoundingBoxTemplate& localBox, const PositionTemplate& pos) noexcept { PositionTemplate min = pos + localBox.min; PositionTemplate max = pos + localBox.max; return BoundsTemplate(min, max); } template constexpr BoundingBoxTemplate operator-(const BoundsTemplate& box, const PositionTemplate& pos) noexcept { Vector min = box.min - pos; Vector max = box.max - pos; return BoundingBoxTemplate(min, max); } typedef BoundsTemplate> Bounds; }; ================================================ FILE: Physics3D/math/cframe.h ================================================ #pragma once #include "linalg/vec.h" #include "rotation.h" namespace P3D { template struct CFrameTemplate { public: Vector position; RotationTemplate rotation; CFrameTemplate(const Vector& position, const RotationTemplate& rotation) : position(position), rotation(rotation) {} explicit CFrameTemplate(const Vector& position) : position(position), rotation() {} explicit CFrameTemplate(const T& x, const T& y, const T& z) : position(x, y, z), rotation() {} explicit CFrameTemplate(const T& x, const T& y, const T& z, const RotationTemplate& rotation) : position(x, y, z), rotation(rotation) {} explicit CFrameTemplate(const RotationTemplate& rotation) : position(0,0,0), rotation(rotation) {} CFrameTemplate() : position(0, 0, 0), rotation() {} template CFrameTemplate(const CFrameTemplate& other) : position(static_cast>(other.position)), rotation(static_cast>(other.rotation)) {} Vector localToGlobal(const Vector& lVec) const { return rotation.localToGlobal(lVec) + position; } Vector globalToLocal(const Vector& gVec) const { return rotation.globalToLocal(gVec - position); } Vector localToRelative(const Vector& lVec) const { return rotation.localToGlobal(lVec); } Vector relativeToLocal(const Vector& rVec) const { return rotation.globalToLocal(rVec); } Vector globalToRelative(const Vector & gVec) const { return gVec - position; } Vector relativeToGlobal(const Vector & rVec) const { return rVec + position; } CFrameTemplate localToGlobal(const CFrameTemplate& lFrame) const { return CFrameTemplate(position + rotation.localToGlobal(lFrame.position), rotation.localToGlobal(lFrame.rotation)); } CFrameTemplate globalToLocal(const CFrameTemplate& gFrame) const { return CFrameTemplate(rotation.globalToLocal(gFrame.position - position), rotation.globalToLocal(gFrame.rotation)); } CFrameTemplate localToRelative(const CFrameTemplate& lFrame) const { return CFrameTemplate(rotation.localToGlobal(lFrame.position), rotation.localToGlobal(lFrame.rotation)); } CFrameTemplate relativeToLocal(const CFrameTemplate& rFrame) const { return CFrameTemplate(rotation.globalToLocal(rFrame.position), rotation.globalToLocal(rFrame.rotation)); } CFrameTemplate globalToRelative(const CFrameTemplate& gFrame) const { return CFrameTemplate(gFrame.position - this->position, gFrame.rotation); } CFrameTemplate relativeToGlobal(const CFrameTemplate& gFrame) const { return CFrameTemplate(gFrame.position + this->position, gFrame.rotation); } RotationTemplate localToGlobal(const RotationTemplate& localRot) const { return rotation.localToGlobal(localRot); } RotationTemplate globalToLocal(const RotationTemplate& globalRot) const { return rotation.globalToLocal(globalRot); } CFrameTemplate operator~() const { return CFrameTemplate(rotation.globalToLocal(-position), ~rotation); } Vector getPosition() const { return position; } RotationTemplate getRotation() const { return rotation; } void translate(const Vector& delta) { this->position += delta; } void rotate(const Rotation& rot) { this->rotation = rot * this->rotation; } CFrameTemplate& operator+=(const Vector& delta) { position += delta; return *this; } CFrameTemplate& operator-=(const Vector& delta) { position -= delta; return *this; } CFrameTemplate operator+(const Vector& delta) const { return CFrameTemplate(this->position + delta, this->rotation); } CFrameTemplate operator-(const Vector& delta) const { return CFrameTemplate(this->position - delta, this->rotation); } CFrameTemplate extendLocal(const Vector& delta) const { return CFrameTemplate(this->localToGlobal(delta), this->rotation); } /* Converts this CFrame to a 4x4 matrix, where for any Vec3 p: cframe.asMat4() * Vec4(p, 1.0) == cframe.localToGlobal(p) */ Matrix asMat4() const { return Matrix(rotation.asRotationMatrix(), this->position, Vector(), static_cast(1)); } /* Converts this CFrame to a 4x4 matrix with given scaling factor, where for any Vec3 p: cframe.asMat4WithPreScale(scale) * Vec4(p, 1.0) == cframe.localToGlobal(scale * p) */ Matrix asMat4WithPreScale(const DiagonalMatrix& scale) const { return Matrix(rotation.asRotationMatrix() * scale, this->position, Vector(), static_cast(1)); } /* Converts this CFrame to a 4x4 matrix with given scaling factor, where for any Vec3 p: cframe.asMat4WithPostScale(scale) * Vec4(p, 1.0) == scale * cframe.localToGlobal(p) */ Matrix asMat4WithPostScale(const DiagonalMatrix& scale) const { return Matrix(scale * rotation.asRotationMatrix(), this->position, Vector::ZEROS(), static_cast(1)); } }; typedef CFrameTemplate CFrame; typedef CFrameTemplate CFramef; }; ================================================ FILE: Physics3D/math/constants.h ================================================ #pragma once #include namespace P3D { #define EPSILON(T) epsilon() #define PI pi() #define E e() #define G g() #define HALF_PI half_pi() #define TWO_PI two_pi() #define GOLDEN_RATIO golden_ratio() #define SQ2_2 sq2_2() #define SQ3_2 sq3_2() template constexpr T epsilon() { return std::numeric_limits::epsilon(); } template constexpr T pi() { return static_cast(3.14159265358979323846264338327950288); } template constexpr T two_pi() { return static_cast(6.28318530717958647692528676655900576); } template constexpr T half_pi() { return static_cast(1.57079632679489661923132169163975144); } template constexpr T e() { return static_cast(2.71828182845904523536028747135266249); } template constexpr T g() { return static_cast(1.61803398874989484820458683436563811772030917980576286213544862270526046281890); } template constexpr T golden_ratio() { return static_cast(1.61803398874989484820458683436563811); } template constexpr T sq2_2() { return static_cast(0.70710678118654752440084436210484903); } template constexpr T sq3_2() { return static_cast(0.86602540378443864676372317075293618); } }; ================================================ FILE: Physics3D/math/fix.h ================================================ #pragma once #include namespace P3D { template struct Fix { int64_t value; static constexpr int64_t ONE = int64_t(1) << N; constexpr Fix() noexcept : value(0) {} constexpr Fix(double d) noexcept : value(static_cast(d * ONE)) {} constexpr Fix(float f) noexcept : value(static_cast(static_cast(f) * ONE)) {} constexpr Fix(int l) noexcept : value(static_cast(l) << N) {} constexpr Fix(int64_t l) noexcept : value(l << N) {} inline constexpr operator double() const noexcept { return static_cast(value) / ONE; } inline constexpr operator float() const noexcept { return static_cast(value) / ONE; } inline explicit constexpr operator int64_t() const noexcept { if(value >= 0 || (value & (ONE-1)) == 0) { return value >> N; } else { return (value >> N) + 1; } } inline Fix& operator++() noexcept { value += ONE; return *this; } inline Fix operator++(int) & noexcept { Fix old = *this; value += ONE; return old; } inline Fix& operator--() noexcept { value -= ONE; return *this; } inline Fix operator--(int) & noexcept { Fix old = *this; value -= ONE; return old; } inline Fix& operator+=(const Fix& b) noexcept { this->value += b.value; return *this; } inline Fix& operator-=(const Fix& b) noexcept { this->value -= b.value; return *this; } inline Fix& operator+=(int64_t b) noexcept { this->value += (b << N); return *this; } inline Fix& operator-=(int64_t b) noexcept { this->value -= (b << N); return *this; } inline Fix& operator+=(double b) noexcept { this->value += static_cast(ONE * b); return *this; } inline Fix& operator-=(double b) noexcept { this->value -= static_cast(ONE * b); return *this; } inline Fix& operator+=(float b) noexcept { this->value += static_cast(ONE * static_cast(b)); return *this; } inline Fix& operator-=(float b) noexcept { this->value -= static_cast(ONE * static_cast(b)); return *this; } inline constexpr Fix operator-() const noexcept {Fix result; result.value = -this->value; return result;} }; template inline constexpr Fix operator+(Fix a, Fix b) noexcept {Fix result; result.value = a.value + b.value; return result;} template inline constexpr Fix operator-(Fix a, Fix b) noexcept {Fix result; result.value = a.value - b.value; return result;} template inline constexpr Fix operator+(Fix a, int64_t b) noexcept {Fix result; result.value = a.value + (b << N); return result;} template inline constexpr Fix operator-(Fix a, int64_t b) noexcept {Fix result; result.value = a.value - (b << N); return result;} template inline constexpr Fix operator+(int64_t a, Fix b) noexcept {Fix result; result.value = (a << N) + b.value; return result;} template inline constexpr Fix operator-(int64_t a, Fix b) noexcept {Fix result; result.value = (a << N) - b.value; return result;} template inline constexpr Fix operator+(Fix a, double b) noexcept {return a + Fix(b);} template inline constexpr Fix operator+(double a, Fix b) noexcept {return Fix(a) + b;} template inline constexpr Fix operator-(Fix a, double b) noexcept {return a - Fix(b);} template inline constexpr Fix operator-(double a, Fix b) noexcept {return Fix(a) - b;} template inline constexpr Fix operator+(Fix a, float b) noexcept {return a + Fix(b);} template inline constexpr Fix operator+(float a, Fix b) noexcept {return Fix(a) + b;} template inline constexpr Fix operator-(Fix a, float b) noexcept {return a - Fix(b);} template inline constexpr Fix operator-(float a, Fix b) noexcept {return Fix(a) - b;} template inline constexpr Fix operator*(Fix a, int64_t b) noexcept {Fix result; result.value = a.value * b; return result;} template inline constexpr Fix operator*(int64_t a, Fix b) noexcept {Fix result; result.value = a * b.value; return result;} template inline constexpr Fix operator*(Fix a, int b) noexcept { Fix result; result.value = a.value * b; return result; } template inline constexpr Fix operator*(int a, Fix b) noexcept { Fix result; result.value = a * b.value; return result; } template inline constexpr Fix operator*(Fix a, double b) noexcept {Fix result; result.value = static_cast(a.value * b); return result;} template inline constexpr Fix operator*(Fix a, float b) noexcept {Fix result; result.value = static_cast(a.value * b); return result;} template inline constexpr Fix operator*(double a, Fix b) noexcept {Fix result; result.value = static_cast(a * b.value); return result;} template inline constexpr Fix operator*(float a, Fix b) noexcept {Fix result; result.value = static_cast(a * b.value); return result;} template inline constexpr Fix operator/(Fix a, double b) noexcept {Fix result; result.value = static_cast(a.value / b); return result;} template inline constexpr Fix operator/(Fix a, float b) noexcept {Fix result; result.value = static_cast(a.value / b); return result;} template inline constexpr Fix operator/(double a, Fix b) noexcept {Fix result; result.value = static_cast(a / b.value); return result;} template inline constexpr Fix operator/(float a, Fix b) noexcept {Fix result; result.value = static_cast(a / b.value); return result;} template inline constexpr Fix operator/(Fix a, int64_t b) noexcept {Fix result; result.value = a.value / b; return result;} template inline constexpr Fix operator/(Fix a, int b) noexcept { Fix result; result.value = a.value / b; return result; } template inline constexpr Fix operator<<(Fix a, int64_t b) noexcept {Fix result; result.value = a.value << b; return result;} template inline constexpr Fix operator>>(Fix a, int64_t b) noexcept {Fix result; result.value = a.value >> b; return result;} template inline constexpr Fix operator<<(Fix a, int b) noexcept { Fix result; result.value = a.value << b; return result; } template inline constexpr Fix operator>>(Fix a, int b) noexcept { Fix result; result.value = a.value >> b; return result; } #define CREATE_COMPARISONS(T1, T2, V1, V2) \ template inline constexpr bool operator==(T1 a, T2 b) noexcept { return V1 == V2; } \ template inline constexpr bool operator!=(T1 a, T2 b) noexcept { return V1 != V2; } \ template inline constexpr bool operator>=(T1 a, T2 b) noexcept { return V1 >= V2; } \ template inline constexpr bool operator<=(T1 a, T2 b) noexcept { return V1 <= V2; } \ template inline constexpr bool operator>(T1 a, T2 b) noexcept { return V1 > V2; } \ template inline constexpr bool operator<(T1 a, T2 b) noexcept { return V1 < V2; } CREATE_COMPARISONS(Fix, Fix, a.value, b.value); CREATE_COMPARISONS(Fix, double, a, Fix(b)); CREATE_COMPARISONS(double, Fix, Fix(a), b); CREATE_COMPARISONS(Fix, float, a, Fix(b)); CREATE_COMPARISONS(float, Fix, Fix(a), b); CREATE_COMPARISONS(Fix, int64_t, a.value, b << N); CREATE_COMPARISONS(int64_t, Fix, a << N, b.value); CREATE_COMPARISONS(Fix, int, a.value, int64_t(b) << N); CREATE_COMPARISONS(int, Fix, int64_t(a) << N, b.value); #undef CREATE_COMPARISONS template inline constexpr Fix min(Fix first, Fix second) noexcept { return (first.value <= second.value) ? first : second; } template inline constexpr Fix max(Fix first, Fix second) noexcept { return (first.value >= second.value) ? first : second; } }; ================================================ FILE: Physics3D/math/globalCFrame.h ================================================ #pragma once #include "position.h" #include "rotation.h" #include "cframe.h" namespace P3D { class GlobalCFrame { public: Position position; Rotation rotation; GlobalCFrame() : position(), rotation() {} GlobalCFrame(const Fix<32>& x, const Fix<32>& y, const Fix<32>& z) : position(x, y, z), rotation() {} GlobalCFrame(double x, double y, double z) : position(x, y, z), rotation() {} GlobalCFrame(double x, double y, double z, const Rotation& rotation) : position(x, y, z), rotation(rotation) {} GlobalCFrame(const Position& position) : position(position), rotation() {} GlobalCFrame(const Rotation& rotation) : position(), rotation(rotation) {} GlobalCFrame(const Position& position, const Rotation& rotation) : position(position), rotation(rotation) {} inline Position getPosition() const {return position;} inline Rotation getRotation() const { return rotation; } inline Vec3 globalToLocal(const Position& globalPos) const { return rotation.globalToLocal(Vec3(globalPos - this->position)); } inline Position localToGlobal(const Vec3& localVec) const { return position + Vec3Fix(rotation.localToGlobal(localVec)); } inline Vec3 localToRelative(const Vec3& localVec) const { return rotation.localToGlobal(localVec); } inline Vec3 relativeToLocal(const Vec3& relativeVec) const { return rotation.globalToLocal(relativeVec); } inline Vec3 globalToRelative(const Position& gPos) const { return gPos - position; } inline Position relativeToGlobal(const Vec3& rVec) const { return position + rVec; } inline GlobalCFrame localToGlobal(const CFrame& localFrame) const { return GlobalCFrame(position + rotation.localToGlobal(localFrame.getPosition()), rotation * localFrame.getRotation()); } inline CFrame globalToLocal(const GlobalCFrame& globalFrame) const { return CFrame(rotation.globalToLocal(Vec3(globalFrame.position - position)), ~rotation * globalFrame.rotation); } inline Rotation localToGlobal(const Rotation& localRot) const { return rotation * localRot; } inline Rotation globalToLocal(const Rotation& globalRot) const { return ~rotation * globalRot; } inline CFrame localToRelative(const CFrame& lFrame) const { return CFrame(rotation * lFrame.position, rotation * lFrame.rotation); } inline CFrame relativeToLocal(const CFrame& rFrame) const { return CFrame(rotation.globalToLocal(rFrame.position), ~rotation * rFrame.rotation); } inline void translate(const Vec3Fix& offset) { this->position += offset; } inline GlobalCFrame translated(const Vec3Fix& offset) { return GlobalCFrame(position + offset, rotation); } inline void rotate(const Rotation& rot) { this->rotation = rot * this->rotation; } inline GlobalCFrame rotated(const Rotation& rot) const { return GlobalCFrame(position, rot * rotation); } inline GlobalCFrame& operator+=(const Vec3Fix& offset) { this->position += offset; return *this; } inline GlobalCFrame& operator-=(const Vec3Fix& offset) { this->position -= offset; return *this; } inline GlobalCFrame operator+(const Vec3Fix& offset) const { return GlobalCFrame(position + offset, this->rotation); } inline GlobalCFrame operator-(const Vec3Fix& offset) const { return GlobalCFrame(position - offset, this->rotation); } inline GlobalCFrame extendLocal(const Vec3& offset) const { return GlobalCFrame(this->localToGlobal(offset), this->rotation); } /* Converts this CFrame to a 4x4 matrix, where for any Vec3 p: cframe.asMat4() * Vec4(p, 1.0) == cframe.localToGlobal(p) Note: loss of precision for far out positions, it is recommended to first compute the relative position, and then construct this matrix. This is because the position is unsafely cast to a Vec3. */ Mat4 asMat4() const { return join(rotation.asRotationMatrix(), castPositionToVec3(this->position), Vec3(0.0, 0.0, 0.0), 1.0); } /* Converts this CFrame to a 4x4 matrix with given scaling factor, where for any Vec3 p: cframe.asMat4WithPreScale(scale) * Vec4(p, 1.0) == cframe.localToGlobal(scale * p) Note: loss of precision for far out positions, it is recommended to first compute the relative position, and then construct this matrix. This is because the position is unsafely cast to a Vec3. */ Mat4 asMat4WithPreScale(const DiagonalMat3& scale) const { return join(rotation.asRotationMatrix() * scale, castPositionToVec3(this->position), Vec3(0.0, 0.0, 0.0), 1.0); } /* Converts this CFrame to a 4x4 matrix with given scaling factor, where for any Vec3 p: cframe.asMat4WithPostScale(scale) * Vec4(p, 1.0) == scale * cframe.localToGlobal(p) Note: loss of precision for far out positions, it is recommended to first compute the relative position, and then construct this matrix. This is because the position is unsafely cast to a Vec3. */ Mat4 asMat4WithPostScale(const DiagonalMat3& scale) const { return join(scale * rotation.asRotationMatrix(), castPositionToVec3(this->position), Vec3(0.0, 0.0, 0.0), 1.0); } }; inline CFrame operator-(GlobalCFrame cf, Position relativeTo) { return CFrame(cf.getPosition() - relativeTo, cf.getRotation()); } inline GlobalCFrame operator+(Position relativeTo, CFrame cf) { return GlobalCFrame(relativeTo + cf.getPosition(), cf.getRotation()); } }; ================================================ FILE: Physics3D/math/globalTransform.h ================================================ #pragma once #include "position.h" #include "linalg/mat.h" #include "transform.h" #include "globalCFrame.h" namespace P3D { class GlobalTransform { public: Position position; Mat3 transform; GlobalTransform() : position(), transform(Mat3::IDENTITY()) {} GlobalTransform(const Fix<32>& x, const Fix<32>& y, const Fix<32>& z) : position(x, y, z), transform(Mat3::IDENTITY()) {} GlobalTransform(double x, double y, double z) : position(x, y, z), transform(Mat3::IDENTITY()) {} GlobalTransform(double x, double y, double z, const Mat3& transform) : position(x, y, z), transform(transform) {} GlobalTransform(const Position& position) : position(position), transform(Mat3::IDENTITY()) {} GlobalTransform(const Mat3& transform) : position(), transform(transform) {} GlobalTransform(const Position& position, const Mat3& transform) : position(position), transform(transform) {} GlobalTransform(const GlobalCFrame& cframe) : position(cframe.getPosition()), transform(cframe.getRotation()) {} inline Position getPosition() const { return position; } inline Mat3 getTransform() const { return transform; } inline Vec3 globalToLocal(const Position& globalPos) const { return ~transform * Vec3(globalPos - this->position); } inline Position localToGlobal(const Vec3& localVec) const { return position + Vec3Fix(transform * localVec); } inline Vec3 localToRelative(const Vec3& localVec) const { return transform * localVec; } inline Vec3 relativeToLocal(const Vec3& relativeVec) const { return ~transform * relativeVec; } inline GlobalTransform localToGlobal(const Transform& localTransform) const { return GlobalTransform(position + transform * localTransform.getPosition(), transform * localTransform.getLocalTransformation()); } inline Transform globalToLocal(const GlobalTransform& globalFrame) const { return Transform(~transform * Vec3(globalFrame.position - position), ~transform * globalFrame.transform); } inline Mat3 localToGlobal(const Mat3& localRot) const { return transform * localRot; } inline Mat3 globalToLocal(const Mat3& globalRot) const { return ~transform * globalRot; } inline Transform localToRelative(const Transform& lTransform) const { return Transform(transform * lTransform.position, transform * lTransform.transform); } inline Transform relativeToLocal(const Transform& lTransform) const { return Transform(~transform * lTransform.position, ~transform * lTransform.transform); } inline void translate(const Vec3Fix& offset) { this->position += offset; } inline GlobalTransform translated(const Vec3Fix& offset) const { return GlobalTransform(position + offset); } inline void rotate(const Mat3& rot) { this->transform = rot * this->transform; } inline GlobalTransform rotated(const Mat3& rot) const { return GlobalTransform(position, rot * transform); } }; }; ================================================ FILE: Physics3D/math/linalg/commonMatrices.h ================================================ #pragma once #include "mat.h" #include "quat.h" #include "../constants.h" namespace P3D { #define QROT_X_90(Type) Quaternion {\ SQ2_2, SQ2_2, 0, 0} #define QROT_X_180(Type) Quaternion {\ 0, 1, 0, 0} #define QROT_X_270(Type) Quaternion {\ -SQ2_2, SQ2_2, 0, 0} #define QROT_Y_90(Type) Quaternion {\ SQ2_2, 0, SQ2_2, 0} #define QROT_Y_180(Type) Quaternion {\ 0, 0, 1, 0} #define QROT_Y_270(Type) Quaternion {\ SQ2_2, 0, -SQ2_2, 0} #define QROT_Z_90(Type) Quaternion {\ SQ2_2, 0, 0, SQ2_2} #define QROT_Z_180(Type) Quaternion {\ 0, 0, 0, 1} #define QROT_Z_270(Type) Quaternion {\ -SQ2_2, 0, 0, SQ2_2} #define ROT_X_90(Type) Matrix {\ 1, 0, 0,\ 0, 0, -1,\ 0, 1, 0} #define ROT_X_180(Type) Matrix {\ 1, 0, 0,\ 0, -1, 0,\ 0, 0, -1} #define ROT_X_270(Type) Matrix {\ 1, 0, 0, \ 0, 0, 1, \ 0, -1, 0} #define ROT_Y_90(Type) Matrix {\ 0, 0, 1,\ 0, 1, 0,\ -1, 0, 0} #define ROT_Y_180(Type) Matrix {\ -1, 0, 0,\ 0, 1, 0,\ 0, 0,-1} #define ROT_Y_270(Type) Matrix {\ 0, 0,-1,\ 0, 1, 0,\ 1, 0, 0} #define ROT_Z_90(Type) Matrix {\ 0, -1, 0,\ 1, 0, 0,\ 0, 0, 1} #define ROT_Z_180(Type) Matrix {\ -1, 0, 0,\ 0, -1, 0,\ 0, 0, 1} #define ROT_Z_270(Type) Matrix {\ 0, 1, 0,\ -1, 0, 0,\ 0, 0, 1} }; ================================================ FILE: Physics3D/math/linalg/eigen.cpp ================================================ #include "eigen.h" #include namespace P3D { template void update(EigenValues& e, bool* changed, int k, N t) { N y = e[k]; e[k] = y + t; changed[k] = !(y == e[k]); } template void rotateEigen(Matrix& copy, int k, int l, int i, int j, N c, N s) { N SKL = copy(k, l); N SIJ = copy(i, j); copy(k, l) = c * SKL - s * SIJ; copy(i, j) = s * SKL + c * SIJ; } EigenSet getEigenDecomposition(const SymmetricMat3& sm) { Mat3 copy(sm); EigenValues eigenValues{sm(0, 0),sm(1, 1),sm(2, 2)}; Mat3 eigenVectors = Mat3::IDENTITY(); bool changed[3]{true, true, true}; int tieBreaker = 0; const int values[6]{ 0,1, 0,2, 1,2 }; while(changed[0] || changed[1] || changed[2]) { double top = std::abs(copy(0, 1)); double topRight = std::abs(copy(0, 2)); double right = std::abs(copy(1, 2)); int k, l; // find which of the three upper off-diagonal elements is the biggest if(top > topRight && top > right) { k = 0; l = 1; } else if(topRight > top && topRight > right) { k = 0; l = 2; } else if(right > top && right > topRight) { k = 1; l = 2; } else { // TIEBREAKER k = values[tieBreaker * 2]; l = values[tieBreaker * 2 + 1]; tieBreaker = (tieBreaker + 1) % 3; } double p = copy(k, l); if(p == 0) { return EigenSet(eigenValues, eigenVectors); } double y = (eigenValues[l] - eigenValues[k]) / 2; double d = std::abs(y) + std::sqrt(p * p + y * y); double r = std::sqrt(p * p + d * d); double c = d / r; double s = p / r; double t = p * p / d; if(y < 0) { s = -s; t = -t; }; copy(k, l) = 0.0; update(eigenValues, changed, k, -t); update(eigenValues, changed, l, t); for(int i = 0; i <= k - 1; i++) rotateEigen(copy, i, k, i, l, c, s); for(int i = k + 1; i <= l - 1; i++) rotateEigen(copy, k, i, i, l, c, s); for(int i = l + 1; i < 3; i++) rotateEigen(copy, k, i, l, i, c, s); for(int i = 0; i < 3; i++) { double EIK = eigenVectors(i, k); double EIL = eigenVectors(i, l); eigenVectors(i, k) = c * EIK - s * EIL; eigenVectors(i, l) = s * EIK + c * EIL; } } return EigenSet(eigenValues, eigenVectors); } }; ================================================ FILE: Physics3D/math/linalg/eigen.h ================================================ #pragma once #include "mat.h" #include #include namespace P3D { template struct EigenValues { T values[Size]; EigenValues(std::initializer_list list) { assert(list.size() == Size); const double* listValues = list.begin(); for(std::size_t i = 0; i < Size; i++) { values[i] = listValues[i]; } } DiagonalMatrix asDiagonalMatrix() const { return DiagonalMatrix(values); } const T& operator[](std::size_t index) const { return values[index]; } T& operator[](std::size_t index) { return values[index]; } }; template struct EigenSet { EigenValues eigenValues; Matrix eigenVectors; EigenSet(EigenValues eigenValues, const Matrix& eigenVectors) : eigenValues(eigenValues), eigenVectors(eigenVectors) {}; }; EigenSet getEigenDecomposition(const SymmetricMat3& sm); }; ================================================ FILE: Physics3D/math/linalg/largeMatrix.h ================================================ #pragma once #include "mat.h" #include #include namespace P3D { template class UnmanagedLargeVector { public: T* data; size_t n; UnmanagedLargeVector() : n(0), data(nullptr) {} UnmanagedLargeVector(T* dataBuf, size_t size) : data(dataBuf), n(size) {} UnmanagedLargeVector(UnmanagedLargeVector&& other) noexcept : data(other.data), n(other.n) { other.data = nullptr; other.n = 0; } inline UnmanagedLargeVector& operator=(UnmanagedLargeVector&& other) noexcept { std::swap(this->data, other.data); std::swap(this->n, other.n); return *this; } size_t size() const { return n; } template inline void setSubVector(const VectorType& vec, size_t offset = 0) { assert(offset + vec.size() <= n); for(size_t i = 0; i < vec.size(); i++) { this->data[i + offset] = vec[i]; } } template inline Vector getSubVector(size_t offset = 0) { assert(offset + Size <= n); Vector result; for (size_t i = 0; i < Size; i++) { result[i] = this->data[i + offset]; } return result; } inline UnmanagedLargeVector subVector(size_t offset, size_t size) { assert(offset + size <= n); return UnmanagedLargeVector(this->data + offset, size); } inline UnmanagedLargeVector subVector(size_t offset, size_t size) const { assert(offset + size <= n); return UnmanagedLargeVector(this->data + offset, size); } T& operator[] (size_t index) { assert(index >= 0 && index < n); return data[index]; } const T& operator[] (size_t index) const { assert(index >= 0 && index < n); return data[index]; } inline UnmanagedLargeVector& operator+=(const UnmanagedLargeVector& other) { assert(this->n == other.n); for(size_t i = 0; i < n; i++) { this->data[i] += other.data[i]; } return *this; } inline UnmanagedLargeVector& operator-=(const UnmanagedLargeVector& other) { assert(this->n == other.n); for(size_t i = 0; i < n; i++) { this->data[i] -= other.data[i]; } return *this; } }; template class LargeVector : public UnmanagedLargeVector { public: LargeVector() = default; LargeVector(size_t size) : UnmanagedLargeVector(new T[size], size) {} LargeVector(size_t size, const T* initialData) : UnmanagedLargeVector(new T[size], size) { for(size_t i = 0; i < size; i++) { this->data[i] = initialData[i]; } } ~LargeVector() { delete[] this->data; } LargeVector(const LargeVector& other) : UnmanagedLargeVector(new T[other.n], other.n) { for(int i = 0; i < other.n; i++) { this->data[i] = other.data[i]; } } inline LargeVector& operator=(const LargeVector& other) { delete[] this->data; this->n = other.n; this->data = new T[other.n]; for(int i = 0; i < other.n; i++) { this->data[i] = other.data[i]; } return *this; } }; template class UnmanagedLargeMatrix { public: T* data; size_t w, h; UnmanagedLargeMatrix() : w(0), h(0), data(nullptr) {} UnmanagedLargeMatrix(T* buffer, size_t width, size_t height) : w(width), h(height), data(buffer) {} inline UnmanagedLargeMatrix(UnmanagedLargeMatrix&& other) noexcept : data(other.data), w(other.w), h(other.h) { other.data = nullptr; other.w = 0; other.h = 0; } inline UnmanagedLargeMatrix& operator=(UnmanagedLargeMatrix&& other) noexcept { std::swap(this->data, other.data); std::swap(this->w, other.w); std::swap(this->h, other.h); return *this; } T& operator()(size_t row, size_t col) { assert(row >= 0 && row < h); assert(col >= 0 && col < w); return data[w * row + col]; } const T& operator()(size_t row, size_t col) const { assert(row >= 0 && row < h); assert(col >= 0 && col < w); return data[w * row + col]; } void setSubMatrix(size_t topLeftRow, size_t topLeftCol, const UnmanagedLargeMatrix& matrix) { for (size_t row = 0; row < matrix.h; row++) { for (size_t col = 0; col < matrix.w; col++) { (*this)(row + topLeftRow, col + topLeftCol) = matrix(row, col); } } } size_t width() const { return w; } size_t height() const { return h; } LargeVector getRow(size_t row) const { LargeVector result(w); for(size_t i = 0; i < w; i++) { result[i] = (*this)(row, i); } return result; } LargeVector getCol(size_t col) const { LargeVector result(h); for(size_t i = 0; i < h; i++) { result[i] = (*this)(i, col); } return result; } template void setRow(size_t row, const VectorType& value) { assert(w == value.size()); for(size_t i = 0; i < w; i++) { (*this)(row, i) = value[i]; } } template void setCol(size_t col, const VectorType& value) { assert(h == value.size()); for(size_t i = 0; i < h; i++) { (*this)(i, col) = value[i]; } } inline UnmanagedLargeMatrix& operator+=(const UnmanagedLargeMatrix& other) { assert(this->w == other.w); assert(this->h == other.h); for(size_t i = 0; i < w * h; i++) { this->data[i] += other.data[i]; } return *this; } inline UnmanagedLargeMatrix& operator-=(const UnmanagedLargeMatrix& other) { assert(this->w == other.w); assert(this->h == other.h); for(size_t i = 0; i < w * h; i++) { this->data[i] -= other.data[i]; } return *this; } T* begin() {return data;} T* end() {return data + w * h;} const T* begin() const { return data; } const T* end() const { return data + w * h; } }; template class LargeMatrix : public UnmanagedLargeMatrix { public: LargeMatrix() = default; LargeMatrix(size_t width, size_t height) : UnmanagedLargeMatrix(new T[width * height], width, height) {} ~LargeMatrix() { delete[] this->data; } LargeMatrix(const LargeMatrix& other) : UnmanagedLargeMatrix(new T[other.w * other.h], other.w, other.h) { for(size_t i = 0; i < other.w * other.h; i++) { this->data[i] = other.data[i]; } } LargeMatrix& operator=(const LargeMatrix& other) { delete[] this->data; this->h = other.h; this->w = other.w; this->data = new T[other.h * other.w]; for(size_t i = 0; i < other.w * other.h; i++) { this->data[i] = other.data[i]; } return *this; } LargeMatrix(LargeMatrix&& other) noexcept : UnmanagedLargeMatrix(other.data, other.w, other.h) { other.data = nullptr; other.w = 0; other.h = 0; } LargeMatrix& operator=(LargeMatrix&& other) noexcept { std::swap(this->data, other.data); std::swap(this->w, other.w); std::swap(this->h, other.h); return *this; } static LargeMatrix zero(size_t width, size_t height) { LargeMatrix result(width, height); for(size_t i = 0; i < width * height; i++) { result.data[i] = 0; } return result; } }; constexpr size_t getAmountOfElementsForSymmetric(size_t size) { return size * (size + 1) / 2; } template class UnmanagedLargeSymmetricMatrix { public: T* data; size_t size; UnmanagedLargeSymmetricMatrix() : data(nullptr), size(0) {} UnmanagedLargeSymmetricMatrix(T* data, size_t size) : data(data), size(size) {} inline UnmanagedLargeSymmetricMatrix(UnmanagedLargeSymmetricMatrix&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } inline UnmanagedLargeSymmetricMatrix& operator=(UnmanagedLargeSymmetricMatrix&& other) noexcept { std::swap(this->data, other.data); std::swap(this->size, other.size); return *this; } size_t width() const { return size; } size_t height() const { return size; } LargeVector getRow(size_t row) const { LargeVector result(size); for(size_t i = 0; i < size; i++) { result[i] = (*this)(row, i); } return result; } LargeVector getCol(size_t col) const { LargeVector result(size); for(size_t i = 0; i < size; i++) { result[i] = (*this)(i, col); } return result; } template void setRow(size_t row, const VectorType& value) { assert(size == value.size()); for(size_t i = 0; i < size; i++) { (*this)(row, i) = value[i]; } } template void setCol(size_t col, const VectorType& value) { assert(size == value.size()); for(size_t i = 0; i < size; i++) { (*this)(i, col) = value[i]; } } inline UnmanagedLargeSymmetricMatrix& operator+=(const UnmanagedLargeSymmetricMatrix& other) { assert(this->size == other.size); for(size_t i = 0; i < size * (size + 1) / 2; i++) { this->data[i] += other.data[i]; } return *this; } inline UnmanagedLargeSymmetricMatrix& operator-=(const UnmanagedLargeSymmetricMatrix& other) { assert(this->size == other.size); for(size_t i = 0; i < size * (size + 1) / 2; i++) { this->data[i] -= other.data[i]; } return *this; } T& operator()(size_t row, size_t col) { size_t a = (row >= col) ? row : col; // max size_t b = (row >= col) ? col : row; // min assert(b >= 0); assert(a < size); return this->data[a * (a + 1) / 2 + b]; } const T& operator()(size_t row, size_t col) const { size_t a = (row >= col) ? row : col; // max size_t b = (row >= col) ? col : row; // min assert(b >= 0); assert(a < size); return this->data[a * (a + 1) / 2 + b]; } }; template class LargeSymmetricMatrix : public UnmanagedLargeSymmetricMatrix { public: LargeSymmetricMatrix() = default; LargeSymmetricMatrix(size_t size) : UnmanagedLargeSymmetricMatrix(new T[getAmountOfElementsForSymmetric(size)], size) {} ~LargeSymmetricMatrix() { delete[] this->data; } LargeSymmetricMatrix(const LargeSymmetricMatrix& other) : UnmanagedLargeSymmetricMatrix(new T[getAmountOfElementsForSymmetric(other.size)], other.size) { for(size_t i = 0; i < getAmountOfElementsForSymmetric(other.size); i++) { this->data[i] = other.data[i]; } } inline LargeSymmetricMatrix& operator=(const LargeSymmetricMatrix& other) { delete[] this->data; this->size = other.size; this->data = new T[getAmountOfElementsForSymmetric(other.size)]; for(size_t i = 0; i < getAmountOfElementsForSymmetric(other.size); i++) { this->data[i] = other.data[i]; } return *this; } }; template class UnmanagedVerticalFixedMatrix { public: T* data; size_t cols; UnmanagedVerticalFixedMatrix() : data(nullptr), cols(0) {} UnmanagedVerticalFixedMatrix(T* data, size_t width) : data(data), cols(width) {} inline UnmanagedVerticalFixedMatrix(UnmanagedVerticalFixedMatrix&& other) : data(other.data), cols(other.cols) { other.data = nullptr; other.cols = 0; } inline UnmanagedVerticalFixedMatrix& operator=(UnmanagedVerticalFixedMatrix&& other) { std::swap(this->data, other.data); std::swap(this->cols, other.cols); return *this; } size_t width() const { return cols; } constexpr size_t height() const { return Rows; } LargeVector getRow(size_t row) const { LargeVector result(cols); for(size_t i = 0; i < cols; i++) { result[i] = (*this)(row, i); } return result; } Vector getCol(size_t col) const { Vector result; for(size_t i = 0; i < Rows; i++) { result[i] = (*this)(i, col); } return result; } template void setRow(size_t row, const VectorType& value) { assert(cols == value.size()); for(size_t i = 0; i < cols; i++) { (*this)(row, i) = value[i]; } } void setCol(size_t col, const Vector& value) { for(size_t i = 0; i < Rows; i++) { (*this)(i, col) = value[i]; } } UnmanagedVerticalFixedMatrix subCols(size_t offset, size_t size) { assert(offset + size <= cols); return UnmanagedVerticalFixedMatrix(data + Rows * offset, size); } UnmanagedVerticalFixedMatrix subCols(size_t offset, size_t size) const { assert(offset + size <= cols); return UnmanagedVerticalFixedMatrix(data + Rows * offset, size); } inline UnmanagedVerticalFixedMatrix& operator+=(const UnmanagedVerticalFixedMatrix& other) { assert(this->cols == other.cols); for(size_t i = 0; i < Rows * cols; i++) { this->data[i] += other.data[i]; } return *this; } inline UnmanagedVerticalFixedMatrix& operator-=(const UnmanagedVerticalFixedMatrix& other) { assert(this->cols == other.cols); for(size_t i = 0; i < Rows * cols; i++) { this->data[i] -= other.data[i]; } return *this; } T& operator()(size_t row, size_t col) { assert(row >= 0 && row < Rows && col >= 0 && col < cols); return data[col * Rows + row]; } const T& operator()(size_t row, size_t col) const { assert(row >= 0 && row < Rows && col >= 0 && col < cols); return data[col * Rows + row]; } T* begin() { return data; } const T* begin() const { return data; } T* end() { return data + cols*Rows; } const T* end() const { return data + cols*Rows; } }; template class VerticalFixedMatrix : public UnmanagedVerticalFixedMatrix { public: VerticalFixedMatrix() = default; VerticalFixedMatrix(size_t cols) : UnmanagedVerticalFixedMatrix(new T[cols * Rows], cols * Rows) {} ~VerticalFixedMatrix() { delete[] this->data; } VerticalFixedMatrix(const VerticalFixedMatrix& other) : UnmanagedVerticalFixedMatrix(new T[other.cols * Rows], other.cols * Rows) { for(size_t i = 0; i < other.cols * Rows; i++) { this->data[i] = other.data[i]; } } inline VerticalFixedMatrix& operator=(const VerticalFixedMatrix& other) { delete[] this->data; this->size = other.size; this->data = new T[other.cols * Rows]; for(size_t i = 0; i < other.cols * Rows; i++) { this->data[i] = other.data[i]; } return *this; } }; template class UnmanagedHorizontalFixedMatrix { public: T* data; size_t rows; UnmanagedHorizontalFixedMatrix() : data(nullptr), rows(0) {} UnmanagedHorizontalFixedMatrix(T* data, size_t width) : data(data), rows(width) {} inline UnmanagedHorizontalFixedMatrix(UnmanagedHorizontalFixedMatrix&& other) : data(other.data), rows(other.rows) { other.data = nullptr; other.rows = 0; } inline UnmanagedHorizontalFixedMatrix& operator=(UnmanagedHorizontalFixedMatrix&& other) { std::swap(this->data, other.data); std::swap(this->rows, other.rows); return *this; } constexpr size_t width() const { return Cols; } size_t height() const { return rows; } Vector getRow(size_t row) const { Vector result; for(size_t i = 0; i < Cols; i++) { result[i] = (*this)(row, i); } return result; } LargeVector getCol(size_t col) const { LargeVector result(rows); for(size_t i = 0; i < rows; i++) { result[i] = (*this)(i, col); } return result; } void setRow(size_t row, const Vector& value) { for(size_t i = 0; i < Cols; i++) { (*this)(row, i) = value[i]; } } template void setCol(size_t col, const VectorType& value) { assert(rows == value.size()); for(size_t i = 0; i < rows; i++) { (*this)(i, col) = value[i]; } } UnmanagedHorizontalFixedMatrix subRows(size_t offset, size_t size) { assert(offset + size <= rows); return UnmanagedHorizontalFixedMatrix(data + Cols * offset, size); } UnmanagedHorizontalFixedMatrix subRows(size_t offset, size_t size) const { assert(offset + size <= rows); return UnmanagedHorizontalFixedMatrix(data + Cols * offset, size); } inline UnmanagedHorizontalFixedMatrix& operator+=(const UnmanagedHorizontalFixedMatrix& other) { assert(this->rows == other.rows); for(size_t i = 0; i < rows * Cols; i++) { this->data[i] += other.data[i]; } return *this; } inline UnmanagedHorizontalFixedMatrix& operator-=(const UnmanagedHorizontalFixedMatrix& other) { assert(this->rows == other.rows); for(size_t i = 0; i < rows * Cols; i++) { this->data[i] -= other.data[i]; } return *this; } T& operator()(size_t row, size_t col) { assert(row >= 0 && row < rows && col >= 0 && col < Cols); return data[row * Cols + col]; } const T& operator()(size_t row, size_t col) const { assert(row >= 0 && row < rows && col >= 0 && col < Cols); return data[row * Cols + col]; } T* begin() { return data; } const T* begin() const { return data; } T* end() { return data + rows * Cols; } const T* end() const { return data + rows * Cols; } }; template class HorizontalFixedMatrix : public UnmanagedHorizontalFixedMatrix { public: HorizontalFixedMatrix() = default; HorizontalFixedMatrix(size_t cols) : UnmanagedHorizontalFixedMatrix(new T[cols * Cols], cols) {} ~HorizontalFixedMatrix() { delete[] this->data; } HorizontalFixedMatrix(const HorizontalFixedMatrix& other) : UnmanagedHorizontalFixedMatrix(new T[other.rows * Cols], other.rows * Cols) { for(size_t i = 0; i < other.rows * Cols; i++) { this->data[i] = other.data[i]; } } inline HorizontalFixedMatrix& operator=(const HorizontalFixedMatrix& other) { delete[] this->data; this->size = other.size; this->data = new T[other.rows * Cols]; for(size_t i = 0; i < other.rows * Cols; i++) { this->data[i] = other.data[i]; } return *this; } }; /* Operators */ template LargeVector operator*(const UnmanagedLargeMatrix& m, const UnmanagedLargeVector& v) { assert(v.n == m.w); LargeVector newVector(m.h); for(size_t i = 0; i < m.h; i++) { T total = m(i, 0) * v[0]; for(size_t j = 1; j < m.w; j++) { total += m(i, j) * v[j]; } newVector[i] = total; } return newVector; } template LargeVector operator*(const UnmanagedLargeSymmetricMatrix& m, const UnmanagedLargeVector& v) { assert(v.n == m.size); LargeVector newVector(m.size); for(size_t i = 0; i < m.size; i++) { T total = m(i, 0) * v[0]; for(size_t j = 1; j < m.size; j++) { total += m(i, j) * v[j]; } newVector[i] = total; } return newVector; } template Vector operator*(const UnmanagedVerticalFixedMatrix& m, const UnmanagedLargeVector& v) { assert(m.cols == v.n); Vector result; for(size_t i = 0; i < Size; i++) { T total = m(i, 0) * v[0]; for(size_t j = 1; j < m.cols; j++) { total += m(i, j) * v[j]; } result[i] = total; } return result; } template Matrix operator*(const UnmanagedVerticalFixedMatrix& m1, const UnmanagedHorizontalFixedMatrix& m2) { assert(m1.cols == m2.rows); Matrix result; inMemoryMatrixMultiply(m1, m2, result); return result; } template void inMemoryMatrixMultiply(const M1& m1, const M2& m2, MResult& result) { assert(m1.height() == result.height()); // result height assert(m2.width() == result.width()); // result width assert(m1.width() == m2.height()); // intermediate for(std::size_t col = 0; col < result.width(); col++) { for(std::size_t row = 0; row < result.height(); row++) { auto sum = m1(row, 0) * m2(0, col); for(std::size_t i = 1; i < m1.width(); i++) { sum += m1(row, i) * m2(i, col); } result(row, col) = sum; } } } template void inMemoryMatrixVectorMultiply(const M& m, const V& v, VResult& result) { assert(v.size() == m.width()); assert(result.size() == m.height()); for(std::size_t row = 0; row < m.height(); row++) { auto sum = m(row, 0) * v[0]; for(std::size_t col = 1; col < m.width(); col++) { sum += m(row, col) * v[col]; } result[row] = sum; } } template void inMemoryVectorNegate(V& v) { for(std::size_t i = 0; i < v.size(); i++) { v[i] = -v[i]; } } template void inMemoryMatrixNegate(M& m) { for(std::size_t row = 0; row < m.height(); row++) { for(std::size_t col = 0; col < m.width(); col++) { m(row, col) = -m(row, col); } } } }; ================================================ FILE: Physics3D/math/linalg/largeMatrixAlgorithms.h ================================================ #pragma once #include "largeMatrix.h" #include #include #include #include namespace P3D { template static void swapRows(UnmanagedLargeVector& v, std::size_t rowA, std::size_t rowB) { std::swap(v[rowA], v[rowB]); } template static void swapRows(UnmanagedLargeMatrix& m, std::size_t rowA, std::size_t rowB) { for(std::size_t i = 0; i < m.w; i++) { std::swap(m(rowA, i), m(rowB, i)); } } template static void swapRows(UnmanagedHorizontalFixedMatrix& m, std::size_t rowA, std::size_t rowB) { for(std::size_t i = 0; i < Cols; i++) { std::swap(m(rowA, i), m(rowB, i)); } } template static void swapRows(UnmanagedVerticalFixedMatrix& m, std::size_t rowA, std::size_t rowB) { for(std::size_t i = 0; i < m.cols; i++) { std::swap(m(rowA, i), m(rowB, i)); } } template static void subtractRowsFactorTimes(UnmanagedLargeVector& v, std::size_t editRow, std::size_t subtractingRow, T factor) { v[editRow] -= v[subtractingRow] * factor; } template static void subtractRowsFactorTimes(UnmanagedLargeMatrix& m, std::size_t editRow, std::size_t subtractingRow, T factor) { for(std::size_t i = 0; i < m.w; i++) { m(editRow, i) -= m(subtractingRow, i) * factor; } } template static void subtractRowsFactorTimes(UnmanagedHorizontalFixedMatrix& m, std::size_t editRow, std::size_t subtractingRow, T factor) { for(std::size_t i = 0; i < Cols; i++) { m(editRow, i) -= m(subtractingRow, i) * factor; } } template static void subtractRowsFactorTimes(UnmanagedVerticalFixedMatrix& m, std::size_t editRow, std::size_t subtractingRow, T factor) { for(std::size_t i = 0; i < m.cols; i++) { m(editRow, i) -= m(subtractingRow, i) * factor; } } template static void multiplyRowBy(UnmanagedLargeVector& v, std::size_t row, T factor) { v[row] *= factor; } template static void multiplyRowBy(UnmanagedLargeMatrix& m, std::size_t row, T factor) { for(std::size_t i = 0; i < m.w; i++) { m(row, i) *= factor; } } template static void multiplyRowBy(UnmanagedHorizontalFixedMatrix& m, std::size_t row, T factor) { for(std::size_t i = 0; i < Cols; i++) { m(row, i) *= factor; } } template static void multiplyRowBy(UnmanagedVerticalFixedMatrix& m, std::size_t row, T factor) { for(std::size_t i = 0; i < m.cols; i++) { m(row, i) *= factor; } } template static std::size_t getHeight(UnmanagedLargeVector& v) { return v.n; } template static std::size_t getHeight(UnmanagedLargeMatrix& m) { return m.h; } template static std::size_t getHeight(UnmanagedHorizontalFixedMatrix& m) { return m.rows; } template static std::size_t getHeight(UnmanagedVerticalFixedMatrix& m) { return Rows; } template void destructiveSolve(System& m, SolutionType& v) { assert(getHeight(v) == m.w && m.w == m.h); std::size_t size = getHeight(v); // make matrix upper triangular for(std::size_t i = 0; i < size; i++) { auto bestPivot = std::abs(m(i, i)); std::size_t bestPivotIndex = i; for(std::size_t j = i + 1; j < size; j++) { auto newPivot = std::abs(m(j, i)); if(newPivot > bestPivot) { bestPivot = newPivot; bestPivotIndex = j; } } if(bestPivotIndex != i) { swapRows(m, bestPivotIndex, i); swapRows(v, bestPivotIndex, i); } auto pivot = m(i, i); for(std::size_t j = i + 1; j < size; j++) { auto factor = m(j, i) / pivot; m(j, i) -= m(i, i) * factor; for(std::size_t k = i + 1; k < size; k++) { m(j, k) -= m(i, k) * factor; } subtractRowsFactorTimes(v, j, i, factor); } } // back substitution for(signed long long i = size - 1; i >= 0; i--) { multiplyRowBy(v, i, 1 / m(i, i)); for(signed long long j = i - 1; j >= 0; j--) { subtractRowsFactorTimes(v, j, i, m(j, i)); } } } }; ================================================ FILE: Physics3D/math/linalg/mat.h ================================================ #pragma once #include "vec.h" #include #include #include #include #include #include namespace P3D { template class Matrix { public: T data[Width * Height]; inline constexpr T& operator()(std::size_t row, std::size_t col) { assert(row >= 0 && row < Height); assert(col >= 0 && col < Width); return data[col * Height + row]; } inline constexpr T operator()(std::size_t row, std::size_t col) const { assert(row >= 0 && row < Height); assert(col >= 0 && col < Width); return data[col * Height + row]; } Matrix() : data{} {} /* Initialize matrices like so Matrix{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; */ inline constexpr Matrix(std::initializer_list list) : data{} { assert(list.size() == Width * Height); auto listIter = list.begin(); for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { (*this)(row, col) = *listIter; ++listIter; } } } inline constexpr Matrix(const Matrix&) = default; inline constexpr Matrix& operator=(const Matrix&) = default; inline constexpr Matrix(Matrix&&) = default; inline constexpr Matrix& operator=(Matrix&&) = default; template inline constexpr Matrix(const Matrix& m) : data{} { for(std::size_t row = 0; row < Height; row++) { for(std::size_t col = 0; col < Width; col++) { (*this)(row, col) = static_cast(m(row, col)); } } } inline static Matrix fromRows(std::initializer_list> rows) { assert(rows.size() == Height); Matrix result; auto rowIter = rows.begin(); for(std::size_t row = 0; row < Height; row++) { const Vector& curRow = *rowIter; for(std::size_t col = 0; col < Width; col++) { result(row, col) = curRow[col]; } rowIter++; } return result; } inline static Matrix fromColumns(std::initializer_list> columns) { assert(columns.size() == Width); Matrix result; auto colIter = columns.begin(); for(std::size_t col = 0; col < Width; col++) { const Vector& curCol = *colIter; for(std::size_t row = 0; row < Height; row++) { result(row, col) = curCol[row]; } colIter++; } return result; } constexpr size_t width() const { return Width; } constexpr size_t height() const { return Height; } void setDataRowMajor(const T* data) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { (*this)(row, col) = data[row * Width + col]; } } } void setDataColMajor(const T* data) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { (*this)(row, col) = data[row + col * Height]; } } } Matrix transpose() const { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(col, row) = (*this)(row, col); } } return result; } template Matrix getSubMatrix(std::size_t topLeftRow, std::size_t topLeftCol) const { assert(topLeftRow >= 0 && topLeftRow + SubHeight <= Height); assert(topLeftCol >= 0 && topLeftCol + SubWidth <= Width); Matrix result; for (std::size_t row = 0; row < SubHeight; row++) { for (std::size_t col = 0; col < SubWidth; col++) { result(row, col) = (*this)(row+topLeftRow, col+topLeftCol); } } return result; } template void setSubMatrix(const Matrix& mat, std::size_t topLeftRow, std::size_t topLeftCol) { assert(topLeftRow >= 0 && topLeftRow + SubHeight <= Height); assert(topLeftCol >= 0 && topLeftCol + SubWidth <= Width); for (std::size_t row = 0; row < SubHeight; row++) { for (std::size_t col = 0; col < SubWidth; col++) { (*this)(row + topLeftRow, col + topLeftCol) = mat(row, col); } } } Matrix withoutCol(std::size_t colToDelete) const { assert(colToDelete >= 0 && colToDelete < Width); Matrix newMat; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < colToDelete; col++) { newMat(row, col) = (*this)(row, col); } for (std::size_t col = colToDelete+1; col < Width; col++) { newMat(row, col - 1) = (*this)(row, col); } } return newMat; } Matrix withoutRow(std::size_t rowToDelete) const { assert(rowToDelete >= 0 && rowToDelete < Height); Matrix newMat; for (std::size_t col = 0; col < Width; col++) { for (std::size_t row = 0; row < rowToDelete; row++) { newMat(row, col) = (*this)(row, col); } for (std::size_t row = rowToDelete; row < Height - 1; row++) { newMat(row, col) = (*this)(row + 1, col); } } return newMat; } Matrix withoutRowCol(std::size_t rowToDelete, std::size_t colToDelete) const { assert(colToDelete >= 0 && colToDelete < Width); assert(rowToDelete >= 0 && rowToDelete < Height); Matrix newMat; for (std::size_t row = 0; row < rowToDelete; row++) { for (std::size_t col = 0; col < colToDelete; col++) { newMat(row, col) = (*this)(row, col); } for (std::size_t col = colToDelete + 1; col < Width; col++) { newMat(row, col - 1) = (*this)(row, col); } } for (std::size_t row = rowToDelete + 1; row < Height; row++) { for (std::size_t col = 0; col < colToDelete; col++) { newMat(row - 1, col) = (*this)(row, col); } for (std::size_t col = colToDelete + 1; col < Width; col++) { newMat(row - 1, col - 1) = (*this)(row, col); } } return newMat; } Vector getRow(std::size_t row) const { assert(row >= 0 && row < Height); Vector result; for(std::size_t i = 0; i < Width; i++) { result[i] = (*this)(row, i); } return result; } Vector getCol(std::size_t col) const { assert(col >= 0 && col < Width); Vector result; for(std::size_t i = 0; i < Height; i++) { result[i] = (*this)(i, col); } return result; } void setRow(std::size_t row, const Vector& data) { assert(row >= 0 && row < Height); for (std::size_t i = 0; i < Width; i++) { (*this)(row, i) = data[i]; } } void setCol(std::size_t col, const Vector& data) { assert(col >= 0 && col < Width); for (std::size_t i = 0; i < Height; i++) { (*this)(i, col) = data[i]; } } static inline constexpr Matrix ZEROS() { Matrix mat; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) = 0; } } return mat; } static inline constexpr Matrix IDENTITY() { Matrix mat; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) = (row == col) ? T(1) : T(0); } } return mat; } static inline constexpr Matrix DIAGONAL(const T& diagonalVal) { Matrix mat; for(std::size_t row = 0; row < Height; row++) { for(std::size_t col = 0; col < Width; col++) { mat(row, col) = (row == col) ? diagonalVal : T(0); } } return mat; } static Matrix fromColMajorData(const T* data) { Matrix mat; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) = data[row + col * Height]; } } return mat; } static Matrix fromRowMajorData(const T* data) { Matrix mat; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) = data[row * Width + col]; } } return mat; } void toColMajorData(T* buf) const { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { buf[row + col * Height] = (*this)(row, col); } } } void toRowMajorData(T* buf) const { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { buf[row * Width + col] = (*this)(row, col); } } } }; /* Symmetric matrix indexing 0 1 2 3 4 5 6 7 8 9 A B C D E (3, 0) = 6 (1, 0) = 1 (2, 2) = 5 (a, b) = a*(a+1)/2+b */ template class SymmetricMatrix { T data[Size * (Size + 1) / 2]; public: inline constexpr T& operator()(std::size_t row, std::size_t col) { std::size_t a = (row >= col) ? row : col; // max std::size_t b = (row >= col) ? col : row; // min assert(b >= 0); assert(a < Size); return data[a * (a + 1) / 2 + b]; } inline constexpr T operator()(std::size_t row, std::size_t col) const { std::size_t a = (row >= col) ? row : col; // max std::size_t b = (row >= col) ? col : row; // min assert(b >= 0); assert(a < Size); return data[a * (a + 1) / 2 + b]; } SymmetricMatrix() : data{} {} /* Initialize symmetric matrices like so SymmetricMatrix{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; */ inline constexpr SymmetricMatrix(const std::initializer_list& list) : data{} { assert(list.size() == Size * (Size + 1) / 2); auto listIter = list.begin(); for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { (*this)(row, col) = *listIter; ++listIter; } } } template inline constexpr SymmetricMatrix(const SymmetricMatrix& m) : data{} { for(std::size_t row = 0; row < Size; row++) { for(std::size_t col = 0; col <= row; col++) { (*this)(row, col) = m(row, col); } } } constexpr size_t width() const { return Size; } constexpr size_t height() const { return Size; } static inline constexpr SymmetricMatrix ZEROS() { SymmetricMatrix mat; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { mat(row, col) = 0; } } return mat; } static inline constexpr SymmetricMatrix IDENTITY() { SymmetricMatrix mat; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { mat(row, col) = (row == col) ? T(1) : T(0); } } return mat; } static inline constexpr SymmetricMatrix DIAGONAL(const T& diagonalVal) { SymmetricMatrix mat; for(std::size_t row = 0; row < Size; row++) { for(std::size_t col = 0; col <= row; col++) { mat(row, col) = (row == col) ? diagonalVal : T(0); } } return mat; } operator Matrix() const { Matrix mat; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { const T& val = (*this)(row, col); mat(row, col) = val; mat(col, row) = val; } } return mat; } Vector getRow(std::size_t row) const { assert(row >= 0 && row < Size); Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = (*this)(row, i); } return result; } Vector getCol(std::size_t col) const { assert(col >= 0 && col < Size); Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = (*this)(i, col); } return result; } }; template class DiagonalMatrix { public: T data[Size]; inline constexpr T& operator[](std::size_t index) { assert(index >= 0 && index < Size); return data[index]; } inline constexpr const T& operator[](std::size_t index) const { assert(index >= 0 && index < Size); return data[index]; } inline constexpr T operator()(std::size_t row, std::size_t col) const { return (row == col) ? data[row] : static_cast(0); } DiagonalMatrix() : data{} { for(std::size_t i = 0; i < Size; i++) { data[i] = T(); } } /* Initialize diagonal matrices like so DiagonalMatrix{ 1, 2, 3, 4 }; */ inline constexpr DiagonalMatrix(const std::initializer_list& list) : data{} { assert(list.size() == Size); auto listIter = list.begin(); for (std::size_t i = 0; i < Size; i++) { (*this)[i] = *listIter; ++listIter; } } inline constexpr DiagonalMatrix(const T* list) : data{} { for(std::size_t i = 0; i < Size; i++) { (*this)[i] = list[i]; } } template inline constexpr DiagonalMatrix(const DiagonalMatrix& m) : data{} { for(std::size_t i = 0; i < Size; i++) { (*this)[i] = static_cast(m[i]); } } constexpr size_t width() const { return Size; } constexpr size_t height() const { return Size; } static inline constexpr DiagonalMatrix ZEROS() { DiagonalMatrix mat; for (std::size_t i = 0; i < Size; i++) { mat[i] = 0; } return mat; } static inline constexpr DiagonalMatrix IDENTITY() { DiagonalMatrix mat; for (std::size_t i = 0; i < Size; i++) { mat[i] = T(1); } return mat; } static inline constexpr DiagonalMatrix DIAGONAL(const T& diagonalVal) { DiagonalMatrix mat; for(std::size_t i = 0; i < Size; i++) { mat[i] = diagonalVal; } return mat; } operator SymmetricMatrix() const { SymmetricMatrix mat; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col < row; col++) { mat(row, col) = 0; } mat(row, row) = this->data[row]; } return mat; } operator Matrix() const { Matrix mat; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col < row; col++) { mat(row, col) = 0; mat(col, row) = 0; } mat(row, row) = (*this)[row]; } return mat; } }; template using SquareMatrix = Matrix; template using UnitaryMatrix = SquareMatrix; /* ===== Predefined matrices */ template using RotationMatrix = Matrix; // Mat2 typedef Matrix Mat2; typedef Matrix Mat2f; typedef Matrix Mat2l; typedef Matrix RotMat2; // Mat3 typedef Matrix Mat3; typedef Matrix Mat3f; typedef Matrix Mat3l; typedef SymmetricMatrix SymmetricMat3; typedef SymmetricMatrix SymmetricMat3f; typedef SymmetricMatrix SymmetricMat3l; typedef DiagonalMatrix DiagonalMat3; typedef DiagonalMatrix DiagonalMat3f; typedef DiagonalMatrix DiagonalMat3l; // Mat4 typedef Matrix Mat4; typedef Matrix Mat4f; typedef Matrix Mat4l; /* ===== Operators ===== */ template T det(const Matrix& matrix) { T total = 0; Matrix allButFirstRow = matrix.withoutRow(0); for (std::size_t col = 0; col < Size; col++) { T detOfMinor = det(allButFirstRow.withoutCol(col)); T value = detOfMinor * matrix(0, col); total += (col % 2 == 0)? value : -value; } return total; } template T det(const Matrix & matrix) { return 1; // for shits n giggles } template T det(const Matrix& matrix) { return matrix(0, 0); } template T det(const Matrix & matrix) { return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0); } template T det(const SymmetricMatrix& mat) { return det(static_cast>(mat)); } template T det(const DiagonalMatrix& mat) { T total = mat[0]; for (std::size_t i = 1; i < Size; i++) { total *= mat[i]; } return total; } template T det(const DiagonalMatrix& mat) { return 1; } template Matrix joinHorizontal(const Matrix& mat1, const Matrix& mat2) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width1; col++) { result(row, col) = mat1(row, col); } for (std::size_t col = 0; col < Width2; col++) { result(row, col + Width1) = mat2(row, col); } } return result; } template Matrix joinVertical(const Matrix& mat1, const Matrix& mat2) { Matrix result; for (std::size_t row = 0; row < Height1; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = mat1(row, col); } } for (std::size_t row = 0; row < Height2; row++) { for (std::size_t col = 0; col < Width; col++) { result(row + Height1, col) = mat2(row, col); } } return result; } template Matrix join(const Matrix& topLeft, const Matrix& topRight, const Matrix& botLeft, const Matrix& botRight) { Matrix result; for(std::size_t row = 0; row < Height1; row++) { for(std::size_t col = 0; col < Width1; col++) { result(row, col) = topLeft(row, col); } for(std::size_t col = 0; col < Width2; col++) { result(row, col + Width1) = topRight(row, col); } } for(std::size_t row = 0; row < Height2; row++) { for(std::size_t col = 0; col < Width1; col++) { result(row + Height1, col) = botLeft(row, col); } for(std::size_t col = 0; col < Width2; col++) { result(row + Height1, col + Width1) = botRight(row, col); } } return result; } template Matrix join(const Matrix & topLeft, const Vector & topRight, const Vector & botLeft, const T & botRight) { Matrix result; for(std::size_t row = 0; row < Height; row++) { for(std::size_t col = 0; col < Width; col++) { result(row, col) = topLeft(row, col); } result(row, Width) = topRight[row]; } for(std::size_t col = 0; col < Width; col++) { result(Height, col) = botLeft[col]; } result(Height, Width) = botRight; return result; } template Matrix joinDiagonal(const Matrix& topLeft, const Matrix& botRight) { Matrix result; for(std::size_t row = 0; row < Height1; row++) { for(std::size_t col = 0; col < Width1; col++) { result(row, col) = topLeft(row, col); } for(std::size_t col = 0; col < Width2; col++) { result(row, col + Width1) = 0; } } for(std::size_t row = 0; row < Height2; row++) { for(std::size_t col = 0; col < Width1; col++) { result(row + Height1, col) = 0; } for(std::size_t col = 0; col < Width2; col++) { result(row + Height1, col + Width1) = botRight(row, col); } } return result; } template Matrix joinDiagonal(const Matrix& topLeft, const T& botRight) { Matrix result; for(std::size_t row = 0; row < Height; row++) { for(std::size_t col = 0; col < Width; col++) { result(row, col) = topLeft(row, col); } result(row, Width) = 0; } for(std::size_t col = 0; col < Width; col++) { result(Height, col) = 0; } result(Height, Width) = botRight; return result; } /* ===== Everything standard matrix ===== */ template Matrix operator+(const Matrix& m1, const Matrix& m2) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = m1(row, col) + m2(row, col); } } return result; } template Matrix operator-(const Matrix& m1, const Matrix& m2) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = m1(row, col) - m2(row, col); } } return result; } template Matrix operator-(const Matrix& m) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = -m(row, col); } } return result; } template Vector operator*(const Matrix& m, const Vector& v) { Vector result; for (std::size_t row = 0; row < Height; row++) { T sum = m(row, 0) * v[0]; for (std::size_t col = 1; col < Width; col++) { sum += m(row, col) * v[col]; } result[row] = sum; } return result; } // Do Not Remove, actually improves performance on VC++ template Matrix operator*(const Matrix& m, const Matrix & m2) { Matrix result; result.data[0] = m.data[0] * m2.data[0] + m.data[3] * m2.data[1] + m.data[6] * m2.data[2]; result.data[1] = m.data[1] * m2.data[0] + m.data[4] * m2.data[1] + m.data[7] * m2.data[2]; result.data[2] = m.data[2] * m2.data[0] + m.data[5] * m2.data[1] + m.data[8] * m2.data[2]; result.data[3] = m.data[0] * m2.data[3] + m.data[3] * m2.data[4] + m.data[6] * m2.data[5]; result.data[4] = m.data[1] * m2.data[3] + m.data[4] * m2.data[4] + m.data[7] * m2.data[5]; result.data[5] = m.data[2] * m2.data[3] + m.data[5] * m2.data[4] + m.data[8] * m2.data[5]; result.data[6] = m.data[0] * m2.data[6] + m.data[3] * m2.data[7] + m.data[6] * m2.data[8]; result.data[7] = m.data[1] * m2.data[6] + m.data[4] * m2.data[7] + m.data[7] * m2.data[8]; result.data[8] = m.data[2] * m2.data[6] + m.data[5] * m2.data[7] + m.data[8] * m2.data[8]; return result; } // Do Not Remove, actually improves performance on VC++ template Vector operator*(const Matrix& m, const Vector& v) { T x = m.data[0] * v[0] + m.data[3] * v[1] + m.data[6] * v[2]; T y = m.data[1] * v[0] + m.data[4] * v[1] + m.data[7] * v[2]; T z = m.data[2] * v[0] + m.data[5] * v[1] + m.data[8] * v[2]; return Vector(x, y, z); } template Matrix operator*(const Matrix& m1, const Matrix& m2) { Matrix result; for (std::size_t col = 0; col < ResultWidth; col++) { for (std::size_t row = 0; row < ResultHeight; row++) { T sum = m1(row, 0) * m2(0, col); for (std::size_t i = 1; i < IntermediateSize; i++) { sum += m1(row, i) * m2(i, col); } result(row, col) = sum; } } return result; } template Matrix operator*(const Matrix& m1, const T& factor) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = m1(row, col) * factor; } } return result; } template Matrix operator*(const T& factor, const Matrix& m1) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = factor * m1(row, col); } } return result; } template Matrix operator/(const Matrix& m1, const T& factor) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = m1(row, col) / factor; } } return result; } template Matrix& operator+=(Matrix& m1, const Matrix& m2) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { m1(row, col) += m2(row, col); } } return m1; } template Matrix& operator-=(Matrix& m1, const Matrix& m2) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { m1(row, col) -= m2(row, col); } } return m1; } template Matrix& operator*=(Matrix& mat, const T& factor) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) *= factor; } } return mat; } template Matrix& operator/=(Matrix& mat, const T& factor) { for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { mat(row, col) /= factor; } } return mat; } template Matrix operator~(const Matrix& matrix) { Matrix result; T d = det(matrix); for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col < Size; col++) { Matrix tmp = matrix.withoutRowCol(row, col); T coFactor = det(tmp); T value = coFactor / d; // transpose the coFactors here, therefore indexing (col, row) result(col, row) = ((row + col) % 2 == 0) ? value : -value; } } return result; } template Matrix inverse(const Matrix& matrix) { return ~matrix; } /* ===== Everything symmetric matrix ===== */ template SymmetricMatrix operator+(const SymmetricMatrix& m1, const SymmetricMatrix& m2) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = m1(row, col) + m2(row, col); } } return result; } template SymmetricMatrix operator-(const SymmetricMatrix& m1, const SymmetricMatrix& m2) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = m1(row, col) - m2(row, col); } } return result; } template SymmetricMatrix operator-(const SymmetricMatrix& m) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = -m(row, col); } } return result; } template Vector operator*(const SymmetricMatrix& m, const Vector& v) { Vector result; for (std::size_t row = 0; row < Size; row++) { T sum = m(row, 0) * v[0]; for (std::size_t col = 1; col < Size; col++) { sum += m(row, col) * v[col]; } result[row] = sum; } return result; } template SymmetricMatrix operator*(const SymmetricMatrix& m1, const SymmetricMatrix& m2) { SymmetricMatrix result; for (std::size_t col = 0; col < Size; col++) { for (std::size_t row = 0; row <= col; row++) { T sum = m1(row, 0) * m2(0, col); for (std::size_t i = 1; i < Size; i++) { sum += m1(row, i) * m2(i, col); } result(row, col) = sum; } } return result; } template SymmetricMatrix operator*(const SymmetricMatrix& m1, const T& factor) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = m1(row, col) * factor; } } return result; } template SymmetricMatrix operator*(const T& factor, const SymmetricMatrix& m1) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = factor * m1(row, col); } } return result; } template SymmetricMatrix operator/(const SymmetricMatrix& m1, const T& factor) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = m1(row, col) / factor; } } return result; } template SymmetricMatrix& operator+=(SymmetricMatrix& m1, const SymmetricMatrix& m2) { for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { m1(row, col) += m2(row, col); } } return m1; } template SymmetricMatrix& operator-=(SymmetricMatrix& m1, const SymmetricMatrix& m2) { for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { m1(row, col) -= m2(row, col); } } return m1; } template SymmetricMatrix& operator*=(SymmetricMatrix& mat, const T& factor) { for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { mat(row, col) *= factor; } } return mat; } template SymmetricMatrix& operator/=(SymmetricMatrix& mat, const T& factor) { for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { mat(row, col) /= factor; } } return mat; } template SymmetricMatrix operator~(const SymmetricMatrix& matrix) { SymmetricMatrix result; T d = det(matrix); for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { Matrix tmp = static_cast>(matrix).withoutRowCol(row, col); T coFactor = det(tmp); T value = coFactor / d; // transpose the coFactors here, therefore indexing (col, row) result(col, row) = ((row + col) % 2 == 0) ? value : -value; } } return result; } template SymmetricMatrix inverse(const SymmetricMatrix& matrix) { return ~matrix; } /* ===== Everything Diagonal Matrix ===== */ template DiagonalMatrix operator+(const DiagonalMatrix& m1, const DiagonalMatrix& m2) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = m1[i] + m2[i]; } return result; } template DiagonalMatrix operator-(const DiagonalMatrix& m1, const DiagonalMatrix& m2) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = m1[i] - m2[i]; } return result; } template DiagonalMatrix operator-(const DiagonalMatrix& m) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = -m[i]; } return result; } template Vector operator*(const DiagonalMatrix& m, const Vector& v) { Vector result; for (std::size_t i = 0; i < Size; i++) { result[i] = m[i] * v[i]; } return result; } template DiagonalMatrix operator*(const DiagonalMatrix& m1, const DiagonalMatrix& m2) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = m1[i] * m2[i]; } return result; } template DiagonalMatrix operator*(const DiagonalMatrix& m1, const T& factor) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = m1[i] * factor; } return result; } template DiagonalMatrix operator*(const T& factor, const DiagonalMatrix& m1) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = factor * m1[i]; } return result; } template DiagonalMatrix operator/(const DiagonalMatrix& m1, const T& factor) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = m1[i] / factor; } return result; } template DiagonalMatrix& operator+=(DiagonalMatrix& m1, const DiagonalMatrix& m2) { for (std::size_t i = 0; i < Size; i++) { m1[i] += m2[i]; } return m1; } template DiagonalMatrix& operator-=(DiagonalMatrix& m1, const DiagonalMatrix& m2) { for (std::size_t i = 0; i < Size; i++) { m1[i] -= m2[i]; } return m1; } template DiagonalMatrix& operator*=(DiagonalMatrix& mat, const T& factor) { for (std::size_t i = 0; i < Size; i++) { mat[i] *= factor; } return mat; } template DiagonalMatrix& operator*=(DiagonalMatrix& mat, const DiagonalMatrix& other) { for (std::size_t i = 0; i < Size; i++) { mat[i] *= other[i]; } return mat; } template DiagonalMatrix& operator/=(DiagonalMatrix& mat, const T& factor) { for (std::size_t i = 0; i < Size; i++) { mat[i] /= factor; } return mat; } template DiagonalMatrix operator~(const DiagonalMatrix& matrix) { DiagonalMatrix result; for (std::size_t i = 0; i < Size; i++) { result[i] = 1 / matrix[i]; } return result; } template DiagonalMatrix inverse(const DiagonalMatrix& matrix) { return ~matrix; } /* ===== Compatibility between the matrix types ===== */ template SymmetricMatrix operator*(const SymmetricMatrix& m1, const DiagonalMatrix& m2) { return m1 * static_cast>(m2); } template SymmetricMatrix operator*(const DiagonalMatrix& m1, const SymmetricMatrix& m2) { return static_cast>(m1) * m2; } template Matrix operator*(const SymmetricMatrix& m1, const Matrix& m2) { return static_cast>(m1)* m2; } template Matrix operator*(const Matrix& m1, const SymmetricMatrix& m2) { return m1 * static_cast>(m2); } template Matrix operator*(const DiagonalMatrix& m1, const Matrix& m2) { return static_cast>(m1)* m2; } template Matrix operator*(const Matrix& m1, const DiagonalMatrix& m2) { return m1 * static_cast>(m2); } /* ===== Other functions ===== */ template Matrix outer(const Vector& v1, const Vector& v2) { Matrix result; for (std::size_t row = 0; row < Height; row++) { for (std::size_t col = 0; col < Width; col++) { result(row, col) = v1[row] * v2[col]; } } return result; } template SymmetricMatrix selfOuter(const Vector& v) { SymmetricMatrix result; for (std::size_t row = 0; row < Size; row++) { for (std::size_t col = 0; col <= row; col++) { result(row, col) = v[row] * v[col]; } } return result; } template Vector toVector(const Matrix& mat) { Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = mat(i, 0); } return result; } template Vector toVector(const Matrix& mat) { Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = mat(0, i); } return result; } template Matrix toRowMatrix(const Vector& vec) { Matrix result; for(std::size_t i = 0; i < Size; i++) { result(0, i) = vec[i]; } return result; } template Matrix toColMatrix(const Vector& vec) { Matrix result; for(std::size_t i = 0; i < Size; i++) { result(i, 0) = vec[i]; } return result; } template SymmetricMatrix makeSymmetric(const SquareMatrix& mat) { SymmetricMatrix result; for(std::size_t row = 0; row < Size; row++) { for(std::size_t col = 0; col <= row; col++) { result(row, col) = mat(row, col); assert(std::abs(mat(row, col) - mat(col, row)) < 1E-6); } } return result; } /* Computes mat + mat.transpose() */ template SymmetricMatrix addTransposed(const SquareMatrix& mat) { SymmetricMatrix result; for(std::size_t resultRow = 0; resultRow < Size; resultRow++) { for(std::size_t resultCol = 0; resultCol <= resultRow; resultCol++) { result(resultRow, resultCol) = mat(resultRow, resultCol) + mat(resultCol, resultRow); } } return result; } /* computes m * sym * m.transpose() */ template SymmetricMatrix mulSymmetricLeftRightTranspose(const SymmetricMatrix& sym, const SquareMatrix& m) { SquareMatrix symResult = m * sym * m.transpose(); return makeSymmetric(symResult); } /* computes m.transpose() * sym * m */ template SymmetricMatrix mulSymmetricLeftTransposeRight(const SymmetricMatrix& sym, const SquareMatrix& m) { SquareMatrix symResult = m.transpose() * sym * m; return makeSymmetric(symResult); } /* Generic matrix functions */ template auto getSubMatrix(const MatrixT& matrix, size_t rowOffset = 0, size_t colOffset = 0) -> Matrix::type, R, C> { assert(rowOffset >= 0 && rowOffset + R <= matrix.height() && colOffset >= 0 && colOffset + C <= matrix.width()); Matrix result; for(size_t r = 0; r < R; r++) { for(size_t c = 0; c < C; c++) { result(r, c) = matrix(r + rowOffset, c + colOffset); } } return result; } template void setSubMatrix(MatrixT& matrix, const Matrix()(0,0))>::type, R, C>& value, size_t rowOffset = 0, size_t colOffset = 0) { assert(rowOffset >= 0 && rowOffset + value.height() <= matrix.height() && colOffset >= 0 && colOffset + value.width() <= matrix.width()); for(size_t r = 0; r < R; r++) { for(size_t c = 0; c < C; c++) { matrix(r + rowOffset, c + colOffset) = value(r, c); } } } template void setSubMatrix(MatrixT& matrix, const SymmetricMatrix()(0, 0))>::type, Size>& value, size_t rowOffset = 0, size_t colOffset = 0) { assert(rowOffset >= 0 && rowOffset + value.height() <= matrix.height() && colOffset >= 0 && colOffset + value.width() <= matrix.width()); for(size_t r = 0; r < Size; r++) { for(size_t c = 0; c < r; c++) { const auto& v = value(r, c); matrix(r + rowOffset, c + colOffset) = v; matrix(c + colOffset, r + rowOffset) = v; } matrix(r + rowOffset, r + colOffset) = value(r, r); } } template void setSubMatrix(MatrixT& matrix, const DiagonalMatrix()(0, 0))>::type, Size>& value, size_t rowOffset = 0, size_t colOffset = 0) { assert(rowOffset >= 0 && rowOffset + value.height() <= matrix.height() && colOffset >= 0 && colOffset + value.width() <= matrix.width()); for(size_t r = 0; r < Size; r++) { for(size_t c = 0; c < Size; c++) { matrix(r + rowOffset, c + colOffset) = (r == c)? value[r] : 0; } } } }; ================================================ FILE: Physics3D/math/linalg/quat.h ================================================ #pragma once #include #include #include "vec.h" namespace P3D { template struct Quaternion { T w; union { struct { T i; T j; T k; }; Vector v; }; constexpr Quaternion() : w(0), i(0), j(0), k(0) {} constexpr Quaternion(T w, T i, T j, T k) : w(w), i(i), j(j), k(k) {} constexpr Quaternion(T w, Vector v) : w(w), v(v) {} template explicit operator Quaternion() const { Quaternion result; result.w = static_cast(w); result.i = static_cast(i); result.j = static_cast(j); result.k = static_cast(k); return result; } static inline constexpr Quaternion IDENTITY() { return Quaternion(1, 0, 0, 0); } static inline constexpr Quaternion ZEROS() { return Quaternion(0, 0, 0, 0); } }; typedef Quaternion Quat4; typedef Quaternion Quat4f; typedef Quaternion Quat4l; typedef Quaternion Quat4i; template Quaternion operator*(const Quaternion& a, const Quaternion& b) { T w = a.w * b.w - (a.v * b.v); Vector v = a.w * b.v + a.v * b.w + (a.v % b.v); return Quaternion(w, v); } // computes q * Quaternion(0, v) template Quaternion operator*(const Quaternion& q, const Vector& v) { return Quaternion(-(v * q.v), q.w * v + q.v % v); } // computes Quaternion(0, v) * q template Quaternion operator*(const Vector& v, const Quaternion& q) { return Quaternion(-(v * q.v), q.w * v + v % q.v); } // computes q * v * conj(q) if q is a unit quaternion template Vector mulQuaternionLeftRightConj(const Quaternion& q, const Vector& v) { Vector u = q.v; Vector cross_uv = u % v; return v + ((cross_uv * q.w) + u % cross_uv) * static_cast(2); } // computes conj(q) * v * q if q is a unit quaternion template Vector mulQuaternionLeftConjRight(const Quaternion& q, const Vector& v) { Vector u = q.v; Vector cross_uv = v % u; return v + ((cross_uv * q.w) + cross_uv % u) * static_cast(2); } template Quaternion operator*(const Quaternion& quat, T factor) { return Quaternion(quat.w * factor, quat.v * factor); } template Quaternion operator*(T factor, const Quaternion& quat) { return Quaternion(factor * quat.w, factor * quat.v); } template Quaternion operator+(const Quaternion& a, const Quaternion& b) { return Quaternion(a.w + b.w, a.v + b.v); } template Quaternion operator-(const Quaternion& a, const Quaternion& b) { return Quaternion(a.w - b.w, a.v - b.v); } template Quaternion operator/(const Quaternion& quat, T factor) { return Quaternion(quat.w / factor, quat.v / factor); } template Quaternion operator-(const Quaternion& quat) { return Quaternion(-quat.w, -quat.v); } template Quaternion& operator+=(Quaternion& quat, const Quaternion& other) { quat.w += other.w; quat.v += other.v; return quat; } template Quaternion& operator-=(Quaternion& quat, const Quaternion& other) { quat.w -= other.w; quat.v -= other.v; return quat; } template Quaternion& operator*=(Quaternion& quat, T factor) { quat.w *= factor; quat.v *= factor; return quat; } template Quaternion& operator/=(Quaternion& quat, T factor) { quat.w /= factor; quat.v /= factor; return quat; } template T dot(const Quaternion& a, const Quaternion& b) { return a.w * b.w + a.i * b.i + a.j * b.j + a.k * b.k; } template Quaternion operator!(const Quaternion& quat) { return Quaternion(quat.w, -quat.v); } template Quaternion conj(const Quaternion& quat) { return !quat; } template T lengthSquared(const Quaternion& q) { return q.w * q.w + q.i * q.i + q.j * q.j + q.k * q.k; } template T length(const Quaternion& quat) { return std::sqrt(lengthSquared(quat)); } template Quaternion normalize(const Quaternion& quat) { return quat / length(quat); } template Quaternion operator~(const Quaternion& quat) { return !quat / lengthSquared(quat); } template Quaternion inverse(const Quaternion& quat) { return ~quat; } }; ================================================ FILE: Physics3D/math/linalg/trigonometry.cpp ================================================ #include "trigonometry.h" #include "mat.h" #include namespace P3D { template Matrix rotate(const Matrix& mat, T angle, T x, T y, T z) { T s = sin(angle); T c = cos(angle); T C = (1 - c); Matrix rotator{ x * x * C + c, x * y * C - z * s, x * z * C + y * s, y * x * C + z * s, y * y * C + c, y * z * C - x * s, z * x * C - y * s, z * y * C + x * s, z * z * C + c }; Matrix leftSide = mat.template getSubMatrix<4, 3>(0, 0); Matrix rotated = leftSide * rotator; Matrix translation = mat.template getSubMatrix<4, 1>(0, 3); return joinHorizontal(rotated, translation); } Mat4f ortho(float left, float right, float bottom, float top, float zNear, float zFar) { float r00 = 2.0f / (right - left); float r11 = 2.0f / (top - bottom); float r22 = -2.0f / (zFar - zNear); float r30 = (left + right) / (left - right); float r31 = (top + bottom) / (bottom - top); float r32 = (zFar + zNear) / (zNear - zFar); float r33 = 1.0f; return Mat4f{ r00, 0, 0, r30, 0, r11, 0, r31, 0, 0, r22, r32, 0, 0, 0, r33 }; } Mat4f perspective(float fov, float aspect, float zNear, float zFar) { float t = tan(fov / 2); float r00 = 1 / (t * aspect); float r11 = 1 / t; float r22 = (zFar + zNear) / (zNear - zFar); float r32 = (zFar + zFar) * zNear / (zNear - zFar); float r23 = -1; return Mat4f{ r00, 0, 0, 0, 0, r11, 0, 0, 0, 0, r22, r32, 0, 0, r23, 0 }; } Mat4f lookAt(const Vec3f& from, const Vec3f& to, const Vec3f& up) { /*Vec3f z = normalize(to - from); Vec3f x = normalize(up) % z; Vec3f y = z % x; return Mat4f { x.x, x.y, x.z, -from.x, y.x, y.y, y.z, -from.y, -z.x, -z.y, -z.z, from.z, 0.0f, 0.0f, 0.0f, 1.0f };*/ Vec3f f = normalize(to - from); Vec3f u = normalize(up); Vec3f s = normalize(f % u); u = s % f; return Mat4f{ s.x, s.y, s.z, -dot(s, from), u.x, u.y, u.z, -dot(u, from), -f.x, -f.y, -f.z, dot(f, from), 0.0f, 0.0f, 0.0f, 1.0f }; } }; ================================================ FILE: Physics3D/math/linalg/trigonometry.h ================================================ #pragma once #include "vec.h" #include "mat.h" #include "quat.h" #include namespace P3D { /* Creates a matrix such that for any vector x: v % x == createCrossProductEquivalent(v) * x */ template Matrix createCrossProductEquivalent(const Vector & vec) { return Matrix{ 0, -vec.z, vec.y, vec.z, 0, -vec.x, -vec.y, vec.x, 0 }; } /* Returns a matrix equivalent to (v % (v % x)) = skewSymmetricSquared(v) * x which is also equal to createCrossProductEquivalent(v)^2 = skewSymmetricSquared(v) */ template SymmetricMatrix skewSymmetricSquared(const Vector & v) { N x = v.x, y = v.y, z = v.z; return SymmetricMatrix{ -(y * y + z * z), x * y, -(x * x + z * z), x * z, y * z, -(x * x + y * y) }; } template SymmetricMatrix transformBasis(const SymmetricMatrix & sm, const Matrix & rotation) { Matrix r = rotation * sm * ~rotation; return SymmetricMatrix{ r(0, 0), r(1, 0), r(1, 1), r(2, 0), r(2, 1), r(2, 2) }; } template SymmetricMatrix transformBasis(const DiagonalMatrix & dm, const Matrix & rotation) { Matrix r = rotation * dm * ~rotation; return SymmetricMatrix{ r(0, 0), r(1, 0), r(1, 1), r(2, 0), r(2, 1), r(2, 2) }; } template SymmetricMatrix multiplyLeftRight(const SymmetricMatrix & sm, const Matrix & otherMat) { Matrix r = otherMat * sm * otherMat.transpose(); return SymmetricMatrix{ r(0, 0), r(1, 0), r(1, 1), r(2, 0), r(2, 1), r(2, 2) }; } template UnitaryMatrix rotMatX(T angle) { T sina = sin(angle); T cosa = cos(angle); return UnitaryMatrix{ 1, 0, 0, 0, cosa, -sina, 0, sina, cosa }; } template UnitaryMatrix rotMatY(T angle) { T sina = sin(angle); T cosa = cos(angle); return UnitaryMatrix{ cosa, 0, sina, 0, 1, 0, -sina, 0, cosa }; } template UnitaryMatrix rotMatZ(T angle) { T sina = sin(angle); T cosa = cos(angle); return UnitaryMatrix{ cosa, -sina, 0, sina, cosa, 0, 0, 0, 1 }; } template Quaternion rotQuatX(T angle) { return Quaternion(std::cos(angle/2), std::sin(angle/2), 0, 0); } template Quaternion rotQuatY(T angle) { return Quaternion(std::cos(angle/2), 0, std::sin(angle/2), 0); } template Quaternion rotQuatZ(T angle) { return Quaternion(std::cos(angle/2), 0, 0, std::sin(angle/2)); } template Vector getPerpendicular(Vector v) { Vector notInline(0, 0, 0); notInline[getAbsMinElementIndex(v)] = 1; return v % notInline; } template UnitaryMatrix faceMatX(Vector x, Vector yHint) { x = normalize(x); Vector z = normalize(x % yHint); Vector y = z % x; return UnitaryMatrix::fromColumns({x, y, z}); } template UnitaryMatrix faceMatX(Vector x) { Vector yHint(0,0,0); yHint[getAbsMinElementIndex(x)] = 1; return faceMatX(x, yHint); } template UnitaryMatrix faceMatY(Vector y, Vector zHint) { y = normalize(y); Vector x = normalize(y % zHint); Vector z = x % y; return UnitaryMatrix::fromColumns({x, y, z}); } template UnitaryMatrix faceMatY(Vector y) { Vector zHint(0, 0, 0); zHint[getAbsMinElementIndex(y)] = 1; return faceMatY(y, zHint); } template UnitaryMatrix faceMatZ(Vector z, Vector xHint) { z = normalize(z); Vector y = normalize(z % xHint); Vector x = y % z; return UnitaryMatrix::fromColumns({x, y, z}); } template UnitaryMatrix faceMatZ(Vector z) { Vector xHint(0, 0, 0); xHint[getAbsMinElementIndex(z)] = 1; return faceMatZ(z, xHint); } /* Produces a rotation matrix from the provided euler angles Equivalent to rotMatZ(gamma) * rotMatX(alpha) * rotMatY(beta) */ template UnitaryMatrix rotationMatrixfromEulerAngles(T alpha, T beta, T gamma) { return rotMatZ(gamma) * rotMatX(alpha) * rotMatY(beta); } template RotationMatrix rotationMatrixFromRotationVec(Vector rotVec) { T angleSq = lengthSquared(rotVec); T angle = std::sqrt(angleSq); // sinc(angle) = sin(angle) / angle // around angle=0 =~ 1 - angle^2 / 6 + angle^4 / 120 - angle^6 / 5040 T sincAngle = (angleSq > 1E-20) ? std::sin(angle) / angle : 1 - angleSq / 6; T cosAngle = std::cos(angle); // cosc(angle) = (cos(angle) - 1) / angle^2 // around angle=0 =~ 1/2 - angle^2 / 24 + angle^4 / 720 - angle^6 / 40320 T coscAngle = (angleSq > 1E-20) ? (1 - cosAngle) / angleSq : T(0.5) - angleSq / 24; Vector sincVec = rotVec * sincAngle; Matrix rotor{ cosAngle, -sincVec.z, sincVec.y, sincVec.z, cosAngle, -sincVec.x, -sincVec.y, sincVec.x, cosAngle }; return outer(rotVec, rotVec) * coscAngle + rotor; } template Quaternion rotationQuaternionFromRotationVec(Vector rotVec) { T angleSq = lengthSquared(rotVec); T angle = std::sqrt(angleSq); // sincDiv2(angle) = sin(angle/2) / angle // around angle=0 =~ 1 - angle^2 / 24 + angle^4 / 480 - angle^6 / 20160 T sincAngleDiv2 = (angleSq > 1E-20) ? std::sin(angle/2) / angle : 1 - angleSq / 24; return Quaternion(std::cos(angle/2), sincAngleDiv2 * rotVec); } template Vector rotationVectorFromRotationMatrix(const RotationMatrix& m) { assert(isValidRotationMatrix(m)); Vector axisOfRotation(m(2, 1) - m(1, 2), m(0, 2) - m(2, 0), m(1, 0) - m(0, 1)); if(axisOfRotation[0] == 0 && axisOfRotation[1] == 0 && axisOfRotation[2] == 0) return Vector(0, 0, 0); T trace = m(0, 0) + m(1, 1) + m(2, 2); T angle = std::acos((trace - 1) / 2); return normalize(axisOfRotation) * angle; } template Vector rotationVectorFromRotationQuaternion(const Quaternion& q) { assert(isValidRotationQuaternion(q)); // q.v = sin(length(result)) * normalize(result); Vector axisOfRotation = q.v; // length is sin, still need to multiply by T sinAngleDiv2 = length(axisOfRotation); T cosAngleDiv2 = q.w; // asincAngle == asin(sinAngle) / sinAngle, though this incorporates the sign brought by the w term // atan is defined for +-infinity, and sin and cos can never be 0 at the same time, so no worry T asincAngleDiv2 = (sinAngleDiv2 > 1E-10) ? std::atan(sinAngleDiv2 / cosAngleDiv2) / sinAngleDiv2 : 1 / cosAngleDiv2; return axisOfRotation * asincAngleDiv2 * T(2); } template Matrix rotationMatrixFromQuaternion(const Quaternion& quat) { assert(isValidRotationQuaternion(quat)); Vector quatVSq = elementWiseSquare(quat.v); Vector diagElements = Vector(1,1,1) - T(2) * addSelfOpposites(quatVSq); Vector selfMul = T(2) * mulSelfOpposites(quat.v); Vector mvMul = (T(2)*quat.w) * quat.v; return Matrix{ diagElements.x, selfMul.z - mvMul.z, selfMul.y + mvMul.y, selfMul.z + mvMul.z, diagElements.y, selfMul.x - mvMul.x, selfMul.y - mvMul.y, selfMul.x + mvMul.x, diagElements.z }; } }; #include namespace P3D { template Quaternion rotationQuaternionFromRotationMatrix(const Matrix& a) { assert(isValidRotationMatrix(a)); Quaternion q; T trace = a(0, 0) + a(1, 1) + a(2, 2); if(trace > 0) { T s = T(0.5) / std::sqrt(trace + 1); q.w = T(0.25) / s; q.i = (a(2, 1) - a(1, 2)) * s; q.j = (a(0, 2) - a(2, 0)) * s; q.k = (a(1, 0) - a(0, 1)) * s; } else { if(a(0, 0) > a(1, 1) && a(0, 0) > a(2, 2)) { T s = T(2.0) * std::sqrt(1 + a(0, 0) - a(1, 1) - a(2, 2)); q.w = (a(2, 1) - a(1, 2)) / s; q.i = T(0.25) * s; q.j = (a(0, 1) + a(1, 0)) / s; q.k = (a(0, 2) + a(2, 0)) / s; } else if(a(1, 1) > a(2, 2)) { T s = T(2.0) * std::sqrt(1 + a(1, 1) - a(0, 0) - a(2, 2)); q.w = (a(0, 2) - a(2, 0)) / s; q.i = (a(0, 1) + a(1, 0)) / s; q.j = T(0.25) * s; q.k = (a(1, 2) + a(2, 1)) / s; } else { T s = T(2.0) * std::sqrt(1 + a(2, 2) - a(0, 0) - a(1, 1)); q.w = (a(1, 0) - a(0, 1)) / s; q.i = (a(0, 2) + a(2, 0)) / s; q.j = (a(1, 2) + a(2, 1)) / s; q.k = T(0.25) * s; } } return q; } template Matrix rotateAround(T angle, Vector normal) { // Using Rodrigues rotation formula; normal = normalize(normal); Matrix W{ 0, -normal.z, normal.y, normal.z, 0, -normal.x, -normal.y, normal.x, 0 }; Matrix W2 = W * W; T s = sin(angle); T s2 = sin(angle / 2); Matrix R = Matrix::IDENTITY() + W * s + W2 * (2 * s2 * s2); return R; } // mat4 Mat4f ortho(float left, float right, float bottom, float top, float zNear, float zFar); Mat4f perspective(float fov, float aspect, float zNear, float zFar); Mat4f lookAt(const Vec3f& from, const Vec3f& to, const Vec3f& up = Vec3f(0, 1.0f, 0)); template Matrix rotate(const Matrix&, T angle, T x, T y, T z); template Matrix translate(const Matrix& mat, T x, T y, T z) { Matrix r{x, y, z, 1.0}; Matrix rr = mat * r; return joinHorizontal(mat.template getSubMatrix<4, 3>(0, 0), rr); } template Matrix scale(const Matrix& mat, const Vector& scaleVec) { Matrix result; for(int i = 0; i < 3; i++) { for(int j = 0; j < 4; j++) { result(j, i) = mat(j, i) * scaleVec[i]; } } for(int j = 0; j < 4; j++) { result(j, 3) = mat(j, 3); } return result; } template Matrix scale(const Matrix& mat, T x, T y, T z) { return scale(mat, Vector(x, y, z)); } template Matrix scale(const Matrix& mat, T v) { return scale(mat, v, v, v); } template Matrix translate(const Matrix& mat, const Vector& dv) { return translate(mat, dv.x, dv.y, dv.z); } inline bool equalsApproximately(double value, double comparedTo) { auto delta = comparedTo - value; return delta < 1E-8 && delta > -1E-8; } inline bool equalsApproximately(float value, float comparedTo) { auto delta = comparedTo - value; return delta < 1E-4 && delta > -1E-4; } template bool isValidRotationMatrix(const Matrix& mat) { constexpr T zero = 0; constexpr T one = 1; for(int i = 0; i < 3; i++) { T rowLengthSq = lengthSquared(mat.getRow(i)); T colLengthSq = lengthSquared(mat.getCol(i)); if(!equalsApproximately(rowLengthSq, one) || !equalsApproximately(colLengthSq, one)) { return false; } } for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if(i == j) continue; if(!equalsApproximately(mat.getRow(i) * mat.getRow(j), zero) || !equalsApproximately(mat.getCol(i) * mat.getCol(j), zero)) { return false; } } } T detMat = det(mat); if(!equalsApproximately(detMat, one)) { return false; } return true; } template bool isValidRotationQuaternion(const Quaternion& quat) { return equalsApproximately(lengthSquared(quat), T(1)); } }; ================================================ FILE: Physics3D/math/linalg/vec.h ================================================ #pragma once #include #include #include namespace P3D { template struct Vector { T data[Size]; constexpr Vector() noexcept : data{} { for(T& item : this->data) { item = 0; } } template constexpr operator Vector() const noexcept { Vector result; for(size_t i = 0; i < Size; i++) { result.data[i] = static_cast(this->data[i]); } return result; } constexpr size_t size() const { return Size; } constexpr T& operator[](size_t index) noexcept { return data[index]; } constexpr const T& operator[](size_t index) const noexcept { return data[index]; } static constexpr Vector full(T v) noexcept { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = v; } return result; } template constexpr Vector getSubVector(size_t startingAt = 0) const noexcept { Vector result; for(size_t i = 0; i < SubSize; i++) { result[i] = this->data[i + startingAt]; } return result; } }; template struct Vector { union { T data[1]; struct { T x; }; }; constexpr Vector() noexcept : data{0} {} constexpr Vector(T x) noexcept : data{x} {} template constexpr operator Vector() const noexcept { return Vector(static_cast(this->data[0])); } constexpr size_t size() const { return 1; } constexpr T& operator[](size_t index) noexcept { return data[index]; } constexpr const T& operator[](size_t index) const noexcept { return data[index]; } static constexpr Vector full(T v) noexcept { return Vector(v); } template constexpr Vector getSubVector(size_t startingAt = 0) const noexcept { Vector result; for(size_t i = 0; i < SubSize; i++) { result[i] = this->data[i + startingAt]; } return result; } constexpr operator T() const { return data[0]; } }; template struct Vector { union { T data[2]; struct { T x; T y; }; }; constexpr Vector() noexcept : data{0, 0} {} constexpr Vector(T x, T y) noexcept : data{x, y} {} template constexpr operator Vector() const noexcept { return Vector(static_cast(this->data[0]), static_cast(this->data[1])); } constexpr size_t size() const { return 2; } constexpr T& operator[](size_t index) noexcept { return data[index]; } constexpr const T& operator[](size_t index) const noexcept { return data[index]; } static constexpr Vector full(T v) noexcept { return Vector(v, v); } template constexpr Vector getSubVector(size_t startingAt = 0) const noexcept { Vector result; for(size_t i = 0; i < SubSize; i++) { result[i] = this->data[i + startingAt]; } return result; } }; template struct Vector { union { T data[3]; struct { T x; T y; T z; }; }; constexpr Vector() noexcept : data{0, 0, 0} {} constexpr Vector(T x, T y, T z) noexcept : data{x, y, z} {}; template constexpr operator Vector() const noexcept { return Vector(static_cast(this->data[0]), static_cast(this->data[1]), static_cast(this->data[2])); } constexpr size_t size() const { return 3; } constexpr T& operator[](size_t index) noexcept { return data[index]; } constexpr const T& operator[](size_t index) const noexcept { return data[index]; } static constexpr Vector full(T v) noexcept { return Vector(v, v, v); } template constexpr Vector getSubVector(size_t startingAt = 0) const noexcept { Vector result; for(size_t i = 0; i < SubSize; i++) { result[i] = this->data[i + startingAt]; } return result; } }; template struct Vector { union { T data[4]; struct { T x; T y; T z; T w; }; }; constexpr Vector() noexcept : data{0, 0, 0, 0} {} constexpr Vector(T x, T y, T z, T w) noexcept : data{x, y, z, w} {} template constexpr operator Vector() const noexcept { return Vector(static_cast(this->data[0]), static_cast(this->data[1]), static_cast(this->data[2]), static_cast(this->data[3])); } constexpr size_t size() const { return 4; } constexpr T& operator[](size_t index) noexcept { return data[index]; } constexpr const T& operator[](size_t index) const noexcept { return data[index]; } static constexpr Vector full(T v) noexcept { return Vector(v, v, v, v); } template constexpr Vector getSubVector(size_t startingAt = 0) const noexcept { Vector result; for(size_t i = 0; i < SubSize; i++) { result[i] = this->data[i + startingAt]; } return result; } }; typedef Vector Vec1; typedef Vector Vec1f; typedef Vector Vec1l; typedef Vector Vec1i; typedef Vector Vec2; typedef Vector Vec2f; typedef Vector Vec2l; typedef Vector Vec2i; typedef Vector Vec3; typedef Vector Vec3f; typedef Vector Vec3l; typedef Vector Vec3i; typedef Vector Vec4; typedef Vector Vec4f; typedef Vector Vec4l; typedef Vector Vec4i; typedef Vector Vec5; typedef Vector Vec5f; typedef Vector Vec5l; typedef Vector Vec5i; typedef Vector Vec6; typedef Vector Vec6f; typedef Vector Vec6l; typedef Vector Vec6i; template constexpr auto operator*(const Vector& a, const Vector& b) noexcept -> decltype(a[0] * b[0] + a[1] * b[1]) { decltype(a[0] * b[0] + a[1] * b[1]) result = a[0] * b[0]; for(size_t i = 1; i < Size; i++) { result += a[i] * b[i]; } return result; } template constexpr auto dot(const Vector& a, const Vector& b) noexcept -> decltype(a[0] * b[0] + a[1] * b[1]) { return a * b; } template constexpr auto dot(const Vector& vec) noexcept -> decltype(vec[0] * vec[0] + vec[1] * vec[1]) { return vec * vec; } template constexpr auto operator+(const Vector& a, const Vector& b) noexcept -> Vector { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = a[i] + b[i]; } return result; } template constexpr auto operator-(const Vector& a, const Vector& b) noexcept -> Vector { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = a[i] - b[i]; } return result; } template constexpr auto operator*(const Vector& vec, const T2& factor) noexcept -> Vector { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = vec[i] * factor; } return result; } template constexpr auto operator*(const T1& factor, const Vector& vec) noexcept -> Vector { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = factor * vec[i]; } return result; } template constexpr auto operator/(const Vector& vec, const T2& factor) noexcept -> Vector { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = vec[i] / factor; } return result; } template constexpr Vector operator-(const Vector& vec) noexcept { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = -vec[i]; } return result; } template constexpr Vector& operator+=(Vector& vec, const Vector& other) noexcept { for(size_t i = 0; i < Size; i++) { vec[i] += other[i]; } return vec; } template constexpr Vector& operator-=(Vector& vec, const Vector& other) noexcept { for(size_t i = 0; i < Size; i++) { vec[i] -= other[i]; } return vec; } template constexpr Vector& operator*=(Vector& vec, const T2& factor) noexcept { for(size_t i = 0; i < Size; i++) { vec[i] *= factor; } return vec; } template constexpr Vector& operator/=(Vector& vec, const T2& factor) noexcept { for(size_t i = 0; i < Size; i++) { vec[i] /= factor; } return vec; } template constexpr auto operator%(const Vector& first, const Vector& second) noexcept -> decltype(first[0] * second[1] - first[1] * second[0]) { return first[0] * second[1] - first[1] * second[0]; } template constexpr auto cross(const Vector& first, const Vector& second) noexcept -> decltype(first[0] * second[1] - first[1] * second[0]) { return first % second; } template constexpr auto operator%(const Vector& first, const Vector& second) noexcept -> Vector { return Vector{ first[1] * second[2] - first[2] * second[1], first[2] * second[0] - first[0] * second[2], first[0] * second[1] - first[1] * second[0] }; } template constexpr auto cross(const Vector& first, const Vector& second) noexcept -> Vector { return first % second; } template constexpr bool operator==(const Vector& first, const Vector& second) noexcept { for(size_t i = 0; i < Size; i++) if(first[i] != second[i]) return false; return true; } template constexpr bool operator!=(const Vector& first, const Vector& second) noexcept { return !(first == second); } template constexpr Vector join(const Vector& first, const Vector& second) noexcept { Vector result; for(size_t i = 0; i < Size1; i++) { result[i] = first[i]; } for(size_t i = 0; i < Size2; i++) { result[i + Size1] = second[i]; } return result; } template constexpr Vector join(const Vector& vec, const T& extraValue) noexcept { Vector result; for(size_t i = 0; i < Size; i++) { result[i] = vec[i]; } result[Size] = extraValue; return result; } template constexpr Vector join(const T& extraValue, const Vector& vec) noexcept { Vector result; result[0] = extraValue; for(size_t i = 0; i < Size; i++) { result[i + 1] = vec[i]; } return result; } template constexpr Vector withoutIndex(const Vector& vec, size_t indexToRemove) { assert(indexToRemove < Size); Vector result; for(size_t i = 0; i < indexToRemove; i++) { result[i] = vec[i]; } for(size_t i = indexToRemove + 1; i < Size; i++) { result[i - 1] = vec[i]; } return result; } template constexpr T lengthSquared(const Vector& vec) noexcept { T sum = vec[0] * vec[0]; for(size_t i = 1; i < Size; i++) { sum += vec[i] * vec[i]; } return sum; } template constexpr T length(const Vector& vec) noexcept { return std::sqrt(lengthSquared(vec)); } template constexpr T length(const Vector& vec) noexcept { return std::hypot(vec[0], vec[1]); } template constexpr bool isLongerThan(const Vector& vec, const T2& length) noexcept { return lengthSquared(vec) > length * length; } template constexpr bool isShorterThan(const Vector& vec, const T2& length) noexcept { return lengthSquared(vec) < length * length; } // vec template constexpr Vector withLength(const Vector& vec, const T& newLength) noexcept { return vec * (newLength / length(vec)); } template constexpr Vector maxLength(const Vector& vec, const T& maxLength) noexcept { if(isLongerThan(vec, maxLength)) return withLength(vec, maxLength); else return vec; } template constexpr Vector minLength(const Vector& vec, const T& minLength) noexcept { if(isShorterThan(vec, minLength)) return withLength(vec, minLength); else return vec; } template constexpr Vector normalize(const Vector& vec) noexcept { return vec / length(vec); } template constexpr Vector abs(const Vector& vec) noexcept { Vector result; for(size_t i = 0; i < Size; i++) result[i] = std::abs(vec[i]); return result; } /** * used to project the result of a dotproduct back onto the original vector * @param v the result of dot(onto, otherVec) * @return vec * (v/lengthSquared(vec)) */ template constexpr Vector reProject(const Vector& onto, const T& v) noexcept { return onto * v / lengthSquared(onto); } /** * projects vec onto onto * @param vec vector to be projected * @param onto vector to be projected on * @return a projected version of the given vector */ template constexpr Vector project(const Vector& vec, const Vector& onto) noexcept { return onto * ((onto * vec) / lengthSquared(onto)); } /** * projects vec onto a plane with normal vector planeNormal * @param vec vector to be projected * @param planeNormal plane to be projected on * @return a projected version of the given vector */ template constexpr Vector projectToPlaneNormal(const Vector& vec, const Vector& planeNormal) noexcept { return vec - vec * planeNormal * planeNormal / lengthSquared(planeNormal); } /** * returns the distance of the given point to the line that goes through the origin along this vector * @param point * @return the distance */ template constexpr T pointToLineDistance(const Vector& line, const Vector& point) noexcept { return length(point - project(point, line)); } /** * returns the squared distance of the given point to the line that goes through the origin along this vector * @param point * @return the square of the distance */ template constexpr T pointToLineDistanceSquared(const Vector& line, const Vector& point) noexcept { return lengthSquared(point - project(point, line)); } template constexpr Vector elementWiseMul(const Vector& first, const Vector& second) noexcept { Vector result; for(size_t i = 0; i < Size; i++) result[i] = first[i] * second[i]; return result; } template constexpr Vector elementWiseSquare(const Vector& vec) noexcept { Vector result; for(size_t i = 0; i < Size; i++) result[i] = vec[i] * vec[i]; return result; } template constexpr Vector elementWiseCube(const Vector& vec) noexcept { Vector result; for(size_t i = 0; i < Size; i++) result[i] = vec[i] * vec[i] * vec[i]; return result; } /* computes ( a.y * b.z + a.z * b.y, a.z * b.x + a.x * b.z, a.x * b.y + a.y * b.x )*/ template constexpr Vector mulOppositesBiDir(const Vector& a, const Vector& b) noexcept { return Vector( a[1] * b[2] + a[2] * b[1], a[2] * b[0] + a[0] * b[2], a[0] * b[1] + a[1] * b[0] ); } // computes (vec.y * vec.z, vec.z * vec.x, vec.x * vec.y) template constexpr Vector mulSelfOpposites(const Vector& vec) noexcept { return Vector(vec[1] * vec[2], vec[2] * vec[0], vec[0] * vec[1]); } // computes (vec.y + vec.z, vec.z + vec.x, vec.x + vec.y) template constexpr Vector addSelfOpposites(const Vector& vec) noexcept { return Vector(vec[1] + vec[2], vec[2] + vec[0], vec[0] + vec[1]); } template constexpr size_t getMaxElementIndex(const Vector& vec) noexcept { size_t max = 0; for(size_t i = 1; i < Size; i++) { if(vec[i] > vec[max]) { max = i; } } return max; } template constexpr size_t getMinElementIndex(const Vector& vec) noexcept { size_t min = 0; for(size_t i = 1; i < Size; i++) { if(vec[i] > vec[min]) { min = i; } } return min; } template constexpr size_t getAbsMaxElementIndex(const Vector& vec) noexcept { size_t max = 0; for(size_t i = 1; i < Size; i++) { if(std::abs(vec[i]) > std::abs(vec[max])) { max = i; } } return max; } template constexpr size_t getAbsMinElementIndex(const Vector& vec) noexcept { size_t min = 0; for(size_t i = 1; i < Size; i++) { if(std::abs(vec[i]) < std::abs(vec[min])) { min = i; } } return min; } template constexpr T sumElements(const Vector& vec) noexcept { T sum = vec[0]; for(size_t i = 1; i < Size; i++) { sum += vec[i]; } return sum; } template constexpr auto angleBetween(const Vector& first, const Vector& second) noexcept -> decltype(acos(normalize(first)* normalize(second))) { return acos(normalize(first) * normalize(second)); } template constexpr Vector bisect(const Vector& first, const Vector& second) noexcept { return first * length(second) + second * length(first); } }; ================================================ FILE: Physics3D/math/mathUtil.h ================================================ #pragma once #include namespace P3D { /* Returns a random number between fMin and fMax */ inline double fRand(double fMin, double fMax) { double f = (double) rand() / RAND_MAX; return fMin + f * (fMax - fMin); } /* Returns the amount of leading zeros */ inline uint32_t ctz(uint32_t n) { #ifdef _MSC_VER unsigned long ret = 0; _BitScanForward(&ret, static_cast(n)); return static_cast(ret); #else return static_cast(__builtin_ctz(n)); #endif } /* Returns whether the given whole number is a power of 2 */ template constexpr bool powOf2(T n) { return n && !(n & n - 1); } template constexpr T bmask(char x) { return static_cast(1) << x; } template constexpr void bmset(T& n, M mask) { n |= mask; } template constexpr void bset(T& n, char x) { bmset(n, bmask(x)); } template constexpr void bmclear(T& n, M mask) { n &= ~mask; } template constexpr void bclear(T& n, char x) { bmclear(n, bmask(x)); } template constexpr void bmflip(T& n, M mask) { n ^= mask; } template constexpr void bflip(T& n, char x) { bmflip(n, bmask(x)); } template constexpr bool bmtest(T n, M mask) { return n & mask; } template constexpr bool btest(T n, char x) { return bmtest(n, bmask(x)); } }; ================================================ FILE: Physics3D/math/position.h ================================================ #pragma once #include "fix.h" #include "linalg/vec.h" namespace P3D { typedef Vector, 3> Vec3Fix; template struct PositionTemplate { T x; T y; T z; constexpr PositionTemplate() noexcept : x(), y(), z() {} constexpr PositionTemplate(const PositionTemplate&) = default; constexpr PositionTemplate& operator=(const PositionTemplate&) = default; template constexpr PositionTemplate(const PositionTemplate& other) : x(static_cast(other.x)), y(static_cast(other.y)), z(static_cast(other.z)) {} template constexpr PositionTemplate& operator=(const PositionTemplate& other) { this->x = static_cast(other.x); this->y = static_cast(other.y); this->z = static_cast(other.z); return *this; } constexpr PositionTemplate(T x, T y, T z) noexcept : x(x), y(y), z(z) {} constexpr PositionTemplate(double x, double y, double z) noexcept : x(T(x)), y(T(y)), z(T(z)) {} }; typedef PositionTemplate> Position; template constexpr Vector operator-(const PositionTemplate& a, const PositionTemplate& b) noexcept { return Vector(a.x - b.x, a.y - b.y, a.z - b.z); } template constexpr PositionTemplate operator+(const PositionTemplate& a, const Vector& b) noexcept { return PositionTemplate(a.x + b.x, a.y + b.y, a.z + b.z); } template constexpr PositionTemplate operator-(const PositionTemplate& a, const Vector& b) noexcept { return PositionTemplate(a.x - b.x, a.y - b.y, a.z - b.z); } template constexpr void operator+=(PositionTemplate& pos, const Vector& offset) noexcept { //inline void operator+=(Position& pos, const Vec3Fix& offset) noexcept { //int64_t tester = offset.x.value; pos.x += offset.x; pos.y += offset.y; pos.z += offset.z; } template constexpr void operator-=(PositionTemplate& pos, const Vector& offset) noexcept { //inline void operator-=(Position& pos, const Vec3Fix& offset) noexcept { //int64_t tester = offset.x.value; pos.x -= offset.x; pos.y -= offset.y; pos.z -= offset.z; } template constexpr bool operator==(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x == second.x && first.y == second.y && first.z == second.z; } template constexpr bool operator!=(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x != second.x || first.y != second.y || first.z != second.z; } template constexpr PositionTemplate max(const PositionTemplate& p1, const PositionTemplate& p2) noexcept { return PositionTemplate(p1.x > p2.x ? p1.x : p2.x, p1.y > p2.y ? p1.y : p2.y, p1.z > p2.z ? p1.z : p2.z); } template constexpr PositionTemplate min(const PositionTemplate& p1, const PositionTemplate& p2) noexcept { return PositionTemplate(p1.x < p2.x ? p1.x : p2.x, p1.y < p2.y ? p1.y : p2.y, p1.z < p2.z ? p1.z : p2.z); } template constexpr PositionTemplate avg(const PositionTemplate& first, const PositionTemplate& second) noexcept { return PositionTemplate((first.x + second.x) / 2, (first.y + second.y) / 2, (first.z + second.z) / 2); } // unconventional operator, compares xyz individually, returns true if all are true template constexpr bool operator>=(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x >= second.x && first.y >= second.y && first.z >= second.z; } // unconventional operator, compares xyz individually, returns true if all are true template constexpr bool operator<=(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x <= second.x && first.y <= second.y && first.z <= second.z; } // unconventional operator, compares xyz individually, returns true if all are true template constexpr bool operator>(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x > second.x && first.y > second.y && first.z > second.z; } // unconventional operator, compares xyz individually, returns true if all are true template constexpr bool operator<(const PositionTemplate& first, const PositionTemplate& second) noexcept { return first.x < second.x && first.y < second.y && first.z < second.z; } // cast Vec3 to Position, not recommended due to potential loss of precision constexpr Position castVec3ToPosition(const Vec3& vec) noexcept { return Position(vec.x, vec.y, vec.z); } // cast Vec3f to Position, not recommended due to potential loss of precision constexpr Position castVec3fToPosition(const Vec3f& vec) noexcept { return Position(vec.x, vec.y, vec.z); } // cast Position to Vec3, not recommended due to potential loss of precision // If possible, compute relative positions, eg: pos1-pos2 = vec3 delta constexpr Vec3 castPositionToVec3(const Position& pos) noexcept { return Vec3(static_cast(pos.x), static_cast(pos.y), static_cast(pos.z)); } // cast Position to Vec3f, not recommended due to potential loss of precision // If possible, compute relative positions, eg: pos1-pos2 = vec3f delta constexpr Vec3f castPositionToVec3f(const Position& pos) noexcept { return Vec3f(static_cast(pos.x), static_cast(pos.y), static_cast(pos.z)); } }; ================================================ FILE: Physics3D/math/predefinedTaylorExpansions.h ================================================ #pragma once #include #include #include "taylorExpansion.h" #include "linalg/trigonometry.h" namespace P3D { template TaylorExpansion generateTaylorForSinWave(T currentAngle, T frequencyMultiplier) { TaylorExpansion result; T cosValue = std::cos(currentAngle * frequencyMultiplier); result[0] = cosValue * frequencyMultiplier; if constexpr(N > 1) { T totalMultiplier = frequencyMultiplier; T sinValue = std::sin(currentAngle * frequencyMultiplier); T values[4]{cosValue, -sinValue, -cosValue, sinValue}; for(std::size_t i = 1; i < N; i++) { totalMultiplier *= frequencyMultiplier; result[i] = values[i % 4] * totalMultiplier; } } return result; } template TaylorExpansion generateTaylorForCosWave(T currentAngle, T frequencyMultiplier) { TaylorExpansion result; T sinValue = std::sin(currentAngle * frequencyMultiplier); result[0] = -sinValue * frequencyMultiplier; if constexpr(N > 1) { T totalMultiplier = frequencyMultiplier; T cosValue = std::cos(currentAngle * frequencyMultiplier); T values[4]{-sinValue, -cosValue, sinValue, cosValue}; for(std::size_t i = 1; i < N; i++) { totalMultiplier *= frequencyMultiplier; result[i] = values[i % 4] * totalMultiplier; } } return result; } template FullTaylorExpansion generateFullTaylorForSinWave(T currentAngle, T frequencyMultiplier) { T sinValue = std::sin(currentAngle * frequencyMultiplier); T cosValue = std::cos(currentAngle * frequencyMultiplier); FullTaylorExpansion result{sinValue}; T values[4]{cosValue, -sinValue, -cosValue, sinValue}; T totalMultiplier = frequencyMultiplier; for(std::size_t i = 0; i < N - 1; i++) { result.setDerivative(i, values[i % 4] * totalMultiplier); totalMultiplier *= frequencyMultiplier; } return result; } template FullTaylorExpansion generateFullTaylorForCosWave(T currentAngle, T frequencyMultiplier) { T sinValue = std::sin(currentAngle * frequencyMultiplier); T cosValue = std::cos(currentAngle * frequencyMultiplier); FullTaylorExpansion result{cosValue}; T values[4]{-sinValue, -cosValue, sinValue, cosValue}; T totalMultiplier = frequencyMultiplier; for(std::size_t i = 0; i < N - 1; i++) { result.setDerivative(i, values[i % 4] * totalMultiplier); totalMultiplier *= frequencyMultiplier; } return result; } template TaylorExpansion, Derivs - 1> generateTaylorForSkewSymmetricSquared(const FullTaylorExpansion, Derivs>& inputVector) { static_assert(Derivs >= 2 && Derivs <= 3); Vector f = inputVector.getConstantValue(); TaylorExpansion, Derivs - 1> result; if constexpr(Derivs >= 2) { Vector ff = inputVector.getDerivative(0); Vector ffxf = elementWiseMul(ff, f); Vector ffMixed = mulOppositesBiDir(ff, f); result[0] = SymmetricMatrix{ -2 * (ffxf.y + ffxf.z), ffMixed.z, -2 * (ffxf.x + ffxf.z), ffMixed.y, ffMixed.x, -2 * (ffxf.x + ffxf.y) }; if constexpr(Derivs >= 3) { Vector fff = inputVector.getDerivative(1); Vector fffxf = elementWiseSquare(ff) + elementWiseMul(fff, f); Vector fffMixed = mulOppositesBiDir(f, fff) + mulSelfOpposites(ff) * T(2); result[1] = SymmetricMatrix{ -2 * (fffxf.y + fffxf.z), fffMixed.z, -2 * (fffxf.x + fffxf.z), fffMixed.y, fffMixed.x, -2 * (fffxf.x + fffxf.y) }; } } return result; } template FullTaylorExpansion, Derivs> generateFullTaylorForSkewSymmetricSquared(const FullTaylorExpansion, Derivs>& inputVector) { return FullTaylorExpansion, Derivs>::fromConstantAndDerivatives(skewSymmetricSquared(inputVector.getConstantValue()), generateTaylorForSkewSymmetricSquared(inputVector)); } template TaylorExpansion generateTaylorForRotationMatrix(const TaylorExpansion& rotationVecTaylor, const Matrix& rotation) { Mat3 angularVelEquiv = createCrossProductEquivalent(rotationVecTaylor[0]); TaylorExpansion result; result[0] = angularVelEquiv * rotation; if constexpr(Derivs >= 2) { Mat3 angularAccelEquiv = createCrossProductEquivalent(rotationVecTaylor[1]); result[1] = (angularAccelEquiv + angularVelEquiv * angularVelEquiv) * rotation; } // TODO add further derivatives return result; } }; ================================================ FILE: Physics3D/math/ray.h ================================================ #pragma once #include "linalg/vec.h" #include "position.h" #include "bounds.h" namespace P3D { struct Ray { constexpr static double EPSILON = 0.0000001; Position origin; Vec3 direction; Position intersect(double t) { return origin + direction * t; } double hitPlane(const Position& point, const Vec3& normal) const { double denominator = direction * normal; if (std::abs(denominator) < EPSILON) return std::numeric_limits::infinity(); double t = (point - origin) * normal / denominator; return t; } double hitDisk(const Position& center, const Vec3& normal, double radius) { double t = hitPlane(center, normal); Position intersection = intersect(t); if (lengthSquared(castPositionToVec3(intersection)) < radius * radius) return std::numeric_limits::infinity(); return t; } double hitSphere(const Position& center, double radius) { Vec3 temp = origin - center; double a = direction * direction; double b = 2.0 * temp * direction; double c = temp * temp - radius * radius; double discriminant = b * b - 4.0 * a * c; if (discriminant < 0.0) return std::numeric_limits::infinity(); double e = std::sqrt(discriminant); double t = (-b - e) / 2.0 / a; if (t > EPSILON) return t; t = (-b + e) / 2.0 / a; if (t > EPSILON) return t; return std::numeric_limits::infinity(); } double hitTriangle(const Vec3& v0, const Vec3& v1, const Vec3& v2) const { double a = v0.x - v1.x; double b = v0.x - v2.x; double c = direction.x; double d = v0.x - origin.x; double e = v0.y - v1.y; double f = v0.y - v2.y; double g = direction.x; double h = v0.y - origin.y; double i = v0.z - v1.z; double j = v0.z - v2.z; double k = direction.z; double l = v0.z - origin.z; double m = f * k - g * j; double n = k * k - g * l; double p = f * l - h * j; double q = g * i - e * k; double s = e * j - f * i; double inverseDenominator = 1.0 * (a * m + b * q + c * s); double e1 = d * m - b * n - c * p; double beta = e1 * inverseDenominator; if (beta < 0.0) return std::numeric_limits::infinity(); double r = e * l - h * i; double e2 = a * n + d * q + c * r; double gamma = e2 * inverseDenominator; if (gamma < 0.0) return std::numeric_limits::infinity(); if (beta + gamma > 1.0) return std::numeric_limits::infinity(); double e3 = a * p - b * r + d * s; double t = e3 * inverseDenominator; if (t < EPSILON) return std::numeric_limits::infinity(); return t; } }; /* Returns true if the given ray intersects the given bounds. */ inline bool doRayAndBoundsIntersect(const Bounds& bounds, const Ray& ray) { // everything is computed relative to the ray origin Vec3Fix lMin = bounds.min - ray.origin; Vec3Fix lMax = bounds.max - ray.origin; Vec3 dir = ray.direction; { Vec3Fix intersectingSide = (dir.x > 0) ? lMin : lMax; double intersectionDistance = static_cast(intersectingSide.x) / dir.x; Vec3Fix intersect = dir * intersectionDistance; if (intersect.y >= lMin.y && intersect.z >= lMin.z && intersect.y <= lMax.y && intersect.z <= lMax.z) { return true; } } { Vec3Fix intersectingSide = (dir.y > 0) ? lMin : lMax; double intersectionDistance = static_cast(intersectingSide.y) / dir.y; Vec3Fix intersect = dir * intersectionDistance; if (intersect.x >= lMin.x && intersect.z >= lMin.z && intersect.x <= lMax.x && intersect.z <= lMax.z) { return true; } } { Vec3Fix intersectingSide = (dir.z > 0) ? lMin : lMax; double intersectionDistance = static_cast(intersectingSide.z) / dir.z; Vec3Fix intersect = dir * intersectionDistance; if (intersect.x >= lMin.x && intersect.y >= lMin.y && intersect.x <= lMax.x && intersect.y <= lMax.y) { return true; } } return false; } }; ================================================ FILE: Physics3D/math/rotation.h ================================================ #pragma once #include "linalg/mat.h" #include "linalg/trigonometry.h" #include "linalg/quat.h" #include "constants.h" #include #include namespace P3D { template class MatrixRotationTemplate { Matrix rotationMatrix; constexpr explicit MatrixRotationTemplate(const Matrix& rotationMatrix) : rotationMatrix(rotationMatrix) {} public: constexpr MatrixRotationTemplate(); Vector localToGlobal(const Vector& vec) const; Vector globalToLocal(const Vector& vec) const; MatrixRotationTemplate localToGlobal(const MatrixRotationTemplate& rot) const; MatrixRotationTemplate globalToLocal(const MatrixRotationTemplate& rot) const; /* Transforms the given symmetric matrix into the basis of this rotation effectively brings a local matrix into the global space defined as rot * sm * ~rot rot.localToGlobal(sm * v) == rot.localToGlobal(sm) * rot.localToGlobal(v) == (rot * sm * ~rot) * rot * v == (rot * sm) * v == rot * (sm * v) */ SymmetricMatrix localToGlobal(const SymmetricMatrix& sm) const; /* Transforms the given symmetric matrix from the basis of this rotation effectively brings a global matrix into the local space defined as ~rot * sm * rot rot.globalToLocal(sm * v) == rot.globalToLocal(sm) * rot.globalToLocal(v) == (~rot * sm * rot) * ~rot * v == (~rot * sm) * v == ~rot * (sm * v) */ SymmetricMatrix globalToLocal(const SymmetricMatrix& sm) const; MatrixRotationTemplate operator~() const; MatrixRotationTemplate operator*(const MatrixRotationTemplate& rot2) const; MatrixRotationTemplate& operator*=(const MatrixRotationTemplate& rot2); Vector operator*(const Vector& vec) const; /* returns the projection of the x-axis == this->localToGlobal(Vec3(1,0,0)) */ Vector getX() const; /* returns the projection of the y-axis == this->localToGlobal(Vec3(0,1,0)) */ Vector getY() const; /* returns the projection of the z-axis == this->localToGlobal(Vec3(0,0,1)) */ Vector getZ() const; static MatrixRotationTemplate rotX(T angle); static MatrixRotationTemplate rotY(T angle); static MatrixRotationTemplate rotZ(T angle); static MatrixRotationTemplate fromEulerAngles(T alpha, T beta, T gamma) { return MatrixRotationTemplate::rotZ(gamma) * MatrixRotationTemplate::rotX(alpha) * MatrixRotationTemplate::rotY(beta); } static MatrixRotationTemplate fromDirection(const Vector& direction, const Vector& up = { 0, 1, 0 }); static MatrixRotationTemplate fromRotationVector(const Vector& rotationVector); static MatrixRotationTemplate fromRotationMatrix(const Matrix& rotationMatrix); static MatrixRotationTemplate fromRotationQuaternion(const Quaternion& rotationQuaternion); static MatrixRotationTemplate faceX(Vector xDirection); static MatrixRotationTemplate faceX(Vector xDirection, Vector yHint); static MatrixRotationTemplate faceY(Vector yDirection); static MatrixRotationTemplate faceY(Vector yDirection, Vector zHint); static MatrixRotationTemplate faceZ(Vector zDirection); static MatrixRotationTemplate faceZ(Vector zDirection, Vector xHint); Matrix asRotationMatrix() const; Quaternion asRotationQuaternion() const; Vector asRotationVector() const; /*operator Matrix() const { return this->asRotationMatrix(); }*/ template operator MatrixRotationTemplate() const; struct Predefined{ static constexpr MatrixRotationTemplate IDENTITY{Matrix{1,0,0,0,1,0,0,0,1}}; static constexpr MatrixRotationTemplate X_90 {Matrix{ 1, 0, 0, 0, 0,-1, 0, 1, 0}}; static constexpr MatrixRotationTemplate Y_90 {Matrix{ 0, 0, 1, 0, 1, 0,-1, 0, 0}}; static constexpr MatrixRotationTemplate Z_90 {Matrix{ 0,-1, 0, 1, 0, 0, 0, 0, 1}}; static constexpr MatrixRotationTemplate X_180{Matrix{ 1, 0, 0, 0,-1, 0, 0, 0,-1}}; static constexpr MatrixRotationTemplate Y_180{Matrix{-1, 0, 0, 0, 1, 0, 0, 0,-1}}; static constexpr MatrixRotationTemplate Z_180{Matrix{-1, 0, 0, 0,-1, 0, 0, 0, 1}}; static constexpr MatrixRotationTemplate X_270{Matrix{ 1, 0, 0, 0, 0, 1, 0,-1, 0}}; static constexpr MatrixRotationTemplate Y_270{Matrix{ 0, 0,-1, 0, 1, 0, 1, 0, 0}}; static constexpr MatrixRotationTemplate Z_270{Matrix{ 0, 1, 0,-1, 0, 0, 0, 0, 1}}; }; }; template class QuaternionRotationTemplate { Quaternion rotationQuaternion; constexpr explicit QuaternionRotationTemplate(const Quaternion& rotationQuaternion) : rotationQuaternion(rotationQuaternion) {}; public: constexpr QuaternionRotationTemplate(); Vector localToGlobal(const Vector& vec) const; Vector globalToLocal(const Vector& vec) const; QuaternionRotationTemplate localToGlobal(const QuaternionRotationTemplate& rot) const; QuaternionRotationTemplate globalToLocal(const QuaternionRotationTemplate& rot) const; /* Transforms the given symmetric matrix into the basis of this rotation effectively brings a local matrix into the global space defined as q * sm * ~q q.localToGlobal(sm * v) == q.localToGlobal(sm) * q.localToGlobal(v) == (q * sm * ~q) * q * v * ~q == (q * sm) * v * ~q == q * (sm * v) * ~q */ SymmetricMatrix localToGlobal(const SymmetricMatrix& sm) const; /* Transforms the given symmetric matrix from the basis of this rotation effectively brings a global matrix into the local space defined as ~q * sm * q q.globalToLocal(sm * v) == q.globalToLocal(sm) * q.globalToLocal(v) == (~q * sm * q) * ~q * v * q == (~q * sm) * v * q == ~q * (sm * v) * q */ SymmetricMatrix globalToLocal(const SymmetricMatrix& sm) const; QuaternionRotationTemplate operator~() const; QuaternionRotationTemplate operator*(const QuaternionRotationTemplate& rot2) const; QuaternionRotationTemplate& operator*=(const QuaternionRotationTemplate& rot2); Vector operator*(const Vector& vec) const; /* returns the projection of the x-axis == this->localToGlobal(Vec3(1,0,0)) */ Vector getX() const; /* returns the projection of the y-axis == this->localToGlobal(Vec3(0,1,0)) */ Vector getY() const; /* returns the projection of the z-axis == this->localToGlobal(Vec3(0,0,1)) */ Vector getZ() const; static QuaternionRotationTemplate rotX(T angle); static QuaternionRotationTemplate rotY(T angle); static QuaternionRotationTemplate rotZ(T angle); static QuaternionRotationTemplate fromEulerAngles(T alpha, T beta, T gamma) { return QuaternionRotationTemplate::rotZ(gamma) * QuaternionRotationTemplate::rotX(alpha) * QuaternionRotationTemplate::rotY(beta); } static QuaternionRotationTemplate fromRotationVec(const Vector& rotationVector); static QuaternionRotationTemplate fromRotationMatrix(const Matrix& rotationMatrix); static QuaternionRotationTemplate fromRotationQuaternion(const Quaternion& rotationQuaternion); static QuaternionRotationTemplate faceX(Vector xDirection); static QuaternionRotationTemplate faceX(Vector xDirection, Vector yHint); static QuaternionRotationTemplate faceY(Vector yDirection); static QuaternionRotationTemplate faceY(Vector yDirection, Vector zHint); static QuaternionRotationTemplate faceZ(Vector zDirection); static QuaternionRotationTemplate faceZ(Vector zDirection, Vector xHint); Matrix asRotationMatrix() const; Quaternion asRotationQuaternion() const; Vector asRotationVector() const; /*operator Matrix() const { return this->asRotationMatrix(); }*/ template operator QuaternionRotationTemplate() const; struct Predefined{ static constexpr QuaternionRotationTemplate IDENTITY{Quaternion(1,0,0,0)}; static constexpr QuaternionRotationTemplate X_90 {Quaternion(sq2_2(), sq2_2(), 0, 0)}; static constexpr QuaternionRotationTemplate Y_90 {Quaternion(sq2_2(), 0, sq2_2(), 0)}; static constexpr QuaternionRotationTemplate Z_90 {Quaternion(sq2_2(), 0, 0, sq2_2())}; static constexpr QuaternionRotationTemplate X_180{Quaternion(0, 1, 0, 0)}; static constexpr QuaternionRotationTemplate Y_180{Quaternion(0, 0, 1, 0)}; static constexpr QuaternionRotationTemplate Z_180{Quaternion(0, 0, 0, 1)}; static constexpr QuaternionRotationTemplate X_270{Quaternion(sq2_2(), -sq2_2(), 0, 0)}; static constexpr QuaternionRotationTemplate Y_270{Quaternion(sq2_2(), 0, -sq2_2(), 0)}; static constexpr QuaternionRotationTemplate Z_270{Quaternion(sq2_2(), 0, 0, -sq2_2())}; }; }; template constexpr MatrixRotationTemplate::MatrixRotationTemplate() : rotationMatrix(Matrix::IDENTITY()) {} template Vector MatrixRotationTemplate::localToGlobal(const Vector& vec) const { return rotationMatrix * vec; } template Vector MatrixRotationTemplate::globalToLocal(const Vector& vec) const { return rotationMatrix.transpose() * vec; } template MatrixRotationTemplate MatrixRotationTemplate::localToGlobal(const MatrixRotationTemplate& rot) const { return MatrixRotationTemplate(this->rotationMatrix * rot.rotationMatrix); } template MatrixRotationTemplate MatrixRotationTemplate::globalToLocal(const MatrixRotationTemplate& rot) const { return MatrixRotationTemplate(this->rotationMatrix.transpose() * rot.rotationMatrix); } template SymmetricMatrix MatrixRotationTemplate::localToGlobal(const SymmetricMatrix& sm) const { return mulSymmetricLeftRightTranspose(sm, this->rotationMatrix); } template SymmetricMatrix MatrixRotationTemplate::globalToLocal(const SymmetricMatrix& sm) const { return mulSymmetricLeftTransposeRight(sm, this->rotationMatrix); } template MatrixRotationTemplate MatrixRotationTemplate::operator~() const { return MatrixRotationTemplate(rotationMatrix.transpose()); } template MatrixRotationTemplate MatrixRotationTemplate::operator*(const MatrixRotationTemplate& rot2) const { return this->localToGlobal(rot2); } template Vector MatrixRotationTemplate::operator*(const Vector& vec) const { return this->localToGlobal(vec); } template Vector MatrixRotationTemplate::getX() const { return this->rotationMatrix.getCol(0); } template Vector MatrixRotationTemplate::getY() const { return this->rotationMatrix.getCol(1); } template Vector MatrixRotationTemplate::getZ() const { return this->rotationMatrix.getCol(2); } template MatrixRotationTemplate MatrixRotationTemplate::rotX(T angle) { return MatrixRotationTemplate(rotMatX(angle)); } template MatrixRotationTemplate MatrixRotationTemplate::rotY(T angle) { return MatrixRotationTemplate(rotMatY(angle)); } template MatrixRotationTemplate MatrixRotationTemplate::rotZ(T angle) { return MatrixRotationTemplate(rotMatZ(angle)); } template Matrix MatrixRotationTemplate::asRotationMatrix() const { return this->rotationMatrix; } template Quaternion MatrixRotationTemplate::asRotationQuaternion() const { return rotationQuaternionFromRotationMatrix(this->rotationMatrix); } template Vector MatrixRotationTemplate::asRotationVector() const { return rotationVectorFromRotationMatrix(this->rotationMatrix); } template MatrixRotationTemplate MatrixRotationTemplate::fromDirection(const Vector& direction, const Vector& up) { Vector z = normalize(direction); Vector x = normalize(z % up); Vector y = normalize(x % z); return fromRotationMatrix(Matrix { x.x, y.x, -z.x, x.y, y.y, -z.y, x.z, y.z, -z.z }); } template MatrixRotationTemplate MatrixRotationTemplate::fromRotationMatrix(const Matrix& rotationMatrix) { assert(isValidRotationMatrix(rotationMatrix)); return MatrixRotationTemplate(rotationMatrix); } template MatrixRotationTemplate MatrixRotationTemplate::fromRotationQuaternion(const Quaternion& rotationQuaternion) { assert(isValidRotationQuaternion(rotationQuaternion)); return MatrixRotationTemplate(rotationMatrixFromQuaternion(rotationQuaternion)); } template MatrixRotationTemplate MatrixRotationTemplate::fromRotationVector(const Vector& rotationVector) { return MatrixRotationTemplate(rotationMatrixFromRotationVec(rotationVector)); } template MatrixRotationTemplate MatrixRotationTemplate::faceX(Vector xDirection) { return MatrixRotationTemplate::fromRotationMatrix(faceMatX(xDirection)); } template MatrixRotationTemplate MatrixRotationTemplate::faceX(Vector xDirection, Vector yHint) { return MatrixRotationTemplate::fromRotationMatrix(faceMatX(xDirection, yHint)); } template MatrixRotationTemplate MatrixRotationTemplate::faceY(Vector yDirection) { return MatrixRotationTemplate::fromRotationMatrix(faceMatY(yDirection)); } template MatrixRotationTemplate MatrixRotationTemplate::faceY(Vector yDirection, Vector zHint) { return MatrixRotationTemplate::fromRotationMatrix(faceMatY(yDirection, zHint)); } template MatrixRotationTemplate MatrixRotationTemplate::faceZ(Vector zDirection) { return MatrixRotationTemplate::fromRotationMatrix(faceMatZ(zDirection)); } template MatrixRotationTemplate MatrixRotationTemplate::faceZ(Vector zDirection, Vector xHint) { return MatrixRotationTemplate::fromRotationMatrix(faceMatZ(zDirection, xHint)); } template template MatrixRotationTemplate::operator MatrixRotationTemplate() const { return MatrixRotationTemplate::fromRotationMatrix(static_cast>(this->rotationMatrix)); } template constexpr QuaternionRotationTemplate::QuaternionRotationTemplate() : rotationQuaternion(1, 0, 0, 0) {} template Vector QuaternionRotationTemplate::localToGlobal(const Vector& v) const { return mulQuaternionLeftRightConj(this->rotationQuaternion, v); } template Vector QuaternionRotationTemplate::globalToLocal(const Vector& v) const { return mulQuaternionLeftConjRight(this->rotationQuaternion, v); } template QuaternionRotationTemplate QuaternionRotationTemplate::localToGlobal(const QuaternionRotationTemplate& rot) const { return QuaternionRotationTemplate(this->rotationQuaternion * rot.rotationQuaternion); } template QuaternionRotationTemplate QuaternionRotationTemplate::globalToLocal(const QuaternionRotationTemplate& rot) const { return QuaternionRotationTemplate(conj(this->rotationQuaternion) * rot.rotationQuaternion); } template SymmetricMatrix QuaternionRotationTemplate::localToGlobal(const SymmetricMatrix& sm) const { SquareMatrix rotMat = this->asRotationMatrix(); return mulSymmetricLeftRightTranspose(sm, rotMat); } template SymmetricMatrix QuaternionRotationTemplate::globalToLocal(const SymmetricMatrix& sm) const { SquareMatrix rotMat = this->asRotationMatrix(); return mulSymmetricLeftTransposeRight(sm, rotMat); } template QuaternionRotationTemplate QuaternionRotationTemplate::operator~() const { return QuaternionRotationTemplate(conj(rotationQuaternion)); } template QuaternionRotationTemplate QuaternionRotationTemplate::operator*(const QuaternionRotationTemplate& rot2) const { return this->localToGlobal(rot2); } template Vector QuaternionRotationTemplate::operator*(const Vector& vec) const { return this->localToGlobal(vec); } template Vector QuaternionRotationTemplate::getX() const { T w = this->rotationQuaternion.w; Vector u = this->rotationQuaternion.v; return Vector( 1 - 2 * (u.y * u.y + u.z * u.z), 2 * (u.y * u.z + w * u.z), 2 * (u.z * u.x - w * u.y) ); } template Vector QuaternionRotationTemplate::getY() const { T w = this->rotationQuaternion.w; Vector u = this->rotationQuaternion.v; return Vector( 2 * (u.x * u.y - w * u.z), 1 - 2 * (u.x * u.x + u.z * u.z), 2 * (u.y * u.z + w * u.x) ); } template Vector QuaternionRotationTemplate::getZ() const { T w = this->rotationQuaternion.w; Vector u = this->rotationQuaternion.v; return Vector( 2 * (u.x * u.z + w * u.y), 2 * (u.y * u.z - w * u.x), 1 - 2 * (u.x * u.x + u.y * u.y) ); } template QuaternionRotationTemplate QuaternionRotationTemplate::rotX(T angle) { return QuaternionRotationTemplate(rotQuatX(angle)); } template QuaternionRotationTemplate QuaternionRotationTemplate::rotY(T angle) { return QuaternionRotationTemplate(rotQuatY(angle)); } template QuaternionRotationTemplate QuaternionRotationTemplate::rotZ(T angle) { return QuaternionRotationTemplate(rotQuatZ(angle)); } template Matrix QuaternionRotationTemplate::asRotationMatrix() const { return rotationMatrixFromQuaternion(this->rotationQuaternion); } template Quaternion QuaternionRotationTemplate::asRotationQuaternion() const { return this->rotationQuaternion; } template Vector QuaternionRotationTemplate::asRotationVector() const { return rotationVectorFromRotationQuaternion(this->rotationQuaternion); } template QuaternionRotationTemplate QuaternionRotationTemplate::fromRotationMatrix(const Matrix& rotationMatrix) { assert(isValidRotationMatrix(rotationMatrix)); return QuaternionRotationTemplate(rotationQuaternionFromRotationMatrix(rotationMatrix)); } template QuaternionRotationTemplate QuaternionRotationTemplate::fromRotationQuaternion(const Quaternion& rotationQuaternion) { assert(isValidRotationQuaternion(rotationQuaternion)); return QuaternionRotationTemplate(rotationQuaternion); } template QuaternionRotationTemplate QuaternionRotationTemplate::fromRotationVec(const Vector& rotationVector) { return QuaternionRotationTemplate(rotationQuaternionFromRotationVec(rotationVector)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceX(Vector xDirection) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatX(xDirection)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceX(Vector xDirection, Vector yHint) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatX(xDirection, yHint)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceY(Vector yDirection) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatY(yDirection)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceY(Vector yDirection, Vector zHint) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatY(yDirection, zHint)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceZ(Vector zDirection) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatZ(zDirection)); } template QuaternionRotationTemplate QuaternionRotationTemplate::faceZ(Vector zDirection, Vector xHint) { return QuaternionRotationTemplate::fromRotationMatrix(faceMatZ(zDirection, xHint)); } template template QuaternionRotationTemplate::operator QuaternionRotationTemplate() const { return QuaternionRotationTemplate::fromRotationQuaternion(static_cast>(this->rotationQuaternion)); } template using RotationTemplate = MatrixRotationTemplate; typedef RotationTemplate Rotation; typedef RotationTemplate Rotationf; }; ================================================ FILE: Physics3D/math/taylorExpansion.h ================================================ #pragma once #include #include #include namespace P3D { template struct Derivatives { T data[DerivationCount]; constexpr const T& operator[](std::size_t index) const { assert(index >= 0 && index < DerivationCount); return data[index]; } constexpr T& operator[](std::size_t index) { assert(index >= 0 && index < DerivationCount); return data[index]; } T* begin() { return data; } const T* begin() const { return data; } T* end() { return data + DerivationCount; } const T* end() const { return data + DerivationCount; } template auto transform(const Func& transformFunc) const -> Derivativesdata[0])), DerivationCount> { Derivativesdata[0])), DerivationCount> result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = transformFunc(this->data[i]); } return result; } }; template struct TaylorExpansion { Derivatives derivs; constexpr const T& operator[](std::size_t index) const { return derivs[index]; } constexpr T& operator[](std::size_t index) { return derivs[index]; } T* begin() { return derivs.begin(); } const T* begin() const { return derivs.begin(); } T* end() { return derivs.end(); } const T* end() const { return derivs.end(); } /* Evaluates the taylor expansion at x returns (derivatives[0]+(derivatives[1]+(derivatives[2]+...)*(x/3))*(x/2))*x = derivatives[0]*x + derivatives[1]*x^2/2! + derivatives[2]*x^3/3! + derivatives[3]*x^4/4! + ... */ template T operator()(const T2& x) const { T result = derivs[DerivationCount - 1]; for(std::size_t i = DerivationCount - 1; i > 0; i--) { result *= x / (i+1); result += derivs[i-1]; } result *= x; return result; } template auto transform(const Func& transformFunc) const -> TaylorExpansionderivs[0])), DerivationCount> { return TaylorExpansionderivs[0])), DerivationCount>{this->derivs.transform(transformFunc)}; } }; template struct FullTaylorExpansion { Derivatives derivs; inline const T& getConstantValue() const { return derivs[0]; } inline const T& getDerivative(std::size_t i) const { return derivs[i+1]; } inline void setConstantValue(const T& newConstValue) { derivs[0] = newConstValue; } inline void setDerivative(std::size_t i, const T& newDerivative) { derivs[i+1] = newDerivative; } inline TaylorExpansion getDerivatives() const { TaylorExpansion result; for(std::size_t i = 0; i < DerivationCount - 1; i++) { result[i] = derivs[i+1]; } return result; } static FullTaylorExpansion fromConstantAndDerivatives(const T& constantValue, const TaylorExpansion& newDerivs) { FullTaylorExpansion result; result.setConstantValue(constantValue); for(std::size_t i = 0; i < DerivationCount - 1; i++) { result.setDerivative(i, newDerivs[i]); } return result; } /* Evaluates the taylor expansion at x returns constantValue + (derivatives[0]+(derivatives[1]+(derivatives[2]+...)*(x/3))*(x/2))*x = constantValue + derivatives[0]*x + derivatives[1]*x^2/2! + derivatives[2]*x^3/3! + derivatives[3]*x^4/4! + ... */ template T operator()(const T2& x) const { std::size_t NumberOfDerivs = DerivationCount - 1; T derivsFactor = this->getDerivative(NumberOfDerivs - 1); for(std::size_t i = NumberOfDerivs - 1; i > 0; i--) { derivsFactor *= x / (i + 1); derivsFactor += this->getDerivative(i - 1); } derivsFactor *= x; return this->getConstantValue() + derivsFactor; } template auto transform(const Func& transformFunc) const -> FullTaylorExpansionderivs[0])), DerivationCount> { return FullTaylorExpansionderivs[0])), DerivationCount>{this->derivs.transform(transformFunc)}; } }; template Derivatives operator-(const Derivatives& obj) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = -obj[i]; } return result; } template Derivatives operator+(const Derivatives& first, const Derivatives& second) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = first[i] + second[i]; } return result; } template Derivatives operator-(const Derivatives& first, const Derivatives& second) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = first[i] - second[i]; } return result; } template Derivatives operator*(const T2& factor, const Derivatives& second) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = factor * second[i]; } return result; } template Derivatives operator*(const Derivatives& first, const T2& factor) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = first[i] * factor; } return result; } template Derivatives operator/(const Derivatives& first, const T2& factor) { Derivatives result; for(std::size_t i = 0; i < DerivationCount; i++) { result[i] = first[i] / factor; } return result; } template Derivatives& operator+=(Derivatives& first, const Derivatives& second) { for(std::size_t i = 0; i < DerivationCount; i++) { first[i] += second[i]; } return first; } template Derivatives& operator-=(Derivatives& first, const Derivatives& second) { for(std::size_t i = 0; i < DerivationCount; i++) { first[i] -= second[i]; } return first; } template Derivatives& operator*=(Derivatives& first, const T2& factor) { for(std::size_t i = 0; i < DerivationCount; i++) { first[i] *= factor; } return first; } template Derivatives& operator/=(Derivatives& first, const T2& factor) { for(std::size_t i = 0; i < DerivationCount; i++) { first[i] /= factor; } return first; } template TaylorExpansion operator-(const TaylorExpansion& obj) { return TaylorExpansion{-obj.derivs}; } template TaylorExpansion operator+(const TaylorExpansion& first, const TaylorExpansion& second) { return TaylorExpansion{first.derivs + second.derivs}; } template TaylorExpansion operator-(const TaylorExpansion& first, const TaylorExpansion& second) { return TaylorExpansion{first.derivs - second.derivs}; } template TaylorExpansion operator*(const TaylorExpansion& first, const T2& second) { return TaylorExpansion{first.derivs * second}; } template TaylorExpansion operator*(const T2& first, const TaylorExpansion& second) { return TaylorExpansion{first * second.derivs}; } template TaylorExpansion operator/(const TaylorExpansion& first, const T2& second) { return TaylorExpansion{first.derivs / second}; } template TaylorExpansion& operator+=(TaylorExpansion& first, const TaylorExpansion& second) { first.derivs += second.derivs; return first; } template TaylorExpansion& operator-=(TaylorExpansion& first, const TaylorExpansion& second) { first.derivs -= second.derivs; return first; } template TaylorExpansion& operator*=(TaylorExpansion& first, const T2& second) { first.derivs *= second; return first; } template TaylorExpansion& operator/=(TaylorExpansion& first, const T2& second) { first.derivs /= second; return first; } template FullTaylorExpansion operator-(const FullTaylorExpansion& obj) { return FullTaylorExpansion{-obj.derivs}; } template FullTaylorExpansion operator+(const FullTaylorExpansion& first, const FullTaylorExpansion& second) { return FullTaylorExpansion{first.derivs + second.derivs}; } template FullTaylorExpansion operator-(const FullTaylorExpansion& first, const FullTaylorExpansion& second) { return FullTaylorExpansion{first.derivs - second.derivs}; } template FullTaylorExpansion operator+(const FullTaylorExpansion& first, const T& second) { FullTaylorExpansion result = first; result.derivs[0] += second; return result; } template FullTaylorExpansion operator-(const FullTaylorExpansion& first, const T& second) { FullTaylorExpansion result = first; result.derivs[0] -= second; return result; } template FullTaylorExpansion operator*(const FullTaylorExpansion& first, const T2& second) { return FullTaylorExpansion{first.derivs * second}; } template FullTaylorExpansion operator*(const T2& first, const FullTaylorExpansion& second) { return FullTaylorExpansion{first * second.derivs}; } template FullTaylorExpansion operator/(const FullTaylorExpansion& first, const T2& second) { return FullTaylorExpansion{first.derivs / second}; } template FullTaylorExpansion& operator+=(FullTaylorExpansion& first, const FullTaylorExpansion& second) { first.derivs += second.derivs; return first; } template FullTaylorExpansion& operator-=(FullTaylorExpansion& first, const FullTaylorExpansion& second) { first.derivs -= second.derivs; return first; } template FullTaylorExpansion& operator+=(FullTaylorExpansion& first, const T& second) { first.derivs[0] += second; return first; } template FullTaylorExpansion& operator-=(FullTaylorExpansion& first, const T& second) { first.derivs[0] -= second; return first; } template FullTaylorExpansion& operator*=(FullTaylorExpansion& first, const T2& second) { first.derivs *= second; return first; } template FullTaylorExpansion& operator/=(FullTaylorExpansion& first, const T2& second) { first.derivs /= second; return first; } /* Pascal indices for constructing derivatives of multiplications 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 */ template struct PascalIndex { enum { value = PascalIndex::value + PascalIndex::value}; }; template struct PascalIndex<0, Index> { enum { value = 1 }; }; template struct PascalIndex { enum { value = 1 }; }; template struct PascalIndex { enum { value = 1 }; }; // get the pascal triangle index of this layer and index // should always get optimized to a constant expression constexpr int pascalIndex(int layer, int index) { if (layer == 0 || index == 0 || index == layer) { return 1; } else { return pascalIndex(layer - 1, index - 1) + pascalIndex(layer - 1, index); } } template auto computeDerivativeForIndex(const Derivatives& first, const Derivatives& second) -> decltype(first[0] * second[0]) { decltype(first[0] * second[0]) thisStepResult = first[Index] * second[Layer - Index]; if constexpr(Index == 0) { return thisStepResult; } else if constexpr(Index == Layer) { return thisStepResult + computeDerivativeForIndex(first, second); } else { return static_cast(PascalIndex::value) * thisStepResult + computeDerivativeForIndex(first, second); } } template void computeDerivatives(const Derivatives& first, const Derivatives& second, Derivatives& result) { if constexpr(CurDerivative > 0) { computeDerivatives(first, second, result); } result[CurDerivative] = computeDerivativeForIndex(first, second); } // multiplication-like derivatives template auto derivsOfMultiplication(const Derivatives& first, const Derivatives& second) -> Derivatives { Derivatives result; computeDerivatives(first, second, result); return result; } // multiplication-like derivatives template auto derivsOfMultiplication(const FullTaylorExpansion& first, const FullTaylorExpansion& second) -> FullTaylorExpansion { return FullTaylorExpansion{derivsOfMultiplication(first.derivs, second.derivs)}; } #define DEFAULT_TAYLOR_LENGTH 2 template using Taylor = TaylorExpansion; template using FullTaylor = FullTaylorExpansion; }; ================================================ FILE: Physics3D/math/transform.h ================================================ #pragma once #include "linalg/vec.h" #include "linalg/mat.h" #include "cframe.h" namespace P3D { template class TransformTemplate { public: Vector position; Matrix transform; TransformTemplate(const Vector& position, const Matrix& transform) : position(position), transform(transform) {} explicit TransformTemplate(const Vector& position) : position(position), transform(Mat3::IDENTITY()) {} explicit TransformTemplate(const T& x, const T& y, const T& z) : position(x, y, z), transform(Mat3::IDENTITY()) {} explicit TransformTemplate(const Matrix& transform) : position(0, 0, 0), transform(transform) {} TransformTemplate() : position(0, 0, 0), transform(Mat3::IDENTITY()) {} template TransformTemplate(const TransformTemplate& other) : position(Vector(other.position)), transform(Matrix(other.transform)) {} template TransformTemplate(const CFrameTemplate& other) : position(Vector(other.position)), transform(Matrix(other.rotation)) {} inline Vector localToGlobal(const Vector& lVec) const { return transform * lVec + position; } inline Vector globalToLocal(const Vector& gVec) const { return ~transform * (gVec - position); } inline Vector localToRelative(const Vector& lVec) const { return transform * lVec; } inline Vector relativeToLocal(const Vector& rVec) const { return ~transform * rVec; } inline TransformTemplate localToGlobal(TransformTemplate lFrame) const { return TransformTemplate(position + transform * lFrame.position, transform * lFrame.transform); } inline TransformTemplate globalToLocal(const TransformTemplate& gFrame) const { return TransformTemplate(~transform * (gFrame.position - position), ~transform * gFrame.transform); } inline TransformTemplate localToRelative(const TransformTemplate& lFrame) const { return TransformTemplate(transform * lFrame.position, transform * lFrame.transform); } inline TransformTemplate relativeToLocal(const TransformTemplate& rFrame) const { return TransformTemplate(~transform * rFrame.position, ~transform * rFrame.transform); } inline Matrix localToGlobal(const Matrix& localRot) const { return transform * localRot; } inline Matrix globalToLocal(const Matrix& globalRot) const { return ~transform * globalRot; } inline TransformTemplate operator~() const { return TransformTemplate(~transform * -position, ~transform); } inline Vector getPosition() const { return position; } inline Matrix getLocalTransformation() const { return transform; } inline TransformTemplate& operator+=(const Vector& delta) { position += delta; return *this; } inline TransformTemplate& operator-=(const Vector& delta) { position -= delta; return *this; } void translate(const Vector& translation) { position += translation; } void transformGlobalSide(const Matrix& transformation) { transform = transformation * transform; } void transformLocalSide(const Matrix& transformation) { transform = transform * transformation; } }; template TransformTemplate Mat4ToTransform(const Matrix& mat) { return TransformTemplate(toVector(mat.getSubMatrix<3, 1>(0, 3)), mat.getSubMatrix<3, 3>(0, 0)); assert(mat[3][0] == 0.0); assert(mat[3][1] == 0.0); assert(mat[3][2] == 0.0); assert(mat[3][3] == 1.0); }; template Matrix TransformToMat4(const TransformTemplate& transform) { return Matrix(Matrix(transform.transform), transform.position, Vector(0, 0, 0), 1.0); } typedef TransformTemplate Transform; typedef TransformTemplate Transformf; }; ================================================ FILE: Physics3D/math/utils.h ================================================ #pragma once #include "linalg/vec.h" namespace P3D { /* Computes the intersection of a line along starting at r0 and the surface with normalvec n starting at s0 With relativePos == s0-r0 Ray expression: r0+t*r Plane equation: n*(p-s0) == 0 Returns t from the ray expression To get the actual intersection point: p=r0+t*r */ template inline N lineSurfaceIntersection(Vector relativePos, Vector r, Vector n) { return (relativePos * n) / (r * n); } /* See rayTriangleIntersection */ template struct RayIntersection { T d, s, t; inline bool lineIntersectsTriangle() { return s >= 0 && t >= 0 && s + t <= 1; } inline bool rayIntersectsTriangle() { return d >= 0 && lineIntersectsTriangle(); } }; /* Ray is defined as: P0 + r*P Triangle surface is defined by: S0 + s*U + t*V To solve: P0 + r*P = S0 + s*U + t*V => s*U + t*V - r*P + R0 = 0 with R0 = P0-S0 Solving the system yields: d = -R0 * u%v / n*P s = -P * R0%u / n*P t = P * R0%v / n*P */ template inline RayIntersection rayTriangleIntersection(Vector point, Vector ray, Vector v0, Vector v1, Vector v2) { Vector u = v1 - v0; Vector v = v2 - v0; Vector n = u%v; Vector surfacePos = v0; Vector relPoint = point - v0; T d = -relPoint * n / (n*ray); // lineSurfaceIntersection(relPoint, P, n); T s = -ray * (relPoint%u) / (n*ray); T t = ray * (relPoint%v) / (n*ray); return RayIntersection{d, s, t}; } /* Computes the squared distance of a point at relativePoint to a plane going through the origin, with normal planeNormal (planeNormal * relativePoint)*(planeNormal * relativePoint) / planeNormal.lengthSquared(); */ inline double pointToPlaneDistanceSquared(Vec3 planeNormal, Vec3 relativePoint) { return (planeNormal * relativePoint)*(planeNormal * relativePoint) / lengthSquared(planeNormal); } inline float pointToPlaneDistanceSquared(Vec3f planeNormal, Vec3f relativePoint) { return (planeNormal * relativePoint)*(planeNormal * relativePoint) / lengthSquared(planeNormal); } struct CrossOverPoint { double s; double t; }; /* Get where the two lines are closest to eachother P := P0+s*U Q := Q0+t*V With W0 = P0-Q0 */ inline CrossOverPoint getNearestCrossoverOfRays(Vec3 U, Vec3 V, Vec3 W0) { double a = U*U; double b = U*V; double c = V*V; double d = U*W0; double e = V*W0; double s = (b*e - c*d) / (a*c - b*b); double t = (a*e - b*d) / (a*c - b*b); return CrossOverPoint{s, t}; } }; ================================================ FILE: Physics3D/misc/catchable_assert.h ================================================ #pragma once #include #include namespace P3D { class assertion_error : public std::exception { const char* file; int line; public: assertion_error(const char* file, int line) : std::exception(), file(file), line(line) {} }; #ifndef NDEBUG #define catchable_assert(condition) if(!(condition)) throw assertion_error(__FILE__, __LINE__) #else #define catchable_assert(condition) #endif }; ================================================ FILE: Physics3D/misc/cpuid.cpp ================================================ #include "cpuid.h" #ifdef _WIN32 #include #endif #include namespace P3D { class CPUID { uint32_t regs[4]; public: explicit CPUID(unsigned i, unsigned ecx) { #ifdef _WIN32 __cpuidex(( int*) regs, ( int) i, ( int) ecx); #else asm volatile ("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "a" (i), "c" (ecx)); #endif } const uint32_t& EAX() const { return regs[0]; } const uint32_t& EBX() const { return regs[1]; } const uint32_t& ECX() const { return regs[2]; } const uint32_t& EDX() const { return regs[3]; } }; CPUIDCheck::CPUIDCheck() : available(0) { CPUID cpuid0(0, 0); if(cpuid0.EAX() >= 1) { CPUID cpuid1(1, 0); if(cpuid1.EDX() & (1 << 23)) this->available |= MMX; if(cpuid1.EDX() & (1 << 25)) this->available |= SSE; if(cpuid1.EDX() & (1 << 26)) this->available |= SSE2; if(cpuid1.ECX() & (1 << 0)) this->available |= SSE3; if(cpuid1.ECX() & (1 << 9)) this->available |= SSSE3; if(cpuid1.ECX() & (1 << 19)) this->available |= SSE4_1; if(cpuid1.ECX() & (1 << 20)) this->available |= SSE4_2; if(cpuid1.ECX() & (1 << 28)) this->available |= AVX; if(cpuid1.ECX() & (1 << 12)) this->available |= FMA; } if(cpuid0.EAX() >= 7) { CPUID cpuid7(7, 0); if(cpuid7.EBX() & (1 << 5)) this->available |= AVX2; if(cpuid7.EBX() & (1 << 16)) this->available |= AVX512_F; } } CPUIDCheck CPUIDCheck::availableCPUHardware; }; ================================================ FILE: Physics3D/misc/cpuid.h ================================================ #pragma once namespace P3D { class CPUIDCheck { unsigned int available; CPUIDCheck(); static CPUIDCheck availableCPUHardware; // statically initialized public: static constexpr unsigned int MMX = 1 << 0; static constexpr unsigned int SSE = 1 << 1; static constexpr unsigned int SSE2 = 1 << 2; static constexpr unsigned int SSE3 = 1 << 3; static constexpr unsigned int SSSE3 = 1 << 4; static constexpr unsigned int SSE4_1 = 1 << 5; static constexpr unsigned int SSE4_2 = 1 << 6; static constexpr unsigned int AVX = 1 << 7; static constexpr unsigned int AVX2 = 1 << 8; static constexpr unsigned int FMA = 1 << 9; static constexpr unsigned int AVX512_F = 1 << 10; static constexpr int TECHNOLOGY_COUNT = 11; static constexpr const char* NAMES[TECHNOLOGY_COUNT]{"MMX", "SSE", "SSE2", "SSE3", "SSSE3", "SSE4_1", "SSE4_2", "AVX", "AVX2", "FMA", "AVX512_F"}; // usage: hasTechnology(SSE | SSE2 | AVX | FMA) // returns true if all given technologies are available inline static bool hasTechnology(unsigned int technologies) { return (availableCPUHardware.available & technologies) == technologies; } inline static void enableTechnology(unsigned int technologies) { availableCPUHardware.available |= technologies; } inline static void disableTechnology(unsigned int technologies) { availableCPUHardware.available &= ~technologies; } }; }; ================================================ FILE: Physics3D/misc/debug.cpp ================================================ #include "debug.h" #include "../geometry/shape.h" #include "../part.h" #include "serialization/serialization.h" #include "toString.h" #include #include #include namespace P3D { namespace Debug { void(*logVecAction)(Position, Vec3, VectorType) = [](Position, Vec3, VectorType) {}; void(*logPointAction)(Position, PointType) = [](Position, PointType) {}; void(*logCFrameAction)(CFrame, CFrameType) = [](CFrame, CFrameType) {}; void(*logShapeAction)(const Polyhedron&, const GlobalCFrame&) = [](const Polyhedron&, const GlobalCFrame&) {}; void(*logAction)(const char*, std::va_list) = [](const char* format, std::va_list args) { vprintf(format, args); }; void(*logWarnAction)(const char*, std::va_list) = [](const char* format, std::va_list args) { std::cout << "WARN: "; vprintf(format, args); }; void(*logErrorAction)(const char*, std::va_list) = [](const char* format, std::va_list args) { std::cout << "ERROR: "; vprintf(format, args); }; void logVector(Position origin, Vec3 vec, VectorType type) { logVecAction(origin, vec, type); }; void logPoint(Position point, PointType type) { logPointAction(point, type); } void logCFrame(CFrame frame, CFrameType type) { logCFrameAction(frame, type); }; void logShape(const Polyhedron& shape, const GlobalCFrame& location) { logShapeAction(shape, location); }; void log(const char* format, ...) { std::va_list args; va_start(args, format); logAction(format, args); va_end(args); } void logWarn(const char* format, ...) { std::va_list args; va_start(args, format); logWarnAction(format, args); va_end(args); } void logError(const char* format, ...) { std::va_list args; va_start(args, format); logErrorAction(format, args); va_end(args); } void setVectorLogAction(void(*logger)(Position origin, Vec3 vec, VectorType type)) { logVecAction = logger; }; void setPointLogAction(void(*logger)(Position point, PointType type)) { logPointAction = logger; } void setCFrameLogAction(void(*logger)(CFrame frame, CFrameType type)) { logCFrameAction = logger; }; void setShapeLogAction(void(*logger)(const Polyhedron& shape, const GlobalCFrame& location)) { logShapeAction = logger; } void setLogAction(void(*logger)(const char* format, std::va_list args)) { logAction = logger; } void setLogWarnAction(void(*logger)(const char* format, std::va_list args)) { logWarnAction = logger; } void setLogErrorAction(void(*logger)(const char* format, std::va_list args)) { logErrorAction = logger; } void saveIntersectionError(const Part& first, const Part& second, const char* reason) { logWarn("First cframe: %s", str(first.getCFrame()).c_str()); logWarn("Second cframe: %s", str(second.getCFrame()).c_str()); std::ofstream file; std::stringstream name; name << "../"; name << reason; name << ".nativeParts"; file.open(name.str().c_str(), std::ios::binary | std::ios::out); SerializationSessionPrototype session; const Part* parts[2]{&first, &second}; session.serializeParts(parts, 2, file); file.close(); } }; }; ================================================ FILE: Physics3D/misc/debug.h ================================================ #pragma once #include #include "../math/linalg/vec.h" #include "../math/position.h" #include "../math/cframe.h" #include "../math/globalCFrame.h" namespace P3D { class Part; class Polyhedron; namespace Debug { enum VectorType { INFO_VEC, FORCE, MOMENT, IMPULSE, ANGULAR_IMPULSE, POSITION, VELOCITY, ACCELERATION, ANGULAR_VELOCITY, }; enum PointType { INFO_POINT, CENTER_OF_MASS, INTERSECTION, }; enum CFrameType { OBJECT_CFRAME, INERTIAL_CFRAME }; void logVector(Position origin, Vec3 vec, VectorType type); void logPoint(Position point, PointType type); void logCFrame(CFrame frame, CFrameType type); void logShape(const Polyhedron& shape, const GlobalCFrame& location); void log(const char* format, ...); void logWarn(const char* format, ...); void logError(const char* format, ...); void setVectorLogAction(void(*logger)(Position origin, Vec3 vec, VectorType type)); void setPointLogAction(void(*logger)(Position point, PointType type)); void setCFrameLogAction(void(*logger)(CFrame frame, CFrameType type)); void setShapeLogAction(void(*logger)(const Polyhedron& shape, const GlobalCFrame& location)); void setLogAction(void(*logger)(const char* format, std::va_list args)); void setLogWarnAction(void(*logger)(const char* format, std::va_list args)); void setLogErrorAction(void(*logger)(const char* format, std::va_list args)); void saveIntersectionError(const Part& first, const Part& second, const char* reason); }; }; ================================================ FILE: Physics3D/misc/physicsProfiler.cpp ================================================ #include "physicsProfiler.h" namespace P3D { const char * physicsLabels[]{ "GJK Col", "GJK No Col", "EPA", "Collision", "Externals", "Col. Handling", "Constraints", "Tree Bounds", "Tree Structure", "Wait for lock", "Updates", "Queue", "Other" }; const char * intersectionLabels[]{ "Colission", "GJK Reject", "Part Dist Reject", "Part Bound Reject" }; const char* iterationLabels[]{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15+", "MAX", }; BreakdownAverageProfiler physicsMeasure(physicsLabels, 100); HistoricTally intersectionStatistics(intersectionLabels, 1); CircularBuffer gjkCollideIterStats(1); CircularBuffer gjkNoCollideIterStats(1); HistoricTally GJKCollidesIterationStatistics(iterationLabels, 1); HistoricTally GJKNoCollidesIterationStatistics(iterationLabels, 1); HistoricTally EPAIterationStatistics(iterationLabels, 1); }; ================================================ FILE: Physics3D/misc/physicsProfiler.h ================================================ #pragma once #include "profiling.h" namespace P3D { enum class PhysicsProcess { GJK_COL, GJK_NO_COL, EPA, COLISSION_OTHER, EXTERNALS, COLISSION_HANDLING, CONSTRAINTS, UPDATE_TREE_BOUNDS, UPDATE_TREE_STRUCTURE, WAIT_FOR_LOCK, UPDATING, QUEUE, OTHER, COUNT }; enum class IntersectionResult { COLISSION, GJK_REJECT, PART_DISTANCE_REJECT, PART_BOUNDS_REJECT, COUNT }; enum class IterationTime { INSTANT_QUIT = 0, ONE_ITER = 1, TOOMANY = 15, LIMIT_REACHED = 16, COUNT = 17 }; extern BreakdownAverageProfiler physicsMeasure; extern HistoricTally intersectionStatistics; extern CircularBuffer gjkCollideIterStats; extern CircularBuffer gjkNoCollideIterStats; extern HistoricTally GJKCollidesIterationStatistics; extern HistoricTally GJKNoCollidesIterationStatistics; extern HistoricTally EPAIterationStatistics; }; ================================================ FILE: Physics3D/misc/profiling.h ================================================ #pragma once #include #include #include "../datastructures/buffers.h" #include "../datastructures/parallelArray.h" namespace P3D { class TimerMeasure { std::chrono::high_resolution_clock::time_point lastClock = std::chrono::high_resolution_clock::now(); public: CircularBuffer pastTickLengths; CircularBuffer pastBetweenTimes; TimerMeasure(size_t capacity) : pastTickLengths(capacity), pastBetweenTimes(capacity) {} inline void start() { std::chrono::high_resolution_clock::time_point curTime = std::chrono::high_resolution_clock::now(); std::chrono::nanoseconds timeSinceLast = curTime - lastClock; pastBetweenTimes.add(timeSinceLast); lastClock = curTime; } inline void end() { std::chrono::nanoseconds timeTaken = std::chrono::high_resolution_clock::now() - lastClock; pastTickLengths.add(timeTaken); } }; template class HistoricTally { ParallelArray(Category::COUNT)> currentTally; public: char const* labels[static_cast(Category::COUNT)]; CircularBuffer(Category::COUNT)>> history; inline HistoricTally(char const* const labels[static_cast(Category::COUNT)], size_t size) : history(size) { for(size_t i = 0; i < static_cast(Category::COUNT); i++) { this->labels[i] = labels[i]; } clearCurrentTally(); } inline void addToTally(Category category, Unit amount) { currentTally[static_cast(category)] += amount; } inline void clearCurrentTally() { for(size_t i = 0; i < static_cast(Category::COUNT); i++) { currentTally[i] = Unit(0); } } inline void nextTally() { history.add(currentTally); clearCurrentTally(); } constexpr inline size_t size() const { return static_cast(Category::COUNT); } }; template class BreakdownAverageProfiler : public HistoricTally { std::chrono::high_resolution_clock::time_point startTime = std::chrono::high_resolution_clock::now(); ProcessType currentProcess = static_cast(-1); public: CircularBuffer tickHistory; inline BreakdownAverageProfiler(char const* const labels[static_cast(ProcessType::COUNT)], size_t capacity) : HistoricTally(labels, capacity), tickHistory(capacity) {} inline void mark(ProcessType process) { std::chrono::high_resolution_clock::time_point curTime = std::chrono::high_resolution_clock::now(); if(currentProcess != static_cast(-1)) { HistoricTally::addToTally(currentProcess, curTime - startTime); } startTime = curTime; currentProcess = process; } inline void mark(ProcessType process, ProcessType overrideOldProcess) { std::chrono::high_resolution_clock::time_point curTime = std::chrono::high_resolution_clock::now(); if(currentProcess != static_cast(-1)) { HistoricTally::addToTally(overrideOldProcess, curTime - startTime); } startTime = curTime; currentProcess = process; } inline void end() { std::chrono::high_resolution_clock::time_point curTime = std::chrono::high_resolution_clock::now(); this->addToTally(currentProcess, curTime - startTime); tickHistory.add(curTime); currentProcess = static_cast(-1); this->nextTally(); } inline double getAvgTPS() { size_t numTicks = tickHistory.size(); if(numTicks != 0) { std::chrono::high_resolution_clock::time_point firstTime = tickHistory.tail(); std::chrono::high_resolution_clock::time_point lastTime = tickHistory.front(); std::chrono::nanoseconds delta = lastTime - firstTime; double timeTaken = delta.count() * 1E-9; return (numTicks - 1) / timeTaken; } return 0.0; } }; }; ================================================ FILE: Physics3D/misc/serialization/dynamicSerialize.h ================================================ #pragma once #include "serializeBasicTypes.h" namespace P3D { typedef uint32_t ClassIDType; template class DynamicSerializerRegistry { public: struct DynamicSerializer { ClassIDType serializerID; DynamicSerializer(ClassIDType serializerID) : serializerID(serializerID) {} virtual void serialize(const BaseType& object, std::ostream& ostream) const = 0; virtual BaseType* deserialize(std::istream& istream, ArgsToInstantiate... args) const = 0; }; template class ConcreteDynamicSerializer : public DynamicSerializer { void (*serializeFunc)(const ConcreteType&, std::ostream&); ConcreteType* (*deserializeFunc)(std::istream&, ArgsToInstantiate...); public: ConcreteDynamicSerializer(void (*sf)(const ConcreteType&, std::ostream&), ConcreteType* (*dsf)(std::istream&, ArgsToInstantiate...), ClassIDType serializerID) : DynamicSerializer(serializerID), serializeFunc(sf), deserializeFunc(dsf) {} virtual void serialize(const BaseType& object, std::ostream& ostream) const override { const ConcreteType& concreteObject = static_cast(object); this->serializeFunc(concreteObject, ostream); } virtual BaseType* deserialize(std::istream& istream, ArgsToInstantiate... args) const override { return this->deserializeFunc(istream, args...); } }; private: std::unordered_map serializeRegistry; std::map deserializeRegistry; public: DynamicSerializerRegistry() = default; DynamicSerializerRegistry(std::initializer_list> initList) : serializeRegistry(), deserializeRegistry() { for(const std::pair& item : initList) { const DynamicSerializer* ds = item.second; serializeRegistry.emplace(item.first, ds); if(deserializeRegistry.find(ds->serializerID) != deserializeRegistry.end()) throw std::logic_error("Duplicate serializerID?"); deserializeRegistry.emplace(ds->serializerID, ds); } } template::value, ClassIDType>::type> void registerSerializerDeserializer(void (*serializeFunc)(const ConcreteType&, std::ostream&), ConcreteType* (*deserializeFunc)(std::istream&), ClassIDType serializerID) { if(deserializeRegistry.find(serializerID) != deserializeRegistry.end()) { throw std::logic_error("A serializer with this id has already been defined!"); } std::type_index ti = typeid(ConcreteType); if(serializeRegistry.find(ti) != serializeRegistry.end()) { throw std::logic_error("A serializer for this type has already been defined!"); } ConcreteDynamicSerializer* newSerializer = new ConcreteDynamicSerializer(serializeFunc, deserializeFunc, serializerID); serializeRegistry.emplace(ti, newSerializer); deserializeRegistry.emplace(serializerID, newSerializer); } template::value, ClassIDType>::type> void registerSerializerDeserializer(void (*serialize)(const ConcreteType&, std::ostream&), ConcreteType* (*deserialize)(std::istream&)) { ClassIDType i = 0; while(deserializeRegistry.find(i++) != deserializeRegistry.end()); registerSerializerDeserializer(serialize, deserialize, i); } void serialize(const BaseType& object, std::ostream& ostream) const { auto location = serializeRegistry.find(typeid(object)); if(location == serializeRegistry.end()) { throw SerializationException("This class is not in the serialization registry!"); } const DynamicSerializer* serializer = (*location).second; serializeBasicTypes(serializer->serializerID, ostream); serializer->serialize(object, ostream); } BaseType* deserialize(std::istream& istream, ArgsToInstantiate... args) const { ClassIDType serialID = deserializeBasicTypes(istream); auto location = deserializeRegistry.find(serialID); if(location == deserializeRegistry.end()) { throw SerializationException("Invalid dynamic class ID!"); } const DynamicSerializer* deserializer = (*location).second; return deserializer->deserialize(istream, args...); } }; }; ================================================ FILE: Physics3D/misc/serialization/serialization.cpp ================================================ #include "serialization.h" #include #include #include #include #include #include "../../geometry/polyhedron.h" #include "../../geometry/builtinShapeClasses.h" #include "../../geometry/shape.h" #include "../../geometry/shapeClass.h" #include "../../part.h" #include "../../world.h" #include "../../layer.h" #include "../../hardconstraints/hardConstraint.h" #include "../../hardconstraints/fixedConstraint.h" #include "../../hardconstraints/motorConstraint.h" #include "../../hardconstraints/sinusoidalPistonConstraint.h" #include "../../constraints/ballConstraint.h" #include "../../constraints/hingeConstraint.h" #include "../../constraints/barConstraint.h" #include "../../externalforces/directionalGravity.h" namespace P3D { #define CURRENT_VERSION_ID 2 #pragma region serializeComponents void serializePolyhedron(const Polyhedron& poly, std::ostream& ostream) { serializeBasicTypes(poly.vertexCount, ostream); serializeBasicTypes(poly.triangleCount, ostream); for(int i = 0; i < poly.vertexCount; i++) { serializeBasicTypes(poly.getVertex(i), ostream); } for(int i = 0; i < poly.triangleCount; i++) { serializeBasicTypes(poly.getTriangle(i), ostream); } } Polyhedron deserializePolyhedron(std::istream& istream) { uint32_t vertexCount = deserializeBasicTypes(istream); uint32_t triangleCount = deserializeBasicTypes(istream); Vec3f* vertices = new Vec3f[vertexCount]; Triangle* triangles = new Triangle[triangleCount]; for(uint32_t i = 0; i < vertexCount; i++) { vertices[i] = deserializeBasicTypes(istream); } for(uint32_t i = 0; i < triangleCount; i++) { triangles[i] = deserializeBasicTypes(istream); } Polyhedron result(vertices, triangles, vertexCount, triangleCount); delete[] vertices; delete[] triangles; return result; } void ShapeSerializer::include(const Shape& shape) { sharedShapeClassSerializer.include(shape.baseShape.get()); } void ShapeSerializer::serializeShape(const Shape& shape, std::ostream& ostream) const { sharedShapeClassSerializer.serializeIDFor(shape.baseShape.get(), ostream); serializeBasicTypes(shape.getWidth(), ostream); serializeBasicTypes(shape.getHeight(), ostream); serializeBasicTypes(shape.getDepth(), ostream); } Shape ShapeDeserializer::deserializeShape(std::istream& istream) const { const ShapeClass* baseShape = sharedShapeClassDeserializer.deserializeObject(istream); double width = deserializeBasicTypes(istream); double height = deserializeBasicTypes(istream); double depth = deserializeBasicTypes(istream); return Shape(intrusive_ptr(baseShape), width, height, depth); } void serializeFixedConstraint(const FixedConstraint& object, std::ostream& ostream) {} FixedConstraint* deserializeFixedConstraint(std::istream& istream) { return new FixedConstraint(); } void serializeMotorConstraint(const ConstantSpeedMotorConstraint& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.speed, ostream); serializeBasicTypes(constraint.currentAngle, ostream); } ConstantSpeedMotorConstraint* deserializeMotorConstraint(std::istream& istream) { double speed = deserializeBasicTypes(istream); double currentAngle = deserializeBasicTypes(istream); return new ConstantSpeedMotorConstraint(speed, currentAngle); } void serializePistonConstraint(const SinusoidalPistonConstraint& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.minValue, ostream); serializeBasicTypes(constraint.maxValue, ostream); serializeBasicTypes(constraint.period, ostream); serializeBasicTypes(constraint.currentStepInPeriod, ostream); } SinusoidalPistonConstraint* deserializePistonConstraint(std::istream& istream) { double minLength = deserializeBasicTypes(istream); double maxLength = deserializeBasicTypes(istream); double period = deserializeBasicTypes(istream); double currentStepInPeriod = deserializeBasicTypes(istream); SinusoidalPistonConstraint* newConstraint = new SinusoidalPistonConstraint(minLength, maxLength, period); newConstraint->currentStepInPeriod = currentStepInPeriod; return newConstraint; } void serializeSinusoidalMotorConstraint(const MotorConstraintTemplate& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.minValue, ostream); serializeBasicTypes(constraint.maxValue, ostream); serializeBasicTypes(constraint.period, ostream); serializeBasicTypes(constraint.currentStepInPeriod, ostream); } MotorConstraintTemplate* deserializeSinusoidalMotorConstraint(std::istream& istream) { double minLength = deserializeBasicTypes(istream); double maxLength = deserializeBasicTypes(istream); double period = deserializeBasicTypes(istream); double currentStepInPeriod = deserializeBasicTypes(istream); MotorConstraintTemplate* newConstraint = new MotorConstraintTemplate(minLength, maxLength, period); newConstraint->currentStepInPeriod = currentStepInPeriod; return newConstraint; } void serializeBallConstraint(const BallConstraint& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.attachA, ostream); serializeBasicTypes(constraint.attachB, ostream); } BallConstraint* deserializeBallConstraint(std::istream& istream) { Vec3 attachA = deserializeBasicTypes(istream); Vec3 attachB = deserializeBasicTypes(istream); return new BallConstraint(attachA, attachB); } void serializeHingeConstraint(const HingeConstraint& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.attachA, ostream); serializeBasicTypes(constraint.axisA, ostream); serializeBasicTypes(constraint.attachB, ostream); serializeBasicTypes(constraint.axisB, ostream); } HingeConstraint* deserializeHingeConstraint(std::istream& istream) { Vec3 attachA = deserializeBasicTypes(istream); Vec3 axisA = deserializeBasicTypes(istream); Vec3 attachB = deserializeBasicTypes(istream); Vec3 axisB = deserializeBasicTypes(istream); return new HingeConstraint(attachA, axisA, attachB, axisB); } void serializeBarConstraint(const BarConstraint& constraint, std::ostream& ostream) { serializeBasicTypes(constraint.attachA, ostream); serializeBasicTypes(constraint.attachB, ostream); serializeBasicTypes(constraint.barLength, ostream); } BarConstraint* deserializeBarConstraint(std::istream& istream) { Vec3 attachA = deserializeBasicTypes(istream); Vec3 attachB = deserializeBasicTypes(istream); double length = deserializeBasicTypes(istream); return new BarConstraint(attachA, attachB, length); } void serializePolyhedronShapeClass(const PolyhedronShapeClass& polyhedron, std::ostream& ostream) { serializePolyhedron(polyhedron.asPolyhedron(), ostream); } PolyhedronShapeClass* deserializePolyhedronShapeClass(std::istream& istream) { Polyhedron poly = deserializePolyhedron(istream); PolyhedronShapeClass* result = new PolyhedronShapeClass(std::move(poly)); return result; } void serializeDirectionalGravity(const DirectionalGravity& gravity, std::ostream& ostream) { serializeBasicTypes(gravity.gravity, ostream); } DirectionalGravity* deserializeDirectionalGravity(std::istream& istream) { Vec3 g = deserializeBasicTypes(istream); return new DirectionalGravity(g); } #pragma endregion #pragma region serializePartPhysicalAndRelated static void serializeLayer(const Part& part, std::ostream& ostream) { serializeBasicTypes(part.getLayerID(), ostream); } static WorldLayer* deserializeLayer(std::vector& knownLayers, std::istream& istream) { uint32_t id = deserializeBasicTypes(istream); return getLayerByID(knownLayers, id); } void SerializationSessionPrototype::serializePartData(const Part& part, std::ostream& ostream) { shapeSerializer.serializeShape(part.hitbox, ostream); serializeBasicTypes(part.properties, ostream); this->serializePartExternalData(part, ostream); } void SerializationSessionPrototype::serializePartExternalData(const Part& part, std::ostream& ostream) { // no extra data by default } Part* DeSerializationSessionPrototype::deserializePartData(const GlobalCFrame& cframe, WorldLayer* layer, std::istream& istream) { Shape shape = shapeDeserializer.deserializeShape(istream); PartProperties properties = deserializeBasicTypes(istream); Part* result = this->deserializePartExternalData(Part(shape, cframe, properties), istream); result->layer = layer; return result; } Part* DeSerializationSessionPrototype::deserializePartExternalData(Part&& part, std::istream& istream) { return new Part(std::move(part)); } void SerializationSessionPrototype::serializeRigidBodyInContext(const RigidBody& rigidBody, std::ostream& ostream) { serializeLayer(*rigidBody.mainPart, ostream); serializePartData(*rigidBody.mainPart, ostream); serializeBasicTypes(static_cast(rigidBody.parts.size()), ostream); for(const AttachedPart& atPart : rigidBody.parts) { serializeBasicTypes(atPart.attachment, ostream); serializeLayer(*atPart.part, ostream); serializePartData(*atPart.part, ostream); } } RigidBody DeSerializationSessionPrototype::deserializeRigidBodyWithContext(const GlobalCFrame& cframeOfMain, std::vector& layers, std::istream& istream) { WorldLayer* layer = deserializeLayer(layers, istream); Part* mainPart = deserializePartData(cframeOfMain, layer, istream); RigidBody result(mainPart); uint32_t size = deserializeBasicTypes(istream); result.parts.reserve(size); for(uint32_t i = 0; i < size; i++) { CFrame attach = deserializeBasicTypes(istream); WorldLayer* layer = deserializeLayer(layers, istream); Part* newPart = deserializePartData(cframeOfMain.localToGlobal(attach), layer, istream); result.parts.push_back(AttachedPart{attach, newPart}); } return result; } void SerializationSessionPrototype::serializeConstraintInContext(const PhysicalConstraint& constraint, std::ostream& ostream) { std::uint32_t indexA = this->physicalIndexMap[constraint.physA]; std::uint32_t indexB = this->physicalIndexMap[constraint.physB]; serializeBasicTypes(indexA, ostream); serializeBasicTypes(indexB, ostream); dynamicConstraintSerializer.serialize(*constraint.constraint, ostream); } PhysicalConstraint DeSerializationSessionPrototype::deserializeConstraintInContext(std::istream& istream) { std::uint32_t indexA = deserializeBasicTypes(istream); std::uint32_t indexB = deserializeBasicTypes(istream); Physical* physA = indexToPhysicalMap[indexA]; Physical* physB = indexToPhysicalMap[indexB]; return PhysicalConstraint(physA, physB, dynamicConstraintSerializer.deserialize(istream)); } static void serializeHardPhysicalConnection(const HardPhysicalConnection& connection, std::ostream& ostream) { serializeBasicTypes(connection.attachOnChild, ostream); serializeBasicTypes(connection.attachOnParent, ostream); dynamicHardConstraintSerializer.serialize(*connection.constraintWithParent, ostream); } static HardPhysicalConnection deserializeHardPhysicalConnection(std::istream& istream) { CFrame attachOnChild = deserializeBasicTypes(istream); CFrame attachOnParent = deserializeBasicTypes(istream); HardConstraint* constraint = dynamicHardConstraintSerializer.deserialize(istream); return HardPhysicalConnection(std::unique_ptr(constraint), attachOnChild, attachOnParent); } void SerializationSessionPrototype::serializePhysicalInContext(const Physical& phys, std::ostream& ostream) { physicalIndexMap.emplace(&phys, currentPhysicalIndex++); serializeRigidBodyInContext(phys.rigidBody, ostream); serializeBasicTypes(static_cast(phys.childPhysicals.size()), ostream); for(const ConnectedPhysical& p : phys.childPhysicals) { serializeHardPhysicalConnection(p.connectionToParent, ostream); serializePhysicalInContext(p, ostream); } } void SerializationSessionPrototype::serializeMotorizedPhysicalInContext(const MotorizedPhysical& phys, std::ostream& ostream) { serializeBasicTypes(phys.motionOfCenterOfMass, ostream); serializeBasicTypes(phys.getMainPart()->getCFrame(), ostream); serializePhysicalInContext(phys, ostream); } void DeSerializationSessionPrototype::deserializeConnectionsOfPhysicalWithContext(std::vector& layers, Physical& physToPopulate, std::istream& istream) { uint32_t childrenCount = deserializeBasicTypes(istream); physToPopulate.childPhysicals.reserve(childrenCount); for(uint32_t i = 0; i < childrenCount; i++) { HardPhysicalConnection connection = deserializeHardPhysicalConnection(istream); GlobalCFrame cframeOfConnectedPhys = physToPopulate.getCFrame().localToGlobal(connection.getRelativeCFrameToParent()); RigidBody b = deserializeRigidBodyWithContext(cframeOfConnectedPhys, layers, istream); physToPopulate.childPhysicals.emplace_back(std::move(b), &physToPopulate, std::move(connection)); ConnectedPhysical& currentlyWorkingOn = physToPopulate.childPhysicals.back(); indexToPhysicalMap.push_back(static_cast(¤tlyWorkingOn)); deserializeConnectionsOfPhysicalWithContext(layers, currentlyWorkingOn, istream); } } MotorizedPhysical* DeSerializationSessionPrototype::deserializeMotorizedPhysicalWithContext(std::vector& layers, std::istream& istream) { Motion motion = deserializeBasicTypes(istream); GlobalCFrame cf = deserializeBasicTypes(istream); MotorizedPhysical* mainPhys = new MotorizedPhysical(deserializeRigidBodyWithContext(cf, layers, istream)); indexToPhysicalMap.push_back(static_cast(mainPhys)); mainPhys->motionOfCenterOfMass = motion; deserializeConnectionsOfPhysicalWithContext(layers, *mainPhys, istream); mainPhys->refreshPhysicalProperties(); return mainPhys; } #pragma endregion #pragma region serializeWorld #pragma region information collection void SerializationSessionPrototype::collectPartInformation(const Part& part) { this->shapeSerializer.include(part.hitbox); } void SerializationSessionPrototype::collectPhysicalInformation(const Physical& phys) { for(const Part& p : phys.rigidBody) { collectPartInformation(p); } for(const ConnectedPhysical& p : phys.childPhysicals) { collectConnectedPhysicalInformation(p); } } void SerializationSessionPrototype::collectMotorizedPhysicalInformation(const MotorizedPhysical& motorizedPhys) { collectPhysicalInformation(motorizedPhys); } void SerializationSessionPrototype::collectConnectedPhysicalInformation(const ConnectedPhysical& connectedPhys) { collectPhysicalInformation(connectedPhys); } #pragma endregion static void serializeVersion(std::ostream& ostream) { serializeBasicTypes(CURRENT_VERSION_ID, ostream); } static void assertVersionCorrect(std::istream& istream) { uint32_t readVersionID = deserializeBasicTypes(istream); if(readVersionID != CURRENT_VERSION_ID) { throw SerializationException( "This serialization version is outdated and cannot be read! Current " + std::to_string(CURRENT_VERSION_ID) + " version from stream: " + std::to_string(readVersionID) ); } } void SerializationSessionPrototype::serializeWorldLayer(const WorldLayer& layer, std::ostream& ostream) { uint32_t numberOfUnPhysicaledPartsInLayer = 0; layer.tree.forEach([&numberOfUnPhysicaledPartsInLayer](const Part& p) { if(p.getPhysical() == nullptr) { numberOfUnPhysicaledPartsInLayer++; } }); serializeBasicTypes(numberOfUnPhysicaledPartsInLayer, ostream); layer.tree.forEach([this, &ostream](const Part& p) { if(p.getPhysical() == nullptr) { serializeBasicTypes(p.getCFrame(), ostream); this->serializePartData(p, ostream); } }); } void SerializationSessionPrototype::serializeWorld(const WorldPrototype& world, std::ostream& ostream) { for(const MotorizedPhysical* p : world.physicals) { collectMotorizedPhysicalInformation(*p); } for(const ColissionLayer& clayer : world.layers) { for(const WorldLayer& layer : clayer.subLayers) { layer.tree.forEach([this](const Part& p) { if(p.getPhysical() == nullptr) { collectPartInformation(p); } }); } } serializeCollectedHeaderInformation(ostream); // actually serialize the world serializeBasicTypes(world.age, ostream); serializeBasicTypes(world.getLayerCount(), ostream); for(int i = 0; i < world.getLayerCount(); i++) { for(int j = 0; j <= i; j++) { serializeBasicTypes(world.doLayersCollide(i, j), ostream); } } for(const ColissionLayer& layer : world.layers) { serializeWorldLayer(layer.subLayers[ColissionLayer::TERRAIN_PARTS_LAYER], ostream); } serializeBasicTypes(static_cast(world.physicals.size()), ostream); for(const MotorizedPhysical* p : world.physicals) { serializeMotorizedPhysicalInContext(*p, ostream); } serializeBasicTypes(static_cast(world.constraints.size()), ostream); for(const ConstraintGroup& cg : world.constraints) { serializeBasicTypes(static_cast(cg.constraints.size()), ostream); for(const PhysicalConstraint& c : cg.constraints) { this->serializeConstraintInContext(c, ostream); } } serializeBasicTypes(static_cast(world.externalForces.size()), ostream); for(ExternalForce* force : world.externalForces) { dynamicExternalForceSerializer.serialize(*force, ostream); } } void DeSerializationSessionPrototype::deserializeWorldLayer(WorldLayer& layer, std::istream& istream) { uint32_t extraPartsInLayer = deserializeBasicTypes(istream); for(uint32_t i = 0; i < extraPartsInLayer; i++) { GlobalCFrame cf = deserializeBasicTypes(istream); layer.tree.add(deserializePartData(cf, &layer, istream)); } } void DeSerializationSessionPrototype::deserializeWorld(WorldPrototype& world, std::istream& istream) { this->deserializeAndCollectHeaderInformation(istream); world.age = deserializeBasicTypes(istream); world.layers.clear(); uint32_t layerCount = deserializeBasicTypes(istream); world.layers.reserve(layerCount); for(uint32_t i = 0; i < layerCount; i++) { world.layers.emplace_back(&world, false); } for(int i = 0; i < world.getLayerCount(); i++) { for(int j = 0; j <= i; j++) { bool layersCollide = deserializeBasicTypes(istream); world.setLayersCollide(i, j, layersCollide); } } for(ColissionLayer& layer : world.layers) { deserializeWorldLayer(layer.subLayers[ColissionLayer::TERRAIN_PARTS_LAYER], istream); } uint32_t numberOfPhysicals = deserializeBasicTypes(istream); world.physicals.reserve(numberOfPhysicals); for(uint32_t i = 0; i < numberOfPhysicals; i++) { world.addPhysicalWithExistingLayers(deserializeMotorizedPhysicalWithContext(world.layers, istream)); } std::uint32_t constraintCount = deserializeBasicTypes(istream); world.constraints.reserve(constraintCount); for(std::uint32_t cg = 0; cg < constraintCount; cg++) { ConstraintGroup group; std::uint32_t numberOfConstraintsInGroup = deserializeBasicTypes(istream); for(std::uint32_t c = 0; c < numberOfConstraintsInGroup; c++) { group.constraints.push_back(this->deserializeConstraintInContext(istream)); } world.constraints.push_back(std::move(group)); } uint32_t forceCount = deserializeBasicTypes(istream); world.externalForces.reserve(forceCount); for(uint32_t i = 0; i < forceCount; i++) { ExternalForce* force = dynamicExternalForceSerializer.deserialize(istream); world.externalForces.push_back(force); } } void SerializationSessionPrototype::serializeParts(const Part* const parts[], size_t partCount, std::ostream& ostream) { for(size_t i = 0; i < partCount; i++) { collectPartInformation(*(parts[i])); } serializeCollectedHeaderInformation(ostream); serializeBasicTypes(static_cast(partCount), ostream); for(size_t i = 0; i < partCount; i++) { serializeBasicTypes(parts[i]->getCFrame(), ostream); serializePartData(*(parts[i]), ostream); } } std::vector DeSerializationSessionPrototype::deserializeParts(std::istream& istream) { deserializeAndCollectHeaderInformation(istream); size_t numberOfParts = deserializeBasicTypes(istream); std::vector result; result.reserve(numberOfParts); for(size_t i = 0; i < numberOfParts; i++) { GlobalCFrame cframeOfPart = deserializeBasicTypes(istream); Part* newPart = deserializePartData(cframeOfPart, nullptr, istream); result.push_back(newPart); } return result; } void SerializationSessionPrototype::serializeCollectedHeaderInformation(std::ostream& ostream) { serializeVersion(ostream); this->shapeSerializer.sharedShapeClassSerializer.serializeRegistry([](const ShapeClass* sc, std::ostream& ostream) {dynamicShapeClassSerializer.serialize(*sc, ostream); }, ostream); } void DeSerializationSessionPrototype::deserializeAndCollectHeaderInformation(std::istream& istream) { assertVersionCorrect(istream); shapeDeserializer.sharedShapeClassDeserializer.deserializeRegistry([](std::istream& istream) {return dynamicShapeClassSerializer.deserialize(istream); }, istream); } static const ShapeClass* builtinKnownShapeClasses[]{&CubeClass::instance, &SphereClass::instance, &CylinderClass::instance}; SerializationSessionPrototype::SerializationSessionPrototype(const std::vector& knownShapeClasses) : shapeSerializer(builtinKnownShapeClasses) { for(const ShapeClass* sc : knownShapeClasses) { shapeSerializer.sharedShapeClassSerializer.addPredefined(sc); } } DeSerializationSessionPrototype::DeSerializationSessionPrototype(const std::vector& knownShapeClasses) : shapeDeserializer(builtinKnownShapeClasses) { for(const ShapeClass* sc : knownShapeClasses) { shapeDeserializer.sharedShapeClassDeserializer.addPredefined(sc); } } #pragma endregion #pragma region dynamic serializers static DynamicSerializerRegistry::ConcreteDynamicSerializer fixedConstraintSerializer (serializeFixedConstraint, deserializeFixedConstraint, 0); static DynamicSerializerRegistry::ConcreteDynamicSerializer motorConstraintSerializer (serializeMotorConstraint, deserializeMotorConstraint, 1); static DynamicSerializerRegistry::ConcreteDynamicSerializer pistonConstraintSerializer (serializePistonConstraint, deserializePistonConstraint, 2); static DynamicSerializerRegistry::ConcreteDynamicSerializer> sinusiodalMotorConstraintSerializer (serializeSinusoidalMotorConstraint, deserializeSinusoidalMotorConstraint, 3); static DynamicSerializerRegistry::ConcreteDynamicSerializer ballConstraintSerializer (serializeBallConstraint, deserializeBallConstraint, 0); static DynamicSerializerRegistry::ConcreteDynamicSerializer hingeConstraintSerializer (serializeHingeConstraint, deserializeHingeConstraint, 1); static DynamicSerializerRegistry::ConcreteDynamicSerializer barConstraintSerializer (serializeBarConstraint, deserializeBarConstraint, 2); static DynamicSerializerRegistry::ConcreteDynamicSerializer polyhedronSerializer (serializePolyhedronShapeClass, deserializePolyhedronShapeClass, 0); static DynamicSerializerRegistry::ConcreteDynamicSerializer gravitySerializer (serializeDirectionalGravity, deserializeDirectionalGravity, 0); DynamicSerializerRegistry dynamicConstraintSerializer{ {typeid(BallConstraint), &ballConstraintSerializer}, {typeid(HingeConstraint), &hingeConstraintSerializer}, {typeid(BarConstraint), &barConstraintSerializer} }; DynamicSerializerRegistry dynamicHardConstraintSerializer{ {typeid(FixedConstraint), &fixedConstraintSerializer}, {typeid(ConstantSpeedMotorConstraint), &motorConstraintSerializer}, {typeid(SinusoidalPistonConstraint), &pistonConstraintSerializer}, {typeid(MotorConstraintTemplate), &sinusiodalMotorConstraintSerializer} }; DynamicSerializerRegistry dynamicShapeClassSerializer{ {typeid(PolyhedronShapeClass), &polyhedronSerializer} }; DynamicSerializerRegistry dynamicExternalForceSerializer{ {typeid(DirectionalGravity), &gravitySerializer} }; #pragma endregion }; ================================================ FILE: Physics3D/misc/serialization/serialization.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include "../../math/fix.h" #include "../../math/linalg/vec.h" #include "../../math/linalg/mat.h" #include "../../math/cframe.h" #include "../../math/globalCFrame.h" #include "../../geometry/polyhedron.h" #include "../../geometry/shape.h" #include "../../part.h" #include "../../world.h" #include "../../physical.h" #include "../../hardconstraints/hardConstraint.h" #include "../../hardconstraints/fixedConstraint.h" #include "../../hardconstraints/motorConstraint.h" #include "../../constraints/ballConstraint.h" #include "../../externalforces/directionalGravity.h" #include "serializeBasicTypes.h" #include "sharedObjectSerializer.h" #include "dynamicSerialize.h" namespace P3D { void serializePolyhedron(const Polyhedron& poly, std::ostream& ostream); Polyhedron deserializePolyhedron(std::istream& istream); void serializeFixedConstraint(const FixedConstraint& object, std::ostream& ostream); FixedConstraint* deserializeFixedConstraint(std::istream& istream); void serializeMotorConstraint(const ConstantSpeedMotorConstraint& constraint, std::ostream& ostream); ConstantSpeedMotorConstraint* deserializeMotorConstraint(std::istream& istream); void serializeDirectionalGravity(const DirectionalGravity& gravity, std::ostream& ostream); DirectionalGravity* deserializeDirectionalGravity(std::istream& istream); class ShapeSerializer { public: SharedObjectSerializer sharedShapeClassSerializer; ShapeSerializer() = default; template inline ShapeSerializer(const List& knownShapeClasses) : sharedShapeClassSerializer(knownShapeClasses) {} void include(const Shape& shape); void serializeShape(const Shape& shape, std::ostream& ostream) const; }; class ShapeDeserializer { public: SharedObjectDeserializer sharedShapeClassDeserializer; ShapeDeserializer() = default; template inline ShapeDeserializer(const List& knownShapeClasses) : sharedShapeClassDeserializer(knownShapeClasses) {} Shape deserializeShape(std::istream& ostream) const; }; class SerializationSessionPrototype { protected: ShapeSerializer shapeSerializer; std::map physicalIndexMap; std::uint32_t currentPhysicalIndex = 0; private: void collectMotorizedPhysicalInformation(const MotorizedPhysical& motorizedPhys); void collectConnectedPhysicalInformation(const ConnectedPhysical& connectedPhys); void collectPhysicalInformation(const Physical& phys); void serializeMotorizedPhysicalInContext(const MotorizedPhysical& motorizedPhys, std::ostream& ostream); void serializePhysicalInContext(const Physical& phys, std::ostream& ostream); void serializeRigidBodyInContext(const RigidBody& rigidBody, std::ostream& ostream); void serializeWorldLayer(const WorldLayer& layer, std::ostream& ostream); void serializeConstraintInContext(const PhysicalConstraint& constraint, std::ostream& ostream); protected: virtual void collectPartInformation(const Part& part); virtual void serializeCollectedHeaderInformation(std::ostream& ostream); // serializes everything but the part's cframe and layer index, to be done by the calling code as needed // calls serializePartExternalData for extending this serialization void serializePartData(const Part& part, std::ostream& ostream); virtual void serializePartExternalData(const Part& part, std::ostream& ostream); public: /*initializes the SerializationSession with the given ShapeClasses as "known" at deserialization, making it unneccecary to serialize them. Implicitly the builtin ShapeClasses from the physics engine, such as cubeClass and sphereClass are also included in this list */ SerializationSessionPrototype(const std::vector& knownShapeClasses = std::vector()); void serializeWorld(const WorldPrototype& world, std::ostream& ostream); void serializeParts(const Part* const parts[], size_t partCount, std::ostream& ostream); }; class DeSerializationSessionPrototype { private: MotorizedPhysical* deserializeMotorizedPhysicalWithContext(std::vector& layers, std::istream& istream); void deserializeConnectionsOfPhysicalWithContext(std::vector& layers, Physical& physToPopulate, std::istream& istream); RigidBody deserializeRigidBodyWithContext(const GlobalCFrame& cframeOfMain, std::vector& layers, std::istream& istream); PhysicalConstraint deserializeConstraintInContext(std::istream& istream); void deserializeWorldLayer(WorldLayer& layer, std::istream& istream); protected: ShapeDeserializer shapeDeserializer; std::vector indexToPhysicalMap; // creates a part with the given cframe, layer, and extra data it deserializes // calls deserializePartExternalData for extending this deserialization Part* deserializePartData(const GlobalCFrame& cframe, WorldLayer* layer, std::istream& istream); virtual Part* deserializePartExternalData(Part&& part, std::istream& istream); virtual void deserializeAndCollectHeaderInformation(std::istream& istream); public: /*initializes the DeSerializationSession with the given ShapeClasses as "known" at deserialization, these are used along with the deserialized ShapeClasses Implicitly the builtin ShapeClasses from the physics engine, such as cubeClass and sphereClass are also included in this list */ DeSerializationSessionPrototype(const std::vector& knownShapeClasses = std::vector()); void deserializeWorld(WorldPrototype& world, std::istream& istream); std::vector deserializeParts(std::istream& istream); }; template std::vector castVector(std::vector&& old) { std::vector result(old.size()); for(size_t i = 0; i < old.size(); i++) { result[i] = static_cast(old[i]); } return result; } template class SerializationSession : private SerializationSessionPrototype { protected: using SerializationSessionPrototype::shapeSerializer; using SerializationSessionPrototype::serializeCollectedHeaderInformation; virtual void collectExtendedPartInformation(const ExtendedPartType& part) {} virtual void serializePartExternalData(const ExtendedPartType& part, std::ostream& ostream) {} private: virtual void collectPartInformation(const Part& part) final override { SerializationSessionPrototype::collectPartInformation(part); const ExtendedPartType& p = static_cast(part); collectExtendedPartInformation(p); } virtual void serializePartExternalData(const Part& part, std::ostream& ostream) final override { const ExtendedPartType& p = static_cast(part); serializePartExternalData(p, ostream); } public: using SerializationSessionPrototype::SerializationSessionPrototype; void serializeWorld(const World& world, std::ostream& ostream) { SerializationSessionPrototype::serializeWorld(world, ostream); } void serializeParts(const ExtendedPartType* const parts[], size_t partCount, std::ostream& ostream) { std::vector baseParts(partCount); for(size_t i = 0; i < partCount; i++) { baseParts[i] = parts[i]; } SerializationSessionPrototype::serializeParts(&baseParts[0], partCount, ostream); } }; template class DeSerializationSession : private DeSerializationSessionPrototype { protected: using DeSerializationSessionPrototype::shapeDeserializer; using DeSerializationSessionPrototype::deserializeAndCollectHeaderInformation; virtual ExtendedPartType* deserializeExtendedPart(Part&& partPrototype, std::istream& istream) = 0; private: inline virtual Part* deserializePartExternalData(Part&& part, std::istream& istream) final override { return deserializeExtendedPart(std::move(part), istream); } public: using DeSerializationSessionPrototype::DeSerializationSessionPrototype; void deserializeWorld(World& world, std::istream& istream) { DeSerializationSessionPrototype::deserializeWorld(world, istream); } std::vector deserializeParts(std::istream& istream) { return castVector(DeSerializationSessionPrototype::deserializeParts(istream)); } }; extern DynamicSerializerRegistry dynamicConstraintSerializer; extern DynamicSerializerRegistry dynamicHardConstraintSerializer; extern DynamicSerializerRegistry dynamicShapeClassSerializer; extern DynamicSerializerRegistry dynamicExternalForceSerializer; }; ================================================ FILE: Physics3D/misc/serialization/serializeBasicTypes.cpp ================================================ #include "serializeBasicTypes.h" #include namespace P3D { void serializeBasicTypes(const char* data, size_t size, std::ostream& ostream) { ostream.write(data, size); } void deserializeBasicTypes(char* buf, size_t size, std::istream& istream) { istream.read(buf, size); } template<> void serializeBasicTypes(const char& c, std::ostream& ostream) { ostream << c; } template<> char deserializeBasicTypes(std::istream& istream) { return istream.get(); } template<> void serializeBasicTypes(const bool& b, std::ostream& ostream) { serializeBasicTypes(b ? 255 : 0, ostream); } template<> bool deserializeBasicTypes(std::istream& istream) { return deserializeBasicTypes(istream) != 0; } void serializeString(const std::string& str, std::ostream& ostream) { serializeBasicTypes(str.c_str(), str.length(), ostream); serializeBasicTypes('\0', ostream); } std::string deserializeString(std::istream& istream) { std::stringstream sstream; while(char c = istream.get()) { sstream << c; } return sstream.str(); } }; ================================================ FILE: Physics3D/misc/serialization/serializeBasicTypes.h ================================================ #pragma once #include #include #include #include namespace P3D { class SerializationException : public std::exception { std::string message; public: SerializationException() = default; SerializationException(std::string message) : message(message) {} virtual const char* what() const noexcept override { return message.c_str(); } }; void serializeBasicTypes(const char* data, size_t size, std::ostream& ostream); void deserializeBasicTypes(char* buf, size_t size, std::istream& istream); /* Trivial value serialization Included are: char, int, float, double, long, Fix, Vector, Matrix, SymmetricMatrix, DiagonalMatrix, CFrame, Transform, GlobalCFrame, GlobalTransform, Bounds, GlobalBounds */ template::value, int> = 0> void serializeBasicTypes(const T& i, std::ostream& ostream) { serializeBasicTypes(reinterpret_cast(&i), sizeof(T), ostream); } /* Trivial value deserialization Included are: char, int, float, double, long, Fix, Vector, Matrix, SymmetricMatrix, DiagonalMatrix, CFrame, Transform, GlobalCFrame, GlobalTransform, Bounds, GlobalBounds */ template::value, int> = 0> T deserializeBasicTypes(std::istream& istream) { union { char buf[sizeof(T)]; T value; } un{}; deserializeBasicTypes(un.buf, sizeof(T), istream); return un.value; } template<> void serializeBasicTypes(const char& c, std::ostream& ostream); template<> char deserializeBasicTypes(std::istream& istream); template<> void serializeBasicTypes(const bool& b, std::ostream& ostream); template<> bool deserializeBasicTypes(std::istream& istream); void serializeString(const std::string& str, std::ostream& ostream); std::string deserializeString(std::istream& istream); template void serializeArray(const T* data, size_t size, std::ostream& ostream) { for(size_t i = 0; i < size; i++) { serializeBasicTypes(data[i], ostream); } } template void deserializeArray(T* buf, size_t size, std::istream& istream) { for(size_t i = 0; i < size; i++) { buf[i] = deserializeBasicTypes(istream); } } }; ================================================ FILE: Physics3D/misc/serialization/sharedObjectSerializer.h ================================================ #pragma once #include #include #include #include #include "serializeBasicTypes.h" namespace P3D { template class SharedObjectSerializer { SerializeID curPredefinedID = std::numeric_limits::max(); SerializeID curDynamicID = 0; std::vector itemsYetToSerialize; public: std::map objectToIDMap; SharedObjectSerializer() = default; template SharedObjectSerializer(const ListType& knownObjects) { for(const T& obj : knownObjects) { addPredefined(obj); } } void addPredefined(const T& obj) { assert(objectToIDMap.find(obj) == objectToIDMap.end()); objectToIDMap.emplace(obj, curPredefinedID); curPredefinedID--; } void include(const T& obj) { if(objectToIDMap.find(obj) == objectToIDMap.end()) { itemsYetToSerialize.push_back(obj); objectToIDMap.emplace(obj, curDynamicID); curDynamicID++; } } // The given deserializer must be of the form 'serialize(T, std::ostream&)'. The object may be passed by ref or const ref template void serializeRegistry(Serializer serialize, std::ostream& ostream) { serializeBasicTypes(static_cast(itemsYetToSerialize.size()), ostream); for(const T& item : itemsYetToSerialize) { serialize(item, ostream); } itemsYetToSerialize.clear(); } void serializeIDFor(const T& obj, std::ostream& ostream) const { auto found = objectToIDMap.find(obj); if(found == objectToIDMap.end()) throw SerializationException("The given object was not registered!"); serializeBasicTypes((*found).second, ostream); } }; template class SharedObjectDeserializer { SerializeID curPredefinedID = std::numeric_limits::max(); SerializeID curDynamicID = 0; public: std::map IDToObjectMap; SharedObjectDeserializer() = default; template SharedObjectDeserializer(const ListType& knownObjects) { for(const T& obj : knownObjects) { addPredefined(obj); } } void addPredefined(const T& obj) { IDToObjectMap.emplace(curPredefinedID, obj); curPredefinedID--; } // The given deserializer must be of the form 'T deserialize(std::istream&)', it may return references or const refs. template void deserializeRegistry(Deserializer deserialize, std::istream& istream) { size_t numberOfObjectsToDeserialize = deserializeBasicTypes(istream); for(size_t i = 0; i < numberOfObjectsToDeserialize; i++) { T object = deserialize(istream); IDToObjectMap.emplace(curDynamicID, object); curDynamicID++; } } T deserializeObject(std::istream& istream) const { SerializeID id = deserializeBasicTypes(istream); auto found = IDToObjectMap.find(id); if(found == IDToObjectMap.end()) throw SerializationException("There is no associated object for the id " + std::to_string(id)); return (*found).second; } }; template class FixedSharedObjectSerializerDeserializer { std::map objectToIDMap; std::map IDToObjectMap; SerializeID currentID = 0; public: void registerObject(const T& object) { objectToIDMap.emplace(object, currentID); IDToObjectMap.emplace(currentID, object); currentID++; } void removeObject(const T& object) { auto iter = objectToIDMap.find(object); SerializeID id = iter.second(); IDToObjectMap.erase(id); objectToIDMap.erase(object); } FixedSharedObjectSerializerDeserializer(std::initializer_list list) { for(const T& item : list) { registerObject(item); } } void serialize(const T& object, std::ostream& ostream) const { auto iter = objectToIDMap.find(object); if(iter != objectToIDMap.end()) { serializeBasicTypes((*iter).second, ostream); } else { throw SerializationException("The given object was not registered"); } } T deserialize(std::istream& istream) const { SerializeID id = deserializeBasicTypes(istream); auto iter = IDToObjectMap.find(id); if(iter != IDToObjectMap.end()) { return (*iter).second; } else { throw SerializationException("There is no registered object matching id " + std::to_string(id)); } } }; }; ================================================ FILE: Physics3D/misc/toString.h ================================================ #pragma once #include #include #include #include #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" #include "../math/linalg/eigen.h" #include "../math/linalg/largeMatrix.h" #include "../math/cframe.h" #include "../math/position.h" #include "../math/globalCFrame.h" #include "../math/taylorExpansion.h" #include "../math/boundingBox.h" #include "../motion.h" #include "../relativeMotion.h" namespace P3D { template inline std::ostream& operator<<(std::ostream& os, const P3D::UnmanagedLargeMatrix& matrix) { for(int i = 0; i < matrix.h; i++) { for(int j = 0; j < matrix.w; j++) { os << matrix(i, j) << '\t'; } os << '\n'; } return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::UnmanagedVerticalFixedMatrix& matrix) { for(int i = 0; i < Rows; i++) { for(int j = 0; j < matrix.cols; j++) { os << matrix(i, j) << '\t'; } os << '\n'; } return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::UnmanagedHorizontalFixedMatrix& matrix) { for(int i = 0; i < matrix.rows; i++) { for(int j = 0; j < Cols; j++) { os << matrix(i, j) << '\t'; } os << '\n'; } return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::UnmanagedLargeVector& vector) { for(int i = 0; i < vector.n; i++) { os << vector[i] << ','; } return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::Vector& vector) { os << std::fixed << std::setprecision(4); os << '('; for(size_t i = 0; i < Size - 1; i++) { if (vector[i] >= 0) os << "+"; os << vector[i] << ", "; } if (vector[Size - 1] >= 0) os << "+"; os << vector[Size - 1] << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, P3D::Fix f) { int64_t intPart = f.value >> N; int64_t frac = f.value & ((1ULL << N) - 1); if(intPart < 0) { if(frac == 0) { intPart = -intPart; } else { intPart = -intPart-1; frac = (1ULL << N) - frac; } os << '-'; } std::streamsize prec = os.precision(); for(int i = 0; i < prec; i++) { frac *= 5; // multiply by powers of 10, dividing out the /2 with the shifts // multiply by 5 instead of 10, to stay away from the integer limit just a little longer } frac = frac >> (N - prec - 1); // shift right, but minus the divisions previously done, except for last bit, which is used for rounding if(frac & 1) { frac = (frac >> 1) + 1; int64_t maxFrac = 1; for(int i = 0; i < prec; i++) { maxFrac *= 10; } if(frac >= maxFrac) { frac = 0; ++intPart; } } else { frac = frac >> 1; } bool fixed = os.flags() & std::ios::fixed; os << intPart; if(frac == 0) { if(fixed) { os << '.'; for(int i = 0; i < prec; i++) { os << '0'; } } } else { os << '.'; std::streamsize digits = 1; int64_t digitCounter = 10; while(frac >= digitCounter) { digitCounter *= 10; digits++; } std::streamsize leadingZeros = prec - digits; for(int i = 0; i < leadingZeros; i++) { os << '0'; } if(!fixed) { while(frac % 10 == 0) { frac /= 10; } } os << frac; } return os; } inline std::ostream& operator<<(std::ostream& os, P3D::Position position) { os << "(" << position.x << ", " << position.y << ", " << position.z << ")"; return os; } inline std::ostream& operator<<(std::ostream& os, P3D::BoundingBox box) { os << "BoundingBox(" << box.min << ", " << box.max << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::Matrix& matrix) { os << "("; for(size_t row = 0; row < Height; row++) { for(size_t col = 0; col < Width - 1; col++) { os << matrix(row, col) << ", "; } os << matrix(row, Width - 1) << "; "; } os << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::SymmetricMatrix& matrix) { os << "("; for(size_t row = 0; row < Size; row++) { for(size_t col = 0; col < Size - 1; col++) { os << matrix(row, col) << ", "; } os << matrix(row, Size - 1) << "; "; } os << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::DiagonalMatrix& matrix) { os << "Diag("; for(size_t i = 0; i < Size; i++) { os << matrix[i] << "; "; } os << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::EigenValues& v) { os << "EigenValues("; for(size_t i = 0; i < Size - 1; i++) os << v[i] << ", "; os << v[Size - 1] << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::Quaternion& quat) { os << quat.w; if(quat.i >= 0) os << '+'; os << quat.i << 'i'; if(quat.j >= 0) os << '+'; os << quat.j << 'j'; if(quat.k >= 0) os << '+'; os << quat.k << 'k'; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::RotationTemplate& rotation) { os << rotation.asRotationMatrix(); return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::CFrameTemplate& cframe) { os << "CFrame(" << cframe.position << ", " << cframe.rotation << ")"; return os; } inline std::ostream& operator<<(std::ostream& os, const P3D::GlobalCFrame& cframe) { os << "GlobalCFrame(" << cframe.position << ", " << cframe.rotation << ")"; return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::TaylorExpansion& taylor) { if constexpr(Size > 0) { os << taylor[0] << "x"; for(std::size_t i = 1; i < Size; i++) { os << " + " << taylor[i] << "x^" << (i + 1) << '/' << (i+1) << '!'; } } return os; } template inline std::ostream& operator<<(std::ostream& os, const P3D::FullTaylorExpansion& taylor) { os << taylor.getConstantValue() << " + "; os << taylor.getDerivatives(); return os; } inline std::ostream& operator<<(std::ostream& os, const P3D::TranslationalMotion& motion) { os << "{vel: " << motion.getVelocity(); os << ", accel: " << motion.getAcceleration() << "}"; return os; } inline std::ostream& operator<<(std::ostream& os, const P3D::RotationalMotion& motion) { os << "{angularVel: " << motion.getAngularVelocity(); os << ", angularAccel: " << motion.getAngularAcceleration() << "}"; return os; } inline std::ostream& operator<<(std::ostream& os, const P3D::Motion& motion) { os << "{vel: " << motion.getVelocity(); os << ", angularVel: " << motion.getAngularVelocity(); os << ", accel: " << motion.getAcceleration(); os << ", angularAccel: " << motion.getAngularAcceleration() << "}"; return os; } inline std::ostream& operator<<(std::ostream& os, const P3D::RelativeMotion& relMotion) { os << "{motion: " << relMotion.relativeMotion; os << ", offset: " << relMotion.locationOfRelativeMotion << "}"; return os; } template inline std::string str(const T& obj) { std::stringstream ss; ss.precision(4); ss << obj; return ss.str(); } }; ================================================ FILE: Physics3D/misc/unreachable.h ================================================ #pragma once namespace P3D { #ifdef NDEBUG #ifdef __GNUC__ // GCC 4.8+, Clang, Intel and other compilers compatible with GCC (-std=c++0x or above) [[noreturn]] inline __attribute__((always_inline)) void unreachable() {__builtin_unreachable();} #elif defined(_MSC_VER) // MSVC [[noreturn]] __forceinline void unreachable() {__assume(false);} #else // ??? inline void unreachable() {} #endif #else #include inline void unreachable() {assert(false);} #endif } ================================================ FILE: Physics3D/misc/validityHelper.cpp ================================================ #include "validityHelper.h" #include #include #include #include "../geometry/polyhedron.h" #include "../geometry/indexedShape.h" #include "../misc/debug.h" #include "../boundstree/boundsTree.h" #include "../part.h" #include "../physical.h" namespace P3D { bool isValidTriangle(Triangle t, int vertexCount) { return t.firstIndex != t.secondIndex && t.secondIndex != t.thirdIndex && t.thirdIndex != t.firstIndex && t.firstIndex >= 0 && t.firstIndex < vertexCount && t.secondIndex >= 0 && t.secondIndex < vertexCount && t.thirdIndex >= 0 && t.thirdIndex < vertexCount; } // for every edge, of every triangle, check that it coincides with exactly one other triangle, in reverse order, revamped to be O(triangleCount) instead of O(triangleCount^2) static bool isComplete(const TriangleMesh& mesh) { struct HitCount { int ascendingCount = 0; int descendingCount = 0; }; bool verdict = true; // stores number of times two vertices appear as an edge, indexed by the lowest vertex std::vector> hitCounts(mesh.triangleCount); for(int i = 0; i < mesh.triangleCount; i++) { const Triangle& t = mesh.getTriangle(i); std::pair indexPairs[]{{t.firstIndex, t.secondIndex}, {t.secondIndex, t.thirdIndex}, {t.thirdIndex, t.firstIndex}}; for(std::pair indexes : indexPairs) { if(indexes.first == indexes.second) { Debug::logError("Invalid triangle! Triangle %d has a duplicate index! {%d, %d, %d}", i, t.firstIndex, t.secondIndex, t.thirdIndex); verdict = false; } bool firstIsSmallerThanSecond = indexes.first < indexes.second; std::pair sortedIndexes = firstIsSmallerThanSecond ? indexes : std::pair{indexes.second, indexes.first}; std::map& mp = hitCounts[sortedIndexes.first]; auto found = mp.find(sortedIndexes.second); if(found == mp.end()) { if(firstIsSmallerThanSecond) { mp.emplace(sortedIndexes.second, HitCount{1,0}); } else { mp.emplace(sortedIndexes.second, HitCount{0,1}); } } else { if(firstIsSmallerThanSecond) { (*found).second.ascendingCount++; } else { (*found).second.descendingCount++; } } } } for(int firstVertex = 0; firstVertex < hitCounts.size(); firstVertex++) { std::map& everythingConnected = hitCounts[firstVertex]; for(std::pair edge : everythingConnected) { if(edge.second.ascendingCount != 1 || edge.second.descendingCount != 1) { Debug::logError("Edge {%d,%d} has bad triangles connected, ascendingCount: %d, descendingCount: %d", firstVertex, edge.first, edge.second.ascendingCount, edge.second.descendingCount); verdict = false; } } } return verdict; } bool isValid(const TriangleMesh& mesh) { bool verdict = true; for(int i = 0; i < mesh.triangleCount; i++) { const Triangle& t = mesh.getTriangle(i); if(!isValidTriangle(t, mesh.vertexCount)) { Debug::logError("Invalid triangle! Triangle %d {%d, %d, %d} points to a nonexistent vertex or has a duplicate index!", i, t.firstIndex, t.secondIndex, t.thirdIndex); verdict = false; } } if(verdict == false) { return false; } int* usageCounts = new int[mesh.vertexCount]; for(int i = 0; i < mesh.vertexCount; i++) { usageCounts[i] = 0; } for(int i = 0; i < mesh.triangleCount; i++) { usageCounts[mesh.getTriangle(i).firstIndex]++; usageCounts[mesh.getTriangle(i).secondIndex]++; usageCounts[mesh.getTriangle(i).thirdIndex]++; } for(int i = 0; i < mesh.vertexCount; i++) { if(usageCounts[i] == 0) { Debug::logError("Vertex %d unused!", i); verdict = false; } } delete[] usageCounts; return verdict; } bool isValid(const Polyhedron& poly) { if(!isValid(static_cast(poly))) { Debug::logError("Invalid polyhedron: bad TriangleMesh"); return false; } if(!isComplete(poly)) { Debug::logError("Invalid polyhedron: incomplete"); return false; } // inverted to catch NaNs if(!(poly.getVolume() > 0)) { Debug::logError("Invalid polyhedron: inverted! Volume=%f", poly.getVolume()); return false; } return true; } bool isValid(const IndexedShape& shape) { if(!isValid(static_cast(shape))) { return false; }; // Assert that all neighbors are filled in for(int i = 0; i < shape.triangleCount; i++) for(int j = 0; j < 3; j++) if(shape.neighbors[i][j] < 0 || shape.neighbors[i][j] >= shape.triangleCount) return false; // Assert that, every triangle's neighbors have it as one of their neighbors for(int i = 0; i < shape.triangleCount; i++) { TriangleNeighbors thisNeighbors = shape.neighbors[i]; for(int j = 0; j < 3; j++) { int other = thisNeighbors[j]; if(!shape.neighbors[other].hasNeighbor(i)) return false; // check that if they ARE neighbors, then they must share vertices int neighborsIndex = shape.neighbors[other].getNeighborIndex(i); Triangle t1 = shape.getTriangle(i); Triangle t2 = shape.getTriangle(other); if(!(t1[(j + 1) % 3] == t2[(neighborsIndex + 2) % 3] && t1[(j + 2) % 3] == t2[(neighborsIndex + 1) % 3])) return false; } } return true; } static bool isConnectedPhysicalValid(const ConnectedPhysical* phys, const MotorizedPhysical* mainPhys); static bool isPhysicalValid(const Physical* phys, const MotorizedPhysical* mainPhys) { if(phys->mainPhysical != mainPhys) { Debug::logError("Physical's parent is not mainPhys!"); DEBUGBREAK; return false; } for(const Part& part : phys->rigidBody) { if(part.getPhysical() != phys) { Debug::logError("part's parent's child is not part"); DEBUGBREAK; return false; } } for(const ConnectedPhysical& subPhys : phys->childPhysicals) { if(!isConnectedPhysicalValid(&subPhys, mainPhys)) return false; } return true; } static bool isConnectedPhysicalValid(const ConnectedPhysical* phys, const MotorizedPhysical* mainPhys) { return isPhysicalValid(phys, mainPhys); } bool isMotorizedPhysicalValid(const MotorizedPhysical* mainPhys) { return isPhysicalValid(mainPhys, mainPhys); } }; ================================================ FILE: Physics3D/misc/validityHelper.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "../math/linalg/mat.h" #include "../math/linalg/largeMatrix.h" #include "../math/cframe.h" #include "../motion.h" #include "../boundstree/boundsTree.h" #include namespace P3D { #ifdef _MSC_VER #define DEBUGBREAK __debugbreak() #else #define DEBUGBREAK #endif inline bool isValid(double d) { return std::isfinite(d) && std::abs(d) < 100000.0; } inline bool isValid(float f) { return std::isfinite(f) && std::abs(f) < 100000.0; // sanity check } template inline bool isVecValid(const Vector& vec) { for(size_t i = 0; i < Size; i++) { if(!isValid(vec[i])) return false; } return true; } template inline bool isMatValid(const Matrix& mat) { for(size_t row = 0; row < Height; row++) { for(size_t col = 0; col < Width; col++) { if(!isValid(mat(row, col))) return false; } } return true; } template inline bool isMatValid(const SymmetricMatrix& mat) { for(size_t row = 0; row < Size; row++) { for(size_t col = row; col < Size; col++) { if(!isValid(mat(row, col))) return false; } } return true; } template inline bool isMatValid(const DiagonalMatrix& mat) { for(size_t i = 0; i < Size; i++) { if(!isValid(mat[i])) return false; } return true; } template inline bool isVecValid(const UnmanagedLargeVector& vec) { for(size_t i = 0; i < vec.size(); i++) { if(!isValid(vec[i])) return false; } return true; } template inline bool isMatValid(const UnmanagedLargeMatrix& mat) { for(size_t row = 0; row < mat.height(); row++) { for(size_t col = 0; col < mat.width(); col++) { if(!isValid(mat(row, col))) return false; } } return true; } template inline bool isMatValid(const UnmanagedHorizontalFixedMatrix& mat) { for(size_t row = 0; row < mat.height(); row++) { for(size_t col = 0; col < mat.width(); col++) { if(!isValid(mat(row, col))) return false; } } return true; } template inline bool isMatValid(const UnmanagedVerticalFixedMatrix& mat) { for(size_t row = 0; row < mat.height(); row++) { for(size_t col = 0; col < mat.width(); col++) { if(!isValid(mat(row, col))) return false; } } return true; } template inline bool isRotationValid(const MatrixRotationTemplate& rotation) { SquareMatrix rotMat = rotation.asRotationMatrix(); return isValidRotationMatrix(rotMat); } template inline bool isRotationValid(const QuaternionRotationTemplate& rotation) { Quaternion rotQuat = rotation.asRotationQuaternion(); return isValidRotationQuaternion(rotQuat); } template inline bool isCFrameValid(const CFrameTemplate& cframe) { return isVecValid(cframe.getPosition()) && isRotationValid(cframe.getRotation()); } template inline bool isTaylorExpansionValid(const TaylorExpansion& taylor) { for(const Vec3& v : taylor) { if(!isVecValid(v)) return false; } return true; } inline bool isTranslationalMotionValid(const TranslationalMotion& motion) { return isTaylorExpansionValid(motion.translation); } inline bool isRotationalMotionValid(const RotationalMotion& motion) { return isTaylorExpansionValid(motion.rotation); } inline bool isMotionValid(const Motion& motion) { return isTranslationalMotionValid(motion.translation) && isRotationalMotionValid(motion.rotation); } struct Triangle; class TriangleMesh; class Polyhedron; struct IndexedShape; bool isValid(const TriangleMesh& mesh); bool isValid(const Polyhedron& poly); bool isValid(const IndexedShape& shape); bool isValidTriangle(Triangle t, int vertexCount); class Part; class MotorizedPhysical; bool isMotorizedPhysicalValid(const MotorizedPhysical* mainPhys); template inline bool isBoundsTreeValidRecursive(const TreeTrunk& curNode, int curNodeSize, int depth = 0) { for(int i = 0; i < curNodeSize; i++) { const TreeNodeRef& subNode = curNode.subNodes[i]; BoundsTemplate foundBounds = curNode.getBoundsOfSubNode(i); if(subNode.isTrunkNode()) { const TreeTrunk& subTrunk = subNode.asTrunk(); int subTrunkSize = subNode.getTrunkSize(); BoundsTemplate realBounds = TrunkSIMDHelperFallback::getTotalBounds(subTrunk, subTrunkSize); if(realBounds != foundBounds) { std::cout << "(" << i << "/" << curNodeSize << ") Trunk bounds not up to date\n"; return false; } if(!isBoundsTreeValidRecursive(subTrunk, subTrunkSize, depth + 1)) { std::cout << "(" << i << "/" << curNodeSize << ")\n"; return false; } } else { const Boundable* itemB = static_cast(subNode.asObject()); if(foundBounds != itemB->getBounds()) { std::cout << "(" << i << "/" << curNodeSize << ") Leaf not up to date\n"; return false; } } } return true; } template bool isBoundsTreeValid(const BoundsTreePrototype& tree) { std::pair baseTrunk = tree.getBaseTrunk(); return isBoundsTreeValidRecursive(baseTrunk.first, baseTrunk.second); } template bool isBoundsTreeValid(const BoundsTree& tree) { return isBoundsTreeValid(tree.getPrototype()); } template inline void treeValidCheck(const BoundsTree& tree) { if(!isBoundsTreeValid(tree)) throw "tree invalid!"; } }; ================================================ FILE: Physics3D/motion.h ================================================ #pragma once #include "math/linalg/vec.h" #include "math/rotation.h" #include "math/taylorExpansion.h" namespace P3D { struct TranslationalMotion { Taylor translation; inline TranslationalMotion() : translation{Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0)} {} inline TranslationalMotion(Vec3 velocity) : translation{velocity, Vec3(0.0, 0.0, 0.0)} {} inline TranslationalMotion(Vec3 velocity, Vec3 acceleration) : translation{velocity, acceleration} {} inline TranslationalMotion(const Taylor& translation) : translation(translation) {} inline Vec3 getVelocity() const { return translation[0]; } inline Vec3 getAcceleration() const { return translation[1]; } inline Vec3 getOffsetAfterDeltaT(double deltaT) const { return translation(deltaT); } inline TranslationalMotion operator-() const { return TranslationalMotion{-translation}; } }; inline TranslationalMotion operator+(const TranslationalMotion& first, const TranslationalMotion& second) { return TranslationalMotion(first.translation + second.translation); } inline TranslationalMotion& operator+=(TranslationalMotion& first, const TranslationalMotion& second) { first.translation += second.translation; return first; } inline TranslationalMotion operator-(const TranslationalMotion& first, const TranslationalMotion& second) { return TranslationalMotion(first.translation - second.translation); } inline TranslationalMotion& operator-=(TranslationalMotion& first, const TranslationalMotion& second) { first.translation -= second.translation; return first; } inline TranslationalMotion operator*(const TranslationalMotion& motion, double factor) { return TranslationalMotion(motion.translation * factor); } inline TranslationalMotion operator*(double factor, const TranslationalMotion& motion) { return motion * factor; } inline TranslationalMotion& operator*=(TranslationalMotion& motion, double factor) { motion.translation *= factor; return motion; } struct RotationalMotion { Taylor rotation; inline RotationalMotion() : rotation{Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0)} {} inline RotationalMotion(Vec3 angularVelocity) : rotation{angularVelocity, Vec3(0.0, 0.0, 0.0)} {} inline RotationalMotion(Vec3 angularVelocity, Vec3 angularAcceleration) : rotation{angularVelocity, angularAcceleration} {} inline RotationalMotion(const Taylor& rotation) : rotation(rotation) {} inline Vec3 getAngularVelocity() const { return rotation[0]; } inline Vec3 getAngularAcceleration() const { return rotation[1]; } inline Vec3 getVelocityOfPoint(Vec3 relativePoint) const { return getAngularVelocity() % relativePoint; } inline Vec3 getAccelerationOfPoint(Vec3 relativePoint) const { return getAngularAcceleration() % relativePoint + getAngularVelocity() % (getAngularVelocity() % relativePoint); } inline TranslationalMotion getTranslationalMotionOfPoint(Vec3 relativePoint) const { return TranslationalMotion(getVelocityOfPoint(relativePoint), getAccelerationOfPoint(relativePoint)); } inline Vec3 getRotationAfterDeltaT(double deltaT) const { return rotation(deltaT); } }; inline RotationalMotion operator*(const RotationalMotion& motion, double factor) { return RotationalMotion(motion.getAngularVelocity() * factor, motion.getAngularAcceleration() * factor); } inline RotationalMotion operator*(double factor, const RotationalMotion& motion) { return motion * factor; } struct Movement { Vec3 translation; Vec3 rotation; }; struct Motion { TranslationalMotion translation; RotationalMotion rotation; inline Motion() = default; inline Motion(Vec3 velocity, Vec3 angularVelocity) : translation(velocity), rotation(angularVelocity) {} inline Motion(Vec3 velocity, Vec3 angularVelocity, Vec3 acceleration, Vec3 angularAcceleration) : translation(velocity, acceleration), rotation(angularVelocity, angularAcceleration) {} inline Motion(TranslationalMotion translationMotion) : translation(translationMotion), rotation() {} inline Motion(RotationalMotion rotationMotion) : translation(), rotation(rotationMotion) {} inline Motion(TranslationalMotion translationMotion, RotationalMotion rotationMotion) : translation(translationMotion), rotation(rotationMotion) {} inline Vec3 getVelocityOfPoint(Vec3 relativePoint) const { return translation.getVelocity() + rotation.getVelocityOfPoint(relativePoint); } inline Vec3 getAccelerationOfPoint(Vec3 relativePoint) const { return translation.getAcceleration() + rotation.getAccelerationOfPoint(relativePoint); } inline TranslationalMotion getTranslationalMotionOfPoint(Vec3 relativePoint) const { return translation + rotation.getTranslationalMotionOfPoint(relativePoint); } inline Motion getMotionOfPoint(Vec3 relativePoint) const { return Motion( getTranslationalMotionOfPoint(relativePoint), rotation ); } inline Motion addRelativeMotion(const Motion& relativeMotion) const { return Motion( translation.getVelocity() + relativeMotion.translation.getVelocity(), rotation.getAngularVelocity() + relativeMotion.rotation.getAngularVelocity(), translation.getAcceleration() + relativeMotion.translation.getAcceleration() + rotation.getAngularVelocity() % relativeMotion.translation.getVelocity() * 2.0, rotation.getAngularAcceleration() + relativeMotion.rotation.getAngularAcceleration() + rotation.getAngularVelocity() % relativeMotion.rotation.getAngularVelocity() ); } inline Motion addOffsetRelativeMotion(Vec3 offset, const Motion& relativeMotion) const { return this->getMotionOfPoint(offset).addRelativeMotion(relativeMotion); } inline Movement getMovementAfterDeltaT(double deltaT) const { return Movement{translation.getOffsetAfterDeltaT(deltaT), rotation.getRotationAfterDeltaT(deltaT)}; } inline Vec3 getVelocity() const { return translation.getVelocity(); } inline Vec3 getAcceleration() const { return translation.getAcceleration(); } inline Vec3 getAngularVelocity() const { return rotation.getAngularVelocity(); } inline Vec3 getAngularAcceleration() const { return rotation.getAngularAcceleration(); } inline Vec6 getDerivAsVec6(int deriv) const { return join(translation.translation[deriv], rotation.rotation[deriv]); } }; inline Motion operator+(const TranslationalMotion& motionOfStart, const Motion& motionToTranslate) { return Motion(motionOfStart + motionToTranslate.translation, motionToTranslate.rotation); } inline TranslationalMotion localToGlobal(const Rotation& rot, const TranslationalMotion& motion) { return TranslationalMotion(motion.translation.transform([&rot](const Vec3& v) {return rot.localToGlobal(v); })); } inline TranslationalMotion globalToLocal(const Rotation& rot, const TranslationalMotion& motion) { return TranslationalMotion(motion.translation.transform([&rot](const Vec3& v) {return rot.globalToLocal(v); })); } inline RotationalMotion localToGlobal(const Rotation& rot, const RotationalMotion& motion) { return RotationalMotion(motion.rotation.transform([&rot](const Vec3& v) {return rot.localToGlobal(v); })); } inline RotationalMotion globalToLocal(const Rotation& rot, const RotationalMotion& motion) { return RotationalMotion(motion.rotation.transform([&rot](const Vec3& v) {return rot.globalToLocal(v); })); } inline Motion localToGlobal(const Rotation& rot, const Motion& motion) { return Motion(localToGlobal(rot, motion.translation), localToGlobal(rot, motion.rotation)); } inline Motion globalToLocal(const Rotation& rot, const Motion& motion) { return Motion(globalToLocal(rot, motion.translation), globalToLocal(rot, motion.rotation)); } }; ================================================ FILE: Physics3D/part.cpp ================================================ #include "part.h" #include "physical.h" #include "geometry/intersection.h" #include "misc/validityHelper.h" #include "misc/catchable_assert.h" #include #include #include #include "layer.h" namespace P3D { namespace { void recalculate(Part* part) { part->maxRadius = part->hitbox.getMaxRadius(); } void recalculateAndUpdateParent(Part* part, const Bounds& oldBounds) { recalculate(part); Physical* phys = part->getPhysical(); if(phys != nullptr) { phys->notifyPartPropertiesChanged(part); } if(part->layer != nullptr) part->layer->notifyPartBoundsUpdated(part, oldBounds); } }; Part::Part(const Shape& shape, const GlobalCFrame& position, const PartProperties& properties) : hitbox(shape), properties(properties), maxRadius(shape.getMaxRadius()), cframe(position) { } Part::Part(const Shape& shape, Part& attachTo, const CFrame& attach, const PartProperties& properties) : hitbox(shape), properties(properties), maxRadius(shape.getMaxRadius()), cframe(attachTo.cframe.localToGlobal(attach)) { attachTo.attach(this, attach); } Part::Part(const Shape& shape, Part& attachTo, HardConstraint* constraint, const CFrame& attachToParent, const CFrame& attachToThis, const PartProperties& properties) : hitbox(shape), properties(properties), maxRadius(shape.getMaxRadius()), cframe(attachTo.getCFrame().localToGlobal(attachToParent.localToGlobal(constraint->getRelativeCFrame()).localToGlobal(attachToThis))) { attachTo.attach(this, constraint, attachToParent, attachToThis); } Part::Part(Part&& other) noexcept : cframe(other.cframe), layer(other.layer), parent(other.parent), hitbox(std::move(other.hitbox)), maxRadius(other.maxRadius), properties(std::move(other.properties)) { if (parent != nullptr) parent->notifyPartStdMoved(&other, this); if (layer != nullptr) layer->notifyPartStdMoved(&other, this); other.parent = nullptr; other.layer = nullptr; } Part& Part::operator=(Part&& other) noexcept { this->cframe = other.cframe; this->layer = other.layer; this->parent = other.parent; this->hitbox = std::move(other.hitbox); this->maxRadius = other.maxRadius; this->properties = std::move(other.properties); if (parent != nullptr) parent->notifyPartStdMoved(&other, this); if (layer != nullptr) layer->notifyPartStdMoved(&other, this); other.parent = nullptr; other.layer = nullptr; return *this; } WorldPrototype* Part::getWorld() { if (layer == nullptr) return nullptr; return layer->parent->world; } Part::~Part() { this->removeFromWorld(); } int Part::getLayerID() const { return layer->getID(); } void Part::removeFromWorld() { Physical* partPhys = this->getPhysical(); if(partPhys) partPhys->removePart(this); if(this->layer) this->layer->removePart(this); } PartIntersection Part::intersects(const Part& other) const { CFrame relativeTransform = this->cframe.globalToLocal(other.cframe); std::optional result = intersectsTransformed(this->hitbox, other.hitbox, relativeTransform); if(result) { Position intersection = this->cframe.localToGlobal(result.value().intersection); Vec3 exitVector = this->cframe.localToRelative(result.value().exitVector); catchable_assert(isVecValid(exitVector)); return PartIntersection(intersection, exitVector); } return PartIntersection(); } BoundingBox Part::getLocalBounds() const { Vec3 v = Vec3(this->hitbox.scale[0], this->hitbox.scale[1], this->hitbox.scale[2]); return BoundingBox(-v, v); } BoundsTemplate Part::getBounds() const { BoundingBox boundsOfHitbox = this->hitbox.getBounds(this->cframe.getRotation()); assert(isVecValid(boundsOfHitbox.min)); assert(isVecValid(boundsOfHitbox.max)); return BoundsTemplate(boundsOfHitbox + getPosition()); } void Part::scale(double scaleX, double scaleY, double scaleZ) { Bounds oldBounds = this->getBounds(); this->hitbox = this->hitbox.scaled(scaleX, scaleY, scaleZ); recalculateAndUpdateParent(this, oldBounds); } void Part::setScale(const DiagonalMat3& scale) { Bounds oldBounds = this->getBounds(); this->hitbox.scale = scale; recalculateAndUpdateParent(this, oldBounds); } void Part::setCFrame(const GlobalCFrame& newCFrame) { Bounds oldBounds = this->getBounds(); Physical* partPhys = this->getPhysical(); if(partPhys) { partPhys->setPartCFrame(this, newCFrame); } else { this->cframe = newCFrame; } if(this->layer != nullptr) this->layer->notifyPartGroupBoundsUpdated(this, oldBounds); } Vec3 Part::getVelocity() const { return this->getMotion().getVelocity(); } Vec3 Part::getAngularVelocity() const { return this->getMotion().getAngularVelocity(); } Motion Part::getMotion() const { if(this->getPhysical() == nullptr) return Motion(); Motion parentsMotion = this->getPhysical()->getMotion(); if(this->isMainPart()) { return parentsMotion; } else { Vec3 offset = this->getAttachToMainPart().getPosition(); return parentsMotion.getMotionOfPoint(offset); } } void Part::setVelocity(Vec3 velocity) { Vec3 oldVel = this->getVelocity(); this->getMainPhysical()->motionOfCenterOfMass.translation.translation[0] += (velocity - oldVel); } void Part::setAngularVelocity(Vec3 angularVelocity) { Vec3 oldAngularVel = this->getAngularVelocity(); this->getMainPhysical()->motionOfCenterOfMass.rotation.rotation[0] += (angularVelocity - oldAngularVel); } void Part::setMotion(Vec3 velocity, Vec3 angularVelocity) { setAngularVelocity(angularVelocity); // angular velocity must come first, as it affects the velocity that setVelocity() uses setVelocity(velocity); } bool Part::isTerrainPart() const { return this->getPhysical() == nullptr; } const Shape& Part::getShape() const { return this->hitbox; } void Part::setShape(Shape newShape) { Bounds oldBounds = this->getBounds(); this->hitbox = std::move(newShape); recalculateAndUpdateParent(this, oldBounds); } void Part::translate(Vec3 translation) { Bounds oldBounds = this->getBounds(); Physical* phys = this->getPhysical(); if(phys) { phys->mainPhysical->translate(translation); } else { this->cframe += translation; } if(this->layer != nullptr) this->layer->notifyPartGroupBoundsUpdated(this, oldBounds); } double Part::getWidth() const { return this->hitbox.getWidth(); } double Part::getHeight() const { return this->hitbox.getHeight(); } double Part::getDepth() const { return this->hitbox.getDepth(); } void Part::setWidth(double newWidth) { Bounds oldBounds = this->getBounds(); this->hitbox.setWidth(newWidth); recalculateAndUpdateParent(this, oldBounds); } void Part::setHeight(double newHeight) { Bounds oldBounds = this->getBounds(); this->hitbox.setHeight(newHeight); recalculateAndUpdateParent(this, oldBounds); } void Part::setDepth(double newDepth) { Bounds oldBounds = this->getBounds(); this->hitbox.setDepth(newDepth); recalculateAndUpdateParent(this, oldBounds); } double Part::getFriction() { return this->properties.friction; } double Part::getDensity() { return this->properties.density; } double Part::getBouncyness() { return this->properties.bouncyness; } Vec3 Part::getConveyorEffect() { return this->properties.conveyorEffect; } void Part::setMass(double mass) { setDensity(mass / hitbox.getVolume()); // TODO update necessary? } void Part::setFriction(double friction) { this->properties.friction = friction; // TODO update necessary? } void Part::setDensity(double density) { this->properties.density = density; // TODO update necessary? } void Part::setBouncyness(double bouncyness) { this->properties.bouncyness = bouncyness; // TODO update necessary? } void Part::setConveyorEffect(const Vec3& conveyorEffect) { this->properties.conveyorEffect = conveyorEffect; // TODO update necessary? } void Part::applyForce(Vec3 relativeOrigin, Vec3 force) { Physical* phys = this->getPhysical(); assert(phys != nullptr); Vec3 originOffset = this->getPosition() - phys->mainPhysical->getPosition(); phys->mainPhysical->applyForce(originOffset + relativeOrigin, force); } void Part::applyForceAtCenterOfMass(Vec3 force) { Physical* phys = this->getPhysical(); assert(phys != nullptr); Vec3 originOffset = this->getCenterOfMass() - phys->mainPhysical->getPosition(); phys->mainPhysical->applyForce(originOffset, force); } void Part::applyMoment(Vec3 moment) { Physical* phys = this->getPhysical(); assert(phys != nullptr); phys->mainPhysical->applyMoment(moment); } static void mergePartLayers(Part* first, Part* second, const std::vector& layersOfFirst, const std::vector& layersOfSecond) { for(const FoundLayerRepresentative& l1 : layersOfFirst) { for(const FoundLayerRepresentative& l2 : layersOfSecond) { if(l1.layer == l2.layer) { l1.layer->mergeGroups(l1.part, l2.part); break; } } } } static void updateGroupBounds(std::vector& layers, std::vector& bounds) { for(size_t i = 0; i < layers.size(); i++) { layers[i].layer->notifyPartGroupBoundsUpdated(layers[i].part, bounds[i]); } } static std::vector getAllPartsInPhysical(Part* rep) { std::vector result; Physical* repPhys = rep->getPhysical(); if(repPhys != nullptr) { repPhys->mainPhysical->forEachPart([&result](Part& part) { result.push_back(&part); }); } else { result.push_back(rep); } return result; } static void addAllToGroupLayer(Part* layerOwner, const std::vector& partsToAdd) { layerOwner->layer->addAllToGroup(partsToAdd.begin(), partsToAdd.end(), layerOwner); for(Part* p : partsToAdd) { p->layer = layerOwner->layer; } } template static void mergeLayersAround(Part* first, Part* second, PhysicalMergeFunc mergeFunc) { if(second->layer != nullptr) { std::vector layersOfSecond = findAllLayersIn(second); std::vector boundsOfSecondParts(layersOfSecond.size()); for(size_t i = 0; i < layersOfSecond.size(); i++) { boundsOfSecondParts[i] = layersOfSecond[i].part->getBounds(); } if(first->layer != nullptr) { std::vector layersOfFirst = findAllLayersIn(first); mergeFunc(); updateGroupBounds(layersOfSecond, boundsOfSecondParts); mergePartLayers(first, second, layersOfFirst, layersOfSecond); } else { std::vector partsInFirst = getAllPartsInPhysical(first); mergeFunc(); updateGroupBounds(layersOfSecond, boundsOfSecondParts); addAllToGroupLayer(second, partsInFirst); } } else { if(first->layer != nullptr) { std::vector partsInSecond = getAllPartsInPhysical(second); mergeFunc(); addAllToGroupLayer(first, partsInSecond); } else { mergeFunc(); } } } void Part::attach(Part* other, const CFrame& relativeCFrame) { mergeLayersAround(this, other, [&]() { Physical* partPhys = this->ensureHasPhysical(); partPhys->attachPart(other, this->transformCFrameToParent(relativeCFrame)); }); } void Part::attach(Part* other, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat) { mergeLayersAround(this, other, [&]() { Physical* partPhys = this->ensureHasPhysical(); partPhys->attachPart(other, constraint, attachToThis, attachToThat); }); } void Part::detach() { if(this->parent == nullptr) throw std::logic_error("No physical to detach from!"); this->parent->detachPart(this); if(this->layer != nullptr) { this->layer->moveOutOfGroup(this); } } Physical* Part::getPhysical() const { return this->parent; } void Part::setRigidBodyPhysical(Physical* phys) { this->parent = phys; this->parent->rigidBody.mainPart->parent = phys; for(AttachedPart& p : this->parent->rigidBody.parts) { p.part->parent = phys; } } MotorizedPhysical* Part::getMainPhysical() const { Physical* phys = this->getPhysical(); return phys->mainPhysical; } Physical* Part::ensureHasPhysical() { if(this->parent == nullptr) { this->parent = new MotorizedPhysical(this); } return this->parent; } bool Part::hasAttachedParts() const { Physical* thisPhys = this->getPhysical(); return thisPhys != nullptr && !thisPhys->rigidBody.parts.empty(); } CFrame& Part::getAttachToMainPart() const { assert(!this->isMainPart()); return this->parent->rigidBody.getAttachFor(this).attachment; } CFrame Part::transformCFrameToParent(const CFrame& cframeRelativeToPart) const { if(this->isMainPart()) { return cframeRelativeToPart; } else { return this->getAttachToMainPart().localToGlobal(cframeRelativeToPart); } } bool Part::isMainPart() const { return this->parent == nullptr || this->parent->rigidBody.mainPart == this; } void Part::makeMainPart() { if(!this->isMainPart()) { this->parent->makeMainPart(this); } } bool Part::isValid() const { assert(std::isfinite(hitbox.getVolume())); assert(std::isfinite(maxRadius)); assert(std::isfinite(properties.density)); assert(std::isfinite(properties.friction)); assert(std::isfinite(properties.bouncyness)); assert(isVecValid(properties.conveyorEffect)); return true; } }; ================================================ FILE: Physics3D/part.h ================================================ #pragma once // this is a central class to everything else, but applications using the library don't need to include all these, as they are implementation details. namespace P3D { class Part; class HardConstraint; class RigidBody; class Physical; class ConnectedPhysical; class MotorizedPhysical; class WorldLayer; class WorldPrototype; }; #include "geometry/shape.h" #include "math/linalg/mat.h" #include "math/position.h" #include "math/globalCFrame.h" #include "math/bounds.h" #include "motion.h" namespace P3D { struct PartProperties { double density; double friction; double bouncyness; /* This is extra velocity that should be added to any colission if this part is anchored, this gives the velocity of another part sliding on top of it, with perfect friction In other words, this is the desired relative velocity for there to be no friction */ Vec3 conveyorEffect{0, 0, 0}; }; struct PartIntersection { bool intersects; Position intersection; Vec3 exitVector; PartIntersection() : intersects(false) {} PartIntersection(const Position& intersection, const Vec3& exitVector) : intersects(true), intersection(intersection), exitVector(exitVector) {} }; class Part { friend class RigidBody; friend class Physical; friend class ConnectedPhysical; friend class MotorizedPhysical; friend class WorldPrototype; friend class ConstraintGroup; GlobalCFrame cframe; Physical* parent = nullptr; public: WorldLayer* layer = nullptr; Shape hitbox; double maxRadius; PartProperties properties; Part() = default; Part(const Shape& shape, const GlobalCFrame& position, const PartProperties& properties); Part(const Shape& shape, Part& attachTo, const CFrame& attach, const PartProperties& properties); Part(const Shape& shape, Part& attachTo, HardConstraint* constraint, const CFrame& attachToParent, const CFrame& attachToThis, const PartProperties& properties); ~Part(); Part(const Part& other) = delete; Part& operator=(const Part& other) = delete; Part(Part&& other) noexcept; Part& operator=(Part&& other) noexcept; WorldPrototype* getWorld(); PartIntersection intersects(const Part& other) const; void scale(double scaleX, double scaleY, double scaleZ); void setScale(const DiagonalMat3& scale); BoundsTemplate getBounds() const; BoundingBox getLocalBounds() const; Position getPosition() const { return cframe.getPosition(); } double getMass() const { return hitbox.getVolume() * properties.density; } void setMass(double mass); Vec3 getLocalCenterOfMass() const { return hitbox.getCenterOfMass(); } Position getCenterOfMass() const { return cframe.localToGlobal(this->getLocalCenterOfMass()); } SymmetricMat3 getInertia() const { return hitbox.getInertia() * properties.density; } const GlobalCFrame& getCFrame() const { return cframe; } void setCFrame(const GlobalCFrame& newCFrame); Vec3 getVelocity() const; Vec3 getAngularVelocity() const; Motion getMotion() const; // does not modify angular velocity void setVelocity(Vec3 velocity); // modifies velocity void setAngularVelocity(Vec3 angularVelocity); void setMotion(Vec3 velocity, Vec3 angularVelocity); bool isTerrainPart() const; bool isMainPart() const; void makeMainPart(); const Shape& getShape() const; void setShape(Shape newShape); void translate(Vec3 translation); double getWidth() const; double getHeight() const; double getDepth() const; void setWidth(double newWidth); void setHeight(double newHeight); void setDepth(double newDepth); double getFriction(); double getDensity(); double getBouncyness(); Vec3 getConveyorEffect(); void setFriction(double friction); void setDensity(double density); void setBouncyness(double bouncyness); void setConveyorEffect(const Vec3& conveyorEffect); void applyForce(Vec3 relativeOrigin, Vec3 force); void applyForceAtCenterOfMass(Vec3 force); void applyMoment(Vec3 moment); int getLayerID() const; CFrame& getAttachToMainPart() const; CFrame transformCFrameToParent(const CFrame& cframeRelativeToPart) const; Physical* getPhysical() const; void setRigidBodyPhysical(Physical* phys); MotorizedPhysical* getMainPhysical() const; Physical* ensureHasPhysical(); bool hasAttachedParts() const; void attach(Part* other, const CFrame& relativeCFrame); void attach(Part* other, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat); void detach(); void removeFromWorld(); bool isValid() const; }; }; ================================================ FILE: Physics3D/physical.cpp ================================================ #include "physical.h" #include "inertia.h" #include "world.h" #include "math/linalg/mat.h" #include "math/linalg/trigonometry.h" #include "misc/debug.h" #include "misc/validityHelper.h" #include "misc/unreachable.h" #include "layer.h" #include #include #include /* ===== Physical Structure ===== */ namespace P3D { #pragma region structure Physical::Physical(Part* part, MotorizedPhysical* mainPhysical) : rigidBody(part), mainPhysical(mainPhysical) { assert(part->parent == nullptr); part->parent = this; } Physical::Physical(RigidBody&& rigidBody, MotorizedPhysical* mainPhysical) : rigidBody(std::move(rigidBody)), mainPhysical(mainPhysical) { for(Part& p : this->rigidBody) { assert(p.parent == nullptr); p.parent = this; } } Physical::Physical(Physical&& other) noexcept : rigidBody(std::move(other.rigidBody)), mainPhysical(other.mainPhysical), childPhysicals(std::move(other.childPhysicals)) { this->rigidBody.mainPart->setRigidBodyPhysical(this); for(ConnectedPhysical& p : this->childPhysicals) { p.parent = this; } } Physical& Physical::operator=(Physical&& other) noexcept { this->rigidBody = std::move(other.rigidBody); this->mainPhysical = other.mainPhysical; this->childPhysicals = std::move(other.childPhysicals); this->rigidBody.mainPart->setRigidBodyPhysical(this); for(ConnectedPhysical& p : this->childPhysicals) { p.parent = this; } return *this; } ConnectedPhysical::ConnectedPhysical(RigidBody&& rigidBody, Physical* parent, HardPhysicalConnection&& connectionToParent) : Physical(std::move(rigidBody), parent->mainPhysical), parent(parent), connectionToParent(std::move(connectionToParent)) {} ConnectedPhysical::ConnectedPhysical(Physical&& phys, Physical* parent, HardPhysicalConnection&& connectionToParent) : Physical(std::move(phys)), parent(parent), connectionToParent(std::move(connectionToParent)) {} ConnectedPhysical::ConnectedPhysical(Physical&& phys, Physical* parent, HardConstraint* constraintWithParent, const CFrame& attachOnThis, const CFrame& attachOnParent) : Physical(std::move(phys)), parent(parent), connectionToParent(std::unique_ptr(constraintWithParent), attachOnThis, attachOnParent) {} MotorizedPhysical::MotorizedPhysical(Part* mainPart) : Physical(mainPart, this) { refreshPhysicalProperties(); } MotorizedPhysical::MotorizedPhysical(RigidBody&& rigidBody) : Physical(std::move(rigidBody), this) { refreshPhysicalProperties(); } MotorizedPhysical::MotorizedPhysical(Physical&& movedPhys) : Physical(std::move(movedPhys)) { this->setMainPhysicalRecursive(this); refreshPhysicalProperties(); } void Physical::makeMainPart(Part* newMainPart) { if (rigidBody.getMainPart() == newMainPart) { Debug::logWarn("Attempted to replace mainPart with mainPart"); return; } AttachedPart& atPart = rigidBody.getAttachFor(newMainPart); makeMainPart(atPart); } void Physical::makeMainPart(AttachedPart& newMainPart) { CFrame newCenterCFrame = rigidBody.makeMainPart(newMainPart); // Update attached physicals for(ConnectedPhysical& connectedPhys : childPhysicals) { connectedPhys.connectionToParent.attachOnParent = newCenterCFrame.globalToLocal(connectedPhys.connectionToParent.attachOnParent); } if(!isMainPhysical()) { ConnectedPhysical* self = (ConnectedPhysical*) this; self->connectionToParent.attachOnChild = newCenterCFrame.globalToLocal(self->connectionToParent.attachOnChild); } } template static inline bool liesInVector(const std::vector& vec, const T* ptr) { return &vec[0] <= ptr && &vec[0]+vec.size() > ptr; } /* We will build a new tree, starting from a copy of the current physical S is the physical we are currently adding a child to OS is old location of S, which is now invalid and which will be replaced in the next iteration P (for parent) is the old parent of S, to be moved to be a child of S T (for trash) is the physical currently being replaced by P We wish to make S the new Main Physical M / \ /\ P / \ S / \ Split S off from the tree Add a new empty node to S, call it T . / \ S P /|\ / \ T OS Copy P to T, reversing OS's attachment S /|\ . T / \ / \ P OS T is the new S OS is the new T P is the new OS P's parent is the new P if P has no parent, then the separate tree of S is copied into it and is the new mainPhysical */ void ConnectedPhysical::makeMainPhysical() { Physical* P = this->parent; Physical newTop = std::move(*this); ConnectedPhysical* T = nullptr; ConnectedPhysical* OS = this; Physical* S = &newTop; // OS's Physical fields are invalid, but it's ConnectedPhysical fields are still valid bool firstRun = true; while(true) { bool PIsMain = P->isMainPhysical(); // this part moves P down to be the child of S // swap attachOnParent and attachOnThis and use inverted constraint if(firstRun) { S->childPhysicals.push_back(ConnectedPhysical(std::move(*P), S, std::move(*OS).connectionToParent.inverted())); T = &S->childPhysicals.back(); firstRun = false; } else { *T = ConnectedPhysical(std::move(*P), S, std::move(*OS).connectionToParent.inverted()); } // at this point, P::Physical is no longer valid, but the ConnectedPhysical's fields can still be used S = T; T = OS; if(!PIsMain) { ConnectedPhysical* OP = static_cast(P); OS = OP; P = OP->parent; } else { break; } } S->childPhysicals.remove(std::move(*T)); // remove the last trash Physical *P = std::move(newTop); MotorizedPhysical* OP = static_cast(P); OP->refreshPhysicalProperties(); } std::size_t ConnectedPhysical::getIndexInParent() const { return &this->parent->childPhysicals[0] - this; } RelativeMotion ConnectedPhysical::getRelativeMotionBetweenParentAndSelf() const { return connectionToParent.getRelativeMotion(); } bool Physical::isMainPhysical() const { Physical* ptr = mainPhysical; return ptr == this; } MotorizedPhysical* Physical::makeMainPhysical() { if(this->isMainPhysical()) { return static_cast(this); } else { MotorizedPhysical* main = this->mainPhysical; static_cast(this)->makeMainPhysical(); return main; } } void Physical::removeChild(ConnectedPhysical* child) { assert(liesInVector(this->childPhysicals, child)); *child = std::move(childPhysicals.back()); childPhysicals.pop_back(); } void Physical::attachPhysical(Physical* phys, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat) { if(this->mainPhysical == phys->mainPhysical) { throw std::invalid_argument("Attempting to create HardConstraint loop"); } this->attachPhysical(phys->makeMainPhysical(), constraint, attachToThis, attachToThat); } static void removePhysicalFromList(std::vector& physicals, MotorizedPhysical* physToRemove) { for(MotorizedPhysical*& item : physicals) { if(item == physToRemove) { item = std::move(physicals.back()); physicals.pop_back(); return; } } unreachable(); // No physical found to remove! } void Physical::attachPhysical(MotorizedPhysical* phys, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat) { WorldPrototype* world = this->getWorld(); if(world != nullptr) { if(mainPhysical->getWorld() != nullptr) { assert(mainPhysical->getWorld() == world); removePhysicalFromList(world->physicals, mainPhysical); } } ConnectedPhysical childToAdd(std::move(*phys), this, constraint, attachToThat, attachToThis); childPhysicals.push_back(std::move(childToAdd)); ConnectedPhysical& p = childPhysicals.back(); p.parent = this; p.setMainPhysicalRecursive(this->mainPhysical); delete phys; childPhysicals.back().refreshCFrameRecursive(); mainPhysical->refreshPhysicalProperties(); } void Physical::attachPart(Part* part, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat) { Physical* partPhys = part->getPhysical(); if(partPhys) { attachPhysical(partPhys, constraint, attachToThis, part->transformCFrameToParent(attachToThat)); } else { WorldPrototype* world = this->getWorld(); if(world != nullptr) { world->onPartAdded(part); } childPhysicals.push_back(ConnectedPhysical(Physical(part, this->mainPhysical), this, constraint, attachToThat, attachToThis)); childPhysicals.back().refreshCFrame(); } mainPhysical->refreshPhysicalProperties(); } static Physical* findPhysicalParent(Physical* findIn, const ConnectedPhysical* toBeFound) { if(liesInVector(findIn->childPhysicals, toBeFound)) { return findIn; } for(ConnectedPhysical& p : findIn->childPhysicals) { Physical* result = findPhysicalParent(&p, toBeFound); if(result != nullptr) { return result; } } return nullptr; } void Physical::attachPhysical(Physical* phys, const CFrame& attachment) { attachPhysical(phys->makeMainPhysical(), attachment); } void Physical::attachPhysical(MotorizedPhysical* phys, const CFrame& attachment) { this->childPhysicals.reserve(this->childPhysicals.size() + phys->childPhysicals.size()); phys->rigidBody.mainPart->setRigidBodyPhysical(this); this->rigidBody.attach(std::move(phys->rigidBody), attachment); for(ConnectedPhysical& conPhys : phys->childPhysicals) { this->childPhysicals.push_back(std::move(conPhys)); ConnectedPhysical& conPhysOnThis = this->childPhysicals.back(); conPhysOnThis.connectionToParent.attachOnParent = attachment.localToGlobal(conPhysOnThis.connectionToParent.attachOnParent); conPhysOnThis.parent = this; conPhysOnThis.setMainPhysicalRecursive(this->mainPhysical); conPhysOnThis.refreshCFrameRecursive(); } WorldPrototype* world = this->getWorld(); if(world != nullptr) { if(phys->getWorld() != nullptr) { assert(phys->getWorld() == world); removePhysicalFromList(world->physicals, phys->mainPhysical); } } delete phys; mainPhysical->refreshPhysicalProperties(); } void Physical::attachPart(Part* part, const CFrame& attachment) { Physical* partPhys = part->getPhysical(); if(partPhys != nullptr) { // part is already in a physical if(partPhys->mainPhysical == this->mainPhysical) { throw std::invalid_argument("Part already attached to this physical!"); } // attach other part's entire physical if(part->isMainPart()) { attachPhysical(partPhys, attachment); } else { CFrame newAttach = attachment.localToGlobal(~part->getAttachToMainPart()); attachPhysical(partPhys, newAttach); } } else { WorldPrototype* world = this->getWorld(); if(world != nullptr) { world->onPartAdded(part); } part->parent = this; rigidBody.attach(part, attachment); } this->mainPhysical->refreshPhysicalProperties(); } void Physical::detachAllChildPhysicals() { WorldPrototype* world = this->getWorld(); for(ConnectedPhysical& child : childPhysicals) { MotorizedPhysical* newPhys = new MotorizedPhysical(std::move(static_cast(child))); if(world != nullptr) { world->notifyPhysicalHasBeenSplit(this->mainPhysical, newPhys); } } childPhysicals.clear(); // calls the destructors on all (now invalid) children, deleting the constraints in the process } void Physical::detachFromRigidBody(Part* part) { part->parent = nullptr; rigidBody.detach(part); WorldPrototype* world = this->getWorld(); // TODO? } void Physical::detachFromRigidBody(AttachedPart&& part) { part.part->parent = nullptr; rigidBody.detach(std::move(part)); } static void computeInternalRelativeMotionTree(MonotonicTreeBuilder& builder, MonotonicTreeNode& curNode, const ConnectedPhysical& conPhys, const RelativeMotion& motionOfParent) { RelativeMotion motionOfSelf = motionOfParent + conPhys.getRelativeMotionBetweenParentAndSelf(); curNode.value = motionOfSelf.extendEnd(conPhys.rigidBody.localCenterOfMass); std::size_t size = conPhys.childPhysicals.size(); if(size == 0) { curNode.children = nullptr; } else { curNode.children = builder.alloc(size); for(std::size_t i = 0; i < size; i++) { computeInternalRelativeMotionTree(builder, curNode.children[i], conPhys.childPhysicals[i], motionOfSelf); } } } InternalMotionTree MotorizedPhysical::getInternalRelativeMotionTree(UnmanagedArray>&& mem) const noexcept { MonotonicTreeBuilder builder(std::move(mem)); std::size_t mainSize = this->childPhysicals.size(); MonotonicTreeNode* childNodes = builder.alloc(mainSize); for(std::size_t i = 0; i < mainSize; i++) { MonotonicTreeNode& curNode = childNodes[i]; const ConnectedPhysical& conPhys = this->childPhysicals[i]; RelativeMotion motionOfPrimallyAttached = conPhys.getRelativeMotionBetweenParentAndSelf(); curNode.value = motionOfPrimallyAttached.extendEnd(conPhys.rigidBody.localCenterOfMass); std::size_t size = conPhys.childPhysicals.size(); if(size == 0) { curNode.children = nullptr; } else { curNode.children = builder.alloc(size); for(std::size_t j = 0; j < size; j++) { computeInternalRelativeMotionTree(builder, curNode.children[j], conPhys.childPhysicals[j], motionOfPrimallyAttached); } } } return InternalMotionTree(this, MonotonicTree(std::move(builder))); } COMMotionTree MotorizedPhysical::getCOMMotionTree(UnmanagedArray>&& mem) const noexcept { return this->getInternalRelativeMotionTree(std::move(mem)).normalizeCenterOfMass(); } void Physical::detachPartAssumingMultipleParts(Part* part) { assert(part->getPhysical() == this); assert(rigidBody.getPartCount() > 1); if(part == rigidBody.getMainPart()) { AttachedPart& newMainPartAndLaterOldPart = rigidBody.parts.back(); makeMainPart(newMainPartAndLaterOldPart); // now points to old part detachFromRigidBody(std::move(newMainPartAndLaterOldPart)); } else { detachFromRigidBody(part); } this->mainPhysical->refreshPhysicalProperties(); } void Physical::detachPart(Part* part) { assert(part->getPhysical() == this); WorldPrototype* world = this->getWorld(); if(rigidBody.getPartCount() == 1) { assert(part == rigidBody.getMainPart()); // we have to disconnect this from other physicals as we're detaching the last part in the physical this->detachAllChildPhysicals(); MotorizedPhysical* mainPhys = this->mainPhysical; // save main physical because it'll get deleted by parent->detachChild() if(this != mainPhys) { ConnectedPhysical& self = static_cast(*this); MotorizedPhysical* newPhys = new MotorizedPhysical(std::move(static_cast(self))); if(world != nullptr) { world->notifyPhysicalHasBeenSplit(mainPhys, newPhys); } self.parent->childPhysicals.remove(std::move(self)); // double move, but okay, since remove really only needs the address of self mainPhys->refreshPhysicalProperties(); } // After this, self, and hence also *this* is no longer valid! // It has been removed } else { detachPartAssumingMultipleParts(part); MotorizedPhysical* newPhys = new MotorizedPhysical(part); part->parent = newPhys; if(world != nullptr) { world->physicals.push_back(newPhys); } } } void Physical::removePart(Part* part) { assert(part->getPhysical() == this); if(rigidBody.getPartCount() == 1) { assert(part == rigidBody.getMainPart()); // we have to disconnect this from other physicals as we're removing the last part in the physical this->detachAllChildPhysicals(); MotorizedPhysical* mainPhys = this->mainPhysical; // save main physical because it'll get deleted by parent->detachChild() if(this != mainPhys) { ConnectedPhysical& self = static_cast(*this); self.parent->childPhysicals.remove(std::move(self)); mainPhys->refreshPhysicalProperties(); } else { WorldPrototype* mainPhysWorld = this->getWorld(); if(mainPhysWorld != nullptr) { removePhysicalFromList(mainPhysWorld->physicals, mainPhys); } delete mainPhys; } // After this, self, and hence also *this* is no longer valid! // It has been removed } else { detachPartAssumingMultipleParts(part); } part->parent = nullptr; } // TODO: this seems to need to update the encompassing MotorizedPhysical as well void Physical::notifyPartPropertiesChanged(Part* part) { rigidBody.refreshWithNewParts(); } void Physical::notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept { rigidBody.notifyPartStdMoved(oldPartPtr, newPartPtr); } void Physical::updateConstraints(double deltaT) { for(ConnectedPhysical& p : childPhysicals) { p.connectionToParent.update(deltaT); p.updateConstraints(deltaT); } } void Physical::updateAttachedPhysicals() { for(ConnectedPhysical& p : childPhysicals) { p.refreshCFrame(); p.updateAttachedPhysicals(); } } void Physical::setCFrame(const GlobalCFrame& newCFrame) { if(this->isMainPhysical()) { MotorizedPhysical* motorThis = static_cast(this); motorThis->setCFrame(newCFrame); } else { ConnectedPhysical* connectedThis = static_cast(this); connectedThis->setCFrame(newCFrame); } } void ConnectedPhysical::setCFrame(const GlobalCFrame& newCFrame) { CFrame relativeCFrameToParent = this->getRelativeCFrameToParent(); GlobalCFrame resultingCFrame = newCFrame.localToGlobal(~relativeCFrameToParent); this->parent->setCFrame(resultingCFrame); } void MotorizedPhysical::setCFrame(const GlobalCFrame& newCFrame) { rigidBody.setCFrame(newCFrame); for(ConnectedPhysical& conPhys : childPhysicals) { conPhys.refreshCFrameRecursive(); } } void Physical::setPartCFrame(Part* part, const GlobalCFrame& newCFrame) { if(part == rigidBody.mainPart) { setCFrame(newCFrame); } else { CFrame attach = rigidBody.getAttachFor(part).attachment; GlobalCFrame newMainCFrame = newCFrame.localToGlobal(~attach); setCFrame(newMainCFrame); } } #pragma endregion /* ===== Refresh functions ===== */ #pragma region refresh void MotorizedPhysical::rotateAroundCenterOfMass(const Rotation& rotation) { rigidBody.rotateAroundLocalPoint(totalCenterOfMass, rotation); } void Physical::translateUnsafeRecursive(const Vec3Fix& translation) { rigidBody.translate(translation); for(ConnectedPhysical& conPhys : childPhysicals) { conPhys.translateUnsafeRecursive(translation); } } void MotorizedPhysical::translate(const Vec3& translation) { translateUnsafeRecursive(translation); } void MotorizedPhysical::refreshPhysicalProperties() { ALLOCA_COMMotionTree(cache, this, size); this->totalCenterOfMass = cache.centerOfMass; this->totalMass = cache.totalMass; SymmetricMat3 totalInertia = cache.getInertia(); this->forceResponse = SymmetricMat3::IDENTITY() * (1 / cache.totalMass); this->momentResponse = ~totalInertia; } void ConnectedPhysical::refreshCFrame() { GlobalCFrame newPosition = parent->getCFrame().localToGlobal(getRelativeCFrameToParent()); rigidBody.setCFrame(newPosition); } void ConnectedPhysical::refreshCFrameRecursive() { this->refreshCFrame(); for(ConnectedPhysical& childPhys : childPhysicals) { childPhys.refreshCFrameRecursive(); } } void MotorizedPhysical::fullRefreshOfConnectedPhysicals() { for(ConnectedPhysical& conPhys : childPhysicals) { conPhys.refreshCFrameRecursive(); } } #pragma endregion /* ===== Update ===== */ #pragma region update void MotorizedPhysical::update(double deltaT) { Vec3 accel = forceResponse * totalForce * deltaT; Vec3 localMoment = getCFrame().relativeToLocal(totalMoment); Vec3 localRotAcc = momentResponse * localMoment * deltaT; Vec3 rotAcc = getCFrame().localToRelative(localRotAcc); totalForce = Vec3(); totalMoment = Vec3(); motionOfCenterOfMass.translation.translation[0] += accel; motionOfCenterOfMass.rotation.rotation[0] += rotAcc; Vec3 oldCenterOfMass = this->totalCenterOfMass; Vec3 angularMomentumBefore = getTotalAngularMomentum(); updateConstraints(deltaT); refreshPhysicalProperties(); Vec3 deltaCOM = this->totalCenterOfMass - oldCenterOfMass; Vec3 movementOfCenterOfMass = motionOfCenterOfMass.getVelocity() * deltaT + accel * deltaT * deltaT * 0.5 - getCFrame().localToRelative(deltaCOM); rotateAroundCenterOfMass(Rotation::fromRotationVector(motionOfCenterOfMass.getAngularVelocity() * deltaT)); translateUnsafeRecursive(movementOfCenterOfMass); Vec3 angularMomentumAfter = getTotalAngularMomentum(); SymmetricMat3 globalMomentResponse = getCFrame().getRotation().localToGlobal(momentResponse); Vec3 deltaAngularVelocity = globalMomentResponse * (angularMomentumAfter - angularMomentumBefore); this->motionOfCenterOfMass.rotation.rotation[0] -= deltaAngularVelocity; updateAttachedPhysicals(); } #pragma endregion /* ===== application of forces and the like */ #pragma region apply void MotorizedPhysical::applyForceAtCenterOfMass(Vec3 force) { assert(isVecValid(force)); totalForce += force; Debug::logVector(getCenterOfMass(), force, Debug::FORCE); } void MotorizedPhysical::applyForce(Vec3Relative origin, Vec3 force) { assert(isVecValid(origin)); assert(isVecValid(force)); totalForce += force; Debug::logVector(getCenterOfMass() + origin, force, Debug::FORCE); applyMoment(origin % force); } void MotorizedPhysical::applyMoment(Vec3 moment) { assert(isVecValid(moment)); totalMoment += moment; Debug::logVector(getCenterOfMass(), moment, Debug::MOMENT); } void MotorizedPhysical::applyImpulseAtCenterOfMass(Vec3 impulse) { assert(isVecValid(impulse)); Debug::logVector(getCenterOfMass(), impulse, Debug::IMPULSE); motionOfCenterOfMass.translation.translation[0] += forceResponse * impulse; } void MotorizedPhysical::applyImpulse(Vec3Relative origin, Vec3Relative impulse) { assert(isVecValid(origin)); assert(isVecValid(impulse)); Debug::logVector(getCenterOfMass() + origin, impulse, Debug::IMPULSE); motionOfCenterOfMass.translation.translation[0] += forceResponse * impulse; Vec3 angularImpulse = origin % impulse; applyAngularImpulse(angularImpulse); } void MotorizedPhysical::applyAngularImpulse(Vec3 angularImpulse) { assert(isVecValid(angularImpulse)); Debug::logVector(getCenterOfMass(), angularImpulse, Debug::ANGULAR_IMPULSE); Vec3 localAngularImpulse = getCFrame().relativeToLocal(angularImpulse); Vec3 localRotAcc = momentResponse * localAngularImpulse; Vec3 rotAcc = getCFrame().localToRelative(localRotAcc); motionOfCenterOfMass.rotation.rotation[0] += rotAcc; } void MotorizedPhysical::applyDragAtCenterOfMass(Vec3 drag) { assert(isVecValid(drag)); Debug::logVector(getCenterOfMass(), drag, Debug::POSITION); translate(forceResponse * drag); } void MotorizedPhysical::applyDrag(Vec3Relative origin, Vec3Relative drag) { assert(isVecValid(origin)); assert(isVecValid(drag)); Debug::logVector(getCenterOfMass() + origin, drag, Debug::POSITION); translateUnsafeRecursive(forceResponse * drag); Vec3 angularDrag = origin % drag; applyAngularDrag(angularDrag); } void MotorizedPhysical::applyAngularDrag(Vec3 angularDrag) { assert(isVecValid(angularDrag)); Debug::logVector(getCenterOfMass(), angularDrag, Debug::INFO_VEC); Vec3 localAngularDrag = getCFrame().relativeToLocal(angularDrag); Vec3 localRotAcc = momentResponse * localAngularDrag; Vec3 rotAcc = getCFrame().localToRelative(localRotAcc); rotateAroundCenterOfMass(Rotation::fromRotationVector(rotAcc)); } void Physical::applyDragToPhysical(Vec3 origin, Vec3 drag) { Vec3 COMOffset = mainPhysical->getCenterOfMass() - getCFrame().getPosition(); mainPhysical->applyDrag(origin + COMOffset, drag); } void Physical::applyImpulseToPhysical(Vec3 origin, Vec3 impulse) { Vec3 COMOffset = mainPhysical->getCenterOfMass() - getCFrame().getPosition(); mainPhysical->applyImpulse(origin + COMOffset, impulse); } void Physical::applyForceToPhysical(Vec3 origin, Vec3 force) { Vec3 COMOffset = mainPhysical->getCenterOfMass() - getCFrame().getPosition(); mainPhysical->applyForce(origin + COMOffset, force); } #pragma endregion #pragma region getters WorldPrototype* Physical::getWorld() const { return this->rigidBody.mainPart->getWorld(); } double Physical::getVelocityKineticEnergy() const { return rigidBody.mass * lengthSquared(getMotionOfCenterOfMass().getVelocity()) / 2; } double Physical::getAngularKineticEnergy() const { Vec3 localAngularVel = getCFrame().relativeToLocal(getMotionOfCenterOfMass().getAngularVelocity()); return (rigidBody.inertia * localAngularVel) * localAngularVel / 2; } double Physical::getKineticEnergy() const { return getVelocityKineticEnergy() + getAngularKineticEnergy(); } CFrame Physical::getRelativeCFrameToMain() const { return mainPhysical->getCFrame().globalToLocal(this->getCFrame()); } Vec3 Physical::localToMain(const Vec3Local& vec) const { Position globalPos = this->getCFrame().localToGlobal(vec); return mainPhysical->getCFrame().globalToLocal(globalPos); } Vec3 Physical::localToMainCOMRelative(const Vec3Local& vec) const { Position globalPos = this->getCFrame().localToGlobal(vec); return mainPhysical->getCFrame().globalToRelative(globalPos) - mainPhysical->totalCenterOfMass; } /*Motion MotorizedPhysical::getMotionOfCenterOfMass() const { return this->motionOfCenterOfMass; }*/ Vec3 MotorizedPhysical::getVelocityOfCenterOfMass() const { return this->motionOfCenterOfMass.getVelocity(); } /* Computes the force->acceleration transformation matrix Such that: a = M*F */ SymmetricMat3 MotorizedPhysical::getResponseMatrix(const Vec3Local& r) const { Mat3 crossMat = createCrossProductEquivalent(r); SymmetricMat3 rotationFactor = multiplyLeftRight(momentResponse , crossMat); return forceResponse + rotationFactor; } Mat3 MotorizedPhysical::getResponseMatrix(const Vec3Local& actionPoint, const Vec3Local& responsePoint) const { Mat3 actionCross = createCrossProductEquivalent(actionPoint); Mat3 responseCross = createCrossProductEquivalent(responsePoint); Mat3 rotationFactor = responseCross * momentResponse * actionCross; return Mat3(forceResponse) - rotationFactor; } double MotorizedPhysical::getInertiaOfPointInDirectionLocal(const Vec3Local& localPoint, const Vec3Local& localDirection) const { SymmetricMat3 accMat = getResponseMatrix(localPoint); Vec3 force = localDirection; Vec3 accel = accMat * force; double accelInForceDir = accel * localDirection / lengthSquared(localDirection); return 1 / accelInForceDir; /*SymmetricMat3 accelToForceMat = ~accMat; Vec3 imaginaryForceForAcceleration = accelToForceMat * direction; double forcePerAccelRatio = imaginaryForceForAcceleration * direction / direction.lengthSquared(); return forcePerAccelRatio;*/ } double MotorizedPhysical::getInertiaOfPointInDirectionRelative(const Vec3Relative& relPoint, const Vec3Relative& relDirection) const { return getInertiaOfPointInDirectionLocal(getCFrame().relativeToLocal(relPoint), getCFrame().relativeToLocal(relDirection)); } CFrame ConnectedPhysical::getRelativeCFrameToParent() const { return connectionToParent.getRelativeCFrameToParent(); } Position MotorizedPhysical::getCenterOfMass() const { return getCFrame().localToGlobal(totalCenterOfMass); } GlobalCFrame MotorizedPhysical::getCenterOfMassCFrame() const { return GlobalCFrame(getCFrame().localToGlobal(totalCenterOfMass), getCFrame().getRotation()); } Vec3 MotorizedPhysical::getTotalImpulse() const { return this->motionOfCenterOfMass.getVelocity() * this->totalMass; } Vec3 MotorizedPhysical::getTotalAngularMomentum() const { Rotation selfRot = this->getCFrame().getRotation(); ALLOCA_COMMotionTree(cache, this, size); SymmetricMat3 totalInertia = selfRot.localToGlobal(cache.getInertia()); Vec3 localInternalAngularMomentum = cache.getInternalAngularMomentum(); Vec3 globalInternalAngularMomentum = selfRot.localToGlobal(localInternalAngularMomentum); Vec3 externalAngularMomentum = totalInertia * this->motionOfCenterOfMass.getAngularVelocity(); return externalAngularMomentum + globalInternalAngularMomentum; } Motion Physical::getMotion() const { if(this->isMainPhysical()) { return ((MotorizedPhysical*) this)->getMotion(); } else { return ((ConnectedPhysical*) this)->getMotion(); } } Motion MotorizedPhysical::getMotion() const { ALLOCA_COMMotionTree(cache, this, size); GlobalCFrame cf = this->getCFrame(); TranslationalMotion motionOfCom = localToGlobal(cf.getRotation(), cache.motionOfCenterOfMass); return -motionOfCom + motionOfCenterOfMass.getMotionOfPoint(cf.localToRelative(-cache.centerOfMass)); } Motion ConnectedPhysical::getMotion() const { // All motion and offset variables here are expressed in the global frame Motion parentMotion = parent->getMotion(); RelativeMotion motionBetweenParentAndChild = connectionToParent.getRelativeMotion(); RelativeMotion inGlobalFrame = motionBetweenParentAndChild.extendBegin(CFrame(parent->getCFrame().getRotation())); Motion result = inGlobalFrame.applyTo(parentMotion); return result; } Motion Physical::getMotionOfCenterOfMass() const { return this->getMotion().getMotionOfPoint(this->getCFrame().localToRelative(this->rigidBody.localCenterOfMass)); } Motion MotorizedPhysical::getMotionOfCenterOfMass() const { return this->motionOfCenterOfMass; } Motion ConnectedPhysical::getMotionOfCenterOfMass() const { return this->getMotion().getMotionOfPoint(this->getCFrame().localToRelative(this->rigidBody.localCenterOfMass)); } size_t Physical::getNumberOfPhysicalsInThisAndChildren() const { size_t totalPhysicals = 1; for(const ConnectedPhysical& child : childPhysicals) { totalPhysicals += child.getNumberOfPhysicalsInThisAndChildren(); } return totalPhysicals; } size_t Physical::getNumberOfPartsInThisAndChildren() const { size_t totalParts = rigidBody.getPartCount(); for(const ConnectedPhysical& child : childPhysicals) { totalParts += child.getNumberOfPartsInThisAndChildren(); } return totalParts; } void Physical::setMainPhysicalRecursive(MotorizedPhysical* newMainPhysical) { this->mainPhysical = newMainPhysical; for(ConnectedPhysical& child : childPhysicals) { child.setMainPhysicalRecursive(newMainPhysical); } } std::tuple InternalMotionTree::getInternalMotionOfCenterOfMass() const { TranslationalMotion totalMotion(Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0)); Vec3 totalCenterOfMass = this->motorPhys->rigidBody.localCenterOfMass * motorPhys->rigidBody.mass; double totalMass = motorPhys->rigidBody.mass; motorPhys->mutuallyRecurse(relativeMotionTree, [&totalMass, &totalCenterOfMass, &totalMotion](const MonotonicTreeNode& node, const ConnectedPhysical& conPhys) { double mass = conPhys.rigidBody.mass; totalMotion += node.value.relativeMotion.translation * mass; totalCenterOfMass += node.value.locationOfRelativeMotion.getPosition() * mass; totalMass += mass; }); totalCenterOfMass *= (1 / totalMass); totalMotion *= (1 / totalMass); return std::make_tuple(totalMass, totalCenterOfMass, totalMotion); } COMMotionTree InternalMotionTree::normalizeCenterOfMass() && { std::tuple m = this->getInternalMotionOfCenterOfMass(); for(MonotonicTreeNode& elem : relativeMotionTree) { elem.value.locationOfRelativeMotion -= std::get<1>(m); elem.value.relativeMotion.translation -= std::get<2>(m); } return COMMotionTree(this->motorPhys, std::move(this->relativeMotionTree), std::get<0>(m), std::get<1>(m), std::get<2>(m)); } SymmetricMat3 COMMotionTree::getInertia() const { SymmetricMat3 totalInertia = getTranslatedInertiaAroundCenterOfMass(motorPhys->rigidBody.inertia, motorPhys->rigidBody.mass, this->mainCOMOffset); motorPhys->mutuallyRecurse(relativeMotionTree, [&totalInertia](const MonotonicTreeNode& node, const ConnectedPhysical& conPhys) { totalInertia += getTransformedInertiaAroundCenterOfMass(conPhys.rigidBody.inertia, conPhys.rigidBody.mass, node.value.locationOfRelativeMotion); }); return totalInertia; } FullTaylor COMMotionTree::getInertiaDerivatives() const { FullTaylor totalInertia = getTranslatedInertiaDerivativesAroundCenterOfMass(motorPhys->rigidBody.inertia, motorPhys->rigidBody.mass, this->mainCOMOffset, -this->motionOfCenterOfMass); motorPhys->mutuallyRecurse(relativeMotionTree, [&totalInertia](const MonotonicTreeNode& node, const ConnectedPhysical& conPhys) { totalInertia += getTransformedInertiaDerivativesAroundCenterOfMass(conPhys.rigidBody.inertia, conPhys.rigidBody.mass, node.value.locationOfRelativeMotion, node.value.relativeMotion); }); return totalInertia; } Motion COMMotionTree::getMotion() const { GlobalCFrame cf = this->motorPhys->getCFrame(); TranslationalMotion motionOfCom = localToGlobal(cf.getRotation(), this->motionOfCenterOfMass); return -motionOfCom + this->motorPhys->motionOfCenterOfMass.getMotionOfPoint(cf.localToRelative(-this->centerOfMass)); } Vec3 COMMotionTree::getInternalAngularMomentum() const { Vec3 velocityOfMain = -motionOfCenterOfMass.getVelocity(); Vec3 totalAngularMomentum = getAngularMomentumFromOffsetOnlyVelocity(this->mainCOMOffset, velocityOfMain, motorPhys->rigidBody.mass); motorPhys->mutuallyRecurse(relativeMotionTree, [&totalAngularMomentum](const MonotonicTreeNode& node, const ConnectedPhysical& conPhys) { const RelativeMotion& relMotion = node.value; SymmetricMat3 relativeInertia = relMotion.locationOfRelativeMotion.getRotation().localToGlobal(conPhys.rigidBody.inertia); totalAngularMomentum += getAngularMomentumFromOffset( relMotion.locationOfRelativeMotion.getPosition(), relMotion.relativeMotion.getVelocity(), relMotion.relativeMotion.getAngularVelocity(), relativeInertia, conPhys.rigidBody.mass); }); return totalAngularMomentum; } #pragma endregion #pragma region isValid bool Physical::isValid() const { assert(rigidBody.isValid()); for(const ConnectedPhysical& p : childPhysicals) { assert(p.isValid()); assert(p.parent == this); } return true; } bool MotorizedPhysical::isValid() const { assert(Physical::isValid()); assert(isVecValid(totalForce)); assert(isVecValid(totalMoment)); assert(std::isfinite(totalMass)); assert(isVecValid(totalCenterOfMass)); assert(isMatValid(forceResponse)); assert(isMatValid(momentResponse)); return true; } bool ConnectedPhysical::isValid() const { assert(Physical::isValid()); assert(isCFrameValid(connectionToParent.attachOnParent)); assert(isCFrameValid(connectionToParent.attachOnChild)); return true; } #pragma endregion std::vector findAllLayersIn(MotorizedPhysical* phys) { std::vector result; phys->forEachPart([&result](Part& p) { for(FoundLayerRepresentative& item : result) { if(item.layer == p.layer) { return; } } result.push_back(FoundLayerRepresentative{p.layer, &p}); }); return result; } std::vector findAllLayersIn(Part* part) { if(part->layer != nullptr) { Physical* partPhys = part->getPhysical(); if(partPhys) { return findAllLayersIn(partPhys->mainPhysical); } else { return std::vector{FoundLayerRepresentative{part->layer, part}}; } } else { return std::vector{}; } } }; ================================================ FILE: Physics3D/physical.h ================================================ #pragma once #include "math/linalg/vec.h" #include "math/linalg/mat.h" #include "math/cframe.h" #include "math/bounds.h" #include "math/position.h" #include "math/globalCFrame.h" #include "datastructures/unorderedVector.h" #include "datastructures/iteratorEnd.h" #include "datastructures/monotonicTree.h" #include "part.h" #include "rigidBody.h" #include "hardconstraints/hardConstraint.h" #include "hardconstraints/hardPhysicalConnection.h" #include namespace P3D { typedef Vec3 Vec3Local; typedef Vec3 Vec3Relative; class WorldPrototype; class ConnectedPhysical; class MotorizedPhysical; class InternalMotionTree; class COMMotionTree; class Physical { void makeMainPart(AttachedPart& newMainPart); protected: void updateAttachedPhysicals(); void updateConstraints(double deltaT); void translateUnsafeRecursive(const Vec3Fix& translation); void setMainPhysicalRecursive(MotorizedPhysical* newMainPhysical); // deletes the given physical void attachPhysical(Physical* phys, const CFrame& attachment); // deletes the given physical void attachPhysical(MotorizedPhysical* phys, const CFrame& attachment); // deletes the given physical void attachPhysical(Physical* phys, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat); // deletes the given physical void attachPhysical(MotorizedPhysical* phys, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat); void removeChild(ConnectedPhysical* child); void detachFromRigidBody(Part* part); void detachFromRigidBody(AttachedPart&& part); void detachPartAssumingMultipleParts(Part* part); // expects a function of type void(const Part&) template void forEachPartInChildren(const Func& func) const; // expects a function of type void(Part&) template void forEachPartInChildren(const Func& func); // expects a function of type void(const Part&) template void forEachPartInThisAndChildren(const Func& func) const; // expects a function of type void(Part&) template void forEachPartInThisAndChildren(const Func& func); // expects a function of type void(const Physical& parent, const ConnectedPhysical& child) template void forEachHardConstraintInChildren(const Func& func) const; // expects a function of type void(Physical& parent, ConnectedPhysical& child) template void forEachHardConstraintInChildren(const Func& func); // expects a predicate of type bool(const Part& p) template const Part* findFirstInThisAndChildren(const Pred& pred) const; // expects a predicate of type bool(Part& p) template Part* findFirstInThisAndChildren(const Pred& pred); public: RigidBody rigidBody; MotorizedPhysical* mainPhysical; UnorderedVector childPhysicals; Physical() = default; Physical(Part* mainPart, MotorizedPhysical* mainPhysical); Physical(RigidBody&& rigidBody, MotorizedPhysical* mainPhysical); Physical(Physical&& other) noexcept; Physical& operator=(Physical&& other) noexcept; Physical(const Physical&) = delete; void operator=(const Physical&) = delete; WorldPrototype* getWorld() const; // expects a function of type void(const Physical& parent, const ConnectedPhysical& child) template void forEachHardConstraint(const Func& func); void setCFrame(const GlobalCFrame& newCFrame); void setPartCFrame(Part* part, const GlobalCFrame& newCFrame); inline const GlobalCFrame& getCFrame() const { return rigidBody.getCFrame(); } inline Position getPosition() const { return getCFrame().getPosition(); } /* Returns the motion of this physical positioned at it's getCFrame() The motion is oriented globally */ Motion getMotion() const; /* Returns the motion of this physical positioned at it's center of mass The motion is oriented globally */ Motion getMotionOfCenterOfMass() const; double getVelocityKineticEnergy() const; double getAngularKineticEnergy() const; double getKineticEnergy() const; void applyDragToPhysical(Vec3 origin, Vec3 drag); void applyForceToPhysical(Vec3 origin, Vec3 force); void applyImpulseToPhysical(Vec3 origin, Vec3 impulse); CFrame getRelativeCFrameToMain() const; Vec3 localToMain(const Vec3Local& vec) const; Vec3 localToMainCOMRelative(const Vec3Local& vec) const; void makeMainPart(Part* newMainPart); void attachPart(Part* part, const CFrame& attachment); /* detaches a part from this Physical Leaves the detached part with a MotorizedPhysical parent in which it is the only part As opposed to Physical::removePart(Part*) this Physical may no longer be valid after calling detachPart */ void detachPart(Part* part); /* removes a part from this Physical After this method: part->hasPhysical() == false As opposed to Physical::detachPart(Part*) this Physical may no longer be valid after calling removePart */ void removePart(Part* part); void attachPart(Part* part, HardConstraint* constraint, const CFrame& attachToThis, const CFrame& attachToThat); size_t getNumberOfPhysicalsInThisAndChildren() const; size_t getNumberOfPartsInThisAndChildren() const; void notifyPartPropertiesChanged(Part* part); void notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept; bool isValid() const; bool isMainPhysical() const; MotorizedPhysical* makeMainPhysical(); private: void detachAllChildPhysicals(); }; class ConnectedPhysical : public Physical { friend class Physical; friend class MotorizedPhysical; void refreshCFrame(); void refreshCFrameRecursive(); public: HardPhysicalConnection connectionToParent; Physical* parent; ConnectedPhysical() = default; ConnectedPhysical(RigidBody&& rigidBody, Physical* parent, HardPhysicalConnection&& connectionToParent); ConnectedPhysical(Physical&& phys, Physical* parent, HardPhysicalConnection&& connectionToParent); ConnectedPhysical(Physical&& phys, Physical* parent, HardConstraint* constraintWithParent, const CFrame& attachOnThis, const CFrame& attachOnParent); void setCFrame(const GlobalCFrame& newCFrame); CFrame getRelativeCFrameToParent() const; /* Returns the motion of this physical positioned at it's getCFrame() The motion is oriented globally */ Motion getMotion() const; /* Returns the motion of this physical positioned at it's center of mass The motion is oriented globally */ Motion getMotionOfCenterOfMass() const; /* Makes this physical the main Physical */ void makeMainPhysical(); std::size_t getIndexInParent() const; RelativeMotion getRelativeMotionBetweenParentAndSelf() const; bool isValid() const; }; class MotorizedPhysical : public Physical { friend class Physical; friend class ConnectedPhysical; public: void refreshPhysicalProperties(); Vec3 totalForce = Vec3(0.0, 0.0, 0.0); Vec3 totalMoment = Vec3(0.0, 0.0, 0.0); double totalMass; Vec3 totalCenterOfMass; SymmetricMat3 forceResponse; SymmetricMat3 momentResponse; Motion motionOfCenterOfMass; explicit MotorizedPhysical(Part* mainPart); explicit MotorizedPhysical(RigidBody&& rigidBody); explicit MotorizedPhysical(Physical&& movedPhys); /* Returns the motion of this physical positioned at it's getCFrame() The motion is oriented globally */ Motion getMotion() const; /* Returns the motion of this physical positioned at it's center of mass The motion is oriented globally */ Motion getMotionOfCenterOfMass() const; Vec3 getVelocityOfCenterOfMass() const; Position getCenterOfMass() const; GlobalCFrame getCenterOfMassCFrame() const; Vec3 getTotalImpulse() const; Vec3 getTotalAngularMomentum() const; // connectedPhysFunc is of the form void(const RelativeMotion& relativeMotionOfThisPhysical, const ConnectedPhysical& currentPhysical) template void mutuallyRecurse(const MonotonicTree& tree, F connectedPhysFunc) const { struct NestedRecurse { static void recurse(const MonotonicTreeNode& currentNode, const Physical& currentPhys, F func) { std::size_t childCount = currentPhys.childPhysicals.size(); for(std::size_t i = 0; i < childCount; i++) { const ConnectedPhysical& conPhys = currentPhys.childPhysicals[i]; MonotonicTreeNode& currentChildNode = currentNode.children[i]; func(currentChildNode, conPhys); recurse(currentChildNode, conPhys, func); } } }; std::size_t childCount = this->childPhysicals.size(); for(std::size_t i = 0; i < childCount; i++) { const ConnectedPhysical& conPhys = this->childPhysicals[i]; const MonotonicTreeNode& currentChildNode = tree[i]; connectedPhysFunc(currentChildNode, conPhys); NestedRecurse::recurse(currentChildNode, conPhys, connectedPhysFunc); } } InternalMotionTree getInternalRelativeMotionTree(UnmanagedArray>&& mem) const noexcept; COMMotionTree getCOMMotionTree(UnmanagedArray>&& mem) const noexcept; void update(double deltaT); void setCFrame(const GlobalCFrame& newCFrame); void translate(const Vec3& translation); void rotateAroundCenterOfMass(const Rotation& rotation); void applyForceAtCenterOfMass(Vec3 force); void applyForce(Vec3Relative origin, Vec3 force); void applyMoment(Vec3 moment); void applyImpulseAtCenterOfMass(Vec3 impulse); void applyImpulse(Vec3Relative origin, Vec3Relative impulse); void applyAngularImpulse(Vec3 angularImpulse); void applyDragAtCenterOfMass(Vec3 drag); void applyDrag(Vec3Relative origin, Vec3Relative drag); void applyAngularDrag(Vec3 angularDrag); SymmetricMat3 getResponseMatrix(const Vec3Local& localPoint) const; Mat3 getResponseMatrix(const Vec3Local& actionPoint, const Vec3Local& responsePoint) const; double getInertiaOfPointInDirectionLocal(const Vec3Local& localPoint, const Vec3Local& localDirection) const; double getInertiaOfPointInDirectionRelative(const Vec3Relative& relativePoint, const Vec3Relative& relativeDirection) const; inline Part* getMainPart() { return this->rigidBody.mainPart; } inline const Part* getMainPart() const { return this->rigidBody.mainPart; } void fullRefreshOfConnectedPhysicals(); bool isSinglePart() const { return this->childPhysicals.size() == 0 && this->rigidBody.getPartCount() == 1; } // expects a function of type void(const Part&) template void forEachPart(const Func& func) const { this->forEachPartInThisAndChildren(func); } // expects a function of type void(Part&) template void forEachPart(const Func& func) { this->forEachPartInThisAndChildren(func); } // expects a function of type void(const Part&) template void forEachPartExceptMainPart(const Func& func) const { this->rigidBody.forEachAttachedPart(func); this->forEachPartInChildren(func); } // expects a function of type void(Part&) template void forEachPartExceptMainPart(const Func& func) { this->rigidBody.forEachAttachedPart(func); this->forEachPartInChildren(func); } // expects a function of type void(const Physical& parent, const ConnectedPhysical& child) template void forEachHardConstraint(const Func& func) const { this->forEachHardConstraintInChildren(func); } // expects a function of type void(Physical& parent, ConnectedPhysical& child) template void forEachHardConstraint(const Func& func) { this->forEachHardConstraintInChildren(func); } // expects a predicate of type bool(const Part& p) template const Part* findFirst(const Pred& pred) const { return this->findFirstInThisAndChildren(pred); } // expects a predicate of type bool(Part& p) template Part* findFirst(const Pred& pred) { return this->findFirstInThisAndChildren(pred); } bool isValid() const; }; // expects a function of type void(const Part&) template void Physical::forEachPartInChildren(const Func& func) const { for(const ConnectedPhysical& conPhys : this->childPhysicals) { conPhys.forEachPartInThisAndChildren(func); } } // expects a function of type void(Part&) template void Physical::forEachPartInChildren(const Func& func) { for(ConnectedPhysical& conPhys : this->childPhysicals) { conPhys.forEachPartInThisAndChildren(func); } } // expects a function of type void(const Part&) template void Physical::forEachPartInThisAndChildren(const Func& func) const { this->rigidBody.forEachPart(func); this->forEachPartInChildren(func); } // expects a function of type void(Part&) template void Physical::forEachPartInThisAndChildren(const Func& func) { this->rigidBody.forEachPart(func); this->forEachPartInChildren(func); } // expects a function of type void(const Physical& parent, const ConnectedPhysical& child) template void Physical::forEachHardConstraint(const Func& func) { if(!this->isMainPhysical()) { ConnectedPhysical* conThis = static_cast(this); func(*conThis->parent, *conThis); } for(ConnectedPhysical& conPhys : this->childPhysicals) { func(*this, conPhys); } } // expects a function of type void(const Physical& parent, const ConnectedPhysical& child) template void Physical::forEachHardConstraintInChildren(const Func& func) const { for(const ConnectedPhysical& conPhys : this->childPhysicals) { func(*this, conPhys); conPhys.forEachHardConstraintInChildren(func); } } // expects a function of type void(Physical& parent, ConnectedPhysical& child) template void Physical::forEachHardConstraintInChildren(const Func& func) { for(ConnectedPhysical& conPhys : this->childPhysicals) { func(*this, conPhys); conPhys.forEachHardConstraintInChildren(func); } } // expects a function of type bool(const Part& p) template const Part* Physical::findFirstInThisAndChildren(const Pred& pred) const { if(pred(this->rigidBody.mainPart)) { return this->rigidBody.mainPart; } else { for(const AttachedPart& p : this->rigidBody.parts) { if(pred(p.part)) { return p.part; } } for(const ConnectedPhysical& conPhys : childPhysicals) { const Part* found = conPhys.findFirstInThisAndChildren(pred); if(found != nullptr) { return found; } } return nullptr; } } // expects a function of type bool(Part& p) template Part* Physical::findFirstInThisAndChildren(const Pred& pred) { if(pred(*this->rigidBody.mainPart)) { return this->rigidBody.mainPart; } else { for(const AttachedPart& p : this->rigidBody.parts) { if(pred(*p.part)) { return p.part; } } for(ConnectedPhysical& conPhys : childPhysicals) { Part* found = conPhys.findFirstInThisAndChildren(pred); if(found != nullptr) { return found; } } return nullptr; } } // used to precompute all the internal motions in the MotorizedPhysical class InternalMotionTree { const MotorizedPhysical* motorPhys; MonotonicTree relativeMotionTree; public: inline InternalMotionTree(const MotorizedPhysical* motorPhys, MonotonicTree&& relativeMotionTree) : motorPhys(motorPhys), relativeMotionTree(std::move(relativeMotionTree)) {} // connectedPhysFunc is of the form void(const RelativeMotion& relativeMotionOfThisPhysical, const ConnectedPhysical& currentPhysical) template void forEach(F connectedPhysFunc) const { motorPhys->mutuallyRecurse(relativeMotionTree, connectedPhysFunc); } // returns the total mass, the center of mass relative to the MotorizedPhysical's CFrame, and the motion of the center of mass in that CFrame std::tuple getInternalMotionOfCenterOfMass() const; // normalizes this motion tree relative to this->getInternalMotionOfCenterOfMass() COMMotionTree normalizeCenterOfMass()&&; MonotonicTreeNode* getPtrToFree() { return relativeMotionTree.getPtrToFree(); } }; // Same as InternalMotionTree, but relative to the position and speed of the center of mass class COMMotionTree { friend class InternalMotionTree; public: const MotorizedPhysical* motorPhys; MonotonicTree relativeMotionTree; double totalMass; Vec3 centerOfMass; TranslationalMotion motionOfCenterOfMass; Vec3 mainCOMOffset; private: inline COMMotionTree(const MotorizedPhysical* motorPhys, MonotonicTree&& relativeMotionTree, double totalMass, Vec3 centerOfMass, TranslationalMotion motionOfCenterOfMass) : motorPhys(motorPhys), relativeMotionTree(std::move(relativeMotionTree)), totalMass(totalMass), centerOfMass(centerOfMass), motionOfCenterOfMass(motionOfCenterOfMass), mainCOMOffset(motorPhys->rigidBody.localCenterOfMass - centerOfMass) {} public: // connectedPhysFunc is of the form void(const RelativeMotion& relativeMotionOfThisPhysical, const ConnectedPhysical& currentPhysical) template void forEach(F connectedPhysFunc) const { motorPhys->mutuallyRecurse(relativeMotionTree, connectedPhysFunc); } SymmetricMat3 getInertia() const; FullTaylor getInertiaDerivatives() const; Motion getMotion() const; Vec3 getInternalAngularMomentum() const; inline Vec3 getRelativePosOfMain() const { return motorPhys->rigidBody.localCenterOfMass - centerOfMass; } inline TranslationalMotion getMotionOfMain() const { return -motionOfCenterOfMass; } inline MonotonicTreeNode* getPtrToFree() { return relativeMotionTree.getPtrToFree(); } }; // Introduces extra variables std::size_t sizeName and COMMotionTree resultName // Warning, make sure to use with braces, is not if() else proof #define ALLOCA_COMMotionTree(resultName, motorPhysName, sizeName) \ std::size_t sizeName = motorPhysName->getNumberOfPhysicalsInThisAndChildren() - 1; \ COMMotionTree resultName = motorPhysName->getCOMMotionTree(UnmanagedArray>(\ static_cast*>(alloca(sizeof(MonotonicTreeNode) * sizeName)), sizeName)\ ); // Introduces extra variables std::size_t sizeName and InternalMotionTree resultName // Warning, make sure to use with braces, is not if() else proof #define ALLOCA_InternalMotionTree(resultName, motorPhysName, sizeName) \ std::size_t sizeName = motorPhysName->getNumberOfPhysicalsInThisAndChildren() - 1; \ InternalMotionTree resultName = motorPhysName->getInternalRelativeMotionTree(UnmanagedArray>(\ static_cast*>(alloca(sizeof(MonotonicTreeNode) * sizeName)), sizeName)\ ); struct FoundLayerRepresentative { WorldLayer* layer; Part* part; }; std::vector findAllLayersIn(MotorizedPhysical* phys); std::vector findAllLayersIn(Part* part); }; ================================================ FILE: Physics3D/relativeMotion.h ================================================ #pragma once #include "math/cframe.h" #include "motion.h" namespace P3D { struct RelativeMotion { Motion relativeMotion; CFrame locationOfRelativeMotion; RelativeMotion() = default; inline RelativeMotion(const Motion& relativeMotion, const CFrame& locationOfRelativeMotion) : relativeMotion(relativeMotion), locationOfRelativeMotion(locationOfRelativeMotion) {} /* Returns a new RelativeMotion of the new endpoint where the new endpoint is rigidly attached to the old endpoint via extension The extension vector is in the local space of the end point */ inline RelativeMotion extendEnd(const Vec3& extension) const { Vec3 relativeExtension = locationOfRelativeMotion.localToRelative(extension); Motion resultingMotion = relativeMotion.getMotionOfPoint(relativeExtension); return RelativeMotion(resultingMotion, locationOfRelativeMotion + relativeExtension); } /* Returns a new RelativeMotion of the new endpoint where the new endpoint is rigidly attached to the old endpoint via extension The extension vector is in the local space of the end point */ inline RelativeMotion extendEnd(const CFrame& extension) const { Vec3 relativeExtension = locationOfRelativeMotion.localToRelative(extension.getPosition()); Motion resultingMotion = relativeMotion.getMotionOfPoint(relativeExtension); return RelativeMotion(resultingMotion, locationOfRelativeMotion.localToGlobal(extension)); } /* Returns a new RelativeMotion where this relativeMotion has been attached onto an extension */ inline RelativeMotion extendBegin(const Vec3& extension) const { return RelativeMotion(relativeMotion, locationOfRelativeMotion + extension); } /* Returns a new RelativeMotion where this relativeMotion has been attached onto an extension */ inline RelativeMotion extendBegin(const CFrame& extension) const { return RelativeMotion(localToGlobal(extension.getRotation() , relativeMotion), extension.localToGlobal(locationOfRelativeMotion)); } inline Motion applyTo(const Motion& originMotion) const { return originMotion.addOffsetRelativeMotion(locationOfRelativeMotion.getPosition(), relativeMotion); } }; /* Appends the second RelativeMotion onto the end of the first one */ inline RelativeMotion operator+(const RelativeMotion& first, const RelativeMotion& second) { CFrame relativeCFrameToBeAdded = first.locationOfRelativeMotion.localToRelative(second.locationOfRelativeMotion); Motion rotatedSecondMotion = localToGlobal(first.locationOfRelativeMotion.getRotation(), second.relativeMotion); Motion motionOfEndpointWithSecondMotion = first.relativeMotion.addOffsetRelativeMotion(relativeCFrameToBeAdded.getPosition(), rotatedSecondMotion); CFrame resultingOffset = relativeCFrameToBeAdded + first.locationOfRelativeMotion.getPosition(); return RelativeMotion(motionOfEndpointWithSecondMotion, resultingOffset); } /* Appends the second RelativeMotion onto the end of the first one */ inline RelativeMotion& operator+=(RelativeMotion& first, const RelativeMotion& second) { first = first + second; return first; } }; ================================================ FILE: Physics3D/rigidBody.cpp ================================================ #include "rigidBody.h" #include "inertia.h" #include "misc/validityHelper.h" #include namespace P3D { RigidBody::RigidBody(Part* mainPart) : mainPart(mainPart), mass(mainPart->getMass()), localCenterOfMass(mainPart->getLocalCenterOfMass()), inertia(mainPart->getInertia()) {} void RigidBody::attach(RigidBody&& otherBody, const CFrame& attachment) { const GlobalCFrame& cf = this->getCFrame(); otherBody.mainPart->cframe = cf.localToGlobal(attachment); this->parts.push_back(AttachedPart{attachment, otherBody.mainPart}); for(AttachedPart& ap : otherBody.parts) { CFrame globalAttach = attachment.localToGlobal(ap.attachment); this->parts.push_back(AttachedPart{globalAttach, ap.part}); ap.part->cframe = cf.localToGlobal(globalAttach); } } void RigidBody::attach(Part* part, const CFrame& attachment) { parts.push_back(AttachedPart{attachment, part}); part->cframe = getCFrame().localToGlobal(attachment); refreshWithNewParts(); } void RigidBody::detach(Part* part) { for(AttachedPart& atPart : parts) { if(atPart.part == part) { parts.remove(std::move(atPart)); refreshWithNewParts(); return; } } throw "part not found!"; } void RigidBody::detach(AttachedPart&& part) { parts.remove(std::move(part)); refreshWithNewParts(); return; } const AttachedPart& RigidBody::getAttachFor(const Part* attachedPart) const { for(const AttachedPart& p : parts) { if(p.part == attachedPart) { return p; } } throw "Part not in this physical!"; } AttachedPart& RigidBody::getAttachFor(const Part* attachedPart) { for(AttachedPart& p : parts) { if(p.part == attachedPart) { return p; } } throw "Part not in this physical!"; } template static inline bool liesInVector(const std::vector& vec, const T* ptr) { return &vec[0] <= ptr && &vec[0]+vec.size() > ptr; } // Not to be used by CFrame RigidBody::makeMainPart(AttachedPart& newMainPart) { if(liesInVector(parts, &newMainPart)) { // Update parts CFrame newCenterCFrame = newMainPart.attachment; std::swap(mainPart, newMainPart.part); for(AttachedPart& atPart : parts) { if(&atPart != &newMainPart) { atPart.attachment = newCenterCFrame.globalToLocal(atPart.attachment); } } newMainPart.attachment = ~newCenterCFrame; // since this CFrame is now the attachment of the former mainPart, we can just invert the original CFrame // refresh is needed for resyncing inertia, center of mass, etc refreshWithNewParts(); return newCenterCFrame; } else { throw "Attempting to make a part not in this physical the mainPart!"; } } void RigidBody::notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept { if(this->mainPart == oldPartPtr) { this->mainPart = newPartPtr; } else { for(AttachedPart& atPart : this->parts) { if(atPart.part == oldPartPtr) { atPart.part = newPartPtr; return; } } assert(false); // cannot happen, part must be in this rigidbody } } void RigidBody::refreshWithNewParts() { double totalMass = mainPart->getMass(); Vec3 totalCenterOfMass = mainPart->getLocalCenterOfMass() * mainPart->getMass(); for(const AttachedPart& p : parts) { totalMass += p.part->getMass(); totalCenterOfMass += p.attachment.localToGlobal(p.part->getLocalCenterOfMass()) * p.part->getMass(); } totalCenterOfMass /= totalMass; SymmetricMat3 totalInertia = getTranslatedInertiaAroundCenterOfMass(mainPart->getInertia(), mainPart->getMass(), mainPart->getLocalCenterOfMass() - totalCenterOfMass);; for(const AttachedPart& p : parts) { const Part* part = p.part; CFrame attachRelativeToCOM = CFrame(p.attachment.getPosition() - totalCenterOfMass, p.attachment.getRotation()); totalInertia += getTransformedInertiaAroundCenterOfMass(part->getInertia(), part->getMass(), part->getLocalCenterOfMass(), attachRelativeToCOM); } this->mass = totalMass; this->localCenterOfMass = totalCenterOfMass; this->inertia = totalInertia; } void RigidBody::setCFrame(const GlobalCFrame& newCFrame) { this->mainPart->cframe = newCFrame; for(const AttachedPart& p : parts) { p.part->cframe = newCFrame.localToGlobal(p.attachment); } } void RigidBody::setCFrameOfPart(Part* part, const GlobalCFrame& newCFrame) { if(part == mainPart) { setCFrame(newCFrame); } else { CFrame attach = getAttachFor(part).attachment; GlobalCFrame newMainCFrame = newCFrame.localToGlobal(~attach); setCFrame(newMainCFrame); } } void RigidBody::translate(const Vec3Fix& translation) { mainPart->cframe += translation; for(AttachedPart& atPart : parts) { atPart.part->cframe += translation; } } void RigidBody::rotateAroundLocalPoint(const Vec3& localPoint, const Rotation& rotation) { Vec3 relPoint = getCFrame().localToRelative(localPoint); Vec3 relativeRotationOffset = rotation * relPoint - relPoint; mainPart->cframe.rotate(rotation); mainPart->cframe -= relativeRotationOffset; for(AttachedPart& atPart : parts) { atPart.part->cframe = mainPart->cframe.localToGlobal(atPart.attachment); } } void RigidBody::setAttachFor(Part* part, const CFrame& attach) { if(mainPart == part) { for(AttachedPart& ap : parts) { ap.attachment = attach.globalToLocal(ap.attachment); } } else { AttachedPart& atPart = getAttachFor(part); atPart.attachment = attach; } refreshWithNewParts(); } bool RigidBody::isValid() const { assert(std::isfinite(mass)); assert(isVecValid(localCenterOfMass)); assert(isMatValid(inertia)); assert(mainPart->isValid()); for(const AttachedPart& ap : parts) { assert(isCFrameValid(ap.attachment)); assert(ap.part->isValid()); } return true; } }; ================================================ FILE: Physics3D/rigidBody.h ================================================ #pragma once #include "datastructures/unorderedVector.h" #include "datastructures/iteratorEnd.h" #include "part.h" namespace P3D { struct AttachedPart { CFrame attachment = CFrame(); Part* part = nullptr; }; struct PartIter { AttachedPart* iter; AttachedPart* iterEnd; Part* first; PartIter() = default; PartIter(AttachedPart* iter, AttachedPart* iterEnd, Part* first) : iter(iter - 1) , iterEnd(iterEnd) , first(first) {} inline Part& operator*() const { return (first != nullptr) ? *first : *iter->part; } inline void operator++() { ++iter; first = nullptr; } inline bool operator!=(IteratorEnd) const { return this->iter != this->iterEnd; } inline bool operator==(IteratorEnd) const { return this->iter == this->iterEnd; } }; struct ConstPartIter { const AttachedPart* iter; const AttachedPart* iterEnd; const Part* first; ConstPartIter() = default; ConstPartIter(const AttachedPart* iter, const AttachedPart* iterEnd, const Part* first) : iter(iter - 1) , iterEnd(iterEnd) , first(first) {} inline const Part& operator*() const { return (first != nullptr) ? *first : *iter->part; } inline void operator++() { ++iter; first = nullptr; } inline bool operator!=(IteratorEnd) const { return this->iter != this->iterEnd; } inline bool operator==(IteratorEnd) const { return this->iter == this->iterEnd; } }; class RigidBody { public: Part* mainPart; UnorderedVector parts; double mass; // precomputed cached value for this whole physical Vec3 localCenterOfMass; // precomputed cached value for this whole physical SymmetricMat3 inertia; // precomputed cached value for this whole physical RigidBody() = default; RigidBody(Part* mainPart); RigidBody(RigidBody&& other) = default; RigidBody& operator=(RigidBody&& other) = default; RigidBody(const RigidBody&) = delete; void operator=(const RigidBody&) = delete; void attach(RigidBody&& otherBody, const CFrame& attachment); void attach(Part* part, const CFrame& attachment); void detach(Part* part); void detach(AttachedPart&& part); const AttachedPart& getAttachFor(const Part* attachedPart) const; AttachedPart& getAttachFor(const Part* attachedPart); inline const Part* getMainPart() const { return mainPart; } inline Part* getMainPart() { return mainPart; } // returns the offset CFrame from the original mainPart, can be used for syncing calling function CFrame makeMainPart(AttachedPart& newMainPart); void notifyPartStdMoved(Part* oldPartPtr, Part* newPartPtr) noexcept; // expects a function of type void(const Part&) template void forEachAttachedPart(const Func& func) const { for (const AttachedPart& atPart : parts) { func(static_cast(*atPart.part)); } } // expects a function of type void(Part&) template void forEachAttachedPart(const Func& func) { for (const AttachedPart& atPart : parts) { func(*atPart.part); } } // expects a function of type void(const Part&) template void forEachPart(const Func& func) const { func(static_cast(*this->mainPart)); this->forEachAttachedPart(func); } // expects a function of type void(Part&) template void forEachPart(const Func& func) { func(*this->mainPart); this->forEachAttachedPart(func); } PartIter begin() { if (parts.empty()) { return PartIter(nullptr, nullptr, mainPart); } else { return PartIter(&parts[0], &parts[0] + parts.size(), mainPart); } } ConstPartIter begin() const { if (parts.empty()) { return ConstPartIter(nullptr, nullptr, mainPart); } else { return ConstPartIter(&parts[0], &parts[0] + parts.size(), mainPart); } } IteratorEnd end() const { return IteratorEnd(); } const Part& operator[](size_t index) const { return (index == 0) ? *mainPart : *parts[index - 1].part; } Part& operator[](size_t index) { return (index == 0) ? *mainPart : *parts[index - 1].part; } inline size_t getPartCount() const { return parts.size() + 1; } void setCFrame(const GlobalCFrame& newCFrame); void setCFrameOfPart(Part* part, const GlobalCFrame& newCFrame); void translate(const Vec3Fix& translation); void rotateAroundLocalPoint(const Vec3& localPoint, const Rotation& rotation); void setAttachFor(Part* part, const CFrame& newAttach); inline const GlobalCFrame& getCFrame() const { return mainPart->getCFrame(); } inline Position getPosition() const { return getCFrame().getPosition(); } inline Position getCenterOfMass() const { return getCFrame().localToGlobal(localCenterOfMass); } inline GlobalCFrame getCenterOfMassCFrame() const { return GlobalCFrame(getCFrame().localToGlobal(localCenterOfMass), getCFrame().getRotation()); } bool isValid() const; void refreshWithNewParts(); private: }; }; ================================================ FILE: Physics3D/softlinks/alignmentLink.cpp ================================================ #include "alignmentLink.h" namespace P3D { AlignmentLink::AlignmentLink(const AttachedPart& partA, const AttachedPart& partB) : SoftLink(partA, partB) { offset = this->attachedPartA.part->getCFrame().rotation.localToGlobal(this->attachedPartB.part->getCFrame().rotation); } void AlignmentLink::update() { Vec3 momentDir = getGlobalMoment(); this->attachedPartA.part->applyMoment(momentDir); this->attachedPartB.part->applyMoment(momentDir); } Vec3 AlignmentLink::getGlobalMoment() noexcept { Rotation rot1 = this->attachedPartA.part->getCFrame().getRotation().localToGlobal(this->offset); Rotation rot2 = this->attachedPartB.part->getCFrame().getRotation(); Rotation deltRot = rot2.globalToLocal(rot1); Vec3 momentDir = deltRot.asRotationVector(); return momentDir; } }; ================================================ FILE: Physics3D/softlinks/alignmentLink.h ================================================ #pragma once #include "softLink.h" namespace P3D { class AlignmentLink : public SoftLink { public: Rotation offset; AlignmentLink(const AttachedPart& partA, const AttachedPart& partB); ~AlignmentLink() override = default; AlignmentLink(const AlignmentLink& other) = delete; AlignmentLink& operator=(const AlignmentLink& other) = delete; AlignmentLink(AlignmentLink&& other) = delete; AlignmentLink& operator=(AlignmentLink&& other) = delete; void update() override; private: [[nodiscard]] Vec3 getGlobalMoment() noexcept; }; }; ================================================ FILE: Physics3D/softlinks/elasticLink.cpp ================================================ #include "elasticLink.h" namespace P3D { ElasticLink::ElasticLink(const AttachedPart& partA, const AttachedPart& partB, double restLength, double stiffness) : SoftLink(partA, partB) , restLength(restLength) , stiffness(stiffness) {} void ElasticLink::update() { auto optionalForce = forceAppliedToTheLink(); if (!optionalForce) return; Vec3 force = optionalForce.value(); this->attachedPartA.part->applyForce(this->getRelativePositionOfAttachmentB(), force); this->attachedPartB.part->applyForce(this->getRelativePositionOfAttachmentA(), -force); } std::optional ElasticLink::forceAppliedToTheLink() { Vec3 difference = this->getGlobalPositionOfAttachmentA() - this->getGlobalPositionOfAttachmentB(); double distance = length(difference); if (distance <= this->restLength) return std::nullopt; double magnitude = std::abs(distance - this->restLength) * this->stiffness; Vec3 forceDirection = normalize(difference); Vec3 force = magnitude * -forceDirection; return force; } }; ================================================ FILE: Physics3D/softlinks/elasticLink.h ================================================ #pragma once #include "softLink.h" #include namespace P3D { class ElasticLink : public SoftLink { public: double restLength; double stiffness; ElasticLink(const AttachedPart& partA, const AttachedPart& partB, double restLength, double stiffness); ~ElasticLink() override = default; ElasticLink(const ElasticLink&) = delete; ElasticLink& operator=(const ElasticLink&) = delete; ElasticLink(ElasticLink&&) = delete; ElasticLink& operator=(ElasticLink&&) = delete; void update() override; private: std::optional forceAppliedToTheLink(); }; }; ================================================ FILE: Physics3D/softlinks/magneticLink.cpp ================================================ #include "magneticLink.h" namespace P3D { MagneticLink::MagneticLink(const AttachedPart& partA, const AttachedPart& partB, double magneticStrength) : SoftLink(partA, partB) , magneticStrength(magneticStrength) {} void MagneticLink::update() { Vec3 force = forceAppliedToTheLink(); this->attachedPartB.part->applyForce(this->getRelativePositionOfAttachmentA(), force); this->attachedPartA.part->applyForce(this->getRelativePositionOfAttachmentB(), -force); } Vec3 MagneticLink::forceAppliedToTheLink() noexcept { Vec3 difference = this->getGlobalPositionOfAttachmentA() - this->getGlobalPositionOfAttachmentB(); Vec3 forceDirection = normalize(difference); double distance = lengthSquared(difference); return this->magneticStrength * forceDirection / distance; } }; ================================================ FILE: Physics3D/softlinks/magneticLink.h ================================================ #pragma once #include "softLink.h" namespace P3D { class MagneticLink : public SoftLink { public: double magneticStrength; MagneticLink(const AttachedPart& partA, const AttachedPart& partB, double magneticStrength); ~MagneticLink() override = default; MagneticLink(const MagneticLink& other) = delete; MagneticLink& operator=(const MagneticLink& other) = delete; MagneticLink(MagneticLink&& other) = delete; MagneticLink& operator=(MagneticLink&& other) = delete; void update() override; private: [[nodiscard]] Vec3 forceAppliedToTheLink() noexcept; }; }; ================================================ FILE: Physics3D/softlinks/softLink.cpp ================================================ #include "softLink.h" namespace P3D { SoftLink::SoftLink(const AttachedPart& attachedPartA, const AttachedPart& attachedPartB) : attachedPartA(attachedPartA) , attachedPartB(attachedPartB) {} SoftLink::~SoftLink() = default; GlobalCFrame SoftLink::getGlobalCFrameOfAttachmentA() const { return this->attachedPartA.part->getCFrame(); } GlobalCFrame SoftLink::getGlobalCFrameOfAttachmentB() const { return this->attachedPartB.part->getCFrame(); } CFrame SoftLink::getLocalCFrameOfAttachmentA() const { return this->attachedPartA.attachment; } CFrame SoftLink::getLocalCFrameOfAttachmentB() const { return this->attachedPartB.attachment; } CFrame SoftLink::getRelativeOfAttachmentA() const { return this->getGlobalCFrameOfAttachmentA().localToRelative(this->attachedPartA.attachment); } CFrame SoftLink::getRelativeOfAttachmentB() const { return this->getGlobalCFrameOfAttachmentB().localToRelative(this->attachedPartB.attachment); } Position SoftLink::getGlobalPositionOfAttachmentB() const { return this->getGlobalCFrameOfAttachmentB().getPosition(); } Position SoftLink::getGlobalPositionOfAttachmentA() const { return this->getGlobalCFrameOfAttachmentA().getPosition(); } Vec3 SoftLink::getLocalPositionOfAttachmentA() const { return this->getLocalCFrameOfAttachmentA().getPosition(); } Vec3 SoftLink::getLocalPositionOfAttachmentB() const { return this->getLocalCFrameOfAttachmentB().getPosition(); } Vec3 SoftLink::getRelativePositionOfAttachmentA() const { return this->getRelativeOfAttachmentA().getPosition(); } Vec3 SoftLink::getRelativePositionOfAttachmentB() const { return this->getRelativeOfAttachmentB().getPosition(); } }; ================================================ FILE: Physics3D/softlinks/softLink.h ================================================ #pragma once #include "../math/linalg/vec.h" #include "../rigidBody.h" #include "../part.h" namespace P3D { class SoftLink { public: AttachedPart attachedPartA; AttachedPart attachedPartB; SoftLink(const SoftLink& other) = delete; SoftLink& operator=(const SoftLink& other) = delete; SoftLink(SoftLink&& other) = delete; SoftLink& operator=(SoftLink&& other) = delete; virtual ~SoftLink(); virtual void update() = 0; SoftLink(const AttachedPart& attachedPartA, const AttachedPart& attachedPartB); GlobalCFrame getGlobalCFrameOfAttachmentA() const; GlobalCFrame getGlobalCFrameOfAttachmentB() const; CFrame getLocalCFrameOfAttachmentA() const; CFrame getLocalCFrameOfAttachmentB() const; CFrame getRelativeOfAttachmentA() const; CFrame getRelativeOfAttachmentB() const; Position getGlobalPositionOfAttachmentA() const; Position getGlobalPositionOfAttachmentB() const; Vec3 getLocalPositionOfAttachmentA() const; Vec3 getLocalPositionOfAttachmentB() const; Vec3 getRelativePositionOfAttachmentA() const; Vec3 getRelativePositionOfAttachmentB() const; }; }; ================================================ FILE: Physics3D/softlinks/springLink.cpp ================================================ #include "springLink.h" namespace P3D { SpringLink::SpringLink(const AttachedPart& partA, const AttachedPart& partB, double restLength, double stiffness) : SoftLink(partA, partB) , restLength(restLength) , stiffness(stiffness) {} void SpringLink::update() { Vec3 force = forceAppliedToTheLink(); this->attachedPartB.part->applyForce(this->getRelativePositionOfAttachmentA(), force); this->attachedPartA.part->applyForce(this->getRelativePositionOfAttachmentB(), -force); } Vec3 SpringLink::forceAppliedToTheLink() noexcept { Vec3 difference = this->getGlobalPositionOfAttachmentB() - this->getGlobalPositionOfAttachmentA(); double distance = length(difference); double magnitude = std::abs(distance - this->restLength) * this->stiffness; Vec3 forceDirection = normalize(difference); return forceDirection * -magnitude; } }; ================================================ FILE: Physics3D/softlinks/springLink.h ================================================ #pragma once #include "softLink.h" namespace P3D { class SpringLink : public SoftLink { public: double restLength; double stiffness; SpringLink(const AttachedPart& partA, const AttachedPart& partB, double restLength, double stiffness); ~SpringLink() override = default; SpringLink(const SpringLink& other) = delete; SpringLink& operator=(const SpringLink& other) = delete; SpringLink(SpringLink&& other) = delete; SpringLink& operator=(SpringLink&& other) = delete; void update() override; private: [[nodiscard]] Vec3 forceAppliedToTheLink() noexcept; }; }; ================================================ FILE: Physics3D/threading/physicsThread.cpp ================================================ #include "physicsThread.h" #include "../world.h" #include "../worldPhysics.h" #include "../misc/physicsProfiler.h" #include namespace P3D { using namespace std::chrono; static void emptyFunc(WorldPrototype*) {} PhysicsThread::PhysicsThread(WorldPrototype* world, UpgradeableMutex* worldMutex, void(&tickFunction)(WorldPrototype*), std::chrono::milliseconds tickSkipTimeout, unsigned int threadCount) : world(world), worldMutex(worldMutex), tickFunction(tickFunction), tickSkipTimeout(tickSkipTimeout), threadPool(threadCount == 0 ? std::thread::hardware_concurrency() : threadCount) {} PhysicsThread::PhysicsThread(WorldPrototype* world, UpgradeableMutex* worldMutex, std::chrono::milliseconds tickSkipTimeout, unsigned int threadCount) : PhysicsThread(world, worldMutex, emptyFunc, tickSkipTimeout, threadCount) {} PhysicsThread::PhysicsThread(void(&tickFunction)(WorldPrototype*), std::chrono::milliseconds tickSkipTimeout, unsigned int threadCount) : PhysicsThread(nullptr, nullptr, tickFunction, tickSkipTimeout, threadCount) {} PhysicsThread::PhysicsThread(std::chrono::milliseconds tickSkipTimeout, unsigned int threadCount) : PhysicsThread(nullptr, nullptr, emptyFunc, tickSkipTimeout, threadCount) {} PhysicsThread::~PhysicsThread() { this->stop(); } void PhysicsThread::runTick() { physicsMeasure.mark(PhysicsProcess::OTHER); this->world->tick(this->threadPool); physicsMeasure.mark(PhysicsProcess::OTHER); tickFunction(this->world); physicsMeasure.end(); GJKCollidesIterationStatistics.nextTally(); GJKNoCollidesIterationStatistics.nextTally(); EPAIterationStatistics.nextTally(); } void PhysicsThread::start() { assert(!this->shouldBeRunning); if(this->thread.joinable()) this->thread.join(); this->shouldBeRunning = true; this->thread = std::thread([this] () { time_point nextTarget = system_clock::now(); while (this->shouldBeRunning) { microseconds tickTime = microseconds((long long) (1000000 * this->world->deltaT / this->speed)); this->runTick(); nextTarget += tickTime; time_point curTime = system_clock::now(); if (curTime < nextTarget) { std::this_thread::sleep_until(nextTarget); } else { // We're behind schedule milliseconds tickSkipTimeout = this->tickSkipTimeout.load(); if (nextTarget < curTime - tickSkipTimeout) { double ticksToSkip = (curTime - nextTarget).count() / (1000.0 * tickSkipTimeout.count()); std::cout << "Can't keep up! Skipping " << ticksToSkip << " ticks!"; nextTarget = curTime; } } } }); } void PhysicsThread::stopAsync() { this->shouldBeRunning = false; } void PhysicsThread::stop() { this->stopAsync(); if(this->thread.joinable()) this->thread.join(); } bool PhysicsThread::isRunning() { return this->shouldBeRunning; } void PhysicsThread::toggleRunningAsync() { if(this->shouldBeRunning) { this->stopAsync(); } else { this->start(); } } void PhysicsThread::toggleRunning() { if(this->shouldBeRunning) { this->stop(); } else { this->start(); } } } ================================================ FILE: Physics3D/threading/physicsThread.h ================================================ #pragma once #include #include #include "threadPool.h" #include "upgradeableMutex.h" namespace P3D { class WorldPrototype; class PhysicsThread { std::thread thread; ThreadPool threadPool; std::atomic shouldBeRunning = false; public: std::atomic speed = 1.0; std::atomic tickSkipTimeout; WorldPrototype* world; UpgradeableMutex* worldMutex; void(*tickFunction)(WorldPrototype*); PhysicsThread(WorldPrototype* world, UpgradeableMutex* worldMutex, void(&tickFunction)(WorldPrototype*), std::chrono::milliseconds tickSkipTimeout = std::chrono::milliseconds(1000), unsigned int threadCount = 0); PhysicsThread(WorldPrototype* world, UpgradeableMutex* worldMutex, std::chrono::milliseconds tickSkipTimeout = std::chrono::milliseconds(1000), unsigned int threadCount = 0); PhysicsThread(void(&tickFunction)(WorldPrototype*), std::chrono::milliseconds tickSkipTimeout = std::chrono::milliseconds(1000), unsigned int threadCount = 0); PhysicsThread(std::chrono::milliseconds tickSkipTimeout = std::chrono::milliseconds(1000), unsigned int threadCount = 0); ~PhysicsThread(); // Runs one tick. The PhysicsThread must not be running! void runTick(); // Starts the PhysicsThread void start(); // Stops the PhysicsThread, and returns once it has been stopped completely void stop(); // instructs the PhysicsThread to stop, but returns immediately. PhysicsThread may still be running for some time before finally stopping void stopAsync(); bool isRunning(); // Starts if isRunning() == false, stop() if isRunning() == true void toggleRunning(); // Starts if isRunning() == false, stopAsync() if isRunning() == true void toggleRunningAsync(); }; } ================================================ FILE: Physics3D/threading/sharedLockGuard.h ================================================ #pragma once #include namespace P3D { class SharedLockGuard { std::shared_mutex& mutex; bool isHard = false; public: inline SharedLockGuard(std::shared_mutex& mutex) : mutex(mutex) { mutex.lock_shared(); } inline ~SharedLockGuard() { if(isHard) { mutex.unlock(); } else { mutex.unlock_shared(); } } inline void upgrade() { if(isHard) { throw "Attemt to upgrade already hard lock!"; } mutex.unlock_shared(); mutex.lock(); isHard = true; } inline void downgrade() { if(!isHard) { throw "Attempt to downgrade already soft lock!"; } mutex.unlock(); mutex.lock_shared(); isHard = false; } }; class UnlockOnDestroy { std::shared_mutex& mutex; public: /* assumes it is given a locked mutex Unlocks when destroyed*/ inline UnlockOnDestroy(std::shared_mutex& mutex) : mutex(mutex) {} ~UnlockOnDestroy() { mutex.unlock(); } }; class UnlockSharedOnDestroy { std::shared_mutex& mutex; public: /* assumes it is given a shared_locked mutex Unlocks when destroyed*/ inline UnlockSharedOnDestroy(std::shared_mutex& mutex) : mutex(mutex) {} ~UnlockSharedOnDestroy() { mutex.unlock_shared(); } }; }; ================================================ FILE: Physics3D/threading/threadPool.h ================================================ #pragma once #include #include #include #include #include namespace P3D { class ThreadPool { std::function funcToRun = []() {}; std::vector threads{}; // protects shouldStart, threadsWorking and their condition variables std::mutex mtx; // this tells the threads to start working std::condition_variable threadStarter; bool shouldStart = false; // this keeps track of the number of threads that are currently performing work, main thread may only return once all threads have finished working. std::condition_variable threadsFinished; int threadsWorking = 0; // No explicit protection required since only the main thread may write to it and only in the destructor, so not when a new job is presented bool shouldExit = false; public: ThreadPool(unsigned int numThreads) : threads(numThreads - 1) { for(std::thread& t : threads) { t = std::thread([this]() { std::unique_lock selfLock(mtx); // locks mtx while(true) { threadStarter.wait(selfLock, [this]() -> bool {return shouldStart; }); // this unlocks the mutex. And relocks when exiting threadsWorking++; selfLock.unlock(); if(shouldExit) break; funcToRun(); selfLock.lock(); shouldStart = false; // once any thread finishes we assume we've reached the end, keep all threads from threadsWorking--; if(threadsWorking == 0) { threadsFinished.notify_one(); } } }); } } ThreadPool() : ThreadPool(std::thread::hardware_concurrency()) {} // cleanup ~ThreadPool() { shouldExit = true; mtx.lock(); shouldStart = true; mtx.unlock(); threadStarter.notify_all();// all threads start running for(std::thread& t : threads) t.join(); // let threads exit } // this work function may only return once all work has been completed void doInParallel(std::function&& work) { funcToRun = std::move(work); std::unique_lock selfLock(mtx); // locks mtx shouldStart = true; selfLock.unlock(); threadStarter.notify_all();// all threads start running funcToRun(); selfLock.lock(); shouldStart = false; threadsFinished.wait(selfLock, [this]() -> bool {return threadsWorking == 0; }); selfLock.unlock(); } }; }; ================================================ FILE: Physics3D/threading/upgradeableMutex.cpp ================================================ #include "upgradeableMutex.h" // disable "Caller failing to hold lock 'this->writerLockout' before calling function 'std::_Mutex_base::unlock'" warning. #pragma warning(disable : 26110) namespace P3D { // Unlocked -> Exclusive void UpgradeableMutex::lock() { writerLockout.lock(); stateMutex.lock(); } // Exclusive -> Unlocked void UpgradeableMutex::unlock() { stateMutex.unlock(); writerLockout.unlock(); } // Unlocked -> Shared void UpgradeableMutex::lock_shared() { stateMutex.lock_shared(); } // Shared -> Unlocked void UpgradeableMutex::unlock_shared() { stateMutex.unlock_shared(); } // Unlocked -> Shared Upgradeable void UpgradeableMutex::lock_upgradeable() { writerLockout.lock(); stateMutex.lock_shared(); } // Shared Upgradeable -> Unlocked void UpgradeableMutex::unlock_upgradeable() { stateMutex.unlock_shared(); writerLockout.unlock(); } // Shared Upgradeable -> Exclusive void UpgradeableMutex::upgrade() { stateMutex.unlock_shared(); stateMutex.lock(); } // Exclusive -> Shared Upgradeable void UpgradeableMutex::downgrade() { stateMutex.unlock(); stateMutex.lock_shared(); } // Exclusive -> Shared void UpgradeableMutex::final_downgrade() { stateMutex.unlock(); stateMutex.lock_shared(); writerLockout.unlock(); } // Shared Upgradeable -> Shared void UpgradeableMutex::cancel_upgrade() { writerLockout.unlock(); } }; ================================================ FILE: Physics3D/threading/upgradeableMutex.h ================================================ #pragma once #include #include namespace P3D { /* This mutex allows minimal reader interference for operations that require read/write exclusivity. It allows a read lock with promise of upgrading to a write lock later, ensuring no writers in between. For everything else, acts like a shared_mutex Each actor has 4 states: - Unlocked - Shared - Shared Upgradeable - Exclusive Most basic usage: mtx.lock_shared_upgradeable(); // Shared read operations here, writers must wait until unlock. // Build some state here from reading, other readers allowed. mtx.upgrade(); // Write here, other readers are excluded, other writers are exluded. // State is consistent with read section. mtx.unlock(); Read-Write-Read usage: mtx.lock_shared_upgradeable(); // Shared read operations here, writers must wait until unlock. // Build some state here from reading, other readers allowed. mtx.upgrade(); // Write here, other readers are excluded, other writers are exluded. // State is consistent with read section. mtx.downgrade(); // Read section, still maintain upgradability, other readers allowed. mtx.upgrade(); // Write section mtx.final_downgrade(); // Read section again, other readers allowed. Writers still excluded. // State is consistent with previous read and write sections. mtx.unlock_shared(); // Other writers now allowed. All function names are formatted snake_case to be compatible with std::lock and std::shared_lock */ class UpgradeableMutex { std::shared_mutex stateMutex; // Only one thread can be upgrading or have an exclusive lock at a time std::mutex writerLockout; public: // Unlocked -> Exclusive void lock(); // Exclusive -> Unlocked void unlock(); // Unlocked -> Shared void lock_shared(); // Shared -> Unlocked void unlock_shared(); // Unlocked -> Shared Upgradeable void lock_upgradeable(); // Shared Upgradeable -> Unlocked void unlock_upgradeable(); // Shared Upgradeable -> Exclusive void upgrade(); // Exclusive -> Shared Upgradeable void downgrade(); // Exclusive -> Shared void final_downgrade(); // calling final_downgrade instead of downgrade will allow another lock_upgradeable to make a reservation // Shared Upgradeable -> Shared void cancel_upgrade(); }; }; ================================================ FILE: Physics3D/world.cpp ================================================ #include "world.h" #include #include "misc/debug.h" #include "layer.h" #include "misc/validityHelper.h" #include "worldIteration.h" #include "threading/threadPool.h" namespace P3D { // #define CHECK_WORLD_VALIDITY #ifdef CHECK_WORLD_VALIDITY #define ASSERT_VALID if (!isValid()) throw "World not valid!"; #else #define ASSERT_VALID #endif #pragma region worldValidity bool WorldPrototype::isValid() const { for(const MotorizedPhysical* phys : this->physicals) { if(phys->getWorld() != this) { Debug::logError("physicals's world is not correct!"); DEBUGBREAK; return false; } if(!isMotorizedPhysicalValid(phys)) { Debug::logError("Physical invalid!"); DEBUGBREAK; return false; } bool result = true; phys->forEachPart([&result](const Part& part) { if(part.layer == nullptr) { Debug::logError("Part in physical has no layer!"); DEBUGBREAK; result = false; } else { if(!part.layer->tree.contains(&part)) { Debug::logError("Part not in tree!"); DEBUGBREAK; result = false; } } }); if(result == false) return false; } for(const ColissionLayer& cl : layers) { for(const WorldLayer& l : cl.subLayers) { treeValidCheck(l.tree); for(const Part& p : l.tree) { if(p.layer != &l) { Debug::logError("Part contained in layer, but it's layer field is not the layer"); DEBUGBREAK; return false; } } } } return true; } #pragma endregion WorldPrototype::WorldPrototype(double deltaT) : deltaT(deltaT), layers(), colissionMask() { layers.emplace_back(this, true); } WorldPrototype::~WorldPrototype() { } static std::pair pairLayers(int layer1, int layer2) { if(layer1 < layer2) { return std::make_pair(layer1, layer2); } else { return std::make_pair(layer2, layer1); } } bool WorldPrototype::doLayersCollide(int layer1, int layer2) const { if(layer1 == layer2) { return layers[layer1].collidesInternally; } else { std::pair checkPair = pairLayers(layer1, layer2); for(std::pair itemInColissionMask : colissionMask) { if(itemInColissionMask == checkPair) { return true; } } return false; } } void WorldPrototype::setLayersCollide(int layer1, int layer2, bool collide) { if(layer1 == layer2) { layers[layer1].collidesInternally = collide; } else { std::pair checkAddPair = pairLayers(layer1, layer2); for(std::pair& itemInColissionMask : colissionMask) { if(itemInColissionMask == checkAddPair) { if(!collide) { // remove for no collide itemInColissionMask = colissionMask.back(); colissionMask.pop_back(); } return; } } if(collide) { colissionMask.push_back(checkAddPair); } } } int WorldPrototype::createLayer(bool collidesInternally, bool collidesWithOthers) { int layerIndex = static_cast(layers.size()); layers.emplace_back(this, collidesInternally); if(collidesWithOthers) { for(int i = 0; i < layerIndex; i++) { colissionMask.emplace_back(i, layerIndex); } } return layerIndex; } void WorldPrototype::deleteLayer(int layerIndex, int layerToMoveTo) { } static void createNodeFor(P3D::BoundsTree& tree, MotorizedPhysical* phys) { if(phys->isSinglePart()) { tree.add(phys->getMainPart()); } else { P3D::TrunkAllocator& alloc = tree.getPrototype().getAllocator(); P3D::TreeTrunk* newNode = alloc.allocTrunk(); int newNodeSize = 0; phys->forEachPart([&alloc, &newNode, &newNodeSize, &tree](Part& p) { newNodeSize = P3D::addRecursive(alloc, *newNode, newNodeSize, P3D::TreeNodeRef(static_cast(&p)), p.getBounds()); }); tree.getPrototype().addGroupTrunk(newNode, newNodeSize); } } void WorldPrototype::addPart(Part* part, int layerIndex) { ASSERT_VALID; if(part->layer) { Debug::logWarn("This part is already in a world"); ASSERT_VALID; return; } Physical* partPhys = part->ensureHasPhysical(); physicals.push_back(partPhys->mainPhysical); WorldLayer* worldLayer = &layers[layerIndex].subLayers[ColissionLayer::FREE_PARTS_LAYER]; partPhys->mainPhysical->forEachPart([worldLayer](Part& p) { p.layer = worldLayer; }); createNodeFor(worldLayer->tree, partPhys->mainPhysical); objectCount += partPhys->mainPhysical->getNumberOfPartsInThisAndChildren(); ASSERT_VALID; partPhys->mainPhysical->forEachPart([this](Part& p) { this->onPartAdded(&p); }); ASSERT_VALID; } static void createNewNodeFor(MotorizedPhysical* motorPhys, BoundsTree& layer, Part* repPart) { size_t totalParts = 0; motorPhys->forEachPart([&layer, &totalParts](Part& p) { if(&p.layer->tree == &layer) { totalParts++; } }); assert(totalParts >= 1); if(totalParts == 1) { layer.add(repPart); } else { TrunkAllocator& alloc = layer.getPrototype().getAllocator(); TreeTrunk* newNode = alloc.allocTrunk(); int newNodeSize = 0; motorPhys->forEachPart([&alloc, &newNode, &newNodeSize, &layer, repPart](Part& p) { if(&p.layer->tree == &layer) { newNodeSize = addRecursive(alloc, *newNode, newNodeSize, TreeNodeRef(static_cast(&p)), p.getBounds()); } }); layer.getPrototype().addGroupTrunk(newNode, newNodeSize); } } void WorldPrototype::addPhysicalWithExistingLayers(MotorizedPhysical* motorPhys) { physicals.push_back(motorPhys); std::vector foundLayers = findAllLayersIn(motorPhys); for(const FoundLayerRepresentative& l : foundLayers) { createNewNodeFor(motorPhys, l.layer->tree, l.part); } ASSERT_VALID; } void WorldPrototype::addTerrainPart(Part* part, int layerIndex) { objectCount++; WorldLayer* worldLayer = &layers[layerIndex].subLayers[ColissionLayer::TERRAIN_PARTS_LAYER]; part->layer = worldLayer; worldLayer->addPart(part); ASSERT_VALID; this->onPartAdded(part); ASSERT_VALID; } void WorldPrototype::removePart(Part* part) { ASSERT_VALID; part->removeFromWorld(); ASSERT_VALID; } void WorldPrototype::deletePart(Part* partToDelete) const { delete partToDelete; } void WorldPrototype::clear() { this->constraints.clear(); this->externalForces.clear(); for(MotorizedPhysical* phys : this->physicals) { delete phys; } this->physicals.clear(); std::vector partsToDelete; this->forEachPart([&partsToDelete](Part& part) { part.parent = nullptr; part.layer = nullptr; partsToDelete.push_back(&part); }); this->objectCount = 0; for(ColissionLayer& cl : this->layers) { for(WorldLayer& layer : cl.subLayers) { layer.tree.clear(); } } for(Part* p : partsToDelete) { this->onPartRemoved(p); this->deletePart(p); } } int WorldPrototype::getLayerCount() const { return static_cast(this->layers.size()); } void WorldPrototype::optimizeLayers() { for(ColissionLayer& layer : layers) { layer.subLayers[ColissionLayer::TERRAIN_PARTS_LAYER].optimize(); } ASSERT_VALID; } static void assignLayersForPhysicalRecurse(const Physical& phys, std::vector>>& foundLayers) { phys.rigidBody.forEachPart([&foundLayers](const Part& part) { for(std::pair>& knownLayer : foundLayers) { if(part.layer == knownLayer.first) { knownLayer.second.push_back(&part); return; } } foundLayers.emplace_back(part.layer, std::vector{&part}); }); for(const ConnectedPhysical& conPhys : phys.childPhysicals) { assignLayersForPhysicalRecurse(conPhys, foundLayers); } } void WorldPrototype::notifyPhysicalHasBeenSplit(const MotorizedPhysical* mainPhysical, MotorizedPhysical* newlySplitPhysical) { assert(mainPhysical->getWorld() == this); assert(newlySplitPhysical->getWorld() == nullptr); this->physicals.push_back(newlySplitPhysical); std::vector>> layersThatNeedToBeSplit; assignLayersForPhysicalRecurse(*newlySplitPhysical, layersThatNeedToBeSplit); for(const std::pair>& layer : layersThatNeedToBeSplit) { layer.first->splitGroup(layer.second.begin(), layer.second.end()); } // split object tree // TODO: The findGroupFor and grap calls can be merged as an optimization /*NodeStack stack = objectTree.findGroupFor(newlySplitPhysical->getMainPart(), newlySplitPhysical->getMainPart()->getBounds()); TreeNode* node = *stack; TreeNode newNode = objectTree.grab(newlySplitPhysical->getMainPart(), newlySplitPhysical->getMainPart()->getBounds()); if(!newNode.isGroupHead) { newNode.isGroupHead = true; // node should still be valid at this time for(TreeIterator iter(*node); iter != IteratorEnd();) { TreeNode* objectNode = *iter; Part* part = static_cast(objectNode->object); if(part->parent->mainPhysical == newlySplitPhysical) { newNode.addInside(iter.remove()); } else { ++iter; } } stack.updateBoundsAllTheWayToTop(); } objectTree.add(std::move(newNode)); assert(this->isValid());*/ } void WorldPrototype::onPartAdded(Part* newPart) {} void WorldPrototype::onPartRemoved(Part* removedPart) {} void WorldPrototype::addExternalForce(ExternalForce* force) { externalForces.push_back(force); } void WorldPrototype::removeExternalForce(ExternalForce* force) { externalForces.erase(std::remove(externalForces.begin(), externalForces.end(), force)); } }; ================================================ FILE: Physics3D/world.h ================================================ #pragma once #include #include #include #include "part.h" #include "physical.h" #include "constraints/constraintGroup.h" #include "softlinks/softLink.h" #include "externalforces/externalForce.h" #include "colissionBuffer.h" namespace P3D { class Physical; class MotorizedPhysical; class ConnectedPhysical; class Part; class WorldLayer; class ColissionLayer; class ThreadPool; class WorldPrototype { private: friend class Physical; friend class MotorizedPhysical; friend class ConnectedPhysical; friend class Part; friend class WorldLayer; friend class ColissionLayer; /* Splits newlySplitPhysical from mainPhysical in the world tree, also adds the new physical to the list of physicals */ void notifyPhysicalHasBeenSplit(const MotorizedPhysical* mainPhysical, MotorizedPhysical* newlySplitPhysical); protected: // event handlers virtual void onPartAdded(Part* newPart); virtual void onPartRemoved(Part* removedPart); // called when the part has already been removed from the world virtual void deletePart(Part* partToDelete) const; public: // AABB trees for storing parts std::vector layers; // Sparse matrix listing layer-layer colissions std::vector> colissionMask; // All physicals std::vector physicals; void addPhysicalWithExistingLayers(MotorizedPhysical* motorPhys); // Extra world features std::vector externalForces; std::vector constraints; std::vector softLinks; void addLink(SoftLink* link); ColissionBuffer curColissions; size_t age = 0; size_t objectCount = 0; double deltaT; WorldPrototype(double deltaT); ~WorldPrototype(); WorldPrototype(const WorldPrototype&) = delete; WorldPrototype& operator=(const WorldPrototype&) = delete; WorldPrototype(WorldPrototype&&) = delete; WorldPrototype& operator=(WorldPrototype&&) = delete; virtual void tick(ThreadPool& threadPool); void tick(); virtual void addPart(Part* part, int layerIndex = 0); virtual void removePart(Part* part); void addTerrainPart(Part* part, int layerIndex = 0); bool doLayersCollide(int layer1, int layer2) const; void setLayersCollide(int layer1, int layer2, bool collide); int createLayer(bool collidesInternally, bool collidesWithOthers); // deletes a layer, moves all parts from the deleted layer to layerToMoveTo void deleteLayer(int layerIndex, int layerToMoveTo); void optimizeLayers(); // removes everything from this world, parts, physicals, forces, constraints void clear(); inline size_t getPartCount() const { return objectCount; } int getLayerCount() const; virtual double getTotalKineticEnergy() const; virtual double getTotalPotentialEnergy() const; virtual double getPotentialEnergyOfPhysical(const MotorizedPhysical& p) const; virtual double getTotalEnergy() const; void addExternalForce(ExternalForce* force); void removeExternalForce(ExternalForce* force); virtual bool isValid() const; // include worldIteration.h to use // expects a function of the form void(Part& part) template void forEachPart(const Func& funcToRun) const; // include worldIteration.h to use // expects a function of the form void(Part& part) template void forEachPartFiltered(const Filter& filter, const Func& funcToRun) const; }; template class World : public WorldPrototype { public: World(double deltaT) : WorldPrototype(deltaT) {} virtual void onCollide(Part* partA, Part* partB) {} virtual void onCollide(T* partA, T* partB) {} virtual void onPartAdded(T* part) {} virtual void onPartRemoved(T* part) {} virtual void deletePart(T* part) const { delete part; } void onPartAdded(Part* part) final override { this->onPartAdded(static_cast(part)); } void onPartRemoved(Part* part) final override { this->onPartRemoved(static_cast(part)); } // called when the part has already been removed from the world void deletePart(Part* partToDelete) const final override { this->deletePart(static_cast(partToDelete)); } // include worldIteration.h to use // expects a function of the form void(T& part) template void forEachPart(const Func& funcToRun) const; // include worldIteration.h to use // expects a function of the form void(T& part) template void forEachPartFiltered(const Filter& filter, const Func& funcToRun) const; }; }; ================================================ FILE: Physics3D/worldIteration.h ================================================ #pragma once #include "world.h" #include "layer.h" namespace P3D { // expects a function of the form void(Part& part) template void WorldPrototype::forEachPart(const Func& funcToRun) const { for(const ColissionLayer& layer : this->layers) { layer.forEach(funcToRun); } } // expects a function of the form void(Part& part) template void WorldPrototype::forEachPartFiltered(const Filter& filter, const Func& funcToRun) const { for(const ColissionLayer& layer : this->layers) { layer.forEachFiltered(filter, funcToRun); } } // expects a function of the form void(T& part) template template void World::forEachPart(const Func& funcToRun) const { static_cast(this)->forEachPart([&funcToRun](Part& part) {funcToRun(static_cast(part)); }); } // expects a function of the form void(T& part) template template void World::forEachPartFiltered(const Filter& filter, const Func& funcToRun) const { static_cast(this)->forEachPartFiltered(filter, [&funcToRun](Part& part) {funcToRun(static_cast(part)); }); } }; ================================================ FILE: Physics3D/worldPhysics.cpp ================================================ #include "worldPhysics.h" #include "world.h" #include "layer.h" #include "math/mathUtil.h" #include "math/linalg/vec.h" #include "math/linalg/trigonometry.h" #include "misc/debug.h" #include "misc/physicsProfiler.h" #include #include #include #define COLLISSION_DEPTH_FORCE_MULTIPLIER 2000 namespace P3D { /* exitVector is the distance p2 must travel so that the shapes are no longer colliding */ void handleCollision(Part& part1, Part& part2, Position collisionPoint, Vec3 exitVector) { Debug::logPoint(collisionPoint, Debug::INTERSECTION); MotorizedPhysical& phys1 = *part1.getPhysical()->mainPhysical; MotorizedPhysical& phys2 = *part2.getPhysical()->mainPhysical; double sizeOrder = std::min(part1.maxRadius, part2.maxRadius); if(lengthSquared(exitVector) <= 1E-8 * sizeOrder * sizeOrder) { return; // don't do anything for very small colissions } Vec3 collissionRelP1 = collisionPoint - phys1.getCenterOfMass(); Vec3 collissionRelP2 = collisionPoint - phys2.getCenterOfMass(); double inertia1A = phys1.getInertiaOfPointInDirectionRelative(collissionRelP1, exitVector); double inertia2A = phys2.getInertiaOfPointInDirectionRelative(collissionRelP2, exitVector); double combinedInertia = 1 / (1 / inertia1A + 1 / inertia2A); // Friction double staticFriction = part1.properties.friction * part2.properties.friction; double dynamicFriction = part1.properties.friction * part2.properties.friction; Vec3 depthForce = -exitVector * (COLLISSION_DEPTH_FORCE_MULTIPLIER * combinedInertia); phys1.applyForce(collissionRelP1, depthForce); phys2.applyForce(collissionRelP2, -depthForce); Vec3 part1ToColission = collisionPoint - part1.getPosition(); Vec3 part2ToColission = collisionPoint - part2.getPosition(); Vec3 relativeVelocity = (part1.getMotion().getVelocityOfPoint(part1ToColission) - part1.properties.conveyorEffect) - (part2.getMotion().getVelocityOfPoint(part2ToColission) - part2.properties.conveyorEffect); bool isImpulseColission = relativeVelocity * exitVector > 0; Vec3 impulse; double combinedBouncyness = part1.properties.bouncyness * part2.properties.bouncyness; if(isImpulseColission) { // moving towards the other object Vec3 desiredAccel = -exitVector * (relativeVelocity * exitVector) / lengthSquared(exitVector) * (1.0 + combinedBouncyness); Vec3 zeroRelVelImpulse = desiredAccel * combinedInertia; impulse = zeroRelVelImpulse; phys1.applyImpulse(collissionRelP1, impulse); phys2.applyImpulse(collissionRelP2, -impulse); relativeVelocity += desiredAccel; } Vec3 slidingVelocity = exitVector % relativeVelocity % exitVector / lengthSquared(exitVector); // Compute combined inertia in the horizontal direction double inertia1B = phys1.getInertiaOfPointInDirectionRelative(collissionRelP1, slidingVelocity); double inertia2B = phys2.getInertiaOfPointInDirectionRelative(collissionRelP2, slidingVelocity); double combinedHorizontalInertia = 1 / (1 / inertia1B + 1 / inertia2B); if(isImpulseColission) { Vec3 maxFrictionImpulse = -exitVector % impulse % exitVector / lengthSquared(exitVector) * staticFriction; Vec3 stopFricImpulse = -slidingVelocity * combinedHorizontalInertia; Vec3 fricImpulse = (lengthSquared(stopFricImpulse) < lengthSquared(maxFrictionImpulse)) ? stopFricImpulse : maxFrictionImpulse; phys1.applyImpulse(collissionRelP1, fricImpulse); phys2.applyImpulse(collissionRelP2, -fricImpulse); } double normalForce = length(depthForce); double frictionForce = normalForce * dynamicFriction; double slidingSpeed = length(slidingVelocity) + 1E-100; Vec3 dynamicFricForce; double dynamicSaturationSpeed = sizeOrder * 0.01; if(slidingSpeed > dynamicSaturationSpeed) { dynamicFricForce = -slidingVelocity / slidingSpeed * frictionForce; } else { double effectFactor = slidingSpeed / (dynamicSaturationSpeed); dynamicFricForce = -slidingVelocity / slidingSpeed * frictionForce * effectFactor; } phys1.applyForce(collissionRelP1, dynamicFricForce); phys2.applyForce(collissionRelP2, -dynamicFricForce); assert(phys1.isValid()); assert(phys2.isValid()); } /* exitVector is the distance p2 must travel so that the shapes are no longer colliding */ void handleTerrainCollision(Part& part1, Part& part2, Position collisionPoint, Vec3 exitVector) { Debug::logPoint(collisionPoint, Debug::INTERSECTION); MotorizedPhysical& phys1 = *part1.getPhysical()->mainPhysical; double sizeOrder = std::min(part1.maxRadius, part2.maxRadius); if(lengthSquared(exitVector) <= 1E-8 * sizeOrder * sizeOrder) { return; // don't do anything for very small colissions } Vec3 collissionRelP1 = collisionPoint - phys1.getCenterOfMass(); double inertia = phys1.getInertiaOfPointInDirectionRelative(collissionRelP1, exitVector); // Friction double staticFriction = part1.properties.friction * part2.properties.friction; double dynamicFriction = part1.properties.friction * part2.properties.friction; Vec3 depthForce = -exitVector * (COLLISSION_DEPTH_FORCE_MULTIPLIER * inertia); phys1.applyForce(collissionRelP1, depthForce); //Vec3 rigidBodyToPart = part1.getCFrame().getPosition() - part1.getPhysical()->rigidBody.getCenterOfMass(); Vec3 partToColission = collisionPoint - part1.getPosition(); Vec3 relativeVelocity = part1.getMotion().getVelocityOfPoint(partToColission) - part1.properties.conveyorEffect + part2.getCFrame().localToRelative(part2.properties.conveyorEffect); bool isImpulseColission = relativeVelocity * exitVector > 0; Vec3 impulse; double combinedBouncyness = part1.properties.bouncyness * part2.properties.bouncyness; if(isImpulseColission) { // moving towards the other object Vec3 desiredAccel = -exitVector * (relativeVelocity * exitVector) / lengthSquared(exitVector) * (1.0 + combinedBouncyness); Vec3 zeroRelVelImpulse = desiredAccel * inertia; impulse = zeroRelVelImpulse; phys1.applyImpulse(collissionRelP1, impulse); relativeVelocity += desiredAccel; } Vec3 slidingVelocity = exitVector % relativeVelocity % exitVector / lengthSquared(exitVector); // Compute combined inertia in the horizontal direction double combinedHorizontalInertia = phys1.getInertiaOfPointInDirectionRelative(collissionRelP1, slidingVelocity); if(isImpulseColission) { Vec3 maxFrictionImpulse = -exitVector % impulse % exitVector / lengthSquared(exitVector) * staticFriction; Vec3 stopFricImpulse = -slidingVelocity * combinedHorizontalInertia; Vec3 fricImpulse = (lengthSquared(stopFricImpulse) < lengthSquared(maxFrictionImpulse)) ? stopFricImpulse : maxFrictionImpulse; phys1.applyImpulse(collissionRelP1, fricImpulse); } double normalForce = length(depthForce); double frictionForce = normalForce * dynamicFriction; double slidingSpeed = length(slidingVelocity) + 1E-100; Vec3 dynamicFricForce; double dynamicSaturationSpeed = sizeOrder * 0.01; if(slidingSpeed > dynamicSaturationSpeed) { dynamicFricForce = -slidingVelocity / slidingSpeed * frictionForce; } else { double effectFactor = slidingSpeed / (dynamicSaturationSpeed); dynamicFricForce = -slidingVelocity / slidingSpeed * frictionForce * effectFactor; } phys1.applyForce(collissionRelP1, dynamicFricForce); assert(phys1.isValid()); } /* ===== World Tick ===== */ void WorldPrototype::tick(ThreadPool& threadPool) { tickWorldUnsynchronized(*this, threadPool); } void WorldPrototype::tick() { ThreadPool singleThreadPool(1); tickWorldUnsynchronized(*this, singleThreadPool); } void tickWorldUnsynchronized(WorldPrototype& world, ThreadPool& threadPool) { physicsMeasure.mark(PhysicsProcess::COLISSION_OTHER); findColissionsParallel(world, world.curColissions, threadPool); physicsMeasure.mark(PhysicsProcess::EXTERNALS); applyExternalForces(world); physicsMeasure.mark(PhysicsProcess::COLISSION_HANDLING); handleColissions(world.curColissions); physicsMeasure.mark(PhysicsProcess::OTHER); intersectionStatistics.nextTally(); physicsMeasure.mark(PhysicsProcess::CONSTRAINTS); handleConstraints(world); physicsMeasure.mark(PhysicsProcess::UPDATING); update(world); } void tickWorldSynchronized(WorldPrototype& world, ThreadPool& threadPool, UpgradeableMutex& worldMutex) { physicsMeasure.mark(PhysicsProcess::WAIT_FOR_LOCK); worldMutex.lock_upgradeable(); physicsMeasure.mark(PhysicsProcess::COLISSION_OTHER); findColissionsParallel(world, world.curColissions, threadPool); physicsMeasure.mark(PhysicsProcess::EXTERNALS); applyExternalForces(world); physicsMeasure.mark(PhysicsProcess::COLISSION_HANDLING); handleColissions(world.curColissions); physicsMeasure.mark(PhysicsProcess::OTHER); intersectionStatistics.nextTally(); physicsMeasure.mark(PhysicsProcess::CONSTRAINTS); handleConstraints(world); physicsMeasure.mark(PhysicsProcess::WAIT_FOR_LOCK); worldMutex.upgrade(); physicsMeasure.mark(PhysicsProcess::UPDATING); update(world); physicsMeasure.mark(PhysicsProcess::WAIT_FOR_LOCK); worldMutex.unlock(); } void applyExternalForces(WorldPrototype& world) { for(ExternalForce* force : world.externalForces) { force->apply(&world); } } PartIntersection safeIntersects(const Part& p1, const Part& p2) { #ifdef CATCH_INTERSECTION_ERRORS try { return p1.intersects(p2); } catch(const std::exception& err) { Debug::logError("Error occurred during intersection: %s", err.what()); Debug::saveIntersectionError(p1, p2, "colError"); throw err; } catch(...) { Debug::logError("Unknown error occured during intersection"); Debug::saveIntersectionError(p1, p2, "colError"); throw "exit"; } #else return p1.intersects(p2); #endif } void refineColissions(std::vector& colissions) { for (size_t i = 0; i < colissions.size();) { Colission& col = colissions[i]; PartIntersection result = safeIntersects(*col.p1, *col.p2); if (result.intersects) { intersectionStatistics.addToTally(IntersectionResult::COLISSION, 1); // add extra information col.intersection = result.intersection; col.exitVector = result.exitVector; i++; } else { intersectionStatistics.addToTally(IntersectionResult::GJK_REJECT, 1); col = std::move(colissions.back()); colissions.pop_back(); } } } void parallelRefineColissions(ThreadPool& threadPool, std::vector& colissions) { std::vector wantedColission; const size_t workEnd = colissions.size(); size_t currIndex = 0; std::mutex colissionMutex, statsMutex, indexMutex, vecMutex; threadPool.doInParallel([&] { while(true) { indexMutex.lock(); size_t claimedWork = currIndex; currIndex++; indexMutex.unlock(); if(claimedWork >= workEnd) { break; } Colission col = colissions[claimedWork]; PartIntersection result = safeIntersects(*col.p1, *col.p2); if(result.intersects) { statsMutex.lock(); intersectionStatistics.addToTally(IntersectionResult::COLISSION, 1); statsMutex.unlock(); // add extra information col.intersection = result.intersection; col.exitVector = result.exitVector; vecMutex.lock(); wantedColission.push_back(col); vecMutex.unlock(); } else { statsMutex.lock(); intersectionStatistics.addToTally(IntersectionResult::GJK_REJECT, 1); statsMutex.unlock(); } } }); colissions.swap(wantedColission); } void findColissions(WorldPrototype& world, ColissionBuffer& curColissions) { curColissions.clear(); for(const ColissionLayer& layer : world.layers) { if(layer.collidesInternally) { layer.getInternalColissions(curColissions); } } for(std::pair collidingLayers : world.colissionMask) { getColissionsBetween(world.layers[collidingLayers.first], world.layers[collidingLayers.second], curColissions); } refineColissions(curColissions.freePartColissions); refineColissions(curColissions.freeTerrainColissions); } void findColissionsParallel(WorldPrototype& world, ColissionBuffer& curColissions, ThreadPool& threadPool) { curColissions.clear(); for(const ColissionLayer& layer : world.layers) { if(layer.collidesInternally) { layer.getInternalColissions(curColissions); } } for(std::pair collidingLayers : world.colissionMask) { getColissionsBetween(world.layers[collidingLayers.first], world.layers[collidingLayers.second], curColissions); } parallelRefineColissions(threadPool, curColissions.freePartColissions); parallelRefineColissions(threadPool, curColissions.freeTerrainColissions); } void handleColissions(ColissionBuffer& curColissions) { for(Colission c : curColissions.freePartColissions) { handleCollision(*c.p1, *c.p2, c.intersection, c.exitVector); } for(Colission c : curColissions.freeTerrainColissions) { handleTerrainCollision(*c.p1, *c.p2, c.intersection, c.exitVector); } } void handleConstraints(WorldPrototype& world) { for(const ConstraintGroup& group : world.constraints) { group.apply(); } } void update(WorldPrototype& world) { for(MotorizedPhysical* physical : world.physicals) { physical->update(world.deltaT); } for(ColissionLayer& layer : world.layers) { layer.refresh(); } world.age++; for(SoftLink* springLink : world.softLinks) { springLink->update(); } } double WorldPrototype::getTotalKineticEnergy() const { double total = 0.0; for(const MotorizedPhysical* p : this->physicals) { total += p->getKineticEnergy(); } return total; } double WorldPrototype::getTotalPotentialEnergy() const { double total = 0.0; for(ExternalForce* force : externalForces) { total += force->getTotalPotentialEnergyForThisForce(this); } return total; } double WorldPrototype::getPotentialEnergyOfPhysical(const MotorizedPhysical& p) const { double total = 0.0; for(ExternalForce* force : externalForces) { total += force->getPotentialEnergyForObject(this, p); } return total; } double WorldPrototype::getTotalEnergy() const { return getTotalKineticEnergy() + getTotalPotentialEnergy(); } void WorldPrototype::addLink(SoftLink* link) { softLinks.push_back(link); } }; ================================================ FILE: Physics3D/worldPhysics.h ================================================ #pragma once #include "part.h" #include "math/linalg/vec.h" #include "math/position.h" #include "colissionBuffer.h" #include "world.h" #include "threading/threadPool.h" #include "threading/upgradeableMutex.h" namespace P3D { void handleCollision(Part& part1, Part& part2, Position collisionPoint, Vec3 exitVector); void handleTerrainCollision(Part& part1, Part& part2, Position collisionPoint, Vec3 exitVector); PartIntersection safeIntersects(const Part& p1, const Part& p2); void refineColissions(std::vector& colissions); void parallelRefineColissions(ThreadPool& threadPool, std::vector& colissions); void findColissions(WorldPrototype& world, ColissionBuffer& curColissions); void findColissionsParallel(WorldPrototype& world, ColissionBuffer& curColissions, ThreadPool& threadPool); void applyExternalForces(WorldPrototype& world); void handleColissions(ColissionBuffer& curColissions); void handleConstraints(WorldPrototype& world); void update(WorldPrototype& world); void tickWorldUnsynchronized(WorldPrototype& world, ThreadPool& threadPool); void tickWorldSynchronized(WorldPrototype& world, ThreadPool& threadPool, UpgradeableMutex& worldMutex); }; ================================================ FILE: Physics3D.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29403.142 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Physics3D", "Physics3D\Physics3D.vcxproj", "{DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "application", "application\application.vcxproj", "{4DB099FF-8264-4262-8195-EBA4109123CC}" ProjectSection(ProjectDependencies) = postProject {ADC11C63-6986-41DC-9297-FC5DC58A2B55} = {ADC11C63-6986-41DC-9297-FC5DC58A2B55} {60F3448D-6447-47CD-BF64-8762F8DB9361} = {60F3448D-6447-47CD-BF64-8762F8DB9361} {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} {13E417C8-0C80-482C-A415-8C11A5C770DA} = {13E417C8-0C80-482C-A415-8C11A5C770DA} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util", "util\util.vcxproj", "{60F3448D-6447-47CD-BF64-8762F8DB9361}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchmarks", "benchmarks\benchmarks.vcxproj", "{CA5FECF4-EDD2-4387-9967-66B047052B0B}" ProjectSection(ProjectDependencies) = postProject {ADC11C63-6986-41DC-9297-FC5DC58A2B55} = {ADC11C63-6986-41DC-9297-FC5DC58A2B55} {60F3448D-6447-47CD-BF64-8762F8DB9361} = {60F3448D-6447-47CD-BF64-8762F8DB9361} {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} {13E417C8-0C80-482C-A415-8C11A5C770DA} = {13E417C8-0C80-482C-A415-8C11A5C770DA} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "engine", "engine\engine.vcxproj", "{ADC11C63-6986-41DC-9297-FC5DC58A2B55}" ProjectSection(ProjectDependencies) = postProject {60F3448D-6447-47CD-BF64-8762F8DB9361} = {60F3448D-6447-47CD-BF64-8762F8DB9361} {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} {13E417C8-0C80-482C-A415-8C11A5C770DA} = {13E417C8-0C80-482C-A415-8C11A5C770DA} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "graphics", "graphics\graphics.vcxproj", "{13E417C8-0C80-482C-A415-8C11A5C770DA}" ProjectSection(ProjectDependencies) = postProject {60F3448D-6447-47CD-BF64-8762F8DB9361} = {60F3448D-6447-47CD-BF64-8762F8DB9361} {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests\tests.vcxproj", "{874CA9E0-23D2-4B91-837B-BCE8B7C9668D}" ProjectSection(ProjectDependencies) = postProject {ADC11C63-6986-41DC-9297-FC5DC58A2B55} = {ADC11C63-6986-41DC-9297-FC5DC58A2B55} {60F3448D-6447-47CD-BF64-8762F8DB9361} = {60F3448D-6447-47CD-BF64-8762F8DB9361} {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} {13E417C8-0C80-482C-A415-8C11A5C770DA} = {13E417C8-0C80-482C-A415-8C11A5C770DA} {4DB099FF-8264-4262-8195-EBA4109123CC} = {4DB099FF-8264-4262-8195-EBA4109123CC} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "examples", "examples\examples.vcxproj", "{BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}" ProjectSection(ProjectDependencies) = postProject {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} = {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289} EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Debug|x64.ActiveCfg = Debug|x64 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Debug|x64.Build.0 = Debug|x64 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Debug|x86.ActiveCfg = Debug|Win32 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Debug|x86.Build.0 = Debug|Win32 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Release|x64.ActiveCfg = Release|x64 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Release|x64.Build.0 = Release|x64 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Release|x86.ActiveCfg = Release|Win32 {DC20CBAC-AB67-4A0C-BBE2-65DC81DEF289}.Release|x86.Build.0 = Release|Win32 {4DB099FF-8264-4262-8195-EBA4109123CC}.Debug|x64.ActiveCfg = Debug|x64 {4DB099FF-8264-4262-8195-EBA4109123CC}.Debug|x64.Build.0 = Debug|x64 {4DB099FF-8264-4262-8195-EBA4109123CC}.Debug|x86.ActiveCfg = Debug|Win32 {4DB099FF-8264-4262-8195-EBA4109123CC}.Debug|x86.Build.0 = Debug|Win32 {4DB099FF-8264-4262-8195-EBA4109123CC}.Release|x64.ActiveCfg = Release|x64 {4DB099FF-8264-4262-8195-EBA4109123CC}.Release|x64.Build.0 = Release|x64 {4DB099FF-8264-4262-8195-EBA4109123CC}.Release|x86.ActiveCfg = Release|Win32 {4DB099FF-8264-4262-8195-EBA4109123CC}.Release|x86.Build.0 = Release|Win32 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Debug|x64.ActiveCfg = Debug|x64 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Debug|x64.Build.0 = Debug|x64 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Debug|x86.ActiveCfg = Debug|Win32 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Debug|x86.Build.0 = Debug|Win32 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Release|x64.ActiveCfg = Release|x64 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Release|x64.Build.0 = Release|x64 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Release|x86.ActiveCfg = Release|Win32 {60F3448D-6447-47CD-BF64-8762F8DB9361}.Release|x86.Build.0 = Release|Win32 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Debug|x64.ActiveCfg = Debug|x64 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Debug|x64.Build.0 = Debug|x64 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Debug|x86.ActiveCfg = Debug|Win32 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Debug|x86.Build.0 = Debug|Win32 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Release|x64.ActiveCfg = Release|x64 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Release|x64.Build.0 = Release|x64 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Release|x86.ActiveCfg = Release|Win32 {CA5FECF4-EDD2-4387-9967-66B047052B0B}.Release|x86.Build.0 = Release|Win32 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Debug|x64.ActiveCfg = Debug|x64 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Debug|x64.Build.0 = Debug|x64 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Debug|x86.ActiveCfg = Debug|Win32 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Debug|x86.Build.0 = Debug|Win32 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Release|x64.ActiveCfg = Release|x64 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Release|x64.Build.0 = Release|x64 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Release|x86.ActiveCfg = Release|Win32 {ADC11C63-6986-41DC-9297-FC5DC58A2B55}.Release|x86.Build.0 = Release|Win32 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Debug|x64.ActiveCfg = Debug|x64 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Debug|x64.Build.0 = Debug|x64 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Debug|x86.ActiveCfg = Debug|Win32 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Debug|x86.Build.0 = Debug|Win32 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Release|x64.ActiveCfg = Release|x64 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Release|x64.Build.0 = Release|x64 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Release|x86.ActiveCfg = Release|Win32 {13E417C8-0C80-482C-A415-8C11A5C770DA}.Release|x86.Build.0 = Release|Win32 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Debug|x64.ActiveCfg = Debug|x64 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Debug|x64.Build.0 = Debug|x64 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Debug|x86.ActiveCfg = Debug|Win32 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Debug|x86.Build.0 = Debug|Win32 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Release|x64.ActiveCfg = Release|x64 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Release|x64.Build.0 = Release|x64 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Release|x86.ActiveCfg = Release|Win32 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D}.Release|x86.Build.0 = Release|Win32 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Debug|x64.ActiveCfg = Debug|x64 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Debug|x64.Build.0 = Debug|x64 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Debug|x86.ActiveCfg = Debug|Win32 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Debug|x86.Build.0 = Debug|Win32 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Release|x64.ActiveCfg = Release|x64 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Release|x64.Build.0 = Release|x64 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Release|x86.ActiveCfg = Release|Win32 {BBE4C27E-7EDA-4F07-A5A0-9103BFB4C47C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D9298BAF-A6CD-4B96-BF4B-DFB2F3FBAF00} EndGlobalSection EndGlobal ================================================ FILE: README.md ================================================ # Physics3D ![Ubuntu CI Badge](https://github.com/ThePhysicsGuys/Physics3D/workflows/Ubuntu/badge.svg) ![MSVC CI Badge](https://github.com/ThePhysicsGuys/Physics3D/workflows/MSVC/badge.svg) A 3D physics engine, written in C++ ![Different Shapes](pictures/frontPage.png) [Imgur Library with the history of the engine](https://imgur.com/a/history-of-physics3d-engine-JrVHmMI) Other images can be found in [pictures/](pictures/) ## Project structure The Physics3D project consists of 7 projects, each with its own role: - The [Physics3D](/Physics3D) project contains the physics engine can be compiled separately. - The [graphics](/graphics) project contains all the logic to interact with OpenGL, visual debugging and gui code. - The [engine](/engine) project contains general concepts that can be applied in multiple environments like a layer systems and an event system. - The [util](/util) project contains code that can be used in all project, like logging, utilities and resource management. - The [application](/application) project contains an executable example application for visualizing, debugging and testing the physics engine. This project depends on the engine, graphics and physics project. Every project, including the physics project depends on util. - The [tests](/tests) project contains an executable with unit test for the physics engine. - The [benchmarks](/benchmarks) project contains an executable with benchmarks to evaluate the physics engine's performance. - [examples](/examples) contains minimal examples on how to use the library. These include a bare bones graphics implementation. ## Dependencies ### Application & engine & graphics - [GLFW](https://www.glfw.org/) Verified working with GLFW 3.2.1 - [GLEW](http://glew.sourceforge.net/) Verified with GLEW 2.1.0 - [stb_image](https://github.com/nothings/stb) Verified with stb_image.h v2.22 - [FreeType](https://www.freetype.org/) Verified with FreeType v2.9.0 - [Dear ImGui](https://github.com/ocornut/imgui/tree/docking) Verified with ImGui v1.77. Make sure to grab the files from the experimental docking branch. - some OpenGL functionality may not be available on virtual machines, enabling 3D acceleration might solve this ## Setup Guide ### CMake If you suddenly can't build the project anymore after pulling, perhaps one of the dependencies has changed. Try running [install/clean.sh](/install/clean.sh) (Unix) or [install/clean.bat](/install/clean.bat) (Windows) and rerun the setup script in the steps below. If you still have build problems, please create an issue as we want setting up to be as easy as possible. #### Platform independent using vcpkg 1. Clone the repository 2. If you do not have cmake already: download it from [cmake.org/download/](https://cmake.org/download/) 3. Run [install/setup.sh](/install/setup.sh) (Unix) or [install/setup.bat](/install/setup.bat) (Windows) from the Physics3D directory, this will install the necessary dependencies using [vcpkg](https://github.com/microsoft/vcpkg) and create the build folders. It will also run cmake for debug and release. The dependencies can be installed on their own with [install/setupDependencies.sh](/install/setupDependencies.sh) (Unix) or [install/setupDependencies.bat](/install/setupDependencies.bat) (Windows) The build directories can be generated on their own with [install/setupBuild.sh](/install/setupBuild.sh) (Unix) or [install/setupBuild.bat](/install/setupBuild.bat) (Windows) 4. Make the build from the build directory `cd build` with `cmake --build .`. To speed up the build, multithreaded building can be enabled by addding `--parallel` or `-- -j 5` to the end of the build command. 5. To run the application, you must also run it from the build directory: `cd build` `Debug\.\application`. Tests and benchmarks can be run from anywhere. #### Ubuntu specific using apt-get If you are using Ubuntu, we recommend using this installation method instead, as setting up using vcpkg can take a very long time. This method should get you a working version of the engine starting from a clean Ubuntu 18.04. 1. Clone the repository 2. Run [install/setupUbuntu.sh](/install/setupUbuntu.sh) from the Physics3D directory, this will install the necessary dependencies and create the build folders. It will also run cmake for debug and release. The dependencies can be installed on their own with [install/setupDependenciesUbuntu.sh](/install/setupDependenciesUbuntu.sh) The build directories can be generated on their own with [install/setupBuildUbuntu.sh](/install/setupBuildUbuntu.sh) 3. Make the build from the build directory `cd build` with `cmake --build .`. To speed up the build, multithreaded building can be enabled by addding `--parallel` or `-- -j 5` to the end of the build command. 4. To run the application, you must also run it from the build directory: `./application`. Tests and benchmarks can be run from anywhere. ### Visual Studio 1. Clone the repository 2. The physics project on its own does not depend on any libraries, so if you wish to only build it then you may skip step 3. 3. Download the dependencies, the Visual Studio configuration expects the libraries to be stored in Physics3D/lib/, includes should be stored in Physics3D/include/, with Physics3D/ the root folder of the git project. Your project structure should look like this: Physics3D/ | - include/ | | - freetype/ | | | - (freetype headers) | | - GL | | | - (glew headers) | | - GLFW/ | | | - (glfw3 headers) | | - imgui/ | | | - imconfig.h | | | - imgui.h & .cpp | | | - imgui_draw.h | | | - imgui_internal.h | | | - imgui_widgets.cpp | | | - imgui_rectpack.h | | | - imgui_textedit.h | | | - imgui_truetype.h | | | - imgui_impl_glfw.h & .cpp | | | - imgui_impl_opengl3.h & .cpp | | - stb_image.h | | - ft2build.h | - lib/ | | - freetype.lib | | - glew32.lib | | - glew32s.lib | | - glfw3.lib | - (Project and other files) 4. The configuration should already be configured in the provided project and solution files 5. You are done! ## Authors * **Lennart Van Hirtum** - [VonTum](https://github.com/VonTum) * **Matthias Vandersanden** - [olympus112](https://github.com/olympus112) This list is inconclusive as new contributors are always welcome! ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: application/application.cpp ================================================ #include "core.h" #include "application.h" #include #include #include #include #include #include "view/screen.h" #include "ecs/entityBuilder.h" #include "input/standardInputHandler.h" #include "ecs/components.h" #include "../graphics/texture.h" #include "../graphics/ecs/components.h" #include "../graphics/debug/guiDebug.h" #include "../graphics/debug/visualDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "worlds.h" #include "worldBuilder.h" #include "io/serialization.h" #include "io/saveDialog.h" #include "../util/parseCPUIDArgs.h" #include "../util/cmdParser.h" #include "../graphics/meshRegistry.h" #include "../engine/event/keyEvent.h" #include "../engine/input/keyboard.h" #include "../engine/event/windowEvent.h" #include "../util/stringUtil.h" #include #include "builtinWorlds.h" #include "engine/resource/meshResource.h" #include "graphics/ecs/materials.h" #include "graphics/resource/textureResource.h" #include "util/resource/resourceManager.h" #define TICKS_PER_SECOND 120.0 #define TICK_SKIP_TIME std::chrono::milliseconds(1000) namespace P3D::Application { PlayerWorld world(1 / TICKS_PER_SECOND); UpgradeableMutex worldMutex; PhysicsThread physicsThread(&world, &worldMutex, Graphics::AppDebug::logTickEnd, TICK_SKIP_TIME); Screen screen; void init(const ::Util::ParsedArgs& cmdArgs); void setupWorld(const ::Util::ParsedArgs& cmdArgs); void setupGL(); void setupDebug(); void loadFile(const char* file); void init(const ::Util::ParsedArgs& cmdArgs) { auto start = std::chrono::high_resolution_clock::now(); Log::init("latest.log"); Log::info(::Util::printAndParseCPUIDArgs(cmdArgs)); bool quickBoot = cmdArgs.hasFlag("quickBoot"); setupGL(); Log::info("Init MeshRegistry"); Graphics::MeshRegistry::init(); /*ResourceManager::add("floorMaterial", "../res/textures/floor/floor_color.jpg"); WorldImportExport::registerTexture(ResourceManager::get("floorMaterial"));*/ Log::info("Initializing world"); WorldBuilder::init(); if(cmdArgs.argCount() >= 1) { loadFile(cmdArgs[0].c_str()); } else { Log::info("Creating default world"); setupWorld(cmdArgs); } Log::info("Initializing screen"); screen.onInit(quickBoot); if(!world.isValid()) { throw "World not valid!"; } Log::info("Initializing debug"); setupDebug(); auto stop = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(stop - start); Log::info("Init Physics3D in %.4f ms", duration.count() / 1.0f); } void setupGL() { Log::info("Initializing GLFW"); if (!initGLFW()) { Log::error("GLFW not initialised"); std::cin.get(); stop(-1); } new(&screen) Screen(1800, 900, &world, &worldMutex); Log::info("Initializing GLEW"); if (!initGLEW()) { Log::error("GLEW not initialised"); std::cin.get(); stop(-1); } std::filesystem::copy_file("../res/default_imgui.ini", "../res/imgui.ini", std::filesystem::copy_options::skip_existing); } void setupWorld(const ::Util::ParsedArgs& cmdArgs) { Log::info("Initializing world"); world.addExternalForce(new DirectionalGravity(Vec3(0, -10.0, 0.0))); //buildFloorWorld(screen, world); return; //buildBallWorld(screen, world); return; //buildDebugWorld(screen, world); return; //buildShowcaseWorld(screen, world); return; buildBoxWorld(screen, world); return; PartProperties basicProperties{1.0, 0.7, 0.3}; WorldBuilder::buildFloorAndWalls(50.0, 50.0, 1.0); GlobalCFrame origin(0.0, 5.0, 0.0, Rotation::fromEulerAngles(-3.14 / 4, 3.14 / 4, 0.0)); // Load textures Graphics::TextureResource* wallAlbedo = ResourceManager::add("wall albedo", "../res/textures/wall/wall_color.jpg"); Graphics::TextureResource* wallNormal = ResourceManager::add("wall normal", "../res/textures/wall/wall_normal.jpg"); wallAlbedo->generateMipmap(); wallNormal->generateMipmap(); int n = 3; for(int x = 0; x < n; x++) { for(int y = 0; y < n; y++) { for(int z = 0; z < n; z++) { GlobalCFrame cf = origin.localToGlobal(CFrame(x, y, z)); std::string name = "part " + std::to_string((x * n + y) * n + z); ExtendedPart* part = new ExtendedPart(boxShape(0.5, 0.5, 0.5), cf, basicProperties, name); IRef material = screen.registry.add(part->entity, Graphics::Materials::GREEN); material->set(Graphics::Comp::Material::Map_Albedo, SRef(wallAlbedo)); material->set(Graphics::Comp::Material::Map_Normal, SRef(wallNormal)); world.addPart(part); } } } { ExtendedPart* partA = new ExtendedPart(boxShape(1.0, 0.49, 3.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); ExtendedPart* partB = new ExtendedPart(boxShape(1.0, 0.5, 3.0), GlobalCFrame(2.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partB"); screen.registry.add(partA->entity, Graphics::Materials::GOLD); screen.registry.add(partB->entity, Graphics::Materials::SILVER); world.addPart(partA); partA->attach(partB, CFrame(0.0, 1.0, 0.0)); } { ExtendedPart* partA = new ExtendedPart(boxShape(1.0, 0.49, 3.0), GlobalCFrame(-3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); ExtendedPart* partB = new ExtendedPart(boxShape(1.0, 0.5, 3.0), GlobalCFrame(-2.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partB"); screen.registry.add(partA->entity, Graphics::Materials::RED); screen.registry.add(partB->entity, Graphics::Materials::BLUE); world.addPart(partA); partA->attach(partB, new MotorConstraintTemplate(0.5), CFrame(0.0, 1.0, 0.0), CFrame(1.0, 0.0, 0.0)); } // Lights { Comp::Light::Attenuation attenuation = {1, 1, 1}; auto lights = EntityBuilder(screen.registry).name("Lights").get(); auto sphereData = Graphics::MeshRegistry::getMesh(&SphereClass::instance); EntityBuilder(screen.registry).parent(lights).transform(Position(10, 5, -10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 300, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(10, 5, 10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 300, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(-10, 5, -10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 200, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(-10, 5, 10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 500, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(0, 5, 0), 0.2).light(Graphics::Color(1, 0.90f, 0.75f), 400, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); } { ExtendedPart* cornerPart = new ExtendedPart(cornerShape(2.0, 2.0, 2.0), GlobalCFrame(3.0, 3.0, -5.0), {1.0, 1.0, 1.0}, "CORNER"); ExtendedPart* wedgePart = new ExtendedPart(wedgeShape(2.0, 2.0, 2.0), GlobalCFrame(-3.0, 3.0, -5.0), {1.0, 1.0, 1.0}, "WEDGE"); screen.registry.add(cornerPart->entity, Graphics::Materials::metalDiffuse(Graphics::Colors::ACCENT)); screen.registry.add(wedgePart->entity, Graphics::Materials::metalNoReflection(Graphics::Colors::FUCHSIA)); world.addPart(cornerPart); world.addPart(wedgePart); } { ExtendedPart* partA = new ExtendedPart(boxShape(1.0, 0.49, 3.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); ExtendedPart* partB = new ExtendedPart(boxShape(1.0, 0.5, 3.0), GlobalCFrame(2.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partB"); EntityBuilder(screen.registry, partA->entity).light(Graphics::Color(0.1f, 0.94f, 0.49f), 500, Comp::Light::Attenuation{0.8, 0.5, 0.2}); world.addPart(partA); world.addPart(partB); //world.addLink(new SpringLink({ CFrame{1.0, 0.0, 0.0}, partA }, { CFrame{0.0, 0.0, 0.0}, partB }, 5.0, 5.0)); world.addLink(new ElasticLink({CFrame{1.0, 0.0, 0.0}, partA}, {CFrame{0.0, 0.0, 0.0}, partB}, 5.0, 5.0)); } { ExtendedPart* partA = new ExtendedPart(boxShape(1.0, 0.49, 1.0), GlobalCFrame(12.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); ExtendedPart* partB = new ExtendedPart(boxShape(1.0, 0.5, 1.0), GlobalCFrame(14.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partB"); ExtendedPart* partC = new ExtendedPart(boxShape(1.0, 0.5, 1.0), GlobalCFrame(16.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partC"); ConstraintGroup cg; cg.add(partA, partB, new BarConstraint(Vec3(0.5, 0.0, 0.0), Vec3(-0.5, 0.0, 0.0), 1.0)); cg.add(partB, partC, new BallConstraint(Vec3(1, 0.0, 0.0), Vec3(-1, 0.0, 0.0))); cg.add(partC, partA, new HingeConstraint(Vec3(-2, 2.0, 0.0), Vec3(0,0,1), Vec3(2, 2.0, 0.0), Vec3(0, 0, 1))); world.constraints.push_back(cg); world.addPart(partA); world.addPart(partB); world.addPart(partC); } ExtendedPart* tetrahedron = new ExtendedPart(polyhedronShape(ShapeLibrary::tetrahedron), GlobalCFrame(-7.0, 3.0, 6.0), WorldBuilder::basicProperties); ExtendedPart* octahedron = new ExtendedPart(polyhedronShape(ShapeLibrary::octahedron), GlobalCFrame(-7.0, 3.0, 8.0), WorldBuilder::basicProperties); ExtendedPart* icosahedron = new ExtendedPart(polyhedronShape(ShapeLibrary::icosahedron), GlobalCFrame(-7.0, 3.0, 10.0), WorldBuilder::basicProperties); world.addPart(tetrahedron); world.addPart(octahedron); world.addPart(icosahedron); //world.addLink(new MagneticLink({ CFrame{1.0, 0.0, 0.0}, partA }, { CFrame{0.0, 0.0, 0.0}, partB }, +8.0)); //world.addLink(new AlignmentLink({ CFrame{1.0, 0.0, 0.0}, partA}, { CFrame{0.0, 0.0, 0.0}, partB })); //return; ExtendedPart* partA = new ExtendedPart(boxShape(5.0, 10.0, 5.0), GlobalCFrame(0.0, 6.0, -10.0), WorldBuilder::basicProperties); ExtendedPart* partB = new ExtendedPart(sphereShape(0.45), GlobalCFrame(8.0, 6.0, -10.0), WorldBuilder::basicProperties); ExtendedPart* partC = new ExtendedPart(boxShape(0.9, 0.15, 0.15), GlobalCFrame(9.0, 6.0, -10.0), WorldBuilder::basicProperties); ExtendedPart* partD = new ExtendedPart(boxShape(0.9, 0.15, 0.15), GlobalCFrame(10.0, 6.0, -10.0), WorldBuilder::basicProperties); ExtendedPart* partE = new ExtendedPart(cylinderShape(0.3, 1.3), GlobalCFrame(11.0, 6.0, -10.0), WorldBuilder::basicProperties); world.createLayer(false, true); world.addPart(partA, 1); world.addPart(partB, 1); world.addPart(partC, 1); world.addPart(partD, 1); world.addPart(partE, 1); ConstraintGroup cg; cg.add(partA, partB, new BallConstraint(Vec3(7.0, 0.0, 0.0), Vec3(-1.0, 0.0, 0.0))); cg.add(partB, partC, new BallConstraint(Vec3(0.5, 0.0, 0.0), Vec3(-0.5, 0.0, 0.0))); cg.add(partC, partD, new BallConstraint(Vec3(0.5, 0.0, 0.0), Vec3(-0.5, 0.0, 0.0))); cg.add(partD, partE, new BallConstraint(Vec3(0.5, 0.0, 0.0), Vec3(-0.5, 0.0, 0.0))); world.constraints.push_back(std::move(cg)); } void setupDebug() { Graphics::AppDebug::setupDebugHooks(); } void loadFile(const char* file) { Log::info("Loading file %s", file); auto startTime = std::chrono::high_resolution_clock::now(); if(::Util::endsWith(file, ".parts")) { WorldImportExport::loadLoosePartsIntoWorld(file, world); } else if(::Util::endsWith(file, ".nativeParts")) { WorldImportExport::loadNativePartsIntoWorld(file, world); } else if(::Util::endsWith(file, ".world")) { WorldImportExport::loadWorld(file, world); } std::chrono::nanoseconds deltaTime = std::chrono::high_resolution_clock::now() - startTime; Log::info("File loaded, took %.4f ms", deltaTime.count() / 1E6); } bool onFileDrop(Engine::WindowDropEvent& event) { loadFile(event.getPath().c_str()); return true; } bool onKeyPress(Engine::KeyPressEvent& keyEvent) { using namespace Engine; Key pressedKey = keyEvent.getKey(); if(pressedKey == Keyboard::KEY_S && keyEvent.getModifiers().isCtrlPressed()) { handler->setKey(Keyboard::KEY_S, false); char pathBuffer[MAX_PATH_LENGTH]; if(saveWorldDialog(pathBuffer)) { Log::info("Saving world to: %s", pathBuffer); std::unique_lock(worldMutex); WorldImportExport::saveWorld(pathBuffer, world); } return true; } else if(pressedKey == Keyboard::KEY_O && keyEvent.getModifiers().isCtrlPressed()) { handler->setKey(Keyboard::KEY_O, false); char pathBuffer[MAX_PATH_LENGTH]; if(openWorldDialog(pathBuffer)) { Log::info("Opening world: %s", pathBuffer); std::unique_lock(worldMutex); WorldImportExport::loadWorld(pathBuffer, world); } return true; } else { return false; } } void onEvent(Engine::Event& event) { using namespace Engine; screen.onEvent(event); EventDispatcher dispatcher(event); dispatcher.dispatch(onFileDrop); dispatcher.dispatch(onKeyPress); } void stop(int returnCode) { Log::info("Closing physics"); physicsThread.stop(); Log::info("Closing screen"); screen.onClose(); Log::stop(); exit(returnCode); } // Ticks void togglePause() { physicsThread.toggleRunning(); } void pause() { physicsThread.stop(); } void unpause() { physicsThread.start(); } bool isPaused() { return !physicsThread.isRunning(); } void setSpeed(double newSpeed) { physicsThread.speed = newSpeed; } double getSpeed() { return physicsThread.speed; } void runTick() { physicsThread.runTick(); } void toggleFlying() { // Through using syncModification, we ensure that the creation or deletion of the player shape is not handled by the physics thread, thus avoiding a race condition with the Registry // TODO this is not a proper solution, it should be an asyncModification! But at least it fixes the sporadic crash std::unique_lock worldLock(worldMutex); if (screen.camera.flying) { Log::info("Creating player"); screen.camera.flying = false; // this modifies Registry, which may or may not be a race condition screen.camera.attachment = new ExtendedPart(polyhedronShape(ShapeLibrary::createPrism(50, 0.3f, 1.5f)), GlobalCFrame(screen.camera.cframe.getPosition()), {1.0, 5.0, 0.0}, "Player"); screen.world->addPart(screen.camera.attachment); } else { Log::info("Destroying player"); delete screen.camera.attachment; screen.camera.flying = true; } } }; // namespace P3D::Application int main(int argc, const char** args) { using namespace P3D::Application; using namespace P3D::Graphics; Util::ParsedArgs inputArgs(argc, args); init(inputArgs); Log::info("Started rendering"); while (!screen.shouldClose()) { graphicsMeasure.mark(GraphicsProcess::UPDATE); screen.onUpdate(); screen.onRender(); graphicsMeasure.end(); } Log::info("Closing by screen.shouldClose()"); stop(0); } ================================================ FILE: application/application.h ================================================ #pragma once #include namespace P3D::Engine { struct Event; }; namespace P3D::Application { class Screen; class PlayerWorld; extern PlayerWorld world; extern UpgradeableMutex worldMutex; extern Screen screen; void pause(); void unpause(); bool isPaused(); void togglePause(); void runTick(); void setSpeed(double newSpeed); double getSpeed(); void stop(int returnCode); void toggleFlying(); void onEvent(Engine::Event& event); }; ================================================ FILE: application/application.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US ///////////////////////////////////////////////////////////////////////////// // // SHADER // IDR_SHADER1 SHADER "..\\res\\shaders\\basic.shader" IDR_SHADER2 SHADER "..\\res\\shaders\\vector.shader" IDR_SHADER3 SHADER "..\\res\\shaders\\origin.shader" IDR_SHADER4 SHADER "..\\res\\shaders\\font.shader" IDR_SHADER5 SHADER "..\\res\\shaders\\depth.shader" IDR_SHADER6 SHADER "..\\res\\shaders\\quad.shader" IDR_SHADER7 SHADER "..\\res\\shaders\\postprocess.shader" IDR_SHADER8 SHADER "..\\res\\shaders\\skybox.shader" IDR_SHADER9 SHADER "..\\res\\shaders\\mask.shader" IDR_SHADER10 SHADER "..\\res\\shaders\\point.shader" IDR_SHADER11 SHADER "..\\res\\shaders\\test.shader" IDR_SHADER12 SHADER "..\\res\\shaders\\blur.shader" IDR_SHADER13 SHADER "..\\res\\shaders\\line.shader" IDR_SHADER14 SHADER "..\\res\\shaders\\instance.shader" IDR_SHADER15 SHADER "..\\res\\shaders\\sky.shader" IDR_SHADER16 SHADER "..\\res\\shaders\\lighting.shader" IDR_SHADER17 SHADER "..\\res\\shaders\\debug.shader" IDR_SHADER18 SHADER "..\\res\\shaders\\depthbuffer.shader" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // OBJ // IDR_OBJ2 OBJ "..\\res\\models\\stall.obj" IDR_OBJ1 OBJ "..\\res\\models\\sphere.obj" ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. MAINICON ICON "..\\res\\icon.ico" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: application/application.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {4DB099FF-8264-4262-8195-EBA4109123CC} application 10.0 Application true v142 MultiByte Application false v142 true MultiByte Application true v142 MultiByte Application false v142 true MultiByte NativeRecommendedRules.ruleset false Level3 Disabled true true $(SolutionDir)include stdcpp17 Level3 Disabled true true $(SolutionDir)include;$(SolutionDir)application;$(SolutionDir) _MBCS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) Use core.h NotSet stdcpp17 true $(SolutionDir)lib;$(OutDir) Physics3D.lib;util.lib;engine.lib;graphics.lib;%(AdditionalDependencies) /NODEFAULTLIB:LIBCMT Level3 MaxSpeed true true true true stdcpp17 true true Level3 MaxSpeed true true true true $(SolutionDir)include;$(SolutionDir)application;$(SolutionDir) _MBCS;NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) Speed false Use core.h NotSet true stdcpp17 true true true $(SolutionDir)lib;$(OutDir) Physics3D.lib;util.lib;engine.lib;graphics.lib;%(AdditionalDependencies) /NODEFAULTLIB:LIBCMT Create core.h Create core.h Designer ================================================ FILE: application/builtinWorlds.cpp ================================================ #include "core.h" #include "builtinWorlds.h" #include "worldBuilder.h" #include "extendedPart.h" #include "ecs/entityBuilder.h" #include "ecs/components.h" #include "../graphics/meshRegistry.h" #include #include #include #include #include #include #include #include #include "engine/resource/meshResource.h" #include "graphics/resource/textureResource.h" #include "util/resource/resourceManager.h" #include "graphics/meshRegistry.h" namespace P3D::Application { using namespace WorldBuilder; void buildBenchmarkWorld(PlayerWorld& world) { WorldBuilder::buildFloor(150.0, 150.0); GlobalCFrame origin(0, 20, 0, Rotation::fromEulerAngles(0, PI / 4, PI / 4)); for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { for(int k = 0; k < 10; k++) { world.addPart(new ExtendedPart(boxShape(1.0, 1.0, 1.0), origin.localToGlobal(CFrame(i * 1.00001, j * 1.00001, k * 1.0001)), basicProperties)); } } } } void buildShowcaseWorld(Screen& screen, PlayerWorld& world) { buildFloorWorld(screen, world); // Part factories WorldBuilder::SpiderFactory spiderFactories[]{{0.5, 4},{0.5, 6},{0.5, 8},{0.5, 10}}; { ExtendedPart* partA = new ExtendedPart(boxShape(1.0, 0.49, 3.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); ExtendedPart* partB = new ExtendedPart(boxShape(1.0, 0.5, 3.0), GlobalCFrame(2.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "partA"); EntityBuilder(screen.registry, partA->entity).light(Graphics::Color(0.1f, 0.94f, 0.49f), 500, Comp::Light::Attenuation{0.8, 0.5, 0.2}); world.addPart(partA); world.addPart(partB); } { auto pistonFolder = EntityBuilder(screen.registry).name("PistonPart").get(); ExtendedPart* centerPart = new ExtendedPart(sphereShape(1.0), GlobalCFrame(-15.0, 4.0, 13.0), basicProperties, "Center", pistonFolder); Shape box = boxShape(1.0, 1.0, 1.0); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0), basicProperties), "IDENTITY", pistonFolder); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_90), CFrame(0.0, 0.0, 0.0), basicProperties), "X_90", pistonFolder); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::Y_90), CFrame(0.0, 0.0, 0.0), basicProperties), "Y_90", pistonFolder); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_180), CFrame(0.0, 0.0, 0.0), basicProperties), "X_180", pistonFolder); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_270), CFrame(0.0, 0.0, 0.0), basicProperties), "X_270", pistonFolder); new ExtendedPart(Part(box, *centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::Y_270), CFrame(0.0, 0.0, 0.0), basicProperties), "Y_270", pistonFolder); world.addPart(centerPart); } { ExtendedPart* sateliteBody = new ExtendedPart(cylinderShape(0.5, 1.0), GlobalCFrame(0.0, 7.0, 0.0, Rotation::Predefined::X_90), basicProperties, "Satelite Body"); ExtendedPart* wing1 = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Wing 1"); ExtendedPart* wing2 = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Wing 2"); sateliteBody->attach(wing1, new SinusoidalPistonConstraint(0.0, 5.0, 1.0), CFrame(0.5, 0.0, 0.0, Rotation::Predefined::Y_90), CFrame(-0.5, 0.0, 0.0, Rotation::Predefined::Y_90)); sateliteBody->attach(wing2, new SinusoidalPistonConstraint(0.0, 5.0, 1.0), CFrame(-0.5, 0.0, 0.0, Rotation::Predefined::Y_270), CFrame(-0.5, 0.0, 0.0, Rotation::Predefined::Y_90)); //sateliteBody->attach(wing2, new SinusoidalPistonConstraint(Vec3(-1.0, 0.0, 0.0), 1.0, 3.0, 1.0), CFrame(-0.5, 0.0, 0.0), CFrame(0.5, 0.0, 0.0)); world.addPart(sateliteBody); sateliteBody->getMainPhysical()->motionOfCenterOfMass.rotation.rotation[0] = Vec3(0, 2, 0); } world.addPart(new ExtendedPart(sphereShape(2.0), GlobalCFrame(10, 3, 10), {1.0, 0.3, 0.7}, "SphereThing")); /*ExtendedPart* conveyor = new ExtendedPart(boxShape(1.0, 0.3, 50.0), GlobalCFrame(10.0, 0.65, 0.0), {2.0, 1.0, 0.3}); conveyor->properties.conveyorEffect = Vec3(0, 0, 2.0); world.addTerrainPart(conveyor); world.addPart(new ExtendedPart(boxShape(0.2, 0.2, 0.2), GlobalCFrame(10, 1.0, 0.0), {1.0, 0.2, 0.3}, "TinyCube"));*/ // hollow box WorldBuilder::HollowBoxParts parts = WorldBuilder::buildHollowBox(Bounds(Position(12.0, 3.0, 14.0), Position(20.0, 8.0, 20.0)), 0.3); parts.front->setMaterial(Graphics::Comp::Material(Graphics::Color(0.4f, 0.6f, 1.0f, 0.3f))); parts.back->setMaterial(Graphics::Comp::Material(Graphics::Color(0.4f, 0.6f, 1.0f, 0.3f))); // Rotating walls /*ExtendedPart* rotatingWall = new ExtendedPart(boxShape(5.0, 3.0, 0.5), GlobalCFrame(Position(-12.0, 1.7, 0.0)), {1.0, 1.0, 0.7}); ExtendedPart* rotatingWall2 = new ExtendedPart(boxShape(5.0, 3.0, 0.5), GlobalCFrame(Position(-12.0, 1.7, 5.0)), {1.0, 1.0, 0.7}); world.addPart(rotatingWall, true); world.addPart(rotatingWall2, true); rotatingWall->getMainPhysical()->motionOfCenterOfMass.angularVelocity = Vec3(0, -0.7, 0); rotatingWall2->getMainPhysical()->motionOfCenterOfMass.angularVelocity = Vec3(0, 0.7, 0);*/ // Many many parts /*for(int i = 0; i < 3; i++) { ExtendedPart* newCube = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(fRand(-10.0, 0.0), fRand(1.0, 1.0), fRand(-10.0, 0.0)), {1.0, 0.2, 0.7}); world.addPart(newCube); }*/ WorldBuilder::buildCar(GlobalCFrame(5.0, 1.0, 5.0)); WorldBuilder::buildConveyor(1.5, 7.0, GlobalCFrame(-10.0, 1.0, -10.0, Rotation::fromEulerAngles(0.15, 0.0, 0.0)), 1.5); WorldBuilder::buildConveyor(1.5, 7.0, GlobalCFrame(-12.5, 1.0, -14.0, Rotation::fromEulerAngles(0.0, 3.1415 / 2, -0.15)), 1.5); WorldBuilder::buildConveyor(1.5, 7.0, GlobalCFrame(-16.5, 1.0, -11.5, Rotation::fromEulerAngles(-0.15, 0.0, -0.0)), -1.5); WorldBuilder::buildConveyor(1.5, 7.0, GlobalCFrame(-14.0, 1.0, -7.5, Rotation::fromEulerAngles(0.0, 3.1415 / 2, 0.15)), -1.5); int minX = 0; int maxX = 3; int minY = 0; int maxY = 3; int minZ = 0; int maxZ = 3; auto cubeFolder = EntityBuilder(screen.registry).name("Cubes").get(); auto cylinderFolder = EntityBuilder(screen.registry).name("Cylinders").get(); auto triangleFolder = EntityBuilder(screen.registry).name("Triangles").get(); auto sphereFolder = EntityBuilder(screen.registry).name("Spheres").get(); auto spiderFolder = EntityBuilder(screen.registry).name("Spiders").get(); GlobalCFrame rootFrame(Position(0.0, 15.0, 0.0), Rotation::fromEulerAngles(3.1415 / 4, 3.1415 / 4, 0.0)); for(double x = minX; x < maxX; x += 1.00001) { for(double y = minY; y < maxY; y += 1.00001) { for(double z = minZ; z < maxZ; z += 1.00001) { ExtendedPart* newCube = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(Position(x - 5, y + 10, z - 5)), {1.0, 1.0, 0.0}, "Box", cubeFolder); newCube->setMaterial(Graphics::Comp::Material(Vec4f(float((x - minX) / (maxX - minX)), float((y - minY) / (maxY - minY)), float((z - minZ) / (maxZ - minZ)), 1.0f))); world.addPart(newCube); world.addPart(new ExtendedPart(sphereShape(0.5), GlobalCFrame(Position(x + 5, y + 1, z - 5)), {1.0, 0.2, 0.5}, "Sphere", sphereFolder)); spiderFactories[rand() & 0x00000003].buildSpider(GlobalCFrame(Position(x + y * 0.1, y + 1, z)), spiderFolder); world.addPart(new ExtendedPart(WorldBuilder::triangle, GlobalCFrame(Position(x - 20, y + 1, z + 20)), {1.0, 0.2, 0.5}, "Triangle", triangleFolder)); world.addPart(new ExtendedPart(cylinderShape(0.3, 1.2), GlobalCFrame(x - 5, y + 1, z + 5, Rotation::fromEulerAngles(3.1415 / 4, 3.1415 / 4, 0.0)), {1.0, 0.2, 0.5}, "cylinderShape", cylinderFolder)); } } } //auto terrainFolder = screen.registry.create(); //screen.registry.add(terrainFolder, "Terrain"); //WorldBuilder::buildTerrain(150, 150, terrainFolder); auto ropeFolder = EntityBuilder(screen.registry).name("Ropes").get(); ExtendedPart* ropeA = new ExtendedPart(boxShape(2.0, 1.5, 0.7), GlobalCFrame(10.0, 2.0, -10.0), {1.0, 0.7, 0.3}, "RopeA", ropeFolder); ExtendedPart* ropeB = new ExtendedPart(boxShape(1.5, 1.2, 0.9), GlobalCFrame(10.0, 2.0, -14.0), {1.0, 0.7, 0.3}, "RopeB", ropeFolder); ExtendedPart* ropeC = new ExtendedPart(boxShape(2.0, 1.5, 0.7), GlobalCFrame(10.0, 2.0, -18.0), {1.0, 0.7, 0.3}, "RopeC", ropeFolder); world.addPart(ropeA); world.addPart(ropeB); world.addPart(ropeC); ConstraintGroup group; group.add(ropeA, ropeB, new BallConstraint(Vec3(0.0, 0.0, -2.0), Vec3(0.0, 0.0, 2.0))); group.add(ropeB, ropeC, new BallConstraint(Vec3(0.0, 0.0, -2.0), Vec3(0.0, 0.0, 2.0))); world.constraints.push_back(group); ExtendedPart* singularPart = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(7.0, 1.0, 5.0), {1.3, 1.2, 1.1}, "SingularPart"); world.addPart(singularPart); ExtendedPart* ep1 = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "MainPart"); ExtendedPart* ap1 = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(), {1.0, 1.0, 1.0}, "AttachedPart"); ep1->attach(ap1, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0.0, 0.0, 0.0)); world.addPart(ep1); world.addPart(ap1); ep1->getMainPhysical()->applyAngularImpulse(Vec3(1.0, 0.5, 0.0) * 1); ExtendedPart* ep1ap2 = new ExtendedPart(boxShape(0.5, 0.5, 0.5), ep1, CFrame(-0.5, 0.5, 0.5), {1.0, 1.0, 1.0}, "Ep1Ap2"); ExtendedPart* ep2 = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(-3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "MainPart"); ExtendedPart* ap2 = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(), {1.0, 1.0, 1.0}, "AttachedPart"); ep2->attach(ap2, CFrame(1.0, 0.0, 0.0)); world.addPart(ep2); world.addPart(ap2); ep2->getMainPhysical()->applyAngularImpulse(Vec3(1.0, 0.5, 0.0) * 1); //SoftLink. { ExtendedPart* a = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "SpringLinkMain1"); ExtendedPart* b = new ExtendedPart(boxShape(1.0, 2.0, 1.0), GlobalCFrame(), {1.0, 1.0, 1.0}, "SpringLinkPart1"); world.addPart(a); world.addPart(b); world.addLink(new SpringLink({CFrame{1.0, 0.0, 0.0}, a}, {CFrame{0.0, 0.0, 0.0}, b}, 15.0, 0.5)); } { ExtendedPart* a = new ExtendedPart(boxShape(2.0, 0.5, 1.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "SpringLinkMain2"); ExtendedPart* b = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), {2.0, 1.0, 1.0}, "SpringLinkPart2"); world.addPart(a); world.addPart(b); world.addLink(new SpringLink({CFrame{1.0, 0.0, 0.0}, a}, {CFrame{0.0, 0.0, 0.0}, b}, 15.0, 0.5)); } { ExtendedPart* a = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(3.0, 3.0, 0.0), {1.0, 1.0, 1.0}, "SpringLinkMain3"); ExtendedPart* b = new ExtendedPart(boxShape(2.0, 2.0, 2.0), GlobalCFrame(), {1.0, 0.5, 1.0}, "SpringLinkPart3"); world.addPart(a); world.addPart(b); world.addLink(new SpringLink({CFrame{1.0, 0.0, 0.0}, a}, {CFrame{0.0, 0.0, 0.0}, b}, 15.0, 0.5)); } /*Vec3 angularVel(0.0, 0.0, -1.0); { ExtendedPart* nativeFixedConstraintGroupMain = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(Position(-3.0, 7.0, 2.0), Rotation::fromEulerAngles(0.0, 0.0, -0.5)), {1.0, 1.0, 1.0}, "MainPart"); ExtendedPart* f2 = new ExtendedPart(boxShape(0.9, 0.9, 0.9), GlobalCFrame(), {1.0, 1.0, 1.0}, "f2"); ExtendedPart* f3 = new ExtendedPart(boxShape(0.8, 0.8, 0.8), GlobalCFrame(), {1.0, 1.0, 1.0}, "f3"); ExtendedPart* f4 = new ExtendedPart(boxShape(0.7, 0.7, 0.7), GlobalCFrame(), {1.0, 1.0, 1.0}, "f4"); ExtendedPart* f5 = new ExtendedPart(boxShape(0.6, 0.6, 0.6), GlobalCFrame(), {1.0, 1.0, 1.0}, "f5"); nativeFixedConstraintGroupMain->attach(f2, CFrame(1.2, 0.0, 0.0)); f2->attach(f3, CFrame(1.2, 0.0, 0.0)); f3->attach(f4, CFrame(1.2, 0.0, 0.0)); f4->attach(f5, CFrame(1.2, 0.0, 0.0)); world.addPart(f2); f2->getMainPhysical()->motionOfCenterOfMass.rotation.angularVelocity = angularVel; } { ExtendedPart* fixedConstraintGroupMain = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(Position(-3.0, 7.0, -2.0), Rotation::fromEulerAngles(0.0, 0.0, -0.5)), {1.0, 1.0, 1.0}, "MainPart"); ExtendedPart* f2 = new ExtendedPart(boxShape(0.9, 0.9, 0.9), GlobalCFrame(), {1.0, 1.0, 1.0}, "f2"); ExtendedPart* f3 = new ExtendedPart(boxShape(0.8, 0.8, 0.8), GlobalCFrame(), {1.0, 1.0, 1.0}, "f3"); ExtendedPart* f4 = new ExtendedPart(boxShape(0.7, 0.7, 0.7), GlobalCFrame(), {1.0, 1.0, 1.0}, "f4"); ExtendedPart* f5 = new ExtendedPart(boxShape(0.6, 0.6, 0.6), GlobalCFrame(), {1.0, 1.0, 1.0}, "f5"); fixedConstraintGroupMain->attach(f2, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); f2->attach(f3, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); f3->attach(f4, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); f4->attach(f5, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); world.addPart(f2); f2->getMainPhysical()->motionOfCenterOfMass.rotation.angularVelocity = angularVel; }*/ { ExtendedPart* fixedConstraintGroupMain = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(-3.0, 3.0, 7.0), {1.0, 1.0, 1.0}, "MainPart"); ExtendedPart* f2 = new ExtendedPart(boxShape(0.9, 0.9, 0.9), GlobalCFrame(), {1.0, 1.0, 1.0}, "f2"); ExtendedPart* f3 = new ExtendedPart(boxShape(0.8, 0.8, 0.8), GlobalCFrame(), {1.0, 1.0, 1.0}, "f3"); ExtendedPart* f4 = new ExtendedPart(boxShape(0.7, 0.7, 0.7), GlobalCFrame(), {1.0, 1.0, 1.0}, "f4"); ExtendedPart* f5 = new ExtendedPart(boxShape(0.6, 0.6, 0.6), GlobalCFrame(), {1.0, 1.0, 1.0}, "f5"); f2->attach(f3, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); fixedConstraintGroupMain->attach(f2, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); f3->attach(f4, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); f4->attach(f5, new FixedConstraint(), CFrame(1.2, 0.0, 0.0), CFrame(0, 0, 0)); world.addPart(f2); } { double turnSpeed = 10.0; auto poweredCarFolder = screen.registry.create(); screen.registry.add(poweredCarFolder, "Powered Car"); ExtendedPart* poweredCarBody = new ExtendedPart(boxShape(1.0, 0.4, 2.0), GlobalCFrame(-6.0, 1.0, 0.0), basicProperties, "Chassis", poweredCarFolder); ExtendedPart* FLWheel = new ExtendedPart(cylinderShape(0.5, 0.2), GlobalCFrame(), basicProperties, "Front Left Wheel", poweredCarFolder); ExtendedPart* FRWheel = new ExtendedPart(cylinderShape(0.5, 0.2), GlobalCFrame(), basicProperties, "Front Right Wheel", poweredCarFolder); ExtendedPart* BLWheel = new ExtendedPart(cylinderShape(0.5, 0.2), GlobalCFrame(), basicProperties, "Back Left Wheel", poweredCarFolder); ExtendedPart* BRWheel = new ExtendedPart(cylinderShape(0.5, 0.2), GlobalCFrame(), basicProperties, "Back Right Wheel", poweredCarFolder); poweredCarBody->attach(FLWheel, new ConstantSpeedMotorConstraint(turnSpeed), CFrame(Vec3(0.55, 0.0, 1.0), Rotation::Predefined::Y_90), CFrame(Vec3(0.0, 0.0, 0.15), Rotation::Predefined::Y_180)); poweredCarBody->attach(BLWheel, new ConstantSpeedMotorConstraint(turnSpeed), CFrame(Vec3(0.55, 0.0, -1.0), Rotation::Predefined::Y_90), CFrame(Vec3(0.0, 0.0, 0.15), Rotation::Predefined::Y_180)); poweredCarBody->attach(FRWheel, new ConstantSpeedMotorConstraint(-turnSpeed), CFrame(Vec3(-0.55, 0.0, 1.0), Rotation::Predefined::Y_270), CFrame(Vec3(0.0, 0.0, 0.15), Rotation::Predefined::Y_180)); poweredCarBody->attach(BRWheel, new ConstantSpeedMotorConstraint(-turnSpeed), CFrame(Vec3(-0.55, 0.0, -1.0), Rotation::Predefined::Y_270), CFrame(Vec3(0.0, 0.0, 0.15), Rotation::Predefined::Y_180)); world.addPart(poweredCarBody); //poweredCarBody->getMainPhysical()->motionOfCenterOfMass.angularVelocity = Vec3(0.0, 1.0, 0.0); } { ExtendedPart* mainBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 5.0, 5.0), basicProperties, "Main Block"); ExtendedPart* attachedBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Attached Block"); mainBlock->attach(attachedBlock, new ConstantSpeedMotorConstraint(1.0), CFrame(Vec3(0.0, 0.0, 0.5)), CFrame(Vec3(1.0, 0.0, -0.5))); world.addPart(mainBlock); } // needed for conservation of angular momentum { ExtendedPart* mainBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 5.0, -5.0), basicProperties, "Main Block"); ExtendedPart* attachedBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Attached Block"); mainBlock->attach(attachedBlock, new MotorConstraintTemplate(0.0, 6.283, 1.0), CFrame(Vec3(0.0, 0.0, 0.5)), CFrame(Vec3(0.0, 0.0, -0.5))); world.addPart(mainBlock); } { ExtendedPart* mainBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 5.0, 10.0), basicProperties, "Main Block"); ExtendedPart* attachedBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Attached Block"); ExtendedPart* anotherAttachedBlock = new ExtendedPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties, "Another Attached Block"); ExtendedPart* attachedCylinder = new ExtendedPart(cylinderShape(0.5, 1.0), GlobalCFrame(), basicProperties, "Rotating Attached Block"); ExtendedPart* attachedBall = new ExtendedPart(sphereShape(0.5), GlobalCFrame(), basicProperties, "Attached Ball"); mainBlock->attach(attachedBlock, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.5, 0.0, 0.0, Rotation::Predefined::Y_90), CFrame(-0.5, 0.0, 0.0, Rotation::Predefined::Y_90)); attachedBlock->attach(anotherAttachedBlock, new SinusoidalPistonConstraint(1.0, 2.0, 0.7), CFrame(0.0, 0.5, 0.0, Rotation::Predefined::X_270), CFrame(0.0, -0.5, 0.0, Rotation::Predefined::X_270)); attachedBlock->attach(attachedCylinder, new ConstantSpeedMotorConstraint(3.0), CFrame(0.0, 0.0, 0.55), CFrame(0.0, 0.0, -0.55)); attachedCylinder->attach(attachedBall, new SinusoidalPistonConstraint(0.0, 2.0, 0.7), CFrame(0.0, 0.5, 0.0, Rotation::Predefined::X_270), CFrame(0.0, -0.5, 0.0, Rotation::Predefined::X_270)); world.addPart(mainBlock); mainBlock->getMainPhysical()->motionOfCenterOfMass.rotation.rotation[0] = Vec3(0, 2, 0); } { //WorldBuilder::buildTrebuchet(GlobalCFrame(0, 2, -20), 1.0, 2.0, 0.6, 0.2, 2.4, 0.15, 0); } { ExtendedPart* box1 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-10.0, 5.0, -10.0), basicProperties, "Box1"); ExtendedPart* box2 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-11.1, 5.0, -8.1, Rotation::rotY(3.1415 * 2 / 3)), basicProperties, "Box2"); ExtendedPart* box3 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-8.9, 5.0, -8.1, Rotation::rotY(-3.1415 * 2 / 3)), basicProperties, "Box3"); world.addPart(box1); world.addPart(box2); world.addPart(box3); ConstraintGroup group; group.add(box1, box3, new HingeConstraint(Vec3(1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(-1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0))); group.add(box2, box1, new HingeConstraint(Vec3(1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(-1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0))); //group.add(box3->parent, box2->parent, new HingeConstraint(Vec3(1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(-1.5, 0.0, 0.0), Vec3(0.0, 1.0, 0.0))); world.constraints.push_back(std::move(group)); } { ExtendedPart* box1 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-10.0, 5.0, 10.0), basicProperties, "Box1"); ExtendedPart* box2 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-11.1, 5.0, 11.9, Rotation::rotY(3.1415 * 2 / 3)), basicProperties, "Box2"); ExtendedPart* box3 = new ExtendedPart(boxShape(2.0, 1.0, 1.0), GlobalCFrame(-8.9, 5.0, 11.9, Rotation::rotY(-3.1415 * 2 / 3)), basicProperties, "Box3"); world.addPart(box1); world.addPart(box2); world.addPart(box3); ConstraintGroup group; group.add(box1, box3, new BallConstraint(Vec3(1.0, 0.0, 0.7), Vec3(-1.0, 0.0, 0.7))); group.add(box2, box1, new BallConstraint(Vec3(1.0, 0.0, 0.7), Vec3(-1.0, 0.0, 0.7))); group.add(box3, box2, new BallConstraint(Vec3(1.0, 0.0, 0.7), Vec3(-1.0, 0.0, 0.7))); world.constraints.push_back(std::move(group)); } Shape torusShape = polyhedronShape(ShapeLibrary::createTorus(1.0f, 0.6f, 80, 80)); Graphics::MeshRegistry::registerShapeClass(torusShape.baseShape.get(), Graphics::ExtendedTriangleMesh::generateSmoothNormalsShape(torusShape.baseShape->asPolyhedron())); world.addPart(new ExtendedPart(torusShape, Position(-10.0, 3.0, 0.0), basicProperties, "Torus")); Vec2f toyPoints[]{{0.2f, 0.2f},{0.3f, 0.4f},{0.2f, 0.6f},{0.3f, 0.8f},{0.4f,0.7f},{0.5f,0.4f},{0.6f,0.2f},{0.75f,0.1f},{0.9f,0.015f}}; Shape toyShape = polyhedronShape(ShapeLibrary::createRevolvedShape(0.0f, toyPoints, 9, 1.0f, 10)); world.addPart(new ExtendedPart(toyShape, Position(-10.0, 3.0, 3.0), basicProperties, "ToyPoints")); Vec2f arrowPoints[]{{0.3f,0.1f},{0.3f,0.04f},{1.0f,0.04f}}; Shape arrorShape = polyhedronShape(ShapeLibrary::createRevolvedShape(0.0f, arrowPoints, 3, 1.0f, 40)); world.addPart(new ExtendedPart(arrorShape, Position(-7.0, 3.0, 0.0), basicProperties, "ArrowPoints")); } void buildDebugWorld(Screen& screen, PlayerWorld& world) { buildFloorWorld(screen, world); auto parent = EntityBuilder(screen.registry).name("Spheres").get(); int s = 10; for (int x = 0; x < s; x++) { for (int y = 0; y < s; y++) { for (int z = 0; z < s; z++) { ExtendedPart* part = new ExtendedPart(sphereShape(0.45), Position(x, 2 + y, z), PartProperties(), "Sphere", parent); auto mat = screen.registry.add(part->entity, Graphics::Colors::RED); mat->metalness = 1.0f * x / s; mat->roughness = 1.0f * y / s; mat->ao = 1.0f * z / s; world.addTerrainPart(part); } } } } void buildFloorWorld(Screen& screen, PlayerWorld& world) { WorldBuilder::buildFloorAndWalls(50.0, 50.0, 1.0); Comp::Light::Attenuation attenuation = { 1, 1, 1 }; auto lights = EntityBuilder(screen.registry).name("Lights").get(); auto sphereData = Graphics::MeshRegistry::getMesh(&SphereClass::instance); EntityBuilder(screen.registry).parent(lights).transform(Position(10, 5, -10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 300, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(10, 5, 10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 300, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(-10, 5, -10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 200, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); EntityBuilder(screen.registry).parent(lights).transform(Position(-10, 5, 10), 0.2).light(Graphics::Color(1, 0.84f, 0.69f), 500, attenuation).hitbox(sphereShape(1.0)).mesh(sphereData); } void buildBallWorld(Screen& screen, PlayerWorld& world) { buildFloorWorld(screen, world); Engine::MeshResource* ball = ResourceManager::add("ball", "../res/models/ball.obj"); Graphics::Comp::Mesh mesh = Graphics::MeshRegistry::registerShape(*ball->getShape()); Graphics::TextureResource* ballAlbedo = ResourceManager::add("ball albedo", "../res/textures/ball/ball_color.png"); Graphics::TextureResource* ballNormal = ResourceManager::add("ball normal", "../res/textures/ball/ball_normal.png"); Graphics::TextureResource* ballMetal = ResourceManager::add("ball metal", "../res/textures/ball/ball_metal.png"); Graphics::TextureResource* ballGloss = ResourceManager::add("ball gloss", "../res/textures/ball/ball_gloss.png"); Graphics::TextureResource* ballAO = ResourceManager::add("ball ao", "../res/textures/ball/ball_ao.png"); auto entity = EntityBuilder(screen.registry).transform(Position(0, 8, 0)).mesh(mesh).hitbox(boxShape(10, 10, 10)).get(); auto material = screen.registry.add(entity); material->set(Graphics::Comp::Material::Map_Albedo, SRef(dynamic_cast(ballAlbedo))); material->set(Graphics::Comp::Material::Map_Normal, SRef(dynamic_cast(ballNormal))); material->set(Graphics::Comp::Material::Map_Metalness, SRef(dynamic_cast(ballMetal))); material->set(Graphics::Comp::Material::Map_Roughness, SRef(dynamic_cast(ballGloss))); material->set(Graphics::Comp::Material::Map_AO, SRef(dynamic_cast(ballAO))); } void buildBoxWorld(Screen& screen, PlayerWorld& world) { buildFloorWorld(screen, world); Engine::MeshResource* ball = ResourceManager::add("ball", "../res/models/box_t.obj"); Graphics::ExtendedTriangleMesh shape = *ball->getShape(); const Vec3f* normals = shape.normals.get(); Graphics::Comp::Mesh mesh = Graphics::MeshRegistry::registerShape(shape); Graphics::TextureResource* boxAlbedo = ResourceManager::add("box albedo", "../res/textures/box/MetalPlates006_1K_Color.jpg"); Graphics::TextureResource* boxNormal = ResourceManager::add("box normal", "../res/textures/box/MetalPlates006_1K_NormalGL.jpg"); Graphics::TextureResource* boxMetalness = ResourceManager::add("box metal", "../res/textures/box/MetalPlates006_1K_Metalness.jpg"); Graphics::TextureResource* boxRougness = ResourceManager::add("box roughness", "../res/textures/box/MetalPlates006_1K_Roughness.jpg"); auto entity = EntityBuilder(screen.registry).transform(Position(0, 8, 0)).mesh(mesh).hitbox(boxShape(10, 10, 10)).get(); auto material = screen.registry.add(entity); material->set(Graphics::Comp::Material::Map_Albedo, SRef(dynamic_cast(boxAlbedo))); //material->set(Graphics::Comp::Material::Map_Normal, SRef(dynamic_cast(boxNormal))); material->set(Graphics::Comp::Material::Map_Metalness, SRef(dynamic_cast(boxMetalness))); material->set(Graphics::Comp::Material::Map_Roughness, SRef(dynamic_cast(boxRougness))); } }; ================================================ FILE: application/builtinWorlds.h ================================================ #pragma once #include "worlds.h" #include "view/screen.h" namespace P3D::Application { void buildBenchmarkWorld(PlayerWorld& world); void buildShowcaseWorld(Screen& screen, PlayerWorld& world); void buildDebugWorld(Screen& screen, PlayerWorld& world); void buildBallWorld(Screen& screen, PlayerWorld& world); void buildFloorWorld(Screen& screen, PlayerWorld& world); void buildBoxWorld(Screen& screen, PlayerWorld& world); }; ================================================ FILE: application/core.cpp ================================================ #include "core.h" ================================================ FILE: application/core.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/log.h" #include #define irepeat(x, n) for (int x = 0; x < n; x++) #define frepeat(x, n) for (float x = 0.0f; x < n; x+=1.0f) #define isrepeat(x, n, s) for (int x = 0; x < n; x+=s) #define fsrepeat(x, n, s) for (float x = 0.0f; x < n; x+=s) #ifdef _MSC_VER #define P3D_DEBUGBREAK __debugbreak() #else #include #define P3D_DEBUGBREAK raise(SIGTRAP) #endif ================================================ FILE: application/debugData.natvis ================================================ {double(value) / (1ULL << $T1),g} parent={(void*) parent} mainPhys={(void*) parent->mainPhysical} TrunkNode Size={nodeCount} isGroupHead={isGroupHead} LeafNode {object} isGroupHead={isGroupHead} bounds nodeCount subTrees object index={index}/{node->nodeCount} bounds={node->bounds} isGroupHead={node->isGroupHead} leafNode bounds={node->bounds} Size={top - stack + 1} Top={top->node->bounds} top - stack + 1 stack Invalid LeafNode {(void*) ptr} TreeTrunk {{Size: {(ptr & 0b111) + 1}}} Grouphead {{Size: {(ptr & 0b111) + 1}}} (ptr & 0b111) + 1 ((TreeTrunk*) (ptr & 0xFFFFFFFFFFFFFFF0))->subNodes (TreeTrunk*) (ptr & 0xFFFFFFFFFFFFFFF0) BoundsTree {{baseTrunkSize: {baseTrunkSize}}} baseTrunkSize baseTrunk.subNodes baseTrunk ================================================ FILE: application/ecs/components.h ================================================ #pragma once #include #include #include #include #include #include #include "Physics3D/softlinks/elasticLink.h" #include "Physics3D/softlinks/magneticLink.h" #include "Physics3D/softlinks/springLink.h" #include "../application/extendedPart.h" #include "../worlds.h" #include "Physics3D/misc/toString.h" #include "Physics3D/softlinks/alignmentLink.h" namespace P3D::Application { namespace Comp { struct Hitbox : RC { std::variant hitbox; Hitbox() : hitbox(boxShape(2.0, 2.0, 2.0)) {} Hitbox(ExtendedPart* part) : hitbox(part) {} Hitbox(const Shape& shape) : hitbox(shape) {} Shape getShape() { if (std::holds_alternative(this->hitbox)) { return std::get(hitbox); } else { return std::get(this->hitbox)->hitbox; } } bool isPartAttached() { return std::holds_alternative(this->hitbox); } ExtendedPart* getPart() { return std::get(this->hitbox); } DiagonalMat3 getScale() { return this->getShape().scale; } void setScale(const DiagonalMat3& scale) { if (std::holds_alternative(this->hitbox)) { std::get(this->hitbox)->setScale(scale); } else { std::get(this->hitbox).scale = scale; } } }; struct Transform : RC { struct ScaledCFrame { GlobalCFrame cframe; DiagonalMat3 scale; ScaledCFrame() : cframe(GlobalCFrame()) , scale(DiagonalMat3::IDENTITY()) {} ScaledCFrame(const Position& position) : cframe(position) , scale(DiagonalMat3::IDENTITY()) {} ScaledCFrame(const Position& position, double scale) : cframe(position) , scale(DiagonalMat3::DIAGONAL(scale)) {} ScaledCFrame(const GlobalCFrame& cframe) : cframe(cframe) , scale(DiagonalMat3::IDENTITY()) {} ScaledCFrame(const GlobalCFrame& cframe, double scale) : cframe(cframe) , scale(DiagonalMat3::DIAGONAL(scale)) {} ScaledCFrame(const GlobalCFrame& cframe, const DiagonalMat3& scale) : cframe(cframe) , scale(scale) {} void translate(const Vec3& translation) { this->cframe.translate(translation); } void rotate(const Rotation& rotation) { this->cframe.rotate(rotation); } }; std::variant root; std::variant offset; std::function onChange; Transform() : root(ScaledCFrame()) , offset(nullptr) {} Transform(ExtendedPart* root) : root(root) , offset(nullptr) {} Transform(ExtendedPart* root, CFrame* offset) : root(root) , offset(offset) {} Transform(ExtendedPart* root, const CFrame& offset) : root(root) , offset(offset) {} Transform(const Position& position) : root(ScaledCFrame(position)) , offset(nullptr) {} Transform(const Position& position, double scale) : root(ScaledCFrame(position, scale)) , offset(nullptr) {} Transform(const GlobalCFrame& cframe) : root(ScaledCFrame(cframe)) , offset(nullptr) {} Transform(const GlobalCFrame& cframe, double scale) : root(ScaledCFrame(cframe, scale)) , offset(nullptr) {} Transform(const GlobalCFrame& cframe, const DiagonalMat3& scale) : root(ScaledCFrame(cframe, scale)) , offset(nullptr) {} bool isRootPart() { return std::holds_alternative(this->root); } bool isRootCFrame() { return std::holds_alternative(this->root); } bool hasOffset() { return std::holds_alternative(this->offset) || std::holds_alternative(this->offset) && std::get(this->offset) != nullptr; } bool isOffsetStoredLocal() { if (!hasOffset()) return false; return std::holds_alternative(this->offset); } bool isOffsetStoredRemote() { if (!hasOffset()) return false; return std::holds_alternative(this->offset); } void addCallback(const std::function& onChange) { this->onChange = onChange; } void setRoot(ExtendedPart* root) { this->root = root; } void setRoot(const ScaledCFrame& root) { this->root = root; } void setOffset(CFrame* offset) { this->offset = offset; } void setOffset(const CFrame& offset) { this->offset = offset; } template void modify(UpgradeableMutex& mutex, const Function& function) { if (isRootPart() || isOffsetStoredRemote()) { mutex.lock(); function(); mutex.unlock(); } else function(); if (onChange != nullptr) onChange(); } void translate(const Vec3& translation) { if (hasOffset()) { Vec3 relativeTranslation = getRootCFrame().relativeToLocal(translation); if (isOffsetStoredLocal()) { std::get(this->offset).translate(relativeTranslation); } else { std::get(this->offset)->translate(relativeTranslation); } } else { if (isRootPart()) { std::get(this->root)->translate(translation); } else { std::get(this->root).translate(translation); } } if (onChange != nullptr) onChange(); } void rotate(const Vec3& normal, double angle) { if (hasOffset()) { Vec3 relativeNormal = getRootCFrame().relativeToLocal(normal); Rotation relativeRotation = Rotation::fromRotationVector(relativeNormal * angle); if (isOffsetStoredLocal()) { std::get(this->offset).rotate(relativeRotation); } else { std::get(this->offset)->rotate(relativeRotation); } } else { Rotation rotation = Rotation::fromRotationVector(normal * angle); if (isRootPart()) { ExtendedPart* part = std::get(this->root); part->setCFrame(part->getCFrame().rotated(rotation)); } else { std::get(this->root).rotate(rotation); } } if (onChange != nullptr) onChange(); } void rotate(const Rotation& rotation) { if (hasOffset()) { Rotation rootRotation = getRootCFrame().getRotation(); Rotation relativeRotation = ~rootRotation * rotation * rootRotation; if (isOffsetStoredLocal()) { std::get(this->offset).rotate(relativeRotation); } else { std::get(this->offset)->rotate(relativeRotation); } } else { if (isRootPart()) { ExtendedPart* part = std::get(this->root); part->setCFrame(part->getCFrame().rotated(rotation)); } else { std::get(this->root).rotate(rotation); } } if (onChange != nullptr) onChange(); } void scale(double scaleX, double scaleY, double scaleZ) { if (isRootPart()) { std::get(this->root)->scale(scaleX, scaleY, scaleZ); } else { std::get(this->root).scale *= DiagonalMat3({ scaleX, scaleY, scaleZ }); } if (onChange != nullptr) onChange(); } GlobalCFrame getRootCFrame() { if (isRootPart()) { return std::get(this->root)->getCFrame(); } else { return std::get(this->root).cframe; } } CFrame getOffsetCFrame() { if (hasOffset()) { if (isOffsetStoredLocal()) return std::get(this->offset); else return *std::get(this->offset); } else { return CFrame(); } } GlobalCFrame getCFrame() { GlobalCFrame rootCFrame = getRootCFrame(); CFrame offsetCFrame = getOffsetCFrame(); return rootCFrame.localToGlobal(offsetCFrame); } void setCFrame(const GlobalCFrame& cframe) { if (hasOffset()) { CFrame relativeCFrame = getRootCFrame().globalToLocal(cframe); if (isOffsetStoredLocal()) { std::get(this->offset) = relativeCFrame; } else { *std::get(this->offset) = relativeCFrame; } } else { if (isRootPart()) { std::get(this->root)->setCFrame(cframe); } else { std::get(this->root).cframe = cframe; } } if (onChange != nullptr) onChange(); } Position getPosition() { return getCFrame().getPosition(); } void setPosition(const Position& position) { GlobalCFrame cframe = getCFrame(); cframe.position = position; setCFrame(cframe); } Rotation getRotation() { return getCFrame().getRotation(); } void setRotation(const Rotation& rotation) { GlobalCFrame cframe = getCFrame(); cframe.rotation = rotation; setCFrame(cframe); } DiagonalMat3 getScale() { if (isRootPart()) { return std::get(this->root)->hitbox.scale; } else { return std::get(this->root).scale; } } void setScale(const DiagonalMat3& scale) { if (isRootPart()) { std::get(this->root)->setScale(scale); } else { std::get(this->root).scale = scale; } if (onChange != nullptr) onChange(); } double getWidth() { if (isRootPart()) { return std::get(this->root)->hitbox.getWidth(); } else { return std::get(this->root).scale[0] * 2; } } void setWidth(double width) { if (isRootPart()) { std::get(this->root)->hitbox.setWidth(width); } else { std::get(this->root).scale[0] = width / 2; } if (onChange != nullptr) onChange(); } double getHeight() { if (isRootPart()) { return std::get(this->root)->hitbox.getHeight(); } else { return std::get(this->root).scale[1] * 2; } } void setHeight(double height) { if (isRootPart()) { std::get(this->root)->hitbox.setHeight(height); } else { std::get(this->root).scale[1] = height / 2; } if (onChange != nullptr) onChange(); } double getDepth() { if (isRootPart()) { return std::get(this->root)->hitbox.getDepth(); } else { return std::get(this->root).scale[2] * 2; } } void setDepth(double depth) { if (isRootPart()) { std::get(this->root)->hitbox.setDepth(depth); } else { std::get(this->root).scale[2] = depth / 2; } if (onChange != nullptr) onChange(); } double getMaxRadius() { if (isRootPart()) { return std::get(this->root)->hitbox.getMaxRadius(); } else { return length(Vec3 { getWidth(), getHeight(), getDepth() }); } } Mat4f getModelMatrix(bool scaled = true) { if (scaled) return getCFrame().asMat4WithPreScale(getScale()); else return getCFrame().asMat4(); } }; // The name of an entity struct Name : RC { std::string name; Name(const std::string& name) : name(name) {} void setName(const std::string& name) { this->name = name; } }; // The collider of the entity, as it is being physicsed in the engine struct Collider : RC { ExtendedPart* part; Collider(ExtendedPart* part) : part(part) {} ExtendedPart* operator->() const { return part; } }; struct Light : public RC { struct Attenuation { float constant; float linear; float exponent; }; Graphics::Color color; float intensity; Attenuation attenuation; Light(const Graphics::Color& color, float intensity, const Attenuation& attenuation) : color(color) , intensity(intensity) , attenuation(attenuation) {} }; struct Attachment : public RC { Part* from; Part* to; CFrame* attachment; bool isAttachmentToMainPart = false; Attachment(Part* from, Part* to) : from(from), to(to) { this->isAttachmentToMainPart = to->isMainPart(); this->attachment = &getChildPart()->getAttachToMainPart(); } void setAttachment(const CFrame& cframe) { to->getPhysical()->rigidBody.setAttachFor(getChildPart(), cframe); } ExtendedPart* getMainPart() { return reinterpret_cast(isAttachmentToMainPart ? to : from); } ExtendedPart* getChildPart() { return reinterpret_cast(isAttachmentToMainPart ? from : to); } }; struct SoftLink : public RC { P3D::SoftLink* link; SoftLink(P3D::SoftLink* link) : link(link) {} void setPositionA(const Vec3& position) { link->attachedPartA.attachment.position = position; } void setPositionB(const Vec3& position) { link->attachedPartB.attachment.position = position; } Vec3 getPositionA() const { return link->attachedPartA.attachment.position; } Vec3 getPositionB() const { return link->attachedPartB.attachment.position; } }; struct MagneticLink : public SoftLink { MagneticLink(P3D::MagneticLink* link) : SoftLink(link) {} }; struct SpringLink : public SoftLink { SpringLink(P3D::SpringLink* link) : SoftLink(link) {} }; struct ElasticLink : public SoftLink { ElasticLink(P3D::ElasticLink* link) : SoftLink(link) {} }; struct AlignmentLink : public SoftLink { AlignmentLink(P3D::AlignmentLink* link) : SoftLink(link) {} }; struct HardConstraint : public RC { HardPhysicalConnection* hardConstraint; HardConstraint(HardPhysicalConnection* hardConstraint) : hardConstraint(hardConstraint) {} CFrame* getChildAttachment() { return &hardConstraint->attachOnChild; } CFrame* getParentAttachment() { return &hardConstraint->attachOnParent; } }; struct FixedConstraint : public HardConstraint { FixedConstraint(HardPhysicalConnection* connection) : HardConstraint(connection) {} }; } }; ================================================ FILE: application/ecs/entityBuilder.h ================================================ #pragma once #include #include "../engine/ecs/registry.h" #include "components.h" namespace P3D::Application { class EntityBuilder { private: Engine::Registry64& registry; Engine::Registry64::entity_type entity; public: explicit EntityBuilder(Engine::Registry64& registry) : registry(registry), entity(registry.create()) {} EntityBuilder(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity) : registry(registry), entity(entity) {} [[nodiscard]] Engine::Registry64::entity_type get() const { return entity; } EntityBuilder& parent(const Engine::Registry64::entity_type& entity) { this->registry.setParent(this->entity, entity); return *this; } template EntityBuilder& transform(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& name(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& light(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& collider(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& mesh(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& material(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& hitbox(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& attachment(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& magneticLink(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& elasticLink(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& springLink(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& alignmentLink(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } template EntityBuilder& fixedConstraint(Args&&... args) { this->registry.add(this->entity, std::forward(args)...); return *this; } }; } ================================================ FILE: application/eventHandler.cpp ================================================ #include "core.h" #include "eventHandler.h" #include "view/screen.h" #include "extendedPart.h" namespace P3D::Application { void* EventHandler::getPtr() const { return pointer; } void EventHandler::setPtr(void* ptr) { pointer = ptr; } void EventHandler::setCameraMoveCallback(CameraMoveHandler handler) { cameraMoveHandler = handler; } void EventHandler::setWindowResizeCallback(WindowResizeHandler handler) { windowResizeHandler = handler; } void EventHandler::setPartRayIntersectCallback(PartRayIntersectHandler handler) { partRayIntersectHandler = handler; } void EventHandler::setPartDragCallback(PartDragHandler handler) { partDragHandler = handler; } void EventHandler::setPartClickCallback(PartClickHandler handler) { partClickHandler = handler; } void EventHandler::setPartTouchCallback(PartTouchHandler handler) { partTouchHandler = handler; } void EventHandler::setPartReleaseCallback(PartReleaseHandler handler) { partReleaseHandler = handler; } }; ================================================ FILE: application/eventHandler.h ================================================ #pragma once namespace P3D::Application { class Screen; struct Camera; struct ExtendedPart; typedef void (*CameraMoveHandler) (Screen&, Camera*, Vec3); typedef void (*WindowResizeHandler) (Screen&, Vec2i); typedef void (*PartRayIntersectHandler) (Screen&, ExtendedPart*, Position); typedef void (*PartDragHandler) (Screen&, ExtendedPart*, Vec3); typedef void (*PartClickHandler) (Screen&, ExtendedPart*, Vec3); typedef void (*PartTouchHandler) (ExtendedPart*, ExtendedPart*, Vec3); typedef void (*PartReleaseHandler) (ExtendedPart*, ExtendedPart*); class EventHandler { private: void* pointer = nullptr; public: CameraMoveHandler cameraMoveHandler = [] (Screen&, Camera*, Vec3) {}; WindowResizeHandler windowResizeHandler = [] (Screen&, Vec2i) {}; PartRayIntersectHandler partRayIntersectHandler = [] (Screen&, ExtendedPart*, Position) {}; PartDragHandler partDragHandler = [] (Screen&, ExtendedPart*, Vec3) {}; PartClickHandler partClickHandler = [] (Screen&, ExtendedPart*, Vec3) {}; PartTouchHandler partTouchHandler = [] (ExtendedPart*, ExtendedPart*, Vec3) {}; PartReleaseHandler partReleaseHandler = [] (ExtendedPart*, ExtendedPart*) {}; void* getPtr() const; void setPtr(void* ptr); void setCameraMoveCallback(CameraMoveHandler handler); void setWindowResizeCallback(WindowResizeHandler handler); void setPartRayIntersectCallback(PartRayIntersectHandler handler); void setPartDragCallback(PartDragHandler handler); void setPartClickCallback(PartClickHandler handler); void setPartTouchCallback(PartTouchHandler handler); void setPartReleaseCallback(PartReleaseHandler handler); }; } ================================================ FILE: application/extendedPart.cpp ================================================ #include "core.h" #include "application.h" #include "extendedPart.h" #include "ecs/components.h" #include "view/screen.h" #include "../engine/ecs/registry.h" #include #include "../graphics/meshRegistry.h" namespace P3D::Application { ExtendedPart::ExtendedPart(Part&& part, const Graphics::Comp::Mesh& mesh, const std::string& name, const Entity& parent) : Part(std::move(part)) { if (this->entity == Engine::Registry64::null_entity) this->entity = screen.registry.create(); if (parent != Engine::Registry64::null_entity) screen.registry.setParent(this->entity, parent); screen.registry.add(this->entity, mesh); screen.registry.add(this->entity, this); screen.registry.add(this->entity, this); if (!name.empty()) screen.registry.add(this->entity, name); } ExtendedPart::ExtendedPart(const Shape& hitbox, const GlobalCFrame& position, const PartProperties& properties, const Graphics::Comp::Mesh& mesh, const std::string& name, const Entity& parent) : Part(hitbox, position, properties) { if (this->entity == Engine::Registry64::null_entity) this->entity = screen.registry.create(); if (parent != Engine::Registry64::null_entity) screen.registry.setParent(this->entity, parent); screen.registry.add(this->entity, mesh); screen.registry.add(this->entity, this); screen.registry.add(this->entity, this); if (!name.empty()) screen.registry.add(this->entity, name); } ExtendedPart::ExtendedPart(Part&& part, const std::string& name, const Entity& parent) : Part(std::move(part)) { if (this->entity == Engine::Registry64::null_entity) this->entity = screen.registry.create(); if (parent != Engine::Registry64::null_entity) screen.registry.setParent(this->entity, parent); screen.registry.add(this->entity, Graphics::MeshRegistry::getMesh(part.hitbox.baseShape.get())); screen.registry.add(this->entity, this); screen.registry.add(this->entity, this); if (!name.empty()) screen.registry.add(this->entity, name); } ExtendedPart::ExtendedPart(const Shape& hitbox, const GlobalCFrame& position, const PartProperties& properties, const std::string& name, const Entity& parent) : Part(hitbox, position, properties) { if (this->entity == Engine::Registry64::null_entity) this->entity = screen.registry.create(); if (parent != Engine::Registry64::null_entity) screen.registry.setParent(this->entity, parent); screen.registry.add(this->entity, Graphics::MeshRegistry::getMesh(hitbox.baseShape.get())); screen.registry.add(this->entity, this); screen.registry.add(this->entity, this); if (!name.empty()) screen.registry.add(this->entity, name); } ExtendedPart::ExtendedPart(const Shape& hitbox, ExtendedPart* attachTo, const CFrame& attach, const PartProperties& properties, const std::string& name, const Entity& parent) : Part(hitbox, *attachTo, attach, properties) { if (this->entity == Engine::Registry64::null_entity) this->entity = screen.registry.create(); if (parent != Engine::Registry64::null_entity) screen.registry.setParent(this->entity, parent); screen.registry.add(this->entity, Graphics::MeshRegistry::getMesh(hitbox.baseShape.get())); screen.registry.add(this->entity, this); screen.registry.add(this->entity, this); if (!name.empty()) screen.registry.add(this->entity, name); } ExtendedPart::~ExtendedPart() { // have to do the same as Part's destructor here, because if I don't then PlayerWorld tries to update a deleted entity this->removeFromWorld(); if (this->entity != Engine::Registry64::null_entity) screen.registry.destroy(this->entity); } void ExtendedPart::setMaterial(const Graphics::Comp::Material& material) { screen.registry.add(this->entity, material); } void ExtendedPart::setName(const std::string& name) { screen.registry.add(this->entity, name); } void ExtendedPart::setColor(const Graphics::Color& color) { screen.registry.getOrAdd(this->entity)->albedo = color; } Graphics::Color ExtendedPart::getColor() const { return screen.registry.get(this->entity)->albedo; } }; ================================================ FILE: application/extendedPart.h ================================================ #pragma once #include "../graphics/ecs/components.h" #include "../graphics/extendedTriangleMesh.h" #include #include "../engine/ecs/registry.h" namespace P3D::Application { struct ExtendedPart : public Part { using Entity = Engine::Registry64::entity_type; public: Entity entity = 0; ExtendedPart() = default; ExtendedPart(Part&& part, const std::string& name = "", const Entity& parent = 0); ExtendedPart(Part&& part, const Graphics::Comp::Mesh& mesh, const std::string& name = "", const Entity& parent = 0); ExtendedPart(const Shape& hitbox, const GlobalCFrame& position, const PartProperties& properties, const std::string& name = "", const Entity& parent = 0); ExtendedPart(const Shape& hitbox, const GlobalCFrame& position, const PartProperties& properties, const Graphics::Comp::Mesh& mesh, const std::string& name = "", const Entity& parent = 0); ExtendedPart(const Shape& hitbox, ExtendedPart* attachTo, const CFrame& attach, const PartProperties& properties, const std::string& name = "", const Entity& parent = 0); ~ExtendedPart(); void setMaterial(const Graphics::Comp::Material& material); void setName(const std::string& name); void setColor(const Graphics::Color& color); Graphics::Color getColor() const; }; }; ================================================ FILE: application/input/playerController.cpp ================================================ #include "core.h" #include "playerController.h" #include #include "application.h" #include "view/screen.h" #include "standardInputHandler.h" #include "engine/options/keyboardOptions.h" #include "../extendedPart.h" #define RUN_SPEED 5 #define JUMP_SPEED 6 #define AIR_RUN_SPEED_FACTOR 2 namespace P3D::Application { void PlayerController::apply(WorldPrototype* world) { // Player movement if(!screen.camera.flying) { using namespace Engine; ExtendedPart* player = screen.camera.attachment; Physical* playerPhys = player->getPhysical(); Vec3f playerX = screen.camera.cframe.rotation * Vec3(1, 0, 0); Vec3f playerZ = screen.camera.cframe.rotation * Vec3(0, 0, 1); Vec3 up(0, 1, 0); Vec3 forward = normalize(playerZ % up % up); Vec3 right = -normalize(playerX % up % up); Vec3 total(0, 0, 0); if(handler->getKey(KeyboardOptions::Move::forward)) total += forward; if(handler->getKey(KeyboardOptions::Move::backward)) total -= forward; if(handler->getKey(KeyboardOptions::Move::right)) total += right; if(handler->getKey(KeyboardOptions::Move::left)) total -= right; Vec3 runVector = (lengthSquared(total) >= 0.00005) ? normalize(total) * RUN_SPEED : Vec3(0, 0, 0); Vec3 desiredSpeed = runVector; Vec3 actualSpeed = playerPhys->getMotion().getVelocity(); Vec3 speedToGain = desiredSpeed - actualSpeed; speedToGain.y = 0; playerPhys->mainPhysical->applyForceAtCenterOfMass(speedToGain * player->getMass() * AIR_RUN_SPEED_FACTOR); if(handler->getKey(KeyboardOptions::Move::jump)) runVector += Vec3(0, JUMP_SPEED, 0); player->properties.conveyorEffect = runVector; } } }; ================================================ FILE: application/input/playerController.h ================================================ #pragma once #include namespace P3D::Application { class PlayerController : public ExternalForce { virtual void apply(WorldPrototype* world) override; }; }; ================================================ FILE: application/input/standardInputHandler.cpp ================================================ #include "core.h" #include "standardInputHandler.h" #include #include #include #include "../engine/input/keyboard.h" #include "../application/worldBuilder.h" #include "../engine/options/keyboardOptions.h" #include "../graphics/renderer.h" #include "../application.h" #include "../graphics/gui/gui.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/buffers/frameBuffer.h" #include "../graphics/glfwUtils.h" #include "../worlds.h" #include "../view/screen.h" #include "ecs/components.h" #include "../view/camera.h" #include #include "layer/pickerLayer.h" #include "../picker/tools/translationTool.h" namespace P3D::Application { #define KEY_BIND(name) \ if (key == name) #define KEY_RANGE(first, second) \ if (key > first && key < second) StandardInputHandler::StandardInputHandler(GLFWwindow* window, Screen& screen) : InputHandler(window), screen(screen) {} void StandardInputHandler::onEvent(Engine::Event& event) { using namespace Engine; Application::onEvent(event); EventDispatcher dispatcher(event); if (dispatcher.dispatch(EVENT_BIND(StandardInputHandler::onKeyPress))) return; if (dispatcher.dispatch(EVENT_BIND(StandardInputHandler::onDoubleKeyPress))) return; if (dispatcher.dispatch(EVENT_BIND(StandardInputHandler::onWindowResize))) return; } bool StandardInputHandler::onWindowResize(Engine::WindowResizeEvent& event) { Vec2i dimension = Vec2i(event.getWidth(), event.getHeight()); Graphics::Renderer::viewport(Vec2i(), dimension); (*screen.eventHandler.windowResizeHandler) (screen, dimension); return true; } bool StandardInputHandler::onFrameBufferResize(Engine::FrameBufferResizeEvent& event) { Vec2i dimension = Vec2i(event.getWidth(), event.getHeight()); Graphics::Renderer::viewport(Vec2i(), dimension); float aspect = float(dimension.x) / float(dimension.y); screen.camera.onUpdate(aspect); screen.dimension = dimension; screen.screenFrameBuffer->resize(screen.dimension); Graphics::GUI::windowInfo.aspect = aspect; Graphics::GUI::windowInfo.dimension = dimension; (*screen.eventHandler.windowResizeHandler) (screen, dimension); return true; } bool StandardInputHandler::onKeyPressOrRepeat(Engine::KeyPressEvent& event) { using namespace Engine; Key key = event.getKey(); KEY_BIND(KeyboardOptions::Tick::Speed::up) { setSpeed(getSpeed() * 1.5); Log::info("TPS is now: %f", getSpeed()); } KEY_BIND(KeyboardOptions::Tick::Speed::down) { setSpeed(getSpeed() / 1.5); Log::info("TPS is now: %f", getSpeed()); } KEY_BIND(KeyboardOptions::Tick::run) { if (isPaused()) runTick(); } KEY_BIND(Keyboard::KEY_O) { std::unique_lock worldLock(*screen.worldMutex); Position pos(0.0 + (rand() % 100) * 0.001, 1.0 + (rand() % 100) * 0.001, 0.0 + (rand() % 100) * 0.001); WorldBuilder::createDominoAt(GlobalCFrame(pos, Rotation::fromEulerAngles(0.2, 0.3, 0.7))); Log::info("Created domino! There are %d objects in the world! ", screen.world->getPartCount()); } return true; } bool StandardInputHandler::onKeyPress(Engine::KeyPressEvent& event) { using namespace Graphics::VisualDebug; using namespace Engine; Key key = event.getKey(); KEY_BIND(KeyboardOptions::Tick::pause) { togglePause(); } KEY_BIND(KeyboardOptions::Part::remove) { if (screen.selectedPart != nullptr) { std::unique_lock worldLock(*screen.worldMutex); delete screen.selectedPart; TranslationTool::magnet.selectedPart = nullptr; screen.selectedPart = nullptr; } } KEY_BIND(KeyboardOptions::Debug::pies) { renderPiesEnabled = !renderPiesEnabled; } KEY_BIND(KeyboardOptions::Part::makeMainPart) { Log::info("Made %s the main part of it's physical", screen.registry.getOr(screen.selectedPart->entity, "").name.c_str()); screen.selectedPart->makeMainPart(); } KEY_BIND(KeyboardOptions::Part::makeMainPhysical) { if (screen.selectedPart) { Physical* selectedPartPhysical = screen.selectedPart->getPhysical(); if (selectedPartPhysical != nullptr) { if (!selectedPartPhysical->isMainPhysical()) { Log::info("Made %s the main physical", screen.registry.getOr(screen.selectedPart->entity, "").name.c_str()); ((ConnectedPhysical*) selectedPartPhysical)->makeMainPhysical(); } else { Log::warn("This physical is already the main physical!"); } } else { Log::warn("This part has no physical!"); } } } KEY_BIND(KeyboardOptions::World::valid) { Log::debug("Checking World::isValid()"); std::shared_lock worldReadLock(*screen.worldMutex); if(screen.world->isValid()) { Log::info("World is valid!"); } else { Log::info("World is not valid!"); } } KEY_BIND(KeyboardOptions::Edit::rotate) { PickerLayer::toolManagers[0].selectTool("Rotate"); } KEY_BIND(KeyboardOptions::Edit::translate) { PickerLayer::toolManagers[0].selectTool("Translate"); } KEY_BIND(KeyboardOptions::Edit::scale) { PickerLayer::toolManagers[0].selectTool("Scale"); } KEY_BIND(KeyboardOptions::Edit::select) { PickerLayer::toolManagers[0].selectTool("Select"); } KEY_BIND(KeyboardOptions::Edit::region) { PickerLayer::toolManagers[0].selectTool("Select region"); } KEY_BIND(KeyboardOptions::Debug::spheres) { colissionSpheresMode = static_cast((static_cast(colissionSpheresMode) + 1) % 3); } KEY_BIND(KeyboardOptions::Debug::tree) { colTreeRenderMode++; int worldLayerCount = getMaxLayerID(world.layers); // must be a signed int, because size_t comparison makes the comparison unsigned if(colTreeRenderMode >= worldLayerCount) { colTreeRenderMode = -2; } } KEY_BIND(KeyboardOptions::Application::close) { Graphics::GLFW::closeWindow(); } else if (key == Keyboard::KEY_F11) { Graphics::GLFW::swapFullScreen(); } KEY_RANGE(Keyboard::KEY_F1, Keyboard::KEY_F9) { toggleVectorType(static_cast(key.getCode() - Keyboard::KEY_F1.getCode())); } KEY_RANGE(Keyboard::KEY_NUMBER_1, Keyboard::KEY_NUMBER_3) { togglePointType(static_cast(key.getCode() - Keyboard::KEY_NUMBER_1.getCode())); } return onKeyPressOrRepeat(event); }; bool StandardInputHandler::onDoubleKeyPress(Engine::DoubleKeyPressEvent& event) { using namespace Engine; Key key = event.getKey(); KEY_BIND(KeyboardOptions::Move::fly) { toggleFlying(); } return true; } }; ================================================ FILE: application/input/standardInputHandler.h ================================================ #pragma once #include "../engine/input/inputHandler.h" #include "../engine/event/keyEvent.h" #include "../engine/event/windowEvent.h" namespace P3D::Application { class Screen; class StandardInputHandler : public Engine::InputHandler { public: Screen& screen; StandardInputHandler(GLFWwindow* window, Screen& screen); void onEvent(Engine::Event& event) override; bool onFrameBufferResize(Engine::FrameBufferResizeEvent& event); bool onWindowResize(Engine::WindowResizeEvent& event); bool onKeyPress(Engine::KeyPressEvent& event); bool onKeyPressOrRepeat(Engine::KeyPressEvent& event); bool onDoubleKeyPress(Engine::DoubleKeyPressEvent& event); }; }; ================================================ FILE: application/io/saveDialog.cpp ================================================ #include "core.h" #include "saveDialog.h" #include #include #include namespace P3D::Application { #ifdef _WIN32 #include bool saveWorldDialog(char* worldFilePath) { //glfwGetWin32Window(window); OPENFILENAME ofn; strcpy(worldFilePath, "new.world"); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); //ofn.hwndOwner = hwnd; ofn.lpstrFilter = "World Files (*.world)\0*.world\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = worldFilePath; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; ofn.lpstrDefExt = "world"; return GetSaveFileName(&ofn); // saves the filename to input parameter worldFilePath, returns true if user confirmed } bool openWorldDialog(char* worldFilePath) { //glfwGetWin32Window(window); OPENFILENAME ofn; strcpy(worldFilePath, ""); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); //ofn.hwndOwner = hwnd; ofn.lpstrFilter = "World Files (*.world)\0*.world\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = worldFilePath; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; ofn.lpstrDefExt = "world"; return GetOpenFileName(&ofn); // saves the filename to input parameter worldFilePath, returns true if user confirmed } #else static void stripFinalNewlines(char* fileName) { std::size_t lastChar = strlen(fileName) - 1; while(isspace(fileName[lastChar])){ fileName[lastChar] = '\0'; lastChar--; } } bool saveWorldDialog(char* worldFilePath) { FILE* fileDialog = popen("zenity --file-selection --save --confirm-overwrite --file-filter=*.world 2>/dev/null", "r"); if(fileDialog != NULL) { if(!fgets(worldFilePath, MAX_PATH_LENGTH, fileDialog)) throw "fgets error!"; if(pclose(fileDialog) == 0) { // OK save file stripFinalNewlines(worldFilePath); return true; } else { return false; } } else { std::cout << "Save *.world to> "; std::string fileName; std::cin >> fileName; if(fileName.empty()) return false; strcpy(worldFilePath, fileName.c_str()); return true; } } bool openWorldDialog(char* worldFilePath) { FILE* fileDialog = popen("zenity --file-selection --file-filter=*.world 2>/dev/null", "r"); if(fileDialog != NULL) { if(!fgets(worldFilePath, MAX_PATH_LENGTH, fileDialog)) throw "fgets error!"; if(pclose(fileDialog) == 0) { // OK save file stripFinalNewlines(worldFilePath); return true; } else { return false; } } else { std::cout << "Input world to open> "; std::string fileName; std::cin >> fileName; if(fileName.empty()) return false; strcpy(worldFilePath, fileName.c_str()); return true; } } #endif }; ================================================ FILE: application/io/saveDialog.h ================================================ #pragma once namespace P3D::Application { constexpr size_t MAX_PATH_LENGTH = 1024; // returns true if user selected save bool saveWorldDialog(char* worldFilePath); // returns true if user selected open bool openWorldDialog(char* worldFilePath); }; ================================================ FILE: application/io/serialization.cpp ================================================ #include "core.h" #include "serialization.h" #include "../util/fileUtils.h" #include "extendedPart.h" #include "application.h" #include "view/screen.h" #include "ecs/components.h" #include "../engine/ecs/registry.h" #include #include "../worlds.h" #include #include #include #include #include namespace P3D::Application { FixedSharedObjectSerializerDeserializer textureSerializer{nullptr}; void WorldImportExport::registerTexture(Graphics::Texture* texture) { textureSerializer.registerObject(texture); } static void serializeMaterial(const Graphics::Comp::Material& material, std::ostream& ostream) { textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Albedo).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Normal).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Metalness).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Roughness).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_AO).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Gloss).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Specular).get(), ostream); textureSerializer.serialize(material.get(Graphics::Comp::Material::Map_Displacement).get(), ostream); serializeBasicTypes(material.albedo, ostream); serializeBasicTypes(material.metalness, ostream); serializeBasicTypes(material.roughness, ostream); serializeBasicTypes(material.ao, ostream); } static Graphics::Comp::Material deserializeMaterial(std::istream& istream) { SRef albedoMap(textureSerializer.deserialize(istream)); SRef normalMap(textureSerializer.deserialize(istream)); SRef metalnessMap(textureSerializer.deserialize(istream)); SRef roughnessMap(textureSerializer.deserialize(istream)); SRef aoMap(textureSerializer.deserialize(istream)); SRef glossMap(textureSerializer.deserialize(istream)); SRef specularMap(textureSerializer.deserialize(istream)); SRef displacementrMap(textureSerializer.deserialize(istream)); Graphics::Color albedo = deserializeBasicTypes(istream); float metalness = deserializeBasicTypes(istream); float roughness = deserializeBasicTypes(istream); float ao = deserializeBasicTypes(istream); Graphics::Comp::Material material = Graphics::Comp::Material(albedo, metalness, roughness, ao); material.set(Graphics::Comp::Material::Map_Albedo, albedoMap); material.set(Graphics::Comp::Material::Map_Normal, normalMap); material.set(Graphics::Comp::Material::Map_Metalness, metalnessMap); material.set(Graphics::Comp::Material::Map_Roughness, roughnessMap); material.set(Graphics::Comp::Material::Map_AO, aoMap); material.set(Graphics::Comp::Material::Map_Gloss, glossMap); material.set(Graphics::Comp::Material::Map_Specular, specularMap); material.set(Graphics::Comp::Material::Map_Displacement, displacementrMap); return material; } class Serializer : public SerializationSession { public: using SerializationSession::SerializationSession; virtual void serializePartExternalData(const ExtendedPart& part, std::ostream& ostream) override { // TODO integrate components into serialization serializeMaterial(screen.registry.getOr(part.entity), ostream); serializeString(screen.registry.getOr(part.entity, "").name, ostream); } }; class Deserializer : public DeSerializationSession { public: using DeSerializationSession::DeSerializationSession; virtual ExtendedPart* deserializeExtendedPart(Part&& partPhysicalData, std::istream& istream) override { Graphics::Comp::Material material = deserializeMaterial(istream); ExtendedPart* result = new ExtendedPart(std::move(partPhysicalData), deserializeString(istream)); result->setMaterial(material); return result; } }; static void openWriteFile(std::ofstream& fstream, const char* fileName) { std::string fullPath = Util::getFullPath(fileName); Log::info("Writing to file %s", fullPath.c_str()); fstream.open(fullPath, std::ios::binary); if(!fstream.is_open()) { throw "File not opened!"; } } static void openReadFile(std::ifstream& fstream, const char* fileName) { std::string fullPath = Util::getFullPath(fileName); Log::info("Reading file %s", fullPath.c_str()); fstream.open(fullPath, std::ios::binary); if(!fstream.is_open()) { throw "File not opened!"; } } void WorldImportExport::saveLooseParts(const char* fileName, size_t numberOfParts, const ExtendedPart* const parts[]) { std::ofstream file; openWriteFile(file, fileName); Serializer serializer; serializer.serializeParts(parts, numberOfParts, file); file.close(); } void WorldImportExport::loadLoosePartsIntoWorld(const char* fileName, PlayerWorld& world) { std::ifstream file; openReadFile(file, fileName); Deserializer d; std::vector result = d.deserializeParts(file); file.close(); for(ExtendedPart* p : result) { world.addPart(p); } } void WorldImportExport::loadNativePartsIntoWorld(const char* fileName, PlayerWorld& world) { std::ifstream file; openReadFile(file, fileName); DeSerializationSessionPrototype d; std::vector result = d.deserializeParts(file); file.close(); for(Part* p : result) { Log::debug("Part cframe: %s", str(p->getCFrame()).c_str()); world.addPart(new ExtendedPart(std::move(*p))); } } void WorldImportExport::saveWorld(const char* fileName, const PlayerWorld& world) { std::ofstream file; openWriteFile(file, fileName); Serializer serializer; serializer.serializeWorld(world, file); file.close(); } void WorldImportExport::loadWorld(const char* fileName, PlayerWorld& world) { std::ifstream file; openReadFile(file, fileName); if(!file.is_open()) { throw std::runtime_error("Could not open file!"); } Deserializer deserializer; deserializer.deserializeWorld(world, file); assert(world.isValid()); file.close(); } }; ================================================ FILE: application/io/serialization.h ================================================ #pragma once #include #include "extendedPart.h" #include "../worlds.h" #include "../extendedPart.h" namespace P3D::Application { namespace WorldImportExport { void registerTexture(Graphics::Texture* texture); void saveWorld(const char* fileName, const PlayerWorld& world); void saveLooseParts(const char* fileName, size_t numberOfParts, const ExtendedPart* const parts[]); void loadWorld(const char* fileName, PlayerWorld& world); void loadLoosePartsIntoWorld(const char* fileName, PlayerWorld& world); void loadNativePartsIntoWorld(const char* fileName, PlayerWorld& world); }; }; ================================================ FILE: application/layer/cameraLayer.cpp ================================================ #include "core.h" #include "cameraLayer.h" #include "../view/screen.h" #include "../application/input/standardInputHandler.h" #include "../engine/options/keyboardOptions.h" namespace P3D::Application { void CameraLayer::onInit(Engine::Registry64& registry) { Screen* screen = static_cast(this->ptr); // Camera init screen->camera.setPosition(Position(1.0, 2.0, 3.0)); screen->camera.setRotation(Vec3(0, 3.1415, 0.0)); screen->camera.onUpdate(1.0f, screen->camera.aspect, 0.01f, 10000.0f); } void CameraLayer::onUpdate(Engine::Registry64& registry) { using namespace Engine; Screen* screen = static_cast(this->ptr); std::chrono::time_point curUpdate = std::chrono::steady_clock::now(); std::chrono::nanoseconds deltaTnanos = curUpdate - this->lastUpdate; this->lastUpdate = curUpdate; double speedAdjustment = deltaTnanos.count() * 0.000000001 * 60.0; // IO events if (handler->anyKey) { bool leftDragging = handler->leftDragging; if (handler->getKey(KeyboardOptions::Move::forward)) screen->camera.move(*screen, 0, 0, -1 * speedAdjustment, leftDragging); if (handler->getKey(KeyboardOptions::Move::backward)) screen->camera.move(*screen, 0, 0, 1 * speedAdjustment, leftDragging); if (handler->getKey(KeyboardOptions::Move::right)) screen->camera.move(*screen, 1 * speedAdjustment, 0, 0, leftDragging); if (handler->getKey(KeyboardOptions::Move::left)) screen->camera.move(*screen, -1 * speedAdjustment, 0, 0, leftDragging); if (handler->getKey(KeyboardOptions::Move::ascend)) if (screen->camera.flying) screen->camera.move(*screen, 0, 1 * speedAdjustment, 0, leftDragging); if (handler->getKey(KeyboardOptions::Move::descend)) if (screen->camera.flying) screen->camera.move(*screen, 0, -1 * speedAdjustment, 0, leftDragging); if (handler->getKey(KeyboardOptions::Rotate::left)) screen->camera.rotate(*screen, 0, 1 * speedAdjustment, 0, leftDragging); if (handler->getKey(KeyboardOptions::Rotate::right)) screen->camera.rotate(*screen, 0, -1 * speedAdjustment, 0, leftDragging); if (handler->getKey(KeyboardOptions::Rotate::up)) screen->camera.rotate(*screen, 1 * speedAdjustment, 0, 0, leftDragging); if (handler->getKey(KeyboardOptions::Rotate::down)) screen->camera.rotate(*screen, -1 * speedAdjustment, 0, 0, leftDragging); } screen->camera.onUpdate(); } void CameraLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { Screen* screen = static_cast(this->ptr); screen->camera.onEvent(event); } void CameraLayer::onRender(Engine::Registry64& registry) { Screen* screen = static_cast(this->ptr); } void CameraLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/cameraLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" #include namespace P3D::Application { class Screen; class CameraLayer : public Engine::Layer { private: std::chrono::time_point lastUpdate = std::chrono::steady_clock::now(); public: inline CameraLayer() : Layer() {}; inline CameraLayer(Screen* screen, char flags = None) : Layer("CameraLayer", screen, flags) {}; virtual void onInit(Engine::Registry64& registry) override; virtual void onUpdate(Engine::Registry64& registry) override; virtual void onEvent(Engine::Registry64& registry, Engine::Event& event) override; virtual void onRender(Engine::Registry64& registry) override; virtual void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/constraintLayer.cpp ================================================ #include "core.h" #include "constraintLayer.h" #include "worlds.h" #include "../view/screen.h" #include "../shader/shaders.h" #include "../graphics/renderer.h" #include "../graphics/meshRegistry.h" #include #include #include #include #include #include #include #include #include #include namespace P3D::Application { void ConstraintLayer::onInit(Engine::Registry64& registry) { } void ConstraintLayer::onUpdate(Engine::Registry64& registry) { } static void renderObject(const Graphics::Comp::Mesh& shape, const GlobalCFrame& cframe, const DiagonalMat3f& scale, const Graphics::Comp::Material& material) { Shaders::basicShader->updateMaterial(material); Shaders::basicShader->updateTexture(false); Shaders::basicShader->updateModel(cframe, scale); MeshRegistry::get(shape)->render(); } static void renderConstraintLineBetween(Position p1, Position p2) { Vec3 delta = p2 - p1; if (lengthSquared(delta) < 0.0001) return; Position center = p1 + delta/2; Rotation rot = Rotation::faceY(delta); renderObject(MeshRegistry::box, GlobalCFrame(center, rot), DiagonalMat3f{0.2f, float(length(delta) / 2), 0.2f}, Graphics::Comp::Material(Color(1.0f, 0.0f, 0.0f, 1.0f))); renderObject(MeshRegistry::box, GlobalCFrame(p1, rot), DiagonalMat3f{0.25f, 0.25f, 0.25f}, Graphics::Comp::Material(Color(0.0f, 1.0f, 0.0f, 1.0f))); renderObject(MeshRegistry::box, GlobalCFrame(p2, rot), DiagonalMat3f{0.25f, 0.25f, 0.25f}, Graphics::Comp::Material(Color(0.0f, 1.0f, 0.0f, 1.0f))); } static Color constraintBarColor = Colors::RGB_R; constexpr static float constraintBarThickness = 0.02f; constexpr static float innerBallThickness = 0.06f; constexpr static float outerBallThickness = 0.07f; static Graphics::Comp::Material innerConstraintColor = Graphics::Comp::Material(Color(0.0f, 0.0f, 1.0f, 1.0f)); static Graphics::Comp::Material outerConstraintColor = Graphics::Comp::Material(Color(0.0f, 0.0f, 1.0f, 0.7f)); static void renderBar(GlobalCFrame cframe, Vec3 delta, float thickness, Color color) { if (lengthSquared(delta) < 0.0001) return; Rotation rotation = Rotation::faceZ(delta); renderObject(MeshRegistry::box, cframe.localToGlobal(CFrame(delta/2, rotation)), DiagonalMat3f{thickness, thickness, float(length(delta) / 2)}, Graphics::Comp::Material(color)); } static void renderPiston(const SinusoidalPistonConstraint* piston, const GlobalCFrame& start, const GlobalCFrame& end, int segments, float minThickness, float maxThickness) { Vec3 delta = start.globalToLocal(end).getPosition(); Vec3 step = delta / segments; if (lengthSquared(delta) < 0.0001) return; Rotation rot = Rotation::faceZ(delta); for (int i = 0; i < segments; i++) { Vec3 center = step * (i + 0.5); float thickness = i / (segments - 1.0f) * (maxThickness - minThickness) + minThickness; Graphics::Comp::Material mat = (i%2 == 0) ? Graphics::Comp::Material(Color(1.0f, 0.8f, 0.1f, 1.0f)) : Graphics::Comp::Material(Color(0.9f, 0.9f, 0.9f, 1.0f)); renderObject(MeshRegistry::cylinder, start.localToGlobal(CFrame(center, rot)), DiagonalMat3f{thickness, thickness, float(length(step) / 2)}, mat); } renderObject(MeshRegistry::sphere, start, DiagonalMat3f::IDENTITY() * minThickness * 1.2f, Graphics::Comp::Material(Color(0.0f, 1.0f, 0.0f, 1.0f))); renderObject(MeshRegistry::sphere, end, DiagonalMat3f::IDENTITY() * maxThickness * 1.2f, Graphics::Comp::Material(Color(0.0f, 1.0f, 0.0f, 1.0f))); } static void renderMotor(const ConstantSpeedMotorConstraint* motor, const GlobalCFrame& start, const GlobalCFrame& end) { renderObject(MeshRegistry::hexagon, start.localToGlobal(CFrame(Vec3(0, 0, 0.05))), DiagonalMat3f{0.2f, 0.2f, 0.1f}, Graphics::Comp::Material(Color(1.0f, 1.0f, 0.0f, 1.0f))); renderObject(MeshRegistry::hexagon, end.localToGlobal(CFrame(Vec3(0, 0, -0.05))), DiagonalMat3f{0.2f, 0.2f, 0.1f}, Graphics::Comp::Material(Color(0.7f, 0.7f, 0.0f, 1.0f))); } static void renderBallConstraint(const GlobalCFrame& cframeA, const GlobalCFrame& cframeB, const BallConstraint* bc) { renderBar(cframeA, bc->attachA, constraintBarThickness, constraintBarColor); renderBar(cframeB, bc->attachB, constraintBarThickness, constraintBarColor); renderObject(MeshRegistry::sphere, cframeA.localToGlobal(CFrame(bc->attachA)), DiagonalMat3f::IDENTITY() * innerBallThickness, innerConstraintColor); renderObject(MeshRegistry::sphere, cframeB.localToGlobal(CFrame(bc->attachB)), DiagonalMat3f::IDENTITY() * outerBallThickness, outerConstraintColor); } static void renderHingeConstraint(const GlobalCFrame& cframeA, const GlobalCFrame& cframeB, const HingeConstraint* hc) { renderBar(cframeA, hc->attachA, constraintBarThickness, constraintBarColor); renderBar(cframeB, hc->attachB, constraintBarThickness, constraintBarColor); CFrame atA(hc->attachA, Rotation::faceZ(hc->axisA)); CFrame atB(hc->attachB, Rotation::faceZ(hc->axisB)); renderObject(MeshRegistry::cylinder, cframeA.localToGlobal(atA), DiagonalMat3f::IDENTITY() * innerBallThickness, innerConstraintColor); renderObject(MeshRegistry::cylinder, cframeB.localToGlobal(atB), DiagonalMat3f::IDENTITY() * outerBallThickness, outerConstraintColor); } static void renderBarConstraint(const GlobalCFrame& cframeA, const GlobalCFrame& cframeB, const BarConstraint* bc) { renderBar(cframeA, bc->attachA, constraintBarThickness, constraintBarColor); renderBar(cframeB, bc->attachB, constraintBarThickness, constraintBarColor); Position globalA = cframeA.localToGlobal(bc->attachA); Position globalB = cframeB.localToGlobal(bc->attachB); Position barCenter = avg(globalA, globalB); Vec3 bar; if(globalA != globalB) { bar = withLength(Vec3(globalB - globalA), bc->barLength); } else { bar = Vec3(bc->barLength, 0, 0); } renderBar(barCenter - bar/2, bar, constraintBarThickness, constraintBarColor); renderObject(MeshRegistry::sphere, cframeA.localToGlobal(CFrame(bc->attachA)), DiagonalMat3f::IDENTITY() * innerBallThickness, innerConstraintColor); renderObject(MeshRegistry::sphere, cframeB.localToGlobal(CFrame(bc->attachB)), DiagonalMat3f::IDENTITY() * outerBallThickness, outerConstraintColor); renderObject(MeshRegistry::sphere, barCenter - bar / 2, DiagonalMat3f::IDENTITY() * innerBallThickness, innerConstraintColor); renderObject(MeshRegistry::sphere, barCenter + bar / 2, DiagonalMat3f::IDENTITY() * outerBallThickness, outerConstraintColor); } static void renderHardConstraint( const ConnectedPhysical& conPhys) { GlobalCFrame cframeOfConPhys = conPhys.getCFrame(); GlobalCFrame cframeOfParent = conPhys.parent->getCFrame(); renderBar(cframeOfConPhys, conPhys.connectionToParent.attachOnChild.position, constraintBarThickness, constraintBarColor); renderBar(cframeOfParent, conPhys.connectionToParent.attachOnParent.position, constraintBarThickness, constraintBarColor); const HardConstraint* constraint = conPhys.connectionToParent.constraintWithParent.get(); GlobalCFrame startOfConstraint = cframeOfConPhys.localToGlobal(conPhys.connectionToParent.attachOnChild); GlobalCFrame endOfConstraint = cframeOfParent.localToGlobal(conPhys.connectionToParent.attachOnParent); const auto& info(typeid(*constraint)); if (info == typeid(SinusoidalPistonConstraint)) { renderPiston(dynamic_cast(constraint), startOfConstraint, endOfConstraint, 3, 0.1f, 0.12f); } else if (info == typeid(ConstantSpeedMotorConstraint)) { renderMotor(dynamic_cast(constraint), startOfConstraint, endOfConstraint); } } static void recurseRenderHardConstraints(const Physical& physical) { for(const ConnectedPhysical& conPhys : physical.childPhysicals) { renderHardConstraint(conPhys); recurseRenderHardConstraints(conPhys); } } static void renderConstraint(const PhysicalConstraint& constraint) { const auto& info(typeid(*constraint.constraint)); GlobalCFrame cfA = constraint.physA->getCFrame(); GlobalCFrame cfB = constraint.physB->getCFrame(); if(info == typeid(BallConstraint)) { renderBallConstraint(cfA, cfB, dynamic_cast(constraint.constraint)); } else if(info == typeid(HingeConstraint)) { renderHingeConstraint(cfA, cfB, dynamic_cast(constraint.constraint)); } else if(info == typeid(BarConstraint)) { renderBarConstraint(cfA, cfB, dynamic_cast(constraint.constraint)); } } static void renderSpringLink(const GlobalCFrame& start) { renderObject(MeshRegistry::sphere, start.localToGlobal(CFrame(Vec3(0, 0, 0.05))), DiagonalMat3f{ 0.2f, 0.2f, 0.1f }, Graphics::Comp::Material(Color(1.0f, 1.0f, 0.0f, 1.0f))); } /*static void renderSoftLinkConstraint(const ConstraintLayer* cl, const SoftLink* link) { renderConstraintLineBetween(link->getGlobalPositionOfAttach2(), link->getGlobalPositionOfAttach1()); }*/ void ConstraintLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); beginScene(); enableBlending(); Shaders::basicShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); Shaders::maskShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); { std::shared_lock worldReadLock(*screen->worldMutex); for(MotorizedPhysical* phys : screen->world->physicals) { recurseRenderHardConstraints(*phys); } for(const ConstraintGroup& g : screen->world->constraints) { for(const PhysicalConstraint& constraint : g.constraints) { renderConstraint(constraint); } } /*for (const SoftLink* springLink : screen->world->springLinks) { renderSoftLinkConstraint(this, springLink); }*/ } endScene(); } void ConstraintLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void ConstraintLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/constraintLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class ConstraintLayer : public Engine::Layer { public: ConstraintLayer() = default; ConstraintLayer(Screen* screen, char flags = None) : Layer("Constraint layer", screen, flags) {} virtual ~ConstraintLayer() = default; void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onRender(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/debugLayer.cpp ================================================ #include "core.h" #include "debugLayer.h" #include "../graphics/renderer.h" #include "../graphics/ecs/components.h" #include "../graphics/mesh/arrayMesh.h" #include "../graphics/mesh/pointMesh.h" #include "../graphics/mesh/vectorMesh.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/debug/visualDebug.h" #include #include #include #include #include #include #include "worlds.h" #include "view/screen.h" #include "graphics/meshRegistry.h" #include "shader/shaders.h" namespace P3D::Application { Vec4f colors[] { Colors::BLUE, Colors::GREEN, Colors::YELLOW, Colors::ORANGE, Colors::RED, Colors::PURPLE }; void renderSphere(double radius, const Position& position, const Color& color) { Shaders::basicShader->updateMaterial(Graphics::Comp::Material(color)); Shaders::basicShader->updateModel(join(Mat3f::IDENTITY() * static_cast(radius), castPositionToVec3f(position), Vec3f(0.0f,0.0f,0.0f),1.0f)); Graphics::MeshRegistry::get(Graphics::MeshRegistry::sphere)->render(); } void renderBox(const GlobalCFrame& cframe, double width, double height, double depth, const Color& color) { Shaders::basicShader->updateMaterial(Graphics::Comp::Material(color)); Shaders::basicShader->updateModel(Mat4f(cframe.asMat4WithPreScale(DiagonalMat3{width, height, depth}))); Graphics::MeshRegistry::get(Graphics::MeshRegistry::box)->render(); } void renderBounds(const Bounds& bounds, const Color& color) { Vec3Fix diagonal = bounds.getDiagonal(); Position position = bounds.getCenter(); renderBox(GlobalCFrame(position), static_cast(diagonal.x), static_cast(diagonal.y), static_cast(diagonal.z), color); } static Color getCyclingColor(int depth) { Color color = colors[depth % 6]; color.a = 0.3f; return color; } static void renderBoundsForDepth(const Bounds& bounds, int depth) { renderBounds(bounds.expanded((10 - depth) * 0.002), getCyclingColor(depth)); } static void recursiveRenderColTree(const P3D::TreeTrunk& curTrunk, int curTrunkSize, int depth) { for(int i = 0; i < curTrunkSize; i++) { const TreeNodeRef& subNode = curTrunk.subNodes[i]; if(subNode.isTrunkNode()) { recursiveRenderColTree(subNode.asTrunk(), subNode.getTrunkSize(), depth + 1); } renderBoundsForDepth(curTrunk.getBoundsOfSubNode(i), depth); } } static bool recursiveColTreeForOneObject(const TreeTrunk& curTrunk, int curTrunkSize, const Part* part, const Bounds& bounds, int depth) { for(int i = 0; i < curTrunkSize; i++) { const TreeNodeRef& subNode = curTrunk.subNodes[i]; if(subNode.isTrunkNode()) { if(recursiveColTreeForOneObject(subNode.asTrunk(), subNode.getTrunkSize(), part, bounds, depth + 1)) { renderBoundsForDepth(curTrunk.getBoundsOfSubNode(i), depth); return true; } } else { if(subNode.asObject() == part) { return true; } } } return false; } static void renderTree(const BoundsTree& tree) { auto baseTrunk = tree.getPrototype().getBaseTrunk(); recursiveRenderColTree(baseTrunk.first, baseTrunk.second, 0); } static void renderTreeForOneObject(const BoundsTree& tree, const Part& part) { auto baseTrunk = tree.getPrototype().getBaseTrunk(); recursiveColTreeForOneObject(baseTrunk.first, baseTrunk.second, &part, part.getBounds(), 0); } void DebugLayer::onInit(Engine::Registry64& registry) { // Origin init originMesh = new ArrayMesh(nullptr, 1, 3, Renderer::POINT); // Vector init vectorMesh = new VectorMesh(nullptr, 0); // Point init pointMesh = new PointMesh(nullptr, 0); } void DebugLayer::onUpdate(Engine::Registry64& registry) { } void DebugLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void DebugLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; using namespace VisualDebug; using namespace AppDebug; Screen* screen = static_cast(this->ptr); beginScene(); enableBlending(); graphicsMeasure.mark(GraphicsProcess::VECTORS); // Initialize vector log buffer AddableBuffer& vecLog = getVectorBuffer(); AddableBuffer& pointLog = getPointBuffer(); { std::shared_lock worldReadLock(*screen->worldMutex); for(const MotorizedPhysical* physical : screen->world->physicals) { Position com = physical->getCenterOfMass(); pointLog.add(ColoredPoint(com, Debug::CENTER_OF_MASS)); } Screen* screen = static_cast(this->ptr); if(screen->selectedPart != nullptr) { const GlobalCFrame& selectedCFrame = screen->selectedPart->getCFrame(); Motion partMotion = screen->selectedPart->getMotion(); Polyhedron asPoly = screen->selectedPart->hitbox.asPolyhedron(); for(const Vec3f& corner : asPoly.iterVertices()) { vecLog.add(ColoredVector(selectedCFrame.localToGlobal(corner), partMotion.getVelocityOfPoint(selectedCFrame.localToRelative(corner)), Debug::VELOCITY)); } if(colissionSpheresMode == SphereColissionRenderMode::SELECTED) { Physical& selectedPhys = *screen->selectedPart->getPhysical(); for(Part& part : selectedPhys.rigidBody) { Color yellow = Colors::YELLOW; yellow.a = 0.5; BoundingBox localBounds = screen->selectedPart->getLocalBounds(); renderBox(screen->selectedPart->getCFrame().localToGlobal(CFrame(localBounds.getCenter())), localBounds.getWidth(), localBounds.getHeight(), localBounds.getDepth(), yellow); Color green = Colors::GREEN; green.a = 0.5; renderSphere(part.maxRadius, part.getPosition(), green); } } } if(colissionSpheresMode == SphereColissionRenderMode::ALL) { for(MotorizedPhysical* phys : screen->world->physicals) { for(Part& part : phys->rigidBody) { Color yellow = Colors::YELLOW; yellow.a = 0.5; BoundingBox localBounds = part.getLocalBounds(); renderBox(part.getCFrame().localToGlobal(CFrame(localBounds.getCenter())), localBounds.getWidth(), localBounds.getHeight(), localBounds.getDepth(), yellow); Color green = Colors::GREEN; green.a = 0.5; renderSphere(part.maxRadius, part.getPosition(), green); } } } if(colTreeRenderMode == -2) { if(screen->selectedPart != nullptr) { renderTreeForOneObject(screen->selectedPart->layer->tree, *screen->selectedPart); } } else if(colTreeRenderMode >= 0) { renderTree(getLayerByID(screen->world->layers, colTreeRenderMode)->tree); } worldReadLock.unlock(); // unlock_shared } disableDepthTest(); // Update debug meshes graphicsMeasure.mark(GraphicsProcess::VECTORS); updateVectorMesh(vectorMesh, vecLog.data, vecLog.size); updatePointMesh(pointMesh, pointLog.data, pointLog.size); // Render vector mesh graphicsMeasure.mark(GraphicsProcess::VECTORS); Shaders::vectorShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); vectorMesh->render(); // Render point mesh graphicsMeasure.mark(GraphicsProcess::VECTORS); Shaders::pointShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); pointMesh->render(); // Render origin mesh graphicsMeasure.mark(GraphicsProcess::ORIGIN); Shaders::originShader->updateProjection(screen->camera.viewMatrix, screen->camera.getViewRotation(), screen->camera.projectionMatrix, screen->camera.orthoMatrix, screen->camera.cframe.position); originMesh->render(); endScene(); } void DebugLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/debugLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Graphics { class ArrayMesh; class PointMesh; class VectorMesh; }; class Screen; namespace P3D::Application { class DebugLayer : public Engine::Layer { private: Graphics::VectorMesh* vectorMesh = nullptr; Graphics::PointMesh* pointMesh = nullptr; Graphics::ArrayMesh* originMesh = nullptr; public: inline DebugLayer() : Layer() {}; inline DebugLayer(Screen* screen, char flags = NoUpdate | NoEvents) : Layer("Debug layer", screen, flags) {}; virtual void onInit(Engine::Registry64& registry) override; virtual void onUpdate(Engine::Registry64& registry) override; virtual void onEvent(Engine::Registry64& registry, Engine::Event& event) override; virtual void onRender(Engine::Registry64& registry) override; virtual void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/debugOverlay.cpp ================================================ #include "core.h" #include "debugOverlay.h" #include "view/screen.h" #include "shader/shaders.h" #include "../util/resource/resourceManager.h" #include "../graphics/renderer.h" #include "../graphics/debug/profilerUI.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/resource/fontResource.h" #include "../graphics/path/path.h" #include "../graphics/gui/gui.h" #include #include #include #include #include #include "worlds.h" namespace P3D::Application { Graphics::BarChartClassInfo iterChartClasses[] { { "GJK Collide" , Vec3f(0.2f, 0.2f, 1.0f) }, { "GJK No Collide", Vec3f(1.0f, 0.5f, 0.0f) }, { "EPA" , Vec3f(1.0f, 1.0f, 0.0f) } }; Graphics::BarChart iterationChart("Iteration Statistics", "", GJKCollidesIterationStatistics.labels, iterChartClasses, Vec2f(-1.0f + 0.1f, -0.3f), Vec2f(0.8f, 0.6f), 3, 17); Graphics::SlidingChart fpsSlidingChart("Fps Fps", Vec2f(-0.3f, 0.2f), Vec2f(0.7f, 0.4f)); void DebugOverlay::onInit(Engine::Registry64& registry) { using namespace Graphics; fpsSlidingChart.add(SlidingChartDataSetInfo("Fps 1", 100, Colors::ORANGE, 2.0)); fpsSlidingChart.add(SlidingChartDataSetInfo("Fps 2", 50, Colors::BLUE, 1.0)); } void DebugOverlay::onUpdate(Engine::Registry64& registry) { using namespace Graphics::VisualDebug; fieldIndex = 0; } void DebugOverlay::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void DebugOverlay::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace VisualDebug; using namespace Renderer; Screen* screen = static_cast(this->ptr); Graphics::Shaders::guiShader->bind(); Graphics::Shaders::guiShader->setUniform("projectionMatrix", screen->camera.orthoMatrix); beginScene(); enableBlending(); disableDepthTest(); disableCulling(); Path::bind(GUI::batch); ResourceManager::get("font")->getAtlas()->bind(); Shaders::fontShader->updateProjection(screen->camera.orthoMatrix); Graphics::Shaders::guiShader->setUniform("projectionMatrix", screen->camera.orthoMatrix); graphicsMeasure.mark(GraphicsProcess::PROFILER); size_t objectCount = screen->world->getPartCount(); addDebugField(screen->dimension, GUI::font, "Screen", str(screen->dimension) + ", [" + std::to_string(screen->camera.aspect) + ":1]", ""); addDebugField(screen->dimension, GUI::font, "Position", str(screen->camera.cframe.position), ""); addDebugField(screen->dimension, GUI::font, "Objects", objectCount, ""); //addDebugField(screen->dimension, GUI::font, "Intersections", getTheoreticalNumberOfIntersections(objectCount), ""); addDebugField(screen->dimension, GUI::font, "AVG Collide GJK Iterations", gjkCollideIterStats.avg(), ""); addDebugField(screen->dimension, GUI::font, "AVG No Collide GJK Iterations", gjkNoCollideIterStats.avg(), ""); addDebugField(screen->dimension, GUI::font, "TPS", physicsMeasure.getAvgTPS(), ""); addDebugField(screen->dimension, GUI::font, "FPS", Graphics::graphicsMeasure.getAvgTPS(), ""); /*addDebugField(screen->dimension, GUI::font, "World Kinetic Energy", screen->world->getTotalKineticEnergy(), ""); addDebugField(screen->dimension, GUI::font, "World Potential Energy", screen->world->getTotalPotentialEnergy(), ""); addDebugField(screen->dimension, GUI::font, "World Energy", screen->world->getTotalEnergy(), "");*/ addDebugField(screen->dimension, GUI::font, "World Age", screen->world->age, " ticks"); if (renderPiesEnabled) { float leftSide = float(screen->dimension.x) / float(screen->dimension.y); PieChart graphicsPie = toPieChart(Graphics::graphicsMeasure, "Graphics", Vec2f(-leftSide + 1.5f, -0.7f), 0.2f); PieChart physicsPie = toPieChart(physicsMeasure, "Physics", Vec2f(-leftSide + 0.3f, -0.7f), 0.2f); PieChart intersectionPie = toPieChart(intersectionStatistics, "Intersections", Vec2f(-leftSide + 2.7f, -0.7f), 0.2f); physicsPie.renderText(GUI::font); graphicsPie.renderText(GUI::font); intersectionPie.renderText(GUI::font); physicsPie.renderPie(); graphicsPie.renderPie(); intersectionPie.renderPie(); ParallelArray gjkColIter = GJKCollidesIterationStatistics.history.avg(); ParallelArray gjkNoColIter = GJKNoCollidesIterationStatistics.history.avg(); ParallelArray epaIter = EPAIterationStatistics.history.avg(); for (size_t i = 0; i < GJKCollidesIterationStatistics.size(); i++) { iterationChart.data(0, i) = WeightValue { (float) gjkColIter[i], std::to_string(gjkColIter[i]) }; iterationChart.data(1, i) = WeightValue { (float) gjkNoColIter[i], std::to_string(gjkNoColIter[i]) }; iterationChart.data(2, i) = WeightValue { (float) epaIter[i], std::to_string(epaIter[i]) }; } iterationChart.position = Vec2f(-leftSide + 0.1f, -0.3f); iterationChart.render(); { graphicsMeasure.mark(GraphicsProcess::WAIT_FOR_LOCK); std::shared_lock worldReadLock(*screen->worldMutex); Screen* screen = static_cast(this->ptr); graphicsMeasure.mark(GraphicsProcess::PROFILER); size_t layerCount = getMaxLayerID(screen->world->layers); Vec2i d = screen->dimension; float availableSpace = float(d.x) / float(d.y); float widthPerTree = availableSpace / layerCount; int i = 0; for(const ColissionLayer& clayer : screen->world->layers) { for(const WorldLayer& layer : clayer.subLayers) { float xPos = widthPerTree / 2 + widthPerTree * i; renderTreeStructure(layer.tree, pieColors[i], Vec2f(xPos, 0.95f), widthPerTree * 0.7f); i++; } } } /*fpsSlidingChart.add("Fps 1", Graphics::graphicsMeasure.getAvgTPS()); fpsSlidingChart.add("Fps 2", physicsMeasure.getAvgTPS()); fpsSlidingChart.render();*/ Path::batch->pushCommand(0); // if this is not here then the trees won't render properly if fpsSlidingChart.render is commented for some reason } GUI::batch->submit(); endScene(); } void DebugOverlay::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/debugOverlay.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class DebugOverlay : public Engine::Layer { public: inline DebugOverlay() : Layer() {}; inline DebugOverlay(Screen* screen, char flags = NoEvents) : Layer("Debug overlay", screen, flags) {}; virtual void onInit(Engine::Registry64& registry) override; virtual void onUpdate(Engine::Registry64& registry) override; virtual void onEvent(Engine::Registry64& registry, Engine::Event& event) override; virtual void onRender(Engine::Registry64& registry) override; virtual void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/guiLayer.cpp ================================================ #include "core.h" #include "guiLayer.h" #include "view/screen.h" #include "view/frames.h" #include "input/standardInputHandler.h" #include "../engine/event/mouseEvent.h" #include "../graphics/renderer.h" #include "../graphics/shader/shaders.h" #include "../graphics/gui/gui.h" #include "../graphics/buffers/frameBuffer.h" namespace P3D::Application { bool onMouseMove(const Engine::MouseMoveEvent& event) { using namespace Graphics; return false; } bool onMousePress(const Engine::MousePressEvent& event) { using namespace Graphics; return false; } bool onMouseRelease(const Engine::MouseReleaseEvent& event) { using namespace Graphics; return false; } bool onMouseDrag(const Engine::MouseDragEvent& event) { using namespace Graphics; return false; } bool onWindowResize(const Engine::WindowResizeEvent& event) { using namespace Graphics; float aspect = ((float) event.getWidth()) / ((float) event.getHeight()); Vec2i dimension = Vec2i(event.getWidth(), event.getHeight()); return GUI::onWindowResize({ dimension, aspect }); } void GuiLayer::onInit(Engine::Registry64& registry) { using namespace Graphics; Screen* screen = static_cast(this->ptr); // GUI init GUI::onInit({ screen->dimension, screen->camera.aspect }); Shaders::guiShader->init(screen->camera.orthoMatrix); } void GuiLayer::onUpdate(Engine::Registry64& registry) { using namespace Graphics; Screen* screen = static_cast(this->ptr); // Update GUI GUI::onUpdate(screen->camera.orthoMatrix); } void GuiLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(onMouseMove); dispatcher.dispatch(onMousePress); dispatcher.dispatch(onMouseRelease); dispatcher.dispatch(onMouseDrag); dispatcher.dispatch(onWindowResize); } void GuiLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); bindFramebuffer(screen->screenFrameBuffer->getID()); beginScene(); Frames::onRender(registry); endScene(); } void GuiLayer::onClose(Engine::Registry64& registry) { using namespace Graphics; GUI::onClose(); } }; ================================================ FILE: application/layer/guiLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class GuiLayer : public Engine::Layer { public: GuiLayer() : Layer() {} GuiLayer(Screen* screen, char flags = None) : Layer("Gui", screen, flags) {} void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/imguiLayer.cpp ================================================ #include "core.h" #include "imguiLayer.h" #include #include #include #include "../view/screen.h" #include "../graphics/glfwUtils.h" #include "../graphics/texture.h" #include "../graphics/buffers/frameBuffer.h" #include "../graphics/renderer.h" #include "../util/resource/resourceManager.h" #include "../engine/event/windowEvent.h" #include "../application/input/standardInputHandler.h" #include "../graphics/gui/gui.h" #include #include "../graphics/gui/imgui/imguiStyle.h" namespace P3D::Application { void ImGuiLayer::onInit(Engine::Registry64& registry) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void) io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking Graphics::setupImGuiStyle(); GLFWwindow* window = Graphics::GLFW::getCurrentContext(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 330"); io.IniFilename = "../res/imgui.ini"; } void ImGuiLayer::onUpdate(Engine::Registry64& registry) { } void ImGuiLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { if (!sceneHovered) { event.handled = event.inCategory(Engine::EventCategoryMouse) && ImGui::GetIO().WantCaptureMouse; } } void ImGuiLayer::onRender(Engine::Registry64& registry) { Screen* screen = static_cast(this->ptr); ImGui::Begin("Scene", (bool*)0, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar /*| ImGuiWindowFlags_NoInputs*/); sceneHovered = ImGui::IsWindowHovered(); ImVec2 pos = ImGui::GetWindowPos(); ImVec2 min = ImGui::GetWindowContentRegionMin(); ImVec2 max = ImGui::GetWindowContentRegionMax(); ImVec2 size = ImVec2(max.x - min.x, max.y - min.y); min = ImVec2(min.x + pos.x, min.y + pos.y); max = ImVec2(max.x + pos.x, max.y + pos.y); handler->viewport = Vec4(min.x, min.y, size.x, size.y); SRef texture = screen->screenFrameBuffer->texture; ImGui::Image((void*) (std::intptr_t) texture->getID(), size, ImVec2(0, 1), ImVec2(1, 0)); ImGui::End(); Graphics::renderImGuiStyleEditor(); if (Graphics::GUI::windowInfo.dimension.x != size.x || Graphics::GUI::windowInfo.dimension.y != size.y) { Engine::FrameBufferResizeEvent event(static_cast(size.x), static_cast(size.y)); handler->onFrameBufferResize(event); } } void ImGuiLayer::onClose(Engine::Registry64& registry) { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); } void ImGuiLayer::begin() { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); static bool opt_fullscreen_persistant = true; bool opt_fullscreen = opt_fullscreen_persistant; ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; if (opt_fullscreen) { ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->GetWorkPos()); ImGui::SetNextWindowSize(viewport->GetWorkSize()); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; } if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) window_flags |= ImGuiWindowFlags_NoBackground; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::Begin("Physics3D", (bool*) true, window_flags); ImGui::PopStyleVar(); if (opt_fullscreen) ImGui::PopStyleVar(2); ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { ImGuiID dockspace_id = ImGui::GetID("DockSpace"); ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); } if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Save world", "CTRL-S")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_S, Engine::Modifier::CTRL); handler->onEvent(event); } if (ImGui::MenuItem("Load world", "CTRL-O")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_O, Engine::Modifier::CTRL); handler->onEvent(event); } if (ImGui::MenuItem("Exit", "esc")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_ESCAPE); handler->onEvent(event); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Edit")) { if (ImGui::MenuItem("Play / pause", "P")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_P); handler->onEvent(event); } if (ImGui::MenuItem("Tick", "T")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_T); handler->onEvent(event); } if (ImGui::MenuItem("New part", "O")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_O); handler->onEvent(event); } if (ImGui::MenuItem("Delete part", "DELETE")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_DELETE); handler->onEvent(event); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Debug")) { if (ImGui::MenuItem("Debug info", "F")) { Engine::KeyPressEvent event(Engine::Keyboard::KEY_F); handler->onEvent(event); } ImGui::EndMenu(); } ImGui::EndMenuBar(); } } void ImGuiLayer::end() { // End the dockspace ImGui::End(); // End imgui ImGuiIO& io = ImGui::GetIO(); Screen* screen = static_cast(this->ptr); io.DisplaySize = ImVec2((float) screen->dimension.x, (float) screen->dimension.y); ImGui::Render(); Graphics::Renderer::bindFramebuffer(0); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { GLFWwindow* backupContext = Graphics::GLFW::getCurrentContext(); ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); Graphics::GLFW::setCurrentContext(backupContext); } } }; ================================================ FILE: application/layer/imguiLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class ImGuiLayer : public Engine::Layer { public: bool sceneHovered = false; inline ImGuiLayer() : Layer() {}; inline ImGuiLayer(Screen* screen, char flags = None) : Layer("ImGui", screen, flags) {}; virtual void onInit(Engine::Registry64& registry) override; virtual void onUpdate(Engine::Registry64& registry) override; virtual void onEvent(Engine::Registry64& registry, Engine::Event& event) override; virtual void onRender(Engine::Registry64& registry) override; virtual void onClose(Engine::Registry64& registry) override; void begin(); void end(); }; }; ================================================ FILE: application/layer/modelLayer.cpp ================================================ #include "core.h" #include "modelLayer.h" #include #include "view/screen.h" #include "shader/shaders.h" #include "extendedPart.h" #include "worlds.h" #include "../engine/ecs/registry.h" #include "ecs/components.h" #include "../graphics/meshRegistry.h" #include "../graphics/ecs/components.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/gui/color.h" #include #include #include "skyboxLayer.h" #include "../util/resource/resourceManager.h" #include "../layer/shadowLayer.h" #include "../graphics/batch/instanceBatchManager.h" #include "picker/tools/selectionTool.h" namespace P3D::Application { enum class RelationToSelectedPart { NONE, SELF, DIRECT_ATTACH, MAINPART, PHYSICAL_ATTACH, MAINPHYSICAL_ATTACH }; static RelationToSelectedPart getRelationToSelectedPart(const Part* selectedPart, const Part* testPart) { if (selectedPart == nullptr) return RelationToSelectedPart::NONE; if (testPart == selectedPart) return RelationToSelectedPart::SELF; Physical* selectedPartPhysical = selectedPart->getPhysical(); Physical* testPartPhysical = testPart->getPhysical(); if (selectedPartPhysical != nullptr && testPartPhysical != nullptr) { if (testPartPhysical == testPartPhysical) { if(testPart->isMainPart()) { return RelationToSelectedPart::MAINPART; } else { return RelationToSelectedPart::DIRECT_ATTACH; } } else if (testPartPhysical->mainPhysical == selectedPartPhysical->mainPhysical) { if(testPartPhysical->isMainPhysical()) { return RelationToSelectedPart::MAINPHYSICAL_ATTACH; } else { return RelationToSelectedPart::PHYSICAL_ATTACH; } } } return RelationToSelectedPart::NONE; } static Color getAmbientForPartForSelected(Screen* screen, Part* part) { switch (getRelationToSelectedPart(screen->selectedPart, part)) { case RelationToSelectedPart::NONE: return Color(0.0f, 0, 0, 0); case RelationToSelectedPart::SELF: return Color(0.5f, 0, 0, 0); case RelationToSelectedPart::DIRECT_ATTACH: return Color(0, 0.25f, 0, 0); case RelationToSelectedPart::MAINPART: return Color(0, 1.0f, 0, 0); case RelationToSelectedPart::PHYSICAL_ATTACH: return Color(0, 0, 0.25f, 0); case RelationToSelectedPart::MAINPHYSICAL_ATTACH: return Color(0, 0, 1.0f, 0); } return Color(0, 0, 0, 0); } static Color getAlbedoForPart(Screen* screen, ExtendedPart* part) { Color computedAmbient = getAmbientForPartForSelected(screen, part); // if (part->entity is intersected) // computedAmbient = Vec4f(computedAmbient) + Vec4f(-0.1f, -0.1f, -0.1f, 0); return computedAmbient; } void ModelLayer::onInit(Engine::Registry64& registry) { using namespace Graphics; Screen* screen = static_cast(this->ptr); // Dont know anymore Shaders::basicShader->updateTexture(false); // Instance batch manager manager = new InstanceBatchManager(Shaders::instanceShader, DEFAULT_UNIFORM_BUFFER_LAYOUT); } void ModelLayer::onUpdate(Engine::Registry64& registry) { auto view = registry.view(); std::size_t index = 0; for (auto entity : view) { IRef light = view.get(entity); Position position; IRef transform = registry.get(entity); if (transform.valid()) position = transform->getCFrame().getPosition(); Shaders::basicShader->updateLight(index, position, *light); Shaders::instanceShader->updateLight(index, position, *light); index += 1; } Shaders::basicShader->updateLightCount(index); Shaders::instanceShader->updateLightCount(index); } void ModelLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void ModelLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); beginScene(); // Matrices graphicsMeasure.mark(UPDATE); Shaders::debugShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); Shaders::basicShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); Shaders::instanceShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); // Shadow Vec3f from = { -10, 10, -10 }; Vec3f to = { 0, 0, 0 }; Vec3f sunDirection = to - from; ShadowLayer::lightView = lookAt(from, to); ShadowLayer::lighSpaceMatrix = ShadowLayer::lightProjection * ShadowLayer::lightView; Renderer::activeTexture(32); Renderer::bindTexture2D(ShadowLayer::depthMap); Shaders::instanceShader->setUniform("shadowMap", 32); Shaders::instanceShader->setUniform("lightMatrix", ShadowLayer::lighSpaceMatrix); Shaders::instanceShader->updateSunDirection(sunDirection); // Skybox Shaders::instanceShader->setUniform("skyboxTexture", 31); SkyboxLayer::skyboxTexture->bind(31); graphicsMeasure.mark(PHYSICALS); // Filter on mesh ID and transparency struct EntityInfo { Engine::Registry64::entity_type entity = 0; Comp::Transform transform; Graphics::Comp::Material material; IRef mesh; IRef collider; }; std::map transparentEntities; { std::shared_lock worldReadLock(*screen->worldMutex); VisibilityFilter filter = VisibilityFilter::forWindow(screen->camera.cframe.position, screen->camera.getForwardDirection(), screen->camera.getUpDirection(), screen->camera.fov, screen->camera.aspect, screen->camera.zfar); auto view = registry.view(); for (auto entity : view) { EntityInfo info; info.entity = entity; info.mesh = view.get(entity); if (!info.mesh.valid()) continue; if (info.mesh->id == -1) continue; if (!info.mesh->visible) continue; info.collider = registry.get(entity); if (info.collider.valid()) if (!filter(*info.collider->part)) continue; info.transform = registry.getOr(entity); info.material = registry.getOr(entity); if (info.material.albedo.a < 1.0f) { double distance = lengthSquared(Vec3(screen->camera.cframe.position - info.transform.getPosition())); transparentEntities.insert(std::make_pair(distance, info)); } else { Mat4f modelMatrix = info.transform.getModelMatrix(); if (info.collider.valid()) info.material.albedo += getAlbedoForPart(screen, info.collider->part); manager->add(info.mesh->id, modelMatrix, info.material); } } Shaders::instanceShader->bind(); manager->submit(); // Render transparent meshes Shaders::basicShader->bind(); enableBlending(); for (auto iterator = transparentEntities.rbegin(); iterator != transparentEntities.rend(); ++iterator) { EntityInfo info = iterator->second; if (info.mesh->id == -1) continue; if (!info.mesh->visible) continue; if (info.collider.valid()) info.material.albedo += getAlbedoForPart(screen, info.collider->part); Shaders::basicShader->updateMaterial(info.material); Shaders::basicShader->updateModel(info.transform.getModelMatrix()); MeshRegistry::meshes[info.mesh->id]->render(); } auto scf = SelectionTool::selection.getCFrame(); auto shb = SelectionTool::selection.getHitbox(); if (scf.has_value() && shb.has_value()) { Graphics::Comp::Mesh data = MeshRegistry::getMesh(shb->baseShape.get()); Shaders::debugShader->updateModel(scf.value().asMat4WithPreScale(shb->scale)); MeshRegistry::meshes[data.id]->render(); } // Hitbox drawing for (auto entity : SelectionTool::selection) { IRef transform = registry.get(entity); if (transform.valid()) { IRef hitbox = registry.get(entity); if (hitbox.valid()) { Shape shape = hitbox->getShape(); if (!hitbox->isPartAttached()) shape = shape.scaled(transform->getScale()); Graphics::Comp::Mesh data = MeshRegistry::getMesh(shape.baseShape.get()); Shaders::debugShader->updateModel(transform->getCFrame().asMat4WithPreScale(shape.scale)); MeshRegistry::meshes[data.id]->render(); } } } } endScene(); } void ModelLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/modelLayer.h ================================================ #pragma once #include "../graphics/batch/instanceBatchManager.h" #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class ModelLayer final : public Engine::Layer { public: Graphics::InstanceBatchManager* manager = nullptr; public: ModelLayer() : Layer() {} ModelLayer(Screen* screen, char flags = None) : Layer("Model", screen, flags) {} virtual ~ModelLayer() = default; void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/pickerLayer.cpp ================================================ #include "core.h" #include "pickerLayer.h" #include "../application.h" #include "../graphics/debug/visualDebug.h" #include "view/screen.h" #include "input/standardInputHandler.h" #include "../engine/tool/toolManager.h" #include "../graphics/renderer.h" #include "../picker/tools/selectionTool.h" #include "../picker/tools/regionSelectionTool.h" #include "../picker/tools/translationTool.h" #include "../picker/tools/rotationTool.h" #include "../picker/tools/scaleTool.h" #include "../picker/ray.h" #include "picker/tools/alignmentLinkTool.h" #include "picker/tools/attachmentTool.h" #include "picker/tools/elasticLinkTool.h" #include "picker/tools/fixedConstraintTool.h" #include "picker/tools/magneticLinkTool.h" #include "picker/tools/motorConstraintTool.h" #include "picker/tools/pathTool.h" #include "picker/tools/pistonConstraintTool.h" #include "picker/tools/springLinkTool.h" #include "picker/tools/toolSpacing.h" namespace P3D::Application { std::vector PickerLayer::toolManagers; void PickerLayer::onInit(Engine::Registry64& registry) { Engine::ToolManager toolManager; toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.registerTool(); toolManager.selectTool(); toolManagers.push_back(std::move(toolManager)); } void PickerLayer::onUpdate(Engine::Registry64& registry) { Screen* screen = static_cast(this->ptr); Graphics::graphicsMeasure.mark(Graphics::GraphicsProcess::PICKER); // Update selection context SelectionTool::mouse = handler->mousePosition; SelectionTool::ray = getMouseRay(*screen, handler->mousePosition); if (!isPaused()) SelectionTool::selection.recalculateSelection(); for (Engine::ToolManager& toolManager : toolManagers) toolManager.onUpdate(); } void PickerLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { for (Engine::ToolManager& toolManager : toolManagers) toolManager.onEvent(event); } void PickerLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); beginScene(); clearDepth(); enableDepthTest(); for (Engine::ToolManager& toolManager : toolManagers) toolManager.onRender(); disableDepthTest(); endScene(); } void PickerLayer::onClose(Engine::Registry64& registry) { for (Engine::ToolManager& toolManager : toolManagers) toolManager.onClose(); } }; /* void moveGrabbedEntityLateral(Screen& screen) { if (screen.world->selectedPart == nullptr) return; if (!screen.selectedEntity) return; Vec3 cameraDirection = screen.camera.cframe.rotation * Vec3(0, 0, 1); double distance = Vec3(screen.selectedPoint - screen.camera.cframe.position) * cameraDirection / (SelectionTool::ray.direction * cameraDirection); Position planeIntersection = screen.camera.cframe.position + SelectionTool::ray.direction * distance; if (isPaused()) { Vec3 translation = planeIntersection - screen.selectedPoint; screen.selectedPoint += translation; Ref transform = screen.registry.get(screen.selectedEntity); if (transform.valid()) transform->translate(translation); } else { screen.world->selectedPart = screen.selectedPart; screen.world->magnetPoint = planeIntersection; } } void moveGrabbedEntityTransversal(Screen& screen, double dz) { if (screen.world->selectedPart == nullptr) return; if (!screen.selectedEntity) return; Vec3 cameraDirection = screen.camera.cframe.rotation * Vec3(0, 0, 1); Vec3 cameraYDirection = normalize(Vec3(cameraDirection.x, 0, cameraDirection.z)); double distance = Vec3(screen.selectedPoint - screen.camera.cframe.position) * cameraDirection / (SelectionTool::ray.direction * cameraDirection); Position planeIntersection = screen.camera.cframe.position + SelectionTool::ray.direction * distance; Vec3 translation = -cameraYDirection * dz; if (isPaused()) { screen.selectedPoint += translation; Ref transform = screen.registry.get(screen.selectedEntity); if (transform.valid()) transform->translate(translation); } else { screen.world->selectedPart = screen.selectedPart; screen.world->magnetPoint += translation; } } */ ================================================ FILE: application/layer/pickerLayer.h ================================================ #pragma once #include "../engine/tool/toolManager.h" #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class PickerLayer : public Engine::Layer { public: static std::vector toolManagers; PickerLayer() : Layer() {} PickerLayer(Screen* screen, char flags = None) : Layer("Picker", screen, flags) {} void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/postprocessLayer.cpp ================================================ #include "core.h" #include "postprocessLayer.h" #include "../graphics/buffers/frameBuffer.h" #include "../graphics/mesh/primitive.h" #include "../graphics/renderer.h" #include "../graphics/texture.h" #include "../application/shader/shaders.h" #include "../view/screen.h" namespace P3D::Application { void PostprocessLayer::onInit(Engine::Registry64& registry) { } void PostprocessLayer::onUpdate(Engine::Registry64& registry) { } void PostprocessLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void PostprocessLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Graphics::Renderer; Screen* screen = static_cast(this->ptr); } void PostprocessLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/postprocessLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class PostprocessLayer : public Engine::Layer { private: Engine::Registry64::entity_type entity; public: PostprocessLayer() : Layer() {} PostprocessLayer(Screen* screen, char flags = 0) : Layer("Postprocess", screen, flags) {}; void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/shadowLayer.cpp ================================================ #include "core.h" #include "shadowLayer.h" #include #include #include #include #include "view/screen.h" #include "../graphics/gui/gui.h" #include "../graphics/renderer.h" #include "../graphics/shader/shader.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/buffers/frameBuffer.h" #include "../util/resource/resourceManager.h" #include "../graphics/meshRegistry.h" #include "ecs/components.h" #include "worlds.h" #include "application.h" #include "shader/shaders.h" namespace P3D::Application { float near = 0.001f; float far = 100.0f; Mat4f ShadowLayer::lightProjection = ortho(-25.0, 25.0, -25.0, 25.0, near, far); Mat4f ShadowLayer::lightView = lookAt({ -15.0, 15.0, -15.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }); Mat4f ShadowLayer::lighSpaceMatrix = lightProjection * lightView; unsigned int ShadowLayer::depthMap = 0; GLID depthMapFBO; GLID WIDTH = 2048; GLID HEIGHT = 2048; IndexedMesh* mesh = nullptr; void ShadowLayer::onInit(Engine::Registry64& registry) { glGenFramebuffers(1, &depthMapFBO); glGenTextures(1, &depthMap); glBindTexture(GL_TEXTURE_2D, depthMap); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WIDTH, HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, 0/*screen->screenFrameBuffer->getID()*/); } void ShadowLayer::onUpdate(Engine::Registry64& registry) { } void ShadowLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { using namespace Engine; } void ShadowLayer::renderScene(Engine::Registry64& registry) { std::vector visibleParts; std::shared_lock worldReadLock(*screen.worldMutex); screen.world->forEachPart([&visibleParts](ExtendedPart& part) { visibleParts.push_back(&part); }); for (ExtendedPart* part : visibleParts) { IRef mesh = registry.get(part->entity); if (!mesh.valid()) continue; if (mesh->id == -1) continue; if (!mesh->visible) continue; Shaders::depthShader->updateModel(part->getCFrame().asMat4WithPreScale(part->hitbox.scale)); Graphics::MeshRegistry::meshes[mesh->id]->render(); } } void ShadowLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); Shaders::depthShader->bind(); Shaders::depthShader->updateLight(lighSpaceMatrix); glViewport(0, 0, WIDTH, HEIGHT); glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); Renderer::disableCulling(); //glCullFace(GL_FRONT_AND_BACK); renderScene(registry); //glCullFace(GL_BACK); Renderer::enableCulling(); glBindFramebuffer(GL_FRAMEBUFFER, screen->screenFrameBuffer->getID()); glViewport(0, 0, GUI::windowInfo.dimension.x, GUI::windowInfo.dimension.y); /*Shaders::depthBufferShader.bind(); Shaders::depthBufferShader.updatePlanes(near, far); Shaders::depthBufferShader.updateDepthMap(0, depthMap); Graphics::renderQuad();*/ } void ShadowLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/shadowLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class ShadowLayer : public Engine::Layer { public: static unsigned int depthMap; static Mat4f lightProjection; static Mat4f lighSpaceMatrix; static Mat4f lightView; inline ShadowLayer() : Layer() {}; inline ShadowLayer(Screen* screen, char flags = None) : Layer("ShadowLayer", screen, flags) {}; void renderScene(Engine::Registry64& registry); virtual void onInit(Engine::Registry64& registry) override; virtual void onUpdate(Engine::Registry64& registry) override; virtual void onEvent(Engine::Registry64& registry, Engine::Event& event) override; virtual void onRender(Engine::Registry64& registry) override; virtual void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/skyboxLayer.cpp ================================================ #include "core.h" #include "skyboxLayer.h" #include #include #include "shader/shaders.h" #include "view/screen.h" #include "../graphics/renderer.h" #include "../graphics/texture.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/resource/textureResource.h" #include "../graphics/gui/guiUtils.h" #include "../util/resource/resourceManager.h" #include "../graphics/mesh/primitive.h" #include "graphics/meshRegistry.h" namespace P3D::Application { SkyboxCycle lightColorCycle; SkyboxCycle skyColorCycle; SkyboxCycle horizonColorCycle; SkyboxCycle mistColorCycle; Color lightColor; Color skyColor; Color mistColor; Color horizonColor; Vec2f mist; float starBrightness; Graphics::CubeMap* SkyboxLayer::skyboxTexture = nullptr; float getScroll(const Screen* screen) { return static_cast(std::atan2(screen->camera.viewMatrix(1, 0), screen->camera.viewMatrix(0, 0)) / screen->camera.fov / 2.0); } bool useNewSky; bool pauze; float time; void updateMist(float time) { float mistFactor; if (time > 0.5F) mistFactor = 1.0f - GUI::smoothstep(0.7083333f, 0.875f, time); else mistFactor = GUI::smoothstep(0.20833333f, 0.375f, time); mist = Vec2f(15.0f + mistFactor * 20.0f, 75.0f + mistFactor * 50.0f); } void updateStars(float time) { if (time > 0.5F) starBrightness = GUI::smoothstep(0.9166667f, 1.0f, time); else starBrightness = 1.0f - GUI::smoothstep(0.125f, 0.20833333f, time); } void SkyboxLayer::onInit(Engine::Registry64& registry) { skyboxTexture = new CubeMap("../res/skybox/right.jpg", "../res/skybox/left.jpg", "../res/skybox/top.jpg", "../res/skybox/bottom.jpg", "../res/skybox/front.jpg", "../res/skybox/back.jpg"); ResourceManager::add("night", "../res/textures/night.png"); ResourceManager::add("uv", "../res/textures/uv.png"); lightColorCycle = SkyboxCycle(Color(0.42f, 0.45f, 0.90f), Color(1.0f, 0.95f, 0.95f), Color(1.0f, 0.45f, 0.56f), Color(1.0f, 0.87f, 0.6f), 3.0f, 8.0f, 18.0f); skyColorCycle = SkyboxCycle(Color(0.31f, 0.44f, 0.64f), Color(0.96f, 0.93f, 0.9f), Color(0.996f, 0.77f, 0.57f), Color(1.0f, 0.94f, 0.67f), 3.0f, 8.0f, 18.0f); horizonColorCycle = SkyboxCycle(Color(0.2f, 0.2f, 0.42f), Color(0.6f, 0.9f, 1.0f), Color(0.93f, 0.49f, 0.57f), Color(1.0f, 0.64f, 0.47f), 3.0f, 8.0f, 18.0f); mistColorCycle = SkyboxCycle(Color(0.29f, 0.41f, 0.61f), Color(0.96f, 0.9f, 0.77f), Color(1.0f, 0.68f, 0.85f), Color(1.0f, 0.87f, 0.82f), 3.0f, 8.0f, 18.0f); } void SkyboxLayer::onUpdate(Engine::Registry64& registry) { if (!pauze) time = fmod((float) (glfwGetTime() / 30.0), 1.0f); lightColor = lightColorCycle.sample(time); skyColor = skyColorCycle.sample(time); mistColor = mistColorCycle.sample(time); horizonColor = horizonColorCycle.sample(time); updateMist(time); updateStars(time); } void SkyboxLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { } void SkyboxLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Graphics::Renderer; graphicsMeasure.mark(GraphicsProcess::SKYBOX); Screen* screen = static_cast(this->ptr); beginScene(); if (useNewSky) { disableCulling(); disableDepthMask(); enableBlending(); ResourceManager::get("night")->bind(); Shaders::skyShader->setUniform("nightTexture", 0); Shaders::skyShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); Shaders::skyShader->setUniform("starBrightness", starBrightness); Shaders::skyShader->setUniform("skyColor", Vec3f(skyColor)); Shaders::skyShader->setUniform("horizonColor", Vec3f(horizonColor)); float scroll = getScroll(screen); Shaders::skyShader->setUniform("scroll", scroll); Shaders::skyShader->setUniform("time", time); Shaders::skyShader->setUniform("skyboxSize", 550.0f); Shaders::skyShader->setUniform("segmentCount", 25.0f); Graphics::MeshRegistry::get(Graphics::MeshRegistry::sphere)->render(); } else { disableCulling(); disableDepthMask(); enableBlending(); Shaders::skyboxShader->updateProjection(screen->camera.viewMatrix, screen->camera.projectionMatrix, screen->camera.cframe.position); skyboxTexture->bind(); Graphics::MeshRegistry::get(Graphics::MeshRegistry::sphere)->render(); } endScene(); } void SkyboxLayer::onClose(Engine::Registry64& registry) { skyboxTexture->close(); } }; ================================================ FILE: application/layer/skyboxLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" #include "../util/valueCycle.h" #include "../graphics/gui/color.h" namespace P3D { namespace Graphics { class CubeMap; } } namespace P3D::Application { struct SkyboxCycle : public Util::ValueCycle { public: SkyboxCycle() {} SkyboxCycle(const Graphics::Color& night, const Graphics::Color& day, const Graphics::Color& dusk, const Graphics::Color& dawn, float midnightEnd, float middayStart, float middayEnd) { float _midnightEnd = midnightEnd / 24.0f; float _middayStart = middayStart / 24.0f; float _middayEnd = middayEnd / 24.0f; addKeyframe(0.0f, night); addKeyframe(_midnightEnd, night); addKeyframe(_midnightEnd + (_middayStart - _midnightEnd) * 0.5f, dawn); addKeyframe(_middayStart, day); addKeyframe(_middayEnd, day); addKeyframe(_middayEnd + (1.0f - _middayEnd) * 0.5f, dusk); addKeyframe(1.0f, night); } }; class Screen; float getScroll(const Screen* screen); extern bool useNewSky; extern bool pauze; extern float time; class SkyboxLayer : public Engine::Layer { public: static Graphics::CubeMap* skyboxTexture; SkyboxLayer() : Layer() {} SkyboxLayer(Screen* screen, char flags = NoEvents) : Layer("Skybox", screen, flags) {} void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/layer/testLayer.cpp ================================================ #include "core.h" #include "GL/glew.h" #include "testLayer.h" #include "view/screen.h" #include "../graphics/renderer.h" #include "imgui/imgui.h" namespace P3D::Application { void TestLayer::onInit(Engine::Registry64& registry) { } void TestLayer::onUpdate(Engine::Registry64& registry) { } void TestLayer::onEvent(Engine::Registry64& registry, Engine::Event& event) { using namespace Engine; } void TestLayer::onRender(Engine::Registry64& registry) { using namespace Graphics; using namespace Renderer; Screen* screen = static_cast(this->ptr); } void TestLayer::onClose(Engine::Registry64& registry) { } }; ================================================ FILE: application/layer/testLayer.h ================================================ #pragma once #include "../engine/layer/layer.h" namespace P3D::Application { class Screen; class TestLayer : public Engine::Layer { public: TestLayer() : Layer() {}; TestLayer(Screen* screen, char flags = None) : Layer("TestLayer", screen, flags) {}; void onInit(Engine::Registry64& registry) override; void onUpdate(Engine::Registry64& registry) override; void onEvent(Engine::Registry64& registry, Engine::Event& event) override; void onRender(Engine::Registry64& registry) override; void onClose(Engine::Registry64& registry) override; }; }; ================================================ FILE: application/legacy/frames.h ================================================ #pragma once #include "../graphics/gui/frame.h" #include "../graphics/gui/label.h" #include "../graphics/gui/slider.h" #include "../graphics/gui/checkBox.h" #include "../graphics/gui/image.h" #include "../graphics/gui/button.h" #include "../graphics/gui/directionEditor.h" #include "../graphics/gui/colorPicker.h" #include "../physics/misc/toString.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/shader/shaderProgram.h" #include "../graphics/renderUtils.h" #include "../graphics/texture.h" #include "../graphics/resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "../util/resource/resource.h" #include "shader/shaders.h" #include "extendedPart.h" #include "application.h" #include "worlds.h" #include "screen.h" namespace Application { // Frame blueprint struct FrameBlueprint { virtual void init() = 0; virtual void update() = 0; }; struct ResourceManagerFrame : public FrameBlueprint, public Frame { std::vector images; ResourceManagerFrame(double x, double y, std::string title) : Frame(x, y, 1, 1, title) { init(); GUI::add(this); } void init() override { update(); } void update() override { auto textures = ResourceManager::getResourcesOfType(ResourceType::Texture); if (textures.size() != images.size()) { for (Image* i : images) remove(i); images.clear(); for (Resource* r : textures) { TextureResource* tr = static_cast(r); Image* i = (new Image(0, 0, tr))->fixWidth(0.1); images.push_back(i); add(i); } } } }; struct ImageFrame : public FrameBlueprint, public Frame { Image* image = nullptr; ImageFrame(double x, double y, std::string title) : Frame(x, y, title) { init(); add(image); GUI::add(this); } void init() override { image = (new Image(0, 0, nullptr))->fixWidth(1); } void update() override { } }; // Environment frame struct EnvironmentFrame : public FrameBlueprint, public Frame { Label* gammaLabel = nullptr; Slider* gammaSlider = nullptr; Label* gammaValueLabel = nullptr; Label* exposureLabel = nullptr; Slider* exposureSlider = nullptr; Label* exposureValueLabel = nullptr; CheckBox* hdrCheckBox = nullptr; Label* sunLabel = nullptr; Button* sunColorButton = nullptr; DirectionEditor* sunDirectionEditor = nullptr; EnvironmentFrame(double x, double y) : Frame(x, y, "Environment") { // frame = new Frame(x, y, "Environment"); init(); add(hdrCheckBox, Align::FILL); add(gammaLabel, Align::CENTER); add(gammaSlider, Align::RELATIVE); add(gammaValueLabel, Align::FILL); add(exposureLabel, Align::CENTER); add(exposureSlider, Align::RELATIVE); add(exposureValueLabel, Align::FILL); add(sunLabel, Align::CENTER); add(sunColorButton, Align::CENTER); add(sunDirectionEditor, Align::CENTER); GUI::add(this); } void init() override { hdrCheckBox = new CheckBox("HDR", 0, 0, true); hdrCheckBox->checked = true; hdrCheckBox->action = [] (CheckBox* c) { ApplicationShaders::basicShader.updateHDR(c->checked); }; gammaLabel = new Label("Gamma", 0, 0); gammaSlider = new Slider(0, 0, 0, 3, 1); gammaSlider->action = [] (Slider* s) { ApplicationShaders::basicShader.updateGamma(float(s->value)); }; gammaValueLabel = new Label("", 0, 0); exposureLabel = new Label("Exposure", 0, 0); exposureSlider = new Slider(0, 0, 0, 2, 1); exposureSlider->action = [] (Slider* s) { ApplicationShaders::basicShader.updateExposure(float(s->value)); }; exposureValueLabel = new Label("", 0, 0); sunLabel = new Label("Sun", 0, 0); sunColorButton = new Button(0, 0, GUI::sliderBarWidth, GUI::sliderHandleHeight, false); sunDirectionEditor = new DirectionEditor(0, 0, GUI::sliderBarWidth, GUI::sliderBarWidth); sunDirectionEditor->action = [] (DirectionEditor* d) { ApplicationShaders::basicShader.updateSunDirection(Vec3f(d->modelMatrix * Vec4f(0, 1, 0, 1))); }; sunColorButton->setColor(Color(1)); sunColorButton->action = [] (Button* button) { EnvironmentFrame* environmentFrame = static_cast(button->parent); GUI::colorPickerFrame->visible = true; GUI::colorPickerFrame->anchor = environmentFrame; GUI::select(GUI::colorPickerFrame); GUI::colorPicker->setRgba(button->idleColor); GUI::colorPicker->focus = button; GUI::colorPicker->action = [] (ColorPicker* p) { ApplicationShaders::basicShader.updateSunColor(Vec3(p->getRgba())); }; }; } void update() override { if (!visible) return; if (hdrCheckBox->checked) { exposureSlider->enable(); exposureValueLabel->enable(); exposureLabel->enable(); exposureValueLabel->text = std::to_string(exposureSlider->value); } else { exposureSlider->disable(); exposureValueLabel->disable(); exposureLabel->disable(); } if (GUI::colorPicker->focus == sunColorButton) { sunColorButton->setColor(GUI::colorPicker->getRgba()); } gammaValueLabel->text = std::to_string(gammaSlider->value); } }; // Debug frame struct DebugFrame : public FrameBlueprint, public Frame { Label* vectorLabel = nullptr; CheckBox* infoVectorCheckBox = nullptr; CheckBox* positionCheckBox = nullptr; CheckBox* velocityCheckBox = nullptr; CheckBox* momentCheckBox = nullptr; CheckBox* forceCheckBox = nullptr; CheckBox* accelerationCheckBox = nullptr; CheckBox* angularImpulseCheckBox = nullptr; CheckBox* impulseCheckBox = nullptr; CheckBox* angularVelocityCheckBox = nullptr; Label* pointLabel = nullptr; CheckBox* infoPointCheckBox = nullptr; CheckBox* centerOfMassCheckBox = nullptr; CheckBox* intersectionCheckBox = nullptr; Label* renderLabel = nullptr; CheckBox* renderPiesCheckBox = nullptr; CheckBox* renderSpheresCheckBox = nullptr; DebugFrame(double x, double y) : Frame(x, y, "Debug") { init(); add(vectorLabel, Align::CENTER); add(infoVectorCheckBox, Align::FILL); add(positionCheckBox, Align::FILL); add(velocityCheckBox, Align::FILL); add(accelerationCheckBox, Align::FILL); add(forceCheckBox, Align::FILL); add(momentCheckBox, Align::FILL); add(impulseCheckBox, Align::FILL); add(angularVelocityCheckBox, Align::FILL); add(angularImpulseCheckBox, Align::FILL); add(pointLabel, Align::CENTER); add(infoPointCheckBox, Align::FILL); add(centerOfMassCheckBox, Align::FILL); add(intersectionCheckBox, Align::FILL); add(renderLabel, Align::CENTER); add(renderPiesCheckBox, Align::FILL); add(renderSpheresCheckBox, Align::FILL); GUI::add(this); } void init() override { visible = false; vectorLabel = new Label("Vectors", 0, 0); infoVectorCheckBox = new CheckBox("Info", 0, 0, true); positionCheckBox = new CheckBox("Position", 0, 0, true); velocityCheckBox = new CheckBox("Velocity", 0, 0, true); accelerationCheckBox = new CheckBox("Acceleration", 0, 0, true); forceCheckBox = new CheckBox("Force", 0, 0, true); momentCheckBox = new CheckBox("Moment", 0, 0, true); impulseCheckBox = new CheckBox("Impulse", 0, 0, true); angularVelocityCheckBox = new CheckBox("Angular velocity", 0, 0, true); angularImpulseCheckBox = new CheckBox("Angular impulse", 0, 0, true); pointLabel = new Label("Points", 0, 0); infoPointCheckBox = new CheckBox("Info", 0, 0, true); centerOfMassCheckBox = new CheckBox("Center of mass", 0, 0, true); intersectionCheckBox = new CheckBox("Intersections", 0, 0, true); renderLabel = new Label("Render", 0, 0); renderPiesCheckBox = new CheckBox("Statistics", 0, 0, true); renderSpheresCheckBox = new CheckBox("Collision spheres", 0, 0, true); infoVectorCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::INFO_VEC); }; velocityCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::VELOCITY); }; accelerationCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::ACCELERATION); }; forceCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::FORCE); }; angularImpulseCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::ANGULAR_IMPULSE); }; positionCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::POSITION); }; momentCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::MOMENT); }; impulseCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::IMPULSE); }; angularVelocityCheckBox->action = [] (CheckBox* c) { toggleVectorType(Debug::ANGULAR_VELOCITY); }; infoPointCheckBox->action = [] (CheckBox* c) { togglePointType(Debug::INFO_POINT); }; centerOfMassCheckBox->action = [] (CheckBox* c) { togglePointType(Debug::CENTER_OF_MASS); }; intersectionCheckBox->action = [] (CheckBox* c) { togglePointType(Debug::INTERSECTION); }; renderPiesCheckBox->action = [] (CheckBox* c) { Debug::renderPiesEnabled = !Debug::renderPiesEnabled; }; renderSpheresCheckBox->action = [] (CheckBox* c) { Debug::colissionSpheresMode = static_cast((static_cast(Debug::colissionSpheresMode) + 1) % 3); }; } void update() override { if (!visible) return; infoVectorCheckBox->checked = Debug::vectorDebugEnabled[Debug::INFO_VEC]; positionCheckBox->checked = Debug::vectorDebugEnabled[Debug::POSITION]; velocityCheckBox->checked = Debug::vectorDebugEnabled[Debug::VELOCITY]; momentCheckBox->checked = Debug::vectorDebugEnabled[Debug::MOMENT]; forceCheckBox->checked = Debug::vectorDebugEnabled[Debug::FORCE]; accelerationCheckBox->checked = Debug::vectorDebugEnabled[Debug::ACCELERATION]; angularImpulseCheckBox->checked = Debug::vectorDebugEnabled[Debug::ANGULAR_IMPULSE]; impulseCheckBox->checked = Debug::vectorDebugEnabled[Debug::IMPULSE]; angularVelocityCheckBox->checked = Debug::vectorDebugEnabled[Debug::ANGULAR_VELOCITY]; infoPointCheckBox->checked = Debug::pointDebugEnabled[Debug::INFO_POINT]; centerOfMassCheckBox->checked = Debug::pointDebugEnabled[Debug::CENTER_OF_MASS]; intersectionCheckBox->checked = Debug::pointDebugEnabled[Debug::INTERSECTION]; renderPiesCheckBox->checked = Debug::renderPiesEnabled; renderSpheresCheckBox->checked = Debug::colissionSpheresMode != Debug::SphereColissionRenderMode::NONE; } }; // Properties frame struct PropertiesFrame : public FrameBlueprint, public Frame { Label* generalLabel = nullptr; Label* partNameLabel = nullptr; Label* partMeshIDLabel = nullptr; Label* physicalLabel = nullptr; Label* positionLabel = nullptr; Label* velocityLabel = nullptr; Label* angularVelocityLabel = nullptr; Label* kineticEnergyLabel = nullptr; Label* potentialEnergyLabel = nullptr; Label* energyLabel = nullptr; Label* massLabel = nullptr; Label* inertiaLabel = nullptr; Label* frictionLabel = nullptr; Label* bouncynessLabel = nullptr; Label* densityLabel = nullptr; Label* conveyorEffectLabel = nullptr; Label* materialLabel = nullptr; Button* ambientColorButton = nullptr; Button* diffuseColorButton = nullptr; Button* specularColorButton = nullptr; Slider* reflectanceSlider = nullptr; CheckBox* renderModeCheckBox = nullptr; PropertiesFrame(double x, double y) : Frame(x, y, "Properties") { init(); add(generalLabel, Align::CENTER); add(partNameLabel, Align::FILL); add(partMeshIDLabel, Align::FILL); add(physicalLabel, Align::CENTER); add(positionLabel, Align::FILL); add(velocityLabel, Align::FILL); add(angularVelocityLabel, Align::FILL); add(kineticEnergyLabel, Align::FILL); add(potentialEnergyLabel, Align::FILL); add(energyLabel, Align::FILL); add(massLabel, Align::FILL); add(inertiaLabel, Align::FILL); add(frictionLabel, Align::FILL); add(bouncynessLabel, Align::FILL); add(densityLabel, Align::FILL); add(conveyorEffectLabel, Align::FILL); add(materialLabel, Align::CENTER); add(ambientColorButton, Align::FILL); add(diffuseColorButton, Align::FILL); add(specularColorButton, Align::FILL); add(reflectanceSlider, Align::FILL); add(renderModeCheckBox, Align::FILL); GUI::add(this); } void init() override { generalLabel = new Label("General", 0, 0); partNameLabel = new Label("", 0, 0); partMeshIDLabel = new Label("", 0, 0); physicalLabel = new Label("Physical", 0, 0); positionLabel = new Label("", 0, 0); velocityLabel = new Label("", 0, 0); angularVelocityLabel = new Label("", 0, 0); kineticEnergyLabel = new Label("", 0, 0); potentialEnergyLabel = new Label("", 0, 0); energyLabel = new Label("", 0, 0); massLabel = new Label("", 0, 0); inertiaLabel = new Label("", 0, 0); frictionLabel = new Label("", 0, 0); bouncynessLabel = new Label("", 0, 0); densityLabel = new Label("", 0, 0); conveyorEffectLabel = new Label("", 0, 0); materialLabel = new Label("Material", 0, 0); renderModeCheckBox = new CheckBox("Wireframe", 0, 0, true); renderModeCheckBox->action = [] (CheckBox* c) { if (screen.selectedPart) { if (screen.selectedPart->renderMode == Renderer::FILL) { screen.selectedPart->renderMode = Renderer::WIREFRAME; } else { screen.selectedPart->renderMode = Renderer::FILL; } } }; ambientColorButton = new Button(0, 0, GUI::sliderBarWidth, GUI::sliderHandleHeight, false); ambientColorButton->action = [] (Button* button) { PropertiesFrame* propertiesFrame = static_cast(button->parent); if (screen.selectedPart) { GUI::colorPicker->focus = button; GUI::select(GUI::colorPickerFrame); GUI::colorPickerFrame->visible = true; GUI::colorPickerFrame->anchor = propertiesFrame; GUI::colorPicker->action = [] (ColorPicker* colorPicker) { screen.selectedPart->material.ambient = colorPicker->getRgba(); }; } }; diffuseColorButton = new Button(0, 0, GUI::sliderBarWidth, GUI::sliderHandleHeight, false); diffuseColorButton->action = [] (Button* button) { PropertiesFrame* propertiesFrame = static_cast(button->parent); if (screen.selectedPart) { GUI::colorPicker->focus = button; GUI::select(GUI::colorPickerFrame); GUI::colorPickerFrame->visible = true; GUI::colorPickerFrame->anchor = propertiesFrame; GUI::colorPicker->action = [] (ColorPicker* colorPicker) { screen.selectedPart->material.diffuse = Vec3(colorPicker->getRgba()); }; } }; specularColorButton = new Button(0, 0, GUI::sliderBarWidth, GUI::sliderHandleHeight, false); specularColorButton->action = [] (Button* button) { PropertiesFrame* propertiesFrame = static_cast(button->parent); if (screen.selectedPart) { GUI::colorPicker->focus = button; GUI::select(GUI::colorPickerFrame); GUI::colorPickerFrame->visible = true; GUI::colorPickerFrame->anchor = propertiesFrame; GUI::colorPicker->action = [] (ColorPicker* colorPicker) { screen.selectedPart->material.specular = Vec3(colorPicker->getRgba()); }; } }; reflectanceSlider = new Slider(0, 0, 0, 5, 1); reflectanceSlider->action = [] (Slider* slider) { if (screen.selectedPart) { screen.selectedPart->material.reflectance = static_cast(slider->value); } }; } void update() override { if (!visible) return; ExtendedPart* selectedPart = screen.selectedPart; PlayerWorld* world = screen.world; if (selectedPart) { partMeshIDLabel->text = "MeshID: " + str(selectedPart->visualData.drawMeshId); positionLabel->text = "Position: " + str(selectedPart->getCFrame().position); partNameLabel->text = "Name: " + selectedPart->name; velocityLabel->text = "Velocity: " + str(selectedPart->getMotion().velocity); angularVelocityLabel->text = "Angular Velocity: " + str(selectedPart->getMotion().angularVelocity); double kineticEnergy = selectedPart->parent->getKineticEnergy(); double potentialEnergy = world->getPotentialEnergyOfPhysical(*selectedPart->parent->mainPhysical); kineticEnergyLabel->text = "Kinetic Energy: " + str(kineticEnergy); potentialEnergyLabel->text = "Potential Energy: " + str(potentialEnergy); energyLabel->text = "Energy: " + str(kineticEnergy + potentialEnergy); massLabel->text = "Mass: " + str(selectedPart->getMass()); inertiaLabel->text = "Inertia: " + str(Mat3(selectedPart->getInertia())); frictionLabel->text = "Friction: " + str(selectedPart->properties.friction); bouncynessLabel->text = "Bouncyness: " + str(selectedPart->properties.bouncyness); densityLabel->text = "Density: " + str(selectedPart->properties.density); conveyorEffectLabel->text = "ConveyorEffect: " + str(selectedPart->properties.conveyorEffect); renderModeCheckBox->checked = selectedPart->renderMode == Renderer::WIREFRAME; ambientColorButton->disabled = false; ambientColorButton->setColor(selectedPart->material.ambient); if (GUI::colorPicker->focus == ambientColorButton) { GUI::colorPicker->setRgba(selectedPart->material.ambient); } diffuseColorButton->disabled = false; diffuseColorButton->setColor(Vec4(selectedPart->material.diffuse, 1)); if (GUI::colorPicker->focus == diffuseColorButton) { GUI::colorPicker->setRgba(Vec4(selectedPart->material.diffuse, 1)); } specularColorButton->disabled = false; specularColorButton->setColor(Vec4(selectedPart->material.specular, 1)); if (GUI::colorPicker->focus == specularColorButton) { GUI::colorPicker->setRgba(Vec4(selectedPart->material.specular, 1)); } reflectanceSlider->disabled = false; reflectanceSlider->value = selectedPart->material.reflectance; } else { partMeshIDLabel->text = "MeshID: -"; positionLabel->text = "Position: -"; partNameLabel->text = "Name: -"; velocityLabel->text = "Velocity: -"; angularVelocityLabel->text = "Angular Velocity: -"; kineticEnergyLabel->text = "Kinetic Energy: -"; potentialEnergyLabel->text = "Potential Energy: -"; energyLabel->text = "Energy: -"; massLabel->text = "Mass: -"; inertiaLabel->text = "Inertia: -"; frictionLabel->text = "Friction: -"; bouncynessLabel->text = "Bouncyness: -"; densityLabel->text = "Density: -"; conveyorEffectLabel->text = "ConveyorEffect: -"; ambientColorButton->disabled = true; ambientColorButton->setColor(Color(1)); diffuseColorButton->disabled = true; diffuseColorButton->setColor(Color(1)); specularColorButton->disabled = true; specularColorButton->setColor(Color(1)); reflectanceSlider->disabled = true; reflectanceSlider->value = (reflectanceSlider->max + reflectanceSlider->min) / 2; renderModeCheckBox->checked = false; } } }; }; ================================================ FILE: application/math.natvis ================================================ {{{x, g}, {y, g}}} {{{x, g}, {y, g}, {z, g}}} {{{x, g}, {y, g}, {z, g}, {w, g}}} {data[0],g}, {data[2],g} {data[1],g}, {data[3],g} {data[0],g}, {data[3],g}, {data[6],g} {data[1],g}, {data[4],g}, {data[7],g} {data[2],g}, {data[5],g}, {data[8],g} {data[0],g}, {data[4],g}, {data[8],g}, {data[12],g} {data[1],g}, {data[5],g}, {data[9],g}, {data[13],g} {data[2],g}, {data[6],g}, {data[10],g}, {data[14],g} {data[3],g}, {data[7],g}, {data[11],g}, {data[15],g} {{{x, g}, {y, g}, {z, g}}} Width = {width}, Height = {height} width height width * height data Size = {size} size size data ================================================ FILE: application/picker/ray.cpp ================================================ #include "core.h" #include "ray.h" #include "view/screen.h" namespace P3D::Application { Vec2f getNormalizedDeviceSpacePosition(const Vec2f& viewportSpacePosition, const Vec2f& screenSize) { float x = 2 * viewportSpacePosition.x / screenSize.x - 1; float y = 2 * viewportSpacePosition.y / screenSize.y - 1; return Vec2f(x, -y); } Vec3f calcRay(const Screen& screen, Vec2f mouse) { Vec2f normalizedDeviceSpacePosition = getNormalizedDeviceSpacePosition(mouse, screen.dimension); Vec4f clipSpacePosition = Vec4f(normalizedDeviceSpacePosition.x, normalizedDeviceSpacePosition.y, -1.0f, 1.0f); Vec4f eyeCoordinates = screen.camera.invertedProjectionMatrix * clipSpacePosition; eyeCoordinates = Vec4f(eyeCoordinates.x, eyeCoordinates.y, -1.0f, 0.0f); Vec4f rayWorld = screen.camera.invertedViewMatrix * eyeCoordinates; Vec3f rayDirection = normalize(Vec3f(rayWorld.x, rayWorld.y, rayWorld.z)); return rayDirection; } Ray getMouseRay(const Screen& screen, const Vec2f& mouse) { Position start = screen.camera.cframe.getPosition(); Vec3f direction = calcRay(screen, mouse); return Ray { start, Vec3(direction) }; } }; ================================================ FILE: application/picker/ray.h ================================================ #pragma once #include namespace P3D::Application { class Screen; Ray getMouseRay(const Screen& screen, const Vec2f& mouse); }; ================================================ FILE: application/picker/selection.cpp ================================================ #include "core.h" #include "selection.h" #include "application.h" #include "view/screen.h" #include "ecs/components.h" namespace P3D::Application { void Selection::recalculateSelection() { this->boundingBox = std::nullopt; for (auto entity : this->selection) expandSelection(entity); } void Selection::expandSelection(const Engine::Registry64::entity_type& entity) { IRef transform = screen.registry.get(entity); if (transform.invalid()) return; IRef hitbox = screen.registry.get(entity); if (selection.empty() || !this->boundingBox.has_value()) { if (hitbox.valid()) this->boundingBox = hitbox->getShape().getBounds(); else this->boundingBox = BoundingBox(0.2, 0.2, .2); } else { IRef referenceTransform = screen.registry.get(selection[0]); GlobalCFrame referenceFrame = referenceTransform->getCFrame(); CFrame relativeFrame = referenceFrame.globalToLocal(transform->getCFrame()); if (hitbox.valid()) { BoundingBox rotatedBounds = hitbox->getShape().getBounds(relativeFrame.getRotation()); this->boundingBox = this->boundingBox->expanded(relativeFrame.getPosition() + rotatedBounds.min).expanded(relativeFrame.getPosition() + rotatedBounds.max); } else { this->boundingBox = this->boundingBox->expanded(boxShape(0.2, 0.2, 0.2).getBounds(relativeFrame.getRotation())); } } } bool Selection::empty() const { return this->selection.empty(); } std::size_t Selection::size() const { return this->selection.size(); } bool Selection::contains(const Engine::Registry64::entity_type& entity) const { for(const Engine::Registry64::entity_type& found : selection) if(found == entity) return true; return false; } bool Selection::isMultiSelection() const { return size() > 1; } bool Selection::isSingleSelection() const { return size() == 1; } Engine::Registry64::entity_type& Selection::operator[](int index) { return this->selection[index]; } void Selection::add(const Selection& other, bool recalculateBounds) { for (const Engine::Registry64::entity_type& entity : other) this->add(entity, recalculateBounds); } void Selection::remove(const Selection& other, bool recalculateBounds) { for (const Engine::Registry64::entity_type& entity : other) this->remove(entity, false); if (recalculateBounds) { this->boundingBox = std::nullopt; for (const Engine::Registry64::entity_type entity : *this) expandSelection(entity); } } void Selection::add(const Engine::Registry64::entity_type& entity, bool recalculateBounds) { auto iterator = std::find(this->selection.begin(), this->selection.end(), entity); if (iterator != this->selection.end()) return; this->selection.push_back(entity); if (recalculateBounds) expandSelection(entity); } void Selection::remove(const Engine::Registry64::entity_type& entity, bool recalculateBounds) { if (entity == Engine::Registry64::null_entity) return; auto iterator = std::find(this->selection.begin(), this->selection.end(), entity); if (iterator == this->selection.end()) return; this->selection.erase(iterator); if (recalculateBounds) { this->boundingBox = std::nullopt; for (const Engine::Registry64::entity_type entity : *this) expandSelection(entity); } } void Selection::toggle(const Engine::Registry64::entity_type& entity, bool recalculateBounds) { auto iterator = std::find(this->selection.begin(), this->selection.end(), entity); if (iterator == selection.end()) add(entity, recalculateBounds); else remove(entity, recalculateBounds); } void Selection::clear() { this->selection.clear(); this->boundingBox = std::nullopt; } void Selection::translate(const Vec3& translation) { for (auto entity : this->selection) { IRef transform = screen.registry.get(entity); if (transform.valid()) transform->translate(translation); } } void Selection::rotate(const Vec3& normal, double angle) { std::optional reference = getCFrame(); if (!reference.has_value()) return; Rotation rotation = Rotation::fromRotationVector(angle * normal); for (auto entity : this->selection) { IRef transform = screen.registry.get(entity); if (transform.valid()) { //transform->rotate(normal, angle); transform->rotate(rotation); Vec3 delta = transform->getPosition() - reference->getPosition(); Vec3 translation = rotation * delta - delta; transform->translate(translation); } } } void Selection::scale(const Vec3& scale) { std::optional reference = getCFrame(); if (!reference.has_value()) return; for (auto entity : this->selection) { IRef transform = screen.registry.get(entity); Vec3 delta = transform->getPosition() - reference->getPosition(); Vec3 translation = elementWiseMul(delta, scale - Vec3(1.0, 1.0, 1.0)); transform->translate(translation); transform->scale(scale.x, scale.y, scale.z); } recalculateSelection(); } std::optional Selection::getHitbox() const { if (this->selection.empty()) return std::nullopt; if (this->size() == 1) { IRef hitbox = screen.registry.get(this->selection[0]); if (hitbox.invalid()) return std::nullopt; return hitbox->getShape(); } return boxShape(boundingBox->getWidth(), boundingBox->getHeight(), boundingBox->getDepth()); } std::optional Selection::getCFrame() const { if (this->selection.empty()) return std::nullopt; IRef transform = screen.registry.get(this->selection[0]); if (transform.invalid()) return std::nullopt; return GlobalCFrame(transform->getCFrame().localToGlobal(this->boundingBox->getCenter()), transform->getRotation()); } std::vector::const_iterator Selection::begin() const { return this->selection.begin(); } std::vector::const_iterator Selection::end() const { return this->selection.end(); } std::vector::iterator Selection::begin() { return this->selection.begin(); } std::vector::iterator Selection::end() { return this->selection.end(); } std::optional Selection::first() const { if (selection.empty()) return std::nullopt; return selection[0]; } std::optional Selection::last() const { if (selection.empty()) return std::nullopt; return selection[selection.size() - 1]; } } ================================================ FILE: application/picker/selection.h ================================================ #pragma once #include "../engine/ecs/registry.h" #include "../application/ecs/components.h" #include namespace P3D::Application { struct Selection { private: // A bounding box in the cframe of the first entity in the selection std::optional boundingBox; std::vector selection; void expandSelection(const Engine::Registry64::entity_type& entity); public: [[nodiscard]] bool empty() const; [[nodiscard]] std::size_t size() const; [[nodiscard]] bool contains(const Engine::Registry64::entity_type& entity) const; [[nodiscard]] bool isMultiSelection() const; [[nodiscard]] bool isSingleSelection() const; [[nodiscard]] Engine::Registry64::entity_type& operator[](int index); void add(const Selection& other, bool recalculateBounds = true); void add(const Engine::Registry64::entity_type& entity, bool recalculateBounds = true); void remove(const Selection& other, bool recalculateBounds = true); void remove(const Engine::Registry64::entity_type& entity, bool recalculateBounds = true); void toggle(const Engine::Registry64::entity_type& entity, bool recalculateBounds = true); void clear(); void translate(const Vec3& translation); void rotate(const Vec3& normal, double angle); void scale(const Vec3& scale); void recalculateSelection(); [[nodiscard]] std::optional getCFrame() const; [[nodiscard]] std::optional getHitbox() const; [[nodiscard]] std::vector::const_iterator begin() const; [[nodiscard]] std::vector::const_iterator end() const; [[nodiscard]] std::vector::iterator begin(); [[nodiscard]] std::vector::iterator end(); [[nodiscard]] std::optional first() const; [[nodiscard]] std::optional last() const; }; } ================================================ FILE: application/picker/tools/alignmentLinkTool.cpp ================================================ #include "core.h" #include "alignmentLinkTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void AlignmentLinkTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void AlignmentLinkTool::onDeregister() { // Remove texture } void AlignmentLinkTool::onSelect() { linkSelection(); } void AlignmentLinkTool::linkSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); AttachedPart partA { cframe, parentCollider->part }; AttachedPart partB { CFrame(), childCollider->part }; AlignmentLink* link = new AlignmentLink(partA, partB); screen.worldMutex->lock(); try { world.addLink(link); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/alignmentLinkTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class AlignmentLinkTool : public Engine::ButtonTool { public: DEFINE_TOOL("Alignment Link", "Select two entities to create an alignment link beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~AlignmentLinkTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void linkSelection(); }; } ================================================ FILE: application/picker/tools/attachmentTool.cpp ================================================ #include "core.h" #include "attachmentTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void AttachmentTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void AttachmentTool::onDeregister() { // Remove texture } void AttachmentTool::onSelect() { attachSelection(); } void AttachmentTool::attachSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); screen.worldMutex->lock(); try { parentCollider->part->attach(childCollider->part, cframe); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/attachmentTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class AttachmentTool : public Engine::ButtonTool { public: DEFINE_TOOL("Attachment", "Select two entities to create an attachment beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~AttachmentTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void attachSelection(); }; } ================================================ FILE: application/picker/tools/elasticLinkTool.cpp ================================================ #include "core.h" #include "elasticLinkTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/softlinks/elasticLink.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void ElasticLinkTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void ElasticLinkTool::onDeregister() { // Remove texture } void ElasticLinkTool::onSelect() { linkSelection(); } void ElasticLinkTool::linkSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); AttachedPart part1 { cframe, parentCollider->part }; AttachedPart part2 { CFrame(), childCollider->part }; ElasticLink* link = new ElasticLink(part1, part2, 5.0, 1.0); screen.worldMutex->lock(); try { world.addLink(link); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/elasticLinkTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class ElasticLinkTool : public Engine::ButtonTool { public: DEFINE_TOOL("Elastic Link", "Select two entities to create an elastic link beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~ElasticLinkTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void linkSelection(); }; } ================================================ FILE: application/picker/tools/fixedConstraintTool.cpp ================================================ #include "core.h" #include "fixedConstraintTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/hardconstraints/fixedConstraint.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void FixedConstraintTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void FixedConstraintTool::onDeregister() { // Remove texture } void FixedConstraintTool::onSelect() { fixSelection(); } void FixedConstraintTool::fixSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); FixedConstraint* constraint = new FixedConstraint(); screen.worldMutex->lock(); try { parentCollider->part->attach(childCollider->part, constraint, cframe, CFrame()); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/fixedConstraintTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class FixedConstraintTool : public Engine::ButtonTool { public: DEFINE_TOOL("Fixed Constraint", "Select two entities to create a fixed constraints beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~FixedConstraintTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void fixSelection(); }; } ================================================ FILE: application/picker/tools/magneticLinkTool.cpp ================================================ #include "core.h" #include "magneticLinkTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/softlinks/magneticLink.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void MagneticLinkTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void MagneticLinkTool::onDeregister() { // Remove texture } void MagneticLinkTool::onSelect() { linkSelection(); } void MagneticLinkTool::linkSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); AttachedPart part1 { cframe, parentCollider->part }; AttachedPart part2 { CFrame(), childCollider->part }; MagneticLink* link = new MagneticLink(part1, part2, 1.0); screen.worldMutex->lock(); try { world.addLink(link); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/magneticLinkTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class MagneticLinkTool : public Engine::ButtonTool { public: DEFINE_TOOL("Magnetic Link", "Select two entities to create a magnetic link beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~MagneticLinkTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void linkSelection(); }; } ================================================ FILE: application/picker/tools/motorConstraintTool.cpp ================================================ #include "core.h" #include "motorConstraintTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/hardconstraints/constraintTemplates.h" #include "Physics3D/hardconstraints/fixedConstraint.h" #include "Physics3D/hardconstraints/controller/constController.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void MotorConstraintTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void MotorConstraintTool::onDeregister() { // Remove texture } void MotorConstraintTool::onSelect() { constrainSelection(); } void MotorConstraintTool::constrainSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); MotorConstraintTemplate* constraint = new MotorConstraintTemplate(1.0); screen.worldMutex->lock(); try { parentCollider->part->attach(childCollider->part, constraint, cframe, CFrame()); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/motorConstraintTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class MotorConstraintTool : public Engine::ButtonTool { public: DEFINE_TOOL("Motor Constraint", "Select two entities to create a motor constraint beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~MotorConstraintTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void constrainSelection(); }; } ================================================ FILE: application/picker/tools/pathTool.cpp ================================================ #include #include "pathTool.h" #include "application/application.h" #include "application/view/screen.h" #include "selectionTool.h" #include "util/resource/resourceManager.h" #include "graphics/resource/textureResource.h" #include "application/shader/shaders.h" #include "graphics/meshRegistry.h" #include "graphics/mesh/primitive.h" namespace P3D::Application { static URef deltaLine = nullptr; void PathTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); deltaLine = std::make_unique(); } void PathTool::onDeregister() { } void PathTool::onRender() { Renderer::enableDepthTest(); int divisions = 20; double size = 1.0; Vec3 right = screen.camera.getRightDirection(); Vec3 up = screen.camera.getUpDirection(); Vec3 forward = screen.camera.getForwardDirection(); Vec3 center = castPositionToVec3(screen.camera.cframe.getPosition() + forward * distance); Shaders::maskShader->updateColor(Colors::RED); Shaders::maskShader->updateModel(GlobalCFrame().asMat4()); for (int index = -divisions / 2; index <= divisions / 2; index++) { Vec3 dx = right * index * size; Vec3 dy = up * index * size; Vec3 mx = right * divisions / 2 * size; Vec3 my = up * divisions / 2 * size; deltaLine->resize(center - mx + dy, center + mx + dy); deltaLine->render(); deltaLine->resize(center + dx - my, center + dx + my); deltaLine->render(); } Shaders::maskShader->updateColor(Colors::RED); for (std::size_t index = 0; index < nodes.size(); index++) { Shaders::maskShader->updateModel(GlobalCFrame(castVec3ToPosition(nodes[index])).asMat4WithPreScale(DiagonalMat3({0.1, 0.1, 0.1}))); MeshRegistry::get(MeshRegistry::sphere)->render(); if (index > 0) { Shaders::maskShader->updateModel(GlobalCFrame().asMat4()); deltaLine->resize(nodes[index], nodes[index - 1]); deltaLine->render(); } } Renderer::disableDepthTest(); } void PathTool::onUpdate() { } void PathTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(PathTool::onMousePress)); dispatcher.dispatch(EVENT_BIND(PathTool::onMouseRelease)); dispatcher.dispatch(EVENT_BIND(PathTool::onMouseDrag)); } Vec3 intersect(double distance) { Vec3 normal = -screen.camera.getForwardDirection(); Position center = screen.camera.cframe.getPosition() - normal * distance; double t = (center - SelectionTool::ray.origin) * normal / (SelectionTool::ray.direction * normal); return castPositionToVec3(SelectionTool::ray.origin) + t * SelectionTool::ray.direction; } bool PathTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; nodes.push_back(intersect(distance)); active = true; } bool PathTool::onMouseRelease(Engine::MouseReleaseEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; } bool PathTool::onMouseDrag(Engine::MouseDragEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; nodes.push_back(intersect(distance)); active = false; } } ================================================ FILE: application/picker/tools/pathTool.h ================================================ #pragma once #include "engine/event/mouseEvent.h" #include "engine/tool/stateTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class PathTool : public Engine::StateTool { private: bool active = false; double distance = 20; std::vector nodes; public: DEFINE_TOOL("Path tool", "", Graphics::GLFW::Cursor::ARROW); ~PathTool() override = default; void onRegister() override; void onDeregister() override; void onRender() override; void onUpdate() override; void onEvent(Engine::Event& event) override; bool onMousePress(Engine::MousePressEvent& event); bool onMouseRelease(Engine::MouseReleaseEvent& event); bool onMouseDrag(Engine::MouseDragEvent& event); }; }; ================================================ FILE: application/picker/tools/pistonConstraintTool.cpp ================================================ #include "core.h" #include "pistonConstraintTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/hardconstraints/fixedConstraint.h" #include "Physics3D/hardconstraints/sinusoidalPistonConstraint.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void PistonConstraintTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void PistonConstraintTool::onDeregister() { // Remove texture } void PistonConstraintTool::onSelect() { constrainSelection(); } void PistonConstraintTool::constrainSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); SinusoidalPistonConstraint* constraint = new SinusoidalPistonConstraint(2, 5, 1); screen.worldMutex->lock(); try { parentCollider->part->attach(childCollider->part, constraint, cframe, CFrame()); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/pistonConstraintTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class PistonConstraintTool : public Engine::ButtonTool { public: DEFINE_TOOL("Piston Constraint", "Select two entities to create a motor constraint beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~PistonConstraintTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void constrainSelection(); }; } ================================================ FILE: application/picker/tools/regionSelectionTool.cpp ================================================ #include "core.h" #include "regionSelectionTool.h" #include "../engine/event/event.h" #include "../engine/event/mouseEvent.h" #include "../graphics/gui/gui.h" #include "../graphics/gui/guiUtils.h" #include "../graphics/path/path.h" #include "../graphics/resource/textureResource.h" #include #include "../util/resource/resourceManager.h" #include "selectionTool.h" #include "../../view/screen.h" #include "../../application.h" #include "../../shader/shaders.h" #include "../../input/standardInputHandler.h" #include "../../ecs/components.h" #include "../../worlds.h" #include namespace P3D::Application { void RegionSelectionTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void RegionSelectionTool::onDeregister() { // Remove texture } void RegionSelectionTool::onRender() { using namespace Graphics; Renderer::beginScene(); Graphics::Shaders::guiShader->bind(); Graphics::Shaders::guiShader->setUniform("projectionMatrix", screen.camera.orthoMatrix); Path::batch = GUI::batch; if (getToolStatus() == kActive) { Vec2 a = GUI::map(Vec2(region.x, region.y)); Vec2 b = GUI::map(handler->getMousePosition()); Vec2 position(std::min(a.x, b.x), std::min(a.y, b.y)); Vec2 dimension(std::abs(a.x - b.x), std::abs(a.y - b.y)); Path::rect(position, dimension); Path::batch->submit(); } Renderer::endScene(); } void RegionSelectionTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(RegionSelectionTool::onMousePress)); dispatcher.dispatch(EVENT_BIND(RegionSelectionTool::onMouseRelease)); } bool RegionSelectionTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; region.x = event.getX(); region.y = event.getY(); // Multi selection check if (!event.getModifiers().isCtrlPressed()) SelectionTool::clear(); setToolStatus(kActive); return false; } bool RegionSelectionTool::onMouseRelease(Engine::MouseReleaseEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; region.z = event.getX(); region.w = event.getY(); if (region.x == region.z && region.y == region.w) { // Single selection auto intersectedEntity = SelectionTool::getIntersectedEntity(); if (intersectedEntity.has_value()) SelectionTool::toggle(intersectedEntity->first); } else { // Region selection Vec4 mappedRegion = GUI::mapRegion(region, Vec2(0, screen.dimension.x), Vec2(screen.dimension.y, 0), Vec2(-1, 1), Vec2(-1, 1)); auto [left, right] = GUI::minmax(mappedRegion.x, mappedRegion.z); auto [down, up] = GUI::minmax(mappedRegion.y, mappedRegion.w); VisibilityFilter visibilityFilter = VisibilityFilter::forSubWindow(screen.camera.cframe.position, screen.camera.getForwardDirection(), screen.camera.getUpDirection(), screen.camera.fov, screen.camera.aspect, screen.camera.zfar, left, right, down, up); auto view = screen.registry.view(); std::shared_lock worldReadLock(*screen.worldMutex); for (auto entity : view) { IRef hitbox = view.get(entity); IRef transform = view.get(entity); Shape shape = hitbox->getShape(); if (!transform->isRootPart()) shape = shape.scaled(transform->getScale()); Bounds bounds = shape.getBounds(transform->getRotation()) + transform->getPosition(); if (visibilityFilter(bounds)) SelectionTool::select(entity); } } setToolStatus(kIdle); return false; } } ================================================ FILE: application/picker/tools/regionSelectionTool.h ================================================ #pragma once #include "../graphics/glfwUtils.h" #include "../engine/event/mouseEvent.h" #include "../engine/tool/stateTool.h" #include "../engine/ecs/registry.h" #include "ecs/components.h" struct ExtendedPart; namespace P3D::Application { class RegionSelectionTool : public Engine::StateTool { private: enum SelectionToolStatus : Engine::ToolStatus { kIdle = 0, kActive = 1 }; Vec4i region; public: DEFINE_TOOL("Select region", "Selects one or multiple entities within a region.", Graphics::GLFW::Cursor::CROSSHAIR); ~RegionSelectionTool() override = default; void onRegister() override; void onDeregister() override; void onRender() override; void onEvent(Engine::Event& event) override; bool onMousePress(Engine::MousePressEvent& event); bool onMouseRelease(Engine::MouseReleaseEvent& event); }; } ================================================ FILE: application/picker/tools/rotationTool.cpp ================================================ #include "core.h" #include "rotationTool.h" #include "worlds.h" #include "application.h" #include "selectionTool.h" #include "translationTool.h" #include "view/screen.h" #include "shader/shaders.h" #include #include #include #include "../graphics/extendedTriangleMesh.h" #include "../graphics/mesh/primitive.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "engine/input/keyboard.h" #include "imgui/imgui.h" #include "input/standardInputHandler.h" namespace P3D::Application { constexpr static Rotation transformations[] { Rotation::Predefined::Y_90, // X Rotation::Predefined::X_270, // Y Rotation::Predefined::IDENTITY // Z }; static std::optional startPosition; static URef deltaLine = nullptr; static URef unitLine = nullptr; static URef infiniteLine = nullptr; static URef handleMesh; static ExtendedTriangleMesh handleShape; void RotationTool::onRegister() { using namespace Graphics; // Load icon std::string path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); // Create alignment line deltaLine = std::make_unique(); unitLine = std::make_unique(); infiniteLine = std::make_unique(); unitLine->resize(Vec3f(0, 0, 0), Vec3f(0, 0, 0.5)); infiniteLine->resize(Vec3f(0, 0, -100000), Vec3f(0, 0, 100000)); // Create handle shapes handleShape = ExtendedTriangleMesh::generateSmoothNormalsShape(ShapeLibrary::createTorus(1.0f, 0.03f, 80, 12)); handleMesh = std::make_unique(handleShape); // Set idle status setToolStatus(kIdle); } void RotationTool::onDeregister() { // Todo remove icon // Todo remove line handleMesh->close(); } void RotationTool::onRender() { using namespace Graphics; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; bool local = !handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; if (!local) cframe = GlobalCFrame(cframe->position); Mat4f model = cframe->asMat4(); Mat4f modelX = model * joinDiagonal(Mat3f(transformations[0].asRotationMatrix()), 1.0f); Mat4f modelY = model * joinDiagonal(Mat3f(transformations[1].asRotationMatrix()), 1.0f); Mat4f modelZ = model * joinDiagonal(Mat3f(transformations[2].asRotationMatrix()), 1.0f); Mat4f modelC = GlobalCFrame(cframe->getPosition(), Rotation::fromDirection(Vec3(screen.camera.cframe.position - cframe->getPosition()))).asMat4(); auto status = getToolStatus(); Shaders::maskShader->updateModel(modelX); Shaders::maskShader->updateColor(Colors::RGB_R); unitLine->render(); if (status == kRotateX) infiniteLine->render(); Shaders::maskShader->updateModel(modelY); Shaders::maskShader->updateColor(Colors::RGB_G); unitLine->render(); if (status == kRotateY) infiniteLine->render(); Shaders::maskShader->updateModel(modelZ); Shaders::maskShader->updateColor(Colors::RGB_B); unitLine->render(); if (status == kRotateZ) infiniteLine->render(); Shaders::basicShader->updateModel(modelC); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::YELLOW)); handleMesh->render(); // X Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); Shaders::basicShader->updateModel(modelX); handleMesh->render(); // Y Shaders::basicShader->updateModel(modelY); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_G)); handleMesh->render(); // Z Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); Shaders::basicShader->updateModel(modelZ); handleMesh->render(); if (startPosition.has_value() && active) { Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::ORANGE)); Shaders::basicShader->updateModel(GlobalCFrame(startPosition.value()), DiagonalMat3::IDENTITY() * 0.5); Vec3f doubleRelativePosition = (castPositionToVec3f(cframe->position) - castPositionToVec3f(startPosition.value())) * 2.0f; deltaLine->resize(Vec3f(), doubleRelativePosition); deltaLine->render(); } } void RotationTool::onUpdate() { // Keep the tool status if the tool is active if (this->active) return; // Reset tool status setToolStatus(kIdle); SelectionTool::intersectedPoint = std::nullopt; // The intersection distance to find std::optional closestIntersectionDistance; // Look for edit tool intersections if the selection has a cframe std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; bool local = !handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; if (!local) cframe = GlobalCFrame(cframe->position); GlobalCFrame frame = *cframe; for (char status = kRotateX; status <= kRotateC; status++) { if (status == kRotateC) { frame.rotation = Rotation::fromDirection(Vec3(screen.camera.cframe.position - cframe->getPosition())); } else { frame.rotation = cframe->getRotation() * transformations[status - 1]; } std::optional distance = SelectionTool::intersect(frame, handleShape); if (!distance.has_value()) continue; if (!closestIntersectionDistance.has_value() || 0.0 < distance && distance < closestIntersectionDistance) { closestIntersectionDistance = distance; setToolStatus(status); } } if (closestIntersectionDistance.has_value()) SelectionTool::intersectedPoint = SelectionTool::ray.origin + SelectionTool::ray.direction * *closestIntersectionDistance; } void RotationTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(RotationTool::onMousePress)); dispatcher.dispatch(EVENT_BIND(RotationTool::onMouseRelease)); dispatcher.dispatch(EVENT_BIND(RotationTool::onMouseDrag)); } bool RotationTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Auto select an entity if the selection status is idle, in that case no edit tool or entity is intersected auto status = getToolStatus(); if (status == kIdle) { // Find the intersected entity auto intersectedEntity = SelectionTool::getIntersectedEntity(); // If control is pressed, add to selection, but don't change the tool status if (event.getModifiers().isCtrlPressed()) { // No intersection causes nothing if (!intersectedEntity.has_value()) return false; } else { // Clear the selection before selecting the new entity SelectionTool::selection.clear(); // No intersection causes only deselection if (!intersectedEntity.has_value()) return false; setToolStatus(kTranslateC); SelectionTool::intersectedPoint = intersectedEntity->second; } SelectionTool::select(intersectedEntity->first); } // Set the selected point SelectionTool::selectedPoint = SelectionTool::intersectedPoint; // Set start position startPosition = SelectionTool::selectedPoint; // Set edit status to active this->active = true; return false; } bool RotationTool::onMouseRelease(Engine::MouseReleaseEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Set inactive this->active = false; // Reset magnet TranslationTool::magnet.selectedPart = nullptr; // Clear start position startPosition = std::nullopt; return false; }; bool RotationTool::onMouseDrag(Engine::MouseDragEvent& event) { if (!this->active) return false; auto status = getToolStatus(); if (status == kIdle) return false; bool clamp = handler->keys[Engine::Keyboard::KEY_LEFT_ALT.getCode()]; bool local = !handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; std::unique_lock worldWriteLock(*screen.worldMutex); switch (status) { case kRotateX: rotateAroundLine({ 1, 0, 0 }, clamp, local); break; case kRotateY: rotateAroundLine({ 0, 1, 0 }, clamp, local); break; case kRotateZ: rotateAroundLine({ 0, 0, 1 }, clamp, local); break; case kRotateC: rotateAroundLine(screen.camera.cframe.rotation * Vec3(0, 0, 1), clamp, false); break; case kTranslateC: TranslationTool::translateInPlane(screen.camera.cframe.rotation * Vec3(0, 0, 1), clamp, false); break; default: return false; } return true; } void RotationTool::rotateAroundLine(const Vec3& direction, bool clamp, bool local) { if (SelectionTool::selection.empty()) return; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; // Plane of edit tool, which can be expressed as all points p where (p - position) * normal = 0. Where n is the edit direction and p0 the center of the selected part Position position = cframe->getPosition(); // Apply model matrix Vec3 normal = local ? cframe->localToRelative(direction) : direction; // Calculate intersection of mouse ray and edit plane double ln = SelectionTool::ray.direction * normal; if (ln == 0.0) return; // No rotation if plane is perpendicular to mouse ray // Vector from part center to intersection Position intersection = SelectionTool::ray.origin + (position - SelectionTool::ray.origin) * normal / (SelectionTool::ray.direction * normal) * SelectionTool::ray.direction; Vec3 intersectionVector = intersection - position; // Length check Vec3 relativeSelectedPoint = *SelectionTool::selectedPoint - position; double length1sq = lengthSquared(intersectionVector); double length2sq = lengthSquared(relativeSelectedPoint); if (length1sq == 0.0 || length2sq == 0.0) return; // Prevent errors when vector is the zero vector // Triple product to find angle sign double triple = relativeSelectedPoint * (intersectionVector % normal); double sign = triple > 0.0 ? 1.0 : -1.0; // Get angle between last intersectionVector and new one double cosa = relativeSelectedPoint * intersectionVector / sqrt(length1sq * length2sq); // No rotation when vectors coincide if (!(std::abs(cosa) < 1)) return; double angle = sign * acos(cosa); if (clamp) { double rad15 = 0.261799; angle = angle - fmod(angle, rad15); } if (angle != 0.0) { // Update last intersectionVector SelectionTool::selectedPoint = position + intersectionVector; // Apply rotation SelectionTool::selection.rotate(normal, angle); } } } ================================================ FILE: application/picker/tools/rotationTool.h ================================================ #pragma once #include "../graphics/glfwUtils.h" #include "../engine/event/mouseEvent.h" #include "../engine/tool/stateTool.h" #include "ecs/components.h" namespace P3D::Application { class RotationTool : public Engine::StateTool { private: enum SelectionToolStatus : Engine::ToolStatus { kIdle = 0, kRotateX = 1, kRotateY = 2, kRotateZ = 3, kRotateC = 4, kTranslateC = 5 }; bool active = false; public: DEFINE_TOOL("Rotate", "Rotate entities by using the handles", Graphics::GLFW::Cursor::ARROW); virtual ~RotationTool() override = default; void onRegister() override; void onDeregister() override; void onRender() override; void onUpdate() override; void onEvent(Engine::Event& event) override; bool onMousePress(Engine::MousePressEvent& event); bool onMouseRelease(Engine::MouseReleaseEvent& event); bool onMouseDrag(Engine::MouseDragEvent& event); static void rotateAroundLine(const Vec3& direction, bool clamp, bool local); }; }; ================================================ FILE: application/picker/tools/scaleTool.cpp ================================================ #include "core.h" #include "scaleTool.h" #include "worlds.h" #include "application.h" #include "selectionTool.h" #include "translationTool.h" #include "view/screen.h" #include "shader/shaders.h" #include #include #include #include "../graphics/extendedTriangleMesh.h" #include "../graphics/mesh/primitive.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "engine/input/keyboard.h" #include "imgui/imgui.h" #include "input/standardInputHandler.h" namespace P3D::Application { constexpr static Rotation transformations[] { Rotation::Predefined::Y_90, // X, XY Rotation::Predefined::X_270, // Y Rotation::Predefined::IDENTITY, // Z, YZ Rotation::Predefined::Z_270 // XZ }; constexpr static std::size_t mapping[] { 0, // X 1, // Y 2, // Z 2, // C 0, // XY 3, // XZ 2 // YZ }; static LinePrimitive* line = nullptr; static IndexedMesh* quadMesh; static ExtendedTriangleMesh quadShape; static IndexedMesh* handleMesh; static ExtendedTriangleMesh handleShape; static IndexedMesh* centerMesh; static ExtendedTriangleMesh centerShape; static Polyhedron createBoxOnStick(float boxSide, float stickRadius) { Vec2f vecs[] { { 0.0f, stickRadius }, { 1.0f - boxSide, stickRadius }, { 1.0f - boxSide, boxSide / sqrtf(2.0f) }, { 1.0f, boxSide / sqrtf(2.0f) }}; return ShapeLibrary::createRevolvedShape(0.0f, vecs, 4, 1.0f, 4).rotated(Rotation::rotZ(3.14159265359 / 4)); } void ScaleTool::onRegister() { using namespace Graphics; // Load icon std::string path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); // Create alignment line line = new LinePrimitive(); line->resize(Vec3f(0, 0, -100000), Vec3f(0, 0, 100000)); // Create handle shapes handleShape = ExtendedTriangleMesh::generateSplitNormalsShape(createBoxOnStick(0.2f, 0.03f)); handleMesh = new IndexedMesh(handleShape); centerShape = ExtendedTriangleMesh::generateSplitNormalsShape(ShapeLibrary::createCube(0.2f)); centerMesh = new IndexedMesh(centerShape); quadShape = ExtendedTriangleMesh::generateSplitNormalsShape(ShapeLibrary::createBox(0.02f, 0.25f, 0.25f).translated({ 0, 0.5, 0.5 })); quadMesh = new IndexedMesh(quadShape); // Set idle status setToolStatus(kIdle); } void ScaleTool::onDeregister() { // Todo remove icon // Todo remove line handleMesh->close(); centerMesh->close(); quadMesh->close(); } void ScaleTool::onRender() { using namespace Graphics; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; Mat4f model = cframe->asMat4(); Mat4f modelX = model * joinDiagonal(Mat3f(transformations[0].asRotationMatrix()), 1.0f); Mat4f modelY = model * joinDiagonal(Mat3f(transformations[1].asRotationMatrix()), 1.0f); Mat4f modelZ = model * joinDiagonal(Mat3f(transformations[2].asRotationMatrix()), 1.0f); Mat4f modelXZ = model * joinDiagonal(Mat3f(transformations[3].asRotationMatrix()), 1.0f); auto status = getToolStatus(); if (status == kScaleX || status == kScaleXY || status == kScaleXZ || status == kScaleXYZ) { Shaders::maskShader->updateModel(modelX); Shaders::maskShader->updateColor(Colors::RGB_R); line->render(); } if (status == kScaleY || status == kScaleXY || status == kScaleYZ || status == kScaleXYZ) { Shaders::maskShader->updateModel(modelY); Shaders::maskShader->updateColor(Colors::RGB_G); line->render(); } if (status == kScaleZ || status == kScaleXZ || status == kScaleYZ || status == kScaleXYZ) { Shaders::maskShader->updateModel(modelZ); Shaders::maskShader->updateColor(Colors::RGB_B); line->render(); } Shaders::basicShader->updateModel(model); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::WHITE)); centerMesh->render(); // X, XY Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); Shaders::basicShader->updateModel(modelX); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); quadMesh->render(); // Y, XZ Shaders::basicShader->updateModel(modelY); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_G)); handleMesh->render(); Shaders::basicShader->updateModel(modelXZ); quadMesh->render(); // Z, YZ Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); Shaders::basicShader->updateModel(modelZ); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); quadMesh->render(); } void ScaleTool::onUpdate() { // Keep the tool status if the tool is active if (this->active) return; // Reset tool status setToolStatus(kIdle); SelectionTool::intersectedPoint = std::nullopt; // The intersection distance to find std::optional closestIntersectionDistance; // Look for edit tool intersections if the selection has a cframe std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; GlobalCFrame frame = *cframe; for (char status = kScaleX; status <= kScaleYZ; status++) { ExtendedTriangleMesh shape; switch (status) { case kScaleXYZ: shape = centerShape; break; case kScaleX: case kScaleY: case kScaleZ: shape = handleShape; break; case kScaleXY: case kScaleXZ: case kScaleYZ: shape = quadShape; break; default: continue; } frame.rotation = cframe->getRotation() * transformations[mapping[status - 1]]; std::optional distance = SelectionTool::intersect(frame, shape); if (!distance.has_value()) continue; if (!closestIntersectionDistance.has_value() || 0.0 < distance && distance < closestIntersectionDistance) { closestIntersectionDistance = distance; setToolStatus(status); } } if (closestIntersectionDistance.has_value()) SelectionTool::intersectedPoint = SelectionTool::ray.origin + SelectionTool::ray.direction * *closestIntersectionDistance; } void ScaleTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(ScaleTool::onMousePress)); dispatcher.dispatch(EVENT_BIND(ScaleTool::onMouseRelease)); dispatcher.dispatch(EVENT_BIND(ScaleTool::onMouseDrag)); } bool ScaleTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Auto select an entity if the selection status is idle, in that case no edit tool or entity is intersected auto status = getToolStatus(); if (status == kIdle) { // Find the intersected entity auto intersectedEntity = SelectionTool::getIntersectedEntity(); // If control is pressed, add to selection, but don't change the tool status if (event.getModifiers().isCtrlPressed()) { // No intersection causes nothing if (!intersectedEntity.has_value()) return false; } else { // Clear the selection before selecting the new entity SelectionTool::selection.clear(); // No intersection causes only deselection if (!intersectedEntity.has_value()) return false; // If an intersection is found, select it and behave like center edit setToolStatus(kTranslateC); SelectionTool::intersectedPoint = intersectedEntity->second; } SelectionTool::select(intersectedEntity->first); } // Set the selected point SelectionTool::selectedPoint = SelectionTool::intersectedPoint; // Set edit status to active this->active = true; return false; } bool ScaleTool::onMouseRelease(Engine::MouseReleaseEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; this->active = false; TranslationTool::magnet.selectedPart = nullptr; return false; }; bool ScaleTool::onMouseDrag(Engine::MouseDragEvent& event) { if (!this->active) return false; auto status = getToolStatus(); if (status == kIdle) return false; bool clamp = handler->keys[Engine::Keyboard::KEY_LEFT_ALT.getCode()]; std::unique_lock worldLock(*screen.worldMutex); switch (status) { case kScaleX: scaleAlongLine({ 1, 0, 0 }); break; case kScaleY: scaleAlongLine({ 0, 1, 0 }); break; case kScaleZ: scaleAlongLine({ 0, 0, 1 }); break; case kScaleXYZ: scaleXYZ(); break; case kScaleXY: scaleInPlane({ 0, 0, 1 }); break; case kScaleXZ: scaleInPlane({ 0, 1, 0 }); break; case kScaleYZ: scaleInPlane({ 1, 0, 0 }); break; case kTranslateC: TranslationTool::translateInPlane(screen.camera.cframe.rotation * Vec3(0, 0, 1), clamp, false); break; default: return false; } return true; } void ScaleTool::scaleAlongLine(const Vec3& direction) { // Closest point on ray1 (A + s * a) from ray2 (B + t * b). Ray1 is the ray from the parts' center in the direction of the edit tool, ray2 is the mouse ray. Directions a and b are normalized. Only s is calculated. if (SelectionTool::selection.empty()) return; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; // Rotate direction according to model rotation Ray ray1 = { cframe->getPosition(), cframe->localToRelative(direction) }; Ray ray2 = SelectionTool::ray; // Calculate s Vec3 c = ray2.origin - ray1.origin; double ab = ray1.direction * ray2.direction; double bc = ray2.direction * c; double ac = ray1.direction * c; double s = (ac - ab * bc) / (1.0 - ab * ab); // Translation, relative to tool intersection Vec3 translationCorrection = ray1.direction * (ray1.direction * (*SelectionTool::selectedPoint - cframe->getPosition())); Vec3 translation = s * ray1.direction - translationCorrection; *SelectionTool::selectedPoint += translation; SelectionTool::selection.scale(Vec3(1.0, 1.0, 1.0) + translation); } void ScaleTool::scaleInPlane(const Vec3& normal) { if (SelectionTool::selection.empty()) return; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; Vec3 direction = cframe->localToRelative(normal); double distance = (*SelectionTool::selectedPoint - SelectionTool::ray.origin) * direction / (SelectionTool::ray.direction * direction); Position planeIntersection = SelectionTool::ray.origin + SelectionTool::ray.direction * distance; Vec3 translation = planeIntersection - *SelectionTool::selectedPoint; *SelectionTool::selectedPoint += translation; SelectionTool::selection.scale(Vec3(1.0, 1.0, 1.0) + translation); } void ScaleTool::scaleXYZ() { if (SelectionTool::selection.empty()) return; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; std::optional hitbox = SelectionTool::selection.getHitbox(); if (!hitbox.has_value()) return; Vec3 direction = normalize(Vec3(screen.camera.cframe.position - cframe->getPosition())); double distance = (*SelectionTool::selectedPoint - SelectionTool::ray.origin) * direction / (SelectionTool::ray.direction * direction); Position planeIntersection = SelectionTool::ray.origin + SelectionTool::ray.direction * distance; double amount = lengthSquared(castPositionToVec3(*SelectionTool::selectedPoint)) - lengthSquared(castPositionToVec3(planeIntersection)); *SelectionTool::selectedPoint = planeIntersection; SelectionTool::selection.scale({ 1.0 + amount, 1.0 + amount, 1.0 + amount }); } } ================================================ FILE: application/picker/tools/scaleTool.h ================================================ #pragma once #include "../graphics/glfwUtils.h" #include "../engine/event/mouseEvent.h" #include "../engine/tool/stateTool.h" #include "ecs/components.h" namespace P3D::Application { class ScaleTool : public Engine::StateTool { private: enum SelectionToolStatus : Engine::ToolStatus { kIdle = 0, kScaleX = 1, kScaleY = 2, kScaleZ = 3, kScaleXYZ = 4, kScaleXY = 5, kScaleXZ = 6, kScaleYZ = 7, kTranslateC = 8 }; bool active = false; public: DEFINE_TOOL("Scale", "Scale entities by using the handles", Graphics::GLFW::Cursor::ARROW); ~ScaleTool() = default; void onRegister() override; void onDeregister() override; void onRender() override; void onUpdate() override; void onEvent(Engine::Event& event) override; bool onMousePress(Engine::MousePressEvent& event); bool onMouseRelease(Engine::MouseReleaseEvent& event); bool onMouseDrag(Engine::MouseDragEvent& event); void scaleAlongLine(const Vec3& direction); void scaleInPlane(const Vec3& normal); void scaleXYZ(); }; }; ================================================ FILE: application/picker/tools/selectionTool.cpp ================================================ #include "core.h" #include "selectionTool.h" #include "../engine/event/event.h" #include "../engine/event/mouseEvent.h" #include "../graphics/resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "../../view/screen.h" #include "../../application.h" #include "../../shader/shaders.h" #include "../../input/standardInputHandler.h" #include "../../ecs/components.h" #include "../../worlds.h" #include #include #include namespace P3D::Graphics { class TextureResource; } namespace P3D::Application { Ray SelectionTool::ray; Vec2 SelectionTool::mouse; Selection SelectionTool::selection; std::optional SelectionTool::selectedPoint; std::optional SelectionTool::intersectedPoint; void SelectionTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void SelectionTool::onDeregister() { // Remove texture } void SelectionTool::onRender() { using namespace Graphics; } void SelectionTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(SelectionTool::onMousePress)); } bool SelectionTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Multi selection check if (!event.getModifiers().isCtrlPressed()) clear(); // Single selection auto intersectedEntity = getIntersectedEntity(); if (intersectedEntity.has_value()) toggle(intersectedEntity->first); return false; } void SelectionTool::clear() { selection.clear(); } void SelectionTool::toggle(const Engine::Registry64::entity_type& entity) { selection.toggle(entity); } void SelectionTool::select(const Engine::Registry64::entity_type& entity) { selection.add(entity); } void SelectionTool::single(const Engine::Registry64::entity_type& entity) { selection.clear(); selection.add(entity); } std::optional> SelectionTool::getIntersectedCollider() { Engine::Registry64::entity_type intersectedEntity = 0; double closestIntersectionDistance = std::numeric_limits::max(); { auto view = screen.registry.view(); std::shared_lock worldReadLock(*screen.worldMutex); for (auto entity : view) { IRef hitbox = view.get(entity); IRef transform = view.get(entity); std::optional distance = intersect(transform->getCFrame(), hitbox); if (distance.has_value() && distance < closestIntersectionDistance) { closestIntersectionDistance = *distance; intersectedEntity = entity; } } } if (intersectedEntity == 0) return std::nullopt; Position intersection = ray.origin + ray.direction * closestIntersectionDistance; return std::make_pair(intersectedEntity, intersection); } std::optional> SelectionTool::getIntersectedEntity() { Engine::Registry64::entity_type intersectedEntity = Engine::Registry64::null_entity; double closestIntersectionDistance = std::numeric_limits::max(); { auto view = screen.registry.view(); std::shared_lock worldReadLock(*screen.worldMutex); for(auto entity : view) { IRef hitbox = view.get(entity); IRef transform = view.get(entity); std::optional distance = intersect(transform->getCFrame(), hitbox); if(distance.has_value() && distance < closestIntersectionDistance) { closestIntersectionDistance = *distance; intersectedEntity = entity; } } } if (intersectedEntity == 0) return std::nullopt; Position intersection = ray.origin + ray.direction * closestIntersectionDistance; return std::make_pair(intersectedEntity, intersection); } std::optional SelectionTool::intersect(const GlobalCFrame& cframe, IRef hitbox) { Shape shape = hitbox->getShape(); Vec3 relativePosition = cframe.getPosition() - ray.origin; double maxRadius = shape.getMaxRadius(); if (pointToLineDistanceSquared(ray.direction, relativePosition) > maxRadius * maxRadius) return std::nullopt; RayIntersectBoundsFilter filter(ray); if (hitbox->isPartAttached()) if (!filter(*hitbox->getPart())) return std::nullopt; double distance = shape.getIntersectionDistance(cframe.globalToLocal(ray.origin), cframe.relativeToLocal(ray.direction)); if (distance < 0.0 || distance == std::numeric_limits::max()) return std::nullopt; return distance; } std::optional SelectionTool::intersect(const GlobalCFrame& cframe, const Shape& shape) { Vec3 relativePosition = cframe.getPosition() - ray.origin; double maxRadius = shape.getMaxRadius(); if (pointToLineDistanceSquared(ray.direction, relativePosition) > maxRadius * maxRadius) return std::nullopt; double distance = shape.getIntersectionDistance(cframe.globalToLocal(ray.origin), cframe.relativeToLocal(ray.direction)); if (distance == 0.0 || distance == std::numeric_limits::max()) return std::nullopt; return distance; } std::optional SelectionTool::intersect(const GlobalCFrame& cframe, const ExtendedTriangleMesh& shape) { Vec3 relativePosition = cframe.getPosition() - ray.origin; //double maxRadius = shape.getMaxRadius(); //if (pointToLineDistanceSquared(ray.direction, relativePosition) > maxRadius * maxRadius) // return std::nullopt; double distance = shape.getIntersectionDistance(cframe.globalToLocal(ray.origin), cframe.relativeToLocal(ray.direction)); if (distance == 0.0 || distance == std::numeric_limits::max()) return std::nullopt; return distance; } }; ================================================ FILE: application/picker/tools/selectionTool.h ================================================ #pragma once #include "../graphics/glfwUtils.h" #include "../engine/event/mouseEvent.h" #include "../engine/tool/stateTool.h" #include "../engine/ecs/registry.h" #include #include #include "../../ecs/components.h" #include "../selection.h" #include struct ExtendedPart; namespace P3D::Application { class SelectionTool : public Engine::Tool { public: DEFINE_TOOL("Select", "Selects one or multiple entities.", Graphics::GLFW::Cursor::ARROW); static Ray ray; static Vec2 mouse; static Selection selection; static std::optional selectedPoint; static std::optional intersectedPoint; ~SelectionTool() override = default; void onRegister() override; void onDeregister() override; void onRender() override; void onEvent(Engine::Event& event) override; static bool onMousePress(Engine::MousePressEvent& event); static void clear(); static void select(const Engine::Registry64::entity_type& entity); static void toggle(const Engine::Registry64::entity_type& entity); static void single(const Engine::Registry64::entity_type& entity); static std::optional> getIntersectedEntity(); static std::optional> getIntersectedCollider(); static std::optional intersect(const GlobalCFrame& cframe, IRef hitbox); static std::optional intersect(const GlobalCFrame& cframe, const Shape& shape); static std::optional intersect(const GlobalCFrame& cframe, const Graphics::ExtendedTriangleMesh& shape); }; }; ================================================ FILE: application/picker/tools/springLinkTool.cpp ================================================ #include "core.h" #include "springLinkTool.h" #include "application.h" #include "selectionTool.h" #include "graphics/resource/textureResource.h" #include "Physics3D/softlinks/springLink.h" #include "util/resource/resourceManager.h" #include "view/screen.h" namespace P3D::Application { void SpringLinkTool::onRegister() { auto path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); } void SpringLinkTool::onDeregister() { // Remove texture } void SpringLinkTool::onSelect() { linkSelection(); } void SpringLinkTool::linkSelection() { if (SelectionTool::selection.size() < 2) return; IRef parentCollider = screen.registry.get(SelectionTool::selection[0]); if (parentCollider.invalid()) return; for (Engine::Registry64::entity_type& entity : SelectionTool::selection) { IRef childCollider = screen.registry.get(entity); if (childCollider.invalid()) continue; if (childCollider->part == parentCollider->part) continue; CFrame cframe = parentCollider->part->getCFrame().globalToLocal(childCollider->part->getCFrame()); AttachedPart part1 { cframe, parentCollider->part }; AttachedPart part2 { CFrame(), childCollider->part }; SpringLink* link = new SpringLink(part1, part2, 5.0, 1.0); screen.worldMutex->lock(); try { world.addLink(link); } catch (std::invalid_argument& error) { Log::debug(error.what()); } screen.worldMutex->unlock(); } } } ================================================ FILE: application/picker/tools/springLinkTool.h ================================================ #pragma once #include "engine/tool/buttonTool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class SpringLinkTool : public Engine::ButtonTool { public: DEFINE_TOOL("Spring Link", "Select two entities to create a spring link beteen.", Graphics::GLFW::Cursor::CROSSHAIR); ~SpringLinkTool() override = default; void onRegister() override; void onDeregister() override; void onSelect() override; static void linkSelection(); }; } ================================================ FILE: application/picker/tools/toolSpacing.h ================================================ #pragma once #include "engine/tool/tool.h" #include "graphics/glfwUtils.h" namespace P3D::Application { class ToolSpacing : public Engine::Tool { public: DEFINE_TOOL("Spacing", "", Graphics::GLFW::Cursor::CROSSHAIR); }; } ================================================ FILE: application/picker/tools/translationTool.cpp ================================================ #include "core.h" #include "translationTool.h" #include "worlds.h" #include "application.h" #include "selectionTool.h" #include "view/screen.h" #include "shader/shaders.h" #include #include #include #include "../graphics/extendedTriangleMesh.h" #include "../graphics/mesh/primitive.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "imgui/imgui.h" #include "input/standardInputHandler.h" #define PICKER_STRENGTH 100 #define PICKER_SPEED_STRENGTH 12 namespace P3D::Application { constexpr static Rotation transformations[] { Rotation::Predefined::Y_90, // X, XY Rotation::Predefined::X_270, // Y Rotation::Predefined::IDENTITY, // Z, YZ Rotation::Predefined::Z_270 // XZ }; constexpr static std::size_t mapping[] { 0, // X 1, // Y 2, // Z 2, // C 0, // XY 3, // XZ 2 // YZ }; static std::optional startPosition; static URef deltaLine = nullptr; static URef infiniteLine = nullptr; static URef quadMesh; static ExtendedTriangleMesh quadShape; static URef centerMesh; static ExtendedTriangleMesh centerShape; static URef handleMesh; static ExtendedTriangleMesh handleShape; MagnetForce TranslationTool::magnet(PICKER_STRENGTH, PICKER_SPEED_STRENGTH); static Polyhedron createArrow(float arrowHeadLength, float arrowHeadRadius, float stickRadius) { Vec2f contour[] { { 0.0f, stickRadius }, { 1.0f - arrowHeadLength, stickRadius }, { 1.0f - arrowHeadLength, arrowHeadRadius } }; return ShapeLibrary::createRevolvedShape(0.0f, contour, 3, 1.0f, 24); } void TranslationTool::onRegister() { using namespace Graphics; // Load icon std::string path = "../res/textures/icons/" + getName() + ".png"; ResourceManager::add(getName(), path); // Create alignment line deltaLine = std::make_unique(); infiniteLine = std::make_unique(); infiniteLine->resize(Vec3f(0, 0, -100000), Vec3f(0, 0, 100000)); // Create handle shapes handleShape = ExtendedTriangleMesh::generateSplitNormalsShape(createArrow(0.3f, 0.07f, 0.03f)); handleMesh = std::make_unique(handleShape); centerShape = ExtendedTriangleMesh::generateSmoothNormalsShape(ShapeLibrary::createSphere(0.13f, 3)); centerMesh = std::make_unique(centerShape); quadShape = ExtendedTriangleMesh::generateSplitNormalsShape(ShapeLibrary::createBox(0.02f, 0.25f, 0.25f).translated({0, 0.5, 0.5})); quadMesh = std::make_unique(quadShape); // Set idle status setToolStatus(kIdle); screen.world->addExternalForce(&TranslationTool::magnet); } void TranslationTool::onDeregister() { // Todo remove icon // Todo remove line centerMesh->close(); handleMesh->close(); quadMesh->close(); } void TranslationTool::onRender() { using namespace Graphics; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; //bool local = !ImGui::GetIO().KeyCtrl; bool local = handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; if (!local) cframe = GlobalCFrame(cframe->position); std::optional rootCFrame; if (SelectionTool::selection.isSingleSelection()) { IRef rootTransform = screen.registry.get(SelectionTool::selection.first().value()); if (rootTransform.valid() && rootTransform->hasOffset()) rootCFrame = rootTransform->getRootCFrame(); } Mat4f model = cframe->asMat4(); Mat4f modelX = model * joinDiagonal(Mat3f(transformations[0].asRotationMatrix()), 1.0f); Mat4f modelY = model * joinDiagonal(Mat3f(transformations[1].asRotationMatrix()), 1.0f); Mat4f modelZ = model * joinDiagonal(Mat3f(transformations[2].asRotationMatrix()), 1.0f); Mat4f modelXZ = model * joinDiagonal(Mat3f(transformations[3].asRotationMatrix()), 1.0f); auto status = getToolStatus(); if (status == kTranslateX || status == kTranslateXY || status == kTranslateXZ) { Shaders::maskShader->updateModel(modelX); Shaders::maskShader->updateColor(Colors::RGB_R); infiniteLine->render(); } if (status == kTranslateY || status == kTranslateXY || status == kTranslateYZ) { Shaders::maskShader->updateModel(modelY); Shaders::maskShader->updateColor(Colors::RGB_G); infiniteLine->render(); } if (status == kTranslateZ || status == kTranslateXZ || status == kTranslateYZ) { Shaders::maskShader->updateModel(modelZ); Shaders::maskShader->updateColor(Colors::RGB_B); infiniteLine->render(); } // Center Shaders::basicShader->updateModel(model); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::WHITE)); centerMesh->render(); // X, XY Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); Shaders::basicShader->updateModel(modelX); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); quadMesh->render(); // Y, XZ Shaders::basicShader->updateModel(modelY); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_G)); handleMesh->render(); Shaders::basicShader->updateModel(modelXZ); quadMesh->render(); // Z, YZ Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); Shaders::basicShader->updateModel(modelZ); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); quadMesh->render(); // Root XYZ if (rootCFrame.has_value()) { Mat4f rootModel = rootCFrame.value().asMat4WithPreScale(DiagonalMat3::IDENTITY() * 0.25); Mat4f rootModelX = rootModel * joinDiagonal(Mat3f(transformations[0].asRotationMatrix()), 1.0f); Mat4f rootModelY = rootModel * joinDiagonal(Mat3f(transformations[1].asRotationMatrix()), 1.0f); Mat4f rootModelZ = rootModel * joinDiagonal(Mat3f(transformations[2].asRotationMatrix()), 1.0f); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::WHITE)); Shaders::basicShader->updateModel(rootModel); centerMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_R)); Shaders::basicShader->updateModel(rootModelX); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_G)); Shaders::basicShader->updateModel(rootModelY); handleMesh->render(); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::RGB_B)); Shaders::basicShader->updateModel(rootModelZ); handleMesh->render(); Shaders::basicShader->updateModel(Mat4::IDENTITY()); Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::GRAY)); deltaLine->resize(castPositionToVec3f(rootCFrame.value().position), castPositionToVec3f(cframe.value().position)); deltaLine->render(); } // Delta line if (startPosition.has_value() && active) { Shaders::basicShader->updateMaterial(Graphics::Comp::Material(Colors::ORANGE)); Shaders::basicShader->updateModel(GlobalCFrame(startPosition.value()), DiagonalMat3::IDENTITY() * 0.5); Vec3f doubleRelativePosition = (castPositionToVec3f(cframe->position) - castPositionToVec3f(startPosition.value())) * 2.0f; deltaLine->resize(Vec3f(), doubleRelativePosition); deltaLine->render(); centerMesh->render(); } } void TranslationTool::onUpdate() { // Keep the tool status if the tool is active if (this->active) return; // Reset tool status setToolStatus(kIdle); SelectionTool::intersectedPoint = std::nullopt; // The intersection distance to find std::optional closestIntersectionDistance; // Look for edit tool intersections if the selection has a cframe std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; bool local = !handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; if (!local) cframe = GlobalCFrame(cframe->position); GlobalCFrame frame = *cframe; for (char status = kTranslateX; status <= kTranslateYZ; status++) { ExtendedTriangleMesh shape; switch (status) { case kTranslateC: shape = centerShape; break; case kTranslateX: case kTranslateY: case kTranslateZ: shape = handleShape; break; case kTranslateXY: case kTranslateXZ: case kTranslateYZ: shape = quadShape; break; default: continue; } frame.rotation = cframe->getRotation() * transformations[mapping[status - 1]]; std::optional distance = SelectionTool::intersect(frame, shape); if (!distance.has_value()) continue; if (!closestIntersectionDistance.has_value() || 0.0 < distance && distance < closestIntersectionDistance) { closestIntersectionDistance = distance; setToolStatus(status); } } if (closestIntersectionDistance.has_value()) SelectionTool::intersectedPoint = SelectionTool::ray.origin + SelectionTool::ray.direction * *closestIntersectionDistance; } void TranslationTool::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(TranslationTool::onMousePress)); dispatcher.dispatch(EVENT_BIND(TranslationTool::onMouseRelease)); dispatcher.dispatch(EVENT_BIND(TranslationTool::onMouseDrag)); } bool TranslationTool::onMousePress(Engine::MousePressEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Auto select an entity if the selection status is idle, in that case no edit tool or entity is intersected auto status = getToolStatus(); if (status == kIdle) { // Find the intersected entity auto intersectedEntity = SelectionTool::getIntersectedEntity(); // If control is pressed, add to selection, but don't change the tool status if (event.getModifiers().isCtrlPressed()) { // No intersection causes nothing if (!intersectedEntity.has_value()) return false; } else { // Clear the selection before selecting the new entity SelectionTool::selection.clear(); // No intersection causes only deselection if (!intersectedEntity.has_value()) return false; // If an intersection is found, select it and behave like center edit setToolStatus(kTranslateC); SelectionTool::intersectedPoint = intersectedEntity->second; } SelectionTool::select(intersectedEntity->first); } // Set the selected point SelectionTool::selectedPoint = SelectionTool::intersectedPoint; // Set start position std::optional selectionCFrame = SelectionTool::selection.getCFrame(); startPosition = selectionCFrame.has_value() ? std::make_optional(selectionCFrame->position) : std::nullopt; // Set edit status to active this->active = true; return false; } bool TranslationTool::onMouseRelease(Engine::MouseReleaseEvent& event) { using namespace Engine; if (event.getButton() != Mouse::LEFT) return false; // Reset magnet point this->magnet.selectedPart = nullptr; // Set inactive this->active = false; // Reset old position startPosition = std::nullopt; return false; } bool TranslationTool::onMouseDrag(Engine::MouseDragEvent& event) { if (!this->active) return false; auto status = getToolStatus(); if (status == kIdle) return false; bool clamp = handler->keys[Engine::Keyboard::KEY_LEFT_ALT.getCode()]; bool local = !handler->keys[Engine::Keyboard::KEY_LEFT_CONTROL.getCode()]; std::unique_lock worldWriteLock(*screen.worldMutex); switch (status) { case kTranslateX: translateAlongLine({ 1, 0, 0 }, clamp, local); break; case kTranslateY: translateAlongLine({ 0, 1, 0 }, clamp, local); break; case kTranslateZ: translateAlongLine({ 0, 0, 1 }, clamp, local); break; case kTranslateC: translateInPlane(screen.camera.cframe.rotation * Vec3(0, 0, 1), clamp, false); break; case kTranslateXY: translateInPlane({ 0, 0, 1 }, clamp, local); break; case kTranslateXZ: translateInPlane({ 0, 1, 0 }, clamp, local); break; case kTranslateYZ: translateInPlane({ 1, 0, 0 }, clamp, local); break; default: return false; } return true; } void TranslationTool::translateInPlane(const Vec3& normal, bool clamp, bool local) { if (SelectionTool::selection.empty()) return; Vec3 direction; std::optional cframe = SelectionTool::selection.getCFrame(); if (local) { if (!cframe.has_value()) return; direction = cframe->getRotation().localToGlobal(normal); } else { direction = normal; } double distance = (*SelectionTool::selectedPoint - SelectionTool::ray.origin) * direction / (SelectionTool::ray.direction * direction); Position planeIntersection = SelectionTool::ray.origin + SelectionTool::ray.direction * distance; if (isPaused()) { Vec3 translation = planeIntersection - *SelectionTool::selectedPoint; // Clamp to grid if (clamp) { if (local) { Vec3 localTranslation = cframe->getRotation().globalToLocal(translation); localTranslation = Vec3(round(localTranslation.x), round(localTranslation.y), round(localTranslation.z)); translation = cframe->getRotation().localToGlobal(localTranslation); } else { Vec3 resultingPosition = castPositionToVec3(cframe->translated(translation).getPosition()); Vec3 clampedPosition = Vec3(round(resultingPosition.x), round(resultingPosition.y), round(resultingPosition.z)); Vec3 difference = resultingPosition - clampedPosition; translation -= difference; } } *SelectionTool::selectedPoint += translation; SelectionTool::selection.translate(translation); } else { // Only allow single selection if (SelectionTool::selection.size() > 1) return; IRef transform = screen.registry.get(SelectionTool::selection[0]); if (transform.invalid()) return; if (!transform->isRootPart()) return; TranslationTool::magnet.selectedPart = std::get(transform->root); TranslationTool::magnet.magnetPoint = planeIntersection; } } void TranslationTool::translateAlongLine(const Vec3& direction, bool clamp, bool local) { // Closest point on ray1 (A + s * a) from ray2 (B + t * b). Ray1 is the ray from the parts' center in the direction of the edit tool, ray2 is the mouse ray. Directions a and b are normalized. Only s is calculated. if (SelectionTool::selection.empty()) return; std::optional cframe = SelectionTool::selection.getCFrame(); if (!cframe.has_value()) return; // Rotate direction according to model rotation Ray ray1 = { cframe->getPosition(), local ? cframe->localToRelative(direction) : direction }; Ray ray2 = SelectionTool::ray; // Calculate s Vec3 c = ray2.origin - ray1.origin; double ab = ray1.direction * ray2.direction; double bc = ray2.direction * c; double ac = ray1.direction * c; double s = (ac - ab * bc) / (1.0 - ab * ab); // Translation, relative to tool intersection Vec3 translationCorrection = ray1.direction * (ray1.direction * (*SelectionTool::selectedPoint - cframe->getPosition())); Vec3 translation = s * ray1.direction - translationCorrection; // Clamp to grid if (clamp) { if (local) { Vec3 localTranslation = cframe->getRotation().globalToLocal(translation); localTranslation = Vec3(round(localTranslation.x), round(localTranslation.y), round(localTranslation.z)); translation = cframe->getRotation().localToGlobal(localTranslation); } else { Vec3 resultingPosition = castPositionToVec3(cframe->translated(translation).getPosition()); Vec3 clampedPosition = Vec3(round(resultingPosition.x), round(resultingPosition.y), round(resultingPosition.z)); Vec3 difference = resultingPosition - clampedPosition; translation -= difference; } } *SelectionTool::selectedPoint += translation; SelectionTool::selection.translate(translation); } } ================================================ FILE: application/picker/tools/translationTool.h ================================================ #pragma once #include "../graphics/glfwUtils.h" #include "../engine/event/mouseEvent.h" #include "../engine/tool/stateTool.h" #include "ecs/components.h" #include namespace P3D::Application { class TranslationTool : public Engine::StateTool { private: enum SelectionToolStatus : Engine::ToolStatus { kIdle = 0, kTranslateX = 1, kTranslateY = 2, kTranslateZ = 3, kTranslateC = 4, kTranslateXY = 5, kTranslateXZ = 6, kTranslateYZ = 7, }; bool active = false; public: static MagnetForce magnet; DEFINE_TOOL("Translate", "Translate entities by clicking and dragging or using the handles", Graphics::GLFW::Cursor::ARROW); ~TranslationTool() override = default; void onRegister() override; void onDeregister() override; void onRender() override; void onUpdate() override; void onEvent(Engine::Event& event) override; bool onMousePress(Engine::MousePressEvent& event); bool onMouseRelease(Engine::MouseReleaseEvent& event); bool onMouseDrag(Engine::MouseDragEvent& event); static void translateInPlane(const Vec3& normal, bool clamp, bool local); static void translateAlongLine(const Vec3& direction, bool clamp, bool local); }; }; ================================================ FILE: application/resources.cpp ================================================ #include "core.h" #include "resources.h" #include "../util/resource/resourceDescriptor.h" #ifdef _MSC_VER #include "resource.h" ResourceDescriptor applicationResources[] { { IDR_SHADER1, "SHADER" }, { IDR_SHADER2, "SHADER" }, { IDR_SHADER3, "SHADER" }, { IDR_SHADER4, "SHADER" }, { IDR_SHADER5, "SHADER" }, { IDR_SHADER6, "SHADER" }, { IDR_SHADER7, "SHADER" }, { IDR_SHADER8, "SHADER" }, { IDR_SHADER9, "SHADER" }, { IDR_SHADER10, "SHADER" }, { IDR_SHADER11, "SHADER" }, { IDR_SHADER12, "SHADER" }, { IDR_SHADER13, "SHADER" }, { IDR_SHADER14, "SHADER" }, { IDR_SHADER15, "SHADER" }, { IDR_SHADER16, "SHADER" }, { IDR_SHADER17, "SHADER" }, { IDR_SHADER18, "SHADER" }, { IDR_OBJ1, "OBJ" }, { IDR_OBJ2, "OBJ" } }; #else ResourceDescriptor applicationResources[] { { "../res/shaders/basic.shader" , "SHADER" }, { "../res/shaders/vector.shader" , "SHADER" }, { "../res/shaders/origin.shader" , "SHADER" }, { "../res/shaders/font.shader" , "SHADER" }, { "../res/shaders/depth.shader" , "SHADER" }, { "../res/shaders/quad.shader" , "SHADER" }, { "../res/shaders/postprocess.shader" , "SHADER" }, { "../res/shaders/skybox.shader" , "SHADER" }, { "../res/shaders/mask.shader" , "SHADER" }, { "../res/shaders/point.shader" , "SHADER" }, { "../res/shaders/test.shader" , "SHADER" }, { "../res/shaders/blur.shader" , "SHADER" }, { "../res/shaders/line.shader" , "SHADER" }, { "../res/shaders/instance.shader" , "SHADER" }, { "../res/shaders/sky.shader" , "SHADER" }, { "../res/shaders/lighting.shader" , "SHADER" }, { "../res/shaders/debug.shader" , "SHADER" }, { "../res/shaders/depthbuffer.shader" , "SHADER" }, { "../res/models/stall.obj" , "OBJ" }, { "../res/models/sphere.obj" , "OBJ" } }; #endif ================================================ FILE: application/resources.h ================================================ #pragma once struct ResourceDescriptor; extern ResourceDescriptor applicationResources[]; #define BASIC_SHADER 0 #define VECTOR_SHADER 1 #define ORIGIN_SHADER 2 #define FONT_SHADER 3 #define DEPTH_SHADER 4 #define QUAD_SHADER 5 #define POSTPROCESS_SHADER 6 #define SKYBOX_SHADER 7 #define MASK_SHADER 8 #define POINT_SHADER 9 #define TEST_SHADER 10 #define BLUR_SHADER 11 #define LINE_SHADER 12 #define INSTANCE_SHADER 13 #define SKY_SHADER 14 #define LIGHTING_SHADER 15 #define DEBUG_SHADER 16 #define DEPTHBUFFER_SHADER 17 #define SPHERE_MODEL 18 #define STALL_MODEL 19 ================================================ FILE: application/shader/basicShader.cpp ================================================ #include "core.h" #include "basicShader.h" #include "../graphics/ecs/components.h" #include "extendedPart.h" namespace P3D::Application { void BasicShader::updatePart(const ExtendedPart& part) { bind(); updateTexture(false); updateModel(part.getCFrame(), DiagonalMat3f(part.hitbox.scale)); } void BasicShader::updateMaterial(const Graphics::Comp::Material& material) { bind(); setUniform("material.albedo", Vec4f(material.albedo)); setUniform("material.metalness", material.metalness); setUniform("material.roughness", material.roughness); setUniform("material.ambientOcclusion", material.ao); } void BasicShader::updateTexture(bool textured) { bind(); setUniform("material.albedoMap", 0); setUniform("material.normalMap", 1); setUniform("material.metalnessMap", 2); setUniform("material.roughnessMap", 3); setUniform("material.ambientOcclusionMap", 4); setUniform("material.textured", textured); } }; ================================================ FILE: application/shader/basicShader.h ================================================ #pragma once #include "shaderBase.h" #include "util/stringUtil.h" namespace P3D::Graphics::Comp { struct Material; } namespace P3D::Application { using namespace Graphics; struct ExtendedPart; struct BasicShader : public StandardMeshShaderBase, public BasicShaderBase { BasicShader() : ShaderResource("BasicShader", "../res/shaders/basic.shader"), StandardMeshShaderBase("BasicShader", "../res/shaders/basic.shader"), BasicShaderBase("BasicShader", "../res/shaders/basic.shader") {} void updatePart(const ExtendedPart& part); void updateTexture(bool textured); void updateMaterial(const Graphics::Comp::Material& material); }; struct InstanceShader : public InstancedMeshShaderBase, public BasicShaderBase { InstanceShader() : ShaderResource("InstanceShader", "../res/shaders/instance.shader"), InstancedMeshShaderBase("InstanceShader", "../res/shaders/instance.shader"), BasicShaderBase("InstanceShader", "../res/shaders/instance.shader") { InstanceShader::bind(); for (int i = 0; i < 31; i++) setUniform(Util::format("textures[%d]", i), i); } }; }; ================================================ FILE: application/shader/shaderBase.cpp ================================================ #include "core.h" #include "shaderBase.h" #include #include "ecs/components.h" namespace P3D::Application { #pragma region ProjectionShaderBase void ProjectionShaderBase::updateProjection(const Mat4f& viewMatrix, const Mat4f& projectionMatrix, const Position& viewPosition) { bind(); setUniform("viewMatrix", viewMatrix); setUniform("projectionMatrix", projectionMatrix); setUniform("viewPosition", viewPosition); } void ProjectionShaderBase::updateProjectionMatrix(const Mat4f& projectionMatrix) { bind(); setUniform("projectionMatrix", projectionMatrix); } void ProjectionShaderBase::updateViewMatrix(const Mat4f& viewMatrix) { bind(); setUniform("viewMatrix", viewMatrix); } void ProjectionShaderBase::updateViewPosition(const Position& viewPosition) { bind(); setUniform("viewPosition", viewPosition); } #pragma endregion #pragma region StandardMeshShaderBase void StandardMeshShaderBase::updateModel(const Mat4f& modelMatrix) { bind(); setUniform("modelMatrix", modelMatrix); } void StandardMeshShaderBase::updateModel(const GlobalCFrame& modelCFrame, const DiagonalMat3f& scale) { this->updateModel(modelCFrame.asMat4WithPreScale(scale)); } #pragma endregion #pragma region BasicShaderBase void BasicShaderBase::updateLightCount(std::size_t lightCount) { bind(); setUniform("lightCount", static_cast(lightCount)); } void BasicShaderBase::updateLight(std::size_t index, const Position& position, const Comp::Light& light) { bind(); std::string variable = "lights[" + std::to_string(static_cast(index)) + "]."; // position setUniform(variable + "position", position); // color setUniform(variable + "color", Vec3f(light.color)); // intensity setUniform(variable + "intensity", light.intensity); // attenuation.constant setUniform(variable + "attenuation.constant", light.attenuation.constant); // attenuation.linear setUniform(variable + "attenuation.linear", light.attenuation.linear); // attenuation.exponent setUniform(variable + "attenuation.exponent", light.attenuation.exponent); } void BasicShaderBase::updateSunDirection(const Vec3f& sunDirection) { bind(); setUniform("sunDirection", sunDirection); } void BasicShaderBase::updateSunColor(const Vec3f& sunColor) { bind(); setUniform("sunColor", sunColor); } void BasicShaderBase::updateGamma(float gamma) { bind(); setUniform("gamma", gamma); } void BasicShaderBase::updateHDR(float hdr) { bind(); setUniform("hdr", hdr); } void BasicShaderBase::updateExposure(float exposure) { bind(); setUniform("exposure", exposure); } #pragma endregion } ================================================ FILE: application/shader/shaderBase.h ================================================ #pragma once #include #include #include #include "../graphics/resource/shaderResource.h" namespace P3D::Application { namespace Comp { struct Light; } using namespace Graphics; struct ProjectionShaderBase : public virtual ShaderResource { ProjectionShaderBase(const std::string& name, const std::string& path, bool isPath = true) : ShaderResource(name, path, isPath) {} void updateProjection(const Mat4f& viewMatrix, const Mat4f& projectionMatrix, const Position& viewPosition); void updateProjectionMatrix(const Mat4f& projectionMatrix); void updateViewMatrix(const Mat4f& viewMatrix); void updateViewPosition(const Position& viewPosition); }; struct StandardMeshShaderBase : public ProjectionShaderBase { StandardMeshShaderBase(const std::string& name, const std::string& path, bool isPath = true) : ProjectionShaderBase(name, path, isPath) {} void updateModel(const Mat4f& modelMatrix); void updateModel(const GlobalCFrame& modelCFrame, const DiagonalMat3f& scale); }; struct InstancedMeshShaderBase : public ProjectionShaderBase { InstancedMeshShaderBase(const std::string& name, const std::string& path, bool isPath = true) : ProjectionShaderBase(name, path, isPath) {} }; struct BasicShaderBase : public virtual ShaderResource { BasicShaderBase(const std::string& name, const std::string& path, bool isPath = true) : ShaderResource(name, path, isPath) {} void updateSunDirection(const Vec3f& sunDirection); void updateSunColor(const Vec3f& sunColor); void updateGamma(float gamma); void updateHDR(float hdr); void updateExposure(float exposure); void updateLightCount(std::size_t lightCount); void updateLight(std::size_t index, const Position& position, const Comp::Light& light); }; } ================================================ FILE: application/shader/shaders.cpp ================================================ #include "core.h" #include "shaders.h" #include "extendedPart.h" #include "../graphics/texture.h" #include "../graphics/renderer.h" #include "../util/resource/resourceManager.h" #include namespace P3D::Application { namespace Shaders { SRef basicShader; SRef depthShader; SRef vectorShader; SRef originShader; SRef fontShader; SRef postProcessShader; SRef skyboxShader; SRef pointShader; SRef testShader; SRef lineShader; SRef maskShader; SRef instanceShader; SRef lightingShader; SRef skyShader; SRef debugShader; SRef depthBufferShader; void onInit() { // GShader init basicShader = std::make_shared(); depthShader = std::make_shared(); vectorShader = std::make_shared(); fontShader = std::make_shared(); originShader = std::make_shared(); postProcessShader = std::make_shared(); skyboxShader = std::make_shared(); pointShader = std::make_shared(); testShader = std::make_shared(); lineShader = std::make_shared(); maskShader = std::make_shared(); instanceShader = std::make_shared(); skyShader = std::make_shared(); lightingShader = std::make_shared(); debugShader = std::make_shared(); depthBufferShader = std::make_shared(); ResourceManager::add(basicShader.get()); ResourceManager::add(depthShader.get()); ResourceManager::add(vectorShader.get()); ResourceManager::add(fontShader.get()); ResourceManager::add(originShader.get()); ResourceManager::add(postProcessShader.get()); ResourceManager::add(skyboxShader.get()); ResourceManager::add(pointShader.get()); ResourceManager::add(testShader.get()); ResourceManager::add(lineShader.get()); ResourceManager::add(maskShader.get()); ResourceManager::add(instanceShader.get()); ResourceManager::add(skyShader.get()); ResourceManager::add(lightingShader.get()); ResourceManager::add(debugShader.get()); ResourceManager::add(depthBufferShader.get()); } void onClose() { basicShader->close(); depthShader->close(); vectorShader->close(); fontShader->close(); originShader->close(); skyboxShader->close(); postProcessShader->close(); pointShader->close(); testShader->close(); lineShader->close(); maskShader->close(); instanceShader->close(); skyShader->close(); lightingShader->close(); debugShader->close(); depthBufferShader->close(); } } // SkyboxShader void SkyboxShader::updateCubeMap(Graphics::CubeMap* skybox) { bind(); setUniform("skyboxTexture", skybox->getUnit()); } void SkyboxShader::updateLightDirection(const Vec3f& lightDirection) { bind(); setUniform("lightDirection", lightDirection); } // MeskShader void MaskShader::updateColor(const Color& color) { bind(); setUniform("color", Vec4f(color)); } // DepthShader void DepthShader::updateLight(const Mat4f& lightMatrix) { bind(); setUniform("lightMatrix", lightMatrix); } // PostProcessShader void PostProcessShader::updateTexture(SRef texture) { bind(); texture->bind(); setUniform("textureSampler", texture->getUnit()); } void PostProcessShader::updateTexture(SRef texture) { bind(); texture->bind(); setUniform("textureSampler", texture->getUnit()); } // OriginShader void OriginShader::updateProjection(const Mat4f& viewMatrix, const Mat4f& rotatedViewMatrix, const Mat4f& projectionMatrix, const Mat4f& orthoMatrix, const Position& viewPosition) { bind(); setUniform("viewMatrix", viewMatrix); setUniform("rotatedViewMatrix", rotatedViewMatrix); setUniform("projectionMatrix", projectionMatrix); setUniform("orthoMatrix", orthoMatrix); setUniform("viewPosition", viewPosition); } // FontShader void FontShader::updateColor(const Color& color) { bind(); setUniform("color", Vec4f(color)); } void FontShader::updateProjection(const Mat4f& projectionMatrix) { bind(); setUniform("projectionMatrix", projectionMatrix); } void FontShader::updateTexture(SRef texture) { bind(); texture->bind(); setUniform("text", texture->getUnit()); } // SkyShader void SkyShader::updateTime(float time) { bind(); setUniform("time", time); } // DepthBufferShader void DepthBufferShader::updateDepthMap(GLID unit, GLID id) { bind(); Renderer::activeTexture(unit); Renderer::bindTexture2D(id); setUniform("depthMap", unit); } void DepthBufferShader::updatePlanes(float near, float far) { bind(); setUniform("far", far); setUniform("near", near); } }; ================================================ FILE: application/shader/shaders.h ================================================ #pragma once #include #include "../graphics/shader/shader.h" #include "../graphics/resource/shaderResource.h" #include "../graphics/gui/color.h" #include "../shader/basicShader.h" #include "../shader/shaderBase.h" namespace P3D::Graphics { class HDRTexture; class Texture; class CubeMap; }; namespace P3D::Application { using namespace Graphics; struct Light; struct Material; struct ExtendedPart; struct DepthBufferShader : public ShaderResource { DepthBufferShader() : ShaderResource("DepthBufferShader", "../res/shaders/depthbuffer.shader") {} void updateDepthMap(GLID unit, GLID id); void updatePlanes(float near, float far); }; struct DebugShader : public StandardMeshShaderBase { DebugShader() : ShaderResource("DebugShader", "../res/shaders/debug.shader"), StandardMeshShaderBase("DebugShader", "../res/shaders/debug.shader") {} }; struct SkyboxShader : public ProjectionShaderBase { SkyboxShader() : ShaderResource("SkyboxShader", "../res/shaders/skybox.shader"), ProjectionShaderBase("SkyboxShader", "../res/shaders/skybox.shader") {} void updateCubeMap(CubeMap* skybox); void updateLightDirection(const Vec3f& lightDirection); }; struct MaskShader : public StandardMeshShaderBase { MaskShader() : ShaderResource("MaskShader", "../res/shaders/mask.shader"), StandardMeshShaderBase("MaskShader", "../res/shaders/mask.shader") {} void updateColor(const Color& color); }; struct DepthShader : public StandardMeshShaderBase { DepthShader() : ShaderResource("DepthShader", "../res/shaders/depth.shader"), StandardMeshShaderBase("DepthShader", "../res/shaders/depth.shader") {} void updateLight(const Mat4f& lightMatrix); }; struct PostProcessShader : public ShaderResource { PostProcessShader() : ShaderResource("PostProcessShader", "../res/shaders/postProcess.shader") {} void updateTexture(SRef texture); void updateTexture(SRef texture); }; struct OriginShader : public ShaderResource { OriginShader() : ShaderResource("OriginShader", "../res/shaders/origin.shader") {} void updateProjection(const Mat4f& viewMatrix, const Mat4f& rotatedViewMatrix, const Mat4f& projectionMatrix, const Mat4f& orthoMatrix, const Position& viewPosition); }; struct FontShader : public ShaderResource { FontShader() : ShaderResource("FontShader", "../res/shaders/font.shader") {} void updateColor(const Color& color); void updateProjection(const Mat4f& projectionMatrix); void updateTexture(SRef texture); }; struct VectorShader : public ProjectionShaderBase { VectorShader() : ShaderResource("VectorShader", "../res/shaders/vector.shader"), ProjectionShaderBase("VectorShader", "../res/shaders/vector.shader") {} }; struct PointShader : public ProjectionShaderBase { PointShader() : ShaderResource("PointShader", "../res/shaders/point.shader"), ProjectionShaderBase("PointShader", "../res/shaders/point.shader") {} }; struct TestShader : public StandardMeshShaderBase { TestShader() : ShaderResource("TestShader", "../res/shaders/test.shader"), StandardMeshShaderBase("TestShader", "../res/shaders/test.shader") {} }; struct LineShader : public ProjectionShaderBase { LineShader() : ShaderResource("LineShader", "../res/shaders/line.shader"), ProjectionShaderBase("LineShader", "../res/shaders/line.shader") {} }; struct SkyShader : public ProjectionShaderBase { SkyShader() : ShaderResource("SkyShader", "../res/shaders/sky.shader"), ProjectionShaderBase("SkyShader", "../res/shaders/sky.shader") {} void updateTime(float time); }; struct LightingShader : public BasicShaderBase { LightingShader() : ShaderResource("LightingShader", "../res/shaders/lighting.shader"), BasicShaderBase("LightingShader", "../res/shaders/lighting.shader") {} }; namespace Shaders { extern SRef basicShader; extern SRef depthShader; extern SRef vectorShader; extern SRef originShader; extern SRef fontShader; extern SRef postProcessShader; extern SRef skyboxShader; extern SRef pointShader; extern SRef testShader; extern SRef lineShader; extern SRef instanceShader; extern SRef maskShader; extern SRef skyShader; extern SRef lightingShader; extern SRef debugShader; extern SRef depthBufferShader; void onInit(); void onClose(); } }; ================================================ FILE: application/view/camera.cpp ================================================ #include "core.h" #include "camera.h" #include "screen.h" #include "application.h" #include "../graphics/gui/guiUtils.h" #include "../engine/event/event.h" #include "../engine/event/mouseEvent.h" #include "../engine/event/keyEvent.h" #include "../engine/input/keyboard.h" #include "../engine/options/keyboardOptions.h" #include "../extendedPart.h" #include "worlds.h" #include "picker/tools/selectionTool.h" namespace P3D::Application { Camera::Camera(const Position& position, const Rotation& rotation) : cframe(GlobalCFrame(position, rotation)), velocity(0.35), angularVelocity(0.04), flying(true) { onUpdate(); }; Camera::Camera() : cframe(GlobalCFrame()), velocity(0.35), angularVelocity(0.04), flying(true) { onUpdate(); }; void Camera::setPosition(Position position) { flags |= ViewDirty; cframe.position = position; } void Camera::setPosition(Fix<32> x, Fix<32> y, Fix<32> z) { setPosition(Position(x, y, z)); } void Camera::setRotation(const Rotation& rotation) { flags |= ViewDirty; cframe.rotation = rotation; } void Camera::setRotation(double alpha, double beta, double gamma) { this->setRotation(Rotation::fromEulerAngles(alpha, beta, gamma)); } void Camera::setRotation(Vec3 rotation) { setRotation(rotation.x, rotation.y, rotation.z); } Mat4f Camera::getViewRotation() { return joinDiagonal(Mat3f(cframe.rotation.asRotationMatrix().transpose()), 1.0f); } void Camera::rotate(Screen& screen, double dalpha, double dbeta, double dgamma, bool leftDragging, bool accelerating) { flags |= ViewDirty; rotating = accelerating; cframe.rotation = Rotation::rotY(currentAngularVelocity * dbeta) * cframe.rotation * Rotation::rotX(currentAngularVelocity * dalpha); /*if (leftDragging) { screen.world->asyncModification([&screen] () { // TODO Picker::moveGrabbedEntityLateral(screen); }); }*/ // Accelerate camera rotation if (accelerating) currentAngularVelocity += angularVelocity * angularVelocityIncrease; // Clamp camera angular velocity if (currentAngularVelocity > angularVelocity) currentAngularVelocity = angularVelocity; // Save last rotation lastRotation = Vec3(dalpha, dbeta, dgamma); // Save left dragging value wasLeftDragging = leftDragging; } void Camera::rotate(Screen& screen, Vec3 delta, bool leftDragging, bool accelerating) { rotate(screen, delta.x, delta.y, delta.z, leftDragging, accelerating); } void Camera::move(Screen& screen, double dx, double dy, double dz, bool leftDragging, bool accelerating) { flags |= ViewDirty; moving = accelerating; (*screen.eventHandler.cameraMoveHandler) (screen, this, Vec3(dx, dy, dz)); Vec3 translation = Vec3(); if (dx != 0) { Vec3 cameraRotationX = cframe.rotation * Vec3(1, 0, 0); Vec3 translationX = normalize(Vec3(cameraRotationX.x, 0, cameraRotationX.z)) * dx; translation += translationX; } if (dy != 0) { Vec3 translationY = Vec3(0, dy, 0); translation += translationY; } if (dz != 0) { Vec3 cameraRotationZ = cframe.rotation * Vec3(0, 0, 1); Vec3 translationZ = normalize(Vec3(cameraRotationZ.x, 0, cameraRotationZ.z)) * dz; translation += translationZ; } translation *= currentVelocity; cframe += translation; if (wasLeftDragging) SelectionTool::selection.translate(translation); // Accelerate camera movement if (accelerating) currentVelocity += velocity * velocityIncrease; // Clamp camera velocity if (currentVelocity > velocity) currentVelocity = velocity; // Save last direction lastDirection = Vec3(dx, dy, dz); // Save left dragging value wasLeftDragging = leftDragging; } void Camera::move(Screen& screen, Vec3 delta, bool leftDragging, bool accelerating) { move(screen, delta.x, delta.y, delta.z, leftDragging, accelerating); } bool Camera::onKeyRelease(Engine::KeyReleaseEvent& event) { using namespace Engine; Key key = event.getKey(); if (key == KeyboardOptions::Move::forward || key == KeyboardOptions::Move::backward || key == KeyboardOptions::Move::left || key == KeyboardOptions::Move::right || key == KeyboardOptions::Move::ascend || key == KeyboardOptions::Move::descend) { moving = false; } if (key == KeyboardOptions::Rotate::left || key == KeyboardOptions::Rotate::right || key == KeyboardOptions::Rotate::up || key == KeyboardOptions::Rotate::down) { rotating = false; } return false; } bool Camera::onMouseDrag(Engine::MouseDragEvent& event) { double dmx = event.getNewX() - event.getOldX(); double dmy = event.getNewY() - event.getOldY(); // Camera rotating if (event.isRightDragging()) rotate(screen, -dmy * 0.1, -dmx * 0.1, 0, event.isLeftDragging()); // Camera moving if (event.isMiddleDragging()) move(screen, dmx * 0.2, -dmy * 0.2, 0, event.isLeftDragging()); return false; } bool Camera::onMouseScroll(Engine::MouseScrollEvent& event) { velocity = Graphics::GUI::clamp(velocity * (1 + 0.2 * event.getYOffset()), 0.001, 100); thirdPersonDistance -= event.getYOffset(); return true; }; void Camera::onEvent(Engine::Event& event) { using namespace Engine; EventDispatcher dispatcher(event); dispatcher.dispatch(EVENT_BIND(Camera::onMouseDrag)); dispatcher.dispatch(EVENT_BIND(Camera::onMouseScroll)); dispatcher.dispatch(EVENT_BIND(Camera::onKeyRelease)); } void Camera::onUpdate(float fov, float aspect, float znear, float zfar) { this->fov = fov; this->aspect = aspect; this->znear = znear; this->zfar = zfar; flags |= ProjectionDirty; onUpdate(); } void Camera::onUpdate(float aspect) { this->aspect = aspect; flags |= ProjectionDirty; orthoMatrix = ortho(-aspect, aspect, -1.0f, 1.0f, -1000.0f, 1000.0f); onUpdate(); } void Camera::onUpdate() { if (currentVelocity != 0) { // Clamp camera velocity if (currentVelocity < 0) { currentVelocity = 0; wasLeftDragging = false; } else { // Decellerate camera movement if the camera stops moving if (!moving) { move(screen, lastDirection, wasLeftDragging, false); currentVelocity -= velocity * velocityIncrease; } } } if (currentAngularVelocity != 0) { // Clamp camera rotation if (currentAngularVelocity < 0) { currentAngularVelocity = 0; wasLeftDragging = false; } else { // Decellerate camera rotation if the camera stops rotating if (!rotating) { rotate(screen, lastRotation, wasLeftDragging, false); currentAngularVelocity -= angularVelocity * angularVelocityIncrease; } } } // Attach the camera to the attached part, if there is anys if (!flying && attachment != nullptr) { Vec3 vertical = Vec3(0, 1, 0); Vec3 forward = getForwardDirection(); // Sinus angle between camera direction and the xz plane double sinAlpha = vertical * forward; double dy = thirdPersonDistance * sinAlpha; double dz = sqrt(thirdPersonDistance * thirdPersonDistance - dy * dy); Vec3 translationZ = normalize(-Vec3(forward.x, 0, forward.z)) * dz; Vec3 translationY = Vec3(0, -dy, 0); Vec3 translation = translationY + translationZ; this->cframe.position = attachment->getCFrame().position + translation; flags |= ViewDirty; } // Update projection matrix if (flags & ProjectionDirty) { flags ^= ProjectionDirty; projectionMatrix = perspective(fov, aspect, znear, zfar); invertedProjectionMatrix = ~projectionMatrix; } // Update view matrix if (flags & ViewDirty) { flags ^= ViewDirty; viewMatrix = translate(getViewRotation(), -Vec3f(float(cframe.position.x), float(cframe.position.y), float(cframe.position.z))); invertedViewMatrix = ~viewMatrix; } } double Camera::getRightOffsetAtZ1() const { return tan(double(fov) / 2) * aspect; } double Camera::getTopOffsetAtZ1() const { return tan(double(fov) / 2); } }; ================================================ FILE: application/view/camera.h ================================================ #pragma once #include #include namespace P3D::Engine { struct Event; class MouseDragEvent; class MouseScrollEvent; class KeyReleaseEvent; }; namespace P3D::Application { struct ExtendedPart; struct Camera; class Screen; struct Camera { private: enum CameraFlags : char { // No flags None = 0 << 0, // Whether the view matrix needs to be recalculated ViewDirty = 1 << 0, // Whether the projection matrix needs to be recalculated ProjectionDirty = 1 << 1 }; char flags = None; double velocityIncrease = 0.5; double currentVelocity = 0; Vec3 lastDirection = Vec3(); bool moving = false; double angularVelocityIncrease = 0.5; double currentAngularVelocity = 0; Vec3 lastRotation = Vec3(); bool rotating = false; bool wasLeftDragging = false; bool onMouseScroll(Engine::MouseScrollEvent& event); bool onMouseDrag(Engine::MouseDragEvent& event); bool onKeyRelease(Engine::KeyReleaseEvent& event); public: GlobalCFrame cframe; double velocity; double angularVelocity; bool flying; double thirdPersonDistance = 6.0; float fov; float znear; float zfar; float aspect; Mat4f viewMatrix; Mat4f invertedViewMatrix; Mat4f projectionMatrix; Mat4f invertedProjectionMatrix; Mat4f orthoMatrix; ExtendedPart* attachment = nullptr; void onUpdate(); void onUpdate(float fov, float aspect, float znear, float zfar); void onUpdate(float aspect); void onEvent(Engine::Event& event); Camera(const Position& position, const Rotation& rotation); Camera(); void setPosition(Position position); void setPosition(Fix<32> x, Fix<32> y, Fix<32> z); void setRotation(const Rotation& rotation); void setRotation(double alpha, double beta, double gamma); void setRotation(Vec3 rotation); void rotate(Screen& screen, double dalpha, double dbeta, double dgamma, bool leftDragging, bool accelerating = true); void rotate(Screen& screen, Vec3 delta, bool leftDragging, bool accelerating = true); void move(Screen& screen, double dx, double dy, double dz, bool leftDragging, bool accelerating = true); void move(Screen& screen, Vec3 delta, bool leftDragging, bool accelerating = true); Mat4f getViewRotation(); double getRightOffsetAtZ1() const; double getTopOffsetAtZ1() const; inline Vec3 getForwardDirection() const { return -cframe.rotation.getZ(); } inline Vec3 getBackwardDirection() const { return cframe.rotation.getZ(); } inline Vec3 getUpDirection() const { return cframe.rotation.getY(); } inline Vec3 getDownDirection() const { return -cframe.rotation.getY(); } inline Vec3 getLeftDirection() const { return -cframe.rotation.getX(); } inline Vec3 getRightDirection() const { return cframe.rotation.getX(); } }; }; ================================================ FILE: application/view/debugFrame.cpp ================================================ #include "core.h" #include "debugFrame.h" #include "imgui/imgui.h" #include "../graphics/debug/visualDebug.h" namespace P3D::Application { void DebugFrame::onInit(Engine::Registry64& registry) { } void DebugFrame::onRender(Engine::Registry64& registry) { ImGui::Begin("Debug"); using namespace P3D::Debug; using namespace P3D::Graphics::VisualDebug; ImGui::SetNextTreeNodeOpen(true); if (ImGui::TreeNode("Vectors")) { ImGui::Checkbox("Info", &vectorDebugEnabled[INFO_VEC]); ImGui::Checkbox("Position", &vectorDebugEnabled[POSITION]); ImGui::Checkbox("Velocity", &vectorDebugEnabled[VELOCITY]); ImGui::Checkbox("Acceleration", &vectorDebugEnabled[ACCELERATION]); ImGui::Checkbox("Moment", &vectorDebugEnabled[MOMENT]); ImGui::Checkbox("Force", &vectorDebugEnabled[FORCE]); ImGui::Checkbox("Angular impulse", &vectorDebugEnabled[ANGULAR_IMPULSE]); ImGui::Checkbox("Impulse", &vectorDebugEnabled[IMPULSE]); ImGui::TreePop(); } ImGui::SetNextTreeNodeOpen(true); if (ImGui::TreeNode("Points")) { ImGui::Checkbox("Info", &pointDebugEnabled[INFO_POINT]); ImGui::Checkbox("Center of mass", &pointDebugEnabled[CENTER_OF_MASS]); ImGui::Checkbox("Intersections", &pointDebugEnabled[INTERSECTION]); ImGui::TreePop(); } ImGui::SetNextTreeNodeOpen(true); if (ImGui::TreeNode("Render")) { ImGui::Checkbox("Render pies", &renderPiesEnabled); if (ImGui::Button("Switch collision sphere render mode")) colissionSpheresMode = static_cast((static_cast(colissionSpheresMode) + 1) % 3); ImGui::TreePop(); } ImGui::End(); } } ================================================ FILE: application/view/debugFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Application { struct DebugFrame { static bool renderSpheres; static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/ecsFrame.cpp ================================================ #include "core.h" #include "ecsFrame.h" #define IMGUI_DEFINE_MATH_OPERATORS #include "application.h" #include "screen.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" #include "ecs/components.h" #include "../graphics/resource/textureResource.h" #include "../graphics/meshRegistry.h" #include "../util/resource/resourceManager.h" #include "graphics/gui/imgui/imguiExtension.h" #include "Physics3D/hardconstraints/fixedConstraint.h" #include "Physics3D/hardconstraints/motorConstraint.h" #include "Physics3D/softlinks/elasticLink.h" #include "Physics3D/softlinks/magneticLink.h" #include "Physics3D/softlinks/springLink.h" #include "picker/tools/alignmentLinkTool.h" #include "picker/tools/attachmentTool.h" #include "picker/tools/elasticLinkTool.h" #include "picker/tools/fixedConstraintTool.h" #include "picker/tools/magneticLinkTool.h" #include "picker/tools/motorConstraintTool.h" #include "picker/tools/selectionTool.h" #include "picker/tools/springLinkTool.h" #include "util/stringUtil.h" namespace P3D::Application { Graphics::TextureResource* folderIcon; Graphics::TextureResource* openFolderIcon; Graphics::TextureResource* entityIcon; Graphics::TextureResource* colliderIcon; Graphics::TextureResource* terrainIcon; Graphics::TextureResource* mainColliderIcon; Graphics::TextureResource* childColliderIcon; Graphics::TextureResource* attachmentsIcon; Graphics::TextureResource* hardConstraintsIcon; Graphics::TextureResource* softLinksIcon; Graphics::TextureResource* cframeIcon; Graphics::TextureResource* cubeClassIcon; Graphics::TextureResource* sphereClassIcon; Graphics::TextureResource* cylinderClassIcon; Graphics::TextureResource* cornerClassIcon; Graphics::TextureResource* wedgeClassIcon; Graphics::TextureResource* polygonClassIcon; Graphics::TextureResource* shownIcon; Graphics::TextureResource* hiddenIcon; Graphics::TextureResource* physicsIcon; Graphics::TextureResource* addIcon; static int nodeIndex = 0; static void* selectedNode = nullptr; static Engine::Registry64::entity_type selectedNodeEntity = Engine::Registry64::null_entity; static ImGuiTreeNodeFlags baseFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; static ImGuiTreeNodeFlags leafFlags = baseFlags | ImGuiTreeNodeFlags_Leaf; static GLID getColliderIcon(IRef collider) { if (collider.invalid()) return -1; IRef shape = collider->part->hitbox.baseShape; if (shape.get() == &CubeClass::instance) return cubeClassIcon->getID(); if (shape.get() == &SphereClass::instance) return sphereClassIcon->getID(); if (shape.get() == &CornerClass::instance) return cornerClassIcon->getID(); if (shape.get() == &WedgeClass::instance) return wedgeClassIcon->getID(); if (shape.get() == &CylinderClass::instance) return cylinderClassIcon->getID(); return polygonClassIcon->getID(); } static GLID getMeshIcon(IRef mesh) { using namespace Graphics::MeshRegistry; if (mesh.invalid()) return -1; std::size_t shape = mesh->id; if (shape == getID(&CubeClass::instance)) return cubeClassIcon->getID(); if (shape == getID(&SphereClass::instance)) return sphereClassIcon->getID(); if (shape == getID(&CornerClass::instance)) return cornerClassIcon->getID(); if (shape == getID(&WedgeClass::instance)) return wedgeClassIcon->getID(); if (shape == getID(&CylinderClass::instance)) return cylinderClassIcon->getID(); return polygonClassIcon->getID(); } static void HeaderNode() { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; float arrowWidth = g.FontSize; float buttonSize = g.FontSize + g.Style.FramePadding.y * 2; float buttonMargin = g.Style.ItemInnerSpacing.x; float buttonPadding = 5.0; ImVec2 pos = window->DC.CursorPos; ImVec2 startPos = ImVec2(pos.x - window->DC.Indent.x + g.Style.WindowPadding.x, pos.y); ImVec2 endPos = ImVec2(startPos.x + ImGui::GetContentRegionMax().x, startPos.y + buttonSize); ImRect visibilityButton(startPos, ImVec2(startPos.x + buttonSize - buttonPadding * 2, startPos.y + buttonSize)); ImRect arrowButton(ImVec2(pos.x + visibilityButton.GetWidth() + buttonMargin, pos.y), ImVec2(pos.x + visibilityButton.GetWidth() + buttonMargin + arrowWidth, pos.y + buttonSize)); ImRect iconButton(ImVec2(arrowButton.Max.x + buttonMargin, pos.y), ImVec2(arrowButton.Max.x + buttonMargin + buttonSize, pos.y + buttonSize)); ImRect colliderButton(ImVec2(iconButton.Max.x + buttonMargin, pos.y), ImVec2(iconButton.Max.x + buttonMargin + buttonSize - buttonPadding * 2, pos.y + buttonSize)); ImRect mainButton(ImVec2(colliderButton.Max.x, pos.y), endPos); ImVec2 textPos = ImVec2(mainButton.Min.x + buttonMargin * 3, pos.y + g.Style.FramePadding.y); ImRect totalSize = ImRect(startPos, endPos); // Background window->DrawList->AddRectFilled(totalSize.Min - ImVec2(0, GImGui->Style.ItemSpacing.y / 2), totalSize.Max + ImVec2(0, GImGui->Style.ItemSpacing.y / 2), ImGui::GetColorU32(ImGuiCol_TabUnfocusedActive)); // Icons ImGui::DrawIcon(shownIcon->getID(), visibilityButton.Min + ImVec2(0, buttonPadding), visibilityButton.Max - ImVec2(0, buttonPadding)); ImGui::DrawIcon(entityIcon->getID(), iconButton.Min + ImVec2(buttonPadding, buttonPadding), iconButton.Max - ImVec2(buttonPadding, buttonPadding)); ImGui::DrawIcon(physicsIcon->getID(), colliderButton.Min + ImVec2(0, buttonPadding), colliderButton.Max - ImVec2(0, buttonPadding)); // Lines ImGui::DrawLineBetween(visibilityButton, arrowButton, g.Style.ItemSpacing.y); ImGui::DrawLineBetween(iconButton, colliderButton, g.Style.ItemSpacing.y); ImGui::DrawLineBetween(colliderButton, mainButton, g.Style.ItemSpacing.y); // Text ImGui::RenderText(textPos, "Name"); ImGui::ItemSize(totalSize, g.Style.FramePadding.y); ImGui::ItemAdd(totalSize, 0); } template static bool IconTreeNode(ImU32 id, Engine::Registry64& registry, const char* label, ImGuiTreeNodeFlags flags, GLID mainIcon, bool selected, const OnPressed& onPressed, Engine::Registry64::entity_type entity = 0) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; // ID ImGuiContext& g = *GImGui; ImU32 mainId = id == 0 ? window->GetID(label) : id; ImU32 arrowId = window->GetID("Arrow"); ImU32 colliderId = window->GetID("Collider"); ImU32 meshId = window->GetID("Mesh"); // Constants float arrowWidth = g.FontSize; float buttonSize = g.FontSize + g.Style.FramePadding.y * 2; float buttonMargin = g.Style.ItemInnerSpacing.x; float arrowOffset = std::abs(g.FontSize - arrowWidth) / 2.0f; float buttonPadding = 5.0; // Positions ImVec2 pos = window->DC.CursorPos; ImVec2 startPos = ImVec2(pos.x - window->DC.Indent.x + g.Style.WindowPadding.x, pos.y); ImVec2 endPos = ImVec2(startPos.x + ImGui::GetContentRegionMax().x, startPos.y + buttonSize); ImRect visibilityButton(startPos, ImVec2(startPos.x + buttonSize - buttonPadding * 2, startPos.y + buttonSize)); ImRect arrowButton(ImVec2(pos.x + visibilityButton.GetWidth() + buttonMargin, pos.y), ImVec2(pos.x + visibilityButton.GetWidth() + buttonMargin + arrowWidth, pos.y + buttonSize)); ImRect iconButton(ImVec2(arrowButton.Max.x + buttonMargin, pos.y), ImVec2(arrowButton.Max.x + buttonMargin + buttonSize, pos.y + buttonSize)); ImRect colliderButton(ImVec2(iconButton.Max.x + buttonMargin, pos.y), ImVec2(iconButton.Max.x + buttonMargin + buttonSize - buttonPadding * 2, pos.y + buttonSize)); ImRect mainButton(ImVec2(colliderButton.Max.x, pos.y), endPos); ImVec2 textPos = ImVec2(mainButton.Min.x + buttonMargin * 3, pos.y + g.Style.FramePadding.y); ImRect totalSize = ImRect(startPos, endPos); // General bool opened = ImGui::TreeNodeBehaviorIsOpen(mainId); bool leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; bool even = nodeIndex++ % 2 == 0; // Get entity components auto mesh = registry.get(entity); auto collider = registry.get(entity); // Main button bool mainHovered, mainHeld; if (ImGui::ButtonBehavior(mainButton, mainId, &mainHovered, &mainHeld, ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick)) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { if (!leaf) window->DC.StateStorage->SetInt(mainId, opened ? 0 : 1); } else onPressed(); } ImGui::DrawButton(totalSize, even, selected, mainHeld, mainHovered); if (mainIcon == 0) { if (mesh.invalid()) mainIcon = leaf ? entityIcon->getID() : opened ? openFolderIcon->getID() : folderIcon->getID(); else mainIcon = getMeshIcon(mesh); } ImGui::DrawIcon(mainIcon, iconButton.Min, iconButton.Max); // Visibility Button if (mesh.valid()) { bool visible = mesh->visible; bool visibilityHovered, visibilityHeld; if (ImGui::ButtonBehavior(visibilityButton, meshId, &visibilityHovered, &visibilityHeld, ImGuiButtonFlags_PressedOnClickRelease)) mesh->visible = !mesh->visible; ImGui::DrawButton(visibilityButton, true, selected, visibilityHeld, false); if (!visible) ImGui::DrawIcon(hiddenIcon->getID(), visibilityButton.Min + ImVec2(0, buttonPadding), visibilityButton.Max - ImVec2(0, buttonPadding)); else if (visibilityHovered) ImGui::DrawIcon(shownIcon->getID(), visibilityButton.Min + ImVec2(0, buttonPadding), visibilityButton.Max - ImVec2(0, buttonPadding)); } // Arrow button if (!leaf) { bool arrowHovered, arrowHeld; if (ImGui::ButtonBehavior(arrowButton, arrowId, &arrowHovered, &arrowHeld, ImGuiButtonFlags_PressedOnClickRelease)) { if (!leaf) window->DC.StateStorage->SetInt(mainId, opened ? 0 : 1); } ImGui::DrawButton(arrowButton, true, selected, arrowHeld, false); ImGui::RenderArrow(window->DrawList, ImVec2(arrowButton.Min.x + arrowOffset, textPos.y), ImGui::GetColorU32(ImGuiCol_Text), opened ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); } // Collider button if (collider.valid()) { bool colliderHovered, colliderHeld; if (ImGui::ButtonBehavior(colliderButton, colliderId, &colliderHovered, &colliderHeld, ImGuiButtonFlags_PressedOnClickRelease)) { if (entity != Engine::Registry64::null_entity) { if (collider.valid()) { std::unique_lock lock(worldMutex); if (collider->part->isTerrainPart()) { world.removePart(collider->part); world.addPart(collider->part); } else { world.removePart(collider->part); world.addTerrainPart(collider->part); } } } } ImGui::DrawButton(colliderButton, true, selected, colliderHeld, false); std::string text; if (collider->part->isTerrainPart()) { text = "T"; } else if(collider->part->hasAttachedParts()) { if(collider->part->isMainPart()) { text = "M"; } else { text = "C"; } } ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); ImGui::RenderText(ImVec2(colliderButton.Min.x + (buttonSize - buttonPadding * 2 - textSize.x) / 2, textPos.y), text.c_str()); } // Lines ImGui::DrawLine( ImVec2(visibilityButton.Max.x + buttonMargin / 2, visibilityButton.Min.y - g.Style.ItemSpacing.y / 2), ImVec2(visibilityButton.Max.x + buttonMargin / 2, visibilityButton.Max.y + g.Style.ItemSpacing.y / 2) ); // Text ImGui::RenderText(textPos, label); ImGui::ItemSize(totalSize, g.Style.FramePadding.y); ImGui::ItemAdd(totalSize, mainId); if (opened) ImGui::TreePush(label); return opened; } static bool EntityTreeNode(ImU32 id, Engine::Registry64& registry, Engine::Registry64::entity_type entity, IRef collider, ImGuiTreeNodeFlags flags, const char* label, GLID icon) { auto onPressed = [&]() { if (ImGui::GetIO().KeyCtrl) SelectionTool::toggle(entity); else { SelectionTool::selection.clear(); SelectionTool::select(entity); } if (collider.valid()) screen.selectedPart = collider->part; }; bool selected = SelectionTool::selection.contains(entity); bool result = IconTreeNode(id, registry, label, flags, icon, selected, onPressed, entity); return result; } static bool ColliderMenuItem(const char* label, GLID icon, bool selected = false, bool enabled = true) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; ImVec2 pos = window->DC.CursorPos; ImVec2 labelSize = ImGui::CalcTextSize(label, nullptr, true); ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); float minWidth = window->DC.MenuColumns.DeclColumns(labelSize.x, 0, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame float extraWidth = ImMax(0.0f, ImGui::GetContentRegionAvail().x - minWidth); if (selected) ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TabActive)); bool pressed = ImGui::Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(minWidth, 0.0f)); if (selected) ImGui::PopStyleColor(1); ImVec2 start = pos + ImVec2(window->DC.MenuColumns.Pos[2] + extraWidth, 0); ImVec2 end = start + ImVec2(labelSize.y, labelSize.y); ImGui::DrawIcon(icon, start, end); return pressed; } void ECSFrame::renderEntity(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity) { if (entity == selectedNodeEntity) return; ImGui::PushID(static_cast(entity)); auto children = registry.getChildren(entity); bool leaf = children.begin() == registry.end(); auto collider = registry.get(entity); std::vector hardConstraints; std::vector softLinks; std::vector> attachments; if(collider.valid()) { Physical* colliderPhysical = collider->part->getPhysical(); bool isFreePart = !collider->part->isTerrainPart(); if(isFreePart) { // Collect hard constraints colliderPhysical->forEachHardConstraint([&hardConstraints](Physical& parent, ConnectedPhysical& child) { hardConstraints.push_back(&child.connectionToParent); }); // Collect soft links for(SoftLink* softLink : world.softLinks) { if(softLink->attachedPartA.part == collider->part || softLink->attachedPartB.part == collider->part) softLinks.push_back(softLink); } // Collect attachments RigidBody* rigidBody = &colliderPhysical->rigidBody; if(collider->part->isMainPart()) { rigidBody->forEachAttachedPart([&attachments, &collider, &rigidBody](Part& attachment) { if(collider->part != &attachment) { attachments.emplace_back(rigidBody->mainPart, &attachment); } }); } else { attachments.emplace_back(collider->part, rigidBody->mainPart); } } } // Entity node std::string name = registry.getOr(entity, std::to_string(entity)).name; ImU32 id = GImGui->CurrentWindow->GetID(name.c_str()); bool expandable = !leaf || !hardConstraints.empty() || !softLinks.empty() || !attachments.empty(); ImGuiTreeNodeFlags flags = expandable ? baseFlags : leafFlags; bool open = EntityTreeNode(id, registry, entity, collider, flags, name.c_str(), 0); // Render popup if (ImGui::BeginPopupContextItem()) { if (collider.valid()) { if (ImGui::BeginMenu("Change collider shape")) { std::unique_lock lock(worldMutex); DiagonalMat3 scale = collider->part->hitbox.scale; const ShapeClass* shape = collider->part->getShape().baseShape.get(); if (ColliderMenuItem("Box", cubeClassIcon->getID(), shape == &CubeClass::instance)) collider->part->setShape(boxShape(scale[0], scale[1], scale[2])); if (ColliderMenuItem("Sphere", sphereClassIcon->getID(), shape == &SphereClass::instance)) collider->part->setShape(sphereShape(scale[0])); if (ColliderMenuItem("Cylinder", cylinderClassIcon->getID(), shape == &CylinderClass::instance)) collider->part->setShape(cylinderShape(scale[0], scale[1])); if (ColliderMenuItem("Corner", cornerClassIcon->getID(), shape == &CornerClass::instance)) collider->part->setShape(cornerShape(scale[0], scale[1], scale[2])); if (ColliderMenuItem("Wedge", wedgeClassIcon->getID(), shape == &WedgeClass::instance)) collider->part->setShape(wedgeShape(scale[0], scale[1], scale[2])); ImGui::EndMenu(); } } // Collapse / unfold if (!leaf) { if (open) { if (ImGui::MenuItem("Collapse")) GImGui->CurrentWindow->DC.StateStorage->SetInt(id, 0); } else { if (ImGui::MenuItem("Expand")) GImGui->CurrentWindow->DC.StateStorage->SetInt(id, 1); } } // Rename if (ImGui::BeginMenu("Rename")) { static char buffer[20] = ""; if (buffer[0] == '\0') strcpy(buffer, name.c_str()); ImGui::Text("Edit name:"); ImGui::InputText("##edit", buffer, IM_ARRAYSIZE(buffer)); if (ImGui::Button("Apply")) registry.get(entity)->name = buffer; ImGui::EndMenu(); } // Add ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImVec4(0.458f, 0.776f, 0.298f, 1.0f))); if (ImGui::MenuItem("Add")) { auto newEntity = registry.create(); SelectionTool::single(newEntity); } ImGui::PopStyleColor(); // Delete ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImVec4(0.929f, 0.262f, 0.212f, 1.0f))); if (ImGui::MenuItem("Delete")) { registry.destroy(entity); } ImGui::PopStyleColor(); ImGui::EndPopup(); } // Render content if (open) { // Render attachments if (!attachments.empty()) { if (IconTreeNode(0, registry, "Attachments", baseFlags, attachmentsIcon->getID(), false, []() {})) { renderAttachments(registry, entity, *collider, attachments); ImGui::TreePop(); } } // Render hard constraints if (!hardConstraints.empty()) { if (IconTreeNode(0, registry, "Hard Constraints", baseFlags, hardConstraintsIcon->getID(), false, []() {})) { renderHardConstraints(registry, entity, *collider, hardConstraints); ImGui::TreePop(); } } // Render soft links if (!softLinks.empty()) { if (IconTreeNode(0, registry, "Soft Links", baseFlags, softLinksIcon->getID(), false, []() {})) { renderSoftLinks(registry, entity, *collider, softLinks); ImGui::TreePop(); } } // Render children if (!leaf) { for (auto child : children) renderEntity(registry, child); } ImGui::TreePop(); } /*if (ImGui::BeginDragDropSource()) { // check sizeof later ImGui::SetDragDropPayload("ECS_NODE", id, sizeof(id)); float buttonSize = GImGui->FontSize + GImGui->Style.FramePadding.y * 2; ImGui::Image(texture, ImVec2(buttonSize, buttonSize)); ImGui::SameLine(); ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), ImGui::GetCursorPosY() + 4)); ImGui::Text("Move %s", name.c_str()); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ECS_NODE")) { //Engine::Node* source = static_cast(payload->Data); } ImGui::EndDragDropTarget(); }*/ ImGui::PopID(); } template bool softLinkDispatch(Engine::Registry64& registry, const std::vector& links, std::size_t index) { BaseLink* baseLink = links[index]; if (Link* link = dynamic_cast(baseLink)) { std::string name = Util::format("%s %d", Util::decamel(Util::demangle(Util::typeName())).c_str(), index + 1); bool selected = selectedNode == link; auto onPressed = [&] { SelectionTool::selection.clear(); selectedNodeEntity = registry.destroy(selectedNodeEntity); if (selectedNode != link) { Engine::Registry64::entity_type entity = registry.create(); registry.add(entity, name); registry.add(entity, link); selectedNode = link; selectedNodeEntity = entity; SelectionTool::select(selectedNodeEntity); } else selectedNode = nullptr; }; IconTreeNode(0, registry, name.c_str(), leafFlags, ResourceManager::get(Tool::getStaticName())->getID(), selected, onPressed); return true; } return false; } template bool hardConstraintDispatch(Engine::Registry64& registry, const std::vector& constraints, std::size_t index) { HardPhysicalConnection* connection = constraints[index]; if (Constraint* constraint = dynamic_cast(connection->constraintWithParent.get())) { std::string name = Util::format("%s %d", Util::decamel(Util::demangle(Util::typeName())).c_str(), index + 1); bool selected = selectedNode == constraint; auto onPressed = [&] { SelectionTool::selection.clear(); selectedNodeEntity = registry.destroy(selectedNodeEntity); if (selectedNode != constraint) { Engine::Registry64::entity_type entity = registry.create(); registry.add(entity, name); registry.add(entity, connection); selectedNode = constraint; selectedNodeEntity = entity; SelectionTool::select(selectedNodeEntity); } else selectedNode = nullptr; }; IconTreeNode(0, registry, name.c_str(), leafFlags, ResourceManager::get(Tool::getStaticName())->getID(), selected, onPressed); return true; } return false; } void attachmentDispatch(Engine::Registry64& registry, const std::vector>& attachments, std::size_t index) { Part* from = attachments[index].first; Part* to = attachments[index].second; Part* childPart = to->isMainPart() ? from : to; std::string name = Util::format("Attachment %d", index + 1); bool selected = selectedNode == childPart; auto onPressed = [&] { SelectionTool::selection.clear(); selectedNodeEntity = registry.destroy(selectedNodeEntity); if (selectedNode != childPart) { Engine::Registry64::entity_type entity = registry.create(); registry.add(entity, name); registry.add(entity, from, to); selectedNode = childPart; selectedNodeEntity = entity; SelectionTool::select(selectedNodeEntity); } else selectedNode = nullptr; }; IconTreeNode(0, registry, name.c_str(), leafFlags, ResourceManager::get(AttachmentTool::getStaticName())->getID(), selected, onPressed); } void ECSFrame::renderHardConstraints(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector& hardConstraints) { for (std::size_t index = 0; index < hardConstraints.size(); index++) { ImGui::PushID(hardConstraints[index]); bool dispatched = false; dispatched |= hardConstraintDispatch(registry, hardConstraints, index); dispatched |= hardConstraintDispatch(registry, hardConstraints, index); if (!dispatched) IconTreeNode(0, registry, "Unknown hard constraint", leafFlags, hardConstraintsIcon->getID(), selectedNode == hardConstraints[index], []() {}); ImGui::PopID(); } } void ECSFrame::renderSoftLinks(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector& softLinks) { for (std::size_t index = 0; index < softLinks.size(); index++) { ImGui::PushID(softLinks[index]); bool dispatched = false; dispatched |= softLinkDispatch(registry, softLinks, index); dispatched |= softLinkDispatch(registry, softLinks, index); dispatched |= softLinkDispatch(registry, softLinks, index); dispatched |= softLinkDispatch(registry, softLinks, index); if (!dispatched) IconTreeNode(0, registry, "Unknown soft link", leafFlags, softLinksIcon->getID(), selectedNode == softLinks[index], [] {}); ImGui::PopID(); } } void ECSFrame::renderAttachments(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector>& attachments) { for (std::size_t index = 0; index < attachments.size(); index++) { ImGui::PushID(attachments[index].second); attachmentDispatch(registry, attachments, index); ImGui::PopID(); } } void ECSFrame::onInit(Engine::Registry64& registry) { folderIcon = ResourceManager::add("folder", "../res/textures/icons/Folder.png"); openFolderIcon = ResourceManager::add("folder open", "../res/textures/icons/Folder Open.png"); entityIcon = ResourceManager::add("entity", "../res/textures/icons/Entity.png"); colliderIcon = ResourceManager::add("collider", "../res/textures/icons/Collider.png"); terrainIcon = ResourceManager::add("terrain collider", "../res/textures/icons/Terrain Collider.png"); mainColliderIcon = ResourceManager::add("main collider", "../res/textures/icons/Main Collider.png"); childColliderIcon = ResourceManager::add("child collider", "../res/textures/icons/Child Collider.png"); hardConstraintsIcon = ResourceManager::add("hard constraints", "../res/textures/icons/Hard Constraints.png"); softLinksIcon = ResourceManager::add("soft constraints", "../res/textures/icons/Soft Constraints.png"); attachmentsIcon = ResourceManager::add("attachments", "../res/textures/icons/Attachments.png"); cframeIcon = ResourceManager::add("cframe", "../res/textures/icons/Axes.png"); cubeClassIcon = ResourceManager::add("cube", "../res/textures/icons/Cube.png"); sphereClassIcon = ResourceManager::add("sphere", "../res/textures/icons/Sphere.png"); cylinderClassIcon = ResourceManager::add("cylinder", "../res/textures/icons/Cylinder.png"); cornerClassIcon = ResourceManager::add("corner", "../res/textures/icons/Tetrahedron.png"); wedgeClassIcon = ResourceManager::add("wedge", "../res/textures/icons/Wedge.png"); polygonClassIcon = ResourceManager::add("polygon", "../res/textures/icons/Dodecahedron.png"); shownIcon = ResourceManager::add("shown", "../res/textures/icons/Eye.png"); hiddenIcon = ResourceManager::add("hidden", "../res/textures/icons/Hidden.png"); physicsIcon = ResourceManager::add("physics", "../res/textures/icons/Physics.png"); addIcon = ResourceManager::add("add", "../res/textures/icons/Add.png"); } void ECSFrame::onRender(Engine::Registry64& registry) { using namespace Engine; ImGui::Begin("Tree"); auto filter = [®istry](const Registry64::entity_set_iterator& iterator) { return registry.getParent(*iterator) == 0; }; nodeIndex = 0; HeaderNode(); for (auto entity : registry.filter(filter)) renderEntity(registry, registry.getSelf(entity)); ImGui::End(); } } ================================================ FILE: application/view/ecsFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D { class Part; class SoftLink; struct HardPhysicalConnection; namespace Application::Comp { struct Collider; } } namespace P3D::Application { struct ECSFrame { private: static void renderEntity(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity); static void renderHardConstraints(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector& hardConstraints); static void renderSoftLinks(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector& softLinks); static void renderAttachments(Engine::Registry64& registry, const Engine::Registry64::entity_type& entity, const Comp::Collider& collider, const std::vector>& attachments); public: static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/environmentFrame.cpp ================================================ #include "core.h" #include "environmentFrame.h" #include "imgui/imgui.h" #include "shader/shaders.h" // include this because we have a bit of code that uses it. #include "../layer/skyboxLayer.h" namespace P3D::Application { float EnvironmentFrame::hdr = 1.0f; float EnvironmentFrame::gamma = 0.8f; float EnvironmentFrame::exposure = 0.8f; Color EnvironmentFrame::sunColor = Color(1); void EnvironmentFrame::onInit(Engine::Registry64& registry) { } void EnvironmentFrame::onRender(Engine::Registry64& registry) { ImGui::Begin("Environment"); if (ImGui::SliderFloat("HDR", &hdr, 0, 1)) { Shaders::basicShader->updateHDR(hdr); Shaders::instanceShader->updateHDR(hdr); } if (ImGui::SliderFloat("Gamma", &gamma, 0, 3)) { Shaders::basicShader->updateGamma(gamma); Shaders::instanceShader->updateGamma(gamma); } if (ImGui::SliderFloat("Exposure", &exposure, 0, 2)) { Shaders::basicShader->updateExposure(exposure); ; } if (ImGui::ColorEdit3("Sun color", sunColor.data)) { Shaders::basicShader->updateSunColor(sunColor); Shaders::instanceShader->updateSunColor(sunColor); } ImGui::End(); ImGui::Begin("Skybox"); ImGui::Checkbox("New sky", &useNewSky); ImGui::Text("Time: %f", time); ImGui::Checkbox("Pauze", &pauze); ImGui::End(); } } ================================================ FILE: application/view/environmentFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" #include "../graphics/gui/color.h" namespace P3D::Application { struct EnvironmentFrame { static float hdr; static float gamma; static float exposure; static Graphics::Color sunColor; static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/frames.cpp ================================================ #include "core.h" #include "frames.h" #include "resourceFrame.h" #include "layerFrame.h" #include "ecsFrame.h" #include "debugFrame.h" #include "environmentFrame.h" #include "propertiesFrame.h" #include "toolbarFrame.h" #include "imgui/imgui.h" namespace P3D::Application { void Frames::onInit(Engine::Registry64& registry) { ECSFrame::onInit(registry); PropertiesFrame::onInit(registry); LayerFrame::onInit(registry); ResourceFrame::onInit(registry); EnvironmentFrame::onInit(registry); DebugFrame::onInit(registry); ToolbarFrame::onInit(registry); } void Frames::onRender(Engine::Registry64& registry) { ImGui::ShowDemoWindow(); ECSFrame::onRender(registry); PropertiesFrame::onRender(registry); LayerFrame::onRender(registry); ResourceFrame::onRender(registry); EnvironmentFrame::onRender(registry); DebugFrame::onRender(registry); ToolbarFrame::onRender(registry); } } ================================================ FILE: application/view/frames.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Application { struct Frames { static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; }; ================================================ FILE: application/view/layerFrame.cpp ================================================ #include "core.h" #include "layerFrame.h" #include "screen.h" #include "application.h" #include "imgui/imgui.h" namespace P3D::Application { bool LayerFrame::doEvents; bool LayerFrame::noRender; bool LayerFrame::doUpdate; bool LayerFrame::isDisabled; Engine::Layer* LayerFrame::selectedLayer = nullptr; void LayerFrame::onInit(Engine::Registry64& registry) { } void LayerFrame::onRender(Engine::Registry64& registry) { ImGui::Begin("Layers"); using namespace Engine; ImGui::Columns(2, 0, true); ImGui::Separator(); for (Layer* layer : screen.layerStack) { if (ImGui::Selectable(layer->name.c_str(), selectedLayer == layer)) selectedLayer = layer; } ImGui::NextColumn(); ImGui::Text("Name: %s", (selectedLayer) ? selectedLayer->name.c_str() : "-"); if (selectedLayer) { ImGui::SetNextTreeNodeOpen(true); if (ImGui::TreeNode("Flags")) { char& flags = selectedLayer->flags; doEvents = !(flags & Layer::NoEvents); doUpdate = !(flags & Layer::NoUpdate); noRender = !(flags & Layer::NoRender); isDisabled = flags & Layer::Disabled; ImGui::Checkbox("Disabled", &isDisabled); ImGui::Checkbox("Events", &doEvents); ImGui::Checkbox("Update", &doUpdate); ImGui::Checkbox("Render", &noRender); flags = (isDisabled) ? flags | Layer::Disabled : flags & ~Layer::Disabled; flags = (doEvents) ? flags & ~Layer::NoEvents : flags | Layer::NoEvents; flags = (doUpdate) ? flags & ~Layer::NoUpdate : flags | Layer::NoUpdate; flags = (noRender) ? flags & ~Layer::NoRender : flags | Layer::NoRender; ImGui::TreePop(); } } ImGui::Columns(1); ImGui::Separator(); ImGui::End(); } } ================================================ FILE: application/view/layerFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Engine { class Layer; } namespace P3D::Application { struct LayerFrame { static bool doEvents; static bool noRender; static bool doUpdate; static bool isDisabled; static Engine::Layer* selectedLayer; static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/propertiesFrame.cpp ================================================ #include "core.h" #include "propertiesFrame.h" #include "screen.h" #include "application.h" #include "extendedPart.h" #include "ecs/components.h" #include "imgui/imgui.h" #include "picker/tools/selectionTool.h" #include #include #include "Physics3D/hardconstraints/fixedConstraint.h" #include "../graphics/gui/imgui/imguiExtension.h" #include "../graphics/renderer.h" namespace P3D::Application { Graphics::TextureResource* materialIcon; Graphics::TextureResource* hitboxIcon; Graphics::TextureResource* lightIcon; Graphics::TextureResource* nameIcon; Engine::Registry64::component_type deletedComponentIndex = static_cast(-1); std::string errorModalMessage = ""; bool showErrorModal = false; bool _ecs_property_frame_start(Engine::Registry64& registry, Engine::Registry64::component_type index) { std::string label((registry).getComponentName(index)); bool open = ImGui::CollapsingHeader(label.c_str(), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_AllowItemOverlap); ImGui::SameLine(ImGui::GetColumnWidth() - 40.0f); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0, 0.0, 0.0, 0.0)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, GImGui->Style.Colors[ImGuiCol_HeaderActive]); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, GImGui->Style.Colors[ImGuiCol_HeaderHovered]); ImGui::AlignTextToFramePadding(); std::string buttonLabel = "...##button_" + label; if (ImGui::Button(buttonLabel.c_str())) { ImGui::OpenPopup("ComponentSettings"); deletedComponentIndex = index; } ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Settings"); return open; } bool _ecs_property_frame_start2(Engine::Registry64& registry, Engine::Registry64::component_type index, GLID icon = 0) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; // ID ImGuiContext& g = *GImGui; std::string label((registry).getComponentName(index)); ImGui::PushID(index); ImU32 mainId = window->GetID(label.c_str()); ImU32 arrowId = window->GetID("Arrow"); ImU32 editId = window->GetID("Edit"); // Constants float arrowWidth = g.FontSize; float buttonSize = g.FontSize + g.Style.FramePadding.y; float buttonMargin = g.Style.ItemInnerSpacing.x; float arrowOffset = std::abs(g.FontSize - arrowWidth) / 2.0f; float buttonPadding = 10.0; ImVec2 pos = window->DC.CursorPos; ImVec2 startPos = ImVec2(pos.x - window->DC.Indent.x + g.Style.WindowPadding.x, pos.y + g.Style.ItemSpacing.y); ImVec2 endPos = ImVec2(startPos.x + ImGui::GetContentRegionMax().x - g.Style.WindowPadding.x, startPos.y + buttonSize); ImRect arrowButton(ImVec2(startPos.x + g.Style.WindowPadding.x, startPos.y), ImVec2(startPos.x + g.Style.WindowPadding.x + arrowWidth, startPos.y + buttonSize)); ImRect mainButton(ImVec2(arrowButton.Max.x + buttonPadding, startPos.y), endPos); ImRect iconButton(ImVec2(mainButton.Min.x + buttonPadding, startPos.y), ImVec2(mainButton.Min.x + buttonPadding + buttonSize, startPos.y + buttonSize)); ImVec2 textPos = ImVec2(iconButton.Max.x + buttonPadding, startPos.y + g.Style.FramePadding.y / 2.0); ImRect totalSize = ImRect(startPos, endPos); ImRect bb = ImRect(ImVec2(startPos.x, startPos.y - g.Style.ItemSpacing.y), ImVec2(endPos.x, endPos.y + g.Style.ItemSpacing.y)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0, 0.0, 0.0, 0.0)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, GImGui->Style.Colors[ImGuiCol_HeaderActive]); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, GImGui->Style.Colors[ImGuiCol_HeaderHovered]); // Main button bool opened = ImGui::TreeNodeBehaviorIsOpen(mainId, ImGuiTreeNodeFlags_DefaultOpen); bool mainHovered, mainHeld; if (ImGui::ButtonBehavior(mainButton, mainId, &mainHovered, &mainHeld, ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick)) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { window->DC.StateStorage->SetInt(mainId, opened ? 0 : 1); } } ImGui::DrawButton(totalSize, false, false, mainHeld, mainHovered, 12); // Arrow button bool arrowHovered, arrowHeld; if (ImGui::ButtonBehavior(arrowButton, arrowId, &arrowHovered, &arrowHeld, ImGuiButtonFlags_PressedOnClickRelease)) { window->DC.StateStorage->SetInt(mainId, opened ? 0 : 1); } ImGui::DrawButton(arrowButton, true, false, arrowHeld, false, 12); ImGui::RenderArrow(window->DrawList, ImVec2(arrowButton.Min.x + arrowOffset, textPos.y), ImGui::GetColorU32(ImGuiCol_Text), opened ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); // Icon ImGui::DrawIcon(icon, iconButton.Min, iconButton.Max); // Text ImGui::RenderText(textPos, label.c_str()); ImGui::PopStyleColor(3); ImGui::ItemSize(bb, g.Style.FramePadding.y); ImGui::ItemAdd(bb, mainId); //if (opened) ImGui::TreePush(label.c_str()); ImGui::PopID(); return opened; } #define ENTITY_DISPATCH_START(index) \ if ((index) == -1) \ continue #define ENTITY_DISPATCH(registry, entity, index, type, component) \ else if ((index) == (registry).getComponentIndex()) \ renderEntity(registry, entity, index, intrusive_cast(component)) #define ENTITY_DISPATCH_END(registry, entity, index, component) \ else \ renderEntity(registry, entity, index, component) #define ECS_PROPERTY_FRAME_START(registry, index, icon) \ if (_ecs_property_frame_start2(registry, index, icon)) { \ ImGui::Columns(2) #define ECS_PROPERTY_FRAME_END \ ImGui::Columns(1); \ ImGui::TreePop(); \ } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { std::string label(registry.getComponentName(index)); ImGui::CollapsingHeader(label.c_str(), ImGuiTreeNodeFlags_Leaf); } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("collider")->getID()); ExtendedPart* selectedPart = component->part; Motion motion = selectedPart->getMotion(); Vec3f velocity = motion.getVelocity(); Vec3f angularVelocity = motion.getAngularVelocity(); float mass = static_cast(selectedPart->getMass()); float friction = static_cast(selectedPart->getFriction()); float density = static_cast(selectedPart->getDensity()); float bouncyness = static_cast(selectedPart->getBouncyness()); Vec3f conveyorEffect = selectedPart->getConveyorEffect(); TITLE("Part Info", false); PROPERTY_IF_LOCK("Velocity:", ImGui::DragVec3("##Velocity", velocity.data, 0, nullptr, true), selectedPart->setVelocity(velocity);); PROPERTY_IF_LOCK("Angular velocity:", ImGui::DragVec3("##AngularVelocity", angularVelocity.data, 0, nullptr, true), selectedPart->setAngularVelocity(angularVelocity);); PROPERTY("Acceleration:", ImGui::Text(str(motion.getAcceleration()).c_str())); PROPERTY("Angular acceleration:", ImGui::Text(str(motion.getAngularAcceleration()).c_str())); PROPERTY_IF_LOCK("Mass:", ImGui::DragFloat("##Mass", &mass, 0.05f), selectedPart->setMass(mass);); TITLE("Part Properties", true); PROPERTY_IF_LOCK("Friction:", ImGui::DragFloat("##Friction", &friction, 0.05f), selectedPart->setFriction(friction);); PROPERTY_IF_LOCK("Density:", ImGui::DragFloat("##Density", &density, 0.05f), selectedPart->setDensity(density);); PROPERTY_IF_LOCK("Bouncyness:", ImGui::DragFloat("##Bouncyness", &bouncyness, 0.05f), selectedPart->setBouncyness(bouncyness);); PROPERTY_IF_LOCK("Conveyor effect:", ImGui::DragVec3("##ConveyorEffect", conveyorEffect.data, 0, nullptr, true), selectedPart->setConveyorEffect(conveyorEffect);); PROPERTY("Inertia:", ImGui::Text(str(selectedPart->getInertia()).c_str())); Physical* selectedPartPhys = selectedPart->getPhysical(); if (selectedPartPhys != nullptr) { const MotorizedPhysical* physical = selectedPartPhys->mainPhysical; Motion comMotion = physical->getMotionOfCenterOfMass(); TITLE("Physical Info:", true); PROPERTY("Total impulse:", ImGui::Text(str(physical->getTotalImpulse()).c_str())); PROPERTY("Total angular momentum:", ImGui::Text(str(physical->getTotalAngularMomentum()).c_str())); PROPERTY("COM Velocity:", ImGui::Text(str(comMotion.getVelocity()).c_str())); PROPERTY("COM Acceleration:", ImGui::Text(str(comMotion.getAcceleration()).c_str())); PROPERTY("COM Angular velocity:", ImGui::Text(str(comMotion.getAngularVelocity()).c_str())); PROPERTY("COM Angular acceleration:", ImGui::Text(str(comMotion.getAngularAcceleration()).c_str())); } static volatile ExtendedPart* sp = nullptr; if (sp != nullptr) sp = selectedPart; TITLE("Debug", true); PROPERTY_IF( "Debug part:", ImGui::Button("Debug"), Log::debug("Debugging part %d", reinterpret_cast(sp)); P3D_DEBUGBREAK; ); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, nameIcon->getID()); PROPERTY("Name:", ImGui::Text(component->name.c_str())); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { using namespace Graphics::Comp; ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("polygon")->getID()); bool normals = component->flags & Mesh::Flags_Normal; bool uvs = component->flags & Mesh::Flags_UV; bool tangents = component->flags & Mesh::Flags_Tangent; bool bitangents = component->flags & Mesh::Flags_Bitangent; PROPERTY("ID:", ImGui::Text(str(component->id).c_str())); PROPERTY("Visible:", ImGui::Checkbox("##Visible", &component->visible)); TITLE("Flags", true); PROPERTY("Normals:", ImGui::Checkbox("##Normals", &normals)); PROPERTY("UVs:", ImGui::Checkbox("##UVs", &uvs)); PROPERTY("Tangents:", ImGui::Checkbox("##Tangents", &tangents)); PROPERTY("Bitangents:", ImGui::Checkbox("##Bitangents", &bitangents)); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, materialIcon->getID()); PROPERTY("Albedo", ImGui::ColorEdit4("##Albedo", component->albedo.data, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar)); PROPERTY("Metalness", ImGui::SliderFloat("##Metalness", &component->metalness, 0, 1)); PROPERTY("Roughness", ImGui::SliderFloat("##Roughness", &component->roughness, 0, 1)); PROPERTY("Ambient occlusion", ImGui::SliderFloat("##AO", &component->ao, 0, 1)); TITLE("Textures:", true); float size = ImGui::GetTextLineHeightWithSpacing(); if (component->has(Graphics::Comp::Material::Map_Albedo)) PROPERTY( "Albedo", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Albedo)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Normal)) PROPERTY( "Normal", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Normal)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Metalness)) PROPERTY( "Metalness", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Metalness)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Roughness)) PROPERTY( "Roughness", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Roughness)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_AO)) PROPERTY("Ambient occlusion", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_AO)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Gloss)) PROPERTY("Gloss", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Gloss)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Specular)) PROPERTY( "Specular", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Specular)->getID()), ImVec2(size, size)) ); if (component->has(Graphics::Comp::Material::Map_Displacement)) PROPERTY( "Displacement", ImGui::Image(reinterpret_cast(component->get(Graphics::Comp::Material::Map_Displacement)->getID()), ImVec2(size, size)) ); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, lightIcon->getID()); PROPERTY("Color", ImGui::ColorEdit3("##Color", component->color.data, ImGuiColorEditFlags_PickerHueWheel)); PROPERTY("Intensity", ImGui::DragFloat("##Intensity", &component->intensity)); TITLE("Attenuation", true); PROPERTY("Constant", ImGui::SliderFloat("##Constant", &component->attenuation.constant, 0, 2)); PROPERTY("Linear", ImGui::SliderFloat("##Linear", &component->attenuation.linear, 0, 2)); PROPERTY("Exponent", ImGui::SliderFloat("##Exponent", &component->attenuation.exponent, 0, 2)); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("cframe")->getID()); Vec3f position = castPositionToVec3f(component->getPosition()); Vec3f rotation = component->getRotation().asRotationVector(); DiagonalMat3f scale = component->getScale(); bool standalone = component->isRootPart(); PROPERTY_DESC("Standalone", "Whether the transform and scale is coming from the part", ImGui::Checkbox("##TransformHitbox", &standalone)); if (component->hasOffset()) { TITLE("Global CFrame", true); } PROPERTY_IF_LOCK( "Position:", ImGui::DragVec3("##TransformPosition", position.data, 0.0f, nullptr, true), component->setPosition(castVec3fToPosition(position)); ); float rotationSpeeds[] = { 0.02f, 0.02f, 0.02f }; PROPERTY_IF_LOCK( "Rotation:", ImGui::DragVec3("##TransformRotation", rotation.data, 0.0f, rotationSpeeds, true), component->setRotation(Rotation::fromRotationVector(rotation)); ); float min = 0.01f; float* mins[] = { &min, &min, &min }; float scaleSpeeds[] = { 0.01f, 0.01f, 0.01f }; PROPERTY_IF_LOCK( "Scale:", ImGui::DragVec3("##TransformScale", scale.data, 1.0f, scaleSpeeds, true, mins), component->setScale(scale); ); if (component->hasOffset()) { CFrame relativeCFrame = component->getOffsetCFrame(); Vec3f relativePosition = relativeCFrame.getPosition(); Vec3f relativeRotation = relativeCFrame.getRotation().asRotationVector(); TITLE("Relative CFrame", true); PROPERTY_IF_LOCK( "Position:", ImGui::DragVec3("##TransformRelativePosition", relativePosition.data, 0.0f, nullptr, true), component->setPosition(castVec3fToPosition(relativePosition)); ); PROPERTY_IF_LOCK( "Rotation:", ImGui::DragVec3("##TransformRelativeRotation", relativeRotation.data, 0.0f, rotationSpeeds, true), component->setRotation(Rotation::fromRotationVector(relativeRotation)); ); } ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, hitboxIcon->getID()); bool standalone = component->isPartAttached(); Shape shape = component->getShape(); DiagonalMat3f scale = shape.scale; TITLE("Hitbox", false); PROPERTY_DESC("Standalone", "Whether the hitbox is coming from the part", ImGui::Checkbox("##HitboxStandalone", &standalone)); PROPERTY("Volume:", ImGui::Text(str(shape.getVolume()).c_str())); PROPERTY("Center of mass:", ImGui::Text(str(shape.getCenterOfMass()).c_str())); float min = 0.01f; float* mins[] = { &min, &min, &min }; PROPERTY_IF_LOCK("Scale:", ImGui::DragVec3("HitboxScale", scale.data, 1, nullptr, true, mins), component->setScale(scale);); TITLE("Bounding box", true); PROPERTY("Width:", ImGui::Text(str(shape.getWidth()).c_str())); PROPERTY("Height:", ImGui::Text(str(shape.getHeight()).c_str())); PROPERTY("Depth:", ImGui::Text(str(shape.getHeight()).c_str())); PROPERTY("Max radius:", ImGui::Text(str(shape.getMaxRadius()).c_str())); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("attachments")->getID()); bool isAttachmentToMainPart = !component->isAttachmentToMainPart; Vec3f attachmentPosition = component->attachment->position; Vec3f attachmentRotation = component->attachment->rotation.asRotationVector(); bool isEditingFrame = registry.has(entity); PROPERTY( "Is attachment to the main part:", ImGui::Checkbox("##ToMainPart", &isAttachmentToMainPart); ); if (isEditingFrame) { PROPERTY_DESC( "Position:", "Use the tranform component to edit the position", ImGui::Text("(%f, %f, %f)", attachmentPosition.x, attachmentPosition.y, attachmentPosition.z) ); PROPERTY_DESC( "Rotation:", "Use the tranform component to edit the rotation", ImGui::Text("(%f, %f, %f)", attachmentRotation.x, attachmentRotation.y, attachmentRotation.z); ); } else { PROPERTY_IF_LOCK( "Position:", ImGui::DragVec3("##AttachmentPosition", attachmentPosition.data, 0.0f, nullptr, true), component->setAttachment(CFrame(attachmentPosition, Rotation::fromRotationVector(attachmentRotation))); ); float rotationSpeeds[] = { 0.02f, 0.02f, 0.02f }; PROPERTY_IF_LOCK( "Rotation:", ImGui::DragVec3("##AttachmentRotation", attachmentRotation.data, 0.0f, rotationSpeeds, true), component->setAttachment(CFrame(attachmentPosition, Rotation::fromRotationVector(attachmentRotation))); ); PROPERTY_IF( "", ImGui::Button("Edit"), registry.add(entity, component->getMainPart(), component->attachment)->addCallback([] () {Log::debug("Edit"); }); ); } ECS_PROPERTY_FRAME_END; } void renderSoftlink(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Comp::SoftLink* component) { ExtendedPart* partA = reinterpret_cast(component->link->attachedPartA.part); ExtendedPart* partB = reinterpret_cast(component->link->attachedPartB.part); if (partA == nullptr || partB == nullptr) return; Vec3f positionA = component->getPositionA(); Vec3f positionB = component->getPositionB(); IRef nameA = registry.get(partA->entity); IRef nameB = registry.get(partB->entity); IRef transform = registry.get(entity); bool isEditingFrame = transform.valid() && transform->isOffsetStoredRemote(); bool isEditingFrameA = isEditingFrame && std::get(transform->offset) == &component->link->attachedPartA.attachment; bool isEditingFrameB = isEditingFrame && std::get(transform->offset) == &component->link->attachedPartB.attachment; TITLE("Attachment A", false); PROPERTY("Part", ImGui::Text(nameA.valid() ? nameA->name.c_str() : "Part A")); if (isEditingFrameA) { PROPERTY_DESC( "Position:", "Use the tranform component to edit the position", ImGui::Text("(%f, %f, %f)", positionA.x, positionA.y, positionA.z) ); } else { PROPERTY_IF_LOCK("Position", ImGui::DragVec3("##TransformPositionA", positionA.data, 0, nullptr, true), component->setPositionA(positionA);); PROPERTY_IF( "", ImGui::Button("Edit##EditCFrameA"), registry.add(entity, partA, &component->link->attachedPartA.attachment); SelectionTool::selection.recalculateSelection(); ); } TITLE("Attachment B", true); PROPERTY("Part", ImGui::Text(nameB.valid() ? nameB->name.c_str() : "Part B")); if (isEditingFrameB) { PROPERTY_DESC( "Position:", "Use the tranform component to edit the position", ImGui::Text("(%f, %f, %f)", positionB.x, positionB.y, positionB.z) ); } else { PROPERTY_IF_LOCK("Position", ImGui::DragVec3("##TransformPositionB", positionB.data, 0, nullptr, true), component->setPositionB(positionB);); PROPERTY_IF( "", ImGui::Button("Edit##EditCFrameB"), registry.add(entity, partB, &component->link->attachedPartB.attachment); SelectionTool::selection.recalculateSelection(); ); } } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("Magnetic Link")->getID()); renderSoftlink(registry, entity, component.get()); MagneticLink* link = dynamic_cast(component->link); float magneticStrength = static_cast(link->magneticStrength); TITLE("Properties", true); PROPERTY_IF_LOCK( "Magnetic strength", ImGui::DragFloat("##MagneticStrength", &magneticStrength, 0, 0.1f, true), link->magneticStrength = static_cast(magneticStrength); ); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("Elastic Link")->getID()); renderSoftlink(registry, entity, component.get()); ElasticLink* link = dynamic_cast(component->link); float restLenght = static_cast(link->restLength); float stiffness = static_cast(link->stiffness); TITLE("Properties", true); PROPERTY_IF_LOCK("Length", ImGui::DragFloat("##RestLength", &restLenght, 0, 0.1f, true), link->restLength = static_cast(restLenght);); PROPERTY_IF_LOCK("Stiffness", ImGui::DragFloat("##Stiffness", &stiffness, 0, 0.1f, true), link->stiffness = static_cast(stiffness);); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("Spring Link")->getID()); renderSoftlink(registry, entity, component.get()); SpringLink* link = dynamic_cast(component->link); float restLenght = static_cast(link->restLength); float stiffness = static_cast(link->stiffness); TITLE("Properties", true); PROPERTY_IF_LOCK("Length", ImGui::DragFloat("##RestLength", &restLenght, 0, 0.1f, true), link->restLength = static_cast(restLenght);); PROPERTY_IF_LOCK("Stiffness", ImGui::DragFloat("##Stiffness", &stiffness, 0, 0.1f, true), link->stiffness = static_cast(stiffness);); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("Alignmnent Link")->getID()); renderSoftlink(registry, entity, component.get()); AlignmentLink* link = dynamic_cast(component->link); ECS_PROPERTY_FRAME_END; } void renderEntity(Engine::Registry64& registry, Engine::Registry64::entity_type entity, Engine::Registry64::component_type index, const IRef& component) { ECS_PROPERTY_FRAME_START(registry, index, ResourceManager::get("Fixed Constraint")->getID()); FixedConstraint* link = dynamic_cast(component->hardConstraint->constraintWithParent.get()); CFrame* parentCFrame = component->getParentAttachment(); Vec3f parentPosition = parentCFrame->position; Vec3f parentRotation = parentCFrame->rotation.asRotationVector(); CFrame* childCFrame = component->getChildAttachment(); Vec3f childPosition = childCFrame->position; Vec3f childRotation = childCFrame->rotation.asRotationVector(); //IRef nameA = registry.get(component->hardConstraint->constraintWithParent->); //IRef nameB = registry.get(partB->entity); TITLE("Parent", true); PROPERTY("Part", ImGui::Text("Part A")); PROPERTY_IF_LOCK( "Position", ImGui::DragVec3("##ParentPosition", parentPosition.data, 0, nullptr, true), parentCFrame->position = parentPosition; ); PROPERTY_IF_LOCK( "Rotation", ImGui::DragVec3("##ParentRotation", parentRotation.data, 0, nullptr, true), parentCFrame->rotation = Rotation::fromRotationVector(parentRotation); ); TITLE("Child", true); PROPERTY("Part", ImGui::Text("Part B")); PROPERTY_IF_LOCK( "Position", ImGui::DragVec3("##ChildPosition", childPosition.data, 0, nullptr, true), childCFrame->position = childPosition; ); PROPERTY_IF_LOCK( "Rotation", ImGui::DragVec3("##ChildRotation", childRotation.data, 0, nullptr, true), childCFrame->rotation = Rotation::fromRotationVector(childRotation); ); ECS_PROPERTY_FRAME_END; } void PropertiesFrame::onInit(Engine::Registry64& registry) { materialIcon = ResourceManager::add("material", "../res/textures/icons/Material.png"); hitboxIcon = ResourceManager::add("hitbox", "../res/textures/icons/Hitbox.png"); lightIcon = ResourceManager::add("light", "../res/textures/icons/Light.png"); nameIcon = ResourceManager::add("name", "../res/textures/icons/Name.png"); } void PropertiesFrame::onRender(Engine::Registry64& registry) { ImGui::Begin("Properties"); if (SelectionTool::selection.empty()) { std::string label = "Select an entity to see properties"; auto [wx, wy] = ImGui::GetContentRegionAvail(); auto [tx, ty] = ImGui::CalcTextSize(label.c_str()); auto [cx, cy] = ImGui::GetCursorPos(); ImVec2 cursor = ImVec2(cx + (wx - tx) / 2.0f, cy + (wy - ty) / 2.0f); ImGui::SetCursorPos(cursor); ImGui::Text(label.c_str()); ImGui::End(); return; } if (SelectionTool::selection.isMultiSelection()) { std::string label = "Multiple entities selected"; auto [wx, wy] = ImGui::GetContentRegionAvail(); auto [tx, ty] = ImGui::CalcTextSize(label.c_str()); auto [cx, cy] = ImGui::GetCursorPos(); ImVec2 cursor = ImVec2(cx + (wx - tx) / 2.0f, cy + (wy - ty) / 2.0f); ImGui::SetCursorPos(cursor); ImGui::Text(label.c_str()); ImGui::End(); return; } // Dispatch component frames Engine::Registry64::entity_type selectedEntity = SelectionTool::selection.first().value(); auto allComponents = registry.getComponents(selectedEntity); for (auto [index, components] : allComponents) { auto component = *components.begin(); ENTITY_DISPATCH_START(index); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Name, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Transform, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Collider, component); ENTITY_DISPATCH(registry, selectedEntity, index, Graphics::Comp::Mesh, component); ENTITY_DISPATCH(registry, selectedEntity, index, Graphics::Comp::Material, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Light, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Hitbox, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::Attachment, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::MagneticLink, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::SpringLink, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::ElasticLink, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::AlignmentLink, component); ENTITY_DISPATCH(registry, selectedEntity, index, Comp::FixedConstraint, component); ENTITY_DISPATCH_END(registry, selectedEntity, index, component); } // Add new Component if (ImGui::Button("Add new component...", ImVec2(-1, 0))) ImGui::OpenPopup("Add component"); if (ImGui::BeginPopupModal("Add component")) { std::vector components; for (Engine::Registry64::component_type index = 0; index < Engine::Registry64::component_index::index(); index++) components.push_back(registry.getComponentName(index).data()); static int item_current = 0; ImGui::SetNextItemWidth(-1); ImGui::ListBox("##ComponentsModal", &item_current, components.data(), components.size(), 6); if (ImGui::Button("Cancel", ImVec2(-1, 0))) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } // Component Settings if (ImGui::BeginPopup("ComponentSettings")) { if (ImGui::MenuItem("Delete component")) { if (deletedComponentIndex != static_cast(-1)) { if (deletedComponentIndex == registry.getComponentIndex()) { errorModalMessage = "This component can not be deleted."; showErrorModal = true; } else { registry.remove(selectedEntity, deletedComponentIndex); } } } if (ImGui::MenuItem("Component info")) { } ImGui::EndPopup(); } // Error Modal if (showErrorModal) ImGui::OpenPopup("Error##ErrorModal"); if (ImGui::BeginPopupModal("Error##ErrorModal", 0, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), errorModalMessage.c_str()); if (ImGui::Button("Ok##ErrorModalClose", ImVec2(-1, 0))) { ImGui::CloseCurrentPopup(); showErrorModal = false; } ImGui::EndPopup(); } ImGui::End(); } } ================================================ FILE: application/view/propertiesFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Application { struct PropertiesFrame { static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/resourceFrame.cpp ================================================ #include "core.h" #include "resourceFrame.h" #include "imgui/imgui.h" #include "shader/shaders.h" #include "../util/resource/resourceManager.h" #include "../graphics/gui/imgui/imguiExtension.h" #include "../graphics/resource/fontResource.h" #include "../graphics/resource/textureResource.h" #include "../graphics/texture.h" #include "../graphics/font.h" namespace P3D::Application { Resource* ResourceFrame::selectedResource = nullptr; void ResourceFrame::renderTextureInfo(Texture* texture) { ImGui::Text("ID: %d", texture->getID()); ImGui::Text("Width: %d", texture->getWidth()); ImGui::Text("Height: %d", texture->getHeight()); ImGui::Text("Channels: %d", texture->getChannels()); ImGui::Text("Unit: %d", texture->getUnit()); if (ImGui::TreeNodeEx("Preview", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) { float cw = ImGui::GetColumnWidth(); float ch = ImGui::GetWindowHeight(); float tw = ch * texture->getAspect(); float th = cw / texture->getAspect(); ImVec2 size; if (th > ch) size = ImVec2(tw, ch); else size = ImVec2(cw, th); Vec4f c = Colors::ACCENT; ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); ImGui::Image((void*) (intptr_t) texture->getID(), size, ImVec2(0, 0), ImVec2(1, 1), ImVec4(1, 1, 1, 1), ImVec4(c.x, c.y, c.z, c.w)); ImGui::TreePop(); } } void ResourceFrame::renderFontInfo(Font* font) { if (ImGui::TreeNode("Atlas")) { renderTextureInfo(font->getAtlas().get()); ImGui::TreePop(); } char selectedCharacter = 0; if (ImGui::TreeNode("Characters")) { for (int i = 0; i < CHARACTER_COUNT; i++) { Character& character = font->getCharacter(i); float s = float(character.x) / font->getAtlasWidth(); float t = float(character.y) / font->getAtlasHeight(); float ds = float(character.width) / font->getAtlasWidth(); float dt = float(character.height) / font->getAtlasHeight(); ImGui::ImageButton((void*) (intptr_t) font->getAtlas()->getID(), ImVec2(character.width / 2.0f, character.height / 2.0f), ImVec2(s, t), ImVec2(s + ds, t + dt)); float last_button_x2 = ImGui::GetItemRectMax().x; float next_button_x2 = last_button_x2 + ImGui::GetStyle().ItemSpacing.x + character.width / 2.0f; // Expected position if next button was on same line if (i + 1 < CHARACTER_COUNT && next_button_x2 < ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x) ImGui::SameLine(); } if (ImGui::TreeNodeEx("Info", ImGuiTreeNodeFlags_DefaultOpen)) { Character& c = font->getCharacter(selectedCharacter); ImGui::Text("Character: %s", std::string(1, static_cast(selectedCharacter)).c_str()); ImGui::Text("ID: %d", c.id); ImGui::Text("Origin: (%d, %d)", c.x, c.x); ImGui::Text("Size: (%d, %d)", c.width, c.height); ImGui::Text("Bearing: (%d, %d)", c.bx, c.by); ImGui::Text("Advance: %d", c.advance); ImGui::TreePop(); } ImGui::TreePop(); } } void renderPropertyEditor(ShaderResource* shader, Property& property) { ImGui::PushID(&property); std::string name(property.name); std::string uniform(property.uniform); auto renderVectorProperty = [&] (const char** labels, float* data, float* speed, float** min, float** max) -> bool { for (std::size_t i = 0; i < property.defaults.size(); i++) { Property::Default& def = property.defaults.at(i); *(data + i) = def.value; *(speed + i) = def.step.value_or(0.1f); *(min + i) = def.range.has_value() ? &def.range->min : nullptr; *(max + i) = def.range.has_value() ? &def.range->max : nullptr; } PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::DragVecN("", labels, data, property.defaults.size(), 0.0f, speed, false, min, max), for (std::size_t i = 0; i < property.defaults.size(); i++) property.defaults.at(i).value = data[i]; return true; ); return false; }; auto renderMatrixProperty = [&] (const char** labels, float* data, float* speed, float** min, float** max) -> bool { for (std::size_t i = 0; i < property.defaults.size(); i++) { Property::Default& def = property.defaults.at(i); *(data + i) = def.value; *(speed + i) = def.step.value_or(0.1f); *(min + i) = def.range.has_value() ? &def.range->min : nullptr; *(max + i) = def.range.has_value() ? &def.range->max : nullptr; } std::size_t size = 2; if (property.defaults.size() == 9) size = 3; else if (property.defaults.size() == 16) size = 4; bool result = false; PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::DragVecN("", labels, data, size, 0.0f, speed, false, min, max), for (std::size_t i = 0; i < property.defaults.size(); i++) property.defaults.at(i).value = data[i]; result = true; ); for (std::size_t j = 1; j < size; j++) { std::size_t offset = j * size; PROPERTY_IF("", ImGui::DragVecN("", labels + offset, data + offset, size, 0.0f, speed, false, min + offset, max + offset), for (std::size_t i = 0; i < property.defaults.size(); i++) property.defaults.at(i).value = data[i]; ); result = true; } return result; }; switch (property.type) { case Property::Type::None: break; case Property::Type::Bool: { Property::Default& def = property.defaults[0]; bool value = def.value != 0.0f; PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::Checkbox("", &value), def.value = value ? 1.0f : 0.0f; shader->bind(); shader->setUniform(uniform, value); ); break; } case Property::Type::Int: { Property::Default& def = property.defaults[0]; if (def.range.has_value()) { int value = static_cast(def.value); PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::DragInt("", &value, 1.0f, static_cast(def.range->min), static_cast(def.range->max)), def.value = static_cast(value); shader->bind(); shader->setUniform(uniform, value); ); } else { int value = static_cast(def.value); PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::InputInt("", &value), def.value = static_cast(value); shader->bind(); shader->setUniform(uniform, value); ); } } break; case Property::Type::Float: { Property::Default& def = property.defaults[0]; if (def.range.has_value()) { PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::DragFloat("", &def.value, 1.0f, def.range->min, def.range->max), shader->bind(); shader->setUniform(uniform, def.value); ); } else { PROPERTY_DESC_IF(name.c_str(), uniform.c_str(), ImGui::InputFloat("", &def.value), shader->bind(); shader->setUniform(uniform, def.value); ); } } break; case Property::Type::Vec2: { float data[2], speed[2], *min[2], *max[2]; const char* labels[2] { "X", "Y" }; renderVectorProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Vec2f { data[0], data[1] }); } break; case Property::Type::Vec3: { float data[3], speed[3], *min[3], *max[3]; const char* labels[3] { "X", "Y", "Z" }; renderVectorProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Vec3f { data[0], data[1], data[2] }); } break; case Property::Type::Vec4: { float data[4], speed[4], *min[4], *max[4]; const char* labels[4] { "X", "Y", "Z", "W" }; renderVectorProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Vec4f { data[0], data[1], data[2], data[3] }); } break; case Property::Type::Mat2: { float data[4], speed[4], *min[4], *max[4]; const char* labels[4] { "00", "01", "10", "11" }; renderMatrixProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Mat2f { data[0], data[1], data[2], data[3] }); } break; case Property::Type::Mat3: { float data[9], speed[9], *min[9], *max[9]; const char* labels[9] { "00", "01", "02", "10", "11", "12", "20", "21", "22" }; renderMatrixProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Mat3f { data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8] }); } break; case Property::Type::Mat4: { float data[16], speed[16], *min[16], *max[16]; const char* labels[16] { "00", "01", "02", "03", "10", "11", "12", "13", "20", "21", "22", "23", "30", "31", "32", "33" }; renderMatrixProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Mat3f { data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15] }); } break; case Property::Type::Col3: { float data[3], speed[3], *min[3], *max[3]; const char* labels[3] { "R", "G", "B" }; renderVectorProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Vec3f { data[0], data[1], data[2] }); } break; case Property::Type::Col4: { float data[4], speed[4], *min[4], *max[4]; const char* labels[4] { "R", "G", "B", "A" }; renderVectorProperty(labels, data, speed, min, max); shader->bind(); shader->setUniform(uniform, Vec4f { data[0], data[1], data[2], data[3] }); } break; } ImGui::PopID(); } void ResourceFrame::renderShaderInfo(ShaderResource* shader) { ImGui::BeginChild("Shaders"); PROPERTY_FRAME_START("Properties"); for (Property& property : shader->properties) renderPropertyEditor(shader, property); PROPERTY_FRAME_END; /*PROPERTY_FRAME_START("Code"); if (ImGui::BeginTabBar("##tabs")) { for (const auto& [id, stage] : shader->stages) { if (ImGui::BeginTabItem(Shader::getStageName(id).c_str())) { std::string code(stage.view); ImGui::TextWrapped("%s", code.c_str()); } } ImGui::EndTabBar(); } PROPERTY_FRAME_END;*/ ImGui::EndChild(); } void ResourceFrame::onInit(Engine::Registry64& registry) { } void ResourceFrame::onRender(Engine::Registry64& registry) { ImGui::Begin("Resources"); ImGui::Columns(2, 0, true); ImGui::Separator(); auto map = ResourceManager::getResourceMap(); for (auto& [name, resources] : map) { if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth)) { for (Resource* resource : resources) { if (ImGui::Selectable(resource->getName().c_str(), resource == selectedResource)) selectedResource = resource; } ImGui::TreePop(); } } ImGui::NextColumn(); if (selectedResource) { ImGui::Text("Name: %s", selectedResource->getName().c_str()); ImGui::Text("Path: %s", selectedResource->getPath().c_str()); ImGui::Text("Type: %s", selectedResource->getTypeName().c_str()); switch (selectedResource->getType()) { case ResourceType::Texture: renderTextureInfo(static_cast(selectedResource)); break; case ResourceType::Font: renderFontInfo(static_cast(selectedResource)); break; case ResourceType::Shader: renderShaderInfo(static_cast(selectedResource)); break; default: ImGui::Text("Visual respresentation not supported."); break; } } else { ImGui::Text("No resource selected."); } ImGui::Columns(1); ImGui::Separator(); ImGui::End(); } } ================================================ FILE: application/view/resourceFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Graphics { class Texture; class Font; struct ShaderStage; class ShaderResource; } class Resource; namespace P3D::Application { struct ResourceFrame { private: static void renderTextureInfo(Graphics::Texture* texture); static void renderFontInfo(Graphics::Font* font); static void renderShaderInfo(Graphics::ShaderResource* shader); public: static Resource* selectedResource; static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view/screen.cpp ================================================ #include "core.h" #include "screen.h" #include #include #include "../graphics/renderer.h" #include "../graphics/glfwUtils.h" #include "../graphics/texture.h" #include "shader/shaders.h" #include "../graphics/mesh/primitive.h" #include "../graphics/debug/visualDebug.h" #include "../graphics/buffers/frameBuffer.h" #include "../engine/options/keyboardOptions.h" #include "../input/standardInputHandler.h" #include "../worlds.h" #include "../engine/event/windowEvent.h" #include "../util/resource/resourceManager.h" #include "layer/skyboxLayer.h" #include "layer/modelLayer.h" #include "layer/constraintLayer.h" #include "layer/testLayer.h" #include "layer/pickerLayer.h" #include "layer/postprocessLayer.h" #include "layer/guiLayer.h" #include "layer/debugLayer.h" #include "layer/debugOverlay.h" #include "layer/imguiLayer.h" #include "layer/shadowLayer.h" #include "layer/cameraLayer.h" #include "ecs/components.h" #include "../util/systemVariables.h" #include "frames.h" #include "imgui/imgui.h" struct GLFWwindow; namespace P3D::Application { bool initGLFW() { // Set window hints //Renderer::setGLFWMultisampleSamples(4); // Initialize GLFW if (!Graphics::GLFW::init()) { Log::error("GLFW failed to initialize"); return false; } //Renderer::enableMultisampling(); Log::info("Initialized GLFW"); return true; } bool initGLEW() { // Init GLEW after creating a valid rendering context if (!Graphics::Renderer::initGLEW()) { terminateGLFW(); Log::error("GLEW Failed to initialize!"); return false; } Log::info("Initialized GLEW"); return true; } void terminateGLFW() { Log::info("Closing GLFW"); GLFW::terminate(); Log::info("Closed GLFW"); } Screen::Screen() { }; Screen::Screen(int width, int height, PlayerWorld* world, UpgradeableMutex* worldMutex) : world(world), worldMutex(worldMutex) { using namespace Graphics; // Init registry component order registry.init(); // Create a windowed mode window and its OpenGL context GLFWwindow* context = GLFW::createContext(width, height, "Physics3D"); if (!GLFW::validContext(context)) { Log::fatal("Invalid rendering context"); terminateGLFW(); exit(-1); } // Make the window's context current GLFW::setCurrentContext(context); // already set using application resources on windows #ifndef _WIN32 Graphics::GLFW::setWindowIconFromPath("../res/textures/logo128.png"); #endif Log::info("OpenGL vendor: (%s)", Renderer::getVendor()); Log::info("OpenGL renderer: (%s)", Renderer::getRenderer()); Log::info("OpenGL version: (%s)", Renderer::getVersion()); Log::info("OpenGL shader version: (%s)", Renderer::getShaderVersion()); SystemVariables::set("OPENGL_SHADER_VERSION", Renderer::parseShaderVersion(Renderer::getShaderVersion())); SystemVariables::set("MAX_TEXTURE_IMAGE_UNITS", Renderer::getMaxTextureUnits()); Log::info("OpenGL maximum texture units: %d", Renderer::getMaxTextureUnits()); } // Handler StandardInputHandler* handler = nullptr; // Layers ImGuiLayer imguiLayer; CameraLayer cameraLayer; SkyboxLayer skyboxLayer; ModelLayer modelLayer; ConstraintLayer constraintLayer; ShadowLayer shadowLayer; PickerLayer pickerLayer; PostprocessLayer postprocessLayer; GuiLayer guiLayer; DebugLayer debugLayer; TestLayer testLayer; DebugOverlay debugOverlay; bool USE_IMGUI = true; void Screen::onInit(bool quickBoot) { // Log init Log::setLogLevel(Log::Level::INFO); // Properties init properties = Util::PropertiesParser::read("../res/.properties"); // load options from properties Engine::KeyboardOptions::load(properties); // InputHandler init handler = new StandardInputHandler(GLFW::getCurrentContext(), *this); // Screen size init dimension = GLFW::getWindowSize(); // Framebuffer init quad = new Quad(); screenFrameBuffer = std::make_shared(dimension.x, dimension.y); // GShader init Shaders::onInit(); // Layer creation if(USE_IMGUI) imguiLayer = ImGuiLayer(this); cameraLayer = CameraLayer(this); if(!quickBoot) skyboxLayer = SkyboxLayer(this); modelLayer = ModelLayer(this); constraintLayer = ConstraintLayer(this, Engine::Layer::NoUpdate | Engine::Layer::NoEvents); shadowLayer = ShadowLayer(this); if(USE_IMGUI) debugLayer = DebugLayer(this); pickerLayer = PickerLayer(this); postprocessLayer = PostprocessLayer(this); if(USE_IMGUI) guiLayer = GuiLayer(this); //testLayer = TestLayer(this); if(USE_IMGUI) debugOverlay = DebugOverlay(this); if(!quickBoot) layerStack.pushLayer(&skyboxLayer); layerStack.pushLayer(&constraintLayer); layerStack.pushLayer(&modelLayer); layerStack.pushLayer(&shadowLayer); if(USE_IMGUI) layerStack.pushLayer(&debugLayer); layerStack.pushLayer(&pickerLayer); layerStack.pushLayer(&postprocessLayer); if(USE_IMGUI) layerStack.pushLayer(&guiLayer); if(USE_IMGUI) layerStack.pushLayer(&debugOverlay); //layerStack.pushLayer(&testLayer); layerStack.pushLayer(&cameraLayer); if(USE_IMGUI) layerStack.pushLayer(&imguiLayer); // Layer init layerStack.onInit(registry); // Resize Engine::FrameBufferResizeEvent event(dimension.x, dimension.y); handler->onFrameBufferResize(event); // Init frames if(USE_IMGUI) Frames::onInit(registry); } void Screen::onUpdate() { // Update layers layerStack.onUpdate(registry); } void Screen::onEvent(Engine::Event& event) { using namespace Engine; /*// Consume ImGui events if (event.inCategory(EventCategoryKeyboard | EventCategoryMouseButton) || ImGui::IsAnyItemHovered() || ImGui::IsAnyItemActive()) { ImGuiIO& io = ImGui::GetIO(); if (io.WantCaptureKeyboard || io.WantTextInput | io.WantCaptureMouse) { event.handled = true; return; } }*/ layerStack.onEvent(registry, event); } void Screen::onRender() { using namespace Graphics; using namespace Renderer; // Reset screen framebuffer defaultSettings(screenFrameBuffer->getID()); if (USE_IMGUI) imguiLayer.begin(); // Render layers layerStack.onRender(registry); if (USE_IMGUI) imguiLayer.end(); if (!USE_IMGUI) { // Blit screen framebuffer to main framebuffer glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFrameBuffer->getID()); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer(0, 0, screenFrameBuffer->dimension.x, screenFrameBuffer->dimension.y, 0, 0, dimension.x, dimension.y, GL_COLOR_BUFFER_BIT, GL_LINEAR); } graphicsMeasure.mark(GraphicsProcess::FINALIZE); // Finalize GLFW::swapInterval(1); GLFW::swapBuffers(); GLFW::pollEvents(); graphicsMeasure.mark(GraphicsProcess::OTHER); } void Screen::onClose() { screenFrameBuffer->close(); layerStack.onClose(registry); ResourceManager::close(); Shaders::onClose(); Engine::KeyboardOptions::save(properties); Util::PropertiesParser::write("../res/.properties", properties); terminateGLFW(); } bool Screen::shouldClose() { return GLFW::isWindowClosed(); } }; ================================================ FILE: application/view/screen.h ================================================ #pragma once #include "../eventHandler.h" #include "../util/properties.h" #include "../graphics/extendedTriangleMesh.h" #include "../engine/event/event.h" #include "../engine/layer/layerStack.h" #include "../engine/ecs/registry.h" #include "camera.h" namespace P3D { class UpgradeableMutex; }; namespace P3D::Graphics { struct Quad; class FrameBuffer; class HDRFrameBuffer; class IndexedMesh; }; namespace P3D::Application { class StandardInputHandler; class PlayerWorld; bool initGLEW(); bool initGLFW(); void terminateGLFW(); class Screen { public: Engine::Registry64 registry; PlayerWorld* world; UpgradeableMutex* worldMutex; Vec2i dimension; Camera camera; Engine::LayerStack layerStack; EventHandler eventHandler; Util::Properties properties; SRef screenFrameBuffer = nullptr; Graphics::Quad* quad = nullptr; ExtendedPart* selectedPart = nullptr; Screen(); Screen(int width, int height, PlayerWorld* world, UpgradeableMutex* worldMutex); void onInit(bool quickBoot); void onUpdate(); void onEvent(Engine::Event& event); void onRender(); void onClose(); bool shouldClose(); }; extern StandardInputHandler* handler; }; ================================================ FILE: application/view/toolbarFrame.cpp ================================================ #include "core.h" #include "toolbarFrame.h" #include "../layer/pickerLayer.h" #include "../engine/tool/toolManager.h" #include "../graphics/gui/imgui/imguiExtension.h" #include "../input/standardInputHandler.h" #include "../engine/options/keyboardOptions.h" #include "application.h" #include "screen.h" #include "picker/tools/toolSpacing.h" namespace P3D::Application { void ToolbarFrame::onInit(Engine::Registry64& registry) { std::string path = "../res/textures/icons/"; ResourceManager::add("play", path + "Play.png"); ResourceManager::add("pause", path + "Pause.png"); ResourceManager::add("tick", path + "Tick.png"); ResourceManager::add("reset", path + "Reset.png"); } void ToolbarFrame::onRender(Engine::Registry64& registry) { ImGui::BeginToolbar("Toolbar"); for (Engine::ToolManager& toolManager : PickerLayer::toolManagers) { for (Engine::Tool* tool : toolManager) { bool selected = toolManager.isSelected(tool); if (dynamic_cast(tool) != nullptr) { ImGui::SameLine(); ImGui::BulletText(""); } else if (ImGui::ToolBarButton(tool, selected)) { if (selected) toolManager.deselectTool(); else toolManager.selectTool(tool); } } } ImGui::ToolBarSpacing(); if (ImGui::ToolBarButton("Play / Pause", "Play / Pause the simulation", isPaused() ? "play" : "pause")) { Engine::KeyPressEvent event(Engine::KeyboardOptions::Tick::pause); handler->onEvent(event); } if (ImGui::ToolBarButton("Tick", "Run one tick of the simulation", "tick")) { Engine::KeyPressEvent event(Engine::KeyboardOptions::Tick::run); handler->onEvent(event); } ImGui::EndToolBar(); } } ================================================ FILE: application/view/toolbarFrame.h ================================================ #pragma once #include "../engine/ecs/registry.h" namespace P3D::Application { struct ToolbarFrame { static void onInit(Engine::Registry64& registry); static void onRender(Engine::Registry64& registry); }; } ================================================ FILE: application/view.natvis ================================================ position={cframe.position}, rotation={cframe.rotation}, aspect={aspect, g} cframe.position cframe.rotation aspect fov znear zfar speed rspeed flying attachment id={id}, dimension=({width}, {height}), unit={unit}, channels={channels} id unit width height format channels {constant, g}, {linear, g}, {exponent,g} position={position}, color={color}, intensity={intensity,g}, attenuation={attenuation} amb={ambient}, dif={diffuse}, spec={specular}, refl={reflectance,g}}} ================================================ FILE: application/worldBuilder.cpp ================================================ #include "core.h" #include "worldBuilder.h" #include "application.h" #include #include #include #include "../graphics/gui/gui.h" #include "../util/resource/resourceLoader.h" #include "../util/resource/resourceManager.h" #include "../graphics/resource/textureResource.h" #include #include "view/screen.h" #include "ecs/components.h" #include #include namespace P3D::Application { namespace WorldBuilder { Shape wedge; Shape treeTrunk; Shape icosahedron; Shape triangle; Shape arrow; PartProperties basicProperties{1.0, 0.7, 0.3}; void init() { wedge = polyhedronShape(ShapeLibrary::wedge); treeTrunk = polyhedronShape(ShapeLibrary::createPrism(7, 0.5, 11.0).rotated(Rotation::Predefined::X_270)); icosahedron = polyhedronShape(ShapeLibrary::icosahedron); triangle = polyhedronShape(ShapeLibrary::trianglePyramid); Vec2f inbetweenPoints[3]{Vec2f(0.0f, 0.03f), Vec2f(0.7f, 0.03f), Vec2f(0.7f, 0.06f)}; arrow = polyhedronShape(ShapeLibrary::createRevolvedShape(0.0f, inbetweenPoints, 3, 1.0f, 50)); } void createDominoAt(const GlobalCFrame& cframe) { ExtendedPart* domino = new ExtendedPart(boxShape(0.2, 1.4, 0.6), cframe, { 1, 0.7, 0.3 }); world.addPart(domino); } void makeDominoStrip(int dominoCount, Position dominoStart, Vec3 dominoOffset) { for (int i = 0; i < dominoCount; i++) { createDominoAt(GlobalCFrame(dominoStart + dominoOffset * i)); } } void makeDominoTower(int floors, int circumference, Position origin) { double radius = circumference / 4.4; Rotation sideways = Rotation::fromEulerAngles(PI / 2, 0.0, 0.0); for (int floor = 0; floor < floors; floor++) { for (int j = 0; j < circumference; j++) { double angle = (2 * PI * (j + (floor % 2) / 2.0)) / circumference; Vec3 pos = Vec3(std::cos(angle) * radius, floor * 0.7 + 0.30, std::sin(angle) * radius); createDominoAt(GlobalCFrame(origin + pos, Rotation::rotY(-angle) * sideways)); } } } void buildFloor(double width, double depth, int folder) { ExtendedPart* floorExtendedPart = new ExtendedPart(boxShape(width, 1.0, depth), GlobalCFrame(0.0, 0.0, 0.0), { 2.0, 1.0, 0.3 }, "Floor", folder); screen.registry.getOrAdd(floorExtendedPart->entity);// ->set(Comp::Material::ALBEDO, ResourceManager::get("floorMaterial")); world.addTerrainPart(floorExtendedPart); } void buildFloorAndWalls(double width, double depth, double wallHeight, int folder) { auto floorWallFolder = screen.registry.create(folder); screen.registry.add(floorWallFolder, "Floor and walls"); buildFloor(width, depth, floorWallFolder); PartProperties wallProperties { 2.0, 1.0, 0.3 }; world.addTerrainPart(new ExtendedPart(boxShape(0.7, wallHeight, depth), GlobalCFrame(width / 2, wallHeight / 2, 0.0), wallProperties, "Wall", floorWallFolder)); world.addTerrainPart(new ExtendedPart(boxShape(width, wallHeight, 0.7), GlobalCFrame(0.0, wallHeight / 2, depth / 2), wallProperties, "Wall", floorWallFolder)); world.addTerrainPart(new ExtendedPart(boxShape(0.7, wallHeight, depth), GlobalCFrame(-width / 2, wallHeight / 2, 0.0), wallProperties, "Wall", floorWallFolder)); world.addTerrainPart(new ExtendedPart(boxShape(width, wallHeight, 0.7), GlobalCFrame(0.0, wallHeight / 2, -depth / 2), wallProperties, "Wall", floorWallFolder)); } SpiderFactory::SpiderFactory(double spiderSize, int legCount) : spiderSize(spiderSize), legCount(legCount), bodyShape(polyhedronShape(ShapeLibrary::createPointyPrism(legCount, 0.5f, 0.2f, 0.1f, 0.1f))) {} void SpiderFactory::buildSpider(const GlobalCFrame& spiderPosition, int folder) { //ExtendedPart* spiderBody = createUniquePart(screen, createPointyPrism(legCount, 0.5, 0.2, 0.1, 0.1), spiderPosition, 1.0, 0.0, "SpiderBody"); auto spiderID = screen.registry.create(); screen.registry.add(spiderID, "Spider"); if (folder != 0) screen.registry.setParent(spiderID, folder); ExtendedPart* spiderBody = new ExtendedPart(bodyShape, spiderPosition, { 1.0, 0.5, 0.3 }, "Body", spiderID); screen.registry.getOrAdd(spiderBody->entity)->albedo = Graphics::Color(0.6f, 0.6f, 0.6f, 1.0f); //PartFactory legFactory(BoundingBox(0.05, 0.5, 0.05).toShape(), screen, "SpiderLeg"); CFrame topOfLeg(Vec3(0.0, 0.25, 0.0), Rotation::fromEulerAngles(0.2, 0.0, 0.0)); world.addPart(spiderBody); for (int i = 0; i < legCount; i++) { ExtendedPart* leg = new ExtendedPart(boxShape(0.05, 0.5, 0.05), GlobalCFrame(), { 1.0, 0.5, 0.3 }, "LegPart " + std::to_string(i), spiderID); screen.registry.getOrAdd(leg->entity)->albedo = Graphics::Color(0.4f, 0.4f, 0.4f, 1.0f); double angle = i * PI * 2 / legCount; CFrame attachPointOnBody(Rotation::rotY(angle) * Vec3(0.5, 0.0, 0.0), Rotation::rotY(angle + PI / 2)); CFrame attach = attachPointOnBody.localToGlobal(~topOfLeg); spiderBody->attach(leg, attach); } } HollowBoxParts buildHollowBox(Bounds box, double wallThickness) { double width = static_cast(box.getWidth()); double height = static_cast(box.getHeight()); double depth = static_cast(box.getDepth()); Shape XPlate = boxShape(wallThickness, height - wallThickness * 2, depth); Shape YPlate = boxShape(width, wallThickness, depth); Shape ZPlate = boxShape(width - wallThickness * 2, height - wallThickness * 2, wallThickness); auto hollowBoxFolder = screen.registry.create(); screen.registry.add(hollowBoxFolder, "Hollow Box"); ExtendedPart* bottom = new ExtendedPart(YPlate, GlobalCFrame(box.getCenter() - Vec3(0, height / 2 - wallThickness / 2, 0)), { 1.0, 0.2, 0.3 }, "BottomPlate", hollowBoxFolder); ExtendedPart* top = new ExtendedPart(YPlate, bottom, CFrame(0, height - wallThickness, 0), { 1.0, 0.2, 0.3 }, "TopPlate"); ExtendedPart* left = new ExtendedPart(XPlate, bottom, CFrame(width / 2 - wallThickness / 2, height / 2 - wallThickness / 2, 0), { 1.0, 0.2, 0.3 }, "LeftPlate", hollowBoxFolder); ExtendedPart* right = new ExtendedPart(XPlate, bottom, CFrame(-width / 2 + wallThickness / 2, height / 2 - wallThickness / 2, 0), { 1.0, 0.2, 0.3 }, "RightPlate", hollowBoxFolder); ExtendedPart* front = new ExtendedPart(ZPlate, bottom, CFrame(0, height / 2 - wallThickness / 2, depth / 2 - wallThickness / 2), { 1.0, 0.2, 0.3 }, "FrontPlate", hollowBoxFolder); ExtendedPart* back = new ExtendedPart(ZPlate, bottom, CFrame(0, height / 2 - wallThickness / 2, -depth / 2 + wallThickness / 2), { 1.0, 0.2, 0.3 }, "BackPlate", hollowBoxFolder); return HollowBoxParts { bottom, top, left, right, front, back }; } double getYOffset(double x, double z) { double regimeYOffset = -10 * cos(x / 30.0 + z / 20.0) - 7 * sin(-x / 25.0 + z / 17.0 + 2.4) + 2 * sin(x / 4.0 + z / 7.0) + sin(x / 9.0 - z / 3.0) + sin(-x / 3.0 + z / 5.0); double distFromOriginSq = x * x + z * z; if (distFromOriginSq < 5000.0) { double factor = distFromOriginSq / 5000.0; return regimeYOffset * factor + (1 - factor) * -5; } else { return regimeYOffset; } } void buildTree(Position treePos, int treeFolder) { GlobalCFrame trunkCFrame(treePos, Rotation::fromEulerAngles(fRand(-0.1, 0.1), fRand(-3.1415, 3.1415), fRand(-0.1, 0.1))); ExtendedPart* trunk = new ExtendedPart(treeTrunk, trunkCFrame, { 1.0, 1.0, 0.3 }, "Trunk", treeFolder); screen.registry.getOrAdd(trunk->entity)->albedo = Graphics::Color::get<0x654321>(); world.addTerrainPart(trunk); Position treeTop = trunkCFrame.localToGlobal(Vec3(0.0, 5.5, 0.0)); auto leavesFolder = screen.registry.create(treeFolder); screen.registry.add(leavesFolder, "Leaves"); for (int j = 0; j < 15; j++) { GlobalCFrame leavesCFrame(treeTop + Vec3(fRand(-1.0, 1.0), fRand(-1.0, 1.0), fRand(-1.0, 1.0)), Rotation::fromEulerAngles(fRand(0.0, 3.1415), fRand(0.0, 3.1415), fRand(0.0, 3.1415))); ExtendedPart* leaves = new ExtendedPart(icosahedron.scaled(2.1, 1.9, 1.7), leavesCFrame, { 1.0, 1.0, 0.3 }, "Leaf", leavesFolder); screen.registry.getOrAdd(leaves->entity)->albedo = Graphics::Color(fRand(-0.2, 0.2), 0.6 + fRand(-0.2, 0.2), 0.0, 1.0); world.addTerrainPart(leaves); } } void buildConveyor(double width, double length, const GlobalCFrame& cframe, double speed, int folder) { auto conveyorFolder = screen.registry.create(folder); screen.registry.add(conveyorFolder, "Conveyor"); ExtendedPart* conveyor = new ExtendedPart(boxShape(width, 0.3, length), cframe, { 1.0, 0.8, 0.0, Vec3(0.0, 0.0, speed) }, "Conveyor", conveyorFolder); screen.registry.getOrAdd(conveyor->entity)->albedo = Graphics::Color(0.2f, 0.2f, 0.2f, 1.0f); world.addTerrainPart(conveyor); ExtendedPart* leftWall = new ExtendedPart(boxShape(0.2, 0.6, length), cframe.localToGlobal(CFrame(-width / 2 - 0.1, 0.1, 0.0)), { 1.0, 0.4, 0.3, Vec3(0.0, 0.0, 0.0) }, "Left Wall", conveyorFolder); world.addTerrainPart(leftWall); ExtendedPart* rightWall = new ExtendedPart(boxShape(0.2, 0.6, length), cframe.localToGlobal(CFrame(width / 2 + 0.1, 0.1, 0.0)), { 1.0, 0.4, 0.3, Vec3(0.0, 0.0, 0.0) }, "Right Wall", conveyorFolder); world.addTerrainPart(rightWall); } void buildTrebuchet(const GlobalCFrame& cframe, double baseWidth, double baseLength, double wheelRadius, double wheelThickness, double armLength, double armThickness, int folder) { PartProperties wheelproperties; wheelproperties.friction = 2.0; wheelproperties.bouncyness = 0.2; wheelproperties.density = 1.0; ExtendedPart* base = new ExtendedPart(boxShape(baseWidth, 0.2, baseLength), cframe, {1.0, 0.8, 0.0}, "Base", folder); ExtendedPart* FLWheel = new ExtendedPart(cylinderShape(wheelRadius, wheelThickness), cframe, wheelproperties, "FLWheel", folder); FLWheel->setColor(Graphics::Colors::BLACK); ExtendedPart* FRWheel = new ExtendedPart(cylinderShape(wheelRadius, wheelThickness), cframe, wheelproperties, "FRWheel", folder); FRWheel->setColor(Graphics::Colors::BLACK); ExtendedPart* BLWheel = new ExtendedPart(cylinderShape(wheelRadius, wheelThickness), cframe, wheelproperties, "BLWheel", folder); BLWheel->setColor(Graphics::Colors::BLACK); ExtendedPart* BRWheel = new ExtendedPart(cylinderShape(wheelRadius, wheelThickness), cframe, wheelproperties, "BRWheel", folder); BRWheel->setColor(Graphics::Colors::BLACK); ExtendedPart* arm = new ExtendedPart(boxShape(armThickness, armThickness, armLength), cframe, {1.0, 0.8, 0.0}, "Arm", folder); ExtendedPart* weight = new ExtendedPart(boxShape(armThickness * 4, armThickness * 4, armThickness * 4), cframe, {1.0, 0.8, 0.0}, "Weight", folder); weight->setColor(Graphics::Colors::GRAY); arm->attach(weight, CFrame(0.0, 0.0, armLength / 2)); ConstraintGroup cg; cg.add(base, FLWheel, new HingeConstraint(Vec3(baseWidth / 2, 0.0, baseLength / 2), Vec3(1.0, 0.0, 0.0), Vec3(0.0, 0.0, -wheelThickness / 2 * 1.1), Vec3(0.0, 0.0, 1.0))); cg.add(base, FRWheel, new HingeConstraint(Vec3(-baseWidth / 2, 0.0, baseLength / 2), Vec3(-1.0, 0.0, 0.0), Vec3(0.0, 0.0, -wheelThickness / 2 * 1.1), Vec3(0.0, 0.0, 1.0))); cg.add(base, BLWheel, new HingeConstraint(Vec3(baseWidth / 2, 0.0, -baseLength / 2), Vec3(1.0, 0.0, 0.0), Vec3(0.0, 0.0, -wheelThickness / 2 * 1.1), Vec3(0.0, 0.0, 1.0))); cg.add(base, BRWheel, new HingeConstraint(Vec3(-baseWidth / 2, 0.0, -baseLength / 2), Vec3(-1.0, 0.0, 0.0), Vec3(0.0, 0.0, -wheelThickness / 2 * 1.1), Vec3(0.0, 0.0, 1.0))); cg.add(base, arm, new HingeConstraint(Vec3(0.0, armLength * 1.5, 0.0), Vec3(1.0, 0.0, 0.0), Vec3(0.0, 0.0, armLength * 0.2), Vec3(1.0, 0.0, 0.0))); world.constraints.push_back(cg); world.addPart(FLWheel); world.addPart(FRWheel); world.addPart(BLWheel); world.addPart(BRWheel); world.addPart(base); world.addPart(arm); } std::array produceAxes(const GlobalCFrame& cf, const PartProperties& properties, double scale) { ExtendedPart* zAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), cf.extendLocal(Vec3(0.0, 0.0, 0.5 * scale)), properties, "zAxis"); ExtendedPart* xAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), zAxis, CFrame(0.5 * scale, 0.0, -0.5 * scale, Rotation::Predefined::Y_90), properties, "xAxis"); ExtendedPart* yAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), zAxis, CFrame(0.0, 0.5 * scale, -0.5 * scale, Rotation::Predefined::X_270), properties, "yAxis"); xAxis->setColor(Graphics::Color(1.0f, 0.0f, 0.0f, 1.0f)); yAxis->setColor(Graphics::Color(0.0f, 1.0f, 0.0f, 1.0f)); zAxis->setColor(Graphics::Color(0.0f, 0.0f, 1.0f, 1.0f)); return std::array{xAxis, yAxis, zAxis}; } void attachAxes(ExtendedPart* part, double scale) { ExtendedPart* xAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), part, CFrame(0.5 * scale, 0.0, 0.0, Rotation::Predefined::Y_90), part->properties, "xAxis"); ExtendedPart* yAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), part, CFrame(0.0, 0.5 * scale, 0.0, Rotation::Predefined::X_270), part->properties, "yAxis"); ExtendedPart* zAxis = new ExtendedPart(arrow.scaled(scale, scale, scale), part, CFrame(0.0, 0.0, 0.5 * scale), part->properties, "zAxis"); xAxis->setColor(Graphics::Color(1.0f, 0.0f, 0.0f, 1.0f)); yAxis->setColor(Graphics::Color(0.0f, 1.0f, 0.0f, 1.0f)); zAxis->setColor(Graphics::Color(0.0f, 0.0f, 1.0f, 1.0f)); } void buildTerrain(double width, double depth, int folder) { Log::subject s("Terrain"); Log::info("Starting terrain building!"); int maxProgress = 10; int lastProgress = 0; auto groundFolder = screen.registry.create(folder); screen.registry.add(groundFolder, "Ground"); Log::info("0%%"); for (double x = -width / 2; x < width / 2; x += 3.0) { for (double z = -depth / 2; z < depth / 2; z += 3.0) { double yOffset = getYOffset(x, z); Position pos((x / 3.0 + fRand(0.0, 1.0)) * 3.0, fRand(0.0, 1.0) + yOffset, (z / 3.0 + fRand(0.0, 1.0)) * 3.0); GlobalCFrame cf(pos, Rotation::fromEulerAngles(fRand(0.0, 3.1415), fRand(0.0, 3.1415), fRand(0.0, 3.1415))); ExtendedPart* newPart = new ExtendedPart(icosahedron.scaled(4.0, 4.0, 4.0), cf, { 1.0, 1.0, 0.3 }, "Ground", groundFolder); screen.registry.getOrAdd(newPart->entity)->albedo = Graphics::Color(0.0, yOffset / 40.0 + 0.5, 0.0, 1.0); world.addTerrainPart(newPart); } double progress = (x + width / 2) / width; int progressToInt = static_cast(progress * maxProgress); if (progressToInt > lastProgress) Log::info("%d%%", int(progressToInt * 100.0 / maxProgress)); lastProgress = progressToInt; } Log::info("100%%"); auto treeFolder = screen.registry.create(folder); screen.registry.add(treeFolder, "Tree"); Log::info("Finished terrain, adding trees!"); for (int i = 0; i < width * depth / 70; i++) { double x = fRand(-width / 2, width / 2); double z = fRand(-depth / 2, depth / 2); if (std::abs(x) < 30.0 && std::abs(z) < 30.0) continue; Position treePos(x, getYOffset(x, z) + 8.0, z); buildTree(treePos, treeFolder); } Log::info("Optimizing terrain! (This will take around 1.5x the time it took to build the world)"); world.optimizeLayers(); } void buildCar(const GlobalCFrame& location, int folder) { PartProperties carProperties { 1.0, 0.7, 0.3 }; PartProperties wheelProperties { 1.0, 2.0, 0.7 }; auto carFolder = screen.registry.create(folder); screen.registry.add(carFolder, "Car"); ExtendedPart* carBody = new ExtendedPart(boxShape(2.0, 0.1, 1.0), location, carProperties, "CarBody", carFolder); ExtendedPart* carLeftPanel = new ExtendedPart(boxShape(2.0, 0.4, 0.1), carBody, CFrame(0.0, 0.25, -0.5), carProperties, "CarLeftSide", carFolder); ExtendedPart* carRightPanel = new ExtendedPart(boxShape(2.0, 0.4, 0.1), carBody, CFrame(0.0, 0.25, 0.5), carProperties, "CarRightSide", carFolder); ExtendedPart* carLeftWindow = new ExtendedPart(boxShape(1.4, 0.8, 0.05), carLeftPanel, CFrame(-0.3, 0.6, 0.0), carProperties, "WindowLeft", carFolder); ExtendedPart* carWedgeLeft = new ExtendedPart(wedge.scaled(0.6, 0.8, 0.1), carLeftWindow, CFrame(1.0, 0.0, 0.0), carProperties, "WedgeLeft", carFolder); ExtendedPart* carRightWindow = new ExtendedPart(boxShape(1.4, 0.8, 0.05), carRightPanel, CFrame(-0.3, 0.6, 0.0), carProperties, "WindowRight", carFolder); ExtendedPart* carWedgeRight = new ExtendedPart(wedge.scaled(0.6, 0.8, 0.1), carRightWindow, CFrame(1.0, 0.0, 0.0), carProperties, "WedgeRight", carFolder); ExtendedPart* carFrontPanel = new ExtendedPart(boxShape(0.1, 0.4, 1.0), carBody, CFrame(1.0, 0.25, 0.0), carProperties, "FrontPanel", carFolder); ExtendedPart* carTrunkPanel = new ExtendedPart(boxShape(0.1, 1.2, 1.0), carBody, CFrame(-1.0, 0.65, 0.0), carProperties, "TrunkPanel", carFolder); ExtendedPart* carRoof = new ExtendedPart(boxShape(1.4, 0.1, 1.0), carBody, CFrame(-0.3, 1.25, 0.0), carProperties, "Roof", carFolder); ExtendedPart* carWindshield = new ExtendedPart(boxShape(1.0, 0.05, 1.0), carBody, CFrame(Vec3(0.7, 0.85, 0.0), Rotation::fromEulerAngles(0.0, 0.0, -0.91)), carProperties, "Windshield", carFolder); ExtendedPart* wheel1 = new ExtendedPart(sphereShape(0.25), location.localToGlobal(CFrame(0.8, 0.0, 0.8)), wheelProperties, "Wheel", carFolder); ExtendedPart* wheel2 = new ExtendedPart(sphereShape(0.25), location.localToGlobal(CFrame(0.8, 0.0, -0.8)), wheelProperties, "Wheel", carFolder); ExtendedPart* wheel3 = new ExtendedPart(sphereShape(0.25), location.localToGlobal(CFrame(-0.8, 0.0, 0.8)), wheelProperties, "Wheel", carFolder); ExtendedPart* wheel4 = new ExtendedPart(sphereShape(0.25), location.localToGlobal(CFrame(-0.8, 0.0, -0.8)), wheelProperties, "Wheel", carFolder); screen.registry.getOrAdd(carLeftWindow->entity)->albedo = Graphics::Color(0.7f, 0.7f, 1.0f, 0.5f); screen.registry.getOrAdd(carRightWindow->entity)->albedo = Graphics::Color(0.7f, 0.7f, 1.0f, 0.5f); screen.registry.getOrAdd(carWindshield->entity)->albedo = Graphics::Color(0.7f, 0.7f, 1.0f, 0.5f); world.addPart(carBody); world.addPart(wheel1); world.addPart(wheel2); world.addPart(wheel3); world.addPart(wheel4); ConstraintGroup car; car.add(carBody, wheel1, new BallConstraint(Vec3(0.8, 0.0, 0.8), Vec3(0,0,0))); car.add(carBody, wheel2, new BallConstraint(Vec3(0.8, 0.0, -0.8), Vec3(0,0,0))); car.add(carBody, wheel3, new BallConstraint(Vec3(-0.8, 0.0, 0.8), Vec3(0,0,0))); car.add(carBody, wheel4, new BallConstraint(Vec3(-0.8, 0.0, -0.8), Vec3(0,0,0))); world.constraints.push_back(std::move(car)); } } }; ================================================ FILE: application/worldBuilder.h ================================================ #pragma once #include "worlds.h" #include "extendedPart.h" #include "../graphics/debug/guiDebug.h" #include #include #include #include namespace P3D::Application { namespace WorldBuilder { extern Shape wedge; extern Shape treeTrunk; extern Shape icosahedron; extern Shape triangle; extern Shape arrow; extern PartProperties basicProperties; void init(); void createDominoAt(const GlobalCFrame& cframe); void makeDominoStrip(int dominoCount, Position dominoStart, Vec3 dominoOffset); void makeDominoTower(int floors, int circumference, Position origin); void buildTerrain(double width, double depth, int folder = 0); void buildCar(const GlobalCFrame& location, int folder = 0); void buildFloor(double width, double depth, int folder = 0); void buildFloorAndWalls(double width, double depth, double wallHeight, int folder = 0); void buildConveyor(double width, double length, const GlobalCFrame& cframe, double speed, int folder = 0); void buildTrebuchet(const GlobalCFrame& cframe, double baseWidth, double baseLength, double wheelRadius, double wheelThickness, double armLength, double armThickness, int folder); std::array produceAxes(const GlobalCFrame & cf, const PartProperties& properties, double scale = 1.0); void attachAxes(ExtendedPart* part, double scale = 1.0); struct HollowBoxParts { ExtendedPart* bottom; ExtendedPart* top; ExtendedPart* left; ExtendedPart* right; ExtendedPart* front; ExtendedPart* back; }; HollowBoxParts buildHollowBox(Bounds box, double wallThickness); struct SpiderFactory { Shape bodyShape; double spiderSize; int legCount; SpiderFactory(double spiderSize, int legCount); void buildSpider(const GlobalCFrame& spiderPosition, int folder = 0); }; } }; ================================================ FILE: application/worlds.cpp ================================================ #include "core.h" #include "worlds.h" #include "application.h" #include "view/screen.h" #include "input/standardInputHandler.h" #include #include "../engine/options/keyboardOptions.h" #include #include "input/playerController.h" #include "ecs/components.h" namespace P3D::Application { PlayerWorld::PlayerWorld(double deltaT) : World(deltaT) { this->addExternalForce(new PlayerController()); } void PlayerWorld::onPartAdded(ExtendedPart* part) { screen.registry.add(part->entity, part); } void PlayerWorld::onPartRemoved(ExtendedPart* part) { screen.registry.remove(part->entity); screen.registry.get(part->entity)->setRoot(Comp::Transform::ScaledCFrame { part->getCFrame(), part->hitbox.scale }); } }; ================================================ FILE: application/worlds.h ================================================ #pragma once #include "extendedPart.h" #include #include namespace P3D::Application { class PlayerWorld : public World { public: PlayerWorld(double deltaT); void onPartAdded(ExtendedPart* part) override; void onPartRemoved(ExtendedPart* part) override; }; }; ================================================ FILE: benchmarks/basicWorld.cpp ================================================ #include "worldBenchmark.h" #include #include #include #include namespace P3D { class BasicWorldBenchmark : public WorldBenchmark { public: BasicWorldBenchmark() : WorldBenchmark("basicWorld", 1000) {} void init() { createFloor(50, 50, 10); //Polyhedron cube = ShapeLibrary::createCube(0.9); for(int x = -5; x < 5; x++) { for(int y = 0; y < 5; y++) { for(int z = -5; z < 5; z++) { world.addPart(new Part(boxShape(0.9, 0.9, 0.9), GlobalCFrame(x, y + 1.0, z), {1.0, 0.7, 0.5})); } } } } } basicWorld; }; ================================================ FILE: benchmarks/benchmark.cpp ================================================ #include "benchmark.h" #include #include #include #include #include #include "../util/terminalColor.h" #include "../util/parseCPUIDArgs.h" std::vector* knownBenchmarks = nullptr; Benchmark::Benchmark(const char* name) : name(name) { if(knownBenchmarks == nullptr) { knownBenchmarks = new std::vector(); } knownBenchmarks->push_back(this); } Benchmark* getBenchFor(std::string id) { if(id.length() == 0) return nullptr; if(id[0] >= '0' && id[0] <= '9') { return (*knownBenchmarks)[std::stoi(id)]; } else { for(Benchmark* b : *knownBenchmarks) { if(id == b->name) { return b; } } } return nullptr; } std::vector split(std::string str, char splitChar) { std::vector substrings; int lastIndex = 0; int index = 0; while(index < str.length()) { while(str[index] != splitChar) { index++; } substrings.push_back(str.substr(lastIndex, index - lastIndex)); index++; lastIndex = index; } return substrings; } static void runBenchmark(Benchmark* bench) { setColor(TerminalColor::CYAN); auto createStart = std::chrono::high_resolution_clock::now(); bench->init(); auto createFinish = std::chrono::high_resolution_clock::now(); std::cout << bench->name << ": "; std::cout.flush(); setColor(TerminalColor::YELLOW); std::cout << '(' << (createFinish - createStart).count() / 1000000.0 << "ms)"; std::cout.flush(); auto runStart = std::chrono::high_resolution_clock::now(); bench->run(); auto runFinish = std::chrono::high_resolution_clock::now(); double deltaTimeMS = (runFinish - runStart).count() / 1000000.0; setColor(TerminalColor::GREEN); std::cout << " (" << deltaTimeMS << "ms)\n"; std::cout.flush(); bench->printResults(deltaTimeMS); } static void runBenchmarks(const std::vector& benchmarks) { setColor(TerminalColor::CYAN); std::cout << "[NAME]"; setColor(TerminalColor::YELLOW); std::cout << " [INITIALIZATION]"; setColor(TerminalColor::GREEN); std::cout << " [RUNTIME]\n"; setColor(TerminalColor::WHITE); for(const std::string& c : benchmarks) { Benchmark* b = getBenchFor(c); if(b != nullptr) { runBenchmark(b); } } } int main(int argc, const char** args) { Util::ParsedArgs pa(argc, args); std::cout << Util::printAndParseCPUIDArgs(pa).c_str() << "\n"; if(pa.argCount() >= 1) { runBenchmarks(pa.args()); } else { setColor(TerminalColor::WHITE); std::cout << "The following benchmarks are available:\n"; setColor(TerminalColor::CYAN); for(std::size_t i = 0; i < knownBenchmarks->size(); i++) { std::cout << i << ") " << (*knownBenchmarks)[i]->name << "\n"; } setColor(TerminalColor::WHITE); std::cout << "Run> "; setColor(TerminalColor::GREEN); std::string cmd; std::cin >> cmd; if(cmd.empty()) { setColor(TerminalColor::WHITE); return 0; } cmd.append(";"); std::vector commands = split(cmd, ';'); runBenchmarks(commands); } return 0; } ================================================ FILE: benchmarks/benchmark.h ================================================ #pragma once class Benchmark { public: const char* name; Benchmark(const char* name); virtual ~Benchmark() {} virtual void init() {} virtual void run() = 0; virtual void printResults(double timeTaken) {} }; ================================================ FILE: benchmarks/benchmarks.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 16.0 {CA5FECF4-EDD2-4387-9967-66B047052B0B} benchmarks 10.0 benchmarks Application true v142 MultiByte Application false v142 true MultiByte Application true v142 MultiByte Application false v142 true MultiByte Level3 MaxSpeed true true true true NotSet $(SolutionDir)include;$(SolutionDir) _MBCS;NDEBUG;%(PreprocessorDefinitions) stdcpp17 true Console true true $(OutDir) graphics.lib;util.lib;Physics3D.lib;%(AdditionalDependencies) Level3 Disabled true true Console Level3 Disabled true true NotSet stdcpp17 true $(SolutionDir)include;$(SolutionDir) Console graphics.lib;util.lib;Physics3D.lib;%(AdditionalDependencies) $(OutDir) Level3 MaxSpeed true true true true true Console true true ================================================ FILE: benchmarks/complexObjectBenchmark.cpp ================================================ #include "worldBenchmark.h" #include #include #include #include namespace P3D { class ComplexObjectBenchmark : public WorldBenchmark { public: ComplexObjectBenchmark() : WorldBenchmark("complexObject", 10000) {} void init() { createFloor(50, 50, 10); Polyhedron object = ShapeLibrary::icosahedron; world.addPart(new Part(polyhedronShape(ShapeLibrary::createSphere(1.0, 7)), GlobalCFrame(0, 2.0, 0), basicProperties)); } } complexObjectBench; }; ================================================ FILE: benchmarks/ecsBenchmark.cpp ================================================ #include "benchmark.h" #include "../engine/ecs/registry.h" #include "../util/log.h" namespace P3D::Engine { class ECSGetFromRegistryBenchmark : public Benchmark { public: ECSGetFromRegistryBenchmark() : Benchmark("ecsGetFromRegistryBenchmark") {} Registry64 registry; std::vector v{1, 2, 3, 4, 5, 6}; int errors = 0; struct A : public RC { int i; A(int i) : i(i) {} }; void init() override { int amount = 1000000; for(int i = 0; i < amount; i++) { auto id = registry.create(); registry.add(id, i); } } void run() override { int i = 0; auto view = registry.view(); for(auto entity : view) { auto& comp = *registry.get(entity); if(comp.i != i) errors++; i++; } } void printResults(double timeTaken) override { Log::error("Amount of errors: %d\n", errors); } } ecsGetFromRegistryBenchmark; class ECSGetFromViewConjunctionBenchmark : public Benchmark { public: ECSGetFromViewConjunctionBenchmark() : Benchmark("ecsGetFromViewConjunctionBenchmark") {} Registry64 registry; int errors = 0; struct A : public RC { int i; A(int i) : i(i) {} }; void init() override { int amount = 1000000; for(int i = 0; i < amount; i++) { auto id = registry.create(); registry.add(id, i); } } void run() override { int i = 0; auto view = registry.view>(); for(auto entity : view) { auto comp = view.get(entity); if(comp->i != i) errors++; i++; } } void printResults(double timeTaken) override { Log::error("Amount of errors: %d\n", errors); } } ecsGetFromViewConjunctionBenchmark; /*class ECSGetFromViewDisjunctionBenchmark : public Benchmark { public: ECSGetFromViewDisjunctionBenchmark() : Benchmark("ecsGetFromViewDisjunctionBenchmark") {} Registry64 registry; int errors = 0; struct A : public RefCountable { int i; A(int i) : i(i) {} }; void init() override { int amount = 1000000; for (int i = 0; i < amount; i++) { auto id = registry.create(); registry.add(id, i); } } void run() override { int i = 0; auto view = registry.view(Registry64::disjunction()); for (auto entity : view) { auto comp = view.get(entity); if (comp->i != i) errors++; i++; } } void printResults(double timeTaken) override { Log::error("Amount of errors: %d\n", errors); } } ecsGetFromViewDisjunctionBenchmark;*/ }; ================================================ FILE: benchmarks/getBoundsPerformance.cpp ================================================ #include "benchmark.h" #include #include #include namespace P3D { class GetBounds : public Benchmark { Polyhedron poly; double result = 0; public: GetBounds() : Benchmark("getBounds") {} void init() override { this->poly = ShapeLibrary::createSphere(1.0, 2); } void run() override { Mat3f m = rotationMatrixfromEulerAngles(0.1f, 0.05f, 0.7f); for(size_t i = 0; i < 10000000; i++) { //Mat3 m = rotationMatrixfromEulerAngles(0.1 * (i%5), 0.2 * (i%6), 0.3 * (i%7)); BoundingBox r = this->poly.getBounds(m); result += r.min.x + r.min.y + r.min.z + r.max.x + r.max.y + r.max.z; } } } getBounds; }; ================================================ FILE: benchmarks/manyCubesBenchmark.cpp ================================================ #include #include #include #include "worldBenchmark.h" #include #include namespace P3D { class ManyCubesBenchmark : public WorldBenchmark { public: ManyCubesBenchmark() : WorldBenchmark("manyCubes", 10000) {} void init() { createFloor(50, 50, 10); int minX = -5; int maxX = 5; int minY = -5; int maxY = 5; int minZ = -5; int maxZ = 5; GlobalCFrame ref(0, 15, 0, Rotation::fromEulerAngles(3.1415 / 4, 3.1415 / 4, 0.0)); //Part* newCube = new Part(boxShape(1.0, 1.0, 1.0), ref.localToGlobal(CFrame(0,0,0)), {1.0, 0.2, 0.5}); //world.addPart(newCube); for(double x = minX; x < maxX; x += 1.01) { for(double y = minY; y < maxY; y += 1.01) { for(double z = minZ; z < maxZ; z += 1.01) { Part* newCube = new Part(polyhedronShape(ShapeLibrary::createBox(1.0, 1.0, 1.0)), ref.localToGlobal(CFrame(x, y, z)), {1.0, 0.2, 0.5}); world.addPart(newCube); } } } } } manyCubesBench; }; ================================================ FILE: benchmarks/rotationBenchmark.cpp ================================================ #include "benchmark.h" #include #include namespace P3D { static double randomDouble() { return double(rand()) / RAND_MAX; } #define ROTATION_BENCH_SIZE 10000000 class RotationLocalToGlobalVecCPU : public Benchmark { Rotation rot; Vec3 curValue; public: RotationLocalToGlobalVecCPU() : Benchmark("rotationL2GVecCPU") {} void init() override { this->rot = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); curValue = Vec3(randomDouble(), randomDouble(), randomDouble()); } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE * 100; round++) { curValue = rot.localToGlobal(curValue); } } } rotationLocalToGlobalVecCPU; class RotationLocalToGlobalMatCPU : public Benchmark { Rotation rot; SymmetricMat3 curValue; public: RotationLocalToGlobalMatCPU() : Benchmark("rotationL2GMatCPU") {} void init() override { this->rot = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); curValue = SymmetricMat3{randomDouble(), randomDouble(), randomDouble(), randomDouble(), randomDouble(), randomDouble()}; } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE * 100; round++) { curValue = rot.localToGlobal(curValue); } } } rotationLocalToGlobalMatCPU; class RotationLocalToGlobalRotCPU : public Benchmark { Rotation rot; Rotation curValue; public: RotationLocalToGlobalRotCPU() : Benchmark("rotationL2GRotCPU") {} void init() override { this->rot = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); curValue = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE * 100; round++) { curValue = rot.localToGlobal(curValue); } } } rotationLocalToGlobalRotCPU; class RotationLocalToGlobalVecMem : public Benchmark { Rotation* rot = nullptr; Vec3 curValue; public: RotationLocalToGlobalVecMem() : Benchmark("rotationL2GVecMem") {} void init() override { if(rot == nullptr) this->rot = new Rotation[ROTATION_BENCH_SIZE]; for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { this->rot[round] = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); } curValue = Vec3(randomDouble(), randomDouble(), randomDouble()); } ~RotationLocalToGlobalVecMem() { delete[] rot; } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { curValue = rot[round].localToGlobal(curValue); } } } rotationLocalToGlobalVecMem; class RotationLocalToGlobalMatMem : public Benchmark { Rotation* rot = nullptr; SymmetricMat3 curValue; public: RotationLocalToGlobalMatMem() : Benchmark("rotationL2GMatMem") {} void init() override { if(rot == nullptr) this->rot = new Rotation[ROTATION_BENCH_SIZE]; for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { this->rot[round] = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); } curValue = SymmetricMat3{randomDouble(), randomDouble(), randomDouble(), randomDouble(), randomDouble(), randomDouble()}; } ~RotationLocalToGlobalMatMem() { delete[] rot; } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { curValue = rot[round].localToGlobal(curValue); } } } rotationLocalToGlobalMatMem; class RotationLocalToGlobalRotMem : public Benchmark { Rotation* rot = nullptr; Rotation curValue; public: RotationLocalToGlobalRotMem() : Benchmark("rotationL2GRotMem") {} void init() override { if(rot == nullptr) this->rot = new Rotation[ROTATION_BENCH_SIZE]; for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { this->rot[round] = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); } curValue = Rotation::fromEulerAngles(randomDouble(), randomDouble(), randomDouble()); } ~RotationLocalToGlobalRotMem() { delete[] rot; } void run() override { for(int round = 0; round < ROTATION_BENCH_SIZE; round++) { curValue = rot[round].localToGlobal(curValue); } } } rotationLocalToGlobalRotMem; }; ================================================ FILE: benchmarks/threadResponseTime.cpp ================================================ #include "benchmark.h" #include #include #include #include #include #include using namespace std::chrono; namespace P3D { class ThreadCreateBenchmark : public Benchmark { public: ThreadCreateBenchmark() : Benchmark("threadCreateResponseTime") {} virtual void init() override {} virtual void run() override { decltype(high_resolution_clock::now()) start; std::mutex coutMutex; std::cout << "\n"; auto work = [&start, &coutMutex]() { auto response = high_resolution_clock::now(); nanoseconds delay = response - start; coutMutex.lock(); std::cout << delay.count() / 1000 << " microseconds\n"; coutMutex.unlock(); std::this_thread::sleep_for(milliseconds(1000)); }; std::vector threads(std::thread::hardware_concurrency() - 1); for(int iter = 0; iter < 5; iter++) { std::cout << "Run " << iter << "\n"; start = high_resolution_clock::now(); for(std::thread& t : threads) t = std::thread(work); work(); for(std::thread& t : threads) t.join(); } } virtual void printResults(double timeTaken) override {} } threadCreate; class ThreadPoolBenchmark : public Benchmark { public: ThreadPoolBenchmark() : Benchmark("threadPoolResponseTime") {} virtual void init() override {} virtual void run() override { decltype(high_resolution_clock::now()) start; std::mutex coutMutex; std::cout << "\n"; auto work = [&start, &coutMutex]() { auto response = high_resolution_clock::now(); nanoseconds delay = response - start; coutMutex.lock(); std::cout << delay.count() / 1000 << " microseconds\n"; coutMutex.unlock(); std::this_thread::sleep_for(milliseconds(1000)); }; std::vector threads(std::thread::hardware_concurrency() - 1); ThreadPool threadPool; for(int iter = 0; iter < 5; iter++) { std::cout << "Run " << iter << "\n"; start = high_resolution_clock::now(); threadPool.doInParallel(work); } } virtual void printResults(double timeTaken) override {} } threadPool; }; ================================================ FILE: benchmarks/worldBenchmark.cpp ================================================ #include "worldBenchmark.h" #include "../util/log.h" #include "../util/terminalColor.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace P3D { WorldBenchmark::WorldBenchmark(const char* name, int tickCount) : Benchmark(name), world(0.005), tickCount(tickCount) { world.addExternalForce(new DirectionalGravity(Vec3(0, -10, 0))); } void WorldBenchmark::run() { world.isValid(); Part& partToTrack = *world.physicals[0]->getMainPart(); for(int i = 0; i < tickCount; i++) { if(i % (tickCount / 8) == 0) { Log::print("Tick %d\n", i); Position pos = partToTrack.getCFrame().getPosition(); Log::print("Location of object: %.5f %.5f %.5f\n", double(pos.x), double(pos.y), double(pos.z)); std::size_t partsOutOfBounds = 0; world.forEachPartFiltered(OutOfBoundsFilter(Bounds(Position(-100.0, -100.0, -100.0), Position(100.0, 100.0, 100.0))), [&partsOutOfBounds](const Part&) { partsOutOfBounds++; }); Log::print("%d/%d parts out of bounds!\n", partsOutOfBounds, world.getPartCount()); } physicsMeasure.mark(PhysicsProcess::OTHER); world.tick(); physicsMeasure.end(); GJKCollidesIterationStatistics.nextTally(); GJKNoCollidesIterationStatistics.nextTally(); EPAIterationStatistics.nextTally(); } world.isValid(); } static const std::size_t LABEL_LENGTH = 23; static const std::size_t COUNT_LENGTH = 11; static const std::size_t FRACTION_LENGTH = 6; static const std::size_t BAR_LENGTH = 36; void printToLength(std::string text, std::size_t length) { std::cout << text; setColor(TerminalColor::BLACK); for(std::size_t i = text.size(); i < length; i++) { std::cout << ' '; } } static TerminalColor colors[]{ TerminalColor::BLUE, TerminalColor::RED, TerminalColor::YELLOW, TerminalColor::MAGENTA, TerminalColor::GREEN, TerminalColor::CYAN, TerminalColor::WHITE, /*Log::RED, Log::GREEN, Log::BLUE, Log::YELLOW, Log::MAGENTA, Log::AQUA, Log::WHITE,*/ }; TerminalColor getColor(std::size_t i) { //return (colors[i] >= 0x7) ? colors[i] : colors[i] | (Log::WHITE << 4); return colors[i % 7]; } /*int getBGColor(std::size_t i) { return colors[i % 7] << 4 | colors[i % 7]; }*/ template void printBreakdown(const T* values, const char** labels, std::size_t N, std::string unit) { T total = values[0]; T max = values[0]; for(std::size_t i = 1; i < N; i++) { total += values[i]; max = (values[i] > max) ? values[i] : max; } for(std::size_t i = 0; i < N; i++) { T v = values[i]; double fractionOfTotal = total != 0 ? double(v) / total : 0.0; double fractionOfMax = total != 0 ? double(v) / max : 0.0; setColor(getColor(i)); printToLength(labels[i] + std::string(":"), LABEL_LENGTH); std::stringstream ss; ss.precision(5); ss << std::fixed; ss << v; ss << unit; setColor(getColor(i)); printToLength(ss.str(), COUNT_LENGTH); std::stringstream ss2; ss2.precision(2); ss2 << std::fixed; ss2 << (fractionOfTotal * 100); ss2 << "%"; setColor(getColor(i)); printToLength(ss2.str(), FRACTION_LENGTH); setColor(TerminalColor::BLACK); std::cout << ' '; setColor(TerminalColor::WHITE, TerminalColor::WHITE); std::cout << ' '; std::size_t thisBarLength = static_cast(std::ceil(BAR_LENGTH * fractionOfMax)); setColor(getColor(i), getColor(i)); for(std::size_t i = 0; i < thisBarLength; i++) { std::cout << '='; } setColor(TerminalColor::BLACK); std::cout << '|'; std::cout << '\n'; } } void WorldBenchmark::printResults(double timeTakenMillis) { double tickTime = (timeTakenMillis) / tickCount; Log::print("%d ticks at %f ticks per second\n", tickCount, 1000 / tickTime); auto physicsBreakdown = physicsMeasure.history.avg(); double millis[physicsMeasure.size()]; for(std::size_t i = 0; i < physicsMeasure.size(); i++) { millis[i] = physicsBreakdown[i].count() / 1000000.0; } setColor(TerminalColor::WHITE); std::cout << "\n"; setColor(TerminalColor::MAGENTA); std::cout << "[Physics Profiler]\n"; printBreakdown(millis, physicsMeasure.labels, physicsMeasure.size(), "ms"); setColor(TerminalColor::WHITE); std::cout << "\n"; setColor(TerminalColor::MAGENTA); std::cout << "[Intersection Statistics]\n"; printBreakdown(intersectionStatistics.history.avg().values, intersectionStatistics.labels, intersectionStatistics.size(), ""); setColor(TerminalColor::WHITE); } void WorldBenchmark::createFloor(double w, double h, double wallHeight) { world.addTerrainPart(new Part(boxShape(w, 1.0, h), GlobalCFrame(0.0, 0.0, 0.0), basicProperties)); world.addTerrainPart(new Part(boxShape(0.8, wallHeight, h), GlobalCFrame(w, wallHeight / 2, 0.0), basicProperties)); world.addTerrainPart(new Part(boxShape(0.8, wallHeight, h), GlobalCFrame(-w, wallHeight / 2, 0.0), basicProperties)); world.addTerrainPart(new Part(boxShape(w, wallHeight, 0.8), GlobalCFrame(0.0, wallHeight / 2, h), basicProperties)); world.addTerrainPart(new Part(boxShape(w, wallHeight, 0.8), GlobalCFrame(0.0, wallHeight / 2, -h), basicProperties)); } }; ================================================ FILE: benchmarks/worldBenchmark.h ================================================ #pragma once #include "benchmark.h" #include namespace P3D { static const PartProperties basicProperties{1.0, 0.7, 0.5}; class WorldBenchmark : public Benchmark { protected: WorldPrototype world; int tickCount; public: WorldBenchmark(const char* name, int tickCount); virtual void run() override; virtual void printResults(double timeTaken) override; void createFloor(double w, double h, double wallHeight); }; }; ================================================ FILE: engine/core.cpp ================================================ #include "core.h" ================================================ FILE: engine/core.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/log.h" ================================================ FILE: engine/ecs/registry.h ================================================ #pragma once #include #include #include #include #include #include #include "../util/typetraits.h" #include "../util/iteratorUtils.h" #include "../util/stringUtil.h" #include "../Physics3D/datastructures/smartPointers.h" namespace P3D::Engine { template struct registry_traits; template struct registry_traits>> : registry_traits> {}; /** * entity = 8 parent bits, 8 self bits */ template <> struct registry_traits { using representation_type = std::uint16_t; using entity_type = std::uint8_t; using component_type = std::uint16_t; static constexpr entity_type entity_mask = 0xFF; static constexpr std::size_t parent_shift = 8u; }; /** * entity = 16 parent bits, 16 self bits */ template <> struct registry_traits { using representation_type = std::uint32_t; using entity_type = std::uint16_t; using component_type = std::uint16_t; static constexpr entity_type entity_mask = 0xFFFF; static constexpr std::size_t parent_shift = 16u; }; /** * entity = 32 parent bits, 32 self bits */ template <> struct registry_traits { using representation_type = std::uint64_t; using entity_type = std::uint32_t; using component_type = std::uint16_t; static constexpr entity_type entity_mask = 0xFFFFFFFF; static constexpr std::size_t parent_shift = 32u; }; template class Registry { //-------------------------------------------------------------------------------------// // Contructor // //-------------------------------------------------------------------------------------// public: Registry() = default; ~Registry() = default; Registry(Registry&&) = delete; Registry(const Registry&) = delete; Registry& operator=(const Registry&) = delete; Registry& operator=(Registry&&) = delete; //-------------------------------------------------------------------------------------// // Types // //-------------------------------------------------------------------------------------// public: using registry_type = Registry; using traits_type = registry_traits; using entity_type = typename traits_type::entity_type; using component_type = typename traits_type::component_type; using representation_type = typename traits_type::representation_type; //-------------------------------------------------------------------------------------// // Member types // //-------------------------------------------------------------------------------------// private: struct entity_compare { bool operator()(const representation_type& left, const representation_type& right) const noexcept { return self(left) < self(right); } }; public: using entity_set = std::set; using entity_queue = std::queue; using entity_map = std::unordered_multimap>; using type_map = std::unordered_map; using component_vector = std::vector; using component_vector_iterator = decltype(std::declval().begin()); using component_map_iterator = decltype(std::declval().begin()); using entity_set_iterator = decltype(std::declval().begin()); // Null entity inline static entity_type null_entity = static_cast(0u); //-------------------------------------------------------------------------------------// // Members // //-------------------------------------------------------------------------------------// private: entity_set entities; component_vector components; type_map type_mapping; entity_queue id_queue; entity_type id_counter = null_entity; constexpr entity_type nextID() noexcept { return ++id_counter; } //-------------------------------------------------------------------------------------// // Helper structs // //-------------------------------------------------------------------------------------// private: template struct type_index { static Type next() noexcept { static Type value{}; return value++; } }; public: template struct component_index { using traits_type = registry_traits; using component_type = typename traits_type::component_type; static component_type index() { static const component_type value = type_index::next(); return value; } }; private: static constexpr entity_type self(const representation_type& entity) noexcept { return static_cast(entity & traits_type::entity_mask); } static constexpr entity_type parent(const representation_type& entity) noexcept { return static_cast(entity >> traits_type::parent_shift) & traits_type::entity_mask; } static constexpr representation_type merge(const entity_type& parent, const entity_type& entity) noexcept { return (static_cast(parent) << traits_type::parent_shift) | static_cast(entity); } //-------------------------------------------------------------------------------------// // View iterators // //-------------------------------------------------------------------------------------// public: struct component_iterator : component_map_iterator { public: component_iterator() = default; explicit component_iterator(component_map_iterator&& iterator) : component_map_iterator(std::move(iterator)) {} auto& operator*() const { return component_map_iterator::operator*().first; } }; //-------------------------------------------------------------------------------------// // View types // //-------------------------------------------------------------------------------------// private: template struct type {}; public: struct no_type { template static IRef get(Registry* registry, const entity_type& entity) { return registry->get(entity); } template static auto getAll(Registry* registry, const entity_type& entity) { return registry->getAll(entity); } }; template struct only { template static IRef get(Registry* registry, const entity_type& entity) { if (std::is_same_v) { component_type index = registry->getComponentIndex(); entity_map* map = registry->components[index]; auto component_iterator = map->find(entity); return intrusive_cast(component_iterator->second); } else { return registry->get(entity); } } template static auto getAll(Registry* registry, const entity_type& entity) { return registry->getAll(entity); } }; template struct conj { template static IRef get(Registry* registry, const entity_type& entity) { if (is_part_of) { component_type index = registry->getComponentIndex(); entity_map* map = registry->components[index]; auto component_iterator = map->find(entity); return intrusive_cast(component_iterator->second); } else { return registry->get(entity); } } template static auto getAll(Registry* registry, const entity_type& entity) { return registry->getAll(entity); } }; template struct disj { template static IRef get(Registry* registry, const entity_type& entity) { return registry->get(entity); } template static auto getAll(Registry* registry, const entity_type& entity) { return registry->getAll(entity); } }; template struct exor {}; template struct neg; //-------------------------------------------------------------------------------------// // Basic view // //-------------------------------------------------------------------------------------// public: template class basic_view { private: Registry* registry; BeginType start; EndType stop; public: basic_view() = default; basic_view(Registry* registry, const BeginType& start, const EndType& stop) : registry(registry) , start(start) , stop(stop) {} BeginType begin() const { return start; } EndType end() const { return stop; } template IRef get(const entity_type& entity) { return ViewType::template get(this->registry, entity); } template auto getAll(const entity_type& entity) { return ViewType::template getAll(this->registry, entity); } }; entity_set_iterator begin() noexcept { return entities.begin(); } entity_set_iterator end() noexcept { return entities.end(); } //-------------------------------------------------------------------------------------// // Views helper Functions // //-------------------------------------------------------------------------------------// private: template std::enable_if_t init() {} template std::enable_if_t extract_smallest_component(component_type& smallest_component, std::size_t& smallest_size, std::vector& other_components) {} template void extract_smallest_component(component_type& smallest_component, std::size_t& smallest_size, std::vector& other_components) noexcept { component_type current_component = getComponentIndex(); const std::size_t current_size = this->components[current_component]->size(); if (current_size < smallest_size) { other_components.push_back(smallest_component); smallest_component = current_component; smallest_size = current_size; } else other_components.push_back(current_component); extract_smallest_component(smallest_component, smallest_size, other_components); } template std::enable_if_t insert_entities(entity_set& entities) {} template void insert_entities(entity_set& entities) noexcept { std::size_t component = getComponentIndex(); entity_map* map = this->components[component]; if (map != nullptr) { component_iterator first(map->begin()); component_iterator last(map->end()); entities.insert(first, last); } insert_entities(entities); } template auto filter_view(const Iterator& first, const Iterator& last, const Filter& filter) noexcept { using iterator_type = filter_iterator; iterator_type start(first, last, filter); iterator_end stop; return basic_view(this, start, stop); } template auto transform_view(const Iterator& first, const Iterator& last, const Transform& transform) noexcept { using iterator_type = transform_iterator; iterator_type start(first, last, transform); iterator_end stop; return basic_view(this, start, stop); } template auto filter_transform_view(const Iterator& first, const Iterator& last, const Filter& filter, const Transform& transform) noexcept { using iterator_type = filter_transform_iterator; iterator_type start(first, last, filter, transform); iterator_end stop; return basic_view(this, start, stop); } template auto default_view(const Iterator& first, const Iterator& last) noexcept { using iterator_type = default_iterator; iterator_type start(first, last); iterator_end stop; return basic_view(this, start, stop); } //-------------------------------------------------------------------------------------// // Filter matching // //-------------------------------------------------------------------------------------// template struct match { constexpr std::enable_if_t, bool> operator()(Registry& registry, const entity_type& entity) { return registry.has(entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return !match()(registry, entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return match()(registry, entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return match()(registry, entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return match()(registry, entity) && match>()(registry, entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return match()(registry, entity) || match>()(registry, entity); } }; template struct match> { constexpr bool operator()(Registry& registry, const entity_type& entity) { return match()(registry, entity) != match()(registry, entity); } }; //-------------------------------------------------------------------------------------// // Mutators // //-------------------------------------------------------------------------------------// public: /** * Returns the index of the given component */ template [[nodiscard]] component_type getComponentIndex() { component_type index = component_index::index(); if (index >= this->type_mapping.size()) { std::string fullName = Util::typeName(); std::string camelCase = Util::demangle(fullName); std::string name = Util::decamel(camelCase); this->type_mapping.insert(std::make_pair(index, name)); } while (index >= this->components.size()) this->components.push_back(new entity_map()); return index; } /** * Returns a string view of name of the component corresponding to the given component id */ [[nodiscard]] std::string_view getComponentName(const component_type& component) { return type_mapping.at(component); } /** * Returns a string view of the name of the given component */ template [[nodiscard]] std::string_view getComponentName() { return this->type_mapping.at(getComponentIndex()); } /** * Initializes the component vector to create an order in the components */ template void init() { component_type index = getComponentIndex(); init(); } /** * Creates a new entity with an empty parent and adds it to the registry */ [[nodiscard]] entity_type create(const entity_type& parent = null_entity) noexcept { representation_type id; if (this->id_queue.empty()) { id = merge(parent, nextID()); } else { entity_type lastID = this->id_queue.front(); id = merge(parent, lastID); this->id_queue.pop(); } this->entities.insert(id); return static_cast(id); } /** * Removes the given entity from the registry, returns the null entity if the entity is successfully removed */ entity_type destroy(const entity_type& entity) noexcept { if (entity == null_entity) return null_entity; auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator != this->entities.end()) { this->entities.erase(entities_iterator); id_queue.push(entity); for (entity_map* map : this->components) { auto component_iterator = map->find(entity); if (component_iterator != map->end()) { // the component's intrusive_ptr will clean up the component map->erase(component_iterator); } } return null_entity; } return entity; } /** * Instantiates a component using the given type and arguments and adds it to the given entity */ template IRef add(const entity_type& entity, Args&&... args) noexcept { auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return IRef(); IRef component = make_intrusive(std::forward(args)...); component_type index = getComponentIndex(); entity_map* map = this->components[index]; auto result = map->insert(std::make_pair(entity, intrusive_cast(component))); return component; } /** * Removes the component with the given component id from the given entity, returns whether the erasure was successful */ bool remove(const entity_type& entity, const component_type& index) noexcept { if (index >= this->components.size()) return false; auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return false; entity_map* map = this->components[index]; auto component_iterator = map->find(entity); if (component_iterator == map->end()) return false; map->erase(component_iterator); // Todo check if really destroyed return true; } /** * Removes the component of the given type from the given entity, returns whether the erasure was successful */ template bool remove(const entity_type& entity) noexcept { component_type index = getComponentIndex(); return remove(entity, index); } //-------------------------------------------------------------------------------------// // Getters // //-------------------------------------------------------------------------------------// /** * Returns the component of the given type from the given entity, nullptr if no such component exists */ template [[nodiscard]] IRef get(const entity_type& entity) noexcept { if (entity == null_entity) return IRef(); auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return IRef(); component_type index = getComponentIndex(); entity_map* map = this->components[index]; auto component_iterator = map->find(entity); if (component_iterator == map->end()) return IRef(); return intrusive_cast(component_iterator->second); } /** * Returns the first component of the given type from the given entity, nullptr if no such component exists */ template [[nodiscard]] auto getAll(const entity_type& entity) noexcept { auto transform = [](const component_map_iterator& iterator) { return intrusive_cast(iterator->second); }; if (entity == null_entity) return transform_view>(component_map_iterator{}, component_map_iterator{}, transform); auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return transform_view>(component_map_iterator{}, component_map_iterator{}, transform); component_type index = getComponentIndex(); entity_map* map = this->components[index]; auto component_range = map->equal_range(entity); if (component_range.first == map->end()) return transform_view>(component_map_iterator{}, component_map_iterator{}, transform); component_map_iterator first(component_range.first); component_map_iterator last(component_range.second); return transform_view>(first, last, transform); } /** * Returns the component of the given type from the given entity, or creates one using the provides arguments and returns it. */ template [[nodiscard]] IRef getOrAdd(const entity_type& entity, Args&&... args) { auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return IRef(); component_type index = getComponentIndex();; entity_map* map = this->components[index]; auto component_iterator = map->find(entity); if (component_iterator == map->end()) { IRef component = make_intrusive(std::forward(args)...); map->insert(std::make_pair(entity, intrusive_cast(component))); return component; } return intrusive_cast(component_iterator->second); } /** * Returns the component of the given type from the given entity, the default value if no such component exists, note that the component will be copied */ template [[nodiscard]] Component getOr(const entity_type& entity, Args&&... args) { IRef result = get(entity); if (result == nullptr) return Component(std::forward(args)...); else return *result; } /** * Returns whether the registry contains the given entity */ [[nodiscard]] bool contains(const entity_type& entity) noexcept { return this->entities.find(static_cast(entity)) != entities.end(); } /** * Returns whether the given entity has a component of the given type */ template [[nodiscard]] bool has(const entity_type& entity) noexcept { return count(entity); } /** * Returns whether the given entity has a component with the given component index */ [[nodiscard]] bool has(const entity_type& entity, const component_type& index) noexcept { return count(entity, index); } /** * Returns the number of components of the given type */ template [[nodiscard]] std::size_t count(const entity_type& entity) noexcept { component_type index = getComponentIndex(); return count(entity, index); } /** * Returns the number of components with the given component index */ [[nodiscard]] std::size_t count(const entity_type& entity, const component_type& index) noexcept { auto entities_iterator = this->entities.find(static_cast(entity)); if (entities_iterator == this->entities.end()) return 0; entity_map* map = this->components[index]; return map->count(entity); } //-------------------------------------------------------------------------------------// // Parent & self // //-------------------------------------------------------------------------------------// /** * Returns the parent of the given entity */ [[nodiscard]] constexpr entity_type getParent(const representation_type& entity) { return parent(entity); } /** * Returns the parent of the given entity */ [[nodiscard]] constexpr entity_type getParent(const entity_type& entity) { entity_set_iterator iterator = this->entities.find(static_cast(entity)); if (iterator != this->entities.end()) return parent(*iterator); return null_entity; } /** * Returns the self id of the given entity */ [[nodiscard]] constexpr entity_type getSelf(const representation_type& entity) { return self(entity); } /** * Returns the self id of the given entity */ [[nodiscard]] constexpr entity_type getSelf(const entity_type& entity) { return entity; } /** * Sets the parent of the given entity to the given parent, returns true if successful, returns false if the entity does not exist. */ bool setParent(const entity_type& entity, const entity_type& parent) noexcept { auto entity_iterator = this->entities.find(static_cast(entity)); if (entity_iterator == this->entities.end()) return false; if (parent != null_entity) { auto parent_iterator = this->entities.find(parent); if (parent_iterator == this->entities.end()) return false; } representation_type newEntity = merge(parent, entity); auto hint_iterator = entity_iterator; ++hint_iterator; this->entities.erase(entity_iterator); this->entities.insert(hint_iterator, newEntity); return true; } /** * Returns the children of the given parent entity */ [[nodiscard]] auto getChildren(const entity_type& entity) noexcept { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); auto filter = [this, entity](const entity_set_iterator& iterator) { return parent(*iterator) == entity; }; auto transform = [](const entity_set_iterator& iterator) { return self(*iterator); }; if (!contains(entity)) return filter_transform_view(last, last, filter, transform); return filter_transform_view(first, last, filter, transform); } /** * Returns the children of the given parent entity */ [[nodiscard]] auto getChildren(const representation_type& entity) noexcept { return getChildren(self(entity)); } //-------------------------------------------------------------------------------------// // Views // //-------------------------------------------------------------------------------------// /** * Returns an iterator which iterates over all entities having all the given components */ private: template [[nodiscard]] auto view(type>) noexcept { // Todo make more efficient return view(type>{}); } template [[nodiscard]] auto view(type>) noexcept { static_assert(unique_types); std::vector other_components; other_components.reserve(sizeof...(Components)); component_type smallest_component = getComponentIndex(); std::size_t smallest_size = this->components[smallest_component]->size(); extract_smallest_component(smallest_component, smallest_size, other_components); auto filter = [this, other_components](const component_map_iterator& iterator) { for (component_type component : other_components) { auto component_iterator = this->components[component]->find(iterator->first); if (component_iterator == this->components[component]->end()) return false; } return true; }; auto transform = [](const component_map_iterator& iterator) { return iterator->first; }; entity_map* map = this->components[smallest_component]; component_map_iterator first(map->begin()); component_map_iterator last(map->end()); return filter_transform_view>(first, last, filter, transform); } /** * Returns an iterator which iterates over all entities having any of the given components */ template [[nodiscard]] auto view(type>) noexcept { static_assert(unique_types); entity_set entities; insert_entities(entities); auto filter = [entities](const entity_set_iterator& iterator) { return entities.find(*iterator) != entities.end(); }; return filter_view>(this->entities.begin(), this->entities.end(), filter); } public: /** * Returns a view which iterates over the components of the given entity */ [[nodiscard]] auto getComponents(const entity_type& entity) noexcept { component_vector_iterator first = components.begin(); component_vector_iterator last = components.end(); auto filter = [entity](const component_vector_iterator& iterator) { entity_map* map = *iterator; return map->find(entity) != map->end(); }; auto transform = [this, first, entity](const component_vector_iterator& iterator) { entity_map* map = *iterator; auto component_range = map->equal_range(entity); component_map_iterator firstComponent(component_range.first); component_map_iterator lastComponent(component_range.second); auto component_transform = [](const component_map_iterator& iterator) { return iterator->second; }; auto result = std::make_pair(std::distance(first, iterator), transform_view(firstComponent, lastComponent, component_transform)); return result; }; return filter_transform_view(first, last, filter, transform); } /** * Returns a view that iterates over all entities which satisfy the given filter */ template [[nodiscard]] auto filter(const Filter& filter) { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); return filter_view(first, last, filter); } /** * Returns a view that iterates over all entities which satisfy the given filter */ template [[nodiscard]] auto filter() { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); auto filter = [this](const entity_set_iterator& iterator) { return !match()(*this, *iterator); }; return filter_view(first, last, filter); } /** * Returns a view that iterates over all entities and outputs the transformed entity */ template [[nodiscard]] auto transform(const Transform& transform) { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); return transform_view(first, last, transform); } /** * Returns a view that iterates over all entities which satisfy the given filter and outputs the transformed entity */ template [[nodiscard]] auto filter_transform(const Filter& filter, const Transform& transform) { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); return filter_transform_view(first, last, filter, transform); } /** * Returns a view that iterates over all entities which satisfy the given filter and outputs the transformed entity */ template [[nodiscard]] auto filter_transform(const Transform& transform) { entity_set_iterator first = this->entities.begin(); entity_set_iterator last = this->entities.end(); auto filter = [](const entity_set_iterator& iterator) { return !match()(*iterator); }; return filter_transform_view(first, last, filter, transform); } /** * Returns an iterator which iterates over all entities which satisfy the view type */ template [[nodiscard]] auto view() noexcept { if constexpr (sizeof...(Type) == 1) if constexpr (std::is_base_of_v) return view(type>{}); else return view(type{}); else return view(type>{}); } }; typedef Registry Registry16; typedef Registry Registry32; typedef Registry Registry64; }; ================================================ FILE: engine/engine.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {ADC11C63-6986-41DC-9297-FC5DC58A2B55} engine 10.0 Application true v142 MultiByte Application false v142 true MultiByte StaticLibrary true v142 MultiByte StaticLibrary false v142 true MultiByte Level3 MaxSpeed true true true true $(SolutionDir)include;$(SolutionDir)engine;$(SolutionDir); Use core.h _MBCS;NDEBUG;%(PreprocessorDefinitions) stdcpp17 NotSet true true true $(SolutionDir)lib;$(OutDir) %(AdditionalDependencies) Level3 Disabled true true Level3 Disabled true true $(SolutionDir)include;$(SolutionDir)engine;$(SolutionDir); Use core.h true stdcpp17 true NotSet %(AdditionalDependencies) $(SolutionDir)lib;$(OutDir) true Level3 MaxSpeed true true true true true true Create core.h Create Create core.h ================================================ FILE: engine/event/event.h ================================================ #pragma once #include namespace P3D::Engine { enum class EventType { None = 0, WindowClose, WindowResize, WindowDrop, FrameBufferResize, MouseScroll, MouseMove, MouseDrag, MousePress, MouseRelease, MouseExit, MouseEnter, KeyPress, KeyRelease, KeyDoublePress }; enum EventCategory : char { EventCategoryNone = 0 << 0, EventCategoryInput = 1 << 0, EventCategoryMouse = 1 << 1, EventCategoryMouseButton = 1 << 2, EventCategoryKeyboard = 1 << 3, EventCategoryWindow = 1 << 4, EventCategoryApplication = 1 << 5 }; #define EVENT_TYPE(type) \ inline static EventType getStaticType() { return EventType::type; } \ inline virtual EventType getType() const override { return getStaticType(); } \ inline virtual std::string getName() const override { return #type; } #define EVENT_CATEGORY(category) \ virtual char getCategory() const override { return category; } struct Event { public: bool handled = false; [[nodiscard]] virtual EventType getType() const = 0; [[nodiscard]] virtual char getCategory() const = 0; [[nodiscard]] virtual std::string getName() const = 0; [[nodiscard]] bool inCategory(char category) const { return (category & getCategory()) != 0; } [[nodiscard]] bool inCategory(const EventCategory& category) const { return inCategory(static_cast(category)); } }; #define EVENT_BIND(function) \ [&] (auto& event) -> bool { return function(event); } class EventDispatcher { private: Event& event; public: EventDispatcher(Event& event) : event(event) {} template bool dispatch(const F& function) { if (event.getType() == T::getStaticType()) { event.handled = function(static_cast(event)); return true; } return false; } }; }; ================================================ FILE: engine/event/keyEvent.h ================================================ #pragma once #include "event.h" #include "../input/keyboard.h" #include "../input/modifiers.h" namespace P3D::Engine { class KeyEvent : public Event { private: Key key; Modifiers modifiers; public: EVENT_CATEGORY(EventCategoryInput | EventCategoryKeyboard); inline Key getKey() const { return key; } inline Modifiers getModifiers() const { return modifiers; } protected: inline KeyEvent(const Key& key, const Modifiers& modifiers) : key(key), modifiers(modifiers) {} }; class DoubleKeyPressEvent : public KeyEvent { public: EVENT_TYPE(KeyDoublePress); inline DoubleKeyPressEvent(const Key& key, const Modifiers& modifiers) : KeyEvent(key, modifiers) {} }; class KeyPressEvent : public KeyEvent { private: bool repeat; public: EVENT_TYPE(KeyPress); inline KeyPressEvent(const Key& key, const Modifiers& modifiers = 0, bool repeat = false) : KeyEvent(key, modifiers), repeat(repeat) {} inline bool isRepeated() const { return repeat; } }; class KeyReleaseEvent : public KeyEvent { public: EVENT_TYPE(KeyRelease); inline KeyReleaseEvent(const Key& key, const Modifiers& modifiers = 0) : KeyEvent(key, modifiers) {} }; }; ================================================ FILE: engine/event/mouseEvent.h ================================================ #pragma once #include "event.h" #include "../input/mouse.h" #include "../input/modifiers.h" namespace P3D::Engine { class MouseEvent : public Event { private: int x; int y; protected: inline MouseEvent(int x, int y) : x(x), y(y) {}; public: EVENT_CATEGORY(EventCategoryMouse | EventCategoryInput); // Returns the x position at the time of the event inline int getX() const { return x; } // Returns the y position at the time of the event inline int getY() const { return y; } }; class MouseMoveEvent : public MouseEvent { private: int newX; int newY; public: EVENT_TYPE(MouseMove); inline MouseMoveEvent(int oldX, int oldY, int newX, int newY) : MouseEvent(oldX, oldY), newX(newX), newY(newY) {} inline int getOldX() const { return getX(); } inline int getOldY() const { return getY(); } inline int getNewX() const { return newX; } inline int getNewY() const { return newY; } }; class MouseScrollEvent : public MouseEvent { private: int xOffset; int yOffset; public: EVENT_TYPE(MouseScroll); inline MouseScrollEvent(int x, int y, int xOffset, int yOffset) : MouseEvent(x, y), xOffset(xOffset), yOffset(yOffset) {} inline int getXOffset() const { return xOffset; } inline int getYOffset() const { return yOffset; } }; class MouseEnterEvent : public MouseEvent { public: EVENT_TYPE(MouseEnter); inline MouseEnterEvent(int x, int y) : MouseEvent(x, y) {} }; class MouseExitEvent : public MouseEvent { public: EVENT_TYPE(MouseExit); inline MouseExitEvent(int x, int y) : MouseEvent(x, y) {} }; class MouseButtonEvent : public MouseEvent { private: Button button; Modifiers modifiers; protected: inline MouseButtonEvent(int x, int y, const Button& button, const Modifiers& modifiers = Modifier::NONE) : MouseEvent(x, y), button(button), modifiers(modifiers) {} public: EVENT_CATEGORY(EventCategoryMouse | EventCategoryMouseButton | EventCategoryInput); inline Button getButton() const { return button; } inline Modifiers getModifiers() const { return modifiers; } }; class MousePressEvent : public MouseButtonEvent { public: EVENT_TYPE(MousePress); inline MousePressEvent(int x, int y, const Button& button, const Modifiers& modifiers = Modifier::NONE) : MouseButtonEvent(x, y, button, modifiers) {} }; class MouseReleaseEvent : public MouseButtonEvent { public: EVENT_TYPE(MouseRelease); inline MouseReleaseEvent(int x, int y, const Button& button, const Modifiers& modifiers = Modifier::NONE) : MouseButtonEvent(x, y, button, modifiers) {} }; class MouseDragEvent : public MouseButtonEvent { private: int newX; int newY; public: EVENT_TYPE(MouseDrag); inline MouseDragEvent(int oldX, int oldY, int newX, int newY, const Button& button, const Modifiers& modifiers) : MouseButtonEvent(oldX, oldY, button, modifiers), newX(newX), newY(newY) {} inline int getOldX() const { return getX(); } inline int getOldY() const { return getY(); } inline int getNewX() const { return newX; } inline int getNewY() const { return newY; } inline bool isLeftDragging() const { return getButton() == Mouse::LEFT; } inline bool isMiddleDragging() const { return getButton() == Mouse::MIDDLE; } inline bool isRightDragging() const { return getButton() == Mouse::RIGHT; } }; }; ================================================ FILE: engine/event/windowEvent.h ================================================ #pragma once #include "event.h" namespace P3D::Engine { class WindowEvent : public Event { public: EVENT_CATEGORY(EventCategoryWindow); protected: inline WindowEvent() {}; }; class WindowResizeEvent : public WindowEvent { private: unsigned int width; unsigned int height; public: EVENT_TYPE(WindowResize); inline WindowResizeEvent(unsigned int width, unsigned int height) : WindowEvent(), width(width), height(height) {}; inline unsigned int getWidth() const { return width; } inline unsigned int getHeight() const { return height; } }; class WindowCloseEvent : public WindowEvent { public: EVENT_TYPE(WindowClose); inline WindowCloseEvent() : WindowEvent() {}; }; class WindowDropEvent : public WindowEvent { private: std::string path; public: EVENT_TYPE(WindowDrop); inline WindowDropEvent(const std::string& path) : WindowEvent(), path(path) {}; inline std::string getPath() const { return path; } }; class FrameBufferResizeEvent : public WindowEvent { private: unsigned int width; unsigned int height; public: EVENT_TYPE(FrameBufferResize); inline FrameBufferResizeEvent(unsigned int width, unsigned int height) : WindowEvent(), width(width), height(height) {}; inline unsigned int getWidth() const { return width; } inline unsigned int getHeight() const { return height; } }; }; ================================================ FILE: engine/input/inputHandler.cpp ================================================ #include "core.h" #include "inputHandler.h" #include "keyboard.h" #include "mouse.h" #include "event/event.h" #include "event/keyEvent.h" #include "event/mouseEvent.h" #include "event/windowEvent.h" namespace P3D::Engine { #pragma region callbacks void InputHandler::keyCallback(int code, int action, int mods) { Key key = Keyboard::getKey(code); Modifiers modifiers = Modifiers(mods); if (action == Keyboard::KEY_PRESS) { if (keys[key.getCode()] == false && glfwGetTime() - timestamp[key.getCode()] < keyInterval) { DoubleKeyPressEvent event(key, mods); onEvent(event); } else { KeyPressEvent event(key, mods); onEvent(event); } keys[key.getCode()] = true; timestamp[key.getCode()] = glfwGetTime(); anyKey++; } else if (action == Keyboard::KEY_RELEASE) { keys[key.getCode()] = false; anyKey--; KeyReleaseEvent event(key, mods); onEvent(event); } else if (action == Keyboard::KEY_REPEAT) { KeyPressEvent event(key, mods, true); onEvent(event); } } void InputHandler::cursorCallback(double x, double y) { int oldX = static_cast(this->mousePosition.x); int oldY = static_cast(this->mousePosition.y); int newX = static_cast(x - viewport.x); int newY = static_cast(y - viewport.y); MouseMoveEvent event(oldX, oldY, newX, newY); onEvent(event); if (leftDragging) { MouseDragEvent event(oldX, oldY, newX, newY, Mouse::LEFT, Modifiers(0)); onEvent(event); } if (middleDragging) { MouseDragEvent event(oldX, oldY, newX, newY, Mouse::MIDDLE, Modifiers(0)); onEvent(event); } if (rightDragging) { MouseDragEvent event(oldX, oldY, newX, newY, Mouse::RIGHT, Modifiers(0)); onEvent(event); } this->mousePosition = Vec2(newX, newY); } void InputHandler::cursorEnterCallback(int entered) { int oldX = static_cast(this->mousePosition.x); int oldY = static_cast(this->mousePosition.y); if (entered) { MouseEnterEvent event(oldX, oldY); onEvent(event); } else { leftDragging = false; middleDragging = false; rightDragging = false; MouseExitEvent event(oldX, oldY); onEvent(event); } } void InputHandler::scrollCallback(double xOffset, double yOffset) { MouseScrollEvent event(static_cast(mousePosition.x), static_cast(mousePosition.y), static_cast(xOffset), static_cast(yOffset)); onEvent(event); } void InputHandler::mouseButtonCallback(int button, int action, int mods) { int oldX = static_cast(this->mousePosition.x); int oldY = static_cast(this->mousePosition.y); if (action == Mouse::PRESS) { if (Mouse::RIGHT == button) rightDragging = true; if (Mouse::MIDDLE == button) middleDragging = true; if (Mouse::LEFT == button) leftDragging = true; MousePressEvent event(oldX, oldY, Mouse::getButton(button), Modifiers(mods)); onEvent(event); } else if (action == Mouse::RELEASE) { if (Mouse::RIGHT == button) rightDragging = false; if (Mouse::MIDDLE == button) middleDragging = false; if (Mouse::LEFT == button) leftDragging = false; MouseReleaseEvent event(oldX, oldY, Mouse::getButton(button), Modifiers(mods)); onEvent(event); } } void InputHandler::windowSizeCallback(int width, int height) { WindowResizeEvent event(width, height); onEvent(event); } void InputHandler::windowDropCallback(const char* path) { std::string stdPath(path); WindowDropEvent event(stdPath); onEvent(event); } void InputHandler::framebufferSizeCallback(int width, int height) { FrameBufferResizeEvent event(width, height); onEvent(event); } #pragma endregion Vec2 InputHandler::getMousePosition() const { double x, y; glfwGetCursorPos(window, &x, &y); return Vec2(x - viewport.x, y - viewport.y); } bool InputHandler::getKey(const Key& key) const { if (key.getCode() < Keyboard::KEY_FIRST || key.getCode() > Keyboard::KEY_LAST) return false; return keys[key.getCode()]; } void InputHandler::setKey(const Key& key, bool pressed) { if (key.getCode() < Keyboard::KEY_FIRST || key.getCode() > Keyboard::KEY_LAST) return; keys[key.getCode()] = pressed; } void InputHandler::setCallBacks(GLFWwindow* window) { glfwSetWindowUserPointer(window, this); glfwSetKeyCallback(window, [] (GLFWwindow* window, int key, int scancode, int action, int mods) { static_cast(glfwGetWindowUserPointer(window))->keyCallback(key, action, mods); }); glfwSetCursorPosCallback(window, [] (GLFWwindow* window, double x, double y) { static_cast(glfwGetWindowUserPointer(window))->cursorCallback(x, y); }); glfwSetCursorEnterCallback(window, [] (GLFWwindow* window, int entered) { static_cast(glfwGetWindowUserPointer(window))->cursorEnterCallback(entered); }); glfwSetScrollCallback(window, [] (GLFWwindow* window, double xOffset, double yOffset) { static_cast(glfwGetWindowUserPointer(window))->scrollCallback(xOffset, yOffset); }); glfwSetMouseButtonCallback(window, [] (GLFWwindow* window, int button, int action, int mods) { static_cast(glfwGetWindowUserPointer(window))->mouseButtonCallback(button, action, mods); }); glfwSetWindowSizeCallback(window, [] (GLFWwindow* window, int width, int height) { static_cast(glfwGetWindowUserPointer(window))->windowSizeCallback(width, height); }); glfwSetFramebufferSizeCallback(window, [] (GLFWwindow* window, int width, int height) { static_cast(glfwGetWindowUserPointer(window))->framebufferSizeCallback(width, height); }); glfwSetDropCallback(window, [] (GLFWwindow* window, int count, const char** paths) { InputHandler* inputHandler = static_cast(glfwGetWindowUserPointer(window)); for (int i = 0; i < count; i++) inputHandler->windowDropCallback(paths[i]); }); } InputHandler::InputHandler(GLFWwindow* window) : window(window) { for (bool& k : this->keys) k = false; setCallBacks(window); } }; ================================================ FILE: engine/input/inputHandler.h ================================================ #pragma once #include "keyboard.h" #include namespace P3D::Engine { class Event; class InputHandler { private: void setCallBacks(GLFWwindow* window); protected: GLFWwindow* window; private: bool modifiers[1]; double timestamp[GLFW_KEY_LAST + 1]; void keyCallback(int key, int action, int mods); void cursorCallback(double x, double y); void cursorEnterCallback(int entered); void windowDropCallback(const char* path); void scrollCallback(double xOffset, double yOffset); void mouseButtonCallback(int button, int action, int mods); void windowSizeCallback(int width, int height); void framebufferSizeCallback(int width, int height); double keyInterval = 0.2; public: bool keys[GLFW_KEY_LAST + 1]; char anyKey = 0; bool rightDragging = false; bool middleDragging = false; bool leftDragging = false; Vec2 mousePosition; Vec4 viewport; InputHandler(GLFWwindow* window); bool getKey(const Key& key) const; void setKey(const Key& key, bool pressed); Vec2 getMousePosition() const; virtual void onEvent(Event& event) {}; }; }; ================================================ FILE: engine/input/keyboard.cpp ================================================ #include "core.h" #include "keyboard.h" #include #include namespace P3D::Engine { std::string Key::getName() const { return name; } int Key::getCode() const { return code; } // Key <-> Key bool Key::operator==(const Key& other) const { return other.code == code; } bool Key::operator!=(const Key& other) const { return other.code != code; } bool Key::operator>=(const Key& other) const { return other.code >= code; } bool Key::operator>(const Key& other) const { return other.code > code; } bool Key::operator<=(const Key& other) const { return other.code <= code; } bool Key::operator<(const Key& other) const { return other.code < code; } // Key <-> int bool Key::operator==(int other) const { return other == code; } bool Key::operator!=(int other) const { return other != code; } bool Key::operator>=(int other) const { return other >= code; } bool Key::operator>(int other) const { return other > code; } bool Key::operator<=(int other) const { return other <= code; } bool Key::operator<(int other) const { return other < code; } // Key <-> string bool Key::operator==(const std::string& other) const { return other == name; } bool Key::operator!=(const std::string& other) const { return other != name; } namespace Keyboard { const Key KEY_UNKNOWN = { "none" , GLFW_KEY_UNKNOWN }; const Key KEY_SPACE = { "space" , GLFW_KEY_SPACE }; const Key KEY_APOSTROPHE = { "apostrophe" , GLFW_KEY_APOSTROPHE }; const Key KEY_COMMA = { "comma" , GLFW_KEY_COMMA }; const Key KEY_MINUS = { "minus" , GLFW_KEY_MINUS }; const Key KEY_PERIOD = { "period" , GLFW_KEY_PERIOD }; const Key KEY_SLASH = { "slash" , GLFW_KEY_SLASH }; const Key KEY_NUMBER_0 = { "number_0" , GLFW_KEY_0 }; const Key KEY_NUMBER_1 = { "number_1" , GLFW_KEY_1 }; const Key KEY_NUMBER_2 = { "number_2" , GLFW_KEY_2 }; const Key KEY_NUMBER_3 = { "number_3" , GLFW_KEY_3 }; const Key KEY_NUMBER_4 = { "number_4" , GLFW_KEY_4 }; const Key KEY_NUMBER_5 = { "number_5" , GLFW_KEY_5 }; const Key KEY_NUMBER_6 = { "number_6" , GLFW_KEY_6 }; const Key KEY_NUMBER_7 = { "number_7" , GLFW_KEY_7 }; const Key KEY_NUMBER_8 = { "number_8" , GLFW_KEY_8 }; const Key KEY_NUMBER_9 = { "number_9" , GLFW_KEY_9 }; const Key KEY_SEMICOLON = { "semicolon" , GLFW_KEY_SEMICOLON }; const Key KEY_EQUAL = { "equal" , GLFW_KEY_EQUAL }; const Key KEY_A = { "a" , GLFW_KEY_A }; const Key KEY_B = { "b" , GLFW_KEY_B }; const Key KEY_C = { "c" , GLFW_KEY_C }; const Key KEY_D = { "d" , GLFW_KEY_D }; const Key KEY_E = { "e" , GLFW_KEY_E }; const Key KEY_F = { "f" , GLFW_KEY_F }; const Key KEY_G = { "g" , GLFW_KEY_G }; const Key KEY_H = { "h" , GLFW_KEY_H }; const Key KEY_I = { "i" , GLFW_KEY_I }; const Key KEY_J = { "j" , GLFW_KEY_J }; const Key KEY_K = { "k" , GLFW_KEY_K }; const Key KEY_L = { "l" , GLFW_KEY_L }; const Key KEY_M = { "m" , GLFW_KEY_M }; const Key KEY_N = { "n" , GLFW_KEY_N }; const Key KEY_O = { "o" , GLFW_KEY_O }; const Key KEY_P = { "p" , GLFW_KEY_P }; const Key KEY_Q = { "q" , GLFW_KEY_Q }; const Key KEY_R = { "r" , GLFW_KEY_R }; const Key KEY_S = { "s" , GLFW_KEY_S }; const Key KEY_T = { "t" , GLFW_KEY_T }; const Key KEY_U = { "u" , GLFW_KEY_U }; const Key KEY_V = { "v" , GLFW_KEY_V }; const Key KEY_W = { "w" , GLFW_KEY_W }; const Key KEY_X = { "x" , GLFW_KEY_X }; const Key KEY_Y = { "y" , GLFW_KEY_Y }; const Key KEY_Z = { "z" , GLFW_KEY_Z }; const Key KEY_LEFT_BRACKET = { "left_bracket" , GLFW_KEY_LEFT_BRACKET }; const Key KEY_BACKSLASH = { "backslash" , GLFW_KEY_BACKSLASH }; const Key KEY_RIGHT_BRACKET = { "right_bracket" , GLFW_KEY_RIGHT_BRACKET }; const Key KEY_GRAVE_ACCENT = { "grave_accent" , GLFW_KEY_GRAVE_ACCENT }; const Key KEY_WORLD_1 = { "world_1" , GLFW_KEY_WORLD_1 }; const Key KEY_WORLD_2 = { "world_2" , GLFW_KEY_WORLD_2 }; const Key KEY_ESCAPE = { "escape" , GLFW_KEY_ESCAPE }; const Key KEY_ENTER = { "enter" , GLFW_KEY_ENTER }; const Key KEY_TAB = { "tab" , GLFW_KEY_TAB }; const Key KEY_BACKSPACE = { "backspace" , GLFW_KEY_BACKSPACE }; const Key KEY_INSERT = { "insert" , GLFW_KEY_INSERT }; const Key KEY_DELETE = { "delete" , GLFW_KEY_DELETE }; const Key KEY_RIGHT = { "right" , GLFW_KEY_RIGHT }; const Key KEY_LEFT = { "left" , GLFW_KEY_LEFT }; const Key KEY_DOWN = { "down" , GLFW_KEY_DOWN }; const Key KEY_UP = { "up" , GLFW_KEY_UP }; const Key KEY_PAGE_UP = { "page_up" , GLFW_KEY_PAGE_UP }; const Key KEY_PAGE_DOWN = { "page_down" , GLFW_KEY_PAGE_DOWN }; const Key KEY_HOME = { "home" , GLFW_KEY_HOME }; const Key KEY_END = { "end" , GLFW_KEY_END }; const Key KEY_CAPS_LOCK = { "caps_lock" , GLFW_KEY_CAPS_LOCK }; const Key KEY_SCROLL_LOCK = { "scroll_lock" , GLFW_KEY_SCROLL_LOCK }; const Key KEY_NUM_LOCK = { "num_lock" , GLFW_KEY_NUM_LOCK }; const Key KEY_PRINT_SCREEN = { "print_screen" , GLFW_KEY_PRINT_SCREEN }; const Key KEY_PAUSE = { "pause" , GLFW_KEY_PAUSE }; const Key KEY_F1 = { "f1" , GLFW_KEY_F1 }; const Key KEY_F2 = { "f2" , GLFW_KEY_F2 }; const Key KEY_F3 = { "f3" , GLFW_KEY_F3 }; const Key KEY_F4 = { "f4" , GLFW_KEY_F4 }; const Key KEY_F5 = { "f5" , GLFW_KEY_F5 }; const Key KEY_F6 = { "f6" , GLFW_KEY_F6 }; const Key KEY_F7 = { "f7" , GLFW_KEY_F7 }; const Key KEY_F8 = { "f8" , GLFW_KEY_F8 }; const Key KEY_F9 = { "f9" , GLFW_KEY_F9 }; const Key KEY_F10 = { "f10" , GLFW_KEY_F10 }; const Key KEY_F11 = { "f11" , GLFW_KEY_F11 }; const Key KEY_F12 = { "f12" , GLFW_KEY_F12 }; const Key KEY_F13 = { "f13" , GLFW_KEY_F13 }; const Key KEY_F14 = { "f14" , GLFW_KEY_F14 }; const Key KEY_F15 = { "f15" , GLFW_KEY_F15 }; const Key KEY_F16 = { "f16" , GLFW_KEY_F16 }; const Key KEY_F17 = { "f17" , GLFW_KEY_F17 }; const Key KEY_F18 = { "f18" , GLFW_KEY_F18 }; const Key KEY_F19 = { "f19" , GLFW_KEY_F19 }; const Key KEY_F20 = { "f20" , GLFW_KEY_F20 }; const Key KEY_F21 = { "f21" , GLFW_KEY_F21 }; const Key KEY_F22 = { "f22" , GLFW_KEY_F22 }; const Key KEY_F23 = { "f23" , GLFW_KEY_F23 }; const Key KEY_F24 = { "f24" , GLFW_KEY_F24 }; const Key KEY_F25 = { "f25" , GLFW_KEY_F25 }; const Key KEY_NUMPAD_0 = { "numpad_0" , GLFW_KEY_KP_0 }; const Key KEY_NUMPAD_1 = { "numpad_1" , GLFW_KEY_KP_1 }; const Key KEY_NUMPAD_2 = { "numpad_2" , GLFW_KEY_KP_2 }; const Key KEY_NUMPAD_3 = { "numpad_3" , GLFW_KEY_KP_3 }; const Key KEY_NUMPAD_4 = { "numpad_4" , GLFW_KEY_KP_4 }; const Key KEY_NUMPAD_5 = { "numpad_5" , GLFW_KEY_KP_5 }; const Key KEY_NUMPAD_6 = { "numpad_6" , GLFW_KEY_KP_6 }; const Key KEY_NUMPAD_7 = { "numpad_7" , GLFW_KEY_KP_7 }; const Key KEY_NUMPAD_8 = { "numpad_8" , GLFW_KEY_KP_8 }; const Key KEY_NUMPAD_9 = { "numpad_9" , GLFW_KEY_KP_9 }; const Key KEY_NUMPAD_DECIMAL = { "numpad_decimal" , GLFW_KEY_KP_DECIMAL }; const Key KEY_NUMPAD_DIVIDE = { "numpad_divide" , GLFW_KEY_KP_DIVIDE }; const Key KEY_NUMPAD_MULTIPLY = { "numpad_multiply", GLFW_KEY_KP_MULTIPLY }; const Key KEY_NUMPAD_SUBTRACT = { "numpad_subtract", GLFW_KEY_KP_SUBTRACT }; const Key KEY_NUMPAD_ADD = { "numpad_add" , GLFW_KEY_KP_ADD }; const Key KEY_NUMPAD_ENTER = { "numpad_enter" , GLFW_KEY_KP_ENTER }; const Key KEY_NUMPAD_EQUAL = { "numpad_equal" , GLFW_KEY_KP_EQUAL }; const Key KEY_LEFT_SHIFT = { "left_shift" , GLFW_KEY_LEFT_SHIFT }; const Key KEY_LEFT_CONTROL = { "left_control" , GLFW_KEY_LEFT_CONTROL }; const Key KEY_LEFT_ALT = { "left_alt" , GLFW_KEY_LEFT_ALT }; const Key KEY_LEFT_SUPER = { "left_super" , GLFW_KEY_LEFT_SUPER }; const Key KEY_RIGHT_SHIFT = { "rigth_shift" , GLFW_KEY_RIGHT_SHIFT }; const Key KEY_RIGHT_CONTROL = { "right_control" , GLFW_KEY_RIGHT_CONTROL }; const Key KEY_RIGHT_ALT = { "right_alt" , GLFW_KEY_RIGHT_ALT }; const Key KEY_RIGHT_SUPER = { "right_super" , GLFW_KEY_RIGHT_SUPER }; const Key KEY_MENU = { "menu" , GLFW_KEY_MENU }; const int KEY_FIRST = GLFW_KEY_SPACE; const int KEY_LAST = GLFW_KEY_LAST; const int KEY_PRESS = GLFW_PRESS; const int KEY_RELEASE = GLFW_RELEASE; const int KEY_REPEAT = GLFW_REPEAT; std::map keymap = { { KEY_UNKNOWN.getCode() , KEY_UNKNOWN }, { KEY_SPACE.getCode() , KEY_SPACE }, { KEY_APOSTROPHE.getCode() , KEY_APOSTROPHE }, { KEY_COMMA.getCode() , KEY_COMMA }, { KEY_MINUS.getCode() , KEY_MINUS }, { KEY_PERIOD.getCode() , KEY_PERIOD }, { KEY_SLASH.getCode() , KEY_SLASH }, { KEY_NUMBER_0.getCode() , KEY_NUMBER_0 }, { KEY_NUMBER_1.getCode() , KEY_NUMBER_1 }, { KEY_NUMBER_2.getCode() , KEY_NUMBER_2 }, { KEY_NUMBER_3.getCode() , KEY_NUMBER_3 }, { KEY_NUMBER_4.getCode() , KEY_NUMBER_4 }, { KEY_NUMBER_5.getCode() , KEY_NUMBER_5 }, { KEY_NUMBER_6.getCode() , KEY_NUMBER_6 }, { KEY_NUMBER_7.getCode() , KEY_NUMBER_7 }, { KEY_NUMBER_8.getCode() , KEY_NUMBER_8 }, { KEY_NUMBER_9.getCode() , KEY_NUMBER_9 }, { KEY_SEMICOLON.getCode() , KEY_SEMICOLON }, { KEY_A.getCode() , KEY_A }, { KEY_B.getCode() , KEY_B }, { KEY_C.getCode() , KEY_C }, { KEY_D.getCode() , KEY_D }, { KEY_E.getCode() , KEY_E }, { KEY_F.getCode() , KEY_F }, { KEY_G.getCode() , KEY_G }, { KEY_H.getCode() , KEY_H }, { KEY_I.getCode() , KEY_I }, { KEY_J.getCode() , KEY_J }, { KEY_K.getCode() , KEY_K }, { KEY_L.getCode() , KEY_L }, { KEY_M.getCode() , KEY_M }, { KEY_N.getCode() , KEY_N }, { KEY_O.getCode() , KEY_O }, { KEY_P.getCode() , KEY_P }, { KEY_Q.getCode() , KEY_Q }, { KEY_R.getCode() , KEY_R }, { KEY_S.getCode() , KEY_S }, { KEY_T.getCode() , KEY_T }, { KEY_U.getCode() , KEY_U }, { KEY_V.getCode() , KEY_V }, { KEY_W.getCode() , KEY_W }, { KEY_X.getCode() , KEY_X }, { KEY_Y.getCode() , KEY_Y }, { KEY_Z.getCode() , KEY_Z }, { KEY_LEFT_BRACKET.getCode() , KEY_LEFT_BRACKET }, { KEY_BACKSLASH.getCode() , KEY_BACKSLASH }, { KEY_RIGHT_BRACKET.getCode() , KEY_RIGHT_BRACKET }, { KEY_GRAVE_ACCENT.getCode() , KEY_GRAVE_ACCENT }, { KEY_WORLD_1.getCode() , KEY_WORLD_1 }, { KEY_WORLD_2.getCode() , KEY_WORLD_2 }, { KEY_ESCAPE.getCode() , KEY_ESCAPE }, { KEY_ENTER.getCode() , KEY_ENTER }, { KEY_TAB.getCode() , KEY_TAB }, { KEY_BACKSPACE.getCode() , KEY_BACKSPACE }, { KEY_INSERT.getCode() , KEY_INSERT }, { KEY_DELETE.getCode() , KEY_DELETE }, { KEY_RIGHT.getCode() , KEY_RIGHT }, { KEY_LEFT.getCode() , KEY_LEFT }, { KEY_DOWN.getCode() , KEY_DOWN }, { KEY_UP.getCode() , KEY_UP }, { KEY_PAGE_UP.getCode() , KEY_PAGE_UP }, { KEY_PAGE_DOWN.getCode() , KEY_PAGE_DOWN }, { KEY_HOME.getCode() , KEY_HOME }, { KEY_END.getCode() , KEY_END }, { KEY_CAPS_LOCK.getCode() , KEY_CAPS_LOCK }, { KEY_SCROLL_LOCK.getCode() , KEY_SCROLL_LOCK }, { KEY_NUM_LOCK.getCode() , KEY_NUM_LOCK }, { KEY_PRINT_SCREEN.getCode() , KEY_PRINT_SCREEN }, { KEY_PAUSE.getCode() , KEY_PAUSE }, { KEY_F1.getCode() , KEY_F1 }, { KEY_F2.getCode() , KEY_F2 }, { KEY_F3.getCode() , KEY_F3 }, { KEY_F4.getCode() , KEY_F4 }, { KEY_F5.getCode() , KEY_F5 }, { KEY_F6.getCode() , KEY_F6 }, { KEY_F7.getCode() , KEY_F7 }, { KEY_F8.getCode() , KEY_F8 }, { KEY_F9.getCode() , KEY_F9 }, { KEY_F10.getCode() , KEY_F10 }, { KEY_F11.getCode() , KEY_F11 }, { KEY_F12.getCode() , KEY_F12 }, { KEY_F13.getCode() , KEY_F13 }, { KEY_F14.getCode() , KEY_F14 }, { KEY_F15.getCode() , KEY_F15 }, { KEY_F16.getCode() , KEY_F16 }, { KEY_F17.getCode() , KEY_F17 }, { KEY_F18.getCode() , KEY_F18 }, { KEY_F19.getCode() , KEY_F19 }, { KEY_F20.getCode() , KEY_F20 }, { KEY_F21.getCode() , KEY_F21 }, { KEY_F22.getCode() , KEY_F22 }, { KEY_F23.getCode() , KEY_F23 }, { KEY_F24.getCode() , KEY_F24 }, { KEY_F25.getCode() , KEY_F25 }, { KEY_NUMPAD_0.getCode() , KEY_NUMPAD_0 }, { KEY_NUMPAD_1.getCode() , KEY_NUMPAD_1 }, { KEY_NUMPAD_2.getCode() , KEY_NUMPAD_2 }, { KEY_NUMPAD_3.getCode() , KEY_NUMPAD_3 }, { KEY_NUMPAD_4.getCode() , KEY_NUMPAD_4 }, { KEY_NUMPAD_5.getCode() , KEY_NUMPAD_5 }, { KEY_NUMPAD_6.getCode() , KEY_NUMPAD_6 }, { KEY_NUMPAD_7.getCode() , KEY_NUMPAD_7 }, { KEY_NUMPAD_8.getCode() , KEY_NUMPAD_8 }, { KEY_NUMPAD_9.getCode() , KEY_NUMPAD_9 }, { KEY_NUMPAD_DECIMAL.getCode() , KEY_NUMPAD_DECIMAL }, { KEY_NUMPAD_DIVIDE.getCode() , KEY_NUMPAD_DIVIDE }, { KEY_NUMPAD_MULTIPLY.getCode(), KEY_NUMPAD_MULTIPLY }, { KEY_NUMPAD_SUBTRACT.getCode(), KEY_NUMPAD_SUBTRACT }, { KEY_NUMPAD_ADD.getCode() , KEY_NUMPAD_ADD }, { KEY_NUMPAD_ENTER.getCode() , KEY_NUMPAD_ENTER }, { KEY_NUMPAD_EQUAL.getCode() , KEY_NUMPAD_EQUAL }, { KEY_LEFT_SHIFT.getCode() , KEY_LEFT_SHIFT }, { KEY_LEFT_CONTROL.getCode() , KEY_LEFT_CONTROL }, { KEY_LEFT_ALT.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_LEFT_SUPER.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_RIGHT_SHIFT.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_RIGHT_CONTROL.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_RIGHT_ALT.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_RIGHT_SUPER.getCode() , KEY_NUMPAD_SUBTRACT }, { KEY_MENU.getCode() , KEY_MENU }, }; Key getKey(const std::string& name) { for (auto key : keymap) { if (key.second.getName() == name) return key.second; } return KEY_UNKNOWN; } Key getKey(int code) { try { Key& key = keymap.at(code); return key; } catch (const std::out_of_range&) { return KEY_UNKNOWN; } } } }; ================================================ FILE: engine/input/keyboard.h ================================================ #pragma once namespace P3D::Engine { struct Key { private: std::string name; int code; public: inline Key(const std::string& name, int code) : name(name), code(code) {} std::string getName() const; int getCode() const; // Key <-> Key bool operator==(const Key& other) const; bool operator!=(const Key& other) const; bool operator>=(const Key& other) const; bool operator>(const Key& other) const; bool operator<=(const Key& other) const; bool operator<(const Key& other) const; // Key <-> int bool operator==(int other) const; bool operator!=(int other) const; bool operator>=(int other) const; bool operator>(int other) const; bool operator<=(int other) const; bool operator<(int other) const; // Key <-> string bool operator==(const std::string& other) const; bool operator!=(const std::string& other) const; }; namespace Keyboard { Key getKey(const std::string& name); Key getKey(int code); extern const Key KEY_UNKNOWN; extern const Key KEY_SPACE; extern const Key KEY_APOSTROPHE; extern const Key KEY_COMMA; extern const Key KEY_MINUS; extern const Key KEY_PERIOD; extern const Key KEY_SLASH; extern const Key KEY_NUMBER_0; extern const Key KEY_NUMBER_1; extern const Key KEY_NUMBER_2; extern const Key KEY_NUMBER_3; extern const Key KEY_NUMBER_4; extern const Key KEY_NUMBER_5; extern const Key KEY_NUMBER_6; extern const Key KEY_NUMBER_7; extern const Key KEY_NUMBER_8; extern const Key KEY_NUMBER_9; extern const Key KEY_SEMICOLON; extern const Key KEY_EQUAL; extern const Key KEY_A; extern const Key KEY_B; extern const Key KEY_C; extern const Key KEY_D; extern const Key KEY_E; extern const Key KEY_F; extern const Key KEY_G; extern const Key KEY_H; extern const Key KEY_I; extern const Key KEY_J; extern const Key KEY_K; extern const Key KEY_L; extern const Key KEY_M; extern const Key KEY_N; extern const Key KEY_O; extern const Key KEY_P; extern const Key KEY_Q; extern const Key KEY_R; extern const Key KEY_S; extern const Key KEY_T; extern const Key KEY_U; extern const Key KEY_V; extern const Key KEY_W; extern const Key KEY_X; extern const Key KEY_Y; extern const Key KEY_Z; extern const Key KEY_LEFT_BRACKET; extern const Key KEY_BACKSLASH; extern const Key KEY_RIGHT_BRACKET; extern const Key KEY_GRAVE_ACCENT; extern const Key KEY_WORLD_1; extern const Key KEY_WORLD_2; extern const Key KEY_ESCAPE; extern const Key KEY_ENTER; extern const Key KEY_TAB; extern const Key KEY_BACKSPACE; extern const Key KEY_INSERT; extern const Key KEY_DELETE; extern const Key KEY_RIGHT; extern const Key KEY_LEFT; extern const Key KEY_DOWN; extern const Key KEY_UP; extern const Key KEY_PAGE_UP; extern const Key KEY_PAGE_DOWN; extern const Key KEY_HOME; extern const Key KEY_END; extern const Key KEY_CAPS_LOCK; extern const Key KEY_SCROLL_LOCK; extern const Key KEY_NUM_LOCK; extern const Key KEY_PRINT_SCREEN; extern const Key KEY_PAUSE; extern const Key KEY_F1; extern const Key KEY_F2; extern const Key KEY_F3; extern const Key KEY_F4; extern const Key KEY_F5; extern const Key KEY_F6; extern const Key KEY_F7; extern const Key KEY_F8; extern const Key KEY_F9; extern const Key KEY_F10; extern const Key KEY_F11; extern const Key KEY_F12; extern const Key KEY_F13; extern const Key KEY_F14; extern const Key KEY_F15; extern const Key KEY_F16; extern const Key KEY_F17; extern const Key KEY_F18; extern const Key KEY_F19; extern const Key KEY_F20; extern const Key KEY_F21; extern const Key KEY_F22; extern const Key KEY_F23; extern const Key KEY_F24; extern const Key KEY_F25; extern const Key KEY_NUMPAD_0; extern const Key KEY_NUMPAD_1; extern const Key KEY_NUMPAD_2; extern const Key KEY_NUMPAD_3; extern const Key KEY_NUMPAD_4; extern const Key KEY_NUMPAD_5; extern const Key KEY_NUMPAD_6; extern const Key KEY_NUMPAD_7; extern const Key KEY_NUMPAD_8; extern const Key KEY_NUMPAD_9; extern const Key KEY_NUMPAD_DECIMAL; extern const Key KEY_NUMPAD_DIVIDE; extern const Key KEY_NUMPAD_MULTIPLY; extern const Key KEY_NUMPAD_SUBTRACT; extern const Key KEY_NUMPAD_ADD; extern const Key KEY_NUMPAD_ENTER; extern const Key KEY_NUMPAD_EQUAL; extern const Key KEY_LEFT_SHIFT; extern const Key KEY_LEFT_CONTROL; extern const Key KEY_LEFT_ALT; extern const Key KEY_LEFT_SUPER; extern const Key KEY_RIGHT_SHIFT; extern const Key KEY_RIGHT_CONTROL; extern const Key KEY_RIGHT_ALT; extern const Key KEY_RIGHT_SUPER; extern const Key KEY_MENU; extern const int KEY_FIRST; extern const int KEY_LAST; extern const int KEY_PRESS; extern const int KEY_RELEASE; extern const int KEY_REPEAT; } }; ================================================ FILE: engine/input/modifiers.cpp ================================================ #include "core.h" #include #include "modifiers.h" namespace P3D::Engine { int Modifiers::getModifiers() const { return modifiers; } bool Modifiers::isCtrlPressed() const { return modifiers & Modifier::CTRL; } bool Modifiers::isShiftPressed() const { return modifiers & Modifier::SHIFT; } bool Modifiers::isSuperPressed() const { return modifiers & Modifier::SUPER; } bool Modifiers::isAltPressed() const { return modifiers & Modifier::ALT; } namespace Modifier { const int NONE = 0; const int SHIFT = GLFW_MOD_SHIFT; const int CTRL = GLFW_MOD_CONTROL; const int ALT = GLFW_MOD_ALT; const int SUPER = GLFW_MOD_SUPER; }; }; ================================================ FILE: engine/input/modifiers.h ================================================ #pragma once namespace P3D::Engine { namespace Modifier { extern const int NONE; extern const int SHIFT; extern const int CTRL; extern const int ALT; extern const int SUPER; }; struct Modifiers { private: int modifiers; public: inline Modifiers(int modifiers) : modifiers(modifiers) {} int getModifiers() const; bool isCtrlPressed() const; bool isShiftPressed() const; bool isSuperPressed() const; bool isAltPressed() const; }; }; ================================================ FILE: engine/input/mouse.cpp ================================================ #include "core.h" #include "mouse.h" #include #include namespace P3D::Engine { std::string Button::getName() const { return name; } int Button::getCode() const { return code; } // Button <-> Button bool Button::operator==(const Button& other) const { return other.code == code; } bool Button::operator!=(const Button& other) const { return other.code != code; } // Key <-> int bool Button::operator==(int other) const { return other == code; } bool Button::operator!=(int other) const { return other != code; } // Key <-> string bool Button::operator==(const std::string& other) const { return other == name; } bool Button::operator!=(const std::string& other) const { return other != name; } namespace Mouse { const Button UNKNOWN = { "none" , -1 }; const Button LEFT = { "mouse_left" , GLFW_MOUSE_BUTTON_LEFT }; const Button RIGHT = {"mouse_right" , GLFW_MOUSE_BUTTON_RIGHT }; const Button MIDDLE = { "mouse_middle", GLFW_MOUSE_BUTTON_MIDDLE }; const Button MOUSE_4 = { "mouse_4" , GLFW_MOUSE_BUTTON_4 }; const Button MOUSE_5 = { "mouse_5" , GLFW_MOUSE_BUTTON_5 }; const Button MOUSE_6 = { "mouse_6" , GLFW_MOUSE_BUTTON_6 }; const Button MOUSE_7 = { "mouse_7" , GLFW_MOUSE_BUTTON_7 }; const Button MOUSE_8 = { "mouse_8" , GLFW_MOUSE_BUTTON_8 }; const int MOUSE_FIRST = GLFW_MOUSE_BUTTON_LAST; const int MOUSE_LAST = GLFW_MOUSE_BUTTON_LEFT; const int PRESS = GLFW_PRESS; const int RELEASE = GLFW_RELEASE; const int REPEAT = GLFW_REPEAT; std::map buttonmap = { { UNKNOWN.getCode(), UNKNOWN }, { LEFT.getCode() , LEFT }, { RIGHT.getCode() , RIGHT }, { MIDDLE.getCode() , MIDDLE }, { MOUSE_4.getCode(), MOUSE_4 }, { MOUSE_5.getCode(), MOUSE_5 }, { MOUSE_6.getCode(), MOUSE_6 }, { MOUSE_7.getCode(), MOUSE_7 }, { MOUSE_8.getCode(), MOUSE_8 } }; Button getButton(std::string name) { for (auto& button : buttonmap) { if (button.second.getName() == name) return button.second; } throw "Unreachable"; } Button getButton(int code) { try { Button& key = buttonmap.at(code); return key; } catch (const std::out_of_range&) { return UNKNOWN; } } } }; ================================================ FILE: engine/input/mouse.h ================================================ #pragma once namespace P3D::Engine { struct Button { private: std::string name; int code; public: inline Button(const std::string& name, int code) : name(name), code(code) {} std::string getName() const; int getCode() const; // Button <-> Button bool operator==(const Button& other) const; bool operator!=(const Button& other) const; // Key <-> int bool operator==(int other) const; bool operator!=(int other) const; // Key <-> string bool operator==(const std::string& other) const; bool operator!=(const std::string& other) const; }; namespace Mouse { Button getButton(std::string name); Button getButton(int code); extern const Button LEFT; extern const Button RIGHT; extern const Button MIDDLE; extern const Button MOUSE_4; extern const Button MOUSE_5; extern const Button MOUSE_6; extern const Button MOUSE_7; extern const Button MOUSE_8; extern const int MOUSE_FIRST; extern const int MOUSE_LAST; extern const int PRESS; extern const int RELEASE; extern const int REPEAT; }; }; ================================================ FILE: engine/io/export.cpp ================================================ #include "core.h" #include "export.h" #include "../graphics/extendedTriangleMesh.h" #include #include #include "../util/fileUtils.h" namespace P3D { /* Export */ template void Export::write(std::ostream& output, T const* buffer, int size) { for (int i = 0; i < size; i++) { output.write((T*) (buffer + i * sizeof(T)), 1); } } template void Export::write(std::ostream& output, const T& value) { char const* v = reinterpret_cast(&value); write(output, v, sizeof(T)); } std::string Export::str(Vec3 vector) { std::stringstream ss; ss << vector.x << " " << vector.y << " " << vector.z; return ss.str(); } std::string Export::str(Vec4 vector) { std::stringstream ss; ss << vector.x << " " << vector.y << " " << vector.z << " " << vector.w; return ss.str(); } std::string Export::str(Mat3 matrix) { std::stringstream ss; double data[9]; matrix.toColMajorData(data); ss << data[0]; for (int i = 1; i < 9; i++) { ss << ' ' << data[i]; } return ss.str(); } std::string Export::str(Position pos) { std::stringstream ss; ss << pos.x.value << " " << pos.y.value << " " << pos.z.value; return ss.str(); } std::string Export::str(DiagonalMat3 matrix) { return Export::str(Vec3(matrix[0], matrix[1], matrix[2])); } std::string Export::str(int num) { return std::to_string(num); } std::string Export::str(double num) { return std::to_string(num); } /* End of Export */ /* OBJExport */ void saveBinaryObj(std::string filename, const Graphics::ExtendedTriangleMesh& shape) { Util::warnIfFileExists(filename); std::ofstream output; output.open(filename, std::ios::binary | std::ios::out); enum : char { V, VN, VT, VNT }; char flag = V; if (shape.uvs != nullptr && shape.normals != nullptr) { flag = VNT; } else if (shape.uvs != nullptr) { flag = VT; } else if (shape.normals != nullptr) { flag = VN; } Export::write(output, flag); Export::write(output, shape.vertexCount); Export::write(output, shape.triangleCount); for (Vec3f vertex : shape.iterVertices()) { Export::write(output, vertex); } if (shape.normals != nullptr) { for (int i = 0; i < shape.vertexCount; i++) { Vec3f normal = shape.normals.get()[i]; Export::write(output, normal); } } if (shape.uvs != nullptr) { for (int i = 0; i < shape.vertexCount; i++) { Vec2f uv = shape.uvs.get()[i]; Export::write(output, uv); } } for (Triangle triangle : shape.iterTriangles()) { for (int index : triangle.indexes) { Export::write(output, index); } } output.close(); } void saveNonBinaryObj(const std::string& filename, const Graphics::ExtendedTriangleMesh& shape) { Util::warnIfFileExists(filename); std::ofstream output; output.open(filename); for (Vec3f vertex : shape.iterVertices()) { output << "v " << vertex.x << " " << vertex.y << " " << vertex.z << std::endl; } if (shape.normals != nullptr) { for (int i = 0; i < shape.vertexCount; i++) { Vec3 normal = shape.normals.get()[i]; output << "vn " << normal.x << " " << normal.y << " " << normal.z << std::endl; } } if (shape.uvs != nullptr) { for (int i = 0; i < shape.vertexCount; i++) { Vec2 uv = shape.uvs.get()[i]; output << "vt " << uv.x << " " << uv.y << std::endl; } } for (Triangle triangle : shape.iterTriangles()) { output << "f"; for (int index : triangle.indexes) { index++; if (shape.normals != nullptr && shape.uvs != nullptr) { output << " " << index << "/" << index << "/" << index; } else if (shape.uvs != nullptr) { output << " " << index << "//" << index; } else if (shape.normals != nullptr) { output << " " << index << "/" << index; } else { output << " " << index; } } output << std::endl; } output.close(); } void OBJExport::save(const std::string& filename, const Graphics::ExtendedTriangleMesh& shape, bool binary) { if (binary) saveBinaryObj(filename, shape); else saveNonBinaryObj(filename, shape); } /* End of OBJExport */ }; ================================================ FILE: engine/io/export.h ================================================ #pragma once namespace P3D::Graphics { struct ExtendedTriangleMesh; }; namespace P3D { namespace Export { template void write(std::ostream& output, T const * buffer, int size); template void write(std::ostream& output, const T& value); std::string str(Vec4 vector); std::string str(Vec3 vector); std::string str(Position pos); std::string str(Mat3 matrix); std::string str(DiagonalMat3 matrix); std::string str(int num); std::string str(double num); }; namespace OBJExport { void save(const std::string& file, const Graphics::ExtendedTriangleMesh&, bool binary = false); }; }; ================================================ FILE: engine/io/import.cpp ================================================ #include "core.h" #include "import.h" #include #include #include "../util/stringUtil.h" #include #include "../graphics/extendedTriangleMesh.h" namespace P3D { /* Import */ int Import::parseInt(const std::string& num) { return std::stoi(num); } long long Import::parseLong(const std::string& num) { return std::stoll(num); } Fix<32> Import::parseFix(const std::string& num) { long long v = std::stoll(num); return Fix<32>(static_cast(v)); } double Import::parseDouble(const std::string& num) { return std::stod(num); } float Import::parseFloat(const std::string& num) { return std::stof(num); } Vec3 Import::parseVec3(const std::string& vec) { std::vector tokens = Util::split(vec, ' '); Vec3 vector = Vec3(); for (int i = 0; i < 3; i++) vector[i] = Import::parseDouble(tokens[i]); return vector; } Position Import::parsePosition(const std::string& vec) { std::vector tokens = Util::split(vec, ' '); return Position(Import::parseFix(tokens[0]), Import::parseFix(tokens[1]), Import::parseFix(tokens[2])); } Vec4 Import::parseVec4(const std::string& vec) { std::vector tokens = Util::split(vec, ' '); Vec4 vector; for (int i = 0; i < 4; i++) vector[i] = Import::parseDouble(tokens[i]); return vector; } Vec4f Import::parseVec4f(const std::string& vec) { std::vector tokens = Util::split(vec, ' '); Vec4f vector; for (int i = 0; i < 4; i++) vector[i] = Import::parseFloat(tokens[i]); return vector; } Vec3f Import::parseVec3f(const std::string& vec) { std::vector tokens = Util::split(vec, ' '); Vec3f vector = Vec3f(); for (int i = 0; i < 3; i++) vector[i] = Import::parseFloat(tokens[i]); return vector; } DiagonalMat3 Import::parseDiagonalMat3(const std::string& mat) { std::vector tokens = Util::split(mat, ' '); DiagonalMat3 matrix = DiagonalMat3(); for (int i = 0; i < 3; i++) matrix[i] = Import::parseDouble(tokens[i]); return matrix; } Mat3 Import::parseMat3(const std::string& mat) { std::vector tokens = Util::split(mat, ' '); double data[9]; for (int i = 0; i < 9; i++) data[i] = Import::parseDouble(tokens[i]); return Matrix::fromColMajorData(data); } template T Import::read(std::istream& input) { char * buffer = (char*) alloca(sizeof(T)); input.read(buffer, sizeof(T)); return *reinterpret_cast(buffer); } /* End of Import */ /* OBJImport */ struct Vertex { int position; std::optional normal; std::optional uv; Vertex() : position(0), normal(std::nullopt), uv(std::nullopt) {} Vertex(const std::string_view& line) { std::vector tokens = Util::split_view(line, '/'); size_t length = tokens.size(); // Positions position = std::stoi(std::string(tokens[0])) - 1; // Uvs if (length > 1 && !tokens[1].empty()) uv = std::stoi(std::string(tokens[1])) - 1; // Normals if (length > 2 && !tokens[2].empty()) normal = std::stoi(std::string(tokens[2])) - 1; } bool operator==(const Vertex& other) const { return position == other.position && normal == other.normal && uv == other.uv; } }; struct VertexHasher { size_t operator()(const P3D::Vertex& vertex) const { size_t result = std::hash()(vertex.position); if (vertex.normal.has_value()) result = result >> 1 ^ std::hash()(vertex.normal.value()) << 1; if (vertex.uv.has_value()) result = result >> 1 ^ std::hash()(vertex.uv.value()) << 1; return result; } }; struct Face { Vertex vertices[3]; Face(const Vertex& v1, const Vertex& v2, const Vertex& v3) { vertices[0] = v1; vertices[1] = v2; vertices[2] = v3; } Vertex& operator[](std::size_t index) { assert(index < 3); return vertices[index]; } const Vertex& operator[](int index) const { assert(index < 3); return vertices[index]; } }; Graphics::ExtendedTriangleMesh reorderWithoutSharedVerticesSupport(const std::vector& positions, const std::vector& normals, const std::vector& uvs, const std::vector& faces) { // Positions Vec3f* positionArray = new Vec3f[positions.size()]; for (std::size_t i = 0; i < positions.size(); i++) positionArray[i] = positions[i]; // Normals Vec3f* normalArray = nullptr; if (!normals.empty()) normalArray = new Vec3f[positions.size()]; // UVs, tangents and bitangents Vec2f* uvArray = nullptr; Vec3f* tangentArray = nullptr; Vec3f* bitangentArray = nullptr; if (!uvs.empty()) { uvArray = new Vec2f[positions.size()]; tangentArray = new Vec3f[positions.size()]; bitangentArray = new Vec3f[positions.size()]; } // Triangles Triangle* triangleArray = new Triangle[faces.size()]; for (std::size_t faceIndex = 0; faceIndex < faces.size(); faceIndex++) { const Face& face = faces[faceIndex]; // Save triangle triangleArray[faceIndex] = Triangle { face.vertices[0].position, face.vertices[1].position, face.vertices[2].position }; // Calculate tangent and bitangent Vec3f tangent; Vec3f bitangent; if (uvArray) { Vec3f edge1 = positions[face.vertices[1].position] - positions[face.vertices[0].position]; Vec3f edge2 = positions[face.vertices[2].position] - positions[face.vertices[0].position]; Vec2f dUV1 = uvs[*face.vertices[1].uv] - uvs[*face.vertices[0].uv]; Vec2f dUV2 = uvs[*face.vertices[2].uv] - uvs[*face.vertices[0].uv]; float f = 1.0f / (dUV1.x * dUV2.y - dUV2.x * dUV1.y); tangent.x = f * (dUV2.y * edge1.x - dUV1.y * edge2.x); tangent.y = f * (dUV2.y * edge1.y - dUV1.y * edge2.y); tangent.z = f * (dUV2.y * edge1.z - dUV1.y * edge2.z); tangent = normalize(tangent); bitangent.x = f * (-dUV2.x * edge1.x + dUV1.x * edge2.x); bitangent.y = f * (-dUV2.x * edge1.y + dUV1.x * edge2.y); bitangent.z = f * (-dUV2.x * edge1.z + dUV1.x * edge2.z); bitangent = normalize(bitangent); } for (int vertexIndex = 0; vertexIndex < 3; vertexIndex++) { const Vertex& vertex = face[vertexIndex]; // Save normal if (normalArray && vertex.normal.has_value()) normalArray[vertex.position] = normals[*vertex.normal]; // Save uv, tangent and bitangent if (uvArray && vertex.uv.has_value()) { uvArray[vertex.position] = uvs[*vertex.uv]; tangentArray[vertex.position] = tangent; bitangentArray[vertex.position] = bitangent; } } } Graphics::ExtendedTriangleMesh result(positionArray, static_cast(positions.size()), triangleArray, static_cast(faces.size())); result.setNormalBuffer(SRef(normalArray)); result.setUVBuffer(SRef(uvArray)); result.setTangentBuffer(SRef(tangentArray)); result.setBitangentBuffer(SRef(bitangentArray)); return result; } Graphics::ExtendedTriangleMesh reorderWithSharedVerticesSupport(const std::vector& positions, const std::vector& normals, const std::vector& uvs, const std::vector& faces) { std::unordered_map mapping; // Get index of each vertex - uv - normal tuple auto getIndex = [&mapping] (const Vertex& vertex) -> int { auto iterator = mapping.find(vertex); if (iterator == mapping.end()) { int index = static_cast(mapping.size()); mapping.emplace(vertex, index); return index; } return iterator->second; }; // Fill triangle array Triangle* triangleArray = new Triangle[faces.size()]; for (std::size_t faceIndex = 0; faceIndex < faces.size(); faceIndex++) { const Face& face = faces[faceIndex]; const Vertex& v0 = face.vertices[0]; const Vertex& v1 = face.vertices[1]; const Vertex& v2 = face.vertices[2]; int i0 = getIndex(v0); int i1 = getIndex(v1); int i2 = getIndex(v2); // Save triangle triangleArray[faceIndex] = Triangle { i0, i1, i2 }; } // Array size std::size_t size = mapping.size(); // Positions Vec3f* positionArray = new Vec3f[size]; // Normals Vec3f* normalArray = nullptr; if (!normals.empty()) normalArray = new Vec3f[size]; // UV Vec2f* uvArray = nullptr; if (!uvs.empty()) uvArray = new Vec2f[size]; // Fill arrays for (const auto& [vertex, index] : mapping) { // Store position positionArray[index] = positions[vertex.position]; // Store normal if (normalArray && vertex.normal.has_value()) normalArray[index] = normals[*vertex.normal]; // Store uv if (uvArray && vertex.uv.has_value()) uvArray[index] = uvs[*vertex.uv]; } Graphics::ExtendedTriangleMesh result(positionArray, size, triangleArray, static_cast(faces.size())); result.setNormalBuffer(SRef(normalArray)); result.setUVBuffer(SRef(uvArray)); return result; } Graphics::ExtendedTriangleMesh loadBinaryObj(std::istream& input) { char flag = Import::read(input); int vertexCount = Import::read(input); int triangleCount = Import::read(input); char V = 0; char VN = 1; char VT = 2; char VNT = 3; Vec3f* vertices = new Vec3f[vertexCount]; for (int i = 0; i < vertexCount; i++) { Vec3 vertex = Import::read(input); vertices[i] = vertex; } Vec3f* normals = nullptr; if (flag == VN || flag == VNT) { normals = new Vec3f[vertexCount]; for (int i = 0; i < vertexCount; i++) { normals[i] = Import::read(input); } } Vec2f* uvs = nullptr; Vec3f* tangents = nullptr; Vec3f* bitangents = nullptr; if (flag == VT || flag == VNT) { uvs = new Vec2f[vertexCount]; tangents = new Vec3f[vertexCount]; bitangents = new Vec3f[vertexCount]; for (int i = 0; i < vertexCount; i++) { uvs[i] = Import::read(input); } } Triangle* triangles = new Triangle[triangleCount]; for (int i = 0; i < triangleCount; i++) { Triangle triangle = Import::read(input); triangles[i] = triangle; // Calculate (bi)tangents if (flag == VT || flag == VNT) { Vec3f tangent; Vec3f bitangent; Vec3f edge1 = vertices[triangle.secondIndex] - vertices[triangle.firstIndex]; Vec3f edge2 = vertices[triangle.thirdIndex] - vertices[triangle.firstIndex]; Vec2f dUV1 = uvs[triangle.secondIndex] - uvs[triangle.firstIndex]; Vec2f dUV2 = uvs[triangle.thirdIndex] - uvs[triangle.firstIndex]; float f = 1.0f / (dUV1.x * dUV2.y - dUV2.x * dUV1.y); tangent.x = f * (dUV2.y * edge1.x - dUV1.y * edge2.x); tangent.y = f * (dUV2.y * edge1.y - dUV1.y * edge2.y); tangent.z = f * (dUV2.y * edge1.z - dUV1.y * edge2.z); tangents[triangle.firstIndex] = normalize(tangent); tangents[triangle.secondIndex] = normalize(tangent); tangents[triangle.thirdIndex] = normalize(tangent); bitangent.x = f * (-dUV2.x * edge1.x + dUV1.x * edge2.x); bitangent.y = f * (-dUV2.x * edge1.y + dUV1.x * edge2.y); bitangent.z = f * (-dUV2.x * edge1.z + dUV1.x * edge2.z); bitangents[triangle.firstIndex] = normalize(bitangent); bitangents[triangle.secondIndex] = normalize(bitangent); bitangents[triangle.thirdIndex] = normalize(bitangent); } } Graphics::ExtendedTriangleMesh result(vertices, vertexCount, triangles, triangleCount); result.setNormalBuffer(SRef(normals)); result.setUVBuffer(SRef(uvs)); result.setTangentBuffer(SRef(tangents)); result.setBitangentBuffer(SRef(bitangents)); return result; } Graphics::ExtendedTriangleMesh loadNonBinaryObj(std::istream& input) { std::vector vertices; std::vector normals; std::vector uvs; std::vector faces; std::string line; while (getline(input, line)) { std::vector tokens = Util::split_view(line, ' '); if (tokens.empty()) continue; if (tokens[0] == "v") { float x = std::stof(std::string(tokens[1])); float y = std::stof(std::string(tokens[2])); float z = std::stof(std::string(tokens[3])); vertices.emplace_back(x, y, z); } else if (tokens[0] == "f") { Vertex v1(tokens[1]); Vertex v2(tokens[2]); Vertex v3(tokens[3]); faces.emplace_back(v1, v2, v3); if (tokens.size() > 4) faces.emplace_back(v1, v3, Vertex(tokens[4])); } else if (tokens[0] == "vt") { float u = std::stof(std::string(tokens[1])); float v = 1.0f - std::stof(std::string(tokens[2])); uvs.emplace_back(u, v); } else if (tokens[0] == "vn") { float x = std::stof(std::string(tokens[1])); float y = std::stof(std::string(tokens[2])); float z = std::stof(std::string(tokens[3])); normals.emplace_back(x, y, z); } } return reorderWithSharedVerticesSupport(vertices, normals, uvs, faces); } Graphics::ExtendedTriangleMesh OBJImport::load(std::istream& file, bool binary) { if (binary) return loadBinaryObj(file); else return loadNonBinaryObj(file); } Graphics::ExtendedTriangleMesh OBJImport::load(const std::string& file) { bool binary; if (Util::endsWith(file, ".bobj")) binary = true; else if (Util::endsWith(file, ".obj")) binary = false; else return Graphics::ExtendedTriangleMesh(); return OBJImport::load(file, binary); } Graphics::ExtendedTriangleMesh OBJImport::load(const std::string& file, bool binary) { std::ifstream input; if (binary) input.open(file, std::ios::binary); else input.open(file); Graphics::ExtendedTriangleMesh shape = load(input, binary); input.close(); return shape; } /* End of OBJImport */ } ================================================ FILE: engine/io/import.h ================================================ #pragma once #include namespace P3D::Graphics { struct ExtendedTriangleMesh; }; namespace P3D { namespace Import { int parseInt(const std::string& num); long long parseLong(const std::string& num); Fix<32> parseFix(const std::string& num); float parseFloat(const std::string& num); double parseDouble(const std::string& num); Vec3 parseVec3(const std::string& vec); Vec3f parseVec3f(const std::string& vec); Vec4 parseVec4(const std::string& vec); Vec4f parseVec4f(const std::string& vec); Position parsePosition(const std::string& pos); DiagonalMat3 parseDiagonalMat3(const std::string& mat); Mat3 parseMat3(const std::string& mat); template T read(std::istream& input); }; namespace OBJImport { Graphics::ExtendedTriangleMesh load(std::istream& file, bool binary = false); Graphics::ExtendedTriangleMesh load(const std::string& file, bool binary); Graphics::ExtendedTriangleMesh load(const std::string& file); }; }; ================================================ FILE: engine/layer/layer.h ================================================ #pragma once #include "../event/event.h" #include "../ecs/registry.h" #include namespace P3D::Engine { class Layer { protected: void* ptr = nullptr; public: enum LayerFlags : char { // No flags, update, render and events are called None = 0 << 0, // Whether the layer is disabled, no update, render or event calls Disabled = 1 << 0, // Whether the layer is hidden, no render calls NoRender = 1 << 1, // Whether the layer receives events NoEvents = 1 << 2, // Whether the layer updates NoUpdate = 1 << 3 }; std::string name; char flags; inline Layer() : ptr(nullptr), name(), flags(None) {}; inline Layer(const std::string& name, void* ptr, char flags = None) : ptr(ptr), name(name), flags(flags) {} inline virtual void onAttach() {} inline virtual void onDetach() {} inline virtual void onInit(Registry64& registry) {} inline virtual void onEvent(Registry64& registry, Event& event) {} inline virtual void onUpdate(Registry64& registry) {} inline virtual void onRender(Registry64& registry) {} inline virtual void onClose(Registry64& registry) {} }; }; ================================================ FILE: engine/layer/layerStack.cpp ================================================ #include "core.h" #include "layerStack.h" namespace P3D::Engine { LayerStack::LayerStack() { insert = 0; } void LayerStack::pushLayer(Layer* layer) { stack.insert(stack.begin() + insert++, layer); layer->onAttach(); } void LayerStack::popLayer(Layer* layer) { const auto where = std::find(begin(), end(), layer); if (where != end()) { stack.erase(where); insert--; layer->onDetach(); } } void LayerStack::onInit(Registry64& registry) { for (auto* layer : *this) layer->onInit(registry); } void LayerStack::onUpdate(Registry64& registry) { for (auto* layer : *this) { if (layer->flags & (Layer::Disabled | Layer::NoUpdate)) continue; layer->onUpdate(registry); } } void LayerStack::onEvent(Registry64& registry, Event& event) { for (auto i = rbegin(); i != rend(); ++i) { auto* layer = *i; if (layer->flags & (Layer::Disabled | Layer::NoEvents)) continue; layer->onEvent(registry, event); if (event.handled) return; } } void LayerStack::onRender(Registry64& registry) { for (auto* layer : *this) { if (layer->flags & (Layer::Disabled | Layer::NoRender)) continue; layer->onRender(registry); } } void LayerStack::onClose(Registry64& registry) { for (auto i = rbegin(); i != rend(); ++i) (*i)->onClose(registry); } std::vector::iterator LayerStack::begin() { return stack.begin(); } std::vector::iterator LayerStack::end() { return stack.end(); } std::vector::reverse_iterator LayerStack::rbegin() { return stack.rbegin(); } std::vector::reverse_iterator LayerStack::rend() { return stack.rend(); } }; ================================================ FILE: engine/layer/layerStack.h ================================================ #pragma once #include "layer.h" #include "../engine/ecs/registry.h" namespace P3D::Engine { class LayerStack { private: std::vector stack; int insert; public: LayerStack(); void pushLayer(Layer* layer); void popLayer(Layer* layer); void onInit(Registry64& registry); void onUpdate(Registry64& registry); void onEvent(Registry64& registry, Event& event); void onRender(Registry64& registry); void onClose(Registry64& registry); std::vector::iterator begin(); std::vector::iterator end(); std::vector::reverse_iterator rbegin(); std::vector::reverse_iterator rend(); }; }; ================================================ FILE: engine/options/keyboardOptions.cpp ================================================ #include "core.h" #include "keyboardOptions.h" namespace P3D::Engine { namespace KeyboardOptions { namespace Move { Key forward = Keyboard::KEY_UNKNOWN; Key backward = Keyboard::KEY_UNKNOWN; Key right = Keyboard::KEY_UNKNOWN; Key left = Keyboard::KEY_UNKNOWN; Key jump = Keyboard::KEY_UNKNOWN; Key fly = Keyboard::KEY_UNKNOWN; Key ascend = Keyboard::KEY_UNKNOWN; Key descend = Keyboard::KEY_UNKNOWN; }; namespace Rotate { Key up = Keyboard::KEY_UNKNOWN; Key down = Keyboard::KEY_UNKNOWN; Key left = Keyboard::KEY_UNKNOWN; Key right = Keyboard::KEY_UNKNOWN; }; namespace Tick { Key run = Keyboard::KEY_UNKNOWN; Key pause = Keyboard::KEY_UNKNOWN; namespace Speed { Key up = Keyboard::KEY_UNKNOWN; Key down = Keyboard::KEY_UNKNOWN; }; }; namespace Debug { Key spheres = Keyboard::KEY_UNKNOWN; Key tree = Keyboard::KEY_UNKNOWN; Key pies = Keyboard::KEY_UNKNOWN; Key frame = Keyboard::KEY_UNKNOWN; }; namespace Edit { Key translate = Keyboard::KEY_UNKNOWN; Key rotate = Keyboard::KEY_UNKNOWN; Key scale = Keyboard::KEY_UNKNOWN; Key select = Keyboard::KEY_UNKNOWN; Key region = Keyboard::KEY_UNKNOWN; }; namespace Part { Key remove = Keyboard::KEY_UNKNOWN; Key anchor = Keyboard::KEY_UNKNOWN; Key makeMainPart = Keyboard::KEY_UNKNOWN; Key makeMainPhysical = Keyboard::KEY_UNKNOWN; } namespace Application { Key close = Keyboard::KEY_UNKNOWN; } namespace World { Key valid = Keyboard::KEY_UNKNOWN; } Key loadKey(const Util::Properties& properties, std::string key) { return Keyboard::getKey(properties.get(key)); } void saveKey(Util::Properties& properties, std::string property, const Key& key) { properties.set(property, key.getName()); } void load(const Util::Properties& properties) { // Move Move::forward = loadKey(properties, "move.forward"); Move::backward = loadKey(properties, "move.backward"); Move::right = loadKey(properties, "move.right"); Move::left = loadKey(properties, "move.left"); Move::jump = loadKey(properties, "move.jump"); Move::fly = loadKey(properties, "move.fly"); Move::ascend = loadKey(properties, "move.ascend"); Move::descend = loadKey(properties, "move.descend"); // Rotate Rotate::up = loadKey(properties, "rotate.up"); Rotate::down = loadKey(properties, "rotate.down"); Rotate::left = loadKey(properties, "rotate.left"); Rotate::right = loadKey(properties, "rotate.right"); // Tick Tick::run = loadKey(properties, "tick.run"); Tick::pause = loadKey(properties, "tick.pause"); Tick::Speed::up = loadKey(properties, "tick.speed.up"); Tick::Speed::down = loadKey(properties, "tick.speed.down"); // Debug Debug::spheres = loadKey(properties, "debug.spheres"); Debug::tree = loadKey(properties, "debug.tree"); Debug::pies = loadKey(properties, "debug.pies"); Debug::frame = loadKey(properties, "debug.frame"); // Part Part::anchor = loadKey(properties, "part.anchor"); Part::remove = loadKey(properties, "part.delete"); Part::makeMainPart = loadKey(properties, "part.makeMainPart"); Part::makeMainPhysical = loadKey(properties, "part.makeMainPhysical"); // Edit Edit::translate = loadKey(properties, "edit.translate"); Edit::rotate = loadKey(properties, "edit.rotate"); Edit::scale = loadKey(properties, "edit.scale"); Edit::select = loadKey(properties, "edit.select"); Edit::region = loadKey(properties, "edit.region"); // Application Application::close = loadKey(properties, "application.close"); // World World::valid = loadKey(properties, "world.valid"); } void save(Util::Properties& properties) { // Move saveKey(properties, "move.forward", Move::forward); saveKey(properties, "move.backward", Move::backward); saveKey(properties, "move.right", Move::right); saveKey(properties, "move.left", Move::left); saveKey(properties, "move.jump", Move::jump); saveKey(properties, "move.fly", Move::fly); saveKey(properties, "move.ascend", Move::ascend); saveKey(properties, "move.descend", Move::descend); // Rotate saveKey(properties, "rotate.up", Rotate::up); saveKey(properties, "rotate.down", Rotate::down); saveKey(properties, "rotate.left", Rotate::left); saveKey(properties, "rotate.right", Rotate::right); // Tick saveKey(properties, "tick.run", Tick::run); saveKey(properties, "tick.pause", Tick::pause); saveKey(properties, "tick.speed.up", Tick::Speed::up); saveKey(properties, "tick.speed.down", Tick::Speed::down); // Debug saveKey(properties, "debug.spheres", Debug::spheres); saveKey(properties, "debug.tree", Debug::tree); saveKey(properties, "debug.pies", Debug::pies); saveKey(properties, "debug.frame", Debug::frame); // Part saveKey(properties, "part.anchor", Part::anchor); saveKey(properties, "part.delete", Part::remove); // Edit saveKey(properties, "edit.translate", Edit::translate); saveKey(properties, "edit.rotate", Edit::rotate); saveKey(properties, "edit.scale", Edit::scale); saveKey(properties, "edit.select", Edit::select); saveKey(properties, "edit.region", Edit::region); // Application saveKey(properties, "application.close", Application::close); // World saveKey(properties, "world.valid", World::valid); } } }; ================================================ FILE: engine/options/keyboardOptions.h ================================================ #pragma once #include "../input/keyboard.h" #include "../util/properties.h" namespace P3D::Engine { namespace KeyboardOptions { using Engine::Key; namespace Move { extern Key forward; extern Key backward; extern Key right; extern Key left; extern Key jump; extern Key fly; extern Key ascend; extern Key descend; }; namespace Rotate { extern Key up; extern Key down; extern Key left; extern Key right; } namespace Tick { extern Key pause; extern Key run; namespace Speed { extern Key up; extern Key down; }; }; namespace Debug { extern Key spheres; extern Key tree; extern Key pies; extern Key frame; }; namespace Edit { extern Key translate; extern Key rotate; extern Key scale; extern Key select; extern Key region; }; namespace Part { extern Key remove; extern Key anchor; extern Key makeMainPart; extern Key makeMainPhysical; } namespace Application { extern Key close; } namespace World { extern Key valid; } void load(const Util::Properties& properties); void save(Util::Properties& properties); } }; ================================================ FILE: engine/resource/meshResource.cpp ================================================ #include "core.h" #include "meshResource.h" #include "../io/import.h" #include "../graphics/extendedTriangleMesh.h" namespace P3D::Engine { MeshResource* MeshAllocator::load(const std::string& name, const std::string& path) { Graphics::ExtendedTriangleMesh shape = OBJImport::load(path); return new MeshResource(name, path, shape); } }; ================================================ FILE: engine/resource/meshResource.h ================================================ #pragma once #include "../util/resource/resource.h" #include "../graphics/mesh/indexedMesh.h" #include "../graphics/extendedTriangleMesh.h" namespace P3D::Engine { class MeshResource; class MeshAllocator : public ResourceAllocator { public: virtual MeshResource* load(const std::string& name, const std::string& path) override; }; class MeshResource : public Resource { private: Graphics::ExtendedTriangleMesh shape; public: DEFINE_RESOURCE(Mesh, "../res/fonts/default/default.ttf"); MeshResource(const std::string& path, Graphics::ExtendedTriangleMesh shape) : Resource(path, path), shape(shape) { } MeshResource(const std::string& name, const std::string& path, Graphics::ExtendedTriangleMesh shape) : Resource(name, path), shape(shape) { } Graphics::ExtendedTriangleMesh* getShape() { return &shape; } void close() override { } static MeshAllocator getAllocator() { return MeshAllocator(); } }; }; ================================================ FILE: engine/tool/buttonTool.h ================================================ #pragma once #include "tool.h" namespace P3D::Engine { typedef char ToolStatus; class ButtonTool : public Tool { }; }; ================================================ FILE: engine/tool/stateTool.h ================================================ #pragma once #include "tool.h" namespace P3D::Engine { typedef char ToolStatus; class StateTool : public Tool { private: ToolStatus toolStatus = 0; protected: void setToolStatus(ToolStatus toolStatus) { this->toolStatus = toolStatus; } StateTool() : Tool() {} public: ToolStatus getToolStatus() const { return toolStatus; } }; }; ================================================ FILE: engine/tool/tool.h ================================================ #pragma once #include namespace P3D::Engine { class Event; #define DEFINE_TOOL(name, description, cursor) \ inline std::string getName() { return getStaticName(); } \ inline std::string getDescription() { return getStaticDescription(); } \ inline static std::string getStaticName() { return name; } \ inline static std::string getStaticDescription() { return description; } \ inline int getCursorType() { return cursor; } \ class Tool { public: Tool() = default; virtual ~Tool() = default; /* Returns the name of this tool */ virtual std::string getName() = 0; static std::string getStaticName(); /* Returns the description of this tool */ virtual std::string getDescription() = 0; static std::string getStaticDescription(); /* When the tool gets registered */ virtual void onRegister() {} /* When the tool gets deregistered */ virtual void onDeregister() {} /* When the tool is selected */ virtual void onSelect() {} /* When the tool is deselected */ virtual void onDeselect() {} /* When the tool gets updated */ virtual void onUpdate() {} /* When the tool gets rendered */ virtual void onRender() {} /* When the tool receives an event */ virtual void onEvent(Event& event) {} }; }; ================================================ FILE: engine/tool/toolManager.cpp ================================================ #include "core.h" #include "toolManager.h" #include "buttonTool.h" namespace P3D::Engine { ToolManager::ToolManager(): activeTool(nullptr) { } ToolManager::~ToolManager() { ToolManager::onClose(); } ToolManager& ToolManager::operator=(ToolManager&& other) noexcept { if (this != &other) { activeTool = std::exchange(other.activeTool, nullptr); tools = std::move(other.tools); } return *this; } ToolManager::ToolManager(ToolManager&& other) noexcept: activeTool(other.activeTool), tools(std::move(other.tools)) { other.activeTool = nullptr; } void ToolManager::onEvent(Event& event) { if (activeTool == nullptr) return; activeTool->onEvent(event); } void ToolManager::onUpdate() { if (activeTool == nullptr) return; activeTool->onUpdate(); } void ToolManager::onRender() { if (activeTool == nullptr) return; activeTool->onRender(); } void ToolManager::onClose() { if (activeTool != nullptr) activeTool->onDeselect(); for (Tool* tool : tools) { tool->onDeregister(); delete tool; } activeTool = nullptr; tools.clear(); } bool ToolManager::deselectTool() { if (activeTool == nullptr) return false; activeTool->onDeselect(); activeTool = nullptr; return true; } bool ToolManager::selectTool(const std::string& name) { auto iterator = std::find_if(tools.begin(), tools.end(), [&name](Tool* tool) { return tool->getName() == name; }); if (iterator == tools.end()) return false; Tool* tool = *iterator; if (dynamic_cast(tool) != nullptr) { tool->onSelect(); tool->onDeselect(); } else { if (activeTool != nullptr) activeTool->onDeselect(); activeTool = tool; activeTool->onSelect(); } return true; } bool ToolManager::selectTool(Tool* tool) { auto iterator = std::find(tools.begin(), tools.end(), tool); if (iterator == tools.end()) return false; if (tool == nullptr) return deselectTool(); if (dynamic_cast(tool) != nullptr) { tool->onSelect(); tool->onDeselect(); } else { if (activeTool != nullptr) activeTool->onDeselect(); activeTool = tool; activeTool->onSelect(); } return true; } bool ToolManager::isSelected(Tool* tool) { return activeTool == tool; } bool ToolManager::isSelected(const std::string& name) { return activeTool->getName() == name; } std::vector::iterator ToolManager::begin() { return tools.begin(); } std::vector::iterator ToolManager::end() { return tools.end(); } } ================================================ FILE: engine/tool/toolManager.h ================================================ #pragma once #include "tool.h" namespace P3D::Engine { class Event; class ToolManager { public: Tool* activeTool; std::vector tools; ToolManager(); virtual ~ToolManager(); ToolManager(ToolManager&& other) noexcept; ToolManager& operator=(ToolManager&& other) noexcept; ToolManager(const ToolManager& other) = delete; ToolManager& operator=(const ToolManager& other) = delete; template std::enable_if_t, bool> registerTool(Args&&... args) { DerivedTool* tool = new DerivedTool(std::forward(args)...); tools.push_back(tool); tool->onRegister(); return true; } template std::enable_if_t, bool> deregisterTool() { auto iterator = std::find_if(tools.begin(), tools.end(), [] (Tool* tool) { return tool->getName() == DerivedTool::getStaticName(); }); if (iterator == tools.end()) return false; DerivedTool* tool = *iterator; tools.erase(iterator); tool->onDeregister(); delete tool; return true; } virtual void onEvent(Event& event); virtual void onUpdate(); virtual void onRender(); virtual void onClose(); bool deselectTool(); bool selectTool(const std::string& name); bool selectTool(Tool* tool); bool isSelected(Tool* tool); bool isSelected(const std::string& name); template bool selectTool() { return selectTool(T::getStaticName()); } template bool isSelected() { return isSelected(T::getStaticName()); } std::vector::iterator begin(); std::vector::iterator end(); }; }; ================================================ FILE: examples/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(Physics3D-OpenGL-Demo VERSION 1.0) message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Building with: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") set(CMAKE_CXX_STANDARD 17) 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") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -march=native") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") endif() add_executable(openglBasic openglBasic.cpp ) # OpenGL, GLEW, GLFW find_package(OpenGL REQUIRED) find_package(GLEW REQUIRED) find_package(glfw3 REQUIRED) target_link_libraries(openglBasic glfw) target_link_libraries(openglBasic OpenGL::GL) target_link_libraries(openglBasic GLEW::GLEW) # Physics3D add_subdirectory(../Physics3D build) target_include_directories(openglBasic PUBLIC ../) target_link_libraries(openglBasic Physics3D) ================================================ FILE: examples/examples.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 16.0 Win32Proj {bbe4c27e-7eda-4f07-a5a0-9103bfb4c47c} examples 10.0 Application true v142 Unicode Application false v142 true Unicode Application true v142 Unicode Application false v142 true Unicode true false true false Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true true true Level3 true _DEBUG;_CONSOLE;%(PreprocessorDefinitions);GLEW_STATIC true $(SolutionDir)include;$(SolutionDir)application;$(SolutionDir) stdcpp17 Console true $(SolutionDir)lib;$(OutDir) glfw3.lib;glew32s.lib;opengl32.lib;Physics3D.lib;%(AdditionalDependencies) Level3 true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions);GLEW_STATIC true $(SolutionDir)include;$(SolutionDir)application;$(SolutionDir) stdcpp17 Console true true true $(SolutionDir)lib;$(OutDir) glfw3.lib;glew32s.lib;opengl32.lib;Physics3D.lib;%(AdditionalDependencies) ================================================ FILE: examples/openglBasic.cpp ================================================ // Include GLEW. Always include it before gl.h and glfw3.h, since it's a bit magic. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace P3D; GLFWwindow* initWindow() { // Initialise GLFW if(!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return nullptr; } glfwWindowHint(GLFW_SAMPLES, 4); // 4x antialiasing glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want OpenGL 3.3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // We don't want the old OpenGL // Open a window and create its OpenGL context GLFWwindow* window = glfwCreateWindow( 1024, 768, "Physics3D Demo", nullptr, nullptr); if(window == nullptr) { std::cerr << "Failed to open GLFW window." << std::endl; glfwTerminate(); return nullptr; } glfwMakeContextCurrent(window); // Initialize GLEW if(glewInit() != GLEW_OK) { std::cerr << "Failed to initialize GLEW" << std::endl; return nullptr; } // Ensure we can capture the escape key being pressed below glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE); return window; } struct Color { union { float data[3]; struct { float r; float g; float b; }; Vec3f asVec3f; }; }; namespace Colors { static constexpr Color BLUE {0.0f, 0.0f, 1.0f}; static constexpr Color RED {1.0f, 0.0f, 0.0f}; static constexpr Color GREEN {0.0f, 1.0f, 0.0f}; static constexpr Color WHITE {1.0f, 1.0f, 1.0f}; static constexpr Color BLACK {0.0f, 0.0f, 0.0f}; static constexpr Color GRAY {0.5f, 0.5f, 0.5f}; } const char* const basicVertexShader = R"( #version 300 es precision highp float; layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec3 vertexNormal_modelspace; uniform mat4 modelMatrix; uniform mat4 pvMatrix; uniform vec3 lightDirection; uniform vec3 partColor; out vec3 fragmentColor; void main(){ // Output position of the vertex, in clip space : pvMatrix * modelMatrix * position gl_Position = pvMatrix * modelMatrix * vec4(vertexPosition_modelspace,1); vec3 vertexNormal_worldspace = mat3(modelMatrix) * vertexNormal_modelspace; float brightness = (dot(-vertexNormal_worldspace, lightDirection) + 1.0f) / 2.0f; //fragmentColor = vec3(brightness, brightness, brightness); fragmentColor = brightness * partColor; } )"; const char* basicFragmentShader = R"( #version 300 es precision highp float; out vec3 color; in vec3 fragmentColor; void main() { color = fragmentColor; } )"; GLuint loadShaders(const char* vertexShaderSourceCode, const char* fragmentShaderSourceCode) { // Create the shaders GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER); GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); GLint Result = GL_FALSE; int InfoLogLength; // Compile Vertex Shader printf("Compiling vertex shader\n"); glShaderSource(VertexShaderID, 1, &vertexShaderSourceCode , NULL); glCompileShader(VertexShaderID); // Check Vertex Shader glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector VertexShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); printf("%s\n", &VertexShaderErrorMessage[0]); } // Compile Fragment Shader printf("Compiling fragment shader\n"); glShaderSource(FragmentShaderID, 1, &fragmentShaderSourceCode , NULL); glCompileShader(FragmentShaderID); // Check Fragment Shader glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector FragmentShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); printf("%s\n", &FragmentShaderErrorMessage[0]); } // Link the program printf("Linking program\n"); GLuint ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); // Check the program glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector ProgramErrorMessage(InfoLogLength+1); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); printf("%s\n", &ProgramErrorMessage[0]); } glDetachShader(ProgramID, VertexShaderID); glDetachShader(ProgramID, FragmentShaderID); glDeleteShader(VertexShaderID); glDeleteShader(FragmentShaderID); return ProgramID; } class RenderShader { GLuint programID; GLuint modelMatrixID; GLuint pvMatrixID; GLuint lightDirectionID; GLuint partColorID; public: RenderShader() { GLuint programID = loadShaders(basicVertexShader, basicFragmentShader); glUseProgram(programID); modelMatrixID = glGetUniformLocation(programID, "modelMatrix"); pvMatrixID = glGetUniformLocation(programID, "pvMatrix"); lightDirectionID = glGetUniformLocation(programID, "lightDirection"); partColorID = glGetUniformLocation(programID, "partColor"); } void setModelMatrix(Mat4f modelMatrix) { float modelData[16]; modelMatrix.toColMajorData(modelData); glUniformMatrix4fv(modelMatrixID, 1, GL_FALSE, modelData); } void setPVMatrix(Mat4f pvMatrix) { float pvData[16]; pvMatrix.toColMajorData(pvData); glUniformMatrix4fv(pvMatrixID, 1, GL_FALSE, pvData); } void setLightDirection(Vec3f lightDirection) { glUniform3fv(lightDirectionID, 1, lightDirection.data); } void setPartColor(Color color) { glUniform3fv(partColorID, 1, color.data); } }; class ArrayMesh { GLuint vertexArrayID; GLuint vertexBuffer; GLuint normalsBuffer; size_t triangleCount; constexpr static GLuint INVALID = 0; public: ArrayMesh() : vertexArrayID(INVALID), vertexBuffer(INVALID), normalsBuffer(INVALID), triangleCount(0) {} ArrayMesh(const float* vertexData, const float* normalsData, size_t triangleCount) : triangleCount(triangleCount) { glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); // benerate buffers GLuint buffers[2]; glGenBuffers(2, buffers); vertexBuffer = buffers[0]; normalsBuffer = buffers[1]; // load vertices glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, triangleCount * 3 * 3 * sizeof(float), vertexData, GL_STATIC_DRAW); // load normals glBindBuffer(GL_ARRAY_BUFFER, normalsBuffer); glBufferData(GL_ARRAY_BUFFER, triangleCount * 3 * 3 * sizeof(float), normalsData, GL_STATIC_DRAW); } ~ArrayMesh() { if(vertexArrayID != INVALID) { GLuint buffers[2]{vertexBuffer, normalsBuffer}; glDeleteBuffers(2, buffers); glDeleteVertexArrays(1, &vertexArrayID); } } ArrayMesh(ArrayMesh&) = delete; ArrayMesh& operator=(ArrayMesh&) = delete; ArrayMesh(ArrayMesh&& other) noexcept : vertexArrayID(other.vertexArrayID), vertexBuffer(other.vertexBuffer), normalsBuffer(other.normalsBuffer), triangleCount(other.triangleCount) { other.vertexArrayID = INVALID; other.vertexBuffer = INVALID; other.normalsBuffer = INVALID; } ArrayMesh& operator=(ArrayMesh&& other) noexcept { std::swap(this->vertexArrayID, other.vertexArrayID); std::swap(this->vertexBuffer, other.vertexBuffer); std::swap(this->normalsBuffer, other.normalsBuffer); std::swap(this->triangleCount, other.triangleCount); return *this; } void bind() const { // 1st attribute buffer : vertices glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glVertexAttribPointer( 0, // attribute 0. No particular reason for 0, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset ); // 2nd attribute buffer : normals glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, normalsBuffer); glVertexAttribPointer( 1, // attribute. No particular reason for 1, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_TRUE, // normalized? 0, // stride (void*)0 // array buffer offset ); } void unbind() const { glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); } void render() const { glDrawArrays(GL_TRIANGLES, 0, 3 * triangleCount); } }; /* To optimally reuse our ArrayMeshes so we don't use too much vram, we create just one arrayMesh for each ShapeClass. All Parts (no matter the size / scale) that have the same underlying ShapeClass will get the same ArrayMesh index. This class manages the conversion and registration of these meshes. */ class MeshRegistry { std::vector knownShapeClasses; std::vector meshes; int findKnownShapeClassIndex(const ShapeClass* shapeClass) { for(size_t i = 0; i < knownShapeClasses.size(); i++) { if(knownShapeClasses[i] == shapeClass) { return static_cast(i); } } return -1; } public: int registerShapeClassMesh(const ShapeClass* shapeClass, ArrayMesh&& mesh) { knownShapeClasses.push_back(shapeClass); meshes.push_back(std::move(mesh)); return static_cast(meshes.size() - 1); } int getMeshIndexFor(const ShapeClass* shapeClass) { for(size_t i = 0; i < knownShapeClasses.size(); i++) { if(knownShapeClasses[i] == shapeClass) { return static_cast(i); } } Polyhedron shapeClassPolyhedron = shapeClass->asPolyhedron(); Vec3f* vertexBuf = new Vec3f[shapeClassPolyhedron.triangleCount * 3]; Vec3f* normalBuf = new Vec3f[shapeClassPolyhedron.triangleCount * 3]; for(int i = 0; i < shapeClassPolyhedron.triangleCount; i++) { Triangle t = shapeClassPolyhedron.getTriangle(i); vertexBuf[i*3+0] = shapeClassPolyhedron.getVertex(t.firstIndex); vertexBuf[i*3+1] = shapeClassPolyhedron.getVertex(t.secondIndex); vertexBuf[i*3+2] = shapeClassPolyhedron.getVertex(t.thirdIndex); Vec3f normal = normalize(shapeClassPolyhedron.getNormalVecOfTriangle(t)); normalBuf[i*3+0] = normal; normalBuf[i*3+1] = normal; normalBuf[i*3+2] = normal; } ArrayMesh arMesh(reinterpret_cast(vertexBuf), reinterpret_cast(normalBuf), shapeClassPolyhedron.triangleCount); delete[] vertexBuf; delete[] normalBuf; return registerShapeClassMesh(shapeClass, std::move(arMesh)); } const ArrayMesh& operator[](int meshIndex) const { return meshes[meshIndex]; } }; MeshRegistry meshRegistry; class CustomPart : public Part { public: // This index is the visual shape of the part, it indexes into MeshRegistry to find the proper ArrayMesh to draw. // For ease this is generated from the base shape's shapeClass. int meshIndex; Color color; CustomPart(const Shape& shape, const GlobalCFrame& position, const PartProperties& properties, int meshIndex, Color color) : Part(shape, position, properties), meshIndex(meshIndex), color(color) {} CustomPart(const Shape& shape, const GlobalCFrame& position, const PartProperties& properties, Color color) : Part(shape, position, properties), meshIndex(meshRegistry.getMeshIndexFor(shape.baseShape.get())), color(color) {} }; void render(World& world, UpgradeableMutex& worldMutex, RenderShader& shader) { Mat4f viewMatrix = lookAt(Vec3f(12.0f, 6.0f, 15.0f), Vec3f(0.0f, 0.0f, 0.0f), Vec3f(0.0f, 1.0f, 0.0f)); Mat4f projectionMatrix = perspective(0.8f, 4.0f/3.0f, 0.1f, 100.0f); shader.setPVMatrix(projectionMatrix * viewMatrix); // multithreaded application, must use synchronization with PhysicsThread std::shared_lock worldReadLock(worldMutex); world.forEachPart([&](const CustomPart& part) { const ArrayMesh& mesh = meshRegistry[part.meshIndex]; mesh.bind(); shader.setPartColor(part.color); shader.setModelMatrix(part.getCFrame().asMat4WithPreScale(part.hitbox.scale)); mesh.render(); mesh.unbind(); }); } int main(int argc, const char** argv) { std::cout << "Starting Physics3D Demo" << std::endl; GLFWwindow* window = initWindow(); if(!window) return -1; // Enable depth test glEnable(GL_DEPTH_TEST); // Accept fragment if it closer to the camera than the former one glDepthFunc(GL_LESS); // Add face culling glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); RenderShader shader; // World constructor accepts the tick delay. This code creates a world to run at 100 TPS. World world(1 / 100.0); UpgradeableMutex worldMutex; PartProperties basicProperties; basicProperties.density = 1.0; basicProperties.friction = 0.5; basicProperties.bouncyness = 0.4; // We must manage Part memory ourselves. World is simply a Part* container. std::unique_ptr floor = std::make_unique( boxShape(20.0, 0.5, 20.0), GlobalCFrame(0.0, -2.0, 0.0), basicProperties, Colors::WHITE); std::unique_ptr wall = std::make_unique( boxShape(3.0, 1.0, 10.0), GlobalCFrame(7.0, -2.0, 0.0, Rotation::fromEulerAngles(0.0, 0.0, 0.2)), basicProperties, Colors::GRAY); std::unique_ptr box = std::make_unique( boxShape(2.0, 0.5, 3.0), GlobalCFrame(0.0, 1.3, 1.0, Rotation::fromEulerAngles(0.2, 0.1, 0.3)), basicProperties, Colors::BLUE); std::unique_ptr sphere = std::make_unique( sphereShape(1.0), GlobalCFrame(1.4, 4.7, 1.0), basicProperties, Colors::GREEN); std::unique_ptr icosahedron = std::make_unique( polyhedronShape(ShapeLibrary::icosahedron), GlobalCFrame(4.0, 1.3, 4.0), basicProperties, Colors::RED); world.addTerrainPart(floor.get()); world.addTerrainPart(wall.get()); world.addPart(box.get()); world.addPart(sphere.get()); world.addPart(icosahedron.get()); // We can set part velocities and other properties. icosahedron->setAngularVelocity(Vec3(5.0, 5.0, 0.0)); // External Force memory is also not managed by the engine. std::unique_ptr gravity = std::make_unique(Vec3(0.0, -10.0, 0.0)); world.addExternalForce(gravity.get()); PhysicsThread physicsThread(&world, &worldMutex); physicsThread.start(); shader.setLightDirection(normalize(Vec3f(0.4f, -1.0f, 0.2f))); // render loop do { // Clear the screen. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); render(world, worldMutex, shader); // Swap buffers glfwSwapBuffers(window); glfwPollEvents(); // Check if the ESC key was pressed or the window was closed } while(glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0); return 0; } ================================================ FILE: graphics/batch/batch.h ================================================ #pragma once #include "batchConfig.h" #include "../buffers/vertexArray.h" #include "../buffers/vertexBuffer.h" #include "../buffers/indexBuffer.h" #include "../renderer.h" namespace P3D::Graphics { template class Batch { protected: VertexArray* vao = nullptr; VertexBuffer* vbo = nullptr; IndexBuffer* ibo = nullptr; BatchConfig config; std::vector vertexBuffer; std::vector indexBuffer; size_t indexCounter; public: Vertex* vertexPointer; unsigned int* indexPointer; size_t currentIndex; Batch(const BatchConfig& config) : config(config) { vao = new VertexArray(); vbo = new VertexBuffer(config.bufferLayout, nullptr, 0); ibo = new IndexBuffer(nullptr, 0); vao->addBuffer(vbo); vertexPointer = vertexBuffer.data(); indexPointer = indexBuffer.data(); indexCounter = 0; currentIndex = 0; }; Batch(const Batch&) = delete; Batch& operator=(const Batch&) = delete; ~Batch() { close(); } Batch(Batch&& other) { vao = other.vao; other.vao = nullptr; vbo = other.vbo; other.vbo = nullptr; ibo = other.ibo; other.ibo = nullptr; indexCounter = other.indexCounter; other.indexCounter = 0; currentIndex = other.currentIndex; other.currentIndex = 0; vertexBuffer = std::move(other.vertexBuffer); other.vertexBuffer.clear(); indexBuffer = std::move(other.indexBuffer); other.indexBuffer.clear(); vertexPointer = other.vertexPointer; other.vertexPointer = other.vertexBuffer.data(); indexPointer = other.indexPointer; other.indexPointer = other.indexBuffer.data(); config = std::move(other.config); } Batch& operator=(Batch&& other) { if (this != &other) { close(); std::swap(vao, other.vao); std::swap(vbo, other.vbo); std::swap(ibo, other.ibo); config = std::move(other.config); vertexBuffer = std::move(other.vertexBuffer); indexBuffer = std::move(other.indexBuffer); indexCounter = std::move(other.indexCounter); currentIndex = std::move(other.currentIndex); vertexPointer = std::move(other.vertexPointer); indexPointer = std::move(other.indexPointer); } return *this; } inline void pushVertex(Vertex vertex) { *vertexPointer++ = vertex; indexCounter++; } inline void pushVertices(std::initializer_list vertices) { for (Vertex vertex : vertices) { *vertexPointer++ = vertex; indexCounter++; } } inline void pushIndex(size_t index) { *indexPointer++ = currentIndex + index; } inline void pushIndices(std::initializer_list indices) { for (size_t index : indices) *indexPointer++ = currentIndex + index; } void reserve(size_t vertexCount, size_t indexCount) { size_t oldVertexBufferSize = vertexBuffer.size(); vertexBuffer.resize(oldVertexBufferSize + vertexCount); vertexPointer = vertexBuffer.data() + oldVertexBufferSize; size_t oldIndexBufferSize = indexBuffer.size(); indexBuffer.resize(oldIndexBufferSize + indexCount); indexPointer = indexBuffer.data() + oldIndexBufferSize; } void endIndex() { currentIndex += indexCounter; indexCounter = 0; } virtual void submit() { vao->bind(); vbo->fill((const void*) vertexBuffer.data(), vertexBuffer.size() * sizeof(Vertex), Graphics::Renderer::STREAM_DRAW); ibo->fill((const unsigned int*) indexBuffer.data(), indexBuffer.size(), Graphics::Renderer::STREAM_DRAW); Graphics::Renderer::drawElements(config.type, indexBuffer.size(), Graphics::Renderer::UINT, nullptr); vbo->unbind(); ibo->unbind(); vao->unbind(); clear(); } void clear() { vertexBuffer.clear(); indexBuffer.clear(); vertexPointer = vertexBuffer.data(); indexPointer = indexBuffer.data(); currentIndex = 0; indexCounter = 0; } void close() { clear(); vao->close(); vbo->close(); ibo->close(); } }; }; ================================================ FILE: graphics/batch/batchConfig.h ================================================ #pragma once #include "../buffers/bufferLayout.h" namespace P3D::Graphics { struct BatchConfig { // Layout of the vertex buffer BufferLayout bufferLayout; // Primitive type unsigned int type; BatchConfig(BufferLayout bufferLayout, unsigned int type) : bufferLayout(bufferLayout), type(type) {}; }; }; ================================================ FILE: graphics/batch/commandBatch.h ================================================ #pragma once #include "batch.h" #include "../shader/shader.h" namespace P3D::Graphics { template class CommandBatch : public Batch { protected: std::vector commandBuffer; size_t currentIndexOffset; public: CommandBatch(BatchConfig config) : Batch(config), currentIndexOffset(0) { } virtual void pushCommand(Command command) { size_t count = Batch::indexBuffer.size() - currentIndexOffset; if (count == 0) return; commandBuffer.push_back(command); currentIndexOffset += count; } virtual void submit() { }; virtual void clear() { currentIndexOffset = 0; Batch::clear(); commandBuffer.clear(); } }; }; ================================================ FILE: graphics/batch/guiBatch.h ================================================ #pragma once #include "commandBatch.h" #include "batchConfig.h" #include "../gui/color.h" #include "../buffers/bufferLayout.h" #include "../shader/shaders.h" namespace P3D::Graphics { struct GuiCommand { // Amount of indices to render size_t count; // Optional texture ID GLID textureID; // Offset in index buffer size_t indexOffset; }; struct GuiVertex { Vec2f pos; Vec2f uv; Color col; }; class GuiBatch : public CommandBatch { public: GuiBatch() : CommandBatch( BatchConfig( BufferLayout({ BufferElement("pos", BufferDataType::FLOAT2), BufferElement("uv", BufferDataType::FLOAT2), BufferElement("col", BufferDataType::FLOAT4) }), Renderer::TRIANGLES )) { } void pushCommand(GLID textureID) { size_t count = Batch::indexBuffer.size() - currentIndexOffset; if (count == 0) return; GuiCommand command = { count, textureID, currentIndexOffset }; CommandBatch::pushCommand(command); } void submit() override { Shaders::guiShader->bind(); if (commandBuffer.empty()) pushCommand(0); Batch::vao->bind(); Batch::vbo->fill((const void*) Batch::vertexBuffer.data(), Batch::vertexBuffer.size() * sizeof(GuiVertex), Renderer::STREAM_DRAW); Batch::ibo->fill((const unsigned int*) Batch::indexBuffer.data(), Batch::indexBuffer.size(), Renderer::STREAM_DRAW); GLID lastID = -1; size_t lastCount = 0; size_t lastIndexOffset = 0; for (const GuiCommand& command : commandBuffer) { GLID ID = command.textureID; size_t count = command.count; size_t indexOffset = command.indexOffset; if (ID == lastID) { // merge calls, no shader update lastCount += count; } else { // render merged calls, shader update, texture bind, render this call Renderer::drawElements(Renderer::TRIANGLES, lastCount, Renderer::UINT, (const void*) (intptr_t) (lastIndexOffset * sizeof(unsigned int))); // update shader if (ID == 0) { Shaders::guiShader->setTextured(false); } else { Renderer::bindTexture2D(ID); Shaders::guiShader->setTextured(true); } lastCount = count; lastIndexOffset = indexOffset; } // update last ID lastID = ID; } Renderer::drawElements(Renderer::TRIANGLES, lastCount, Renderer::UINT, (const void*) (intptr_t) (lastIndexOffset * sizeof(unsigned int))); Batch::vbo->unbind(); Batch::ibo->unbind(); Batch::vao->unbind(); clear(); } void clear() { currentIndexOffset = 0; Batch::clear(); commandBuffer.clear(); } }; }; ================================================ FILE: graphics/batch/instanceBatch.h ================================================ #pragma once #include #include #include #include "../renderer.h" #include "../mesh/indexedMesh.h" #include "../buffers/bufferLayout.h" #include "../meshRegistry.h" #include "graphics/debug/guiDebug.h" #include "shader/shaders.h" #include "util/stringUtil.h" #include "util/systemVariables.h" namespace P3D::Graphics { class InstanceBatch { public: struct Uniform { Mat4f modelMatrix = Mat4f::IDENTITY(); Vec4f albedo = Vec4f::full(1.0f); float metalness = 1.0f; float roughness = 1.0f; float ao = 1.0f; unsigned textureFlags1; unsigned textureFlags2; }; private: std::size_t mesh = -1; std::vector uniformBuffer; std::map textureBuffer; public: InstanceBatch(std::size_t id, const BufferLayout& uniformBufferLayout) : mesh(id) { if (id >= MeshRegistry::meshes.size()) { Log::error("Creating an instance batch for an invalid mesh: %d", id); return; } if (MeshRegistry::get(id)->uniformBuffer == nullptr) { VertexBuffer* uniformBuffer = new VertexBuffer(uniformBufferLayout, nullptr, 0); MeshRegistry::get(id)->addUniformBuffer(uniformBuffer); } } ~InstanceBatch() = default; InstanceBatch(const InstanceBatch&) = default; InstanceBatch(InstanceBatch&&) noexcept = default; InstanceBatch& operator=(const InstanceBatch&) = default; InstanceBatch& operator=(InstanceBatch&&) noexcept = default; bool add(const Mat4f& modelMatrix, const Comp::Material& material) { using namespace Comp; std::uint64_t textureFlags = 0; int textureCount = material.getTextureCount(); if (textureCount > 0) { int maxTextures = SystemVariables::get("MAX_TEXTURE_IMAGE_UNITS"); int remainingTextures = maxTextures - static_cast(textureBuffer.size()); // Check if buffer is full if (textureCount > remainingTextures) { // Get all new textures std::set newTextures; for (std::size_t index = 0; index < Material::MAP_COUNT; index++) { SRef texture = material.get(index); if (texture == nullptr) continue; if (textureBuffer.find(texture->getID()) == textureBuffer.end()) newTextures.insert(texture->getID()); } // Fail if there is not enough space if (static_cast(newTextures.size()) > remainingTextures) return false; } // Add all IDs for (std::size_t index = 0; index < Material::MAP_COUNT; index++) { SRef texture = material.get(index); if (texture == nullptr) continue; addTexture(texture->getID()); } // Create texture flag for (std::size_t index = 0; index < Material::MAP_COUNT; index++) { textureFlags <<= 8; SRef texture = material.get(index); if (texture != nullptr) textureFlags |= static_cast(textureBuffer[texture->getID()] + 1u); } } Uniform uniform { modelMatrix, material.albedo, material.metalness, material.roughness, material.ao, static_cast(textureFlags >> 32), static_cast(textureFlags & 0xFFFFFFFF) }; uniformBuffer.push_back(uniform); return true; } void addTexture(GLID id) { auto iterator = textureBuffer.find(id); if (iterator == textureBuffer.end()) textureBuffer[id] = static_cast(textureBuffer.size()); } void bindTextures() { for (auto [id, unit] : textureBuffer) { Renderer::activeTexture(unit); Renderer::bindTexture2D(id); } } void submit() { if (mesh >= MeshRegistry::meshes.size()) { Log::error("Trying to sumbit a mesh that has not been registered in the mesh registry"); return; } if (uniformBuffer.empty()) return; // Bind textures if (!textureBuffer.empty()) bindTextures(); // Draw meshes MeshRegistry::get(mesh)->fillUniformBuffer(uniformBuffer.data(), uniformBuffer.size() * sizeof(Uniform), Renderer::STREAM_DRAW); MeshRegistry::get(mesh)->renderInstanced(uniformBuffer.size()); clear(); } void clear() { uniformBuffer.clear(); } }; }; ================================================ FILE: graphics/batch/instanceBatchManager.h ================================================ #pragma once #include #include "instanceBatch.h" #include "../util/log.h" #include "../shader/shader.h" #include "../buffers/bufferLayout.h" namespace P3D::Graphics { class InstanceBatchManager { private: SRef shader; BufferLayout uniformBufferLayout; std::unordered_map> batches; public: InstanceBatchManager(SRef shader, const BufferLayout& uniformBufferLayout) : shader(shader), uniformBufferLayout(uniformBufferLayout) {} void add(std::size_t mesh, const Mat4f& modelMatrix, const Comp::Material& material) { if (mesh >= MeshRegistry::meshes.size()) { Log::error("Trying to add a mesh that has not been registered in the mesh registry"); return; } auto batchIterator = batches.find(mesh); if (batchIterator == batches.end()) batchIterator = batches.emplace(mesh, std::vector { InstanceBatch(mesh, uniformBufferLayout) }).first; std::vector& currentBatches = batchIterator->second; InstanceBatch& currentBatch = currentBatches[currentBatches.size() - 1]; bool success = currentBatch.add(modelMatrix, material); if (!success) { InstanceBatch newBatch(mesh, uniformBufferLayout); newBatch.add(modelMatrix, material); currentBatches.push_back(newBatch); } } void submit(std::size_t mesh) { shader->bind(); auto iterator = batches.find(mesh); if (iterator != batches.end()) for (InstanceBatch& batch : iterator->second) batch.submit(); } void submit() { shader->bind(); for (auto& [mesh, batches] : this->batches) for (auto& batch : batches) batch.submit(); } void clear() { for (auto& [mesh, batches] : this->batches) for (auto& batch : batches) batch.clear(); } }; }; ================================================ FILE: graphics/bindable.cpp ================================================ #include "core.h" #include "bindable.h" namespace P3D::Graphics { Bindable::Bindable() : id(0) {} Bindable::Bindable(GLID id) : id(id) {} GLID Bindable::getID() const { return id; } }; ================================================ FILE: graphics/bindable.h ================================================ #pragma once typedef unsigned int GLID; namespace P3D::Graphics { class Bindable { protected: GLID id; Bindable(); Bindable(GLID id); public: [[nodiscard]] GLID getID() const; virtual void bind() = 0; virtual void unbind() = 0; virtual void close() = 0; }; }; ================================================ FILE: graphics/buffers/bufferLayout.cpp ================================================ #include "core.h" #include "bufferLayout.h" #include namespace P3D::Graphics { namespace BufferDataType { const Info NONE = { "none" , 0 , 0, 0, false }; const Info BOOL = { "bool" , GL_BYTE , 1, 1, true }; const Info UINT = { "uint" , GL_UNSIGNED_INT , 1, 4, true }; const Info UINT2 = { "vec2u" , GL_UNSIGNED_INT , 2, 8, true }; const Info UINT3 = { "vec3u" , GL_UNSIGNED_INT , 3, 12, true }; const Info UINT4 = { "vec4u" , GL_UNSIGNED_INT , 4, 16, true }; const Info INT = { "int" , GL_INT , 1, 4, true }; const Info INT2 = { "vec2i" , GL_INT , 2, 8, true }; const Info INT3 = { "vec3i" , GL_INT , 3, 12, true }; const Info INT4 = { "vec4i" , GL_INT , 4, 16, true }; const Info FLOAT = { "float" , GL_FLOAT , 1, 4, false }; const Info FLOAT2 = { "vec2" , GL_FLOAT , 2, 8, false }; const Info FLOAT3 = { "vec3" , GL_FLOAT , 3, 12, false }; const Info FLOAT4 = { "vec4" , GL_FLOAT , 4, 16, false }; const Info MAT2 = { "mat2" , GL_FLOAT , 2, 8, false }; // per row const Info MAT3 = { "mat3" , GL_FLOAT , 3, 12, false }; // per row const Info MAT4 = { "mat4" , GL_FLOAT , 4, 16, false }; // per row } }; ================================================ FILE: graphics/buffers/bufferLayout.h ================================================ #pragma once namespace P3D::Graphics { #define DEFAULT_UNIFORM_BUFFER_LAYOUT \ BufferLayout({ \ BufferElement("vModelMatrix", BufferDataType::MAT4, true), \ BufferElement("vAlbedo", BufferDataType::FLOAT4, true), \ BufferElement("vMRAo", BufferDataType::FLOAT3, true), \ BufferElement("vTextureFlags", BufferDataType::UINT2, true) \ }) namespace BufferDataType { struct Info { // name of data type std::string name; // data type of each component int type; // amount of components per attributes int count; // size of type in bytes int size; // Whether the type is integral bool integral; Info(const std::string& name, int type, int count, int size, bool integral) : name(name) , type(type) , count(count) , size(size) , integral(integral) {} bool operator==(const Info& other) const { return other.name == name; } }; extern const Info NONE; extern const Info BOOL; extern const Info UINT; extern const Info UINT2; extern const Info UINT3; extern const Info UINT4; extern const Info INT; extern const Info INT2; extern const Info INT3; extern const Info INT4; extern const Info FLOAT; extern const Info FLOAT2; extern const Info FLOAT3; extern const Info FLOAT4; extern const Info MAT2; extern const Info MAT3; extern const Info MAT4; }; struct BufferElement { std::string name; BufferDataType::Info info; bool instanced; bool normalized; BufferElement(const std::string& name, const BufferDataType::Info& info, bool instanced = false, bool normalized = false) : name(name) , info(info) , instanced(instanced) , normalized(normalized) {} }; struct BufferLayout { std::vector elements; int stride; BufferLayout(const std::vector& elements) : elements(elements) , stride(0) { for (BufferElement& element : this->elements) { int multiplier = (element.info == BufferDataType::MAT2 || element.info == BufferDataType::MAT3 || element.info == BufferDataType::MAT4) ? element.info.count : 1; stride += element.info.size * multiplier; } } BufferLayout() : elements({}) , stride(0) {} }; }; ================================================ FILE: graphics/buffers/frameBuffer.cpp ================================================ #include "core.h" #include "frameBuffer.h" #include #include #include "renderer.h" #include "renderBuffer.h" #include "texture.h" namespace P3D::Graphics { #pragma region FrameBuffer //! FrameBuffer FrameBuffer::FrameBuffer(unsigned int width, unsigned int height) { glGenFramebuffers(1, &id); bind(); texture = std::make_shared(width, height); renderBuffer = std::make_shared(width, height); attach(texture); attach(renderBuffer); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Log::error("FrameBuffer object with id (%d) not complete", id); unbind(); } FrameBuffer::FrameBuffer(SRef colorAttachment, SRef depthStencilAttachment) { glGenFramebuffers(1, &id); bind(); attach(colorAttachment); attach(depthStencilAttachment); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Log::error("FrameBuffer object with id (%d) not complete", id); unbind(); } FrameBuffer::~FrameBuffer() { Log::warn("Closing framebuffer #%d", id); glDeleteFramebuffers(1, &id); } void FrameBuffer::bind() { Renderer::bindFramebuffer(id); } void FrameBuffer::unbind() { Renderer::bindFramebuffer(0); } void FrameBuffer::resize(const Vec2i& dimension) { this->dimension = dimension; if (texture) texture->resize(dimension.x, dimension.y); if (renderBuffer) renderBuffer->resize(dimension.x, dimension.y); } void FrameBuffer::attach(SRef texture) { bind(); this->texture = texture; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->getID(), 0); } void FrameBuffer::attach(SRef renderBuffer) { bind(); this->renderBuffer = renderBuffer; glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer->getID()); } void FrameBuffer::close() { // TODO remove } #pragma endregion #pragma region FrameBuffer //! MainFrameBuffer MainFrameBuffer::MainFrameBuffer(unsigned int width, unsigned int height) : Bindable() { glGenFramebuffers(1, &id); bind(); fragment = std::make_shared(width, height); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fragment->getID(), 0); brightness = std::make_shared(width, height); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, brightness->getID(), 0); renderBuffer = std::make_shared(width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer->getID()); GLID attachements[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, attachements); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Log::error("FrameBuffer object with id (%d) not complete", id); unbind(); } MainFrameBuffer::~MainFrameBuffer() { Log::warn("Closing main framebuffer #%d", id); glDeleteFramebuffers(1, &id); } void MainFrameBuffer::bind() { Renderer::bindFramebuffer(id); } void MainFrameBuffer::unbind() { Renderer::bindFramebuffer(0); } void MainFrameBuffer::resize(const Vec2i& dimension) { this->dimension = dimension; fragment->resize(dimension.x, dimension.y); brightness->resize(dimension.x, dimension.y); renderBuffer->resize(dimension.x, dimension.y); } void MainFrameBuffer::close() { // TODO remove } #pragma endregion #pragma region HDRFrameBuffer //! HDRFrameBuffer HDRFrameBuffer::HDRFrameBuffer(unsigned int width, unsigned int height) { glGenFramebuffers(1, &id); bind(); texture = std::make_shared(width, height); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->getID(), 0); renderBuffer = std::make_shared(width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer->getID()); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Log::error("FrameBuffer object with id (%d) not complete", id); unbind(); }; HDRFrameBuffer::~HDRFrameBuffer() { Log::warn("Closing HDR framebuffer #%d", id); glDeleteFramebuffers(1, &id); } void HDRFrameBuffer::resize(const Vec2i& dimension) { this->dimension = dimension; if (texture) texture->resize(dimension.x, dimension.y); if (renderBuffer) renderBuffer->resize(dimension.x, dimension.y); }; void HDRFrameBuffer::bind() { Renderer::bindFramebuffer(id); }; void HDRFrameBuffer::unbind() { Renderer::bindFramebuffer(0); }; void HDRFrameBuffer::close() { // TODO remove }; #pragma endregion #pragma region MultisampleFrameBuffer //! MultisampleFrameBuffer MultisampleFrameBuffer::MultisampleFrameBuffer(unsigned int width, unsigned int height, int samples) { glGenFramebuffers(1, &id); bind(); texture = std::make_shared(width, height, samples); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, texture->getID(), 0); renderBuffer = std::make_shared(width, height, samples); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer->getID()); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Log::error("FrameBuffer object with id (%d) not complete", id); unbind(); }; MultisampleFrameBuffer::~MultisampleFrameBuffer() { Log::warn("Closing multisample framebuffer #%d", id); glDeleteFramebuffers(1, &id); } void MultisampleFrameBuffer::resize(const Vec2i& dimension) { this->dimension = dimension; if (texture) texture->resize(dimension.x, dimension.y); if (renderBuffer) renderBuffer->resize(dimension.x, dimension.y); }; void MultisampleFrameBuffer::bind() { Renderer::bindFramebuffer(id); }; void MultisampleFrameBuffer::unbind() { Renderer::bindFramebuffer(0); }; void MultisampleFrameBuffer::close() { // TODO remove }; #pragma endregion #pragma region DepthFrameBuffer //! DepthFrameBuffer DepthFrameBuffer::DepthFrameBuffer(unsigned int width, unsigned int height) : width(width), height(height) { glGenFramebuffers(1, &id); bind(); texture = std::make_shared(width, height); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture->getID(), 0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); unbind(); } DepthFrameBuffer::~DepthFrameBuffer() { Log::warn("Closing depth framebuffer #%d", id); glDeleteFramebuffers(1, &id); } void DepthFrameBuffer::bind() { Renderer::bindFramebuffer(id); } void DepthFrameBuffer::unbind() { Renderer::bindFramebuffer(0); } void DepthFrameBuffer::close() { // TODO remove } #pragma endregion } ================================================ FILE: graphics/buffers/frameBuffer.h ================================================ #pragma once #include "../bindable.h" namespace P3D::Graphics { class Texture; class RenderBuffer; class FrameBuffer : public Bindable { private: FrameBuffer(); public: Vec2i dimension; SRef texture; SRef renderBuffer; FrameBuffer(unsigned int width, unsigned int height); FrameBuffer(SRef colorAttachment, SRef depthStencilAttachment); virtual ~FrameBuffer(); void bind() override; void unbind() override; void close() override; void resize(const Vec2i& dimension); void attach(SRef texture); void attach(SRef renderBuffer); }; class MainFrameBuffer : public Bindable { private: MainFrameBuffer(); public: Vec2i dimension; SRef fragment; SRef brightness; SRef renderBuffer; MainFrameBuffer(unsigned int width, unsigned int height); virtual ~MainFrameBuffer(); void bind() override; void unbind() override; void close() override; void resize(const Vec2i& dimension); }; class HDRTexture; class HDRFrameBuffer : public Bindable { public: Vec2i dimension; SRef texture; SRef renderBuffer; HDRFrameBuffer(); HDRFrameBuffer(unsigned int width, unsigned int height); virtual ~HDRFrameBuffer(); void bind() override; void unbind() override; void close() override; void resize(const Vec2i& dimension); }; class MultisampleTexture; class MultisampleRenderBuffer; class MultisampleFrameBuffer : public Bindable { public: Vec2i dimension; SRef texture; SRef renderBuffer; MultisampleFrameBuffer(); MultisampleFrameBuffer(unsigned int width, unsigned int height, int samples); ~MultisampleFrameBuffer(); void bind() override; void unbind() override; void close() override; void resize(const Vec2i& dimension); }; class DepthTexture; class DepthFrameBuffer : public Bindable { public: unsigned int width; unsigned int height; SRef texture; DepthFrameBuffer(unsigned int width, unsigned int height); ~DepthFrameBuffer(); void bind() override; void unbind() override; void close() override; }; }; ================================================ FILE: graphics/buffers/indexBuffer.cpp ================================================ #include "core.h" #include "indexBuffer.h" #include namespace P3D::Graphics { IndexBuffer::IndexBuffer() : Bindable() { Log::debug("Created empty index buffer"); }; IndexBuffer::IndexBuffer(const unsigned int* data, size_t size, GLFLAG mode) { glGenBuffers(1, &id); bind(); if (size != 0) fill(data, size, mode); } IndexBuffer::IndexBuffer(const unsigned int* data, size_t size) : IndexBuffer(data, size, GL_STATIC_DRAW) {} IndexBuffer::~IndexBuffer() { close(); Log::warn("Deleted index buffer with id (%d)", id); } IndexBuffer::IndexBuffer(IndexBuffer&& other) { id = other.id; other.id = 0; } IndexBuffer& IndexBuffer::operator=(IndexBuffer&& other) { if (this != &other) { close(); std::swap(id, other.id); } return *this; } void IndexBuffer::fill(const unsigned int* data, size_t size, GLFLAG mode) { bind(); glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(unsigned int), data, mode); } void IndexBuffer::update(const unsigned int* data, size_t size, int offset) { bind(); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, size * sizeof(unsigned int), data); } void IndexBuffer::bind() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); } void IndexBuffer::unbind() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void IndexBuffer::close() { unbind(); glDeleteBuffers(1, &id); id = 0; } }; ================================================ FILE: graphics/buffers/indexBuffer.h ================================================ #pragma once #include "../bindable.h" #include "../renderer.h" namespace P3D::Graphics { class IndexBuffer : public Bindable { public: IndexBuffer(); IndexBuffer(const unsigned int* data, size_t size, GLFLAG mode); IndexBuffer(const unsigned int* data, size_t size); ~IndexBuffer(); IndexBuffer(IndexBuffer&& other); IndexBuffer(const IndexBuffer&) = delete; IndexBuffer& operator=(IndexBuffer&& other); IndexBuffer& operator=(const IndexBuffer&) = delete; void fill(const unsigned int* data, size_t size, GLFLAG mode); void update(const unsigned int* data, size_t size, int offset); void bind() override; void unbind() override; void close() override; }; }; ================================================ FILE: graphics/buffers/renderBuffer.cpp ================================================ #include "core.h" #include "renderBuffer.h" #include #include #include "renderer.h" namespace P3D::Graphics { RenderBuffer::RenderBuffer(unsigned int width, unsigned int height) : width(width), height(height) { glGenRenderbuffers(1, &id); bind(); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); unbind(); } RenderBuffer::~RenderBuffer() { close(); } RenderBuffer::RenderBuffer(RenderBuffer&& other) { id = other.id; other.id = 0; } RenderBuffer& RenderBuffer::operator=(RenderBuffer&& other) { if (this != &other) { close(); std::swap(id, other.id); } return *this; } void RenderBuffer::resize(unsigned int width, unsigned int height) { bind(); this->width = width; this->height = height; glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); unbind(); } void RenderBuffer::bind() { Renderer::bindRenderbuffer(id); } void RenderBuffer::unbind() { Renderer::bindRenderbuffer(0); } void RenderBuffer::close() { glDeleteRenderbuffers(1, &id); id = 0; } // MultisampleRenderBuffer MultisampleRenderBuffer::MultisampleRenderBuffer(unsigned int width, unsigned int height, unsigned int samples) : width(width), height(height), samples(samples) { glGenRenderbuffers(1, &id); bind(); glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, width, height); unbind(); } MultisampleRenderBuffer::~MultisampleRenderBuffer() { close(); } MultisampleRenderBuffer::MultisampleRenderBuffer(MultisampleRenderBuffer&& other) { id = other.id; other.id = 0; } MultisampleRenderBuffer& MultisampleRenderBuffer::operator=(MultisampleRenderBuffer&& other) { if (this != &other) { close(); std::swap(id, other.id); } return *this; } void MultisampleRenderBuffer::resize(unsigned int width, unsigned int height) { bind(); this->width = width; this->height = height; glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, width, height); unbind(); } void MultisampleRenderBuffer::bind() { Renderer::bindRenderbuffer(id); } void MultisampleRenderBuffer::unbind() { Renderer::bindRenderbuffer(0); } void MultisampleRenderBuffer::close() { glDeleteRenderbuffers(1, &id); id = 0; } }; ================================================ FILE: graphics/buffers/renderBuffer.h ================================================ #pragma once #include "../bindable.h" namespace P3D::Graphics { class RenderBuffer : public Bindable { public: unsigned int width; unsigned int height; RenderBuffer(unsigned int width, unsigned int height); ~RenderBuffer(); RenderBuffer(RenderBuffer&& other); RenderBuffer(const RenderBuffer&) = delete; RenderBuffer& operator=(RenderBuffer&& other); RenderBuffer& operator=(const RenderBuffer&) = delete; void bind() override; void unbind() override; void close() override; void resize(unsigned int width, unsigned int height); }; class MultisampleRenderBuffer : public Bindable { public: unsigned int width; unsigned int height; unsigned int samples; MultisampleRenderBuffer(unsigned int width, unsigned int height, unsigned int samples); ~MultisampleRenderBuffer(); MultisampleRenderBuffer(MultisampleRenderBuffer&& other); MultisampleRenderBuffer(const MultisampleRenderBuffer&) = delete; MultisampleRenderBuffer& operator=(MultisampleRenderBuffer&& other); MultisampleRenderBuffer& operator=(const MultisampleRenderBuffer&) = delete; void bind() override; void unbind() override; void close() override; void resize(unsigned int width, unsigned int height); }; }; ================================================ FILE: graphics/buffers/vertexArray.cpp ================================================ #include "core.h" #include "vertexArray.h" #include #include "vertexBuffer.h" #include "bufferLayout.h" namespace P3D::Graphics { VertexArray::VertexArray() : attributeArrayOffset(0) { glGenVertexArrays(1, &id); glBindVertexArray(id); } VertexArray::~VertexArray() { close(); Log::debug("Deleted vertex array with id (%d)", id); } VertexArray::VertexArray(VertexArray&& other) { attributeArrayOffset = other.attributeArrayOffset; other.attributeArrayOffset = 0; id = other.id; other.id = 0; } VertexArray& VertexArray::operator=(VertexArray&& other) { if (this != &other) { close(); std::swap(id, other.id); } return *this; } void VertexArray::bind() { glBindVertexArray(id); } void VertexArray::unbind() { glBindVertexArray(0); } void VertexArray::addBuffer(VertexBuffer* buffer) { bind(); buffer->bind(); size_t offset = 0; for (size_t i = 0; i < buffer->layout.elements.size(); i++) { auto& element = buffer->layout.elements[i]; int iterations = (element.info == BufferDataType::MAT2 || element.info == BufferDataType::MAT3 || element.info == BufferDataType::MAT4) ? element.info.count : 1; for (size_t j = 0; j < iterations; j++) { glEnableVertexAttribArray(attributeArrayOffset); if (element.info.integral) glVertexAttribIPointer(attributeArrayOffset, element.info.count, element.info.type, buffer->layout.stride, (const void*) offset); else glVertexAttribPointer(attributeArrayOffset, element.info.count, element.info.type, element.normalized, buffer->layout.stride, (const void*) offset); if (element.instanced) glVertexAttribDivisor(attributeArrayOffset, 1); offset += element.info.size; attributeArrayOffset++; } } } void VertexArray::close() { unbind(); glDeleteVertexArrays(1, &id); id = 0; } }; ================================================ FILE: graphics/buffers/vertexArray.h ================================================ #pragma once #include "../bindable.h" namespace P3D::Graphics { struct BufferLayout; class VertexBuffer; class VertexArray : public Bindable { public: unsigned attributeArrayOffset; VertexArray(); ~VertexArray(); VertexArray(VertexArray&& other); VertexArray(const VertexArray&) = delete; VertexArray& operator=(VertexArray&& other); VertexArray& operator=(const VertexArray&) = delete; void bind() override; void unbind() override; void close() override; void addBuffer(VertexBuffer* buffer); }; }; ================================================ FILE: graphics/buffers/vertexBuffer.cpp ================================================ #include "core.h" #include "vertexBuffer.h" #include namespace P3D::Graphics { VertexBuffer::VertexBuffer() : Bindable(), currentSize(0), currentCapacity(0), layout(BufferLayout()) { Log::debug("Created empty vertex buffer"); }; VertexBuffer::VertexBuffer(const BufferLayout& layout, const void* data, size_t sizeInBytes, GLFLAG mode) : Bindable(), currentSize(sizeInBytes), currentCapacity(sizeInBytes), layout(layout) { glGenBuffers(1, &id); bind(); if (sizeInBytes != 0) fill(data, sizeInBytes, mode); } VertexBuffer::VertexBuffer(const BufferLayout& layout, const void* data, size_t sizeInBytes) : VertexBuffer(layout, data, sizeInBytes, GL_STATIC_DRAW) {} VertexBuffer::~VertexBuffer() { close(); Log::warn("Deleted vertex buffer with id (%d)", id); } VertexBuffer::VertexBuffer(VertexBuffer&& other) : Bindable(other.id), currentSize(other.currentSize), currentCapacity(other.currentCapacity), layout(std::move(other.layout)) { // Reset to prevent destruction by rvalue other.id = 0; } VertexBuffer& VertexBuffer::operator=(VertexBuffer&& other) { if (this != &other) { close(); std::swap(id, other.id); std::swap(layout, other.layout); std::swap(currentSize, other.currentSize); std::swap(currentCapacity, other.currentCapacity); } return *this; } void VertexBuffer::fill(const void* data, std::size_t sizeInBytes, GLFLAG mode) { currentSize = sizeInBytes; currentCapacity = sizeInBytes; if (currentSize == 0) return; bind(); glBufferData(GL_ARRAY_BUFFER, sizeInBytes, data, mode); } void VertexBuffer::update(const void* data, std::size_t sizeInBytes, std::size_t offset) { size_t newCapacity = offset + sizeInBytes; bind(); if (newCapacity > currentCapacity) { Log::error("Buffer update exceeds buffer capacity: buffer=%d, size=%d, capacity=%d", id, sizeInBytes + offset, currentCapacity); return; } glBufferSubData(GL_ARRAY_BUFFER, offset, sizeInBytes, data); } size_t VertexBuffer::size() const { return currentSize; } size_t VertexBuffer::capacity() const { return currentCapacity; } void VertexBuffer::bind() { glBindBuffer(GL_ARRAY_BUFFER, id); } void VertexBuffer::unbind() { glBindBuffer(GL_ARRAY_BUFFER, 0); } void VertexBuffer::close() { unbind(); glDeleteBuffers(1, &id); id = 0; layout = BufferLayout(); currentCapacity = 0; currentCapacity = 0; } }; ================================================ FILE: graphics/buffers/vertexBuffer.h ================================================ #pragma once #include "../bindable.h" #include "../renderer.h" #include "bufferLayout.h" namespace P3D::Graphics { class VertexBuffer : public Bindable { private: std::size_t currentSize; std::size_t currentCapacity; public: BufferLayout layout; VertexBuffer(); VertexBuffer(const BufferLayout& layout, const void* data, std::size_t sizeInBytes, GLFLAG mode); VertexBuffer(const BufferLayout& layout, const void* data, std::size_t sizeInBytes); ~VertexBuffer(); VertexBuffer(VertexBuffer&& other); VertexBuffer(const VertexBuffer&) = delete; VertexBuffer& operator=(VertexBuffer&& other); VertexBuffer& operator=(const VertexBuffer&) = delete; void fill(const void* data, std::size_t sizeInBytes, GLFLAG mode); void update(const void* data, std::size_t sizeInBytes, std::size_t offset); std::size_t size() const; std::size_t capacity() const; void bind() override; void unbind() override; void close() override; }; }; ================================================ FILE: graphics/component.natvis ================================================ pos={position}, dim={dimension}, parent=yes, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} pos={position}, dim={dimension}, parent=no, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} size={children.elements.size()}, pos={position}, dim={dimension}, parent=yes, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} size={children.elements.size()}, pos={position}, dim={dimension}, parent=no, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} textured={textured}, pos={position}, dim={dimension}, parent={parent} text={text}, textured={textured}, pos={position}, dim={dimension}, parent={parent} *(Component*)this,nd text textured texture={texture}, pos={position}, dim={dimension}, parent=yes, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} texture=none, pos={position}, dim={dimension}, parent=no, visible={visible}, disabled={disabled}, resizing={resizing}, align={align,en} *(Component*)this,nd texture text={text}, pos={position}, dim={dimension}, parent={parent} *(Component*)this,nd text scale foregroundColor backgroundColor text={label->text}, checked={checked}, pos={position}, dim={dimension}, parent={parent} checked={checked}, pos={position}, dim={dimension}, parent={parent} *(Component*)this,nd label textured value={value,g}, range=({min,g} ... {max,g}), pos={position}, dim={dimension}, parent={parent} *(Component*)this,nd value min max title={title->text}, pos={position}, dim={dimension}, anchor={parent, g} *(Container*)this,nd title minimized anchor size={elements.size()} ================================================ FILE: graphics/core.cpp ================================================ #include "core.h" ================================================ FILE: graphics/core.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/log.h" #ifdef _MSC_VER #define P3D_DEBUGBREAK __debugbreak() #else #define P3D_DEBUGBREAK raise(SIGTRAP) #endif ================================================ FILE: graphics/debug/guiDebug.cpp ================================================ #include "core.h" #include "guiDebug.h" #include "threePhaseBuffer.h" #include #include #include #include #include "../util/log.h" #include namespace P3D::Graphics { void renderQuad() { static unsigned int VAO = 0; static unsigned int VBO; if (VAO == 0) { float vertices[] = { // positions // texture Coords -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, }; // setup plane VAO glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*) 0); } glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } void clearError() { while (glGetError() != GL_NO_ERROR); } bool logCall(const char* func, const char* file, int line) { bool response = true; unsigned int error = glGetError(); while (error != GL_NO_ERROR) { Log::error("[OpenGL error 0x%x] %s:%d at %s", error, file, line, func); error = glGetError(); response = false; } return response; } namespace AppDebug { ThreePhaseBuffer vecBuf(256); ThreePhaseBuffer pointBuf(256); namespace Logging { using namespace Debug; void logVector(Position origin, Vec3 vec, VectorType type) { vecBuf.add(ColoredVector(origin, vec, type)); } void logPoint(Position point, PointType type) { pointBuf.add(ColoredPoint(point, type)); } void logCFrame(CFrame frame, CFrameType type) { switch (type) { case OBJECT_CFRAME: { Vec3 pos = frame.position; Rotation rot = frame.rotation; // buf.add(ColoredVec(frame.position, rot * Vec3(1.0, 0.0, 0.0), 0.0)); // buf.add(ColoredVec(frame.position, rot * Vec3(0.0, 1.0, 0.0), 0.3)); // buf.add(ColoredVec(frame.position, rot * Vec3(0.0, 0.0, 1.0), 0.6)); } break; case INERTIAL_CFRAME: { Vec3 pos = frame.position; Rotation rot = frame.rotation; // buf.add(ColoredVec(frame.position, rot * Vec3(1.0, 0.0, 0.0), 0.1)); // buf.add(ColoredVec(frame.position, rot * Vec3(0.0, 1.0, 0.0), 0.4)); // buf.add(ColoredVec(frame.position, rot * Vec3(0.0, 0.0, 1.0), 0.7)); } break; } } void logShape(const Polyhedron& shape, const GlobalCFrame& location) { for (int i = 0; i < shape.triangleCount; i++) { Triangle t = shape.getTriangle(i); for (int j = 0; j < 3; j++) { Debug::logVector(location.localToGlobal(shape.getVertex(t[j])), location.localToRelative(shape.getVertex(t[(j + 1) % 3]) - shape.getVertex(t[j])), Debug::INFO_VEC); } } } }; void logTickStart() { } void logTickEnd(WorldPrototype* world) { vecBuf.pushWriteBuffer(); pointBuf.pushWriteBuffer(); } void logFrameStart() { } void logFrameEnd() { } void setupDebugHooks() { Log::info("Set up debug hooks!"); Debug::setVectorLogAction(Logging::logVector); Debug::setPointLogAction(Logging::logPoint); Debug::setCFrameLogAction(Logging::logCFrame); Debug::setShapeLogAction(Logging::logShape); // leave physics logging default printf //Debug::setLogAction([](const char* format, std::va_list args) {Log::debug(format, ); }) } /* Returns a copy of the current vec buffer */ AddableBuffer& getVectorBuffer() { return vecBuf.pullOutputBuffer(); } /* Returns a copy of the current point buffer */ AddableBuffer& getPointBuffer() { return pointBuf.pullOutputBuffer(); } } }; ================================================ FILE: graphics/debug/guiDebug.h ================================================ #pragma once #include #include "../util/log.h" #include "threePhaseBuffer.h" #include namespace P3D { class WorldPrototype; }; namespace P3D::Graphics { #define ASSERT(x) if (!(x)) throw std::logic_error("Assert failed") #define glCall(x) {Graphics::clearError(); x; ASSERT(Graphics::logCall(#x, __FILE__, __LINE__));} void clearError(); bool logCall(const char* func, const char* file, int line); namespace AppDebug { struct ColoredVector { Vec3 origin, vector; P3D::Debug::VectorType type; ColoredVector() = default; ColoredVector(Vec3 origin, Vec3 vector, P3D::Debug::VectorType type) : origin(origin), vector(vector), type(type) {} ColoredVector(Position origin, Vec3 vector, P3D::Debug::VectorType type) : origin(castPositionToVec3(origin)), vector(vector), type(type) {} }; struct ColoredPoint { Vec3 point; P3D::Debug::PointType type; ColoredPoint() = default; ColoredPoint(Vec3 point, P3D::Debug::PointType type) : point(point), type(type) {} ColoredPoint(Position point, P3D::Debug::PointType type) : point(castPositionToVec3(point)), type(type) {} }; void setupDebugHooks(); void logTickEnd(WorldPrototype* world); void logFrameStart(); void logFrameEnd(); void renderQuad(); AddableBuffer& getVectorBuffer(); AddableBuffer& getPointBuffer(); } }; ================================================ FILE: graphics/debug/profilerUI.cpp ================================================ #include "core.h" #include "profilerUI.h" #include "../application/picker/selection.h" #include "../application/picker/tools/selectionTool.h" #include "../application/extendedPart.h" #include #include #include "texture.h" #include "path/path.h" #include "gui/gui.h" #include "font.h" #include #include namespace P3D::Graphics { #pragma region PieChart //! PieChart #define MAX_ANGLE 0.1f const Color pieColors[30] { Color(0.2f,0.2f,1), Color(1,0.5f,0), Color(1,1,0), Color(1,0,1), Color(0,1,0), Color(0,1,1), Color(1,1,1), Color(1,0,0), Color(0.5f,0,0), Color(0,0.5f,0), Color(0,0,0.5f), Color(0.5f,0.5f,0), Color(0.5f,0,0.5f), Color(0,0.5f,0.5f), Color(0.5f,0.5f,0.5f), }; void PieChart::renderPie() const { Vec2f cursorPosition = Vec2f(pieSize, 0); float oldAngle = 0.0f; float newAngle = 0.0f; float totalWeight = getTotal(); if (totalWeight == 0.0) return; for (DataPoint dataPoint : parts) { float angle = float(PI * 2 * dataPoint.weight / totalWeight); int subdivisions = int(angle / MAX_ANGLE + 1); newAngle += angle; Path::circleSegmentFilled(piePosition, pieSize, oldAngle, newAngle, dataPoint.color, subdivisions); oldAngle = newAngle; } } void PieChart::renderText(Graphics::Font* font) const { // Title Vec2f titlePosition = piePosition + Vec2f(pieSize * 1.3f, pieSize * 1.1f); Path::text(font, title, 0.001, Vec2(titlePosition.x, titlePosition.y), Vec4f(1, 1, 1, 1)); Vec2f textPosition = Vec2(piePosition + Vec2f(pieSize * 1.3f, pieSize * 1.1f - 0.05f)); float totalWeight = getTotal(); Path::text(font, totalValue, 0.0006, textPosition + Vec2(0.50, 0.035), Color(1, 1, 1, 1)); for (int i = 0; i < parts.size(); i++) { const DataPoint& p = parts[i]; Vec2 linePosition = textPosition + Vec2(0, -i * 0.035); Path::text(font, p.label, 0.0006, linePosition, p.color); std::stringstream percent; percent.precision(4); percent << p.weight / totalWeight * 100; percent << "%"; Path::text(font, percent.str(), 0.0006, linePosition + Vec2(0.35, 0), p.color); Path::text(font, p.value, 0.0006, linePosition + Vec2(0.50, 0), p.color); } } void PieChart::add(DataPoint& dataPoint) { this->parts.push_back(dataPoint); } float PieChart::getTotal() const { float totalWeight = 0; for (DataPoint p : parts) totalWeight += p.weight; return totalWeight; } #pragma endregion #pragma region BarChart //! BarChart void BarChart::render() { float titleHeight = 0.045f; float marginLeft = 0.0f; float marginBottom = this->dimension.y * 0.045f; float marginTop = titleHeight + 0.05f; float max = getMaxWeight(); Vec2f drawingPosition = position + Vec2f(marginLeft, marginBottom); Vec2f drawingSize = this->dimension - Vec2f(marginLeft, marginBottom + marginTop); float categoryWidth = drawingSize.x / data.w; float barWidth = drawingSize.x / ((data.h + 0.5f) * data.w); for (int cl = 0; cl < data.h; cl++) { const BarChartClassInfo& info = classes[cl]; for (int i = 0; i < data.w; i++) { const WeightValue& dataPoint = data(cl, i); float height = drawingSize.y * dataPoint.weight / max; Vec2f bottomLeft = drawingPosition + Vec2f(categoryWidth * i + barWidth * cl, 0); Path::rectFilled(bottomLeft, Vec2f(barWidth, height), 0.0f, info.color); } } Path::text(GUI::font, title, 0.001, position + Vec2f(0, this->dimension.y - titleHeight), Colors::WHITE); for (int cl = 0; cl < data.h; cl++) { const BarChartClassInfo& info = classes[cl]; for (int i = 0; i < data.w; i++) { const WeightValue& dataPoint = data(cl, i); Vec2f bottomLeft = drawingPosition + Vec2f(categoryWidth * i + barWidth * cl, 0); float height = drawingSize.y * dataPoint.weight / max; Vec2f topTextPosition = bottomLeft + Vec2(0, height + drawingSize.y * 0.02); //topTextPosition.x *= GUI::screen->dimension.x / GUI::screen->dimension.y; Path::text(GUI::font, dataPoint.value, 0.0005, topTextPosition, info.color); } } for (int i = 0; i < data.w; i++) { Vec2f botLeft = position + Vec2f(marginLeft, 0) + Vec2f(categoryWidth * i, 0); Path::text(GUI::font, labels[i], 0.0005, botLeft, Colors::WHITE); } for (int cl = 0; cl < data.h; cl++) Path::text(GUI::font, classes[cl].name, 0.0007, drawingPosition + Vec2f(this->dimension.x - 0.3f, drawingSize.y - 0.035f * cl), classes[cl].color); } float BarChart::getMaxWeight() const { float best = 0; for (const WeightValue& wv : data) { if (wv.weight > best) best = wv.weight; } return best; } #pragma endregion #pragma region Tree //! Tree static void recursiveRenderTree(const P3D::TreeTrunk& curTrunk, int curTrunkSize, const Color& treeColor, Vec2f origin, float allottedWidth, float maxCost) { for(int i = 0; i < curTrunkSize; i++) { const P3D::TreeNodeRef& subNode = curTrunk.subNodes[i]; Vec2f nextStep = origin + Vec2f(-allottedWidth / 2 + allottedWidth * ((curTrunkSize != 1) ? (float(i) / (curTrunkSize - 1)) : 0.5f), -0.1f); float colorDarkning = pow(1.0f * P3D::computeCost(curTrunk.getBoundsOfSubNode(i)) / maxCost, 0.25f); //Path::bezierVertical(origin, nextStep, 1.0f, Vec4f(treeColor * colorDarkning, 1.0f), 15); Path::line(origin, nextStep, Color(treeColor.r * colorDarkning, treeColor.g * colorDarkning, treeColor.b * colorDarkning), 0.5f); if(subNode.isTrunkNode()) { recursiveRenderTree(subNode.asTrunk(), subNode.getTrunkSize(), treeColor, nextStep, allottedWidth / curTrunkSize, maxCost); if(subNode.isGroupHead()) { Path::circleFilled(nextStep, 0.006f, Colors::RED, 8); } } else { P3D::Application::ExtendedPart* extPart = reinterpret_cast(subNode.asObject()); if(P3D::Application::SelectionTool::selection.contains(extPart->entity)) { Path::circleFilled(nextStep, 0.012f, Colors::YELLOW, 8); } } } } void renderTreeStructure(const P3D::BoundsTree& tree, const Color& treeColor, Vec2f origin, float allottedWidth) { if(tree.isEmpty()) { return; } auto base = tree.getPrototype().getBaseTrunk(); float maxCost = P3D::computeCost(base.first.getBoundsOfSubNode(0)); for(int i = 1; i < base.second; i++) { float subCost = P3D::computeCost(base.first.getBoundsOfSubNode(i)); if(subCost > maxCost) maxCost = subCost; } recursiveRenderTree(base.first, base.second, treeColor, origin, allottedWidth, maxCost); } #pragma endregion #pragma region SlidingDataChart //! SlidingDataChart void SlidingChartDataSetInfo::add(float value) { if (data.size() == 0) { deviation = 1.0f; mean = value; data.add(value); return; } data.add(value); float variance = deviation * deviation; float newVariance = variance; float newMean = mean; if (data.size() == size) { float s = size; float s1 = s - 1.0f; newMean = mean + (value - data.front()) / s; float diffMean = newMean - mean; newVariance = variance + diffMean * s / s1 * (2.0f * (data.front() - mean) + s1 * diffMean); } else { float s = data.size(); float s1 = s - 1.0f; float s2 = s - 2.0f; newMean = (mean * s1 + value) / s; newVariance = (s2 * variance + (value - newMean) * (value - mean)) / s1; } mean = newMean; deviation = sqrt(newVariance); } SlidingChart::SlidingChart(const std::string& title, const Vec2& position, const Vec2& dimension) : title(title), position(position), dimension(dimension) {} void SlidingChart::add(const SlidingChartDataSetInfo& dataSet) { dataSets[dataSet.title] = dataSet; } void SlidingChart::add(const std::string& title, float value) { return dataSets.at(title).add(value); } SlidingChartDataSetInfo SlidingChart::get(const std::string& title) { return dataSets.at(title); } void SlidingChart::render() { float axisOffset = 0.03; Path::rect(position, dimension, 0.0f, Vec4f(0.4f, 0.4f, 0.4f, 1.0f)); Path::line(position + Vec2f(-axisOffset, -dimension.y), position + Vec2f(dimension.x + axisOffset, -dimension.y), Colors::WHITE, 2.0f); Path::line(position + Vec2f(0, axisOffset), position + Vec2f(0, -dimension.y - axisOffset), Colors::WHITE, 2.0f); for (auto dataSetIterator : dataSets) { SlidingChartDataSetInfo& dataSet = dataSetIterator.second; float usedDeviaton = fmax(dataSet.deviation, 0.1 * dataSet.mean); float stepX = dimension.x / dataSet.size; float stepY = dimension.y / usedDeviaton / 6.82f; float startY = dataSet.mean - usedDeviaton; Vec2f bottomLeft = position - Vec2f(0.0f, dimension.y); int i = 0; for (float value : dataSet.data) { Vec2f point = bottomLeft + Vec2f(i * stepX, (value - startY) * stepY); Path::lineTo(point); i++; } Path::stroke(dataSet.color, dataSet.lineSize); float lastValue = dataSet.data.front(); float lastY = (lastValue - startY) * stepY; Path::text(GUI::font, std::to_string(lastValue), 0.0008f, Vec2f((bottomLeft.x + dimension.x) * 1.01f, bottomLeft.y + lastY), dataSet.color, Path::TextPivotVC); } Vec2f titleSize = GUI::font->size(title, 0.001f); Path::text(GUI::font, title, 0.001f, Vec2f(position.x + dimension.x / 2.0f, position.y + axisOffset), Colors::WHITE, Path::TextPivotHC); } Vec2 SlidingChart::resize() { return Vec2(dimension); } #pragma endregion }; ================================================ FILE: graphics/debug/profilerUI.h ================================================ #pragma once #include "../gui/color.h" #include #include namespace P3D { template struct BoundsTree; class Part; } namespace P3D::Graphics { class Font; extern const Color pieColors[30]; struct WeightValue { float weight; std::string value; }; struct DataPoint { float weight; std::string value; Color color; const char* label; DataPoint() : color(), weight(0) {} DataPoint(float weight, std::string value, Vec3f color, const char* label) : weight(weight), value(value), color(color), label(label) {} }; struct PieChart { const char* title; Vec2f piePosition; float pieSize; std::vector parts; std::string totalValue; inline PieChart(const char* title, std::string totalValue, Vec2f piePosition, float pieSize) : title(title), totalValue(totalValue), piePosition(piePosition), pieSize(pieSize) {} void renderPie() const; void renderText(Graphics::Font* font) const; void add(DataPoint& p); float getTotal() const; }; struct BarChartClassInfo { std::string name; Color color; }; struct BarChart { Vec2f position; Vec2f dimension; const char* title; const char** labels; BarChartClassInfo* classes; LargeMatrix data; std::string totalValue; inline BarChart(const char* title, std::string totalValue, const char** labels, BarChartClassInfo* classes, Vec2f chartPosition, Vec2f chartSize, int classCount, int barCount) : title(title), totalValue(totalValue), classes(classes), labels(labels), data(barCount, classCount), position(chartPosition), dimension(chartSize) {} void render(); float getMaxWeight() const; }; struct SlidingChartDataSetInfo { int size; std::string title; CircularBuffer data; Color color; float lineSize; float mean; float deviation; SlidingChartDataSetInfo() : title(""), size(0), mean(0), deviation(1), color(Colors::ALPHA), lineSize(0), data() {} SlidingChartDataSetInfo(const std::string& title, int size, Color color = Colors::ACCENT, float lineSize = 1.0f) : title(title), size(size), mean(0), deviation(1), color(color), lineSize(lineSize), data(size) {} void add(float value); }; struct SlidingChart { Vec2 position; Vec2 dimension; std::string title; std::map dataSets; SlidingChart(const std::string& title, const Vec2& position, const Vec2& dimension); void add(const std::string& title, float value); void add(const SlidingChartDataSetInfo& dataSet); SlidingChartDataSetInfo get(const std::string& title); void render(); Vec2 resize(); }; void renderTreeStructure(const P3D::BoundsTree& tree, const Color& treeColor, Vec2f origin, float allottedWidth); }; ================================================ FILE: graphics/debug/threePhaseBuffer.h ================================================ #pragma once #include #include namespace P3D { template class ThreePhaseBuffer { AddableBuffer writeBuf; BufferWithCapacity readyBuf; size_t readySize = 0; bool newDataAvailable = false; public: AddableBuffer outputBuf; std::mutex swapLock; ThreePhaseBuffer(size_t initialCapacity) : writeBuf(initialCapacity), readyBuf(initialCapacity), outputBuf(initialCapacity) {} ~ThreePhaseBuffer() {} ThreePhaseBuffer(const ThreePhaseBuffer&) = delete; ThreePhaseBuffer(const ThreePhaseBuffer&&) = delete; ThreePhaseBuffer& operator=(const ThreePhaseBuffer&) = delete; ThreePhaseBuffer& operator=(const ThreePhaseBuffer&&) = delete; inline void add(const T& obj) { writeBuf.add(obj); } inline void pushWriteBuffer() { std::lock_guard lg(swapLock); readySize = writeBuf.size; std::swap(static_cast&>(writeBuf), readyBuf); writeBuf.clear(); newDataAvailable = true; } inline AddableBuffer& pullOutputBuffer() { std::lock_guard lg(swapLock); if(newDataAvailable) { std::swap(static_cast&>(readyBuf), static_cast&>(outputBuf)); newDataAvailable = false; } outputBuf.size = readySize; return outputBuf; } }; }; ================================================ FILE: graphics/debug/visualDebug.cpp ================================================ #include "core.h" #include "visualDebug.h" #include "mesh/vectorMesh.h" #include "mesh/pointMesh.h" #include "font.h" #include "batch/commandBatch.h" #include "batch/guiBatch.h" #include "path/path.h" #include "gui/gui.h" #include "threePhaseBuffer.h" #include "batch/commandBatch.h" #include namespace P3D::Graphics { const char* const graphicsDebugLabels[] { "Update", "Skybox", "Vectors", "Lighting", "Wait For Lock", "Physicals", "Origin", "Picker", "Profiler", "Finalize", "Other" }; BreakdownAverageProfiler graphicsMeasure(graphicsDebugLabels, 60); namespace VisualDebug { std::map vectorDebugEnabled { { P3D::Debug::INFO_VEC , false }, { P3D::Debug::VELOCITY , false }, { P3D::Debug::ACCELERATION , false }, { P3D::Debug::FORCE , false }, { P3D::Debug::ANGULAR_IMPULSE , false }, { P3D::Debug::POSITION , false }, { P3D::Debug::MOMENT , false }, { P3D::Debug::IMPULSE , false }, { P3D::Debug::ANGULAR_VELOCITY, false } }; std::map pointDebugEnabled { { P3D::Debug::INFO_POINT , false }, { P3D::Debug::CENTER_OF_MASS, false }, { P3D::Debug::INTERSECTION , false }, }; struct PointColorPair { Vec3f color1; Vec3f color2; }; std::map vectorColors { { P3D::Debug::INFO_VEC , Vec3f(0, 1, 0) }, { P3D::Debug::VELOCITY , Vec3f(0, 0, 1) }, { P3D::Debug::ACCELERATION , Vec3f(0, 1, 1) }, { P3D::Debug::FORCE , Vec3f(1, 0, 0) }, { P3D::Debug::POSITION , Vec3f(1, 1, 0) }, { P3D::Debug::MOMENT , Vec3f(1, 0, 1) }, { P3D::Debug::IMPULSE , Vec3f(0.5, 0.7, 1) }, { P3D::Debug::ANGULAR_VELOCITY, Vec3f(0.75, 0.75, 0.75) }, { P3D::Debug::ANGULAR_IMPULSE , Vec3f(0.8, 0.1, 0.4) } }; std::map pointColors { { P3D::Debug::INFO_POINT , PointColorPair { Vec3f(1.0f, 0.5f, 0.0f), Vec3f(1.0f, 0.2f, 0.0f) }}, { P3D::Debug::CENTER_OF_MASS, PointColorPair { Vec3f(1.0f, 1.0f, 0.0f), Vec3f(0.0f, 0.0f, 0.0f) }}, { P3D::Debug::INTERSECTION , PointColorPair { Vec3f(0.0f, 0.0f, 1.0f), Vec3f(0.0f, 1.0f, 0.0f) }}, }; AddableBuffer visibleVectors(900); AddableBuffer visiblePoints(1000); SphereColissionRenderMode colissionSpheresMode; int colTreeRenderMode = -1; // -2 for selected, -1 for none, n >= 0 for layer tree n bool renderPiesEnabled = false; int fieldIndex = 0; void toggleVectorType(P3D::Debug::VectorType type) { vectorDebugEnabled[type] = !vectorDebugEnabled[type]; } void togglePointType(P3D::Debug::PointType type) { pointDebugEnabled[type] = !pointDebugEnabled[type]; } std::string toString(std::chrono::nanoseconds time) { std::stringstream string; string.precision(4); string << time.count() * 0.000001f; string << "ms"; return string.str(); } size_t getTheoreticalNumberOfIntersections(size_t objectCount) { return (objectCount - 1) * objectCount / 2; } void updateVectorMesh(Graphics::VectorMesh* vectorMesh, AppDebug::ColoredVector* data, size_t size) { visibleVectors.clear(); for (size_t i = 0; i < size; i++) { const AppDebug::ColoredVector& vector = data[i]; if (vectorDebugEnabled[vector.type]) { visibleVectors.add(vector.origin.x); visibleVectors.add(vector.origin.y); visibleVectors.add(vector.origin.z); visibleVectors.add(vector.vector.x); visibleVectors.add(vector.vector.y); visibleVectors.add(vector.vector.z); visibleVectors.add(vectorColors[vector.type].x); visibleVectors.add(vectorColors[vector.type].y); visibleVectors.add(vectorColors[vector.type].z); } } vectorMesh->update(visibleVectors.data, visibleVectors.size / 9); } void updatePointMesh(Graphics::PointMesh* pointMesh, AppDebug::ColoredPoint* data, size_t size) { visiblePoints.clear(); for (size_t i = 0; i < size; i++) { const AppDebug::ColoredPoint& point = data[i]; if (pointDebugEnabled[point.type]) { Vec3f color1 = pointColors[point.type].color1; Vec3f color2 = pointColors[point.type].color2; visiblePoints.add(point.point.x); visiblePoints.add(point.point.y); visiblePoints.add(point.point.z); visiblePoints.add(0.025); visiblePoints.add(color1.x); visiblePoints.add(color1.y); visiblePoints.add(color1.z); visiblePoints.add(color2.x); visiblePoints.add(color2.y); visiblePoints.add(color2.z); } } pointMesh->update(visiblePoints.data, visiblePoints.size / 10); } void addDebugField(Vec2 dimension, Graphics::Font* font, const char* varName, std::string value, const char* unit) { std::stringstream string; string.precision(4); string << varName << ": " << value << unit; Path::text(font, string.str(), 0.001, Vec2(-dimension.x / dimension.y * 0.99, (1 - fieldIndex * 0.05) * 0.95), Color(1, 1, 1, 1)); fieldIndex++; } } }; ================================================ FILE: graphics/debug/visualDebug.h ================================================ #pragma once #include "profilerUI.h" #include "guiDebug.h" #include namespace P3D::Graphics { class PointMesh; class VectorMesh; class Font; enum GraphicsProcess { UPDATE, SKYBOX, VECTORS, LIGHTING, WAIT_FOR_LOCK, PHYSICALS, ORIGIN, PICKER, PROFILER, FINALIZE, OTHER, COUNT }; extern const char* const graphicsDebugLabels[]; extern BreakdownAverageProfiler graphicsMeasure; namespace VisualDebug { enum class SphereColissionRenderMode : int { NONE, SELECTED, ALL }; extern bool renderPiesEnabled; extern AddableBuffer visibleVectors; extern std::map vectorDebugEnabled; extern std::map pointDebugEnabled; extern SphereColissionRenderMode colissionSpheresMode; extern int colTreeRenderMode; extern int fieldIndex; void toggleVectorType(P3D::Debug::VectorType type); void togglePointType(P3D::Debug::PointType type); void updateVectorMesh(Graphics::VectorMesh* vectorMesh, AppDebug::ColoredVector* data, size_t size); void updatePointMesh(Graphics::PointMesh* pointMesh, AppDebug::ColoredPoint* data, size_t size); void addDebugField(Vec2 dimension, Graphics::Font* font, const char* varName, std::string value, const char* unit); size_t getTheoreticalNumberOfIntersections(size_t objCount); std::string toString(std::chrono::nanoseconds t); template void addDebugField(Vec2 dimension, Graphics::Font* font, const char* varName, T value, const char* unit) { addDebugField(dimension, font, varName, std::to_string(value), unit); } template PieChart toPieChart(BreakdownAverageProfiler& profiler, const char* title, Vec2f piePosition, float pieSize) { auto results = profiler.history.avg(); auto averageTotalTime = results.sum(); PieChart chart(title, toString(averageTotalTime), piePosition, pieSize); for (size_t i = 0; i < profiler.size(); i++) { DataPoint p = DataPoint(static_cast(results[i].count()), toString(results[i]), pieColors[i], profiler.labels[i]); chart.add(p); } return chart; } template PieChart toPieChart(HistoricTally& tally, const char* title, Vec2f piePosition, float pieSize) { int sum = 0; for (auto entry : tally.history) sum += (int) entry.sum(); auto results = tally.history.avg(); Unit avgTotal = (tally.history.size() != 0) ? (sum / tally.history.size()) : 0; PieChart chart(title, std::to_string(avgTotal), piePosition, pieSize); for (size_t i = 0; i < tally.size(); i++) { DataPoint p = DataPoint(static_cast(results[i]), std::to_string(results[i]), pieColors[i], tally.labels[i]); chart.add(p); } return chart; } } }; ================================================ FILE: graphics/ecs/components.h ================================================ #pragma once #include #include "../graphics/texture.h" #include "../graphics/gui/color.h" #include "Physics3D/datastructures/smartPointers.h" #include "Physics3D/math/mathUtil.h" namespace P3D::Graphics::Comp { // The mesh of an entity, as it is rendered struct Mesh : public RC { typedef int Flags; inline static Flags Flags_None = 0 << 0; inline static Flags Flags_Normal = 1 << 0; inline static Flags Flags_UV = 1 << 1; inline static Flags Flags_Tangent = 1 << 2; inline static Flags Flags_Bitangent = 1 << 3; // The mesh id in the mesh registry std::size_t id; // Flags indicated the type of data this mesh contains Flags flags; // Whether the mesh is visible bool visible; Mesh() : id(-1) , flags(Flags_None) , visible(false) {} Mesh(std::size_t id, Flags flags = Flags_None) : id(id) , flags(flags) , visible(true) {} bool valid() const { return id != -1; } }; struct Material : public RC { static constexpr std::size_t MAP_COUNT = 8; private: SRef maps[MAP_COUNT]; public: typedef int Map; inline static Map Map_None = 0 << 0; inline static Map Map_Albedo = 1 << 0; inline static Map Map_Normal = 1 << 1; inline static Map Map_Metalness = 1 << 2; inline static Map Map_Roughness = 1 << 3; inline static Map Map_AO = 1 << 4; inline static Map Map_Gloss = 1 << 5; inline static Map Map_Specular = 1 << 6; inline static Map Map_Displacement = 1 << 7; Color albedo; float metalness; float roughness; float ao; constexpr Material(Color albedo = Color(1), float metalness = 0.0f, float roughness = 1.0f, float ao = 1.0f) : albedo(albedo) , metalness(metalness) , roughness(roughness) , ao(ao) , maps{} {} ~Material() override = default; void set(Map map, SRef texture) { maps[ctz(map)] = texture; } void reset(Map map) { if (map == Map_None) return; maps[ctz(map)] = nullptr; } [[nodiscard]] SRef get(Map map) const { if (map == Map_None) return nullptr; return maps[ctz(map)]; } [[nodiscard]] SRef get(std::size_t index) const { if (index >= MAP_COUNT) return nullptr; return maps[index]; } /*[[nodiscard]] std::map getTextures() const { std::map result; for (std::size_t index = 0; index < MAP_COUNT; index++) if (maps[index] != nullptr) result.emplace(1 << index, maps[index]); return result; }*/ [[nodiscard]] int getTextureCount() const { return has(Map_Albedo) + has(Map_Normal) + has(Map_Metalness) + has(Map_Roughness) + has(Map_AO) + has(Map_Gloss) + has(Map_Specular) + has(Map_Displacement); } [[nodiscard]] bool has(Map map) const { if (map == Map_None) return false; return maps[ctz(map)] != nullptr; } [[nodiscard]] Map getMaps() const { return Map_None | (has(Map_Albedo) ? Map_Albedo : Map_None) | (has(Map_Normal) ? Map_Normal : Map_None) | ( has(Map_Metalness) ? Map_Metalness : Map_None) | (has(Map_Roughness) ? Map_Roughness : Map_None) | (has(Map_AO) ? Map_AO : Map_None) | ( has(Map_Gloss) ? Map_Gloss : Map_None) | (has(Map_Specular) ? Map_Specular : Map_None) | (has(Map_Displacement) ? Map_Displacement : Map_None); } }; } ================================================ FILE: graphics/ecs/materials.h ================================================ #pragma once #include "components.h" namespace P3D::Graphics { namespace Materials { inline Comp::Material metal(const Color& color) { return Comp::Material(color, 1.0, 0.2, 1.0); } inline Comp::Material metalRough(const Color& color) { return Comp::Material(color, 1.0, 0.65, 1.0); } inline Comp::Material metalDiffuse(const Color& color) { return Comp::Material(color, 0.75, 0.35, 1.0); } inline Comp::Material metalNoReflection(const Color& color) { return Comp::Material(color, 1.0, 0.0, 1.0); } inline Comp::Material plastic(const Color& color) { return Comp::Material(color, 0.0, 0.35, 1.0); } inline Comp::Material plasticDiffuse(const Color& color) { return Comp::Material(color, 0.0, 0.8, 0.8); } inline Comp::Material plasticDark(const Color& color) { return Comp::Material(color, 0.0, 0.0, 0.0); } inline Comp::Material plasticNoReflection(const Color& color) { return Comp::Material(color, 0.0, 0.0, 1.0); } inline static Comp::Material COPPER = metal(Color::get<0xB24819>()); inline static Comp::Material GOLD = metal(Color::get<0xFFDE00>()); inline static Comp::Material SILVER = metal(Color::get<0xD9D9D9>()); inline static Comp::Material IRON = metalDiffuse(Color::get<0xAAAAAA>()); inline static Comp::Material WHITE = plastic(Color::get<0xFFFFFF>()); inline static Comp::Material RED = plasticDiffuse(Color::get<0xFF0000>()); inline static Comp::Material GREEN = plasticDiffuse(Color::get<0x00FF00>()); inline static Comp::Material BLUE = plasticDiffuse(Color::get<0x0000FF>()); } }; ================================================ FILE: graphics/extendedTriangleMesh.cpp ================================================ #include "core.h" #include "extendedTriangleMesh.h" #include namespace P3D::Graphics { // Todo move to TriangleMesh ExtendedTriangleMesh ExtendedTriangleMesh::generateSmoothNormalsShape(const TriangleMesh& underlyingMesh) { Vec3f* normalBuffer = new Vec3f[underlyingMesh.vertexCount]; underlyingMesh.computeNormals(normalBuffer); ExtendedTriangleMesh result(underlyingMesh); result.setNormalBuffer(SRef(normalBuffer)); return result; } // Todo move to TriangleMesh ExtendedTriangleMesh ExtendedTriangleMesh::generateSplitNormalsShape(const TriangleMesh& underlyingMesh) { Vec3f* newVertices = new Vec3f[underlyingMesh.triangleCount * 3]; Vec3f* newNormals = new Vec3f[underlyingMesh.triangleCount * 3]; Triangle* newTriangles = new Triangle[underlyingMesh.triangleCount]; for (int i = 0; i < underlyingMesh.triangleCount; i++) { Triangle t = underlyingMesh.getTriangle(i); Vec3f a = underlyingMesh.getVertex(t.firstIndex); Vec3f b = underlyingMesh.getVertex(t.secondIndex); Vec3f c = underlyingMesh.getVertex(t.thirdIndex); Vec3f normal = normalize(underlyingMesh.getNormalVecOfTriangle(t)); newVertices[i * 3] = a; newVertices[i * 3 + 1] = b; newVertices[i * 3 + 2] = c; newNormals[i * 3] = normal; newNormals[i * 3 + 1] = normal; newNormals[i * 3 + 2] = normal; newTriangles[i] = Triangle{i * 3, i * 3 + 1, i * 3 + 2}; } ExtendedTriangleMesh result(TriangleMesh(underlyingMesh.triangleCount * 3, underlyingMesh.triangleCount, newVertices, newTriangles)); result.setNormalBuffer(SRef(newNormals)); return result; } } ================================================ FILE: graphics/extendedTriangleMesh.h ================================================ #pragma once #include #include #include #include "ecs/components.h" class Polyhedron; namespace P3D::Graphics { struct ExtendedTriangleMesh : public TriangleMesh { SRef normals; SRef uvs; SRef tangents; SRef bitangents; ExtendedTriangleMesh() : TriangleMesh() , normals(nullptr) , uvs(nullptr) , tangents(nullptr) , bitangents(nullptr) {} ExtendedTriangleMesh(const Vec3f* vertices, int vertexCount, const Triangle* triangles, int triangleCount) : TriangleMesh(vertexCount, triangleCount, vertices, triangles) { } explicit ExtendedTriangleMesh(const TriangleMesh& shape) : TriangleMesh(shape) {} void setNormalBuffer(const SRef& normals) { this->normals = normals; } void setUVBuffer(const SRef& uvs) { this->uvs = uvs; } void setTangentBuffer(const SRef& tangents) { this->tangents = tangents; } void setBitangentBuffer(const SRef& bitangents) { this->bitangents = bitangents; } Comp::Mesh::Flags getFlags() const { using namespace Comp; return Mesh::Flags_None | (normals != nullptr ? Mesh::Flags_Normal : Mesh::Flags_None) | (uvs != nullptr ? Mesh::Flags_UV : Mesh::Flags_None) | (tangents != nullptr ? Mesh::Flags_Tangent : Mesh::Flags_None) | (bitangents != nullptr ? Mesh::Flags_Bitangent : Mesh::Flags_None); } static ExtendedTriangleMesh generateSmoothNormalsShape(const TriangleMesh& underlyingMesh); static ExtendedTriangleMesh generateSplitNormalsShape(const TriangleMesh& underlyingMesh); }; }; ================================================ FILE: graphics/font.cpp ================================================ #include "core.h" #include "font.h" #include #include #include "gui/gui.h" #include "mesh/primitive.h" #include "path/path.h" #include namespace P3D::Graphics { #pragma region Character Character::Character() : id(0), x(0), y(0), width(0), height(0), bx(0), by(0), advance(0) { }; Character::Character(unsigned int id, int x, int y, int width, int height, int bx, int by, unsigned int advance) : id(id), x(x), y(y), width(width), height(height), bx(bx), by(by), advance(advance) { }; #pragma endregion #pragma region Font Font::Font() { } Font::Font(std::string font) { FT_Library library; FT_Face face; FT_Error error; Log::subject s(font); // Init error = FT_Init_FreeType(&library); if (error) { Log::error("Error loading freetype library"); return; } error = FT_New_Face(library, font.c_str(), 0, &face); if (error) { Log::error("FREETYTPE: Failed to read font (%s)", font.c_str()); if (error == FT_Err_Unknown_File_Format) Log::error("FREETYTPE: Font format not supported", font.c_str()); return; } FT_Set_Pixel_Sizes(face, 0, 48); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Calculate atlas dimension int maxCharacterHeight = (face->size->metrics.height >> 6); int maxDimension = maxCharacterHeight * ceil(sqrt(CHARACTER_COUNT)); int atlasDimension = 1; // Resize atlas to maxDimension with powers of 2 while (atlasDimension < maxDimension) atlasDimension <<= 1; // Allocate memory int penX = 0; int penY = 0; char* pixels = (char*) calloc(atlasDimension * atlasDimension * 4, 1); // Render glyphs to atlas for (unsigned char character = 0; character < CHARACTER_COUNT; character++) { if (FT_Load_Char(face, character, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT)) { Log::error("FREETYTPE: Failed to load Glyph"); continue; } FT_Bitmap* bitmap = &face->glyph->bitmap; if (penX + static_cast(bitmap->width) >= atlasDimension) { penX = 0; penY += maxCharacterHeight; } for (int row = 0; row < static_cast(bitmap->rows); row++) { for (int col = 0; col < static_cast(bitmap->width); col++) { int x = penX + col; int y = penY + row; pixels[(x + y * atlasDimension) * 4 + 0] = 0xff; pixels[(x + y * atlasDimension) * 4 + 1] = 0xff; pixels[(x + y * atlasDimension) * 4 + 2] = 0xff; pixels[(x + y * atlasDimension) * 4 + 3] = bitmap->buffer[col + row * bitmap->pitch]; } } characters[character] = Character( character, penX, penY, bitmap->width, bitmap->rows + 1, face->glyph->bitmap_left, face->glyph->bitmap_top, face->glyph->advance.x ); penX += maxCharacterHeight; } FT_Done_Face(face); FT_Done_FreeType(library); atlas = std::make_shared(atlasDimension, atlasDimension, pixels, GL_RGBA); } void Font::close() { // TODO remove } Vec2f Font::size(const std::string& text, double fontSize) { std::string::const_iterator iterator; float width = 0.0f; float height = 0.0f; for (iterator = text.begin(); iterator != text.end(); iterator++) { Character& character = characters[*iterator]; float advance = character.advance >> 6; if (iterator == text.begin()) width += (advance - character.bearing.x) * fontSize; else if (iterator == text.end() - 1) width += (character.bearing.x + character.size.x) * fontSize; else width += advance * fontSize; height = fmax(character.size.y * fontSize, height); } return Vec2f(width, height); } Character& Font::getCharacter(unsigned int id) { if (id >= CHARACTER_COUNT) return characters[32]; return characters[id]; } unsigned int Font::getAtlasID() const { return atlas->getID(); } unsigned int Font::getAtlasWidth() const { return atlas->getWidth(); } unsigned int Font::getAtlasHeight() const { return atlas->getHeight(); } SRef Font::getAtlas() { return atlas; } #pragma endregion }; ================================================ FILE: graphics/font.h ================================================ #pragma once #include #include FT_FREETYPE_H #include "texture.h" #include #define CHARACTER_COUNT 128 namespace P3D::Graphics { struct Character { union { struct { int x; int y; }; Vec2i origin; }; union { struct { int width; int height; }; Vec2i size; }; union { struct { int bx; int by; }; Vec2i bearing; }; unsigned int advance; unsigned int id; Character(); Character(unsigned int id, int x, int y, int width, int height, int bx, int by, unsigned int advance); }; class Font { private: SRef atlas; Character characters[CHARACTER_COUNT]; public: Font(); Font(std::string font); void close(); Vec2f size(const std::string& text, double scale); SRef getAtlas(); Character& getCharacter(unsigned int id); unsigned int getAtlasID() const; unsigned int getAtlasWidth() const; unsigned int getAtlasHeight() const; }; }; ================================================ FILE: graphics/glfwUtils.cpp ================================================ #include "core.h" #include "glfwUtils.h" #include //#define STB_IMAGE_IMPLEMENTATION #include #include namespace P3D::Graphics { namespace GLFW { namespace Cursor { int ARROW = GLFW_ARROW_CURSOR; int IBEAM = GLFW_IBEAM_CURSOR; int CROSSHAIR = GLFW_CROSSHAIR_CURSOR; int HAND = GLFW_HAND_CURSOR; int HRESIZE = GLFW_HRESIZE_CURSOR; int VRESIZE = GLFW_VRESIZE_CURSOR; } GLFWwindow* currentContext = nullptr; GLFWmonitor* currentMonitor = nullptr; int windowPosition[2] { 0, 0 }; int windowSize[2] { 0, 0 }; GLFWcursor* currentCursor; int currentCursorType; bool init() { return glfwInit(); } void terminate() { glfwTerminate(); } void setCurrentContext(GLFWwindow* context) { currentContext = context; glfwMakeContextCurrent(currentContext); currentMonitor = glfwGetPrimaryMonitor(); glfwGetWindowPos(currentContext, &windowPosition[0], &windowPosition[1]); glfwGetWindowSize(currentContext, &windowSize[0], &windowSize[1]); } GLFWwindow* createContext(int width, int height, const char* title) { GLFWwindow* context = glfwCreateWindow(width, height, title, nullptr, nullptr); return context; } bool validContext(GLFWwindow* context) { return context != nullptr; } GLFWwindow* getCurrentContext() { return currentContext; } bool isFullScreen() { return glfwGetWindowMonitor(currentContext) != nullptr; } void setFullScreen(bool fullscreen) { if (isFullScreen() == fullscreen) return; if (fullscreen) { glfwGetWindowPos(currentContext, &windowPosition[0], &windowPosition[1]); glfwGetWindowSize(currentContext, &windowSize[0], &windowSize[1]); const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); glfwSetWindowMonitor(currentContext, currentMonitor, 0, 0, mode->width, mode->height, 0); } else { glfwSetWindowMonitor(currentContext, nullptr, windowPosition[0], windowPosition[1], windowSize[0], windowSize[1], 0); } } void swapFullScreen() { setFullScreen(!isFullScreen()); } void swapInterval(int interval) { glfwSwapInterval(interval); } void swapBuffers() { glfwSwapBuffers(currentContext); } void pollEvents() { glfwPollEvents(); } void closeWindow() { glfwSetWindowShouldClose(currentContext, GLFW_TRUE); } bool isWindowClosed() { return glfwWindowShouldClose(currentContext); } void setMultisampleSamples(int samples) { glfwWindowHint(GLFW_SAMPLES, samples); }; Vec2i getWindowSize() { int width; int height; glfwGetWindowSize(currentContext, &width, &height); return Vec2i(width, height); } Vec4i getFrameSize() { int left; int top; int right; int bottom; glfwGetWindowFrameSize(currentContext, &left, &top, &right, &bottom); return Vec4i(left, top, right, bottom); } void enableCursor() { glfwSetInputMode(currentContext, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } void disableCursor() { glfwSetInputMode(currentContext, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); } void setCursor(int type) { if (currentCursorType == type) return; if (currentCursor) glfwDestroyCursor(currentCursor); currentCursorType = type; currentCursor = glfwCreateStandardCursor(type); glfwSetCursor(currentContext, currentCursor); } void setWindowIcon(const GLFWimage* image) { glfwSetWindowIcon(currentContext, 1, image); } void setWindowIconFromPath(const char* path) { GLFWimage img; int channels; img.pixels = stbi_load(path, &img.width, &img.height, &channels, STBI_rgb_alpha); if(img.pixels != nullptr) { Graphics::GLFW::setWindowIcon(&img); stbi_image_free(img.pixels); } else { throw std::runtime_error("Logo not found!"); } } } }; ================================================ FILE: graphics/glfwUtils.h ================================================ #pragma once struct GLFWwindow; struct GLFWimage; namespace P3D::Graphics { namespace GLFW { namespace Cursor { extern int ARROW; extern int IBEAM; extern int CROSSHAIR; extern int HAND; extern int HRESIZE; extern int VRESIZE; } extern bool init(); extern void terminate(); extern GLFWwindow* getCurrentContext(); extern GLFWwindow* createContext(int width, int height, const char* title); extern void setCurrentContext(GLFWwindow* context); extern bool validContext(GLFWwindow* context); extern void swapInterval(int interval); extern void swapBuffers(); extern void pollEvents(); extern bool isFullScreen(); extern void setFullScreen(bool fullscreen); extern void swapFullScreen(); extern void closeWindow(); extern bool isWindowClosed(); extern Vec2i getWindowSize(); extern Vec4i getFrameSize(); extern void enableCursor(); extern void disableCursor(); extern void setCursor(int type); extern void setMultisampleSamples(int samples); extern void setWindowIcon(const GLFWimage* image); extern void setWindowIconFromPath(const char* path); } }; ================================================ FILE: graphics/graphics.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {13E417C8-0C80-482C-A415-8C11A5C770DA} graphics 10.0 Application true v142 MultiByte Application false v142 true MultiByte StaticLibrary true v142 MultiByte false StaticLibrary false v142 true MultiByte Level3 MaxSpeed true true true true $(SolutionDir)include;$(SolutionDir)graphics;$(SolutionDir); NotUsing core.h FT2_BUILD_LIBRARY;GLEW_STATIC;_MBCS;NDEBUG;%(PreprocessorDefinitions); stdcpp17 NotSet true true true freetype.lib;glfw3.lib;glew32s.lib;opengl32.lib; $(SolutionDir)lib;$(OutDir) Level3 Disabled true true Level3 Disabled true true $(SolutionDir)include;$(SolutionDir)graphics;$(SolutionDir); GLEW_STATIC;_MBCS;%(PreprocessorDefinitions);FT2_BUILD_LIBRARY; NotUsing core.h stdcpp17 true NotSet freetype.lib;glfw3.lib;glew32s.lib;opengl32.lib; $(SolutionDir)lib;$(OutDir) true false Level3 MaxSpeed true true true true true true NotUsing NotUsing NotUsing NotUsing Create core.h Create ================================================ FILE: graphics/gui/color.h ================================================ #pragma once #include namespace P3D::Graphics { template struct ColorTemplate { union { struct { T r; T g; T b; T a; }; T data[4]; }; constexpr ColorTemplate() : r(static_cast(0)), g(static_cast(0)), b(static_cast(0)), a(static_cast(1)) {} constexpr ColorTemplate(T r, T g, T b) : r(r), g(g), b(b), a(a) {} constexpr ColorTemplate(T r, T g, T b, T a) : r(r), g(g), b(b), a(a) {} constexpr ColorTemplate(T value) : r(value), g(value), b(value), a(static_cast(1)) {} constexpr ColorTemplate(T value, T a) : r(value), g(value), b(value), a(a) {} constexpr ColorTemplate(const Vector& rgb) : r(rgb.x), g(rgb.y), b(rgb.z), a(static_cast(1)) {} constexpr ColorTemplate(const Vector rgba) : r(rgba.x), g(rgba.y), b(rgba.z), a(rgba.w) {} ~ColorTemplate() = default; constexpr operator Vector() const { return Vector { r, g, b }; } constexpr operator Vector() const { return Vector { r, g, b, a }; } constexpr T& operator[](size_t index) noexcept { return data[index]; } // format: 0xRRGGBBAA template constexpr static ColorTemplate get() { if constexpr (Alpha == true) { constexpr int value = Hex; return ColorTemplate { static_cast(value >> 24 & 0xFF) / static_cast(255), static_cast(value >> 16 & 0xFF) / static_cast(255), static_cast(value >> 8 & 0xFF) / static_cast(255), static_cast(value & 0xFF) / static_cast(255) }; } else { constexpr int value = Hex << 8 | 0xFF; return ColorTemplate { static_cast(value >> 24 & 0xFF) / static_cast(255), static_cast(value >> 16 & 0xFF) / static_cast(255), static_cast(value >> 8 & 0xFF) / static_cast(255), static_cast(value & 0xFF) / static_cast(255) }; } } constexpr static ColorTemplate hsvaToRgba(const ColorTemplate& hsva) { constexpr T h = hsva.r * static_cast(360); constexpr T s = hsva.g; constexpr T v = hsva.b; constexpr T a = hsva.a; if (s == static_cast(0)) return ColorTemplate(v, v, v, a); constexpr int hi = static_cast(h / static_cast(60)) % 6; constexpr T f = h / static_cast(60) - static_cast(hi); constexpr T p = v * (static_cast(1) - s); constexpr T q = v * (static_cast(1) - s * f); constexpr T t = v * (static_cast(1) - s * (static_cast(1) - f)); switch (hi) { case 0: return ColorTemplate(v, t, p, a); case 1: return ColorTemplate(q, v, p, a); case 2: return ColorTemplate(p, v, t, a); case 3: return ColorTemplate(p, q, v, a); case 4: return ColorTemplate(t, p, v, a); case 5: return ColorTemplate(v, p, q, a); default: return ColorTemplate(); } } constexpr static ColorTemplate rgbaToHsva(const ColorTemplate& rgba) { constexpr T r = rgba.r; constexpr T g = rgba.g; constexpr T b = rgba.b; constexpr T a = rgba.a; constexpr T min = fmin(fmin(r, g), b); constexpr T max = fmax(fmax(r, g), b); constexpr T d = max - min; if (d == static_cast(0)) return ColorTemplate(static_cast(0), static_cast(0), max); constexpr T dr = ((max - r) / static_cast(6) + max / static_cast(2)) / d; constexpr T dg = ((max - g) / static_cast(6) + max / static_cast(2)) / d; constexpr T db = ((max - b) / static_cast(6) + max / static_cast(2)) / d; T h = static_cast(0); if (r == max) h = db - dg; else if (g == max) h = static_cast(1) / static_cast(3) + dr - db; else if (b == max) h = static_cast(2) / static_cast(3) + dg - dr; if (h < static_cast(0)) h += static_cast(1); if (h > static_cast(1)) h -= static_cast(1); return ColorTemplate(h, d / max, max); } constexpr static ColorTemplate blend(const ColorTemplate& color1, const ColorTemplate& color2, T blend) { return (static_cast(1) - blend) * color1 + blend * color2; } }; template constexpr ColorTemplate& operator+=(ColorTemplate& first, const ColorTemplate& second) { first.r += second.r; first.g += second.g; first.b += second.b; first.a += second.a; return first; } template constexpr ColorTemplate operator+(const ColorTemplate& first, const ColorTemplate& second) { return ColorTemplate { first.r + second.r, first.g + second.g, first.b + second.b, first.a + second.a }; } template constexpr ColorTemplate operator*(const ColorTemplate& color, T factor) { return ColorTemplate { color.r * factor, color.g * factor, color.b * factor, color.a * factor }; } template constexpr ColorTemplate operator*(T factor, const ColorTemplate& color) { return ColorTemplate { factor * color.r, factor * color.g, factor * color.b, factor * color.a }; } typedef ColorTemplate Color; namespace Colors { constexpr static Color DISABLED = Color::get<0xA0A0A0>(); constexpr static Color ACCENT = Color::get<0x1F6678>(); constexpr static Color BACK = Color::get<0x4D4D4D>(); constexpr static Color ALPHA = Color::get<0x0, true>(); constexpr static Color RGB_R = Color::get<0xFF0000>(); constexpr static Color RGB_G = Color::get<0x00FF00>(); constexpr static Color RGB_B = Color::get<0x0000FF>(); constexpr static Color A = Color::get<0x0, true>(); constexpr static Color NAVY = Color::get<0x001F3F>(); constexpr static Color BLUE = Color::get<0x0074D9>(); constexpr static Color AQUA = Color::get<0x7FDBFF>(); constexpr static Color TEAL = Color::get<0x39CCCC>(); constexpr static Color OLIVE = Color::get<0x3D9970>(); constexpr static Color GREEN = Color::get<0x2ECC40>(); constexpr static Color LIME = Color::get<0x01FF70>(); constexpr static Color YELLOW = Color::get<0xFFDC00>(); constexpr static Color ORANGE = Color::get<0xFF851B>(); constexpr static Color RED = Color::get<0xFF4136>(); constexpr static Color MAROON = Color::get<0x85144b>(); constexpr static Color FUCHSIA = Color::get<0xF012BE>(); constexpr static Color PURPLE = Color::get<0xB10DC9>(); constexpr static Color BLACK = Color::get<0x111111>(); constexpr static Color GRAY = Color::get<0xAAAAAA>(); constexpr static Color SILVER = Color::get<0xDDDDDD>(); constexpr static Color WHITE = Color::get<0xFFFFFF>(); } }; ================================================ FILE: graphics/gui/gui.cpp ================================================ #include "core.h" #include "gui.h" #include "../util/resource/resourceManager.h" namespace P3D::Graphics { namespace GUI { // Global WindowInfo windowInfo; Font* font = nullptr; // Batch GuiBatch* batch; void onInit(const WindowInfo& info) { // Init Shaders::onInit(); GUI::windowInfo = info; GUI::batch = new GuiBatch(); // font GUI::font = ResourceManager::add("font", "../res/fonts/droid.ttf"); //font->getAtlas()->generateMipmap(); } bool onWindowResize(const WindowInfo& info) { GUI::windowInfo = info; return false; } void onUpdate(const Mat4f& orthoMatrix) { Shaders::quadShader->updateProjection(orthoMatrix); } void onClose() { // Shaders Shaders::onClose(); // Fonts font->close(); } } } ================================================ FILE: graphics/gui/gui.h ================================================ #pragma once #include "../batch/guiBatch.h" #include "../resource/fontResource.h" namespace P3D::Graphics { class Font; namespace GUI { // Window struct WindowInfo { Vec2i dimension; float aspect; }; // Global extern WindowInfo windowInfo; extern Font* font; // Batch extern GuiBatch* batch; void onInit(const WindowInfo& info); bool onWindowResize(const WindowInfo& info); void onUpdate(const Mat4f& orthoMatrix); void onClose(); } }; ================================================ FILE: graphics/gui/guiUtils.cpp ================================================ #include "core.h" #include "guiUtils.h" #include "gui.h" namespace P3D::Graphics { namespace GUI { double clamp(double value, double min, double max) { if (value < min) return min; if (value > max) return max; return value; } float clamp(float value, float min, float max) { if(value < min) return min; if(value > max) return max; return value; } double smoothstep(double start, double end, double x) { double t = clamp((x - start) / (end - start), 0.0, 1.0); return t * t * (3.0 - 2.0 * t); } float smoothstep(float start, float end, float x) { float t = clamp((x - start) / (end - start), 0.0f, 1.0f); return t * t * (3.0f - 2.0f * t); } double map(double x, double minIn, double maxIn, double minOut, double maxOut) { return (x - minIn) * (maxOut - minOut) / (maxIn - minIn) + minOut; } Vec2 map(const Vec2& point) { return Vec2( map(point.x, 0, GUI::windowInfo.dimension.x, -GUI::windowInfo.aspect, GUI::windowInfo.aspect), map(GUI::windowInfo.dimension.y - point.y, 0, GUI::windowInfo.dimension.y, -1, 1)); } Vec4 mapRegion(const Vec4& region, const Vec2& inXDimension, const Vec2& inYDimension, const Vec2& outXDimension, const Vec2& outYDimension) { Vec2 inPoint1 = Vec2(region.x, region.y); Vec2 inPoint2 = Vec2(region.z, region.w); Vec2 outPoint1 = mapDimension(inPoint1, inXDimension, inYDimension, outXDimension, outYDimension); Vec2 outPoint2 = mapDimension(inPoint2, inXDimension, inYDimension, outXDimension, outYDimension); return Vec4(outPoint1.x, outPoint1.y, outPoint2.x, outPoint2.y); } Vec2 mapDimension(const Vec2& point, const Vec2& inXDimension, const Vec2& inYDimension, const Vec2& outXDimension, const Vec2& outYDimension) { return Vec2(map(point.x, inXDimension.x, inXDimension.y, outXDimension.x, outXDimension.y), map(point.y, inYDimension.x, inYDimension.y, outYDimension.x, outYDimension.y)); } Vec2 unmap(const Vec2& point) { return Vec2(map(point.x, -GUI::windowInfo.aspect, GUI::windowInfo.aspect, 0, GUI::windowInfo.dimension.x), GUI::windowInfo.dimension.y - map(point.y, -1, 1, 0, GUI::windowInfo.dimension.y)); } Vec2 mapDimension(const Vec2& dimension) { return Vec2(map(dimension.x, 0, GUI::windowInfo.dimension.x, 0, 2 * GUI::windowInfo.aspect), map(dimension.y, 0, GUI::windowInfo.dimension.y, 0, 2)); } Vec2 unmapDimension(const Vec2& dimension) { return Vec2(map(dimension.x, 0, 2 * GUI::windowInfo.aspect, 0, GUI::windowInfo.dimension.x), map(dimension.y, 0, 2, 0, GUI::windowInfo.dimension.y)); } bool between(double value, double min, double max) { return min <= value && value <= max; } std::pair minmax(double value1, double value2) { return (value1 < value2) ? std::pair { value1, value2 } : std::pair { value2, value1 }; } } }; ================================================ FILE: graphics/gui/guiUtils.h ================================================ #pragma once namespace P3D::Graphics { namespace GUI { // Clamps a value between a range double clamp(double value, double min, double max); float clamp(float value, float min, float max); // Performs a smooth Hermite interpolation between 0 and 1 when start < x < end double smoothstep(double start, double end, double x); float smoothstep(float start, float end, float x); // Maps a value from an input range to an output range double map(double x, double minIn, double maxIn, double minOut, double maxOut); // Maps a point from screen space to view space Vec2 map(const Vec2& point); // Maps a point from view space to screen space Vec2 unmap(const Vec2& point); // Maps a dimension from screen space to view space Vec2 mapDimension(const Vec2& dimension); // Maps a region to a given range Vec4 mapRegion(const Vec4& region, const Vec2& inXDimension, const Vec2& inYDimension, const Vec2& outXDimension, const Vec2& outYDimension); // Maps a point from the input range to the output range Vec2 mapDimension(const Vec2& point, const Vec2& inXDimension, const Vec2& inYDimension, const Vec2& outXDimension, const Vec2& outYDimension); // Maps a dimension from view space to screen space Vec2 unmapDimension(const Vec2& dimension); // Return whether the given value is between min and max bool between(double value, double min, double max); // Returns the min and max of two numbers std::pair minmax(double value1, double value2); } }; ================================================ FILE: graphics/gui/imgui/imguiExtension.h ================================================ #pragma once #include "../../resource/textureResource.h" #include "../util/resource/resourceManager.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" #include "../engine/tool/tool.h" namespace ImGui { inline void DrawLineBetween(const ImRect& button1, const ImRect& button2, float yExtent = 0) { ImVec2 pos1 = ImVec2( button1.Max.x + (button2.Min.x - button1.Max.x) / 2, ImMin(button1.Min.y, button2.Min.y) - yExtent / 2 ); ImVec2 pos2 = ImVec2( pos1.x, ImMax(button1.Max.y, button2.Max.y) + yExtent / 2 ); GImGui->CurrentWindow->DrawList->AddLine(pos1, pos2, ImGui::GetColorU32(ImVec4(0.7f, 0.7f, 0.7f, 0.3f))); } inline void DrawLine(const ImVec2& pos1, const ImVec2& pos2) { GImGui->CurrentWindow->DrawList->AddLine(pos1, pos2, ImGui::GetColorU32(ImVec4(0.7f, 0.7f, 0.7f, 0.3f))); } // Draw buttons inline void DrawButton(const ImRect& button, bool even, bool selected, bool held, bool hovered, float rounding = 0.0f) { ImU32 color = even ? ImGui::GetColorU32(ImVec4(0, 0, 0, 0)) : ImGui::GetColorU32(ImVec4(0.4f, 0.4f, 0.4f, 0.1f)); if (held || selected) color = ImGui::GetColorU32(ImGuiCol_ButtonActive); else if (hovered) color = ImGui::GetColorU32(ImGuiCol_ButtonHovered); GImGui->CurrentWindow->DrawList->AddRectFilled(ImVec2(button.Min.x, button.Min.y - GImGui->Style.ItemSpacing.y / 2), ImVec2(button.Max.x, button.Max.y + GImGui->Style.ItemSpacing.y / 2), color, rounding); } // Draw icons inline void DrawIcon(GLID icon, const ImVec2& min, const ImVec2& max) { if (icon == 0) return; GImGui->CurrentWindow->DrawList->AddImage(reinterpret_cast(icon), min, max); } inline ImU32 HexToImU32(const char* hex) { int i[4]; sscanf(hex, "%02X%02X%02X%02X", (unsigned int*) &i[0], (unsigned int*) &i[1], (unsigned int*) &i[2], (unsigned int*) &i[3]); ImVec4 col = { i[0] / 255.0f, i[1] / 255.0f, i[2] / 255.0f, i[3] / 255.0f, }; return ImGui::ColorConvertFloat4ToU32(col); } inline bool InputDouble3(const char* label, double v[3], ImGuiInputTextFlags flags) { char format[16] = "%f"; return InputScalarN(label, ImGuiDataType_Double, v, 3, NULL, NULL, format, flags); } inline bool InputFloat9(const char* label, float v[9], ImGuiInputTextFlags flags) { char format[16] = "%f"; bool i1 = InputFloat3("", v); bool i2 = InputFloat3("", v + 3); bool i3 = InputFloat3("", v + 6); return i1 | i2 | i3; } inline bool DragVecN(const char* id, const char** labels, float* data, int components, float resetValue = 0.0f, float* speed = nullptr, bool resetAll = false, float** min = nullptr, float** max = nullptr, ImU32* colors = nullptr) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; if (data == nullptr) return false; bool result = false; float size = GImGui->Style.ItemInnerSpacing.x * 2.0f; for (int i = 0; i < components; i++) { float label_size = CalcTextSize(labels[i], nullptr, true).x; float padding_size = GImGui->Style.FramePadding.x * 2.0f; float spacing_size = GImGui->Style.ItemInnerSpacing.x * 2.0f; size += label_size + padding_size + spacing_size; } if (resetAll) { float label_size = CalcTextSize("Reset", nullptr, true).x; float padding_size = GImGui->Style.FramePadding.x * 2.0f; float spacing_size = GImGui->Style.ItemInnerSpacing.x * 2.0f; size += label_size + padding_size + spacing_size; } BeginGroup(); PushMultiItemsWidths(components, GetColumnWidth() - size); PushID(id); for (int i = 0; i < components; i++) { if (i > 0) SameLine(0, GImGui->Style.ItemInnerSpacing.x); if (colors) { PushStyleColor(ImGuiCol_Button, colors[3 * i + 0]); PushStyleColor(ImGuiCol_ButtonHovered, colors[3 * i + 1]); PushStyleColor(ImGuiCol_ButtonActive, colors[3 * i + 2]); } if(Button(labels[i])) { *(data + i) = resetValue; result = true; } if (colors) PopStyleColor(3); SameLine(0, GImGui->Style.ItemInnerSpacing.x); PushID(i); result |= DragScalar("", ImGuiDataType_Float, data + i, speed == nullptr ? 0.1f : *(speed + i), min == nullptr ? nullptr : *(min + i), max == nullptr ? nullptr : *(max + i), "%.2f"); PopID(); PopItemWidth(); } if (resetAll) { SameLine(0, GImGui->Style.ItemInnerSpacing.x); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, -1)); if (ImageButton((ImTextureID) ResourceManager::get("reset")->getID(), ImVec2(20, 20), ImVec2(0, 1), ImVec2(1, 0))) { for (int i = 0; i < components; i++) *(data + i) = resetValue; result = true; } PopStyleVar(1); } PopID(); EndGroup(); return result; } inline bool DragVec3(const char* id, float values[3], float resetValue = 0.0f, float* speed = nullptr, bool resetAll = false, float** min = nullptr, float** max = nullptr) { const char* labels[] = { "X", "Y", "Z" }; ImU32 colors[] = { 4280887473, // Red Idle 4280097889, // Red Hover 4280821893, // Red Active 4281503782, // Green Idle 4280574236, // Green Hover 4281566504, // Green Active 4289808168, // Blue Idle 4284558364, // Blue Hover 4286922280, // Blue Active }; return DragVecN(id, labels, values, 3, resetValue, speed, resetAll, min, max, colors); } inline void HelpMarker(const char* description) { TextDisabled("(?)"); if (IsItemHovered()) { BeginTooltip(); PushTextWrapPos(GetFontSize() * 35.0f); TextUnformatted(description); PopTextWrapPos(); EndTooltip(); } } inline void BeginToolbar(const char* name) { Begin(name, 0, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize); } inline bool ToolBarButton(const char* name, const char* description, const char* resource, bool selected = false) { P3D::Graphics::TextureResource* image = ResourceManager::get(resource); if (image == nullptr) return false; PushStyleVar(ImGuiStyleVar_FrameRounding, 0); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, -1)); SameLine(); ImVec4 bg = selected ? ImVec4(0.2, 0.2, 0.2, 0.5) : ImVec4(0, 0, 0, 0); ImGui::PushID(name); bool result = ImageButton((ImTextureID) image->getID(), ImVec2(25, 25), ImVec2(0, 0), ImVec2(1, 1), 2, bg); ImGui::PopID(); PopStyleVar(2); if (IsItemHovered()) { BeginTooltip(); Text(name); TextDisabled(description); EndTooltip(); } return result; } inline bool ToolBarButton(P3D::Engine::Tool* tool, bool selected = false) { return ToolBarButton(tool->getName().c_str(), tool->getDescription().c_str(), tool->getName().c_str(), selected); } inline void ToolBarSpacing() { SameLine(0, 30); Spacing(); } inline void EndToolBar() { End(); } } #define PROPERTY_FRAME_START(label) \ if (ImGui::CollapsingHeader((label), ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_AllowItemOverlap)) { \ ImGui::Columns(2) #define PROPERTY_FRAME_END\ ImGui::Columns(1); \ } #define PROPERTY_DESC_IF(text, desc, widget, code) \ { \ ImGui::TextUnformatted(text); \ ImGui::SameLine(); \ ImGui::HelpMarker(desc); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ if (widget) { \ code \ }; \ ImGui::NextColumn(); \ } #define PROPERTY_IF(text, widget, code) \ { \ ImGui::TextUnformatted(text); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ if (widget) { \ code \ }; \ ImGui::NextColumn(); \ } #define PROPERTY_DESC_IF_LOCK(text, desc, widget, code) \ { \ ImGui::TextUnformatted(text); \ ImGui::SameLine(); \ ImGui::HelpMarker(desc); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ if (widget) { \ screen.worldMutex->lock() \ code \ screen.worldMutex->unlock() \ }; \ ImGui::NextColumn(); \ } #define PROPERTY_IF_LOCK(text, widget, code) \ { \ ImGui::TextUnformatted(text); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ if (widget) { \ screen.worldMutex->lock(); \ code \ screen.worldMutex->unlock(); \ }; \ ImGui::NextColumn(); \ } #define PROPERTY_DESC(text, desc, widget) \ { \ ImGui::TextUnformatted(text); \ ImGui::SameLine(); \ ImGui::HelpMarker(desc); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ widget; \ ImGui::NextColumn(); \ } #define PROPERTY(text, widget) \ { \ ImGui::TextUnformatted(text); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ widget; \ ImGui::NextColumn(); \ } #define TITLE_DESC(text, desc, newline) \ { \ if (newline) { \ ECS_PROPERTY("", ); \ } \ ImGui::TextColored(GImGui->Style.Colors[ImGuiCol_ButtonActive], text); \ ImGui::SameLine(); \ ImGui::HelpMarker(desc); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ ImGui::NextColumn(); \ } #define TITLE(text, newline) \ { \ if (newline) { \ PROPERTY("", ); \ } \ ImGui::TextColored(GImGui->Style.Colors[ImGuiCol_ButtonActive], text); \ ImGui::NextColumn(); \ ImGui::SetNextItemWidth(-1); \ ImGui::NextColumn(); \ } ================================================ FILE: graphics/gui/imgui/imguiStyle.cpp ================================================ #include "core.h" #include "imguiStyle.h" namespace P3D::Graphics { ImGuiColors theme = Dark; ImVec4 text = ImVec4(0.80f, 0.80f, 0.83f, 1.00f); ImVec4 idle = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); ImVec4 hover = ImVec4(0.10f, 0.40f, 0.75f, 0.75f); ImVec4 active = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); ImVec4 dark = ImVec4(0.176f, 0.228f, 0.435f, 1.00f); void renderImGuiStyleEditor() { ImGui::Begin("Style"); ImGuiStyle* style = &ImGui::GetStyle(); const char* themes[] = { "Classic", "Classic Light", "Classic Dark", "Light", "Gray", "Dark" }; const char* current = themes[theme]; ImGui::Text("Theme"); if (ImGui::BeginCombo("Theme", current)) { for (int i = 0; i < IM_ARRAYSIZE(themes); i++) { bool is_selected = i == theme; if (ImGui::Selectable(themes[i], is_selected)) { theme = static_cast(i); setupImGuiColors(style, theme); if (i >= 3) setupImGuiAccent(style); } if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } bool edited = false; if (theme >= 3) { ImGui::Text("Accent"); edited |= ImGui::ColorEdit4("Text", (float*) &text, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar); edited |= ImGui::ColorEdit4("Idle", (float*) &idle, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar); edited |= ImGui::ColorEdit4("Hover", (float*) &hover, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar); edited |= ImGui::ColorEdit4("Active", (float*) &active, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar); edited |= ImGui::ColorEdit4("Dark", (float*) &dark, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_AlphaBar); if (edited) setupImGuiAccent(style); } ImGui::End(); } void setupImGuiLayoutStyle(ImGuiStyle* style) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromFileTTF("../res/fonts/droid.ttf", 20); style->WindowPadding = ImVec2(8, 10); style->FramePadding = ImVec2(10, 5); style->ItemSpacing = ImVec2(15, 8); style->ItemInnerSpacing = ImVec2(6, 6); style->IndentSpacing = 20.0f; style->ScrollbarSize = 20.0f; style->GrabMinSize = 20.0f; style->WindowBorderSize = 0.0f; style->ChildBorderSize = 1.0f; style->FrameBorderSize = 0.0f; style->PopupBorderSize = 0.0f; style->TabBorderSize = 0.0f; style->WindowRounding = 6.0f; style->ChildRounding = 10.0f; style->FrameRounding = 6.0f; style->PopupRounding = 6.0f; style->ScrollbarRounding = 3.0f; style->GrabRounding = 5.0f; style->WindowTitleAlign = ImVec2(0.5f, 0.5f); style->WindowMenuButtonPosition = ImGuiDir_Right; } void setupImGuiAccent(ImGuiStyle* style) { style->Colors[ImGuiCol_Text] = text; style->Colors[ImGuiCol_FrameBgHovered] = idle; style->Colors[ImGuiCol_CheckMark] = active; style->Colors[ImGuiCol_SliderGrab] = active; style->Colors[ImGuiCol_SliderGrabActive] = active; style->Colors[ImGuiCol_Button] = idle; style->Colors[ImGuiCol_ButtonHovered] = hover; style->Colors[ImGuiCol_ButtonActive] = active; style->Colors[ImGuiCol_HeaderHovered] = hover; style->Colors[ImGuiCol_HeaderActive] = active; style->Colors[ImGuiCol_SeparatorHovered] = hover; style->Colors[ImGuiCol_SeparatorActive] = active; style->Colors[ImGuiCol_ResizeGrip] = idle; style->Colors[ImGuiCol_ResizeGripHovered] = hover; style->Colors[ImGuiCol_ResizeGripActive] = active; style->Colors[ImGuiCol_TabHovered] = hover; style->Colors[ImGuiCol_TabActive] = active; style->Colors[ImGuiCol_DockingPreview] = hover; style->Colors[ImGuiCol_TextSelectedBg] = hover; style->Colors[ImGuiCol_NavHighlight] = active; style->Colors[ImGuiCol_Tab] = idle; style->Colors[ImGuiCol_TabUnfocusedActive] = dark; style->Colors[ImGuiCol_TabUnfocused] = active; } void setupImGuiColors(ImGuiStyle* style, ImGuiColors colors) { switch (colors) { case Light: break; case Gray: style->Colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); style->Colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); style->Colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); style->Colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); style->Colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); style->Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); style->Colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); style->Colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f); style->Colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); style->Colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); style->Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); style->Colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); style->Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); style->Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); style->Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); style->Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f); style->Colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f); style->Colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); style->Colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); style->Colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); style->Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); style->Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); style->Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); style->Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); style->Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); style->Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); break; case Dark: style->Colors[ImGuiCol_TextDisabled] = ImVec4(0.24f, 0.23f, 0.29f, 1.00f); style->Colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.05f, 0.07f, 1.00f); style->Colors[ImGuiCol_ChildBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style->Colors[ImGuiCol_PopupBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style->Colors[ImGuiCol_Border] = ImVec4(0.80f, 0.80f, 0.83f, 0.88f); style->Colors[ImGuiCol_BorderShadow] = ImVec4(0.92f, 0.91f, 0.88f, 0.00f); style->Colors[ImGuiCol_FrameBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f); style->Colors[ImGuiCol_FrameBgActive] = ImVec4(0.56f, 0.56f, 0.58f, 1.00f); style->Colors[ImGuiCol_TitleBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f); style->Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 0.98f, 0.95f, 0.75f); style->Colors[ImGuiCol_TitleBgActive] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style->Colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f); style->Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f); style->Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.31f); style->Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.56f, 0.56f, 0.58f, 1.00f); style->Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.06f, 0.05f, 0.07f, 1.00f); style->Colors[ImGuiCol_Header] = ImVec4(0.10f, 0.09f, 0.12f, 1.00f); style->Colors[ImGuiCol_PlotLines] = ImVec4(0.40f, 0.39f, 0.38f, 0.63f); style->Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style->Colors[ImGuiCol_PlotHistogram] = ImVec4(0.40f, 0.39f, 0.38f, 0.63f); style->Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style->Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.2f, 0.2f, 0.2f, 0.75f); break; case ClassicLight: ImGui::StyleColorsLight(style); break; case ClassicDark: ImGui::StyleColorsDark(style); break; case Classic: ImGui::StyleColorsClassic(style); } } void setupImGuiStyle() { ImGuiStyle* style = &ImGui::GetStyle(); setupImGuiLayoutStyle(style); setupImGuiAccent(style); setupImGuiColors(style, ImGuiColors::Dark); } } ================================================ FILE: graphics/gui/imgui/imguiStyle.h ================================================ #pragma once #include "imgui/imgui.h" namespace P3D::Graphics { enum ImGuiColors { Classic, ClassicLight, ClassicDark, Light, Gray, Dark }; extern ImGuiColors theme; extern ImVec4 text; extern ImVec4 idle; extern ImVec4 hover; extern ImVec4 active; extern ImVec4 dark; void setupImGuiLayoutStyle(ImGuiStyle* style); void setupImGuiAccent(ImGuiStyle* style); void setupImGuiColors(ImGuiStyle* style, ImGuiColors colors); void setupImGuiStyle(); void renderImGuiStyleEditor(); } ================================================ FILE: graphics/gui/imgui/legacy_imgui_impl_glfw.cpp ================================================ #include "imgui/imgui.h" #include "imgui_impl_glfw.h" // GLFW #include #ifdef _WIN32 #undef APIENTRY #define GLFW_EXPOSE_NATIVE_WIN32 #include // for glfwGetWin32Window #endif #define GLFW_HAS_WINDOW_TOPMOST (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ GLFW_FLOATING #define GLFW_HAS_WINDOW_HOVERED (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ GLFW_HOVERED #define GLFW_HAS_WINDOW_ALPHA (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwSetWindowOpacity #define GLFW_HAS_PER_MONITOR_DPI (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetMonitorContentScale #define GLFW_HAS_VULKAN (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwCreateWindowSurface // Data enum GlfwClientApi { GlfwClientApi_Unknown, GlfwClientApi_OpenGL }; static GLFWwindow* g_Window = NULL; static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown; static double g_Time = 0.0; static bool g_MouseJustPressed[5] = { false, false, false, false, false }; static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; static bool g_InstalledCallbacks = false; static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL; static GLFWscrollfun g_PrevUserCallbackScroll = NULL; static GLFWkeyfun g_PrevUserCallbackKey = NULL; static GLFWcharfun g_PrevUserCallbackChar = NULL; static const char* ImGuiGetClipboardText(void* user_data) { return glfwGetClipboardString((GLFWwindow*) user_data); } static void ImGuiSetClipboardText(void* user_data, const char* text) { glfwSetClipboardString((GLFWwindow*) user_data, text); } void ImGuiMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { if (g_PrevUserCallbackMousebutton != NULL) g_PrevUserCallbackMousebutton(window, button, action, mods); if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed)) g_MouseJustPressed[button] = true; } void ImGuiScrollCallback(GLFWwindow* window, double xoffset, double yoffset) { if (g_PrevUserCallbackScroll != NULL) g_PrevUserCallbackScroll(window, xoffset, yoffset); ImGuiIO& io = ImGui::GetIO(); io.MouseWheelH += (float)xoffset; io.MouseWheel += (float)yoffset; } void ImGuiKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (g_PrevUserCallbackKey != NULL) g_PrevUserCallbackKey(window, key, scancode, action, mods); ImGuiIO& io = ImGui::GetIO(); if (action == GLFW_PRESS) io.KeysDown[key] = true; if (action == GLFW_RELEASE) io.KeysDown[key] = false; // Modifiers are not reliable across systems io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; } void ImGuiCharCallback(GLFWwindow* window, unsigned int c) { if (g_PrevUserCallbackChar != NULL) g_PrevUserCallbackChar(window, c); ImGuiIO& io = ImGui::GetIO(); io.AddInputCharacter(c); } bool ImGuiInitGLFW(GLFWwindow* window, bool install_callbacks) { g_Window = window; g_Time = 0.0; // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendPlatformName = "imgui_impl_glfw"; // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; io.KeyMap[ImGuiKey_KeyPadEnter] = GLFW_KEY_KP_ENTER; io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; io.SetClipboardTextFn = ImGuiSetClipboardText; io.GetClipboardTextFn = ImGuiGetClipboardText; io.ClipboardUserData = g_Window; #if defined(_WIN32) io.ImeWindowHandle = (void*)glfwGetWin32Window(g_Window); #endif g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. g_PrevUserCallbackMousebutton = NULL; g_PrevUserCallbackScroll = NULL; g_PrevUserCallbackKey = NULL; g_PrevUserCallbackChar = NULL; if (install_callbacks) { g_InstalledCallbacks = true; g_PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGuiMouseButtonCallback); g_PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGuiScrollCallback); g_PrevUserCallbackKey = glfwSetKeyCallback(window, ImGuiKeyCallback); g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGuiCharCallback); } g_ClientApi = GlfwClientApi_OpenGL; return true; } void ImGuiShutdownGLFW() { if (g_InstalledCallbacks) { glfwSetMouseButtonCallback(g_Window, g_PrevUserCallbackMousebutton); glfwSetScrollCallback(g_Window, g_PrevUserCallbackScroll); glfwSetKeyCallback(g_Window, g_PrevUserCallbackKey); glfwSetCharCallback(g_Window, g_PrevUserCallbackChar); g_InstalledCallbacks = false; } for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) { glfwDestroyCursor(g_MouseCursors[cursor_n]); g_MouseCursors[cursor_n] = NULL; } g_ClientApi = GlfwClientApi_Unknown; } static void ImGuiUpdateMousePosAndButtons() { // Update buttons ImGuiIO& io = ImGui::GetIO(); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; g_MouseJustPressed[i] = false; } // Update mouse position const ImVec2 mouse_pos_backup = io.MousePos; io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); const bool focused = glfwGetWindowAttrib(g_Window, GLFW_FOCUSED) != 0; if (focused) { if (io.WantSetMousePos) { glfwSetCursorPos(g_Window, (double)mouse_pos_backup.x, (double)mouse_pos_backup.y); } else { double mouse_x, mouse_y; glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); } } } static void ImGuiUpdateMouseCursor() { ImGuiIO& io = ImGui::GetIO(); if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) return; ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); } else { // Show OS mouse cursor // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here. glfwSetCursor(g_Window, g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } void ImGuiNewFrameGLFW() { ImGuiIO& io = ImGui::GetIO(); IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); // Setup display size (every frame to accommodate for window resizing) int w, h; int display_w, display_h; glfwGetWindowSize(g_Window, &w, &h); glfwGetFramebufferSize(g_Window, &display_w, &display_h); io.DisplaySize = ImVec2((float)w, (float)h); if (w > 0 && h > 0) io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); // Setup time step double current_time = glfwGetTime(); io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); g_Time = current_time; ImGuiUpdateMousePosAndButtons(); ImGuiUpdateMouseCursor(); } ================================================ FILE: graphics/gui/imgui/legacy_imgui_impl_glfw.h ================================================ #pragma once struct GLFWwindow; bool ImGuiInitGLFW(GLFWwindow* window, bool install_callbacks); void ImGuiShutdownGLFW(); void ImGuiNewFrameGLFW(); void ImGuiMouseButtonCallback(GLFWwindow* window, int button, int action, int mods); void ImGuiScrollCallback(GLFWwindow* window, double xoffset, double yoffset); void ImGuiKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); void ImGuiCharCallback(GLFWwindow* window, unsigned int c); ================================================ FILE: graphics/gui/imgui/legacy_imgui_impl_opengl3.cpp ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 2.x 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. // 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. // 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. // 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. // 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. // 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). // 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. // 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. // 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. // 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. // 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. // 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". // 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. // 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. // 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. // 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. // 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. // 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. // 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". // 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. // 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. // 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. // 2017-05-01: OpenGL: Fixed save and restore of current blend func state. // 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. // 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. // 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) //---------------------------------------- // OpenGL GLSL GLSL // version version string //---------------------------------------- // 2.0 110 "#version 110" // 2.1 120 "#version 120" // 3.0 130 "#version 130" // 3.1 140 "#version 140" // 3.2 150 "#version 150" // 3.3 330 "#version 330 core" // 4.0 400 "#version 400 core" // 4.1 410 "#version 410 core" // 4.2 420 "#version 410 core" // 4.3 430 "#version 430 core" // ES 2.0 100 "#version 100" = WebGL 1.0 // ES 3.0 300 "#version 300 es" = WebGL 2.0 //---------------------------------------- #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include "imgui/imgui.h" #include "imgui_impl_opengl3.h" #include #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t #else #include // intptr_t #endif #if defined(__APPLE__) #include "TargetConditionals.h" #endif // Auto-enable GLES on matching platforms #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #elif defined(__EMSCRIPTEN__) #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #endif // GL includes #if defined(IMGUI_IMPL_OPENGL_ES2) #include #elif defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) #include // Use GL ES 3 #else #include // Use GL ES 3 #endif #else // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Needs to be initialized with gl3wInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Needs to be initialized with glewInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) #include // Needs to be initialized with gladLoadGL() in user's code #else #include IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #endif // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. #if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2) #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 0 #else #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 1 #endif // OpenGL Data static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries. static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. static GLuint g_FontTexture = 0; static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; // Functions bool ImGuiInitOpenGl(const char* glsl_version) { // Query for GL version #if !defined(IMGUI_IMPL_OPENGL_ES2) GLint major, minor; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); g_GlVersion = major * 1000 + minor; #else g_GlVersion = 2000; // GLES 2 #endif // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_opengl3"; #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 3200) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif // Store GLSL version string so we can refer to it later in case we recreate shaders. // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. #if defined(IMGUI_IMPL_OPENGL_ES2) if (glsl_version == NULL) glsl_version = "#version 100"; #elif defined(IMGUI_IMPL_OPENGL_ES3) if (glsl_version == NULL) glsl_version = "#version 300 es"; #else if (glsl_version == NULL) glsl_version = "#version 130"; #endif IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); strcpy(g_GlslVersionString, glsl_version); strcat(g_GlslVersionString, "\n"); // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! // If auto-detection fails or doesn't select the same GL loader file as used by your application, // you are likely to get a crash below. // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. const char* gl_loader = "Unknown"; IM_UNUSED(gl_loader); #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) gl_loader = "GL3W"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) gl_loader = "GLEW"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) gl_loader = "GLAD"; #else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM gl_loader = "Custom"; #endif // Make a dummy GL call (we don't actually need the result) // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. GLint current_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); return true; } void ImGuiShutdownOpenGL() { ImGui_ImplOpenGL3_DestroyDeviceObjects(); } void ImGuiNewFrameOpenGL() { if (!g_ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) { // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); float L = draw_data->DisplayPos.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; const float ortho_projection[4][4] = { { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f, 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, }; glUseProgram(g_ShaderHandle); glUniform1i(g_AttribLocationTex, 0); glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); #ifdef GL_SAMPLER_BINDING glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif (void)vertex_array_object; #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(vertex_array_object); #endif // Bind vertex/index buffers and setup attributes for ImDrawVert glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); glEnableVertexAttribArray(g_AttribLocationVtxPos); glEnableVertexAttribArray(g_AttribLocationVtxUV); glEnableVertexAttribArray(g_AttribLocationVtxColor); glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); } // OpenGL3 Render function. // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. void ImGuiRenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); if (fb_width <= 0 || fb_height <= 0) return; // Backup GL state GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); #ifdef GL_SAMPLER_BINDING GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); #endif GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); #endif #ifdef GL_POLYGON_MODE GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); #endif GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); GLboolean last_enable_blend = glIsEnabled(GL_BLEND); GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); bool clip_origin_lower_left = true; #if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) if (last_clip_origin == GL_UPPER_LEFT) clip_origin_lower_left = false; #endif // Setup desired GL state // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. GLuint vertex_array_object = 0; #ifndef IMGUI_IMPL_OPENGL_ES2 glGenVertexArrays(1, &vertex_array_object); #endif ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; // Upload vertex/index buffers glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback != NULL) { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); else pcmd->UserCallback(cmd_list, pcmd); } else { // Project scissor/clipping rectangles into framebuffer space ImVec4 clip_rect; clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Apply scissor/clipping rectangle if (clip_origin_lower_left) glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); else glScissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) // Bind texture, Draw glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 3200) glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); else #endif glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); } } } } // Destroy the temporary VAO #ifndef IMGUI_IMPL_OPENGL_ES2 glDeleteVertexArrays(1, &vertex_array_object); #endif // Restore modified GL state glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); #ifdef GL_SAMPLER_BINDING glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array_object); #endif glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); #endif glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); } bool ImGui_ImplOpenGL3_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGenTextures(1, &g_FontTexture); glBindTexture(GL_TEXTURE_2D, g_FontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); #ifdef GL_UNPACK_ROW_LENGTH glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); return true; } void ImGui_ImplOpenGL3_DestroyFontsTexture() { if (g_FontTexture) { ImGuiIO& io = ImGui::GetIO(); glDeleteTextures(1, &g_FontTexture); io.Fonts->TexID = 0; g_FontTexture = 0; } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. static bool CheckShader(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetShaderiv(handle, GL_COMPILE_STATUS, &status); glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } // If you get an error please report on GitHub. You may try different GL context version or GLSL version. static bool CheckProgram(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetProgramiv(handle, GL_LINK_STATUS, &status); glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } bool ImGui_ImplOpenGL3_CreateDeviceObjects() { // Backup GL state GLint last_texture, last_array_buffer; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); #endif // Parse GLSL version string int glsl_version = 130; sscanf(g_GlslVersionString, "#version %d", &glsl_version); const GLchar* vertex_shader_glsl_120 = "uniform mat4 ProjMtx;\n" "attribute vec2 Position;\n" "attribute vec2 UV;\n" "attribute vec4 Color;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_130 = "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 UV;\n" "in vec4 Color;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_300_es = "precision mediump float;\n" "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_410_core = "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* fragment_shader_glsl_120 = "#ifdef GL_ES\n" " precision mediump float;\n" "#endif\n" "uniform sampler2D Texture;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_130 = "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_300_es = "precision mediump float;\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_410_core = "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "uniform sampler2D Texture;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; // Select shaders matching our GLSL versions const GLchar* vertex_shader = NULL; const GLchar* fragment_shader = NULL; if (glsl_version < 130) { vertex_shader = vertex_shader_glsl_120; fragment_shader = fragment_shader_glsl_120; } else if (glsl_version >= 410) { vertex_shader = vertex_shader_glsl_410_core; fragment_shader = fragment_shader_glsl_410_core; } else if (glsl_version == 300) { vertex_shader = vertex_shader_glsl_300_es; fragment_shader = fragment_shader_glsl_300_es; } else { vertex_shader = vertex_shader_glsl_130; fragment_shader = fragment_shader_glsl_130; } // Create shaders const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; g_VertHandle = glCreateShader(GL_VERTEX_SHADER); glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); glCompileShader(g_VertHandle); CheckShader(g_VertHandle, "vertex shader"); const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); glCompileShader(g_FragHandle); CheckShader(g_FragHandle, "fragment shader"); g_ShaderHandle = glCreateProgram(); glAttachShader(g_ShaderHandle, g_VertHandle); glAttachShader(g_ShaderHandle, g_FragHandle); glLinkProgram(g_ShaderHandle); CheckProgram(g_ShaderHandle, "shader program"); g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); // Create buffers glGenBuffers(1, &g_VboHandle); glGenBuffers(1, &g_ElementsHandle); ImGui_ImplOpenGL3_CreateFontsTexture(); // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array); #endif return true; } void ImGui_ImplOpenGL3_DestroyDeviceObjects() { if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } ImGui_ImplOpenGL3_DestroyFontsTexture(); } ================================================ FILE: graphics/gui/imgui/legacy_imgui_impl_opengl3.h ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 2.x 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. // About GLSL version: // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. #pragma once // Backend API IMGUI_IMPL_API bool ImGuiInitOpenGl(const char* glsl_version = NULL); IMGUI_IMPL_API void ImGuiShutdownOpenGL(); IMGUI_IMPL_API void ImGuiNewFrameOpenGL(); IMGUI_IMPL_API void ImGuiRenderDrawData(ImDrawData* draw_data); // (Optional) Called by Init/NewFrame/Shutdown IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); // Specific OpenGL versions //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android // Desktop OpenGL: attempt to detect default GL loader based on available header files. // If auto-detection fails or doesn't select the same GL loader file as used by your application, // you are likely to get a crash in ImGui_ImplOpenGL3_Init(). // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) #if defined(__has_include) #if __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GLEW #elif __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GLAD #elif __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GL3W #else #error "Cannot detect OpenGL loader!" #endif #else #define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W #endif #endif ================================================ FILE: graphics/gui/orderedVector.h ================================================ #pragma once #include template struct OrderedVector { private: std::vector elements; public: inline typename std::vector::const_iterator begin() const { return elements.begin(); } inline typename std::vector::const_iterator end() const { return elements.end(); } inline typename std::vector::const_reverse_iterator rbegin() const { return elements.rbegin(); } inline typename std::vector::const_reverse_iterator rend() const { return elements.rend(); } inline void insert(const T& element) { elements.insert(begin(), element); } inline void add(const T& element) { elements.push_back(element); } inline void remove(typename std::vector::const_iterator iterator) { elements.erase(iterator); } inline void remove(const T& element) { for (auto iterator = begin(); iterator != end(); ++iterator) { if (&element == &*iterator) elements.erase(iterator); } } inline void select(const T& element) { for (auto iterator = begin(); iterator != end(); ++iterator) { if (element == *iterator) { auto resultValue = *iterator; elements.erase(iterator); elements.insert(begin(), resultValue); return; } } } inline size_t size() const { return elements.size(); } T operator[](size_t index) const { return elements[index]; } }; ================================================ FILE: graphics/legacy/button.cpp ================================================ #include "core.h" #include "button.h" #include "texture.h" #include "font.h" #include "path/path.h" #include "mesh/primitive.h" Button::Button(double x, double y, double width, double height, bool textured) : Component(x, y, width, height) { this->textured = textured; this->borderColor = GUI::borderColor; this->padding = GUI::padding; this->margin = GUI::margin; if (textured) this->borderWidth = 0; else this->borderWidth = GUI::borderWidth; } Button::Button(std::string text, double x, double y, bool textured) : Component(x, y) { this->fontColor = GUI::fontColor; this->fontSize = GUI::fontSize; this->textured = textured; this->padding = GUI::padding; this->margin = GUI::margin; this->text = text; this->borderColor = GUI::borderColor; if (textured) this->borderWidth = 0; else this->borderWidth = GUI::borderWidth; } void Button::render() { if (visible) { resize(); Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; Vec2f buttonPosition = position; Vec2f buttonDimension = dimension; if (borderWidth > 0) { Path::rectFilled(buttonPosition, buttonDimension, 0, COLOR::blend(borderColor, blendColor)); buttonPosition = position + Vec2f(borderWidth, -borderWidth); buttonDimension = dimension - Vec2f(borderWidth) * 2; } if (pressed) if (textured) Path::rectUV(pressTexture->getID(), position, dimension); else Path::rectFilled(position, dimension, 0, COLOR::blend(pressColor, blendColor)); else if (hovering) if (textured) Path::rectUV(hoverTexture->getID(), position, dimension); else Path::rectFilled(position, dimension, 0, COLOR::blend(hoverColor, blendColor)); else if (textured) Path::rectUV(idleTexture->getID(), position, dimension); else Path::rectFilled(position, dimension, 0, COLOR::blend(idleColor, blendColor)); if (!text.empty()) Path::text(GUI::font, text, fontSize, position + Vec2(borderWidth, -borderWidth), COLOR::blend(fontColor, blendColor)); } } Vec2 Button::resize() { if (resizing) { dimension = GUI::font->size(text, fontSize) + Vec2(borderWidth) * 2; } return dimension; } void Button::enter() { if (disabled) return; hovering = true; } void Button::exit() { if (disabled) return; hovering = false; pressed = false; } void Button::press(Vec2 point) { if (disabled) return; pressed = true; } void Button::release(Vec2 point) { if (disabled) return; pressed = false; (*action)(this); } void Button::setColor(const Color& color) { idleColor = color; pressColor = color; hoverColor = color; } ================================================ FILE: graphics/legacy/button.h ================================================ #pragma once #include "component.h" class Button; class Texture; typedef void (*ButtonAction) (Button*); class Button : public Component { public: ButtonAction action = [] (Button*) {}; Texture* hoverTexture = nullptr; Texture* idleTexture = nullptr; Texture* pressTexture = nullptr; std::string text; Color fontColor; double fontSize; bool textured = false; bool hovering = false; bool pressed = false; Color hoverColor; Color idleColor; Color pressColor; Color borderColor; double borderWidth; void setColor(const Color& color); Button(double x, double y, double width, double height, bool textured); Button(std::string text, double x, double y, bool textured); void render() override; Vec2 resize() override; void press(Vec2 point) override; void release(Vec2 point) override; void enter() override; void exit() override; }; ================================================ FILE: graphics/legacy/checkBox.cpp ================================================ #include "core.h" #include "checkBox.h" #include "label.h" #include "path/path.h" #include "texture.h" #include "renderUtils.h" #include "mesh/primitive.h" CheckBox::CheckBox(std::string text, double x, double y, double width, double height, bool textured) : Component(x, y, width, height) { this->label = new Label(text, x, y); this->checkBoxLabelOffset = GUI::checkBoxLabelOffset; this->textured = textured; this->padding = GUI::padding; this->margin = GUI::margin; if (textured) { this->checkedTexture = GUI::checkBoxCheckedTexture; this->uncheckedTexture = GUI::checkBoxUncheckedTexture; this->hoverCheckedTexture = GUI::checkBoxHoverCheckedTexture; this->hoverUncheckedTexture = GUI::checkBoxHoverUncheckedTexture; this->pressCheckedTexture = GUI::checkBoxPressCheckedTexture; this->pressUncheckedTexture = GUI::checkBoxPressUncheckedTexture; } else { // TODO colors } } CheckBox::CheckBox(std::string text, double x, double y, bool textured) : CheckBox(text, x, y, GUI::checkBoxSize + 2 * padding, GUI::checkBoxSize + 2 * padding, textured) { } CheckBox::CheckBox(double x, double y, bool textured) : CheckBox("", x, y, GUI::checkBoxSize + 2 * padding, GUI::checkBoxSize + 2 * padding, textured) { } CheckBox::CheckBox(double x, double y, double width, double height, bool textured) : CheckBox("", x, y, width, height, textured) { } void CheckBox::render() { if (visible) { resize(); Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; Vec2f checkBoxPosition; Vec2f checkBoxDimension; if (label->text.empty()) { // No text, resizing using default width checkBoxPosition = position + Vec2(padding, -padding); checkBoxDimension = Vec2(GUI::checkBoxSize); } else { if (label->dimension.y > GUI::checkBoxSize) { // Label defines component size, checkbox is centered vertically checkBoxPosition = position + Vec2(padding, -padding - (label->dimension.y - GUI::checkBoxSize) / 2); checkBoxDimension = Vec2(GUI::checkBoxSize); } else { // Checkbox defines component size checkBoxPosition = position + Vec2(padding, -padding); checkBoxDimension = Vec2(GUI::checkBoxSize); } } if (pressed) if (textured) if (checked) Path::rectUV(pressCheckedTexture->getID(), checkBoxPosition, checkBoxDimension); else Path::rectUV(pressUncheckedTexture->getID(), checkBoxPosition, checkBoxDimension); else if (checked) Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(pressCheckedColor, blendColor)); else Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(pressUncheckedColor, blendColor)); else if (hovering) if (textured) if (checked) Path::rectUV(checkedTexture->getID(), checkBoxPosition, checkBoxDimension); else Path::rectUV(uncheckedTexture->getID(), checkBoxPosition, checkBoxDimension); else if (checked) Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(checkedColor, blendColor)); else Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(uncheckedColor, blendColor)); else if (textured) if (checked) Path::rectUV(hoverCheckedTexture->getID(), checkBoxPosition, checkBoxDimension); else Path::rectUV(hoverUncheckedTexture->getID(), checkBoxPosition, checkBoxDimension); else if (checked) Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(hoverCheckedColor, blendColor)); else Path::rectFilled(checkBoxPosition, checkBoxPosition, 0.0f, COLOR::blend(hoverUncheckedColor, blendColor)); if (!label->text.empty()) { Vec2 labelPosition; if (label->dimension.y > GUI::checkBoxSize) { // Label defines the size of the component labelPosition = position + Vec2(padding + checkBoxDimension.x + checkBoxLabelOffset, -padding); } else { // Label is centered vertically labelPosition = position + Vec2(padding + checkBoxDimension.x + checkBoxLabelOffset, -padding - (GUI::checkBoxSize - label->dimension.y) / 2); } label->position = labelPosition; label->render(); } if (debug) { Path::rect(position, dimension, 0.0f, COLOR::RED); Path::rect(checkBoxPosition, checkBoxDimension, 0.0f, COLOR::GREEN); } } } Vec2 CheckBox::resize() { if (!label->text.empty()) { label->resize(); if (label->dimension.y > GUI::checkBoxSize) { // Label defines dimension dimension = label->dimension + Vec2(GUI::checkBoxSize + checkBoxLabelOffset, 0) + Vec2(padding) * 2; } else { // Checkbox defines dimension dimension = Vec2(GUI::checkBoxSize + checkBoxLabelOffset + label->width, GUI::checkBoxSize) + Vec2(padding) * 2; } } return dimension; } void CheckBox::disable() { disabled = true; label->disable(); } void CheckBox::enable() { disabled = false; label->enable(); } void CheckBox::enter() { if (disabled) return; hovering = true; } void CheckBox::exit() { if (disabled) return; hovering = false; pressed = false; } void CheckBox::press(Vec2 point) { if (disabled) return; pressed = true; } void CheckBox::release(Vec2 point) { if (disabled) return; pressed = false; checked = !checked; (*action)(this); } ================================================ FILE: graphics/legacy/checkBox.h ================================================ #pragma once #include "component.h" class CheckBox; class Texture; class Label; typedef void (*CheckBoxAction) (CheckBox*); class CheckBox : public Component { public: CheckBoxAction action = [] (CheckBox*) {}; Texture* pressUncheckedTexture = nullptr; Texture* pressCheckedTexture = nullptr; Texture* hoverUncheckedTexture = nullptr; Texture* hoverCheckedTexture = nullptr; Texture* uncheckedTexture = nullptr; Texture* checkedTexture = nullptr; bool textured = false; bool hovering = false; bool pressed = false; bool checked = false; Color pressUncheckedColor; Color pressCheckedColor; Color hoverUncheckedColor; Color hoverCheckedColor; Color uncheckedColor; Color checkedColor; double checkOffset; double checkBoxLabelOffset; Label* label; CheckBox(double x, double y, bool textured); CheckBox(std::string text, double x, double y, bool textured); CheckBox(double x, double y, double width, double height, bool textured); CheckBox(std::string text, double x, double y, double width, double height, bool textured); void render() override; Vec2 resize() override; void disable() override; void enable() override; void press(Vec2 point) override; void release(Vec2 point) override; void enter() override; void exit() override; }; ================================================ FILE: graphics/legacy/colorPicker.cpp ================================================ #include "core.h" #include "colorPicker.h" #include "guiUtils.h" #include "texture.h" #include "path/path.h" #include "mesh/primitive.h" ColorPicker::ColorPicker(double x, double y, double size) : Component(x, y, size, size) { this->padding = GUI::padding; this->margin = GUI::margin; this->background = COLOR::BACK; dimension = Vec2( padding + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + GUI::colorPickerSpacing + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + GUI::colorPickerSpacing + size + GUI::colorPickerSpacing + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + padding, padding + size + padding ); setRgba(Vec4(1)); } ColorPicker::ColorPicker(double x, double y) : Component(x, y) { this->background = COLOR::BACK; this->padding = GUI::padding; this->margin = GUI::margin; dimension = Vec2( padding + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + GUI::colorPickerSpacing + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + GUI::colorPickerSpacing + GUI::colorPickerHueSize + GUI::colorPickerSpacing + GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth + padding, padding + GUI::colorPickerHueSize + padding ); setRgba(Vec4(1)); }; void ColorPicker::render() { if (visible) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); Vec4 blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; // Brightness Vec2f brightnessBorderPosition = position + Vec2(padding, -padding); Vec2f brightnessBorderDimension = Vec2(GUI::colorPickerBarWidth + 2 * GUI::colorPickerBarBorderWidth, height - 2 * padding); Path::rectFilled(brightnessBorderPosition, brightnessBorderDimension, 0.0f, COLOR::blend(GUI::colorPickerBarBorderColor, blendColor)); Vec2f brightnessPosition = brightnessBorderPosition + Vec2(GUI::colorPickerBarBorderWidth, -GUI::colorPickerBarBorderWidth); Vec2f brightnessDimension = Vec2(GUI::colorPickerBarWidth, brightnessBorderDimension.y - 2 * GUI::colorPickerBarBorderWidth); Path::rectUV(GUI::colorPickerBrightnessTexture->getID(), brightnessPosition, brightnessDimension, Vec2f(0), Vec2f(1), COLOR::blend(COLOR::hsvaToRgba(Vec4(hsva.x, hsva.y, 1, 1)), blendColor)); Vec2f brightnessSelectorPosition = brightnessPosition + Vec2((GUI::colorPickerBarWidth - GUI::colorPickerSelectorWidth) / 2, -(1 - hsva.z) * brightnessDimension.y + GUI::colorPickerSelectorHeight / 2); Vec2f brightnessSelectorDimension = Vec2(GUI::colorPickerSelectorWidth, GUI::colorPickerSelectorHeight); Path::rectFilled(brightnessSelectorPosition, brightnessSelectorDimension, 0.0f, COLOR::blend(GUI::colorPickerSelectorColor, blendColor)); // Alpha Vec2f alphaBorderPosition = brightnessBorderPosition + Vec2(brightnessBorderDimension.x + GUI::colorPickerSpacing, 0); Vec2f alphaBorderDimension = brightnessBorderDimension; Path::rectFilled(alphaBorderPosition, alphaBorderDimension, 0.0f, COLOR::blend(GUI::colorPickerBarBorderColor, blendColor));; Vec2f alphaPosition = alphaBorderPosition + Vec2(GUI::colorPickerBarBorderWidth, -GUI::colorPickerBarBorderWidth); Vec2f alphaDimension = brightnessDimension; Path::rectUV(GUI::colorPickerAlphaPatternTexture->getID(), alphaPosition, alphaDimension); Path::rectUV(GUI::colorPickerAlphaBrightnessTexture->getID(), alphaPosition, alphaDimension, Vec2f(0), Vec2f(1), COLOR::blend(COLOR::hsvaToRgba(Vec4(hsva.x, hsva.y, hsva.z, 1)), blendColor)); Vec2f alphaSelectorPosition = alphaPosition + Vec2((GUI::colorPickerBarWidth - GUI::colorPickerSelectorWidth) / 2, -(1 - hsva.w) * brightnessDimension.y + GUI::colorPickerSelectorHeight / 2); Vec2f alphaSelectorDimension = brightnessSelectorDimension; Path::rectFilled(alphaSelectorPosition, alphaSelectorDimension, 0.0f, COLOR::blend(GUI::colorPickerSelectorColor, blendColor)); // Hue Vec2f huePosition = Vec2(alphaBorderPosition.x + alphaBorderDimension.x + GUI::colorPickerSpacing, alphaBorderPosition.y); Vec2f hueDimension = Vec2(alphaBorderDimension.y); Path::rectUV(GUI::colorPickerHueTexture->getID(), huePosition, hueDimension, Vec2f(0), Vec2f(1), blendColor); Vec2f crosshairPosition = position + crosshair + Vec2(-GUI::colorPickerCrosshairSize, GUI::colorPickerCrosshairSize) / 2; Vec2f crosshairDimension = Vec2(GUI::colorPickerCrosshairSize); Path::rectUV(GUI::colorPickerCrosshairTexture->getID(), crosshairPosition, crosshairDimension, Vec2f(0), Vec2f(1), blendColor); // Color Vec2f colorBorderPosition = Vec2(huePosition.x + hueDimension.x + GUI::colorPickerSpacing, brightnessBorderPosition.y); Vec2f colorBorderDimension = brightnessBorderDimension; Path::rectFilled(colorBorderPosition, colorBorderDimension, 0.0f, COLOR::blend(GUI::colorPickerBarBorderColor, blendColor)); Vec2f colorPosition = colorBorderPosition + Vec2(GUI::colorPickerBarBorderWidth, -GUI::colorPickerBarBorderWidth); Vec2f colorDimension = brightnessDimension; Path::rectUV(GUI::colorPickerAlphaPatternTexture->getID(), colorPosition, colorDimension); Path::rectFilled(colorPosition, colorDimension, 0.0f, COLOR::blend(COLOR::hsvaToRgba(hsva), blendColor)); } } Vec2 ColorPicker::resize() { return dimension; } void ColorPicker::setRgba(Color rgba) { hsva = COLOR::rgbaToHsva(rgba); double radius = height / 2 - padding; Vec2 relativeCenter = Vec2(padding + 2 * GUI::colorPickerBarWidth + 2 * GUI::colorPickerSpacing + 2 * 2 * GUI::colorPickerBarBorderWidth + radius, -height / 2); double angle = -(hsva.x - 0.25) * 2 * 3.14159265359; crosshair = relativeCenter + Vec2(cos(angle), sin(angle)) * hsva.y * radius; } Color ColorPicker::getRgba() { return COLOR::hsvaToRgba(hsva); } void ColorPicker::press(Vec2 point) { if (disabled) return; // BrightnessPicker Vec2 brightnessPosition = position + Vec2(padding + GUI::colorPickerBarBorderWidth, -GUI::colorPickerBarBorderWidth - padding); Vec2 brightnessDimension = Vec2(GUI::colorPickerBarWidth, height - 2 * padding - 2 * GUI::colorPickerBarBorderWidth); if (!colorPicking && !brightnessPicking && !alphaPicking) { brightnessPicking = GUI::intersectsSquare(point, brightnessPosition, brightnessDimension); } if (brightnessPicking) { double leftBottomY = brightnessPosition.y - brightnessDimension.y; double relativeY = point.y - leftBottomY; hsva.z = GUI::clamp(relativeY / brightnessDimension.y, 0, 1); (*action)(this); } // AlphaPicker Vec2 alphaPosition = brightnessPosition + Vec2(brightnessDimension.x + GUI::colorPickerSpacing + GUI::colorPickerBarBorderWidth, 0); Vec2 alphaDimension = brightnessDimension; if (!colorPicking && !brightnessPicking && !alphaPicking) { alphaPicking = GUI::intersectsSquare(point, alphaPosition, alphaDimension); } if (alphaPicking) { double leftBottomY = alphaPosition.y - alphaDimension.y; double relativeY = point.y - leftBottomY; hsva.w = GUI::clamp(relativeY / alphaDimension.y, 0, 1); (*action)(this); } // ColorPicker double radius = height / 2 - padding; Vec2 center = position + Vec2(padding + 2 * GUI::colorPickerBarWidth + 2 * GUI::colorPickerSpacing + 2 * 2 * GUI::colorPickerBarBorderWidth + radius, -height / 2); Vec2 pointer = (point - center); double l = length(pointer); if (!colorPicking && !brightnessPicking && !alphaPicking) { colorPicking = l <= radius; } if (colorPicking) { if (l > radius) { pointer = pointer / l * radius; point = center + pointer; l = radius; } crosshair = point - position; double hue = fmod(atan2(-pointer.y, pointer.x) / (2 * 3.14159265359) + 1.25, 1); double saturation = l / radius; hsva = Vec4(hue, saturation, hsva.z, hsva.w); (*action)(this); } } void ColorPicker::drag(Vec2 newPoint, Vec2 oldPoint) { if (disabled) return; press(newPoint); } void ColorPicker::release(Vec2 point) { if (disabled) return; colorPicking = false; brightnessPicking = false; alphaPicking = false; } ================================================ FILE: graphics/legacy/colorPicker.h ================================================ #pragma once #include "component.h" class ColorPicker; class Texture; typedef void (*ColorPickerAction) (ColorPicker*); class ColorPicker : public Component { private: Vec2 crosshair; bool colorPicking; bool brightnessPicking; bool alphaPicking; public: ColorPickerAction action = [] (ColorPicker*) {}; Color hsva; Color background; Component* focus = nullptr; ColorPicker(double x, double y, double size); ColorPicker(double x, double y); void setRgba(Color rgba); Color getRgba(); Vec2 resize() override; void render() override; void press(Vec2 point) override; void drag(Vec2 newPoint, Vec2 oldPoint) override; void release(Vec2 point) override; }; ================================================ FILE: graphics/legacy/component.h ================================================ #pragma once #include "core.h" #include "layout.h" #include "gui.h" namespace P3D::Graphics { class Component { public: /* Alignment of the component within its current layout FLOW: FILL: The component fills up the remaining width of the parent container RELATIVE: The components are placed next to eachother, filling up the container CENTER: The same as fill but the filled components are centered */ Align align = Align::RELATIVE; /* Parent of this component */ Component* parent = nullptr; /* Determines if the component has a fixed size */ bool resizing = true; /* Determines if the component is disabled, the component will still be rendered, but will not be interactable. */ bool disabled = false; /* The topleft edge of the container, margin not included, padding included This property can be altered by its parent */ union { struct { double x; double y; }; Vec2 position; }; /* The size of the container, margin not included, padding included This property can be altered by its parent */ union { struct { float width; float height; }; Vec2f dimension; }; /* Padding of this component */ double padding = GUI::padding; /* Margin of this component */ double margin = GUI::margin; /* Determines if this component and its content should be rendered */ bool visible = true; /* Debug */ bool debug = false; /* Constructors */ Component(Vec2 position, Vec2f dimension) : position(position), dimension(dimension), resizing(false) {}; Component(Vec2 position) : position(position), dimension(Vec2f(0.0f, 0.0f)), resizing(true) {}; Component(double x, double y, float width, float height) : Component(Vec2(x, y), Vec2f(width, height)) {}; Component(double x, double y) : Component(Vec2(x, y)) {}; /* Returns this if the component contains the point */ virtual Component* intersect(Vec2 point) { Vec2f halfDimension = dimension / 2; Vec2 center = position + Vec2f(halfDimension.x, -halfDimension.y); if (std::abs(point.x - center.x) < halfDimension.x && std::abs(point.y - center.y) < halfDimension.y) return this; return nullptr; } /* Returns the minimal size of the container, margin not included, padding included */ virtual Vec2 resize() = 0; /* Renders the component, resizing may happen */ virtual void render() = 0; /* Disables the component and all its children */ virtual void disable() { disabled = true; }; /* Enables the component and all its children */ virtual void enable() { disabled = false; }; /* Drag behaviour of this component */ virtual void drag(Vec2 mxyNew, Vec2 mxyOld) {}; /* Hover behaviour of this component */ virtual void hover(Vec2 mxy) {}; /* Press behaviour of this component */ virtual void press(Vec2 mxy) {}; /* Release behaviour of this component */ virtual void release(Vec2 mxy) {} /* Enter behaviour of this component */ virtual void enter() {}; /* Exit behaviour of this component */ virtual void exit() {}; }; }; ================================================ FILE: graphics/legacy/container.cpp ================================================ #include "core.h" #include "container.h" #include "layout.h" #include "path/path.h" #include "mesh/primitive.h" namespace P3D::Graphics { Container::Container(Vec2 position) : Component(position), layout(new FlowLayout()) { }; Container::Container(Vec2 position, Vec2 dimension) : Component(position, dimension), layout(new FlowLayout()) { }; Container::Container(double x, double y, double width, double height) : Container(Vec2(x, y), Vec2(width, height)) { }; Container::Container(double x, double y) : Container(Vec2(x, y)) { }; void Container::add(Component* child) { add(child, Align::RELATIVE); } void Container::add(Component* child, Align alignment) { child->parent = this; children.add(std::make_pair(child, alignment)); } std::pair Container::get(Component* child) { for (auto iterator = children.begin(); iterator != children.end(); ++iterator) { if (child == iterator->first) return *iterator; } throw "Unreachable"; } void Container::remove(Component* child) { for (auto iterator = children.begin(); iterator != children.end(); ++iterator) { if (child == iterator->first) { children.remove(iterator); return; } } } Component* Container::intersect(Vec2 point) { for (auto iterator = children.begin(); iterator != children.end(); ++iterator) { if (iterator->first->intersect(point)) return iterator->first; } return this->Component::intersect(point); } void Container::renderChildren() { for (auto child : children) { child.first->render(); if (debug) { Path::rect(child.first->position + Vec2(-margin, margin), child.first->dimension + Vec2f::full(float(margin) * 2), 0, COLOR::RGB_R); Path::rect(child.first->position, child.first->dimension, 0, COLOR::RGB_G); Path::rect(child.first->position + Vec2(padding, -padding), child.first->dimension + Vec2f::full(-float(padding) * 2), 0, COLOR::RGB_B); } } } }; ================================================ FILE: graphics/legacy/container.h ================================================ #pragma once #include "orderedVector.h" #include "component.h" namespace P3D::Graphics { class Container : public Component { public: /* Layout of the container's children FLOW: The children are placed next to eachother until they fill the width of the container, much like lines of text in a paragraph */ Layout* layout; /* Children of this container, ordered by z-index */ OrderedVector> children; /* Constructors */ Container(Vec2 position); Container(Vec2 position, Vec2 dimension); Container(double x, double y, double width, double height); Container(double x, double y); /* Adds the child to the end of the container */ void add(Component* child); /* Adds the child to the end of the container with a given alignment */ void add(Component* child, Align alignment); /* Returns the child with its alignment */ std::pair get(Component* child); /* Removes the given child from the children */ void remove(Component* child); /* Returns the intersected component at the given point. */ virtual Component* intersect(Vec2 point); /* Renders the children of the container */ virtual void renderChildren(); virtual void render() = 0; virtual Vec2 resize() = 0; }; } ================================================ FILE: graphics/legacy/cshader.cpp ================================================ #include "core.h" #include #include "cshader.h" #include "renderer.h" #include "debug/guiDebug.h" #include #include #include #include "../util/stringUtil.h" #include "../util/fileUtils.h" namespace P3D::Graphics { #pragma region compile GLID CShader::createShader(const ShaderSource& shaderSource) { GLID program = glCreateProgram(); Log::subject s(shaderSource.name); Log::info("Compiling shader (%s)", shaderSource.name.c_str()); GLID cs = 0; cs = Shader::compile("Compute shader", shaderSource.computeSource, GL_VERTEX_SHADER); glAttachShader(program, cs); glCall(glLinkProgram(program)); glCall(glValidateProgram(program)); glDeleteShader(cs); Log::info("Created shader with id (%d)", program); return program; } #pragma endregion #pragma region constructors CShader::CShader() {}; CShader::CShader(const ShaderSource& shaderSource) : Shader(shaderSource) { id = createShader(shaderSource); bool result = addShaderStage(shaderSource.computeSource, COMPUTE); }; CShader::CShader(const std::string& name, const std::string& path, const std::string& computeSource) : CShader(ShaderSource(name, path, "", "", "", "", "", computeSource)) {} CShader::~CShader() { close(); } CShader::CShader(CShader&& other) { // TODO } CShader& CShader::operator=(CShader&& other) { if (this != &other) { close(); } return *this; } #pragma endregion bool CShader::addShaderStage(const std::string& source, const ShaderFlag& flag) { ShaderStage stage(source); if (!stage.source.empty()) { addUniforms(stage); flags |= flag; switch (flag) { case COMPUTE: computeStage = stage; break; default: break; } } return true; } void CShader::close() { Shader::close(); } void CShader::dispatch(unsigned int x, unsigned int y, unsigned int z) { glDispatchCompute(x, y, z); } void CShader::barrier(unsigned int flags) { glMemoryBarrier(flags); } }; ================================================ FILE: graphics/legacy/cshader.h ================================================ #pragma once #include "shader.h" namespace P3D::Graphics { class CShader : public Shader { private: GLID createShader(const ShaderSource& shaderSource); protected: bool addShaderStage(const std::string& source, const ShaderFlag& flag) override; public: ShaderStage computeStage; CShader(); CShader(const ShaderSource& shaderSource); CShader(const std::string& name, const std::string& path, const std::string& computeShader); ~CShader(); CShader(CShader&& other); CShader(const CShader&) = delete; CShader& operator=(CShader&& other); CShader& operator=(const CShader&) = delete; void close() override; void dispatch(unsigned int x, unsigned int y, unsigned int z); void barrier(unsigned int flags); }; ShaderSource parseShader(const std::string& name, const std::string& path, const std::string& computePath); }; ================================================ FILE: graphics/legacy/directionEditor.cpp ================================================ #include "core.h" #include "directionEditor.h" #include "../physics/math/cframe.h" #include "../physics/math/mathUtil.h" #include "../physics/math/globalCFrame.h" #include "gui.h" #include "texture.h" #include "renderUtils.h" #include "path/path.h" #include "buffers/frameBuffer.h" #include "mesh/indexedMesh.h" #include "mesh/primitive.h" #include "meshLibrary.h" DirectionEditor::DirectionEditor(double x, double y, double width, double height) : Component(x, y, width, height) { viewPosition = Position(0.0, 0.0, -3.0); viewMatrix = CFrameToMat4(GlobalCFrame(viewPosition)); modelMatrix = Matrix::IDENTITY(); //.scale(4, 0.5, 4); rspeed = 10; } void DirectionEditor::render() { if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; // Draw onto gui framebuffer GUI::guiFrameBuffer->bind(); Renderer::enableDepthTest(); Renderer::clearColor(); Renderer::clearDepth(); Library::vector->render(); GUI::guiFrameBuffer->unbind(); Renderer::disableDepthTest(); Path::rect(position, dimension, 0.0f, COLOR::blend(COLOR::BACK, blendColor)); Vec2 contentPosition = position + Vec2(GUI::padding, -GUI::padding); Vec2 contentDimension = dimension - Vec2(GUI::padding) * 2; Path::rectUV(GUI::guiFrameBuffer->texture->getID(), contentPosition, contentDimension, Vec2f(0), Vec2f(1), blendColor); } } void DirectionEditor::rotate(double dalpha, double dbeta, double dgamma) { modelMatrix = Mat4f(Matrix(rotX(float(dalpha)) * Mat3f(modelMatrix.getSubMatrix<3, 3>(0,0)) * rotZ(float(-dbeta))), 1.0f); (*action)(this); } Vec2 DirectionEditor::resize() { return dimension; } void DirectionEditor::drag(Vec2 newPoint, Vec2 oldPoint) { if (disabled) return; Vec2 dmxy = newPoint - oldPoint; rotate(dmxy.y * rspeed, dmxy.x * rspeed, 0); } ================================================ FILE: graphics/legacy/directionEditor.h ================================================ #pragma once #include "component.h" class DirectionEditor; typedef void (*DirectionEditorAction) (DirectionEditor*); class DirectionEditor : public Component { private: Mat4f viewMatrix; Position viewPosition; double rspeed; void rotate(double dalpha, double dbeta, double dgamma); public: Mat4f modelMatrix; DirectionEditorAction action = [] (DirectionEditor*) {}; DirectionEditor(double x, double y, double width, double height); void render() override; Vec2 resize() override; void drag(Vec2 newPoint, Vec2 oldPoint) override; }; ================================================ FILE: graphics/legacy/frame.cpp ================================================ #include "core.h" #include "frame.h" #include "path/path.h" #include "button.h" #include "label.h" #include "texture.h" #include "renderUtils.h" #include "guiUtils.h" #include "mesh/primitive.h" #include "buffers/frameBuffer.h" #include "../physics/math/mathUtil.h" Frame::Frame() : Frame(0, 0) { }; Frame::Frame(double x, double y, std::string name) : Container(x, y) { this->backgroundColor = GUI::frameBackgroundColor; this->titleBarColor = GUI::frameTitleBarColor; this->titleBarHeight = GUI::frameTitleBarHeight; this->buttonOffset = GUI::frameButtonOffset; this->closeButton = new Button(position.x, position.y, titleBarHeight - 2 * buttonOffset, titleBarHeight - 2 * buttonOffset, true); this->closeButton->parent = this; this->closeButton->idleTexture = GUI::closeButtonIdleTexture; this->closeButton->hoverTexture = GUI::closeButtonHoverTexture; this->closeButton->pressTexture = GUI::closeButtonPressTexture; this->closeButton->action = [] (Button* button) { button->parent->visible = false; }; this->minimizeButton = new Button(position.x, position.y, titleBarHeight - 2 * buttonOffset, titleBarHeight - 2 * buttonOffset, true); this->minimizeButton->parent = this; this->minimizeButton->idleTexture = GUI:: minimizeButtonIdleTexture; this->minimizeButton->hoverTexture = GUI::minimizeButtonHoverTexture; this->minimizeButton->pressTexture = GUI::minimizeButtonPressTexture; this->minimizeButton->action = [] (Button* button) { Frame* frame = static_cast(button->parent); frame->minimized = !frame->minimized; }; title = new Label(name, position.x, position.y); anchor = nullptr; }; Frame::Frame(double x, double y, double width, double height, std::string name) : Container(x, y, width, height) { this->padding = GUI::padding; this->margin = GUI::margin; this->backgroundColor = GUI::frameBackgroundColor; this->titleBarColor = GUI::frameTitleBarColor; this->titleBarHeight = GUI::frameTitleBarHeight; this->buttonOffset = GUI::frameButtonOffset; this->closeButton = new Button(position.x, position.y, titleBarHeight - 2 * buttonOffset, titleBarHeight - 2 * buttonOffset, true); this->closeButton->parent = this; this->closeButton->idleTexture = GUI::closeButtonIdleTexture; this->closeButton->hoverTexture = GUI::closeButtonHoverTexture; this->closeButton->pressTexture = GUI::closeButtonPressTexture; this->closeButton->action = [] (Button* button) { button->parent->visible = false; }; this->minimizeButton = new Button(position.x, position.y, titleBarHeight - 2 * buttonOffset, titleBarHeight - 2 * buttonOffset, true); this->minimizeButton->parent = this; this->minimizeButton->idleTexture = GUI::minimizeButtonIdleTexture; this->minimizeButton->hoverTexture = GUI::minimizeButtonHoverTexture; this->minimizeButton->pressTexture = GUI::minimizeButtonPressTexture; this->minimizeButton->action = [] (Button* button) { Frame* frame = (Frame*) button->parent; frame->minimized = !frame->minimized; }; title = new Label(name, position.x, position.y); anchor = nullptr; }; Vec2 Frame::resize() { if (anchor) position = Vec2(anchor->position.x + anchor->dimension.x + 2 * GUI::padding, anchor->position.y); // Button closeButton->position = position + Vec2(width - titleBarHeight + buttonOffset, -buttonOffset); minimizeButton->position = position + Vec2(width - 2 * titleBarHeight + buttonOffset, -buttonOffset); // Title if (!title->text.empty()) { title->resize(); double yOffset = (titleBarHeight - title->height) / 2; title->position = position + Vec2(yOffset, -yOffset); } // Content if (!minimized) { Vec2 positionOffset = Vec2(padding, -padding - titleBarHeight); Vec2 dimensionOffset = Vec2(2 * padding, 2 * padding + titleBarHeight); position += positionOffset; dimension -= dimensionOffset; if (title->text.empty()) dimension = layout->resize(this); else dimension = layout->resize(this, Vec2(title->dimension.x + (titleBarHeight - title->height) / 2 + 2 * titleBarHeight, 0)); position -= positionOffset; dimension += dimensionOffset; } else { dimension = Vec2(dimension.x, titleBarHeight); } return dimension; } Component* Frame::intersect(Vec2 point) { if (closeButton->intersect(point)) return closeButton; if (minimizeButton->intersect(point)) return minimizeButton; if (!minimized) { for (auto component : children) { if (component.first->intersect(point)) return component.first; } } if (GUI::intersectsSquare(point, position, dimension)) { return this; } return nullptr; } void Frame::disable() { disabled = true; minimizeButton->disable(); closeButton->disable(); title->disable(); for (auto component : children) component.first->disable(); } void Frame::enable() { disabled = false; minimizeButton->enable(); closeButton->enable(); title->enable(); for (auto component : children) component.first->enable(); } void Frame::press(Vec2 point) { if (GUI::between(point.x - x, 0, GUI::frameResizeHandleSize)) { resizeFlags |= resizingW; resizing = false; } if (GUI::between(point.y - y, -GUI::frameResizeHandleSize, 0)) { resizeFlags |= resizingN; resizing = false; } if (GUI::between(point.x - x - width, -GUI::frameResizeHandleSize, 0)) { resizeFlags |= resizingE; resizing = false; } if (GUI::between(point.y - y + height, 0, GUI::frameResizeHandleSize)) { resizeFlags |= resizingS; resizing = false; } } void Frame::release(Vec2 point) { resizeFlags = None; } void Frame::drag(Vec2 newPoint, Vec2 oldPoint) { if (disabled) return; if (resizeFlags != None) { // component resizing should be off right now if (resizeFlags & resizingE) { width = GUI::clamp(newPoint.x - x, title->width, 10); } else if (resizeFlags & resizingW) { width = GUI::clamp(x - newPoint.x + width, title->width, 10); x = newPoint.x; } if (resizeFlags & resizingS) { height = GUI::clamp(y - newPoint.y, titleBarHeight, 10); } else if (resizeFlags & resizingN) { height = GUI::clamp(newPoint.y - y + height, titleBarHeight, 10); y = newPoint.y; } } else { position += newPoint - oldPoint; anchor = nullptr; } } void Frame::render() { if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; resize(); // TitleBar Vec2f titleBarPosition = position; Vec2f titleBarDimension = Vec2f(width, titleBarHeight); Path::rectFilled(titleBarPosition, titleBarDimension, 0, COLOR::blend(titleBarColor, blendColor)); // Buttons closeButton->render(); minimizeButton->render(); // Title if (!title->text.empty()) title->render(); if (!minimized) { // Padding Vec2f offsetPosition = titleBarPosition + Vec2f(0, -titleBarHeight); Vec2f offsetDimension = dimension + Vec2f(0, -titleBarHeight); Vec2f xRange = Vec2f(-GUI::windowInfo.aspect, GUI::windowInfo.aspect) * 2; Vec2f yRange = Vec2(-1, 1); Color color = COLOR::blend(Color(0.4, 0.4, 0.4, 1), blendColor); Path::rectUVRange(GUI::blurFrameBuffer->texture->getID(), offsetPosition, offsetDimension, xRange, yRange, color); renderChildren(); } } } ================================================ FILE: graphics/legacy/frame.h ================================================ #pragma once #include "component.h" #include "container.h" class Button; class Label; class Frame : public Container { private: enum ResizingFlags : char { // Not resizing None = 0 << 0, // Resizing using top handle resizingN = 1 << 0, // Resizing using right handle resizingE = 1 << 1, // Resizing using bottom handle resizingS = 1 << 2, // Resizing using left handle resizingW = 1 << 3 }; char resizeFlags = None; public: Component* anchor; Button* closeButton; Button* minimizeButton; double buttonOffset; Label* title; double titleBarHeight; Color titleBarColor; Color backgroundColor; bool minimized = false; Frame(); Frame(double x, double y, std::string title = ""); Frame(double x, double y, double width, double height, std::string title = ""); Vec2 resize() override; void render() override; Component* intersect(Vec2 point) override; void drag(Vec2 newPoint, Vec2 oldPoint) override; void press(Vec2 point) override; void release(Vec2 point) override; void disable() override; void enable() override; }; ================================================ FILE: graphics/legacy/gshader.cpp ================================================ #include "core.h" #include #include "gshader.h" #include "renderer.h" #include "debug/guiDebug.h" #include #include #include #include "../../util/systemVariables.h" #include "../util/fileUtils.h" namespace P3D::Graphics { #pragma region compile GLID GShader::createShader(const ShaderSource& shaderSource) { if (SystemVariables::get("OPENGL_SHADER_VERSION") < 330) { Log::print(Log::Color::ERROR, "shader version not supported\n"); Log::setDelimiter("\n"); return 0; } GLID program = glCreateProgram(); Log::subject s(shaderSource.name); Log::info("Compiling shader (%s)", shaderSource.name.c_str()); GLID vs = 0; GLID fs = 0; GLID gs = 0; GLID tcs = 0; GLID tes = 0; vs = Shader::compile("Vertex shader", shaderSource.vertexSource, GL_VERTEX_SHADER); glAttachShader(program, vs); fs = Shader::compile("Fragment shader", shaderSource.fragmentSource, GL_FRAGMENT_SHADER); glAttachShader(program, fs); if (!shaderSource.geometrySource.empty()) { gs = Shader::compile("Geometry shader", shaderSource.geometrySource, GL_GEOMETRY_SHADER); glAttachShader(program, gs); } if (!shaderSource.tesselationControlSource.empty()) { tcs = Shader::compile("Tesselation control shader", shaderSource.tesselationControlSource, GL_TESS_CONTROL_SHADER); glAttachShader(program, tcs); } if (!shaderSource.tesselationEvaluateSource.empty()) { tes = Shader::compile("Tesselation evaluate", shaderSource.tesselationEvaluateSource, GL_TESS_EVALUATION_SHADER); glAttachShader(program, tes); } glCall(glLinkProgram(program)); glCall(glValidateProgram(program)); glDeleteShader(vs); glDeleteShader(fs); if (!shaderSource.geometrySource.empty()) glDeleteShader(gs); if (!shaderSource.tesselationControlSource.empty()) glDeleteShader(tcs); if (!shaderSource.tesselationEvaluateSource.empty()) glDeleteShader(tes); Log::info("Created shader with id (%d)", program); return program; } void GShader::reload(const ShaderSource& shaderSource) { Log::subject s(name); Log::info("Reloading shader (%s)", name.c_str()); GShader shader(shaderSource); if (shader.id == 0) { Log::error("Reloading failed"); return; } unbind(); glDeleteProgram(id); this->id = shader.id; shader.id = 0; // Avoid deletion this->uniforms = shader.uniforms; this->vertexStage = shader.vertexStage; this->fragmentStage = shader.fragmentStage; this->geometryStage = shader.geometryStage; this->tesselationControlStage = shader.tesselationControlStage; this->tesselationEvaluationStage = shader.tesselationEvaluationStage; this->flags = shader.flags; this->name = shader.name; Log::info("Reloading succesful"); } #pragma endregion #pragma region parse ShaderSource parseShader(const std::string& name, const std::string& path, const std::string& vertexPath, const std::string& fragmentPath, const std::string& geometryPath, const std::string& tesselationControlPath, const std::string& tesselationEvaluatePath) { Log::subject s(name); std::string vertexFile = Util::parseFile(vertexPath); std::string fragmentFile = Util::parseFile(fragmentPath); std::string geometryFile = Util::parseFile(geometryPath); std::string tesselationControlFile = Util::parseFile(tesselationControlPath); std::string tesselationEvaluateFile = Util::parseFile(tesselationEvaluatePath); return ShaderSource(name, path, vertexFile, fragmentFile, geometryFile, tesselationControlFile, tesselationEvaluateFile, ""); } bool GShader::addShaderStage(const std::string& source, const ShaderFlag& flag) { ShaderStage stage(source); if (SystemVariables::get("OPENGL_SHADER_VERSION") < stage.info.version.version) { Log::error("Shader version %d %snot supported for shader %s", stage.info.version.version, stage.info.version.core ? "core " : "", name.c_str()); return false; } if (!stage.source.empty()) { addUniforms(stage); flags |= flag; switch (flag) { case VERTEX: vertexStage = stage; break; case FRAGMENT: fragmentStage = stage; break; case GEOMETRY: geometryStage = stage; break; case TESSELATION_CONTROL: tesselationControlStage = stage; break; case TESSELATION_EVALUATE: tesselationEvaluationStage = stage; break; default: break; } } return true; } #pragma endregion #pragma region constructors GShader::GShader() {}; GShader::GShader(const ShaderSource& shaderSource) : Shader(shaderSource) { id = createShader(shaderSource); bool succes = true; succes &= addShaderStage(shaderSource.vertexSource, VERTEX); succes &= addShaderStage(shaderSource.fragmentSource, FRAGMENT); succes &= addShaderStage(shaderSource.geometrySource, GEOMETRY); succes &= addShaderStage(shaderSource.tesselationControlSource, TESSELATION_CONTROL); succes &= addShaderStage(shaderSource.tesselationEvaluateSource, TESSELATION_EVALUATE); if (!succes) { Log::error("Error during shader stage initialization, closing shader"); close(); } }; GShader::GShader(const std::string& name, const std::string& path, const std::string& vertexSource, const std::string& fragmentSource, const std::string& geometrySource, const std::string& tesselationControlSource, const std::string& tesselationEvaluateSource) : GShader(ShaderSource(name, path, vertexSource, fragmentSource, geometrySource, tesselationControlSource, tesselationEvaluateSource, "")) {} GShader::~GShader() { close(); } GShader::GShader(GShader&& other) { vertexStage = other.vertexStage; other.vertexStage = ShaderStage(); fragmentStage = other.fragmentStage; other.fragmentStage = ShaderStage(); geometryStage = other.geometryStage; other.geometryStage = ShaderStage(); tesselationControlStage = other.tesselationControlStage; other.tesselationControlStage = ShaderStage(); tesselationEvaluationStage = other.tesselationEvaluationStage; other.tesselationEvaluationStage = ShaderStage(); } GShader& GShader::operator=(GShader&& other) { if (this != &other) { close(); } return *this; } #pragma endregion #pragma region bindable void GShader::close() { if (id != 0) { Log::subject s(name); unbind(); Log::info("Closing shader"); glDeleteProgram(id); id = 0; Log::info("Closed shader"); } } #pragma endregion }; ================================================ FILE: graphics/legacy/gshader.h ================================================ #pragma once #include "shader.h" namespace P3D::Graphics { class GShader : public Shader { private: GLID createShader(const ShaderSource& shaderSource); protected: virtual bool addShaderStage(const std::string& source, const ShaderFlag& flag) override; public: ShaderStage vertexStage; ShaderStage fragmentStage; ShaderStage geometryStage; ShaderStage tesselationControlStage; ShaderStage tesselationEvaluationStage; GShader(); GShader(const ShaderSource& shaderSource); GShader(const std::string& name, const std::string& path, const std::string& vertexShader, const std::string& fragmentShader, const std::string& geometryShader, const std::string& tesselationControlSource, const std::string& tesselationEvaluateSource); ~GShader(); GShader(GShader&& other); GShader(const GShader&) = delete; GShader& operator=(GShader&& other); GShader& operator=(const GShader&) = delete; void reload(const ShaderSource& shaderSource); void close() override; }; ShaderSource parseShader(const std::string& name, const std::string& path, const std::string& vertexPath, const std::string& fragmentPath, const std::string& geometryPath = "", const std::string& tesselationControlPath = "", const std::string& tesselationEvaluatePath = ""); }; ================================================ FILE: graphics/legacy/image.cpp ================================================ #include "core.h" #include "image.h" #include "texture.h" #include "guiUtils.h" #include "path/path.h" #include "mesh/primitive.h" Image::Image(double x, double y, double width, double height) : Component(x, y, width, height) { Vec2 dimension = GUI::unmap(Vec2(width, height)); this->texture = nullptr; this->margin = GUI::margin; this->padding = GUI::padding; this->fixedWidth = true; this->fixedHeight = true; } Image::Image(double x, double y, Texture* texture) : Component(x, y) { this->texture = texture; this->margin = GUI::margin; this->padding = GUI::padding; this->fixedWidth = false; this->fixedHeight = false; }; Image::Image(double x, double y, double width, double height, Texture* texture) : Component(x, y, width, height) { this->texture = texture; this->margin = GUI::margin; this->padding = GUI::padding; this->fixedWidth = true; this->fixedHeight = true; } void Image::render() { if (!texture) return; if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; if (texture) Path::rectUV(texture->getID(), position, dimension); else Path::rectFilled(position, dimension, 0, COLOR::blend(COLOR::BLACK, blendColor)); Path::rect(position, dimension, 0.0f, COLOR::R); } } Vec2 Image::resize() { if (!texture) return dimension; if (resizing) { if (fixedWidth && fixedHeight) return dimension; dimension -= Vec2(GUI::padding) * 2; if (fixedWidth) this->height = this->width / texture->getAspect(); else if (fixedHeight) this->width = this->height * texture->getAspect(); else dimension = Vec2(texture->getWidth(), texture->getHeight()); dimension += Vec2(GUI::padding) * 2; } return dimension; } Image* Image::fixHeight(float height) { fixedHeight = true; this->height = height; return this; } Image* Image::fixWidth(float width) { fixedWidth = true; this->width = width; return this; } ================================================ FILE: graphics/legacy/image.h ================================================ #pragma once #include "component.h" class Texture; class Image : public Component { private: bool fixedHeight; bool fixedWidth; public: Texture* texture; Image(double x, double y, double width, double height); Image(double x, double y, Texture* texture); Image(double x, double y, double width, double height, Texture* texture); Image* fixHeight(float height); Image* fixWidth(float width); Vec2 resize() override; void render() override; }; ================================================ FILE: graphics/legacy/label.cpp ================================================ #include "core.h" #include "label.h" #include "gui.h" #include "font.h" #include "path/path.h" #include "renderUtils.h" #include "mesh/primitive.h" Label::Label(std::string text, double x, double y) : Label(text, x, y, GUI::fontSize, GUI::fontColor) {}; Label::Label(std::string text, double x, double y, double size) : Label(text, x, y, size, GUI::fontColor) {}; Label::Label(std::string text, double x, double y, double size, Color color) : Label(text, x, y, size, color, GUI::font) {}; Label::Label(std::string text, double x, double y, double scale, Color color, Font* font) : Component(x, y) { this->font = font; this->text = text; this->scale = scale; this->padding = GUI::padding; this->margin = GUI::margin; this->foregroundColor = color; this->backgroundColor = GUI::labelBackgroundColor; }; void Label::render() { if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; resize(); Vec2 textPosition = position + Vec2(padding, -dimension.y + padding); Path::text(font, text, scale, textPosition, COLOR::blend(foregroundColor, blendColor)); if (debug) { Path::rect(position, dimension, 0.0f, COLOR::R); Path::rect(position + Vec2f(padding, -padding), dimension - Vec2f(padding) * 2, 0.0f, COLOR::G); } } } Vec2 Label::resize() { if (resizing) { Vec2 fontDimension = font->size(text, scale); dimension = fontDimension + Vec2(padding) * 2; } return dimension; } ================================================ FILE: graphics/legacy/label.h ================================================ #pragma once #include "component.h" class Font; class Label : public Component { public: std::string text; Color backgroundColor; Color foregroundColor; double scale; Font* font; Label(std::string text, double x, double y); Label(std::string text, double x, double y, double scale); Label(std::string text, double x, double y, double scale, Color color); Label(std::string text, double x, double y, double scale, Color color, Font* font); Vec2 resize() override; void render() override; }; ================================================ FILE: graphics/legacy/layout.cpp ================================================ #include "core.h" #include "layout.h" #include "container.h" namespace P3D::Graphics { Vec2 FlowLayout::resize(Container* container, Vec2 minDimension) { // Resulting width of the container double contentWidth = minDimension.x; // Resulting height of the container double contentHeight = minDimension.y; // Width of the current row of components double rowWidth = 0; // Height of the current row of components double rowHeight = 0; // Components to be centered std::vector centeredComponents; for (int i = 0; i < container->children.size(); i++) { auto child = container->children[i]; Component* component = child.first; Align alignment = child.second; Vec2 componentSize = component->resize(); // NO HEIGHT CHECK YET if (alignment == Align::FILL || alignment == Align::CENTER) { // Calculate new row width, component margin included double newRowWidth = rowWidth + component->margin + componentSize.x + component->margin; if (newRowWidth <= container->width || container->resizing) { // Set component position relative to parent with old rowWidth and contentHeight, component margin included component->position = container->position + Vec2(rowWidth, -contentHeight) + Vec2(component->margin, -component->margin); // End of the current row, component keeps dimension // Update row width with calculated value rowWidth = newRowWidth; // Update row height, component margin included rowHeight = fmax(rowHeight, component->margin + componentSize.y + component->margin); // Resize the container so the component fits in contentWidth = fmax(contentWidth, rowWidth); // Next line contentHeight += rowHeight; // Reset row size rowWidth = 0; rowHeight = 0; if (alignment == Align::CENTER) { centeredComponents.push_back(component); } } else { // NO CHECK IF COMPONENT WIDTH IS GREATER THAN CONTENTWIDTH // Component does not fit in the current row, advance to the next row contentHeight += rowHeight; // Set component position relative to parent, component margin included component->position = container->position + Vec2(0, -contentHeight) + Vec2(component->margin, -component->margin); // Advance content height, component margin included contentHeight += component->margin + componentSize.y + component->margin; // Reset row size rowWidth = 0; rowHeight = 0; } } else if (alignment == Align::RELATIVE) { // Calculate new row width, component margin included double newRowWidth = rowWidth + component->margin + componentSize.x + component->margin; if (newRowWidth <= container->width || container->resizing) { // Set component position relative to parent component->position = container->position + Vec2(rowWidth, -contentHeight) + Vec2(component->margin, -component->margin); // Update row width with calculated value rowWidth = newRowWidth; // Update row height, component margin included rowHeight = fmax(rowHeight, component->margin + componentSize.y + component->margin); // Resize the container contentWidth = fmax(contentWidth, rowWidth); } else { // Component does not fit in the current row, advance to the next row contentHeight += rowHeight; // Set component position relative to parent component->position = container->position + Vec2(0, -contentHeight) + Vec2(component->margin, -component->margin); // Set new row size rowWidth = component->margin + componentSize.x + component->margin; rowHeight = component->margin + componentSize.y + component->margin; } } } // Center remaining components for (Component* component : centeredComponents) { // Distance from component position to the end of the container minus the component margin double remainingWidth = container->x + contentWidth - component->x - component->margin; // Component width double componentWidth = component->width; // Move component to the center component->x += (remainingWidth - componentWidth) / 2; } // Add height of last row contentHeight += rowHeight; if (container->resizing) { container->dimension = Vec2f(contentWidth, contentHeight); } return container->dimension; } }; ================================================ FILE: graphics/legacy/layout.h ================================================ #pragma once namespace P3D::Graphics { class Container; enum class Align { FILL, RELATIVE, CENTER }; class Layout { public: // Positions the components of the given container (not including the padding of the container) virtual Vec2 resize(Container* container, Vec2 minDimension = Vec2()) = 0; }; class FlowLayout : public Layout { public: Vec2 resize(Container* container, Vec2 minDimension = Vec2()) override; }; }; ================================================ FILE: graphics/legacy/panel.cpp ================================================ #include "core.h" #include "panel.h" #include "gui.h" #include "layout.h" #include "path/path.h" #include "mesh/primitive.h" #include "../physics/math/mathUtil.h" Panel::Panel(double x, double y) : Container(x, y) { this->background = COLOR::BACK; this->padding = GUI::padding; this->margin = GUI::margin; }; Panel::Panel(double x, double y, double width, double height) : Container(x, y, width, height) { this->background = COLOR::BACK; this->padding = GUI::padding; this->margin = GUI::margin; }; Vec2 Panel::resize() { Vec2 positionOffset = Vec2(padding, -padding); Vec2 dimensionOffset = Vec2(2 * padding); position += positionOffset; dimension -= dimensionOffset; dimension = layout->resize(this); position -= positionOffset; dimension += dimensionOffset; return dimension; } void Panel::render() { if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; resize(); Path::rectFilled(position, dimension, 0, COLOR::blend(background, blendColor)); renderChildren(); } } ================================================ FILE: graphics/legacy/panel.h ================================================ #pragma once #include "component.h" #include "container.h" class Panel : public Container { public: Color background; Panel(double x, double y); Panel(double x, double y, double width, double height); Vec2 resize() override; void render() override; }; ================================================ FILE: graphics/legacy/shader.cpp ================================================ #include "core.h" #include #include "shader.h" #include "renderer.h" #include "debug/guiDebug.h" #include #include #include namespace P3D::Graphics { #pragma region uniforms int Shader::getUniform(const std::string& uniform) const { auto iterator = uniforms.find(uniform); if (iterator != uniforms.end()) return iterator->second; return -1; } void Shader::addUniform(const std::string& uniform) { if (uniforms.find(uniform) == uniforms.end()) createUniform(uniform); } void Shader::createUniform(const std::string& uniform) { bind(); Log::subject s(name); int location = id != 0 ? glGetUniformLocation(id, uniform.c_str()) : -1; if (location < 0) Log::error("Could not find uniform (%s) in shader (%s)", uniform.c_str(), name.c_str()); else Log::debug("Created uniform (%s) in shader (%s) with id (%d)", uniform.c_str(), name.c_str(), location); uniforms.insert({ uniform, location }); } void Shader::setUniform(const std::string& uniform, GLID value) const { if (id == 0) return; glUniform1i(uniforms.at(uniform), value); } void Shader::setUniform(const std::string& uniform, int value) const { if (id == 0) return; glUniform1i(uniforms.at(uniform), value); } void Shader::setUniform(const std::string& uniform, float value) const { if (id == 0) return; glUniform1f(uniforms.at(uniform), value); } void Shader::setUniform(const std::string& uniform, const Vec2f& value) const { if (id == 0) return; glUniform2f(uniforms.at(uniform), value.x, value.y); } void Shader::setUniform(const std::string& uniform, const Vec3f& value) const { if (id == 0) return; glUniform3f(uniforms.at(uniform), value.x, value.y, value.z); } void Shader::setUniform(const std::string& uniform, const Position& value) const { if (id == 0) return; glUniform3f(uniforms.at(uniform), float(value.x), float(value.y), float(value.z)); } void Shader::setUniform(const std::string& uniform, const Vec4f& value) const { if (id == 0) return; glUniform4f(uniforms.at(uniform), value.x, value.y, value.z, value.w); } void Shader::setUniform(const std::string& uniform, const Mat2f& value) const { if (id == 0) return; float buf[4]; value.toColMajorData(buf); glUniformMatrix2fv(uniforms.at(uniform), 1, GL_FALSE, buf); } void Shader::setUniform(const std::string& uniform, const Mat3f& value) const { if (id == 0) return; float buf[9]; value.toColMajorData(buf); glUniformMatrix3fv(uniforms.at(uniform), 1, GL_FALSE, buf); } void Shader::setUniform(const std::string& uniform, const Mat4f& value) const { if (id == 0) return; float buf[16]; value.toColMajorData(buf); glUniformMatrix4fv(uniforms.at(uniform), 1, GL_FALSE, buf); } #pragma endregion #pragma region compile GLID Shader::compile(const std::string& name, const std::string& source, unsigned int type) { Log::setDelimiter(""); Log::info("%s: ", name.c_str()); GLID id = glCreateShader(type); const char* src = source.c_str(); glShaderSource(id, 1, &src, NULL); glCall(glCompileShader(id)); int result; glGetShaderiv(id, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { int length; glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length); char* message = (char*) alloca(length * sizeof(char)); glGetShaderInfoLog(id, length, &length, message); Log::print(Log::Color::ERROR, "fail\n"); Log::error(message); glDeleteShader(id); Log::setDelimiter("\n"); return 0; } else { Log::print(Log::Color::DEBUG, "done\n"); } Log::setDelimiter("\n"); return id; } #pragma endregion #pragma region parse ShaderSource parseShader(const std::string& name, const std::string& path, const std::string& shaderText) { std::istringstream shaderTextStream(shaderText); return parseShader(name, path, shaderTextStream); } ShaderSource parseShader(const std::string& name, const std::string& path) { std::ifstream input; input.open(path); if (input.is_open()) { std::string line; std::stringstream shaderTextStream; while (getline(input, line)) shaderTextStream << line << "\n"; return parseShader(name, path, shaderTextStream); } else return ShaderSource(); } ShaderSource parseShader(const std::string& name, const std::string& path, std::istream& shaderTextStream) { Log::subject s(name); Log::info("Reading (%s)", name.c_str()); enum class ShaderType { NONE = -1, COMMON = 0, VERTEX = 1, FRAGMENT = 2, GEOMETRY = 3, TESSELATION_CONTROL = 4, TESSELATION_EVALUATE = 5, COMPUTE = 6 }; ShaderType type = ShaderType::NONE; std::string line; std::stringstream stringStream[7]; int lineNumber = 0; while (getline(shaderTextStream, line)) { lineNumber++; if (line == "\r") { continue; } if (line.find("[vertex]") != std::string::npos) { type = ShaderType::VERTEX; } else if (line.find("[fragment]") != std::string::npos) { type = ShaderType::FRAGMENT; } else if (line.find("[geometry]") != std::string::npos) { type = ShaderType::GEOMETRY; } else if (line.find("[tesselation control]") != std::string::npos) { type = ShaderType::TESSELATION_CONTROL; } else if (line.find("[tesselation evaluate]") != std::string::npos) { type = ShaderType::TESSELATION_EVALUATE; } else if (line.find("[compute]") != std::string::npos) { type = ShaderType::COMPUTE; } else if (line.find("[common]") != std::string::npos) { type = ShaderType::COMMON; } else { if (type == ShaderType::NONE) { Log::warn("(line %d): code before the first #shader instruction will be ignored", lineNumber); continue; } stringStream[(int) type] << line << "\n"; } } std::string commonSource = stringStream[(int) ShaderType::COMMON].str(); std::string vertexSource = stringStream[(int) ShaderType::VERTEX].str(); std::string fragmentSource = stringStream[(int) ShaderType::FRAGMENT].str(); std::string geometrySource = stringStream[(int) ShaderType::GEOMETRY].str(); std::string tesselationControlSource = stringStream[(int) ShaderType::TESSELATION_CONTROL].str(); std::string tesselationEvaluateSource = stringStream[(int) ShaderType::TESSELATION_EVALUATE].str(); std::string computeSource = stringStream[(int) ShaderType::COMPUTE].str(); Log::setDelimiter(""); if (!commonSource.empty()) { Log::info("Common file: "); Log::print(Log::Color::DEBUG, "done\n"); } if (!vertexSource.empty()) { Log::info("Vertex shader: "); Log::print(Log::Color::DEBUG, "done\n"); vertexSource = commonSource + vertexSource; } if (!fragmentSource.empty()) { Log::info("Fragment shader: "); Log::print(Log::Color::DEBUG, "done\n"); fragmentSource = commonSource + fragmentSource; } if (!geometrySource.empty()) { Log::info("Geometry shader: "); Log::print(Log::Color::DEBUG, "done\n"); geometrySource = commonSource + geometrySource; } if (!tesselationControlSource.empty()) { Log::info("Tesselation control shader: "); Log::print(Log::Color::DEBUG, "done\n"); tesselationControlSource = commonSource + tesselationControlSource; } if (!tesselationEvaluateSource.empty()) { Log::info("Tesselation evaluation shader: "); Log::print(Log::Color::DEBUG, "done\n"); tesselationEvaluateSource = commonSource + tesselationEvaluateSource; } if (!computeSource.empty()) { Log::info("Tesselation evaluation shader: "); Log::print(Log::Color::DEBUG, "done\n"); computeSource = commonSource + computeSource; } Log::setDelimiter("\n"); return ShaderSource(name, path, vertexSource, fragmentSource, geometrySource, tesselationControlSource, tesselationEvaluateSource, computeSource); } #pragma endregion #pragma region constructors ShaderSource::ShaderSource() = default; ShaderSource::ShaderSource(const std::string& name, const std::string& path, const std::string& vertexSource, const std::string& fragmentSource, const std::string& geometrySource, const std::string& tesselationControlSource, const std::string& tesselationEvaluateSource, const std::string& computeSource) : name(name), path(path), vertexSource(vertexSource), fragmentSource(fragmentSource), geometrySource(geometrySource), tesselationControlSource(tesselationControlSource), tesselationEvaluateSource(tesselationEvaluateSource), computeSource(computeSource) {} Shader::Shader() = default; Shader::Shader(const ShaderSource& shaderSource) : name(shaderSource.name) {} Shader::~Shader() { close(); } Shader::Shader(Shader&& other) { id = other.id; other.id = 0; uniforms = other.uniforms; other.uniforms = std::unordered_map(); flags = other.flags; other.flags = NONE; name = other.name; other.name = std::string(); } Shader& Shader::operator=(Shader&& other) noexcept { if (this != &other) { close(); std::swap(id, other.id); name = std::move(other.name); uniforms = std::move(other.uniforms); } return *this; } #pragma endregion #pragma region bindable void Shader::bind() { Renderer::bindShader(id); } void Shader::unbind() { Renderer::bindShader(0); } void Shader::close() { if (id != 0) { Log::subject s(name); unbind(); Log::info("Closing shader"); glDeleteProgram(id); id = 0; Log::info("Closed shader"); } } #pragma endregion #pragma region ShaderStage ShaderStage::ShaderStage() = default; ShaderStage::ShaderStage(const std::string& source) : source(source) { this->info = Parser::parse(this->source.c_str()); } std::vector getSuffixes(const ShaderStage& stage, const Parser::Local& local) { std::vector suffixes; auto strct = stage.info.structs.find(local.type.string(stage.source.c_str())); bool isStruct = strct != stage.info.structs.end(); std::string localName(local.name.view(stage.source.c_str())); if (local.amount != 0) { if (isStruct) { // struct array for (int i = 0; i < local.amount; i++) { std::string variable = localName + "[" + std::to_string(i) + "]."; for (const Parser::Local& local : strct->second.locals) for (const std::string& localSuffix : getSuffixes(stage, local)) suffixes.push_back(variable + localSuffix); } } else { // normal array for (int i = 0; i < local.amount; i++) suffixes.push_back(localName + "[" + std::to_string(i) + "]"); } } else { if (isStruct) { std::string variable = localName + "."; for (const Parser::Local& local : strct->second.locals) for (const std::string& localSuffix : getSuffixes(stage, local)) suffixes.push_back(variable + localSuffix); } else { suffixes.push_back(localName); } } return suffixes; } void Shader::addUniforms(const ShaderStage& stage) { for (const Parser::Local& uniform : stage.info.uniforms) { std::vector variables = getSuffixes(stage, uniform); for (const std::string& variable : variables) addUniform(variable); } } #pragma endregion }; ================================================ FILE: graphics/legacy/shader.h ================================================ #pragma once #include "../bindable.h" #include "parser.h" namespace P3D::Graphics { struct ShaderStage { std::string source; Parser::Parse info; ShaderStage(); ShaderStage(const std::string& source); }; struct ShaderSource { std::string name; std::string path; std::string vertexSource; std::string fragmentSource; std::string geometrySource; std::string tesselationControlSource; std::string tesselationEvaluateSource; std::string computeSource; ShaderSource(); ShaderSource(const std::string& name, const std::string& path, const std::string& vertexSource, const std::string& fragmentSource, const std::string& geometrySource, const std::string& tesselationControlSource, const std::string& tesselationEvaluateSource, const std::string& computeSource); }; class Shader : public Bindable { public: enum ShaderFlag : char { NONE = 0 << 0, VERTEX = 1 << 0, FRAGMENT = 1 << 1, GEOMETRY = 1 << 2, TESSELATION_CONTROL = 1 << 3, TESSELATION_EVALUATE = 1 << 4, COMPUTE = 1 << 5 }; char flags = NONE; std::string name; ~Shader(); Shader(const Shader&) = delete; Shader& operator=(Shader&& other) noexcept; Shader& operator=(const Shader&) = delete; static GLID compile(const std::string& name, const std::string& source, unsigned int type); int getUniform(const std::string& uniform) const; void createUniform(const std::string& uniform); void setUniform(const std::string& uniform, int value) const; void setUniform(const std::string& uniform, GLID value) const; void setUniform(const std::string& uniform, float value) const; void setUniform(const std::string& uniform, const Vec2f& value) const; void setUniform(const std::string& unfiorm, const Vec3f& value) const; void setUniform(const std::string& unfiorm, const Vec4f& value) const; void setUniform(const std::string& uniform, const Mat2f& value) const; void setUniform(const std::string& uniform, const Mat3f& value) const; void setUniform(const std::string& uniform, const Mat4f& value) const; void setUniform(const std::string& uniform, const Position& value) const; void bind() override; void unbind() override; void close() override; protected: std::unordered_map uniforms; Shader(); Shader(const ShaderSource& shaderSource); Shader(Shader&& other); void addUniform(const std::string& uniform); void addUniforms(const ShaderStage& stage); virtual bool addShaderStage(const std::string& source, const ShaderFlag& flag) = 0; }; ShaderSource parseShader(const std::string& name, const std::string& path); ShaderSource parseShader(const std::string& name, const std::string& path, std::istream& shaderTextStream); ShaderSource parseShader(const std::string& name, const std::string& path, const std::string& shaderText); }; ================================================ FILE: graphics/legacy/shaderLexer.cpp ================================================ #include "core.h" #include "shaderLexer.h" #include "../util/stringUtil.h" namespace P3D::Graphics { std::vector ShaderLexer::types = { TokenType(TokenType::NONE, std::regex("(.*?)")), TokenType(TokenType::COMMA, ','), TokenType(TokenType::EOL, ';'), TokenType(TokenType::RP, ')'), TokenType(TokenType::LP, '('), TokenType(TokenType::ASSIGN, '='), TokenType(TokenType::LC, '{'), TokenType(TokenType::RC, '}'), TokenType(TokenType::LB, '['), TokenType(TokenType::RB, ']'), TokenType(TokenType::DOT, '.'), TokenType(TokenType::IO, std::regex("in|out")), TokenType(TokenType::LAYOUT, std::regex("layout")), TokenType(TokenType::UNIFORM, std::regex("uniform")), TokenType(TokenType::QUALIFIER, std::regex("const")), TokenType(TokenType::STRUCT, std::regex("struct")), TokenType(TokenType::VERSION, std::regex("#version")), TokenType(TokenType::PREP, std::regex("#ifdef|#ifndef|#else|#endif|#define|#if|#undef")), TokenType(TokenType::TYPE, std::regex("mat2|mat3|mat4|float|int|vec2|vec3|vec4|VS_OUT|sampler2D|void|sampler3D")), TokenType(TokenType::ID, std::regex("[A-Za-z_]\\w*")), TokenType(TokenType::OP, std::regex("&&?|\\|\\|?|\\+\\+?|--?|\\*\\*?|\\:|\\/\\/?|\\?|<=?|>=?")), TokenType(TokenType::NUMBER, std::regex("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?")), TokenType(TokenType::STRING, std::regex("\"((?:\\\\.|[^\\\\\"])*)\"")), TokenType(TokenType::SINGLECOMMENT, std::regex("\\/[\\/]+.*")), TokenType(TokenType::MULTICOMMENT, std::regex("\\/\\*((?:.|[^\\*\\/])*)\\*\\/")) }; std::string lexSingleComment(const std::string& input) { int index = 0; while (input.length() != index && input.at(index) != '\n') index++; return input.substr(0, index); } TokenType ShaderLexer::getMatch(const std::string& input) { TokenType match = ShaderLexer::types[0]; for (int i = 1; i < ShaderLexer::types.size(); i++) { if (types[i].accepting) { if (input.length() == 1 && input.at(0) == types[i].character) return types[i]; else continue; } else { if (std::regex_match(input, types[i].regex)) return types[i]; } } return match; } Token ShaderLexer::popToken(std::string& input, const TokenType& type, std::string value) { // Throw exception if the token is not recognized if (type == TokenType::NONE) Log::error("Type not recognized for value %s", value.c_str()); // removes the token from the input input.erase(0, value.length()); // removes all leading whitespaces from the input input = Util::ltrim(input); // remove quotes from the value if the type is a string, regex or comment switch (type) { case TokenType::SINGLECOMMENT: value = value.substr(2, value.length()); break; case TokenType::MULTICOMMENT: value = value.substr(1, value.length() - 1); case TokenType::STRING: value = value.substr(1, value.length() - 1); break; } return Token(type, value); } Token ShaderLexer::nextToken(std::string& input) { std::string currentToken; TokenType lastMatch = ShaderLexer::types[0]; for (int i = 0; i < input.length(); i++) { currentToken.push_back(input.at(i)); // search for matches TokenType match = getMatch(currentToken); // No current match if (match == TokenType::NONE) { // Earlier match found if (lastMatch != TokenType::NONE) { // Remove last char currentToken.erase(currentToken.end() - 1); // break the loop break; } // else continue to search for a match } else if (match == TokenType::SINGLECOMMENT) { return popToken(input, match, lexSingleComment(input)); } else { // update last match lastMatch = match; } } return popToken(input, lastMatch, currentToken); } TokenStack ShaderLexer::lexDebug(const std::string& input) { std::function(const std::string&)> collect = [input] (const std::string& key) { std::vector list; size_t lastIndex = 0; while (true) { size_t index = input.find(key, lastIndex); if (index == input.npos) { break; } else { list.push_back(index); lastIndex = index + key.size(); } } return list; }; std::function(size_t, bool)> next = [input] (size_t offset, bool allowEnter) { size_t start = offset; size_t index = offset; while (true) { const char& c = input[index]; if (c == ' ' || c == '\t' || c == '\r' || c == ';' || c == '\n' || c == '[') { if (start == index) { if (!allowEnter && c == '\n') return std::make_pair(size_t(0), size_t(0)); else start++; } else { break; } } index++; } return std::make_pair(start, index); }; std::function until = [input] (char l, char r, size_t offset) { int depth = -1; size_t index = offset; size_t size = input.length(); while (index < size) { const char& c = input[index]; if (c == l) { depth++; } else if (c == r) { if (depth == 0) break; else if (depth > 0) depth--; } index++; } return index; }; TokenStack tokens; std::vector defines = collect("#define "); std::vector uniforms = collect("uniform "); std::vector structs = collect("struct "); for (size_t index : defines) { tokens.add(Token(TokenType(TokenType::PREP, 0), "#define")); index += 7; // length("#define ") auto [n1, n2] = next(index, false); std::string name = input.substr(n1, n2 - n1); tokens.add(Token(TokenType(TokenType::ID, 0), name)); index = n2; auto [n3, n4] = next(index, false); std::string value = input.substr(n3, n4 - n3); if (!value.empty()) tokens.add(Token(TokenType(TokenType::NUMBER, 0), value)); } for (size_t index : structs) { tokens.add(Token(TokenType(TokenType::STRUCT, 0), "struct")); index += 6; // length("uniform ") size_t end = until('{', '}', index); std::string content = input.substr(index, end - index + 1); TokenStack strct = lex(content); tokens.addAll(strct); tokens.add(Token(TokenType(TokenType::EOL, 0), "")); } for (size_t index : uniforms) { tokens.add(Token(TokenType(TokenType::UNIFORM, 0), "uniform")); index += 7; // length("uniform ") auto [n1, n2] = next(index, true); std::string type = input.substr(n1, n2 - n1); tokens.add(Token(TokenType(TokenType::TYPE, 0), type)); index = n2; auto [n3, n4] = next(index, true); std::string name = input.substr(n3, n4 - n3); tokens.add(Token(TokenType(TokenType::ID, 0), name)); index = n4; auto [n5, n6] = next(index, true); std::string array = input.substr(n5 - 1, n6 - n5 + 1); if (!array.empty() && Util::startsWith(array, "[") && Util::endsWith(array, "]")) { TokenStack content = lex(array); tokens.addAll(content); } tokens.add(Token(TokenType(TokenType::EOL, 0), "")); } tokens.flip(); return tokens; } TokenStack ShaderLexer::lex(const std::string& input) { TokenStack tokens; std::string code = Util::trim(input); while (code.length() != 0) { Token token = nextToken(code); if (token.type == TokenType::SINGLECOMMENT || token.type == TokenType::MULTICOMMENT) continue; tokens.add(token); } tokens.flip(); return tokens; } Token TokenStack::peek(size_t offset) const { if (read) if (available(offset)) return *(iterator + offset); return Token(ShaderLexer::types[0], ""); } Token TokenStack::pop() { if (read) if (available()) return *iterator++; return Token(ShaderLexer::types[0], ""); } void TokenStack::discard() { if (read) if (available()) iterator++; } void TokenStack::add(const Token& token) { if (!read) stack.push_back(token); } void TokenStack::addAll(const TokenStack& tokens) { int index = 0; while (tokens.available(index)) add(tokens.peek(index++)); } void TokenStack::flip() { read = !read; if (read) iterator = stack.begin(); } TokenStack TokenStack::until(const TokenType::Type& type, bool popType) { TokenStack content; if (read) { while (available()) { if (iterator->type == type) break; content.add(*iterator++); } if (popType) discard(); } content.flip(); return content; } bool TokenStack::available(size_t offset) const { if (read) return iterator + offset < stack.end(); return false; } size_t TokenStack::size() const { return stack.size(); } }; ================================================ FILE: graphics/legacy/shaderLexer.h ================================================ #pragma once #include "../util/stringUtil.h" #include namespace P3D::Graphics { struct TokenType { enum Type : char { NONE, ID, OP, COMMA, DOT, ASSIGN, NUMBER, VERSION, PREP, STRING, SINGLECOMMENT, MULTICOMMENT, LP, RP, LB, RB, LC, RC, EOL, IO, STRUCT, LAYOUT, LOCATION, QUALIFIER, UNIFORM, TYPE, }; public: Type type; std::regex regex; char character; bool accepting; TokenType(Type type, std::regex regex) : type(type), regex(regex), character(), accepting(false) {} TokenType(Type type, char character) : type(type), character(character), accepting(true) {} operator Type() const { return type; } bool operator==(Type other) const { return type == other; } bool operator==(TokenType other) const { return type == other.type; } bool operator!=(Type other) const { return type != other; } bool operator!=(TokenType other) const { return type != other.type; } }; struct Token { TokenType type; std::string value; Token(const TokenType& type, const std::string& value) : type(type), value(value) {} }; struct TokenStack { private: bool read = false; std::vector stack; std::vector::iterator iterator; public: void add(const Token& token); void addAll(const TokenStack& tokens); void flip(); TokenStack until(const TokenType::Type& type, bool popType = true); Token peek(size_t offset = 0) const; Token pop(); void discard(); bool available(size_t offset = 0) const; size_t size() const; }; class ShaderLexer { friend TokenStack; private: static std::vector types; static Token nextToken(std::string& input); static TokenType getMatch(const std::string& input); static Token popToken(std::string& input, const TokenType& type, std::string value); public: static TokenStack lex(const std::string& input); static TokenStack lexDebug(const std::string& input); }; }; ================================================ FILE: graphics/legacy/shaderParser.cpp ================================================ #include "core.h" #include "shaderParser.h" namespace P3D::Graphics { std::map variableTypeMap = { { "void", ShaderVariableType::VOID }, { "int", ShaderVariableType::INT }, { "float", ShaderVariableType::FLOAT }, { "mat2", ShaderVariableType::MAT2 }, { "mat3", ShaderVariableType::MAT3 }, { "mat4", ShaderVariableType::MAT4 }, { "vec2", ShaderVariableType::VEC2 }, { "vec3", ShaderVariableType::VEC3 }, { "vec4", ShaderVariableType::VEC4 }, { "sampler2D", ShaderVariableType::SAMPLER2D }, { "sampler3D", ShaderVariableType::SAMPLER3D }, { "struct", ShaderVariableType::STRUCT }, { "VS_OUT", ShaderVariableType::VS_OUT }, }; ShaderLocal parseLocal(TokenStack& tokens, const ShaderDefines& defines); ShaderVariableType ShaderParser::parseVariableType(const std::string& value) { auto iterator = variableTypeMap.find(value); if (iterator != variableTypeMap.end()) return iterator->second; return ShaderVariableType::NONE; } ShaderIOType parseIOType(const Token& token) { std::string value = token.value; if (value == "in") return ShaderIOType::IN; if (value == "out") return ShaderIOType::OUT; return ShaderIOType::NONE; } TokenStack nextScope(TokenStack& tokens, const TokenType::Type& ltype, const TokenType::Type& rtype) { int depth = -1; TokenStack scope; while (tokens.available()) { Token token = tokens.pop(); if (token.type == ltype) { if (++depth == 0) continue; } else if (token.type == rtype) { if (depth == 0) break; else if (depth > 0) depth--; } scope.add(token); } scope.flip(); return scope; } bool testFunction(TokenStack& tokens) { if (ShaderParser::parseVariableType(tokens.peek().value) == ShaderVariableType::VOID) return true; if (tokens.peek(1).type == TokenType::ID) if (tokens.peek(2).type == TokenType::LP) return true; return false; } int parseArray(TokenStack& tokens, const ShaderDefines& defines) { if (tokens.peek().type == TokenType::LB) { tokens.discard(); Token next = tokens.pop(); switch (next.type) { case TokenType::NUMBER: { return std::stoi(next.value); break; } case TokenType::ID: { if (defines.find(next.value) != defines.end()) return int (defines.at(next.value)); } case TokenType::RB: { return 0; } } } return -1; } ShaderLocals parseStructContent(TokenStack& scope, const ShaderStructs& structs, const ShaderDefines& defines) { ShaderLocals locals; while (scope.available()) { const Token& token = scope.peek(); switch (token.type) { case TokenType::TYPE: { ShaderLocal local = parseLocal(scope, defines); locals.push_back(local); break; } case TokenType::ID: { if (structs.find(token.value) != structs.end()) { ShaderLocal local = parseLocal(scope, defines); locals.push_back(local); continue; } } default: { scope.discard(); } } } return locals; } ShaderStruct parseStruct(TokenStack& tokens, const ShaderStructs& structs, const ShaderDefines& defines) { tokens.discard(); // pop STRUCT std::string name = tokens.pop().value; TokenStack scope = nextScope(tokens, TokenType::LC, TokenType::RC); ShaderLocals locals = parseStructContent(scope, structs, defines); return ShaderStruct(name, locals); } ShaderUniform parseUniform(TokenStack& tokens, const ShaderDefines& defines) { tokens.discard(); // pop UNIFORM std::string variableType = tokens.pop().value; std::string name = tokens.pop().value; int amount = parseArray(tokens, defines); tokens.until(TokenType::EOL); return ShaderUniform(name, variableType, amount != -1, amount); } ShaderVSOUT parseVSOUT(TokenStack& tokens, const std::string& ioType, const ShaderDefines& defines) { TokenStack scope = nextScope(tokens, TokenType::LC, TokenType::RC); ShaderLocals locals = parseStructContent(scope, ShaderStructs(), defines); std::string name = tokens.pop().value; int amount = parseArray(tokens, defines); return ShaderVSOUT(name, ioType, "VS_OUT", amount != -1, amount, locals); } ShaderLocal parseLocal(TokenStack& tokens, const ShaderDefines& defines) { std::string variableType = tokens.pop().value; std::string name = tokens.pop().value; int amount = parseArray(tokens, defines); return ShaderLocal(name, variableType, amount != -1, amount); } ShaderGlobal parseGlobal(TokenStack& tokens, const ShaderDefines& defines) { std::string ioType = tokens.pop().value; std::string variableType = tokens.pop().value; switch (ShaderParser::parseVariableType(variableType)) { case ShaderVariableType::VS_OUT: { ShaderVSOUT vsout = parseVSOUT(tokens, ioType, defines); return vsout; break; } default: { std::string name = tokens.pop().value; int amount = parseArray(tokens, defines); return ShaderGlobal(name, ioType, variableType, amount != -1, amount); break; } } } ShaderLayoutItem parseLayoutItem(TokenStack& tokens) { tokens.discard(); TokenStack scope = nextScope(tokens, TokenType::LP, TokenType::RP); ShaderLayoutAttributes attributes; while (scope.available()) { TokenStack setting = scope.until(TokenType::COMMA); std::string attribute = setting.pop().value; std::string value = (setting.peek().type == TokenType::ASSIGN) ? setting.pop().value : ""; attributes.push_back(ShaderLayoutAttribute(attribute, value)); } std::string ioType = tokens.pop().value; std::string variableType = ""; std::string name = ""; if (tokens.peek().type == TokenType::TYPE) { variableType = tokens.pop().value; name = tokens.pop().value; } return ShaderLayoutItem(attributes, ioType, variableType, name); } void parseDefine(TokenStack& tokens, ShaderDefines& defines) { std::string name = tokens.pop().value; float value = 1; if (tokens.peek().type == TokenType::NUMBER) { try { value = std::stof(tokens.pop().value); } catch (...) { return; } defines[name] = value; } } void parseUnDef(TokenStack& tokens, ShaderDefines& defines) { std::string name = tokens.pop().value; auto iterator = defines.find(name); if (iterator != defines.end()) defines.erase(iterator); } bool parseIf(TokenStack& tokens, ShaderDefines& defines) { Token token = tokens.pop(); switch (token.type) { case TokenType::NUMBER: { return std::stof(token.value); break; } case TokenType::ID: { if (defines.find(token.value) != defines.end()) return defines.at(token.value); break; } } return false; } bool parseIfDef(TokenStack& tokens, ShaderDefines& defines) { Token token = tokens.pop(); if (defines.find(token.value) != defines.end()) return true; return false; } bool parseIfNDef(TokenStack& tokens, ShaderDefines& defines) { return !parseIfDef(tokens, defines); } void parseIfBody() { } ShaderVersion parseVersion(TokenStack& tokens) { ShaderVersion version; tokens.discard(); if (tokens.peek().type == TokenType::NUMBER) { version.version = std::stoi(tokens.pop().value); version.version = std::clamp(version.version, 300, 450); // Todo check correctness if (tokens.peek().type == TokenType::ID && tokens.peek().value == "core") { version.core = true; tokens.pop(); } } return version; } void parsePreprocessor(TokenStack& tokens, ShaderDefines& defines) { std::string preprocessor = tokens.pop().value; if (preprocessor == "#define") { parseDefine(tokens, defines); } else if (preprocessor == "#if") { bool result = parseIf(tokens, defines); } else if (preprocessor == "#ifdef") { bool result = parseIfDef(tokens, defines); } else if (preprocessor == "#ifndef") { bool result = parseIfNDef(tokens, defines); } else if (preprocessor == "#undef") { parseUnDef(tokens, defines); } } ShaderInfo ShaderParser::parse(const std::string& code) { #ifdef NDEBUG // RELEASE TokenStack tokens = ShaderLexer::lex(code); #else TokenStack tokens = ShaderLexer::lexDebug(code); #endif // NDEBUG return parseTokens(tokens); } ShaderInfo ShaderParser::parseTokens(TokenStack& tokens) { ShaderVersion version; ShaderLayout layout; ShaderUniforms uniforms; ShaderGlobals globals; ShaderLocals locals; ShaderDefines defines; ShaderStructs structs; while (tokens.available()) { const Token& token = tokens.peek(); switch (token.type) { case TokenType::UNIFORM: { ShaderUniform uniform = parseUniform(tokens, defines); uniforms.push_back(uniform); break; } case TokenType::IO: { ShaderGlobal global = parseGlobal(tokens, defines); globals.push_back(global); break; } case TokenType::TYPE: { if (testFunction(tokens)) { nextScope(tokens, TokenType::LC, TokenType::RC); continue; } ShaderLocal local = parseLocal(tokens, defines); locals.push_back(local); break; } case TokenType::LAYOUT: { ShaderLayoutItem layoutItem = parseLayoutItem(tokens); layout.push_back(layoutItem); break; } case TokenType::VERSION: { version = parseVersion(tokens); break; } case TokenType::PREP: { parsePreprocessor(tokens, defines); break; } case TokenType::STRUCT: { ShaderStruct strct = parseStruct(tokens, structs, defines); structs[strct.name] = strct; break; } case TokenType::ID: { if (structs.find(token.value) != structs.end()) { ShaderLocal local = parseLocal(tokens, defines); locals.push_back(local); continue; } } default: { tokens.discard(); } } } return ShaderInfo(version, layout, uniforms, globals, locals, defines, structs); } }; ================================================ FILE: graphics/legacy/shaderParser.h ================================================ #pragma once #include #include "shaderLexer.h" namespace P3D::Graphics { enum class ShaderVariableType { NONE, VOID, INT, FLOAT, VEC2, VEC3, VEC4, MAT2, MAT3, MAT4, STRUCT, VS_OUT, SAMPLER2D, SAMPLER3D }; enum class ShaderIOType { NONE, IN, OUT }; struct ShaderLocal { std::string name; std::string variableType; bool array; int amount; ShaderLocal(const std::string& name, const std::string& variableType, bool array, int amount) : name(name), variableType(variableType), array(array), amount(amount) {} }; typedef std::vector ShaderLocals; struct ShaderGlobal { std::string name; std::string ioType; std::string variableType; bool array; int amount; ShaderGlobal(const std::string& name, const std::string& ioType, const std::string& variableType, bool array, int amount) : name(name), ioType(ioType), variableType(variableType), array(array), amount(amount) {}; }; typedef std::vector ShaderGlobals; struct ShaderLayoutAttribute { std::string attribute; std::string value; ShaderLayoutAttribute(const std::string& attribute, const std::string& value) : attribute(attribute), value(value) {} }; typedef std::vector ShaderLayoutAttributes; struct ShaderLayoutItem { ShaderLayoutAttributes attributes; std::string ioType; std::string variableType; std::string name; ShaderLayoutItem(const ShaderLayoutAttributes& attributes, const std::string& ioType, const std::string& variableType, const std::string& name) : attributes(attributes), ioType(ioType), variableType(variableType), name(name) {} }; typedef std::vector ShaderLayout; struct ShaderVSOUT : public ShaderGlobal { ShaderLocals locals; ShaderVSOUT(const std::string& name, const std::string& ioType, const std::string& variableType, bool array, int amount, const ShaderLocals& locals) : ShaderGlobal(name, ioType, variableType, array, amount), locals(locals) {} }; struct ShaderVersion { int version; bool core; }; struct ShaderStruct { std::string name; ShaderLocals locals; ShaderStruct() {}; ShaderStruct(const std::string& name, const ShaderLocals& locals) : name(name), locals(locals) {} }; typedef std::unordered_map ShaderStructs; typedef ShaderLocal ShaderUniform; typedef std::vector ShaderUniforms; typedef std::unordered_map ShaderDefines; struct ShaderInfo { ShaderVersion version; ShaderLayout layout; ShaderUniforms uniforms; ShaderGlobals globals; ShaderLocals locals; ShaderDefines defines; ShaderStructs structs; ShaderInfo() {} ShaderInfo(const ShaderVersion& version, const ShaderLayout& layout, const ShaderUniforms& uniforms, const ShaderGlobals& globals, const ShaderLocals& locals, const ShaderDefines& defines, const ShaderStructs& structs) : version(version), layout(layout), uniforms(uniforms), globals(globals), locals(locals), defines(defines), structs(structs) {} }; class ShaderParser { public: static ShaderVariableType parseVariableType(const std::string& value); static ShaderInfo parse(const std::string& code); static ShaderInfo parseTokens(TokenStack& tokens); }; }; ================================================ FILE: graphics/legacy/slider.cpp ================================================ #include "core.h" #include "slider.h" #include "path/path.h" #include "renderUtils.h" #include "mesh/primitive.h" Slider::Slider(double x, double y) : Slider(x, y, 0, 1, 0) {} Slider::Slider(double x, double y, double width, double height) : Slider(x, y, width, height, 0, 1, 0) {} Slider::Slider(double x, double y, double min, double max, double value) : Component(x, y) { this->min = min; this->max = max; this->value = value; this->handleColor = GUI::sliderHandleColor; this->backgroundColor = GUI::sliderBackgroundColor; this->foregroundFilledColor = GUI::sliderForegroundFilledColor; this->foregroundEmptyColor = GUI::sliderForegroundEmptyColor; this->handleWidth = GUI::sliderHandleWidth; this->handleHeight = GUI::sliderHandleHeight; this->barWidth = GUI::sliderBarWidth; this->barHeight = GUI::sliderBarHeight; } Slider::Slider(double x, double y, double width, double height, double min, double max, double value) : Component(x, y, width, height) { this->min = min; this->max = max; this->value = value; handleColor = GUI::sliderHandleColor; backgroundColor = GUI::sliderBackgroundColor; foregroundFilledColor = GUI::sliderForegroundFilledColor; foregroundEmptyColor = GUI::sliderForegroundEmptyColor; this->padding = GUI::padding; this->margin = GUI::margin; handleWidth = GUI::sliderHandleWidth; handleHeight = height - 2 * padding; barWidth = width - 2 * padding - handleWidth; barHeight = GUI::sliderBarHeight; } void Slider::render() { if (visible) { Color blendColor = (disabled) ? COLOR::DISABLED : COLOR::WHITE; resize(); Path::rectFilled(position, dimension, 0.0f, COLOR::blend(backgroundColor, blendColor)); double progress = (value - min) / (max - min); Vec2 sliderFilledPosition = position + Vec2(padding + handleWidth / 2, -height / 2 + barHeight / 2); Vec2 sliderFilledDimension = Vec2(barWidth * progress, barHeight); Path::rectFilled(sliderFilledPosition, sliderFilledDimension, 0.0f, COLOR::blend(foregroundFilledColor, blendColor)); Vec2 sliderEmptyPosition = sliderFilledPosition + Vec2(sliderFilledDimension.x, 0); Vec2 sliderEmptyDimension = Vec2(barWidth * (1.0 - progress), barHeight); Path::rectFilled(sliderEmptyPosition, sliderEmptyDimension, 0.0f, COLOR::blend(foregroundEmptyColor, blendColor)); Vec2 handlePosition = Vec2(sliderEmptyPosition.x - handleWidth / 2, position.y - height / 2 + handleHeight / 2); Vec2 handleDimension = Vec2(handleWidth, handleHeight); Path::rectFilled(handlePosition, handleDimension, 0.0f, COLOR::blend(handleColor, blendColor)); Path::rect(handlePosition, handleDimension, 0.0f, COLOR::blend(COLOR::ACCENT, blendColor)); if (debug) Path::rect(position, dimension, 0.0f, COLOR::RED); } } Vec2 Slider::resize() { if (resizing) { dimension = Vec2(GUI::sliderBarWidth + GUI::sliderHandleWidth, GUI::sliderHandleHeight) + Vec2(padding) * 2; } return dimension; } void Slider::drag(Vec2 newPoint, Vec2 oldPoint) { if (disabled) return; press(newPoint); } void Slider::press(Vec2 point) { if (disabled) return; double x = position.x + padding + handleWidth / 2; double w = dimension.x - 2 * padding - handleWidth; if (point.x < x || point.x > x + w) return; value = min + (max - min) * (point.x - x) / w; (*action)(this); } ================================================ FILE: graphics/legacy/slider.h ================================================ #pragma once #include "component.h" class Slider; typedef void(*SliderAction) (Slider*); class Slider : public Component { public: SliderAction action = [] (Slider*) {}; Color handleColor; Color backgroundColor; Color foregroundFilledColor; Color foregroundEmptyColor; double handleWidth; double barWidth; double barHeight; double handleHeight; double min = 0.0; double max = 1.0; double value = 0; Slider(double x, double y); Slider(double x, double y, double min, double max, double value); Slider(double x, double y, double width, double height); Slider(double x, double y, double width, double height, double min, double max, double value); void render() override; Vec2 resize() override; void drag(Vec2 newPoint, Vec2 oldPoint) override; void press(Vec2 point) override; }; ================================================ FILE: graphics/legacy/text.cpp ================================================ #include "core.h" #include "text.h" #pragma region Word Word::Word(double fontSize) : fontSize(fontSize), width(0.0) { } void Word::addCharacter(const Character& character) { characters.push_back(character); width += character.width + (double) character.advance; } #pragma endregion #pragma region Line Line::Line(double maxWidth, double spaceWidth) : maxWidth(maxWidth), spaceWidth(spaceWidth), width(0.0) { } bool Line::addWord(const Word& word) { // Get word width double extraWidth = word.width; // Add space in front extraWidth += (!words.empty() ? spaceWidth : 0.0); if (width + extraWidth <= maxWidth) { words.push_back(word); width += extraWidth; return true; } return false; } #pragma endregion #pragma region Text Text::Text(std::string textString, double x, double y, Font* font, double fontSize, double maxWidth, double spaceWidth, TextFlags textFlags) : textString(textString), x(x), y(y), font(font), fontSize(fontSize), maxWidth(maxWidth), spaceWidth(spaceWidth), textFlags(textFlags) { } #pragma endregion #pragma region TextLoader std::vector TextLoader::loadText(const Text& text) { const int ASCII_SPACE = 32; std::vector lines; Line currentLine(text.maxWidth, text.spaceWidth); Word currentWord(text.fontSize); for (char character : text.textString) { int ascii = (int) character; // Add word if character is a space if (ascii == ASCII_SPACE) { // Check if the word can be added bool added = currentLine.addWord(currentWord); if (!added) { // Add word to new line lines.push_back(currentLine); currentLine = Line(text.maxWidth, text.spaceWidth); currentLine.addWord(currentWord); } currentWord = Word(text.fontSize); } else { // Add character to the word currentWord.addCharacter(text.font->getCharacter(ascii)); } } // Add last word bool added = currentLine.addWord(currentWord); if (!added) { // Add word to new line lines.push_back(currentLine); currentLine = Line(text.maxWidth, text.spaceWidth); currentLine.addWord(currentWord); } // Add last line lines.push_back(currentLine); return lines; } #pragma endregion ================================================ FILE: graphics/legacy/text.h ================================================ #pragma once #include "core.h" #include "font.h" struct Word { // List of characters in this word std::vector characters; // Font size of the word double fontSize; // Width of the word double width; Word(double fontSize); void addCharacter(const Character& character); }; struct Line { // Words of this line std::vector words; // The max width of this line double maxWidth; // The current width of this line double width; // The width of a space character double spaceWidth; Line(double maxWidth, double spaceWidth); bool addWord(const Word& word); }; enum TextFlags : char { // Align text pivot horizontal left TextPivotHL = 1 << 0, // Align text pivot horizontal centered TextPivotHC = 1 << 1, // Align text pivot horizontal right TextPivotHR = 1 << 2, // Align text pivot vertical top TextPivotVT = 1 << 3, // Align text pivot vertical centered TextPivotVC = 1 << 4, // Align text pivot vertical bottom TextPivotVB = 1 << 5 }; class Font; struct Text { std::string textString; Font* font; double fontSize; TextFlags textFlags; double spaceWidth; double x; double y; double maxWidth; Text(std::string textString, double x, double y, Font* font, double fontSize, double maxWidth, double spaceWidth, TextFlags textFlags); }; class TextLoader { public: std::vector loadText(const Text& text); }; ================================================ FILE: graphics/mesh/abstractMesh.cpp ================================================ #include "core.h" #include "abstractMesh.h" #include "buffers/vertexArray.h" #include "renderer.h" namespace P3D::Graphics { AbstractMesh::AbstractMesh(GLFLAG renderMode) : renderMode(renderMode) { vao = new VertexArray(); } AbstractMesh::AbstractMesh() : AbstractMesh(Renderer::TRIANGLES) {} }; ================================================ FILE: graphics/mesh/abstractMesh.h ================================================ #pragma once #include "../buffers/bufferLayout.h" #include "../renderer.h" namespace P3D::Graphics { class VertexArray; class AbstractMesh { public: VertexArray* vao = nullptr; GLFLAG renderMode; AbstractMesh(GLFLAG renderMode); AbstractMesh(); virtual void render() = 0; virtual void close() = 0; }; }; ================================================ FILE: graphics/mesh/arrayMesh.cpp ================================================ #include "core.h" #include "arrayMesh.h" #include "buffers/vertexArray.h" #include "buffers/vertexBuffer.h" #include "renderer.h" namespace P3D::Graphics { ArrayMesh::ArrayMesh(Vec3f* positions, const unsigned int vertexCount) : ArrayMesh(reinterpret_cast(positions), vertexCount * 3, 3) { }; ArrayMesh::ArrayMesh(Vec2f* positions, const unsigned int vertexCount) : ArrayMesh(reinterpret_cast(positions), vertexCount * 2, 2) { }; ArrayMesh::ArrayMesh(const float* positions, const unsigned int vertexCount, const unsigned int dimensions) : ArrayMesh(positions, vertexCount, dimensions, Graphics::Renderer::TRIANGLES) { }; ArrayMesh::ArrayMesh(const float* vertices, const float* uv, const unsigned int vertexCount, const unsigned int dimensions) : AbstractMesh(), vertexCount(vertexCount) { vertexBufferLayout = { {{ "vposition", (dimensions == 2) ? BufferDataType::FLOAT2 : BufferDataType::FLOAT3 }} }; vertexBuffer = new VertexBuffer(vertexBufferLayout, vertices, dimensions * vertexCount * sizeof(float)); uvBufferLayout = { {{ "vUV", BufferDataType::FLOAT2 }} }; uvBuffer = new VertexBuffer(uvBufferLayout, uv, 2 * vertexCount * sizeof(float)); vao->addBuffer(vertexBuffer); vao->addBuffer(uvBuffer); } ArrayMesh::ArrayMesh(const float* vertices, const unsigned int vertexCount, const unsigned int dimensions, unsigned int renderMode) : AbstractMesh(renderMode), vertexCount(vertexCount) { vertexBufferLayout = { {{ "vposition", (dimensions == 2) ? BufferDataType::FLOAT2 : BufferDataType::FLOAT3 }} }; vertexBuffer = new VertexBuffer(vertexBufferLayout, vertices, dimensions * vertexCount * sizeof(float)); vao->addBuffer(vertexBuffer); } void ArrayMesh::render() { vao->bind(); Graphics::Renderer::drawArrays(renderMode, 0, vertexCount); } void ArrayMesh::close() { vertexBuffer->close(); vao->close(); } }; ================================================ FILE: graphics/mesh/arrayMesh.h ================================================ #pragma once #include "abstractMesh.h" #include "../buffers/bufferLayout.h" namespace P3D::Graphics { class VertexBuffer; class ArrayMesh : public AbstractMesh { public: BufferLayout vertexBufferLayout; BufferLayout uvBufferLayout; VertexBuffer* vertexBuffer = nullptr; VertexBuffer* uvBuffer = nullptr; const int vertexCount; ArrayMesh(const float* positions, const float* uv, const unsigned int vertexCount, const unsigned int dimensions); ArrayMesh(const float* positions, const unsigned int vertexCount, const unsigned int dimensions, unsigned int renderMode); ArrayMesh(const float* positions, const unsigned int vertexCount, const unsigned int dimensions); ArrayMesh(Vec3f* positions, const unsigned int vertexCount); ArrayMesh(Vec2f* positions, const unsigned int vertexCount); void render() override; void close() override; }; }; ================================================ FILE: graphics/mesh/indexedMesh.cpp ================================================ #include "core.h" #include "indexedMesh.h" #include "buffers/indexBuffer.h" #include "buffers/vertexBuffer.h" #include "buffers/vertexArray.h" #include "extendedTriangleMesh.h" #include "renderer.h" namespace P3D::Graphics { IndexedMesh::IndexedMesh(const ExtendedTriangleMesh& shape) : AbstractMesh(), vertexCount(shape.vertexCount), triangleCount(shape.triangleCount) { float* vertices = new float[shape.vertexCount * 3]; for (int i = 0; i < vertexCount; i++) { Vec3f vertex = shape.getVertex(i); vertices[i * 3] = vertex.x; vertices[i * 3 + 1] = vertex.y; vertices[i * 3 + 2] = vertex.z; } unsigned int* triangles = new unsigned int[shape.triangleCount * 3]; for (int i = 0; i < triangleCount; i++) { Triangle triangle = shape.getTriangle(i); triangles[i * 3] = triangle.firstIndex; triangles[i * 3 + 1] = triangle.secondIndex; triangles[i * 3 + 2] = triangle.thirdIndex; } BufferLayout vertexBufferLayout = { {{ "vPosition", BufferDataType::FLOAT3 }} }; vertexBuffer = std::make_unique(vertexBufferLayout, vertices, 3 * vertexCount * sizeof(float)); BufferLayout normalBufferLayout = { {{ "vNormal", BufferDataType::FLOAT3 }} }; normalBuffer = std::make_unique(normalBufferLayout, reinterpret_cast(shape.normals.get()), 3 * vertexCount * sizeof(float)); BufferLayout uvBufferLayout = { {{ "vUV", BufferDataType::FLOAT2 }} }; uvBuffer = std::make_unique(uvBufferLayout, reinterpret_cast(shape.uvs.get()), 2 * vertexCount * sizeof(float)); BufferLayout tangentBufferLayout = { {{ "vTangent", BufferDataType::FLOAT3 }} }; tangentBuffer = std::make_unique(tangentBufferLayout, reinterpret_cast(shape.tangents.get()), 3 * vertexCount * sizeof(float)); BufferLayout bitangentBufferLayout = { {{ "vBitangent", BufferDataType::FLOAT3 }} }; bitangentBuffer = std::make_unique(bitangentBufferLayout, reinterpret_cast(shape.bitangents.get()), 3 * vertexCount * sizeof(float)); indexBuffer = std::make_unique(triangles, 3 * triangleCount); vao->addBuffer(vertexBuffer.get()); vao->addBuffer(normalBuffer.get()); vao->addBuffer(uvBuffer.get()); vao->addBuffer(tangentBuffer.get()); vao->addBuffer(bitangentBuffer.get()); } IndexedMesh::IndexedMesh(const float* vertices, const float* normals, const float* uvs, const unsigned int* indices, std::size_t vertexCount, std::size_t triangleCount) : AbstractMesh(), vertexCount(vertexCount), triangleCount(triangleCount) { BufferLayout vertexBufferLayout = { {{ "vposition", BufferDataType::FLOAT3 }} }; vertexBuffer = std::make_unique(vertexBufferLayout, vertices, 3 * vertexCount * sizeof(float)); BufferLayout normalBufferLayout = { {{ "vnormal", BufferDataType::FLOAT3 }} }; normalBuffer = std::make_unique(normalBufferLayout, normals, 3 * vertexCount * sizeof(float)); BufferLayout uvBufferLayout = { {{ "vUV", BufferDataType::FLOAT2 }} }; uvBuffer = std::make_unique(uvBufferLayout, uvs, 2 * vertexCount * sizeof(float)); // TODO Generate tangents and bitangents BufferLayout tangentBufferLayout = { {{ "vTangent", BufferDataType::FLOAT3 }} }; tangentBuffer = std::make_unique(tangentBufferLayout, nullptr, 0); BufferLayout bitangentBufferLayout = { {{ "vBitangent", BufferDataType::FLOAT3 }} }; bitangentBuffer = std::make_unique(bitangentBufferLayout, nullptr, 0); indexBuffer = std::make_unique(indices, 3 * triangleCount); vao->addBuffer(vertexBuffer.get()); vao->addBuffer(normalBuffer.get()); vao->addBuffer(uvBuffer.get()); vao->addBuffer(tangentBuffer.get()); vao->addBuffer(bitangentBuffer.get()); } IndexedMesh::~IndexedMesh() { close(); Log::debug("Closed indexed mesh"); } IndexedMesh::IndexedMesh(IndexedMesh&& other) { vao = other.vao; indexBuffer.swap(other.indexBuffer); vertexBuffer.swap(other.vertexBuffer); normalBuffer.swap(other.normalBuffer); uvBuffer.swap(other.uvBuffer); tangentBuffer.swap(other.tangentBuffer); bitangentBuffer.swap(other.bitangentBuffer); uniformBuffer.swap(other.uniformBuffer); indexBuffer.swap(other.indexBuffer); vertexCount = other.vertexCount; triangleCount = other.triangleCount; // Reset so they cant be deleted by close() other.vao = nullptr; other.indexBuffer = nullptr; other.vertexBuffer = nullptr; other.normalBuffer = nullptr; other.uvBuffer = nullptr; other.bitangentBuffer = nullptr; other.tangentBuffer = nullptr; other.uniformBuffer = nullptr; other.close(); } IndexedMesh& IndexedMesh::operator=(IndexedMesh&& other) { if (this != &other) { close(); std::swap(vao, other.vao); std::swap(indexBuffer, other.indexBuffer); std::swap(vertexBuffer, other.vertexBuffer); std::swap(normalBuffer, other.normalBuffer); std::swap(uvBuffer, other.uvBuffer); std::swap(tangentBuffer, other.tangentBuffer); std::swap(bitangentBuffer, other.bitangentBuffer); std::swap(uniformBuffer, other.uniformBuffer); std::swap(vertexCount, other.vertexCount); std::swap(triangleCount, other.triangleCount); } return *this; } void IndexedMesh::render() { render(Renderer::FILL); } void IndexedMesh::addUniformBuffer(VertexBuffer* uniformBuffer) { this->uniformBuffer = URef(uniformBuffer); vao->addBuffer(uniformBuffer); } void IndexedMesh::updateUniformBuffer(const void* data, size_t sizeInBytes, int offset) { if (uniformBuffer) uniformBuffer->update(data, sizeInBytes, offset); else Log::error("Updating non existing uniform buffer"); } void IndexedMesh::fillUniformBuffer(const void* data, size_t sizeInBytes, GLFLAG mode) { if (uniformBuffer) uniformBuffer->fill(data, sizeInBytes, mode); else Log::error("Filling non existing uniform buffer"); } void IndexedMesh::render(GLFLAG mode) { vao->bind(); indexBuffer->bind(); Renderer::polygonMode(Renderer::FRONT_AND_BACK, mode); Renderer::drawElements(renderMode, triangleCount * 3, Renderer::UINT, 0); Renderer::polygonMode(Renderer::FRONT_AND_BACK, Renderer::FILL); } void IndexedMesh::renderInstanced(size_t primitives) { renderInstanced(primitives, Renderer::FILL); } void IndexedMesh::renderInstanced(size_t primitives, GLFLAG mode) { vao->bind(); indexBuffer->bind(); Renderer::polygonMode(Renderer::FRONT_AND_BACK, mode); Renderer::drawElementsInstanced(renderMode, triangleCount * 3, Renderer::UINT, 0, primitives); Renderer::polygonMode(Renderer::FRONT_AND_BACK, Renderer::FILL); } void IndexedMesh::close() { if (vertexBuffer) vertexBuffer->close(); if (indexBuffer) indexBuffer->close(); if (normalBuffer) normalBuffer->close(); if (uvBuffer) uvBuffer->close(); if (tangentBuffer) tangentBuffer->close(); if (bitangentBuffer) bitangentBuffer->close(); if (uniformBuffer) uniformBuffer->close(); if (vao) vao->close(); vao = nullptr; indexBuffer = nullptr; vertexBuffer = nullptr; normalBuffer = nullptr; uvBuffer = nullptr; tangentBuffer = nullptr; bitangentBuffer = nullptr; uniformBuffer = nullptr; triangleCount = 0; vertexCount = 0; } }; ================================================ FILE: graphics/mesh/indexedMesh.h ================================================ #pragma once #include "abstractMesh.h" #include "../buffers/bufferLayout.h" #include "../renderer.h" #include "../buffers/indexBuffer.h" #include "../buffers/vertexBuffer.h" namespace P3D::Graphics { class IndexBuffer; class VertexBuffer; struct ExtendedTriangleMesh; class IndexedMesh : public AbstractMesh { public: URef indexBuffer = nullptr; URef vertexBuffer = nullptr; URef normalBuffer = nullptr; URef uvBuffer = nullptr; URef tangentBuffer = nullptr; URef bitangentBuffer = nullptr; URef uniformBuffer = nullptr; std::size_t vertexCount; std::size_t triangleCount; IndexedMesh(const ExtendedTriangleMesh& shape); IndexedMesh(const float* vertices, const float* normals, const float* uvs, const unsigned int* indices, std::size_t vertexCount, std::size_t triangleCount); virtual ~IndexedMesh(); IndexedMesh(IndexedMesh&& other); IndexedMesh(const IndexedMesh&) = delete; IndexedMesh& operator=(IndexedMesh&& other); IndexedMesh& operator=(const IndexedMesh&) = delete; void addUniformBuffer(VertexBuffer* uniformBuffer); void updateUniformBuffer(const void* data, size_t sizeInBytes, int offset); void fillUniformBuffer(const void* data, size_t sizeInBytes, GLFLAG mode); void render() override; void render(GLFLAG mode); void renderInstanced(size_t primitives); void renderInstanced(size_t primitives, GLFLAG mode); void close() override; }; }; ================================================ FILE: graphics/mesh/pointMesh.cpp ================================================ #include "core.h" #include "pointMesh.h" #include "buffers/vertexArray.h" #include "buffers/vertexBuffer.h" #include "renderer.h" namespace P3D::Graphics { PointMesh::PointMesh(const float* vertices, const size_t vertexCount, size_t capacity) : AbstractMesh(Renderer::POINT), vertexCount(vertexCount), capacity(capacity) { vertexBufferLayout = BufferLayout({ BufferElement("vposition", BufferDataType::FLOAT3), BufferElement("vsize", BufferDataType::FLOAT), BufferElement("vcolor1", BufferDataType::FLOAT3), BufferElement("vcolor2", BufferDataType::FLOAT3) }); vertexBuffer = new VertexBuffer(vertexBufferLayout, nullptr, 10 * capacity * sizeof(float), Renderer::DYNAMIC_DRAW); vao->addBuffer(vertexBuffer); } void PointMesh::render() { vao->bind(); Renderer::drawArrays(renderMode, 0, vertexCount); } void PointMesh::close() { vertexBuffer->close(); vao->close(); } void PointMesh::update(const float* vertices, const size_t vertexCount) { vertexBuffer->bind(); this->vertexCount = vertexCount; if (vertexCount > capacity) { capacity = vertexCount; Log::warn("Point buffer overflow, creating new buffer with size (%d)", vertexCount); vertexBuffer->fill(vertices, capacity * vertexBufferLayout.stride * sizeof(float), Graphics::Renderer::DYNAMIC_DRAW); } else { vertexBuffer->update(vertices, vertexCount * vertexBufferLayout.stride * sizeof(float), 0); } } }; ================================================ FILE: graphics/mesh/pointMesh.h ================================================ #pragma once #include "abstractMesh.h" namespace P3D::Graphics { class VertexBuffer; class PointMesh : public AbstractMesh { public: VertexBuffer* vertexBuffer = nullptr; BufferLayout vertexBufferLayout; size_t vertexCount; size_t capacity; PointMesh(const float* vertices, const size_t size) : PointMesh(vertices, size, size) {}; PointMesh(const float* vertices, const size_t size, size_t capacity); void update(const float* vertices, const size_t vertexCount); void render() override; void close() override; }; }; ================================================ FILE: graphics/mesh/primitive.h ================================================ #pragma once #include "../graphics/renderer.h" #include namespace P3D::Graphics { struct Primitive { unsigned int vao; unsigned int vbo; int M; int N; template void resize(float(&vertices)[M][N]) { using namespace Renderer; bindVertexArray(vao); bindBuffer(ARRAY_BUFFER, vbo); bufferSubData(ARRAY_BUFFER, 0, M * N * sizeof(float), vertices); bindBuffer(ARRAY_BUFFER, 0); bindVertexArray(0); } virtual void render() = 0; protected: // M is size of vertex, N is amount of vertices Primitive(int M, int N) { using namespace Renderer; this->M = M; this->N = N; genVertexArrays(1, &vao); genBuffers(1, &vbo); bindVertexArray(vao); bindBuffer(ARRAY_BUFFER, vbo); bufferData(ARRAY_BUFFER, sizeof(float) * M * N, 0, DYNAMIC_DRAW); enableVertexAttribArray(0); vertexAttribPointer(0, M, FLOAT, false, 0, 0); bindBuffer(ARRAY_BUFFER, 0); bindVertexArray(0); } ~Primitive() { using namespace Renderer; delBuffers(1, &vbo); delVertexArrays(1, &vao); } }; // Quad struct Quad : public Primitive { Quad() : Primitive(4, 4) { resize(Vec2f(-1, 1), Vec2f(2.0f, 2.0f)); } Quad(Vec2f topLeft, Vec2f dimension) : Primitive(4, 4) { resize(topLeft, dimension); } void resize(Vec2f position, Vec2f dimension, Vec2f xRange, Vec2f yRange) { float dx = xRange.y - xRange.x; float dy = yRange.y - yRange.x; float u = (position.x - xRange.x) / dx; float v = (position.y - dimension.y - yRange.x) / dy; float du = dimension.x / dx; float dv = dimension.y / dy; float vertices[4][4] = { { position.x, position.y , u, v + dv}, { position.x, position.y - dimension.y, u, v }, { position.x + dimension.x, position.y - dimension.y, u + du, v }, { position.x + dimension.x, position.y , u + du, v + dv} }; Primitive::resize(vertices); } void resize(Vec2f position, Vec2f dimension) { float vertices[4][4] = { { position.x, position.y , 0.0f, 1.0f}, { position.x, position.y - dimension.y, 0.0f, 0.0f}, { position.x + dimension.x, position.y - dimension.y, 1.0f, 0.0f}, { position.x + dimension.x, position.y , 1.0f, 1.0f} }; Primitive::resize(vertices); } void render(int mode) { using namespace Renderer; polygonMode(FRONT_AND_BACK, mode); bindVertexArray(vao); drawArrays(QUADS, 0, 4); bindVertexArray(0); polygonMode(FRONT_AND_BACK, FILL); } void render() override { render(Renderer::FILL); } }; // Line struct LinePrimitive : public Primitive { void resize(Vec3f p1, Vec3f p2) { float vertices[2][3] = { { p1.x, p1.y, p1.z }, { p2.x, p2.y, p2.z } }; Primitive::resize(vertices); } void resize(Vec2f p1, Vec2f p2) { float vertices[2][3] = { { p1.x, p1.y, 0 }, { p2.x, p2.y, 0 } }; Primitive::resize(vertices); } LinePrimitive() : Primitive(3, 2) { resize(Vec3f::full(-1.0f), Vec3f::full(1.0f)); } void render(int mode) { using namespace Renderer; polygonMode(FRONT_AND_BACK, mode); bindVertexArray(vao); drawArrays(LINE_STRIP, 0, 2); bindVertexArray(0); polygonMode(FRONT_AND_BACK, FILL); } void render() override { render(Renderer::FILL); } }; // Triangle struct TrianglePrimitive : public Primitive { bool patched; TrianglePrimitive(bool patched = false) : Primitive(3, 3) { this->patched = patched; resize(Vec3f::full(-1.0f), Vec3f::full(1.0f), Vec3f(0.0f, 1.0f, -1.0f)); } void resize(Vec3f p1, Vec3f p2, Vec3f p3) { float vertices[3][3] = { { p1.x, p1.y, p1.z }, { p2.x, p2.y, p2.z }, { p3.x, p3.y, p3.z } }; Primitive::resize(vertices); } void resize(Vec2f p1, Vec2f p2, Vec2f p3) { float vertices[3][3] = { { p1.x, p1.y, 0 }, { p2.x, p2.y, 0 }, { p3.x, p3.y, 0 } }; Primitive::resize(vertices); } void render(int mode) { using namespace Renderer; polygonMode(FRONT_AND_BACK, mode); bindVertexArray(vao); drawArrays(patched ? PATCHES : TRIANGLES, 0, 3); bindVertexArray(0); polygonMode(FRONT_AND_BACK, FILL); } void render() override { render(Renderer::FILL); } }; }; ================================================ FILE: graphics/mesh/vectorMesh.cpp ================================================ #include "core.h" #include "vectorMesh.h" #include "renderer.h" #include "buffers/vertexBuffer.h" #include "buffers/vertexArray.h" namespace P3D::Graphics { VectorMesh::VectorMesh(const float* vertices, const size_t vertexCount, size_t capacity) : AbstractMesh(Graphics::Renderer::POINT), vertexCount(vertexCount), capacity(capacity) { vertexBufferLayout = BufferLayout({ BufferElement("vposition", BufferDataType::FLOAT3), BufferElement("vdirection", BufferDataType::FLOAT3), BufferElement("vcolor", BufferDataType::FLOAT3) }); vertexBuffer = new VertexBuffer(vertexBufferLayout, vertices, 9 * capacity * sizeof(float), Graphics::Renderer::DYNAMIC_DRAW); vao->addBuffer(vertexBuffer); } void VectorMesh::render() { vao->bind(); Graphics::Renderer::drawArrays(renderMode, 0, vertexCount); } void VectorMesh::close() { vertexBuffer->close(); vao->close(); } void VectorMesh::update(const float* vertices, const size_t vertexCount) { vertexBuffer->bind(); this->vertexCount = vertexCount; if (vertexCount > capacity) { capacity = vertexCount; Log::warn("Vector buffer overflow, creating new buffer with size (%d)", vertexCount); vertexBuffer->fill(vertices, capacity * vertexBufferLayout.stride * sizeof(float), Graphics::Renderer::DYNAMIC_DRAW); } else { vertexBuffer->update(vertices, vertexCount * vertexBufferLayout.stride * sizeof(float), 0); } } }; ================================================ FILE: graphics/mesh/vectorMesh.h ================================================ #pragma once #include "abstractMesh.h" namespace P3D::Graphics { class VertexBuffer; class VectorMesh : public AbstractMesh { public: VertexBuffer* vertexBuffer = nullptr; BufferLayout vertexBufferLayout; size_t vertexCount; size_t capacity; VectorMesh(const float* vertices, const size_t size) : VectorMesh(vertices, size, size) {}; VectorMesh(const float* vertices, const size_t size, size_t capacity); void update(const float* vertices, const size_t vertexCount); void render() override; void close() override; }; }; ================================================ FILE: graphics/meshRegistry.cpp ================================================ #include "core.h" #include "meshRegistry.h" #include #include #include #include "mesh/indexedMesh.h" #include #include #include namespace P3D::Graphics::MeshRegistry { std::vector meshes; std::map shapeClassMeshIds; Comp::Mesh box; Comp::Mesh sphere; Comp::Mesh cylinder; Comp::Mesh wedge; Comp::Mesh corner; Comp::Mesh hexagon; Comp::Mesh quad; ExtendedTriangleMesh createQuad(const Vec3f& center, const Vec3f& normal, const Vec2f& dimension) { Vec3f up(0.0f, 1.0f, 0.0f); Vec3f u = up % normal; Vec3f v = normal % u; Vec3f du = u * dimension.x / 2.0f; Vec3f dv = v * dimension.y / 2.0f; Vec3f bl = center - du - dv; Vec3f br = center + du - dv; Vec3f tl = center - du + dv; Vec3f tr = center + du + dv; Vec3f* vertexBuffer = new Vec3f[4] { bl, br, tl, tr }; Triangle* triangleBuffer = new Triangle[2] { Triangle { 0, 1, 3 }, Triangle { 0, 3, 2 } }; Vec3f* normalBuffer = new Vec3f[4] { normal, normal, normal, normal, }; Vec2f* uvBuffer = new Vec2f[4] { Vec2f { 0.0f, 0.0f }, Vec2f { 1.0f, 0.0f }, Vec2f { 0.0f, 1.0f }, Vec2f { 1.0f, 1.0f }, }; ExtendedTriangleMesh mesh(vertexBuffer, 4, triangleBuffer, 2); mesh.setNormalBuffer(SRef(normalBuffer)); mesh.setUVBuffer(SRef(uvBuffer)); return mesh; } ExtendedTriangleMesh createCylinder(int sides, double radius, double height) { if (sides < 2) throw std::logic_error("Cannot create cylinder with <2 sides"); int vertexCount = sides * 4; Vec3f* vertexBuffer = new Vec3f[vertexCount]; float r = static_cast(radius); float h = static_cast(height); // vertices for (int i = 0; i < sides; i++) { float angle = i * pi() * 2 / sides; Vec3f bottom(std::cos(angle) * r, std::sin(angle) * r, h / 2); Vec3f top(std::cos(angle) * r, std::sin(angle) * r, -h / 2); vertexBuffer[i * 2] = bottom; vertexBuffer[i * 2 + 1] = top; vertexBuffer[i * 2 + sides * 2] = bottom; vertexBuffer[i * 2 + 1 + sides * 2] = top; } int triangleCount = sides * 2 + (sides - 2) * 2; Triangle* triangleBuffer = new Triangle[triangleCount]; // sides for (int i = 0; i < sides; i++) { int bottomLeft = i * 2; int bottomRight = ((i + 1) % sides) * 2; triangleBuffer[i * 2] = Triangle{bottomLeft, bottomLeft + 1, bottomRight}; // botLeft, botRight, topLeft triangleBuffer[i * 2 + 1] = Triangle{bottomRight + 1, bottomRight, bottomLeft + 1}; // topRight, topLeft, botRight } Triangle* capOffset = triangleBuffer + static_cast(sides) * 2; // top and bottom for (int i = 0; i < sides - 2; i++) { // common corner is i=0 capOffset[i] = Triangle{sides * 2 + 0, sides * 2 + (i + 1) * 2, sides * 2 + (i + 2) * 2}; capOffset[i + (sides - 2)] = Triangle{sides * 2 + 1, sides * 2 + (i + 2) * 2 + 1, sides * 2 + (i + 1) * 2 + 1}; } Vec3f* normalBuffer = new Vec3f[vertexCount]; for (int i = 0; i < sides * 2; i++) { Vec3f vertex = vertexBuffer[i]; normalBuffer[i] = normalize(Vec3(vertex.x, vertex.y, 0)); } for (int i = 0; i < sides; i++) { normalBuffer[i * 2 + sides * 2] = Vec3f(0, 0, 1); normalBuffer[i * 2 + sides * 2 + 1] = Vec3f(0, 0, -1); } Vec2f* uvBuffer = new Vec2f[vertexCount]; for (int i = 0; i < vertexCount; i++) { Vec3f& p = vertexBuffer[i]; float u; float v = p.y / h; if (p.x >= 0) { if (p.x == 0) { u = std::asin(p.y / std::sqrt(p.x * p.x + p.y * p.y)); } else { u = std::atan2(p.y, p.x); } } else { u = -std::asin(p.y / std::sqrt(p.x * p.x + p.y * p.y)) + PI; } uvBuffer[i] = Vec2f(u, v); } ExtendedTriangleMesh cylinderShape(vertexBuffer, vertexCount, triangleBuffer, triangleCount); cylinderShape.setNormalBuffer(SRef(normalBuffer)); cylinderShape.setUVBuffer(SRef(uvBuffer)); return cylinderShape; } ExtendedTriangleMesh createSphere(double radius, int steps) { Polyhedron sphere(ShapeLibrary::createSphere(static_cast(radius), steps)); ExtendedTriangleMesh sphereShape(sphere); int i = 0; Vec3f* normalBuffer = new Vec3f[sphereShape.vertexCount]; Vec2f* uvBuffer = new Vec2f[sphereShape.vertexCount]; for (Vec3f vertex : sphereShape.iterVertices()) { Vec3f normal = normalize(vertex); Vec2f uv = Vec2f(atan2(normal.x, normal.z) / (2 * PI) + 0.5, normal.y * 0.5 + 0.5); normalBuffer[i] = normal; uvBuffer[i] = uv; i++; } sphereShape.setNormalBuffer(SRef(normalBuffer)); sphereShape.setUVBuffer(SRef(uvBuffer)); return sphereShape; } ExtendedTriangleMesh createBox(float width, float height, float depth) { Polyhedron box(ShapeLibrary::createBox(width, height, depth)); ExtendedTriangleMesh boxShape = ExtendedTriangleMesh::generateSplitNormalsShape(box); Vec2f* uvBuffer = new Vec2f[boxShape.triangleCount * 3]; for (std::size_t ti = 0; ti < boxShape.triangleCount; ti++) { Triangle t = boxShape.getTriangle(ti); Vec3f v[3]; v[0] = boxShape.getVertex(t.firstIndex); v[1] = boxShape.getVertex(t.secondIndex); v[2] = boxShape.getVertex(t.thirdIndex); Vec3f normalVec = boxShape.getNormalVecOfTriangle(t); int side = getAbsMaxElementIndex(normalVec); Vec3f sizes{width, height, depth}; for (std::size_t i = 0; i < 3; i++) { Vec2f vec = withoutIndex(v[i], side); Vec2f dim = withoutIndex(sizes, side); vec.x = (vec.x + dim.x / 2.0) / dim.x; vec.y = (vec.y + dim.y / 2.0) / dim.y; uvBuffer[ti * 3 + i] = vec; } } boxShape.setUVBuffer(SRef(uvBuffer)); return boxShape; } ExtendedTriangleMesh createCube(float size) { return createBox(size, size, size); } ExtendedTriangleMesh createHexagon(float radius, float height) { ExtendedTriangleMesh hexagonMesh(ShapeLibrary::createPrism(6, radius, height)); return hexagonMesh; } void generateCylindricalUVs(ExtendedTriangleMesh& mesh) { BoundingBox b = mesh.getBounds(); Vec2f* uvBuffer = new Vec2f[mesh.vertexCount]; for (int i = 0; i < mesh.vertexCount; i++) { Vec3f p = mesh.getVertex(i); p.x /= static_cast(b.getWidth()); p.y /= static_cast(b.getHeight()); p.z /= static_cast(b.getDepth()); float phi = std::atan2(p.x, p.z); float u = phi / two_pi(); float v = (p.y + 1.0f) / 2.0f; uvBuffer[i] = Vec2f(u, v); } mesh.setUVBuffer(SRef(uvBuffer)); } void generateSphericalUVs(ExtendedTriangleMesh& mesh) { BoundingBox b = mesh.getBounds(); Vec2f* uvBuffer = new Vec2f[mesh.vertexCount]; for (int i = 0; i < mesh.vertexCount; i++) { Vec3f p = mesh.getVertex(i); p.x /= static_cast(b.getWidth()); p.y /= static_cast(b.getHeight()); p.z /= static_cast(b.getDepth()); float phi = std::atan2(p.x, p.z); float theta = std::acos(p.y); float u = phi / two_pi(); float v = 1.0f - theta * pi(); uvBuffer[i] = Vec2f(u, v); } mesh.setUVBuffer(SRef(uvBuffer)); } void generateLightProbeUVs(ExtendedTriangleMesh& mesh) { BoundingBox b = mesh.getBounds(); Vec2f* uvBuffer = new Vec2f[mesh.vertexCount]; for (int i = 0; i < mesh.vertexCount; i++) { Vec3f p = mesh.getVertex(i); p.x /= static_cast(b.getWidth()); p.y /= static_cast(b.getHeight()); p.z /= static_cast(b.getDepth()); float alpha = std::acos(p.z); float sinBeta = p.y / std::sqrt(p.x * p.x + p.y * p.y); float cosBeta = p.x / std::sqrt(p.x * p.x + p.y * p.y); float u = (1.0f + alpha / pi() * cosBeta) / 2.0f; float v = (1.0f + alpha / pi() * sinBeta) / 2.0f; uvBuffer[i] = Vec2f(u, v); } mesh.setUVBuffer(SRef(uvBuffer)); } void init() { sphere = registerShapeClass(&SphereClass::instance, ExtendedTriangleMesh::generateSmoothNormalsShape(SphereClass::instance.asPolyhedron())); cylinder = registerShapeClass(&CylinderClass::instance, createCylinder(64, 1.0, 2.0)); box = registerShapeClass(&CubeClass::instance, createCube(2)); wedge = registerShapeClass(&WedgeClass::instance); corner = registerShapeClass(&CornerClass::instance); hexagon = registerShape(createHexagon(0.5, 1.0)); quad = registerShape(createQuad(Vec3f(0.0f, 2.0f, 0.0f), Vec3f(0.0f, 0.0f, 1.0f), Vec2f(1.0f, 1.0f))); } Comp::Mesh registerShape(IndexedMesh* mesh, Comp::Mesh::Flags flags) { std::size_t id = meshes.size(); meshes.push_back(mesh); return Comp::Mesh(id, flags); } Comp::Mesh registerShape(const ExtendedTriangleMesh& mesh) { std::size_t id = meshes.size(); meshes.push_back(new IndexedMesh(mesh)); return Comp::Mesh(id, mesh.getFlags()); } Comp::Mesh registerShapeClass(const ShapeClass* shapeClass, const ExtendedTriangleMesh& mesh) { Comp::Mesh meshData = registerShape(mesh); auto iterator = shapeClassMeshIds.find(shapeClass); if (iterator != shapeClassMeshIds.end()) iterator->second = meshData; else shapeClassMeshIds.emplace(shapeClass, meshData); return meshData; } Comp::Mesh registerShapeClass(const ShapeClass* shapeClass) { auto iterator = shapeClassMeshIds.find(shapeClass); if (iterator != shapeClassMeshIds.end()) return iterator->second; ExtendedTriangleMesh shape = ExtendedTriangleMesh::generateSplitNormalsShape(shapeClass->asPolyhedron()); generateSphericalUVs(shape); return registerShapeClass(shapeClass, shape); } Comp::Mesh getMesh(const ShapeClass* shapeClass) { auto iterator = shapeClassMeshIds.find(shapeClass); if (iterator != shapeClassMeshIds.end()) return iterator->second; else return registerShapeClass(shapeClass); } std::size_t getID(const ShapeClass* shapeClass) { auto iterator = shapeClassMeshIds.find(shapeClass); if (iterator != shapeClassMeshIds.end()) return iterator->second.id; else return registerShapeClass(shapeClass).id; } IndexedMesh* get(std::size_t id) { return meshes[id]; } IndexedMesh* get(const Comp::Mesh& mesh) { if (mesh.id >= meshes.size()) return nullptr; return get(mesh.id); } IndexedMesh* get(const ShapeClass* shapeClass) { return get(getID(shapeClass)); } }; ================================================ FILE: graphics/meshRegistry.h ================================================ #pragma once #include "extendedTriangleMesh.h" #include "mesh/indexedMesh.h" #include namespace P3D::Graphics::MeshRegistry { extern std::vector meshes; extern std::map shapeClassMeshIds; extern Comp::Mesh box; extern Comp::Mesh sphere; extern Comp::Mesh cylinder; extern Comp::Mesh wedge; extern Comp::Mesh corner; extern Comp::Mesh hexagon; extern Comp::Mesh quad; void init(); Comp::Mesh registerShape(IndexedMesh* mesh, Comp::Mesh::Flags flags); Comp::Mesh registerShape(const ExtendedTriangleMesh& mesh); Comp::Mesh registerShapeClass(const ShapeClass* shapeClass, const ExtendedTriangleMesh& mesh); Comp::Mesh registerShapeClass(const ShapeClass* shapeClass); Comp::Mesh getMesh(const ShapeClass* shapeClass); std::size_t getID(const ShapeClass* shapeClass); IndexedMesh* get(std::size_t id); IndexedMesh* get(const Comp::Mesh& mesh); IndexedMesh* get(const ShapeClass* shapeClass); void generateCylindricalUVs(ExtendedTriangleMesh& mesh); void generateSphericalUVs(ExtendedTriangleMesh& mesh); void generateLightProbeUVs(ExtendedTriangleMesh& mesh); ExtendedTriangleMesh createCube(float size); ExtendedTriangleMesh createBox(float width, float height, float depth); ExtendedTriangleMesh createSphere(double radius, int steps = 1); ExtendedTriangleMesh createCylinder(int sides, double radius, double height); ExtendedTriangleMesh createHexagon(float radius, float height); }; ================================================ FILE: graphics/path/path.cpp ================================================ #include "core.h" #include "path.h" #include "font.h" #include #include "../batch/guiBatch.h" #define DEFAULT_PATTERN_2D(color) [color] (int i, const Vec2f& p) { return color; } namespace P3D::Graphics { namespace Path { #pragma region Batch //! Batch GuiBatch* batch = nullptr; void bind(GuiBatch* batch) { Path::batch = batch; } void submit() { if (batch) batch->submit(); } // Adds the vertices to the batch with the necessary indices, this does not reserve space on the batch. //? Expects vertices in counter-clockwise order void pushQuad(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Color& colorA = Color(1), const Color& colorB = Color(1), const Color& colorC = Color(1), const Color& colorD = Color(1), const Vec2f& uvA = Vec2f(0, 0), const Vec2f& uvB = Vec2f(1, 0), const Vec2f& uvC = Vec2f(1, 1), const Vec2f& uvD = Vec2f(0, 1)) { Path::batch->pushVertices({ { a, uvA, colorA }, { b, uvB, colorB }, { c, uvC, colorC }, { d, uvD, colorD } }); Path::batch->pushIndices({ 0, 1, 2, 2, 3, 0 }); Path::batch->endIndex(); } // Adds the vertices to the batch with the necessary indices, this does not reserve space on the batch //? Expects vertices in counter-clockwise order void pushTriangle(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Color& colorA = Color(1), const Color& colorB = Color(1), const Color& colorC = Color(1), const Vec2f& uvA = Vec2f(0, 0), const Vec2f& uvB = Vec2f(1, 0), const Vec2f& uvC = Vec2f(0.5, 1)) { Path::batch->pushVertices({ { a, uvA, colorA }, { b, uvB, colorB }, { c, uvC, colorC } }); Path::batch->pushIndices({ 0, 1, 2 }); Path::batch->endIndex(); } // Adds the vertices to the batch with the necessary indices, this does not reserve space on the batch void pushLine(const Vec2f& a, const Vec2f& b, const Color& colorA, const Color& colorB, float thickness) { Vec2f dxy = normalize(Vec2f(b.y - a.y, a.x - b.x)) / GUI::windowInfo.dimension.y * 3.0 * thickness; pushQuad(Vec2f(a + dxy), Vec2f(b + dxy), Vec2f(b - dxy), Vec2f(a - dxy), colorA, colorB, colorB, colorA); } #pragma endregion #pragma region Primitives //! Primitives void line(const Vec2f& a, const Vec2f& b, const Color& color, float thickness) { line(a, b, color, color, thickness); } void line(const Vec2f& a, const Vec2f& b, const Color& colorA, const Color& colorB, float thickness) { size_t vertexCount = 4; size_t indexCount = 6; Path::batch->reserve(vertexCount, indexCount); pushLine(a, b, colorA, colorB, thickness); } void circle(const Vec2f& center, float radius, const Color& color, float thickness, size_t precision) { size_t vertexCount = 4 * precision; size_t indexCount = 6 * precision; Path::batch->reserve(vertexCount, indexCount); Vec2f oldPoint = center + Vec2f(radius, 0); float step = 2.0 * PI / (float) precision; for (size_t i = 1; i <= precision; i++) { float angle = i * step; Vec2f newPoint = Vec2f(center.x + radius * cos(angle), center.y + radius * sin(angle)); pushLine(oldPoint, newPoint, color, color, thickness); oldPoint = newPoint; } } void circleFilled(const Vec2f& center, float radius, const Color& color, size_t precision) { size_t vertexCount = precision; size_t indexCount = 3 * (precision - 2); Path::batch->reserve(vertexCount, indexCount); float step = 2.0f * PI / (float) precision; for (size_t i = 0; i < precision; i++) { float angle = i * step; Vec2f point = Vec2f(center.x + radius * cos(angle), center.y + radius * sin(angle)); Path::batch->pushVertex({ point, Vec2(1.0, 1.0), color }); } for (size_t i = 1; i < precision - 1; i++) Path::batch->pushIndices({ 0, i + 1, i }); Path::batch->endIndex(); } void circleSegment(const Vec2f& center, float radius, float minAngle, float maxAngle, bool sides, const Color& color, float thickness, size_t precision) { size_t vertexCount = 4 * (precision + (sides? 2 : 0)); size_t indexCount = 6 * (precision + (sides? 2 : 0)); Path::batch->reserve(vertexCount, indexCount); Vec2f oldPoint = Vec2f(center.x + radius * cos(minAngle), center.y + radius * sin(minAngle)); if (sides) pushLine(center, oldPoint, color, color, thickness); float step = (maxAngle - minAngle) / (float)precision; for (size_t i = 1; i <= precision; i++) { float angle = minAngle + i * step; Vec2f newPoint = Vec2f(center.x + radius * cos(angle), center.y + radius * sin(angle)); pushLine(oldPoint, newPoint, color, color, thickness); oldPoint = newPoint; } if (sides) pushLine(center, oldPoint, color, color, thickness); } void circleSegmentFilled(const Vec2f& center, float radius, float minAngle, float maxAngle, const Color& color, size_t precision) { size_t vertexCount = precision + 2; size_t indexCount = 3 * precision; Path::batch->reserve(vertexCount, indexCount); Vec2f oldPoint = Vec2f(center.x + radius * cos(minAngle), center.y + radius * sin(minAngle)); Path::batch->pushVertex({ center, Vec2f(1.0f, 1.0f), color }); Path::batch->pushVertex({ oldPoint, Vec2f(1.0f, 1.0f), color }); float step = (maxAngle - minAngle) / (float) precision; for (size_t i = 1; i <= precision; i++) { float angle = minAngle + i * step; Vec2f newPoint = Vec2f(center.x + radius * cos(angle), center.y + radius * sin(angle)); Path::batch->pushVertex({ newPoint, Vec2(1.0, 1.0), color }); } for (size_t i = 1; i <= precision + 1; i++) Path::batch->pushIndices({ 0, i + 1, i }); Path::batch->endIndex(); } void triangle(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Color& color, float thickness) { size_t vertexCount = 4 * 3; size_t indexCount = 6 * 3; Path::batch->reserve(vertexCount, indexCount); pushLine(a, b, color, color, thickness); pushLine(b, c, color, color, thickness); pushLine(c, a, color, color, thickness); } void triangleFilled(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Color& color) { size_t vertexCount = 3; size_t indexCount = 3; Path::batch->reserve(vertexCount, indexCount); pushTriangle(a, b, c, color, color, color); } void rect(const Vec2f& pos, const Vec2f& dim, float rounding, const Color& color, float thickness) { //if (rounding == 0.0) { quad(pos, pos + Vec2f(dim.x, 0), pos + Vec2f(dim.x, dim.y), pos + Vec2f(0, dim.y), color, thickness); //} // TODO add rounding } void rectFilled(const Vec2f& pos, const Vec2f& dim, float rounding, const Color& color) { //if (rounding == 0) { size_t vertexCount = 4; size_t indexCount = 6; Path::batch->reserve(vertexCount, indexCount); pushQuad(pos, pos + Vec2f(dim.x, 0), pos + Vec2f(dim.x, dim.y), pos + Vec2f(0, dim.y), color, color, color, color); //} // TODO add rounding } void rectUV(GLID id, const Vec2f& pos, const Vec2f& dim, const Vec2f& uvMin, const Vec2f& uvMax, const Color& color) { size_t vertexCount = 4; size_t indexCount = 6; Vec2f a = pos; Vec2f b = Vec2f(pos.x + dim.x, pos.y); Vec2f c = Vec2f(pos.x + dim.x, pos.y + dim.y); Vec2f d = Vec2f(pos.x, pos.y + dim.y); Vec2f uvA = uvMin; Vec2f uvB = Vec2f(uvMax.x, uvMin.y); Vec2f uvC = uvMax; Vec2f uvD = Vec2f(uvMin.x, uvMax.y); Path::batch->pushCommand(0); Path::batch->reserve(vertexCount, indexCount); pushQuad(a, b, c, d, color, color, color, color, uvA, uvB, uvC, uvD); Path::batch->pushCommand(id); } void rectUVRange(GLID id, const Vec2f& pos, const Vec2f& dim, const Vec2f& xRange, const Vec2f& yRange, const Color& color) { size_t vertexCount = 4; size_t indexCount = 6; float dx = xRange.y - xRange.x; float dy = yRange.y - yRange.x; float u = (pos.x - xRange.x) / dx; float v = (pos.y - dim.y - yRange.x) / dy; float du = dim.x / dx; float dv = dim.y / dy; Vec2f a = pos; Vec2f b = Vec2f(pos.x + dim.x, pos.y); Vec2f c = Vec2f(pos.x + dim.x, pos.y - dim.y); Vec2f d = Vec2f(pos.x, pos.y - dim.y); Vec2f uvA = Vec2f(u, v + dv); Vec2f uvB = Vec2f(u + du, v + dv); Vec2f uvC = Vec2f(u + du, v); Vec2f uvD = Vec2f(u, v); Path::batch->pushCommand(0); Path::batch->reserve(vertexCount, indexCount); pushQuad(a, b, c, d, color, color, color, color, uvA, uvB, uvC, uvD); Path::batch->pushCommand(id); } void quad(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Color& color, float thickness) { size_t vertexCount = 4 * 4; size_t indexCount = 6 * 4; Path::batch->reserve(vertexCount, indexCount); pushLine(a, b, color, color, thickness); pushLine(b, c, color, color, thickness); pushLine(c, d, color, color, thickness); pushLine(d, a, color, color, thickness); } void quadFilled(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Color& color) { size_t vertexCount = 4; size_t indexCount = 6; Path::batch->reserve(vertexCount, indexCount); pushQuad(a, b, c, d, color, color, color, color); } void quadUV(GLID id, const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Vec2f& uvA, const Vec2f& uvB, const Vec2f& uvC, const Vec2f& uvD) { size_t vertexCount = 4; size_t indexCount = 6; Path::batch->pushCommand(0); Path::batch->reserve(vertexCount, indexCount); pushQuad(a, b, c, d, Color(1), Color(1), Color(1), Color(1), uvA, uvB, uvC, uvD); Path::batch->pushCommand(id); } void text(Font* font, const std::string& text, double size, const Vec2f& pos, const Color& color, char textPivot) { if (text.empty()) return; size_t vertexCount = 4 * text.size(); size_t indexCount = 6 * text.size(); Vec2f textSize = font->size(text, size); // TextPivotHL default float x = pos.x; // TextPivotVB default float y = pos.y; if (textPivot & TextPivotHC) x -= textSize.x / 2.0f; if (textPivot & TextPivotHR) x -= textSize.x; if (textPivot & TextPivotVT) y -= textSize.y; if (textPivot & TextPivotVC) y -= textSize.y / 2.0f; Path::batch->pushCommand(0); Path::batch->reserve(vertexCount, indexCount); for (char chr : text) { int ascii = (int) chr; const Character& character = font->getCharacter(ascii); float descend = character.height - character.by; float xpos = x + character.bx * size; float ypos = y - descend * size; float w = character.width * size; float h = character.height * size; float s = float(character.x) / font->getAtlasWidth(); float t = float(character.y) / font->getAtlasHeight(); float ds = float(character.width) / font->getAtlasWidth(); float dt = float(character.height) / font->getAtlasHeight(); Vec2f a = Vec2f(xpos, ypos); Vec2f b = Vec2f(xpos + w, ypos); Vec2f c = Vec2f(xpos + w, ypos + h); Vec2f d = Vec2f(xpos, ypos + h); Vec2f uvA = Vec2f(s, t + dt); Vec2f uvB = Vec2f(s + ds, t + dt); Vec2f uvC = Vec2f(s + ds, t); Vec2f uvD = Vec2f(s, t); pushQuad(a, b, c, d, color, color, color, color, uvA, uvB, uvC, uvD); x += (character.advance >> 6) * size; } Path::batch->pushCommand(font->getAtlasID()); } void bezier(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Color& color, float thickness, size_t precision) { bezier(a, b, c, d, DEFAULT_PATTERN_2D(color), thickness, precision); } void bezier(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, Pattern2D pattern, float thickness, size_t precision) { size_t vertexCount = 4 * precision; size_t indexCount = 6 * precision; Path::batch->reserve(vertexCount, indexCount); Vec2f oldPoint = a; Color oldColor = pattern(0, oldPoint); float step = 1.0f / (float)precision; for (size_t i = 1; i <= precision; i++) { float t = i * step; float s = 1 - t; float w1 = s * s * s; float w2 = 3.0f * s * s * t; float w3 = 3.0f * s * t * t; float w4 = t * t * t; Vec2f newPoint = w1 * a + w2 * b + w3 * c + w4 * d; Color newColor = pattern(i, newPoint); pushLine(oldPoint, newPoint, oldColor, newColor, thickness); oldPoint = newPoint; oldColor = newColor; } } void bezierHorizontal(const Vec2f& start, const Vec2f& end, const Color& color, float thickness, size_t precision) { bezierHorizontal(start, end, DEFAULT_PATTERN_2D(color), thickness, precision); } void bezierHorizontal(const Vec2f& start, const Vec2f& end, Pattern2D pattern, float thickness, size_t precision) { float mid = (start.x + end.x) / 2.0f; Vec2f c1 = Vec2f(mid, start.y); Vec2f c2 = Vec2f(mid, end.y); bezier(start, c1, c2, end, pattern, thickness, precision); } void bezierVertical(const Vec2f& start, const Vec2f& end, const Color& color, float thickness, size_t precision) { bezierVertical(start, end, DEFAULT_PATTERN_2D(color), thickness, precision); } void bezierVertical(const Vec2f& start, const Vec2f& end, Pattern2D pattern, float thickness, size_t precision) { float mid = (start.y + end.y) / 2.0f; Vec2f c1 = Vec2f(start.x, mid); Vec2f c2 = Vec2f(end.x, mid); bezier(start, c1, c2, end, pattern, thickness, precision); } void polyLine(Vec2f* points, size_t size, const Color& color, float thickness, bool closed) { polyLine(points, size, DEFAULT_PATTERN_2D(color), thickness, closed); } void polyLine(Vec2f* points, size_t size, Pattern2D pattern, float thickness, bool closed) { if (size == 0) return; if (size == 1) return; // Points not supported yet size_t vertexCount = 4 * (size - (closed? 0 : 1)); size_t indexCount = 6 * (size - (closed ? 0 : 1)); Path::batch->reserve(vertexCount, indexCount); Color oldColor = pattern(0, points[0]); for (size_t i = 0; i < size - 1; i++) { Color newColor = pattern(i + 1, points[i + 1]); pushLine(points[i], points[i + 1], oldColor, newColor, thickness); oldColor = newColor; } if (closed) pushLine(points[size - 1], points[0], oldColor, pattern(0, points[0]), thickness); } void polygonFilled(Vec2f* points, size_t size, const Color& color) { polygonFilled(points, size, DEFAULT_PATTERN_2D(color)); } void polygonFilled(Vec2f* points, size_t size, Pattern2D pattern) { if (size == 0) { return; } if (size == 1) { // point not supported return; } if (size == 2) { line(points[0], points[1], pattern(0, points[0]), pattern(1, points[1]), 1.0f); return; } size_t vertexCount = size; size_t indexCount = 3 * (size - 2); Path::batch->reserve(vertexCount, indexCount); for (size_t i = 0; i < size; i++) Path::batch->pushVertex({ points[i], Vec2f(1.0f, 1.0f), pattern(i, points[i]) }); for (size_t i = 0; i < size - 2; i++) Path::batch->pushIndices({ 0, i + 1, i + 2 }); Path::batch->endIndex(); } void catmullRom(Vec2f* points, size_t size, const Color& color, int precision, float thickness, bool closed, float tension, float alpha) { catmullRom(points, size, DEFAULT_PATTERN_2D(color), precision, thickness, closed, tension, alpha); } void catmullRom(Vec2f* points, size_t size, Pattern2D pattern, int precision, float thickness, bool closed, float tension, float alpha) { // Checks if (tension == 1.0f) polyLine(points, size, pattern, thickness, closed); if (size == 0) return; if (size == 1) return; // Points not supported yet if (size == 2) line(points[0], points[1], pattern(0, points[0]), pattern(1, points[1]), 1.0f); // Build lists Vec2f* pList = (Vec2f*) alloca((size + 1) * sizeof(Vec2f)); float* tList = (float*) alloca((size + 1) * sizeof(float)); Vec2 start = points[0] * 2 - points[1]; Vec2 end = points[size - 1] * 2 - points[size - 2]; float alpha2 = alpha / 2.0f; pList[0] = points[0] - start; tList[0] = pow(lengthSquared(pList[0]), alpha2); for (size_t i = 1; i < size; i++) { pList[i] = points[i] - points[i - 1]; tList[i] = pow(lengthSquared(pList[i]), alpha2); } pList[size] = end - points[size - 1]; tList[size] = pow(lengthSquared(pList[size]), alpha2); // Reserve vertices size_t vertexCount = 4 * size * precision; size_t indexCount = 6 * size * precision; Path::batch->reserve(vertexCount, indexCount); float s = 1.0f - tension; float step = 1.0f / (float)precision; for (size_t i = 1; i < size; i++) { // Calculate segment float t012 = tList[i - 1] + tList[i]; Vec2f p20 = pList[i - 1] + pList[i]; float t123 = tList[i] + tList[i + 1]; Vec2f p31 = pList[i] + pList[i + 1]; Vec2f m1 = s * (pList[i] + tList[i] * (pList[i - 1] / tList[i - 1] - p20 / t012)); Vec2f m2 = s * (pList[i] + tList[i] * (pList[i + 1] / tList[i + 1] - p31 / t123)); Vec2f a = -2.0f * pList[i] + m1 + m2; Vec2f b = 3.0f * pList[i] - m1 - m1 - m2; Vec2f c = m1; Vec2f d = points[i - 1]; // Split segment Vec2f oldPoint = d; Color oldColor = pattern((i - 1) * precision, oldPoint); for (size_t j = 1; j <= precision; j++) { float t = j * step; float w4 = 1.0f; float w3 = w4 * t; float w2 = w3 * t; float w1 = w2 * t; Vec2f newPoint = a * w1 + b * w2 + c * w3 + d * w4; Color newColor = pattern((i - 1) * precision + j, newPoint); pushLine(oldPoint, newPoint, oldColor, newColor, thickness); oldPoint = newPoint; oldColor = newColor; } } } #pragma endregion #pragma region Path //! Path std::vector path; void lineTo(const Vec2f& vertex) { path.push_back(vertex); } void arcTo(const Vec2f& center, float radius, float minAngle, float maxAngle, size_t precision) { if (precision == 0 || radius == 0.0f) { path.push_back(center); return; } path.reserve(path.size() + precision + 1); for (size_t i = 0; i <= precision; i++) { float angle = minAngle + ((float)i / (float)precision) * (maxAngle - minAngle); path.push_back(Vec2f(center.x + radius * cos(angle), center.y + radius * sin(angle))); } } void bezierTo(const Vec2f& end, const Vec2f& tc1, const Vec2f& tc2, size_t precision) { Vec2f start = path.back(); float step = 1.0f / (float)precision; for (int i = 1; i <= precision; i++) { float t = i * step; float s = 1 - t; float w1 = s * s * s; float w2 = 3.0f * s * s * t; float w3 = 3.0f * s * t * t; float w4 = t * t * t; path.push_back(w1 * start + w2 * tc1 + w3 * tc2 + w4 * end); } } void bezierTo(const Vec2f& end, size_t precision) { Vec2f start = path.back(); float mid = (start.y + end.y) / 2.0f; Vec2f c1 = Vec2f(start.x, mid); Vec2f c2 = Vec2f(end.x, mid); bezierTo(end, c1, c2, precision); } void stroke(const Color& color, float thickness, bool closed) { stroke(DEFAULT_PATTERN_2D(color), thickness, closed); } void stroke(Pattern2D pattern, float thickness, bool closed) { polyLine(path.data(), path.size(), pattern, thickness, closed); clear(); } void fill(const Color& color) { fill(DEFAULT_PATTERN_2D(color)); } void fill(Pattern2D pattern) { polygonFilled(path.data(), path.size(), pattern); clear(); } int size() { return path.size(); } void clear() { path.clear(); } #pragma endregion } }; ================================================ FILE: graphics/path/path.h ================================================ #pragma once #include "../gui/gui.h" #define DEFAULT_SCISSOR Vec4f(0, 0, GUI::windowInfo.dimension.x, GUI::windowInfo.dimension.y); namespace P3D::Graphics { namespace Path { //! Pattern typedef std::function Pattern2D; //! Flags enum TextFlags : char { // Align text pivot horizontal left TextPivotHL = 1 << 0, // Align text pivot horizontal centered TextPivotHC = 1 << 1, // Align text pivot horizontal right TextPivotHR = 1 << 2, // Align text pivot vertical top TextPivotVT = 1 << 3, // Align text pivot vertical centered TextPivotVC = 1 << 4, // Align text pivot vertical bottom TextPivotVB = 1 << 5 }; //! Batch // Current batch extern GuiBatch* batch; // Binds the given batch void bind(GuiBatch* batch); // Submits the current batch void submit(); //! Primitives // Adds a line to the batch void line(const Vec2f& a, const Vec2f& b, const Color& color = Colors::WHITE, float thickness = 1.0f); // Adds a line to the batch void line(const Vec2f& a, const Vec2f& b, const Color& colorA = Colors::WHITE, const Color& colorB = Colors::WHITE, float thickness = 1.0f); // Adds a circle to the the batch void circle(const Vec2f& center, float radius, const Color& color = Colors::WHITE, float thickness = 1.0f, size_t precision = 20); // Adds a filled circle to the batch void circleFilled(const Vec2f& center, float radius, const Color& color = Colors::WHITE, size_t precision = 20); // Adds a circle tott the batch void circleSegment(const Vec2f& center, float radius, float minAngle, float maxAngle, bool sides, const Color& color = Colors::WHITE, float thickness = 1.0f, size_t precision = 20); // Adds a filled circle to the batch void circleSegmentFilled(const Vec2f& center, float radius, float minAngle, float maxAngle, const Color& color = Colors::WHITE, size_t precision = 20); // Adds a triangle to the batch void triangle(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Color& color = Colors::WHITE, float thickness = 1.0f); // Adds a filled triangle to the batch void triangleFilled(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Color& color = Colors::WHITE); // Adds a rectangle to the batch, with pos being the bottomleft corner and dim the dimension void rect(const Vec2f& pos = Vec2f(0, 0), const Vec2f& dim = Vec2f(1, 1), float rounding = 0.0f, const Color& color = Colors::WHITE, float thickness = 1.0f); // Adds a filled rectangle to the batch, with pos being the bottomleft corner and dim the dimension void rectFilled(const Vec2f& pos = Vec2f(0, 0), const Vec2f& dim = Vec2f(1, 1), float rounding = 0.0f, const Color& color = Colors::WHITE); //? Adds a rect with UV coordinates and a texture id to the batch, with pos being the topleft corner and dim the dimension, uvMin starts default at lower left, uvMax at upper right void rectUV(GLID id, const Vec2f& pos = Vec2f(0, 0), const Vec2f& dim = Vec2f(1, 1), const Vec2f& uvMin = Vec2f(0, 0), const Vec2f& uvMax = Vec2f(1, 1), const Color& color = Colors::WHITE); // Adds a rect with an UV range and a texture id to the batch, with pos being the bottomleft corner and dim the dimension void rectUVRange(GLID id, const Vec2f& pos, const Vec2f& dim, const Vec2f& xRange, const Vec2f& yRange, const Color& color = Colors::WHITE); // Adds a quad to the batch void quad(const Vec2f& a = Vec2f(0, 0), const Vec2f& b = Vec2f(1, 0), const Vec2f& c = Vec2f(1, 1), const Vec2f& d = Vec2f(0, 1), const Color& color = Colors::WHITE, float thickness = 1.0f); // Adds a filled quad to the batch void quadFilled(const Vec2f& a = Vec2f(0, 0), const Vec2f& b = Vec2f(1, 0), const Vec2f& c = Vec2f(1, 1), const Vec2f& d = Vec2f(0, 1), const Color& color = Colors::WHITE); //? Adds a quad with UV coordinates and a texture id to the batch, default starting at the upper left corner and going counter-clockwise void quadUV(GLID id, const Vec2f& a = Vec2f(0, 0), const Vec2f& b = Vec2f(1, 0), const Vec2f& c = Vec2f(1, 1), const Vec2f& d = Vec2f(0, 1), const Vec2f& uvA = Vec2f(0, 0), const Vec2f& uvB = Vec2f(1, 0), const Vec2f& uvC = Vec2f(1, 1), const Vec2f& uvD = Vec2f(0, 1)); // Adds a string to the batch with the given font void text(Font* font, const std::string& text, double size, const Vec2f& pos, const Color& color = Colors::WHITE, char textPivot = TextPivotHL | TextPivotVB); // Adds a bezier curve to the batch, with the given control points void bezier(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, const Color& color = Colors::WHITE, float thickness = 1.0f, size_t precision = 20); // Adds a bezier curve to the batch, with the given control points void bezier(const Vec2f& a, const Vec2f& b, const Vec2f& c, const Vec2f& d, Pattern2D pattern, float thickness = 1.0f, size_t precision = 20); // Adds a horizontal oriented bezier curve to the batch, with the given start and end void bezierHorizontal(const Vec2f& start, const Vec2f& end, const Color& color = Colors::WHITE, float thickness = 1.0f, size_t precision = 20); // Adds a horizontal oriented bezier curve to the batch, with the given start and end void bezierHorizontal(const Vec2f& start, const Vec2f& end, Pattern2D pattern, float thickness = 1.0f, size_t precision = 20); // Adds a vertical oriented bezier curve to the batch, with the given start and end void bezierVertical(const Vec2f& start, const Vec2f& end, const Color& color = Colors::WHITE, float thickness = 1.0f, size_t precision = 20); // Adds a vertical oriented bezier curve to the batch, with the given start and end void bezierVertical(const Vec2f& start, const Vec2f& end, Pattern2D pattern, float thickness = 1.0f, size_t precision = 20); // Adds a polyline to the batch, through the given points void polyLine(Vec2f* points, size_t size, const Color& color = Colors::WHITE, float thickness = 1.0f, bool closed = false); // Adds a polyline to the batch, through the given points void polyLine(Vec2f* points, size_t size, Pattern2D pattern, float thickness = 1.0f, bool closed = false); // Adds a polygon to the batch, with the given points void polygonFilled(Vec2f* points, size_t size, const Color& color = Colors::WHITE); // Adds a polygon to the batch, with the given points void polygonFilled(Vec2f* points, size_t size, Pattern2D pattern); // Adds an Catmull-Rom spline to batch, interpolating the given control points void catmullRom(Vec2f* points, size_t size, const Color& color = Colors::WHITE, int precision = 20, float thickness = 1.0f, bool closed = false, float tension = 0.0f, float alpha = 0.5f); // Adds an Catmull-Rom spline to batch, interpolating the given control points void catmullRom(Vec2f* points, size_t size, Pattern2D pattern, int precision = 20, float thickness = 1.0f, bool closed = false, float tension = 0.0f, float alpha = 0.5f); //! Polygon building // Adds a vertex to the current path void lineTo(const Vec2f& vertex); // Adds and arc around the given center to the path void arcTo(const Vec2f& center, float radius, float minAngle, float maxAngle, size_t precision = 20); // Adds a bezier curve to the path with the given tangent control points, starting from the last point in the path void bezierTo(const Vec2f& end, const Vec2f& tc1, const Vec2f& tc2, size_t precision = 20); // Adds a bezier curve to the path, starting from the last point in the path void bezierTo(const Vec2f& end, size_t precision = 20); // Fills the convex polygon defined by the current path void fill(const Color& color = Colors::WHITE); // Fills the convex polygon defined by the current path void fill(Pattern2D pattern); // Draws the current path void stroke(const Color& color = Colors::WHITE, float thickness = 1.0f, bool closed = false); // Draws the current path void stroke(Pattern2D pattern, float thickness = 1.0f, bool closed = false); // Return the amount of vertices in the path int size(); // Removes all vertices from the path void clear(); } }; ================================================ FILE: graphics/path/path3D.cpp ================================================ #include "core.h" #include "path3D.h" #include #define DEFAULT_PATTERN_3D(color) [color] (int i, const Vec3f& p) { return color; } namespace P3D::Graphics { namespace Path3D { //! Batch Graphics::Batch* batch = nullptr; void bind(Graphics::Batch* batch) { Path3D::batch = batch; } //! Primitives void line(const Vec3f& a, const Vec3f& b, const Color& colorA, const Color& colorB, float thickness) { size_t vertexCount = 4; size_t indexCount = 6; Path3D::batch->reserve(vertexCount, indexCount); Path3D::batch->pushVertices({ { a, colorA }, { b, colorB } }); Path3D::batch->pushIndices({ 0, 1 }); Path3D::batch->endIndex(); } void circle(const Vec3f& center, float radius, const Vec3f& normal, float thickness, const Color& color, size_t precision) { Vec3f n = normalize(normal); Vec3f u = Vec3f(n.y, -n.x, 0); if (lengthSquared(u) == 0) u = Vec3f(n.z, 0, -n.x); u = normalize(u); Vec3f v = n % u; size_t vertexCount = precision; size_t indexCount = 2 * precision; Path3D::batch->reserve(vertexCount, indexCount); Vec3f point = center + radius * u; Path3D::batch->pushVertex({ point, color }); float step = 2.0 * PI / (float) precision; for (size_t i = 1; i < precision; i++) { float angle = i * step; point = center + radius * (std::cos(angle) * u + std::sin(angle) * v); Path3D::batch->pushVertex({ point, color }); Path3D::batch->pushIndices({ i - 1, i }); } Path3D::batch->pushIndices({ precision - 1, 0 }); Path3D::batch->endIndex(); } void triangle(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Color& colorA, const Color& colorB, const Color& colorC, float thickness) { size_t vertexCount = 3; size_t indexCount = 6; Path3D::batch->reserve(vertexCount, indexCount); Path3D::batch->pushVertices({ { a, colorA }, { b, colorB }, { c, colorC } }); Path3D::batch->pushIndices({ 0, 1, 1, 2, 2, 0 }); Path3D::batch->endIndex(); } void quad(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Vec3f& d, const Color& colorA, const Color& colorB, const Color& colorC, Color colorD, float thickness) { size_t vertexCount = 4; size_t indexCount = 8; Path3D::batch->reserve(vertexCount, indexCount); Path3D::batch->pushVertices({ { a, colorA }, { b, colorB }, { c, colorC }, { d, colorD } }); Path3D::batch->pushIndices({ 0, 1, 1, 2, 2, 3, 3, 0 }); Path3D::batch->endIndex(); } void polyLine(Vec3f* points, size_t size, Pattern3D pattern, float thickness, bool closed) { if (size == 0) return; if (size == 1) return; // Points not supported yet size_t vertexCount = size; size_t indexCount = 2 * (size - (closed ? 0 : 1)); Path3D::batch->reserve(vertexCount, indexCount); Path3D::batch->pushVertex({ points[0], pattern(0, points[0]) }); for (size_t i = 1; i < size; i++) { Path3D::batch->pushVertex({ points[i], pattern(i, points[i]) }); Path3D::batch->pushIndices({ i - 1, i }); } if (closed) Path3D::batch->pushIndices({ size - 1, 0 }); Path3D::batch->endIndex(); } void polyLine(Vec3f* points, size_t size, const Color& color, float thickness, bool closed) { polyLine(points, size, DEFAULT_PATTERN_3D(color)); } //! Path std::vector path; void lineTo(const Vec3f& vertex) { path.push_back(vertex); } void bezierTo(const Vec3f& end, const Vec3f& tc1, const Vec3f& tc2, size_t precision) { Vec3f start = path.back(); float step = 1.0f / (float)precision; for (size_t i = 1; i <= precision; i++) { float t = i * step; float s = 1 - t; float w1 = s * s * s; float w2 = 3.0f * s * s * t; float w3 = 3.0f * s * t * t; float w4 = t * t * t; path.push_back(w1 * start + w2 * tc1 + w3 * tc2 + w4 * end); } } void bezierTo(const Vec3f& end, size_t precision) { Vec3f start = path.back(); float midX = (start.y + end.y) / 2.0f; float midZ = (start.y + end.y) / 2.0f; Vec3f c1 = Vec3f(midX, start.y, midZ); Vec3f c2 = Vec3f(midX, end.y, midZ); bezierTo(end, c1, c2, precision); } void stroke(const Color& color, float thickness, bool closed) { stroke(DEFAULT_PATTERN_3D(color), thickness, closed); } void stroke(Pattern3D pattern, float thickness, bool closed) { polyLine(path.data(), path.size(), pattern, thickness, closed); clear(); } int size() { return path.size(); } void clear() { path.clear(); } } }; ================================================ FILE: graphics/path/path3D.h ================================================ #pragma once #include "../batch/batch.h" #include "../gui/gui.h" namespace P3D::Graphics { namespace Path3D { //! Pattern typedef std::function Pattern3D; //! Batch struct Vertex { Vec3f pos; Vec4f col; }; extern Graphics::Batch* batch; void bind(Graphics::Batch* batch); //! Primitives /* Adds a line to the batch */ void line(const Vec3f& a, const Vec3f& b, const Color& colorA = Colors::WHITE, const Color& colorB = Colors::WHITE, float thickness = 1.0f); /* Adds a triangle to the batch */ void triangle(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Color& colorA = Colors::WHITE, const Color& colorB = Colors::WHITE, const Color& colorC = Colors::WHITE, float thickness = 1.0f); /* Adds a quad to the batch */ void quad(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Vec3f& d, const Color& colorA = Colors::WHITE, const Color& colorB = Colors::WHITE, const Color& colorC = Colors::WHITE, Color colorD = Colors::WHITE, float thickness = 1.0f); /* Adds a circle perpendicular to the given normal to the batch */ void circle(const Vec3f& center, float radius, const Vec3f& normal, float thickness = 1.0f, const Color& color = Colors::WHITE, size_t precision = 20); /* Adds a linestrip to the batch */ void polyLine(Vec3f* points, size_t size, const Color& color = Colors::WHITE, float thickness = 1.0f, bool closed = false); /* Adds a linestrip to the batch */ void polyLine(Vec3f* points, size_t size, Pattern3D pattern, float thickness = 1.0f, bool closed = false); //! Polygon building /* Adds a vertex to the current path */ void lineTo(const Vec3f& vertex); /* Adds a bezier curve to the path with the given tangent control points, starting from the last point in the path */ void bezierTo(const Vec3f& end, const Vec3f& tc1, const Vec3f& tc2, size_t precision); /* Adds a bezier curve to the path, starting from the last point in the path*/ void bezierTo(const Vec3f& end, size_t precision); /* Draws the current path */ void stroke(const Color& color = Colors::WHITE, float thickness = 1.0f, bool closed = false); /* Draws the current path */ void stroke(Pattern3D pattern, float thickness = 1.0f, bool closed = false); /* Returns the amount of vertices in the current path */ int size(); /* Removes all vertices from the path */ void clear(); } }; ================================================ FILE: graphics/renderer.cpp ================================================ #include "core.h" #include #include #include "renderer.h" namespace P3D::Graphics { namespace Renderer { GLFLAG WIREFRAME = GL_LINE; GLFLAG FILL = GL_FILL; GLFLAG POINT = GL_POINT; GLFLAG FRONT_AND_BACK = GL_FRONT_AND_BACK; GLFLAG STATIC_DRAW = GL_STATIC_DRAW; GLFLAG STREAM_DRAW = GL_STREAM_DRAW; GLFLAG DYNAMIC_DRAW = GL_DYNAMIC_DRAW; GLFLAG ARRAY_BUFFER = GL_ARRAY_BUFFER; GLFLAG PATCHES = GL_PATCHES; GLFLAG QUADS = GL_QUADS; GLFLAG LINE_STRIP = GL_LINE_STRIP; GLFLAG TRIANGLES = GL_TRIANGLES; GLFLAG LINES = GL_LINES; GLFLAG POINTS = GL_POINTS; GLFLAG FLOAT = GL_FLOAT; GLFLAG UINT = GL_UNSIGNED_INT; std::stack states; RenderState current; RenderState saveState() { RenderState state; int intbuffer; unsigned char boolbuffer; /*glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &intbuffer); state.dfbo = intbuffer; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &intbuffer); state.rfbo = intbuffer; glGetIntegerv(GL_RENDERBUFFER_BINDING, &intbuffer); state.rbo = intbuffer;*/ // glGetIntegerv(GL_TEXTURE_BINDING_2D, &intbuffer); // state.texture = intbuffer; // glGetIntegerv(GL_CURRENT_PROGRAM, &intbuffer); // state.program = intbuffer; // glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &intbuffer); // state.vao = intbuffer; // glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &intbuffer); // state.vbo = intbuffer; // glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &intbuffer); // state.vbo = intbuffer; glGetBooleanv(GL_DEPTH_WRITEMASK, &boolbuffer); state.depthmask = boolbuffer; glGetIntegerv(GL_ACTIVE_TEXTURE, &state.activeTexture); glGetIntegerv(GL_VIEWPORT, state.viewport); glGetIntegerv(GL_POLYGON_MODE, state.mode); state.blend = glIsEnabled(GL_BLEND); state.cull = glIsEnabled(GL_CULL_FACE); state.depth = glIsEnabled(GL_DEPTH_TEST); //state.scissor = glIsEnabled(GL_SCISSOR_TEST); return state; } void loadState(const RenderState& state) { current = state; /*bindReadbuffer(state.rfbo); bindDrawbuffer(state.dfbo); bindRenderbuffer(state.rbo);*/ // glUseProgram(state.program); // glBindVertexArray(state.vao); // glBindBuffer(GL_ARRAY_BUFFER, state.vbo); // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state.ibo); // glActiveTexture(state.activeTexture); // glBindTexture(GL_TEXTURE_2D, state.texture); glDepthMask(state.depthmask); glViewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]); glPolygonMode(state.mode[0], state.mode[1]); if (state.blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); if (state.cull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (state.depth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); //if (state.scissor) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); } RenderState getState() { RenderState state; int intbuffer; unsigned char boolbuffer; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &intbuffer); state.dfbo = intbuffer; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &intbuffer); state.rfbo = intbuffer; glGetIntegerv(GL_RENDERBUFFER_BINDING, &intbuffer); state.rbo = intbuffer; glGetIntegerv(GL_TEXTURE_BINDING_2D, &intbuffer); state.texture = intbuffer; glGetIntegerv(GL_CURRENT_PROGRAM, &intbuffer); state.program = intbuffer; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &intbuffer); state.vao = intbuffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &intbuffer); state.vbo = intbuffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &intbuffer); state.vbo = intbuffer; glGetBooleanv(GL_DEPTH_WRITEMASK, &boolbuffer); state.depthmask = boolbuffer; glGetIntegerv(GL_ACTIVE_TEXTURE, &state.activeTexture); glGetIntegerv(GL_VIEWPORT, state.viewport); glGetIntegerv(GL_POLYGON_MODE, state.mode); state.blend = glIsEnabled(GL_BLEND); state.cull = glIsEnabled(GL_CULL_FACE); state.depth = glIsEnabled(GL_DEPTH_TEST); state.scissor = glIsEnabled(GL_SCISSOR_TEST); return state; } void beginScene() { RenderState backup = saveState(); states.push(backup); } void endScene() { RenderState backup = states.top(); loadState(backup); states.pop(); } bool initGLEW() { return glewInit() == GLEW_OK; } void clearDepth() { glClear(GL_DEPTH_BUFFER_BIT); } void clearColor() { glClear(GL_COLOR_BUFFER_BIT); } void clearStencil() { glClear(GL_STENCIL_BUFFER_BIT); } void lineStipple(int factor, short pattern) { glLineStipple(factor, pattern); } void lineWidth(float size) { glLineWidth(size); } void viewport(const Vec2i& origin, const Vec2i& dimension) { current.viewport[0] = origin.x; current.viewport[1] = origin.y; current.viewport[2] = dimension.x; current.viewport[3] = dimension.y; glViewport(origin.x, origin.y, dimension.x, dimension.y); } void enableDepthTest() { glEnable(GL_DEPTH_TEST); } void disableDepthTest() { glDisable(GL_DEPTH_TEST); } void enableDepthMask() { glDepthMask(GL_TRUE); } void disableDepthMask() { glDepthMask(GL_FALSE); } void enableCulling() { glEnable(GL_CULL_FACE); } void disableCulling() { glDisable(GL_CULL_FACE); } void enableBlending() { glEnable(GL_BLEND); } void disableBlending() { glDisable(GL_BLEND); } void standardBlendFunction() { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void enableMultisampling() { glEnable(GL_MULTISAMPLE); } void polygonMode(int face, int mode) { glPolygonMode(face, mode); } const char* getVendor() { return reinterpret_cast(glGetString(GL_VENDOR)); } const char* getVersion() { return reinterpret_cast(glGetString(GL_VERSION)); } const char* getRenderer() { return reinterpret_cast(glGetString(GL_RENDERER)); } const char* getShaderVersion() { return reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); } int parseShaderVersion(const char* version) { return 100 * (version[0] - '0') + 10 * (version[1] - '0'); } int getMaxTextureUnits() { int result; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &result); return result; } void genBuffers(int count, GLID* id) { glGenBuffers(count, id); } void delBuffers(int count, GLID* id) { glDeleteBuffers(count, id); } void genVertexArrays(int count, GLID* id) { glGenVertexArrays(count, id); } void delVertexArrays(int count, GLID* id) { glDeleteVertexArrays(count, id); } void bindBuffer(GLFLAG target, GLID id) { glBindBuffer(target, id); } void bufferData(GLFLAG target, int size, int offset, GLFLAG type) { glBufferData(target, size, reinterpret_cast(offset), type); } void bufferSubData(GLFLAG target, int offset, int size, const void* pointer) { glBufferSubData(target, offset, size, pointer); } void enableVertexAttribArray(GLID id) { glEnableVertexAttribArray(id); } void bindTexture2D(GLID id) { glBindTexture(GL_TEXTURE_2D, id); } void activeTexture(GLID unit) { glActiveTexture(GL_TEXTURE0 + unit); } void vertexAttribPointer(GLID id, int size, GLFLAG type, bool normalized, int stride, const void* pointer) { glVertexAttribPointer(id, size, type, normalized, stride, pointer); } void bindShader(GLID id) { //if (current.program != id) { // current.program = id; glUseProgram(id); //} } void bindFramebuffer(GLID id) { glBindFramebuffer(GL_FRAMEBUFFER, id); } void bindDrawbuffer(GLID id) { //if (current.dfbo != id) { // current.dfbo = id; glBindFramebuffer(GL_DRAW_BUFFER, id); //} } void bindReadbuffer(GLID id) { //if (current.rfbo != id) { // current.rfbo = id; glBindFramebuffer(GL_READ_BUFFER, id); //} } void bindRenderbuffer(GLID id) { //if (current.rbo != id) { // current.rbo = id; glBindRenderbuffer(GL_RENDERBUFFER, id); //} } void bindVertexArray(GLID id) { glBindVertexArray(id); } void scissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } void drawElements(GLFLAG mode, std::size_t count, GLFLAG type, const void* offset) { glDrawElements(mode, count, type, offset); } void drawElementsInstanced(GLFLAG mode, std::size_t count, GLFLAG type, const void* offset, std::size_t primitives) { glDrawElementsInstanced(mode, count, type, offset, primitives); } void drawArrays(GLFLAG mode, int first, std::size_t count) { glDrawArrays(mode, first, count); } void defaultSettings(GLID defaultFrameBuffer) { bindFramebuffer(defaultFrameBuffer); standardBlendFunction(); enableDepthTest(); glLineWidth(1.5); disableCulling(); clearColor(); clearDepth(); } } }; ================================================ FILE: graphics/renderer.h ================================================ #pragma once #include "bindable.h" #include typedef unsigned int GLFLAG; namespace P3D::Graphics { namespace Renderer { // GL constants extern GLFLAG WIREFRAME; extern GLFLAG FILL; extern GLFLAG POINT; extern GLFLAG FRONT_AND_BACK; extern GLFLAG STATIC_DRAW; extern GLFLAG DYNAMIC_DRAW; extern GLFLAG STREAM_DRAW; extern GLFLAG ARRAY_BUFFER; extern GLFLAG TRIANGLES; extern GLFLAG PATCHES; extern GLFLAG QUADS; extern GLFLAG LINE_STRIP; extern GLFLAG LINES; extern GLFLAG POINTS; extern GLFLAG UINT; extern GLFLAG FLOAT; struct RenderState { GLID dfbo; GLID rfbo; GLID rbo; GLID texture; GLID program; GLID vao; GLID vbo; GLID ibo; int activeTexture; int viewport[4]; int mode[2]; bool blend; bool cull; bool depth; bool scissor; bool depthmask; }; // GLEW binding extern bool initGLEW(); extern void clearDepth(); extern void clearColor(); extern void clearStencil(); extern void lineStipple(int factor, short pattern); extern void lineWidth(float size); extern void viewport(const Vec2i& origin, const Vec2i& dimension); extern void enableDepthMask(); extern void disableDepthMask(); extern void enableDepthTest(); extern void disableDepthTest(); extern void enableCulling(); extern void disableCulling(); extern void enableBlending(); extern void disableBlending(); extern void standardBlendFunction(); extern void enableMultisampling(); extern const char* getVendor(); extern const char* getVersion(); extern const char* getRenderer(); extern const char* getShaderVersion(); extern int getMaxTextureUnits(); extern int parseShaderVersion(const char* version); extern void genBuffers(int count, GLID* id); extern void genVertexArrays(int count, GLID* id); void delBuffers(int count, GLID* id); void delVertexArrays(int count, GLID* id); extern void bindBuffer(GLFLAG target, GLID id); extern void bufferData(GLFLAG target, int size, int offset, GLFLAG type); extern void bufferSubData(GLFLAG target, int offset, int size, const void* pointer); extern void enableVertexAttribArray(GLID id); extern void vertexAttribPointer(GLID id, int size, GLFLAG type, bool normalized, int stride, const void* pointer); extern void bindShader(GLID id); extern void bindTexture2D(GLID id); extern void bindFramebuffer(GLID id); extern void bindDrawbuffer(GLID id); extern void bindReadbuffer(GLID id); extern void bindRenderbuffer(GLID id); extern void bindVertexArray(GLID id); void activeTexture(GLID unit); extern void polygonMode(int face, int mode); extern void scissor(int x, int y, int width, int height); extern void drawElementsInstanced(GLFLAG mode, size_t count, GLFLAG type, const void* offset, size_t primitives); extern void drawElements(GLFLAG mode, size_t count, GLFLAG type, const void* offset); extern void drawArrays(GLFLAG mode, int first, size_t count); extern void defaultSettings(GLID defaultFrameBuffer); extern RenderState getState(); extern void beginScene(); extern void endScene(); } }; ================================================ FILE: graphics/resource/fontResource.cpp ================================================ #include "core.h" #include "fontResource.h" namespace P3D::Graphics { FontResource* FontAllocator::load(const std::string& name, const std::string& path) { Font font(path); if (font.getAtlasID() != 0) { return new FontResource(name, path, std::move(font)); } else { return nullptr; } } }; ================================================ FILE: graphics/resource/fontResource.h ================================================ #pragma once #include "../util/resource/resource.h" #include "../font.h" namespace P3D::Graphics { class FontResource; class FontAllocator : public ResourceAllocator { public: virtual FontResource* load(const std::string& name, const std::string& path) override; }; class FontResource : public Resource, public Font { public: DEFINE_RESOURCE(Font, "../res/fonts/default/default.ttf"); FontResource(const std::string& path, Font&& font) : Resource(path, path), Font(std::move(font)) { } FontResource(const std::string& name, const std::string& path, Font&& font) : Resource(name, path), Font(std::move(font)) { } virtual void close() override { Font::close(); }; static FontAllocator getAllocator() { return FontAllocator(); } }; }; ================================================ FILE: graphics/resource/shaderResource.cpp ================================================ #include "core.h" #include "shaderResource.h" #include #include namespace P3D::Graphics { ShaderResource* ShaderAllocator::load(const std::string& name, const std::string& path) {; return new ShaderResource(name, path, true); } }; ================================================ FILE: graphics/resource/shaderResource.h ================================================ #pragma once #include "../util/resource/resource.h" #include "../shader/shader.h" namespace P3D::Graphics { class ShaderResource; class ShaderAllocator : public ResourceAllocator { public: virtual ShaderResource* load(const std::string& name, const std::string& path) override; }; class ShaderResource : public Resource, public Shader { public: DEFINE_RESOURCE(Shader, "../res/shaders/basic.shader"); ShaderResource() : Resource(""), Shader() {} ShaderResource(const std::string& name, const std::string& path, bool isPath = true) : Resource(name, path), Shader(name, path, isPath) {} void close() override { Shader::close(); } static ShaderAllocator getAllocator() { return ShaderAllocator(); } }; }; ================================================ FILE: graphics/resource/textureResource.cpp ================================================ #include "core.h" #include "textureResource.h" namespace P3D::Graphics { TextureResource* TextureAllocator::load(const std::string& name, const std::string& path) { Texture texture = Texture::load(path); if (texture.getID() != 0) { return new TextureResource(name, path, std::move(texture)); } else { return nullptr; } } }; ================================================ FILE: graphics/resource/textureResource.h ================================================ #pragma once #include "../util/resource/resource.h" #include "../texture.h" namespace P3D::Graphics { class TextureResource; class TextureAllocator : public ResourceAllocator { public: virtual TextureResource* load(const std::string& name, const std::string& path) override; }; class TextureResource : public Resource, public Texture { public: DEFINE_RESOURCE(Texture, "../res/textures/default/default.png"); TextureResource(const std::string& path, Texture&& texture) : Resource(path, path), Texture(std::move(texture)) { } TextureResource(const std::string& name, const std::string& path, Texture&& texture) : Resource(name, path), Texture(std::move(texture)) { } virtual void close() override { Texture::close(); }; static TextureAllocator getAllocator() { return TextureAllocator(); } }; }; ================================================ FILE: graphics/shader/lexer.cpp ================================================ #include "lexer.h" namespace P3D::Graphics { constexpr bool Lexer::isDigit(char c) noexcept { return c >= '0' && c <= '9'; } constexpr bool Lexer::isSpace(char c) noexcept { switch (c) { case ' ': case '\r': case '\n': case '\t': return true; default: return false; } } constexpr bool Lexer::isLetter(char c) noexcept { return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; } constexpr bool Lexer::isIdentifierBody(char c) noexcept { return isIdentifierPrefix(c) || isDigit(c); } constexpr bool Lexer::isOperator(char c) noexcept { switch (c) { case '+': case '-': case '*': case '/': case '<': case '>': case '~': case '&': case '|': case '!': case '?': return true; default: return false; } } constexpr bool Lexer::isIdentifierPrefix(char c) noexcept { return isLetter(c) || c == '_'; } constexpr Lexer::Token Lexer::lexNumber() noexcept { const std::size_t start = current; popChar(); bool dot = false; while (isDigit(peekChar()) || peekChar() == '.') { if (peekChar() == '.') { if (dot) return Token(Token::Number, start, current); else dot = true; } popChar(); } return Token(Token::Number, start, current); } constexpr Lexer::Token Lexer::lexIdentifier() noexcept { const std::size_t start = current; popChar(); while (isIdentifierBody(peekChar())) popChar(); std::string_view type = view(start, current); if (type == "true" || type == "false") return Token(Token::Boolean, start, current); if (type == "in" || type == "out") return Token(Token::InOut, start, current); if (type == "uniform") return Token(Token::Uniform, start, current); if (type == "const" || type == "smooth" || type == "flat") return Token(Token::Qualifier, start, current); if (type == "struct") return Token(Token::Struct, start, current); if (type == "layout") return Token(Token::Layout, start, current); if (type == "bool" || type == "mat2" || type == "mat3" || type == "mat4" || type == "float" || type == "int" || type == "vec2" || type == "vec3" || type == "vec4" || type == "VS_OUT" || type == "void" || type == "sampler2D" || type == "sampler3D" || type == "color3" || type == "color4") return Token(Token::Datatype, start, current); return Token(Token::Identifier, start, current); } constexpr Lexer::Token Lexer::lexSingleComment() noexcept { const std::size_t start = current; while (peekChar() != '\n' && peekChar() != '\0') popChar(); return Token(Token::Comment, start, current); } constexpr Lexer::Token Lexer::lexMultiComment() noexcept { const std::size_t start = current; while (true) { switch (peekChar()) { case '\0': return Token(Token::Comment, start, current); case '*': switch (peekChar(1)) { case '\0': popChar(); return Token(Token::Comment, start, current - 1); case '/': popChar(); popChar(); return Token(Token::Comment, start, current - 2); default: break; } default: popChar(); break; } } } constexpr Lexer::Token Lexer::lexString(char type) noexcept { const std::size_t start = current; popChar(); while (peekChar() != type) popChar(); popChar(); return Token(Token::String, start + 1, current - 1); } constexpr Lexer::Token Lexer::lexOperator() noexcept { const std::size_t start = current; popChar(); while (isOperator(peekChar())) popChar(); return Token(Token::Operator, start, current); } constexpr Lexer::Token Lexer::lexOperatorOrComment() noexcept { char next = peekChar(); if (next == '/') { switch (peekChar(1)) { case '\0': return lexChar(Token::Operator); case '/': popChar(); popChar(); return lexSingleComment(); case '*': popChar(); popChar(); return lexMultiComment(); default: return lexOperator(); } } if (next == '-' || next == '+') if (isDigit(peekChar(1))) return lexNumber(); return lexOperator(); } constexpr Lexer::Token Lexer::lexVersionOrPreprocessor() noexcept { const auto start = current; popChar(); if (isIdentifierPrefix(peekChar())) popChar(); else return Token(Token::Error, start, current); while (isIdentifierBody(peekChar())) popChar(); if (view(start, current) == "#version") return Token(Token::Version, start, current); return Token(Token::Preprocessor, start, current); } constexpr Lexer::Token Lexer::lexEnd() const noexcept { return Token(Token::End, current, current + 1); } constexpr Lexer::Token Lexer::lexChar(const Token::Type type) noexcept { std::size_t start = current; current++; return Token(type, start, current); } constexpr char Lexer::peekChar(std::size_t offset) const noexcept { return code[current + offset]; } constexpr char Lexer::popChar() noexcept { return code[current++]; } Lexer::Lexer(const char* code) noexcept : current(0), code(code) { } Lexer::Token Lexer::peek(std::size_t offset) noexcept { while (isSpace(peekChar())) popChar(); std::size_t temp = current; Token token = next(); for (std::size_t index = 0; index < offset; index++) token = next(); current = temp; return token; } void Lexer::advance(std::size_t offset) noexcept { current += offset; } constexpr std::string_view Lexer::view(std::size_t start, std::size_t stop) noexcept { return std::string_view(this->code + start, stop - start); } std::string Lexer::string(std::size_t start, std::size_t stop) noexcept { return std::string(this->code + start, stop - start); } Lexer::Token Lexer::next() noexcept { while (isSpace(peekChar())) popChar(); const char character = peekChar(); if (character == 0) return Token(Token::End, current, current); if (isIdentifierPrefix(character)) return lexIdentifier(); if (isDigit(character)) return lexNumber(); if (isOperator(character)) return lexOperatorOrComment(); switch (character) { case '\0': return lexEnd(); case '#': return lexVersionOrPreprocessor(); case ':': return lexChar(Token::Colon); case '=': return lexChar(Token::Equals); case '(': return lexChar(Token::LeftParenthesis); case ')': return lexChar(Token::RightParenthesis); case '{': return lexChar(Token::LeftCurl); case '}': return lexChar(Token::RightCurl); case '[': return lexChar(Token::LeftBracket); case ']': return lexChar(Token::RightBracket); case '\'': return lexChar(Token::SingleQuote); case '"': return lexString('"'); case '.': return lexChar(Token::Dot); case ',': return lexChar(Token::Comma); case ';': return lexChar(Token::Semicolon); default: return lexChar(Token::Error); } } } ================================================ FILE: graphics/shader/lexer.h ================================================ #pragma once #include #include namespace P3D::Graphics { class Lexer { public: struct Range { std::size_t start = 0; std::size_t stop = 0; [[nodiscard]] constexpr std::size_t size() const { return stop - start; } [[nodiscard]] constexpr const char* first(const char* code) const { return code + start; } [[nodiscard]] constexpr const char* last(const char* code) const { return code + stop; } [[nodiscard]] constexpr std::string_view view(const char* code) const { return std::string_view(this->first(code), this->size()); } [[nodiscard]] std::string string(const char* code) const { return std::string(this->first(code), this->size()); } }; struct Token { enum Type : char { Error, Comma, Semicolon, Colon, LeftParenthesis, RightParenthesis, Equals, Dot, InOut, Layout, Uniform, Qualifier, Struct, Version, Preprocessor, Datatype, Identifier, LeftCurl, RightCurl, LeftBracket, RightBracket, Number, Boolean, Operator, SingleQuote, DoubleQuote, String, Comment, End }; Type type; Range range; constexpr Token(const Type type, std::size_t start, std::size_t stop) : type(type), range(Range { start, stop }) {} [[nodiscard]] constexpr std::size_t size() const { return this->range.size(); } [[nodiscard]] constexpr std::string_view view(const char* code) const { return this->range.view(code); } [[nodiscard]] std::string string(const char* code) const { return this->range.string(code); } [[nodiscard]] constexpr const char* first(const char* code) const { return this->range.first(code); } [[nodiscard]] constexpr const char* last(const char* code) const { return this->range.last(code); } }; private: std::size_t current; [[nodiscard]] constexpr static bool isDigit(char c) noexcept; [[nodiscard]] constexpr static bool isSpace(char c) noexcept; [[nodiscard]] constexpr static bool isLetter(char c) noexcept; [[nodiscard]] constexpr static bool isIdentifierPrefix(char c) noexcept; [[nodiscard]] constexpr static bool isIdentifierBody(char c) noexcept; [[nodiscard]] constexpr static bool isOperator(char c) noexcept; [[nodiscard]] constexpr Token lexString(char type) noexcept; [[nodiscard]] constexpr Token lexNumber() noexcept; [[nodiscard]] constexpr Token lexIdentifier() noexcept; [[nodiscard]] constexpr Token lexOperatorOrComment() noexcept; [[nodiscard]] constexpr Token lexVersionOrPreprocessor() noexcept; [[nodiscard]] constexpr Token lexSingleComment() noexcept; [[nodiscard]] constexpr Token lexMultiComment() noexcept; [[nodiscard]] constexpr Token lexOperator() noexcept; [[nodiscard]] constexpr Token lexChar(Token::Type type) noexcept; [[nodiscard]] constexpr Token lexEnd() const noexcept; [[nodiscard]] constexpr char peekChar(std::size_t offset = 0) const noexcept; constexpr char popChar() noexcept; public: const char* code; explicit Lexer(const char* code) noexcept; void advance(std::size_t offset) noexcept; [[nodiscard]] constexpr std::string_view view(std::size_t start, std::size_t stop) noexcept; [[nodiscard]] std::string string(std::size_t start, std::size_t stop) noexcept; [[nodiscard]] Token peek(std::size_t offset = 0) noexcept; [[nodiscard]] Token next() noexcept; }; } ================================================ FILE: graphics/shader/parser.cpp ================================================ #include "core.h" #include "parser.h" #include "lexer.h" #include namespace P3D::Graphics { Parser::Type Parser::parseType(std::string_view type) { if (type == "void") return Type::Void; if (type == "bool") return Type::Bool; if (type == "int") return Type::Int; if (type == "float") return Type::Float; if (type == "mat2") return Type::Mat2; if (type == "mat3") return Type::Mat3; if (type == "mat4") return Type::Mat4; if (type == "vec2") return Type::Vec2; if (type == "vec3") return Type::Vec3; if (type == "vec4") return Type::Vec4; if (type == "struct") return Type::Struct; if (type == "VS_OUT") return Type::VSOut; if (type == "struct") return Type::Struct; if (type == "sampler2d") return Type::Sampler2D; if (type == "sampler3d") return Type::Sampler3D; return Type::None; } Parser::IO Parser::parseIO(std::string_view io) { if (io == "in") return IO::In; if (io == "out") return IO::Out; return IO::None; } void parseUntil(Lexer& lexer, Lexer::Token::Type ltype, Lexer::Token::Type rtype) { int depth = -1; Lexer::Token next = lexer.next(); while (next.type != Lexer::Token::End) { if (next.type == ltype) { depth++; } else if (next.type == rtype) { if (depth == 0) return; if (depth > 0) depth--; } next = lexer.next(); } } void parseUntil(Lexer& lexer, Lexer::Token::Type type) { Lexer::Token next = lexer.next(); while (next.type != Lexer::Token::End) { if (next.type == type) return; next = lexer.next(); } } std::size_t parseArray(Lexer& lexer, Parser::Parse& result) { Lexer::Token leftBracket = lexer.peek(); if (leftBracket.type == Lexer::Token::LeftBracket) { lexer.advance(leftBracket.size()); Lexer::Token next = lexer.next(); switch (next.type) { case Lexer::Token::Number: { std::size_t amount; std::stringstream stream(std::string(next.first(lexer.code), next.size())); stream >> amount; if (stream.fail()) return static_cast(-1); return amount; } case Lexer::Token::Identifier: { std::string define(next.string(lexer.code)); if (auto iterator = result.defines.find(define); iterator != result.defines.end()) return static_cast(iterator->second); return static_cast(-1); } case Lexer::Token::RightBracket: { return 0; } default: return static_cast(-1); } } return 0; } void parseUniform(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { Lexer::Token type = lexer.next(); if (type.type != Lexer::Token::Datatype && type.type != Lexer::Token::Identifier) return; if (type.type == Lexer::Token::Identifier && result.structs.find(type.string(lexer.code)) == result.structs.end()) return; Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; std::size_t amount = parseArray(lexer, result); if (amount == static_cast(-1)) return; parseUntil(lexer, Lexer::Token::Semicolon); result.uniforms.push_back(Parser::Local { type.range, name.range, amount }); } Parser::Locals parseScope(Lexer& lexer, Parser::Parse& result) { Parser::Locals locals; Lexer::Token next = lexer.next(); if (next.type != Lexer::Token::LeftCurl) return locals; int depth = 1; while (next.type != Lexer::Token::End) { next = lexer.next(); if (next.type == Lexer::Token::LeftCurl) { depth++; continue; } if (next.type == Lexer::Token::RightCurl) { depth--; if (depth == 0) break; continue; } if (next.type == Lexer::Token::Datatype || next.type == Lexer::Token::Identifier && result.structs.find(next.string(lexer.code)) != result.structs.end()) { Lexer::Token type = next; Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) continue; std::size_t amount = parseArray(lexer, result); if (amount == static_cast(-1)) continue; locals.push_back(Parser::Local { type.range, name.range, amount }); } } return locals; } void parseVSOut(Lexer& lexer, const Lexer::Token& type, const Parser::IO& io, Parser::Parse& result) { Parser::Locals locals = parseScope(lexer, result); Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; std::size_t amount = parseArray(lexer, result); if (amount == static_cast(-1)) return; result.vsOuts.push_back(Parser::VSOut { io, type.range, name.range, amount, locals }); } void parseGlobal(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { Parser::IO io = Parser::parseIO(current.view(lexer.code)); if (io == Parser::IO::None) return; Lexer::Token type = lexer.next(); if (type.type != Lexer::Token::Datatype) return; if (Parser::parseType(type.view(lexer.code)) == Parser::Type::VSOut) { parseVSOut(lexer, type, io, result); return; } Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; std::size_t amount = parseArray(lexer, result); if (amount == static_cast(-1)) return; result.globals.emplace_back(Parser::Global { io, type.range, name.range, amount }); } void parseVersion(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { Lexer::Token versionToken = lexer.next(); if (versionToken.type != Lexer::Token::Number) return; std::size_t version; std::stringstream stream(std::string(versionToken.first(lexer.code), versionToken.size())); stream >> version; if (stream.fail()) return; bool core = false; Lexer::Token coreToken = lexer.peek(); if (coreToken.type == Lexer::Token::Identifier && coreToken.view(lexer.code) == "core") { lexer.advance(coreToken.size()); core = true; } result.version = Parser::Version { version, core }; } void parseDefine(Lexer& lexer, Parser::Parse& result) { Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; int number = 1; Lexer::Token numberToken = lexer.next(); if (numberToken.type == Lexer::Token::Number) { std::stringstream stream(std::string(numberToken.first(lexer.code), numberToken.size())); stream >> number; if (stream.fail()) return; } result.defines[name.string(lexer.code)] = number; } void parsePreprocessor(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { if (current.view(lexer.code) == "#define") parseDefine(lexer, result); } void parseIdentifier(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { Lexer::Token type = current; if (result.structs.find(current.string(lexer.code)) == result.structs.end()) return; Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; std::size_t amount = parseArray(lexer, result); if (amount == static_cast(-1)) return; result.locals.push_back(Parser::Local { type.range, name.range, amount }); } void parseDatatype(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { if (Parser::parseType(current.view(lexer.code)) == Parser::Type::Void || lexer.peek().type == Lexer::Token::Identifier && lexer.peek(1).type == Lexer::Token::LeftParenthesis) parseUntil(lexer, Lexer::Token::LeftCurl, Lexer::Token::RightCurl); } void parseStruct(Lexer& lexer, const Lexer::Token& current, Parser::Parse& result) { Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return; Parser::Locals locals = parseScope(lexer, result); result.structs[name.string(lexer.code)] = Parser::Struct { name.range, locals }; } Parser::Parse Parser::parse(const char* code) { Parse result; Lexer lexer(code); Lexer::Token current = lexer.next(); while (current.type != Lexer::Token::End) { switch (current.type) { case Lexer::Token::Uniform: { parseUniform(lexer, current, result); break; } case Lexer::Token::InOut: { parseGlobal(lexer, current, result); break; } case Lexer::Token::Datatype: { parseDatatype(lexer, current, result); break; } case Lexer::Token::Layout: { // TODO break; } case Lexer::Token::Version: { parseVersion(lexer, current, result); break; } case Lexer::Token::Preprocessor: { parsePreprocessor(lexer, current, result); break; } case Lexer::Token::Struct: { parseStruct(lexer, current, result); break; } case Lexer::Token::Identifier: { parseIdentifier(lexer, current, result); break; } default: break; } current = lexer.next(); } return result; } } ================================================ FILE: graphics/shader/parser.h ================================================ #pragma once #include #include #include #include "lexer.h" namespace P3D::Graphics { class Parser { public: enum class Type : char { None, Void, Bool, Int, Float, Vec2, Vec3, Vec4, Mat2, Mat3, Mat4, Struct, VSOut, Sampler2D, Sampler3D }; enum class IO { None, In, Out }; struct Local { Lexer::Range type; Lexer::Range name; std::size_t amount; }; typedef std::vector Locals; struct Global { IO io; Lexer::Range type; Lexer::Range name; std::size_t amount; }; typedef std::vector Globals; struct Layout { struct Attribute { Lexer::Range attribute; Lexer::Range value; }; IO io; Lexer::Range type; Lexer::Range name; std::vector attributes; }; typedef std::vector Layouts; struct VSOut { IO io; Lexer::Range type; Lexer::Range name; std::size_t amount; Locals locals; }; typedef std::vector VSOuts; struct Version { std::size_t version = 0; bool core = false; }; struct Struct { Lexer::Range name; Locals locals; }; typedef std::unordered_map Structs; typedef std::unordered_map Defines; struct Parse { Version version; Layouts layouts; Locals uniforms; Globals globals; VSOuts vsOuts; Locals locals; Defines defines; Structs structs; }; static Type parseType(std::string_view type); static IO parseIO(std::string_view io); static Parse parse(const char* code); }; } ================================================ FILE: graphics/shader/propertiesParser.cpp ================================================ #include "core.h" #include "propertiesParser.h" #include "lexer.h" namespace P3D::Graphics { std::optional parseType(const Lexer& lexer, const Lexer::Token& token) { std::string_view type = token.view(lexer.code); static std::unordered_map map { { { "bool" , Property::Type::Bool }, { "int" , Property::Type::Int }, { "float", Property::Type::Float }, { "vec2" , Property::Type::Vec2 }, { "vec3" , Property::Type::Vec3 }, { "vec4" , Property::Type::Vec4 }, { "mat2" , Property::Type::Mat2 }, { "mat3" , Property::Type::Mat3 }, { "mat4" , Property::Type::Mat4 }, { "col3" , Property::Type::Col3 }, { "col4" , Property::Type::Col4 }, } }; auto iterator = map.find(type); if (iterator != map.end()) return std::make_optional(iterator->second); return std::nullopt; } std::size_t arity(const Property::Type& type) { static std::unordered_map map { { { Property::Type::Bool , 1 }, { Property::Type::Int , 1 }, { Property::Type::Float, 1 }, { Property::Type::Vec2 , 2 }, { Property::Type::Vec3 , 3 }, { Property::Type::Vec4 , 4 }, { Property::Type::Mat2 , 4 }, { Property::Type::Mat3 , 9 }, { Property::Type::Mat4 , 16 }, { Property::Type::Col3 , 3 }, { Property::Type::Col4 , 4 }, } }; return map.at(type); } std::optional parseDefault(Lexer& lexer, const Lexer::Token& token) { Property::Default value; if (token.type == Lexer::Token::Boolean) value.value = token.view(lexer.code) == "true" ? 1.0f : 0.0f; else if (token.type == Lexer::Token::Number) { float number = std::stof(token.string(lexer.code)); value.value = number; } else return std::nullopt; if (lexer.peek().type != Lexer::Token::LeftParenthesis) return value; Lexer::Token parenthesis = lexer.next(); Lexer::Token start = lexer.next(); if (start.type != Lexer::Token::Number) return std::nullopt; if (lexer.next().type != Lexer::Token::Colon) return std::nullopt; Lexer::Token stop = lexer.next(); if (start.type != Lexer::Token::Number) return std::nullopt; value.range = { std::stof(start.string(lexer.code)), std::stof(stop.string(lexer.code)) }; parenthesis = lexer.next(); if (parenthesis.type != Lexer::Token::RightParenthesis) return std::nullopt; return std::make_optional(value); } std::optional parseProperty(Lexer& lexer, const Lexer::Token& token) { Property property; if (token.type != Lexer::Token::Datatype) return std::nullopt; std::optional type = parseType(lexer, token); if (!type.has_value()) return std::nullopt; property.type = type.value(); Lexer::Token name = lexer.next(); if (name.type != Lexer::Token::Identifier) return std::nullopt; property.name = name.view(lexer.code); Lexer::Token next = lexer.next(); if (next.type == Lexer::Token::LeftParenthesis) { Lexer::Token uniform = lexer.next(); if (uniform.type != Lexer::Token::Identifier) return std::nullopt; property.uniform = uniform.view(lexer.code); next = lexer.next(); if (next.type != Lexer::Token::RightParenthesis) return std::nullopt; next = lexer.next(); } else { property.uniform = property.name; } if (next.type == Lexer::Token::Semicolon) { for (std::size_t i = 0; i < arity(property.type); i++) { Property::Default value { 0.0, std::nullopt }; property.defaults.push_back(value); } return std::make_optional(property); } if (next.type != Lexer::Token::Equals) return std::nullopt; for (std::size_t i = 0; i < arity(property.type); i++) { if (i != 0) { next = lexer.next(); if (next.type != Lexer::Token::Comma) return std::nullopt; } std::optional value = parseDefault(lexer, lexer.next()); if (!value.has_value()) return std::nullopt; property.defaults.push_back(value.value()); } next = lexer.next(); if (next.type == Lexer::Token::Semicolon) return std::make_optional(property); return std::nullopt; } std::vector PropertiesParser::parse(const char* source, std::string_view view) { std::vector properties; std::string code(view); std::size_t source_offset = std::distance(source, view.data()); Lexer lexer(code.data()); Lexer::Token current = lexer.next(); while (current.type != Lexer::Token::End) { std::optional property = parseProperty(lexer, current); if (property.has_value()) { std::size_t property_offset = source_offset + std::distance(code.c_str(), property->name.data()); property->name = std::string_view(source + property_offset, property->name.size()); std::size_t uniform_offset = source_offset + std::distance(code.c_str(), property->uniform.data()); property->uniform = std::string_view(source + uniform_offset, property->uniform.size()); properties.push_back(property.value()); } current = lexer.next(); } return properties; } } ================================================ FILE: graphics/shader/propertiesParser.h ================================================ #pragma once #include namespace P3D::Graphics { // [()] [= [( : )]]; // vec3 TheInputColor (inputColor) = 0.1 (0:1), 0.2 (-10,10), 0.3; struct Property { struct Range { float min; float max; }; struct Default { float value = 0.0; std::optional range; std::optional step; }; enum class Type { None, Bool, Int, Float, Vec2, Vec3, Vec4, Mat2, Mat3, Mat4, Col3, Col4, }; std::string_view name; std::string_view uniform; Type type = Type::None; std::vector defaults; }; class PropertiesParser { public: static std::vector parse(const char* source, std::string_view view); }; } ================================================ FILE: graphics/shader/shader.cpp ================================================ #include "core.h" #include "shader.h" #include #include #include #include "renderer.h" #include "debug/guiDebug.h" #include "util/stringUtil.h" #include "util/systemVariables.h" namespace P3D::Graphics { Shader::Stage::ID Shader::Stage::common = 1; Shader::Stage::ID Shader::Stage::properties = 2; Shader::Stage::ID Shader::Stage::vertex = GL_VERTEX_SHADER; Shader::Stage::ID Shader::Stage::fragment = GL_FRAGMENT_SHADER; Shader::Stage::ID Shader::Stage::geometry = GL_GEOMETRY_SHADER; Shader::Stage::ID Shader::Stage::tesselationControl = GL_TESS_CONTROL_SHADER; Shader::Stage::ID Shader::Stage::tesselationEvaluation = GL_TESS_EVALUATION_SHADER; Shader::Stage::ID Shader::Stage::compute = GL_COMPUTE_SHADER; Shader::Stage::Stage(const std::string_view& view) : view(view) { } Shader::Shader() = default; Shader::Shader(const std::string& name, const std::string& path, bool isPath) : name(name) { Source source; // Parsing if (isPath) { std::ifstream istream; istream.open(path); if (!istream.is_open()) { Log::error("Failed to read path [%s] to parse shader [%s]", path.c_str(), name.c_str()); return; } source = parse(istream); } else { std::istringstream istream(path); source = parse(istream); } // Compilation this->id = compile(source); // Stages this->code = source.source; for (const auto& [stage, view] : source.views) this->stages.emplace(stage, Stage(std::string_view(this->code.data() + view.first, view.second))); // Properties auto iterator = this->stages.find(Stage::properties); if (iterator != this->stages.end()) { this->properties = PropertiesParser::parse(this->code.data(), iterator->second.view); } } Shader::~Shader() { //Todo fix error Log::info("Deleted shader [%s] with id [%u]", name.c_str(), id); Shader::unbind(); glDeleteProgram(id); } void Shader::bind() { Renderer::bindShader(id); } void Shader::unbind() { Renderer::bindShader(0); } void Shader::close() { // Todo remove } std::string Shader::getStageName(const Stage::ID& stage) { if (stage == Stage::common) return std::string("common"); if (stage == Stage::properties) return std::string("properties"); if (stage == Stage::vertex) return std::string("vertex"); if (stage == Stage::fragment) return std::string("fragment"); if (stage == Stage::geometry) return std::string("geometry"); if (stage == Stage::tesselationControl) return std::string("tesselation control"); if (stage == Stage::tesselationEvaluation) return std::string("tesselation evaluation"); if (stage == Stage::compute) return std::string("compute"); return std::string(); } GLID Shader::compile(const Source& source) { // Only OpenGL > 330 is supported if (SystemVariables::get("OPENGL_SHADER_VERSION") < 330) { Log::error("Shader version [%d] not supported", SystemVariables::get("OPENGL_SHADER_VERSION")); return 0; } // Shader creation GLID program = glCreateProgram(); if (program == 0) { Log::error("Compilation failed, could not create a new program"); return 0; } // Get common source auto iterator = source.views.find(Stage::common); std::string common = iterator != source.views.end() ? source.source.substr(iterator->second.first, iterator->second.second) : std::string(); // Compilation and attachment std::vector compiledStages; for (const auto& [stage, view] : source.views) { if (stage == Stage::common || stage == Stage::properties) continue; std::string code = source.source.substr(view.first, view.second); GLID id = compile(common, code, stage); if (id == 0) { Log::error("Compilation failed"); for (GLID compiledStage : compiledStages) { glDetachShader(program, compiledStage); glDeleteShader(compiledStage); } glDeleteProgram(program); return 0; } glAttachShader(program, id); compiledStages.push_back(id); } // Linking and validation glCall(glLinkProgram(program)); glCall(glValidateProgram(program)); // Deletion of stages for (GLID compiledStage : compiledStages) glDeleteShader(compiledStage); Log::info("Compilation of shader [%d] successful", program); return program; } GLID Shader::compile(const std::string& common, const std::string& source, const Stage::ID& stage) { GLID id = glCreateShader(stage); if (id == 0) { Log::error("Failed to create a %s stage", getStageName(stage).c_str()); return 0; } const char* src[] = { common.c_str(), source.c_str() }; const int sizes[] = { static_cast(common.length()), static_cast(source.length()) }; glShaderSource(id, 2, src, sizes); glCompileShader(id); int compiled; glGetShaderiv(id, GL_COMPILE_STATUS, &compiled); if (compiled == GL_FALSE) { int length; glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length); char* message = static_cast(alloca(length * sizeof(char))); glGetShaderInfoLog(id, length, &length, message); Log::error("Compilation of %s stage failed", getStageName(stage).c_str()); Log::error(message); glDeleteShader(id); return 0; } return id; } Shader::Source Shader::parse(std::istream& istream) { Source source; std::stringstream code; constexpr Stage::ID none = 0; Stage::ID old = none; Stage::ID current = none; std::size_t start = 0; std::size_t length = 0; std::string line; std::size_t lineNumber = 0; while (getline(istream, line)) { code << line << '\n'; lineNumber++; std::string trimmedLine = Util::ltrim(line); if (trimmedLine.rfind("[vertex]", 0) != std::string::npos) { current = Stage::vertex; } else if (trimmedLine.rfind("[fragment]", 0) != std::string::npos) { current = Stage::fragment; } else if (trimmedLine.rfind("[geometry]", 0) != std::string::npos) { current = Stage::geometry; } else if (trimmedLine.rfind("[tesselation control]", 0) != std::string::npos) { current = Stage::tesselationControl; } else if (trimmedLine.rfind("[tesselation evaluate]", 0) != std::string::npos) { current = Stage::tesselationEvaluation; } else if (trimmedLine.rfind("[compute]", 0) != std::string::npos) { current = Stage::compute; } else if (trimmedLine.rfind("[common]", 0) != std::string::npos) { current = Stage::common; } else if (trimmedLine.rfind("[properties]", 0) != std::string::npos) { current = Stage::properties; } if (current == none) Log::warn("Code at line [%d] does not belong to a shader stage", lineNumber); if (current != old) { if (old != none) { std::pair view(start, length); auto [iterator, inserted] = source.views.emplace(old, view); if (!inserted) Log::warn("Duplicate shader stages encountered, new stage is discarded"); } start += length + line.length() + 1; length = 0; old = current; continue; } length += line.length() + 1; } // Last stage std::pair view(start, length); auto [iterator, inserted] = source.views.emplace(old, view); if (!inserted) Log::warn("Duplicate shader stages encountered, new stage is discarded"); source.source = code.str(); return source; } int Shader::getUniform(const std::string& uniform) { if (id == 0) { Log::warn("Requesting a uniform [%s] from an empty shader [%s]", uniform.c_str(), name.c_str()); \ return -1; } auto iterator = uniforms.find(uniform); if (iterator != uniforms.end()) return iterator->second; int location = glGetUniformLocation(id, uniform.c_str()); if (location != -1) { Log::debug("Created uniform [%s] with id [%d] in shader [%s] ", uniform.c_str(), location, name.c_str()); uniforms.emplace(uniform, location); return location; } Log::warn("Uniform [%s] not found in shader [%s]", uniform.c_str(), name.c_str()); uniforms.emplace(uniform, location); return -1; } void Shader::addUniform(const std::string& uniform) { if (uniforms.find(uniform) == uniforms.end()) createUniform(uniform); } void Shader::createUniform(const std::string& uniform) { Log::subject s(name); if (id == 0) { Log::warn("Creating a uniform [%s] in an empty shader [%s]", uniform.c_str(), name.c_str()); return; } bind(); int location = glGetUniformLocation(id, uniform.c_str()); if (location == -1) { Log::warn("Uniform [%s] not found in shader [%s]", uniform.c_str(), name.c_str()); return; } uniforms.emplace(uniform, location); Log::debug("Created uniform [%s] with id [%d] in shader [%s] ", uniform.c_str(), location, name.c_str()); } //Log::warn("Setting an unknown uniform [%s] in a shader [%s]", uniform.c_str(), name.c_str()); #define UNIFORM_CHECK(uniform) \ this->getUniform(uniform); \ if (id == 0) { \ Log::warn("Setting a uniform [%s] in an empty shader [%s]", uniform.c_str(), name.c_str()); \ return; \ } \ if (location == -1) \ return; void Shader::setUniform(const std::string& uniform, GLID value) { int location = UNIFORM_CHECK(uniform); glUniform1i(location, value); } void Shader::setUniform(const std::string& uniform, int value) { int location = UNIFORM_CHECK(uniform); glUniform1i(location, value); } void Shader::setUniform(const std::string& uniform, float value) { int location = UNIFORM_CHECK(uniform); glUniform1f(location, value); } void Shader::setUniform(const std::string& uniform, const Vec2f& value) { int location = UNIFORM_CHECK(uniform); glUniform2f(location, value.x, value.y); } void Shader::setUniform(const std::string& uniform, const Vec3f& value) { int location = UNIFORM_CHECK(uniform); glUniform3f(location, value.x, value.y, value.z); } void Shader::setUniform(const std::string& uniform, const Position& value) { int location = UNIFORM_CHECK(uniform); glUniform3f(location, static_cast(value.x), static_cast(value.y), static_cast(value.z)); } void Shader::setUniform(const std::string& uniform, const Vec4f& value) { int location = UNIFORM_CHECK(uniform); glUniform4f(uniforms.at(uniform), value.x, value.y, value.z, value.w); } void Shader::setUniform(const std::string& uniform, const Mat2f& value) { int location = UNIFORM_CHECK(uniform); float buf[4]; value.toColMajorData(buf); glUniformMatrix2fv(location, 1, GL_FALSE, buf); } void Shader::setUniform(const std::string& uniform, const Mat3f& value) { int location = UNIFORM_CHECK(uniform); float buf[9]; value.toColMajorData(buf); glUniformMatrix3fv(location, 1, GL_FALSE, buf); } void Shader::setUniform(const std::string& uniform, const Mat4f& value) { int location = UNIFORM_CHECK(uniform); float buf[16]; value.toColMajorData(buf); glUniformMatrix4fv(location, 1, GL_FALSE, buf); } CShader::CShader(const std::string& name, const std::string& path, bool isPath) : Shader(name, path, isPath) { } void CShader::dispatch(unsigned int x, unsigned int y, unsigned int z) { glDispatchCompute(x, y, z); } void CShader::barrier(unsigned int flags) { glMemoryBarrier(flags); } } ================================================ FILE: graphics/shader/shader.h ================================================ #pragma once #include "../bindable.h" #include "propertiesParser.h" namespace P3D::Graphics { class Shader : public Bindable { public: struct Stage { using ID = unsigned; static ID common; static ID properties; static ID vertex; static ID geometry; static ID fragment; static ID tesselationControl; static ID tesselationEvaluation; static ID compute; std::string_view view; explicit Stage(const std::string_view& view); }; struct Source { std::string source; std::unordered_map> views; }; std::string name; std::string code; std::unordered_map stages; std::unordered_map uniforms; std::vector properties; Shader(); Shader(const std::string& name, const std::string& path, bool isPath); virtual ~Shader(); Shader(const Shader&) = delete; Shader(Shader&&) noexcept = delete; Shader& operator=(const Shader&) = delete; Shader& operator=(Shader&& other) noexcept = delete; [[nodiscard]] static std::string getStageName(const Stage::ID& stage); [[nodiscard]] static GLID compile(const Source& source); [[nodiscard]] static GLID compile(const std::string& common, const std::string& source, const Stage::ID& stage); [[nodiscard]] static Source parse(std::istream& istream); void bind() override; void unbind() override; void close() override; [[nodiscard]] int getUniform(const std::string& uniform); void addUniform(const std::string& uniform); void createUniform(const std::string& uniform); void setUniform(const std::string& uniform, int value); void setUniform(const std::string& uniform, GLID value); void setUniform(const std::string& uniform, float value); void setUniform(const std::string& uniform, const Vec2f& value); void setUniform(const std::string& uniform, const Vec3f& value); void setUniform(const std::string& uniform, const Vec4f& value); void setUniform(const std::string& uniform, const Mat2f& value); void setUniform(const std::string& uniform, const Mat3f& value); void setUniform(const std::string& uniform, const Mat4f& value); void setUniform(const std::string& uniform, const Position& value); }; class CShader : public Shader { public: CShader(const std::string& name, const std::string& path, bool isPath); void dispatch(unsigned int x, unsigned int y, unsigned int z); void barrier(unsigned int flags); }; } ================================================ FILE: graphics/shader/shaders.cpp ================================================ #include "core.h" #include "shaders.h" #include "texture.h" #include "../util/resource/resourceManager.h" #include "../graphics/renderer.h" #include namespace P3D::Graphics { namespace Shaders { SRef guiShader; SRef quadShader; SRef horizontalBlurShader; SRef verticalBlurShader; void onInit() { std::string guiShaderSource = R"( [common] #version 330 core [vertex] layout(location = 0) in vec2 vposition; layout(location = 1) in vec2 vuv; layout(location = 2) in vec4 vcolor; uniform mat4 projectionMatrix; out vec4 fcolor;out vec2 fuv; void main() { gl_Position = projectionMatrix * vec4(vposition, 0.0, 1.0); fcolor = vcolor; fuv = vuv; } [fragment] out vec4 outColor; in vec2 fuv; in vec4 fcolor; uniform sampler2D textureSampler; uniform bool textured = false; void main() { outColor = (textured) ? texture(textureSampler, fuv) * fcolor : fcolor; } )"; std::string quadShaderSource = R"( [common] #version 330 core [vertex] layout(location = 0) in vec4 vposition; uniform mat4 projectionMatrix; out vec2 fUV; void main() { gl_Position = projectionMatrix * vec4(vposition.xy, 0.0, 1.0);fUV = vposition.zw; } [fragment] out vec4 outColor; in vec2 fUV; uniform bool textured; uniform sampler2D textureSampler; uniform vec4 color = vec4(1, 1, 1, 1); void main() { outColor = (textured) ? texture(textureSampler, fUV) * color : color; } )"; std::string horizontalBlurShaderVertexSource = R"( [common] #version 330 [vertex] layout(location = 0) in vec2 vposition; out vec2 fUV[15]; uniform float width = 3.0; void main() { float part = 1.0 / width; vec2 UV = vposition * 0.5 + 0.5; gl_Position = vec4(vposition, 0.0, 1.0); fUV[0] = UV + vec2(0.0, -part * 7.0); fUV[1] = UV + vec2(0.0, -part * 6.0); fUV[2] = UV + vec2(0.0, -part * 5.0); fUV[3] = UV + vec2(0.0, -part * 4.0); fUV[4] = UV + vec2(0.0, -part * 3.0); fUV[5] = UV + vec2(0.0, -part * 2.0); fUV[6] = UV + vec2(0.0, -part); fUV[7] = UV; fUV[8] = UV + vec2(0.0, part); fUV[9] = UV + vec2(0.0, part * 2.0); fUV[10] = UV + vec2(0.0, part * 3.0); fUV[11] = UV + vec2(0.0, part * 4.0); fUV[12] = UV + vec2(0.0, part * 5.0); fUV[13] = UV + vec2(0.0, part * 6.0); fUV[13] = UV + vec2(0.0, part * 7.0); } )"; std::string verticalBlurShaderVertexSource = R"( [common] #version 330 [vertex] layout(location = 0) in vec2 vposition; out vec2 fUV[15]; uniform float width = 3.0; void main() { float part = 1.0 / width; vec2 UV = vposition * 0.5 + 0.5; gl_Position = vec4(vposition, 0.0, 1.0); fUV[0] = UV + vec2(-part * 7.0, 0.0); fUV[1] = UV + vec2(-part * 6.0, 0.0); fUV[2] = UV + vec2(-part * 5.0, 0.0); fUV[3] = UV + vec2(-part * 4.0, 0.0); fUV[4] = UV + vec2(-part * 3.0, 0.0); fUV[5] = UV + vec2(-part * 2.0, 0.0); fUV[6] = UV + vec2(-part, 0.0); fUV[7] = UV; fUV[8] = UV + vec2(part, 0.0); fUV[9] = UV + vec2(part * 2.0, 0.0); fUV[10] = UV + vec2(part * 3.0, 0.0); fUV[11] = UV + vec2(part * 4.0, 0.0); fUV[12] = UV + vec2(part * 5.0, 0.0); fUV[13] = UV + vec2(part * 6.0, 0.0); fUV[14] = UV + vec2(part * 7.0, 0.0); } )"; std::string blurShaderFragmentSource = R"( [fragment] in vec2 fUV[15]; out vec4 outColor; uniform sampler2D image; void main() { outColor = vec4(0.0); outColor += texture(image, fUV[0]) * 0.0044299121055113265; outColor += texture(image, fUV[1]) * 0.00895781211794; outColor += texture(image, fUV[2]) * 0.0215963866053; outColor += texture(image, fUV[3]) * 0.0443683338718; outColor += texture(image, fUV[4]) * 0.0776744219933; outColor += texture(image, fUV[5]) * 0.115876621105; outColor += texture(image, fUV[6]) * 0.147308056121; outColor += texture(image, fUV[7]) * 0.159576912161; outColor += texture(image, fUV[8]) * 0.147308056121; outColor += texture(image, fUV[9]) * 0.115876621105; outColor += texture(image, fUV[10]) * 0.0776744219933; outColor += texture(image, fUV[11]) * 0.0443683338718; outColor += texture(image, fUV[12]) * 0.0215963866053; outColor += texture(image, fUV[13]) * 0.00895781211794; outColor += texture(image, fUV[14]) * 0.0044299121055113265; } )"; // GShader source init std::string horizontalBlurShaderSource(horizontalBlurShaderVertexSource + blurShaderFragmentSource); std::string verticalBlurShaderSource(verticalBlurShaderVertexSource + blurShaderFragmentSource); // GShader init guiShader = std::make_shared(guiShaderSource); quadShader = std::make_shared(quadShaderSource); horizontalBlurShader = std::make_shared(horizontalBlurShaderSource); verticalBlurShader = std::make_shared(verticalBlurShaderSource); ResourceManager::add(guiShader.get()); ResourceManager::add(quadShader.get()); ResourceManager::add(horizontalBlurShader.get()); ResourceManager::add(verticalBlurShader.get()); } void onClose() { horizontalBlurShader->close(); verticalBlurShader->close(); guiShader->close(); quadShader->close(); } } // GuiShader void GuiShader::init(const Mat4f& orthoMatrix) { bind(); setUniform("projectionMatrix", orthoMatrix); setUniform("textureSampler", 0); } void GuiShader::setTextured(bool textured) { bind(); setUniform("textured", textured); } // QuadShader void QuadShader::updateProjection(const Mat4f& projectionMatrix) { bind(); setUniform("projectionMatrix", projectionMatrix); } void QuadShader::updateColor(const Vec4& color) { bind(); setUniform("textured", false); setUniform("color", color); } void QuadShader::updateTexture(GLID texture, GLID unit) { updateTexture(texture, unit, Vec4f::full(1.0f)); } void QuadShader::updateTexture(GLID texture, GLID unit, const Vec4f& color) { bind(); Renderer::activeTexture(unit); Renderer::bindTexture2D(texture); setUniform("textured", true); setUniform("textureSampler", unit); setUniform("color", color); } // BlurShader void BlurShader::updateWidth(float width) { bind(); setUniform("width", width); } void BlurShader::updateTexture(SRef texture) { bind(); texture->bind(); setUniform("image", texture->getUnit()); } }; ================================================ FILE: graphics/shader/shaders.h ================================================ #pragma once #include "shader.h" #include "../graphics/resource/shaderResource.h" #include namespace P3D::Graphics { class Texture; struct GuiShader : public ShaderResource { explicit GuiShader(const std::string& source) : ShaderResource("GuiShader", source, false) {} void init(const Mat4f& orthoMatrix); void setTextured(bool textured); }; struct QuadShader : public ShaderResource { explicit QuadShader(const std::string& source) : ShaderResource("QuadShader", source, false) {} void updateProjection(const Mat4f& projectionMatrix); void updateColor(const Vec4& color); void updateTexture(GLID texture, GLID unit); void updateTexture(GLID texture, GLID unit, const Vec4f& color); }; struct BlurShader : public ShaderResource { explicit BlurShader(const std::string& source) : ShaderResource("BlurShader", source, false) {} void updateWidth(float width); void updateTexture(SRef texture); }; namespace Shaders { extern SRef guiShader; extern SRef quadShader; extern SRef horizontalBlurShader; extern SRef verticalBlurShader; void onInit(); void onClose(); } }; ================================================ FILE: graphics/texture.cpp ================================================ #include "core.h" #include "texture.h" #include #include #define STB_IMAGE_IMPLEMENTATION #include namespace P3D::Graphics { #pragma region Texture //! Texture SRef Texture::_white; int getChannelsFromFormat(int format) { switch (format) { case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: return 1; case GL_RG: return 2; case GL_RGB: return 3; case GL_RGBA: return 4; default: Log::warn("Unknown format: %x, assuming 4 channels", format); return 4; } } int getFormatFromChannels(int channels) { switch (channels) { case 1: return GL_RED; case 2: return GL_RG; case 3: return GL_RGB; case 4: return GL_RGBA; default: Log::warn("Unknown amount of channels: %d, assuming RGB", channels); return GL_RGB; } } Texture Texture::load(const std::string& name) { int width; int height; int channels; //stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load(name.c_str(), &width, &height, &channels, 0); if (data) { int format = getFormatFromChannels(channels); Texture texture(width, height, data, format); stbi_image_free(data); return texture; } else { Log::subject s(name); Log::error("Failed to load texture"); return Texture(); } } SRef Texture::white() { if (_white == nullptr) { Color buffer = Colors::WHITE; _white = std::make_shared(1, 1, &buffer, GL_RGBA); } return _white; } Texture::Texture() : width(0), height(0), internalFormat(0), format(0), target(0), type(0), unit(0), channels(0) { } Texture::Texture(int width, int height) : Texture(width, height, nullptr, GL_RGBA) { } Texture::Texture(int width, int height, const void* buffer, int format) : Texture(width, height, buffer, GL_TEXTURE_2D, format, format, GL_UNSIGNED_BYTE) { } Texture::Texture(int width, int height, const void* buffer, int target, int format, int internalFormat, int type) : width(width), height(height), target(target), format(format), internalFormat(internalFormat), type(type), unit(0) { glGenTextures(1, &id); channels = getChannelsFromFormat(format); Texture::bind(); Texture::create(target, 0, internalFormat, width, height, 0, format, type, buffer); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); Texture::unbind(); } void Texture::create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer) { glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, buffer); } Texture::Texture(Texture&& other) : Bindable(other.id), width(other.width), height(other.height), internalFormat(other.internalFormat), format(other.format), target(other.target), type(other.type), unit(other.unit), channels(other.channels) { other.id = 0; other.width = 0; other.height = 0; other.type = 0; other.internalFormat = 0; other.format = 0; other.channels = 0; other.type = 0; other.unit = 0; } Texture& Texture::operator=(Texture&& other) { if (this != &other) { id = 0; width = 0; height = 0; type = 0; internalFormat = 0; format = 0; channels = 0; type = 0; unit = 0; std::swap(id, other.id); std::swap(width, other.width); std::swap(height, other.height); std::swap(internalFormat, other.internalFormat); std::swap(format, other.format); std::swap(target, other.target); std::swap(type, other.type); std::swap(unit, other.unit); std::swap(channels, other.channels); } return *this; } Texture::~Texture() { if (id == 0) return; Log::warn("Closed texture #%d", id); glDeleteTextures(1, &id); } void Texture::loadFrameBufferTexture(int width, int height) { bind(); glCopyTexImage2D(target, 0, internalFormat, 0, 0, width, height, 0); unbind(); } void Texture::resize(int width, int height, const void* buffer) { bind(); glTexImage2D(target, 0, internalFormat, width, height, 0, format, type, buffer); unbind(); } SRef Texture::colored(const Color& color) { bind(); unsigned char* buffer = (unsigned char*) malloc(width * height * channels); glGetTexImage(target, 0, format, type, buffer); for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { for (int k = 0; k < channels; k++) { int index = (i + height * j) * channels + k; unsigned char value = static_cast(buffer[index] * color.data[k]); buffer[index] = value; } } } SRef texture = std::make_shared(width, height, buffer, format); free(buffer); return texture; } void Texture::generateMipmap() { bind(); glGenerateMipmap(target); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } void Texture::resize(int width, int height) { resize(width, height, nullptr); } void Texture::bind(int unit) { this->unit = unit; glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(target, id); } void Texture::bind() { bind(unit); } void Texture::unbind() { glBindTexture(target, 0); } void Texture::close() { // Todo remove } float Texture::getAspect() const { return static_cast(width) / static_cast(height); } int Texture::getWidth() const { return width; } int Texture::getHeight() const { return height; } int Texture::getInternalFormat() const { return internalFormat; } int Texture::getFormat() const { return format; } int Texture::getTarget() const { return target; } int Texture::getType() const { return type; } int Texture::getChannels() const { return channels; } int Texture::getUnit() const { return unit; } void Texture::setUnit(int unit) { this->unit = unit; } #pragma endregion #pragma region HDRTexture //! HDRTexture HDRTexture::HDRTexture(int width, int height) : HDRTexture(width, height, nullptr) { }; HDRTexture::HDRTexture(int width, int height, const void* buffer) : Texture(width, height, buffer, GL_TEXTURE_2D, GL_RGBA, GL_RGBA16F, GL_FLOAT) { } #pragma endregion #pragma region TextureMultisample //! TextureMultisample MultisampleTexture::MultisampleTexture(int width, int height, int samples) : Texture(width, height, nullptr, GL_TEXTURE_2D_MULTISAMPLE, 0, GL_RGBA, 0), samples(samples) { glGenTextures(1, &id); bind(); create(target, 0, internalFormat, width, height, 0, format, type, nullptr); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); unbind(); } void MultisampleTexture::create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer) { glTexImage2DMultisample(target, samples, internalFormat, width, height, GL_TRUE); } void MultisampleTexture::resize(int width, int height) { bind(); glTexImage2DMultisample(target, samples, internalFormat, width, height, GL_TRUE); unbind(); } #pragma endregion #pragma region CubeMap //! CubeMap CubeMap::CubeMap(const std::string& right, const std::string& left, const std::string& top, const std::string& bottom, const std::string& front, const std::string& back) : Texture(0, 0, nullptr, GL_TEXTURE_CUBE_MAP, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE) { bind(); load(right, left, top, bottom, front, back); glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); unbind(); } void CubeMap::create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer) { } void CubeMap::load(const std::string& right, const std::string& left, const std::string& top, const std::string& bottom, const std::string& front, const std::string& back) { unsigned char* data; std::string faces[6] = { right, left, top, bottom, front, back }; stbi_set_flip_vertically_on_load(false); for (int i = 0; i < 6; i++) { data = stbi_load(faces[i].c_str(), &width, &height, &channels, 0); format = internalFormat = getFormatFromChannels(channels); if (data) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width, height, 0, internalFormat, type, data); //glGenerateMipmap(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i); } else { Log::subject s(faces[i]); Log::error("Failed to load texture"); } stbi_image_free(data); } } void CubeMap::resize(int width, int height) { Log::error("Resizing of cubemap not supported yet"); } #pragma endregion #pragma region DepthTexture //! DepthTexture DepthTexture::DepthTexture(int width, int height) : Texture(width, height, nullptr, GL_TEXTURE_2D, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_FLOAT) { } #pragma endregion }; ================================================ FILE: graphics/texture.h ================================================ #pragma once #include "bindable.h" #include "gui/color.h" namespace P3D::Graphics { class Texture : public Bindable { private: static SRef _white; protected: int width; int height; int internalFormat; int format; int target; int type; int unit; int channels; virtual void create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer); public: Texture(); Texture(int width, int height); Texture(int width, int height, const void* buffer, int format); Texture(int width, int height, const void* buffer, int target, int format, int internalFormat, int type); ~Texture(); Texture(Texture&& other); Texture(const Texture&) = delete; Texture& operator=(Texture&& other); Texture& operator=(const Texture&) = delete; virtual void bind(int unit); virtual void bind() override; virtual void unbind() override; virtual void close() override; virtual void resize(int width, int height); virtual void resize(int width, int height, const void* buffer); void loadFrameBufferTexture(int width, int height); SRef colored(const Color& color); void generateMipmap(); static Texture load(const std::string& name); static SRef white(); [[nodiscard]] float getAspect() const; [[nodiscard]] int getWidth() const; [[nodiscard]] int getHeight() const; [[nodiscard]] int getInternalFormat() const; [[nodiscard]] int getFormat() const; [[nodiscard]] int getTarget() const; [[nodiscard]] int getType() const; [[nodiscard]] int getChannels() const; [[nodiscard]] int getUnit() const; void setUnit(int unit); }; class HDRTexture : public Texture { public: HDRTexture(int width, int height, const void* buffer); HDRTexture(int width, int height); }; class MultisampleTexture : public Texture { protected: int samples; virtual void create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer) override; public: MultisampleTexture(int width, int height, int samples); void resize(int width, int height) override; }; class CubeMap : public Texture { protected: virtual void create(int target, int level, int internalFormat, int width, int height, int border, int format, int type, const void* buffer) override; public: CubeMap(const std::string& right, const std::string& left, const std::string& top, const std::string& bottom, const std::string& front, const std::string& back); void load(const std::string& right, const std::string& left, const std::string& top, const std::string& bottom, const std::string& front, const std::string& back); void resize(int width, int height) override; }; class DepthTexture : public Texture { public: DepthTexture(int width, int height); }; }; ================================================ FILE: install/clean.bat ================================================ cd %~dp0.. rmdir /S /Q include rmdir /S /Q vcpkg rmdir /S /Q build ================================================ FILE: install/clean.sh ================================================ # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" cd $INSTALLPATH/.. rm -rf include rm -rf build rm -rf vcpkg ================================================ FILE: install/setup.bat ================================================ echo Installing dependencies call %~dp0\setupDependencies.bat echo Creating build directories call %~dp0\setupBuild.bat echo Done ================================================ FILE: install/setup.sh ================================================ #!/bin/sh # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" echo Installing dependencies sh $INSTALLPATH/setupDependencies.sh echo Creating build directories sh $INSTALLPATH/setupBuild.sh echo Done ================================================ FILE: install/setupBuild.bat ================================================ mkdir %~dp0..\build mkdir %~dp0..\build_debug cmake -DCMAKE_TOOLCHAIN_FILE=%~dp0..\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -S . -B build cmake -DCMAKE_TOOLCHAIN_FILE=%~dp0..\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build_debug ================================================ FILE: install/setupBuild.sh ================================================ #!/bin/sh # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" cd $INSTALLPATH/.. mkdir build mkdir build_debug toolpath="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=$toolpath -S . -B build cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$toolpath -S . -B build_debug ================================================ FILE: install/setupBuildUbuntu.sh ================================================ #!/bin/sh # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" cd $INSTALLPATH/.. mkdir build mkdir build_debug cmake -DCMAKE_BUILD_TYPE=Release -S . -B build cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build_debug ================================================ FILE: install/setupDependencies.bat ================================================ cd %~dp0.. if NOT EXIST vcpkg ( git clone https://github.com/microsoft/vcpkg call .\vcpkg\bootstrap-vcpkg.bat ) .\vcpkg\vcpkg.exe install glfw3 opengl glew freetype stb --triplet x64-windows mkdir include cd include git clone -b docking https://github.com/ocornut/imgui.git cd imgui git checkout 05bc204dbd80dfebb3dab1511caf1cb980620c76 cd .. copy imgui\examples\imgui_impl_opengl3.cpp imgui copy imgui\examples\imgui_impl_opengl3.h imgui copy imgui\examples\imgui_impl_glfw.cpp imgui copy imgui\examples\imgui_impl_glfw.h imgui cd .. ================================================ FILE: install/setupDependencies.sh ================================================ #!/bin/sh # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" cd $INSTALLPATH/.. git clone https://github.com/microsoft/vcpkg ./vcpkg/bootstrap-vcpkg.sh ./vcpkg/vcpkg install glfw3 opengl glew freetype stb mkdir include cd include git clone -b docking https://github.com/ocornut/imgui.git cd imgui git checkout 05bc204dbd80dfebb3dab1511caf1cb980620c76 cd .. cp imgui/examples/imgui_impl_opengl3.cpp imgui cp imgui/examples/imgui_impl_opengl3.h imgui cp imgui/examples/imgui_impl_glfw.cpp imgui cp imgui/examples/imgui_impl_glfw.h imgui cd .. ================================================ FILE: install/setupDependenciesUbuntu.sh ================================================ #!/bin/sh INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" cd $INSTALLPATH/.. sudo add-apt-repository universe sudo apt update sudo apt-get install build-essential freeglut3 freeglut3-dev mesa-common-dev libglfw3 libglfw3-dev libglew-dev git libfreetype6-dev libgl1-mesa-dev sudo apt install cmake mkdir include cd include wget https://raw.githubusercontent.com/nothings/stb/master/stb_image.h git clone -b docking https://github.com/ocornut/imgui.git cd imgui git checkout 05bc204dbd80dfebb3dab1511caf1cb980620c76 cd .. cp imgui/examples/imgui_impl_opengl3.cpp imgui cp imgui/examples/imgui_impl_opengl3.h imgui cp imgui/examples/imgui_impl_glfw.cpp imgui cp imgui/examples/imgui_impl_glfw.h imgui cd .. ================================================ FILE: install/setupUbuntu.sh ================================================ #!/bin/sh # gets directory of this file to find the others INSTALLPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" echo Installing dependencies sh $INSTALLPATH/setupDependenciesUbuntu.sh echo Creating build directories sh $INSTALLPATH/setupBuildUbuntu.sh echo Done ================================================ FILE: res/default_imgui.ini ================================================ [Window][Physics3D] Pos=0,0 Size=3440,1346 Collapsed=0 [Window][Debug##Default] Pos=60,60 Size=400,400 Collapsed=0 [Window][Skybox] Pos=1916,777 Size=702,569 Collapsed=0 DockId=0x00000004,1 [Window][Properties] Pos=2620,543 Size=820,803 Collapsed=0 DockId=0x0000000F,0 [Window][Layers] Pos=1916,777 Size=702,569 Collapsed=0 DockId=0x00000004,3 [Window][Resources] Pos=0,777 Size=1914,569 Collapsed=0 DockId=0x00000005,1 [Window][Environment] Pos=1916,777 Size=702,569 Collapsed=0 DockId=0x00000004,2 [Window][Test] Pos=0,25 Size=2383,48 Collapsed=0 DockId=0x00000006,0 [Window][Scene] Pos=0,100 Size=2618,675 Collapsed=0 DockId=0x0000000B,0 [Window][Debug] Pos=0,777 Size=1914,569 Collapsed=0 DockId=0x00000005,0 [Window][Tree] Pos=2620,25 Size=820,516 Collapsed=0 DockId=0x00000002,0 [Window][Dear ImGui Demo] Pos=0,100 Size=2618,675 Collapsed=0 DockId=0x0000000B,1 [Window][Example: Custom rendering] Pos=142,220 Size=1193,649 Collapsed=0 [Window][Example: Property editor] Pos=60,60 Size=430,450 Collapsed=1 [Window][Dear ImGui Style Editor] Pos=470,57 Size=502,910 Collapsed=0 [Window][Example: Simple layout] Pos=60,60 Size=500,440 Collapsed=1 [Window][Example: Console] Pos=60,60 Size=520,600 Collapsed=0 [Window][Example: Documents] Pos=60,63 Size=736,1136 Collapsed=0 [Window][Example: Constrained Resize] Pos=922,255 Size=1108,608 Collapsed=0 DockId=0x00000003,1 [Window][Example: Long text display] Pos=60,60 Size=520,600 Collapsed=1 [Window][Transform] Pos=651,282 Size=120,95 Collapsed=0 [Window][Stacked 1] Pos=675,334 Size=449,232 Collapsed=0 [Window][Same title as another window##1] Pos=16,503 Size=496,75 Collapsed=0 [Window][###AnimatedTitle] Pos=134,305 Size=272,60 Collapsed=0 [Window][Same title as another window##2] Pos=60,393 Size=496,75 Collapsed=0 [Window][Example: Auto-resizing window] Pos=60,60 Size=496,330 Collapsed=1 [Window][Style] Pos=1916,777 Size=702,569 Collapsed=0 DockId=0x00000004,0 [Window][Add component] Pos=479,102 Size=817,607 Collapsed=0 [Window][] Pos=0,25 Size=3440,106 Collapsed=0 DockId=0x0000000D [Window][DockSpace Demo] Size=3440,1346 Collapsed=0 [Window][Another Window] Pos=1138,405 Size=374,250 Collapsed=0 [Window][Toolbar] Pos=0,25 Size=2618,73 Collapsed=0 DockId=0x0000000A,0 [Window][Error##ErrorModal] Pos=761,403 Size=279,93 Collapsed=0 [Docking][Data] DockSpace ID=0xEB0AB953 Window=0x0DCF7F20 Pos=0,25 Size=3440,1321 Split=Y Selected=0x18B8C0DE DockNode ID=0x0000000D Parent=0xEB0AB953 SizeRef=1800,70 DockNode ID=0x0000000E Parent=0xEB0AB953 SizeRef=1800,803 Split=X DockNode ID=0x00000007 Parent=0x0000000E SizeRef=1369,875 Split=Y DockNode ID=0x00000001 Parent=0x00000007 SizeRef=1707,750 Split=Y Selected=0x18B8C0DE DockNode ID=0x00000006 Parent=0x00000001 SizeRef=2383,48 HiddenTabBar=1 Selected=0x784DD132 DockNode ID=0x00000008 Parent=0x00000001 SizeRef=2383,936 Split=Y Selected=0x18B8C0DE DockNode ID=0x0000000A Parent=0x00000008 SizeRef=2383,73 HiddenTabBar=1 Selected=0x507852CA DockNode ID=0x0000000B Parent=0x00000008 SizeRef=2383,675 Selected=0x18B8C0DE DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1707,569 Split=X Selected=0xAD6468A3 DockNode ID=0x00000005 Parent=0x00000003 SizeRef=1000,931 Selected=0xAD6468A3 DockNode ID=0x00000004 Parent=0x00000003 SizeRef=367,931 Selected=0xF27C976E DockNode ID=0x00000009 Parent=0x0000000E SizeRef=429,875 Split=Y Selected=0xC89E3217 DockNode ID=0x00000002 Parent=0x00000009 SizeRef=552,516 Selected=0x170CF1E2 DockNode ID=0x0000000F Parent=0x00000009 SizeRef=552,803 Selected=0xC89E3217 ================================================ FILE: res/shaders/basic.shader ================================================ [common] #version 330 core vec4 apply(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 0.0); } vec3 apply3(mat4 matrix, vec3 vector) { return mat3(matrix) * vector; } vec4 applyT(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 1.0); } vec3 applyT3(mat4 matrix, vec3 vector) { return (matrix * vec4(vector, 1.0)).xyz; } vec4 applyN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 0.0)); } vec3 apply3N(mat4 matrix, vec3 vector) { return normalize(mat3(matrix) * vector); } vec4 applyTN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 1.0)); } vec3 applyT3N(mat4 matrix, vec3 vector) { return normalize((matrix * vec4(vector, 1.0)).xyz); } vec4 rgba(vec3 color) { return vec4(color, 1.0); } vec4 rgba(float color) { return vec4(color, color, color, 1.0); } //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; layout(location = 1) in vec3 vNormal; layout(location = 2) in vec2 vUV; layout(location = 3) in vec3 vTangent; layout(location = 4) in vec3 vBitangent; smooth out vec3 fPosition; smooth out vec2 fUV; smooth out vec3 fNormal; uniform mat4 viewMatrix; uniform mat4 modelMatrix; uniform mat4 projectionMatrix; void main() { fUV = vUV; fPosition = applyT3(modelMatrix, vPosition); mat3 normalMatrix = transpose(inverse(mat3(modelMatrix))); fNormal = normalMatrix * vNormal; gl_Position = applyT(projectionMatrix * viewMatrix, fPosition); } //------------------------------------------------------------------------------// [fragment] // Out out vec4 outColor; // In smooth in vec2 fUV; smooth in vec3 fPosition; smooth in vec3 fNormal; // General vec3 N; vec3 V; // Material vec4 albedo; float roughness; float metalness; float ambientOcclusion; // Structs struct Attenuation { float constant; float linear; float exponent; }; struct Light { vec3 position; vec3 color; float intensity; Attenuation attenuation; }; struct Material { vec4 albedo; float roughness; float metalness; float ambientOcclusion; sampler2D albedoMap; sampler2D normalMap; sampler2D metalnessMap; sampler2D roughnessMap; sampler2D ambientOcclusionMap; int textured; }; // Material uniform Material material; // Transform uniform vec3 viewPosition; // Light #define maxLights 10 uniform int lightCount; uniform Light lights[maxLights]; // Environment uniform vec3 sunDirection = vec3(1, 1, 1); uniform vec3 sunColor = vec3(1, 1, 1); uniform float exposure = 1.0; uniform float gamma = 1.0; uniform float hdr = 1.0; // Constants const float PI = 3.14159265359; float ggxTrowbridgeReitz(vec3 N, vec3 H, float roughness) { float alpha = roughness * roughness; float alpha2 = alpha * alpha; float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; float numerator = alpha2; float denominator = (NdotH2 * (alpha2 - 1.0) + 1.0); denominator = max(PI * denominator * denominator, 0.001); return numerator / denominator; } float ggxSchlick(float NdotV, float roughness) { float r = roughness + 1.0; float k = (r * r) / 8.0; float numerator = NdotV; float denominator = NdotV * (1.0 - k) + k; return numerator / denominator; } float smith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx2 = ggxSchlick(NdotV, roughness); float ggx1 = ggxSchlick(NdotL, roughness); return ggx1 * ggx2; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { // F0: surface reflection at zero incidence return F0 + (1.0 - F0) * pow(1.0 - min(cosTheta, 1.0), 5.0); } vec3 calcDirectionalLight() { vec3 directionalLight = normalize(sunDirection); float directionalFactor = 0.4 * max(dot(N, directionalLight), 0.0); vec3 directional = directionalFactor * sunColor; return directional; } vec3 calcLightColor(Light light) { // General light variables vec3 L = normalize(light.position - fPosition); vec3 H = normalize(V + L); float distance = length(light.position - fPosition); //float scaledDistance = distance / light.intensity; //float attenuation = 1.0 / (light.attenuation.constant + light.attenuation.linear * scaledDistance + light.attenuation.exponent * scaledDistance * scaledDistance); float attenuation = 1.0 / (distance * distance); vec3 radiance = light.color * attenuation * light.intensity; // Fresnel vec3 F0_NM = vec3(0.04); // Non metallic F0 vec3 F0 = mix(F0_NM, albedo.rgb, metalness); float cosTheta = max(dot(H, V), 0.0); vec3 F = fresnelSchlick(cosTheta, F0); // DFG float D = ggxTrowbridgeReitz(N, H, roughness); float G = smith(N, V, L, roughness); vec3 DFG = D * F * G; // Cook Torrance vec3 numerator = DFG; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); vec3 specular = numerator / max(denominator, 0.001); // Light contribution constants vec3 kS = F; vec3 kD = vec3(1.0) - kS; kD *= 1.0 - metalness; float NdotL = max(dot(N, L), 0.0); vec3 Lo = (kD * albedo.rgb / PI + specular) * radiance * NdotL; return Lo; } vec3 getNormalFromMap() { vec3 tangentNormal = texture(material.normalMap, fUV).xyz * 2.0 - 1.0; vec3 Q1 = dFdx(fPosition); vec3 Q2 = dFdy(fPosition); vec2 st1 = dFdx(fUV); vec2 st2 = dFdy(fUV); vec3 N = normalize(fNormal); vec3 T = normalize(Q1 * st2.t - Q2 * st1.t); vec3 B = -normalize(cross(N, T)); mat3 TBN = mat3(T, B, N); return TBN * tangentNormal; } void main() { /*if (material.textured == 1) { N = normalize(getNormalFromMap()); albedo = texture(material.albedoMap, fUV) * texture(material.albedoMap, fUV); roughness = 1 - texture(material.roughnessMap, fUV).r; metalness = texture(material.metalnessMap, fUV).r; ambientOcclusion = texture(material.ambientOcclusionMap, fUV).r; } else {*/ N = normalize(fNormal); albedo = material.albedo; roughness = material.roughness; metalness = material.metalness; ambientOcclusion = material.ambientOcclusion; /*}*/ V = normalize(viewPosition - fPosition); // Light calculations vec3 Lo = vec3(0); for (int i = 0; i < min(maxLights, lightCount); i++) { if (lights[i].intensity > 0) { Lo += calcLightColor(lights[i]); } } // Ambient vec3 ambient = vec3(1.0) * albedo.rgb * ambientOcclusion; // Directional vec3 Ld = calcDirectionalLight(); // Combine ambient and lighting vec3 color = ambient + Lo + Ld; // HDR color = hdr * (vec3(1.0) - exp(-color * exposure)) + (1.0 - hdr) * color; // Gamma color = pow(color, vec3(1.0 / gamma)); // Outcolor outColor = vec4(color, albedo.a); } ================================================ FILE: res/shaders/blur.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec4 positions; out vec2 fUV; void main() { gl_Position = vec4(positions.xy, 0.0, 1.0); fUV = positions.zw; } //------------------------------------------------------------------------------// [fragment] out vec4 outColor; in vec2 fUV; uniform sampler2D image; uniform bool horizontal; uniform float weight[5] = { 0.227027f, 0.1945946f, 0.1216216f, 0.054054f, 0.016216f }; void main() { vec2 offset = 4.0 / textureSize(image, 0); vec3 result = texture(image, fUV).rgb * weight[0]; if (horizontal) { for (int i = 1; i < 5; ++i) { result += texture(image, fUV + vec2(offset.x * i, 0.0)).rgb * weight[i]; result += texture(image, fUV - vec2(offset.x * i, 0.0)).rgb * weight[i]; } } else { for (int i = 1; i < 5; ++i) { result += texture(image, fUV + vec2(0.0, offset.y * i)).rgb * weight[i]; result += texture(image, fUV - vec2(0.0, offset.y * i)).rgb * weight[i]; } } outColor = vec4(result, 1.0); } ================================================ FILE: res/shaders/compute.shader ================================================ [compute] #version 430 layout(local_size_x = 1, local_size_y = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; void main() { vec4 pixel = vec4(1, 0, 0, 1); // get index in global work group i.e x,y position ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); // output to a specific pixel in the image imageStore(img_output, pixel_coords, pixel); } ================================================ FILE: res/shaders/debug.shader ================================================ [common] #version 400 core /* Usage: // Once ApplicationShaders::debugShader.updateProjection(viewMatrix, projectionMatrix, viewPosition); // For every model ApplicationShaders::debugShader.updateModel(modelMatrix); mesh->render(); */ // Debug vector type defenitions #define FACES fColor = vec4(0.8, 0.8, 0.8, 0.5); vec3 direction = vec3(viewPosition - center); /*if (dot(faceNormal, direction) < 0.0) return;*/ gl_Position = transform * gl_in[0].gl_Position; EmitVertex(); gl_Position = transform * gl_in[1].gl_Position; EmitVertex(); gl_Position = transform * gl_in[2].gl_Position; EmitVertex(); EndPrimitive(); #define VERTEXNORMALS fColor = vec4(0, 0, 1, 1); draw(transform, gl_in[0].gl_Position.xyz, gInput[0].gNormal); draw(transform, gl_in[1].gl_Position.xyz, gInput[1].gNormal); draw(transform, gl_in[2].gl_Position.xyz, gInput[2].gNormal); #define FACENORMALS fColor = vec4(1, 0, 1, 1); draw(transform, center, faceNormal); #define TANGENTS fColor = vec4(1, 0, 0, 1); draw(transform, gl_in[0].gl_Position.xyz, gInput[0].gTangent); draw(transform, gl_in[1].gl_Position.xyz, gInput[1].gTangent); draw(transform, gl_in[2].gl_Position.xyz, gInput[2].gTangent); #define BITANGENTS fColor = vec4(0, 1, 0, 1); draw(transform, gl_in[0].gl_Position.xyz, gInput[0].gBitangent); draw(transform, gl_in[1].gl_Position.xyz, gInput[1].gBitangent); draw(transform, gl_in[2].gl_Position.xyz, gInput[2].gBitangent); // Amount of different debug vector to show (max 5) #define VECTOR_TYPES 1 // Reorder for different types #define TYPE_1 FACES #define TYPE_2 FACENORMALS #define TYPE_3 VERTEXNORMALS #define TYPE_4 TANGENTS #define TYPE_5 BITANGENTS // Define to show arrow head //#define ARROW_HEAD #define ARROW_LENGTH 0.05 #define ARROW_WIDTH 0.025 //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; layout(location = 1) in vec3 vNormal; layout(location = 2) in vec2 vUV; layout(location = 3) in vec3 vTangent; layout(location = 4) in vec3 vBitangent; out vec3 gNormal; out vec3 gTangent; out vec3 getBitangent; out VS_OUT { vec3 gNormal; vec3 gTangent; vec3 gBitangent; } vOutput; uniform mat4 modelMatrix; vec4 applyT(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 1.0); } vec3 apply3(mat4 matrix, vec3 vector) { return mat3(matrix) * vector; } void main() { mat3 invModelMatrix = transpose(inverse(mat3(modelMatrix))); vOutput.gNormal = normalize(invModelMatrix * vNormal); vOutput.gTangent = normalize(apply3(modelMatrix, vTangent)); vOutput.gBitangent = normalize(apply3(modelMatrix, vBitangent)); gl_Position = applyT(modelMatrix, vPosition); } //------------------------------------------------------------------------------// [geometry] layout(triangles, invocations = VECTOR_TYPES) in; layout(line_strip, max_vertices = 54) out; in VS_OUT { vec3 gNormal; vec3 gTangent; vec3 gBitangent; } gInput[]; out vec4 fColor; uniform vec3 viewPosition; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; vec3 center() { return vec3(gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3; } vec3 faceNormal() { vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position); vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position); vec3 norm = normalize(cross(a, b)); return -norm; } void draw(mat4 transform, vec3 center, vec3 normal) { float arrowHeight = 0.15 * ARROW_LENGTH; vec4 arrowTop = vec4(center + ARROW_LENGTH * normal, 1); vec3 norm = normalize(cross(arrowTop.xyz - viewPosition, normal)); vec4 arrowLeft = arrowTop - vec4(arrowHeight * normal - ARROW_WIDTH * norm, 0); vec4 arrowRight = arrowTop - vec4(arrowHeight * normal + ARROW_WIDTH * norm, 0); vec4 arrowBase = arrowTop - arrowHeight * vec4(normal, 0); gl_Position = transform * vec4(center, 1); EmitVertex(); #ifdef ARROW_HEAD gl_Position = transform * arrowBase; EmitVertex(); gl_Position = transform * arrowLeft; EmitVertex(); #endif gl_Position = transform * arrowTop; EmitVertex(); #ifdef ARROW_HEAD gl_Position = transform * arrowRight; EmitVertex(); gl_Position = transform * arrowBase; EmitVertex(); #endif EndPrimitive(); } void main() { vec3 center = center(); vec3 faceNormal = faceNormal(); mat4 transform = projectionMatrix * viewMatrix; switch (gl_InvocationID) { case 0: { TYPE_1 } break; case 1: { TYPE_2 } break; case 2: { TYPE_3 } break; case 3: { TYPE_4 } break; case 4: { TYPE_5 } break; } } //------------------------------------------------------------------------------// [fragment] in vec4 fColor; out vec4 outColor; void main() { outColor = fColor; } ================================================ FILE: res/shaders/depth.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; uniform mat4 modelMatrix; uniform mat4 lightMatrix; void main() { gl_Position = lightMatrix * modelMatrix * vec4(vPosition, 1.0); } //------------------------------------------------------------------------------// [fragment] void main() {} ================================================ FILE: res/shaders/depthbuffer.shader ================================================ [common] #version 330 core [vertex] layout(location = 0) in vec4 vPosition; uniform mat4 projectionMatrix; out vec2 fUV; void main() { gl_Position = vec4(vPosition.xy, 0.0, 1.0); fUV = vPosition.zw; } [fragment] out vec4 outColor; in vec2 fUV; uniform sampler2D depthMap; uniform float near; uniform float far; float linearizeDepth(float depth) { float z = depth * 2.0 - 1.0; // Back to NDC return (2.0 * near * far) / (far + near - z * (far - near)); } void main() { float depthValue = texture(depthMap, fUV).r; outColor = vec4(vec3(linearizeDepth(depthValue) / far), 1.0); // perspective outColor = vec4(vec3(depthValue), 1.0); // orthographic } ================================================ FILE: res/shaders/font.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout (location = 0) in vec4 vertex; out vec2 fUVTexture; uniform mat4 projectionMatrix; void main() { gl_Position = projectionMatrix * vec4(vertex.xy, 0, 1); fUVTexture = vertex.zw; } //------------------------------------------------------------------------------// [fragment] in vec2 fUVTexture; out vec4 outColor; uniform sampler2D text; uniform vec4 color; void main() { outColor = vec4(color.xyz, color.w * texture(text, fUVTexture).r); } ================================================ FILE: res/shaders/gui.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec2 vposition; layout(location = 1) in vec2 vuv; layout(location = 2) in vec4 vcolor; uniform mat4 projectionMatrix; out vec4 fcolor; out vec2 fuv; void main() { gl_Position = projectionMatrix * vec4(vposition, 0.0, 1.0); fcolor = vcolor; fuv = vuv; } //------------------------------------------------------------------------------// [fragment] out vec4 outColor; in vec2 fuv; in vec4 fcolor; uniform sampler2D textureSampler; uniform bool textured = false; void main() { outColor = (textured) ? texture(textureSampler, fuv) * fcolor : fcolor; } ================================================ FILE: res/shaders/instance.shader ================================================ [properties] int mode = 0; int reflectionSamples = 8; [common] #version 330 core vec4 apply(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 0.0); } vec3 apply3(mat4 matrix, vec3 vector) { return mat3(matrix) * vector; } vec4 applyT(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 1.0); } vec3 applyT3(mat4 matrix, vec3 vector) { return (matrix * vec4(vector, 1.0)).xyz; } vec4 applyN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 0.0)); } vec3 apply3N(mat4 matrix, vec3 vector) { return normalize(mat3(matrix) * vector); } vec4 applyTN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 1.0)); } vec3 applyT3N(mat4 matrix, vec3 vector) { return normalize((matrix * vec4(vector, 1.0)).xyz); } vec4 rgba(vec3 color) { return vec4(color, 1.0); } vec4 rgba(float color) { return vec4(color, color, color, 1.0); } //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; layout(location = 1) in vec3 vNormal; layout(location = 2) in vec2 vUV; layout(location = 3) in vec3 vTangent; layout(location = 4) in vec3 vBitangent; layout(location = 5) in mat4 vModelMatrix; layout(location = 9) in vec4 vAlbedo; layout(location = 10) in vec3 vMRAo; layout(location = 11) in uvec2 vTextureFlags; smooth out vec3 fPosition; smooth out vec3 fNormal; smooth out vec2 fUV; smooth out vec4 fLightSpacePosition; flat out vec4 fAlbedo; flat out float fMetalness; flat out float fRoughness; flat out float fAmbientOcclusion; flat out uvec2 fTextureFlags; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform mat4 lightMatrix; void main() { fAlbedo = vAlbedo; fMetalness = vMRAo.x; fRoughness = vMRAo.y; fAmbientOcclusion = vMRAo.z; fTextureFlags = vTextureFlags; fUV = vUV; fPosition = applyT3(vModelMatrix, vPosition); mat3 normalMatrix = transpose(inverse(mat3(vModelMatrix))); fNormal = normalMatrix * vNormal; fLightSpacePosition = applyT(lightMatrix, fPosition); gl_Position = applyT(projectionMatrix * viewMatrix, fPosition); } //------------------------------------------------------------------------------// [fragment] // Out out vec4 outColor; // In smooth in vec2 fUV; smooth in vec3 fPosition; smooth in vec3 fNormal; smooth in vec4 fLightSpacePosition; flat in vec4 fAlbedo; flat in float fRoughness; flat in float fMetalness; flat in float fAmbientOcclusion; flat in uvec2 fTextureFlags; const ivec2 Flag_Albedo = ivec2(0, 24); const ivec2 Flag_Normal = ivec2(0, 16); const ivec2 Flag_Metalness = ivec2(0, 8); const ivec2 Flag_Roughness = ivec2(0, 0); const ivec2 Flag_AO = ivec2(1, 24); const ivec2 Flag_Gloss = ivec2(1, 16); const ivec2 Flag_Specular = ivec2(1, 8); const ivec2 Flag_Displacement = ivec2(1, 0); const int Mode_Default = 0; const int Mode_Position = 1; const int Mode_Normal = 2; const int Mode_UV = 3; const int Mode_LightSpace = 4; const int Mode_Albedo = 5; const int Mode_Metalness = 6; const int Mode_Roughness = 7; const int Mode_AO = 8; const int Mode_TextureFlags = 9; uniform int mode = Mode_Default; // General vec3 N; vec3 V; vec4 albedo; float roughness; float metalness; float ambientOcclusion; // Attenuation struct Attenuation { float constant; float linear; float exponent; }; // Light struct Light { vec3 position; vec3 color; float intensity; Attenuation attenuation; }; #define maxLights 10 uniform int lightCount; uniform Light lights[maxLights]; // Shadow uniform sampler2D shadowMap; // Transform uniform vec3 viewPosition; // Textures //uniform sampler2D textures[##MAX_TEXTURE_IMAGE_UNITS]; uniform sampler2D textures[31]; uniform samplerCube skyboxTexture; // Environment uniform int reflectionSamples = 10; uniform vec3 sunDirection = vec3(1, 1, 1); uniform vec3 sunColor = vec3(1, 1, 1); uniform float exposure = 0.8; uniform float gamma = 1.0; uniform float hdr = 1.0; // Constants float PI = 3.14159265359; float TWOPI = 6.28318531; float ggxTrowbridgeReitz(vec3 N, vec3 H, float alpha) { float alpha2 = alpha * alpha; float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; float numerator = alpha2; float denominator = (NdotH2 * (alpha2 - 1.0) + 1.0); denominator = max(PI * denominator * denominator, 0.001); return numerator / denominator; } float ggxSchlick(float NdotV, float alpha) { float t = alpha + 1.0; float k = (t * t) / 8.0; float numerator = NdotV; float denominator = NdotV * (1.0 - k) + k; return numerator / denominator; } float smith(vec3 N, vec3 V, vec3 L, float k) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx2 = ggxSchlick(NdotV, k); float ggx1 = ggxSchlick(NdotL, k); return ggx1 * ggx2; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - min(cosTheta, 1.0), 5.0); } vec3 calcDirectionalLight() { vec3 L = normalize(sunDirection); float directionalFactor = 0.4 * max(dot(N, -L), 0.0); vec3 directional = directionalFactor * sunColor; return directional; } float calcShadow() { // perform perspective divide vec3 projCoords = fLightSpacePosition.xyz / fLightSpacePosition.w; // transform to [0,1] range projCoords = projCoords * 0.5 + 0.5; if (projCoords.z > 1.0) return 0.0; // check whether current frag pos is in shadow //float bias = 0.005; float bias = max(0.05 * (1.0 - dot(N, -sunDirection)), 0.005); float currentDepth = projCoords.z; float shadow = 0.0; vec2 texelSize = 0.5 / textureSize(shadowMap, 0); for (int x = -2; x <= 2; ++x) { for (int y = -2; y <= 2; ++y) { float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; } } shadow /= 25.0; return shadow; } vec3 calcLightColor(Light light) { // General light variables vec3 L = normalize(light.position - fPosition); vec3 H = normalize(V + L); float distance = length(light.position - fPosition); float cosTheta = max(dot(H, V), 0.0); float scaledDistance = distance/* / light.intensity*/; float attenuation = 1.0 / (light.attenuation.constant + light.attenuation.linear * scaledDistance + light.attenuation.exponent * scaledDistance * scaledDistance); //float attenuation = 1.0 / (distance * distance); vec3 radiance = light.color * attenuation * light.intensity * cosTheta; // Fresnel vec3 F0_NM = vec3(0.04); // Non metallic F0 vec3 F0 = mix(F0_NM, albedo.rgb, metalness); vec3 F = fresnelSchlick(cosTheta, F0); // Cook Torrance float NDF = ggxTrowbridgeReitz(N, H, roughness * roughness); float G = smith(N, V, L, roughness); vec3 numerator = NDF * F * G; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; vec3 specular = numerator / denominator; // Light contribution constants vec3 kS = F; vec3 kD = (vec3(1.0) - kS) * (1.0 - metalness); float NdotL = max(dot(N, L), 0.0); vec3 Lo = (kD / PI * albedo.rgb + specular) * radiance * NdotL; return Lo; } float rand(float n) { return fract(sin(n) * 43758.5453123); } vec3 skybox() { vec3 wo = normalize(viewPosition - fPosition); vec3 wi = reflect(-wo, N); vec3 w = -wi; vec3 u = cross(vec3(0.0, 1.0, 0.0), w); vec3 v = cross(w, u); vec3 result = texture(skyboxTexture, wi).rgb; for (int s = 1; s <= reflectionSamples; s++) { float progress = float(s) / float(reflectionSamples); float angle = (10.0 + 3.0 * rand(s)) * TWOPI * progress; vec3 offset = cos(angle) * u + sin(angle) * v; vec3 scaledOffset = (1.0 - metalness) * offset * mix(0.02, 0.35, progress); vec3 ws = normalize(wi + scaledOffset); result += texture(skyboxTexture, ws).rgb; } result /= max(float(reflectionSamples), 1.0); return result; } int getTextureMapIndex(ivec2 flag) { return int((fTextureFlags[flag.x] >> uint(flag.y)) & uint(0xFF)); } vec4 getTextureMap(flat int map) { return texture(textures[map - 1], fUV); } vec4 getAlbedo() { int map = getTextureMapIndex(Flag_Albedo); if (map == 0) return fAlbedo; return vec4(getTextureMap(map).rgb, 1.0); } vec3 getNormal() { int map = getTextureMapIndex(Flag_Normal); if (map == 0) return normalize(fNormal); //return getTextureMap(map).rgb; vec3 tangentNormal = getTextureMap(map).xyz * 2.0 - 1.0; vec3 Q1 = dFdx(fPosition); vec3 Q2 = dFdy(fPosition); vec2 st1 = dFdx(fUV); vec2 st2 = dFdy(fUV); vec3 N = normalize(fNormal); vec3 T = normalize(Q1 * st2.t - Q2 * st1.t); vec3 B = -normalize(cross(N, T)); mat3 TBN = mat3(T, B, N); return TBN * tangentNormal; } float getMetalness() { int map = getTextureMapIndex(Flag_Metalness); if (map == 0) return fMetalness; return fMetalness * getTextureMap(map).r; } float getRoughness() { int map = getTextureMapIndex(Flag_Roughness); if (map == 0) return fRoughness; return fRoughness * getTextureMap(map).r; } float getAmbientOcclusion() { int map = getTextureMapIndex(Flag_AO); if (map == 0) return fAmbientOcclusion; return fAmbientOcclusion * getTextureMap(map).r; } vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } void main() { albedo = getAlbedo(); roughness = getRoughness(); metalness = getMetalness(); ambientOcclusion = getAmbientOcclusion(); N = getNormal(); V = normalize(viewPosition - fPosition); // Light calculations vec3 Lo = vec3(0); for (int i = 0; i < min(maxLights, lightCount); i++) if (lights[i].intensity > 0) Lo += calcLightColor(lights[i]); // Ambient vec3 ambient = albedo.rgb * mix(vec3(ambientOcclusion), skybox(), metalness); // Directional vec3 Ld = calcDirectionalLight(); // Shadow float shadow = calcShadow(); // Combine ambient and lighting vec3 color = ambient + (1.0 - shadow) * Ld * ambient + Lo; // HDR color = hdr * (vec3(1.0) - exp(-color * exposure)) + (1.0 - hdr) * color; // Gamma color = pow(color, vec3(1.0 / gamma)); // Outcolor if (mode == Mode_Default) outColor = vec4(color, albedo.a); else if (mode == Mode_Position) outColor = vec4(fPosition, 1.0); else if (mode == Mode_Normal) outColor = vec4(N, 1.0); else if (mode == Mode_UV) outColor = vec4(fUV, 0.0, 1.0); else if (mode == Mode_LightSpace) outColor = vec4(fLightSpacePosition); else if (mode == Mode_Albedo) outColor = vec4(albedo); else if (mode == Mode_Metalness) outColor = vec4(vec3(metalness), 1.0); else if (mode == Mode_Roughness) outColor = vec4(vec3(roughness), 1.0); else if (mode == Mode_AO) outColor = vec4(vec3(ambientOcclusion), 1.0); else if (mode == Mode_TextureFlags) outColor = vec4(vec3(float(getTextureMapIndex(Flag_Albedo)) / 3.0), 1.0); else outColor = vec4(hsv2rgb(vec3(mod(gl_PrimitiveID, 50) / 50.0, 1.0, 1.0)), 1.0); //outColor = vec4(vec3(fbm(fNormal)), 1.0); } ================================================ FILE: res/shaders/lighting.shader ================================================ [common] #version 330 core vec4 apply(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 0.0); } vec3 apply3(mat4 matrix, vec3 vector) { return mat3(matrix) * vector; } vec4 applyT(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 1.0); } vec3 applyT3(mat4 matrix, vec3 vector) { return (matrix * vec4(vector, 1.0)).xyz; } vec4 applyN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 0.0)); } vec3 apply3N(mat4 matrix, vec3 vector) { return normalize(mat3(matrix) * vector); } vec4 applyTN(mat4 matrix, vec3 vector) { return normalize(matrix * vec4(vector, 1.0)); } vec3 applyT3N(mat4 matrix, vec3 vector) { return normalize((matrix * vec4(vector, 1.0)).xyz); } vec4 rgba(vec3 color) { return vec4(color, 1.0); } vec4 rgba(float color) { return vec4(color, color, color, 1.0); } //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; layout(location = 1) in vec3 vNormal; layout(location = 2) in vec2 vUV; layout(location = 3) in vec3 vTangent; layout(location = 4) in vec3 vBitangent; layout(location = 5) in mat4 vModelMatrix; layout(location = 9) in vec4 vAlbedo; layout(location = 10) in vec3 vMRAo; smooth out vec3 fPosition; smooth out vec2 fUV; smooth out vec3 fNormal; flat out vec4 fAlbedo; flat out float fMetallic; flat out float fRoughness; flat out float fAmbientOcclusion; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; void main() { fAlbedo = vAlbedo; fMetallic = vMRAo.x; fRoughness = vMRAo.y; fAmbientOcclusion = vMRAo.z; fUV = vec2(vUV.x, 1-vUV.y); fPosition = applyT3(vModelMatrix, vPosition); fNormal = apply3(vModelMatrix, vNormal); gl_Position = applyT(projectionMatrix * viewMatrix, fPosition); } //------------------------------------------------------------------------------// [fragment] // Out out vec4 outColor; // In smooth in vec2 fUV; smooth in vec3 fPosition; smooth in vec3 fNormal; flat in vec4 fAlbedo; flat in float fRoughness; flat in float fMetallic; flat in float fAmbientOcclusion; // General vec3 N; vec3 V; vec4 albedo; float roughness; float metallic; float ambientOcclusion; // Structs struct Attenuation { float constant; float linear; float exponent; }; struct Light { vec3 position; vec3 color; float intensity; Attenuation attenuation; }; // Transform uniform vec3 viewPosition; // Light #define maxLights 10 uniform int lightCount; uniform Light lights[maxLights]; // Textures uniform sampler2D albedoMap; uniform sampler2D normalMap; uniform sampler2D metallicMap; uniform sampler2D roughnessMap; uniform sampler2D ambientOcclusionMap; uniform int textured; // Environment uniform vec3 sunDirection = vec3(1, 1, 1); uniform vec3 sunColor = vec3(1, 1, 1); uniform float exposure = 1.0; uniform float gamma = 1.0; uniform float hdr = 1.0; // Constants const float PI = 3.14159265359; float ggxTrowbridgeReitz(vec3 N, vec3 H, float roughness) { float alpha = roughness * roughness; float alpha2 = alpha * alpha; float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; float numerator = alpha2; float denominator = (NdotH2 * (alpha2 - 1.0) + 1.0); denominator = max(PI * denominator * denominator, 0.001); return numerator / denominator; } float ggxSchlick(float NdotV, float roughness) { float r = roughness + 1.0; float k = (r * r) / 8.0; float numerator = NdotV; float denominator = NdotV * (1.0 - k) + k; return numerator / denominator; } float smith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); float ggx2 = ggxSchlick(NdotV, roughness); float ggx1 = ggxSchlick(NdotL, roughness); return ggx1 * ggx2; } vec3 fresnelSchlick(float cosTheta, vec3 F0) { // F0: surface reflection at zero incidence return F0 + (1.0 - F0) * pow(1.0 - min(cosTheta, 1.0), 5.0); } vec3 calcDirectionalLight() { vec3 directionalLight = normalize(sunDirection); float directionalFactor = 0.4 * max(dot(N, directionalLight), 0.0); vec3 directional = directionalFactor * sunColor; return directional; } vec3 calcLightColor(Light light) { // General light variables vec3 L = normalize(light.position - fPosition); vec3 H = normalize(V + L); float distance = length(light.position - fPosition); //float scaledDistance = distance / light.intensity; //float attenuation = 1.0 / (light.attenuation.constant + light.attenuation.linear * scaledDistance + light.attenuation.exponent * scaledDistance * scaledDistance); float attenuation = 1.0 / (distance * distance); vec3 radiance = light.color * attenuation; // Fresnel vec3 F0_NM = vec3(0.04); // Non metallic F0 vec3 F0 = mix(F0_NM, albedo.rgb, metallic); float cosTheta = max(dot(H, V), 0.0); vec3 F = fresnelSchlick(cosTheta, F0); // DFG float D = ggxTrowbridgeReitz(N, H, roughness); float G = smith(N, V, L, roughness); vec3 DFG = D * F * G; // Cook Torrance vec3 numerator = DFG; float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); vec3 specular = numerator / max(denominator, 0.001); // Light contribution constants vec3 kS = F; vec3 kD = vec3(1.0) - kS; kD *= 1.0 - metallic; float NdotL = max(dot(N, L), 0.0); vec3 Lo = (kD * albedo.rgb / PI + specular) * radiance * NdotL; return Lo; } vec3 getNormalFromMap() { vec3 tangentNormal = texture(normalMap, fUV).xyz * 2.0 - 1.0; vec3 Q1 = dFdx(fPosition); vec3 Q2 = dFdy(fPosition); vec2 st1 = dFdx(fUV); vec2 st2 = dFdy(fUV); vec3 N = normalize(fNormal); vec3 T = normalize(Q1 * st2.t - Q2 * st1.t); vec3 B = -normalize(cross(N, T)); mat3 TBN = mat3(T, B, N); return normalize(TBN * tangentNormal); } void main() { if (textured == 1) { albedo = texture(albedoMap, fUV) * texture(albedoMap, fUV); N = getNormalFromMap(); roughness = 1-texture(roughnessMap, fUV).r; metallic = texture(metallicMap, fUV).r; ambientOcclusion = texture(ambientOcclusionMap, fUV).r; } else { albedo = fAlbedo; roughness = fRoughness; metallic = fMetallic; ambientOcclusion = fAmbientOcclusion; } V = normalize(viewPosition - fPosition); // Light calculations vec3 Lo = vec3(0); for (int i = 0; i < min(maxLights, lightCount); i++) { if (lights[i].intensity > 0) { Lo += calcLightColor(lights[i]); } } // Ambient vec3 ambient = vec3(0.03) * albedo.rgb * ambientOcclusion; // Combine ambient and lighting vec3 color = ambient + Lo; // HDR color = hdr * (vec3(1.0) - exp(-color * exposure)) + (1.0 - hdr) * color; // Gamma color = pow(color, vec3(1.0 / gamma)); // Outcolor outColor = vec4(color, albedo.a); //outColor = rgba(N); //outColor = texture(normalMap, fUV); //outColor = texture(textureMap, fUV); //outColor = vec4(fUV, 0, 1); } ================================================ FILE: res/shaders/line.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout (location = 0) in vec3 vposition; layout (location = 1) in vec4 vcolor; out vec4 fcolor; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; void main() { gl_Position = projectionMatrix * viewMatrix * vec4(vposition, 1.0); fcolor = vcolor; } //------------------------------------------------------------------------------// [fragment] in vec4 fcolor; out vec4 outColor; void main() { outColor = fcolor; } ================================================ FILE: res/shaders/mask.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vPosition; uniform mat4 projectionMatrix; uniform mat4 modelMatrix; uniform mat4 viewMatrix; uniform vec3 viewPosition; void main() { gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vPosition, 1.0); } //------------------------------------------------------------------------------// [fragment] out vec4 outColor; uniform vec4 color = vec4(1, 1, 1, 1); void main() { outColor = color; } ================================================ FILE: res/shaders/origin.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 rotation; void main() { gl_Position = vec4(0.8, -0.8, -1, 1); } //------------------------------------------------------------------------------// [geometry] layout(points) in; layout(line_strip, max_vertices = 256) out; uniform mat4 rotatedViewMatrix; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform mat4 orthoMatrix; uniform vec3 viewPosition; out vec3 fcolor; int gridSize = 32; void main() { vec3 unitVectors[3] = vec3[](vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1)); float size = 0.1; for (int i = 0; i < 3; i++) { vec3 rotatedUnitVector = (rotatedViewMatrix * vec4(unitVectors[i], 0)).xyz * size; float arrowLength = 0.025; float arrowWidth = 0.00625; vec4 origin = vec4(0, 0, gl_in[0].gl_Position.zw); vec4 arrowTop = origin + vec4(rotatedUnitVector, 0); vec3 norm = normalize(cross(arrowTop.xyz, rotatedUnitVector)); vec3 unitRotation = normalize(rotatedUnitVector); vec4 arrowLeft = arrowTop - vec4(arrowLength * unitRotation - arrowWidth * norm, 0); vec4 arrowRight = arrowTop - vec4(arrowLength * unitRotation + arrowWidth * norm, 0); vec4 arrowBase = arrowTop - arrowLength * vec4(unitRotation, 0); fcolor = unitVectors[i]; vec4 position = vec4(gl_in[0].gl_Position.xy, 0, 0); gl_Position = position + orthoMatrix * origin; EmitVertex(); gl_Position = position + orthoMatrix * arrowBase; EmitVertex(); gl_Position = position + orthoMatrix * arrowLeft; EmitVertex(); gl_Position = position + orthoMatrix * arrowTop; EmitVertex(); gl_Position = position + orthoMatrix * arrowRight; EmitVertex(); gl_Position = position + orthoMatrix * arrowBase; EmitVertex(); EndPrimitive(); } /*float camerax = floor(viewPosition.x); float cameraz = floor(viewPosition.z); for (float t = 0.0; t < gridSize / 2; t++) { fcolor = vec3(0.5 - t / gridSize); gl_Position = projectionMatrix * viewMatrix * vec4(camerax - gridSize / 2, 0, cameraz + t, 1); EmitVertex(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax + gridSize / 2, 0, cameraz + t, 1); EmitVertex(); EndPrimitive(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax - gridSize / 2, 0, cameraz - t, 1); EmitVertex(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax + gridSize / 2, 0, cameraz - t, 1); EmitVertex(); EndPrimitive(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax + t, 0, cameraz - gridSize / 2, 1); EmitVertex(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax + t, 0, cameraz + gridSize / 2, 1); EmitVertex(); EndPrimitive(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax - t, 0, cameraz - gridSize / 2, 1); EmitVertex(); gl_Position = projectionMatrix * viewMatrix * vec4(camerax - t, 0, cameraz + gridSize / 2, 1); EmitVertex(); EndPrimitive(); }*/ } //------------------------------------------------------------------------------// [fragment] layout(location = 0) out vec4 outColor; in vec3 fcolor; void main() { outColor = vec4(fcolor, 1); } ================================================ FILE: res/shaders/point.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vposition; layout(location = 1) in float vsize; layout(location = 2) in vec3 vcolor1; layout(location = 3) in vec3 vcolor2; out vec3 gcolor1; out vec3 gcolor2; out float gsize; void main() { gcolor1 = vcolor1; gcolor2 = vcolor2; gsize = vsize; gl_Position = vec4(vposition, 1); } //------------------------------------------------------------------------------// [geometry] layout(points) in; layout(triangle_strip, max_vertices = 24) out; in vec3 gcolor1[]; in vec3 gcolor2[]; in float gsize[]; out vec3 fcolor; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform vec3 viewPosition; void makeTriangle(vec4 a, vec4 b, vec4 c, vec3 color) { fcolor = color; gl_Position = projectionMatrix * viewMatrix * a; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * b; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * c; EmitVertex(); EndPrimitive(); } void main() { vec4 origin = gl_in[0].gl_Position; float size = gsize[0] * sqrt(length(origin.xyz-viewPosition)); vec3 colorA = gcolor1[0]; vec3 colorB = gcolor2[0]; vec4 xPos = origin + vec4(size, 0, 0, 0); vec4 xNeg = origin + vec4(-size, 0, 0, 0); vec4 yPos = origin + vec4(0, size, 0, 0); vec4 yNeg = origin + vec4(0, -size, 0, 0); vec4 zPos = origin + vec4(0, 0, size, 0); vec4 zNeg = origin + vec4(0, 0, -size, 0); makeTriangle(xPos, yPos, zPos, colorA); makeTriangle(zPos, yPos, xNeg, colorB); makeTriangle(xNeg, yPos, zNeg, colorA); makeTriangle(zNeg, yPos, xPos, colorB); makeTriangle(zNeg, yNeg, xNeg, colorB); makeTriangle(xPos, yNeg, zNeg, colorA); makeTriangle(zPos, yNeg, xPos, colorB); makeTriangle(xNeg, yNeg, zPos, colorA); } //------------------------------------------------------------------------------// [fragment] layout(location = 0) out vec4 outColor; in vec3 fcolor; void main() { outColor = vec4(fcolor, 1); } ================================================ FILE: res/shaders/postprocess.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec4 positions; out vec2 fUV; void main() { gl_Position = vec4(positions.xy, 0.0, 1.0); fUV = positions.zw; } //------------------------------------------------------------------------------// [fragment] in vec2 fUV; out vec4 outColor; uniform sampler2D textureSampler; const float offset = 1.0 / 600.0; float rbgToLin(float channel) { if (channel <= 0.04045) { return channel / 12.92; } else { return pow((channel + 0.055) / 1.055, 2.4); } } vec4 kernelEffect() { vec2 offsets[9] = vec2[]( vec2(-offset, offset), vec2(0.0f, offset), vec2(offset, offset), vec2(-offset, 0.0f), vec2(0.0f, 0.0f), vec2(offset, 0.0f), vec2(-offset, -offset), vec2(0.0f, -offset), vec2(offset, -offset) ); float gx[9] = float[]( -1, 0, 1, -1, 0, 1, -1, 0, 1 ); float gy[9] = float[]( -1, -1, -1, 0, 0, 0, 1, 1, 1 ); float kernel[9] = float[]( 0, 0, 0, 0, 1, 0, 0, 0, 0 ); vec3 sampleGrayTexture[9]; vec3 sampleColorTexture[9]; vec3 color = vec3(0.0); vec3 sx = vec3(0.0); vec3 sy = vec3(0.0); for (int i = 0; i < 9; i++) { vec3 rgb = vec3(texture(textureSampler, fUV.st + offsets[i])); sampleColorTexture[i] = rgb; color += rgb * kernel[i]; //float y = 0.2126 * rbgToLin(rgb.x) + 0.7152 * rbgToLin(rgb.y) + 0.0722 * rbgToLin(rgb.z); //sampleGrayTexture[i] = vec3(y); //sx += sampleGrayTexture[i] * gx[i]; //sy += sampleGrayTexture[i] * gy[i]; } //vec3 s = vec3(1.0) - sqrt(sx*sx + sy*sy); return vec4(color, 1.0); } vec4 defaultEffect() { return texture(textureSampler, fUV.st); } vec4 pixelEdgeEffect() { vec4 color = vec4(fwidth(defaultEffect().x * 64.0)); return 1.0 - floor(color); } vec4 knitEffect() { vec2 resolution = vec2(1920, 1080); vec2 tileSize = vec2(16.0, 16.0); float threads = 10.0; vec2 posInTile = mod(gl_FragCoord.xy, tileSize); vec2 tileNum = floor(gl_FragCoord.xy / tileSize); vec2 nrmPosInTile = posInTile / tileSize; tileNum.y += floor(abs(nrmPosInTile.x - 0.5) + nrmPosInTile.y); vec2 texCoord = tileNum * tileSize / resolution; //texCoord.y = 1.0 - texCoord.y; vec3 color = texture(textureSampler, texCoord).rgb; color *= fract((nrmPosInTile.y + abs(nrmPosInTile.x - 0.5)) * floor(threads)); return vec4(color, 1.0); } void main() { outColor = defaultEffect(); } ================================================ FILE: res/shaders/quad.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec4 vPosition; uniform mat4 projectionMatrix; out vec2 fUV; void main() { gl_Position = projectionMatrix * vec4(vPosition.xy, 0.0, 1.0); fUV = vPosition.zw; } //------------------------------------------------------------------------------// [fragment] out vec4 outColor; in vec2 fUV; uniform bool textured; uniform sampler2D textureSampler; uniform vec4 color = vec4(1, 1, 1, 1); void main() { outColor = (textured) ? texture(textureSampler, fUV) * color : color; } ================================================ FILE: res/shaders/sky.shader ================================================ [properties] float densityFalloff (uniform_ddkd) = 4 (0:100); int inScatterPoints = 10 (1:100); int opticalDepthPoints = 10 (1:100); vec3 sunPosition = 10.0, 20.0, 0.0; vec3 waveLengths = 700, 530, 440; float scatteringStrength = 1.0 (1.0:10.0); float atmosphereScale = 1.0 (0.0:5.0); [common] #version 330 core const float INFINITY = 1.0 / 0.0; const float EPSILON = 0.0001; const float PI = 3.14159265358979323846; uniform mat4 uViewMatrix; uniform mat4 uProjectionMatrix; uniform float densityFalloff = 4.0; uniform int inScatterPoints = 10; uniform int opticalDepthPoints = 10; uniform vec3 sunPosition = vec3(10.0, 20.0, 0.0); uniform vec3 waveLengths = vec3(700, 530, 440); uniform float scatteringStrength = 1.0; uniform float atmosphereScale = 1.0; vec4 applyT(mat4 matrix, vec3 vector) { return matrix * vec4(vector, 1.0); } vec3 applyT3(mat4 matrix, vec3 vector) { return (matrix * vec4(vector, 1.0)).xyz; } /////////////////////////////////////////////////////////////////////////////////////// [vertex] layout(location = 0) in vec4 vPositionUV; smooth out vec2 fUV; void main() { fUV = vPositionUV.zw; gl_Position = vec4(vPositionUV.xy, 0.0, 1.0); } /////////////////////////////////////////////////////////////////////////////////////// [fragment] // Structs struct Sphere { vec3 center; float radius; }; struct Plane { vec3 center; vec3 normal; }; struct Ray { vec3 origin; vec3 direction; }; struct Hit { float t; float dt; }; struct Screen { vec2 dimension; }; // In smooth in vec2 fUV; // Out out vec4 outColor; uniform Sphere uAtmosphere = Sphere(vec3(0.0, 0.0, 0.0), 10.0); uniform Plane uGround = Plane(vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0)); uniform Ray uCamera = Ray(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0)); uniform Screen uScreen = Screen(vec2(1.0, 1.0)); uniform sampler2D uOriginalColor; // Functions bool inside(vec3 point, Sphere sphere) { return length(point - sphere.center) <= sphere.radius; } bool above(vec3 point, Plane plane) { return dot(point - plane.center, plane.normal) > 0.0; } Hit hitSphere(Sphere sphere, Ray ray) { vec3 offset = ray.origin - sphere.center; float a = 1.0; float b = 2.0 * dot(offset, ray.direction); float c = dot(offset, offset) - sphere.radius * sphere.radius; float d = b * b - 4.0 * a * c; if (d > 0.0) { float s = sqrt(d); float near = (-b - s) / (2.0 * a); float far = (-b + s) / (2.0 * a); // Behind if (near < 0.0 && far < 0.0) return Hit(INFINITY, 0.0); // Inside if (near * far < 0.0) return Hit(max(near, far), 0.0); // Outside return Hit(min(near, far), 0.0); } // Miss return Hit(INFINITY, 0.0); } Hit hitPlane(Plane plane, Ray ray) { float denominator = dot(ray.direction, plane.normal); // Parallel if (abs(denominator) < EPSILON) return Hit(INFINITY, 0.0); float t = -dot(plane.center - ray.origin, plane.normal) / denominator; // Behind if (t < 0.0) return Hit(INFINITY, 0.0); return Hit(t, 0.0); } Hit hitDisk(Plane plane, Sphere sphere, Ray ray) { Hit planeHit = hitPlane(plane, ray); if (planeHit.t == INFINITY) return Hit(INFINITY, 0.0); vec3 point = ray.origin + planeHit.t * ray.direction; if (!inside(point, sphere)) return Hit(INFINITY, 0.0); return planeHit; } Hit hitHemisphere(Plane plane, Sphere sphere, Ray ray) { Hit sphereHit = hitSphere(sphere, ray); Hit planeHit = hitPlane(plane, ray); Hit hit = Hit(min(sphereHit.t, planeHit.t), 0.0); return hit; } float densityAtPoint(vec3 densitySamplePoint) { float heightAboveSurface = dot(densitySamplePoint - uGround.center, uGround.normal); float atmosphereHeight = uAtmosphere.radius; float normalizedHeightAboveSurface = heightAboveSurface / atmosphereHeight; float localDensity = exp(-normalizedHeightAboveSurface * (densityFalloff / 10.0)) * (1.0 - normalizedHeightAboveSurface); return localDensity; } float opticalDepth(Ray ray, float rayLength) { vec3 densityAtSamplePoint = ray.origin; float stepSize = rayLength / float(opticalDepthPoints - 1); float opticalDepth = 0.0; for (int i = 0; i < opticalDepthPoints; i++) { float localDensity = densityAtPoint(densityAtSamplePoint); opticalDepth += localDensity * stepSize; densityAtSamplePoint += ray.direction * stepSize; } return opticalDepth; } vec3 calculateLight(Ray ray, float rayLength, vec3 originalColor) { vec3 scatteringCoefficients = pow(vec3(400.0) / waveLengths, vec3(4.0)) * scatteringStrength; vec3 inScatterPoint = ray.origin; float stepSize = rayLength / float(inScatterPoints - 1); float viewRayOpticalDepth = 0.0; vec3 inScatteredLight = vec3(0.0); for (int i = 0; i < inScatterPoints; i++) { Ray sunRay = Ray(inScatterPoint, normalize(sunPosition - inScatterPoint)); Ray viewRay = Ray(inScatterPoint, -ray.direction); float sunRayLength = hitSphere(uAtmosphere, sunRay).t; float viewRayLength = stepSize * float(i); float sunRayOpticalDepth = opticalDepth(sunRay, sunRayLength); viewRayOpticalDepth = opticalDepth(viewRay, viewRayLength); vec3 transmittance = exp(-(sunRayOpticalDepth + viewRayOpticalDepth) * scatteringCoefficients); float localDensity = densityAtPoint(inScatterPoint); inScatteredLight += localDensity * transmittance * scatteringCoefficients * stepSize; inScatterPoint += ray.direction * stepSize; } float originalColorTransmittance = exp(-viewRayOpticalDepth); return originalColor * originalColorTransmittance + normalize(inScatteredLight); } float atan2(float y, float x) { bool s = abs(x) > abs(y); return mix(PI / 2.0 - atan(x, y), atan(y, x), s); } const float pi = 3.14159265359; const float invPi = 1.0 / pi; const float zenithOffset = 0.1; const float multiScatterPhase = 0.1; const float density = 0.7; const float anisotropicIntensity = 0.0; //Higher numbers result in more anisotropic scattering const vec3 skyColor = vec3(0.39, 0.57, 1.0) * (1.0 + anisotropicIntensity); //Make sure one of the conponents is never 0.0 #define smooth(x) x*x*(3.0-2.0*x) #define zenithDensity(x) density / pow(max(x - zenithOffset, 0.35e-2), 0.75) vec3 getSkyAbsorption(vec3 x, float y) { vec3 absorption = x * -y; absorption = exp2(absorption) * 2.0; return absorption; } float getSunPoint(vec2 p, vec2 lp) { return smoothstep(0.03, 0.026, distance(p, lp)) * 50.0; } float getRayleigMultiplier(vec2 p, vec2 lp) { return 1.0 + pow(1.0 - clamp(distance(p, lp), 0.0, 1.0), 2.0) * pi * 0.5; } float getMie(vec2 p, vec2 lp) { float disk = clamp(1.0 - pow(distance(p, lp), 0.1), 0.0, 1.0); return disk * disk * (3.0 - 2.0 * disk) * 2.0 * pi; } vec3 getAtmosphericScattering(vec2 p, vec2 lp) { vec2 correctedLp = lp / max(uScreen.dimension.x, uScreen.dimension.y) * uScreen.dimension.xy; float zenith = zenithDensity(p.y); float sunPointDistMult = clamp(length(max(correctedLp.y + multiScatterPhase - zenithOffset, 0.0)), 0.0, 1.0); float rayleighMult = getRayleigMultiplier(p, correctedLp); vec3 absorption = getSkyAbsorption(skyColor, zenith); vec3 sunAbsorption = getSkyAbsorption(skyColor, zenithDensity(correctedLp.y + multiScatterPhase)); vec3 sky = skyColor * zenith * rayleighMult; vec3 sun = getSunPoint(p, correctedLp) * absorption; vec3 mie = getMie(p, correctedLp) * sunAbsorption; vec3 totalSky = mix(sky * absorption, sky / (sky + 0.5), sunPointDistMult); totalSky += sun + mie; totalSky *= sunAbsorption * 0.5 + 0.5 * length(sunAbsorption); return totalSky; } vec3 jodieReinhardTonemap(vec3 c) { float l = dot(c, vec3(0.2126, 0.7152, 0.0722)); vec3 tc = c / (c + 1.0); return mix(c / (l + 1.0), tc, tc); } // Main void main() { float s = 1; vec3 up = vec3(0.0, 1.0, 0.0); vec3 w = -uCamera.direction; vec3 u = cross(up, w); vec3 v = cross(w, u); vec3 cam = vec3((gl_FragCoord.xy - uScreen.dimension / 2.0) / uScreen.dimension.x * s, -1.0); //vec3 cam = vec3(fUV * 2.0 - vec2(1.0), -1); vec3 dir = normalize(cam.x * u + cam.y * v + cam.z * w); Ray ray = Ray(uCamera.origin, dir); float distanceThroughAtmosphere = hitHemisphere(uGround, uAtmosphere, ray).t; vec3 originalColor = texture(uOriginalColor, fUV).rgb; /*if (distanceThroughAtmosphere != INFINITY) { //vec3 pointInAtmosphere = ray.origin + ray.direction * (distanceThroughAtmosphere + EPSILON); vec3 light = calculateLight(Ray(ray.origin, ray.direction), distanceThroughAtmosphere - EPSILON * 2.0, originalColor); outColor = vec4(light, 1.0); } else { outColor = vec4(originalColor, 1.0); }*/ /*if (distanceThroughAtmosphere != INFINITY) originalColor *= 0.6; outColor = vec4(originalColor, 1.0);*/ //outColor = vec4(dir / 2.0 + vec3(0.5), 1.0); vec2 iMouse = uScreen.dimension / 2.0; vec2 position = gl_FragCoord.xy / max(uScreen.dimension.x, uScreen.dimension.y) * 2.0; vec2 lightPosition = iMouse.xy / uScreen.dimension.xy * 2.0 + ((iMouse.x + iMouse.y) == 0.0 ? vec2(1.0, 0.4) : vec2(0.0)); vec3 color = originalColor*getAtmosphericScattering(position, lightPosition) * pi; color = jodieReinhardTonemap(color); color = pow(color, vec3(2.2)); //Back to linear outColor = vec4(color, 1.0); } ================================================ FILE: res/shaders/skybox.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout (location = 0) in vec3 vposition; out vec3 ftextureUV; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform vec3 viewPosition; uniform vec3 lightDirection; const float PI = 3.141592654; const vec4 Esun = vec4(1.0, 1.0, 1.0, 12.0); const float g = 5.2; float density(float h) { float H = 8500; return exp(-h / H); } vec3 Bm(float h) { vec3 cBm = vec3(4e-6, 6e-6, 2.4e-6); return cBm; } vec3 Br(float h) { vec3 cBr = vec3(0.00000519673, 0.0000121427, 0.0000296453); return density(h) * cBr; } vec3 rayleigh(float h, float cosphi) { float phase = 1.0 + cosphi * cosphi; return 3.0 / 16.0 / PI * Br(h) * phase; } vec3 mie(float h, float cosphi) { float phase = pow(1.0 - g, 2.0) / pow(1.0 + g * g - 2.0 * g * cosphi, 1.5); return 1.0 / 4.0 / PI * Bm(h) * phase; } vec3 Fex(float h, float s) { return exp(-(Br(h) + Bm(h)) * s); } vec3 scatter(float h, float s, float cosphi) { return (rayleigh(h, cosphi) + mie(h, cosphi)) / (Br(h) + Bm(h)) * Esun.w * (1.0 - Fex(h, s)); } void main() { vec4 position = projectionMatrix * vec4((viewMatrix * vec4(vposition, 0.0)).xyz, 1.0); vec3 light = lightDirection; vec3 normal = vposition; /*float distance = (1.05 - pow(normal.y, 0.2)) * 150000; float cosphi = dot(normal, light) / length(light) / length(normal); float h = position.y; scattering = scatter(h, distance, cosphi); extinction = Fex(h, distance);*/ ftextureUV = vposition; gl_Position = position; } //------------------------------------------------------------------------------// [fragment] out vec4 outColor; in vec3 ftextureUV; uniform samplerCube skyboxTexture; void main() { vec4 skybox = texture(skyboxTexture, ftextureUV); outColor = skybox; } ================================================ FILE: res/shaders/test.shader ================================================ [common] #version 410 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vposition; layout(location = 1) in vec3 vnormal; layout(location = 2) in vec2 vUV; uniform mat4 modelMatrix; out vec3 tcposition; out vec3 tcnormal; out vec2 tcUV; void main() { tcposition = (modelMatrix * vec4(vposition, 1.0)).xyz; tcnormal = (modelMatrix * vec4(vnormal, 0.0)).xyz; tcUV = vUV; } //------------------------------------------------------------------------------// [tesselation control] // define the number of CPs in the output patch layout(vertices = 4) out; uniform vec3 viewPosition; // attributes of the input CPs in vec3 tcposition[]; in vec3 tcnormal[]; in vec2 tcUV[]; // attributes of the output CPs out vec3 teposition[]; out vec3 tenormal[]; out vec2 teUV[]; void main() { // Set the control points of the output patch teposition[gl_InvocationID] = tcposition[gl_InvocationID]; tenormal[gl_InvocationID] = tcnormal[gl_InvocationID]; teUV[gl_InvocationID] = tcUV[gl_InvocationID]; // Calculate the distance from the camera to the three control points if (gl_InvocationID == 0) { gl_TessLevelInner[0] = 5.0; gl_TessLevelInner[1] = 5.0; gl_TessLevelOuter[0] = 5.0; gl_TessLevelOuter[1] = 5.0; gl_TessLevelOuter[2] = 5.0; gl_TessLevelOuter[3] = 5.0; } } //------------------------------------------------------------------------------// [tesselation evaluate] layout(quads, equal_spacing, ccw) in; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform sampler2D displacementMap; //uniform float gDispFactor; in vec3 teposition[]; in vec3 tenormal[]; in vec2 teUV[]; out vec3 fposition; out vec3 fnormal; out vec2 fUV; vec2 interpolate(vec2 v0, vec2 v1, vec2 v2, vec2 v3) { // interpolate in horizontal direction between vert. 0 and 3 vec2 p0 = mix(v0, v3, gl_TessCoord.x); // interpolate in horizontal direction between vert. 1 and 2 vec2 p1 = mix(v1, v2, gl_TessCoord.x); // interpolate in vertical direction vec2 p = mix(p0, p1, gl_TessCoord.y); return p; } vec3 interpolate(vec3 v0, vec3 v1, vec3 v2, vec3 v3) { // interpolate in horizontal direction between vert. 0 and 3 vec3 p0 = mix(v3, v0, gl_TessCoord.x); // interpolate in horizontal direction between vert. 1 and 2 vec3 p1 = mix(v2, v1, gl_TessCoord.x); // interpolate in vertical direction vec3 p = mix(p0, p1, gl_TessCoord.y); return p; } void main() { // Interpolate the attributes of the output vertex using the barycentric coordinates fposition = (interpolate(teposition[0], teposition[1], teposition[2], teposition[3])); fnormal = normalize(interpolate(tenormal[0], tenormal[1], tenormal[2], tenormal[3])); fUV = interpolate(teUV[0], teUV[1], teUV[2], teUV[3]); //fposition += fnormal * length(gl_TessCoord.xy); gl_Position = projectionMatrix * viewMatrix * vec4(fposition, 1.0); } //------------------------------------------------------------------------------// [fragment] in vec3 fposition; in vec3 fnormal; in vec2 fUV; out vec4 outColor; void main() { fposition; fnormal; fUV; vec3 color = vec3(1); outColor = vec4(color, 1); } ================================================ FILE: res/shaders/vector.shader ================================================ [common] #version 330 core //------------------------------------------------------------------------------// [vertex] layout(location = 0) in vec3 vposition; layout(location = 1) in vec3 vrotation; layout(location = 2) in vec3 vcolor; out vec3 grotation; out vec3 gcolor; void main() { gcolor = vcolor; grotation = vrotation; gl_Position = vec4(vposition, 1); } //------------------------------------------------------------------------------// [geometry] layout(points) in; layout(line_strip, max_vertices = 6) out; in vec3 grotation[]; in vec3 gcolor[]; out vec3 fcolor; uniform mat4 viewMatrix; uniform mat4 projectionMatrix; uniform vec3 viewPosition; void main() { float arrowLength = 0.1; float arrowWidth = 0.025; vec4 origin = gl_in[0].gl_Position; vec4 arrowTop = gl_in[0].gl_Position + vec4(grotation[0], 0); vec3 norm = normalize(cross(arrowTop.xyz - viewPosition, grotation[0])); vec3 unitRotation = normalize(grotation[0]); vec4 arrowLeft = arrowTop - vec4(arrowLength * unitRotation - arrowWidth * norm, 0); vec4 arrowRight = arrowTop - vec4(arrowLength * unitRotation + arrowWidth * norm, 0); vec4 arrowBase = arrowTop - arrowLength * vec4(unitRotation, 0); fcolor = gcolor[0]; gl_Position = projectionMatrix * viewMatrix * origin; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * arrowBase; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * arrowLeft; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * arrowTop; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * arrowRight; EmitVertex(); gl_Position = projectionMatrix * viewMatrix * arrowBase; EmitVertex(); EndPrimitive(); } //------------------------------------------------------------------------------// [fragment] layout(location = 0) out vec4 outColor; in vec3 fcolor; void main() { outColor = vec4(fcolor, 1); } ================================================ FILE: tests/boundsTree2Tests.cpp ================================================ #include "testsMain.h" #include #include "testsMain.h" #include "compare.h" #include "generators.h" #include #include #include #include using namespace P3D; static void shuffleTreeRecursive(TreeTrunk& curTrunk, int curTrunkSize) { for(int iter = 0; iter < (curTrunkSize - 1) * curTrunkSize; iter++) { int index1 = generateInt(curTrunkSize); int index2; do { index2 = generateInt(curTrunkSize); } while(index1 == index2); BoundsTemplate tmpBounds = curTrunk.getBoundsOfSubNode(index1); TreeNodeRef tmpNode = std::move(curTrunk.subNodes[index1]); curTrunk.setSubNode(index1, std::move(curTrunk.subNodes[index2]), curTrunk.getBoundsOfSubNode(index2)); curTrunk.setSubNode(index2, std::move(tmpNode), tmpBounds); } for(int i = 0; i < curTrunkSize; i++) { TreeNodeRef& subNode = curTrunk.subNodes[i]; if(subNode.isTrunkNode()) { shuffleTreeRecursive(subNode.asTrunk(), subNode.getTrunkSize()); } } } static void shuffleTree(BoundsTreePrototype& tree) { std::pair baseTrunk = tree.getBaseTrunk(); shuffleTreeRecursive(baseTrunk.first, baseTrunk.second); } template static void shuffleTree(BoundsTree& tree) { shuffleTree(tree.getPrototype()); } static BoundsTemplate generateBoundsTreeBounds() { float x = generateFloat(-100.0f, 100.0f); float y = generateFloat(-100.0f, 100.0f); float z = generateFloat(-100.0f, 100.0f); float w = generateFloat(0.2f, 50.0f); float h = generateFloat(0.2f, 50.0f); float d = generateFloat(0.2f, 50.0f); return BoundsTemplate(PositionTemplate(x - w, y - h, z - d), PositionTemplate(x + w, y + h, z + d)); } static std::vector generateBoundsTreeItems(int size) { std::vector allItems; for(int i = 0; i < size; i++) { allItems.push_back(BasicBounded{generateBoundsTreeBounds()}); } return allItems; } TEST_CASE(testAddRemoveToBoundsTree) { BoundsTree tree; ASSERT_TRUE(isBoundsTreeValid(tree)); constexpr int itemCount = 1000; std::vector itemsInTree = generateBoundsTreeItems(itemCount); // add all to tree for(int i = 0; i < itemCount; i++) { BasicBounded& cur = itemsInTree[i]; ASSERT_FALSE(tree.contains(&cur)); tree.add(&cur); ASSERT_TRUE(tree.contains(&cur)); } ASSERT_TRUE(isBoundsTreeValid(tree)); // assert all in tree for(int i = 0; i < itemCount; i++) { BasicBounded& cur = itemsInTree[i]; ASSERT_TRUE(tree.contains(&cur)); } ASSERT_TRUE(isBoundsTreeValid(tree)); // remove all from tree for(int i = 0; i < itemCount; i++) { BasicBounded& cur = itemsInTree[i]; ASSERT_TRUE(tree.contains(&cur)); tree.remove(&cur); ASSERT_FALSE(tree.contains(&cur)); } ASSERT_TRUE(isBoundsTreeValid(tree)); // assert all removed from tree for(int i = 0; i < itemCount; i++) { BasicBounded& cur = itemsInTree[i]; ASSERT_FALSE(tree.contains(&cur)); } ASSERT_TRUE(isBoundsTreeValid(tree)); } // check all groups are distinct, but internally the same static bool groupsMatchTree(const std::vector>& groups, const BoundsTree& tree) { for(const std::vector& groupA : groups) { for(const std::vector& groupB : groups) { bool isSameGroup = &groupA == &groupB; for(const BasicBounded* objA : groupA) { for(const BasicBounded* objB : groupB) { if(tree.groupContains(objA, objB) != isSameGroup) { std::cout << (isSameGroup ? "Objects should be in same group but aren't!" : "Objects shouldn't be in same group but are!"); return false; } } } } } // verify that all groups in the tree match a group in the lists bool result = true; tree.forEach([&](const BasicBounded& obj) { size_t groupSize = tree.groupSize(&obj); for(const std::vector& g : groups) { for(BasicBounded* item : g) { if(&obj == item) { if(groupSize != g.size()) { std::cout << "Tree item's group not same size as in groups list!\n"; result = false; } return; } } } std::cout << "Tree item's group not in groups list!\n"; result = false; }); return result; } static void mergeGroups(std::vector>& groups, BoundsTree& tree, int groupA, int groupB) { BasicBounded* objA = groups[groupA][0]; BasicBounded* objB = groups[groupB][0]; tree.mergeGroups(objA, objB); groups[groupA].insert(groups[groupA].end(), groups[groupB].begin(), groups[groupB].end()); groups[groupB] = std::move(groups.back()); groups.pop_back(); } TEST_CASE(testBoundsTreeGroupCreation) { BoundsTree tree; ASSERT_TRUE(isBoundsTreeValid(tree)); constexpr int itemCount = 50; //constexpr int itemCount = 20; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups; ASSERT_TRUE(isBoundsTreeValid(tree)); for(int i = 0; i < itemCount; i++) { BasicBounded* newObj = &allItems[i]; int groupToAddTo = generateInt(groups.size() + 1) - 1; if(groupToAddTo == -1) { std::vector newGroup{newObj}; groups.push_back(std::move(newGroup)); tree.add(newObj); } else { std::vector& grp = groups[groupToAddTo]; grp.push_back(newObj); BasicBounded* grpRep = grp[0]; tree.addToGroup(newObj, grpRep); ASSERT_TRUE(tree.groupContains(newObj, grpRep)); } ASSERT_TRUE(isBoundsTreeValid(tree)); } ASSERT_TRUE(groupsMatchTree(groups, tree)); ASSERT_TRUE(isBoundsTreeValid(tree)); } static std::vector> createGroups(BoundsTree& tree, std::vector& allItems) { assert(tree.isEmpty()); std::vector> groups; // add to groups for(int i = 0; i < allItems.size() / 2; i++) { BasicBounded* newObj = &allItems[i]; int groupToAddTo = generateInt(groups.size() + 1) - 1; if(groupToAddTo == -1) { std::vector newGroup{newObj}; groups.push_back(std::move(newGroup)); tree.add(newObj); } else { std::vector& grp = groups[groupToAddTo]; grp.push_back(newObj); BasicBounded* grpRep = grp[0]; tree.addToGroup(newObj, grpRep); } } // also add some loose objects for(int i = allItems.size() / 2; i < allItems.size(); i++) { BasicBounded* newObj = &allItems[i]; std::vector newGroup{newObj}; groups.push_back(std::move(newGroup)); tree.add(newObj); } shuffleTree(tree); return groups; } TEST_CASE(testBoundsTreeGroupMerging) { BoundsTree tree; ASSERT_TRUE(isBoundsTreeValid(tree)); constexpr int itemCount = 50; //constexpr int itemCount = 20; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups = createGroups(tree, allItems); ASSERT_TRUE(groupsMatchTree(groups, tree)); ASSERT_TRUE(isBoundsTreeValid(tree)); while(groups.size() > 1) { int mergeIdxA = generateInt(groups.size()); int mergeIdxB; do { mergeIdxB = generateInt(groups.size()); } while(mergeIdxA == mergeIdxB); mergeGroups(groups, tree, mergeIdxA, mergeIdxB); ASSERT_TRUE(isBoundsTreeValid(tree)); ASSERT_TRUE(groupsMatchTree(groups, tree)); } } TEST_CASE_SLOW(testBoundsTreeGroupMergingSplitting) { std::vector itemCounts{5, 15, 30, 50}; for(int& itemCount : itemCounts) { //printf("\n"); BoundsTree tree; ASSERT_TRUE(isBoundsTreeValid(tree)); std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups; tree.add(&allItems[0]); ASSERT_TRUE(isBoundsTreeValid(tree)); groups.push_back(std::vector{&allItems[0]}); for(int i = 1; i < itemCount; i++) { BasicBounded* newObj = &allItems[i]; std::vector& grp = groups[0]; grp.push_back(newObj); BasicBounded* grpRep = grp[0]; tree.addToGroup(newObj, grpRep); ASSERT_TRUE(tree.groupContains(newObj, grpRep)); ASSERT_TRUE(isBoundsTreeValid(tree)); } //printf("groups.size() = %d groups[0].size() = %d\n", (int) groups.size(), (int) groups[0].size()); ASSERT_TRUE(groupsMatchTree(groups, tree)); // do a whole bunch of splits and merges for(int iter = 0; iter < 1000; iter++) { int group = generateInt(groups.size()); bool split = generateBool() || groups.size() == 1; if(split) { std::vector& grp = groups[group]; size_t originalSize = grp.size(); std::vector newGroup; // remove a subset of the elements from the group for(size_t i = 0; i < grp.size(); ) { if(generateBool()) { newGroup.push_back(grp[i]); grp[i] = grp.back(); grp.pop_back(); } else { i++; } } int newGroupSize = newGroup.size(); int leftoverSize = grp.size(); //printf("Split group %d into size %d-%d/%d\n", group, newGroupSize, leftoverSize, (int) originalSize); tree.splitGroup(newGroup.begin(), newGroup.end()); if(grp.size() == 0) { grp = std::move(newGroup); } else { if(newGroupSize > 0) { groups.push_back(std::move(newGroup)); } } } else { // merge int group2; do { group2 = generateInt(groups.size()); } while(group == group2); //printf("Merged groups %d, %d\n", group, group2); mergeGroups(groups, tree, group, group2); } ASSERT_TRUE(isBoundsTreeValid(tree)); ASSERT_TRUE(groupsMatchTree(groups, tree)); } } } TEST_CASE(testForEachColission) { BoundsTree tree; constexpr int itemCount = 100; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups = createGroups(tree, allItems); std::set> foundColissions; ASSERT_TRUE(isBoundsTreeValid(tree)); tree.forEachColission([&](BasicBounded* a, BasicBounded* b) { ASSERT_STRICT(a != b); if(b < a) std::swap(a, b); std::pair col(a, b); ASSERT_FALSE(foundColissions.find(col) != foundColissions.end()); // no duplicate colissions foundColissions.insert(col); }); for(size_t i = 0; i < groups.size(); i++) { std::vector& grp = groups[i]; for(size_t aIndex = 0; aIndex < grp.size(); aIndex++) { BasicBounded* a = grp[aIndex]; for(size_t bIndex = aIndex + 1; bIndex < grp.size(); bIndex++) { BasicBounded* b = grp[bIndex]; if(a > b) std::swap(a, b); std::pair col(a, b); ASSERT_FALSE(foundColissions.find(col) != foundColissions.end()); } } } for(size_t i = 0; i < groups.size(); i++) { std::vector& groupA = groups[i]; for(size_t j = i + 1; j < groups.size(); j++) { std::vector& groupB = groups[j]; for(int ai = 0; ai < groupA.size(); ai++) { for(int bi = 0; bi < groupB.size(); bi++) { BasicBounded* a = groupA[ai]; BasicBounded* b = groupB[bi]; if(a > b) std::swap(a, b); std::pair col(a, b); bool wasFound = foundColissions.find(col) != foundColissions.end(); bool shouldBeFound = intersects(a->bounds, b->bounds); ASSERT_STRICT(wasFound == shouldBeFound); } } } } } TEST_CASE(testForEachColissionBetween) { BoundsTree tree1; BoundsTree tree2; constexpr int itemCount = 100; std::vector allItems1 = generateBoundsTreeItems(itemCount); std::vector allItems2 = generateBoundsTreeItems(itemCount); std::vector> groups1 = createGroups(tree1, allItems1); std::vector> groups2 = createGroups(tree2, allItems2); std::set> foundColissions; tree1.forEachColissionWith(tree2, [&](BasicBounded* a, BasicBounded* b) { std::pair col(a, b); ASSERT_FALSE(foundColissions.find(col) != foundColissions.end()); // no duplicate colissions foundColissions.insert(col); }); for(size_t i = 0; i < groups1.size(); i++) { std::vector& groupA = groups1[i]; for(size_t j = i + 1; j < groups2.size(); j++) { std::vector& groupB = groups2[i]; for(BasicBounded* a : groupA) { for(BasicBounded* b : groupB) { std::pair col(a, b); bool wasFound = foundColissions.find(col) != foundColissions.end(); bool shouldBeFound = intersects(a->bounds, b->bounds); ASSERT_STRICT(wasFound == shouldBeFound); } } } } } TEST_CASE(testUpdatePartBounds) { BoundsTree tree; constexpr int itemCount = 100; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups = createGroups(tree, allItems); ASSERT_TRUE(isBoundsTreeValid(tree)); for(int iter = 0; iter < 500; iter++) { BasicBounded& selectedItem = allItems[generateSize_t(allItems.size())]; BoundsTemplate oldBounds = selectedItem.getBounds(); selectedItem.bounds = generateBoundsTreeBounds(); tree.updateObjectBounds(&selectedItem, oldBounds); ASSERT_TRUE(tree.contains(&selectedItem)); ASSERT_TRUE(isBoundsTreeValid(tree)); } } TEST_CASE(testUpdateGroupBounds) { BoundsTree tree; constexpr int itemCount = 10; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups = createGroups(tree, allItems); ASSERT_TRUE(isBoundsTreeValid(tree)); for(int iter = 0; iter < 500; iter++) { std::vector& selectedGroup = groups[generateSize_t(groups.size())]; size_t selectedGroupSize = selectedGroup.size(); BasicBounded& selectedItem = *selectedGroup[generateSize_t(selectedGroupSize)]; BoundsTemplate oldBounds = selectedItem.getBounds(); for(BasicBounded* item : selectedGroup) { item->bounds = generateBoundsTreeBounds(); } tree.updateObjectGroupBounds(&selectedItem, oldBounds); ASSERT_TRUE(tree.contains(&selectedItem)); ASSERT_TRUE(isBoundsTreeValid(tree)); } } TEST_CASE(testImproveStructureValidity) { BoundsTree tree; constexpr int itemCount = 100; std::vector allItems = generateBoundsTreeItems(itemCount); std::vector> groups = createGroups(tree, allItems); ASSERT_TRUE(groupsMatchTree(groups, tree)); ASSERT_TRUE(isBoundsTreeValid(tree)); for(int iter = 0; iter < 10; iter++) { for(BasicBounded& bb : allItems) { bb.bounds = generateBoundsTreeBounds(); } tree.recalculateBounds(); ASSERT_TRUE(groupsMatchTree(groups, tree)); ASSERT_TRUE(isBoundsTreeValid(tree)); for(int i = 0; i < 5; i++) { tree.improveStructure(); ASSERT_TRUE(groupsMatchTree(groups, tree)); ASSERT_TRUE(isBoundsTreeValid(tree)); } } } ================================================ FILE: tests/compare.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IS_SUBCLASS_OF(Child, BaseClass) typename std::enable_if::value>::type* = nullptr #define IS_ARITHMETIC(Type) typename std::enable_if::value>::type* = nullptr template bool tolerantEquals(const Num1& first, const Num2& second, Tol tolerance) { auto diff = first - second; return diff <= tolerance && -diff <= tolerance; } template bool tolerantNotEquals(const Num1& first, const Num2& second, Tol tolerance) { auto diff = first - second; return diff > tolerance && -diff > tolerance; } template bool tolerantLessThan(const Num1& first, const Num2& second, Tol tolerance) { return first < second + tolerance; } template bool tolerantGreaterThan(const Num1& first, const Num2& second, Tol tolerance) { return first + tolerance > second; } template bool tolerantLessOrEqual(const Num1& first, const Num2& second, Tol tolerance) { return first <= second + tolerance; } template bool tolerantGreaterOrEqual(const Num1& first, const Num2& second, Tol tolerance) { return first + tolerance >= second; } template bool tolerantEquals(const P3D::Vector& first, const P3D::Vector& second, Tol tolerance) { for(size_t i = 0; i < Size; i++) { if(!tolerantEquals(first[i], second[i], tolerance)) return false; } return true; } template bool tolerantEquals(const P3D::LargeVector& first, const P3D::LargeVector& second, Tol tolerance) { if(first.n != second.n) throw "Dimensions must match!"; for(size_t i = 0; i < first.n; i++) { if(!tolerantEquals(first[i], second[i], tolerance)) return false; } return true; } template bool tolerantEquals(const P3D::Matrix& first, const P3D::Matrix& second, Tol tolerance) { for(size_t row = 0; row < Height; row++) for(size_t col = 0; col < Width; col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::SymmetricMatrix& first, const P3D::SymmetricMatrix& second, Tol tolerance) { for(size_t row = 0; row < Size; row++) for(size_t col = 0; col < Size; col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::DiagonalMatrix& first, const P3D::DiagonalMatrix& second, Tol tolerance) { for(size_t i = 0; i < Size; i++) if(!tolerantEquals(first[i], second[i], tolerance)) return false; return true; } template bool tolerantEquals(const P3D::LargeMatrix& first, const P3D::LargeMatrix& second, Tol tolerance) { if(first.w != second.w || first.h != second.h) throw "Dimensions must match!"; for(size_t row = 0; row < first.h; row++) for(size_t col = 0; col < first.w; col++) if(!tolerantEquals(first.get(row, col), second.get(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::LargeSymmetricMatrix& first, const P3D::LargeSymmetricMatrix& second, Tol tolerance) { if(first.size != second.size) throw "Dimensions must match!"; for(size_t row = 0; row < first.size; row++) for(size_t col = row; col < first.size; col++) if(!tolerantEquals(first.get(row, col), second.get(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::Position& a, const P3D::Position& b, Tol tolerance) { P3D::Vec3 delta = a - b; return tolerantEquals(delta, P3D::Vec3(0, 0, 0), tolerance); } template bool tolerantEquals(const P3D::Quaternion& a, const P3D::Quaternion& b, Tol tolerance) { return tolerantEquals(a.w, b.w, tolerance) && tolerantEquals(a.i, b.i, tolerance) && tolerantEquals(a.j, b.j, tolerance) && tolerantEquals(a.k, b.k, tolerance); } template bool tolerantEquals(const P3D::MatrixRotationTemplate& a, const P3D::MatrixRotationTemplate& b, Tol tolerance) { return tolerantEquals(a.asRotationMatrix(), b.asRotationMatrix(), tolerance); } template bool tolerantEquals(const P3D::QuaternionRotationTemplate& a, const P3D::QuaternionRotationTemplate& b, Tol tolerance) { P3D::Quaternion aq = a.asRotationQuaternion(); P3D::Quaternion bq = b.asRotationQuaternion(); // Quaternions double cover the plane of possible rotations, -q and q express the same rotation. // Therefore we must make sure we are comparing correctly if(dot(aq, bq) > 0) { // quaternions are aligned return tolerantEquals(aq, bq, tolerance); } else { // quaternions are not aligned return tolerantEquals(aq, -bq, tolerance); } } template bool tolerantEquals(const P3D::CFrame& first, const P3D::CFrame& second, Tol tolerance) { return tolerantEquals(first.position, second.position, tolerance) && tolerantEquals(first.rotation, second.rotation, tolerance); } template bool tolerantEquals(const P3D::GlobalCFrame& first, const P3D::GlobalCFrame& second, Tol tolerance) { return tolerantEquals(first.position, second.position, tolerance) && tolerantEquals(first.rotation, second.rotation, tolerance); } template bool tolerantEquals(const P3D::EigenValues& a, const P3D::EigenValues& b, Tol tolerance) { return tolerantEquals(a[0], b[0], tolerance) && tolerantEquals(a[1], b[1], tolerance) && tolerantEquals(a[2], b[2], tolerance) || tolerantEquals(a[0], b[0], tolerance) && tolerantEquals(a[1], b[2], tolerance) && tolerantEquals(a[2], b[1], tolerance) || tolerantEquals(a[0], b[1], tolerance) && tolerantEquals(a[1], b[0], tolerance) && tolerantEquals(a[2], b[2], tolerance) || tolerantEquals(a[0], b[1], tolerance) && tolerantEquals(a[1], b[2], tolerance) && tolerantEquals(a[2], b[0], tolerance) || tolerantEquals(a[0], b[2], tolerance) && tolerantEquals(a[1], b[0], tolerance) && tolerantEquals(a[2], b[1], tolerance) || tolerantEquals(a[0], b[2], tolerance) && tolerantEquals(a[1], b[1], tolerance) && tolerantEquals(a[2], b[0], tolerance); } template bool tolerantEquals(const P3D::Derivatives& first, const P3D::Derivatives& second, Tol tolerance) { for(std::size_t i = 0; i < DerivationCount; i++) { if(!tolerantEquals(first[i], second[i], tolerance)) return false; } return true; } template bool tolerantEquals(const P3D::TaylorExpansion& first, const P3D::TaylorExpansion& second, Tol tolerance) { return tolerantEquals(first.derivs, second.derivs, tolerance); } template bool tolerantEquals(const P3D::FullTaylorExpansion& first, const P3D::FullTaylorExpansion& second, Tol tolerance) { return tolerantEquals(first.derivs, second.derivs, tolerance); } template bool tolerantEquals(const P3D::TranslationalMotion& first, const P3D::TranslationalMotion& second, Tol tolerance) { return tolerantEquals(first.translation, second.translation, tolerance); } template bool tolerantEquals(const P3D::RotationalMotion& first, const P3D::RotationalMotion& second, Tol tolerance) { return tolerantEquals(first.rotation, second.rotation, tolerance); } template bool tolerantEquals(const P3D::Motion& first, const P3D::Motion& second, Tol tolerance) { return tolerantEquals(first.translation, second.translation, tolerance) && tolerantEquals(first.rotation, second.rotation, tolerance); } template bool tolerantEquals(const P3D::RelativeMotion& first, const P3D::RelativeMotion& second, Tol tolerance) { return tolerantEquals(first.relativeMotion, second.relativeMotion, tolerance) && tolerantEquals(first.locationOfRelativeMotion, second.locationOfRelativeMotion, tolerance); } template bool tolerantEquals(const P3D::BoundingBox& first, const P3D::BoundingBox& second, Tol tolerance) { return tolerantEquals(first.min, second.min, tolerance) && tolerantEquals(first.max, second.max, tolerance); } template bool tolerantEquals(const std::pair& first, const std::pair& second, Tol tolerance) { return tolerantEquals(first.first, second.first, tolerance) && tolerantEquals(first.second, second.second, tolerance); } template bool tolerantEquals(const std::array& first, const std::array& second, Tol tolerance) { for(std::size_t i = 0; i < Size; i++) { if(!tolerantEquals(first[i], second[i], tolerance)) { return false; } } return true; } template bool tolerantEquals(const std::vector& first, const std::vector& second, Tol tolerance) { if(first.size() != second.size()) throw std::logic_error("Incompatible vector sizes!"); for(std::size_t i = 0; i < first.size(); i++) { if(!tolerantEquals(first[i], second[i], tolerance)) { return false; } } return true; } template bool tolerantEquals(const P3D::Matrix& first, const P3D::UnmanagedHorizontalFixedMatrix& second, Tol tolerance) { assert(first.height() == second.height()); for(size_t row = 0; row < Height; row++) for(size_t col = 0; col < Width; col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::UnmanagedHorizontalFixedMatrix& second, const P3D::Matrix& first, Tol tolerance) { return tolerantEquals(first, second, tolerance); } template bool tolerantEquals(const P3D::Matrix& first, const P3D::UnmanagedVerticalFixedMatrix& second, Tol tolerance) { assert(first.width() == second.width()); for(size_t row = 0; row < Height; row++) for(size_t col = 0; col < Width; col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::UnmanagedVerticalFixedMatrix& second, const P3D::Matrix& first, Tol tolerance) { return tolerantEquals(first, second, tolerance); } template bool tolerantEquals(const P3D::UnmanagedVerticalFixedMatrix& first, const P3D::UnmanagedVerticalFixedMatrix& second, Tol tolerance) { assert(first.width() == second.width()); for(size_t row = 0; row < first.height(); row++) for(size_t col = 0; col < first.width(); col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } template bool tolerantEquals(const P3D::UnmanagedHorizontalFixedMatrix& first, const P3D::UnmanagedHorizontalFixedMatrix& second, Tol tolerance) { assert(first.width() == second.width()); for(size_t row = 0; row < first.height(); row++) for(size_t col = 0; col < first.width(); col++) if(!tolerantEquals(first(row, col), second(row, col), tolerance)) return false; return true; } ================================================ FILE: tests/constraintTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "generators.h" #include "estimateMotion.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace P3D; #define ASSERT(cond) ASSERT_TOLERANT(cond, 0.05) #define DELTA_T 0.0001 TEST_CASE(testMotionOfPhysicalSinglePart) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Physical* p1Phys = p1.ensureHasPhysical(); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(-0.3, 1.7, -1.1)); p1Phys->mainPhysical->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Position p1PosBefore = p1.getCenterOfMass(); p1Phys->mainPhysical->update(DELTA_T); Position p1PosMid = p1.getCenterOfMass(); p1Phys->mainPhysical->update(DELTA_T); Position p1PosAfter = p1.getCenterOfMass(); TranslationalMotion estimatedVelAccel1 = estimateMotion(p1PosBefore, p1PosMid, p1PosAfter, DELTA_T); ASSERT(estimatedVelAccel1.getVelocity() == p1calculatedVelBefore); ASSERT(estimatedVelAccel1.getAcceleration() == p1calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalPartsBasic) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(0, 0, 0)); Physical* p1Phys = p1.getPhysical(); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p2calculatedVelBefore = p2.getMotion().getVelocity(); Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Vec3 p2calculatedAccelBefore = p2.getMotion().getAcceleration(); Position p1PosBefore = p1.getCenterOfMass(); Position p2PosBefore = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosMid = p1.getCenterOfMass(); Position p2PosMid = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosAfter = p1.getCenterOfMass(); Position p2PosAfter = p2.getCenterOfMass(); TranslationalMotion estimatedVelAccel1 = estimateMotion(p1PosBefore, p1PosMid, p1PosAfter, DELTA_T); TranslationalMotion estimatedVelAccel2 = estimateMotion(p2PosBefore, p2PosMid, p2PosAfter, DELTA_T); ASSERT(estimatedVelAccel1.getVelocity() == p1calculatedVelBefore); ASSERT(estimatedVelAccel2.getVelocity() == p2calculatedVelBefore); ASSERT(estimatedVelAccel1.getAcceleration() == p1calculatedAccelBefore); ASSERT(estimatedVelAccel2.getAcceleration() == p2calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalPartsRotation) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Motion COMMotion(Vec3(0, 0, 0), Vec3(-0.3, 1.7, -1.1)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p2calculatedVelBefore = p2.getMotion().getVelocity(); logStream << p1.getPhysical()->getMotion() << "\n"; logStream << p1.getMotion() << "\n"; logStream << p2.getMotion() << "\n"; Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Vec3 p2calculatedAccelBefore = p2.getMotion().getAcceleration(); GlobalCFrame p1CFrameBefore = p1.getCFrame(); GlobalCFrame p2CFrameBefore = p2.getCFrame(); p1.getMainPhysical()->update(DELTA_T); GlobalCFrame p1CFrameMid = p1.getCFrame(); GlobalCFrame p2CFrameMid = p2.getCFrame(); p1.getMainPhysical()->update(DELTA_T); GlobalCFrame p1CFrameAfter = p1.getCFrame(); GlobalCFrame p2CFrameAfter = p2.getCFrame(); Motion estimatedMotion1 = estimateMotion(p1CFrameBefore, p1CFrameMid, p1CFrameAfter, DELTA_T); Motion estimatedMotion2 = estimateMotion(p2CFrameBefore, p2CFrameMid, p2CFrameAfter, DELTA_T); ASSERT(estimatedMotion1.getVelocity() == p1calculatedVelBefore); ASSERT(estimatedMotion2.getVelocity() == p2calculatedVelBefore); //ASSERT(estimatedMotion1.getAcceleration() == p1calculatedAccelBefore); //ASSERT(estimatedMotion2.getAcceleration() == p2calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalPartsBasicFixedConstraint) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0, 0, 0)); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(0, 0, 0)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p2calculatedVelBefore = p2.getMotion().getVelocity(); Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Vec3 p2calculatedAccelBefore = p2.getMotion().getAcceleration(); Position p1PosBefore = p1.getCenterOfMass(); Position p2PosBefore = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosMid = p1.getCenterOfMass(); Position p2PosMid = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosAfter = p1.getCenterOfMass(); Position p2PosAfter = p2.getCenterOfMass(); TranslationalMotion estimatedVelAccel1 = estimateMotion(p1PosBefore, p1PosMid, p1PosAfter, DELTA_T); TranslationalMotion estimatedVelAccel2 = estimateMotion(p2PosBefore, p2PosMid, p2PosAfter, DELTA_T); ASSERT(estimatedVelAccel1.getVelocity() == p1calculatedVelBefore); ASSERT(estimatedVelAccel2.getVelocity() == p2calculatedVelBefore); ASSERT(estimatedVelAccel1.getAcceleration() == p1calculatedAccelBefore); ASSERT(estimatedVelAccel2.getAcceleration() == p2calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalPartsRotationFixedConstraint) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0, 0, 0)); Motion COMMotion(Vec3(0, 0, 0), Vec3(-0.3, 1.7, -1.1)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p2calculatedVelBefore = p2.getMotion().getVelocity(); logStream << p1.getPhysical()->getMotion() << "\n"; logStream << p1.getMotion() << "\n"; logStream << p2.getMotion() << "\n"; Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Vec3 p2calculatedAccelBefore = p2.getMotion().getAcceleration(); Position p1PosBefore = p1.getCenterOfMass(); Position p2PosBefore = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosMid = p1.getCenterOfMass(); Position p2PosMid = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosAfter = p1.getCenterOfMass(); Position p2PosAfter = p2.getCenterOfMass(); TranslationalMotion estimatedVelAccel1 = estimateMotion(p1PosBefore, p1PosMid, p1PosAfter, DELTA_T); TranslationalMotion estimatedVelAccel2 = estimateMotion(p2PosBefore, p2PosMid, p2PosAfter, DELTA_T); ASSERT(estimatedVelAccel2.getVelocity() == p2calculatedVelBefore); ASSERT(estimatedVelAccel1.getVelocity() == p1calculatedVelBefore); //ASSERT(estimatedVelAccel2.getAcceleration() == p2calculatedAccelBefore); //ASSERT(estimatedVelAccel1.getAcceleration() == p1calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalParts) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(-0.3, 1.7, -1.1)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; Vec3 p1calculatedVelBefore = p1.getMotion().getVelocity(); Vec3 p2calculatedVelBefore = p2.getMotion().getVelocity(); Vec3 p1calculatedAccelBefore = p1.getMotion().getAcceleration(); Vec3 p2calculatedAccelBefore = p2.getMotion().getAcceleration(); Position p1PosBefore = p1.getCenterOfMass(); Position p2PosBefore = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosMid = p1.getCenterOfMass(); Position p2PosMid = p2.getCenterOfMass(); p1.getMainPhysical()->update(DELTA_T); Position p1PosAfter = p1.getCenterOfMass(); Position p2PosAfter = p2.getCenterOfMass(); TranslationalMotion estimatedVelAccel1 = estimateMotion(p1PosBefore, p1PosMid, p1PosAfter, DELTA_T); TranslationalMotion estimatedVelAccel2 = estimateMotion(p2PosBefore, p2PosMid, p2PosAfter, DELTA_T); ASSERT(estimatedVelAccel1.getVelocity() == p1calculatedVelBefore); ASSERT(estimatedVelAccel2.getVelocity() == p2calculatedVelBefore); //ASSERT(estimatedVelAccel1.getAcceleration() == p1calculatedAccelBefore); //ASSERT(estimatedVelAccel2.getAcceleration() == p2calculatedAccelBefore); } TEST_CASE(testMotionOfPhysicalJointsBasic) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Part p1e(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2e(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1e.attach(&p2e, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0, 0, 0)); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(0, 0, 0)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; p1e.getMainPhysical()->motionOfCenterOfMass = COMMotion; ASSERT(p1.getMotion() == p1e.getMotion()); ASSERT(p2.getMotion() == p2e.getMotion()); } TEST_CASE(testMotionOfPhysicalJointsRotation) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Part p1e(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2e(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1e.attach(&p2e, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0.0, 0.0, 0.0)); Motion COMMotion(Vec3(0, 0, 0), Vec3(-0.3, 1.7, -1.1)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; p1e.getMainPhysical()->motionOfCenterOfMass = COMMotion; logStream << p1.getPhysical()->getMotion() << "\n"; logStream << p1.getMotion() << "\n"; logStream << p2.getMotion() << "\n"; logStream << p1e.getPhysical()->getMotion() << "\n"; logStream << p1e.getMotion() << "\n"; logStream << p2e.getMotion() << "\n"; ASSERT(p1.getMotion() == p1e.getMotion()); ASSERT(p2.getMotion() == p2e.getMotion()); } TEST_CASE(testMotionOfPhysicalJoints) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Part p1e(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2e(sphereShape(1.0), GlobalCFrame(1.0, 0.0, 0.0), {3.0, 1.0, 1.0}); p1e.attach(&p2e, new FixedConstraint(), CFrame(1.0, 0.0, 0.0), CFrame(0, 0, 0)); Motion COMMotion(Vec3(1.0, 0.7, 1.3), Vec3(-0.3, 1.7, -1.1)); p1.getMainPhysical()->motionOfCenterOfMass = COMMotion; p1e.getMainPhysical()->motionOfCenterOfMass = COMMotion; logStream << p1.getPhysical()->getMotion() << "\n"; logStream << p1.getMotion() << "\n"; logStream << p2.getMotion() << "\n"; logStream << p1e.getPhysical()->getMotion() << "\n"; logStream << p1e.getMotion() << "\n"; logStream << p2e.getMotion() << "\n"; ASSERT(p1.getMotion() == p1e.getMotion()); ASSERT(p2.getMotion() == p2e.getMotion()); } TEST_CASE(testFixedConstraintProperties) { Part p1(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2(sphereShape(1.0), GlobalCFrame(), {3.0, 1.0, 1.0}); p1.attach(&p2, CFrame(1.0, 0.0, 0.0)); Part p1e(sphereShape(1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part p2e(sphereShape(1.0), GlobalCFrame(), {3.0, 1.0, 1.0}); p1e.attach(&p2e, new FixedConstraint(), CFrame(0.3, -0.8, 0.0), CFrame(-0.7, -0.8, 0)); MotorizedPhysical* phys1 = p1.getMainPhysical(); MotorizedPhysical* phys1e = p1e.getMainPhysical(); ASSERT(p1.getCFrame() == GlobalCFrame(0.0, 0.0, 0.0)); ASSERT(p2.getCFrame() == GlobalCFrame(1.0, 0.0, 0.0)); ASSERT(p1e.getCFrame() == GlobalCFrame(0.0, 0.0, 0.0)); ASSERT(p2e.getCFrame() == GlobalCFrame(1.0, 0.0, 0.0)); ASSERT(phys1->totalMass == phys1e->totalMass); ASSERT(phys1->getCenterOfMass() == phys1e->getCenterOfMass()); ASSERT(phys1->forceResponse == phys1e->forceResponse); ASSERT(phys1->momentResponse == phys1e->momentResponse); } TEST_CASE(testApplyForceToFixedConstraint) { CFrame attach(Vec3(1.3, 0.7, 0.9), Rotation::fromEulerAngles(0.3, -0.7, 0.9)); Part p1(boxShape(1.0, 2.0, 3.0), GlobalCFrame(), {1.0, 1.0, 1.0}); Part p2(boxShape(1.5, 2.3, 1.2), GlobalCFrame(), {1.0, 1.0, 1.0}); p1.attach(&p2, attach); Part p1e(boxShape(1.0, 2.0, 3.0), GlobalCFrame(), {1.0, 1.0, 1.0}); Part p2e(boxShape(1.5, 2.3, 1.2), GlobalCFrame(), {1.0, 1.0, 1.0}); p1e.attach(&p2e, new FixedConstraint(), attach, CFrame()); MotorizedPhysical* phys1 = p1.getMainPhysical(); MotorizedPhysical* phys1e = p1e.getMainPhysical(); phys1->applyForceAtCenterOfMass(Vec3(2.7, 3.9, -2.3)); phys1e->applyForceAtCenterOfMass(Vec3(2.7, 3.9, -2.3)); phys1->update(DELTA_T); phys1e->update(DELTA_T); ASSERT(phys1->getMotion() == phys1e->getMotion()); ASSERT(p1.getCFrame() == p1e.getCFrame()); ASSERT(p2.getCFrame() == p2e.getCFrame()); ASSERT(phys1->forceResponse == phys1e->forceResponse); ASSERT(phys1->momentResponse == phys1e->momentResponse); phys1->applyImpulseAtCenterOfMass(Vec3(2.7, 3.9, -2.3)); phys1e->applyImpulseAtCenterOfMass(Vec3(2.7, 3.9, -2.3)); phys1->update(DELTA_T); phys1e->update(DELTA_T); ASSERT(phys1->getMotion() == phys1e->getMotion()); ASSERT(p1.getCFrame() == p1e.getCFrame()); ASSERT(p2.getCFrame() == p2e.getCFrame()); ASSERT(phys1->forceResponse == phys1e->forceResponse); ASSERT(phys1->momentResponse == phys1e->momentResponse); } TEST_CASE(testPlainAttachAndFixedConstraintIndistinguishable) { Part firstPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part secondPart(boxShape(0.5, 0.5, 0.5), GlobalCFrame(1.0, 0.0, 0.0), {1.0, 1.0, 1.0}); firstPart.attach(&secondPart, CFrame(1.0, 0.0, 0.0)); Part firstPart2(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part secondPart2(boxShape(0.5, 0.5, 0.5), GlobalCFrame(1.0, 0.0, 0.0), {1.0, 1.0, 1.0}); firstPart2.attach(&secondPart2, new FixedConstraint(), CFrame(0.5, 0.0, 0.0), CFrame(-0.5, 0.0, 0.0)); Vec3 impulse(1.3, 4.7, 0.2); Vec3 impulsePos(2.6, 1.7, 2.8); MotorizedPhysical* main1 = firstPart.getMainPhysical(); MotorizedPhysical* main2 = firstPart2.getMainPhysical(); main1->applyImpulse(impulsePos, impulse); main2->applyImpulse(impulsePos, impulse); main1->update(0.5); main2->update(0.5); ASSERT(main1->getMotionOfCenterOfMass() == main2->getMotionOfCenterOfMass()); ASSERT(main1->getCFrame() == main2->getCFrame()); } TEST_CASE(testInternalMotionOfCenterOfMass) { Part firstPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); Part secondPart(boxShape(0.5, 0.5, 0.5), GlobalCFrame(1.0, 0.0, 0.0), {1.0, 1.0, 1.0}); SinusoidalPistonConstraint* piston = new SinusoidalPistonConstraint(0.3, 1.0, 1.0); piston->currentStepInPeriod = 0.7; firstPart.attach(&secondPart, piston, CFrame(0.0, 0.0, 1.0), CFrame(0.0, 0.0, -1.0)); MotorizedPhysical* phys = firstPart.getMainPhysical(); Vec3 original = phys->totalCenterOfMass; ALLOCA_InternalMotionTree(cache, phys, size); TranslationalMotion motionOfCom = std::get<2>(cache.getInternalMotionOfCenterOfMass()); phys->update(DELTA_T); Vec3 v2 = phys->totalCenterOfMass; phys->update(DELTA_T); Vec3 v3 = phys->totalCenterOfMass; TranslationalMotion estimatedMotion = estimateMotion(original, v2, v3, DELTA_T); ASSERT(motionOfCom == estimatedMotion); } static bool haveSameMotorPhys(const Part& first, const Part& second) { return first.getPhysical() != nullptr && second.getPhysical() != nullptr && first.getMainPhysical() == second.getMainPhysical(); } TEST_CASE(attachPhysicalsNoLayers) { for(int iter = 0; iter < 100; iter++) { std::vector firstPhysParts = generateMotorizedPhysicalParts(); std::vector secondPhysParts = generateMotorizedPhysicalParts(); ASSERT_FALSE(haveSameMotorPhys(firstPhysParts[0], secondPhysParts[0])); generateAttachment(oneOf(firstPhysParts), oneOf(secondPhysParts)); ASSERT_TRUE(haveSameMotorPhys(firstPhysParts[0], secondPhysParts[0])); } } bool areInSameGroup(const Part& a, const Part& b) { return a.layer->tree.groupContains(&a, &b); } static bool pairwiseCorrectlyGrouped(const std::vector& la, const std::vector& lb, bool expectedAreInSameGroup) { for(const Part& a : la) { for(const Part& b : lb) { if(a.layer == b.layer) { if(areInSameGroup(a, b) != expectedAreInSameGroup) { return false; } } } } return true; } TEST_CASE(attachPhysicalsWithLayers) { for(int iter = 0; iter < 100; iter++) { WorldPrototype testWorld(0.005); testWorld.createLayer(true, true); testWorld.createLayer(false, false); std::vector firstPhysParts = generateMotorizedPhysicalParts(); std::vector secondPhysParts = generateMotorizedPhysicalParts(); generateLayerAssignment(firstPhysParts, testWorld); generateLayerAssignment(secondPhysParts, testWorld); ASSERT_FALSE(haveSameMotorPhys(firstPhysParts[0], secondPhysParts[0])); ASSERT_TRUE(pairwiseCorrectlyGrouped(firstPhysParts, firstPhysParts, true)); ASSERT_TRUE(pairwiseCorrectlyGrouped(secondPhysParts, secondPhysParts, true)); ASSERT_TRUE(pairwiseCorrectlyGrouped(firstPhysParts, secondPhysParts, false)); Part& attachedPart1 = oneOf(firstPhysParts); Part& attachedPart2 = oneOf(secondPhysParts); generateAttachment(attachedPart1, attachedPart2); ASSERT_TRUE(haveSameMotorPhys(firstPhysParts[0], secondPhysParts[0])); ASSERT_TRUE(pairwiseCorrectlyGrouped(firstPhysParts, firstPhysParts, true)); ASSERT_TRUE(pairwiseCorrectlyGrouped(secondPhysParts, secondPhysParts, true)); ASSERT_TRUE(pairwiseCorrectlyGrouped(firstPhysParts, secondPhysParts, true)); } } ================================================ FILE: tests/ecsTests.cpp ================================================ #include "testsMain.h" #include #include "../engine/ecs/registry.h" #include "../application/ecs/components.h" namespace P3D { TEST_CASE(idGeneration) { using namespace P3D::Engine; Registry16 registry; auto id1 = registry.create(); auto id2 = registry.create(); registry.destroy(id1); auto id3 = registry.create(); ASSERT_TRUE(id3 == id1); } TEST_CASE(componentGeneration) { using namespace P3D::Engine; Registry16 registry; auto id = registry.create(); struct A : public RC {}; struct B : public RC {}; registry.add(id); registry.add(id); registry.remove(id); ASSERT_FALSE(registry.has(id)); ASSERT_TRUE(registry.has(id)); } TEST_CASE(componentAccess) { using namespace P3D::Engine; Registry16 registry; auto id = registry.create(); struct A : public RC { int a; float b; A(int a, float b) : a(a), b(b) {} }; registry.add(id, 1, 1.2); ASSERT_TRUE(registry.get(id)->a == 1); ASSERT_TOLERANT(registry.get(id)->b == 1.2, 0.000001); } TEST_CASE(viewTest) { struct A : public RC {}; struct B : public RC {}; struct C : public RC {}; using namespace P3D::Engine; Registry16 registry; auto id1 = registry.create(); auto id2 = registry.create(); auto id3 = registry.create(); auto id4 = registry.create(); auto id5 = registry.create(); registry.add(id1); registry.add(id2); registry.add(id3); registry.add(id4); registry.add(id1); registry.add(id3); registry.add(id5); registry.add(id1); registry.add(id2); registry.add(id3); registry.add(id4); registry.add(id5); std::size_t count = 0; for(auto _ : registry.view()) { count++; } ASSERT_TRUE(count == 2); } TEST_CASE(entityParent) { using namespace P3D::Engine; Registry16 registry; auto id1 = registry.create(); auto parent = registry.create(); ASSERT_TRUE(registry.getParent(id1) == registry.null_entity); id1 = registry.setParent(id1, parent); ASSERT_FALSE(registry.getParent(id1) == registry.null_entity); } TEST_CASE(getChildren) { using namespace P3D::Engine; Registry16 registry; auto id1 = registry.create(); auto id2 = registry.create(); auto parent = registry.create(); id1 = registry.setParent(id1, parent); id2 = registry.setParent(id2, parent); std::size_t count = 0; for(auto _ : registry.getChildren(parent)) { count++; } ASSERT_TRUE(count == 2); } TEST_CASE(getFromView) { using namespace P3D::Engine; Registry16 registry; auto id = registry.create(); struct A : public RC { int idx = 0; A(int idx) : idx(idx) {} }; registry.add(id, 1); registry.add(id, 2); registry.add(id, 3); auto view = registry.view(); for(auto entity : view) { intrusive_ptr component = view.get(entity); ASSERT_TRUE(component->idx > 0 && component->idx < 4); } } }; ================================================ FILE: tests/estimateMotion.cpp ================================================ #include "estimateMotion.h" #include #include namespace P3D { Vec3 getVelocityBySimulation(const Motion& m, const Vec3& point, double deltaT) { Rotation rotation = Rotation::fromRotationVector(m.getAngularVelocity() * deltaT); Vec3 delta = m.getVelocity() * deltaT + (rotation * point - point); return delta / deltaT; } Motion getMotionBySimulation(const Motion& m, const Vec3& point, double deltaT) { Vec3 mainVel1 = m.getVelocity(); Vec3 mainVel2 = mainVel1 + m.getAcceleration() * deltaT; Vec3 angularVel2 = m.getAngularVelocity() + m.getAngularAcceleration() * deltaT; Vec3 rotVec1 = m.getAngularVelocity() * deltaT; Vec3 rotVec2 = angularVel2 * deltaT; Rotation rotation1 = Rotation::fromRotationVector(rotVec1); Rotation rotation2 = Rotation::fromRotationVector(rotVec2); Vec3 pos0 = point; Vec3 pos1 = mainVel1 * deltaT + rotation1 * pos0; Vec3 pos2 = mainVel2 * deltaT + rotation2 * pos1; Vec3 delta1 = pos1 - pos0; Vec3 delta2 = pos2 - pos1; Vec3 vel1 = delta1 / deltaT; Vec3 vel2 = delta2 / deltaT; TranslationalMotion tResult(vel1, (vel2 - vel1) / deltaT); RotationalMotion rResult(m.getAngularVelocity(), m.getAngularAcceleration()); return Motion(tResult, rResult); } TranslationalMotion estimateMotion(const Vec3& startPos, const Vec3& midPos, const Vec3& endPos, double stepT) { Vec3 distance1 = midPos - startPos; Vec3 distance2 = endPos - midPos; Vec3 velocity1 = distance1 / stepT; Vec3 velocity2 = distance2 / stepT; Vec3 acceleration = (velocity2 - velocity1) / stepT; return TranslationalMotion(velocity1, acceleration); } TranslationalMotion estimateMotion(const Position& startPos, const Position& midPos, const Position& endPos, double stepT) { Vec3 distance1 = midPos - startPos; Vec3 distance2 = endPos - midPos; Vec3 velocity1 = distance1 / stepT; Vec3 velocity2 = distance2 / stepT; Vec3 acceleration = (velocity2 - velocity1) / stepT; return TranslationalMotion(velocity1, acceleration); } // secondRot = delta * firstRot // delta = secondRot * ~firstRot static Vec3 getRotationVecFor(const Rotation& firstRot, const Rotation& secondRot) { Rotation delta = secondRot * ~firstRot; return delta.asRotationVector(); } Vec3 getRotationVelFor(const Rotation& before, const Rotation& after, double deltaT) { Vec3 rotationVec = getRotationVecFor(before, after); return rotationVec / deltaT; } Motion estimateMotion(const CFrame& step1, const CFrame& step2, const CFrame& step3, double stepT) { Vec3 vel1 = Vec3(step2.getPosition() - step1.getPosition()) / stepT; Vec3 vel2 = Vec3(step3.getPosition() - step2.getPosition()) / stepT; Vec3 rotVel1 = getRotationVelFor(step1.getRotation(), step2.getRotation(), stepT); Vec3 rotVel2 = getRotationVelFor(step2.getRotation(), step3.getRotation(), stepT); Vec3 accel = (vel2 - vel1) / stepT; Vec3 rotAccel = (rotVel2 - rotVel1) / stepT; return Motion(vel1, rotVel1, accel, rotAccel); } Motion estimateMotion(const GlobalCFrame& step1, const GlobalCFrame& step2, const GlobalCFrame& step3, double stepT) { Vec3 vel1 = Vec3(step2.getPosition() - step1.getPosition()) / stepT; Vec3 vel2 = Vec3(step3.getPosition() - step2.getPosition()) / stepT; Vec3 rotVel1 = getRotationVelFor(step1.getRotation(), step2.getRotation(), stepT); Vec3 rotVel2 = getRotationVelFor(step2.getRotation(), step3.getRotation(), stepT); Vec3 accel = (vel2 - vel1) / stepT; Vec3 rotAccel = (rotVel2 - rotVel1) / stepT; return Motion(vel1, rotVel1, accel, rotAccel); } CFrame simulateForTime(const Motion& motion, const CFrame& startingCFrame, double deltaT) { Movement mov = motion.getMovementAfterDeltaT(deltaT); return CFrame(startingCFrame.getPosition() + mov.translation, Rotation::fromRotationVector(mov.rotation) * startingCFrame.getRotation()); } }; ================================================ FILE: tests/estimateMotion.h ================================================ #pragma once #include #include #include #include #include namespace P3D { Vec3 getVelocityBySimulation(const Motion& m, const Vec3& point, double deltaT); Motion getMotionBySimulation(const Motion& m, const Vec3& point, double deltaT); TranslationalMotion estimateMotion(const Vec3& startPos, const Vec3& midPos, const Vec3& endPos, double stepT); TranslationalMotion estimateMotion(const Position& startPos, const Position& midPos, const Position& endPos, double stepT); Vec3 getRotationVelFor(const Rotation& before, const Rotation& after, double deltaT); Motion estimateMotion(const CFrame& step1, const CFrame& step2, const CFrame& step3, double stepT); Motion estimateMotion(const GlobalCFrame& step1, const GlobalCFrame& step2, const GlobalCFrame& step3, double stepT); CFrame simulateForTime(const Motion& motion, const CFrame& startingCFrame, double deltaT); }; ================================================ FILE: tests/estimationTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include #include using namespace P3D; TEST_CASE_SLOW(volumeApproximation) { Polyhedron s = ShapeLibrary::house; BoundingBox b = s.getBounds(); int sampleCount = 100; double dx = (b.max.x - b.min.x) / sampleCount; double dy = (b.max.y - b.min.y) / sampleCount; double dz = (b.max.z - b.min.z) / sampleCount; double sampleVolume = dx * dy * dz; long totalContainedCubes = 0; for(int xi = 0; xi < sampleCount; xi++) { for(int yi = 0; yi < sampleCount; yi++) { for(int zi = 0; zi < sampleCount; zi++) { double x = b.min.x + dx * xi + dx / 2; double y = b.min.y + dy * yi + dy / 2; double z = b.min.z + dz * zi + dz / 2; if(s.containsPoint(Vec3(x, y, z))) totalContainedCubes++; } } } double estimatedVolume = totalContainedCubes * sampleVolume; ASSERT_TOLERANT(estimatedVolume == s.getVolume(), s.getVolume() * 0.001); } TEST_CASE_SLOW(centerOfMassApproximation) { Polyhedron s = ShapeLibrary::house; BoundingBox b = s.getBounds(); int sampleCount = 100; double dx = (b.max.x - b.min.x) / sampleCount; double dy = (b.max.y - b.min.y) / sampleCount; double dz = (b.max.z - b.min.z) / sampleCount; double sampleVolume = dx * dy * dz; Vec3 total = Vec3(0, 0, 0); for(int xi = 0; xi < sampleCount; xi++) { for(int yi = 0; yi < sampleCount; yi++) { for(int zi = 0; zi < sampleCount; zi++) { double x = b.min.x + dx * xi + dx / 2; double y = b.min.y + dy * yi + dy / 2; double z = b.min.z + dz * zi + dz / 2; if(s.containsPoint(Vec3(x, y, z))) { total += Vec3(x, y, z) * sampleVolume; } } } } Vec3 estimatedCOM = total / s.getVolume(); ASSERT_TOLERANT(estimatedCOM == s.getCenterOfMass(), (dx + dy + dz) / 3 * 0.01); } TEST_CASE_SLOW(inertiaApproximation) { Polyhedron s = ShapeLibrary::house; BoundingBox b = s.getBounds(); int sampleCount = 100; double dx = (b.max.x - b.min.x) / sampleCount; double dy = (b.max.y - b.min.y) / sampleCount; double dz = (b.max.z - b.min.z) / sampleCount; double sampleVolume = dx * dy * dz; SymmetricMat3 totalInertia = SymmetricMat3::ZEROS(); for(int xi = 0; xi < sampleCount; xi++) { for(int yi = 0; yi < sampleCount; yi++) { for(int zi = 0; zi < sampleCount; zi++) { double x = b.min.x + dx * xi + dx / 2; double y = b.min.y + dy * yi + dy / 2; double z = b.min.z + dz * zi + dz / 2; if(s.containsPoint(Vec3(x, y, z))) { SymmetricMat3 inertiaOfPoint{ y * y + z * z, -x * y, x * x + z * z, -x * z, -y * z, x * x + y * y }; totalInertia += inertiaOfPoint * sampleVolume; } } } } double v = s.getVolume(); ASSERT_TOLERANT(totalInertia == s.getInertia(CFrame()), v * v * 0.001); } ================================================ FILE: tests/generators.cpp ================================================ #include "generators.h" #include #include #include #include #include namespace P3D { static std::default_random_engine generator; int generateInt(int max) { return std::uniform_int_distribution(0, max - 1)(generator); } size_t generateSize_t(size_t max) { return std::uniform_int_distribution(0, max - 1)(generator); } double generateDouble() { return std::uniform_real_distribution(0.0, 2.0)(generator); } float generateFloat() { return std::uniform_real_distribution(0.0f, 2.0f)(generator); } double generateDouble(double min, double max) { return std::uniform_real_distribution(min, max)(generator); } float generateFloat(float min, float max) { return std::uniform_real_distribution(min, max)(generator); } bool generateBool() { return std::uniform_int_distribution(0, 1)(generator) == 1; } Shape generateShape() { return boxShape(generateDouble(), generateDouble(), generateDouble()); } Polyhedron generateConvexPolyhedron() { constexpr size_t MAX_POINT_COINT = 50; Vec3f vecBuf[MAX_POINT_COINT * 10]{generateVec3f(), generateVec3f(), generateVec3f(), generateVec3f()}; if((vecBuf[1] - vecBuf[0]) % (vecBuf[2] - vecBuf[0]) * vecBuf[3] > 0) { for(int i = 0; i < 4; i++) { vecBuf[i].z = -vecBuf[i].z; } } Triangle triangleBuf[MAX_POINT_COINT * 2 * 10]{{0,1,2}, {0,2,3}, {0,3,1}, {3,2,1}}; TriangleNeighbors neighborBuf[MAX_POINT_COINT * 2 * 10]; int removalBuf[MAX_POINT_COINT * 2 * 10]; EdgePiece edgeBuf[MAX_POINT_COINT * 4 * 10]; ConvexShapeBuilder builder(vecBuf, triangleBuf, 4, 4, neighborBuf, removalBuf, edgeBuf); int numExtraPoints = generateInt(MAX_POINT_COINT - 4); for(int i = 0; i < numExtraPoints; i++) { builder.addPoint(generateVec3f()); } return builder.toPolyhedron(); } static Triangle finishTriangle(int maxIndex, int firstIndex) { int secondIndex, thirdIndex; do { secondIndex = generateInt(maxIndex); } while(secondIndex == firstIndex); do { thirdIndex = generateInt(maxIndex); } while(thirdIndex == firstIndex || thirdIndex == secondIndex); return Triangle{firstIndex, secondIndex, thirdIndex}; } Triangle generateTriangle(int maxIndex) { int firstIndex = generateInt(maxIndex); return finishTriangle(maxIndex, firstIndex); } TriangleMesh generateTriangleMesh() { int numVertices = generateInt(46) + 4; int numTriangles = generateInt(100) + numVertices; EditableMesh mesh(numVertices, numTriangles); for(int i = 0; i < numVertices; i++) { mesh.setVertex(i, generateVec3f()); } for(int i = 0; i < numVertices; i++) { mesh.setTriangle(i, finishTriangle(numVertices, i)); // ensure one triangle per vertex } for(int i = numVertices; i < numTriangles; i++) { mesh.setTriangle(i, generateTriangle(numVertices)); // extra triangles } return TriangleMesh(std::move(mesh)); } PositionTemplate generatePositionf() { return PositionTemplate(generateFloat(), generateFloat(), generateFloat()); } Position generatePosition() { return Position(generateDouble(), generateDouble(), generateDouble()); } BoundsTemplate generateBoundsf() { PositionTemplate a = generatePositionf(); PositionTemplate b = generatePositionf(); return BoundsTemplate(PositionTemplate(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)), PositionTemplate(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z))); } Bounds generateBounds() { Position a = generatePosition(); Position b = generatePosition(); return Bounds(Position(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)), Position(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z))); } DiagonalMat3 generateDiagonalMatrix() { return DiagonalMat3(); } Rotation generateRotation() { return Rotation::fromEulerAngles(generateDouble() * 1.507075, generateDouble() * 1.507075, generateDouble() * 1.507075); } CFrame generateCFrame() { return CFrame(generateVec3(), generateRotation()); } GlobalCFrame generateGlobalCFrame() { return GlobalCFrame(generatePosition(), generateRotation()); } TranslationalMotion generateTranslationalMotion() { return TranslationalMotion(generateTaylor<2>(generateVec3)); } RotationalMotion generateRotationalMotion() { return RotationalMotion(generateTaylor<2>(generateVec3)); } Motion generateMotion() { return Motion(generateTranslationalMotion(), generateRotationalMotion()); } RelativeMotion generateRelativeMotion() { return RelativeMotion(generateMotion(), generateCFrame()); } PartProperties generatePartProperties() { return PartProperties{generateDouble(),generateDouble(),generateDouble()}; } Part generatePart() { return Part(generateShape(), generateGlobalCFrame(), generatePartProperties()); } Part generatePart(Part& attachTo) { return Part(generateShape(), attachTo, generateCFrame(), generatePartProperties()); } Part generatePart(Part& attachTo, HardConstraint* constraint) { return Part(generateShape(), attachTo, constraint, generateCFrame(), generateCFrame(), generatePartProperties()); } HardConstraint* generateHardConstraint() { return new MotorConstraintTemplate(generateDouble()); } void generateAttachment(Part& first, Part& second) { if(generateBool()) { first.attach(&second, generateCFrame()); } else { first.attach(&second, generateHardConstraint(), generateCFrame(), generateCFrame()); } } std::vector generateMotorizedPhysicalParts() { //int size = int(std::sqrt(rand() % 1000)) + 1; int size = rand() % 5 + 1; std::vector parts(size); parts[0] = generatePart(); if(generateBool()) parts[0].ensureHasPhysical(); for(int i = 1; i < size; i++) { parts[i] = generatePart(); generateAttachment(parts[i], parts[rand() % i]); } return parts; } void generateLayerAssignment(std::vector& parts, WorldPrototype& world) { std::vector layerParts(world.layers.size(), nullptr); int selectedSubLayer = rand() % ColissionLayer::NUMBER_OF_SUBLAYERS; for(Part& curPart : parts) { int selectedLayer = rand() % world.layers.size(); WorldLayer& addTo = world.layers[selectedLayer].subLayers[selectedSubLayer]; if(layerParts[selectedLayer] == nullptr) { addTo.tree.add(&curPart); layerParts[selectedLayer] = &curPart; } else { addTo.tree.addToGroup(&curPart, layerParts[selectedLayer]); } curPart.layer = &addTo; } } }; ================================================ FILE: tests/generators.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace P3D { template T generate() { throw "Unsupported generate type!"; } // generates between -2.0 and 2.0 template<> inline double generate() { return rand() * 4.0 / RAND_MAX - 2.0; } // generates between -2.0 and 2.0 template<> inline float generate() { return rand() * 4.0f / RAND_MAX - 2.0f; } template<> inline int8_t generate() { return (rand() % 200) - 100; } template<> inline uint8_t generate() { return uint8_t(rand()) & 0xFF; } template<> inline int16_t generate() { return (rand() % 200) - 100; } template<> inline uint16_t generate() { return rand() % 100; } template<> inline int32_t generate() { return (rand() % 200) - 100; } template<> inline uint32_t generate() { return rand() % 100; } template<> inline int64_t generate() { return (rand() % 200) - 100; } template<> inline uint64_t generate() { return rand() % 100; } int generateInt(int max); size_t generateSize_t(size_t max); double generateDouble(); float generateFloat(); double generateDouble(double min, double max); float generateFloat(float min, float max); bool generateBool(); Shape generateShape(); Polyhedron generateConvexPolyhedron(); TriangleMesh generateTriangleMesh(); template Vector generateVector() { Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = generate(); } return result; } template Matrix generateMatrix() { Matrix result; for(std::size_t i = 0; i < Height; i++) { for(std::size_t j = 0; j < Width; j++) { result(i, j) = generate(); } } return result; } inline Vec3 generateVec3() { return generateVector(); } inline Vec3f generateVec3f() { return generateVector(); } Triangle generateTriangle(int maxIndex); Position generatePosition(); Bounds generateBounds(); BoundsTemplate generateBoundsf(); PositionTemplate generatePositionf(); Rotation generateRotation(); CFrame generateCFrame(); GlobalCFrame generateGlobalCFrame(); template auto generateTaylor(ItemGenerator itemGenerator) -> TaylorExpansion { TaylorExpansion result; for(auto& item : result) { item = itemGenerator(); } return result; } template auto generateFullTaylor(ItemGenerator itemGenerator) -> FullTaylorExpansion { FullTaylorExpansion result; for(auto& item : result) { item = itemGenerator(); } return result; } TranslationalMotion generateTranslationalMotion(); RotationalMotion generateRotationalMotion(); Motion generateMotion(); RelativeMotion generateRelativeMotion(); PartProperties generatePartProperties(); Part generatePart(); Part generatePart(Part& attachTo); Part generatePart(Part& attachTo, HardConstraint* constraint); HardConstraint* generateHardConstraint(); void generateAttachment(Part& first, Part& second); std::vector generateMotorizedPhysicalParts(); void generateLayerAssignment(std::vector& parts, WorldPrototype& world); template auto oneOf(const Collection& collection) -> decltype(collection[0]) { return collection[generateSize_t(collection.size())]; } template auto oneOf(Collection& collection) -> decltype(collection[0]) { return collection[generateSize_t(collection.size())]; } template DiagonalMatrix generateDiagonalMatrix() { T buf[size]; for(size_t i = 0; i < size; i++) { buf[i] = generate(); } return DiagonalMatrix(buf); } }; ================================================ FILE: tests/geometryTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include #include #include #include #include #include #include #include #include "testValues.h" #include "generators.h" #include #include using namespace P3D; #define ASSERT(condition) ASSERT_TOLERANT(condition, 0.00001) template bool isDiagonalTolerant(const Matrix& m, Tol tolerance) { for(size_t row = 0; row < Size; row++) { for(size_t col = 0; col < Size; col++) { if(row == col) continue; if(!tolerantEquals(m[row][col], 0, tolerance)) { return false; } } } return true; } template bool isDiagonalTolerant(const SymmetricMatrix& m, Tol tolerance) { for(size_t row = 0; row < Size - 1; row++) { for(size_t col = row + 1; col < Size; col++) { if(!tolerantEquals(m[row][col], 0, tolerance)) { return false; } } } return true; } template bool isSymmetricTolerant(const Matrix& m, Tol tolerance) { for(size_t row = 0; row < Size - 1; row++) { for(size_t col = row + 1; col < Size; col++) { if(!tolerantEquals(m[row][col], m[col][row], tolerance)) { return false; } } } return true; } #define ASSERT_DIAGONAL_TOLERANT(matrix, tolerance) if(!isDiagonalTolerant(matrix, tolerance)) throw AssertionError(__LINE__, errMsg(matrix)) #define ASSERT_DIAGONAL(matrix) ASSERT_DIAGONAL_TOLERANT(matrix, 0.00000001) TEST_CASE(shapeVolume) { Polyhedron boxShape = ShapeLibrary::createBox(2, 2, 2); CFramef transform(Vec3f(0.3f, 0.7f, -3.5f), Rotationf::fromEulerAngles(0.7f, 0.2f, 0.3f)); Polyhedron transformedShape = boxShape.localToGlobal(transform); ASSERT(boxShape.getVolume() == transformedShape.getVolume()); ASSERT(boxShape.getVolume() == 8.0); } TEST_CASE(shapeCenterOfMass) { Polyhedron boxShape = ShapeLibrary::createBox(2.0, 2.0, 2.0); CFramef transform(Vec3f(0.3f, 0.7f, -3.5f), Rotationf::fromEulerAngles(0.7f, 0.2f, 0.3f)); Polyhedron transformedShape = boxShape.localToGlobal(transform); ASSERT(transform.localToGlobal(boxShape.getCenterOfMass()) == transformedShape.getCenterOfMass()); } /*TEST_CASE(shapeInertiaMatrix) { Polyhedron boxShape = ShapeLibrary::createBox(2.0, 2.0, 2.0); CFramef transform(Vec3f(0,0,0), rotationMatrixfromEulerAngles(0.7f, 0.2f, 0.3f)); Polyhedron transformedShape = boxShape.localToGlobal(transform); logf("Inertia of boxShape: %s", str(boxShape.getInertiaAroundCenterOfMass()).c_str()); logf("Inertia of transformed boxShape: %s", str(transformedShape.getInertiaAroundCenterOfMass()).c_str()); Polyhedron h = ShapeLibrary::house; Polyhedron newHouse = ShapeLibrary::house.translated(-Vec3f(ShapeLibrary::house.getCenterOfMass())); Polyhedron rotatedHouse = newHouse.rotated(rotationMatrixfromEulerAngles(0.0, 0.3, 0.0)); logf("Inertia of House: %s", str(newHouse.getInertiaAroundCenterOfMass()).c_str()); logf("Inertia of Rotated House: %s", str(rotatedHouse.getInertiaAroundCenterOfMass()).c_str()); }*/ TEST_CASE(shapeInertiaRotationInvariance) { Polyhedron testShape = ShapeLibrary::house.translated(-Vec3f(ShapeLibrary::house.getCenterOfMass())); Vec3 testMoment = Vec3(0.7, -3.2, 4.8); Vec3 momentResult = ~testShape.getInertiaAroundCenterOfMass() * testMoment; logStream << "Center Of Mass: " << testShape.getCenterOfMass() << '\n'; logStream << "reference inertia: " << testShape.getInertiaAroundCenterOfMass() << "\n"; logStream << "Inertial eigenValues: " << getEigenDecomposition(testShape.getInertiaAroundCenterOfMass()).eigenValues.asDiagonalMatrix() << "\n"; for(const Rotation& testRotation : rotations) { Polyhedron rotatedShape = testShape.rotated(static_cast(testRotation)); Vec3 rotatedTestMoment = testRotation * testMoment; SymmetricMat3 inertia = rotatedShape.getInertiaAroundCenterOfMass(); Vec3 rotatedMomentResult = ~inertia * rotatedTestMoment; logStream << "testRotation: " << testRotation << "\ninertia: " << inertia << "\n"; logStream << "Inertial eigenValues: " << getEigenDecomposition(inertia).eigenValues.asDiagonalMatrix() << "\n"; ASSERT(rotatedMomentResult == testRotation * momentResult); } } TEST_CASE(shapeInertiaEigenValueInvariance) { Polyhedron testShape = ShapeLibrary::house.translated(-Vec3f(ShapeLibrary::house.getCenterOfMass())); EigenValues initialEigenValues = getEigenDecomposition(testShape.getInertiaAroundCenterOfMass()).eigenValues; for(Rotation testRotation : rotations) { logStream << testRotation << '\n'; Polyhedron rotatedShape = testShape.rotated(static_cast(testRotation)); EigenValues newEigenValues = getEigenDecomposition(rotatedShape.getInertiaAroundCenterOfMass()).eigenValues; ASSERT(initialEigenValues == newEigenValues); } } TEST_CASE(testRayIntersection) { ASSERT_TRUE(rayTriangleIntersection(Vec3(-1.0, 0.3, 0.3), Vec3(1.0, 0.0, 0.0), Vec3(), Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0)).rayIntersectsTriangle()); ASSERT_FALSE(rayTriangleIntersection(Vec3(-1.0, -0.3, 0.3), Vec3(1.0, 0.0, 0.0), Vec3(), Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0)).rayIntersectsTriangle()); ASSERT_FALSE(rayTriangleIntersection(Vec3(-1.0, 0.3, -0.3), Vec3(1.0, 0.0, 0.0), Vec3(), Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0)).rayIntersectsTriangle()); ASSERT_FALSE(rayTriangleIntersection(Vec3(-1.0, -0.3, -0.3), Vec3(1.0, 0.0, 0.0), Vec3(), Vec3(0.0, 0.0, 1.0), Vec3(0.0, 1.0, 0.0)).rayIntersectsTriangle()); } TEST_CASE(testGetFurthestPointInDirection) { for(Vec3f vertex : ShapeLibrary::icosahedron.iterVertices()) { ASSERT(ShapeLibrary::icosahedron.furthestInDirection(vertex) == vertex); } } TEST_CASE(testCornerShape) { for(int i = 0; i < 10000; i++) { Vec3 direction = generateVec3(); Vec3 point = generateVec3(); Vec3 origin = generateVec3(); Rotation rotation = generateRotation(); DiagonalMat3 scale = generateDiagonalMatrix(); ASSERT(CornerClass::instance.asPolyhedron().furthestInDirectionFallback(direction) == CornerClass::instance.furthestInDirection(direction)); ASSERT(CornerClass::instance.asPolyhedron().getScaledMaxRadiusSq(scale) == CornerClass::instance.getScaledMaxRadiusSq(scale)); ASSERT(CornerClass::instance.asPolyhedron().containsPoint(point) == CornerClass::instance.containsPoint(point)); ASSERT(CornerClass::instance.asPolyhedron().getIntersectionDistance(origin, direction) == CornerClass::instance.getIntersectionDistance(origin, direction)); ASSERT(CornerClass::instance.asPolyhedron().getBounds(Mat3f(rotation.asRotationMatrix() * scale)) == CornerClass::instance.getBounds(rotation, scale)); } } TEST_CASE(testWedgeShape) { for(int i = 0; i < 10000; i++) { Vec3 direction = generateVec3(); Vec3 point = generateVec3(); Vec3 origin = generateVec3(); Rotation rotation = generateRotation(); DiagonalMat3 scale = generateDiagonalMatrix(); ASSERT(WedgeClass::instance.asPolyhedron().furthestInDirectionFallback(direction) == WedgeClass::instance.furthestInDirection(direction)); ASSERT(WedgeClass::instance.asPolyhedron().getBounds(Mat3f(rotation.asRotationMatrix() * scale)) == WedgeClass::instance.getBounds(rotation, scale)); ASSERT(WedgeClass::instance.asPolyhedron().getScaledMaxRadiusSq(scale) == WedgeClass::instance.getScaledMaxRadiusSq(scale)); ASSERT(WedgeClass::instance.asPolyhedron().containsPoint(point) == WedgeClass::instance.containsPoint(point)); ASSERT(WedgeClass::instance.asPolyhedron().getIntersectionDistance(origin, direction) == WedgeClass::instance.getIntersectionDistance(origin, direction)); } } TEST_CASE(testTriangleMeshOptimizedGetBounds) { for(int iter = 0; iter < 1000; iter++) { TriangleMesh mesh = generateTriangleMesh(); logStream << "NewPoly: " << mesh.vertexCount << " vertices, " << mesh.triangleCount << " triangles\n"; for(int i = 0; i < mesh.vertexCount; i++) { logStream << mesh.getVertex(i) << "\n"; } BoundingBox reference = mesh.getBoundsFallback(); if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { ASSERT(reference == mesh.getBoundsSSE()); } if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { ASSERT(reference == mesh.getBoundsAVX()); } } } TEST_CASE(testTriangleMeshOptimizedGetBoundsRotated) { for(int iter = 0; iter < 1000; iter++) { TriangleMesh mesh = generateTriangleMesh(); logStream << "NewPoly: " << mesh.vertexCount << " vertices, " << mesh.triangleCount << " triangles\n"; for(int i = 0; i < mesh.vertexCount; i++) { logStream << mesh.getVertex(i) << "\n"; } Mat3f rot = generateMatrix(); BoundingBox reference = mesh.getBoundsFallback(rot); if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { ASSERT(reference == mesh.getBoundsSSE(rot)); } if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { ASSERT(reference == mesh.getBoundsAVX(rot)); } } } TEST_CASE(testTriangleMeshOptimizedFurthestIndexInDirection) { for(int iter = 0; iter < 1000; iter++) { TriangleMesh mesh = generateTriangleMesh(); logStream << "NewPoly: " << mesh.vertexCount << " vertices, " << mesh.triangleCount << " triangles\n"; for(int i = 0; i < mesh.vertexCount; i++) { logStream << mesh.getVertex(i) << "\n"; } Vec3 dir = generateVec3(); int reference = mesh.furthestIndexInDirectionFallback(dir); logStream << "reference: " << reference << "\n"; if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { int sseVertex = mesh.furthestIndexInDirectionSSE(dir); logStream << "sseVertex: " << sseVertex << "\n"; ASSERT(mesh.getVertex(reference) * dir == mesh.getVertex(sseVertex) * dir); } if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2 | CPUIDCheck::SSE4_1)) { int sse4Vertex = mesh.furthestIndexInDirectionSSE4(dir); logStream << "sse4Vertex: " << sse4Vertex << "\n"; ASSERT(mesh.getVertex(reference) * dir == mesh.getVertex(sse4Vertex) * dir); } if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { int avxVertex = mesh.furthestIndexInDirectionAVX(dir); logStream << "avxVertex: " << avxVertex << "\n"; ASSERT(mesh.getVertex(reference) * dir == mesh.getVertex(avxVertex) * dir); } } } TEST_CASE(testTriangleMeshOptimizedFurthestInDirection) { for(int iter = 0; iter < 1000; iter++) { TriangleMesh mesh = generateTriangleMesh(); logStream << "NewPoly: " << mesh.vertexCount << " vertices, " << mesh.triangleCount << " triangles\n"; for(int i = 0; i < mesh.vertexCount; i++) { logStream << mesh.getVertex(i) << "\n"; } Vec3 dir = generateVec3(); Vec3 reference = mesh.furthestInDirectionFallback(dir); logStream << "reference: " << reference << "\n"; if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2)) { Vec3f sseVertex = mesh.furthestInDirectionSSE(dir); logStream << "sseVertex: " << sseVertex << "\n"; ASSERT(reference * dir == sseVertex * dir); // dot with dir as we don't really care for the exact vertex in a tie } if(CPUIDCheck::hasTechnology(CPUIDCheck::SSE | CPUIDCheck::SSE2 | CPUIDCheck::SSE4_1)) { Vec3f sse4Vertex = mesh.furthestInDirectionSSE4(dir); logStream << "sse4Vertex: " << sse4Vertex << "\n"; ASSERT(reference * dir == sse4Vertex * dir); // dot with dir as we don't really care for the exact vertex in a tie } if(CPUIDCheck::hasTechnology(CPUIDCheck::AVX | CPUIDCheck::AVX2 | CPUIDCheck::FMA)) { Vec3f avxVertex = mesh.furthestInDirectionAVX(dir); logStream << "avxVertex: " << avxVertex << "\n"; ASSERT(reference * dir == avxVertex * dir); // dot with dir as we don't really care for the exact vertex in a tie } } } ================================================ FILE: tests/guiTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include #include #include #include #include #include using namespace P3D; TEST_CASE(transformationTest) { Mat4f viewMatrix = Mat4f::IDENTITY(); Mat4f projectionMatrix = perspective(1, 2, 0.1f, 100); Mat4f inverseViewMatrix = ~viewMatrix; Mat4f inverseProjectionMatrix = ~projectionMatrix; Vec4f point = Vec4f(1.0, 2.0, 3.0, 1.0); Vec4f transformedPoint = projectionMatrix * viewMatrix * point; Vec4f transformedBack = inverseViewMatrix * inverseProjectionMatrix * transformedPoint; ASSERT_TOLERANT(point == transformedBack, 0.000001); } ================================================ FILE: tests/indexedShapeTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include #include #include #include #include using namespace P3D; TEST_CASE(testIndexedShape) { Vec3f verts[]{Vec3f(0.0, 0.0, 0.0), Vec3f(1.0, 0.0, 0.0), Vec3f(0.0, 0.0, 1.0), Vec3f(0.0, 1.0, 0.0)}; Triangle triangles[]{{0,1,2},{0,3,1},{0,2,3},{1,3,2}}; TriangleNeighbors neighBuf[4]; fillNeighborBuf(triangles, 4, neighBuf); IndexedShape s(verts, triangles, 4, 4, neighBuf); ASSERT_TRUE(isValid(s)); } TEST_CASE(buildShape) { Vec3f verts[10]{Vec3f(0.0, 0.0, 0.0), Vec3f(1.0, 0.0, 0.0), Vec3f(0.0, 0.0, 1.0), Vec3f(0.0, 1.0, 0.0)}; Triangle triangles[20]{{0,1,2},{0,3,1},{0,2,3},{1,3,2}}; TriangleNeighbors neighBuf[20]; ShapeBuilder b(verts, triangles, 4, 4, neighBuf); b.addPoint(Vec3f(1, 1, 1), 3); IndexedShape result = b.toIndexedShape(); ASSERT_TRUE(isValid(result)); } TEST_CASE(buildConvexShape) { int builderRemovalBuffer[1000]; EdgePiece builderAddingBuffer[1000]; Vec3f verts[10]{Vec3f(0.0, 0.0, 0.0), Vec3f(1.0, 0.0, 0.0), Vec3f(0.0, 0.0, 1.0), Vec3f(0.0, 1.0, 0.0)}; Triangle triangles[20]{{0,1,2},{0,3,1},{0,2,3},{1,3,2}}; TriangleNeighbors neighBuf[20]; Polyhedron simpleTetrahedron(verts, triangles, 4, 4); ASSERT_TRUE(isValid(simpleTetrahedron)); ConvexShapeBuilder builder(verts, triangles, 4, 4, neighBuf, builderRemovalBuffer, builderAddingBuffer); builder.addPoint(Vec3f(-1, 10, -1), 3); ASSERT_TRUE(isValid(builder.toIndexedShape())); Vec3f verts2[10]{Vec3f(0.0, 0.0, 0.0), Vec3f(1.0, 0.0, 0.0), Vec3f(0.0, 0.0, 1.0), Vec3f(0.0, 1.0, 0.0)}; Triangle triangles2[20]{{0,1,2},{0,3,1},{0,2,3},{1,3,2}}; TriangleNeighbors neighBuf2[20]; Polyhedron simpleTetrahedron2(verts2, triangles2, 4, 4); ASSERT_TRUE(isValid(simpleTetrahedron2)); ConvexShapeBuilder builder2(verts2, triangles2, 4, 4, neighBuf2, builderRemovalBuffer, builderAddingBuffer); builder2.addPoint(Vec3f(0.4f, 0.4f, 0.4f), 3); ASSERT_TRUE(isValid(builder2.toIndexedShape())); builder2.addPoint(Vec3f(-0.5f, 50.0f, -0.5f), 3); ASSERT_TRUE(isValid(builder2.toIndexedShape())); Vec3f newIcosaVerts[30]; Triangle newIcosaTriangles[40]; TriangleNeighbors icosaNeighBuf[40]; ConvexShapeBuilder icosaBuilder(ShapeLibrary::icosahedron, newIcosaVerts, newIcosaTriangles, icosaNeighBuf, builderRemovalBuffer, builderAddingBuffer); ASSERT_TRUE(isValid(icosaBuilder.toIndexedShape())); icosaBuilder.addPoint(Vec3f(0, 1.1f, 0)); icosaBuilder.addPoint(Vec3f(0, -1.1f, 0)); icosaBuilder.addPoint(Vec3f(1.1f, 0, 0)); icosaBuilder.addPoint(Vec3f(-1.1f, 0, 0)); icosaBuilder.addPoint(Vec3f(0, 0, 1.1f)); icosaBuilder.addPoint(Vec3f(0, 0, -1.1f)); ASSERT_TRUE(isValid(icosaBuilder.toIndexedShape())); } ================================================ FILE: tests/inertiaTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include #include #include "simulation.h" #include "randomValues.h" using namespace P3D; #define REMAINS_CONSTANT(v) REMAINS_CONSTANT_TOLERANT(v, 0.0001) #define ASSERT(v) ASSERT_TOLERANT(v, 0.0001) #define DELTA_T 0.0001 TEST_CASE(movedInertialMatrixForBox) { Polyhedron originalShape = ShapeLibrary::createBox(1.0, 2.0, 3.0); Vec3 translation(3.1, -2.7, 7.9); Polyhedron translatedTriangle = originalShape.translated(translation); SymmetricMat3 triangleInertia = originalShape.getInertia(CFrame()); SymmetricMat3 translatedTriangleInertia = translatedTriangle.getInertia(CFrame()); ASSERT(getTranslatedInertia(triangleInertia, originalShape.getVolume(), translation, originalShape.getCenterOfMass()) == translatedTriangleInertia); } TEST_CASE(movedInertialMatrixForDifficuiltPart) { Polyhedron originalShape = ShapeLibrary::trianglePyramid; Vec3 translation(3.1, -2.7, 7.9); Polyhedron translatedTriangle = originalShape.translated(translation); SymmetricMat3 triangleInertia = originalShape.getInertia(CFrame()); SymmetricMat3 translatedTriangleInertia = translatedTriangle.getInertia(CFrame()); ASSERT(getTranslatedInertia(triangleInertia, originalShape.getVolume(), translation, originalShape.getCenterOfMass()) == translatedTriangleInertia); } TEST_CASE(inertiaTranslationDerivatives) { SymmetricMat3 inert = ShapeLibrary::trianglePyramid.getInertia(CFrame()); double mass = ShapeLibrary::trianglePyramid.getVolume(); Vec3 start(0.8, 0.4, 0.2); TranslationalMotion motion(Vec3(0.3, 0.4, 0.5), Vec3(-0.8, 0.5, -0.3)); std::array points = computeTranslationOverTime(start, motion, DELTA_T); std::array inertias; for(int i = 0; i < 3; i++) { inertias[i] = getTranslatedInertiaAroundCenterOfMass(inert, mass, points[i]); } FullTaylor estimatedTaylor = estimateDerivatives(inertias, DELTA_T); FullTaylor inertialTaylor = getTranslatedInertiaDerivativesAroundCenterOfMass(inert, mass, start, motion); ASSERT(estimatedTaylor == inertialTaylor); } TEST_CASE(inertiaRotationDerivatives) { SymmetricMat3 inert = ShapeLibrary::trianglePyramid.getInertia(CFrame()); Rotation start = Rotation::fromEulerAngles(0.8, 0.4, 0.2); RotationalMotion motion(Vec3(0.3, 0.4, 0.5), Vec3(-0.8, 0.5, -0.3)); std::array rotations = computeRotationOverTime(start, motion, DELTA_T); std::array inertias; for(int i = 0; i < 3; i++) { inertias[i] = getRotatedInertia(inert, rotations[i]); } FullTaylor estimatedTaylor = estimateDerivatives(inertias, DELTA_T); FullTaylor inertialTaylor = getRotatedInertiaTaylor(inert, start, motion); ASSERT(estimatedTaylor == inertialTaylor); } TEST_CASE(inertiaTransformationDerivatives) { Polyhedron centeredTrianglePyramid = ShapeLibrary::trianglePyramid.translated(-ShapeLibrary::trianglePyramid.getCenterOfMass()); SymmetricMat3 inert = centeredTrianglePyramid.getInertia(CFrame()); double mass = centeredTrianglePyramid.getVolume(); RotationalMotion rotation(Vec3(0.3, 0.4, 0.5), Vec3(-0.8, 0.5, -0.3)); TranslationalMotion translation(Vec3(-0.23, 0.25, -0.7), Vec3(-0.2, -0.7, 0.333)); Motion motion(translation, rotation); Vec3 startingTranslation(1.2, -0.7, 2.1); Rotation startingRotation = Rotation::fromEulerAngles(0.5, -0.6, 0.7); CFrame start(startingTranslation, startingRotation); std::array cframes = computeCFrameOverTime(start, motion, DELTA_T); std::array inertias; for(int i = 0; i < 3; i++) { inertias[i] = getTransformedInertiaAroundCenterOfMass(inert, mass, cframes[i]); } FullTaylor estimatedTaylor = estimateDerivatives(inertias, DELTA_T); FullTaylor inertialTaylor = getTransformedInertiaDerivativesAroundCenterOfMass(inert, mass, start, motion); ASSERT(estimatedTaylor == inertialTaylor); } TEST_CASE(inertiaTransformationDerivativesForOffsetCenterOfMass) { Vec3 com = ShapeLibrary::trianglePyramid.getCenterOfMass(); SymmetricMat3 inert = ShapeLibrary::trianglePyramid.getInertia(CFrame()); double mass = ShapeLibrary::trianglePyramid.getVolume(); RotationalMotion rotation(Vec3(0.3, 0.4, 0.5), Vec3(-0.8, 0.5, -0.3)); TranslationalMotion translation(Vec3(-0.23, 0.25, -0.7), Vec3(-0.2, -0.7, 0.333)); Motion motion(translation, rotation); Vec3 startingTranslation(1.2, -0.7, 2.1); Rotation startingRotation = Rotation::fromEulerAngles(0.5, -0.6, 0.7); CFrame start(startingTranslation, startingRotation); std::array cframes = computeCFrameOverTime(start, motion, DELTA_T); std::array inertias; for(int i = 0; i < 3; i++) { inertias[i] = getTransformedInertiaAroundCenterOfMass(inert, mass, com, cframes[i]); } FullTaylor estimatedTaylor = estimateDerivatives(inertias, DELTA_T); FullTaylor inertialTaylor = getTransformedInertiaDerivativesAroundCenterOfMass(inert, mass, com, start, motion); ASSERT(estimatedTaylor == inertialTaylor); } // test getting angular momentum for 2x2x2 box rotating at omega_x rad/s around the x axis // moving at (0, vy, vz), at (0, 0, cz), relative to (0,0,0) // answer computed by manually running through the angular momentum integral // integrate(dz=1..2, dy=-1..1, dx=-1..1){offset % (angVel % offset + velocity)} TEST_CASE(premadeAngularMomentum) { double omega_x = -2.1; double vy = 1.3; double vz = 2.9; double cz = 3.7; // 2x2x2 box double boxInertiaXX = 16.0 / 3.0; double boxVolume = 8.0; double selfBoxAngularMomentum = boxInertiaXX * omega_x; double velocityAngularMomentum = -boxVolume * cz * vy; double totalTrueAngularMomentum = selfBoxAngularMomentum + velocityAngularMomentum; SymmetricMat3 inertia{ boxInertiaXX, 0, boxInertiaXX, 0, 0, boxInertiaXX }; Vec3 angularVel(omega_x, 0, 0); Vec3 vel(0, vy, vz); Vec3 offset(0, 0, cz); Vec3 boxNotRotatingAngMom = getAngularMomentumFromOffsetOnlyVelocity(offset, vel, boxVolume); Vec3 boxNotRotatingAngMom2 = getAngularMomentumFromOffset(offset, vel, Vec3(0, 0, 0), inertia, boxVolume); ASSERT(boxNotRotatingAngMom == Vec3(velocityAngularMomentum, 0, 0)); ASSERT(boxNotRotatingAngMom == boxNotRotatingAngMom2); Vec3 boxAlsoRotatingAngMom = getAngularMomentumFromOffset(offset, vel, angularVel, inertia, boxVolume); ASSERT(boxAlsoRotatingAngMom == Vec3(totalTrueAngularMomentum, 0, 0)); } TEST_CASE(translatedAngularMomentum) { Polyhedron simpleBox = ShapeLibrary::createCube(1.0f); SymmetricMat3 inertia = simpleBox.getInertiaAroundCenterOfMass(); double mass = simpleBox.getVolume(); Vec3 offset(1.3, 0.7, -2.1); Vec3 angularVel(3.2, 1.3, -2.1); SymmetricMat3 offsetInertia = getTranslatedInertiaAroundCenterOfMass(inertia, mass, offset); Vec3 angularMomentumTarget = offsetInertia * angularVel; Vec3 rotationAngularMomentum = inertia * angularVel; Vec3 velocity = angularVel % offset; Vec3 angularMomentumFromVelocity = offset % velocity * mass; Vec3 angularMomentumTest = rotationAngularMomentum + angularMomentumFromVelocity; Vec3 computedAngularMomentumFromVelocity = getAngularMomentumFromOffsetOnlyVelocity(offset, velocity, mass); Vec3 computedAngularMomentumFromVelocityAndAngular = getAngularMomentumFromOffset(offset, velocity, angularVel, inertia, mass); ASSERT(angularMomentumTarget == angularMomentumTest); ASSERT(angularMomentumFromVelocity == computedAngularMomentumFromVelocity); ASSERT(angularMomentumTarget == computedAngularMomentumFromVelocityAndAngular); } ================================================ FILE: tests/jointTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "generators.h" #include "estimateMotion.h" #include #include #include #include #include #include #include #include #include #include #include using namespace P3D; #define ASSERT(cond) ASSERT_TOLERANT(cond, 0.05) #define DELTA_T 0.0001 TEST_CASE(testConstraintMatrixPack) { Matrix paramToMotionA = generateMatrix(); Matrix paramToMotionB = generateMatrix(); Matrix motionToEqA = generateMatrix(); Matrix motionToEqB = generateMatrix(); Matrix errorMat = generateMatrix(); double matrixBuf[6 * 4 * 4]; double errorBuf[6 * NUMBER_OF_ERROR_DERIVATIVES]; ConstraintMatrixPack cmp(matrixBuf, errorBuf, paramToMotionA, paramToMotionB, motionToEqA, motionToEqB, errorMat); ASSERT(cmp.getParameterToMotionMatrixA() == paramToMotionA); ASSERT(cmp.getParameterToMotionMatrixB() == paramToMotionB); ASSERT(cmp.getMotionToEquationMatrixA() == motionToEqA); ASSERT(cmp.getMotionToEquationMatrixB() == motionToEqB); ASSERT(cmp.getErrorMatrix() == errorMat); } TEST_CASE(testBallConstraintMatrices) { Part firstPart(boxShape(1.0, 2.0, 3.0), GlobalCFrame(Position(0.0, 0.0, 0.0)), {0.3, 0.7, 0.9}); Part secondPart(sphereShape(1.5), GlobalCFrame(Position(1.0, 0.0, 0.0)), {0.1, 0.6, 0.6}); Vec3 attach1(0.0, 0.0, 0.0); Vec3 attach2(-1.0, 0.0, 0.0); BallConstraint bc(attach1, attach2); PhysicalConstraint pc(firstPart.ensureHasPhysical(), secondPart.ensureHasPhysical(), &bc); double matrixBuf[6 * 3 * 4 + 100]; double errorBuf[NUMBER_OF_ERROR_DERIVATIVES * 3 + 100]; ConstraintMatrixPack matrices = pc.getMatrices(matrixBuf, errorBuf); Vec3 param = generateVec3(); LargeVector paramBig(3); paramBig.setSubVector(param, 0); Vec6 motionA = matrices.getParameterToMotionMatrixA() * paramBig; firstPart.getPhysical()->applyImpulseToPhysical(attach1, param); ASSERT(firstPart.getMotion().getDerivAsVec6(0) == motionA); Vec6 motionB = -(matrices.getParameterToMotionMatrixB() * paramBig); secondPart.getPhysical()->applyImpulseToPhysical(attach2, -param); ASSERT(secondPart.getMotion().getDerivAsVec6(0) == motionB); } TEST_CASE(testBallConstraintSymmetric) { Part firstPart(generateShape(), generateGlobalCFrame(), {0.3, 0.7, 0.9}); Part secondPart(generateShape(), generateGlobalCFrame(), {0.1, 0.6, 0.6}); firstPart.ensureHasPhysical(); secondPart.ensureHasPhysical(); Vec3 attach1 = generateVec3(); Vec3 attach2 = generateVec3(); BallConstraint bcA(attach1, attach2); PhysicalConstraint pcA(firstPart.getPhysical(), secondPart.getPhysical(), &bcA); BallConstraint bcB(attach2, attach1); PhysicalConstraint pcB(secondPart.getPhysical(), firstPart.getPhysical(), &bcB); double matrixBufA[6 * 3 * 4 + 100]; double errorBufA[NUMBER_OF_ERROR_DERIVATIVES * 3 + 100]; double matrixBufB[6 * 3 * 4 + 100]; double errorBufB[NUMBER_OF_ERROR_DERIVATIVES * 3 + 100]; ConstraintMatrixPack matricesA = pcA.getMatrices(matrixBufA, errorBufA); ConstraintMatrixPack matricesB = pcB.getMatrices(matrixBufB, errorBufB); ASSERT(matricesA.getParameterToMotionMatrixA() == matricesB.getParameterToMotionMatrixB()); ASSERT(matricesA.getParameterToMotionMatrixB() == matricesB.getParameterToMotionMatrixA()); ASSERT(matricesA.getMotionToEquationMatrixA() == matricesB.getMotionToEquationMatrixB()); ASSERT(matricesA.getMotionToEquationMatrixB() == matricesB.getMotionToEquationMatrixA()); auto errB = matricesB.getErrorMatrix(); inMemoryMatrixNegate(errB); ASSERT(matricesA.getErrorMatrix() == errB); } /*TEST_CASE(testBallConstraint) { Part part1(boxShape(2.0, 2.0, 2.0), GlobalCFrame(0.0, 0.0, 0.0), {1.0, 1.0, 1.0}); part1.ensureHasParent(); Part part2(boxShape(2.0, 2.0, 2.0), GlobalCFrame(6.0, 0.0, 0.0), {1.0, 1.0, 1.0}); part2.ensureHasParent(); ConstraintGroup group; group.ballConstraints.push_back(BallConstraint{Vec3(3.0, 0.0, 0.0), part1.parent->mainPhysical, Vec3(-3.0, 0.0, 0.0), part2.parent->mainPhysical}); part1.parent->mainPhysical->applyForceAtCenterOfMass(Vec3(2, 0, 0)); group.apply(); ASSERT(part1.getMotion().getAcceleration() == Vec3(0.125, 0.0, 0.0)); ASSERT(part2.getMotion().getAcceleration() == Vec3(0.125, 0.0, 0.0)); }*/ ================================================ FILE: tests/lexerTests.cpp ================================================ #include "testsMain.h" #include "../graphics/shader/lexer.h" #include "../graphics/shader/parser.h" #include "../util/log.h" using namespace P3D; static const char* code = R"( struct Light { vec3 position; vec3 color; float intensity; Attenuation attenuation; }; #define maxLights 10 uniform Light lights[maxLights]; )"; static const char* names[] = { "Error", "Comma", "Semicolon", "LeftParenthesis", "RightParenthesis", "Equals", "Dot", "InOut", "Layout", "Uniform", "Qualifier", "Struct", "Version", "Preprocessor", "Datatype", "Identifier", "LeftCurl", "RightCurl", "LeftBracket", "RightBracket", "Number", "Operator", "SingleQuote", "DoubleQuote", "String", "Comment", "End" }; TEST_CASE(lexVersionPreprocessor) { using namespace P3D::Graphics; const char* code = R"( #version 123; #version #ifdef 2 #else 4 )"; Lexer lexer(code); auto current = lexer.next(); while(current.type != Lexer::Token::End) { Log::debug("%s[%s]", names[current.type], current.string(code).c_str()); current = lexer.next(); } } TEST_CASE(lexShader) { using namespace P3D::Graphics; Lexer lexer(code); auto current = lexer.next(); while(current.type != Lexer::Token::End) { Log::debug("%s[%s]", names[current.type], current.string(code).c_str()); current = lexer.next(); } } TEST_CASE(parseShader) { using namespace Graphics; Parser::Parse result = Parser::parse(code); } ================================================ FILE: tests/mathTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "testValues.h" #include "randomValues.h" #include "simulation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace P3D; #define ASSERT(condition) ASSERT_TOLERANT(condition, 0.00000001) #define DELTA_T 0.00001 TEST_CASE(subMatrixOperations) { Matrix mat{ 5,7,9, 6,9,2, 3,9,5, 3,8,4, 3,8,1 }; ASSERT((mat.getSubMatrix<3, 2>(1, 0)) == (Matrix{ 6, 9, 3, 9, 3, 8 })); ASSERT((mat.withoutRow(2)) == (Matrix{ 5, 7, 9, 6, 9, 2, 3, 8, 4, 3, 8, 1 })); ASSERT((mat.withoutCol(1)) == (Matrix{ 5, 9, 6, 2, 3, 5, 3, 4, 3, 1 })); } TEST_CASE(determinant) { Matrix m3{ 1, 7, 21, 7, 6, 9, 1, 3, 8 }; logf("m=%s\nm.det()=%f", str(m3).c_str(), det(m3)); ASSERT(det(m3) == 7); Mat4 m4{ 1, 7, 21, 6, 7, 6, 9, 8, 1, 3, 8, 4, 2, 9, 7, 3 }; logf("m=%s\nm.det()=%f", str(m4).c_str(), det(m4)); ASSERT(det(m4) == -983); } TEST_CASE(matrixInverse2) { Mat2 m{4,7,9,3}; logf("m=%s\n~m=%s\nm.inverse()=%s\nm.det()=%f", str(m).c_str(), str(~m).c_str(), str(~m).c_str(), det(m)); ASSERT(det(m) * det(~m) == 1.0); ASSERT(m * ~m == Mat2::IDENTITY()); ASSERT(~m * m == Mat2::IDENTITY()); } TEST_CASE(matrixInverse3) { Mat3 m{1,3,4,7,6,9,5,3,2}; SymmetricMat3 s{1, 4, 7, 6, 9, 8}; DiagonalMat3 d{7, 5, 3}; logf("m=%s\n~m=%s\nm.det()=%f", str(m).c_str(), str(~m).c_str(), det(m)); ASSERT(m * ~m == Mat3::IDENTITY()); ASSERT(~m * m == Mat3::IDENTITY()); ASSERT(s * ~s == SymmetricMat3::IDENTITY()); ASSERT(~s * s == SymmetricMat3::IDENTITY()); ASSERT(d * ~d == DiagonalMat3::IDENTITY()); ASSERT(~d * d == DiagonalMat3::IDENTITY()); } TEST_CASE(matrixInverse4) { Mat4 m{ 1, 3, 4, 7, 7, 9, 3, 5, 4, 9, 1, 2, 6, 7, 6, 9}; logf("m=%s\n~m=%s\nm.inverse()=%s\nm.det()=%f", str(m).c_str(), str(~m).c_str(), str(~m).c_str(), det(m)); ASSERT(det(m) * det(~m) == 1.0); ASSERT(m * ~m == Mat4::IDENTITY()); ASSERT(~m * m == Mat4::IDENTITY()); } TEST_CASE(cframeInverse) { CFrame A(Vec3(1.0, 0.8, 0.9), Rotation::fromEulerAngles(0.3, 0.7, 0.9)); CFrame B(Vec3(8.2, -0.8, 3.4), Rotation::fromEulerAngles(0.4, 0.4, 0.3)); ASSERT(A.localToGlobal(A.globalToLocal(B)) == B); ASSERT(A.globalToLocal(A.localToGlobal(B)) == B); ASSERT(A.globalToLocal(A) == CFrame()); ASSERT(A.localToGlobal(~A) == CFrame()); ASSERT((~A).localToGlobal(A) == CFrame()); ASSERT(A.localToRelative(B) + A.position == A.localToGlobal(B)); } TEST_CASE(matrixAssociativity) { Mat3 A3{1, 2, 3, 4, 5, 6, 7, 8, 9}; Mat3 B3{11, 13, 17, 19, 23, 29, 31, 37, 41}; Mat3 C3{0.1, 0.2, 0.7, 0.9, -0.3, -0.5, 0.9, 0.1, -0.3}; ASSERT((A3 * B3) * C3 == A3 * (B3 * C3)); Vec3 v3(17, -0.7, 9.4); ASSERT((A3 * B3) * v3 == (A3 * (B3 * v3))); Mat4 A4{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; Mat4 B4{11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 57, 61, 67, 71}; Mat4 C4{0.1, 0.2, 0.7, 0.9, -0.3, -0.5, 0.9, 0.1, -0.3, -0.8, 1.6, 0.4, 0.7, 0.3, 0.1, 0.1}; ASSERT((A4 * B4) * C4 == A4 * (B4 * C4)); Vec4 v4(17, -0.7, 9.4, 0.888); ASSERT((A4 * B4) * v4 == (A4 * (B4 * v4))); } TEST_CASE(cframeAssociativity) { CFrame A(Vec3(0.7, 1.02, 0.9), Rotation::fromEulerAngles(0.7, 0.9, 0.3)); CFrame B(Vec3(-0.2, 10.02, 0.3), Rotation::fromEulerAngles(0.2, 0.2, 0.1)); CFrame C(Vec3(-0.2343657, 17.02, -9.3), Rotation::fromEulerAngles(0.9, 0.4, 0.9)); Vec3 v(17, -0.7, 9.4); ASSERT(A.localToGlobal(B).localToGlobal(C) == A.localToGlobal(B.localToGlobal(C))); ASSERT(A.localToGlobal(B).localToGlobal(v) == A.localToGlobal(B).localToGlobal(v)); } TEST_CASE(fromEuler) { ASSERT(rotMatZ(0.5) * rotMatX(0.3) * rotMatY(0.7) == rotationMatrixfromEulerAngles(0.3, 0.7, 0.5)); } TEST_CASE(crossProduct) { Vec3 x(1.0, 0.0, 0.0); Vec3 y(0.0, 1.0, 0.0); Vec3 z(0.0, 0.0, 1.0); Vec3 v(1.3, 9.2, -3.7); Vec3 u(-7.3, 1.8, 0.5); ASSERT(v % u == -(u % v)); ASSERT((2.0 * v) % (3.0 * u) == 6.0 * (v % u)); ASSERT(x % y == z); ASSERT(y % x == -z); ASSERT(y % z == x); ASSERT(z % y == -x); ASSERT(z % x == y); ASSERT(x % z == -y); } TEST_CASE(eigenDecomposition) { for(double x = -1.25; x < 1.5; x += 0.1) { for(double y = -1.35; y < 1.5; y += 0.1) { for(double z = -1.55; z < 1.5; z += 0.1) { Mat3 orthoPart = rotationMatrixfromEulerAngles(0.21, 0.31, 0.41); DiagonalMat3 eigenMat{x,y,z}; SymmetricMat3 testMat = mulSymmetricLeftRightTranspose(SymmetricMat3(eigenMat), orthoPart); EigenSet v = getEigenDecomposition(testMat); EigenValues realValues{x,y,z}; ASSERT(realValues == v.eigenValues); ASSERT(transformBasis(v.eigenValues.asDiagonalMatrix(), v.eigenVectors) == testMat); } } } } TEST_CASE(largeMatrixVectorSolve) { LargeMatrix mat(5, 5); LargeVector vec(5); for(int i = 0; i < 5; i++) { vec[i] = fRand(-1.0, 1.0); } for(int i = 0; i < 5; i++) { for(int j = 0; j < 5; j++) { mat(i, j) = fRand(-1.0, 1.0); } } mat(0, 0) = 0; LargeVector newVector = mat * vec; LargeVector solutionVector = newVector; destructiveSolve(mat, solutionVector); ASSERT(solutionVector == vec); } TEST_CASE(testTaylorExpansion) { FullTaylorExpansion testTaylor{2.0, 5.0, 2.0, 3.0, -0.7}; double x = 0.47; double correctResult = testTaylor.getConstantValue() + testTaylor.getDerivative(0) * x + testTaylor.getDerivative(1) * x * x / 2 + testTaylor.getDerivative(2) * x * x * x / 6 + testTaylor.getDerivative(3) * x * x * x * x / 24; ASSERT(correctResult == testTaylor(x)); } TEST_CASE(testSinTaylorExpansion) { double curAngle = 0.93; double multiplier = 13.97; // Taylor expansion for sin(x * multiplier) at x=curAngle FullTaylorExpansion testTaylor = generateFullTaylorForSinWave(curAngle, multiplier); double constTerm = sin(curAngle * multiplier); double firstDerivative = multiplier * cos(curAngle * multiplier); double secondDerivative = multiplier * multiplier * (-sin(curAngle * multiplier)); double thirdDerivative = multiplier * multiplier * multiplier * (-cos(curAngle * multiplier)); double fourthDerivative = multiplier * multiplier * multiplier * multiplier * sin(curAngle * multiplier); ASSERT(testTaylor.getConstantValue() == constTerm); ASSERT(testTaylor.getDerivative(0) == firstDerivative); ASSERT(testTaylor.getDerivative(1) == secondDerivative); ASSERT(testTaylor.getDerivative(2) == thirdDerivative); ASSERT(testTaylor.getDerivative(3) == fourthDerivative); } TEST_CASE(testMultiplicationDerivatives) { constexpr int COUNT = 4; FullTaylorExpansion mats = createRandomFullTaylorExpansion>(); FullTaylorExpansion vecs = createRandomFullTaylorExpansion>(); FullTaylorExpansion resultToTest = derivsOfMultiplication(mats, vecs); std::array matVals = computeOverTime(mats, DELTA_T); std::array vecVals = computeOverTime(vecs, DELTA_T); std::array targetResults; for(int i = 0; i < COUNT; i++) targetResults[i] = matVals[i] * vecVals[i]; FullTaylorExpansion estimatedRealDerivatives = estimateDerivatives(targetResults, DELTA_T); logStream << "resultToTest: " << resultToTest << "\n"; logStream << "estimatedRealDerivatives: " << estimatedRealDerivatives << "\n"; for(int i = 0; i < COUNT; i++) { double tolerance = 0.0000001 * std::exp(10.0 * i); logStream << "tolerance: " << tolerance << "\n"; ASSERT_TOLERANT(resultToTest.derivs[i] == estimatedRealDerivatives.derivs[i], tolerance); } } TEST_CASE(testCrossProductEquivalent) { Vec3 v(1.3, -0.5, 0.7); Vec3 x(0.6, 0.5, -1.2); ASSERT(v % x == createCrossProductEquivalent(v) * x); } TEST_CASE(testSkewSymmetricValid) { Vec3 v(1.3, -0.5, 0.7); Vec3 x(0.6, 0.5, -1.2); ASSERT((v % (v % x)) == skewSymmetricSquared(v) * x); Mat3 sv = createCrossProductEquivalent(v); ASSERT(sv * sv == Mat3(skewSymmetricSquared(v))); } TEST_CASE(testSkewSymmetricDerivatives) { Vec3 start(1.3, 1.7, -0.5); Vec3 deriv1(-0.2, 0.4, -0.5); Vec3 deriv2(-0.3, 0.2, -0.1); FullTaylor vecInput{start, deriv1, deriv2}; FullTaylor skewSymOut = generateFullTaylorForSkewSymmetricSquared(vecInput); SymmetricMat3 s0 = skewSymmetricSquared(vecInput(0.0)); SymmetricMat3 s1 = skewSymmetricSquared(vecInput(DELTA_T)); SymmetricMat3 s2 = skewSymmetricSquared(vecInput(DELTA_T * 2.0)); ASSERT_TOLERANT(s0 == skewSymOut.getConstantValue(), 0.000000001); ASSERT_TOLERANT((s1 - s0) / DELTA_T == skewSymOut.getDerivative(0), 0.00005); ASSERT_TOLERANT((s2 + s0 - 2.0 * s1) / (DELTA_T * DELTA_T) == skewSymOut.getDerivative(1), 0.00005); } TEST_CASE(testSimulation) { FullTaylorExpansion realDerivs{Vec3(0.4, -0.6, 0.7), Vec3(-0.2, 0.4, -0.8), Vec3(0.5, -0.35, 0.7)}; double deltaT = 0.000001; std::array computedPoints = computeTranslationOverTime(realDerivs.getConstantValue(), realDerivs.getDerivatives(), deltaT); FullTaylorExpansion computedDerivs = estimateDerivatives(computedPoints, deltaT); ASSERT_TOLERANT(realDerivs == computedDerivs, 0.0005); } TEST_CASE(testRotationDerivs) { Mat3 rot0 = rotationMatrixfromEulerAngles(0.3, -0.5, 0.7); for(Vec3 angularVel : vectors) { for(Vec3 angularAccel : vectors) { logStream << "angularVel: " << angularVel << " accel: " << angularAccel << "\n"; TaylorExpansion rotVecTaylor{angularVel, angularAccel}; FullTaylorExpansion rotTaylor = FullTaylorExpansion::fromConstantAndDerivatives(rot0, generateTaylorForRotationMatrix(rotVecTaylor, rot0)); Vec3 rotVec1 = rotVecTaylor(DELTA_T); Vec3 rotVec2 = rotVecTaylor(DELTA_T * 2); Mat3 deltaRot1 = rotationMatrixFromRotationVec(rotVec1); Mat3 deltaRot2 = rotationMatrixFromRotationVec(rotVec2); Mat3 rot1 = deltaRot1 * rot0; Mat3 rot2 = deltaRot2 * rot0; ASSERT_TOLERANT((rot1 - rot0) / DELTA_T == rotTaylor.getDerivative(0), 0.0005 * lengthSquared(angularVel) + lengthSquared(angularAccel)); ASSERT_TOLERANT((rot2 + rot0 - 2.0 * rot1) / (DELTA_T * DELTA_T) == rotTaylor.getDerivative(1), 0.05 * (lengthSquared(angularVel) + lengthSquared(angularAccel))); } } } TEST_CASE(testFromRotationVec) { double deltaT = 0.0001; Vec3 rotations[]{Vec3(1,0,0), Vec3(0,1,0), Vec3(0,0,1), Vec3(-5.0, 3.0, 9.0), Vec3(3.8, -0.5, 0.4)}; for(Vec3 rotVec : rotations) { Vec3 rotationVec = rotVec * deltaT; Vec3 pointToRotate = Vec3(1.0, 0.0, 0.0); Mat3 rot = rotationMatrixFromRotationVec(rotationVec); Vec3 rotateByRotVec = pointToRotate; Vec3 rotateByRotMat = pointToRotate; for(int i = 0; i < 500; i++) { rotateByRotVec = rotateByRotVec + rotationVec % rotateByRotVec; rotateByRotMat = rot * rotateByRotMat; } logStream << "rotVec: " << rotationVec << ", rot: " << rot << '\n'; logStream << "newPointRotVec: " << rotateByRotVec << "\nnewPointRotMat: " << rotateByRotMat << '\n'; ASSERT_TOLERANT(rotateByRotVec == rotateByRotMat, 0.01); } } // Everything Quaternions TEST_CASE(quaternionBasicRotation) { Vec3 v = Vec3(0.0, 1.0, 0.0); Quat4 q = QROT_X_180(double); Quat4 r = q * v * conj(q); ASSERT(r.w == 0.0); ASSERT(r.v == Vec3(0.0, -1.0, 0.0)); } TEST_CASE(quaternionRotationShorthand) { Vec3 v = Vec3(0.7, 1.0, 0.3); Quat4 q = normalize(Quat4(0.8, 0.4, 0.3, -0.4)); ASSERT(Quat4(0.0, mulQuaternionLeftRightConj(q, v)) == q * v * conj(q)); ASSERT(Quat4(0.0, mulQuaternionLeftConjRight(q, v)) == conj(q) * v * q); } TEST_CASE(quaternionFromRotationVecToRotationMatrix) { for(double x = -1.25; x < 1.5; x += 0.1) { for(double y = -1.35; y < 1.5; y += 0.1) { for(double z = -1.55; z < 1.5; z += 0.1) { Vec3 rotVec(x, y, z); ASSERT(rotationMatrixFromQuaternion(rotationQuaternionFromRotationVec(rotVec)) == rotationMatrixFromRotationVec(rotVec)); } } } } TEST_CASE(interchanceQuaternionAndMatrix) { for(double x = -1.25; x < 1.5; x += 0.1) { for(double y = -1.35; y < 1.5; y += 0.1) { for(double z = -1.55; z < 1.5; z += 0.1) { Mat3 rotMat = rotationMatrixfromEulerAngles(x, y, z); Quat4 rotQuat = rotationQuaternionFromRotationMatrix(rotMat); Mat3 resultMat = rotationMatrixFromQuaternion(rotQuat); ASSERT(rotMat == resultMat); } } } } TEST_CASE(testFixCastToInt) { for(int i = 0; i < 10000; i++) { int64_t a = rand() - RAND_MAX / 2; Fix<32> f(a); logStream << a << '\n'; ASSERT_STRICT(static_cast(f) == a); } for(int i = 0; i < 10000; i++) { double d = double(rand()) / RAND_MAX * 100000.0; Fix<32> f(d); logStream << d << '\n'; ASSERT_STRICT(static_cast(f) == static_cast(d)); } } ================================================ FILE: tests/motionTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "randomValues.h" #include #include "estimateMotion.h" #include #include #include #include #include using namespace P3D; #define REMAINS_CONSTANT(v) REMAINS_CONSTANT_TOLERANT(v, 0.005) #define ASSERT(v) ASSERT_TOLERANT(v, 0.005) #define DELTA_T 0.0001 TEST_CASE(testBasicMotion) { Vec3 vel(1.3, 0.7, 0.9); Vec3 angularVel(1.7, 0.9, 1.2); Vec3 point(1.2, -0.5, 0.7); Motion m(vel, angularVel); ASSERT_TOLERANT(m.getVelocityOfPoint(point) == getVelocityBySimulation(m, point, DELTA_T), 0.05); } TEST_CASE(testAdvancedMotion) { Vec3 vel(1.3, 0.7, 0.9); Vec3 angularVel(1.7, 0.9, 1.2); Vec3 accel(7.1, 0.9, -3.8); Vec3 angularAccel(1.8, 0.67, -4.1); Vec3 point(1.2, -0.5, 0.7); Motion m(vel, angularVel, accel, angularAccel); ASSERT_TOLERANT(m.getMotionOfPoint(point) == getMotionBySimulation(m, point, DELTA_T), 0.05); } TEST_CASE(testMotionOfMotorConstraintCorrect) { HardPhysicalConnection connection(std::unique_ptr(new ConstantSpeedMotorConstraint(createRandomDouble())), createRandomCFrame(), createRandomCFrame()); connection.update(1.23456789); RelativeMotion calculatedMotion = connection.getRelativeMotion(); CFrame cf1 = connection.getRelativeCFrameToParent(); connection.update(DELTA_T); CFrame cf2 = connection.getRelativeCFrameToParent(); connection.update(DELTA_T); CFrame cf3 = connection.getRelativeCFrameToParent(); ASSERT(calculatedMotion.relativeMotion == estimateMotion(cf1, cf2, cf3, DELTA_T)); } TEST_CASE(testMotionOfPistonConstraintCorrect) { HardPhysicalConnection connection(std::unique_ptr(new SinusoidalPistonConstraint(1.0, 5.0, 1.0)), createRandomCFrame(), createRandomCFrame()); connection.update(1.23456789); RelativeMotion calculatedMotion = connection.getRelativeMotion(); CFrame cf1 = connection.getRelativeCFrameToParent(); connection.update(DELTA_T); CFrame cf2 = connection.getRelativeCFrameToParent(); connection.update(DELTA_T); CFrame cf3 = connection.getRelativeCFrameToParent(); ASSERT(calculatedMotion.relativeMotion == estimateMotion(cf1, cf2, cf3, DELTA_T)); } TEST_CASE(testPistonConstraint) { SinusoidalPistonConstraint constraint(1.0, 2.5, 0.7); constraint.update(1.23456789); CFrame cf1 = constraint.getRelativeCFrame(); RelativeMotion relMotion = constraint.getRelativeMotion(); constraint.update(DELTA_T); CFrame cf2 = constraint.getRelativeCFrame(); ASSERT((cf2.getPosition() - cf1.getPosition()) == relMotion.relativeMotion.getVelocity() * DELTA_T); } TEST_CASE(testExtendBegin) { RelativeMotion relMotion = createRandomRelativeMotion(); Vec3 offsetFromBegin = createRandomNonzeroVec3(); RelativeMotion resultingMotion1 = relMotion.extendBegin(offsetFromBegin); RelativeMotion resultingMotion2 = RelativeMotion(Motion(), CFrame(offsetFromBegin)) + relMotion; ASSERT(resultingMotion1 == resultingMotion2); } TEST_CASE(testExtendEnd) { RelativeMotion relMotion = createRandomRelativeMotion(); Vec3 offsetFromEnd = createRandomNonzeroVec3(); RelativeMotion resultingMotion1 = relMotion.extendEnd(offsetFromEnd); RelativeMotion resultingMotion2 = relMotion + RelativeMotion(Motion(), CFrame(offsetFromEnd)); ASSERT(resultingMotion1 == resultingMotion2); } TEST_CASE(testExtendingRelativeMotionCVecCommutes) { RelativeMotion relMotion = createRandomRelativeMotion(); Vec3 offsetFromBegin = createRandomNonzeroVec3(); Vec3 offsetFromEnd = createRandomNonzeroVec3(); RelativeMotion resultingMotion1 = relMotion.extendBegin(offsetFromBegin).extendEnd(offsetFromEnd); RelativeMotion resultingMotion2 = relMotion.extendEnd(offsetFromEnd).extendBegin(offsetFromBegin); ASSERT(resultingMotion1 == resultingMotion2); } TEST_CASE(testExtendingRelativeMotionCFrameCommutes) { RelativeMotion relMotion = createRandomRelativeMotion(); CFrame offsetFromBegin = createRandomCFrame(); CFrame offsetFromEnd = createRandomCFrame(); RelativeMotion resultingMotion1 = relMotion.extendBegin(offsetFromBegin).extendEnd(offsetFromEnd); RelativeMotion resultingMotion2 = relMotion.extendEnd(offsetFromEnd).extendBegin(offsetFromBegin); ASSERT(resultingMotion1 == resultingMotion2); } TEST_CASE(testExtendingRelativeMotionVecCorrect) { RelativeMotion relMotion = createRandomRelativeMotion(); Motion motionOfOrigin = createRandomMotion(); Vec3 offsetFromBegin = createRandomNonzeroVec3(); Vec3 offsetFromEnd = createRandomNonzeroVec3(); // manually compute resulting motion Motion motionOfStartPoint = motionOfOrigin.getMotionOfPoint(offsetFromBegin); Motion motionOfEnd = motionOfStartPoint.getMotionOfPoint(relMotion.locationOfRelativeMotion.getPosition()).addRelativeMotion(relMotion.relativeMotion); RelativeMotion relMotionOfBeginIncluded = relMotion.extendBegin(offsetFromBegin); ASSERT(motionOfEnd == relMotionOfBeginIncluded.applyTo(motionOfOrigin)); } TEST_CASE(testExtendingRelativeMotionCFrameCorrect) { RelativeMotion relMotion = createRandomRelativeMotion(); Motion motionOfOrigin = createRandomMotion(); CFrame offsetFromBegin = createRandomCFrame(); CFrame offsetFromEnd = createRandomCFrame(); // manually compute resulting motion Motion motionOfStartPoint = motionOfOrigin.getMotionOfPoint(offsetFromBegin.getPosition()); Motion rotatedRelativeMotion = localToGlobal(offsetFromBegin.getRotation(), relMotion.relativeMotion); CFrame rotatedOffset = offsetFromBegin.localToRelative(relMotion.locationOfRelativeMotion); Motion motionOfEnd = motionOfStartPoint.getMotionOfPoint(rotatedOffset.getPosition()).addRelativeMotion(rotatedRelativeMotion); RelativeMotion relMotionOfBeginIncluded = relMotion.extendBegin(offsetFromBegin); ASSERT(motionOfEnd == relMotionOfBeginIncluded.applyTo(motionOfOrigin)); } TEST_CASE(testJoiningRelativeMotionCorrect) { RelativeMotion r1 = createRandomRelativeMotion(); RelativeMotion r2 = createRandomRelativeMotion(); Motion motionOfEndPoint = r1.relativeMotion; CFrame offsetForSecondEndPoint = r1.locationOfRelativeMotion.localToRelative(r2.locationOfRelativeMotion); Motion secondMotionInNewSpace = localToGlobal(r1.locationOfRelativeMotion.getRotation(), r2.relativeMotion); Motion motionOfSecondEndPoint = motionOfEndPoint.getMotionOfPoint(offsetForSecondEndPoint.getPosition()) .addRelativeMotion(secondMotionInNewSpace); ASSERT((r1 + r2).locationOfRelativeMotion == offsetForSecondEndPoint + r1.locationOfRelativeMotion.getPosition()); ASSERT((r1 + r2).relativeMotion == motionOfSecondEndPoint); } TEST_CASE(testComplexRelativeMotionAssociative) { Motion m0 = createRandomMotion(); RelativeMotion r1 = createRandomRelativeMotion(); RelativeMotion r2 = createRandomRelativeMotion(); RelativeMotion r3 = createRandomRelativeMotion(); ASSERT((r1 + r2) + r3 == r1 + (r2 + r3)); } TEST_CASE(testReducedRelativeMotionAssociative) { Vec3 piston1Direction = createRandomNonzeroVec3(); Vec3 piston2Direction = createRandomNonzeroVec3(); Vec3 motor1Direction = createRandomNonzeroVec3(); Vec3 motor2Direction = createRandomNonzeroVec3(); RelativeMotion p1(Motion(createRandomNonzeroDouble() * piston1Direction, Vec3(), createRandomNonzeroDouble() * piston1Direction, Vec3()), CFrame(createRandomNonzeroDouble() * piston1Direction)); RelativeMotion p2(Motion(createRandomNonzeroDouble() * piston2Direction, Vec3(), createRandomNonzeroDouble() * piston2Direction, Vec3()), CFrame(createRandomNonzeroDouble() * piston2Direction)); RelativeMotion m1(Motion(Vec3(), createRandomNonzeroDouble() * motor1Direction, Vec3(), createRandomNonzeroDouble() * motor1Direction), CFrame(createRandomNonzeroDouble() * motor1Direction)); RelativeMotion m2(Motion(Vec3(), createRandomNonzeroDouble() * motor2Direction, Vec3(), createRandomNonzeroDouble() * motor2Direction), CFrame(createRandomNonzeroDouble() * motor2Direction)); ASSERT(p1 + p2 == p2 + p1); // pistons are commutative ASSERT((p1 + p2) + m1 == p1 + (p2 + m1)); ASSERT((m1 + m2) + p1 == m1 + (m2 + p1)); ASSERT(((m1 + p1) + m2) + p2 == m1 + (p1 + (m2 + p2))); } TEST_CASE(testSimulateRelativeMotion) { Motion motionOfOrigin = createRandomMotion(); RelativeMotion relMotion = createRandomRelativeMotion(); /*motionOfOrigin.acceleration = Vec3(0, 0, 0); relMotion.relativeMotion.acceleration = Vec3(0, 0, 0); motionOfOrigin.angularAcceleration = Vec3(0, 0, 0); relMotion.relativeMotion.angularAcceleration = Vec3(0, 0, 0); relMotion.relativeMotion.angularVelocity = Vec3(0, 0, 0);*/ //relMotion.relativeMotion.velocity = Vec3(0, 0, 0); Coriolis effect! CFrame origin1 = CFrame(); CFrame origin2 = simulateForTime(motionOfOrigin, CFrame(), DELTA_T); CFrame origin3 = simulateForTime(motionOfOrigin, CFrame(), 2 * DELTA_T); CFrame relative1 = relMotion.locationOfRelativeMotion; CFrame relative2 = simulateForTime(relMotion.relativeMotion, relMotion.locationOfRelativeMotion, DELTA_T); CFrame relative3 = simulateForTime(relMotion.relativeMotion, relMotion.locationOfRelativeMotion, 2 * DELTA_T); CFrame p1 = origin1.localToGlobal(relative1); CFrame p2 = origin2.localToGlobal(relative2); CFrame p3 = origin3.localToGlobal(relative3); Motion estimatedMotion = estimateMotion(p1, p2, p3, DELTA_T); Motion calculatedMotion = relMotion.applyTo(motionOfOrigin); ASSERT(estimatedMotion == calculatedMotion); } TEST_CASE(testSimulateRelativeToRelativeMotion) { RelativeMotion motionOfOrigin = createRandomRelativeMotion(); RelativeMotion relMotion = createRandomRelativeMotion(); /*motionOfOrigin.acceleration = Vec3(0, 0, 0); relMotion.relativeMotion.acceleration = Vec3(0, 0, 0); motionOfOrigin.angularAcceleration = Vec3(0, 0, 0); relMotion.relativeMotion.angularAcceleration = Vec3(0, 0, 0); relMotion.relativeMotion.angularVelocity = Vec3(0, 0, 0);*/ //relMotion.relativeMotion.velocity = Vec3(0, 0, 0); Coriolis effect! CFrame origin1 = motionOfOrigin.locationOfRelativeMotion; CFrame origin2 = simulateForTime(motionOfOrigin.relativeMotion, motionOfOrigin.locationOfRelativeMotion, DELTA_T); CFrame origin3 = simulateForTime(motionOfOrigin.relativeMotion, motionOfOrigin.locationOfRelativeMotion, 2 * DELTA_T); CFrame relative1 = relMotion.locationOfRelativeMotion; CFrame relative2 = simulateForTime(relMotion.relativeMotion, relMotion.locationOfRelativeMotion, DELTA_T); CFrame relative3 = simulateForTime(relMotion.relativeMotion, relMotion.locationOfRelativeMotion, 2 * DELTA_T); CFrame p1 = origin1.localToGlobal(relative1); CFrame p2 = origin2.localToGlobal(relative2); CFrame p3 = origin3.localToGlobal(relative3); Motion estimatedMotion = estimateMotion(p1, p2, p3, DELTA_T); RelativeMotion calculatedMotion = motionOfOrigin + relMotion; ASSERT(estimatedMotion == calculatedMotion.relativeMotion); ASSERT(p1 == calculatedMotion.locationOfRelativeMotion); } ================================================ FILE: tests/physicalStructureTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include "randomValues.h" #include "estimateMotion.h" #include #include #include #include #include using namespace P3D; #define ASSERT(x) ASSERT_STRICT(x) static CFrame cf() { return createRandomCFrame(); } static Part* createPart() { return new Part(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), {1.0, 1.0, 1.0}); } TEST_CASE(testBasicCreateDestroy) { Part* p = createPart(); p->ensureHasPhysical(); delete p; } TEST_CASE(testManyAttachBasic) { Part* a = createPart(); Part* b = createPart(); Part* c = createPart(); Part* d = createPart(); a->attach(b, cf()); a->attach(c, cf()); c->attach(d, cf()); // a should be mainPart ASSERT_TRUE(a->isMainPart()); ASSERT_FALSE(b->isMainPart()); ASSERT_FALSE(c->isMainPart()); ASSERT_FALSE(d->isMainPart()); delete b; delete a; delete c; delete d; } TEST_CASE(testManualDetach) { Part* a = createPart(); Part* b = createPart(); Part* c = createPart(); Part* d = createPart(); a->attach(b, cf()); a->attach(c, cf()); c->attach(d, cf()); // a should be mainPart ASSERT_TRUE(a->isMainPart()); ASSERT_FALSE(b->isMainPart()); ASSERT_FALSE(c->isMainPart()); ASSERT_FALSE(d->isMainPart()); b->detach(); ASSERT_TRUE(b->isMainPart()); a->detach(); ASSERT(c->getPhysical()->rigidBody.mainPart != a); delete b; delete a; delete c; delete d; } TEST_CASE(testManyAttachComplex) { Part* a = createPart(); Part* b = createPart(); Part* c = createPart(); Part* d = createPart(); Part* e1 = createPart(); Part* e2 = createPart(); e1->attach(e2, cf()); Part* f1 = createPart(); Part* f2 = createPart(); f1->attach(f2, cf()); Part* g1 = createPart(); Part* g2 = createPart(); g1->attach(g2, cf()); // single new part attachments a->attach(b, cf()); b->attach(c, cf()); d->attach(b, cf()); a->attach(e1, cf()); a->attach(f2, cf()); g1->attach(a, cf()); ASSERT_TRUE(a->getPhysical()->rigidBody.getPartCount() == 10); Part* parts[]{a,b,c,d,e1,e2,f1,f2,g1,g2}; Physical* aParent = a->getPhysical(); for(Part* p : parts) { ASSERT_TRUE(p->getPhysical() == aParent); delete p; } } TEST_CASE(testBasicMakeMainPhysical) { Part* a = createPart(); Part* b = createPart(); a->attach(b, new FixedConstraint(), cf(), cf()); ASSERT_TRUE(a->getPhysical() != nullptr); ASSERT_TRUE(b->getPhysical() != nullptr); ASSERT_TRUE(a->getPhysical()->isMainPhysical()); ASSERT_FALSE(b->getPhysical()->isMainPhysical()); MotorizedPhysical* m = b->getMainPhysical(); MotorizedPhysical* resultingMain = b->getPhysical()->makeMainPhysical(); ASSERT_TRUE(resultingMain == m); ASSERT_TRUE(b->getPhysical()->isMainPhysical()); ASSERT_FALSE(a->getPhysical()->isMainPhysical()); ASSERT_TRUE(a->getPhysical()->childPhysicals.size() == 0); ASSERT_TRUE(b->getPhysical()->childPhysicals.size() == 1); ASSERT_TRUE(&b->getPhysical()->childPhysicals[0] == a->getPhysical()); ASSERT_TRUE(a->isValid()); ASSERT_TRUE(b->isValid()); } TEST_CASE(testAdvancedMakeMainPhysical) { Part* a = createPart(); Part* b = createPart(); Part* c = createPart(); Part* d = createPart(); Part* e = createPart(); Part* f = createPart(); Part* g = createPart(); a->attach(b, new FixedConstraint(), cf(), cf()); a->attach(e, new FixedConstraint(), cf(), cf()); b->attach(c, new FixedConstraint(), cf(), cf()); b->attach(d, new FixedConstraint(), cf(), cf()); e->attach(f, new FixedConstraint(), cf(), cf()); f->attach(g, new FixedConstraint(), cf(), cf()); const int partCount = 7; Part* parts[partCount]{a,b,c,d,e,f,g}; MotorizedPhysical* m = a->getMainPhysical(); GlobalCFrame cframesBefore[partCount]; for(int i = 0; i < partCount; i++) { cframesBefore[i] = parts[i]->getCFrame(); } m->fullRefreshOfConnectedPhysicals(); for(int i = 0; i < partCount; i++) { GlobalCFrame cfr = parts[i]->getCFrame(); ASSERT_TOLERANT(cframesBefore[i] == cfr, 0.0005); cframesBefore[i] = cfr; } MotorizedPhysical* resultingMain = f->getPhysical()->makeMainPhysical(); ASSERT_TRUE(resultingMain == m); ASSERT_TRUE(m->isValid()); for(int i = 0; i < partCount; i++) { GlobalCFrame cfr = parts[i]->getCFrame(); ASSERT_TOLERANT(cframesBefore[i] == cfr, 0.0005); cframesBefore[i] = cfr; } m->fullRefreshOfConnectedPhysicals(); for(int i = 0; i < partCount; i++) { GlobalCFrame cfr = parts[i]->getCFrame(); ASSERT_TOLERANT(cframesBefore[i] == cfr, 0.0005); cframesBefore[i] = cfr; } } TEST_CASE(testAttachConnectedPhysicalToConnectedPhysical) { Part* a = createPart(); Part* b = createPart(); Part* c = createPart(); Part* d = createPart(); Part* e = createPart(); Part* f = createPart(); Part* g = createPart(); a->attach(b, new FixedConstraint(), cf(), cf()); a->attach(e, new FixedConstraint(), cf(), cf()); b->attach(c, new FixedConstraint(), cf(), cf()); b->attach(d, new FixedConstraint(), cf(), cf()); e->attach(f, new FixedConstraint(), cf(), cf()); f->attach(g, new FixedConstraint(), cf(), cf()); Part* a2 = createPart(); Part* b2 = createPart(); Part* c2 = createPart(); Part* d2 = createPart(); Part* e2 = createPart(); Part* f2 = createPart(); Part* g2 = createPart(); a2->attach(b2, new FixedConstraint(), cf(), cf()); a2->attach(e2, new FixedConstraint(), cf(), cf()); b2->attach(c2, new FixedConstraint(), cf(), cf()); b2->attach(d2, new FixedConstraint(), cf(), cf()); e2->attach(f2, new FixedConstraint(), cf(), cf()); f2->attach(g2, new FixedConstraint(), cf(), cf()); e->attach(f2, new FixedConstraint(), cf(), cf()); Part* parts[]{a,b,c,d,e,f,g,a2,b2,c2,d2,e2,f2,g2}; MotorizedPhysical* mainPhys = a->getMainPhysical(); for(Part* p : parts) { ASSERT_TRUE(p->getMainPhysical() == mainPhys); } for(Part* p : parts) { delete p; } } ================================================ FILE: tests/physicsTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include #include "simulation.h" #include "generators.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/log.h" using namespace P3D; #define REMAINS_CONSTANT(v) REMAINS_CONSTANT_TOLERANT(v, 0.0005) #define ASSERT(v) ASSERT_TOLERANT(v, 0.0005) static const double DELTA_T = 0.01; static const int TICKS = 300; static const PartProperties basicProperties{0.7, 0.2, 0.6}; TEST_CASE_SLOW(positionInvariance) { Rotation rotation = Rotation::fromEulerAngles(0.0, 0.0, 0.0); Position origins[]{Position(0,0,0), Position(0,0,1), Position(5.3,0,-2.4),Position(0.3,0,0.7),Position(0.0000001,0,0.00000001)}; CFrame houseRelative(Vec3(0.7, 0.6, 1.6), Rotation::fromEulerAngles(0.3, 0.7, 0.9)); CFrame icosaRelative(Vec3(0.7, 3.0, 1.6), Rotation::fromEulerAngles(0.1, 0.1, 0.1)); for(Position o:origins) { GlobalCFrame origin(o, rotation); WorldPrototype world(DELTA_T); world.addExternalForce(new DirectionalGravity(Vec3(0, -1, 0))); Part housePart(polyhedronShape(ShapeLibrary::house), origin.localToGlobal(houseRelative), {1.0, 1.0, 0.7}); Part icosaPart(polyhedronShape(ShapeLibrary::icosahedron), origin.localToGlobal(icosaRelative), {10.0, 0.7, 0.7}); Part flooring(boxShape(200.0, 0.3, 200.0), origin, {1.0, 1.0, 0.7}); world.addPart(&housePart); world.addPart(&icosaPart); world.addTerrainPart(&flooring); for(int i = 0; i < TICKS; i++) world.tick(); REMAINS_CONSTANT_TOLERANT(origin.globalToLocal(housePart.getCFrame()), 0.0002); REMAINS_CONSTANT_TOLERANT(origin.globalToLocal(icosaPart.getCFrame()), 0.0002); } } TEST_CASE_SLOW(rotationInvariance) { CFrame houseRelative(Vec3(0.7, 0.6, 1.6), Rotation::fromEulerAngles(0.3, 0.7, 0.9)); CFrame icosaRelative(Vec3(0.7, 3.0, 1.6), Rotation::fromEulerAngles(0.1, 0.1, 0.1)); for(double rotation = -1.5; rotation < 1.500001; rotation += 0.3) { logStream << rotation << '\n'; GlobalCFrame origin(Position(0.0,0.0,0.0), Rotation::rotY(rotation)); WorldPrototype world(DELTA_T); world.addExternalForce(new DirectionalGravity(Vec3(0, -1, 0))); Part housePart(polyhedronShape(ShapeLibrary::house), origin.localToGlobal(houseRelative), {1.0, 1.0, 0.7}); Part icosaPart(polyhedronShape(ShapeLibrary::icosahedron), origin.localToGlobal(icosaRelative), {10.0, 0.7, 0.7}); Part flooring(boxShape(200.0, 0.3, 200.0), origin, {1.0, 1.0, 0.7}); world.addPart(&housePart); world.addPart(&icosaPart); world.addTerrainPart(&flooring); for(int i = 0; i < TICKS; i++) world.tick(); REMAINS_CONSTANT_TOLERANT(origin.globalToLocal(housePart.getCFrame()), 0.02); REMAINS_CONSTANT_TOLERANT(origin.globalToLocal(icosaPart.getCFrame()), 0.02); } } TEST_CASE(applyForceToRotate) { Part part(boxShape(1.0, 1.0, 1.0), GlobalCFrame(0,0,0, Rotation::fromEulerAngles(0.3, 0.7, 0.9)), {1.0, 1.0, 0.7}); Physical* partPhys = part.ensureHasPhysical(); Vec3 relAttach = Vec3(1.0, 0.0, 0.0); Vec3 force = Vec3(0.0, 1.0, 0.0); partPhys->mainPhysical->applyForce(relAttach, force); ASSERT(partPhys->mainPhysical->totalForce == force); ASSERT(partPhys->mainPhysical->totalMoment == Vec3(0.0, 0.0, 1.0)); } TEST_CASE(momentToAngularVelocity) { Part part(boxShape(1.0, 1.0, 1.0), GlobalCFrame(Rotation::rotY(PI / 2)), {1.0, 1.0, 0.7}); MotorizedPhysical& p = *part.ensureHasPhysical()->mainPhysical; Vec3 moment(1.0, 0.0, 0.0); for(int i = 0; i < 50; i++) { p.applyMoment(moment); p.update(0.05); } ASSERT(p.getMotion().getAngularVelocity() == moment * (50.0 * 0.05) * p.momentResponse(0, 0)); } TEST_CASE(rotationImpulse) { Part part(boxShape(0.2, 20.0, 0.2), GlobalCFrame(0,0,0), {1.0, 1.0, 0.7}); MotorizedPhysical& veryLongBoxPhysical = *part.ensureHasPhysical()->mainPhysical; Vec3 xMoment = Vec3(1.0, 0.0, 0.0); Vec3 yMoment = Vec3(0.0, 1.0, 0.0); Vec3 zMoment = Vec3(0.0, 0.0, 1.0); { veryLongBoxPhysical.totalForce = Vec3(); veryLongBoxPhysical.totalMoment = Vec3(); } } /*TEST_CASE(testPointAcceleration) { Part testPart(boxShape(1.0, 2.0, 3.0), GlobalCFrame(0,0,0), {1.0, 1.0, 0.7}); testPart.ensureHasPhysical(); MotorizedPhysical& testPhys = *testPart.getMainPhysical(); Vec3 localPoint(3, 5, 7); Vec3 force(-4, -3, 0.5); double deltaT = 0.00001; testPhys.applyForce(localPoint, force); Vec3 acceleration = testPhys.getMotion().getAcceleration(); Vec3 angularAcceleration = testPhys.getMotion().rotation.angularAcceleration; Vec3 pointAcceleration = testPhys.getMotion().getAccelerationOfPoint(localPoint); testPhys.update(deltaT); Vec3 actualAcceleration = testPhys.getMotion().getVelocity() / deltaT; Vec3 actualAngularAcceleration = testPhys.getMotion().rotation.angularVelocity / deltaT; Vec3 actualPointAcceleration = testPhys.getMotion().getVelocityOfPoint(testPhys.getCFrame().localToRelative(localPoint)) / deltaT; ASSERT(acceleration == actualAcceleration); ASSERT(angularAcceleration == actualAngularAcceleration); ASSERT(pointAcceleration == actualPointAcceleration); }*/ /*TEST_CASE(testGetPointAccelerationMatrix) { Part testPart(boxShape(1.0, 2.0, 3.0), GlobalCFrame(0,0,0), {1.0, 1.0, 0.7}); testPart.ensureHasPhysical(); MotorizedPhysical& testPhys = *testPart.getMainPhysical(); Vec3 localPoint(3, 5, 7); Vec3 force(-4, -3, 0.5); testPhys.applyForce(localPoint, force); SymmetricMat3 accelMatrix = testPhys.getResponseMatrix(localPoint); logStream << accelMatrix; Vec3 actualAcceleration = testPhys.getMotion().getAccelerationOfPoint(localPoint); ASSERT(actualAcceleration == accelMatrix * force); }*/ TEST_CASE(impulseTest) { Part part(boxShape(1.0, 2.0, 2.5), GlobalCFrame(0,0,0), {1.0, 1.0, 0.7}); MotorizedPhysical& p = *part.ensureHasPhysical()->mainPhysical; p.applyImpulseAtCenterOfMass(Vec3(15, 0, 0)); ASSERT(p.getMotion().getVelocity() == Vec3(3,0,0)); ASSERT(p.getMotion().getAngularVelocity() == Vec3(0, 0, 0)); Vec3 angularImpulse = Vec3(0, 2, 0) % Vec3(-15, 0, 0); p.applyImpulse(Vec3(0, 2, 0), Vec3(-15, 0, 0)); ASSERT(p.getMotion().getVelocity() == Vec3(0, 0, 0)); ASSERT(p.getMotion().getAngularVelocity() == ~part.getInertia() * angularImpulse); } TEST_CASE(testPointAccelMatrixImpulse) { Part part(boxShape(1.0, 2.0, 3.0), GlobalCFrame(7.6, 3.4, 3.9, Rotation::fromEulerAngles(1.1, 0.7, 0.9)), {1.0, 1.0, 0.7}); MotorizedPhysical& p = *part.ensureHasPhysical()->mainPhysical; Vec3 localPoint(0.8, 0.6, 0.9); Vec3 localImpulse(0.3, -0.7, 0.6); Vec3 estimatedAccel = p.getResponseMatrix(localPoint) * localImpulse; p.applyImpulse(part.getCFrame().localToRelative(localPoint), part.getCFrame().localToRelative(localImpulse)); Vec3 realAccel = part.getCFrame().relativeToLocal(p.getMotion().getVelocityOfPoint(part.getCFrame().localToRelative(localPoint))); ASSERT(estimatedAccel == realAccel); } TEST_CASE(inelasticColission) { Part part(boxShape(1.0, 2.0, 3.0), GlobalCFrame(7.6, 3.4, 3.9, Rotation::fromEulerAngles(1.1, 0.7, 0.9)), {1.0, 1.0, 0.7}); MotorizedPhysical& p = *part.ensureHasPhysical()->mainPhysical; Vec3 localPoint(0.8, 0.6, 0.9); Vec3 relativePoint = p.getCFrame().localToRelative(localPoint); p.getMotion().getVelocity() = Vec3(0.3, -1.3, 1.2); p.getMotion().getAngularVelocity() = Vec3(0.7, 0.5, -0.9); Vec3 velOfPoint = p.getMotion().getVelocityOfPoint(relativePoint); ASSERT(velOfPoint.y < 0); logStream << "totalVelocity: " << str(velOfPoint); Vec3 direction(0.0, 170.0, 0.0); //double inertia = p.getInertiaOfPointInDirection(localPoint, p.getCFrame().relativeToLocal(direction)); //Log::warn("inertia: %f", inertia); logStream << "velInDirection: " << velOfPoint * normalize(direction); //Vec3 relativeImpulse = -velOfPoint * direction.normalize() * direction.normalize() * inertia; Vec3 desiredAccel = -velOfPoint * direction * direction / lengthSquared(direction); Vec3 relativeImpulse = p.getCFrame().localToRelative(~p.getResponseMatrix(localPoint) * p.getCFrame().relativeToLocal(desiredAccel)); Vec3 estimatedAccelLocal = p.getResponseMatrix(localPoint) * p.getCFrame().relativeToLocal(relativeImpulse); Vec3 estimatedAccelRelative = p.getCFrame().localToRelative(estimatedAccelLocal); p.applyImpulse(relativePoint, relativeImpulse); Vec3 velOfPointAfter = p.getMotion().getVelocityOfPoint(relativePoint); logStream << "New velocity: " << str(velOfPointAfter); logStream << "velInDirection After: " << velOfPointAfter * normalize(direction); logStream << "estimatedAccelRelative: " << str(estimatedAccelRelative); logStream << "Actual accel: " << str(velOfPointAfter - velOfPoint); ASSERT(estimatedAccelRelative == velOfPointAfter - velOfPoint); ASSERT(velOfPointAfter.y == 0); } TEST_CASE(inelasticColission2) { Part part(boxShape(1.0, 2.0, 3.0), GlobalCFrame(/*Vec3(7.6, 3.4, 3.9), rotationMatrixfromEulerAngles(1.1, 0.7, 0.9)*/), {1.0, 1.0, 0.7}); MotorizedPhysical& p = *part.ensureHasPhysical()->mainPhysical; Vec3 localPoint(0.8, 0.6, 0.9); Vec3 relativePoint = p.getCFrame().localToRelative(localPoint); Vec3 normal(0.0, 170.0, 0.0); p.getMotion().getVelocity() = Vec3(0.3, -1.3, 1.2); p.getMotion().getAngularVelocity() = Vec3(0.7, 0.5, -0.9); Vec3 velOfPoint = p.getMotion().getVelocityOfPoint(relativePoint); ASSERT(velOfPoint.y < 0); double inertia = p.getInertiaOfPointInDirectionRelative(localPoint, normal); double normalVelocity = velOfPoint * normalize(normal); double desiredAccel = -normalVelocity; Vec3 impulse = normalize(normal) * desiredAccel * inertia; p.applyImpulse(relativePoint, impulse); Vec3 velOfPointAfter = p.getMotion().getVelocityOfPoint(relativePoint); logStream << "New velocity: " + str(velOfPointAfter); logStream << "velInDirection After: ", velOfPointAfter * normalize(normal); //logStream << "estimatedAccelRelative: " + str(estimatedAccelRelative); logStream << "Actual accel: " + str(velOfPointAfter - velOfPoint); //ASSERT(estimatedAccelRelative == velOfPointAfter - velOfPoint); ASSERT(velOfPointAfter.y == 0); } /*TEST_CASE(testPointAccelMatrixAndInertiaInDirection) { Part part(boxShape(1.0, 1.0, 1.0), GlobalCFrame(Position(7.6, 3.4, 3.9), Rotation::fromEulerAngles(1.1, 0.7, 0.9)), {1.0, 1.0, 0.7}); MotorizedPhysical p(&part); Vec3 localPoint(0.8, 0.6, 0.9); Vec3 localImpulse(0.3, -0.7, 0.6); Vec3 estimatedAccel = p.getResponseMatrix(localPoint) * localImpulse; p.applyImpulse(part.getCFrame().localToRelative(localPoint), part.getCFrame().localToRelative(localImpulse)); Vec3 realAccel = part.getCFrame().relativeToLocal(p.getVelocityOfPoint(part.getCFrame().localToRelative(localPoint))); ASSERT(estimatedAccel == realAccel); }*/ TEST_CASE(testChangeInertialBasis) { Rotation rotation = Rotation::fromEulerAngles(0.6, 0.3, 0.7); Polyhedron rotatedTriangle = ShapeLibrary::trianglePyramid.rotated(static_cast(rotation)); SymmetricMat3 triangleInertia = ShapeLibrary::trianglePyramid.getInertia(CFrame()); SymmetricMat3 rotatedTriangleInertia = rotatedTriangle.getInertia(CFrame()); ASSERT(getEigenDecomposition(triangleInertia).eigenValues == getEigenDecomposition(rotatedTriangleInertia).eigenValues); ASSERT(getRotatedInertia(triangleInertia, rotation) == rotatedTriangleInertia); } TEST_CASE(testMultiPartPhysicalSimple) { Shape box(boxShape(1.0, 0.5, 0.5)); Shape box2(boxShape(1.0, 0.5, 0.5)); Shape doubleBox(boxShape(2.0, 0.5, 0.5)); Part p1(box, GlobalCFrame(), {10.0, 0.5, 0.5}); Part p2(box2, GlobalCFrame(), {10.0, 0.5, 0.5}); Part doubleP(doubleBox, GlobalCFrame(), {10.0, 0.5, 0.5}); p1.ensureHasPhysical(); MotorizedPhysical& phys = *p1.getMainPhysical(); phys.attachPart(&p2, CFrame(Vec3(1.0, 0.0, 0.0))); doubleP.ensureHasPhysical(); MotorizedPhysical& phys2 = *doubleP.getMainPhysical(); ASSERT(phys.totalMass == p1.getMass() + p2.getMass()); ASSERT(phys.totalCenterOfMass == Vec3(0.5, 0, 0)); ASSERT(phys.forceResponse == phys2.forceResponse); ASSERT(phys.momentResponse == phys2.momentResponse); } TEST_CASE(testMultiPartPhysicalRotated) { Shape box(boxShape(1.0, 0.5, 0.5)); Shape box2(boxShape(0.5, 0.5, 1.0)); Shape doubleBox(boxShape(2.0, 0.5, 0.5)); Part* p1 = new Part(box, GlobalCFrame(), {10.0, 0.0, 0.7}); Part* p2 = new Part(box2, GlobalCFrame(), {10.0, 0.0, 0.7}); Part* doubleP = new Part(doubleBox, GlobalCFrame(), {10.0, 0, 0.7}); MotorizedPhysical phys(p1); phys.attachPart(p2, CFrame(Vec3(1.0, 0.0, 0.0), Rotation::Predefined::Y_90)); MotorizedPhysical phys2(doubleP); ASSERT(phys.totalMass == p1->getMass() + p2->getMass()); ASSERT(phys.totalCenterOfMass == Vec3(0.5, 0, 0)); ASSERT(phys.forceResponse == phys2.forceResponse); ASSERT(phys.momentResponse == phys2.momentResponse); } TEST_CASE(testShapeNativeScaling) { Polyhedron testPoly = ShapeLibrary::createPointyPrism(4, 1.0f, 1.0f, 0.5f, 0.5f); Shape shape1(polyhedronShape(testPoly)); Shape shape2 = shape1.scaled(2.0, 4.0, 3.7); Polyhedron scaledTestPoly = testPoly.scaled(2.0f, 4.0f, 3.7f); ASSERT(shape2.getInertia() == scaledTestPoly.getInertiaAroundCenterOfMass()); } TEST_CASE(testPhysicalInertiaDerivatives) { Polyhedron testPoly = ShapeLibrary::createPointyPrism(4, 1.0f, 1.0f, 0.5f, 0.5f); Part mainPart(polyhedronShape(testPoly), GlobalCFrame(), basicProperties); Part part1_mainPart(polyhedronShape(ShapeLibrary::house), mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.3), CFrame(0.3, 0.7, -0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, 0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part part2_mainPart(boxShape(1.0, 0.3, 2.0), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part part1_part1_mainPart(cylinderShape(1.0, 0.3), part1_mainPart, new MotorConstraintTemplate(1.3), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)) , basicProperties); Part part1_part1_part1_mainPart(polyhedronShape(ShapeLibrary::trianglePyramid), part1_part1_mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.3), CFrame(0.3, 0.7, -0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, 0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); MotorizedPhysical* motorPhys = mainPart.getMainPhysical(); std::size_t size = motorPhys->getNumberOfPhysicalsInThisAndChildren() - 1; UnmanagedArray> arr(new MonotonicTreeNode[size], size); FullTaylor inertiaTaylor = motorPhys->getCOMMotionTree(std::move(arr)).getInertiaDerivatives(); double deltaT = 0.00001; std::array inertias; inertias[0] = motorPhys->getCOMMotionTree(std::move(arr)).getInertia(); motorPhys->update(deltaT); inertias[1] = motorPhys->getCOMMotionTree(std::move(arr)).getInertia(); motorPhys->update(deltaT); inertias[2] = motorPhys->getCOMMotionTree(std::move(arr)).getInertia(); delete[] arr.getPtrToFree(); FullTaylor estimatedInertiaTaylor = estimateDerivatives(inertias, deltaT); ASSERT_TOLERANT(inertiaTaylor == estimatedInertiaTaylor, 0.01); } TEST_CASE(testCenterOfMassKept) { Polyhedron testPoly = ShapeLibrary::createPointyPrism(4, 1.0f, 1.0f, 0.5f, 0.5f); Part mainPart(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties); Part part1_mainPart(boxShape(1.0, 1.0, 1.0), mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); ALLOCA_COMMotionTree(t, mainPart.getMainPhysical(), size); logStream << t.getRelativePosOfMain(); ASSERT(t.getRelativePosOfMain() == -t.relativeMotionTree[0].value.locationOfRelativeMotion.getPosition()); ASSERT(t.getMotionOfMain() == -t.relativeMotionTree[0].value.relativeMotion.translation); } TEST_CASE(testBasicAngularMomentum) { double motorSpeed = 1.0; MotorConstraintTemplate* constraint = new MotorConstraintTemplate(motorSpeed); Part mainPart(cylinderShape(1.0, 1.0), GlobalCFrame(), basicProperties); Part attachedPart(cylinderShape(1.0, 1.0), mainPart, constraint, CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); ALLOCA_COMMotionTree(t, mainPart.getMainPhysical(), size); SymmetricMat3 inertia = attachedPart.getInertia(); Vec3 angularVel = constraint->getRelativeMotion().relativeMotion.getAngularVelocity(); Vec3 angularMomentum = inertia * angularVel; ASSERT(t.getInternalAngularMomentum() == angularMomentum); } TEST_CASE(testBasicAngularMomentumTurned) { double motorSpeed = 1.0; MotorConstraintTemplate* constraint = new MotorConstraintTemplate(motorSpeed); Part mainPart(cylinderShape(1.0, 1.0), GlobalCFrame(), basicProperties); Part attachedPart(cylinderShape(1.0, 1.0), mainPart, constraint, CFrame(0.0, 0.0, 0.0, Rotation::Predefined::Y_90), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); ALLOCA_COMMotionTree(t, mainPart.getMainPhysical(), size); SymmetricMat3 inertia = attachedPart.getInertia(); Vec3 angularVel = constraint->getRelativeMotion().relativeMotion.getAngularVelocity(); Vec3 angularMomentum = Rotation::Predefined::Y_90.localToGlobal(inertia * angularVel); ASSERT(t.getInternalAngularMomentum() == angularMomentum); } TEST_CASE(testFixedConstraintAngularMomentum) { double motorSpeed = 1.0; MotorConstraintTemplate* constraint1 = new MotorConstraintTemplate(motorSpeed); double offset = 5.0; Part mainPart1(cylinderShape(1.0, 1.0), GlobalCFrame(), basicProperties); Part attachedPart1(cylinderShape(1.0, 1.0), mainPart1, constraint1, CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); Part attachedPart1A(boxShape(1.0, 1.0, 1.0), attachedPart1, new FixedConstraint(), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(offset, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); Part attachedPart1B(boxShape(1.0, 1.0, 1.0), attachedPart1, new FixedConstraint(), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(-offset, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); ALLOCA_COMMotionTree(t1, mainPart1.getMainPhysical(), size1); MotorConstraintTemplate* constraint2 = new MotorConstraintTemplate(motorSpeed); Part mainPart2(cylinderShape(1.0, 1.0), GlobalCFrame(), basicProperties); Part attachedPart2(cylinderShape(1.0, 1.0), mainPart2, constraint2, CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); Part attachedPart2A(boxShape(1.0, 1.0, 1.0), attachedPart2, CFrame(offset, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); Part attachedPart2B(boxShape(1.0, 1.0, 1.0), attachedPart2, CFrame(-offset, 0.0, 0.0, Rotation::Predefined::IDENTITY), basicProperties); ALLOCA_COMMotionTree(t2, mainPart2.getMainPhysical(), size2); ASSERT(t1.totalMass == t2.totalMass); ASSERT(t1.centerOfMass == t2.centerOfMass); ASSERT(t1.motionOfCenterOfMass == t2.motionOfCenterOfMass); ASSERT(t1.getInertia() == t2.getInertia()); ASSERT(t1.getInertiaDerivatives() == t2.getInertiaDerivatives()); ASSERT(t1.getMotion() == t2.getMotion()); ASSERT(t1.getInternalAngularMomentum() == t2.getInternalAngularMomentum()); } static Position getTotalCenterOfMassOfPhysical(const MotorizedPhysical* motorPhys) { double totalMass = 0.0; Vec3 totalCenterOfMass(0.0, 0.0, 0.0); motorPhys->forEachPart([&totalMass, &totalCenterOfMass](const Part& p) { const GlobalCFrame& pcf = p.getCFrame(); const Vec3 pcom = p.getCenterOfMass() - Position(0,0,0); totalCenterOfMass += pcom * p.getMass(); totalMass += p.getMass(); }); return Position(0, 0, 0) + totalCenterOfMass / totalMass; } static Vec3 getTotalMotionOfCenterOfMassOfPhysical(const MotorizedPhysical* motorPhys) { double totalMass = 0.0; Vec3 totalVelocityOfCenterOfMass(0.0, 0.0, 0.0); motorPhys->forEachPart([&totalMass, &totalVelocityOfCenterOfMass](const Part& p) { const GlobalCFrame& pcf = p.getCFrame(); Vec3 relativePartCOM = pcf.localToRelative(p.getLocalCenterOfMass()); Motion m = p.getMotion().getMotionOfPoint(relativePartCOM); Vec3 relVel = m.getVelocity(); totalVelocityOfCenterOfMass += relVel * p.getMass(); totalMass += p.getMass(); }); return totalVelocityOfCenterOfMass / totalMass; } static SymmetricMat3 getTotalInertiaOfPhysical(const MotorizedPhysical* motorPhys) { SymmetricMat3 totalInertia{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; Position com = motorPhys->getCenterOfMass(); motorPhys->forEachPart([&com, &totalInertia](const Part& p) { const GlobalCFrame& pcf = p.getCFrame(); SymmetricMat3 globalInertia = pcf.getRotation().localToGlobal(p.getInertia()); Vec3 relativePartCOM = pcf.localToRelative(p.getLocalCenterOfMass()); Vec3 offset = p.getCenterOfMass() - com; SymmetricMat3 inertiaOfThis = getTranslatedInertiaAroundCenterOfMass(globalInertia, p.getMass(), offset); totalInertia += inertiaOfThis; }); return totalInertia; } static Vec3 getTotalAngularMomentumOfPhysical(const MotorizedPhysical* motorPhys) { Vec3 totalAngularMomentum(0.0, 0.0, 0.0); Position com = motorPhys->getCenterOfMass(); Vec3 comVel = motorPhys->getMotionOfCenterOfMass().getVelocity(); motorPhys->forEachPart([&com, &comVel, &totalAngularMomentum](const Part& p) { const GlobalCFrame& pcf = p.getCFrame(); SymmetricMat3 globalInertia = pcf.getRotation().localToGlobal(p.getInertia()); Vec3 relativePartCOM = pcf.localToRelative(p.getLocalCenterOfMass()); Vec3 offset = p.getCenterOfMass() - com; Motion m = p.getMotion().getMotionOfPoint(relativePartCOM); Vec3 relVel = m.getVelocity(); Vec3 angMomOfThis = getAngularMomentumFromOffset(offset, relVel, m.getAngularVelocity(), globalInertia, p.getMass()); totalAngularMomentum += angMomOfThis; }); return totalAngularMomentum; } std::vector producePhysical() { std::vector result; result.reserve(10); Part& mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house.rotated(Rotationf::fromEulerAngles(1.0f, -0.3f, 0.5f))), GlobalCFrame(), PartProperties{0.7, 0.2, 0.6}); mainPart.ensureHasPhysical(); Part& part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, CFrame(0.3, 0.7, -0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), basicProperties); Part& part2_mainPart = result.emplace_back(boxShape(1.0, 0.3, 2.0), mainPart, CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part& part1_part1_mainPart = result.emplace_back(cylinderShape(1.0, 0.3), part1_mainPart, CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part& part1_part1_part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::trianglePyramid), part1_part1_mainPart, CFrame(0.1, 0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); return result; } std::vector produceMotorizedPhysical() { std::vector result; result.reserve(10); Part& mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house.rotated(Rotationf::fromEulerAngles(1.0f, -0.3f, 0.5f))), GlobalCFrame(), PartProperties{0.7, 0.2, 0.6}); mainPart.ensureHasPhysical(); Part& part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.3), CFrame(0.3, 0.7, -0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, 0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part& part2_mainPart = result.emplace_back(boxShape(1.0, 0.3, 2.0), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part& part1_part1_mainPart = result.emplace_back(cylinderShape(1.0, 0.3), part1_mainPart, new MotorConstraintTemplate(1.3), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, -0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); Part& part1_part1_part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::trianglePyramid), part1_part1_mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.3), CFrame(0.3, 0.7, -0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.1, 0.2, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); return result; } struct FullGlobalDiagnostic { Motion motionOfCOM; Vec3 getVelocity; double kineticEnergy; double inertiaInDirection; Vec3 getTotalAngularMomentum; Position positions[TICKS]; }; template bool tolerantEquals(const FullGlobalDiagnostic& first, const FullGlobalDiagnostic& second, Tol tolerance) { if(tolerantEquals(first.motionOfCOM, second.motionOfCOM, tolerance) && tolerantEquals(first.getVelocity, second.getVelocity, tolerance) && tolerantEquals(first.kineticEnergy, second.kineticEnergy, tolerance) && tolerantEquals(first.inertiaInDirection, second.inertiaInDirection, tolerance) && tolerantEquals(first.getTotalAngularMomentum, second.getTotalAngularMomentum, tolerance)) { for(std::size_t i = 0; i < TICKS; i++) { if(!tolerantEquals(first.positions[i], second.positions[i], tolerance)) { return false; } } return true; } else { return false; } } std::ostream& operator<<(std::ostream& ostream, const FullGlobalDiagnostic& d) { ostream << "FullGlobalDiagnostic{motionOfCOM: " << d.motionOfCOM << "\n"; ostream << "getVelocity: " << d.getVelocity << "\n"; ostream << "kineticEnergy: " << d.kineticEnergy << "\n"; ostream << "inertiaInDirection: " << d.inertiaInDirection << "\n"; ostream << "getTotalAngularMomentum: " << d.getTotalAngularMomentum << "\n"; ostream << "lastPosition: " << d.positions[TICKS - 1] << "}"; return ostream; } static Vec3 inertiaOfRelativePos = Vec3(2.5, 1.2, -2.0); static Vec3 inertiaOfRelativeDir = normalize(Vec3(3.5, -2.0, 1.34)); static FullGlobalDiagnostic doDiagnostic(MotorizedPhysical* p) { FullGlobalDiagnostic result; result.motionOfCOM = p->getMotionOfCenterOfMass(); result.getVelocity = p->getVelocityOfCenterOfMass(); result.kineticEnergy = p->getKineticEnergy(); result.inertiaInDirection = p->getInertiaOfPointInDirectionRelative(inertiaOfRelativePos, inertiaOfRelativeDir); result.getTotalAngularMomentum = p->getTotalAngularMomentum(); for(std::size_t i = 0; i < TICKS; i++) { p->update(DELTA_T); result.positions[i] = p->getCenterOfMass(); } return result; } static const Motion motionOfCOM = Motion(Vec3(1.3, 0.7, -2.1), Vec3(2.1, 0.7, 3.7)); static FullGlobalDiagnostic runDiagnosticForCFrame(MotorizedPhysical* p, const GlobalCFrame& cframeOfStart) { p->setCFrame(cframeOfStart); p->motionOfCenterOfMass = motionOfCOM; for(ConnectedPhysical& c : p->childPhysicals) { SinusoidalPistonConstraint* constraint = dynamic_cast(c.connectionToParent.constraintWithParent.get()); constraint->currentStepInPeriod = 0; } p->fullRefreshOfConnectedPhysicals(); p->refreshPhysicalProperties(); return doDiagnostic(p); } TEST_CASE(basicFullRotationSymmetryInvariance) { const int ticksToSim = 10; Position origin(-143.3, 700.3, 1000.0); Part centerPart(sphereShape(1.0), GlobalCFrame(origin), basicProperties); Shape box = boxShape(1.0, 1.0, 1.0); Part xPart(box, centerPart, CFrame(1.0, 0.0, 0.0), basicProperties); Part yPart(box, centerPart, CFrame(0.0, 1.0, 0.0), basicProperties); Part zPart(box, centerPart, CFrame(0.0, 0.0, 1.0), basicProperties); Part nxPart(box, centerPart, CFrame(-1.0, 0.0, 0.0), basicProperties); Part nyPart(box, centerPart, CFrame(0.0, -1.0, 0.0), basicProperties); Part nzPart(box, centerPart, CFrame(0.0, 0.0, -1.0), basicProperties); MotorizedPhysical* motorPhys = centerPart.getMainPhysical(); FullGlobalDiagnostic reference = runDiagnosticForCFrame(motorPhys, GlobalCFrame(origin)); logStream << reference; Rotation rotations[9]{ Rotation::Predefined::X_90, Rotation::Predefined::X_180, Rotation::Predefined::X_270, Rotation::Predefined::Y_90, Rotation::Predefined::Y_180, Rotation::Predefined::Y_270, Rotation::Predefined::Z_90, Rotation::Predefined::Z_180, Rotation::Predefined::Z_270, }; for(int i = 0; i < 9; i++) { ASSERT(reference == runDiagnosticForCFrame(motorPhys, GlobalCFrame(origin, rotations[i]))); } } TEST_CASE(angularMomentumOverLocalToGlobal) { const int ticksToSim = 10; Position origin(-143.3, 700.3, 1000.0); std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); Motion motionOfCOM = Motion(Vec3(1.3, 0.7, -2.1), Vec3(2.1, 0.7, 3.7)); Rotation rotation = Rotation::fromEulerAngles(1.0, 0.2, -0.9); motorPhys->setCFrame(GlobalCFrame(origin)); motorPhys->motionOfCenterOfMass = motionOfCOM; Vec3 firstAngularMomentumFromParts = getTotalAngularMomentumOfPhysical(motorPhys); Vec3 firstAngularMomentum = motorPhys->getTotalAngularMomentum(); motorPhys->setCFrame(GlobalCFrame(origin, rotation)); motorPhys->motionOfCenterOfMass = localToGlobal(rotation, motionOfCOM); Vec3 secondAngularMomentumFromParts = getTotalAngularMomentumOfPhysical(motorPhys); Vec3 secondAngularMomentum = motorPhys->getTotalAngularMomentum(); ASSERT(rotation.localToGlobal(firstAngularMomentum) == secondAngularMomentum); ASSERT(rotation.localToGlobal(firstAngularMomentumFromParts) == secondAngularMomentumFromParts); } TEST_CASE(hardConstrainedFullRotationFollowsCorrectly) { const int ticksToSim = 10; Position origin(-143.3, 700.3, 1000.0); Part centerPart(sphereShape(1.0), GlobalCFrame(origin), basicProperties); Shape box = boxShape(1.0, 1.0, 1.0); Part zPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::IDENTITY), CFrame(0.0, 0.0, 0.0), basicProperties); Part yPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_90), CFrame(0.0, 0.0, 0.0), basicProperties); Part xPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::Y_90), CFrame(0.0, 0.0, 0.0), basicProperties); Part nzPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_180), CFrame(0.0, 0.0, 0.0), basicProperties); Part nyPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::X_270), CFrame(0.0, 0.0, 0.0), basicProperties); Part nxPart(box, centerPart, new SinusoidalPistonConstraint(1.0, 3.0, 1.0), CFrame(0.0, 0.0, 0.0, Rotation::Predefined::Y_270), CFrame(0.0, 0.0, 0.0), basicProperties); MotorizedPhysical* p = centerPart.getMainPhysical(); FullGlobalDiagnostic reference = runDiagnosticForCFrame(p, GlobalCFrame(origin)); logStream << reference; Rotation rotations[9]{ Rotation::Predefined::X_90, Rotation::Predefined::X_180, Rotation::Predefined::X_270, Rotation::Predefined::Y_90, Rotation::Predefined::Y_180, Rotation::Predefined::Y_270, Rotation::Predefined::Z_90, Rotation::Predefined::Z_180, Rotation::Predefined::Z_270, }; for(int i = 0; i < 9; i++) { ASSERT(reference == runDiagnosticForCFrame(p, GlobalCFrame(origin, rotations[i]))); } } TEST_CASE(basicAngularMomentumOfSinglePart) { Polyhedron testPoly = ShapeLibrary::createPointyPrism(4, 1.0f, 1.0f, 0.5f, 0.5f); Part mainPart(polyhedronShape(testPoly), GlobalCFrame(Position(1.3, 2.7, -2.6), Rotation::fromEulerAngles(0.6, -0.7, -0.3)), basicProperties); mainPart.ensureHasPhysical(); MotorizedPhysical* motorPhys = mainPart.getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(2.0, 3.0, 1.0), Vec3(-1.7, 3.3, 12.0)); ALLOCA_COMMotionTree(t, mainPart.getMainPhysical(), size); ASSERT(motorPhys->getTotalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); } TEST_CASE(motorizedPhysicalAngularMomentum) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(t.getInternalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); ASSERT(motorPhys->getTotalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); } TEST_CASE(physicalTotalAngularMomentum) { std::vector phys = producePhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(2.0, 3.0, 1.0), Vec3(-1.7, 3.3, 12.0)); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(motorPhys->getTotalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); } TEST_CASE(basicMotorizedPhysicalTotalAngularMomentum) { std::vector result; result.reserve(10); Part& mainPart = result.emplace_back(boxShape(1.0, 1.0, 1.0), GlobalCFrame(), basicProperties); mainPart.ensureHasPhysical(); Part& part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.0), CFrame(0.0, 0.0, 0.0), CFrame(0.0, 0.0, 0.0), basicProperties); /*Part& part2_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.7, -2.0, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties);*/ /*Part& part3_mainPart = result.emplace_back(boxShape(1.0, 1.0, 1.0), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.7, -2.0, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties);*/ MotorizedPhysical* motorPhys = result[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(0.0, 0.0, 0.0), Vec3(-1.7, 3.3, 12.0)); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(motorPhys->getTotalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); } TEST_CASE(totalInertiaOfPhysical) { std::vector phys = producePhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(t.getInertia() == getTotalInertiaOfPhysical(motorPhys)); } TEST_CASE(totalInertiaOfBasicMotorizedPhysical) { std::vector result; result.reserve(10); Part& mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), GlobalCFrame(), basicProperties); mainPart.ensureHasPhysical(); /*Part& part1_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, new SinusoidalPistonConstraint(0.0, 2.0, 1.0), CFrame(0.0, 0.0, 0.0), CFrame(0.0, 0.0, 0.0), basicProperties);*/ Part& part2_mainPart = result.emplace_back(polyhedronShape(ShapeLibrary::house), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.7, -2.0, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties); /*Part& part3_mainPart = result.emplace_back(boxShape(1.0, 1.0, 1.0), mainPart, new MotorConstraintTemplate(1.7), CFrame(-0.3, 0.7, 0.5, Rotation::fromEulerAngles(0.7, 0.3, 0.7)), CFrame(0.7, -2.0, -0.5, Rotation::fromEulerAngles(0.2, -0.257, 0.4)), basicProperties);*/ MotorizedPhysical* motorPhys = result[0].getMainPhysical(); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(t.getInertia() == getTotalInertiaOfPhysical(motorPhys)); } TEST_CASE(totalCenterOfMassOfPhysical) { std::vector phys = producePhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); ASSERT(motorPhys->getCenterOfMass() == getTotalCenterOfMassOfPhysical(motorPhys)); } TEST_CASE(totalCenterOfMassOfMotorizedPhysical) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); ASSERT(motorPhys->getCenterOfMass() == getTotalCenterOfMassOfPhysical(motorPhys)); } TEST_CASE(totalVelocityOfCenterOfMassOfPhysical) { std::vector phys = producePhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = motionOfCOM; ASSERT(motorPhys->getMotionOfCenterOfMass().getVelocity() == getTotalMotionOfCenterOfMassOfPhysical(motorPhys)); } TEST_CASE(totalVelocityOfCenterOfMassOfMotorizedPhysical) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = motionOfCOM; ASSERT(motorPhys->getMotionOfCenterOfMass().getVelocity() == getTotalMotionOfCenterOfMassOfPhysical(motorPhys)); } TEST_CASE(totalInertiaOfMotorizedPhysical) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(t.getInertia() == getTotalInertiaOfPhysical(motorPhys)); } TEST_CASE(motorizedPhysicalTotalAngularMomentum) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(2.0, 3.0, 1.0), Vec3(-1.7, 3.3, 12.0)); ALLOCA_COMMotionTree(t, motorPhys, size); ASSERT(motorPhys->getTotalAngularMomentum() == getTotalAngularMomentumOfPhysical(motorPhys)); } TEST_CASE(conservationOfAngularMomentum) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(2.0, 3.0, 1.0), Vec3(-1.7, 3.3, 12.0)); Vec3 initialAngularMomentum = motorPhys->getTotalAngularMomentum(); for(int i = 0; i < TICKS; i++) { motorPhys->update(DELTA_T); ASSERT(initialAngularMomentum == motorPhys->getTotalAngularMomentum()); } } TEST_CASE(conservationOfCenterOfMass) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(0.0, 0.0, 0.0), Vec3(-1.7, 3.3, 12.0)); Position initialCenterOfMass = motorPhys->getCenterOfMass(); for(int i = 0; i < TICKS; i++) { motorPhys->update(DELTA_T); ASSERT(initialCenterOfMass == motorPhys->getCenterOfMass()); } } TEST_CASE(angularMomentumVelocityInvariance) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = Motion(Vec3(0.0, 0.0, 0.0), Vec3(-1.7, 3.3, 12.0)); Vec3 stillAngularMomentum = motorPhys->getTotalAngularMomentum(); Vec3 stillAngularMomentumPartsBased = getTotalAngularMomentumOfPhysical(motorPhys); motorPhys->motionOfCenterOfMass = Motion(Vec3(50.3, 12.3, -74.2), Vec3(-1.7, 3.3, 12.0)); Vec3 movingAngularMomentum = motorPhys->getTotalAngularMomentum(); Vec3 movingAngularMomentumPartsBased = getTotalAngularMomentumOfPhysical(motorPhys); ASSERT(stillAngularMomentum == movingAngularMomentum); ASSERT(stillAngularMomentumPartsBased == movingAngularMomentumPartsBased); } TEST_CASE(setVelocity) { for(int iter = 0; iter < 100; iter++) { std::vector phys = produceMotorizedPhysical(); MotorizedPhysical* motorPhys = phys[0].getMainPhysical(); motorPhys->motionOfCenterOfMass = generateMotion(); Part& p = oneOf(phys); { Vec3 chosenVelocity = generateVec3(); Vec3 prevAngularVelocity = p.getAngularVelocity(); p.setVelocity(chosenVelocity); ASSERT(p.getVelocity() == chosenVelocity); ASSERT(p.getAngularVelocity() == prevAngularVelocity); // angular velocity is maintained } { Vec3 chosenAngularVelocity = generateVec3(); p.setAngularVelocity(chosenAngularVelocity); ASSERT(p.getAngularVelocity() == chosenAngularVelocity); } { Vec3 chosenMotionVel = generateVec3(); Vec3 chosenMotionAngularVel = generateVec3(); p.setMotion(chosenMotionVel, chosenMotionAngularVel); ASSERT(p.getVelocity() == chosenMotionVel); ASSERT(p.getAngularVelocity() == chosenMotionAngularVel); ASSERT(p.getMotion().getVelocity() == chosenMotionVel); ASSERT(p.getMotion().getAngularVelocity() == chosenMotionAngularVel); } } } ================================================ FILE: tests/randomValues.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include namespace P3D { inline double createRandomDouble() { return 2.0 * rand() / RAND_MAX - 1.0; } inline double createRandomNonzeroDouble() { tryAgain: double result = createRandomDouble(); if(std::abs(result) < 0.3) { goto tryAgain; } return result; } // creates a random vector with elements between -1.0 and 1.0 template Vector createRandomVecTemplate() { Vector result; for(std::size_t i = 0; i < Size; i++) { result[i] = createRandomDouble(); } return result; } // creates a random vector with elements between -1.0 and 1.0 template Matrix createRandomMatrixTemplate() { Matrix result; for(std::size_t y = 0; y < Height; y++) { for(std::size_t x = 0; x < Width; x++) { result(x, y) = createRandomDouble(); } } return result; } // creates a random average sized vector with elements between -1.0 and 1.0 template Vector createRandomNonzeroVecTemplate() { Vector result; tryAgain: for(std::size_t i = 0; i < Size; i++) { result[i] = createRandomDouble(); } if(lengthSquared(result) < 0.3) { goto tryAgain; } return result; } // creates a random rotation template RotationTemplate createRandomRotationTemplate() { Vector angles = createRandomNonzeroVecTemplate() * (PI / 2); return Rotation::fromEulerAngles(angles.x, angles.y, angles.z); } // creates a random rotation template CFrameTemplate createRandomCFrameTemplate() { return CFrameTemplate(createRandomNonzeroVecTemplate(), createRandomRotationTemplate()); } inline Vec3 createRandomVec() { return createRandomVecTemplate(); } inline Vec3 createRandomNonzeroVec3() { return createRandomNonzeroVecTemplate(); } inline Rotation createRandomRotation() { return createRandomRotationTemplate(); } inline CFrame createRandomCFrame() { return createRandomCFrameTemplate(); } inline TranslationalMotion createRandomTranslationalMotion() { return TranslationalMotion(createRandomNonzeroVec3(), createRandomNonzeroVec3()); } inline RotationalMotion createRandomRotationalMotion() { return RotationalMotion(createRandomNonzeroVec3(), createRandomNonzeroVec3()); } inline Motion createRandomMotion() { return Motion(createRandomTranslationalMotion(), createRandomRotationalMotion()); } inline RelativeMotion createRandomRelativeMotion() { return RelativeMotion(createRandomMotion(), createRandomCFrame()); } template inline Derivatives createRandomDerivatives() { Derivatives result; for(T& item : result) { item = createFunc(); } return result; } template inline TaylorExpansion createRandomTaylorExpansion() { return TaylorExpansion{createRandomDerivatives()}; } template inline FullTaylorExpansion createRandomFullTaylorExpansion() { return FullTaylorExpansion{createRandomDerivatives()}; } template inline std::array createRandomArray() { std::array result; for(T& item : result) { item = createFunc(); } return result; } }; ================================================ FILE: tests/rotationTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "testValues.h" #include #include #include #include #include #include #include #include using namespace P3D; #define ASSERT(condition) ASSERT_TOLERANT(condition, 0.00000001) #define FOR_XYZ(start, end, delta) for(auto x = start; x < end; x += delta) for(auto y = start; y < end; y += delta) for(auto z = start; z < end; z += delta) TEST_CASE(testFromRotationVecInvertsFromRotationMatrix) { FOR_XYZ(-1.55, 1.55, 0.13) { Vec3 v(x, y, z); Rotation rot = Rotation::fromRotationVector(v); Vec3 resultingVec = rot.asRotationVector(); // logStream << "v = " << v << "\n rot = " << rot << " resultingVec = " << resultingVec; ASSERT(v == resultingVec); Rotation m = Rotation::fromEulerAngles(x, y, z); Vec3 rotVec = m.asRotationVector(); Rotation resultingRot = Rotation::fromRotationVector(rotVec); // logStream << "m = " << m << "\n rotVec = " << rotVec << " resultingMat = " << resultingMat; ASSERT(m == resultingRot); } } TEST_CASE(rotationGlobalToLocalInvertsLocalToGlobal) { FOR_XYZ(-1.55, 1.55, 0.13) { Rotation rotToTest = Rotation::fromEulerAngles(x, y, z); Vec3 testVec(0.7, -0.5, 0.3); ASSERT(rotToTest.localToGlobal(rotToTest.globalToLocal(testVec)) == testVec); ASSERT(rotToTest.globalToLocal(rotToTest.localToGlobal(testVec)) == testVec); Rotation testRot = Rotation::fromEulerAngles(0.5, 0.4, 0.5); ASSERT(rotToTest.localToGlobal(rotToTest.globalToLocal(testRot)) == testRot); ASSERT(rotToTest.globalToLocal(rotToTest.localToGlobal(testRot)) == testRot); SymmetricMat3 testMat{ 0.7, 0.3, 0.8, -0.2, 0.4, -1.0, }; ASSERT(rotToTest.localToGlobal(rotToTest.globalToLocal(testMat)) == testMat); ASSERT(rotToTest.globalToLocal(rotToTest.localToGlobal(testMat)) == testMat); } } TEST_CASE(rotationGlobalToLocalMatrixIdentity) { FOR_XYZ(-1.55, 1.55, 0.13) { Rotation rotToTest = Rotation::fromEulerAngles(x, y, z); Vec3 testVec(0.7, -0.5, 0.3); SymmetricMat3 testMat{ 0.7, 0.3, 0.8, -0.2, 0.4, -1.0, }; ASSERT(rotToTest.localToGlobal(testMat * testVec) == rotToTest.localToGlobal(testMat) * rotToTest.localToGlobal(testVec)); ASSERT(rotToTest.globalToLocal(testMat * testVec) == rotToTest.globalToLocal(testMat) * rotToTest.globalToLocal(testVec)); } } TEST_CASE(rotationAssociative) { FOR_XYZ(-1.55, 1.55, 0.13) { Rotation rotToTest1 = Rotation::fromEulerAngles(x, y, z); Rotation rotToTest2 = Rotation::fromEulerAngles(-0.17, 0.26, -0.247); Vec3 testVec(0.7, -0.5, 0.3); Rotation testRot = Rotation::fromEulerAngles(0.5, 0.4, 0.5); SymmetricMat3 testMat{ 0.7, 0.3, 0.8, -0.2, 0.4, -1.0, }; ASSERT(rotToTest1.localToGlobal(rotToTest2.localToGlobal(testVec)) == rotToTest1.localToGlobal(rotToTest2).localToGlobal(testVec)); ASSERT(rotToTest1.localToGlobal(rotToTest2.localToGlobal(testRot)) == rotToTest1.localToGlobal(rotToTest2).localToGlobal(testRot)); ASSERT(rotToTest1.localToGlobal(rotToTest2.localToGlobal(testMat)) == rotToTest1.localToGlobal(rotToTest2).localToGlobal(testMat)); } } TEST_CASE(rotXrotYrotZ) { FOR_XYZ(-1.5, 1.5, 0.1) { double angle = x; Vec2 a(y, z); Mat2 rotMat{ std::cos(angle), -std::sin(angle), std::sin(angle), std::cos(angle) }; Vec2 b = rotMat * a; double offset = 0.57; ASSERT(Rotation::rotX(angle) * Vec3(offset, a.x, a.y) == Vec3(offset, b.x, b.y)); ASSERT(Rotation::rotY(angle) * Vec3(a.y, offset, a.x) == Vec3(b.y, offset, b.x)); ASSERT(Rotation::rotZ(angle) * Vec3(a.x, a.y, offset) == Vec3(b.x, b.y, offset)); } } TEST_CASE(faceDirection) { FOR_XYZ(-1.5, 1.5, 0.1) { if(x == 0.0 && y == 0.0 && z == 0.0) continue; Vec3 faceDirection = normalize(Vec3(x, y, z)); ASSERT(Rotation::faceX(faceDirection) * Vec3(1.0, 0.0, 0.0) == faceDirection); ASSERT(Rotation::faceY(faceDirection) * Vec3(0.0, 1.0, 0.0) == faceDirection); ASSERT(Rotation::faceZ(faceDirection) * Vec3(0.0, 0.0, 1.0) == faceDirection); } } TEST_CASE(rotationMatrix) { FOR_XYZ(-1.55, 1.55, 0.1) { Mat3 mat = rotationMatrixfromEulerAngles(x, y, z); Rotation rot = Rotation::fromEulerAngles(x, y, z); ASSERT(rot == Rotation::fromRotationMatrix(mat)); ASSERT(mat == rot.asRotationMatrix()); } } TEST_CASE(rotationQuaternionNegative) { FOR_XYZ(-1.55, 1.55, 0.1) { QuaternionRotationTemplate quatRot = QuaternionRotationTemplate::fromEulerAngles(x, y, z); Quat4 q1 = quatRot.asRotationQuaternion(); Quat4 q2 = -q1; QuaternionRotationTemplate rq1 = QuaternionRotationTemplate::fromRotationQuaternion(q1); QuaternionRotationTemplate rq2 = QuaternionRotationTemplate::fromRotationQuaternion(q2); ASSERT(rq1.asRotationVector() == rq2.asRotationVector()); ASSERT(rq1.asRotationMatrix() == rq2.asRotationMatrix()); } } TEST_CASE(quaternionFromRotVecInverse) { // 1.55 to try to stay below a length of PI, since otherwise duplicate vectors will emerge FOR_XYZ(-1.55, 1.55, 0.1) { Vec3 rotVec = Vec3(x, y, z); ASSERT(rotVec == rotationVectorFromRotationQuaternion(rotationQuaternionFromRotationVec(rotVec))); } } TEST_CASE(rotationImplementationIdenticalFromEulerAngles) { FOR_XYZ(1.55, 1.55, 0.1) { QuaternionRotationTemplate quatRot = QuaternionRotationTemplate::fromEulerAngles(x, y, z); MatrixRotationTemplate matRot = MatrixRotationTemplate::fromEulerAngles(x, y, z); ASSERT(quatRot.asRotationMatrix() == matRot.asRotationMatrix()); ASSERT(quatRot.asRotationQuaternion() == matRot.asRotationQuaternion()); ASSERT(quatRot.asRotationVector() == matRot.asRotationVector()); } } TEST_CASE(rotationImplementationIdenticalLocalGlobal) { FOR_XYZ(1.5, 1.5, 0.5) { QuaternionRotationTemplate quatRot = QuaternionRotationTemplate::fromEulerAngles(x, y, z); MatrixRotationTemplate matRot = MatrixRotationTemplate::fromEulerAngles(x, y, z); FOR_XYZ(1.5, 1.5, 0.5) { Vec3 vec = Vec3(x, y, z); ASSERT(quatRot.localToGlobal(vec) == matRot.localToGlobal(vec)); ASSERT(quatRot.globalToLocal(vec) == matRot.globalToLocal(vec)); } FOR_XYZ(1.5, 1.5, 0.5) { QuaternionRotationTemplate quatRot2 = QuaternionRotationTemplate::fromEulerAngles(x, y, z); MatrixRotationTemplate matRot2 = MatrixRotationTemplate::fromEulerAngles(x, y, z); ASSERT(quatRot.localToGlobal(quatRot2).asRotationMatrix() == matRot.localToGlobal(matRot2).asRotationMatrix()); ASSERT(quatRot.globalToLocal(quatRot2).asRotationMatrix() == matRot.globalToLocal(matRot2).asRotationMatrix()); ASSERT((quatRot * quatRot2).asRotationMatrix() == (matRot * matRot2).asRotationMatrix()); } SymmetricMat3 mat{ 1.3, 0.4, -2.1, -1.2, 0.1, 0.7 }; ASSERT(quatRot.localToGlobal(mat) == matRot.localToGlobal(mat)); ASSERT(quatRot.globalToLocal(mat) == matRot.globalToLocal(mat)); } } TEST_CASE(rotationImplementationIdenticalInverse) { FOR_XYZ(1.5, 1.5, 0.5) { QuaternionRotationTemplate quatRot = QuaternionRotationTemplate::fromEulerAngles(x, y, z); MatrixRotationTemplate matRot = MatrixRotationTemplate::fromEulerAngles(x, y, z); ASSERT((~quatRot).asRotationMatrix() == (~matRot).asRotationMatrix()); } } TEST_CASE(rotationImplementationIdenticalFaceMatrices) { FOR_XYZ(1.5, 1.5, 0.5) { Vec3 faceDir = Vec3(x, y, z); ASSERT(MatrixRotationTemplate::faceX(faceDir).asRotationMatrix() == QuaternionRotationTemplate::faceX(faceDir).asRotationMatrix()); ASSERT(MatrixRotationTemplate::faceY(faceDir).asRotationMatrix() == QuaternionRotationTemplate::faceY(faceDir).asRotationMatrix()); ASSERT(MatrixRotationTemplate::faceZ(faceDir).asRotationMatrix() == QuaternionRotationTemplate::faceZ(faceDir).asRotationMatrix()); } } TEST_CASE(rotationImplementationIdenticalGetXYZ) { FOR_XYZ(1.5, 1.5, 0.5) { MatrixRotationTemplate matRot = MatrixRotationTemplate::fromEulerAngles(x, y, z); QuaternionRotationTemplate quatRot = QuaternionRotationTemplate::fromEulerAngles(x, y, z); ASSERT(matRot.getX() == quatRot.getX()); ASSERT(matRot.getY() == quatRot.getY()); ASSERT(matRot.getZ() == quatRot.getZ()); ASSERT(matRot.getX() == matRot.localToGlobal(Vec3(1, 0, 0))); ASSERT(matRot.getY() == matRot.localToGlobal(Vec3(0, 1, 0))); ASSERT(matRot.getZ() == matRot.localToGlobal(Vec3(0, 0, 1))); ASSERT(quatRot.getX() == quatRot.localToGlobal(Vec3(1, 0, 0))); ASSERT(quatRot.getY() == quatRot.localToGlobal(Vec3(0, 1, 0))); ASSERT(quatRot.getZ() == quatRot.localToGlobal(Vec3(0, 0, 1))); } } ================================================ FILE: tests/simulation.h ================================================ #pragma once #include #include #include #include #include #include namespace P3D { template std::array computeTranslationOverTime(Vec3 start, TaylorExpansion derivatives, double deltaT) { std::array result; for(std::size_t i = 0; i < Size + 1; i++) { result[i] = start + derivatives(deltaT * i); } return result; } template std::array computeRotationOverTime(Rotation start, TaylorExpansion derivatives, double deltaT) { std::array result; for(std::size_t i = 0; i < Size + 1; i++) { result[i] = Rotation::fromRotationVector(derivatives(deltaT * i)) * start; } return result; } inline std::array computeTranslationOverTime(Vec3 start, TranslationalMotion motion, double deltaT) { return computeTranslationOverTime(start, motion.translation, deltaT); } inline std::array computeRotationOverTime(Rotation start, RotationalMotion motion, double deltaT) { return computeRotationOverTime(start, motion.rotation, deltaT); } inline std::array computeCFrameOverTime(CFrame start, Motion motion, double deltaT) { std::array result; for(std::size_t i = 0; i < 3; i++) { double t = deltaT * i; Vec3 offset = start.getPosition() + motion.translation.getOffsetAfterDeltaT(t); Rotation rot = Rotation::fromRotationVector(motion.rotation.getRotationAfterDeltaT(t)) * start.getRotation(); result[i] = CFrame(offset, rot); } return result; } template std::array computeOverTime(const TaylorExpansion& taylor, double deltaT) { std::array result; for(std::size_t i = 0; i < Size; i++) { result[i] = taylor(deltaT * i); } return result; } template std::array computeOverTime(const FullTaylorExpansion& taylor, double deltaT) { std::array result; for(std::size_t i = 0; i < Size; i++) { result[i] = taylor(deltaT * i); } return result; } template inline FullTaylorExpansion estimateDerivatives(const std::array& points, double deltaT) { struct Recurse { static T estimateLastDerivative(const std::array& points, std::size_t index, std::size_t size, double invDT) { if(size == 0) { return points[index]; } else { T delta = estimateLastDerivative(points, index + 1, size - 1, invDT) - estimateLastDerivative(points, index, size - 1, invDT); return delta * invDT; } } }; FullTaylorExpansion result; double invDT = 1.0 / deltaT; for(std::size_t i = 0; i < Size; i++) { result.derivs[i] = Recurse::estimateLastDerivative(points, 0, i, invDT); } return result; } //template //inline FullTaylorExpansion estimateDerivatives(const std::array& points, double deltaT) { // return FullTaylorExpansion{points[0], (points[1] - points[0]) / deltaT, (points[0] + points[2] - points[1] * 2.0) / (deltaT * deltaT)}; //} }; ================================================ FILE: tests/testFrameworkConsistencyTests.cpp ================================================ #include "testsMain.h" #include "compare.h" #include "testValues.h" #include "simulation.h" #include "randomValues.h" #include #include #include using namespace P3D; #define DELTA_T 0.005 TEST_CASE(testEstimateDerivativesOfComputeOverTime) { FullTaylorExpansion startTaylor = createRandomFullTaylorExpansion(); logStream << "startTaylor: " << startTaylor << "\n"; std::array points = computeOverTime(startTaylor, DELTA_T); FullTaylorExpansion endTaylor = estimateDerivatives(points, DELTA_T); ASSERT_TOLERANT(startTaylor == endTaylor, 0.01); } ================================================ FILE: tests/testValues.cpp ================================================ #include "testValues.h" #include namespace P3D { const Vec3 vectors[13]{ Vec3(0.0, 0.0, 0.0), Vec3(1.0, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(0.0, 0.0, 1.0), Vec3(-1.0, 0.0, 0.0), Vec3(0.0, -1.0, 0.0), Vec3(0.0, 0.0, -1.0), Vec3(2.3, 7.6, 0.1), Vec3(1000.3, 934.2, 3.401), Vec3(2.6, 0.65, 1.7), Vec3(-2.32, 0.0000012, 3.8), Vec3(-5.3, -4.3, -3.3), Vec3(-1000, -1000, -1000), }; const Vec3 unitVectors[12]{ Vec3(1.0, 0.0, 0.0), Vec3(0.0, 1.0, 0.0), Vec3(0.0, 0.0, 1.0), Vec3(-1.0, 0.0, 0.0), Vec3(0.0, -1.0, 0.0), Vec3(0.0, 0.0, -1.0), normalize(Vec3(2.3, 7.6, 0.1)), normalize(Vec3(1000.3, 934.2, 3.401)), normalize(Vec3(2.6, 0.65, 1.7)), normalize(Vec3(-2.32, 0.0000012, 3.8)), normalize(Vec3(-5.3, -4.3, -3.3)), normalize(Vec3(-1000, -1000, -1000)), }; const Mat3 matrices[9]{ Mat3{0,0,0,0,0,0,0,0,0}, Mat3{1,0,0,0,1,0,0,0,1}, Mat3{0,1,0,0,0,1,0,0,0}, Mat3{0,0,1,0,1,0,1,0,0}, Mat3{0,0,0,1,0,0,0,1,0}, Mat3{1,0,0,0,0,0,0,0,0}, Mat3{1,1,1,1,1,1,1,1,1}, Mat3{5.3, 0,0,0, 7.3, 0,0,0, 2.3}, Mat3{3.7, 2.3, -3.5, 1.3, -2.5, 1.6, -3.2, 2.3, 3.4}, }; const Rotation rotations[19]{ Rotation(), Rotation::rotX(0.8), Rotation::rotY(0.8), Rotation::rotZ(0.8), Rotation::rotX(-0.8), Rotation::rotY(-0.8), Rotation::rotZ(-0.8), Rotation::fromEulerAngles(0.2, 0.3, 0.4), Rotation::fromEulerAngles(1.2, -0.5, 0.3), Rotation::Predefined::IDENTITY, Rotation::Predefined::X_90, Rotation::Predefined::X_180, Rotation::Predefined::X_270, Rotation::Predefined::Y_90, Rotation::Predefined::Y_180, Rotation::Predefined::Y_270, Rotation::Predefined::Z_90, Rotation::Predefined::Z_180, Rotation::Predefined::Z_270, }; Vec3f badVerticesI[8]{ {3.7427008831009605f, 0.19949999999997348f, -2.8372588570310806f}, {3.7427008831009791f, -0.00050000000002671519f, -2.8372588570310575f}, {4.7430976602077610f, -0.00050000000004078726f, -3.8166496311117655f}, {4.7430976602077424f, 0.19949999999995938f, -3.8166496311117886f}, {4.1624397862784015f, 0.19950000000005996f, -2.4085173811281768f}, {4.1624397862784202f, -0.00049999999994022881f, -2.4085173811281537f}, {5.1628365633852020f, -0.00049999999995430089f, -3.3879081552088617f}, {5.1628365633851834f, 0.19950000000004586f, -3.3879081552088848f}, }; Vec3f badVerticesJ[8]{ {5.1677962452787920f, -0.00049999999985722576f, -3.3185062700045100f}, {5.1677962452787707f, 0.19950000000014328f, -3.3185062700046259f}, {6.5642349861367375f, 0.19950000000024037f, -3.4183002729857077f}, {6.5642349861367588f, -0.00049999999976013676f, -3.4183002729855918f}, {5.1250273868583260f, -0.00050000000020963831f, -3.9169800160865020f}, {5.1250273868583047f, 0.19949999999979087f, -3.9169800160866179f}, {6.5214661277162715f, 0.19949999999988796f, -4.0167740190677002f}, {6.5214661277162929f, -0.00050000000011254930f, -4.0167740190675838f} }; }; ================================================ FILE: tests/testValues.h ================================================ #pragma once #include #include #include namespace P3D { extern const Vec3 vectors[13]; extern const Vec3 unitVectors[12]; extern const Mat3 matrices[9]; extern const Rotation rotations[19]; // test for bad vertices extern Vec3f badVerticesI[8]; extern Vec3f badVerticesJ[8]; }; ================================================ FILE: tests/tests.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 15.0 {874CA9E0-23D2-4B91-837B-BCE8B7C9668D} tests 10.0 Application true v142 MultiByte Application false v142 true MultiByte Application true v142 MultiByte Application false v142 true MultiByte Level3 Disabled true true engine.lib;graphics.lib;physics.lib;util.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 Disabled true true $(SolutionDir)include;$(SolutionDir) NotSet stdcpp17 true $(SolutionDir)lib;$(OutDir) engine.lib;graphics.lib;Physics3D.lib;util.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true true true true true engine.lib;graphics.lib;physics.lib;util.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true true true $(SolutionDir)include;$(SolutionDir) NotSet _MBCS;NDEBUG;%(PreprocessorDefinitions) stdcpp17 true true true $(SolutionDir)lib;$(OutDir) engine.lib;graphics.lib;Physics3D.lib;util.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) ================================================ FILE: tests/testsMain.cpp ================================================ #include "testsMain.h" #include #include #include #include #include #include #include "stdarg.h" #include #include "../util/terminalColor.h" #include "../util/parseCPUIDArgs.h" #include "../util/cmdParser.h" #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) static const char sepChar = '\\'; #else static const char sepChar = '/'; #endif using namespace std; using namespace chrono; static const TerminalColor SUCCESS_COLOR = TerminalColor::GREEN; static const TerminalColor FAILURE_COLOR = TerminalColor::RED; static const TerminalColor ERROR_COLOR = TerminalColor::MAGENTA; static const TerminalColor SKIP_COLOR = TerminalColor::LIGHT_GRAY; stringstream logStream; thread_local TestInterface __testInterface; // a referenceable boolean, for use in AssertComparer and TolerantAssertComparer // using just a literal true would cause bugs as the literal falls out of scope after the return, leading to unpredictable results const bool reffableTrue = true; static void resetLog() { logStream.str(""); logStream.clear(); } char logBuffer[1 << 16]; void logf(const char* format, ...) { va_list args; va_start(args, format); int length = std::vsnprintf(logBuffer, 1 << 16, format, args); va_end(args); logStream << logBuffer << '\n'; } static void printDeltaTime(time_point startTime, TerminalColor c) { duration delta = system_clock::now() - startTime; setColor(c); cout << " (" << fixed << delta.count() << "s)\n"; } static ifstream getFileStream(const char* fileName) { ifstream s(fileName); if(s.good()) return s; string path = string("tests") + sepChar + fileName; s = ifstream(path); if(s.good()) return s; path = string("..") + sepChar + path; s = ifstream(path); if(s.good()) return s; return ifstream(string("..") + sepChar + path); } #define SHOW_LINES_BEFORE 5 #define SHOW_LINES_AFTER 2 static void printFileSlice(const char* fileName, int line) { ifstream inFile = getFileStream(fileName); int t = 0; if(inFile.good()) { // skip lines until right before first line to show for(int i = 0; i < line - SHOW_LINES_BEFORE - 1; i++) { string l; getline(inFile, l); } setColor(TerminalColor::WHITE); string s; for(int i = -SHOW_LINES_BEFORE; i <= SHOW_LINES_AFTER; i++) { if(!getline(inFile, s)) break; if(i == 0) { setColor(TerminalColor::YELLOW); } printf("%d: %s", line + i, s.c_str()); if(i == 0) { cout << " <<<<"; setColor(TerminalColor::WHITE); } cout << endl; } } else { setColor(TerminalColor::WHITE); printf("Could not open File %s for debugging :(\n", fileName); } } static void dumpLog() { setColor(TerminalColor::GREEN); cout << logStream.str().c_str(); } enum class TestResult { SUCCESS = 0, FAILURE = 1, ERROR = 2, SKIP = 3 }; struct TestFlags { bool coverageEnabled; bool allowSkip; bool catchErrors; bool debugOnFailure; }; class Test { public: const char* filePath; const char* fileName; const char* funcName; TestType type; void(*testFunc)(); Test() : filePath(nullptr), funcName(nullptr), fileName(nullptr), testFunc(nullptr), type(TestType::NORMAL) {}; Test(const char* filePath, const char* funcName, void(*testFunc)(), TestType type) : filePath(filePath), funcName(funcName), testFunc(testFunc), type(type), fileName(std::strrchr(this->filePath, sepChar) ? std::strrchr(this->filePath, sepChar) + 1 : this->filePath) {} private: TestResult runNoErrorChecking(TestFlags flags, time_point& startTime) { __testInterface = TestInterface(flags.debugOnFailure); try { startTime = system_clock::now(); testFunc(); std::size_t assertCount = __testInterface.getAssertCount(); setColor(assertCount > 0 ? TerminalColor::GRAY : TerminalColor::RED); cout << " [" << assertCount << "]"; printDeltaTime(startTime, SUCCESS_COLOR); return TestResult::SUCCESS; } catch(AssertionError& e) { printDeltaTime(startTime, FAILURE_COLOR); dumpLog(); setColor(TerminalColor::RED); printf("An assertion was incorrect at line %d:\n", e.line); printFileSlice(filePath, e.line); setColor(TerminalColor::YELLOW); cout << e.what() << endl; setColor(TerminalColor::WHITE); return TestResult::FAILURE; } } public: TestResult run(TestFlags flags) { resetLog(); setColor(TerminalColor::CYAN); cout << fileName << ":" << funcName; if(flags.allowSkip && type == TestType::SLOW) { setColor(SKIP_COLOR); cout << " (skip)\n"; return TestResult::SKIP; } time_point startTime; if(!flags.catchErrors) { return runNoErrorChecking(flags, startTime); } else { try { return runNoErrorChecking(flags, startTime); } catch(exception& e) { printDeltaTime(startTime, ERROR_COLOR); dumpLog(); setColor(TerminalColor::RED); printf("An general error was thrown: %s\n", e.what()); return TestResult::ERROR; } catch(string& e) { printDeltaTime(startTime, ERROR_COLOR); dumpLog(); setColor(TerminalColor::RED); printf("An string exception was thrown: %s\n", e.c_str()); return TestResult::ERROR; } catch(const char* ex) { printDeltaTime(startTime, ERROR_COLOR); dumpLog(); setColor(TerminalColor::RED); printf("A char* exception was thrown: %s\n", ex); return TestResult::ERROR; } catch(...) { printDeltaTime(startTime, ERROR_COLOR); dumpLog(); setColor(TerminalColor::RED); printf("An unknown exception was thrown\n"); return TestResult::ERROR; } } } }; AssertionError::AssertionError(int line, const char* info) : line(line), info(info) {} const char* AssertionError::what() const noexcept { return info; } // For some reason having this in static memory breaks it, a pointer seems to work vector* tests = nullptr; #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN #include HANDLE console; static void initConsole() { console = GetStdHandle(STD_OUTPUT_HANDLE); HWND consoleWindow = GetConsoleWindow(); RECT r; GetWindowRect(consoleWindow, &r); MoveWindow(consoleWindow, r.left, r.top, 800, 800, TRUE); } #else static void initConsole() {} #endif static bool isCoveredBy(Test& test, const std::vector& filters) { if(filters.size() == 0) { return true; } for(const std::string& filter : filters) { if(filter == test.fileName || filter + ".cpp" == test.fileName || filter == test.funcName || filter == test.fileName + std::string(":") + test.funcName) { return true; } } return false; } static void runTests(const std::vector& filter, TestFlags flags) { setColor(TerminalColor::WHITE); cout << "Starting tests: "; setColor(SUCCESS_COLOR); cout << "[SUCCESS] "; setColor(FAILURE_COLOR); cout << "[FAILURE] "; setColor(ERROR_COLOR); cout << "[ERROR] "; setColor(SKIP_COLOR); cout << "[SKIP]\n"; setColor(TerminalColor::WHITE); cout << "Number of tests: " << tests->size() << endl; int totalTestsRan = 0; int resultCounts[4]{0,0,0,0}; for(Test& t : *tests) { if(isCoveredBy(t, filter)) { TestResult result = t.run(flags); if(result != TestResult::SKIP) totalTestsRan++; resultCounts[static_cast(result)]++; } } setColor(TerminalColor::WHITE); cout << "Tests finished! Ran " << totalTestsRan << "/" << tests->size() << " tests\n"; setColor(SUCCESS_COLOR); cout << resultCounts[0] << " SUCCESS\n"; setColor(FAILURE_COLOR); cout << resultCounts[1] << " FAILURE\n"; setColor(ERROR_COLOR); cout << resultCounts[2] << " ERROR\n"; setColor(SKIP_COLOR); cout << resultCounts[3] << " SKIP" << endl; } TestFlags getTestFlags(const Util::ParsedArgs& cmdArgs) { TestFlags result; result.coverageEnabled = cmdArgs.hasFlag("coverage"); result.catchErrors = !cmdArgs.hasFlag("nocatch"); result.allowSkip = cmdArgs.argCount() == 0; result.debugOnFailure = cmdArgs.hasFlag("debug"); if(cmdArgs.hasFlag("noskip")) result.allowSkip = false; return result; } int main(int argc, const char** argv) { initConsole(); Util::ParsedArgs cmdArgs(argc, argv); std::cout << Util::printAndParseCPUIDArgs(cmdArgs).c_str() << "\n"; TestFlags flags = getTestFlags(cmdArgs); runTests(cmdArgs.args(), flags); if(flags.coverageEnabled) return 0; while(true) { string input; setColor(TerminalColor::WHITE); cout << "> "; cin >> input; if(input == "") { continue; } else if(input == "exit") { break; } else { flags.allowSkip = false; runTests(std::vector{input}, flags); } } } static void logAssertError(string text) { setColor(TerminalColor::RED); cout << text.c_str(); } TestAdder::TestAdder(const char* file, const char* name, void(*f)(), TestType isSlow) { if(tests == nullptr) tests = new vector(); tests->push_back(Test(file, name, f, isSlow)); } ================================================ FILE: tests/testsMain.h ================================================ #pragma once #include "compare.h" #include #include class TestInterface { size_t assertCount = 0; public: bool debugOnFailure; TestInterface() : debugOnFailure(false) {} TestInterface(bool debugOnFailure) : debugOnFailure(debugOnFailure) {} inline void markAssert() { assertCount++; } inline size_t getAssertCount() const { return assertCount; } }; enum class TestType { NORMAL, SLOW }; #define __JOIN2(a,b) a##b #define __JOIN(a,b) __JOIN2(a,b) #define TEST_CASE(func) void func(); static TestAdder __JOIN(tAdder, __LINE__)(__FILE__, #func, func, TestType::NORMAL); void func() #define TEST_CASE_SLOW(func) void func(); static TestAdder __JOIN(tAdder, __LINE__)(__FILE__, #func, func, TestType::SLOW); void func() struct TestAdder { TestAdder(const char* filePath, const char* nameName, void(*testFunc)(), TestType isSlow); }; class AssertionError { const char* info; public: int line; AssertionError(int line, const char* info); const char* what() const noexcept; }; void logf(const char* format, ...); extern std::stringstream logStream; extern thread_local TestInterface __testInterface; extern const bool reffableTrue; // Testing utils: template const char* errMsg(const R& first, const P& second, const char* sep) { std::stringstream s; s << first; s << ' '; s << sep; s << '\n'; s << second; std::string msg = s.str(); const char* data = msg.c_str(); char* dataBuf = new char[msg.size() + 1]; for(int i = 0; i < msg.size() + 1; i++) dataBuf[i] = data[i]; return dataBuf; } template const char* errMsg(const R& first) { std::stringstream s; s << "("; s << first; s << ")"; std::string msg = s.str(); const char* data = msg.c_str(); char* dataBuf = new char[msg.size() + 1]; for(int i = 0; i < msg.size() + 1; i++) dataBuf[i] = data[i]; return dataBuf; } #ifdef _MSC_VER #define __DEBUG_BREAK __debugbreak(); #else #define __DEBUG_BREAK #endif #define __ASSERT_FAILURE(line, printText) if(__testInterface.debugOnFailure) {__DEBUG_BREAK} throw AssertionError(line, printText) template class AssertComparer { public: const int line; const T& arg; AssertComparer(int line, const T& arg) : line(line), arg(arg) {} template AssertComparer operator<(const P& other) const { if(!(arg < other)) { __ASSERT_FAILURE(line, errMsg(arg, other, "<")); }; return AssertComparer(this->line, reffableTrue); } template AssertComparer operator>(const P& other) const { if(!(arg > other)) { __ASSERT_FAILURE(line, errMsg(arg, other, ">")); }; return AssertComparer(this->line, reffableTrue); } template AssertComparer operator<=(const P& other) const { if(!(arg <= other)) { __ASSERT_FAILURE(line, errMsg(arg, other, "<=")); }; return AssertComparer(this->line, reffableTrue); } template AssertComparer operator>=(const P& other) const { if(!(arg >= other)) { __ASSERT_FAILURE(line, errMsg(arg, other, ">=")); }; return AssertComparer(this->line, reffableTrue); } template AssertComparer operator==(const P& other) const { if(!(arg == other)) { __ASSERT_FAILURE(line, errMsg(arg, other, "==")); }; return AssertComparer(this->line, reffableTrue); } template AssertComparer operator!=(const P& other) const { if(!(arg != other)) { __ASSERT_FAILURE(line, errMsg(arg, other, "!=")); }; return AssertComparer(this->line, reffableTrue); } }; template class TolerantAssertComparer { public: const int line; const T& arg; const Tol tolerance; TolerantAssertComparer(int line, const T& arg, Tol tolerance) : line(line), arg(arg), tolerance(tolerance) {} template TolerantAssertComparer operator<(const T2& other) const { if(!tolerantLessThan(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, "<")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } template TolerantAssertComparer operator>(const T2& other) const { if(!tolerantGreaterThan(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, ">")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } template TolerantAssertComparer operator<=(const T2& other) const { if(!tolerantLessOrEqual(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, "<=")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } template TolerantAssertComparer operator>=(const T2& other) const { if(!tolerantGreaterOrEqual(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, ">=")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } template TolerantAssertComparer operator==(const T2& other) const { if(!tolerantEquals(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, "==")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } template TolerantAssertComparer operator!=(const T2& other) const { if(!tolerantNotEquals(arg, other, tolerance)) { __ASSERT_FAILURE(line, errMsg(arg, other, "!=")); }; return TolerantAssertComparer(this->line, reffableTrue, this->tolerance); } }; struct AssertBuilder { int line; AssertBuilder(int line) : line(line) {}; template AssertComparer operator<(const T& other) const { return AssertComparer(line, other); } }; template struct TolerantAssertBuilder { int line; Tol tolerance; TolerantAssertBuilder(int line, Tol tolerance) : line(line), tolerance(tolerance) {}; template TolerantAssertComparer operator<(const T& other) const { return TolerantAssertComparer(line, other, tolerance); } }; #define ASSERT_STRICT(condition) do {if(!(AssertBuilder(__LINE__) < condition).arg) {__ASSERT_FAILURE(__LINE__, "false");}__testInterface.markAssert(); }while(false) #define ASSERT_TOLERANT(condition, tolerance) do {if(!(TolerantAssertBuilder(__LINE__, tolerance) < condition).arg) {__ASSERT_FAILURE(__LINE__, "false");} __testInterface.markAssert(); }while(false) #define ASSERT_TRUE(condition) do {if(!(condition)) {__ASSERT_FAILURE(__LINE__, "false");}__testInterface.markAssert(); }while(false) #define ASSERT_FALSE(condition) do {if(condition) {__ASSERT_FAILURE(__LINE__, "true");}__testInterface.markAssert(); }while(false) #define PREV_VAL_NAME __JOIN(____previousValue, __LINE__) #define ISFILLED_NAME __JOIN(____isFilled, __LINE__) #define REMAINS_CONSTANT_STRICT(value) do{\ static bool ISFILLED_NAME = false;\ static auto PREV_VAL_NAME = value;\ if(ISFILLED_NAME) ASSERT_STRICT(PREV_VAL_NAME == (value));\ ISFILLED_NAME = true;\ }while(false) #define REMAINS_CONSTANT_TOLERANT(value, tolerance) do{\ static bool ISFILLED_NAME = false;\ static auto PREV_VAL_NAME = value;\ if(ISFILLED_NAME) ASSERT_TOLERANT(PREV_VAL_NAME == (value), tolerance);\ ISFILLED_NAME = true;\ }while(false) ================================================ FILE: util/cmdParser.h ================================================ #pragma once #include #include namespace Util { class ParsedArgs { struct OptionalArg { std::string option; std::string value; }; std::vector inputArgs; std::vector flags; std::vector optionals; public: ParsedArgs(int argc, const char** argv) { for(int i = 1; i < argc; i++) { const char* item = argv[i]; if(item[0] == '-') { if(item[1] == '-') { if(i + 1 < argc) { optionals.push_back(OptionalArg{std::string(item + 2), std::string(argv[i + 1])}); i++; } else { throw "No associated value for the final optional arg"; } } else { flags.emplace_back(item + 1); } } else { inputArgs.emplace_back(item); } } } bool hasFlag(const char* flag) const { for(const std::string& f : flags) { if(f == flag) { return true; } } return false; } std::string getOptional(const char* key) const { for(const OptionalArg& o : optionals) { if(o.option == key) { return o.value; } } return std::string(); } std::size_t argCount() const { return inputArgs.size(); } const std::vector& args() { return inputArgs; } const std::string& operator[](std::size_t index) const { return inputArgs[index]; } auto begin() const { return inputArgs.begin(); } auto end() const { return inputArgs.end(); } }; }; ================================================ FILE: util/fileUtils.cpp ================================================ #include "fileUtils.h" #include "log.h" #include #include #ifdef _WIN32 #include #include bool Util::doesFileExist(const std::string& fileName) { struct stat buffer; if (stat(fileName.c_str(), &buffer) != -1) { return true; } return false; } std::string Util::getFullPath(const std::string& fileName) { TCHAR buf[MAX_PATH] = TEXT(""); TCHAR** lppPart = {NULL}; GetFullPathName(fileName.c_str(), MAX_PATH, buf, lppPart); return std::string(buf); } #else #include // for some reason gcc still does not support #if __GNUC__ >= 8 #include namespace fs = std::filesystem; #else #include namespace fs = std::experimental::filesystem; #endif bool Util::doesFileExist(const std::string& fileName) { return fs::exists(fileName); } std::string Util::getFullPath(const std::string& fileName) { char* path = realpath(fileName.c_str(), NULL); std::string result(path); free(path); return result; } #endif namespace Util { void warnIfFileExists(const std::string& fileName) { if (doesFileExist(fileName)) { Log::warn("File already exists: %s", fileName.c_str()); } } std::string parseFile(const std::string& path) { if (path.empty()) return std::string(); std::ifstream fileStream(path); Log::setDelimiter(""); Log::info("Reading (%s) ... ", path.c_str()); if (fileStream.fail() || !fileStream.is_open()) { Log::error("Fail"); Log::setDelimiter("\n"); return ""; } Log::debug("Done"); Log::setDelimiter("\n"); std::string line; std::stringstream stringStream; while (getline(fileStream, line)) stringStream << line << "\n"; fileStream.close(); return stringStream.str(); } }; ================================================ FILE: util/fileUtils.h ================================================ #pragma once #include namespace Util { bool doesFileExist(const std::string& fileName); void warnIfFileExists(const std::string& fileName); std::string parseFile(const std::string& path); std::string getFullPath(const std::string& filename); }; ================================================ FILE: util/iteratorUtils.h ================================================ #pragma once struct iterator_end {}; template class default_iterator { private: Iterator current; Iterator end; public: default_iterator() = default; default_iterator(const Iterator& start, const Iterator& end) : current(start), end(end) { } default_iterator& operator++() { ++current; return *this; } bool operator!=(IteratorEnd) const { return current != end; } decltype(*current)& operator*() const { return *current; } bool operator==(Iterator iterator) const { return current == iterator; } }; template class filter_iterator { private: Iterator current; Iterator end; Filter filter; public: filter_iterator() = default; filter_iterator(const Iterator& start, const Iterator& end, const Filter& filter) : current(start), end(end), filter(filter) { while (current != end && !filter(current)) ++current; } filter_iterator& operator++() { do { ++current; } while (current != end && !filter(current)); return *this; } bool operator!=(IteratorEnd) const { return current != end; } decltype(*current)& operator*() const { return *current; } bool operator==(Iterator iterator) const { return current == iterator; } }; template class transform_iterator { private: Iterator current; Iterator end; Transform transform; public: transform_iterator() = default; transform_iterator(const Iterator& start, const Iterator& end, const Transform& transform) : current(start), end(end), transform(transform) { } transform_iterator& operator++() { ++current; return *this; } bool operator!=(IteratorEnd) const { return current != end; } decltype(transform(current)) operator*() const { return transform(current); } bool operator==(Iterator iterator) const { return current == iterator; } }; template class filter_transform_iterator { private: Iterator current; Iterator end; Filter filter; Transform transform; public: filter_transform_iterator() = default; filter_transform_iterator(const Iterator& start, const Iterator& end, const Filter& filter, const Transform& transform) : current(start), end(end), filter(filter), transform(transform) { while (current != end && !filter(current)) ++current; } filter_transform_iterator& operator++() { do { ++current; } while (current != end && !filter(current)); return *this; } bool operator!=(IteratorEnd) const { return current != end; } decltype(transform(current)) operator*() const { return transform(current); } bool operator==(Iterator iterator) const { return current == iterator; } }; ================================================ FILE: util/log.cpp ================================================ #include "log.h" #include #include #include #include "terminalColor.h" namespace Log { static Level logLevel = Level::INFO; static std::stack subjects; static std::string lastSubject = ""; static bool newSubject = false; static std::string delimiter = "\n"; static FILE* logFile = nullptr; static void logTogether(const char* message, va_list args) { vprintf(message, args); if(logFile) { vfprintf(logFile, message, args); fflush(logFile); } } // the va_list must be recreated for both print and fprint #define va_logf(format, message) va_list args; va_start(args, format); vprintf(message.c_str(), args); va_end(args); if(logFile) {va_start(args, format); vfprintf(logFile, message.c_str(), args); va_end(args); fflush(logFile);} void init(const char* logFileName) { #ifdef _MSC_VER fopen_s(&logFile, logFileName, "w"); #else logFile = fopen(logFileName, "w"); #endif } void stop() { fclose(logFile); logFile = nullptr; // set to nullptr to prevent logs after program end from crashing the program by writing to an invalid file } std::string topSubject() { if (subjects.empty()) return ""; return subjects.top(); } subject::subject(const std::string& title) { if (subjects.empty()) { subjects.push(title); newSubject = title != lastSubject; } else { if (title == subjects.top()) { newSubject = false; } else if (title == lastSubject) { newSubject = false; } else { newSubject = true; } subjects.push(title); } } subject::~subject() { lastSubject = subjects.top(); subjects.pop(); newSubject = lastSubject != topSubject(); } bool emptySubject() { return topSubject().empty(); } void setLogLevel(Level level) { logLevel = level; } Level getLogLevel() { return logLevel; } static void printSubject(std::string subject) { setColor(Color::SUBJECT); printf(subject.c_str()); fprintf(logFile, subject.c_str()); fflush(logFile); setColor(Color::NORMAL); newSubject = false; } static void addSubjectIfNeeded() { if(newSubject && !topSubject().empty()) printSubject("\n[" + topSubject() + "]:\n"); if(!emptySubject()) printSubject("|\t"); } void setDelimiter(std::string delimiter) { Log::delimiter = delimiter; } void debug(std::string format, ...) { if (logLevel != Level::NONE) { addSubjectIfNeeded(); std::string message = "[DEBUG]: " + format + delimiter; setColor(Color::DEBUG); va_logf(format, message); setColor(Color::NORMAL); } } void info(std::string format, ...) { if (logLevel <= Level::INFO) { addSubjectIfNeeded(); std::string message = "[INFO]: " + format + delimiter; setColor(Color::INFO); va_logf(format, message); setColor(Color::NORMAL); } } void warn(std::string format, ...) { if (logLevel <= Level::WARNING) { addSubjectIfNeeded(); std::string message = "[WARN]: " + format + delimiter; setColor(Color::WARNING); va_logf(format, message); setColor(Color::NORMAL); } } void error(std::string format, ...) { if (logLevel <= Level::ERROR) { addSubjectIfNeeded(); std::string message = "[ERROR]: " + format + delimiter; setColor(Color::ERROR); va_logf(format, message); setColor(Color::NORMAL); } } void fatal(std::string format, ...) { if (logLevel <= Level::FATAL) { addSubjectIfNeeded(); std::string message = "[FATAL]: " + format + delimiter; setColor(Color::FATAL); va_logf(format, message); setColor(Color::NORMAL); } } void print(std::string format, ...) { setColor(Color::NORMAL); va_logf(format, format); } void print(TerminalColorPair color, std::string format, ...) { setColor(color); va_logf(format, format); } } ================================================ FILE: util/log.h ================================================ #pragma once #include #include "terminalColor.h" #define LOG_DEBUG(FMT, ...) Log::debug(std::string("[%s:%d] ") + FMT, __FUNCTION__, __LINE__, __VA_ARGS__); namespace Log { namespace Color { const TerminalColorPair DEBUG { TerminalColor::GREEN , TerminalColor::BLACK}; const TerminalColorPair INFO { TerminalColor::CYAN , TerminalColor::BLACK}; const TerminalColorPair WARNING { TerminalColor::YELLOW , TerminalColor::BLACK}; const TerminalColorPair ERROR { TerminalColor::RED , TerminalColor::BLACK}; const TerminalColorPair FATAL { TerminalColor::BLACK , TerminalColor::RED }; const TerminalColorPair NORMAL { TerminalColor::WHITE , TerminalColor::BLACK}; const TerminalColorPair SUBJECT { TerminalColor::MAGENTA, TerminalColor::BLACK}; } enum class Level : char { INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3, NONE = 4 }; class subject { public: subject(const std::string& title); ~subject(); subject(subject&& other) = delete; subject(const subject&) = delete; subject& operator=(subject&& other) = delete; subject& operator=(const subject&) = delete; }; void init(const char* logFile); void stop(); void setDelimiter(std::string delimiter); void debug(std::string format, ...); void info(std::string format, ...); void warn(std::string format, ...); void error(std::string format, ...); void fatal(std::string format, ...); void print(std::string format, ...); void print(TerminalColorPair color, std::string format, ...); void setLogLevel(Log::Level logLevel); Level getLogLevel(); }; ================================================ FILE: util/parseCPUIDArgs.h ================================================ #pragma once #include #include "cmdParser.h" #include namespace Util { inline std::string printAndParseCPUIDArgs(const ParsedArgs& cmdArgs) { std::string message("Detected CPU Technologies: "); for(size_t i = 0; i < P3D::CPUIDCheck::TECHNOLOGY_COUNT; i++) { if(P3D::CPUIDCheck::hasTechnology(1U << i)) message.append(P3D::CPUIDCheck::NAMES[i]).append(" "); } bool disabledSomething = false; for(int techI = 0; techI < P3D::CPUIDCheck::TECHNOLOGY_COUNT; techI++) { if(cmdArgs.hasFlag(P3D::CPUIDCheck::NAMES[techI])) { P3D::CPUIDCheck::disableTechnology(1 << techI); message.append("\nDisabled technology -").append(P3D::CPUIDCheck::NAMES[techI]); disabledSomething = true; } } if(disabledSomething) { message.append("\nEnabled CPU Technologies: "); for(size_t i = 0; i < P3D::CPUIDCheck::TECHNOLOGY_COUNT; i++) { if(P3D::CPUIDCheck::hasTechnology(1U << i)) message.append(P3D::CPUIDCheck::NAMES[i]).append(" "); } } return message; } inline std::string printAndParseCPUIDArgs(int argc, const char** argv) { return printAndParseCPUIDArgs(ParsedArgs(argc, argv)); } }; ================================================ FILE: util/properties.cpp ================================================ #include "properties.h" #include #include "stringUtil.h" #include "log.h" namespace Util { std::regex Properties::regex = std::regex(" *.+ *: *.+ *"); std::string Properties::get(const std::string& property) const { try { std::string value = properties.at(property); return value; } catch (const std::out_of_range&) { return ""; } } std::map Properties::get() { return properties; } void Properties::set(const std::string& property, const std::string& value) { if (properties.find(property) == properties.end()) properties.insert(std::make_pair(property, value)); else properties[property] = value; } void Properties::remove(const std::string& property) { for (auto iterator = properties.begin(); iterator != properties.end(); iterator++) { if (iterator->first == property) { properties.erase(iterator); return; } } } namespace PropertiesParser { Properties read(const std::string& file) { Log::subject s(file); std::ifstream inputstream; inputstream.open(file.c_str()); if (!inputstream.is_open()) { Log::error("Properties file can't be found"); return Properties(); } Properties properties(file); std::string line; while (getline(inputstream, line)) { line = trim(line); if (line.empty() || line.at(0) == '#') { continue; } else if (!std::regex_match(line, Properties::regex)) { Log::warn("Incorrect syntax: %s", line.c_str()); } else { size_t pos = line.find(':'); std::string property = rtrim(line.substr(0, pos)); std::string value = until(ltrim(line.substr(pos + 1, line.length())), ' '); properties.set(property, value); } } inputstream.close(); return properties; } void write(const std::string& filename, Properties& properties) { Log::subject s(filename); Log::info("Writing %d properties", properties.get().size()); std::ifstream ifile; ifile.open(filename.c_str(), std::ios::in); if (!ifile.is_open()) Log::error("Properties file can't be found, creating new one"); // Read and save std::string line; std::vector lines; while (getline(ifile, line)) { std::string trimmedLine = trim(line); if (trimmedLine.empty() || trimmedLine.at(0) == '#' || !std::regex_match(trimmedLine, Properties::regex)) { lines.push_back(line); continue; } else { size_t pos = line.find(':'); std::string property = line.substr(0, pos); std::string value = line.substr(pos + 1, line.length()); std::string trimmedProperty = rtrim(property); std::string trimmedValue = until(ltrim(value), ' '); std::string newTrimmedValue = properties.get(trimmedProperty); if (trimmedValue == newTrimmedValue) { lines.push_back(line); continue; } auto position = value.find(trimmedValue); std::string newValue = value.replace(position, position + trimmedValue.length(), newTrimmedValue); std::string newLine = property + ":" + newValue; lines.push_back(newLine); } } ifile.close(); // Write std::ofstream ofile; ofile.open(filename.c_str(), std::ios::out | std::ios::trunc); for (std::string& line : lines) ofile << line << std::endl; ofile.close(); } } }; ================================================ FILE: util/properties.h ================================================ #pragma once #include #include #include namespace Util { class Properties { private: std::string filename; std::map properties; public: static std::regex regex; inline Properties() {}; inline Properties(const std::string& filename) : filename(filename) {}; std::string get(const std::string& property) const; std::map get(); void set(const std::string& property, const std::string& value); void remove(const std::string& property); }; namespace PropertiesParser { Properties read(const std::string& file); void write(const std::string& file, Properties& properties); }; }; ================================================ FILE: util/resource/resource.cpp ================================================ #include "resource.h" #include "resourceManager.h" void Resource::setName(const std::string& name) { ResourceManager::onResourceNameChange(this, name); } void Resource::setPath(const std::string& path) { ResourceManager::onResourcePathChange(this, path); } std::string Resource::getName() const { return name; } std::string Resource::getPath() const { return path; } Resource::Resource(const std::string& path) { this->name = path; this->path = path; } Resource::Resource(const std::string& name, const std::string& path) { this->name = name; this->path = path; } ================================================ FILE: util/resource/resource.h ================================================ #pragma once #include class ResourceManager; #pragma region ResourceAllocator //! ResourceAllocator template class ResourceAllocator { friend ResourceManager; protected: ResourceAllocator() {}; public: virtual T* load(const std::string& name, const std::string& path) = 0; }; #pragma endregion #pragma region Resource //! Resource enum class ResourceType { None = 0, Mesh, Font, Texture, Shader, OBJ }; class Resource { friend ResourceManager; protected: std::string path; std::string name; Resource(const std::string& path); Resource(const std::string& name, const std::string& path); public: virtual ResourceType getType() const = 0; virtual std::string getTypeName() const = 0; virtual void close() = 0; std::string getName() const; std::string getPath() const; void setName(const std::string& name); void setPath(const std::string& path); template static ResourceAllocator getAllocator() { return T::getAllocator(); } }; #define DEFINE_RESOURCE(type, path) \ inline static std::string getDefaultPath() { return path; } \ inline static ResourceType getStaticType() { return ResourceType::type; } \ inline static std::string getStaticTypeName() { return #type; } \ inline virtual std::string getTypeName() const override { return getStaticTypeName(); } \ inline virtual ResourceType getType() const override { return getStaticType(); } #pragma endregion ================================================ FILE: util/resource/resourceDescriptor.h ================================================ #pragma once #include #ifdef _MSC_VER struct ResourceDescriptor { const int id; const std::string type; }; #else // ResourceDescriptors for reading from files struct ResourceDescriptor { const char* fileName; const std::string type; }; #endif ================================================ FILE: util/resource/resourceLoader.cpp ================================================ #include "resourceLoader.h" #include "resourceDescriptor.h" #include "../log.h" #ifdef _WIN32 #include class ResourceLoader { public: struct Parameters { std::size_t size = 0; void* pointer = nullptr; }; private: HRSRC hResource = nullptr; HGLOBAL hMemory = nullptr; Parameters parameters; public: ResourceLoader(int id, const std::string& type) { hResource = FindResource(nullptr, MAKEINTRESOURCEA(id), type.c_str()); hMemory = LoadResource(nullptr, hResource); parameters.size = SizeofResource(nullptr, hResource); parameters.pointer = LockResource(hMemory); } std::string getResourceString() const { std::string string; if (parameters.pointer != nullptr) string = std::string(reinterpret_cast(parameters.pointer), parameters.size); return string; } }; std::string getResourceAsString(const ResourceDescriptor* list, int id) { ResourceDescriptor resource = list[id]; ResourceLoader manager(resource.id, resource.type); return manager.getResourceString(); } #else #include #include #include std::string getResourceAsString(const ResourceDescriptor* list, int id) { const ResourceDescriptor& resource = list[id]; std::ifstream file; file.open(resource.fileName, std::ios::in); if(!file) { Log::error("Could not open %s", resource.fileName); throw std::runtime_error("Could not open file!"); } return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); } #endif ================================================ FILE: util/resource/resourceLoader.h ================================================ #pragma once #include struct ResourceDescriptor; std::string getResourceAsString(const ResourceDescriptor* list, int id); ================================================ FILE: util/resource/resourceManager.cpp ================================================ #include "resourceManager.h" std::unordered_map ResourceManager::defaultResources = {}; std::unordered_map ResourceManager::resources = {}; ResourceManager::ResourceManager() { } ResourceManager::~ResourceManager() { ResourceManager::close(); } ================================================ FILE: util/resource/resourceManager.h ================================================ #pragma once #include "../log.h" #include #include #include #include #include "resource.h" class ResourceManager { friend Resource; private: struct CountedResource { Resource* value; int count; }; static std::unordered_map defaultResources; static std::unordered_map resources; static void onResourceNameChange(Resource* changedResource, const std::string& newName) { auto iterator = ResourceManager::resources.find(changedResource->getName()); if (iterator == ResourceManager::resources.end()) { Log::error("The changed resource is not found (%s)", changedResource->getName().c_str()); return; } else { if (ResourceManager::resources.find(newName) != ResourceManager::resources.end()) { Log::error("Resource name has already been taken (%s)", newName.c_str()); return; } else { CountedResource countedResource = iterator->second; countedResource.value->name = newName; ResourceManager::resources.emplace(newName, countedResource); ResourceManager::resources.erase(iterator); } } } static void onResourcePathChange(Resource* changedResource, const std::string& newPath) { auto iterator = ResourceManager::resources.find(changedResource->getName()); if (iterator == ResourceManager::resources.end()) { Log::error("The changed resource is not found (%s)", changedResource->getName().c_str()); return; } else { auto& resource = iterator->second.value; if (resource->getName() == resource->getPath()) { onResourceNameChange(changedResource, newPath); } else { //? Load new resource } resource->path = newPath; } } ResourceManager(); ~ResourceManager(); public: template static T* getDefaultResource() { //Log::subject("DEFAULT"); //Log::debug("Getting default resource: (%s)", T::getStaticTypeName().c_str()); auto iterator = ResourceManager::defaultResources.find(T::getStaticType()); if (iterator != ResourceManager::defaultResources.end()) { //Log::debug("Found default resource: (%s)", T::getStaticTypeName().c_str()); return static_cast(iterator->second); } else { //Log::debug("Default resource not found: (%s), trying to load default resource", T::getStaticTypeName().c_str()); Resource* defaultResource = ResourceManager::add("default_" + T::getStaticTypeName(), T::getDefaultPath()); if (defaultResource) { //Log::debug("Loaded default resource; (%s)", T::getStaticTypeName().c_str()); ResourceManager::defaultResources[T::getStaticType()] = defaultResource; return static_cast(defaultResource); } else { //Log::debug("Default resource not loaded: (%s)", T::getStaticTypeName().c_str()); return nullptr; } } } template::value>> static T* get(const std::string& name) { //Log::subject s("GET"); //Log::debug("Getting resource: (%s)", name.c_str()); auto iterator = ResourceManager::resources.find(name); if (iterator != ResourceManager::resources.end()) { //Log::debug("Found resource: (%s)", name.c_str()); return static_cast(iterator->second.value); } else { //Log::warn("Resource not loaded: (%s), using default resource", name.c_str()); return ResourceManager::getDefaultResource(); } } static void add(Resource* resource) { if (resource == nullptr) return; auto iterator = ResourceManager::resources.find(resource->name); if (iterator != ResourceManager::resources.end()) { auto& resource = (*iterator).second; resource.count++; } else { CountedResource countedResource = { resource, 1 }; ResourceManager::resources.emplace(resource->name, countedResource); } } template::value>> static T* add(const std::string& name, const std::string& path) { Log::subject s("ADD"); //Log::debug("Adding resource: (%s, %s)", name.c_str(), path.c_str()); auto iterator = ResourceManager::resources.find(name); if (iterator != ResourceManager::resources.end()) { //Log::debug("Found resource: (%s, %s)", name.c_str(), path.c_str()); auto& resource = (*iterator).second; resource.count++; return static_cast(resource.value); } else { //Log::debug("Resource not found: (%s, %s), trying to load resource", name.c_str(), path.c_str()); T* resource = T::getAllocator().load(name, path); if (resource == nullptr) { Log::warn("Resource not loaded: (%s, %s)", name.c_str(), path.c_str()); return nullptr; } else { //Log::debug("Loaded resource: (%s, %s)", name.c_str(), path.c_str()); CountedResource countedResource = { resource, 1 }; ResourceManager::resources.emplace(name, countedResource); return resource; } } } template::value>> static T* add(const std::string& path) { return add(path, path); } static void close() { for (auto iterator : resources) { iterator.second.value->close(); } resources.clear(); defaultResources.clear(); } static bool exists(const std::string& name) { auto iterator = ResourceManager::resources.find(name); return iterator != ResourceManager::resources.end(); } template::value>> static std::vector getResourcesOfClass() { std::vector list; for (auto iterator : ResourceManager::resources) { if (typeid(T) == typeid(iterator.second.value)) list.push_back(static_cast(iterator.second.value)); } return list; } static std::vector getResourcesOfType(ResourceType type) { std::vector list; for (auto iterator : ResourceManager::resources) { if (iterator.second.value->getType() == type) list.push_back(iterator.second.value); } return list; } static std::vector getResources() { std::vector list; for (auto iterator : ResourceManager::resources) { list.push_back(iterator.second.value); } return list; } static std::map> getResourceMap() { std::map> map; for (auto iterator : ResourceManager::resources) { Resource* resource = iterator.second.value; if (map.find(resource->getTypeName()) == map.end()) map[resource->getTypeName()] = std::vector(); map[resource->getTypeName()].push_back(resource); } return map; } }; ================================================ FILE: util/stringUtil.cpp ================================================ #include "stringUtil.h" #include #include #include namespace Util { static std::string demangle_win(const std::string& fullName) { std::string name = fullName; std::size_t angleIndex = name.find_first_of('<'); if (angleIndex != std::string::npos) name = name.substr(0, angleIndex); std::size_t colonIndex = name.find_last_of(':'); if (colonIndex != std::string::npos) name = name.substr(colonIndex + 1); return name; } #ifdef __GNUG__ #include #include #include std::string demangle(const std::string& fullName) { int status = 0; std::unique_ptr demangled { abi::__cxa_demangle(fullName.c_str(), NULL, NULL, &status), std::free }; if (status != 0) return fullName; std::string result = demangled.get(); std::string name = demangle_win(result); return name; } #elif _MSC_VER std::string demangle(const std::string& fullName) { return demangle_win(fullName); } #else std::string demangle(const std::string& fullName) { return fullName; } #endif std::string decamel(const std::string& string) { std::string result = string; if (result.empty()) return result; for (std::size_t index = string.size() - 1; index >= 1; index--) if (isupper(string[index])) result.insert(index, " "); return result; } std::vector split(const std::string& string, char splitter) { std::vector elements; size_t length = string.size(); size_t start = 0; for (size_t i = 0; i < length; i++) { if (string[i] == splitter) { elements.push_back(string.substr(start, i - start)); start = i + 1; } } if (start < length) elements.push_back(string.substr(start, length - start)); return elements; } std::vector split_view(const std::string_view& string, char splitter) { std::vector elements; size_t length = string.size(); size_t start = 0; for (size_t i = 0; i < length; i++) { if (string[i] == splitter) { elements.push_back(string.substr(start, i - start)); start = i + 1; } } if (start < length) elements.push_back(string.substr(start, length - start)); return elements; } bool startsWith(const std::string& string, const std::string& prefix) { size_t l1 = string.length(); size_t l2 = prefix.length(); if (l2 > l1) return false; for (size_t i = 0; i < l2; i++) { if (string[i] != prefix[i]) return false; } return true; } bool endsWith(const std::string& string, const std::string& suffix) { size_t l1 = string.length(); size_t l2 = suffix.length(); if (l2 > l1) return false; for (size_t i = 1; i <= l2; i++) { if (string[l1 - i] != suffix[l2 - i]) return false; } return true; } std::string until(const std::string& string, char end) { size_t l = string.length(); for (size_t i = 0; i < l; i++) { if (string.at(i) == end) return string.substr(0, i); } return string; } std::string ltrim(const std::string& string) { std::string result = string; auto iterator = std::find_if(result.begin(), result.end(), [](int ch) { return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r'; }); result.erase(result.begin(), iterator); return result; } std::string rtrim(const std::string& string) { std::string result = string; auto iterator = std::find_if(result.rbegin(), result.rend(), [](int ch) { return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r'; }); result.erase(iterator.base(), result.end()); return result; } std::string trim(const std::string& string) { return ltrim(rtrim(string)); } }; ================================================ FILE: util/stringUtil.h ================================================ #pragma once #include #include #include #include namespace Util { std::string demangle(const std::string& fullName); std::string decamel(const std::string& string); template std::string typeName() { return typeid(T).name(); } std::vector split(const std::string& string, char splitter); std::vector split_view(const std::string_view& string, char splitter); bool startsWith(const std::string& string, const std::string& prefix); bool endsWith(const std::string& string, const std::string& suffix); std::string until(const std::string& string, char end); std::string ltrim(const std::string& string); std::string rtrim(const std::string& string); std::string trim(const std::string& string); template std::string format(const std::string& format, Args... args) { int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' if (size_s <= 0) throw std::runtime_error("Error during formatting."); auto size = static_cast(size_s); auto buffer = std::make_unique(size); std::snprintf(buffer.get(), size, format.c_str(), args ...); return std::string(buffer.get(), buffer.get() + size - 1); // We don't want the '\0' inside } }; ================================================ FILE: util/systemVariables.cpp ================================================ #include "systemVariables.h" #include #include static std::unordered_map variables; int SystemVariables::get(const std::string& key) { const auto iterator = variables.find(key); if (iterator == variables.end()) throw std::runtime_error("System variable \"" + key + "\" not found"); return iterator->second; } void SystemVariables::set(const std::string& key, int value) { variables[key] = value; } ================================================ FILE: util/systemVariables.h ================================================ #pragma once #include struct SystemVariables { public: static int get(const std::string& key); static void set(const std::string& key, int value); }; ================================================ FILE: util/terminalColor.cpp ================================================ #include "terminalColor.h" void setColor(TerminalColor foreground) { setColor(foreground, TerminalColor::BLACK); } void setColor(TerminalColorPair color) { setColor(color.foreground, color.background); } #ifdef _MSC_VER #include static HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); void setColor(TerminalColor foreground, TerminalColor background) { SetConsoleTextAttribute(console, int(foreground) | (int(background) << 4)); } #else #include static char cvtWinColorToUnixColor(int winColor){ switch(winColor){ case 0: return '0'; // BLACK case 1: return '4'; // BLUE case 2: return '2'; // GREEN case 3: return '6'; // AQUA case 4: return '1'; // RED case 5: return '5'; // MAGENTA case 6: return '3'; // YELLOW case 7: return '7'; // WHITE default: return '7'; // WHITE } } void setColor(TerminalColor foreground, TerminalColor background) { std::cout << "\033[0;"; if(int(foreground) & 0b00001000){ // strong std::cout << "1;"; } if(background != TerminalColor::BLACK) { std::cout << '4' << cvtWinColorToUnixColor(int(background) & 0b00000111) << ';'; } std::cout << '3' << cvtWinColorToUnixColor(int(foreground) & 0b00000111); std::cout << 'm'; } #endif ================================================ FILE: util/terminalColor.h ================================================ #pragma once enum class TerminalColor { BLACK = 0, DARK_BLUE = 1, DARK_GREEN = 2, AQUA = 3, DARK_RED = 4, PURPLE = 5, DARK_YELLOW = 6, LIGHT_GRAY = 7, GRAY = 8, BLUE = 9, GREEN = 10, CYAN = 11, RED = 12, MAGENTA = 13, YELLOW = 14, WHITE = 15, }; struct TerminalColorPair { TerminalColor foreground; TerminalColor background; }; void setColor(TerminalColor foreground); void setColor(TerminalColor foreground, TerminalColor background); void setColor(TerminalColorPair color); ================================================ FILE: util/tracker.h ================================================ #pragma once template struct Tracker { public: static int created; static int alive; Tracker() { created++; alive++; } Tracker(const Tracker&) { created++; alive++; } protected: ~Tracker() { alive--; } }; template int Tracker::created(0); template int Tracker::alive(0); ================================================ FILE: util/typetraits.h ================================================ #pragma once #include /** * Determine if the given type is a templated type */ template struct is_template : std::false_type {}; template typename Type, typename... Arguments> struct is_template> : std::true_type {}; /** * Determine if the given types are unique */ template inline constexpr auto unique_types = std::true_type {}; template inline constexpr auto unique_types = std::bool_constant<(!std::is_same_v && ...) && unique_types> {}; /** * Determine if the first type is part of the parameter pack */ template inline constexpr auto is_part_of = std::disjunction_v...>; ================================================ FILE: util/util.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 15.0 {60F3448D-6447-47CD-BF64-8762F8DB9361} util 10.0 Application true v142 MultiByte Application false v142 true MultiByte StaticLibrary true v142 MultiByte StaticLibrary false v142 true MultiByte Level3 Disabled true true NotSet stdcpp17 true Level3 Disabled true true Level3 MaxSpeed true true true true true true Level3 MaxSpeed true true true true NotSet _MBCS;NDEBUG;%(PreprocessorDefinitions) stdcpp17 true true true ================================================ FILE: util/valueCycle.cpp ================================================ #include "valueCycle.h" namespace Util { float linear(float t) { return t; } float smoothstep(float t) { return t * t * (3.0f - 2.0f * t); } float easeInQuad(float t) { return t * t; } float easeOutQuad(float t) { return t * (2.0f - t); } float easeInOutQuad(float t) { return (t < 0.5f) ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t; } float easeInCubic(float t) { return t * t * t; } float easeOutCubic(float t) { return (--t) * t * t + 1; } float easeInOuCubic(float t) { return (t < 0.5f) ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f; } }; ================================================ FILE: util/valueCycle.h ================================================ #pragma once #include #include #include namespace Util { float linear(float t); float smoothstep(float t); float easeInQuad(float t); float easeOutQuad(float t); float easeInOutQuad(float t); float easeInCubic(float t); float easeOutCubic(float t); float easeInOuCubic(float t); template struct ValueCycle { private: std::map keyframes; public: ValueCycle() {} ValueCycle(std::initializer_list> keyframes) : keyframes(keyframes) {} /* Add a keyframe at the given time */ void addKeyframe(float time, const T& frame) { keyframes[time] = frame; } /* Delete the keyframe at the given time */ void deleteKeyframe(float time) { keyframes.erase(time); } /* Sample the keyframe, time should be between 0.0f and 1.0f */ T sample(float time) { std::pair previous; std::pair next; for (const auto& iterator : keyframes) { if (iterator.first > time) { next = iterator; break; } previous = iterator; } float blend = interpolate((time - previous.first) / (next.first - previous.first)); return (1.0f - blend) * previous.second + blend * next.second; } /* Get all timestamps */ std::vector getTimestamps() { std::vector timestamps; timestamps.reserve(keyframes.size()); for (const auto& timestamp : keyframes) timestamps.push_back(timestamp.first); return timestamps; } }; } ================================================ FILE: world.grammar ================================================