├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── build.yml │ ├── documentation.yml │ ├── enforce_main.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── api │ ├── index.md │ └── toc.yml ├── developers │ ├── STYLE_GUIDE.md │ ├── code-contribution-guide.md │ ├── docs-contribution-guide.md │ ├── index.md │ └── toc.yml ├── docfx.json ├── index.md ├── manual │ ├── assets.md │ ├── build-configurations.md │ ├── coordinate-systems.md │ ├── getting-started.md │ ├── index.md │ ├── introduction.md │ ├── materials.md │ ├── math.md │ ├── old-entity-model.md │ ├── profiling.md │ ├── scripting.md │ ├── shaders.md │ └── toc.yml ├── media │ ├── .keep │ ├── banner.png │ ├── favicon.ico │ └── logo_64x64.png └── toc.yml └── src ├── .idea └── .idea.KorpiEngine │ └── .idea │ ├── .gitignore │ ├── .name │ ├── discord.xml │ ├── encodings.xml │ ├── indexLayout.xml │ ├── sonarlint.xml │ └── vcs.xml ├── Core.Tests ├── Core.Tests.csproj ├── Entities │ └── Transform.Tests.cs └── Math │ ├── AABox2DTests.cs │ ├── DVector3Tests.cs │ ├── Line2DTests.cs │ ├── MathHelper.cs │ ├── Matrix4x4Tests.cs │ ├── PlaneTests.cs │ ├── QuaternionTests.cs │ ├── RayTests.cs │ ├── Util.cs │ ├── Vector2Tests.cs │ ├── Vector3Tests.cs │ └── Vector4Tests.cs ├── Core ├── Animations │ ├── AnimationClip.cs │ ├── AnimationCurve.cs │ ├── AnimationState.cs │ └── Components │ │ └── Animation.cs ├── Application.cs ├── AssetManagement │ ├── Asset.cs │ ├── AssetImportContext.cs │ ├── AssetProviders │ │ ├── AssetProvider.cs │ │ ├── UncompressedAssetDatabase.Core.cs │ │ ├── UncompressedAssetDatabase.Utils.cs │ │ └── UncompressedAssetProvider.cs │ ├── AssetRef.cs │ ├── Exceptions │ │ ├── AssetImportException.cs │ │ ├── AssetLoadException.cs │ │ └── ObjectDestroyedException.cs │ ├── ExternalAssetInfo.cs │ ├── ImportedAsset.cs │ └── Importing │ │ ├── AssetImporter.cs │ │ ├── AssetImporterAttribute.cs │ │ ├── BuiltInImporters │ │ ├── ModelImporter.cs │ │ ├── ShaderImporter.cs │ │ └── TextureImporter.cs │ │ └── Texture2DLoader.cs ├── Assets │ ├── Defaults │ │ ├── AmbientLight.kshader │ │ ├── Basic.kshader │ │ ├── Bloom.kshader │ │ ├── DOF.kshader │ │ ├── Depth.kshader │ │ ├── DirectionalLight.kshader │ │ ├── GBufferCombine.kshader │ │ ├── GBufferDebug.kshader │ │ ├── Gizmos.kshader │ │ ├── ImGui.kshader │ │ ├── Invalid.kshader │ │ ├── PBR.glsl │ │ ├── PointLight.kshader │ │ ├── ProceduralSkybox.kshader │ │ ├── Random.glsl │ │ ├── SSR.kshader │ │ ├── Standard.kshader │ │ ├── TAA.kshader │ │ ├── Tonemapper.kshader │ │ ├── Utilities.glsl │ │ ├── VertexAttributes.glsl │ │ ├── bricks_albedo.jpg │ │ ├── bricks_normal.jpg │ │ ├── default_albedo.png │ │ ├── default_emission.png │ │ ├── default_normal.png │ │ ├── default_surface.png │ │ ├── grid.png │ │ └── white_pixel.png │ └── log4net.config ├── Core.csproj ├── Core.csproj.DotSettings ├── EngineConstants.cs ├── EngineObject.cs ├── Entities │ ├── Attributes │ │ ├── DisallowMultipleComponentAttribute.cs │ │ └── RequireComponentAttribute.cs │ ├── Components │ │ └── EntityComponent.cs │ ├── Coroutines │ │ ├── Coroutine.cs │ │ ├── CoroutineUpdateStage.cs │ │ ├── WaitForEndOfFrame.cs │ │ ├── WaitForFixedUpdate.cs │ │ └── WaitForSecondsRealtime.cs │ ├── Entity.Components.cs │ ├── Entity.Hierarchy.cs │ ├── Entity.Systems.cs │ ├── Entity.Updates.cs │ ├── Entity.cs │ ├── EntityUpdateStage.cs │ ├── Models │ │ └── Model.cs │ ├── Space.cs │ ├── Systems │ │ ├── EntitySystem.cs │ │ ├── SceneSystem.cs │ │ └── SystemBucket.cs │ └── Transform.cs ├── InputManagement │ ├── Cursor.cs │ ├── CursorLockState.cs │ ├── IInputState.cs │ ├── Input.cs │ ├── KeyCode.cs │ └── MouseButton.cs ├── Mathematics │ ├── AABox.cs │ ├── AABox2D.cs │ ├── BoundingFrustum.cs │ ├── Constants.cs │ ├── ContainmentType.cs │ ├── Hash.cs │ ├── IMappable.cs │ ├── IPoints.cs │ ├── ITransformable.cs │ ├── LinqUtil.cs │ ├── MathOps.cs │ ├── MathOps.tt │ ├── MathOpsPartial.cs │ ├── Matrix4x4.cs │ ├── Plane.cs │ ├── PlaneIntersectionType.cs │ ├── Quad.cs │ ├── Quaternion.cs │ ├── Random.cs │ ├── Ray.cs │ ├── Sphere.cs │ ├── Stats.cs │ ├── Structs.cs │ ├── Structs.tt │ ├── StructsPartial.cs │ ├── TemplateHelpers.tt │ ├── Triangle.cs │ ├── Triangle2D.cs │ └── ValueDomain.cs ├── Rendering │ ├── Buffers │ │ ├── GBuffer.cs │ │ ├── GraphicsBuffer.cs │ │ └── GraphicsFrameBuffer.cs │ ├── Cameras │ │ ├── CameraClearFlags.cs │ │ ├── CameraClearType.cs │ │ ├── CameraDebugDrawType.cs │ │ ├── CameraProjectionType.cs │ │ └── Components │ │ │ └── Camera.cs │ ├── DisplayState.cs │ ├── Exceptions │ │ ├── RenderStateException.cs │ │ └── ResourceLeakException.cs │ ├── Graphics.cs │ ├── GraphicsContext.cs │ ├── GraphicsDevice.cs │ ├── GraphicsObject.cs │ ├── GraphicsProgram.cs │ ├── GraphicsResource.cs │ ├── GraphicsTexture.cs │ ├── GraphicsVertexArrayObject.cs │ ├── Lighting │ │ └── Components │ │ │ ├── AmbientLight.cs │ │ │ ├── DirectionalLight.cs │ │ │ └── PointLight.cs │ ├── Materials │ │ ├── Material.cs │ │ └── MaterialPropertyBlock.cs │ ├── Meshes │ │ ├── Components │ │ │ ├── MeshDebugGizmoDrawer.cs │ │ │ ├── MeshRenderer.cs │ │ │ └── SkinnedMeshRenderer.cs │ │ ├── IndexFormat.cs │ │ ├── Mesh.cs │ │ ├── MeshVertexLayout.cs │ │ ├── PrimitiveBatch.cs │ │ ├── PrimitiveType.cs │ │ └── Vertices │ │ │ ├── VertexAttribute.cs │ │ │ └── VertexAttributeType.cs │ ├── Pipeline │ │ ├── PipelineNodes.cs │ │ └── RenderPipeline.cs │ ├── Primitives │ │ ├── BlendMode.cs │ │ ├── BlendType.cs │ │ ├── BlitFilter.cs │ │ ├── BufferType.cs │ │ ├── ClearFlags.cs │ │ ├── ComponentRenderOrder.cs │ │ ├── CubemapFace.cs │ │ ├── DepthMode.cs │ │ ├── FBOTarget.cs │ │ ├── PolyFace.cs │ │ ├── RasterizerState.cs │ │ ├── TextureImageFormat.cs │ │ ├── TextureMag.cs │ │ ├── TextureMin.cs │ │ ├── TextureType.cs │ │ ├── TextureWrap.cs │ │ ├── Topology.cs │ │ └── WindingOrder.cs │ ├── Shaders │ │ ├── Shader.cs │ │ ├── ShaderSourceDescriptor.cs │ │ └── ShaderType.cs │ ├── Textures │ │ ├── RenderTexture.cs │ │ ├── Texture.cs │ │ ├── Texture2D.cs │ │ ├── Texture2DArray.cs │ │ └── TextureCubemap.cs │ └── Windowing │ │ ├── GraphicsWindow.cs │ │ ├── WindowState.cs │ │ └── WindowingSettings.cs ├── SceneManagement │ ├── EmptyScene.cs │ ├── EntitySceneRenderer.cs │ ├── Scene.cs │ ├── SceneLoadMode.cs │ └── SceneManager.cs ├── Threading │ ├── Jobs │ │ ├── ExampleJob.cs │ │ ├── IAwaitable.cs │ │ ├── IKorpiJob.cs │ │ ├── JobCompletionState.cs │ │ └── KorpiJob.cs │ ├── ObjectPool.cs │ ├── Pooling │ │ ├── GlobalJobPool.cs │ │ ├── IJobPool.cs │ │ ├── JobSingleThreadPool.cs │ │ ├── JobThreadPool.cs │ │ ├── JobTplPool.cs │ │ ├── PriorityWorkQueue.cs │ │ ├── QueueType.cs │ │ └── WorkItemPriority.cs │ └── Threads │ │ ├── ThreadConfig.cs │ │ ├── ThreadStatus.cs │ │ └── WorkerThread.cs ├── Tools │ ├── Debug.cs │ ├── Gizmos │ │ ├── GizmoTypes.cs │ │ └── Gizmos.cs │ ├── Logging │ │ ├── DefaultLogger.cs │ │ ├── IKorpiLogger.cs │ │ └── LogFactory.cs │ ├── Profiling │ │ ├── DummyProfilerZone.cs │ │ ├── IProfilerZone.cs │ │ ├── ProfileAttribute.cs │ │ ├── ProfileInternalAttribute.cs │ │ ├── ProfilePlotType.cs │ │ ├── Profiler.cs │ │ └── Tracy │ │ │ ├── TracyProfiler.cs │ │ │ └── TracyProfilerZone.cs │ └── Serialization │ │ ├── Formats │ │ ├── BinaryTagConverter.cs │ │ └── StringTagConverter.cs │ │ ├── ISerializable.cs │ │ ├── ISerializationCallbackReceiver.cs │ │ ├── SerializationAttributes.cs │ │ ├── SerializedProperty.Compound.cs │ │ ├── SerializedProperty.List.cs │ │ ├── SerializedProperty.cs │ │ └── Serializer.cs ├── UI │ ├── DearImGui │ │ ├── CameraEditor.cs │ │ ├── DebugStatsWindow.cs │ │ ├── DirectionalLightEditor.cs │ │ ├── EntityComponentEditor.cs │ │ ├── EntityEditor.cs │ │ ├── IImGuiRenderer.cs │ │ ├── ImGuiWindow.cs │ │ ├── ImGuiWindowManager.cs │ │ └── MemoryEditor.cs │ ├── EditorGUI.cs │ └── GUI.cs └── Utils │ ├── AssemblyLoadAttributes.cs │ ├── AssemblyManager.cs │ ├── Exceptions │ ├── IdOverflowException.cs │ └── KorpiException.cs │ ├── Internal │ ├── Hashing.cs │ ├── MemoryReleaseSystem.cs │ ├── MemoryUtils.cs │ ├── MultiValueDictionary.cs │ └── RuntimeUtils.cs │ ├── Platform │ ├── DisplayInfo.cs │ ├── GraphicsInfo.cs │ ├── SystemInfo.cs │ └── WindowInfo.cs │ ├── SafeDisposable.cs │ ├── Time.cs │ ├── TypeID.cs │ └── UUID.cs ├── Directory.Build.targets ├── KorpiEngine.sln ├── KorpiEngine.sln.DotSettings ├── Networking ├── Multiplayer │ ├── HighLevel │ │ ├── Authentication │ │ │ ├── Authenticator.cs │ │ │ └── PasswordAuthenticator.cs │ │ ├── Channel.cs │ │ ├── Connections │ │ │ ├── KickReason.cs │ │ │ ├── LocalConnectionState.cs │ │ │ ├── NetworkConnection.cs │ │ │ └── RemoteConnectionState.cs │ │ ├── EventArgs │ │ │ ├── ClientListArgs.cs │ │ │ ├── ClientReceivedMessageArgs.cs │ │ │ └── ServerReceivedMessageArgs.cs │ │ └── Messages │ │ │ ├── AuthPasswordNetMessage.cs │ │ │ ├── AuthRequestNetMessage.cs │ │ │ ├── AuthResponseNetMessage.cs │ │ │ ├── ClientConnectionChangeNetMessage.cs │ │ │ ├── ConnectedClientsNetMessage.cs │ │ │ ├── Handlers │ │ │ ├── ClientMessageHandler.cs │ │ │ ├── MessageHandlerCollection.cs │ │ │ └── ServerMessageHandler.cs │ │ │ ├── MessageManager.cs │ │ │ └── NetMessage.cs │ ├── LowLevel │ │ ├── InternalPacketType.cs │ │ ├── NetStack │ │ │ ├── Buffers │ │ │ │ ├── ArrayPool.cs │ │ │ │ ├── ArrayPoolEventSource.cs │ │ │ │ ├── DefaultArrayPool.cs │ │ │ │ ├── DefaultArrayPoolBucket.cs │ │ │ │ └── Utilities.cs │ │ │ ├── LICENSE │ │ │ ├── Quantization │ │ │ │ ├── BoundedRange.cs │ │ │ │ ├── HalfPrecision.cs │ │ │ │ └── SmallestThree.cs │ │ │ ├── README.md │ │ │ ├── Serialization │ │ │ │ └── BitBuffer.cs │ │ │ ├── Threading │ │ │ │ ├── ArrayQueue.cs │ │ │ │ ├── ConcurrentBuffer.cs │ │ │ │ └── ConcurrentPool.cs │ │ │ └── Unsafe │ │ │ │ └── Memory.cs │ │ └── Transports │ │ │ ├── AddressType.cs │ │ │ ├── EventArgs │ │ │ ├── ClientConnectionStateArgs.cs │ │ │ ├── ClientReceivedDataArgs.cs │ │ │ ├── RemoteConnectionStateArgs.cs │ │ │ ├── ServerConnectionStateArgs.cs │ │ │ └── ServerReceivedDataArgs.cs │ │ │ ├── LiteNetLib │ │ │ ├── Core │ │ │ │ ├── ClientSocket.cs │ │ │ │ ├── CommonSocket.cs │ │ │ │ ├── LiteNetLib │ │ │ │ │ ├── BaseChannel.cs │ │ │ │ │ ├── ConnectionRequest.cs │ │ │ │ │ ├── INetEventListener.cs │ │ │ │ │ ├── InternalPackets.cs │ │ │ │ │ ├── Layers │ │ │ │ │ │ ├── Crc32cLayer.cs │ │ │ │ │ │ ├── PacketLayerBase.cs │ │ │ │ │ │ └── XorEncryptLayer.cs │ │ │ │ │ ├── NatPunchModule.cs │ │ │ │ │ ├── NativeSocket.cs │ │ │ │ │ ├── NetConstants.cs │ │ │ │ │ ├── NetDebug.cs │ │ │ │ │ ├── NetManager.HashSet.cs │ │ │ │ │ ├── NetManager.PacketPool.cs │ │ │ │ │ ├── NetManager.Socket.cs │ │ │ │ │ ├── NetManager.cs │ │ │ │ │ ├── NetPacket.cs │ │ │ │ │ ├── NetPeer.cs │ │ │ │ │ ├── NetStatistics.cs │ │ │ │ │ ├── NetUtils.cs │ │ │ │ │ ├── PausedSocketFix.cs │ │ │ │ │ ├── PooledPacket.cs │ │ │ │ │ ├── ReliableChannel.cs │ │ │ │ │ ├── SequencedChannel.cs │ │ │ │ │ ├── Trimming.cs │ │ │ │ │ └── Utils │ │ │ │ │ │ ├── CRC32C.cs │ │ │ │ │ │ ├── FastBitConverter.cs │ │ │ │ │ │ ├── INetSerializable.cs │ │ │ │ │ │ ├── NetDataReader.cs │ │ │ │ │ │ ├── NetDataWriter.cs │ │ │ │ │ │ ├── NetPacketProcessor.cs │ │ │ │ │ │ ├── NetSerializer.cs │ │ │ │ │ │ ├── NtpPacket.cs │ │ │ │ │ │ ├── NtpRequest.cs │ │ │ │ │ │ └── Preserve.cs │ │ │ │ ├── Packet.cs │ │ │ │ ├── QueueUtils.cs │ │ │ │ └── ServerSocket.cs │ │ │ └── LiteNetLibTransport.cs │ │ │ └── Transport.cs │ ├── NetClientManager.cs │ ├── NetServerManager.cs │ ├── NetworkManager.cs │ └── TransportManager.cs ├── Networking.csproj ├── Networking.csproj.DotSettings ├── Utility │ ├── ArraySegmentUtils.cs │ ├── BufferPool.cs │ └── ByteArrayPool.cs └── WebRequest.cs ├── OpenGL ├── Exceptions │ ├── GLException.cs │ ├── GLObjectNotBoundException.cs │ ├── GLShaderCompileException.cs │ ├── GLShaderProgramException.cs │ └── GLShaderProgramLinkException.cs ├── GLGraphicsContext.cs ├── GLGraphicsDevice.cs ├── GLImGuiRenderer.cs ├── GLInputState.cs ├── GLWindow.cs ├── OpenGL.csproj ├── OpenGL.csproj.DotSettings └── Resources │ ├── GLBuffer.cs │ ├── GLFrameBuffer.cs │ ├── GLGraphicsProgram.cs │ ├── GLGraphicsProgramFactory.cs │ ├── GLGraphicsShader.cs │ ├── GLTexture.cs │ └── GLVertexArrayObject.cs ├── README.md ├── Sandbox ├── Program.cs ├── Sandbox.csproj ├── Sandbox.csproj.DotSettings ├── Scenes │ ├── ExampleScene.cs │ ├── FullExample │ │ ├── DemoOscillate.cs │ │ └── FullExampleScene.cs │ ├── PrimitiveExample │ │ └── PrimitiveExampleScene.cs │ └── SponzaExample │ │ ├── SponzaExampleScene.cs │ │ └── SponzaLoader.cs └── Scripts │ └── DemoFreeCam.cs └── global.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Japsuu 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: japsu 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report and help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Do this... 16 | 2. Do that... 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Runtime info (please complete the following information):** 26 | - OS: [e.g. Windows, Linux] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Korpi Discord 4 | url: https://discord.gg/AhSX58wmWG 5 | about: Please ask and answer questions here. 6 | - name: Maintainer contact 7 | url: mailto://jasper.honkasalo@proton.me 8 | about: Please report security vulnerabilities here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: 'Build Source' 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | env: 9 | DOTNET_VERSION: '8.0.400' 10 | 11 | jobs: 12 | build: 13 | 14 | name: build-${{matrix.os}} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Setup .NET Core 23 | uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: ${{ env.DOTNET_VERSION }} 26 | 27 | - name: Install dependencies 28 | run: dotnet restore ./src/KorpiEngine.sln 29 | 30 | - name: Build 31 | run: dotnet build --configuration Production --no-restore ./src/KorpiEngine.sln -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: 'Build Documentation' 2 | 3 | # Trigger the action on push to main 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | actions: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 16 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 17 | concurrency: 18 | group: "pages" 19 | cancel-in-progress: false 20 | 21 | jobs: 22 | publish-docs: 23 | environment: 24 | name: github-pages 25 | url: ${{ steps.deployment.outputs.page_url }} 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | - name: Dotnet Setup 31 | uses: actions/setup-dotnet@v3 32 | with: 33 | dotnet-version: 8.x 34 | 35 | - run: dotnet tool update -g docfx 36 | - run: docfx docs/docfx.json 37 | 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: 'docs/_site' 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/enforce_main.yml: -------------------------------------------------------------------------------- 1 | name: 'Check Branch' 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | check_branch: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check branch 13 | if: github.head_ref != 'dev' 14 | run: | 15 | echo "ERROR: You can only merge to main from dev." 16 | exit 1 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'Test Source' 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | env: 9 | DOTNET_VERSION: '8.0.400' 10 | 11 | jobs: 12 | test: 13 | 14 | name: test-${{matrix.os}} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Setup .NET Core 23 | uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: ${{ env.DOTNET_VERSION }} 26 | 27 | - name: Install dependencies 28 | run: dotnet restore ./src/KorpiEngine.sln 29 | 30 | - name: Test 31 | run: dotnet test --no-restore --verbosity normal ./src/KorpiEngine.sln -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Common IntelliJ Platform excludes 2 | 3 | # User specific 4 | **/.idea/**/workspace.xml 5 | **/.idea/**/tasks.xml 6 | **/.idea/shelf/* 7 | **/.idea/dictionaries 8 | **/.idea/httpRequests/ 9 | 10 | # Sensitive or high-churn files 11 | **/.idea/**/dataSources/ 12 | **/.idea/**/dataSources.ids 13 | **/.idea/**/dataSources.xml 14 | **/.idea/**/dataSources.local.xml 15 | **/.idea/**/sqlDataSources.xml 16 | **/.idea/**/dynamic.xml 17 | 18 | # Rider 19 | # Rider auto-generates .iml files, and contentModel.xml 20 | **/.idea/**/*.iml 21 | **/.idea/**/contentModel.xml 22 | **/.idea/**/modules.xml 23 | 24 | *.suo 25 | *.user 26 | .vs/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | _UpgradeReport_Files/ 30 | [Pp]ackages/ 31 | 32 | Thumbs.db 33 | Desktop.ini 34 | .DS_Store 35 | 36 | # Build folder 37 | [Bb]uilds/ 38 | [Bb]uild/ 39 | 40 | # DocFx 41 | .cache 42 | **/_site/ 43 | 44 | # User tools folder (profiler, etc) 45 | [Tt]ools/[Uu]ser/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jasper Honkasalo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /**/DROP/ 2 | /**/TEMP/ 3 | /**/packages/ 4 | /**/bin/ 5 | /**/obj/ 6 | _site 7 | api/* 8 | !api/index.md 9 | !api/toc.yml 10 | log.txt -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # KorpiEngine Scripting API Reference 2 | 3 | > [!NOTE] 4 | > Use the sidebar to navigate the manual. 5 | 6 | Welcome to the Scripting API Reference pages. 7 | 8 | This section of the documentation contains details of the scripting API that the engine provides. You should be familiar with the basic theory and practice of scripting in KorpiEngine which is explained in the [Scripting section](~/manual/scripting.md) of the manual. 9 | 10 | Here you can navigate through all of the classes in the public API, which are described along with their methods, properties and any other information relevant to their use. -------------------------------------------------------------------------------- /docs/api/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Core 2 | href: Core/toc.yml 3 | - name: Networking 4 | href: Networking/toc.yml -------------------------------------------------------------------------------- /docs/developers/docs-contribution-guide.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to Korpi Engine Documentation 3 | 4 | ## How can I contribute to the docs? 5 | 6 | First, you should read the [Code Contribution Guide](code-contribution-guide.md), as the process is very similar. 7 | 8 | Second, please contact me (Japsu) on the [Korpi Discord](https://discord.gg/AhSX58wmWG) or [open an issue](https://github.com/japsuu/KorpiEngine/issues/new/choose) before starting work on any documentation changes, to avoid duplicate work. 9 | Please state that you would like to work on the documentation in your message. 10 | 11 | Once your issue has been approved, you can start working on your changes. When you're done, open a pull request (PR) from your branch to the main repository's `main` branch. -------------------------------------------------------------------------------- /docs/developers/index.md: -------------------------------------------------------------------------------- 1 | ### For engine developers 2 | 3 | If you are an engine developer looking for more in-depth, technical info about the engine, you are in the right place. 4 | 5 | This section is updated every once-in-a-while with new articles about the inner-workings of the engine. 6 | 7 | > [!NOTE] 8 | > If you'd like to help by writing more technical articles for this documentation, please see the [Contribution Guide](https://github.com/japsuu/KorpiEngine/blob/main/CONTRIBUTING.md). -------------------------------------------------------------------------------- /docs/developers/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Code Contribution Guide 2 | href: code-contribution-guide.md 3 | - name: Documentation Contribution Guide 4 | href: docs-contribution-guide.md 5 | - name: Style Guide 6 | href: STYLE_GUIDE.md -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../src/Core", 7 | "files": [ 8 | "**/*.csproj" 9 | ] 10 | } 11 | ], 12 | "dest": "api/Core", 13 | "namespaceLayout": "flattened", 14 | "memberLayout": "samePage" 15 | }, 16 | { 17 | "src": [ 18 | { 19 | "src": "../src/Networking", 20 | "files": [ 21 | "**/*.csproj" 22 | ] 23 | } 24 | ], 25 | "dest": "api/Networking", 26 | "namespaceLayout": "flattened", 27 | "memberLayout": "samePage" 28 | } 29 | ], 30 | "build": { 31 | "content": [ 32 | { 33 | "files": [ 34 | "**/*.{md,yml}" 35 | ], 36 | "exclude": [ 37 | "_site/**" 38 | ] 39 | } 40 | ], 41 | "resource": [ 42 | { 43 | "files": [ 44 | "media/favicon.ico", 45 | "**/*.png", 46 | "**/*.jpg", 47 | "**/*.mp4" 48 | ] 49 | } 50 | ], 51 | "output": "_site", 52 | "template": [ 53 | "default", 54 | "modern" 55 | ], 56 | "globalMetadata": { 57 | "_appName": "KorpiEngine", 58 | "_appFooter": "Made with <3", 59 | "_appTitle": "KorpiEngine", 60 | "_appLogoPath": "media/logo_64x64.png", 61 | "_appFaviconPath": "media/favicon.ico", 62 | "_enableSearch": true, 63 | "pdf": false, 64 | "_disableBreadcrumb": true 65 | }, 66 | "sitemap":{ 67 | "baseUrl": "https://japsuu.github.io/KorpiEngine", 68 | "priority": 0.1, 69 | "changefreq": "monthly", 70 | "fileOptions":{ 71 | "**/api/**.yml": { 72 | "priority": 0.3 73 | }, 74 | "**/getting-started.md": { 75 | "baseUrl": "https://dotnet.github.io/KorpiEngine/manual", 76 | "priority": 0.8 77 | } 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | _layout: landing 3 | --- 4 | 5 | KorpiEngine Banner 6 | 7 | # KorpiEngine Documentation 8 | 9 | Docs and guides to help you work with the engine. 10 | 11 | > [!IMPORTANT] 12 | > A great effort is being made to keep the documentation up to date. However, the engine is still in development and some features may not be fully documented yet. If you find any issues or have any questions, feel free to [open an issue on the GitHub repository](https://github.com/japsuu/KorpiEngine/issues). 13 | 14 | --- 15 | 16 | ## [Manual](~/manual/index.md) 17 | 18 | If you are looking for usage guides or examples, refer to the "_**Manual**_" section. 19 | 20 | ## [Scripting Reference](~/api/index.md) 21 | 22 | Refer to the "_**API documentation**_" for scripting. 23 | 24 | ## [For Developers](~/developers/index.md) 25 | 26 | If you are an engine developer, refer to the "_**For Developers**_" section for more in-depth articles. -------------------------------------------------------------------------------- /docs/manual/build-configurations.md: -------------------------------------------------------------------------------- 1 | 2 | # Build Configurations 3 | 4 | The KorpiEngine solution has three default build configurations: `Debug`, `Release`, and `Production`. 5 | 6 | Each build configuration has a different use case and is optimized for different scenarios. 7 | 8 | ## Default Build Configurations 9 | 10 | The `Debug` build configuration is used for development and debugging purposes. 11 | It is the slowest build configuration with all optimizations disabled, 12 | but it has the most debugging information available. 13 | 14 | The `Release` build configuration is used for testing and performance profiling. 15 | It is faster than the `Debug` build configuration with some optimizations enabled, 16 | but still has most of the debugging information available. 17 | 18 | The `Production` build configuration is used for shipping the final product. 19 | It is the fastest build configuration with all optimizations enabled, 20 | and all tooling and debugging information stripped. 21 | 22 |
23 | 24 | ## Default Preprocessor Defines 25 | 26 | The following preprocessor defines are available in KorpiEngine projects, 27 | in addition to the default ones (e.g. `NETCOREAPP`, `NET`, etc.) provided by the .NET SDK. 28 | 29 | | Preprocessor define | Debug | Release | Production | Description | 30 | |---------------------|:-----:|:-------:|:----------:|-----------------------------------------------------| 31 | | `KORPI_DEBUG` | ✔ | | | This is the debug build configuration. | 32 | | `KORPI_RELEASE` | | ✔ | | This is the release build configuration. | 33 | | `KORPI_PRODUCTION` | | | ✔ | This is the production build configuration. | 34 | | `KORPI_PROFILE` | ✔ | ✔ | | Profiling functionality is included with the build. | 35 | | `KORPI_TOOLS` | ✔ | ✔ | | Tooling functionality is included with the build. | 36 | | `KORPI_OPTIMIZED` | | ✔ | ✔ | Optimized code compilation is enabled. | 37 | -------------------------------------------------------------------------------- /docs/manual/coordinate-systems.md: -------------------------------------------------------------------------------- 1 | 2 | # Coordinate Systems 3 | 4 |
5 | 6 | ## Introduction 7 | 8 | Korpi Engine uses a right-handed coordinate system, similar to OpenGL. 9 | 10 | This means that the positive x-axis points to the right, the positive y-axis points up, and the negative z-axis points forward. 11 | 12 | This can be easily visualized by holding your right hand in front of you, with your thumb pointing to the right, your index finger pointing up, and your middle finger pointing towards you. Your thumb represents the x-axis, your index finger represents the y-axis, and your middle finger represents the z-axis. 13 | 14 | ![Right-Handed Coordinate System](https://learn.microsoft.com/en-us/windows/mixed-reality/design/images/004-coord-system-right-hand.png) 15 | 16 |
17 | 18 | ## Rotations 19 | 20 | Positive rotations are counter-clockwise when looking down the axis of rotation towards the origin. 21 | 22 | *Positive x rotation*: 23 | 24 | ![Positive X rotation](https://www.evl.uic.edu/ralph/508S98/gif/rightx.gif) 25 | 26 | *positive y rotation* 27 | 28 | ![Positive Y rotation](https://www.evl.uic.edu/ralph/508S98/gif/righty.gif) 29 | 30 | *positive z rotation* 31 | 32 | ![Positive Z rotation](https://www.evl.uic.edu/ralph/508S98/gif/rightz.gif) 33 | -------------------------------------------------------------------------------- /docs/manual/index.md: -------------------------------------------------------------------------------- 1 |  2 | # KorpiEngine User Manual 3 | 4 | > [!NOTE] 5 | > Use the sidebar to navigate the manual. 6 | 7 | The User Manual helps you learn how to use the engine. 8 | You can read it from start to finish, or use it as a reference. 9 | 10 |
11 | 12 | [Introduction](introduction.md) - Learn about the engine and who it is for. 13 | 14 | [Getting Started](getting-started.md) - Learn how to build and run the example project, and start developing your own game. 15 | 16 | [Scripting](scripting.md) - Learn how to use the scripting API to create custom game logic. 17 | -------------------------------------------------------------------------------- /docs/manual/materials.md: -------------------------------------------------------------------------------- 1 | 2 | # Working With Materials 3 | 4 | Materials are used to define the visual appearance of objects in the scene. They are composed of a shader, and a set of properties that are passed to the shader at runtime. 5 | 6 |
7 | 8 | ## Creating a Material 9 | 10 | ```csharp 11 | // Creating a material requires a reference to the shader that will be used to render the object. 12 | AssetRef shaderRef = Shader.Find("Assets/Example.kshader"); 13 | 14 | // Create a new material using the shader reference, a name, and whether all textures should be set to default values (will be removed in the future). 15 | Material material = new Material(shaderRef, "directional light material", false); 16 | 17 | ``` 18 | 19 |
20 | 21 | ## Setting Material Properties 22 | 23 | Material properties can be set using the `Material` API (see ). 24 | 25 | ```csharp 26 | material.SetTexture("_ExampleTexture", value); 27 | material.SetVector("_ExampleVector", value); 28 | material.SetColor("_ExampleColor", value); 29 | material.SetFloat("_ExampleFloat", value); 30 | ``` -------------------------------------------------------------------------------- /docs/manual/math.md: -------------------------------------------------------------------------------- 1 | 2 | # Math 3 | 4 | Korpi Engine uses a modified version of [vimaec/Math3D](https://github.com/vimaec/Math3D) for most of its math operations. 5 | 6 | Their API documentation can be found [here](https://vimaec.github.io/Math3D/). 7 | 8 |
9 | 10 | ## Angles 11 | 12 | In the Korpi Engine when working with angles, you can use either degrees or radians. 13 | Most methods provide both versions, so you can use the one you prefer. 14 | 15 | You can convert between radians/degrees by calling 16 | ```csharp 17 | float radians = degrees.ToRadians(); 18 | float degrees = radians.ToDegrees(); 19 | ``` 20 | 21 | Internally, the engine uses radians for all calculations, so if you need to squeeze out every bit of performance, you should use radians. -------------------------------------------------------------------------------- /docs/manual/scripting.md: -------------------------------------------------------------------------------- 1 | 2 | # Scripting 3 | 4 | Korpi Engine uses a Unity-like C# scripting API for game logic. 5 | The scripting API is designed to be quite similar to Unity's API, 6 | to make it easier for developers to transition from Unity to KorpiEngine. 7 | 8 | > [!NOTE] 9 | > Most of the API has been annotated with XML comments, so you can see the documentation in your IDE. 10 | 11 |
12 | 13 | ## Entity/Component/System model 14 | 15 |
16 | 17 | ### Entities 18 | 19 | Entities are the basic building blocks of the game world. They are similar to GameObjects in Unity. 20 | 21 | You can create entities by calling `scene.CreateEntity()`, or by creating an `Entity` object. 22 | 23 | > [!WARNING] 24 | > If you create an `Entity` object with `new Entity()`, you need to also spawn it into a scene by calling `entity.Spawn(scene)`. 25 | 26 | They: 27 | - have a `Transform` component that defines their position, rotation and scale. 28 | - can have multiple components attached to them. 29 | - can be parented to other entities. 30 | - can have a name for debugging purposes. 31 | - can be dynamically created and destroyed at runtime. 32 | - cannot be inherited from. 33 | 34 |
35 | 36 | ### Components 37 | 38 | Components are reusable pieces that can be attached to entities to give them functionality. 39 | 40 | Similarly to Unity, they can be added to Entities by calling `AddComponent()` on an entity, queried with `GetComponent()`, and removed with `RemoveComponent()`. 41 | 42 | They: 43 | - inherit from `EntityComponent`. 44 | - can be added and removed at runtime. 45 | - have the usual expected lifecycle methods (`OnAwake`, `OnStart`, `OnUpdate`, `OnDestroy`, ...). 46 | 47 |
48 | 49 | ### Systems 50 | 51 | > [!NOTE] 52 | > The systems API is still a work in progress. It can be used, but it's not as polished as the rest of the API. 53 | 54 | `EntitySystem` is a base class for systems that operate on the components of a single entity. 55 | 56 | `SceneSystem` is a base class for systems that operate on all components of a given type in a scene. 57 | -------------------------------------------------------------------------------- /docs/manual/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: introduction.md 3 | - name: Getting Started 4 | href: getting-started.md 5 | - name: Build Configurations 6 | href: build-configurations.md 7 | - name: Scripting 8 | href: scripting.md 9 | - name: Profiling 10 | href: profiling.md 11 | - name: Coordinate Systems 12 | href: coordinate-systems.md 13 | - name: Math 14 | href: math.md 15 | - name: Working With Assets 16 | href: assets.md 17 | - name: Working With Shaders 18 | href: shaders.md 19 | - name: Working With Materials 20 | href: materials.md -------------------------------------------------------------------------------- /docs/media/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/docs/media/.keep -------------------------------------------------------------------------------- /docs/media/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/docs/media/banner.png -------------------------------------------------------------------------------- /docs/media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/docs/media/favicon.ico -------------------------------------------------------------------------------- /docs/media/logo_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/docs/media/logo_64x64.png -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Manual 2 | href: manual/ 3 | homepage: manual/index.md 4 | - name: Scripting API 5 | href: api/ 6 | homepage: api/index.md 7 | - name: For Developers 8 | href: developers/ 9 | homepage: developers/index.md 10 | - name: GitHub 11 | href: https://github.com/japsuu/KorpiEngine 12 | - name: Discord 13 | href: https://discord.gg/AhSX58wmWG -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /.idea.KorpiEngine.iml 6 | /contentModel.xml 7 | /modules.xml 8 | /projectSettingsUpdater.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | # GitHub Copilot persisted chat sessions 15 | /copilot/chatSessions 16 | -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/.name: -------------------------------------------------------------------------------- 1 | KorpiEngine -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/sonarlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /src/.idea/.idea.KorpiEngine/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Core.Tests/Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | KorpiEngine.Core.Tests 11 | KorpiEngine.Core.Tests 12 | 13 | 14 | 15 | 16 | KORPI_DEBUG;KORPI_TOOLS;KORPI_PROFILE;TRACY_ENABLE;TRACE 17 | ..\..\Build\Debug\ 18 | 19 | 20 | 21 | 22 | KORPI_RELEASE;KORPI_TOOLS;KORPI_PROFILE;KORPI_OPTIMIZE;TRACY_ENABLE;TRACE 23 | ..\..\Build\Release\ 24 | 25 | 26 | 27 | 28 | KORPI_PRODUCTION;KORPI_OPTIMIZE 29 | ..\..\Build\Production\ 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Core.Tests/Math/DVector3Tests.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | 3 | namespace KorpiEngine.Core.Tests.Math; 4 | 5 | public class DVector3Tests 6 | { 7 | // A test for Cross (DVector3, DVector3) 8 | [Test] 9 | public void DVector3CrossTest() 10 | { 11 | var a = new DVector3(1.0d, 0.0d, 0.0d); 12 | var b = new DVector3(0.0d, 1.0d, 0.0d); 13 | 14 | var expected = new DVector3(0.0d, 0.0d, 1.0d); 15 | DVector3 actual; 16 | 17 | actual = Mathematics.MathOps.Cross(a, b); 18 | Assert.True(MathHelper.Equal(expected, actual), "Vector3f.Cross did not return the expected value."); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/AssetProviders/AssetProvider.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | public abstract class AssetProvider 6 | { 7 | internal void Initialize() 8 | { 9 | OnInitialize(); 10 | } 11 | 12 | 13 | internal void Shutdown() 14 | { 15 | OnShutdown(); 16 | } 17 | 18 | //TODO: Add LoadAsync variants. 19 | public AssetRef LoadAsset(string relativeAssetPath, ushort subID = 0, AssetImporter? customImporter = null) where T : Asset => new(InternalLoadAsset(relativeAssetPath, subID, customImporter)); 20 | public AssetRef LoadAsset(UUID assetID, ushort subID = 0) where T : Asset => new(InternalLoadAsset(assetID, subID)); 21 | 22 | 23 | /// 24 | /// Gets the importer for the specified file extension. 25 | /// 26 | /// The file extension to get an importer for, including the leading dot. 27 | /// The importer for the specified file extension. 28 | public abstract AssetImporter GetImporter(string fileExtension); 29 | public abstract bool HasAsset(UUID assetID); 30 | protected internal abstract T InternalLoadAsset(string relativeAssetPath, ushort subID = 0, AssetImporter? customImporter = null) where T : Asset; 31 | protected internal abstract T InternalLoadAsset(UUID assetID, ushort subID = 0) where T : Asset; 32 | 33 | 34 | protected virtual void OnInitialize() { } 35 | protected virtual void OnShutdown() { } 36 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/Exceptions/AssetImportException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | internal class AssetImportException : KorpiException 6 | { 7 | public AssetImportException(string message) : base($"Failed to import asset of type {typeof(T).Name}: {message}") 8 | { 9 | } 10 | 11 | 12 | public AssetImportException(string message, Exception ex) : base($"Failed to import asset of type {typeof(T).Name}: {message}", ex) 13 | { 14 | } 15 | } 16 | 17 | internal class AssetImportException : KorpiException 18 | { 19 | public AssetImportException(string path, string message) : base($"Failed to import asset at '{path}': {message}") 20 | { 21 | } 22 | 23 | 24 | public AssetImportException(string path, string message, Exception ex) : base($"Failed to import asset at '{path}': {message}", ex) 25 | { 26 | } 27 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/Exceptions/AssetLoadException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | internal class AssetLoadException : KorpiException 6 | { 7 | public AssetLoadException(string assetPath, string info) : base($"Failed to load asset of type {typeof(T).Name} at path '{assetPath}': {info}") { } 8 | 9 | 10 | public AssetLoadException(UUID assetID, ushort subID, string info) : base($"Failed to load sub-asset '{subID}' of type {typeof(T).Name} from assetID '{assetID}': {info}") { } 11 | 12 | 13 | public AssetLoadException(string assetPath, Exception inner) : base($"Failed to load asset of type {typeof(T).Name} at path '{assetPath}'", inner) { } 14 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/Exceptions/ObjectDestroyedException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | internal class ObjectDestroyedException(string message) : KorpiException(message); -------------------------------------------------------------------------------- /src/Core/AssetManagement/ExternalAssetInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using KorpiEngine.Utils; 3 | 4 | namespace KorpiEngine.AssetManagement; 5 | 6 | /// 7 | /// If an asset is loaded from an external source, 8 | /// this class contains information about the source. 9 | /// 10 | public sealed class ExternalAssetInfo(UUID assetID, ushort subID) 11 | { 12 | /// 13 | /// The ID of the asset in the asset database.
14 | /// None, if the asset is a runtime asset. 15 | ///
16 | public readonly UUID AssetID = assetID; 17 | 18 | /// 19 | /// The subID of the asset in the asset database.
20 | /// 0 if the asset is the main asset of the external source. 21 | ///
22 | public readonly ushort SubID = subID; 23 | 24 | /// 25 | /// Whether this asset is the main asset of the external source. 26 | /// 27 | public bool IsMainAsset => SubID == 0; 28 | 29 | 30 | public override string ToString() 31 | { 32 | StringBuilder sb = new(); 33 | sb.Append("[AssetID: "); 34 | sb.Append(AssetID); 35 | 36 | if (IsMainAsset) 37 | sb.Append(", Main Asset"); 38 | else 39 | { 40 | sb.Append(", SubID: "); 41 | sb.Append(SubID); 42 | } 43 | 44 | sb.Append(']'); 45 | return sb.ToString(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/ImportedAsset.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | /// 6 | /// Represents a fully imported asset. 7 | /// 8 | internal class ImportedAsset 9 | { 10 | private readonly Asset _mainAsset; 11 | private readonly IReadOnlyList _subAssets; 12 | private readonly IReadOnlyList> _dependencies; 13 | 14 | public readonly UUID AssetID; 15 | public readonly string RelativeAssetPath; 16 | 17 | 18 | public ImportedAsset(AssetImportContext context) 19 | { 20 | _mainAsset = context.MainAsset ?? throw new InvalidOperationException("Main asset not set in import context."); 21 | _subAssets = context.SubAssets; 22 | _dependencies = context.Dependencies; 23 | 24 | AssetID = context.AssetID; 25 | RelativeAssetPath = context.RelativeAssetPath; 26 | } 27 | 28 | 29 | public Asset GetAsset(ushort subID) 30 | { 31 | if (subID == 0) 32 | return _mainAsset; 33 | 34 | if (subID > _subAssets.Count) 35 | throw new ArgumentOutOfRangeException(nameof(subID), "SubID out of range."); 36 | 37 | return _subAssets[subID - 1]; 38 | } 39 | 40 | 41 | public void Destroy() 42 | { 43 | Asset.AllowManualExternalDestroy = true; 44 | 45 | // Destroy the main asset 46 | _mainAsset.DestroyImmediate(); 47 | 48 | // Destroy sub-assets 49 | foreach (Asset asset in _subAssets) 50 | asset.DestroyImmediate(); 51 | 52 | Asset.AllowManualExternalDestroy = false; 53 | 54 | // Release dependencies 55 | foreach (AssetRef dependency in _dependencies) 56 | dependency.Release(); 57 | } 58 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/Importing/AssetImporter.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.AssetManagement; 2 | 3 | /// 4 | /// Base class for all asset importers. 5 | /// Implementations should be decorated with the . 6 | /// 7 | public abstract class AssetImporter 8 | { 9 | /// 10 | /// Imports the asset with all their sub-assets into the given context. 11 | /// 12 | /// The context to import the asset(s) into. 13 | public abstract void Import(AssetImportContext context); 14 | } -------------------------------------------------------------------------------- /src/Core/AssetManagement/Importing/BuiltInImporters/TextureImporter.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | 3 | namespace KorpiEngine.AssetManagement; 4 | 5 | [AssetImporter(".png", ".bmp", ".jpg", ".jpeg", ".qoi", ".psd", ".tga", ".dds", ".hdr", ".ktx", ".pkm", ".pvr")] 6 | internal class TextureImporter : AssetImporter 7 | { 8 | public bool GenerateMipmaps { get; set; } = true; 9 | public TextureWrap TextureWrap { get; set; } = TextureWrap.Repeat; 10 | public TextureMin TextureMinFilter { get; set; } = TextureMin.LinearMipmapLinear; 11 | public TextureMag TextureMagFilter { get; set; } = TextureMag.Linear; 12 | 13 | 14 | public override void Import(AssetImportContext context) 15 | { 16 | FileInfo filePath = UncompressedAssetDatabase.GetFileInfoFromRelativePath(context.RelativeAssetPath); 17 | // Load the Texture into a TextureData Object and serialize to Asset Folder 18 | Texture2D texture = Texture2DLoader.FromFile(filePath.FullName); 19 | 20 | texture.SetTextureFilters(TextureMinFilter, TextureMagFilter); 21 | texture.SetWrapModes(TextureWrap, TextureWrap); 22 | 23 | if (GenerateMipmaps) 24 | texture.GenerateMipmaps(); 25 | 26 | context.SetMainAsset(texture); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/Basic.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/Basic" 2 | 3 | Properties 4 | { 5 | _Texture0("texture", TEXTURE_2D) 6 | } 7 | 8 | Pass 0 9 | { 10 | BlendSrc SrcAlpha 11 | BlendDst One 12 | 13 | Vertex 14 | { 15 | in vec3 vertexPosition; 16 | in vec2 vertexTexCoord; 17 | 18 | out vec2 TexCoords; 19 | 20 | void main() 21 | { 22 | gl_Position = vec4(vertexPosition, 1.0); 23 | TexCoords = vertexTexCoord; 24 | } 25 | } 26 | 27 | Fragment 28 | { 29 | in vec2 TexCoords; 30 | uniform sampler2D _Texture0; 31 | 32 | out vec4 finalColor; 33 | 34 | void main() 35 | { 36 | finalColor = vec4(texture(_Texture0, TexCoords).xyz, 1.0); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/Bloom.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/Bloom" 2 | 3 | Properties 4 | { 5 | _Resolution("resolution", FLOAT2) 6 | _GColor("g color", TEXTURE_2D) 7 | _Radius("radius", FLOAT) 8 | _Threshold("threshold", FLOAT) 9 | _Alpha("alpha", FLOAT) 10 | } 11 | 12 | Pass 0 13 | { 14 | DepthTest Off 15 | DepthWrite Off 16 | // DepthMode Less 17 | Blend On 18 | BlendSrc SrcAlpha 19 | BlendDst OneMinusSrcAlpha 20 | BlendMode Add 21 | Cull Off 22 | // Winding CW 23 | 24 | Vertex 25 | { 26 | in vec3 vertexPosition; 27 | in vec2 vertexTexCoord; 28 | 29 | out vec2 TexCoords; 30 | 31 | void main() 32 | { 33 | gl_Position =vec4(vertexPosition, 1.0); 34 | TexCoords = vertexTexCoord; 35 | } 36 | } 37 | 38 | Fragment 39 | { 40 | layout(location = 0) out vec4 OutputColor; 41 | 42 | in vec2 TexCoords; 43 | uniform vec2 _Resolution; 44 | 45 | uniform sampler2D _GColor; 46 | 47 | uniform float _Radius; 48 | uniform float _Threshold; 49 | uniform float _Alpha; 50 | 51 | // ---------------------------------------------------------------------------- 52 | 53 | void main() 54 | { 55 | // Kawase Blur 56 | vec2 ps = (vec2(1.0, 1.0) / _Resolution) * _Radius; 57 | vec3 thres = vec3(_Threshold, _Threshold, _Threshold); 58 | vec3 zero = vec3(0.0, 0.0, 0.0); 59 | vec3 color = max(texture(_GColor, TexCoords).rgb - thres, zero); 60 | color += max(texture(_GColor, TexCoords + vec2(ps.x, ps.y)).rgb - thres, zero); 61 | color += max(texture(_GColor, TexCoords + vec2(ps.x, -ps.y)).rgb - thres, zero); 62 | color += max(texture(_GColor, TexCoords + vec2(-ps.x, ps.y)).rgb - thres, zero); 63 | color += max(texture(_GColor, TexCoords + vec2(-ps.x, -ps.y)).rgb - thres, zero); 64 | color /= 5.0; 65 | 66 | 67 | OutputColor = vec4(color, _Alpha); 68 | } 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/Depth.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/Depth" 2 | 3 | Properties 4 | { 5 | _MatMVP("mvp matrix", MATRIX_4X4) 6 | } 7 | 8 | Pass 0 9 | { 10 | CullFace Front 11 | 12 | Vertex 13 | { 14 | layout (location = 0) in vec3 vertexPosition; 15 | 16 | uniform mat4 _MatMVP; 17 | void main() 18 | { 19 | gl_Position = _MatMVP * vec4(vertexPosition, 1.0); 20 | } 21 | } 22 | 23 | Fragment 24 | { 25 | layout (location = 0) out float fragmentdepth; 26 | 27 | void main() 28 | { 29 | 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/GBufferCombine.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/GBuffer" 2 | 3 | Properties 4 | { 5 | _GAlbedoAO("g diffuse", TEXTURE_2D) 6 | _GLighting("g lighting", TEXTURE_2D) 7 | } 8 | 9 | Pass 0 10 | { 11 | DepthTest Off 12 | DepthWrite Off 13 | // DepthMode Less 14 | Blend On 15 | BlendSrc SrcAlpha 16 | BlendDst One 17 | BlendMode Add 18 | Cull Off 19 | // Winding CW 20 | 21 | Vertex 22 | { 23 | in vec3 vertexPosition; 24 | in vec2 vertexTexCoord; 25 | 26 | out vec2 TexCoords; 27 | 28 | void main() 29 | { 30 | gl_Position =vec4(vertexPosition, 1.0); 31 | TexCoords = vertexTexCoord; 32 | } 33 | } 34 | 35 | Fragment 36 | { 37 | in vec2 TexCoords; 38 | 39 | uniform sampler2D _GAlbedoAO; // Diffuse 40 | uniform sampler2D _GLighting; // Lighting 41 | 42 | layout(location = 0) out vec4 OutputColor; 43 | 44 | void main() 45 | { 46 | vec4 albedoAO = texture(_GAlbedoAO, TexCoords); 47 | vec3 diffuseColor = albedoAO.rgb * 0.01; 48 | vec3 lightingColor = texture(_GLighting, TexCoords).rgb; 49 | // Apply AO onto the lightingColor 50 | // AO comes in as 0-1, 0 being no AO, 1 being full AO 51 | lightingColor *= (1.0 - albedoAO.w); 52 | 53 | vec3 color = diffuseColor + (lightingColor); 54 | 55 | OutputColor = vec4(color, 1.0); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/Gizmos.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/Gizmos" 2 | 3 | Properties 4 | { 5 | _MatMVP("mvp matrix", MATRIX_4X4) 6 | } 7 | 8 | Pass 0 9 | { 10 | // Default Raster state 11 | Blend On 12 | BlendSrc SrcAlpha 13 | BlendDst One 14 | 15 | Vertex 16 | { 17 | layout (location = 0) in vec3 vertexPosition; 18 | layout (location = 1) in vec4 vertexColor; 19 | 20 | out vec4 VertColor; 21 | 22 | uniform mat4 _MatMVP; 23 | 24 | void main() 25 | { 26 | VertColor = vertexColor; 27 | 28 | gl_Position = _MatMVP * vec4(vertexPosition, 1.0); 29 | } 30 | } 31 | 32 | Fragment 33 | { 34 | layout (location = 6) out vec4 gUnlit; // Unlit obj buffer 35 | 36 | in vec4 VertColor; 37 | 38 | void main() 39 | { 40 | gUnlit = VertColor; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/ImGui.kshader: -------------------------------------------------------------------------------- 1 | Shader "Default/ImGui" 2 | 3 | Properties 4 | { 5 | _MatProjection("projection matrix", MATRIX_4X4) 6 | _MainTexture("main texture", TEXTURE_2D) 7 | } 8 | 9 | Pass 0 10 | { 11 | BlendSrc SrcAlpha 12 | BlendDst One 13 | 14 | Vertex 15 | { 16 | in vec2 in_position; 17 | in vec2 in_texCoords; 18 | in vec4 in_color; 19 | 20 | uniform mat4 _MatProjection; 21 | 22 | out vec4 color; 23 | out vec2 texCoords; 24 | 25 | void main() 26 | { 27 | gl_Position = _MatProjection * vec4(in_position, 0, 1); 28 | color = in_color; 29 | texCoords = in_texCoords; 30 | } 31 | } 32 | 33 | Fragment 34 | { 35 | in vec4 color; 36 | in vec2 texCoords; 37 | 38 | uniform sampler2D _MainTexture; 39 | 40 | out vec4 outputColor; 41 | 42 | void main() 43 | { 44 | outputColor = color * texture(_MainTexture, texCoords); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/PBR.glsl: -------------------------------------------------------------------------------- 1 | #ifndef PBR_FUNCTIONS 2 | #define PBR_FUNCTIONS 3 | // ------------------------------------------------------------------------------ 4 | #ifndef MATH_PI 5 | #define MATH_PI 6 | const float PI = 3.14159265359; 7 | #endif 8 | // ---------------------------------------------------------------------------- 9 | float DistributionGGX(vec3 N, vec3 H, float roughness) 10 | { 11 | float a = roughness*roughness; 12 | float a2 = a*a; 13 | float NdotH = max(dot(N, H), 0.0); 14 | float NdotH2 = NdotH*NdotH; 15 | 16 | float nom = a2; 17 | float denom = (NdotH2 * (a2 - 1.0) + 1.0); 18 | denom = PI * denom * denom; 19 | 20 | return nom / denom; 21 | } 22 | // ---------------------------------------------------------------------------- 23 | float GeometrySchlickGGX(float NdotV, float roughness) 24 | { 25 | float r = (roughness + 1.0); 26 | float k = (r*r) / 8.0; 27 | 28 | float nom = NdotV; 29 | float denom = NdotV * (1.0 - k) + k; 30 | 31 | return nom / denom; 32 | } 33 | // ---------------------------------------------------------------------------- 34 | float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) 35 | { 36 | float NdotV = max(dot(N, V), 0.0); 37 | float NdotL = max(dot(N, L), 0.0); 38 | float ggx2 = GeometrySchlickGGX(NdotV, roughness); 39 | float ggx1 = GeometrySchlickGGX(NdotL, roughness); 40 | 41 | return ggx1 * ggx2; 42 | } 43 | // ---------------------------------------------------------------------------- 44 | vec3 FresnelSchlick(float cosTheta, vec3 F0){ 45 | //return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 46 | return F0 + (1.0 - F0) * exp2(-9.28 * cosTheta); // Faster and but slightly less accurate i think 47 | } 48 | // ---------------------------------------------------------------------------- 49 | #endif -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/Random.glsl: -------------------------------------------------------------------------------- 1 | #ifndef SHADER_RANDOM 2 | #define SHADER_RANDOM 3 | 4 | uint triple32(uint x) { 5 | // https://nullprogram.com/blog/2018/07/31/ 6 | x ^= x >> 17; 7 | x *= 0xed5ad4bbu; 8 | x ^= x >> 11; 9 | x *= 0xac4c1b51u; 10 | x ^= x >> 15; 11 | x *= 0x31848babu; 12 | x ^= x >> 14; 13 | return uint(x); 14 | } 15 | 16 | uint randState = triple32(uint(gl_FragCoord.x + int(_Resolution.x) * gl_FragCoord.y) + uint(_Resolution.x * _Resolution.y) * uint(_Frame)); 17 | uint RandNext() { return randState = triple32(randState); } 18 | uvec2 RandNext2() { return uvec2(RandNext(), RandNext()); } 19 | uvec3 RandNext3() { return uvec3(RandNext2(), RandNext()); } 20 | uvec4 RandNext4() { return uvec4(RandNext3(), RandNext()); } 21 | float RandNextF() { return float(RandNext()) / float(0xffffffffu); } 22 | vec2 RandNext2F() { return vec2(RandNext2()) / float(0xffffffffu); } 23 | vec3 RandNext3F() { return vec3(RandNext3()) / float(0xffffffffu); } 24 | vec4 RandNext4F() { return vec4(RandNext4()) / float(0xffffffffu); } 25 | 26 | float RandF (uint seed) { return float(triple32(seed)) / float(0xffffffffu); } 27 | vec2 Rand2F(uvec2 seed) { return vec2(triple32(seed.x), triple32(seed.y)) / float(0xffffffffu); } 28 | 29 | #endif -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/VertexAttributes.glsl: -------------------------------------------------------------------------------- 1 | #ifndef SHADER_VERTEXATTRIBUTES 2 | #define SHADER_VERTEXATTRIBUTES 3 | layout (location = 0) in vec3 vertexPosition; 4 | 5 | #ifdef HAS_UV 6 | layout (location = 1) in vec2 vertexTexCoord0; 7 | #else 8 | vec2 vertexTexCoord0 = vec2(0.0, 0.0); 9 | #endif 10 | 11 | #ifdef HAS_UV2 12 | layout (location = 2) in vec2 vertexTexCoord1; 13 | #else 14 | vec2 vertexTexCoord1 = vec2(0.0, 0.0); 15 | #endif 16 | 17 | #ifdef HAS_NORMALS 18 | layout (location = 3) in vec3 vertexNormal; 19 | #else 20 | vec3 vertexNormal = vec3(0.0, 1.0, 0.0); 21 | #endif 22 | 23 | #ifdef HAS_TANGENTS 24 | layout (location = 4) in vec3 vertexTangent; 25 | #else 26 | vec3 vertexTangent = vec3(1.0, 0.0, 0.0); 27 | #endif 28 | 29 | #ifdef HAS_COLORS 30 | layout (location = 5) in vec4 vertexColor; 31 | #else 32 | vec4 vertexColor = vec4(1.0, 1.0, 1.0, 1.0); 33 | #endif 34 | 35 | #ifdef SKINNED 36 | #ifdef HAS_BONEINDICES 37 | layout (location = 6) in vec4 vertexBoneIndices; 38 | #else 39 | vec4 vertexBoneIndices = vec4(0, 0, 0, 0); 40 | #endif 41 | 42 | #ifdef HAS_BONEWEIGHTS 43 | layout (location = 7) in vec4 vertexBoneWeights; 44 | #else 45 | vec4 vertexBoneWeights = vec4(0.0, 0.0, 0.0, 0.0); 46 | #endif 47 | 48 | const int MAX_BONE_INFLUENCE = 4; 49 | const int MAX_BONES = 100; 50 | uniform mat4 bindPoses[MAX_BONES]; 51 | uniform mat4 boneTransforms[MAX_BONES]; 52 | #endif 53 | #endif -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/bricks_albedo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/bricks_albedo.jpg -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/bricks_normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/bricks_normal.jpg -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/default_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/default_albedo.png -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/default_emission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/default_emission.png -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/default_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/default_normal.png -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/default_surface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/default_surface.png -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/grid.png -------------------------------------------------------------------------------- /src/Core/Assets/Defaults/white_pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japsuu/KorpiEngine/b76bb0b74fa9f6842d4c1992df120d6f392929a8/src/Core/Assets/Defaults/white_pixel.png -------------------------------------------------------------------------------- /src/Core/Assets/log4net.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Core/EngineConstants.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine; 4 | 5 | /// 6 | /// Contains constants used throughout the engine. 7 | /// 8 | public static class EngineConstants 9 | { 10 | public const string ENGINE_NAME = "Korpi Engine"; 11 | public const string ENGINE_VERSION = "Dev"; 12 | public const string DEFAULT_SHADER_DEFINE = $"KORPI_{ENGINE_VERSION}"; 13 | public const string ASSETS_FOLDER_NAME = "Assets"; 14 | public const string DEFAULTS_FOLDER_NAME = "Defaults"; 15 | public const string WEB_ASSETS_FOLDER_NAME = "WebAssets"; 16 | 17 | #region UPDATE LOOP 18 | 19 | /// 20 | /// The maximum number of fixed updates to be executed per second. 21 | /// 22 | public const int FIXED_UPDATE_FRAME_FREQUENCY = 20; 23 | 24 | /// 25 | /// The amount of time (in seconds) between each fixed update. 26 | /// 27 | public const float FIXED_DELTA_TIME = 1f / FIXED_UPDATE_FRAME_FREQUENCY; 28 | 29 | /// 30 | /// The threshold at which the engine will warn the user that the update loop is running too slowly. 31 | /// Default: 10fps 32 | /// 33 | public const float DELTA_TIME_SLOW_THRESHOLD = 0.1f; 34 | 35 | /// 36 | /// An upper limit on the amount of time the engine will report as having passed by the . 37 | /// 38 | public const float MAX_DELTA_TIME = 0.5f; 39 | 40 | #endregion 41 | } -------------------------------------------------------------------------------- /src/Core/Entities/Attributes/DisallowMultipleComponentAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | public class DisallowMultipleComponentAttribute : Attribute; -------------------------------------------------------------------------------- /src/Core/Entities/Attributes/RequireComponentAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | public class RequireComponentAttribute(params Type[] types) : Attribute 5 | { 6 | public Type[] Types { get; } = types; 7 | } -------------------------------------------------------------------------------- /src/Core/Entities/Coroutines/Coroutine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace KorpiEngine.Entities; 4 | 5 | /// 6 | /// Represents an instruction that can be executed over multiple frames. 7 | /// 8 | public sealed class Coroutine 9 | { 10 | /// 11 | /// True if the coroutine has finished executing, false otherwise. 12 | /// 13 | internal bool IsDone { get; private set; } 14 | 15 | /// 16 | /// The instruction to execute. 17 | /// 18 | private readonly Stack _instructions = []; 19 | 20 | 21 | internal Coroutine(IEnumerator routine) 22 | { 23 | _instructions.Push(routine); 24 | } 25 | 26 | 27 | internal void Run(CoroutineUpdateStage stage) 28 | { 29 | // If there are no instructions left, the coroutine is finished. 30 | if (_instructions.Count == 0) 31 | { 32 | IsDone = true; 33 | return; 34 | } 35 | 36 | IEnumerator instruction = _instructions.Peek(); 37 | 38 | // If the current instruction is a yield instruction, handle it. 39 | switch (instruction.Current) 40 | { 41 | case WaitForEndOfFrame when stage != CoroutineUpdateStage.EndOfFrame: 42 | case WaitForFixedUpdate when stage != CoroutineUpdateStage.FixedUpdate: 43 | return; 44 | } 45 | 46 | // If the current instruction has no more steps, pop it off the stack. 47 | if (!instruction.MoveNext()) 48 | { 49 | _instructions.Pop(); 50 | return; 51 | } 52 | 53 | // If the current instruction is another coroutine, push it onto the stack. 54 | if (instruction.Current is IEnumerator next && instruction != next) 55 | _instructions.Push(next); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Core/Entities/Coroutines/CoroutineUpdateStage.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | internal enum CoroutineUpdateStage 4 | { 5 | /// 6 | /// Called from Update loop. 7 | /// 8 | Update, 9 | 10 | /// 11 | /// Called at the end of the frame after the engine has rendered every Camera and GUI, 12 | /// just before displaying the frame on screen. 13 | /// 14 | EndOfFrame, 15 | 16 | /// 17 | /// Called from FixedUpdate loop. 18 | /// 19 | FixedUpdate 20 | } -------------------------------------------------------------------------------- /src/Core/Entities/Coroutines/WaitForEndOfFrame.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | public sealed class WaitForEndOfFrame; -------------------------------------------------------------------------------- /src/Core/Entities/Coroutines/WaitForFixedUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | public sealed class WaitForFixedUpdate; -------------------------------------------------------------------------------- /src/Core/Entities/Coroutines/WaitForSecondsRealtime.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using KorpiEngine.Utils; 3 | 4 | namespace KorpiEngine.Entities; 5 | 6 | public sealed class WaitForSecondsRealtime(float seconds) : IEnumerator 7 | { 8 | private readonly double _endTime = Time.TotalTime + seconds; 9 | 10 | public object? Current => null; 11 | 12 | 13 | public bool MoveNext() => Time.TotalTime < _endTime; 14 | public void Reset() { } 15 | } -------------------------------------------------------------------------------- /src/Core/Entities/Entity.Updates.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | public sealed partial class Entity 4 | { 5 | internal void EnsureComponentInitialization() 6 | { 7 | foreach (EntityComponent component in _components) 8 | { 9 | if (!component.HasAwoken) 10 | component.InternalAwake(); 11 | 12 | if (component.HasStarted) 13 | continue; 14 | 15 | if (component.EnabledInHierarchy) 16 | component.InternalStart(); 17 | } 18 | } 19 | 20 | 21 | internal void Update(EntityUpdateStage stage) 22 | { 23 | UpdateComponentsRecursive(stage); 24 | UpdateSystemsRecursive(stage); 25 | } 26 | 27 | 28 | /// 29 | /// Propagates system updates downwards in the hierarchy. 30 | /// 31 | private void UpdateSystemsRecursive(EntityUpdateStage stage) 32 | { 33 | _systemBuckets.Update(stage); 34 | 35 | if (!HasChildren) 36 | return; 37 | 38 | foreach (Entity child in _childList) 39 | child.UpdateSystemsRecursive(stage); 40 | } 41 | 42 | 43 | /// 44 | /// Propagates component updates downwards in the hierarchy. 45 | /// 46 | private void UpdateComponentsRecursive(EntityUpdateStage stage) 47 | { 48 | foreach (EntityComponent component in _components) 49 | component.Update(stage); 50 | 51 | if (!HasChildren) 52 | return; 53 | 54 | foreach (Entity child in _childList) 55 | child.UpdateComponentsRecursive(stage); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Core/Entities/EntityUpdateStage.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | /// 4 | /// Determines the stage at which a system is updated. 5 | /// 6 | public enum EntityUpdateStage 7 | { 8 | PreUpdate, 9 | Update, 10 | PostUpdate, 11 | PreFixedUpdate, 12 | FixedUpdate, 13 | PostFixedUpdate, 14 | PostRender, 15 | /*PreRender, 16 | Render, 17 | PostRender, 18 | RenderDepth, 19 | DrawGizmos*/ 20 | } -------------------------------------------------------------------------------- /src/Core/Entities/Space.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | /// 4 | /// The coordinate space in which to operate. 5 | /// 6 | public enum Space 7 | { 8 | /// 9 | /// Applies transformation relative to the world coordinate system. 10 | /// 11 | World, 12 | 13 | /// 14 | /// Applies transformation relative to the local coordinate system. 15 | /// 16 | Self 17 | } -------------------------------------------------------------------------------- /src/Core/Entities/Systems/SceneSystem.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | /// 4 | /// A system that influences all existing components of a given type in the world. 5 | /// 6 | public abstract class SceneSystem 7 | { 8 | 9 | 10 | public abstract void TryRegisterComponent(T c) where T : EntityComponent; 11 | public abstract void TryUnregisterComponent(T c) where T : EntityComponent; 12 | 13 | 14 | public void OnRegister(IEnumerable existingComponents) 15 | { 16 | foreach (EntityComponent component in existingComponents) 17 | TryRegisterComponent(component); 18 | 19 | Initialize(); 20 | } 21 | 22 | 23 | public void OnUnregister() 24 | { 25 | Deinitialize(); 26 | } 27 | 28 | protected virtual void Initialize() { } 29 | protected virtual void Deinitialize() { } 30 | 31 | public abstract void Update(EntityUpdateStage stage); 32 | } -------------------------------------------------------------------------------- /src/Core/Entities/Systems/SystemBucket.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Entities; 2 | 3 | /// 4 | /// A collection of system buckets grouped by update stage. 5 | /// 6 | internal class SystemBucketCollection 7 | { 8 | private readonly Dictionary _buckets = []; 9 | 10 | 11 | public void AddSystem(ulong id, IEntitySystem system) 12 | { 13 | EntityUpdateStage[] stages = system.UpdateStages; 14 | 15 | foreach (EntityUpdateStage stage in stages) 16 | { 17 | if (!_buckets.ContainsKey(stage)) 18 | _buckets.Add(stage, new SystemBucket()); 19 | 20 | _buckets[stage].AddSystem(id, system); 21 | } 22 | } 23 | 24 | 25 | public bool RemoveSystem(ulong id) 26 | { 27 | bool removed = false; 28 | 29 | foreach (SystemBucket bucket in _buckets.Values) 30 | removed |= bucket.RemoveSystem(id); 31 | 32 | return removed; 33 | } 34 | 35 | 36 | public void Update(EntityUpdateStage stage) 37 | { 38 | if (!_buckets.TryGetValue(stage, out SystemBucket? bucket)) 39 | return; 40 | 41 | bucket.Update(stage); 42 | } 43 | 44 | 45 | public void Clear() 46 | { 47 | _buckets.Clear(); 48 | } 49 | } 50 | 51 | /// 52 | /// A collection of systems that are updated at the same time. 53 | /// 54 | internal class SystemBucket 55 | { 56 | private readonly Dictionary _systems = []; 57 | 58 | 59 | public void AddSystem(ulong id, IEntitySystem system) 60 | { 61 | _systems.Add(id, system); 62 | } 63 | 64 | 65 | public bool RemoveSystem(ulong id) 66 | { 67 | return _systems.Remove(id); 68 | } 69 | 70 | 71 | public void Update(EntityUpdateStage stage) 72 | { 73 | foreach (IEntitySystem system in _systems.Values) 74 | system.Update(stage); 75 | } 76 | } -------------------------------------------------------------------------------- /src/Core/InputManagement/Cursor.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.InputManagement; 2 | 3 | public static class Cursor 4 | { 5 | private static CursorLockState lockState = CursorLockState.None; 6 | 7 | public static CursorLockState LockState 8 | { 9 | get => lockState; 10 | set 11 | { 12 | if (LockState == value) 13 | return; 14 | 15 | Application.SetCursorState(value); 16 | lockState = value; 17 | } 18 | } 19 | 20 | public static bool IsHidden => LockState != CursorLockState.None; 21 | 22 | 23 | internal static void Update(CursorLockState state) 24 | { 25 | LockState = state; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Core/InputManagement/CursorLockState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.InputManagement; 2 | 3 | public enum CursorLockState 4 | { 5 | /// The cursor is visible and the cursor motion is not limited. 6 | None, 7 | 8 | /// Hides the cursor. 9 | Hidden, 10 | 11 | /// Hides the cursor and locks it to the window. 12 | Locked, 13 | } -------------------------------------------------------------------------------- /src/Core/InputManagement/IInputState.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | 3 | namespace KorpiEngine.InputManagement; 4 | 5 | public interface IInputState 6 | { 7 | public IKeyboardState KeyboardState { get; } 8 | public IMouseState MouseState { get; } 9 | } 10 | 11 | public interface IKeyboardState 12 | { 13 | public bool IsKeyDown(KeyCode key); 14 | public bool IsKeyPressed(KeyCode key); 15 | public bool IsKeyReleased(KeyCode key); 16 | } 17 | 18 | public interface IMouseState 19 | { 20 | /// 21 | /// The current mouse position. 22 | /// 23 | public Vector2 Position { get; } 24 | 25 | /// 26 | /// The previous frame mouse position. 27 | /// 28 | public Vector2 PreviousPosition { get; } 29 | 30 | /// 31 | /// The change in mouse position since the last frame. 32 | /// 33 | public Vector2 PositionDelta { get; } 34 | 35 | /// 36 | /// The current scroll-wheel position. 37 | /// 38 | public Vector2 Scroll { get; } 39 | 40 | /// 41 | /// The previous frame scroll-wheel position. 42 | /// 43 | public Vector2 PreviousScroll { get; } 44 | 45 | /// 46 | /// The change in scroll-wheel position since the last frame. 47 | /// 48 | public Vector2 ScrollDelta { get; } 49 | 50 | public bool IsButtonDown(MouseButton button); 51 | public bool IsButtonPressed(MouseButton button); 52 | public bool IsButtonReleased(MouseButton button); 53 | } -------------------------------------------------------------------------------- /src/Core/InputManagement/Input.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | using KorpiEngine.Rendering; 3 | 4 | namespace KorpiEngine.InputManagement; 5 | 6 | public static class Input 7 | { 8 | internal static IKeyboardState KeyboardState { get; private set; } = null!; 9 | internal static IMouseState MouseState { get; private set; } = null!; 10 | 11 | public static Vector2 MousePosition => new(MouseState.Position.X, Graphics.ViewportResolution.Y - MouseState.Position.Y); 12 | public static float MouseX => MouseState.Position.X; 13 | public static float MouseY => MouseState.Position.Y; 14 | 15 | public static Vector2 MousePreviousPosition => new(MouseState.PreviousPosition.X, Graphics.ViewportResolution.Y - MouseState.PreviousPosition.Y); 16 | public static float MousePreviousX => MouseState.PreviousPosition.X; 17 | public static float MousePreviousY => MouseState.PreviousPosition.Y; 18 | 19 | public static Vector2 MouseDelta => new(MouseState.PositionDelta.X, MouseState.PositionDelta.Y); 20 | public static Vector2 ScrollDelta => new(MouseState.ScrollDelta.X, MouseState.ScrollDelta.Y); 21 | 22 | 23 | public static void Update(IInputState inputState) 24 | { 25 | KeyboardState = inputState.KeyboardState; 26 | MouseState = inputState.MouseState; 27 | } 28 | 29 | 30 | public static bool GetKey(KeyCode key) => KeyboardState.IsKeyDown(key); 31 | public static bool GetKeyDown(KeyCode key) => KeyboardState.IsKeyPressed(key); 32 | public static bool GetKeyUp(KeyCode key) => KeyboardState.IsKeyReleased(key); 33 | 34 | public static bool GetMouseButton(MouseButton button) => MouseState.IsButtonDown(button); 35 | public static bool GetMouseButtonDown(MouseButton button) => MouseState.IsButtonPressed(button); 36 | public static bool GetMouseButtonUp(MouseButton button) => MouseState.IsButtonReleased(button); 37 | } -------------------------------------------------------------------------------- /src/Core/InputManagement/MouseButton.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.InputManagement; 2 | 3 | /// Specifies the buttons of a mouse. 4 | public enum MouseButton 5 | { 6 | /// The first button. 7 | Button1 = 0, 8 | /// 9 | /// The left mouse button. This corresponds to 10 | /// 11 | Left = 0, 12 | /// The second button. 13 | Button2 = 1, 14 | /// 15 | /// The right mouse button. This corresponds to 16 | /// 17 | Right = 1, 18 | /// The third button. 19 | Button3 = 2, 20 | /// 21 | /// The middle mouse button. This corresponds to 22 | /// 23 | Middle = 2, 24 | /// The fourth button. 25 | Button4 = 3, 26 | /// The fifth button. 27 | Button5 = 4, 28 | /// The sixth button. 29 | Button6 = 5, 30 | /// The seventh button. 31 | Button7 = 6, 32 | /// The eighth button. 33 | Button8 = 7, 34 | /// The highest mouse button available. 35 | Last = 7, 36 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/Constants.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | // Copyright (C) The Mono.Xna Team 7 | // This file is subject to the terms and conditions defined in 8 | // file 'LICENSE.txt', which is part of this source code package. 9 | 10 | namespace KorpiEngine.Mathematics; 11 | 12 | public static class Constants 13 | { 14 | public static readonly Plane XYPlane = new(Vector3.UnitZ, 0); 15 | public static readonly Plane XZPlane = new(Vector3.UnitY, 0); 16 | public static readonly Plane YZPlane = new(Vector3.UnitX, 0); 17 | 18 | public const float PI = (float)Math.PI; 19 | public const float HALF_PI = PI / 2f; 20 | public const float TWO_PI = PI * 2f; 21 | public const float TOLERANCE = 0.0000001f; 22 | public const float LOG10_E = 0.4342945f; 23 | public const float LOG2_E = 1.442695f; 24 | public const float E = (float)Math.E; 25 | 26 | public const double RADIANS_TO_DEGREES = 57.295779513082320876798154814105; 27 | public const double DEGREES_TO_RADIANS = 0.017453292519943295769236907684886; 28 | 29 | public const double ONE_TENTH_OF_A_DEGREE = DEGREES_TO_RADIANS / 10; 30 | 31 | public const double MM_TO_FEET = 0.00328084; 32 | public const double FEET_TO_MM = 1 / MM_TO_FEET; 33 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/ContainmentType.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | // Copyright (C) The Mono.Xna Team 7 | // This file is subject to the terms and conditions defined in 8 | // file 'LICENSE.txt', which is part of this source code package. 9 | 10 | namespace KorpiEngine.Mathematics; 11 | 12 | /// 13 | /// Defines how the bounding volumes intersect or contain one another. 14 | /// 15 | public enum ContainmentType 16 | { 17 | /// 18 | /// Indicates that there is no overlap between two bounding volumes. 19 | /// 20 | Disjoint, 21 | 22 | /// 23 | /// Indicates that one bounding volume completely contains another volume. 24 | /// 25 | Contains, 26 | 27 | /// 28 | /// Indicates that bounding volumes partially overlap one another. 29 | /// 30 | Intersects 31 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/Hash.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | // Copyright (C) The Mono.Xna Team 7 | // This file is subject to the terms and conditions defined in 8 | // file 'LICENSE.txt', which is part of this source code package. 9 | 10 | namespace KorpiEngine.Mathematics; 11 | 12 | public static class Hash 13 | { 14 | // Discussion: if we want to go deeper into the subject, check out 15 | // https://en.wikipedia.org/wiki/List_of_hash_functions 16 | // https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes 17 | // https://en.wikipedia.org/wiki/Jenkins_hash_function#cite_note-11 18 | // https://referencesource.microsoft.com/#System.Numerics/System/Numerics/HashCodeHelper.cs 19 | // https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Numerics/Hashing/HashHelpers.cs 20 | 21 | 22 | public static int Combine(int h1, int h2) 23 | { 24 | unchecked 25 | { 26 | // RyuJIT optimizes this to use the ROL instruction 27 | // Related GitHub pull request: dotnet/coreclr#1830 28 | uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); 29 | return ((int)rol5 + h1) ^ h2; 30 | } 31 | } 32 | 33 | 34 | public static int Combine(IList xs) 35 | { 36 | if (xs.Count == 0) 37 | return 0; 38 | int r = xs[0]; 39 | for (int i = 1; i < xs.Count; ++i) 40 | r = Combine(r, i); 41 | return r; 42 | } 43 | 44 | 45 | public static int Combine(params int[] xs) => Combine(xs as IList); 46 | 47 | public static int Combine(int x0, int x1, int x2) => Combine(Combine(x0, x1), x2); 48 | 49 | public static int Combine(int x0, int x1, int x2, int x3) => Combine(Combine(x0, x1, x2), x3); 50 | 51 | public static int HashValues(this IEnumerable values) => values.Aggregate(0, (acc, x) => Combine(acc, x)); 52 | 53 | public static int HashCodes(this IEnumerable values) => values.Select(x => x.GetHashCode()).HashValues(); 54 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/IMappable.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Mathematics; 2 | 3 | public interface IMappable 4 | { 5 | TContainer Map(Func f); 6 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/IPoints.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Mathematics; 2 | 3 | public interface IPoints 4 | { 5 | int NumPoints { get; } 6 | 7 | Vector3 GetPoint(int n); 8 | } 9 | 10 | public interface IPoints2D 11 | { 12 | int NumPoints { get; } 13 | 14 | Vector2 GetPoint(int n); 15 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/LinqUtil.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Mathematics; 2 | 3 | public static class LinqUtil 4 | { 5 | public static AABox ToAABox(this IEnumerable self) => AABox.Create(self); 6 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/PlaneIntersectionType.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | // Copyright (C) The Mono.Xna Team 7 | // This file is subject to the terms and conditions defined in 8 | // file 'LICENSE.txt', which is part of this source code package. 9 | 10 | namespace KorpiEngine.Mathematics; 11 | 12 | /// 13 | /// Defines the intersection between a Plane and a bounding volume. 14 | /// 15 | public enum PlaneIntersectionType 16 | { 17 | /// 18 | /// There is no intersection, the bounding volume is in the negative half space of the plane. 19 | /// 20 | Front, 21 | 22 | /// 23 | /// There is no intersection, the bounding volume is in the positive half space of the plane. 24 | /// 25 | Back, 26 | 27 | /// 28 | /// The plane is intersected. 29 | /// 30 | Intersecting 31 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/Quad.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | 7 | namespace KorpiEngine.Mathematics; 8 | 9 | public partial struct Quad : ITransformable3D, IPoints, IMappable 10 | { 11 | public Quad Transform(Matrix4x4 mat) => Map(x => x.Transform(mat)); 12 | 13 | public int NumPoints => 4; 14 | 15 | public Vector3 GetPoint(int n) => n == 0 ? A : n == 1 ? B : n == 2 ? C : D; 16 | 17 | public Quad Map(Func f) => new(f(A), f(B), f(C), f(D)); 18 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/Triangle.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace KorpiEngine.Mathematics; 10 | 11 | public partial struct Triangle : ITransformable3D, IPoints, IMappable 12 | { 13 | public Triangle Transform(Matrix4x4 mat) => Map(x => x.Transform(mat)); 14 | 15 | public int NumPoints => 3; 16 | 17 | public Vector3 GetPoint(int n) => n == 0 ? A : n == 1 ? B : C; 18 | 19 | public Triangle Map(Func f) => new(f(A), f(B), f(C)); 20 | 21 | public float LengthA => A.Distance(B); 22 | public float LengthB => B.Distance(C); 23 | public float LengthC => C.Distance(A); 24 | 25 | public bool HasArea => A != B && B != C && C != A; 26 | 27 | public float Area => (B - A).Cross(C - A).Length() * 0.5f; 28 | 29 | public float Perimeter => LengthA + LengthB + LengthC; 30 | public Vector3 MidPoint => (A + B + C) / 3f; 31 | public Vector3 NormalDirection => (B - A).Cross(C - A); 32 | public Vector3 Normal => NormalDirection.Normalize(); 33 | public Vector3 SafeNormal => NormalDirection.SafeNormalize(); 34 | public AABox BoundingBox => AABox.Create(A, B, C); 35 | public Sphere BoundingSphere => Sphere.Create(A, B, C); 36 | 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public bool IsSliver(float tolerance = Constants.TOLERANCE) => LengthA <= tolerance || LengthB <= tolerance || LengthC <= tolerance; 40 | 41 | 42 | public Line Side(int n) => n == 0 ? AB : n == 1 ? BC : CA; 43 | 44 | public Vector3 Binormal => (B - A).SafeNormalize(); 45 | public Vector3 Tangent => (C - A).SafeNormalize(); 46 | 47 | public Line AB => new(A, B); 48 | public Line BC => new(B, C); 49 | public Line CA => new(C, A); 50 | 51 | public Line BA => AB.Inverse; 52 | public Line CB => BC.Inverse; 53 | public Line AC => CA.Inverse; 54 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/Triangle2D.cs: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // Copyright (C) 2024 KorpiEngine Team. 3 | // Copyright (C) 2019 VIMaec LLC. 4 | // Copyright (C) 2019 Ara 3D. Inc 5 | // https://ara3d.com 6 | // This file is subject to the terms and conditions defined in 7 | // file 'LICENSE.txt', which is part of this source code package. 8 | 9 | namespace KorpiEngine.Mathematics; 10 | 11 | public partial struct Triangle2D 12 | { 13 | public int Count => 3; 14 | 15 | public Vector2 this[int n] => n == 0 ? A : n == 1 ? B : C; 16 | 17 | // Compute the signed area of a triangle. 18 | public float Area => 0.5f * (A.X * (C.Y - B.Y) + B.X * (A.Y - C.Y) + C.X * (B.Y - A.Y)); 19 | 20 | 21 | // Test if a given point p2 is on the left side of the line formed by p0-p1. 22 | public static bool OnLeftSideOfLine(Vector2 p0, Vector2 p1, Vector2 p2) => new Triangle2D(p0, p2, p1).Area > 0; 23 | 24 | 25 | // Test if a given point is inside a given triangle in R2. 26 | public bool Contains(Vector2 pp) 27 | { 28 | // Point in triangle test using barycentric coordinates 29 | Vector2 v0 = B - A; 30 | Vector2 v1 = C - A; 31 | Vector2 v2 = pp - A; 32 | 33 | float dot00 = v0.Dot(v0); 34 | float dot01 = v0.Dot(v1); 35 | float dot02 = v0.Dot(v2); 36 | float dot11 = v1.Dot(v1); 37 | float dot12 = v1.Dot(v2); 38 | 39 | float invDenom = 1f / (dot00 * dot11 - dot01 * dot01); 40 | dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; 41 | dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; 42 | 43 | return dot11 > 0 && dot00 > 0 && dot11 + dot00 < 1; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Core/Mathematics/ValueDomain.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Mathematics; 2 | 3 | public class ValueDomain 4 | { 5 | public readonly double Lower; 6 | public readonly double Upper; 7 | 8 | 9 | public ValueDomain(double lower, double upper) 10 | { 11 | (Lower, Upper) = (lower, upper); 12 | } 13 | 14 | 15 | public double Normalize(double value) => value.Clamp(Lower, Upper) / Upper; 16 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Buffers/GraphicsBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public abstract class GraphicsBuffer(int handle) : GraphicsObject(handle) 4 | { 5 | internal abstract int SizeInBytes { get; } 6 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Buffers/GraphicsFrameBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public abstract class GraphicsFrameBuffer(int handle) : GraphicsObject(handle) 4 | { 5 | public struct Attachment 6 | { 7 | public GraphicsTexture Texture; 8 | public bool IsDepth; 9 | } 10 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Cameras/CameraClearFlags.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | [Flags] 4 | public enum CameraClearFlags 5 | { 6 | None = 0, 7 | Color = 1, 8 | Depth = 2, 9 | Stencil = 4 10 | } 11 | 12 | public static class CameraClearFlagsExtensions 13 | { 14 | public static bool HasFlagFast(this CameraClearFlags value, CameraClearFlags flag) 15 | { 16 | return (value & flag) != 0; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Cameras/CameraClearType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// Defines how the camera clears the screen. 5 | /// 6 | public enum CameraClearType 7 | { 8 | /// 9 | /// The camera does not clear the screen. 10 | /// 11 | Nothing, 12 | 13 | /// 14 | /// The camera clears the screen with a solid color. 15 | /// 16 | SolidColor, 17 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Cameras/CameraProjectionType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum CameraProjectionType { Perspective, Orthographic } -------------------------------------------------------------------------------- /src/Core/Rendering/DisplayState.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | 3 | namespace KorpiEngine.Rendering; 4 | 5 | public readonly struct DisplayState(Int2 resolution) 6 | { 7 | public readonly Int2 Resolution = resolution; 8 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Exceptions/RenderStateException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.Rendering; 4 | 5 | /// 6 | /// The exception that is thrown when a render state is invalid. 7 | /// 8 | internal class RenderStateException : KorpiException 9 | { 10 | internal RenderStateException(string message) : base(message) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Exceptions/ResourceLeakException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.Rendering; 4 | 5 | internal class ResourceLeakException : KorpiException 6 | { 7 | public ResourceLeakException(string message) : base(message) 8 | { 9 | } 10 | 11 | 12 | public ResourceLeakException(string message, Exception innerException) : base(message, innerException) 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/Core/Rendering/GraphicsObject.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// Represents a graphics resource handle. 5 | /// 6 | public abstract class GraphicsObject : GraphicsResource, IEquatable 7 | { 8 | /// 9 | /// The OpenGL handle. 10 | /// 11 | public readonly int Handle; 12 | 13 | 14 | /// 15 | /// Initializes a new instance of the GraphicsResource class. 16 | /// 17 | protected GraphicsObject(int handle) 18 | { 19 | Handle = handle; 20 | } 21 | 22 | 23 | public bool Equals(GraphicsObject? other) 24 | { 25 | return other != null && Handle.Equals(other.Handle); 26 | } 27 | 28 | 29 | public override bool Equals(object? obj) 30 | { 31 | return obj is GraphicsObject o && Equals(o); 32 | } 33 | 34 | 35 | public override int GetHashCode() 36 | { 37 | return Handle.GetHashCode(); 38 | } 39 | 40 | 41 | public override string ToString() 42 | { 43 | return $"{GetType().Name}({Handle})"; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Core/Rendering/GraphicsProgram.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// Represents a shaderProgram object. 5 | /// 6 | public abstract class GraphicsProgram(int handle) : GraphicsObject(handle); -------------------------------------------------------------------------------- /src/Core/Rendering/GraphicsResource.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.Rendering; 4 | 5 | /// 6 | /// Represents a graphics (GPU) resource.
7 | /// Can be derived to inherit the dispose pattern. 8 | ///
9 | public abstract class GraphicsResource : SafeDisposable 10 | { 11 | protected override bool RequiresMainThreadDispose => true; 12 | 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | protected GraphicsResource() 18 | { 19 | } 20 | } -------------------------------------------------------------------------------- /src/Core/Rendering/GraphicsTexture.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public abstract class GraphicsTexture(int handle) : GraphicsObject(handle) 4 | { 5 | public abstract TextureType Type { get; protected set; } 6 | } -------------------------------------------------------------------------------- /src/Core/Rendering/GraphicsVertexArrayObject.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public abstract class GraphicsVertexArrayObject(int handle) : GraphicsObject(handle); -------------------------------------------------------------------------------- /src/Core/Rendering/Lighting/Components/AmbientLight.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.AssetManagement; 2 | using KorpiEngine.Entities; 3 | using KorpiEngine.Mathematics; 4 | 5 | namespace KorpiEngine.Rendering; 6 | 7 | public class AmbientLight : EntityComponent 8 | { 9 | public override ComponentRenderOrder RenderOrder => ComponentRenderOrder.LightingPass; 10 | 11 | public ColorHDR SkyColor { get; set; } = ColorHDR.White; 12 | public ColorHDR GroundColor { get; set; } = ColorHDR.White; 13 | public float SkyIntensity { get; set; } = 0.4f; 14 | public float GroundIntensity { get; set; } = 0.05f; 15 | 16 | private Material? _lightMat; 17 | 18 | 19 | protected override void OnRenderObject() 20 | { 21 | _lightMat ??= new Material(Asset.Load("Assets/Defaults/AmbientLight.kshader"), "ambient light material", false); 22 | 23 | _lightMat.SetColor("_SkyColor", SkyColor); 24 | _lightMat.SetColor("_GroundColor", GroundColor); 25 | _lightMat.SetFloat("_SkyIntensity", SkyIntensity); 26 | _lightMat.SetFloat("_GroundIntensity", GroundIntensity); 27 | 28 | GBuffer gBuffer = Camera.RenderingCamera.GBuffer!; 29 | _lightMat.SetTexture("_GAlbedoAO", gBuffer.AlbedoAO); 30 | _lightMat.SetTexture("_GNormalMetallic", gBuffer.NormalMetallic); 31 | _lightMat.SetTexture("_GPositionRoughness", gBuffer.PositionRoughness); 32 | 33 | Graphics.Blit(_lightMat); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Meshes/IndexFormat.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// The format of the mesh index buffer data. 5 | /// 6 | public enum IndexFormat 7 | { 8 | UInt16, 9 | UInt32 10 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Meshes/PrimitiveType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum PrimitiveType 4 | { 5 | Cube, 6 | Sphere, 7 | Capsule, 8 | Quad, 9 | Torus 10 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Meshes/Vertices/VertexAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum VertexAttribute 4 | { 5 | Position, 6 | TexCoord0, 7 | TexCoord1, 8 | Normal, 9 | Tangent, 10 | Color, 11 | BoneWeights, 12 | BoneIndices 13 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Meshes/Vertices/VertexAttributeType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// The data-type of a vertex attribute. 5 | /// 6 | public enum VertexAttributeType 7 | { 8 | Byte = 5120, 9 | UnsignedByte = 5121, 10 | Short = 5122, 11 | UnsignedShort = 5123, 12 | Int = 5124, 13 | UnsignedInt = 5125, 14 | Float = 5126 15 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/BlendMode.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum BlendMode 4 | { 5 | Add = 32774, 6 | Min = 32775, 7 | Max = 32776, 8 | Subtract = 32778, 9 | ReverseSubtract = 32779, 10 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/BlendType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum BlendType 4 | { 5 | Zero = 0, 6 | One = 1, 7 | SrcColor = 768, 8 | OneMinusSrcColor = 769, 9 | SrcAlpha = 770, 10 | OneMinusSrcAlpha = 771, 11 | DstAlpha = 772, 12 | OneMinusDstAlpha = 773, 13 | DstColor = 774, 14 | OneMinusDstColor = 775, 15 | SrcAlphaSaturate = 776, 16 | ConstantColor = 32769, 17 | OneMinusConstantColor = 32770, 18 | ConstantAlpha = 32771, 19 | OneMinusConstantAlpha = 32772, 20 | Src1Alpha = 34185, 21 | Src1Color = 35065, 22 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/BlitFilter.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum BlitFilter 4 | { 5 | Nearest, 6 | Linear 7 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/BufferType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum BufferType 4 | { 5 | VertexBuffer, 6 | ElementsBuffer, 7 | UniformBuffer, 8 | StructuredBuffer, 9 | /// 10 | /// DO NOT USE! 11 | /// This is used to count the number of possible buffer types. 12 | /// 13 | Count 14 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/ClearFlags.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | [Flags] 4 | public enum ClearFlags 5 | { 6 | Color = 1 << 1, 7 | Depth = 1 << 2, 8 | Stencil = 1 << 3 9 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/ComponentRenderOrder.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum ComponentRenderOrder 4 | { 5 | None, 6 | GeometryPass, 7 | LightingPass 8 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/CubemapFace.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum CubemapFace 4 | { 5 | PositiveX = 34069, 6 | NegativeX = 34070, 7 | PositiveY = 34071, 8 | NegativeY = 34072, 9 | PositiveZ = 34073, 10 | NegativeZ = 34074 11 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/DepthMode.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum DepthMode 4 | { 5 | Never = 512, 6 | Less = 513, 7 | Equal = 514, 8 | LessOrEqual = 515, 9 | Greater = 516, 10 | NotEqual = 517, 11 | GreaterOrEqual = 518, 12 | Always = 519 13 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/FBOTarget.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum FBOTarget 4 | { 5 | ReadFramebuffer = 36008, 6 | DrawFramebuffer = 36009, 7 | Framebuffer = 36160, 8 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/PolyFace.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum PolyFace 4 | { 5 | Front = 1028, 6 | Back = 1029, 7 | FrontAndBack = 1032, 8 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/RasterizerState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public struct RasterizerState 4 | { 5 | public bool EnableDepthTest = true; 6 | public bool EnableDepthWrite = true; 7 | public DepthMode DepthMode = DepthMode.LessOrEqual; 8 | 9 | public bool EnableBlend = true; 10 | public BlendType BlendSrc = BlendType.SrcAlpha; 11 | public BlendType BlendDst = BlendType.OneMinusSrcAlpha; 12 | public BlendMode BlendMode = BlendMode.Add; 13 | 14 | public bool EnableCulling = true; 15 | public PolyFace FaceCulling = PolyFace.Back; 16 | 17 | public WindingOrder WindingOrder = WindingOrder.CCW; 18 | 19 | 20 | public RasterizerState() 21 | { 22 | } 23 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/TextureMag.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum TextureMag 4 | { 5 | Nearest = 9728, 6 | Linear = 9729 7 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/TextureMin.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum TextureMin 4 | { 5 | Nearest = 9728, 6 | Linear = 9729, 7 | NearestMipmapNearest = 9984, 8 | LinearMipmapNearest = 9985, 9 | NearestMipmapLinear = 9986, 10 | LinearMipmapLinear = 9987 11 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/TextureType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum TextureType 4 | { 5 | Texture1D, 6 | Texture2D, 7 | Texture3D, 8 | TextureCubeMap, 9 | Texture1DArray, 10 | Texture2DArray, 11 | TextureCubeMapArray, 12 | Texture2DMultisample, 13 | Texture2DMultisampleArray, 14 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/TextureWrap.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum TextureWrap 4 | { 5 | Repeat = 10497, 6 | ClampToBorder = 33069, 7 | ClampToEdge = 33071, 8 | MirroredRepeat = 33648 9 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/Topology.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum Topology 4 | { 5 | Points, 6 | Lines, 7 | LineLoop, 8 | LineStrip, 9 | Triangles, 10 | TriangleStrip, 11 | TriangleFan, 12 | Quads 13 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Primitives/WindingOrder.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum WindingOrder 4 | { 5 | CW = 2304, 6 | CCW = 2305 7 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Shaders/ShaderSourceDescriptor.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// Describes a shader source file. 5 | /// 6 | public class ShaderSourceDescriptor 7 | { 8 | /// 9 | /// Specifies the type of shader. 10 | /// 11 | public ShaderType Type { get; } 12 | 13 | /// 14 | /// Specifies the source code for this shader. 15 | /// 16 | public string Source { get; } 17 | 18 | 19 | /// 20 | /// Initializes a new instance of the ShaderSourceDescriptor. 21 | /// 22 | /// Specifies the type of the shader. 23 | /// The source code for this shader. 24 | public ShaderSourceDescriptor(ShaderType type, string source) 25 | { 26 | Type = type; 27 | Source = source; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Shaders/ShaderType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | public enum ShaderType 4 | { 5 | Vertex = 35633, 6 | Fragment = 35632, 7 | Geometry = 36313 8 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Windowing/WindowState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Rendering; 2 | 3 | /// 4 | /// Represents the display state of a window. 5 | /// 6 | public enum WindowState 7 | { 8 | /// 9 | /// The window is in a normal (windowed) state. 10 | /// 11 | Normal, 12 | 13 | /// 14 | /// The window is minimized (iconified). 15 | /// 16 | Minimized, 17 | 18 | /// 19 | /// The window covers the whole working area, which includes the desktop but not the taskbar and/or panels. 20 | /// 21 | Maximized, 22 | 23 | /// 24 | /// The window covers the whole screen, including any taskbars and/or panels. 25 | /// 26 | Fullscreen 27 | } -------------------------------------------------------------------------------- /src/Core/Rendering/Windowing/WindowingSettings.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | 3 | namespace KorpiEngine.Rendering; 4 | 5 | /// 6 | /// Defines the settings for the window. 7 | /// 8 | public readonly struct WindowingSettings 9 | { 10 | /// 11 | /// The text shown in the window title bar. 12 | /// 13 | public readonly string WindowTitle; 14 | 15 | /// 16 | /// The size of the window. 17 | /// must not be set to State.Fullscreen for this to take effect. 18 | /// 19 | public readonly Int2 WindowSize; 20 | 21 | /// 22 | /// The display state of the window. 23 | /// 24 | public readonly WindowState State; 25 | 26 | 27 | public WindowingSettings(string windowTitle, Int2 windowSize, WindowState state) 28 | { 29 | WindowTitle = windowTitle; 30 | WindowSize = windowSize; 31 | State = state; 32 | } 33 | 34 | 35 | public static WindowingSettings Windowed(string windowTitle, Int2 windowSize) 36 | { 37 | return new WindowingSettings(windowTitle, windowSize, WindowState.Normal); 38 | } 39 | 40 | 41 | public static WindowingSettings Fullscreen(string windowTitle) 42 | { 43 | return new WindowingSettings(windowTitle, new Int2(1920, 1080), WindowState.Fullscreen); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Core/SceneManagement/EmptyScene.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.SceneManagement; 2 | 3 | public class EmptyScene : Scene; -------------------------------------------------------------------------------- /src/Core/SceneManagement/EntitySceneRenderer.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | using KorpiEngine.Tools; 3 | 4 | namespace KorpiEngine.SceneManagement; 5 | 6 | internal sealed class EntitySceneRenderer 7 | { 8 | private readonly List _cameras = []; 9 | 10 | /// 11 | /// Cameras that will be rendered to the screen. 12 | /// 13 | private readonly PriorityQueue _renderQueueScreen = new(); 14 | 15 | /// 16 | /// Cameras that will be rendered to a RenderTexture. 17 | /// 18 | private readonly PriorityQueue _renderQueueTexture = new(); 19 | 20 | 21 | public void TryRegisterComponent(T c) 22 | { 23 | switch (c) 24 | { 25 | case Camera camera: 26 | _cameras.Add(camera); 27 | break; 28 | } 29 | } 30 | 31 | 32 | public void TryUnregisterComponent(T c) 33 | { 34 | switch (c) 35 | { 36 | case Camera camera: 37 | _cameras.Remove(camera); 38 | break; 39 | } 40 | } 41 | 42 | 43 | [ProfileInternal] 44 | public void Render() 45 | { 46 | // Construct ordered render queues 47 | foreach (Camera c in _cameras) 48 | { 49 | if (!c.EnabledInHierarchy) 50 | continue; 51 | 52 | if (c.TargetTexture.IsAvailable) 53 | _renderQueueTexture.Enqueue(c, c.RenderPriority); 54 | else 55 | _renderQueueScreen.Enqueue(c, c.RenderPriority); 56 | } 57 | 58 | // Render all cameras that target a RenderTexture 59 | while (_renderQueueTexture.Count > 0) 60 | _renderQueueTexture.Dequeue().Render(); 61 | 62 | // Render all cameras that target the screen 63 | while (_renderQueueScreen.Count > 0) 64 | _renderQueueScreen.Dequeue().Render(); 65 | 66 | // Clear the render queues 67 | _renderQueueScreen.Clear(); 68 | _renderQueueTexture.Clear(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Core/SceneManagement/SceneLoadMode.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.SceneManagement; 2 | 3 | /// 4 | /// Determines how to load a scene. 5 | /// The new scene will always be loaded before any old scenes are unloaded. 6 | /// 7 | public enum SceneLoadMode 8 | { 9 | /// 10 | /// This scene will replace all currently loaded scenes, unloading them in the process. 11 | /// 12 | Single, 13 | 14 | /// 15 | /// This scene will be loaded on top of all other pre-existing scenes. 16 | /// 17 | Additive 18 | } -------------------------------------------------------------------------------- /src/Core/Threading/Jobs/ExampleJob.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Test job with an integer result equal to the product of 7 and 6. 5 | /// 6 | public class ExampleJob : KorpiJob 7 | { 8 | public override float GetPriority() => WorkItemPriority.NORMAL; 9 | 10 | 11 | public override void Execute() 12 | { 13 | // Do some work. 14 | int result = 7 * 6; 15 | 16 | // Set the result of the job. 17 | SetResult(result); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Core/Threading/Jobs/IAwaitable.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace KorpiEngine.Threading; 4 | 5 | public interface IAwaitable : INotifyCompletion 6 | { 7 | public bool IsCompleted { get; } 8 | 9 | public void GetResult(); 10 | 11 | public IAwaitable GetAwaiter(); 12 | } 13 | 14 | public interface IAwaitable : INotifyCompletion 15 | { 16 | public bool IsCompleted { get; } 17 | 18 | public T GetResult(); 19 | 20 | public IAwaitable GetAwaiter(); 21 | } -------------------------------------------------------------------------------- /src/Core/Threading/Jobs/IKorpiJob.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Represents a job that can be executed by a . 5 | /// 6 | public interface IKorpiJob 7 | { 8 | /// 9 | /// The current completion state of the job. 10 | /// 11 | public JobCompletionState CompletionState { get; } 12 | 13 | /// 14 | /// Returns the current priority of the job. 15 | /// 16 | /// 17 | public float GetPriority(); 18 | 19 | /// 20 | /// Executes the job. 21 | /// 22 | public void Execute(); 23 | 24 | /// 25 | /// Signals the job that it has been completed. 26 | /// 27 | /// The new completion state of the job. 28 | public void SignalCompletion(JobCompletionState completionState); 29 | } -------------------------------------------------------------------------------- /src/Core/Threading/Jobs/JobCompletionState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Represents the completion state of a . 5 | /// 6 | public enum JobCompletionState 7 | { 8 | /// 9 | /// The job has not been completed yet. 10 | /// 11 | None, 12 | 13 | /// 14 | /// The job has been completed successfully. 15 | /// 16 | Completed, 17 | 18 | /// 19 | /// The job has been aborted. 20 | /// 21 | Aborted 22 | } -------------------------------------------------------------------------------- /src/Core/Threading/ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace KorpiEngine.Threading; 4 | 5 | /// 6 | /// Thread-safe object pool. 7 | /// 8 | /// 9 | public sealed class ObjectPool : IDisposable where T : class 10 | { 11 | private readonly ConcurrentBag _objects; 12 | private readonly SemaphoreSlim _semaphore; 13 | private readonly Func _objectGenerator; 14 | 15 | public ObjectPool(int capacity, Func objectGenerator) 16 | { 17 | if (capacity <= 0) 18 | { 19 | throw new ArgumentException("Capacity must be greater than zero.", nameof(capacity)); 20 | } 21 | 22 | _objects = new ConcurrentBag(); 23 | _semaphore = new SemaphoreSlim(capacity, capacity); 24 | _objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator)); 25 | } 26 | 27 | public T Get() 28 | { 29 | _semaphore.Wait(); 30 | 31 | return _objects.TryTake(out T? item) ? item : _objectGenerator(); 32 | } 33 | 34 | public void Return(T item) 35 | { 36 | ArgumentNullException.ThrowIfNull(item); 37 | 38 | _objects.Add(item); 39 | _semaphore.Release(); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | _semaphore.Dispose(); 45 | while (_objects.TryTake(out T? item)) 46 | { 47 | (item as IDisposable)?.Dispose(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Core/Threading/Pooling/IJobPool.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Represents an object that can pool jobs. 5 | /// 6 | public interface IJobPool 7 | { 8 | public void EnqueueWorkItem(IKorpiJob korpiJob); 9 | 10 | 11 | public void FixedUpdate(); 12 | 13 | 14 | public void Shutdown(); 15 | } -------------------------------------------------------------------------------- /src/Core/Threading/Pooling/QueueType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Queue type used by the thread-pool to handle updates pushed to main. 5 | /// 6 | public enum QueueType 7 | { 8 | /// 9 | /// Executed in a batch with all other actions each frame. 10 | /// 11 | Default, 12 | 13 | /// 14 | /// Only executed if within the invocation budget of the current frame. 15 | /// Useful for expensive operations such as instantiation to avoid slamming Unity with a ton of work from 16 | /// a large number of jobs. 17 | /// 18 | Throttled 19 | } -------------------------------------------------------------------------------- /src/Core/Threading/Pooling/WorkItemPriority.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Represents the execution priority of a job. 5 | /// Higher priority (low numerical priority value) jobs are executed first. 6 | /// 7 | public static class WorkItemPriority 8 | { 9 | /// 10 | /// The task should be executed as soon as possible. 11 | /// 12 | public const float HIGHEST = 0; 13 | 14 | /// 15 | /// A high priority. 16 | /// 17 | public const float HIGH = 25; 18 | 19 | /// 20 | /// The default priority. 21 | /// 22 | public const float NORMAL = 50; 23 | 24 | /// 25 | /// A low priority. 26 | /// 27 | public const float LOW = 75; 28 | 29 | /// 30 | /// The lowest priority. 31 | /// The task will be executed when all other tasks have been completed. 32 | /// 33 | public const float LOWEST = 100; 34 | } -------------------------------------------------------------------------------- /src/Core/Threading/Threads/ThreadConfig.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Parameters for thread states. 5 | /// A value of -1 for a specific state will disable that state and any others below it. 6 | /// 7 | public struct ThreadConfig(int spinCycles, int yieldCycles, int napCycles, int napInterval, int sleepInterval) 8 | { 9 | /// 10 | /// Number of spin cycles before the thread drops to the yielding state. 11 | /// 12 | public int SpinCycles { get; set; } = spinCycles; 13 | 14 | /// 15 | /// Number of yield cycles before the thread drops to the napping state. 16 | /// 17 | public int YieldCycles { get; set; } = yieldCycles; 18 | 19 | /// 20 | /// Number of nap cycles before the thread drops to the sleeping state. 21 | /// 22 | public int NapCycles { get; set; } = napCycles; 23 | 24 | /// 25 | /// How long the thread should sleep in milliseconds between nap cycles. 26 | /// 27 | public int NapInterval { get; set; } = napInterval; 28 | 29 | /// 30 | /// How long the thread should sleep in milliseconds between sleep cycles. 31 | /// 32 | public readonly int SleepInterval = sleepInterval; 33 | 34 | 35 | public static ThreadConfig Default() 36 | { 37 | return new ThreadConfig( 38 | 100, 39 | 500, 40 | 5, 41 | 1, 42 | 5 43 | ); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Core/Threading/Threads/ThreadStatus.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Threading; 2 | 3 | /// 4 | /// Status of a . 5 | /// 6 | public enum ThreadStatus 7 | { 8 | /// 9 | /// Thread is actively spinning and checking for work. 10 | /// 11 | Spinning = 0, 12 | 13 | /// 14 | /// Thread is checking for work but yielding if there is none. 15 | /// 16 | Yielding = 1, 17 | 18 | /// 19 | /// Thread is sleeping for short intervals while periodically checking for work. 20 | /// 21 | Napping = 2, 22 | 23 | /// 24 | /// Thread is sleeping for longer intervals while periodically checking for work. 25 | /// 26 | Sleeping = 3, 27 | 28 | /// 29 | /// Thread has completed shutting down. 30 | /// 31 | Offline = 4 32 | } -------------------------------------------------------------------------------- /src/Core/Tools/Debug.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using KorpiEngine.Utils; 3 | 4 | namespace KorpiEngine.Tools; 5 | 6 | internal static class Debug 7 | { 8 | [StackTraceHidden] 9 | [Conditional("KORPI_TOOLS")] 10 | public static void Assert(bool condition, string message) 11 | { 12 | if (!condition) 13 | throw new KorpiException($"Assertion failed (Engine bug): {message}"); 14 | } 15 | 16 | /// 17 | /// Throws an exception if the current thread is not the main thread. 18 | /// 19 | [StackTraceHidden] 20 | [Conditional("KORPI_TOOLS")] 21 | public static void AssertMainThread(bool shouldBeMainThread) 22 | { 23 | string log = shouldBeMainThread ? 24 | "This method must be called from the main thread." : 25 | "This method must not be called from the main thread."; 26 | Assert(Application.IsMainThread == shouldBeMainThread, log); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Core/Tools/Logging/IKorpiLogger.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools.Logging; 2 | 3 | public interface IKorpiLogger 4 | { 5 | public bool IsFatalEnabled { get; } 6 | public bool IsWarnEnabled { get; } 7 | public bool IsInfoEnabled { get; } 8 | public bool IsDebugEnabled { get; } 9 | public bool IsErrorEnabled { get; } 10 | 11 | public void Verbose(object message, Exception? exception = null); 12 | public void VerboseFormat(IFormatProvider provider, string format, params object[] args); 13 | public void VerboseFormat(string format, params object[] args); 14 | 15 | public void Debug(object message, Exception? exception = null); 16 | public void DebugFormat(IFormatProvider provider, string format, params object[] args); 17 | public void DebugFormat(string format, params object[] args); 18 | 19 | public void Error(object message, Exception? exception = null); 20 | public void ErrorFormat(IFormatProvider provider, string format, params object[] args); 21 | public void ErrorFormat(string format, params object[] args); 22 | 23 | public void Fatal(object message, Exception? exception = null); 24 | public void FatalFormat(IFormatProvider provider, string format, params object[] args); 25 | public void FatalFormat(string format, params object[] args); 26 | 27 | public void Info(object message, Exception? exception = null); 28 | public void InfoFormat(IFormatProvider provider, string format, params object[] args); 29 | public void InfoFormat(string format, params object[] args); 30 | 31 | public void Warn(object message, Exception? exception = null); 32 | public void WarnFormat(IFormatProvider provider, string format, params object[] args); 33 | public void WarnFormat(string format, params object[] args); 34 | } -------------------------------------------------------------------------------- /src/Core/Tools/Profiling/DummyProfilerZone.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools; 2 | 3 | /// 4 | /// Used when profiling is disabled. 5 | /// 6 | internal struct DummyProfilerZone : IProfilerZone 7 | { 8 | public void EmitName(string name) { } 9 | public void EmitColor(uint color) { } 10 | public void EmitText(string text) { } 11 | public void Dispose() { } 12 | } -------------------------------------------------------------------------------- /src/Core/Tools/Profiling/IProfilerZone.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools; 2 | 3 | /// 4 | /// Represents a zone in the profiler. 5 | /// 6 | public interface IProfilerZone : IDisposable 7 | { 8 | /// 9 | /// Emits a custom name for this zone. 10 | /// 11 | /// The name to emit. 12 | public void EmitName(string name); 13 | 14 | /// 15 | /// Emits a custom color for this zone. 16 | /// 17 | /// The color to emit. 18 | public void EmitColor(uint color); 19 | 20 | /// 21 | /// Emits custom text for this zone. 22 | /// 23 | /// The text to emit. 24 | public void EmitText(string text); 25 | } -------------------------------------------------------------------------------- /src/Core/Tools/Profiling/ProfilePlotType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools; 2 | 3 | public enum ProfilePlotType 4 | { 5 | /// 6 | /// Values will be displayed as plain numbers. 7 | /// 8 | Number = 0, 9 | 10 | /// 11 | /// Treats the values as memory sizes. Will display kilobytes, megabytes, etc. 12 | /// 13 | Memory = 1, 14 | 15 | /// 16 | /// Values will be displayed as percentage (with value 100 being equal to 100%). 17 | /// 18 | Percentage = 2 19 | } -------------------------------------------------------------------------------- /src/Core/Tools/Profiling/Tracy/TracyProfilerZone.cs: -------------------------------------------------------------------------------- 1 | using bottlenoselabs.C2CS.Runtime; 2 | using Tracy; 3 | 4 | namespace KorpiEngine.Tools; 5 | 6 | public readonly struct TracyProfilerZone : IProfilerZone 7 | { 8 | public readonly PInvoke.TracyCZoneCtx Context; 9 | 10 | public uint Id => Context.Data.Id; 11 | public int Active => Context.Data.Active; 12 | 13 | 14 | internal TracyProfilerZone(PInvoke.TracyCZoneCtx context) 15 | { 16 | Context = context; 17 | } 18 | 19 | 20 | public void EmitName(string name) 21 | { 22 | using CString namestr = TracyProfiler.GetCString(name, out ulong nameln); 23 | PInvoke.TracyEmitZoneName(Context, namestr, nameln); 24 | } 25 | 26 | 27 | public void EmitColor(uint color) 28 | { 29 | PInvoke.TracyEmitZoneColor(Context, color); 30 | } 31 | 32 | 33 | public void EmitText(string text) 34 | { 35 | using CString textstr = TracyProfiler.GetCString(text, out ulong textln); 36 | PInvoke.TracyEmitZoneText(Context, textstr, textln); 37 | } 38 | 39 | 40 | public void Dispose() 41 | { 42 | PInvoke.TracyEmitZoneEnd(Context); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Core/Tools/Serialization/ISerializable.cs: -------------------------------------------------------------------------------- 1 | using static KorpiEngine.Tools.Serialization.Serializer; 2 | 3 | namespace KorpiEngine.Tools.Serialization 4 | { 5 | public interface ISerializable 6 | { 7 | public SerializedProperty Serialize(SerializationContext ctx); 8 | public void Deserialize(SerializedProperty value, SerializationContext ctx); 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/Tools/Serialization/ISerializationCallbackReceiver.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools.Serialization 2 | { 3 | /// 4 | /// Sometimes you dont want a Constructor to be called when deserializing, but still need todo some work after the object has been created 5 | /// This interface allows you to do that 6 | /// 7 | public interface ISerializationCallbackReceiver 8 | { 9 | /// 10 | /// Called right before the Serializer serializes this object 11 | /// 12 | public void OnBeforeSerialize(); 13 | 14 | /// 15 | /// Called right after the Serializer deserializes this object 16 | /// 17 | public void OnAfterDeserialize(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Core/Tools/Serialization/SerializationAttributes.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools.Serialization 2 | { 3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] 4 | public sealed class IgnoreOnNullAttribute : Attribute { } 5 | 6 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] 7 | public class SerializeIgnoreAttribute : Attribute 8 | { 9 | } 10 | 11 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] 12 | public class SerializeFieldAttribute : Attribute 13 | { 14 | } 15 | 16 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] 17 | public class FormerlySerializedAsAttribute : Attribute 18 | { 19 | public string oldName { get; set; } 20 | public FormerlySerializedAsAttribute(string name) => oldName = name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Core/Tools/Serialization/SerializedProperty.List.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Tools.Serialization 2 | { 3 | public sealed partial class SerializedProperty 4 | { 5 | public List List => (Value as List)!; 6 | 7 | public SerializedProperty this[int tagIdx] 8 | { 9 | get { return Get(tagIdx); } 10 | set { List[tagIdx] = value; } 11 | } 12 | 13 | public SerializedProperty Get(int tagIdx) 14 | { 15 | if (TagType != PropertyType.List) 16 | throw new System.InvalidOperationException("Cannot get tag from non-list tag"); 17 | return List[tagIdx]; 18 | } 19 | 20 | public void ListAdd(SerializedProperty tag) 21 | { 22 | if (TagType != PropertyType.List) 23 | throw new System.InvalidOperationException("Cannot add tag to non-list tag"); 24 | List.Add(tag); 25 | tag.Parent = this; 26 | } 27 | 28 | public void ListRemove(SerializedProperty tag) 29 | { 30 | if (TagType != PropertyType.List) 31 | throw new System.InvalidOperationException("Cannot remove tag from non-list tag"); 32 | List.Remove(tag); 33 | tag.Parent = null; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Core/UI/DearImGui/EntityComponentEditor.cs: -------------------------------------------------------------------------------- 1 | #if KORPI_TOOLS 2 | using ImGuiNET; 3 | using KorpiEngine.Entities; 4 | 5 | namespace KorpiEngine.UI.DearImGui; 6 | 7 | public abstract class EntityComponentEditor : ImGuiWindow 8 | { 9 | public override string Title => $"Entity Component Editor - {_componentType}"; 10 | protected override ImGuiWindowFlags Flags => ImGuiWindowFlags.AlwaysAutoResize; 11 | 12 | private readonly EntityComponent _target; 13 | private readonly string _componentType; 14 | 15 | 16 | protected EntityComponentEditor(EntityComponent target) : base(false) 17 | { 18 | _target = target; 19 | _target.Destroying += Destroy; 20 | _componentType = _target.GetType().Name; 21 | } 22 | 23 | 24 | protected sealed override void DrawContent() 25 | { 26 | ImGui.Text($"Component: {_componentType}"); 27 | ImGui.Text($"Entity: {_target.Entity.Name}"); 28 | ImGui.Separator(); 29 | 30 | DrawEditor(); 31 | } 32 | 33 | 34 | protected abstract void DrawEditor(); 35 | 36 | 37 | protected override void OnDestroy() 38 | { 39 | _target.Destroying -= Destroy; 40 | } 41 | } 42 | #endif -------------------------------------------------------------------------------- /src/Core/UI/DearImGui/IImGuiRenderer.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.UI.DearImGui; 2 | 3 | public interface IImGuiRenderer 4 | { 5 | public void Update(); 6 | public void Render(); 7 | } -------------------------------------------------------------------------------- /src/Core/UI/DearImGui/ImGuiWindow.cs: -------------------------------------------------------------------------------- 1 | using ImGuiNET; 2 | 3 | namespace KorpiEngine.UI.DearImGui; 4 | 5 | public abstract class ImGuiWindow 6 | { 7 | protected virtual ImGuiWindowFlags Flags { get; } = ImGuiWindowFlags.None; 8 | 9 | public abstract string Title { get; } 10 | 11 | public bool IsVisible { get; private set; } = true; 12 | 13 | private bool _isDestroyed; 14 | 15 | 16 | protected ImGuiWindow(bool autoRegister) 17 | { 18 | if (autoRegister) 19 | ImGuiWindowManager.RegisterWindow(this); 20 | } 21 | 22 | 23 | public void ToggleVisibility() 24 | { 25 | IsVisible = !IsVisible; 26 | } 27 | 28 | 29 | public void Update() 30 | { 31 | // Only update if the window is visible. 32 | if (!IsVisible || _isDestroyed) 33 | return; 34 | 35 | PreUpdate(); 36 | 37 | ImGui.Begin(Title, Flags); 38 | 39 | DrawContent(); 40 | 41 | ImGui.End(); 42 | } 43 | 44 | 45 | public void Destroy() 46 | { 47 | if (_isDestroyed) 48 | return; 49 | 50 | OnDestroy(); 51 | 52 | ImGuiWindowManager.UnregisterWindow(this); 53 | _isDestroyed = true; 54 | } 55 | 56 | 57 | protected virtual void PreUpdate() { } 58 | protected virtual void OnDestroy() { } 59 | 60 | 61 | protected abstract void DrawContent(); 62 | } -------------------------------------------------------------------------------- /src/Core/UI/DearImGui/ImGuiWindowManager.cs: -------------------------------------------------------------------------------- 1 | using ImGuiNET; 2 | 3 | namespace KorpiEngine.UI.DearImGui; 4 | 5 | public static class ImGuiWindowManager 6 | { 7 | private static readonly Dictionary RegisteredWindows = new(); 8 | 9 | private static bool shouldRenderWindows = true; 10 | 11 | 12 | public static void RegisterWindow(ImGuiWindow window) 13 | { 14 | ArgumentNullException.ThrowIfNull(window); 15 | 16 | RegisteredWindows.Add(window, window.GetType().Name); 17 | } 18 | 19 | 20 | public static void UnregisterWindow(ImGuiWindow window) 21 | { 22 | if (window == null) 23 | throw new ArgumentNullException(nameof(window)); 24 | 25 | RegisteredWindows.Remove(window); 26 | } 27 | 28 | 29 | internal static void Update() 30 | { 31 | ImGui.Begin("Windows", ImGuiWindowFlags.AlwaysAutoResize); 32 | ImGui.Checkbox("Draw Windows", ref shouldRenderWindows); 33 | ImGui.Separator(); 34 | foreach (KeyValuePair kvp in RegisteredWindows) 35 | { 36 | bool windowVisible = kvp.Key.IsVisible; 37 | if (ImGui.Checkbox($"{kvp.Value} -> {kvp.Key.Title}", ref windowVisible)) 38 | kvp.Key.ToggleVisibility(); 39 | } 40 | ImGui.End(); 41 | 42 | if (!shouldRenderWindows) 43 | return; 44 | 45 | foreach (ImGuiWindow window in RegisteredWindows.Keys) 46 | window.Update(); 47 | } 48 | 49 | 50 | internal static void Shutdown() 51 | { 52 | foreach (ImGuiWindow window in RegisteredWindows.Keys) 53 | window.Destroy(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Core/UI/EditorGUI.cs: -------------------------------------------------------------------------------- 1 | #if KORPI_TOOLS 2 | using KorpiEngine.UI.DearImGui; 3 | 4 | namespace KorpiEngine.UI; 5 | 6 | public static class EditorGUI 7 | { 8 | public static EntityEditor EntityEditorWindow { get; private set; } = null!; 9 | 10 | 11 | public static void Initialize() 12 | { 13 | EntityEditorWindow = new EntityEditor(); 14 | } 15 | 16 | 17 | public static void Deinitialize() 18 | { 19 | EntityEditorWindow.Destroy(); 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /src/Core/UI/GUI.cs: -------------------------------------------------------------------------------- 1 | using ImGuiNET; 2 | using KorpiEngine.UI.DearImGui; 3 | 4 | namespace KorpiEngine.UI; 5 | 6 | /// 7 | /// Collection of methods for drawing GUI elements from inside the OnDrawGUI method. 8 | /// All GUI methods must be called between GUI.Begin and GUI.End. 9 | /// 10 | public static class GUI 11 | { 12 | internal static bool AllowDraw { get; set; } 13 | internal static bool IsDrawing { get; private set; } 14 | 15 | public static bool WantCaptureKeyboard { get; internal set; } 16 | public static bool WantCaptureMouse { get; internal set; } 17 | public static DebugStatsWindow DebugStatsWindow { get; private set; } = null!; 18 | 19 | private static bool CanDraw => AllowDraw && IsDrawing; 20 | 21 | 22 | public static void Initialize() 23 | { 24 | DebugStatsWindow = new DebugStatsWindow(); 25 | } 26 | 27 | 28 | public static void Deinitialize() 29 | { 30 | DebugStatsWindow.Destroy(); 31 | } 32 | 33 | 34 | public static void Begin(string title, ImGuiWindowFlags flags = ImGuiWindowFlags.AlwaysAutoResize) 35 | { 36 | if (IsDrawing) 37 | return; 38 | 39 | ImGui.Begin(title, flags); 40 | 41 | IsDrawing = true; 42 | } 43 | 44 | 45 | public static void End() 46 | { 47 | if (!IsDrawing) 48 | return; 49 | 50 | ImGui.End(); 51 | 52 | IsDrawing = false; 53 | } 54 | 55 | 56 | public static void Text(string text) 57 | { 58 | if (!CanDraw) 59 | return; 60 | 61 | ImGui.Text(text); 62 | } 63 | 64 | 65 | public static void FloatSlider(string text, ref float current, float min, float max) 66 | { 67 | if (!CanDraw) 68 | return; 69 | 70 | ImGui.SliderFloat(text, ref current, min, max); 71 | } 72 | } -------------------------------------------------------------------------------- /src/Core/Utils/AssemblyManager.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | internal static class AssemblyManager 4 | { 5 | public static void Initialize() 6 | { 7 | OnApplicationUnloadAttribute.FindAll(); 8 | OnApplicationLoadAttribute.FindAll(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Core/Utils/Exceptions/IdOverflowException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | internal class IdOverflowException(string message) : KorpiException(message); -------------------------------------------------------------------------------- /src/Core/Utils/Exceptions/KorpiException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | internal class KorpiException : Exception 4 | { 5 | public KorpiException(string? message) : base(message) 6 | { 7 | } 8 | 9 | 10 | public KorpiException(string? message, Exception? innerException) : base(message, innerException) 11 | { 12 | } 13 | } -------------------------------------------------------------------------------- /src/Core/Utils/Internal/Hashing.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | internal static class Hashing 4 | { 5 | public static int GetXorHashCode(HashSet set) 6 | { 7 | int hashCode = 0; 8 | 9 | foreach (T item in set) 10 | hashCode ^= HashCode.Combine(set.Comparer.GetHashCode(item!)); 11 | 12 | return hashCode; 13 | } 14 | 15 | public static int GetAdditiveHashCode(SortedSet set) 16 | { 17 | unchecked // Overflow is fine, wrap 18 | { 19 | int hash = 17; 20 | 21 | foreach (T item in set) 22 | hash = hash * 23 + item!.GetHashCode(); 23 | 24 | return hash; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Core/Utils/Internal/MemoryReleaseSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using KorpiEngine.Tools; 3 | 4 | namespace KorpiEngine.Utils; 5 | 6 | /// 7 | /// A system that handles releasing memory. 8 | /// 9 | internal static class MemoryReleaseSystem 10 | { 11 | // Thread-safe queue for IDisposable objects that need to be disposed on the main thread. 12 | private static readonly ConcurrentQueue DisposeQueue = new(); 13 | 14 | 15 | public static void Initialize() 16 | { 17 | Debug.Assert(Application.IsMainThread, "Memory release system can only be initialized on the main thread!"); 18 | } 19 | 20 | /// 21 | /// Adds an object to the disposal queue. 22 | /// The object will be later disposed on the main thread. 23 | /// 24 | public static void AddToDisposeQueue(IDisposable obj) 25 | { 26 | DisposeQueue.Enqueue(obj); 27 | } 28 | 29 | 30 | [ProfileInternal] 31 | public static void ProcessDisposeQueue() 32 | { 33 | Debug.Assert(Application.IsMainThread, "Dispose queue can only be processed on the main thread!"); 34 | 35 | while (DisposeQueue.TryDequeue(out IDisposable? obj)) 36 | { 37 | obj.Dispose(); 38 | } 39 | } 40 | 41 | 42 | public static void Shutdown() 43 | { 44 | Debug.Assert(Application.IsMainThread, "Memory release system can only be shut down on the main thread!"); 45 | 46 | // Process all remaining objects in the queue 47 | ProcessDisposeQueue(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Core/Utils/Platform/DisplayInfo.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | using KorpiEngine.Rendering; 3 | 4 | namespace KorpiEngine.Utils; 5 | 6 | /// 7 | /// Contains information about the display the application is running on. 8 | /// 9 | public static class DisplayInfo 10 | { 11 | /// 12 | /// The current display resolution. 13 | /// 14 | public static Int2 Resolution { get; private set; } 15 | 16 | 17 | internal static void Update(DisplayState state) 18 | { 19 | Resolution = state.Resolution; 20 | } 21 | } -------------------------------------------------------------------------------- /src/Core/Utils/Platform/GraphicsInfo.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | 3 | namespace KorpiEngine.Utils; 4 | 5 | /// 6 | /// Contains information about the current graphics driver. 7 | /// 8 | public static class GraphicsInfo 9 | { 10 | /// 11 | /// Maximum supported texture size of the current graphics driver. 12 | /// 13 | public static int MaxTextureSize { get; private set; } 14 | 15 | /// 16 | /// Maximum supported cube map texture size of the current graphics driver. 17 | /// 18 | public static int MaxCubeMapTextureSize { get; private set; } 19 | 20 | /// 21 | /// Maximum supported array texture layers of the current graphics driver. 22 | /// 23 | public static int MaxArrayTextureLayers { get; private set; } 24 | 25 | /// 26 | /// Maximum supported framebuffer color attachments of the current graphics driver. 27 | /// 28 | public static int MaxFramebufferColorAttachments { get; private set; } 29 | 30 | 31 | public static void Initialize(GraphicsContext context) 32 | { 33 | MaxTextureSize = context.Device.MaxTextureSize; 34 | MaxCubeMapTextureSize = context.Device.MaxCubeMapTextureSize; 35 | MaxArrayTextureLayers = context.Device.MaxArrayTextureLayers; 36 | MaxFramebufferColorAttachments = context.Device.MaxFramebufferColorAttachments; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Core/Utils/Platform/SystemInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace KorpiEngine.Utils; 4 | 5 | /// 6 | /// Contains information about the current system. 7 | /// 8 | public static class SystemInfo 9 | { 10 | public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 11 | public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 12 | public static bool IsFreeBSD() => RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); 13 | public static bool IsMac() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 14 | 15 | 16 | /// 17 | /// Gets the number of processors available to the current process. 18 | /// 19 | public static int ProcessorCount { get; private set; } 20 | 21 | /// 22 | /// ID of the thread updating the main window. 23 | /// 24 | public static int MainThreadId { get; private set; } 25 | 26 | 27 | public static void Initialize() 28 | { 29 | ProcessorCount = Environment.ProcessorCount; 30 | MainThreadId = Environment.CurrentManagedThreadId; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Core/Utils/Platform/WindowInfo.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Mathematics; 2 | using KorpiEngine.Rendering; 3 | 4 | namespace KorpiEngine.Utils; 5 | 6 | public static class WindowInfo 7 | { 8 | public readonly struct WindowResizeEventArgs(int width, int height, float aspectRatio) 9 | { 10 | public readonly int Width = width; 11 | public readonly int Height = height; 12 | public readonly float AspectRatio = aspectRatio; 13 | } 14 | 15 | /// 16 | /// Called when the client window has resized. 17 | /// 18 | public static event Action? WindowResized; 19 | 20 | /// 21 | /// Size of the client window. 22 | /// 23 | public static Int2 ClientSize { get; private set; } 24 | 25 | /// 26 | /// Width of the client window. 27 | /// 28 | public static int ClientWidth => ClientSize.X; 29 | 30 | /// 31 | /// Height of the client window. 32 | /// 33 | public static int ClientHeight => ClientSize.Y; 34 | 35 | /// 36 | /// Aspect ratio of the client window. 37 | /// 38 | public static float ClientAspectRatio { get; private set; } 39 | 40 | 41 | internal static void Update(GraphicsContext context) 42 | { 43 | ClientSize = context.Window.Size; 44 | ClientAspectRatio = ClientWidth / (float)ClientHeight; 45 | WindowResized?.Invoke(new WindowResizeEventArgs(ClientWidth, ClientHeight, ClientAspectRatio)); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Core/Utils/TypeID.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | /// 4 | /// Provides a unique ID for a type. 5 | /// 6 | internal static class TypeID 7 | { 8 | private static ulong nextID; 9 | 10 | /// 11 | /// Provides a unique ID for a type. 12 | /// 13 | public static ulong Get() where T : class 14 | { 15 | if (Interlocked.Read(ref nextID) == ulong.MaxValue) 16 | throw new IdOverflowException("Type ID overflow."); 17 | 18 | return TypedIDs.ID; 19 | } 20 | 21 | // ReSharper disable once UnusedTypeParameter 22 | private static class TypedIDs 23 | { 24 | // ReSharper disable once StaticMemberInGenericType 25 | internal static readonly ulong ID; 26 | 27 | static TypedIDs() 28 | { 29 | ID = Interlocked.Increment(ref nextID); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Core/Utils/UUID.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Utils; 2 | 3 | /// 4 | /// A universally unique 128-bit identifier. 5 | /// 6 | public readonly struct UUID : IEquatable 7 | { 8 | public static readonly UUID Empty = Guid.Empty; 9 | 10 | private readonly Guid _value; 11 | 12 | 13 | public UUID() => _value = Guid.NewGuid(); 14 | private UUID(Guid guid) => _value = guid; 15 | 16 | 17 | public static bool TryParse(string input, out UUID result) 18 | { 19 | if (Guid.TryParse(input, out Guid guid)) 20 | { 21 | result = guid; 22 | return true; 23 | } 24 | 25 | result = Empty; 26 | return false; 27 | } 28 | 29 | 30 | public static UUID Parse(string input) => Guid.Parse(input); 31 | 32 | 33 | public static implicit operator Guid(UUID uuid) => uuid._value; 34 | public static implicit operator UUID(Guid value) => new(value); 35 | public static bool operator ==(UUID a, UUID b) => a._value == b._value; 36 | public static bool operator !=(UUID a, UUID b) => a._value != b._value; 37 | public bool Equals(UUID other) => _value.Equals(other._value); 38 | public override bool Equals(object? obj) => obj is UUID other && other._value == _value; 39 | public override int GetHashCode() => _value.GetHashCode(); 40 | public override string ToString() => _value.ToString(); 41 | } -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <_Parameter1>%(InternalsVisibleTo.Identity) 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_Parameter1>$(AssemblyName)%(InternalsVisibleToSuffix.Identity) 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(DefineConstants);TRACY_ENABLE 26 | 27 | -------------------------------------------------------------------------------- /src/KorpiEngine.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Authentication/Authenticator.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Authentication; 4 | 5 | public abstract class Authenticator 6 | { 7 | protected NetworkManager NetworkManager = null!; 8 | 9 | 10 | public virtual void Initialize(NetworkManager networkManager) 11 | { 12 | NetworkManager = networkManager; 13 | } 14 | 15 | 16 | /// 17 | /// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed. 18 | /// Server listens for this event automatically. 19 | /// 20 | public abstract event Action ConcludedAuthenticationResult; 21 | 22 | 23 | /// 24 | /// Called on the server immediately after a client connects. Can be used to send data to the client for authentication. 25 | /// 26 | /// Connection which is not yet authenticated. 27 | public virtual void OnRemoteConnection(NetworkConnection connection) 28 | { 29 | } 30 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Channel.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.HighLevel; 2 | 3 | /// 4 | /// Channel over which data is sent or received. 5 | /// 6 | public enum Channel : byte 7 | { 8 | /// 9 | /// Data will be sent ordered reliable. 10 | /// 11 | Reliable = 0, 12 | 13 | /// 14 | /// Data will be sent unreliable. 15 | /// 16 | Unreliable = 1 17 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Connections/KickReason.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | public enum KickReason : short 4 | { 5 | /// 6 | /// No reason was specified. 7 | /// 8 | Unset = 0, 9 | 10 | /// 11 | /// Client performed an action which could only be done if trying to exploit the server. 12 | /// 13 | ExploitAttempt = 1, 14 | 15 | /// 16 | /// Data received from the client could not be parsed. This rarely indicates an attack. 17 | /// 18 | MalformedData = 2, 19 | 20 | /// 21 | /// There was a problem with the server that required the client to be kicked. 22 | /// 23 | UnexpectedProblem = 3, 24 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Connections/LocalConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | /// 4 | /// States the local connection can be in. 5 | /// 6 | public enum LocalConnectionState : byte 7 | { 8 | /// 9 | /// Connection is fully stopped. 10 | /// 11 | Stopped = 0, 12 | 13 | /// 14 | /// Connection is starting but not yet established. 15 | /// 16 | Starting = 1, 17 | 18 | /// 19 | /// Connection is established. 20 | /// 21 | Started = 2, 22 | 23 | /// 24 | /// Connection is stopping. 25 | /// 26 | Stopping = 3 27 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Connections/RemoteConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | /// 4 | /// States a remote client can be in. 5 | /// 6 | public enum RemoteConnectionState : byte 7 | { 8 | /// 9 | /// Connection is fully stopped. 10 | /// 11 | Stopped = 0, 12 | 13 | /// 14 | /// Connection is established. 15 | /// 16 | Started = 2 17 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/EventArgs/ClientListArgs.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.EventArgs; 2 | 3 | public readonly struct ClientListArgs 4 | { 5 | public readonly List ClientIds; 6 | 7 | 8 | public ClientListArgs(List clientIds) 9 | { 10 | ClientIds = clientIds; 11 | } 12 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/EventArgs/ClientReceivedMessageArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.EventArgs; 4 | 5 | /// 6 | /// Container about a message received on the client, from the server. 7 | /// 8 | public readonly struct ClientReceivedMessageArgs 9 | { 10 | /// 11 | /// The message received. 12 | /// 13 | public readonly NetMessage Message; 14 | 15 | /// 16 | /// Channel data was received on. 17 | /// 18 | public readonly Channel Channel; 19 | 20 | 21 | public ClientReceivedMessageArgs(NetMessage message, Channel channel) 22 | { 23 | Message = message; 24 | Channel = channel; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/EventArgs/ServerReceivedMessageArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | using KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 3 | 4 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.EventArgs; 5 | 6 | /// 7 | /// Container about a message received on the server. 8 | /// 9 | public readonly struct ServerReceivedMessageArgs 10 | { 11 | /// 12 | /// The message received. 13 | /// 14 | public readonly NetMessage Message; 15 | 16 | /// 17 | /// Channel data was received on. 18 | /// 19 | public readonly Channel Channel; 20 | 21 | /// 22 | /// Connection of client which sent the message. 23 | /// 24 | public readonly NetworkConnection Connection; 25 | 26 | 27 | public ServerReceivedMessageArgs(NetMessage message, Channel channel, NetworkConnection connection) 28 | { 29 | Message = message; 30 | Channel = channel; 31 | Connection = connection; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/AuthPasswordNetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Sent by the client to the server to authenticate with a password. 7 | /// 8 | public class AuthPasswordNetMessage : NetMessage 9 | { 10 | public string Username { get; private set; } 11 | public string Password { get; private set; } 12 | 13 | 14 | /// 15 | /// Constructs a new AuthPasswordNetMessage. 16 | /// 17 | /// Username to authenticate with. Has a 255 character limit. 18 | /// Password to authenticate with. Has a 255 character limit. 19 | public AuthPasswordNetMessage(string username, string password) 20 | { 21 | Username = username; 22 | Password = password; 23 | } 24 | 25 | 26 | protected override void SerializeInternal(BitBuffer buffer) 27 | { 28 | buffer.AddString(Username); 29 | buffer.AddString(Password); 30 | } 31 | 32 | 33 | protected override void DeserializeInternal(BitBuffer buffer) 34 | { 35 | Username = buffer.ReadString(); 36 | Password = buffer.ReadString(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/AuthRequestNetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | public class AuthRequestNetMessage : NetMessage 6 | { 7 | public byte AuthenticationMethod { get; private set; } 8 | 9 | 10 | public AuthRequestNetMessage(byte authenticationMethod) 11 | { 12 | AuthenticationMethod = authenticationMethod; 13 | } 14 | 15 | 16 | protected override void SerializeInternal(BitBuffer buffer) 17 | { 18 | buffer.AddByte(AuthenticationMethod); 19 | } 20 | 21 | 22 | protected override void DeserializeInternal(BitBuffer buffer) 23 | { 24 | AuthenticationMethod = buffer.ReadByte(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/AuthResponseNetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Sent by the server to the client to indicate the result of the authentication process. 7 | /// 8 | public class AuthResponseNetMessage : NetMessage 9 | { 10 | public bool Success { get; private set; } 11 | public string Reason { get; private set; } 12 | 13 | 14 | public AuthResponseNetMessage(bool success, string? reason) 15 | { 16 | Success = success; 17 | Reason = reason ?? string.Empty; 18 | } 19 | 20 | 21 | protected override void SerializeInternal(BitBuffer buffer) 22 | { 23 | buffer.AddBool(Success); 24 | buffer.AddString(Reason); 25 | } 26 | 27 | 28 | protected override void DeserializeInternal(BitBuffer buffer) 29 | { 30 | Success = buffer.ReadBool(); 31 | Reason = buffer.ReadString(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/ClientConnectionChangeNetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Packet sent to all clients when a client connects or disconnects. 7 | /// 8 | public class ClientConnectionChangeNetMessage : NetMessage 9 | { 10 | public int ClientId { get; private set; } 11 | public bool Connected { get; private set; } 12 | 13 | 14 | public ClientConnectionChangeNetMessage(int clientId, bool connected) 15 | { 16 | ClientId = clientId; 17 | Connected = connected; 18 | } 19 | 20 | 21 | protected override void SerializeInternal(BitBuffer buffer) 22 | { 23 | buffer.AddInt(ClientId); 24 | buffer.AddBool(Connected); 25 | } 26 | 27 | 28 | protected override void DeserializeInternal(BitBuffer buffer) 29 | { 30 | ClientId = buffer.ReadInt(); 31 | Connected = buffer.ReadBool(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/ConnectedClientsNetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Packet sent to a new client when they connect to the server. 7 | /// 8 | public class ConnectedClientsNetMessage : NetMessage 9 | { 10 | public List ClientIds { get; private set; } 11 | 12 | 13 | public ConnectedClientsNetMessage(List clientIds) 14 | { 15 | ClientIds = clientIds; 16 | } 17 | 18 | 19 | protected override void SerializeInternal(BitBuffer buffer) 20 | { 21 | buffer.AddUShort((ushort)ClientIds.Count); 22 | foreach (ushort clientId in ClientIds) 23 | buffer.AddUShort(clientId); 24 | } 25 | 26 | 27 | protected override void DeserializeInternal(BitBuffer buffer) 28 | { 29 | ushort count = buffer.ReadUShort(); 30 | ClientIds = new List(count); 31 | for (int i = 0; i < count; i++) 32 | ClientIds.Add(buffer.ReadUShort()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/Handlers/ClientMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Handles packets received on server, from clients. 7 | /// 8 | internal class ClientMessageHandler : MessageHandlerCollection 9 | { 10 | private readonly List> _handlers = new(); 11 | public override bool RequireAuthentication { get; } 12 | 13 | 14 | public ClientMessageHandler(bool requireAuthentication) 15 | { 16 | RequireAuthentication = requireAuthentication; 17 | } 18 | 19 | 20 | public override void RegisterHandler(object obj) 21 | { 22 | if (obj is Action handler) 23 | _handlers.Add(handler); 24 | } 25 | 26 | 27 | public override void UnregisterHandler(object obj) 28 | { 29 | if (obj is Action handler) 30 | _handlers.Remove(handler); 31 | } 32 | 33 | 34 | public override void InvokeHandlers(NetworkConnection conn, NetMessage netMessage, Channel channel) 35 | { 36 | if (netMessage is not T tPacket) 37 | return; 38 | 39 | foreach (Action handler in _handlers) 40 | handler.Invoke(conn, tPacket, channel); 41 | } 42 | 43 | 44 | public override void InvokeHandlers(NetMessage netMessage, Channel channel) 45 | { 46 | // Server does not handle packets from the server. 47 | throw new NotImplementedException(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/Handlers/MessageHandlerCollection.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | internal abstract class MessageHandlerCollection 6 | { 7 | public abstract void RegisterHandler(object obj); 8 | public abstract void UnregisterHandler(object obj); 9 | public abstract void InvokeHandlers(NetMessage netMessage, Channel channel); 10 | public abstract void InvokeHandlers(NetworkConnection conn, NetMessage netMessage, Channel channel); 11 | public abstract bool RequireAuthentication { get; } 12 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/Handlers/ServerMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Handles packets received on clients, from the server. 7 | /// 8 | internal class ServerMessageHandler : MessageHandlerCollection 9 | { 10 | private readonly List> _handlers = new(); 11 | 12 | public override bool RequireAuthentication => false; 13 | 14 | 15 | public override void RegisterHandler(object obj) 16 | { 17 | if (obj is Action handler) 18 | _handlers.Add(handler); 19 | } 20 | 21 | 22 | public override void UnregisterHandler(object obj) 23 | { 24 | if (obj is Action handler) 25 | _handlers.Remove(handler); 26 | } 27 | 28 | 29 | public override void InvokeHandlers(NetMessage netMessage, Channel channel) 30 | { 31 | if (netMessage is not T tPacket) 32 | return; 33 | 34 | foreach (Action handler in _handlers) 35 | handler.Invoke(tPacket, channel); 36 | } 37 | 38 | 39 | public override void InvokeHandlers(NetworkConnection conn, NetMessage netMessage, Channel channel) 40 | { 41 | // Client does not handle packets from other clients. 42 | throw new NotImplementedException(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/HighLevel/Messages/NetMessage.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.HighLevel.Messages; 4 | 5 | /// 6 | /// Represents an message (packet) that can be sent over the network.
7 | /// You can safely cache instances of this class, as it is immutable and won't be reused.

8 | /// Any constructors will be skipped when deserializing, so don't rely on them being called. 9 | ///
10 | public abstract class NetMessage 11 | { 12 | /// 13 | /// Serializes this message into a bit buffer. 14 | /// 15 | /// Empty buffer to serialize the message into. 16 | public void Serialize(BitBuffer buffer) 17 | { 18 | ushort id = MessageManager.MessageIdCache.GetId(GetType()); 19 | buffer.AddUShort(id); 20 | SerializeInternal(buffer); 21 | } 22 | 23 | 24 | /// 25 | /// Deserializes this message from a bit buffer. 26 | /// 27 | /// Buffer containing the serialized message. 28 | public void Deserialize(BitBuffer buffer) 29 | { 30 | DeserializeInternal(buffer); 31 | } 32 | 33 | 34 | protected abstract void SerializeInternal(BitBuffer buffer); 35 | protected abstract void DeserializeInternal(BitBuffer buffer); 36 | 37 | 38 | public override string ToString() 39 | { 40 | return $"{GetType().Name} (ID: {MessageManager.MessageIdCache.GetId(GetType())})"; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/InternalPacketType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | /// 4 | /// Represents the type of data being sent or received. 5 | /// 6 | internal enum InternalPacketType : byte 7 | { 8 | /// 9 | /// Packet contains unknown data. 10 | /// 11 | Unset = 0, 12 | 13 | /// 14 | /// Welcome packet. 15 | /// Contains the client's connectionId. 16 | /// 17 | Welcome = 1, 18 | 19 | /// 20 | /// Packet contains a message. 21 | /// 22 | Message = 2, 23 | 24 | /// 25 | /// Disconnect packet. 26 | /// The client should immediately disconnect from the server. 27 | /// 28 | Disconnect = 3, 29 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/NetStack/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stanislav Denisov (nxrighthere@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/AddressType.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | /// 4 | /// The type of an address. 5 | /// 6 | public enum AddressType 7 | { 8 | IPV4, 9 | IPV6 10 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/EventArgs/ClientConnectionStateArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | public readonly struct ClientConnectionStateArgs 6 | { 7 | /// 8 | /// New connection state. 9 | /// 10 | public readonly LocalConnectionState ConnectionState; 11 | 12 | 13 | public ClientConnectionStateArgs(LocalConnectionState connectionState) 14 | { 15 | ConnectionState = connectionState; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/EventArgs/ClientReceivedDataArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | /// 6 | /// Container about data received on the local client. 7 | /// 8 | public readonly struct ClientReceivedDataArgs 9 | { 10 | /// 11 | /// Data received. 12 | /// 13 | public readonly ArraySegment Segment; 14 | 15 | /// 16 | /// Channel data was received on. 17 | /// 18 | public readonly Channel Channel; 19 | 20 | 21 | public ClientReceivedDataArgs(ArraySegment segment, Channel channel) 22 | { 23 | Segment = segment; 24 | Channel = channel; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/EventArgs/RemoteConnectionStateArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | public readonly struct RemoteConnectionStateArgs 6 | { 7 | /// 8 | /// New connection state. 9 | /// 10 | public readonly RemoteConnectionState ConnectionState; 11 | 12 | /// 13 | /// ConnectionId for which client the state changed. Will be 0 if was for the server. 14 | /// 15 | public readonly int ConnectionId; 16 | 17 | 18 | public RemoteConnectionStateArgs(RemoteConnectionState state, int connectionEventConnectionId) 19 | { 20 | ConnectionState = state; 21 | ConnectionId = connectionEventConnectionId; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/EventArgs/ServerConnectionStateArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | public readonly struct ServerConnectionStateArgs 6 | { 7 | /// 8 | /// New connection state. 9 | /// 10 | public readonly LocalConnectionState ConnectionState; 11 | 12 | 13 | public ServerConnectionStateArgs(LocalConnectionState connectionState) 14 | { 15 | ConnectionState = connectionState; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/EventArgs/ServerReceivedDataArgs.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | /// 6 | /// Container about data received on the server. 7 | /// 8 | public readonly struct ServerReceivedDataArgs 9 | { 10 | /// 11 | /// Data received. 12 | /// Guaranteed to contain at least 1 byte of data (). 13 | /// The offset is always 0. 14 | /// 15 | public readonly ArraySegment Segment; //NOTE: A ReadOnlySpan could be used also, by setting the struct to ref. 16 | 17 | /// 18 | /// Channel data was received on. 19 | /// 20 | public readonly Channel Channel; 21 | 22 | /// 23 | /// Connection of client which sent the data. 24 | /// 25 | public readonly int ConnectionId; 26 | 27 | 28 | public ServerReceivedDataArgs(ArraySegment segment, Channel channel, int connectionId) 29 | { 30 | Segment = segment; 31 | Channel = channel; 32 | ConnectionId = connectionId; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/BaseChannel.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 2 | { 3 | internal abstract class BaseChannel 4 | { 5 | protected readonly NetPeer Peer; 6 | protected readonly Queue OutgoingQueue = new Queue(NetConstants.DefaultWindowSize); 7 | private int _isAddedToPeerChannelSendQueue; 8 | 9 | public int PacketsInQueue => OutgoingQueue.Count; 10 | 11 | protected BaseChannel(NetPeer peer) 12 | { 13 | Peer = peer; 14 | } 15 | 16 | public void AddToQueue(NetPacket packet) 17 | { 18 | lock (OutgoingQueue) 19 | { 20 | OutgoingQueue.Enqueue(packet); 21 | } 22 | AddToPeerChannelSendQueue(); 23 | } 24 | 25 | protected void AddToPeerChannelSendQueue() 26 | { 27 | if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0) 28 | { 29 | Peer.AddToReliableChannelSendQueue(this); 30 | } 31 | } 32 | 33 | public bool SendAndCheckQueue() 34 | { 35 | bool hasPacketsToSend = SendNextPackets(); 36 | if (!hasPacketsToSend) 37 | Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0); 38 | 39 | return hasPacketsToSend; 40 | } 41 | 42 | protected abstract bool SendNextPackets(); 43 | public abstract bool ProcessPacket(NetPacket packet); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Layers/Crc32cLayer.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 4 | { 5 | public sealed class Crc32cLayer : PacketLayerBase 6 | { 7 | public Crc32cLayer() : base(CRC32C.ChecksumSize) 8 | { 9 | 10 | } 11 | 12 | public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) 13 | { 14 | if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize) 15 | { 16 | NetDebug.WriteError("[NM] DataReceived size: bad!"); 17 | //Set length to 0 to have netManager drop the packet. 18 | length = 0; 19 | return; 20 | } 21 | 22 | int checksumPoint = length - CRC32C.ChecksumSize; 23 | if (CRC32C.Compute(data, 0, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint)) 24 | { 25 | NetDebug.Write("[NM] DataReceived checksum: bad!"); 26 | //Set length to 0 to have netManager drop the packet. 27 | length = 0; 28 | return; 29 | } 30 | length -= CRC32C.ChecksumSize; 31 | } 32 | 33 | public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) 34 | { 35 | FastBitConverter.GetBytes(data, length, CRC32C.Compute(data, offset, length)); 36 | length += CRC32C.ChecksumSize; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Layers/PacketLayerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 4 | { 5 | public abstract class PacketLayerBase 6 | { 7 | public readonly int ExtraPacketSizeForLayer; 8 | 9 | protected PacketLayerBase(int extraPacketSizeForLayer) 10 | { 11 | ExtraPacketSizeForLayer = extraPacketSizeForLayer; 12 | } 13 | 14 | public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length); 15 | public abstract void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Layers/XorEncryptLayer.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text; 3 | 4 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 5 | { 6 | public class XorEncryptLayer : PacketLayerBase 7 | { 8 | private byte[] _byteKey; 9 | 10 | public XorEncryptLayer() : base(0) 11 | { 12 | 13 | } 14 | 15 | public XorEncryptLayer(byte[] key) : this() 16 | { 17 | SetKey(key); 18 | } 19 | 20 | public XorEncryptLayer(string key) : this() 21 | { 22 | SetKey(key); 23 | } 24 | 25 | public void SetKey(string key) 26 | { 27 | _byteKey = Encoding.UTF8.GetBytes(key); 28 | } 29 | 30 | public void SetKey(byte[] key) 31 | { 32 | if (_byteKey == null || _byteKey.Length != key.Length) 33 | _byteKey = new byte[key.Length]; 34 | Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length); 35 | } 36 | 37 | public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length) 38 | { 39 | if (_byteKey == null) 40 | return; 41 | for (int i = 0; i < length; i++) 42 | { 43 | data[i] = (byte)(data[i] ^ _byteKey[i % _byteKey.Length]); 44 | } 45 | } 46 | 47 | public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length) 48 | { 49 | if (_byteKey == null) 50 | return; 51 | int cur = offset; 52 | for (int i = 0; i < length; i++, cur++) 53 | { 54 | data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/PausedSocketFix.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_2018_3_OR_NEWER 2 | using System.Net; 3 | 4 | namespace LiteNetLib 5 | { 6 | public class PausedSocketFix 7 | { 8 | private readonly NetManager _netManager; 9 | private readonly IPAddress _ipv4; 10 | private readonly IPAddress _ipv6; 11 | private readonly int _port; 12 | private readonly bool _manualMode; 13 | private bool _initialized; 14 | 15 | public PausedSocketFix(NetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode) 16 | { 17 | _netManager = netManager; 18 | _ipv4 = ipv4; 19 | _ipv6 = ipv6; 20 | _port = port; 21 | _manualMode = manualMode; 22 | UnityEngine.Application.focusChanged += Application_focusChanged; 23 | _initialized = true; 24 | } 25 | 26 | public void Deinitialize() 27 | { 28 | if (_initialized) 29 | UnityEngine.Application.focusChanged -= Application_focusChanged; 30 | _initialized = false; 31 | } 32 | 33 | private void Application_focusChanged(bool focused) 34 | { 35 | //If coming back into focus see if a reconnect is needed. 36 | if (focused) 37 | { 38 | //try reconnect 39 | if (!_initialized) 40 | return; 41 | //Was intentionally disconnected at some point. 42 | if (!_netManager.IsRunning) 43 | return; 44 | //Socket is in working state. 45 | if (_netManager.NotConnected == false) 46 | return; 47 | 48 | //Socket isn't running but should be. Try to start again. 49 | if (!_netManager.Start(_ipv4, _ipv6, _port, _manualMode)) 50 | { 51 | NetDebug.WriteError($"[S] Cannot restore connection. Ipv4 {_ipv4}, Ipv6 {_ipv6}, Port {_port}, ManualMode {_manualMode}"); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/PooledPacket.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 2 | { 3 | public readonly ref struct PooledPacket 4 | { 5 | internal readonly NetPacket _packet; 6 | internal readonly byte _channelNumber; 7 | 8 | /// 9 | /// Maximum data size that you can put into such packet 10 | /// 11 | public readonly int MaxUserDataSize; 12 | 13 | /// 14 | /// Offset for user data when writing to Data array 15 | /// 16 | public readonly int UserDataOffset; 17 | 18 | /// 19 | /// Raw packet data. Do not modify header! Use UserDataOffset as start point for your data 20 | /// 21 | public byte[] Data => _packet.RawData; 22 | 23 | internal PooledPacket(NetPacket packet, int maxDataSize, byte channelNumber) 24 | { 25 | _packet = packet; 26 | UserDataOffset = _packet.GetHeaderSize(); 27 | _packet.Size = UserDataOffset; 28 | MaxUserDataSize = maxDataSize - UserDataOffset; 29 | _channelNumber = channelNumber; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Trimming.cs: -------------------------------------------------------------------------------- 1 | #if NET5_0_OR_GREATER 2 | using System.Diagnostics.CodeAnalysis; 3 | using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; 4 | 5 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 6 | { 7 | internal static class Trimming 8 | { 9 | internal const DynamicallyAccessedMemberTypes SerializerMemberTypes = PublicProperties | NonPublicProperties; 10 | } 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Utils/INetSerializable.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 2 | { 3 | public interface INetSerializable 4 | { 5 | void Serialize(NetDataWriter writer); 6 | void Deserialize(NetDataReader reader); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Utils/NtpRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Sockets; 3 | 4 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 5 | { 6 | internal sealed class NtpRequest 7 | { 8 | private const int ResendTimer = 1000; 9 | private const int KillTimer = 10000; 10 | public const int DefaultPort = 123; 11 | private readonly IPEndPoint _ntpEndPoint; 12 | private int _resendTime = ResendTimer; 13 | private int _killTime = 0; 14 | 15 | public NtpRequest(IPEndPoint endPoint) 16 | { 17 | _ntpEndPoint = endPoint; 18 | } 19 | 20 | public bool NeedToKill => _killTime >= KillTimer; 21 | 22 | public bool Send(Socket socket, int time) 23 | { 24 | _resendTime += time; 25 | _killTime += time; 26 | if (_resendTime < ResendTimer) 27 | { 28 | return false; 29 | } 30 | var packet = new NtpPacket(); 31 | try 32 | { 33 | int sendCount = socket.SendTo(packet.Bytes, 0, packet.Bytes.Length, SocketFlags.None, _ntpEndPoint); 34 | return sendCount == packet.Bytes.Length; 35 | } 36 | catch 37 | { 38 | return false; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/LiteNetLib/Utils/Preserve.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Multiplayer.LowLevel 2 | { 3 | /// 4 | /// PreserveAttribute prevents byte code stripping from removing a class, method, field, or property. 5 | /// 6 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] 7 | public class PreserveAttribute : Attribute 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/Packet.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel; 2 | using KorpiEngine.Networking.Utility; 3 | 4 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 5 | 6 | internal readonly struct Packet 7 | { 8 | public readonly int ConnectionId; 9 | public readonly byte[] Data; 10 | public readonly int Length; 11 | public readonly Channel Channel; 12 | 13 | public Packet(int connectionId, byte[] data, int length, Channel channel) 14 | { 15 | ConnectionId = connectionId; 16 | Data = data; 17 | Length = length; 18 | Channel = channel; 19 | } 20 | 21 | public Packet(int sender, ArraySegment segment, Channel channel, int mtu) 22 | { 23 | //Prefer to max out returned array to mtu to reduce chance of resizing. 24 | int arraySize = Math.Max(segment.Count, mtu); 25 | Data = ByteArrayPool.Rent(arraySize); 26 | Buffer.BlockCopy(segment.Array, segment.Offset, Data, 0, segment.Count); 27 | ConnectionId = sender; 28 | Length = segment.Count; 29 | Channel = channel; 30 | } 31 | 32 | /// 33 | /// Gets the data as an ArraySegment. 34 | /// The offset is always 0. 35 | /// 36 | /// 37 | public ArraySegment GetArraySegment() 38 | { 39 | return new ArraySegment(Data, 0, Length); 40 | } 41 | 42 | /// 43 | /// Gets the data as a ReadOnlySpan. 44 | /// The offset is always 0. 45 | /// 46 | /// 47 | public ReadOnlySpan GetReadOnlySpan() 48 | { 49 | return new ReadOnlySpan(Data, 0, Length); 50 | } 51 | 52 | public void Dispose() 53 | { 54 | ByteArrayPool.Return(Data); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/LowLevel/Transports/LiteNetLib/Core/QueueUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace KorpiEngine.Networking.Multiplayer.LowLevel; 4 | 5 | public static class QueueUtils 6 | { 7 | /// 8 | /// Clears a ConcurrentQueue of any type. 9 | /// 10 | internal static void ClearGenericQueue(ref ConcurrentQueue queue) 11 | { 12 | while (queue.TryDequeue(out _)) 13 | { 14 | } 15 | } 16 | 17 | 18 | /// 19 | /// Clears a queue using Packet type. 20 | /// 21 | /// 22 | internal static void ClearPacketQueue(ref ConcurrentQueue queue) 23 | { 24 | while (queue.TryDequeue(out Packet p)) 25 | p.Dispose(); 26 | } 27 | 28 | 29 | /// 30 | /// Clears a queue using Packet type. 31 | /// 32 | /// 33 | internal static void ClearPacketQueue(ref Queue queue) 34 | { 35 | int count = queue.Count; 36 | for (int i = 0; i < count; i++) 37 | { 38 | Packet p = queue.Dequeue(); 39 | p.Dispose(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Networking/Multiplayer/NetworkManager.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.HighLevel.Connections; 2 | using KorpiEngine.Networking.Multiplayer.LowLevel; 3 | using KorpiEngine.Tools.Logging; 4 | 5 | namespace KorpiEngine.Networking.Multiplayer; 6 | 7 | public class NetworkManager 8 | { 9 | private static readonly IKorpiLogger Logger = LogFactory.GetLogger(typeof(NetworkManager)); 10 | 11 | public readonly TransportManager TransportManager; 12 | public readonly NetServerManager Server; 13 | public readonly NetClientManager Client; 14 | 15 | 16 | public NetworkManager(Transport transportLayer) 17 | { 18 | transportLayer.Initialize(this); 19 | TransportManager = new TransportManager(this, transportLayer); 20 | Server = new NetServerManager(this, TransportManager); 21 | Client = new NetClientManager(this, TransportManager); 22 | } 23 | 24 | 25 | public void Tick() 26 | { 27 | PollSockets(); 28 | IteratePackets(true); 29 | IteratePackets(false); 30 | } 31 | 32 | 33 | public static void ClearClientsCollection(Dictionary clients) 34 | { 35 | // Dispose of all clients and clear the collection. 36 | foreach (NetworkConnection client in clients.Values) 37 | { 38 | client.Dispose(); 39 | } 40 | clients.Clear(); 41 | } 42 | 43 | 44 | /// 45 | /// Tells transport layer to iterate incoming or outgoing packets. 46 | /// 47 | /// True to iterate incoming. 48 | private void IteratePackets(bool incoming) 49 | { 50 | if (incoming) 51 | { 52 | TransportManager.IterateIncoming(true); 53 | TransportManager.IterateIncoming(false); 54 | } 55 | else 56 | { 57 | TransportManager.IterateOutgoing(true); 58 | TransportManager.IterateOutgoing(false); 59 | } 60 | } 61 | 62 | 63 | /// 64 | /// Tells transport layer to poll for new data. 65 | /// 66 | private void PollSockets() 67 | { 68 | TransportManager.PollSockets(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Networking/Networking.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 12 6 | enable 7 | enable 8 | true 9 | KorpiEngine.Networking 10 | KorpiEngine.Networking 11 | Debug;Release;Production 12 | AnyCPU 13 | 14 | 15 | 16 | false 17 | true 18 | KORPI_DEBUG;KORPI_TOOLS;KORPI_PROFILE;TRACY_ENABLE;TRACE;NET_STANDARD_2_0;NETSTACK_SPAN;LITENETLIB_UNSAFE 19 | ..\..\Build\Debug\ 20 | 21 | 22 | 23 | true 24 | false 25 | KORPI_RELEASE;KORPI_TOOLS;KORPI_PROFILE;KORPI_OPTIMIZE;TRACY_ENABLE;TRACE;NET_STANDARD_2_0;NETSTACK_SPAN;LITENETLIB_UNSAFE 26 | ..\..\Build\Release\ 27 | 28 | 29 | 30 | true 31 | false 32 | KORPI_PRODUCTION;KORPI_OPTIMIZE;NET_STANDARD_2_0;NETSTACK_SPAN;LITENETLIB_UNSAFE 33 | ..\..\Build\Production\ 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Networking/Utility/ArraySegmentUtils.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Utility; 2 | 3 | public static class ArraySegmentUtils 4 | { 5 | public static string AsStringHex(this ArraySegment segment) 6 | { 7 | return string.Join(" ", segment.AsSpan().ToArray().Select(b => b.ToString("X2").PadLeft(2, '0'))); 8 | } 9 | 10 | 11 | public static string AsStringDecimal(this ArraySegment segment) 12 | { 13 | return string.Join(" ", segment.AsSpan().ToArray().Select(b => b.ToString())); 14 | } 15 | 16 | 17 | public static string AsStringBits(this ArraySegment segment) 18 | { 19 | return string.Join(" ", segment.AsSpan().ToArray().Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Networking/Utility/BufferPool.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Networking.Multiplayer.LowLevel; 2 | 3 | namespace KorpiEngine.Networking.Utility; 4 | 5 | /// 6 | /// One-time allocation pool of s. 7 | /// 8 | internal static class BufferPool 9 | { 10 | [ThreadStatic] 11 | private static BitBuffer? bitBuffer; 12 | 13 | 14 | /// 15 | /// Gets the thread static . 16 | /// 17 | public static BitBuffer GetBitBuffer() 18 | { 19 | bitBuffer ??= new BitBuffer(1024); 20 | 21 | bitBuffer.Clear(); 22 | 23 | return bitBuffer; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Networking/Utility/ByteArrayPool.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.Networking.Utility; 2 | 3 | /// 4 | /// Retrieves and stores byte arrays using a pooling system. 5 | /// 6 | internal static class ByteArrayPool 7 | { 8 | /// 9 | /// Stored byte arrays. 10 | /// 11 | private static readonly Queue ByteArrays = new(); 12 | 13 | /// 14 | /// Returns a byte array which will be of at least minimum length. The returned array must manually be stored. 15 | /// 16 | public static byte[] Rent(int minimumLength) 17 | { 18 | byte[]? result = null; 19 | 20 | if (ByteArrays.Count > 0) 21 | result = ByteArrays.Dequeue(); 22 | 23 | int doubleMinimumLength = (minimumLength * 2); 24 | if (result == null) 25 | result = new byte[doubleMinimumLength]; 26 | else if (result.Length < minimumLength) 27 | Array.Resize(ref result, doubleMinimumLength); 28 | 29 | return result; 30 | } 31 | 32 | /// 33 | /// Stores a byte array for re-use. 34 | /// 35 | public static void Return(byte[] buffer) 36 | { 37 | if (ByteArrays.Count > 300) 38 | return; 39 | ByteArrays.Enqueue(buffer); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/OpenGL/Exceptions/GLException.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Utils; 2 | 3 | namespace KorpiEngine.OpenGL; 4 | 5 | /// 6 | /// The exception that is thrown when an OpenGL-related error occurs. 7 | /// 8 | internal class GLException : KorpiException 9 | { 10 | internal GLException(string message) : base(message) 11 | { 12 | } 13 | 14 | internal GLException(string message, Exception innerException) : base(message, innerException) 15 | { 16 | } 17 | } -------------------------------------------------------------------------------- /src/OpenGL/Exceptions/GLObjectNotBoundException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.OpenGL; 2 | 3 | /// 4 | /// The exception that is thrown when an object is used which must be bound before usage. 5 | /// 6 | internal class GLObjectNotBoundException : GLException 7 | { 8 | internal GLObjectNotBoundException(string message) : base(message) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/OpenGL/Exceptions/GLShaderCompileException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.OpenGL; 2 | 3 | /// 4 | /// The exception that is thrown when a shader compile error occurs. 5 | /// 6 | internal class GLShaderCompileException : GLShaderProgramException 7 | { 8 | internal GLShaderCompileException(string message, string infoLog) 9 | : base(message, infoLog) 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/OpenGL/Exceptions/GLShaderProgramException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.OpenGL; 2 | 3 | /// 4 | /// The exception that is thrown when a program related error occurs. 5 | /// 6 | internal class GLShaderProgramException : GLException 7 | { 8 | public string InfoLog { get; private set; } 9 | 10 | internal GLShaderProgramException(string message, string infoLog) 11 | : base($"{message}:\n{infoLog}") 12 | { 13 | InfoLog = infoLog; 14 | } 15 | } -------------------------------------------------------------------------------- /src/OpenGL/Exceptions/GLShaderProgramLinkException.cs: -------------------------------------------------------------------------------- 1 | namespace KorpiEngine.OpenGL; 2 | 3 | /// 4 | /// The exception that is thrown when a program link error occurs. 5 | /// 6 | internal class GLShaderProgramLinkException : GLShaderProgramException 7 | { 8 | internal GLShaderProgramLinkException(string message, string infoLog) 9 | : base(message, infoLog) 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/OpenGL/GLGraphicsContext.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.InputManagement; 2 | using KorpiEngine.Rendering; 3 | using KorpiEngine.UI.DearImGui; 4 | using KorpiEngine.Utils; 5 | 6 | namespace KorpiEngine.OpenGL; 7 | 8 | public class GLGraphicsContext : GraphicsContext 9 | { 10 | private const string NOT_INITIALIZED = "Graphics context not initialized."; 11 | 12 | private GLImGuiRenderer? _imGuiRenderer; 13 | private GLGraphicsDevice? _device; 14 | private GLWindow? _window; 15 | 16 | 17 | public override GraphicsDevice Device => _device ?? throw new InvalidOperationException(NOT_INITIALIZED); 18 | public override GraphicsWindow Window => _window ?? throw new InvalidOperationException(NOT_INITIALIZED); 19 | 20 | public override IInputState InputState => _window?.InputState ?? throw new InvalidOperationException(NOT_INITIALIZED); 21 | public override DisplayState DisplayState => _window?.DisplayState ?? throw new InvalidOperationException(NOT_INITIALIZED); 22 | public override IImGuiRenderer ImGuiRenderer => _imGuiRenderer ?? throw new InvalidOperationException(NOT_INITIALIZED); 23 | 24 | 25 | public override void Run(WindowingSettings windowingSettings, Action onLoad, Action onFrameStart, Action onFrameUpdate, Action onFrameRender, Action onFrameEnd, Action onUnload) 26 | { 27 | _window = new GLWindow(windowingSettings, onLoad, onFrameStart, onFrameUpdate, onFrameRender, onFrameEnd, onUnload); 28 | _device = new GLGraphicsDevice(); 29 | _imGuiRenderer = new GLImGuiRenderer(this); 30 | 31 | _window.Run(); 32 | } 33 | 34 | 35 | public override void Shutdown() 36 | { 37 | _device?.Shutdown(); 38 | _window?.Shutdown(); 39 | _imGuiRenderer?.Shutdown(); 40 | _device = null; 41 | _window = null; 42 | _imGuiRenderer = null; 43 | } 44 | } -------------------------------------------------------------------------------- /src/OpenGL/GLInputState.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.InputManagement; 2 | using KorpiEngine.Mathematics; 3 | using OpenTK.Windowing.Desktop; 4 | using OpenTK.Windowing.GraphicsLibraryFramework; 5 | using MouseButton = KorpiEngine.InputManagement.MouseButton; 6 | 7 | namespace KorpiEngine.OpenGL; 8 | 9 | internal class GLInputState(GameWindow window) : IInputState 10 | { 11 | private readonly GLKeyboardState _keyboardState = new(window.KeyboardState); 12 | private readonly GLMouseState _mouseState = new(window.MouseState); 13 | 14 | public IKeyboardState KeyboardState => _keyboardState; 15 | public IMouseState MouseState => _mouseState; 16 | } 17 | 18 | internal class GLKeyboardState(KeyboardState keyboardState) : IKeyboardState 19 | { 20 | public bool IsKeyDown(KeyCode key) => keyboardState.IsKeyDown((Keys)key); 21 | public bool IsKeyPressed(KeyCode key) => keyboardState.IsKeyPressed((Keys)key); 22 | public bool IsKeyReleased(KeyCode key) => keyboardState.IsKeyReleased((Keys)key); 23 | } 24 | 25 | internal class GLMouseState(MouseState mouseState) : IMouseState 26 | { 27 | public Vector2 Position => new(mouseState.X, mouseState.Y); 28 | public Vector2 PreviousPosition => new(mouseState.PreviousX, mouseState.PreviousY); 29 | public Vector2 PositionDelta => new(mouseState.Delta.X, mouseState.Delta.Y); 30 | 31 | public Vector2 Scroll => new(mouseState.Scroll.X, mouseState.Scroll.Y); 32 | public Vector2 PreviousScroll => new(mouseState.PreviousScroll.X, mouseState.PreviousScroll.Y); 33 | public Vector2 ScrollDelta => new(mouseState.ScrollDelta.X, mouseState.ScrollDelta.Y); 34 | 35 | public bool IsButtonDown(MouseButton button) => mouseState.IsButtonDown((OpenTK.Windowing.GraphicsLibraryFramework.MouseButton)button); 36 | public bool IsButtonPressed(MouseButton button) => mouseState.IsButtonPressed((OpenTK.Windowing.GraphicsLibraryFramework.MouseButton)button); 37 | public bool IsButtonReleased(MouseButton button) => mouseState.IsButtonReleased((OpenTK.Windowing.GraphicsLibraryFramework.MouseButton)button); 38 | } -------------------------------------------------------------------------------- /src/OpenGL/OpenGL.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 12 6 | enable 7 | enable 8 | true 9 | KorpiEngine.OpenGL 10 | KorpiEngine.OpenGL 11 | Debug;Release;Production 12 | AnyCPU 13 | 14 | 15 | 16 | 17 | KORPI_DEBUG;KORPI_TOOLS;KORPI_PROFILE;TRACY_ENABLE;TRACE 18 | false 19 | true 20 | ..\..\Build\Debug\ 21 | 22 | 23 | 24 | 25 | KORPI_RELEASE;KORPI_TOOLS;KORPI_PROFILE;KORPI_OPTIMIZE;TRACY_ENABLE;TRACE 26 | true 27 | false 28 | ..\..\Build\Release\ 29 | 30 | 31 | 32 | 33 | KORPI_PRODUCTION;KORPI_OPTIMIZE 34 | true 35 | false 36 | ..\..\Build\Production\ 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/OpenGL/OpenGL.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /src/OpenGL/Resources/GLBuffer.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | using OpenTK.Graphics.OpenGL4; 3 | 4 | namespace KorpiEngine.OpenGL; 5 | 6 | internal sealed class GLBuffer : GraphicsBuffer 7 | { 8 | public readonly BufferType OriginalType; 9 | public readonly BufferTarget Target; 10 | 11 | private static readonly int[] BoundBuffers = new int[(int)BufferType.Count]; 12 | 13 | internal override int SizeInBytes { get; } 14 | 15 | 16 | public GLBuffer(BufferType type, int sizeInBytes, nint data, bool dynamic) : base(GL.GenBuffer()) 17 | { 18 | if (type == BufferType.Count) 19 | throw new ArgumentOutOfRangeException(nameof(type), type, null); 20 | 21 | SizeInBytes = sizeInBytes; 22 | 23 | OriginalType = type; 24 | Target = type switch 25 | { 26 | BufferType.VertexBuffer => BufferTarget.ArrayBuffer, 27 | BufferType.ElementsBuffer => BufferTarget.ElementArrayBuffer, 28 | BufferType.UniformBuffer => BufferTarget.UniformBuffer, 29 | BufferType.StructuredBuffer => BufferTarget.ShaderStorageBuffer, 30 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) 31 | }; 32 | 33 | Bind(); 34 | if (sizeInBytes != 0) 35 | Set(sizeInBytes, data, dynamic); 36 | } 37 | 38 | 39 | public void Set(int sizeInBytes, nint data, bool dynamic) 40 | { 41 | Bind(); 42 | BufferUsageHint usage = dynamic ? BufferUsageHint.DynamicDraw : BufferUsageHint.StaticDraw; 43 | GL.BufferData(Target, sizeInBytes, data, usage); 44 | } 45 | 46 | 47 | public void Update(int offsetInBytes, int sizeInBytes, nint data) 48 | { 49 | Bind(); 50 | GL.BufferSubData(Target, offsetInBytes, sizeInBytes, data); 51 | } 52 | 53 | 54 | protected override void DisposeResources() 55 | { 56 | GL.DeleteBuffer(Handle); 57 | } 58 | 59 | 60 | private void Bind() 61 | { 62 | if (BoundBuffers[(int)OriginalType] == Handle) 63 | return; 64 | 65 | GL.BindBuffer(Target, Handle); 66 | BoundBuffers[(int)OriginalType] = Handle; 67 | } 68 | } -------------------------------------------------------------------------------- /src/OpenGL/Resources/GLGraphicsProgramFactory.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | 3 | namespace KorpiEngine.OpenGL; 4 | 5 | /// 6 | /// Contains methods to automatically initialize shader shaderProgram objects. 7 | /// 8 | internal static class GLGraphicsProgramFactory 9 | { 10 | /// 11 | /// Initializes a shaderProgram object using the shader sources provided. 12 | /// 13 | /// A compiled and linked shaderProgram. 14 | public static GLGraphicsProgram Create(List shaders) 15 | { 16 | if (shaders.Count == 0) 17 | throw new GLException("No shaders provided for shaderProgram creation."); 18 | 19 | // Create a shader shaderProgram instance 20 | GLGraphicsProgram program = new(); 21 | try 22 | { 23 | // compile and attach all shaders 24 | foreach (ShaderSourceDescriptor sourceInfo in shaders) 25 | { 26 | // create a new shader of the appropriate type 27 | using GLGraphicsShader glShader = new((ShaderType)sourceInfo.Type); 28 | 29 | // compile shader source 30 | glShader.CompileSource(sourceInfo.Source); 31 | 32 | // attach shader to the shaderProgram 33 | program.AttachShader(glShader); 34 | } 35 | 36 | // link and return the shaderProgram 37 | program.Link(); 38 | } 39 | catch (Exception e) 40 | { 41 | program.Dispose(); 42 | 43 | throw new GLException("Failed to create shaderProgram.", e); 44 | } 45 | 46 | return program; 47 | } 48 | } -------------------------------------------------------------------------------- /src/OpenGL/Resources/GLVertexArrayObject.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Rendering; 2 | using OpenTK.Graphics.OpenGL4; 3 | 4 | namespace KorpiEngine.OpenGL; 5 | 6 | internal sealed class GLVertexArrayObject : GraphicsVertexArrayObject 7 | { 8 | public GLVertexArrayObject(MeshVertexLayout layout, GraphicsBuffer vertices, GraphicsBuffer? indices) : base(GL.GenVertexArray()) 9 | { 10 | GL.BindVertexArray(Handle); 11 | 12 | BindFormat(layout); 13 | 14 | GL.BindBuffer(BufferTarget.ArrayBuffer, (vertices as GLBuffer)!.Handle); 15 | if (indices != null) 16 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, (indices as GLBuffer)!.Handle); 17 | } 18 | 19 | 20 | private static void BindFormat(MeshVertexLayout layout) 21 | { 22 | foreach (MeshVertexLayout.VertexAttributeDescriptor element in layout.Attributes) 23 | { 24 | int index = element.Semantic; 25 | GL.EnableVertexAttribArray(index); 26 | IntPtr offset = element.Offset; 27 | 28 | if (element.AttributeType == VertexAttributeType.Float) 29 | GL.VertexAttribPointer(index, element.Count, (VertexAttribPointerType)element.AttributeType, element.Normalized, layout.VertexSize, offset); 30 | else 31 | GL.VertexAttribIPointer(index, element.Count, (VertexAttribIntegerType)element.AttributeType, layout.VertexSize, offset); 32 | } 33 | } 34 | 35 | 36 | protected override void DisposeResources() 37 | { 38 | GL.DeleteVertexArray(Handle); 39 | } 40 | } -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | ## Core 4 | 5 | Contains the core functionality of the engine. This includes, for example, the main engine loop, scene management, and basic rendering. 6 | 7 | ## Networking 8 | 9 | Contains networking functionality for the engine. This includes a simple Reliable-UDP server and client implementation. 10 | 11 | ## Sandbox 12 | 13 | Contains a simple example project that demonstrates how to use the engine. 14 | -------------------------------------------------------------------------------- /src/Sandbox/Program.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine; 2 | using KorpiEngine.AssetManagement; 3 | using KorpiEngine.Mathematics; 4 | using KorpiEngine.OpenGL; 5 | using KorpiEngine.Rendering; 6 | using Sandbox.Scenes.PrimitiveExample; 7 | 8 | namespace Sandbox; 9 | 10 | internal static class Program 11 | { 12 | private static void Main(string[] args) 13 | { 14 | Application.Run( 15 | WindowingSettings.Windowed("KorpiEngine Sandbox", new Int2(1920, 1080)), 16 | new UncompressedAssetProvider(), 17 | new GLGraphicsContext()); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Sandbox/Sandbox.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 12 6 | enable 7 | enable 8 | Debug;Release;Production 9 | AnyCPU 10 | Exe 11 | 12 | 13 | 14 | 15 | KORPI_DEBUG;KORPI_TOOLS;KORPI_PROFILE;TRACY_ENABLE;TRACE 16 | false 17 | true 18 | ..\..\Build\Debug\ 19 | 20 | 21 | 22 | 23 | KORPI_RELEASE;KORPI_TOOLS;KORPI_PROFILE;KORPI_OPTIMIZE;TRACY_ENABLE;TRACE 24 | true 25 | false 26 | ..\..\Build\Release\ 27 | 28 | 29 | 30 | 31 | KORPI_PRODUCTION;KORPI_OPTIMIZE 32 | true 33 | false 34 | ..\..\Build\Production\ 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Sandbox/Sandbox.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/Sandbox/Scenes/ExampleScene.cs: -------------------------------------------------------------------------------- 1 | using ImGuiNET; 2 | using KorpiEngine; 3 | using KorpiEngine.SceneManagement; 4 | using KorpiEngine.UI.DearImGui; 5 | using Sandbox.Scenes.FullExample; 6 | using Sandbox.Scenes.PrimitiveExample; 7 | using Sandbox.Scenes.SponzaExample; 8 | 9 | namespace Sandbox.Scenes; 10 | 11 | public abstract class ExampleScene : Scene 12 | { 13 | protected abstract string HelpTitle { get; } 14 | protected abstract string HelpText { get; } 15 | 16 | private HelpWindow _helpWindow = null!; 17 | 18 | 19 | protected override void OnLoad() 20 | { 21 | _helpWindow = new HelpWindow(HelpTitle, HelpText); 22 | } 23 | 24 | 25 | protected override void OnUnload() 26 | { 27 | _helpWindow.Destroy(); 28 | } 29 | } 30 | 31 | 32 | public class HelpWindow(string title, string text) : ImGuiWindow(true) 33 | { 34 | public override string Title => "Help"; 35 | protected override ImGuiWindowFlags Flags => ImGuiWindowFlags.AlwaysAutoResize; 36 | 37 | 38 | protected sealed override void DrawContent() 39 | { 40 | ImGui.Text(title); 41 | ImGui.Separator(); 42 | ImGui.TextWrapped(text); 43 | 44 | ImGui.Separator(); 45 | 46 | if (ImGui.Button("Sponza Example Scene")) 47 | Application.SceneManager.LoadScene(SceneLoadMode.Single); 48 | 49 | if (ImGui.Button("Full Example Scene")) 50 | Application.SceneManager.LoadScene(SceneLoadMode.Single); 51 | 52 | if (ImGui.Button("Primitive Example Scene")) 53 | Application.SceneManager.LoadScene(SceneLoadMode.Single); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Sandbox/Scenes/FullExample/DemoOscillate.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Entities; 2 | using KorpiEngine.Mathematics; 3 | using KorpiEngine.Utils; 4 | using MathOps = KorpiEngine.Mathematics.MathOps; 5 | using Random = KorpiEngine.Mathematics.SharedRandom; 6 | 7 | namespace Sandbox.Scenes.FullExample; 8 | 9 | /// 10 | /// This component makes the entity oscillate up and down. 11 | /// 12 | internal class DemoOscillate : EntityComponent 13 | { 14 | private const float OSCILLATION_SPEED = 1f; 15 | private const float OSCILLATION_HEIGHT = 2f; 16 | 17 | private double _oscillationOffset; 18 | 19 | 20 | protected override void OnStart() 21 | { 22 | // Generate a random offset in the 0-1 range to make the oscillation unique for each entity 23 | _oscillationOffset = Random.Range(0f, 1f); 24 | } 25 | 26 | 27 | protected override void OnUpdate() 28 | { 29 | // Oscillate the entity up and down 30 | double time = Time.TotalTime + _oscillationOffset; 31 | float height = (float)MathOps.Sin(time * OSCILLATION_SPEED) * OSCILLATION_HEIGHT; 32 | Transform.Position = new Vector3(Transform.Position.X, height, Transform.Position.Z); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Sandbox/Scenes/SponzaExample/SponzaExampleScene.cs: -------------------------------------------------------------------------------- 1 | using KorpiEngine.Entities; 2 | using KorpiEngine.Mathematics; 3 | using KorpiEngine.Rendering; 4 | 5 | namespace Sandbox.Scenes.SponzaExample; 6 | 7 | /// 8 | /// This scene demonstrates how to send a web request to load a 3D-model, 9 | /// and spawn it into the scene. 10 | /// 11 | internal class SponzaExampleScene : ExampleScene 12 | { 13 | protected override string HelpTitle => "Sponza Example Scene"; 14 | protected override string HelpText => 15 | "This scene demonstrates how to send a web request to load a 3D-model, and spawn it into the scene.\n" + 16 | "Use the WASD keys to move the camera, and the mouse to look around.\n" + 17 | "Check the console to see the progress of the model loading."; 18 | 19 | 20 | protected override void OnLoad() 21 | { 22 | base.OnLoad(); 23 | 24 | // Create a camera entity with our custom free camera component. 25 | Entity cameraEntity = CreateEntity("Scene Camera"); 26 | cameraEntity.AddComponent(); 27 | cameraEntity.AddComponent(); 28 | cameraEntity.Transform.Position = new Vector3(0f, 1f, 0f); 29 | cameraEntity.Transform.Rotate(new Vector3(0f, 90f, 0f)); 30 | 31 | // Create a directional light to illuminate the scene. 32 | Entity dlEntity = CreateEntity("Directional Light"); 33 | DirectionalLight directionalLight = dlEntity.AddComponent(); 34 | directionalLight.Transform.Forward = new Vector3(-0.225f, -0.965f, -0.135f); 35 | directionalLight.Color = new ColorHDR(1f, 0.9f, 0.7f, 1f); 36 | 37 | // Create an ambient light to provide some base illumination. 38 | Entity alEntity = CreateEntity("Ambient Light"); 39 | AmbientLight ambientLight = alEntity.AddComponent(); 40 | ambientLight.SkyIntensity = 0.4f; 41 | ambientLight.GroundIntensity = 0.2f; 42 | 43 | // Create an entity that loads the Sponza model. 44 | Entity sponzaLoader = CreateEntity("Sponza Loader"); 45 | sponzaLoader.AddComponent(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } --------------------------------------------------------------------------------