diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..29c5e400 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{c,cpp,h,hpp}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..ff664441 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Report a bug that should be fixed +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..23094e9d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest a feature for the project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.gitignore b/.gitignore index fba41f1e..5d998cfd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ x86/ bld/ [Bb]in/ [Bb]in-int/ +[Bb]in_int/ [Oo]bj/ [Ll]og/ [Ll]ogs/ @@ -353,4 +354,13 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ +# Generated Files [Dd]ependencies/ +*.vcxproj +*.vcxproj.filters +*.sln +*.csproj + +*.filters + +Assets/Editor/Layouts/UserLayout.ini diff --git a/Assets/Application.SHConfig b/Assets/Application.SHConfig new file mode 100644 index 00000000..3f7a4ac0 --- /dev/null +++ b/Assets/Application.SHConfig @@ -0,0 +1,4 @@ +Start in Fullscreen: false +Starting Scene ID: 94283040 +Window Size: {x: 1920, y: 1080} +Window Title: SHADE Engine \ No newline at end of file diff --git a/Assets/Audio/Master.bank b/Assets/Audio/Master.bank new file mode 100644 index 00000000..a1b4e563 Binary files /dev/null and b/Assets/Audio/Master.bank differ diff --git a/Assets/Audio/Master.strings.bank b/Assets/Audio/Master.strings.bank new file mode 100644 index 00000000..ecad89fe Binary files /dev/null and b/Assets/Audio/Master.strings.bank differ diff --git a/Assets/Audio/footsteps.bank b/Assets/Audio/footsteps.bank new file mode 100644 index 00000000..ce53111e Binary files /dev/null and b/Assets/Audio/footsteps.bank differ diff --git a/Assets/Editor/Fonts/MaterialIcons-Regular.ttf b/Assets/Editor/Fonts/MaterialIcons-Regular.ttf new file mode 100644 index 00000000..9d09b0fe Binary files /dev/null and b/Assets/Editor/Fonts/MaterialIcons-Regular.ttf differ diff --git a/Assets/Editor/Fonts/Segoe UI.ttf b/Assets/Editor/Fonts/Segoe UI.ttf new file mode 100644 index 00000000..46b3b993 Binary files /dev/null and b/Assets/Editor/Fonts/Segoe UI.ttf differ diff --git a/Assets/Editor/Fonts/fa-solid-900.ttf b/Assets/Editor/Fonts/fa-solid-900.ttf new file mode 100644 index 00000000..f89fc9f8 Binary files /dev/null and b/Assets/Editor/Fonts/fa-solid-900.ttf differ diff --git a/Assets/Editor/Layouts/Default.ini b/Assets/Editor/Layouts/Default.ini new file mode 100644 index 00000000..1099b5ac --- /dev/null +++ b/Assets/Editor/Layouts/Default.ini @@ -0,0 +1,121 @@ +[Window][MainStatusBar] +Pos=0,1007 +Size=1920,20 +Collapsed=0 + +[Window][SHEditorMenuBar] +Pos=0,48 +Size=1920,959 +Collapsed=0 + +[Window][Hierarchy Panel] +Pos=0,189 +Size=308,818 +Collapsed=0 +DockId=0x00000004,0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][Inspector] +Pos=1528,48 +Size=392,959 +Collapsed=0 +DockId=0x00000006,0 + +[Window][Profiler] +Pos=0,48 +Size=308,139 +Collapsed=0 +DockId=0x00000003,0 + +[Window][Viewport] +Pos=227,48 +Size=1457,1012 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面o] +Pos=60,60 +Size=32,64 +Collapsed=0 + +[Window][面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面面] +Pos=60,60 +Size=999,581 +Collapsed=0 + +[Window][o] +Pos=60,60 +Size=32,64 +Collapsed=0 + +[Window][面面] +Pos=60,60 +Size=553,422 +Collapsed=0 + +[Window][] +Pos=60,60 +Size=770,394 +Collapsed=0 + +[Window][ Viewport] +Pos=227,48 +Size=1457,1012 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][ Viewport] +Pos=227,48 +Size=1457,1012 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][ Viewport] +Pos=310,48 +Size=1216,662 +Collapsed=0 +DockId=0x0000000B,0 + +[Window][V] +Pos=310,722 +Size=1501,338 +Collapsed=0 +DockId=0x00000008,0 + +[Window][p] +Pos=310,750 +Size=1501,310 +Collapsed=0 +DockId=0x0000000A,0 + +[Window][ Asset Browser] +Pos=310,712 +Size=1216,295 +Collapsed=0 +DockId=0x0000000C,0 + +[Window][Material Inspector] +Pos=1528,48 +Size=392,959 +Collapsed=0 +DockId=0x00000006,1 + +[Docking][Data] +DockSpace ID=0xC5C9B8AB Window=0xBE4044E9 Pos=0,71 Size=1920,959 Split=X + DockNode ID=0x00000005 Parent=0xC5C9B8AB SizeRef=1526,1036 Split=X + DockNode ID=0x00000001 Parent=0x00000005 SizeRef=308,1036 Split=Y Selected=0x1E6EB881 + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=225,147 Selected=0x1E6EB881 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=225,863 Selected=0xE096E5AE + DockNode ID=0x00000002 Parent=0x00000005 SizeRef=1216,1036 Split=Y Selected=0xB41284E7 + DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1501,672 Split=Y Selected=0xB41284E7 + DockNode ID=0x00000009 Parent=0x00000007 SizeRef=1501,700 Split=Y Selected=0xB41284E7 + DockNode ID=0x0000000B Parent=0x00000009 SizeRef=1501,715 CentralNode=1 Selected=0xB41284E7 + DockNode ID=0x0000000C Parent=0x00000009 SizeRef=1501,295 Selected=0xB128252A + DockNode ID=0x0000000A Parent=0x00000007 SizeRef=1501,310 Selected=0xD446F7B6 + DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1501,338 Selected=0xD9F31532 + DockNode ID=0x00000006 Parent=0xC5C9B8AB SizeRef=392,1036 Selected=0xD3697FB6 + diff --git a/Assets/Materials/BagMaterial.shmat b/Assets/Materials/BagMaterial.shmat new file mode 100644 index 00000000..e538b834 --- /dev/null +++ b/Assets/Materials/BagMaterial.shmat @@ -0,0 +1,8 @@ +- VertexShader: 39210065 + FragmentShader: 46377769 + SubPass: G-Buffer Write + Properties: + data.color: {x: 1, y: 1, z: 1, w: 1} + data.textureIndex: 58303057 + data.alpha: 0 + data.beta: {x: 1, y: 1, z: 1} \ No newline at end of file diff --git a/Assets/Materials/BagMaterial.shmat.shmeta b/Assets/Materials/BagMaterial.shmat.shmeta new file mode 100644 index 00000000..b903a854 --- /dev/null +++ b/Assets/Materials/BagMaterial.shmat.shmeta @@ -0,0 +1,3 @@ +Name: BagMaterial +ID: 123745521 +Type: 7 diff --git a/Assets/Materials/TestMat.shmat b/Assets/Materials/TestMat.shmat new file mode 100644 index 00000000..c1bb43c5 --- /dev/null +++ b/Assets/Materials/TestMat.shmat @@ -0,0 +1,8 @@ +- VertexShader: 39210065 + FragmentShader: 46377769 + SubPass: G-Buffer Write + Properties: + data.color: {x: 1, y: 1, z: 1, w: 1} + data.textureIndex: 64651793 + data.alpha: 0 + data.beta: {x: 1, y: 1, z: 1} \ No newline at end of file diff --git a/Assets/Materials/TestMat.shmat.shmeta b/Assets/Materials/TestMat.shmat.shmeta new file mode 100644 index 00000000..1612ef22 --- /dev/null +++ b/Assets/Materials/TestMat.shmat.shmeta @@ -0,0 +1,3 @@ +Name: TestMat +ID: 126974645 +Type: 7 diff --git a/Assets/Materials/WhiteMat.shmat b/Assets/Materials/WhiteMat.shmat new file mode 100644 index 00000000..5a1cb199 --- /dev/null +++ b/Assets/Materials/WhiteMat.shmat @@ -0,0 +1,8 @@ +- VertexShader: 39210065 + FragmentShader: 46377769 + SubPass: G-Buffer Write + Properties: + data.color: {x: 1, y: 1, z: 1, w: 1} + data.textureIndex: 0 + data.alpha: 0 + data.beta: {x: 1, y: 1, z: 1} \ No newline at end of file diff --git a/Assets/Materials/WhiteMat.shmat.shmeta b/Assets/Materials/WhiteMat.shmat.shmeta new file mode 100644 index 00000000..588afba4 --- /dev/null +++ b/Assets/Materials/WhiteMat.shmat.shmeta @@ -0,0 +1,3 @@ +Name: WhiteMat +ID: 124370424 +Type: 7 diff --git a/Assets/Models/HouseModular.gltf b/Assets/Models/HouseModular.gltf new file mode 100644 index 00000000..3e9e9a05 --- /dev/null +++ b/Assets/Models/HouseModular.gltf @@ -0,0 +1,1358 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v3.3.32", + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "FloorLarge" + }, + { + "mesh" : 1, + "name" : "FloorSmall" + }, + { + "mesh" : 2, + "name" : "FloorLong" + }, + { + "mesh" : 3, + "name" : "Pillar" + }, + { + "mesh" : 4, + "name" : "WallEnd" + }, + { + "mesh" : 5, + "name" : "WallCorner" + }, + { + "mesh" : 6, + "name" : "WallDefault" + }, + { + "mesh" : 7, + "name" : "WallLarge" + }, + { + "mesh" : 8, + "name" : "WallDiagonal" + }, + { + "mesh" : 9, + "name" : "WallTBlock" + }, + { + "mesh" : 10, + "name" : "WindowLarge" + }, + { + "mesh" : 11, + "name" : "WindowSmallOpened" + }, + { + "mesh" : 12, + "name" : "WindowSmallClosed" + }, + { + "mesh" : 13, + "name" : "WindowLargeOpen" + }, + { + "mesh" : 14, + "name" : "WallDoorHole" + }, + { + "mesh" : 15, + "name" : "Door", + "translation" : [ + -0.4000000059604645, + 0, + 0.09999999403953552 + ] + }, + { + "mesh" : 16, + "name" : "DoorFrame" + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "Material", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4000000059604645 + } + } + ], + "meshes" : [ + { + "name" : "Cube.013", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + }, + { + "name" : "Cube.015", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 0 + } + ] + }, + { + "name" : "Cube.014", + "primitives" : [ + { + "attributes" : { + "POSITION" : 8, + "NORMAL" : 9, + "TEXCOORD_0" : 10 + }, + "indices" : 11, + "material" : 0 + } + ] + }, + { + "name" : "Cube.043", + "primitives" : [ + { + "attributes" : { + "POSITION" : 12, + "NORMAL" : 13, + "TEXCOORD_0" : 14 + }, + "indices" : 15, + "material" : 0 + } + ] + }, + { + "name" : "Cube.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 16, + "NORMAL" : 17, + "TEXCOORD_0" : 18 + }, + "indices" : 19, + "material" : 0 + } + ] + }, + { + "name" : "Cube.009", + "primitives" : [ + { + "attributes" : { + "POSITION" : 20, + "NORMAL" : 21, + "TEXCOORD_0" : 22 + }, + "indices" : 23, + "material" : 0 + } + ] + }, + { + "name" : "Cube.002", + "primitives" : [ + { + "attributes" : { + "POSITION" : 24, + "NORMAL" : 25, + "TEXCOORD_0" : 26 + }, + "indices" : 27, + "material" : 0 + } + ] + }, + { + "name" : "Cube.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 28, + "NORMAL" : 29, + "TEXCOORD_0" : 30 + }, + "indices" : 31, + "material" : 0 + } + ] + }, + { + "name" : "Cube.008", + "primitives" : [ + { + "attributes" : { + "POSITION" : 32, + "NORMAL" : 33, + "TEXCOORD_0" : 34 + }, + "indices" : 35, + "material" : 0 + } + ] + }, + { + "name" : "Cube.011", + "primitives" : [ + { + "attributes" : { + "POSITION" : 36, + "NORMAL" : 37, + "TEXCOORD_0" : 38 + }, + "indices" : 39, + "material" : 0 + } + ] + }, + { + "name" : "Cube.034", + "primitives" : [ + { + "attributes" : { + "POSITION" : 40, + "NORMAL" : 41, + "TEXCOORD_0" : 42 + }, + "indices" : 43, + "material" : 0 + } + ] + }, + { + "name" : "Cube.007", + "primitives" : [ + { + "attributes" : { + "POSITION" : 44, + "NORMAL" : 45, + "TEXCOORD_0" : 46 + }, + "indices" : 47, + "material" : 0 + } + ] + }, + { + "name" : "Cube.016", + "primitives" : [ + { + "attributes" : { + "POSITION" : 48, + "NORMAL" : 49, + "TEXCOORD_0" : 50 + }, + "indices" : 51, + "material" : 0 + } + ] + }, + { + "name" : "Cube.025", + "primitives" : [ + { + "attributes" : { + "POSITION" : 52, + "NORMAL" : 53, + "TEXCOORD_0" : 54 + }, + "indices" : 55, + "material" : 0 + } + ] + }, + { + "name" : "Cube.006", + "primitives" : [ + { + "attributes" : { + "POSITION" : 56, + "NORMAL" : 57, + "TEXCOORD_0" : 58 + }, + "indices" : 59, + "material" : 0 + } + ] + }, + { + "name" : "Cube.005", + "primitives" : [ + { + "attributes" : { + "POSITION" : 60, + "NORMAL" : 61, + "TEXCOORD_0" : 62 + }, + "indices" : 63, + "material" : 0 + } + ] + }, + { + "name" : "Cube", + "primitives" : [ + { + "attributes" : { + "POSITION" : 64, + "NORMAL" : 65, + "TEXCOORD_0" : 66 + }, + "indices" : 67, + "material" : 0 + } + ] + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 56, + "max" : [ + 1, + 0.009999998845160007, + 1 + ], + "min" : [ + -1, + -0.03999999910593033, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 56, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 56, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 96, + "type" : "SCALAR" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.5, + 0.009999998845160007, + 0.5 + ], + "min" : [ + -0.5, + -0.03999999910593033, + -0.5 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 24, + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 24, + "type" : "VEC2" + }, + { + "bufferView" : 7, + "componentType" : 5123, + "count" : 36, + "type" : "SCALAR" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1, + 0.009999998845160007, + 0.5 + ], + "min" : [ + -1, + -0.03999999910593033, + -0.5 + ], + "type" : "VEC3" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 36, + "type" : "VEC3" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 36, + "type" : "VEC2" + }, + { + "bufferView" : 11, + "componentType" : 5123, + "count" : 60, + "type" : "SCALAR" + }, + { + "bufferView" : 12, + "componentType" : 5126, + "count" : 66, + "max" : [ + 0.1199999749660492, + 2.200000047683716, + 0.125 + ], + "min" : [ + -0.1200004369020462, + 0, + -0.11499999463558197 + ], + "type" : "VEC3" + }, + { + "bufferView" : 13, + "componentType" : 5126, + "count" : 66, + "type" : "VEC3" + }, + { + "bufferView" : 14, + "componentType" : 5126, + "count" : 66, + "type" : "VEC2" + }, + { + "bufferView" : 15, + "componentType" : 5123, + "count" : 108, + "type" : "SCALAR" + }, + { + "bufferView" : 16, + "componentType" : 5126, + "count" : 68, + "max" : [ + 0.5199999809265137, + 2.200000047683716, + 0.11999999731779099 + ], + "min" : [ + -0.5, + 0, + -0.11999999731779099 + ], + "type" : "VEC3" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 68, + "type" : "VEC3" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 68, + "type" : "VEC2" + }, + { + "bufferView" : 19, + "componentType" : 5123, + "count" : 108, + "type" : "SCALAR" + }, + { + "bufferView" : 20, + "componentType" : 5126, + "count" : 134, + "max" : [ + 1, + 2.200000047683716, + 0.11999999731779099 + ], + "min" : [ + -0.12000006437301636, + 0, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 134, + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 134, + "type" : "VEC2" + }, + { + "bufferView" : 23, + "componentType" : 5123, + "count" : 240, + "type" : "SCALAR" + }, + { + "bufferView" : 24, + "componentType" : 5126, + "count" : 90, + "max" : [ + 0.5000000596046448, + 2.200000047683716, + 0.11999999731779099 + ], + "min" : [ + -0.4999999403953552, + 0, + -0.11999999731779099 + ], + "type" : "VEC3" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 90, + "type" : "VEC3" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 90, + "type" : "VEC2" + }, + { + "bufferView" : 27, + "componentType" : 5123, + "count" : 168, + "type" : "SCALAR" + }, + { + "bufferView" : 28, + "componentType" : 5126, + "count" : 128, + "max" : [ + 1, + 2.200000047683716, + 0.11999999731779099 + ], + "min" : [ + -0.9999999403953552, + 0, + -0.11999999731779099 + ], + "type" : "VEC3" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 128, + "type" : "VEC3" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 128, + "type" : "VEC2" + }, + { + "bufferView" : 31, + "componentType" : 5123, + "count" : 240, + "type" : "SCALAR" + }, + { + "bufferView" : 32, + "componentType" : 5126, + "count" : 96, + "max" : [ + 0.6200000047683716, + 2.200000047683716, + 0.619999885559082 + ], + "min" : [ + -0.5, + 0, + -0.5 + ], + "type" : "VEC3" + }, + { + "bufferView" : 33, + "componentType" : 5126, + "count" : 96, + "type" : "VEC3" + }, + { + "bufferView" : 34, + "componentType" : 5126, + "count" : 96, + "type" : "VEC2" + }, + { + "bufferView" : 35, + "componentType" : 5123, + "count" : 168, + "type" : "SCALAR" + }, + { + "bufferView" : 36, + "componentType" : 5126, + "count" : 200, + "max" : [ + 1, + 2.200000047683716, + 0.11999999731779099 + ], + "min" : [ + -1, + 0, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 37, + "componentType" : 5126, + "count" : 200, + "type" : "VEC3" + }, + { + "bufferView" : 38, + "componentType" : 5126, + "count" : 200, + "type" : "VEC2" + }, + { + "bufferView" : 39, + "componentType" : 5123, + "count" : 360, + "type" : "SCALAR" + }, + { + "bufferView" : 40, + "componentType" : 5126, + "count" : 973, + "max" : [ + 1.0000001192092896, + 2.200000286102295, + 0.11999999731779099 + ], + "min" : [ + -1, + 0, + -0.125 + ], + "type" : "VEC3" + }, + { + "bufferView" : 41, + "componentType" : 5126, + "count" : 973, + "type" : "VEC3" + }, + { + "bufferView" : 42, + "componentType" : 5126, + "count" : 973, + "type" : "VEC2" + }, + { + "bufferView" : 43, + "componentType" : 5123, + "count" : 1632, + "type" : "SCALAR" + }, + { + "bufferView" : 44, + "componentType" : 5126, + "count" : 613, + "max" : [ + 0.5000000596046448, + 2.200000286102295, + 0.33178770542144775 + ], + "min" : [ + -0.4999999403953552, + 0, + -0.125 + ], + "type" : "VEC3" + }, + { + "bufferView" : 45, + "componentType" : 5126, + "count" : 613, + "type" : "VEC3" + }, + { + "bufferView" : 46, + "componentType" : 5126, + "count" : 613, + "type" : "VEC2" + }, + { + "bufferView" : 47, + "componentType" : 5123, + "count" : 1068, + "type" : "SCALAR" + }, + { + "bufferView" : 48, + "componentType" : 5126, + "count" : 538, + "max" : [ + 0.5000000596046448, + 2.200000286102295, + 0.11999999731779099 + ], + "min" : [ + -0.4999999701976776, + 0, + -0.125 + ], + "type" : "VEC3" + }, + { + "bufferView" : 49, + "componentType" : 5126, + "count" : 538, + "type" : "VEC3" + }, + { + "bufferView" : 50, + "componentType" : 5126, + "count" : 538, + "type" : "VEC2" + }, + { + "bufferView" : 51, + "componentType" : 5123, + "count" : 924, + "type" : "SCALAR" + }, + { + "bufferView" : 52, + "componentType" : 5126, + "count" : 1067, + "max" : [ + 1.0000001192092896, + 2.200000286102295, + 0.47529903054237366 + ], + "min" : [ + -1, + 0, + -0.125 + ], + "type" : "VEC3" + }, + { + "bufferView" : 53, + "componentType" : 5126, + "count" : 1067, + "type" : "VEC3" + }, + { + "bufferView" : 54, + "componentType" : 5126, + "count" : 1067, + "type" : "VEC2" + }, + { + "bufferView" : 55, + "componentType" : 5123, + "count" : 1776, + "type" : "SCALAR" + }, + { + "bufferView" : 56, + "componentType" : 5126, + "count" : 202, + "max" : [ + 0.5, + 2.200000286102295, + 0.11999999731779099 + ], + "min" : [ + -0.5, + 0, + -0.11999999731779099 + ], + "type" : "VEC3" + }, + { + "bufferView" : 57, + "componentType" : 5126, + "count" : 202, + "type" : "VEC3" + }, + { + "bufferView" : 58, + "componentType" : 5126, + "count" : 202, + "type" : "VEC2" + }, + { + "bufferView" : 59, + "componentType" : 5123, + "count" : 384, + "type" : "SCALAR" + }, + { + "bufferView" : 60, + "componentType" : 5126, + "count" : 842, + "max" : [ + 0.7999999523162842, + 2.000326156616211, + 0.05949999764561653 + ], + "min" : [ + -0.018654286861419678, + 0.0003262758255004883, + -0.09449999779462814 + ], + "type" : "VEC3" + }, + { + "bufferView" : 61, + "componentType" : 5126, + "count" : 842, + "type" : "VEC3" + }, + { + "bufferView" : 62, + "componentType" : 5126, + "count" : 842, + "type" : "VEC2" + }, + { + "bufferView" : 63, + "componentType" : 5123, + "count" : 3324, + "type" : "SCALAR" + }, + { + "bufferView" : 64, + "componentType" : 5126, + "count" : 298, + "max" : [ + 0.4599999785423279, + 2.0799999237060547, + 0.11121083796024323 + ], + "min" : [ + -0.4599999785423279, + 0, + -0.1087893396615982 + ], + "type" : "VEC3" + }, + { + "bufferView" : 65, + "componentType" : 5126, + "count" : 298, + "type" : "VEC3" + }, + { + "bufferView" : 66, + "componentType" : 5126, + "count" : 298, + "type" : "VEC2" + }, + { + "bufferView" : 67, + "componentType" : 5123, + "count" : 486, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 672, + "byteOffset" : 0, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 672, + "byteOffset" : 672, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 448, + "byteOffset" : 1344, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 192, + "byteOffset" : 1792, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 1984, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 2272, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 192, + "byteOffset" : 2560, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 2752, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 2824, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 3256, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 3688, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 120, + "byteOffset" : 3976, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 792, + "byteOffset" : 4096, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 792, + "byteOffset" : 4888, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 528, + "byteOffset" : 5680, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 216, + "byteOffset" : 6208, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 6424, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 7240, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 544, + "byteOffset" : 8056, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 216, + "byteOffset" : 8600, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1608, + "byteOffset" : 8816, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1608, + "byteOffset" : 10424, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1072, + "byteOffset" : 12032, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 480, + "byteOffset" : 13104, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1080, + "byteOffset" : 13584, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1080, + "byteOffset" : 14664, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 720, + "byteOffset" : 15744, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 336, + "byteOffset" : 16464, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1536, + "byteOffset" : 16800, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1536, + "byteOffset" : 18336, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1024, + "byteOffset" : 19872, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 480, + "byteOffset" : 20896, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1152, + "byteOffset" : 21376, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1152, + "byteOffset" : 22528, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 23680, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 336, + "byteOffset" : 24448, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 2400, + "byteOffset" : 24784, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2400, + "byteOffset" : 27184, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1600, + "byteOffset" : 29584, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 720, + "byteOffset" : 31184, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 11676, + "byteOffset" : 31904, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 11676, + "byteOffset" : 43580, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 7784, + "byteOffset" : 55256, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3264, + "byteOffset" : 63040, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 7356, + "byteOffset" : 66304, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 7356, + "byteOffset" : 73660, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 4904, + "byteOffset" : 81016, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2136, + "byteOffset" : 85920, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 6456, + "byteOffset" : 88056, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 6456, + "byteOffset" : 94512, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 4304, + "byteOffset" : 100968, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1848, + "byteOffset" : 105272, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 12804, + "byteOffset" : 107120, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 12804, + "byteOffset" : 119924, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 8536, + "byteOffset" : 132728, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3552, + "byteOffset" : 141264, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 2424, + "byteOffset" : 144816, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2424, + "byteOffset" : 147240, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1616, + "byteOffset" : 149664, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 151280, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 10104, + "byteOffset" : 152048, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 10104, + "byteOffset" : 162152, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 6736, + "byteOffset" : 172256, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 6648, + "byteOffset" : 178992, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3576, + "byteOffset" : 185640, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3576, + "byteOffset" : 189216, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2384, + "byteOffset" : 192792, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 972, + "byteOffset" : 195176, + "target" : 34963 + } + ], + "buffers" : [ + { + "byteLength" : 196148, + "uri" : "data:application/octet-stream;base64,AACAvwnXIzwAAIA/AACAvwnXIzwAAIA/AACAvwnXIzwAAIA/AACAvwrXI70AAIA/AACAvwrXI70AAIA/AACAvwrXI70AAIA/AACAvwnXIzwAAIC/AACAvwnXIzwAAIC/AACAvwnXIzwAAIC/AACAvwrXI70AAIC/AACAvwrXI70AAIC/AACAvwrXI70AAIC/AACAPwnXIzwAAIA/AACAPwnXIzwAAIA/AACAPwnXIzwAAIA/AACAPwrXI70AAIA/AACAPwrXI70AAIA/AACAPwrXI70AAIA/AACAPwnXIzwAAIC/AACAPwnXIzwAAIC/AACAPwnXIzwAAIC/AACAPwrXI70AAIC/AACAPwrXI70AAIC/AACAPwrXI70AAIC/AAAAAAnXIzwAAIC/AAAAAAnXIzwAAIC/AAAAAAnXIzwAAIC/AAAAAArXI70AAIC/AAAAAArXI70AAIC/AAAAAArXI70AAIC/AAAAAAnXIzwAAIA/AAAAAAnXIzwAAIA/AAAAAAnXIzwAAIA/AAAAAArXI70AAIA/AAAAAArXI70AAIA/AAAAAArXI70AAIA/AACAvwnXIzwAAACAAACAvwnXIzwAAACAAACAvwnXIzwAAACAAACAvwrXI70AAACAAACAvwrXI70AAACAAACAvwrXI70AAACAAACAPwnXIzwAAACAAACAPwnXIzwAAACAAACAPwnXIzwAAACAAACAPwrXI70AAACAAACAPwrXI70AAACAAACAPwrXI70AAACAAAAAAArXI70AAACAAAAAAArXI70AAACAAAAAAArXI70AAACAAAAAAArXI70AAACAAAAAAAnXIzwAAACAAAAAAAnXIzwAAACAAAAAAAnXIzwAAACAAAAAAAnXIzwAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/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AMB/PwDAfz8AwH8/FP13PwDNNzwAwH8/AMB/P5pC/T4AzTc8AMB/PwDAfz8AwH8/AMB/PwAABgAJAAAACQADAAcAEgAWAAcAFgALABQADgARABQAEQAXAAwAAQAFAAwABQAQAAgAAgANAAgADQATABUADwAEABUABAAKAAAAgL8J1yM8AAAAPwAAgL8J1yM8AAAAPwAAgL8J1yM8AAAAPwAAgL8K1yO9AAAAPwAAgL8K1yO9AAAAPwAAgL8K1yO9AAAAPwAAgL8J1yM8AAAAvwAAgL8J1yM8AAAAvwAAgL8J1yM8AAAAvwAAgL8K1yO9AAAAvwAAgL8K1yO9AAAAvwAAgL8K1yO9AAAAvwAAgD8J1yM8AAAAPwAAgD8J1yM8AAAAPwAAgD8J1yM8AAAAPwAAgD8K1yO9AAAAPwAAgD8K1yO9AAAAPwAAgD8K1yO9AAAAPwAAgD8J1yM8AAAAvwAAgD8J1yM8AAAAvwAAgD8J1yM8AAAAvwAAgD8K1yO9AAAAvwAAgD8K1yO9AAAAvwAAgD8K1yO9AAAAvwAAAAAJ1yM8AAAAvwAAAAAJ1yM8AAAAvwAAAAAJ1yM8AAAAvwAAAAAK1yO9AAAAvwAAAAAK1yO9AAAAvwAAAAAK1yO9AAAAvwAAAAAJ1yM8AAAAPwAAAAAJ1yM8AAAAPwAAAAAJ1yM8AAAAPwAAAAAK1yO9AAAAPwAAAAAK1yO9AAAAPwAAAAAK1yO9AAAAPwAAgL8AAAAAAAAAgAAAAAAAAAAAAACAPwAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgL8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAAAAAAAAAACAPwAAAAAAAIA/AAAAgAAAgD8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgD8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/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/v//vrgeBT7MzMy9/v//vrgeBT7MzMy9/v//vrgeBT7MzMy9AAAAP4XrAUDMzMy9AAAAP4XrAUDMzMy9AAAAP4XrAUDMzMy9AAAAP4XrAUDMzMy9/v//voXrAUDMzMy9/v//voXrAUDMzMy9/v//voXrAUDMzMy9AQAAP83MDEDMzMy9AQAAP83MDEDMzMy9AQAAP83MDEDMzMy9/v//vs3MDEDMzMy9/v//vs3MDEDMzMy9/v//vs3MDEDMzMy9AAAAP83MDEAAAACAAAAAP83MDEAAAACA/v//vs3MDEAAAACA/v//vs3MDEAAAACA/v//voXrAUAAAACA/v//vrgeBT4AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAADL5ZD8h+eQ+AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAPwAAAAAAAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAD//38/AAAAADL5ZD8h+eQ+AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAP//fz8AAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAP//fz8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADL5ZD8h+eS+AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACA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AACAP5DC9T2PwvU9AACAPwAAAACPwvU9AACAPwAAAACPwvU9AACAPwAAAACPwvU9AAAAAM3MDEDMzMw9AAAAAM3MDEDMzMw9AAAAAM3MDEDMzMw9AAAAAM3MDEDMzMw9AAAAAIXrAUDMzMw9AAAAAIXrAUDMzMw9AAAAAIXrAUDMzMw9AAAAALgeBT7MzMw9AAAAALgeBT7MzMw9AAAAALgeBT7MzMw9AAAAALgeBT7MzMw9AAAAAJDC9T2PwvU9AAAAAJDC9T2PwvU9AAAAAJDC9T2PwvU9AAAAAJDC9T2PwvU9AAAAAAAAAACPwvU9AAAAAAAAAACPwvU9AAAAAAAAAACPwvU9//9/vwAAAAAAAACA//9/vwAAAAAAAACA//9/v83MDEAAAACA//9/v83MDEAAAACA//9/v4XrAUAAAACA//9/v5DC9T0AAACA//9/v7geBT4AAACAAACAP83MDEAAAACAAACAP83MDEAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAP7geBT4AAACAAACAP5DC9T0AAACAAAAAAAAAAAAAAACAAAAAAM3MDEAAAACAAAAAAM3MDEAAAACAAACAP4XrAUAAAACA//9/vwAAAACPwvW9//9/vwAAAACPwvW9//9/vwAAAACPwvW9//9/v5DC9T2PwvW9//9/v5DC9T2PwvW9//9/v5DC9T2PwvW9AAAAAAAAAACPwvW9AAAAAAAAAACPwvW9AAAAAAAAAACPwvW9//9/vwAAAAAAAACA//9/vwAAAAAAAACA//9/v5DC9T0AAACAAAAAAAAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAACPwvW9AACAPwAAAACPwvW9AACAPwAAAACPwvW9AACAP5DC9T0AAACAAACAP5DC9T2PwvW9AACAP5DC9T2PwvW9AACAP5DC9T2PwvW9AACAP7geBT4AAACAAACAP4XrAUAAAACAAACAP7geBT7MzMy9AACAP7geBT7MzMy9AACAP7geBT7MzMy9AACAP4XrAUDMzMy9AACAP4XrAUDMzMy9AAAAALgeBT7MzMy9AAAAALgeBT7MzMy9AAAAALgeBT7MzMy9AAAAALgeBT7MzMy9//9/v7geBT7MzMy9//9/v7geBT7MzMy9//9/v7geBT7MzMy9AAAAAJDC9T2PwvW9AAAAAJDC9T2PwvW9AAAAAJDC9T2PwvW9AAAAAJDC9T2PwvW9AAAAAIXrAUDMzMy9AAAAAIXrAUDMzMy9AAAAAIXrAUDMzMy9//9/v4XrAUDMzMy9//9/v4XrAUDMzMy9//9/v4XrAUDMzMy9AAAAAM3MDEDMzMy9AAAAAM3MDEDMzMy9AAAAAM3MDEDMzMy9AAAAAM3MDEDMzMy9AACAP83MDEDMzMy9AACAP83MDEDMzMy9AACAP83MDEDMzMy9//9/v83MDEDMzMy9//9/v83MDEDMzMy9//9/v83MDEDMzMy9AAAAAM3MDEAAAACAAAAAAM3MDEAAAACA//9/v83MDEAAAACA//9/v83MDEAAAACAAACAP83MDEAAAACAAACAP83MDEAAAACA//9/v4XrAUAAAACA//9/v7geBT4AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADP5ZD8h+eQ+AACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAADP5ZD8h+eQ+AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AAAAADP5ZD8h+eQ+AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAADL5ZD8h+eQ+AAAAADP5ZD8h+eQ+AAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAP//fz8AAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADP5ZD8h+eS+AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADL5ZD8h+eS+AACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADL5ZD8h+eS+AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAD//3+/AAAAADL5ZD8h+eS+AAAAADP5ZD8h+eS+AACAvwAAAAAAAACAAAAAAAAAAAD//3+/AAAAADP5ZD8h+eS+AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAADL5ZD8h+eS+AAAAADP5ZD8h+eS+AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAD//3+/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAD//3+/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAP//fz8AAACAAAAAAAAAgD8AAACA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yMzMPgAAAL+8HgU+/P//PgAAAL+8HgU+/P//PgAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgPMENT8AAAAA9AQ1PwAAgL8AAAAAAAAAgAAAAAAAAIA/AAAAgPUENT8AAAAA8gQ1PwAAAAAAAIC/AAAAgAAAAAAAAAAAAACAv/MENT8AAAAA9AQ1PwAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgPUENT8AAAAA8gQ1PwAAgL8AAAAAAAAAgP//f78AAAAAAAAAgPMENT8AAAAA9AQ1P/UENT8AAAAA8gQ1PwAAAAAAAAAAAACAv/MENT8AAAAA9AQ1P/UENT8AAAAA8gQ1PwAAgL8AAAAAAAAAgOEF0T7yBVE/2wXRPvMENT8AAAAA9AQ1PwAAAAAAAAAAAACAv+EF0T7yBVE/2wXRPvMENT8AAAAA9AQ1PwAAgL8AAAAAAAAAgP//f78AAAAAAAAAgOEF0T7yBVE/2wXRPvMENT8AAAAA9AQ1PwAAAAAAAAAAAACAv+EF0T7yBVE/2wXRPvMENT8AAAAA9AQ1PwAAgL8AAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAgL8AAAAAAAAAgP//f78AAAAAAAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgP//f78AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAgL8AAAAAAAAAgPMENb8AAAAA9QQ1vwAAAAD//3+/AAAAgAAAgL8AAAAAAAAAgPMENb8AAAAA9QQ1v+wF0b7sBVE/6QXRvvMENb8AAAAA9QQ1vwAAAAD//3+/AAAAgAAAAAAAAAAAAACAvwAAgL8AAAAAAAAAgAAAAAD//3+/AAAAgAAAgL8AAAAAAAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAvwAAAAAAAAAAAACAv/MENb8AAAAA9QQ1v+wF0b7sBVE/6QXRvgAAAAAAAAAAAACAvwAAAAAAAAAAAACAv/IENb8AAAAA9AQ1v+wF0b7sBVE/6QXRvgAAAAAAAAAAAACAvwAAAAAAAAAAAACAv/sENb8AAAAA7QQ1v/IENb8AAAAA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zMzMPQAAgD/NzAxAzMzMPQAAAADNzAxA9MzMPQAAAADNzAxA9MzMPQAAAADNzAxA9MzMPQAAAADNzAxA9MzMPQAAAAAAAAAAj8L1PQAAAAAAAAAAj8L1PQAAAAAAAAAAj8L1PQAAgL/NzAxAzMzMPQAAgL/NzAxAzMzMPQAAgL/NzAxAzMzMPQAAgL+4HgU+zMzMPQAAgL+4HgU+zMzMPQAAgL+4HgU+zMzMPQAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAgL/NzAxAAAAAgAAAgL/NzAxAAAAAgAAAAAAAAAAAAADAsgAAAADNzAxAAABINAAAAADNzAxAAABINAAAgL+QwvU9AAAAgAAAgL+4HgU+AAAAgAAAgL+F6wFAAAAAgAAAgD8AAAAAAAAAgAAAgD8AAAAAAAAAgAAAgD/NzAxAAAAAgAAAgD/NzAxAAAAAgAAAgD+4HgU+AAAAgAAAgD+QwvU9AAAAgAAAgD+F6wFAAAAAgAAAgD+F6wFAzMzMvQAAgD+F6wFAzMzMvQAAgD+F6wFAzMzMvcrMzD2F6wFAx8zMvcrMzD2F6wFAx8zMvcrMzD2F6wFAx8zMvcrMzD2F6wFAx8zMvQAAgD/NzAxAzMzMvQAAgD/NzAxAzMzMvQAAgD/NzAxAzMzMvQAAgD+4HgU+zMzMvQAAgD+4HgU+zMzMvQAAgD+4HgU+zMzMvQAAgD+F6wFAAAAAgAAAgD/NzAxAAAAAgAAAgD/NzAxAAAAAgAAAgD+4HgU+AAAAgAAAgD+QwvU9AAAAgAAAgD+QwvU9j8L1vQAAgD+QwvU9j8L1vQAAgD+QwvU9j8L1vQAAgD8AAAAAAAAAgAAAgD8AAAAAAAAAgAAAgD8AAAAAj8L1vQAAgD8AAAAAj8L1vQAAgD8AAAAAj8L1vQAAAAAAAAAAAADAso/C9T0AAAAAlcL1vY/C9T0AAAAAlcL1vY/C9T0AAAAAlcL1vQAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAACYwvU9AACAv4jC9T0AAAAAAACAv4jC9T0AAAAAAACAv4jC9T0AAAAAAACAv4jC9T2YwvU9AACAv4jC9T2YwvU9AACAv4jC9T2YwvU9AACAv4/C9T2QwvU9lcL1vY/C9T2QwvU9lcL1vY/C9T2QwvU9lcL1vY/C9T2QwvU9lcL1vcDMzD28HgU+AACAv8DMzD28HgU+AACAv8DMzD28HgU+AACAv8rMzD24HgU+x8zMvcrMzD24HgU+x8zMvcrMzD24HgU+x8zMvcrMzD24HgU+x8zMvcDMzD2E6wFAAACAv8DMzD2E6wFAAACAv8DMzD2E6wFAAACAvwAAAAC8HgU+AACAvwAAAACE6wFAAACAvwAAAADNzAxAAACAvwAAAADNzAxAAACAv8DMzD3NzAxAAACAv8DMzD3NzAxAAACAv8DMzD3NzAxAAACAvwAAAADNzAxAAABINAAAAADNzAxAAABINNDMzD3NzAxAwczMvdDMzD3NzAxAwczMvdDMzD3NzAxAwczMvdDMzD3NzAxAwczMvcDMzL3NzAxAAACAv8DMzL3NzAxAAACAv8DMzL3NzAxAAACAv9DMzL3NzAxAwczMvdDMzL3NzAxAwczMvdDMzL3NzAxAwczMvdDMzL3NzAxAwczMvcDMzL2E6wFAAACAv8DMzL2E6wFAAACAv8DMzL2E6wFAAACAvwAAAADNzAxAAACAvwAAAADNzAxAAACAvwAAAADNzAxAAABINAAAAADNzAxAAABINAAAAACE6wFAAACAvwAAAAC8HgU+AACAvwAAAACYwvU9AACAv8DMzL28HgU+AACAv8DMzL28HgU+AACAv8DMzL28HgU+AACAv8rMzL24HgU+x8zMvcrMzL24HgU+x8zMvcrMzL24HgU+x8zMvcrMzL24HgU+x8zMvYjC9b2YwvU9AACAv4jC9b2YwvU9AACAv4jC9b2YwvU9AACAv4/C9b2QwvU9lcL1vY/C9b2QwvU9lcL1vY/C9b2QwvU9lcL1vY/C9b2QwvU9lcL1vYjC9b0AAAAAAACAv4jC9b0AAAAAAACAv4jC9b0AAAAAAACAv4/C9b0AAAAAlcL1vY/C9b0AAAAAlcL1vY/C9b0AAAAAlcL1vY/C9b0AAAAAlcL1vQAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAADAsgAAAAAAAAAAAADAsgAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAj8L1vQAAgL8AAAAAj8L1vQAAgL8AAAAAj8L1vQAAgL+QwvU9AAAAgAAAgL+QwvU9j8L1vQAAgL+QwvU9j8L1vQAAgL+QwvU9j8L1vQAAgL+4HgU+AAAAgAAAgL+4HgU+zMzMvQAAgL+4HgU+zMzMvQAAgL+4HgU+zMzMvQAAgL+F6wFAAAAAgAAAgL+F6wFAzMzMvQAAgL+F6wFAzMzMvQAAgL+F6wFAzMzMvQAAgL/NzAxAAAAAgAAAgL/NzAxAAAAAgAAAgL/NzAxAzMzMvQAAgL/NzAxAzMzMvQAAgL/NzAxAzMzMvcrMzL2F6wFAx8zMvcrMzL2F6wFAx8zMvcrMzL2F6wFAx8zMvcrMzL2F6wFAx8zMvQAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAgP//sTQ3PDw0AACAPwAAxDQAAAAAAACAPwAAgD8AAAAAAAAAgAAAAAAAAAAAAACAPwHjsjMX+WQ/jfnkPgAAgD8AAAAAAAAAgAHjsjMX+WQ/jfnkPgAAxDQAAAAAAACAPwAAgD8AAAAAAAAAgAAAxLQAAAAAAACAP/n/sbQ3PDw0AACAP///sTQ3PDw0AACAPwAAxDQAAAAAAACAPwAAxLQAAAAAAACAPwHjsrMX+WQ/jfnkPgHjsjMX+WQ/jfnkPgAAxDQAAAAAAACAPwHjsrMX+WQ/jfnkPgAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwHjsjMX+WQ/jfnkPgAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgL8AAAAAAAAAgAAAxLQAAAAAAACAP/n/sbQ3PDw0AACAPwAAgL8AAAAAAAAAgAHjsrMX+WQ/jfnkPgAAAAAAAAAAAACAP/r/n7P//38/AAAAgP//sTQ3PDw0AACAPwAAgD8AAAAAAAAAgPn/sbQ3PDw0AACAP/r/n7P//38/AAAAgAAAAAD//38/AAAAgP//sTQ3PDw0AACAPwAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAgL8AAAAAAAAAgPn/sbQ3PDw0AACAPwAAAAD//38/AAAAgAAAgL8AAAAAAAAAgAAAxLQAAAAAAACAPwHjsrMX+WQ/jfnkPgAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAgL8AAAAAAAAAgAAAAAD//38/AAAAgAAAAAAAAIC/AAAAgPr/n7P//38/AAAAgAAAAAD//38/AAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/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AAAAAI/C9T3+/3+/AAAAAI/C9T3+/3+/zcwMQMzMzD3+/3+/zcwMQMzMzD3+/3+/zcwMQMzMzD3+/3+/hesBQMzMzD3+/3+/hesBQMzMzD3+/3+/hesBQMzMzD3+/3+/hesBQMzMzD3+/3+/kML1PY/C9T3+/3+/kML1PY/C9T3+/3+/kML1PY/C9T3+/3+/kML1PY/C9T3+/3+/uB4FPszMzD3+/3+/uB4FPszMzD3+/3+/uB4FPszMzD3+/3+/uB4FPszMzD0AAIA/zcwMQMzMzD0AAIA/zcwMQMzMzD0AAIA/zcwMQMzMzD0AAIA/hesBQMzMzD0AAIA/hesBQMzMzD0AAIA/hesBQMzMzD0AAIA/uB4FPszMzD0AAIA/uB4FPszMzD0AAIA/uB4FPszMzD0AAIA/kML1PY/C9T0AAIA/kML1PY/C9T0AAIA/kML1PY/C9T0AAIA/AAAAAI/C9T0AAIA/AAAAAI/C9T0AAIA/AAAAAI/C9T21HmW/zcwMQMzMzD21HmW/zcwMQMzMzD24HmU/zcwMQMzMzD24HmU/zcwMQMzMzD24HmU/hesBQMzMzD24HmU/hesBQMzMzD24HmU/hesBQMzMzD24HmU/hesBQMzMzD21HmW/hesBQMzMzD21HmW/hesBQMzMzD21HmW/hesBQMzMzD21HmW/hesBQMzMzD24HmU/uB4FPszMzD24HmU/uB4FPszMzD24HmU/uB4FPszMzD24HmU/uB4FPszMzD21HmW/uB4FPszMzD21HmW/uB4FPszMzD21HmW/uB4FPszMzD21HmW/uB4FPszMzD21HmW/kML1PY/C9T21HmW/kML1PY/C9T21HmW/kML1PY/C9T21HmW/kML1PY/C9T24HmU/kML1PY/C9T24HmU/kML1PY/C9T24HmU/kML1PY/C9T24HmU/kML1PY/C9T21HmW/AAAAAI/C9T21HmW/AAAAAI/C9T21HmW/AAAAAI/C9T24HmU/AAAAAI/C9T24HmU/AAAAAI/C9T24HmU/AAAAAI/C9T0AAIC/ZmZmP8zMzD0AAIC/ZmZmP8zMzD0AAIC/ZmZmP8zMzD0AAIC/ZmZmP8zMzD0AAIA/ZmZmP8zMzD0AAIA/ZmZmP8zMzD0AAIA/ZmZmP8zMzD21HmW/ZmZmP1WjzD21HmW/ZmZmP1WjzD21HmW/ZmZmP1WjzD21HmW/ZmZmP1WjzD21HmW/ZmZmP1WjzD24HmU/ZmZmP1WjzD24HmU/ZmZmP1WjzD24HmU/ZmZmP1WjzD24HmU/ZmZmP1WjzD24HmU/ZmZmP1WjzD0AAAAAzcwMQMzMzD0AAAAAzcwMQMzMzD0AAAAAzcwMQMzMzD0AAAAAhesBQMzMzD0AAAAAhesBQMzMzD0AAAAAhesBQMzMzD0AAAAAhesBQMzMzD0AAAAAuB4FPszMzD0AAAAAuB4FPszMzD0AAAAAuB4FPszMzD0AAAAAuB4FPszMzD0AAAAAkML1PY/C9T0AAAAAkML1PY/C9T0AAAAAkML1PY/C9T0AAAAAkML1PY/C9T0AAAAAAAAAAI/C9T0AAAAAAAAAAI/C9T0AAAAAAAAAAI/C9T0AAIAzZmZmP1WjzD0AAIAzZmZmP1WjzD0AAIAzZmZmP1WjzD0AAIAzZmZmP1WjzD0vHGe/6iUCQPz8Zj3zKFy/Urj+P/z8Zj3zKFy/Urj+P/z8Zj3zKFy/Urj+P/z8Zj0vHGc/6iUCQP38Zj0vHGc/6iUCQP38Zj0vHGe/6iUCQGH5h70vHGe/6iUCQGH5h70wHGe/0nxlP/z8Zj3zKFy/16NwP/z8Zj3zKFy/16NwP/z8Zj3zKFy/16NwP/z8Zj0uHGc/0nxlP/38Zj0uHGc/0nxlP/38Zj0wHGe/0nxlP2H5h73zKFy/16NwP2H5h73zKFy/16NwP2H5h73zKFy/16NwP2H5h70uHGc/0nxlP2H5h70vHGc/6iUCQGH5h73yKFw/16NwP2H5h73yKFw/16NwP2H5h73yKFw/16NwP2H5h73zKFw/Urj+P2H5h73zKFw/Urj+P2H5h73zKFw/Urj+P2H5h73yKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzzKFy/16NwP8aQ+bzzKFy/16NwP8aQ+bzzKFy/16NwP8aQ+bzyKFw/16NwP0Klp7zyKFw/16NwP0Klp7zyKFw/16NwP0Klp7zzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP0Klp7yYxGA/16NwP0Klp7yYxGA/16NwP0Klp7yYxGA/Urj+P8aQ+byYxGA/Urj+P8aQ+byYxGA/Urj+P8aQ+byYxGA/Urj+P0Klp7yYxGA/Urj+P0Klp7yYxGA/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFw/Urj+P25zKzzzKFw/Urj+P25zKzzzKFw/Urj+P25zKzzyKFw/16NwP25zKzzyKFw/16NwP25zKzzyKFw/16NwP25zKzzzKFw/Urj+PzylpzzzKFw/Urj+PzylpzzzKFw/Urj+PzylpzzzKFy/Urj+P25zKzzzKFy/Urj+P25zKzzzKFy/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/16NwP25zKzyYxGA/16NwP25zKzyYxGA/16NwP25zKzyYxGA/Urj+PzylpzyYxGA/Urj+PzylpzyYxGA/Urj+PzylpzyYxGA/16NwPzylpzyYxGA/16NwPzylpzyYxGA/16NwPzylpzzyKFw/16NwPzylpzzyKFw/16NwPzylpzzyKFw/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzyKFw/16NwP/38Zj3yKFw/16NwP/38Zj3yKFw/16NwP/38Zj3yKFw/16NwP/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFy/16NwP25zKzzzKFy/16NwP25zKzzzKFy/16NwP25zKzzzKFy/Urj+PzylpzzzKFy/Urj+PzylpzzzKFy/Urj+PzylpzyZxGC/16NwPzylpzyZxGC/16NwPzylpzyZxGC/16NwPzylpzyZxGC/16NwP25zKzyZxGC/16NwP25zKzyZxGC/16NwP25zKzyYxGC/Urj+PzylpzyYxGC/Urj+PzylpzyYxGC/Urj+PzylpzyYxGC/Urj+P25zKzyYxGC/Urj+P25zKzyYxGC/Urj+P25zKzzzKFy/16NwP0Klp7zzKFy/16NwP0Klp7zzKFy/16NwP0Klp7yZxGC/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AAAAAAAAAID+/3+/AAAAAAAAAID+/3+/zcwMQAAAAID+/3+/zcwMQAAAAID+/3+/hesBQAAAAID+/3+/hesBQAAAAID+/3+/kML1PQDcpbj+/3+/kML1PQDcpbj+/3+/uB4FPgAAAID+/3+/uB4FPgAAAIAAAIA/zcwMQAAAAIAAAIA/zcwMQAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/uB4FPgAAAIAAAIA/kML1PQAAAIAAAIA/hesBQAAAAIC1HmW/zcwMQAAAAIC5HmU/zcwMQAAAAIC1HmW/AAAAAAAAAIC4HmU/AAAAAAAAAIAAAIA/ZmZmPwDcpbj+/3+/ZmZmPwAAAID+/3+/ZmZmPwAAAIAAAAAAzcwMQAAAAIAAAAAAAAAAAAAAAIC4HmU/hesBQAAAAIC4HmU/hesBQAAAAIC4HmU/ZmZmPwAAAIC4HmU/ZmZmPwAAAIC1HmW/ZmZmPwAAAIC1HmW/ZmZmPwAAAIC1HmW/hesBQAAAAIC1HmW/hesBQAAAAIAAAIAzhesBQAAAAIAAAIAzhesBQAAAAIAAAIAzZmZmPwAAAIAAAIAzZmZmPwAAAID+/3+/AAAAAI7C9b3+/3+/AAAAAI7C9b3+/3+/AAAAAI7C9b3+/3+/zswMQMzMzL3+/3+/zswMQMzMzL3+/3+/zswMQMzMzL3+/3+/hesBQMzMzL3+/3+/hesBQMzMzL3+/3+/hesBQMzMzL3+/3+/kML1PY7C9b3+/3+/kML1PY7C9b3+/3+/kML1PY7C9b3+/3+/kML1PY7C9b3+/3+/uB4FPszMzL3+/3+/uB4FPszMzL3+/3+/uB4FPszMzL0AAIA/kML1PY7C9b0AAIA/kML1PY7C9b0AAIA/kML1PY7C9b0AAIA/kML1PY7C9b0AAIA/uB4FPszMzL0AAIA/uB4FPszMzL0AAIA/uB4FPszMzL0AAIA/hesBQMzMzL0AAIA/hesBQMzMzL0AAIA/hesBQMzMzL0BAIA/zswMQMzMzL0BAIA/zswMQMzMzL0BAIA/zswMQMzMzL0AAIA/AAAAAI7C9b0AAIA/AAAAAI7C9b0AAIA/AAAAAI7C9b24HmU/kML1PY7C9b24HmU/kML1PY7C9b24HmU/kML1PY7C9b24HmU/kML1PY7C9b21HmW/kML1PY7C9b21HmW/kML1PY7C9b21HmW/kML1PY7C9b21HmW/kML1PY7C9b21HmW/uB4FPszMzL21HmW/uB4FPszMzL21HmW/uB4FPszMzL21HmW/uB4FPszMzL24HmU/uB4FPszMzL24HmU/uB4FPszMzL24HmU/uB4FPszMzL21HmW/hesBQMzMzL21HmW/hesBQMzMzL21HmW/hesBQMzMzL21HmW/hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL21HmW/AAAAAI7C9b21HmW/AAAAAI7C9b21HmW/AAAAAI7C9b24HmU/AAAAAI7C9b24HmU/AAAAAI7C9b24HmU/AAAAAI7C9b26HmU/zswMQMzMzL26HmU/zswMQMzMzL26HmU/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL3+/3+/ZmZmP8zMzL3+/3+/ZmZmP8zMzL3+/3+/ZmZmP8zMzL0AAIA/ZmZmP8zMzL0AAIA/ZmZmP8zMzL0AAIA/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL24HmU/ZmZmP8zMzL24HmU/ZmZmP8zMzL24HmU/ZmZmP8zMzL0AAACmzswMQMzMzL0AAACmzswMQMzMzL0AAACmzswMQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmAAAAAI7C9b0AAACmAAAAAI7C9b0AAACmAAAAAI7C9b0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL3+/3+/AAAAAAAAAC/+/3+/AAAAAAAAAC/+/3+/zswMQACAB7H+/3+/zswMQACAB7H+/3+/hesBQAAAAC/+/3+/hesBQAAAAC/+/3+/kML1PQAAAC/+/3+/kML1PQAAAC/+/3+/uB4FPgAAAC8AAIA/zswMQAAAAC8AAIA/zswMQAAAAC8AAIA/AAAAAAAAAC8AAIA/AAAAAAAAAC8AAIA/uB4FPgAAAC8AAIA/kML1PQAAAC8AAIA/kML1PQAAAC8AAIA/hesBQAAAAC8AAIA/hesBQAAAAC+1HmW/zswMQAAAAC+1HmW/zswMQAAAAC+5HmU/zswMQAAAAC+1HmW/AAAAAAAAAC+4HmU/AAAAAAAAAC8AAIA/ZmZmPwAAAC8AAIA/ZmZmPwAAAC/+/3+/ZmZmPwAAAC/+/3+/ZmZmPwAAAC8AAACmzswMQAAAAIAAAACmAAAAAAAAAIC4HmU/hesBQAAAAIC4HmU/hesBQAAAAIC4HmU/ZmZmPwAAAIC4HmU/ZmZmPwAAAIC1HmW/ZmZmPwAAAIC1HmW/ZmZmPwAAAIC1HmW/hesBQAAAAIC1HmW/hesBQAAAAIAAAACmhesBQAAAAIAAAACmhesBQAAAAIAAAACmZmZmPwAAAIAAAACmZmZmPwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAAAA//9/vwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAA//9/PwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAAAA//9/PwAAAIAAAAAA//9/vwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL///3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAID//3+/doxiMwAAAIAAAAAAAAAAAAAAgD+Sc8U5GM0SuP7/fz8AAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAMvlkPyX55D4AAIC/2Tums9DKT7UAAIC/AAAAAAAAAIAAAAAAMvlkPyX55D6Vc8U5eG9XOP//fz8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAICSc8W5GM0SuP7/fz8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAICec8W5gm9XOP7/fz8AAAAAMflkPyT55D4AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAMflkPyT55D4AAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAICSc8W5GM0SuP7/fz8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD+Sc8U5GM0SuP7/fz8AAIA/AAAAAAAAAICec8W5gm9XOP7/fz8AAAAAMmrXOAAAgD8AAAAAMflkPyT55D4AAAAAMvlkPyX55D4AAAAAPWPXOP//fz8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D6Vc8U5eG9XOP//fz8AAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D4AAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAMflkPyT55D4AAAAAMvlkPyX55D4AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAIC/2Tums9DKT7X//3+/doxiMwAAAICSc8U5GM0SuP7/fz+Vc8U5eG9XOP//fz+ec8W5gm9XOP7/fz+Sc8W5GM0SuP7/fz8AAIA/AAAAAAAAAIAAAAAAPWPXOP//fz8AAAAA//9/PwAAAICSc8U5GM0SuP7/fz+Vc8U5eG9XOP//fz8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAICec8W5gm9XOP7/fz+Sc8W5GM0SuP7/fz8AAAAAMmrXOAAAgD8AAAAA//9/PwAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAPWPXOP//fz8AAAAAMmrXOAAAgD8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D4AAAAAAAAAAP//fz8AAAAAAAAAAP//fz8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D4AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAP//fz8AAAAAPWPXOP//fz8AAAAAMmrXOAAAgD8AAAAA//9/PwAAAIAAAAAA//9/PwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIDGOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAxVnQsgAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIDFOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIC/Pc9zMwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/Ps9zMwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIC/Ps9zMwAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/Ps9zMwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIC/Ps9zMwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/Pc9zMwAAAIDFOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIDGOZGxAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/Pc9zswAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIA/Pc9zswAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAxVnQsgAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIDGOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAACAvwAAAIDGOZGxAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAA//9/vwAAAIAAAAAAxVnQsgAAgL8AAAAAAAAAAAAAgL8AAAAAxVnQsgAAgL8AAAAAAAAAAAAAgL/FOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAAAA//9/PwAAAIAAAAAAAACAPwAAAIDFOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAAAAAACAPwAAAIAAAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAPqngsQAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAAAACAvwAAAIAAAAAAPqngsQAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAAAA//9/PwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAfwcSMgAAgD8AAAAAPqngsQAAgD8AAAAAkFJ0sAAAgD8AAAAA//9/PwAAAIAAAIA/TbFlswAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAPqngsQAAgD8AAAAAkFJ0sAAAgD8AAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAAAA//9/PwAAAIAAAIA/TbFlswAAAIAAAAAAAACAvwAAAIAAAAAAkFJ0sAAAgD8AAAAAfwcSMgAAgD8AAIA/TbFlswAAAIAAAIC/AAAAAAAAAIAAAAAAkFJ0sAAAgD8AAAAAfwcSMgAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAJkMdswAAgL8AAAAAAAAAAAAAgL8AAIA/TbFlswAAAIAAAIC/AAAAAAAAAIAAAAAAJkMdswAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAJkMdswAAgL8AAAAAAAAAAAAAgL8AAAAAJkMdswAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAfwcSMgAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAdbqzswAAgD8AAAAAkFL0MQAAgD8AAIA/SrFlswAAAIAAAAAAAAAAAAAAgD8AAAAAkFL0MQAAgD8AAAAAAACAPwAAAIAAAIA/SrFlswAAAIAAAIC/AAAAAAAAAIAAAAAAdbqzswAAgD8AAAAAkFL0MQAAgD8AAAAAAACAPwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAIA/SrFlswAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/SrFlswAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAkFL0MQAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAA8ssGMQAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAA8ssGMQAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA6zFKswAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA2MsGsQAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA8ssGMQAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAA8ssGMQAAgD8AAAAA6zFKswAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA2MsGsQAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA2MsGsQAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAxDFKMwAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAxDFKMwAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAxDFKMwAAgL8AAAAAAAAAAAAAgL8AAAAAxDFKMwAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAA2MsGsQAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA6zFKswAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA6zFKswAAgL8AAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAmLqzMwAAgD8AAAAA//9/PwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAmLqzMwAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAARUMdsQAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAARUMdsQAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAmLqzMwAAgD8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAdbqzswAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAIA/AAAAAAAAAIAAAAAARUMdsQAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAJkMdMQAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAARUMdsQAAgL8AAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAmLqzMwAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAulJ0sAAAgD8AAAAA8suGMQAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAulJ0sAAAgD8AAAAA8suGMQAAgD8AAIA/bJyGtAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA18uGsQAAgD8AAAAAulJ0sAAAgD8AAAAAAAAAAAAAgD8AAAAA8suGMQAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAA8suGMQAAgD8AAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA18uGsQAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAA18uGsQAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAdLozsgAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAdLozsgAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAdLozsgAAgL8AAAAAAAAAAAAAgL8AAAAAdLozsgAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/bJyGtAAAAIAAAAAA18uGsQAAgD8AAAAAulJ0sAAAgD8AAAAAAACAPwAAAIAAAIA/bJyGtAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAIA/bJyGtAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAmLozMgAAgL8AAIC/AAAAAAAAAIAAAAAAmLqzswAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAAAACAvwAAAIAAAAAAmLqzswAAgD8AAAAAAAAAAAAAgD8AAIA/bZyGtAAAAIAAAIC/AAAAAAAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAIA/bZyGtAAAAIAAAAAAAAAAAAAAgL8AAAAAwjFKMQAAgL8AAAAAAACAPwAAAIAAAIA/bZyGtAAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAwjFKMQAAgL8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/bZyGtAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAmLqzswAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAwjFKMQAAgL8AAIA/AAAAAAAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAwjFKMQAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAA6zFKsQAAgL8AAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAmLqzswAAgD8AAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAPwAAAIAAAIC/AAAAAAAAAID//3+/doxiMwAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAIC/2Tums9DKT7UAAIC/AAAAAAAAAIAAAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAAAAAACAPwAAAIAAAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAIA/AAAAAAAAAIAAAIC/2Tums9DKT7X//3+/doxiMwAAAIAAAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/PwAAAIAAAAAA//9/PwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAAAA//9/PwAAAIAAAAAA//9/PwAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL9N5eo0AACAPwAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAMPlkPyn55L4AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAMPlkPyn55L4AAAAAAAAAAAAAgL8AAAAAMPlkPyn55L7//38/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAMPlkPyn55L4AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL///38/AAAAAAAAAIAAAIA/Ijy8tAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/Ijy8tAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL///38/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAL/lkPyj55L4AAAAAMPlkPyn55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAMPlkPyn55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAMPlkPyn55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAL/lkPyj55L4AAAAAMPlkPyn55L4AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f7///38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAymH8Mv//f78AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAAAAAAAAgL8AAAAAymH8Mv//f78AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIBN5eo0AACAPwAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL///38/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAID//38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAP//f78AAAAAymH8Mv//f78AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAP//f78AAAAAymH8Mv//f78AAAAAAAAAAP//f78AAAAAAAAAAP//f78AAAAAL/lkPyj55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAL/lkPyj55L4AAAAAMvlkPyL55L4AAAAAAACAvwAAAIAAAAAAAAAAAP//f78AAAAAAAAAAP//f78AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAAAAAACAPwAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIC/AAAAAAAAAIBN5eo0AACAPwAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAPwAAAIAAAIA/Ijy8tAAAAIAAAAAA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cDDjPiB9uD0AA/w76vxUP3lQ8z7q/FQ/AAP8O+r8VD8L2PU+6vxUPyQADAAJACQACQAYAAAABgAVAAAAFQADABsAHQAHABsABwACAA4AFAARAA4AEQALACMAFwAIACMACAAfACcAIQASACcAEgANAAUAFgAgAAUAIAAmABMAIgAeABMAHgAQAAoADwAcAAoAHAAaAAQAJQAZAAQAGQABACEDKQNMACEDTAA7AG8AeABVAG8AVQBAAEMAYgBqAEMAagBHADoASwBQADoAUAA+AEEAVwBjAEEAYwBEACwDJwM/ACwDPwBxAF4ANAAqAF4AKgBnAHUAbgA5AHUAOQBcAFsAOAA1AFsANQBgAEkALAAwAEkAMABSAIYAigBkAIYAZABYAC8DfgBMAC8DTAApA4cAjABpAIcAaQBhAI8AhABWAI8AVgB6ADwDNQNzADwDcwCRAHwAgQBQAHwAUABLACgDGgMtACgDLQBKADEDMwN3ADEDdwBNAFMAMQBtAFMAbQB0ACUDLANxACUDcQBCAD0ATgB5AD0AeQBwADYDOANUADYDVAB2ADkDMgNPADkDTwB/ADcDOgOAADcDgABRAEkAUgCCAEkAggB9ACgDSgB+ACgDfgAvAzQDOwOQADQDkAB7AHIAWQCDAHIAgwCOAF0AZgCNAF0AjQCIAFoAXwCJAFoAiQCFAJQAkgCaAJQAmgCbAB8BkgCUAB8BlAAkAS4BOAGbAC4BmwCaAC8BoAChAC8BoQAwAZkAHAGhAJkAoQCgAKsApQCkAKsApACnADMBrgCoADMBqAAxAawAtgCpAKwAqQCmADQBPAG1ADQBtQCvALUAvwC8ALUAvACvAK0AuwDCAK0AwgC5ALoAvQDDALoAwwDAALgAwQDEALgAxADIAMkAxQC+AMkAvgC0ACYBIAHKACYBygAWAdEAzgDGANEAxgCzACEBIAHHACEBxwDPANIA3gDcANIA3ADQAM8A2wDhAM8A4QDVACEBJQH4ACEB+ADXAN0A4wDgAN0A4ADaANYA4gDkANYA5ADnAOgA5QDfAOgA3wDTADsBNgHoADsB6ADTADoB8ADoADoB6AA2Ae0A8QDUAO0A1ADmAJcA9ADvAJcA7wCfACMBJQHVACMB1QDzAPYA/wD8APYA/ADrAOkA+wACAekAAgH5APoAlQCdAPoAnQDsAP0AAAEGAf0ABgEDAfgAAQEEAfgABAHXANgABQH+ANgA/gD1ADsB0wC1ADsBtQA8Ac0A2QD3AM0A9wAJAbEADgELAbEACwEIAQcBCgERAQcBEQHMAAwBDwEVAQwBFQESAcsAEAETAcsAEwEXARgBFAENARgBDQGwACcBKAGqACcBqgC4AB0BGQGyAB0BsgCjACsBKQEbASsBGwGYAKUAqwAqAaUAKgEsARcBGgEoARcBKAEnAZMA+AAlAZMAJQEjAc8A1QAlAc8AJQEhAdcAygAgAdcAIAEhAbcAxwAgAbcAIAEmAZYAHgEiAZYAIgHyAPYAOwE8AfYAPAEIAZwAOQE1AZwANQHqAPYA6wA2AfYANgE7AbEACAE8AbEAPAE0AbEANAEyAbEAMgGiAKQALwEwAaQAMAGnAJ4A7gA3AZ4ANwEtAT4BQgFTAT4BUwFNAUMBPwFLAUMBSwFxAU8BVwFIAU8BSAFAAUkBPQFFAUkBRQFpAU4BVAFeAU4BXgFaAVgBUAFcAVgBXAFkAXIBTAFbAXIBWwFfAWEBWQFKAWEBSgFqAXsBVQFjAXsBYwFnAWsBbQFmAWsBZgFiAUYBdgFuAUYBbgFsAW8BcwFgAW8BYAFoAXgBRAF0AXgBdAFwAXcBRwFWAXcBVgF8AXoBUgFBAXoBQQF1AV0BUQF5AV0BeQFlAYEBfgGKAYEBigGYAYcBfQGDAYcBgwGTAacBmwGIAacBiAGUAZkBpgGyAZkBsgGRAYUBjQGPAYUBjwGVAY4BggGaAY4BmgGSAZ8BfgGBAZ8BgQG5AYkBnAGkAYkBpAGXAa4BogGeAa4BngGqAaABugGlAaABpQGdAbYBrAGoAbYBqAGwAa0BhgF/Aa0BfwGhAaMBtwGzAaMBswGvAZYBkAGxAZYBsQGpAasBtQGMAasBjAGEAbgBgAGLAbgBiwG0AcIBvgG/AcIBvwHDAcUBuwG9AcUBvQHBAccByQG8AccBvAHGAcQBwAHKAcQBygHIAc0B0QHiAc0B4gHcAdABzAHaAdAB2gEAAt0B5QHWAd0B1gHOAdcBywHTAdcB0wH3AdsB4QHtAdsB7QHpAeYB3gHqAeYB6gHyAf8B2QHoAf8B6AHsAe8B5wHYAe8B2AH4AQoC5AHwAQoC8AH0AfoB/AH1AfoB9QHxAdUBBQL7AdUB+wH5Af0BAQLuAf0B7gH2AQYC0gECAgYCAgL+AQQC1AHjAQQC4wEJAggC4AHPAQgCzwEDAusB3wEHAusBBwLzARACDAIaAhACGgIoAhcCCwITAhcCEwIjAjcCKwIYAjcCGAIkAikCNgJCAikCQgIhAhUCHQIfAhUCHwIlAh4CEgIqAh4CKgIiAjACDQIRAjACEQJKAhkCLAI0AhkCNAInAj4CMgIuAj4CLgI6Ai8CSQI1Ai8CNQItAkYCPAI4AkYCOAJAAj0CFgIOAj0CDgIxAjMCRwJDAjMCQwI/AiYCIAJBAiYCQQI5AjsCRQIcAjsCHAIUAkgCDwIbAkgCGwJEAlICTgJPAlICTwJTAlUCSwJNAlUCTQJRAlcCWQJMAlcCTAJWAlQCUAJaAlQCWgJYAl0CYQJyAl0CcgJsAmACXAJqAmACagKQAm0CdQJmAm0CZgJeAmcCWwJjAmcCYwKHAmsCcQJ9AmsCfQJ5AnYCbgJ6AnYCegKCAo8CaQJ4Ao8CeAJ8An8CdwJoAn8CaAKIApkCcwKBApkCgQKFAokCiwKEAokChAKAAmQClAKMAmQCjAKKAo0CkQJ+Ao0CfgKGApYCYgKSApYCkgKOApUCZQJ0ApUCdAKaApgCcAJfApgCXwKTAnsCbwKXAnsClwKDAqECnQKpAqECqQK2AqcCmwKjAqcCowKyAsMCuQKoAsMCqAKzArcCwgLOArcCzgKwAqUCrAKuAqUCrgK0Aq0CogK4Aq0CuAKxArwCnAKgArwCoALVAqkCugLBAqkCwQK2AsoCvwK7AsoCuwLGAr0C1gLBAr0CwQK6AtICyALEAtICxALMAskCpgKeAskCngK+AsAC0wLPAsACzwLLArUCrwLNArUCzQLFAscC0QKrAscCqwKkAtQCnwKqAtQCqgLQAt4C2gLbAt4C2wLfAuEC1wLZAuEC2QLdAuMC5QLYAuMC2ALiAuAC3ALmAuAC5gLkAu4C6gLrAu4C6wLvAvEC5wLpAvEC6QLtAvMC9QLoAvMC6ALyAvAC7AL2AvAC9gL0Av4C+gL7Av4C+wL/AgED9wL5AgED+QL9AgMDBQP4AgMD+AICAwAD/AIGAwADBgMEAw4DCgMLAw4DCwMPAxEDBwMJAxEDCQMNAxMDFQMIAxMDCAMSAxADDAMWAxADFgMUA2UAKgMwA2UAMAOLADYAawAtAzYALQMfA4sAMAMrA4sAKwNoAGUAKQAYA2UA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YfmHveZRuD5SuP4/YfmHveZRuD5SuP4/YfmHveZRuD5SuP4/YfmHveVRuD7Xo3A/xpD5vOVRuD7Xo3A/xpD5vOVRuD7Xo3A/xpD5vOVRuD7Xo3A/xpD5vOdRuL7Xo3A/xpD5vOdRuL7Xo3A/xpD5vOdRuL7Xo3A/xpD5vOdRuL7Xo3A/xpD5vOVRuD7Xo3A/QqWnvOVRuD7Xo3A/QqWnvOVRuD7Xo3A/QqWnvOVRuD7Xo3A/QqWnvOZRuD5SuP4/xpD5vOZRuD5SuP4/xpD5vOZRuD5SuP4/xpD5vOZRuD5SuP4/xpD5vDCJwT7Xo3A/xpD5vDCJwT7Xo3A/xpD5vDCJwT7Xo3A/xpD5vDCJwT7Xo3A/QqWnvDCJwT7Xo3A/QqWnvDCJwT7Xo3A/QqWnvDGJwT5SuP4/xpD5vDGJwT5SuP4/xpD5vDGJwT5SuP4/xpD5vDGJwT5SuP4/QqWnvDGJwT5SuP4/QqWnvDGJwT5SuP4/QqWnvOZRuD5SuP4/QqWnvOZRuD5SuP4/QqWnvOZRuD5SuP4/QqWnvOZRuD5SuP4/QqWnvOZRuL5SuP4/QqWnvOZRuL5SuP4/QqWnvOZRuL5SuP4/QqWnvOZRuL5SuP4/QqWnvOZRuD5SuP4/bnMrPOZRuD5SuP4/bnMrPOZRuD5SuP4/bnMrPOZRuD5SuP4/bnMrPOVRuD7Xo3A/bnMrPOVRuD7Xo3A/bnMrPOVRuD7Xo3A/bnMrPOVRuD7Xo3A/bnMrPOZRuD5SuP4/PKWnPOZRuD5SuP4/PKWnPOZRuD5SuP4/PKWnPOZRuD5SuP4/PKWnPOZRuL5SuP4/bnMrPOZRuL5SuP4/bnMrPOZRuL5SuP4/bnMrPOZRuL5SuP4/bnMrPDGJwT5SuP4/bnMrPDGJwT5SuP4/bnMrPDGJwT5SuP4/bnMrPDCJwT7Xo3A/bnMrPDCJwT7Xo3A/bnMrPDCJwT7Xo3A/bnMrPDGJwT5SuP4/PKWnPDGJwT5SuP4/PKWnPDGJwT5SuP4/PKWnPDCJwT7Xo3A/PKWnPDCJwT7Xo3A/PKWnPDCJwT7Xo3A/PKWnPOVRuD7Xo3A/PKWnPOVRuD7Xo3A/PKWnPOVRuD7Xo3A/PKWnPOVRuD7Xo3A/PKWnPOdRuL7Xo3A/PKWnPOdRuL7Xo3A/PKWnPOdRuL7Xo3A/PKWnPOdRuL7Xo3A/PKWnPOVRuD7Xo3A//fxmPeVRuD7Xo3A//fxmPeVRuD7Xo3A//fxmPeVRuD7Xo3A//fxmPeZRuD5SuP4//fxmPeZRuD5SuP4//fxmPeZRuD5SuP4//fxmPeZRuD5SuP4//fxmPedRuL7Xo3A/bnMrPOdRuL7Xo3A/bnMrPOdRuL7Xo3A/bnMrPOdRuL7Xo3A/bnMrPOZRuL5SuP4/PKWnPOZRuL5SuP4/PKWnPOZRuL5SuP4/PKWnPOZRuL5SuP4/PKWnPDKJwb7Xo3A/PKWnPDKJwb7Xo3A/PKWnPDKJwb7Xo3A/PKWnPDKJwb7Xo3A/bnMrPDKJwb7Xo3A/bnMrPDKJwb7Xo3A/bnMrPDGJwb5SuP4/PKWnPDGJwb5SuP4/PKWnPDGJwb5SuP4/PKWnPDGJwb5SuP4/bnMrPDGJwb5SuP4/bnMrPDGJwb5SuP4/bnMrPOdRuL7Xo3A/QqWnvOdRuL7Xo3A/QqWnvOdRuL7Xo3A/QqWnvOdRuL7Xo3A/QqWnvDKJwb7Xo3A/QqWnvDKJwb7Xo3A/QqWnvDKJwb7Xo3A/QqWnvDKJwb7Xo3A/xpD5vDKJwb7Xo3A/xpD5vDKJwb7Xo3A/xpD5vDGJwb5SuP4/QqWnvDGJwb5SuP4/QqWnvDGJwb5SuP4/QqWnvDGJwb5SuP4/xpD5vDGJwb5SuP4/xpD5vDGJwb5SuP4/xpD5vOZRuL5SuP4/xpD5vOZRuL5SuP4/xpD5vOZRuL5SuP4/xpD5vOZRuL5SuP4/xpD5vOZRuL5SuP4/YfmHveZRuL5SuP4/YfmHveZRuL5SuP4/YfmHveZRuL5SuP4/YfmHvd05uL6Zff4/cL8Kvd05uL6Zff4/cL8Kvd05uL6Zff4/cL8Kvd05uL6Zff4/cL8KvRXvo74mPPo/AGUkvBXvo74mPPo/AGUkvBXvo74mPPo/AGUkvBXvo74mPPo/AGUkvN05uL6gwvs/OEyCvd05uL6gwvs/OEyCvd05uL6gwvs/OEyCvd05uL6gwvs/OEyCvd05uL7chcQ/p+uUPt05uL7chcQ/p+uUPt05uL7chcQ/p+uUPt05uL7chcQ/p+uUPtc5uD6Zff4/dL8Kvdc5uD6Zff4/dL8Kvdc5uD6Zff4/dL8Kvdc5uD6Zff4/dL8KvQ/voz4mPPo/EGUkvA/voz4mPPo/EGUkvA/voz4mPPo/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//9/vwAAAADXv/CzAACAvwAAAAAAAAAA//9/vwAAAAAAAIA/AAAAgAAAgD89z/OyAAAAgAAAAADXv/CzAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAADxj7QzAACAvwAAgL89z/MyAAAAgAAAAADXv/CzAACAvwAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAADxj7QzAACAvwAAgL89z/MyAAAAgAAAAAAAAAAAAACAPwAAAAD//38/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAAAAAACAPwAAAAD//38/AAAAgAAAAAAAAIA/AAAAgAAAgD89z/OyAAAAgAAAgL8+z/MyAAAAgAAAAAAAAAAAAACAvwAAAAD//38/AAAAgAAAAAAAAIA/AAAAgAAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAPwAAgL89z/MyAAAAgAAAAAAAAAAAAACAPwAAAAAAAIA/AAAAgAAAgL89z/MyAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAgL8+z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAvwAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAvwAAgD8+z/OyAAAAgAAAgL8+z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAPwAAgL8+z/MyAAAAgAAAAAAAAAAAAACAPwAAAAD//38/AAAAgAAAAAAAAIA/AAAAgAAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAvwAAAAAAAIC/AAAAgAAAAAD//3+/AAAAgAAAAAAAAAAAAACAPwAAgD8+z/OyAAAAgAAAgL89z/MyAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAPwAAgL89z/MyAAAAgAAAAAAAAAAAAACAPwAAAAAAAIA/AAAAgAAAgL89z/My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AAAAgAAAgD8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgD8iPDy0AAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAgD8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAu+WQ/MvnkvgAAAAAu+WQ/LvnkvgAAAAAAAAAAAACAvwAAAAAt+WQ/NPnkvgAAAAAu+WQ/MvnkvgAAAAAAAAAAAACAvwAAAAAt+WQ/NPnkvgAAAAAu+WQ/MvnkvgAAAAAAAAAAAACAvwAAAAAu+WQ/MvnkvgAAAAAu+WQ/LvnkvgAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAAAnPLwyAACAvwAAgD8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAAAnPLwyAACAvwAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAAAAAIC/AAAAgAAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAnPLwyAACAvwAAAAAAAIA/AAAAgAAAAAAAAAAAAACAvwAAAAAnPLwyAACAvwAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAgD8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgD8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAAAAAAAAAAACAvwAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAgD8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAgD8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAAAAAAIC/AAAAgAAAgL8AAAAAAAAAgAAAAAAAAIA/AAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAgL8AAAAAAAAAgAAAAAAAAIA/AAAAgAAAgD8iPDy0AAAAgAAAAAAAAIC/AAAAgAAAgD8AAAAAAAAAgAAAgD8AAAAAAAAAgAAAgD8AAAAAAAAAgAAAgD8iPDy0AAAAgAAAgD8AAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/AAAAgAAAgD8AAAAA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kACfAJ0AkACdAI4AjACcAKIAjACiAJQAjQCVALwAjQC8AJgAngCkAKEAngChAJsAlgCjAKUAlgClAKgAqgCmAKAAqgCgAJIAuACsAKkAuACpAJEAUQCyAKoAUQCqAK0ArwCzAJMArwCTAKcASgC2ALEASgCxAFQARgC7AJQARgCUALUAuQDDAMAAuQDAAK0AqwC/AMYAqwDGAL0AvgBIAFIAvgBSAK4AwQDEAMoAwQDKAMcAuwDFAMgAuwDIAJcAmQDJAMIAmQDCALcAuQCSAHIAuQByAM0AigCaALoAigC6AM4AbQDTANAAbQDQAM0AywDPANYAywDWAIkA0QDUANoA0QDaANcAhwDVANgAhwDYANsA3QDZANIA3QDSAGsA2wDfAGQA2wBkAHQA4gDeAG4A4gBuAFoAXgBmAOEAXgDhAEwA5QDpAPoA5QD6APQA5wDkAPAA5wDwABcB9QD9AO0A9QDtAOYA7wDjAOsA7wDrAA8B8wD4AAQB8wAEAf8A/gD2AAIB/gACAQoBGQHyAAEBGQEBAQYBCAEAAfEACAHxABABIAH7AAcBIAEHAQwBEQETAQ0BEQENAQkB7gAdARUB7gAVARIBFAEYAQUBFAEFAQ4BHgHqABoBHgEaARYBHAHsAPwAHAH8ACIBIQH5AOgAIQHoABsBAwH3AB8BAwEfAQsBJwEkATABJwEwAT8BLwEjASsBLwErATsBTwFDATEBTwExAT0BQQFOAVkBQQFZATkBLAEzATcBLAE3ATwBNgEqAUIBNgFCAToBRwElASgBRwEoAWABMgFFAU0BMgFNAUABVgFKAUYBVgFGAVIBSAFhAUwBSAFMAUQBXgFVAVEBXgFRAVoBVAEuASYBVAEmAUkBSwFfAVsBSwFbAVcBPgE4AVgBPgFYAVABUwFcATQBUwE0AS0BYgEpATUBYgE1AV0BagFmAWcBagFnAWsBbQFjAWUBbQFlAWkBbwFxAWQBbwFkAW4BbAFoAXIBbAFyAXABdwF/AXwBdwF8AXQBcwF5AYgBcwGIAXYBfQGCAXoBfQF6AXUBgQGHAYQBgQGEAX4BhgGKAXsBhgF7AYMBeAGJAYUBeAGFAYABogGRAZwBogGcAaQBtgGyAZYBtgGWAagBqQGYAY4BqQGOAZ8BqwGwAZQBqwGUAYsBswGtAY0BswGNAZcBlQGqAaABlQGgAYwB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XTjOPtJ8ZT/9/GY9XzjOvtJ8ZT9h+Ye9XzjOvtJ8ZT9h+Ye951G4vtejcD9h+Ye951G4vtejcD9h+Ye951G4vtejcD9h+Ye951G4vtejcD9h+Ye9XTjOPtJ8ZT9h+Ye9XTjOPtJ8ZT9h+Ye9XjjOPuolAkBh+Ye9XjjOPuolAkBh+Ye95VG4PtejcD9h+Ye95VG4PtejcD9h+Ye95VG4PtejcD9h+Ye95VG4PtejcD9h+Ye95lG4PlK4/j9h+Ye95lG4PlK4/j9h+Ye95lG4PlK4/j9h+Ye95lG4PlK4/j9h+Ye95VG4PtejcD/GkPm85VG4PtejcD/GkPm85VG4PtejcD/GkPm85VG4PtejcD/GkPm851G4vtejcD/GkPm851G4vtejcD/GkPm851G4vtejcD/GkPm851G4vtejcD/GkPm85VG4PtejcD9Cpae85VG4PtejcD9Cpae85VG4PtejcD9Cpae85VG4PtejcD9Cpae85lG4PlK4/j/GkPm85lG4PlK4/j/GkPm85lG4PlK4/j/GkPm85lG4PlK4/j/GkPm8MInBPtejcD/GkPm8MInBPtejcD/GkPm8MInBPtejcD/GkPm8MInBPtejcD9Cpae8MInBPtejcD9Cpae8MInBPtejcD9Cpae8MYnBPlK4/j/GkPm8MYnBPlK4/j/GkPm8MYnBPlK4/j/GkPm8MYnBPlK4/j9Cpae8MYnBPlK4/j9Cpae8MYnBPlK4/j9Cpae85lG4PlK4/j9Cpae85lG4PlK4/j9Cpae85lG4PlK4/j9Cpae85lG4PlK4/j9Cpae85lG4vlK4/j9Cpae85lG4vlK4/j9Cpae85lG4vlK4/j9Cpae85lG4vlK4/j9Cpae85lG4PlK4/j9ucys85lG4PlK4/j9ucys85lG4PlK4/j9ucys85lG4PlK4/j9ucys85VG4PtejcD9ucys85VG4PtejcD9ucys85VG4PtejcD9ucys85VG4PtejcD9ucys85lG4PlK4/j88pac85lG4PlK4/j88pac85lG4PlK4/j88pac85lG4PlK4/j88pac85lG4vlK4/j9ucys85lG4vlK4/j9ucys85lG4vlK4/j9ucys85lG4vlK4/j9ucys8MYnBPlK4/j9ucys8MYnBPlK4/j9ucys8MYnBPlK4/j9ucys8MInBPtejcD9ucys8MInBPtejcD9ucys8MInBPtejcD9ucys8MYnBPlK4/j88pac8MYnBPlK4/j88pac8MYnBPlK4/j88pac8MInBPtejcD88pac8MInBPtejcD88pac8MInBPtejcD88pac85VG4PtejcD88pac85VG4PtejcD88pac85VG4PtejcD88pac85VG4PtejcD88pac851G4vtejcD88pac851G4vtejcD88pac851G4vtejcD88pac851G4vtejcD88pac85VG4PtejcD/9/GY95VG4PtejcD/9/GY95VG4PtejcD/9/GY95VG4PtejcD/9/GY95lG4PlK4/j/9/GY95lG4PlK4/j/9/GY95lG4PlK4/j/9/GY95lG4PlK4/j/9/GY951G4vtejcD9ucys851G4vtejcD9ucys851G4vtejcD9ucys851G4vtejcD9ucys85lG4vlK4/j88pac85lG4vlK4/j88pac85lG4vlK4/j88pac85lG4vlK4/j88pac8MonBvtejcD88pac8MonBvtejcD88pac8MonBvtejcD88pac8MonBvtejcD9ucys8MonBvtejcD9ucys8MonBvtejcD9ucys8MYnBvlK4/j88pac8MYnBvlK4/j88pac8MYnBvlK4/j88pac8MYnBvlK4/j9ucys8MYnBvlK4/j9ucys8MYnBvlK4/j9ucys851G4vtejcD9Cpae851G4vtejcD9Cpae851G4vtejcD9Cpae851G4vtejcD9Cpae8MonBvtejcD9Cpae8MonBvtejcD9Cpae8MonBvtejcD9Cpae8MonBvtejcD/GkPm8MonBvtejcD/GkPm8MonBvtejcD/GkPm8MYnBvlK4/j9Cpae8MYnBvlK4/j9Cpae8MYnBvlK4/j9Cpae8MYnBvlK4/j/GkPm8MYnBvlK4/j/GkPm8MYnBvlK4/j/GkPm85lG4vlK4/j/GkPm85lG4vlK4/j/GkPm85lG4vlK4/j/GkPm85lG4vlK4/j/GkPm85lG4vlK4/j9h+Ye95lG4vlK4/j9h+Ye95lG4vlK4/j9h+Ye95lG4vlK4/j9h+Ye93Tm4vtQWAEBOddu73Tm4vtQWAEBOddu73Tm4vtQWAEBOddu73Tm4vtQWAEBOddu7Fe+jvhPx+j9Oddu7Fe+jvhPx+j9Oddu7Fe+jvhPx+j9Oddu7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AAAAPwAAAACOwvW9AAAAPwAAAACOwvW9AAAAPwAAAACOwvW9zczMPpDC9T2OwvW9zczMPpDC9T2OwvW9zczMPpDC9T2OwvW9y8zMvpDC9T2OwvW9y8zMvpDC9T2OwvW9y8zMvpDC9T2OwvW9y8zMvrgeBT7OzMy9y8zMvrgeBT7OzMy9y8zMvrgeBT7OzMy9zczMPrgeBT7OzMy9zczMPrgeBT7OzMy9zczMPrgeBT7OzMy9y8zMvoXrAUDOzMy9y8zMvoXrAUDOzMy9y8zMvoXrAUDOzMy9y8zMvoXrAUDOzMy9zczMPoXrAUDOzMy9zczMPoXrAUDOzMy9zczMPoXrAUDOzMy9zczMPoXrAUDOzMy9y8zMvgAAAACOwvW9y8zMvgAAAACOwvW9zczMPgAAAACOwvW9zczMPgAAAACOwvW9zszMPs7MDEDOzMy9zszMPs7MDEDOzMy9zszMPs7MDEDOzMy9yszMvs7MDEDOzMy9yszMvs7MDEDOzMy9yszMvs7MDEDOzMy9yszMvs7MDEDOzMy9/v//vmZmZj/OzMy9/v//vmZmZj/OzMy9AAAAP2ZmZj/OzMy9AAAAP2ZmZj/OzMy9y8zMvmZmZj/OzMy9y8zMvmZmZj/OzMy9y8zMvmZmZj/OzMy9zczMPmZmZj/OzMy9zczMPmZmZj/OzMy9zczMPmZmZj/OzMy9/v//vgAAAAAAAICt/v//vgAAAAAAAICt/v//vs7MDEAAAICt/v//vs7MDEAAAICt/v//voXrAUAAAICt/v//vpDC9T0AAICt/v//vpDC9T0AAICt/v//vrgeBT4AAACAAAAAP87MDEAAAICtAAAAP87MDEAAAICtAAAAPwAAAAAAAICtAAAAPwAAAAAAAICtAAAAP7geBT4AAICtAAAAP5DC9T0AAICtAAAAP5DC9T0AAICtAAAAP4XrAUAAAICtAAAAP4XrAUAAAICtyszMvs7MDEAAAICtyszMvs7MDEAAAICtzszMPs7MDEAAAICty8zMvgAAAAAAAICtzczMPgAAAAAAAICtAAAAP2ZmZj8AAICt/v//vmZmZj8AAICtzMzMPoXrAUAAAACAzMzMPoXrAUAAAACAzMzMPmZmZj8AAACAzMzMPmZmZj8AAACAzMzMvmZmZj8AAACAzMzMvmZmZj8AAACAzMzMvoXrAUAAAACAzMzMvoXrAUAAAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AACAv6Mu2rUAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADX5ZD8S+eQ+AACAv6Mu2rUAAACAAACAvwAAAADRys+0AAAAAAAAAAAAAIA/AAAAADX5ZD8S+eQ+AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAPwAAAAAsS+s0AAAAAAAAAAAAAIA/AACAPwAAAAAsS+s0AACAPwAAAADMlw01AAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AACAPwAAAADRys80AACAP6Mu2jUAAACAAAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AACAPwAAAAAAAACAAACAP6Mu2jUAAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AAAAADX5ZD8S+eQ+AAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AAAAADX5ZD8S+eQ+AAAAAAAAAAAAAIA/AAAAADP5ZD8X+eQ+AAAAAP//f78AAACAAAAAAAAAAAAAAIA/AAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAvwAAAADRys+0AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAADRys80AACAPwAAAADMlw01AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAs+nsQAAAAAAAIA/AAAAAAAAAAD//38/As+nsQAAAAAAAIA/AAAAAAAAgL8AAACAAAAAAAAAAAD//38/AACAPz3P87IAAACAAs+nsQAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAD//3+/AAAAAPGPtDMAAIC/A8+nsde/cLIAAIA/AAAAAAAAAAD//38/A8+nsde/cLIAAIA/AAAAAAAAAAD//38/AAAAAAAAgD8AAACAAACAPz3P87IAAACAA8+nsde/cLIAAIA/AAAAAAAAAAAAAIA/AAAAANe/8LMAAIC/AAAAAAAAAAD//3+/AAAAANe/8LMAAIC/AAAAAAAAAAD//3+/AAAAAAAAgD8AAACAAACAPz3P87IAAACAAAAAANe/8LMAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAPGPtDMAAIC/AACAvz3P8zIAAACAAAAAANe/8LMAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAPGPtDMAAIC/AACAvz3P8zIAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAPz3P87IAAACAAACAvz7P8zIAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAvz3P8zIAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvz7P8zIAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AACAPz7P87IAAACAAACAvz7P8zIAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAvz7P8zIAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAPz7P87IAAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvz3P8zIAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvz3P8zIAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAPz3P87IAAACAAACAvz3P8zIAAACAA8+nsde/cLIAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvz3P8zIAAACAAs+nsQAAAAAAAIA/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAPz7P87IAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AACAPz3P87IAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPz3P87IAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPz3P87IAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAPz7P87IAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPz3P87IAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAPz3P87IAAACAAAAAAAAAgL8AAACAAAAAAAAAAAD//3+/AAAAAPGPtDMAAIC/AACAPz3P87IAAACAAACAvwAAAAAAAACAAAAAALSsV7L//38/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAAAAAP//f78AAACAAAAAALSsV7L//38/AAAAAAAAAAAAAIA/AACAP2ychrMAAACAAACAvwAAAAAAAACAAAAAAHjIj7L//3+/AAAAAF11w7EAAIC/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAF3Ij7EAAIA/AAAAAAAAAAAAAIA/AAAAALSsV7L//38/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAP02xZbMAAACAAACAv2ychjMAAACAAAAAAP//f78AAACAAAAAALSsV7L//38/AAAAAAAAAAAAAIA/AAAAAHjIj7L//3+/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAP02xZbMAAACAAAAAAAAAgL8AAACAAAAAAF3Ij7EAAIA/AAAAAAAAAAAAAIA/AACAP02xZbMAAACAAACAv2ychjMAAACAAAAAAF3Ij7EAAIA/AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAHS6szMAAIC/AACAP02xZbMAAACAAACAv2ychjMAAACAAAAAAAAAAAAAAIC/AAAAAHS6szMAAIC/AAAAAP//fz8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAF11w7EAAIC/AAAAAHS6szMAAIC/AAAAAF11w7EAAIC/AAAAAHS6szMAAIC/AAAAAP//fz8AAACAAACAP2ychrMAAACAAAAAAF3Ij7EAAIA/AAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAP2ychrMAAACAAAAAAP//f78AAACAAAAAAHjIj7L//3+/AAAAAF11w7EAAIC/AACAP2ychrMAAACAAACAv2ychjMAAACAAAAAAP//f78AAACAAAAAAHjIj7L//3+/AAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AACAP22chrMAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAJa6szH//3+/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAF3IjzMAAIA/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAJa6szH//3+/AACAP22chrMAAACAAAAAAAAAAAAAAIC/AAAAAIysVzEAAIC/AAAAAAAAgD8AAACAAACAP22chrMAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAIysVzEAAIC/AAAAAAAAAAAAAIA/AAAAAF3IjzMAAIA/AAAAAAAAgD8AAACAAACAP22chrMAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAF3IjzMAAIA/AACAP0qxZbMAAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAP0qxZbMAAACAAACAv22chjMAAACAAAAAAAAAAAAAAIA/AAAAAF3IjzMAAIA/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAIysVzEAAIC/AACAP0qxZbMAAACAAAAAAAAAAAAAAIC/AAAAAJa6szH//3+/AAAAAAAAgD8AAACAAACAP0qxZbMAAACAAACAv22chjMAAACAAAAAAAAAAAAAAIC/AAAAAIysVzEAAIC/AAAAAAAAgD8AAACAAACAv22chjMAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAJa6szH//3+/AACAv22chjMAAACAAAAAAAAAgL8AAACAAAAAAAAAAAD//38/AAAAAAAAAAAAAIA/AACAvwTNTjMAAACAAAAAAJ490rAAAIA/AACAvwTNTjMAAACAAAAAAF1bVK0AAIC/AAAAAF1bVK0AAIC/AACAPwTNTrMAAACAAACAvwTNTjMAAACAAAAAAF1bVK0AAIC/AAAAAF1bVK0AAIC/AACAPwTNTrMAAACAAACAvwTNTjMAAACAAAAAAJ490rAAAIA/AAAAAJ490rAAAIA/AACAPwTNTrMAAACAAAAAAJ490rAAAIA/AACAPwTNTrMAAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAP//fz8AAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAACAv6Mu2rUAAACAAACAvwAAAAAAAACAAACAv6Mu2rUAAACAAACAvwAAAADRys+0AAAAAP//fz8AAACAAACAPwAAAAAsS+s0AAAAAP//f78AAACAAACAPwAAAAAAAACAAACAPwAAAADRys80AACAP6Mu2jUAAACAAACAPwAAAAAAAACAAACAP6Mu2jUAAACAAACAPwAAAAAsS+s0AACAPwAAAADMlw01AAAAAAAAgD8AAACAAAAAAP//fz8AAACAAAAAAAAAgD8AAACAAAAAAP//f78AAACAAAAAAP//f78AAACAAACAPwAAAADRys80AACAPwAAAADMlw01AACAvwAAAADRys+0AACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACA//9/vwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAP//fz8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACA//9/vwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAC35ZD80+eS+AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAC35ZD80+eS+AAAAAAAAAAAAAIC/AAAAAC75ZD8u+eS+//9/PwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAC75ZD8u+eS+AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPyQ8PLQAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPyQ8PLQAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC///9/PwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAC75ZD8y+eS+AAAAAC75ZD8u+eS+AAAAAAAAAAAAAIC/AAAAAC35ZD80+eS+AAAAAC75ZD8y+eS+AAAAAAAAAAAAAIC/AAAAAC35ZD80+eS+AAAAAC75ZD8y+eS+AAAAAAAAAAAAAIC/AAAAAC75ZD8y+eS+AAAAAC75ZD8u+eS+AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAACc8vDIAAIC/AACAPwAAAADJl400AACAvwAAAADJl420AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAACc8vDIAAIC/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAACc8vDIAAIC/AAAAAAAAgD8AAACAAAAAAAAAAAAAAIC/AAAAACc8vDIAAIC/AAAAAP//fz8AAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAADJl400AACAvwAAAADJl420AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACA//9/vwAAAAAAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAP//fz8AAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACA//9/vwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAPyQ8PLQAAACAAAAAAAAAgL8AAACA//9/PwAAAAAAAACAAACAPwAAAAAAAACA//9/PwAAAAAAAACAAACAPwAAAAAAAACAAACAPyQ8PLQAAACAAACAPwAAAAAAAACAAAAAAP//fz8AAACA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OQAaADkAHwAQACMAKAAQACgAEwAXAC0ANQAXADUAGwCnAaABFQCnARUAPwAxAAoAAgAxAAIANwBAADwADgBAAA4ALgAwAA8ACwAwAAsAMwAhAAQABwAhAAcAKgAvADIANQAvADUALQChASIAJQChASUAowExADcAOQAxADkANABAAC4ALABAACwARACtAa4BQQCtAUEARQAhACoAKAAhACgAIwChAZEBBQChAQUAIgCqAawBQwCqAUMAJgAqAAcAPAAqADwAQACbAaYBPgCbAT4AGAATACgARAATAEQAPQCvAbEBKwCvASsAQgCwAasBJwCwAScAKQBKAEcAUQBKAFEAUwBMAEYASABMAEgAtwBWALMAUgBWAFIAUABeAFgAWgBeAFoAYwBOAOMAWwBOAFsAWQBoAGAAXwBoAF8AZABwAG0AZQBwAGUAXABqAHYAZgBqAGYAYgBvAM8AdABvAHQAbAB1AH8AfAB1AHwAbQBrAHsAggBrAIIAeQB6AH0AgwB6AIMAgAB3AIEAhAB3AIQAhwCJAIUAfgCJAH4AcwB4AIgAiwB4AIsA3wCSAI4AhgCSAIYAcgCaAIoAhwCaAIcAjwCTAKIAoACTAKAAkQCPAJ8ApQCPAKUAlwCQAJgAvwCQAL8AmwChAKcApAChAKQAngCZAKYAqACZAKgAqwCtAKkAowCtAKMAlQC7AK8ArAC7AKwAlABUALUArQBUAK0AsACyALYAlgCyAJYAqgBNALkAtABNALQAVwBJAL4AlwBJAJcAuAC8AMYAwwC8AMMAsACuAMIAyQCuAMkAwADBAEsAVQDBAFUAsQDEAMcAzQDEAM0AygC+AMgAywC+AMsAmgCcAMwAxQCcAMUAugC8AJUAdQC8AHUA0ACNAJ0AvQCNAL0A0QBwANYA0wBwANMA0ADOANIA2QDOANkAjADUANcA3QDUAN0A2gCKANgA2wCKANsA3gDgANwA1QDgANUAbgDeAOIAZwDeAGcAdwDlAOEAcQDlAHEAXQBhAGkA5ABhAOQATwDnAOsA/ADnAPwA9gDsAOgA9QDsAPUAGwH4AAAB8QD4APEA6QDyAOYA7gDyAO4AEgH3AP0ACAH3AAgBBAEBAfkABQEBAQUBDQEaAfQAAwEaAQMBBwEKAQIB8wAKAfMAEwElAf8ACwElAQsBDwEVARcBEAEVARABDAHwACABFgHwABYBFAEYARwBCQEYAQkBEQEhAe0AHQEhAR0BGQEfAe8A/gAfAf4AJAEjAfsA6gAjAeoAHgEGAfoAIgEGASIBDgEsASgBNAEsATQBQgEyASYBLgEyAS4BPgFSAUYBMwFSATMBPwFEAVEBXQFEAV0BPAEvATcBOgEvAToBQAE5AS0BRQE5AUUBPQFKAScBKwFKASsBZAE1AUgBUAE1AVABQwFZAU0BSQFZAUkBVQFLAWUBTwFLAU8BRwFgAVYBUwFgAVMBWwFYATEBKQFYASkBTAFOAWIBXgFOAV4BWgFBATsBXAFBAVwBVAFXAWEBOAFXATgBMAFjASoBNgFjATYBXwFtAWkBagFtAWoBbgFwAWYBaAFwAWgBbAFyAXQBZwFyAWcBcQFvAWsBdQFvAXUBcwF6AYIBfwF6AX8BdwF2AXwBiwF2AYsBeQGAAYUBfQGAAX0BeAGEAYoBhwGEAYcBgQGJAY0BfgGJAX4BhgF7AYwBiAF7AYgBgwENADoAqAENAKgBlgE2AKQBpQE2AKUBOAA2AAEAjwE2AI8BpAESABQAnwESAJ8BmAEcACAAmgEcAJoBnQEZAB0AngEZAJ4BnAEIAAwAlQEIAJUBkwEAAAkAlAEAAJQBjgE7AAYAkgE7AJIBqQEeADgApQEeAKUBmQEGAAMAkAEGAJABkgH+AfwBtQH+AbUBuAH0AfEBuQH0AbkB3gHIAeMB6QHIAekBywEEAg8C5wEEAucBzgHyAfgB4wHyAeMByAERAv4BuAERArgB8AH6AQACuwH6AbsBsgH/AQECvgH/Ab4BugHPAegB0QHPAdEBwQEGAggCxAEGAsQBxwEHAgUC0AEHAtABwwEDAgkCyQEDAskBzQHmAbQBvAHmAbwB1AHVAb0BwAHVAcAB2AEOAvsBswEOArMB5QHeAbkBtgHeAbYB7AHCAdMB3AHCAdwBxgHWAdkB2wHWAdsB0gEOAuUB5wEOAucBDwLfAe0B6gHfAeoB5AHmAdQB0QHmAdEB6AHXAfQB+AHXAfgB2gEBAhEC8AEBAvABvgHFAdoB+AHFAfgB8gHXAb8B8QHXAfEB9AHHAfMBEALHARACBgLuAbcB/QHuAf0BCwLvAQwCDQLvAQ0C6wHzAcoBCgLzAQoCEALMAesBDQLMAQ0CAgLdAeIBEwLdARMCGAL2AeABGQL2ARkCFwLhAfcB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Urj+P/z8Zj3zKFy/Urj+P/z8Zj0vHGc/6iUCQP38Zj0vHGc/6iUCQP38Zj0vHGe/6iUCQGH5h70vHGe/6iUCQGH5h70wHGe/0nxlP/z8Zj3zKFy/16NwP/z8Zj3zKFy/16NwP/z8Zj3zKFy/16NwP/z8Zj0uHGc/0nxlP/38Zj0uHGc/0nxlP/38Zj0wHGe/0nxlP2H5h73zKFy/16NwP2H5h73zKFy/16NwP2H5h73zKFy/16NwP2H5h70uHGc/0nxlP2H5h70vHGc/6iUCQGH5h73yKFw/16NwP2H5h73yKFw/16NwP2H5h73yKFw/16NwP2H5h73zKFw/Urj+P2H5h73zKFw/Urj+P2H5h73zKFw/Urj+P2H5h73yKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzyKFw/16NwP8aQ+bzzKFy/16NwP8aQ+bzzKFy/16NwP8aQ+bzzKFy/16NwP8aQ+bzyKFw/16NwP0Klp7zyKFw/16NwP0Klp7zyKFw/16NwP0Klp7zzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+bzzKFw/Urj+P8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP8aQ+byYxGA/16NwP0Klp7yYxGA/16NwP0Klp7yYxGA/16NwP0Klp7yYxGA/Urj+P8aQ+byYxGA/Urj+P8aQ+byYxGA/Urj+P8aQ+byYxGA/Urj+P0Klp7yYxGA/Urj+P0Klp7yYxGA/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFw/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFy/Urj+P0Klp7zzKFw/Urj+P25zKzzzKFw/Urj+P25zKzzzKFw/Urj+P25zKzzyKFw/16NwP25zKzzyKFw/16NwP25zKzzyKFw/16NwP25zKzzzKFw/Urj+PzylpzzzKFw/Urj+PzylpzzzKFw/Urj+PzylpzzzKFy/Urj+P25zKzzzKFy/Urj+P25zKzzzKFy/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/Urj+P25zKzyYxGA/16NwP25zKzyYxGA/16NwP25zKzyYxGA/16NwP25zKzyYxGA/Urj+PzylpzyYxGA/Urj+PzylpzyYxGA/Urj+PzylpzyYxGA/16NwPzylpzyYxGA/16NwPzylpzyYxGA/16NwPzylpzzyKFw/16NwPzylpzzyKFw/16NwPzylpzzyKFw/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzzKFy/16NwPzylpzzyKFw/16NwP/38Zj3yKFw/16NwP/38Zj3yKFw/16NwP/38Zj3yKFw/16NwP/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFw/Urj+P/38Zj3zKFy/16NwP25zKzzzKFy/16NwP25zKzzzKFy/16NwP25zKzzzKFy/Urj+PzylpzzzKFy/Urj+PzylpzzzKFy/Urj+PzylpzyZxGC/16NwPzylpzyZxGC/16NwPzylpzyZxGC/16NwPzylpzyZxGC/16NwP25zKzyZxGC/16NwP25zKzyZxGC/16NwP25zKzyYxGC/Urj+PzylpzyYxGC/Urj+PzylpzyYxGC/Urj+PzylpzyYxGC/Urj+P25zKzyYxGC/Urj+P25zKzyYxGC/Urj+P25zKzzzKFy/16NwP0Klp7zzKFy/16NwP0Klp7zzKFy/16NwP0Klp7yZxGC/16NwP0Klp7yZxGC/16NwP0Klp7yZxGC/16NwP0Klp7yZxGC/16NwP8aQ+byZxGC/16NwP8aQ+byZxGC/16NwP8aQ+byYxGC/Urj+P0Klp7yYxGC/Urj+P0Klp7yYxGC/Urj+P0Klp7yYxGC/Urj+P8aQ+byYxGC/Urj+P8aQ+byYxGC/Urj+P8aQ+bzzKFy/Urj+P8aQ+bzzKFy/Urj+P8aQ+bzzKFy/Urj+P8aQ+bzzKFy/Urj+P8aQ+bzzKFy/Urj+P2H5h73zKFy/Urj+P2H5h73zKFy/Urj+P2H5h73zKFy/Urj+P2H5h70AAAAA6iUCQPz8Zj0AAAAA6iUCQPz8Zj0AAAAAUrj+P0Klp7wAAAAAUrj+P25zKzwAAAAAUrj+P/z8Zj0AAAAAUrj+P/z8Zj0AAAAAUrj+P/z8Zj0AAAAAUrj+PzylpzwAAAAAUrj+P8aQ+bwAAAAAUrj+P8aQ+bwAAAAAUrj+P2H5h70AAAAAUrj+P2H5h70AAAAA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hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL24HmU/hesBQMzMzL21HmW/AAAAAI7C9b21HmW/AAAAAI7C9b21HmW/AAAAAI7C9b24HmU/AAAAAI7C9b24HmU/AAAAAI7C9b24HmU/AAAAAI7C9b26HmU/zswMQMzMzL26HmU/zswMQMzMzL26HmU/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL21HmW/zswMQMzMzL3+/3+/ZmZmP8zMzL3+/3+/ZmZmP8zMzL3+/3+/ZmZmP8zMzL0AAIA/ZmZmP8zMzL0AAIA/ZmZmP8zMzL0AAIA/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL21HmW/ZmZmP8zMzL24HmU/ZmZmP8zMzL24HmU/ZmZmP8zMzL24HmU/ZmZmP8zMzL0AAACmzswMQMzMzL0AAACmzswMQMzMzL0AAACmzswMQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmhesBQMzMzL0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmkML1PY7C9b0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmuB4FPszMzL0AAACmAAAAAI7C9b0AAACmAAAAAI7C9b0AAACmAAAAAI7C9b0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL0AAACmZmZmP8zMzL3+/3+/AAAAAAAAAC/+/3+/AAAAAAAAAC/+/3+/zswMQACAB7H+/3+/zswMQACAB7H+/3+/hesBQAAAAC/+/3+/hesBQAAAAC/+/3+/kML1PQAAAC/+/3+/kML1PQAAAC/+/3+/uB4FPgAAAC8AAIA/zswMQAAAAC8AAIA/zswMQAAAAC8AAIA/AAAAAAAAAC8AAIA/AAAAAAAAAC8AAIA/uB4FPgAAAC8AAIA/kML1PQAAAC8AAIA/kML1PQAAAC8AAIA/hesBQAAAAC8AAIA/hesBQAAAAC+1HmW/zswMQAAAAC+1HmW/zswMQAAAAC+5HmU/zswMQAAAAC+1HmW/AAAAAAAAAC+4HmU/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PWPXOP//fz8AAAAAMmrXOAAAgD8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D4AAAAAAAAAAP//fz8AAAAAAAAAAP//fz8AAAAAMvlkPyL55D4AAAAAMvlkPyX55D4AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAP//fz8AAAAAPWPXOP//fz8AAAAAMmrXOAAAgD8AAAAA//9/PwAAAIAAAAAA//9/PwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIDGOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAxVnQsgAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAIA/AAAAAAAAAIDFOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAIC/Pc9zMwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgD8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/Ps9zMwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIC/Ps9zMwAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAIC/Ps9zMwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIC/Ps9zMwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAA//9/PwAAAIAAAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/Pc9zMwAAAIDFOZGxAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/Pc9zMwAAAIDGOZGxAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAIA/Pc9zswAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/Pc9zswAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgL8AAIA/Pc9zswAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/Pc9zswAAAIAAAAAAAACAvwAAAIAAAAAA//9/vwAAAIAAAAAAAAAAAAAAgD8AAIA/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AAAAAP//f78AAAAAL/lkPyj55L4AAAAAMPlkPyn55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAMPlkPyn55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAMPlkPyn55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAL/lkPyj55L4AAAAAMPlkPyn55L4AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f7///38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAymH8Mv//f78AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAAAAAAAAgL8AAAAAymH8Mv//f78AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIBN5eo0AACAPwAAAIAAAIC/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL///38/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAID//38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAP//f78AAAAAymH8Mv//f78AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAP//f78AAAAAymH8Mv//f78AAAAAAAAAAP//f78AAAAAAAAAAP//f78AAAAAL/lkPyj55L4AAAAAMvlkPyL55L4AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAL/lkPyj55L4AAAAAMvlkPyL55L4AAAAAAACAvwAAAIAAAAAAAAAAAP//f78AAAAAAAAAAP//f78AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAAAAAACAPwAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAIC/AAAAAAAAAIBN5eo0AACAPwAAAIAAAIC/AAAAAAAAAID//3+/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DgAUABEADgARAAsAIwAXAAgAIwAIAB8AJwAhABIAJwASAA0ABQAWACAABQAgACYAEwAiAB4AEwAeABAACgAPABwACgAcABoABAAlABkABAAZAAEAJwMvA0wAJwNMADsAbwB4AFUAbwBVAEAAQwBiAGoAQwBqAEcAOgBLAFAAOgBQAD4AQQBXAGMAQQBjAEQAMgMtAz8AMgM/AHEAXgA0ACoAXgAqAGcAdQBuADkAdQA5AFwAWwA4ADUAWwA1AGAASQAsADAASQAwAFIAhgCKAGQAhgBkAFgANQN+AEwANQNMAC8DhwCMAGkAhwBpAGEAjwCEAFYAjwBWAHoAQgM7A3MAQgNzAJEAfACBAFAAfABQAEsALgMgAy0ALgMtAEoANwM5A3cANwN3AE0AUwAxAG0AUwBtAHQAKwMyA3EAKwNxAEIAPQBOAHkAPQB5AHAAPAM+A1QAPANUAHYAPwM4A08APwNPAH8APQNAA4AAPQOAAFEASQBSAIIASQCCAH0ALgNKAH4ALgN+ADUDOgNBA5AAOgOQAHsAcgBZAIMAcgCDAI4AXQBmAI0AXQCNAIgAWgBfAIkAWgCJAIUAlACSAJoAlACaAJsAHwGSAJQAHwGUACQBLgE4AZsALgGbAJoALwGgAKEALwGhADABmQAcAaEAmQChAKAAqwClAKQAqwCkAKcAMwGuAKgAMwGoADEBrAC2AKkArACpAKYANAE8AbUANAG1AK8AtQC/ALwAtQC8AK8ArQC7AMIArQDCALkAugC9AMMAugDDAMAAuADBAMQAuADEAMgAyQDFAL4AyQC+ALQAJgEgAcoAJgHKABYB0QDOAMYA0QDGALMAIQEgAccAIQHHAM8A0gDeANwA0gDcANAAzwDbAOEAzwDhANUAIQElAfgAIQH4ANcA3QDjAOAA3QDgANoA1gDiAOQA1gDkAOcA6ADlAN8A6ADfANMAOwE2AegAOwHoANMAOgHwAOgAOgHoADYB7QDxANQA7QDUAOYAlwD0AO8AlwDvAJ8AIwElAdUAIwHVAPMA9gD/APwA9gD8AOsA6QD7AAIB6QACAfkA+gCVAJ0A+gCdAOwA/QAAAQYB/QAGAQMB+AABAQQB+AAEAdcA2AAFAf4A2AD+APUAOwHTALUAOwG1ADwBzQDZAPcAzQD3AAkBsQAOAQsBsQALAQgBBwEKAREBBwERAcwADAEPARUBDAEVARIBywAQARMBywATARcBGAEUAQ0BGAENAbAAJwEoAaoAJwGqALgAHQEZAbIAHQGyAKMAKwEpARsBKwEbAZgApQCrACoBpQAqASwBFwEaASgBFwEoAScBkwD4ACUBkwAlASMBzwDVACUBzwAlASEB1wDKACAB1wAgASEBtwDHACABtwAgASYBlgAeASIBlgAiAfIA9gA7ATwB9gA8AQgBnAA5ATUBnAA1AeoA9gDrADYB9gA2ATsBsQAIATwBsQA8ATQBsQA0ATIBsQAyAaIApAAvATABpAAwAacAngDuADcBngA3AS0BPwFCAVQBPwFUAU8BQwFAAUwBQwFMAXMBTgFWAUgBTgFIAT4BSQE9AUUBSQFFAWkBTQFSAV4BTQFeAVkBWAFQAVwBWAFcAWQBcgFLAVsBcgFbAWABYgFaAUoBYgFKAWwBfAFXAWMBfAFjAWgBawFuAWYBawFmAWEBRgF1AW0BRgFtAWoBbwFxAV8BbwFfAWcBeAFEAXQBeAF0AXABdgFHAVUBdgFVAXoBewFTAUEBewFBAXcBXQFRAXkBXQF5AWUBgwGAAYwBgwGMAZsBiQF9AYUBiQGFAZUBqgGeAYoBqgGKAZgBmQGnAbMBmQGzAZMBhgGNAZEBhgGRAZYBkAGEAZwBkAGcAZQBowF/AYIBowGCAbwBiwGfAagBiwGoAZoBsAGkAaABsAGgAawBoQG6AaYBoQGmAZ0BuAGvAasBuAGrAbQBrgGIAX4BrgF+AaIBpQG5AbUBpQG1AbEBlwGSAbIBlwGyAakBrQG2AY4BrQGOAYcBuwGBAY8BuwGPAbcBxAHAAcEBxAHBAcUBxwG9Ab8BxwG/AcMByQHLAb4ByQG+AcgBxgHCAcwBxgHMAcoBzwHSAeQBzwHkAd8B0wHQAdwB0wHcAQMC3gHmAdgB3gHYAc4B2QHNAdUB2QHVAfkB3QHiAe4B3QHuAekB6AHgAewB6AHsAfQBAgLbAesBAgLrAfAB8gHqAdoB8gHaAfwBDALnAfMBDALzAfgB+wH+AfYB+wH2AfEB1gEFAv0B1gH9AfoB/wEBAu8B/wHvAfcBCALUAQQCCAIEAgACBgLXAeUBBgLlAQoCCwLjAdEBCwLRAQcC7QHhAQkC7QEJAvUBEwIQAhwCEwIcAisCGQINAhUCGQIVAiUCOgIuAhoCOgIaAigCKQI3AkMCKQJDAiMCFgIdAiECFgIhAiYCIAIUAiwCIAIsAiQCMwIPAhICMwISAkwCGwIvAjgCGwI4AioCQAI0AjACQAIwAjwCMQJKAjYCMQI2Ai0CSAI/AjsCSAI7AkQCPgIYAg4CPgIOAjICNQJJAkUCNQJFAkECJwIiAkICJwJCAjkCPQJGAh4CPQIeAhcCSwIRAh8CSwIfAkcCVAJQAlECVAJRAlUCVwJNAk8CVwJPAlMCWQJbAk4CWQJOAlgCVgJSAlwCVgJcAloCXwJiAnQCXwJ0Am8CYwJgAmwCYwJsApMCbgJ2AmgCbgJoAl4CaQJdAmUCaQJlAokCbQJyAn4CbQJ+AnkCeAJwAnwCeAJ8AoQCkgJrAnsCkgJ7AoACggJ6AmoCggJqAowCnAJ3AoMCnAKDAogCiwKOAoYCiwKGAoECZgKVAo0CZgKNAooCjwKRAn8CjwJ/AocCmAJkApQCmAKUApAClgJnAnUClgJ1ApoCmwJzAmECmwJhApcCfQJxApkCfQKZAoUCowKgAqwCowKsArsCqQKdAqUCqQKlArUCygK+AqoCygKqArgCuQLHAtMCuQLTArMCpgKtArECpgKxArYCsAKkArwCsAK8ArQCwwKfAqICwwKiAtwCqwK/AsgCqwLIAroC0ALEAsAC0ALAAswCwQLaAsYCwQLGAr0C2ALPAssC2ALLAtQCzgKoAp4CzgKeAsICxQLZAtUCxQLVAtECtwKyAtICtwLSAskCzQLWAq4CzQKuAqcC2wKhAq8C2wKvAtcC5ALgAuEC5ALhAuUC5wLdAt8C5wLfAuMC6QLrAt4C6QLeAugC5gLiAuwC5gLsAuoC9ALwAvEC9ALxAvUC9wLtAu8C9wLvAvMC+QL7Au4C+QLuAvgC9gLyAvwC9gL8AvoCBAMAAwEDBAMBAwUDBwP9Av8CBwP/AgMDCQMLA/4CCQP+AggDBgMCAwwDBgMMAwoDFAMQAxEDFAMRAxUDFwMNAw8DFwMPAxMDGQMbAw4DGQMOAxgDFgMSAxwDFgMcAxoDZQAwAzYDZQA2A4sANgBrADMDNgAzAyUDiwA2AzEDiwAxA2gAZQApAB4DZQAeAzADPAA/AC0DPAAtAygDRQBIACoDRQAqAywDQgBFACwDQgAsAysDMgA3ACYDMgAmAyMDKAAzACQDKAAkAx0DbAAvACIDbAAiAzQDRgBoADEDRgAxAykDLgArAB8DLgAfAyEDWgNJA1QDWgNUA1wDcANrA04DcANOA2ADYQNQA0YDYQNGA1cDYwNoA0wDYwNMA0MDbQNmA0UDbQNFA08DTQNiA1gDTQNYA0QDZwNuA18DZwNfA0sDRwNZA1sDRwNbA1EDUgNdA24DUgNuA2cDVgNKA2UDVgNlA2wDSANTA2kDSANpA2QDXgNVA2oDXgNqA28DhwN3A4IDhwOCA4kDmQOVA3wDmQN8A44DjwN+A3QDjwN0A4YDkQOUA3oDkQN6A3EDlwOSA3MDlwNzA30DewOQA4UDewOFA3IDkwOYA40DkwONA3kDdQOIA4oDdQOKA38DgAOLA5gDgAOYA5MDhAN4A5IDhAOSA5cDdgOBA5QDdgOUA5EDjAODA5YDjAOWA5oDBgQEBJ4DBgSeA6ED5QPhA6MD5QOjA8sDsgPQA9gDsgPYA7UDDQQYBNUDDQTVA7gD4gPqA9AD4gPQA7IDHAQHBKIDHASiA+ADAgQJBKUDAgSlA5sDCAQKBKgDCASoA6QDuQPWA7sDuQO7A6sDDwQRBK4DDwSuA7EDEAQOBLoDEAS6A60DDAQTBLQDDAS0A7cD0wOdA6YD0wOmA78DwQOnA6oDwQOqA8UDFwQDBJwDFwScA9IDywOjA58DywOfA9sDrAO+A8kDrAPJA7AD9QP5A8gD9QPIA70DHgT7A9UDHgTVAxgE8gPtA9kD8gPZA9ED/QP0A7wD/QO8A9cD9wP+A+oD9wPqA8cDCgQbBN8DCgTfA6gDrwPHA+oDrwPqA+IDwwOpA+EDwwPhA+UDzAPcA+wDzAPsA/EDxAPmA/8DxAP/A/gD1APAA/MD1APzA/wDFwTSA/sDFwT7Ax4EwgPGA/oDwgP6A/YD3QMUBB0E3QMdBO4DsQPkAxoEsQMaBA8E3gOgAwUE3gMFBBUE7gMdBBYE7gMWBNoD4wOzAxIE4wMSBBkEtgPaAxYEtgMWBAsE6wMABCkE6wMpBCIEygPwAygEygMoBCUE7wPPAyAE7wMgBCcE6APNAyYE6AMmBCQEzgPpAyEEzgMhBB8EAQTnAyMEAQQjBCoEAAAAvwAAAACPwvU9AAAAvwAAAACPwvU9AAAAvwAAAACPwvU9AAAAv87MDEDMzMw9AAAAv87MDEDMzMw9AAAAv87MDEDMzMw9AAAAPwAAAACPwvU9AAAAPwAAAACPwvU9AAAAPwAAAACPwvU9AAAAP87MDEDMzMw9AAAAP87MDEDMzMw9AAAAP87MDEDMzMw978TRvgAAAACPwvU978TRvgAAAACPwvU978TRvgAAAACPwvU9XwLcvs7MDEDMzMw9XwLcvs7MDEDMzMw9GBLSPgAAAACPwvU9GBLSPgAAAACPwvU9GBLSPgAAAACPwvU9ik/cPs7MDEDMzMw9ik/cPs7MDEDMzMw9ik/cPs7MDEDMzMw9AAAAv4XrAUDMzMw9AAAAv4XrAUDMzMw9AAAAP4XrAUDMzMw9AAAAP4XrAUDMzMw9AAAAP4XrAUDMzMw9XwLcvoXrAUDMzMw9XwLcvoXrAUDMzMw9XwLcvoXrAUDMzMw9iE/cPoXrAUDMzMw9iE/cPoXrAUDMzMw9iE/cPoXrAUDMzMw9iE/cPoXrAUDMzMw9iE/cPoXrAUDMzMw978TRvpDC9T2PwvU978TRvpDC9T2PwvU978TRvpDC9T2PwvU978TRvpDC9T2PwvU9AAAAv5DC9T2PwvU9AAAAv5DC9T2PwvU9AAAAv5DC9T2PwvU9AAAAP5DC9T2PwvU9AAAAP5DC9T2PwvU9AAAAP5DC9T2PwvU9GBLSPpDC9T2PwvU9GBLSPpDC9T2PwvU9GBLSPpDC9T2PwvU9GBLSPpDC9T2PwvU9AAAAv7geBT7MzMw9AAAAv7geBT7MzMw9AAAAv7geBT7MzMw9XwLcvrgeBT7MzMw9XwLcvrgeBT7MzMw9XwLcvrgeBT7MzMw9XwLcvrgeBT7MzMw9AAAAP7geBT7MzMw9AAAAP7geBT7MzMw9AAAAP7geBT7MzMw9iE/cPrgeBT7MzMw9iE/cPrgeBT7MzMw9iE/cPrgeBT7MzMw9iE/cPrgeBT7MzMw9AAAAM87MDEDMzMw9AAAAM87MDEDMzMw9AAAAM87MDEDMzMw9AAAAs4XrAUDMzMw9AAAAs4XrAUDMzMw9AAAAs4XrAUDMzMw9AAAAv87MDEBedFGxAAAAv87MDEBedFGxAAAAPwAAAABedFGxAAAAPwAAAABedFGxiE/cPoXrAUBedFGxiE/cPoXrAUBedFGxAAAAv4XrAUBedFGxAAAAv5DC9T1edFGxAAAAv7geBT5edFGxAACAsoXrAUBedFGxAACAMs7MDEBedFGxiU/cPs7MDEBedFGxAAAAP7geBT5edFGxAAAAP5DC9T1edFGxiU/cPrgeBT5edFGxiU/cPrgeBT5edFGxXwLcvrgeBT5edFGxXwLcvrgeBT5edFGx78TRvpDC9T1edFGx78TRvpDC9T1edFGxGRLSPpDC9T1edFGxGRLSPpDC9T1edFGx78TRvgAAAABedFGx78TRvgAAAABedFGxAAAAvwAAAABedFGxAAAAvwAAAABedFGxAAAAP4XrAUBedFGxXwLcvoXrAUBedFGxXwLcvoXrAUBedFGxGRLSPgAAAABedFGxGRLSPgAAAABedFGxAAAAP87MDEBedFGxAAAAP87MDEBedFGxXwLcvs7MDEBedFGxAAAAvwAAAACPwvW9AAAAvwAAAACPwvW9AAAAvwAAAACPwvW9AAAAv5DC9T2PwvW9AAAAv5DC9T2PwvW9AAAAv5DC9T2PwvW978TRvgAAAACPwvW978TRvgAAAACPwvW978TRvgAAAACPwvW9AAAAvwAAAABedFGxAAAAvwAAAABedFGxAAAAv5DC9T1edFGx78TRvgAAAABedFGx78TRvgAAAABedFGx78TRvpDC9T1edFGx78TRvpDC9T1edFGx78TRvpDC9T2PwvW978TRvpDC9T2PwvW978TRvpDC9T2PwvW978TRvpDC9T2PwvW9XwLcvrgeBT5edFGxXwLcvrgeBT5edFGxXwLcvrgeBT7NzMy9XwLcvrgeBT7NzMy9XwLcvrgeBT7NzMy9XwLcvrgeBT7NzMy9XwLcvoXrAUBedFGxXwLcvoXrAUBedFGxXwLcvoXrAUDNzMy9XwLcvoXrAUDNzMy9XwLcvoXrAUDNzMy9AACAsoXrAUBedFGxiE/cPoXrAUBedFGxiE/cPoXrAUBedFGxAAAAAIXrAUDNzMy9AAAAAIXrAUDNzMy9iE/cPoXrAUDNzMy9iE/cPoXrAUDNzMy9iE/cPoXrAUDNzMy9AAAAAM7MDEDNzMy9AAAAAM7MDEDNzMy9iE/cPs7MDEDNzMy9iE/cPs7MDEDNzMy9XwLcvs7MDEDNzMy9XwLcvs7MDEDNzMy9AACAMs7MDEBedFGxiU/cPs7MDEBedFGxXwLcvs7MDEBedFGxAAAAv87MDEBedFGxAAAAv87MDEBedFGxAAAAv4XrAUBedFGxAAAAv87MDEDNzMy9AAAAv87MDEDNzMy9AAAAv87MDEDNzMy9AAAAv4XrAUDNzMy9AAAAv4XrAUDNzMy9AAAAv7geBT7NzMy9AAAAv7geBT7NzMy9AAAAv7geBT7NzMy9AAAAv7geBT5edFGxAAAAP87MDEBedFGxAAAAP87MDEBedFGxAAAAP87MDEDNzMy9AAAAP87MDEDNzMy9AAAAP87MDEDNzMy9AAAAP4XrAUBedFGxAAAAP4XrAUDNzMy9AAAAP4XrAUDNzMy9AAAAP7geBT5edFGxAAAAP7geBT7NzMy9AAAAP7geBT7NzMy9AAAAP7geBT7NzMy9AAAAP5DC9T1edFGxAAAAP5DC9T2PwvW9AAAAP5DC9T2PwvW9AAAAP5DC9T2PwvW9AAAAPwAAAABedFGxAAAAPwAAAABedFGxGRLSPgAAAABedFGxGRLSPgAAAABedFGxAAAAPwAAAACPwvW9AAAAPwAAAACPwvW9AAAAPwAAAACPwvW9GhLSPgAAAACPwvW9GhLSPgAAAACPwvW9GhLSPgAAAACPwvW9GhLSPpDC9T2PwvW9GhLSPpDC9T2PwvW9GhLSPpDC9T2PwvW9GhLSPpDC9T2PwvW9ik/cPrgeBT7NzMy9ik/cPrgeBT7NzMy9ik/cPrgeBT7NzMy9ik/cPrgeBT7NzMy9GRLSPpDC9T1edFGxGRLSPpDC9T1edFGxiU/cPrgeBT5edFGxiU/cPrgeBT5edFGxAACAvwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAD//38/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAI8FBzIAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAD//38/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAACO44q0AAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAI8FBzIAAIA/AAAAAIn7rjIAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAI8FBzIAAIA/AACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAv6O8BrIAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAI8FBzIAAIA/AAAAAIn7rjIAAIA/AAAAAAAAAAD//38/AAAAADH5ZD8k+eQ+JfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAD//38/AAAAADH5ZD8k+eQ+AAAAAAAAAAAAAIA/AAAAADH5ZD8m+eQ+AACAPwAAAAAAAACAAACAvwAAAACO44q0JvnkvjD5ZD92R0u0AAAAAAAAAAAAAIA/AAAAADH5ZD8m+eQ+AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADH5ZD8k+eQ+AAAAAAAAAAAAAIA/AAAAADH5ZD8k+eQ+JfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAADH5ZD8m+eQ+AACAPwAAAAAAAACAAACAv6O8BrIAAACAJvnkvjD5ZD92R0u0AAAAAAAAAAAAAIA/AAAAADH5ZD8m+eQ+AAAAAAAAAAAAAIA/AAAAAIn7rjIAAIA/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAAIn7rjIAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAACAv6O8BrIAAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAv6O8BrIAAACAJvnkvjD5ZD92R0u0JfnkPjD5ZD8AAACAAACAPwAAAAAAAACAJfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAACAvwAAAACO44q0JvnkvjD5ZD92R0u0AAAAAP//f78AAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAACAvwAAAACO44q0AAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAAAAAAAAAAD//3+/AACAvwAAAAAAAACAAAAAAAAAAAD//3+/AAAAADD5ZD8l+eS+AAAAAP//f78AAACAAAAAAAAAAAD//3+/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAACAvwAAAAAAAACAAAAAAP//f78AAACAAACAPwAAAAAAAACAJfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAAAAAAAAAAD//3+/AAAAADD5ZD8l+eS+JfnkPjD5ZD8AAACAAACAPwAAAAAAAACAJfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADD5ZD8l+eS+JfnkPjD5ZD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAgL8AAACAAACAv/MayrLKa6i0AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAv/MayrLKa6i0AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADD5ZD8l+eS+AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADH5ZD8o+eS+AACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAADH5ZD8o+eS+AACAPwAAAAAAAACA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WAUAQCCN87nMzEw/ABCrObAqDb3MzEw/ABCrObAqDb3MzEw/ABCrObAqDb3MzEw/ABCrObAqDb3MzEw/WAUAQLAqDb3MzEw/WAUAQLAqDb3MzEw/WAUAQLAqDb3MzEw/WAUAQLAqDb0AAICzsQqAPyCN87kAAICzsQqAPyCN87kAAICzsQqAPyCN87kAAICzsQqAP7AqDb0AAICzsQqAP7AqDb0AAICzsQqAP7AqDb3MzEw/sQqAP7AqDb3MzEw/sQqAP7AqDb3MzEw/sQqAP7AqDb3MzEw/sQqAPyCN87nMzEw/sQqAPyCN87nMzEw/sQqAPyCN87kgX449pPCIP7AqDb0gX449pPCIP7AqDb0gX449pPCIP7AqDb0gX449viT3P7AqDb0gX449viT3P7AqDb0gX449viT3P7AqDb0gX449viT3P7AqDb3nADs/viT3P7AqDb3nADs/viT3P7AqDb3nADs/viT3P7AqDb3nADs/viT3P7AqDb3nADs/pPCIP7AqDb3nADs/pPCIP7AqDb3nADs/pPCIP7AqDb3nADs/pPCIPyCN87nnADs/pPCIPyCN87nnADs/pPCIPyCN87nnADs/viT3PyCN87nnADs/viT3PyCN87nnADs/viT3PyCN87nnADs/viT3PyCN87kgX449viT3PyCN87kgX449viT3PyCN87kgX449viT3PyCN87kgX449viT3PyCN87kgX449pPCIPyCN87kgX449pPCIPyCN87kgX449pPCIPyCN87nnADs/OAqPPSCN87nnADs/OAqPPSCN87nnADs/OAqPPSCN87nnADs/OAqPPSCN87nnADs/fUluPyCN87nnADs/fUluPyCN87nnADs/fUluPyCN87nnADs/fUluPyCN87kgX449fUluPyCN87kgX449fUluPyCN87kgX449fUluPyCN87kgX449fUluPyCN87kgX449OAqPPSCN87kgX449OAqPPSCN87kgX449OAqPPSCN87kgX449OAqPPSCN87kgX449OAqPPbAqDb0gX449OAqPPbAqDb0gX449OAqPPbAqDb0gX449OAqPPbAqDb0gX449fUluP7AqDb0gX449fUluP7AqDb0gX449fUluP7AqDb0gX449fUluP7AqDb3nADs/fUluP7AqDb3nADs/fUluP7AqDb3nADs/fUluP7AqDb3nADs/fUluP7AqDb3nADs/OAqPPbAqDb3nADs/OAqPPbAqDb3nADs/OAqPPbAqDb3nADs/OAqPPbAqDb3U96I9LjqKP7AqDb3U96I9LjqKP7AqDb3U96I9LjqKP7AqDb3U96I9M9v1P7AqDb3U96I9M9v1P7AqDb3U96I9M9v1P7AqDb3RbTg/M9v1P7AqDb3RbTg/M9v1P7AqDb3RbTg/M9v1P7AqDb3RbTg/LjqKP7AqDb3RbTg/LjqKP7AqDb3RbTg/LjqKP7AqDb3RbTg/LjqKPyCN87nRbTg/LjqKPyCN87nRbTg/LjqKPyCN87nRbTg/M9v1PyCN87nRbTg/M9v1PyCN87nRbTg/M9v1PyCN87nU96I9M9v1PyCN87nU96I9M9v1PyCN87nU96I9M9v1PyCN87nU96I9LjqKPyCN87nU96I9LjqKPyCN87nU96I9LjqKPyCN87nRbTg/7KKjPSCN87nRbTg/7KKjPSCN87nRbTg/7KKjPSCN87nRbTg/ZrZrPyCN87nRbTg/ZrZrPyCN87nRbTg/ZrZrPyCN87nU96I9ZrZrPyCN87nU96I9ZrZrPyCN87nU96I9ZrZrPyCN87nU96I97KKjPSCN87nU96I97KKjPSCN87nU96I97KKjPSCN87nU96I97KKjPbAqDb3U96I97KKjPbAqDb3U96I97KKjPbAqDb3U96I9ZrZrP7AqDb3U96I9ZrZrP7AqDb3U96I9ZrZrP7AqDb3RbTg/ZrZrP7AqDb3RbTg/ZrZrP7AqDb3RbTg/ZrZrP7AqDb3RbTg/7KKjPbAqDb3RbTg/7KKjPbAqDb3RbTg/7KKjPbAqDb0gX449viT3P5az1LwgX449viT3P5az1LwgX449viT3P5az1LwgX449pPCIP5az1LwgX449pPCIP5az1LwgX449pPCIP5az1LznADs/pPCIP5az1LznADs/pPCIP5az1LznADs/pPCIP5az1LznADs/viT3P5az1LznADs/viT3P5az1LznADs/viT3P5az1LznADs/viT3PwDgErznADs/viT3PwDgErznADs/viT3PwDgErznADs/viT3PwDgErznADs/pPCIPwDgErznADs/pPCIPwDgErznADs/pPCIPwDgErwgX449pPCIPwDgErwgX449pPCIPwDgErwgX449pPCIPwDgErwgX449viT3PwDgErwgX449viT3PwDgErwgX449viT3PwDgErwgX449viT3PwDgErznADs/fUluPwDgErznADs/fUluPwDgErznADs/fUluPwDgErznADs/fUluPwDgErznADs/OAqPPQDgErznADs/OAqPPQDgErznADs/OAqPPQDgErwgX449OAqPPQDgErwgX449OAqPPQDgErwgX449OAqPPQDgErwgX449fUluPwDgErwgX449fUluPwDgErwgX449fUluPwDgErwgX449fUluPwDgErwgX449fUluP5az1LwgX449fUluP5az1LwgX449fUluP5az1LwgX449OAqPPZaz1LwgX449OAqPPZaz1LwgX449OAqPPZaz1LznADs/OAqPPZaz1LznADs/OAqPPZaz1LznADs/OAqPPZaz1LznADs/fUluP5az1LznADs/fUluP5az1LznADs/fUluP5az1LzU96I9M9v1P5az1LzU96I9M9v1P5az1LzU96I9M9v1P5az1LzU96I9LjqKP5az1LzU96I9LjqKP5az1LzU96I9LjqKP5az1LzRbTg/LjqKP5az1LzRbTg/LjqKP5az1LzRbTg/LjqKP5az1LzRbTg/M9v1P5az1LzRbTg/M9v1P5az1LzRbTg/M9v1P5az1LzRbTg/M9v1PwDgErzRbTg/M9v1PwDgErzRbTg/M9v1PwDgErzRbTg/M9v1PwDgErzRbTg/LjqKPwDgErzRbTg/LjqKPwDgErzRbTg/LjqKPwDgErzU96I9LjqKPwDgErzU96I9LjqKPwDgErzU96I9LjqKPwDgErzU96I9M9v1PwDgErzU96I9M9v1PwDgErzU96I9M9v1PwDgErzU96I9M9v1PwDgErzRbTg/ZrZrPwDgErzRbTg/ZrZrPwDgErzRbTg/ZrZrPwDgErzRbTg/ZrZrPwDgErzRbTg/8KKjPQDgErzRbTg/8KKjPQDgErzRbTg/8KKjPQDgErzU96I98KKjPQDgErzU96I98KKjPQDgErzU96I98KKjPQDgErzU96I9ZrZrPwDgErzU96I9ZrZrPwDgErzU96I9ZrZrPwDgErzU96I9ZrZrPwDgErzU96I9ZrZrP5az1LzU96I9ZrZrP5az1LzU96I9ZrZrP5az1LzU96I98KKjPZaz1LzU96I9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AAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIA/AAAAAAAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD///38/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAID//38/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAP//fz8AAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAAAAAP//f78AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD///38/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAID//38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAv0lLa7YAAAAAAAAAAAAAgD8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAv0lLa7YAAAAAAAAAAAAAgD8AAIC/AAAAAAAAAIAAAAAAAACAv1BLazYAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAv1BLazYAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL///3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAUsOYMwAAgD///3+/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAID//38/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAUsOYMwAAgD///38/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAFsOYMwAAgD8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD8AAAAAFsOYMwAAgD8AAIA/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIC/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIC/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAAAAAACAvwAAAIAAAAAAAAAAAAAAgL8AAIA/AAAAAAAAAIAAAAAAAAAAAAAAgL8AAAAAAACAPwAAAIAAAIA/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAUsOYMwAAgD8AAAAAAACAPwAAAID//38/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD///38/AAAAAAAAAID//3+/AAAAAAAAAIAAAAAAAACAvwAAAIAAAAAAAAAAAAAAgD///3+/AAAAAAAAAIAAAAAAAAAAAAAAgD8AAAAAUsOYMwAAgD8AAAAAAACAPwAAAIAAAAAAAAAAAAAAgD8AAAAA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OAARABIAOAASADsAOwASAAMAOwADABMAOgA7ABMAOgATABQAOQA6ABQAOQAUABUACAA5ABUACAAVAAIACgA0ADkACgA5AAgACwAwADMACwAzAAkAPAA9AEAAPABAAD8APQA+AEEAPQBBAEAAPwBAAEMAPwBDAEIAQABBAEQAQABEAEMAAgAVADwAAgA8ABgAFQAUAD0AFQA9ADwAFAATAD4AFAA+AD0AEwADABkAEwAZAD4APgAZABoAPgAaAEEAQQAaABsAQQAbAEQARAAbAAcARAAHABwAQwBEABwAQwAcAB0AQgBDAB0AQgAdAB4AFgBCAB4AFgAeAAYAFwA/AEIAFwBCABYAGAA8AD8AGAA/ABcARQBGAEsARQBLAEkARgBHAE0ARgBNAEsASABKAE8ASABPAE4ASgBMAFAASgBQAE8ABgAeAEUABgBFACIAHgAdAEYAHgBGAEUAHQAcAEcAHQBHAEYAHAAHACMAHAAjAEcARwAjACUARwAlAE0ATAAkACYATAAmAFAAUAAmAAUAUAAFACcATwBQACcATwAnACgATgBPACgATgAoACkAHwBOACkAHwApAAQAIABIAE4AIABOAB8AIgBFAEkAIgBJACEAUQBSAFUAUQBVAFQAUgBTAFYAUgBWAFUAVABVAFgAVABYAFcAVQBWAFkAVQBZAFgABAApAFEABABRACwAKQAoAFIAKQBSAFEAKAAnAFMAKABTAFIAJwAFAC0AJwAtAFMAUwAtAC4AUwAuAFYAVgAuAC8AVgAvAFkAWQAvAAEAWQABAA4AWABZAA4AWAAOAA0AVwBYAA0AVwANAAwAKgBXAAwAKgAMAAAAKwBUAFcAKwBXACoALABRAFQALABUACsAWgBbAGAAWgBgAF4AWwBcAGIAWwBiAGAAXQBfAGQAXQBkAGMAXwBhAGUAXwBlAGQAAgAYAFoAAgBaAAgAGAAXAFsAGABbAFoAFwAWAFwAFwBcAFsAFgAGACIAFgAiAFwAXAAiACEAXAAhAGIAYQAgAB8AYQAfAGUAZQAfAAQAZQAEACwAZABlACwAZAAsACsAYwBkACsAYwArACoACwBjACoACwAqAAAACQBdAGMACQBjAAsACABaAF4ACABeAAoAZgBnAGwAZgBsAGoAZwBoAG4AZwBuAGwAaQBrAHAAaQBwAG8AawBtAHEAawBxAHAABwAbAGYABwBmACMAGwAaAGcAGwBnAGYAGgAZAGgAGgBoAGcAGQADABIAGQASAGgAaAASABEAaAARAG4AbQAQAA8AbQAPAHEAcQAPAAEAcQABAC8AcABxAC8AcAAvAC4AbwBwAC4AbwAuAC0AJgBvAC0AJgAtAAUAJABpAG8AJABvACYAIwBmAGoAIwBqACUApACiAJgApACYAJkAqgB0AHYAqgB2AKsApQCjAJoApQCaAJsAqwB2AHgAqwB4AKwAqQCmAJUAqQCVAJYArAB4AHoArAB6AK0AogCoAJcAogCXAJgArQB6AHwArQB8AK4AowCkAJkAowCZAJoArgB8AH4ArgB+AK8ApwClAJsApwCbAJMArwB+AIAArwCAALAApgCnAJMApgCTAJUAsACAAIIAsACCALEAqACpAJYAqACWAJcAsQCCAHQAsQB0AKoAhACDAHYAhAB2AHQAgwCFAHgAgwB4AHYAhQCGAHoAhQB6AHgAhgCHAHwAhgB8AHoAhwCIAH4AhwB+AHwAiACJAIAAiACAAH4AiQCKAIIAiQCCAIAAigCEAHQAigB0AIIAlAByAIMAlACDAIQAcgCcAIUAcgCFAIMAnACdAIYAnACGAIUAnQCeAIcAnQCHAIYAngCfAIgAngCIAIcAnwCgAIkAnwCJAIgAoAChAIoAoACKAIkAoQCUAIQAoQCEAIoAjACLAHIAjAByAJQAiwCNAJwAiwCcAHIAjQCOAJ0AjQCdAJwAjgCPAJ4AjgCeAJ0AjwCQAJ8AjwCfAJ4AkACRAKAAkACgAJ8AkQCSAKEAkQChAKAAkgCMAJQAkgCUAKEAjgCNAKkAjgCpAKgAiwCMAKcAiwCnAKYAjACSAKUAjAClAKcAkQCQAKQAkQCkAKMAjwCOAKgAjwCoAKIAjQCLAKYAjQCmAKkAkgCRAKMAkgCjAKUAkACPAKIAkACiAKQAgQCxAKoAgQCqAHMAfwCwALEAfwCxAIEAfQCvALAAfQCwAH8AewCuAK8AewCvAH0AeQCtAK4AeQCuAHsAdwCsAK0AdwCtAHkAdQCrAKwAdQCsAHcAcwCqAKsAcwCrAHUA4gDjAOcA4gDnAOUA4wDkAOkA4wDpAOcA5gDoAOwA5gDsAOsA6ADqAO0A6ADtAOwAsgC+AOIAsgDiAL0AvgC/AOMAvgDjAOIAvwDAAOQAvwDkAOMAwACzAMEAwADBAOQA5ADBAMIA5ADCAOkA6gDDAMQA6gDEAO0A7QDEALUA7QC1AMUA7ADtAMUA7ADFAMYA6wDsAMYA6wDGAMcAugDrAMcAugDHALQAvADmAOsAvADrALoAvQDiAOUAvQDlALsA7gDvAPIA7gDyAPEA7wDwAPMA7wDzAPIA8QDyAPUA8QD1APQA8gDzAPYA8gD2APUAtADHAO4AtADuAMoAxwDGAO8AxwDvAO4AxgDFAPAAxgDwAO8AxQC1AMsAxQDLAPAA8ADLAMwA8ADMAPMA8wDMAM0A8wDNAPYA9gDNALkA9gC5AM4A9QD2AM4A9QDOAM8A9AD1AM8A9ADPANAAyAD0ANAAyADQALgAyQDxAPQAyQD0AMgAygDuAPEAygDxAMkA9wD4AP0A9wD9APsA+AD5AP8A+AD/AP0A+gD8AAEB+gABAQAB/AD+AAIB/AACAQEBuADQAPcAuAD3ANQA0ADPAPgA0AD4APcAzwDOAPkAzwD5APgAzgC5ANUAzgDVAPkA+QDVANcA+QDXAP8A/gDWANgA/gDYAAIBAgHYALcAAgG3ANkAAQECAdkAAQHZANoAAAEBAdoAAAHaANsA0QAAAdsA0QDbALYA0gD6AAAB0gAAAdEA1AD3APsA1AD7ANMAAwEEAQcBAwEHAQYBBAEFAQgBBAEIAQcBBgEHAQoBBgEKAQkBBwEIAQsBBwELAQoBtgDbAAMBtgADAd4A2wDaAAQB2wAEAQMB2gDZAAUB2gAFAQQB2QC3AN8A2QDfAAUBBQHfAOAABQHgAAgBCAHgAOEACAHhAAsBCwHhALMACwGzAMAACgELAcAACgHAAL8ACQEKAb8ACQG/AL4A3AAJAb4A3AC+ALIA3QAGAQkB3QAJAdwA3gADAQYB3gAGAd0ADAENARIBDAESARABDQEOARQBDQEUARIBDwERARYBDwEWARUBEQETARcBEQEXARYBtADKAAwBtAAMAboAygDJAA0BygANAQwByQDIAA4ByQAOAQ0ByAC4ANQAyADUAA4BDgHUANMADgHTABQBEwHSANEAEwHRABcBFwHRALYAFwG2AN4AFgEXAd4AFgHeAN0AFQEWAd0AFQHdANwAvQAVAdwAvQDcALIAuwAPARUBuwAVAb0AugAMARABugAQAbwAGAEZAR4BGAEeARwBGQEaASABGQEgAR4BGwEdASIBGwEiASEBHQEfASMBHQEjASIBuQDNABgBuQAYAdUAzQDMABkBzQAZARgBzADLABoBzAAaARkBywC1AMQAywDEABoBGgHEAMMAGgHDACABHwHCAMEAHwHBACMBIwHBALMAIwGzAOEAIgEjAeEAIgHhAOAAIQEiAeAAIQHgAN8A2AAhAd8A2ADfALcA1gAbASEB1gAhAdgA1QAYARwB1QAcAdcAVgFUAUoBVgFKAUsBXAEmASgBXAEoAV0BVwFVAUwBVwFMAU0BXQEoASoBXQEqAV4BWwFYAUcBWwFHAUgBXgEqASwBXgEsAV8BVAFaAUkBVAFJAUoBXwEsAS4BXwEuAWABVQFWAUsBVQFLAUwBYAEuATABYAEwAWEBWQFXAU0BWQFNAUUBYQEwATIBYQEyAWIBWAFZAUUBWAFFAUcBYgEyATQBYgE0AWMBWgFbAUgBWgFIAUkBYwE0ASYBYwEmAVwBNgE1ASgBNgEoASYBNQE3ASoBNQEqASgBNwE4ASwBNwEsASoBOAE5AS4BOAEuASwBOQE6ATABOQEwAS4BOgE7ATIBOgEyATABOwE8ATQBOwE0ATIBPAE2ASYBPAEmATQBRgEkATUBRgE1ATYBJAFOATcBJAE3ATUBTgFPATgBTgE4ATcBTwFQATkBTwE5ATgBUAFRAToBUAE6ATkBUQFSATsBUQE7AToBUgFTATwBUgE8ATsBUwFGATYBUwE2ATwBPgE9ASQBPgEkAUYBPQE/AU4BPQFOASQBPwFAAU8BPwFPAU4BQAFBAVABQAFQAU8BQQFCAVEBQQFRAVABQgFDAVIBQgFSAVEBQwFEAVMBQwFTAVIBRAE+AUYBRAFGAVMBQAE/AVsBQAFbAVoBPQE+AVkBPQFZAVgBPgFEAVcBPgFXAVkBQwFCAVYBQwFWAVUBQQFAAVoBQQFaAVQBPwE9AVgBPwFYAVsBRAFDAVUBRAFVAVcBQgFBAVQBQgFUAVYBMwFjAVwBMwFcASUBMQFiAWMBMQFjATMBLwFhAWIBLwFiATEBLQFgAWEBLQFhAS8BKwFfAWABKwFgAS0BKQFeAV8BKQFfASsBJwFdAV4BJwFeASkBJQFcAV0BJQFdAScBhAFoAXABhAFwAYcBzgHQAdIBzgHSAdYBjAGDAXsBjAF7AY8B2QHbAd8B2QHfAeMBbQF8AXQBbQF0AWUBggFzAWsBggFrAXoB5QHnAesB5QHrAe8BfwGMAY8BfwGPAXcB8gH0AfYB8gH2AfoBZAGEAYcBZAGHAWwBiAFxAZQBiAGUAZABcgGBAZoBcgGaAZUBgAGKAZwBgAGcAZkBigGIAZABigGQAZwBjgF5AaQBjgGkAZ8BeAFpAaYBeAGmAaMBagGGAakBagGpAacBhgGOAZ8BhgGfAakBdgGOAbMBdgGzAa4BjQGFAbUBjQG1AbIBhgFnAbkBhgG5AbYBZgF1Aa0BZgGtAbgBbgGIAcEBbgHBAbwBiQGLAccBiQHHAcIBigF9AckBigHJAcYBfgFvAb0BfgG9AcoBlwGbAQIClwECAgUC4QHeAUYC4QFGAkMCrAGwARYCrAEWAhoC8wHwAVsC8wFbAlgCkwGYAQYCkwEGAvwB4AHcAT4C4AE+AkkCnQGRAQACnQEAAgQC2AHiAUQC2AFEAkACxAHIASoCxAEqAi0CkgGWAf4BkgH+AQEC3QHaAUIC3QFCAj8CwAHFAS4CwAEuAiQCywG+ASgCywEoAiwCqAGrARECqAERAhUC7QHqAVQC7QFUAlECvwHDASYCvwEmAikC/wH9ATEC/wExAjUC/QEHAjkC/QE5AjECBwIDAjcCBwI3AjkCAwL/ATUCAwI1AjcCDQIKAjwCDQI8AkECCwIUAkgCCwJIAj0CEwIPAkUCEwJFAkcCDwINAkECDwJBAkUCGwIYAkoCGwJKAk8CGQIiAlYCGQJWAksCIQIdAlMCIQJTAlUCHQIbAk8CHQJPAlMCJwIlAlkCJwJZAl0CJQIvAmECJQJhAlkCLwIrAl8CLwJfAmECKwInAl0CKwJdAl8C8QH5AV4C8QFeAlwCugGvARwCugEcAh4C9wH1AVoC9wFaAmICsQG0ASACsQEgAhcCzwHMATMCzwEzAjAC+wH4AWMC+wFjAmACtwG7AR8CtwEfAiMCzQHVATYCzQE2AjQC0wHRATIC0wEyAjoC6QHmAVAC6QFQAk0CngGhAQgCngEIAgwC1wHUATsC1wE7AjgC5AHuAVIC5AFSAk4CqgGgAQ4CqgEOAhAC7AHoAUwC7AFMAlcCogGlARICogESAgkCZQLWAmYCZQJmAmQCaAJqAmUCaAJlAmQCZgLVAmcCZgJnAmQCZwJpAmgCZwJoAmQCaAJpAm4CaAJuAmwCagJoAmwCagJsAnECbQKMAm8CbQJvAmsCawJvAnICawJyAnACcAJyAngCcAJ4AnMCZQJqAnECZQJxAnUC1gJlAnUC1gJ1AncCdAJ5AnoCdAJ6AnYCcgJ/AnsCcgJ7AngCfAKAAnoCfAJ6AnkCfAJ+AoECfAKBAoACkQJ9AnsCkQJ7An8CgAKCAoMCgAKDAnoCgAKBAoQCgAKEAoICggKFAoYCggKGAoMCggKEAocCggKHAoUChQKIAokChQKJAoYChQKHAooChQKKAogCiAKLAowCiAKMAokCiAKKAo0CiAKNAosCiwKOAm8CiwJvAowCiwKNAo8CiwKPAo4CjgJ/AnICjgJyAm8CjgKPApECjgKRAn8CkAKSApECkAKRAo8CrwKQAo8CrwKPAo0ClAJ9ApEClAKRApIClgKVApIClgKSApAClQKXApQClQKUApIClgKbApkClgKZApUCmQKcApcCmQKXApUCmQKaAp4CmQKeApwCmgKZApsCmgKbArYCnQKgAqICnQKiApgCnQKfAqECnQKhAqACpAKjAqACpAKgAqECogKgAqMCogKjAqUCpwKmAqMCpwKjAqQCpQKjAqYCpQKmAqgCqgKpAqYCqgKmAqcCqAKmAqkCqAKpAqsCrQKsAqkCrQKpAqoCqwKpAqwCqwKsAq4CtgKbAqwCtgKsAq0CrgKsApsCrgKbApYCrgKWApACrgKQAq8CqwKuAq8CqwKvArACrwKNAooCrwKKArACsQKwAooCsQKKAocCqAKrArACqAKwArECpQKoArECpQKxArICsgKxAocCsgKHAoQCogKlArICogKyArMCsgKEAoECsgKBArMCkwKzAoECkwKBAn4CmAKiArMCmAKzApMCtAK3ArYCtAK2Aq0CzQK0Aq0CzQKtAqoCtwK5ApoCtwKaArYCvQK7ArgCvQK4ArUCuwK+AroCuwK6ArgCvAK7Ar0CvAK9AswCvAK/Ar4CvAK+ArsCvgK/AsMCvgLDAroCvALKAsACvALAAr8CvwLAAsUCvwLFAsMCuQLBAp4CuQKeApoCwgLEAqECwgKhAp8CxALGAqQCxAKkAqECwALKAscCwALHAsUCxgLIAqcCxgKnAqQCygLLAskCygLJAscCvALMAssCvALLAsoCywLMAs4CywLOAskCzAK9ArUCzAK1As4CyALNAqoCyAKqAqcCzwKJAowCzwKMAm0C0QKGAokC0QKJAs8CZwLVAtICZwLSAtACaQJnAtACaQLQAm4C1QJmAtQC1QLUAtIC0wKDAoYC0wKGAtECZgLWAncCZgJ3AtQCdgJ6AoMCdgKDAtMC2wLdAtgC2wLYAtcC2AJJA9kC2ALZAtcC2QJIA9oC2QLaAtcC2gLcAtsC2gLbAtcC2wLcAuEC2wLhAt8C3QLbAt8C3QLfAuQC4AL/AuIC4ALiAt4C3gLiAuUC3gLlAuMC4wLlAusC4wLrAuYC2ALdAuQC2ALkAugCSQPYAugCSQPoAuoC5wLsAu0C5wLtAukC5QLyAu4C5QLuAusC7wLzAu0C7wLtAuwC7wLxAvQC7wL0AvMCBAPwAu4CBAPuAvIC8wL1AvYC8wL2Au0C8wL0AvcC8wL3AvUC9QL4AvkC9QL5AvYC9QL3AvoC9QL6AvgC+AL7AvwC+AL8AvkC+AL6Av0C+AL9AvsC+wL+Av8C+wL/AvwC+wL9AgAD+wIAA/4C/gIBA+IC/gLiAv8C/gIAAwID/gICAwEDAQPyAuUCAQPlAuICAQMCAwQDAQMEA/ICAwMFAwQDAwMEAwIDIgMDAwIDIgMCAwADBwPwAgQDBwMEAwUDCQMIAwUDCQMFAwMDCAMKAwcDCAMHAwUDCQMOAwwDCQMMAwgDDAMPAwoDDAMKAwgDDAMNAxEDDAMRAw8DDQMMAw4DDQMOAykDEAMTAxUDEAMVAwsDEAMSAxQDEAMUAxMDFwMWAxMDFwMTAxQDFQMTAxYDFQMWAxgDGgMZAxYDGgMWAxcDGAMWAxkDGAMZAxsDHQMcAxkDHQMZAxoDGwMZAxwDGwMcAx4DIAMfAxwDIAMcAx0DHgMcAx8DHgMfAyEDKQMOAx8DKQMfAyADIQMfAw4DIQMOAwkDIQMJAwMDIQMDAyIDHgMhAyIDHgMiAyMDIgMAA/0CIgP9AiMDJAMjA/0CJAP9AvoCGwMeAyMDGwMjAyQDGAMbAyQDGAMkAyUDJQMkA/oCJQP6AvcCFQMYAyUDFQMlAyYDJQP3AvQCJQP0AiYDBgMmA/QCBgP0AvECCwMVAyYDCwMmAwYDJwMqAykDJwMpAyADQAMnAyADQAMgAx0DKgMsAw0DKgMNAykDMAMuAysDMAMrAygDLgMxAy0DLgMtAysDLwMuAzADLwMwAz8DLwMyAzEDLwMxAy4DMQMyAzYDMQM2Ay0DLwM9AzMDLwMzAzIDMgMzAzgDMgM4AzYDLAM0AxEDLAMRAw0DNQM3AxQDNQMUAxIDNwM5AxcDNwMXAxQDMwM9AzoDMwM6AzgDOQM7AxoDOQMaAxcDPQM+AzwDPQM8AzoDLwM/Az4DLwM+Az0DPgM/A0EDPgNBAzwDPwMwAygDPwMoA0EDOwNAAx0DOwMdAxoDQgP8Av8CQgP/AuACRAP5AvwCRAP8AkID2gJIA0UD2gJFA0MD3ALaAkMD3AJDA+ECSAPZAkcDSANHA0UDRgP2AvkCRgP5AkQD2QJJA+oC2QLqAkcD6QLtAvYC6QL2AkYDHYXrvgAAADKCwuM9HYXrvgAAADKCwuM9HYXrvgAAADLizN69HYXrvgAAADLizN69nfvLvgAAADKCwuM9nfvLvgAAADKCwuM9nfvLvgAAADLizN69nfvLvgAAADLizN69HYXrvgAAAACsqry9HYXrvgAAAABsoME9nfvLvgAAADIsG0Q9nfvLvgAAADIsG0Q9nfvLvgAAADLsLzq9nfvLvgAAADLsLzq9XI/CvgAAADIsG0Q9XI/CvgAAADIsG0Q9XI/CvgAAADLsLzq9XI/CvgAAADLsLzq9XI/CvlC4/j/sLzq9XI/CvlC4/j/sLzq9XI/CvlC4/j/sLzq9XI/CvlC4/j/sLzq9XI/CvlC4/j8sG0Q9XI/CvlC4/j8sG0Q9XI/CvlC4/j8sG0Q9XI/CvlC4/j8sG0Q9fEXHvlDw/z9ywuM9fEXHvlDw/z9ywuM9fEXHvlDw/z9ywuM9fEXHvlDw/z9ywuM9HoXrvrgeBUBMoME9HoXrvrgeBUBMoME9HoXrvrgeBUBywuM9HoXrvrgeBUBywuM9HoXrvrgeBUBywuM9HoXrvrgeBUBywuM9fEXHvlDw/z8MG0Q9fEXHvlDw/z8MG0Q9fEXHvlDw/z8MG0Q9fEXHvlDw/z8MG0Q9fEXHvlDw/z/yzN69fEXHvlDw/z/yzN69fEXHvlDw/z/yzN69fEXHvlDw/z/yzN69fEXHvlDw/z8MMDq9fEXHvlDw/z8MMDq9fEXHvlDw/z8MMDq9fEXHvlDw/z8MMDq9HoXrvrgeBUDMqry9HoXrvrgeBUDMqry9HoXrvrgeBUDyzN69HoXrvrgeBUDyzN69HoXrvrgeBUDyzN69HoXrvrgeBUDyzN69HYXrPgAAADKCwuM9HYXrPgAAADKCwuM9HYXrPgAAADLizN69HYXrPgAAADLizN69nfvLPgAAADKCwuM9nfvLPgAAADKCwuM9nfvLPgAAADLizN69nfvLPgAAADLizN69HYXrPgAAAACsqry9HYXrPgAAAABsoME9nfvLPgAAADIsG0Q9nfvLPgAAADIsG0Q9nfvLPgAAADLsLzq9nfvLPgAAADLsLzq9XI/CPgAAADIsG0Q9XI/CPgAAADIsG0Q9XI/CPgAAADLsLzq9XI/CPgAAADLsLzq9XI/CPlC4/j/sLzq9XI/CPlC4/j/sLzq9XI/CPlC4/j/sLzq9XI/CPlC4/j/sLzq9XI/CPlC4/j8sG0Q9XI/CPlC4/j8sG0Q9XI/CPlC4/j8sG0Q9XI/CPlC4/j8sG0Q9fEXHPlDw/z9ywuM9fEXHPlDw/z9ywuM9fEXHPlDw/z9ywuM9fEXHPlDw/z9ywuM9HoXrPrgeBUBMoME9HoXrPrgeBUBMoME9HoXrPrgeBUBywuM9HoXrPrgeBUBywuM9HoXrPrgeBUBywuM9HoXrPrgeBUBywuM9fEXHPlDw/z8MG0Q9fEXHPlDw/z8MG0Q9fEXHPlDw/z8MG0Q9fEXHPlDw/z8MG0Q9fEXHPlDw/z/yzN69fEXHPlDw/z/yzN69fEXHPlDw/z/yzN69fEXHPlDw/z/yzN69fEXHPlDw/z8MMDq9fEXHPlDw/z8MMDq9fEXHPlDw/z8MMDq9fEXHPlDw/z8MMDq9HoXrPrgeBUDMqry9HoXrPrgeBUDMqry9HoXrPrgeBUDyzN69HoXrPrgeBUDyzN69HoXrPrgeBUDyzN69HoXrPrgeBUDyzN69HoXrPoYycj9coME9HoXrPoYycj9coME9XI/CPgLliD/sLzq9XI/CPgLliD/sLzq9XI/CPgLliD/sLzq9XI/CPgLliD8sG0Q9XI/CPgLliD8sG0Q9XI/CPgLliD8sG0Q9jKDJPjQ5hz8cG0Q9jKDJPjQ5hz8cG0Q9jKDJPjQ5hz8cG0Q9jKDJPjQ5hz8cG0Q9jKDJPjQ5hz/8Lzq9jKDJPjQ5hz/8Lzq9jKDJPjQ5hz/8Lzq9jKDJPjQ5hz/8Lzq9HoXrPoYycj+8qry9HoXrPoYycj+8qry9jKDJPjQ5hz96wuM9jKDJPjQ5hz96wuM9jKDJPjQ5hz96wuM9jKDJPjQ5hz96wuM9HoXrPoYycj96wuM9HoXrPoYycj96wuM9HoXrPoYycj96wuM9HoXrPoYycj96wuM9jKDJPjQ5hz/qzN69jKDJPjQ5hz/qzN69jKDJPjQ5hz/qzN69jKDJPjQ5hz/qzN69HoXrPoYycj/qzN69HoXrPoYycj/qzN69HoXrPoYycj/qzN69HoXrPoYycj/qzN69HoXrvoYycj9coME9HoXrvoYycj9coME9XI/CvgLliD/sLzq9XI/CvgLliD/sLzq9XI/CvgLliD/sLzq9XI/CvgLliD8sG0Q9XI/CvgLliD8sG0Q9XI/CvgLliD8sG0Q9jKDJvjQ5hz8cG0Q9jKDJvjQ5hz8cG0Q9jKDJvjQ5hz8cG0Q9jKDJvjQ5hz8cG0Q9jKDJvjQ5hz/8Lzq9jKDJvjQ5hz/8Lzq9jKDJvjQ5hz/8Lzq9jKDJvjQ5hz/8Lzq9HoXrvoYycj+8qry9HoXrvoYycj+8qry9jKDJvjQ5hz96wuM9jKDJvjQ5hz96wuM9jKDJvjQ5hz96wuM9jKDJvjQ5hz96wuM9HoXrvoYycj96wuM9HoXrvoYycj96wuM9HoXrvoYycj96wuM9HoXrvoYycj96wuM9jKDJvjQ5hz/qzN69jKDJvjQ5hz/qzN69jKDJvjQ5hz/qzN69jKDJvjQ5hz/qzN69HoXrvoYycj/qzN69HoXrvoYycj/qzN69HoXrvoYycj/qzN69HoXrvoYycj/qzN69BHPIPhhkyD92wuM9BHPIPhhkyD92wuM9BHPIPhhkyD92wuM9BHPIPhhkyD92wuM9HoXrPs+yuT92wuM9HoXrPs+yuT92wuM9HoXrPs+yuT92wuM9BHPIPhhkyD/uzN69BHPIPhhkyD/uzN69BHPIPhhkyD/uzN69BHPIPhhkyD/uzN69HoXrPs+yuT/uzN69HoXrPs+yuT/uzN69HoXrPs+yuT/uzN69HoXrPs+yuT9UoME9XI/CPhchyj/sLzq9XI/CPhchyj/sLzq9XI/CPhchyj/sLzq9XI/CPhchyj8sG0Q9XI/CPhchyj8sG0Q9XI/CPhchyj8sG0Q9BHPIPhhkyD8UG0Q9BHPIPhhkyD8UG0Q9BHPIPhhkyD8UG0Q9BHPIPhhkyD8UG0Q9BHPIPhhkyD8EMDq9BHPIPhhkyD8EMDq9BHPIPhhkyD8EMDq9BHPIPhhkyD8EMDq9HoXrPs+yuT/Eqry9BHPIvhhkyD92wuM9BHPIvhhkyD92wuM9BHPIvhhkyD92wuM9BHPIvhhkyD92wuM9HoXrvs+yuT92wuM9HoXrvs+yuT92wuM9HoXrvs+yuT92wuM9BHPIvhhkyD/uzN69BHPIvhhkyD/uzN69BHPIvhhkyD/uzN69BHPIvhhkyD/uzN69HoXrvs+yuT/uzN69HoXrvs+yuT/uzN69HoXrvs+yuT/uzN69HoXrvs+yuT9UoME9XI/Cvhchyj/sLzq9XI/Cvhchyj/sLzq9XI/Cvhchyj/sLzq9XI/Cvhchyj8sG0Q9XI/Cvhchyj8sG0Q9XI/Cvhchyj8sG0Q9BHPIvhhkyD8UG0Q9BHPIvhhkyD8UG0Q9BHPIvhhkyD8UG0Q9BHPIvhhkyD8UG0Q9BHPIvhhkyD8EMDq9BHPIvhhkyD8EMDq9BHPIvhhkyD8EMDq9BHPIvhhkyD8EMDq9HoXrvs+yuT/Eqry9QNzHPp+S6z90wuM9QNzHPp+S6z90wuM9QNzHPp+S6z90wuM9QNzHPp+S6z90wuM9HoXrPoCx1T90wuM9HoXrPoCx1T90wuM9HoXrPoCx1T90wuM9HoXrPoCx1T90wuM9QNzHPp+S6z/wzN69QNzHPp+S6z/wzN69QNzHPp+S6z/wzN69QNzHPp+S6z/wzN69HoXrPoCx1T/wzN69HoXrPoCx1T/wzN69HoXrPoCx1T/wzN69HoXrPoCx1T9QoME9HoXrPoCx1T9QoME9XI/CPkwp7j/sLzq9XI/CPkwp7j/sLzq9XI/CPkwp7j/sLzq9XI/CPkwp7j8sG0Q9XI/CPkwp7j8sG0Q9XI/CPkwp7j8sG0Q9QNzHPp+S6z8QG0Q9QNzHPp+S6z8QG0Q9QNzHPp+S6z8QG0Q9QNzHPp+S6z8QG0Q9QNzHPp+S6z8IMDq9QNzHPp+S6z8IMDq9QNzHPp+S6z8IMDq9QNzHPp+S6z8IMDq9HoXrPoCx1T/Iqry9QNzHvp+S6z90wuM9QNzHvp+S6z90wuM9QNzHvp+S6z90wuM9QNzHvp+S6z90wuM9HoXrvoCx1T90wuM9HoXrvoCx1T90wuM9HoXrvoCx1T90wuM9QNzHvp+S6z/wzN69QNzHvp+S6z/wzN69QNzHvp+S6z/wzN69QNzHvp+S6z/wzN69HoXrvoCx1T/wzN69HoXrvoCx1T/wzN69HoXrvoCx1T/wzN69HoXrvoCx1T9QoME9XI/Cvkwp7j/sLzq9XI/Cvkwp7j/sLzq9XI/Cvkwp7j/sLzq9XI/Cvkwp7j8sG0Q9XI/Cvkwp7j8sG0Q9XI/Cvkwp7j8sG0Q9QNzHvp+S6z8QG0Q9QNzHvp+S6z8QG0Q9QNzHvp+S6z8QG0Q9QNzHvp+S6z8QG0Q9QNzHvp+S6z8IMDq9QNzHvp+S6z8IMDq9QNzHvp+S6z8IMDq9QNzHvp+S6z8IMDq9HoXrvoCx1T/Iqry9AACAv2xLB7MAAACA2zdds4tEfjMAAIA/AACAv21LB7MAAACA2zddMxMVh7MAAIC/2zdds4tEfjMAAIA/Yv9/Pya2jrsAAACA2zddMxMVh7MAAIC/Yv9/PyW2jrsAAACAAACAv21LB7MAAACAAACAv2xLB7MAAACASiP6tawtEjMAAIA/Yv9/Pya2jrsAAACASiP6NawtErMAAIC/Yv9/PyW2jrsAAACASiP6tawtEjMAAIA/AACAPwAAAAAAAACASiP6NawtErMAAIC/AACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAACVGRLcAAIC/SqtAN8wU3rMAAIC/AACAPwAAAAAAAACAOatAty2qsTP//38/AAAAAAAAgL8AAACAAAAAAMLOTDcAAIA/AACAPwAAAAAAAACARekQs1NBSDMAAIA/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/Sf5/P1Hi7LsAAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACARekQs1NBSDMAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAOatAty2qsTP//38/AAAAAAAAgL8AAACAAAAAAMLOTDcAAIA/Sf5/P1Hi7LsAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/R+kQM+aLhrMAAIC/Sf5/P1Pi7LsAAACAAAAAAAAAgL8AAACAAAAAACVGRLcAAIC/SqtAN8wU3rMAAIC/Sf5/P1Pi7LsAAACAAACAvwAAAAAAAACAAAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAR+kQM+aLhrMAAIC/2zddM2aWejMAAIA/AACAP2xLB7NQYmm02zdds+eKjbMAAIC/AACAP21LB7MAAACAYv9/vya2jrsAAACA2zddM2aWejMAAIA/Yv9/vyW2jrsAAACA2zdds+eKjbMAAIC/AACAP21LB7MAAACAAACAP2xLB7NQYmm0Yv9/vya2jrsAAACASiP6NawtEjMAAIA/Yv9/vyW2jrsAAACASiP6tawtErMAAIC/AACAvwAAAAAAAACASiP6NawtEjMAAIA/AACAvwAAAAAAAACASiP6tawtErMAAIC/AACAvwAAAAAAAACAOatAt7gU3rP//3+/AAAAAAAAgL8AAACAAAAAACVGRLcAAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAMLOTDcAAIA/SqtANz2qsTMAAIA/Sv5/v0/i7LsAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/R+kQM6xRejMAAIA/AAAAAAAAgD8AAACA//9/PwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAR+kQM6xRejMAAIA///9/PwAAAAAAAACASv5/v0/i7LsAAACAAAAAAAAAgL8AAACAAAAAAMLOTDcAAIA/SqtANz2qsTMAAIA/Sv5/v1Hi7LsAAACARekQs/4wlrMAAIC/AAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/Sv5/v1Hi7LsAAACAOatAt7gU3rP//3+/AAAAAAAAgL8AAACAAAAAACVGRLcAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACARekQs/4wlrMAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAP2xLB7NQYmm0AACAPwAAAAAAAACAAACAvwAAAAAAAACAixfHtrWGG7MAAIC/SiP6tawtErMAAIC/AACAvwAAAAAAAACASiP6NawtEjMAAIA/fhfHNlZoQjMAAIA/Yv9/vya2jrsAAACAVv9/vyEQlLsAAACASiP6NawtEjMAAIA/fhfHNlZoQjMAAIA/Yv9/vyW2jrsAAACAVv9/vyEQlLsAAACAixfHtrWGG7MAAIC/SiP6tawtErMAAIC/AACAP21LB7MAAACAAACAPwAAAAAAAACAYv9/vya2jrsAAACAVv9/vyEQlLsAAACA2zddM2aWejMAAIA/2dXTMwJCdjMAAIA/2zddM2aWejMAAIA/2dXTMwJCdjMAAIA/AACAP2xLB7NQYmm0AACAPwAAAAAAAACAYv9/vyW2jrsAAACAVv9/vyEQlLsAAACA39XTswkFk7MAAIC/2zdds+eKjbMAAIC/39XTswkFk7MAAIC/2zdds+eKjbMAAIC/AACAP21LB7MAAACAAACAPwAAAAAAAACAAACAv2xLB7MAAACAAACAvwAAAAAAAACASiP6NawtErMAAIC/fhfHNquGG7MAAIC/AACAPwAAAAAAAACAixfHtmJoQjMAAIA/SiP6tawtEjMAAIA/AACAPwAAAAAAAACAixfHtmJoQjMAAIA/SiP6tawtEjMAAIA/Vv9/PyEQlLsAAACAYv9/Pya2jrsAAACASiP6NawtErMAAIC/fhfHNquGG7MAAIC/Vv9/PyEQlLsAAACAYv9/PyW2jrsAAACAAACAv21LB7MAAACAAACAvwAAAAAAAACA39XTs0I7azMAAIA/2zdds4tEfjMAAIA/Vv9/PyEQlLsAAACAYv9/Pya2jrsAAACAAACAv2xLB7MAAACAAACAvwAAAAAAAACA39XTs0I7azMAAIA/2zdds4tEfjMAAIA/2zddMxMVh7MAAIC/2dXTM8gnhrMAAIC/Vv9/PyEQlLsAAACAYv9/PyW2jrsAAACAAACAv21LB7MAAACAAACAvwAAAAAAAACA2zddMxMVh7MAAIC/2dXTM8gnhrMAAIC/bf9/vykhibsAAACAVv9/vyEQlLsAAACA2dXTMwJCdjMAAIA//70GNOo2lTMAAIA/2dXTMwJCdjMAAIA//70GNOo2lTMAAIA/AACAPwAAAAAAAACAbf9/vyshibsAAACAVv9/vyEQlLsAAACA/70GtLjLa7MAAIC/39XTswkFk7MAAIC//70GtLjLa7MAAIC/39XTswkFk7MAAIC/AACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAKuIVt2iFd7MAAIC/ixfHtrWGG7MAAIC/AACAvwAAAAAAAACAfhfHNlZoQjMAAIA/HeIVN1uFdzP//38/bf9/vykhibsAAACAVv9/vyEQlLsAAACAfhfHNlZoQjMAAIA/HeIVN1uFdzP//38/bf9/vyshibsAAACAVv9/vyEQlLsAAACAKuIVt2iFd7MAAIC/ixfHtrWGG7MAAIC/AACAPwAAAAAAAACA/70GtMrYsDMAAIA/39XTs0I7azMAAIA/Vv9/PyEQlLsAAACAbP9/PyUhibsAAACAAACAvwAAAAAAAACA/70GtMrYsDMAAIA/39XTs0I7azMAAIA/2dXTM8gnhrMAAIC//70GNBKjSrMAAIC/Vv9/PyEQlLsAAACAbP9/PychibsAAACAAACAvwAAAAAAAACA2dXTM8gnhrMAAIC//70GNBKjSrMAAIC/AACAvwAAAAAAAACAfhfHNquGG7MAAIC/HeIVN3ZETrP//3+/AACAPwAAAAAAAACAKuIVt2iFdzMAAIA/ixfHtmJoQjMAAIA/AACAPwAAAAAAAACAKuIVt2iFdzMAAIA/ixfHtmJoQjMAAIA/Vv9/PyEQlLsAAACAbP9/PyUhibsAAACAfhfHNquGG7MAAIC/HeIVN3ZETrP//3+/Vv9/PyEQlLsAAACAbP9/PychibsAAACAAACAvwAAAAAAAACAbf9/vykhibsAAACASv5/v0/i7LsAAACAR+kQM6xRejMAAIA//70GNOo2lTMAAIA/R+kQM6xRejMAAIA//70GNOo2lTMAAIA///9/PwAAAAAAAACAAACAPwAAAAAAAACAbf9/vyshibsAAACASv5/v1Hi7LsAAACA/70GtLjLa7MAAIC/RekQs/4wlrMAAIC//70GtLjLa7MAAIC/RekQs/4wlrMAAIC/AACAPwAAAAAAAACA//9/PwAAAAAAAACAAACAPwAAAAAAAACAAACAvwAAAAAAAACAOatAt7gU3rP//3+/KuIVt2iFd7MAAIC/AACAvwAAAAAAAACAHeIVN1uFdzP//38/SqtANz2qsTMAAIA/bf9/vykhibsAAACASv5/v0/i7LsAAACAHeIVN1uFdzP//38/SqtANz2qsTMAAIA/bf9/vyshibsAAACASv5/v1Hi7LsAAACAOatAt7gU3rP//3+/KuIVt2iFd7MAAIC/AACAPwAAAAAAAACA/70GtMrYsDMAAIA/RekQs1NBSDMAAIA/Sf5/P1Hi7LsAAACAbP9/PyUhibsAAACAAACAvwAAAAAAAACA/70GtMrYsDMAAIA/RekQs1NBSDMAAIA/R+kQM+aLhrMAAIC//70GNBKjSrMAAIC/Sf5/P1Pi7LsAAACAbP9/PychibsAAACAAACAvwAAAAAAAACAR+kQM+aLhrMAAIC//70GNBKjSrMAAIC/AACAvwAAAAAAAACAHeIVN3ZETrP//3+/SqtAN8wU3rMAAIC/AACAPwAAAAAAAACAOatAty2qsTP//38/KuIVt2iFdzMAAIA/AACAPwAAAAAAAACAOatAty2qsTP//38/KuIVt2iFdzMAAIA/Sf5/P1Hi7LsAAACAbP9/PyUhibsAAACAHeIVN3ZETrP//3+/SqtAN8wU3rMAAIC/Sf5/P1Pi7LsAAACAbP9/PychibsAAACAAACAvwAAAAAAAACA4LxAP+avAD/gvEA/5q8AP0LVOz/nrwA/QtU7P+evAD/VwD8/5q8AP9XAPz/mrwA/TNE8P+evAD9M0Tw/568AP3yjOz/nrwA/pu5AP+evAD+zAz8/5q8AP7MDPz/mrwA/cY49P+evAD9xjj0/568AP3DUPj/mrwA/cNQ+P+avAD+yvT0/568AP7K9PT/nrwA/5to9P2z8GD/m2j0/bPwYP+baPT9s/Bg/5to9P2z8GD/Ruj4/avkYP9G6Pj9q+Rg/0bo+P2r5GD/Ruj4/avkYP4G3Pz9+8Bg/gbc/P37wGD+Btz8/fvAYP4G3Pz9+8Bg/pe5AP85bGT+l7kA/zlsZPwKcQD9qsBY/ApxAP2qwFj8CnEA/arAWPwKcQD9qsBY/xgI/P7cDGT/GAj8/twMZP8YCPz+3Axk/xgI/P7cDGT+a3jw/pPkYP5rePD+k+Rg/mt48P6T5GD+a3jw/pPkYP9KSPT8RBxk/0pI9PxEHGT/Skj0/EQcZP9KSPT8RBxk/fKM7P85bGT98ozs/zlsZP//1Oz/qrhY///U7P+quFj//9Ts/6q4WP//1Oz/qrhY/4LxAPwIpTD/gvEA/AilMP0PVOz8CKUw/Q9U7PwIpTD/WwD8/AilMP9bAPz8CKUw/TNE8PwIpTD9M0Tw/AilMP3yjOz8CKUw/pu5APwIpTD+yAz8/AilMP7IDPz8CKUw/cY49PwIpTD9xjj0/AilMP3DUPj8CKUw/cNQ+PwIpTD+yvT0/AilMP7K9PT8CKUw/S9c9P65+Mz9L1z0/rn4zP0vXPT+ufjM/S9c9P65+Mz8mtz4/XHgzPya3Pj9ceDM/Jrc+P1x4Mz8mtz4/XHgzP3+zPz/cejM/f7M/P9x6Mz9/sz8/3HozP3+zPz/cejM/pu5AP44XMz+m7kA/jhczPxKcQD80xDU/EpxAPzTENT8SnEA/NMQ1PxKcQD80xDU/R/8+P55tMz9H/z4/nm0zP0f/Pj+ebTM/R/8+P55tMz+t2jw/noYzP63aPD+ehjM/rdo8P56GMz+t2jw/noYzP1iPPT8ydDM/WI89PzJ0Mz9Yjz0/MnQzP1iPPT8ydDM/fKM7P44XMz98ozs/jhczPy/2Oz9YwzU/L/Y7P1jDNT8v9js/WMM1Py/2Oz9YwzU/pu5AP0igPz+m7kA/SKA/P37KPT/Y0z8/fso9P9jTPz9+yj0/2NM/P8vFPj+v0D8/y8U+P6/QPz/LxT4/r9A/P3wBPz9Qyz8/fAE/P1DLPz98AT8/UMs/P3wBPz9Qyz8/5I49P5rOPz/kjj0/ms4/P+SOPT+azj8/5I49P5rOPz98ozs/SKA/P3yjOz9IoD8/Kro/P+/RPz8quj8/79E/Pyq6Pz/v0T8/Kro/P+/RPz95rEA/m/ZAP3msQD+b9kA/eaxAP5v2QD95rEA/m/ZAP/zVPD/Q1z8//NU8P9DXPz/81Tw/0Nc/P/zVPD/Q1z8/ueU7Py32QD+55Ts/LfZAP7nlOz8t9kA/ueU7Py32QD+m7kA/2wUNP6buQD/bBQ0/TMw9PyrWDD9MzD0/KtYMP0zMPT8q1gw/oMc+P6jUDD+gxz4/qNQMP6DHPj+o1Aw/PAM/P8/ZDD88Az8/z9kMPzwDPz/P2Qw/PAM/P8/ZDD+ikD0/fNsMP6KQPT982ww/opA9P3zbDD+ikD0/fNsMP3yjOz/bBQ0/fKM7P9sFDT8rvD8/MtAMPyu8Pz8y0Aw/K7w/PzLQDD8rvD8/MtAMP3GsQD8osAs/caxAPyiwCz9xrEA/KLALP3GsQD8osAs/89c8P8bUDD/z1zw/xtQMP/PXPD/G1Aw/89c8P8bUDD+g5Ts/aK8LP6DlOz9orws/oOU7P2ivCz+g5Ts/aK8LP9S2Pz9mpjk/1LY/P2amOT/Utj8/ZqY5P9S2Pz9mpjk/RqRAP2hdOz9GpEA/aF07P0akQD9oXTs/VNg8PzavOT9U2Dw/Nq85P1TYPD82rzk/VNg8PzavOT/07Ts/w1w7P/TtOz/DXDs/9O07P8NcOz+m7kA/61s5P+TQPT9CqTk/5NA9P0KpOT/k0D0/Qqk5P3i+Pj+GpDk/eL4+P4akOT94vj4/hqQ5P2IAPz92nDk/YgA/P3acOT9iAD8/dpw5P2IAPz92nDk/Ho89P2ahOT8ejz0/ZqE5Px6PPT9moTk/Ho89P2ahOT98ozs/61s5P9a5Pz9Y4BI/1rk/P1jgEj/WuT8/WOASP9a5Pz9Y4BI/OqRAP0kwET86pEA/STARPzqkQD9JMBE/Rts8PzXnEj9G2zw/NecSP0bbPD815xI/Rts8PzXnEj/Q7Ts/KS8RP9DtOz8pLxE/0O07PykvET+m7kA/1TATP5nTPT9L6RI/mdM9P0vpEj+Z0z0/S+kSPzjBPj8J5xI/OME+PwnnEj84wT4/CecSPwEDPz/D7hI/AQM/P8PuEj8BAz8/w+4SPwEDPz/D7hI/upE9P0bxEj+6kT0/RvESP7qRPT9G8RI/upE9P0bxEj98ozs/1TATPyq1Pz+hkDY/KrU/P6GQNj8qtT8/oZA2Pyq1Pz+hkDY/LKBAP86QOD8soEA/zpA4PyygQD/OkDg/LKBAP86QOD+A2Tw/6po2P4DZPD/qmjY/gNk8P+qaNj+A2Tw/6po2PxLyOz8OkDg/EvI7Pw6QOD8S8js/DpA4P6buQD+8OTY/pu5AP7w5Nj8Y1D0/+JM2PxjUPT/4kzY/GNQ9P/iTNj/Puj4/cY42P8+6Pj9xjjY/z7o+P3GONj/U/z4/CoU2P9T/Pj8KhTY/1P8+PwqFNj/U/z4/CoU2PzuPPT/MijY/O489P8yKNj87jz0/zIo2PzuPPT/MijY/fKM7P7w5Nj+suD8/a+gVP6y4Pz9r6BU/rLg/P2voFT+suD8/a+gVPx6gQD9a8BM/HqBAP1rwEz8eoEA/WvATP/DcPD9t8BU/8Nw8P23wFT/w3Dw/bfAVP/DcPD9t8BU/6PE7PwrvEz/o8Ts/Cu8TP+jxOz8K7xM/pu5AP1JGFj9A1z0/3PIVP0DXPT/c8hU/QNc9P9zyFT8Evj4/OvAVPwS+Pj868BU/BL4+PzrwFT/kAj8/PfkVP+QCPz89+RU/5AI/Pz35FT/kAj8/PfkVP0aSPT8s/BU/RpI9Pyz8FT9Gkj0/LPwVP0aSPT8s/BU/fKM7P1JGFj8YATUAKgAYASoAEwEVASsALwAVAS8AJwENARoAIQANASEAEgEQASAAHgAQAR4AGgEYAE4AXAAYAFwAJgDtAFAAWgDtAFoABAEoAGAAZAAoAGQALAAmAS4AFAAmARQAHAFhACkAMwBhADMAaQCUAJcACgCUAAoADgAdARUAGQAdARkAIAFWACIAHABWABwAUgAjAFcAVAAjAFQAHwD6AGsAZwD6AGcACwFNABcAEgBNABIASgBRABsAJQBRACUAWwBLABMALQBLAC0AZQD3AF8AaAD3AGgA+QAIAWIAXgAIAV4A9QDwAFgAUwDwAFMA7gBsAIQANwBsADcAPwBqADQAMQBqADEAZgBwAHsAQwBwAEMARwAGAV0ATwAGAU8AAgH9AAABTAD9AEwASAAjAScAHQAjAR0ADgEpATAAMgApATIAFwFGAEQAcQBGAHEAbgBBAHYAcgBBAHIARQDBAMwAegDBAHoAbwC+ALYAhQC+AIUAbQA2AIIAgAA2AIAAOwBCAHgAhgBCAIYAPAA9AIkAiwA9AIsAOAA5AIwAfAA5AHwAPgA6AH4AdAA6AHQAQAAIAJ4ArAAIAKwAAgALAJkAowALAKMABQARAJIAlQARAJUADwDhAOQAlgDhAJYAkwAMAJoAkAAMAJAAEAAAAKQAjgAAAI4ACQAEAKEApwAEAKcAAQAHAKsAnQAHAJ0ADQADAK4AqAADAKgABgD8APMAtgD8ALYAvgD/AAoBywD/AMsAwABuAHEAwgBuAMIAvwB3AMcAwwB3AMMAcwCDALQAsgCDALIAgQB5AMoAuAB5ALgAhwCIALoAvACIALwAigCNAL0AzQCNAM0AfQB/ALEAxgB/AMYAdQAfASIB4wAfAeMA4ACfAOsA2QCfANkArQCYAOUA0ACYANAAogCSAN8A4gCSAOIAlQCbAOcA3QCbAN0AkQClANIA3AClANwAjwCgAM8A1ACgANQApgCqANcA6QCqAOkAnACvANoA1QCvANUAqQBJAGMACQFJAAkB/gBVAFkA8gBVAPIA+wC/AMIAAAG/AAAB/QDIAAUBAQHIAAEBxAC1APEA7wC1AO8AswDJAAcB9ADJAPQAtwC5APYA+AC5APgAuwC9APoACwG9AAsBzQCwAOwAAwGwAAMBxQAWACQAIQEWACEBHgHrACkBFwHrABcB2QDmACQBDwHmAA8B0QDfAB0BIAHfACAB4gDoACUBGwHoABsB3gDSABABGgHSABoB3ADOAAwBEQHOABEB0wDYABYBKAHYACgB6gDbABkBFAHbABQB1gA=" + } + ] +} diff --git a/Assets/Models/HouseModular.shmodel b/Assets/Models/HouseModular.shmodel new file mode 100644 index 00000000..4fb25c50 Binary files /dev/null and b/Assets/Models/HouseModular.shmodel differ diff --git a/Assets/Models/HouseModular.shmodel.shmeta b/Assets/Models/HouseModular.shmodel.shmeta new file mode 100644 index 00000000..936b3fa9 --- /dev/null +++ b/Assets/Models/HouseModular.shmodel.shmeta @@ -0,0 +1,55 @@ +Name: HouseModular +ID: 75328301 +Type: 4 +Sub Assets: +Name: FloorLarge +ID: 142812576 +Type: 8 +Name: FloorSmall +ID: 139921228 +Type: 8 +Name: FloorLong +ID: 136991843 +Type: 8 +Name: Pillar +ID: 150352316 +Type: 8 +Name: WallEnd +ID: 139594893 +Type: 8 +Name: WallCorner +ID: 134714737 +Type: 8 +Name: WallDefault +ID: 140834166 +Type: 8 +Name: WallLarge +ID: 142689599 +Type: 8 +Name: WallDiagonal +ID: 144002377 +Type: 8 +Name: WallTBlock +ID: 149359798 +Type: 8 +Name: WindowLarge +ID: 148351779 +Type: 8 +Name: WindowSmallOpened +ID: 149786048 +Type: 8 +Name: WindowSmallClosed +ID: 147863396 +Type: 8 +Name: WindowLargeOpen +ID: 138781993 +Type: 8 +Name: WallDoorHole +ID: 150924328 +Type: 8 +Name: Door +ID: 147152385 +Type: 8 +Name: DoorFrame +ID: 146862321 +Type: 8 diff --git a/Assets/Models/racoon.fbx b/Assets/Models/racoon.fbx new file mode 100644 index 00000000..4d823d9d Binary files /dev/null and b/Assets/Models/racoon.fbx differ diff --git a/Assets/Models/racoon.gltf b/Assets/Models/racoon.gltf new file mode 100644 index 00000000..459a542d --- /dev/null +++ b/Assets/Models/racoon.gltf @@ -0,0 +1,4993 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v3.3.27", + "version" : "2.0" + }, + "extensionsUsed" : [ + "KHR_materials_specular", + "KHR_materials_ior" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 55 + ] + } + ], + "nodes" : [ + { + "name" : "L_Toe_end", + "rotation" : [ + -1.304514398725587e-07, + -4.8278069232242024e-14, + -3.113858042524953e-07, + 1 + ], + "translation" : [ + 2.9270432744255004e-09, + 0.02392714098095894, + 1.3476908478082805e-10 + ] + }, + { + "children" : [ + 0 + ], + "name" : "L_Toe", + "rotation" : [ + 0.32702386379241943, + 1.1310142156162328e-07, + 1.641405731334089e-07, + 0.945016086101532 + ], + "scale" : [ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation" : [ + -8.650776095464607e-09, + 0.03380582109093666, + -2.448857117087755e-09 + ] + }, + { + "children" : [ + 1 + ], + "name" : "L_Feet", + "rotation" : [ + 0.516292929649353, + -0.020581310614943504, + -0.05452270060777664, + 0.854426920413971 + ], + "translation" : [ + 1.2865877252465907e-09, + 0.06353945285081863, + 2.6193447411060333e-10 + ] + }, + { + "children" : [ + 2 + ], + "name" : "L_Shin", + "rotation" : [ + -0.054226718842983246, + 0.00034972387948073447, + -0.0027083493769168854, + 0.9985249042510986 + ], + "scale" : [ + 0.9999998807907104, + 0.9999999403953552, + 0.9999998807907104 + ], + "translation" : [ + -8.217813984856548e-09, + 0.012935775332152843, + -1.1059455573558807e-09 + ] + }, + { + "children" : [ + 3 + ], + "name" : "L_Knee", + "rotation" : [ + -0.117364302277565, + -0.00023353073629550636, + -0.005353146698325872, + 0.9930744767189026 + ], + "scale" : [ + 1, + 1.0000001192092896, + 1 + ], + "translation" : [ + -7.161837345392996e-09, + 0.08009886741638184, + -3.725290298461914e-09 + ] + }, + { + "children" : [ + 4 + ], + "name" : "L_Thigh", + "rotation" : [ + 0.005340703763067722, + -0.08032803982496262, + -0.9945576786994934, + 0.06613556295633316 + ], + "scale" : [ + 1.0000009536743164, + 1.0000001192092896, + 1.0000014305114746 + ], + "translation" : [ + 0.06634333729743958, + 0.021777987480163574, + -0.000205356627702713 + ] + }, + { + "name" : "Head_end", + "rotation" : [ + 0, + 3.552713678800501e-15, + 0, + 1 + ], + "translation" : [ + -8.470329472543003e-22, + 0.11583378911018372, + 0 + ] + }, + { + "children" : [ + 6 + ], + "name" : "Head", + "rotation" : [ + 0, + 5.960462701182223e-08, + 0, + 1 + ], + "scale" : [ + 1, + 0.9999999403953552, + 1 + ], + "translation" : [ + 0, + 0.022377878427505493, + 0 + ] + }, + { + "children" : [ + 7 + ], + "name" : "Neck", + "translation" : [ + 0, + 0.10304805636405945, + 0 + ] + }, + { + "name" : "L_Hand_end", + "rotation" : [ + 1.3239958462918366e-08, + -2.4324227076988336e-09, + 1.4901161193847656e-08, + 1 + ], + "translation" : [ + 2.2351740014414645e-08, + 0.016836093738675117, + -5.329070518200751e-15 + ] + }, + { + "children" : [ + 9 + ], + "name" : "L_Hand", + "rotation" : [ + -0.10859407484531403, + -0.0013414795976132154, + -0.012280543334782124, + 0.9940094351768494 + ], + "scale" : [ + 1, + 1, + 0.9999999403953552 + ], + "translation" : [ + -5.215407838932151e-08, + 0.030574528500437737, + 4.579678858362968e-09 + ] + }, + { + "children" : [ + 10 + ], + "name" : "L_Forearm", + "rotation" : [ + 0.03182216361165047, + -0.010124370455741882, + -0.05386859551072121, + 0.9979895353317261 + ], + "scale" : [ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation" : [ + -1.4001724224499412e-08, + 0.011892830953001976, + -4.656612873077393e-10 + ] + }, + { + "children" : [ + 11 + ], + "name" : "L_Elbow", + "rotation" : [ + 0.13403145968914032, + 0.0004466302052605897, + 0.0229647234082222, + 0.9907108545303345 + ], + "translation" : [ + 9.490547014934236e-09, + 0.07338026165962219, + 1.862645149230957e-09 + ] + }, + { + "children" : [ + 12 + ], + "name" : "L_Shoulder", + "rotation" : [ + -0.05528340861201286, + 0.01580565795302391, + -0.27442947030067444, + 0.9598866701126099 + ], + "translation" : [ + 1.1175854908174188e-08, + 0.034574370831251144, + -3.3306690738754696e-15 + ] + }, + { + "children" : [ + 13 + ], + "name" : "L_Clavicle", + "rotation" : [ + -4.527326780134899e-08, + -2.4482876170850432e-08, + -0.6586140990257263, + 0.7524808645248413 + ], + "scale" : [ + 0.9999998807907104, + 0.9999998807907104, + 1 + ], + "translation" : [ + 0.03500552102923393, + 0.07119831442832947, + -6.646381223163189e-10 + ] + }, + { + "name" : "R_Hand_end", + "rotation" : [ + 1.3239958462918366e-08, + 2.4324227076988336e-09, + -1.4901161193847656e-08, + 1 + ], + "translation" : [ + -2.2351740014414645e-08, + 0.016836093738675117, + -5.329070518200751e-15 + ] + }, + { + "children" : [ + 15 + ], + "name" : "R_Hand", + "rotation" : [ + -0.10859407484531403, + 0.0013414795976132154, + 0.012280543334782124, + 0.9940094351768494 + ], + "scale" : [ + 1, + 1, + 0.9999999403953552 + ], + "translation" : [ + 5.215407838932151e-08, + 0.030574528500437737, + 4.579678858362968e-09 + ] + }, + { + "children" : [ + 16 + ], + "name" : "R_Forearm", + "rotation" : [ + 0.03182216361165047, + 0.010124370455741882, + 0.05386859551072121, + 0.9979895353317261 + ], + "scale" : [ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation" : [ + 1.4001724224499412e-08, + 0.011892830953001976, + -4.656612873077393e-10 + ] + }, + { + "children" : [ + 17 + ], + "name" : "R_Elbow", + "rotation" : [ + 0.13403145968914032, + -0.0004466302052605897, + -0.0229647234082222, + 0.9907108545303345 + ], + "translation" : [ + -9.490547014934236e-09, + 0.07338026165962219, + 1.862645149230957e-09 + ] + }, + { + "children" : [ + 18 + ], + "name" : "R_Shoulder", + "rotation" : [ + -0.05528340861201286, + -0.01580565795302391, + 0.27442947030067444, + 0.9598866701126099 + ], + "translation" : [ + -1.1175854908174188e-08, + 0.034574370831251144, + -3.3306690738754696e-15 + ] + }, + { + "children" : [ + 19 + ], + "name" : "R_Clavicle", + "rotation" : [ + -4.527326780134899e-08, + 2.4482876170850432e-08, + 0.6586140990257263, + 0.7524808645248413 + ], + "scale" : [ + 0.9999998807907104, + 0.9999998807907104, + 1 + ], + "translation" : [ + -0.03500552102923393, + 0.07119831442832947, + -6.646381223163189e-10 + ] + }, + { + "name" : "L_IK_Arm_Pole_end", + "rotation" : [ + -8.14913803104389e-10, + -2.8273916541365907e-08, + 3.597233089180918e-08, + 1 + ], + "translation" : [ + 1.3742706528319104e-08, + 0.04507105425000191, + 1.6264998237147665e-08 + ] + }, + { + "children" : [ + 21 + ], + "name" : "L_IK_Arm_Pole", + "rotation" : [ + -0.3575689494609833, + -0.6109033823013306, + 0.6082502007484436, + 0.3591284155845642 + ], + "scale" : [ + 0.9999999403953552, + 0.9999998807907104, + 0.9999999403953552 + ], + "translation" : [ + 0.0021197572350502014, + -0.04126967862248421, + -0.053202081471681595 + ] + }, + { + "children" : [ + 22 + ], + "name" : "L_IK_Arm_Target", + "rotation" : [ + -0.0011026781285181642, + 0.0018760154489427805, + -0.8620717525482178, + 0.5067814588546753 + ], + "scale" : [ + 0.9999999403953552, + 1, + 1 + ], + "translation" : [ + 0.17300567030906677, + 0.02745041251182556, + 3.304734264020226e-10 + ] + }, + { + "name" : "R_IK_Arm_Pole_end", + "rotation" : [ + -8.14913803104389e-10, + 2.8273916541365907e-08, + -3.597233089180918e-08, + 1 + ], + "translation" : [ + -1.3742706528319104e-08, + 0.04507105425000191, + 1.6264998237147665e-08 + ] + }, + { + "children" : [ + 24 + ], + "name" : "R_IK_Arm_Pole", + "rotation" : [ + -0.3575689494609833, + 0.6109033823013306, + -0.6082502007484436, + 0.3591284155845642 + ], + "scale" : [ + 0.9999999403953552, + 0.9999998807907104, + 0.9999999403953552 + ], + "translation" : [ + -0.0021197572350502014, + -0.04126967862248421, + -0.053202081471681595 + ] + }, + { + "children" : [ + 25 + ], + "name" : "R_IK_Arm_Target", + "rotation" : [ + -0.0011026781285181642, + -0.0018760154489427805, + 0.8620717525482178, + 0.5067814588546753 + ], + "scale" : [ + 0.9999999403953552, + 1, + 1 + ], + "translation" : [ + -0.17300567030906677, + 0.02745041251182556, + 3.304734264020226e-10 + ] + }, + { + "children" : [ + 8, + 14, + 20, + 23, + 26 + ], + "name" : "Upper_Spine", + "translation" : [ + 0, + 0.06622835993766785, + 0 + ] + }, + { + "children" : [ + 27 + ], + "name" : "Lower_Spine", + "translation" : [ + 0, + 0.06622838973999023, + 0 + ] + }, + { + "name" : "Tail_end", + "translation" : [ + 0, + 0.07595176249742508, + -1.3838050705317073e-09 + ] + }, + { + "children" : [ + 29 + ], + "name" : "Tail", + "rotation" : [ + -0.7071068286895752, + 0, + 0, + 0.7071068286895752 + ], + "translation" : [ + -5.8597615213960615e-18, + 0.03983837366104126, + -0.09847982972860336 + ] + }, + { + "name" : "L_Hip_end", + "translation" : [ + 0, + 0.032987553626298904, + -1.5967565047958487e-09 + ] + }, + { + "children" : [ + 31 + ], + "name" : "L_Hip", + "translation" : [ + 0.06953180581331253, + 0.04957667365670204, + 0.061330340802669525 + ] + }, + { + "name" : "L_Butt_end", + "translation" : [ + 0, + 0.03298754245042801, + 1.3750955929481279e-09 + ] + }, + { + "children" : [ + 33 + ], + "name" : "L_Butt", + "translation" : [ + 0.06953180581331253, + -0.0007792188553139567, + -0.04653617739677429 + ] + }, + { + "name" : "R_Toe_end", + "rotation" : [ + -1.304514398725587e-07, + 4.8278069232242024e-14, + 3.113858042524953e-07, + 1 + ], + "translation" : [ + -2.9270432744255004e-09, + 0.02392714098095894, + 1.3476908478082805e-10 + ] + }, + { + "children" : [ + 35 + ], + "name" : "R_Toe", + "rotation" : [ + 0.32702386379241943, + -1.1310142156162328e-07, + -1.641405731334089e-07, + 0.945016086101532 + ], + "scale" : [ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation" : [ + 8.650776095464607e-09, + 0.03380582109093666, + -2.448857117087755e-09 + ] + }, + { + "children" : [ + 36 + ], + "name" : "R_Feet", + "rotation" : [ + 0.516292929649353, + 0.020581310614943504, + 0.05452270060777664, + 0.854426920413971 + ], + "translation" : [ + -1.2865877252465907e-09, + 0.06353945285081863, + 2.6193447411060333e-10 + ] + }, + { + "children" : [ + 37 + ], + "name" : "R_Shin", + "rotation" : [ + -0.054226718842983246, + -0.00034972387948073447, + 0.0027083493769168854, + 0.9985249042510986 + ], + "scale" : [ + 0.9999998807907104, + 0.9999999403953552, + 0.9999998807907104 + ], + "translation" : [ + 8.217813984856548e-09, + 0.012935775332152843, + -1.1059455573558807e-09 + ] + }, + { + "children" : [ + 38 + ], + "name" : "R_Knee", + "rotation" : [ + -0.117364302277565, + 0.00023353073629550636, + 0.005353146698325872, + 0.9930744767189026 + ], + "scale" : [ + 1, + 1.0000001192092896, + 1 + ], + "translation" : [ + 7.161837345392996e-09, + 0.08009886741638184, + -3.725290298461914e-09 + ] + }, + { + "children" : [ + 39 + ], + "name" : "R_Thigh", + "rotation" : [ + 0.005340703763067722, + 0.08032803982496262, + 0.9945576786994934, + 0.06613556295633316 + ], + "scale" : [ + 1.0000009536743164, + 1.0000001192092896, + 1.0000014305114746 + ], + "translation" : [ + -0.06634333729743958, + 0.021777987480163574, + -0.000205356627702713 + ] + }, + { + "name" : "R_Hip_end", + "translation" : [ + 0, + 0.032987553626298904, + -1.5967565047958487e-09 + ] + }, + { + "children" : [ + 41 + ], + "name" : "R_Hip", + "translation" : [ + -0.06953180581331253, + 0.04957667365670204, + 0.061330340802669525 + ] + }, + { + "name" : "R_Butt_end", + "translation" : [ + 0, + 0.03298754245042801, + 1.3750955929481279e-09 + ] + }, + { + "children" : [ + 43 + ], + "name" : "R_Butt", + "translation" : [ + -0.06953180581331253, + -0.0007792188553139567, + -0.04653617739677429 + ] + }, + { + "children" : [ + 5, + 28, + 30, + 32, + 34, + 40, + 42, + 44 + ], + "name" : "Pelvis", + "translation" : [ + 0, + 0.15915730595588684, + 0 + ] + }, + { + "name" : "L_IK_Leg_Pole_end", + "translation" : [ + 0, + 0.04320859909057617, + 2.2203057170600005e-09 + ] + }, + { + "children" : [ + 46 + ], + "name" : "L_IK_Leg_Pole", + "rotation" : [ + 0, + 0, + -1, + 0 + ], + "translation" : [ + -0.008841380476951599, + -0.08020301908254623, + 0.0748630166053772 + ] + }, + { + "children" : [ + 47 + ], + "name" : "L_IK_Leg_Target", + "rotation" : [ + -0.7071068286895752, + 0, + 0, + 0.7071068286895752 + ], + "translation" : [ + 0.08565311133861542, + 0.027707800269126892, + 0.00015427125617861748 + ] + }, + { + "name" : "R_IK_Leg_Pole_end", + "translation" : [ + 0, + 0.04320859909057617, + 2.2203057170600005e-09 + ] + }, + { + "children" : [ + 49 + ], + "name" : "R_IK_Leg_Pole", + "rotation" : [ + 0, + 0, + -1, + 0 + ], + "translation" : [ + 0.008841380476951599, + -0.08020301908254623, + 0.0748630166053772 + ] + }, + { + "children" : [ + 50 + ], + "name" : "R_IK_Leg_Target", + "rotation" : [ + -0.7071068286895752, + 0, + 0, + 0.7071068286895752 + ], + "translation" : [ + -0.08565311133861542, + 0.027707800269126892, + 0.00015427125617861748 + ] + }, + { + "children" : [ + 45, + 48, + 51 + ], + "name" : "Root" + }, + { + "mesh" : 0, + "name" : "Bag", + "skin" : 0 + }, + { + "mesh" : 1, + "name" : "Raccoon", + "skin" : 0 + }, + { + "children" : [ + 53, + 54, + 52 + ], + "name" : "Armature" + } + ], + "animations" : [ + { + "channels" : [ + { + "sampler" : 0, + "target" : { + "node" : 52, + "path" : "translation" + } + }, + { + "sampler" : 1, + "target" : { + "node" : 52, + "path" : "rotation" + } + }, + { + "sampler" : 2, + "target" : { + "node" : 52, + "path" : "scale" + } + }, + { + "sampler" : 3, + "target" : { + "node" : 45, + "path" : "translation" + } + }, + { + "sampler" : 4, + "target" : { + "node" : 45, + "path" : "rotation" + } + }, + { + "sampler" : 5, + "target" : { + "node" : 45, + "path" : "scale" + } + }, + { + "sampler" : 6, + "target" : { + "node" : 5, + "path" : "translation" + } + }, + { + "sampler" : 7, + "target" : { + "node" : 5, + "path" : "rotation" + } + }, + { + "sampler" : 8, + "target" : { + "node" : 5, + "path" : "scale" + } + }, + { + "sampler" : 9, + "target" : { + "node" : 4, + "path" : "translation" + } + }, + { + "sampler" : 10, + "target" : { + "node" : 4, + "path" : "rotation" + } + }, + { + "sampler" : 11, + "target" : { + "node" : 4, + "path" : "scale" + } + }, + { + "sampler" : 12, + "target" : { + "node" : 3, + "path" : "translation" + } + }, + { + "sampler" : 13, + "target" : { + "node" : 3, + "path" : "rotation" + } + }, + { + "sampler" : 14, + "target" : { + "node" : 3, + "path" : "scale" + } + }, + { + "sampler" : 15, + "target" : { + "node" : 2, + "path" : "translation" + } + }, + { + "sampler" : 16, + "target" : { + "node" : 2, + "path" : "rotation" + } + }, + { + "sampler" : 17, + "target" : { + "node" : 2, + "path" : "scale" + } + }, + { + "sampler" : 18, + "target" : { + "node" : 1, + "path" : "translation" + } + }, + { + "sampler" : 19, + "target" : { + "node" : 1, + "path" : "rotation" + } + }, + { + "sampler" : 20, + "target" : { + "node" : 1, + "path" : "scale" + } + }, + { + "sampler" : 21, + "target" : { + "node" : 0, + "path" : "translation" + } + }, + { + "sampler" : 22, + "target" : { + "node" : 0, + "path" : "rotation" + } + }, + { + "sampler" : 23, + "target" : { + "node" : 0, + "path" : "scale" + } + }, + { + "sampler" : 24, + "target" : { + "node" : 28, + "path" : "translation" + } + }, + { + "sampler" : 25, + "target" : { + "node" : 28, + "path" : "rotation" + } + }, + { + "sampler" : 26, + "target" : { + "node" : 28, + "path" : "scale" + } + }, + { + "sampler" : 27, + "target" : { + "node" : 27, + "path" : "translation" + } + }, + { + "sampler" : 28, + "target" : { + "node" : 27, + "path" : "rotation" + } + }, + { + "sampler" : 29, + "target" : { + "node" : 27, + "path" : "scale" + } + }, + { + "sampler" : 30, + "target" : { + "node" : 8, + "path" : "translation" + } + }, + { + "sampler" : 31, + "target" : { + "node" : 8, + "path" : "rotation" + } + }, + { + "sampler" : 32, + "target" : { + "node" : 8, + "path" : "scale" + } + }, + { + "sampler" : 33, + "target" : { + "node" : 7, + "path" : "translation" + } + }, + { + "sampler" : 34, + "target" : { + "node" : 7, + "path" : "rotation" + } + }, + { + "sampler" : 35, + "target" : { + "node" : 7, + "path" : "scale" + } + }, + { + "sampler" : 36, + "target" : { + "node" : 6, + "path" : "translation" + } + }, + { + "sampler" : 37, + "target" : { + "node" : 6, + "path" : "rotation" + } + }, + { + "sampler" : 38, + "target" : { + "node" : 6, + "path" : "scale" + } + }, + { + "sampler" : 39, + "target" : { + "node" : 14, + "path" : "translation" + } + }, + { + "sampler" : 40, + "target" : { + "node" : 14, + "path" : "rotation" + } + }, + { + "sampler" : 41, + "target" : { + "node" : 14, + "path" : "scale" + } + }, + { + "sampler" : 42, + "target" : { + "node" : 13, + "path" : "translation" + } + }, + { + "sampler" : 43, + "target" : { + "node" : 13, + "path" : "rotation" + } + }, + { + "sampler" : 44, + "target" : { + "node" : 13, + "path" : "scale" + } + }, + { + "sampler" : 45, + "target" : { + "node" : 12, + "path" : "translation" + } + }, + { + "sampler" : 46, + "target" : { + "node" : 12, + "path" : "rotation" + } + }, + { + "sampler" : 47, + "target" : { + "node" : 12, + "path" : "scale" + } + }, + { + "sampler" : 48, + "target" : { + "node" : 11, + "path" : "translation" + } + }, + { + "sampler" : 49, + "target" : { + "node" : 11, + "path" : "rotation" + } + }, + { + "sampler" : 50, + "target" : { + "node" : 11, + "path" : "scale" + } + }, + { + "sampler" : 51, + "target" : { + "node" : 10, + "path" : "translation" + } + }, + { + "sampler" : 52, + "target" : { + "node" : 10, + "path" : "rotation" + } + }, + { + "sampler" : 53, + "target" : { + "node" : 10, + "path" : "scale" + } + }, + { + "sampler" : 54, + "target" : { + "node" : 9, + "path" : "translation" + } + }, + { + "sampler" : 55, + "target" : { + "node" : 9, + "path" : "rotation" + } + }, + { + "sampler" : 56, + "target" : { + "node" : 9, + "path" : "scale" + } + }, + { + "sampler" : 57, + "target" : { + "node" : 20, + "path" : "translation" + } + }, + { + "sampler" : 58, + "target" : { + "node" : 20, + "path" : "rotation" + } + }, + { + "sampler" : 59, + "target" : { + "node" : 20, + "path" : "scale" + } + }, + { + "sampler" : 60, + "target" : { + "node" : 19, + "path" : "translation" + } + }, + { + "sampler" : 61, + "target" : { + "node" : 19, + "path" : "rotation" + } + }, + { + "sampler" : 62, + "target" : { + "node" : 19, + "path" : "scale" + } + }, + { + "sampler" : 63, + "target" : { + "node" : 18, + "path" : "translation" + } + }, + { + "sampler" : 64, + "target" : { + "node" : 18, + "path" : "rotation" + } + }, + { + "sampler" : 65, + "target" : { + "node" : 18, + "path" : "scale" + } + }, + { + "sampler" : 66, + "target" : { + "node" : 17, + "path" : "translation" + } + }, + { + "sampler" : 67, + "target" : { + "node" : 17, + "path" : "rotation" + } + }, + { + "sampler" : 68, + "target" : { + "node" : 17, + "path" : "scale" + } + }, + { + "sampler" : 69, + "target" : { + "node" : 16, + "path" : "translation" + } + }, + { + "sampler" : 70, + "target" : { + "node" : 16, + "path" : "rotation" + } + }, + { + "sampler" : 71, + "target" : { + "node" : 16, + "path" : "scale" + } + }, + { + "sampler" : 72, + "target" : { + "node" : 15, + "path" : "translation" + } + }, + { + "sampler" : 73, + "target" : { + "node" : 15, + "path" : "rotation" + } + }, + { + "sampler" : 74, + "target" : { + "node" : 15, + "path" : "scale" + } + }, + { + "sampler" : 75, + "target" : { + "node" : 23, + "path" : "translation" + } + }, + { + "sampler" : 76, + "target" : { + "node" : 23, + "path" : "rotation" + } + }, + { + "sampler" : 77, + "target" : { + "node" : 23, + "path" : "scale" + } + }, + { + "sampler" : 78, + "target" : { + "node" : 22, + "path" : "translation" + } + }, + { + "sampler" : 79, + "target" : { + "node" : 22, + "path" : "rotation" + } + }, + { + "sampler" : 80, + "target" : { + "node" : 22, + "path" : "scale" + } + }, + { + "sampler" : 81, + "target" : { + "node" : 21, + "path" : "translation" + } + }, + { + "sampler" : 82, + "target" : { + "node" : 21, + "path" : "rotation" + } + }, + { + "sampler" : 83, + "target" : { + "node" : 21, + "path" : "scale" + } + }, + { + "sampler" : 84, + "target" : { + "node" : 26, + "path" : "translation" + } + }, + { + "sampler" : 85, + "target" : { + "node" : 26, + "path" : "rotation" + } + }, + { + "sampler" : 86, + "target" : { + "node" : 26, + "path" : "scale" + } + }, + { + "sampler" : 87, + "target" : { + "node" : 25, + "path" : "translation" + } + }, + { + "sampler" : 88, + "target" : { + "node" : 25, + "path" : "rotation" + } + }, + { + "sampler" : 89, + "target" : { + "node" : 25, + "path" : "scale" + } + }, + { + "sampler" : 90, + "target" : { + "node" : 24, + "path" : "translation" + } + }, + { + "sampler" : 91, + "target" : { + "node" : 24, + "path" : "rotation" + } + }, + { + "sampler" : 92, + "target" : { + "node" : 24, + "path" : "scale" + } + }, + { + "sampler" : 93, + "target" : { + "node" : 30, + "path" : "translation" + } + }, + { + "sampler" : 94, + "target" : { + "node" : 30, + "path" : "rotation" + } + }, + { + "sampler" : 95, + "target" : { + "node" : 30, + "path" : "scale" + } + }, + { + "sampler" : 96, + "target" : { + "node" : 29, + "path" : "translation" + } + }, + { + "sampler" : 97, + "target" : { + "node" : 29, + "path" : "rotation" + } + }, + { + "sampler" : 98, + "target" : { + "node" : 29, + "path" : "scale" + } + }, + { + "sampler" : 99, + "target" : { + "node" : 32, + "path" : "translation" + } + }, + { + "sampler" : 100, + "target" : { + "node" : 32, + "path" : "rotation" + } + }, + { + "sampler" : 101, + "target" : { + "node" : 32, + "path" : "scale" + } + }, + { + "sampler" : 102, + "target" : { + "node" : 31, + "path" : "translation" + } + }, + { + "sampler" : 103, + "target" : { + "node" : 31, + "path" : "rotation" + } + }, + { + "sampler" : 104, + "target" : { + "node" : 31, + "path" : "scale" + } + }, + { + "sampler" : 105, + "target" : { + "node" : 34, + "path" : "translation" + } + }, + { + "sampler" : 106, + "target" : { + "node" : 34, + "path" : "rotation" + } + }, + { + "sampler" : 107, + "target" : { + "node" : 34, + "path" : "scale" + } + }, + { + "sampler" : 108, + "target" : { + "node" : 33, + "path" : "translation" + } + }, + { + "sampler" : 109, + "target" : { + "node" : 33, + "path" : "rotation" + } + }, + { + "sampler" : 110, + "target" : { + "node" : 33, + "path" : "scale" + } + }, + { + "sampler" : 111, + "target" : { + "node" : 40, + "path" : "translation" + } + }, + { + "sampler" : 112, + "target" : { + "node" : 40, + "path" : "rotation" + } + }, + { + "sampler" : 113, + "target" : { + "node" : 40, + "path" : "scale" + } + }, + { + "sampler" : 114, + "target" : { + "node" : 39, + "path" : "translation" + } + }, + { + "sampler" : 115, + "target" : { + "node" : 39, + "path" : "rotation" + } + }, + { + "sampler" : 116, + "target" : { + "node" : 39, + "path" : "scale" + } + }, + { + "sampler" : 117, + "target" : { + "node" : 38, + "path" : "translation" + } + }, + { + "sampler" : 118, + "target" : { + "node" : 38, + "path" : "rotation" + } + }, + { + "sampler" : 119, + "target" : { + "node" : 38, + "path" : "scale" + } + }, + { + "sampler" : 120, + "target" : { + "node" : 37, + "path" : "translation" + } + }, + { + "sampler" : 121, + "target" : { + "node" : 37, + "path" : "rotation" + } + }, + { + "sampler" : 122, + "target" : { + "node" : 37, + "path" : "scale" + } + }, + { + "sampler" : 123, + "target" : { + "node" : 36, + "path" : "translation" + } + }, + { + "sampler" : 124, + "target" : { + "node" : 36, + "path" : "rotation" + } + }, + { + "sampler" : 125, + "target" : { + "node" : 36, + "path" : "scale" + } + }, + { + "sampler" : 126, + "target" : { + "node" : 35, + "path" : "translation" + } + }, + { + "sampler" : 127, + "target" : { + "node" : 35, + "path" : "rotation" + } + }, + { + "sampler" : 128, + "target" : { + "node" : 35, + "path" : "scale" + } + }, + { + "sampler" : 129, + "target" : { + "node" : 42, + "path" : "translation" + } + }, + { + "sampler" : 130, + "target" : { + "node" : 42, + "path" : "rotation" + } + }, + { + "sampler" : 131, + "target" : { + "node" : 42, + "path" : "scale" + } + }, + { + "sampler" : 132, + "target" : { + "node" : 41, + "path" : "translation" + } + }, + { + "sampler" : 133, + "target" : { + "node" : 41, + "path" : "rotation" + } + }, + { + "sampler" : 134, + "target" : { + "node" : 41, + "path" : "scale" + } + }, + { + "sampler" : 135, + "target" : { + "node" : 44, + "path" : "translation" + } + }, + { + "sampler" : 136, + "target" : { + "node" : 44, + "path" : "rotation" + } + }, + { + "sampler" : 137, + "target" : { + "node" : 44, + "path" : "scale" + } + }, + { + "sampler" : 138, + "target" : { + "node" : 43, + "path" : "translation" + } + }, + { + "sampler" : 139, + "target" : { + "node" : 43, + "path" : "rotation" + } + }, + { + "sampler" : 140, + "target" : { + "node" : 43, + "path" : "scale" + } + }, + { + "sampler" : 141, + "target" : { + "node" : 48, + "path" : "translation" + } + }, + { + "sampler" : 142, + "target" : { + "node" : 48, + "path" : "rotation" + } + }, + { + "sampler" : 143, + "target" : { + "node" : 48, + "path" : "scale" + } + }, + { + "sampler" : 144, + "target" : { + "node" : 47, + "path" : "translation" + } + }, + { + "sampler" : 145, + "target" : { + "node" : 47, + "path" : "rotation" + } + }, + { + "sampler" : 146, + "target" : { + "node" : 47, + "path" : "scale" + } + }, + { + "sampler" : 147, + "target" : { + "node" : 46, + "path" : "translation" + } + }, + { + "sampler" : 148, + "target" : { + "node" : 46, + "path" : "rotation" + } + }, + { + "sampler" : 149, + "target" : { + "node" : 46, + "path" : "scale" + } + }, + { + "sampler" : 150, + "target" : { + "node" : 51, + "path" : "translation" + } + }, + { + "sampler" : 151, + "target" : { + "node" : 51, + "path" : "rotation" + } + }, + { + "sampler" : 152, + "target" : { + "node" : 51, + "path" : "scale" + } + }, + { + "sampler" : 153, + "target" : { + "node" : 50, + "path" : "translation" + } + }, + { + "sampler" : 154, + "target" : { + "node" : 50, + "path" : "rotation" + } + }, + { + "sampler" : 155, + "target" : { + "node" : 50, + "path" : "scale" + } + }, + { + "sampler" : 156, + "target" : { + "node" : 49, + "path" : "translation" + } + }, + { + "sampler" : 157, + "target" : { + "node" : 49, + "path" : "rotation" + } + }, + { + "sampler" : 158, + "target" : { + "node" : 49, + "path" : "scale" + } + }, + { + "sampler" : 159, + "target" : { + "node" : 55, + "path" : "translation" + } + }, + { + "sampler" : 160, + "target" : { + "node" : 55, + "path" : "rotation" + } + }, + { + "sampler" : 161, + "target" : { + "node" : 55, + "path" : "scale" + } + } + ], + "name" : "Armature|Armature|ArmatureAction", + "samplers" : [ + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 15 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 16 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 17 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 18 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 19 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 20 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 21 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 22 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 23 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 24 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 25 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 26 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 27 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 28 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 29 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 30 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 31 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 32 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 33 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 34 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 35 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 36 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 37 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 38 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 39 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 40 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 41 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 42 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 43 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 44 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 45 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 46 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 47 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 48 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 49 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 50 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 51 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 52 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 53 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 54 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 55 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 56 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 57 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 58 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 59 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 60 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 61 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 62 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 63 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 64 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 65 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 66 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 67 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 68 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 69 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 70 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 71 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 72 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 73 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 74 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 75 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 76 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 77 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 78 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 79 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 80 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 81 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 82 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 83 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 84 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 85 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 86 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 87 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 88 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 89 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 90 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 91 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 92 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 93 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 94 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 95 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 96 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 97 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 98 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 99 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 100 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 101 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 102 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 103 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 104 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 105 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 106 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 107 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 108 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 109 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 110 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 111 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 112 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 113 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 114 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 115 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 116 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 117 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 118 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 119 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 120 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 121 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 122 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 123 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 124 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 125 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 126 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 127 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 128 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 129 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 130 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 131 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 132 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 133 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 134 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 135 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 136 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 137 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 138 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 139 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 140 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 141 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 142 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 143 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 144 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 145 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 146 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 147 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 148 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 149 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 150 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 151 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 152 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 153 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 154 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 155 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 156 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 157 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 158 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 159 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 160 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 161 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 162 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 163 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 164 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 165 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 166 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 167 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 168 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 169 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 170 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 171 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 172 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 173 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 174 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 175 + }, + { + "input" : 14, + "interpolation" : "LINEAR", + "output" : 176 + } + ] + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "BagMaterial", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.5 + } + }, + { + "alphaMode" : "BLEND", + "doubleSided" : true, + "extensions" : { + "KHR_materials_specular" : { + "specularColorFactor" : [ + 0, + 0, + 0 + ] + }, + "KHR_materials_ior" : { + "ior" : 1.4500000476837158 + } + }, + "name" : "BodyMaterial", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor" : 0 + } + } + ], + "meshes" : [ + { + "name" : "Cube.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2, + "JOINTS_0" : 3, + "WEIGHTS_0" : 4 + }, + "indices" : 5, + "material" : 0 + } + ] + }, + { + "name" : "Cube.012", + "primitives" : [ + { + "attributes" : { + "POSITION" : 7, + "NORMAL" : 8, + "TEXCOORD_0" : 9, + "COLOR_0" : 10, + "JOINTS_0" : 11, + "WEIGHTS_0" : 12 + }, + "indices" : 13, + "material" : 1 + } + ] + } + ], + "skins" : [ + { + "inverseBindMatrices" : 6, + "joints" : [ + 52, + 45, + 5, + 4, + 3, + 2, + 1, + 0, + 28, + 27, + 8, + 7, + 6, + 14, + 13, + 12, + 11, + 10, + 9, + 20, + 19, + 18, + 17, + 16, + 15, + 23, + 22, + 21, + 26, + 25, + 24, + 30, + 29, + 32, + 31, + 34, + 33, + 40, + 39, + 38, + 37, + 36, + 35, + 42, + 41, + 44, + 43, + 48, + 47, + 46, + 51, + 50, + 49 + ], + "name" : "Armature" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 506, + "max" : [ + 0.1090814545750618, + 0.40452075004577637, + 0.0857388824224472 + ], + "min" : [ + -0.09462108463048935, + 0.2630254030227661, + -0.11617939174175262 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 506, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 506, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5121, + "count" : 506, + "type" : "VEC4" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 506, + "type" : "VEC4" + }, + { + "bufferView" : 5, + "componentType" : 5123, + "count" : 2346, + "type" : "SCALAR" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 53, + "type" : "MAT4" + }, + { + "bufferView" : 7, + "componentType" : 5126, + "count" : 3484, + "max" : [ + 0.2035536766052246, + 0.5987313389778137, + 0.09013944119215012 + ], + "min" : [ + -0.19493983685970306, + -0.0017474208725616336, + -0.19020147621631622 + ], + "type" : "VEC3" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 3484, + "type" : "VEC3" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 3484, + "type" : "VEC2" + }, + { + "bufferView" : 10, + "componentType" : 5123, + "count" : 3484, + "normalized" : true, + "type" : "VEC4" + }, + { + "bufferView" : 11, + "componentType" : 5121, + "count" : 3484, + "type" : "VEC4" + }, + { + "bufferView" : 12, + "componentType" : 5126, + "count" : 3484, + "type" : "VEC4" + }, + { + "bufferView" : 13, + "componentType" : 5123, + "count" : 17472, + "type" : "SCALAR" + }, + { + "bufferView" : 14, + "componentType" : 5126, + "count" : 51, + "max" : [ + 2.125 + ], + "min" : [ + 0.041666666666666664 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 15, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 16, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 19, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 20, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 23, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 24, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 27, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 28, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 31, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 32, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 33, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 34, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 35, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 36, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 37, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 38, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 39, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 40, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 41, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 42, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 43, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 44, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 45, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 46, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 47, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 48, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 49, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 50, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 51, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 52, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 53, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 54, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 55, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 56, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 57, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 58, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 59, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 60, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 61, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 62, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 63, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 64, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 65, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 66, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 67, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 68, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 69, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 70, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 71, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 72, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 73, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 74, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 75, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 76, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 77, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 78, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 79, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 80, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 81, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 82, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 83, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 84, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 85, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 86, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 87, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 88, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 89, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 90, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 91, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 92, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 93, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 94, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 95, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 96, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 97, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 98, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 99, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 100, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 101, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 102, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 103, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 104, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 105, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 106, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 107, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 108, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 109, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 110, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 111, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 112, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 113, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 114, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 115, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 116, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 117, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 118, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 119, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 120, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 121, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 122, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 123, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 124, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 125, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 126, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 127, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 128, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 129, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 130, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 131, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 132, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 133, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 134, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 135, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 136, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 137, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 138, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 139, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 140, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 141, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 142, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 143, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 144, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 145, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 146, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 147, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 148, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 149, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 150, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 151, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 152, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 153, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 154, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 155, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 156, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 157, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 158, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 159, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 160, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 161, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 162, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 163, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 164, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 165, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 166, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 167, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 168, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 169, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 170, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 171, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 172, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 173, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 174, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + }, + { + "bufferView" : 175, + "componentType" : 5126, + "count" : 51, + "type" : "VEC4" + }, + { + "bufferView" : 176, + "componentType" : 5126, + "count" : 51, + "type" : "VEC3" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 6072, + "byteOffset" : 0, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 6072, + "byteOffset" : 6072, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 4048, + "byteOffset" : 12144, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2024, + "byteOffset" : 16192, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 8096, + "byteOffset" : 18216, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 4692, + "byteOffset" : 26312, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3392, + "byteOffset" : 31004 + }, + { + "buffer" : 0, + "byteLength" : 41808, + "byteOffset" : 34396, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 41808, + "byteOffset" : 76204, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 27872, + "byteOffset" : 118012, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 27872, + "byteOffset" : 145884, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 13936, + "byteOffset" : 173756, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 55744, + "byteOffset" : 187692, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 34944, + "byteOffset" : 243436, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 204, + "byteOffset" : 278380 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 278584 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 279196 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 280012 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 280624 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 281236 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 282052 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 282664 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 283276 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 284092 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 284704 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 285316 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 286132 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 286744 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 287356 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 288172 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 288784 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 289396 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 290212 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 290824 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 291436 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 292252 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 292864 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 293476 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 294292 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 294904 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 295516 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 296332 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 296944 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 297556 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 298372 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 298984 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 299596 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 300412 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 301024 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 301636 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 302452 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 303064 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 303676 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 304492 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 305104 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 305716 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 306532 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 307144 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 307756 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 308572 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 309184 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 309796 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 310612 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 311224 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 311836 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 312652 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 313264 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 313876 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 314692 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 315304 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 315916 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 316732 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 317344 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 317956 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 318772 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 319384 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 319996 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 320812 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 321424 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 322036 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 322852 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 323464 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 324076 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 324892 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 325504 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 326116 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 326932 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 327544 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 328156 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 328972 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 329584 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 330196 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 331012 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 331624 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 332236 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 333052 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 333664 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 334276 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 335092 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 335704 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 336316 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 337132 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 337744 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 338356 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 339172 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 339784 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 340396 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 341212 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 341824 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 342436 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 343252 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 343864 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 344476 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 345292 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 345904 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 346516 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 347332 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 347944 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 348556 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 349372 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 349984 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 350596 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 351412 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 352024 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 352636 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 353452 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 354064 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 354676 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 355492 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 356104 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 356716 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 357532 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 358144 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 358756 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 359572 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 360184 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 360796 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 361612 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 362224 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 362836 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 363652 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 364264 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 364876 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 365692 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 366304 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 366916 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 367732 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 368344 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 368956 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 369772 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 370384 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 370996 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 371812 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 372424 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 373036 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 373852 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 374464 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 375076 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 375892 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 376504 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 377116 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 377932 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 378544 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 379156 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 379972 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 380584 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 381196 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 382012 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 382624 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 383236 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 384052 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 384664 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 385276 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 386092 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 386704 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 387316 + }, + { + "buffer" : 0, + "byteLength" : 612, + "byteOffset" : 388132 + } + ], + "buffers" : [ + { + "byteLength" : 388744, + "uri" : "data:application/octet-stream;base64,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CQgAAAkIAAAJCAAACQgAAAkVCAAJFQgACQgAAAkIAAAJCAAACQgAAAkIAAAJAAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkAAAAJAAAACQAAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJAAAACQgAAAkIAAAJAAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJAAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkAAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkAAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkAAAAJAAAACQAAAAkIAAAJCAAACQgAAAkAAAAJAAAACQAAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJAAAACQgAAAkAAAAJCAAACQAAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJAAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAgJExQJCBMUCAkTAAkIEwAICRMACQgTAAgJEwAJCBMACAkTAAgJEwAICRMACAkTAAgJEwAICRMACAkTAAgJEwAICQAACAkAAAgJAAAICQAACAkTAAgJEwAJCAAACAkAAAkIAAAJCAAACQgAAAkIAAAJCBMACQgTAAkAAAAJAAAACRMAAAkAAAAJEwgUCRMIFAkTFAgJExQICRMUCBMJFAgTFAkAExQJABMUCQATFAkAExQJABMUCQATFAAAFBMAABMUAAAUEwAAExQAABMUAAATFAAAExQJABMUCQgTFAkIEwgUCQkTCBQIEwkUCQgTFAgTCRQJCBMUCAkTFAkIExQICRMACQgTAAkIExQJCBMACAkTAAkIEwAJCBMACAkTAAkIEwAICRMACAkTAAkIEwAICRMACAkTAAgJEwAICRMACAkTAAgJEwAICRMACAkTAAgJEwAICRMACAkAAAgJAAAICQAACAkAAAgJAAAICQAACAkAAAgJAAAICQAACAkTAAgJAAAJCAAACQgTAAkIAAAJCAAACQgAAAkIAAAJCAAACQgAAAkIEwAJCBMACQgTAAkIEwAJAAAACQAAAAkAAAAJAAAACQAAAAkTCBQJEwgUCRMIFAkTCBQJExQICRMUCAkTFAgTCRQIExQJABMUCQATFAkAExQJABMUCQATFAkAExQJABMUCQATFAkAExQAABQTAAAUEwAAExQAABQTAAAUEwAAExQAABQTAAATFAAAExQAABQTAAATFAAAExQJCBMUCQATFAkIExQICRMJFAgTCQgUEwgJFAkTCBQJCBMUCBMJFAkIExQJCBMUCAkTFAkIExQJCBMUCAkTFAkIExQJCBMACQgTAAkIEwAICRMACAkTAAkIEwAICRMACAkAAAgJAAAICQAACAkAAAkIAAAJCAAACQgAAAkIAAAJAAAACRMIFAkTCBQJExQIExQJABMUCQATFAkAExQAABQTAAAUEwAAFBMAABMUCQATCRQICRMIFAkIExQJCBMUCQgTFAgJExQJCBMUCAkTAAkIEwAICRMACQgTAAkIEwAJCBMACAkTAAgJEwAICRMACAkTAAgJEwAICRMACAkTAAgJEwAICQAACAkAAAgJAAAICQAACAkTAAgJEwAICRMACAkTAAkIEwAJCBMACQgAAAkIEwAJCBMACQgTAAkTCAAJEwgUCRMIAAkTCBQJEwgUCRMIFAkTFAgTCRQIEwkUCBMJFAgTFAkAExQJABMUCQATFAkAExQJABMUCQATFAAAFBMAABMUAAAUEwAAExQAABMUAAATFAAAExQAABMUCQATFAkAExQJCAkTFAgTCRQICRMIFBMJCBQJCBMUCAkTFAkIExQICRMACQgTAAkIEwAJCBMACQgTAAkIEwAJCBMACQgTAAgJEwAICRMACAkTAAgJEwAICRMACAkTAAgJAAAICQAACAkAAAgJAAAICQAACAkAAAgJEwAICRMACQgTAAkIEwAJCAAACQgTAAkIAAAJCBMACQgTAAkIEwAJEwgACRMIFAkAAAAJEwgUCRMIFAkTCBQJExQIEwkUCBMUCQATFAkAExQJABMUCQATFAkAExQJABMUAAAUEwAA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XwAVAF8ALQAXACMAbgAXAG4AZQAvABcAZQAvAGUAaQAtAF8APwAtAD8AFAAjABcAVgAjAFYAVwAWAC4AWAAWAFgAWQAvABYAWQAvAFkAWgAXAC8AWgAXAFoAVgBbAEIAXABbAFwAPwBCACMAVwBCAFcAXAAuAFQAXQAuAF0AWABUAF4APgBUAD4AXQBeAFUAXwBeAF8APgBVAFsAPwBVAD8AXwBiAHYAgQBiAIEAeQBkAG0AfgBkAH4AegBoAGQAegBoAHoAfAB3AHEAfwB3AH8AgQAuABYAYQAuAGEAbwBVAF4AYwBVAGMAawAjAEIAdAAjAHQAbgAWAC8AaQAWAGkAYQBbAFUAawBbAGsAZwBUAC4AbwBUAG8AdQB7AHoAfgB7AH4AgACBAH8AeACBAHgAeQB5AHgAfAB5AHwAfQB9AHwAegB9AHoAewBzAGYAewBzAHsAgABwAGAAeABwAHgAfwBqAGIAeQBqAHkAfQBsAHIAgABsAIAAfgBgAGgAfABgAHwAeABmAGoAfQBmAH0AewAhAA8AhAAhAIQAiwAOACEAiwAOAIsAggAcAIkAhQAcAIUADADmAfYBigDmAYoAHQBIAIcAgwBIAIMAKwDxAfcBiADxAYgASQAwAEUAhgAwAIYAhQBFACsAgwBFAIMAhgApABEAjwApAI8AlQAQACkAlQAQAJUAjQAnAJMAjwAnAI8AEQDpAfgBlADpAZQAKABGAJEAjQBGAI0AKgDwAfkBkgDwAZIARwAsAFwAkAAsAJAAjgBcAFcAjABcAIwAkACWANgANgGWADYB1gDYAJcA1wDYANcANgE2AdcAmQA2AZkA2QDWADYB2QDWANkAmACYANkANwGYADcB2gDZAJkA2wDZANsANwE3AdsAmwA3AZsA3ADaADcB3ADaANwAmgCaANwAOAGaADgB3QDcAJsA3gDcAN4AOAE4Ad4AnQA4AZ0A3wDdADgB3wDdAN8AnACcAN8AOQGcADkB4ADfAJ0A4QDfAOEAOQE5AeEAnwA5AZ8A4gDgADkB4gDgAOIAngCeAOIAOgGeADoB4wDiAJ8A5ADiAOQAOgE6AeQAoQA6AaEA5QDjADoB5QDjAOUAoACgAOUAOwGgADsB5gDlAKEA5wDlAOcAOwE7AecAowA7AaMA6ADmADsB6ADmAOgAogCiAOgAPAGiADwB6QDoAKMA6gDoAOoAPAE8AeoApQA8AaUA6wDpADwB6wDpAOsApACkAOsAPQGkAD0B7ADrAKUA7QDrAO0APQE9Ae0ApwA9AacA7gDsAD0B7gDsAO4ApgCmAO4APgGmAD4B7wDuAKcA8ADuAPAAPgE+AfAAqQA+AakA8QDvAD4B8QDvAPEAqACoAPEAPwGoAD8B8gDxAKkA8wDxAPMAPwE/AfMAqwA/AasA9ADyAD8B9ADyAPQAqgCqAPQAQAGqAEAB9QD0AKsA9gD0APYAQAFAAfYArQBAAa0A9wD1AEAB9wD1APcArACsAPcAQQGsAEEB+AD3AK0A+QD3APkAQQFBAfkArwBBAa8A+gD4AEEB+gD4APoArgCuAPoAQgGuAEIB+wD6AK8A/AD6APwAQgFCAfwAsQBCAbEA/QD7AEIB/QD7AP0AsACwAP0AQwGwAEMB/gD9ALEA/wD9AP8AQwFDAf8AswBDAbMAAAH+AEMBAAH+AAABsgCyAAABRAGyAEQBAQEAAbMAAgEAAQIBRAFEAQIBtQBEAbUAAwEBAUQBAwEBAQMBtAC0AAMBRQG0AEUBBAEDAbUABQEDAQUBRQFFAQUBtwBFAbcABgEEAUUBBgEEAQYBtgC2AAYBRgG2AEYBBwEGAbcACAEGAQgBRgFGAQgBuQBGAbkACQEHAUYBCQEHAQkBuAC4AAkBRwG4AEcBCgEJAbkACwEJAQsBRwFHAQsBuwBHAbsADAEKAUcBDAEKAQwBugC6AAwBSAG6AEgBDQEMAbsADgEMAQ4BSAFIAQ4BvQBIAb0ADwENAUgBDwENAQ8BvAC8AA8BSQG8AEkBEAEPAb0AEQEPAREBSQFJAREBvwBJAb8AEgEQAUkBEgEQARIBvgC+ABIBSgG+AEoBEwESAb8AFAESARQBSgFKARQBwQBKAcEAFQETAUoBFQETARUBwADAABUBSwHAAEsBFgEVAcEAFwEVARcBSwFLARcBwwBLAcMAGAEWAUsBGAEWARgBwgDCABgBTAHCAEwBGQEYAcMAGgEYARoBTAFMARoBxQBMAcUAGwEZAUwBGwEZARsBxADEABsBTQHEAE0BHAEbAcUAHQEbAR0BTQFNAR0BxwBNAccAHgEcAU0BHgEcAR4BxgDGAB4BTgHGAE4BHwEeAccAIAEeASABTgFOASAByQBOAckAIQEfAU4BIQEfASEByADIACEBTwHIAE8BIgEhAckAIwEhASMBTwFPASMBywBPAcsAJAEiAU8BJAEiASQBygDKACQBUAHKAFABJQEkAcsAJgEkASYBUAFQASYBzQBQAc0AJwElAVABJwElAScBzADMACcBUQHMAFEBKAEnAc0AKQEnASkBUQFRASkBzwBRAc8AKgEoAVEBKgEoASoBzgDOACoBUgHOAFIBKwEqAc8ALAEqASwBUgFSASwB0QBSAdEALQErAVIBLQErAS0B0ADQAC0BUwHQAFMBLgEtAdEALwEtAS8BUwFTAS8B0wBTAdMAMAEuAVMBMAEuATAB0gDSADABVAHSAFQBMQEwAdMAMgEwATIBVAFUATIB1QBUAdUAMwExAVQBMwExATMB1ADUADMBVQHUAFUBNAEzAdUANQEzATUBVQFVATUBlwBVAZcA2AA0AVUB2AA0AdgAlgCWANYAlgGWAJYBVgHWAJgAWAHWAFgBlgGZANcAlwGZAJcBWQHXAJcAVwHXAFcBlwGYANoAmAGYAJgBWAHaAJoAWgHaAFoBmAGbANsAmQGbAJkBWwHbAJkAWQHbAFkBmQGaAN0AmgGaAJoBWgHdAJwAXAHdAFwBmgGdAN4AmwGdAJsBXQHeAJsAWwHeAFsBmwGcAOAAnAGcAJwBXAHgAJ4AXgHgAF4BnAGfAOEAnQGfAJ0BXwHhAJ0AXQHhAF0BnQGeAOMAngGeAJ4BXgHjAKAAYAHjAGABngGhAOQAnwGhAJ8BYQHkAJ8AXwHkAF8BnwGgAOYAoAGgAKABYAHmAKIAYgHmAGIBoAGjAOcAoQGjAKEBYwHnAKEAYQHnAGEBoQGiAOkAogGiAKIBYgHpAKQAZAHpAGQBogGlAOoAowGlAKMBZQHqAKMAYwHqAGMBowGkAOwApAGkAKQBZAHsAKYAZgHsAGYBpAGnAO0ApQGnAKUBZwHtAKUAZQHtAGUBpQGmAO8ApgGmAKYBZgHvAKgAaAHvAGgBpgGpAPAApwGpAKcBaQHwAKcAZwHwAGcBpwGoAPIAqAGoAKgBaAHyAKoAagHyAGoBqAGrAPMAqQGrAKkBawHzAKkAaQHzAGkBqQGqAPUAqgGqAKoBagH1AKwAbAH1AGwBqgGtAPYAqwGtAKsBbQH2AKsAawH2AGsBqwGsAPgArAGsAKwBbAH4AK4AbgH4AG4BrAGvAPkArQGvAK0BbwH5AK0AbQH5AG0BrQGuAPsArgGuAK4BbgH7ALAAcAH7AHABrgGxAPwArwGxAK8BcQH8AK8AbwH8AG8BrwGwAP4AsAGwALABcAH+ALIAcgH+AHIBsAGzAP8AsQGzALEBcwH/ALEAcQH/AHEBsQGyAAEBsgGyALIBcgEBAbQAdAEBAXQBsgG1AAIBswG1ALMBdQECAbMAcwECAXMBswG0AAQBtAG0ALQBdAEEAbYAdgEEAXYBtAG3AAUBtQG3ALUBdwEFAbUAdQEFAXUBtQG2AAcBtgG2ALYBdgEHAbgAeAEHAXgBtgG5AAgBtwG5ALcBeQEIAbcAdwEIAXcBtwG4AAoBuAG4ALgBeAEKAboAegEKAXoBuAG7AAsBuQG7ALkBewELAbkAeQELAXkBuQG6AA0BugG6ALoBegENAbwAfAENAXwBugG9AA4BuwG9ALsBfQEOAbsAewEOAXsBuwG8ABABvAG8ALwBfAEQAb4AfgEQAX4BvAG/ABEBvQG/AL0BfwERAb0AfQERAX0BvQG+ABMBvgG+AL4BfgETAcAAgAETAYABvgHBABQBvwHBAL8BgQEUAb8AfwEUAX8BvwHAABYBwAHAAMABgAEWAcIAggEWAYIBwAHDABcBwQHDAMEBgwEXAcEAgQEXAYEBwQHCABkBwgHCAMIBggEZAcQAhAEZAYQBwgHFABoBwwHFAMMBhQEaAcMAgwEaAYMBwwHEABwBxAHEAMQBhAEcAcYAhgEcAYYBxAHHAB0BxQHHAMUBhwEdAcUAhQEdAYUBxQHGAB8BxgHGAMYBhgEfAcgAiAEfAYgBxgHJACABxwHJAMcBiQEgAccAhwEgAYcBxwHIACIByAHIAMgBiAEiAcoAigEiAYoByAHLACMByQHLAMkBiwEjAckAiQEjAYkByQHKACUBygHKAMoBigElAcwAjAElAYwBygHNACYBywHNAMsBjQEmAcsAiwEmAYsBywHMACgBzAHMAMwBjAEoAc4AjgEoAY4BzAHPACkBzQHPAM0BjwEpAc0AjQEpAY0BzQHOACsBzgHOAM4BjgErAdAAkAErAZABzgHRACwBzwHRAM8BkQEsAc8AjwEsAY8BzwHQAC4B0AHQANABkAEuAdIAkgEuAZIB0AHTAC8B0QHTANEBkwEvAdEAkQEvAZEB0QHSADEB0gHSANIBkgExAdQAlAExAZQB0gHVADIB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x6buul52vb1wdoc9CbY+vELSt72/bxY9Khviu15Bsb1LPYc9ALqjvCWFsL0eLhY9Fg82vDe8nL2BsoY90O22vAZIpb1ryxU9lClCvA03iL23J4Y9niHKvOcKmr27aBU9E0ROvEI7d735CIU9uc6CvBEgkr1WJhU95cYMvHh/b71JSoQ9+I0Aunh/b71JSoQ9+I0Auro5j73JuhQ9lBU0u7o5j73JuhQ9lBU0u7MFgL2onXQ9OccRPJYHkr1U9xM9gHFKO54HXL3+oao9D/JwPJe+WL3yabM9w/yWPBYxlb17UgM9ZsoWO8zAmb22WBM99obrO9wxnL0IFwM9oOy/OzDanb2acO48DnisO+vtpb3GjQM96aa7O/mpr72FBAQ9MWG3O+G5pr1nfO88mL+rO5SZr70yiPA8JAerO35Jp71zW9Q8jATgO35Jp71zW9Q8jATgO/YEn70ZONM8kKPeO/YEn70ZONM8kKPeOweOr73NftU8i2XhOweOr73NftU8i2XhO0aMp72Z94A8kiurPKmIr71pLII8EjCrPOWPn72MhX88DyerPEWMp73t0Wo8U1q/POOPn738cGg8Llq/PKmIr73cMm08eFq/PEWMp73HkVQ8XH3VPKmIr73x61Y8UH3VPOOPn72fN1I8ZX3VPEWMp72DLu07UbMbPeOPn73gg+g7RbMbPamIr70j2fE7XLMbPUWMp72ZXNo7ar4gPamIr72rBt87ar4gPeOPn72HstU7Z74gPUWMp70pAMg7Ht4lPamIr70Aqsw7Gd4lPeOPn71SVsM7I94lPUWMp72gYis7krNGPeOPn73RDyI7lbNGPamIr71utTQ7j7NGPUWMp72VTKg4t1ZTPeOPn73rDIK4uFZTPamIr70SU2k5tFZTPUWMp71mfZK6RrtGPUWMp71mfZK6RrtGPeOPn738IqW6SbtGPeOPn738IqW6SbtGPamIr72er3+6QrtGPamIr72er3+6QrtGPUaMp71ExsK6oeAlPeOPn73ia9W6neAlPaqIr72oILC6peAlPUaMp72lZci6TakgPeOPn708C9u6SqkgPaqIr70OwLW6UakgPUaMp73HLsu6v2gbPaqIr70xibi6xWgbPeOPn71a1N26uGgbPUaMp70WZNK6fDvVPOOPn72sCeW6DzrVPKqIr71/vr+65zzVPEaMp70bZNK6/Vy+PKqIr72Gvr+65l++POOPn72xCeW6Flq+PEaMp70gZNK67z6pPOOPn723CeW6kTapPKqIr72Jvr+6S0epPLKJp714ntK6PILoO4Qdob3Ft+C68gvmO+H1rb0rhcS6iPjqO2GEp72pWKm6kkWNO0HQnb2jALy628eHO4I4sb2xsJa6SsOSO2mSp73dtc06gskvudXztL3FOOQ6TNkrOdXztL3FOOQ6TNkrOfwwmr3yMrc6GNsCuvwwmr3yMrc6GNsCuqLkp70ye1o70y/ou9mlsb0vz2M7nX7fu9mlsb0vz2M7nX7fu2sjnr00J1E7COHwu2sjnr00J1E7COHwu7AiqL1BALM7RtwlvLAiqL1BALM7RtwlvEWnob3tea87A4sovEWnob3tea87A4sovB2err2XhrY7iC0jvB2err2XhrY7iC0jvCcqqL0kQFQ8fgIkvAkEsL2oeVY8f9IgvEdQoL2kBlI8fTInvC/wp73OeLs8wqEXvC/wp73OeLs8wqEXvEaRn73lV7o8fJ8bvEaRn73lV7o8fJ8bvBdPsL26mbw8CKQTvBdPsL26mbw8CKQTvHRZp72yi+08f00avNJXnr0Sf+w8eRcgvBRbsL1TmO48hYMUvF6Dpr0HwQQ9hKYpvBZssL3xOAU95U4hvKmanL0dSQQ9JP4xvPVflb0hAgQ9n/D1u2+ckr2EvAM9YNwsu2+ckr2EvAM9YNwsuzqPl71b/Os8FdrduyADmb0x37k8Ss3RuyADmb0x37k8Ss3Ru/HSlL0dXuw8JVcZu/HSlL0dXuw8JVcZu1i6lb1m1Lk8hhODuVi6lb1m1Lk8hhODuVi6lb1m1Lk8hhODuSpRl73k5O08OqUJO865mL2KB9A8w5uJO865mL2KB9A8w5uJOxpSmb0cFHY8APqpPBpSmb0NbWA8AfS+PJxslb3GuUo8hcmoPJxslb0YEzk8r46+PBMclL25xNk7QEeoPBMclL3xD8U7hmW+PAQHlr1VYT080vglup1slb2g/4A6HV+oPJ1slb2g/4A6HV+oPJxslb0/HUs6NWG+PJxslb0/HUs6NWG+PBlSmb1JeMC6j8moPBpSmb1k5sO6E16+PBlSmb3rR8e6IurUPJ1slb2WBBU6tJbUPJ1slb2WBBU6tJbUPKtYmb0OGM+62WobPT1amb3YVs26gKggPVePlb1byqG543AbPVePlb1byqG543AbPdmflb2Xw865P6ggPdmflb2Xw865P6ggPQpLlL0/VCw703cbPf5klL109Bc72aYgPWSPlb13drg7DoMbPRMclL3grbA7OHHUPJxslb0dyCc8wZXUPBpSmb0tZks8zgfVPLFYmb2uveA7JaYbPTlamb29ds07c7EgPfVcmb3HUrk7E78lPZGflb0MFqc7B6UgPXTBlb0kmpM7ubclPZuclL3P5QE7K8klPfhMlr2zFyk7ntc0PS1vmb2b2B87GnI/PXq5lb0Bty06asA4PfhMlr0LKy66otw0PfhMlr0LKy66otw0PaVgmb12zAu4J51KPS1vmb17MaC6VHg/PS1vmb17MaC6VHg/PQldmb326ce6z9slPXbClb007fW55dQlPXbClb007fW55dQlPcAXmr0EbUs8AR/guxMKt72/eQU9qjrLu74/ub1VWQU98NDhur4/ub1VWQU98NDhugmItr0DK+88NMa7u8q0uL3wzu88Yu27usq0uL3wzu88Yu27ugJDtr3BPL08iMO1uwJDtr3BPL08iMO1uwsGub3nhL0802slOgsGub3nhL0802slOgsGub3nhL0802slOoaltb0UCFI8YnjHu7seub3FskQ8vR+EOYTLur3IuOk7iJqoPIXLur13mdQ7eXe+PLeCub2MhL06TKWoPLeCub2MhL06TKWoPLWCub1jz1I8UxOpPLWCub3r3kA8fZ6+PNextb3mcn08mCKqPNextb2Ul2c8kPu+PDvLtb3ActM8B0iYOzvLtb3ActM8B0iYO5cOtr0FE/E8GGciOxKNtr0NyAQ96twiO9extb39Z1I8gQrVPLWCub1tWi8825vUPGartb3xhu47XqYbPemptb3jO9s7erEgPblgub2fRsc7LIMbPetQub1c3bU7F6UgPaWdur3BQUo79XcbPUqEur2E0TU75aYgPcZgub1yQBU5/nAbPcZgub1yQBU5/nAbPYLLur1l5L87hnjUPLeCub1D/4U6Yp3UPLeCub1D/4U6Yp3UPLWCub0tf6E6k3C+PLWCub0tf6E6k3C+PNextb1YNJC6Qu/UPNextb1fxIy6Imm+PGurtb31EZi682obPeSptb1AUpa6jaggPSentb0455C629slPaVQub2/Tms4TKggPaVQub2/Tms4TKggPdMuub3QW6G38NQlPdMuub3QW6G38NQlPfJNur11rx87OcklPYCnuL1IUGO5W9w0PYCnuL1IUGO5W9w0PWuVtb1DcVK6Nng/PWuVtb1DcVK6Nng/PZs3ub2k05E6K8A4PYCnuL2YbUY7Vdc0PaKjtb0YiMo5G51KPWuVtb0mVTs7+nE/PTyntb3zFcc79L4lPc8vub07WaI7z7clPdextb24Q4m6/PSoPMarvr1P4bI9RbFVPIuOwL1qMr49+xhbPP73wL3ad8s9nKt3PKxThb2+JjE+m6OLvT7KLL3WIDM+ghCtvThCtL2lLC8+YW1UvRrUh73B3UA+mr+TvfuXsr2zkT4+oFNgvW8gOr3PKUM+Y1W3vT6QhL0zv1U+eJKVvSRFN70zk1Y+cFe5ved9rb0z61Q+/ZpjvQtPc70R628+bmiUvcuTpb07s3A+fpdgvcJ0a71lv4Y+wtCMvWdpnL2NJYc+U9dZvfzsXr2Z/ZU+24uFvRtTlb1nGZc+qk5SvfzsO72iM6U+72GDvUcaeb2VwKY+NN9jvfNHGL0U07M+SQdyvXUNS709ZrU+Gw1avQ9A7rzuB8E+9fVWvfBWIb3WQMI+SuZGvRxHwLzUMMo+AtZBvacLBb321so+pug3vZn+qrzkTc4+DJs2vWeS77wEuc4+lCQvvdb8p7z5bc8+dHE0vdb8p7z5bc8+dHE0vQcD7LyIyc8+lI0tvQcD7LyIyc8+lI0tvcrir7xCEtA+YYI5vSIw9rxWYtA+yZwyvYJxxLwuE9E+mH5HvWQsCL0NUdE+LENAvaq++7werNQ+JZxtvXeVK72l79Q+D9xjvSRKIb3z39w+4zuPvfKOWb1orN0+xYqGvSrEML3gyuU+FjGavRjjbL3/Eec+URuOvd0oN72Z/e8+BxGfvWuZcr1QMvE+YwWRveAoOL2w1/k+jhifvTnWcL2cm/o+AzKRvT5OM71YiAE/cvuXvRX+ab1znAE/mrqMvSs/K71FoQU/AQeKvU+5YL06WAU/IJqCvZgNML2ueww/R4E2vQZ/Zb26YAs/Zos9vdWaaL06gQ8/LkULvZTuRL33dBA/zDgDvYwjhr1+jQ4/kFETvVkYi73KABI/osjNvAd5m73cpxE/eNXLvFhvdb26WRI/zrvPvBgykb0DxBM/LrJ4vE8ueL2hYBM/B1SLvAZNpr1lJxQ/TrxavAOvkb2VhRQ/5+bQuwOvkb2VhRQ/5+bQu3W/dr3arBM/yDAEvHW/dr3arBM/yDAEvEr+p71OXhU/PmyZu0r+p71OXhU/PmyZu5TLkL067xM/rnJZu5XaeL3q8xI/HIeZu9sppb2J6hQ/R67/uhsKkL11zxI/eoWyuxsKkL11zxI/eoWyuysAfb3e4BE/wQLYuysAfb3e4BE/wQLYux6Uob0JvhM/NgiNux6Uob0JvhM/NgiNuxE9j7087xE/Hl4uvDz6fr0sHBE/J1E9vAT9nr1MwhI/FmsfvBtHi71aBxE/0XZ9vCtGfb38oxA/jt98vCHrl726ahE/FA5+vCtxeL3uZA8/Iv+RvAs9iL2jNQ8/KsCbvEVoYL06lA8/Gj6IvIW4Xb0h9g0/G+eOvN2cSr0uqQ4/xi2EvHDVcL0xQw0/UZ2ZvELYT70Hvg0/mXxYvL4VYr1RtQw/2XRrvL54Pb08ww4/jRxGvLtmR70pFA4/qM8FvLtmR70pFA4/qM8FvGDBMr0sMg8/I4Xmu2DBMr0sMg8/I4Xmu/nFW73s7gw/eZUZvPnFW73s7gw/eZUZvAuaN72zQw4/cPS2u0LzUb0q8gw/tZLauy3gHL1Hiw8/RMKWu7lIGb2Z4Q0/6jk3uyX6R72LXQw/XmIBu/DXCr0FLAw/RBpPPDQrQ72DJQs/ujEUPCavBL2zYwo/jnLpPAGjPb1HtAk/qY28PHjh27z3dAY/FOJFPSe6Ib2lpwY/wMovPVXp17z3pgE/gSZ9PZaAGb0P4AI/B+hlPYZN9rzQav8+xx6BPZ1BtryJZv0+XzqCPXSVGb0ttQA/D0t3PT8dAb13h/w+bXl5PWaiFL3n2/0+KRN2PY5g3LxBOPs+yIV7PaC1Ar2KOfs+blRxPaC1Ar2KOfs+blRxPfiC67xKPfo+zAF0PfiC67xKPfo+zAF0PdhzEL0mOfw+1+huPdhzEL0mOfw+1+huPQzlAr3S0/o+2DRrPd1UD71Yuvs+CKxoPRh47rz07/k+7TJuPfFhAr3Pffo+RgdiPUau7rycrfk+ZHFlPacoDr0ZUPs+HSRfPcKqAL0iyPk+HxJTPUBNCr3Ldvo+4IJPPfwX77wcGvk+9iFXPR0K/ryhb/g+MdlAPQsA87xL/Pc+q21EPcCsBL1m4vg+GnM9PX/HAr1w3vU+klIyPQmcDL2GDvk+bRU6PTREE71AHfo+tjZFPcKuD72z7vc+kKYxPZCIGb2LBPk+hE05PVkzFr30C/c+xEotPfJDHr0Od/c+hKEwPdTeFL1Vr/U+O1YrPTroH73pl/U+bw8tPT4BF73ZUvQ+6GIsPWwUH709y/M+XBgvPc86Eb03nfM+44AvPUlxGr0XZ/I+ww80PbR+B7359/I+/zs4PUKaC72Ba/E+Jw9BPXmaAL3V6fI+4XA/PUPoAL065/E+gVZIPbAF9bwi//M+ZzRBPVi57ryLAvM+f15OPQvy6rxu+vU+cptFPbkR4LwwCfY+cQpVPWp96by6Ofc+n3hJPfI347xSsPc+jsdWPcLH4Lx0/Pc+WzJjPdhH4LyZHvg+vnFrPRlw3LwFEPY+htlgPevP27wpEvY+TcpoPQ7c7LwAfvI+bglZPafA7Lx5QPI+f6lgPVJ7Ab2CPfE+QPNRPZ8UAr1Z7vA+g5VZPQYRDr1doPA+OyVKPU2nD73jSfA+GwBSPa7KH70DzPE+olc7PT4zI71Ck/E+FyBDPWm0JL1hd/M+Fik1PTQbKL2mW/M+5g08PZTJJb04kPU+m1QyPRnTKL3BlfU+Vlg4PeTnI734vfc+4h43PaHOJr0Z4/c+I4c9PTSOH72Fj/k+YcdCPaF1Ir3Z0Pk+uKFKPWeOGL2B0fo+XD5SPT7TGr3gLPs+LXxbPS6jHb3Qm/s+X3VhPS6jHb3Qm/s+X3VhPaEqKL0rBf0+UJ9mPRt4Jr1YHvo+e3hPPRt4Jr1YHvo+e3hPPV6sNb3rFPs+ffBQPZJzK70iD/g+NyBBPZJzK70iD/g+NyBBPRtoPb2ahfg+ui4+PSYZLr05oPU+O8Y7PSYZLr05oPU+O8Y7PX1pQr1CkvU+cR43PQZDLr1GRfM+6NpAPQZDLr1GRfM+6NpAPdRlRL0XxPI+h7Y+Pf5fKb14YvE+7kNJPf5fKb14YvE+7kNJPYljPr19lPA+Wd9KPc8/Er0d/O8+FPBXPc8/Er0d/O8+FPBXPQM9Gr1OGe8+siddPce8Ar0DpfA+6RdfPce8Ar0DpfA+6RdfPccCBL1z5e8+MhllPSND67xuB/I+cf9lPSND67xuB/I+cf9lPcdr47xdefE+p9FsPdgS2LxCE/Y+X11uPdgS2LxCE/Y+X11uPQr+xLzBEPY+Fk12Pdlx3Ly5RPg+mxNxPdlx3Ly5RPg+mxNxPeqmyLxHt/g+enR4PX5PmrwdoPk+VsiBPfQ4mbz09vU+bkyCPeSi0rzwTvA+WkJ4Pbikwbzin+4+tu6CPQ5UBr3PUu4+AeVtPQuxKr19LO0+OxNjPS0KCb34NOw+Jax3PS+pPr30feo+8qNnPd3TEL0B2+Y+3FmBPYYPtrxgTek+6UmOPenpUb39T+Y+G0tiPdU4G70U0OI+PS59PWS+4rzTDOQ+R4qIPSqBFr0nXNw+rHddPR37T705LeA+OGlcPQdDRL0yQ9s+HjpPPTn0hr1uDdw+e1ErPYLVc73Cwdk+cKctPRlwmr0LjuE+i+0lPf7OlL2X2dU+9y26PK9iqr0tUtc+iprQO69iqr0tUtc+iprQO0o6d73C4dU+k4YGPUqKgb0d4dM+M9lWPEZQZb2olNM+C4C8PG0vib0HZtQ+kwcNOm0vib0HZtQ+kwcNOoBecL2ZeNM+N3YsPMZgWr1c9dI+uiShPAFtab0uRtM+hs0bPAFtab0uRtM+hs0bPBz2eb3o9tM+UoJxuhz2eb3o9tM+UoJxumfOb72rzNM+INfKumfOb72rzNM+INfKumfOb72rzNM+INfKumfOb72rzNM+INfKurUkcL3C+NM+kZ1OvHDyW70wdtM+lBS7vL/7hb2ZnNQ+Q+tFvGdpZL3Lu9M+bp9SvGdpZL3Lu9M+bp9SvLmCUL3PGdM+r5G5vLmCUL3PGdM+r5G5vDvJZ73fPdM+RedUvBSCU715htI+H8i6vCPbc70VU9M+GL3ZuiPbc70VU9M+GL3ZurlDgb15FNE+uGpWvBQabb22NdA+z8C+vKckh72TK9E+Yz+/uqckh72TK9E+Yz+/usWJmb29w8w+GGhcvHMCj71EZMs+2+TEvJJfnr1V8sw+kNPWupJfnr1V8sw+kNPWurlLtb2VQcc+OIxivKCorL0/z8Q+AKPJvHVOuL0Qt8c+R/QLu3VOuL0Qt8c+R/QLuykN070M08A+c+FavPCby70LPL0+iXXIvPLU1L2di8E+ToTwuvLU1L2di8E+ToTwurAq7709s7o+YmpLvKdg6L3SfbY+ZMK9vJ7K8L1Ug7s+XADGup7K8L1Ug7s+XADGuvwNBb4oRbU+g3BDvIy4Dr4ZZLE+nxJAvCbuB742RrU+A85BuybuB742RrU+A85Bu+fkAb7ko7E+BNOqvAe3C75eQ64+owOdvKHl7L1IRaE+V5f+vGRKA7770qA+URnYvFNNyL1DG6I+I84bvcN/7L1qgpw+jPSpvPA0zL1eRZs+ah7AvJQjAr6NZZ0+eaybvBg78r1xwps+Uimuuxg78r1xwps+Uimuuwafzb3BQZo+uXlxuwafzb3BQZo+uXlxu+lcB75z35w+Ad3ju+lcB75z35w+Ad3ju5cY871l55w+z/o/PDfEzr0epZs+0fJ/PHNtB74l0Z0+ZFUQPKIg9b3asKE+E1K3PP2zzL3ctaI+D2D2PO5ZCb6eG6E+igOLPLaMBL6s37A++Ml+PEODEL456qw+Pw1UPKsg6b3lV7Y+ffOaPMEIB76UdbQ+j9fuO6egEr6B+68+T06+O4oQ7739gbo+wS8WPBzF0r0Ko8A+8c4ePFk4tr2j6cY+K0ogPCY2zL2FHL0+yuumPL65rr2sesQ+vA+pPKbGrr1Uz64+0x8VPS/5kb1XcLs+BX8VPWMnmL0nKao+IhgtPcKyrb0DkJ4+lAISPRi+fL3G/Lc+XCwrPTuETL3UG8Q+ZwscPcMTcL2eIsY+LIsLPeJ/K725R8w+JEcMPbP0Sb2PZs0+VZL+PL1YGr368c8+Ih8CPdeZNb3Lr9A+Ct3sPFReFL3c8dA+AhAAPVReFL3c8dA+AhAAPfYKL71XjdE+jcroPPYKL71XjdE+jcroPLE7Eb2Vo9E+hY4GPcqADb0m4tI+iIUYPYsiLb0uItI+UOz0PLneLL25M9M+7HsLPQoYNb30VNY+q3AvPQf1Cr3CS9Y+GblBPYRoV73modI+BfOWPIRoV73modI+BfOWPDAbXr1GDtI+qn+YPJVXeL2itM8+6LihPIvibr3jxNI+Ja4bPODGhL0DldA+sg8lPN7bm73wUcw+QNwmPFZNk70s5Mo+WDCoPLjAuL3OTJk+IGmaPDu6r73B95M+PCa0PCX2ur0uL5g+93FduyX2ur0uL5g+93Fdu/lLtr2W+ZI+i/ZCu/lLtr2W+ZI+i/ZCuxn4tr2T7pg+6HrUvBT2rr0vF5M+y/XlvE6Fqr1E/J0+FRkwvZYolL2Rk6k+FWlIveLHqb32IK4+UZ4vvStTdL0me7c+RkNDvQncjL1yz7o+rBcrvRUkRL2EvcM+4XM0vet8Y70J0cU+CbEfvU7nIr27zcs+CfoovYbzPL1PFs0+4IEWvU9OE71dec8+08MhvTmAKr2ra9A+socQvax4Eb15fNA+iYkgvax4Eb15fNA+iYkgvYuEKL0bVtE+pIEPvYuEKL0bVtE+pIEPvVgHGL0fEtE+OBslvbOMKL1n/9E+5kExvVDVML3Z49E+xysTvU5/Rb1L09I+g7kcvXQhfL2CjtY+A3czveAOeL2iSdQ+ly2+vPlmoL3EL9g+wlDGvMdLqb2UM9g+izc4vCvfx71ds+I+ZXnZvItE0b39y+0+4hLpvIBPzb3C9+I+5PVbvHmen70H598+de5IvaY2q70c8Ok++qxRvb6Qhr3aqd4+5zBvvQLIVL1ipNU+lqlPvTh/kb1oYeg+q8Z5vS7gk72emvI+5i+AvQexkb1wzfs+5O+Bvcp3q72yivQ+SnNavVYOpr1J1P0+XJRhvcfdyL06uPk+vx7wvItaub0XOAM/TCbyvPB+yr3Ba/o+RkFwvIosvb2cJAM/vJlUvKJOw70Zc/o+wWEvuqJOw70Zc/o+wWEvuiIL1b1GLu4+7Nt4vDsa0L2+a+4+wBbYujsa0L2+a+4+wBbYupwHub12nPE+fRqtPIZGzL2tTuM+EAH5OoZGzL2tTuM+EAH5Or2quL2Pseg+JjDxPHqjlr0LG+8+huklPecMib0mH+o+/DdDPVfUlr2LBvU+4HkZPY71dL05YvE+KUI0PRJ8cb1TVvU+wjspPSvTab2ig+4+Nk5IPbmcZ72vuPk+eD0zPZKsVr2UYf0+CftLPfaNj70AZPs+Fz0kPdLlfL2xSQA/kOo5PflGrL0UEvs+N1CwPPeQl73XEAI/GgLmPIYiaL11/QQ//j8PPb2Iqr0bJQQ/Coc9O72Iqr0bJQQ/Coc9OyImgr1VYAc/Cy5zPKSjk71Hhwc/x6A0ulP4t71DTQk/Xfgxu1P4t71DTQk/Xfgxux01p71hXAc/fu/6tw2Vo72ZRQg/eYFUuw2Vo72ZRQg/eYFUu6Ivsr0bNwo/2b8uur7UmL1h2Ac/JxFXu8RPl72ldwg/cYnKu8RPl72ldwg/cYnKu6/xib3arQk/p6bpu+mNiL1DRAk/id6lux35ib3+CQo/e7AcvB35ib3+CQo/e7AcvJxXdb1lbAs/CTADvFxfdL0LpQo/i7KSu9gueb0Xmws/+3EqvNgueb0Xmws/+3EqvK7VfL25fQs/fmB6vDyLir2lCAo/AntuvCfhhL3uLQw/vEqfvDrKj70Dxgo/ccSavBnYkr3RXQ4/qY+evLR8m715EQ0/kr2cvATooL0J2BA/+Ph9vOtrp73Xqw8/5M9/vCflp72+XBI/as4cvIe/q708ZBM/Z5aGu4e/q708ZBM/Z5aGu+bYrb3ORRE/FxshvE9Usr2qRxI/CKuQu09Usr2qRxI/CKuQu2kIsr0uxg8/3m8hvD3+tb0TnxA/yRGVuz3+tb0TnxA/yRGVuznqrL0BQQ4/6i52vLjqsb2ajQ0/FLEYvHjhtL03Dw4/qPyHu3jhtL03Dw4/qPyHu83arr0p6Qw/aqRbvKl9q70qggo/VxoOvLmVrb3nlwo/qYtXu7mVrb3nlwo/qYtXu0Yrqb0OrQo/PaRlvIDVob3+agg/iZYPvAG2ob1X8wg/uZJcvJ2Rlr1RkQg/zxg9vLAjmr0VVQk/TyuGvAGPo70Bogs/FTmPvBN4ur3uUg4/Oh3cuopWwL1xAA4/C19pu4pWwL1xAA4/C19pu/ltvL3TTRE/c4kKu+Y6wr3DOxE/fXWCu+Y6wr3DOxE/fXWCu/jmuL0uNhM/DFUDu5wqvr1cUBM/qgGKu5wqvr1cUBM/qgGKu0Q+sb26eRQ/V/LduiJptb3puxQ/JgmKuyJptb3puxQ/JgmKu6nJs71+RBM/fh5MvE6Uvb3pzBE/T10+vLavqb2xhRA/XQbBvKB1tb3sBg8/1OyxvNJpl73OQQ0/xrQMvdF6h70DIwo/baA0vWq9pr1sqws/xAwCvfLymb3Bigg/WP0nvU6Os7182gk/Aq7jvBb9qb1qcAY/cg0Xvf7tvb1ROw0/CgWdvNmwu728eAg/bbGuvPSswL2FqAs/VyyJvP31vL0ThQg/jpk5vNTXwr2J3Qw/GKYjvDU0w726yg8/xZorvBPHnb3ckQI/Ya9ivd31lL3TvQQ/cHxgvae7jL1q4AE/nFl/vVXdhr3jDAU/MDBxve7rfL0RLAk/SD6AO9ibTL1HBgI/0cFQPQZ2O71tFAA/GZpkPYnMtb10nYc+HcDwvOVtv734kog+ebo0u+Vtv734kog+ebo0u9lRwr0WcnI+KWn4vCbFzb2/7nQ+Gko9uybFzb2/7nQ+Gko9u9gvzL0FoFU+hsL8vGZa2L05l1Y+dQVfu2Za2L05l1Y+dQVfu9560b3TUj4+eq76vAEY3b3Emj0+DeGBuwEY3b3Emj0+DeGBux0I0r1AHC8+8evuvNUJ3L3oTS4+KOGJu9UJ3L3oTS4+KOGJu93y0b3k4is+X42uPCqJ0b1WFzs+FkrBPHdPzL3PrFY+2jfMPIt5wr1ZhXc+h6zNPDMRtr0l04k+ZgjFPLOkE75LmrA+XEePu7OkE75LmrA+XEePu0fIHr5daas+EBWOO+nvKr4616Y+p7c7O3pJHb74Mqk+ZHAjPEFpH7697as+VZlfu0FpH7697as+VZlfu84tK74wQac+8qMgu84tK74wQac+8qMgu/UFIb7mdKo+/ZcYvKbPKr7TzKY+8ir/u0U8F778HK4+gJoxvHj3H74PSqg+AA5tvEQFFr7MKas+Ua2KvKvpKb5UaqU+XcFEvLuhG75v6aA+KE6PvA+EJ77K+aA+Bk9WvGe/D74Y2aA+zXSzvPYrGr661p4+kVNLvAvrJr4dnJ8+P/sPvN9sDb5TEZ4+8lWDvIYMF76cB54+uvmcu4YMF76cB54+uvmcuyK8Jr7GL58+6SwsuyK8Jr7GL58+6SwsuwseF76jup4+6ffJO6LOJr4hpJ8+EYpmO1VfGL4HEqE+RiNLPLxkJ75uCKE+dT8APJ0FKr7hBqE+6ovmO7APKr61e6U+FaflOw5JLL6X1qQ+bAXVO8bjLb4hWqQ+cjPNO8bjLb4hWqQ+cjPNO5YMLb7wC6Y+ZpgqOzs+Lb4HcKY+vI8iuzs+Lb4HcKY+vI8iu0SjLr5ReKU+g5QgO0SjLr5ReKU+g5QgO/TOLr4d2aU+T9cju/TOLr4d2aU+T9cju/TOLr4d2aU+T9cjuwLeML4VzaQ+RDoeOwYLMb6HLKU+La4ku23jL764kqM+kujhO3l+NL605KM+z14bO6nLM75c8KI+TnPYO17tOL7MvaI+oOwaOxekNL7UP6Q+9gMluycUOb7aGqM+NhAlu4E7NL5V46M+UmLvuzyjML4CzqQ+0Jfwu3erOL4RwKI+GTbvu5V8M74636I+rWkyvPjsN74PwaE+SzYyvFfhL74jwqM+WdEzvHAJMr6x4p8+zEQ1vNxcLr4Vp6A+qcw3vBJzNr7o154+P+g0vM66Mb7u0p4+wTP0u0oJLr7llJ8+xbX1u0AXNr5s1p0+9vLxu4ltMb4BG54+vAYruzywLb6OB58+v1nUusLLNb5YPJ0+7XnVuuvKML6YfZ0+g5v3OqSDM756BZ0+qIM8Oxj/L75D4Jw++KrEO2Y2Lr6jQp4+zr8+OzQLLr51gJ0++OXcO3Y3Lb7lA58+MbC8O5mvLL5apJ8+fVlmO05LLb6+K54+rq0OPHliLb7B758+se4KPDjsLL56rKA+g6zjO290Lb7B8J4+tZ8vPLPlL76B5aE+428ePAVkL77TY6A+nX9DPEG4Mr4oo6E+UIYdPKKLMb6HMqA+BiREPF00Nb7coqA+8MMdPBNsM76ndJ8+/udCPOYKOL4Mj6E+nwTfOwVuNb7qEJ4+b3EIPGLIN76zJ54+lLTbOxO1M76njZ0+LEEtPLfcNL74PJ0+/C65O1QrN75ISZ0+4gxcOxk1M74I1pw+ufkMPEITMr5IkZw+tbbbO4+aMb5DX5w+UQ49PDv/Mb7q8pw+MhBTPIERML7lGpw+2tBfPP3DML7+HZw+h0AoPFvVL77A8Js+EfRMPMU6L778Tpw+PrIhPDvBLr48CJw+7SxRPFDXLb6wypw+BqcoPJjqLb6oYpw+xidNPIRaLb6uUp0+nDA+PIG/Lb6eopw+ojVgPBSELb5a7p0+gqNUPBbqLb7TGZ0+Wf9vPPvgLr4x+J4+n0JlPEm3Lr4QCJ4+Ao14PHRqML4XzJ4+2eZmPMqUL77AuZ0+NDR9PKXNMb5+S54+H9xkPAuiML4nlp0+Tll4PNlTML6Hjpw+dnVvPPzkLr6lnJw+EZZ5PNezLr6DMZw+Y8RrPP2eOr5Kvpw+yFkhO/BEOr6hhZw+QOgfu7OBPr4OwZs+8lMMO51DO74hkp0+l9DEOw4+P77sa5w+ZAm2O0/mPL5qaqA+0KLAO+YrQb7svJ4+0y2xO1l9Pb4xZqE+9JgTO2HKQb5WjJ8+lMUDO+qfPb6OvaE+cCwluy7uQb421p8+GfIlu4k3Pb6VZ6E+7knuuw2IQb4Gjp8+zdTnu+94PL6/daA+VGIxvKbIQL54wJ4+jZYrvB3oOr4vup0+7g40vIIYP77RcZw+vh0uvIZpOr5B25w+P3Dyu111Pr7KxJs+UjrsuxM2Pr6hjps+CfQlu70ZQr79kJo+EVTcu5mwQr5lAps+V0kfvFcKRb5cvpk+r6DLu5feQb5Oc5o+VSQnuzQBRb5Rq5k+oaUouwEjQr6DlJo+z7TUOqIZRb7Fwpk+iCqMOv7NQr4RBps+AmCXO739RL5TI5o+6tJgOx3BRL7+k5w+aeCSO6acRr5+C5s+TZ1aOw1tRb7xEp0+mWXEOrJrR75M4po+03+BOiWYRb6TQZ0+Sa8nu0+eR74G6Jo+s0Qpu9A1Rb7cFp0+z7vYu+5CR7445po+OYbJuydvRL5dmJw++y4dvAleRr5sD5s+UgILvPXfRL61Hpo+m14MvN3FK77B+qA+D3vYO93FK77B+qA+D3vYO31iK7543J8+dYg5O31iK7543J8+dYg5OxRuK77mdJ8+jyAhuxRuK77mdJ8+jyAhuxRuK77mdJ8+jyAhuyWKKb6MzJ8+R+pGOyp6Kb6VYp8+HBYpuyp6Kb6VYp8+HBYpu9CYKb4DyJ8+HWsGvB0MKr6Y/qA+Hs1HvD2QK76V2J8+P4j/uz2QK76V2J8+P4j/u4TsK74x9qA+UyM+vITsK74x9qA+UyM+vMafLb4xWKQ+MFY3vMafLb4xWKQ+MFY3vK4GLL7BzKQ+i7M8vCppLr4PdqU++57zuyppLr4PdqU++57zu33bLL4PBqY+vzz4uwP8Cr5lMp4+6rKavIlyCL4s3p0+iAeavC7ADL4h4KA++SXAvA0vCr644KA+GxvIvKB4E75r3as+Mc+RvBZWEb6wgqw+xsCVvP0JFb5Ula4+9uNNvBdSE764fK8+lS9HvLLvEL4GMbA+AlpUvKm0Dr49Uq0+uAyavJH6Br4Y4KA+flXRvNmGBb7K350+svSmvDTc4rx7GN0+YbtvPWZbpbw93N4+6OOIPXgZq7xgweM+BJSRPZuQV7wdJOE+y/OTPXBsR7zeT+I+NiqXPQdIKbzkI+E+A4+UPbk5H7wBI+E+cfOTPbk5H7wBI+E+cfOTPbuBHLzPnuE+dOGWPf129ru9A+I+SkmZPdD7E7zLguE+GQCWPdD7E7zLguE+GQCWPcWoD7wHnOE+uBCSPfxk67s2zeE+ZQaYPfxk67s2zeE+ZQaYPSIO5rvG++E+Cq+TPUDa3buYauM+I6ZtPfBoFbvUwOI+S+tTPQqmCrxuaeI+ApttPeBoFLzUTuE+RX1tPXz6jrsyN+I+NGJWPcCna7sLfuE+S+tTPbMqGrx9J+E+9maQPcewHbzAV+M+QPCaPYnKfrwUG+g+KeybPWTqQL1HNhE/7TOluxNlM72OnxE/oG0SvBNlM72OnxE/oG0SvPyVTr1KfBA/Myjiu/yVTr1KfBA/Myjiu6sQVr2R4g8/whhEvGINNb0zXRE/j9ynvNix8by+xlY+I5vGvUJTpLzW/VY+yybKvesw+rxx8Ec+OqbEvUINs7wZMEw+CSnIvVId4bxvNzo+PaW8vWfHo7xWiEA+RUzCvdKyi7yfSUM+PEPFvdKyi7yfSUM+PEPFvYfJibxrYUM+YKLNveqwlbwJ/00+6y3KveqwlbwJ/00+6y3KvQr/krw5Q04+ajrSvVZfg7zb6VY+njXMvVZfg7zb6VY+njXMvWb/fbzrI1c+lEDUvcyphrzIYVg+P4HnvZCMnLyXVE4+/IjlvVQulLzgTVo+lGgBvlKtrLw7g04+9ngAvs4jo7zPHFw+HrwQvvC2vrxbuE4+Q94Pvobcq7w7N1w+C2wgviJHybxGSU4+Dacfvl0+pbyElFk+pxMvvrQdwbwhv0w+vIkuvhy9srwk3T4+kUQtvqbztbyksT4+BBUevmKBrLyqaz8+sAkOvkadnryJxkA+ivH8vbDKkbw0SUI+WzjhvYunvr3v2+w+CrYpvE9vvL27Pew+yVvzu/QYy72vYOs+QlUEvItBwb3dg+4+y4I4vJvFwr12LPA+TTwivJvFwr12LPA+TTwivKBSzb2Z0uw+HXURvCSqzr3MP+4+nLb7uySqzr3MP+4+nLb7uwCy2L1iuuo+24zAu9f22b2LyOs+dyKdu9f22b2LyOs+dyKdu4Qj171Cguk+/WSnu53m4L05leg+QSQtuwkO470Tweg+t/mXugkO470Tweg+t/mXuhJg4b30Zuc+z16wugVl5b1E9OY+pRGiOgVl5b1E9OY+pRGiOkaD3r3jdOc+f55CurJS1b04I+k+zk5Ru70x4L2wZ+c+90KSOr0x4L2wZ+c+90KSOuIm1b19g+k+JJ99uuIm1b19g+k+JJ99utTt3r1zlug+1mTAOifp1b0evOo+ejSYuLLf4b3Pweg+EqiqOjX6173Hyes+oooruidR4b3Ktuk+E1HiuXRI2b1LU+w+jYw0u1cNzr0Kzu4+y/6juwYozL1dQe4+gNAgu6YYwr14zPA++czeu2vgv71DLvA+Wnl9u2tGvb1Whu4+WEZCu13Cu7273ew+HrCNu13Cu7273ew+HrCNuwbeyb2/1Ow+qq3butWWyL09Yus+QLg6u9WWyL09Yus+QLg6u1Ijyb1R2eo+2da1uwcBCbzlgw0/GvwfOwcBCbzlgw0/GvwfO2WnJrw7Uw0/f981u4LdTbyHjRA/dFz7OoLdTbyHjRA/dFz7OivAZ7usBQ4/OBeQO4sHqTp1hw4/F/wfO9UeCbyR/BA/8bRsO0q8ibtlbhE/b1z7OlCtV7yOnxM/mH9nO0RjI7zVAxQ/ztAPO6zzh7y1URM/ztAPO6zzh7y1URM/ztAPOxbRirxuVxU/BKuGO4odgbxB3BU/5PZUOw5eobxechU/5fZUOw5eobxechU/5fZUO7Rxn7x7VhY/anocO7Rxn7x7VhY/anocO8S9pbwM/xQ/8wkSOhbckby8IhM/HpLCug5eobxechU/aNyDuqzzh7y1URM/vq+iuxbRirxuVxU/C1FEu0+tV7yOnxM/2wjVu4odgbxB3BU/a9yDuoodgbxB3BU/a9yDukRjI7zVAxQ/v6+iu0RjI7zVAxQ/v6+iu9TIX7zTrxU/9wkSOmqiC7xeHBQ/I5LCugAFK7t1lxE/vEwru0e8ibtlbhE/oFPpu0e8ibtlbhE/oFPpuz0dSzshuA4/g981u4sHqTp1hw4/x+4CvIsHqTp1hw4/x+4CvCvAZ7usBQ4/XvsivAcBCbzlgw0/xu4CvNIeCbyR/BA/mtMQvILdTbyHjRA/n1Ppu2V8Z7yrYRA/uEwru5gFEbtniAw/N8PcOZgFEbtniAw/N8PcOX5gm7tniAw/pC3Pu1hXvrp+uBA/FQePuVhXvrp+uBA/FQePuQ13lDtniAw/yVNBO3S4ODxniAw/N8PcOXcvkDvZtRA/63j7OhymJzx+uBA/DAePucR0ZDvsoxQ/mV/fOieeAzweuRQ/YfsTuNQvoLoeuRQ/YfsTuNQvoLoeuRQ/YfsTuC5M8Do1fBc/y2ATO/U7hzs+Cxg/r6+NOssrtLo+Cxg/ra+NOssrtLo+Cxg/ra+NOjBGuDl1Rhk//h0muTBGuDl1Rhk//h0muQ7ZNLs1fBc/WJ4ZuwuYRLvsoxQ/fa6cu8ArtLo+Cxg/99qQu9QvoLoeuRQ/GzgYvC5M8Do1fBc/vU7ju8J0ZDvsoxQ/cZo4vPM7hzs+Cxg/+dqQu/M7hzs+Cxg/+dqQuymeAzweuRQ/HDgYvCmeAzweuRQ/HDgYvKCS0js1fBc/Vp4Zu2RgIzzsoxQ/fq6cu7z1SzzZtRA/w93IuxymJzx+uBA/PelDvBymJzx+uBA/PelDvEwnYjxniAw/pC3Pu3S4ODxniAw/vBNWvHS4ODxniAw/vBNWvA13lDtniAw/lYJ/vJwFEbtniAw/vRNWvHcvkDvZtRA/30xovFRXvrp+uBA/POlDvBQZb7vZtRA/w93Iu8Avjzws3Qw/yO4CvHQsnjzLrww/kN81u4vCrjwRew8/GiTYuxtxTjwuVg0/XfsivG4F/Tsuzw0/xO4CvG4F/Tsuzw0/xO4CvEDmizwX4g8/QD4IvBkGUzy6TBA/DyTYuxkGUzy6TBA/DyTYu1xCujwZVxI/uqO1u725oDyZuBI/2CODu725oDyZuBI/2CODu7iT1zyFEhI/4CODu06p6jwaMhQ/EHrrur3Z5TxlwxQ/Bq5xOb3Z5TxlwxQ/Bq5xOUc6Az2sYBQ/l61xOWzeBz3HZRU/HNhyO2zeBz3HZRU/HNhyO7LwAj2x3xM/9SzmOkey4Dy34hE/X/sJuko6Az2sYBQ/9/+SO0o6Az2sYBQ/9/+SO7mT1zyFEhI/h+hOO7mT1zyFEhI/h+hOO0+p6jwbMhQ/A/WtO2BCujwZVxI/7CSTO73Z5TxlwxQ//P+SO765oDyZuBI/lOhOOzlxzzyFhBQ/DS3mOnPSkzx5yxI/EfsJurBmODyDchA/S/cIux0GUzy7TBA/Ug0gO6cSwTuR/A0/dN81u3kF/Tsuzw0/H/wfOyJxTjwuVg0/NxeQO8Ivjzws3Qw/DPwfO8Ivjzws3Qw/DPwfO0TmizwX4g8/K4WHO4/CrjwRew8/Qg0gO4/CrjwRew8/Qg0gOyuZuzyrUQ8/YvcIu0xZzz1swPU+hK2Hu0xZzz1swPU+hK2HuxTLzz1RYvY+1mXuuyT83T3aBvc+Ru82uyT83T3aBvc+Ru82uyd70T2jDvQ+Hagwu/Ky1D0vXPI+LUJmu3nO3z3NjfU+BDbKurue4j2nFvQ+k1QTu4Ja7D2PDvc+mowquUvE7j1k7vU+VvY2umUY6z00Qvg+s+KTumUY6z00Qvg+s+KTujlT9D16Q/g+iayPOoYg9z300vc+yUxqOtTx9D0dNfk+fT4nOtTx9D0dNfk+fT4nOqyA+T0CE/k+mjcmOqyA+T0CE/k+mjcmOjd98z2Gavk+f6eZukss6z02r/g+Gphcuysg9j1iNPk+ewLvugUV7T35QPg+zamsuwVM9j1BQvg+Z4BFu1kj7z3UDPc+dYDDu91O+D040vc+VHvNut1O+D040vc+VHvNuuvA8D0q7fU+7I+eu+vA8D0q7fU+7I+euwci9z00G/c+kVlDuo9R8D0sbPU+mxE1ux265D2bhvM+w0mcu9kg5T0ZFfQ+rfj0u9kg5T0ZFfQ+rfj0u1km1z19uPE+viTSuyKY1z1iWvI+iG4cvCKY1z1iWvI+iG4cvEd21T0rDPQ+Qhs0vHw+0j2fvvU+vrQmvBRD4z2mi/U+K0YPvEJ+4D1LBfc+AmMDvHJX3j3Zkvc+EtC0uwRyyz0Lr/M+yWW+uwRyyz0Lr/M+yWW+uwwzzT1vjfQ+02MfvKLr3z2bLfM+tuV+u6Lr3z2bLfM+tuV+u4cOyz1VW/E+MvSMu14SzD3CBu8+D1+7u7mI3z3IKPE++pMpu4524D2tJO8+Xqd5uyqN8j1QP/E+520OukWl8z3Nq+8+igHBujs38z393fI+GlDJujs38z393fI+GlDJuuwJAD5Wp/E+YOiuOs+aAT4Y1PA+KPOAOhp6AT5wuvI+JQZ4Ohp6AT5wuvI+JQZ4Ol2qBD5iD/I++MlxOl2qBD5iD/I++MlxOsSRAD6NPPM+9mmxutQM9D2Le/M+iC+Ou4c9Aj59ufI+0YoSu8bI9T1j3PI+/r/huzhQAT7CpfE+cV+Cu7cm9j0UPfE+HdQDvD1eAj4m0/A+yRIQuz1eAj4m0/A+yRIQu8829j01qu8+XKzfu8829j01qu8+XKzfu1/IAD6KEPA+bSupugun9D3aAO8+cUaLuzUk4j3nX+4+qofru6+04z2qIu8+MSUtvK+04z2qIu8+MSUtvIcPzj0LJu4+NU8dvJDQzz1vBO8+JYBdvJDQzz1vBO8+JYBdvAw00D0jWPE+8Th2vDUwzz22rPM+gQNfvFQA5D0CJvE++vBCvMIp4z2WK/M+x3QuvNRk4T3k7vM+SiTvu6ZL0D3t2+w+A7YpvKZL0D3t2+w+A7YpvGsTzj26Pew+sFvzuw+93D2sYOs+N1UEvA+93D2sYOs+N1UEvKbl0j3dg+4+xoI4vLVp1D11LPA+RzwivLn23j2Z0uw+FXURvD1O4D3LP+4+hrb7uxdW6j1iuuo+zozAu/Ka6z2LyOs+aCKdu53H6D0/guk+8GSnu53H6D0/guk+8GSnu7WK8j05leg+HiQtuyKy9D0Tweg+dfmXuiwE8z3zZuc+f16wuiwE8z3zZuc+f16wuh0J9z1D9OY+4RGiOh0J9z1D9OY+4RGiOmAn8D3idOc+FZ5Cusv25j01I+k+ok5Ru9fV8T2vZ+c+OUOSOv3K5j19g+k+hp59uuuR8D1xlug+FmXAOkGN5z0dvOo+ei+YuMyD8z3Pweg+QaiqOsyD8z3Pweg+QaiqOlOe6T3Gyes+RoorulOe6T3Gyes+RoorukH18j3Ituk+c1DiuYvs6j1LU+w+d4w0u2+x3z0Hzu4+uf6jux3M3T1aQe4+UtAgux3M3T1aQe4+UtAgu8G80z12zPA+7szeu4aE0T1CLvA+Lnl9u4aE0T1CLvA+Lnl9u4bqzj1Uhu4+KEZCu3VmzT263ew+DrCNuyCC2z291Ow+Wa3buvA62j09Yus+H7g6u2rH2j1P2eo+yta1uzC1vb1swPU+k62HuzC1vb1swPU+k62Hu/smvr1PYvY+5GXuuwpYzL3aBvc+Yu82uwpYzL3aBvc+Yu82uwvXv72jDvQ+Nqgwu9kOw70vXPI+NkJmu10qzr3NjfU+LTbKuqD60L2nFvQ+xlQTu2a22r2PDvc+i44quTAg3b1k7vU+gPY2ukt02b00Qvg+3eKTukt02b00Qvg+3eKTuh6v4r16Q/g+YayPOmt85b300vc+SUxqOrlN470dNfk+PT4nOrlN470dNfk+PT4nOpTc570CE/k+ozcmOpTc570CE/k+ozcmOhvZ4b2Gavk+tKeZujCI2b00r/g+JZhcuxB85L1iNPk+fQLvuutw2735QPg+3qmsu+un5L1BQvg+e4BFuz5/3b3UDPc+jIDDu8Kq5r040vc+yXvNusKq5r040vc+yXvNus8c370n7fU+/I+eu88c370n7fU+/I+eu+t95b00G/c+GFpDunSt3r0sbPU+xxE1uwUW072bhvM+2Umcu8B8070ZFfQ+uPj0u8B8070ZFfQ+uPj0u0CCxb19uPE+0CTSuwb0xb1iWvI+kW4cvAb0xb1iWvI+kW4cvC3Sw70rDPQ+Shs0vGKawL2fvvU+yLQmvPme0b2mi/U+NEYPvCbazr1LBfc+C2MDvFezzL3Zkvc+GtC0u+rNub0Kr/M+12W+u/KOu71tjfQ+3GMfvIlHzr2aLfM+4uV+u21qub1VW/E+SPSMu0Fuur3CBu8+H1+7u0Fuur3CBu8+H1+7u53kzb3IKPE+GZQpu3LSzr2tJO8+e6d5u3LSzr2tJO8+e6d5uw7p4L1QP/E+c24OuisB4r3Nq+8+yAHBuisB4r3Nq+8+yAHBuiGT4b393fI+fVDJurtv7r1Wp/E+F+iuOoKR8b0Y1PA+6vKAOoKR8b0Y1PA+6vKAOhhQ8b1wuvI+iQV4OqCw971iD/I+UclxOqCw971iD/I+UclxOnB/772NPPM+cmqxurlo4r2Le/M+mi+Ou/fW8r19ufI+6ooSu/fW8r19ufI+6ooSu64k5L1j3PI+G8Dhu64k5L1j3PI+G8Dhu1X88L3CpfE+hl+Cu52C5L0UPfE+JdQDvF4Y870m0/A+1xIQu7SS5L00qu8+Y6zfu6Ps772KEPA+uiupuvAC473aAO8+fkaLuxuA0L3nX+4+sofru5QQ0r2qIu8+PCUtvGtrvL0LJu4+PU8dvHcsvr1tBO8+LIBdvPSPvr0jWPE++jh2vBmMvb22rPM+hgNfvBmMvb22rPM+hgNfvDVc0r0CJvE+//BCvKeF0b2WK/M+zHQuvKeF0b2WK/M+zHQuvLzAz73j7vM+VyTvu8q7EL0d9Pg+W7J6Pcq7EL0d9Pg+W7J6Pcq7EL0d9Pg+W7J6PSvHEb2Klvg+BY93PSvHEb2Klvg+BY93PTaPEr38r/k+oZ15PTaPEr38r/k+oZ15PWbtC71YwPg+JSV+PWbtC71YwPg+JSV+PX9mBL3cp/g+686APX9mBL3cp/g+686APdemDL1Pt/k+z2F+Pe57C73CEvg+7U57PYZvCr0Ct/c+Htd2PYVhA71u6fc+Psh+PXHPAb0ih/c+xT96Pe659by0F/g+NnuAPfzX8bw1vPc+X8x8PdhQ57x5n/g+EeeAPfCp+Lz7xPg+qrmBPfCp+Lz7xPg+qrmBPTLl7Lzr+/g+yNGBPcIP+LxAvPk+pwSCPSZE9ryn2Po+Sk+BPWSmBL2Bsvk+uw+BPe7g6LzsuPk+YO6BPTz55bwVwvo+N0OBPbAj3rw4hPk++cmAPU812LxjSPk+lnd9PbkX2rzCm/o+kfJ/PfWn07xlbPo+HYB7PSdB27yYrfs+u918PRwr1bx+j/s+j4B4PX0W4rxLivw+QUR5PfUr5rwpwPs+9nB/PTVV6LwVZ/w+7y98PTkt9byg5fs+FlN/PUohA72b+fs+cSh9PZVv87zLr/w+cOl6PZVv87zLr/w+cOl6PYmeAb3M0vw+LYh4PYmeAb3M0vw+LYh4PQoT77yrE/0+IRl2Paq16ryrFP0+fSVxPbig/7ziQf0+WyFzPdT1+7yLRP0+1rxtPXwoCL28Dv0+oXFwPV3eBr14D/0+OzBrPf8pD71bgfw+HwVvPf8pD71bgfw+HwVvPTlQCb0qq/w+QJt1PTlQCb0qq/w+QJt1PcxzDr1HX/w+uT5zPcxzDr1HX/w+uT5zPcxzDr1HX/w+uT5zPZM1C72w4Ps+lqt5PZulDL100/o+Vql8Pbs0Eb03t/s+1DF1Pbs0Eb03t/s+1DF1PWoIE72SuPo+k593PWoIE72SuPo+k593PZvzE704ovs+G9RvPQImFr2Mj/o+CPdxPbSvE71Kg/s+BYVqPWD2Fb1JX/o+f3xsPYS4EL11R/s+qGhmPRmHDr2Mdvw+CwBqPRmHDr2Mdvw+CwBqPfAhDL00LPw+dy5mPfAhDL00LPw+dy5mPcNXCb3Cz/s+CVlkPcNXCb3Cz/s+CVlkPWTtBL35s/w+LgZnPavR+LxA4vw+XTRpPWN1A72zBvw+RolkPWN1A72zBvw+RolkPbjH9rzSI/w+xF5mPbjH9rzSI/w+xF5mPXvCA71uD/s+TfNjPUeoBL0G8/k+BV5lPe5H9rwsGfs+JN1lPeVZC73BEvs+2x9kPb7NDL2YCfo+LXZlPWC0DL2FC/k+p4toPX++Er3rL/o+CQpoPccpEr0VHvk+4B5rPc40Fb0vPPk+DHxvPRy/Dr1iQfg+W7huPRy/Dr1iQfg+W7huPcCfC72YZPg+rMxrPcCfC72YZPg+rMxrPQ9PEb2dS/g+tRVzPQ9PEb2dS/g+tRVzPdZACL0CuPc+euNxPQD0/7zLifc+QNt0PZASBr3iG/g+KhNtPZASBr3iG/g+KhNtPaZX/Lzh+Pc+cHRvPaZX/Lzh+Pc+cHRvPb8zBb0O5vg+h6loPSNS+bwT0vg+KdRqPSY297xZ7fk+sXxnPY8p6bz96vg+CFFuPYBJ5rw5+Pk+SFNrPUIr3bx3FPk+yMpyPUP07LyDIPg+W2FyPUP07LyDIPg+W2FyPR2t4rxmbPg+4b10PcBD77zyvPc++op3PblA4bxTSvg+e/d4PYaG4rwgVfg+kfx9PYKt17x1Kfk+gSh4PbBI07whPPo+kwV2PfnK1LzYUvs+RnJzPeKD2bwcE/o+CF1wPUp22ryyG/s++l5uPSId3ryP1/s+P0ptPQdH5rxdFPs+zZppPeq557xVC/w+dtdpPeq557xVC/w+dtdpPdyc6LzruPw+r61sPWAG3LwjNfw+lm1wPZj23LwQgPw+6OZ0Pd1kFb3VePk+VYp0PUkvBL1U3vo+9T+APQUgJD0ltvA+HENxPcAoHT0qy+8+4/VtPRUCID337fI+dApzPdMwMT3u+e8+W5VuPRe5Qj0UmO8+aZlnPbYdMT2d0fI+DblwPenbLD0KIu4+DyJpPWodJz25V+0+4WxgPc9EPz0gkO0+kC1hPao8Oj00tew+bYJXPd2EUD1CF+4+cM1WPcSwTD1dTO0+MRxNPQvIXT2gt+8+Bb5MPYSmUj3Q7+8+RGJdPWaFXD0YpfA+wEVUParGVD3UxvI+bGRePaXAVD0lLPY+3nxbPUXlQz3ip/I+KG1pPWGhYD1s2vI+kdJRPTSRYT1UB/Y+1BdOPYtoZT3ecvI+v4ZFPedkYz3eCPI+SlU6PVXUZj3i0PU+Y3ZAPSePYT2Lh/Y+TY0zPVm+YT1hKvk+OWc9PU6IXz3AHPk+DsYxPdwjVz2s4/s+jQU+PRAxXT3VJ/k+WzNKPaW5Vj16RPs+AG1HPXgcUT1Xfvk+5ERWPasIQD3Du/k+7d1gPVMCTD3eG/w+zalOPS+oOz1hi/w++e9XPZMRSD0RlP0+gRJEPa2gQz2P6P0+tgU5Pbs0Nj1PLP4+FBdNPW2BMD0Vi/4+kvBBPZ1oJD3Znv0+H2dWPVQNHj3r8/0+ZlZMPY6EFj039/s+aj1fPaKMKj38Jfw+5NxfPUNUHj2HVfs+W2pkPYJzLT0eifk+iJloPU0tLz2CN/Y+ls1uPcWRHD1gO/k+PGtrPRTQHD0dHPY+e29xPe+EDz09Q/k+aKtnPTalDj2L6/U+2cptPS5ZBz1qN/k+gxpfPcUGBj10rvU+zw1lPQd2Dz0vJPw+yY9WPcc1Cz2JI/I+vqlnPe6GFj3iMO8+OTlmPSMvEz27i/I+78pvPc7kQj2TPPY+aPJmPSc3VD1mD/w+KjgzPQ9IWz0ZHO8+nOFCPcXXAL0ltvA+HENxPQDB87wqy+8+4/VtPapz+bz37fI+dApzPZPoDb3u+e8+W5VuPddwH70UmO8+aZlnPXbVDb2d0fI+DblwPamTCb0KIu4+DyJpPSrVA725V+0+4WxgPZD8G70gkO0+kC1hPWr0Fr00tew+bYJXPZ08Lb1CF+4+cM1WPYRoKb1dTO0+MRxNPct/Or2gt+8+Bb5MPUReL73Q7+8+RGJdPSY9Ob0YpfA+wEVUPWp+Mb3UxvI+bGRePWV4Mb0lLPY+3nxbPQWdIL3ip/I+KG1pPSFZPb1s2vI+kdJRPfRIPr1UB/Y+1BdOPUsgQr3ecvI+v4ZFPaccQL3eCPI+SlU6PRWMQ73i0PU+Y3ZAPedGPr2Lh/Y+TY0zPRl2Pr1hKvk+OWc9PQ5APL3AHPk+DsYxPZzbM72s4/s+jQU+PdDoOb3VJ/k+WzNKPWVxM716RPs+AG1HPTjULb1Xfvk+5ERWPWzAHL3Du/k+7d1gPRO6KL3eG/w+zalOPe5fGL1hi/w++e9XPVPJJL0RlP0+gRJEPW1YIL2P6P0+tgU5PXvsEr1PLP4+FBdNPS05Db0Vi/4+kvBBPV4gAb3Znv0+H2dWPSeK9bzr8/0+ZlZMPZ145rw39/s+aj1fPWJEB738Jfw+5NxfPQYY9ryHVfs+W2pkPUMrCr0eifk+iJloPQ3lC72CN/Y+ls1uPQqT8rxgO/k+PGtrPacP87wdHPY+e29xPV152Lw9Q/k+aKtnPe651ryL6/U+2cptPd0hyLxqN/k+gxpfPQt9xbx0rvU+zw1lPY9b2LwvJPw+yY9WPRLbz7yJI/I+vqlnPV195rziMO8+OTlmPcbN37y7i/I+78pvPY6cH72TPPY+aPJmPefuML1mD/w+KjgzPc//N70ZHO8+nOFCPdmPST2r3/k+DUBuPdmPST2r3/k+DUBuPdmPST2r3/k+DUBuPTJBRj0Xgvk+ckVuPTJBRj0Xgvk+ckVuPfL3Rz2Jm/o+xaZvPfL3Rz2Jm/o+xaZvPVRUTj3lq/k+n79qPVRUTj3lq/k+n79qPSz2Uz1pk/k+w6pkPSz2Uz1pk/k+w6pkPZBUTj3dovo+u4JrPfnESz1P/vg+yHJpPY/YRz2Povg+SBFnPRmVUT361Pg+3tFiPUHCTT2vcvg+G+xfPUJLVj1BA/k+dWVbPZTvUj3Cp/g+tkJYPbhTWT0Gi/k+cs5UPWA0WD2HsPk+P5BdPWA0WD2HsPk+P5BdPdg0Wj155/k+6AZYPdnaWD3Lp/o+ZnVdPSfJVz00xPs+jSpcPaJdVD0Mnvo+qg9lPXgKWz15pPo+xC9WPSo4Wj2irfs+LWRUPTeIWj3Fb/o+nl9QPcSKVz3wM/o+ykdMPc6bWT1Ph/s+0vFNPYpgVj3yV/s+0oFJPbJ/Vj0lmfw+4YpNPcZKUz0Le/w+IExJPZ0EUj3Zdf0+G61PPaZBVz21q/w+wIdTPevTUz2iUv0+v4xTPVXSVD0s0fw+q6BaPaYdUD0n5fw+ABRiPUXlUD1Zm/0+525YPUXlUD1Zm/0+525YPWsvTD1Zvv0+bzVfPWsvTD1Zvv0+bzVfPX3+TD04//0+YN5UPUH2SD04AP4+hEJRPcabRz1vLf4+qtFbPY8MQz0ZMP4+sGdYPTV4Qj1J+v0+tetiPTzfPT0F+/0+FRFgPRfyPj3nbP0+GyRpPRfyPj3nbP0+GyRpPTkFRz23lv0+Rp5lPTkFRz23lv0+Rp5lPe4uQz3USv0+5MVpPe4uQz3USv0+5MVpPe4uQz3USv0+5MVpPQ1MSj09zPw+/61oPSKyTD0Bv/s+H/lqPSEvRD3Dovw+wf5sPSEvRD3Dovw+wf5sPXDtRT0epPs+BnxvPXDtRT0epPs+BnxvPUE7Pj3Fjfw+jfFtPRKVPz0Ze/s+trFwPQtEOT3Xbvw+AgxsPUZuOj3VSvs++tFuPZlGNj0CM/w+L/RnPateOj0ZYv0+KftmPateOj0ZYv0+KftmPRl7Nz3BF/0+WYVjPRl7Nz3BF/0+WYVjPfmZNj1Pu/w+5UxgPfmZNj1Pu/w+5UxgPY2DOj2Gn/0+WO5cPbU5Pz3Mzf0+7oFVPXCaOD1A8vw+jcNaPXCaOD1A8vw+jcNaPaLYPD1eD/0+CalTPaLYPD1eD/0+CalTPffzNz36+vs+Zd5aPacFOT2T3vo+QClcPS1xPD25BPw+IURTPVnENT1O/vs+CCRiPaeWNj0l9fo+n+9jPSeNOT0S9/k+DMxkPQIzNz15G/s++mFqPR1POj2iCfo+6shqPQeEPT28J/o+rQdvPTLKPj3vLPk+sqZoPTLKPj3vLPk+sqZoPeT6PD0lUPk+DsdkPeT6PD0lUPk+DsdkPWolQj0qN/k+fHBsPWolQj0qN/k+fHBsPU/QQz2Po/g+bXVjPQozST1Zdfg+I4JcPYvpPz1vB/k+5eRfPYvpPz1vB/k+5eRfPWSfRD1u5Pg+XB5ZPWSfRD1u5Pg+XB5ZPXn8Oz2a0fk+I7NdPSmxQD2gvfk+zT9WPWXXPT3m2Po+GjZUPcKCRj2K1vk+zaVPPa0cRD3G4/o+rVpNPa+fTD0DAPo+ClVLPZfJST0QDPk+h7VSPZfJST0QDPk+h7VSPeKfTT3zV/k+6I1OPZlWTj1+qPg+F2hVPbncUT3gNfk+sS9PPSVwVj2sQPk+o1hRPY+TUj0CFfo+QWJKPb05UT2uJ/s+FqJHPQmLTj1lPvw+hI1HPWDhSj2p/vo+xtdIPd3WSD0+B/w+CK1IPfc+Rz0dw/w+vxNKPT56Qj3r//s+EtFMPXx6Qj3i9vw+LZRNPXx6Qj3i9vw+LZRNPdUJRT14pP0+BOFOPZ2NSj2wIP0+Ww5KPWWpTj2da/0+UONLPcZDQj1iZPo+SMZwPWv3Uj3hyfs+sh1kPbHIkz2zyw0+KFYqPcFlxD1R5w8+kwUOPdWV3T1N7RA+zFiMPFyv5D3i2w4+ZEk5u1yv5D3i2w4+ZEk5u0+P3T14Jw8+aLnCvPkExT0gug4+rOwrvWyhkz27MA8+sJZWvbx7RD1Wpw8+W6CAva4SIj2YsQM+bCQQvd3pGD1Ubf89ZvpTu93pGD1Ubf89ZvpTu3z1HT3YIQQ+zqzSPEFXRj0VsAs+vKZGPZEkgr2zyw0+KFYqPaHBsr1R5w8+kwUOPbXxy71N7RA+zFiMPDwL073i2w4+ZEk5uzwL073i2w4+ZEk5uzDry714Jw8+aLnCvNlgs70gug4+rOwrvUz9gb27MA8+sJZWvXwzIb1Wpw8+W6CAvdyU/byYsQM+bCQQvTlD67xUbf89ZvpTuzlD67xUbf89ZvpTu3ha9bzYIQQ+zqzSPAEPI70VsAs+vKZGPeh1gz78rE6+aPZxP+h1gz78rE6+aPZxP/TTJj637bA+v5RsP1VBsz6j7hG+jgFtP1VBsz6j7hG+jgFtP049fT4DKj6/80MfP2Hncj489IC+jTBwP2Hncj489IC+jTBwP8VBNT7qBY++oJhxP8VBNT7qBY++oJhxP2EpHT7yZJg+4DdxP+Ekez4IPj6/H2EfP760NT6XjUS/35odPzGwej5/MU6/+iwKP3/CbT44d06/hDELP1FxMz43NVS/nfsHP1dtez7lKEK/440aP4XFlT6mFjG/JQUpP/aoMD5D2Ei/GHgYP4+FTT7GySm/RJM4P4uaEz4PQi+/+Oo2Pxe8qD56+hm/jk06P4CKxT2A2/a+jOpeP6IRoj1GHwK/6IdbPyN7GT5S8Oy+yatfP87aebzW9HO9BoR/P2jTtb3WSWG8/PZ+P0/HwDuNcBG+d2Z9PyzqMr5s6FI/iwgKPyzqMr5s6FI/iwgKP4c3tL1EVU4/wdkVP4c3tL1EVU4/wdkVPyhplr4G2E0/z1EEPyhplr4G2E0/z1EEP5jLe760lXI/0thQvi4CDL7ajXU/8Wx9vrbF1b4y4WY/sxzjvdffrr4cjW0/EdkYPoJ9EL9SC0s/3FFqPgXCX757Jng/NjrmPQ16iL4BOB8/u308P9xVzL7bfgE/78ZDPwFvNb4uti8/3ZA0PxNV4zO+84E8wPd/P1oBnr1YTzc/xJoxP3EO8bN2uTo/4iAvPylJor3WTn4/pxKqPdej17NbQn8/uLCbPYql2Lym4XU/PeGNvgjCzbLhNHU/EhqTvm2Ngzp7fVM/zz8QP22Ngzp7fVM/zz8QP2jSnjRfDFQ/f20PP2jSnjRfDFQ/f20PP3nCVjy0fhC+kmp9P3x6rDQtPBK+SWB9PxqYSj1bsv++o21dP+GD1zNPCAK/P4RcP7aLpT0xCTK/bMk2P0TI9zGjWzS/pq01P42jsD1TAk6/rVwWP6+eJDERkE+/eNgVPzZcqD1vplm/Sh8FP3syPbN4vFq/agEFP818pj0/OUq/Q5cbPyweuDL4c0u/OWEbP+yenT0Wnqe+yBZxP+yenT0Wnqe+yBZxP/QNlTQMV6e+1vBxP/QNlTQMV6e+1vBxP6XlQD2XVnA+jI54P5oN4T1Srok+3fV0P/ByNzQv428+UeB4P56Pgz05zsQ+YcJrPwmqLbMgisw+Rq9qP5MZ5D0qMr8+Q8NrP8DXBz50lcI++1dqPzLrLz6AV7I+O+drP71FzzIG2sw+2Z1qP20pRD6C35Q+yvpvPwI7UTOdJ5o+/B50PzJ5cD7nn40+gI1uP1ceXT6Dd4M+jylxP0hilz7b5HU+qLNsPwAAAAB+X4k+CJ12P/LJZj6Ihn8+ARhxP9vGtD5xuHE+W8NnPxkCk7PNq4M+xWN3PzDhXD5jHHY+e0hyP96yxD67G3w+xMtjPxuPerNIRHI+brt4P/77TT5oYhE+gR54Pz8ofTLmvQ0+IYl9P2yyzz7kHRM+7hRnP2aYKj5pnZi9VbN7P6XLxj4v1AG9ncZrP7H2MjNNtKu9RBl/P4nZvj0gfrO+OJBuP21IKTHAGri+mOBuP/EJoj7KMpu+8htmP+KvEz30diq/ucQ+Py0tWD4FISG/7HI/PzAeODK3cC2/JUs8Pwal17xpBGO/ikDsPph6NzyfiEC/WrIoP/LQ9jEKKmW/VjXkPgBbx71k13m/ssVHPo757L73kVa/DryTPmalI7EtOXu/oeNEPqEfEL7hKX2/lk5BvaEfEL7hKX2/lk5BvYoID7/hIFS/ZjEOvYoID7/hIFS/ZjEOvYMdpDFcmX+/7SZlvYMdpDFcmX+/7SZlvdz7870tJHO/HCqUvslX+r4GnE2/IUauvrJu9bE0pXS/HMuWvqP0Dr2Bhle/yNsJv9mhmD2u5i+/AwQ5vyriZjIj0Vy/kYUBv3fhKT1cHSe/hKNBv9jyJrL4UTG/9qQ4vwVhwD0yDha/KgNOv5gvsj14rQm/dqxWv3MPyz3vp+m+el5iv3K0VrMoYA+/ZRVUv0lGZz7tl1y/dKbovklGZz7tl1y/dKbovgoQXrN+xV6/xEL8vgoQXrN+xV6/xEL8vgoQXrN+xV6/xEL8vvXKyz5Lwy+/CsIbv/XKyz5Lwy+/CsIbvzRslT534XK/lkf4PTFoKLO7tX2/sakIPjFoKLO7tX2/sakIPqNOJT94QUK/wDeuPWfpmD6gnm6/cs5RPgAAAAB23Xm/hNVePgAAAAB23Xm/hNVePmizJT9FNz2/sto+PqtFnT4O426/Rjk/PiAYJT9yiD6/dNsxPgAAAACO7nq/zr9KPgAAAACO7nq/zr9KPtjgoD4KU3G/VDbmPQAAAAD2Sn6/4B/sPQAAAAD2Sn6/4B/sPStUIz8J2kK/YQDvPUsHnT43cHO/ikUnvW9hID8uiEe/QLmDu/QUMLK8oX+/E51bvfQUMLK8oX+/E51bvezZij4udGy/VaWKvq3+27LA8nO/vD6bvq3+27LA8nO/vD6bviE/Gz/0d0S/pvZUvoH3jT67ZDa/2wMlv37Bl7SDtTq/GSUvv37Bl7SDtTq/GSUvv+zfAz9qACm/7PALv8YUpD56iZ++vgBlv4k7LD+wB6C+wKorvzFY/LNm+qm+BXtxvzQwrz5F6cm8Dndwv6MHMz8EP+I7ivo2v25IM7OGffi81+F/v1PxnD7FhYM+oKJqv8nknrQK+oU+fxR3v/+hHj8yYJ8++XI4vxerfj7PSSQ/Rrg5v/R45j43Lh8/TREkv8PBi7QGuyU/Hx1Dv/sXeD5g62I/BPXJvv28Bz/KN0c/VFGsvkSbCLT58mc/d6nYvhuujD6tm3Q/R9fbvVZWsDJfCn4/kej8vesjDT8KtVQ/7Bqave23jD54P3Q/4cXzPfKlDD+ImFM/qpr6PQbprDI6RX4/qqntPTzYhz5bUXA/PDVhPjGyKTJ4TXk/LrJoPjUoCj+5xlA/B/VVPlNMhT72E3A/0yprPvicBz+RIVI/I7laPot2EzPTf3g/Vg92PojViT72N3U/nqHMPX4LCD/6Alg/YTWZPencpjLTYn4/mJvlPbvOfz7KZUg/iOYRv7vOfz7KZUg/iOYRvwhU1z5F8B0/HUsqvwhU1z5F8B0/HUsqvwUSk7REvUk/L5kdvwUSk7REvUk/L5kdvwG1/D1Ua48+d7ZzvwbBP7TBoIw+gSd2v14nJj4yDkU+2MF3v4QsBz7a1XQ+YER2v/xBXT6gJUs+Gr10vzj4YbOxAmc+imZ5vybpKD5OM2k+nat1vz8k2D5VTj0+jTFjv5gihrOJQ1I+iIt6v66fKz6NYUc+Wmh3v/vJYzN4+Us+qt56v0Z00j44RTg+Gchkv8dLHT4fXW0+Ked1v1QMvj4SWV0+HC5nvwAAAAAbuHc+cmV4v20b+D3IvI8+X71zvzj0+7Na0JU+tct0v1+HmD4TJ4k+no5qv7cfqD0mRaI+M+Rxv5gMYT5fPqE+ql5sv8h59bGvzaY+jAhyv21GTj2Yla0+On5wv2IJGTPpVrA+ZFZwv+eZJT5L0q4+sQZtv6mL/zyPzbg+35tuv3E18j2aH7o+aY5svyYD0bLGH7o+WnxuvyXE3Dw5EqY+mA9yv8fD6zMp9aY+vAFyv68nyD1yy6k+oDZwvzlzKD1aZGC+Uo55vzlzKD1aZGC+Uo55vxtD4D0gvG++Ok13vxtD4D0gvG++Ok13v6TwYTRvG1O+L4B6v6TwYTRvG1O+L4B6vxLMDT2q7z+/HSspv7gCvT2t0kq/eGcavw6BPTMWrD6/ZtIqv84qqDzKF1C/DQQVv4rvwTKUE0+/U4QWv+K9hz2OMle/rZ0Jv/zcxTy3gz2/hv4rv2Pso7BfYD6/xCYrv5wfpz3akj+/O4Uov/gMgz2kkRK/yD9Rv6HqGz7Vjgy/fmFSv6Vf9LPPIha/U1pPvxDp4T3pIau+Up5vv2hztzP5I7O+rdFvv6/Saj6rn5u+j7dsv39M7j0LcsS9lhJ9v9UOiT6eKsq9+lt1vyRJiTTeoci9w8R+v/NEpD1l9RM+33p8vw1sBTQlAxo+hBZ9v0N/gT7cAPA9Udp1v9koHD34wLc+1L5uv4JdSz49qq4+ATZrv7iQwTOqfLg+ts1uvwCm+TzJ7A8/PpFTv1qOADM5HA4/++5Uv35VDD4PAw8/E2lRv/r8hD0YcU8/0xYVvwAAAABvNU4/G7QXv/yXDD1KakY/TYYhv23a7T2DVnk/y01HvsW817006Hg/h7FVvgAAAAAzPno/YPFXvg5RMT7yT3U/yP1oPg5RMT7yT3U/yP1oPlYb2rIN2Hk/iDZfPlYb2rIN2Hk/iDZfPmDPtz1Ee2w/UKC+PmDPtz1Ee2w/UKC+Ph5nOD51q1k/WDn9PlHMbD5XcFo/UkzvPrtvqrJDHVs/uGEEP7SP4z2Fv0Q/SkwhPytMq7NDiUA/2rcoPz9PKz6yiko/JJgWP+ykpD23VRM/oVZQP1uGd7SEtA0/DTRVPy5hsz2ogho/8+BKP5kWBz4Ui7Y+xcdsP0L8MLQF/MQ+fktsP1CU0D00FJM+6tFzP10dhD7tFbA+LSFnP5WjjT7cvk8+73ZwP/zC0rRY5NI+W0ZpPzj5yz6q9u0+K25KPz0MAT/mhqY+DtFMP8JIW7PmMw0/UolVP6r99D4bIw4/KiYuP+FbGD+yyto+TDouPwbXCjUhjiU/OkNDP5xq7T53sro+nbZOP16PGT9IYTQ+5s1HP7EDBrU9m9s+fUFnP80XqT60oEK+sa9sP7seYbXxySm+1nR8P8HO0D5pnVK+RbxjP18xWT585Am/Cr5QP89aKjP6BAu/1PZWPwx1uD5yst6+7UJTP/6FCD4b5hO/5iVOP2LTVT5Bhvy+iy9YP/yupLB8vhy/XmdKPya9szylWDS/Y5o1P6ztEjPqUTe/Z7AyP80DYD1q1iu/ej09P4D3br0lzHu/89suPoD3br0lzHu/89suPjSoHr3gy3q/L4xJPjSoHr3gy3q/L4xJPi8+FjRaK3y/iXswPi8+FjRaK3y/iXswPpzZpr22wHO/Uc6Wvtbz+71NAHW/t3CGvlSW2zOxZnS/e16YvnMTzL3FpH6/2RTPPMVefzMt73+/Lpy5PEAgdL5o/Xe/MVmNPQ8Npb1HQDu/f1ctP9EaSjMsNj+/xjcqPy0URb7OAzK/MEExP53qvb62twC/cuBHPyGVAr96lVi/uMYePjxRH7/MTKm+g6A1P5tuUb8RlAG/NsSLPhxKA7+40OA9Z/dZP18xbb+5wUw+tyijPsm+d79D8YA+tjOpu0YMLr8rlTu/lJDvvGNZLr8jxx2+W0E3P2NZLr8jxx2+W0E3P7GfOzwoLAC/XJVdPyGDk74Tmmi/RNKaPiGDk74Tmmi/RNKaPqAutT3rIDC/UWU4PwzMs7073nW/GFmHPgzMs7073nW/GFmHPsiU4j2KGSi/2fw+P0O2p758BG6/yiEsvrFqlz5LVtO+iopcP5mNnT6N6dq+g5lZP8MRGj/P8mu9JOpLP81DGz+vL00+hvhEP2/EBj9sjm2+ZGVRPypm5j4qk+y+oKJDPzsz/D5Nalq+WP5XP7ZG0D4qqwG/9p5CPxJwxD7xc4u+ZeRhP2Ri4z5EXNA9qeJjPwPQiz4bXAW/cwlPPxP90j5uZZ2+bJNbP8Y2oT5qCgu/oUNHP9SI6z4SsUu9W/JiP+7fDz+UoOW+WeoxP8r10j6vaRm/F7YvPzXGKj9GrYO+dv0yPy1RCD8HB0W/IGC0PoBEJD+xFkC/5K4iPoBEJD+xFkC/5K4iPjTpuD5BL02/2wb0Pk3NgT7o/na/T26OPQVciz6lOW6/QL56PhhfjD6q/nW//TAdvRhfjD6q/nW//TAdvagSaT5mRHm/f3klPFz6uT4jtGe/e1xiPrtjbD9Y3n2+3AmWPrtjbD9Y3n2+3AmWPnplLj5vBXy/QJIvvXplLj5vBXy/QJIvvYFhbz9SjLK+MbmBvYFhbz9SjLK+MbmBvYFhbz9SjLK+MbmBvYFhbz9SjLK+MbmBvR2VOT6YsXu/Ps65vKwAfz596ne/FnFFvDX0lD4jKHS/UkKbvWQMXj9yOMi+X5advmQMXj9yOMi+X5advrx4ST+W4dy+h9Dhvrx4ST+W4dy+h9DhvnvbHT8g2jo/mgqXvq48Gj9odBU/E1ILv31fGz+Qaks/7SKEvH1fGz+Qaks/7SKEvPdaCz/urEo/z/eNvgtiAT9NXSk/2dANv+MEET9b9lI/CF4Zu+MEET9b9lI/CF4Zu87cEz9fFEI/fv6avrcxAT9axxk/TLwev6jKGz8yI0s/dEC6OqjKGz8yI0s/dEC6OgWGHD9OuDc/da+qvnRVAj8GygY/KU0uv618JD+SKEQ/dj+vO618JD+SKEQ/dj+vO+DcID94OjA/coC5vn9VBD9kYeQ+fAo7v1IcKD9gCUE/qLhaPFIcKD9gCUE/qLhaPFBaGz8X5DA/oRvJvuqg9z4kVss+d6xHv2mkJD+8/kM/uMV2PGmkJD+8/kM/uMV2PJw2Ez++BjM/+2HZvpgIDj9bBzY/ICndvnw0ID/VqEc/1GAivHw0ID/VqEc/1GAivOUP3j78BMs+7CFPv1MEwD7un80+SONVv8wVnT7i5J6+U1ZmvxYdcD6ihqi+vylqv6Zq2z58sW2+kolfvwbKQD7W+mS/bKrPvh1isD4WLlq/GI3JvuEUHj6qVGm/t0DDviDCJD4Kqny/npfrOiDCJD4Kqny/npfrOm39lj5BmnS//wkePG39lj5BmnS//wkePFJJHT5K5Hy/rdW+vFJJHT5K5Hy/rdW+vIZURj5tsWW/ZynLPmbnsj5BIFm/fN7LPgfOMz4g9mm/RWC7Pqq6mj64ILW+XJpiP5f12z7daYW+oFZdPyzDjz5ultC+vHdeP/YY5D4UNbY+k05SPyVk3D6NhLE+ildVPwFz+T5MLbo+W0JLP3WeFz9IfzA/FoLVPqnbFT+ikzM/XiTQPokYGz9KJi4/rTfTPiv5Hz9mvi8/qlq+Pku1Hz/b3jU/H8OmPogwBj82rd0+4rs7P9MfCD8FawQ/TKwrPwU+1T6Qgxo+FYNlP4sRxT7sW5g+5KhfP0Q80T62mLo99npoP0TV0D7rvgo+PyZnPzK2Gj+atF++6CREP0Yqnj7ckG0+xh9sP/3Wkj57+nU+L2ltPysLgD4VIZ8+hb9qP61nWD7DfZ4+dFZtP3/vvD5nyMg+TbdXPwmBYD4sOcU+LntlP+t2Jj4iPbg+sjJrP0AFtj7A2/0+ztVKPxPlZT4Uqtc+nfRgP8zVtz7BdBE/IY09P62uDT/6RdW9l4tTP62uDT/6RdW9l4tTP0BZ9z48Tjk/0TX8PpsxRz/HQg++ucMcP5sxRz/HQg++ucMcPyFC/T5RFC0/Ts0LP0BdDT/toUs/fa9/Pl/yDD+04Uo/f0yGPqsIGT/ZND8/RxaVPnVgBj/bsRc/0WscPzdRwj6gTEu/BgfzPjmZkj5FXFq/+HHfPnYqnz6CBD2/GDcZPwEKhT6thk+/ClQGP88xhT6YYjC/bywtP7oyjT6fbj+/NZ0aPw7wFz5uTLo+KWhrP/N+Tz56Zqk+bPJrP/uXjT7U1oY++phsP11kwD4VE0g++ednPyGQ9D5DhjY+wTpcPx3PJz+l6GY9J8pAPxTcCj8kYlM+R3lQP75hOj8lEDs+sCUpP7rNFT/q8gc+vspMP9jLPD+WThA+ShYpP3ZSFj/y20Y80TFPP8BwOz9iSlU9A9otP1CNCz/81GC+syBPP/SWNT8NxM29zZoyP0Yh2T7DJgS/6Xw+PzdOKD/dX5y+MVcwP+xEqD4G1O2++4NSP7CnHT8S3KC+9/Y4P22ohj6Sk5u+AmtqPyo8aT54u4G+8a5wP01Bdb7k6wK/1kVTP+DxDz+SjG2+cjJLP7LQCT8zqEi+MdNRP7Q0aD8zfQ6+9nHLPi9Jaz+pYSq+2+O2PtJZZz9D9gC+4IHRPi37fT80r/u9QenJPC37fT80r/u9QenJPCyYfT+B8gu+m5eruyyYfT+B8gu+m5eru4WVfT9kPvy9u0l2PYWVfT9kPvy9u0l2PWQPbD9GPB2+Bte1vpEWaz+18iG+qcy5vlxHbD92JRm+iZO1vufnGz9d2Xy+xPVAv38GJT+HU4i+8nQ3v4GgFD/OWX6+PoBGv6Obqz75E6S+pc9iv3iGzT4AP8y+mg9Tv6wvjj5ABZ++CLlov7OoaL7BGwm/cTdQv5FIg76Ut/y+3r5Uvz1dTL/sIge/c3OUvs5XU7+qJfO+tBOcvpjPXL8/bgG/fe+jPJjPXL8/bgG/fe+jPHNhZb/84OG+MKVNPXNhZb/84OG+MKVNPR3xRr+5SQm/tKSoPpLqTb+GCvW+7UG0Ptw/hb5B99y+wxxdP8gMSL/NWNi+DRLrPi0TT788QOC+QNbIPraFiL7JVLq+OndkPwAMhb44+dO+JFVfP004UT6ujXO+EhdzP9OcAD917US+ns1XP1vlRD7YaZC+XJ9wP1FQ+T7Km3a+i+5WP5RhNz6mH6O+HEpuP5Ru9T4EW46+8hhVP4cPir6s7+G+EBxbP7uBKD7Cz52+ct1vPwo5+T6WJou+o4ZUPwqKmr5OLOS+9MBXPwoIBz7EW42+EblzP9JOnD1Ozo6+IBB1Pwlfor5fxce+YUldP03k7D7N4G++UeJaPwow1j6Ao2++KqxgPyrJXj/I/Bq+ggLwPu10Wz9QURi+42L8PnM+Yz/02Cq+jr7bPvvZfT86cfK94N9UPfvZfT86cfK94N9UPXoIfj8xdPS9JFsFPXoIfj8xdPS9JFsFPcVJaj+IfRa+PCTAvvziZD9uTB6+UjrXvtgrCz/2pX++8yNNv7uL/D6c1IS+Z45Uv9b3Xz5Cnpq+rYltv8wTID5W06C+r7lvv8eflL7leNy+8cVav2uzkL4jxti+V1lcv62qV7+U7dO+mpWwvmOOT79txdW+zxDSvm9NbL/iQMS+xTYEPW9NbL/iQMS+xTYEPXJDbL+WIsW+xKUEunJDbL+WIsW+xKUEulmbUr9E2M++AMbLPolpUb8K4sy+jYzTPq6cVL96itq+eze3PtsSSL+xvuO+7PXfPnebUr84BLa+rCPjPtTdqr67+cO+44hcP0/ztL4/eKW+zLtgP97bx74Ux1U9y09rP8ixCD3l12G+yox5P6LYxD7LeTW+iu1nP9ozKDvoRRE+7Wh9P4W2uj6wXS8+5E1qP4SPzLxxDiQ/ZmtEP4SPzLxxDiQ/ZmtEP3OJir5qviI/6BI5P3OJir5qviI/6BI5P6ZxWz5k5ik/pXc3P6ZxWz5k5ik/pXc3P+eNBb1q7VY/V9MKPz31uj20pFc/RPgHP5vKGb6mY1Y/h4QGPw6rBr14aGE/eiDyPuxyv738hmE/PX7tPn2JIj1JFmI/o0/vPiJmB73WpmU/EpvhPunCwjymHmY/sv/fPmkGnr1/lmU/1AbfPj9pB73JRmc/ot3aPkpheL2RCmc/rE/aPlsc9jvYb2c/6M7aPpPsB70Vsmg/PsLUPupnaTs/IWg/8+DXPh7DZ73+xmc/EnXXPmyrCL2lDmo/0bDOPomb7zzuymg/NnzUPpRMp70lH2g/B9XTPmzT/rzLQVo/oI0FPw0kMr6pCVM/u+UJP3X4BD7XhVQ/YMwKP9tdvDuQRyG+9Mx8P/TGfb6juRu+avB0P1dogz6EWAm+2gd1PwIcEj13S3q/nNpTPgIcEj13S3q/nNpTPvItlL3EBXi/8pNyPvItlL3EBXi/8pNyPig9BD7cl3a/qShxPig9BD7cl3a/qShxPoM4FT2toH+/bM0iPW/Rsjmsy3+/o6UjPQ4XcT0JWn+/mocjPZhKFT3Kv3+/O6XNPLAxPjq37H+/e5/GPPpPbz3EfH+/SYbGPEpUFT3B0H+/BnctPPmygj30d3+/cZ4NPGEimLvY/H+/vKYNPGJWFT1B1H+/9C4XO2CAerxX+H+/ltoguLGXmD3WSX+/FuBVuK9WFT1u1H+/vD6CNA/TqD3BIH+/EasguyCevbxA7n+/qioeu5pYEz2U1X+/bGh0OFFJBr0O3H+/Yf+Yu1ffuz0h636/zIeEu/KLED1xb3+/Yk5mvRkCy7zArH+/IrUzvfEurj3U2n6/14wovfDmLD3OFnG/J9OqvhdysL6u1ma/D6mFvlPaxj4qrGK/mbCCvtjcND0otGq/xTLLvqPWMT9qhyW/bF2hvqPWMT9qhyW/bF2hvmNJKb+twSu/ismrvmNJKb+twSu/ismrvq41ST1Tg2i/0L/UvvZZEz/PFCu/RknxvvZZEz/PFCu/RknxvjHKA78/0C6/qrEEvzHKA78/0C6/qrEEv/HHeD3T2Oi+v3Zjv/HHeD3T2Oi+v3Zjv5qYm76S1sa+g7Vev5qYm76S1sa+g7Vevzg6zj7qC8K+skhVvzg6zj7qC8K+skhVv5oiUz3A/To9dmR/v/Je9z5FvJE7oSJgvw2kx77B//a5pLxrv7vjfD1+JNo8r2t/v7vjfD1+JNo8r2t/v+Mesr63qn+8xPlvv+Mesr63qn+8xPlvv6VY6z5JW4k8yk9jv6VY6z5JW4k8yk9jvwibrD3wtQ2+9p18v1B/nb5NoV2+pTNtv+Fk6j7N/fC9fZhhv9Lv3T1ktpK+3LBzv0Nd6j66wnO+uUtbv2gWkb72X8S+WARhv7uKSr+7kMe+TUzxvnfrbr9Qsbe+ujeGvHfrbr9Qsbe+ujeGvDy0U78NbH++4f4Av5InXL+cE469g24Bv5InXL+cE469g24Bv2w7er/BI1e+m36mvGw7er/BI1e+m36mvNG2f7//Zxw9junjvNG2f7//Zxw9junjvNG2f7//Zxw9junjvME+X78Nsuy9OH7zPmnEUb90IK8+04LrPmnEUb90IK8+04LrPhL4Gr9R4zA/pEzKPjIP+74gekg/QNTDPkQzc7/VK5I+6n8BPiMEar89L78+CbghPi3Hf7/Pwgm9cAjJvOP8f78msEi4r6sfPPwKe7/U/6i9rN01vlsMa7+2TMi+2pSAvVsMa7+2TMi+2pSAvcW2ar8tBsy+i5fHvMW2ar8tBsy+i5fHvCVT+b6qWV+/qsUnvdQ/674GVWO/2gKRvOm11L5l02i/wdOCvPZHZb8+ZuO+HLTGvPZHZb8+ZuO+HLTGvF5VsL6RVHC/ytb+u43imL5bT3S/u64RPKyHWr+dRgW/zoWIvKyHWr+dRgW/zoWIvKRaUr9x5RG/p5eaO6RaUr9x5RG/p5eaO2bWf79srR87K5IRPdhaf7980rW6okaRPT+MVr8mzv8+sVxgPrf4f78ub8c6b/pyPFtgY79Ehtk+8y8zPuq5377/PlE/jTzAPqX+s77U4Vk/XKPHPhizmr5XW1w//LjRPg8hqr5zglg/P8PVPiEATb+yCwk/FYqJPkWOTL93twU/xnuYPpySfr9k8hu7LujXPRcGTb/zJv4+jH+rPlWjBL9LVDQ/EmT4Pqa2c78VRiq9zkKbPm4PRb/BcyG/7tLJPW4PRb/BcyG/7tLJPTIRML9kSyO+3kw1P4re5b44Cl6/gPlbPore5b44Cl6/gPlbPoEPi76KRna/3AbiPCpVTb/ysBi/bKL3PCpVTb/ysBi/bKL3PB/hXr9Eb5q9Duf4vt1JYT/wkBS+yIfnvhqJfj+AltO9YwPePBqJfj+AltO9YwPePIpSYz9kK6a9iMTnvp/efz+cTda8+bqVPJ/efz+cTda8+bqVPCXIZT8AiSu8oaXhviXIZT8AiSu8oaXhvhG6fj+E38s9+8BmuxG6fj+E38s9+8BmuxG6fj+E38s9+8Bmu/KGZz+pL4W9XejXvkVZfD8xq5S9jnkbvnfQfz8Mjvm8lCu7vOz8fz9zEyI4MsMePGMVbD/jf8O+iLd6vWMVbD/jf8O+iLd6vSiKcj9FdJU+Az8GPkA5aj8K1L0+0EsjPp9eFD8/wDQ/alfQPqXr8T4TREo/Yu7HPhEyRj92Cto+9b3vPhEyRj92Cto+9b3vPk2RWj/yd6Q99q8DPzDbWD9aneC93R4FP2e31j7FAFM/6s/CPsimYz+PVNg+lmEzPsg0qj6tdls/tEnJPi9ckD4JxF0/KibTPonBVj9FJ/8+EyVgPhMuTT9U1Qg/g1CJPvvXfz+6kzE7HrAOPbdhfz8bvHi6nz2OPZguXD/0hwK/RfKFvJguXD/0hwK/RfKFvPr4fz+sq846RHxuPCmLZj+SPt6+gf/DvCmLZj+SPt6+gf/DvE/Iaz9tCce+niLHvE/Iaz9tCce+niLHvMyr4z4hQ2W/2iyBvAmt+T7bc1+/GrCQvDUfwD4oSW2/DyX4u7YhqT5FnnG/EJoSPAypmz7Fx3O/cVzhPMdEVD+9GA+/tdSVO8dEVD+9GA+/tdSVO7NoTz/+3RW/5FDyPLNoTz/+3RW/5FDyPBaifj9d4PS6UlHTPXh2Rz/Bjx6/82XFPXh2Rz/Bjx6/82XFPbzs8z5uiVq/EoNXPrzs8z5uiVq/EoNXPsAjdD9wARq9RdSYPhNYTT+6s/0+PqGqPlQwMj8qxA2+ZVs0P5Q6AT82LDY/MTH6Pj4QoD7ADVo/4UDXPuTMTD9xewU/rf2XPpaoAz/qUFu/1fkhvW0PWT967UK+kVj9PqaIYD/N8CO+b97nPtS/Wz+agg6+tMz8PqlnAD+cwL2+3x1Iv+zFuz7hxO2+OV5Ov5//MD9FjVq+t7Iwv6FNHj+zEfG9zOpGv3c8Pj8GERG9NBErv+yC+T6xkTe+ZchavyNSJT+vk6g9ZVJCvzQrBj80WpM9xT5Zv0SyQT8MA8g92oElvwXxIT8EhxU+y7ZCv7JBQT/M8ho+0Fwjv5RqHT+OoRc+XEpGv1AgQj81dBs+S0wiv/QIET8TACs+PJNOv/3iMj/Kwl89ppc2v+Gu7T6TWWw+0+hav6bH8j5o3C0+rShdv2zctD5ER5o+qLtiv5Mivj7lOI0+jPZiv7+Hiz4xfKk+EkZnvyNHoj7idKM+/6Fkv7/QVT5GwrQ+bHlpv4B2jj5mzLI+IBBlv33ENz6v9KA+oKNuv4oCiD4gyZ0+YtppvyaLXT7QzpS+8ZpuvyaLXT7QzpS+8ZpuvyOGoj4ovq++eExivyOGoj4ovq++eExiv/5jJj4zdlW/9Q0HvwT+cz7IDWK/kQbPvkZDCz7fNF6/bYb0vgExXz7u9WS/A//HvkbcOD52h0C/BkYiv9nSlj4Y6UG/KCgVvz54kT7sLgK/5RRQvxqF0T7jV/K+W7JHvzyOvz73k3y+T9tkvw5v8T7QSE2+AtdbvxPJ1z4T8Jq9FFhnv9rf9z5OA169oJFfvz+c1D5sGMc9DI1nv1pO7D5gPaU9dilivyjvtD5VEZs+cpVivzpYzz7Ru4U+clBgvw9RgD4sHQY//mdQv0EGpz4oSPI+w31Rv4CrDT5v7h4/e4pFv25vfj4zaw4/n/5Kv34Iwj0zNBo/tOZKv2Ve370/ZUY/MFwfv5ajfT7HDwQ/t/BRv6yYpT1D6Dg/8tQvv5suiT6O3Bo/HfY/v/8Zm716n0s/4vAZv1d6W71E8mM/ZmrnvtyZW75/TGw/hoWjvuxMTT4ROEs/cvsSvytGj74gJXQ/3+XhPStGj74gJXQ/3+XhPWDDtL7e22g/W0hgPmDDtL7e22g/W0hgPiwwwT3BfX4/fNVaPSwwwT3BfX4/fNVaPUS4Qr5IqF4+VBZ1PxLQL75gkl8+Ue11P9RECr4dhsM+KQ9qP+1bMT4/1Sa/qAo9P+1bMT4/1Sa/qAo9P/xUZj5W2SG/hc09P/xUZj5W2SG/hc09Pzi2Bb037Rq/e6BLPzi2Bb037Rq/e6BLP7hchj6gxlC//woEP501pD7Mj1K/2HzwPjqIpbw3dkW/AdgiPwyyRz6u7yy/5gg2P6Sngj4jUTS/CJIpPwJDGLz8xBm/sqhMP5/lHD6KFp6+r09wP50PZT2PxYS+7NN2P3EPrD7X7gq/Ow9FPxdY1z4RaWg+5N5gP07eIj/akym9gTlFPx3Lrj6OUWY+96BpPyaiJj/ICi8/od+oPgUIHD8fDjI/WszCPsnYXD8ba109proAP8zwJT/FNyk/1IvBPszwJT/FNyk/1IvBPsEIET/6zYo8f+hSP8EIET/6zYo8f+hSP8PbHz/3tTk//TmUPsPbHz/3tTk//TmUPgoJ1j5FV/Y+DUNFP1srBD/V4hg/8iMdPwp9wD1RXZ4+JkFyP14JnD7izB0/GN85PxzK5z4/SDU/aLYKP0v0qj7qnlY/FJ3cPkw46j4JEkw/5MHJPqdKkz4IjEw/UC0HP70A5D7ckUI/SFfyPr0cWj4WHyY/Mv46P1KYxD47JCk/0h4lPxXXFT5kAsk+aHJoP1KQsz6tNP8+vPVKP79QaT7z0XQ8sTx5PzIcPj70bC+9d097P/RRyD4liBM+E7BoP95Tsz7gLr6+ah9cP38b1z57Yam+v1JYPybrqT41bKO+OD9jPxJ09T68hiO/YQ4aPxJ09T68hiO/YQ4aPzi4Cj+NhgO/pUYqPzi4Cj+NhgO/pUYqP/MF0j7Aqii/XW4hP/MF0j7Aqii/XW4hPyGXCD8NYkm/8wufPmKVrj6y+1u/Yi3DPshRNT/zjCK/n/GdPn/QCT+zw0+/qHNoPrS8Oz8vqie/Ors6PtBqjz5OcWq/ZmKTPknAEz/PCkK/3JqbPoH1vD5WFVy/Cc+0PvURPT99Jx6/hzaKPgkiJT+/iQy/+BIIP4XUQD8BSt++bxj8PsLdBz9FDh+/jZITPyHsGz+EsR69ictKP1Viij5O1jK/ypopP6hkJj3l7V2/KF3+Pqhqbj7FVgC/JVZVPxa4YL7FnTe/nU4pPzpb0DynIqy+owJxPzSgy753kN++OpROP1XW0D3JiCe9QXN+P7HO7b6NPUe9bF5iPxAkUT0Aamk+7ep4P79cxL53daU+QXpdP//3lT50hc4+u+xdPzd7w71UnSQ/DYdCP8pRBD+Vxvk+vhU0P6aHrj5pZUA/hpUQPwF1LD8anPI+QDARPz6gJz9a8iA/RsrWPjNiTz+CxJA+eHwDP0lCXT8n6cs+DEydPm+YZD/A4x88uGzmPtHleD8Ns0M9FHtqPoZfYz8K/HK+3XfJPqlVbT+HtJi+bYBoPhh/cT/ejZ++4mLpPVG9az9IWKO+cYVlPpFqfj82NlU9l+zIPUGiez/llhA9TtA4PgZNXj/mie4+fwouPlhvXT+OAO0+bWVGPkajHT8qvD0/LOyIPo9sFz/6bUY/JGtjPuwDPD5xzWc/gObDPtAWIz5zHW8/ia2jPhTtzb57fD0/n/IJP6hIzr79x0Q/dFz+PpbZOb9b2qw++mEZP0eIM7/pSrc+QdAdP0YCS7+PcDO9S44bP2S4Q78/Jji9rJwkP99FNL8I/Nu+VrQQP28vJ7+jSum+3dkaP5vT9b5QBzy/jIz1Pjpvxb6zt0S/mL0CP6yQGb6Gi2m/CSHDPi/cJb2GFme/llXbPiw5gz64PUG/ho8aPyw5gz64PUG/ho8aP0Vd4D5aUu2+HihFP/h7lj2z7z2/GZsqP/h7lj2z7z2/GZsqP47Itz6bwg2/5ldAP1j1y70WGAO/dGdaP1j1y70WGAO/dGdaP1p4jz6nGMm+eztgP8Q8Rb5gXm68vS17P8Q8Rb5gXm68vS17PxgDfj7cslE99KZ3P+BnOL66Utk+5CljP+BnOL66Utk+5CljPztdZT4Ykdc+RQNhP2nXc71dkjA/kbs4P2nXc71dkjA/kbs4P2VbVz5mlAs/ybxPPzgChD6AgVM/pDwAPzgChD6AgVM/pDwAP7W2mD4hziQ/6WY0P1HsFD89GjQ/1gHRPlHsFD89GjQ/1gHRPhGTAj8cjxg/dcgePy31Sj++/L4+xsf2Pi31Sj++/L4+xsf2PsDmJD82fJw+/YAzP/qPUz8XuK68QgoQP/qPUz8XuK68QgoQPzuRGj8XLGs7ohFMP+MBOD/Hop2+4ZMfP+MBOD/Hop2+4ZMfP2ky7D7NW0a+6qZdP9U0vT7Lvze7qOBtP616Cz9U0QE+2jJUPz6FEj90M6U+Sv1APx3bEz/GgKo+Qc4+Pw4/5j4DnwU/R4w5P/Jlpz6dfgE/SVtMP2I84T7zjMU+6phPP8VpyT50yYM+ivJhP0O7+T4aqQ4+Wp5cP9j0qT5gOuA+lOFVPxw2Cz964nI+lRROP0bdED9xiyo+5rdOP1uYoD7y9sM+XHVeP9crUD8T16G9jp4TPzTtVz8dr0I+dp4AP/tyaz/ucqO+f/NpPvtyaz/ucqO+f/NpPmECdj8bUME9sSOFPmECdj8bUME9sSOFPtc9aT8ahdC+JguCvTxvGj/QYkq/LorXvTavUz85QuG+AVazvkPpfz9g7NE8ZDTIO4/hbT8yLz69yrG7vn/Fdj8tpoE+QVunPY1hbT/6Ojk+1NSnvtQvej/XhRQ+1S4ePtRZZj+nyYs+uzuuPtRZZj+nyYs+uzuuPiS+RD8bqa0+U+IKPyS+RD8bqa0+U+IKP+CaUj8fw4M+E8YBP9oKOj/PoN8+I7oHP0R9Gz9uYi893BJLPwHxGj/TzSw+widHP1rIrD63bou+fK1mP/vy7T7Hro2+1lFXPxLLkT5G/JE99Lh0P5rWAz/GoSW9iTJbP6l5CT+3ZcA+YVhBP+gVET/eJRg/kxISP9jkGT8sECw/5lTdPreTFT/x/Tg/Shy9Pi5F3T46Oyk/NgYdP1glCz/Tsy4/Tjr6PmHY+j4F+DE/96cGP+94DD/2ICo/TdgBP2gq+D4UkzE/EWgIP7A/Hj+9gEA/4XdqPrA/Hj+9gEA/4XdqPuijIz9lbTw/DCRkPuijIz9lbTw/DCRkPuxjFz96UDE/UnDTPosLFT9a/TY/U1vGPuEzfD640Y0++sNtP+U/Hj5ezMk+Vu1nP7sE1L0dpAW+TGx8P/bvbb7YMQS8Efx4P+Joqr4zKMy+qsJaPwoo/75V8XS+qVRVP2Xd5L44nhW/nl0tP8Y1qr7OVfm+csNOP8Y1qr7OVfm+csNOPzwOMb9aU7i+mUogPz66FL+ACp++LZhAPz66FL+ACp++LZhAP72IXb9aZwW+wcL3PktnRL8CGaK98vEiP0tnRL8CGaK98vEiP8zBM7/Nk1e9NcY1P4moc7+GNAs+X8qMPjbYWb+oEB0+jJkAPzbYWb+oEB0+jJkAPynMW78q7yY+Sd34PreKZ7+QGMM++lBEPg6AUb93dao+wdjvPg6AUb93dao+wdjvPlKGRr868q8+E5cHPzD4475tYl0/UrxtPr/SAr9SLS0/xcIHP89XAL9yjyU/CCcTP89XAL9yjyU/CCcTP0NFqT4Z72Q/6W2aPoAgoD5R/2I/ZlWuPoAgoD5R/2I/ZlWuPqUUhL2IhRo/zW9LP8Oz5b6t5Cc+XuhgP6VgPz3yeh8/veVHP4bkYr052aE+ZnVyP8Rk4r3leiM+iCB7PwxmND/bk3i95vg0PwxmND/bk3i95vg0P3wzbb25BVY9Vzh/P95YUj9MZpu9xpwQP95YUj9MZpu9xpwQPwypgT1X8fY7qXp/P5cfaD9oIRE+1lrLPpcfaD9oIRE+1lrLPoJNPj7hu5Y9btV6P5qvXT+PBOg+lKNYPpqvXT+PBOg+lKNYPmo+GT7SKJA+HKNyP4qMHD8gtEg/K5PaPYqMHD8gtEg/K5PaPcIeED/4xRQ/YXAWvxvGTT8/GLw+apHvvuNNAj+nBtg+VRFAv8OqLD9qP5E+QX4uv+a67D4EQrg+unRPv3YG6T5smOU+pu5EvzJZGj8z0IQ+nyNBv5bEHD9ayr0+QsIyv388RT/Erw8+QzIfv/ZnSD9m3X0+zRkSvwmmWj/Yjus9YNoBv+R6cz9gk8875R+evgEAbT873zY+5ZuqvhPxdT8sjn29VoeKvvKAej/p4Oq9s1svPiigfj+5Zru9FfRFPZd7eD97j/g9O61UvnytPz9I1hQ+JpAlvxknNT+Ujos+muImv7tiED97+pg90odSv/J0Pj8tGgM9mt0qv2KJOT+9Gxi+DT0sv3hrFj95nD29yc5Ov60tFz8gUT2+exlJvzJADD+SVO++oZwxv+om2T5og0W/I8jyvsOeKT9xNu++SN0Vv501/j7EsEi/rdO+vilEDz8Vukq/8TN6vok+rz6pPG2/cNgevtkhoz5ac3G/vmzBvcdapD4fbGm//RmDvtcWqT7lk3C/H8S0vVqzLT9l49y+VjQYv1qzLT9l49y+VjQYv6G9rT4FJmm/pQpxvurmBj/adcq+X5ZAv+rmBj/adcq+X5ZAv/C00j6pCqo+N0ZZv2LvBj8QbNU+85Q9v7L1vz6YSsQ+6RRYvyYU4z53mvU+19BBv9qevD6XmKs+2/1dv4wd1D4GttA+NFJQv8ynvz70vYo+mwZjvzpUyz6msqY+kqpbv7/22z6WHQI+7t5kvwTl0j4ljUE+VzNkv4U7HD/UEEO+KtlEvweHQD/J5wm/d2/CvgNyQj+mgya/KsDSOQNyQj+mgya/KsDSOf9FcT+4gBq9tg6qvvbFfz+6Byi9xEEZvPbFfz+6Byi9xEEZvFghbT/fRj0+3R6ovq0vej/qPlg+VNqPvK0vej/qPlg+VNqPvJbHaj/XFDs+BWi1vj3gej88PUs+Y/d8vD3gej88PUs+Y/d8vFO/az/luAE+KcK8vtbNfT9SqQU+Gpjau9bNfT9SqQU+Gpjau2YcbT9SIP880VzAvrLQfz+j8xo9+GZju7LQfz+j8xo9+GZju9ZJbD/NZcW9Iby+vhIbfz81iqq9BUjPuxIbfz81iqq9BUjPu018bD9v7gC+jiq5PgR5bT83OXw7jzm/Pk8DbT84x849Z3i6PsXHbD/9uTI+aOmsPqwmbj9N7Ug+lbSePn0rcT+D7zu9fB6qPnu9Pj+W8Qy/d8LAPmisAz/jUXU+3MxSvzLL8D7Qpd4+BZdEv60jHD+ILdU+9pwsvzQeJ73W/EY+Fed6P1EnuL729Fw/BoG1PlEnuL729Fw/BoG1Ps3+qj5IbBO/yQc/P83+qj5IbBO/yQc/P+ch5z6RqEG/zkvyPlfdt75DtGc/LChpvpJDkj7LLys9Mxl1v2i+UT6vRBU9CGZ6v88IfT6lIDm+l7Rzv0PyLT5zUSS+lOl4vyFGNT6uNdO+18Fkv8/sBj6GEqq+qRhvv/z4KT82RMS+7Fwkv/z4KT82RMS+7Fwkv4rOdT8N8Y2+33QNPUczPD9WqWi9Y+4sv0czPD9WqWi9Y+4svxf+fj/B0K893Ii0PA6sKj/6RZk+Sr0uvw6sKj/6RZk+Sr0uv42WYT+YffE+TZACPRUsXz/yoew+BnEmPniffD83tLo99P4IPkCtYD/b+uU+D0krPnKcfD/Ntp49z+ARPlYCYz92keU+IyDmPS3ofT8Ry4c9Mk7fPRaeZD8Y0eU+LBUCvaiEfz9N2no9dRdNO0bjXj9SDNI+7/OKvt+neD8u2k49yPZtvnVFbj9FgaK+JPA5vgNQcj8myaS+Oyu1POCAcj8YyJq+oHDZPW1Scz/k+I6+CbkLPiWBdD9WBIe+xFcKPp5ZHz+m+kc/LDRFvZ5ZHz+m+kc/LDRFvV02FD/F1Tc/kbnFPs47FT/vAjs/DB+2PrI12j63rbc+85hUP3DDHj/nqkg/RGD9vHDDHj/nqkg/RGD9vLlPHj/JJkk/arRZvLlPHj/JJkk/arRZvB5AFT8GWzM/0qLSvp2zFT/S3zg/Jy29vqi1Gj8NDS8/o1zRvgKm5j4Kn74+KbtPv63g+z6PaK8+BOZMvyxw6T7I8sA+JmlOv134pj5ruMq+VcFbv1bNnD7I3eS+QilXv3xg0j6p47u+FKZVv9exUz5ACmm/Ipq3vhvSQj6lLm2/zTumvkb5dD7hU2m/SWOrvlxSFz5jLn2/eGECvFxSFz5jLn2/eGECvHdOFT5FQn2/QlPRO3dOFT5FQn2/QlPROy81JD6NV2y/MM+yPnB8IT600W2/dHGrPvYbhz4zX9u+GzpdP7nHgj63Bu2+JUpZPyWuXD7V9/m+aX9YPxOM3z5vYrs+IWRSP+7U2j6+WcM+e9FRP8OSnj4TNPE+a29TP8OSnj4TNPE+a29TP4zNEz8A1j4/KI2qPmPKGz98G0s/6ftjvGPKGz98G0s/6ftjvLE4Bz8Nmkg/3HOnPrE4Bz8Nmkg/3HOnPg8eDz9SOlQ/eKhkvA8eDz9SOlQ/eKhkvA8eDz9SOlQ/eKhkvGVQ5D7cA1Y/ZbqjPszF+D63uV8/c/ZTvP2LaT3N8CA/IIxGP/F13D6Pnlk/vkSbPshuuD5Wxjo/3tEUP0lY8T7HzVM/EmCcPnHK6T6uumM/8gRTvGF7+D5Ezl8/GPZVvPF62z6ALFY/rp2uvk//6T6I7VE/rWGwvnKr6D7qU1M/P2Grvsl1eT4NzOg+AVBbv1OigT6o3ug+v5RavwCxlT4kfeA+GpFZv85aOr4zKem+Gxpfv2GIkb1kefC+/0Vhv31VU75PeOm+9p9dv/F6vr6d1lq/JDS5vhrqkr5IYmS/wKuyvhxfvr6QZl2/frasvom2yb4r62K/+OV4voqC7b6U1mC/UXXtvXrQi774UXS/ZW/3vXNXzL6Of1+/Z3ePvj4bhLwfLH2/NOAWvs52y77YV1y/xOqivnRwQL/KPiG/mh9IvsCGTb8kuAq/851+vvwoeb8WFmu+82eJuy5SQL8EqSK/0OA2PnKJfb/megO+lGpTvUDDcb+9iUA+7CCKPrwgSr/eYIe9iC8cP9Iqc78Ix2o+xKVZPtd4sr4Blhs/2ak2PxYj3b560hY/1dIuP+EFkj6nKhk/z7A/PwGygD6uQwo/8p1NP0ezGD8ft4c+dPFBPx2hKj/2e0M+JHs4PwSr9T6pwvo+fFo6P4qdPD9hMt++dE8EPwQGrz7wlwi/SQpGP+3ZVD+htsG+eVHQPjkS2D4KZWS/2e4kPgs1ILy8THm/jodoPn6BID9ETEW/8eHoPYixDz49d3m/pW8zvncAIj+lOEG/NvMwPqxOTD+UadG+loziPshQtj44f1y/N425Pmx+LT5BIHq/WCoEvuiw8D3sMX6/fLN9PHmkzL4QMF+/nPaQvgljz76dCWq/CVBLPM4qUL93+gq/cPBWvlyYTL8jABm/EeKCvZxkfb+pkhG+yf/vOwilZL/iJ8W+Gv5tPlywcr//ojQ+xqCHPmpeT7+Gxm+9LV0VP/QZ6r730Ao/T3U0P7et8r4WtbU+/EtOP1Osbj7hKgE/YdFUP8RpQT1FNho+n8p8Pw5cKD9yVRA+rHE9P6VVCz9gwQW8+r9WP/ewBj9gwQG/084uPyx/Vb7bQ92+BJtgP+aEr77ZQ0u/UYUAP+iNq76ITGe/uM6IPnxvyL60dmu/bpPgvEH7776boVi/YcGBPksylb1LLwC/QM9cP6/KHb6uYPu+IIFbP7cqzj4NzNo+VjlPP0Cx+z6oRLI+bVZMP5qEDz8980U/6baXPtMLNz/4VyY/Fg+EPk4cFj/4VU8/tCx1vGzmPD/9tiw/KU6XvIWfDT9r/0Q/iFmjvrS9ND9t2iU/anGSvmP6qj4LSdw+R7NWvyH58T6uI7Q+19hOv9sTS77XTeu+U59dv8s7Mr7p3va+F85bv8Y/1L5JdFy/OqWWvqYX+L4of1a/XK6AvhdI+r4FVF+/JpAKu9oP/b4Fili/XWVNvjTx/L2FiRC/JepQv1tlJj6GZHe/8g1Mvrir/b5AXl6/NgKLO4qGDD7Ak32/Zkclu3+v+L6dMFm/pohXPnD9HD4GL3i/rPpDPsrt7L0jvBG/DWBQP2DpLj72jES/dBYeP2OuHz+GsVc+I7FAP4KeST+dWw++gaAZPzKjWT/Eivg+bt5QPn+ndj88Jkq+vjI5PsbrXT92CP8+DROlvOGfez+jlTu+GJuVvNceVz+upPk+SJxyvuD6dT8OpTe+/DhYvrbpGz8b42E+mQhDv0JnRz8p4um9Atwdv8uCMz5eFkO/LpIfv06b+b1FRty+IftkP06b+b1FRty+IftkP4VJPL4uHG+/AM2cPoVJPL4uHG+/AM2cPvHt6r26Qn6/r3+gvPHt6r26Qn6/r3+gvPHt6r26Qn6/r3+gvN+D/D1TG3C/Sf6lPmRm6T3IVH6/V9IxO2Rm6T3IVH6/V9IxO3LSGT4qtm+/AW2ivuvqhz6R9/S+XEdWvzQwAb28ZHG/mLSpvjQwAb28ZHG/mLSpviyDCz4rDva+JsZdvyyDCz4rDva+JsZdvwUIxj65Ac8+PC1UvwUIxj65Ac8+PC1Uvyiz4z6mbsI+TqlPv/2wBj+w1kY/0Eixvv2wBj+w1kY/0EixvtTfEj8olT0/nSKzvhBOjT7aGFW/IQr2vhm9Vj7vtWW/y93GvmXBxD7TMKG+FC1ev6+IpT4H3Zi+u+Blv2pe7z65HKo+CrVRvxD61D6j2rc+3+FVv//+Ej9b8B8/JnQHv9QJEz9IRS8/O7/lvgiBBD+39io/Ue4Iv4Hzuz5lXsU+ZLdYvw28fD7LY5K+XAltv2sCKD7cnVa/phUFv0gckD6IrOi+CltYPxwNpT1vQDu/VFctPwEURT7EAzK/PUExP2oTzD3IpH6/ShDPPE4gdD5p/Xe/jFiNPazZpj24wHO/Os6Wvtbz+z06AHW/THGGvm73bj03zHu/PtouPm73bj03zHu/PtouPmmoHj3ky3q/+ItJPmmoHj3ky3q/+ItJPoy9s7z9WDS/DJo1P4UDYL031iu/qD09P/mFCL4S5hO/7SVOP07TVb4dhvy+ly9YPz0xWb5r5Am/F75QPwx1uL5Wst6+80JTP9MXqb6poEK+r69sP67O0L57nVK+SbxjP6lq7b54sro+m7ZOP2OPGb9LYTQ+4s1HP6b99L4aIw4/LSYuP+RbGL++yto+RjouPzj5y76u9u0+KW5KP0QMAb/shqY+CtFMP2odhL74FbA+KSFnP5qjjb7bvk8+7nZwP5wWB74ai7Y+xMdsPyKU0L0vFJM+6tFzP+CkpL25VRM/oVZQPwNhs72jgho/+OBKP7KP472Jv0Q/R0whPz1PK763iko/HpgWPyVnOL58q1k/QTn9PlbMbL5dcFo/OkzvPhNRMb70T3U/qP1oPhNRMb70T3U/qP1oPmDPt71De2w/VqC+PmDPt71De2w/VqC+Pmza7b2BVnk/2k1HvsC81z0z6Hg/hLFVvvn8hL0YcU8/0xYVv/qXDL1LakY/S4Yhv+ml+bzM7A8/PZFTv3xVDL4UAw8/D2lRv8AoHL3/wLc+0r5uv4NdS75Dqq4+ATZrv+NEpL1t9RM+33p8vz1/gb7fAPA9Utp1v2hM7r38ccS9lhJ9v9MOib6mKsq9+Ft1vwrp4b3pIau+Up5vv7bSar6rn5u+j7dsvwYNg72skRK/wz9Rv6LqG77Vjgy/fWFSvwDdxby0gz2/iv4rv6kfp73jkj+/MYUov6gqqLzJF1C/DwQVv969h72OMle/rZ0Jv+nLDb2R7z+/Oyspv8MCvb2q0kq/e2cavwJzKL2bY2C+XY55vwJzKL2bY2C+XY55v8hD4L0Ou2++SE13v8hD4L0Ou2++SE13vzzE3LxeEqY+kg9yvy4oyL2/y6k+kTZwv8iL/7yhzbg+25tuv3Y18r2eH7o+aI5sv1hGTr2cla0+On5wv+OZJb5O0q4+rwZtv7MfqL0lRaI+NORxv5cMYb5dPqE+q15sv30b+L3JvI8+X71zv16HmL4TJ4k+no5qv8lLHb4fXW0+Ked1v1UMvr4SWV0+HC5nv7GfK76NYUc+Wmh3v0p00r46RTg+GMhkvyLpKL4yM2k+n6t1vzwk2L5HTj0+jTFjv40sB77I1XQ+YER2v/pBXb5/JUs+G710vym1/L1la48+dLZzv2onJr4rDkU+2MF3v73Of77OZUg/guYRv73Of77OZUg/guYRvwtU175N8B0/FUsqvwtU175N8B0/FUsqv4bVib74N3U/C6HMPX0LCL/7Alg/jzWZPU9Mhb70E3A/6CprPvqcB7+RIVI//LhaPjvYh75aUXA/UzVhPjMoCr+6xlA/EvVVPu23jL57P3Q/n8XzPfClDL+KmFM/Zpr6PRqujL6sm3Q/htfbvegjDb8KtVQ/dxuavQQYeL5l62I/6fTJvvy8B7/MN0c/T1GsvjOrfr7YSSQ/PLg5v/145r4+Lh8/QxEkv1zxnL6/hYM+oKJqv/uhHr8uYJ8+/XI4vzQwr7736Mm8Dndwv6MHM7/cQOI7i/o2v8UUpL6AiZ++vQBlv4k7LL+yB6C+vaorv4H3jb67ZDa/3AMlv+rfA79vACm/6/ALv+vZir4udGy/W6WKviE/G7/1d0S/rvZUvkgHnb43cHO/SkUnvXBhIL8tiEe/u7mDu9bgoL4KU3G/NTbmPStUI78J2kK/VADvPaxFnb4O426/QDk/PiIYJb9wiD6/cNsxPmrpmL6gnm6/as5RPmmzJb9FNz2/o9o+Pjxslb544XK/Kkf4PaVOJb93QUK/aDeuPU1GZ77sl1y/eqbovk1GZ77sl1y/eqbovvLKy75Mwy+/CsIbv/LKy75Mwy+/CsIbv7Evsr2ArQm/b6xWv3MPy730p+m+eV5iv4jhKb1cHSe/g6NBv/9gwL0sDha/LQNOv6b0Dj2Chle/xdsJv9yhmL2u5i+/AwQ5v9r78z0uJHO/FyqUvsxX+j4FnE2/I0auvqMfED7hKX2/nE5BvaMfED7hKX2/nE5BvYkIDz/jIFS/QzEOvYkIDz/jIFS/QzEOvQJbxz1l13m/rsVHPo/57D72kVa/D7yTPhSl1zxqBGO/iUDsPnR6N7yfiEC/WrIoP+GvE73xdiq/u8Q+Py0tWL4EISG/7HI/P4rZvr0ffrO+OJBuP/EJor7KMpu+8htmP2OYKr5nnZi9VbN7P6XLxr4v1AG9ncZrP/z7Tb5nYhE+gR54P2qyz77eHRM+8RRnPy/hXL5kHHY+ekhyP9uyxL60G3w+xctjP/bJZr6Jhn8+ARhxP9jGtL5vuHE+W8NnP1geXb6Dd4M+jylxP0til77b5HU+qLNsP24pRL6B35Q+y/pvPzx5cL7qn40+fo1uP8PXB75wlcI+/FdqPzvrL75+V7I+OudrP52Pg703zsQ+YcJrP4gZ5L0UMr8+RsNrP6XlQL2pVnA+io54P8MN4b1Zrok+3PV0P9Senb0Anqe+yhZxP9Senb0Anqe+yhZxP2dBNb48BY++v5hxP2dBNb48BY++v5hxP658pr0hOUq/aZcbP1y0Nb5FjUS/SpsdPzpcqL1xplm/Rh8FPz1xM74gNVS/w/sHP5WjsL1dAk6/oVwWPwGpML5W2Ei//3cYP6SLpb0oCTK/dMk2P4aaE74SQi+/9uo2P/eXSr14sv++mm1dP5kRor1BHwK/7IdbP7W/Vrw0fxC+jWp9P5HEwLvNcBG+dGZ9P2Jxg7pzfVM/3D8QP2Jxg7pzfVM/3D8QPxQ4tD1bVU4/ndkVPxQ4tD1bVU4/ndkVP06l2Dym4XU/ReGNvhwCDD7FjXU/TG59vhJJoj3WTn4/SROqPQjCXz53Jng/TzvmPUIBnj1ETzc/2poxPxRvNT4Rti8/+JA0PwV6iD7wNx8/zH08P35VzD6ffgE/L8dDP9Pfrj4TjW0/I9oYPnx9ED9GC0s/xVJqPoPLez6YlXI/+NpQvrPF1T4o4WY/IB/jvWrqMj6y6FI/GggKP2rqMj6y6FI/GggKP2Rplj4+2E0/ZVEEP2Rplj4+2E0/ZVEEP13beTx283O9CIR/P5rUtT1ROGG8+PZ+P6eKxb2E2/a+iupePzt7Gb5o8Oy+w6tfP5eFTb7KySm/QJM4PyC8qL6B+hm/h006P29te776KEK/yI0aP4/Flb60FjG/EwUpPymwer52MU6/Bi0KP4TCbb44d06/hDELPz8ke76ZPT6/s2EfPwE9fb6/KT6/TEQfP6bmcr4I84C+xDBwP6bmcr4I84C+xDBwP+t1g76PrE6+a/ZxP+t1g76PrE6+a/ZxP5UpHb4TZZg+2jdxP/LTJr627bA+wJRsPxHwF75tTLo+KWhrP/l2Jr4yPbg+rzJrP/5+T759Zqk+bfJrP7JnWL7FfZ4+clZtP/2Xjb7U1oY++JhsP//Wkr58+nU+L2ltP1tkwL4UE0g++ednP0HV0L7rvgo+PyZnPx2Q9L5DhjY+wTpcPxzPJ7+m6GY9KcpAPxPcCr8dYlM+SXlQP71hOr8WEDs+siUpP7jNFb/l8gc+v8pMP9fLPL+WThA+SxYpP3ZSFr8M3EY80jFPP75wO799SlU9A9otP1GNC78B1WC+siBPP/SWNb8axM29zZoyP0oh2b7DJgS/6Hw+PzdOKL/mX5y+LlcwP/BEqL4F1O2+/INSP7GnHb8W3KC+9PY4P2+ohr6Uk5u+AmtqPy08ab54u4G+8a5wP0hBdT7m6wK/1UVTP+DxD7+TjG2+cjJLP7PQCb8mqEi+MdNRP7Y0aL8qfQ6+93HLPi5Ja7+7YSq+1+O2PtNZZ78x9gC+3YHRPi37fb8gr/u9VOnJPC37fb8gr/u9VOnJPCqYfb+P8gu+UperuyqYfb+P8gu+Uperu4WVfb94Pvy9uEl2PYWVfb94Pvy9uEl2PWMPbL9NPB2+Bte1vpEWa7/F8iG+qMy5vlxHbL9zJRm+i5O1vubnG79p2Xy+xfVAv34GJb+RU4i+8nQ3v4KgFL/JWX6+P4BGv6Obq775E6S+pc9iv3yGzb4FP8y+mA9Tv64vjr5DBZ++B7lov66oaD7EGwm/cTdQv4xIgz6Yt/y+3b5UvzxdTD/sIge/d3OUvs5XUz+uJfO+uBOcvpnPXD89bgG/Wu+jPJnPXD89bgG/Wu+jPHNhZT//4OG+I6VNPXNhZT//4OG+I6VNPR3xRj+5SQm/tKSoPpLqTT+GCvW+8kG0Pto/hT4799y+xRxdP8YMSD/QWNi+DxLrPisTTz9GQOC+PtbIPrSFiD7CVLq+O3dkPwEMhT47+dO+JFVfP084Ub6mjXO+EhdzP9OcAL967US+ns1XP1vlRL7WaZC+XJ9wP1FQ+b67m3a+je5WP49hN76oH6O+HEpuP5Nu9b7tWo6+9hhVP4sPij6u7+G+DhxbP7WBKL6+z52+ct1vPwg5+b6VJou+pIZUPw6Kmj5NLOS+88BXPwwIB77DW42+EblzP9dOnL1Ezo6+IRB1Pwlfoj5fxce+YUldP07k7L7I4G++UuJaPwkw1r6Ro2++KaxgPynJXr+t/Bq+gwLwPu10W78/URi+42L8PnE+Y78E2Sq+j77bPvvZfb8gcfK9499UPfvZfb8gcfK9499UPXwIfr+Sc/S9KlsFPXwIfr+Sc/S9KlsFPcVJar+HfRa+QCTAvgDjZL8ATB6+VDrXvtkrC78Bpn++8yNNv8GL/L581IS+aY5Uv9P3X75Knpq+q4ltv80TIL5g06C+rrlvv8eflD7seNy+7sVav3KzkD4qxti+VFlcv6yqVz+U7dO+mJWwvmKOTz9zxdW+zhDSvm9NbD/iQMS+wjYEPW9NbD/iQMS+wjYEPWxDbD+vIsW+4a0EumxDbD+vIsW+4a0EulmbUj9E2M++AMbLPohpUT8P4sy+kYzTPq6cVD94itq+eze3PtwSSD+uvuO+7PXfPnCbUj9ZBLa+rSPjPs7dqj6p+cO+6IhcP1PztD42eKW+zbtgP8zbxz7NxlU9z09rP9KxCL3u12G+yox5P6TYxL7seTW+iu1nPws0KLvJRRE+7Wh9P4W2ur7GXS8+5E1qP52PzDxpDiQ/bWtEP52PzDxpDiQ/bWtEP3GJij5tviI/5xI5P3GJij5tviI/5xI5P51xW75j5ik/pnc3P51xW75j5ik/pnc3P/uNBT1l7VY/YdMKPyz1ur23pFc/Q/gHP53KGT6iY1Y/jYQGPyCrBj14aGE/eyDyPu1yvz37hmE/QX7tPl2JIr1NFmI/lk/vPjpmBz3WpmU/D5vhPszCwrynHmY/sP/fPm0Gnj2DlmU/xQbfPj1pBz3HRmc/qd3aPlNheD2PCmc/sE/aPsMc9rvYb2c/6c7aPoPsBz0Usmg/RMLUPtloabs8IWg//ODXPiDDZz36xmc/HHXXPl+rCD2lDmo/1rDOPmqb77zvymg/NnzUPo9Mpz0kH2g/C9XTPmTT/jzLQVo/oI0FP/wjMj6qCVM/u+UJP1/4BL7hhVQ/U8wKPxRcvLuXRyG+9Mx8P9/GfT6PuRu+bPB0P1Fog75iWAm+2gd1P/UbEr13S3q/j9pTPvUbEr13S3q/j9pTPt0tlD3DBXi//5NyPt0tlD3DBXi//5NyPig9BL7dl3a/nyhxPig9BL7dl3a/nyhxPpo4Fb2toH+/gM0iPePXsrmsy3+/u6UjPeoWcb0JWn+/i4cjPaRKFb3Kv3+/UqXNPJozPrq37H+/oJ/GPAFQb73EfH+/UIbGPE1UFb3B0H+/HXctPP+ygr30d3+/ap4NPA8imDvY/H+/x6YNPF9WFb1B1H+/qS4XO8GAejxX+H+/pRghuLeXmL3WSX+/ZOFVuLZWFb1u1H+/riByNCbTqL3BIH+/K6sguyyevTxA7n+/ASseu6VYE72W1X+/qGl0OEtJBj0O3H+/Dv+Yu2jfu70g636/zYeEu+6LEL1vb3+/VU5mvUACyzy/rH+/A7UzvfEurr3T2n6/5IwovfPmLL3MFnG/L9OqvkNysD6j1ma/KKmFvlTaxr4prGK/nLCCvt7cNL0ktGq/zzLLvp3WMb9vhyW/al2hvp3WMb9vhyW/al2hvmRJKT+owSu/ksmrvmRJKT+owSu/ksmrvq01Sb1Ug2i/zr/Uvu1ZE7/bFCu/O0nxvu1ZE7/bFCu/O0nxvjfKAz870C6/qrEEvzfKAz870C6/qrEEv/HHeL3Z2Oi+v3Zjv/HHeL3Z2Oi+v3Zjv5aYmz6P1sa+hLVev5aYmz6P1sa+hLVevzY6zr72C8K+rkhVvzY6zr72C8K+rkhVv5wiU72t/To9dmR/v/Je975MupE7oCJgvwukxz7xxfa5pLxrv8DjfL1bJNo8r2t/v8DjfL1bJNo8r2t/v/Aesj40q3+8wPlvv/Aesj40q3+8wPlvv6hY675kWok8yk9jv6hY675kWok8yk9jvxCbrL3vtQ2+9518v1t/nT4loV2+pTNtv+Rk6r7d/fC9fZhhv9fv3b1htpK+3bBzv01d6r5bwnO+vEtbv3YWkT7mX8S+WQRhv7qKSj+7kMe+VEzxvmzrbj+Msbe+dDiGvGzrbj+Msbe+dDiGvDy0Uz8MbH++3/4Av5QnXD+tE469fm4Bv5QnXD+tE469fm4Bv2k7ej/uI1e+mH+mvGk7ej/uI1e+mH+mvNG2fz9HaBw9BerjvNG2fz9HaBw9BerjvNG2fz9HaBw9BerjvMM+Xz8bsuy9N37zPmrEUT+BIK8+zYLrPmrEUT+BIK8+zYLrPg/4Gj9R4zA/qEzKPiYP+z4hekg/RtTDPkMzcz/WK5I+9X8BPiEEaj9AL78+GrghPi3Hfz/9wgm9DQjJvOP8fz/yxUa4FawfPPsKez/l/6i9pt01vlsMaz+7TMi+g5SAvVsMaz+7TMi+g5SAvbu2aj9ZBsy+WpfHvLu2aj9ZBsy+WpfHvClT+T6qWV+/KcUnvYQ/6z4cVWO/qgKRvN+11D5o02i/1NSCvPFHZT9RZuO+abXGvPFHZT9RZuO+abXGvGFVsD6RVHC/z9j+u5rimD5YT3S/+q8RPK6HWj+aRgW/+IWIvK6HWj+aRgW/+IWIvLtaUj9P5RG/CJuaO7taUj9P5RG/CJuaO2bWfz95sh87e5MRPdhafz8gvrW6QUaRPUCMVj8bzv8+zlxgPrf4fz/Sb8c6u/lyPGNgYz8chtk+GjAzPvq53z76PlE/lDzAPqD+sz7W4Vk/XqPHPhezmj5dW1w/4LjRPtEgqj6Eglg/LMPVPhQATT/ICwk/DYqJPlqOTD9stwU/gHuYPp+Sfj/T/hu7t+fXPRMGTT8DJ/4+h3+rPkSjBD9hVDQ/+GP4Pqa2cz9IRiq9zEKbPmEPRT/RcyG/+dLJPWEPRT/RcyG/+dLJPSwRMD8/SyO+5kw1P23e5T5BCl6/cvlbPm3e5T5BCl6/cvlbPmEPiz6ORna/CQjiPDBVTT/rsBi/PKT3PDBVTT/rsBi/PKT3PCDhXj/6bpq9Duf4vuFJYb9gkBS+zIfnvhuJfr88ltO9SAPePBuJfr88ltO9SAPePI1SY7/sKqa9jcTnvqDef78zTNa8zrqVPKDef78zTNa8zrqVPCXIZb/Rhiu8oaXhviXIZb/Rhiu8oaXhvg+6fr/Y38s9jMFmuw+6fr/Y38s9jMFmuw+6fr/Y38s9jMFmu++GZ7//L4W9XujXvkRZfL+0q5S9iXkbvnfQf78akPm8lSu7vOz8f7/q8xs4O8MePGAVbL/1f8O+hLd6vWAVbL/1f8O+hLd6vS2Kcr8hdJU+DD8GPj85ar8I1L0+z0sjPqFeFL9AwDQ/Y1fQPqrr8b4SREo/Xe7HPgwyRr+UCto+7b3vPgwyRr+UCto+7b3vPk6RWr8OeKQ99K8DPy/bWL+lneC93B4FP2a31r7GAFM/5c/CPsimY7+QVNg+nGEzPtQ0qr6odls/v0nJPihckL4IxF0/NSbTPoHBVr9YJ/8+GyVgPgAuTb911Qg/eVCJPvvXf787nTE7FrAOPbZhf7+hcHi6nT2OPY8uXL8DiAK/PPKFvI8uXL8DiAK/PPKFvPr4f7/ToM46RXxuPCeLZr+WPt6+gv/DvCeLZr+WPt6+gv/DvE7Ia79rCce+niLHvE7Ia79rCce+niLHvOCr474dQ2W/4yyBvDGt+b7Pc1+/LbCQvCUfwL4qSW2/7iT4u7chqb5FnnG/+5kSPEypm768x3O/cVzhPLlEVL/RGA+/ldSVO7lEVL/RGA+/ldSVO6xoT78L3hW/3FDyPKxoT78L3hW/3FDyPBSifr+YKPW6XFHTPaB2R7+Njx6/KWbFPaB2R7+Njx6/KWbFPUXt875AiVq/bINXPkXt875AiVq/bINXPrwjdL/8ARq9U9SYPi1YTb9Ss/0+X6GqPmEwMr+rww2+YFs0P646Ab8aLDY/SjH6Pj8QoL7ADVo/4EDXPtrMTL+BewU/r/2XPpuoA7/oUFu/1vkhvW4PWb937UK+klj9PqaIYL/U8CO+b97nPtS/W7+vgg6+ssz8PqpnAL+awL2+3h1Iv+zFu77XxO2+PF5Ov5//ML9NjVq+trIwv6JNHr+sEfG9y+pGv3c8Pr/6EBG9NBErv++C+b6tkTe+ZchavyNSJb+vk6g9ZVJCvzYrBr8yWpM9xT5Zv0WyQb8SA8g92oElvwTxIb8ChxU+zLZCv7JBQb/T8ho+0Fwjv5VqHb+QoRc+XEpGv1AgQr8xdBs+SUwiv/QIEb8WACs+PJNOv/3iMr/Jwl89pZc2v+Cu7b6WWWw+0uhav6bH8r5p3C0+rShdv23ctL5ER5o+p7tiv5Uivr7qOI0+i/Ziv8SHi741fKk+EUZnvyVHor7ndKM+/6Fkv8HQVb5DwrQ+bHlpv5N2jr51zLI+HBBlv63EN77L9KA+mKNuv3MCiL4UyZ0+Z9ppv4yLXb7YzpS+6Zpuv4yLXb7YzpS+6Zpuv/KFor5Avq++fExiv/KFor5Avq++fExivwVkJr44dlW/7Q0Hvw/+c77SDWK/XwbPvlFDC77oNF6/SYb0vhQxX7789WS/vv7HvlLcOL59h0C/+0Uiv93Slr4b6UG/JSgVvz54kb7sLgK/5RRQvxuF0b7hV/K+XLJHvzuOv778k3y+T9tkvwpv8b7OSE2+BNdbvwrJ174Y8Jq9Flhnv9ff975MA169oZFfvzOc1L5iGMc9D41nv1VO7L5aPaU9dylivyLvtL5MEZs+dpVivzpYz77Ou4U+c1Bgvw5RgL4rHQY/AGhQv0IGp74lSPI+w31Rv4KrDb5w7h4/eYpFv3Fvfr4zaw4/oP5Kv4EIwr06NBo/r+ZKv2Ze3z0/ZUY/L1wfv52jfb7NDwQ/s/BRv7OYpb1M6Dg/59Qvv5wuib6V3Bo/GPY/vwgamz1+n0s/2/AZv1R6Wz1G8mM/Wmrnvt2ZWz6ATGw/gYWjvudMTb4bOEs/ZfsSvy5Gjz4hJXQ/hOXhPS5Gjz4hJXQ/hOXhPV/DtD7f22g/UEhgPl/DtD7f22g/UEhgPg0wwb3BfX4/idVaPQ0wwb3BfX4/idVaPWi4Qj6jqF4+TRZ1PxrQLz6fkl8+Te11P91ECj46hsM+IQ9qP/tbMb5F1Sa/oQo9P/tbMb5F1Sa/oQo9P/tUZr5S2SG/iM09P/tUZr5S2SG/iM09Pzi2BT047Rq/e6BLPzi2BT047Rq/e6BLP8lchr6yxlC/3AoEP6U1pL7Sj1K/wHzwPimIpTxBdkW/89ciPyGyR7647yy/3Ag2P6qngr4mUTS/A5IpP9pBGDwKxRm/p6hMP8flHL6YFp6+q09wP+8PZb2SxYS+6dN2P24PrL7Y7gq/Ow9FPx5Y174FaWg+495gP1DeIr+ckym9gjlFPxnLrr5VUWY++qBpPyWiJr/OCi8/j9+oPvsHHL8VDjI/nMzCPt3YXL/Aal09g7oAP9DwJb/KNyk/sIvBPtDwJb/KNyk/sIvBPsoIEb+zzYo8eehSP8oIEb+zzYo8eehSP8DbH7/0tTk/GzqUPsDbH7/0tTk/GzqUPvMI1r4qV/Y+G0NFP1MrBL/G4hg/CCQdPwt9wL1SXZ4+JUFyP1MJnL7ZzB0/I985Pw/K574zSDU/f7YKP0j0qr7pnlY/GJ3cPko46r4HEkw/7cHJPqhKk74JjEw/Ti0HP7cA5L7akUI/U1fyPrUcWr4UHyY/NP46P1CYxL48JCk/0x4lPy/XFb5yAsk+ZHJoP1uQs764NP8+tvVKP8VQab4x1XQ8rjx5PyccPr71bC+9eE97P+5RyL5liBM+ErBoP61Ts76iLr6+fx9cP1Ib175YYam+0VJYPz3rqb42bKO+Mz9jP+Bz9b6fhiO/lA4aP+Bz9b6fhiO/lA4aPz64Cr+DhgO/pUYqPz64Cr+DhgO/pUYqP9oF0r62qii/cW4hP9oF0r62qii/cW4hPx+XCL8NYkm/AAyfPneVrr7K+1u/4CzDPr1RNb/ijCK/FvKdPoXQCb+8w0+//3JoPrO8O784qie/2ro6PuJqj75McWq/bWKTPkzAE7/WCkK/q5qbPmv1vL5MFVy/TM+0PgMSPb+NJx6/8jWKPggiJb/EiQy/9BIIP4rUQL8ASt++Xxj8PsDdB79DDh+/k5ITPyPsG7+PsR69h8tKP1Niir5D1jK/15opP7dkJr3P7V2/dl3+PtZqbr7MVgC/HVZVPy+4YD63nTe/q04pP4xb0Ly0Iqy+oAJxPyKgyz5zkN++PpROP4XW0L3piCe9QHN+P67O7T7ePUe9bV5iPxMkUb0Tamk+7Op4P9VcxD5+daU+O3pdPwb4lb6Ehc4+tuxdP2N7wz1MnSQ/FIdCP8FRBL+Nxvk+xxU0P46Hrr5WZUA/p5UQP/10LL8anPI+RTARPzOgJ79T8iA/hsrWPjdiT7+PxJA+b3wDP0ZCXb8q6cs+FkydPm2YZL/X5B88wGzmPs/leL88s0M9FntqPopfY7/2+3K+0XfJPqxVbb+JtJi+Q4BoPhh/cb/rjZ++RmLpPVG9a786WKO+oIVlPpFqfr8zNlU9p+zIPUWie7+olhA9+c84Pv9MXr/oie4+DgsuPk5vXb+DAO0+SWZGPkOjHb8svD0/NOyIPoVsF7/0bUY/82tjPiUEPL5pzWc/lubDPgQXI75SHW8/Qq6jPsrszT5ffD0/4PIJP21Izj6/x0Q/ZV3+PoXZOT9D2qw+FGIZPyaIMz/WSrc+atAdP0ICSz+RcDO9UY4bP1y4Qz87Jji9tZwkP+NFND8D/Nu+VLQQP3EvJz+8Sum+09kaP5rT9T5NBzy/moz1PkNvxT6lt0S/qb0CP7aQGT5xi2m/ciHDPgDcJT2KFme/hFXbPkM5g77aPUG/Vo8aP0M5g77aPUG/Vo8aPzJd4L5LUu2+KShFPxN8lr3E7z2/B5sqPxN8lr3E7z2/B5sqP4/It76ewg2/5VdAPxv1yz0WGAO/dGdaPxv1yz0WGAO/dGdaP2B4j76xGMm+eDtgP5M8RT6qXW68wC17P5M8RT6qXW68wC17PzIDfr7nslE986Z3P8FnOD7BUtk+5SljP8FnOD7BUtk+5SljPxJ3Zb54Zdc+EQxhP7jWcz01kjA/t7s4P7jWcz01kjA/t7s4P2G6V76GeAs/WMlPP1UChL5tgVM/vTwAP1UChL5tgVM/vTwAP5DImL70yCQ/3Gc0P0nsFL86GjQ/8wHRPknsFL86GjQ/8wHRPg+TAr82jxg/XsgePyz1Sr+2/L4+zcf2Piz1Sr+2/L4+zcf2PsHmJL8qfJw+AIEzP/2PU78auK68OwoQP/2PU78auK68OwoQPzSRGr/vLms7phFMP+EBOL+9op2+5JMfP+EBOL+9op2+5JMfP5Iy7L4DXEa+3KZdP+I0vb6ivze7pOBtP7N6C79j0QE+1jJUPzuFEr9tM6U+TP1AP8raE78Egqo+Os4+P63/5b663wU/R3E5P0MWp749IwI/7wJMP3TH4L5n18U+3qZPPzAsyb4OR4Q+6O1hP04U475fSs09QwFkP15DG79lL00+4vhEP5uL677p1Fa9XediP1OGxL58Y4u+GeJhP4wy/L6qclq+A/5XP03Xi74OWQW/LgpPP5MP077GQJ2+ipVbPz03ob6vBwu/b0VHPx3eD78qoOW+8usxP1Pz0r6daBm/xLcvP7HKKr9P2oO+5vAyPwJOCL8lCkW/GVy0PuVEJL+0FkC/YagiPuVEJL+0FkC/YagiPlfmuL47ME2/wAX0PkrNgb7p/na/0G2OPQFci76jOW6/Xb56PhtfjL6q/nW/VDIdvRtfjL6q/nW/VDIdvboSab5lRHm/CHslPGH6ub4ltGe/OVxiPqxjbL+j3n2+IwqWPqxjbL+j3n2+IwqWPoZlLr5xBXy/E5IvvYZlLr5xBXy/E5IvvYxhb78njLK+1reBvYxhb78njLK+1reBvYxhb78njLK+1reBvYxhb78njLK+1reBvR2VOb6YsXu/58+5vLgAf7596ne/jnhFvDj0lL4hKHS/iUKbvXAMXr86OMi+aJadvnAMXr86OMi+aJadvq14Sb964dy+3NDhvq14Sb964dy+3NDhvm3bHb8y2jo/eAqXvq48Gr9mdBU/FFILv3dfG7+Xaks/wSGEvHdfG7+Xaks/wSGEvPdaC7/trEo/2veNvghiAb9JXSk/49ANv+IEEb9b9lI/0WEZu+IEEb9b9lI/0WEZu87cE79eFEI/gf6avrUxAb9dxxk/Sbwev6zKG78uI0s/aSa6OqzKG78uI0s/aSa6OgeGHL9MuDc/eK+qvnZVAr8KygY/Jk0uv618JL+SKEQ/CTmvO618JL+SKEQ/CTmvO9vcIL95OjA/d4C5voFVBL9pYeQ+eQo7v08cKL9jCUE/9rdaPE8cKL9jCUE/9rdaPFFaG78a5DA/lRvJvuyg974oVss+d6xHv26kJL+4/kM/Mcl2PG6kJL+4/kM/Mcl2PKI2E7/BBjM/22HZvpsIDr9bBzY/Eyndvns0IL/VqEc/Ql0ivHs0IL/VqEc/Ql0ivOkP3r78BMs+6iFPv0kEwL4AoM0+ReNVv9AVnb7l5J6+U1ZmvxMdcL5vhqi+ySlqv6Zq2752sW2+kolfvwLKQL7Y+mS/X6rPvhRisL4XLlq/GI3JvukUHr6mVGm/xkDDviLCJL4Kqny/76jrOiLCJL4Kqny/76jrOmX9lr5DmnS/ngoePGX9lr5DmnS/ngoePFhJHb5K5Hy/KNW+vFhJHb5K5Hy/KNW+vIlURr5ssWW/aSnLPmPnsr5BIFm/fN7LPgnOM74j9mm/NGC7Pq26mr6/ILW+WppiP571277YaYW+n1ZdPzDDj75/ltC+undeP/IY5L4UNbY+lE5SPyhk3L6ShLE+iFdVP/xy+b5JLbo+XUJLP3WeF79IfzA/EoLVPqbbFb+nkzM/VSTQPooYG79DJi4/wjfTPir5H79gvi8/w1q+Pky1H7/e3jU/FMOmPogwBr8vrd0+5rs7P9MfCL8FawQ/TKwrPwY+1b6Ygxo+FINlP4sRxb7tW5g+5KhfP0U80b65mLo99XpoPzS2Gr+KtF++6SREP0oqnr7kkG0+xh9sPywLgL4YIZ8+g79qP37vvL5nyMg+T7dXPxSBYL40OcU+LHtlP0AFtr7D2/0+zdVKPxPlZb4Uqtc+nfRgP8TVt764dBE/Ko09P3RBs75x7xG+gQFtP3RBs75x7xG+gQFtP8OuDb/mRtW9hYtTP8OuDb/mRtW9hYtTP4gqn76MBD2/CDcZPwwKhb6hhk+/GFQGPz1Rwr6lTEu/9AbzPj2Zkr41XFq/NnLfPtYyjb6ubj+/HZ0aP+Uxhb6gYjC/YywtP40xR79oQw++wcMcP40xR79oQw++wcMcP0BZ974qTjk/Azb8Ph1C/b5OFC0/U80LP05dDb/koUs/gK9/Pl7yDL+04Uo/gUyGPq0IGb/bND8/OhaVPnVgBr/asRc/02scP3y9Pr+W8Qy/dcLAPnwrcb8s7zu9fh6qPgNyQr+mgya/brDSOQNyQr+mgya/brDSOffFf79aByi9rEEZvPfFf79aByi9rEEZvAqHQL/D5wm/eG/CvgBGcb+WgBq9sQ6qvoY7HL/IEEO+KNlEv8H2276WHQI+7N5kvwTl0r4mjUE+VzNkv8ynv770vYo+mwZjvzlUy76lsqY+k6pbv9OevL6amKs+2/1dv4kd1L4JttA+NFJQv7D1v76fSsQ+5xRYvxgU4752mvU+29BBv/m00r6jCqo+N0ZZv3HvBr8NbNU+6pQ9v/vmBr8Adsq+SpZAv/vmBr8Adsq+SpZAv3mzLb9v49y+MTQYv3mzLb9v49y+MTQYv669rb4RJmm/xQlxvsxapL4hbGm/4hmDvt8Wqb7kk3C/acS0vYw+r76mPG2/jtgevqE1/r7AsEi/uNO+vtgho75Yc3G/DG3BvStED78Uukq/7jN6vjtvGr/QYkq/IYrXvTOvU79CQuG+AVazvo3hbb9QLz69zrG7vtY9ab8ahdC+HAuCvcaeKb97Nu++P90Vv2GJOb/SGxi+DD0svzpADL+aVO++mZwxv/Em2b5og0W/HcjyvrUtF78uUT2+dxlJv3drFr9PnD29y85Ov7liEL+A+pg91IdSv+50Pr9wGgM9m90qv3utP79D1hQ+KpAlv4xhbb8DOzk+1NSnvgEAbb833zY+55uqvn/Fdr8qpoE+N1unPdQver/PhRQ+vC4ePtRZZr+qyYs+uzuuPtRZZr+qyYs+uzuuPkPpf78S7NE8rzTIO1wCdr9HT8E95iOFPlwCdr9HT8E95iOFPjLsV78jqEI+0aAAP5tza78gcaO+cu5pPptza78gcaO+cu5pPmgrUL/TtaG9vZ8TP50rC7/E13I+dxxOP8zn+b7UfQ4+fJNcP7ndEL+3eCo+jbhOP+WSoL5awcM+JYJeP6HNkb7x55E9xLh0PzMAqr7JUeE+3JVVP2TIrL6+bou+eK1mP//y7b7Ero2+1FFXP0h9G79SYi892RJLPwbxGr/OzSw+vidHP9+aUr8mw4M+E8YBP9sKOr/NoN8+I7oHP+cVEb/cJRg/lRISPyW+RL8cqa0+U+IKPyW+RL8cqa0+U+IKP9jkGb8qECw/7VTdPi1F3b44Oyk/OgYdPwxmNL8plHi96Pg0PwxmNL8plHi96Pg0P7rkYj062aE+ZnVyP99XAD+XjyU/0iYTP99XAD+XjyU/0iYTP7pk4j24eiM+iSB7P1lfP70dex8/nuVHP3EgoL5s/2I/7lSuPnEgoL5s/2I/7lSuPmYq+L4gkzE/BGgIP2HY+r4I+DE/86cGP+OjI79lbTw/NiRkPuOjI79lbTw/NiRkPup4DL/xICo/W9gBP10lC7/Vsy4/PDr6Pqw/Hr+3gEA/ZXhqPqw/Hr+3gEA/ZXhqPudjF79yUDE/fHDTPowLFb9c/TY/RVvGPs0zfL6o0Y0+/cNtP/Q/Hr5YzMk+V+1nP7EE1D0YpAW+TGx8P/fvbT4wMwS8Efx4P+Noqj4qKMy+qsJaP/8n/z5m8XS+rVRVP2rd5D5JnhW/jV0tP8E1qj7PVfm+c8NOP8E1qj7PVfm+c8NOPzsOMT9pU7i+lUogP0W6FD+eCp++IZhAP0W6FD+eCp++IZhAP7eIXT9PZwW+18L3PlBnRD+dGKK98PEiP1BnRD+dGKK98PEiP8bBMz8flFe9OsY1P4Wocz+PNAs+a8qMPjTYWT/CEB0+jJkAPzTYWT/CEB0+jJkAPyXMWz8J7yY+WN34PreKZz+RGMM++lBEPg2AUT9zdao+xNjvPg2AUT9zdao+xNjvPleGRj9G8q8+B5cHPzT44z50Yl0/7bttPrrSAj9GLS0/2cIHP1FFqb4T72Q/+m2aPg0VhD2ChRo/z29LP8+z5T6k5Cc+XOhgP8ozbT0TBlY9Vzh/P95YUr9XZpu9x5wQP95YUr9XZpu9x5wQP9Oogb0f8fY7qXp/P5gfaL9ZIRE+1VrLPpgfaL9ZIRE+1VrLPkVNPr7/u5Y9b9V6P5qvXb+BBOg+1KNYPpqvXb+BBOg+1KNYPko+Gb7eKJA+HKNyP4KMHL8jtEg/wZPaPYKMHL8jtEg/wZPaPcceEL/6xRQ/W3AWvyLGTb8rGLw+ZpHvvuJNAr+pBtg+VBFAv8WqLL9aP5E+Qn4uv+W67L79Qbg+vHRPv3sG6b5tmOU+pO5EvzFZGr810IQ+nyNBv5jEHL9fyr0+P8Iyv348Rb/Vrw8+RDIfv/RnSL993X0+zhkSvwqmWr/Gjus9X9oBv+N6c784lM876B+evhPxdb+jjX29XIeKvvKAer/H4Oq9o1svPiigfr9fZru95vNFPZh7eL8aj/g9QK1UvhInNb+Njos+peImv6cjHL+MLdU+/Jwsv2OsA7/TUXU+4MxSvy3L8L7Upd4+BZdEv7iTFb/u/Tg/VRy9Pql5Cb+6ZcA+YFhBP5PWA7+UoSW9jTJbP1ohbb/URj0+3R6ovq0ver/5Plg+WdqPvK0ver/5Plg+WdqPvJfHar/UFDs+A2i1vj/ger8uPUs+Zvd8vD/ger8uPUs+Zvd8vFO/a7/yuAE+KMK8vtbNfb9VqQU+JZjau9bNfb9VqQU+JZjau2Ycbb+IIP880VzAvrLQf7/T8xo9N2dju7LQf7/T8xo9N2dju9ZJbL/+ZcW9Hry+vhIbf79Piqq9DkjPuxIbf79Piqq9DkjPu018bL+E7gC+iyq5PgR5bb8kO3w7kTm/Pk8Dbb80x849Z3i6PsXHbL/4uTI+aemsPqombr9b7Ug+mLSePp5ZH7+m+kc/zTRFvZ5ZH7+m+kc/zTRFvV82FL/K1Tc/drnFPtE7Fb/zAjs/9x62PsY12r7Srbc+55hUP2/DHr/nqkg/IWP9vG/DHr/nqkg/IWP9vMRPHr/AJkk/krFZvMRPHr/AJkk/krFZvBVAFb8BWzM//aLSvq6zFb/L3zg/Cy29vpm1Gr8DDS8/8FzRvgGm5r4Nn74+KLtPv4/g+76OaK8+DOZMv01w6b7I8sA+HGlOv1j4pr5uuMq+VsFbv1HNnL7Y3eS+PylXv39g0r6X47u+GqZVv9OxU75ACmm/H5q3vgzSQr6qLm2/uzumvlj5dL7hU2m/Q2OrvlZSF75jLn2/DWMCvFZSF75jLn2/DWMCvGJOFb5GQn2/O0zRO2JOFb5GQn2/O0zROzE1JL6UV2y/Es+yPlN8Ib600W2/e3GrPvMbh75AX9u+FzpdP7HHgr6vBu2+KEpZPyOuXL7S9/m+an9YPyqM376aYrs+EmRSP/vU2r7uWcM+bNFRP8+Snr44NPE+X29TP8+Snr44NPE+X29TP4bNE7/91T4/Ro2qPkbKG7+UG0s/8+9jvEbKG7+UG0s/8+9jvKg4B78Umkg/3HOnPqg4B78Umkg/3HOnPuMdD79wOlQ/jaJkvOMdD79wOlQ/jaJkvOMdD79wOlQ/jaJkvHFQ5L7YA1Y/aLqjPu/F+L6tuV8/1/tTvGiKab3R8CA/HYxGPyR23L52nlk/AEWbPtduuL5Rxjo/4dEUPzxY8b6/zVM/TWCcPp3K6b6jumM/LgZTvHx7+L48zl8/8/NVvOd62754LFY/4J2uvmr/6b5z7VE/6WGwvpOr6L7kU1M/MmGrvsp1eb4BzOg+BFBbv06igb633ug+u5RavwSxlb4TfeA+HJFZv9paOj5DKem+Fhpfv4uIkT1tefC+/EVhv4ZVUz5heOm+759dvwN7vj6f1lq/CDS5vjHqkj5FYmS/uauyviZfvj6PZl2/cbasvom2yT4p62K/F+Z4vmmC7T6d1mC/iHXtvYbQiz73UXS/Um/3vS9XzD6uf1+/BXePvtYbhDwfLH2/RuAWvnR2yz4UWFy/8OmivnRwQD/TPiG/Nh9IvtmGTT8quAq/jpx+vgkpeT8qFWu+c2+JuztSQD8CqSK/COA2PoiJfT/BeAO+wWdTvTTDcT8qikA+DSGKPtMgSj/EYYe9ZC8cP8kqcz88x2o+QKZZPrd4sj4Olhs/1qk2P9Qi3T590hY/5tIuP/0Fkr6qKhk/yLA/PwWygL60Qwo/7p1NP1azGL8Gt4c+bfFBPyKhKr/oe0M+IHs4Pw+r9b6Owvo+gVo6P5SdPL9hMt++Zk8EPxgGr74AmAi/OApGP9nZVL/WtsG+k1HQPkcS2L4dZWS/6uwkPow0IDy/THm/YIdoPmiBIL9UTEW/X+LoPa2xD740d3m/R3AzvlEAIr+/OEG/kfMwPptOTL+uadG+vYziPi9Rtr43f1y/3oy5Pqx9Lb5MIHq/HyoEvr+u8L30MX6/ZqN9PBykzD4cMF+/2faQvkdjzz6SCWq/4T9LPNcqUD9k+gq/pfBWvjiYTD9RABm/yOKCvZ9kfT9GkhG+5g7wOwalZD/FJ8W+ev5tPnGwcj+oojQ+WKCHPn5eTz9xyG+9D10VP2Aa6j4q0Qo/BnU0Pyuu8j7DtbU+tktOP2isbr4ZKwE/PdFUP/BvQb0zNxo+kcp8PxlcKL/IVBA+qHE9P7lVC7/s0wW87L9WP/iwBr+BwQG/uc4uPx1+VT6NRN2+6JpgP8CErz4AREu/H4UAP72Nqz6PTGe/wc6IPk5vyD6+dmu/5JLgvPr67z6ooVi/lMGBPkcylT1MLwC/QM9cP1DKHT6vYPu+JIFbP44qzr7ay9o+bTlPPz2x+76iRLI+b1ZMP4OED79B80U/MbeXPs4LN7/5VyY/Mg+EPkocFr/7VU8//S51vF3mPL8Mtyw/YVKXvIifDb9l/0Q/o1mjvpu9NL992iU/nXGSvmP6qr4USdw+RbNWvzL58b7XI7Q+ythOv7QTSz6ETeu+a59dv5s7Mj7B3va+Jc5bv7w/1D49dFy/haWWvqMX+D4gf1a/na6AvvJH+j4QVF+/lYEKu+kP/T4Gili/BmVNvi7w/D1ziRC/NupQvx1mJr57ZHe/Ig5MvsOr/T49Xl6/QAaLOxyHDL67k32/rD8lu4iv+D6tMFm/iodXPpT9HL4TL3i/h/lDPobu7D1DvBG/819QP0PpLr5EjUS/FRYeP3OuH7+isVc+FLFAP5SeSb+WWw++aaAZPzujWb+uivg+Tt5QPn2ndr8jJkq+6jI5PtLrXb9MCP8+ehOlvN+fe7/ilTu+wpaVvNIeV7+cpPk+1pxyvtv6db+9pDe+kDlYvrPpG78N42E+mwhDvztnR7+B4Om9Fdwdv/2CM75MFkO/QJIfv5eb+T1gRty+G/tkP5eb+T1gRty+G/tkP+dIPD43HG+/98ycPudIPD43HG+/98ycPm/t6j27Qn6/m32gvG/t6j27Qn6/m32gvG/t6j27Qn6/m32gvKyD/L1OG3C/b/6lPkVm6b3GVH6/sN4xO0Vm6b3GVH6/sN4xO1LSGb42tm+/x2yivuTqh77K9/S+TUdWv1YxAT3JZHG/S7SpvlYxAT3JZHG/S7SpvvOCC753Dva+EsZdv/OCC753Dva+EsZdv9MHxr7YAc8+QC1Uv9MHxr7YAc8+QC1UvyCz4768bsI+SalPv8iwBr/b1kY/rUixvsiwBr/b1kY/rUixvqrfEr9LlT0/lSKzvhhOjb7WGFW/KAr2viO9Vr7vtWW/x93GvmTBxL7FMKG+Fy1ev66Ipb4k3Zi+t+Blv2Re777EHKo+CbVRvxD61L6i2rc+4OFVv/7+Er9Z8B8/KHQHv9gJE79ERS8/N7/lvgeBBL+39io/U+4Iv3Hzu76NXsU+X7dYvwy8fL6iY5K+Ywltv3ECKL7PnVa/uxUFvwRJ0L5qqgG/155CPy1m5r4uk+y+nqJDP2vEBr9bjm2+Z2VRPzockL5SrOi+G1tYP3SNnb406dq+oJlZP5ydO7zyKwC/e5VdP5hZLj9/xR2+QEE3P5hZLj9/xR2+QEE3P1gutb2kIDC/l2U4Pw+U4r0UGSi/Rf0+PwSDkz7smWi/SNOaPgSDkz7smWi/SNOaPkMMLj8ylTu/m4fvvH/Msz0R3nW/PVqHPn/Msz0R3nW/PVqHPk62pz58BG6/kiEsviGVAj98lVi/dsYePtrqvT7HtwC/WeBHP7BuUT8NlAG/w8OLPmwxbT9/wUw+fiijPntRHz/jTKm+RqA1PzZKAz/fz+A9WvdZP8y+dz828YA+4y+pu5Fql74IVtO+nopcP78RGr/g8mu9JepLPyceJz3O/EY+Fud6P1AnuD729Fw/CoG1PlAnuD729Fw/CoG1Psv+qr5GbBO/ywc/P8v+qr5GbBO/ywc/P+Yh576XqEG/u0vyPlfdtz5DtGc/NyhpvpZDkr7ELys9Mxl1v16+Ub6eRBU9CWZ6v9MIfb6jIDm+l7Rzv0fyLb53USS+lOl4vx1GNb6gNdO+2sFkv8PsBr59Eqq+qhhvv/n4Kb8uRMS+8Vwkv/n4Kb8uRMS+8Vwkv4rOdb8K8Y2+cXQNPUgzPL82qWi9YO4sv0gzPL82qWi9YO4svxb+fr/P0K89zoi0PAysKr8ARpk+S70uvwysKr8ARpk+S70uv42WYb+bffE+cZACPRYsX7/voew+9HAmPniffL8rtLo98/4IPkCtYL/f+uU+C0krPnGcfL+4tp493+ARPlYCY799keU+ESDmPS3ofb8Jy4c9Rk7fPROeZL8d0eU+nRUCvaiEf79I2no9ZxVNO0TjXr9XDNI+/vOKvt+neL882k490vZtvnZFbr9HgaK+HPA5vgNQcr8myaS+DCu1POCAcr8XyJq+vHDZPWxSc7/p+I6+ErkLPiWBdL9YBIe+vFcKPo0sBjy7FSG/mPRGv6SAvz69A2y/WEXOvaiXmTyZUya/+YtCv8VNvb6N7FG8LtZtvzpiFr9tQBI/MLwSvzpiFr9tQBI/MLwSvx0nzb6+QlK9si5qv3v9Ir9dBgY/l/AQv3v9Ir9dBgY/l/AQv9BFAb8B36+9091bv/u4Qb/p59o+UzP9vvu4Qb/p59o+UzP9vgi0cDs58Si/V1ZAv2yBGb9FmCW++6RIv3sKb7/zHVA+xNeWvnsKb7/zHVA+xNeWvsA/hr4KQkS/LwsWv7bJMr+NMB2/jEe8PrbJMr+NMB2/jEe8PuZw6z5XrGG/jQPbvfKS9z4tVF2/lBMMvq6Nrj4ehSy/4MsnP66Nrj4ehSy/4MsnP/9XFj826wy/SOcXP/9XFj826wy/SOcXPwl6aT7WlyC8jT55P1bEgD6fCB09wJN3Py2BA7+/uqs+rClKP21zir5+pgs/kBRLP4mMR78+4hc/GdFNPhzqLr+6rjY/omQePuAhE7+0704/LZACPljyXL5ocxY/kp9HP/h0B79Fa1c/JLnfPUnSV75A+x4/wUJBP0wKQz5d2ec8gTV7P0rZ3z76WRy/oAEpP0rZ3z76WRy/oAEpP6ZnYz4l7mY83JR5PyYD+j6cCxq//8whPyYD+j6cCxq//8whP2IJ1D78Y2e/27/bvWaLLb+W8FW++W80P2aLLb+W8FW++W80P5IEc7/D96C+6d1Ru3FqK784EnK+HkA0P3FqK784EnK+HkA0P6C5trxAwG49PYB/P0V5ID+2/qs+6fczP3SwCbxrlA09htZ/PzdSIT+tcK0+UdwyP8jrtTyDQpO8P+V/P5S1Hj+4Src+ur0yP1v3JL8F8pm+Hf4zP1v3JL8F8pm+Hf4zP5tG97wxkYA90WB/PyskwT4PQyg/7AYnP8WLPL95+DI9380sP8WLPL95+DI9380sPxq67L4zaFs/icdoPhq67L4zaFs/icdoPrcWfL+A4zG+lzpFPPzoc79v35q+OnTcvDfaR7/xW6o+WmwHv9XfOL/ve069C5wwvyNyV77Y6vk+ctdYv5hJ5b2q0JY+EvVyv2UvVD7s9lg/ETD6vmUvVD7s9lg/ETD6voZC/T4ucQ4/nukqv4ZC/T4ucQ4/nukqv7k8LD9FVz0/JHCQPLh+Tj+YNxc/IdysvFOWXz/PS/k+SrYwvFJ3Fj9Qzc4+i3Qzv1J3Fj9Qzc4+i3Qzv/oUZD/Gf+g+wShRu3+xHT8XALQ+3Xc0v3+xHT8XALQ+3Xc0v2uhBL3emqk9Zfx+v5JpLr9dqkK+vvY0v7ycUb2r3Qw+Pzp9v8Y6ML8suSC+O0k1v/LKcr8JOqK+gXE4vPDGNL+YzYA9d4s0P/DGNL+YzYA9d4s0P5Nsf7/oKIk9KcRAuwVGNb+R5AE9GZU0PwVGNb+R5AE9GZU0P3/IAjvcE4E9k31/PyUdNT85tZg9O+ozPyyZ3ztB2CA97ct/P6cINj8gsaY9AsoyP5i+gTyWVPi75fV/P/DJND/TG+w9m9QyP85JNb8lEye9t3I0P85JNb8lEye9t3I0PyqlZrsksIw9xmR/P/3KGT+Bnuw+Jf4mP2VKLb8EPI8+fUouP2VKLb8EPI8+fUouP10VB75ztng/3H5JPl0VB75ztng/3H5JPnRtfb+LpBA+KRTZOzSqf787dDw9hhy3vAw4IL9GVwk/5uwQv5KBMb/KUTs+IGwyvwEDHLyane4+TXxiv7YnKzz/tI8+3bF1vye3Bz9M9ys/JnUEvye3Bz9M9ys/JnUEv0woLD9mAp8+q/orv0woLD9mAp8+q/orvw9KaT/FudI+Vh9SPNNfeD9deXc+bpmJvE4Ifj83sPw9KJsavAC+Mj+NVhg+dEQzvwC+Mj+NVhg+dEQzv/wafz8n9ao9kxlAuwslND89nrs9HF80vwslND89nrs9HF80vzAnAjuR7LI9SQV/vxrXM7+A/qM9rAg1vzGFzjsdkQ0+YIl9v6d+Mr/YtdQ9oJI1vwqVf7/kVGY9oDUjvEM5LT/TCyG+jiQ4v2G0cT+hiqi+gZ1pvEssLz+N1x++CFs2v3F6Jj1tbBQ+MRV9v+iyFr+2M9I+uEQyv+iyFr+2M9I+uEQyvx8odz3QbEM+z9F6v1QYDr+pT/I+OB4vv1QYDr+pT/I+OB4vv3yd0j2UkZ4+PPxxv18R577DeB4/L4skv18R577DeB4/L4skvx43Mj/aUw2+M1o0v7Z4Pj4oJds+eGlivwVjR75bq1g/A9H9vgVjR75bq1g/A9H9vnO9SD8dwCI+F5IZv3PwFz+/6EY/GN9WPnPwFz+/6EY/GN9WPkR5aD/bY9a+PXqMO23eZD/FHeW+I5CzvIqYMT8qXhm+PFk0P4qYMT8qXhm+PFk0PzzAFT/HgtO+Ba4yPzzAFT/HgtO+Ba4yPwNmHzy0N847mvt/P87lLL1G//G89qh/P9rysL4maCw/40gnPxzOFL+8PNg+8w0yP7Q7Hb+e9kk/dXOePOMuPr89Wys/H3zqu8HbVr+yKws/OEjruwOEH793MLg+RcoxP1hKX7+mUvo+xMZdvN0SJL+3nK0+9EswP48Kj7sunuY8aOV/Pwl6KT8EMH++svM0Pwl6KT8EMH++svM0P5GMZLwCUZQ78/h/P+6IIz+/Npq+rjw1P+6IIz+/Npq+rjw1P57XbT/UT72+2iktvGwQtb6gxSY/mtUrP2wQtb6gxSY/mtUrPxQTlr5OA3Q/V3GYvYf1wr5rHyQ/bpQqP4f1wr5rHyQ/bpQqPxJvML77HsI8Nhl8P4n8Fz4zbhi/tSFKP1rVR74/ISg8cxB7P1QGHD6PxBi/Ka9JP+T7er7/n5y8oCR4P+lIMj6EWBW/2xZLP6PM474BXB0/abwmP6PM474BXB0/abwmPzT5Jr5r5Q49cWp8Pz/d/j4cyLK+Vz1LPzkyvb1+Ejs/OSYtPzkyvb1+Ejs/OSYtP2C0SD+ui+s+aGLVPmC0SD+ui+s+aGLVPjc+GL56Fn0/KHC6vDXJjL5m5XQ/TQ7FvV9z7D4870M/9IHlvk3ZCz6sVjA/UUI2vw+uKj/nZ1I+uWc3v+nI/z7yA9o9cRVcv2pZcD/uSz6+62OUvmpZcD/uSz6+62OUvnHFMT9lVeu+WbsNv3HFMT9lVeu+WbsNv/aEPD+JxyW/TotIPtWaGD/TOUu/NzD2PVos+D5hVl6/+1nUPUTfDD9hqw6/oCwfv0TfDD9hqw6/oCwfv1sv5T5LY2O/wI7TPT10/z4Peha/fgcjvz10/z4Peha/fgcjv5iinD5qhO086pxzv4htbToY0CY/ajBCv+u0tj7ySkE9FNduv0z8DT3xFyg/s+BAv8N1lr5OwnM/wDSrvXavj730nDA/znI4P3avj730nDA/znI4P3yS7j3yMX4/j6yzPOQo1b1Oui0/giE6P+Qo1b1Oui0/giE6P2JZLL7uhZY82018PxryAL5zmSq/HSE8P+GnRL4n4Fq7uzt7P+n97L2NTS6/5iM5P9o5cr73Ik+8rLZ4P5l7ob1tBi2/oJc7P1NFNL7Dmy0/Mqk2P1NFNL7Dmy0/Mqk2P18TIr71Rm47dsV8P34Vmj5bCxa/l5RAP/UfEz63qCo/UT07P/UfEz63qCo/UT07Pz0gZz9/JPk9cyjTPj0gZz9/JPk9cyjTPqobOj7Ka3s/D49JPQ4qyT0Iw34/qlqtur/NKT/lMh0/UgLbvrvYvD5CvSg/hMUnv+HzJT+7XHg84uJCv79n9z6Af7a7yB9gv+6QRj8ETgO/JVO8vu6QRj8ETgO/JVO8voHU4z6g2yS/m1Afv4HU4z6g2yS/m1Afv4b1yj7cXGm/GGTfPSZ7YD5jn3m/zKIKPUnrtT0m+H6/mKVFPDsrkj6U4yu/cBEvvzsrkj6U4yu/cBEvv1dVMj0cv3+/7NsVPC3naz6sgCq/Wp81vy3naz6sgCq/Wp81v2denj5AH3E8pWpzv69ejD7o4C4/4Egtvx2ttT7lYJq7slZvv9R8mD4jGio/L3cvv2RP2z3hgX4/P11PPIguBrwuFSG/CfVGv4guBrwuFSG/CfVGvxeBv76aA2y/iUjOvS2UmbwwUya/UoxCvy2UmbwwUya/UoxCv7FNvT432lG8MtZtv1NiFj+MQBI/+LsSv9smzT65PlK9wy5qv5n9Ij+7BgY/HfAQv8dFAT9o3K+9391bvw+5QT+T6No+fTL9vo9IcLvm8Ci/oFZAv49IcLvm8Ci/oFZAv6SBGT8jliW+66RIv4wKbz/mHVA+XdeWvkdAhj6YQUS/pQsWv0dAhj6YQUS/pQsWv/XJMj/uMB2/W0W8PvXJMj/uMB2/W0W8PvNw675IrGG/ugbbvU6T974uVF2/4xAMvkWOrr6JhSy/SssnPwNYFr/m6gy/j+cXPxJ7ab4koiC8fT55P4zEgL6iCB09uJN3PzGBAz/Nuqs+pylKPzGBAz/Nuqs+pylKPzVzij6bpgs/hRRLPzVzij6bpgs/hRRLP4CMRz8l4hc/v9JNPrvpLj/0rjY/AGcePr8hEz+3704/WZICPiXyXD6EcxY/gJ9HPyXyXD6EcxY/gJ9HPx91Bz8ma1c/GLvfPWDTVz4o+x4/wkJBP2DTVz4o+x4/wkJBP0QKQ74hz+c8hDV7P2zZ374iWhy/cQEpP75nY75N5GY825R5P+4C+r5SCxq/XM0hP9UJ1L7lY2e/eb7bvYcQtT63xSY/fdUrP4cQtT63xSY/fdUrP7ETlj42A3Q/j3GYvWL1wj6XHyQ/TZQqP2L1wj6XHyQ/TZQqP9luMD6iIsI8OBl8P5z8F74Kbhi/0yFKPzXVRz6HJCg8dhB7P2sGHL58xBi/NK9JPyD8ej4pnZy8nSR4P/pIMr6RWBW/0RZLP+HM4z5LXB0/DrwmP+HM4z5LXB0/DrwmP6D5Jj7o5g49bGp8P+rc/r6vyLK+Tz1LP1QzvT1GEjs/byYtP1QzvT1GEjs/byYtPza0SL8xi+s+kWPVPja0SL8xi+s+kWPVPpVAGD5lFn0/Y2W6vJ/JjD5a5XQ/Sw3Fvbxy7L6p70M/JYHlvkXYC77ZVjA/MkI2vwWuKr+pZ1I+xmc3vzbJ/74iA9o9YBVcv0VZcL+cTD6+nWSUvkVZcL+cTD6+nWSUvknFMb+gVeu+c7sNv0nFMb+gVeu+c7sNv8+EPL/JxyW/QYpIPs+aGL/nOUu/Gy32PUEs+L5qVl6/YFnUPT/fDL9iqw6/pSwfvz/fDL9iqw6/pSwfv2kv5b5BY2O/PJDTPXt0/74peha/TQcjv3t0/74peha/TQcjv9qinL4YgO084Jxzv5VpbLoi0CY/YjBCvya1tr4bSUE9C9duvzD5Db0UGCg/l+BAv9Z1lj5OwnM/uDOrvYuvjz3gnDA/33I4P+mR7r30MX4/46yzPEsp1T0eui0/riE6P6RZLD6yiJY81k18PzfyAD5umSq/ISE8PzfyAD5umSq/ISE8P/+nRD6Z0lq7uDt7PwD+7D1pTS6/CCQ5PwD+7D1pTS6/CCQ5Pzw6cj7RLk+8pbZ4Py18oT1JBi2/wJc7Py18oT1JBi2/wJc7P11FND65my0/O6k2P/kTIj4bCW47ccV8PzgVmr51Cxa/k5RAPzgVmr51Cxa/k5RAP0AfE76UqCo/ej07PzggZ78II/k9oyjTPjggZ78II/k9oyjTPu0bOr7Fa3s/rpBJPR8pyb0Jw34/vwWtutvNKb/VMh0/JgLbvtvNKb/VMh0/JgLbvpHYvL4svSg/psUnv5HYvL4svSg/psUnv93zJb+lUXg85uJCv89n9750fLa7wx9gv9CQRr82TgO/FFO8vpHU477I2yS/bVAfv3H1yr7TXGm/f2ffPeV6YL5gn3m/XKwKPfbqtb0l+H6//L1FPEQrkr6j4yu/YBEvv1tVMr0cv3+/NusVPLTna77RgCq/LJ81v4Nenr5VGXE8oWpzv19ejL7A4C4/F0ktv19ejL7A4C4/F0ktvx+ttb7yYpq7sVZvv498mL4EGio/XHcvv498mL4EGio/XHcvvyRO273lgX4/KWRPPN3qNb/4Sme+OZUqP93qNb/4Sme+OZUqP93qNb/4Sme+OZUqP/DhSr/xAe6+0B3KPvDhSr/xAe6+0B3KPhzTQ79W55y7qeIkPxzTQ79W55y7qeIkPz0SB7+vS4e+aK1OPz0SB7+vS4e+aK1OP3p4nb5su4u+ZFtpP3p4nb5su4u+ZFtpP8YtBr+cMkw9BKVZP63ABL90xSO/1ToRP4Sl1b4qVma/j9UCPkPwbL4JUC2/g9syP1BJpL3xMna/USuGPo7jmj2TWyK/4vpEPwwQiT6OtWS/mbu4Ps0+yj4/X+i+OXpMP0Dujr3AHoW+n4x2P0Dujr3AHoW+n4x2P0lnJD6+BV++bHN2P7PK1L2JL1w9Az5+P73GtL3ak4k+HIx1P1zkpL53flc9b/xxPyGeWj4Ra4w7ahh6P/tNdz5E93E+0/FwP0gZFz87nRe+gCVLPz/zWz/Pm6e+0VfJPn+PJj+eNBg+O6U+P61Zcj9t9Q88JOSkPr+KIz/lkt4+N38iP0pIaj++CrA+zmhXPuOv/D6/MDo/Fi/0PmGWgD4EX+k+F5paP1gjbj6rQCU/+zs6P8qGjb28GfE+WCVhPz+Ukr7kevY+pRNUPxH2PzwIlTc//WQyPxH2PzwIlTc//WQyP+yIZL5mIj0/uckiP+yIZL5mIj0/uckiP5PJUT6hQ2o/vdOxPo2l1T4dVmY/zdYCvoezwb1XkXY/neeAPpRJpD3TMnY/HSyGvhZXx75G2mg/6KwUPiYQib5ptWQ/Pby4vrqoMb+tXjc/k2aVPbqoMb+tXjc/k2aVPR1q5L6KfjY/0YUKPx1q5L6KfjY/0YUKP4V7I7+9LiM/BrzcPoV7I7+9LiM/BrzcPoV7I7+9LiM/BrzcPsyK+r5pG+8+IIw8P5P2BL9mh4c+egBQP+QuOr9cueQ+FmQFP+QuOr9cueQ+FmQFP8bqRL8gXWg+AewYP8bqRL8gXWg+AewYPySHZr9+Qtc+P4DjPZ/deL93yAg+NEhFPjbzW7/Zm6c+8FfJvqFZcr9r9w+8Y+Skvh8ZF78GnRc+nyVLv++qHr8pwyo/46rTvu+qHr8pwyo/46rTvhk+yr6pXug+jnpMvxk+yr6pXug+jnpMv6xmJL77BF8+f3N2v6xmJL77BF8+f3N2v1zimr0aWyI/SftEv93wbD7dTy0/oNsyv7Pujj1FH4U+i4x2v7Pujj1FH4U+i4x2vzZ5nT71u4s+L1tpvzZ5nT71u4s+L1tpv/7K1D2oL1y9AT5+v3rGtD3/k4m+F4x1v4bkpD4XfVe9afxxvwWdWr7Kfoy7eBh6vxZOd74S+HG+xPFwv5aWgL67X+m+3Zlav3OPJr9kNRi+PKU+v5iKI7/Ukt6+Y38ivy9Iar+DCrC+VGtXvuiv/L6bMDq/ei/0vuiv/L6bMDq/ei/0viojbr6rQCW//zs6vyojbr6rQCW//zs6v0VsO79S+S2/pXs+vUVsO79S+S2/pXs+vU3JUb6dQ2q/6tOxvj+zwT1SkXa/xOeAvrf2P7xPlTe/tGQyv7f2P7xPlTe/tGQyv9yIZD6JIj2/lMkiv9yIZD6JIj2/lMkivxmGjT2CGvG+JiVhv4mUkj6Ke/a+aBNUv47Bnz6hPIy+e+Rov0GL+j60G+++4Ys8v4z2BD+1h4e+cgBQv5guOj/cuOS+umQFv31q5D7Nfja/T4UKv31q5D7Nfja/T4UKv4t7Iz/KLiO/zbvcvghXxz5y2mi//KgUvsSoMT+rXje/CGSVvdaqHj98wyq/IarTPhSHZj9XQte+mobjvY7deD/cyAi+KElFvt/4cj9EPyY+Di2KvpjqRD/YXWi+K+wYvyfTQz9u95w7oOIkvwLrNT9qSmc+HZUqv6YtBj+IM0y9F6VZv2wSBz+4S4c+R61Ov2wSBz+4S4c+R61Ov7TABD9ixSM/4ToRv9vhSj/aAe4+PR7KvlBsOz9J+S0/wXc+PeT4cr8LPya+9SyKPpXBn75WPIw+g+RoP0yE6bzk9Uu+rMN6P+oKpL4X+tG+lplaP2kkab2OG+y8e3p/P5GHTT4XkH++AIRyP8y44D50aIe+n9VbPzypgj7iDAA76YV3P51r/7so0Ru/yRtLPyM3Db7a50G/F18jPz8Nqz6Friu/bowpPxPHcz7wWli/bgz1Pvb5Iz8AYB2/TaLrPvB/Fj/fo0O/Jc6HPmHkXz/xwde+BJ51PolWJj8E9YG+TW03P5pfTz8RG1S+9m0MP+ZCIz/j3Ny6Hy9FP+t8Ij82byE+Y6lBP3IR6j4Xi5q6ga5jP78XWD+aHBm9tOwIP9STWD+N5wo+TAAEP3ZwfD8PTBS+zwanPak9dj9dWlO+Qrg3vjxufz+Swn49qHDEPAqEdT/DCIE9F2SNviwzdD/S75g+DU7tPOiPbj/anpA+8RNpvt5NTj9m5xU/i36zPdQdUj/KEpw+W133PjeiQz97F+4+NNbkPibEHT/fNaA+wQI5P8643j7So6U+HB5XP1eEGD8ckAk/eM8YP2Tdwz5qsA8/IuE7P+g3Cz+OZU0/Jut7PneR8z7GImE/4paJPAomaj76vF8/+YzbPl+D6T3S6ng/WsZQPoYL1r1f+U4/SEMUP38LgL6m52I/LIPHPgA6x75CzRg/xJ8zP/M5Fj7upwo/cuVTP39VmL2vKfI+FMBgP05Vbz6jEaI+ellrP1Ebfj5LMiU+lod0P/pnpL3EcqA+IzxyP3aNkb2f0RM+GKp8P84BAr/DQqU+UndMP50ZA7+SyMA9H49aP1mHMb8coKM+d04lP2MPN78Xp2c9YV4yP103DL+23CE/dEUMP+JfKL9icFG+m5c5P+6G8L6EiQK/yXo4P63r875oNA2+xkteP4SN5z6CUCc+tHVgPz8XRj8WqB4/ixwGvgAKWj90uAW/b6wqPUSD6Tyk9Uu+r8N6P/wKpD4X+tG+kplaP0YkaT3DGuy8e3p/P6qHTb79j3++AIRyPxy54L6aaIe+hNVbPzypgr6YCgA76YV3P/Jo/zsX0Ru/1htLPyA3DT6/50G/N18jP1INq75jriu/jYwpP8jGc76vWli/Zg31Pvr5I7/zXx2/aaLrPuJ/Fr/Do0O/A8+HPlzkX7/8wde+Fp51Pq1WJr8m9YG+JW03P6FfT78vG1S+6G0MP/RCI78439y6Ei9FP/F8Ir8rbyE+X6lBP40R6r72kZq6eq5jP78XWL+qHBm9s+wIP9aTWL+Y5wo+RwAEP3NwfL8fTBS+CAenPao9dr95WlO+E7g3vjtuf7+ywn495HDEPAmEdb/cCIE9GmSNviozdL/d75g+DE/tPO2Pbr/ZnpA+mxNpvuBNTr9i5xU/Z36zPdkdUr/OEpw+S133PjqiQ796F+4+LdbkPirEHb/bNaA+vwI5P8243r7Xo6U+HB5XP1qEGL8fkAk/cc8YP2Ddw75jsA8/KuE7P+g3C7+XZU0/qOp7PnSR877IImE/VZCJPAQmar4NvV8/rIzbPkGD6b3o6ng/0sRQPlcL1j1R+U4/WkMUP3QLgD6252I/8oLHPu85xz4izRg/458zP/w5Fr7Wpwo/geVTP0xVmD2pKfI+F8BgP21Vb77BEaI+c1lrP1Ebfr5EMiU+l4d0P+VnpD3dcqA+IDxyP4WNkT2k0RM+GKp8P9YBAj/GQqU+THdMP50ZAz+uyMA9H49aP2CHMT8loKM+bk4lP10PNz92p2c9Zl4yP043DD+g3CE/nUUMP/BfKD92cFG+j5c5PxaH8D6RiQK/sXo4P7vr8z5aNA2+xEteP32N575uUCc+t3VgP0QXRr8PqB4/qhwGvvwJWr96uAW/+K0qPSew0z4eSme+DM9hPyew0z4eSme+DM9hPyew0z4eSme+DM9hP3b9BD7aAe6++TRgP3b9BD7aAe6++TRgP3E9wD5qBZ27QERtP3E9wD5qBZ27QERtPzGsGj99S4e+q3JAPzGsGj99S4e+q3JAP759RT/au4u+SiUTP759RT/au4u+SiUTP9VgJT/+M0w9yv5CPzzvwT5NxSO/fjUrP45m/rtLVma/X2bfPia2Fz99UC2/fXHfPmGwZT4cM3a/bjchPhRKQT+YWyK/UmsqPo4Z2j7FtWS/RDISvr2+YT8uXui+uTwDvsPjZD/pHoW+8rW6PsPjZD/pHoW+8rW6Pq4Pdz+fAl++VgsVPsF/aT94KVw9yBXQPrJ4Yj/7k4k+TxbDPg+MTD8UeVc9r1gZP2O5fj/4fIw7GwbMPbI+eD8u+HE+4Y59PXH1bz/KnBe+g3uhvovZIz/Xm6e+G/Mxv2bcaD9RNRg+MaLGvnd1GT9K8g88uuRMv+EpTT+gkt4+bFPSvsuM9z6BCrA++BNOv/U4Gz+5MDo/Xp+kvvLDYz8tX+k+YJvSPGqEQz83QCU/KHmKO6qXUD83GvE+mh6tPqfxMj84e/Y+Y2AHP3uMKj9GlTc/iJ9RPnuMKj9GlTc/iJ9RPnIVCT+6Ij0/AX7RPnIVCT+6Ij0/AX7RPpORyT7DQ2o/faGyvdhh/jvOVWY/YWjfvmsfVz5vkXY/3fErPhexZb4WM3Y/8jYhvnzPjjxM2mg//Y/UPoga2r5ktWQ/+DUSPmQaFb7XXjc/cbQuP2QaFb7XXjc/cbQuP8CqwD7KfjY/On8XP8CqwD7KfjY/On8XP7szWT6GLiM/CqE9P7szWT6GLiM/CqE9P7szWT6GLiM/CqE9P654DD9zG+8+OIMxPyqWHD+1h4c+Jdo+P7NSij4vueQ+DlhaP7NSij4vueQ+DlhaPzvQqD4IXmg+bZlqPzvQqD4IXmg+bZlqP4l3Mb7JQtc+mf9jPwiJ8b3OyAg+JOd7P7DZI78UnKc+6/IxP5d1Gb/Z+Q+8oORMP1j1b7/ynBc+CnyhPtbGFb9Fwyo/QinsPtbGFb9Fwyo/QinsPpS+Yb9xXug+RT8DPpS+Yb9xXug+RT8DPqwPd7+3A18+/QkVvqwPd7+3A18+/QkVvkxKQb98WyI/52gqvne2F79pUC0/4nDfvs7jZL+jHoU+6bW6vs7jZL+jHoU+6bW6vtZ9Rb/iu4s+KCUTv9Z9Rb/iu4s+KCUTv85/ab+WLFy9gxXQvtJ4Yr/nk4m+wxXDvv6LTL+EfFe9wVgZv2i5fr9td4y7SgTMvb8+eL/Y93G+Zoh9vfDDY79SX+m+Vn7SvHrcaL9aNBi+/6HGPuIpTb+Hkt6+hVPSPhON976mCrC+2hNOPy85G7+OMDq/P5+kPi85G7+OMDq/P5+kPiKEQ7+NQCW/zeyJuyKEQ7+NQCW/zeyJuxi/ir5f+S2/fIQuPxi/ir5f+S2/fIQuP86Ryb7NQ2q/tpmyPekeV75zkXa/+/Ervn+MKr9JlTe/Np9Rvn+MKr9JlTe/Np9RviUVCb/DIj2/rn7RviUVCb/DIj2/rn7RvrCXUL+KGvG+ER6tvo3xMr8Xe/a+lGAHvxuyRL+/PIy+nxYUv4F4DL9lG+++YIMxvxiWHL+ch4e+Oto+v3ZSir67uOS+N1hav1yqwL7Rfja/Un8Xv1yqwL7Rfja/Un8Xv0k0Wb5jLiO/HaE9v4/djryc2mi/kY7Uvk0YFT59Xje/7LQuv6HGFT93wyq/Minsvkx2MT4RQte+1P9jv0yJ8T2gyAi+Jed7v2ohGT39PyY+FGx8vxnQqL52XWi+fJlqv549wL5T+Jw7N0Rtv+Sv076qSmc+FM9hv75gJb9qMky93v5Cvz+sGr99S4c+oHJAvz+sGr99S4c+oHJAvznvwb5ExSM/hzUrvx38BL6sAe4+ETVgv6y+ij4D+S0/7oQuv5gcGb3LPya+Gmx8PxqyRD+YPIw+rBYUPyaOlD56aJe+Tv9oP0gzFT/Te2m+hatHP6Yzaj90RA2+KE3CPrQFfj/Paf29TP4SPLQFfj/Paf29TP4SPFelaz9TXB2+I/O3vvg0ID9o6Ha+u+Q9vxc/vj7c+52+Cylgv+1iXL5MFAm/iRFRv464TL91zga/1q6TvnK3XL9JsAG//JSDu3K3XL9JsAG//JSDu1LNSb80jAm/wYaZPhgpdL5ApgO/O+ZSPyqOlL59aJe+Tf9oP0kzFb/ce2m+gqtHP6gzar9vRA2+Jk3CPrQFfr/Waf29pf4SPLQFfr/Waf29pf4SPFela79vXB2+I/O3vvc0IL9y6Ha+vOQ9vxo/vr7g+52+Cilgv+piXD5MFAm/iRFRv424TD90zga/166TvnG3XD9KsAG/NJWDu3G3XD9KsAG/NJWDu1HNST80jAm/wIaZPhcpdD4+pgO/POZSP3OVNj0AvFQ+qjpfP8ikhT4Es0A9QCVTPmuYKD3wnkw+9qthP6S7hD6E0F4/hFuEPuQbQT0QMF4+74RcP0qFhj5cK0s9WLNnPm7LWT8GCoc+dPRLPfTzXD6ZM1w/VjqFPkSRWT/cvoU+CJVbP7Z1gj6S/l0/vHWBPm8hWT88G4M+PEJaP+SBdz53S1w/1CR0PrwpWD+wXXo+OVVYPxQZaj6cB1c/4OlsPlSXWT/oEWc+TsRWPyidXj5GC1Y/8EJgPr14Vz/o6Fw+JF5WP7AIWz6o+lY/+E1aPp2+VT/QtFs+XJugPtR7+D5+TFY/gIRaPkgxoT6SJfQ+GLJVP9QPWz5ugKE+ksz8PgbiVj9A7Fk+Sg+jPjow+D6VnqM+drz0Pn+roz7auvs+c2ysPkRL+D5cWaw+EMj4PoGerD4szfc+x4atPnpi+D40b60+NpT4PpCgrT6OMPg+DESuPgRY+D6Aya0+Hgr4Po30rT5q5Pc+6vCsPgBe9z5KW60+lAX3PtUupT7qb/E+SL6nPijc7j4gC6M+aAXwPggQVT9IH1s+MjimPvqk7D5yb1Q/sCFbPn4WVT+s1Fs+Z29UP9DoWz5TPVU/+MtgPmNvVD9wRGE+CL1VP7DYbT46b1Q/RJ5uPqhOVj8sb3s+C29UP7j9ez68yVY/ipuDPt5uVD9uw4M+HgFXP0A3hj7GblQ/RFOGPlxXTj0YSnE+gR5XP0iDhz508FA95OZ6Pr5uVD8Anoc+RDRaPfzbcD5M2VY9/NVmPsQGXT3k5no+3ueDPQidbz6isoU93OZ6Pkr2gT3IWGQ+2tO3PSS+bD5+LLY9rIJePlp1uT285no+8tz6PTQQaT7eFf49iOZ6PlZk9z1wFlc+KYQhPrgnZT6VCyA+YDhPPjHmIj5U5no+sZ1CPvBiYj5TgUI+LF5JPqEVQz4o5no+td5gPtgrYT6XwmA+YCxHPgW6YT7s5Xo+1YiAPlCuYD6Zn4A+wOV6PpTkgD4IokY+UlCTPniOYD5AXZQ+bFNFPqHxkT6I5Xo+UjOnPhBVXz68Jac+UOV6PnKPpj4A9UQ+UOSyPoj4Xz7am7E+mIZFPrbpsj4w5Xo+VEW/PlD8Xz6gQr8+JMpDPhzBvj4Q5Xo+8ObSPiiEZj6UL9Q+0LpRPhMD0j7k5Ho+5O7cPYK7Nj8SQek+GONpPjws2T0rsTI/Fq/pPjDoWD7MVd89UOQ6P0sR6T7E5Ho+hKscPgTlNT9SFhk+OdgwP2LDHz5c4zo/cBlKPki+ND/CPks+InIuPzIjSj7+4To/vs5iPg49Nz/yP2A+0OA6P9A+Zj66mzM/dKBsPl4kOD8G1m8+K201P+rWaT4o4Do/mqhwPtR8OD9rLyI/INwePnLjbT6k3zo/Y4YgP7Qf0T7fjSA/COsOPk67cz7qGzY/HtEjP/TYLj4UMyA/ODoiPqeGHj+AVs8+cY0ePyh9Ej6F2CE/tCcyPh6yGz/ExCk+ENEZP9iVyz591hk/WP0ZPm1bHT8kUDk+V7EVP7AZNT5Tuxc/aH9DPsVQEz8GqcU+h1QTP7zVJT7D4Q8/OGhEPhKyDD+2Ar0+NrQMP1giNz7CgxI/aGhQPj55Cz9IaVg+6a0OP1BNYD5xZwc/CNOwPkBoBz94g08+Dm8JP+Svbj7L8AQ/WE5tPsvwBD8o76E+gMMMPxCncD4YnQk/5J9/PpcOBj/iEoQ+vA4GP+SElD7tQgw/VAN7PlDCCz/8YoQ+odYNP14VgD6gvgk/PEyMPlBgDj/wAIc+1a8PPy7ogj4dmg0/akyMPgRgED8CrYg+gdQPP5BMjD7QGhE/0GOFPhDZET8Eq4k+bBYSP94ahz5WohE/vkyMPsOqEz+ucYk+lpITP7yPhj5ysRM//kyMPgImFj+EBok+OCkWP1BNjD5K8hU/vLGFPltJGT/eSYg+2gkZPwArhD73ORk/1E2MPo5cHT/GQoc+Bj8dP5xOjD5UEB0/lh2CPro7Ij/SDIY+eNwhP6hvfz6ZGyI/uE+MPr+FJj+k6IQ+ahomP4APez7fgSY/7lCMPuRZjj6cujg/knooP6IrhD5IJo0+tJ42PzkAKD/8XHg+RoCPPuTUOj8Diyg/ilGMPvzjjz7ALjg/HGeRPvDVOj9mSY4+qo01Pww+kz7i7jY/6biQPuwWMz9ekZU+Htc6PzGFmj6qqDQ/gi2XPrSOLj9yZZ0+mtc6PypnqT5HVjQ/et+pPsTWOj8cjag+VcwtP1xCuj6QjzQ/cHG6PmI1Lj/y07k+vtQ6P9VyzD6sQTU/HK7LPuDROj/NL80+i6AvPw8H3z7GPTY/cdzfPqCoMT/pPN4+bs46P0F78D6mOTc/FNvvPs7KOj99J/E+KKgzP25U/T7f8zc/7dH9PiwgNT+A4Pw+6Mc6PzSRAT/iSDg/WmIBP4bGOj9PxgE/Mss1P6hUAj/MVzg/1sdWP0TqnD7LhgI/wOk1P8iGWT/WUp0+USgCPybGOj8OC1Q/atOcPubaVj+EMp4+eKxZP6ydnj4OC1Q/HiGePn4HVz986qA+DgtUP3jgoD4NAlo/QGihPgFmVz+yp6g+DgtUP8axqD5Yulo/siupPjrAVz8kT7Q+9XJbP0LBtD4OC1Q/wGG0PnjqVz9Y+7w+DgtUPwbSvD7wzVs/3Ha9PjkVWD9O8cU+QiFcP7SLxj4OC1Q/JmzFPlU4WD9ONc8+DgtUPyR/zj6WYlw/6sbPPqhJWD9m1Ng+ZYRcPwof2T4OC1Q/pjfYPtFRWD/mF+M+DgtUPyzr4j4jlVw/ENLiPk4lWT8sqPo+DgtUPzYO+z5mWF4/rqn5PrjSWD9YFgQ/UshdPyDQAz8OC1Q/wz4EPzQTWT/90Qo/xohZP6DEcD0OC1Q/SEILPzxwVD9gDmw9pBleP35FCj/6oF4/oNF7PRj/Vz9Qda49EKNbP2ASsT00cFQ/aO+tPdZaVz8giNs9JHBUP8D23D1PUFo/4BfbPcinVj90Bwo+BHBUP4CwCz5U4lg/QJIIPoSZVj/Q5B8+4m9UPxS/ID4axFg/0CgfPrBIVj8Qpyo+yCJYPzTJKj7Qb1Q/+MAqPvpJVj/QpTM+1yBYP8CLND6+b1Q/qEszPh66VT/IFz4+whRXP6hPPz6ib1Q/bKQ9PvpYVT8oqUM+IFtWP9A3RT6Ub1Q/kMJCPs4SVT9Ac0Y+i29UP8jkRT7AvlU/WPdGPvJJVT84x0s+eG9UP4AKSz7sO1Y/NDlMPmQ0VT8wS1E+wv9VP9jIUT5hb1Q/uKdQPvIiVT/s8lU+Tm9UP6i2VT6u11U/jBZWPua5sj7kXAQ/NSJVP/gRVz4BKq0+ZXgEP0LWVT8oHFc+1hO4PhhOAz9Wb1Q/wO5WPs3vsT4klQI/1JmtPpS+Aj8e4LU+RrQBP8mirj6+6fk+wiWvPjqg+T4ZEq4+wBD6Pixfrj5C8Pg+DJiuPirS+D50Jq4+6Av5PiTHrT4KF/k+6CytPuQB+j6cea0+eiH5PhLJrD6yyPk+IHetPtbp+D5Uh6w+aoX5PggLpT7YxP8+IlulPqSmAD/cK6M+e6MAP7NwVz/QuVg+oplXPwicWD6SkKM+m6cBP4xKVz9svFc+nGRXPxBLVz59h6U++wEDPx7fVj8g/lY+c+xWP8hVVj5w66Y+VpcBPyJVVz/E9VM+8fVXP3gGVj7QiFg/ZNBLPi2bWj94y0k+bApaP6S6Uz42N1o/KM1dPkv0Wz+4slM+cppcPwihZD4on14/uApWPk4CXj9gkk4+XUVfP9iHZT7BzGE/sLhYPqyhYT9AqGM+FqNhP/Q5Uj5kdGc/fJxfPiJXZT+cl2g+WOdpP4AoVj6eBWw/6Ch3PoOOcT9gUnU+wnd0P6Lioj6OxGc/8K52Pp70az9cwoI+0DJoP+yOgT7xxW8/SBeFPvxVcD+Exps+LbFrP7Z2hT4oWGg/uP+DPsb2kjxAZDI+NZtrP4iFhj5ODG8/ZCuIPsINbz90iJk+q8gEPOyGKz4xLA0/eiEmP5qEbj/cpZg+ksJuP7BwiT5wN2w/+C2dPqZUaT/oMp8+EH9tP7AOoD7WyAo/hvAnP1+qaz+G/5s+BsAIP5jZKT+wyWg/7OedPt2ECj+Opic/8mwIP3qRKT8L2w88YCEqPo7rDD9g0SU/Oj8JPxS6JT+wAAc/KqwnPyv2VzxIhiI+QsILP0DhIz8iiwY/XIohP8zpAz/yiSM/5oK8PHAgEj6pQgk/MKgfP0E6Az+8QBw/6wYAP3Q0Hj+b7BE9+HH7PYYkBj9+XRo/RyoAP8YcFz95ofg+qtEYPyR7Qz3Y7NQ97TQDP2RXFT80B/s+qCcSP2ib8j5wghM/pBBvPdB1sD2yiQA/xJ0QP05m9j4rbQ0/9zvzPr4DCj9a8Ig9mLuFPSbW+j40kQs/BUXvPih2Dj9+MO0+YOcKP0Qm2D7IOA8/pCvbPpj9Cj+O9NM+bLUVP1rO0D4aUQw/XEzJPpShED/hptU+sk4JP6njIz6gZzw9KK/LPkT8Bz9N/TM+UEh1PU5hwj7s3wo/Z5sYPuB3Cj1+pNI+en0FP/2jFT7QtHw9W7slPvClpT0ZJws+8MZCPWuJBj5AnZQ91wEQPhDczD1m/v49MCNdPYKRuj1o/JQ94rHBPYDHZD0GX649UKHDPWLRnz1QqI89frSqPQABXT0GF489gHS7PRRRdD1wN+I9xDlCPWjgBD6CmJc9YPHtPWzAdT3oAQw+CuHsPYCmBD7+bLU9mBscPrfoBT5QNBI+tWURPhSKIz73XSI+eNrzPaKhyz08mic+ds7cPSikNT7OIJM9uDo5Pm6gnz0IdkQ+Oi2EPTDALz5sOFY9+FNFPhQhaD00jU4+RHRBPYibPT7rwzE90NRKPtPwID0I7UM+W/kYPXTPRT5O32M/sBmEPvZP4zy48Dc+JsvZPJy+OT6qcGg/AumEPtNLCD3gljA+dgyaPCjPMD6m2cE8lFIpPpMlDD0YNRk+69g4PZSpID6aiGM/PAuDPozLYj/geIA+vEJhPyCOgz4SbGA/CMSAPg0TXz94OHQ+Ka5hP8h5dD50IXY98GlZPgIKqz2MdFE+Jl3qPeBfRj67EBk+uI05PiHNPD7wZCw+AXA3Prg7DT55v10+wEIqPlfOWz70Fww+7AqAPpz7KD5xqn8+2JQKPiZPlD6wFyc+SViUPkhvCD4XjaY+IJglPql6pT5YgQU+MsuwPrAHIz4GX64+UGUBPq4Ouz6AFB4+Go22PpgC+D356Mg+CNAPPj5I1j54EwA+Qw3NPsBSLD4N3MQ+wFDpPZsF0z6Q/tc97tzDPjALuj1aEbU+sA++PW6/0j6gc7M9xIEsPhoTDT+2gsY+MN+OPaoYUD5AeQ0/2rO0Pkg5iD20oQg+mWUMP66Z2D7gTpI9RkUtPmEDEz9GxFA+1hUUP4xdCT6inxE/7sIrPjPyGD9C2k8+eUAbP7qXBz58NBY/YCMnPuDiHz+S90w+Vn0kPzRRAj4ovRo/0sQiPkYKJz+0o/k9JGUfPzTiAj60WSk/aAbZPTqBIj+4cL89IS4sP0+k6z5Yqjs+QHyhPdb2JT9oVO8+IGYfPuz/2j7Qfjc+mjHiPkC6Hz6I8dk+WMYUPv3J4j6w2xg+M/LlPhwQFD4nI90+aAUPPvrH4D4kHQk+6/zZPoBz+D3sINc+aGrTPa4Q3j7Ynu89toPbPkCGzT2JtuE+cJDnPbU63z5gBMg9njrkPpi+Az4Wx+Q+4ILgPaVW4j6QrsI9FkTnPgC7/j3xvfE+eGDCPeLm/j4ooKM9CZvzPqBJ1z0a3O8+yIqtPdp+/T6Y4JY9ZTnuPtCFmj0edfw+kGaKPQ4M4D6Qcag94H/LPT6JDD/rGOo+sOeJPXzmhD04YQw/wK37PsAafT28Ocs9avIPPxxogz128g0/QOzHPS0NEz9wp4A9kqEPPyj1vz0xDxY/NyB3PQZFET8E8bc9DBQZP7+bbT0W7hI/GCWkPZo8Gz8fbFs9okQUP8jAgz3opB0/0rX5PtzHAD5HX0Q98GYVP9Z+Aj+Y+MU9IbD2PqCl6D06KAE/QOu7PWeI6z5wfQs+ECboPuDnDT77MgI/YG20PQcaAD/YfLA940QBP+CpqT3KHAI/YJGkPYueAD8gCZ49kO7/PlCCkj1FbwE/oCKaPXTcAD9gfY89JG4CP6D8lT36+yE/mxErP8r7Aj+QLp49ueEgP+QXKz+YBwI/kEiNPWQLIz8U/io/QTciP5z6MT+9PSQ/hs4xP+QoID8Q+DE/Qz8iPz58Mz/DFyA/4n8zP2JkJD+UUzM/WkkiP6UsNT/ghyQ/Egk1Pz0LID8tNTU/RXciP9YEPT8vJSA/zB89P7PLJD+M9zw/93oiP3HSPT+8yiQ/bMc9P2IuID/E7j0/en4iP6qfPj8ByCQ/PZc+P3s5ID9XvT4/do0iPxZlQz+IrSA/EoVDP0l+JD/PWUM/uJciP7ZqRT+CwSA/kUpFP1dbJD/YDkU/iZMiP2SmRz8c3Ww/ihwUPxmjHz8g40Y/FqhqPwcRFD/yhSU/iudGP+YRbz9OKRQ/sMJsP0akGD+QkGo/HpkYP8H0bj+8sBg/nL5sP8pbGT/vjGo/jlAZP0Lwbj8paBk/nrpsP10UGj/v624/tiAaPzuJaj8iCRo/d5ZsP8jAID9MaWo/crcgP2LDbj+SziA/Mo5sP3RNIj+Ft24/81siP7hkaj9sRSI/oIZsP8i4Iz/IYWo/IbIjP22rbj9uxyM/4V5sPwrsKj+UwGo/iesqPwT+bT/+6io/ElZsP6RhLD9W52k/7l8sPzvIbj9eUyw/NFVsP1xWLz8RNy8/EDciP4c6cD8pDS8/jFYUP8goIz/qa2g/5zQvP1hTbD/+ljM/CQwpPydNHT/0P28/loAzP+HuGT8Q3h0/YGZpP26jMz9kaSE/YCsdP+libD/q3DU/bCoeP6yJHT+Wb2o/sOQ1P1+6JD/kUh0/IFZuP/vSNT/KwiE/bBcjP4OUIz9qYCM/Zt0fP6N8Iz/HLjc9+icPP57YIT+FsiY/V48uPY5GED9m5SA/CrAmP78ZPz26BQ4/8M4iP8y+Jj8fLlM9PPwPP99sST2+UxE//xJcPfyoDj+nOWM9R4wQPzfsbD23Fg8/j8ZZPZAHEj9PIEo9IksTPx+iNz3YVxQ/jUUDP/B3vT1neTs9CokSP58bIz3uXxE/9hsgP0WGJz+X6Cs9EJwTP0LiAz8QD7c9ByoPPYPSEj957AQ/8BirPRFeHz9eFik/V/ICP+DKrj10kAM/YNOlPS8RID8eayo/Q4ceP93pMT8aXh4/L30zPwjTHD8XPzI/fqMcP4zjMz/vXhk/SPUyP0JkGT9ZzTQ/SUYbPxTmJj9gXBU/atEzP6/2Zj8R0iM/LcIVP1zVNT8+BWc/BEkiP/yzaD+4uSM/W7FoP0tDIj+4smg/ELggPwdGFj/Fzzc/6RlnP9K9ID9Q1Gg/CgAaP6HYaD8TSBk/zHUZPzzHPz/AkGc/S/cZP27tGT8el0A/XKBnP/0+GT/9PBs/dqY+P/mEGz9Qez8/qvocPwipPT+GiBk/xqg2P0KLHD/upTU/Aj4eP4A3NT9kTh4/4kQ9P6hbHj+0GT4/rmseP7vuPj/RFh0/JIM+Px01HT+zXT8/NM8bP+5NQD+KsB0/6t9BP2r8Hj9W/0I/jiwdPxMrQz+1GRw/QYJDP/L3Zz/MdhY/ikUfP3LPRD9ZkB0/FjZFPzrxaD+UCBU/btxoP0mRGD+Ubho/AF9BPwC0Zz9HiBg/u7cdP0p1JD9XwnI9TpUNP19qdj2SMww/ghT+PmC2eT1PY2I9MEQNPz9OZz1g8ws/DPj/PuDTdj1/00U97sAMPzSkIz/Agic/Fx5NPVbsCj/Q0AE/kJFzPVh4JD+XDik/O9clP+xKJD/JdCg/wqImPwgPKz/MYTI/FCYrP2Y8ND8RNy8/FwgzP5EScj8CByQ/IIUnPwvdMT/rzyc/iH0zP+nTJT/qnzE/XBUmP/svMz+hpAE/8DqFPd7TIz9qVSo/DF0APxD2hD1O4v4+4CmHPatOJj+46jQ/QQIoPzRCNT9yniY/y/88PyiaJj/z1T0/l/AnP4ZTPT993Sc/FjA+P5a6KT/hPz4/r3opP1IZPz+/kSs/UlE/P9zdcT82Lxo/OxsrPyUgNj8Ney4/HiQ3P1QNcj949yA/H+ouP0cZNT+REnI/CIIiP6Z1cD8I4SA/9GZwP0VrIj9EnHA/FCoaPwKgcD8hchk/SqRwP2y7GD9KISs/nidAPx/WcT+Qdhk/+KYqPzv2QD9+ynE/Ur8YP0A4KT908D8/Dg4pP1gvQz91nnE/2KsWP5qkJz+x+UQ/eLhwP0ozFT9L9Cc/MuFCP2RjJz+HlkE/Cd4lP9yYRD+qIyY/4MNCP+qSJj9ZrD4/58cnPxANPz9UVXA/+N4jP4qm3T4o0a89GD7aPrgVsT0NYNY+oFu2PQSrZj5Q2CQ//gNrPqgfLT8EkmQ+IHscP6SYeT5yGSU/2jV2Pt41HT9UBH4+dMUsPyDCiD4GayU/9ISJPlbzLD/b14c+srQdP8b6lz6kaSY/tiGYPpATHj8O2ak+DkQmP6Bqqj42cB4/AFy9PmrSJj9t+r8+0uYePzmb0D7QPSo/DEPUPi+0JD8gDOM+VIEtP2Bb5j4eVSk/857zPvRjMD9UJfY+WiAtP/SQ/z50bTI/dK0AP9K6Lz/KdwI/FlYzP8oxAz+s4TA/MC0DP8J+Mz8EXFw/dredPqHcAz8kFTE/zSdfP8R6nj7ejFw/qgCfPgJkXz+Ayp8+uPdcP6zRoT6A4l8/KLSiPh70XT/srak+Bh1hP6itqj5CF18/4HS1PlSyYj+glrY+AaNfP/havj7Oe2M/vIa/PuTrXz8Qd8c+ysdjP1p6yD7qBGA/BpfQPui/Yz9YT9E+tghgP5SN2T56n2M/HLvZPk4SYD8QZeI+1pRjP4Ks4T7ZEWI/GKn1PmilZT8GhPE+NCVnP0JO/T6y0GQ/pH0APxluaT/qvvk+vEdrPz68AT/Ih20/jOoAP5n/aD+mggI/tCptPwYFBT916Wk/GCQFPwpjcD9WugQ/ML1tP4CFAT1UFW4/itMHP3oNaj9QRBI9t1hqPwXyBz/4XnE/4N7sPHTPcT8lewc/cNdtPzDSFT3SgWo/sLooPQz5cD9QjAc9Nr4WPwQGaj/4o20/oMQuPYRtHT/Q/W0/5NlqP3AxQT1I2RA/StdkP/lWcD9Axx49NeAaP1dRZz/uQiA/pW1qPxUGFj+8WmM/OQsfPySbZT8RdSI/KURoPwf3Gz8OvmI/SrAlP5p4Zj9BxSM/OyJjPwyqJz+sv2k/SCcrP/WWZz/ISCs/EgJrP3ytKj9y+mM/ITUuP6HbaT98py4/FW9lP/zNLD9e7G0/zdIwP5VEbT8BqWY/8KqIPTEDLj/kN3I/+/FkPwDVej1NIzI/AphnP6lOaD8I8JI9xh1lP0CPiD1MRGc/IImVPQvfYj/AU3M97ndiP2CWjT3g/WU/QCKgPY4IYD9w3LQ9/mpkP/gfuz3qa14/KCDaPRKtYj/YGts9VxVcP3wKBj7ZaV8/mOcDPtNbWz/gXBk+4vtdP4ibEz7bP1w/TPwfPiisWj+QjyQ+C9ddP0BaGz6s61w/jK8kPvrgXT8QmSE+iPlbPxi8Jz6qE1w+51kEP+RGXT+IziY+1IRmPkwgBD+Yl1w/iBwpPnaWUT6TOgQ/nfhdP3x+JD4sHls+i3oDP67YUT5tTwM/ZGdkPiBUAz/Ga1k+SFsCP6SDYT7VSAI/YJBRPv4cAj/AA1g+UMEAP7IbUj6sjgA/QCRePrjFAD/GZ1c+ioT9PoDZWj7+nf0+OuJTPqhd/T7wS1c+jHj4PjJlUD4cr/w+6rhNPuTT/j4IH1A+Qjr6PjaaSz6Ml/s+Qv1OPkhB+D4KmUs+wkv4PlRGUT7GkfY+ItFNPlR29T6Uj1I+6rX0PnINUT54MvM+rphVPj5O9D5CL1U+ZMPxPhT+Wj54MvQ+EvtdPlpb8T7u9V4+Cr70Pj4UYz5wAvM+LMZfPjz29j7EqWY+nvD1Pl7lXz4gwvo+EPRmPpB//D4QL18+IDP9Ptq2Yz5EnP8+noBoPvzpAD9wPWw+wKoBPyzibD441v0+5EhxPoi8/j5QmWw+mkT1Pn4xcT786PQ+oMZnPmJz8T6w5Gs+iFnwPgYkYT7YLu8+uC9kPvZs7T5CYFU+sKjvPnavVT5Mf+0+UpFPPpCS8T5E000+UNbvPrjtSj7AmPQ+QNJHPsDD8z5YKEg+Jnv4PhbuRD7kw/g+Rk1IPlDt/D4qu0U+Ri3+PuidSz6WjwA/rlZKPjCLAT+Y+0g+3kMCP9QAXz+A+yQ+fEJfPwArIj6WbkM+3gH/PrAKYD9MWic+JatgP7ggJT5wCUI+3Nb4PjLdYD+IIys+79ZhPwjYKT5OuEQ+pvTyPn1IYT+Qry8++n5iP9iBLz5ISEs+lgbuPn01YT+MOzQ+FHdiP4w/NT6ex1Q+aArrPjjBYD9YCDg+2tdhP9gHOj48YmY+Bp/rPr5AXz+YCzs+PoZfP4BiPT4uIG8+xFnvPjZOXj9QHzo+XS9eP5QIPD5utnQ+CKj0PmdnXT/A+Dc+RAFdPzhROT5utnQ+xHH/PkVtXD8YCDE+ULhbP8htMT7yKm8+EkUCP3VdXD+I8iw+M6VbP7CLLD7OH1o/3K8rPlw8Wj/sYDI+WUdcP4jwOz7ggVs/6I0/PuAPXj+ojT8+RjNgPwCqQT648F0/iBREPmLwYD/4D0c+cuxmPwiHQz5lRWQ/uOs9PofoaD/wPTs+dDdpP0CfLz5NYWU/eF83PjJ3bj+AFkY+0zFwPxD+Mz5ed3Y/wKZRPlyieD8K9LQ+EAN4P2gFOD64NHo/ROPBPg61dD9YPLg+599wPxjXqD7QP3E/ht25PlATdj/ecMM+q4ByP44KxD6S6nY/9PbPPuavcj/yj88+qjZ3PzRh3z4DDHY/cBkcPlhUez/c688+mLZwP5iL8z0CC30/fBzjPgpJbj8IoiA+grBpP/heED6gemc/qCMkPspuZD+Y4Ro+VzpkP+AvJz7QPWI/EH4gPrJoZT8QMi8+9v1fP2Q0HD6CFGE/iPYUPoZCZD941QY+F5RoP7hN5T2uTmk/YK/GPchAbT/g7c09o5JpPxC6rT2/zWs/MPK4PXz3aT8QqaI9ZiNsPxDRsj1NIzI/uBpgP/WDaj9wDZ49D8kxPzqXWD+OWmw/kHysPTpgLj9DeF8/fhsuP1aHWT/jNio/XiNfP2xiKj/UU1o/HVojP65JXz9hIyQ/d1xbP4BsGz+0iV8/0pQcP6IYXD+pHRU/bQVgP8mZDz/It2A/O9lxP0DyJT3CTRY/p1tcP+a3ED8QTVw/6dpyP1BCOz3QsRg/WHRYPwyiEz+IY1c/6mpzP8DPWz0YTR4/+Y1YP9Y8HT/GF1Q/cpsZPySNUT97QXM/8HyHPf9LID9Rr1U/jOQkP/1ZUD8WgSM/hHtMPwYPcj8oP6o9MqMlP9T2Uz+kkys/ylRQPwIBKj9cFlM/DYYsP9hITD+1CnA/yDvCPaqbLT8Kz1M/8DAwP3jhUD9kGW4/MGS9PRScKj92wVU/eiUlP3pVVz8HMm4/GKrGPQqzcD9wcc89lBRzP1CVrz3mI3Q/OIe5PTyIeT+Qq/A+yH10P5CPhT0wo3U/8OCIPSPQdz+QVvw+c7N0P5A7TT1M2HU/YI5NPRxpdj81BgI/ZP1zP4BKJj22FnU/wGohPVowdT/sfwQ/6LRyP7AADz1PkHM/AAEEPZ7Icz8EVAY/5E1yPxKHAz+2z3M/EdkBP3yXbz/eJf8+QndxPwbl+z7S9Ws/uI72PiisaD8Sce4+HmtuP7YA8z7Ui2s/fsLqPrHscD8sJu8+0mJuP/wT5j73MHM/Sir4PvqWcz8ylOw+B/BxP7rp3j4/YXQ/Ft30PrjMdj8iv+0+zBl2Pwak+D5yE3U/zlP/Pn6Zaj/cVNQ+kb1pP2rO2z76TWc/AGfSPtg0az/eKcs+7CFrPyKQwT5yh2c/hpfJPoxJZz/8ccA+qlBmPzBdtz7COmQ/4FerPkwLaj9kN7g+iGBnP/DZqz5KvG0//GWrPnJBZT9IgqM+0pZqP6hPoj5ooGI/jDqjPoxvZD90XaA+ufEFP0pDLT/8CmQ/DgCfPlMAYj8UNqA+1NMEP5gLLz+QsGE/DuCePgs0BD9s0C4/8GMFPxIALT/72QE/1HUtP+lFAz+2cSs/CzL5PpRlKj8SGf0+WvMnP5pW6j669CU/P/LvPmIHIz87jdk+QpUgPwhS4T6ZNR0/BiTLPrx5Gj9+uMI+mpQTP21nPj747IU9LPm8Ppg7DD+pRbo+KDkVP20RSj7A+4w9ve22PiT+DD8mqao+4sUVP4vKYj5oXo89u7yqPk97DT8Zwpg+LpQVP5ZWgj7gdYw9yyGaPiRSDT/84oc+GkoVP5mDiD5gFg0/fiSUPlBhiD3UUHU+9QEVPwA2dD6uHA0/MpmiPgAwhj3CLGQ+7KYUP4StYj5eRw0//2arPoBMhj3qkaw+SDfDPWvkoz5w58c92CaUPsCOzD2g5YA+ABnRPYVdXj4AS9M95ZhCPrCXzT1bCjM+IHq+Pab0Zj/gRto+kLtmP8oZ4T6b3Wg/6IbgPnr4ZT8wok09rnVkPysqCD/Uk2Q/0K48PdXIJj/j/XE/FCRnPwA+Xj1Zkic/fnZtPww2ZD9hcQQ/v6mJPuB9MT/c24k+jhk0P+TJgT5wLTE/IguEPiSZMz9O4XM+ssQxPyjtej5wATQ/gip+PkPhND+i+iQ/2GlCPqYuIz9E5kU+pA2FPlacND8IDyY/0ENXPhFTJD9cp1o+29OJPsI7NT/0Hyc/iMVoPv5SJT/42Ws+qT4hP4RYcT5FSCA/pLRgPmh6HD+srXc+bmgbP2BXaD6fYhg/tKx9PkAeFz98dnA+dTYVP9JbgT4JyxM/ADV4Pt3DEj82WIM+1lMRPyBqfz62Sw8/DHB3PrKTET8UDmw+dR8VPxhNYD4gzBk/pJdVPhr7Hj+Qfkw+kgSUPcBzST0tOvY+ZLMHPwbstD3g1Rc9ykm+PcBRpTy6QMY9QDEdPR5Zoz3glQo9F/LyPmx4Az8OCbI9QD+WPILF7z7Ycf4+/8buPoJkAz+OpOw+ZKL/PmX+8D4O9QY/25/qPsqvAz+ewes+UzoHP9mL6T44JQA/3wzhPiCMAz8M5OM+BNf/Pq4u3j4DLQc/KVDdPiCqAj9lLOE+qCr+PoZT2T6qNwY/bZkJPmAuvjzthtg+rrABPxII9j0AyEM8LUXePjyi+z7G4f894L4DPRbL6j1AB4k8PifvPSCJFj3aKOA9gBGjPP6O3T2A/Io8bifKPaCQrDx2x8o9AASUPMpsyz3Ad4I8AilfPxrSXz/O0L89wEmNPCoZtD2AnX48VB/vPvQA/T7q/MA9gEJ3PFRzZD/5AFs/Tpu1PQByXDy/pe4+NO37PlXlaz9WXFo/ALplPxRLXj8zSms/cqldP16NYT+Ld2I/8ktnP3HiYT/i22Q/1MhjP30+aD9/Y2Q/Tq1qP+BcYT9GMWo/9CBkPxzbbT+IymI/WDBwPwDmXz8k/Gs/oPZkP06abz/uWmU/Xt9sP15zZj84I3M/VrljP+jibj94ymk/1LVyPwwzaz9cVGw/8rhoP5exbD/g12s/EA5vP1Ytbz8UMWs/vNZpP39kaT98v2w/QO9nP4+HcD/WGGk/ekxqP4a6Zj/yxms/7RlnPzVVaj8UR2U/PVdqPxDuZD8y12w/WFtkPzx3aj/6smI/oHprPxu+YT+CIm4/cLFjP3jKaT/w+WE/0DZpP2KgXz/ndGk/GoNjP6b5aD++oGI/CBpmP+iOYz/8zGc/FDlkP6j9ZT8EMmQ/o3xnP/RnZT8wmWY/bNtkP9isZz/dvWY/tqllPxUkZj/zPmg/HA5nP/cHaD/MWGU/CIFoP+SnZj8nNWk/CLxnPxgfaT/SmmU/4AtpP0yzZT8ksmk/Y+9kP4jpaD9w12Q/xK9oPwamZD9K3mg/FfFkPzIqaT9FsGQ/OvloP9C6ZD8UX2k/zJRkP9H9aD9camQ/fltpP6R6ZD+WCGk/WDlkP+wfaT/ib2Q/sOtoP1ApZD9a3Gg/OWhkP2bQaD+2I2Q/cnpoP5haZD/Cp2g/xl1kP9xUaD/qc2Q/b6VoP8OiZD8PX2g/6ItkPxSdaD+om2Q/6sRoP56BZD8WzWg/WIlkP5jeaD9ui2g/C4hoP052aT8e+Gg/nw9pP1QKaD/jEmg/otBnPwzWaD+ar2c/xB5oP7yAZj+b2Gg/yh9nP/DbaD9a8WU/fC1pPzrRZj9x4mk/YstlP4q5aT90uGY/QN5qP0g3Zj+YN2o/HPpmPxBNaz98BWc/GHFqP6VcZz9Y+Wo/FCFoP4xIaj8+22c/aGdqP9qtaD+kAGo/OiFoP6qGaT92PWg/gMxpP/DYZz/U8Gk/pr1nP+a6aT+UtGc/6o9pP9LgZz8plWk/OLNnPy1VaT/Fy2c/sG9pP4mrZz9+N2k/LqdnP7xZaT8EpGc/+jdpP0p0Zz8oXGk//I1nP3xgaT+cVGc/AnVpP9WMZz+qoWk/5UpnP52ZaT9Qjmc/UOBpP95iZz8YvWk/xZVnP6D/aT/ojWc/DNVpP2SdZz+L0Wk/4LJnPz7s2z1ARnY8xsNdP6eyaT/6w+Q9wN1IPHqmYD8MHHA/OlLuPYC4BDzgJOA+MH75PoCCaD+AMnQ/0kLnPYCrZTzmlvE9QHUePExl3z6oaPo+WwviPlS6/D4bguQ+7E7+Pnep4j44rPs+lmxxPzgfcj8y7uQ+0C39PnMzdj9iTmw/ZyPpPiis/T5zM3Y/MzJiP2BQ6T50yv4+eMjrPgAP/T6zIHI/VFhdPwUm7D4IKP4+5oDZPjRcBz+eTtg+u98HP9mL3T6LKAg/avTcPvDvCD8j8es+bCQIP+Q67D6n5wg/spLwPrTTBz/gfPE+AG8IP9Kt8T6fSwk/vKDsPpDWCT8BMtw+iucJPyvM1z6p2Ag/KEJYPxhiWD6DyK4+0qf4Ppz2rj7EfPg+RpmvPjRA+T588a8+Dsf4PtTsuD7uLQA/Use6Pgpt/D6297s+Wi8BP0K8Uz+gElc+gii+PsJA/T4pCFM/eBtXPpK7Uz9Y81U+1wZTP1QWVj5ZqlM/MEtRPhDfUj+oyFE++pRTPwDHSz4Co1I/yDhMPkbMUz8Qc0Y+UCBTP+T2Rj4ohlM/0KhDPv6DUj8oN0U+JCVTP2AXPj6BylE/6E4/PoKVUj9YpTM+p75QP8CKND7wllI/oKYqPt68UD9QyCo+RkZSP2DkHz64G1A/7CcfPkQ4Uj8YBwo+wP1PP5SRCD5yhVE/eIfbPQCQTj+gFts9UuFQP+h0rj1gPU0/YBGxPeoCTz/90Qo/tldPP0DEcD16/Ek/gEUKP5Q/Sj/w0Hs9akNPP1gWBD/OTUo/INADP9HwTj8sqPo+ub1JP66p+T5OxE8/5hfjPvqASz8Q0uI+dMxPP2bU2D65kUs/Ch/ZPsbdTz9ONc8+hrNLP+rGzz7iAFA/TvHFPtr0Sz+0i8Y+pCtQP1j7vD4qSEw/3Ha9PuBVUD8kT7Q+JqNMP0LBtD4csFA/sqeoPsVbTT+yK6k+nw5RP3zqoD4QFE4/QGihPjc7UT+EMp4+pGlOP6ydnj4BVwI/XjQ9P0dOUT9E6pw+hIsCP1aiPz9Wj04/1lKdPpmTAT8GRD0/F8sBP6DBPz+nWf0+xJs9P2rc/T5gb0A/b4HwPrxbPj/gM/E+Hu1BP1AO3z7bXj8/EOvfPvDzQz/Qesw+/GFAPwZAzT5SA0Y/Dkq6PjgaQT96gbo+M3VHPzhtqT5KWEE/p5qoPhvkRz8Uh5o+LglBP2A1lz5WJ0c/dDmTPm7CPj/rtZA+Up9CPxncjz7ogD0/4T2OPjgmQD8XUY4+JPQ8P0p5KD80d5Q+/BaNPpASPz/A/Sc/kHOcPqiEJj8SuZM+SRgmP0wZmz7uOiI/kJKSPuXaIT8g55g+BFwdP25akT5EDx0/Un+WPv9IGT/EUZA+IgkZP3JwlD7CJRY/HJSPPszxFT/C6JI+k6oTP0Aojz44khM/JgqSPuvYET+C7o4+HhYSP6R+kT7VXxA/MuyPPnEaET92NZM+GWAOP96XkT5irw8/rrCVPhnCCz9SNZQ+JtYNPwCDmD7gnAk/yMeYPnBCDD9IFps+lm4JPwg/oT7Awgw/EESgPhZ4Cz9iYaw+kqwOP3xwqD5/3w8/fmG2Pn+BEj/eYrA+ra0VPwgJvj7dtxc/+Ne2Pu2sGz9WtMM+mlYdP7Lwuz6ZLCA/anrHPoHSIT8Ehr8+ZLlwPnZCPT9NKCI/3CnJPszGcz5ypz8/hMojP+wtwT4krWw+2ps9P77ebz6eVUA/DNZiPrGEPj/sRWY+DydCPyIfSj7gBUE/okpLPq5SRz+Erxw+/OE/PxweGT5u70Q/7PLcPTQNPz8eQek+MvOFPtgz2T3OF0M/Ka/pPqBwjj4E59I+yqKHProv1D50B5I+dUW/PuTmij7iQr8+9P+YPnLksj7o6Io+IJyxPuAhmD5+M6c+xDqLPr6Ppj7Kapg+eFCTPkaeij6QXZQ+zDuYPvyIgD6Yjoo+6OSAPriUlz4J32A+BlCKPj/DYD7AT5c+CZ5CPqq0iT75gUI+CjeWPmmEIT6AUog+HQwgPipKkz5i3fo9cl6GPipl9z1aW48+JtS3PaiHhD4SLbY9bKWLPgrogz1WGIM+tvaBPXy6iD6sNFo97HiCPuTZVj3we4c+nFdOPdxBgj78vlE/goKHPuQrSz1CDYc+KxJPP4oIhz553FE/ijaGPmpMTz94vYU+BhRSP/Cagz5mvE8/BBqDPnSPUj9Ibns+b7RQPwxcej50IVM/GNhtPufWUT/Y6Gw+f6FTP3TLYD6Z01I/WEJgPnrIUz8Y01s+bCBTPxC0Wz5Sgqo+AOPqPvbOUz+YHVs+FAavPmCi6j7lLFM/HA9bPt4Qqz4gZO0+urSuPowc7T7o060+5sH2PjhZrj60nPY+IieuPrbJ9z6wWa4+QLH3PoSRrj4Eq/c+Z8iuPtKm9z7a4K4+Epv2Pjtcrz7otfY+OiGyPuLS7T70N7U+JqnvPn5Isz6co+s+oJJSPziEWj4j/rY+wATuPoz8UT8I61k+6IBSP5wIWz7w41E/nExaPo4aUj+cnF4+/mVRP3DnXD5eiVA/nBdqPlZHTz/kD2c+DZxOP0h/dz72kkw/fCF0PuhITT/oc4I+iN9KP2hzgT4wqkw/SjiFPnQNSj/gWIQ+nBxBPerOiz7LWEw/HIOGPmuWNj36iJA+QKNJPwCihT4s9Us9+myMPhS0QD1eVJE+tCJ2PfQxjj6MImg9WKCTPs4Kqz2GLJI+hqGfPdirmD5mXuo9sLaXPv7P3D2YFKA+gxEZPoofnj7BZhE+biGpPiHOPD60s6Q+Y3E3PlxItD59wF0+lMSlPsHPWz4A2rQ+awuAPvRnpj7bq38+Upu1PqpPlD6kWac++ViUPs6ttj6SjaY+NhmoPlR7pT6UJLg+rMuwPkxhqT60X64+djK6PiUPuz7I2qs+xo22PmDkvD506cg+0PyyPrtI1j7q2ro+lw3NPn67pD613MQ+ppDAPjsG0z4C5cQ+u93DPvxhzD43ErU+CmHLPi3A0j6qB84+4LIsPga8aD+og8Y+4izXPq5XUD4MU2g/6rS0PpzW2D68xgg+0WtpP3ua2D6yUNY+DnAtPpLJYj94+VA+3rNhPyx+CT4CMGQ/ROYrPgzZXD8ABFA+NodaP5qzBz7WmV8/Vj0nPubmVT8uEk0+s0hRP2BnAj4oEFs/LNYiPlS+Tj/8xPk9PmdWP7DsAj4ecEw/HBvZPR5LUz9Mdr89OJxJP3Sk6z6CD50+0H6hPZDVTz+ZVO8+fjGrPiYA2z5aJZ8+4DHiPogHqz7m8dk+hoGwPknK4j7Kdq4+gPLlPozcsD6HI90+8GGzPlnI4D4IVrY+aP3ZPr7HvD6IIdc+/AnGPigR3j7W/L4+TITbPvCCxz7+tuE+ZgDBPkY73z5g48g+/TrkPkAFuT6Ix+Q+usPCPjRX4j7EOMo+dUTnPsQ1uz5TvvE+IEzKPirn/j70+9E+XJvzPuYRxT6R3O8+hIHPPjB//T7SK9U+8DnuPrRC1D6Cdfw+SErYPrIM4D74x9A++LLLPeBJaT+PGeo+PmrYPowDhT2jc2k/MK77PpBA2z5kZ8s9nt9lP0SCgz3u4Wc/6BPIPfrDYj8kvoA9XjJmP3AVwD08wV8/V0V3PYOOZD8kCrg9r7tcP7+4bT0P5WI/KDWkPfSSWj9Xf1s9UI5hPwzEgz2WKlg//LX5PnCAuj7/Z0Q952tgP+J+Aj/2Zck+Y7D2Puy6wD5MKAE/ROnLPq2I6z7MJbU+XSboPprwsz4MMwI/qsjNPiIaAD/SxM4+/kQBP4R50D7gHAI/kr/RPqmeAD+oYdM+4u7/Pk5D1j5lbwE/QFvUPpvcAD+ABNc+QW4CP7Rk1T7RVz8/JMA5P+D7Aj84WNM+jT0+P925OT+6BwI/sJHXPjhnQD+u0zk/FpM/PyTXMj+SmUE/OwMzP7iEPT+x2TI/Fps/P4RVMT+Wcz0/31ExPzTAQT8ufjE/LqU/Px2lLz+y40E/r8gvPw5nPT+UnC8/GtM/P+zMJz8CgT0/9bEnP4YnQj812ic/ytY/P1D/Jj+PJkI/VQonPzaKPT8B4yY/Tto/PxcyJj/VI0I/hTomP1CVPT9qFCY/SOk/P69sIT9aCT4/rkwhPx7aQT/xdyE/jPM/PwtnHz9XHT4/MYcfPyy3QT/nwh8/XO8/P2ArHT8YxVk/LNk1P+v+PD+h7h0/EJBXP7DkNT/B4UI/OOodP+D5Wz9qzDU/rKpZP3BRMT+MeFc/mFwxP73cWz/9RDE/l6ZZP+2ZMD/odFc/KKUwPzzYWz+NjTA/mqJZP1nhLz/s01s//9QvPzhxVz+T7C8/dn5ZP+40KT9KUVc/RT4pP16rWz8mJyk/LXZZP0KoJz+An1s/wpknP7RMVz9MsCc/nW5ZP+48Jj/ESVc/lUMmP2qTWz9HLiY/4EZZP60JHz+OqFc/LwofPwLmWj+6Ch8/ED5ZPxKUHT9Uz1Y/yZUdPziwWz9Yoh0/Lj1ZP1qfGj/kkkw/sppCP4QiXT+Q6Bo/YLIxP/aoQT/oU1U/z8AaP1Q7WT+4XhY/3mdGP5uERz/uJ1w/IXUWP7VKNz+080Y/W05WP0hSFj82xT4/ZKZHP+NKWT/OGBQ/QYY7PxlIRz+SV1c/BxEUPzQWQj/gfkc/Hj5bP8AiFD+fHj8/VLpBP1bwQD9YcUE/Ojk9PxxVQT83Rjc9/KxmP3M0Pz8/Hz4/l6AuPSqOZT84QT4/uCE+Pyc3Pz2Cz2c/wipAP/YSPj/HS1M9JNhlP3+DST1UgGQ/tzdcPbQrZz/fWmM9u0dlP88UbT2ivWY/D+BZPRfMYz8/MUo9UIhiPx+qNz19e2E/lEUDPw6Gyz4PiDs90UpjPzcmIz2UdGQ/ync9P3tLPT8f7ys9pTdiP0fiAz9AIM0+ByoPPcYBYz957AQ/ph3QPuW5PD9muzs/ZfICP0Axzz6AkAM/Bm/RPgNtPT+kZjo/F+M7P+PnMj/suTs/kVQxP9wuOj+qkjI/U/85PzjuMD/EujY/fNwxPxXANj9nBDA/HKI4P67rPT82uDI/WAAxP63eUz+lIyY/Ah4zP2b8Lj867VM/sqwnP/ubVT/+OyY/V5lVP2uyJz+zmlU/pT0pP9yhMz/+AS0/5QFUP+Y3KT9MvFU/rPUvP5zAVT+krTA/n9E2P4gKJT+7eFQ/a/4vP0BJNz+kOiQ/WYhUP7m2MD/RmDg/SismP87gOD90ViU/gFY6P7goJz9Z5DY/+SguPxfnOT/UKy8/1pk7P0KaLz84qjs/3ownP3y3Oz8PuCY/gsc7PwbjJT+kcjo/oE4mP/CQOj8PdCU/Bis5P9aDJD9gDDs/2PEiPz5YPD9u0iE/Yog6P6+mIT+LdTk/gU8hP+zfVD/rfjM/X6E8P04CID8r7Do/rJsfPzTZVT8i7TQ/asRVP2xkMT9qyjc/xHIjP/ybVD9wbTE/kBM7P3hcQD9/8XI9cj9oP++edj2SoWk/6hT+Pgyt2z4/jmI93JBoP79+Zz0I4mk/bvj/PlIJ3D7n9kU9oBRpPwoAQT8CTz0/V0lNPbXpaj/90AE/kHHcPirUQT8owzs/DjNDP9iGQD+e0EU//i4+P9xqSD/1bzI/6YFIP1qVMD/kkkw/qskxP476Xj+07iU/8+BEP7T0Mj++K0U/OlQxP7wvQz/WMTM/LXFDP8ehMT/HpAE/DpXZPrIvQT9XfDo/OV0AP1am2T6q4v4+aBnZPn6qQz8L5y8/Fl5FP5CPLz9I+kM/+NEnP/z1Qz/O+yY/aExFPz5+Jz9QOUU/qqEmP2sWRz/hkSY/gtZGP3K4JT+T7Ug/coAlP9rFXj+Cxi8/EndIP5uxLj/e1ks/oq0tP1D1Xj8+/ig/8EVMP3y4Lz+O+l4/sHMnP6JdXT+uFCk/7U5dP3KKJz9ChF0/ossvP/6HXT+XgzA/RoxdP0s6MT8cfUg/JaokPxy+Xj8ofzA/zQJIP4nbIz9+sl4/ZDYxPxSURj9O4SQ/4GlGP2iiIT9uhl4/3kkzP2wART8Q2B8/cqBdP27CND8gUEU/kfAhPza/RD85OyM/3DlDP+U4ID99f0M/4g0iP77uQz9pJSY/uCNFP7HEJT9SPV0/vxYmPy+n3T4k8M4+xD7aPgafzj7CYNY+nk3NPoTIZj4861A/zhFrPiKjSD+0vmQ+IEpZP/q3eT7Hp1A/TmV2PhSNWD/UEX4+UPtIP3LTiD6aUlA/NYyJPmjJSD9I8oc+ngpYPyoOmD5mTk8/IECYPnOmVz/Y8Kk+zm1PP1CNqj5cQ1c/03a9PnrYTj9mIcA+tsRWP+6z0D5uZUs/4WTUPnvuUD+/IeM+ThpIPzJ45j6kRUw/MLHzPnswRT+APfY+TXNIP4ug/z58IUM/0LcAP5HTRT/5fgI/RDZCP147Az9AqkQ/SDQDP+YMQj8auks/dredPhjmAz8idkQ/T+5IP8R6nj4+iUs/qgCfPhyySD+Ayp8+ZB5LP6zRoT6bM0g/KLSiPv8hSj/srak+GPlGP6itqj7c/kg/4HS1PsxjRT+glrY+HnNIP/havj5QmkQ/vIa/PjoqSD8Qd8c+VE5EP1p6yD40EUg/CpfQPjRWRD9YT9E+aA1IP5SN2T6jdkQ/HLvZPtEDSD8UZeI+RoFEP4is4T5GBEY/GKn1PrZwQj8IhPE+6vBAP0ZO/T5qRUM/pn0APweoPj/svvk+YM48P0C8AT9Vjjo/juoAP4UWPz+oggI/aOs6PwgFBT+rLD4/GSQFPxWzNz9YugQ/yQA6P4zTBz/gIzs/4IUBPWi9PT8G8gc/dtM+P/BEEj2pRjY/KHsHPzeCNz/g3uw8nAk7P/DRFT0eXz4/ULooPR7oNz/giwc9Dz07P4DDLj0/djw/uHpUPwsHPj9wMEE9jiVDP++CUD9WkTY/calZPyaKOD9Axh49P5hAP2QvVz/++kU/GRNUPyG+Oz/9JVs/SMNEP5rlWD8gLUg/lDxWPw6vQT+swls/V2hLPyAIWD9IfUk/gl5bPxpiTT8SwVQ/Vt9QP8bpVj/UAFE/qX5TP41lUD9Khlo/Me1TPyClVD+GX1Q/qBFZPwqGUj9glFA/vDdCP5CpiD3ailY/JDxRP7zuQz9A03o9PrtTP9ZITD8dkkA/UO6SPVjbVz++6FY/7sJDPxCOiD1wnEE/aIeVPZ4BRj+gUnM9tGhGP2iVjT3O4kI/UCCgPfLXSD/Q2rQ9mnVEP4Aduz12dEo/IB7aPWQzRj/4F9s9zcpMP3AJBj5edkk/GOYDPhqETT+oWxk+KeRKP+CZEz4EoEw/xPofPqIzTj9AjiQ+7AhLP4hYGz4q9Es/0K0kPur+Sj8olyE+QOZMP2y6Jz6hnYw+2NTpPuyYSz+gzCY+NtaRPvxH6j4uSEw/wBopPgVfhz56E+o+P+dKP4h8JD7oIow+iJPrPiSAhz7C6es+hMeQPmTg6z6vSYs+ENLtPqFVjz7w9u0+AFyHPqpO7j6wlYo+/gXxPqqhhz5Ia/E+8qWNPi798D62R4o+IgT1PpAAjD6m6vQ+7oSIPgAr9T7SOYo+GBD6Pm/Ghj6O2fU+RnCFPry08z5Wo4Y+aE74PvBghD4c8fY+ehKGPmJH+j5fYIQ+6Dz6PgQ3hz7i9vs+anyFPlQS/T6n24c+wNL9PpAahz4yVv8+LmCJPlw6/j54K4k+omIAP+QSjD4yVv4+YpGNPqKWAD/QDo4+lMr9PvodkD4qhv8+7naOPmaS+z686JE+AJj8PoyGjj6Mxvc+3A2SPgoJ9j5iK44+glX1Pj5vkD5a7PI+INSSPpy08D6QspQ+HjPvPuoElT5osvQ+SDiXPhTM8z5+4JQ+BET9Ppcslz6mn/0+KXeSPp6KAD84hpQ+jhcBP+Aljz7mrAE/uKuQPtaNAj/4Q4k++G8BP5ZriT6shAI/hFyGPgl7AD+DfYU+K1kBP7kKhD7i7/0++nyCPuLE/j4FqII+gg36PtwKgT7MxPk+eLqCPlSb9T5jcYE+Xlv0PsdihD5+afE+KL+DPkZy7z6kEYM+7ADuPg7fST9M+SQ+b51JP9woIj4aS4A+wobzPjDVSD/YVyc+xjRIP0AeJT4pMX8+zLH5PqcCSD/QICs++QhHPzDVKT4C8IA+ApT/PlSXRz+orC8+42BGP6R+Lz4EOIQ+BkECP0SqRz94ODQ+tGhGPzw8NT6m94g+Hr8DP3geSD9MBTg+2AdHP4AEOj75xJE+znQDP96eST/QCDs+WVlJP5BfPT7wI5Y+a5cBP2KRSj/AHDo+M7BKPwAGPD4Q75g+juD9PjF4Sz9s9jc+Td5LP/BOOT4Q75g+2hbzPmRyTD84BjE+VidNPwBsMT5KKZY+dv7tPkKCTD+48Cw+gjpNPwCKLD7gv04/iK4rPjujTj9wXzI+LJhMP2DuOz6UXU0/1Is/PqLPSj8Eiz8+QqxIP8SmQT677ko/yBFEPhrvRz9sDEc+/9xKPziPTj4kRE4/gMlJPkE8Rz/ANVI+CEBKP0AHVj7m6kw/8K9TPoiZST+wg2U+ehJHPzS0WD5YPUc/eKNjPvNqQT8gll8+7IdDP3CRaD5I+D4/CCJWPo3ZPD/MH3c+Vp4zP6Lioj4TUTc/1Ed1PlwaQT80p3Y+R+o8P1a9gj7eq0A/lIqBPh7ANz+Exps+Ixk5P3oRhT6VLT0/iHGFPm6GQD82+4M+pvmSPCS1oT6CQz0/UICGPlcIOT90iJk+mdI5P4AliD6LzgQ88COlPiFBDT8RY08/gJE5P9ylmD5CHDo/vGqJPqreOz/4LZ0+eME+P+Qynz4Jlzo/sA6gPsDbCj/clU0/uWs8P4b/mz7j0Ag/TK5LP21MPz/o550+/5cKPxzgTT8Dfgg/wPZLP4vhDzy+1qU+vAANP3KzTz/dUwk/DM5PPz0TBz+k3U0/6/9XPECkqT782As/76RRPxSjBj/YAFQ/hv8DP0QDUj9Wirw8ItexPrlcCT/w4FU/SlYDP09OWT+yIAA/DV1XP+PxET3MCrw+v0IGP1IvWz8/SgA/43VePwfd+D4CxFw//IFDPQKsxT4HVwM/8jhgP7tO+z5EbmM/8N7yPtcWYj+MGG89rsnOPmavAD/W9WQ/CrX2PrYraD/oj/M+NJdrPwr1iD0weNk+ASn7PiUGaj+AkO8+kiVnP56B7T7+tWo/lGvYPphsZj/AeNs+bqdqPzsu1D5E8F8/2BbRPgZYaT/ei8k+pAllP5z11T4RWWw/ZeYjPnBZ4z7G/cs+FrBtP9H/Mz44Pdw+mqjCPqjPaj9Rnhg+gpfpPgz50j54LHA/eaYVPuRP2z6VvSU+3HzRPsUpCz62jeI+u4sGPka/1T7DAxA+gq/HPoYD/z0+Qt8+/pW6PdCn1T7utsE97k3ePs5irj2g/sk+7tWfPfD81j6Wuao93EbfPu4ajz3qCcw+jFd0PUZZwj7kPkI99na4PqKblz2oar8+VMV1PRzmtD7q4+w9bpO4PipvtT0K2aw+A+oFPmbMsT6hXyI+wO+9Ppqjyz2gGac+JiKTPYpJnj6yLoQ95gajPjw6Vj0CPZg+VHZBPUgZnD4bxTE9nnyVPjvyID2O8Jg+o5koPY6XlD4mMkc/ULiEPqP6GD1Y/5c++/5EP/wVhD5om0c//IqDPi5ySD80wYA+tFVFP6wHgz7YEkY/lnWAPnYwRz9AdHQ+estJP/gzdD4Gztk83AeePuRtQD9s5IQ+ZlPjPNTunj7bTQg9vJuiPtYPmjy2f6I+9t3BPPw9pj47KQw9tkyuPlvcOD1ckqo+aQwzPr5Hyz7VmkI+PoDHPuVpPj7satk+qzy9Pud1aT/XE0o+JqfXPiIutz6ptWg/kvHCPo4YYj/webo+7HZgPyZUyz54Lls/aLbZPgIMVT8GguE+GGlYP4556j7WpE8/6RrwPj6QUj9UT/k+EC1LPyo7/T7onU0/jOYBP8IXSD9/VAM/4RpKP64/BD/VukY/bHEFP1qKSD9a3wQ/DX9GP4xlRj8K4J4+Ev8FP5dGSD8iC0Q/DgCfPsgVRj8UNqA+tnVFP4w6oz6QpkM/cF2gPqfUQj9IgqM+lbVAP/DZqz5Lfz0/qE+iPtJZOj/8Zas+NDY3PxTXqD5O1jY/ht25PnCVNT+OCsQ+D2EzP1o8uD7OCj4/ZDe4PjL0PD8ikME+c8VBPyxdtz5Y20M/4FerPo7MQD/8ccA+qo5AP4aXyT4kyEA/BmfSPkbhPD/eKcs+nnw9P+BU1D40ZjU/9o/PPhQmNj+86d4+hisxP/T2zz5y3zA/OGHfPsHBLD/m688+9NQyP/gTHD7PAjI/4nDDPmLhLT9I48E+x90wP9z9Nz6Wrjg/DPgzPsBzLz8O9LQ+FWkyP7idUT7naDo/2A9GPnD3Pz/8ODs+TfNBPyCCQz6pqD8/yJovPpB+Qz9sWzc+RndDP1wuLz5XmkQ/qOc9PqqlRD+wLCc+NaJGP2x7ID6KZUE/ACAkPlxxRD/M3ho+dZc6P2idID7sLz8/sFsQPs2dRD9I0wY+FwsrP34c4z5HKjg/kIXzPYFMQD+ASeU9CKA7P7DpzT3ejS4/lqvwPi69ND/ggrk95C04P+Bszz041jg/mDfCPRQ+Uj/kN3I/fcw1P3iRrz3Xrjo/AKbGPXrHOj9gYL09+OhVP0WfbT9vvTw/0M2yPQ8TPT/g7rg9TYY8P6B5rD0egVc/hullP1DpPj+QpqI9IE4/P3C3rT3dXD4/AAuePV3bVz8HZl4/RBhUP3gIXz+K01M/ZPlkP/LuTz9WXV8/eBpQP+osZD8sEkk/DjdfP2rbST9DJGM/iiRBPwn3Xj/eTEI/GWhiP7TVOj9Oe14/1FE1P/bIXT/wBzc/wPAlPckFPD8TJWI/RwY2P+A/Oz31bzY/rDNiP9hpPj9jDGY/QnY1PyDMWz0eWjk/Nh1nPyIFRD+/8mU/5PRCP/Foaj+nnzU/aHqHPX5TPz+W82w/CgRGP27RaD+anEo/xCZuPwXSNj+gO6o9JTlJPzgFcj89W0s/5olqP7BLUT/zK24/DblPP2Rqaz+zU1M/s7FqPyJUUD9Fv2g/hN1KP0ArZz9kYzQ/8IyFPflFMD+YVvw+Bz4zPxDeiD3ILTQ/QDhNPQGtMT86BgI/+AgzP6CKTT3Z4zQ/gEgmPcXlMj/xfwQ/kMozP+BoIT1OLDY/wP8OPX5NND8JVAY/81A1PyAABD06yDU/FYcDP2dGND8U2QE/n344P+Il/z7YnjY/DOX7PkwgPD+6jvY+9Gk/PxZx7j4Bqzk/ugDzPkeKPD9+wuo+bCk3PzIm7z5Hszk//hPmPiPlND9MKvg+In80PzaU7D7etDM/Ht30PmVJMT8sv+0+UvwxPxCk+D6sAjM/1FP/PotYPj9uzts+fzg/P+iG4D54IUE/4EbaPo1aQT/KGeE+BpI/P/Crxj2ay0c/YPQUPgziSD84Mhw+PNiqPvzvXz/nzGI+Tg7XPjr4qj42PWg/wuuYPgwoYD/GV4I+LMjXPhJXmj6WbGg/KAeIPnt3YD+isYg+Ka5oP6gllD4Ozdg+upB1PljDYD8+hXQ+sqtoP1Oaoj4wWdk+mmdkPo4gYT/89GI+EYNoPxpoqz7oUdk+y5KsPkYXyj5S5aM+WuvIPsInlD68wcc+j+aAPnCfxj5lX14+OBPGPr4JlD2UuOE+vZL2PsTmbT+q8bQ9OOznPuJPvj3UkfA+QkbGPbZA5z7GXqM9SpTpPkJR8z4QJHI/Pg+yPQKD8T5XK/A+1mV2P2Ul7z7IOXI/rAjtPsrOdT8iV/E+hKduP9786j468HE/xhjsPnJkbj+47uk+CHx1PwVo4T4NGHI/D0bkPjq4dT/5gt4+lnduPwSs3T7Y+3I/SI/hPsWPdj9gqNk+PW9vP4WcCT60A+8+Z+PYPpT3cz+GDvY9eMj0Ppap3j6E1Xc/luf/Pc5u6j5e0eo9SFbyPv4s7z2eFeg+Bi/gPbK18D4+ld09BjfyPo4tyj3WHfA+ps3KPaSm8T4Gc8s9Zr/yPhosWz8GXlA/Dte/PVIS8j5uH7Q9DvLyPkKG7z66Hnc/KgPBPdgs8z5sdmA/Jy9VP6qhtT1qA/Q+fg3vPuqodz9q6Gc/ztNVPxO9YT8M5VE/SE1nP6qGUj9wkF0/lLhNPwZPYz+wTU4/9d5gP0lnTD+UQWQ/oMxLP2KwZj8+004/VjRmPyoPTD8w3mk/lWVNP2ozbD8gSlA/OP9nP4I5Sz9mnWs/MNVKP3TiaD/CvEk/SyZvP8h2TD/85Wo/pmVGP+e4bj8Q/UQ/bldoPzB3Rz+stGg/QFhEPyQRaz/HAkE/JjRnP2JZRj+UZ2U/pXBDP1DyYz+QqD8/6htlP6XjRT+avWI/MGlEPwAdYz/s2kU/I0phP+LYRT8k8WA/6lhDP2xeYD/kuEU/DLZeP4K1RD8uwV0/oA1CP4C0Xz+oZUY/Av1dP1L5Rj9zo1s/OrtGPyqGXz98Nkc/1KNePxgWSj/6kV8/JmNIPyo8YD95Mko/FjVgP3yzSD8Fa2E/9JZJP4LeYD9Kg0g/8MBiP2uGSj8mJ2I/LvFHPy0RYz8oKEg/31thPxqvRz/6qmI/+vpGPxq/Yz8HEUc/6Z1hP0IkRz9dtmE//X1GP3TyYD+aRkc/ftpgP12ARz8aqWA/1lFHPyj0YD/wBUc/WLNgP+Y2Rz/ivWA/CdFGP92XYD9QMkc/b21gP6LURj+4fWA/iSdHP2w8YD8zEEc/+HJgP3BERz9kLGA/ylNHP0trYD+6X0c/yCZgP7C1Rz+tXWA/YohHP9tgYD9I20c//nZgP7SKRz/VpWA/D9FHP/uOYD8Lk0c/up5gPzZrRz+whGA/DGNHP2uMYD+IUUc/gI5kPxOoRz9ieWU/BDhHP7YSZT/OJUg/+BVkP39fSD8f2WQ/hYBIP9YhZD9kr0k/rttkP1UQST8A32Q/xj5KP48wZT/lXkk/huVlP7xkSj+ZvGU/qHdJP1LhZj/Y+Ek/rjpmPwQ2ST8mUGc/pSpJPyl0Zj9800g/cPxmPwwPSD+gS2Y/4lRIP3hqZj9Ggkc/swNmP+QOSD++iWU/qPJHP5LPZT8uV0g/6PNlP3xySD/5vWU/jHtIP/qSZT9OT0g/PJhlP+x8SD9CWGU/WmRIP8ByZT+WhEg/kTplP/OISD/OXGU/G4xIPw47ZT/Vu0g/Ol9lPyKiSD+QY2U/h9tIPxR4ZT9Mo0g/vKRlPzrlSD+wnGU/0KFIP2PjZT9AzUg/LMBlP1yaSD+yAmY/OKJIPx/YZT+8kkg/oNRlP0B9SD+O8ts9njTzPtjGWT98fUY/YsrkPdyf9D6KqVw/FBRAP9ZY7j36wPY+kovgPvTmeD+ShWQ/of07PzpJ5z1qufM+dp3xPRLz9T4Iy98+9HF4P65v4j62R3c/l+XkPi18dj/UDuM+ns53P6ZvbT/nED4/wFLlPqwMdz+GNnI/veFDP3KI6T6iy3Y/hjZyP+r9TT99tOk+Sjx2P6Au7D4iGXc/yCNuP8rXUj9Ni+w+WYx2P9bT2T5cSm4/ZqDYPjrHbT9V3t0+HnxtP25F3T7MtGw/xUbsPhF6bT9Dj+w+kLZsP9rp8D7eyG0/KtPxPgotbT+wAvI+KFBsP5bz7D5Fx2s/MIHcPk69az8rHNg+TM5sP1xETD+gnWQ+uKdOP6jKXT7E1E4/WLhTPu2cUD9YYFg+RulQPwAFVj5jRVE/qJpYPryTuj4ELvE+SG5RP7C4WD6IelE/DEpXPubyUT+IVVY+iVW8Pi5G8j6OlFE/mLtXPozRuT6KOvM+gii+Pq4T9T49AFI/5P1WPuADuz6QXPU+lzWwPrjr9z4eI68+uCf4PugjsD4Kevc+mvyvPpIk9z6YSK8+EOP3PoYVrz7UzPc+wH+4Plpj8j4rilE/+PRTPm5WUD8Mz0s+TuhCP6ChTT1yoEM/LSoIP+tMRD/Qrjw9uLxBP5A8Xj3fgEw/1oJMP2ZKTT8+ClE/EuBDP2JxBD+qpok+Yj5EPxrPiT58oUE/ssmBPjKURD9uA4Q+BipCP3Tncz53/kM/pO96PnrDQT+CKn4+/ORAP/L0JD/gZ7c+eCkjPyCptT6q/4Q+jydBP3QKJj8g/aw+804kP7pKqz60wIk+rH1AP20cJz/+PaQ+4E8lP/ayoj5HPCE/4PGfPgZFID+KQqg+vHgcP3rFnD4WZhs/lG+kPnhhGD+kxJk+pBwXP+ReoD6rNRU/Sj6XPvLJEz/ufpw+UcMSP1ZBlT4hUxE//OOYPu1KDz9U4Jw+ZpIRP1SRoj5rHRU/NHKoPh3JGT/Oza0+8fYeP65bsj5L9iY/rcARPxmfJz8FoRI/AAIpP5uoED9AQyY/fswQP7KgHj+wYvA+EaklP3PKDz96Yyg/mNkPP9pHHz9kB/U+MOQnPzX5Dj/oqio/JBoPPzM7ID8Gzfk+TGMqP+leDj/pOSs/MLYPP6SkLD/Fwg4/Vp0hP3Qy/j5r/iw/DkYOP3VWLT/nDw8/oNYiP6ju/z4YWy4/uMsOP+xELT9Afg8/VKUrP7RHED/HMSM/nDT+PoTnLT88nQ8/TC8jP97I+T4aGyw/F+MQP4azIj/cCv0+dGEiP2Ra+T6AXyI/Gpr9Pi6lIT/8Kvk+3L0hP+zN/D514iA/KBX5Ph4wID/4dfQ+GyEhP+xo9D4etx8/XtPvPsjVID8wu+8+0gUiP0r+7z7HMSM/5k7wPqc3KD8WjBM//h8iPwKp9D4WIiM/jvb0Pl8YKj+bNRI/9ZQpP5JpET/Uuhk/rr0JPxD5HD+ITvw+tk8YP7K0CT/Uuhk/OIgGP9QHGj+cIPw+EPkcP7K2+T4S6hw/aBv3PgMJGj+e4Pk+Mv4ZP96i9z55bBc/5B76PjlnFz9Kc/g+FlgXP/7k+z7StBk/KKMDP8bVFT/6Rfo+jpUVP7xt+T5gYRU/ejX7PkN6GT9igAE/BroUP/gH+j5a7Rg/MrkAP+fzGD9LFAI/rsAYP/rLAz+ohRg//MwBP6niFz8UxwM/8NQXP0AFAj/o+xY/qKgDP6g/FT8suvc+WKcXP+xKAT+FKxY/yjIDP2YSFz9gCfU+wNIVP7Aq+D6YXhc/2LD2PmHiGT+SZvU+RwEVPzTqBT+yqhk/ijbzPobEHD92gPQ+Vh0UP7D8CD/Pihw/XvHxPoB7FT+VVQk/XeMWPyGVCT+5KhY/JEYGP5BVFz9MeAY/xoQYPyCLBj+Gvgg/zhwMPy1xGD+TXhE/V5MHP+QVDD9hvQg/uHIJP65FFT+wRxE/LXEYPxX7Dz+QYBg/rpUOPxRIFT+GExA/UTwVPyrhDj+YWxI/Ej4QP9VSEj98WA8/hr4IPzTwBj/8QRI/KjIRP0hqED9MYRA/IhQQP/DtDz/6mAg/mPIEP7/eDz+W6BA/eCEIP+AtBD8T/w4/nkgQPxsjCD80bgU/ZfMHP9cKBz/Rxwc/TiMFPyE4Bz+QAQc/RzAHP3ZgBT+WcgY/hO4GP2MGBz9WwgQ/SbcPP4v9Dj8SwAU/4ZQGPz/1ET+0gw0/hGUQPyQ6Dz+yShI/BmYOP6gdFT/Irw0/gMwEP/b3CD+R3xQ/74QMP+g2GD/2MA0/jhkEP0yKCz8w9xc/CdMLP8U8BT9kzgs/3GYGP1H+Cz8SxQU/fj0JP5i/Bj/sYgk/fLwHP7NyCT+TbQE/QhjxPspBAz+sHPE+0tgBP+Y8+D57L/8+YmLxPpub+z6kEvI+/fAKP4gQCj/VRQA/Wmr4Ps59/T4iFfk+RMcLP+o0Bz94WgE/oGH/PmQ0AD99JAA/zP4MPxNEBD+DoAI/tnf/PsWxAj/CZwI/q28CP5xdAz8V0g4/zqABPwm1Az/V8gI/ElgEP06FBD+vZhA/K6gAP8A9BD9yeAI/Ic0DPz56/z7qAQU/pDcDPxnoED/SvwE/ZREFP3q4/z7c7RA/YIIEP8BGED/CbAI/XOMPP1qsBD/r2A8/ZgsCP7vwDj9WsgQ/qwMPP1J6Aj/16g0/ErMEPwEEDT/QhAc/EkoOP1idBz8rZww/mGAKP/jpDT/8hQo/x20PP8GHCj9lEQU/eDDxPtztED/Yfwo/9ZAPP4iYBz+CAAU/ElX4Pt7cED9qjAc/0WsDPyhG+D50MBA/cnLnPrD4KT+y8QA/ReIoP15AAT9JpQ0/rD7nPgA6KT9e9vw+dDAQPzz15D7wIhA/JHfiPv2nDT8GGOU+bp8NPzr24j4Rags/vEjlPmtoCz8wsuM+uVILP1j45j7ehyg/WoL4Pv8KCj82ZOU+LNsJPyiU5D4LoAk/eEPmPsbVJz/SWPU+nRgJP74q5T4ENic/1lz0PmWTJz8qf/Y+qtYnP8w0+T5SLSc/WDn2PmgqJz8Qkvk+3KUmP0jc9j4sXiY/1s/5Pi2OCT/YKeM+AEwmP4LN9T78GAs/mNDgPkyIJT/8gPk+2hIKP8B+4z7pYQs/8jTiPpWHDT/0DeE+R1INP5Iy3z7YRSU/BD/+PtgBED/2OuA+6coPP3gM3j6gRSU/NKwBP6qDJj/6nQE/9sQnP5B6AT84VSY/8Dr+PupgJz8k+v0+2E0oP+aM/T4YYBo/EOvpPniJIT9CSAo/ZGMgP/NMCj9L3Bc/xCfpPpyHIT8/lAc/xWsaP0qJ5z7Faxo/lCjlPjjpFz9SGOc+eesXP7YO5T6vlhU/8tTmPqiYFT++SeU+oncVPyR16D54iSE/MAcFPz4JFD84sOY+0MwTP5bX5T79kRM/ToTnPuhhIT++BAM/puwSP8hI5j6v6CA/pjQCP0zvID8ehQM/pMAgP90nBT82lSA/ZTIDPxoGID+WFgU/YvUfP7xiAz9FMx8/wvAEPyqUEz8CWOQ+/scfP3a8Aj/bchU/7GDiPhV2Hj8WhgQ/gxoUP5zN5D5oohU/PNHjPh/oFz+aMOM+YNcXP95V4T4ZfB0/COkGP1VlGj/u++I+0FoaP4LR4D6yxBw/2oQJP67/HT8v5Ak/mD4fP94tCj+Ogx4/AUYHP9ePHz/FhAc/NIogP0ydBz+0Dhs/PB4SPwc1JT9EvOI+UhglPxDe5D4OaR0/OsURP7TvIj+48uI+tBIbP1RUEz/fOxs/OIkUPxRbHT/5yRI/znQdP6zEEz8zoR8/PDYSP+K3Hz+z8hI/vtkfP0RnET8yhiA/rGfjPgSUIT8FvBE/9gciP3IREj/nPx4/TmbkPu49Ij/+KhE/jGgdP4rL5T4KaiM/KoARPzOZHj9aE+U+KG8gP2zV5D6yOB4/Uu/lPspUID/SW+Y+5KIePxgC5z4wfSA/qgXoPvZEHj+UnOc+MKAiP/WtEj/SiCA/tITpPnCMID/iIBQ/qschPwSuEj+08R8/hqITP3XIHT/SoBQ/YlsePzZiFT8G7iI/asjqPkqiGz8+lhU/oz8cP5qHFj8HNSU/xpHrPqckJT8aRuk+YRIlP3L95j4c2yI/8MjoPnzFIj8CvOY+gtAiPyjQ5D72nw0/gjr9PmQnKT9KBgU/rAkoP4YLBT/MgAw/fqj4PmQnKT/tkAc/nHwMP1Y8/j5iUws/0CX/Ph6GCz86jPk+HIkKP25Z+j4ptAo/1F71Ppf5CT9YAPY+E3ALP4KF9D72ISk/xt4JP68yCj+k0fI+MsAJP1zO8j55bQo/srPxPlHtKD9gkAs/VrYJP5At8T5Meig/LzUMP3qEKD84GAs/62AoPxe9CT/zLSg/aFYLP7iwJz9Awgk/lpYnPxwwCz/L5iY/dN8JP177CD8g1PI+V2gnP9HJCz81iAg/hJn2PtsnJj/hQQo/4VcJP26k8z6lSAk/sI72PjCgCT9i8/o+QbEIP0BS+z57OyU/+hYIP1xACj/M0P8+xCoJPyonAD+igCQ/9K0FP2CxJT94YAU/SOsmPxcmBT+dPCY/UMsHP/lCJz9onQc/mDIoP0yNBz/NIhA/JgnrPiwjDz/YN+s+zmUQPxbE7z6IOxE/cAjrPkbxDT/EARQ/aFQSP+oX6z6JXhE/xrnvPkrFCz/fWhI/aFQSP/DA7z6ubhE/YCT0PgamCT9k5RA/Oy4SPy479D51rBA/3EL0PhxTET9wD/c+kcIHP6DyDz+RrRE/9Mb3PoTlED9Ql/c+/6cGP9PsDz/YBRE/HgL5PnlvED8CHPc+OvcPP/RF9D7gnAY/ox4RP5o3ED8k8/c+P4kHP+IgEz94Tg8/PJ/0PtZVBz9eVxE/6FEIPybCEj/Ifgc/3MEQP0jkCD/3MRI/ggQIP4CiED9VWgk/aKMRP4stCz8MKxM/Zo8KP3noEz+9MQ0/oOgUPxNpDD/kzBU/J38LP2q4Fj99iAo/+ZEXP+onDj9+oes+gs8JPxKnFD8e8Qg/RFUVP/SnDj+iQPA+zoMPP97n7z5qgQQ/6LHtPTgfCz8YDdw90lMNP+ix7T2QDgU/6LHtPRGSCj8YDdw9aoEEPyAb8j3SUw0/IBvyPTgfCz9QduA9q8YMP+ix7T04Hws/cN/kPYQ5DD/ose09q8YMPyAb8j0Rkgo/UHbgPesECj9QduA9EZIKP3Df5D3rBAo/cN/kPRGSCj+oSOk96wQKP6hI6T0Rkgo/6LHtPTgfCz+oSOk9XqwLP+ix7T04Hws/6LHtPV6sCz8gG/I9XqwLP2CE9j2EOQw/IBvyPTgfCz8gG/I9OB8LP2CE9j0Rkgo/IBvyPesECj8gG/I9EZIKP2CE9j3rBAo/YIT2PRGSCj+A7fo96wQKP4Dt+j0Rkgo/qFb/PTgfCz+A7fo9OB8LP6hW/z1erAs/gO36PYQ5DD+A7fo9OB8LP/TfAT5erAs/qFb/PTgfCz+QFAQ+hDkMP6hW/z0Rkgo/9N8BPusECj/03wE+EZIKP5AUBD7rBAo/kBQEPhGSCj8oSQY+6wQKPyhJBj6QDgU/qFb/PRGSCj/EfQg+OB8LPyhJBj6rxgw/qFb/PWqBBD+oVv89OB8LP8R9CD7SUw0/qFb/PavGDD+A7fo9q8YMP2CE9j1qgQQ/gO36PdJTDT+A7fo9aoEEP2CE9j3SUw0/YIT2PZAOBT+A7fo9kA4FP2CE9j23mwU/gO36PbebBT9ghPY93igGP4Dt+j23mwU/qFb/PesECj/EfQg+3igGP6hW/z3Edwk/xH0IPgS2Bj+oVv89nuoIP8R9CD7Edwk/KEkGPsR3CT+QFAQ+KkMHP6hW/z2e6gg/KEkGPlDQBz+oVv89nuoIP5AUBD4qQwc/gO36PSpDBz9ghPY9UNAHP4Dt+j0EtgY/gO36PQS2Bj9ghPY9BLYGPyAb8j3eKAY/YIT2Pd4oBj8gG/I9t5sFPyAb8j3eKAY/6LHtPcR3CT8YDdw9BLYGP+ix7T2e6gg/GA3cPbebBT/ose096wQKPxgN3D3Edwk/UHbgPcR3CT9w3+Q9KkMHP+ix7T2e6gg/UHbgPVDQBz/ose09nuoIP3Df5D0qQwc/IBvyPVDQBz8gG/I9UNAHP2CE9j14XQg/IBvyPXhdCD9ghPY9nuoIPyAb8j14XQg/6LHtPZ7qCD+oSOk9nuoIP+ix7T3Edwk/qEjpPcR3CT/ose096wQKP+ix7T3Edwk/IBvyPcR3CT9ghPY9xHcJP4Dt+j2e6gg/YIT2PZ7qCD+A7fo9nuoIP6hW/z14XQg/gO36PXhdCD+oVv89nuoIP/TfAT7Edwk/9N8BPsR3CT+oVv896wQKP6hW/z2QDgU/IBvyPYQ5DD9ghPY9/n4kP0hjgz2o9yQ/gEl4Pc00JT9wmY89UDEjPzgugT3neSE/CJ2BPVOlIz/wMJE9fyEjPzAbaj18UiM/AN5VPWQuIT+wTWk9A/MgP8DpVD2yXx8/IAx1PUC/Hj8wK2M9LfgdPwhehj0I2h8/IAuGPYC7Hj+wyos9yCEgP0BFlj2idiA/UIKpPe3dIT/w2JI985weP5gomT0L0h4/wMmrPQJSHT+Qd5g9mw0cP5iglj0Ybx0/EKytPcZBHD84N7Y9/TceP5Bfwj1OJR0/aMbHPUCjHz+YgdE9nGofP+AQvj1eHSA/8EvKPa75ID9Qjrw9OsEiP1D6uj1GaiE/+JHMPXwhIz+AO8w9XnchP4Cv2D0cRCE/QMfiPbVpIz9wMtk9YKIjPxBj4z06OSU/2HPTPXjWJT/gadw9VaQmP1jAxz2SwiQ/UOjHPW/jJT8gQMI9OH8kPyCitz19KyQ/YEWkPdcHJj+g1bQ94dIlP1DtoT25Vic/EK61PSA7Jz+w7p891pMoP7jctz3Wkyg/cAaePbCXJz9Yn809C3gnP5DAhT3CmyU/IARoPRdmJj8QNYs9p1AiPxjmpj2K/R4/AJnZPeYAHT+gkoA9dCwTP9CenD38txM/MP+kPan+Ez8ggI49paoRP1Asnz2Nrg8/MKyePcUwEj8QqYw9W5gRP/gxrT3/0BE/gOW4PUFXDz8Aqa09mRIPP7ByuT1AQA0/0N6mPbyGDD8ANbE9jaALPxAtmT20zQ0/AI2ZPWWCDD9w55I9pyAOP5DJhj3Fgg4/0BVhPTMiED/gvoo9D18MP9Bygz1ynAw/4NBbPWbgCj+AP4Q9TWkJPzhghj0HAgs/YHVXPZ+lCT+gs0M9TuoLP0CWJz20rAo/cBgbPVOODT9glwQ92UwNPyCMMT2IGw4/YEMVPUgaDz8gCjU9AikRP2CwOD14nA8/gAEQPVCYET8wyRA9l6sPP6D65zxRcA8/QEy5PM/rET+AnOU8VC0SP4B7tjzCAxQ/8BYAPZO5FD/Au9Y8macVP8AlGz2OehM/oMkaPZLIFD/g3ic9tCwTPxBsQD3jyxI/wDJtPanyFD/A5EY9cLUUP4Cecj3idRY/QPBEPfhVFj8AO3c9juQXP1DkPz2O5Bc/IKR7Pf3AFj8gkg09bJwWP/jimT23dRQ/SGeuPaZfFT8wlJM92qYQPwAfZz25zgw/oMLjPKWCCj9Q4J89aoEEP+ix7T04Hws/GA3cPdJTDT/ose09kA4FP+ix7T0Rkgo/GA3cPWqBBD8gG/I90lMNPyAb8j04Hws/UHbgPavGDD/ose09OB8LP3Df5D2EOQw/6LHtPavGDD8gG/I9EZIKP1B24D3rBAo/UHbgPRGSCj9w3+Q96wQKP3Df5D0Rkgo/qEjpPesECj+oSOk9EZIKP+ix7T04Hws/qEjpPV6sCz/ose09OB8LP+ix7T1erAs/IBvyPV6sCz9ghPY9hDkMPyAb8j04Hws/IBvyPTgfCz9ghPY9EZIKPyAb8j3rBAo/IBvyPRGSCj9ghPY96wQKP2CE9j0Rkgo/gO36PesECj+A7fo9EZIKP6hW/z04Hws/gO36PTgfCz+oVv89XqwLP4Dt+j2EOQw/gO36PTgfCz/03wE+XqwLP6hW/z04Hws/kBQEPoQ5DD+oVv89EZIKP/TfAT7rBAo/9N8BPhGSCj+QFAQ+6wQKP5AUBD4Rkgo/KEkGPusECj8oSQY+kA4FP6hW/z0Rkgo/xH0IPjgfCz8oSQY+q8YMP6hW/z1qgQQ/qFb/PTgfCz/EfQg+0lMNP6hW/z2rxgw/gO36PavGDD9ghPY9aoEEP4Dt+j3SUw0/gO36PWqBBD9ghPY90lMNP2CE9j2QDgU/gO36PZAOBT9ghPY9t5sFP4Dt+j23mwU/YIT2Pd4oBj+A7fo9t5sFP6hW/z3rBAo/xH0IPt4oBj+oVv89xHcJP8R9CD4EtgY/qFb/PZ7qCD/EfQg+xHcJPyhJBj7Edwk/kBQEPipDBz+oVv89nuoIPyhJBj5Q0Ac/qFb/PZ7qCD+QFAQ+KkMHP4Dt+j0qQwc/YIT2PVDQBz+A7fo9BLYGP4Dt+j0EtgY/YIT2PQS2Bj8gG/I93igGP2CE9j3eKAY/IBvyPbebBT8gG/I93igGP+ix7T3Edwk/GA3cPQS2Bj/ose09nuoIPxgN3D23mwU/6LHtPesECj8YDdw9xHcJP1B24D3Edwk/cN/kPSpDBz/ose09nuoIP1B24D1Q0Ac/6LHtPZ7qCD9w3+Q9KkMHPyAb8j1Q0Ac/IBvyPVDQBz9ghPY9eF0IPyAb8j14XQg/YIT2PZ7qCD8gG/I9eF0IP+ix7T2e6gg/qEjpPZ7qCD/ose09xHcJP6hI6T3Edwk/6LHtPesECj/ose09xHcJPyAb8j3Edwk/YIT2PcR3CT+A7fo9nuoIP2CE9j2e6gg/gO36PZ7qCD+oVv89eF0IP4Dt+j14XQg/qFb/PZ7qCD/03wE+xHcJP/TfAT7Edwk/qFb/PesECj+oVv89kA4FPyAb8j2EOQw/YIT2PdT7wT5E8hY+lLS9PrCp8D0kd7w+cA28PTdNPj4tRg0/SJu9PkCMiz3GBD8+nIwTP5jOPT5WGRo/eQ06PhowIj/KATc+NL4qP0P8DT72GC0/ek7MPaZvLz+yqeo+RElKPsCX1z7QnEQ+8ifGPnAOOD5M/ME+zGuvPj61vT6Eur4++Xe8PoThyz5HhT4+iYdoP0mcvT7AAdg+wzQ/Prg+Yj8i9T0+IbBbP8InOj7Ml1M/ZxA3PoEISz9mBQ4+xq9IPxJVzD0DWkY/zqnqPhDAlT7wl9c+aJaYPjwoxj643Z4+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8LDQoOCw0KDgsNCg4LDQoOCw0KDgsKDQ4LCg0JCwoNCQsKDQkLCg0JCwoNCQsKDQkLCg0JCwoNCQsKDQ4LCg0JCwoNEwsKDQ4LCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoTDQsKDRMLChMNCwoNEwsKEw0LCg0TCwoTDQsKDRMLCg0TCwoTDQsKEw0LCg0TCwoTDQsKDRMLChMNCwoNEwsKEw0LCg0TCwoTDQsKDRMLChMNCwoNCQsKEw0LCg0JCwoNCQsKEw0LChMNCwoNCQsKDQkKCxMNCgsNCQoLCRMKCw0JCQ0KCw0JCgsJEw0KCQ0TCAkTDQgNCQ4ICQgNEwkNCA4JCBMNCAkNDggJDQ4ICRMNCAkBDQgJAQ4ICQEhAQgJIQgBCSEIAQkhAQgCKwEhCAIBCCErAQIhJQECJQgBAiEAAQIlIQECISsBAiUAAQIlIQIBISUBAiUhAQIlIQIBJSEBAiUhAQIjJQECIyUBAiMAAQIjAAECJQABAiUAAQIfJQECIx8BAiUfAQIjJQECIy0BAiUfAQIfIwEfAiUBAiMfAR8CJQECHyMBHwIlAR8CJQEfAiUBHwIlAR8CJQEfAiUBHwIjAR8CIx8BAiUfAQIlHwECJQEfAiMfAQIAHwECJR8BAiUfAQIjHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAQAAHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwEjAB8BAAAfAQgJHwEICR8BCAkBHwgJAR8ICQEfCAkBHwgJHwEICR8BCAkBHwgJAR8ICQEIHwkBCB8JAQgJHwEIHwkIAQkfAQgJAggBCR8ICQENCAkBHwgJAQ0ICQ0OCAkNDggJDRMJCA0OCQgNEwkIDQ4JDRMIDQkOCAkNEwgJDQoLCQ0TCg0JCgsKCw0JCgsNCQoLCRMLCg0JCwoTDQsKDQkLCg0JCwoNCQsKDQkLCg0JCwoTDQsKEw0LCg0JCwoNCQsKEw0LCg0JCwoTDQsKDQkLCg0TCwoTDQsKDQ4LCg0TCwoNAAsKEw0LCg0ACwoNEwsKDQALCg0ACw0KAAsKAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALCgAACwoAAAsKAAALCgAACwoAAAsKAAALCgAACwoAAAsKAAALCgAACwoAAAsKAAALCgAACwoAAAsKDQALCg0ACwoNAAsKAAALCg0TCwoNEwsKDQALCg0TCwoNEwsKDQALCg0ACwoNEwsKDRMLCg0TCwoNEwsKEw0LCg0TCwoTDQsKDRMLCg0TCwoTDQsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDRMLCg0TCwoNEwsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDRMLCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNDgsKDQ4LCg0ACwoNDgsKDQ4LCg0OCwoODQsKDQ4LDgoNCw4NCgsODQoLDg0KCw4NCgsODQoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoLDQ4KCw0OCgsNDgoNCw4KCw0OCg0LDgoNCw4KDQ4LAA0OCwoODQsADg0LAA4NCwAODQsADg0LAA4NCwAODQsADg0LAA4NCwAODQsADg0AAA4NAAAODQAADg0AAA4PAAAODwAADg8AAA4PAAAODwAADw4QAA4PEAAODxAADg8AAA8OAAAODxANDg8QAA4NDwkODxANDg0PCA4PEAAODxANDg8QDQ4PDQgODw0IDw4QAA8OEAAODxANDg8NCQ8OEAAODxANDg0PCQ8OEAAODxAADw4QAA4PDQAODxAADw4QAA4PAAAODQAADg0LAA4NAAAODQsADg0JIQ0OCQsODQkIDQ4JCA4JDQgNDgkLDQ4JIQ0OCwoNDgsKDQ4LCg0LDgoNCwoODQsOCgsNCg4LDQ4KCw0OCgsNDgoNCw4KCw0OCgsNDgoNDgsKDQsOCg0OCwAODQsADg0LAAsNDgoLDQoOCw0KDgsKDQ4LCg0OCwoNDg0KCwkNCQoLDQkOCAkNDggJCA0OCQ0IDggJDQ4ICQINAQgJIQEICQIBIQgCAQIhCCECAQACASEAAiEBAAIhAQACIQEAAiEBAAIhAwECAwQhAiEBAwIhAwACAwQAAgMhAAIhASMCAwQAAgMjBAIDIwQCASMhAgEjIQMCBAADAgQAAiMDBAIjAQADAgQAAiMBAwIjAQAEAgMAAgMBIwIjAQAEAgMjAiMEAQIEAyMCASMEAgQDIwIBIwMCASMDAgMEAAIDBAACAQMhAgMEAQIDIQQCAwQAAgMEAAIDBCECAwQAAgMEAAIDBAADAgQAAwIEAAQDAgADBAIAAwIEAAQDAgAEAwIAAwQCAAQAAAAEBQAABAMAAAQAAAAEBQAABAAAAAQFAAAEAwIABAMCAAQDAgAEBQAABAUAAAQDAgAEAAAABAIDAAQAAAAEAgMABAAAAAQCAwAEAAAABAIDAAQAAAAEAgMABAIDAAQAAAAEAAAABAMCAAQAAAADAgQAAwIEAAQFAAAEBQAABAUAAAQFAAAEBQAABAUAAAQFAAAEBQAABQQAAAUEAAAFBAAABQQAAAUEAAAFBAAABQAAAAUGAAAFAAAABQYAAAUGAAAFBgAABQYAAAUGAAAFBgAABgAAAAYFAAAGBQAABgAAAAYFAAAGBQAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABQYAAAUGAAAFBgAABQYAAAUGAAAFBgAABQYAAAUGAAAFBgAABQYEAAUGBAAFBgQABQYEAAUGBAAFBAYABQQAAAUEAAAFBAAABQQAAAUEAAAFBAAABQQAAAUEAAAFBAAABQQAAAUEAAAFBAAABQQCAAQFAgAEBQIABAUCAAQFAgAEBQAABAUAAAQFAAAEBQAABAUAAAQFAAAEBQAABAUAAAQFAAAEBQAABAUAAAQFAAAFBAIABQQCAAQFAAAEBQAABQQAAAUEAAAFBAAABAUAAAUEAAAFBAAABQYAAAUGAAAFBgAABQYAAAUGAAAFBgAABQQAAAUGAAAFBgAABQYAAAUGAAAFBgAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAFBgAABQYAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABQQCAAQFAAAEBQAABAUAAAQFAAAEBQAABAUAAAUEAAAFBAAABQQAAAUEAAAFBAAABQQAAAUEAAAFBgAABQYAAAUGAAAFBgAABQYAAAUGAAAFBgAABQYAAAUEAAAFBAAABAUAAAQFAAAFBgAABQYAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABgUAAAYFAAAGBQAABQYAAAUGAAAGBQAABgUAAAYFAAAGBQAABgAAAAYFAAAGBQAABgUAAAYFAAAGBQAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGBQAABgUAAAUGAAADBAIAAwQCAAMCBAAjAgEfAQIjHyMCAQABIwIAASMCAAEjAh8BIwgCAQgjAgEjAgABCAkCAQgJAggJDSMICQIfCAkNDggJDQ4JDQ4IDQ4JCA0JDggNDgkIDQkLCg0OCwkNCwoJDQsKDgsKDQkLDQoOCwoNCQsKDQkLDQoOCw0KDgsKDQkLDQoOCw0KDgsNCg4LDQoOCw0KDgsKDQ4LCg4NCw0KAAsNCg4LDQAACw0AAAsAAAALDQAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsKAAALAAAACwAAAAsAAAALAAAACw0AAAsNAAALDQoACw0KAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCgAACwoAAAsAAAALAAAACwAAAAsAAAALCgAACwoAAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALDQAACw0AAAsNAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACw0AAAsNAAALDQAACw0AAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACwoNAAsKAAALCgAACwoAAAsAAAALAAAACwAAAAsAAAALCgAACwoNAAsKDQALCg0ACwoNAAsKDQALCg0ACw4KDQsKDQALDg0ACw0AAAsNAAALDg0ACw0OAAsODQALDg0ACw0OAAsNDgALDQ4ACw4NCgsODQALDQ4ACw0OAAsNAAALDQAACwAAAAsNAAALDQAACwAAAAsAAAALDQAACwAAAAsNAAALAAAACw0AAAsAAAALDQAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALDQAACwAAAAsNAAALDQAACw0OAAsNAAALDQ4KCw4KDQsNCg4LDgoNCw0OCgsODQoLDQ4KCw0OCgsNCg4LDQ4KCw0OCgsNDgoLDQoOCw0KDgsNCg4LDQoOCw0OCg0LCg4NCw4KDQ4LCg0OCwoNDgkLDQ4JCw4NCQgODQkIDg0JCA4NCAkODQgPDg0IDwgJDQ4ICQ0CCAkNAggJAgEICQIBCAkCAQgBCQIIAQkCCAEJAgECIwgBAiMhAQIjIQIBIyECASMhAgEjIQIjAQACIwEhAiMBIQIhAQACASEAAQIhCAgBAiEICQIhCAkODQ4JDQ8LAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAAAQgJHwEfCAkBAiMfAR8CCAECIx8BHwIjAR8CIwEfAiMBHwIjAR8IAgEfCAIfAQIIAR8ICQEfCAkfAQgJHwEjAB8BIwAfASMtHwEjAB8BAAAfAQAAHwEAAB8BAAAfAAAAHwAAAB8AAAAfAAAAHwEAAB8tIwEfAQIjDw4QAA8OEAAQDw4AEBEAABAPDhEQDwAAEA8AABARAAAQEQAAEA8AABARAAAPEA4AEA8AAA8QDgAQEQAAEA8OERARDwAPEA4AEA8OERARAAAPEA4AEA8OERAPDhEQEQAAEBEAABAPDhEQEQAAEA8OERARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEAAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEQAAAREAAAERAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAAQEQAAEBEAABARAAAQEQAAERAAABEQAAAREAAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAAEBEAABARAAAQEQAADw4QAA8OEAAPDhAADw4QAA8QDgAPDhAADxAOAA8OEAAPDhAADw4QAA8OEAAODxAACwoNAAsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTAAsKEwALChMNCwoTAAsKEwALChMACwoAAAsKEwALCgAACwoAAAsKAAALCgAACwoAAAsKAAALCgAACwoAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsKEwALEwoACwoTAAsKEwALChMNCwoTAAsKEw0LChMUCwoTCQsKEwkLChMJCwoTCQsKEwkLChMJCwoTCQsKEwkLChMJCwoTCQoLEwkKCxMJCRMKCxMJCgsJEw0IEwkUCAkIExQJCBMUCAkTFAgJExQICQETCAkBEwgBCR8BCAklAQgfCQEICR8BHwgJAQgfCQEfCAkBHwgJAR8ICQEfCAkfAQgJHwEICR8BAAAfAS0AHwEAAB8BAAAfAQAAHwEAAB8BAAAfAQAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAQAAHwEAAB8BAAAfAQAAHwEAAB8BAAAfASUAHwElLR8BJQIBHyUtAR8lAgEfJQIBHyUtAR8lLQEfJQIBJR8tASUfLQElLR8BJS0CASUtIwElHwIBJS0fASUtAgElLQIBJS0AASUtAAElAislAQIrASUCKyUBKwIBJQIrASUrIQElKwIBJSsAAQglIQErCCUBCAkrCAEJKwgJARMICQEUCAkTFAgJExQJCBMNCRMIFAkTDQgTCRQICRMKCxMJCgsKCxMJCgsTCQsKEwkLChMJCwoTCQsKEwkLChMJCwoTCQsKEwkLChMJCwoTDQsKEwkLChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKExQLChMJCwoTFAsKEwkLChMUCwoTCQsKEwkLEwoUCxMKFAsKEwkLEwoUEwoLCRMLChQTCQoLExQLChMJFAgTFAkrCRMUCBMUCQgJCBMUCRMIFAgJExQICSUTAQgJKwEICSUBKwglASUrCCslAQAlASsAJSsBACUrAQAlKwEAJSsBACUrJgElJicrJSsBJiUrJgAlJicAJSYrACUrAS0lJicAJSYtJyUmLSclAS0rJQEtKyYlJwAmJScAJS0mJyUtAQAmJScAJS0BJiUtAQAnJSYAJSYBLSUtAQAnJSYtJS0nASUnJi0lAS0nJScmLSUBLSYlAS0mJSYnACUmJwAlASYrJSYnASUmKyclJicAJSYnACUmJyslJicAJSYnACUmJwAmJScAJiUnACcmJQAmJyUAJiUnACcmJQAnJiUAJiclACcAAAAnKAAAJyYAACcAAAAnKAAAJwAAACcoAAAnJiUAJyYlACcmJQAnKAAAJygAACcmJQAnAAAAJyUmACcAAAAnJSYAJwAAACclJgAnAAAAJyUmACcAAAAnJSYAJyUmACcAAAAnAAAAJyYlACcAAAAmJScAJiUnACcoAAAnKAAAJygAACcoAAAnKAAAJygAACcoAAAnKAAAKCcAACgnAAAoJwAAKCcAACgnAAAoJwAAKAAAACgpAAAoAAAAKCkAACgpAAAoKQAAKCkAACgpAAAoKQAAKQAAACkoAAApKAAAKQAAACkoAAApKAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKCkAACgpAAAoKQAAKCkAACgpAAAoKQAAKCkAACgpAAAoKQAAKCknACgpJwAoKScAKCknACgpJwAoJykAKCcAACgnAAAoJwAAKCcAACgnAAAoJwAAKCcAACgnAAAoJwAAKCcAACgnAAAoJwAAKCclACcoJQAnKCUAJyglACcoJQAnKAAAJygAACcoAAAnKAAAJygAACcoAAAnKAAAJygAACcoAAAnKAAAJygAACcoAAAoJyUAKCclACcoAAAnKAAAKCcAACgnAAAoJwAAJygAACgnAAAoJwAAKCkAACgpAAAoKQAAKCkAACgpAAAoKQAAKCcAACgpAAAoKQAAKCkAACgpAAAoKQAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAAoKQAAKCkAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKCclACcoAAAnKAAAJygAACcoAAAnKAAAJygAACgnAAAoJwAAKCcAACgnAAAoJwAAKCcAACgnAAAoKQAAKCkAACgpAAAoKQAAKCkAACgpAAAoKQAAKCkAACgnAAAoJwAAJygAACcoAAAoKQAAKCkAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKSgAACkoAAApKAAAKCkAACgpAAApKAAAKSgAACkoAAApKAAAKQAAACkoAAApKAAAKSgAACkoAAApKAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApKAAAKSgAACgpAAAmJyUAJiclACYlJwAtJQEfASUtHy0lAQABLSUAAS0lAAEtJR8BLQglAQgtJQEtJQABCAklAQgJJQgJEy0ICSUfCAkTFAgJExQJExQIExQJCBMJFAgTFAkIEwkLChMUCwkTCwoJEwsKFAsKEwkLEwoUCwoTCQsKEwkLEwoUCxMKFAsKEwkLEwoUCxMKFAsTChQLEwoUCxMKFAsKExQLChQTCxMKAAsTChQLEwAACxMAAAsAAAALEwAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsKAAALAAAACwAAAAsAAAALAAAACxMAAAsTAAALEwoACxMKAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALCgAACwoAAAsAAAALAAAACwAAAAsAAAALCgAACwoAAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALEwAACxMAAAsTAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACxMAAAsTAAALEwAACxMAAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKAAALCgAACwoAAAsAAAALAAAACwAAAAsAAAALCgAACwoTAAsKEwALChMACwoTAAsKEwALEwoACwoTAAsKEwALEwoUCwoTFAsKEwALChMUCwoTFAsKExQLChMUCxQKEwsUEwoLFBMKCxQTCgsUEwoLFBMKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKCxMUCgsTFAoLExQKEwsUCgsTFAoTCxQKEwsUChMUCwATFAsKFBMLABQTCwAUEwsAFBMLABQTCwAUEwsAFBMLABQTCwAUEwsAFBMLABQTAAAUEwAAFBMAABQTAAAUFQAAFBUAABQVAAAUFQAAFBUAABUUFgAUFRYAFBUWABQVAAAVFAAAFBUWExQVFgAUExUJFBUWExQTFQgUFRYAFBUWExQVFhMUFRMIFBUTCBUUFgAVFBYAFBUWExQVEwkVFBYAFBUWExQTFQkVFBYAFBUWABUUFgAUFRMAFBUWABUUFgAUFQAAFBMAABQTCwAUEwAAFBMLABQTCSsTFAkLFBMJCBQJEwgTFAkLExQLChMUCwoTCxQKEwsUCgsTChQLExQKCxMKFAsTChQLExQKCxMUCgsTChQLChMUCxMUCgsTChQLChMUCwoTFAsTFAoLExQKEwsUChMUCwoTCxQKExQLABQTCwAUEwsAFAkTFQgJFBMUEwgVFBMIFQgJEyUICRMlFBMICQgJExQUEwkIFBMJCBQTCQgTFAkLExQJCxMUCwoTFAsKEwsKFBMLFAoLEwoUCxMUCgsTChQLEwoUCxMUCgsTFAoLEwoUCxMKFAsTFAoLExQKCxMUCgsTFAoLFBMKCxQTCgsUEwALExQACxMUAAsUChMLExQACxQKEwsTChQLExQKCxMAAAsTAAALEwAACxMAAAsTAAALAAAACxMAAAsAAAALEwAACxMAAAsTFAALExQACxMUAAsTFAALFBMACxQTAAsUEwALFBMACxQKEwsTAAALEwAACxMAAAsTCgALEwAACwAAAAsTAAALAAAACxMAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACAklAQgJJQEICSUBCAEJJQgBCSUIAQklASUtCAElLSsBJS0rJQEtKyUBLSslAS0rJS0BACUtASslLQErJSsBACUBKwABJSsICAElKwgJJSsVFBYAFRQWABYVFAAWFwAAFhUUFxYVAAAWFQAAFhcAABYXAAAWFQAAFhcAABUWFAAWFQAAFRYUABYXAAAWFRQXFhcVABUWFAAWFRQXFhcAABUWFAAWFRQXFhUUFxYXAAAWFwAAFhUUFxYXAAAWFRQXFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFwAAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFxYAABcWAAAXFgAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABYXAAAWFwAAFhcAABYXAAAXFgAAFxYAABcWAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAWFwAAFhcAABYXAAAVFBYAFRQWABUUFgAVFBYAFRYUABUUFgAVFhQAFRQWABUUFgAVFBYAFRQWABQVFgALChMUCwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMACwoTAAsKEwALChMNCwoTAAsKEwALChMNCwoTDQsKEw0LChMNCwoTDQsKEw0LChMNCwoTDQsKEwALChMACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAEICR8BHwgJASUtHwEfJQgBJS0fAR8lLQEfJS0BHyUtAR8lLQEfCCUBHwglHwElCAEfCAkBHwgJHwEICR8BLQAfAS0AHwEjLR8BLQAfAQAAHwEAAB8BAAAfAQAAHwAAAB8AAAAfAAAAHwAAAB8BAAAfIy0BHwElLQsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAA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AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACt6n8/F5WqOQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA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got/P6P76DoAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/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AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA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AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAANDJKD+TQDg+jmUUPu+UgTywXRI/TmeQPttw1D3XCC49sF0SP05nkD7bcNQ91wguPaab2z7/Tsc+cioDPhEBXD0Fg8w+ZOi2PqM4BT4e4ec9BYPMPmTotj6jOAU+HuHnPYdt3z4G67s++ftAPr0uBTz9BQQ/kizzPjheEzzkC7I5/QUEP5Is8z44XhM85AuyOR2I5D7HIbE+2TxUPuPD3jmPIiY/DsmNPpphkD2stmw7jyImPw7JjT6aYZA9rLZsOxPZ+D6VaK0+rnwzPgAAAADuxTE//EaAPprKOz2EehY87sUxP/xGgD6ayjs9hHoWPJfYPT9J0S8+vJixPQAAAACDnxg/HjRVPtZNSD4AAAAAHdEdP/35fz7LeeU9VCWwPM3f9T5ffsw+PGvePVjgQDyyFCw/D3p2PsD8rT2mMQ07zgJAP5XBDD5oZuY9AAAAAM4CQD+VwQw+aGbmPQAAAAASbVM/ul4GPuyzLz0AAAAAL1ZgP4lO/T0AAAAAAAAAAI78ST94BQw+8oCFPXh9FDz0RF8/MewCPgAAAAAAAAAA9ERfPzHsAj4AAAAAAAAAAEm2az+/TaI9AAAAAAAAAABJtms/v02iPQAAAAAAAAAA0MllP3ex0T0AAAAAAAAAAOPnbD/rwJg9AAAAAAAAAACw00I/7mxDPkIRRT0AAAAA8T1jP3IQ5j0AAAAAAAAAANMpWT+1Ihc+w7+GOwAAAAAeAGo/Ff+vPQAAAAAAAAAAevxIP45zIz6fsl09mPGWOshfXj/uKPY9lcY2PAAAAACWMQo/1jSKPgDQQj4AAAAAy9dKP1okDz6fhG49RLUdPA1/Xz/NAwI+AAAAAAAAAABj6PY+RyOaPqroXT4AAAAA0HVDP7YFFj4hVaE92Yc3PNB1Qz+2BRY+IVWhPdmHNzyq4VY/WnkkPgAAAAAAAAAAquFWP1p5JD4AAAAAAAAAAILUPj/rchE+GFGzPQSUzDxezVE/ico4PgAAAAAAAAAAbn07PwxWFz7U4bs9lBrmPLcfTz8kgUM+AAAAAAAAAAAMpx4/6rHCPgAAAAAAAAAAD5JVP8K3KT4AAAAAAAAAALtkLj+JNqM+AAAAAAAAAACDkwU/+dj0PgAAAAAAAAAAg5MFP/nY9D4AAAAAAAAAAIeHOT/y8Iw+AAAAAAAAAABkDk4/cMZHPgAAAAAAAAAAZA5OP3DGRz4AAAAAAAAAAB3aCT/GS+w+AAAAAAAAAAAd2gk/xkvsPgAAAAAAAAAA9aYoPxayrj4AAAAAAAAAAPWmKD8Wsq4+AAAAAAAAAAD1pig/FrKuPgAAAAAAAAAADTs2P+eJkz4AAAAAAAAAAOa2MT81kpw+AAAAAAAAAAAfVi4/w1OjPgAAAAAAAAAAhDJiP+Zr7j0AAAAAAAAAALHyWj89NRQ+AAAAAAAAAAC1N3c/t4QMPQAAAAAAAAAA/WJkPxLo3D0AAAAAAAAAAAkZez/33pw8AAAAAAAAAAATRl0/tucKPgAAAAAAAAAAF2QlP9Q3tT4AAAAAAAAAAMrndT9qgyE9AAAAAAAAAAAP91Y/wSMkPgAAAAAAAAAAKS9yP2QNXT0AAAAAAAAAANc8HT9ShsU+AAAAAAAAAACSXV0/uIkKPgAAAAAAAAAAI+slP7kptD4AAAAAAAAAABaBdj+Z7hc9AAAAAAAAAADDpWg/5tG6PQAAAAAAAAAAv445P4LijD4AAAAAAAAAAFabfz/RVMk6AAAAAAAAAAA52nE/cFxiPQAAAAAAAAAA3hRUP4isLz4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAASOWU/dTfWPQAAAAAAAAAADJ5sP6APmz0AAAAAAAAAAHcQVz8jviM+AAAAAAAAAABfL08/gkJDPgAAAAAAAAAA8GFOP0F4Rj4AAAAAAAAAAIo9Oz/shIk+AAAAAAAAAAAorSE/sKW8PgAAAAAAAAAAr9hIP0SdXD4AAAAAAAAAABjzNz/RGZA+AAAAAAAAAADoABw/Mf7HPgAAAAAAAAAARrBHP+o+YT4AAAAAAAAAAPyBRT8S+Gk+AAAAAAAAAABUDE4/sc5HPgAAAAAAAAAA4NZXP4CkID4AAAAAAAAAAFrMVD+Wziw+AAAAAAAAAAC4XWM/RBLlPQAAAAAAAAAA9YtYPy3QHT4AAAAAAAAAAAIHcD/Vj389AAAAAAAAAACEqGM/4LviPQAAAAAAAAAAfj5xPx8YbD0AAAAAAAAAAAGATz/9/0E+AAAAAAAAAACLlWg/pFO7PQAAAAAAAAAA8xJ4P6Gh/TwAAAAAAAAAADwJUz8S2zM+AAAAAAAAAAD+QFk/CPwaPgAAAAAAAAAA5QNJP2vwWz4AAAAAAAAAAPFeSD88hF4+AAAAAAAAAAApKEk/WV9bPgAAAAAAAAAAdbBLPys+UT4AAAAAAAAAAIK+Sj/2BVU+AAAAAAAAAACAr00/AUJJPgAAAAAAAAAAOsJLPxf3UD4AAAAAAAAAABElTT+9a0s+AAAAAAAAAABmcEw/aj5OPgAAAAAAAAAAVp9MP6WCTT4AAAAAAAAAAFOMTD+zzk0+AAAAAAAAAACb5Uw/lWlMPgAAAAAAAAAAZsVMP2nqTD4AAAAAAAAAALyaTj8QlUU+AAAAAAAAAADTmE0/t5xJPgAAAAAAAAAAFA9QP7DDPz4AAAAAAAAAALKoTD84XU0+AAAAAAAAAAC0e08/MxFCPgAAAAAAAAAAXFZLP5KmUj4AAAAAAAAAADvQSD8Vv1w+AAAAAAAAAAAuhks/SOdRPgAAAAAAAAAAqWBLP1x9Uj4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAACwZQM/oTT5PgAAAAAAAAAAsGUDP6E0+T4AAAAAAAAAAPHyAD8eGv4+AAAAAAAAAADx8gA/Hhr+PgAAAAAAAAAAe6UfPwu1wD4AAAAAAAAAAHulHz8LtcA+AAAAAAAAAAB7pR8/C7XAPgAAAAAAAAAAal4cPytDxz4AAAAAAAAAADNtJD+ZJbc+AAAAAAAAAAAzbSQ/mSW3PgAAAAAAAAAA1yk9P1GshT4AAAAAAAAAAP05RD8QGG8+AAAAAAAAAAAYxRQ/z3XWPgAAAAAAAAAAGMUUP8911j4AAAAAAAAAAC7mIj+jM7o+AAAAAAAAAAAu5iI/ozO6PgAAAAAAAAAAK/EqP6gdqj4AAAAAAAAAACvxKj+oHao+AAAAAAAAAABeyU0/iNpIPgAAAAAAAAAAdtksPxNNpj4AAAAAAAAAAHbZLD8TTaY+AAAAAAAAAAAWiFA/qt89PgAAAAAAAAAAHqj4PnzckT7R9mo+AAAAAHZJ6z7Pg7I+eGVEPgAAAAApYQk/4LeDPqALUz4AAAAA5hkDP793pD7qqCo+AAAAAPa8Xz9Qt689AsIkPQAAAAC5fGA/YA6IPawXaD0AAAAA9KRSP/GR6j10RoA9AAAAAGp1Uz8vkLw9g8SnPQAAAABPW1A/wYAJPvVHVD0AAAAATexgP9Tq1j0Ty4Y8AAAAAAxZ5j7F1NY+YaQFPgAAAAAKeuU+hKHOPuPIFz4AAAAAxL1WPz/kvz0Qam89icQTPPoiXD/iBb09ocRDPQAAAAA3NGg/EOaaPfrgjTwAAAAADGhhP5QJyD082LI8AAAAAN0eZD+j178934t5PAAAAADAZF8/e5jcPRYGoTwAAAAAMLdeP5rO4z2j35k8AAAAADC3Xj+azuM9o9+ZPAAAAADRpmE/wpbUPUGVcTwAAAAAWi9jPxOVzz2DgDc8AAAAADXdYD/7wto995pyPAAAAAA13WA/+8LaPfeacjwAAAAAcWJePz0l7j2wDmM8sFmZOmpOYj+dbNU9RQBBPAAAAABqTmI/nWzVPUUAQTwAAAAARPpfP4eZ5j0iG0Y8IfDQOdZsWD8mCRY+OU7WO9SGyDrJblY/3cghPqRSVjuqW5E6tYJXP+t6GD76LPU7m2zoOlB4Vj+srhs+X0cEPHPnCjvUxVU/VIsjPhdnhTu1Epk6OZ5UP1hGKD4iUH07EMClOon/Wz8goPQ9lBydPIOOAzv4JGY/iWm6PaJ1IzwAAAAAb8VtP+vzgz3yCd47AAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAD6UzY/295PPuGXMD0SrSo9HckuP4y1ST7a7bs9snjpPJYrGD/JBEY+W1gBPhDprz3XWDc/yf1wPglWBD3kSoQ8dA8GP5xLgT6OhAE+4kzHPcAZID8URS8+UsktPnQqCj1QGSI/3ImOPizhsD1mnAU7UBkiP9yJjj4s4bA9ZpwFO1fG/j662u0+hn8aPSv07zg9uSY/rlyrPuGUBzxzDL07PbkmP65cqz7hlAc8cwy9O8ChAz9ZB/c+jScOO9fVmDrqshc/fAexPtt1SD2FflA86rIXP3wHsT7bdUg9hX5QPOnqET9/l9c+7mQDPGMRbzpSGEY/KwdlPnvjJTsAAAAAr4NAP9TVfT60d9s4AAAAAGKnZT9UxaE9df5DPHX+QzxUVWo/iV5qPVGY4DwAAAAAgbd7P+EPiTwAAAAAAAAAALWzfj9oJaY7AAAAAAAAAABRnn8/1V7DOgAAAAAAAAAAJvZ/P0CkHTkAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAADl8n8/abJROQAAAAAAAAAA4/x1PzCzXTyHgF08YJNFPGVQPT9x2YM+q70cOx6TGDoAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAsMoyP7tlhz5K66Q8+2KLPDGyQD/N2mo+g+OSPAAAAAAihVI/AscCPkLtKj1JkgY8ZChSP/4FiT3hE3k97FlSPWQoUj/+BYk94RN5PexZUj1sADc//mxSPmd3Vj27m9886pc1P+EpJj6SkwA+VLg4Ow4ZET/HYh0+cjcVPpIBCT4HThg/KVRGPg+KLD6cpi89Xw0bP4qImD7W/rk9CUG3OzgYHT99l68+i4sFPXnUMDw4GB0/fZevPouLBT151DA8DIsRP1uNwz6Kawk93/GCPP3sIj+ToTk+xgItPiF7WjywyjI/u2WHPkrrpDz7Yos8MbJAP83aaj6D45I8AAAAACKFUj8CxwI+Qu0qPUmSBjxkKFI//gWJPeETeT3sWVI9ZChSP/4FiT3hE3k97FlSPWwANz/+bFI+Z3dWPbub3zzqlzU/4SkmPpKTAD5UuDg7DhkRP8diHT5yNxU+kgEJPgdOGD8qVEY+D4osPp6mLz1fDRs/ioiYPtf+uT0HQbc7OBgdP32Xrz6LiwU9edQwPDgYHT99l68+i4sFPXnUMDwMixE/W43DPoprCT3g8YI8/ewiP5KhOT7HAi0+I3taPAAABgAKAAAACgACAAAAAgDHAQAAxwEDANUBBQABANUBAQAEAAUACwAHAAUABwABAAYACABHAAYARwAKAAsADAAJAAsACQAHAA8ADAALAA8ACwANAA0ACwAFAA0ABQAOAA0ADgARAA0AEQAQAA8ADQAQAA8AEAASABUAEwAQABUAEAARABMAFAASABMAEgAQABYAFwAUABYAFAATABgAFgATABgAEwAVABsAFwAWABsAFgAZABkAFgAYABkAGAAaACEAHQAZACEAGQAaAB0AHwAbAB0AGwAZACIAIwAeACIAHgAcACQAIgAcACQAHAAgACUAJwAjACUAIwAiACYAJQAiACYAIgAkACkAKAAlACkAJQAmACgAKgAnACgAJwAlACsAKgAoACsAKAApACsALQAsACsALAAqACoALAAuACoALgAnACwALQAvACwALwAuACcALgAwACcAMAAjAC4ALwAxAC4AMQAwADAAMQA0ADAANAAyACMAMAAyACMAMgAeADMANQA3ADMANwA2AB8AMwA2AB8ANgAbADYAOAAXADYAFwAbADcAOQA4ADcAOAA2ADgAOQA7ADgAOwA6ABcAOAA6ABcAOgAUADoAOwA9ADoAPQA8ABQAOgA8ABQAPAASADwAPgAPADwADwASAD0APwA+AD0APgA8AD4AQAAMAD4ADAAPAD8AQQBAAD8AQAA+AEAAQQBFAEAARQBDAAwAQABDAAwAQwAJAEIARgBHAEIARwAIAEQASABGAEQARgBCAEcARgBJAEcASQBLAEYASABKAEYASgBJAEkASgBOAEkATgBMAEsASQBMAEsATABNAE0ATABPAE0ATwBRAEwATgBQAEwAUABPAE8AUABUAE8AVABSAFEATwBSAFEAUgBTAFMAUgBVAFMAVQBWAFIAVABXAFIAVwBVAFUAWABZAFUAWQBWAFcAWgBYAFcAWABVAFkAWABbAFkAWwBdAFgAWgBcAFgAXABbAFsAXABgAFsAYABeAF0AWwBeAF0AXgBfAF8AXgBhAF8AYQBjAF4AYABiAF4AYgBhAGEAYgBmAGEAZgBkAGMAYQBkAGMAZABlAGUAZABnAGUAZwBoAGQAZgBpAGQAaQBnAGcAagBrAGcAawBoAGkAbABqAGkAagBnAGsAagBuAGsAbgBwAGoAbAByAGoAcgBuAG0AcwB0AG0AdABvAHEAdQBzAHEAcwBtAHQAcwB2AHQAdgB3AHMAdQB4AHMAeAB2AHkAewB3AHkAdwB2AHoAeQB2AHoAdgB4AH4AfAB5AH4AeQB6AHwAfQB7AHwAewB5AH8AhAB9AH8AfQB8AIEAfwB8AIEAfAB+AIYAgACDAIYAgwCIAIkAhQCAAIkAgACGAIYAiACMAIYAjACKAIkAhgCKAIkAigCNAIoAjACRAIoAkQCOAI0AigCOAI0AjgCPAI4AkgCVAI4AlQCPAJEAlACSAJEAkgCOAJIAlACZAJIAmQCWAJUAkgCWAJUAlgCXAJYAmgCdAJYAnQCXAJkAmwCaAJkAmgCWAJoAmwCfAJoAnwCeAJ0AmgCeAJ0AngChAJ4AnwCkAJ4ApACiAKEAngCiAKEAogCjAKMAogClAKMApQCmAKIApACnAKIApwClAKgAqgCmAKgApgClAKkAqAClAKkApQCnAK0AqwCoAK0AqACpAKsArACqAKsAqgCoAK4ArwCsAK4ArACrALAArgCrALAAqwCtALMArwCuALMArgCxALEArgCwALEAsACyALYAtACxALYAsQCyALQAtQCzALQAswCxALkAtQC0ALkAtAC3ALcAtAC2ALcAtgC4ALwAugC3ALwAtwC4ALoAuwC5ALoAuQC3AL0AvgC7AL0AuwC6AL8AvQC6AL8AugC8AL0AwQDDAL0AwwC+AL8AxQDBAL8AwQC9AMIAwADGAMIAxgDIAMAAxADHAMAAxwDGAMYAxwDLAMYAywDJAMgAxgDJAMgAyQDKAMoAyQDMAMoAzADNAMkAywDOAMkAzgDMAM8A0QDNAM8AzQDMANAAzwDMANAAzADOANQA0gDPANQAzwDQANIA0wDRANIA0QDPANUA1wDTANUA0wDSANYA1QDSANYA0gDUANoA2ADVANoA1QDWANgA2QDXANgA1wDVANsA3QDZANsA2QDYANwA2wDYANwA2ADaAOAA3gDbAOAA2wDcAN4A3wDdAN4A3QDbAOEA4wDfAOEA3wDeAOIA4QDeAOIA3gDgAOgA5ADhAOgA4QDiAOQA5gDjAOQA4wDhAOoA6wDnAOoA5wDlAOwA6gDlAOwA5QDpAO8A6wDqAO8A6gDtAO0A6gDsAO0A7ADuAPAA7QDuAPAA7gDxAPIA7wDtAPIA7QDwAPUA8wDwAPUA8ADxAPMA9ADyAPMA8gDwAPgA9ADzAPgA8wD2APYA8wD1APYA9QD3APsA+QD2APsA9gD3APkA+gD4APkA+AD2AP4A+gD5AP4A+QD8APwA+QD7APwA+wD9AAEB/wD8AAEB/AD9AP8AAAH+AP8A/gD8AAQBAAH/AAQB/wACAQIB/wABAQIBAQEDAQMBBgEFAQMBBQECAQIBBQEHAQIBBwEEAQUBBgEKAQUBCgEIAQcBBQEIAQcBCAEJAQkBCAELAQkBCwEPAQgBCgENAQgBDQELARMBEQEMARMBDAEOAREBEgEQAREBEAEMARYBEgERARYBEQEUARQBEQETARQBEwEVARcBFAEVARcBFQEYARkBFgEUARkBFAEXARoBFwEYARoBGAEbARwBGQEXARwBFwEaARoBGwEfARoBHwEdARwBGgEdARwBHQEeAR4BHQEgAR4BIAEhAR0BHwEiAR0BIgEgASABIwEkASABJAEhASIBJQEjASIBIwEgASYBJwEkASYBJAEjASgBJgEjASgBIwElAScBJgEpAScBKQErASYBKAEqASYBKgEpASoBLQEsASoBLAEpASkBLAEuASkBLgErASwBLQExASwBMQEvAS4BLAEvAS4BLwEwATQBMAEvATQBLwEyATIBLwExATIBMQEzAToBNgEyAToBMgEzATYBOAE0ATYBNAEyATsBPAE3ATsBNwE1AT0BOwE1AT0BNQE5AT4BQAE8AT4BPAE7AT8BPgE7AT8BOwE9AUIBQQE+AUIBPgE/AUEBQwFAAUEBQAE+ASsAQwFBASsAQQFCAUABQwFEAUABRAFFASsARAFDASsASAFGASsARgFEAUUBRAFGAUUBRgFHAUcBRgFIAUcBSAFJAUkBSAEpAEkBKQAmACsAKQBIAUsBRwFJAUsBSQFKAUkBJgAkAEkBJABKAUsBSgFMAUsBTAFPAUoBJAAgAEoBIABMAU0BIQAaAE0BGgBOAVABTQFOAVABTgFRAVUBSwFPAVUBTwFSAVMBUAFRAVMBUQFUATgBUwFUATgBVAE0ATwBVQFSATwBUgE3AVUBRQFHAVUBRwFLAUABRQFVAUABVQE8AVYBVAFRAVYBUQFXAVQBVgEwAVQBMAE0AVYBVwFaAVYBWgFYAVgBLgEwAVgBMAFWAScBWAFZAScBWQEkAS4BWAEnAS4BJwErAVgBWgFcAVgBXAFZAVoBVwGABVoBgAVbAVwBWgFbAVwBWwFdAV4BXAFdAV4BXQFgAV8BWQFcAV8BXAFeAWMBXwFeAWMBXgFhAWEBXgFgAWEBYAFiAWQBYQFiAWQBYgFlAWMBYQFkAWMBZAFmAWcBZAFlAWcBZQFqAWgBZgFkAWgBZAFnAWcBawFtAWcBbQFoAWwBawFnAWwBZwFqAWwBcAFvAWwBbwFrAWsBbwFzAWsBcwFtAXABzQFyAXABcgFvAW8BcgF4AW8BeAFzAX0BeQF0AX0BdAF3AXkBewFuAXkBbgF0AXoBbwR7AXoBewF5AX8BegF5AX8BeQF9AYEBfgF8AYEBfAGAAYABfAF2AYABdgGDAYUBgQGAAYUBgAGEAYQBgAGDAYQBgwGHAYkBhQGEAYkBhAGIAYgBhAGHAYgBhwGLAY0BiQGIAY0BiAGMAYwBiAGLAYwBiwGPAZEBjQGMAZEBjAGQAZABjAGPAZABjwGTAZUBkQGQAZUBkAGUAZQBkAGTAZQBkwGXAZwBlQGUAZwBlAGYAZgBlAGXAZgBlwGbAZ0BnAGYAZ0BmAGZAZkBmAGbAZkBmwHHBKABlQGcAaABnAGeAZ8BngGcAZ8BnAGdAZ4BnwGjAZ4BowGhAaABngGhAaABoQGiAaIBoQGlAaIBpQGnAaMBqQGlAaMBpQGhAaYBpAGqAaYBqgGrAagBrAGqAagBqgGkAasBqgGtAasBrQGuAawBrwGtAawBrQGqAbIBrgGtAbIBrQGwAa8BsQGwAa8BsAGtAbQBswGwAbQBsAGxAbMBtQGyAbMBsgGwAcYEmgGzAcYEswG0AZoBlgG1AZoBtQGzAZYBkgG2AZYBtgG1AbUBtgG4AbUBuAGyAZIBjgG3AZIBtwG2AbYBtwG5AbYBuQG4AbkBuwG6AbkBugG4AbgBugGuAbgBrgGyAbwBvgGuAbwBrgG6Ab8BvAG6Ab8BugG7Ab0B3gG+Ab0BvgG8AcABvQG8AcABvAG/AcIBwAG/AcIBvwHBAcEBvwG7AcEBuwHDAcUBwgHBAcUBwQHEAcQBwQHDAcQBwwHGAQIAxQHEAQIAxAHHAccBxAHGAccBxgHIAQMAxwHIAQMAyAHJAcsByAHGAcsBxgHOAckByAHLAckBywHMAc8BcQHMAc8BzAHLAdABzwHLAdABywHOAYIBdQFxAYIBcQHPAYYBggHPAYYBzwHQAYoBhgHQAYoB0AHRAdEB0AHOAdEBzgHSAY4BigHRAY4B0QG3AbcB0QHSAbcB0gG5Ac4BxgHDAc4BwwHSAdIBwwG7AdIBuwG5AXAB0wHKAXABygHNAdMB1QEEANMBBADKAdQB1gHVAdQB1QHTAdQB0wFwAdQBcAFsAdYBDgAFANYBBQDVAREADgDWAREA1gHXAdcB1gHUAdcB1AHYAREA1wFgAREAYAFdAWAB1wHYAWAB2AFiAdgBagFlAdgBZQFiAdQBbAFqAdQBagHYAQIACgDZAQIA2QHFAdkB2gHCAdkBwgHFAUsATQDaAUsA2gHZAQoARwBLAAoASwDZAdoBTQBRANoBUQDbAcIB2gHbAcIB2wHAAdsB3AG9AdsBvQHAAVEAUwDcAVEA3AHbAdwBUwBWANwBVgDdAb0B3AHdAb0B3QHeAVkA3wHdAVkA3QFWAN8B4AHeAd8B3gHdAV0A4QHfAV0A3wFZAOEB4gHgAeEB4AHfAeMB5AHiAeMB4gHhAV8A4wHhAV8A4QFdAGMA5QHjAWMA4wFfAOUB5gHkAeUB5AHjAecB6AHmAecB5gHlAWUA5wHlAWUA5QFjAOkB5wFlAOkBZQBoAOoB6AHnAeoB5wHpAY0N7QHrAY0N6wGADYAN6wHuAYAN7gGBDe0BDALsAe0B7AHrAesB7AHvAesB7wHuAfIB8AHuAfIB7gHvAYIN8QHqAYIN6gGBDYQN9gHxAYQN8QGCDfgB9AHwAfgB8AHyAYUN+gH1AYUN9QGDDfsB+QHzAfsB8wH3AYYN/QH6AYYN+gGFDf4B/AH5Af4B+QH7AYYN/AH/AYYN/wGHDfwB/gEBAvwBAQL/AYcN/wECAocNAgKIDf8BAQIDAv8BAwICAokNdAB3AIkNdwCIDQUCBAICAgUCAgIDAgYCBAIFAgYCBQIIAooNiQ0EAooNBAIGAgsCCgIHAgsCBwIJAowNawBwAIwNcACLDe0BCgILAu0BCwIMAo0NjA0KAo0NCgLtAQwCCwINAgwCDQIPAgkCDgINAgkCDQILAhACDwINAhACDQIOAhMCEQIPAhMCDwIQAg8CEQLsAQ8C7AEMAhQCEgIRAhQCEQITAhECEgLvAREC7wHsARcCFQITAhcCEwIQAhUCFgIUAhUCFAITAhgCGQIWAhgCFgIVAhoCGAIVAhoCFQIXAhsCHgIZAhsCGQIYAh0CGwIYAh0CGAIaAhwCHwIeAhwCHgIbAjoCHAIbAjoCGwIdAiECIAIeAiECHgIfAiACIgIZAiACGQIeAiYCJAIgAiYCIAIhAiQC+AEiAiQCIgIgAigCJwIjAigCIwIlAicC+wH3AScC9wEjAioCKQInAioCJwIoAikC/gH7ASkC+wEnAikCKwIBAikCAQL+ASoCLAIrAioCKwIpAisCLQIDAisCAwIBAiwCLgItAiwCLQIrAi4CMAIvAi4CLwItAi0CLwIFAi0CBQIDAjACMwIxAjACMQIvAi8CMQIIAi8CCAIFAjQCNgI1AjQCNQIyAjICNQI3AjICNwIJAjYCOgIdAjYCHQI1AjUCHQIaAjUCGgI3AgkCNwI4AgkCOAIOAhoCFwI4AhoCOAI3AhACDgI4AhACOAIXAjkCOwI6AjkCOgI2Ap4COQI2Ap4CNgI0AjsCPQIcAjsCHAI6AjwCPwI9AjwCPQI7AqcCPAI7AqcCOwI5Aj0CPgIfAj0CHwIcAj8CQAI+Aj8CPgI9AkECPwI8AkECPAJDAkUCQAI/AkUCPwJBAkcCQgJEAkcCRAJJAkgCRgJCAkgCQgJHAkwCSAJHAkwCRwJKAkoCRwJJAkoCSQJLAk8CTQJKAk8CSgJLAk0CTgJMAk0CTAJKAk4CTQJQAk4CUAJSAk0CTwJRAk0CUQJQAlACUQJVAlACVQJTAlICUAJTAlICUwJUAlQCUwJWAlQCVgJXAlMCVQJYAlMCWAJWAlYCWQJbAlYCWwJXAlgCWgJZAlgCWQJWAlkCWgJdAlkCXQJcAlsCWQJcAlsCXAJeAlwCXQJhAlwCYQJfAl4CXAJfAl4CXwJjAmUCYAJiAmUCYgJmAmcCZAJgAmcCYAJlAmkCaAJlAmkCZQJmAmgCagJnAmgCZwJlAm0CawJoAm0CaAJpAmsCbAJqAmsCagJoAm4CcAJsAm4CbAJrAm8CbgJrAm8CawJtAm4CbwJzAm4CcwJxAnACbgJxAnACcQJyAnECdAJ2AnECdgJyAnMCdQJ0AnMCdAJxAnUCeAJ3AnUCdwJ0AnQCdwJ5AnQCeQJ2AncCeAJ7AncCewJ6AnkCdwJ6AnkCegJ8AnoCewKBAnoCgQJ9AnwCegJ9AnwCfQJ/AoIChAJ/AoICfwJ9AoYCggJ9AoYCfQKBAowChAKCAowCggKIAogCggKGAogChgKKAo8CjQKHAo8ChwKJAo0CjgKLAo0CiwKHApUCjgKNApUCjQKRApECjQKPApECjwKTApACkgKXApAClwKWApQCkAKWApQClgKYApYClwKbApYCmwKZApgClgKZApgCmQKaApkCLAIqApkCKgKaApsCLgIsApsCLAKZApcCnwKcApcCnAKbApsCnAIwApsCMAIuApwCnQIzApwCMwIwAp8CogKdAp8CnQKcAqACpAKiAqACogKfApICoAKfApICnwKXAqMCpwI5AqMCOQKeAqUCqAKnAqUCpwKjAqgCQwI8AqgCPAKnAqwCqgKpAqwCqQKmAqoCSQJEAqoCRAKpAqsCSwJJAqsCSQKqAq0CqwKqAq0CqgKsAq8CrQKsAq8CrAKuAq4CrAKmAq4CpgKwArMCrwKuArMCrgKxArECrgKwArECsAKAArYCtAKyArYCsgK1AnsCtQKyAnsCsgKBAnMCtgK1AnMCtQJ1AngCdQK1AngCtQJ7ArcCuQK0ArcCtAK2Am8CtwK2Am8CtgJzArkCtwK6ArkCugK9AroCtwJvAroCbwJtAr0CugK7Ar0CuwK/AmkCuwK6AmkCugJtAr4CwQLAAr4CwAK8AsACwwK4AsACuAK8AsICwALBAsICwQLJAsQCwwLAAsQCwALCAsMCxAKtAsMCrQKvArgCwwKvArgCrwKzAsQCxQKrAsQCqwKtAsYCxQLEAsYCxALCAsUCTwJLAsUCSwKrAk8CxQLGAk8CxgJRAlECxgLHAlECxwJVAskCxwLGAskCxgLCAlUCxwLIAlUCyAJYAsoCyALHAsoCxwLJAskCwQLLAskCywLKAsgCygLMAsgCzALNAsoCywLOAsoCzgLMAs0CzALOAs0CzgLRAssC1QLPAssCzwLOAtECzgLPAtECzwLSAloCzQLRAloC0QJdAl0C0QLSAl0C0gJhAtYC1ALTAtYC0wLQAtQCZgJiAtQCYgLTAmYC1AK7AmYCuwJpAr8CuwLUAr8C1ALWAtUCywLBAtUCwQK+AlgCyALNAlgCzQJaArAC1wKFArAChQKAAqYCoQLXAqYC1wKwAqECkwKPAqECjwLXAtcCjwKJAtcCiQKFApgCmgLYApgC2ALbApoCKgIoApoCKALYAtgCKAIlAtgCJQLZAtsC2ALZAtsC2QLcApQCmALbApQC2wLeAt4C2wLcAt4C3ALgAt8C4gLkAt8C5ALjApUC3wLjApUC4wKOAuMC5AJ+AuMCfgKDAo4C4wKDAo4CgwKLAukC5QLkAukC5ALiAuUC5wJ+AuUCfgLkAuYC/gLnAuYC5wLlAuoC5gLlAuoC5QLpAuwC6gLpAuwC6QLrAusC6QLiAusC4gLuAkwC7ALrAkwC6wJIAkgC6wLuAkgC7gJGAu0C7wJAAu0CQAJFAuEC3QLvAuEC7wLtAt0C2gLwAt0C8ALvAu8C8AI+Au8CPgJAAtoCJgIhAtoCIQLwAvACIQIfAvACHwI+AvEC8gLqAvEC6gLsAk4C8QLsAk4C7AJMAvIC8QLzAvIC8wL1AvMC8QJOAvMCTgJSAvUC8wL0AvUC9AL2AlQC9ALzAlQC8wJSAvYC+AL3AvYC9wL1AvcC+wLyAvcC8gL1AvkC9wL4AvkC+AIFA/wC+wL3AvwC9wL5AvsC/AL+AvsC/gLmAvIC+wLmAvIC5gLqAv0CAAMBA/0CAQP/AgIDAAP9AgID/QL6AgADcAJyAgADcgIBA3ACAAMCA3ACAgNsAmwCAgMDA2wCAwNqAgYDAwMCAwYDAgP6AmoCAwMEA2oCBANnAggDBAMDAwgDAwMGAwUD+AIJAwUDCQMHAwQDCAMLAwQDCwMNAwcDCQMOAwcDDgMKAw4DEAMMAw4DDAMKAwkDEwMPAwkDDwMOAw8DEQMQAw8DEAMOAxEDWwJeAhEDXgIQAxADXgJjAhADYwIMAxIDVwJbAhIDWwIRAxMDEgMRAxMDEQMPA1cCEgP0AlcC9AJUAvYC9AISA/YCEgMTAxMDCQP4AhMD+AL2AmcCBAMNA2cCDQNkAgEDcgJ2AgEDdgIUA/8CAQMUA/8CFAPoAhQDdgJ5AhQDeQJ8AugCFAN8AugCfAJ/AhkCIgIVAxkCFQMWAvgBFgMVA/gBFQMiAhQCFgIVAxQCFQMWAxQCFgMXAxQCFwMSAvgB8gEXA/gBFwMWA+8BEgIXA+8BFwPyARkDGAMAAhkDAAJ3ABgDGgP9ARgD/QEAAh0DGwMYAx0DGAMZAxsDHAMaAxsDGgMYAx4DIAMcAx4DHAMbAx8DHgMbAx8DGwMdA80AIQMeA80AHgMfAyEDIgMgAyEDIAMeAyMDJAMiAyMDIgMhA9EAIwMhA9EAIQPNACUDIwPRACUD0QDTACYDJAMjAyYDIwMlAyUD0wDXACUD1wAnAyYDJQMnAyYDJwMoAygDJwMpAygDKQMqAycD1wDZACcD2QApAykD2QDdACkD3QArAyoDKQMrAyoDKwMsAywDKwMtAywDLQMuAysD3QDfACsD3wAtAy0D3wDjAC0D4wAvAy4DLQMvAy4DLwMwAzADLwMxAzADMQMzAy8D4wDmAC8D5gAxA+sANQMyA+sAMgPnADUDNgM0AzUDNAMyA+8ANwM1A+8ANQPrADcDOAM2AzcDNgM1AzoDOAM3AzoDNwM5AzkDNwPvADkD7wDyADkDOwM8AzkDPAM6A/IA9AA7A/IAOwM5AzsD9AD4ADsD+AA9AzwDOwM9AzwDPQM+Az8DQAM+Az8DPgM9A/oAPwM9A/oAPQP4AEEDPwP6AEED+gD+AEIDQAM/A0IDPwNBAwABQwNBAwABQQP+AEMDRANCA0MDQgNBA0UDQwMAAUUDAAEEAUYDRANDA0YDQwNFA0cDRQMEAUcDBAEHAUgDRgNFA0gDRQNHA0cDSQNLA0cDSwNIAwcBSgNJAwcBSQNHA0kDSgNOA0kDTgNMA0sDSQNMA0sDTANNA08DUQNNA08DTQNMA1ADTwNMA1ADTANOA1MDTwNQA1MDUANVA1cDUQNPA1cDTwNTA1IDVANZA1IDWQNYA1YDUgNYA1YDWANaA1gDWQNeA1gDXgNcA1oDWANcA1oDXANgA1sDXQNiA1sDYgNhA18DWwNhA18DYQNjA2EDYgNlA2EDZQNkA2MDYQNkA2MDZANmA2kDZwNkA2kDZANlA2cDaANmA2cDZgNkA2wDaANnA2wDZwNqA2oDZwNpA2oDaQNrA28DbQNqA28DagNrA20DbgNsA20DbANqA3QDbgNtA3QDbQNwA3ADbQNvA3ADbwNyA3gDdgNxA3gDcQNzA3YDdwN1A3YDdQNxA3oDdwN2A3oDdgN5A3kDdgN4A3kDeAMQARIBewN5AxIBeQMQAXsDfAN6A3sDegN5A30DewMSAX0DEgEWAX4DfAN7A34DewN9A30DFgEZAX0DGQF/A34DfQN/A34DfwOAA38DGQEcAX8DHAGBA4ADfwOBA4ADgQOCA4QDgwOBA4QDgQMcAYMDhQOCA4MDggOBA4gDhgODA4gDgwOEA4YDhwOFA4YDhQODA4YDigOOA4YDjgOHA4gDjAOKA4gDigOGA4kDiwORA4kDkQOPA40DiQOPA40DjwOQA48DkgOUA48DlAOQA5EDkwOSA5EDkgOPA5IDkwOXA5IDlwOVA5QDkgOVA5QDlQOWA5UDmAOaA5UDmgOWA5cDmQOYA5cDmAOVA5sDmgOYA5sDmAOZA5YDmgOcA5YDnAOdA5sDngOcA5sDnAOaA58DnQOcA58DnAOeA5sDogOgA5sDoAOeA58DngOgA58DoAOhA6MDoQOgA6MDoAOiA5sDpgOkA5sDpAOiA6MDogOkA6MDpAOlA6cDpQOkA6cDpAOmA6cDpgOoA6cDqAOpA5sDqAOmA5sDrAOqA5sDqgOoA6kDqAOqA6kDqgOrA60DqwOqA60DqgOsA5sDrgOsA60DrAOuA60DrgOvA5sDmQOwA5sDsAOuA68DrgOwA68DsAOxA5cDsQOwA5cDsAOZA7IDtAOvA7IDrwOxA5MDsgOxA5MDsQOXA7MDtQO0A7MDtAOyA5EDswOyA5EDsgOTA7cDtgO0A7cDtAO1A7QDtgOtA7QDrQOvA7YDuAOrA7YDqwOtA7cDuQO4A7cDuAO2A7kDuwO6A7kDugO4A7gDugOpA7gDqQOrA7oDvAOnA7oDpwOpA70DvAO6A70DugO7A70DvwO+A70DvgO8A7wDvgOlA7wDpQOnA78DwQPAA78DwAO+A74DwAOjA74DowOlA8ADwgOhA8ADoQOjA8EDwwPCA8EDwgPAA8MDxQPEA8MDxAPCA8IDxAOfA8IDnwOhA8QDxgOdA8QDnQOfA8UDxwPGA8UDxgPEA8cDkAOUA8cDlAPGA8YDlAOWA8YDlgOdA8gDjQOQA8gDkAPHA8sDyAPHA8sDxwPFA8oDhwOOA8oDjgPJA80DygPJA80DyQPMA84DywPFA84DxQPDA9ADzQPMA9ADzAPPA9MD0APPA9MDzwPSA9EDzgPDA9EDwwPBA9QD0QPBA9QDwQO/A9YD0wPSA9YD0gPVA9kD1gPVA9kD1QPYA9cD1AO/A9cDvwO9A9kD2APbA9kD2wPcA9oD1wO9A9oDvQO7A90D2gO7A90DuwO5A98D3APbA98D2wPeA+ID3wPeA+ID3gPhA+AD3QO5A+ADuQO3A+MD4AO3A+MDtwO1A+ID4QPkA+ID5APlA+gD5QPkA+gD5APnA+YD4wO1A+YDtQOzA4gD6APnA4gD5wOMA4sD5gOzA4sDswORA+oD5QPoA+oD6APpA+kD6AOIA+kDiAOEAyEB6gPpAyEB6QMeAR4B6QOEAx4BhAMcAeID5QPqA+ID6gPrA+sD6gMhAesDIQHsA+0D3wPiA+0D4gPrA+8D7QPrA+8D6wPsA+4D3APfA+4D3wPtA/AD7gPtA/AD7QPvA1kBXwHvA1kB7wPsA+8DXwFjAe8DYwHwA/ID7gPwA/ID8APxA/ADYwFmAfADZgHxA/MD9QPyA/MD8gPxA2YB9gPzA2YB8wPxA/MD9gP3A/MD9wP0A/QDDgT1A/QD9QPzA2gB+AP2A2gB9gNmAfYD+AP6A/YD+gP3A2kB/QP8A2kB/AP5A/kD/AP/A/kD/wP7A/0DbQT+A/0D/gP8A/wD/gMABPwDAAT/AwAEAgQBBAAEAQT/A/8DAQQFBP8DBQT7AwIEXQQDBAIEAwQBBAEEAwQHBAEEBwQFBPcD+gMEBPcDBAQIBAgEBAQGBAgEBgQJBPcDCAQKBPcDCgT0AwoECAQJBAoECQQLBAoEDAQOBAoEDgT0AwsEDQQMBAsEDAQKBA0EzQPQAw0E0AMMBAwE0APTAwwE0wMOBA4E0wPWAw4E1gP1A8oDzQMNBMoDDQQPBA8EDQQLBA8ECwQQBIcDygMPBIcDDwSFA4UDDwQQBIUDEASCAwkEEQQQBAkEEAQLBBAEEQSAAxAEgAOCAwkEBgQSBAkEEgQRBBEEEgR+AxEEfgOAAxMEEgQGBBMEBgQUBHwDfgMSBHwDEgQTBHoDfAMTBHoDEwQVBBUEEwQUBBUEFAQWBHoDFQQXBHoDFwR3AxUEFgQYBBUEGAQXBBcEGAQcBBcEHAQaBHcDFwQaBHcDGgR1A3QDGQQdBHQDHQRuAxkEGwQeBBkEHgQdBB0EHgQgBB0EIAQfBG4DHQQfBG4DHwRsA2wDHwQhBGwDIQRoAx8EIAQiBB8EIgQhBCEEIgQkBCEEJAQjBGgDIQQjBGgDIwRmAyUEYwNmAyUEZgMjBCgEJQQjBCgEIwQkBCYEXwNjAyYEYwMlBCkEJgQlBCkEJQQoBCsEKAQkBCsEJAQuBCwEKQQoBCwEKAQrBDAELAQrBDAEKwQvBC8EKwQuBC8ELgQyBDQEMAQvBDQELwQzBDMELwQyBDMEMgQ2BDkENAQzBDkEMwQ3BDcEMwQ2BDcENgQ4BDsENwQ4BDsEOAQ+BDwEOQQ3BDwENwQ7BBsEPAQ7BBsEOwQeBB4EOwQ+BB4EPgQgBD4EOAQ2BD4ENgQ/BCAEPgQ/BCAEPwQiBD8ENgQyBD8EMgQuBCIEPwQuBCIELgQkBEAEQQQ6BEAEOgQ9BBgEQAQ9BBgEPQQcBBQEBgRBBBQEQQRABBYEFARABBYEQAQYBEEEQgQ1BEEENQQ6BAYEQwRCBAYEQgRBBEMERgRFBEMERQRCBEIERQQxBEIEMQQ1BEUESAQtBEUELQQxBEYESQRIBEYESARFBEkETARLBEkESwRIBEgESwQqBEgEKgQtBEsETgQnBEsEJwQqBEwETwROBEwETgRLBE8EVgNaA08EWgNOBE4EWgNgA04EYAMnBFEDVwNQBFEDUARRBFEEUARNBFEETQRSBE0DUQNRBE0DUQRTBFMEUQRSBFMEUgRUBFUESwNNA1UETQNTBFcEVQRTBFcEUwRUBFYESANLA1YESwNVBFgEVgRVBFgEVQRXBFoEWARXBFoEVwRZBFkEVwRUBFkEVARbBF0EWgRZBF0EWQRcBFwEWQRbBFwEWwReBAMEXQRcBAMEXARfBF8EXAReBF8EXgRgBAcEAwRfBAcEXwREBEQEXwRgBEQEYARHBFsEYQRgBFsEYAReBGEESgRHBGEERwRgBFIETQRKBFIESgRhBFQEUgRhBFQEYQRbBGIEXQQCBGIEAgRlBGIEYwRaBGIEWgRdBGQEngRjBGQEYwRiBGcEZARiBGcEYgRlBGUEAgQABGUEAARmBGgEZwRlBGgEZQRmBEADQgNkBEADZARnBD4DQANnBD4DZwRoBGkEPAM+A2kEPgNoBGsEaQRoBGsEaARmBGoEOgM8A2oEPANpBGwEagRpBGwEaQRrBP4DawRmBP4DZgQABGwEawT+A2wE/gNtBHAEagRsBHAEbARuBG0EbwRuBG0EbgRsBG4EbwR6AW4EegFxBHQEcARuBHQEbgRxBHYEdARxBHYEcQRzBHEEegF/AXEEfwFzBDYDOANwBDYDcAR0BDQDNgN0BDQDdAR2BHUEdwQwA3UEMAMzA3IEeAR3BHIEdwR1BHgEegR5BHgEeQR3BHcEeQQuA3cELgMwA3oEfAR7BHoEewR5BHkEewQsA3kELAMuA3wEfgR9BHwEfQR7BHsEfQQqA3sEKgMsA34EgAR/BH4EfwR9BH0EfwQoA30EKAMqA4AEoAGBBIAEgQR/BH8EgQQmA38EJgMoA4EEggSFBIEEhQQmA6ABogGCBKABggSBBKIBpwGEBKIBhASCBIIEhASHBIIEhwSFBIcEigSIBIcEiASFBIUEiAQkA4UEJAMmA4oEjQSLBIoEiwSIBIgEiwQiA4gEIgMkA40EjwSOBI0EjgSLBIsEjgQgA4sEIAMiA48EkgSRBI8EkQSOBI4EkQQcA44EHAMgA5IElQSUBJIElASRBJEElAQaA5EEGgMcA5UE9QH6AZUE+gGUBJQE+gH9AZQE/QEaA5cE8QH2AZcE9gGWBJgElwSWBJgElgSTBOgB6gHxAegB8QGXBOYB6AGXBOYBlwSYBOQB5gGYBOQBmASZBJkEmASTBJkEkwSQBOIB5AGZBOIBmQSaBJoEmQSQBJoEkASMBOAB4gGaBOABmgSbBJsEmgSMBJsEjASJBN4B4AGbBN4BmwScBJwEmwSJBJwEiQSGBJ0EvgHeAZ0E3gGcBIMEnQScBIMEnASGBKsBrgG+AasBvgGdBKYBqwGdBKYBnQSDBIAEkQGVAYAElQGgAY0BkQGABI0BgAR+BHwEiQGNAXwEjQF+BIUBiQF8BIUBfAR6BHgEgQGFAXgEhQF6BHIEfgGBAXIEgQF4BDoDagRwBDoDcAQ4A20E/QN7AW0EewFvBEIDRAOeBEIDngRkBEQDRgOfBEQDnwSeBJ4EnwSgBJ4EoARjBEgDVgSfBEgDnwRGA58EVgRYBJ8EWASgBFoEYwSgBFoEoARYBHsB/QNpAXsBaQFuAfUD1gPZA/UD2QPyA9kD3APuA9kD7gPyAyQBWQHsAyQB7AMhAaMEEAF4A6MEeAOhBKEEeANzA6EEcwOlBFQDowShBFQDoQRZA1kDoQSlBFkDpQReA10DpASmBF0DpgRiA6QEcgNvA6QEbwOmBKYEbwNrA6YEawNpA2IDpgRpA2IDaQNlA6cEogRVA6cEVQNQAwkBDwGiBAkBogSnBAcBCQGnBAcBpwRKA0oDpwRQA0oDUANOA6gEygDNAKgEzQAfA6oEqAQfA6oEHwMdA6kEyADKAKkEygCoBKsEqQSoBKsEqASqBKwErQSrBKwEqwSqBBkDrASqBBkDqgQdA3sAfQCtBHsArQSsBHcAewCsBHcArAQZA30AhACuBH0ArgStBK0ErgSxBK0EsQSrBIUAiQCwBIUAsASvBK8EsASzBK8EswSyBLMEtgS1BLMEtQSyBLEEtASpBLEEqQSrBLYEvgDDALYEwwC1BLQEwgDIALQEyACpBL4AtgS3BL4AtwS7ALYEswS4BLYEuAS3BLcEuAS6BLcEugS5BLsAtwS5BLsAuQS5ALkEugS8BLkEvAS7BLkAuQS7BLkAuwS1ALsEvAS+BLsEvgS9BLUAuwS9BLUAvQSzAL0EvgTABL0EwAS/BLMAvQS/BLMAvwSvAL8EwASmAL8EpgCqAK8AvwSqAK8AqgCsAMAEwQSjAMAEowCmAL4EwgTBBL4EwQTABMIElwCdAMIEnQDBBMEEnQChAMEEoQCjAMMElQCXAMMElwDCBLwEwwTCBLwEwgS+BMQEjwCVAMQElQDDBLoExATDBLoEwwS8BMUEjQCPAMUEjwDEBLgExQTEBLgExAS6BLAEiQCNALAEjQDFBLMEsATFBLMExQS4BMsExgS0AcsEtAHIBMgEtAGxAcgEsQHKBM0EywTIBM0EyATJBMkEyATKBMkEygTkBM8E0QTHBM8ExwTMBNAEzwTMBNAEzATOBNQE0gTPBNQEzwTQBNIE0wTRBNIE0QTPBNIE1QTXBNIE1wTTBNQE1gTVBNQE1QTSBNUE1gTZBNUE2QTYBNcE1QTYBNcE2ATaBNkE3gTcBNkE3ATYBNgE3ASpAdgEqQHaBNsE3QTgBNsE4ATfBKgB2wTfBKgB3wSsAeAE4gThBOAE4QTfBN8E4QSvAd8ErwGsAeEEygSxAeEEsQGvAeIE5ATKBOIEygThBOAEZQXjBOAE4wTiBOQE4gTjBOQE4wTlBOgEyQTkBOgE5ATlBOsE6ATlBOsE5QTmBOMEXgXmBOME5gTlBOkEzQTJBOkEyQToBO0E6QToBO0E6ATrBPEE7wTsBPEE7ATwBPAE7ATnBPAE5wTyBPEE8ATzBPEE8wT2BPAE8gT0BPAE9ATzBPME9AQYBfMEGAX1BPYE8wT1BPYE9QT3BPkE8QT2BPkE9gT4BPgE9gT3BPgE9wT6BP0E+QT4BP0E+AT7BPsE+AT6BPsE+gT8BP4E+wT8BP4E/AQABf8E/QT7BP8E+wT+BAIF/wT+BAIF/gQBBQEF/gQABQEFAAUDBQUFAgUBBQUFAQUEBQQFAQUDBQQFAwUGBQUFBAUHBQUFBwUKBQQFBgUIBQQFCAUHBQcFCAUfBQcFHwUJBQoFBwUJBQoFCQULBQwFCgULBQwFCwUOBQ0FBQUKBQ0FCgUMBRAFDQUMBRAFDAUPBQ8FDAUOBQ8FDgURBRAFDwUSBRAFEgXyBBIFDwURBRIFEQUTBfQE8gQSBfQEEgUUBRQFEgUTBRQFEwUVBRgF9AQUBRgFFAUWBRYFFAUVBRYFFQUXBRkFFgUXBRkFFwUbBRoFGAUWBRoFFgUZBR0FGgUZBR0FGQUcBRwFGQUbBRwFGwUeBQYFHQUcBQYFHAUIBQgFHAUeBQgFHgUfBR4FGwUhBR4FIQUgBR8FHgUgBR8FIAUjBSAFIQUzBSAFMwUiBSMFIAUiBSMFIgUkBSUFIwUkBSUFJAUmBQkFHwUjBQkFIwUlBQsFCQUlBQsFJQUnBScFJQUmBScFJgUoBSkFJwUoBSkFKAUqBQ4FCwUnBQ4FJwUpBREFDgUpBREFKQUrBSsFKQUqBSsFKgUsBREFKwUtBREFLQUTBS0FKwUsBS0FLAUuBRUFEwUtBRUFLQUvBS8FLQUuBS8FLgUwBRcFFQUvBRcFLwUxBTEFLwUwBTEFMAUyBSEFMQUyBSEFMgUzBRcFMQUhBRcFIQUbBTAFNAUzBTAFMwUyBTQFNQUiBTQFIgUzBSwFNAUwBSwFMAUuBSwFKgU1BSwFNQU0BSoFKAUmBSoFJgU1BTUFJgUkBTUFJAUiBR0FBgU3BR0FNwU2BRoFHQU2BRoFNgU5BTYFNwVJBTYFSQU4BTkFNgU4BTkFOAU6BRoFOQU7BRoFOwUYBTsFOQU6BTsFOgU8BT0FOwU8BT0FPAU+BfUEGAU7BfUEOwU9BfcE9QQ9BfcEPQU/BT8FPQU+BT8FPgVABfoE9wQ/BfoEPwVBBUEFPwVABUEFQAVCBfwE+gRBBfwEQQVDBUMFQQVCBUMFQgVEBUUFQwVEBUUFRAVGBfwEQwVFBfwERQUABQMFAAVFBQMFRQVHBUcFRQVGBUcFRgVIBQYFAwVHBQYFRwU3BTcFRwVIBTcFSAVJBUgFRgVLBUgFSwVKBUkFSAVKBUkFSgVNBUoFSwVdBUoFXQVMBU0FSgVMBU0FTAVOBU8FTQVOBU8FTgVQBTgFSQVNBTgFTQVPBToFOAVPBToFTwVRBVEFTwVQBVEFUAVSBToFUQVTBToFUwU8BVMFUQVSBVMFUgVUBVUFUwVUBVUFVAVWBT4FPAVTBT4FUwVVBUAFPgVVBUAFVQVXBVcFVQVWBVcFVgVYBUIFQAVXBUIFVwVZBVkFVwVYBVkFWAVaBUQFQgVZBUQFWQVbBVsFWQVaBVsFWgVcBUYFRAVbBUYFWwVLBUsFWwVcBUsFXAVdBV0FXAVaBV0FWgVMBUwFWgVYBUwFWAVOBU4FWAVWBU4FVgVQBVAFVgVUBVAFVAVSBecEXwUQBecEEAXyBGEFDQUQBWEFEAVfBWUFYAVeBWUFXgXjBGQFBQUNBWQFDQVhBWYFYgVgBWYFYAVlBd0EZgVlBd0EZQXgBGgFagVjBWgFYwVnBdkEaAVnBdkEZwXeBNYEaQVoBdYEaAXZBGkFbAVqBWkFagVoBW0F/wQCBW0FAgVrBWsFAgUFBWsFBQVkBXAFbgVsBXAFbAVpBW0FbwX9BG0F/QT/BP0EbwVyBf0EcgX5BG4FcAVzBW4FcwVxBXEFcwXqBHEF6gTuBPkEcgXvBPkE7wTxBHAF1ATQBHAF0ARzBXMF0ATOBHMFzgTqBNYE1ARwBdYEcAVpBdcE2gR0BdcEdAV2BakBdQV0BakBdAXaBHcFdgV0BXcFdAV1BXgFdgV3BXgFdwV5BdME1wR2BdMEdgV4BdMEeAV6BdMEegXRBHkFewV6BXkFegV4BccE0QR6BccEegV7BccEewV8BccEfAWZAXkFfQV8BXkFfAV7BZ0BmQF8BZ0BfAV9BXcFfgV9BXcFfQV5BX0FfgWfAX0FnwGdAZ8BfgV/BZ8BfwWjAXcFdQV/BXcFfwV+BakBowF/BakBfwV1BVsBFQARAFsBEQBdAYAFGAAVAIAFFQBbAVcBUQFOAVcBTgGABRoAGACABRoAgAVOAUIBPwGDBUIBgwWBBSsAQgGBBSsAgQWCBYEFgwWEBYEFhAWCBT8BPQGFBT8BhQWDBYMFhQWGBYMFhgWEBYUFhwWJBYUFiQWGBT0BOQGHBT0BhwWFBYgFiwWMBYgFjAWKBToBMwGLBToBiwWIBYsFMwExAYsFMQGNBYwFiwWNBYwFjQWOBZAFjgWNBZAFjQWPBY8FjQUxAY8FMQEtASoBkQWPBSoBjwUtAZEFkgWQBZEFkAWPBZMFkQUqAZMFKgEoAZQFkgWRBZQFkQWTBZMFlQWWBZMFlgWUBSgBJQGVBSgBlQWTBZcFmAWWBZcFlgWVBSIBlwWVBSIBlQUlAZoFmAWXBZoFlwWZBZkFlwUiAZkFIgEfAZwFmgWZBZwFmQWbBZsFmQUfAZsFHwEbAZsFGwEYAZsFGAGdBZwFmwWdBZwFnQWeBZ0FGAEVAZ0FFQGfBZ4FnQWfBZ4FnwWgBZ8FFQETAZ8FEwGhBaAFnwWhBaAFoQWiBaEFpAWmBaEFpgWiBRMBDgGkBRMBpAWhBacFowUNAacFDQEKAagFpQWjBagFowWnBaoFqAWnBaoFpwWpBakFpwUKAakFCgEGAQMBqwWpBQMBqQUGAasFrAWqBasFqgWpBasFAwEBAasFAQGtBawFqwWtBawFrQWuBa0FrwWwBa0FsAWuBQEB/QCvBQEBrwWtBa8F/QD7AK8F+wCxBbAFrwWxBbAFsQWyBbEFswW0BbEFtAWyBfsA9wCzBfsAswWxBbMF9wD1ALMF9QC1BbQFswW1BbQFtQW2BbUFtwW4BbUFuAW2BfUA8QC3BfUAtwW1BbcF8QDuALcF7gC5BbgFtwW5BbgFuQW6BbkF7gDsALkF7AC7BboFuQW7BboFuwW8BbsFvgXABbsFwAW8BewA6QC+BewAvgW7Bb0FwQXCBb0FwgW/BegA4gDBBegAwQW9BeIA4ADDBeIAwwXBBcEFwwXEBcEFxAXCBcMFxQXGBcMFxgXEBeAA3ADFBeAAxQXDBdwA2gDHBdwAxwXFBcUFxwXIBcUFyAXGBccFyQXKBccFygXIBdoA1gDJBdoAyQXHBdYA1ADLBdYAywXJBckFywXMBckFzAXKBcsFzQXOBcsFzgXMBdQA0ADNBdQAzQXLBdAAzgDPBdAAzwXNBc0FzwXQBc0F0AXOBdIF0AXPBdIFzwXRBdEFzwXOANEFzgDLANQF0gXRBdQF0QXTBdMF0QXLANMFywDHANUF0wXHANUFxwDEANcF1AXTBdcF0wXVBdkF2gXYBdkF2AXWBb8A2QXWBb8A1gXFANkF2wXcBdkF3AXaBb8AvADbBb8A2wXZBdsF3QXeBdsF3gXcBbwAuADdBbwA3QXbBd0FuAC2AN0FtgDfBd4F3QXfBd4F3wXgBd8F4QXiBd8F4gXgBbYAsgDhBbYA4QXfBeEFsgCwAOEFsADjBeIF4QXjBeIF4wXkBeMF5QXmBeMF5gXkBbAArQDlBbAA5QXjBeUF5wXoBeUF6AXmBa0AqQDnBa0A5wXlBakApwDpBakA6QXnBecF6QXqBecF6gXoBewF6gXpBewF6QXrBesF6QWnAOsFpwCkAO4F7AXrBe4F6wXtBe0F6wWkAO0FpACgAO8F7QWgAO8FoACcAPAF7gXtBfAF7QXvBZgA8QXvBZgA7wWcAPEF8gXwBfEF8AXvBfQF8gXxBfQF8QXzBfMF8QWYAPMFmACTAJAA9QXzBZAA8wWTAPUF9gX0BfUF9AXzBfgF9gX1BfgF9QX3BfcF9QWQAPcFkACLAPkF9wWLAPkFiwCHAPoF+AX3BfoF9wX5BfkFhwCCAPkFggD8BfoF+QX8BfoF/AX+BYEAfgD/BYEA/wX7BfsF/wUABvsFAAb9Bf8FAQYCBv8FAgYABn4AegABBn4AAQb/BXoAeAADBnoAAwYBBgEGAwYEBgEGBAYCBgYGBAYDBgYGAwYFBgUGAwZ4AAUGeAB1AAcGCQYGBgcGBgYFBnEABwYFBnEABQZ1AAwGCgYIBgwGCAYLBgsGCAZyAAsGcgBsAA0GDgYMBg0GDAYLBmkADQYLBmkACwZsABAGDgYNBhAGDQYPBg8GDQZpAA8GaQBmABIGEAYPBhIGDwYRBhEGDwZmABEGZgBiABMGEQZiABMGYgBgABQGEgYRBhQGEQYTBhYGFAYTBhYGEwYVBhUGEwZgABUGYABcABcGFQZcABcGXABaABgGFgYVBhgGFQYXBhkGGgYYBhkGGAYXBlcAGQYXBlcAFwZaABwGGgYZBhwGGQYbBhsGGQZXABsGVwBUAB4GHAYbBh4GGwYdBh0GGwZUAB0GVABQAB8GHQZQAB8GUABOACAGHgYdBiAGHQYfBiIGIAYfBiIGHwYhBiEGHwZOACEGTgBKACMGIQZKACMGSgBIACQGIgYhBiQGIQYjBiUGJwYkBiUGJAYjBkQAJQYjBkQAIwZIACoGKAYmBioGJgYpBikGJgZFACkGRQBBAD8AKwYpBj8AKQZBACsGLAYqBisGKgYpBi0GLgYsBi0GLAYrBj0ALQYrBj0AKwY/ADAGLgYtBjAGLQYvBi8GLQY9AC8GPQA7ADEGLwY7ADEGOwA5ADIGMAYvBjIGLwYxBjcAMwYxBjcAMQY5ADMGNAYyBjMGMgYxBjgGNAYzBjgGMwY2BjYGMwY3ADYGNwA1ADkGNQY0ADkGNAAxADoGNwY1BjoGNQY5BjsGOQYxADsGMQAvADwGOgY5BjwGOQY7Bj4GPAY7Bj4GOwY9Bj0GOwYvAD0GLwAtACsAPgY9BisAPQYtACsAQAY/BisAPwY+Bj8GQQY8Bj8GPAY+BkAGQgZBBkAGQQY/BkIGRAZDBkIGQwZBBkEGQwY6BkEGOgY8BkMGRQY3BkMGNwY6BkQGRwZFBkQGRQZDBkYGSQY0BkYGNAY4BkgGSgZJBkgGSQZGBkkGSgZMBkkGTAZLBjQGSQZLBjQGSwYyBksGTQYwBksGMAYyBkwGTgZNBkwGTQZLBk0GTwYuBk0GLgYwBk4GUAZPBk4GTwZNBlEGTwZQBlEGUAZSBiwGLgZPBiwGTwZRBlEGUgZUBlEGVAZTBiwGUQZTBiwGUwYqBlMGVgYoBlMGKAYqBlQGWAZWBlQGVgZTBlUGWQYkBlUGJAYnBlcGWgZZBlcGWQZVBlkGWwYiBlkGIgYkBloGXAZbBloGWwZZBlsGXAZeBlsGXgZdBiIGWwZdBiIGXQYgBl0GXwYeBl0GHgYgBl4GYAZfBl4GXwZdBl8GYAZiBl8GYgZhBh4GXwZhBh4GYQYcBmEGYwYaBmEGGgYcBmIGZAZjBmIGYwZhBhgGGgZjBhgGYwZlBmUGYwZkBmUGZAZmBhYGGAZlBhYGZQZnBmcGZQZmBmcGZgZoBmkGZwZoBmkGaAZqBhQGFgZnBhQGZwZpBhIGFAZpBhIGaQZrBmsGaQZqBmsGagZsBm0GawZsBm0GbAZuBhAGEgZrBhAGawZtBm8GDgYQBm8GEAZtBnAGbwZtBnAGbQZuBpsNjg1xBpsNcQZzBo4Njw10Bo4NdAZxBnMGcQZyBnMGcgaSBnEGdAZ1BnEGdQZyBngGdQZ0BngGdAZ2BpANjw1wBpANcAZ3BpINkA13BpINdwZ8Bn4GeAZ2Bn4GdgZ6BpMNkQ17BpMNewaABoEGfQZ5BoEGeQZ/BpQNkw2ABpQNgAaDBoQGgQZ/BoQGfwaCBpQNlQ2FBpQNhQaCBoIGhQaHBoIGhwaEBpUNlg2IBpUNiAaFBoUGiAaJBoUGiQaHBpcNlg0EBpcNBAYGBosGiQaIBosGiAaKBowGjgaLBowGiwaKBpgNjAaKBpgNigaXDZEGjwaNBpEGjQaQBpoNmQ0KBpoNCgYMBnMGkgaRBnMGkQaQBpsNcwaQBpsNkAaaDZIGlQaTBpIGkwaRBo8GkQaTBo8GkwaUBpYGlAaTBpYGkwaVBpkGlgaVBpkGlQaXBpUGkgZyBpUGcgaXBpoGmQaXBpoGlwaYBpcGcgZ1BpcGdQaYBp0GlgaZBp0GmQabBpsGmQaaBpsGmgacBp4GmwacBp4GnAafBqAGnQabBqAGmwaeBqEGngafBqEGnwakBqMGoAaeBqMGngahBqIGoQakBqIGpAalBsAGowahBsAGoQaiBqcGpQakBqcGpAamBqYGpAafBqYGnwaoBqwGpwamBqwGpgaqBqoGpgaoBqoGqAZ+Bq4GqwapBq4GqQatBq0GqQZ9Bq0GfQaBBrAGrgatBrAGrQavBq8GrQaBBq8GgQaEBq8GhAaHBq8GhwaxBrAGrwaxBrAGsQayBrEGhwaJBrEGiQazBrIGsQazBrIGswa0BrQGswa1BrQGtQa2BrMGiQaLBrMGiwa1BrYGtQa3BrYGtwa5BrUGiwaOBrUGjga3BroGuAa7BroGuwa8BrgGjwa9BrgGvQa7BrwGuwajBrwGowbABrsGvQagBrsGoAajBo8GlAa+Bo8Gvga9BqAGvQa+BqAGvgadBpYGnQa+BpYGvgaUBr8GvAbABr8GwAbBBiQHuga8BiQHvAa/BsEGwAaiBsEGogbDBsIGwQbDBsIGwwbFBi0HvwbBBi0HwQbCBsMGogalBsMGpQbEBsUGwwbEBsUGxAbGBscGyQbCBscGwgbFBssGxwbFBssGxQbGBs0GzwbKBs0GygbIBs4GzQbIBs4GyAbMBtIG0AbNBtIGzQbOBtAG0QbPBtAGzwbNBtUG0QbQBtUG0AbTBtMG0AbSBtMG0gbUBtQG2AbWBtQG1gbTBtMG1gbXBtMG1wbVBtYG2QbbBtYG2wbXBtgG2gbZBtgG2QbWBtoG3QbcBtoG3AbZBtkG3AbeBtkG3gbbBtwG3QbhBtwG4QbfBt4G3AbfBt4G3wbgBt8G4gbjBt8G4wbgBuEG5AbiBuEG4gbfBuIG5QbnBuIG5wbjBuQG6QblBuQG5QbiBusG7AboBusG6AbmBu0G6wbmBu0G5gbqBu8G7AbrBu8G6wbuBu4G6wbtBu4G7QbwBvMG7wbuBvMG7gbxBvEG7gbwBvEG8AbyBvQG8QbyBvQG8gb2BvUG8wbxBvUG8Qb0BvQG9wb5BvQG+Qb1BvYG+Ab3BvYG9wb0BvcG+Ab8BvcG/Ab6BvkG9wb6BvkG+gb7BvsG+gb9BvsG/Qb+BvoG/Ab/BvoG/wb9Bv0GAAcBB/0GAQf+Bv8GAgcAB/8GAAf9BgAHAwcHBwAHBwcBBwIHBQcDBwIHAwcABwgHAwcFBwgHBQcKBwwHBwcDBwwHAwcIBxIHDgcIBxIHCAcKBw4HEAcMBw4HDAcIBxUHDwcNBxUHDQcTBxMHDQcRBxMHEQcUBxsHFwcTBxsHEwcUBxcHGQcVBxcHFQcTBxYHHAcdBxYHHQcYBxoHHgccBxoHHAcWBxwHHwchBxwHIQcdBx4HIAcfBx4HHwccBx8HIAewBh8HsAayBiEHHweyBiEHsga0Bh0HIQciBx0HIgclByEHtAa2BiEHtgYiByIHtga5BiIHuQYjByUHIgcjByUHIwcoByYHJQcoByYHKAcqBxgHHQclBxgHJQcmBykHJAe/BikHvwYtBysHKQctBysHLQcuBy4HLQfCBi4HwgbJBjIHLAcvBzIHLwcwBzAHLwfKBjAHygbPBjEHMAfPBjEHzwbRBjMHMgcwBzMHMAcxBzUHNAcyBzUHMgczBzQHNgcsBzQHLAcyBzkHNwc0BzkHNAc1BzcHBgc2BzcHNgc0BzwHOwc4BzwHOAc6BwEHBwc4BwEHOAc7B/kG+wY7B/kGOwc8B/4GAQc7B/4GOwf7Bj0HPAc6Bz0HOgc/B/UG+QY8B/UGPAc9Bz8HQwdABz8HQAc9B0AH8wb1BkAH9QY9B0MHRQdBB0MHQQdAB+8G8wZAB+8GQAdBB0QHQgdGB0QHRgdHB0YHQgc+B0YHPgdJB0gHTwdHB0gHRwdGB0oHSAdGB0oHRgdJB0kHNQczB0kHMwdKBz4HOQc1Bz4HNQdJB0oHMwcxB0oHMQdLB0wHSAdKB0wHSgdLB0sHMQfRBksH0QbVBtUG1wZMB9UGTAdLB9cG2wZNB9cGTQdMB08HSAdMB08HTAdNB9sG3gZOB9sGTgdNB1AHTwdNB1AHTQdOB08HUAdRB08HUQdHB04HUwdSB04HUgdQB1AHUgdUB1AHVAdRB1MHVwdUB1MHVAdSB1EHVAdVB1EHVQdbB1cHWAdVB1cHVQdUB+AG4wZXB+AGVwdTB+MG5wZYB+MGWAdXB1wHVgdZB1wHWQdaB1oHWQfoBloH6AbsBuwG7wZBB+wGQQdaB0UHXAdaB0UHWgdBB1sHRAdHB1sHRwdRB94G4AZTB94GUwdOBzYHBgcLBzYHCwddBywHNgddBywHXQcnBycHXQcVBycHFQcZB10HCwcPB10HDwcVBx4HYQdeBx4HXgcgByAHXgeuBiAHrgawBl4HXwerBl4HqwauBmEHYgdfB2EHXwdeBxoHZAdhBxoHYQceB2QHZgdiB2QHYgdhB2UHaQdqB2UHagdoBxsHFAdpBxsHaQdlB2kHCQcEB2kHBAdqBxQHEQcJBxQHCQdpB28HaAdqB28HagdrB2sHagcEB2sHBAdtB2wHawdtB2wHbQeEB3AHbwdrB3AHawdsB3IHcQdvB3IHbwdwB3EHdAdoB3EHaAdvB9IGzgZxB9IGcQdyB84GzAZ0B84GdAdxB3MHywbGBnMHxgZ1B2cHcwd1B2cHdQdjB2MHdQd2B2MHdgdgB3UHxgbEBnUHxAZ2B2AHdgenBmAHpwasBnYHxAalBnYHpQanBncHcgdwB3cHcAd4B9QG0gZyB9QGcgd3B3gHewd5B3gHeQd3B3kH2AbUBnkH1AZ3B3sHfAd6B3sHegd5B9oG2AZ5B9oGeQd6B3wHewd9B3wHfQd+B30Hewd4B30HeAeBB38Hiwd+B38Hfgd9B4IHfwd9B4IHfQeBB4EHbAeEB4EHhAeCB3gHcAdsB3gHbAeBB4MHhQeHB4MHhweGB4gHgAeDB4gHgweGB4YHhwf4BoYH+Ab2BvYG8gaIB/YGiAeGB/IG8AaJB/IGiQeIB4wHgAeIB4wHiAeJB/AG7QaKB/AGigeJB44HjAeJB44HiQeKB4sHjQePB4sHjwd+B4oHkweRB4oHkQeOB40HkAeUB40HlAePB5QHkAeSB5QHkgeWB48HlAeVB48HlQeZB5UHlAeWB5UHlgeXB5cHlgfkBpcH5AbhBpYHkgfpBpYH6QbkBpgHlwfhBpgH4QbdBpkHlQeXB5kHlweYB90G2gZ6B90GegeYB3wHmQeYB3wHmAd6B5kHfAd+B5kHfgePB+0G6gaTB+0GkweKB4cHmgf8BocH/Ab4BoUHbgeaB4UHmgeHB5oHAgf/BpoH/wb8Bm4HBQcCB24HAgeaB58GnAabB58GmweoBn4GqAabB34GmwecB5oGnAebB5oGmwecBpoGmAadB5oGnQecB34GnAedB34GnQd4BnUGeAadB3UGnQeYBp8HBAaGBp8HhgaeB54HhgaDBp4HgwagB6MHnweeB6MHngehB6EHngegB6EHoAeiB6QHoQeiB6QHogemB6UHowehB6UHoQekB9AFpQekB9AFpAenB6cHpAemB6cHpgeoB6kHpweoB6kHqAeqB84F0AWnB84FpwepB6sHzAXOBasHzgWpB6wHqwepB6wHqQeqB6sHrQfKBasHygXMBawHrgetB6wHrQerB64HsAevB64HrwetB60HrwfIBa0HyAXKBa8HsQfGBa8HxgXIBbAHsgexB7AHsQevB7IHtAezB7IHswexB7EHswfEBbEHxAXGBbMHtQfCBbMHwgXEBbQHtge1B7QHtQezB7YHuQe3B7YHtwe1B7UHtwe/BbUHvwXCBbwFwAW4B7wFuAe7B7sHuAe6B7sHuge8B7oFvAW7B7oFuwe9B70Huwe8B70HvAe+B8AHvwe9B8AHvQe+B78HuAW6Bb8HugW9B78HwAfCB78HwgfBB7gFvwfBB7gFwQe2BcEHwwe0BcEHtAW2BcIHxAfDB8IHwwfBB8UHwwfEB8UHxAfGB7IFtAXDB7IFwwfFB8cHsAWyBccHsgXFB8gHxwfFB8gHxQfGB64FsAXHB64FxwfJB8kHxwfIB8kHyAfKB8sHrAWuBcsHrgXJB8wHywfJB8wHyQfKB80HqgWsBc0HrAXLB84HzQfLB84HywfMB80HzgfRB80H0QfPB6oFzQfPB6oFzwfQB88H0gfUB88H1AfQB9EH0wfSB9EH0gfPB9UH0gfTB9UH0wfXB9YH1AfSB9YH0gfVB9gH2gfWB9gH1gfVB9wH2AfVB9wH1QfXB9kH3gffB9kH3wfbB90H4AfeB90H3gfZB94H4QfjB94H4wffB+AH5gfhB+AH4QfeB+IH5wfoB+IH6AfkB+UH6QfnB+UH5wfiB+cH6gfrB+cH6wfoB+kH7AfqB+kH6gfnB+8H6wfqB+8H6gftB+0H6gfsB+0H7AfuB/IH8AftB/IH7QfuB/AH8QfvB/AH7wftB/UH8QfwB/UH8AfzB/MH8AfyB/MH8gf0B/sH9wfzB/sH8wf0B/cH+Qf1B/cH9QfzB/4H+Af2B/4H9gf8B/wH9gf6B/wH+gf9BwAI/wf8BwAI/Af9B/8HpgX+B/8H/gf8B6IFpgX/B6IF/wcBCAEI/wcACAEIAAgCCAMIoAWiBQMIogUBCAQIAwgBCAQIAQgCCAMIBQieBQMIngWgBQQIBggFCAQIBQgDCAUIBwicBQUInAWeBQYICAgHCAYIBwgFCAoInAUHCAoIBwgJCAkIBwgICAkICAgLCA4ICggJCA4ICQgMCAwICQgLCAwICwgNCAwIDQgUCAwIFAgQCA4IDAgQCA4IEAgSCA8IFQgXCA8IFwgRCBMIFggVCBMIFQgPCBUIFggaCBUIGggYCBcIFQgYCBcIGAgZCBgIGwgdCBgIHQgZCBoIHAgbCBoIGwgYCBsIHAggCBsIIAgeCB0IGwgeCB0IHggfCCEIHwgeCCEIHgggCBwIIwgiCBwIIgggCCEIIAgiCCEIIggkCCUIJAgiCCUIIggjCCEIJAgmCCEIJggoCCUIJwgmCCUIJggkCCkIKAgmCCkIJggnCCEIKAgqCCEIKggsCCkIKwgqCCkIKggoCC0ILAgqCC0IKggrCC0ILwguCC0ILggsCCEILAguCCEILggwCCEIMAgyCC8IMQgwCC8IMAguCDMIMggwCDMIMAgxCCEIMgg0CDMINQg0CDMINAgyCCEINAg2CCEINggfCDUINwg2CDUINgg0CB0IHwg2CB0INgg3CDgINwg1CDgINQg6CBkIHQg3CBkINwg4CDkIOAg6CDkIOgg7CBcIGQg4CBcIOAg5CD0IOwg6CD0IOgg8CDoINQgzCDoIMwg8CDwIMwgxCDwIMQg+CD0IPAg+CD0IPgg/CD8IPghACD8IQAhBCD4IMQgvCD4ILwhACEAILwgtCEAILQhCCEMIQQhACEMIQAhCCEMIQghECEMIRAhFCEIILQgrCEIIKwhECEUIRAhGCEUIRghHCEQIKwgpCEQIKQhGCEYIKQgnCEYIJwhICEcIRghICEcISAhJCEkISAhKCEkISghLCEgIJwglCEgIJQhKCEoIJQgjCEoIIwhMCEsISghMCEsITAhNCE0ITAgaCE0IGggWCEwIIwgcCEwIHAgaCE4ITQgWCE4IFggTCFEISwhNCFEITQhOCFAITwgUCFAIFAgNCFMIUghPCFMITwhQCFQISQhLCFQISwhRCFYIVQhSCFYIUghTCFkIWAhVCFkIVQhWCFcIRwhJCFcISQhUCFoIRQhHCFoIRwhXCFwIWwhYCFwIWAhZCF8IXghbCF8IWwhcCF0IQwhFCF0IRQhaCF8IYghhCF8IYQheCGAIQQhDCGAIQwhdCGMIPwhBCGMIQQhgCGUIZAhhCGUIYQhiCGgIZwhkCGgIZAhlCGYIPQg/CGYIPwhjCGkIOwg9CGkIPQhmCGgIawhqCGgIaghnCG4IbQhqCG4IaghrCGwIOQg7CGwIOwhpCA4IEghtCA4IbQhuCBEIFwg5CBEIOQhsCHAIbwhuCHAIbghrCG8ICggOCG8IDghuCJgFmgVvCJgFbwhwCJoFnAUKCJoFCghvCGgIcQhwCGgIcAhrCHEIcgiYBXEImAVwCHMIcQhoCHMIaAhlCHUIcghxCHUIcQhzCHQIcwhlCHQIZQhiCHYIdQhzCHYIcwh0CHgIcgh1CHgIdQh3CHUIdgh5CHUIeQh3CHcIegh7CHcIewh4CHkIfQh6CHkIegh3CHoIfAhcCnoIXAp7CH0Ifgh8CH0IfAh6CH8IgAh+CH8Ifgh9CHkIgQh/CHkIfwh9CIIIhQiACIIIgAh/CIQIggh/CIQIfwiBCIIIhAiJCIIIiQiGCIcIhQiCCIcIggiGCIcIhgiKCIcIigiLCIYIiQiPCIYIjwiKCIsIigiNCIsIjQjrCIoIjwiTCIoIkwiNCJgIkgiOCJgIjgiUCJQIjgiICJQIiAiWCJUIlAiWCJUIlggOCZoImAiUCJoIlAiVCJwImwiXCJwIlwiZCJsIngiRCJsIkQiXCKAInwibCKAImwicCJ8IogieCJ8IngibCKQIowifCKQInwigCKMIpgiiCKMIogifCKgIpwijCKgIowikCKcIqgimCKcIpgijCKwIqwinCKwIpwioCKsIrgiqCKsIqginCLAIrwirCLAIqwisCK8IsgiuCK8IrgirCLcIswivCLcIrwiwCLMItgiyCLMIsgivCLgItAizCLgIswi3CLQIowm2CLQItgizCLsIuQi3CLsItwiwCLoIuAi3CLoItwi5CLkIvAi+CLkIvgi6CLsIvQi8CLsIvAi5CL0IwgjACL0IwAi8CL4IvAjACL4IwAjECMEIxgjFCMEIxQi/CMMIvwjFCMMIxQjHCMYIyQjICMYIyAjFCMcIxQjICMcIyAjKCM0IywjICM0IyAjJCMoIyAjLCMoIywjMCM8IzAjLCM8IywjOCM4IywjNCM4IzQjQCKIJzwjOCKIJzgi1CLUIzgjQCLUI0AixCLEI0AjRCLEI0QitCNAIzQjTCNAI0wjRCK0I0QjSCK0I0gipCNEI0wjUCNEI1AjSCNQI0wjVCNQI1QjWCNMIzQjJCNMIyQjVCNcI1QjJCNcIyQjYCNkI1gjVCNkI1QjXCGIG1wjYCGIG2AhkBmAG2QjXCGAG1whiBl4G2gjZCF4G2QhgBtoI2wjWCNoI1gjZCFwG3AjaCFwG2gheBtwI3QjbCNwI2wjaCFoG3gjcCFoG3AhcBt4I3wjdCN4I3QjcCFcG4AjeCFcG3ghaBuAI4gjfCOAI3wjeCOQI4QhYBuQIWAZUBuYI4wjhCOYI4QjkCOUI5AhUBuUIVAZSBucI5gjkCOcI5AjlCIsI6wjjCIsI4wjmCOcIhwiLCOcIiwjmCOkI6AjnCOkI5wjlCOcI6AiFCOcIhQiHCHwIfgjoCHwI6AjpCOgIfgiACOgIgAiFCFAG6QjlCFAG5QhSBlAGXAp8CFAGfAjpCOII6gjsCOII7AjfCO4I7AjqCO4I6giMCO8I7QjsCO8I7AjuCOwI7QjdCOwI3QjfCJ0I7giMCJ0IjAiQCKEI7wjuCKEI7gidCKUI8AjvCKUI7wihCPAI8QjtCPAI7QjvCKkI0gjwCKkI8AilCNII1AjxCNII8QjwCO0I8QjbCO0I2wjdCPEI1AjWCPEI1gjbCPII8whkBvIIZAbYCMYI8gjYCMYI2AjJCMEI9AjyCMEI8gjGCPQI9gjzCPQI8wjyCL0I+Aj1CL0I9QjCCPgI+Qj3CPgI9wj1CLsI+gj4CLsI+Ai9CPoIrAf5CPoI+Qj4CPsIrgesB/sIrAf6CPwI+wj6CPwI+gi7CP4I/Qj7CP4I+wj8CP0IsAeuB/0Irgf7CAAJ/wj9CAAJ/Qj+CP8IsgewB/8IsAf9CAIJAQn/CAIJ/wgACQEJtAeyBwEJsgf/CAQJAwkBCQQJAQkCCQMJtge0BwMJtAcBCQcJBQkDCQcJAwkECQUJuQe2BwUJtgcDCboHBgkJCboHCQm8BwYJCAkLCQYJCwkJCbwHCQkKCbwHCgm+BwkJCwkMCQkJDAkKCQwJCwmVCAwJlQgOCQsJCAmaCAsJmgiVCA8JDQkMCQ8JDAkOCQoJDAkNCQoJDQkXCQ8JDgmWCA8JlggQCRAJEwkRCRAJEQkPCQ0JDwkRCQ0JEQkUCREJEgkVCREJFQkUCRMJIwkSCRMJEgkRCRQJFQkYCRQJGAkWCQ0JFAkWCQ0JFgkXCRcJFgnCBxcJwgfABxYJGAnEBxYJxAfCB8QHGAkZCcQHGQnGBxgJFQkbCRgJGwkZCcYHGQkaCcYHGgnIBxkJGwkcCRkJHAkaCRwJGwkdCRwJHQkeCRsJFQkSCRsJEgkdCR0JHwkgCR0JIAkeCRIJIwkfCRIJHwkdCR8JIQk3CR8JNwkgCSMJJAkhCSMJIQkfCScJJAkjCScJIwkTCSYJNAkiCSYJIgklCSkJJgklCSkJJQkoCYMIJwkTCYMIEwkQCYQIgQgpCYQIKQkoCYEIKwkqCYEIKgkpCSoJLAkmCSoJJgkpCSoJKwkvCSoJLwktCSwJKgktCSwJLQkuCS4JLQlcCC4JXAhZCC0JLwlfCC0JXwhcCF8ILwl0CF8IdAhiCC8JKwl2CC8Jdgh0CDAJLglZCDAJWQhWCDIJLAkuCTIJLgkwCTEJMAlWCDEJVghTCDMJMgkwCTMJMAkxCSYJLAkyCSYJMgk0CTIJMwk1CTIJNQk0CTQJNQk4CTQJOAkiCTUJMwmMCTUJjAk2CTUJNgk5CTUJOQk4CYsJOgk4CYsJOAk5CToJQQk9CToJPQk4CTgJPQlACTgJQAk8CTcJOwmECTcJhAkgCUEJQgk+CUEJPgk9CT0JPgljCT0JYwlACUUJRAlBCUUJQQk6CUQJRglCCUQJQglBCUkJSAlECUkJRAlFCUgJSglGCUgJRglECQAI/QdICQAISAlJCf0H+gdKCf0HSglICfsH9AdMCfsHTAlLCUsJTAlNCUsJTQlHCUwJTglPCUwJTwlNCfQH8gdOCfQHTglMCfIH7gdQCfIHUAlOCU4JUAlRCU4JUQlPCVAJUglTCVAJUwlRCe4H7AdSCe4HUglQCVQJUgnsB1QJ7AfpB1cJUwlSCVcJUglUCVUJVAnpB1UJ6QflB1kJVwlUCVkJVAlVCVoJXQlTCVoJUwlXCVwJWglXCVwJVwlZCWAJXglaCWAJWglcCV4JYQldCV4JXQlaCWQJYgleCWQJXglgCWIJZQlhCWIJYQleCT8JZgliCT8JYglkCWYJZwllCWYJZQliCWgJaQlnCWgJZwlmCUMJaAlmCUMJZgk/CUcJTQloCUcJaAlDCU0JTwlpCU0JaQloCWkJagllCWkJZQlnCU8JUQlqCU8JaglpCWoJXQlhCWoJYQllCVEJUwldCVEJXQlqCUAJYwlfCUAJXwlrCWsJXwlbCWsJWwluCTwJQAlrCTwJawltCW0JawluCW0JbglwCXAJbglxCXAJcQlzCW4JWwlYCW4JWAlxCXEJWAlWCXEJVgl0CXMJcQl0CXMJdAl2CXYJdAngB3YJ4AfdB3QJVgnmB3QJ5gfgB9cHdwl1CdcHdQncB3cJeAlyCXcJcgl1CdMHeQl3CdMHdwnXB3kJegl4CXkJeAl3CXsJeQnTB3sJ0wfRB30Jegl5CX0JeQl7CXwJewnRB3wJ0QfOB34JfQl7CX4Jewl8CYAJfwl9CYAJfQl+CX8JgQl6CX8Jegl9CR4Jggl/CR4JfwmACYIJgwmBCYIJgQl/CSAJhAmCCSAJggkeCYQJhQmDCYQJgwmCCTsJbAmFCTsJhQmECYEJgwmFCYEJhQmGCYYJhQlsCYYJbAlvCXgJhglvCXgJbwlyCXoJgQmGCXoJhgl4CRwJHgmACRwJgAmHCYAJfgmICYAJiAmHCYkJhwmICYkJiAmKCRoJHAmHCRoJhwmJCcoHiQmKCcoHignMB8gHGgmJCcgHiQnKB84HzAeKCc4Higl8CYoJiAl+CYoJfgl8CQAISQmLCQAIiwkCCEkJRQk6CUkJOgmLCQIIiwk5CQIIOQkECDYJBggECDYJBAg5CYwJCAgGCIwJBgg2CY0JjAkzCY0JMwkxCQsICAiMCQsIjAmNCVAIjQkxCVAIMQlTCA0ICwiNCQ0IjQlQCHYIKwmBCHYIgQh5CMAHvgcKCcAHCgkXCZYIiAiDCJYIgwgQCQcJBAmcCAcJnAiZCAQJAgmgCAQJoAicCKAIAgkACaAIAAmkCAAJ/gioCAAJqAikCKgI/gj8CKgI/AisCPwIuwiwCPwIsAisCPcI+QiOCfcIjgmQCfkIrAeqB/kIqgeOCZAJjgmRCZAJkQmTCY4JqgeoB44JqAeRCZMJkQmUCZMJlAmVCZEJqAemB5EJpgeUCZUJlAmXCZUJlwmYCZQJpgeiB5QJogeXCZgJlwmaCZgJmgmbCZcJogegB5cJoAeaCZsJmgmABpsJgAZ7BpoJoAeDBpoJgwaABp0JnAl8Bp0JfAZ3Bp4JmQmcCZ4JnAmdCW4GnQl3Bm4GdwZwBmwGngmdCWwGnQluBmoGnwmeCWoGnglsBp8JlgmZCZ8JmQmeCWgGoAmfCWgGnwlqBqAJkgmWCaAJlgmfCWYGoQmgCWYGoAloBqEJjwmSCaEJkgmgCWQG8wihCWQGoQlmBvMI9giPCfMIjwmhCacJpAnPCKcJzwiiCaQJpgnMCKQJzAjPCKkJpQmkCakJpAmnCaUJwAmmCaUJpgmkCasJqAmjCasJowmtCawJqgmoCawJqAmrCbAJrAmrCbAJqwmuCa4JqwmtCa4JrQmvCa4JrwmzCa4JswmxCbAJrgmxCbAJsQmyCbEJtAm1CbEJtQmyCbMJtgm0CbMJtAmxCbUJtAm4CbUJuAm6CbQJtgnECLQJxAi4CbcJuwm8CbcJvAm5CcMIxwi7CcMIuwm3CbwJuwm9CbwJvQm+CbsJxwjKCLsJygi9Cb0JygjMCL0JzAimCb4JvQmmCb4JpgnACbwJvgm/CbwJvwlBCsAJwQm/CcAJvwm+CcQJwQnACcQJwAmlCccJwgnBCccJwQnECb8JwQnCCb8Jwgk6CsUJxAmlCcUJpQmpCckJxwnECckJxAnFCc0JzAnICc0JyAnLCcwJzgnDCcwJwwnICc0J0gnPCc0JzwnMCcwJzwnQCcwJ0AnOCc8J0Qn0Cc8J9AnQCdIJ0wnRCdIJ0QnPCdUJ1AnSCdUJ0gnNCdQJ1gnTCdQJ0wnSCdkJ1wnUCdkJ1AnVCdcJ2AnWCdcJ1gnUCdoJ3AnYCdoJ2AnXCdsJ2gnXCdsJ1wnZCd4J3QnaCd4J2gnbCd0J3wncCd0J3AnaCeEJ4AndCeEJ3QneCeAJ4gnfCeAJ3wndCeEJ5gnjCeEJ4wngCeAJ4wnkCeAJ5AniCeMJ5Qn7CeMJ+wnkCeYJ5wnlCeYJ5QnjCegJ6gnnCegJ5wnmCekJ6AnmCekJ5gnhCewJ6wnoCewJ6AnpCesJ7QnqCesJ6gnoCewJzgnuCewJ7gnrCe4J7wntCe4J7QnrCdAJ8AnuCdAJ7gnOCfAJ8QnvCfAJ7wnuCfQJ8gnwCfQJ8AnQCfIJ8wnxCfIJ8QnwCfUJ9wnzCfUJ8wnyCfYJ9QnyCfYJ8gn0CfkJ+An1CfkJ9Qn2CfgJ+gn3CfgJ9wn1CeIJ5An4CeIJ+An5CeQJ+wn6CeQJ+gn4CfoJ/An9CfoJ/Qn3CfsJ/wn8CfsJ/An6CfwJ/gkPCvwJDwr9Cf8JAAr+Cf8J/gn8CQEKAgoACgEKAAr/CeUJAQr/CeUJ/wn7CecJAwoBCucJAQrlCQMKBAoCCgMKAgoBCgUKBgoECgUKBAoDCuoJBQoDCuoJAwrnCe0JBwoFCu0JBQrqCQcKCAoGCgcKBgoFCu0J7wkJCu0JCQoHCgkKCgoICgkKCAoHCvEJCwoJCvEJCQrvCQsKDAoKCgsKCgoJCvMJDQoLCvMJCwrxCQ0KDgoMCg0KDAoLCv0JDwoOCv0JDgoNCvMJ9wn9CfMJ/QkNCgwKDgoPCgwKDwoQChAKDwr+CRAK/gkRCggKCgoMCggKDAoQCggKEAoRCggKEQoGCgYKEQoCCgYKAgoEChEK/gkAChEKAAoCCvkJEgoTCvkJEwriCfYJFQoSCvYJEgr5CRIKFAolChIKJQoTChUKFgoUChUKFAoSCvYJ9AkXCvYJFwoVChcKGAoWChcKFgoVChkKGgoYChkKGAoXCtEJGQoXCtEJFwr0CdMJGwoZCtMJGQrRCRsKHAoaChsKGgoZCtYJHQobCtYJGwrTCR0KHgocCh0KHAobCtgJHwodCtgJHQrWCR8KIAoeCh8KHgodCiEKIgogCiEKIAofCtgJ3AkhCtgJIQofCt8JIwohCt8JIQrcCSMKJAoiCiMKIgohCuIJEwojCuIJIwrfCRMKJQokChMKJAojCiQKJgonCiQKJwoiCiUKKQomCiUKJgokCiYKKAo5CiYKOQonCikKKgooCikKKAomCisKLAoqCisKKgopChQKKwopChQKKQolChYKLQorChYKKwoUCi0KLgosCi0KLAorChYKGAovChYKLwotCi8KMAouCi8KLgotCjEKMgowCjEKMAovChoKMQovChoKLwoYChwKMwoxChwKMQoaCjMKNAoyCjMKMgoxCh4KNQozCh4KMwocCjUKNgo0CjUKNAozCiAKNwo1CiAKNQoeCjcKOAo2CjcKNgo1CiIKJwo3CiIKNwogCicKOQo4CicKOAo3CjkKKAo2CjkKNgo4CigKKgo0CigKNAo2CioKLAoyCioKMgo0CiwKLgowCiwKMAoyCsMJzgnsCcMJ7Ak7Cj0KOwrsCT0K7AnpCUEKvwk6CkEKOgo8CkAKPQrpCUAK6QnhCUIKQQo8CkIKPAo+CrkJvAlBCrkJQQpCCkQKQwo/CkQKPwpGCrUJuglDCrUJQwpECrIJtQlECrIJRApFCkUKRApGCkUKRgpICkkKRwreCUkK3gnbCUcKQArhCUcK4QneCUwKRQpICkwKSApKCkkK2wnZCUkK2QlLCtkJ1QlOCtkJTgpLCkoKTQpPCkoKTwpMCk0KygnGCU0KxglPCtUJzQnLCdUJywlOCkwKTwqsCUwKrAmwCU8KxgmqCU8KqgmsCbIJRQpMCrIJTAqwCbMJUgpQCrMJUAq2CcQItglQCsQIUApRClMKUQpQClMKUApSClQKVQpTClQKUwpSCq8JVApSCq8JUgqzCa8JrQlWCq8JVgpUClUKVApWClUKVgpXCqMJVwpWCqMJVgqtCaMJtAhYCqMJWApXClUKVwpYClUKWApZCrgIWQpYCrgIWAq0CFMKVQpZClMKWQpaClkKuAi6CFkKughaCroIvghbCroIWwpaClMKWgpbClMKWwpRCsQIUQpbCsQIWwq+CF0KXApQBl0KUAZOBnsIXApdCnsIXQpeCl4KXQpfCl4KXwpgCl8KXQpOBl8KTgZMBmAKXwphCmAKYQpkCkoGYQpfCkoGXwpMBmMKYQpKBmMKSgZIBmcKZAphCmcKYQpjCmoKZQpkCmoKZApnCnMKYApkCnMKZAplCmgKZgpiCmgKYgpyCmsKaQpmCmsKZgpoCooFjAVlCooFZQpqCoYFiQVpCoYFaQprCmsKaApuCmsKbgpsCoQFhgVrCoQFawpsCmwKbgpwCmwKcAptCoQFbAptCoQFbQqCBWgKcgpvCmgKbwpuCm4KbwpxCm4KcQpwCisAbQpwCisAcApxCm8KQgZABm8KQAZxCisAcQpABm8KcgpEBm8KRAZCBnIKYgpHBnIKRwZEBisAggVtCmUKjAWOBWUKjgVzCnMKdApeCnMKXgpgCnQKcwqOBXQKjgWQBZQFlgV4CJQFeAh0CpAFkgWUBZAFlAV0CnQKeAh7CHQKewheCpYFmAVyCJYFcgh4CHcKdQr+B3cK/gemBXUKeAr4B3UK+Af+B9sH3wd1CtsHdQp3Ct8H4wd4Ct8HeAp1CuQH6Ad6CuQHegp5CnkKegr1B3kK9Qf5B3oK7wfxB3oK8Qf1B+gH6wfvB+gH7wd6CnsK1gfaB3sK2gd2CqgFewp2CqgFdgqlBaoF0Ad7CqoFewqoBdAH1AfWB9AH1gd7CnwKpQfQBXwK0AXSBX4KowelB34KpQd8Cn0KfArSBX0K0gXUBX8Kfgp8Cn8KfAp9CoAKfgp/CoAKfwqBCp8Howd+Cp8HfgqACgIGgAqBCgIGgQoABgQGnweACgQGgAoCBgAGgQqCCgAGggr9BYEKfwqFCoEKhQqCCv4FgwqECv4FhAr6BYMKhgqHCoMKhwqECocKhgqJCocKiQqKCoUKfwp9CoUKfQqICooKiQrYBYoK2AXaBYgKfQrUBYgK1AXXBdoF3AWLCtoFiwqKCooKiwqMCooKjAqHCosKjQqOCosKjgqMCtwF3gWNCtwFjQqLCo0KjwqQCo0KkAqOCt4F4AWPCt4FjwqNCo8KkQqSCo8KkgqQCuAF4gWRCuAFkQqPCpEKkwqUCpEKlAqSCuIF5AWTCuIFkwqRCpMK6AXqBZMK6gWUCuQF5gXoBeQF6AWTCpQK6gXsBZQK7AWVCpIKlAqVCpIKlQqWCpYKlQrwBZYK8AXyBZUK7AXuBZUK7gXwBZcKlgryBZcK8gX0BZAKkgqWCpAKlgqXCpgKlwr0BZgK9AX2BY4KkAqXCo4KlwqYCpkKmAr2BZkK9gX4BYwKjgqYCowKmAqZCoQKmQr4BYQK+AX6BYcKjAqZCocKmQqECpoKmwrDCpoKwwqcCp0KmgqcCp0KnAqgCp8KnQqgCp8KoAqiCqIKoAqjCqIKowqlCqAKnAqmCqAKpgqjCqUKowqnCqUKpwqpCqYKqgqnCqYKpwqjCqwKqQqnCqwKpwqqCqwKqgqtCqwKrQqwCqYKrgqtCqYKrQqqCrIKsAqtCrIKrQquCrEKtAqzCrEKswqvCqsKrwqzCqsKswq1CrYKtQqzCrYKswq0CqsKtQq3CqsKtwqoCrYKuAq3CrYKtwq1CqQKqAq3CqQKtwq4CroKuQq4CroKuAq2CrkKoQqkCrkKpAq4CrwKuwq5CrwKuQq6CrsKngqhCrsKoQq5Cr0KvAq6Cr0KugrACr4KvQrACr4KwArBCsEKwAq0CsEKtAqxCsAKugq2CsAKtgq0CsMKwgqyCsMKsgquCpsKvwrCCpsKwgrDCpwKwwquCpwKrgqmCsQKxwrtCsQK7QrGCskKywrICskKyArFCsoKzArLCsoKywrJCswKzgrNCswKzQrLCssKzQrPCssKzwrICs4K0grRCs4K0QrNCs8KzQrRCs8K0QrTCtUK0wrRCtUK0QrSCtYK2QrXCtYK1wrUCtAK1ArXCtAK1wrYCtoK2ArXCtoK1wrZCtoK2QrbCtoK2wrcCtYK3grbCtYK2wrZCt8K3ArbCt8K2wreCtUK0grhCtUK4QrdCuAK3QrhCuAK4QriCs4K4grhCs4K4QrSCuUK4AriCuUK4grjCuMK4grOCuMKzgrMCugK5QrjCugK4wrmCuYK4wrMCuYKzArKCukK6wrkCukK5ArnCuoK7ArrCuoK6wrpCuwK2grcCuwK3ArrCusK3ArfCusK3wrkCu0K2AraCu0K2grsCsYK7QrsCsYK7ArqCscK0ArYCscK2ArtCu4K8QoXC+4KFwvwCvMK9QryCvMK8grvCvQK9gr1CvQK9QrzCvYK+Ar3CvYK9wr1CvUK9wr6CvUK+gryCvgK/Ar7CvgK+wr3CvoK9wr7CvoK+wr+CgAL/gr7CgAL+wr8Cv8KAwsBC/8KAQv9CvkK/QoBC/kKAQsCCwQLAgsBCwQLAQsDCwQLAwsFCwQLBQsGC/8KBwsFC/8KBQsDCwkLBgsFCwkLBQsHCwAL/AoLCwALCwsICwoLCAsLCwoLCwsMC/gKDAsLC/gKCwv8Cg8LCgsMCw8LDAsNCw0LDAv4Cg0L+Ar2ChILDwsNCxILDQsQCxALDQv2ChAL9gr0ChMLFQsOCxMLDgsRCxQLFgsVCxQLFQsTCxYLBAsGCxYLBgsVCxULBgsJCxULCQsOCxcLAgsECxcLBAsWC/AKFwsWC/AKFgsUC/EK+QoCC/EKAgsXCxgLGgtBCxgLQQsZCxsLHgsaCxsLGgsYCxwLHwseCxwLHgsbCx8LIgshCx8LIQseCx4LIQskCx4LJAsaCyILJgslCyILJQshCyQLIQslCyQLJQsoCykLKAslCykLJQsmCykLLQsrCykLKwsoCyQLKAsrCyQLKwssCy8LLAsrCy8LKwstCzALLgsxCzALMQsyCyoLMwsxCyoLMQsuCzQLMgsxCzQLMQszCyoLJws1CyoLNQszCzQLMws1CzQLNQs2CyMLNgs1CyMLNQsnCzgLNAs2CzgLNgs3CzcLNgsjCzcLIwsgCzoLOAs3CzoLNws5CzkLNwsgCzkLIAsdCzsLPgs4CzsLOAs6Cz0LQAs+Cz0LPgs7C0ALMAsyC0ALMgs+Cz4LMgs0Cz4LNAs4C0ELLAsvC0ELLws/CxkLQQs/CxkLPws8CxoLJAssCxoLLAtBC0MLRgtrC0MLawtEC0cLSQtFC0cLRQtCC0gLSgtJC0gLSQtHC0oLTAtLC0oLSwtJC0kLSwtNC0kLTQtFC0wLUAtPC0wLTwtLC00LSwtPC00LTwtRC1MLUQtPC1MLTwtQC1QLVwtVC1QLVQtSC04LUgtVC04LVQtWC1gLVgtVC1gLVQtXC1gLVwtZC1gLWQtaC1QLXAtZC1QLWQtXC14LWgtZC14LWQtcC1MLUAtfC1MLXwtbC10LWwtfC10LXwtgC0wLYAtfC0wLXwtQC2ILXQtgC2ILYAthC2ELYAtMC2ELTAtKC2ULYgthC2ULYQtkC2QLYQtKC2QLSgtIC2cLaQtjC2cLYwtmC2gLagtpC2gLaQtnC2oLWAtaC2oLWgtpC2kLWgteC2kLXgtjC2sLVgtYC2sLWAtqC0QLawtqC0QLagtoC0YLTgtWC0YLVgtrC20LcAuVC20LlQtuC3ELcwtvC3ELbwtsC3ILdAtzC3ILcwtxC3QLdgt1C3QLdQtzC3MLdQt3C3MLdwtvC3YLegt5C3YLeQt1C3cLdQt5C3cLeQt7C30Lewt5C30LeQt6C34LgQt/C34Lfwt8C3gLfAt/C3gLfwuAC4ILgAt/C4ILfwuBC4ILgQuDC4ILgwuEC34LhguDC34LgwuBC4gLhAuDC4gLgwuGC30LeguJC30LiQuFC4cLhQuJC4cLiQuKC3YLiguJC3YLiQt6C4wLhwuKC4wLiguLC4sLigt2C4sLdgt0C48LjAuLC48LiwuOC44Liwt0C44LdAtyC5ELkwuNC5ELjQuQC5ILlAuTC5ILkwuRC5QLgguEC5QLhAuTC5MLhAuIC5MLiAuNC5ULgAuCC5ULgguUC24LlQuUC24LlAuSC3ALeAuAC3ALgAuVC5cLmgu/C5cLvwuYC5sLnQuZC5sLmQuWC5wLngudC5wLnQubC54LoAufC54LnwudC50LnwuhC50LoQuZC6ALpAujC6ALowufC6ELnwujC6ELowumC6gLpgujC6gLowukC6cLqwupC6cLqQulC6ILpQupC6ILqQuqC6wLqgupC6wLqQurC6wLqwutC6wLrQuuC6cLrwutC6cLrQurC7ELrgutC7ELrQuvC6gLpAuzC6gLswuwC7ILsAuzC7ILswu0C6ALtAuzC6ALswukC7YLsgu0C7YLtAu1C7ULtAugC7ULoAueC7kLtgu1C7kLtQu4C7gLtQueC7gLngucC7sLvQu3C7sLtwu6C7wLvgu9C7wLvQu7C74LrAuuC74Lrgu9C70LrguxC70LsQu3C78LqgusC78LrAu+C5gLvwu+C5gLvgu8C5oLoguqC5oLqgu/C8ELwgvpC8EL6QvEC8ULwAvDC8ULwwvHC8YLxQvHC8YLxwvIC8gLxwvJC8gLyQvKC8cLwwvLC8cLywvJC8oLyQvNC8oLzQvOC8sLzwvNC8sLzQvJC9ELzgvNC9ELzQvPC9IL0AvTC9IL0wvVC8wL1AvTC8wL0wvQC9YL1QvTC9YL0wvUC9YL2AvXC9YL1wvVC9IL1QvXC9IL1wvaC9wL2gvXC9wL1wvYC9EL2QvdC9EL3QvOC9sL3gvdC9sL3QvZC8oLzgvdC8oL3QveC+AL3wveC+AL3gvbC98LyAvKC98LygveC+ML4gvfC+ML3wvgC+ILxgvIC+ILyAvfC+UL5AvhC+UL4QvnC+YL5QvnC+YL5wvoC+gL5wvYC+gL2AvWC+cL4QvcC+cL3AvYC+kL6AvWC+kL1gvUC8IL5gvoC8IL6AvpC8QL6QvUC8QL1AvMC+oL6wsTDOoLEwzsC+0L6gvsC+0L7AvwC+8L7QvwC+8L8AvyC/IL8AvzC/IL8wv1C/AL7Av2C/AL9gvzC/UL8wv3C/UL9wv5C/YL+gv3C/YL9wvzC/wL+Qv3C/wL9wv6C/wL+gv9C/wL/QsADPYL/gv9C/YL/Qv6CwIMAAz9CwIM/Qv+CwEMBAwDDAEMAwz/C/sL/wsDDPsLAwwFDAYMBQwDDAYMAwwEDPsLBQwHDPsLBwz4CwYMCAwHDAYMBwwFDPQL+AsHDPQLBwwIDAoMCQwIDAoMCAwGDAkM8Qv0CwkM9AsIDAwMCwwJDAwMCQwKDAsM7gvxCwsM8QsJDA0MDAwKDA0MCgwQDA4MDQwQDA4MEAwRDBEMEAwEDBEMBAwBDBAMCgwGDBAMBgwEDBMMEgwCDBMMAgz+C+sLDwwSDOsLEgwTDOwLEwz+C+wL/gv2CxQMGQyPDBQMjwwXDBgMIAwbDBgMGwwVDBwMHwwaDBwMGgwWDB4MLAwfDB4MHwwcDCAMIgwdDCAMHQwbDCEMIwwiDCEMIgwgDHAMIQwgDHAMIAwYDCMMJQwkDCMMJAwiDCIMJAwnDCIMJwwdDCUMggwmDCUMJgwkDCQMJgwpDCQMKQwnDCgMKgwsDCgMLAweDCkMLQwqDCkMKgwoDCoMKwyQDCoMkAwsDC0MLgwrDC0MKwwqDC8MMQwuDC8MLgwtDCYMLwwtDCYMLQwpDDAMMgwxDDAMMQwvDIIMMAwvDIIMLwwmDDIMNAwzDDIMMwwxDDEMMww2DDEMNgwuDDQMjgw1DDQMNQwzDDMMNQw3DDMMNww2DDYMNww7DDYMOww4DC4MNgw4DC4MOAwrDCsMOAw5DCsMOQyQDDgMOww9DDgMPQw5DDUMPgw6DDUMOgw3DD4MQAw8DD4MPAw6DD8MQQxADD8MQAw+DI4MPww+DI4MPgw1DEEMQwxCDEEMQgxADEAMQgxGDEAMRgw8DEMMVwxFDEMMRQxCDEIMRQxJDEIMSQxGDDkMPQxHDDkMRwxLDEsMRwxKDEsMSgxODJAMOQxLDJAMSwxMDEwMSwxODEwMTgxQDE8MTQxRDE8MUQxSDE0MSAxEDE0MRAxRDFIMUQxTDFIMUwxUDFEMRAxWDFEMVgxTDFQMUwxVDFQMVQxoDFMMVgxYDFMMWAxVDFwMWQxXDFwMVwxDDFUMWAxaDFUMWgxlDF8MWwxZDF8MWQxcDF0MXAxDDF0MQwxBDGEMXwxcDGEMXAxdDGUMWgxeDGUMXgxiDGIMXgxgDGIMYAxkDGMMYgxkDGMMZAx5DGYMZQxiDGYMYgxjDGgMVQxlDGgMZQxmDGkMaAxmDGkMZgxnDGcMZgxjDGcMYwx3DGoMVAxoDGoMaAxpDG8MagxpDG8MaQxrDGsMaQxnDGsMZwxtDG4MdAxxDG4McQxsDGwMcQwhDGwMIQxwDHEMcgwjDHEMIwwhDHQMdgxyDHQMcgxxDHMMdwx4DHMMeAx1DG0MZwx3DG0MdwxzDHcMYwx5DHcMeQx4DHgMeQx7DHgMewx6DHUMeAx6DHUMegx9DHoMewyGDHoMhgx8DH0Megx8DH0MfAx/DH4MfwyBDH4MgQyADHYMfgyADHYMgAxyDHIMgAwlDHIMJQwjDIAMgQyCDIAMggwlDIEMgwwwDIEMMAyCDH8MfAyDDH8MgwyBDIMMhAwyDIMMMgwwDHwMhgyEDHwMhAyDDIQMhQw0DIQMNAwyDIYMhwyFDIYMhQyEDHsMiQyHDHsMhwyGDIkMigyIDIkMiAyHDIcMiAyNDIcMjQyFDHkMZAyJDHkMiQx7DGQMYAyKDGQMigyJDIsMYQxdDIsMXQyMDIgMiwyMDIgMjAyNDIwMXQxBDIwMQQw/DI0MjAw/DI0MPwyODIUMjQyODIUMjgw0DBcMjwxqDBcMagxvDI8MUgxUDI8MVAxqDBkMTwxSDBkMUgyPDB8MTAxQDB8MUAwaDCwMkAxMDCwMTAwfDJEMkwzGDJEMxgySDJIMlwyUDJIMlAyRDJQMlgyTDJQMkwyRDJUMogyWDJUMlgyUDJcMmQyVDJcMlQyUDJgMmgyZDJgMmQyXDMUMmAyXDMUMlwySDJoMnAybDJoMmwyZDJkMmwyeDJkMngyVDJwMyQydDJwMnQybDJsMnQyfDJsMnwyeDJ4MoAyiDJ4MogyVDJ8MowygDJ8MoAyeDKAMoQzHDKAMxwyiDKMMpAyhDKMMoQygDKUMpwykDKUMpAyjDJ0MpQyjDJ0MowyfDKYMqAynDKYMpwylDMkMpgylDMkMpQydDKgMqgypDKgMqQynDKcMqQysDKcMrAykDKoMyAyrDKoMqwypDKkMqwytDKkMrQysDKwMrQywDKwMsAyuDKQMrAyuDKQMrgyhDKEMrgyvDKEMrwzHDK4MsAyxDK4MsQyvDKsMsgywDKsMsAytDLIMtAyxDLIMsQywDLMMtQy0DLMMtAyyDMgMswyyDMgMsgyrDLUMtwy2DLUMtgy0DLQMtgy5DLQMuQyxDLcMwwy4DLcMuAy2DLYMuAy6DLYMugy5DK8MsQy5DK8MuQy7DLsMuQy6DLsMugy9DMcMrwy7DMcMuwy8DLwMuwy9DLwMvQy+DL4MvQy/DL4MvwzADL0Mugy4DL0MuAy/DMAMvwzBDMAMwQzCDL8MuAzDDL8MwwzBDMYMwAzCDMYMwgzEDJIMxgzEDJIMxAzFDJMMvgzADJMMwAzGDJYMvAy+DJYMvgyTDKIMxwy8DKIMvAyWDMoMywz/DMoM/wzMDMsMygzNDMsMzQzQDM0MygzMDM0MzAzPDM4MzQzPDM4MzwzbDNAMzQzODNAMzgzSDNEM0AzSDNEM0gzTDP4MywzQDP4M0AzRDNMM0gzUDNMM1AzVDNIMzgzXDNIM1wzUDNUM1AzWDNUM1gwCDdQM1wzYDNQM2AzWDNcMzgzbDNcM2wzZDNgM1wzZDNgM2QzcDNkM2wwADdkMAA3aDNwM2QzaDNwM2gzdDN4M3AzdDN4M3QzgDNYM2AzcDNYM3AzeDN8M3gzgDN8M4AzhDAIN1gzeDAIN3gzfDOEM4AziDOEM4gzjDOAM3QzlDOAM5QziDOMM4gzkDOMM5AwBDeIM5QzmDOIM5gzkDOUM5wzpDOUM6QzmDN0M2gznDN0M5wzlDNoMAA3oDNoM6AznDOcM6AzqDOcM6gzpDOQM5gzpDOQM6QzrDOsM6QzqDOsM6gztDOwM6wztDOwM7QzuDAEN5AzrDAEN6wzsDO4M7QzvDO4M7wzwDO0M6gzyDO0M8gzvDPAM7wzxDPAM8Qz8DO8M8gzzDO8M8wzxDOgM9AzyDOgM8gzqDPQM9gzzDPQM8wzyDAAN9Qz0DAAN9AzoDPUM9wz2DPUM9gz0DPcM+Qz4DPcM+Az2DPYM+AzxDPYM8QzzDPkM+wz6DPkM+gz4DPgM+gz8DPgM/AzxDP8M/Qz7DP8M+wz5DMsM/gz9DMsM/Qz/DMwM/wz5DMwM+Qz3DM8MzAz3DM8M9wz1DNsMzwz1DNsM9QwADQMNCA1+DQMNfg0GDQcNDw0KDQcNCg0EDQsNDg0JDQsNCQ0FDQ0NGw0ODQ0NDg0LDQ8NEQ0MDQ8NDA0KDRANEg0RDRANEQ0PDV8NEA0PDV8NDw0HDRINFA0TDRINEw0RDRENEw0WDRENFg0MDRQNcQ0VDRQNFQ0TDRMNFQ0YDRMNGA0WDRcNGQ0bDRcNGw0NDRgNHA0ZDRgNGQ0XDRkNGg1/DRkNfw0bDRwNHQ0aDRwNGg0ZDR4NIA0dDR4NHQ0cDRUNHg0cDRUNHA0YDR8NIQ0gDR8NIA0eDXENHw0eDXENHg0VDSENIw0iDSENIg0gDSANIg0lDSANJQ0dDSMNfQ0kDSMNJA0iDSINJA0mDSINJg0lDSUNJg0qDSUNKg0nDR0NJQ0nDR0NJw0aDRoNJw0oDRoNKA1/DScNKg0sDScNLA0oDSQNLQ0pDSQNKQ0mDS0NLw0rDS0NKw0pDS4NMA0vDS4NLw0tDX0NLg0tDX0NLQ0kDTANMg0xDTANMQ0vDS8NMQ01DS8NNQ0rDTINRg00DTINNA0xDTENNA04DTENOA01DSgNLA02DSgNNg06DToNNg05DToNOQ09DX8NKA06DX8NOg07DTsNOg09DTsNPQ0/DT4NPA1ADT4NQA1BDTwNNw0zDTwNMw1ADUENQA1CDUENQg1DDUANMw1FDUANRQ1CDUMNQg1EDUMNRA1XDUINRQ1HDUINRw1EDUsNSA1GDUsNRg0yDUQNRw1JDUQNSQ1UDU4NSg1IDU4NSA1LDUwNSw0yDUwNMg0wDVANTg1LDVANSw1MDVQNSQ1NDVQNTQ1RDVENTQ1PDVENTw1TDVINUQ1TDVINUw1oDVUNVA1RDVUNUQ1SDVcNRA1UDVcNVA1VDVgNVw1VDVgNVQ1WDVYNVQ1SDVYNUg1mDVkNQw1XDVkNVw1YDV4NWQ1YDV4NWA1aDVoNWA1WDVoNVg1cDV0NYw1gDV0NYA1bDVsNYA0QDVsNEA1fDWANYQ0SDWANEg0QDWMNZQ1hDWMNYQ1gDWINZg1nDWINZw1kDVwNVg1mDVwNZg1iDWYNUg1oDWYNaA1nDWcNaA1qDWcNag1pDWQNZw1pDWQNaQ1sDWkNag11DWkNdQ1rDWwNaQ1rDWwNaw1uDW0Nbg1wDW0NcA1vDWUNbQ1vDWUNbw1hDWENbw0UDWENFA0SDW8NcA1xDW8NcQ0UDXANcg0fDXANHw1xDW4Naw1yDW4Ncg1wDXINcw0hDXINIQ0fDWsNdQ1zDWsNcw1yDXMNdA0jDXMNIw0hDXUNdg10DXUNdA1zDWoNeA12DWoNdg11DXgNeQ13DXgNdw12DXYNdw18DXYNfA10DWgNUw14DWgNeA1qDVMNTw15DVMNeQ14DXoNUA1MDXoNTA17DXcNeg17DXcNew18DXsNTA0wDXsNMA0uDXwNew0uDXwNLg19DXQNfA19DXQNfQ0jDQYNfg1ZDQYNWQ1eDX4NQQ1DDX4NQw1ZDQgNPg1BDQgNQQ1+DQ4NOw0/DQ4NPw0JDRsNfw07DRsNOw0ODWgAawCMDWgAjA2NDQoCjA2LDQoCiw0HAm8AdACJDW8AiQ2KDQQCiQ2IDQQCiA0CAgAChw2IDQACiA13AP0Bhg2HDf0Bhw0AAvwBhg2FDfwBhQ35AfkBhQ2DDfkBgw3zAfQBhA2CDfQBgg3wAfABgg2BDfABgQ3uAekBgA2BDekBgQ3qAWgAjQ2ADWgAgA3pAQ4Gmw2aDQ4Gmg0MBpAGjQaZDZAGmQ2aDQkGmA2XDQkGlw0GBooGiAaWDYoGlg2XDYYGBAaWDYYGlg2VDYMGhgaVDYMGlQ2UDYIGfwaTDYIGkw2UDX8GeQaRDX8GkQ2TDXoGdgaQDXoGkA2SDXYGdAaPDXYGjw2QDW8GcAaPDW8Gjw2ODQ4GbwaODQ4Gjg2bDauqKj2rqqo9AAAAPquqKj5VVVU+AACAPlVVlT6rqqo+AADAPlVV1T6rquo+AAAAP6uqCj9VVRU/AAAgP6uqKj9VVTU/AABAP6uqSj9VVVU/AABgP6uqaj9VVXU/AACAP1VVhT+rqoo/AACQP1VVlT+rqpo/AACgP1VVpT+rqqo/AACwP1VVtT+rqro/AADAP1VVxT+rqso/AADQP1VV1T+rqto/AADgP1VV5T+rquo/AADwP1VV9T+rqvo/AAAAQKuqAkBVVQVAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEzvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAP0zvrjMAAAAAAAAAAAAAgD9M764zAAAAAAAAAAAAAIA/TO+uMwAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAi+iI+AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/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HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPxUAgD8DAIA/HQCAPwTGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMATGADLfCqQ9sFQMMLJe8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P7Je8L2I33S572qvuxo6fj+yXvC9iN90ue9qr7saOn4/sl7wvYjfdLnvaq+7Gjp+P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P///fz/1/38/9/9/P8QOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUscQOsrGc8FM8s4QUsaUgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P6UgXr1UW7c5i4Exu1Gffz+lIF69VFu3OYuBMbtRn38/pSBevVRbtzmLgTG7UZ9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P/7/fz///38///9/P6eZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsaeZErL3III9CjiPsY4bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/b/WHvCFvZb3fxFo/jhsEP2/1h7whb2W938RaP44bBD9v9Ye8IW9lvd/EWj+OGwQ/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AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP/7/fz8AAIA/AQCAP54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WL54O+LHYAsQ88W6WLzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPzoSDLSK16OpiSyntAAAgD86Egy0itejqYksp7QAAIA/OhIMtIrXo6mJLKe0AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9AAAAAAAAAAC+ooc9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cYN/P/9M8jBS438z7EzyvFTjfz+LHQIw7P1/M4cdArzv/X8//v//D/z/fzMAAAAcAACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAP///fz///38/AACAP/3/fz/+/38/AACAPwAAgD///38/AACAPwAAgD///38/AACAP///fz///38/AACAP///fz///38/AACAP///fz///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwEAgD///38/AACAP///fz///38/AACAP///fz///38/AACAPwAAgD///38/AACAP///fz/+/38/AACAPwAAgD8AAIA/AACAPwEAgD///38/AACAPwEAgD8AAIA/AACAPwAAgD///38/AACAP/7/fz///38/AACAPwAAgD8AAIA/AACAP///fz///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAP///fz///38/AACAPwEAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD///38/AACAP///fz///38/AACAPwAAgD///38/AACAPwAAgD/+/38/AACAPwAAgD/9/38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD///38/AACAPwAAgD8AAIA/AACAP/3/fz/+/38/AACAPwAAgD8AAIA/AACAPwAAgD///38/AACAPwEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKAEAgJw8Ou09AQAAKKj//zBCOu09AAAApwDAFag/Ou09AAAUqACA3KdKOu09AACMqAAAgqdIOu09AACIqP//vzJBOu09AACgJwEAADJCOu09AAAAp////7JAOu09//9/p///v7JAOu09//8/GwEAgDI/Ou09AQAgp/3/f7JBOu09AABgp///37I/Ou09/v+/pgYAgDE+Ou09AABAJwAAJCdCOu09AAAAp/z//7FCOu09AABAp/7//7E+Ou09/v9/GgAAwLJDOu09AgCAJgEAgLJBOu09AAAAKACApKdAOu09AABgKN//fzFAOu09AADAp70AALBBOu09AADYpsT/fzE4Ou09AAAgJ/f//zI8Ou09AAAQKOz/fzJBOu09AAAAKAoAgLJCOu09////pvv/PzM+Ou09//+fJ9X//zE+Ou09AADAp9b//zE+Ou09AADAJwAApKhBOu09AAAQKAoAoLJAOu09AADgpwUAMLM9Ou09/f//pq//fzFEOu09AACAJgoAwLJAOu09AADAp+v/fzJAOu09AAAgKBUAALI8Ou09AAAgKNf//zFAOu09//9/Jtv//zFDOu09AAAQKOD//zE6Ou09AABAJ8j/fzFCOu09AAAEKABIRKhCOu09AABiqAEAgJw8Ou09AQAAKIAAAIAAAIAnAACAkAAAgD+AAACAAACAJwAAgJAAAIA/gAAAgAAAgCcAAICQAACAP4AAAIAAAIAnAACAkAAAgD+AAACAAACAJwAAgJAAAIA/gAAAgAAAgCcAAICQAACAP4AAAIAAAIAnAACAkAAAgD+AAACAAACAJwAAgJAAAIA/gAAAgAAAgCcAAICQAACAP4AAAIAAAIAnAACAkAAAgD+AAACAAACAJwAAgJAAAIA/AACAmAAAuCcAAICwAACAPwAA4KcAAIAnAAAAMgAAgD8AAICnAACAJwAAgA8AAIA/AABApwAAoCcAAICyAACAPwAAACcAAEAnAAAAjwAAgD8AAIAmAACIJwAAgDIAAIA/AACApgAAkCcAAACyAACAPwAAgCYAADAnAAAAMgAAgD8AAECmAAB4JwAAQA4AAIA/AAAAAAAAmCcAAAAAAACAPwAAgKUAAIwnAACADQAAgD8AAIAmAACgJ////7EAAIA/AAAAAAAAcCcAAAAAAACAPwAAAAAAAKAnAAAAAAAAgD8AAIAmAACQJwAAgI4AAIA/AAAAAAAAkCcAAAAAAACAPwIAgCYAANAnAgCAjgAAgD8BACAoAQDgJwEAALIAAIA/AAAAJwAAoCcAAACPAACAPwAA/KcAAAgnAAD8DwAAgD8AAGAnAAAgKAAAYI8AAIA///9/JgAAgCcAAICyAACAPwEAgCcAAIAnAQAAsgAAgD8AAKCnAAAAJwAAoA8AAIA/AACApgAAoKcAAICyAACAPwAAQKcAAACnAAAAsgAAgD8AAAAAAAAkKAAAAAAAAIA/AAAApgAAWCcAAAAOAACAPwAAAKYAAIQnAAAADgAAgD8AAIAZAABIKAAAgDEAAIA/AAAAmgAAgKcAAACyAACAP///f6YAAMCmAAAAMgAAgD8AAICnAACAJwAAgA8AAIA/AACAJgAAoCcAAICOAACAP/z/fyb8/3+n/P9/jgAAgD8AAICmAQDAJ///fw4AAIA/AACApgAAgCYAAIAOAACAPwAAAKYAAACmAAAADgAAgD8AAHinAAC8JwAAgDAAAIA/gAAAgAAAgCcAAICQAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP///fz///38/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP///fz///38/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwEAgD8BAIA/AACAP///fz///38/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/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t/8/rm71ni3e64k8tf5vrvB1ZTKSYSuxdCIAMgAAgD/wdWUykmErsXQiADIAAIA/8HVlMpJhK7F0IgAyAACAP/B1ZTKSYSuxdCIAMgAAgD/wdWUykmErsXQiADIAAIA/8HVlMpJhK7F0IgAyAACAP/B1ZTKSYSuxdCIAMgAAgD/wdWUykmErsXQiADIAAIA/8HVlMpJhK7F0IgAyAACAP/B1ZTKSYSuxdCIAMgAAgD/wdWUykmErsXQiADIAAIA/8LVjMmNiJbGuPIAyAACAP/D1ZDKCYiWxAADsLQAAgD/wdWgyUmIlsZw3ADIAAIA/8MVjMtJhJrEA2H6xAACAP/BiYDKy4Sex0NGAMAAAgD/we2UyMmInscg5ATEAAIA/8HVjMjJiJbGwo/+xAACAP/BVYzKSYiOxfFP/sQAAgD/wNWMy8mElsXhw/7EAAIA/8DVkMrJhJ7EAEHgsAACAP/D1YTLSYSWxAG6OLQAAgD/w9WMy8mAtsb66/zEAAIA/8BVlMlJiIbFZSAAyAACAP/DlYTJSYiexALCkLQAAgD/wrWQysmEnsQCwgiwAAIA/sJxkMtJBJ7EACEwtAACAP/ARZTIy4iWxARgDLQAAgD/w1WMyUmEmsebxP7IAAIA/8NVhMvJhJ7FIn/+xAACAP/D1ZTIyYiWxANTMLQAAgD/w9WEyVGEtsRLwvzIAAIA/8HVlMgRiLbFkEMAyAACAP/B1ZDLDYC2xA4d/MgAAgD/w9WAylGElsRICADMAAIA/8PVhMsNhJbHvC4AyAACAP/A1ZDKxYiexMod/sgAAgD/wlWQykmArsVAa/zEAAIA/8HVkMnNiKbE/LIAyAACAP/AVZjLiYSWxQKd/sQAAgD/whWMy0mEnsQAwFiwAAIA/8LViMoJhKbFUh38xAACAP/B1YTJiYiWxAJrALQAAgD/wtWEygmIlsQC8yy0AAIA/8HViMgRhLbE04L8yAACAP/B1YjKEYjGxli/AMgAAgD/wdWUygmEpscAWADIAAIA/8PViMlNjKbEza4AyAACAP/B1ZDLkYSmxiBvAMgAAgD/w9WMys2EpsY/8fzIAAIA/8HVlMpJhK7F0IgAyAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP/7/fz///38///9/P///fz8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AQCAPwAAgD8AAIA///9/PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwEAgD8BAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD///38///9/PwAAgD///38/AACAPwAAgD///38/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AQCAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA///9/PwAAgD8AAIA/AACAPwAAgD///38///9/PwAAgD8AAIA/AACAP///fz///38///9/PwAAgD///38///9/PwAAgD8AAIA///9/PwEAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD///38///9/P///fz8AAIA/AACAPwAAgD///38/AACAPwAAgD8AAIA/AACAP///fz///38/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP/RhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPRhD71v0JE95rE2sPNhD7110JE9C7I2sPZhD7100JE95rE2sPVhD71v0JE9ybE2sPVhD71w0JE9TbE2sPJhD71w0JE987E2sPJhD71v0JE9A7I2sO9hD71y0JE9a7I2sOthD71y0JE9u7E2sPdhD71q0JE9TLE2sPthD71t0JE9/7E2sPxhD71v0JE9DrI2sPNhD71z0JE9S7I2sPFhD71y0JE9GrI2sPVhD71s0JE90bE2sPFhD71y0JE96LE2sO1hD71q0JE9MrI2sPRhD7110JE927E2sPVhD71x0JE9GLI2sPdhD71w0JE9ArI2sPVhD71y0JE9KrI2sPRhD7110JE9QbI2sPVhD7100JE9KLI2sPJhD7110JE9b7I2sPNhD71u0JE9Y7E2sPNhD71w0JE94bE2sPphD7100JE9RrI2sPthD71y0JE90LE2sPthD7100JE9ybE2sPZhD71y0JE9FbI2sPphD71w0JE9R7I2sOthD71u0JE9ZLE2sABiD7120JE9WrI2sABiD71v0JE9crI2sPlhD71u0JE9n7E2sPZhD71s0JE9rrE2sPdhD71v0JE9zLE2sPVhD71w0JE9erE2sPVhD71u0JE947E2sPZhD71o0JE9TrE2sPRhD71v0JE95rE2sHtyQrNpTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/e3JCs2lO0jLvmig/lqJAP3tyQrNpTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/e3JCs2lO0jLvmig/lqJAP3tyQrNpTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/e3JCs2lO0jLvmig/lqJAP3tyQrNpTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/e3JCs2pO0jLvmig/lqJAP3pyQrNoTtIy75ooP5aiQD98ckKzbE7SMu+aKD+WokA/e3JCs2pO0jLvmig/lqJAP3xyQrNqTtIy75ooP5aiQD97ckKza07SMu6aKD+XokA/e3JCs2pO0jLvmig/lqJAP3pyQrNoTtIy7pooP5eiQD97ckKzaE7SMu+aKD+WokA/e3JCs2xO0jLvmig/lqJAP3pyQrNoTtIy75ooP5aiQD97ckKzaE7SMu6aKD+XokA/e3JCs2ZO0jLvmig/lqJAP3pyQrNsTtIy75ooP5aiQD96ckKzaU7SMu+aKD+WokA/fHJCs21O0jLvmig/lqJAP3pyQrNmTtIy75ooP5aiQD98ckKzaU7SMu+aKD+WokA/fHJCs2xO0jLvmig/lqJAP3xyQrNsTtIy75ooP5aiQD97ckKzak7SMu+aKD+WokA/fHJCs2xO0jLvmig/lqJAP3xyQrNsTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/fHJCs2lO0jLvmig/lqJAP3tyQrNqTtIy75ooP5aiQD96ckKzbU7SMu+aKD+WokA/enJCs2lO0jLvmig/lqJAP3tyQrNpTtIy75ooP5aiQD96ckKzZ07SMu6aKD+XokA/e3JCs2pO0jLvmig/lqJAP3pyQrNqTtIy75ooP5aiQD98ckKzak7SMu+aKD+WokA/enJCs2hO0jLvmig/lqJAP3pyQrNsTtIy75ooP5aiQD97ckKzbE7SMu+aKD+WokA/enJCs2lO0jLvmig/lqJAP3tyQrNsTtIy75ooP5aiQD97ckKzaU7SMu+aKD+WokA/e3JCs2lO0jLvmig/lqJAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/8/38/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/9/38/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/9/38/AACAPwAAgD/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAPwAAgD/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/+/38/AACAPwAAgD/+/38/AACAP/7/fz/+/38/AACAPwAAgD/+/38/AACAPwAAgD/+/38/AACAPwAAgD8AAIA/AACAP/7/fz8AAIA/AACAPwAAgD8AAIA/AACAP/7/fz/+/38/AACAP/7/fz8AAIA/AACAP/7/fz/+/38/AACAPwAAgD8AAIA/AACAP/7/fz/+/38/AACAP/7/fz/9/38/AACAP/7/fz/+/38/AACAP/7/fz/9/38/AACAP/7/fz/+/38/AACAP/7/fz/+/38/AACAPw+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09omqQsA+fmbLfnQ09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/v9/P/3/fz/9/38//v9/P/z/fz/9/38//v9/P/z/fz///38//v9/P/7/fz/9/38//v9/P///fz8AAIA//v9/P/7/fz///38//v9/P/3/fz/8/38//v9/P/z/fz/9/38//v9/P/3/fz/9/38//v9/P/z/fz8AAIA//v9/P/7/fz/9/38//v9/P/3/fz/9/38//v9/P/z/fz/8/38//v9/P/7/fz///38//v9/P/3/fz/9/38//v9/P/3/fz/9/38//v9/P/z/fz/9/38//v9/P/7/fz8AAIA//v9/P/3/fz/9/38//v9/P/3/fz/9/38//v9/P/7/fz///38//v9/P/7/fz/9/38//v9/P///fz/9/38//v9/P/3/fz/9/38//v9/P///fz/9/38//v9/P/3/fz/9/38//v9/P/3/fz/9/38//v9/P/7/fz///38//v9/P/3/fz/9/38//v9/P/3/fz/7/38//v9/P/z/fz/9/38//v9/P/3/fz/9/38//v9/P/z/fz/7/38//v9/P/3/fz/9/38//v9/P/z/fz/9/38//v9/PxfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvRfrCjthCim9b+pZvafqCjthCim9b+pZvbrqCjtkCim9b+pZvT7qCjtlCim9b+pZvZfqCjtnCim9b+pZvX3qCjtrCim9cOpZvfnpCjtmCim9b+pZvZDqCjtpCim9cOpZvcPqCjtnCim9b+pZvZzrCjttCim9b+pZvV3qCjtmCim9cOpZvRXqCjtnCim9cOpZvcnpCjtpCim9b+pZvZfqCjtwCim9bupZvcvqCjtiCim9b+pZvYnqCjtoCim9b+pZveDpCjtsCim9bepZvcfpCjtcCim9bupZvQjrCjtlCim9b+pZvdnpCjtkCim9b+pZvaHqCjthCim9cOpZvR3rCjtmCim9b+pZvZ/qCjtqCim9cOpZvfrqCjtiCim9b+pZvarqCjtfCim9b+pZveTpCjtuCim9bupZvRPqCjtnCim9b+pZvRLqCjtjCim9bupZvb7qCjtmCim9b+pZvVTqCjtrCim9b+pZvYHqCjtkCim9bupZvXrqCjtjCim9b+pZvWTqCjtoCim9b+pZvSjqCjtmCim9b+pZve3pCjtzCim9bupZvRfqCjtnCim9cOpZvULrCjtgCim9b+pZvYHqCjtnCim9cepZvW3rCjtkCim9b+pZvZ3qCjtqCim9b+pZvRfrCjthCim9b+pZvUcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5IE7e+KWQcv0q2Gz+w37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkgTt74pZBy/SrYbP6/ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+SBO3vilkHL9Kths/r9+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+SBO3vilkHL9Kths/sN+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+SBO3vilkHL9Kths/sN+3PkcTt74qZBy/SbYbP67ftz5IE7e+KWQcv0q2Gz+w37c+SBO3vilkHL9Kths/r9+3PkgTt74pZBy/SrYbP7Dftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Kths/rt+3PkcTt74qZBy/SbYbP67ftz5HE7e+KmQcv0m2Gz+u37c+RxO3vipkHL9Jths/rt+3PkcTt74qZBy/SrYbP67ftz5HE7e+KmQcv0q2Gz+u37c+RxO3vipkHL9Jths/rt+3Pv//fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//v9/P/3/fz8AAIA//f9/P///fz8AAIA//v9/P///fz8AAIA//f9/P/3/fz8AAIA//v9/P///fz8AAIA//f9/P/3/fz8AAIA//v9/P///fz8AAIA/AACAP///fz/+/38//f9/PwAAgD8AAIA/AACAPwAAgD8AAIA//P9/PwAAgD8AAIA/AACAP/3/fz/+/38//v9/P/3/fz8AAIA//v9/P/3/fz8AAIA//v9/P///fz/+/38//f9/PwAAgD/+/38//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/PwAAgD8AAIA//v9/P///fz8AAIA//f9/PwAAgD8AAIA//v9/P///fz/+/38//P9/P/3/fz8AAIA//P9/P///fz8AAIA//v9/PwAAgD8AAIA//v9/P///fz8AAIA/AACAP///fz8AAIA/AACAP///fz8AAIA/AACAP/3/fz8AAIA//v9/P///fz8AAIA//v9/P///fz8AAIA/AACAPwAAgD8AAIA//f9/P///fz8AAIA//v9/P///fz8AAIA//f9/P///fz8AAIA//v9/P///fz8BAIA/AACAP///fz8AAIA//f9/P///fz8AAIA//f9/P///fz8AAIA//f9/P0TwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbskTwELJrnDg9Q2SbshfPArJrnDg9nUn7sgdz+7JsnDg9V67qssE/orFsnDg95jaqs8MvZDBtnDg9dQppskgyirJtnDg98O4UMd0vTbFunDg9jWNJsj/dhrJunDg990sNs0nRfTJsnDg9ZuiAsvyqgLBsnDg9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/v9/P/3/fz///38//v9/P/7/fz/9/38//v9/P///fz8AAIA//v9/P/7/fz///38//v9/P/3/fz/9/38//v9/P/7/fz/9/38//v9/P/7/fz///38//v9/P///fz/9/38//v9/P/3/fz/9/38//v9/P/7/fz/9/38//v9/P/3/fz/9/38//v9/P/3/fz/9/38//v9/P/3/fz///38//v9/P/3/fz/9/38//v9/P/z/fz/9/38//v9/P/z/fz/9/38//v9/P/3/fz/8/38//v9/P/3/fz/9/38//v9/P///fz8AAIA//v9/P/z/fz/9/38//v9/PxfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvRfrCrthCim9b+pZvT/rCrtlCim9b+pZvULqCrtnCim9b+pZvU3qCrtvCim9bupZvTDqCrteCim9b+pZvaLqCrtiCim9b+pZvW3qCrtpCim9b+pZvT7qCrtkCim9cOpZvczqCrtoCim9b+pZvYHqCrtcCim9b+pZvenqCrtkCim9cOpZvYPqCrtgCim9cOpZvXDqCrtiCim9b+pZvbjqCrtqCim9b+pZvXrqCrtfCim9bupZvWDqCrtkCim9b+pZvQrqCrtmCim9bupZvffpCrtoCim9b+pZvc3qCrtnCim9b+pZvXvrCrtwCim9cepZvWLqCrttCim9b+pZvcnpCrtoCim9bepZvbDqCrtkCim9b+pZvcXqCrtsCim9cOpZvX3qCrtrCim9cOpZvWfqCrtqCim9b+pZvfvpCrthCim9b+pZvcDqCrtlCim9b+pZvUvqCrttCim9cOpZvYrrCrtqCim9b+pZvbPqCrtvCim9b+pZvVnqCrtuCim9bupZvfPqCrtnCim9b+pZvX/qCrtbCim9b+pZvWzqCrtnCim9bupZvTXqCrtrCim9b+pZvZHqCrtpCim9cOpZvTfqCrtlCim9b+pZvUbqCrtoCim9b+pZvW/qCrtjCim9b+pZvRfrCrthCim9b+pZvUcTt74qZBw/SbYbv67ftz5HE7e+KmQcP0m2G7+u37c+RxO3vipkHD9Jthu/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zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JveIv2KKQLSM9zK/JvfQENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAACZjJs9AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP7Fmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PbFmjj3nEEs9hjV7PSNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PyNdBjirB4q73O8wOmf/fz8jXQY4qweKu9zvMDpn/38/I10GOKsHirvc7zA6Z/9/PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAk7HzHQc9AABkMAAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAACwAAMCnAAAArgAAgD8AAAAsAADApwAAAK4AAIA/AAAALAAAwKcAAACuAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP7Fmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vbFmjj2CQ0y6Fp4+vdu4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P9u4gTc71Re7eaHCOdL/fz/buIE3O9UXu3mhwjnS/38/27iBNzvVF7t5ocI50v9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/P/3/fz+0/38/tv9/PwCAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMACAjbH3HQc9ANCkMAAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAYCsAAGopAgCmKgAAgD8AAGArAABqKQIApioAAIA/AABgKwAAaikCAKYqAACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwAAgD8BAIA/AACAPwPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuQPfh723Z7I8G1VXuSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPSrAjzrMBqU9c5l+PxYgiD0qwI86zAalPXOZfj8WIIg9KsCPOswGpT1zmX4/FiCIPRUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAPxUAgD8CAIA/HwCAP0G75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sUG75DHhCqQ9Aq16sZBd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+P5Bd8L2p33Q5FWqvOx46fj+QXfC9qd90ORVqrzseOn4/kF3wvanfdDkVaq87Hjp+PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/PwAAgD/3/38/+f9/P2fBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MGfBGTGa8FM8kdG2MHseXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/P3seXr1WW7e5zn8xO1Offz97Hl69Vlu3uc5/MTtTn38/ex5evVZbt7nOfzE7U59/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PwAAgD/9/38//P9/PxwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsRwVTTL4III9KBaYsYwbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaP4wbBD+U9Yc8Sm9lPeTEWj+MGwQ/lPWHPEpvZT3kxFo/jBsEP5T1hzxKb2U95MRaPwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/PwUAgD8MAIA/+f9/P9SRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02MdSRgjH5dwo9xi02Ma9vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP69vpz53ZumzYJUttJLscT+vb6c+d2bps2CVLbSS7HE/r2+nPndm6bNglS20kuxxP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP/7/fz///38/AQCAP7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72L7ywdrDaAsQ81s72LzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPzoSDLQHHQkpiSynNAAAgD86Egy0Bx0JKYkspzQAAIA/OhIMtAcdCSmJLKc0AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAApDHzHQc9AACNsAAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAECyA/3+vAAAALgAAgD8AABAsgP9/rwAAAC4AAIA/AAAQLID/f68AAAAuAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP7Fmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vbFmjr2CQ0y6uJw+vWgblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P2gblDcR1Rc7BZ7CudL/fz9oG5Q3EdUXOwWewrnS/38/aBuUNxHVFzsFnsK50v9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/P/3/fz+1/38/t/9/PwBAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsABAcLL1HQc9ALDlsAAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAQCsA4P8uABB6LQAAgD8AAEArAOD/LgAQei0AAIA/AABAKwDg/y4AEHotAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP+Zqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOeZqrz14++I84MMhOfQENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP3DbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPXDbELx8QaS9yFGZPQAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP27UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP+Zqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOeZqr714++I84MMhOfQENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1P/QENb8AAAAAAAAAAPIENT/0BDW/AAAAAAAAAADyBDU/9AQ1vwAAAAAAAAAA8gQ1PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP3DbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPXDbEDx8QaS9yFGZPQAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAAIAAAACAAACAvy69OzMAAACAAAAAgAAAgL8uvTszAAAAgAAAAIAAAIC/Lr07MwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP27UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAG7UjLGA+zA9AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAgEvvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAP0vvrrMAAAAAAAAAgAAAgD9L766zAAAAAAAAAIAAAIA/S++uswAAAAAAAACAAACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPw==" + } + ] +} diff --git a/Assets/Models/racoon.shmodel b/Assets/Models/racoon.shmodel new file mode 100644 index 00000000..9c5d866a Binary files /dev/null and b/Assets/Models/racoon.shmodel differ diff --git a/Assets/Models/racoon.shmodel.shmeta b/Assets/Models/racoon.shmodel.shmeta new file mode 100644 index 00000000..96d1291c --- /dev/null +++ b/Assets/Models/racoon.shmodel.shmeta @@ -0,0 +1,10 @@ +Name: racoon +ID: 77816045 +Type: 4 +Sub Assets: +Name: Bag +ID: 144838771 +Type: 8 +Name: Raccoon +ID: 149697411 +Type: 8 diff --git a/Assets/RaccoonBag_Color_Ver4.dds b/Assets/RaccoonBag_Color_Ver4.dds new file mode 100644 index 00000000..a229bc00 Binary files /dev/null and b/Assets/RaccoonBag_Color_Ver4.dds differ diff --git a/Assets/RaccoonBag_Color_Ver4.shtex b/Assets/RaccoonBag_Color_Ver4.shtex new file mode 100644 index 00000000..effba95a Binary files /dev/null and b/Assets/RaccoonBag_Color_Ver4.shtex differ diff --git a/Assets/RaccoonBag_Color_Ver4.shtex.shmeta b/Assets/RaccoonBag_Color_Ver4.shtex.shmeta new file mode 100644 index 00000000..d386e9a4 --- /dev/null +++ b/Assets/RaccoonBag_Color_Ver4.shtex.shmeta @@ -0,0 +1,3 @@ +Name: RaccoonBag_Color_Ver4 +ID: 58303057 +Type: 3 diff --git a/Assets/RaccoonPreTexturedVer1_Base9.dds b/Assets/RaccoonPreTexturedVer1_Base9.dds new file mode 100644 index 00000000..1a35b4de Binary files /dev/null and b/Assets/RaccoonPreTexturedVer1_Base9.dds differ diff --git a/Assets/RaccoonPreTexturedVer1_Base9.shtex b/Assets/RaccoonPreTexturedVer1_Base9.shtex new file mode 100644 index 00000000..38e0cd0f Binary files /dev/null and b/Assets/RaccoonPreTexturedVer1_Base9.shtex differ diff --git a/Assets/RaccoonPreTexturedVer1_Base9.shtex.shmeta b/Assets/RaccoonPreTexturedVer1_Base9.shtex.shmeta new file mode 100644 index 00000000..e1a72340 --- /dev/null +++ b/Assets/RaccoonPreTexturedVer1_Base9.shtex.shmeta @@ -0,0 +1,3 @@ +Name: RaccoonPreTexturedVer1_Base9 +ID: 64651793 +Type: 3 diff --git a/Assets/Scenes/M2Scene.shade b/Assets/Scenes/M2Scene.shade new file mode 100644 index 00000000..bf910737 --- /dev/null +++ b/Assets/Scenes/M2Scene.shade @@ -0,0 +1,226 @@ +- EID: 0 + Name: Camera + IsActive: true + NumberOfChildren: 0 + Components: + Camera Component: + Position: {x: 0, y: 0, z: 8} + Pitch: 0 + Yaw: 0 + Roll: 0 + Width: 1920 + Height: 1080 + Near: 0.00999999978 + Far: 10000 + Perspective: true + Light Component: + Position: {x: 0, y: 0, z: 0} + Type: Directional + Direction: {x: 1.79999995, y: 0, z: 1} + Color: {x: 0.951541841, y: 0.921719015, z: 0.553319454, w: 1} + Layer: 4294967295 + Strength: 0 + Scripts: ~ +- EID: 1 + Name: Floor + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -1.440328, y: -4.41369677, z: -5} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 49.4798889, y: 0.5, z: 17.5} + Renderable Component: + Mesh: 149697411 + Material: 126974645 + RigidBody Component: + Type: Static + Mass: 1 + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 1, y: 1, z: 1} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Scripts: ~ +- EID: 10 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -4.40482807, y: 2.57871056, z: -5.21213436} + Rotate: {x: -0.361265004, y: 1.11661232, z: -0.626627684} + Scale: {x: 0.999982238, y: 0.999987125, z: 0.999981165} + RigidBody Component: + Type: Dynamic + Mass: 1 + Drag: 0 + Angular Drag: 0 + Use Gravity: true + Interpolate: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 1, y: 1, z: 1} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Scripts: ~ +- EID: 3 + Name: Empty + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.0094268322, y: 0, z: 0} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + Scripts: ~ +- EID: 4 + Name: Empty2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 0, z: 0} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + Scripts: ~ +- EID: 9 + Name: Bag + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 0, z: 0} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + Renderable Component: + Mesh: 144838771 + Material: 123745521 + Scripts: ~ +- EID: 6 + Name: AI + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -8, y: -2, z: 2.5} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + Renderable Component: + Mesh: 149697411 + Material: 126974645 + RigidBody Component: + Type: Dynamic + Mass: 1 + Drag: 0 + Angular Drag: 0 + Use Gravity: true + Interpolate: false + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: true + Freeze Rotation Y: true + Freeze Rotation Z: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.5, y: 0.5, z: 0.5} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0.5, z: 0} + Scripts: ~ +- EID: 7 + Name: BigBoi + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: -16.8647861, z: -14.039052} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 28.1434975, y: 28.1434975, z: 28.1434975} + Renderable Component: + Mesh: 149697411 + Material: 126974645 + Scripts: ~ +- EID: 8 + Name: AmbientLight + IsActive: true + NumberOfChildren: 0 + Components: + Light Component: + Position: {x: 0, y: 0, z: 0} + Type: Ambient + Direction: {x: 0, y: 0, z: 1} + Color: {x: 1, y: 1, z: 1, w: 1} + Layer: 4294967295 + Strength: 0.25 + Scripts: ~ +- EID: 5 + Name: item + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: -2, z: -5} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 2, y: 2, z: 2} + Renderable Component: + Mesh: 144838771 + Material: 123745521 + RigidBody Component: + Type: Dynamic + Mass: 1 + Drag: 0 + Angular Drag: 0 + Use Gravity: true + Interpolate: false + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: true + Freeze Rotation Y: true + Freeze Rotation Z: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 1, y: 1, z: 1} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0.5, z: 0} + - Is Trigger: true + Type: Box + Half Extents: {x: 2, y: 2, z: 2} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0.5, z: 0} + Scripts: ~ \ No newline at end of file diff --git a/Assets/Scenes/M2Scene.shade.shmeta b/Assets/Scenes/M2Scene.shade.shmeta new file mode 100644 index 00000000..9289949c --- /dev/null +++ b/Assets/Scenes/M2Scene.shade.shmeta @@ -0,0 +1,3 @@ +Name: M2Scene +ID: 94283040 +Type: 5 diff --git a/Assets/Scenes/Scene2.shade b/Assets/Scenes/Scene2.shade new file mode 100644 index 00000000..2f38a933 --- /dev/null +++ b/Assets/Scenes/Scene2.shade @@ -0,0 +1,72 @@ +- EID: 0 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 0.304069757, z: 1.73034382} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + Camera Component: + Position: {x: 0, y: 0.304069757, z: 1.73034382} + Pitch: 0 + Yaw: 0 + Roll: 0 + Width: 1200 + Height: 1080 + Near: 0.00999999978 + Far: 10000 + Perspective: true + Scripts: ~ +- EID: 1 + Name: Raccoon + IsActive: true + NumberOfChildren: 1 + Components: + Transform Component: + Translate: {x: 0, y: 0, z: 0} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + Renderable Component: + Mesh: 149697411 + Material: 126974645 + Scripts: ~ +- EID: 3 + Name: Bag + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0.006237939, y: -0.000393368304, z: 0} + Rotate: {x: -0, y: 2.79945588, z: 0} + Scale: {x: 1.0000881, y: 1, z: 1.0000881} + Renderable Component: + Mesh: 144838771 + Material: 123745521 + Scripts: ~ +- EID: 2 + Name: DirectionalLight + IsActive: true + NumberOfChildren: 0 + Components: + Light Component: + Position: {x: 0, y: 0, z: 0} + Type: Directional + Direction: {x: 0, y: 0, z: 1} + Color: {x: 1, y: 1, z: 1, w: 1} + Layer: 4294967295 + Strength: 0 + Scripts: ~ +- EID: 4 + Name: AmbientLight + IsActive: true + NumberOfChildren: 0 + Components: + Light Component: + Position: {x: 0, y: 0, z: 0} + Type: Ambient + Direction: {x: 0, y: 0, z: 1} + Color: {x: 1, y: 1, z: 1, w: 1} + Layer: 4294967295 + Strength: 0.600000024 + Scripts: ~ \ No newline at end of file diff --git a/Assets/Scenes/Scene2.shade.shmeta b/Assets/Scenes/Scene2.shade.shmeta new file mode 100644 index 00000000..bbcbc66c --- /dev/null +++ b/Assets/Scenes/Scene2.shade.shmeta @@ -0,0 +1,3 @@ +Name: Scene2 +ID: 87285316 +Type: 5 diff --git a/Assets/Scripts/AIPrototype.cs b/Assets/Scripts/AIPrototype.cs new file mode 100644 index 00000000..62255778 --- /dev/null +++ b/Assets/Scripts/AIPrototype.cs @@ -0,0 +1,183 @@ +鏤using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using SHADE; + +public class AIPrototype : Script +{ + //This object's relevant components + private Transform transform; + private RigidBody rb; + + /*[SerializeField] + [Tooltip("The list of waypoints that the object will move around on")] + private Vector3[] waypoints;*/ + + private Vector3[] waypoints = { new Vector3(-8.0f, -2.0f, 3.5f), new Vector3(-8.0f, -2.0f, -13.0f), new Vector3(8.0f, -2.0f, -13.0f), new Vector3(8.0f, -2.0f, 3.5f) }; + + [SerializeField] + [Tooltip("How much force is applied in movement")] + private float movementForceMultiplier = 100.0f; + + [SerializeField] + [Tooltip("How fast the object moves about waypoints")] + private float patrolSpeed = 0.4f; + + [SerializeField] + [Tooltip("How fast the object moves while chasing")] + private float chaseSpeed = 0.8f; + + [SerializeField] + [Tooltip("How near the player must be to the AI for capture")] + private float distanceToCapture = 1.2f; + + [SerializeField] + [Tooltip("How near the player must be for the chase to begin. Should be less than distanceToEndChase")] + private float distanceToStartChase = 2.0f; + + [SerializeField] + [Tooltip("How far the player must be for the chase to end. Should be greater than distanceToStartChase")] + private float distanceToEndChase = 2.5f; + + //Whether the AI is chasing or not + private bool chaseMode; + + //To cycle depending on the length of waypoints + private int currentTargetWaypointIndex; + + private GameObject? player; + + + protected override void awake() + { + transform = GetComponent(); + if (transform == null) + { + Debug.LogError("Transform is NULL!"); + } + + rb = GetComponent(); + if (rb == null) + { + Debug.LogError("Rigidbody is NULL!"); + } + + currentTargetWaypointIndex = 0; + + player = GameObject.Find("Player"); + if (player == null) + { + Debug.LogError("Player is NULL!"); + } + + chaseMode = false; + } + + protected override void fixedUpdate() + { + //Patrolling + if (!chaseMode) + { + //Head towards the next target + Vector3 normalisedDifference = waypoints[currentTargetWaypointIndex] - transform.GlobalPosition; + normalisedDifference /= normalisedDifference.GetMagnitude(); + + //transform.GlobalPosition += normalisedDifference * moveSpeed * (float)Time.DeltaTime; + //rb.LinearVelocity = normalisedDifference * patrolSpeed; + + //ORIGINAL INTENDED CODE + /*rb.AddForce(new Vector3(normalisedDifference.x, 0.0f, normalisedDifference.z) * movementForceMultiplier); + float currentSpeed = MathF.Sqrt(rb.LinearVelocity.x * rb.LinearVelocity.x + rb.LinearVelocity.z * rb.LinearVelocity.z); + if (currentSpeed > patrolSpeed) + { + float adjustmentFactor = patrolSpeed / currentSpeed; + Vector3 adjustedVelocity = rb.LinearVelocity; + //adjustedVelocity *= adjustmentFactor; + adjustedVelocity.x = patrolSpeed; + adjustedVelocity.z = patrolSpeed; + rb.LinearVelocity = adjustedVelocity; + }*/ + + //TODO delete this when original intended code above works with velocity being limited correctly + rb.LinearVelocity = normalisedDifference * patrolSpeed; + + //transform.GlobalRotation.SetLookRotation(waypoints[currentTargetWaypointIndex], Vector3.Up); + + //Cycle to next waypoint if near enough current waypoint + if ((waypoints[currentTargetWaypointIndex] - transform.GlobalPosition).GetSqrMagnitude() <= 0.5f) + { + ++currentTargetWaypointIndex; + if (currentTargetWaypointIndex >= waypoints.Length) + { + currentTargetWaypointIndex = 0; //Recycle + } + } + + //Go chase if near enough to player + if (player != null) + { + Transform pTransform = player.GetValueOrDefault().GetComponent(); + if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() <= distanceToStartChase) + { + //Start the chase + chaseMode = true; + } + } + } + else //Chasing + { + if (player != null) + { + Transform pTransform = player.GetValueOrDefault().GetComponent(); + + //Chase the player + Vector3 normalisedDifference = pTransform.GlobalPosition - transform.GlobalPosition; + normalisedDifference /= normalisedDifference.GetMagnitude(); + + //transform.GlobalPosition += normalisedDifference * moveSpeed * (float)Time.DeltaTime; + + //ORIGINAL INTENDED CODE + /*rb.AddForce(new Vector3(normalisedDifference.x, 0.0f, normalisedDifference.z) * movementForceMultiplier); + float currentSpeed = MathF.Sqrt(rb.LinearVelocity.x * rb.LinearVelocity.x + rb.LinearVelocity.z * rb.LinearVelocity.z); + if (currentSpeed > chaseSpeed) + { + float adjustmentFactor = chaseSpeed / currentSpeed; + Vector3 adjustedVelocity = rb.LinearVelocity; + adjustedVelocity *= adjustmentFactor; + rb.LinearVelocity = adjustedVelocity; + }*/ + + //TODO delete this when original intended code above works with velocity being limited correctly + rb.LinearVelocity = normalisedDifference * chaseSpeed; + + //Capture player if near enough + if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() <= distanceToCapture) + { + player.GetValueOrDefault().GetScript().currentState = PlayerController.RaccoonStates.CAUGHT; + } + + //End chase if too far + if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() >= distanceToEndChase) + { + //Stop the chase + chaseMode = false; + + //Find the nearest waypoint to go instead + float nearestWaypointDistance = 99999999999999.9f; + for (int i = 0; i < waypoints.Length; ++i) + { + if ((waypoints[i] - transform.GlobalPosition).GetSqrMagnitude() < nearestWaypointDistance) + { + nearestWaypointDistance = waypoints[i].GetSqrMagnitude(); + currentTargetWaypointIndex = i; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/AIPrototype.cs.shmeta b/Assets/Scripts/AIPrototype.cs.shmeta new file mode 100644 index 00000000..80a7d4b4 --- /dev/null +++ b/Assets/Scripts/AIPrototype.cs.shmeta @@ -0,0 +1,3 @@ +Name: AIPrototype +ID: 163215061 +Type: 9 diff --git a/Assets/Scripts/CameraControl.cs b/Assets/Scripts/CameraControl.cs new file mode 100644 index 00000000..b25d65eb --- /dev/null +++ b/Assets/Scripts/CameraControl.cs @@ -0,0 +1,21 @@ +鏤using System; +using SHADE; + +namespace SHADE_Scripting +{ + public class CameraControl :Script + { + public float turnSpeed = 0.5f; + + protected override void update() + { + //Camera + Camera cam = GetComponent(); + Vector2 mouseVel = Input.GetMouseVelocity(); + + cam.Pitch -= mouseVel.y * turnSpeed * (float)Time.DeltaTime; + cam.Yaw += mouseVel.x * turnSpeed * (float)Time.DeltaTime; + + } + } +} diff --git a/Assets/Scripts/CameraControl.cs.shmeta b/Assets/Scripts/CameraControl.cs.shmeta new file mode 100644 index 00000000..bf68c31c --- /dev/null +++ b/Assets/Scripts/CameraControl.cs.shmeta @@ -0,0 +1,3 @@ +Name: CameraControl +ID: 158782344 +Type: 9 diff --git a/Assets/Scripts/CameraFix.cs b/Assets/Scripts/CameraFix.cs new file mode 100644 index 00000000..0ca085ad --- /dev/null +++ b/Assets/Scripts/CameraFix.cs @@ -0,0 +1,23 @@ +鏤using SHADE; +using System; + +public class CameraFix : Script +{ + + private Transform tranform; + public Vector3 pos = Vector3.Zero; + public Vector3 rot = Vector3.Zero; + protected override void awake() + { + tranform = GetComponent(); + if (tranform == null) + Debug.LogError("tranform is NULL!"); + else + { + tranform.LocalPosition = pos; + tranform.LocalEulerAngles = rot; + } + + + } +} diff --git a/Assets/Scripts/CameraFix.cs.shmeta b/Assets/Scripts/CameraFix.cs.shmeta new file mode 100644 index 00000000..d1e5412a --- /dev/null +++ b/Assets/Scripts/CameraFix.cs.shmeta @@ -0,0 +1,3 @@ +Name: CameraFix +ID: 162231964 +Type: 9 diff --git a/Assets/Scripts/Item.cs b/Assets/Scripts/Item.cs new file mode 100644 index 00000000..5047a241 --- /dev/null +++ b/Assets/Scripts/Item.cs @@ -0,0 +1,17 @@ +鏤using SHADE; +using System; +public class Item : Script +{ + public enum ItemCategory + { + LIGHT, + MEDIUM, + HEAVY + } + + public ItemCategory currCategory; + + protected override void awake() + { + } +} \ No newline at end of file diff --git a/Assets/Scripts/Item.cs.shmeta b/Assets/Scripts/Item.cs.shmeta new file mode 100644 index 00000000..84830b76 --- /dev/null +++ b/Assets/Scripts/Item.cs.shmeta @@ -0,0 +1,3 @@ +Name: Item +ID: 163145289 +Type: 9 diff --git a/Assets/Scripts/PhysicsTest.cs b/Assets/Scripts/PhysicsTest.cs new file mode 100644 index 00000000..9726a51c --- /dev/null +++ b/Assets/Scripts/PhysicsTest.cs @@ -0,0 +1,71 @@ +鏤using SHADE; +using System; +public class PhysicsTest : Script +{ + [SerializeField] + [Tooltip("Force to apply when pressing Space.")] + private Vector3 Force = new Vector3(0.0f, 200.0f, 0.0f); + private Transform Transform; + private RigidBody RigidBody; + private Collider Collider; + + protected override void awake() + { + Transform = GetComponent(); + if (Transform == null) + { + Debug.LogError("Transform is NULL!"); + } + RigidBody = GetComponent(); + if (RigidBody == null) + { + Debug.LogError("RigidBody is NULL!"); + } + Collider = GetComponent(); + if (Collider == null) + { + Debug.LogError("Collider is NULL!"); + } + + var subColider = Collider.CollisionShapeCount; + Debug.Log($"There are {subColider} colliders."); + } + protected override void update() + { + if (Input.GetKeyUp(Input.KeyCode.Space)) + { + RigidBody.AddForce(Force); + Debug.Log($"Jump!"); + } + } + + protected override void fixedUpdate() + { + Debug.Log("Fixed Update"); + } + + protected override void onCollisionEnter(CollisionInfo info) + { + Debug.Log($"Collision Enter: {info.GameObject.Name}"); + } + protected override void onCollisionStay(CollisionInfo info) + { + Debug.Log($"Collision Stay: {info.GameObject.Name}"); + } + protected override void onCollisionExit(CollisionInfo info) + { + Debug.Log($"Collision Exit: {info.GameObject.Name}"); + } + protected override void onTriggerEnter(CollisionInfo info) + { + Debug.Log($"Trigger Enter: {info.GameObject.Name}"); + } + protected override void onTriggerStay(CollisionInfo info) + { + Debug.Log($"Trigger Stay: {info.GameObject.Name}"); + } + protected override void onTriggerExit(CollisionInfo info) + { + Debug.Log($"Trigger Exit: {info.GameObject.Name}"); + } +} \ No newline at end of file diff --git a/Assets/Scripts/PhysicsTest.cs.shmeta b/Assets/Scripts/PhysicsTest.cs.shmeta new file mode 100644 index 00000000..99b809c5 --- /dev/null +++ b/Assets/Scripts/PhysicsTest.cs.shmeta @@ -0,0 +1,3 @@ +Name: PhysicsTest +ID: 159771801 +Type: 9 diff --git a/Assets/Scripts/PickAndThrow.cs b/Assets/Scripts/PickAndThrow.cs new file mode 100644 index 00000000..ec8846c5 --- /dev/null +++ b/Assets/Scripts/PickAndThrow.cs @@ -0,0 +1,98 @@ +鏤using SHADE; +using System; +using static PlayerController; + +public class PickAndThrow : Script +{ + public Vector3 throwForce = new Vector3(100.0f, 200.0f, 100.0f); + public GameObject item; + private PlayerController pc; + private Camera cam; + private Transform itemTransform; + private RigidBody itemRidibody; + private Transform raccoonHoldLocation; + private float lastXDir; + private float lastZDir; + private bool inRange = false; + + protected override void awake() + { + pc = GetScript(); + raccoonHoldLocation = GetComponentInChildren(); + if (raccoonHoldLocation == null) + Debug.Log("CHILD EMPTY"); + else + raccoonHoldLocation.LocalPosition = new Vector3(0.0f, 1.0f, 0.0f); + } + protected override void update() + { + if (cam == null) + cam = GetComponentInChildren(); + else if (cam != null) + { + Vector3 camerAixs = cam.GetForward(); + camerAixs.y = 0; + camerAixs.Normalise(); + lastXDir = camerAixs.x; + lastZDir = camerAixs.z; + } + + if (item.GetScript() != null && itemTransform == null && itemRidibody == null) + { + itemTransform = item.GetComponent(); + if (itemTransform == null) + Debug.Log("Item transform EMPTY"); + + itemRidibody = item.GetComponent(); + if (itemRidibody == null) + Debug.Log("Item rb EMPTY"); + } + + if (pc != null && inRange && !pc.holdItem && Input.GetKey(Input.KeyCode.E)) + pc.holdItem = true; + + if (pc != null && itemRidibody != null && itemTransform != null && pc.holdItem) + { + itemTransform.LocalPosition = raccoonHoldLocation.GlobalPosition; + itemRidibody.IsGravityEnabled = false; + itemRidibody.LinearVelocity = Vector3.Zero; + itemRidibody.AngularVelocity = Vector3.Zero; + + if (Input.GetMouseButtonDown(Input.MouseCode.LeftButton)) + { + pc.holdItem = false; + inRange = false; + itemRidibody.IsGravityEnabled = true; + itemRidibody.AddForce(new Vector3(throwForce.x * lastXDir, throwForce.y, throwForce.z * lastZDir)); + itemRidibody.LinearVelocity += pc.rb.LinearVelocity; + } + } + else if(!pc.holdItem && itemRidibody != null) + itemRidibody.IsGravityEnabled = true; + } + protected override void onCollisionEnter(CollisionInfo info) + { + } + protected override void onTriggerEnter(CollisionInfo info) + { + //Debug.Log("ENTER"); + if (info.GameObject.GetScript() != null && !pc.holdItem) + { + item = info.GameObject; + inRange = true; + } + } + protected override void onTriggerStay(CollisionInfo info) + { + //Debug.Log("STAY"); + } + protected override void onTriggerExit(CollisionInfo info) + { + //Debug.Log("EXIT"); + if (info.GameObject.GetScript() != null && !pc.holdItem) + { + inRange = false; + } + } + +} \ No newline at end of file diff --git a/Assets/Scripts/PickAndThrow.cs.shmeta b/Assets/Scripts/PickAndThrow.cs.shmeta new file mode 100644 index 00000000..0eb38f59 --- /dev/null +++ b/Assets/Scripts/PickAndThrow.cs.shmeta @@ -0,0 +1,3 @@ +Name: PickAndThrow +ID: 165331952 +Type: 9 diff --git a/Assets/Scripts/PlayerController.cs b/Assets/Scripts/PlayerController.cs new file mode 100644 index 00000000..4a02d470 --- /dev/null +++ b/Assets/Scripts/PlayerController.cs @@ -0,0 +1,358 @@ +鏤using SHADE; +using System; +using static Item; + +public class PlayerController : Script +{ + public enum RaccoonStates + { + IDILE, + WALKING, + RUNNING, + JUMP, + FALLING, + CAUGHT, + TOTAL + } + + public RigidBody rb { get; set; } + private Transform tranform; + private Camera cam; + private PickAndThrow pat; + + //to be remove + public float drag = 2.0f; + public bool holdItem { get; set; } + [SerializeField] + [Tooltip("The current state fo the raccoon")] + public RaccoonStates currentState = RaccoonStates.IDILE; + + //Movement variables============================================================ + [SerializeField] + [Tooltip("Max vel for walking")] + public float maxMoveVel = 2.0f; + [SerializeField] + [Tooltip("how much force is apply for walking")] + public float moveForce = 50.0f; + [SerializeField] + [Tooltip("increase the moveForce and maxMoveVel by its amt")] + public float sprintMultiplier = 2.0f; + + private float oldForce; + private float maxOldVel; + private bool sprintIncreaseOnce = false; + + public float xAxisMove { get; set; } + public float zAxisMove { get; set; } + + public Vector2 axisMove { get; set; } + + public bool isMoveKeyPress { get; set; } + + [SerializeField] + [Tooltip("curr not working")] + public float rotationFactorPerFrame = 1.0f; + + //Jumping vars================================================================== + [SerializeField] + [Tooltip("max height of the jump")] + public float maxJumpHeight = 4.0f; + [SerializeField] + [Tooltip("max amt of time it will take for the jump")] + public float maxJumpTime = 0.75f; + [SerializeField] + [Tooltip("increase gravity when falling")] + public float fallMultipler = 2.0f; + private float initialJumpVel; + private bool isGrounded = true; + private float gravity = -9.8f; + private float groundGravity = -0.5f; + + //ItemMultipler================================================================== + public float lightMultiper = 0.75f; + public float mediumMultiper = 0.5f; + public float heavyMultiper = 0.25f; + + protected override void awake() + { + //default setup + isMoveKeyPress = false; + holdItem = false; + + //Jump setup + float timeToApex = maxJumpTime / 2; + gravity = (-2 * maxJumpHeight) / MathF.Pow(timeToApex, 2); + initialJumpVel = (2 * maxJumpHeight) / timeToApex; + + //rigidbody check + rb = GetComponent(); + if (rb == null) + Debug.LogError("RigidBody is NULL!"); + else + { + rb.IsGravityEnabled = false; + rb.FreezeRotationX = true; + rb.FreezeRotationY = true; + rb.FreezeRotationZ = true; + rb.Drag = drag; + rb.Interpolating = false; + } + + //Transform check + tranform = GetComponent(); + if(tranform == null) + Debug.LogError("tranform is NULL!"); + + //PickAndThrow checl + pat = GetScript(); + if (pat == null) + Debug.LogError("PickAndThrow is NULL!"); + + //toRemove + tranform.LocalPosition = new Vector3(-3.0f, -2.0f, -5.0f); + tranform.LocalRotation = Quaternion.Euler(0.0f, 0.0f, 0.0f); + } + + protected override void update() + { + if (cam == null) + cam = GetComponentInChildren(); + + //toRemove + if (Input.GetKey(Input.KeyCode.G)) + { + tranform.LocalRotation = Quaternion.Euler(0.0f, 0.0f, 0.0f); + tranform.LocalPosition = new Vector3(-3.0f, -2.0f, -5.0f); + } + + GotCaught(); + MoveKey(); + + + + //Debug.Log(currentState.ToString() + " x:" + rb.LinearVelocity.x.ToString() + " y:" + rb.LinearVelocity.y.ToString() + " z:" + rb.LinearVelocity.z.ToString()); + } + + protected override void fixedUpdate() + { + //Rotation(); + Move(); + Sprint(); + Jump(); + Gravity(); + } + + + private void MoveKey() + { + /* if (Input.GetKey(Input.KeyCode.A)) + xAxisMove = -1; + else if (Input.GetKey(Input.KeyCode.D)) + xAxisMove = 1; + else + xAxisMove = 0; + + if (Input.GetKey(Input.KeyCode.W)) + zAxisMove = -1; + else if (Input.GetKey(Input.KeyCode.S)) + zAxisMove = 1; + else + zAxisMove = 0;*/ + + + xAxisMove = 0; + zAxisMove = 0; + axisMove = Vector2.Zero; + if (Input.GetKey(Input.KeyCode.W)) + { + Vector3 camerAixs = cam.GetForward(); + camerAixs.y = 0; + camerAixs.Normalise(); + xAxisMove = camerAixs.x; + zAxisMove = camerAixs.z; + axisMove += new Vector2(camerAixs.x, camerAixs.z); + } + if (Input.GetKey(Input.KeyCode.S)) + { + Vector3 camerAixs = cam.GetForward(); + camerAixs.y = 0; + camerAixs.Normalise(); + xAxisMove = -camerAixs.x; + zAxisMove = -camerAixs.z; + axisMove -= new Vector2(camerAixs.x, camerAixs.z); + } + if (Input.GetKey(Input.KeyCode.A)) + { + Vector3 camerAixs = cam.GetRight(); + camerAixs.y = 0; + camerAixs.Normalise(); + xAxisMove = -camerAixs.x; + zAxisMove = -camerAixs.z; + axisMove -= new Vector2(camerAixs.x, camerAixs.z); + } + if (Input.GetKey(Input.KeyCode.D)) + { + Vector3 camerAixs = cam.GetRight(); + camerAixs.y = 0; + camerAixs.Normalise(); + xAxisMove = camerAixs.x; + zAxisMove = camerAixs.z; + axisMove += new Vector2(camerAixs.x, camerAixs.z); + } + axisMove.Normalise(); + isMoveKeyPress = xAxisMove != 0 || zAxisMove != 0; + + if(isMoveKeyPress && currentState != RaccoonStates.RUNNING && isGrounded) + currentState = RaccoonStates.WALKING; + + if (!isMoveKeyPress && isGrounded) + currentState = RaccoonStates.IDILE; + } + + private void Move() + { + if (rb != null) + { + rb.AddForce(new Vector3(axisMove.x, 0.0f,axisMove.y) * moveForce); + + if (isMoveKeyPress) + { + if (rb.LinearVelocity.x > maxMoveVel || rb.LinearVelocity.x < -maxMoveVel) + { + Vector3 v = rb.LinearVelocity; + v.x = System.Math.Clamp(v.x, -maxMoveVel, maxMoveVel); + rb.LinearVelocity = v; + } + if (rb.LinearVelocity.z > maxMoveVel || rb.LinearVelocity.z < -maxMoveVel) + { + Vector3 v = rb.LinearVelocity; + v.z = System.Math.Clamp(v.z, -maxMoveVel, maxMoveVel); + rb.LinearVelocity = v; + } + } + } + } + + private void Sprint() + { + if (Input.GetKey(Input.KeyCode.LeftShift) && isMoveKeyPress && isGrounded) + { + currentState = RaccoonStates.RUNNING; + holdItem = false; + if (!sprintIncreaseOnce) + { + sprintIncreaseOnce = true; + oldForce = moveForce; + moveForce *= sprintMultiplier; + + maxOldVel = maxMoveVel; + maxMoveVel *= sprintMultiplier; + } + } + + if (Input.GetKeyUp(Input.KeyCode.LeftShift)) + { + if(isMoveKeyPress) + currentState = RaccoonStates.WALKING; + sprintIncreaseOnce = false; + moveForce = oldForce; + maxMoveVel = maxOldVel; + } + } + + //press and hold jump + private void Jump() + { + if (currentState == RaccoonStates.WALKING || currentState == RaccoonStates.RUNNING || currentState == RaccoonStates.IDILE) + { + if (Input.GetKeyDown(Input.KeyCode.Space) && isGrounded && rb != null) + { + currentState = RaccoonStates.JUMP; + Vector3 v = rb.LinearVelocity; + v.y = initialJumpVel * 0.5f; + if (pat != null && pat.item.GetScript() != null && holdItem) + { + Item item = pat.item.GetScript(); + if (item.currCategory == ItemCategory.LIGHT) + v.y *= lightMultiper; + if (item.currCategory == ItemCategory.MEDIUM) + v.y *= mediumMultiper; + if (item.currCategory == ItemCategory.HEAVY) + v.y *= heavyMultiper; + } + rb.LinearVelocity = v; + } + } + + if(rb != null && !isGrounded && (rb.LinearVelocity.y < 0.0f || Input.GetKeyUp(Input.KeyCode.Space))) + currentState = RaccoonStates.FALLING; + } + + private void Rotation() + { + Vector3 poitionToLookAt; + poitionToLookAt.x = xAxisMove; + poitionToLookAt.y = 0.0f; + poitionToLookAt.z = zAxisMove; + + if (tranform != null) + { + Quaternion currentRotation = tranform.LocalRotation; + if (currentState == RaccoonStates.WALKING || currentState == RaccoonStates.RUNNING) + { + Quaternion targetRotation = Quaternion.LookRotation(poitionToLookAt, new Vector3(0.0f, 1.0f, 0.0f)); + tranform.LocalRotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * (float)Time.DeltaTime); + } + } + } + + private void Gravity() + { + if (rb != null) + { + //check player vel.y if its close to zero its on the ground + if (SHADE.Math.CompareFloat(rb.LinearVelocity.y, 0.0f)) + isGrounded = true; + else + isGrounded = false; + + Vector3 v = rb.LinearVelocity; + + if (isGrounded) + v.y = groundGravity; + else if (currentState == RaccoonStates.FALLING) + { + float prevYVel = v.y; + float newYVel = v.y + (gravity * fallMultipler * (float)Time.DeltaTime); + float nextYVel = (prevYVel + newYVel) * 0.5f; + v.y = nextYVel; + } + else + { + float prevYVel = v.y; + float newYVel = v.y + (gravity * (float)Time.DeltaTime); + float nextYVel = (prevYVel + newYVel) * 0.5f; + v.y = nextYVel; + } + + rb.LinearVelocity = v; + + } + } + + private void GotCaught() + { + if (currentState == RaccoonStates.CAUGHT && tranform != null) + { + currentState = RaccoonStates.IDILE; + tranform.LocalPosition = new Vector3(-3.0f, -2.0f, -5.0f); + } + } + + protected override void onCollisionEnter(CollisionInfo info) + { + } + + +} + diff --git a/Assets/Scripts/PlayerController.cs.shmeta b/Assets/Scripts/PlayerController.cs.shmeta new file mode 100644 index 00000000..8b71915c --- /dev/null +++ b/Assets/Scripts/PlayerController.cs.shmeta @@ -0,0 +1,3 @@ +Name: PlayerController +ID: 164563088 +Type: 9 diff --git a/Assets/Scripts/PrintWhenActive.cs b/Assets/Scripts/PrintWhenActive.cs new file mode 100644 index 00000000..11d7f025 --- /dev/null +++ b/Assets/Scripts/PrintWhenActive.cs @@ -0,0 +1,9 @@ +鏤using SHADE; + +public class PrintWhenActive : Script +{ + protected override void update() + { + Debug.Log("Active!"); + } +} \ No newline at end of file diff --git a/Assets/Scripts/PrintWhenActive.cs.shmeta b/Assets/Scripts/PrintWhenActive.cs.shmeta new file mode 100644 index 00000000..2b8c4173 --- /dev/null +++ b/Assets/Scripts/PrintWhenActive.cs.shmeta @@ -0,0 +1,3 @@ +Name: PrintWhenActive +ID: 162536221 +Type: 9 diff --git a/Assets/Scripts/RaccoonShowcase.cs b/Assets/Scripts/RaccoonShowcase.cs new file mode 100644 index 00000000..3c767f7f --- /dev/null +++ b/Assets/Scripts/RaccoonShowcase.cs @@ -0,0 +1,55 @@ +using SHADE; +using System; +using System.Collections.Generic; + +public class RaccoonShowcase : Script +{ + [SerializeField] + [Tooltip("Speed of the rotation in radians per second.")] + [Range(-1.0f, 2.0f)] + private double RotateSpeed = 1.0; + //[SerializeField] + //[Range(-5, 20)] + //private int test = 5; + [SerializeField] + [Tooltip("Speed of the scaling in radians per second around each axis.")] + private Vector3 ScaleSpeed = Vector3.One; + private Transform Transform; + private double rotation = 0.0; + private Vector3 scale = Vector3.Zero; + private double originalScale = 1.0f; + [Tooltip("Sample list of Vector3s.")] + public List vecList = new List(new Vector3[] { new Vector3(1, 2, 3), new Vector3(4, 5, 6) }); + [Range(-5, 5)] + public List intList = new List(new int[] { 2, 8, 2, 6, 8, 0, 1 }); + public List enumList = new List(new Light.Type[] { Light.Type.Point, Light.Type.Directional, Light.Type.Ambient }); + + protected override void awake() + { + Transform = GetComponent(); + if (Transform == null) + { + Debug.LogError("Transform is NULL!"); + } + + foreach (var child in Owner) + { + Debug.Log(child.Name); + } + + originalScale = Transform.LocalScale.z; + } + protected override void update() + { + //rotation += RotateSpeed * 0.16; + //scale += ScaleSpeed * 0.16; + //Transform.LocalRotation = new Vector3(0.0f, rotation, 0.0f); + //Transform.LocalScale = new Vector3(System.Math.Abs(System.Math.Sin(scale.x)) * originalScale, System.Math.Abs(System.Math.Cos(scale.y)) * originalScale, System.Math.Abs(System.Math.Sin(scale.z)) * originalScale); + } + + protected override void onDrawGizmos() + { + Gizmos.DrawLine(new Vector3(-1.0f, 0.0f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f)); + Gizmos.DrawLine(new Vector3(-1.0f, 1.0f, 0.0f), new Vector3(1.0f, 1.0f, 0.0f), Color.Red); + } +} \ No newline at end of file diff --git a/Assets/Scripts/RaccoonShowcase.cs.shmeta b/Assets/Scripts/RaccoonShowcase.cs.shmeta new file mode 100644 index 00000000..6ce5bc3d --- /dev/null +++ b/Assets/Scripts/RaccoonShowcase.cs.shmeta @@ -0,0 +1,3 @@ +Name: RaccoonShowcase +ID: 159969631 +Type: 9 diff --git a/Assets/Scripts/RaccoonSpin.cs b/Assets/Scripts/RaccoonSpin.cs new file mode 100644 index 00000000..84100a21 --- /dev/null +++ b/Assets/Scripts/RaccoonSpin.cs @@ -0,0 +1,32 @@ +鏤using SHADE; +using System; + +public class RaccoonSpin : Script +{ + [SerializeField] + [Tooltip("Speed of the rotation in radians per second.")] + private float RotateSpeed = 1.0f; + private float rotation = 0.0f; + [SerializeField] + private CallbackEvent emptyEvent; + [SerializeField] + private CallbackEvent testEvent; + [SerializeField] + private CallbackEvent testEvent3 = new CallbackEvent(); + private Transform Transform; + + protected override void awake() + { + emptyEvent = new CallbackEvent(); + emptyEvent.RegisterAction(() => Debug.Log("Empty event action!")); + testEvent = new CallbackEvent(); + Action action = (x) => Debug.Log($"{x}"); + testEvent.RegisterAction(action); + + Transform = GetComponent(); + if (Transform == null) + { + Debug.LogError("Transform is NULL!"); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/RaccoonSpin.cs.shmeta b/Assets/Scripts/RaccoonSpin.cs.shmeta new file mode 100644 index 00000000..9a1e05c8 --- /dev/null +++ b/Assets/Scripts/RaccoonSpin.cs.shmeta @@ -0,0 +1,3 @@ +Name: RaccoonSpin +ID: 157367824 +Type: 9 diff --git a/Assets/Scripts/ThirdPersonCamera.cs b/Assets/Scripts/ThirdPersonCamera.cs new file mode 100644 index 00000000..fed26ae9 --- /dev/null +++ b/Assets/Scripts/ThirdPersonCamera.cs @@ -0,0 +1,60 @@ +鏤using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SHADE; + + +namespace SHADE_Scripting +{ + public class ThirdPersonCamera: Script + { + + public float armLength = 2.0f; + public float turnSpeedPitch = 0.3f; + public float turnSpeedYaw = 0.5f; + public float pitchClamp = 45.0f; + + protected override void awake() + { + AddComponent(); + + if(!GetComponent()) + { + AddComponent(); + } + GetComponent().SetMainCamera(); + if (!GetComponent()) + { + AddComponent(); + } + GetComponent().ArmLength = armLength; + } + + protected override void update() + { + if (Input.GetMouseButton(Input.MouseCode.RightButton)) + { + CameraArm arm = GetComponent(); + if (arm) + { + Vector2 vel = Input.GetMouseVelocity(); + arm.Pitch -= vel.y * turnSpeedPitch * Time.DeltaTimeF; + arm.Yaw += vel.x * turnSpeedYaw * Time.DeltaTimeF; + + if (arm.Pitch > pitchClamp) + { + arm.Pitch = pitchClamp; + } + else if (arm.Pitch < 0) + { + arm.Pitch = 0; + } + + } + } + } + + } +} diff --git a/Assets/Scripts/ThirdPersonCamera.cs.shmeta b/Assets/Scripts/ThirdPersonCamera.cs.shmeta new file mode 100644 index 00000000..2f18c2fb --- /dev/null +++ b/Assets/Scripts/ThirdPersonCamera.cs.shmeta @@ -0,0 +1,3 @@ +Name: ThirdPersonCamera +ID: 154161201 +Type: 9 diff --git a/Assets/Shaders/DebugDraw_FS.glsl b/Assets/Shaders/DebugDraw_FS.glsl new file mode 100644 index 00000000..266f8ad4 --- /dev/null +++ b/Assets/Shaders/DebugDraw_FS.glsl @@ -0,0 +1,17 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +#extension GL_EXT_nonuniform_qualifier : require + +layout(location = 0) in struct +{ + vec4 vertColor; +} In; + + +layout(location = 0) out vec4 outColor; + +void main() +{ + outColor = In.vertColor; +} \ No newline at end of file diff --git a/Assets/Shaders/DebugDraw_FS.shshaderb b/Assets/Shaders/DebugDraw_FS.shshaderb new file mode 100644 index 00000000..7b596885 Binary files /dev/null and b/Assets/Shaders/DebugDraw_FS.shshaderb differ diff --git a/Assets/Shaders/DebugDraw_FS.shshaderb.shmeta b/Assets/Shaders/DebugDraw_FS.shshaderb.shmeta new file mode 100644 index 00000000..58f62b44 --- /dev/null +++ b/Assets/Shaders/DebugDraw_FS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: DebugDraw_FS +ID: 36671027 +Type: 2 diff --git a/Assets/Shaders/DebugDraw_VS.glsl b/Assets/Shaders/DebugDraw_VS.glsl new file mode 100644 index 00000000..cb0886d1 --- /dev/null +++ b/Assets/Shaders/DebugDraw_VS.glsl @@ -0,0 +1,24 @@ +#version 450 +#extension GL_KHR_vulkan_glsl : enable + +layout(location = 0) in vec4 aVertexPos; +layout(location = 1) in vec4 aVertColor; + + +layout(location = 0) out struct +{ + vec4 vertColor; // location 0 + +} Out; + +layout(set = 2, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; +} cameraData; + +void main() +{ + gl_Position = cameraData.vpMat * vec4 (aVertexPos.xyz, 1.0f); + Out.vertColor = aVertColor; +} \ No newline at end of file diff --git a/Assets/Shaders/DebugDraw_VS.shshaderb b/Assets/Shaders/DebugDraw_VS.shshaderb new file mode 100644 index 00000000..2d8cd0ed Binary files /dev/null and b/Assets/Shaders/DebugDraw_VS.shshaderb differ diff --git a/Assets/Shaders/DebugDraw_VS.shshaderb.shmeta b/Assets/Shaders/DebugDraw_VS.shshaderb.shmeta new file mode 100644 index 00000000..ab840f4c --- /dev/null +++ b/Assets/Shaders/DebugDraw_VS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: DebugDraw_VS +ID: 48002439 +Type: 2 diff --git a/Assets/Shaders/DeferredComposite_CS.glsl b/Assets/Shaders/DeferredComposite_CS.glsl new file mode 100644 index 00000000..5af585ba --- /dev/null +++ b/Assets/Shaders/DeferredComposite_CS.glsl @@ -0,0 +1,97 @@ +#version 450 + +struct DirectionalLightStruct +{ + vec3 direction; + uint isActive; + uint cullingMask; + vec4 diffuseColor; +}; + +struct AmbientLightStruct +{ + vec4 ambientColor; + float strength; + uint isActive; + uint cullingMask; +}; + +layout(local_size_x = 16, local_size_y = 16) in; +layout(set = 4, binding = 0, rgba32f) uniform image2D positions; +layout(set = 4, binding = 1, rgba32f) uniform image2D normals; +layout(set = 4, binding = 2, rgba8) uniform image2D albedo; +layout(set = 4, binding = 3, r32ui) uniform uimage2D lightLayerData; +layout(set = 4, binding = 4, r8) uniform image2D ssaoBlurredImage; +layout(set = 4, binding = 5, rgba8) uniform image2D targetImage; + +layout(set = 1, binding = 0) uniform LightCounts +{ + uint directionalLights; + uint pointLights; + uint spotLights; + uint ambientLights; + +} lightCounts; + +layout(std430, set = 1, binding = 1) buffer DirectionalLightData +{ + DirectionalLightStruct dLightData[]; +} DirLightData; + +layout(std430, set = 1, binding = 4) buffer AmbientLightData +{ + AmbientLightStruct aLightData[]; +} AmbLightData; + +void main() +{ + // convenient variables + ivec2 globalThread = ivec2(gl_GlobalInvocationID); + + // Get the diffuse color of the pixel + vec3 pixelDiffuse = imageLoad (albedo, globalThread).rgb; + + // Get position of fragment in world space + vec3 positionView = imageLoad (positions, globalThread).rgb; + + // normal of fragment + vec3 normalView = imageLoad(normals, globalThread).rgb; + + // light layer index + uint lightLayer = imageLoad (lightLayerData, globalThread).r; + + vec3 fragColor = vec3 (0.0f); + + for (int i = 0; i < lightCounts.directionalLights; ++i) + { + if ((lightLayer & DirLightData.dLightData[i].cullingMask) != 0) + { + // get normalized direction of light + vec3 dLightNormalized = normalize (DirLightData.dLightData[i].direction); + + // Get diffuse strength + float diffuseStrength = max (0, dot (dLightNormalized, normalView)); + + // Calculate the fragment color + fragColor += DirLightData.dLightData[i].diffuseColor.rgb * diffuseStrength.rrr * pixelDiffuse; + } + } + + for (int i = 0; i < lightCounts.ambientLights; ++i) + { + if ((lightLayer & AmbLightData.aLightData[i].cullingMask) != 0) + { + // Just do some add + //fragColor += pixelDiffuse.rgb * AmbLightData.aLightData[i].ambientColor.rgb * vec3 (0.5f); + fragColor += pixelDiffuse.rgb * AmbLightData.aLightData[i].ambientColor.rgb * vec3 (AmbLightData.aLightData[i].strength); + } + } + + float ssaoVal = imageLoad (ssaoBlurredImage, globalThread).r; + fragColor *= ssaoVal; + + // store result into result image + imageStore(targetImage, ivec2(gl_GlobalInvocationID.xy), vec4(fragColor.rgb, 1.0f)); + //imageStore(targetImage, ivec2(gl_GlobalInvocationID.xy), vec4(ssaoVal.rrr, 1.0f)); + +} \ No newline at end of file diff --git a/Assets/Shaders/DeferredComposite_CS.shshaderb b/Assets/Shaders/DeferredComposite_CS.shshaderb new file mode 100644 index 00000000..172a6a32 Binary files /dev/null and b/Assets/Shaders/DeferredComposite_CS.shshaderb differ diff --git a/Assets/Shaders/DeferredComposite_CS.shshaderb.shmeta b/Assets/Shaders/DeferredComposite_CS.shshaderb.shmeta new file mode 100644 index 00000000..62c2a3fe --- /dev/null +++ b/Assets/Shaders/DeferredComposite_CS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: DeferredComposite_CS +ID: 45072428 +Type: 2 diff --git a/Assets/Shaders/Kirsch_CS.glsl b/Assets/Shaders/Kirsch_CS.glsl new file mode 100644 index 00000000..3dec174d --- /dev/null +++ b/Assets/Shaders/Kirsch_CS.glsl @@ -0,0 +1,167 @@ +//#version 450 +// +//layout(local_size_x = 16, local_size_y = 16) in; +//layout(set = 4, binding = 0, rgba8) uniform image2D targetImage; +// +// +//void main() +//{ +// ivec2 imageSize = imageSize (targetImage); +// +// if (gl_GlobalInvocationID.x >= imageSize.x && gl_GlobalInvocationID.y >= imageSize.y) +// return; +// +// // load the image +// vec4 color = imageLoad (targetImage, ivec2 (gl_GlobalInvocationID)); +// +// // get the average +// float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; +// +// // store result into result image +// imageStore(targetImage, ivec2(gl_GlobalInvocationID), vec4(average, average, average, 1.0f)); +// +//} +// +// +// +// + +/* Start Header *****************************************************************/ + +/*! \file (e.g. kirsch.comp) + + \author William Zheng, william.zheng, 60001906. Brandon Mak, brandon.hao 390003920. + + \par william.zheng\@digipen.edu. brandon.hao\@digipen.edu. + + \date Sept 20, 2022 + + \brief Copyright (C) 20xx DigiPen Institute of Technology. + + Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. */ + + /* End Header *******************************************************************/ + +#version 450 + +#define MASK_WIDTH 3 +#define HALF_M_WIDTH MASK_WIDTH / 2 +#define SHM_WIDTH 18 +#define NUM_MASKS 8 + +layout(local_size_x = 16, local_size_y = 16) in; +layout(set = 4, binding = 0, rgba8) uniform image2D inputImage; +layout(set = 4, binding = 1, rgba8) uniform image2D resultImage; + +const float kirsch[8][3][3] = { + { + {5, 5, 5}, + {-3, 0, -3}, /*rotation 1 */ + {-3, -3, -3} + }, + { + {5, 5, -3}, + {5, 0, -3}, /*rotation 2 */ + {-3, -3, -3} + }, + { + {5, -3, -3}, + {5, 0, -3}, /*rotation 3 */ + {5, -3, -3} + }, + { + {-3, -3, -3}, + {5, 0, -3}, /*rotation 4 */ + {5, 5, -3} + }, + { + {-3, -3, -3}, + {-3, 0, -3}, /*rotation 5 */ + {5, 5, 5} + }, + { + {-3, -3, -3}, + {-3, 0, 5}, /*rotation 6 */ + {-3, 5, 5} + }, + { + {-3, -3, 5}, + {-3, 0, 5}, /*rotation 7 */ + {-3, -3, 5} + }, + { + {-3, 5, 5}, + {-3, 0, 5}, /*rotation 8 */ + {-3, -3, -3} + } +}; + +vec3 GetImageValues(ivec2 uv, ivec2 inputImageSize) +{ + if (uv.x >= 0 && uv.y >= 0 && uv.x < inputImageSize.x && uv.y < inputImageSize.y) + { + return imageLoad(inputImage, uv).rgb; + } + else + return vec3(0.0f); +} + +//two extra row/col +shared vec3 sData[16 + 2][16 + 2]; + +void main() +{ + // convenient variables + ivec3 globalThread = ivec3(gl_GlobalInvocationID); + ivec3 localThread = ivec3(gl_LocalInvocationID); + ivec2 inputImageSize = imageSize(inputImage); + + // load shared memory + ivec2 start = ivec2(gl_WorkGroupID) * ivec2(gl_WorkGroupSize) - ivec2(HALF_M_WIDTH); + for (int i = localThread.x; i < SHM_WIDTH; i += int(gl_WorkGroupSize.x)) + { + for (int j = localThread.y; j < SHM_WIDTH; j += int(gl_WorkGroupSize.y)) + { + // get from source image (either real values or 0) + vec3 sourceValue = GetImageValues(start + ivec2(i, j), inputImageSize); + sData[i][j] = sourceValue; + } + } + + // wait for shared memory to finish loading + barrier(); + + // max (between all 8 masks) + vec3 maxSum = vec3(0.0f); + + // loop through all masks + for (int i = 0; i < NUM_MASKS; ++i) + { + vec3 sum = vec3(0.0f); + + // start of shared memory + ivec2 shmStart = ivec2(localThread + HALF_M_WIDTH); + for (int j = -1; j < HALF_M_WIDTH + 1; ++j) + { + for (int k = -1; k < HALF_M_WIDTH + 1; ++k) + { + // Perform convolution using shared_memory + sum += sData[shmStart.x + j][shmStart.y + k] * kirsch[i][j + 1][k + 1]; + } + } + + // Get highest sum + maxSum = max(sum, maxSum); + } + + // average the max sum + maxSum = min(max(maxSum / 8, 0), 1.0f); + + // store result into result image + imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), vec4(maxSum, 1.0f)); + +} + + + + diff --git a/Assets/Shaders/Kirsch_CS.shshaderb b/Assets/Shaders/Kirsch_CS.shshaderb new file mode 100644 index 00000000..4c54946c Binary files /dev/null and b/Assets/Shaders/Kirsch_CS.shshaderb differ diff --git a/Assets/Shaders/Kirsch_CS.shshaderb.shmeta b/Assets/Shaders/Kirsch_CS.shshaderb.shmeta new file mode 100644 index 00000000..af092818 --- /dev/null +++ b/Assets/Shaders/Kirsch_CS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: Kirsch_CS +ID: 39301863 +Type: 2 diff --git a/Assets/Shaders/PureCopy_CS.glsl b/Assets/Shaders/PureCopy_CS.glsl new file mode 100644 index 00000000..89da6dd9 --- /dev/null +++ b/Assets/Shaders/PureCopy_CS.glsl @@ -0,0 +1,67 @@ +//#version 450 +// +//layout(local_size_x = 16, local_size_y = 16) in; +//layout(set = 4, binding = 0, rgba8) uniform image2D targetImage; +// +// +//void main() +//{ +// ivec2 imageSize = imageSize (targetImage); +// +// if (gl_GlobalInvocationID.x >= imageSize.x && gl_GlobalInvocationID.y >= imageSize.y) +// return; +// +// // load the image +// vec4 color = imageLoad (targetImage, ivec2 (gl_GlobalInvocationID)); +// +// // get the average +// float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; +// +// // store result into result image +// imageStore(targetImage, ivec2(gl_GlobalInvocationID), vec4(average, average, average, 1.0f)); +// +//} +// +// +// +// + +/* Start Header *****************************************************************/ + +/*! \file (e.g. kirsch.comp) + + \author William Zheng, william.zheng, 60001906. Brandon Mak, brandon.hao 390003920. + + \par william.zheng\@digipen.edu. brandon.hao\@digipen.edu. + + \date Sept 20, 2022 + + \brief Copyright (C) 20xx DigiPen Institute of Technology. + + Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. */ + + /* End Header *******************************************************************/ + +#version 450 + + +layout(local_size_x = 16, local_size_y = 16) in; +layout(set = 4, binding = 0, rgba8) uniform image2D inputImage; +layout(set = 4, binding = 1, rgba8) uniform image2D targetImage; + + +void main() +{ + // convenient variables + ivec2 globalThread = ivec2(gl_GlobalInvocationID); + + vec3 color = imageLoad (inputImage, globalThread).rgb; + + // store result into result image + imageStore(targetImage, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1.0f)); + +} + + + + diff --git a/Assets/Shaders/PureCopy_CS.shshaderb b/Assets/Shaders/PureCopy_CS.shshaderb new file mode 100644 index 00000000..30a629f5 Binary files /dev/null and b/Assets/Shaders/PureCopy_CS.shshaderb differ diff --git a/Assets/Shaders/PureCopy_CS.shshaderb.shmeta b/Assets/Shaders/PureCopy_CS.shshaderb.shmeta new file mode 100644 index 00000000..8c3d446d --- /dev/null +++ b/Assets/Shaders/PureCopy_CS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: PureCopy_CS +ID: 34987209 +Type: 2 diff --git a/Assets/Shaders/SSAOBlur_CS.glsl b/Assets/Shaders/SSAOBlur_CS.glsl new file mode 100644 index 00000000..64066525 --- /dev/null +++ b/Assets/Shaders/SSAOBlur_CS.glsl @@ -0,0 +1,58 @@ +#version 450 + +#define BLUR_WIDTH 5 +#define BLUR_HALF_WIDTH BLUR_WIDTH / 2 +#define SHM_WIDTH BLUR_WIDTH + 16 - 1 + +layout(local_size_x = 16, local_size_y = 16) in; +layout(set = 4, binding = 0, r8) uniform image2D ssaoImage; +layout(set = 4, binding = 1, r8) uniform image2D ssaoBlurImage; + + +float GetSSAOValue(ivec2 uv, ivec2 imageSize) +{ + if (uv.x >= 0 && uv.y >= 0 && uv.x < imageSize.x && uv.y < imageSize.y) + { + return imageLoad (ssaoImage, uv).r; + } + + return 0.0f; +} + +shared float sharedPixels[16 + BLUR_WIDTH - 1][16 + BLUR_WIDTH - 1]; + +void main() +{ + ivec2 globalThread = ivec2 (gl_GlobalInvocationID.xy); + ivec2 localThread = ivec2 (gl_LocalInvocationID.xy); + ivec2 inputImageSize = imageSize(ssaoImage); + + // Load color into shared memory + ivec2 start = ivec2 (gl_WorkGroupID) * ivec2 (gl_WorkGroupSize) - (BLUR_HALF_WIDTH); + for (int i = localThread.x; i < SHM_WIDTH; i += int (gl_WorkGroupSize.x)) + { + for (int j = localThread.y; j < SHM_WIDTH; j += int (gl_WorkGroupSize.y)) + { + float value = GetSSAOValue (start + ivec2 (i, j), inputImageSize); + sharedPixels[i][j] = value; + } + } + + // wait for all shared memory to load + barrier(); + + ivec2 shmStart = ivec2 (localThread + (BLUR_HALF_WIDTH)); + + float sum = 0; + for (int i = -BLUR_HALF_WIDTH; i <= BLUR_HALF_WIDTH; ++i) + { + for (int j = -BLUR_HALF_WIDTH; j <= BLUR_HALF_WIDTH; ++j) + { + float sharedVal = sharedPixels[shmStart.x + i][shmStart.y + j]; + sum += sharedVal; + } + } + + sum /= (BLUR_WIDTH * BLUR_WIDTH); + imageStore(ssaoBlurImage, globalThread, vec4(sum.rrr, 1.0f)); +} \ No newline at end of file diff --git a/Assets/Shaders/SSAOBlur_CS.shshaderb b/Assets/Shaders/SSAOBlur_CS.shshaderb new file mode 100644 index 00000000..e25b57ba Binary files /dev/null and b/Assets/Shaders/SSAOBlur_CS.shshaderb differ diff --git a/Assets/Shaders/SSAOBlur_CS.shshaderb.shmeta b/Assets/Shaders/SSAOBlur_CS.shshaderb.shmeta new file mode 100644 index 00000000..57bdb72b --- /dev/null +++ b/Assets/Shaders/SSAOBlur_CS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: SSAOBlur_CS +ID: 39760835 +Type: 2 diff --git a/Assets/Shaders/SSAO_CS.glsl b/Assets/Shaders/SSAO_CS.glsl new file mode 100644 index 00000000..1a572521 --- /dev/null +++ b/Assets/Shaders/SSAO_CS.glsl @@ -0,0 +1,104 @@ +#version 450 + +const uint NUM_SAMPLES = 64; +const uint NUM_ROTATIONS = 16; +const int ROTATION_KERNEL_W = 4; +const int ROTATION_KERNEL_H = 4; + +// can perhaps pass in as push constant. +const float RADIUS = 0.5f; +const float BIAS = 0.025f; + +layout(local_size_x = 16, local_size_y = 16) in; +layout(set = 4, binding = 0, rgba32f) uniform image2D positions; +layout(set = 4, binding = 1, rgba32f) uniform image2D normals; +layout(set = 4, binding = 2, rgba32f) uniform image2D outputImage; + + +// SSAO data +layout(std430, set = 5, binding = 0) buffer SSAOData +{ + vec4 samples[NUM_SAMPLES]; + +} ssaoData; + +layout (set = 5, binding = 1) uniform sampler2D noiseTexture; + +layout(set = 2, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; + mat4 viewMat; + mat4 projMat; + +} cameraData; + + +void main() +{ + // image size of the SSAO image + ivec2 ssaoSize = imageSize (outputImage); + + // global thread + ivec2 globalThread = ivec2 (gl_GlobalInvocationID.xy); + + // load all the necessary variables + vec3 viewSpacePos = imageLoad (positions, globalThread).rgb; + vec3 viewSpaceNormal = normalize (imageLoad (normals, globalThread).rgb); + + // Get the noise dimension. This should be 4x4 + vec2 noiseDim = vec2 (textureSize(noiseTexture, 0)); + + // Get normlized thread UV coordinates + vec2 threadUV = (vec2(globalThread)) / vec2(ssaoSize); + vec2 noiseUVMult = vec2 (vec2(ssaoSize) / noiseDim); + noiseUVMult *= threadUV; + + // sample from the noise + vec3 randomVec = texture(noiseTexture, noiseUVMult).rgb; + + // Gram schmidt + vec3 tangent = normalize (randomVec - (viewSpaceNormal * dot(viewSpaceNormal, randomVec))); + vec3 bitangent = normalize (cross (tangent, viewSpaceNormal)); + + // matrix for tangent space to view space + mat3 TBN = mat3(tangent, bitangent, viewSpaceNormal); + + float occlusion = 0.0f; + for (int i = 0; i < NUM_SAMPLES; ++i) + { + // We want to get a position at an offset from the view space position. Offset scaled by radius. + vec3 displacementVector = TBN * ssaoData.samples[i].rgb; + + // Why are we adding positions? + displacementVector = viewSpacePos + displacementVector * RADIUS; + + // Now we take that offset position and bring it to clip space + vec4 offsetPos = vec4 (displacementVector, 1.0f); + offsetPos = cameraData.projMat * offsetPos; + + // then we do perspective division + offsetPos.xyz /= offsetPos.w; + + // and bring it from [-1, 1] to screen coordinates + offsetPos.xyz = ((offsetPos.xyz * 0.5f) + 0.5f); + offsetPos.xy *= vec2(ssaoSize.xy); + + // Now we attempt to get a position at that point. + float sampleDepth = imageLoad (positions, ivec2 (offsetPos.xy)).z; + + // skip checks + if (sampleDepth == 0.0f) + continue; + + // if sampled fragment is in front of current fragment, just occlude + float rangeCheck = smoothstep (0.0f, 1.0f, RADIUS / abs (viewSpacePos.z - sampleDepth)); + occlusion += (sampleDepth <= displacementVector.z - BIAS ? 1.0f : 0.0f) * rangeCheck; + } + + occlusion = 1.0f - (occlusion / float(NUM_SAMPLES)); + + // store result into result image + imageStore(outputImage, globalThread, occlusion.rrrr); + +} \ No newline at end of file diff --git a/Assets/Shaders/SSAO_CS.shshaderb b/Assets/Shaders/SSAO_CS.shshaderb new file mode 100644 index 00000000..69f7a44f Binary files /dev/null and b/Assets/Shaders/SSAO_CS.shshaderb differ diff --git a/Assets/Shaders/SSAO_CS.shshaderb.shmeta b/Assets/Shaders/SSAO_CS.shshaderb.shmeta new file mode 100644 index 00000000..9a477b24 --- /dev/null +++ b/Assets/Shaders/SSAO_CS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: SSAO_CS +ID: 38430899 +Type: 2 diff --git a/Assets/Shaders/TestCube_FS.glsl b/Assets/Shaders/TestCube_FS.glsl new file mode 100644 index 00000000..d6f88687 --- /dev/null +++ b/Assets/Shaders/TestCube_FS.glsl @@ -0,0 +1,50 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +#extension GL_EXT_nonuniform_qualifier : require + +struct MatPropData +{ + vec4 color; + int textureIndex; + float alpha; + vec3 beta; +}; + +layout(location = 0) in struct +{ + vec4 vertPos; // location 0 + vec2 uv; // location = 1 + vec4 normal; // location = 2 + +} In; + +// material stuff +layout(location = 3) flat in struct +{ + int materialIndex; + uint eid; + uint lightLayerIndex; +} In2; + +layout (set = 0, binding = 1) uniform sampler2D textures[]; // for textures (global) +layout (std430, set = 3, binding = 0) buffer MaterialProperties // For materials +{ + MatPropData data[]; +} MatProp; + +layout(location = 0) out vec4 position; +layout(location = 1) out uint outEntityID; +layout(location = 2) out uint lightLayerIndices; +layout(location = 3) out vec4 normals; +layout(location = 4) out vec4 albedo; + +void main() +{ + position = In.vertPos; + normals = In.normal; + albedo = texture(textures[nonuniformEXT(MatProp.data[In2.materialIndex].textureIndex)], In.uv) * MatProp.data[In2.materialIndex].color; + + outEntityID = In2.eid; + lightLayerIndices = In2.lightLayerIndex; +} \ No newline at end of file diff --git a/Assets/Shaders/TestCube_FS.shshaderb b/Assets/Shaders/TestCube_FS.shshaderb new file mode 100644 index 00000000..bcf5bf5e Binary files /dev/null and b/Assets/Shaders/TestCube_FS.shshaderb differ diff --git a/Assets/Shaders/TestCube_FS.shshaderb.shmeta b/Assets/Shaders/TestCube_FS.shshaderb.shmeta new file mode 100644 index 00000000..fbe098b1 --- /dev/null +++ b/Assets/Shaders/TestCube_FS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: TestCube_FS +ID: 46377769 +Type: 2 diff --git a/Assets/Shaders/TestCube_VS.glsl b/Assets/Shaders/TestCube_VS.glsl new file mode 100644 index 00000000..0e055395 --- /dev/null +++ b/Assets/Shaders/TestCube_VS.glsl @@ -0,0 +1,63 @@ +#version 450 +#extension GL_KHR_vulkan_glsl : enable + +//#include "ShaderDescriptorDefinitions.glsl" + + +layout(location = 0) in vec3 aVertexPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec3 aNormal; +layout(location = 3) in vec3 aTangent; +layout(location = 4) in mat4 worldTransform; +layout(location = 8) in uvec2 integerData; + + +layout(location = 0) out struct +{ + vec4 vertPos; // location 0 + vec2 uv; // location = 1 + vec4 normal; // location = 2 + +} Out; + +// material stuff +layout(location = 3) out struct +{ + int materialIndex; + uint eid; + uint lightLayerIndex; + +} Out2; + +layout(set = 2, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; + mat4 viewMat; + mat4 projMat; +} cameraData; + +void main() +{ + Out2.materialIndex = gl_InstanceIndex; + Out2.eid = integerData[0]; + Out2.lightLayerIndex = integerData[1]; + + // for transforming gBuffer position and normal data + mat4 modelViewMat = cameraData.viewMat * worldTransform; + + // gBuffer position will be in view space + Out.vertPos = modelViewMat * vec4(aVertexPos, 1.0f); + + // uvs for texturing in fragment shader + Out.uv = aUV; + + mat3 transposeInv = mat3 (transpose(inverse(modelViewMat))); + + // normals are also in view space + Out.normal.rgb = transposeInv * aNormal.rgb; + Out.normal.rgb = normalize (Out.normal.rgb); + + // clip space for rendering + gl_Position = cameraData.vpMat * worldTransform * vec4 (aVertexPos, 1.0f); +} \ No newline at end of file diff --git a/Assets/Shaders/TestCube_VS.shshaderb b/Assets/Shaders/TestCube_VS.shshaderb new file mode 100644 index 00000000..fb282b4d Binary files /dev/null and b/Assets/Shaders/TestCube_VS.shshaderb differ diff --git a/Assets/Shaders/TestCube_VS.shshaderb.shmeta b/Assets/Shaders/TestCube_VS.shshaderb.shmeta new file mode 100644 index 00000000..5c9f895f --- /dev/null +++ b/Assets/Shaders/TestCube_VS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: TestCube_VS +ID: 39210065 +Type: 2 diff --git a/Assets/TD_Checker_Base_Color.dds b/Assets/TD_Checker_Base_Color.dds new file mode 100644 index 00000000..9dcbfeee Binary files /dev/null and b/Assets/TD_Checker_Base_Color.dds differ diff --git a/Assets/TD_Checker_Base_Color.shtex b/Assets/TD_Checker_Base_Color.shtex new file mode 100644 index 00000000..c99ff515 Binary files /dev/null and b/Assets/TD_Checker_Base_Color.shtex differ diff --git a/Assets/TD_Checker_Base_Color.shtex.shmeta b/Assets/TD_Checker_Base_Color.shtex.shmeta new file mode 100644 index 00000000..8c0d5b77 --- /dev/null +++ b/Assets/TD_Checker_Base_Color.shtex.shmeta @@ -0,0 +1,3 @@ +Name: TD_Checker_Base_Color +ID: 51995224 +Type: 3 diff --git a/Dependencies.bat b/Dependencies.bat index bbce926b..44185ff8 100644 --- a/Dependencies.bat +++ b/Dependencies.bat @@ -7,35 +7,41 @@ echo "SHADE DEPENDENCIES (Default - All in 10 Seconds)" echo "A - All" echo "B - VMA" echo "C - msdf" -echo "D - assimp" -echo "E - ktx" -echo "F - spdlog" -echo "G - reactphysics3d" -echo "H - imgui" -echo "I - imguizmo" -echo "J - imnodes" -echo "K - tracy" -echo "L - RTTR" -echo "M - yamlcpp" +echo "D - ModelCompiler" +echo "E - spdlog" +echo "F - reactphysics3d" +echo "G - imgui" +echo "H - imguizmo" +echo "I - imnodes" +echo "J - tracy" +echo "K - RTTR" +echo "L - yamlcpp" +echo "M - SDL" +echo "N - dotnet" +echo "O - tinyddsloader" +echo "P - fmod" echo --------------------------------------------------- echo. -choice /C ABCDEFGHIJKLM /T 10 /D A +choice /C ABCDEFGHIJKLMNOP /T 10 /D A set _e=%ERRORLEVEL% if %_e%==1 goto VMA if %_e%==2 goto VMA if %_e%==3 goto MSDF -if %_e%==4 goto assimp -if %_e%==5 goto ktx -if %_e%==6 goto spdlog -if %_e%==7 goto reactphysics3d -if %_e%==8 goto imgui -if %_e%==9 goto imguizmo -if %_e%==10 goto imnodes -if %_e%==11 goto tracy -if %_e%==12 goto RTTR -if %_e%==13 goto yamlcpp +if %_e%==4 goto ModelCompiler +if %_e%==5 goto spdlog +if %_e%==6 goto reactphysics3d +if %_e%==7 goto imgui +if %_e%==8 goto imguizmo +if %_e%==9 goto imnodes +if %_e%==10 goto tracy +if %_e%==11 goto RTTR +if %_e%==12 goto yamlcpp +if %_e%==13 goto SDL +if %_e%==14 goto dotnet +if %_e%==15 goto tinyddsloader +if %_e%==16 goto fmod :VMA echo -----------------------VMA---------------------------- @@ -47,66 +53,107 @@ if %_e%==2 (goto :done) else (goto :MSDF) echo -----------------------MSDF---------------------------- rmdir "Dependencies/msdf" /S /Q git clone --recurse-submodules https://github.com/SHADE-DP/msdf-atlas-gen.git "Dependencies/msdf" -if %_e%==3 (goto :done) else (goto :assimp) +if %_e%==3 (goto :done) else (goto :ModelCompiler) -:assimp -echo -----------------------assimp---------------------------- -rmdir "Dependencies/assimp" /S /Q -git clone https://github.com/SHADE-DP/assimp.git "Dependencies/assimp" -if %_e%==4 (goto :done) else (goto :ktx) +:ModelCompiler +echo -----------------------ModelCompiler---------------------------- +rmdir "Dependencies/ModelCompiler" /S /Q +git clone https://github.com/SHADE-DP/ModelCompiler.git "Dependencies/ModelCompiler" +git clone https://github.com/SHADE-DP/assimp.git "Dependencies/ModelCompiler/Dependencies/assimp" +if %_e%==4 (goto :done) else (goto :spdlog) -:ktx -rmdir "Dependencies/ktx" /S /Q -echo -----------------------ktx---------------------------- -git clone https://github.com/SHADE-DP/ktx.git "Dependencies/ktx" -if %_e%==5 (goto :done) else (goto :spdlog) +@REM :ktx +@REM rmdir "Dependencies/ktx" /S /Q +@REM echo -----------------------ktx---------------------------- +@REM git clone https://github.com/SHADE-DP/ktx.git "Dependencies/ktx" +@REM if %_e%==5 (goto :done) else (goto :spdlog) :spdlog echo -----------------------spdlog---------------------------- rmdir "Dependencies/spdlog" /S /Q git clone https://github.com/SHADE-DP/spdlog.git "Dependencies/spdlog" -if %_e%==6 (goto :done) else (goto :reactphysics3d) +if %_e%==5 (goto :done) else (goto :reactphysics3d) :reactphysics3d echo -----------------------reactphysics3d---------------------------- rmdir "Dependencies/reactphysics3d" /S /Q git clone https://github.com/SHADE-DP/reactphysics3d.git "Dependencies/reactphysics3d" -if %_e%==7 (goto :done) else (goto :imgui) +if %_e%==6 (goto :done) else (goto :imgui) :imgui echo -----------------------imgui---------------------------- rmdir "Dependencies/imgui" /S /Q git clone https://github.com/SHADE-DP/imgui.git "Dependencies/imgui" -if %_e%==8 (goto :done) else (goto :imguizmo) +if %_e%==7 (goto :done) else (goto :imguizmo) :imguizmo echo -----------------------imguizmo---------------------------- rmdir "Dependencies/imguizmo" /S /Q git clone https://github.com/SHADE-DP/ImGuizmo.git "Dependencies/imguizmo" -if %_e%==9 (goto :done) else (goto :imnodes) +if %_e%==8 (goto :done) else (goto :imnodes) :imnodes echo -----------------------imnodes---------------------------- rmdir "Dependencies/imnodes" /S /Q git clone https://github.com/SHADE-DP/imnodes.git "Dependencies/imnodes" -if %_e%==10 (goto :done) else (goto :tracy) +if %_e%==9 (goto :done) else (goto :tracy) :tracy echo -----------------------tracy---------------------------- rmdir "Dependencies/tracy" /S /Q git clone https://github.com/SHADE-DP/tracy.git "Dependencies/tracy" -if %_e%==11 (goto :done) else (goto :RTTR) +if %_e%==10 (goto :done) else (goto :RTTR) :RTTR echo -----------------------RTTR---------------------------- rmdir "Dependencies/RTTR" /S /Q git clone https://github.com/SHADE-DP/RTTR.git "Dependencies/RTTR" -if %_e%==12 (goto :done) else (goto :yamlcpp) +if %_e%==11 (goto :done) else (goto :yamlcpp) :yamlcpp echo -----------------------yamlcpp---------------------------- rmdir "Dependencies/yamlcpp" /S /Q git clone https://github.com/SHADE-DP/yaml-cpp.git "Dependencies/yamlcpp" +if %_e%==12 (goto :done) else (goto :SDL) + +:SDL +echo -----------------------SDL---------------------------- +rmdir "Dependencies/SDL" /S /Q +mkdir "Dependencies/SDL/include" +mkdir "Dependencies/SDL/lib" +powershell -Command "& {wget https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-devel-2.24.0-VC.zip -OutFile "Dependencies/SDL/SDL.zip"}" +powershell -Command "& {Expand-Archive -LiteralPath Dependencies/SDL/SDL.zip -DestinationPath Dependencies/SDL/tmp}" +robocopy "Dependencies/SDL/tmp/SDL2-2.24.0/lib/x64" "Dependencies/SDL/lib/" /ns /nfl /ndl /nc /njh +robocopy "Dependencies/SDL/tmp/SDL2-2.24.0/include/" "Dependencies/SDL/include/" /ns /nfl /ndl /nc /njh +rmdir "Dependencies/SDL/tmp/" /s /q +powershell -Command "& {Remove-Item "Dependencies/SDL/SDL.zip"}" +if %_e%==13 (goto :done) else (goto :dotnet) + +:dotnet +echo -----------------------dotnet---------------------------- +rmdir "Dependencies/dotnet" /S /Q +mkdir "Dependencies/dotnet/include" +mkdir "Dependencies/dotnet/bin" +powershell -Command "& {wget https://raw.githubusercontent.com/dotnet/runtime/main/src/coreclr/hosts/inc/coreclrhost.h -OutFile "Dependencies/dotnet/include/coreclrhost.h"}" +powershell -Command "& {wget https://download.visualstudio.microsoft.com/download/pr/8686fa48-b378-424e-908b-afbd66d6e120/2d75d5c3574fb5d917c5a3cd3f624287/dotnet-sdk-6.0.400-win-x64.zip -OutFile "Dependencies/dotnet/dotnet.zip"}" +powershell -Command "& {Expand-Archive -LiteralPath Dependencies/dotnet/dotnet.zip -DestinationPath Dependencies/dotnet/tmp}" +robocopy "Dependencies/dotnet/tmp/shared/Microsoft.NETCore.App/6.0.8/" "Dependencies/dotnet/bin/" *.dll /ns /nfl /ndl /nc /njh +rmdir "Dependencies/dotnet/tmp/" /s /q +del "Dependencies/dotnet/dotnet.zip" +powershell -Command "& {Remove-Item "Dependencies/dotnet/dotnet.zip"}" +if %_e%==14 (goto :done) else (goto :tinyddsloader) + + +:tinyddsloader +echo --------------------tinyddsloader------------------------- +rmdir "Dependencies/tinyddsloader" /S /Q +git clone https://github.com/SHADE-DP/tinyddsloader.git "Dependencies/tinyddsloader" +if %_e%==15 (goto :done) else (goto :fmod) + +:fmod +echo --------------------fmod------------------------- +rmdir "Dependencies/fmod" /S /Q +git clone https://github.com/SHADE-DP/FMOD.git "Dependencies/fmod" :done echo DONE! diff --git a/Dependencies.lua b/Dependencies.lua index 32382cf9..2e24222b 100644 --- a/Dependencies.lua +++ b/Dependencies.lua @@ -1,15 +1,18 @@ IncludeDir = {} -IncludeDir["assimp"] = "%{wks.location}/Dependencies/assimp" -IncludeDir["imgui"] = "%{wks.location}/Dependencies/imgui" -IncludeDir["imguizmo"] = "%{wks.location}/Dependencies/imguizmo" -IncludeDir["imnodes"] = "%{wks.location}/Dependencies/imnodes" -IncludeDir["msdf_atlas_gen"] = "%{wks.location}/Dependencies/msdf" -IncludeDir["msdfgen"] = "%{wks.location}/Dependencies/msdf/msdfgen" -IncludeDir["spdlog"] = "%{wks.location}/Dependencies/spdlog" -IncludeDir["tracy"] = "%{wks.location}/Dependencies/tracy" -IncludeDir["VMA"] = "%{wks.location}/Dependencies/VMA" -IncludeDir["yamlcpp"] = "%{wks.location}/Dependencies/yamlcpp/include" -IncludeDir["RTTR"] = "%{wks.location}/Dependencies/RTTR" -IncludeDir["ktx"] = "%{wks.location}/Dependencies/ktx" -IncludeDir["reactphysics3d"] = "%{wks.location}/Dependencies/reactphysics3d" +IncludeDir["ModelCompiler"] = "%{wks.location}\\Dependencies\\ModelCompiler" +IncludeDir["imgui"] = "%{wks.location}\\Dependencies\\imgui" +IncludeDir["imguizmo"] = "%{wks.location}\\Dependencies\\imguizmo" +IncludeDir["imnodes"] = "%{wks.location}\\Dependencies\\imnodes" +IncludeDir["msdf_atlas_gen"] = "%{wks.location}\\Dependencies\\msdf" +IncludeDir["msdfgen"] = "%{wks.location}\\Dependencies\\msdf\\msdfgen" +IncludeDir["spdlog"] = "%{wks.location}\\Dependencies\\spdlog" +IncludeDir["tracy"] = "%{wks.location}\\Dependencies\\tracy" +IncludeDir["VMA"] = "%{wks.location}\\Dependencies\\VMA" +IncludeDir["yamlcpp"] = "%{wks.location}\\Dependencies\\yamlcpp\\include" +IncludeDir["RTTR"] = "%{wks.location}\\Dependencies\\RTTR" +IncludeDir["reactphysics3d"] = "%{wks.location}\\Dependencies\\reactphysics3d" +IncludeDir["SDL"] = "%{wks.location}\\Dependencies\\SDL" IncludeDir["VULKAN"] = "$(VULKAN_SDK)" +IncludeDir["dotnet"] = "%{wks.location}\\Dependencies\\dotnet" +IncludeDir["tinyddsloader"] = "%{wks.location}\\Dependencies\\tinyddsloader" +IncludeDir["fmod"] = "%{wks.location}\\Dependencies\\fmod" \ No newline at end of file diff --git a/Premake/premake5.exe b/Premake/premake5.exe index f081fe1f..c25bb3fb 100644 Binary files a/Premake/premake5.exe and b/Premake/premake5.exe differ diff --git a/SHADE.sln b/SHADE.sln deleted file mode 100644 index ff6afa61..00000000 --- a/SHADE.sln +++ /dev/null @@ -1,87 +0,0 @@ -鏤 -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SHADE_Application", "SHADE_Application\SHADE_Application.vcxproj", "{BDC70008-29DE-FE9D-7255-8ABFDEAACF25}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{53E47842-3FC8-3998-A828-34EB942B241A}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGui", "Dependencies\imgui\ImGui.vcxproj", "{C0FF640D-2C14-8DBE-F595-301E616989EF}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "freetype", "Dependencies\msdf\msdfgen\freetype\freetype.vcxproj", "{89895BD8-7556-B6E3-9E6F-A48B8A9BEB71}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msdf-atlas-gen", "Dependencies\msdf\msdf-atlas-gen.vcxproj", "{38BD587B-248B-4C81-0D1F-BDA7F98B28E6}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msdfgen", "Dependencies\msdf\msdfgen\msdfgen.vcxproj", "{8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "reactphysics3d", "Dependencies\reactphysics3d\reactphysics3d.vcxproj", "{2ECAB41A-1A98-A820-032C-1947EF988485}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "Dependencies\spdlog\spdlog.vcxproj", "{8EAD431C-7A4F-6EF2-630A-82464F4BF542}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yaml-cpp", "Dependencies\yamlcpp\yaml-cpp.vcxproj", "{88F1A057-74BE-FB62-9DD7-E90A890331F1}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SHADE_Engine", "SHADE_Engine\SHADE_Engine.vcxproj", "{3F92E998-2BF5-783D-D47A-B1F3C0BC44C0}" - ProjectSection(ProjectDependencies) = postProject - {88F1A057-74BE-FB62-9DD7-E90A890331F1} = {88F1A057-74BE-FB62-9DD7-E90A890331F1} - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA} = {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA} - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6} = {38BD587B-248B-4C81-0D1F-BDA7F98B28E6} - {2ECAB41A-1A98-A820-032C-1947EF988485} = {2ECAB41A-1A98-A820-032C-1947EF988485} - {C0FF640D-2C14-8DBE-F595-301E616989EF} = {C0FF640D-2C14-8DBE-F595-301E616989EF} - {8EAD431C-7A4F-6EF2-630A-82464F4BF542} = {8EAD431C-7A4F-6EF2-630A-82464F4BF542} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BDC70008-29DE-FE9D-7255-8ABFDEAACF25}.Debug|x64.ActiveCfg = Debug|x64 - {BDC70008-29DE-FE9D-7255-8ABFDEAACF25}.Debug|x64.Build.0 = Debug|x64 - {BDC70008-29DE-FE9D-7255-8ABFDEAACF25}.Release|x64.ActiveCfg = Release|x64 - {BDC70008-29DE-FE9D-7255-8ABFDEAACF25}.Release|x64.Build.0 = Release|x64 - {C0FF640D-2C14-8DBE-F595-301E616989EF}.Debug|x64.ActiveCfg = Debug|x64 - {C0FF640D-2C14-8DBE-F595-301E616989EF}.Debug|x64.Build.0 = Debug|x64 - {C0FF640D-2C14-8DBE-F595-301E616989EF}.Release|x64.ActiveCfg = Release|x64 - {C0FF640D-2C14-8DBE-F595-301E616989EF}.Release|x64.Build.0 = Release|x64 - {89895BD8-7556-B6E3-9E6F-A48B8A9BEB71}.Debug|x64.ActiveCfg = Debug|x64 - {89895BD8-7556-B6E3-9E6F-A48B8A9BEB71}.Debug|x64.Build.0 = Debug|x64 - {89895BD8-7556-B6E3-9E6F-A48B8A9BEB71}.Release|x64.ActiveCfg = Release|x64 - {89895BD8-7556-B6E3-9E6F-A48B8A9BEB71}.Release|x64.Build.0 = Release|x64 - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6}.Debug|x64.ActiveCfg = Debug|x64 - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6}.Debug|x64.Build.0 = Debug|x64 - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6}.Release|x64.ActiveCfg = Release|x64 - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6}.Release|x64.Build.0 = Release|x64 - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA}.Debug|x64.ActiveCfg = Debug|x64 - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA}.Debug|x64.Build.0 = Debug|x64 - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA}.Release|x64.ActiveCfg = Release|x64 - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA}.Release|x64.Build.0 = Release|x64 - {2ECAB41A-1A98-A820-032C-1947EF988485}.Debug|x64.ActiveCfg = Debug|x64 - {2ECAB41A-1A98-A820-032C-1947EF988485}.Debug|x64.Build.0 = Debug|x64 - {2ECAB41A-1A98-A820-032C-1947EF988485}.Release|x64.ActiveCfg = Release|x64 - {2ECAB41A-1A98-A820-032C-1947EF988485}.Release|x64.Build.0 = Release|x64 - {8EAD431C-7A4F-6EF2-630A-82464F4BF542}.Debug|x64.ActiveCfg = Debug|x64 - {8EAD431C-7A4F-6EF2-630A-82464F4BF542}.Debug|x64.Build.0 = Debug|x64 - {8EAD431C-7A4F-6EF2-630A-82464F4BF542}.Release|x64.ActiveCfg = Release|x64 - {8EAD431C-7A4F-6EF2-630A-82464F4BF542}.Release|x64.Build.0 = Release|x64 - {88F1A057-74BE-FB62-9DD7-E90A890331F1}.Debug|x64.ActiveCfg = Debug|x64 - {88F1A057-74BE-FB62-9DD7-E90A890331F1}.Debug|x64.Build.0 = Debug|x64 - {88F1A057-74BE-FB62-9DD7-E90A890331F1}.Release|x64.ActiveCfg = Release|x64 - {88F1A057-74BE-FB62-9DD7-E90A890331F1}.Release|x64.Build.0 = Release|x64 - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0}.Debug|x64.ActiveCfg = Debug|x64 - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0}.Debug|x64.Build.0 = Debug|x64 - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0}.Release|x64.ActiveCfg = Release|x64 - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C0FF640D-2C14-8DBE-F595-301E616989EF} = {53E47842-3FC8-3998-A828-34EB942B241A} - {89895BD8-7556-B6E3-9E6F-A48B8A9BEB71} = {53E47842-3FC8-3998-A828-34EB942B241A} - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6} = {53E47842-3FC8-3998-A828-34EB942B241A} - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA} = {53E47842-3FC8-3998-A828-34EB942B241A} - {2ECAB41A-1A98-A820-032C-1947EF988485} = {53E47842-3FC8-3998-A828-34EB942B241A} - {8EAD431C-7A4F-6EF2-630A-82464F4BF542} = {53E47842-3FC8-3998-A828-34EB942B241A} - {88F1A057-74BE-FB62-9DD7-E90A890331F1} = {53E47842-3FC8-3998-A828-34EB942B241A} - EndGlobalSection -EndGlobal diff --git a/SHADE_Application/SHADE_Application.vcxproj b/SHADE_Application/SHADE_Application.vcxproj deleted file mode 100644 index 2616224e..00000000 --- a/SHADE_Application/SHADE_Application.vcxproj +++ /dev/null @@ -1,121 +0,0 @@ -鏤 - - - - Debug - x64 - - - Release - x64 - - - - {BDC70008-29DE-FE9D-7255-8ABFDEAACF25} - true - Win32Proj - SHADE_Application - 10.0 - - - - Application - true - Unicode - v142 - - - Application - false - Unicode - v142 - - - - - - - - - - - - - true - bin\Debug_x86_64\SHADE_Application\ - bin-int\Debug_x86_64\SHADE_Application\ - SHADE_Application - .exe - - - false - bin\Release_x86_64\SHADE_Application\ - bin-int\Release_x86_64\SHADE_Application\ - SHADE_Application - .exe - - - - Use - SBpch.h - Level4 - _DEBUG;%(PreprocessorDefinitions) - ..\Dependencies\spdlog\include;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) - EditAndContinue - Disabled - false - MultiThreadedDebugDLL - true - stdcpplatest - - - Windows - true - wWinMainCRTStartup - - - - - Use - SBpch.h - Level4 - _RELEASE;%(PreprocessorDefinitions) - ..\Dependencies\spdlog\include;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) - Full - true - true - false - true - MultiThreadedDLL - true - stdcpplatest - - - Windows - true - true - wWinMainCRTStartup - - - - - - - - - - - Create - - - - - - - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0} - - - - - - \ No newline at end of file diff --git a/SHADE_Application/SHADE_Application.vcxproj.filters b/SHADE_Application/SHADE_Application.vcxproj.filters deleted file mode 100644 index 1234632d..00000000 --- a/SHADE_Application/SHADE_Application.vcxproj.filters +++ /dev/null @@ -1,30 +0,0 @@ - - - - - {D9DE78AF-4594-F1A4-CE88-EB7B3A3DE8A8} - - - {86EEB3D0-7290-DEA6-5B4B-F2FA478C65F7} - - - - - Application - - - - Scenes - - - - - Application - - - - Scenes - - - - \ No newline at end of file diff --git a/SHADE_Application/premake5.lua b/SHADE_Application/premake5.lua index 8c17bf55..cba0c35e 100644 --- a/SHADE_Application/premake5.lua +++ b/SHADE_Application/premake5.lua @@ -1,9 +1,9 @@ project "SHADE_Application" kind "WindowedApp" language "C++" - cppdialect "C++latest" - targetdir ("bin/" .. outputdir .. "/%{prj.name}") - objdir ("bin-int/" .. outputdir .. "/%{prj.name}") + cppdialect "C++20" + targetdir (outputdir) + objdir (interdir) systemversion "latest" pchheader "SBpch.h" pchsource "%{prj.location}/src/SBpch.cpp" @@ -14,6 +14,7 @@ project "SHADE_Application" files { "%{prj.location}/src/**.h", + "%{prj.location}/src/**.hpp", "%{prj.location}/src/**.c", "%{prj.location}/src/**.cpp", "%{prj.location}/src/**.glsl", @@ -21,11 +22,27 @@ project "SHADE_Application" includedirs { - "%{IncludeDir.spdlog}/include", "../SHADE_Engine/src", - "src" + "src", + "%{IncludeDir.dotnet}/include", + "%{IncludeDir.SDL}/include", + } + + externalincludedirs + { + "%{IncludeDir.RTTR}\\include", + "%{IncludeDir.fmod}/include", + "%{IncludeDir.VULKAN}/Source/SPIRV-Reflect", + "%{IncludeDir.VMA}/include", + "%{IncludeDir.VULKAN}/include", + "%{IncludeDir.spdlog}/include", + "%{IncludeDir.tinyddsloader}", + "%{IncludeDir.reactphysics3d}\\include", + "%{IncludeDir.yamlcpp}" } + externalwarnings "Off" + flags { "MultiProcessorCompile" @@ -33,19 +50,47 @@ project "SHADE_Application" links { - "SHADE_Engine" + "SHADE_Engine", + "SHADE_Managed", + "yaml-cpp", + "SDL2.lib", + "SDL2main.lib" } - postbuildcommands + libdirs { + "%{IncludeDir.spdlog}/lib", + "%{IncludeDir.SDL}/lib" } + + defines + { + "NOMINMAX" + } + + disablewarnings + { + "4251", + "26812", + "26439", + "26451", + "26437", + "4275", + "4635" + } + + linkoptions { "-IGNORE:4006" } warnings 'Extra' filter "configurations:Debug" symbols "On" - defines {"_DEBUG"} + defines {"_DEBUG", "SHEDITOR"} filter "configurations:Release" optimize "On" - defines{"_RELEASE"} \ No newline at end of file + defines{"_RELEASE", "SHEDITOR"} + + filter "configurations:Publish" + optimize "On" + defines{"_RELEASE", "_PUBLISH"} diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index 0cb1ecd1..bf5b8d49 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -1,16 +1,53 @@ #include "SBpch.h" #include "SBApplication.h" +#include "ECS_Base/Managers/SHSystemManager.h" +//#define SHEDITOR #ifdef SHEDITOR #include "Editor/SHEditor.h" -#include "Scenes/SBEditorScene.h" +//#include "Scenes/SBEditorScene.h" #endif // SHEDITOR #include "Tools/SHLogger.h" +#include "Tools/SHFileUtilties.h" #include #include #include +#include + + +#include "Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h" + +// Managers +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneManager.h" + +// Systems +#include "AudioSystem/SHAudioSystem.h" +#include "Camera/SHCameraSystem.h" +#include "FRC/SHFramerateController.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" +#include "Input/SHInputManager.h" +#include "Math/Transform/SHTransformSystem.h" +#include "Physics/System/SHPhysicsSystem.h" +#include "Physics/System/SHPhysicsDebugDrawSystem.h" +#include "Scripting/SHScriptEngine.h" + +// Components +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Math/Transform/SHTransformComponent.h" + +#include "Scenes/SBTestScene.h" + +#include "Assets/SHAssetManager.h" +#include "Scenes/SBMainScene.h" +#include "Serialization/Configurations/SHConfigurationManager.h" + +#include "Tools/SHLogger.h" +#include "Tools/SHDebugDraw.h" + +using namespace SHADE; namespace Sandbox { @@ -19,37 +56,139 @@ namespace Sandbox ( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, - _In_ LPWSTR lpCmdLine, + _In_ LPWSTR lpCmdLine, _In_ INT nCmdShow ) { - SHLOG_TITLE("Initialising SBApplication") + // Set working directory + SHFileUtilities::SetWorkDirToExecDir(); + WindowData wndData{}; + auto& appConfig = SHConfigurationManager::LoadApplicationConfig(&wndData); + window.Create(hInstance, hPrevInstance, lpCmdLine, nCmdShow, wndData); - window.Create(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + // Create Systems + + SHSystemManager::CreateSystem(); + SHSystemManager::CreateSystem(); + SHSystemManager::CreateSystem(); + SHSystemManager::CreateSystem(); - #ifdef SHEDITOR - #else - #endif + SHSystemManager::CreateSystem(); + SHSystemManager::CreateSystem(); + + SHSystemManager::CreateSystem(); + SHGraphicsSystem* graphicsSystem = static_cast(SHSystemManager::GetSystem()); + + // Link up SHDebugDraw + SHSystemManager::CreateSystem(); + SHDebugDraw::Init(SHSystemManager::GetSystem()); + +#ifdef SHEDITOR + SDL_Init(SDL_INIT_VIDEO); + sdlWindow = SDL_CreateWindowFrom(window.GetHWND()); + SHSystemManager::CreateSystem(); + if(auto editor = SHSystemManager::GetSystem()) + { + editor->SetSDLWindow(sdlWindow); + editor->SetSHWindow(&window); + } +#endif + + // Create Routines + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + + SHSystemManager::RegisterRoutine(); + + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + + SHSystemManager::RegisterRoutine(); + + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + + //SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + +#ifdef SHEDITOR + SHSystemManager::RegisterRoutine(); +#endif + + SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); + + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + //SHComponentManager::CreateComponentSparseSet(); + + SHAssetManager::Load(); + + + SHSystemManager::RegisterRoutine(); + + // Set up graphics system and windows + graphicsSystem->SetWindow(&window); + + SHSystemManager::Init(); + + SHSceneManager::InitSceneManager(appConfig.startingSceneID); + + SHFrameRateController::UpdateFRC(); + + // Link up SHDebugDraw + SHDebugDraw::Init(SHSystemManager::GetSystem()); } void SBApplication::Update(void) { + SHGraphicsSystem* graphicsSystem = SHADE::SHSystemManager::GetSystem(); + SHEditor* editor = SHADE::SHSystemManager::GetSystem(); //TODO: Change true to window is open while (!window.WindowShouldClose()) { - #ifdef SHEDITOR - #else - #endif + SHFrameRateController::UpdateFRC(); + SHInputManager::UpdateInput(SHFrameRateController::GetRawDeltaTime()); + SHSceneManager::UpdateSceneManager(); +#ifdef SHEDITOR + if(editor->editorState == SHEditor::State::PLAY) + SHSceneManager::SceneUpdate(0.016f); +#endif + SHSystemManager::RunRoutines(editor->editorState != SHEditor::State::PLAY, 0.016f); + editor->PollPicking(); + + static bool drawColliders = false; + if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::F10)) + { + drawColliders = !drawColliders; + SHSystemManager::GetSystem()->SetDebugDrawFlag(SHPhysicsDebugDrawSystem::DebugDrawFlags::COLLIDER, drawColliders); + } } + // Finish all graphics jobs first + graphicsSystem->AwaitGraphicsExecution(); } void SBApplication::Exit(void) { - #ifdef SHEDITOR - #else - #endif + #ifdef SHEDITOR + SDL_DestroyWindow(sdlWindow); + SDL_Quit(); + #endif + + SHSceneManager::Exit(); + SHSystemManager::Exit(); + SHAssetManager::Exit(); } } diff --git a/SHADE_Application/src/Application/SBApplication.h b/SHADE_Application/src/Application/SBApplication.h index a1bf11eb..63626907 100644 --- a/SHADE_Application/src/Application/SBApplication.h +++ b/SHADE_Application/src/Application/SBApplication.h @@ -1,6 +1,8 @@ #ifndef SB_APPLICATION_H #define SB_APPLICATION_H #include +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include //using namespace SHADE; namespace Sandbox @@ -9,6 +11,7 @@ namespace Sandbox { private: SHADE::SHWindow window; + SDL_Window* sdlWindow; //SHAppConfig config; public: SBApplication() = default; diff --git a/SHADE_Application/src/Scenes/SBMainScene.cpp b/SHADE_Application/src/Scenes/SBMainScene.cpp new file mode 100644 index 00000000..b14f2e6f --- /dev/null +++ b/SHADE_Application/src/Scenes/SBMainScene.cpp @@ -0,0 +1,62 @@ + #include "SBpch.h" +#include "SBMainScene.h" + +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Scene/SHSceneManager.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Scripting/SHScriptEngine.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Graphics/MiddleEnd/Lights/SHLightComponent.h" + +#include "Assets/SHAssetManager.h" +#include "Camera/SHCameraComponent.h" +#include "Resource/SHResourceManager.h" +#include "Serialization/SHSerialization.h" + + using namespace SHADE; + +namespace Sandbox +{ + + void SBMainScene::WindowFocusFunc([[maybe_unused]] void* window, int focused) + { + if (focused) + { + } + else + { + } + } + + void SBMainScene::Load() + { + } + + void SBMainScene::Init() + { + sceneName = SHSerialization::DeserializeSceneFromFile(sceneAssetID); + } + + void SBMainScene::Update(float dt) + { + } + + void SBMainScene::Render() + { + } + + void SBMainScene::Unload() + { + } + + void SBMainScene::Free() + { + //SHSerialization::SerializeScene("resources/scenes/Scene01.SHADE"); + } +} diff --git a/SHADE_Application/src/Scenes/SBMainScene.h b/SHADE_Application/src/Scenes/SBMainScene.h new file mode 100644 index 00000000..7bd10118 --- /dev/null +++ b/SHADE_Application/src/Scenes/SBMainScene.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Scene/SHScene.h" +#include "Scene/SHSceneManager.h" + +namespace Sandbox +{ + class SBMainScene : public SHADE::SHScene + { + private: + EntityID camera; + EntityID testObj; + std::vector stressTestObjects; + + public: + virtual void Load(); + virtual void Init(); + virtual void Update(float dt); + virtual void Render(); + virtual void Free(); + virtual void Unload(); + + //TODO: Change to new window DO IT IN CPP TOO + void WindowFocusFunc(void* window, int focused); + + SBMainScene(void) = default; + }; + +} + diff --git a/SHADE_Application/src/Scenes/SBTestScene.cpp b/SHADE_Application/src/Scenes/SBTestScene.cpp index 46b1bb84..bcc7f09d 100644 --- a/SHADE_Application/src/Scenes/SBTestScene.cpp +++ b/SHADE_Application/src/Scenes/SBTestScene.cpp @@ -1,14 +1,34 @@ -#include "SBpch.h" + #include "SBpch.h" #include "SBTestScene.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Scene/SHSceneManager.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Scripting/SHScriptEngine.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Graphics/MiddleEnd/Lights/SHLightComponent.h" + +#include "Assets/SHAssetManager.h" +#include "Camera/SHCameraComponent.h" +#include "Math/SHColour.h" +#include "Resource/SHResourceManager.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" +#include "Tools/SHDebugDraw.h" + using namespace SHADE; namespace Sandbox { - void SBTestScene::WindowFocusFunc([[maybe_unused]]void* window, int focused) + void SBTestScene::WindowFocusFunc([[maybe_unused]] void* window, int focused) { - if(focused) + if (focused) { } else @@ -21,16 +41,203 @@ namespace Sandbox } void SBTestScene::Init() { + SHADE::SHGraphicsSystem* graphicsSystem = static_cast(SHADE::SHSystemManager::GetSystem()); + // Create temp meshes + const auto CUBE_MESH = SHADE::SHPrimitiveGenerator::Cube(*graphicsSystem); + //Test Racoon mesh + std::vector> handles; + std::vector> texHandles; + for (const auto& asset : SHAssetManager::GetAllAssets()) + { + switch (asset.type) + { + case AssetType::MESH: + if (asset.name == "Raccoon") + handles.emplace_back(SHResourceManager::LoadOrGet(asset.id)); + break; + case AssetType::TEXTURE: + if (asset.name == "RaccoonPreTexturedVer1_Base9") + texHandles.emplace_back(SHResourceManager::LoadOrGet(asset.id)); + break; + } + } + SHResourceManager::FinaliseChanges(); + + // Create Materials + auto baseRaccoonMat = graphicsSystem->AddOrGetBaseMaterialInstance(); + auto baseRaccoonMatInstant = graphicsSystem->AddMaterialInstanceCopy(baseRaccoonMat); + baseRaccoonMatInstant->SetProperty("data.color", SHVec4(1.0f, 1.0f, 1.0f, 1.0f)); + baseRaccoonMatInstant->SetProperty("data.textureIndex", 0); + baseRaccoonMatInstant->SetProperty("data.alpha", 0.1f); + + auto baseFloorMatInstant = graphicsSystem->AddMaterialInstanceCopy(baseRaccoonMat); + baseFloorMatInstant->SetProperty("data.color", SHVec4(1.0f, 1.0f, 1.0f, 1.0f)); + baseFloorMatInstant->SetProperty("data.textureIndex", 0); + baseFloorMatInstant->SetProperty("data.alpha", 0.1f); + + auto dummy = SHEntityManager::CreateEntity<>(); + + auto floor = SHEntityManager::CreateEntity(); + auto& floorRenderable = *SHComponentManager::GetComponent_s(floor); + auto& floorTransform = *SHComponentManager::GetComponent_s(floor); + auto& floorRigidBody = *SHComponentManager::GetComponent_s(floor); + auto& floorCollider = *SHComponentManager::GetComponent_s(floor); + + floorRenderable.SetMesh(CUBE_MESH); + floorRenderable.SetMaterial(graphicsSystem->GetDefaultMaterialInstance()); + + floorTransform.SetWorldScale({ 17.5f, 0.5f, 17.5f }); + floorTransform.SetWorldPosition({ 0.0f, -3.0f, -5.0f }); + + floorRigidBody.SetType(SHRigidBodyComponent::Type::STATIC); + + floorCollider.AddBoundingBox(); + + // Create blank entity with a script + //testObj = SHADE::SHEntityManager::CreateEntity(); + //auto& testObjRenderable = *SHComponentManager::GetComponent(testObj); + //testObjRenderable.Mesh = CUBE_MESH; + //testObjRenderable.SetMaterial(matInst); + + //raccoon ======================================================================================================================= + auto racoon = SHEntityManager::CreateEntity(MAX_EID, "Player"); + auto& racoonRenderable = *SHComponentManager::GetComponent_s(racoon); + auto& racoonTransform = *SHComponentManager::GetComponent_s(racoon); + auto& racoonRigidBody = *SHComponentManager::GetComponent_s(racoon); + auto& racoonCollider = *SHComponentManager::GetComponent_s(racoon); + + racoonRenderable.SetMesh(handles.front()); + racoonRenderable.SetMaterial(baseRaccoonMatInstant); + + racoonTransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); + racoonTransform.SetWorldPosition({ -3.0f, -2.0f, -5.0f }); + + racoonCollider.AddBoundingBox(); + racoonCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f,0.5f,0.0f)); + racoonCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + + auto racoonItemLocation = SHEntityManager::CreateEntity(); + auto& racoonItemLocationTransform = *SHComponentManager::GetComponent_s(racoonItemLocation); + SHSceneManager::GetCurrentSceneGraph().SetParent(racoonItemLocation, racoon); + + auto racoonCamera = SHEntityManager::CreateEntity(); + SHSceneManager::GetCurrentSceneGraph().SetParent(racoonCamera, racoon); + //================================================================================================================================ + + //item =========================================================================================================================== + auto item = SHEntityManager::CreateEntity(MAX_EID, "item"); + auto& itemRenderable = *SHComponentManager::GetComponent_s(item); + auto& itemTransform = *SHComponentManager::GetComponent_s(item); + auto& itemRigidBody = *SHComponentManager::GetComponent_s(item); + auto& itemCollider = *SHComponentManager::GetComponent_s(item); + + itemRenderable.SetMesh(handles.front()); + itemRenderable.SetMaterial(baseRaccoonMatInstant); + + itemTransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); + itemTransform.SetWorldPosition({ 0.0f, -2.0f, -5.0f }); + + itemCollider.AddBoundingBox(); + itemCollider.AddBoundingBox(SHVec3(2.0f,2.0f,2.0f)); + itemCollider.GetCollisionShape(1).SetIsTrigger(true); + + itemCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + itemCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + + itemCollider.GetCollisionShape(1).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + itemCollider.GetCollisionShape(1).SetBoundingBox(SHVec3(1.0f, 1.0f, 1.0f)); + + itemRigidBody.SetInterpolate(false); + itemRigidBody.SetFreezeRotationX(true); + itemRigidBody.SetFreezeRotationY(true); + itemRigidBody.SetFreezeRotationZ(true); + //================================================================================================================================ + + //AI ============================================================================================================================= + auto AI = SHEntityManager::CreateEntity(MAX_EID, "AI"); + auto& AIRenderable = *SHComponentManager::GetComponent_s(AI); + auto& AITransform = *SHComponentManager::GetComponent_s(AI); + auto& AIRigidBody = *SHComponentManager::GetComponent_s(AI); + auto& AICollider = *SHComponentManager::GetComponent_s(AI); + + AIRenderable.SetMesh(handles.front()); + AIRenderable.SetMaterial(baseRaccoonMatInstant); + + AITransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); + AITransform.SetWorldPosition({ -8.0f, -2.0f, 2.5f }); + + AICollider.AddBoundingBox(); + AICollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + AICollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + + AIRigidBody.SetInterpolate(false); + AIRigidBody.SetFreezeRotationX(true); + AIRigidBody.SetFreezeRotationY(true); + AIRigidBody.SetFreezeRotationZ(true); + //================================================================================================================================ + + SHADE::SHScriptEngine* scriptEngine = static_cast(SHADE::SHSystemManager::GetSystem()); + scriptEngine->AddScript(racoon, "PlayerController"); + scriptEngine->AddScript(racoon, "PickAndThrow"); + scriptEngine->AddScript(racoonCamera, "ThirdPersonCamera"); + scriptEngine->AddScript(AI, "AIPrototype"); + scriptEngine->AddScript(item, "Item"); + + auto raccoonShowcase = SHEntityManager::CreateEntity(); + auto& renderableShowcase = *SHComponentManager::GetComponent_s(raccoonShowcase); + auto& transformShowcase = *SHComponentManager::GetComponent_s(raccoonShowcase); + + renderableShowcase.SetMesh(handles.front()); + renderableShowcase.SetMaterial(baseRaccoonMatInstant); + renderableShowcase.GetModifiableMaterial()->SetProperty("data.color", SHVec4(1.0f, 1.0f, 1.0f, 1.0f)); + renderableShowcase.GetModifiableMaterial()->SetProperty("data.alpha", 1.0f); + renderableShowcase.GetModifiableMaterial()->SetProperty("data.textureIndex", 0); + + transformShowcase.SetWorldPosition({ 3.0f, -1.0f, -1.0f }); + transformShowcase.SetLocalScale({ 5.0f, 5.0f, 5.0f }); + scriptEngine->AddScript(raccoonShowcase, "RaccoonShowcase"); + + SHComponentManager::AddComponent(0); + SHComponentManager::AddComponent(0); + SHComponentManager::RemoveComponent (0); + SHComponentManager::RemoveComponent (0); + + auto ambientLight = SHEntityManager::CreateEntity(); + SHComponentManager::GetComponent(ambientLight)->SetColor(SHColour::WHITE); + SHComponentManager::GetComponent(ambientLight)->SetStrength(0.25f); + SHComponentManager::GetComponent(ambientLight)->SetType(SH_LIGHT_TYPE::AMBIENT); } + void SBTestScene::Update(float dt) { - (void)dt; + static float rotation = 0.0f; + SHVec3 direction{0.0f, 0.0f, 1.0f}; + direction = SHVec3::RotateY(direction, rotation); + auto* lightComp =SHComponentManager::GetComponent(0); + lightComp->SetDirection (direction); + rotation += 0.005f; + //auto& transform = *SHADE::SHComponentManager::GetComponent_s(testObj); + + //transform.SetWorldPosition({1.0f, 1.0f, -1.0f}); + //transform.SetWorldRotation(0.0f, 0.0f + rotation, 0.0f); + //rotation += dt * 0.2f; + + // Destroy entity if space is pressed + if (GetKeyState(VK_SPACE) & 0x8000) + { + rotation = 0.0f; + SHADE::SHScriptEngine* scriptEngine = static_cast(SHADE::SHSystemManager::GetSystem()); + scriptEngine->RemoveAllScripts(testObj); + } + + SHDebugDraw::Cube(SHColour::CRIMSON, SHVec3(1.0f, 0.0f, 0.0f), SHVec3(1.0f, 1.0f, 1.0f)); } void SBTestScene::Render() { + } void SBTestScene::Unload() @@ -41,8 +248,4 @@ namespace Sandbox { //SHSerialization::SerializeScene("resources/scenes/Scene01.SHADE"); } - } - - - diff --git a/SHADE_Application/src/Scenes/SBTestScene.h b/SHADE_Application/src/Scenes/SBTestScene.h index 6776c671..3a1598d5 100644 --- a/SHADE_Application/src/Scenes/SBTestScene.h +++ b/SHADE_Application/src/Scenes/SBTestScene.h @@ -9,7 +9,8 @@ namespace Sandbox { private: EntityID camera; - + EntityID testObj; + std::vector stressTestObjects; public: virtual void Load(); diff --git a/SHADE_Application/src/WinMain.cpp b/SHADE_Application/src/WinMain.cpp index 956566ab..f672cead 100644 --- a/SHADE_Application/src/WinMain.cpp +++ b/SHADE_Application/src/WinMain.cpp @@ -26,13 +26,15 @@ INT WINAPI wWinMain ) { const SHADE::SHLogger::Config LOGGER_CONFIG{ .directoryPath = "./logs/" }; - SHADE::SHLogger::Initialise(LOGGER_CONFIG); + auto logger = SHADE::SHLogger::Initialise(LOGGER_CONFIG); try { #ifndef SHEDITOR //ShowWindow(::GetConsoleWindow(), SW_HIDE); - #endif + #endif + + SHLOG_REGISTER(logger) SHADE::SHEngine::Run(hInstance, hPrevInstance, lpCmdLine, nCmdShow); _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); diff --git a/SHADE_CSharp/premake5.lua b/SHADE_CSharp/premake5.lua new file mode 100644 index 00000000..39ef8281 --- /dev/null +++ b/SHADE_CSharp/premake5.lua @@ -0,0 +1,64 @@ +project "SHADE_CSharp" + architecture "x64" + kind "SharedLib" + language "C#" + clr "NetCore" + dotnetframework "net5.0" + namespace ("SHADE") + targetdir (outputdir) + objdir (interdir) + systemversion "latest" + + files + { + "%{prj.location}/src/**.cs", + "%{prj.location}/src/**.tt" + } + + flags + { + "MultiProcessorCompile" + } + + dependson + { + "SHADE_Engine" + } + + warnings 'Extra' + + postbuildcommands + { + "xcopy /r /y /q \"%{outputdir}\\net5.0\\SHADE_CSharp.xml\" \"%{outputdir}\"", + "xcopy /r /y /q \"%{outputdir}\\net5.0\\SHADE_CSharp.pdb\" \"%{outputdir}\"" + } + + filter "configurations:Debug" + symbols "On" + defines {"_DEBUG"} + + filter "configurations:Release" + optimize "On" + defines{"_RELEASE"} + + filter "configurations:Publish" + optimize "On" + defines{"_RELEASE"} + + require "vstudio" + + function platformsElementCS(cfg) + _p(2,'x64') + end + function docsElementCS(cfg) + _p(2,'true') + end + function docsLocationElementCS(cfg) + _p(2,'$(OutDir)') + end + + premake.override(premake.vstudio.cs2005.elements, "projectProperties", function (oldfn, cfg) + return table.join(oldfn(cfg), { + platformsElementCS, docsElementCS, docsLocationElementCS, + }) + end) \ No newline at end of file diff --git a/SHADE_CSharp/src/Events/CallbackAction.cs b/SHADE_CSharp/src/Events/CallbackAction.cs new file mode 100644 index 00000000..b6082c0c --- /dev/null +++ b/SHADE_CSharp/src/Events/CallbackAction.cs @@ -0,0 +1,1115 @@ +鏤/************************************************************************************//*! +\file CallbackAction.cs +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definition of CallbackAction and related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +using System; +using System.Reflection; + +namespace SHADE +{ + /// + /// Interface for a CallbackAction that all variants inherit from. + /// + public interface ICallbackAction + { + /// + /// Whether or not this CallbackAction is runtime assigned. If it is, then the + /// TargetMethodName and TargetObject properties are invalid. + /// + bool IsRuntimeAction { get; } + /// + /// Name of the method that this CallbackAction is using. + /// + string TargetMethodName { get; } + /// + /// Object which the specified target method is called on. + /// + Object TargetObject { get; } + } + + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 0 parameter. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[0]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[0]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke() + { + if (targetAction != null) + { + targetAction.Invoke(); + } + else if (targetMethod != null) + { + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 1 parameter. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[1]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[1]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1) + { + if (targetAction != null) + { + targetAction.Invoke(t1); + } + else if (targetMethod != null) + { + parameters[0] = t1; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 2 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[2]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[2]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 3 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[3]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[3]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 4 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[4]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[4]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 5 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[5]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[5]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 6 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[6]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[6]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5, t6); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + parameters[5] = t6; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 7 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[7]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[7]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5, t6, t7); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + parameters[5] = t6; + parameters[6] = t7; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 8 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[8]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[8]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5, t6, t7, t8); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + parameters[5] = t6; + parameters[6] = t7; + parameters[7] = t8; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 9 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[9]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[9]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5, t6, t7, t8, t9); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + parameters[5] = t6; + parameters[6] = t7; + parameters[7] = t8; + parameters[8] = t9; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with 10 parameters. + /// + public class CallbackAction : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[10]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[10]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10) + { + if (targetAction != null) + { + targetAction.Invoke(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + else if (targetMethod != null) + { + parameters[0] = t1; + parameters[1] = t2; + parameters[2] = t3; + parameters[3] = t4; + parameters[4] = t5; + parameters[5] = t6; + parameters[6] = t7; + parameters[7] = t8; + parameters[8] = t9; + parameters[9] = t10; + _ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } +} diff --git a/SHADE_CSharp/src/Events/CallbackAction.tt b/SHADE_CSharp/src/Events/CallbackAction.tt new file mode 100644 index 00000000..3fbb7617 --- /dev/null +++ b/SHADE_CSharp/src/Events/CallbackAction.tt @@ -0,0 +1,149 @@ +鏤<# +/************************************************************************************//*! +\file CallbackAction.tt +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the T4 template for the definition of CallbackAction and + related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/#> +<#@ template hostspecific="false" language="C#" #> +<#@ output extension=".cs" #> +<# var max = 10; #> +/************************************************************************************//*! +\file CallbackAction.cs +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definition of CallbackAction and related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +using System; +using System.Reflection; + +namespace SHADE +{ + /// + /// Interface for a CallbackAction that all variants inherit from. + /// + public interface ICallbackAction + { + /// + /// Whether or not this CallbackAction is runtime assigned. If it is, then the + /// TargetMethodName and TargetObject properties are invalid. + /// + bool IsRuntimeAction { get; } + /// + /// Name of the method that this CallbackAction is using. + /// + string TargetMethodName { get; } + /// + /// Object which the specified target method is called on. + /// + Object TargetObject { get; } + } + +<# for (int i = 0; i <= max; ++i) { #> + /// + /// Represents a function call that can be serialised and put togetheer with scripts. + /// This variant accepts functions with <#=i#> parameter<# if (i > 1) {#>s<#} #>. + /// + public class CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> : ICallbackAction + { + #region Properties ------------------------------------------------------------ + /// + public Object TargetObject { get; private set; } + /// + public string TargetMethodName => targetMethod == null ? "" : targetMethod.Name; + /// + public bool IsRuntimeAction => targetAction != null; + #endregion + + #region Fields ------------------------------------------------------------------ + private MethodInfo targetMethod; + private Action<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> targetAction; + private Object[] parameters; + #endregion + + #region Constructors ------------------------------------------------------------ + /// + /// Constructs an empty Callback action. + /// + public CallbackAction() {} + /// + /// Constructs a CallbackAction that represents a call to the specified static + /// method. + /// + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(MethodInfo method) + { + // No errors, assign + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[<#=i#>]; + } + /// + /// Constructs a CallbackAction that represents a call to a specified member + /// method on the specified target. + /// + /// Object to call the method on. + /// Method to call. + /// + /// Thrown if a method that is not compatible with the target is specified. The method's + /// source type must match the target's type. + /// + public CallbackAction(Object target, MethodInfo method) + { + // Error Checks + if (method.DeclaringType != target.GetType()) + throw new ArgumentException("[CallbackAction] Attempted register an action using an incompatible target object and method."); + + // No errors, assign + TargetObject = target; + targetMethod = method; + + // Create storage for parameters for calling + parameters = new Object[<#=i#>]; + } + /// + /// Constructs a Callback action based on an action. + /// + /// Action that wraps a function to be called. + public CallbackAction(Action<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> action) + { + targetAction = action; + } + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + /// Invokes the CallbackAction's stored method/action with the specified parameters. + /// + public void Invoke(<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#> t<#=t#><# if (t != i) { #>, <# } #><# } #>) + { + if (targetAction != null) + { + targetAction.Invoke(<# for (int t = 1; t < i + 1; ++t) { #>t<#=t#><# if (t != i) { #>, <# } #><# } #>); + } + else if (targetMethod != null) + { + <# for (int t = 0; t < i; ++t) {#>parameters[<#=t#>] = t<#=t+1#>; + <# } #>_ = targetMethod.Invoke(TargetObject, parameters); + } + } + #endregion + } +<# } #> +} diff --git a/SHADE_CSharp/src/Events/CallbackEvent.cs b/SHADE_CSharp/src/Events/CallbackEvent.cs new file mode 100644 index 00000000..2c3cc388 --- /dev/null +++ b/SHADE_CSharp/src/Events/CallbackEvent.cs @@ -0,0 +1,994 @@ +/************************************************************************************//*! +\file CallbackEvent.cs +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definition of CallbackEvent and related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; + +namespace SHADE +{ + /// + /// Interface for a CallbackEvent that all variants inherit from. + /// + public interface ICallbackEvent + { + /// + /// Registers an empty ICallbackAction. + /// + void RegisterAction(); + /// + /// Registers an ICallbackAction with the event such that it will be called in + /// future + /// + /// ICallbackAction to register with. + void RegisterAction(ICallbackAction action); + /// + /// Deregisters an ICallbackAction that was previously added. This should + /// only emit a warning if an action that was not previous added was + /// provided. + /// + /// ICallbackAction to remove. + void DeregisterAction(ICallbackAction action); + /// + /// Iterable set of ICallbackActions that were registered to this event. + /// + IEnumerable Actions { get; } + } + + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke() + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5, t6); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5, t6, t7); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5, t6, t7, t8); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5, t6, t7, t8, t9); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action action) + { + actions.Add(new CallbackAction(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10) + { + foreach (CallbackAction action in actions) + { + try + { + action.Invoke(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } +} diff --git a/SHADE_CSharp/src/Events/CallbackEvent.tt b/SHADE_CSharp/src/Events/CallbackEvent.tt new file mode 100644 index 00000000..66a8b6d9 --- /dev/null +++ b/SHADE_CSharp/src/Events/CallbackEvent.tt @@ -0,0 +1,152 @@ +<# +/************************************************************************************//*! +\file CallbackEvent.tt +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the T4 template for the definition of CallbackEvent and + related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/#> +<#@ template hostspecific="false" language="C#" #> +<#@ output extension=".cs" #> +<# var max = 10; #> +/************************************************************************************//*! +\file CallbackEvent.cs +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definition of CallbackEvent and related classes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; + +namespace SHADE +{ + /// + /// Interface for a CallbackEvent that all variants inherit from. + /// + public interface ICallbackEvent + { + /// + /// Registers an empty ICallbackAction. + /// + void RegisterAction(); + /// + /// Registers an ICallbackAction with the event such that it will be called in + /// future + /// + /// ICallbackAction to register with. + void RegisterAction(ICallbackAction action); + /// + /// Deregisters an ICallbackAction that was previously added. This should + /// only emit a warning if an action that was not previous added was + /// provided. + /// + /// ICallbackAction to remove. + void DeregisterAction(ICallbackAction action); + /// + /// Iterable set of ICallbackActions that were registered to this event. + /// + IEnumerable Actions { get; } + } + +<# for (int i = 0; i <= max; ++i) { #> + /// + /// A container of CallbackActions that is correlated to a specific scenario as + /// specified by the user of this class. + /// This variant accepts CallbackEvents with 1 generic parameter. + /// + public class CallbackEvent<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> : ICallbackEvent + { + #region Properties -------------------------------------------------------------- + /// + public IEnumerable Actions => actions; + #endregion + + #region Fields ------------------------------------------------------------------ + private List actions = new List(); + #endregion + + #region Usage Functions --------------------------------------------------------- + /// + public void RegisterAction() + { + actions.Add(new CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #>()); + } + /// + public void RegisterAction(ICallbackAction action) + { + // Check if valid action + if (action.GetType() != typeof(CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #>)) + { + Debug.LogWarning("Attempted to register an invalid CallbackAction type. Ignoring.", this); + return; + } + + actions.Add(action); + } + /// + /// Adds a CallbackAction into the event. + /// + /// CallbackAction to add. + public void RegisterAction(CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> action) + { + actions.Add(action); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// System.Action to add as a CallbackAction. + public void RegisterAction(Action<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> action) + { + actions.Add(new CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #>(action)); + } + /// + /// Constructs and adds a CallbackAction into the event. + /// + /// Object to call the method on. + /// Method to call. + public void RegisterAction(Object target, MethodInfo method) + { + actions.Add(new CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #>(target, method)); + } + /// + public void DeregisterAction(ICallbackAction action) + { + if (!actions.Remove(action)) + { + Debug.LogWarning("Attempted to deregister invalid action. Ignored.", this); + } + } + /// + /// Invokes all stored CallbackActions with the specified parameters. + /// + public void Invoke(<# if (i != 0) { for (int t = 1; t < i + 1; ++t) { #>T<#=t#> t<#=t#><# if (t != i) { #>, <# } #><# } } #>) + { + foreach (CallbackAction<# if (i != 0) { #><<# for (int t = 1; t < i + 1; ++t) { #>T<#=t#><# if (t != i) { #>, <# } #><# } #>><# } #> action in actions) + { + try + { + action.Invoke(<# for (int t = 1; t < i + 1; ++t) { #>t<#=t#><# if (t != i) { #>, <# } #><# } #>); + } + catch (Exception e) + { + Debug.LogException(e, this); + } + } + } + #endregion + } +<# } #> +} diff --git a/SHADE_CSharp/src/Utility/Debug.cs b/SHADE_CSharp/src/Utility/Debug.cs new file mode 100644 index 00000000..eb9d9b24 --- /dev/null +++ b/SHADE_CSharp/src/Utility/Debug.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace SHADE +{ + internal static class Debug + { + [DllImport("SHADE_Engine.dll", EntryPoint = "SHLog_Info")] + public static extern void LogInfo([MarshalAs(UnmanagedType.LPStr)] string str); + [DllImport("SHADE_Engine.dll", EntryPoint = "SHLog_Warning")] + public static extern void LogWarning([MarshalAs(UnmanagedType.LPStr)] string str); + [DllImport("SHADE_Engine.dll", EntryPoint = "SHLog_Error")] + public static extern void LogError([MarshalAs(UnmanagedType.LPStr)] string str); + [DllImport("SHADE_Engine.dll", EntryPoint = "SHLog_Critical")] + public static extern void LogCritical([MarshalAs(UnmanagedType.LPStr)] string str); + public static void LogInfo(string msg, Object thrower) + { + LogInfo($"[{thrower.GetType().Name}] {msg}"); + } + public static void LogWarning(string msg, Object thrower) + { + LogWarning($"[{thrower.GetType().Name}] {msg}"); + } + public static void LogError(string msg, Object thrower) + { + LogError($"[{thrower.GetType().Name}] {msg}"); + } + public static void LogCritical(string msg, Object thrower) + { + LogCritical($"[{thrower.GetType().Name}] {msg}"); + } + public static void LogException(Exception exception, Object thrower) + { + LogError($"[{ thrower.GetType().Name }] Unhandled exception: { exception.ToString() }"); + } + } +} \ No newline at end of file diff --git a/SHADE_Engine/NatvisFile.natvis b/SHADE_Engine/NatvisFile.natvis new file mode 100644 index 00000000..6747262b --- /dev/null +++ b/SHADE_Engine/NatvisFile.natvis @@ -0,0 +1,10 @@ + + + + NULL + ID = {id.Data.Index} Version = {id.Data.Version} Type = {"$T1"} + + (*library).objects.denseArray[(*library).objects.sparseArray[id.Data.Index]] + + + \ No newline at end of file diff --git a/SHADE_Engine/SHADE_Engine.vcxproj b/SHADE_Engine/SHADE_Engine.vcxproj deleted file mode 100644 index b49a9186..00000000 --- a/SHADE_Engine/SHADE_Engine.vcxproj +++ /dev/null @@ -1,290 +0,0 @@ -鏤 - - - - Debug - x64 - - - Release - x64 - - - - {3F92E998-2BF5-783D-D47A-B1F3C0BC44C0} - true - Win32Proj - SHADE_Engine - 10.0 - - - - StaticLibrary - true - Unicode - v142 - - - StaticLibrary - false - Unicode - v142 - - - - - - - - - - - - - bin\Debug_x86_64\SHADE_Engine\ - bin-int\Debug_x86_64\SHADE_Engine\ - SHADE_Engine - .lib - - - bin\Release_x86_64\SHADE_Engine\ - bin-int\Release_x86_64\SHADE_Engine\ - SHADE_Engine - .lib - - - - Use - SHpch.h - Level4 - _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;NOMINMAX;_DEBUG;%(PreprocessorDefinitions) - src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;$(VULKAN_SDK)\Source\SPIRV-Reflect;%(AdditionalIncludeDirectories) - EditAndContinue - Disabled - false - MultiThreadedDebugDLL - true - stdcpplatest - - - Windows - true - - - vulkan-1.lib;shaderc_shared.lib;assimp-vc142-mtd.lib;ktxd.lib;librttr_core_d.lib;%(AdditionalDependencies) - libs;$(VULKAN_SDK)\Lib;..\Dependencies\assimp\lib\Debug;..\Dependencies\assimp\lib\Release;..\Dependencies\RTTR\lib;..\Dependencies\ktx\lib\Debug;..\Dependencies\ktx\lib\Release;%(AdditionalLibraryDirectories) - - - - - Use - SHpch.h - Level4 - _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;NOMINMAX;_RELEASE;%(PreprocessorDefinitions) - src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;$(VULKAN_SDK)\Source\SPIRV-Reflect;%(AdditionalIncludeDirectories) - Full - true - true - false - true - MultiThreadedDLL - true - stdcpplatest - - - Windows - true - true - - - vulkan-1.lib;shaderc_shared.lib;assimp-vc142-mt.lib;ktx.lib;librttr_core.lib;%(AdditionalDependencies) - libs;$(VULKAN_SDK)\Lib;..\Dependencies\assimp\lib\Debug;..\Dependencies\assimp\lib\Release;..\Dependencies\RTTR\lib;..\Dependencies\ktx\lib\Debug;..\Dependencies\ktx\lib\Release;%(AdditionalLibraryDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - {88F1A057-74BE-FB62-9DD7-E90A890331F1} - - - {8900D8DD-F5DF-5679-FEF7-E14F6A56BDDA} - - - {38BD587B-248B-4C81-0D1F-BDA7F98B28E6} - - - {2ECAB41A-1A98-A820-032C-1947EF988485} - - - {C0FF640D-2C14-8DBE-F595-301E616989EF} - - - {8EAD431C-7A4F-6EF2-630A-82464F4BF542} - - - - - - \ No newline at end of file diff --git a/SHADE_Engine/SHADE_Engine.vcxproj.filters b/SHADE_Engine/SHADE_Engine.vcxproj.filters deleted file mode 100644 index 3e537216..00000000 --- a/SHADE_Engine/SHADE_Engine.vcxproj.filters +++ /dev/null @@ -1,588 +0,0 @@ - - - - - {1AB26817-067F-C322-2F98-B1CA1BC4F8B0} - - - {EFD23933-5B34-1741-E4A1-5DF350024E00} - - - {261D0942-92A8-7606-9BB9-F9FA07C4D206} - - - {07FEB307-F3F6-D259-1C29-B8DE0881B265} - - - {EE037863-5A8F-E527-63A0-681CCFAA4128} - - - {DBC7D3B0-C769-FE86-B024-12DB9C6585D7} - - - {8A8E2B37-7646-6D84-DF4D-46E0CB240875} - - - {1653CE33-0220-293F-2B39-17E717655ECD} - - - {92C817CE-7EC1-3620-A7F3-1BA5934B162C} - - - {17C745C0-83DD-4356-CC54-CF7738AA14DE} - - - {51443AC7-3D28-FB1C-A688-F56F928BE59E} - - - {573A6CF2-43C9-F5BB-ECE7-09B7D8550662} - - - {08DBDC43-F4D3-FB95-1D06-E11A095EDBA1} - - - {4AD5CA42-3664-540C-DF82-6807CBF064B2} - - - {FB5EE099-67EA-4D5E-70FB-D052DC05AA5E} - - - {BA26540B-263D-52A1-6FB4-DDC2DB092329} - - - {4B204703-3704-0859-A064-02AC8C67F2DA} - - - {EBA1D3FF-D75C-C3AB-8014-3CF66CAE0D3C} - - - {8CDBA7C9-F8E8-D5AF-81CF-D19AEDDBA166} - - - {2460C057-1070-6C28-7929-D14665585BC1} - - - {FBD334F8-67EA-328E-B061-BEAF1CB70316} - - - {1DD51CAD-8960-8A71-9271-0D66FE7BE671} - - - {57DAB30C-4369-3DD6-EC87-51D1D8F54D7C} - - - {9C0DAFD9-086F-8CE7-91DC-D299FD3CC3A6} - - - {EF2D07CC-DB26-261E-0459-0BA3F0B0052A} - - - {3AEF06DD-A6D2-151D-AFD5-43591B38DC6D} - - - {245F5AB0-1085-2417-F9CA-A9E2E58F49E3} - - - {03DB39DE-EFBE-FA33-581F-F5864422E5B5} - - - {576DF841-4392-47C2-6CDD-2C52586146E0} - - - {75F29FE5-6102-4CB6-CABB-B0D4B6EA3A4F} - - - {5BAB2A92-478F-EBE7-B0EF-E53A9CF2D569} - - - {B3B14D12-9FC1-F9E2-087B-5E01F4A9E87B} - - - {AFF4887C-9B2B-8A0D-4418-7010302E060F} - - - {F1B75745-5D6D-D03A-E661-CA115216C73E} - - - {AC05897C-983C-8A0D-4129-70102D3F060F} - - - {ED6CDF9B-D939-3AA7-0253-284FEE7E6F35} - - - {B3F7140E-1F0C-3DBF-E88D-E01E546139F0} - - - {16CF2D0E-82E3-55BF-4B65-F91EB73852F0} - - - - - ECS_Base\Components - - - ECS_Base\Components - - - ECS_Base\Entity - - - ECS_Base\General - - - ECS_Base\General - - - ECS_Base\General - - - ECS_Base\General - - - ECS_Base\General - - - ECS_Base - - - ECS_Base\System - - - ECS_Base\System - - - ECS_Base\System - - - ECS_Base\System - - - Engine - - - Filesystem - - - Graphics\Buffers - - - Graphics\Commands - - - Graphics\Commands - - - Graphics\Commands - - - Graphics\Debugging - - - Graphics\Debugging - - - Graphics\Debugging - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Devices - - - Graphics\Devices - - - Graphics\Devices - - - Graphics\Framebuffer - - - Graphics\Images - - - Graphics\Images - - - Graphics\Images - - - Graphics\Instance - - - Graphics\MiddleEnd\Interface - - - Graphics\MiddleEnd\Interface - - - Graphics\MiddleEnd\PerFrame - - - Graphics\MiddleEnd\PerFrame - - - Graphics\MiddleEnd\Shaders - - - Graphics\MiddleEnd\Shaders - - - Graphics\MiddleEnd\Shaders - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Queues - - - Graphics\RenderGraph - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics - - - Graphics - - - Graphics - - - Graphics\Shaders\BlockInterface - - - Graphics\Shaders - - - Graphics\Shaders - - - Graphics\Shaders\spirv-reflect - - - Graphics\Swapchain - - - Graphics\Swapchain - - - Graphics\Synchronization - - - Graphics\Synchronization - - - Graphics\VertexDescriptors - - - Graphics\Windowing - - - Graphics\Windowing - - - Graphics\Windowing\Surface - - - Math - - - Math - - - Math - - - Math - - - Math\Vector - - - Math\Vector - - - Math\Vector - - - Meta - - - Resource - - - Resource - - - Resource - - - - Scene - - - Scene - - - Scene - - - Tools - - - Tools - - - Tools - - - Tools - - - - - - ECS_Base\Components - - - ECS_Base\Components - - - ECS_Base\Entity - - - ECS_Base\System - - - ECS_Base\System - - - ECS_Base\System - - - Engine - - - Filesystem - - - Graphics\Buffers - - - Graphics\Commands - - - Graphics\Commands - - - Graphics\Debugging - - - Graphics\Debugging - - - Graphics\Debugging - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Descriptors - - - Graphics\Devices - - - Graphics\Devices - - - Graphics\Devices - - - Graphics\Framebuffer - - - Graphics\Images - - - Graphics\Images - - - Graphics\Instance - - - Graphics\MiddleEnd\Interface - - - Graphics\MiddleEnd\Interface - - - Graphics\MiddleEnd\PerFrame - - - Graphics\MiddleEnd\PerFrame - - - Graphics\MiddleEnd\Shaders - - - Graphics\MiddleEnd\Shaders - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Pipeline - - - Graphics\Queues - - - Graphics\RenderGraph - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics\Renderpass - - - Graphics - - - Graphics - - - Graphics\Shaders\BlockInterface - - - Graphics\Shaders - - - Graphics\Shaders - - - Graphics\Shaders\spirv-reflect - - - Graphics\Swapchain - - - Graphics\Synchronization - - - Graphics\Synchronization - - - Graphics\VertexDescriptors - - - Graphics\Windowing - - - Graphics\Windowing - - - Graphics\Windowing\Surface - - - Math - - - Math - - - Math - - - Math\Vector - - - Math\Vector - - - Math\Vector - - - Resource - - - - Scene - - - Scene - - - Tools - - - Tools - - - Tools - - - \ No newline at end of file diff --git a/SHADE_Engine/premake5.lua b/SHADE_Engine/premake5.lua index c858d538..1d6d7bef 100644 --- a/SHADE_Engine/premake5.lua +++ b/SHADE_Engine/premake5.lua @@ -1,52 +1,60 @@ project "SHADE_Engine" - kind "StaticLib" + kind "SharedLib" language "C++" - cppdialect "C++latest" - targetdir ("bin/" .. outputdir .. "/%{prj.name}") - objdir ("bin-int/" .. outputdir .. "/%{prj.name}") + cppdialect "C++20" + targetdir (outputdir) + objdir (interdir) systemversion "latest" pchheader "SHpch.h" pchsource "%{prj.location}/src/SHpch.cpp" staticruntime "off" - + buildoptions{"/bigobj"} files { "%{prj.location}/src/**.h", + "%{prj.location}/src/**.hpp", "%{prj.location}/src/**.c", "%{prj.location}/src/**.cpp", "%{prj.location}/src/**.glsl", - "%{wks.location}/Dependencies/stb_image/**.cpp" + "%{prj.location}/**.natvis" } includedirs { "%{prj.location}/src", - "%{IncludeDir.assimp}/include", + } + + externalincludedirs + { "%{IncludeDir.imgui}", "%{IncludeDir.imguizmo}", "%{IncludeDir.imnodes}", "%{IncludeDir.msdf_atlas_gen}", "%{IncludeDir.msdfgen}", - "%{IncludeDir.spdlog}/include", + "%{IncludeDir.spdlog}\\include", "%{IncludeDir.tracy}", - "%{IncludeDir.VMA}/include", + "%{IncludeDir.VMA}\\include", "%{IncludeDir.yamlcpp}", - "%{IncludeDir.ktx}/include", - "%{IncludeDir.RTTR}/include", - "%{IncludeDir.reactphysics3d}/include", - "%{IncludeDir.VULKAN}/include", - "%{IncludeDir.VULKAN}/Source/SPIRV-Reflect" + "%{IncludeDir.SDL}\\include", + "%{IncludeDir.RTTR}\\include", + "%{IncludeDir.reactphysics3d}\\include", + "%{IncludeDir.VULKAN}\\include", + "%{IncludeDir.VULKAN}\\Source\\SPIRV-Reflect", + "%{IncludeDir.dotnet}\\include", + "%{IncludeDir.tinyddsloader}", + "%{IncludeDir.fmod}\\include" } + externalwarnings "Off" + libdirs { "%{prj.location}/libs", "%{IncludeDir.VULKAN}/Lib", - "%{IncludeDir.assimp}/lib/Debug", - "%{IncludeDir.assimp}/lib/Release", "%{IncludeDir.RTTR}/lib", - "%{IncludeDir.ktx}/lib/Debug", - "%{IncludeDir.ktx}/lib/Release", + "%{IncludeDir.SDL}/lib", + "%{IncludeDir.spdlog}/lib", + "%{IncludeDir.fmod}/lib" } links @@ -56,17 +64,33 @@ project "SHADE_Engine" "msdf-atlas-gen", "reactphysics3d", "imgui", - "spdlog", "vulkan-1.lib", - "shaderc_shared.lib" + "SDL2.lib", + "SDL2main.lib", + "shaderc_shared.lib", + "shlwapi.lib" } + disablewarnings + { + "4251", + "26812", + "26439", + "26451", + "26437", + "4275", + "4635" + } + + linkoptions { "-IGNORE:4006" } + defines { "_LIB", "_GLFW_INCLUDE_NONE", "MSDFGEN_USE_CPP11", - "NOMINMAX" + "NOMINMAX", + "SH_API_EXPORT" } flags @@ -81,19 +105,69 @@ project "SHADE_Engine" "msdf-atlas-gen", "reactphysics3d", "imgui", - "spdlog", } + postbuildcommands + { + "xcopy /s /r /y /q \"%{IncludeDir.spdlog}\\bin\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.SDL}\\lib\\SDL2.dll\" \"$(OutDir)\"", + "xcopy /s /r /y /q \"%{IncludeDir.dotnet}\\bin\" \"$(OutDir)\"" + } + + filter "configurations:Debug" + postbuildcommands + { + "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Debug\\assimp-vc142-mtd.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Debug\\ModelCompiler.exe\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodL.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudioL.dll\" \"$(OutDir)\"" + } + + filter "configurations:Release" + postbuildcommands + { + "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Release\\assimp-vc142-mt.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.ModelCompiler}\\bin\\Release\\ModelCompiler.exe\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmod.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudio.dll\" \"$(OutDir)\"" + } + + filter "configurations:Publish" + postbuildcommands + { + --"xcopy /r /y /q \"%{IncludeDir.assimp}\\bin\\Release\\assimp-vc142-mt.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmod.dll\" \"$(OutDir)\"", + "xcopy /r /y /q \"%{IncludeDir.fmod}\\lib\\fmodstudio.dll\" \"$(OutDir)\"" + } + + filter "configurations:Publish" + postbuildcommands + { + --"xcopy /r /y /q \"%{IncludeDir.assimp}\\bin\\Release\\assimp-vc142-mt.dll\" \"$(OutDir)\"" + } + warnings 'Extra' filter "configurations:Debug" symbols "On" - defines {"_DEBUG"} - links{"assimp-vc142-mtd.lib", "ktxd.lib", "librttr_core_d.lib"} - --links{"fmodstudioL_vc.lib", "fmodL_vc.lib"} + defines {"_DEBUG", "SHEDITOR"} + links{"librttr_core_d.lib", "spdlogd.lib"} + links{"fmodstudioL_vc.lib", "fmodL_vc.lib"} filter "configurations:Release" optimize "On" - defines{"_RELEASE"} - links{"assimp-vc142-mt.lib", "ktx.lib", "librttr_core.lib"} - --links{"fmodstudio_vc.lib", "fmod_vc.lib"} \ No newline at end of file + defines{"_RELEASE", "SHEDITOR"} + links{"librttr_core.lib", "spdlog.lib"} + links{"fmodstudio_vc.lib", "fmod_vc.lib"} + + filter "configurations:Publish" + optimize "On" + defines{"_RELEASE", "_PUBLISH"} + links{"librttr_core.lib", "spdlog.lib"} + excludes + { +-- "%{prj.location}/src/Editor/**.cpp", +-- "%{prj.location}/src/Editor/**.h", +-- "%{prj.location}/src/Editor/**.hpp", + } + links{"fmodstudio_vc.lib", "fmod_vc.lib"} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h new file mode 100644 index 00000000..b411a11e --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h @@ -0,0 +1,30 @@ +/*************************************************************************//** + * \file SHAnimationAsset.h + * \author Loh Xiao Qi + * \date October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include +#include +#include "SHAssetData.h" + +namespace SHADE +{ + struct SH_API SHAnimationAsset : SHAssetData + { + std::string name; + + std::vector nodeChannels; + std::vector meshChannels; + std::vector morphMeshChannels; + + double duration; + double ticksPerSecond; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHAssetData.h b/SHADE_Engine/src/Assets/Asset Types/SHAssetData.h new file mode 100644 index 00000000..8db9824c --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHAssetData.h @@ -0,0 +1,19 @@ +/*************************************************************************//** + * \file SHAssetDataBase.h + * \author Loh Xiao Qi + * \date October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +namespace SHADE +{ + struct SHAssetData + { + virtual ~SHAssetData(){} + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h b/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h new file mode 100644 index 00000000..f4fb49f0 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h @@ -0,0 +1,4 @@ +#pragma once + +#include "SHModelAsset.h" +#include "SHTextureAsset.h" \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHInternalAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHInternalAsset.h new file mode 100644 index 00000000..8bef34e9 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHInternalAsset.h @@ -0,0 +1,21 @@ +/*************************************************************************//** + * \file SHInternalAsset.h + * \author Loh Xiao Qi + * \date October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "SHAsset.h" + +namespace SHADE +{ + struct SHInternalAsset : SHAsset + { + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHMaterialAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHMaterialAsset.h new file mode 100644 index 00000000..a130fc07 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHMaterialAsset.h @@ -0,0 +1,23 @@ +/****************************************************************************** + * \file SHMaterialAsset.h + * \author Loh Xiao Qi + * \date 29 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include "Assets/Asset Types/SHAssetData.h" +#include + +namespace SHADE +{ + struct SHMaterialAsset : SHAssetData + { + std::string name; + std::string data; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h new file mode 100644 index 00000000..c057678b --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h @@ -0,0 +1,48 @@ +/*************************************************************************//** + * \file SHMeshAsset.h + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Struct to contain ready data for loading into GPU. Also used for + * compilation into binary files + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include +#include "Math/SHMath.h" +#include "SHAssetData.h" + +namespace SHADE +{ + struct SHMeshAssetHeader + { + uint32_t vertexCount; + uint32_t indexCount; + std::string name; + }; + + struct SHModelAssetHeader + { + size_t meshCount; + }; + + struct SH_API SHMeshData : SHAssetData + { + SHMeshAssetHeader header; + std::vector VertexPositions; + std::vector VertexTangents; + std::vector VertexNormals; + std::vector VertexTexCoords; + std::vector Indices; + }; + + struct SH_API SHModelAsset : SHAssetData + { + SHModelAssetHeader header; + std::vector subMeshes; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHPrefabAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHPrefabAsset.h new file mode 100644 index 00000000..0db299b2 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHPrefabAsset.h @@ -0,0 +1,23 @@ +/****************************************************************************** + * \file SHPrefabAsset.h + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include "SHAssetData.h" +#include + +namespace SHADE +{ + struct SHPrefabAsset : SHAssetData + { + std::string name; + std::string data; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHSceneAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHSceneAsset.h new file mode 100644 index 00000000..0f7061b1 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHSceneAsset.h @@ -0,0 +1,23 @@ +/****************************************************************************** + * \file SHSceneAsset.h + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include "SHAssetData.h" +#include + +namespace SHADE +{ + struct SHSceneAsset : SHAssetData + { + std::string name; + std::string data; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.cpp b/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.cpp new file mode 100644 index 00000000..d0200b1f --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.cpp @@ -0,0 +1,61 @@ +/*************************************************************************//** + * \file SHShaderAsset.cpp + * \author Brandon Mak + * \date 24 October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ + +#include "SHpch.h" +#include "SHShaderAsset.h" + +namespace SHADE +{ + SHShaderAsset::SHShaderAsset() noexcept + : spirvBinary{}, + shaderType{SH_SHADER_TYPE::VERTEX}, + name{} + { + } + + SHShaderAsset::SHShaderAsset(SHShaderAsset const& rhs) noexcept + : spirvBinary{rhs.spirvBinary}, + shaderType{ rhs.shaderType }, + name{rhs.name} + { + } + + SHShaderAsset::SHShaderAsset(SHShaderAsset&& rhs) noexcept + : spirvBinary{ std::move(rhs.spirvBinary) }, + shaderType{ std::move(rhs.shaderType) }, + name{ std::move(rhs.name) } + { + } + + SHShaderAsset& SHShaderAsset::operator=(SHShaderAsset&& rhs) noexcept + { + if (this == &rhs) + { + return *this; + } + + spirvBinary = std::move(rhs.spirvBinary); + shaderType = std::move(rhs.shaderType); + name = std::move(rhs.name); + } + + SHShaderAsset& SHShaderAsset::operator=(SHShaderAsset const& rhs) noexcept + { + if (this == &rhs) + { + return *this; + } + + spirvBinary = rhs.spirvBinary; + shaderType = rhs.shaderType; + name = rhs.name; + } +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.h new file mode 100644 index 00000000..d71c1bb3 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHShaderAsset.h @@ -0,0 +1,51 @@ +/*************************************************************************//** + * \file SHShaderAsset.h + * \author Brandon Mak + * \date 24 October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include +#include "SHAssetData.h" +#include "SH_API.h" +#include +#include + +namespace SHADE +{ + //! Tighter control over types of shaders. Maps directly to their + //! equivalent vk::ShaderStageFlagBits. + enum class SH_SHADER_TYPE : uint8_t + { + VERTEX = static_cast(vk::ShaderStageFlagBits::eVertex), + FRAGMENT = static_cast(vk::ShaderStageFlagBits::eFragment), + COMPUTE = static_cast(vk::ShaderStageFlagBits::eCompute), + INAVLID_TYPE = std::numeric_limits::max() + }; + + struct SH_API SHShaderAsset : SHAssetData + { + /*-----------------------------------------------------------------------*/ + /* MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! container storing the spirv binary + std::vector spirvBinary; + + //! For the compilation of the shader. Vulkan backend will use it too + SH_SHADER_TYPE shaderType; + + //! Name of the shader file (without parent path) + std::string name; + + SHShaderAsset() noexcept; + SHShaderAsset(SHShaderAsset const& rhs) noexcept; + SHShaderAsset(SHShaderAsset&& rhs) noexcept; + SHShaderAsset& operator= (SHShaderAsset&& rhs) noexcept; + SHShaderAsset& operator= (SHShaderAsset const& rhs) noexcept; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHTextureAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHTextureAsset.h new file mode 100644 index 00000000..d26a2c30 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/SHTextureAsset.h @@ -0,0 +1,49 @@ +#pragma once + +#include "tinyddsloader.h" +#include "Graphics/MiddleEnd/Textures/SHTextureLibrary.h" +#include "SHAssetData.h" + +namespace SHADE +{ + struct SHTextureAsset : SHAssetData + { + bool compiled; + + std::string name; + uint32_t numBytes; + uint32_t width; + uint32_t height; + SHTexture::TextureFormat format; + std::vector mipOffsets; + SHTexture::PixelChannel const * pixelData; + + SHTextureAsset() + : compiled{ false }, + numBytes{ 0 }, + width{ 0 }, + height{ 0 }, + format{ SHTexture::TextureFormat::eUndefined }, + pixelData{ nullptr } + {} + + SHTextureAsset(SHTextureAsset const& rhs) + : compiled{ false }, + numBytes{ rhs.numBytes }, + width{ rhs.width }, + height{ rhs.height }, + format{ rhs.format }, + mipOffsets{ rhs.mipOffsets }, + pixelData(rhs.pixelData) + {} + + //SHTextureAsset(SHTextureAsset&& rhs) + // : numBytes{ rhs.numBytes }, + // width{ rhs.width }, + // height{ rhs.height }, + // format{ rhs.format }, + // mipOffsets{ rhs.mipOffsets }, + // pixelData(std::move(rhs.pixelData)) + //{} + }; +} diff --git a/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.cpp b/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.cpp new file mode 100644 index 00000000..fd1f6d8a --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.cpp @@ -0,0 +1,163 @@ +/*************************************************************************//** + * \file SHShaderSourceCompiler.cpp + * \author Loh Xiao Qi + * \date 23 10 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#include "SHpch.h" +#include "SHShaderSourceCompiler.h" +#include "shaderc/shaderc.hpp" + +#include +#include +#include +#include + +namespace SHADE +{ + std::string SHShaderSourceCompiler::CompileShaderSourceToBinary(AssetPath path, SHShaderAsset const& data) noexcept + { + std::string newPath{ path.string() }; + newPath = newPath.substr(0, newPath.find_last_of('.')); + newPath += SHADER_BUILT_IN_EXTENSION.data(); + + std::ofstream file{ newPath, std::ios::binary | std::ios::out | std::ios::trunc }; + + file.write( + reinterpret_cast(& data.shaderType), sizeof(uint8_t) + ); + + size_t const byteCount = sizeof(uint32_t) * data.spirvBinary.size(); + + file.write( + reinterpret_cast(&byteCount), sizeof(size_t) + ); + + file.write( + reinterpret_cast(data.spirvBinary.data()), byteCount + ); + + file.close(); + + return newPath; + } + + SHShaderAsset const* SHShaderSourceCompiler::CompileShaderSourceToMemory(std::string const& data, std::string const& name, SH_SHADER_TYPE type) noexcept + { + // shaderc compiler + shaderc::Compiler compiler; + shaderc::CompileOptions options; + + options.AddMacroDefinition("MY_DEFINE", "1"); + + //TODO: Check if we need optimisation levels when compiling into spirv + // Set optimization levels + //if (opLevel != shaderc_optimization_level_zero) + // options.SetOptimizationLevel(opLevel); + + // Attempt to get the shaderc equivalent shader stage + shaderc_shader_kind shaderKind; + switch (type) + { + case SH_SHADER_TYPE::VERTEX: + shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; + break; + case SH_SHADER_TYPE::FRAGMENT: + shaderKind = shaderc_shader_kind::shaderc_glsl_fragment_shader; + break; + case SH_SHADER_TYPE::COMPUTE: + shaderKind = shaderc_shader_kind::shaderc_glsl_compute_shader; + break; + default: + shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; + break; + } + + // Compile the shader and get the result + shaderc::SpvCompilationResult compileResult = compiler.CompileGlslToSpv(data, shaderKind, name.c_str(), options); + + if (compileResult.GetCompilationStatus() != shaderc_compilation_status_success) + { + SHLOG_ERROR("Shaderc failed to compile GLSL shader to binary | " + compileResult.GetErrorMessage()); + return nullptr; + } + + auto result = new SHShaderAsset(); + result->spirvBinary.resize(compileResult.end() - compileResult.begin()); + + std::ranges::copy(compileResult.begin(), compileResult.end(), result->spirvBinary.data()); + + result->name = name; + result->shaderType = type; + + return result; + } + + SH_SHADER_TYPE SHShaderSourceCompiler::GetShaderTypeFromFilename(std::string name) noexcept + { + for (auto i { 0 }; i < SHADER_TYPE_MAX_COUNT; ++i) + { + const auto& [SHADER_SUFFIX, SHADER_TYPE] = SHADER_IDENTIFIERS[i]; + if (name.find(SHADER_SUFFIX.data()) != std::string::npos) + { + return SHADER_TYPE; + } + } + + return SH_SHADER_TYPE::INAVLID_TYPE; + } + + std::optional SHShaderSourceCompiler::LoadAndCompileShader(AssetPath path) noexcept + { + auto type = GetShaderTypeFromFilename(path.filename().string()); + + if (type == SH_SHADER_TYPE::INAVLID_TYPE) + { + SHLOG_ERROR("Invalid filename for shaders, follow suffix in SHAssetMacros.h: {}", path.string()); + return {}; + } + + path.make_preferred(); + + std::ifstream file{ path.string(), std::ios::in }; + + if (file.is_open()) + { + std::stringstream stream; + + stream << file.rdbuf(); + + std::string const content = stream.str(); + + auto data = CompileShaderSourceToMemory(content, path.filename().string(), type); + + if (data == nullptr) + { + return{}; + } + + return CompileShaderSourceToBinary(path, *data); + } + + SHLOG_ERROR("Unable to open shader file: {}", path.string()); + + return {}; + } + + std::optional SHShaderSourceCompiler::CompileShaderFromString + (std::string const& string, AssetPath path, SH_SHADER_TYPE type) noexcept + { + auto const data = CompileShaderSourceToMemory(string, path.filename().string(), type); + + if (data == nullptr) + { + return{}; + } + + return CompileShaderSourceToBinary(path, *data); + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.h b/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.h new file mode 100644 index 00000000..4ba87050 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Compilers/SHShaderSourceCompiler.h @@ -0,0 +1,31 @@ +/*************************************************************************//** + * \file SHShaderSourceCompiler.h + * \author Loh Xiao Qi + * \date 23 10 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "Assets/SHAssetMacros.h" +#include "Assets/Asset Types/SHShaderAsset.h" + +namespace SHADE +{ + class SHShaderSourceCompiler + { + private: + static std::string CompileShaderSourceToBinary(AssetPath path, SHShaderAsset const& data) noexcept; + static SHShaderAsset const* CompileShaderSourceToMemory(std::string const& data, std::string const& name, SH_SHADER_TYPE type) noexcept; + + static SH_SHADER_TYPE GetShaderTypeFromFilename(std::string name) noexcept; + + public: + static std::optional LoadAndCompileShader(AssetPath path) noexcept; + static std::optional CompileShaderFromString + (std::string const& string, AssetPath path, SH_SHADER_TYPE type) noexcept; + }; +} diff --git a/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.cpp b/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.cpp new file mode 100644 index 00000000..5093203a --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.cpp @@ -0,0 +1,171 @@ +/*************************************************************************//** + * \file SHTextureCompiler.cpp + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Library to write data in SHTextureAsset into binary file for + * faster loading in the future + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#include "SHpch.h" +#include "SHTextureCompiler.h" + +#include + +namespace SHADE +{ + + std::string SHTextureCompiler::TinyDDSResultToString(tinyddsloader::Result value) + { + switch (value) + { + case tinyddsloader::Result::ErrorFileOpen: + return "File open err"; + case tinyddsloader::Result::ErrorRead: + return "File read err"; + case tinyddsloader::Result::ErrorMagicWord: + return "File header magic word err"; + case tinyddsloader::Result::ErrorSize: + return "File size err"; + case tinyddsloader::Result::ErrorVerify: + return "Pixel format err"; + case tinyddsloader::Result::ErrorNotSupported: + return "Unsupported format"; + case tinyddsloader::Result::ErrorInvalidData: + return "Invalid data"; + default: + return "Unknown"; + } + } + + vk::Format SHTextureCompiler::ddsLoaderToVkFormat(tinyddsloader::DDSFile::DXGIFormat format, bool isLinear) + { + switch (format) + { + case tinyddsloader::DDSFile::DXGIFormat::BC1_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::BC1_UNorm_SRGB: + return isLinear ? vk::Format::eBc1RgbaUnormBlock : vk::Format::eBc1RgbaSrgbBlock; + case tinyddsloader::DDSFile::DXGIFormat::BC2_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::BC2_UNorm_SRGB: + return isLinear ? vk::Format::eBc2UnormBlock : vk::Format::eBc2SrgbBlock; + case tinyddsloader::DDSFile::DXGIFormat::BC3_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::BC3_UNorm_SRGB: + return isLinear ? vk::Format::eBc3UnormBlock : vk::Format::eBc3SrgbBlock; + case tinyddsloader::DDSFile::DXGIFormat::BC5_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::BC5_SNorm: + return isLinear ? vk::Format::eBc5UnormBlock : vk::Format::eBc5SnormBlock; + case tinyddsloader::DDSFile::DXGIFormat::R8G8B8A8_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::R8G8B8A8_UNorm_SRGB: + return isLinear ? vk::Format::eR8G8B8A8Unorm : vk::Format::eR8G8B8A8Srgb; + case tinyddsloader::DDSFile::DXGIFormat::R8G8B8A8_SNorm: + return vk::Format::eR8G8B8A8Snorm; + case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::B8G8R8A8_UNorm_SRGB: + return isLinear ? vk::Format::eB8G8R8A8Unorm : vk::Format::eB8G8R8A8Srgb; + case tinyddsloader::DDSFile::DXGIFormat::B8G8R8X8_UNorm: + case tinyddsloader::DDSFile::DXGIFormat::B8G8R8X8_UNorm_SRGB: + return isLinear ? vk::Format::eB8G8R8A8Unorm : vk::Format::eB8G8R8Srgb; + default: + throw std::runtime_error("Unsupported DDS format."); + } + } + + void SHTextureCompiler::LoadTinyDDS(AssetPath path, SHTextureAsset& asset) noexcept + { + tinyddsloader::Result loadResult = tinyddsloader::Result::Success; + tinyddsloader::DDSFile file; + loadResult = file.Load(path.string().c_str()); + if (loadResult != tinyddsloader::Result::Success) + { + SHLOG_ERROR("Unable to load Texture file: {} at {}", TinyDDSResultToString(loadResult), path.string()); + } + + size_t totalBytes{ 0 }; + + std::vector mipOff(file.GetMipCount()); + + for (size_t i{ 0 }; i < file.GetMipCount(); ++i) + { + mipOff[i] = static_cast(totalBytes); + totalBytes += file.GetImageData(static_cast(i), 0)->m_memSlicePitch; + } + + SHTexture::PixelChannel* pixel = new SHTexture::PixelChannel[totalBytes]; + std::memcpy(pixel, file.GetImageData()->m_mem, totalBytes); + //pixel = std::move(reinterpret_cast(file.GetDDSData())); + + asset.name = path.stem().string(); + asset.compiled = false; + asset.numBytes = static_cast(totalBytes); + asset.width = file.GetWidth(); + asset.height = file.GetHeight(); + asset.format = ddsLoaderToVkFormat(file.GetFormat(), true); + asset.mipOffsets = std::move(mipOff); + asset.pixelData = std::move(pixel); + } + + std::string SHTextureCompiler::WriteToFile(SHTextureAsset const& asset, AssetPath path) noexcept + { + std::string newPath{ path.string() }; + newPath = newPath.substr(0, newPath.find_last_of('.')); + newPath += TEXTURE_EXTENSION; + + std::ofstream file{ newPath, std::ios::out | std::ios::binary }; + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open file for writing texture file: {}", path.string()); + } + + constexpr auto intBytes{ sizeof(uint32_t) }; + + uint32_t const mipOffsetCount{ static_cast(asset.mipOffsets.size()) }; + + file.write( + reinterpret_cast(&asset.numBytes), + intBytes + ); + + file.write( + reinterpret_cast(&asset.width), + intBytes + ); + + file.write( + reinterpret_cast(&asset.height), + intBytes + ); + + file.write( + reinterpret_cast(&asset.format), + sizeof(SHTexture::TextureFormat) + ); + + file.write( + reinterpret_cast(&mipOffsetCount), + intBytes + ); + + file.write( + reinterpret_cast(asset.mipOffsets.data()), + intBytes * asset.mipOffsets.size() + ); + + file.write( + reinterpret_cast(asset.pixelData), + asset.numBytes + ); + + file.close(); + return newPath; + } + + std::optional SHTextureCompiler::CompileTextureAsset(AssetPath path) + { + auto data = new SHTextureAsset(); + LoadTinyDDS(path, *data); + return WriteToFile(*data, path); + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.h b/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.h new file mode 100644 index 00000000..8c4902a2 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Compilers/SHTextureCompiler.h @@ -0,0 +1,33 @@ +/*************************************************************************//** + * \file SHTextureCompiler.h + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Library to write data in SHTextureAsset into binary file for + * faster loading in the future + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once +#define TINYDDSLOADER_IMPLEMENTATION + +#include "Assets/Asset Types/SHTextureAsset.h" +#include "Assets/SHAssetMacros.h" +#include "tinyddsloader.h" + +namespace SHADE +{ + class SHTextureCompiler + { + private: + static std::string TinyDDSResultToString(tinyddsloader::Result value); + static vk::Format ddsLoaderToVkFormat(tinyddsloader::DDSFile::DXGIFormat format, bool isLinear); + static void LoadTinyDDS(AssetPath path, SHTextureAsset& asset) noexcept; + + static std::string WriteToFile(SHTextureAsset const& asset, AssetPath path) noexcept; + public: + static std::optional CompileTextureAsset(AssetPath path); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHAssetLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHAssetLoader.h new file mode 100644 index 00000000..b6b7656b --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHAssetLoader.h @@ -0,0 +1,23 @@ +/*************************************************************************//** + * \file SHAssetLoader.h + * \author Loh Xiao Qi + * \date October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "Assets/SHAssetMacros.h" +#include "Assets/Asset Types/SHAssetData.h" + +namespace SHADE +{ + struct SHAssetLoader + { + virtual SHAssetData* Load(AssetPath path) = 0; + virtual void Write(SHAssetData const* data, AssetPath path) = 0; + }; +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp new file mode 100644 index 00000000..74aa5350 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp @@ -0,0 +1,91 @@ +/*************************************************************************//** + * \file SHModelLoader.cpp + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Implementation for Mesh loader. Accounts for custom binary format + * as well as GLTF file format. + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#include "SHpch.h" +#include "SHModelLoader.h" +#include + +namespace SHADE +{ + void SHModelLoader::ReadHeader(std::ifstream& file, SHMeshLoaderHeader& header) noexcept + { + file.read( + reinterpret_cast(&header), + sizeof(SHMeshLoaderHeader) + ); + } + + void SHModelLoader::ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshData& data) noexcept + { + auto const vertexVec3Byte{ sizeof(SHVec3) * header.vertexCount }; + auto const vertexVec2Byte{ sizeof(SHVec2) * header.vertexCount }; + + data.VertexPositions.resize(header.vertexCount); + data.VertexTangents.resize(header.vertexCount); + data.VertexNormals.resize(header.vertexCount); + data.VertexTexCoords.resize(header.vertexCount); + data.Indices.resize(header.indexCount); + data.header.name.resize(header.charCount); + + file.read(data.header.name.data(), header.charCount); + file.read(reinterpret_cast(data.VertexPositions.data()), vertexVec3Byte); + file.read(reinterpret_cast(data.VertexTangents.data()), vertexVec3Byte); + file.read(reinterpret_cast(data.VertexNormals.data()), vertexVec3Byte); + file.read(reinterpret_cast(data.VertexTexCoords.data()), vertexVec2Byte); + file.read(reinterpret_cast(data.Indices.data()), sizeof(uint32_t) * header.indexCount); + + data.header.vertexCount = header.vertexCount; + data.header.indexCount = header.indexCount; + } + + void SHModelLoader::LoadSHMesh(AssetPath path, SHModelAsset& model) noexcept + { + std::ifstream file{ path.string(), std::ios::in | std::ios::binary }; + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open SHMesh File: {}", path.string()); + return; + } + + file.seekg(0); + + file.read( + reinterpret_cast(&model.header), + sizeof(SHModelAssetHeader) + ); + + std::vector headers(model.header.meshCount); + model.subMeshes.resize(model.header.meshCount); + + for (auto i{ 0 }; i < model.header.meshCount; ++i) + { + model.subMeshes[i] = new SHMeshData(); + ReadHeader(file, headers[i]); + ReadData(file, headers[i], *model.subMeshes[i]); + } + file.close(); + } + + SHAssetData* SHModelLoader::Load(AssetPath path) + { + auto result = new SHModelAsset(); + + LoadSHMesh(path, *result); + + return result; + } + + void SHModelLoader::Write(SHAssetData const* data, AssetPath path) + { + + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h new file mode 100644 index 00000000..35dc4514 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h @@ -0,0 +1,37 @@ +/*************************************************************************//** + * \file SHModelLoader.h + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Library to load gltf mesh files and custom binary format + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once +#include "Assets/Asset Types/SHModelAsset.h" +#include "SHAssetLoader.h" +#include + +namespace SHADE +{ + class SHModelLoader : public SHAssetLoader + { + struct SHMeshLoaderHeader + { + uint32_t vertexCount; + uint32_t indexCount; + uint32_t charCount; + }; + + + void ReadHeader(std::ifstream& file, SHMeshLoaderHeader& header) noexcept; + void ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshData& data) noexcept; + + public: + void LoadSHMesh(AssetPath path, SHModelAsset& model) noexcept; + SHAssetData* Load(AssetPath path) override; + void Write(SHAssetData const* data, AssetPath path) override; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.cpp new file mode 100644 index 00000000..f0d9a29b --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.cpp @@ -0,0 +1,69 @@ +/*************************************************************************//** + * \file SHShaderSourceLoader.cpp + * \author Loh Xiao Qi + * \date 23 10 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ + +#include "SHpch.h" +#include "SHShaderSourceLoader.h" +#include "Assets/Asset Types/SHShaderAsset.h" + +#include + +namespace SHADE +{ + SHAssetData* SHShaderSourceLoader::Load(AssetPath path) + { + auto result = new SHShaderAsset(); + + result->name = path.stem().stem().string(); + + std::ifstream file{ path.string(), std::ios::in | std::ios::binary }; + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open compiled shader file: {}", path.string()); + return nullptr; + } + + size_t byteCount = 0; + + file.read(reinterpret_cast(&result->shaderType), sizeof(uint8_t)); + file.read(reinterpret_cast(&byteCount), sizeof(size_t)); + + result->spirvBinary.resize(byteCount / sizeof(uint32_t)); + + file.read(reinterpret_cast(result->spirvBinary.data()), byteCount); + + file.close(); + + return result; + } + + void SHShaderSourceLoader::Write(SHAssetData const* data, AssetPath path) + { + std::ofstream file{ path, std::ios::binary | std::ios::out | std::ios::trunc }; + + auto asset = *dynamic_cast(data); + + file.write( + reinterpret_cast(&asset.shaderType), sizeof(uint8_t) + ); + + size_t const byteCount = sizeof(uint32_t) * asset.spirvBinary.size(); + + file.write( + reinterpret_cast(&byteCount), sizeof(size_t) + ); + + file.write( + reinterpret_cast(asset.spirvBinary.data()), byteCount + ); + + file.close(); + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.h new file mode 100644 index 00000000..0a4b614f --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHShaderSourceLoader.h @@ -0,0 +1,22 @@ +/*************************************************************************//** + * \file SHShaderSourceLoader.h + * \author Loh Xiao Qi + * \date 23 10 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "Assets/Libraries/Loaders/SHAssetLoader.h" + +namespace SHADE +{ + struct SHShaderSourceLoader : SHAssetLoader + { + SHAssetData* Load(AssetPath path) override; + void Write(SHAssetData const* data, AssetPath path) override; + }; +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.cpp new file mode 100644 index 00000000..b23130f3 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.cpp @@ -0,0 +1,95 @@ +/****************************************************************************** + * \file SHTextBasedLoader.cpp + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#include "SHpch.h" +#include "SHTextBasedLoader.h" + +#include "Assets/Asset Types/SHSceneAsset.h" +#include "Assets/Asset Types/SHPrefabAsset.h" +#include "Assets/Asset Types/SHMaterialAsset.h" + +#include +#include + +namespace SHADE +{ + SHAssetData* SHTextBasedLoader::Load(AssetPath path) + { + std::ifstream file{ path, std::ios::in }; + + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open text File: {}", path.string()); + return nullptr; + } + std::stringstream stream; + + stream << file.rdbuf(); + + std::string content = stream.str(); + + SHAssetData* result; + + if (path.extension().string() == SCENE_EXTENSION) + { + auto data = new SHSceneAsset(); + data->name = path.stem().string(); + data->data = std::move(content); + result = data; + } + else if (path.extension().string() == PREFAB_EXTENSION) + { + auto data = new SHPrefabAsset(); + data->name = path.stem().string(); + data->data = std::move(content); + result = data; + } + else if (path.extension().string() == MATERIAL_EXTENSION) + { + auto data = new SHMaterialAsset(); + data->name = path.stem().string(); + data->data = std::move(content); + result = data; + } + + file.close(); + + return result; + } + + void SHTextBasedLoader::Write(SHAssetData const* data, AssetPath path) + { + std::ofstream file{ path, std::ios::out | std::ios::trunc }; + + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open text File: {}", path.string()); + return; + } + + if (path.extension().string() == SCENE_EXTENSION) + { + auto scene = dynamic_cast(data); + file << scene->data; + } + else if (path.extension().string() == PREFAB_EXTENSION) + { + auto prefab = dynamic_cast(data); + file << prefab->data; + } + else if (path.extension().string() == MATERIAL_EXTENSION) + { + auto material = dynamic_cast(data); + file << material->data; + } + + file.close(); + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.h new file mode 100644 index 00000000..b74c6c94 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextBasedLoader.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * \file Header.h + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once +#include "SHAssetLoader.h" + +#include "Assets/Asset Types/SHPrefabAsset.h" +#include "Assets/Asset Types/SHSceneAsset.h" +#include "Assets/Asset Types/SHMaterialAsset.h" + +namespace SHADE +{ + struct SHTextBasedLoader : SHAssetLoader + { + SHAssetData* Load(AssetPath path) override; + void Write(SHAssetData const* data, AssetPath path) override; + }; +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.cpp new file mode 100644 index 00000000..423301dd --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.cpp @@ -0,0 +1,110 @@ +/*************************************************************************//** + * \file SHTextureLoader.cpp + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Library to load dds textures and custom binary format + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#include "SHpch.h" +#include "SHTextureLoader.h" + +#include + +namespace SHADE +{ + + void SHTextureLoader::LoadSHTexture(AssetPath path, SHTextureAsset& asset) noexcept + { + std::ifstream file{ path.string(), std::ios::in | std::ios::binary }; + if (!file.is_open()) + { + SHLOG_ERROR("Error opening SHTexture file: {}", path.string()); + } + + auto const intBytes{ sizeof(uint32_t) }; + uint32_t mipCount; + + file.read(reinterpret_cast(&asset.numBytes), intBytes); + file.read(reinterpret_cast(&asset.width), intBytes); + file.read(reinterpret_cast(&asset.height), intBytes); + file.read(reinterpret_cast(&asset.format), sizeof(SHTexture::TextureFormat)); + + file.read(reinterpret_cast(&mipCount), intBytes); + std::vector mips(mipCount); + file.read(reinterpret_cast(mips.data()), intBytes * mipCount); + + auto pixel = new SHTexture::PixelChannel[asset.numBytes]; + file.read(reinterpret_cast(pixel), asset.numBytes); + + asset.mipOffsets = std::move(mips); + asset.pixelData = std::move(pixel); + + asset.compiled = true; + file.close(); + } + + SHAssetData* SHTextureLoader::Load(AssetPath path) + { + auto result = new SHTextureAsset(); + + LoadSHTexture(path, *result); + + return result; + } + + void SHTextureLoader::Write(SHAssetData const* data, AssetPath path) + { + std::ofstream file{ path, std::ios::out | std::ios::binary }; + if (!file.is_open()) + { + SHLOG_ERROR("Unable to open file for writing texture file: {}", path.string()); + } + + auto asset = *dynamic_cast(data); + + constexpr auto intBytes{ sizeof(uint32_t) }; + + uint32_t const mipOffsetCount{ static_cast(asset.mipOffsets.size()) }; + + file.write( + reinterpret_cast(&asset.numBytes), + intBytes + ); + + file.write( + reinterpret_cast(&asset.width), + intBytes + ); + + file.write( + reinterpret_cast(&asset.height), + intBytes + ); + + file.write( + reinterpret_cast(&asset.format), + sizeof(SHTexture::TextureFormat) + ); + + file.write( + reinterpret_cast(&mipOffsetCount), + intBytes + ); + + file.write( + reinterpret_cast(asset.mipOffsets.data()), + intBytes * asset.mipOffsets.size() + ); + + file.write( + reinterpret_cast(asset.pixelData), + asset.numBytes + ); + + file.close(); + } +} diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.h new file mode 100644 index 00000000..27f7b844 --- /dev/null +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHTextureLoader.h @@ -0,0 +1,24 @@ +/*************************************************************************//** + * \file SHTextureLoader.h + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Library to load dds textures and custom binary format + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once +#include "Assets/Asset Types/SHTextureAsset.h" +#include "SHAssetLoader.h" + +namespace SHADE +{ + class SHTextureLoader : public SHAssetLoader + { + void LoadSHTexture(AssetPath path, SHTextureAsset& asset) noexcept; + SHAssetData* Load(AssetPath path) override; + void Write(SHAssetData const* data, AssetPath path) override; + }; +} diff --git a/SHADE_Engine/src/Assets/SHAsset.h b/SHADE_Engine/src/Assets/SHAsset.h new file mode 100644 index 00000000..29f43e0c --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAsset.h @@ -0,0 +1,31 @@ +/*************************************************************************//** + * \file SHAsset.h + * \author Loh Xiao Qi + * \date 30 September 2022 + * \brief Struct for asset identification and meta file writing + * + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "Assets/SHAssetMacros.h" +#include "SH_API.h" + +namespace SHADE +{ + struct SH_API SHAsset + { + AssetName name; + AssetID id; + AssetType type; + AssetPath path; + bool isSubAsset; + + std::vector subAssets; + + AssetID parent; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/SHAssetMacros.h b/SHADE_Engine/src/Assets/SHAssetMacros.h new file mode 100644 index 00000000..44dfd5c5 --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetMacros.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * \file SHAssetMacros.h + * \author Loh Xiao Qi + * \brief Macros and typedefs for assets + * + * \copyright Copyright (c) 2022 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited + ******************************************************************************/ +#ifndef SH_ASSET_MACROS_H +#define SH_ASSET_MACROS_H + +#include +#include +#include +#include "Asset Types/SHShaderAsset.h" + +// FMOD Fwd Declare +namespace FMOD +{ + class Sound; + class System; + class ChannelGroup; + class Channel; +} +enum FMOD_RESULT : int; +enum FMOD_SPEAKERMODE : int; + +// Typedefs +typedef uint32_t AssetID; +typedef std::string AssetName; +typedef std::filesystem::path AssetPath; +typedef unsigned char* AssetData; +typedef std::string AssetMetaVersion; +typedef std::string AssetExtension; +typedef size_t AssetTypeMeta; + +typedef FMOD::Sound* SHSound; + +// Asset Meta Version +constexpr std::string_view ASSET_META_VER { "1.0" }; + +// Asset type enum +enum class AssetType : AssetTypeMeta +{ + INVALID, + SHADER, + SHADER_BUILT_IN, + TEXTURE, + MODEL, + SCENE, + PREFAB, + MATERIAL, + MESH, + SCRIPT, + FONT, + MAX_COUNT +}; +constexpr size_t TYPE_COUNT{ static_cast(AssetType::MAX_COUNT) }; + +//Directory +#ifdef _PUBLISH +constexpr std::string_view ASSET_ROOT{ "Assets" }; +constexpr std::string_view BUILT_IN_ASSET_ROOT {"Built_In"}; +#else +constexpr std::string_view ASSET_ROOT {"../../Assets"}; +constexpr std::string_view BUILT_IN_ASSET_ROOT{ "../../Built_In" }; +#endif + +// COMPILER PATHS +constexpr std::string_view MODEL_COMPILER_EXE{ "ModelCompiler.exe" }; + +// INTERNAL ASSET PATHS +constexpr std::string_view SCENE_FOLDER{ "/Scenes/" }; +constexpr std::string_view PREFAB_FOLDER{ "/Prefabs/" }; +constexpr std::string_view MATERIAL_FOLDER{ "/Materials/" }; + + +// ASSET EXTENSIONS +constexpr std::string_view META_EXTENSION {".shmeta"}; +constexpr std::string_view AUDIO_EXTENSION {".ogg"}; +constexpr std::string_view AUDIO_WAV_EXTENSION {".wav"}; +constexpr std::string_view SHADER_EXTENSION{ ".shshader" }; +constexpr std::string_view SHADER_BUILT_IN_EXTENSION{".shshaderb"}; +constexpr std::string_view SCRIPT_EXTENSION {".cs"}; +constexpr std::string_view SCENE_EXTENSION {".shade"}; +constexpr std::string_view PREFAB_EXTENSION {".shprefab"}; +constexpr std::string_view MATERIAL_EXTENSION {".shmat"}; +constexpr std::string_view TEXTURE_EXTENSION {".shtex"}; +constexpr std::string_view MODEL_EXTENSION{ ".shmodel" }; +constexpr std::string_view FONT_EXTENSION{ ".shfont" }; + +constexpr std::string_view EXTENSIONS[] = { + AUDIO_EXTENSION, + SHADER_EXTENSION, + SHADER_BUILT_IN_EXTENSION, + TEXTURE_EXTENSION, + MODEL_EXTENSION, + SCENE_EXTENSION, + PREFAB_EXTENSION, + MATERIAL_EXTENSION, + "dummy", + SCRIPT_EXTENSION, + FONT_EXTENSION, + AUDIO_WAV_EXTENSION, +}; + +constexpr size_t EXTENSIONS_COUNT{ 11 }; + +// EXTERNAL EXTENSIONS +constexpr std::string_view GLSL_EXTENSION{ ".glsl" }; +constexpr std::string_view DDS_EXTENSION{ ".dds" }; +constexpr std::string_view FBX_EXTENSION{ ".fbx" }; +constexpr std::string_view GLTF_EXTENSION{ ".gltf" }; +constexpr std::string_view TTF_EXTENSION{ ".ttf" }; + +constexpr std::string_view EXTERNALS[] = { + GLSL_EXTENSION, + DDS_EXTENSION, + FBX_EXTENSION, + GLTF_EXTENSION, + TTF_EXTENSION +}; + +// SHADER IDENTIFIERS +constexpr std::string_view VERTEX_SHADER{ "_VS" }; +constexpr std::string_view FRAGMENT_SHADER{ "_FS" }; +constexpr std::string_view COMPUTER_SHADER{ "_CS" }; + +constexpr std::pair SHADER_IDENTIFIERS[] = { + std::make_pair(VERTEX_SHADER, SHADE::SH_SHADER_TYPE::VERTEX), + std::make_pair(FRAGMENT_SHADER, SHADE::SH_SHADER_TYPE::FRAGMENT), + std::make_pair(COMPUTER_SHADER, SHADE::SH_SHADER_TYPE::COMPUTE) +}; + +constexpr size_t SHADER_TYPE_MAX_COUNT{ 3 }; +#endif // !SH_ASSET_MACROS_H diff --git a/SHADE_Engine/src/Assets/SHAssetManager.cpp b/SHADE_Engine/src/Assets/SHAssetManager.cpp new file mode 100644 index 00000000..b4ea7d35 --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetManager.cpp @@ -0,0 +1,666 @@ +/****************************************************************************** + * \file SHAssetManager.cpp + * \author Loh Xiao Qi + * \brief Implementations for SHAssetManager.h + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#include "SHpch.h" +#include +#include +#include +#include + +#include "SHAssetManager.h" +#include "SHAssetMetaHandler.h" + +#include "Libraries/Loaders/SHModelLoader.h" +#include "Libraries/Loaders/SHTextureLoader.h" +#include "Libraries/Loaders/SHShaderSourceLoader.h" +#include "Libraries/Loaders/SHTextBasedLoader.h" + +//#include "Libraries/Compilers/SHMeshCompiler.h" +#include "Libraries/Compilers/SHTextureCompiler.h" +#include "Libraries/Compilers/SHShaderSourceCompiler.h" + +#include "Filesystem/SHFileSystem.h" + +namespace SHADE +{ + FolderPointer SHAssetManager::folderRoot{ nullptr }; + + FMOD::System* SHAssetManager::audioSystem; + std::unordered_map* SHAssetManager::audioSoundList; + + std::vector SHAssetManager::loaders(TYPE_COUNT); + + std::unordered_map SHAssetManager::assetCollection; + std::unordered_map SHAssetManager::assetData; + + + /**************************************************************************** + * \brief Static function to generate asset ID. + ****************************************************************************/ + AssetID SHAssetManager::GenerateAssetID(AssetType type) noexcept + { + std::default_random_engine randEngine{ + static_cast(std::chrono::system_clock::now().time_since_epoch().count()) }; + std::mt19937 idGen{ randEngine() }; + AssetID result{ static_cast(type) << 24}; + AssetID unique{ idGen() & ((1 << 24) - 1) }; + + result |= unique; + + while (result == 0 || assetCollection.contains(result)) + { + result = GenerateAssetID(type); + } + return result; + } + + /**************************************************************************** + * \brief Deallocate all memory used by asset data + ****************************************************************************/ + void SHAssetManager::Unload(AssetID assetId) noexcept + { + // TODO + } + + void SHAssetManager::Exit() noexcept + { + delete loaders[static_cast(AssetType::SHADER)]; + delete loaders[static_cast(AssetType::TEXTURE)]; + delete loaders[static_cast(AssetType::MESH)]; + delete loaders[static_cast(AssetType::SCENE)]; + + for (auto const& data : std::ranges::views::values(assetData)) + { + delete data; + } + } + + AssetPath SHAssetManager::GenerateLocalPath(AssetPath path) noexcept + { + if (!SHFileSystem::IsRecognised(path.extension().string().c_str())) + { + //TODO:ASSERT UNRECOGNISED FILE TYPE + return std::filesystem::path(); + } + + AssetType type = SHAssetMetaHandler::GetTypeFromExtension(path.extension().string().c_str()); + std::string folder; + //TODO Implement asset type generation + //switch (type) + //{ + //default: + // //TODO:ASSERT UNSUPPORTED FILE TYPE + // return std::filesystem::path(); + //} + + return std::filesystem::path(std::string(ASSET_ROOT) + folder + path.filename().string()); + } + + AssetPath SHAssetManager::GenerateNewPath(AssetName name, AssetType type) + { + std::string folder; + + switch(type) + { + case AssetType::SHADER: + case AssetType::SHADER_BUILT_IN: + folder = "Shaders/"; + break; + + default: + folder = "/"; + } + + return std::filesystem::path{ + std::string(ASSET_ROOT) + + folder + + name + + std::string(EXTENSIONS[static_cast(type)]) + }; + } + + /**************************************************************************** + * \brief Get record of all assets currently loaded with name and id. + * + * \return const& to unordered_map + ****************************************************************************/ + std::vector SHAssetManager::GetAllAssets() noexcept + { + std::vector result; + result.reserve(assetCollection.size()); + + for (auto const& asset : std::ranges::views::values(assetCollection)) + { + result.push_back(asset); + } + + return result; + } + + /**************************************************************************** + * \brief Create record for new asset. CAN ONLY CREATE FOR CUSTOM + * ASSETS CREATED BY THE ENGINE. + * + * \param type of asset + * \param name of asset + * \return asset id generated for new asset + ****************************************************************************/ + AssetID SHAssetManager::CreateNewAsset(AssetType type, AssetName name) noexcept + { + SHAssetData* data = nullptr; + std::string newPath{ ASSET_ROOT }; + switch (type) + { + case AssetType::PREFAB: + newPath += PREFAB_FOLDER; + newPath += name; + newPath += PREFAB_EXTENSION; + { + auto prefab = new SHPrefabAsset(); + prefab->name = name; + + data = prefab; + } + + break; + + case AssetType::SCENE: + newPath += SCENE_FOLDER; + newPath += name; + newPath += SCENE_EXTENSION; + + { + auto scene = new SHSceneAsset(); + scene->name = name; + + data = scene; + } + break; + + case AssetType::MATERIAL: + newPath += MATERIAL_FOLDER; + newPath += name; + newPath += MATERIAL_EXTENSION; + + { + auto material = new SHMaterialAsset(); + material->name = name; + + data = material; + } + break; + + default: + SHLOG_ERROR("Asset type of {} not an internal asset type, cannot be created", name); + return 0; + } + + auto id = GenerateAssetID(type); + SHAsset asset{ + name, + id, + type, + newPath, + false + }; + + auto result = assetCollection.emplace( + id, + SHAsset( + name, + id, + type, + newPath, + false + ) + ); + + assetData.emplace(id, data); + + SHAssetMetaHandler::WriteMetaData(asset); + SaveAsset(id); + + return id; + } + + bool SHAssetManager::SaveAsset(AssetID id) noexcept + { + if (assetCollection.contains(id)) + { + auto const& asset = assetCollection[id]; + if ( + asset.type == AssetType::SCENE || + asset.type == AssetType::PREFAB || + asset.type == AssetType::MATERIAL + ) + { + if (assetData.contains(id)) + { + auto const data = assetData.at(id); + loaders[static_cast(asset.type)]->Write(data, asset.path); + SHAssetMetaHandler::WriteMetaData(asset); + + return true; + } + + SHLOG_ERROR("Asset data has not been written into, cannot be saved: {}", + asset.path.filename().string()); + + return false; + } + } + + SHLOG_WARNING("Asset id: {} not an internal asset type, save cannot be triggered", id); + return false; + } + + bool SHAssetManager::DeleteAsset(AssetID id) noexcept + { + + if (assetCollection.contains(id)) + { + auto const& asset = assetCollection[id]; + if ( + asset.type == AssetType::SCENE || + asset.type == AssetType::PREFAB || + asset.type == AssetType::MATERIAL + ) + { + return (DeleteLocalFile(asset.path) && DeleteLocalFile(asset.path.string() + META_EXTENSION.data())); + } + SHLOG_WARNING("Asset id: {} not an internal asset type, file deletion not allowed", id); + } + + SHLOG_WARNING("Asset id does not exist, nothing was deleted: {}", id); + return false; + } + + //AssetID SHAssetManager::CreateAsset(AssetName name, AssetType type) noexcept + //{ + // AssetID id = GenerateAssetID(type); + + // assetCollection.emplace_back( + // name, + // id, + // type, + // GenerateNewPath(name, type) + // ); + // return id; + //} + + + /**************************************************************************** + * \brief Import new asset from outside editor window. + * + * \param path - c style string to full path + * \return asset if generated for new + ****************************************************************************/ + AssetID SHAssetManager::ImportNewAsset(char const* p) noexcept + { + std::filesystem::path const path{ p }; + + auto const type = SHAssetMetaHandler::GetTypeFromExtension(path.extension().string()); + auto const id = GenerateAssetID(type); + + std::filesystem::path const newPath{ GenerateLocalPath(path) }; + if (newPath.empty()) + { + SHLOG_WARNING("Unsupported file format for asset: {}", path.string()); + return 0; + } + + std::filesystem::copy(path, newPath); + + auto asset = CreateAssetFromPath(newPath); + assetCollection.insert({asset.id, asset}); + + return id; + } + + /**************************************************************************** + * \brief Search through assets folder for new unregistered assets. + * Takes in no params and returns nothing. Only updates internally. + ****************************************************************************/ + void SHAssetManager::RefreshAllAssets() noexcept + { + + } + + std::vector SHAssetManager::GetAllDataOfType(AssetType type) noexcept + { + auto const toRetrieve = GetAllRecordOfType(type); + std::vector result; + result.reserve(toRetrieve.size()); + for (auto const& get : toRetrieve) + { + result.push_back(LoadData(get)); + } + + return result; + } + + std::vector SHAssetManager::GetAllRecordOfType(AssetType type) noexcept + { + std::vector result; + for (auto const& asset : std::ranges::views::values(assetCollection)) + { + if (asset.type == type) + { + result.push_back(asset); + } + } + + return result; + } + + void SHAssetManager::CompileAsset(AssetPath const& path, bool genMeta) noexcept + { + if (!std::filesystem::exists(path)) + { + SHLOG_ERROR("Path provided does not point to a file: {}", path.string()); + return; + } + AssetPath newPath; + auto const ext{ path.extension().string() }; + if (ext == GLSL_EXTENSION.data()) + { + newPath = SHShaderSourceCompiler::LoadAndCompileShader(path).value(); + } + else if (ext == GLTF_EXTENSION.data() || ext == FBX_EXTENSION.data()) + { + std::string command = MODEL_COMPILER_EXE.data(); + command += " " + path.string(); + std::system(command.c_str()); + + std::string modelPath = path.string().substr(0, path.string().find_last_of('.')); + modelPath += MODEL_EXTENSION; + newPath = modelPath; + } + else if (ext == DDS_EXTENSION.data()) + { + auto pathGen = SHTextureCompiler::CompileTextureAsset(path); + if (!pathGen.has_value()) + { + SHLOG_ERROR("Texture Compilation Failed for: {}", path.string()); + return; + } + newPath = pathGen.value(); + } + else + { + SHLOG_WARNING("File Type compilation not yet Implemented: {}", path.string()); + return; + } + + if (genMeta) + { + GenerateNewMeta(newPath); + } + } + + FolderPointer SHAssetManager::GetRootFolder() noexcept + { + return folderRoot; + } + + void SHAssetManager::RefreshDirectory() noexcept + { + SHFileSystem::DestroyDirectory(folderRoot); + //assetCollection.clear(); + BuildAssetCollection(); + } + + SHAsset SHAssetManager::CreateAssetFromPath(AssetPath path) noexcept + { + SHAsset result; + + result.name = path.stem().string(); + result.type = SHAssetMetaHandler::GetTypeFromExtension(path.extension().string()); + result.id = GenerateAssetID(result.type); + result.path = path; + + return result; + } + + bool SHAssetManager::DeleteLocalFile(AssetPath path) noexcept + { + //TODO Move this to dedicated library + return std::filesystem::remove(path); + } + + void SHAssetManager:: InitLoaders() noexcept + { + loaders[static_cast(AssetType::SHADER)] = dynamic_cast(new SHShaderSourceLoader()); + loaders[static_cast(AssetType::SHADER_BUILT_IN)] = loaders[static_cast(AssetType::SHADER)]; + loaders[static_cast(AssetType::TEXTURE)] = dynamic_cast(new SHTextureLoader()); + loaders[static_cast(AssetType::MODEL)] = dynamic_cast(new SHModelLoader()); + loaders[static_cast(AssetType::SCENE)] = dynamic_cast(new SHTextBasedLoader()); + loaders[static_cast(AssetType::PREFAB)] = loaders[static_cast(AssetType::SCENE)]; + loaders[static_cast(AssetType::MATERIAL)] = loaders[static_cast(AssetType::SCENE)]; + loaders[static_cast(AssetType::MESH)] = nullptr; + } + + /**************************************************************************** + * \brief Load all assets that are in the folder + ****************************************************************************/ + void SHAssetManager::Load() noexcept + { + BuildAssetCollection(); + InitLoaders(); + //CompileAll(); + //LoadAllData(); + } + + /**************************************************************************** + * \brief Load asset data into memory + ****************************************************************************/ + void SHAssetManager::LoadAllData() noexcept + { + for (auto const& asset : std::ranges::views::values(assetCollection)) + { + SHAssetData* data = loaders[static_cast(asset.type)]->Load(asset.path); + assetData.emplace(asset.id, data); + } + } + + SHAssetData* SHAssetManager::LoadData(SHAsset const& asset) noexcept + { + if (asset.isSubAsset) + { + return LoadSubData(asset); + } + + SHAssetData* data = loaders[static_cast(asset.type)]->Load(asset.path); + if (data == nullptr) + { + SHLOG_ERROR("Unable to load asset into memory: {}\n", asset.path.string()); + } + else + { + assetData.emplace(asset.id, data); + } + + return data; + } + + SHAssetData* SHAssetManager::LoadSubData(SHAsset const& asset) noexcept + { + auto const& parent = assetCollection[asset.parent]; + auto parentData = loaders[static_cast(parent.type)]->Load(parent.path); + + if (parentData == nullptr) + { + SHLOG_ERROR("Unable to load asset into memory: {}\n", parent.path.string()); + } + else + { + assetData.emplace(parent.id, parentData); + if (parent.type == AssetType::MODEL) + { + auto parentModel = reinterpret_cast(parentData); + for (auto i {0}; i < parent.subAssets.size(); ++i) + { + assetData.emplace( + parent.subAssets[i]->id, + parentModel->subMeshes[i] + ); + } + } + + return assetData[asset.id]; + } + + return parentData; + } + + void SHAssetManager::LoadNewData(AssetPath path) noexcept + { + } + + std::optional SHAssetManager::GenerateNewMeta(AssetPath path) noexcept + { + auto const ext = path.extension().string(); + if (ext == SHADER_BUILT_IN_EXTENSION.data()) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::SHADER_BUILT_IN), + AssetType::SHADER_BUILT_IN, + path, + false + }; + + assetCollection.emplace(newAsset.id, newAsset); + SHAssetMetaHandler::WriteMetaData(newAsset); + } + else if (ext == TEXTURE_EXTENSION.data()) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::TEXTURE), + AssetType::SHADER_BUILT_IN, + path, + false + }; + + assetCollection.emplace(newAsset.id, newAsset); + SHAssetMetaHandler::WriteMetaData(newAsset); + } + else if (ext == MODEL_EXTENSION) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::MODEL), + AssetType::MODEL, + path, + false + }; + + assetCollection.emplace(newAsset.id, newAsset); + + SHModelAsset* const data = reinterpret_cast(LoadData(newAsset)); + assetData.emplace(newAsset.id, data); + for(auto const& subMesh : data->subMeshes) + { + SHAsset subAsset{ + .name = subMesh->header.name, + .id = GenerateAssetID(AssetType::MESH), + .type = AssetType::MESH, + .isSubAsset = true, + .parent = newAsset.id + }; + + assetCollection.emplace(subAsset.id, subAsset); + assetCollection[newAsset.id].subAssets.push_back(&assetCollection[subAsset.id]); + + assetData.emplace(subAsset.id, subMesh); + } + + SHAssetMetaHandler::WriteMetaData(assetCollection[newAsset.id]); + } + else if (ext == SCRIPT_EXTENSION) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::SCRIPT), + AssetType::SCRIPT, + path, + false + }; + assetCollection.emplace(newAsset.id, newAsset); + SHAssetMetaHandler::WriteMetaData(newAsset); + + return newAsset.id; + } + else if (ext == SCENE_EXTENSION) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::SCENE), + AssetType::SCENE, + path, + false + }; + assetCollection.emplace(newAsset.id, newAsset); + SHAssetMetaHandler::WriteMetaData(newAsset); + + return newAsset.id; + } + else if (ext == FONT_EXTENSION) + { + SHAsset newAsset{ + path.stem().string(), + GenerateAssetID(AssetType::FONT), + AssetType::FONT, + path, + false + }; + assetCollection.emplace(newAsset.id, newAsset); + SHAssetMetaHandler::WriteMetaData(newAsset); + + return newAsset.id; + } + } + + void SHAssetManager::BuildAssetCollection() noexcept + { + std::vector toGenNew; + SHFileSystem::BuildDirectory(ASSET_ROOT.data(), folderRoot, assetCollection, toGenNew); + + for (auto& file : toGenNew) + { + auto newID{ GenerateNewMeta(file->path) }; + if (newID.has_value()) + { + file->assetMeta = &assetCollection[newID.value()]; + } + } + + for (auto& asset : std::ranges::views::values(assetCollection)) + { + if (!asset.subAssets.empty()) + { + // Add subasset data into map, replace pointer and free heap memory + for (auto i{ 0 }; i < asset.subAssets.size(); ++i) + { + auto const id = asset.subAssets[i]->id; + + if (assetCollection.contains(id)) + { + continue; + } + + assetCollection[id] = *asset.subAssets[i]; + delete asset.subAssets[i]; + asset.subAssets[i] = &assetCollection[id]; + } + } + } + } +} diff --git a/SHADE_Engine/src/Assets/SHAssetManager.h b/SHADE_Engine/src/Assets/SHAssetManager.h new file mode 100644 index 00000000..f6ecb3a3 --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetManager.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * \file SHAssetManager.h + * \author Loh Xiao Qi + * \brief Interface for resource manager, to be used by engine side + * operations. + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. +******************************************************************************/ +#pragma once +#include "tinyddsloader.h" + +#include "SHAsset.h" +#include "Asset Types/SHAssetData.h" +#include "Assets/Libraries/Loaders/SHAssetLoader.h" + +#include "Filesystem/SHFolder.h" + +#include "SH_API.h" + +namespace SHADE +{ + class SH_API SHAssetManager + { + public: + /**************************************************************************** + * \brief Static function to generate resource ID. + ****************************************************************************/ + static AssetID GenerateAssetID(AssetType type) noexcept; + static AssetPath GenerateLocalPath(AssetPath path) noexcept; + static AssetPath GenerateNewPath(AssetName name, AssetType type); + + /**************************************************************************** + * \brief Deallocate all memory used by resource data + ****************************************************************************/ + static void Exit() noexcept; + + + static void Unload(AssetID assetId) noexcept; + + /**************************************************************************** + * \brief Load all resources that are in the folder + ****************************************************************************/ + static void Load() noexcept; + + /**************************************************************************** + * \brief Get record of all resources currently loaded with name and id. + * + * \return const& to unordered_map + ****************************************************************************/ + static std::vector GetAllAssets() noexcept; + + /**************************************************************************** + * \brief Create record for new resource. CAN ONLY CREATE FOR CUSTOM + * RESOURCES CREATED BY THE ENGINE. + * + * \param type of resource + * \param name of resource + * \return resource id generated for new asset + ****************************************************************************/ + static AssetID CreateNewAsset(AssetType type, AssetName name) noexcept; + static bool SaveAsset(AssetID id) noexcept; + static bool DeleteAsset(AssetID id) noexcept; + + /**************************************************************************** + * \brief Import new resource from outside editor window. + * + * \param path - c style string to full path + * \return resource if generated for new + ****************************************************************************/ + static AssetID ImportNewAsset(char const* path) noexcept; + + /**************************************************************************** + * \brief Search through resources folder for new unregistered assets. + * Takes in no params and returns nothing. Only updates internally. + ****************************************************************************/ + static void RefreshAllAssets() noexcept; + // -------------------------------------------------------------------------/ + + template + static std::enable_if_t, T* const> GetData(AssetID id) noexcept; + + template + static std::enable_if_t, T const* const> GetConstData(AssetID id) noexcept; + + static std::vector GetAllDataOfType(AssetType type) noexcept; + static std::vector GetAllRecordOfType(AssetType type) noexcept; + + static void CompileAsset(AssetPath const& path, bool genMeta) noexcept; + + static FolderPointer GetRootFolder() noexcept; + static void RefreshDirectory() noexcept; + + private: + + static void InitLoaders() noexcept; + static void LoadAllData() noexcept; + static SHAssetData* LoadData(SHAsset const& asset) noexcept; + static SHAssetData* LoadSubData(SHAsset const& asset) noexcept; + static void LoadNewData(AssetPath path) noexcept; + static std::optional GenerateNewMeta(AssetPath path) noexcept; + + inline static void BuildAssetCollection() noexcept; + + static SHAsset CreateAssetFromPath(AssetPath path) noexcept; + + static bool DeleteLocalFile(AssetPath path) noexcept; + + //TODO use this function to create asset data internall at all calls to generate id + //static AssetID CreateAsset(AssetName name, AssetType type) noexcept; + + static FolderPointer folderRoot; + + static FMOD::System* audioSystem; + static std::unordered_map* audioSoundList; + + static std::vector loaders; + + // For all resources + static std::unordered_map assetCollection; + static std::unordered_map assetData; + }; +} + +#include "SHAssetManager.hpp" diff --git a/SHADE_Engine/src/Assets/SHAssetManager.hpp b/SHADE_Engine/src/Assets/SHAssetManager.hpp new file mode 100644 index 00000000..4f372938 --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetManager.hpp @@ -0,0 +1,47 @@ + +#include "SHAssetManager.h" + +namespace SHADE +{ + template + std::enable_if_t, T* const> SHAssetManager::GetData(AssetID id) noexcept + { + if (!assetData.contains(id)) + { + for (auto const& asset : std::ranges::views::values(assetCollection)) + { + if (asset.id == id) + { + assetData.emplace(id, LoadData(asset)); + return dynamic_cast(assetData[id]); + } + } + + SHLOG_ERROR("Asset ID provided does not exist: {}", id); + return nullptr; + } + + return dynamic_cast(assetData[id]); + } + + template + std::enable_if_t, T const * const> SHAssetManager::GetConstData(AssetID id) noexcept + { + if (!assetData.contains(id)) + { + for (auto const& asset : std::ranges::views::values(assetCollection)) + { + if (asset.id == id) + { + assetData.emplace(id, LoadData(asset)); + return dynamic_cast(assetData[id]); + } + } + + SHLOG_ERROR("Asset ID provided does not exist: {}", id); + return nullptr; + } + + return dynamic_cast(assetData[id]); + } +} diff --git a/SHADE_Engine/src/Assets/SHAssetMetaHandler.cpp b/SHADE_Engine/src/Assets/SHAssetMetaHandler.cpp new file mode 100644 index 00000000..b5c78514 --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetMetaHandler.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * \file SHAssetMetaHandler.cpp + * \author Loh Xiao Qi + * \brief Implementations for SHAssetMetaHandler.h + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited + ******************************************************************************/ +#include "SHpch.h" +#include "SHAssetMetaHandler.h" +#include + +namespace SHADE +{ + /**************************************************************************** + * \param reference to ifstream file to read line from + * \param reference to string to store line into + + * \brief Helper function to retrieve field value from meta data file + * for processing + ****************************************************************************/ + bool GetFieldValue(std::ifstream& file, std::string& line) noexcept + { + line = ""; + if (std::getline(file, line)) + { + line = line.substr(line.find_last_of(':') + 2, line.length()); + return true; + } + return false; + } + + /**************************************************************************** + * \param String containing extension of resource file + + * \brief Get correct resource type from file extension of resource. + ****************************************************************************/ + AssetType SHAssetMetaHandler::GetTypeFromExtension(AssetExtension ext) noexcept + { + for (auto i{0}; i < EXTENSIONS_COUNT; ++i) + { + if (strcmp(ext.c_str(), EXTENSIONS[i].data()) == 0) + { + return static_cast(i); + } + } + + return AssetType::INVALID; + } + + /**************************************************************************** + * \param String containing extension of resource file + + * \brief Get correct resource type from file extension of resource. + ****************************************************************************/ + AssetExtension SHAssetMetaHandler::GetExtensionFromType(AssetType type) noexcept + { + return AssetExtension(EXTENSIONS[static_cast(type)]); + } + + /**************************************************************************** + * \param Create class containing meta data from meta file + + * \brief path to meta data file + ****************************************************************************/ + SHAsset SHAssetMetaHandler::RetrieveMetaData(AssetPath const& path) noexcept + { + std::ifstream metaFile{ path.string(), std::ios_base::in }; + if (!metaFile.is_open()) + { + SHLOG_ERROR("Unable to open meta file: {}", path.string()); + return {}; + } + + std::string line; + SHAsset meta; + + // Get resource name + GetFieldValue(metaFile, line); + std::stringstream nameStream{ line }; + AssetName name; + nameStream >> name; + meta.name = name; + + // Get resource id + GetFieldValue(metaFile, line); + std::stringstream idStream{ line }; + AssetID id; + idStream >> id; + meta.id = id; + + // Get resource type + GetFieldValue(metaFile, line); + std::stringstream typeStream{ line }; + AssetTypeMeta type; + typeStream >> type; + meta.type = static_cast(type); + + meta.isSubAsset = false; + meta.parent = 0; + + // Burn Line + if (std::getline(metaFile, line)) + { + // Name Line + while(GetFieldValue(metaFile, line)) + { + auto subAsset = new SHAsset(); + + // Get resource name + std::stringstream nameStream{ line }; + AssetName name; + nameStream >> name; + subAsset->name = name; + + // Get resource id + GetFieldValue(metaFile, line); + std::stringstream idStream{ line }; + AssetID id; + idStream >> id; + subAsset->id = id; + + // Get resource type + GetFieldValue(metaFile, line); + std::stringstream typeStream{ line }; + AssetTypeMeta type; + typeStream >> type; + subAsset->type = static_cast(type); + + subAsset->isSubAsset = true; + subAsset->parent = meta.id; + + meta.subAssets.push_back(subAsset); + } + } + + metaFile.close(); + + meta.path = path.parent_path().string() + "/" + path.stem().string(); + meta.path.make_preferred(); + + return meta; + } + + /**************************************************************************** + * \param Asset meta data to be written into + * \param Path to be written into + + * \brief Writes meta data into text file + ****************************************************************************/ + void SHAssetMetaHandler::WriteMetaData(SHAsset const& meta) noexcept + { + if (meta.isSubAsset) + { + SHLOG_WARNING("Cannot write subasset meta: {}", meta.name); + return; + } + + //TODO: Write into binary eventually + std::string path{ meta.path.string() }; + path.append(META_EXTENSION); + + std::ofstream metaFile{ path, std::ios_base::out | std::ios_base::trunc }; + + if (!metaFile.is_open()) + { + SHLOG_ERROR("Asset write path is invalid: {}", path); + return; + } + + metaFile << "Name: " << meta.name << "\n"; + metaFile << "ID: " << meta.id << "\n"; + metaFile << "Type: " << static_cast(meta.type) << std::endl; + + if (!meta.subAssets.empty()) + { + metaFile << "Sub Assets:\n"; + + for (auto const& subAsset : meta.subAssets) + { + WriteSubAssetMeta(metaFile, *subAsset); + } + } + + metaFile.close(); + } + + void SHAssetMetaHandler::WriteSubAssetMeta(std::ofstream& metaFile, SHAsset const& subAsset) noexcept + { + metaFile << "Name: " << subAsset.name << "\n"; + metaFile << "ID: " << subAsset.id << "\n"; + metaFile << "Type: " << static_cast(subAsset.type) << std::endl; + } +} diff --git a/SHADE_Engine/src/Assets/SHAssetMetaHandler.h b/SHADE_Engine/src/Assets/SHAssetMetaHandler.h new file mode 100644 index 00000000..ffa965aa --- /dev/null +++ b/SHADE_Engine/src/Assets/SHAssetMetaHandler.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * \file SHAssetMetaHandler.h + * \author Loh Xiao Qi + * \brief Handler classes for meta data for all resources + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited + ******************************************************************************/ +#ifndef SH_RESOURCE_META_HANDLER_H +#define SH_RESOURCE_META_HANDLER_H + +#include "SHAssetMacros.h" +#include "SHAsset.h" +#include + +namespace SHADE +{ + struct SHAssetMetaHandler + { + /**************************************************************************** + * \param String containing extension of resource file + + * \brief Get correct resource type from file extension of resource. + ****************************************************************************/ + static AssetType GetTypeFromExtension(AssetExtension) noexcept; + + /**************************************************************************** + * \param String containing extension of resource file + + * \brief Get correct resource type from file extension of resource. + ****************************************************************************/ + static AssetExtension GetExtensionFromType(AssetType) noexcept; + + /**************************************************************************** + * \param Create class containing meta data from meta file + + * \brief path to meta data file + ****************************************************************************/ + static SHAsset RetrieveMetaData(AssetPath const&) noexcept; + + /**************************************************************************** + * \param Asset meta data to be written into + * \param Path to be written into + + * \brief Writes meta data into text file + ****************************************************************************/ + static void WriteMetaData(SHAsset const&) noexcept; + + static void WriteSubAssetMeta(std::ofstream&, SHAsset const&) noexcept; + }; +} + +#endif // !SH_RESOURCE_META_HANDLER_H diff --git a/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.cpp b/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.cpp new file mode 100644 index 00000000..988b2781 --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.cpp @@ -0,0 +1,58 @@ +/********************************************************************* +* \file SHAudioListenerComponent.cpp +* \author Glence Low +* \brief Definition of the SHAudioListenerComponent class. +* +* \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. +*********************************************************************/ + +#include "SHpch.h" +#include "SHAudioListenerComponent.h" +#include "ECS_Base/Managers/SHSystemManager.h" + +namespace SHADE +{ + const SHVec3 SHAudioListenerComponent::GetPos() const + { + return pos; + } + + const SHVec3 SHAudioListenerComponent::GetVel() const + { + return vel; + } + + const SHVec3 SHAudioListenerComponent::GetForward() const + { + return forward; + } + + const SHVec3 SHAudioListenerComponent::GetUp() const + { + return up; + } + + void SHAudioListenerComponent::SetPos(const SHVec3 p) + { + pos = p; + } + + void SHAudioListenerComponent::SetVel(const SHVec3 v) + { + vel = v; + } + + void SHAudioListenerComponent::SetForward(const SHVec3 f) + { + forward = f; + } + + void SHAudioListenerComponent::SetUp(const SHVec3 u) + { + up = u; + } + +} + diff --git a/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.h b/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.h new file mode 100644 index 00000000..e0fa8104 --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioListenerComponent.h @@ -0,0 +1,41 @@ +#pragma once +/********************************************************************* + * \file SHAudioListenerComponent.h + * \author Glence Low + * \brief Declaration of the SHAudioListenerComponent class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. +*********************************************************************/ + +#pragma once +#include "ECS_Base/Components/SHComponent.h" +#include "Math/SHMath.h" + +namespace SHADE +{ + class SHAudioListenerComponent : public SHComponent + { + friend class SHAudioSystem; + public: + + SHAudioListenerComponent() = default; + ~SHAudioListenerComponent() = default; + + const SHVec3 GetPos() const; + void SetPos(const SHVec3 p); + + const SHVec3 GetVel() const; + const SHVec3 GetForward() const; + const SHVec3 GetUp() const; + + void SetVel(const SHVec3 v); + void SetForward(const SHVec3 f); + void SetUp(const SHVec3 u); + + private: + SHVec3 pos{}, vel{}, forward{}, up{ 0.f,1.f,0.f }; + }; +}//namespace SHADE + diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.cpp b/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.cpp new file mode 100644 index 00000000..2d623ede --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.cpp @@ -0,0 +1,53 @@ +/********************************************************************* +* \file SHAudioSourceComponet.cpp +* \author Glence Low +* \brief Definition of the SHAudioSourceComponet class. +* +* \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. +*********************************************************************/ + +#include "SHpch.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "AudioSystem/SHAudioSystem.h" +#include "SHAudioSourceComponent.h" + +namespace SHADE +{ + /** + * @brief play the sound + * + * @param index where the sound is in the index + */ + void SHAudioSourceComponent::PlaySoundSFX(EntityID id, bool loop, bool spatial, float min , float max ) + { + SHSystemManager::GetSystem()->PlaySFX(id, GetEID(),loop,spatial, min, max); + } + + void SHAudioSourceComponent::PlaySoundBGM(EntityID id, bool loop, bool spatial, float min, float max) + { + SHSystemManager::GetSystem()->PlayBGM(id, GetEID(), loop, spatial, min, max); + } + + /** + * @brief Stop the sound + * + * @param index where the sound is in the index + */ + void SHAudioSourceComponent::StopSound(EntityID id) + { + SHSystemManager::GetSystem()->StopSound(id); + } + + /** + * @brief Mute the sound + * + * @param index where the sound is in the index + */ + void SHAudioSourceComponent::SetMute(EntityID id, bool mute) + { + SHSystemManager::GetSystem()->SetMute(id, mute); + } +} + diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.h b/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.h new file mode 100644 index 00000000..987dc3d4 --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioSourceComponent.h @@ -0,0 +1,60 @@ +/********************************************************************* + * \file SHAudioSourceComponet.h + * \author Glence Low + * \brief Declaration of the SHAudioSourceComponet class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. +*********************************************************************/ + +#pragma once +#include "ECS_Base/Components/SHComponent.h" +#include "ECS_Base/SHECSMacros.h" + +namespace SHADE +{ + class SHAudioSourceComponent : public SHComponent + { + public: + /** + * @brief default constructor for the component + * + */ + SHAudioSourceComponent() = default; + + + /** + * @brief default destructor for the component + * + */ + ~SHAudioSourceComponent() = default; + + /** + * @brief play the sound + * + * @param index where the sound is in the index + */ + void PlaySoundSFX(EntityID id, bool loop = false, bool spatial = false, float min = 5.0f, float max = 1000.f); + + void PlaySoundBGM(EntityID id, bool loop = false, bool spatial = false, float min = 5.0f, float max = 1000.f); + + /** + * @brief Stop the sound + * + * @param index where the sound is in the index + */ + void StopSound(EntityID id); + + /** + * @brief Mute the sound + * + * @param index where the sound is in the index + */ + void SetMute(EntityID id, bool mute); + + private: + + }; +}//namespace SHADE + diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp b/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp new file mode 100644 index 00000000..c913030b --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp @@ -0,0 +1,559 @@ +#include "SHpch.h" +#include "SHAudioSystem.h" +#include "Scene/SHSceneManager.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "ECS_Base/Managers/SHEntityManager.h" + +#include + +#include "AudioSystem/SHAudioListenerComponent.h" +#include "AudioSystem/SHAudioSourceComponent.h" +#include "Math/Transform/SHTransformComponent.h" +#pragma warning(push) +#pragma warning(disable:26812) //disable warning about preference of enum class over enum as ImGuizmo uses enums +#include +#include +#include +#include + +const std::string AUDIO_FOLDER_PATH{ std::string(ASSET_ROOT)+ "/Audio/" }; + +namespace SHADE +{ + SHAudioSystem::SHAudioSystem() + : fmodStudioSystem(nullptr) + , extraDriverData(nullptr) + , soundList() + , bgmChannelGroup(nullptr) + , sfxChannelGroup(nullptr) + , masterGroup(nullptr) + , audioChannels() + , result(FMOD_RESULT_FORCEINT) + , bgmVolume(1.F) + , sfxVolume(1.F) + , masterVolume(1.0F) + , version(0) + , speakerMode(FMOD_SPEAKERMODE_5POINT1) + , paused(false) + { + result = FMOD::Studio::System::create(&fmodStudioSystem); + ErrorCheck(); + } + + SHAudioSystem::~SHAudioSystem() + { + } + + void SHADE::SHAudioSystem::Init() + { + + SHADE::SHComponentManager::CreateComponentSparseSet(); + SHADE::SHComponentManager::CreateComponentSparseSet(); + + denseListener = &SHComponentManager::GetDense(); + fmodStudioSystem->getCoreSystem(&fmodSystem); + + result = fmodStudioSystem->initialize(AUDIO_SYS_MAX_CHANNELS, AUDIO_SYS_MAX_CHANNELS, FMOD_STUDIO_INIT_NORMAL, extraDriverData); + ErrorCheck(); + + fmodSystem->setSoftwareFormat(0, speakerMode, 0); + + result = fmodSystem->createChannelGroup("SFX", &sfxChannelGroup); + ErrorCheck(); + + result = fmodSystem->createChannelGroup("BGM", &bgmChannelGroup); + ErrorCheck(); + + result = fmodSystem->getMasterChannelGroup(&masterGroup); + ErrorCheck(); + + result = masterGroup->addGroup(bgmChannelGroup); + ErrorCheck(); + + result = masterGroup->addGroup(sfxChannelGroup); + ErrorCheck(); + + bgmChannelGroup->setVolume(bgmVolume); + sfxChannelGroup->setVolume(sfxVolume); + masterGroup->setVolume(masterVolume); + + //SHResourceManager::LoadAllAudio(system, soundList); + + + LoadBank((AUDIO_FOLDER_PATH + "Master.bank").data()); + LoadBank((AUDIO_FOLDER_PATH + "Master.strings.bank").data()); + //LoadBank((AUDIO_FOLDER_PATH + "Music.bank").data()); + LoadBank((AUDIO_FOLDER_PATH + "footsteps.bank").data()); + + //auto clip = CreateAudioClip("event:/Characters/sfx_footsteps_human"); + //clip->Play(); + //PlayEventOnce("event:/Characters/sfx_footsteps_raccoon"); + //PlayEventOnce("event:/SFX/Dawn/Dawn_Attack"); + } + + void SHADE::SHAudioSystem::Run(double dt) + { + static_cast(dt); + //if (GetKeyState(VK_SPACE) & 0x8000) + // PlayEventOnce("event:/Characters/sfx_footsteps_raccoon"); + + fmodStudioSystem->update(); + if (!denseListener->empty()) + { + SHAudioListenerComponent& listener = denseListener->at(0); + SHTransformComponent* listenerTransform = SHComponentManager::GetComponent_s(listener.GetEID()); + if (listenerTransform) + { + listener.SetPos(listenerTransform->GetLocalPosition()); + listener.SetForward({ (listenerTransform->GetLocalScale()[0] > 0.f) ? 1.f : -1.f, 0.f, 0.f }); + FMOD_VECTOR pos = { listener.pos[0] ,listener.pos[1] ,0.f }; + FMOD_VECTOR forward = { listener.forward[0] ,listener.forward[1] ,listener.forward[2] }; + FMOD_VECTOR up = { listener.up[0] ,listener.up[1] ,listener.up[2] }; + fmodSystem->set3DListenerAttributes(0, &pos, nullptr, &forward, &up); + } + } + } + + SHAudioSystem::AudioRoutine::AudioRoutine() + : SHSystemRoutine("Audio Routine", false) {} + + void SHAudioSystem::AudioRoutine::Execute(double dt) noexcept + { + reinterpret_cast(system)->Run(dt); + } + + void SHADE::SHAudioSystem::Exit() + { + for (auto& event : eventMap) + { + result = event.second->releaseAllInstances(); + ErrorCheck(); + } + + for (auto& bank : bankMap) + { + result = bank.second->unload(); + ErrorCheck(); + } + + for (auto& sound : soundList) + { + result = sound.second->release(); + ErrorCheck(); + } + + if (bgmChannelGroup) + { + result = bgmChannelGroup->release(); + ErrorCheck(); + } + + if (sfxChannelGroup) + { + result = sfxChannelGroup->release(); + ErrorCheck(); + } + + if (fmodStudioSystem) + { + result = fmodStudioSystem->release(); + ErrorCheck(); + } + } + + void SHAudioSystem::ErrorCheck() const + { + if (result != FMOD_OK) + std::cerr << "Audio system error: " << FMOD_ErrorString(result) << std::endl; + } + + void SHAudioSystem::PlayEventOnce(const char* path, bool isSFX, EntityID eid, bool spatial) + { + if (paused) + return; + auto it = eventMap.find(path); + if (it != eventMap.end()) + { + FMOD::Studio::EventInstance* event = nullptr; + it->second->createInstance(&event); + if (event) + { + + event->setVolume(masterVolume * (isSFX ? sfxVolume : bgmVolume)); + if (spatial) + { + if (SHTransformComponent* audioTransform = SHComponentManager::GetComponent_s(eid)) + { + FMOD_3D_ATTRIBUTES attributes{ {} }; + attributes.forward.z = 1.0f; + attributes.up.y = 1.0f; + SHAudioListenerComponent& listener = denseListener->at(0); + SHTransformComponent* listenerTransform = SHComponentManager::GetComponent_s(listener.GetEID()); + if (listenerTransform) + { + attributes.position.z = listenerTransform->GetLocalPosition()[2]; + } + attributes.position.x = audioTransform->GetLocalPosition()[0]; + attributes.position.y = audioTransform->GetLocalPosition()[1]; + event->set3DAttributes(&attributes); + } + } + event->start(); + event->release(); + } + } + } + + void SHAudioSystem::PlaySFX(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min, float max) + { + SHSound sound = soundList[id]; + int index = GetAvailableChannelIndex(); + if (index >= 0) + { + unsigned int mode{}; + mode |= (loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); + mode |= (spatial ? FMOD_3D : FMOD_2D); + sound->setMode(mode); + result = fmodSystem->playSound(sound, sfxChannelGroup, false, &audioChannels[index]); + if (spatial && SHComponentManager::HasComponent(eid)) + { + SHTransformComponent* audioTransform = SHComponentManager::GetComponent_s(eid); + FMOD_VECTOR fpos{ audioTransform->GetLocalPosition()[0],audioTransform->GetLocalPosition()[1] ,0.f}; + audioChannels[index]->set3DAttributes(&fpos, nullptr); + audioChannels[index]->setMode(mode); + audioChannels[index]->set3DMinMaxDistance(min, max); + } + ErrorCheck(); + } + } + + void SHAudioSystem::PlayBGM(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min, float max) + { + SHSound sound = soundList[id]; + int index = GetAvailableChannelIndex(); + if (index >= 0) + { + unsigned int mode{}; + mode |= (loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); + mode |= (spatial ? FMOD_3D : FMOD_2D); + sound->setMode(mode); + result = fmodSystem->playSound(sound, bgmChannelGroup, false, &audioChannels[index]); + if (spatial && SHComponentManager::HasComponent(eid)) + { + SHTransformComponent* audioTransform = SHComponentManager::GetComponent_s(eid); + FMOD_VECTOR fpos{ audioTransform->GetLocalPosition()[0],audioTransform->GetLocalPosition()[1] ,0.f }; + audioChannels[index]->set3DAttributes(&fpos, nullptr); + audioChannels[index]->setMode(mode); + audioChannels[index]->set3DMinMaxDistance(min, max); + } + ErrorCheck(); + } + } + + void SHAudioSystem::SetMute(EntityID id, bool mute) + { + SHSound sound; + for (auto& channel : audioChannels) + { + channel->getCurrentSound(&sound); + if (soundList.find(id)->second == sound) // tbc + { + channel->setMute(mute); + } + } + } + + void SHAudioSystem::StopSound(EntityID id) + { + SHSound sound; + for (auto& channel : audioChannels) + { + channel->getCurrentSound(&sound); + if (soundList.find(id)->second == sound) // tbc + { + channel->stop(); + } + } + } + + void SHAudioSystem::StopAllSounds() + { + for (auto& channel : audioChannels) + { + bool isPlaying{ false }; + if (channel->isPlaying(&isPlaying) == FMOD_OK && isPlaying) + channel->stop(); + } + } + + std::optional SHAudioSystem::GetEventGUID(const char* path) + { + FMOD_GUID guid; + FMOD::Studio::EventDescription* event; + result = fmodStudioSystem->getEvent(path, &event); + ErrorCheck(); + if (result == FMOD_OK) + { + result = event->getID(&guid); + ErrorCheck(); + if (result == FMOD_OK) + { + return guid; + } + } + return std::nullopt; + } + + AudioClip* SHAudioSystem::CreateAudioClip(const char* path) + { + AudioClipID newID{}; + AudioClip* clip = nullptr; + auto it = eventMap.find(path); + if (it != eventMap.end()) + { + FMOD::Studio::EventInstance* event = nullptr; + it->second->createInstance(&event); + if (event) + { + //event->start(); + newID = clipID; + clipID++; + eventInstances.emplace(newID, AudioClip(newID, event)); + clip = &eventInstances[newID]; + } + } + return clip; + } + + //std::vector SHAudioSystem::GetAllEvents() + //{ + // int count{}; + // stringsBank->getEventCount(&count); + + // std::vector events(count); + // auto eventData = events.data(); + // int finalCount{}; + // stringsBank->getEventList(eventData, count, &finalCount); + // std::vector eventNames; + // std::transform(events.begin(), events.end(), std::back_inserter(eventNames), [](FMOD::Studio::EventDescription* event) + // { + // char path[256]; + // event->getPath(path, 256, nullptr); + // return path; + // }); + // return eventNames; + //} + + //void SHAudioSystem::PlayEventInstance(FMOD::Studio::EventInstance* instance, bool isSFX, EntityID eid, bool spatial) + //{ + // instance->setVolume(masterVolume * (isSFX ? sfxVolume : bgmVolume)); + // FMOD::ChannelGroup* channelGroup; + // if (spatial) + // { + // if (SHTransformComponent* audioTransform = SHComponentManager::GetComponent_s(eid)) + // { + // FMOD_3D_ATTRIBUTES attributes{ {} }; + // attributes.forward.z = 1.0f; + // attributes.up.y = 1.0f; + // SHAudioListenerComponent& listener = denseListener->at(0); + // SHTransformComponent* listenerTransform = SHComponentManager::GetComponent_s(listener.GetEID()); + // if (listenerTransform) + // { + // attributes.position.z = listenerTransform->GetTranslation()[2]; + // } + // attributes.position.x = audioTransform->GetTranslation()[0]; + // attributes.position.y = audioTransform->GetTranslation()[1]; + // instance->set3DAttributes(&attributes); + // } + // } + // instance->start(); + //} + + int SHAudioSystem::GetAvailableChannelIndex() + { + bool isPlaying = false; + for (int i = 0; i < AUDIO_SYS_MAX_CHANNELS; ++i) + { + audioChannels[i]->isPlaying(&isPlaying); + if (!isPlaying) + return i; + } + return -1; + } + + float SHAudioSystem::GetBgmVolume() + { + return bgmVolume; + } + float SHAudioSystem::GetSfxVolume() + { + return sfxVolume; + } + float SHAudioSystem::GetMasterVolume() + { + return masterVolume; + } + void SHAudioSystem::SetBgmVolume(float const bgmvol) + { + bgmChannelGroup->setVolume(bgmvol); + } + void SHAudioSystem::SetSfxVolume(float const sfxvol) + { + sfxChannelGroup->setVolume(sfxvol); + } + void SHAudioSystem::SetMasterVolume(float const mastervol) + { + masterGroup->setVolume(mastervol); + } + + void SHAudioSystem::SetPaused(bool pause) + { + paused = pause; + for (auto const& channel : audioChannels) + { + channel->setPaused(paused); + } + for (auto const& event : eventMap) + { + int instanceCount = 0; + event.second->getInstanceCount(&instanceCount); + std::vector instances(instanceCount); + event.second->getInstanceList(instances.data(), static_cast(instances.size()), &instanceCount); + for (auto const& instance : instances) + { + instance->setPaused(pause); + } + } + } + + bool SHAudioSystem::GetPaused() const + { + return paused; + } + + SHVec3 SHAudioSystem::GetListenerPosition() + { + auto &listener = denseListener->at(0); + SHTransformComponent* listenerTransform = SHComponentManager::GetComponent_s(listener.GetEID()); + if (listenerTransform) + { + return listenerTransform->GetLocalPosition(); + } + return {}; + } + + void SHAudioSystem::LoadBank(const char* path) + { + FMOD::Studio::Bank* bank = nullptr; + result = fmodStudioSystem->loadBankFile(path, FMOD_STUDIO_LOAD_BANK_NORMAL, &bank); + ErrorCheck(); + if (result != FMOD_OK) + return; + bankMap.emplace(path, bank); + bank->loadSampleData(); + int numOfEvents; + bank->getEventCount(&numOfEvents); + if (numOfEvents > 0) + { + std::vector events(numOfEvents); + bank->getEventList(events.data(), numOfEvents, &numOfEvents); + char eventName[512]; + for (int i{}; i < numOfEvents; ++i) + { + FMOD::Studio::EventDescription* event = events[i]; + event->getPath(eventName, 512, nullptr); + eventMap.emplace(eventName, event); + } + } + } + + AudioClip::AudioClip(AudioClipID clipID, FMOD::Studio::EventInstance* inst) + :instance(inst), id(clipID) + { + } + + AudioClip::~AudioClip() + { + } + + void AudioClip::Play(bool isSfx) + { + if (!instance) + return; + instance->start(); + auto audioSystem = SHSystemManager::GetSystem(); + instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); + } + + void AudioClip::Play(SHVec3 position, bool isSfx) + { + if (!instance) + return; + instance->start(); + FMOD_3D_ATTRIBUTES attributes{ {} }; + attributes.forward.z = 1.0f; + attributes.up.y = 1.0f; + + auto audioSystem = SHSystemManager::GetSystem(); + SHVec3 listenerPos = audioSystem->GetListenerPosition(); + attributes.position.x = position[0]; + attributes.position.y = position[1]; + attributes.position.z = listenerPos[2]; + instance->set3DAttributes(&attributes); + instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); + } + + void AudioClip::Stop(bool fadeOut) + { + if (!instance) + return; + instance->stop(fadeOut ? FMOD_STUDIO_STOP_ALLOWFADEOUT : FMOD_STUDIO_STOP_IMMEDIATE); + } + + void AudioClip::SetPause(bool pause) + { + if (!instance) + return; + instance->setPaused(pause); + } + + bool AudioClip::IsPaused() + { + if (!instance) + return true; + + bool paused{}; + instance->getPaused(&paused); + return paused; + } + + + void AudioClip::SetParameter(const char* paramName, float value) + { + if (!instance) + return; + instance->setParameterByName(paramName, value); + } + + void AudioClip::SetParameterLabel(const char* paramName, const char* label) + { + if (!instance) + return; + instance->setParameterByNameWithLabel(paramName, label); + } + + float AudioClip::GetParameterValue(const char* paramName) + { + if (!instance) + return {}; + float value{}; + instance->getParameterByName(paramName, &value); + return value; + } + +} + +#pragma warning(pop) + + diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSystem.h b/SHADE_Engine/src/AudioSystem/SHAudioSystem.h new file mode 100644 index 00000000..f19fcc3b --- /dev/null +++ b/SHADE_Engine/src/AudioSystem/SHAudioSystem.h @@ -0,0 +1,110 @@ +#pragma once +#include +#include +#include +#include +#include +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "ECS_Base/SHECSMacros.h" +#include "Math/SHMath.h" +#include +#include +#include "SH_API.h" +#define AUDIO_SYS_MAX_CHANNELS 1024 + +namespace SHADE +{ + typedef FMOD::Sound* SHSound; + typedef FMOD::Studio::Bank* SHBank; + + class SHAudioListenerComponent; + + typedef uint64_t AudioClipID; + + class AudioClip + { + public: + AudioClip() = default; + AudioClip(AudioClipID clipID, FMOD::Studio::EventInstance* inst); + ~AudioClip(); + void Play(bool isSfx = true); + void Play(SHVec3 position, bool isSfx = true); + void Stop(bool fadeOut = true); + + void SetPause(bool pause); + bool IsPaused(); + void SetParameter(const char* paramName, float value); + void SetParameterLabel(const char* paramName, const char* label); + float GetParameterValue(const char* paramName); + friend class SHAudioSystem; + private: + FMOD::Studio::EventInstance* instance; + AudioClipID id; + }; + + class SH_API SHAudioSystem : public SHSystem + { + public: + SHAudioSystem(); + ~SHAudioSystem(); + + void Init(); + void Run(double dt); + class SH_API AudioRoutine final : public SHSystemRoutine + { + public: + AudioRoutine(); + void Execute(double dt) noexcept override final; + }; + void Exit(); + + int GetAvailableChannelIndex(); + /*std::vector::size_type CreateSound(const char* filepath, bool loop = false);*/ + void PlaySFX(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min = 5.0f, float max = 1000.0f); + void PlayBGM(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min = 5.0f, float max = 1000.0f); + void PlayEventOnce(const char* path, bool isSFX = true, EntityID eid = MAX_EID, bool spatial = false); + void SetMute(EntityID id, bool); + void StopSound(EntityID id); + void StopAllSounds(); + + std::optional GetEventGUID(const char* path); + AudioClip* CreateAudioClip(const char* path); + //std::vector GetAllEvents(); + + float GetBgmVolume(); + float GetSfxVolume(); + float GetMasterVolume(); + void SetBgmVolume(float const bgmvol); + void SetSfxVolume(float const sfxvol); + void SetMasterVolume(float const mastervol); + void SetPaused(bool pause); + bool GetPaused() const; + SHVec3 GetListenerPosition(); + void LoadBank(const char* path); + private: + FMOD::Studio::System* fmodStudioSystem; + FMOD::System* fmodSystem; + bool paused; + void ErrorCheck() const; + + void* extraDriverData; + std::unordered_map soundList; + //std::unordered_map bankMap; + std::unordered_map bankMap; + std::unordered_map eventMap; + std::unordered_map eventInstances; + FMOD::ChannelGroup* bgmChannelGroup, * sfxChannelGroup, * masterGroup; + FMOD::Channel* audioChannels[AUDIO_SYS_MAX_CHANNELS]; + FMOD_RESULT result; + float bgmVolume, sfxVolume, masterVolume; + unsigned int version; + FMOD_SPEAKERMODE speakerMode; + SHBank masterBank, stringsBank, musicBank, sfxBank; //To do: change to map of banks loaded by resource manager + + std::vector* denseListener; + AudioClipID clipID = 0; + }; + +} + diff --git a/SHADE_Engine/src/Camera/SHCameraArmComponent.cpp b/SHADE_Engine/src/Camera/SHCameraArmComponent.cpp new file mode 100644 index 00000000..9cb221ff --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraArmComponent.cpp @@ -0,0 +1,68 @@ +#include "SHpch.h" +#include "SHCameraArmComponent.h" + + + +namespace SHADE +{ + + SHCameraArmComponent::SHCameraArmComponent() + :pitch(0.0f), yaw(0.0f), armLength(1.0f),offset(), dirty(true), lookAtCameraOrigin(true) + { + + } + + + SHVec3 const& SHCameraArmComponent::GetOffset() const noexcept + { + return offset; + } + + float SHCameraArmComponent::GetPitch() const noexcept + { + return pitch; + } + + float SHCameraArmComponent::GetYaw() const noexcept + { + return yaw; + } + + float SHCameraArmComponent::GetArmLength() const noexcept + { + return armLength; + } + + void SHCameraArmComponent::SetPitch(float pitch) noexcept + { + this->pitch = pitch; + dirty = true; + } + + void SHCameraArmComponent::SetYaw(float yaw) noexcept + { + this->yaw = yaw; + dirty = true; + } + + void SHCameraArmComponent::SetArmLength(float length) noexcept + { + this->armLength = length; + dirty = true; + } + +}//namespace SHADE + + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::class_("Camera Arm Component") + .property("Arm Pitch", &SHCameraArmComponent::GetPitch, &SHCameraArmComponent::SetPitch) + .property("Arm Yaw", &SHCameraArmComponent::GetYaw, &SHCameraArmComponent::SetYaw) + .property("Arm Length", &SHCameraArmComponent::GetArmLength, &SHCameraArmComponent::SetArmLength) + .property("Look At Camera Origin", &SHCameraArmComponent::lookAtCameraOrigin); + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Camera/SHCameraArmComponent.h b/SHADE_Engine/src/Camera/SHCameraArmComponent.h new file mode 100644 index 00000000..2b81a808 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraArmComponent.h @@ -0,0 +1,44 @@ +#pragma once + + +#include +#include "ECS_Base/Components/SHComponent.h" +#include "Math/SHMatrix.h" +#include "SH_API.h" + +namespace SHADE +{ + class SH_API SHCameraArmComponent final: public SHComponent + { + private: + float pitch; + float yaw; + float armLength; + + bool dirty; + SHVec3 offset; + + public: + friend class SHCameraSystem; + SHCameraArmComponent(); + virtual ~SHCameraArmComponent() = default; + + bool lookAtCameraOrigin; + //Getters + //SHMatrix const& GetMatrix() const noexcept; + SHVec3 const& GetOffset() const noexcept; + float GetPitch() const noexcept; + float GetYaw() const noexcept; + float GetArmLength() const noexcept; + + //Setters + void SetPitch(float pitch) noexcept; + void SetYaw(float yaw) noexcept; + void SetArmLength(float length) noexcept; + + protected: + + + }; + +}//namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Camera/SHCameraComponent.cpp b/SHADE_Engine/src/Camera/SHCameraComponent.cpp new file mode 100644 index 00000000..ac451df5 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraComponent.cpp @@ -0,0 +1,247 @@ +#include "SHpch.h" +#include "SHCameraComponent.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "SHCameraSystem.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Math/SHMath.h" + +namespace SHADE +{ + SHCameraComponent::SHCameraComponent() + :yaw(0.0f), pitch(0.0f), roll(0.0f) + , width(1920.0f), height(1080.0f), zNear(0.01f), zFar(10000.0f), fov(90.0f), movementSpeed(1.0f), turnSpeed(0.5f) + , perspProj(true), dirtyView(true), dirtyProj(true) + , viewMatrix(), projMatrix() + , position(), offset() + { + ComponentFamily::GetID(); + } + + SHCameraComponent::~SHCameraComponent() + { + } + + void SHCameraComponent::SetYaw(float yaw) noexcept + { + this->yaw = yaw; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 rotation = transform->GetWorldRotation(); + transform->SetWorldRotation(SHVec3{rotation.x,SHMath::DegreesToRadians(yaw), rotation.z}); + } + dirtyView = true; + } + + void SHCameraComponent::SetPitch(float pitch) noexcept + { + this->pitch = pitch; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 rotation = transform->GetWorldRotation(); + transform->SetWorldRotation(SHVec3{ SHMath::DegreesToRadians(pitch),rotation.y, rotation.z }); + } + dirtyView = true; + } + + void SHCameraComponent::SetRoll(float roll) noexcept + { + this->roll = roll; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 rotation = transform->GetWorldRotation(); + transform->SetWorldRotation(SHVec3{ rotation.x,rotation.y, SHMath::DegreesToRadians(roll)}); + } + dirtyView = true; + } + void SHCameraComponent::SetPositionX(float x) noexcept + { + position.x = x; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 position = transform->GetWorldPosition(); + transform->SetWorldRotation(SHVec3{ x,position.y, position.z}); + } + dirtyView = true; + } + void SHCameraComponent::SetPositionY(float y) noexcept + { + position.y = y; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 position = transform->GetWorldPosition(); + transform->SetWorldRotation(SHVec3{ position.x,y, position.z }); + } + dirtyView = true; + } + void SHCameraComponent::SetPositionZ(float z) noexcept + { + position.z = z; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 position = transform->GetWorldPosition(); + transform->SetWorldRotation(SHVec3{ position.x,position.y, z }); + } + dirtyView = true; + } + void SHCameraComponent::SetPosition(float x,float y, float z) noexcept + { + position.x = x; + position.y = y; + position.z = z; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 position = transform->GetWorldPosition(); + transform->SetWorldRotation(SHVec3{ x,y, z }); + } + dirtyView = true; + } + void SHCameraComponent::SetPosition(SHVec3 pos) noexcept + { + this->position = pos; + if (SHComponentManager::HasComponent(GetEID())) + { + auto transform = SHComponentManager::GetComponent(GetEID()); + SHVec3 position = transform->GetWorldPosition(); + transform->SetWorldRotation(pos); + } + dirtyView = true; + } + + void SHCameraComponent::SetWidth(float width) noexcept + { + this->width = width; + dirtyProj = true; + } + + + void SHCameraComponent::SetHeight(float height) noexcept + { + this->height = height; + dirtyProj = true; + } + + void SHCameraComponent::SetNear(float znear) noexcept + { + this->zNear = znear; + dirtyProj = true; + } + + void SHCameraComponent::SetFar(float zFar) noexcept + { + this->zFar = zFar; + dirtyProj = true; + } + + void SHCameraComponent::SetFOV(float fov) noexcept + { + this->fov = fov; + dirtyProj = true; + } + + void SHCameraComponent::SetIsPerspective(bool persp) noexcept + { + this->perspProj = persp; + dirtyProj = true; + } + + SHVec3 SHCameraComponent::GetPosition() const noexcept + { + return position; + } + + float SHCameraComponent::GetYaw() const noexcept + { + return yaw; + } + + float SHCameraComponent::GetPitch() const noexcept + { + return pitch; + } + float SHCameraComponent::GetRoll() const noexcept + { + return roll; + } + + float SHCameraComponent::GetWidth() const noexcept + { + return width; + } + + float SHCameraComponent::GetHeight() const noexcept + { + return height; + } + + float SHCameraComponent::GetNear() const noexcept + { + return zNear; + } + + float SHCameraComponent::GetFar() const noexcept + { + return zFar; + } + + float SHCameraComponent::GetAspectRatio() const noexcept + { + return width/height; + } + + float SHCameraComponent::GetFOV() const noexcept + { + return fov; + } + + bool SHCameraComponent::GetIsPerspective() const noexcept + { + return perspProj; + } + + const SHMatrix& SHCameraComponent::GetViewMatrix() const noexcept + { + return viewMatrix; + } + + const SHMatrix& SHCameraComponent::GetProjMatrix() const noexcept + { + return projMatrix; + } + + //void SHCameraComponent::SetMainCamera(size_t directorCameraIndex) noexcept + //{ + // auto system = SHSystemManager::GetSystem(); + // system->GetDirector(directorCameraIndex)->SetMainCamera(*this); + //} + + + +}//namespace SHADE + + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::class_("Camera Component") + .property("Position", &SHCameraComponent::GetPosition, select_overload(&SHCameraComponent::SetPosition)) + .property("Pitch", &SHCameraComponent::GetPitch, &SHCameraComponent::SetPitch) + .property("Yaw", &SHCameraComponent::GetYaw, &SHCameraComponent::SetYaw) + .property("Roll", &SHCameraComponent::GetRoll, &SHCameraComponent::SetRoll) + .property("Width", &SHCameraComponent::GetWidth, &SHCameraComponent::SetWidth) + .property("Height", &SHCameraComponent::GetHeight, &SHCameraComponent::SetHeight) + .property("Near", &SHCameraComponent::GetNear, &SHCameraComponent::SetNear) + .property("Far", &SHCameraComponent::GetFar, &SHCameraComponent::SetFar) + .property("Perspective", &SHCameraComponent::GetIsPerspective, &SHCameraComponent::SetIsPerspective); + + +} diff --git a/SHADE_Engine/src/Camera/SHCameraComponent.h b/SHADE_Engine/src/Camera/SHCameraComponent.h new file mode 100644 index 00000000..b778b8fa --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraComponent.h @@ -0,0 +1,96 @@ +#pragma once + +#include + +#include "ECS_Base/Components/SHComponent.h" +#include "Math/Vector/SHVec3.h" +#include "Math/SHMatrix.h" +#include "SH_API.h" + +namespace SHADE +{ + + class SH_API SHCameraComponent final : public SHComponent + { + private: + + float yaw; + float pitch; + float roll; + + float width; + float height; + float zNear; + float zFar; + float fov; + + bool dirtyView; + bool dirtyProj; + + + SHMatrix viewMatrix; + SHMatrix projMatrix; + SHVec3 position; + + bool perspProj; + SHVec3 offset; + + + + public: + friend class SHCameraSystem; + + SHCameraComponent(); + virtual ~SHCameraComponent(); + + + //Getters and setters. + void SetYaw(float yaw) noexcept; + void SetPitch(float pitch) noexcept; + void SetRoll(float roll) noexcept; + void SetPositionX(float x) noexcept; + void SetPositionY(float y) noexcept; + void SetPositionZ(float z) noexcept; + void SetPosition(float x, float y, float z) noexcept; + void SetPosition(SHVec3 pos) noexcept; + + void SetWidth(float width) noexcept; + void SetHeight(float height) noexcept; + void SetNear(float znear) noexcept; + void SetFar(float zfar) noexcept; + void SetFOV(float fov) noexcept; + void SetIsPerspective(bool persp) noexcept; + + + SHVec3 GetPosition() const noexcept; + float GetYaw() const noexcept; + float GetPitch() const noexcept; + float GetRoll() const noexcept; + + float GetWidth() const noexcept; + float GetHeight() const noexcept; + float GetNear() const noexcept; + float GetFar() const noexcept; + + float GetAspectRatio() const noexcept; + float GetFOV() const noexcept; + bool GetIsPerspective() const noexcept; + + const SHMatrix& GetViewMatrix() const noexcept; + const SHMatrix& GetProjMatrix() const noexcept; + + //void SetMainCamera(size_t cameraDirectorIndex = 0) noexcept; + + + float movementSpeed; + SHVec3 turnSpeed; + RTTR_ENABLE() + protected: + + + + + }; + + +} diff --git a/SHADE_Engine/src/Camera/SHCameraDirector.cpp b/SHADE_Engine/src/Camera/SHCameraDirector.cpp new file mode 100644 index 00000000..98341098 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraDirector.cpp @@ -0,0 +1,67 @@ +#include "SHpch.h" +#include "SHCameraDirector.h" +#include "SHCameraComponent.h" +#include "SHCameraArmComponent.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/SHECSMacros.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Tools/SHLog.h" + +namespace SHADE +{ + SHCameraDirector::SHCameraDirector() + :mainCameraEID(MAX_EID), transitionCameraEID(MAX_EID) + { + } + + + SHMatrix SHCameraDirector::GetViewMatrix() const noexcept + { + return viewMatrix; + } + SHMatrix SHCameraDirector::GetProjMatrix() const noexcept + { + return projMatrix; + } + SHMatrix SHCameraDirector::GetVPMatrix() const noexcept + { + return projMatrix * viewMatrix; + } + + void SHCameraDirector::UpdateMatrix() noexcept + { + if (mainCameraEID == MAX_EID) + { + auto& dense = SHComponentManager::GetDense(); + if (dense.size() == 0) + { + return; + } + mainCameraEID = dense[0].GetEID(); + } + SHCameraComponent* camComponent = SHComponentManager::GetComponent_s(mainCameraEID); + if (!camComponent) + { + SHLOG_WARNING("Camera Director warning: Entity does not have a camera"); + } + else + { + viewMatrix = camComponent->GetViewMatrix(); + projMatrix = camComponent->GetProjMatrix(); + } + + } + + void SHCameraDirector::SetMainCamera(SHCameraComponent& camera) noexcept + { + if (SHEntityManager::IsValidEID(camera.GetEID()) == false) + { + SHLOG_WARNING("Camera Director Warning: Attempting to set an invalid entity as main camera.") + return; + } + mainCameraEID = camera.GetEID(); + } + + + +} diff --git a/SHADE_Engine/src/Camera/SHCameraDirector.h b/SHADE_Engine/src/Camera/SHCameraDirector.h new file mode 100644 index 00000000..6d5404c5 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraDirector.h @@ -0,0 +1,44 @@ +#pragma once + +#include "SH_API.h" +#include "ECS_Base/Entity/SHEntity.h" +#include "Math/SHMatrix.h" +#include "Resource/SHHandle.h" + + +namespace SHADE +{ + class SHCameraComponent; + + + + class SH_API SHCameraDirector + { + public: + SHCameraDirector(); + ~SHCameraDirector() = default; + + + EntityID mainCameraEID; + EntityID transitionCameraEID; + + + SHMatrix GetViewMatrix() const noexcept; + SHMatrix GetProjMatrix() const noexcept; + SHMatrix GetVPMatrix() const noexcept; + void UpdateMatrix() noexcept; + void SetMainCamera(SHCameraComponent& cam) noexcept; + + + private: + + + protected: + SHMatrix viewMatrix; + SHMatrix projMatrix; + + }; + + typedef Handle DirectorHandle; + +} diff --git a/SHADE_Engine/src/Camera/SHCameraSystem.cpp b/SHADE_Engine/src/Camera/SHCameraSystem.cpp new file mode 100644 index 00000000..ff942666 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraSystem.cpp @@ -0,0 +1,430 @@ +#include "SHpch.h" +#include "SHCameraSystem.h" +#include "SHCameraArmComponent.h" +#include "Math/SHMathHelpers.h" +#include "Input/SHInputManager.h" +#include "Math/Vector/SHVec2.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Math/Transform/SHTransformComponent.h" +#include +#include "Scene/SHSceneManager.h" + +namespace SHADE +{ + void SHCameraSystem::UpdateEditorCamera(double dt) noexcept + { + + + + auto& camera = editorCamera; + SHVec3 view, right, UP; + GetCameraAxis(camera, view, right, UP); + + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::A)) + { + camera.position -= right * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::D)) + { + camera.position += right * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::W)) + { + camera.position += view * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::S)) + { + camera.position -= view * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::Q)) + { + camera.position += UP * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::E)) + { + camera.position -= UP * dt * camera.movementSpeed; + camera.dirtyView = true; + } + if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::RMB)) + { + double mouseX, mouseY; + SHInputManager::GetMouseVelocity(&mouseX, &mouseY); + + //std::cout << camera.yaw << std::endl; + + camera.pitch -= mouseY * dt * camera.turnSpeed.x; + camera.yaw -= mouseX * dt * camera.turnSpeed.y; + camera.dirtyView = true; + } + + UpdateCameraComponent(editorCamera); + } + + void SHCameraSystem::UpdateEditorArm(double dt,bool active ,SHVec3 const& targetPos) noexcept + { + if (active == false) + { + editorCameraArm.offset = SHVec3{0.0f}; + return; + } + + editorCamera.SetPosition(targetPos); + double mouseX, mouseY; + SHInputManager::GetMouseVelocity(&mouseX, &mouseY); + + editorCameraArm.pitch -= mouseY * dt * editorCamera.turnSpeed.x; + editorCameraArm.yaw -= mouseX * dt * editorCamera.turnSpeed.y; + + constexpr float pitchClamp = 85.0f; + + if (editorCameraArm.pitch > pitchClamp) + editorCameraArm.pitch = pitchClamp; + if (editorCameraArm.pitch < -pitchClamp) + editorCameraArm.pitch = -pitchClamp; + + editorCameraArm.armLength -= SHInputManager::GetMouseWheelVerticalDelta() * dt; + + if (editorCameraArm.armLength < 1.0f) + editorCameraArm.armLength = 1.0f; + + UpdatePivotArmComponent(editorCameraArm); + + editorCamera.offset = editorCameraArm.GetOffset(); + + CameraLookAt(editorCamera, targetPos); + + } + + + void SHCameraSystem::Init(void) + { + editorCamera.SetPosition(0.0f, 0.0f, 0.0f); + editorCamera.SetPitch(0.0f); + editorCamera.SetYaw(0.0f); + editorCamera.SetRoll(0.0f); + editorCamera.movementSpeed = 2.0f; + + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + + } + + void SHCameraSystem::Exit(void) + { + + } + + SHCameraComponent* SHCameraSystem::GetEditorCamera(void) noexcept + { + return &editorCamera; + } + + void SHCameraSystem::UpdatePivotArmComponent(SHCameraArmComponent& pivot) noexcept + { + if (pivot.dirty) + { + + SHVec3 offset{ 0.0f,0.0f, pivot.GetArmLength() }; + offset = SHVec3::RotateX(offset, -(SHMath::DegreesToRadians(pivot.GetPitch()))); + offset = SHVec3::RotateY(offset, (SHMath::DegreesToRadians(pivot.GetYaw()))); + + + //pivot.rtMatrix = SHMatrix::RotateX(SHMath::DegreesToRadians(pivot.GetPitch())) + // * SHMatrix::RotateY(SHMath::DegreesToRadians(pivot.GetYaw())) + // * SHMatrix::Translate(SHVec3(0.0f , 0.0f, pivot.GetArmLength())); + + pivot.offset = offset; + // pivot.rtMatrix = SHMatrix::Inverse(pivot.rtMatrix); + } + } + + + void SHCameraSystem::UpdateCameraComponent(SHCameraComponent& camera) noexcept + { + if (camera.isActive == false) + return; + + if (SHComponentManager::HasComponent(camera.GetEID()) == true && &camera != &editorCamera) + { + auto transform = SHComponentManager::GetComponent(camera.GetEID()); + SHVec3 rotation = transform->GetWorldRotation(); + camera.pitch = SHMath::RadiansToDegrees(rotation.x); + camera.yaw = SHMath::RadiansToDegrees(rotation.y); + camera.roll = SHMath::RadiansToDegrees(rotation.z); + camera.position = transform->GetWorldPosition(); + camera.dirtyView = true; + } + + + if (camera.dirtyView) + { + + camera.offset = SHVec3{ 0.0f }; + if (SHComponentManager::HasComponent(camera.GetEID())) + { + auto arm = SHComponentManager::GetComponent(camera.GetEID()); + if (arm->isActive == true) + { + camera.offset = arm->GetOffset(); + if (arm->lookAtCameraOrigin) + CameraLookAt(camera, camera.position); + } + + } + + + + SHVec3 view, right, UP; + + + ClampCameraRotation(camera); + + GetCameraAxis(camera, view, right, UP); + + camera.viewMatrix = SHMatrix::Identity; + camera.viewMatrix(0, 0) = right[0]; + camera.viewMatrix(0, 1) = right[1]; + camera.viewMatrix(0, 2) = right[2]; + + camera.viewMatrix(1, 0) = UP[0]; + camera.viewMatrix(1, 1) = UP[1]; + camera.viewMatrix(1, 2) = UP[2]; + + camera.viewMatrix(2, 0) = view[0]; + camera.viewMatrix(2, 1) = view[1]; + camera.viewMatrix(2, 2) = view[2]; + + camera.viewMatrix(0, 3) = -right.Dot(camera.position + camera.offset); + camera.viewMatrix(1, 3) = -UP.Dot(camera.position + camera.offset); + camera.viewMatrix(2, 3) = -view.Dot(camera.position + camera.offset); + + + + + camera.dirtyView = false; + } + if (camera.dirtyProj == true) + { + if (camera.perspProj == true) + { + const float ASPECT_RATIO = (camera.GetAspectRatio()); + const float TAN_HALF_FOV = tan(SHMath::DegreesToRadians(camera.fov) * 0.5f); + camera.projMatrix = SHMatrix::Identity; + camera.projMatrix(0, 0) = 1.0f / (ASPECT_RATIO * TAN_HALF_FOV); + camera.projMatrix(1, 1) = 1.0f / TAN_HALF_FOV; + camera.projMatrix(2, 2) = camera.zFar / (camera.zFar - camera.zNear); + camera.projMatrix(3, 3) = 0.0f; + + camera.projMatrix(3, 2) = 1.0f; + camera.projMatrix(2, 3) = -(camera.zFar * camera.zNear) / (camera.zFar - camera.zNear); + + + camera.dirtyProj = false; + } + else + { + //const float R = camera.width * 0.5f; + //const float L = -R; + //const float T = camera.height * 0.5f; + //const float B = -T; + + //camera.projMatrix = SHMatrix::Identity; + //camera.projMatrix(0, 0) = 2.0f / (R - L); + //camera.projMatrix(1, 1) = 2.0f / (B - T); + //camera.projMatrix(2, 2) = 1.0f / (camera.zFar - camera.zNear); + //camera.projMatrix(3, 0) = -(R + L) / (R - L); + //camera.projMatrix(3, 1) = -(B + T) / (B - T); + //camera.projMatrix(3, 2) = -camera.zNear / (camera.zFar - camera.zNear); + + camera.dirtyProj = false; + } + } + } + + void SHCameraSystem::GetCameraAxis(SHCameraComponent const& camera, SHVec3& forward, SHVec3& right, SHVec3& upVec) const noexcept + { + SHVec3 target{ 0.0f,0.0f,-1.0f }; + SHVec3 up = { 0.0f,1.0f,0.0f }; + + + + + target = SHVec3::RotateX(target, SHMath::DegreesToRadians(camera.pitch)); + target = SHVec3::RotateY(target, SHMath::DegreesToRadians(camera.yaw)); + target += camera.position; + ////SHVec3::RotateZ(target, SHMath::DegreesToRadians(camera.roll)); + + //target = SHVec3::Normalise(target); + + SHVec3::RotateZ(up, camera.roll); + up = SHVec3::Normalise(up); + + + forward = target - camera.position; forward = SHVec3::Normalise(forward); + right = SHVec3::Cross(forward, up); right = SHVec3::Normalise(right); + upVec = SHVec3::Cross(forward, right); + } + + void SHCameraSystem::CameraSystemUpdate::Execute(double dt) noexcept + { + SHCameraSystem* system = static_cast(GetSystem()); + auto& dense = SHComponentManager::GetDense(); + auto& pivotDense = SHComponentManager::GetDense(); + + for (auto& pivot : pivotDense) + { + if(SHSceneManager::CheckNodeAndComponentsActive(pivot.GetEID())) + system->UpdatePivotArmComponent(pivot); + } + + for (auto& cam : dense) + { + if (SHSceneManager::CheckNodeAndComponentsActive(cam.GetEID())) + system->UpdateCameraComponent(cam); + } + for (auto& handle : system->directorHandleList) + { + handle->UpdateMatrix(); + } + + + } + + + DirectorHandle SHCameraSystem::CreateDirector() noexcept + { + auto handle = directorLibrary.Create(); + directorHandleList.emplace_back(handle); + return handle; + } + + DirectorHandle SHCameraSystem::GetDirector(size_t index) noexcept + { + if (index < directorHandleList.size()) + { + return directorHandleList[index]; + } + else + { + return CreateDirector(); + } + } + void SHCameraSystem::ClampCameraRotation(SHCameraComponent& camera) noexcept + { + constexpr float clampVal = 85.0f; + + + if (camera.pitch > clampVal) + camera.SetPitch(clampVal); + if (camera.pitch < -clampVal) + camera.SetPitch(-clampVal); + if (camera.roll > clampVal) + camera.SetRoll(clampVal); + if (camera.roll < -clampVal) + camera.SetRoll(-clampVal); + + while (camera.yaw > 360) + camera.yaw -= 360; + while (camera.yaw < -360) + camera.yaw += 360; + + } + + void SHCameraSystem::SetMainCamera(EntityID eid, size_t directorIndex) noexcept + { + if (SHComponentManager::HasComponent(eid) && directorIndex < directorHandleList.size()) + directorHandleList[directorIndex]->SetMainCamera(*SHComponentManager::GetComponent(eid)); + else + { + SHLOG_WARNING("Set Main Camera warning: Entity does not have camera component or director does not exist.") + } + } + + void SHCameraSystem::DecomposeViewMatrix(SHMatrix const& viewMatrix, float& pitch, float& yaw, float& roll, SHVec3& pos) noexcept + { + + float initPitch = pitch; + SHVec3 initPos = pos; + SHVec3 translate3, scale; + SHQuaternion quat; + + //SHMatrix viewInverse = viewMatrix; + + viewMatrix.Decompose(translate3, quat, scale); + yaw = 180+ SHMath::RadiansToDegrees(quat.ToEuler().y); + pitch = -SHMath::RadiansToDegrees(quat.ToEuler().x); + + SHVec4 dotPos{ -viewMatrix(0,3),-viewMatrix(1,3), -viewMatrix(2,3), 1.0f }; + SHMatrix mtx = viewMatrix; + mtx(0, 3) = 0.0f; + mtx(1, 3) = 0.0f; + mtx(2, 3) = 0.0f; + mtx.Transpose(); + mtx = SHMatrix::Inverse(mtx); + SHVec4 translate = mtx* dotPos; + + pos.x = translate.x; + pos.y = translate.y; + pos.z = translate.z; + + } + void SHCameraSystem::SetCameraViewMatrix(SHCameraComponent& camera, SHMatrix const& viewMatrix) noexcept + { + DecomposeViewMatrix(viewMatrix, camera.pitch, camera.yaw, camera.roll, camera.position); + camera.dirtyView = true; + } + + void SHCameraSystem::CameraLookAt(SHCameraComponent& camera, SHVec3 target) noexcept + { + + if (camera.position == target) + { + //lets off set it abit so the view is nt fked + target.z -= 0.0001f; + } + SHVec3 forward, right, upVec; + + SHVec3 up = { 0.0f,1.0f,0.0f }; + + + //SHVec3::RotateZ(target, SHMath::DegreesToRadians(camera.roll)); + + //target = SHVec3::Normalise(target); + + SHVec3::RotateZ(up, camera.roll); + up = SHVec3::Normalise(up); + + + forward = target - (camera.position + camera.offset); forward = SHVec3::Normalise(forward); + right = SHVec3::Cross(forward, up); right = SHVec3::Normalise(right); + upVec = SHVec3::Cross(forward, right); + + + SHMatrix viewMtx; + viewMtx = SHMatrix::Identity; + viewMtx(0, 0) = right[0]; + viewMtx(0, 1) = right[1]; + viewMtx(0, 2) = right[2]; + + viewMtx(1, 0) = upVec[0]; + viewMtx(1, 1) = upVec[1]; + viewMtx(1, 2) = upVec[2]; + + viewMtx(2, 0) = forward[0]; + viewMtx(2, 1) = forward[1]; + viewMtx(2, 2) = forward[2]; + + viewMtx(0, 3) = -right.Dot(camera.position + camera.offset); + viewMtx(1, 3) = -upVec.Dot(camera.position + camera.offset); + viewMtx(2, 3) = -forward.Dot(camera.position + camera.offset); + + + SetCameraViewMatrix(camera, viewMtx); + } + +} diff --git a/SHADE_Engine/src/Camera/SHCameraSystem.h b/SHADE_Engine/src/Camera/SHCameraSystem.h new file mode 100644 index 00000000..fc6e9166 --- /dev/null +++ b/SHADE_Engine/src/Camera/SHCameraSystem.h @@ -0,0 +1,66 @@ +#pragma once + +#include "ECS_Base/System/SHSystem.h" +#include "SHCameraComponent.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Resource/SHResourceLibrary.h" +#include "SHCameraDirector.h" +#include "SHCameraArmComponent.h" +#include "SH_API.h" + +namespace SHADE +{ + + class SHCameraArmComponent; + + class SH_API SHCameraSystem final : public SHSystem + { + private: + //A camera component that represents editor camera. + //This is not tied to any entity. Hence this EID should not be used. + SHCameraComponent editorCamera; + SHCameraArmComponent editorCameraArm; + + SHResourceLibrary directorLibrary; + std::vector directorHandleList; + + + void UpdateCameraComponent(SHCameraComponent& camera) noexcept; + void UpdatePivotArmComponent(SHCameraArmComponent& pivot) noexcept; + + + public: + SHCameraSystem(void) = default; + virtual ~SHCameraSystem(void) = default; + + void Init (void); + void Exit (void); + + + friend class EditorCameraUpdate; + + class SH_API CameraSystemUpdate final: public SHSystemRoutine + { + public: + CameraSystemUpdate() : SHSystemRoutine("Camera System Update", true) {}; + virtual void Execute(double dt)noexcept override final; + }; + friend class CameraSystemUpdate; + + + SHCameraComponent* GetEditorCamera (void) noexcept; + void GetCameraAxis(SHCameraComponent const& camera, SHVec3& forward, SHVec3& right, SHVec3& up) const noexcept; + DirectorHandle CreateDirector() noexcept; + DirectorHandle GetDirector(size_t index) noexcept; + void ClampCameraRotation(SHCameraComponent& camera) noexcept; + void UpdateEditorCamera(double dt) noexcept; + void SetMainCamera(EntityID eid, size_t directorIndex) noexcept; + void DecomposeViewMatrix(SHMatrix const& matrix, float& pitch, float& yaw, float& roll, SHVec3& pos) noexcept; + void SetCameraViewMatrix(SHCameraComponent& camera, SHMatrix const& viewMatrix) noexcept; + void CameraLookAt(SHCameraComponent& camera, SHVec3 target) noexcept; + void UpdateEditorArm(double dt,bool active ,SHVec3 const& targetPos) noexcept; + }; + + + +} diff --git a/SHADE_Engine/src/Common/SHCommonTypes.h b/SHADE_Engine/src/Common/SHCommonTypes.h new file mode 100644 index 00000000..97ef7928 --- /dev/null +++ b/SHADE_Engine/src/Common/SHCommonTypes.h @@ -0,0 +1,28 @@ +/************************************************************************************//*! +\file SHCommonTypes.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains the definitions of type alias for commonly used units for + clarity and convenience. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Type used to mark a value that is supposed to represent a size in bytes. + */ + /***********************************************************************************/ + using Byte = size_t; +} diff --git a/SHADE_Engine/src/ECS_Base/Components/SHComponent.h b/SHADE_Engine/src/ECS_Base/Components/SHComponent.h index c9c5e6f1..1c4e5de6 100644 --- a/SHADE_Engine/src/ECS_Base/Components/SHComponent.h +++ b/SHADE_Engine/src/ECS_Base/Components/SHComponent.h @@ -9,18 +9,19 @@ *********************************************************************/ -#ifndef SH_COMPONENT_H -#define SH_COMPONENT_H +#pragma once #include "SHpch.h" #include "../SHECSMacros.h" +#include "SH_API.h" +#include "ECS_Base/General/SHFamily.h" namespace SHADE { class SHComponentManager; - class SHComponent + class SH_API SHComponent { friend SHComponentManager; @@ -47,6 +48,9 @@ namespace SHADE { } + + + public: //Whether or not this component is active. //Systems using this component should are responsible for checking the active state of the component before running their functionality. @@ -58,7 +62,7 @@ namespace SHADE * \return uint32_t * The entityID that this component belongs to. ***************************************************************************/ - uint32_t GetEID()const + uint32_t GetEID()const noexcept { return this->entityID; } @@ -117,5 +121,7 @@ namespace SHADE }; + + + template class SH_API SHFamilyID; } -#endif diff --git a/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.cpp b/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.cpp index 3ccb7778..2627d9fb 100644 --- a/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.cpp +++ b/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.cpp @@ -14,7 +14,7 @@ #include "SHpch.h" #include "SHComponentGroup.h" -#include "../System/SHComponentManager.h" +#include "../Managers/SHComponentManager.h" namespace SHADE diff --git a/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.h b/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.h index 3f67aff0..a1b13ea1 100644 --- a/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.h +++ b/SHADE_Engine/src/ECS_Base/Components/SHComponentGroup.h @@ -12,9 +12,7 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ - -#ifndef SH_COMPONENT_GROUP -#define SH_COMPONENT_GROUP +#pragma once #include "../SHECSMacros.h" #include "../General/SHFamily.h" @@ -24,6 +22,8 @@ #include #include +#include "SH_API.h" + namespace SHADE { enum class OWNERSHIP_TYPE @@ -36,7 +36,7 @@ namespace SHADE //template - class SHComponentGroup + class SH_API SHComponentGroup { public: @@ -180,6 +180,3 @@ namespace SHADE }; } - - -#endif diff --git a/SHADE_Engine/src/ECS_Base/Entity/SHEntity.cpp b/SHADE_Engine/src/ECS_Base/Entity/SHEntity.cpp index 6005fb01..3ef138ac 100644 --- a/SHADE_Engine/src/ECS_Base/Entity/SHEntity.cpp +++ b/SHADE_Engine/src/ECS_Base/Entity/SHEntity.cpp @@ -9,9 +9,9 @@ *********************************************************************/ #include "SHpch.h" #include "SHEntity.h" -#include "../System/SHEntityManager.h" +#include "../Managers/SHEntityManager.h" //#include "Scene/SHSceneGraph.h" -#include "../System/SHComponentManager.h" +#include "../Managers/SHComponentManager.h" namespace SHADE { @@ -28,7 +28,7 @@ namespace SHADE //SHEntityManager::RemoveEntity(this->entityID); } - EntityID SHEntity::GetEID() noexcept + EntityID SHEntity::GetEID() const noexcept { return this->entityID; } @@ -39,36 +39,65 @@ namespace SHADE SHComponentManager::SetActive(entityID, active); } + bool SHEntity::GetActive(void) const noexcept + { + return isActive; + } void SHEntity::SetParent(SHEntity* newParent) noexcept { - (void)newParent; - //TODO + SHSceneManager::GetCurrentSceneGraph().SetParent(GetEID(), newParent->GetEID()); } void SHEntity::SetParent(EntityID newParentID) noexcept { - (void)newParentID; - //TODO + SHSceneManager::GetCurrentSceneGraph().SetParent(GetEID(), newParentID); } - SHEntity* SHEntity::GetParent() noexcept + SHEntity* SHEntity::GetParent()const noexcept { - //TODO - return nullptr; + SHSceneNode* parent = SHSceneManager::GetCurrentSceneGraph().GetParent(GetEID()); + if (parent != nullptr) + return SHEntityManager::GetEntityByID(parent->GetEntityID()); + else + return nullptr; + } + + EntityID SHEntity::GetParentEID()const noexcept + { + SHSceneNode* parent = SHSceneManager::GetCurrentSceneGraph().GetParent(GetEID()); + if (parent != nullptr) + return parent->GetEntityID(); + else + return MAX_EID; } - std::vectorconst& SHEntity::GetChildren() noexcept + std::vectorconst& SHEntity::GetChildren()const noexcept { - //TODO - return std::vector{}; + std::vector childrenEntity; + + auto& childrenNodes = SHSceneManager::GetCurrentSceneGraph().GetChildren(GetEID()); + for (auto& childNode : childrenNodes) + { + childrenEntity.push_back(SHEntityManager::GetEntityByID(childNode->GetEntityID())); + } + + return childrenEntity; } - std::vectorconst& SHEntity::GetChildrenID() noexcept + std::vectorconst& SHEntity::GetChildrenID()const noexcept { - return std::vector{}; + std::vector childrenEntity; + + auto& childrenNodes = SHSceneManager::GetCurrentSceneGraph().GetChildren(GetEID()); + for (auto& childNode : childrenNodes) + { + childrenEntity.push_back(childNode->GetEntityID()); + } + + return childrenEntity; } } diff --git a/SHADE_Engine/src/ECS_Base/Entity/SHEntity.h b/SHADE_Engine/src/ECS_Base/Entity/SHEntity.h index d499042c..690895e8 100644 --- a/SHADE_Engine/src/ECS_Base/Entity/SHEntity.h +++ b/SHADE_Engine/src/ECS_Base/Entity/SHEntity.h @@ -8,13 +8,13 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_ENTITY_H -#define SH_ENTITY_H +#pragma once #include "../SHECSMacros.h" #include "../Components/SHComponent.h" -#include "../System/SHComponentManager.h" +#include "../Managers/SHComponentManager.h" //#include "../../Scene/SHSceneNode.h" +#include "SH_API.h" @@ -23,7 +23,7 @@ namespace SHADE class SHComponentManager; class SHEntityManager; - class SHEntity + class SH_API SHEntity { public: @@ -63,7 +63,7 @@ namespace SHADE * Returns nullptr if the entity does not have such Component. ***************************************************************************/ template - std::enable_if_t, T*> GetComponent() noexcept + std::enable_if_t, T*> GetComponent()const noexcept { return SHComponentManager::GetComponent_s(entityID); @@ -77,7 +77,7 @@ namespace SHADE * \return uint32_t * The entityID of this Entity object. ***************************************************************************/ - EntityID GetEID() noexcept; + EntityID GetEID() const noexcept; /*!************************************************************************* * \brief Set the Active object @@ -91,6 +91,7 @@ namespace SHADE ***************************************************************************/ virtual void SetActive(bool active) noexcept; + bool GetActive(void)const noexcept; /************************************************************************** @@ -124,8 +125,15 @@ namespace SHADE * Returns a pointer to the parent entity. * Returns a nullptr if the parent node is the root node. ***************************************************************************/ - SHEntity* GetParent() noexcept; + SHEntity* GetParent()const noexcept; + /******************************************************************** + * \brief + * Get the entity ID of the parent. + * \return + * return the entity ID of the parent + ********************************************************************/ + EntityID GetParentEID() const noexcept; /************************************************************************** * \brief @@ -133,7 +141,7 @@ namespace SHADE * \return * Return a vector of SHEntity pointers of the children belonging to this entity. ***************************************************************************/ - std::vectorconst& GetChildren() noexcept; + std::vectorconst& GetChildren()const noexcept; /************************************************************************** * \brief @@ -141,11 +149,13 @@ namespace SHADE * \return * return a vector of EntityID of the children belonging to this entity. ***************************************************************************/ - std::vectorconst& GetChildrenID() noexcept; - + std::vectorconst& GetChildrenID()const noexcept; + //Name of the entity. This name is non-unique and only used for the editor. std::string name; - bool isActive; + + + private: @@ -156,8 +166,10 @@ namespace SHADE EntityID entityID; + //Entity active state. This should only be set using the SetActive function which will + //set the active state of all components of this entity. + bool isActive; + + }; } - - -#endif diff --git a/SHADE_Engine/src/ECS_Base/Events/SHComponentAddedEvent.h b/SHADE_Engine/src/ECS_Base/Events/SHComponentAddedEvent.h new file mode 100644 index 00000000..1f2b62d5 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/Events/SHComponentAddedEvent.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ECS_Base/Components/SHComponent.h" + +namespace SHADE +{ + struct SHComponentAddedEvent + { + EntityID eid; + ComponentTypeID addedComponentType; + }; +} diff --git a/SHADE_Engine/src/ECS_Base/Events/SHComponentRemovedEvent.h b/SHADE_Engine/src/ECS_Base/Events/SHComponentRemovedEvent.h new file mode 100644 index 00000000..34d6f9c8 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/Events/SHComponentRemovedEvent.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ECS_Base/Components/SHComponent.h" + +namespace SHADE +{ + struct SHComponentRemovedEvent + { + EntityID eid; + ComponentTypeID removedComponentType; + }; +} diff --git a/SHADE_Engine/src/ECS_Base/Events/SHEntityCreationEvent.h b/SHADE_Engine/src/ECS_Base/Events/SHEntityCreationEvent.h new file mode 100644 index 00000000..709d5147 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/Events/SHEntityCreationEvent.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ECS_Base/Entity/SHEntity.h" + +namespace SHADE +{ + struct SHEntityCreationEvent + { + EntityID eid; + std::vector componentTypeIDs; + EntityID parentEID; + }; +} diff --git a/SHADE_Engine/src/ECS_Base/Events/SHEntityDestroyedEvent.h b/SHADE_Engine/src/ECS_Base/Events/SHEntityDestroyedEvent.h new file mode 100644 index 00000000..770f0c4e --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/Events/SHEntityDestroyedEvent.h @@ -0,0 +1,11 @@ +#pragma once + +#include "ECS_Base/Entity/SHEntity.h" + +namespace SHADE +{ + struct SHEntityDestroyedEvent + { + EntityID eid; + }; +} diff --git a/SHADE_Engine/src/ECS_Base/General/SHFamily.cpp b/SHADE_Engine/src/ECS_Base/General/SHFamily.cpp new file mode 100644 index 00000000..bd5c0378 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/General/SHFamily.cpp @@ -0,0 +1,14 @@ +#pragma once +#include "SHFamily.h" +#include "SHpch.h" + +namespace SHADE +{ + //initialize currentID as 0 + + + + + + +} diff --git a/SHADE_Engine/src/ECS_Base/General/SHFamily.h b/SHADE_Engine/src/ECS_Base/General/SHFamily.h index f97bbe0e..51bd6a25 100644 --- a/SHADE_Engine/src/ECS_Base/General/SHFamily.h +++ b/SHADE_Engine/src/ECS_Base/General/SHFamily.h @@ -11,20 +11,20 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_FAMILY_H -#define SH_FAMILY_H +#pragma once #include "../SHECSMacros.h" +#include "SH_API.h" namespace SHADE { + template - class SHFamilyID + class SH_API SHFamilyID { private: - //this is used to keep track of the new current ID to be assign to a new Derived class type. - static ComponentTypeID currentID; + /*!************************************************************************* * \brief Construct a new SHFamilyID object @@ -47,6 +47,9 @@ namespace SHADE } public: + //this is used to keep track of the new current ID to be assign to a new Derived class type. + static inline ComponentTypeID currentID = 0; + /*!************************************************************************* * \brief * Checks if this identifier is cuurrently in use / valid. @@ -60,7 +63,6 @@ namespace SHADE { return(id < currentID); } - /*!************************************************************************* * \brief * Get the ID of a derived class type. @@ -69,20 +71,27 @@ namespace SHADE * @tparam DerivedClass * The derived class type that we are trying to get the ID of. ***************************************************************************/ +#ifdef SH_API_EXPORT template - static ENABLE_IF_DERIVED(ComponentTypeID, BaseClass, DerivedClass) GetID() noexcept + static SH_API ENABLE_IF_DERIVED(ComponentTypeID, BaseClass, DerivedClass) GetID() noexcept { //The first time a new derived class type call this get id, it will initialize id using the currentID from familyID class. - static ComponentTypeID id = currentID++; + static ComponentTypeID id = SHFamilyID::currentID++; return id; + //return 0; } +#else + template + static SH_API ENABLE_IF_DERIVED(ComponentTypeID, BaseClass, DerivedClass) GetID() noexcept; +#endif // SH_API_EXPORT + + + + + }; - //initialize currentID as 0 - template - ComponentTypeID SHFamilyID::currentID = 0; - -} -#endif \ No newline at end of file + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/General/SHHandleGenerator.h b/SHADE_Engine/src/ECS_Base/General/SHHandleGenerator.h index 48ae2132..7c68f66d 100644 --- a/SHADE_Engine/src/ECS_Base/General/SHHandleGenerator.h +++ b/SHADE_Engine/src/ECS_Base/General/SHHandleGenerator.h @@ -11,8 +11,7 @@ or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_HANDLE_GENERATOR_H -#define SH_HANDLE_GENERATOR_H +#pragma once #include #include @@ -299,7 +298,4 @@ namespace SHADE typedef SHHandleGenerator EntityHandleGenerator; -} - - -#endif +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/General/SHSparseBase.h b/SHADE_Engine/src/ECS_Base/General/SHSparseBase.h index 2deebe74..913fca5a 100644 --- a/SHADE_Engine/src/ECS_Base/General/SHSparseBase.h +++ b/SHADE_Engine/src/ECS_Base/General/SHSparseBase.h @@ -10,8 +10,7 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_SPARSE_BASE_H -#define SH_SPARSE_BASE_H +#pragma once #include "../SHECSMacros.h" @@ -44,7 +43,4 @@ namespace SHADE }; -} - - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/General/SHSparseSet.h b/SHADE_Engine/src/ECS_Base/General/SHSparseSet.h index 3473eb37..8e096a37 100644 --- a/SHADE_Engine/src/ECS_Base/General/SHSparseSet.h +++ b/SHADE_Engine/src/ECS_Base/General/SHSparseSet.h @@ -9,8 +9,7 @@ or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_SPARSE_SET_H -#define SH_SPARSE_SET_H +#pragma once #include "../SHECSMacros.h" #include "../General/SHSparseBase.h" @@ -351,6 +350,4 @@ namespace SHADE } }; -} - -#endif +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/General/SHSparseSetContainer.h b/SHADE_Engine/src/ECS_Base/General/SHSparseSetContainer.h index 8698d1f5..8f8814f6 100644 --- a/SHADE_Engine/src/ECS_Base/General/SHSparseSetContainer.h +++ b/SHADE_Engine/src/ECS_Base/General/SHSparseSetContainer.h @@ -10,8 +10,7 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_SPARSE_SET_CONTAINER_H -#define SH_SPARSE_SET_CONTAINER_H +#pragma once #include "SHSparseSet.h" #include "SHFamily.h" @@ -243,6 +242,3 @@ namespace SHADE }; } - - -#endif diff --git a/SHADE_Engine/src/ECS_Base/System/SHComponentManager.cpp b/SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.cpp similarity index 88% rename from SHADE_Engine/src/ECS_Base/System/SHComponentManager.cpp rename to SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.cpp index be78a146..8a715a49 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHComponentManager.cpp +++ b/SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.cpp @@ -33,12 +33,18 @@ namespace SHADE return; } + std::vector eventVec; + for (uint32_t i = 0; i < componentSet.Size(); ++i) { SHComponent* comp = (SHComponent*) componentSet.GetElement(i, EntityHandleGenerator::GetIndex(entityID)); if (comp) { comp->OnDestroy(); + SHComponentRemovedEvent eventData; + eventData.eid = entityID; + eventData.removedComponentType = i; + eventVec.push_back(eventData); } } @@ -51,8 +57,15 @@ namespace SHADE componentSet.RemoveElements(EntityHandleGenerator::GetIndex(entityID)); + for (auto& eventData : eventVec) + { + SHEventManager::BroadcastEvent(eventData, SH_COMPONENT_REMOVED_EVENT); + } + + //entityHandle.RemoveHandle(entityID); + } diff --git a/SHADE_Engine/src/ECS_Base/System/SHComponentManager.h b/SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.h similarity index 90% rename from SHADE_Engine/src/ECS_Base/System/SHComponentManager.h rename to SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.h index 2f6ff504..8921fbce 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHComponentManager.h +++ b/SHADE_Engine/src/ECS_Base/Managers/SHComponentManager.h @@ -12,20 +12,25 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_ENGINE_H -#define SH_ENGINE_H +#pragma once #include "../General/SHSparseSetContainer.h" #include "../Components/SHComponent.h" #include "../Components/SHComponentGroup.h" +#include "../Events/SHComponentAddedEvent.h" +#include "../Events/SHComponentRemovedEvent.h" //#include "Scene/SHSceneNode.h" +#include "SH_API.h" +#include "Events/SHEventManager.hpp" #include namespace SHADE { - class SHComponentManager + typedef SHFamilyID ComponentFamily; + + class SH_API SHComponentManager { private: @@ -34,7 +39,9 @@ namespace SHADE //The Container of all Componentgroups static std::vector componentGroups; - friend struct SHSceneNode; + friend class SHSceneNode; + + @@ -212,6 +219,11 @@ namespace SHADE comp->OnCreate(); } + SHComponentAddedEvent eventData; + eventData.eid = entityID; + eventData.addedComponentType = ComponentFamily::GetID(); + + SHEventManager::BroadcastEvent(eventData, SH_COMPONENT_ADDED_EVENT); } /************************************************************************** @@ -225,7 +237,7 @@ namespace SHADE * \return * none ***************************************************************************/ - static void AddComponent(EntityID entityID, uint32_t componentTypeID) noexcept + static void AddComponent(EntityID entityID, ComponentTypeID componentTypeID) noexcept { componentSet.GetSparseSet_ID(componentTypeID)->Add(EntityHandleGenerator::GetIndex(entityID)); @@ -243,6 +255,13 @@ namespace SHADE { comp->OnCreate(); } + + SHComponentAddedEvent eventData; + eventData.eid = entityID; + eventData.addedComponentType = componentTypeID; + + SHEventManager::BroadcastEvent(eventData, SH_COMPONENT_ADDED_EVENT); + } @@ -277,7 +296,7 @@ namespace SHADE * \return bool * True if the entity has a component of specified type. ***************************************************************************/ - static bool HasComponent_ID(EntityID entityID, uint32_t componentTypeID) noexcept; + static bool HasComponent_ID(EntityID entityID, ComponentTypeID componentTypeID) noexcept; /*!************************************************************************* @@ -309,6 +328,12 @@ namespace SHADE componentSet.GetSparseSet()->Remove(EntityHandleGenerator::GetIndex(entityID)); + + SHComponentRemovedEvent eventData; + eventData.eid = entityID; + eventData.removedComponentType = ComponentFamily::GetID(); + + SHEventManager::BroadcastEvent(eventData, SH_COMPONENT_REMOVED_EVENT); } /*!************************************************************************* @@ -362,7 +387,7 @@ namespace SHADE * \return * ***************************************************************************/ - static void SwapInDenseByIndexHash_ID(EntityIndex index, EntityID hash, uint32_t componentTypeID) noexcept; + static void SwapInDenseByIndexHash_ID(EntityIndex index, EntityID hash, ComponentTypeID componentTypeID) noexcept; @@ -385,7 +410,7 @@ namespace SHADE * \return * The number of components in the component sparse set. ***************************************************************************/ - static EntityIndex ComponentCount_ID(uint32_t componentTypeID) + static EntityIndex ComponentCount_ID(ComponentTypeID componentTypeID) { return componentSet.GetSparseSet_ID(componentTypeID)->Count(); } @@ -402,9 +427,9 @@ namespace SHADE static void SetActive(EntityID entityID, bool active) noexcept; template - static std::enable_if_t<(... && std::is_base_of_v), uint32_t> CreateComponentGroup(uint32_t numOwningComponents) + static std::enable_if_t<(... && std::is_base_of_v), uint32_t> CreateComponentGroup(ComponentTypeID numOwningComponents) { - std::vector templateIDs{ (SHFamilyID::GetID())... }; + std::vector templateIDs{ (ComponentFamily::GetID())... }; for (auto& g : componentGroups) { @@ -421,11 +446,11 @@ namespace SHADE } SHComponentGroup grp; - for (uint32_t i = 0; i < numOwningComponents; ++i) + for (ComponentTypeID i = 0; i < numOwningComponents; ++i) { grp.ownedComponentTypes.push_back(templateIDs[i]); } - for (uint32_t i = 0; i < templateIDs.size(); ++i) + for (ComponentTypeID i = 0; i < templateIDs.size(); ++i) { grp.componentTypeIDs.push_back(templateIDs[i]); } @@ -460,18 +485,10 @@ namespace SHADE return componentGroups[index]; } - static void AddScriptComponent(EntityID eid, std::string const& scriptClassName) noexcept; - - static void RemoveScriptComponent(EntityID eid, std::string const& scriptClassName) noexcept; - - };// end SHComponentManager -} - - -#endif +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHEntityManager.cpp b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp similarity index 78% rename from SHADE_Engine/src/ECS_Base/System/SHEntityManager.cpp rename to SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp index 4aa38112..6ce4f277 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHEntityManager.cpp +++ b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp @@ -52,63 +52,20 @@ namespace SHADE return entityHandle.GetIndex(entityID); } - EntityID SHEntityManager::CreateEntity(std::vectorconst& componentTypeIDs, std::string const& name,EntityID parentEID) - { - EntityID eID = entityHandle.GetNewHandle(); - EntityIndex eIndex = entityHandle.GetIndex(eID); - if (eIndex > entityVec.size()) - { - assert("FATAL ERROR: EntityIndex out of range in Entity Creation"); - } - else if (eIndex == entityVec.size()) - { - entityVec.emplace_back(std::make_unique()); - } - else - { - if (!entityVec[eIndex]) - { - //There is still an entity stored there.Something went wrong - assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); - } - - //Reset it to a newly constructed entity - entityVec[eIndex].reset(new SHEntity()); - } - - - entityVec[eIndex]->entityID = eID; - entityVec[eIndex]->name = name; - for (auto& id : componentTypeIDs) - { - SHComponentManager::AddComponent(eID, id); - } - - //(SHComponentManager::AddComponent(eID), ...); - /*if (entityHandle.IsValid(parentEID) == false) - { - entityVec[eIndex]->sceneNode.ConnectToRoot(); - } - else - { - entityVec[eIndex]->SetParent(parentEID); - }*/ - - - //TODO Link to Scene graph. - - return eID; - - } - EntityID SHEntityManager::CreateEntity(std::vectorconst& componentTypeIDs, EntityID desiredEID, std::string const& name, EntityID parentEID) { - EntityID eID ; - - if (entityHandle.ClaimHandle(desiredEID) == true) - eID = desiredEID; - else + EntityID eID; + if (desiredEID == MAX_EID) + { eID = entityHandle.GetNewHandle(); + } + else + { + if (entityHandle.ClaimHandle(desiredEID) == true) + eID = desiredEID; + else + eID = entityHandle.GetNewHandle(); + } EntityIndex eIndex = entityHandle.GetIndex(eID); @@ -146,6 +103,18 @@ namespace SHADE SHComponentManager::AddComponent(eID, id); } + + //Link up with scene graph. + + if (parentEID != MAX_EID) + SHSceneManager::GetCurrentSceneGraph().AddNode(eID, SHSceneManager::GetCurrentSceneGraph().GetNode(parentEID)); + else + SHSceneManager::GetCurrentSceneGraph().AddNode(eID); + + //set up event stuff + SHEntityCreationEvent event{ eID,componentTypeIDs, parentEID }; + SHEventManager::BroadcastEvent(event, SH_ENTITY_CREATION_EVENT); + //(SHComponentManager::AddComponent(eID), ...); //if (entityHandle.IsValid(parentEID) == false) @@ -177,6 +146,14 @@ namespace SHADE //Call all the children to Destroy themselves first before the parent is destroyed. if (entityVec[eIndex]) { + auto& children = SHSceneManager::GetCurrentSceneGraph().GetChildren(eID); + for (auto& child : children) + { + DestroyEntity(child->GetEntityID()); + } + + SHSceneManager::GetCurrentSceneGraph().RemoveNode(eID); + //auto& children = entityVec[eIndex]->GetChildrenID(); //while(!children.empty()) //{ @@ -196,6 +173,14 @@ namespace SHADE entityHandle.RemoveHandle(eID); entityVec[eIndex].reset(nullptr); + + + + + SHEntityDestroyedEvent event{eID}; + SHEventManager::BroadcastEvent(event, SH_ENTITY_DESTROYED_EVENT); + + } } @@ -228,5 +213,14 @@ namespace SHADE return SHSerialization::DeserializeEntityToSceneFromString(data); }*/ - + EntityID SHEntityManager::GetEntityByName(std::string const& name) noexcept + { + EntityID result = MAX_EID; + for (auto& entity : entityVec) + { + if (entity->name == name) + result = entity->GetEID(); + } + return result; + } } diff --git a/SHADE_Engine/src/ECS_Base/System/SHEntityManager.h b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.h similarity index 71% rename from SHADE_Engine/src/ECS_Base/System/SHEntityManager.h rename to SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.h index 11e896d5..911748bd 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHEntityManager.h +++ b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.h @@ -12,9 +12,7 @@ or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ - -#ifndef SH_ENTITY_MANAGER_H -#define SH_ENTITY_MANAGER_H +#pragma once #include #include @@ -22,11 +20,16 @@ #include "../Components/SHComponent.h" #include "../General/SHHandleGenerator.h" #include "../SHECSMacros.h" +#include "ECS_Base/Events/SHEntityCreationEvent.h" +#include "ECS_Base/Events/SHEntityDestroyedEvent.h" +#include "Scene/SHSceneManager.h" +#include "Events/SHEventManager.hpp" +#include "SH_API.h" namespace SHADE { - class SHEntityManager + class SH_API SHEntityManager { private: static std::vector> entityVec; @@ -81,55 +84,21 @@ namespace SHADE * EntityID of the new Entity ***************************************************************************/ template - static std::enable_if_t<(... && std::is_base_of_v), EntityID> CreateEntity(std::string const& name = "Default", EntityID parentEID = MAX_EID) - { - EntityID eID = entityHandle.GetNewHandle(); - EntityIndex eIndex = entityHandle.GetIndex(eID); - if (eIndex > entityVec.size()) - { - assert("FATAL ERROR: EntityIndex out of range in Entity Creation"); - } - else if (eIndex == entityVec.size()) - { - entityVec.emplace_back(std::make_unique()); - } - else - { - if (!entityVec[eIndex]) - { - //There is still an entity stored there.Something went wrong - assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); - } - - //Reset it to a newly constructed entity - entityVec[eIndex].reset(new SHEntity()); - } - entityVec[eIndex]->entityID = eID; - entityVec[eIndex]->name = name; - (SHComponentManager::AddComponent(eID),...); - - /*if (entityHandle.IsValid(parentEID) == false) - { - entityVec[eIndex]->sceneNode.ConnectToRoot(); - } - else - { - entityVec[eIndex]->SetParent(parentEID); - }*/ - - //TODO Link up with Scene graph - - return eID; - } - - template - static std::enable_if_t<(... && std::is_base_of_v), EntityID> CreateEntity(EntityID desiredEID, std::string const& name = "Default", EntityID parentEID = MAX_EID) + static std::enable_if_t<(... && std::is_base_of_v), EntityID> CreateEntity(EntityID desiredEID = MAX_EID, std::string const& name = "Default", EntityID parentEID = MAX_EID) { EntityID eID; - if (entityHandle.ClaimHandle(desiredEID) == true) - eID = desiredEID; - else + if (desiredEID == MAX_EID) + { eID = entityHandle.GetNewHandle(); + } + else + { + if (entityHandle.ClaimHandle(desiredEID) == true) + eID = desiredEID; + else + eID = entityHandle.GetNewHandle(); + } + EntityIndex eIndex = entityHandle.GetIndex(eID); if (eIndex > entityVec.size()) { @@ -160,6 +129,23 @@ namespace SHADE entityVec[eIndex]->name = name; (SHComponentManager::AddComponent(eID), ...); + //set up event stuff + std::vector typeIDVec; + (typeIDVec.push_back(ComponentFamily::GetID()), ...); + + + //Link up with scene graph. + + if (parentEID != MAX_EID) + SHSceneManager::GetCurrentSceneGraph().AddNode(eID, SHSceneManager::GetCurrentSceneGraph().GetNode(parentEID)); + else + SHSceneManager::GetCurrentSceneGraph().AddNode(eID); + + SHEntityCreationEvent event{ eID,typeIDVec,parentEID }; + SHEventManager::BroadcastEvent(event, SH_ENTITY_CREATION_EVENT); + + + /*if (entityHandle.IsValid(parentEID) == false) { entityVec[eIndex]->sceneNode.ConnectToRoot(); @@ -170,8 +156,6 @@ namespace SHADE }*/ - //Link up with scene graph. - return eID; } @@ -179,21 +163,6 @@ namespace SHADE - /************************************************************************** - * \brief - * Create Entity using a vector of ComponentTypeIDs. - * \param componentTypeIDs - * Vector of ComponentTypeIDs. This assumes that CreateSparseSet is called - * for these ComponentTypes. - * \param name - * Name of the Entity (this is not unique) - * \param parentEID - * The entity ID of the parent. This does not call UpdateHierarchy hence - * the parent of the entity is not updated until UpdateHierarchy is called. - * \return - * EntityID of the new Entity - ***************************************************************************/ - static EntityID CreateEntity(std::vectorconst& componentTypeIDs,std::string const& name = "Default", EntityID parentEID = MAX_EID); /************************************************************************** * \brief @@ -209,7 +178,7 @@ namespace SHADE * \return * EntityID of the new Entity ***************************************************************************/ - static EntityID CreateEntity(std::vectorconst& componentTypeIDs, EntityID desiredEID, std::string const& name = "Default", EntityID parentEID = MAX_EID); + static EntityID CreateEntity(std::vectorconst& componentTypeIDs, EntityID desiredEID = MAX_EID, std::string const& name = "Default", EntityID parentEID = MAX_EID); /************************************************************************** * \brief @@ -240,6 +209,8 @@ namespace SHADE //static EntityID DuplicateEntity(EntityID eid) noexcept; + static EntityID GetEntityByName(std::string const& name) noexcept; + protected: @@ -247,7 +218,3 @@ namespace SHADE } - - - -#endif diff --git a/SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.cpp b/SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.cpp new file mode 100644 index 00000000..057568d6 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.cpp @@ -0,0 +1,77 @@ +/********************************************************************* + * \file SHSystemManager.cpp + * \author Daniel Chua Yee Chen + * \brief Implementation for the SHSystemManager class. + * SHSystemManager is the interface class where users of the engine create + * the systems that gives the components their functionality. This also + * ensures that the Init and Exit functions are ran at the appropriate time + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#include "SHpch.h" +#include "SHSystemManager.h" +#include + +#include +#include +#include + +namespace SHADE +{ + SHSystemManager::SystemContainer SHSystemManager::systemContainer; + SHSystemManager::SystemRoutineContainer SHSystemManager::systemRoutineContainer; + + void SHSystemManager::Init() noexcept + { + for (auto& system : systemContainer) + { + system.second->Init(); +#ifdef _DEBUG + SHLOG_INFO("Initialising System {}...", system.first) +#endif + } + } + + void SHSystemManager::RunRoutines(bool editorPause, double deltaTime) noexcept + { + for (auto& routine : systemRoutineContainer) + { + if (editorPause == true) + { + if (routine.get()->IsRunInEditorPause) + { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + routine.get()->Execute(deltaTime); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + routine.get()->stats.executionTime = std::chrono::duration(end - start).count(); + } + } + else + { + std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now(); + routine.get()->Execute(deltaTime); + std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); + routine.get()->stats.executionTime = std::chrono::duration(end - start).count(); + } + } + } + + + void SHSystemManager::Exit() noexcept + { + systemRoutineContainer.clear(); + + for (SystemContainer::reverse_iterator it = systemContainer.rbegin(); it != systemContainer.rend(); ++it) + { + (*it).second->Exit(); + //delete system.second; + } + + systemContainer.clear(); + + } + + +} diff --git a/SHADE_Engine/src/ECS_Base/System/SHSystemManager.h b/SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.h similarity index 54% rename from SHADE_Engine/src/ECS_Base/System/SHSystemManager.h rename to SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.h index d5a4866d..995a1cf5 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHSystemManager.h +++ b/SHADE_Engine/src/ECS_Base/Managers/SHSystemManager.h @@ -10,29 +10,33 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ - -#ifndef SH_SYSTEM_MANAGER_H -#define SH_SYSTEM_MANAGER_H +#pragma once -#include +#include #include #include #include +#include #include "../System/SHSystem.h" - +#include "../General/SHFamily.h" +#include "../System/SHSystemRoutine.h" +#include "SH_API.h" namespace SHADE { - class SHSystemManager + + typedef SHFamilyID SystemFamily; + + class SH_API SHSystemManager { //type definition for the container we use to store our system - using SystemContainer = std::unordered_map>; - + using SystemContainer = std::map>; + using SystemRoutineContainer = std::vector>; private: static SystemContainer systemContainer; - + static SystemRoutineContainer systemRoutineContainer; public: /*!************************************************************************* @@ -43,45 +47,82 @@ namespace SHADE ~SHSystemManager() = delete; /************************************************************************** - * \brief + * \brief * Create a system of type T and map it to a name. * throws an error if a system with the same name already exists. * \param name * name of the system - * \return + * \return * none ***************************************************************************/ template - static std::enable_if_t, void> CreateSystem(std::string const& name) + static std::enable_if_t, SystemID> CreateSystem() { - if (systemContainer.find(name) != systemContainer.end()) + SystemTypeID typeID = SystemFamily::GetID(); + + SystemVersionID version = 0; + SystemID id = ((SystemID)version << sizeof(SystemVersionID) * CHAR_BIT) + typeID; + while (systemContainer.find(id) != systemContainer.end()) { - assert("System Creation Error: System with the same name already exist."); + ++version; + id = ((SystemID)version << sizeof(SystemVersionID) * CHAR_BIT) + typeID; } + systemContainer.emplace(id, std::make_unique()); - systemContainer.emplace(name, std::make_unique()); + auto size = systemContainer.size(); + systemContainer[id].get()->systemID = id; + + return id; } /************************************************************************** - * \brief + * \brief * Get a pointer to the System with a specified name. * \param name * Name of the system in the map - * \return + * \return * Base System pointer. ***************************************************************************/ - static SHSystem* GetSystem(std::string name); + template + static std::enable_if_t, T*> GetSystem(SystemVersionID version = 0) + { + SystemTypeID typeID = SystemFamily::GetID(); + + SystemID id = ((SystemID)version << sizeof(SystemVersionID) * CHAR_BIT) + typeID; + + if (systemContainer.find(id) == systemContainer.end()) + { + std::cout << "System Manager error: System Version " << version << " does not exit." << std::endl; + return nullptr; + } + + return (T*)systemContainer.find(id)->second.get(); + } /************************************************************************** - * \brief + * \brief * Call the Init function of all systems. - * \return + * \return * none ***************************************************************************/ static void Init() noexcept; + static void RunRoutines(bool editorPause, double deltaTime) noexcept; + + template + static void RegisterRoutine(SystemVersionID version = 0) noexcept + { + SHSystem* system = GetSystem(version); + if (system == nullptr) + return; + systemRoutineContainer.emplace_back(std::make_unique()); + systemRoutineContainer.back().get()->system = system; + + } + + /************************************************************************** * \brief * Call the Exit function of all systems. @@ -95,8 +136,4 @@ namespace SHADE }; -} - - - -#endif +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/SHECSMacros.h b/SHADE_Engine/src/ECS_Base/SHECSMacros.h index 5243e20f..4690099f 100644 --- a/SHADE_Engine/src/ECS_Base/SHECSMacros.h +++ b/SHADE_Engine/src/ECS_Base/SHECSMacros.h @@ -9,6 +9,10 @@ typedef uint32_t EntityID; typedef uint16_t EntityIndex; typedef uint32_t ComponentTypeID; +typedef uint32_t SystemTypeID; +typedef uint32_t SystemVersionID; +typedef uint64_t SystemID; + const EntityIndex MAX_EID = 51000; @@ -22,4 +26,6 @@ const EntityIndex MAX_EID = 51000; #define ENABLE_IF_UINT(_TYPE, _RETURN)\ typename std::enable_if<(std::is_integral<_TYPE>::value && !std::is_signed<_TYPE>::value),_RETURN>::type + + #endif \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.cpp b/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.cpp new file mode 100644 index 00000000..fa34a5cf --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.cpp @@ -0,0 +1,21 @@ +#include "SHpch.h" +#include "SHFixedSystemRoutine.h" +#include "../SHECSMacros.h" + +namespace SHADE +{ + + void SHFixedSystemRoutine::Execute(double dt) noexcept + { + accumulatedTime += dt; + int counter = 0; + while (accumulatedTime >= fixedTimeStep) + { + ++counter; + accumulatedTime -= fixedTimeStep; + FixedExecute(fixedTimeStep); + } + stats.numSteps = counter; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.h b/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.h new file mode 100644 index 00000000..d54d9441 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/System/SHFixedSystemRoutine.h @@ -0,0 +1,27 @@ +#pragma once + + +#include "SHSystemRoutine.h" +#define DEFAULT_FIXED_STEP 1.0/60.0 + +namespace SHADE +{ + class SHFixedSystemRoutine: public SHSystemRoutine + { + protected: + double accumulatedTime; + double fixedTimeStep; + + SHFixedSystemRoutine(double timeStep = DEFAULT_FIXED_STEP, std::string routineName = "Default Fixed Routine Name", bool editorPause = false) + :SHSystemRoutine(routineName, editorPause), accumulatedTime(0.0), fixedTimeStep(timeStep){} + + public: + ~SHFixedSystemRoutine() = default; + + virtual void Execute(double dt) noexcept override; + + virtual void FixedExecute(double dt) noexcept {} + }; + + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHRoutineStats.h b/SHADE_Engine/src/ECS_Base/System/SHRoutineStats.h new file mode 100644 index 00000000..513358ee --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/System/SHRoutineStats.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace SHADE +{ + struct SHRoutineStats + { + SHRoutineStats(std::string name) + :name(name) + { + } + std::string name; + double executionTime; + int numSteps{1}; + + //friend std::ostream& operator<<(std::ostream& os, const SHRoutineStats& stats); + }; + + //std::ostream& operator<<(std::ostream& os, const SHRoutineStats& stats) + //{ + // os << stats.name << ": Execution Time: " << stats.executionTime << " Number of steps: " << stats.numSteps << std::endl; + // return os; + //} + + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHSystem.h b/SHADE_Engine/src/ECS_Base/System/SHSystem.h index 1a304605..fe852b9a 100644 --- a/SHADE_Engine/src/ECS_Base/System/SHSystem.h +++ b/SHADE_Engine/src/ECS_Base/System/SHSystem.h @@ -8,19 +8,31 @@ consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ -#ifndef SH_SYSTEM_H -#define SH_SYSTEM_H +#pragma once + +#include "../SHECSMacros.h" +#include "SH_API.h" +#include "ECS_Base/General/SHFamily.h" namespace SHADE { - class SHSystem + + class SHSystemManager; + + class SH_API SHSystem { + private: + SystemID systemID; + protected: /*!************************************************************************* * \brief * Protected default constructor for SHSytem class ***************************************************************************/ SHSystem()= default; + + + public: /*!************************************************************************* @@ -35,13 +47,13 @@ namespace SHADE ***************************************************************************/ virtual void Init() = 0; - /*!************************************************************************* - * \brief - * Pure virtual Run function. Derived class must implement this - * \param dt - * Delta time - ***************************************************************************/ - virtual void Run(float dt) = 0; + ///*!************************************************************************* + // * \brief + // * Pure virtual Run function. Derived class must implement this + // * \param dt + // * Delta time + //***************************************************************************/ + //virtual void Run(float dt) = 0; /*!************************************************************************* * \brief @@ -49,7 +61,18 @@ namespace SHADE ***************************************************************************/ virtual void Exit() = 0; - }; -} + friend class SHSystemManager; -#endif \ No newline at end of file + inline SystemID GetSystemID(void) const noexcept { return systemID; } + inline SystemVersionID GetSystemVersion(void) const noexcept { return static_cast(systemID >> sizeof(SystemVersionID) * CHAR_BIT); } + + + }; + + + template class SH_API SHFamilyID; + + + + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/System/SHSystemManager.cpp b/SHADE_Engine/src/ECS_Base/System/SHSystemManager.cpp deleted file mode 100644 index 67d6f781..00000000 --- a/SHADE_Engine/src/ECS_Base/System/SHSystemManager.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/********************************************************************* - * \file SHSystemManager.cpp - * \author Daniel Chua Yee Chen - * \brief Implementation for the SHSystemManager class. - * SHSystemManager is the interface class where users of the engine create - * the systems that gives the components their functionality. This also - * ensures that the Init and Exit functions are ran at the appropriate time - * - * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction - or disclosure of this file or its contents without the prior written - consent of DigiPen Institute of Technology is prohibited. - *********************************************************************/ -#include "SHpch.h" -#include "SHSystemManager.h" -#include - -namespace SHADE -{ - SHSystemManager::SystemContainer SHSystemManager::systemContainer; - - - SHSystem* SHSystemManager::GetSystem(std::string name) - { - if (systemContainer.find(name) == systemContainer.end()) - { - assert("Get System Error: No system with such name exist."); - return nullptr; - } - - return systemContainer.find(name)->second.get(); - } - - void SHSystemManager::Init() noexcept - { - for (auto& system : systemContainer) - { - system.second->Init(); -#ifdef _DEBUG - std::cout << system.first << " Init" << std::endl; -#endif - } - } - - void SHSystemManager::Exit() noexcept - { - for (auto& system : systemContainer) - { - system.second->Exit(); - //delete system.second; - } - - systemContainer.clear(); - - } - - -} diff --git a/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.cpp b/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.cpp new file mode 100644 index 00000000..73157e8b --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.cpp @@ -0,0 +1,23 @@ +#include "SHpch.h" +#include "SHSystemRoutine.h" + + +namespace SHADE +{ + + SHSystem* SHSystemRoutine::GetSystem() const noexcept + { + return system; + } + + std::string const SHSystemRoutine::GetName()const noexcept + { + return name; + } + + SHRoutineStats const& SHSystemRoutine::GetStats()const noexcept + { + return stats; + } + +} diff --git a/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.h b/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.h new file mode 100644 index 00000000..ddac950b --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/System/SHSystemRoutine.h @@ -0,0 +1,54 @@ + + + +#pragma once + +#include "../SHECSMacros.h" +#include "SHRoutineStats.h" +#include "SHSystem.h" +#include +#include "SH_API.h" + + +namespace SHADE +{ + + class SHSystemManager; + + + class SH_API SHSystemRoutine + { + friend class SHSystemManager; + protected: + + SHSystemRoutine(std::string routineName = "Default Routine Name", bool editorPause = false) + :system(nullptr), name(routineName), stats(routineName),IsRunInEditorPause(editorPause){}; + + + SHSystem* system; + std::string name; + SHRoutineStats stats; + + //Whether or not this routine should run when the editor is still in pause + bool IsRunInEditorPause; + + + public: + ~SHSystemRoutine() = default; + + + SHSystem* GetSystem()const noexcept; + std::string const GetName() const noexcept; + SHRoutineStats const& GetStats()const noexcept; + + virtual void Execute(double dt) noexcept { (void)dt; }; + }; + + + + +} + + + + diff --git a/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.cpp b/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.cpp new file mode 100644 index 00000000..050d0c2e --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.cpp @@ -0,0 +1,181 @@ +#include "SHpch.h" +#include "SHECSUnitTest.h" +#include "../Managers/SHComponentManager.h" +#include "../Managers/SHEntityManager.h" +#include "../Managers/SHSystemManager.h" +#include "SHTestComponents.h" +#include "SHTestSystems.h" +#include "Tools/SHLogger.h" + + + +namespace SHADE +{ + void SHECSUnitTest::TestAll(void) noexcept + { + TestBasicEntityCreate(); + TestEntityCreateTemplate(); + TestEntityDestroy(); + TestSystemRoutine(); + } + + + void SHECSUnitTest::TestBasicEntityCreate(void) noexcept + { + + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + + SHLOG_INFO("Test for add and remove component") + + EntityID id1 = SHEntityManager::CreateEntity(); + SHEntityManager::CreateEntity(); + SHEntityManager::CreateEntity(); + + + SHComponentManager::AddComponent(id1); + } + + + void SHECSUnitTest::TestEntityCreateTemplate(void) noexcept + { + std::cout << "\nTest2" << std::endl; + //Test entity Creation. + + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + + + + for (size_t i = 0; i < 10000; ++i) + { + switch (i % 3) + { + case 0: + { + SHEntityManager::CreateEntity(); + }break; + case 1: + { + SHEntityManager::CreateEntity(); + }break; + + case 2: + { + SHEntityManager::CreateEntity(); + }break; + default: + break; + } + + + } + + + auto& denseA = SHComponentManager::GetDense(); + auto& denseB = SHComponentManager::GetDense(); + auto& denseC = SHComponentManager::GetDense(); + + std::cout << "Test Entity Creation" << std::endl; + std::cout << "dense A size: " << denseA.size() << ((denseA.size() == 10000) ? " Success" : " Failure") << std::endl; + std::cout << "dense B size: " << denseB.size() << ((denseB.size() == 3334) ? " Success" : " Failure") << std::endl; + std::cout << "dense C size: " << denseC.size() << ((denseC.size() == 3333) ? " Success" : " Failure") << std::endl; + std::cout << "Number of entities: " << SHEntityManager::GetEntityCount() << (SHEntityManager::GetEntityCount() == 10000 ? " Success" : " Failure") << std::endl; + + SHEntityManager::DestroyAllEntity(); + std::cout << std::endl << "Test Destroy All Entity" << std::endl; + std::cout << "dense A size: " << denseA.size() << ((denseA.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "dense B size: " << denseB.size() << ((denseB.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "dense C size: " << denseC.size() << ((denseC.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "Number of entities: " << SHEntityManager::GetEntityCount() << (SHEntityManager::GetEntityCount() == 0 ? " Success" : " Failure") << std::endl; + + + } + + + void SHECSUnitTest::TestEntityDestroy(void) noexcept + { + std::cout << "\nTest3" << std::endl; + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + SHComponentManager::CreateComponentSparseSet(); + + + + for (size_t i = 0; i < 10000; ++i) + { + switch (i % 3) + { + case 0: + { + SHEntityManager::CreateEntity(); + }break; + case 1: + { + SHEntityManager::CreateEntity(); + }break; + + case 2: + { + SHEntityManager::CreateEntity(); + }break; + default: + break; + } + + + } + + SHEntityManager::DestroyEntity(5000); + SHEntityManager::DestroyEntity(5001); + + + + + auto& denseA = SHComponentManager::GetDense(); + auto& denseB = SHComponentManager::GetDense(); + auto& denseC = SHComponentManager::GetDense(); + + + std::cout << "Test Entity Deletion" << std::endl; + std::cout << "dense A size: " << denseA.size() << ((denseA.size() == 9998) ? " Success" : " Failure") << std::endl; + std::cout << "dense B size: " << denseB.size() << ((denseB.size() == 3333) ? " Success" : " Failure") << std::endl; + std::cout << "dense C size: " << denseC.size() << ((denseC.size() == 3333) ? " Success" : " Failure") << std::endl; + std::cout << "Number of entities: " << SHEntityManager::GetEntityCount() << (SHEntityManager::GetEntityCount() == 9998 ? " Success" : " Failure") << std::endl; + + + std::cout << std::endl << "Test Entity Recreation" << std::endl; + EntityID id = SHEntityManager::CreateEntity(); + std::cout << "dense A size: " << denseA.size() << ((denseA.size() == 9998) ? " Success" : " Failure") << std::endl; + std::cout << "dense B size: " << denseB.size() << ((denseB.size() == 3333) ? " Success" : " Failure") << std::endl; + std::cout << "dense C size: " << denseC.size() << ((denseC.size() == 3334) ? " Success" : " Failure") << std::endl; + std::cout << "Entity ID: " << id << " EntityIndex: " << EntityHandleGenerator::GetIndex(id) << (EntityHandleGenerator::GetIndex(id) == 5001 ? " Success" : " Failure") << std::endl; + std::cout << "Number of entities: " << SHEntityManager::GetEntityCount() << (SHEntityManager::GetEntityCount() == 9999 ? " Success" : " Failure") << std::endl; + + + SHEntityManager::DestroyAllEntity(); + std::cout << std::endl << "Check Destroy All Entity" << std::endl; + std::cout << "dense A size: " << denseA.size() << ((denseA.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "dense B size: " << denseB.size() << ((denseB.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "dense C size: " << denseC.size() << ((denseC.size() == 0) ? " Success" : " Failure") << std::endl; + std::cout << "Number of entities: " << SHEntityManager::GetEntityCount() << (SHEntityManager::GetEntityCount() == 0 ? " Success" : " Failure") << std::endl; + + } + + void SHECSUnitTest::TestSystemRoutine(void) noexcept + { + SHSystemManager::CreateSystem(); + SHSystemManager::CreateSystem(); + + + SHSystemManager::RegisterRoutine(1); + + SHSystemManager::RunRoutines(false, 1.0 / 120.0); + + SHSystemManager::Exit(); + } + + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.h b/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.h new file mode 100644 index 00000000..938ba865 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/UnitTesting/SHECSUnitTest.h @@ -0,0 +1,22 @@ +#pragma once + +namespace SHADE +{ + class SHECSUnitTest + { + public: + SHECSUnitTest() = delete; + ~SHECSUnitTest() = delete; + + static void TestBasicEntityCreate(void) noexcept; + static void TestEntityCreateTemplate(void) noexcept; + static void TestEntityDestroy(void) noexcept; + static void TestSystemRoutine(void) noexcept; + + static void TestAll(void) noexcept; + + + + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestComponents.h b/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestComponents.h new file mode 100644 index 00000000..b95a0233 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestComponents.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../Components/SHComponent.h" +#include + +namespace SHADE +{ + + class SHComponent_A :public SHComponent + { + public: + int value{}; + + }; + + + class SHComponent_B :public SHComponent + { + public: + float x{}; + float y{}; + float z{}; + }; + + class SHComponent_C :public SHComponent + { + public: + std::string value{}; + }; + + class SHComponent_ENUM : public SHComponent + { + public: + enum class Option + { + OPT_A, + OPT_B, + OPT_C + }; + + bool boolTest{}; + int intTest{}; + float floatTest{}; + double doubleTest{}; + long longTest{}; + uint8_t uint8Test{}; + uint16_t uint16Test{}; + uint32_t uint32Test{}; + uint64_t uint64Test{}; + + + Option option; + RTTR_ENABLE() + }; + + RTTR_REGISTRATION + { + using namespace rttr; + registration::enumeration("Option") + ( + value("Option A", SHComponent_ENUM::Option::OPT_A), + value("Option B", SHComponent_ENUM::Option::OPT_B), + value("Option C", SHComponent_ENUM::Option::OPT_C) + ); + rttr::registration::class_("Enum Component") + .property("Option", &SHComponent_ENUM::option) + .property("boolTest", &SHComponent_ENUM::boolTest) + .property("intTest", &SHComponent_ENUM::intTest)( metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("floatTest", &SHComponent_ENUM::floatTest)(metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("doubleTest", &SHComponent_ENUM::doubleTest)(metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("uint8Test", &SHComponent_ENUM::uint8Test)(metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("uint16Test", &SHComponent_ENUM::uint16Test)(metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("uint32Test", &SHComponent_ENUM::uint32Test)(metadata("MIN", 0.0f), metadata("MAX", 1.f)) + .property("uint64Test", &SHComponent_ENUM::uint64Test)(metadata("MIN", 0.0f), metadata("MAX", 1.f)); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestSystems.h b/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestSystems.h new file mode 100644 index 00000000..12da5e37 --- /dev/null +++ b/SHADE_Engine/src/ECS_Base/UnitTesting/SHTestSystems.h @@ -0,0 +1,40 @@ +#pragma once + + +#include +#include "../System/SHSystem.h" +#include "../System/SHSystemRoutine.h" + +namespace SHADE +{ + + class SHTestSystem : public SHSystem + { + public: + SHTestSystem() {}; + ~SHTestSystem() {}; + + + + std::string test{ "Test system" }; + + void Init() {}; + void Exit() {}; + + class SHTestRoutine : public SHSystemRoutine + { + public: + SHTestRoutine() + :SHSystemRoutine("Test System Routine", false) {} + + + virtual void Execute(double dt) noexcept + { + (void)dt; + std::cout << GetName() << " System Version: " << GetSystem()->GetSystemVersion() << std::endl; + } + }; + + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/Command/SHCommand.hpp b/SHADE_Engine/src/Editor/Command/SHCommand.hpp new file mode 100644 index 00000000..149c8986 --- /dev/null +++ b/SHADE_Engine/src/Editor/Command/SHCommand.hpp @@ -0,0 +1,72 @@ +#pragma once + +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include + +#include "SH_API.h" +#include "Scripting/SHScriptEngine.h" +#include "ECS_Base/Managers/SHSystemManager.h" + +namespace SHADE +{ + class SH_API SHBaseCommand + { + public: + virtual ~SHBaseCommand() = default; + virtual void Execute() {} + virtual void Undo() {} + virtual void Merge(std::shared_ptr) {} + };//struct SHBaseCommand + + template + class SHCommand : SHBaseCommand + { + public: + using SHCommandPtr = std::unique_ptr; + typedef std::function SetterFunction; + + SHCommand(T const& oldVal, T const& value, SetterFunction setFnc) + : oldValue(oldVal), newValue(value), set(setFnc) + { + } + + void Execute() override + { + set(newValue); + } + + void Undo() override + { + set(oldValue); + } + + void Merge(std::shared_ptr newCommand) override + { + newValue = std::reinterpret_pointer_cast(newCommand)->newValue; + } + + private: + + T oldValue; + T newValue; + SetterFunction set; + }; + + class SH_API SHCLICommand : SHBaseCommand + { + public: + SHCLICommand() = default; + void Execute() override + { + SHScriptEngine* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->RedoScriptInspectorChanges(); + } + void Undo() override + { + SHScriptEngine* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->UndoScriptInspectorChanges(); + } + }; +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/Command/SHCommandManager.cpp b/SHADE_Engine/src/Editor/Command/SHCommandManager.cpp new file mode 100644 index 00000000..b86f9247 --- /dev/null +++ b/SHADE_Engine/src/Editor/Command/SHCommandManager.cpp @@ -0,0 +1,111 @@ +//#==============================================================# +//|| PCH Include || +//#==============================================================# +#include "SHpch.h" + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "SHCommandManager.h" + +namespace SHADE +{ + + SHCommandManager::CommandStack SHCommandManager::undoStack(defaultStackSize); + SHCommandManager::CommandStack SHCommandManager::redoStack(defaultStackSize); + SHCommandManager::CommandStack SHCommandManager::secondaryUndoStack(defaultStackSize); + SHCommandManager::CommandStack SHCommandManager::secondaryRedoStack(defaultStackSize); + + SHCommandManager::CommandStackPtr SHCommandManager::pCurrUndoStack(&undoStack); + SHCommandManager::CommandStackPtr SHCommandManager::pCurrRedoStack(&redoStack); + + void SHCommandManager::PerformCommand(BaseCommandPtr commandPtr, bool const& overrideValue) + { + *pCurrRedoStack = CommandStack(defaultStackSize); + commandPtr->Execute(); + if (overrideValue && !pCurrUndoStack->Empty()) + { + pCurrUndoStack->Top()->Merge(commandPtr); + } + else + { + pCurrUndoStack->Push(commandPtr); + } + } + + void SHCommandManager::RegisterCommand(BaseCommandPtr commandPtr) + { + pCurrUndoStack->Push(commandPtr); + } + + void SHCommandManager::UndoCommand() + { + if (pCurrUndoStack->Empty()) + return; + pCurrUndoStack->Top()->Undo(); + pCurrRedoStack->Push(pCurrUndoStack->Top()); + pCurrUndoStack->Pop(); + } + + void SHCommandManager::RedoCommand() + { + if (pCurrRedoStack->Empty()) + return; + pCurrRedoStack->Top()->Execute(); + pCurrUndoStack->Push(pCurrRedoStack->Top()); + pCurrRedoStack->Pop(); + } + + std::size_t SHCommandManager::GetUndoStackSize() + { + return pCurrUndoStack->Size(); + } + + std::size_t SHCommandManager::GetRedoStackSize() + { + return pCurrRedoStack->Size(); + } + + void SHCommandManager::PopLatestCommandFromRedoStack() + { + pCurrRedoStack->Pop(); + } + + void SHCommandManager::PopLatestCommandFromUndoStack() + { + pCurrUndoStack->Pop(); + } + + void SHCommandManager::SwapStacks() + { + if (pCurrUndoStack == &undoStack) + { + pCurrUndoStack = &secondaryUndoStack; + } + else + { + secondaryUndoStack.Clear(); + pCurrUndoStack = &undoStack; + } + + if (pCurrRedoStack == &redoStack) + { + pCurrRedoStack = &secondaryRedoStack; + } + else + { + secondaryRedoStack.Clear(); + pCurrRedoStack = &redoStack; + } + } + + void SHCommandManager::ClearAll() + { + undoStack.Clear(); + redoStack.Clear(); + + secondaryUndoStack.Clear(); + secondaryRedoStack.Clear(); + } + +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/Command/SHCommandManager.h b/SHADE_Engine/src/Editor/Command/SHCommandManager.h new file mode 100644 index 00000000..178347b5 --- /dev/null +++ b/SHADE_Engine/src/Editor/Command/SHCommandManager.h @@ -0,0 +1,52 @@ +#pragma once + +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "SHCommand.hpp" +#include "SH_API.h" +#include "Tools/SHDeque.h" + +namespace SHADE +{ + class SH_API SHCommandManager + { + public: + //#==============================================================# + //|| Type Aliases || + //#==============================================================# + using BaseCommandPtr = std::shared_ptr; + template + using SHCommandPtr = std::shared_ptr>; + using CommandStack = SHDeque; + using CommandStackPtr = CommandStack*; + + static void PerformCommand(BaseCommandPtr commandPtr, bool const& overrideValue = false); + static void RegisterCommand(BaseCommandPtr commandPtr); + static void UndoCommand(); + static void RedoCommand(); + static std::size_t GetUndoStackSize(); + static std::size_t GetRedoStackSize(); + + static void PopLatestCommandFromRedoStack(); + static void PopLatestCommandFromUndoStack(); + + static void SwapStacks(); + static void ClearAll(); + + static constexpr CommandStack::SizeType defaultStackSize = 100; + private: + static CommandStackPtr pCurrUndoStack; + static CommandStackPtr pCurrRedoStack; + + static CommandStack undoStack; + static CommandStack secondaryUndoStack; + static CommandStack redoStack; + static CommandStack secondaryRedoStack; + }; +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.cpp b/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.cpp new file mode 100644 index 00000000..97fd8a22 --- /dev/null +++ b/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.cpp @@ -0,0 +1,21 @@ +#include "SHpch.h" + +#include "SHDragDrop.hpp" + +namespace SHADE +{ + bool SHDragDrop::hasDragDrop = false; + + bool SHDragDrop::BeginSource(ImGuiDragDropFlags const flags) + { return ImGui::BeginDragDropSource(flags); } + + void SHDragDrop::EndSource() + { ImGui::EndDragDropSource();} + + bool SHDragDrop::BeginTarget() + { return ImGui::BeginDragDropTarget(); } + + void SHDragDrop::EndTarget() + { ImGui::EndDragDropTarget(); hasDragDrop = false;} + +} diff --git a/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.hpp b/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.hpp new file mode 100644 index 00000000..ce0615e1 --- /dev/null +++ b/SHADE_Engine/src/Editor/DragDrop/SHDragDrop.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +namespace SHADE +{ + //TODO: Convert to RTTR? + + struct SHDragDrop + { + using DragDropTag = std::string_view; + static constexpr DragDropTag DRAG_EID = "DragEID"; + static constexpr DragDropTag DRAG_RESOURCE = "DragResource"; + static bool BeginSource(ImGuiDragDropFlags const flags = 0); + /** + * \brief Ends the DragDrop Source. ONLY CALL IF BeginSource returns true + */ + static void EndSource(); + + template + static bool SetPayload(std::string_view const type, T* object, ImGuiCond const cond = 0) + { + hasDragDrop = ImGui::SetDragDropPayload(type.data(), static_cast(object), sizeof(T), cond); + return hasDragDrop; + } + + static bool BeginTarget(); + /** + * \brief Ends the DragDrop Target. ONLY CALL IF BeginTarget returns true + */ + static void EndTarget(); + + template + static T* AcceptPayload(std::string_view const type, ImGuiDragDropFlags const flags = 0) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(type.data(), flags)) + return static_cast(payload->Data); + return nullptr; + } + + static bool hasDragDrop; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp new file mode 100644 index 00000000..889c24cc --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.cpp @@ -0,0 +1,327 @@ +#include "SHpch.h" +#include "SHAssetBrowser.h" + +#include "Editor/IconsMaterialDesign.h" +#include "Editor/SHImGuiHelpers.hpp" +#include +#include +#include + +#include "Assets/SHAssetManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/IconsFontAwesome6.h" +#include "Editor/SHEditor.h" +#include "Editor/DragDrop/SHDragDrop.hpp" +#include "Editor/EditorWindow/MaterialInspector/SHMaterialInspector.h" +#include "Editor/EditorWindow/SHEditorWindowManager.h" + +namespace SHADE +{ + SHAssetBrowser::SHAssetBrowser() + :SHEditorWindow("\xee\x8b\x87 Asset Browser", ImGuiWindowFlags_MenuBar), rootFolder(SHAssetManager::GetRootFolder()), prevFolder(rootFolder), currentFolder(rootFolder) + { + } + + void SHAssetBrowser::Init() + { + SHEditorWindow::Init(); + } + + void SHAssetBrowser::Update() + { + SHEditorWindow::Update(); + if (Begin()) + { + RecursivelyDrawTree(rootFolder); + DrawMenuBar(); + DrawCurrentFolder(); + DrawAssetBeingCreated(); + + } + ImGui::End(); + if(refreshQueued) + Refresh(); + } + + void SHAssetBrowser::QueueRefresh() noexcept + { + refreshQueued = true; + } + + void SHAssetBrowser::Refresh() noexcept + { + SHAssetManager::RefreshDirectory(); + rootFolder = SHAssetManager::GetRootFolder(); + refreshQueued = false; + } + + void SHAssetBrowser::DrawMenuBar() + { + if (ImGui::BeginMenuBar()) + { + if(ImGui::SmallButton(ICON_MD_SYNC)) + { + QueueRefresh(); + } + if(ImGui::SmallButton(ICON_FA_CIRCLE_PLUS)) + { + isAssetBeingCreated = true; + } + ImGui::EndMenuBar(); + } + } + + //if !compiled, set genMeta to true + + ImRect SHAssetBrowser::RecursivelyDrawTree(FolderPointer folder) + { + auto const& subFolders = folder->subFolders; + auto files = folder->files; + const bool isSelected = std::ranges::find(selectedFolders, folder) != selectedFolders.end(); + ImGuiTreeNodeFlags flags = (subFolders.empty() && files.empty()) ? ImGuiTreeNodeFlags_Leaf : ImGuiTreeNodeFlags_OpenOnArrow; + if (isSelected) + flags |= ImGuiTreeNodeFlags_Selected; + if (folder == rootFolder) + flags |= ImGuiTreeNodeFlags_DefaultOpen; + + bool isOpen = ImGui::TreeNodeEx(folder, flags, "%s %s", ICON_MD_FOLDER, folder->name.data()); + ImGuiID folderID = ImGui::GetItemID(); + const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + + //if (ImGui::BeginPopupContextItem()) + //{ + // ImGui::EndPopup(); + //} + + if (ImGui::IsItemClicked()) + { + selectedFolders.clear(); + selectedFolders.push_back(folder); + } + + if (isOpen) + { + const ImColor treeLineColor = ImGui::GetColorU32(ImGuiCol_CheckMark); + const float horizontalOffset = 0.0f; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 vertLineStart = ImGui::GetCursorScreenPos(); + vertLineStart.x += horizontalOffset; + ImVec2 vertLineEnd = vertLineStart; + for (auto const& subFolder : subFolders) + { + const float horizontalLineSize = 8.0f; + const ImRect childRect = RecursivelyDrawTree(subFolder); + const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f; + drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1); + vertLineEnd.y = midPoint; + } + for (auto& file : files) + { + const float horizontalLineSize = 25.0f; + const ImRect childRect = DrawFile(file); + const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f; + drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1); + vertLineEnd.y = midPoint; + } + drawList->AddLine(vertLineStart, vertLineEnd, treeLineColor, 1); + + ImGui::TreePop(); + } + return nodeRect; + } + + void SHAssetBrowser::DrawCurrentFolder() + { + //auto const& subFolders = currentFolder->subFolders; + //ImVec2 initialCursorPos = ImGui::GetCursorPos(); + //ImVec2 initialRegionAvail = ImGui::GetContentRegionAvail(); + //int maxTiles = initialRegionAvail.x / tileWidth; + //float maxX = (maxTiles - 1)*tileWidth; + //ImVec2 tilePos = initialCursorPos; + //for (auto const& subFolder : subFolders) + //{ + // ImGui::SetCursorPos(tilePos); + // ImGui::BeginGroup(); + // ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, {0.0f, 0.0f}); + // ImGui::Button(ICON_MD_FOLDER, {tileWidth}); + // ImGui::Text(subFolder->name.data()); + // ImGui::PopStyleVar(); + // ImGui::EndGroup(); + // if(tilePos.x >= maxX) + // { + // tilePos.x = initialCursorPos.x; + // } + // else + // { + // ImGui::SameLine(); + // tilePos.x += tileWidth; + // } + //} + } + + ImRect SHAssetBrowser::DrawFile(SHFile& file) noexcept + { + if(file.compilable) + { + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf; + static constexpr std::string_view icon = ICON_MD_FILE_PRESENT; + ImGui::PushID(file.name.data()); + bool const isOpen = ImGui::TreeNodeEx(file.name.data(), flags, "%s %s%s", icon.data(), file.name.data(), file.ext.data()); + const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + if(ImGui::BeginPopupContextItem()) + { + if(ImGui::Selectable("Compile")) + { + SHAssetManager::CompileAsset(file.path, !file.compiled); + QueueRefresh(); + } + ImGui::EndPopup(); + } + ImGui::TreePop(); + ImGui::PopID(); + return nodeRect; + } + if(file.assetMeta) + DrawAsset(file.assetMeta, file.ext); + } + + ImRect SHAssetBrowser::DrawAsset(SHAsset const* const asset, FileExt const& ext /*= ""*/) noexcept + { + if (asset == nullptr) + return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + const bool isSelected = std::ranges::find(selectedAssets, asset->id) != selectedAssets.end(); + ImGuiTreeNodeFlags flags = (!asset->subAssets.empty()) ? ImGuiTreeNodeFlags_OpenOnArrow : ImGuiTreeNodeFlags_Leaf; + if (isSelected) + flags |= ImGuiTreeNodeFlags_Selected; + std::string icon{}; + + switch (asset->type) + { + case AssetType::INVALID: break; + case AssetType::SHADER: icon = ICON_FA_FILE_CODE; break; + case AssetType::SHADER_BUILT_IN: icon = ICON_FA_FILE_CODE; break; + case AssetType::TEXTURE: icon = ICON_FA_IMAGES; break; + case AssetType::MODEL: icon = ICON_FA_CUBES_STACKED; break; + case AssetType::SCENE: icon = ICON_MD_IMAGE; break; + case AssetType::PREFAB: icon = ICON_FA_BOX_OPEN; break; + case AssetType::MATERIAL: break; + case AssetType::MESH: icon = ICON_FA_CUBES; break; + case AssetType::MAX_COUNT: break; + default:; + } + + bool const isOpen = ImGui::TreeNodeEx(asset, flags, "%s %s%s", icon.data(), asset->name.data(), ext.data()); + const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + if (SHDragDrop::BeginSource()) + { + auto id = asset->id; + ImGui::Text("Moving Asset: %s [%zu]", asset->name.data(), asset->id); + SHDragDrop::SetPayload(SHDragDrop::DRAG_RESOURCE, &id); + SHDragDrop::EndSource(); + } + if (ImGui::IsItemClicked()) + { + selectedAssets.clear(); + selectedAssets.push_back(asset->id); + } + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + switch (asset->type) + { + case AssetType::INVALID: break; + case AssetType::SHADER: break; + case AssetType::SHADER_BUILT_IN: break; + case AssetType::TEXTURE: break; + case AssetType::MESH: break; + case AssetType::SCENE: + if(auto editor = SHSystemManager::GetSystem()) + { + editor->LoadScene(asset->id); + } + break; + case AssetType::PREFAB: break; + case AssetType::MATERIAL: + if (auto matInspector = SHEditorWindowManager::GetEditorWindow()) + { + matInspector->OpenMaterial(asset->id); + } + break; + case AssetType::MAX_COUNT: break; + default:; + } + } + + //TODO: Combine Draw asset and Draw Folder recursive drawing + const ImColor treeLineColor = ImGui::GetColorU32(ImGuiCol_CheckMark); + const float horizontalOffset = 0.0f; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 vertLineStart = ImGui::GetCursorScreenPos(); + vertLineStart.x += horizontalOffset; + ImVec2 vertLineEnd = vertLineStart; + if(isOpen) + { + for(auto const& subAsset : asset->subAssets) + { + const float horizontalLineSize = 25.0f; + const ImRect childRect = DrawAsset(subAsset); + const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f; + drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 1); + vertLineEnd.y = midPoint; + } + drawList->AddLine(vertLineStart, vertLineEnd, treeLineColor, 1); + ImGui::TreePop(); + } + return nodeRect; + } + + void SHAssetBrowser::DrawAssetBeingCreated() noexcept + { + if(isAssetBeingCreated) + ImGui::OpenPopup(newAssetPopup.data()); + + if(ImGui::BeginPopupModal(newAssetPopup.data(), &isAssetBeingCreated)) + { + ImGui::RadioButton("Material", true); + ImGui::SameLine(); + if (ImGui::InputText("##newAssetName", &nameOfAssetBeingCreated, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll)) + { + AssetID assetId = SHAssetManager::CreateNewAsset(AssetType::MATERIAL, nameOfAssetBeingCreated); + if (auto matInspector = SHEditorWindowManager::GetEditorWindow()) + { + matInspector->OpenMaterial(assetId, true); + } + nameOfAssetBeingCreated.clear(); + QueueRefresh(); + isAssetBeingCreated = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + //if (ImGui::BeginMenu("Create Asset")) + //{ + // //TODO: Change to rttr type enum align + // if (ImGui::Selectable("Material")) + // { + // assetBeingCreated = { folder, AssetType::MATERIAL, "NewMaterial" }; + // ImGui::TreeNodeSetOpen(folderID, true); + // isOpen = true; + // } + // ImGui::EndMenu(); + //} + //if (!assetBeingCreated.has_value()) + // return; + //auto& path = std::get<0>(assetBeingCreated.value()); + //auto& type = std::get<1>(assetBeingCreated.value()); + //auto& assetName = std::get<2>(assetBeingCreated.value()); + //if (ImGui::InputText("##newAssetName", &assetName, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll)) + //{ + // AssetID assetId = SHAssetManager::CreateNewAsset(type, assetName); + // if (auto matInspector = SHEditorWindowManager::GetEditorWindow()) + // { + // matInspector->OpenMaterial(assetId, true); + // } + // assetBeingCreated.reset(); + // QueueRefresh(); + //} + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.h b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.h new file mode 100644 index 00000000..6d3c5eb4 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/AssetBrowser/SHAssetBrowser.h @@ -0,0 +1,38 @@ +#pragma once + +#include "imgui_internal.h" +#include "Assets/SHAsset.h" +#include "Editor/EditorWindow/SHEditorWindow.h" +#include "Filesystem/SHFolder.h" + +namespace SHADE +{ + class SHAssetBrowser final : public SHEditorWindow + { + public: + SHAssetBrowser(); + + void Init(); + void Update(); + + void QueueRefresh() noexcept; + private: + void DrawMenuBar(); + ImRect RecursivelyDrawTree(FolderPointer folder); + void DrawCurrentFolder(); + ImRect DrawFile(SHFile& file) noexcept; + ImRect DrawAsset(SHAsset const* const asset, FileExt const& ext = "") noexcept; + void DrawAssetBeingCreated() noexcept; + + void Refresh() noexcept; + + FolderPointer rootFolder, prevFolder, currentFolder; + std::vector selectedFolders; + std::vector selectedAssets; + static constexpr float tileWidth = 50.0f; + bool refreshQueued = false; + bool isAssetBeingCreated = false; + static constexpr std::string_view newAssetPopup = "Create New Asset"; + std::string nameOfAssetBeingCreated; + }; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp new file mode 100644 index 00000000..6be89a8b --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.cpp @@ -0,0 +1,449 @@ +//#==============================================================# +//|| PCH Include || +//#==============================================================# +#include "SHpch.h" + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Editor/SHEditor.h" +#include "Editor/SHImGuiHelpers.hpp" +#include "Editor/SHEditorWidgets.hpp" +#include "SHHierarchyPanel.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneManager.h" +#include "Editor/DragDrop/SHDragDrop.hpp" +#include "Tools/SHException.h" +#include "Editor/IconsMaterialDesign.h" +#include "SHHierarchyPanelCommands.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include + +#include "Serialization/SHSerialization.h" +#include "Tools/SHClipboardUtilities.h" + + +namespace SHADE +{ + //#==============================================================# + //|| Public Member Functions || + //#==============================================================# + SHHierarchyPanel::SHHierarchyPanel() + :SHEditorWindow("Hierarchy Panel", ImGuiWindowFlags_MenuBar) + { + } + + void SHHierarchyPanel::Init() + { + SHEditorWindow::Init(); + } + + void SHHierarchyPanel::Update() + { + SHEditorWindow::Update(); + + isAnyNodeSelected = false; + + if (Begin()) + { + if (skipFrame) + { + ImGui::End(); + skipFrame = false; + return; + } + DrawMenuBar(); + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + + if (const auto root = sceneGraph.GetRoot()) + { + auto const& children = root->GetChildren(); + + for (const auto child : children) + { + if (child) + RecursivelyDrawEntityNode(child); + if (skipFrame) + { + ImGui::End(); + return; + } + } + } + else + { + SHLOG_WARNING("Scene Graph root is null! Unable to render hierarchy.") + } + + if (ImGui::IsWindowHovered() && !SHDragDrop::hasDragDrop && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + if (auto editor = SHSystemManager::GetSystem()) + editor->selectedEntities.clear(); + } + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal); + if (ImGui::IsWindowFocused()) + { + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyReleased(ImGuiKey_A)) + { + SelectAllEntities(); + } + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyReleased(ImGuiKey_C)) + { + CopySelectedEntities(); + } + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && !ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyReleased(ImGuiKey_V)) + { + PasteEntities(); + } + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyReleased(ImGuiKey_V)) + { + const auto editor = SHSystemManager::GetSystem(); + if (editor->selectedEntities.size() == 1) + { + PasteEntities(editor->selectedEntities.back()); + } + } + if(ImGui::IsKeyReleased(ImGuiKey_Delete)) + { + DeleteSelectedEntities(); + } + } + + } + if(ImGui::IsWindowHovered() && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + if(ImGui::IsDragDropActive()) + { + ParentSelectedEntities(MAX_EID, draggingEntities); + draggingEntities.clear(); + ImGui::ClearDragDrop(); + } + } + ImGui::End(); + } + + void SHHierarchyPanel::Exit() + { + SHEditorWindow::Exit(); + } + + void SHHierarchyPanel::SetScrollTo(EntityID eid) + { + if (eid == MAX_EID) + return; + scrollTo = eid; + } + + //#==============================================================# + //|| Private Member Functions || + //#==============================================================# + void SHHierarchyPanel::DrawMenuBar() const noexcept + { + if (ImGui::BeginMenuBar()) + { + auto size = ImGui::GetWindowSize(); + auto g = ImGui::GetCurrentContext(); + ImGui::SetCursorPosX(size.x - g->Style.FramePadding.x * 15.0f); + if (ImGui::SmallButton(ICON_MD_CLEAR_ALL)) + { + auto editor = SHSystemManager::GetSystem(); + editor->selectedEntities.clear(); + } + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Clear Selections"); + ImGui::EndTooltip(); + } + if (ImGui::SmallButton(ICON_MD_ADD_CIRCLE)) + { + SHCommandManager::PerformCommand(std::make_shared()); + } + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Add Entity"); + ImGui::EndTooltip(); + } + ImGui::EndMenuBar(); + } + } + + ImRect SHHierarchyPanel::RecursivelyDrawEntityNode(SHSceneNode* const currentNode) + { + if (currentNode == nullptr) + return {}; + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + + //Get node data (Children, eid, selected) + auto& children = currentNode->GetChildren(); + EntityID eid = currentNode->GetEntityID(); + + if (scrollTo != MAX_EID && eid == scrollTo) + { + ImGui::SetScrollHereY(); + scrollTo = MAX_EID; + } + + auto editor = SHSystemManager::GetSystem(); + + const bool isSelected = (std::ranges::find(editor->selectedEntities, eid) != editor->selectedEntities.end()); + + const ImGuiTreeNodeFlags nodeFlags = ((isSelected) ? ImGuiTreeNodeFlags_Selected : 0) | ((children.empty()) ? ImGuiTreeNodeFlags_Leaf : ImGuiTreeNodeFlags_OpenOnArrow); + + //bool highlighted = false; + //if(highlighted) + //{ + // ImGui::PushStyleColor(ImGuiCol_Text, highlightedColor); + //} + + auto* entity = SHEntityManager::GetEntityByID(currentNode->GetEntityID()); + //Draw Node + bool isNodeOpen = ImGui::TreeNodeEx(reinterpret_cast(entity), nodeFlags, "%u: %s", SHEntityManager::GetEntityIndex(eid), entity->name.c_str()); + const ImRect nodeRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + + //Check For Begin Drag + if (SHDragDrop::BeginSource()) + { + std::string moveLabel = "Moving EID: "; + draggingEntities = editor->selectedEntities; + if (!isSelected) + { + draggingEntities.clear(); + draggingEntities.push_back(eid); + } + for (int i = 0; i < static_cast(draggingEntities.size()); ++i) + { + moveLabel.append(std::to_string(draggingEntities[i])); + if (i + 1 < static_cast(draggingEntities.size())) + { + moveLabel.append(", "); + } + } + ImGui::Text(moveLabel.c_str()); + SHDragDrop::SetPayload>(SHDragDrop::DRAG_EID, &draggingEntities); + SHDragDrop::EndSource(); + } + else if (SHDragDrop::BeginTarget()) //If Received DragDrop + { + if (const std::vector* eidPayload = SHDragDrop::AcceptPayload>(SHDragDrop::DRAG_EID)) //If payload is valid + { + ParentSelectedEntities(eid, draggingEntities); + draggingEntities.clear(); + SHDragDrop::EndTarget(); + } + } + + //Context menu + if (ImGui::BeginPopupContextItem(std::to_string(eid).c_str())) + { + if (!isSelected) + { + editor->selectedEntities.clear(); + editor->selectedEntities.push_back(eid); + } + if (ImGui::Selectable("Copy")) + { + CopySelectedEntities(); + } + if (ImGui::Selectable("Paste")) + { + PasteEntities(); + skipFrame = true; + ImGui::EndPopup(); + if (isNodeOpen) + ImGui::TreePop(); + return nodeRect; + } + if (ImGui::Selectable("Paste as Child")) + { + PasteEntities(eid); + skipFrame = true; + } + if (ImGui::Selectable(std::format("{} Delete selected", ICON_MD_DELETE).data())) + { + //SHEntityManager::DestroyEntity(eid); + DeleteSelectedEntities(); + } + + if ((currentNode->GetParent() != sceneGraph.GetRoot()) && ImGui::Selectable(std::format("{} Unparent Selected", ICON_MD_NORTH_WEST).data())) + { + ParentSelectedEntities(MAX_EID, editor->selectedEntities); + } + ImGui::EndPopup(); + } + + //Handle node selection + if (ImGui::IsItemHovered()) + { + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + if (!isSelected) + { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) + { + if (editor->selectedEntities.size() >= 1) + { + SelectRangeOfEntities(editor->selectedEntities[0], eid); + } + else editor->selectedEntities.clear(); + } + else + { + if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) + editor->selectedEntities.clear(); + editor->selectedEntities.push_back(eid); + } + }//if not selected + else + { + if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) + { + auto it = std::ranges::remove(editor->selectedEntities, eid).begin(); + }//if mod ctrl is not pressed + else + { + editor->selectedEntities.clear(); + editor->selectedEntities.push_back(eid); + } + }//if selected + }//if left mouse button released + }//if item hovered + + if (isNodeOpen) + { + const ImColor treeLineColor = ImGui::GetColorU32(ImGuiCol_CheckMark); + const float horizontalOffset = 0.0f; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 vertLineStart = ImGui::GetCursorScreenPos(); + vertLineStart.x += horizontalOffset; + ImVec2 vertLineEnd = vertLineStart; + + for (const auto child : children) + { + const float horizontalLineSize = 8.0f; + const ImRect childRect = RecursivelyDrawEntityNode(child); + const float midPoint = (childRect.Min.y + childRect.Max.y) * 0.5f; + drawList->AddLine(ImVec2(vertLineStart.x, midPoint), ImVec2(vertLineStart.x + horizontalLineSize, midPoint), treeLineColor, 2); + vertLineEnd.y = midPoint; + } + drawList->AddLine(vertLineStart, vertLineEnd, treeLineColor, 2); + ImGui::TreePop(); + } + return nodeRect; + } + + void SHHierarchyPanel::CreateChildEntity(EntityID parentEID) const noexcept + { + SHEntityManager::CreateEntity(MAX_EID, "DefaultChild", parentEID); + } + + void SHHierarchyPanel::ParentSelectedEntities(EntityID parentEID, std::vector const& entities) const noexcept + { + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + //auto const editor = SHSystemManager::GetSystem(); + SHEntityParentCommand::EntityParentData entityParentData; + std::vector parentedEIDS; + for (auto const& eid : entities) + { + if(eid == parentEID) + continue; + if (sceneGraph.GetChild(eid, parentEID) == nullptr) + { + parentedEIDS.push_back(eid); + if (auto parent = sceneGraph.GetParent(eid)) + entityParentData[eid].oldParentEID = parent->GetEntityID(); + entityParentData[eid].newParentEID = parentEID; + } + } + SHCommandManager::PerformCommand(std::make_shared(parentedEIDS, entityParentData)); + } + + void SHHierarchyPanel::SelectRangeOfEntities(EntityID beginEID, EntityID endEID) + { + bool startSelecting = false; bool endSelecting = false; + auto const editor = SHSystemManager::GetSystem(); + editor->selectedEntities.clear(); + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + sceneGraph.Traverse([&](SHSceneNode* nodePtr) + { + auto eid = nodePtr->GetEntityID(); + if (!startSelecting) + { + if (eid == beginEID || eid == endEID) + { + startSelecting = true; + if(std::ranges::find(editor->selectedEntities, eid) == editor->selectedEntities.end()) + editor->selectedEntities.push_back(eid); + } + } + else + { + if (!endSelecting) + { + if (std::ranges::find(editor->selectedEntities, eid) == editor->selectedEntities.end()) + editor->selectedEntities.push_back(eid); + if (eid == endEID || eid == beginEID) + { + endSelecting = true; + } + } + } + }); + } + + void SHHierarchyPanel::SelectAllEntities() + { + const auto editor = SHSystemManager::GetSystem(); + editor->selectedEntities.clear(); + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + sceneGraph.Traverse([&](SHSceneNode* nodePtr) + { + auto eid = nodePtr->GetEntityID(); + editor->selectedEntities.push_back(eid); + }); + } + + void SHHierarchyPanel::CopySelectedEntities() + { + const auto editor = SHSystemManager::GetSystem(); + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + std::vector entitiesToCopy{}; + std::ranges::copy_if(editor->selectedEntities, std::back_inserter(entitiesToCopy), [&sceneGraph](EntityID const& eid) + { + if(sceneGraph.GetParent(eid)->GetEntityID() == MAX_EID) + return true; + return false; + }); + SHClipboardUtilities::WriteToClipboard(SHSerialization::SerializeEntitiesToString(entitiesToCopy)); + } + + void SHHierarchyPanel::PasteEntities(EntityID parentEID) + { + //SetScrollTo(SHSerialization::DeserializeEntitiesFromString(SHClipboardUtilities::GetDataFromClipboard(), parentEID).front()); + SHCommandManager::PerformCommand(std::make_shared(SHClipboardUtilities::GetDataFromClipboard(), parentEID)); + } + + void SHHierarchyPanel::DeleteSelectedEntities() + { + const auto editor = SHSystemManager::GetSystem(); + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + + std::vector entitiesToDelete{}; + std::ranges::copy_if(editor->selectedEntities, std::back_inserter(entitiesToDelete), [&sceneGraph, &selectedEntities = editor->selectedEntities](EntityID const& eid) + { + EntityID parentEID = sceneGraph.GetParent(eid)->GetEntityID(); + if (parentEID == MAX_EID) + return true; + else if(std::ranges::find(selectedEntities, parentEID) == selectedEntities.end()) + return true; + return false; + }); + SHCommandManager::PerformCommand(std::make_shared(entitiesToDelete)); + } + +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.h b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.h new file mode 100644 index 00000000..66b9ca2f --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.h @@ -0,0 +1,44 @@ +#pragma once +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "imgui_internal.h" +#include "ECS_Base/SHECSMacros.h" +#include "Editor/EditorWindow/SHEditorWindow.h" +namespace SHADE +{ + class SHSceneNode; + constexpr ImVec4 highlightedColor = ImVec4(0.f, 0.7f, 0.0f, 1.0f); + + class SHHierarchyPanel final : public SHEditorWindow + { + public: + SHHierarchyPanel(); + void Init() override; + void Update() override; + void Exit() override; + void SetScrollTo(EntityID eid); + private: + void DrawMenuBar() const noexcept; + ImRect RecursivelyDrawEntityNode(SHSceneNode* const); + void CreateChildEntity(EntityID parentEID) const noexcept; + void ParentSelectedEntities(EntityID parentEID, std::vector const& entities) const noexcept; + void SelectRangeOfEntities(EntityID beginEID, EntityID EndEID); + void SelectAllEntities(); + void CopySelectedEntities(); + void PasteEntities(EntityID parentEID = MAX_EID); + void DeleteSelectedEntities(); + bool skipFrame = false; + std::string filter; + bool isAnyNodeSelected = false; + EntityID scrollTo = MAX_EID; + std::vector draggingEntities; + + };//class SHHierarchyPanel + +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.cpp b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.cpp new file mode 100644 index 00000000..78545829 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.cpp @@ -0,0 +1,84 @@ +#include "SHpch.h" +#include "SHHierarchyPanelCommands.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneManager.h" +#include "Serialization/SHSerialization.h" +#include "SHHierarchyPanel.h" +#include "Editor/EditorWindow/SHEditorWindowManager.h" + +namespace SHADE +{ + void SHCreateEntityCommand::Execute() + { + EntityID newEID = SHEntityManager::CreateEntity(eid); + if (eid == MAX_EID) + eid = newEID; + } + + void SHCreateEntityCommand::Undo() + { + SHEntityManager::DestroyEntity(eid); + } + + void SHEntityParentCommand::Execute() + { + auto& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + for (auto const& eid : entities) + { + if (entityParentData[eid].newParentEID == MAX_EID) + sceneGraph.SetParent(eid, nullptr); + else + sceneGraph.SetParent(eid, entityParentData[eid].newParentEID); + } + } + + void SHEntityParentCommand::Undo() + { + auto& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + for (auto const& eid : entities) + { + if (entityParentData[eid].oldParentEID == MAX_EID) + sceneGraph.SetParent(eid, nullptr); + else + sceneGraph.SetParent(eid, entityParentData[eid].oldParentEID); + } + } + + void SHPasteEntitiesCommand::Execute() + { + data.createdEntities.clear(); + data.createdEntities = SHSerialization::DeserializeEntitiesFromString(data.entityData, data.parentEID); + data.entityData = SHSerialization::ResolveSerializedEntityIndices(data.entityData, data.createdEntities); + SHEditorWindowManager::GetEditorWindow()->SetScrollTo(data.createdEntities.begin()->second); + } + + void SHPasteEntitiesCommand::Undo() + { + for (auto const& [oldEID, newEID] : data.createdEntities) + { + SHEntityManager::DestroyEntity(newEID); + } + } + + void SHDeleteEntitiesCommand::Execute() + { + if(!data.createdEntities.empty()) + { + for(auto& eid : data.entitiesToDelete) + { + eid = data.createdEntities[eid]; + } + } + data.entityData = SHSerialization::SerializeEntitiesToString(data.entitiesToDelete); + for (auto const& eid : data.entitiesToDelete) + { + SHEntityManager::DestroyEntity(eid); + } + } + + void SHDeleteEntitiesCommand::Undo() + { + data.createdEntities = SHSerialization::DeserializeEntitiesFromString(data.entityData); + data.entityData = SHSerialization::ResolveSerializedEntityIndices(data.entityData, data.createdEntities); + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.h b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.h new file mode 100644 index 00000000..fccd9489 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/HierarchyPanel/SHHierarchyPanelCommands.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "ECS_Base/SHECSMacros.h" +#include "Editor/Command/SHCommand.hpp" +#include "Serialization/SHSerialization.h" +namespace SHADE +{ + class SHCreateEntityCommand final : public SHBaseCommand + { + public: + void Execute() override; + void Undo() override; + private: + EntityID eid = MAX_EID; + }; + + class SHEntityParentCommand final : public SHBaseCommand + { + public: + struct Data + { + EntityID oldParentEID = MAX_EID; + EntityID newParentEID = MAX_EID; + }; + using EntityParentData = std::unordered_map; + + SHEntityParentCommand(std::vector entityIDs, EntityParentData inEntityParentData) :entities(entityIDs), entityParentData(inEntityParentData) {} + + void Execute() override; + void Undo() override; + private: + std::vector entities{}; + std::unordered_map entityParentData{}; + }; + + class SHPasteEntitiesCommand final : public SHBaseCommand + { + public: + struct Data + { + EntityID parentEID{MAX_EID}; + std::string entityData{}; + SHSerialization::CreatedEntitiesList createdEntities{}; + }; + SHPasteEntitiesCommand() = delete; + SHPasteEntitiesCommand(std::string const& serializedEntityData, EntityID parentEid = MAX_EID):data({parentEid, serializedEntityData, {}}){} + + void Execute() override; + void Undo() override; + private: + Data data; + }; + + class SHDeleteEntitiesCommand final : public SHBaseCommand + { + public: + struct Data + { + std::vector entitiesToDelete{}; + SHSerialization::CreatedEntitiesList createdEntities{}; + std::string entityData{}; + }; + SHDeleteEntitiesCommand() = delete; + SHDeleteEntitiesCommand(std::vector entitiesToBeDeleted): data{entitiesToBeDeleted}{} + void Execute() override; + void Undo() override; + private: + Data data; + }; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.h b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.h new file mode 100644 index 00000000..69f4c145 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.h @@ -0,0 +1,12 @@ +#pragma once +#include "ECS_Base/Components/SHComponent.h" + +namespace SHADE +{ + template::value, bool> = true> + static void DrawContextMenu(T* component); + template, bool> = true> + static void DrawComponent(T* component); +} + +#include "SHEditorComponentView.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp new file mode 100644 index 00000000..6091556e --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -0,0 +1,401 @@ +#pragma once + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Editor/IconsMaterialDesign.h" +#include "Editor/IconsFontAwesome6.h" +#include "ECS_Base/Components/SHComponent.h" +#include "Editor/SHEditorWidgets.hpp" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/MiddleEnd/Lights/SHLightComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Reflection/SHReflectionMetadata.h" +#include "Resource/SHResourceManager.h" + +namespace SHADE +{ + template + std::vector GetRTTREnumNames() + { + auto const rttrType = rttr::type::get(); + if (!rttrType.is_enumeration()) + return {}; + auto const enumAlign = rttrType.get_enumeration(); + auto const names = enumAlign.get_names(); + std::vector result; + std::transform(names.begin(), names.end(), std::back_inserter(result), [](rttr::string_view const& name) {return name.data(); }); + return result; + } + + template::value, bool>> + static void DrawContextMenu(T* component) + { + if (!component) + return; + rttr::string_view componentName = rttr::type::get().get_name(); + + if (ImGui::BeginPopupContextItem(componentName.data())) + { + + if (ImGui::Selectable(std::format("{} Copy {}", ICON_MD_CONTENT_COPY, componentName.data()).data())) + { + //SHClipboardUtil::WriteStringToClipboard(SHClipboardUtil::CFNAME::CFCOMPONENT, SHComponentToString(component)); + } + if (ImGui::Selectable(std::format("{} Paste {}", ICON_MD_CONTENT_PASTE, componentName.data()).data())) + { + //SHStringToComponent(component, SHClipboardUtil::ReadStringFromClipboard(SHClipboardUtil::CFNAME::CFCOMPONENT)); + } + if (ImGui::Selectable(std::format("{} Delete {}", ICON_MD_DELETE, componentName.data()).data())) + { + SHComponentManager::RemoveComponent(component->GetEID()); + } + if (ImGui::Selectable(std::format("{} Reset {}", ICON_MD_RESTART_ALT, componentName.data()).data())) + { + *component = T(); + } + ImGui::EndPopup(); + } + } + template, bool>> + static void DrawComponent(T* component) + { + if (!component) + return; + const auto componentType = rttr::type::get(); + ImGui::PushID(SHFamilyID::GetID()); + SHEditorWidgets::CheckBox("##IsActive", [component]() {return component->isActive; }, [component](bool const& active) {component->isActive = active; }, "Is Component Active"); + ImGui::PopID(); + ImGui::SameLine(); + if (ImGui::CollapsingHeader(componentType.get_name().data())) + { + DrawContextMenu(component); + auto const& properties = componentType.get_properties(); + for (auto const& property : properties) + { + auto const& type = property.get_type(); + auto tooltip = property.get_metadata(META::tooltip); + bool const& isAngleInRad = property.get_metadata(META::angleInRad).is_valid() ? property.get_metadata(META::angleInRad).template get_value() : false; + if (type.is_enumeration()) + { + auto enumAlign = type.get_enumeration(); + auto names = enumAlign.get_names(); + std::vector list; + for (auto const& name : names) + list.push_back(name.data()); + SHEditorWidgets::ComboBox(property.get_name().data(), list, [component, property] {return property.get_value(component).to_int(); }, [component, property](int const& idx) + { + auto enumAlign = property.get_enumeration(); + auto values = enumAlign.get_values(); + auto it = std::next(values.begin(), idx); + property.set_value(component, *it); + }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else if (type.is_arithmetic()) + { + if (type == rttr::type::get()) + { + SHEditorWidgets::CheckBox(property.get_name().data(), [component, property] {return property.get_value(component).to_bool(); }, [component, property](bool const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else if (type == rttr::type::get() || type == rttr::type::get() || type == rttr::type::get() || type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin && metaMax) + { + SHEditorWidgets::SliderInt(property.get_name().data(), metaMin.template get_value(), metaMax.template get_value(), [component, property] {return property.get_value(component).to_int(); }, [component, property](int const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else + { + SHEditorWidgets::DragInt(property.get_name().data(), [component, property] {return property.get_value(component).to_int(); }, [component, property](int const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderScalar(property.get_name().data(), ImGuiDataType_U8, metaMin.template get_value(), metaMax.template get_value(), [component, property] {return property.get_value(component).to_uint8(); }, [component, property](uint8_t const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string(), "%zu"); + } + else + { + SHEditorWidgets::DragScalar(property.get_name().data(), ImGuiDataType_U8, [component, property] {return property.get_value(component).to_uint8(); }, [component, property](uint8_t const& result) {property.set_value(component, result); }, 0.1f, 0, 0, "%zu", tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderScalar(property.get_name().data(), ImGuiDataType_U16, metaMin.template get_value(), metaMax.template get_value(), [component, property] {return property.get_value(component).to_uint16(); }, [component, property](uint16_t const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string(), "%zu"); + } + else + { + SHEditorWidgets::DragScalar(property.get_name().data(), ImGuiDataType_U16, [component, property] {return property.get_value(component).to_uint16(); }, [component, property](uint16_t const& result) {property.set_value(component, result); }, 0.1f, 0, 0, "%zu", tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderScalar(property.get_name().data(), ImGuiDataType_U32, metaMin.template get_value(), metaMax.template get_value(), [component, property] { return property.get_value(component).to_uint32(); }, [component, property](uint32_t const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string(), "%zu"); + } + else + { + SHEditorWidgets::DragScalar(property.get_name().data(), ImGuiDataType_U32, [component, property] { return property.get_value(component).to_uint32(); }, [component, property](uint32_t const& result) {property.set_value(component, result); }, 0.1f, 0, 0, "%zu", tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderScalar(property.get_name().data(), ImGuiDataType_U64, metaMin.template get_value(), metaMax.template get_value(), [component, property] {return property.get_value(component).to_uint64(); }, [component, property](uint64_t const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string(), "%zu"); + } + else + { + SHEditorWidgets::DragScalar(property.get_name().data(), ImGuiDataType_U64, [component, property] {return property.get_value(component).to_uint64(); }, [component, property](uint64_t const& result) {property.set_value(component, result); }, 0.1f, 0, 0, "%zu", tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + float min{}, max{}; + if (metaMin.is_valid()) + min = std::max(metaMin.template get_value(), -FLT_MAX * 0.5f); + if (metaMax.is_valid()) + max = std::min(metaMax.template get_value(), FLT_MAX * 0.5f); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderFloat(property.get_name().data(), min, max, [component, property] {return property.get_value(component).to_float(); }, [component, property](float const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else + { + SHEditorWidgets::DragFloat(property.get_name().data(), [component, property] {return property.get_value(component).to_float(); }, [component, property](float const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + else if (type == rttr::type::get()) + { + auto metaMin = property.get_metadata(META::min); + auto metaMax = property.get_metadata(META::max); + if (metaMin.is_valid() && metaMax.is_valid()) + { + SHEditorWidgets::SliderScalar(property.get_name().data(), ImGuiDataType_Double, metaMin.template get_value(), metaMax.template get_value(), [component, property] {return property.get_value(component).to_double(); }, [component, property](double const& result) {property.set_value(component, result); }, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else + { + SHEditorWidgets::DragScalar(property.get_name().data(), ImGuiDataType_Double, [component, property] {return property.get_value(component).to_double(); }, [component, property](double const& result) {property.set_value(component, result); }, 0.1f, {}, {}, "%.3f", tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + } + } + else if (type == rttr::type::get()) + { + SHEditorWidgets::DragVec4(property.get_name().data(), { "X", "Y", "Z", "W" }, [component, property]() {return property.get_value(component).template convert(); }, [component, property](SHVec4 vec) {return property.set_value(component, vec); }, isAngleInRad, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else if (type == rttr::type::get()) + { + SHEditorWidgets::DragVec3(property.get_name().data(), { "X", "Y", "Z" }, [component, property]() {return property.get_value(component).template convert(); }, [component, property](SHVec3 vec) {return property.set_value(component, vec); }, isAngleInRad, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + else if (type == rttr::type::get()) + { + SHEditorWidgets::DragVec2(property.get_name().data(), { "X", "Y" }, [component, property]() {return property.get_value(component).template convert(); }, [component, property](SHVec2 vec) {return property.set_value(component, vec); }, isAngleInRad, tooltip.is_valid() ? tooltip.template get_value() : std::string()); + } + + } + } + else DrawContextMenu(component); + } + + template<> + static void DrawComponent(SHColliderComponent* component) + { + if (!component) + return; + ImGui::PushID(component); + const auto componentType = rttr::type::get(*component); + SHEditorWidgets::CheckBox("##IsActive", [component]() {return component->isActive; }, [component](bool const& active) {component->isActive = active; }, "Is Component Active"); + ImGui::SameLine(); + if (ImGui::CollapsingHeader(componentType.get_name().data())) + { + DrawContextMenu(component); + + auto& colliders = component->GetCollisionShapes(); + int const size = static_cast(colliders.size()); + ImGui::BeginChild("Collision Shapes", { 0.0f, colliders.empty() ? 1.0f : 250.0f }, true); + std::optional colliderToDelete{ std::nullopt }; + for (int i{}; i < size; ++i) + { + ImGui::PushID(i); + SHCollisionShape* collider = &component->GetCollisionShape(i); + auto cursorPos = ImGui::GetCursorPos(); + + //collider->IsTrigger + + if (collider->GetType() == SHCollisionShape::Type::BOX) + { + SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); + const auto* BOX = reinterpret_cast(collider->GetShape()); + SHEditorWidgets::DragVec3 + ( + "Half Extents", { "X", "Y", "Z" }, + [BOX] { return BOX->GetRelativeExtents(); }, + [collider](SHVec3 const& vec) { collider->SetBoundingBox(vec); }); + } + else if (collider->GetType() == SHCollisionShape::Type::SPHERE) + { + SHEditorWidgets::BeginPanel(std::format("{} Sphere #{}", ICON_MD_CIRCLE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); + const auto* SPHERE = reinterpret_cast(collider->GetShape()); + SHEditorWidgets::DragFloat + ( + "Radius", + [SPHERE] { return SPHERE->GetRelativeRadius(); }, + [collider](float const& value) { collider->SetBoundingSphere(value); }); + } + else if (collider->GetType() == SHCollisionShape::Type::CAPSULE) + { + + } + + { + SHEditorWidgets::BeginPanel("Offsets",{ ImGui::GetContentRegionAvail().x, 30.0f }); + SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [&collider] {return collider->GetPositionOffset(); }, [&collider](SHVec3 const& vec) {collider->SetPositionOffset(vec); }); + SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, + [&collider] + { + auto offset = collider->GetRotationOffset(); + offset.x = SHMath::RadiansToDegrees(offset.x); + offset.y = SHMath::RadiansToDegrees(offset.y); + offset.z = SHMath::RadiansToDegrees(offset.z); + return offset; + }, + [&collider](SHVec3 const& vec) + { + const SHVec3 vecInRad + { + SHMath::DegreesToRadians(vec.x) + , SHMath::DegreesToRadians(vec.y) + , SHMath::DegreesToRadians(vec.z) + }; + collider->SetRotationOffset(vecInRad); + }); + SHEditorWidgets::EndPanel(); + } + + SHEditorWidgets::CheckBox("Is Trigger", [collider] { return collider->IsTrigger(); }, [collider](bool value) { collider->SetIsTrigger(value); }); + + if (ImGui::Button(std::format("{} Remove Collider #{}", ICON_MD_REMOVE, i).data())) + { + colliderToDelete = i; + } + SHEditorWidgets::EndPanel(); + ImGui::PopID(); + } + if (colliderToDelete.has_value()) + { + component->RemoveCollider(colliderToDelete.value()); + } + ImGui::EndChild(); + + if (ImGui::BeginMenu("Add Collider")) + { + if (ImGui::Selectable("Box Collider")) + { + component->AddBoundingBox(); + } + if (ImGui::Selectable("Sphere Collider")) + { + component->AddBoundingSphere(); + } + ImGui::EndMenu(); + } + } + else DrawContextMenu(component); + ImGui::PopID(); + } + + template<> + static void DrawComponent(SHLightComponent* component) + { + if (!component) + return; + const auto componentType = rttr::type::get(*component); + SHEditorWidgets::CheckBox("##IsActive", [component]() {return component->isActive; }, [component](bool const& active) {component->isActive = active; }, "Is Component Active"); + ImGui::SameLine(); + if (ImGui::CollapsingHeader(componentType.get_name().data())) + { + DrawContextMenu(component); + + static auto const enumAlign = rttr::type::get().get_enumeration(); + static std::vector list(GetRTTREnumNames()); + + SHEditorWidgets::ComboBox("Type", list, [component] {return static_cast(component->GetType()); }, [component](int const& idx) + { + component->SetType(static_cast(idx)); + }); + SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component]() {return component->GetPosition(); }, [component](SHVec3 const& vec) {component->SetPosition(vec); }); + SHEditorWidgets::DragVec3("Direction", { "X", "Y", "Z" }, [component]() {return component->GetDirection(); }, [component](SHVec3 const& vec) {component->SetDirection(vec); }); + SHEditorWidgets::ColorPicker("Color", [component]() {return component->GetColor(); }, [component](SHVec4 const& rgba) {component->SetColor(rgba); }); + SHEditorWidgets::DragFloat("Strength", [component]() {return component->GetStrength(); }, [component](float const& value) {component->SetStrength(value); }); + } + else + { + DrawContextMenu(component); + } + } + + template<> + static void DrawComponent(SHRenderable* component) + { + if (!component) + return; + const auto componentType = rttr::type::get(*component); + SHEditorWidgets::CheckBox("##IsActive", [component]() {return component->isActive; }, [component](bool const& active) {component->isActive = active; }, "Is Component Active"); + ImGui::SameLine(); + if (ImGui::CollapsingHeader(componentType.get_name().data())) + { + DrawContextMenu(component); + Handle const& mesh = component->GetMesh(); + Handle const& mat = component->GetMaterial(); + + SHEditorWidgets::DragDropReadOnlyField("Mesh", std::to_string(SHResourceManager::GetAssetID(mesh).value_or(0)).data(), [component]() + { + Handle const& mesh = component->GetMesh(); + return SHResourceManager::GetAssetID(mesh).value_or(0); + }, + [component](AssetID const& id) + { + component->SetMesh(SHResourceManager::LoadOrGet(id)); + SHResourceManager::FinaliseChanges(); + }, SHDragDrop::DRAG_RESOURCE); + + SHEditorWidgets::DragDropReadOnlyField("Material", mat ? std::to_string(SHResourceManager::GetAssetID(mat->GetBaseMaterial()).value_or(0)).data() : "", [component]() + { + Handle const& mat = component->GetMaterial(); + if(!mat) + return static_cast(0); + return SHResourceManager::GetAssetID(mat->GetBaseMaterial()).value_or(0); + }, + [component](AssetID const& id) + { + auto gfxSystem = SHSystemManager::GetSystem(); + component->SetMaterial(gfxSystem->AddOrGetBaseMaterialInstance(SHResourceManager::LoadOrGet(id))); + }, SHDragDrop::DRAG_RESOURCE); + } + else + { + DrawContextMenu(component); + } + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp new file mode 100644 index 00000000..f240e321 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp @@ -0,0 +1,166 @@ +#include "SHpch.h" + +#include "Editor/SHEditor.h" +#include "SHEditorInspector.h" + +#include "ECS_Base/SHECSMacros.h" +#include "ECS_Base/Entity/SHEntity.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Math/Transform/SHTransformComponent.h" + +#include "Editor/SHImGuiHelpers.hpp" +#include "Editor/SHEditorWidgets.hpp" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Scripting/SHScriptEngine.h" +#include "ECS_Base/Managers/SHSystemManager.h" + +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Camera/SHCameraComponent.h" +#include "Camera/SHCameraArmComponent.h" +#include "SHEditorComponentView.h" + +namespace SHADE +{ + template, bool> = true> + bool DrawAddComponentButton(EntityID const& eid) + { + bool selected = false; + if(!SHComponentManager::HasComponent(eid)) + { + const char* componentName = rttr::type::get().get_name().data(); + if(selected = ImGui::Selectable(std::format("Add {}", componentName).data()); selected) + SHComponentManager::AddComponent(eid); + if(ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Adds", componentName); ImGui::SameLine(); + ImGui::TextColored(ImGuiColors::green, "%s", componentName); ImGui::SameLine(); + ImGui::Text("to this entity", componentName); + ImGui::EndTooltip(); + } + } + return selected; + } + + template , bool> = true, std::enable_if_t, bool> = true> + bool DrawAddComponentWithEnforcedComponentButton(EntityID const& eid) + { + bool selected = false; + if (!SHComponentManager::HasComponent(eid)) + { + const char* componentName = rttr::type::get().get_name().data(); + + if(selected = ImGui::Selectable(std::format("Add {}", componentName).data()); selected) + { + if(SHComponentManager::GetComponent_s(eid) == nullptr) + SHComponentManager::AddComponent(eid); + + SHComponentManager::AddComponent(eid); + } + if(ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Adds", componentName); ImGui::SameLine(); + ImGui::TextColored(ImGuiColors::green, "%s", componentName); ImGui::SameLine(); + ImGui::Text("to this entity", componentName); + ImGui::Text("Adds"); ImGui::SameLine(); + ImGui::TextColored(ImGuiColors::red, "%s", rttr::type::get().get_name().data()); ImGui::SameLine(); + ImGui::Text("if the entity does not already have it"); + ImGui::EndTooltip(); + } + } + return selected; + } + + SHEditorInspector::SHEditorInspector() + :SHEditorWindow("Inspector", ImGuiWindowFlags_MenuBar) + { + } + + void SHEditorInspector::Init() + { + SHEditorWindow::Init(); + } + + void SHEditorInspector::Update() + { + SHEditorWindow::Update(); + if (Begin()) + { + auto editor = SHSystemManager::GetSystem(); + if (editor && !editor->selectedEntities.empty()) + { + EntityID const& eid = editor->selectedEntities[0]; + SHEntity* entity = SHEntityManager::GetEntityByID(eid); + SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(eid); + if(!entity || !entityNode) + { + ImGui::End(); + return; + } + ImGui::TextColored(ImGuiColors::green, "EID: %zu", eid); + SHEditorWidgets::CheckBox("##IsActive", [entityNode]()->bool {return entityNode->IsActive(); }, [entityNode](bool const& active) {entityNode->SetActive(active); }); + ImGui::SameLine(); + + ImGui::InputText("##EntityName", &entity->name); + + if (auto transformComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(transformComponent); + } + if(auto renderableComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(renderableComponent); + } + if(auto colliderComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(colliderComponent); + } + if(auto rigidbodyComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(rigidbodyComponent); + } + if(auto lightComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(lightComponent); + } + if (auto cameraComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(cameraComponent); + }if (auto cameraArmComponent = SHComponentManager::GetComponent_s(eid)) + { + DrawComponent(cameraArmComponent); + } + ImGui::Separator(); + // Render Scripts + SHScriptEngine* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->RenderScriptsInInspector(eid); + ImGui::Separator(); + if(ImGui::BeginMenu(std::format("{} Add Component", ICON_MD_LIBRARY_ADD).data())) + { + DrawAddComponentButton(eid); + DrawAddComponentButton(eid); + DrawAddComponentButton(eid); + DrawAddComponentButton(eid); + + // Components that require Transforms + + DrawAddComponentWithEnforcedComponentButton(eid); + DrawAddComponentWithEnforcedComponentButton(eid); + DrawAddComponentWithEnforcedComponentButton(eid); + + + ImGui::EndMenu(); + } + + } + } + ImGui::End(); + } + + void SHEditorInspector::Exit() + { + SHEditorWindow::Exit(); + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.h b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.h new file mode 100644 index 00000000..06676beb --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.h @@ -0,0 +1,30 @@ +#pragma once + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Editor/EditorWindow/SHEditorWindow.h" + + +namespace SHADE +{ + class SHComponent; + + class SHEditorInspector final : public SHEditorWindow + { + public: + SHEditorInspector(); + void Init() override; + void Update() override; + void Exit() override; + + private: + + }; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.cpp b/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.cpp new file mode 100644 index 00000000..13ecb9fa --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.cpp @@ -0,0 +1,257 @@ +#include "SHpch.h" +#include "Serialization/SHSerializationHelper.hpp" +#include "SHMaterialInspector.h" +#include "Editor/SHImGuiHelpers.hpp" +#include + +#include "Assets/SHAssetManager.h" +#include "Editor/IconsMaterialDesign.h" +#include "Editor/SHEditorWidgets.hpp" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Resource/SHResourceManager.h" + +namespace SHADE +{ + SHMaterialInspector::SHMaterialInspector() + :SHEditorWindow("Material Inspector", ImGuiWindowFlags_MenuBar), isDirty(false), isNewMaterial(false), currentViewedMaterial(0) + { + } + + void SHMaterialInspector::OpenMaterial(AssetID const& assetId, bool isNew) noexcept + { + //Get mat data + if(isDirty) + return; + isDirty = isNew; + isOpen = true; + SetFocusToWindow(); + currentViewedMaterial = assetId; + + //currentMatSpec = //Get mat spec + + currentMatSpec = SHResourceManager::LoadOrGet(assetId); + currentMaterial = SHResourceManager::LoadOrGet(assetId); + } + + void SHMaterialInspector::Init() + { + SHEditorWindow::Init(); + } + + void SHMaterialInspector::Update() + { + SHEditorWindow::Update(); + + if (Begin()) + { + if(currentViewedMaterial) + { + DrawMenuBar(); + + //if (SHEditorWidgets::DragDropReadOnlyField("Vertex Shader", std::to_string(currentMatSpec->vertexShader), [&]() {return currentMatSpec->vertexShader; }, [&](AssetID const& id) {currentMatSpec->vertexShader = id; }, SHDragDrop::DRAG_RESOURCE)) + //{ + // isDirty = true; + // vertShaderHandle = SHResourceManager::LoadOrGet(currentMatSpec->vertexShader); + //} + //if (SHEditorWidgets::DragDropReadOnlyField("Fragment Shader", std::to_string(currentMatSpec->fragShader), [&]() {return currentMatSpec->fragShader; }, [&](AssetID const& id) {currentMatSpec->fragShader = id; }, SHDragDrop::DRAG_RESOURCE)) + //{ + // isDirty = true; + // fragShaderHandle = SHResourceManager::LoadOrGet(currentMatSpec->fragShader); + //} + + DrawShaderProperties(/*fragShaderHandle*/); + } + + } + ImGui::End(); + } + + void SHMaterialInspector::Exit() + { + SHEditorWindow::Exit(); + } + + void SHMaterialInspector::DrawMenuBar() + { + if (ImGui::BeginMenuBar()) + { + ImGui::BeginDisabled(!isDirty); + if(ImGui::Button(std::format("{} Save", ICON_MD_SAVE).data())) + { + //save + if(auto matAsset = SHAssetManager::GetData(currentViewedMaterial)) + { + YAML::Emitter out; + out << YAML::BeginSeq; + out << YAML::convert::encode(*currentMatSpec); + out << YAML::EndSeq; + matAsset->data = out.c_str(); + + Handle pipelineProperties = currentMaterial->GetShaderBlockInterface(); + for (int i = 0; i < static_cast(pipelineProperties->GetVariableCount()); ++i) + { + const std::string& PROP_NAME = pipelineProperties->GetVariableName(i); + const YAML::Node& PROP_NODE = currentMatSpec->properties[PROP_NAME.data()]; + if (PROP_NODE.IsDefined()) + { + const std::string& VAR_NAME = pipelineProperties->GetVariableName(i); + const SHShaderBlockInterface::Variable* VARIABLE = pipelineProperties->GetVariable(i); + switch (VARIABLE->type) + { + case SHADE::SHShaderBlockInterface::Variable::Type::FLOAT: + currentMaterial->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::INT: + currentMaterial->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR2: + currentMaterial->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR3: + currentMaterial->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR4: + currentMaterial->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::OTHER: + default: + continue; + break; + } + } + } + + if(SHAssetManager::SaveAsset(currentViewedMaterial)) + { + isDirty = false; + } + } + } + ImGui::EndDisabled(); + ImGui::EndMenuBar(); + } + } + + void SHMaterialInspector::DrawShaderProperties(/*Handle shaderModule*/) + { + /*if(!shaderModule) + return;*/ + auto gfxSystem = SHSystemManager::GetSystem(); + auto interface = gfxSystem->GetDefaultMaterialInstance()->GetBaseMaterial()->GetShaderBlockInterface(); + //auto interface = shaderModule->GetReflectedData().GetDescriptorBindingInfo().GetShaderBlockInterface(SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA); + + int const varCount = static_cast(interface->GetVariableCount()); + + for (int i = 0; i < varCount; ++i) + { + auto variable = interface->GetVariable(i); + const std::string& VAR_NAME = interface->GetVariableName(i); + if(VAR_NAME.empty()) + continue; + switch (variable->type) + { + case SHShaderBlockInterface::Variable::Type::FLOAT: + isDirty |= SHEditorWidgets::DragFloat(VAR_NAME, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return 0.0f; + }, + [&](float const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + break; + case SHShaderBlockInterface::Variable::Type::INT: + isDirty |= SHEditorWidgets::DragInt(VAR_NAME, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return 0; + }, + [&](int const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + if(SHDragDrop::BeginTarget()) + { + if(AssetID* payload = SHDragDrop::AcceptPayload(SHDragDrop::DRAG_RESOURCE)) + { + currentMatSpec->properties[VAR_NAME] = *payload; + isDirty = true; + SHDragDrop::EndTarget(); + } + } + break; + case SHShaderBlockInterface::Variable::Type::VECTOR2: + isDirty |= SHEditorWidgets::DragVec2(VAR_NAME, { "X", "Y" }, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return SHVec2::Zero; + }, + [&](SHVec2 const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + break; + case SHShaderBlockInterface::Variable::Type::VECTOR3: + isDirty |= SHEditorWidgets::DragVec3(VAR_NAME, { "X", "Y", "Z" }, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return SHVec3::Zero; + }, + [&](SHVec3 const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + break; + case SHShaderBlockInterface::Variable::Type::VECTOR4: + isDirty |= SHEditorWidgets::DragVec4(VAR_NAME, { "X", "Y", "Z", "W" }, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return SHVec4::Zero; + }, + [&](SHVec4 const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + break; + case SHShaderBlockInterface::Variable::Type::OTHER: + isDirty |= SHEditorWidgets::InputText(VAR_NAME, + [&]() + { + if (currentMatSpec->properties[VAR_NAME].IsDefined()) + return currentMatSpec->properties[VAR_NAME].as(); + else + return std::string(); + }, + [&](std::string const& value) + { + currentMatSpec->properties[VAR_NAME] = value; + } + ); + default: + continue; + break; + } + } + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.h b/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.h new file mode 100644 index 00000000..79885399 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/MaterialInspector/SHMaterialInspector.h @@ -0,0 +1,33 @@ +#pragma once +#include "Assets/SHAssetMacros.h" +#include "Editor/EditorWindow/SHEditorWindow.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Resource/SHHandle.h" + + +namespace SHADE +{ + class SHMaterialInspector final : public SHEditorWindow + { + public: + SHMaterialInspector(); + ~SHMaterialInspector() = default; + + void Init() override; + void Update() override; + void Exit() override; + + void OpenMaterial(AssetID const& assetId, bool isNew = false) noexcept; + private: + void DrawMenuBar(); + void DrawShaderProperties(/*Handle shaderModule*/); + + bool isDirty; + bool isNewMaterial; + AssetID currentViewedMaterial; + Handle currentMatSpec; + Handle currentMaterial; + Handle vertShaderHandle, fragShaderHandle; + }; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp new file mode 100644 index 00000000..223f9b83 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -0,0 +1,260 @@ +#include "SHpch.h" + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Editor/SHEditorWidgets.hpp" +#include "Editor/SHEditor.h" +#include "SHEditorMenuBar.h" +#include "Editor/IconsMaterialDesign.h" +#include "Editor/Command/SHCommandManager.h" +#include "Scripting/SHScriptEngine.h" +#include "ECS_Base/Managers/SHSystemManager.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include +#include + +#include "Assets/SHAssetManager.h" +#include "Assets/Asset Types/SHSceneAsset.h" +#include "Scene/SHSceneManager.h" +#include "Serialization/SHSerialization.h" +#include "Serialization/Configurations/SHConfigurationManager.h" +#include "Editor/EditorWindow/SHEditorWindowManager.h" + +const std::string LAYOUT_FOLDER_PATH{ std::string(ASSET_ROOT) + "/Editor/Layouts" }; + +namespace SHADE +{ + constexpr ImGuiWindowFlags editorMenuBarFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + constexpr ImGuiWindowFlags dockspaceFlags = ImGuiDockNodeFlags_PassthruCentralNode; + + //#==============================================================# + //|| Public Member Functions || + //#==============================================================# + SHEditorMenuBar::SHEditorMenuBar() + :SHEditorWindow("SHEditorMenuBar", editorMenuBarFlags | ImGuiWindowFlags_NoBackground) + { + } + + void SHEditorMenuBar::Init() + { + SHEditorWindow::Init(); + for(auto const& entry : std::filesystem::directory_iterator(LAYOUT_FOLDER_PATH)) + { + layoutPaths.push_back(entry.path()); + } + } + + void SHEditorMenuBar::Update() + { + SHEditorWindow::Update(); + DrawMainMenuBar(); + DrawSecondaryBar(); + DrawStatusBar(); + } + + //#==============================================================# + //|| Private Member Functions || + //#==============================================================# + void SHEditorMenuBar::DrawMainMenuBar() noexcept + { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { ImVec2(0.0f, 0.0f) }); + if (Begin()) + { + ImGui::PopStyleVar(3); + + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if(ImGui::Selectable("New Scene")) + { + SHSystemManager::GetSystem()->NewScene(); + } + if(ImGui::Selectable("Save")) + { + SHSystemManager::GetSystem()->SaveScene(); + } + if(ImGui::Selectable("Load")) + { + //SHSystemManager::GetSystem()->LoadScene() + } + ImGui::EndMenu(); + } + if(ImGui::BeginMenu("Edit")) + { + ImGui::BeginDisabled(!SHCommandManager::GetUndoStackSize()); + if(ImGui::Button(std::format("{} Undo", ICON_MD_UNDO).data())) + { + SHCommandManager::UndoCommand(); + } + ImGui::EndDisabled(); + ImGui::BeginDisabled(!SHCommandManager::GetRedoStackSize()); + if(ImGui::Button(std::format("{} Redo", ICON_MD_REDO).data())) + { + SHCommandManager::RedoCommand(); + } + ImGui::EndDisabled(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Scripts")) + { + if (ImGui::Selectable("Generate Visual Studio Project")) + { + auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->GenerateScriptsCsProjFile(); + } + ImGui::BeginDisabled(SHSystemManager::GetSystem()->editorState != SHEditor::State::STOP); + if (ImGui::Selectable("Build Scripts - Debug")) + { + auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); + SHSerialization::SerializeSceneToFile(SHSceneManager::GetCurrentSceneAssetID()); + scriptEngine->BuildScriptAssembly(true, true); + SHSceneManager::RestartScene(SHSceneManager::GetCurrentSceneAssetID()); + } + if (ImGui::Selectable("Build Scripts - Release")) + { + auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); + SHSerialization::SerializeSceneToFile(SHSceneManager::GetCurrentSceneAssetID()); + scriptEngine->BuildScriptAssembly(false, true); + SHSceneManager::RestartScene(SHSceneManager::GetCurrentSceneAssetID()); + } + ImGui::EndDisabled(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Window")) + { + for (const auto& window : SHEditorWindowManager::editorWindows | std::views::values) + { + if (window.get() != this) + ImGui::Checkbox(window->windowName.data(), &window->isOpen); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Theme")) + { + const auto styles = rttr::type::get().get_enumeration(); + auto values = styles.get_values(); + for (auto style : values) + { + if (ImGui::Selectable(style.to_string().c_str())) + { + if (auto editor = SHSystemManager::GetSystem()) + editor->SetStyle(style.convert()); + } + } + ImGui::EndMenu(); + } + if(ImGui::BeginMenu("Layout")) + { + for(auto const& entry : layoutPaths) + { + if(ImGui::Selectable(entry.stem().string().c_str())) + { + ImGui::LoadIniSettingsFromDisk(entry.string().c_str()); + } + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Application Config")) + { + auto& appConfig = SHConfigurationManager::applicationConfig; + ImGui::InputText("Window Title", &appConfig.windowTitle); + ImGui::Checkbox("Start in Fullscreen", &appConfig.startInFullScreen); + SHEditorWidgets::DragN("Window Size", { "Width", "Height" }, { &appConfig.windowSize.x, &appConfig.windowSize.y }); + //ImGui::InputScalar("Starting Scene", ImGuiDataType_U32, &appConfig.startingSceneID); + auto sceneAsset = SHAssetManager::GetData(appConfig.startingSceneID); + + if(ImGui::BeginCombo("Starting Scne", sceneAsset ? sceneAsset->name.data() : "")) + { + auto scenes = SHAssetManager::GetAllRecordOfType(AssetType::SCENE); + for(auto const& scene : scenes) + { + if(ImGui::Selectable(scene.name.data())) + { + appConfig.startingSceneID = scene.id; + } + } + ImGui::EndCombo(); + } + if (ImGui::Button("Save")) + { + SHConfigurationManager::SaveApplicationConfig(); + } + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + + const ImGuiID dockspaceId = ImGui::GetID("DockSpace"); + ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); + ImGui::End(); + } + } + + void SHEditorMenuBar::DrawSecondaryBar() const noexcept + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + if(ImGui::BeginViewportSideBar("##SecondaryMenuBar", viewport, ImGuiDir_Up, ImGui::GetFrameHeight(), ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_MenuBar)) + { + ImGui::BeginMenuBar(); + ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x * 0.5f - 80.f); + const auto editor = SHSystemManager::GetSystem(); + ImGui::BeginDisabled(editor->editorState == SHEditor::State::PLAY); + if(ImGui::SmallButton(ICON_MD_PLAY_ARROW)) + { + if(editor->SaveScene()) + { + editor->Play(); + } + } + ImGui::EndDisabled(); + ImGui::BeginDisabled(editor->editorState == SHEditor::State::PAUSE); + if(ImGui::SmallButton(ICON_MD_PAUSE)) + { + editor->Pause(); + } + ImGui::EndDisabled(); + ImGui::BeginDisabled(editor->editorState == SHEditor::State::STOP); + if(ImGui::SmallButton(ICON_MD_STOP)) + { + editor->Stop(); + } + ImGui::EndDisabled(); + ImGui::EndMenuBar(); + } + ImGui::End(); + } + + void SHEditorMenuBar::DrawStatusBar() const noexcept + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { ImVec2(0.0f, 0.0f) }); + if (ImGui::BeginViewportSideBar("MainStatusBar", ImGui::GetMainViewport(), ImGuiDir_Down, menuBarHeight, editorMenuBarFlags)) + { + ImGui::Text("Entity count: "); + } + ImGui::End(); + ImGui::PopStyleVar(3); + + } +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.h b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.h new file mode 100644 index 00000000..e4f1d20b --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.h @@ -0,0 +1,29 @@ +#pragma once + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Editor/EditorWindow/SHEditorWindow.h" + +namespace SHADE +{ + class SHEditorMenuBar final : public SHEditorWindow + { + public: + SHEditorMenuBar(); + virtual void Init() override; + virtual void Update() override; + private: + void DrawMainMenuBar() noexcept; + void DrawSecondaryBar() const noexcept; + void DrawStatusBar() const noexcept; + float menuBarHeight = 20.0f; + std::vector layoutPaths; + };//class SHEditorMenuBar + + struct SHEditorStateChangeEvent + { + SHEditor::State previousState; + }; + +}//namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.cpp b/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.cpp new file mode 100644 index 00000000..11fcb7c2 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.cpp @@ -0,0 +1,54 @@ +#include "SHpch.h" +#include "SHEditorProfiler.h" +#include + +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/Command/SHCommandManager.h" +#include "FRC/SHFramerateController.h" + +namespace SHADE +{ + SHEditorProfiler::SHEditorProfiler() + :SHEditorWindow("Profiler", ImGuiWindowFlags_None) + { + } + + void SHEditorProfiler::Init() + { + SHEditorWindow::Init(); + } + + void SHEditorProfiler::Update() + { + SHEditorWindow::Update(); + + const float dt = static_cast(SHFrameRateController::GetRawDeltaTime()); + if(frames.size() > MaxFramesDisplayed) + { + for (size_t i = 1; i < frames.size(); i++) + { + frames[i-1] = frames[i]; + } + frames[frames.size() - 1] = dt; + } + else + { + frames.push_back(dt); + } + if(Begin()) + { + ImGui::PlotLines("DT", frames.data(), static_cast(frames.size()), 0, nullptr, 0.0f, 16.0f); + } + if(ImGui::CollapsingHeader("Command Manager")) + { + ImGui::Text("Undo: %zu", SHCommandManager::GetUndoStackSize()); + ImGui::Text("Redo: %zu", SHCommandManager::GetRedoStackSize()); + } + ImGui::End(); + } + + void SHEditorProfiler::Exit() + { + SHEditorWindow::Exit(); + } +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.h b/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.h new file mode 100644 index 00000000..3f817a53 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/Profiling/SHEditorProfiler.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Editor/EditorWindow/SHEditorWindow.h" +#include +constexpr uint32_t MaxFramesDisplayed = 100; +namespace SHADE +{ + class SHEditorProfiler final : public SHEditorWindow + { + public: + SHEditorProfiler(); + void Init() override; + void Update() override; + void Exit() override; + + private: + std::vector frames; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.cpp b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.cpp new file mode 100644 index 00000000..5f00cc37 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.cpp @@ -0,0 +1,77 @@ +//#==============================================================# +//|| PCH Include || +//#==============================================================# +#include "SHpch.h" + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "SHEditorWindow.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include + +namespace SHADE +{ +//#==============================================================# +//|| Public Member Functions || +//#==============================================================# + SHEditorWindow::SHEditorWindow(std::string_view const& name, ImGuiWindowFlags const& inFlags) + :isOpen(true), isWindowHovered(false), windowName(name), windowFlags(inFlags), io(ImGui::GetIO()) + { + } + + void SHEditorWindow::Init() + { + } + + void SHEditorWindow::Update() + { + } + + void SHEditorWindow::Exit() + { + } + + //#==============================================================# + //|| Protected Member Functions || + //#==============================================================# + bool SHEditorWindow::Begin() + { + bool result = ImGui::Begin(windowName.data(), &isOpen, windowFlags); + + auto wndSize = ImGui::GetWindowSize(); + auto contentRegionAvail = ImGui::GetContentRegionAvail(); + if( beginContentRegionAvailable.x != contentRegionAvail.x || beginContentRegionAvailable.y != contentRegionAvail.y || windowSize.x != wndSize.x || windowSize.y != wndSize.y) + { + windowSize = {wndSize.x, wndSize.y}; + beginContentRegionAvailable = {contentRegionAvail.x, contentRegionAvail.y}; + + OnResize(); + } + auto wndPos = ImGui::GetWindowPos(); + if(windowPos.x != wndPos.x || windowPos.y != wndPos.y) + { + windowPos = {wndPos.x, wndPos.y}; + OnPosChange(); + } + isWindowHovered = ImGui::IsWindowHovered(); + return result; + } + + void SHEditorWindow::OnResize() + { + } + + void SHEditorWindow::OnPosChange() + { + } + + void SHEditorWindow::SetFocusToWindow() + { + ImGui::SetWindowFocus(windowName.data()); + } +}//namespace SHADE + diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.h b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.h new file mode 100644 index 00000000..faacd8f2 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindow.h @@ -0,0 +1,43 @@ +#pragma once + +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include + +#include "Math/Vector/SHVec2.h" + +//#==============================================================# +//|| Forward Declarations || +//#==============================================================# +struct ImGuiIO; +typedef int ImGuiWindowFlags; + +namespace SHADE +{ + class SHEditorWindow + { + public: + SHEditorWindow(std::string_view const& name, ImGuiWindowFlags const& inFlags); + virtual ~SHEditorWindow() = default; + virtual void Init(); + virtual void Update(); + virtual void Exit(); + bool isOpen; + bool isWindowHovered; + std::string_view windowName; + SHVec2 windowSize; + SHVec2 windowPos; + SHVec2 viewportMousePos; + SHVec2 beginContentRegionAvailable; + protected: + virtual bool Begin(); + virtual void OnResize(); + virtual void OnPosChange(); + virtual void SetFocusToWindow(); + + ImGuiWindowFlags windowFlags = 0; + ImGuiIO& io; + + };//class SHEditorWindow +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h new file mode 100644 index 00000000..2fcde2b2 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h @@ -0,0 +1,8 @@ +#pragma once +#include "MenuBar/SHEditorMenuBar.h" //Menu Bar +#include "HierarchyPanel/SHHierarchyPanel.h" //Hierarchy Panel +#include "Inspector/SHEditorInspector.h" //Inspector +#include "Profiling/SHEditorProfiler.h" //Profiler +#include "ViewportWindow/SHEditorViewport.h" //Editor Viewport +#include "AssetBrowser/SHAssetBrowser.h" //Asset Browser +#include "MaterialInspector/SHMaterialInspector.h" //Material Inspector \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.cpp b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.cpp new file mode 100644 index 00000000..420b5414 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.cpp @@ -0,0 +1,8 @@ +#include "SHpch.h" +#include "SHEditorWindowManager.h" + +namespace SHADE +{ + SHEditorWindowManager::EditorWindowMap SHEditorWindowManager::editorWindows{}; + SHEditorWindowManager::EditorWindowID SHEditorWindowManager::windowCount{}; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.h b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.h new file mode 100644 index 00000000..9e6dd3f4 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowManager.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include "SHEditorWindow.h" +#include "Tools/SHLog.h" + +namespace SHADE +{ + class SH_API SHEditorWindowManager + { + public: + //#==============================================================# + //|| Type Aliases || + //#==============================================================# + using EditorWindowID = uint8_t; + using EditorWindowPtr = std::unique_ptr; + using EditorWindowMap = std::unordered_map; + /** + * @brief Get ID for the Editor Window Type + * + * @tparam T Type of Editor Window + * @return EditorWindowID ID of Editor Window Type + */ + template , bool> = true> + static EditorWindowID GetEditorWindowID() + { + static EditorWindowID id; + static bool idCreated = false; + if (!idCreated) + { + id = windowCount++; + idCreated = true; + } + return id; + } + + /** + * @brief Create an Editor Window + * + * @tparam T Type of Editor Window to create + */ + template , bool> = true> + static void CreateEditorWindow() + { + static bool isCreated = false; + if (!isCreated) + { + editorWindows[GetEditorWindowID()] = std::make_unique(); + isCreated = true; + } + else + { + SHLog::Warning("Attempt to create duplicate of Editor window type"); + } + } + + /** + * @brief Get pointer to the Editor Window + * + * @tparam T Type of editor window to retrieve + * @return T* Pointer to the editor window + */ + template , bool> = true> + static T* GetEditorWindow() + { + return reinterpret_cast(editorWindows[GetEditorWindowID()].get()); + } + + static EditorWindowMap editorWindows; + private: + // Number of windows; used for Editor Window ID Generation + static EditorWindowID windowCount; + // Map of Editor Windows + friend class SHEditor; + }; +} diff --git a/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.cpp b/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.cpp new file mode 100644 index 00000000..d0b32ff5 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.cpp @@ -0,0 +1,199 @@ +#include "SHpch.h" +#include "Editor/SHImGuiHelpers.hpp" +#include "SHEditorViewport.h" + +#include "ImGuizmo.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/IconsMaterialDesign.h" +#include "Editor/SHEditor.h" +#include "Editor/EditorWindow/HierarchyPanel/SHHierarchyPanel.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/MiddleEnd/Interface/SHMousePickSystem.h" +#include + +#include "Camera/SHCameraSystem.h" +#include "FRC/SHFramerateController.h" + +constexpr std::string_view windowName = "\xef\x80\x95 Viewport"; + +namespace SHADE +{ + SHEditorViewport::SHEditorViewport() + :SHEditorWindow("\xee\x90\x8b Viewport", ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoScrollbar) + { + } + + void SHEditorViewport::Init() + { + SHEditorWindow::Init(); + transformGizmo.Init(); + } + + void SHEditorViewport::Update() + { + SHEditorWindow::Update(); + auto camSystem = SHSystemManager::GetSystem(); + SHEditor* editor = SHSystemManager::GetSystem(); + + if (!editor->selectedEntities.empty()) + { + if (SHTransformComponent* transform = SHComponentManager::GetComponent_s(editor->selectedEntities.front())) + { + targetPos = transform->GetWorldPosition(); + } + else + { + targetPos = {}; + } + } + else + { + targetPos = {}; + } + if (shouldUpdateCamera || shouldUpdateCamArm) + { + camSystem->UpdateEditorCamera(SHFrameRateController::GetRawDeltaTime()); + shouldUpdateCamera = false; + } + camSystem->UpdateEditorArm(SHFrameRateController::GetRawDeltaTime(), shouldUpdateCamArm, targetPos); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + if (Begin()) + { + ImGuizmo::SetDrawlist(); + DrawMenuBar(); + auto gfxSystem = SHSystemManager::GetSystem(); + auto const& descriptorSet = gfxSystem->GetPostOffscreenRenderSystem()->GetDescriptorSetGroup()->GetVkHandle()[0]; + auto mousePos = ImGui::GetMousePos(); + beginCursorPos = ImGui::GetCursorScreenPos(); + viewportMousePos = { mousePos.x - beginCursorPos.x, mousePos.y - beginCursorPos.y }; + gfxSystem->GetMousePickSystem()->SetViewportMousePos(viewportMousePos); + ImGui::Image((ImTextureID)descriptorSet, { beginContentRegionAvailable.x, beginContentRegionAvailable.y }); + + if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Right)) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_None); + ImGui::SetCursorScreenPos(ImGui::GetMousePos()); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiColors::green); + ImGui::Text(ICON_FA_EYE); + ImGui::PopStyleColor(); + + shouldUpdateCamera = true; + } + + shouldUpdateCamArm = ImGui::IsWindowHovered() && ImGui::IsKeyDown(ImGuiKey_LeftAlt) && ImGui::IsMouseDown(ImGuiMouseButton_Left); + + if (editor->editorState != SHEditor::State::PLAY && ImGui::IsWindowFocused() && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) + { + if (ImGui::IsKeyReleased(ImGuiKey_W)) + { + transformGizmo.operation = SHTransformGizmo::Operation::TRANSLATE; + } + if (ImGui::IsKeyReleased(ImGuiKey_E)) + { + transformGizmo.operation = SHTransformGizmo::Operation::ROTATE; + } + if (ImGui::IsKeyReleased(ImGuiKey_R)) + { + transformGizmo.operation = SHTransformGizmo::Operation::SCALE; + } + } + } + ImGuizmo::SetRect(beginCursorPos.x, beginCursorPos.y, beginContentRegionAvailable.x, beginContentRegionAvailable.y); + if(editor->editorState != SHEditor::State::PLAY) + transformGizmo.Draw(); + ImGui::End(); + ImGui::PopStyleVar(); + } + + void SHEditorViewport::Exit() + { + SHEditorWindow::Exit(); + } + + void SHEditorViewport::OnResize() + { + SHEditorWindow::OnResize(); + //Get graphics system to resize swapchain image + auto gfxSystem = SHSystemManager::GetSystem(); + + //auto pos = ImGui::GetCursorPos(); + //windowCursorPos = {} + if (beginContentRegionAvailable.x == 0 || beginContentRegionAvailable.y == 0) + { + beginContentRegionAvailable = windowSize; + } + gfxSystem->PrepareResize(static_cast(beginContentRegionAvailable.x), static_cast(beginContentRegionAvailable.y)); + shouldUpdateCamera = true; + } + + void SHEditorViewport::OnPosChange() + { + SHEditorWindow::OnPosChange(); + } + + void SHEditorViewport::DrawMenuBar() noexcept + { + if (ImGui::BeginMenuBar()) + { + ImGui::BeginDisabled(ImGui::IsWindowFocused() && ImGui::IsMouseDown(ImGuiMouseButton_Right)); + bool const isTranslate = transformGizmo.operation == SHTransformGizmo::Operation::TRANSLATE; + ImGui::BeginDisabled(isTranslate); + if (isTranslate) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_CheckMark]); + if (ImGui::Button(ICON_MD_OPEN_WITH)) + { + transformGizmo.operation = SHTransformGizmo::Operation::TRANSLATE; + } + ImGui::EndDisabled(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + { + ImGui::BeginTooltip(); + ImGui::Text("Translate [Q]"); + ImGui::EndTooltip(); + } + if (isTranslate) + ImGui::PopStyleColor(); + + bool const isRotate = transformGizmo.operation == SHTransformGizmo::Operation::ROTATE; + ImGui::BeginDisabled(isRotate); + if (isRotate) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_CheckMark]); + if (ImGui::Button(ICON_MD_AUTORENEW)) + { + transformGizmo.operation = SHTransformGizmo::Operation::ROTATE; + } + ImGui::EndDisabled(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + { + ImGui::BeginTooltip(); + ImGui::Text("Rotate [W]"); + ImGui::EndTooltip(); + } + if (isRotate) + ImGui::PopStyleColor(); + + bool const isScale = transformGizmo.operation == SHTransformGizmo::Operation::SCALE; + ImGui::BeginDisabled(isScale); + if (isScale) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_CheckMark]); + if (ImGui::Button(ICON_MD_EXPAND)) + { + transformGizmo.operation = SHTransformGizmo::Operation::SCALE; + } + ImGui::EndDisabled(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + { + ImGui::BeginTooltip(); + ImGui::Text("Scale [E]"); + ImGui::EndTooltip(); + } + if (isScale) + ImGui::PopStyleColor(); + ImGui::EndDisabled(); + ImGui::EndMenuBar(); + } + } +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.h b/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.h new file mode 100644 index 00000000..8f49c514 --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/ViewportWindow/SHEditorViewport.h @@ -0,0 +1,35 @@ +#pragma once +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "imgui_internal.h" +#include "ECS_Base/SHECSMacros.h" +#include "Editor/EditorWindow/SHEditorWindow.h" +#include "Editor/Gizmos/SHTransformGizmo.h" + +namespace SHADE +{ + class SHEditorViewport final : public SHEditorWindow + { + public: + SHEditorViewport(); + void Init() override; + void Update() override; + void Exit() override; + SHTransformGizmo transformGizmo; + protected: + void OnResize() override; + void OnPosChange() override; + private: + void DrawMenuBar() noexcept; + SHVec2 beginCursorPos; + bool shouldUpdateCamera = false; + bool shouldUpdateCamArm = false; + SHVec3 targetPos; + };//class SHEditorViewport +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.cpp b/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.cpp new file mode 100644 index 00000000..deea62fc --- /dev/null +++ b/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.cpp @@ -0,0 +1,105 @@ +#include "SHpch.h" +#include "SHTransformGizmo.h" + +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/SHEditor.h" +#include "Editor/SHImGuiHelpers.hpp" +#include +#include + +#include "Camera/SHCameraSystem.h" +#include "Editor/Command/SHCommandManager.h" +#include "Editor/EditorWindow/ViewportWindow/SHEditorViewport.h" +#include "Editor/EditorWindow/SHEditorWindowManager.h" + +namespace SHADE +{ + void SHTransformGizmo::Init() + { + auto& style = ImGuizmo::GetStyle(); + style.RotationLineThickness = 2.5f; + } + + void SHTransformGizmo::Draw() + { + bool justChangedTfm = false; + if (!editorCamera) + { + auto const cameraSystem = SHSystemManager::GetSystem(); + editorCamera = cameraSystem->GetEditorCamera(); + } + auto viewportWindow = SHEditorWindowManager::GetEditorWindow(); + ImGuizmo::SetOrthographic(false); + + SHMatrix view = SHMatrix::Transpose(editorCamera->GetViewMatrix()); + SHMatrix proj = SHMatrix::Transpose(editorCamera->GetProjMatrix()); + + //Invert projection y-axis + proj(1, 1) *= -1; + + static SHMatrix gridMat = SHMatrix::Translate(0, -0.5f, 0.f) * SHMatrix::Identity; + + if (selectedEntityTransformComponent == nullptr) + { + SHEditor* editor = SHSystemManager::GetSystem(); + if (editor->selectedEntities.empty()) + return; + EntityID eid = editor->selectedEntities.back(); + selectedEntityTransformComponent = SHComponentManager::GetComponent_s(eid); + justChangedTfm = true; + } + else + { + SHEditor* editor = SHSystemManager::GetSystem(); + if (editor->selectedEntities.empty()) + return; + EntityID eid = editor->selectedEntities.back(); + auto tfmComponent = SHComponentManager::GetComponent_s(eid); + if (selectedEntityTransformComponent != tfmComponent) + { + selectedEntityTransformComponent = tfmComponent; + justChangedTfm = true; + } + } + if (selectedEntityTransformComponent == nullptr) + return; + + if(!selectedEntityTransformComponent->isActive) + return; + + SHMatrix mat = selectedEntityTransformComponent->GetTRS(); + useSnap = ImGui::IsKeyDown(ImGuiKey_LeftCtrl); + if(useSnap) + { + switch (operation) + { + case Operation::TRANSLATE: snap = &translationSnap.x; break; + case Operation::ROTATE: snap = &rotationSnap; break; + case Operation::SCALE: snap = &scaleSnap; break; + default: snap = &translationSnap.x; + } + } + ImGuizmo::Manipulate(&view._11, &proj._11, static_cast(operation), ImGuizmo::MODE::WORLD, &mat._11, nullptr, useSnap ? snap : nullptr); + static bool startRecording = false; + if (!justChangedTfm && ImGuizmo::IsUsing()) + { + + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(selectedEntityTransformComponent->GetTRS(), mat, [tfm = (selectedEntityTransformComponent)](SHMatrix const& mtx) + { + if (!tfm) + return; + SHVec3 translate{}, rotate{}, scale{}; + mtx.Decompose(translate, rotate, scale); + tfm->SetWorldPosition(translate); + tfm->SetWorldRotation(rotate); + tfm->SetWorldScale(scale); + })), startRecording); + if(!startRecording) + startRecording = true; + } + isManipulating = ImGuizmo::IsUsing() || startRecording; + if(startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + startRecording = false; + } +} diff --git a/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.h b/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.h new file mode 100644 index 00000000..fd847335 --- /dev/null +++ b/SHADE_Engine/src/Editor/Gizmos/SHTransformGizmo.h @@ -0,0 +1,54 @@ +#pragma once +#include "Camera/SHCameraComponent.h" +#include "Math/Transform/SHTransformComponent.h" + +namespace SHADE +{ + class SHTransformGizmo + { + public: + enum class Mode + { + WORLD, + LOCAL + }; + + enum class Operation + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + void Init(); + void Draw(); + bool isManipulating = false; + bool useSnap = false; + Mode mode = Mode::WORLD; + Operation operation = Operation::TRANSLATE; + private: + float scaleSnap = 0.25f; + float rotationSnap = 1.0f; + SHVec3 translationSnap = SHVec3(0.25f, 0.25f, 0.25f); + float* snap = nullptr; + SHTransformComponent* selectedEntityTransformComponent{nullptr}; + SHCameraComponent* editorCamera{nullptr}; + }; +} diff --git a/SHADE_Engine/src/Editor/IconsFontAwesome6.h b/SHADE_Engine/src/Editor/IconsFontAwesome6.h new file mode 100644 index 00000000..77831d94 --- /dev/null +++ b/SHADE_Engine/src/Editor/IconsFontAwesome6.h @@ -0,0 +1,1400 @@ +#pragma once +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++ +// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml +// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" + +#define ICON_MIN_FA 0xe005 +#define ICON_MAX_16_FA 0xf8ff +#define ICON_MAX_FA 0xf8ff +#define ICON_FA_0 "0" // U+0030 +#define ICON_FA_1 "1" // U+0031 +#define ICON_FA_2 "2" // U+0032 +#define ICON_FA_3 "3" // U+0033 +#define ICON_FA_4 "4" // U+0034 +#define ICON_FA_5 "5" // U+0035 +#define ICON_FA_6 "6" // U+0036 +#define ICON_FA_7 "7" // U+0037 +#define ICON_FA_8 "8" // U+0038 +#define ICON_FA_9 "9" // U+0039 +#define ICON_FA_A "A" // U+0041 +#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9 +#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb +#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037 +#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039 +#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036 +#define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038 +#define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d +#define ICON_FA_ANCHOR_CIRCLE_CHECK "\xee\x92\xaa" // U+e4aa +#define ICON_FA_ANCHOR_CIRCLE_EXCLAMATION "\xee\x92\xab" // U+e4ab +#define ICON_FA_ANCHOR_CIRCLE_XMARK "\xee\x92\xac" // U+e4ac +#define ICON_FA_ANCHOR_LOCK "\xee\x92\xad" // U+e4ad +#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107 +#define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104 +#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105 +#define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106 +#define ICON_FA_ANGLES_DOWN "\xef\x84\x83" // U+f103 +#define ICON_FA_ANGLES_LEFT "\xef\x84\x80" // U+f100 +#define ICON_FA_ANGLES_RIGHT "\xef\x84\x81" // U+f101 +#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102 +#define ICON_FA_ANKH "\xef\x99\x84" // U+f644 +#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1 +#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557 +#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063 +#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162 +#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886 +#define ICON_FA_ARROW_DOWN_A_Z "\xef\x85\x9d" // U+f15d +#define ICON_FA_ARROW_DOWN_LONG "\xef\x85\xb5" // U+f175 +#define ICON_FA_ARROW_DOWN_SHORT_WIDE "\xef\xa2\x84" // U+f884 +#define ICON_FA_ARROW_DOWN_UP_ACROSS_LINE "\xee\x92\xaf" // U+e4af +#define ICON_FA_ARROW_DOWN_UP_LOCK "\xee\x92\xb0" // U+e4b0 +#define ICON_FA_ARROW_DOWN_WIDE_SHORT "\xef\x85\xa0" // U+f160 +#define ICON_FA_ARROW_DOWN_Z_A "\xef\xa2\x81" // U+f881 +#define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060 +#define ICON_FA_ARROW_LEFT_LONG "\xef\x85\xb7" // U+f177 +#define ICON_FA_ARROW_POINTER "\xef\x89\x85" // U+f245 +#define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061 +#define ICON_FA_ARROW_RIGHT_ARROW_LEFT "\xef\x83\xac" // U+f0ec +#define ICON_FA_ARROW_RIGHT_FROM_BRACKET "\xef\x82\x8b" // U+f08b +#define ICON_FA_ARROW_RIGHT_LONG "\xef\x85\xb8" // U+f178 +#define ICON_FA_ARROW_RIGHT_TO_BRACKET "\xef\x82\x90" // U+f090 +#define ICON_FA_ARROW_RIGHT_TO_CITY "\xee\x92\xb3" // U+e4b3 +#define ICON_FA_ARROW_ROTATE_LEFT "\xef\x83\xa2" // U+f0e2 +#define ICON_FA_ARROW_ROTATE_RIGHT "\xef\x80\x9e" // U+f01e +#define ICON_FA_ARROW_TREND_DOWN "\xee\x82\x97" // U+e097 +#define ICON_FA_ARROW_TREND_UP "\xee\x82\x98" // U+e098 +#define ICON_FA_ARROW_TURN_DOWN "\xef\x85\x89" // U+f149 +#define ICON_FA_ARROW_TURN_UP "\xef\x85\x88" // U+f148 +#define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062 +#define ICON_FA_ARROW_UP_1_9 "\xef\x85\xa3" // U+f163 +#define ICON_FA_ARROW_UP_9_1 "\xef\xa2\x87" // U+f887 +#define ICON_FA_ARROW_UP_A_Z "\xef\x85\x9e" // U+f15e +#define ICON_FA_ARROW_UP_FROM_BRACKET "\xee\x82\x9a" // U+e09a +#define ICON_FA_ARROW_UP_FROM_GROUND_WATER "\xee\x92\xb5" // U+e4b5 +#define ICON_FA_ARROW_UP_FROM_WATER_PUMP "\xee\x92\xb6" // U+e4b6 +#define ICON_FA_ARROW_UP_LONG "\xef\x85\xb6" // U+f176 +#define ICON_FA_ARROW_UP_RIGHT_DOTS "\xee\x92\xb7" // U+e4b7 +#define ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE "\xef\x82\x8e" // U+f08e +#define ICON_FA_ARROW_UP_SHORT_WIDE "\xef\xa2\x85" // U+f885 +#define ICON_FA_ARROW_UP_WIDE_SHORT "\xef\x85\xa1" // U+f161 +#define ICON_FA_ARROW_UP_Z_A "\xef\xa2\x82" // U+f882 +#define ICON_FA_ARROWS_DOWN_TO_LINE "\xee\x92\xb8" // U+e4b8 +#define ICON_FA_ARROWS_DOWN_TO_PEOPLE "\xee\x92\xb9" // U+e4b9 +#define ICON_FA_ARROWS_LEFT_RIGHT "\xef\x81\xbe" // U+f07e +#define ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE "\xee\x92\xba" // U+e4ba +#define ICON_FA_ARROWS_ROTATE "\xef\x80\xa1" // U+f021 +#define ICON_FA_ARROWS_SPIN "\xee\x92\xbb" // U+e4bb +#define ICON_FA_ARROWS_SPLIT_UP_AND_LEFT "\xee\x92\xbc" // U+e4bc +#define ICON_FA_ARROWS_TO_CIRCLE "\xee\x92\xbd" // U+e4bd +#define ICON_FA_ARROWS_TO_DOT "\xee\x92\xbe" // U+e4be +#define ICON_FA_ARROWS_TO_EYE "\xee\x92\xbf" // U+e4bf +#define ICON_FA_ARROWS_TURN_RIGHT "\xee\x93\x80" // U+e4c0 +#define ICON_FA_ARROWS_TURN_TO_DOTS "\xee\x93\x81" // U+e4c1 +#define ICON_FA_ARROWS_UP_DOWN "\xef\x81\xbd" // U+f07d +#define ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT "\xef\x81\x87" // U+f047 +#define ICON_FA_ARROWS_UP_TO_LINE "\xee\x93\x82" // U+e4c2 +#define ICON_FA_ASTERISK "*" // U+002a +#define ICON_FA_AT "@" // U+0040 +#define ICON_FA_ATOM "\xef\x97\x92" // U+f5d2 +#define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e +#define ICON_FA_AUSTRAL_SIGN "\xee\x82\xa9" // U+e0a9 +#define ICON_FA_AWARD "\xef\x95\x99" // U+f559 +#define ICON_FA_B "B" // U+0042 +#define ICON_FA_BABY "\xef\x9d\xbc" // U+f77c +#define ICON_FA_BABY_CARRIAGE "\xef\x9d\xbd" // U+f77d +#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a +#define ICON_FA_BACKWARD_FAST "\xef\x81\x89" // U+f049 +#define ICON_FA_BACKWARD_STEP "\xef\x81\x88" // U+f048 +#define ICON_FA_BACON "\xef\x9f\xa5" // U+f7e5 +#define ICON_FA_BACTERIA "\xee\x81\x99" // U+e059 +#define ICON_FA_BACTERIUM "\xee\x81\x9a" // U+e05a +#define ICON_FA_BAG_SHOPPING "\xef\x8a\x90" // U+f290 +#define ICON_FA_BAHAI "\xef\x99\xa6" // U+f666 +#define ICON_FA_BAHT_SIGN "\xee\x82\xac" // U+e0ac +#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e +#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d +#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462 +#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a +#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9 +#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828 +#define ICON_FA_BARS_STAGGERED "\xef\x95\x90" // U+f550 +#define ICON_FA_BASEBALL "\xef\x90\xb3" // U+f433 +#define ICON_FA_BASEBALL_BAT_BALL "\xef\x90\xb2" // U+f432 +#define ICON_FA_BASKET_SHOPPING "\xef\x8a\x91" // U+f291 +#define ICON_FA_BASKETBALL "\xef\x90\xb4" // U+f434 +#define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd +#define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244 +#define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240 +#define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242 +#define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243 +#define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241 +#define ICON_FA_BED "\xef\x88\xb6" // U+f236 +#define ICON_FA_BED_PULSE "\xef\x92\x87" // U+f487 +#define ICON_FA_BEER_MUG_EMPTY "\xef\x83\xbc" // U+f0fc +#define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3 +#define ICON_FA_BELL_CONCIERGE "\xef\x95\xa2" // U+f562 +#define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6 +#define ICON_FA_BEZIER_CURVE "\xef\x95\x9b" // U+f55b +#define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206 +#define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5 +#define ICON_FA_BIOHAZARD "\xef\x9e\x80" // U+f780 +#define ICON_FA_BITCOIN_SIGN "\xee\x82\xb4" // U+e0b4 +#define ICON_FA_BLENDER "\xef\x94\x97" // U+f517 +#define ICON_FA_BLENDER_PHONE "\xef\x9a\xb6" // U+f6b6 +#define ICON_FA_BLOG "\xef\x9e\x81" // U+f781 +#define ICON_FA_BOLD "\xef\x80\xb2" // U+f032 +#define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7 +#define ICON_FA_BOLT_LIGHTNING "\xee\x82\xb7" // U+e0b7 +#define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2 +#define ICON_FA_BONE "\xef\x97\x97" // U+f5d7 +#define ICON_FA_BONG "\xef\x95\x9c" // U+f55c +#define ICON_FA_BOOK "\xef\x80\xad" // U+f02d +#define ICON_FA_BOOK_ATLAS "\xef\x95\x98" // U+f558 +#define ICON_FA_BOOK_BIBLE "\xef\x99\x87" // U+f647 +#define ICON_FA_BOOK_BOOKMARK "\xee\x82\xbb" // U+e0bb +#define ICON_FA_BOOK_JOURNAL_WHILLS "\xef\x99\xaa" // U+f66a +#define ICON_FA_BOOK_MEDICAL "\xef\x9f\xa6" // U+f7e6 +#define ICON_FA_BOOK_OPEN "\xef\x94\x98" // U+f518 +#define ICON_FA_BOOK_OPEN_READER "\xef\x97\x9a" // U+f5da +#define ICON_FA_BOOK_QURAN "\xef\x9a\x87" // U+f687 +#define ICON_FA_BOOK_SKULL "\xef\x9a\xb7" // U+f6b7 +#define ICON_FA_BOOK_TANAKH "\xef\xa0\xa7" // U+f827 +#define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e +#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+f84c +#define ICON_FA_BORDER_NONE "\xef\xa1\x90" // U+f850 +#define ICON_FA_BORDER_TOP_LEFT "\xef\xa1\x93" // U+f853 +#define ICON_FA_BORE_HOLE "\xee\x93\x83" // U+e4c3 +#define ICON_FA_BOTTLE_DROPLET "\xee\x93\x84" // U+e4c4 +#define ICON_FA_BOTTLE_WATER "\xee\x93\x85" // U+e4c5 +#define ICON_FA_BOWL_FOOD "\xee\x93\x86" // U+e4c6 +#define ICON_FA_BOWL_RICE "\xee\x8b\xab" // U+e2eb +#define ICON_FA_BOWLING_BALL "\xef\x90\xb6" // U+f436 +#define ICON_FA_BOX "\xef\x91\xa6" // U+f466 +#define ICON_FA_BOX_ARCHIVE "\xef\x86\x87" // U+f187 +#define ICON_FA_BOX_OPEN "\xef\x92\x9e" // U+f49e +#define ICON_FA_BOX_TISSUE "\xee\x81\x9b" // U+e05b +#define ICON_FA_BOXES_PACKING "\xee\x93\x87" // U+e4c7 +#define ICON_FA_BOXES_STACKED "\xef\x91\xa8" // U+f468 +#define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1 +#define ICON_FA_BRAIN "\xef\x97\x9c" // U+f5dc +#define ICON_FA_BRAZILIAN_REAL_SIGN "\xee\x91\xac" // U+e46c +#define ICON_FA_BREAD_SLICE "\xef\x9f\xac" // U+f7ec +#define ICON_FA_BRIDGE "\xee\x93\x88" // U+e4c8 +#define ICON_FA_BRIDGE_CIRCLE_CHECK "\xee\x93\x89" // U+e4c9 +#define ICON_FA_BRIDGE_CIRCLE_EXCLAMATION "\xee\x93\x8a" // U+e4ca +#define ICON_FA_BRIDGE_CIRCLE_XMARK "\xee\x93\x8b" // U+e4cb +#define ICON_FA_BRIDGE_LOCK "\xee\x93\x8c" // U+e4cc +#define ICON_FA_BRIDGE_WATER "\xee\x93\x8e" // U+e4ce +#define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1 +#define ICON_FA_BRIEFCASE_MEDICAL "\xef\x91\xa9" // U+f469 +#define ICON_FA_BROOM "\xef\x94\x9a" // U+f51a +#define ICON_FA_BROOM_BALL "\xef\x91\x98" // U+f458 +#define ICON_FA_BRUSH "\xef\x95\x9d" // U+f55d +#define ICON_FA_BUCKET "\xee\x93\x8f" // U+e4cf +#define ICON_FA_BUG "\xef\x86\x88" // U+f188 +#define ICON_FA_BUG_SLASH "\xee\x92\x90" // U+e490 +#define ICON_FA_BUGS "\xee\x93\x90" // U+e4d0 +#define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad +#define ICON_FA_BUILDING_CIRCLE_ARROW_RIGHT "\xee\x93\x91" // U+e4d1 +#define ICON_FA_BUILDING_CIRCLE_CHECK "\xee\x93\x92" // U+e4d2 +#define ICON_FA_BUILDING_CIRCLE_EXCLAMATION "\xee\x93\x93" // U+e4d3 +#define ICON_FA_BUILDING_CIRCLE_XMARK "\xee\x93\x94" // U+e4d4 +#define ICON_FA_BUILDING_COLUMNS "\xef\x86\x9c" // U+f19c +#define ICON_FA_BUILDING_FLAG "\xee\x93\x95" // U+e4d5 +#define ICON_FA_BUILDING_LOCK "\xee\x93\x96" // U+e4d6 +#define ICON_FA_BUILDING_NGO "\xee\x93\x97" // U+e4d7 +#define ICON_FA_BUILDING_SHIELD "\xee\x93\x98" // U+e4d8 +#define ICON_FA_BUILDING_UN "\xee\x93\x99" // U+e4d9 +#define ICON_FA_BUILDING_USER "\xee\x93\x9a" // U+e4da +#define ICON_FA_BUILDING_WHEAT "\xee\x93\x9b" // U+e4db +#define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1 +#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140 +#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805 +#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc +#define ICON_FA_BUS "\xef\x88\x87" // U+f207 +#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e +#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a +#define ICON_FA_C "C" // U+0043 +#define ICON_FA_CABLE_CAR "\xef\x9f\x9a" // U+f7da +#define ICON_FA_CAKE_CANDLES "\xef\x87\xbd" // U+f1fd +#define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec +#define ICON_FA_CALENDAR "\xef\x84\xb3" // U+f133 +#define ICON_FA_CALENDAR_CHECK "\xef\x89\xb4" // U+f274 +#define ICON_FA_CALENDAR_DAY "\xef\x9e\x83" // U+f783 +#define ICON_FA_CALENDAR_DAYS "\xef\x81\xb3" // U+f073 +#define ICON_FA_CALENDAR_MINUS "\xef\x89\xb2" // U+f272 +#define ICON_FA_CALENDAR_PLUS "\xef\x89\xb1" // U+f271 +#define ICON_FA_CALENDAR_WEEK "\xef\x9e\x84" // U+f784 +#define ICON_FA_CALENDAR_XMARK "\xef\x89\xb3" // U+f273 +#define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030 +#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083 +#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8 +#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb +#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786 +#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f +#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b +#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9 +#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df +#define ICON_FA_CAR_BURST "\xef\x97\xa1" // U+f5e1 +#define ICON_FA_CAR_ON "\xee\x93\x9d" // U+e4dd +#define ICON_FA_CAR_REAR "\xef\x97\x9e" // U+f5de +#define ICON_FA_CAR_SIDE "\xef\x97\xa4" // U+f5e4 +#define ICON_FA_CAR_TUNNEL "\xee\x93\x9e" // U+e4de +#define ICON_FA_CARAVAN "\xef\xa3\xbf" // U+f8ff +#define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7 +#define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9 +#define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da +#define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8 +#define ICON_FA_CARROT "\xef\x9e\x87" // U+f787 +#define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218 +#define ICON_FA_CART_FLATBED "\xef\x91\xb4" // U+f474 +#define ICON_FA_CART_FLATBED_SUITCASE "\xef\x96\x9d" // U+f59d +#define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217 +#define ICON_FA_CART_SHOPPING "\xef\x81\xba" // U+f07a +#define ICON_FA_CASH_REGISTER "\xef\x9e\x88" // U+f788 +#define ICON_FA_CAT "\xef\x9a\xbe" // U+f6be +#define ICON_FA_CEDI_SIGN "\xee\x83\x9f" // U+e0df +#define ICON_FA_CENT_SIGN "\xee\x8f\xb5" // U+e3f5 +#define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3 +#define ICON_FA_CHAIR "\xef\x9b\x80" // U+f6c0 +#define ICON_FA_CHALKBOARD "\xef\x94\x9b" // U+f51b +#define ICON_FA_CHALKBOARD_USER "\xef\x94\x9c" // U+f51c +#define ICON_FA_CHAMPAGNE_GLASSES "\xef\x9e\x9f" // U+f79f +#define ICON_FA_CHARGING_STATION "\xef\x97\xa7" // U+f5e7 +#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe +#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080 +#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3 +#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4 +#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201 +#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200 +#define ICON_FA_CHART_SIMPLE "\xee\x91\xb3" // U+e473 +#define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c +#define ICON_FA_CHECK_DOUBLE "\xef\x95\xa0" // U+f560 +#define ICON_FA_CHECK_TO_SLOT "\xef\x9d\xb2" // U+f772 +#define ICON_FA_CHEESE "\xef\x9f\xaf" // U+f7ef +#define ICON_FA_CHESS "\xef\x90\xb9" // U+f439 +#define ICON_FA_CHESS_BISHOP "\xef\x90\xba" // U+f43a +#define ICON_FA_CHESS_BOARD "\xef\x90\xbc" // U+f43c +#define ICON_FA_CHESS_KING "\xef\x90\xbf" // U+f43f +#define ICON_FA_CHESS_KNIGHT "\xef\x91\x81" // U+f441 +#define ICON_FA_CHESS_PAWN "\xef\x91\x83" // U+f443 +#define ICON_FA_CHESS_QUEEN "\xef\x91\x85" // U+f445 +#define ICON_FA_CHESS_ROOK "\xef\x91\x87" // U+f447 +#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078 +#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053 +#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054 +#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077 +#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae +#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c +#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d +#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0 +#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1 +#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d +#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111 +#define ICON_FA_CIRCLE_ARROW_DOWN "\xef\x82\xab" // U+f0ab +#define ICON_FA_CIRCLE_ARROW_LEFT "\xef\x82\xa8" // U+f0a8 +#define ICON_FA_CIRCLE_ARROW_RIGHT "\xef\x82\xa9" // U+f0a9 +#define ICON_FA_CIRCLE_ARROW_UP "\xef\x82\xaa" // U+f0aa +#define ICON_FA_CIRCLE_CHECK "\xef\x81\x98" // U+f058 +#define ICON_FA_CIRCLE_CHEVRON_DOWN "\xef\x84\xba" // U+f13a +#define ICON_FA_CIRCLE_CHEVRON_LEFT "\xef\x84\xb7" // U+f137 +#define ICON_FA_CIRCLE_CHEVRON_RIGHT "\xef\x84\xb8" // U+f138 +#define ICON_FA_CIRCLE_CHEVRON_UP "\xef\x84\xb9" // U+f139 +#define ICON_FA_CIRCLE_DOLLAR_TO_SLOT "\xef\x92\xb9" // U+f4b9 +#define ICON_FA_CIRCLE_DOT "\xef\x86\x92" // U+f192 +#define ICON_FA_CIRCLE_DOWN "\xef\x8d\x98" // U+f358 +#define ICON_FA_CIRCLE_EXCLAMATION "\xef\x81\xaa" // U+f06a +#define ICON_FA_CIRCLE_H "\xef\x91\xbe" // U+f47e +#define ICON_FA_CIRCLE_HALF_STROKE "\xef\x81\x82" // U+f042 +#define ICON_FA_CIRCLE_INFO "\xef\x81\x9a" // U+f05a +#define ICON_FA_CIRCLE_LEFT "\xef\x8d\x99" // U+f359 +#define ICON_FA_CIRCLE_MINUS "\xef\x81\x96" // U+f056 +#define ICON_FA_CIRCLE_NODES "\xee\x93\xa2" // U+e4e2 +#define ICON_FA_CIRCLE_NOTCH "\xef\x87\x8e" // U+f1ce +#define ICON_FA_CIRCLE_PAUSE "\xef\x8a\x8b" // U+f28b +#define ICON_FA_CIRCLE_PLAY "\xef\x85\x84" // U+f144 +#define ICON_FA_CIRCLE_PLUS "\xef\x81\x95" // U+f055 +#define ICON_FA_CIRCLE_QUESTION "\xef\x81\x99" // U+f059 +#define ICON_FA_CIRCLE_RADIATION "\xef\x9e\xba" // U+f7ba +#define ICON_FA_CIRCLE_RIGHT "\xef\x8d\x9a" // U+f35a +#define ICON_FA_CIRCLE_STOP "\xef\x8a\x8d" // U+f28d +#define ICON_FA_CIRCLE_UP "\xef\x8d\x9b" // U+f35b +#define ICON_FA_CIRCLE_USER "\xef\x8a\xbd" // U+f2bd +#define ICON_FA_CIRCLE_XMARK "\xef\x81\x97" // U+f057 +#define ICON_FA_CITY "\xef\x99\x8f" // U+f64f +#define ICON_FA_CLAPPERBOARD "\xee\x84\xb1" // U+e131 +#define ICON_FA_CLIPBOARD "\xef\x8c\xa8" // U+f328 +#define ICON_FA_CLIPBOARD_CHECK "\xef\x91\xac" // U+f46c +#define ICON_FA_CLIPBOARD_LIST "\xef\x91\xad" // U+f46d +#define ICON_FA_CLIPBOARD_QUESTION "\xee\x93\xa3" // U+e4e3 +#define ICON_FA_CLIPBOARD_USER "\xef\x9f\xb3" // U+f7f3 +#define ICON_FA_CLOCK "\xef\x80\x97" // U+f017 +#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da +#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d +#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a +#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2 +#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed +#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee +#define ICON_FA_CLOUD_BOLT "\xef\x9d\xac" // U+f76c +#define ICON_FA_CLOUD_MEATBALL "\xef\x9c\xbb" // U+f73b +#define ICON_FA_CLOUD_MOON "\xef\x9b\x83" // U+f6c3 +#define ICON_FA_CLOUD_MOON_RAIN "\xef\x9c\xbc" // U+f73c +#define ICON_FA_CLOUD_RAIN "\xef\x9c\xbd" // U+f73d +#define ICON_FA_CLOUD_SHOWERS_HEAVY "\xef\x9d\x80" // U+f740 +#define ICON_FA_CLOUD_SHOWERS_WATER "\xee\x93\xa4" // U+e4e4 +#define ICON_FA_CLOUD_SUN "\xef\x9b\x84" // U+f6c4 +#define ICON_FA_CLOUD_SUN_RAIN "\xef\x9d\x83" // U+f743 +#define ICON_FA_CLOVER "\xee\x84\xb9" // U+e139 +#define ICON_FA_CODE "\xef\x84\xa1" // U+f121 +#define ICON_FA_CODE_BRANCH "\xef\x84\xa6" // U+f126 +#define ICON_FA_CODE_COMMIT "\xef\x8e\x86" // U+f386 +#define ICON_FA_CODE_COMPARE "\xee\x84\xba" // U+e13a +#define ICON_FA_CODE_FORK "\xee\x84\xbb" // U+e13b +#define ICON_FA_CODE_MERGE "\xef\x8e\x87" // U+f387 +#define ICON_FA_CODE_PULL_REQUEST "\xee\x84\xbc" // U+e13c +#define ICON_FA_COINS "\xef\x94\x9e" // U+f51e +#define ICON_FA_COLON_SIGN "\xee\x85\x80" // U+e140 +#define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075 +#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651 +#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad +#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5 +#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3 +#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd +#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086 +#define ICON_FA_COMMENTS_DOLLAR "\xef\x99\x93" // U+f653 +#define ICON_FA_COMPACT_DISC "\xef\x94\x9f" // U+f51f +#define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e +#define ICON_FA_COMPASS_DRAFTING "\xef\x95\xa8" // U+f568 +#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066 +#define ICON_FA_COMPUTER "\xee\x93\xa5" // U+e4e5 +#define ICON_FA_COMPUTER_MOUSE "\xef\xa3\x8c" // U+f8cc +#define ICON_FA_COOKIE "\xef\x95\xa3" // U+f563 +#define ICON_FA_COOKIE_BITE "\xef\x95\xa4" // U+f564 +#define ICON_FA_COPY "\xef\x83\x85" // U+f0c5 +#define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9 +#define ICON_FA_COUCH "\xef\x92\xb8" // U+f4b8 +#define ICON_FA_COW "\xef\x9b\x88" // U+f6c8 +#define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d +#define ICON_FA_CROP "\xef\x84\xa5" // U+f125 +#define ICON_FA_CROP_SIMPLE "\xef\x95\xa5" // U+f565 +#define ICON_FA_CROSS "\xef\x99\x94" // U+f654 +#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b +#define ICON_FA_CROW "\xef\x94\xa0" // U+f520 +#define ICON_FA_CROWN "\xef\x94\xa1" // U+f521 +#define ICON_FA_CRUTCH "\xef\x9f\xb7" // U+f7f7 +#define ICON_FA_CRUZEIRO_SIGN "\xee\x85\x92" // U+e152 +#define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2 +#define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3 +#define ICON_FA_CUBES_STACKED "\xee\x93\xa6" // U+e4e6 +#define ICON_FA_D "D" // U+0044 +#define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0 +#define ICON_FA_DELETE_LEFT "\xef\x95\x9a" // U+f55a +#define ICON_FA_DEMOCRAT "\xef\x9d\x87" // U+f747 +#define ICON_FA_DESKTOP "\xef\x8e\x90" // U+f390 +#define ICON_FA_DHARMACHAKRA "\xef\x99\x95" // U+f655 +#define ICON_FA_DIAGRAM_NEXT "\xee\x91\xb6" // U+e476 +#define ICON_FA_DIAGRAM_PREDECESSOR "\xee\x91\xb7" // U+e477 +#define ICON_FA_DIAGRAM_PROJECT "\xef\x95\x82" // U+f542 +#define ICON_FA_DIAGRAM_SUCCESSOR "\xee\x91\xba" // U+e47a +#define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219 +#define ICON_FA_DIAMOND_TURN_RIGHT "\xef\x97\xab" // U+f5eb +#define ICON_FA_DICE "\xef\x94\xa2" // U+f522 +#define ICON_FA_DICE_D20 "\xef\x9b\x8f" // U+f6cf +#define ICON_FA_DICE_D6 "\xef\x9b\x91" // U+f6d1 +#define ICON_FA_DICE_FIVE "\xef\x94\xa3" // U+f523 +#define ICON_FA_DICE_FOUR "\xef\x94\xa4" // U+f524 +#define ICON_FA_DICE_ONE "\xef\x94\xa5" // U+f525 +#define ICON_FA_DICE_SIX "\xef\x94\xa6" // U+f526 +#define ICON_FA_DICE_THREE "\xef\x94\xa7" // U+f527 +#define ICON_FA_DICE_TWO "\xef\x94\xa8" // U+f528 +#define ICON_FA_DISEASE "\xef\x9f\xba" // U+f7fa +#define ICON_FA_DISPLAY "\xee\x85\xa3" // U+e163 +#define ICON_FA_DIVIDE "\xef\x94\xa9" // U+f529 +#define ICON_FA_DNA "\xef\x91\xb1" // U+f471 +#define ICON_FA_DOG "\xef\x9b\x93" // U+f6d3 +#define ICON_FA_DOLLAR_SIGN "$" // U+0024 +#define ICON_FA_DOLLY "\xef\x91\xb2" // U+f472 +#define ICON_FA_DONG_SIGN "\xee\x85\xa9" // U+e169 +#define ICON_FA_DOOR_CLOSED "\xef\x94\xaa" // U+f52a +#define ICON_FA_DOOR_OPEN "\xef\x94\xab" // U+f52b +#define ICON_FA_DOVE "\xef\x92\xba" // U+f4ba +#define ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xef\x90\xa2" // U+f422 +#define ICON_FA_DOWN_LONG "\xef\x8c\x89" // U+f309 +#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019 +#define ICON_FA_DRAGON "\xef\x9b\x95" // U+f6d5 +#define ICON_FA_DRAW_POLYGON "\xef\x97\xae" // U+f5ee +#define ICON_FA_DROPLET "\xef\x81\x83" // U+f043 +#define ICON_FA_DROPLET_SLASH "\xef\x97\x87" // U+f5c7 +#define ICON_FA_DRUM "\xef\x95\xa9" // U+f569 +#define ICON_FA_DRUM_STEELPAN "\xef\x95\xaa" // U+f56a +#define ICON_FA_DRUMSTICK_BITE "\xef\x9b\x97" // U+f6d7 +#define ICON_FA_DUMBBELL "\xef\x91\x8b" // U+f44b +#define ICON_FA_DUMPSTER "\xef\x9e\x93" // U+f793 +#define ICON_FA_DUMPSTER_FIRE "\xef\x9e\x94" // U+f794 +#define ICON_FA_DUNGEON "\xef\x9b\x99" // U+f6d9 +#define ICON_FA_E "E" // U+0045 +#define ICON_FA_EAR_DEAF "\xef\x8a\xa4" // U+f2a4 +#define ICON_FA_EAR_LISTEN "\xef\x8a\xa2" // U+f2a2 +#define ICON_FA_EARTH_AFRICA "\xef\x95\xbc" // U+f57c +#define ICON_FA_EARTH_AMERICAS "\xef\x95\xbd" // U+f57d +#define ICON_FA_EARTH_ASIA "\xef\x95\xbe" // U+f57e +#define ICON_FA_EARTH_EUROPE "\xef\x9e\xa2" // U+f7a2 +#define ICON_FA_EARTH_OCEANIA "\xee\x91\xbb" // U+e47b +#define ICON_FA_EGG "\xef\x9f\xbb" // U+f7fb +#define ICON_FA_EJECT "\xef\x81\x92" // U+f052 +#define ICON_FA_ELEVATOR "\xee\x85\xad" // U+e16d +#define ICON_FA_ELLIPSIS "\xef\x85\x81" // U+f141 +#define ICON_FA_ELLIPSIS_VERTICAL "\xef\x85\x82" // U+f142 +#define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0 +#define ICON_FA_ENVELOPE_CIRCLE_CHECK "\xee\x93\xa8" // U+e4e8 +#define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6 +#define ICON_FA_ENVELOPE_OPEN_TEXT "\xef\x99\x98" // U+f658 +#define ICON_FA_ENVELOPES_BULK "\xef\x99\xb4" // U+f674 +#define ICON_FA_EQUALS "=" // U+003d +#define ICON_FA_ERASER "\xef\x84\xad" // U+f12d +#define ICON_FA_ETHERNET "\xef\x9e\x96" // U+f796 +#define ICON_FA_EURO_SIGN "\xef\x85\x93" // U+f153 +#define ICON_FA_EXCLAMATION "!" // U+0021 +#define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065 +#define ICON_FA_EXPLOSION "\xee\x93\xa9" // U+e4e9 +#define ICON_FA_EYE "\xef\x81\xae" // U+f06e +#define ICON_FA_EYE_DROPPER "\xef\x87\xbb" // U+f1fb +#define ICON_FA_EYE_LOW_VISION "\xef\x8a\xa8" // U+f2a8 +#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070 +#define ICON_FA_F "F" // U+0046 +#define ICON_FA_FACE_ANGRY "\xef\x95\x96" // U+f556 +#define ICON_FA_FACE_DIZZY "\xef\x95\xa7" // U+f567 +#define ICON_FA_FACE_FLUSHED "\xef\x95\xb9" // U+f579 +#define ICON_FA_FACE_FROWN "\xef\x84\x99" // U+f119 +#define ICON_FA_FACE_FROWN_OPEN "\xef\x95\xba" // U+f57a +#define ICON_FA_FACE_GRIMACE "\xef\x95\xbf" // U+f57f +#define ICON_FA_FACE_GRIN "\xef\x96\x80" // U+f580 +#define ICON_FA_FACE_GRIN_BEAM "\xef\x96\x82" // U+f582 +#define ICON_FA_FACE_GRIN_BEAM_SWEAT "\xef\x96\x83" // U+f583 +#define ICON_FA_FACE_GRIN_HEARTS "\xef\x96\x84" // U+f584 +#define ICON_FA_FACE_GRIN_SQUINT "\xef\x96\x85" // U+f585 +#define ICON_FA_FACE_GRIN_SQUINT_TEARS "\xef\x96\x86" // U+f586 +#define ICON_FA_FACE_GRIN_STARS "\xef\x96\x87" // U+f587 +#define ICON_FA_FACE_GRIN_TEARS "\xef\x96\x88" // U+f588 +#define ICON_FA_FACE_GRIN_TONGUE "\xef\x96\x89" // U+f589 +#define ICON_FA_FACE_GRIN_TONGUE_SQUINT "\xef\x96\x8a" // U+f58a +#define ICON_FA_FACE_GRIN_TONGUE_WINK "\xef\x96\x8b" // U+f58b +#define ICON_FA_FACE_GRIN_WIDE "\xef\x96\x81" // U+f581 +#define ICON_FA_FACE_GRIN_WINK "\xef\x96\x8c" // U+f58c +#define ICON_FA_FACE_KISS "\xef\x96\x96" // U+f596 +#define ICON_FA_FACE_KISS_BEAM "\xef\x96\x97" // U+f597 +#define ICON_FA_FACE_KISS_WINK_HEART "\xef\x96\x98" // U+f598 +#define ICON_FA_FACE_LAUGH "\xef\x96\x99" // U+f599 +#define ICON_FA_FACE_LAUGH_BEAM "\xef\x96\x9a" // U+f59a +#define ICON_FA_FACE_LAUGH_SQUINT "\xef\x96\x9b" // U+f59b +#define ICON_FA_FACE_LAUGH_WINK "\xef\x96\x9c" // U+f59c +#define ICON_FA_FACE_MEH "\xef\x84\x9a" // U+f11a +#define ICON_FA_FACE_MEH_BLANK "\xef\x96\xa4" // U+f5a4 +#define ICON_FA_FACE_ROLLING_EYES "\xef\x96\xa5" // U+f5a5 +#define ICON_FA_FACE_SAD_CRY "\xef\x96\xb3" // U+f5b3 +#define ICON_FA_FACE_SAD_TEAR "\xef\x96\xb4" // U+f5b4 +#define ICON_FA_FACE_SMILE "\xef\x84\x98" // U+f118 +#define ICON_FA_FACE_SMILE_BEAM "\xef\x96\xb8" // U+f5b8 +#define ICON_FA_FACE_SMILE_WINK "\xef\x93\x9a" // U+f4da +#define ICON_FA_FACE_SURPRISE "\xef\x97\x82" // U+f5c2 +#define ICON_FA_FACE_TIRED "\xef\x97\x88" // U+f5c8 +#define ICON_FA_FAN "\xef\xa1\xa3" // U+f863 +#define ICON_FA_FAUCET "\xee\x80\x85" // U+e005 +#define ICON_FA_FAUCET_DRIP "\xee\x80\x86" // U+e006 +#define ICON_FA_FAX "\xef\x86\xac" // U+f1ac +#define ICON_FA_FEATHER "\xef\x94\xad" // U+f52d +#define ICON_FA_FEATHER_POINTED "\xef\x95\xab" // U+f56b +#define ICON_FA_FERRY "\xee\x93\xaa" // U+e4ea +#define ICON_FA_FILE "\xef\x85\x9b" // U+f15b +#define ICON_FA_FILE_ARROW_DOWN "\xef\x95\xad" // U+f56d +#define ICON_FA_FILE_ARROW_UP "\xef\x95\xb4" // U+f574 +#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+f1c7 +#define ICON_FA_FILE_CIRCLE_CHECK "\xee\x96\xa0" // U+e5a0 +#define ICON_FA_FILE_CIRCLE_EXCLAMATION "\xee\x93\xab" // U+e4eb +#define ICON_FA_FILE_CIRCLE_MINUS "\xee\x93\xad" // U+e4ed +#define ICON_FA_FILE_CIRCLE_PLUS "\xee\x92\x94" // U+e494 +#define ICON_FA_FILE_CIRCLE_QUESTION "\xee\x93\xaf" // U+e4ef +#define ICON_FA_FILE_CIRCLE_XMARK "\xee\x96\xa1" // U+e5a1 +#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+f1c9 +#define ICON_FA_FILE_CONTRACT "\xef\x95\xac" // U+f56c +#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd +#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3 +#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e +#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5 +#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f +#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570 +#define ICON_FA_FILE_INVOICE_DOLLAR "\xef\x95\xb1" // U+f571 +#define ICON_FA_FILE_LINES "\xef\x85\x9c" // U+f15c +#define ICON_FA_FILE_MEDICAL "\xef\x91\xb7" // U+f477 +#define ICON_FA_FILE_PDF "\xef\x87\x81" // U+f1c1 +#define ICON_FA_FILE_PEN "\xef\x8c\x9c" // U+f31c +#define ICON_FA_FILE_POWERPOINT "\xef\x87\x84" // U+f1c4 +#define ICON_FA_FILE_PRESCRIPTION "\xef\x95\xb2" // U+f572 +#define ICON_FA_FILE_SHIELD "\xee\x93\xb0" // U+e4f0 +#define ICON_FA_FILE_SIGNATURE "\xef\x95\xb3" // U+f573 +#define ICON_FA_FILE_VIDEO "\xef\x87\x88" // U+f1c8 +#define ICON_FA_FILE_WAVEFORM "\xef\x91\xb8" // U+f478 +#define ICON_FA_FILE_WORD "\xef\x87\x82" // U+f1c2 +#define ICON_FA_FILE_ZIPPER "\xef\x87\x86" // U+f1c6 +#define ICON_FA_FILL "\xef\x95\xb5" // U+f575 +#define ICON_FA_FILL_DRIP "\xef\x95\xb6" // U+f576 +#define ICON_FA_FILM "\xef\x80\x88" // U+f008 +#define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0 +#define ICON_FA_FILTER_CIRCLE_DOLLAR "\xef\x99\xa2" // U+f662 +#define ICON_FA_FILTER_CIRCLE_XMARK "\xee\x85\xbb" // U+e17b +#define ICON_FA_FINGERPRINT "\xef\x95\xb7" // U+f577 +#define ICON_FA_FIRE "\xef\x81\xad" // U+f06d +#define ICON_FA_FIRE_BURNER "\xee\x93\xb1" // U+e4f1 +#define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134 +#define ICON_FA_FIRE_FLAME_CURVED "\xef\x9f\xa4" // U+f7e4 +#define ICON_FA_FIRE_FLAME_SIMPLE "\xef\x91\xaa" // U+f46a +#define ICON_FA_FISH "\xef\x95\xb8" // U+f578 +#define ICON_FA_FISH_FINS "\xee\x93\xb2" // U+e4f2 +#define ICON_FA_FLAG "\xef\x80\xa4" // U+f024 +#define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e +#define ICON_FA_FLAG_USA "\xef\x9d\x8d" // U+f74d +#define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3 +#define ICON_FA_FLASK_VIAL "\xee\x93\xb3" // U+e4f3 +#define ICON_FA_FLOPPY_DISK "\xef\x83\x87" // U+f0c7 +#define ICON_FA_FLORIN_SIGN "\xee\x86\x84" // U+e184 +#define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b +#define ICON_FA_FOLDER_CLOSED "\xee\x86\x85" // U+e185 +#define ICON_FA_FOLDER_MINUS "\xef\x99\x9d" // U+f65d +#define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c +#define ICON_FA_FOLDER_PLUS "\xef\x99\x9e" // U+f65e +#define ICON_FA_FOLDER_TREE "\xef\xa0\x82" // U+f802 +#define ICON_FA_FONT "\xef\x80\xb1" // U+f031 +#define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4 +#define ICON_FA_FOOTBALL "\xef\x91\x8e" // U+f44e +#define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e +#define ICON_FA_FORWARD_FAST "\xef\x81\x90" // U+f050 +#define ICON_FA_FORWARD_STEP "\xef\x81\x91" // U+f051 +#define ICON_FA_FRANC_SIGN "\xee\x86\x8f" // U+e18f +#define ICON_FA_FROG "\xef\x94\xae" // U+f52e +#define ICON_FA_FUTBOL "\xef\x87\xa3" // U+f1e3 +#define ICON_FA_G "G" // U+0047 +#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b +#define ICON_FA_GAS_PUMP "\xef\x94\xaf" // U+f52f +#define ICON_FA_GAUGE "\xef\x98\xa4" // U+f624 +#define ICON_FA_GAUGE_HIGH "\xef\x98\xa5" // U+f625 +#define ICON_FA_GAUGE_SIMPLE "\xef\x98\xa9" // U+f629 +#define ICON_FA_GAUGE_SIMPLE_HIGH "\xef\x98\xaa" // U+f62a +#define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3 +#define ICON_FA_GEAR "\xef\x80\x93" // U+f013 +#define ICON_FA_GEARS "\xef\x82\x85" // U+f085 +#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5 +#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d +#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2 +#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b +#define ICON_FA_GIFTS "\xef\x9e\x9c" // U+f79c +#define ICON_FA_GLASS_WATER "\xee\x93\xb4" // U+e4f4 +#define ICON_FA_GLASS_WATER_DROPLET "\xee\x93\xb5" // U+e4f5 +#define ICON_FA_GLASSES "\xef\x94\xb0" // U+f530 +#define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac +#define ICON_FA_GOLF_BALL_TEE "\xef\x91\x90" // U+f450 +#define ICON_FA_GOPURAM "\xef\x99\xa4" // U+f664 +#define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d +#define ICON_FA_GREATER_THAN ">" // U+003e +#define ICON_FA_GREATER_THAN_EQUAL "\xef\x94\xb2" // U+f532 +#define ICON_FA_GRIP "\xef\x96\x8d" // U+f58d +#define ICON_FA_GRIP_LINES "\xef\x9e\xa4" // U+f7a4 +#define ICON_FA_GRIP_LINES_VERTICAL "\xef\x9e\xa5" // U+f7a5 +#define ICON_FA_GRIP_VERTICAL "\xef\x96\x8e" // U+f58e +#define ICON_FA_GROUP_ARROWS_ROTATE "\xee\x93\xb6" // U+e4f6 +#define ICON_FA_GUARANI_SIGN "\xee\x86\x9a" // U+e19a +#define ICON_FA_GUITAR "\xef\x9e\xa6" // U+f7a6 +#define ICON_FA_GUN "\xee\x86\x9b" // U+e19b +#define ICON_FA_H "H" // U+0048 +#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+f6e3 +#define ICON_FA_HAMSA "\xef\x99\xa5" // U+f665 +#define ICON_FA_HAND "\xef\x89\x96" // U+f256 +#define ICON_FA_HAND_BACK_FIST "\xef\x89\x95" // U+f255 +#define ICON_FA_HAND_DOTS "\xef\x91\xa1" // U+f461 +#define ICON_FA_HAND_FIST "\xef\x9b\x9e" // U+f6de +#define ICON_FA_HAND_HOLDING "\xef\x92\xbd" // U+f4bd +#define ICON_FA_HAND_HOLDING_DOLLAR "\xef\x93\x80" // U+f4c0 +#define ICON_FA_HAND_HOLDING_DROPLET "\xef\x93\x81" // U+f4c1 +#define ICON_FA_HAND_HOLDING_HAND "\xee\x93\xb7" // U+e4f7 +#define ICON_FA_HAND_HOLDING_HEART "\xef\x92\xbe" // U+f4be +#define ICON_FA_HAND_HOLDING_MEDICAL "\xee\x81\x9c" // U+e05c +#define ICON_FA_HAND_LIZARD "\xef\x89\x98" // U+f258 +#define ICON_FA_HAND_MIDDLE_FINGER "\xef\xa0\x86" // U+f806 +#define ICON_FA_HAND_PEACE "\xef\x89\x9b" // U+f25b +#define ICON_FA_HAND_POINT_DOWN "\xef\x82\xa7" // U+f0a7 +#define ICON_FA_HAND_POINT_LEFT "\xef\x82\xa5" // U+f0a5 +#define ICON_FA_HAND_POINT_RIGHT "\xef\x82\xa4" // U+f0a4 +#define ICON_FA_HAND_POINT_UP "\xef\x82\xa6" // U+f0a6 +#define ICON_FA_HAND_POINTER "\xef\x89\x9a" // U+f25a +#define ICON_FA_HAND_SCISSORS "\xef\x89\x97" // U+f257 +#define ICON_FA_HAND_SPARKLES "\xee\x81\x9d" // U+e05d +#define ICON_FA_HAND_SPOCK "\xef\x89\x99" // U+f259 +#define ICON_FA_HANDCUFFS "\xee\x93\xb8" // U+e4f8 +#define ICON_FA_HANDS "\xef\x8a\xa7" // U+f2a7 +#define ICON_FA_HANDS_ASL_INTERPRETING "\xef\x8a\xa3" // U+f2a3 +#define ICON_FA_HANDS_BOUND "\xee\x93\xb9" // U+e4f9 +#define ICON_FA_HANDS_BUBBLES "\xee\x81\x9e" // U+e05e +#define ICON_FA_HANDS_CLAPPING "\xee\x86\xa8" // U+e1a8 +#define ICON_FA_HANDS_HOLDING "\xef\x93\x82" // U+f4c2 +#define ICON_FA_HANDS_HOLDING_CHILD "\xee\x93\xba" // U+e4fa +#define ICON_FA_HANDS_HOLDING_CIRCLE "\xee\x93\xbb" // U+e4fb +#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684 +#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5 +#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4 +#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6 +#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f +#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060 +#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6 +#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0 +#define ICON_FA_HASHTAG "#" // U+0023 +#define ICON_FA_HAT_COWBOY "\xef\xa3\x80" // U+f8c0 +#define ICON_FA_HAT_COWBOY_SIDE "\xef\xa3\x81" // U+f8c1 +#define ICON_FA_HAT_WIZARD "\xef\x9b\xa8" // U+f6e8 +#define ICON_FA_HEAD_SIDE_COUGH "\xee\x81\xa1" // U+e061 +#define ICON_FA_HEAD_SIDE_COUGH_SLASH "\xee\x81\xa2" // U+e062 +#define ICON_FA_HEAD_SIDE_MASK "\xee\x81\xa3" // U+e063 +#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064 +#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc +#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025 +#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f +#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590 +#define ICON_FA_HEART "\xef\x80\x84" // U+f004 +#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc +#define ICON_FA_HEART_CIRCLE_CHECK "\xee\x93\xbd" // U+e4fd +#define ICON_FA_HEART_CIRCLE_EXCLAMATION "\xee\x93\xbe" // U+e4fe +#define ICON_FA_HEART_CIRCLE_MINUS "\xee\x93\xbf" // U+e4ff +#define ICON_FA_HEART_CIRCLE_PLUS "\xee\x94\x80" // U+e500 +#define ICON_FA_HEART_CIRCLE_XMARK "\xee\x94\x81" // U+e501 +#define ICON_FA_HEART_CRACK "\xef\x9e\xa9" // U+f7a9 +#define ICON_FA_HEART_PULSE "\xef\x88\x9e" // U+f21e +#define ICON_FA_HELICOPTER "\xef\x94\xb3" // U+f533 +#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502 +#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807 +#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503 +#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591 +#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507 +#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508 +#define ICON_FA_HIPPO "\xef\x9b\xad" // U+f6ed +#define ICON_FA_HOCKEY_PUCK "\xef\x91\x93" // U+f453 +#define ICON_FA_HOLLY_BERRY "\xef\x9e\xaa" // U+f7aa +#define ICON_FA_HORSE "\xef\x9b\xb0" // U+f6f0 +#define ICON_FA_HORSE_HEAD "\xef\x9e\xab" // U+f7ab +#define ICON_FA_HOSPITAL "\xef\x83\xb8" // U+f0f8 +#define ICON_FA_HOSPITAL_USER "\xef\xa0\x8d" // U+f80d +#define ICON_FA_HOT_TUB_PERSON "\xef\x96\x93" // U+f593 +#define ICON_FA_HOTDOG "\xef\xa0\x8f" // U+f80f +#define ICON_FA_HOTEL "\xef\x96\x94" // U+f594 +#define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254 +#define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253 +#define ICON_FA_HOURGLASS_HALF "\xef\x89\x92" // U+f252 +#define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251 +#define ICON_FA_HOUSE "\xef\x80\x95" // U+f015 +#define ICON_FA_HOUSE_CHIMNEY "\xee\x8e\xaf" // U+e3af +#define ICON_FA_HOUSE_CHIMNEY_CRACK "\xef\x9b\xb1" // U+f6f1 +#define ICON_FA_HOUSE_CHIMNEY_MEDICAL "\xef\x9f\xb2" // U+f7f2 +#define ICON_FA_HOUSE_CHIMNEY_USER "\xee\x81\xa5" // U+e065 +#define ICON_FA_HOUSE_CHIMNEY_WINDOW "\xee\x80\x8d" // U+e00d +#define ICON_FA_HOUSE_CIRCLE_CHECK "\xee\x94\x89" // U+e509 +#define ICON_FA_HOUSE_CIRCLE_EXCLAMATION "\xee\x94\x8a" // U+e50a +#define ICON_FA_HOUSE_CIRCLE_XMARK "\xee\x94\x8b" // U+e50b +#define ICON_FA_HOUSE_CRACK "\xee\x8e\xb1" // U+e3b1 +#define ICON_FA_HOUSE_FIRE "\xee\x94\x8c" // U+e50c +#define ICON_FA_HOUSE_FLAG "\xee\x94\x8d" // U+e50d +#define ICON_FA_HOUSE_FLOOD_WATER "\xee\x94\x8e" // U+e50e +#define ICON_FA_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xee\x94\x8f" // U+e50f +#define ICON_FA_HOUSE_LAPTOP "\xee\x81\xa6" // U+e066 +#define ICON_FA_HOUSE_LOCK "\xee\x94\x90" // U+e510 +#define ICON_FA_HOUSE_MEDICAL "\xee\x8e\xb2" // U+e3b2 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_CHECK "\xee\x94\x91" // U+e511 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xee\x94\x92" // U+e512 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_XMARK "\xee\x94\x93" // U+e513 +#define ICON_FA_HOUSE_MEDICAL_FLAG "\xee\x94\x94" // U+e514 +#define ICON_FA_HOUSE_SIGNAL "\xee\x80\x92" // U+e012 +#define ICON_FA_HOUSE_TSUNAMI "\xee\x94\x95" // U+e515 +#define ICON_FA_HOUSE_USER "\xee\x86\xb0" // U+e1b0 +#define ICON_FA_HRYVNIA_SIGN "\xef\x9b\xb2" // U+f6f2 +#define ICON_FA_HURRICANE "\xef\x9d\x91" // U+f751 +#define ICON_FA_I "I" // U+0049 +#define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246 +#define ICON_FA_ICE_CREAM "\xef\xa0\x90" // U+f810 +#define ICON_FA_ICICLES "\xef\x9e\xad" // U+f7ad +#define ICON_FA_ICONS "\xef\xa1\xad" // U+f86d +#define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1 +#define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2 +#define ICON_FA_ID_CARD_CLIP "\xef\x91\xbf" // U+f47f +#define ICON_FA_IGLOO "\xef\x9e\xae" // U+f7ae +#define ICON_FA_IMAGE "\xef\x80\xbe" // U+f03e +#define ICON_FA_IMAGE_PORTRAIT "\xef\x8f\xa0" // U+f3e0 +#define ICON_FA_IMAGES "\xef\x8c\x82" // U+f302 +#define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c +#define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c +#define ICON_FA_INDIAN_RUPEE_SIGN "\xee\x86\xbc" // U+e1bc +#define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275 +#define ICON_FA_INFINITY "\xef\x94\xb4" // U+f534 +#define ICON_FA_INFO "\xef\x84\xa9" // U+f129 +#define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033 +#define ICON_FA_J "J" // U+004a +#define ICON_FA_JAR "\xee\x94\x96" // U+e516 +#define ICON_FA_JAR_WHEAT "\xee\x94\x97" // U+e517 +#define ICON_FA_JEDI "\xef\x99\xa9" // U+f669 +#define ICON_FA_JET_FIGHTER "\xef\x83\xbb" // U+f0fb +#define ICON_FA_JET_FIGHTER_UP "\xee\x94\x98" // U+e518 +#define ICON_FA_JOINT "\xef\x96\x95" // U+f595 +#define ICON_FA_JUG_DETERGENT "\xee\x94\x99" // U+e519 +#define ICON_FA_K "K" // U+004b +#define ICON_FA_KAABA "\xef\x99\xab" // U+f66b +#define ICON_FA_KEY "\xef\x82\x84" // U+f084 +#define ICON_FA_KEYBOARD "\xef\x84\x9c" // U+f11c +#define ICON_FA_KHANDA "\xef\x99\xad" // U+f66d +#define ICON_FA_KIP_SIGN "\xee\x87\x84" // U+e1c4 +#define ICON_FA_KIT_MEDICAL "\xef\x91\xb9" // U+f479 +#define ICON_FA_KITCHEN_SET "\xee\x94\x9a" // U+e51a +#define ICON_FA_KIWI_BIRD "\xef\x94\xb5" // U+f535 +#define ICON_FA_L "L" // U+004c +#define ICON_FA_LAND_MINE_ON "\xee\x94\x9b" // U+e51b +#define ICON_FA_LANDMARK "\xef\x99\xaf" // U+f66f +#define ICON_FA_LANDMARK_DOME "\xef\x9d\x92" // U+f752 +#define ICON_FA_LANDMARK_FLAG "\xee\x94\x9c" // U+e51c +#define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab +#define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109 +#define ICON_FA_LAPTOP_CODE "\xef\x97\xbc" // U+f5fc +#define ICON_FA_LAPTOP_FILE "\xee\x94\x9d" // U+e51d +#define ICON_FA_LAPTOP_MEDICAL "\xef\xa0\x92" // U+f812 +#define ICON_FA_LARI_SIGN "\xee\x87\x88" // U+e1c8 +#define ICON_FA_LAYER_GROUP "\xef\x97\xbd" // U+f5fd +#define ICON_FA_LEAF "\xef\x81\xac" // U+f06c +#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a +#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337 +#define ICON_FA_LEMON "\xef\x82\x94" // U+f094 +#define ICON_FA_LESS_THAN "<" // U+003c +#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537 +#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd +#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb +#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e +#define ICON_FA_LINK "\xef\x83\x81" // U+f0c1 +#define ICON_FA_LINK_SLASH "\xef\x84\xa7" // U+f127 +#define ICON_FA_LIRA_SIGN "\xef\x86\x95" // U+f195 +#define ICON_FA_LIST "\xef\x80\xba" // U+f03a +#define ICON_FA_LIST_CHECK "\xef\x82\xae" // U+f0ae +#define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb +#define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca +#define ICON_FA_LITECOIN_SIGN "\xee\x87\x93" // U+e1d3 +#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124 +#define ICON_FA_LOCATION_CROSSHAIRS "\xef\x98\x81" // U+f601 +#define ICON_FA_LOCATION_DOT "\xef\x8f\x85" // U+f3c5 +#define ICON_FA_LOCATION_PIN "\xef\x81\x81" // U+f041 +#define ICON_FA_LOCATION_PIN_LOCK "\xee\x94\x9f" // U+e51f +#define ICON_FA_LOCK "\xef\x80\xa3" // U+f023 +#define ICON_FA_LOCK_OPEN "\xef\x8f\x81" // U+f3c1 +#define ICON_FA_LOCUST "\xee\x94\xa0" // U+e520 +#define ICON_FA_LUNGS "\xef\x98\x84" // U+f604 +#define ICON_FA_LUNGS_VIRUS "\xee\x81\xa7" // U+e067 +#define ICON_FA_M "M" // U+004d +#define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076 +#define ICON_FA_MAGNIFYING_GLASS "\xef\x80\x82" // U+f002 +#define ICON_FA_MAGNIFYING_GLASS_ARROW_RIGHT "\xee\x94\xa1" // U+e521 +#define ICON_FA_MAGNIFYING_GLASS_CHART "\xee\x94\xa2" // U+e522 +#define ICON_FA_MAGNIFYING_GLASS_DOLLAR "\xef\x9a\x88" // U+f688 +#define ICON_FA_MAGNIFYING_GLASS_LOCATION "\xef\x9a\x89" // U+f689 +#define ICON_FA_MAGNIFYING_GLASS_MINUS "\xef\x80\x90" // U+f010 +#define ICON_FA_MAGNIFYING_GLASS_PLUS "\xef\x80\x8e" // U+f00e +#define ICON_FA_MANAT_SIGN "\xee\x87\x95" // U+e1d5 +#define ICON_FA_MAP "\xef\x89\xb9" // U+f279 +#define ICON_FA_MAP_LOCATION "\xef\x96\x9f" // U+f59f +#define ICON_FA_MAP_LOCATION_DOT "\xef\x96\xa0" // U+f5a0 +#define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276 +#define ICON_FA_MARKER "\xef\x96\xa1" // U+f5a1 +#define ICON_FA_MARS "\xef\x88\xa2" // U+f222 +#define ICON_FA_MARS_AND_VENUS "\xef\x88\xa4" // U+f224 +#define ICON_FA_MARS_AND_VENUS_BURST "\xee\x94\xa3" // U+e523 +#define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227 +#define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229 +#define ICON_FA_MARS_STROKE_RIGHT "\xef\x88\xab" // U+f22b +#define ICON_FA_MARS_STROKE_UP "\xef\x88\xaa" // U+f22a +#define ICON_FA_MARTINI_GLASS "\xef\x95\xbb" // U+f57b +#define ICON_FA_MARTINI_GLASS_CITRUS "\xef\x95\xa1" // U+f561 +#define ICON_FA_MARTINI_GLASS_EMPTY "\xef\x80\x80" // U+f000 +#define ICON_FA_MASK "\xef\x9b\xba" // U+f6fa +#define ICON_FA_MASK_FACE "\xee\x87\x97" // U+e1d7 +#define ICON_FA_MASK_VENTILATOR "\xee\x94\xa4" // U+e524 +#define ICON_FA_MASKS_THEATER "\xef\x98\xb0" // U+f630 +#define ICON_FA_MATTRESS_PILLOW "\xee\x94\xa5" // U+e525 +#define ICON_FA_MAXIMIZE "\xef\x8c\x9e" // U+f31e +#define ICON_FA_MEDAL "\xef\x96\xa2" // U+f5a2 +#define ICON_FA_MEMORY "\xef\x94\xb8" // U+f538 +#define ICON_FA_MENORAH "\xef\x99\xb6" // U+f676 +#define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223 +#define ICON_FA_MESSAGE "\xef\x89\xba" // U+f27a +#define ICON_FA_METEOR "\xef\x9d\x93" // U+f753 +#define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db +#define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130 +#define ICON_FA_MICROPHONE_LINES "\xef\x8f\x89" // U+f3c9 +#define ICON_FA_MICROPHONE_LINES_SLASH "\xef\x94\xb9" // U+f539 +#define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131 +#define ICON_FA_MICROSCOPE "\xef\x98\x90" // U+f610 +#define ICON_FA_MILL_SIGN "\xee\x87\xad" // U+e1ed +#define ICON_FA_MINIMIZE "\xef\x9e\x8c" // U+f78c +#define ICON_FA_MINUS "\xef\x81\xa8" // U+f068 +#define ICON_FA_MITTEN "\xef\x9e\xb5" // U+f7b5 +#define ICON_FA_MOBILE "\xef\x8f\x8e" // U+f3ce +#define ICON_FA_MOBILE_BUTTON "\xef\x84\x8b" // U+f10b +#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527 +#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf +#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd +#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6 +#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1 +#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b +#define ICON_FA_MONEY_BILL_TRANSFER "\xee\x94\xa8" // U+e528 +#define ICON_FA_MONEY_BILL_TREND_UP "\xee\x94\xa9" // U+e529 +#define ICON_FA_MONEY_BILL_WAVE "\xef\x94\xba" // U+f53a +#define ICON_FA_MONEY_BILL_WHEAT "\xee\x94\xaa" // U+e52a +#define ICON_FA_MONEY_BILLS "\xee\x87\xb3" // U+e1f3 +#define ICON_FA_MONEY_CHECK "\xef\x94\xbc" // U+f53c +#define ICON_FA_MONEY_CHECK_DOLLAR "\xef\x94\xbd" // U+f53d +#define ICON_FA_MONUMENT "\xef\x96\xa6" // U+f5a6 +#define ICON_FA_MOON "\xef\x86\x86" // U+f186 +#define ICON_FA_MORTAR_PESTLE "\xef\x96\xa7" // U+f5a7 +#define ICON_FA_MOSQUE "\xef\x99\xb8" // U+f678 +#define ICON_FA_MOSQUITO "\xee\x94\xab" // U+e52b +#define ICON_FA_MOSQUITO_NET "\xee\x94\xac" // U+e52c +#define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c +#define ICON_FA_MOUND "\xee\x94\xad" // U+e52d +#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+f6fc +#define ICON_FA_MOUNTAIN_CITY "\xee\x94\xae" // U+e52e +#define ICON_FA_MOUNTAIN_SUN "\xee\x94\xaf" // U+e52f +#define ICON_FA_MUG_HOT "\xef\x9e\xb6" // U+f7b6 +#define ICON_FA_MUG_SAUCER "\xef\x83\xb4" // U+f0f4 +#define ICON_FA_MUSIC "\xef\x80\x81" // U+f001 +#define ICON_FA_N "N" // U+004e +#define ICON_FA_NAIRA_SIGN "\xee\x87\xb6" // U+e1f6 +#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff +#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c +#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea +#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e +#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249 +#define ICON_FA_NOTES_MEDICAL "\xef\x92\x81" // U+f481 +#define ICON_FA_O "O" // U+004f +#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247 +#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248 +#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613 +#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532 +#define ICON_FA_OM "\xef\x99\xb9" // U+f679 +#define ICON_FA_OTTER "\xef\x9c\x80" // U+f700 +#define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b +#define ICON_FA_P "P" // U+0050 +#define ICON_FA_PAGER "\xef\xa0\x95" // U+f815 +#define ICON_FA_PAINT_ROLLER "\xef\x96\xaa" // U+f5aa +#define ICON_FA_PAINTBRUSH "\xef\x87\xbc" // U+f1fc +#define ICON_FA_PALETTE "\xef\x94\xbf" // U+f53f +#define ICON_FA_PALLET "\xef\x92\x82" // U+f482 +#define ICON_FA_PANORAMA "\xee\x88\x89" // U+e209 +#define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8 +#define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6 +#define ICON_FA_PARACHUTE_BOX "\xef\x93\x8d" // U+f4cd +#define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd +#define ICON_FA_PASSPORT "\xef\x96\xab" // U+f5ab +#define ICON_FA_PASTE "\xef\x83\xaa" // U+f0ea +#define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c +#define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0 +#define ICON_FA_PEACE "\xef\x99\xbc" // U+f67c +#define ICON_FA_PEN "\xef\x8c\x84" // U+f304 +#define ICON_FA_PEN_CLIP "\xef\x8c\x85" // U+f305 +#define ICON_FA_PEN_FANCY "\xef\x96\xac" // U+f5ac +#define ICON_FA_PEN_NIB "\xef\x96\xad" // U+f5ad +#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae +#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044 +#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303 +#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068 +#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce +#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533 +#define ICON_FA_PEOPLE_LINE "\xee\x94\xb4" // U+e534 +#define ICON_FA_PEOPLE_PULLING "\xee\x94\xb5" // U+e535 +#define ICON_FA_PEOPLE_ROBBERY "\xee\x94\xb6" // U+e536 +#define ICON_FA_PEOPLE_ROOF "\xee\x94\xb7" // U+e537 +#define ICON_FA_PEPPER_HOT "\xef\xa0\x96" // U+f816 +#define ICON_FA_PERCENT "%" // U+0025 +#define ICON_FA_PERSON "\xef\x86\x83" // U+f183 +#define ICON_FA_PERSON_ARROW_DOWN_TO_LINE "\xee\x94\xb8" // U+e538 +#define ICON_FA_PERSON_ARROW_UP_FROM_LINE "\xee\x94\xb9" // U+e539 +#define ICON_FA_PERSON_BIKING "\xef\xa1\x8a" // U+f84a +#define ICON_FA_PERSON_BOOTH "\xef\x9d\x96" // U+f756 +#define ICON_FA_PERSON_BREASTFEEDING "\xee\x94\xba" // U+e53a +#define ICON_FA_PERSON_BURST "\xee\x94\xbb" // U+e53b +#define ICON_FA_PERSON_CANE "\xee\x94\xbc" // U+e53c +#define ICON_FA_PERSON_CHALKBOARD "\xee\x94\xbd" // U+e53d +#define ICON_FA_PERSON_CIRCLE_CHECK "\xee\x94\xbe" // U+e53e +#define ICON_FA_PERSON_CIRCLE_EXCLAMATION "\xee\x94\xbf" // U+e53f +#define ICON_FA_PERSON_CIRCLE_MINUS "\xee\x95\x80" // U+e540 +#define ICON_FA_PERSON_CIRCLE_PLUS "\xee\x95\x81" // U+e541 +#define ICON_FA_PERSON_CIRCLE_QUESTION "\xee\x95\x82" // U+e542 +#define ICON_FA_PERSON_CIRCLE_XMARK "\xee\x95\x83" // U+e543 +#define ICON_FA_PERSON_DIGGING "\xef\xa1\x9e" // U+f85e +#define ICON_FA_PERSON_DOTS_FROM_LINE "\xef\x91\xb0" // U+f470 +#define ICON_FA_PERSON_DRESS "\xef\x86\x82" // U+f182 +#define ICON_FA_PERSON_DRESS_BURST "\xee\x95\x84" // U+e544 +#define ICON_FA_PERSON_DROWNING "\xee\x95\x85" // U+e545 +#define ICON_FA_PERSON_FALLING "\xee\x95\x86" // U+e546 +#define ICON_FA_PERSON_FALLING_BURST "\xee\x95\x87" // U+e547 +#define ICON_FA_PERSON_HALF_DRESS "\xee\x95\x88" // U+e548 +#define ICON_FA_PERSON_HARASSING "\xee\x95\x89" // U+e549 +#define ICON_FA_PERSON_HIKING "\xef\x9b\xac" // U+f6ec +#define ICON_FA_PERSON_MILITARY_POINTING "\xee\x95\x8a" // U+e54a +#define ICON_FA_PERSON_MILITARY_RIFLE "\xee\x95\x8b" // U+e54b +#define ICON_FA_PERSON_MILITARY_TO_PERSON "\xee\x95\x8c" // U+e54c +#define ICON_FA_PERSON_PRAYING "\xef\x9a\x83" // U+f683 +#define ICON_FA_PERSON_PREGNANT "\xee\x8c\x9e" // U+e31e +#define ICON_FA_PERSON_RAYS "\xee\x95\x8d" // U+e54d +#define ICON_FA_PERSON_RIFLE "\xee\x95\x8e" // U+e54e +#define ICON_FA_PERSON_RUNNING "\xef\x9c\x8c" // U+f70c +#define ICON_FA_PERSON_SHELTER "\xee\x95\x8f" // U+e54f +#define ICON_FA_PERSON_SKATING "\xef\x9f\x85" // U+f7c5 +#define ICON_FA_PERSON_SKIING "\xef\x9f\x89" // U+f7c9 +#define ICON_FA_PERSON_SKIING_NORDIC "\xef\x9f\x8a" // U+f7ca +#define ICON_FA_PERSON_SNOWBOARDING "\xef\x9f\x8e" // U+f7ce +#define ICON_FA_PERSON_SWIMMING "\xef\x97\x84" // U+f5c4 +#define ICON_FA_PERSON_THROUGH_WINDOW "\xee\x96\xa9" // U+e5a9 +#define ICON_FA_PERSON_WALKING "\xef\x95\x94" // U+f554 +#define ICON_FA_PERSON_WALKING_ARROW_LOOP_LEFT "\xee\x95\x91" // U+e551 +#define ICON_FA_PERSON_WALKING_ARROW_RIGHT "\xee\x95\x92" // U+e552 +#define ICON_FA_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xee\x95\x93" // U+e553 +#define ICON_FA_PERSON_WALKING_LUGGAGE "\xee\x95\x94" // U+e554 +#define ICON_FA_PERSON_WALKING_WITH_CANE "\xef\x8a\x9d" // U+f29d +#define ICON_FA_PESETA_SIGN "\xee\x88\xa1" // U+e221 +#define ICON_FA_PESO_SIGN "\xee\x88\xa2" // U+e222 +#define ICON_FA_PHONE "\xef\x82\x95" // U+f095 +#define ICON_FA_PHONE_FLIP "\xef\xa1\xb9" // U+f879 +#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd +#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0 +#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c +#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3 +#define ICON_FA_PILLS "\xef\x92\x84" // U+f484 +#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818 +#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f +#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072 +#define ICON_FA_PLANE_ARRIVAL "\xef\x96\xaf" // U+f5af +#define ICON_FA_PLANE_CIRCLE_CHECK "\xee\x95\x95" // U+e555 +#define ICON_FA_PLANE_CIRCLE_EXCLAMATION "\xee\x95\x96" // U+e556 +#define ICON_FA_PLANE_CIRCLE_XMARK "\xee\x95\x97" // U+e557 +#define ICON_FA_PLANE_DEPARTURE "\xef\x96\xb0" // U+f5b0 +#define ICON_FA_PLANE_LOCK "\xee\x95\x98" // U+e558 +#define ICON_FA_PLANE_SLASH "\xee\x81\xa9" // U+e069 +#define ICON_FA_PLANE_UP "\xee\x88\xad" // U+e22d +#define ICON_FA_PLANT_WILT "\xee\x96\xaa" // U+e5aa +#define ICON_FA_PLATE_WHEAT "\xee\x95\x9a" // U+e55a +#define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b +#define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6 +#define ICON_FA_PLUG_CIRCLE_BOLT "\xee\x95\x9b" // U+e55b +#define ICON_FA_PLUG_CIRCLE_CHECK "\xee\x95\x9c" // U+e55c +#define ICON_FA_PLUG_CIRCLE_EXCLAMATION "\xee\x95\x9d" // U+e55d +#define ICON_FA_PLUG_CIRCLE_MINUS "\xee\x95\x9e" // U+e55e +#define ICON_FA_PLUG_CIRCLE_PLUS "\xee\x95\x9f" // U+e55f +#define ICON_FA_PLUG_CIRCLE_XMARK "\xee\x95\xa0" // U+e560 +#define ICON_FA_PLUS "+" // U+002b +#define ICON_FA_PLUS_MINUS "\xee\x90\xbc" // U+e43c +#define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce +#define ICON_FA_POO "\xef\x8b\xbe" // U+f2fe +#define ICON_FA_POO_STORM "\xef\x9d\x9a" // U+f75a +#define ICON_FA_POOP "\xef\x98\x99" // U+f619 +#define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011 +#define ICON_FA_PRESCRIPTION "\xef\x96\xb1" // U+f5b1 +#define ICON_FA_PRESCRIPTION_BOTTLE "\xef\x92\x85" // U+f485 +#define ICON_FA_PRESCRIPTION_BOTTLE_MEDICAL "\xef\x92\x86" // U+f486 +#define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f +#define ICON_FA_PUMP_MEDICAL "\xee\x81\xaa" // U+e06a +#define ICON_FA_PUMP_SOAP "\xee\x81\xab" // U+e06b +#define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e +#define ICON_FA_Q "Q" // U+0051 +#define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029 +#define ICON_FA_QUESTION "?" // U+003f +#define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d +#define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e +#define ICON_FA_R "R" // U+0052 +#define ICON_FA_RADIATION "\xef\x9e\xb9" // U+f7b9 +#define ICON_FA_RADIO "\xef\xa3\x97" // U+f8d7 +#define ICON_FA_RAINBOW "\xef\x9d\x9b" // U+f75b +#define ICON_FA_RANKING_STAR "\xee\x95\xa1" // U+e561 +#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543 +#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9 +#define ICON_FA_RECTANGLE_AD "\xef\x99\x81" // U+f641 +#define ICON_FA_RECTANGLE_LIST "\xef\x80\xa2" // U+f022 +#define ICON_FA_RECTANGLE_XMARK "\xef\x90\x90" // U+f410 +#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8 +#define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d +#define ICON_FA_REPEAT "\xef\x8d\xa3" // U+f363 +#define ICON_FA_REPLY "\xef\x8f\xa5" // U+f3e5 +#define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122 +#define ICON_FA_REPUBLICAN "\xef\x9d\x9e" // U+f75e +#define ICON_FA_RESTROOM "\xef\x9e\xbd" // U+f7bd +#define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079 +#define ICON_FA_RIBBON "\xef\x93\x96" // U+f4d6 +#define ICON_FA_RIGHT_FROM_BRACKET "\xef\x8b\xb5" // U+f2f5 +#define ICON_FA_RIGHT_LEFT "\xef\x8d\xa2" // U+f362 +#define ICON_FA_RIGHT_LONG "\xef\x8c\x8b" // U+f30b +#define ICON_FA_RIGHT_TO_BRACKET "\xef\x8b\xb6" // U+f2f6 +#define ICON_FA_RING "\xef\x9c\x8b" // U+f70b +#define ICON_FA_ROAD "\xef\x80\x98" // U+f018 +#define ICON_FA_ROAD_BARRIER "\xee\x95\xa2" // U+e562 +#define ICON_FA_ROAD_BRIDGE "\xee\x95\xa3" // U+e563 +#define ICON_FA_ROAD_CIRCLE_CHECK "\xee\x95\xa4" // U+e564 +#define ICON_FA_ROAD_CIRCLE_EXCLAMATION "\xee\x95\xa5" // U+e565 +#define ICON_FA_ROAD_CIRCLE_XMARK "\xee\x95\xa6" // U+e566 +#define ICON_FA_ROAD_LOCK "\xee\x95\xa7" // U+e567 +#define ICON_FA_ROAD_SPIKES "\xee\x95\xa8" // U+e568 +#define ICON_FA_ROBOT "\xef\x95\x84" // U+f544 +#define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135 +#define ICON_FA_ROTATE "\xef\x8b\xb1" // U+f2f1 +#define ICON_FA_ROTATE_LEFT "\xef\x8b\xaa" // U+f2ea +#define ICON_FA_ROTATE_RIGHT "\xef\x8b\xb9" // U+f2f9 +#define ICON_FA_ROUTE "\xef\x93\x97" // U+f4d7 +#define ICON_FA_RSS "\xef\x82\x9e" // U+f09e +#define ICON_FA_RUBLE_SIGN "\xef\x85\x98" // U+f158 +#define ICON_FA_RUG "\xee\x95\xa9" // U+e569 +#define ICON_FA_RULER "\xef\x95\x85" // U+f545 +#define ICON_FA_RULER_COMBINED "\xef\x95\x86" // U+f546 +#define ICON_FA_RULER_HORIZONTAL "\xef\x95\x87" // U+f547 +#define ICON_FA_RULER_VERTICAL "\xef\x95\x88" // U+f548 +#define ICON_FA_RUPEE_SIGN "\xef\x85\x96" // U+f156 +#define ICON_FA_RUPIAH_SIGN "\xee\x88\xbd" // U+e23d +#define ICON_FA_S "S" // U+0053 +#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d +#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a +#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445 +#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf +#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0 +#define ICON_FA_SCALE_BALANCED "\xef\x89\x8e" // U+f24e +#define ICON_FA_SCALE_UNBALANCED "\xef\x94\x95" // U+f515 +#define ICON_FA_SCALE_UNBALANCED_FLIP "\xef\x94\x96" // U+f516 +#define ICON_FA_SCHOOL "\xef\x95\x89" // U+f549 +#define ICON_FA_SCHOOL_CIRCLE_CHECK "\xee\x95\xab" // U+e56b +#define ICON_FA_SCHOOL_CIRCLE_EXCLAMATION "\xee\x95\xac" // U+e56c +#define ICON_FA_SCHOOL_CIRCLE_XMARK "\xee\x95\xad" // U+e56d +#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e +#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f +#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4 +#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a +#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9 +#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e +#define ICON_FA_SCROLL_TORAH "\xef\x9a\xa0" // U+f6a0 +#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2 +#define ICON_FA_SECTION "\xee\x91\x87" // U+e447 +#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8 +#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233 +#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f +#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064 +#define ICON_FA_SHARE_FROM_SQUARE "\xef\x85\x8d" // U+f14d +#define ICON_FA_SHARE_NODES "\xef\x87\xa0" // U+f1e0 +#define ICON_FA_SHEET_PLASTIC "\xee\x95\xb1" // U+e571 +#define ICON_FA_SHEKEL_SIGN "\xef\x88\x8b" // U+f20b +#define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132 +#define ICON_FA_SHIELD_CAT "\xee\x95\xb2" // U+e572 +#define ICON_FA_SHIELD_DOG "\xee\x95\xb3" // U+e573 +#define ICON_FA_SHIELD_HALVED "\xef\x8f\xad" // U+f3ed +#define ICON_FA_SHIELD_HEART "\xee\x95\xb4" // U+e574 +#define ICON_FA_SHIELD_VIRUS "\xee\x81\xac" // U+e06c +#define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a +#define ICON_FA_SHIRT "\xef\x95\x93" // U+f553 +#define ICON_FA_SHOE_PRINTS "\xef\x95\x8b" // U+f54b +#define ICON_FA_SHOP "\xef\x95\x8f" // U+f54f +#define ICON_FA_SHOP_LOCK "\xee\x92\xa5" // U+e4a5 +#define ICON_FA_SHOP_SLASH "\xee\x81\xb0" // U+e070 +#define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc +#define ICON_FA_SHRIMP "\xee\x91\x88" // U+e448 +#define ICON_FA_SHUFFLE "\xef\x81\xb4" // U+f074 +#define ICON_FA_SHUTTLE_SPACE "\xef\x86\x97" // U+f197 +#define ICON_FA_SIGN_HANGING "\xef\x93\x99" // U+f4d9 +#define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012 +#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7 +#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277 +#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4 +#define ICON_FA_SINK "\xee\x81\xad" // U+e06d +#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8 +#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c +#define ICON_FA_SKULL_CROSSBONES "\xef\x9c\x94" // U+f714 +#define ICON_FA_SLASH "\xef\x9c\x95" // U+f715 +#define ICON_FA_SLEIGH "\xef\x9f\x8c" // U+f7cc +#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de +#define ICON_FA_SMOG "\xef\x9d\x9f" // U+f75f +#define ICON_FA_SMOKING "\xef\x92\x8d" // U+f48d +#define ICON_FA_SNOWFLAKE "\xef\x8b\x9c" // U+f2dc +#define ICON_FA_SNOWMAN "\xef\x9f\x90" // U+f7d0 +#define ICON_FA_SNOWPLOW "\xef\x9f\x92" // U+f7d2 +#define ICON_FA_SOAP "\xee\x81\xae" // U+e06e +#define ICON_FA_SOCKS "\xef\x9a\x96" // U+f696 +#define ICON_FA_SOLAR_PANEL "\xef\x96\xba" // U+f5ba +#define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc +#define ICON_FA_SORT_DOWN "\xef\x83\x9d" // U+f0dd +#define ICON_FA_SORT_UP "\xef\x83\x9e" // U+f0de +#define ICON_FA_SPA "\xef\x96\xbb" // U+f5bb +#define ICON_FA_SPAGHETTI_MONSTER_FLYING "\xef\x99\xbb" // U+f67b +#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891 +#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717 +#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110 +#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc +#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5 +#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd +#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0 +#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8 +#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c +#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150 +#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191 +#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152 +#define ICON_FA_SQUARE_CARET_UP "\xef\x85\x91" // U+f151 +#define ICON_FA_SQUARE_CHECK "\xef\x85\x8a" // U+f14a +#define ICON_FA_SQUARE_ENVELOPE "\xef\x86\x99" // U+f199 +#define ICON_FA_SQUARE_FULL "\xef\x91\x9c" // U+f45c +#define ICON_FA_SQUARE_H "\xef\x83\xbd" // U+f0fd +#define ICON_FA_SQUARE_MINUS "\xef\x85\x86" // U+f146 +#define ICON_FA_SQUARE_NFI "\xee\x95\xb6" // U+e576 +#define ICON_FA_SQUARE_PARKING "\xef\x95\x80" // U+f540 +#define ICON_FA_SQUARE_PEN "\xef\x85\x8b" // U+f14b +#define ICON_FA_SQUARE_PERSON_CONFINED "\xee\x95\xb7" // U+e577 +#define ICON_FA_SQUARE_PHONE "\xef\x82\x98" // U+f098 +#define ICON_FA_SQUARE_PHONE_FLIP "\xef\xa1\xbb" // U+f87b +#define ICON_FA_SQUARE_PLUS "\xef\x83\xbe" // U+f0fe +#define ICON_FA_SQUARE_POLL_HORIZONTAL "\xef\x9a\x82" // U+f682 +#define ICON_FA_SQUARE_POLL_VERTICAL "\xef\x9a\x81" // U+f681 +#define ICON_FA_SQUARE_ROOT_VARIABLE "\xef\x9a\x98" // U+f698 +#define ICON_FA_SQUARE_RSS "\xef\x85\x83" // U+f143 +#define ICON_FA_SQUARE_SHARE_NODES "\xef\x87\xa1" // U+f1e1 +#define ICON_FA_SQUARE_UP_RIGHT "\xef\x8d\xa0" // U+f360 +#define ICON_FA_SQUARE_VIRUS "\xee\x95\xb8" // U+e578 +#define ICON_FA_SQUARE_XMARK "\xef\x8b\x93" // U+f2d3 +#define ICON_FA_STAFF_SNAKE "\xee\x95\xb9" // U+e579 +#define ICON_FA_STAIRS "\xee\x8a\x89" // U+e289 +#define ICON_FA_STAMP "\xef\x96\xbf" // U+f5bf +#define ICON_FA_STAPLER "\xee\x96\xaf" // U+e5af +#define ICON_FA_STAR "\xef\x80\x85" // U+f005 +#define ICON_FA_STAR_AND_CRESCENT "\xef\x9a\x99" // U+f699 +#define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089 +#define ICON_FA_STAR_HALF_STROKE "\xef\x97\x80" // U+f5c0 +#define ICON_FA_STAR_OF_DAVID "\xef\x9a\x9a" // U+f69a +#define ICON_FA_STAR_OF_LIFE "\xef\x98\xa1" // U+f621 +#define ICON_FA_STERLING_SIGN "\xef\x85\x94" // U+f154 +#define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1 +#define ICON_FA_STOP "\xef\x81\x8d" // U+f04d +#define ICON_FA_STOPWATCH "\xef\x8b\xb2" // U+f2f2 +#define ICON_FA_STOPWATCH_20 "\xee\x81\xaf" // U+e06f +#define ICON_FA_STORE "\xef\x95\x8e" // U+f54e +#define ICON_FA_STORE_SLASH "\xee\x81\xb1" // U+e071 +#define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d +#define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc +#define ICON_FA_STROOPWAFEL "\xef\x95\x91" // U+f551 +#define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c +#define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2 +#define ICON_FA_SUITCASE_MEDICAL "\xef\x83\xba" // U+f0fa +#define ICON_FA_SUITCASE_ROLLING "\xef\x97\x81" // U+f5c1 +#define ICON_FA_SUN "\xef\x86\x85" // U+f185 +#define ICON_FA_SUN_PLANT_WILT "\xee\x95\xba" // U+e57a +#define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b +#define ICON_FA_SWATCHBOOK "\xef\x97\x83" // U+f5c3 +#define ICON_FA_SYNAGOGUE "\xef\x9a\x9b" // U+f69b +#define ICON_FA_SYRINGE "\xef\x92\x8e" // U+f48e +#define ICON_FA_T "T" // U+0054 +#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce +#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a +#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009 +#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db +#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b +#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d +#define ICON_FA_TABLET "\xef\x8f\xbb" // U+f3fb +#define ICON_FA_TABLET_BUTTON "\xef\x84\x8a" // U+f10a +#define ICON_FA_TABLET_SCREEN_BUTTON "\xef\x8f\xba" // U+f3fa +#define ICON_FA_TABLETS "\xef\x92\x90" // U+f490 +#define ICON_FA_TACHOGRAPH_DIGITAL "\xef\x95\xa6" // U+f566 +#define ICON_FA_TAG "\xef\x80\xab" // U+f02b +#define ICON_FA_TAGS "\xef\x80\xac" // U+f02c +#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db +#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b +#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c +#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba +#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e +#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f +#define ICON_FA_TEMPERATURE_ARROW_DOWN "\xee\x80\xbf" // U+e03f +#define ICON_FA_TEMPERATURE_ARROW_UP "\xee\x81\x80" // U+e040 +#define ICON_FA_TEMPERATURE_EMPTY "\xef\x8b\x8b" // U+f2cb +#define ICON_FA_TEMPERATURE_FULL "\xef\x8b\x87" // U+f2c7 +#define ICON_FA_TEMPERATURE_HALF "\xef\x8b\x89" // U+f2c9 +#define ICON_FA_TEMPERATURE_HIGH "\xef\x9d\xa9" // U+f769 +#define ICON_FA_TEMPERATURE_LOW "\xef\x9d\xab" // U+f76b +#define ICON_FA_TEMPERATURE_QUARTER "\xef\x8b\x8a" // U+f2ca +#define ICON_FA_TEMPERATURE_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8 +#define ICON_FA_TENGE_SIGN "\xef\x9f\x97" // U+f7d7 +#define ICON_FA_TENT "\xee\x95\xbd" // U+e57d +#define ICON_FA_TENT_ARROW_DOWN_TO_LINE "\xee\x95\xbe" // U+e57e +#define ICON_FA_TENT_ARROW_LEFT_RIGHT "\xee\x95\xbf" // U+e57f +#define ICON_FA_TENT_ARROW_TURN_LEFT "\xee\x96\x80" // U+e580 +#define ICON_FA_TENT_ARROWS_DOWN "\xee\x96\x81" // U+e581 +#define ICON_FA_TENTS "\xee\x96\x82" // U+e582 +#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120 +#define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034 +#define ICON_FA_TEXT_SLASH "\xef\xa1\xbd" // U+f87d +#define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035 +#define ICON_FA_THERMOMETER "\xef\x92\x91" // U+f491 +#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165 +#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164 +#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d +#define ICON_FA_TICKET "\xef\x85\x85" // U+f145 +#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff +#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c +#define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204 +#define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205 +#define ICON_FA_TOILET "\xef\x9f\x98" // U+f7d8 +#define ICON_FA_TOILET_PAPER "\xef\x9c\x9e" // U+f71e +#define ICON_FA_TOILET_PAPER_SLASH "\xee\x81\xb2" // U+e072 +#define ICON_FA_TOILET_PORTABLE "\xee\x96\x83" // U+e583 +#define ICON_FA_TOILETS_PORTABLE "\xee\x96\x84" // U+e584 +#define ICON_FA_TOOLBOX "\xef\x95\x92" // U+f552 +#define ICON_FA_TOOTH "\xef\x97\x89" // U+f5c9 +#define ICON_FA_TORII_GATE "\xef\x9a\xa1" // U+f6a1 +#define ICON_FA_TORNADO "\xef\x9d\xaf" // U+f76f +#define ICON_FA_TOWER_BROADCAST "\xef\x94\x99" // U+f519 +#define ICON_FA_TOWER_CELL "\xee\x96\x85" // U+e585 +#define ICON_FA_TOWER_OBSERVATION "\xee\x96\x86" // U+e586 +#define ICON_FA_TRACTOR "\xef\x9c\xa2" // U+f722 +#define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c +#define ICON_FA_TRAFFIC_LIGHT "\xef\x98\xb7" // U+f637 +#define ICON_FA_TRAILER "\xee\x81\x81" // U+e041 +#define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238 +#define ICON_FA_TRAIN_SUBWAY "\xef\x88\xb9" // U+f239 +#define ICON_FA_TRAIN_TRAM "\xee\x96\xb4" // U+e5b4 +#define ICON_FA_TRANSGENDER "\xef\x88\xa5" // U+f225 +#define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8 +#define ICON_FA_TRASH_ARROW_UP "\xef\xa0\xa9" // U+f829 +#define ICON_FA_TRASH_CAN "\xef\x8b\xad" // U+f2ed +#define ICON_FA_TRASH_CAN_ARROW_UP "\xef\xa0\xaa" // U+f82a +#define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb +#define ICON_FA_TREE_CITY "\xee\x96\x87" // U+e587 +#define ICON_FA_TRIANGLE_EXCLAMATION "\xef\x81\xb1" // U+f071 +#define ICON_FA_TROPHY "\xef\x82\x91" // U+f091 +#define ICON_FA_TROWEL "\xee\x96\x89" // U+e589 +#define ICON_FA_TROWEL_BRICKS "\xee\x96\x8a" // U+e58a +#define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1 +#define ICON_FA_TRUCK_ARROW_RIGHT "\xee\x96\x8b" // U+e58b +#define ICON_FA_TRUCK_DROPLET "\xee\x96\x8c" // U+e58c +#define ICON_FA_TRUCK_FAST "\xef\x92\x8b" // U+f48b +#define ICON_FA_TRUCK_FIELD "\xee\x96\x8d" // U+e58d +#define ICON_FA_TRUCK_FIELD_UN "\xee\x96\x8e" // U+e58e +#define ICON_FA_TRUCK_FRONT "\xee\x8a\xb7" // U+e2b7 +#define ICON_FA_TRUCK_MEDICAL "\xef\x83\xb9" // U+f0f9 +#define ICON_FA_TRUCK_MONSTER "\xef\x98\xbb" // U+f63b +#define ICON_FA_TRUCK_MOVING "\xef\x93\x9f" // U+f4df +#define ICON_FA_TRUCK_PICKUP "\xef\x98\xbc" // U+f63c +#define ICON_FA_TRUCK_PLANE "\xee\x96\x8f" // U+e58f +#define ICON_FA_TRUCK_RAMP_BOX "\xef\x93\x9e" // U+f4de +#define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4 +#define ICON_FA_TURKISH_LIRA_SIGN "\xee\x8a\xbb" // U+e2bb +#define ICON_FA_TURN_DOWN "\xef\x8e\xbe" // U+f3be +#define ICON_FA_TURN_UP "\xef\x8e\xbf" // U+f3bf +#define ICON_FA_TV "\xef\x89\xac" // U+f26c +#define ICON_FA_U "U" // U+0055 +#define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9 +#define ICON_FA_UMBRELLA_BEACH "\xef\x97\x8a" // U+f5ca +#define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd +#define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a +#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c +#define ICON_FA_UNLOCK_KEYHOLE "\xef\x84\xbe" // U+f13e +#define ICON_FA_UP_DOWN "\xef\x8c\xb8" // U+f338 +#define ICON_FA_UP_DOWN_LEFT_RIGHT "\xef\x82\xb2" // U+f0b2 +#define ICON_FA_UP_LONG "\xef\x8c\x8c" // U+f30c +#define ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xef\x90\xa4" // U+f424 +#define ICON_FA_UP_RIGHT_FROM_SQUARE "\xef\x8d\x9d" // U+f35d +#define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093 +#define ICON_FA_USER "\xef\x80\x87" // U+f007 +#define ICON_FA_USER_ASTRONAUT "\xef\x93\xbb" // U+f4fb +#define ICON_FA_USER_CHECK "\xef\x93\xbc" // U+f4fc +#define ICON_FA_USER_CLOCK "\xef\x93\xbd" // U+f4fd +#define ICON_FA_USER_DOCTOR "\xef\x83\xb0" // U+f0f0 +#define ICON_FA_USER_GEAR "\xef\x93\xbe" // U+f4fe +#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501 +#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500 +#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728 +#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406 +#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa +#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502 +#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503 +#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504 +#define ICON_FA_USER_NURSE "\xef\xa0\xaf" // U+f82f +#define ICON_FA_USER_PEN "\xef\x93\xbf" // U+f4ff +#define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234 +#define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b +#define ICON_FA_USER_SHIELD "\xef\x94\x85" // U+f505 +#define ICON_FA_USER_SLASH "\xef\x94\x86" // U+f506 +#define ICON_FA_USER_TAG "\xef\x94\x87" // U+f507 +#define ICON_FA_USER_TIE "\xef\x94\x88" // U+f508 +#define ICON_FA_USER_XMARK "\xef\x88\xb5" // U+f235 +#define ICON_FA_USERS "\xef\x83\x80" // U+f0c0 +#define ICON_FA_USERS_BETWEEN_LINES "\xee\x96\x91" // U+e591 +#define ICON_FA_USERS_GEAR "\xef\x94\x89" // U+f509 +#define ICON_FA_USERS_LINE "\xee\x96\x92" // U+e592 +#define ICON_FA_USERS_RAYS "\xee\x96\x93" // U+e593 +#define ICON_FA_USERS_RECTANGLE "\xee\x96\x94" // U+e594 +#define ICON_FA_USERS_SLASH "\xee\x81\xb3" // U+e073 +#define ICON_FA_USERS_VIEWFINDER "\xee\x96\x95" // U+e595 +#define ICON_FA_UTENSILS "\xef\x8b\xa7" // U+f2e7 +#define ICON_FA_V "V" // U+0056 +#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6 +#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5 +#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb +#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221 +#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226 +#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228 +#define ICON_FA_VEST "\xee\x82\x85" // U+e085 +#define ICON_FA_VEST_PATCHES "\xee\x82\x86" // U+e086 +#define ICON_FA_VIAL "\xef\x92\x92" // U+f492 +#define ICON_FA_VIAL_CIRCLE_CHECK "\xee\x96\x96" // U+e596 +#define ICON_FA_VIAL_VIRUS "\xee\x96\x97" // U+e597 +#define ICON_FA_VIALS "\xef\x92\x93" // U+f493 +#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d +#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2 +#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7 +#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074 +#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8 +#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9 +#define ICON_FA_VIRUS_SLASH "\xee\x81\xb5" // U+e075 +#define ICON_FA_VIRUSES "\xee\x81\xb6" // U+e076 +#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897 +#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770 +#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f +#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028 +#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027 +#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026 +#define ICON_FA_VOLUME_XMARK "\xef\x9a\xa9" // U+f6a9 +#define ICON_FA_VR_CARDBOARD "\xef\x9c\xa9" // U+f729 +#define ICON_FA_W "W" // U+0057 +#define ICON_FA_WALKIE_TALKIE "\xef\xa3\xaf" // U+f8ef +#define ICON_FA_WALLET "\xef\x95\x95" // U+f555 +#define ICON_FA_WAND_MAGIC "\xef\x83\x90" // U+f0d0 +#define ICON_FA_WAND_MAGIC_SPARKLES "\xee\x8b\x8a" // U+e2ca +#define ICON_FA_WAND_SPARKLES "\xef\x9c\xab" // U+f72b +#define ICON_FA_WAREHOUSE "\xef\x92\x94" // U+f494 +#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773 +#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5 +#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e +#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd +#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496 +#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd +#define ICON_FA_WHEAT_AWN_CIRCLE_EXCLAMATION "\xee\x96\x98" // U+e598 +#define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193 +#define ICON_FA_WHEELCHAIR_MOVE "\xee\x8b\x8e" // U+e2ce +#define ICON_FA_WHISKEY_GLASS "\xef\x9e\xa0" // U+f7a0 +#define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb +#define ICON_FA_WIND "\xef\x9c\xae" // U+f72e +#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0 +#define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1 +#define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2 +#define ICON_FA_WINE_BOTTLE "\xef\x9c\xaf" // U+f72f +#define ICON_FA_WINE_GLASS "\xef\x93\xa3" // U+f4e3 +#define ICON_FA_WINE_GLASS_EMPTY "\xef\x97\x8e" // U+f5ce +#define ICON_FA_WON_SIGN "\xef\x85\x99" // U+f159 +#define ICON_FA_WORM "\xee\x96\x99" // U+e599 +#define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad +#define ICON_FA_X "X" // U+0058 +#define ICON_FA_X_RAY "\xef\x92\x97" // U+f497 +#define ICON_FA_XMARK "\xef\x80\x8d" // U+f00d +#define ICON_FA_XMARKS_LINES "\xee\x96\x9a" // U+e59a +#define ICON_FA_Y "Y" // U+0059 +#define ICON_FA_YEN_SIGN "\xef\x85\x97" // U+f157 +#define ICON_FA_YIN_YANG "\xef\x9a\xad" // U+f6ad +#define ICON_FA_Z "Z" // U+005a \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/IconsMaterialDesign.h b/SHADE_Engine/src/Editor/IconsMaterialDesign.h new file mode 100644 index 00000000..3f15892b --- /dev/null +++ b/SHADE_Engine/src/Editor/IconsMaterialDesign.h @@ -0,0 +1,2260 @@ +//Copyright (c) 2017 Juliette Foucaut and Doug Binks +// +//This software is provided 'as-is', without any express or implied +//warranty. In no event will the authors be held liable for any damages +//arising from the use of this software. +// +//Permission is granted to anyone to use this software for any purpose, +//including commercial applications, and to alter it and redistribute it +//freely, subject to the following restrictions: +// +//1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +//2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +//3. This notice may not be removed or altered from any source distribution. + +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++ +// from https://github.com/google/material-design-icons/raw/master/font/MaterialIcons-Regular.codepoints +// for use with https://github.com/google/material-design-icons/blob/master/font/MaterialIcons-Regular.ttf +#pragma once + +#define FONT_ICON_FILE_NAME_MD "MaterialIcons-Regular.ttf" + +#define ICON_MIN_MD 0xe000 +#define ICON_MAX_16_MD 0xf8ff +#define ICON_MAX_MD 0x10fffd +#define ICON_MD_10K "\xee\xa5\x91" // U+e951 +#define ICON_MD_10MP "\xee\xa5\x92" // U+e952 +#define ICON_MD_11MP "\xee\xa5\x93" // U+e953 +#define ICON_MD_123 "\xee\xae\x8d" // U+eb8d +#define ICON_MD_12MP "\xee\xa5\x94" // U+e954 +#define ICON_MD_13MP "\xee\xa5\x95" // U+e955 +#define ICON_MD_14MP "\xee\xa5\x96" // U+e956 +#define ICON_MD_15MP "\xee\xa5\x97" // U+e957 +#define ICON_MD_16MP "\xee\xa5\x98" // U+e958 +#define ICON_MD_17MP "\xee\xa5\x99" // U+e959 +#define ICON_MD_18_UP_RATING "\xef\xa3\xbd" // U+f8fd +#define ICON_MD_18MP "\xee\xa5\x9a" // U+e95a +#define ICON_MD_19MP "\xee\xa5\x9b" // U+e95b +#define ICON_MD_1K "\xee\xa5\x9c" // U+e95c +#define ICON_MD_1K_PLUS "\xee\xa5\x9d" // U+e95d +#define ICON_MD_1X_MOBILEDATA "\xee\xbf\x8d" // U+efcd +#define ICON_MD_20MP "\xee\xa5\x9e" // U+e95e +#define ICON_MD_21MP "\xee\xa5\x9f" // U+e95f +#define ICON_MD_22MP "\xee\xa5\xa0" // U+e960 +#define ICON_MD_23MP "\xee\xa5\xa1" // U+e961 +#define ICON_MD_24MP "\xee\xa5\xa2" // U+e962 +#define ICON_MD_2K "\xee\xa5\xa3" // U+e963 +#define ICON_MD_2K_PLUS "\xee\xa5\xa4" // U+e964 +#define ICON_MD_2MP "\xee\xa5\xa5" // U+e965 +#define ICON_MD_30FPS "\xee\xbf\x8e" // U+efce +#define ICON_MD_30FPS_SELECT "\xee\xbf\x8f" // U+efcf +#define ICON_MD_360 "\xee\x95\xb7" // U+e577 +#define ICON_MD_3D_ROTATION "\xee\xa1\x8d" // U+e84d +#define ICON_MD_3G_MOBILEDATA "\xee\xbf\x90" // U+efd0 +#define ICON_MD_3K "\xee\xa5\xa6" // U+e966 +#define ICON_MD_3K_PLUS "\xee\xa5\xa7" // U+e967 +#define ICON_MD_3MP "\xee\xa5\xa8" // U+e968 +#define ICON_MD_3P "\xee\xbf\x91" // U+efd1 +#define ICON_MD_4G_MOBILEDATA "\xee\xbf\x92" // U+efd2 +#define ICON_MD_4G_PLUS_MOBILEDATA "\xee\xbf\x93" // U+efd3 +#define ICON_MD_4K "\xee\x81\xb2" // U+e072 +#define ICON_MD_4K_PLUS "\xee\xa5\xa9" // U+e969 +#define ICON_MD_4MP "\xee\xa5\xaa" // U+e96a +#define ICON_MD_5G "\xee\xbc\xb8" // U+ef38 +#define ICON_MD_5K "\xee\xa5\xab" // U+e96b +#define ICON_MD_5K_PLUS "\xee\xa5\xac" // U+e96c +#define ICON_MD_5MP "\xee\xa5\xad" // U+e96d +#define ICON_MD_60FPS "\xee\xbf\x94" // U+efd4 +#define ICON_MD_60FPS_SELECT "\xee\xbf\x95" // U+efd5 +#define ICON_MD_6_FT_APART "\xef\x88\x9e" // U+f21e +#define ICON_MD_6K "\xee\xa5\xae" // U+e96e +#define ICON_MD_6K_PLUS "\xee\xa5\xaf" // U+e96f +#define ICON_MD_6MP "\xee\xa5\xb0" // U+e970 +#define ICON_MD_7K "\xee\xa5\xb1" // U+e971 +#define ICON_MD_7K_PLUS "\xee\xa5\xb2" // U+e972 +#define ICON_MD_7MP "\xee\xa5\xb3" // U+e973 +#define ICON_MD_8K "\xee\xa5\xb4" // U+e974 +#define ICON_MD_8K_PLUS "\xee\xa5\xb5" // U+e975 +#define ICON_MD_8MP "\xee\xa5\xb6" // U+e976 +#define ICON_MD_9K "\xee\xa5\xb7" // U+e977 +#define ICON_MD_9K_PLUS "\xee\xa5\xb8" // U+e978 +#define ICON_MD_9MP "\xee\xa5\xb9" // U+e979 +#define ICON_MD_ABC "\xee\xae\x94" // U+eb94 +#define ICON_MD_AC_UNIT "\xee\xac\xbb" // U+eb3b +#define ICON_MD_ACCESS_ALARM "\xee\x86\x90" // U+e190 +#define ICON_MD_ACCESS_ALARMS "\xee\x86\x91" // U+e191 +#define ICON_MD_ACCESS_TIME "\xee\x86\x92" // U+e192 +#define ICON_MD_ACCESS_TIME_FILLED "\xee\xbf\x96" // U+efd6 +#define ICON_MD_ACCESSIBILITY "\xee\xa1\x8e" // U+e84e +#define ICON_MD_ACCESSIBILITY_NEW "\xee\xa4\xac" // U+e92c +#define ICON_MD_ACCESSIBLE "\xee\xa4\x94" // U+e914 +#define ICON_MD_ACCESSIBLE_FORWARD "\xee\xa4\xb4" // U+e934 +#define ICON_MD_ACCOUNT_BALANCE "\xee\xa1\x8f" // U+e84f +#define ICON_MD_ACCOUNT_BALANCE_WALLET "\xee\xa1\x90" // U+e850 +#define ICON_MD_ACCOUNT_BOX "\xee\xa1\x91" // U+e851 +#define ICON_MD_ACCOUNT_CIRCLE "\xee\xa1\x93" // U+e853 +#define ICON_MD_ACCOUNT_TREE "\xee\xa5\xba" // U+e97a +#define ICON_MD_AD_UNITS "\xee\xbc\xb9" // U+ef39 +#define ICON_MD_ADB "\xee\x98\x8e" // U+e60e +#define ICON_MD_ADD "\xee\x85\x85" // U+e145 +#define ICON_MD_ADD_A_PHOTO "\xee\x90\xb9" // U+e439 +#define ICON_MD_ADD_ALARM "\xee\x86\x93" // U+e193 +#define ICON_MD_ADD_ALERT "\xee\x80\x83" // U+e003 +#define ICON_MD_ADD_BOX "\xee\x85\x86" // U+e146 +#define ICON_MD_ADD_BUSINESS "\xee\x9c\xa9" // U+e729 +#define ICON_MD_ADD_CALL "\xee\x83\xa8" // U+e0e8 +#define ICON_MD_ADD_CARD "\xee\xae\x86" // U+eb86 +#define ICON_MD_ADD_CHART "\xee\xa5\xbb" // U+e97b +#define ICON_MD_ADD_CIRCLE "\xee\x85\x87" // U+e147 +#define ICON_MD_ADD_CIRCLE_OUTLINE "\xee\x85\x88" // U+e148 +#define ICON_MD_ADD_COMMENT "\xee\x89\xa6" // U+e266 +#define ICON_MD_ADD_HOME "\xef\xa3\xab" // U+f8eb +#define ICON_MD_ADD_HOME_WORK "\xef\xa3\xad" // U+f8ed +#define ICON_MD_ADD_IC_CALL "\xee\xa5\xbc" // U+e97c +#define ICON_MD_ADD_LINK "\xee\x85\xb8" // U+e178 +#define ICON_MD_ADD_LOCATION "\xee\x95\xa7" // U+e567 +#define ICON_MD_ADD_LOCATION_ALT "\xee\xbc\xba" // U+ef3a +#define ICON_MD_ADD_MODERATOR "\xee\xa5\xbd" // U+e97d +#define ICON_MD_ADD_PHOTO_ALTERNATE "\xee\x90\xbe" // U+e43e +#define ICON_MD_ADD_REACTION "\xee\x87\x93" // U+e1d3 +#define ICON_MD_ADD_ROAD "\xee\xbc\xbb" // U+ef3b +#define ICON_MD_ADD_SHOPPING_CART "\xee\xa1\x94" // U+e854 +#define ICON_MD_ADD_TASK "\xef\x88\xba" // U+f23a +#define ICON_MD_ADD_TO_DRIVE "\xee\x99\x9c" // U+e65c +#define ICON_MD_ADD_TO_HOME_SCREEN "\xee\x87\xbe" // U+e1fe +#define ICON_MD_ADD_TO_PHOTOS "\xee\x8e\x9d" // U+e39d +#define ICON_MD_ADD_TO_QUEUE "\xee\x81\x9c" // U+e05c +#define ICON_MD_ADDCHART "\xee\xbc\xbc" // U+ef3c +#define ICON_MD_ADF_SCANNER "\xee\xab\x9a" // U+eada +#define ICON_MD_ADJUST "\xee\x8e\x9e" // U+e39e +#define ICON_MD_ADMIN_PANEL_SETTINGS "\xee\xbc\xbd" // U+ef3d +#define ICON_MD_ADOBE "\xee\xaa\x96" // U+ea96 +#define ICON_MD_ADS_CLICK "\xee\x9d\xa2" // U+e762 +#define ICON_MD_AGRICULTURE "\xee\xa9\xb9" // U+ea79 +#define ICON_MD_AIR "\xee\xbf\x98" // U+efd8 +#define ICON_MD_AIRLINE_SEAT_FLAT "\xee\x98\xb0" // U+e630 +#define ICON_MD_AIRLINE_SEAT_FLAT_ANGLED "\xee\x98\xb1" // U+e631 +#define ICON_MD_AIRLINE_SEAT_INDIVIDUAL_SUITE "\xee\x98\xb2" // U+e632 +#define ICON_MD_AIRLINE_SEAT_LEGROOM_EXTRA "\xee\x98\xb3" // U+e633 +#define ICON_MD_AIRLINE_SEAT_LEGROOM_NORMAL "\xee\x98\xb4" // U+e634 +#define ICON_MD_AIRLINE_SEAT_LEGROOM_REDUCED "\xee\x98\xb5" // U+e635 +#define ICON_MD_AIRLINE_SEAT_RECLINE_EXTRA "\xee\x98\xb6" // U+e636 +#define ICON_MD_AIRLINE_SEAT_RECLINE_NORMAL "\xee\x98\xb7" // U+e637 +#define ICON_MD_AIRLINE_STOPS "\xee\x9f\x90" // U+e7d0 +#define ICON_MD_AIRLINES "\xee\x9f\x8a" // U+e7ca +#define ICON_MD_AIRPLANE_TICKET "\xee\xbf\x99" // U+efd9 +#define ICON_MD_AIRPLANEMODE_ACTIVE "\xee\x86\x95" // U+e195 +#define ICON_MD_AIRPLANEMODE_INACTIVE "\xee\x86\x94" // U+e194 +#define ICON_MD_AIRPLANEMODE_OFF "\xee\x86\x94" // U+e194 +#define ICON_MD_AIRPLANEMODE_ON "\xee\x86\x95" // U+e195 +#define ICON_MD_AIRPLAY "\xee\x81\x95" // U+e055 +#define ICON_MD_AIRPORT_SHUTTLE "\xee\xac\xbc" // U+eb3c +#define ICON_MD_ALARM "\xee\xa1\x95" // U+e855 +#define ICON_MD_ALARM_ADD "\xee\xa1\x96" // U+e856 +#define ICON_MD_ALARM_OFF "\xee\xa1\x97" // U+e857 +#define ICON_MD_ALARM_ON "\xee\xa1\x98" // U+e858 +#define ICON_MD_ALBUM "\xee\x80\x99" // U+e019 +#define ICON_MD_ALIGN_HORIZONTAL_CENTER "\xee\x80\x8f" // U+e00f +#define ICON_MD_ALIGN_HORIZONTAL_LEFT "\xee\x80\x8d" // U+e00d +#define ICON_MD_ALIGN_HORIZONTAL_RIGHT "\xee\x80\x90" // U+e010 +#define ICON_MD_ALIGN_VERTICAL_BOTTOM "\xee\x80\x95" // U+e015 +#define ICON_MD_ALIGN_VERTICAL_CENTER "\xee\x80\x91" // U+e011 +#define ICON_MD_ALIGN_VERTICAL_TOP "\xee\x80\x8c" // U+e00c +#define ICON_MD_ALL_INBOX "\xee\xa5\xbf" // U+e97f +#define ICON_MD_ALL_INCLUSIVE "\xee\xac\xbd" // U+eb3d +#define ICON_MD_ALL_OUT "\xee\xa4\x8b" // U+e90b +#define ICON_MD_ALT_ROUTE "\xef\x86\x84" // U+f184 +#define ICON_MD_ALTERNATE_EMAIL "\xee\x83\xa6" // U+e0e6 +#define ICON_MD_AMP_STORIES "\xee\xa8\x93" // U+ea13 +#define ICON_MD_ANALYTICS "\xee\xbc\xbe" // U+ef3e +#define ICON_MD_ANCHOR "\xef\x87\x8d" // U+f1cd +#define ICON_MD_ANDROID "\xee\xa1\x99" // U+e859 +#define ICON_MD_ANIMATION "\xee\x9c\x9c" // U+e71c +#define ICON_MD_ANNOUNCEMENT "\xee\xa1\x9a" // U+e85a +#define ICON_MD_AOD "\xee\xbf\x9a" // U+efda +#define ICON_MD_APARTMENT "\xee\xa9\x80" // U+ea40 +#define ICON_MD_API "\xef\x86\xb7" // U+f1b7 +#define ICON_MD_APP_BLOCKING "\xee\xbc\xbf" // U+ef3f +#define ICON_MD_APP_REGISTRATION "\xee\xbd\x80" // U+ef40 +#define ICON_MD_APP_SETTINGS_ALT "\xee\xbd\x81" // U+ef41 +#define ICON_MD_APP_SHORTCUT "\xee\xab\xa4" // U+eae4 +#define ICON_MD_APPLE "\xee\xaa\x80" // U+ea80 +#define ICON_MD_APPROVAL "\xee\xa6\x82" // U+e982 +#define ICON_MD_APPS "\xee\x97\x83" // U+e5c3 +#define ICON_MD_APPS_OUTAGE "\xee\x9f\x8c" // U+e7cc +#define ICON_MD_ARCHITECTURE "\xee\xa8\xbb" // U+ea3b +#define ICON_MD_ARCHIVE "\xee\x85\x89" // U+e149 +#define ICON_MD_AREA_CHART "\xee\x9d\xb0" // U+e770 +#define ICON_MD_ARROW_BACK "\xee\x97\x84" // U+e5c4 +#define ICON_MD_ARROW_BACK_IOS "\xee\x97\xa0" // U+e5e0 +#define ICON_MD_ARROW_BACK_IOS_NEW "\xee\x8b\xaa" // U+e2ea +#define ICON_MD_ARROW_CIRCLE_DOWN "\xef\x86\x81" // U+f181 +#define ICON_MD_ARROW_CIRCLE_LEFT "\xee\xaa\xa7" // U+eaa7 +#define ICON_MD_ARROW_CIRCLE_RIGHT "\xee\xaa\xaa" // U+eaaa +#define ICON_MD_ARROW_CIRCLE_UP "\xef\x86\x82" // U+f182 +#define ICON_MD_ARROW_DOWNWARD "\xee\x97\x9b" // U+e5db +#define ICON_MD_ARROW_DROP_DOWN "\xee\x97\x85" // U+e5c5 +#define ICON_MD_ARROW_DROP_DOWN_CIRCLE "\xee\x97\x86" // U+e5c6 +#define ICON_MD_ARROW_DROP_UP "\xee\x97\x87" // U+e5c7 +#define ICON_MD_ARROW_FORWARD "\xee\x97\x88" // U+e5c8 +#define ICON_MD_ARROW_FORWARD_IOS "\xee\x97\xa1" // U+e5e1 +#define ICON_MD_ARROW_LEFT "\xee\x97\x9e" // U+e5de +#define ICON_MD_ARROW_OUTWARD "\xef\xa3\x8e" // U+f8ce +#define ICON_MD_ARROW_RIGHT "\xee\x97\x9f" // U+e5df +#define ICON_MD_ARROW_RIGHT_ALT "\xee\xa5\x81" // U+e941 +#define ICON_MD_ARROW_UPWARD "\xee\x97\x98" // U+e5d8 +#define ICON_MD_ART_TRACK "\xee\x81\xa0" // U+e060 +#define ICON_MD_ARTICLE "\xee\xbd\x82" // U+ef42 +#define ICON_MD_ASPECT_RATIO "\xee\xa1\x9b" // U+e85b +#define ICON_MD_ASSESSMENT "\xee\xa1\x9c" // U+e85c +#define ICON_MD_ASSIGNMENT "\xee\xa1\x9d" // U+e85d +#define ICON_MD_ASSIGNMENT_IND "\xee\xa1\x9e" // U+e85e +#define ICON_MD_ASSIGNMENT_LATE "\xee\xa1\x9f" // U+e85f +#define ICON_MD_ASSIGNMENT_RETURN "\xee\xa1\xa0" // U+e860 +#define ICON_MD_ASSIGNMENT_RETURNED "\xee\xa1\xa1" // U+e861 +#define ICON_MD_ASSIGNMENT_TURNED_IN "\xee\xa1\xa2" // U+e862 +#define ICON_MD_ASSIST_WALKER "\xef\xa3\x95" // U+f8d5 +#define ICON_MD_ASSISTANT "\xee\x8e\x9f" // U+e39f +#define ICON_MD_ASSISTANT_DIRECTION "\xee\xa6\x88" // U+e988 +#define ICON_MD_ASSISTANT_NAVIGATION "\xee\xa6\x89" // U+e989 +#define ICON_MD_ASSISTANT_PHOTO "\xee\x8e\xa0" // U+e3a0 +#define ICON_MD_ASSURED_WORKLOAD "\xee\xad\xaf" // U+eb6f +#define ICON_MD_ATM "\xee\x95\xb3" // U+e573 +#define ICON_MD_ATTACH_EMAIL "\xee\xa9\x9e" // U+ea5e +#define ICON_MD_ATTACH_FILE "\xee\x88\xa6" // U+e226 +#define ICON_MD_ATTACH_MONEY "\xee\x88\xa7" // U+e227 +#define ICON_MD_ATTACHMENT "\xee\x8a\xbc" // U+e2bc +#define ICON_MD_ATTRACTIONS "\xee\xa9\x92" // U+ea52 +#define ICON_MD_ATTRIBUTION "\xee\xbf\x9b" // U+efdb +#define ICON_MD_AUDIO_FILE "\xee\xae\x82" // U+eb82 +#define ICON_MD_AUDIOTRACK "\xee\x8e\xa1" // U+e3a1 +#define ICON_MD_AUTO_AWESOME "\xee\x99\x9f" // U+e65f +#define ICON_MD_AUTO_AWESOME_MOSAIC "\xee\x99\xa0" // U+e660 +#define ICON_MD_AUTO_AWESOME_MOTION "\xee\x99\xa1" // U+e661 +#define ICON_MD_AUTO_DELETE "\xee\xa9\x8c" // U+ea4c +#define ICON_MD_AUTO_FIX_HIGH "\xee\x99\xa3" // U+e663 +#define ICON_MD_AUTO_FIX_NORMAL "\xee\x99\xa4" // U+e664 +#define ICON_MD_AUTO_FIX_OFF "\xee\x99\xa5" // U+e665 +#define ICON_MD_AUTO_GRAPH "\xee\x93\xbb" // U+e4fb +#define ICON_MD_AUTO_MODE "\xee\xb0\xa0" // U+ec20 +#define ICON_MD_AUTO_STORIES "\xee\x99\xa6" // U+e666 +#define ICON_MD_AUTOFPS_SELECT "\xee\xbf\x9c" // U+efdc +#define ICON_MD_AUTORENEW "\xee\xa1\xa3" // U+e863 +#define ICON_MD_AV_TIMER "\xee\x80\x9b" // U+e01b +#define ICON_MD_BABY_CHANGING_STATION "\xef\x86\x9b" // U+f19b +#define ICON_MD_BACK_HAND "\xee\x9d\xa4" // U+e764 +#define ICON_MD_BACKPACK "\xef\x86\x9c" // U+f19c +#define ICON_MD_BACKSPACE "\xee\x85\x8a" // U+e14a +#define ICON_MD_BACKUP "\xee\xa1\xa4" // U+e864 +#define ICON_MD_BACKUP_TABLE "\xee\xbd\x83" // U+ef43 +#define ICON_MD_BADGE "\xee\xa9\xa7" // U+ea67 +#define ICON_MD_BAKERY_DINING "\xee\xa9\x93" // U+ea53 +#define ICON_MD_BALANCE "\xee\xab\xb6" // U+eaf6 +#define ICON_MD_BALCONY "\xee\x96\x8f" // U+e58f +#define ICON_MD_BALLOT "\xee\x85\xb2" // U+e172 +#define ICON_MD_BAR_CHART "\xee\x89\xab" // U+e26b +#define ICON_MD_BARCODE_READER "\xef\xa1\x9c" // U+f85c +#define ICON_MD_BATCH_PREDICTION "\xef\x83\xb5" // U+f0f5 +#define ICON_MD_BATHROOM "\xee\xbf\x9d" // U+efdd +#define ICON_MD_BATHTUB "\xee\xa9\x81" // U+ea41 +#define ICON_MD_BATTERY_0_BAR "\xee\xaf\x9c" // U+ebdc +#define ICON_MD_BATTERY_1_BAR "\xee\xaf\x99" // U+ebd9 +#define ICON_MD_BATTERY_2_BAR "\xee\xaf\xa0" // U+ebe0 +#define ICON_MD_BATTERY_3_BAR "\xee\xaf\x9d" // U+ebdd +#define ICON_MD_BATTERY_4_BAR "\xee\xaf\xa2" // U+ebe2 +#define ICON_MD_BATTERY_5_BAR "\xee\xaf\x94" // U+ebd4 +#define ICON_MD_BATTERY_6_BAR "\xee\xaf\x92" // U+ebd2 +#define ICON_MD_BATTERY_ALERT "\xee\x86\x9c" // U+e19c +#define ICON_MD_BATTERY_CHARGING_FULL "\xee\x86\xa3" // U+e1a3 +#define ICON_MD_BATTERY_FULL "\xee\x86\xa4" // U+e1a4 +#define ICON_MD_BATTERY_SAVER "\xee\xbf\x9e" // U+efde +#define ICON_MD_BATTERY_STD "\xee\x86\xa5" // U+e1a5 +#define ICON_MD_BATTERY_UNKNOWN "\xee\x86\xa6" // U+e1a6 +#define ICON_MD_BEACH_ACCESS "\xee\xac\xbe" // U+eb3e +#define ICON_MD_BED "\xee\xbf\x9f" // U+efdf +#define ICON_MD_BEDROOM_BABY "\xee\xbf\xa0" // U+efe0 +#define ICON_MD_BEDROOM_CHILD "\xee\xbf\xa1" // U+efe1 +#define ICON_MD_BEDROOM_PARENT "\xee\xbf\xa2" // U+efe2 +#define ICON_MD_BEDTIME "\xee\xbd\x84" // U+ef44 +#define ICON_MD_BEDTIME_OFF "\xee\xad\xb6" // U+eb76 +#define ICON_MD_BEENHERE "\xee\x94\xad" // U+e52d +#define ICON_MD_BENTO "\xef\x87\xb4" // U+f1f4 +#define ICON_MD_BIKE_SCOOTER "\xee\xbd\x85" // U+ef45 +#define ICON_MD_BIOTECH "\xee\xa8\xba" // U+ea3a +#define ICON_MD_BLENDER "\xee\xbf\xa3" // U+efe3 +#define ICON_MD_BLIND "\xef\xa3\x96" // U+f8d6 +#define ICON_MD_BLINDS "\xee\x8a\x86" // U+e286 +#define ICON_MD_BLINDS_CLOSED "\xee\xb0\x9f" // U+ec1f +#define ICON_MD_BLOCK "\xee\x85\x8b" // U+e14b +#define ICON_MD_BLOCK_FLIPPED "\xee\xbd\x86" // U+ef46 +#define ICON_MD_BLOODTYPE "\xee\xbf\xa4" // U+efe4 +#define ICON_MD_BLUETOOTH "\xee\x86\xa7" // U+e1a7 +#define ICON_MD_BLUETOOTH_AUDIO "\xee\x98\x8f" // U+e60f +#define ICON_MD_BLUETOOTH_CONNECTED "\xee\x86\xa8" // U+e1a8 +#define ICON_MD_BLUETOOTH_DISABLED "\xee\x86\xa9" // U+e1a9 +#define ICON_MD_BLUETOOTH_DRIVE "\xee\xbf\xa5" // U+efe5 +#define ICON_MD_BLUETOOTH_SEARCHING "\xee\x86\xaa" // U+e1aa +#define ICON_MD_BLUR_CIRCULAR "\xee\x8e\xa2" // U+e3a2 +#define ICON_MD_BLUR_LINEAR "\xee\x8e\xa3" // U+e3a3 +#define ICON_MD_BLUR_OFF "\xee\x8e\xa4" // U+e3a4 +#define ICON_MD_BLUR_ON "\xee\x8e\xa5" // U+e3a5 +#define ICON_MD_BOLT "\xee\xa8\x8b" // U+ea0b +#define ICON_MD_BOOK "\xee\xa1\xa5" // U+e865 +#define ICON_MD_BOOK_ONLINE "\xef\x88\x97" // U+f217 +#define ICON_MD_BOOKMARK "\xee\xa1\xa6" // U+e866 +#define ICON_MD_BOOKMARK_ADD "\xee\x96\x98" // U+e598 +#define ICON_MD_BOOKMARK_ADDED "\xee\x96\x99" // U+e599 +#define ICON_MD_BOOKMARK_BORDER "\xee\xa1\xa7" // U+e867 +#define ICON_MD_BOOKMARK_OUTLINE "\xee\xa1\xa7" // U+e867 +#define ICON_MD_BOOKMARK_REMOVE "\xee\x96\x9a" // U+e59a +#define ICON_MD_BOOKMARKS "\xee\xa6\x8b" // U+e98b +#define ICON_MD_BORDER_ALL "\xee\x88\xa8" // U+e228 +#define ICON_MD_BORDER_BOTTOM "\xee\x88\xa9" // U+e229 +#define ICON_MD_BORDER_CLEAR "\xee\x88\xaa" // U+e22a +#define ICON_MD_BORDER_COLOR "\xee\x88\xab" // U+e22b +#define ICON_MD_BORDER_HORIZONTAL "\xee\x88\xac" // U+e22c +#define ICON_MD_BORDER_INNER "\xee\x88\xad" // U+e22d +#define ICON_MD_BORDER_LEFT "\xee\x88\xae" // U+e22e +#define ICON_MD_BORDER_OUTER "\xee\x88\xaf" // U+e22f +#define ICON_MD_BORDER_RIGHT "\xee\x88\xb0" // U+e230 +#define ICON_MD_BORDER_STYLE "\xee\x88\xb1" // U+e231 +#define ICON_MD_BORDER_TOP "\xee\x88\xb2" // U+e232 +#define ICON_MD_BORDER_VERTICAL "\xee\x88\xb3" // U+e233 +#define ICON_MD_BOY "\xee\xad\xa7" // U+eb67 +#define ICON_MD_BRANDING_WATERMARK "\xee\x81\xab" // U+e06b +#define ICON_MD_BREAKFAST_DINING "\xee\xa9\x94" // U+ea54 +#define ICON_MD_BRIGHTNESS_1 "\xee\x8e\xa6" // U+e3a6 +#define ICON_MD_BRIGHTNESS_2 "\xee\x8e\xa7" // U+e3a7 +#define ICON_MD_BRIGHTNESS_3 "\xee\x8e\xa8" // U+e3a8 +#define ICON_MD_BRIGHTNESS_4 "\xee\x8e\xa9" // U+e3a9 +#define ICON_MD_BRIGHTNESS_5 "\xee\x8e\xaa" // U+e3aa +#define ICON_MD_BRIGHTNESS_6 "\xee\x8e\xab" // U+e3ab +#define ICON_MD_BRIGHTNESS_7 "\xee\x8e\xac" // U+e3ac +#define ICON_MD_BRIGHTNESS_AUTO "\xee\x86\xab" // U+e1ab +#define ICON_MD_BRIGHTNESS_HIGH "\xee\x86\xac" // U+e1ac +#define ICON_MD_BRIGHTNESS_LOW "\xee\x86\xad" // U+e1ad +#define ICON_MD_BRIGHTNESS_MEDIUM "\xee\x86\xae" // U+e1ae +#define ICON_MD_BROADCAST_ON_HOME "\xef\xa3\xb8" // U+f8f8 +#define ICON_MD_BROADCAST_ON_PERSONAL "\xef\xa3\xb9" // U+f8f9 +#define ICON_MD_BROKEN_IMAGE "\xee\x8e\xad" // U+e3ad +#define ICON_MD_BROWSE_GALLERY "\xee\xaf\x91" // U+ebd1 +#define ICON_MD_BROWSER_NOT_SUPPORTED "\xee\xbd\x87" // U+ef47 +#define ICON_MD_BROWSER_UPDATED "\xee\x9f\x8f" // U+e7cf +#define ICON_MD_BRUNCH_DINING "\xee\xa9\xb3" // U+ea73 +#define ICON_MD_BRUSH "\xee\x8e\xae" // U+e3ae +#define ICON_MD_BUBBLE_CHART "\xee\x9b\x9d" // U+e6dd +#define ICON_MD_BUG_REPORT "\xee\xa1\xa8" // U+e868 +#define ICON_MD_BUILD "\xee\xa1\xa9" // U+e869 +#define ICON_MD_BUILD_CIRCLE "\xee\xbd\x88" // U+ef48 +#define ICON_MD_BUNGALOW "\xee\x96\x91" // U+e591 +#define ICON_MD_BURST_MODE "\xee\x90\xbc" // U+e43c +#define ICON_MD_BUS_ALERT "\xee\xa6\x8f" // U+e98f +#define ICON_MD_BUSINESS "\xee\x82\xaf" // U+e0af +#define ICON_MD_BUSINESS_CENTER "\xee\xac\xbf" // U+eb3f +#define ICON_MD_CABIN "\xee\x96\x89" // U+e589 +#define ICON_MD_CABLE "\xee\xbf\xa6" // U+efe6 +#define ICON_MD_CACHED "\xee\xa1\xaa" // U+e86a +#define ICON_MD_CAKE "\xee\x9f\xa9" // U+e7e9 +#define ICON_MD_CALCULATE "\xee\xa9\x9f" // U+ea5f +#define ICON_MD_CALENDAR_MONTH "\xee\xaf\x8c" // U+ebcc +#define ICON_MD_CALENDAR_TODAY "\xee\xa4\xb5" // U+e935 +#define ICON_MD_CALENDAR_VIEW_DAY "\xee\xa4\xb6" // U+e936 +#define ICON_MD_CALENDAR_VIEW_MONTH "\xee\xbf\xa7" // U+efe7 +#define ICON_MD_CALENDAR_VIEW_WEEK "\xee\xbf\xa8" // U+efe8 +#define ICON_MD_CALL "\xee\x82\xb0" // U+e0b0 +#define ICON_MD_CALL_END "\xee\x82\xb1" // U+e0b1 +#define ICON_MD_CALL_MADE "\xee\x82\xb2" // U+e0b2 +#define ICON_MD_CALL_MERGE "\xee\x82\xb3" // U+e0b3 +#define ICON_MD_CALL_MISSED "\xee\x82\xb4" // U+e0b4 +#define ICON_MD_CALL_MISSED_OUTGOING "\xee\x83\xa4" // U+e0e4 +#define ICON_MD_CALL_RECEIVED "\xee\x82\xb5" // U+e0b5 +#define ICON_MD_CALL_SPLIT "\xee\x82\xb6" // U+e0b6 +#define ICON_MD_CALL_TO_ACTION "\xee\x81\xac" // U+e06c +#define ICON_MD_CAMERA "\xee\x8e\xaf" // U+e3af +#define ICON_MD_CAMERA_ALT "\xee\x8e\xb0" // U+e3b0 +#define ICON_MD_CAMERA_ENHANCE "\xee\xa3\xbc" // U+e8fc +#define ICON_MD_CAMERA_FRONT "\xee\x8e\xb1" // U+e3b1 +#define ICON_MD_CAMERA_INDOOR "\xee\xbf\xa9" // U+efe9 +#define ICON_MD_CAMERA_OUTDOOR "\xee\xbf\xaa" // U+efea +#define ICON_MD_CAMERA_REAR "\xee\x8e\xb2" // U+e3b2 +#define ICON_MD_CAMERA_ROLL "\xee\x8e\xb3" // U+e3b3 +#define ICON_MD_CAMERASWITCH "\xee\xbf\xab" // U+efeb +#define ICON_MD_CAMPAIGN "\xee\xbd\x89" // U+ef49 +#define ICON_MD_CANCEL "\xee\x97\x89" // U+e5c9 +#define ICON_MD_CANCEL_PRESENTATION "\xee\x83\xa9" // U+e0e9 +#define ICON_MD_CANCEL_SCHEDULE_SEND "\xee\xa8\xb9" // U+ea39 +#define ICON_MD_CANDLESTICK_CHART "\xee\xab\x94" // U+ead4 +#define ICON_MD_CAR_CRASH "\xee\xaf\xb2" // U+ebf2 +#define ICON_MD_CAR_RENTAL "\xee\xa9\x95" // U+ea55 +#define ICON_MD_CAR_REPAIR "\xee\xa9\x96" // U+ea56 +#define ICON_MD_CARD_GIFTCARD "\xee\xa3\xb6" // U+e8f6 +#define ICON_MD_CARD_MEMBERSHIP "\xee\xa3\xb7" // U+e8f7 +#define ICON_MD_CARD_TRAVEL "\xee\xa3\xb8" // U+e8f8 +#define ICON_MD_CARPENTER "\xef\x87\xb8" // U+f1f8 +#define ICON_MD_CASES "\xee\xa6\x92" // U+e992 +#define ICON_MD_CASINO "\xee\xad\x80" // U+eb40 +#define ICON_MD_CAST "\xee\x8c\x87" // U+e307 +#define ICON_MD_CAST_CONNECTED "\xee\x8c\x88" // U+e308 +#define ICON_MD_CAST_FOR_EDUCATION "\xee\xbf\xac" // U+efec +#define ICON_MD_CASTLE "\xee\xaa\xb1" // U+eab1 +#define ICON_MD_CATCHING_POKEMON "\xee\x94\x88" // U+e508 +#define ICON_MD_CATEGORY "\xee\x95\xb4" // U+e574 +#define ICON_MD_CELEBRATION "\xee\xa9\xa5" // U+ea65 +#define ICON_MD_CELL_TOWER "\xee\xae\xba" // U+ebba +#define ICON_MD_CELL_WIFI "\xee\x83\xac" // U+e0ec +#define ICON_MD_CENTER_FOCUS_STRONG "\xee\x8e\xb4" // U+e3b4 +#define ICON_MD_CENTER_FOCUS_WEAK "\xee\x8e\xb5" // U+e3b5 +#define ICON_MD_CHAIR "\xee\xbf\xad" // U+efed +#define ICON_MD_CHAIR_ALT "\xee\xbf\xae" // U+efee +#define ICON_MD_CHALET "\xee\x96\x85" // U+e585 +#define ICON_MD_CHANGE_CIRCLE "\xee\x8b\xa7" // U+e2e7 +#define ICON_MD_CHANGE_HISTORY "\xee\xa1\xab" // U+e86b +#define ICON_MD_CHARGING_STATION "\xef\x86\x9d" // U+f19d +#define ICON_MD_CHAT "\xee\x82\xb7" // U+e0b7 +#define ICON_MD_CHAT_BUBBLE "\xee\x83\x8a" // U+e0ca +#define ICON_MD_CHAT_BUBBLE_OUTLINE "\xee\x83\x8b" // U+e0cb +#define ICON_MD_CHECK "\xee\x97\x8a" // U+e5ca +#define ICON_MD_CHECK_BOX "\xee\xa0\xb4" // U+e834 +#define ICON_MD_CHECK_BOX_OUTLINE_BLANK "\xee\xa0\xb5" // U+e835 +#define ICON_MD_CHECK_CIRCLE "\xee\xa1\xac" // U+e86c +#define ICON_MD_CHECK_CIRCLE_OUTLINE "\xee\xa4\xad" // U+e92d +#define ICON_MD_CHECKLIST "\xee\x9a\xb1" // U+e6b1 +#define ICON_MD_CHECKLIST_RTL "\xee\x9a\xb3" // U+e6b3 +#define ICON_MD_CHECKROOM "\xef\x86\x9e" // U+f19e +#define ICON_MD_CHEVRON_LEFT "\xee\x97\x8b" // U+e5cb +#define ICON_MD_CHEVRON_RIGHT "\xee\x97\x8c" // U+e5cc +#define ICON_MD_CHILD_CARE "\xee\xad\x81" // U+eb41 +#define ICON_MD_CHILD_FRIENDLY "\xee\xad\x82" // U+eb42 +#define ICON_MD_CHROME_READER_MODE "\xee\xa1\xad" // U+e86d +#define ICON_MD_CHURCH "\xee\xaa\xae" // U+eaae +#define ICON_MD_CIRCLE "\xee\xbd\x8a" // U+ef4a +#define ICON_MD_CIRCLE_NOTIFICATIONS "\xee\xa6\x94" // U+e994 +#define ICON_MD_CLASS "\xee\xa1\xae" // U+e86e +#define ICON_MD_CLEAN_HANDS "\xef\x88\x9f" // U+f21f +#define ICON_MD_CLEANING_SERVICES "\xef\x83\xbf" // U+f0ff +#define ICON_MD_CLEAR "\xee\x85\x8c" // U+e14c +#define ICON_MD_CLEAR_ALL "\xee\x82\xb8" // U+e0b8 +#define ICON_MD_CLOSE "\xee\x97\x8d" // U+e5cd +#define ICON_MD_CLOSE_FULLSCREEN "\xef\x87\x8f" // U+f1cf +#define ICON_MD_CLOSED_CAPTION "\xee\x80\x9c" // U+e01c +#define ICON_MD_CLOSED_CAPTION_DISABLED "\xef\x87\x9c" // U+f1dc +#define ICON_MD_CLOSED_CAPTION_OFF "\xee\xa6\x96" // U+e996 +#define ICON_MD_CLOUD "\xee\x8a\xbd" // U+e2bd +#define ICON_MD_CLOUD_CIRCLE "\xee\x8a\xbe" // U+e2be +#define ICON_MD_CLOUD_DONE "\xee\x8a\xbf" // U+e2bf +#define ICON_MD_CLOUD_DOWNLOAD "\xee\x8b\x80" // U+e2c0 +#define ICON_MD_CLOUD_OFF "\xee\x8b\x81" // U+e2c1 +#define ICON_MD_CLOUD_QUEUE "\xee\x8b\x82" // U+e2c2 +#define ICON_MD_CLOUD_SYNC "\xee\xad\x9a" // U+eb5a +#define ICON_MD_CLOUD_UPLOAD "\xee\x8b\x83" // U+e2c3 +#define ICON_MD_CLOUDY_SNOWING "\xee\xa0\x90" // U+e810 +#define ICON_MD_CO2 "\xee\x9e\xb0" // U+e7b0 +#define ICON_MD_CO_PRESENT "\xee\xab\xb0" // U+eaf0 +#define ICON_MD_CODE "\xee\xa1\xaf" // U+e86f +#define ICON_MD_CODE_OFF "\xee\x93\xb3" // U+e4f3 +#define ICON_MD_COFFEE "\xee\xbf\xaf" // U+efef +#define ICON_MD_COFFEE_MAKER "\xee\xbf\xb0" // U+eff0 +#define ICON_MD_COLLECTIONS "\xee\x8e\xb6" // U+e3b6 +#define ICON_MD_COLLECTIONS_BOOKMARK "\xee\x90\xb1" // U+e431 +#define ICON_MD_COLOR_LENS "\xee\x8e\xb7" // U+e3b7 +#define ICON_MD_COLORIZE "\xee\x8e\xb8" // U+e3b8 +#define ICON_MD_COMMENT "\xee\x82\xb9" // U+e0b9 +#define ICON_MD_COMMENT_BANK "\xee\xa9\x8e" // U+ea4e +#define ICON_MD_COMMENTS_DISABLED "\xee\x9e\xa2" // U+e7a2 +#define ICON_MD_COMMIT "\xee\xab\xb5" // U+eaf5 +#define ICON_MD_COMMUTE "\xee\xa5\x80" // U+e940 +#define ICON_MD_COMPARE "\xee\x8e\xb9" // U+e3b9 +#define ICON_MD_COMPARE_ARROWS "\xee\xa4\x95" // U+e915 +#define ICON_MD_COMPASS_CALIBRATION "\xee\x95\xbc" // U+e57c +#define ICON_MD_COMPOST "\xee\x9d\xa1" // U+e761 +#define ICON_MD_COMPRESS "\xee\xa5\x8d" // U+e94d +#define ICON_MD_COMPUTER "\xee\x8c\x8a" // U+e30a +#define ICON_MD_CONFIRMATION_NUM "\xee\x98\xb8" // U+e638 +#define ICON_MD_CONFIRMATION_NUMBER "\xee\x98\xb8" // U+e638 +#define ICON_MD_CONNECT_WITHOUT_CONTACT "\xef\x88\xa3" // U+f223 +#define ICON_MD_CONNECTED_TV "\xee\xa6\x98" // U+e998 +#define ICON_MD_CONNECTING_AIRPORTS "\xee\x9f\x89" // U+e7c9 +#define ICON_MD_CONSTRUCTION "\xee\xa8\xbc" // U+ea3c +#define ICON_MD_CONTACT_EMERGENCY "\xef\xa3\x91" // U+f8d1 +#define ICON_MD_CONTACT_MAIL "\xee\x83\x90" // U+e0d0 +#define ICON_MD_CONTACT_PAGE "\xef\x88\xae" // U+f22e +#define ICON_MD_CONTACT_PHONE "\xee\x83\x8f" // U+e0cf +#define ICON_MD_CONTACT_SUPPORT "\xee\xa5\x8c" // U+e94c +#define ICON_MD_CONTACTLESS "\xee\xa9\xb1" // U+ea71 +#define ICON_MD_CONTACTS "\xee\x82\xba" // U+e0ba +#define ICON_MD_CONTENT_COPY "\xee\x85\x8d" // U+e14d +#define ICON_MD_CONTENT_CUT "\xee\x85\x8e" // U+e14e +#define ICON_MD_CONTENT_PASTE "\xee\x85\x8f" // U+e14f +#define ICON_MD_CONTENT_PASTE_GO "\xee\xaa\x8e" // U+ea8e +#define ICON_MD_CONTENT_PASTE_OFF "\xee\x93\xb8" // U+e4f8 +#define ICON_MD_CONTENT_PASTE_SEARCH "\xee\xaa\x9b" // U+ea9b +#define ICON_MD_CONTRAST "\xee\xac\xb7" // U+eb37 +#define ICON_MD_CONTROL_CAMERA "\xee\x81\xb4" // U+e074 +#define ICON_MD_CONTROL_POINT "\xee\x8e\xba" // U+e3ba +#define ICON_MD_CONTROL_POINT_DUPLICATE "\xee\x8e\xbb" // U+e3bb +#define ICON_MD_CONVEYOR_BELT "\xef\xa1\xa7" // U+f867 +#define ICON_MD_COOKIE "\xee\xaa\xac" // U+eaac +#define ICON_MD_COPY_ALL "\xee\x8b\xac" // U+e2ec +#define ICON_MD_COPYRIGHT "\xee\xa4\x8c" // U+e90c +#define ICON_MD_CORONAVIRUS "\xef\x88\xa1" // U+f221 +#define ICON_MD_CORPORATE_FARE "\xef\x87\x90" // U+f1d0 +#define ICON_MD_COTTAGE "\xee\x96\x87" // U+e587 +#define ICON_MD_COUNTERTOPS "\xef\x87\xb7" // U+f1f7 +#define ICON_MD_CREATE "\xee\x85\x90" // U+e150 +#define ICON_MD_CREATE_NEW_FOLDER "\xee\x8b\x8c" // U+e2cc +#define ICON_MD_CREDIT_CARD "\xee\xa1\xb0" // U+e870 +#define ICON_MD_CREDIT_CARD_OFF "\xee\x93\xb4" // U+e4f4 +#define ICON_MD_CREDIT_SCORE "\xee\xbf\xb1" // U+eff1 +#define ICON_MD_CRIB "\xee\x96\x88" // U+e588 +#define ICON_MD_CRISIS_ALERT "\xee\xaf\xa9" // U+ebe9 +#define ICON_MD_CROP "\xee\x8e\xbe" // U+e3be +#define ICON_MD_CROP_16_9 "\xee\x8e\xbc" // U+e3bc +#define ICON_MD_CROP_3_2 "\xee\x8e\xbd" // U+e3bd +#define ICON_MD_CROP_5_4 "\xee\x8e\xbf" // U+e3bf +#define ICON_MD_CROP_7_5 "\xee\x8f\x80" // U+e3c0 +#define ICON_MD_CROP_DIN "\xee\x8f\x81" // U+e3c1 +#define ICON_MD_CROP_FREE "\xee\x8f\x82" // U+e3c2 +#define ICON_MD_CROP_LANDSCAPE "\xee\x8f\x83" // U+e3c3 +#define ICON_MD_CROP_ORIGINAL "\xee\x8f\x84" // U+e3c4 +#define ICON_MD_CROP_PORTRAIT "\xee\x8f\x85" // U+e3c5 +#define ICON_MD_CROP_ROTATE "\xee\x90\xb7" // U+e437 +#define ICON_MD_CROP_SQUARE "\xee\x8f\x86" // U+e3c6 +#define ICON_MD_CRUELTY_FREE "\xee\x9e\x99" // U+e799 +#define ICON_MD_CSS "\xee\xae\x93" // U+eb93 +#define ICON_MD_CURRENCY_BITCOIN "\xee\xaf\x85" // U+ebc5 +#define ICON_MD_CURRENCY_EXCHANGE "\xee\xad\xb0" // U+eb70 +#define ICON_MD_CURRENCY_FRANC "\xee\xab\xba" // U+eafa +#define ICON_MD_CURRENCY_LIRA "\xee\xab\xaf" // U+eaef +#define ICON_MD_CURRENCY_POUND "\xee\xab\xb1" // U+eaf1 +#define ICON_MD_CURRENCY_RUBLE "\xee\xab\xac" // U+eaec +#define ICON_MD_CURRENCY_RUPEE "\xee\xab\xb7" // U+eaf7 +#define ICON_MD_CURRENCY_YEN "\xee\xab\xbb" // U+eafb +#define ICON_MD_CURRENCY_YUAN "\xee\xab\xb9" // U+eaf9 +#define ICON_MD_CURTAINS "\xee\xb0\x9e" // U+ec1e +#define ICON_MD_CURTAINS_CLOSED "\xee\xb0\x9d" // U+ec1d +#define ICON_MD_CYCLONE "\xee\xaf\x95" // U+ebd5 +#define ICON_MD_DANGEROUS "\xee\xa6\x9a" // U+e99a +#define ICON_MD_DARK_MODE "\xee\x94\x9c" // U+e51c +#define ICON_MD_DASHBOARD "\xee\xa1\xb1" // U+e871 +#define ICON_MD_DASHBOARD_CUSTOMIZE "\xee\xa6\x9b" // U+e99b +#define ICON_MD_DATA_ARRAY "\xee\xab\x91" // U+ead1 +#define ICON_MD_DATA_EXPLORATION "\xee\x9d\xaf" // U+e76f +#define ICON_MD_DATA_OBJECT "\xee\xab\x93" // U+ead3 +#define ICON_MD_DATA_SAVER_OFF "\xee\xbf\xb2" // U+eff2 +#define ICON_MD_DATA_SAVER_ON "\xee\xbf\xb3" // U+eff3 +#define ICON_MD_DATA_THRESHOLDING "\xee\xae\x9f" // U+eb9f +#define ICON_MD_DATA_USAGE "\xee\x86\xaf" // U+e1af +#define ICON_MD_DATASET "\xef\xa3\xae" // U+f8ee +#define ICON_MD_DATASET_LINKED "\xef\xa3\xaf" // U+f8ef +#define ICON_MD_DATE_RANGE "\xee\xa4\x96" // U+e916 +#define ICON_MD_DEBLUR "\xee\xad\xb7" // U+eb77 +#define ICON_MD_DECK "\xee\xa9\x82" // U+ea42 +#define ICON_MD_DEHAZE "\xee\x8f\x87" // U+e3c7 +#define ICON_MD_DELETE "\xee\xa1\xb2" // U+e872 +#define ICON_MD_DELETE_FOREVER "\xee\xa4\xab" // U+e92b +#define ICON_MD_DELETE_OUTLINE "\xee\xa4\xae" // U+e92e +#define ICON_MD_DELETE_SWEEP "\xee\x85\xac" // U+e16c +#define ICON_MD_DELIVERY_DINING "\xee\xa9\xb2" // U+ea72 +#define ICON_MD_DENSITY_LARGE "\xee\xae\xa9" // U+eba9 +#define ICON_MD_DENSITY_MEDIUM "\xee\xae\x9e" // U+eb9e +#define ICON_MD_DENSITY_SMALL "\xee\xae\xa8" // U+eba8 +#define ICON_MD_DEPARTURE_BOARD "\xee\x95\xb6" // U+e576 +#define ICON_MD_DESCRIPTION "\xee\xa1\xb3" // U+e873 +#define ICON_MD_DESELECT "\xee\xae\xb6" // U+ebb6 +#define ICON_MD_DESIGN_SERVICES "\xef\x84\x8a" // U+f10a +#define ICON_MD_DESK "\xef\xa3\xb4" // U+f8f4 +#define ICON_MD_DESKTOP_ACCESS_DISABLED "\xee\xa6\x9d" // U+e99d +#define ICON_MD_DESKTOP_MAC "\xee\x8c\x8b" // U+e30b +#define ICON_MD_DESKTOP_WINDOWS "\xee\x8c\x8c" // U+e30c +#define ICON_MD_DETAILS "\xee\x8f\x88" // U+e3c8 +#define ICON_MD_DEVELOPER_BOARD "\xee\x8c\x8d" // U+e30d +#define ICON_MD_DEVELOPER_BOARD_OFF "\xee\x93\xbf" // U+e4ff +#define ICON_MD_DEVELOPER_MODE "\xee\x86\xb0" // U+e1b0 +#define ICON_MD_DEVICE_HUB "\xee\x8c\xb5" // U+e335 +#define ICON_MD_DEVICE_THERMOSTAT "\xee\x87\xbf" // U+e1ff +#define ICON_MD_DEVICE_UNKNOWN "\xee\x8c\xb9" // U+e339 +#define ICON_MD_DEVICES "\xee\x86\xb1" // U+e1b1 +#define ICON_MD_DEVICES_FOLD "\xee\xaf\x9e" // U+ebde +#define ICON_MD_DEVICES_OTHER "\xee\x8c\xb7" // U+e337 +#define ICON_MD_DEW_POINT "\xef\xa1\xb9" // U+f879 +#define ICON_MD_DIALER_SIP "\xee\x82\xbb" // U+e0bb +#define ICON_MD_DIALPAD "\xee\x82\xbc" // U+e0bc +#define ICON_MD_DIAMOND "\xee\xab\x95" // U+ead5 +#define ICON_MD_DIFFERENCE "\xee\xad\xbd" // U+eb7d +#define ICON_MD_DINING "\xee\xbf\xb4" // U+eff4 +#define ICON_MD_DINNER_DINING "\xee\xa9\x97" // U+ea57 +#define ICON_MD_DIRECTIONS "\xee\x94\xae" // U+e52e +#define ICON_MD_DIRECTIONS_BIKE "\xee\x94\xaf" // U+e52f +#define ICON_MD_DIRECTIONS_BOAT "\xee\x94\xb2" // U+e532 +#define ICON_MD_DIRECTIONS_BOAT_FILLED "\xee\xbf\xb5" // U+eff5 +#define ICON_MD_DIRECTIONS_BUS "\xee\x94\xb0" // U+e530 +#define ICON_MD_DIRECTIONS_BUS_FILLED "\xee\xbf\xb6" // U+eff6 +#define ICON_MD_DIRECTIONS_CAR "\xee\x94\xb1" // U+e531 +#define ICON_MD_DIRECTIONS_CAR_FILLED "\xee\xbf\xb7" // U+eff7 +#define ICON_MD_DIRECTIONS_FERRY "\xee\x94\xb2" // U+e532 +#define ICON_MD_DIRECTIONS_OFF "\xef\x84\x8f" // U+f10f +#define ICON_MD_DIRECTIONS_RAILWAY "\xee\x94\xb4" // U+e534 +#define ICON_MD_DIRECTIONS_RAILWAY_FILLED "\xee\xbf\xb8" // U+eff8 +#define ICON_MD_DIRECTIONS_RUN "\xee\x95\xa6" // U+e566 +#define ICON_MD_DIRECTIONS_SUBWAY "\xee\x94\xb3" // U+e533 +#define ICON_MD_DIRECTIONS_SUBWAY_FILLED "\xee\xbf\xb9" // U+eff9 +#define ICON_MD_DIRECTIONS_TRAIN "\xee\x94\xb4" // U+e534 +#define ICON_MD_DIRECTIONS_TRANSIT "\xee\x94\xb5" // U+e535 +#define ICON_MD_DIRECTIONS_TRANSIT_FILLED "\xee\xbf\xba" // U+effa +#define ICON_MD_DIRECTIONS_WALK "\xee\x94\xb6" // U+e536 +#define ICON_MD_DIRTY_LENS "\xee\xbd\x8b" // U+ef4b +#define ICON_MD_DISABLED_BY_DEFAULT "\xef\x88\xb0" // U+f230 +#define ICON_MD_DISABLED_VISIBLE "\xee\x9d\xae" // U+e76e +#define ICON_MD_DISC_FULL "\xee\x98\x90" // U+e610 +#define ICON_MD_DISCORD "\xee\xa9\xac" // U+ea6c +#define ICON_MD_DISCOUNT "\xee\xaf\x89" // U+ebc9 +#define ICON_MD_DISPLAY_SETTINGS "\xee\xae\x97" // U+eb97 +#define ICON_MD_DIVERSITY_1 "\xef\xa3\x97" // U+f8d7 +#define ICON_MD_DIVERSITY_2 "\xef\xa3\x98" // U+f8d8 +#define ICON_MD_DIVERSITY_3 "\xef\xa3\x99" // U+f8d9 +#define ICON_MD_DND_FORWARDSLASH "\xee\x98\x91" // U+e611 +#define ICON_MD_DNS "\xee\xa1\xb5" // U+e875 +#define ICON_MD_DO_DISTURB "\xef\x82\x8c" // U+f08c +#define ICON_MD_DO_DISTURB_ALT "\xef\x82\x8d" // U+f08d +#define ICON_MD_DO_DISTURB_OFF "\xef\x82\x8e" // U+f08e +#define ICON_MD_DO_DISTURB_ON "\xef\x82\x8f" // U+f08f +#define ICON_MD_DO_NOT_DISTURB "\xee\x98\x92" // U+e612 +#define ICON_MD_DO_NOT_DISTURB_ALT "\xee\x98\x91" // U+e611 +#define ICON_MD_DO_NOT_DISTURB_OFF "\xee\x99\x83" // U+e643 +#define ICON_MD_DO_NOT_DISTURB_ON "\xee\x99\x84" // U+e644 +#define ICON_MD_DO_NOT_DISTURB_ON_TOTAL_SILENCE "\xee\xbf\xbb" // U+effb +#define ICON_MD_DO_NOT_STEP "\xef\x86\x9f" // U+f19f +#define ICON_MD_DO_NOT_TOUCH "\xef\x86\xb0" // U+f1b0 +#define ICON_MD_DOCK "\xee\x8c\x8e" // U+e30e +#define ICON_MD_DOCUMENT_SCANNER "\xee\x97\xba" // U+e5fa +#define ICON_MD_DOMAIN "\xee\x9f\xae" // U+e7ee +#define ICON_MD_DOMAIN_ADD "\xee\xad\xa2" // U+eb62 +#define ICON_MD_DOMAIN_DISABLED "\xee\x83\xaf" // U+e0ef +#define ICON_MD_DOMAIN_VERIFICATION "\xee\xbd\x8c" // U+ef4c +#define ICON_MD_DONE "\xee\xa1\xb6" // U+e876 +#define ICON_MD_DONE_ALL "\xee\xa1\xb7" // U+e877 +#define ICON_MD_DONE_OUTLINE "\xee\xa4\xaf" // U+e92f +#define ICON_MD_DONUT_LARGE "\xee\xa4\x97" // U+e917 +#define ICON_MD_DONUT_SMALL "\xee\xa4\x98" // U+e918 +#define ICON_MD_DOOR_BACK "\xee\xbf\xbc" // U+effc +#define ICON_MD_DOOR_FRONT "\xee\xbf\xbd" // U+effd +#define ICON_MD_DOOR_SLIDING "\xee\xbf\xbe" // U+effe +#define ICON_MD_DOORBELL "\xee\xbf\xbf" // U+efff +#define ICON_MD_DOUBLE_ARROW "\xee\xa9\x90" // U+ea50 +#define ICON_MD_DOWNHILL_SKIING "\xee\x94\x89" // U+e509 +#define ICON_MD_DOWNLOAD "\xef\x82\x90" // U+f090 +#define ICON_MD_DOWNLOAD_DONE "\xef\x82\x91" // U+f091 +#define ICON_MD_DOWNLOAD_FOR_OFFLINE "\xef\x80\x80" // U+f000 +#define ICON_MD_DOWNLOADING "\xef\x80\x81" // U+f001 +#define ICON_MD_DRAFTS "\xee\x85\x91" // U+e151 +#define ICON_MD_DRAG_HANDLE "\xee\x89\x9d" // U+e25d +#define ICON_MD_DRAG_INDICATOR "\xee\xa5\x85" // U+e945 +#define ICON_MD_DRAW "\xee\x9d\x86" // U+e746 +#define ICON_MD_DRIVE_ETA "\xee\x98\x93" // U+e613 +#define ICON_MD_DRIVE_FILE_MOVE "\xee\x99\xb5" // U+e675 +#define ICON_MD_DRIVE_FILE_MOVE_OUTLINE "\xee\xa6\xa1" // U+e9a1 +#define ICON_MD_DRIVE_FILE_MOVE_RTL "\xee\x9d\xad" // U+e76d +#define ICON_MD_DRIVE_FILE_RENAME_OUTLINE "\xee\xa6\xa2" // U+e9a2 +#define ICON_MD_DRIVE_FOLDER_UPLOAD "\xee\xa6\xa3" // U+e9a3 +#define ICON_MD_DRY "\xef\x86\xb3" // U+f1b3 +#define ICON_MD_DRY_CLEANING "\xee\xa9\x98" // U+ea58 +#define ICON_MD_DUO "\xee\xa6\xa5" // U+e9a5 +#define ICON_MD_DVR "\xee\x86\xb2" // U+e1b2 +#define ICON_MD_DYNAMIC_FEED "\xee\xa8\x94" // U+ea14 +#define ICON_MD_DYNAMIC_FORM "\xef\x86\xbf" // U+f1bf +#define ICON_MD_E_MOBILEDATA "\xef\x80\x82" // U+f002 +#define ICON_MD_EARBUDS "\xef\x80\x83" // U+f003 +#define ICON_MD_EARBUDS_BATTERY "\xef\x80\x84" // U+f004 +#define ICON_MD_EAST "\xef\x87\x9f" // U+f1df +#define ICON_MD_ECO "\xee\xa8\xb5" // U+ea35 +#define ICON_MD_EDGESENSOR_HIGH "\xef\x80\x85" // U+f005 +#define ICON_MD_EDGESENSOR_LOW "\xef\x80\x86" // U+f006 +#define ICON_MD_EDIT "\xee\x8f\x89" // U+e3c9 +#define ICON_MD_EDIT_ATTRIBUTES "\xee\x95\xb8" // U+e578 +#define ICON_MD_EDIT_CALENDAR "\xee\x9d\x82" // U+e742 +#define ICON_MD_EDIT_DOCUMENT "\xef\xa2\x8c" // U+f88c +#define ICON_MD_EDIT_LOCATION "\xee\x95\xa8" // U+e568 +#define ICON_MD_EDIT_LOCATION_ALT "\xee\x87\x85" // U+e1c5 +#define ICON_MD_EDIT_NOTE "\xee\x9d\x85" // U+e745 +#define ICON_MD_EDIT_NOTIFICATIONS "\xee\x94\xa5" // U+e525 +#define ICON_MD_EDIT_OFF "\xee\xa5\x90" // U+e950 +#define ICON_MD_EDIT_ROAD "\xee\xbd\x8d" // U+ef4d +#define ICON_MD_EDIT_SQUARE "\xef\xa2\x8d" // U+f88d +#define ICON_MD_EGG "\xee\xab\x8c" // U+eacc +#define ICON_MD_EGG_ALT "\xee\xab\x88" // U+eac8 +#define ICON_MD_EJECT "\xee\xa3\xbb" // U+e8fb +#define ICON_MD_ELDERLY "\xef\x88\x9a" // U+f21a +#define ICON_MD_ELDERLY_WOMAN "\xee\xad\xa9" // U+eb69 +#define ICON_MD_ELECTRIC_BIKE "\xee\xac\x9b" // U+eb1b +#define ICON_MD_ELECTRIC_BOLT "\xee\xb0\x9c" // U+ec1c +#define ICON_MD_ELECTRIC_CAR "\xee\xac\x9c" // U+eb1c +#define ICON_MD_ELECTRIC_METER "\xee\xb0\x9b" // U+ec1b +#define ICON_MD_ELECTRIC_MOPED "\xee\xac\x9d" // U+eb1d +#define ICON_MD_ELECTRIC_RICKSHAW "\xee\xac\x9e" // U+eb1e +#define ICON_MD_ELECTRIC_SCOOTER "\xee\xac\x9f" // U+eb1f +#define ICON_MD_ELECTRICAL_SERVICES "\xef\x84\x82" // U+f102 +#define ICON_MD_ELEVATOR "\xef\x86\xa0" // U+f1a0 +#define ICON_MD_EMAIL "\xee\x82\xbe" // U+e0be +#define ICON_MD_EMERGENCY "\xee\x87\xab" // U+e1eb +#define ICON_MD_EMERGENCY_RECORDING "\xee\xaf\xb4" // U+ebf4 +#define ICON_MD_EMERGENCY_SHARE "\xee\xaf\xb6" // U+ebf6 +#define ICON_MD_EMOJI_EMOTIONS "\xee\xa8\xa2" // U+ea22 +#define ICON_MD_EMOJI_EVENTS "\xee\xa8\xa3" // U+ea23 +#define ICON_MD_EMOJI_FLAGS "\xee\xa8\x9a" // U+ea1a +#define ICON_MD_EMOJI_FOOD_BEVERAGE "\xee\xa8\x9b" // U+ea1b +#define ICON_MD_EMOJI_NATURE "\xee\xa8\x9c" // U+ea1c +#define ICON_MD_EMOJI_OBJECTS "\xee\xa8\xa4" // U+ea24 +#define ICON_MD_EMOJI_PEOPLE "\xee\xa8\x9d" // U+ea1d +#define ICON_MD_EMOJI_SYMBOLS "\xee\xa8\x9e" // U+ea1e +#define ICON_MD_EMOJI_TRANSPORTATION "\xee\xa8\x9f" // U+ea1f +#define ICON_MD_ENERGY_SAVINGS_LEAF "\xee\xb0\x9a" // U+ec1a +#define ICON_MD_ENGINEERING "\xee\xa8\xbd" // U+ea3d +#define ICON_MD_ENHANCE_PHOTO_TRANSLATE "\xee\xa3\xbc" // U+e8fc +#define ICON_MD_ENHANCED_ENCRYPTION "\xee\x98\xbf" // U+e63f +#define ICON_MD_EQUALIZER "\xee\x80\x9d" // U+e01d +#define ICON_MD_ERROR "\xee\x80\x80" // U+e000 +#define ICON_MD_ERROR_OUTLINE "\xee\x80\x81" // U+e001 +#define ICON_MD_ESCALATOR "\xef\x86\xa1" // U+f1a1 +#define ICON_MD_ESCALATOR_WARNING "\xef\x86\xac" // U+f1ac +#define ICON_MD_EURO "\xee\xa8\x95" // U+ea15 +#define ICON_MD_EURO_SYMBOL "\xee\xa4\xa6" // U+e926 +#define ICON_MD_EV_STATION "\xee\x95\xad" // U+e56d +#define ICON_MD_EVENT "\xee\xa1\xb8" // U+e878 +#define ICON_MD_EVENT_AVAILABLE "\xee\x98\x94" // U+e614 +#define ICON_MD_EVENT_BUSY "\xee\x98\x95" // U+e615 +#define ICON_MD_EVENT_NOTE "\xee\x98\x96" // U+e616 +#define ICON_MD_EVENT_REPEAT "\xee\xad\xbb" // U+eb7b +#define ICON_MD_EVENT_SEAT "\xee\xa4\x83" // U+e903 +#define ICON_MD_EXIT_TO_APP "\xee\xa1\xb9" // U+e879 +#define ICON_MD_EXPAND "\xee\xa5\x8f" // U+e94f +#define ICON_MD_EXPAND_CIRCLE_DOWN "\xee\x9f\x8d" // U+e7cd +#define ICON_MD_EXPAND_LESS "\xee\x97\x8e" // U+e5ce +#define ICON_MD_EXPAND_MORE "\xee\x97\x8f" // U+e5cf +#define ICON_MD_EXPLICIT "\xee\x80\x9e" // U+e01e +#define ICON_MD_EXPLORE "\xee\xa1\xba" // U+e87a +#define ICON_MD_EXPLORE_OFF "\xee\xa6\xa8" // U+e9a8 +#define ICON_MD_EXPOSURE "\xee\x8f\x8a" // U+e3ca +#define ICON_MD_EXPOSURE_MINUS_1 "\xee\x8f\x8b" // U+e3cb +#define ICON_MD_EXPOSURE_MINUS_2 "\xee\x8f\x8c" // U+e3cc +#define ICON_MD_EXPOSURE_NEG_1 "\xee\x8f\x8b" // U+e3cb +#define ICON_MD_EXPOSURE_NEG_2 "\xee\x8f\x8c" // U+e3cc +#define ICON_MD_EXPOSURE_PLUS_1 "\xee\x8f\x8d" // U+e3cd +#define ICON_MD_EXPOSURE_PLUS_2 "\xee\x8f\x8e" // U+e3ce +#define ICON_MD_EXPOSURE_ZERO "\xee\x8f\x8f" // U+e3cf +#define ICON_MD_EXTENSION "\xee\xa1\xbb" // U+e87b +#define ICON_MD_EXTENSION_OFF "\xee\x93\xb5" // U+e4f5 +#define ICON_MD_FACE "\xee\xa1\xbc" // U+e87c +#define ICON_MD_FACE_2 "\xef\xa3\x9a" // U+f8da +#define ICON_MD_FACE_3 "\xef\xa3\x9b" // U+f8db +#define ICON_MD_FACE_4 "\xef\xa3\x9c" // U+f8dc +#define ICON_MD_FACE_5 "\xef\xa3\x9d" // U+f8dd +#define ICON_MD_FACE_6 "\xef\xa3\x9e" // U+f8de +#define ICON_MD_FACE_RETOUCHING_NATURAL "\xee\xbd\x8e" // U+ef4e +#define ICON_MD_FACE_RETOUCHING_OFF "\xef\x80\x87" // U+f007 +#define ICON_MD_FACEBOOK "\xef\x88\xb4" // U+f234 +#define ICON_MD_FACT_CHECK "\xef\x83\x85" // U+f0c5 +#define ICON_MD_FACTORY "\xee\xae\xbc" // U+ebbc +#define ICON_MD_FAMILY_RESTROOM "\xef\x86\xa2" // U+f1a2 +#define ICON_MD_FAST_FORWARD "\xee\x80\x9f" // U+e01f +#define ICON_MD_FAST_REWIND "\xee\x80\xa0" // U+e020 +#define ICON_MD_FASTFOOD "\xee\x95\xba" // U+e57a +#define ICON_MD_FAVORITE "\xee\xa1\xbd" // U+e87d +#define ICON_MD_FAVORITE_BORDER "\xee\xa1\xbe" // U+e87e +#define ICON_MD_FAVORITE_OUTLINE "\xee\xa1\xbe" // U+e87e +#define ICON_MD_FAX "\xee\xab\x98" // U+ead8 +#define ICON_MD_FEATURED_PLAY_LIST "\xee\x81\xad" // U+e06d +#define ICON_MD_FEATURED_VIDEO "\xee\x81\xae" // U+e06e +#define ICON_MD_FEED "\xef\x80\x89" // U+f009 +#define ICON_MD_FEEDBACK "\xee\xa1\xbf" // U+e87f +#define ICON_MD_FEMALE "\xee\x96\x90" // U+e590 +#define ICON_MD_FENCE "\xef\x87\xb6" // U+f1f6 +#define ICON_MD_FESTIVAL "\xee\xa9\xa8" // U+ea68 +#define ICON_MD_FIBER_DVR "\xee\x81\x9d" // U+e05d +#define ICON_MD_FIBER_MANUAL_RECORD "\xee\x81\xa1" // U+e061 +#define ICON_MD_FIBER_NEW "\xee\x81\x9e" // U+e05e +#define ICON_MD_FIBER_PIN "\xee\x81\xaa" // U+e06a +#define ICON_MD_FIBER_SMART_RECORD "\xee\x81\xa2" // U+e062 +#define ICON_MD_FILE_COPY "\xee\x85\xb3" // U+e173 +#define ICON_MD_FILE_DOWNLOAD "\xee\x8b\x84" // U+e2c4 +#define ICON_MD_FILE_DOWNLOAD_DONE "\xee\xa6\xaa" // U+e9aa +#define ICON_MD_FILE_DOWNLOAD_OFF "\xee\x93\xbe" // U+e4fe +#define ICON_MD_FILE_OPEN "\xee\xab\xb3" // U+eaf3 +#define ICON_MD_FILE_PRESENT "\xee\xa8\x8e" // U+ea0e +#define ICON_MD_FILE_UPLOAD "\xee\x8b\x86" // U+e2c6 +#define ICON_MD_FILE_UPLOAD_OFF "\xef\xa2\x86" // U+f886 +#define ICON_MD_FILTER "\xee\x8f\x93" // U+e3d3 +#define ICON_MD_FILTER_1 "\xee\x8f\x90" // U+e3d0 +#define ICON_MD_FILTER_2 "\xee\x8f\x91" // U+e3d1 +#define ICON_MD_FILTER_3 "\xee\x8f\x92" // U+e3d2 +#define ICON_MD_FILTER_4 "\xee\x8f\x94" // U+e3d4 +#define ICON_MD_FILTER_5 "\xee\x8f\x95" // U+e3d5 +#define ICON_MD_FILTER_6 "\xee\x8f\x96" // U+e3d6 +#define ICON_MD_FILTER_7 "\xee\x8f\x97" // U+e3d7 +#define ICON_MD_FILTER_8 "\xee\x8f\x98" // U+e3d8 +#define ICON_MD_FILTER_9 "\xee\x8f\x99" // U+e3d9 +#define ICON_MD_FILTER_9_PLUS "\xee\x8f\x9a" // U+e3da +#define ICON_MD_FILTER_ALT "\xee\xbd\x8f" // U+ef4f +#define ICON_MD_FILTER_ALT_OFF "\xee\xac\xb2" // U+eb32 +#define ICON_MD_FILTER_B_AND_W "\xee\x8f\x9b" // U+e3db +#define ICON_MD_FILTER_CENTER_FOCUS "\xee\x8f\x9c" // U+e3dc +#define ICON_MD_FILTER_DRAMA "\xee\x8f\x9d" // U+e3dd +#define ICON_MD_FILTER_FRAMES "\xee\x8f\x9e" // U+e3de +#define ICON_MD_FILTER_HDR "\xee\x8f\x9f" // U+e3df +#define ICON_MD_FILTER_LIST "\xee\x85\x92" // U+e152 +#define ICON_MD_FILTER_LIST_ALT "\xee\xa5\x8e" // U+e94e +#define ICON_MD_FILTER_LIST_OFF "\xee\xad\x97" // U+eb57 +#define ICON_MD_FILTER_NONE "\xee\x8f\xa0" // U+e3e0 +#define ICON_MD_FILTER_TILT_SHIFT "\xee\x8f\xa2" // U+e3e2 +#define ICON_MD_FILTER_VINTAGE "\xee\x8f\xa3" // U+e3e3 +#define ICON_MD_FIND_IN_PAGE "\xee\xa2\x80" // U+e880 +#define ICON_MD_FIND_REPLACE "\xee\xa2\x81" // U+e881 +#define ICON_MD_FINGERPRINT "\xee\xa4\x8d" // U+e90d +#define ICON_MD_FIRE_EXTINGUISHER "\xef\x87\x98" // U+f1d8 +#define ICON_MD_FIRE_HYDRANT "\xef\x86\xa3" // U+f1a3 +#define ICON_MD_FIRE_HYDRANT_ALT "\xef\xa3\xb1" // U+f8f1 +#define ICON_MD_FIRE_TRUCK "\xef\xa3\xb2" // U+f8f2 +#define ICON_MD_FIREPLACE "\xee\xa9\x83" // U+ea43 +#define ICON_MD_FIRST_PAGE "\xee\x97\x9c" // U+e5dc +#define ICON_MD_FIT_SCREEN "\xee\xa8\x90" // U+ea10 +#define ICON_MD_FITBIT "\xee\xa0\xab" // U+e82b +#define ICON_MD_FITNESS_CENTER "\xee\xad\x83" // U+eb43 +#define ICON_MD_FLAG "\xee\x85\x93" // U+e153 +#define ICON_MD_FLAG_CIRCLE "\xee\xab\xb8" // U+eaf8 +#define ICON_MD_FLAKY "\xee\xbd\x90" // U+ef50 +#define ICON_MD_FLARE "\xee\x8f\xa4" // U+e3e4 +#define ICON_MD_FLASH_AUTO "\xee\x8f\xa5" // U+e3e5 +#define ICON_MD_FLASH_OFF "\xee\x8f\xa6" // U+e3e6 +#define ICON_MD_FLASH_ON "\xee\x8f\xa7" // U+e3e7 +#define ICON_MD_FLASHLIGHT_OFF "\xef\x80\x8a" // U+f00a +#define ICON_MD_FLASHLIGHT_ON "\xef\x80\x8b" // U+f00b +#define ICON_MD_FLATWARE "\xef\x80\x8c" // U+f00c +#define ICON_MD_FLIGHT "\xee\x94\xb9" // U+e539 +#define ICON_MD_FLIGHT_CLASS "\xee\x9f\x8b" // U+e7cb +#define ICON_MD_FLIGHT_LAND "\xee\xa4\x84" // U+e904 +#define ICON_MD_FLIGHT_TAKEOFF "\xee\xa4\x85" // U+e905 +#define ICON_MD_FLIP "\xee\x8f\xa8" // U+e3e8 +#define ICON_MD_FLIP_CAMERA_ANDROID "\xee\xa8\xb7" // U+ea37 +#define ICON_MD_FLIP_CAMERA_IOS "\xee\xa8\xb8" // U+ea38 +#define ICON_MD_FLIP_TO_BACK "\xee\xa2\x82" // U+e882 +#define ICON_MD_FLIP_TO_FRONT "\xee\xa2\x83" // U+e883 +#define ICON_MD_FLOOD "\xee\xaf\xa6" // U+ebe6 +#define ICON_MD_FLOURESCENT "\xee\xb0\xb1" // U+ec31 +#define ICON_MD_FLOURESCENT2 "\xef\x80\x8d" // U+f00d +#define ICON_MD_FLUORESCENT "\xee\xb0\xb1" // U+ec31 +#define ICON_MD_FLUTTER_DASH "\xee\x80\x8b" // U+e00b +#define ICON_MD_FMD_BAD "\xef\x80\x8e" // U+f00e +#define ICON_MD_FMD_GOOD "\xef\x80\x8f" // U+f00f +#define ICON_MD_FOGGY "\xee\xa0\x98" // U+e818 +#define ICON_MD_FOLDER "\xee\x8b\x87" // U+e2c7 +#define ICON_MD_FOLDER_COPY "\xee\xae\xbd" // U+ebbd +#define ICON_MD_FOLDER_DELETE "\xee\xac\xb4" // U+eb34 +#define ICON_MD_FOLDER_OFF "\xee\xae\x83" // U+eb83 +#define ICON_MD_FOLDER_OPEN "\xee\x8b\x88" // U+e2c8 +#define ICON_MD_FOLDER_SHARED "\xee\x8b\x89" // U+e2c9 +#define ICON_MD_FOLDER_SPECIAL "\xee\x98\x97" // U+e617 +#define ICON_MD_FOLDER_ZIP "\xee\xac\xac" // U+eb2c +#define ICON_MD_FOLLOW_THE_SIGNS "\xef\x88\xa2" // U+f222 +#define ICON_MD_FONT_DOWNLOAD "\xee\x85\xa7" // U+e167 +#define ICON_MD_FONT_DOWNLOAD_OFF "\xee\x93\xb9" // U+e4f9 +#define ICON_MD_FOOD_BANK "\xef\x87\xb2" // U+f1f2 +#define ICON_MD_FOREST "\xee\xaa\x99" // U+ea99 +#define ICON_MD_FORK_LEFT "\xee\xae\xa0" // U+eba0 +#define ICON_MD_FORK_RIGHT "\xee\xae\xac" // U+ebac +#define ICON_MD_FORKLIFT "\xef\xa1\xa8" // U+f868 +#define ICON_MD_FORMAT_ALIGN_CENTER "\xee\x88\xb4" // U+e234 +#define ICON_MD_FORMAT_ALIGN_JUSTIFY "\xee\x88\xb5" // U+e235 +#define ICON_MD_FORMAT_ALIGN_LEFT "\xee\x88\xb6" // U+e236 +#define ICON_MD_FORMAT_ALIGN_RIGHT "\xee\x88\xb7" // U+e237 +#define ICON_MD_FORMAT_BOLD "\xee\x88\xb8" // U+e238 +#define ICON_MD_FORMAT_CLEAR "\xee\x88\xb9" // U+e239 +#define ICON_MD_FORMAT_COLOR_FILL "\xee\x88\xba" // U+e23a +#define ICON_MD_FORMAT_COLOR_RESET "\xee\x88\xbb" // U+e23b +#define ICON_MD_FORMAT_COLOR_TEXT "\xee\x88\xbc" // U+e23c +#define ICON_MD_FORMAT_INDENT_DECREASE "\xee\x88\xbd" // U+e23d +#define ICON_MD_FORMAT_INDENT_INCREASE "\xee\x88\xbe" // U+e23e +#define ICON_MD_FORMAT_ITALIC "\xee\x88\xbf" // U+e23f +#define ICON_MD_FORMAT_LINE_SPACING "\xee\x89\x80" // U+e240 +#define ICON_MD_FORMAT_LIST_BULLETED "\xee\x89\x81" // U+e241 +#define ICON_MD_FORMAT_LIST_NUMBERED "\xee\x89\x82" // U+e242 +#define ICON_MD_FORMAT_LIST_NUMBERED_RTL "\xee\x89\xa7" // U+e267 +#define ICON_MD_FORMAT_OVERLINE "\xee\xad\xa5" // U+eb65 +#define ICON_MD_FORMAT_PAINT "\xee\x89\x83" // U+e243 +#define ICON_MD_FORMAT_QUOTE "\xee\x89\x84" // U+e244 +#define ICON_MD_FORMAT_SHAPES "\xee\x89\x9e" // U+e25e +#define ICON_MD_FORMAT_SIZE "\xee\x89\x85" // U+e245 +#define ICON_MD_FORMAT_STRIKETHROUGH "\xee\x89\x86" // U+e246 +#define ICON_MD_FORMAT_TEXTDIRECTION_L_TO_R "\xee\x89\x87" // U+e247 +#define ICON_MD_FORMAT_TEXTDIRECTION_R_TO_L "\xee\x89\x88" // U+e248 +#define ICON_MD_FORMAT_UNDERLINE "\xee\x89\x89" // U+e249 +#define ICON_MD_FORMAT_UNDERLINED "\xee\x89\x89" // U+e249 +#define ICON_MD_FORT "\xee\xaa\xad" // U+eaad +#define ICON_MD_FORUM "\xee\x82\xbf" // U+e0bf +#define ICON_MD_FORWARD "\xee\x85\x94" // U+e154 +#define ICON_MD_FORWARD_10 "\xee\x81\x96" // U+e056 +#define ICON_MD_FORWARD_30 "\xee\x81\x97" // U+e057 +#define ICON_MD_FORWARD_5 "\xee\x81\x98" // U+e058 +#define ICON_MD_FORWARD_TO_INBOX "\xef\x86\x87" // U+f187 +#define ICON_MD_FOUNDATION "\xef\x88\x80" // U+f200 +#define ICON_MD_FREE_BREAKFAST "\xee\xad\x84" // U+eb44 +#define ICON_MD_FREE_CANCELLATION "\xee\x9d\x88" // U+e748 +#define ICON_MD_FRONT_HAND "\xee\x9d\xa9" // U+e769 +#define ICON_MD_FRONT_LOADER "\xef\xa1\xa9" // U+f869 +#define ICON_MD_FULLSCREEN "\xee\x97\x90" // U+e5d0 +#define ICON_MD_FULLSCREEN_EXIT "\xee\x97\x91" // U+e5d1 +#define ICON_MD_FUNCTIONS "\xee\x89\x8a" // U+e24a +#define ICON_MD_G_MOBILEDATA "\xef\x80\x90" // U+f010 +#define ICON_MD_G_TRANSLATE "\xee\xa4\xa7" // U+e927 +#define ICON_MD_GAMEPAD "\xee\x8c\x8f" // U+e30f +#define ICON_MD_GAMES "\xee\x80\xa1" // U+e021 +#define ICON_MD_GARAGE "\xef\x80\x91" // U+f011 +#define ICON_MD_GAS_METER "\xee\xb0\x99" // U+ec19 +#define ICON_MD_GAVEL "\xee\xa4\x8e" // U+e90e +#define ICON_MD_GENERATING_TOKENS "\xee\x9d\x89" // U+e749 +#define ICON_MD_GESTURE "\xee\x85\x95" // U+e155 +#define ICON_MD_GET_APP "\xee\xa2\x84" // U+e884 +#define ICON_MD_GIF "\xee\xa4\x88" // U+e908 +#define ICON_MD_GIF_BOX "\xee\x9e\xa3" // U+e7a3 +#define ICON_MD_GIRL "\xee\xad\xa8" // U+eb68 +#define ICON_MD_GITE "\xee\x96\x8b" // U+e58b +#define ICON_MD_GOAT "\xf4\x8f\xbf\xbd" // U+10fffd +#define ICON_MD_GOLF_COURSE "\xee\xad\x85" // U+eb45 +#define ICON_MD_GPP_BAD "\xef\x80\x92" // U+f012 +#define ICON_MD_GPP_GOOD "\xef\x80\x93" // U+f013 +#define ICON_MD_GPP_MAYBE "\xef\x80\x94" // U+f014 +#define ICON_MD_GPS_FIXED "\xee\x86\xb3" // U+e1b3 +#define ICON_MD_GPS_NOT_FIXED "\xee\x86\xb4" // U+e1b4 +#define ICON_MD_GPS_OFF "\xee\x86\xb5" // U+e1b5 +#define ICON_MD_GRADE "\xee\xa2\x85" // U+e885 +#define ICON_MD_GRADIENT "\xee\x8f\xa9" // U+e3e9 +#define ICON_MD_GRADING "\xee\xa9\x8f" // U+ea4f +#define ICON_MD_GRAIN "\xee\x8f\xaa" // U+e3ea +#define ICON_MD_GRAPHIC_EQ "\xee\x86\xb8" // U+e1b8 +#define ICON_MD_GRASS "\xef\x88\x85" // U+f205 +#define ICON_MD_GRID_3X3 "\xef\x80\x95" // U+f015 +#define ICON_MD_GRID_4X4 "\xef\x80\x96" // U+f016 +#define ICON_MD_GRID_GOLDENRATIO "\xef\x80\x97" // U+f017 +#define ICON_MD_GRID_OFF "\xee\x8f\xab" // U+e3eb +#define ICON_MD_GRID_ON "\xee\x8f\xac" // U+e3ec +#define ICON_MD_GRID_VIEW "\xee\xa6\xb0" // U+e9b0 +#define ICON_MD_GROUP "\xee\x9f\xaf" // U+e7ef +#define ICON_MD_GROUP_ADD "\xee\x9f\xb0" // U+e7f0 +#define ICON_MD_GROUP_OFF "\xee\x9d\x87" // U+e747 +#define ICON_MD_GROUP_REMOVE "\xee\x9e\xad" // U+e7ad +#define ICON_MD_GROUP_WORK "\xee\xa2\x86" // U+e886 +#define ICON_MD_GROUPS "\xef\x88\xb3" // U+f233 +#define ICON_MD_GROUPS_2 "\xef\xa3\x9f" // U+f8df +#define ICON_MD_GROUPS_3 "\xef\xa3\xa0" // U+f8e0 +#define ICON_MD_H_MOBILEDATA "\xef\x80\x98" // U+f018 +#define ICON_MD_H_PLUS_MOBILEDATA "\xef\x80\x99" // U+f019 +#define ICON_MD_HAIL "\xee\xa6\xb1" // U+e9b1 +#define ICON_MD_HANDSHAKE "\xee\xaf\x8b" // U+ebcb +#define ICON_MD_HANDYMAN "\xef\x84\x8b" // U+f10b +#define ICON_MD_HARDWARE "\xee\xa9\x99" // U+ea59 +#define ICON_MD_HD "\xee\x81\x92" // U+e052 +#define ICON_MD_HDR_AUTO "\xef\x80\x9a" // U+f01a +#define ICON_MD_HDR_AUTO_SELECT "\xef\x80\x9b" // U+f01b +#define ICON_MD_HDR_ENHANCED_SELECT "\xee\xbd\x91" // U+ef51 +#define ICON_MD_HDR_OFF "\xee\x8f\xad" // U+e3ed +#define ICON_MD_HDR_OFF_SELECT "\xef\x80\x9c" // U+f01c +#define ICON_MD_HDR_ON "\xee\x8f\xae" // U+e3ee +#define ICON_MD_HDR_ON_SELECT "\xef\x80\x9d" // U+f01d +#define ICON_MD_HDR_PLUS "\xef\x80\x9e" // U+f01e +#define ICON_MD_HDR_STRONG "\xee\x8f\xb1" // U+e3f1 +#define ICON_MD_HDR_WEAK "\xee\x8f\xb2" // U+e3f2 +#define ICON_MD_HEADPHONES "\xef\x80\x9f" // U+f01f +#define ICON_MD_HEADPHONES_BATTERY "\xef\x80\xa0" // U+f020 +#define ICON_MD_HEADSET "\xee\x8c\x90" // U+e310 +#define ICON_MD_HEADSET_MIC "\xee\x8c\x91" // U+e311 +#define ICON_MD_HEADSET_OFF "\xee\x8c\xba" // U+e33a +#define ICON_MD_HEALING "\xee\x8f\xb3" // U+e3f3 +#define ICON_MD_HEALTH_AND_SAFETY "\xee\x87\x95" // U+e1d5 +#define ICON_MD_HEARING "\xee\x80\xa3" // U+e023 +#define ICON_MD_HEARING_DISABLED "\xef\x84\x84" // U+f104 +#define ICON_MD_HEART_BROKEN "\xee\xab\x82" // U+eac2 +#define ICON_MD_HEAT_PUMP "\xee\xb0\x98" // U+ec18 +#define ICON_MD_HEIGHT "\xee\xa8\x96" // U+ea16 +#define ICON_MD_HELP "\xee\xa2\x87" // U+e887 +#define ICON_MD_HELP_CENTER "\xef\x87\x80" // U+f1c0 +#define ICON_MD_HELP_OUTLINE "\xee\xa3\xbd" // U+e8fd +#define ICON_MD_HEVC "\xef\x80\xa1" // U+f021 +#define ICON_MD_HEXAGON "\xee\xac\xb9" // U+eb39 +#define ICON_MD_HIDE_IMAGE "\xef\x80\xa2" // U+f022 +#define ICON_MD_HIDE_SOURCE "\xef\x80\xa3" // U+f023 +#define ICON_MD_HIGH_QUALITY "\xee\x80\xa4" // U+e024 +#define ICON_MD_HIGHLIGHT "\xee\x89\x9f" // U+e25f +#define ICON_MD_HIGHLIGHT_ALT "\xee\xbd\x92" // U+ef52 +#define ICON_MD_HIGHLIGHT_OFF "\xee\xa2\x88" // U+e888 +#define ICON_MD_HIGHLIGHT_REMOVE "\xee\xa2\x88" // U+e888 +#define ICON_MD_HIKING "\xee\x94\x8a" // U+e50a +#define ICON_MD_HISTORY "\xee\xa2\x89" // U+e889 +#define ICON_MD_HISTORY_EDU "\xee\xa8\xbe" // U+ea3e +#define ICON_MD_HISTORY_TOGGLE_OFF "\xef\x85\xbd" // U+f17d +#define ICON_MD_HIVE "\xee\xaa\xa6" // U+eaa6 +#define ICON_MD_HLS "\xee\xae\x8a" // U+eb8a +#define ICON_MD_HLS_OFF "\xee\xae\x8c" // U+eb8c +#define ICON_MD_HOLIDAY_VILLAGE "\xee\x96\x8a" // U+e58a +#define ICON_MD_HOME "\xee\xa2\x8a" // U+e88a +#define ICON_MD_HOME_FILLED "\xee\xa6\xb2" // U+e9b2 +#define ICON_MD_HOME_MAX "\xef\x80\xa4" // U+f024 +#define ICON_MD_HOME_MINI "\xef\x80\xa5" // U+f025 +#define ICON_MD_HOME_REPAIR_SERVICE "\xef\x84\x80" // U+f100 +#define ICON_MD_HOME_WORK "\xee\xa8\x89" // U+ea09 +#define ICON_MD_HORIZONTAL_DISTRIBUTE "\xee\x80\x94" // U+e014 +#define ICON_MD_HORIZONTAL_RULE "\xef\x84\x88" // U+f108 +#define ICON_MD_HORIZONTAL_SPLIT "\xee\xa5\x87" // U+e947 +#define ICON_MD_HOT_TUB "\xee\xad\x86" // U+eb46 +#define ICON_MD_HOTEL "\xee\x94\xba" // U+e53a +#define ICON_MD_HOTEL_CLASS "\xee\x9d\x83" // U+e743 +#define ICON_MD_HOURGLASS_BOTTOM "\xee\xa9\x9c" // U+ea5c +#define ICON_MD_HOURGLASS_DISABLED "\xee\xbd\x93" // U+ef53 +#define ICON_MD_HOURGLASS_EMPTY "\xee\xa2\x8b" // U+e88b +#define ICON_MD_HOURGLASS_FULL "\xee\xa2\x8c" // U+e88c +#define ICON_MD_HOURGLASS_TOP "\xee\xa9\x9b" // U+ea5b +#define ICON_MD_HOUSE "\xee\xa9\x84" // U+ea44 +#define ICON_MD_HOUSE_SIDING "\xef\x88\x82" // U+f202 +#define ICON_MD_HOUSEBOAT "\xee\x96\x84" // U+e584 +#define ICON_MD_HOW_TO_REG "\xee\x85\xb4" // U+e174 +#define ICON_MD_HOW_TO_VOTE "\xee\x85\xb5" // U+e175 +#define ICON_MD_HTML "\xee\xad\xbe" // U+eb7e +#define ICON_MD_HTTP "\xee\xa4\x82" // U+e902 +#define ICON_MD_HTTPS "\xee\xa2\x8d" // U+e88d +#define ICON_MD_HUB "\xee\xa7\xb4" // U+e9f4 +#define ICON_MD_HVAC "\xef\x84\x8e" // U+f10e +#define ICON_MD_ICE_SKATING "\xee\x94\x8b" // U+e50b +#define ICON_MD_ICECREAM "\xee\xa9\xa9" // U+ea69 +#define ICON_MD_IMAGE "\xee\x8f\xb4" // U+e3f4 +#define ICON_MD_IMAGE_ASPECT_RATIO "\xee\x8f\xb5" // U+e3f5 +#define ICON_MD_IMAGE_NOT_SUPPORTED "\xef\x84\x96" // U+f116 +#define ICON_MD_IMAGE_SEARCH "\xee\x90\xbf" // U+e43f +#define ICON_MD_IMAGESEARCH_ROLLER "\xee\xa6\xb4" // U+e9b4 +#define ICON_MD_IMPORT_CONTACTS "\xee\x83\xa0" // U+e0e0 +#define ICON_MD_IMPORT_EXPORT "\xee\x83\x83" // U+e0c3 +#define ICON_MD_IMPORTANT_DEVICES "\xee\xa4\x92" // U+e912 +#define ICON_MD_INBOX "\xee\x85\x96" // U+e156 +#define ICON_MD_INCOMPLETE_CIRCLE "\xee\x9e\x9b" // U+e79b +#define ICON_MD_INDETERMINATE_CHECK_BOX "\xee\xa4\x89" // U+e909 +#define ICON_MD_INFO "\xee\xa2\x8e" // U+e88e +#define ICON_MD_INFO_OUTLINE "\xee\xa2\x8f" // U+e88f +#define ICON_MD_INPUT "\xee\xa2\x90" // U+e890 +#define ICON_MD_INSERT_CHART "\xee\x89\x8b" // U+e24b +#define ICON_MD_INSERT_CHART_OUTLINED "\xee\x89\xaa" // U+e26a +#define ICON_MD_INSERT_COMMENT "\xee\x89\x8c" // U+e24c +#define ICON_MD_INSERT_DRIVE_FILE "\xee\x89\x8d" // U+e24d +#define ICON_MD_INSERT_EMOTICON "\xee\x89\x8e" // U+e24e +#define ICON_MD_INSERT_INVITATION "\xee\x89\x8f" // U+e24f +#define ICON_MD_INSERT_LINK "\xee\x89\x90" // U+e250 +#define ICON_MD_INSERT_PAGE_BREAK "\xee\xab\x8a" // U+eaca +#define ICON_MD_INSERT_PHOTO "\xee\x89\x91" // U+e251 +#define ICON_MD_INSIGHTS "\xef\x82\x92" // U+f092 +#define ICON_MD_INSTALL_DESKTOP "\xee\xad\xb1" // U+eb71 +#define ICON_MD_INSTALL_MOBILE "\xee\xad\xb2" // U+eb72 +#define ICON_MD_INTEGRATION_INSTRUCTIONS "\xee\xbd\x94" // U+ef54 +#define ICON_MD_INTERESTS "\xee\x9f\x88" // U+e7c8 +#define ICON_MD_INTERPRETER_MODE "\xee\xa0\xbb" // U+e83b +#define ICON_MD_INVENTORY "\xee\x85\xb9" // U+e179 +#define ICON_MD_INVENTORY_2 "\xee\x86\xa1" // U+e1a1 +#define ICON_MD_INVERT_COLORS "\xee\xa2\x91" // U+e891 +#define ICON_MD_INVERT_COLORS_OFF "\xee\x83\x84" // U+e0c4 +#define ICON_MD_INVERT_COLORS_ON "\xee\xa2\x91" // U+e891 +#define ICON_MD_IOS_SHARE "\xee\x9a\xb8" // U+e6b8 +#define ICON_MD_IRON "\xee\x96\x83" // U+e583 +#define ICON_MD_ISO "\xee\x8f\xb6" // U+e3f6 +#define ICON_MD_JAVASCRIPT "\xee\xad\xbc" // U+eb7c +#define ICON_MD_JOIN_FULL "\xee\xab\xab" // U+eaeb +#define ICON_MD_JOIN_INNER "\xee\xab\xb4" // U+eaf4 +#define ICON_MD_JOIN_LEFT "\xee\xab\xb2" // U+eaf2 +#define ICON_MD_JOIN_RIGHT "\xee\xab\xaa" // U+eaea +#define ICON_MD_KAYAKING "\xee\x94\x8c" // U+e50c +#define ICON_MD_KEBAB_DINING "\xee\xa1\x82" // U+e842 +#define ICON_MD_KEY "\xee\x9c\xbc" // U+e73c +#define ICON_MD_KEY_OFF "\xee\xae\x84" // U+eb84 +#define ICON_MD_KEYBOARD "\xee\x8c\x92" // U+e312 +#define ICON_MD_KEYBOARD_ALT "\xef\x80\xa8" // U+f028 +#define ICON_MD_KEYBOARD_ARROW_DOWN "\xee\x8c\x93" // U+e313 +#define ICON_MD_KEYBOARD_ARROW_LEFT "\xee\x8c\x94" // U+e314 +#define ICON_MD_KEYBOARD_ARROW_RIGHT "\xee\x8c\x95" // U+e315 +#define ICON_MD_KEYBOARD_ARROW_UP "\xee\x8c\x96" // U+e316 +#define ICON_MD_KEYBOARD_BACKSPACE "\xee\x8c\x97" // U+e317 +#define ICON_MD_KEYBOARD_CAPSLOCK "\xee\x8c\x98" // U+e318 +#define ICON_MD_KEYBOARD_COMMAND "\xee\xab\xa0" // U+eae0 +#define ICON_MD_KEYBOARD_COMMAND_KEY "\xee\xab\xa7" // U+eae7 +#define ICON_MD_KEYBOARD_CONTROL "\xee\x97\x93" // U+e5d3 +#define ICON_MD_KEYBOARD_CONTROL_KEY "\xee\xab\xa6" // U+eae6 +#define ICON_MD_KEYBOARD_DOUBLE_ARROW_DOWN "\xee\xab\x90" // U+ead0 +#define ICON_MD_KEYBOARD_DOUBLE_ARROW_LEFT "\xee\xab\x83" // U+eac3 +#define ICON_MD_KEYBOARD_DOUBLE_ARROW_RIGHT "\xee\xab\x89" // U+eac9 +#define ICON_MD_KEYBOARD_DOUBLE_ARROW_UP "\xee\xab\x8f" // U+eacf +#define ICON_MD_KEYBOARD_HIDE "\xee\x8c\x9a" // U+e31a +#define ICON_MD_KEYBOARD_OPTION "\xee\xab\x9f" // U+eadf +#define ICON_MD_KEYBOARD_OPTION_KEY "\xee\xab\xa8" // U+eae8 +#define ICON_MD_KEYBOARD_RETURN "\xee\x8c\x9b" // U+e31b +#define ICON_MD_KEYBOARD_TAB "\xee\x8c\x9c" // U+e31c +#define ICON_MD_KEYBOARD_VOICE "\xee\x8c\x9d" // U+e31d +#define ICON_MD_KING_BED "\xee\xa9\x85" // U+ea45 +#define ICON_MD_KITCHEN "\xee\xad\x87" // U+eb47 +#define ICON_MD_KITESURFING "\xee\x94\x8d" // U+e50d +#define ICON_MD_LABEL "\xee\xa2\x92" // U+e892 +#define ICON_MD_LABEL_IMPORTANT "\xee\xa4\xb7" // U+e937 +#define ICON_MD_LABEL_IMPORTANT_OUTLINE "\xee\xa5\x88" // U+e948 +#define ICON_MD_LABEL_OFF "\xee\xa6\xb6" // U+e9b6 +#define ICON_MD_LABEL_OUTLINE "\xee\xa2\x93" // U+e893 +#define ICON_MD_LAN "\xee\xac\xaf" // U+eb2f +#define ICON_MD_LANDSCAPE "\xee\x8f\xb7" // U+e3f7 +#define ICON_MD_LANDSLIDE "\xee\xaf\x97" // U+ebd7 +#define ICON_MD_LANGUAGE "\xee\xa2\x94" // U+e894 +#define ICON_MD_LAPTOP "\xee\x8c\x9e" // U+e31e +#define ICON_MD_LAPTOP_CHROMEBOOK "\xee\x8c\x9f" // U+e31f +#define ICON_MD_LAPTOP_MAC "\xee\x8c\xa0" // U+e320 +#define ICON_MD_LAPTOP_WINDOWS "\xee\x8c\xa1" // U+e321 +#define ICON_MD_LAST_PAGE "\xee\x97\x9d" // U+e5dd +#define ICON_MD_LAUNCH "\xee\xa2\x95" // U+e895 +#define ICON_MD_LAYERS "\xee\x94\xbb" // U+e53b +#define ICON_MD_LAYERS_CLEAR "\xee\x94\xbc" // U+e53c +#define ICON_MD_LEADERBOARD "\xef\x88\x8c" // U+f20c +#define ICON_MD_LEAK_ADD "\xee\x8f\xb8" // U+e3f8 +#define ICON_MD_LEAK_REMOVE "\xee\x8f\xb9" // U+e3f9 +#define ICON_MD_LEAVE_BAGS_AT_HOME "\xef\x88\x9b" // U+f21b +#define ICON_MD_LEGEND_TOGGLE "\xef\x84\x9b" // U+f11b +#define ICON_MD_LENS "\xee\x8f\xba" // U+e3fa +#define ICON_MD_LENS_BLUR "\xef\x80\xa9" // U+f029 +#define ICON_MD_LIBRARY_ADD "\xee\x80\xae" // U+e02e +#define ICON_MD_LIBRARY_ADD_CHECK "\xee\xa6\xb7" // U+e9b7 +#define ICON_MD_LIBRARY_BOOKS "\xee\x80\xaf" // U+e02f +#define ICON_MD_LIBRARY_MUSIC "\xee\x80\xb0" // U+e030 +#define ICON_MD_LIGHT "\xef\x80\xaa" // U+f02a +#define ICON_MD_LIGHT_MODE "\xee\x94\x98" // U+e518 +#define ICON_MD_LIGHTBULB "\xee\x83\xb0" // U+e0f0 +#define ICON_MD_LIGHTBULB_CIRCLE "\xee\xaf\xbe" // U+ebfe +#define ICON_MD_LIGHTBULB_OUTLINE "\xee\xa4\x8f" // U+e90f +#define ICON_MD_LINE_AXIS "\xee\xaa\x9a" // U+ea9a +#define ICON_MD_LINE_STYLE "\xee\xa4\x99" // U+e919 +#define ICON_MD_LINE_WEIGHT "\xee\xa4\x9a" // U+e91a +#define ICON_MD_LINEAR_SCALE "\xee\x89\xa0" // U+e260 +#define ICON_MD_LINK "\xee\x85\x97" // U+e157 +#define ICON_MD_LINK_OFF "\xee\x85\xaf" // U+e16f +#define ICON_MD_LINKED_CAMERA "\xee\x90\xb8" // U+e438 +#define ICON_MD_LIQUOR "\xee\xa9\xa0" // U+ea60 +#define ICON_MD_LIST "\xee\xa2\x96" // U+e896 +#define ICON_MD_LIST_ALT "\xee\x83\xae" // U+e0ee +#define ICON_MD_LIVE_HELP "\xee\x83\x86" // U+e0c6 +#define ICON_MD_LIVE_TV "\xee\x98\xb9" // U+e639 +#define ICON_MD_LIVING "\xef\x80\xab" // U+f02b +#define ICON_MD_LOCAL_ACTIVITY "\xee\x94\xbf" // U+e53f +#define ICON_MD_LOCAL_AIRPORT "\xee\x94\xbd" // U+e53d +#define ICON_MD_LOCAL_ATM "\xee\x94\xbe" // U+e53e +#define ICON_MD_LOCAL_ATTRACTION "\xee\x94\xbf" // U+e53f +#define ICON_MD_LOCAL_BAR "\xee\x95\x80" // U+e540 +#define ICON_MD_LOCAL_CAFE "\xee\x95\x81" // U+e541 +#define ICON_MD_LOCAL_CAR_WASH "\xee\x95\x82" // U+e542 +#define ICON_MD_LOCAL_CONVENIENCE_STORE "\xee\x95\x83" // U+e543 +#define ICON_MD_LOCAL_DINING "\xee\x95\x96" // U+e556 +#define ICON_MD_LOCAL_DRINK "\xee\x95\x84" // U+e544 +#define ICON_MD_LOCAL_FIRE_DEPARTMENT "\xee\xbd\x95" // U+ef55 +#define ICON_MD_LOCAL_FLORIST "\xee\x95\x85" // U+e545 +#define ICON_MD_LOCAL_GAS_STATION "\xee\x95\x86" // U+e546 +#define ICON_MD_LOCAL_GROCERY_STORE "\xee\x95\x87" // U+e547 +#define ICON_MD_LOCAL_HOSPITAL "\xee\x95\x88" // U+e548 +#define ICON_MD_LOCAL_HOTEL "\xee\x95\x89" // U+e549 +#define ICON_MD_LOCAL_LAUNDRY_SERVICE "\xee\x95\x8a" // U+e54a +#define ICON_MD_LOCAL_LIBRARY "\xee\x95\x8b" // U+e54b +#define ICON_MD_LOCAL_MALL "\xee\x95\x8c" // U+e54c +#define ICON_MD_LOCAL_MOVIES "\xee\x95\x8d" // U+e54d +#define ICON_MD_LOCAL_OFFER "\xee\x95\x8e" // U+e54e +#define ICON_MD_LOCAL_PARKING "\xee\x95\x8f" // U+e54f +#define ICON_MD_LOCAL_PHARMACY "\xee\x95\x90" // U+e550 +#define ICON_MD_LOCAL_PHONE "\xee\x95\x91" // U+e551 +#define ICON_MD_LOCAL_PIZZA "\xee\x95\x92" // U+e552 +#define ICON_MD_LOCAL_PLAY "\xee\x95\x93" // U+e553 +#define ICON_MD_LOCAL_POLICE "\xee\xbd\x96" // U+ef56 +#define ICON_MD_LOCAL_POST_OFFICE "\xee\x95\x94" // U+e554 +#define ICON_MD_LOCAL_PRINT_SHOP "\xee\x95\x95" // U+e555 +#define ICON_MD_LOCAL_PRINTSHOP "\xee\x95\x95" // U+e555 +#define ICON_MD_LOCAL_RESTAURANT "\xee\x95\x96" // U+e556 +#define ICON_MD_LOCAL_SEE "\xee\x95\x97" // U+e557 +#define ICON_MD_LOCAL_SHIPPING "\xee\x95\x98" // U+e558 +#define ICON_MD_LOCAL_TAXI "\xee\x95\x99" // U+e559 +#define ICON_MD_LOCATION_CITY "\xee\x9f\xb1" // U+e7f1 +#define ICON_MD_LOCATION_DISABLED "\xee\x86\xb6" // U+e1b6 +#define ICON_MD_LOCATION_HISTORY "\xee\x95\x9a" // U+e55a +#define ICON_MD_LOCATION_OFF "\xee\x83\x87" // U+e0c7 +#define ICON_MD_LOCATION_ON "\xee\x83\x88" // U+e0c8 +#define ICON_MD_LOCATION_PIN "\xef\x87\x9b" // U+f1db +#define ICON_MD_LOCATION_SEARCHING "\xee\x86\xb7" // U+e1b7 +#define ICON_MD_LOCK "\xee\xa2\x97" // U+e897 +#define ICON_MD_LOCK_CLOCK "\xee\xbd\x97" // U+ef57 +#define ICON_MD_LOCK_OPEN "\xee\xa2\x98" // U+e898 +#define ICON_MD_LOCK_OUTLINE "\xee\xa2\x99" // U+e899 +#define ICON_MD_LOCK_PERSON "\xef\xa3\xb3" // U+f8f3 +#define ICON_MD_LOCK_RESET "\xee\xab\x9e" // U+eade +#define ICON_MD_LOGIN "\xee\xa9\xb7" // U+ea77 +#define ICON_MD_LOGO_DEV "\xee\xab\x96" // U+ead6 +#define ICON_MD_LOGOUT "\xee\xa6\xba" // U+e9ba +#define ICON_MD_LOOKS "\xee\x8f\xbc" // U+e3fc +#define ICON_MD_LOOKS_3 "\xee\x8f\xbb" // U+e3fb +#define ICON_MD_LOOKS_4 "\xee\x8f\xbd" // U+e3fd +#define ICON_MD_LOOKS_5 "\xee\x8f\xbe" // U+e3fe +#define ICON_MD_LOOKS_6 "\xee\x8f\xbf" // U+e3ff +#define ICON_MD_LOOKS_ONE "\xee\x90\x80" // U+e400 +#define ICON_MD_LOOKS_TWO "\xee\x90\x81" // U+e401 +#define ICON_MD_LOOP "\xee\x80\xa8" // U+e028 +#define ICON_MD_LOUPE "\xee\x90\x82" // U+e402 +#define ICON_MD_LOW_PRIORITY "\xee\x85\xad" // U+e16d +#define ICON_MD_LOYALTY "\xee\xa2\x9a" // U+e89a +#define ICON_MD_LTE_MOBILEDATA "\xef\x80\xac" // U+f02c +#define ICON_MD_LTE_PLUS_MOBILEDATA "\xef\x80\xad" // U+f02d +#define ICON_MD_LUGGAGE "\xef\x88\xb5" // U+f235 +#define ICON_MD_LUNCH_DINING "\xee\xa9\xa1" // U+ea61 +#define ICON_MD_LYRICS "\xee\xb0\x8b" // U+ec0b +#define ICON_MD_MACRO_OFF "\xef\xa3\x92" // U+f8d2 +#define ICON_MD_MAIL "\xee\x85\x98" // U+e158 +#define ICON_MD_MAIL_LOCK "\xee\xb0\x8a" // U+ec0a +#define ICON_MD_MAIL_OUTLINE "\xee\x83\xa1" // U+e0e1 +#define ICON_MD_MALE "\xee\x96\x8e" // U+e58e +#define ICON_MD_MAN "\xee\x93\xab" // U+e4eb +#define ICON_MD_MAN_2 "\xef\xa3\xa1" // U+f8e1 +#define ICON_MD_MAN_3 "\xef\xa3\xa2" // U+f8e2 +#define ICON_MD_MAN_4 "\xef\xa3\xa3" // U+f8e3 +#define ICON_MD_MANAGE_ACCOUNTS "\xef\x80\xae" // U+f02e +#define ICON_MD_MANAGE_HISTORY "\xee\xaf\xa7" // U+ebe7 +#define ICON_MD_MANAGE_SEARCH "\xef\x80\xaf" // U+f02f +#define ICON_MD_MAP "\xee\x95\x9b" // U+e55b +#define ICON_MD_MAPS_HOME_WORK "\xef\x80\xb0" // U+f030 +#define ICON_MD_MAPS_UGC "\xee\xbd\x98" // U+ef58 +#define ICON_MD_MARGIN "\xee\xa6\xbb" // U+e9bb +#define ICON_MD_MARK_AS_UNREAD "\xee\xa6\xbc" // U+e9bc +#define ICON_MD_MARK_CHAT_READ "\xef\x86\x8b" // U+f18b +#define ICON_MD_MARK_CHAT_UNREAD "\xef\x86\x89" // U+f189 +#define ICON_MD_MARK_EMAIL_READ "\xef\x86\x8c" // U+f18c +#define ICON_MD_MARK_EMAIL_UNREAD "\xef\x86\x8a" // U+f18a +#define ICON_MD_MARK_UNREAD_CHAT_ALT "\xee\xae\x9d" // U+eb9d +#define ICON_MD_MARKUNREAD "\xee\x85\x99" // U+e159 +#define ICON_MD_MARKUNREAD_MAILBOX "\xee\xa2\x9b" // U+e89b +#define ICON_MD_MASKS "\xef\x88\x98" // U+f218 +#define ICON_MD_MAXIMIZE "\xee\xa4\xb0" // U+e930 +#define ICON_MD_MEDIA_BLUETOOTH_OFF "\xef\x80\xb1" // U+f031 +#define ICON_MD_MEDIA_BLUETOOTH_ON "\xef\x80\xb2" // U+f032 +#define ICON_MD_MEDIATION "\xee\xbe\xa7" // U+efa7 +#define ICON_MD_MEDICAL_INFORMATION "\xee\xaf\xad" // U+ebed +#define ICON_MD_MEDICAL_SERVICES "\xef\x84\x89" // U+f109 +#define ICON_MD_MEDICATION "\xef\x80\xb3" // U+f033 +#define ICON_MD_MEDICATION_LIQUID "\xee\xaa\x87" // U+ea87 +#define ICON_MD_MEETING_ROOM "\xee\xad\x8f" // U+eb4f +#define ICON_MD_MEMORY "\xee\x8c\xa2" // U+e322 +#define ICON_MD_MENU "\xee\x97\x92" // U+e5d2 +#define ICON_MD_MENU_BOOK "\xee\xa8\x99" // U+ea19 +#define ICON_MD_MENU_OPEN "\xee\xa6\xbd" // U+e9bd +#define ICON_MD_MERGE "\xee\xae\x98" // U+eb98 +#define ICON_MD_MERGE_TYPE "\xee\x89\x92" // U+e252 +#define ICON_MD_MESSAGE "\xee\x83\x89" // U+e0c9 +#define ICON_MD_MESSENGER "\xee\x83\x8a" // U+e0ca +#define ICON_MD_MESSENGER_OUTLINE "\xee\x83\x8b" // U+e0cb +#define ICON_MD_MIC "\xee\x80\xa9" // U+e029 +#define ICON_MD_MIC_EXTERNAL_OFF "\xee\xbd\x99" // U+ef59 +#define ICON_MD_MIC_EXTERNAL_ON "\xee\xbd\x9a" // U+ef5a +#define ICON_MD_MIC_NONE "\xee\x80\xaa" // U+e02a +#define ICON_MD_MIC_OFF "\xee\x80\xab" // U+e02b +#define ICON_MD_MICROWAVE "\xef\x88\x84" // U+f204 +#define ICON_MD_MILITARY_TECH "\xee\xa8\xbf" // U+ea3f +#define ICON_MD_MINIMIZE "\xee\xa4\xb1" // U+e931 +#define ICON_MD_MINOR_CRASH "\xee\xaf\xb1" // U+ebf1 +#define ICON_MD_MISCELLANEOUS_SERVICES "\xef\x84\x8c" // U+f10c +#define ICON_MD_MISSED_VIDEO_CALL "\xee\x81\xb3" // U+e073 +#define ICON_MD_MMS "\xee\x98\x98" // U+e618 +#define ICON_MD_MOBILE_FRIENDLY "\xee\x88\x80" // U+e200 +#define ICON_MD_MOBILE_OFF "\xee\x88\x81" // U+e201 +#define ICON_MD_MOBILE_SCREEN_SHARE "\xee\x83\xa7" // U+e0e7 +#define ICON_MD_MOBILEDATA_OFF "\xef\x80\xb4" // U+f034 +#define ICON_MD_MODE "\xef\x82\x97" // U+f097 +#define ICON_MD_MODE_COMMENT "\xee\x89\x93" // U+e253 +#define ICON_MD_MODE_EDIT "\xee\x89\x94" // U+e254 +#define ICON_MD_MODE_EDIT_OUTLINE "\xef\x80\xb5" // U+f035 +#define ICON_MD_MODE_FAN_OFF "\xee\xb0\x97" // U+ec17 +#define ICON_MD_MODE_NIGHT "\xef\x80\xb6" // U+f036 +#define ICON_MD_MODE_OF_TRAVEL "\xee\x9f\x8e" // U+e7ce +#define ICON_MD_MODE_STANDBY "\xef\x80\xb7" // U+f037 +#define ICON_MD_MODEL_TRAINING "\xef\x83\x8f" // U+f0cf +#define ICON_MD_MONETIZATION_ON "\xee\x89\xa3" // U+e263 +#define ICON_MD_MONEY "\xee\x95\xbd" // U+e57d +#define ICON_MD_MONEY_OFF "\xee\x89\x9c" // U+e25c +#define ICON_MD_MONEY_OFF_CSRED "\xef\x80\xb8" // U+f038 +#define ICON_MD_MONITOR "\xee\xbd\x9b" // U+ef5b +#define ICON_MD_MONITOR_HEART "\xee\xaa\xa2" // U+eaa2 +#define ICON_MD_MONITOR_WEIGHT "\xef\x80\xb9" // U+f039 +#define ICON_MD_MONOCHROME_PHOTOS "\xee\x90\x83" // U+e403 +#define ICON_MD_MOOD "\xee\x9f\xb2" // U+e7f2 +#define ICON_MD_MOOD_BAD "\xee\x9f\xb3" // U+e7f3 +#define ICON_MD_MOPED "\xee\xac\xa8" // U+eb28 +#define ICON_MD_MORE "\xee\x98\x99" // U+e619 +#define ICON_MD_MORE_HORIZ "\xee\x97\x93" // U+e5d3 +#define ICON_MD_MORE_TIME "\xee\xa9\x9d" // U+ea5d +#define ICON_MD_MORE_VERT "\xee\x97\x94" // U+e5d4 +#define ICON_MD_MOSQUE "\xee\xaa\xb2" // U+eab2 +#define ICON_MD_MOTION_PHOTOS_AUTO "\xef\x80\xba" // U+f03a +#define ICON_MD_MOTION_PHOTOS_OFF "\xee\xa7\x80" // U+e9c0 +#define ICON_MD_MOTION_PHOTOS_ON "\xee\xa7\x81" // U+e9c1 +#define ICON_MD_MOTION_PHOTOS_PAUSE "\xef\x88\xa7" // U+f227 +#define ICON_MD_MOTION_PHOTOS_PAUSED "\xee\xa7\x82" // U+e9c2 +#define ICON_MD_MOTORCYCLE "\xee\xa4\x9b" // U+e91b +#define ICON_MD_MOUSE "\xee\x8c\xa3" // U+e323 +#define ICON_MD_MOVE_DOWN "\xee\xad\xa1" // U+eb61 +#define ICON_MD_MOVE_TO_INBOX "\xee\x85\xa8" // U+e168 +#define ICON_MD_MOVE_UP "\xee\xad\xa4" // U+eb64 +#define ICON_MD_MOVIE "\xee\x80\xac" // U+e02c +#define ICON_MD_MOVIE_CREATION "\xee\x90\x84" // U+e404 +#define ICON_MD_MOVIE_FILTER "\xee\x90\xba" // U+e43a +#define ICON_MD_MOVING "\xee\x94\x81" // U+e501 +#define ICON_MD_MP "\xee\xa7\x83" // U+e9c3 +#define ICON_MD_MULTILINE_CHART "\xee\x9b\x9f" // U+e6df +#define ICON_MD_MULTIPLE_STOP "\xef\x86\xb9" // U+f1b9 +#define ICON_MD_MULTITRACK_AUDIO "\xee\x86\xb8" // U+e1b8 +#define ICON_MD_MUSEUM "\xee\xa8\xb6" // U+ea36 +#define ICON_MD_MUSIC_NOTE "\xee\x90\x85" // U+e405 +#define ICON_MD_MUSIC_OFF "\xee\x91\x80" // U+e440 +#define ICON_MD_MUSIC_VIDEO "\xee\x81\xa3" // U+e063 +#define ICON_MD_MY_LIBRARY_ADD "\xee\x80\xae" // U+e02e +#define ICON_MD_MY_LIBRARY_BOOKS "\xee\x80\xaf" // U+e02f +#define ICON_MD_MY_LIBRARY_MUSIC "\xee\x80\xb0" // U+e030 +#define ICON_MD_MY_LOCATION "\xee\x95\x9c" // U+e55c +#define ICON_MD_NAT "\xee\xbd\x9c" // U+ef5c +#define ICON_MD_NATURE "\xee\x90\x86" // U+e406 +#define ICON_MD_NATURE_PEOPLE "\xee\x90\x87" // U+e407 +#define ICON_MD_NAVIGATE_BEFORE "\xee\x90\x88" // U+e408 +#define ICON_MD_NAVIGATE_NEXT "\xee\x90\x89" // U+e409 +#define ICON_MD_NAVIGATION "\xee\x95\x9d" // U+e55d +#define ICON_MD_NEAR_ME "\xee\x95\xa9" // U+e569 +#define ICON_MD_NEAR_ME_DISABLED "\xef\x87\xaf" // U+f1ef +#define ICON_MD_NEARBY_ERROR "\xef\x80\xbb" // U+f03b +#define ICON_MD_NEARBY_OFF "\xef\x80\xbc" // U+f03c +#define ICON_MD_NEST_CAM_WIRED_STAND "\xee\xb0\x96" // U+ec16 +#define ICON_MD_NETWORK_CELL "\xee\x86\xb9" // U+e1b9 +#define ICON_MD_NETWORK_CHECK "\xee\x99\x80" // U+e640 +#define ICON_MD_NETWORK_LOCKED "\xee\x98\x9a" // U+e61a +#define ICON_MD_NETWORK_PING "\xee\xaf\x8a" // U+ebca +#define ICON_MD_NETWORK_WIFI "\xee\x86\xba" // U+e1ba +#define ICON_MD_NETWORK_WIFI_1_BAR "\xee\xaf\xa4" // U+ebe4 +#define ICON_MD_NETWORK_WIFI_2_BAR "\xee\xaf\x96" // U+ebd6 +#define ICON_MD_NETWORK_WIFI_3_BAR "\xee\xaf\xa1" // U+ebe1 +#define ICON_MD_NEW_LABEL "\xee\x98\x89" // U+e609 +#define ICON_MD_NEW_RELEASES "\xee\x80\xb1" // U+e031 +#define ICON_MD_NEWSPAPER "\xee\xae\x81" // U+eb81 +#define ICON_MD_NEXT_PLAN "\xee\xbd\x9d" // U+ef5d +#define ICON_MD_NEXT_WEEK "\xee\x85\xaa" // U+e16a +#define ICON_MD_NFC "\xee\x86\xbb" // U+e1bb +#define ICON_MD_NIGHT_SHELTER "\xef\x87\xb1" // U+f1f1 +#define ICON_MD_NIGHTLIFE "\xee\xa9\xa2" // U+ea62 +#define ICON_MD_NIGHTLIGHT "\xef\x80\xbd" // U+f03d +#define ICON_MD_NIGHTLIGHT_ROUND "\xee\xbd\x9e" // U+ef5e +#define ICON_MD_NIGHTS_STAY "\xee\xa9\x86" // U+ea46 +#define ICON_MD_NO_ACCOUNTS "\xef\x80\xbe" // U+f03e +#define ICON_MD_NO_ADULT_CONTENT "\xef\xa3\xbe" // U+f8fe +#define ICON_MD_NO_BACKPACK "\xef\x88\xb7" // U+f237 +#define ICON_MD_NO_CELL "\xef\x86\xa4" // U+f1a4 +#define ICON_MD_NO_CRASH "\xee\xaf\xb0" // U+ebf0 +#define ICON_MD_NO_DRINKS "\xef\x86\xa5" // U+f1a5 +#define ICON_MD_NO_ENCRYPTION "\xee\x99\x81" // U+e641 +#define ICON_MD_NO_ENCRYPTION_GMAILERRORRED "\xef\x80\xbf" // U+f03f +#define ICON_MD_NO_FLASH "\xef\x86\xa6" // U+f1a6 +#define ICON_MD_NO_FOOD "\xef\x86\xa7" // U+f1a7 +#define ICON_MD_NO_LUGGAGE "\xef\x88\xbb" // U+f23b +#define ICON_MD_NO_MEALS "\xef\x87\x96" // U+f1d6 +#define ICON_MD_NO_MEALS_OULINE "\xef\x88\xa9" // U+f229 +#define ICON_MD_NO_MEETING_ROOM "\xee\xad\x8e" // U+eb4e +#define ICON_MD_NO_PHOTOGRAPHY "\xef\x86\xa8" // U+f1a8 +#define ICON_MD_NO_SIM "\xee\x83\x8c" // U+e0cc +#define ICON_MD_NO_STROLLER "\xef\x86\xaf" // U+f1af +#define ICON_MD_NO_TRANSFER "\xef\x87\x95" // U+f1d5 +#define ICON_MD_NOISE_AWARE "\xee\xaf\xac" // U+ebec +#define ICON_MD_NOISE_CONTROL_OFF "\xee\xaf\xb3" // U+ebf3 +#define ICON_MD_NORDIC_WALKING "\xee\x94\x8e" // U+e50e +#define ICON_MD_NORTH "\xef\x87\xa0" // U+f1e0 +#define ICON_MD_NORTH_EAST "\xef\x87\xa1" // U+f1e1 +#define ICON_MD_NORTH_WEST "\xef\x87\xa2" // U+f1e2 +#define ICON_MD_NOT_ACCESSIBLE "\xef\x83\xbe" // U+f0fe +#define ICON_MD_NOT_INTERESTED "\xee\x80\xb3" // U+e033 +#define ICON_MD_NOT_LISTED_LOCATION "\xee\x95\xb5" // U+e575 +#define ICON_MD_NOT_STARTED "\xef\x83\x91" // U+f0d1 +#define ICON_MD_NOTE "\xee\x81\xaf" // U+e06f +#define ICON_MD_NOTE_ADD "\xee\xa2\x9c" // U+e89c +#define ICON_MD_NOTE_ALT "\xef\x81\x80" // U+f040 +#define ICON_MD_NOTES "\xee\x89\xac" // U+e26c +#define ICON_MD_NOTIFICATION_ADD "\xee\x8e\x99" // U+e399 +#define ICON_MD_NOTIFICATION_IMPORTANT "\xee\x80\x84" // U+e004 +#define ICON_MD_NOTIFICATIONS "\xee\x9f\xb4" // U+e7f4 +#define ICON_MD_NOTIFICATIONS_ACTIVE "\xee\x9f\xb7" // U+e7f7 +#define ICON_MD_NOTIFICATIONS_NONE "\xee\x9f\xb5" // U+e7f5 +#define ICON_MD_NOTIFICATIONS_OFF "\xee\x9f\xb6" // U+e7f6 +#define ICON_MD_NOTIFICATIONS_ON "\xee\x9f\xb7" // U+e7f7 +#define ICON_MD_NOTIFICATIONS_PAUSED "\xee\x9f\xb8" // U+e7f8 +#define ICON_MD_NOW_WALLPAPER "\xee\x86\xbc" // U+e1bc +#define ICON_MD_NOW_WIDGETS "\xee\x86\xbd" // U+e1bd +#define ICON_MD_NUMBERS "\xee\xab\x87" // U+eac7 +#define ICON_MD_OFFLINE_BOLT "\xee\xa4\xb2" // U+e932 +#define ICON_MD_OFFLINE_PIN "\xee\xa4\x8a" // U+e90a +#define ICON_MD_OFFLINE_SHARE "\xee\xa7\x85" // U+e9c5 +#define ICON_MD_OIL_BARREL "\xee\xb0\x95" // U+ec15 +#define ICON_MD_ON_DEVICE_TRAINING "\xee\xaf\xbd" // U+ebfd +#define ICON_MD_ONDEMAND_VIDEO "\xee\x98\xba" // U+e63a +#define ICON_MD_ONLINE_PREDICTION "\xef\x83\xab" // U+f0eb +#define ICON_MD_OPACITY "\xee\xa4\x9c" // U+e91c +#define ICON_MD_OPEN_IN_BROWSER "\xee\xa2\x9d" // U+e89d +#define ICON_MD_OPEN_IN_FULL "\xef\x87\x8e" // U+f1ce +#define ICON_MD_OPEN_IN_NEW "\xee\xa2\x9e" // U+e89e +#define ICON_MD_OPEN_IN_NEW_OFF "\xee\x93\xb6" // U+e4f6 +#define ICON_MD_OPEN_WITH "\xee\xa2\x9f" // U+e89f +#define ICON_MD_OTHER_HOUSES "\xee\x96\x8c" // U+e58c +#define ICON_MD_OUTBOND "\xef\x88\xa8" // U+f228 +#define ICON_MD_OUTBOUND "\xee\x87\x8a" // U+e1ca +#define ICON_MD_OUTBOX "\xee\xbd\x9f" // U+ef5f +#define ICON_MD_OUTDOOR_GRILL "\xee\xa9\x87" // U+ea47 +#define ICON_MD_OUTGOING_MAIL "\xef\x83\x92" // U+f0d2 +#define ICON_MD_OUTLET "\xef\x87\x94" // U+f1d4 +#define ICON_MD_OUTLINED_FLAG "\xee\x85\xae" // U+e16e +#define ICON_MD_OUTPUT "\xee\xae\xbe" // U+ebbe +#define ICON_MD_PADDING "\xee\xa7\x88" // U+e9c8 +#define ICON_MD_PAGES "\xee\x9f\xb9" // U+e7f9 +#define ICON_MD_PAGEVIEW "\xee\xa2\xa0" // U+e8a0 +#define ICON_MD_PAID "\xef\x81\x81" // U+f041 +#define ICON_MD_PALETTE "\xee\x90\x8a" // U+e40a +#define ICON_MD_PALLET "\xef\xa1\xaa" // U+f86a +#define ICON_MD_PAN_TOOL "\xee\xa4\xa5" // U+e925 +#define ICON_MD_PAN_TOOL_ALT "\xee\xae\xb9" // U+ebb9 +#define ICON_MD_PANORAMA "\xee\x90\x8b" // U+e40b +#define ICON_MD_PANORAMA_FISH_EYE "\xee\x90\x8c" // U+e40c +#define ICON_MD_PANORAMA_FISHEYE "\xee\x90\x8c" // U+e40c +#define ICON_MD_PANORAMA_HORIZONTAL "\xee\x90\x8d" // U+e40d +#define ICON_MD_PANORAMA_HORIZONTAL_SELECT "\xee\xbd\xa0" // U+ef60 +#define ICON_MD_PANORAMA_PHOTOSPHERE "\xee\xa7\x89" // U+e9c9 +#define ICON_MD_PANORAMA_PHOTOSPHERE_SELECT "\xee\xa7\x8a" // U+e9ca +#define ICON_MD_PANORAMA_VERTICAL "\xee\x90\x8e" // U+e40e +#define ICON_MD_PANORAMA_VERTICAL_SELECT "\xee\xbd\xa1" // U+ef61 +#define ICON_MD_PANORAMA_WIDE_ANGLE "\xee\x90\x8f" // U+e40f +#define ICON_MD_PANORAMA_WIDE_ANGLE_SELECT "\xee\xbd\xa2" // U+ef62 +#define ICON_MD_PARAGLIDING "\xee\x94\x8f" // U+e50f +#define ICON_MD_PARK "\xee\xa9\xa3" // U+ea63 +#define ICON_MD_PARTY_MODE "\xee\x9f\xba" // U+e7fa +#define ICON_MD_PASSWORD "\xef\x81\x82" // U+f042 +#define ICON_MD_PATTERN "\xef\x81\x83" // U+f043 +#define ICON_MD_PAUSE "\xee\x80\xb4" // U+e034 +#define ICON_MD_PAUSE_CIRCLE "\xee\x86\xa2" // U+e1a2 +#define ICON_MD_PAUSE_CIRCLE_FILLED "\xee\x80\xb5" // U+e035 +#define ICON_MD_PAUSE_CIRCLE_OUTLINE "\xee\x80\xb6" // U+e036 +#define ICON_MD_PAUSE_PRESENTATION "\xee\x83\xaa" // U+e0ea +#define ICON_MD_PAYMENT "\xee\xa2\xa1" // U+e8a1 +#define ICON_MD_PAYMENTS "\xee\xbd\xa3" // U+ef63 +#define ICON_MD_PAYPAL "\xee\xaa\x8d" // U+ea8d +#define ICON_MD_PEDAL_BIKE "\xee\xac\xa9" // U+eb29 +#define ICON_MD_PENDING "\xee\xbd\xa4" // U+ef64 +#define ICON_MD_PENDING_ACTIONS "\xef\x86\xbb" // U+f1bb +#define ICON_MD_PENTAGON "\xee\xad\x90" // U+eb50 +#define ICON_MD_PEOPLE "\xee\x9f\xbb" // U+e7fb +#define ICON_MD_PEOPLE_ALT "\xee\xa8\xa1" // U+ea21 +#define ICON_MD_PEOPLE_OUTLINE "\xee\x9f\xbc" // U+e7fc +#define ICON_MD_PERCENT "\xee\xad\x98" // U+eb58 +#define ICON_MD_PERM_CAMERA_MIC "\xee\xa2\xa2" // U+e8a2 +#define ICON_MD_PERM_CONTACT_CAL "\xee\xa2\xa3" // U+e8a3 +#define ICON_MD_PERM_CONTACT_CALENDAR "\xee\xa2\xa3" // U+e8a3 +#define ICON_MD_PERM_DATA_SETTING "\xee\xa2\xa4" // U+e8a4 +#define ICON_MD_PERM_DEVICE_INFO "\xee\xa2\xa5" // U+e8a5 +#define ICON_MD_PERM_DEVICE_INFORMATION "\xee\xa2\xa5" // U+e8a5 +#define ICON_MD_PERM_IDENTITY "\xee\xa2\xa6" // U+e8a6 +#define ICON_MD_PERM_MEDIA "\xee\xa2\xa7" // U+e8a7 +#define ICON_MD_PERM_PHONE_MSG "\xee\xa2\xa8" // U+e8a8 +#define ICON_MD_PERM_SCAN_WIFI "\xee\xa2\xa9" // U+e8a9 +#define ICON_MD_PERSON "\xee\x9f\xbd" // U+e7fd +#define ICON_MD_PERSON_2 "\xef\xa3\xa4" // U+f8e4 +#define ICON_MD_PERSON_3 "\xef\xa3\xa5" // U+f8e5 +#define ICON_MD_PERSON_4 "\xef\xa3\xa6" // U+f8e6 +#define ICON_MD_PERSON_ADD "\xee\x9f\xbe" // U+e7fe +#define ICON_MD_PERSON_ADD_ALT "\xee\xa9\x8d" // U+ea4d +#define ICON_MD_PERSON_ADD_ALT_1 "\xee\xbd\xa5" // U+ef65 +#define ICON_MD_PERSON_ADD_DISABLED "\xee\xa7\x8b" // U+e9cb +#define ICON_MD_PERSON_OFF "\xee\x94\x90" // U+e510 +#define ICON_MD_PERSON_OUTLINE "\xee\x9f\xbf" // U+e7ff +#define ICON_MD_PERSON_PIN "\xee\x95\x9a" // U+e55a +#define ICON_MD_PERSON_PIN_CIRCLE "\xee\x95\xaa" // U+e56a +#define ICON_MD_PERSON_REMOVE "\xee\xbd\xa6" // U+ef66 +#define ICON_MD_PERSON_REMOVE_ALT_1 "\xee\xbd\xa7" // U+ef67 +#define ICON_MD_PERSON_SEARCH "\xef\x84\x86" // U+f106 +#define ICON_MD_PERSONAL_INJURY "\xee\x9b\x9a" // U+e6da +#define ICON_MD_PERSONAL_VIDEO "\xee\x98\xbb" // U+e63b +#define ICON_MD_PEST_CONTROL "\xef\x83\xba" // U+f0fa +#define ICON_MD_PEST_CONTROL_RODENT "\xef\x83\xbd" // U+f0fd +#define ICON_MD_PETS "\xee\xa4\x9d" // U+e91d +#define ICON_MD_PHISHING "\xee\xab\x97" // U+ead7 +#define ICON_MD_PHONE "\xee\x83\x8d" // U+e0cd +#define ICON_MD_PHONE_ANDROID "\xee\x8c\xa4" // U+e324 +#define ICON_MD_PHONE_BLUETOOTH_SPEAKER "\xee\x98\x9b" // U+e61b +#define ICON_MD_PHONE_CALLBACK "\xee\x99\x89" // U+e649 +#define ICON_MD_PHONE_DISABLED "\xee\xa7\x8c" // U+e9cc +#define ICON_MD_PHONE_ENABLED "\xee\xa7\x8d" // U+e9cd +#define ICON_MD_PHONE_FORWARDED "\xee\x98\x9c" // U+e61c +#define ICON_MD_PHONE_IN_TALK "\xee\x98\x9d" // U+e61d +#define ICON_MD_PHONE_IPHONE "\xee\x8c\xa5" // U+e325 +#define ICON_MD_PHONE_LOCKED "\xee\x98\x9e" // U+e61e +#define ICON_MD_PHONE_MISSED "\xee\x98\x9f" // U+e61f +#define ICON_MD_PHONE_PAUSED "\xee\x98\xa0" // U+e620 +#define ICON_MD_PHONELINK "\xee\x8c\xa6" // U+e326 +#define ICON_MD_PHONELINK_ERASE "\xee\x83\x9b" // U+e0db +#define ICON_MD_PHONELINK_LOCK "\xee\x83\x9c" // U+e0dc +#define ICON_MD_PHONELINK_OFF "\xee\x8c\xa7" // U+e327 +#define ICON_MD_PHONELINK_RING "\xee\x83\x9d" // U+e0dd +#define ICON_MD_PHONELINK_SETUP "\xee\x83\x9e" // U+e0de +#define ICON_MD_PHOTO "\xee\x90\x90" // U+e410 +#define ICON_MD_PHOTO_ALBUM "\xee\x90\x91" // U+e411 +#define ICON_MD_PHOTO_CAMERA "\xee\x90\x92" // U+e412 +#define ICON_MD_PHOTO_CAMERA_BACK "\xee\xbd\xa8" // U+ef68 +#define ICON_MD_PHOTO_CAMERA_FRONT "\xee\xbd\xa9" // U+ef69 +#define ICON_MD_PHOTO_FILTER "\xee\x90\xbb" // U+e43b +#define ICON_MD_PHOTO_LIBRARY "\xee\x90\x93" // U+e413 +#define ICON_MD_PHOTO_SIZE_SELECT_ACTUAL "\xee\x90\xb2" // U+e432 +#define ICON_MD_PHOTO_SIZE_SELECT_LARGE "\xee\x90\xb3" // U+e433 +#define ICON_MD_PHOTO_SIZE_SELECT_SMALL "\xee\x90\xb4" // U+e434 +#define ICON_MD_PHP "\xee\xae\x8f" // U+eb8f +#define ICON_MD_PIANO "\xee\x94\xa1" // U+e521 +#define ICON_MD_PIANO_OFF "\xee\x94\xa0" // U+e520 +#define ICON_MD_PICTURE_AS_PDF "\xee\x90\x95" // U+e415 +#define ICON_MD_PICTURE_IN_PICTURE "\xee\xa2\xaa" // U+e8aa +#define ICON_MD_PICTURE_IN_PICTURE_ALT "\xee\xa4\x91" // U+e911 +#define ICON_MD_PIE_CHART "\xee\x9b\x84" // U+e6c4 +#define ICON_MD_PIE_CHART_OUTLINE "\xef\x81\x84" // U+f044 +#define ICON_MD_PIE_CHART_OUTLINED "\xee\x9b\x85" // U+e6c5 +#define ICON_MD_PIN "\xef\x81\x85" // U+f045 +#define ICON_MD_PIN_DROP "\xee\x95\x9e" // U+e55e +#define ICON_MD_PIN_END "\xee\x9d\xa7" // U+e767 +#define ICON_MD_PIN_INVOKE "\xee\x9d\xa3" // U+e763 +#define ICON_MD_PINCH "\xee\xac\xb8" // U+eb38 +#define ICON_MD_PIVOT_TABLE_CHART "\xee\xa7\x8e" // U+e9ce +#define ICON_MD_PIX "\xee\xaa\xa3" // U+eaa3 +#define ICON_MD_PLACE "\xee\x95\x9f" // U+e55f +#define ICON_MD_PLAGIARISM "\xee\xa9\x9a" // U+ea5a +#define ICON_MD_PLAY_ARROW "\xee\x80\xb7" // U+e037 +#define ICON_MD_PLAY_CIRCLE "\xee\x87\x84" // U+e1c4 +#define ICON_MD_PLAY_CIRCLE_FILL "\xee\x80\xb8" // U+e038 +#define ICON_MD_PLAY_CIRCLE_FILLED "\xee\x80\xb8" // U+e038 +#define ICON_MD_PLAY_CIRCLE_OUTLINE "\xee\x80\xb9" // U+e039 +#define ICON_MD_PLAY_DISABLED "\xee\xbd\xaa" // U+ef6a +#define ICON_MD_PLAY_FOR_WORK "\xee\xa4\x86" // U+e906 +#define ICON_MD_PLAY_LESSON "\xef\x81\x87" // U+f047 +#define ICON_MD_PLAYLIST_ADD "\xee\x80\xbb" // U+e03b +#define ICON_MD_PLAYLIST_ADD_CHECK "\xee\x81\xa5" // U+e065 +#define ICON_MD_PLAYLIST_ADD_CHECK_CIRCLE "\xee\x9f\xa6" // U+e7e6 +#define ICON_MD_PLAYLIST_ADD_CIRCLE "\xee\x9f\xa5" // U+e7e5 +#define ICON_MD_PLAYLIST_PLAY "\xee\x81\x9f" // U+e05f +#define ICON_MD_PLAYLIST_REMOVE "\xee\xae\x80" // U+eb80 +#define ICON_MD_PLUMBING "\xef\x84\x87" // U+f107 +#define ICON_MD_PLUS_ONE "\xee\xa0\x80" // U+e800 +#define ICON_MD_PODCASTS "\xef\x81\x88" // U+f048 +#define ICON_MD_POINT_OF_SALE "\xef\x85\xbe" // U+f17e +#define ICON_MD_POLICY "\xee\xa8\x97" // U+ea17 +#define ICON_MD_POLL "\xee\xa0\x81" // U+e801 +#define ICON_MD_POLYLINE "\xee\xae\xbb" // U+ebbb +#define ICON_MD_POLYMER "\xee\xa2\xab" // U+e8ab +#define ICON_MD_POOL "\xee\xad\x88" // U+eb48 +#define ICON_MD_PORTABLE_WIFI_OFF "\xee\x83\x8e" // U+e0ce +#define ICON_MD_PORTRAIT "\xee\x90\x96" // U+e416 +#define ICON_MD_POST_ADD "\xee\xa8\xa0" // U+ea20 +#define ICON_MD_POWER "\xee\x98\xbc" // U+e63c +#define ICON_MD_POWER_INPUT "\xee\x8c\xb6" // U+e336 +#define ICON_MD_POWER_OFF "\xee\x99\x86" // U+e646 +#define ICON_MD_POWER_SETTINGS_NEW "\xee\xa2\xac" // U+e8ac +#define ICON_MD_PRECISION_MANUFACTURING "\xef\x81\x89" // U+f049 +#define ICON_MD_PREGNANT_WOMAN "\xee\xa4\x9e" // U+e91e +#define ICON_MD_PRESENT_TO_ALL "\xee\x83\x9f" // U+e0df +#define ICON_MD_PREVIEW "\xef\x87\x85" // U+f1c5 +#define ICON_MD_PRICE_CHANGE "\xef\x81\x8a" // U+f04a +#define ICON_MD_PRICE_CHECK "\xef\x81\x8b" // U+f04b +#define ICON_MD_PRINT "\xee\xa2\xad" // U+e8ad +#define ICON_MD_PRINT_DISABLED "\xee\xa7\x8f" // U+e9cf +#define ICON_MD_PRIORITY_HIGH "\xee\x99\x85" // U+e645 +#define ICON_MD_PRIVACY_TIP "\xef\x83\x9c" // U+f0dc +#define ICON_MD_PRIVATE_CONNECTIVITY "\xee\x9d\x84" // U+e744 +#define ICON_MD_PRODUCTION_QUANTITY_LIMITS "\xee\x87\x91" // U+e1d1 +#define ICON_MD_PROPANE "\xee\xb0\x94" // U+ec14 +#define ICON_MD_PROPANE_TANK "\xee\xb0\x93" // U+ec13 +#define ICON_MD_PSYCHOLOGY "\xee\xa9\x8a" // U+ea4a +#define ICON_MD_PSYCHOLOGY_ALT "\xef\xa3\xaa" // U+f8ea +#define ICON_MD_PUBLIC "\xee\xa0\x8b" // U+e80b +#define ICON_MD_PUBLIC_OFF "\xef\x87\x8a" // U+f1ca +#define ICON_MD_PUBLISH "\xee\x89\x95" // U+e255 +#define ICON_MD_PUBLISHED_WITH_CHANGES "\xef\x88\xb2" // U+f232 +#define ICON_MD_PUNCH_CLOCK "\xee\xaa\xa8" // U+eaa8 +#define ICON_MD_PUSH_PIN "\xef\x84\x8d" // U+f10d +#define ICON_MD_QR_CODE "\xee\xbd\xab" // U+ef6b +#define ICON_MD_QR_CODE_2 "\xee\x80\x8a" // U+e00a +#define ICON_MD_QR_CODE_SCANNER "\xef\x88\x86" // U+f206 +#define ICON_MD_QUERY_BUILDER "\xee\xa2\xae" // U+e8ae +#define ICON_MD_QUERY_STATS "\xee\x93\xbc" // U+e4fc +#define ICON_MD_QUESTION_ANSWER "\xee\xa2\xaf" // U+e8af +#define ICON_MD_QUESTION_MARK "\xee\xae\x8b" // U+eb8b +#define ICON_MD_QUEUE "\xee\x80\xbc" // U+e03c +#define ICON_MD_QUEUE_MUSIC "\xee\x80\xbd" // U+e03d +#define ICON_MD_QUEUE_PLAY_NEXT "\xee\x81\xa6" // U+e066 +#define ICON_MD_QUICK_CONTACTS_DIALER "\xee\x83\x8f" // U+e0cf +#define ICON_MD_QUICK_CONTACTS_MAIL "\xee\x83\x90" // U+e0d0 +#define ICON_MD_QUICKREPLY "\xee\xbd\xac" // U+ef6c +#define ICON_MD_QUIZ "\xef\x81\x8c" // U+f04c +#define ICON_MD_QUORA "\xee\xaa\x98" // U+ea98 +#define ICON_MD_R_MOBILEDATA "\xef\x81\x8d" // U+f04d +#define ICON_MD_RADAR "\xef\x81\x8e" // U+f04e +#define ICON_MD_RADIO "\xee\x80\xbe" // U+e03e +#define ICON_MD_RADIO_BUTTON_CHECKED "\xee\xa0\xb7" // U+e837 +#define ICON_MD_RADIO_BUTTON_OFF "\xee\xa0\xb6" // U+e836 +#define ICON_MD_RADIO_BUTTON_ON "\xee\xa0\xb7" // U+e837 +#define ICON_MD_RADIO_BUTTON_UNCHECKED "\xee\xa0\xb6" // U+e836 +#define ICON_MD_RAILWAY_ALERT "\xee\xa7\x91" // U+e9d1 +#define ICON_MD_RAMEN_DINING "\xee\xa9\xa4" // U+ea64 +#define ICON_MD_RAMP_LEFT "\xee\xae\x9c" // U+eb9c +#define ICON_MD_RAMP_RIGHT "\xee\xae\x96" // U+eb96 +#define ICON_MD_RATE_REVIEW "\xee\x95\xa0" // U+e560 +#define ICON_MD_RAW_OFF "\xef\x81\x8f" // U+f04f +#define ICON_MD_RAW_ON "\xef\x81\x90" // U+f050 +#define ICON_MD_READ_MORE "\xee\xbd\xad" // U+ef6d +#define ICON_MD_REAL_ESTATE_AGENT "\xee\x9c\xba" // U+e73a +#define ICON_MD_RECEIPT "\xee\xa2\xb0" // U+e8b0 +#define ICON_MD_RECEIPT_LONG "\xee\xbd\xae" // U+ef6e +#define ICON_MD_RECENT_ACTORS "\xee\x80\xbf" // U+e03f +#define ICON_MD_RECOMMEND "\xee\xa7\x92" // U+e9d2 +#define ICON_MD_RECORD_VOICE_OVER "\xee\xa4\x9f" // U+e91f +#define ICON_MD_RECTANGLE "\xee\xad\x94" // U+eb54 +#define ICON_MD_RECYCLING "\xee\x9d\xa0" // U+e760 +#define ICON_MD_REDDIT "\xee\xaa\xa0" // U+eaa0 +#define ICON_MD_REDEEM "\xee\xa2\xb1" // U+e8b1 +#define ICON_MD_REDO "\xee\x85\x9a" // U+e15a +#define ICON_MD_REDUCE_CAPACITY "\xef\x88\x9c" // U+f21c +#define ICON_MD_REFRESH "\xee\x97\x95" // U+e5d5 +#define ICON_MD_REMEMBER_ME "\xef\x81\x91" // U+f051 +#define ICON_MD_REMOVE "\xee\x85\x9b" // U+e15b +#define ICON_MD_REMOVE_CIRCLE "\xee\x85\x9c" // U+e15c +#define ICON_MD_REMOVE_CIRCLE_OUTLINE "\xee\x85\x9d" // U+e15d +#define ICON_MD_REMOVE_DONE "\xee\xa7\x93" // U+e9d3 +#define ICON_MD_REMOVE_FROM_QUEUE "\xee\x81\xa7" // U+e067 +#define ICON_MD_REMOVE_MODERATOR "\xee\xa7\x94" // U+e9d4 +#define ICON_MD_REMOVE_RED_EYE "\xee\x90\x97" // U+e417 +#define ICON_MD_REMOVE_ROAD "\xee\xaf\xbc" // U+ebfc +#define ICON_MD_REMOVE_SHOPPING_CART "\xee\xa4\xa8" // U+e928 +#define ICON_MD_REORDER "\xee\xa3\xbe" // U+e8fe +#define ICON_MD_REPARTITION "\xef\xa3\xa8" // U+f8e8 +#define ICON_MD_REPEAT "\xee\x81\x80" // U+e040 +#define ICON_MD_REPEAT_ON "\xee\xa7\x96" // U+e9d6 +#define ICON_MD_REPEAT_ONE "\xee\x81\x81" // U+e041 +#define ICON_MD_REPEAT_ONE_ON "\xee\xa7\x97" // U+e9d7 +#define ICON_MD_REPLAY "\xee\x81\x82" // U+e042 +#define ICON_MD_REPLAY_10 "\xee\x81\x99" // U+e059 +#define ICON_MD_REPLAY_30 "\xee\x81\x9a" // U+e05a +#define ICON_MD_REPLAY_5 "\xee\x81\x9b" // U+e05b +#define ICON_MD_REPLAY_CIRCLE_FILLED "\xee\xa7\x98" // U+e9d8 +#define ICON_MD_REPLY "\xee\x85\x9e" // U+e15e +#define ICON_MD_REPLY_ALL "\xee\x85\x9f" // U+e15f +#define ICON_MD_REPORT "\xee\x85\xa0" // U+e160 +#define ICON_MD_REPORT_GMAILERRORRED "\xef\x81\x92" // U+f052 +#define ICON_MD_REPORT_OFF "\xee\x85\xb0" // U+e170 +#define ICON_MD_REPORT_PROBLEM "\xee\xa2\xb2" // U+e8b2 +#define ICON_MD_REQUEST_PAGE "\xef\x88\xac" // U+f22c +#define ICON_MD_REQUEST_QUOTE "\xef\x86\xb6" // U+f1b6 +#define ICON_MD_RESET_TV "\xee\xa7\x99" // U+e9d9 +#define ICON_MD_RESTART_ALT "\xef\x81\x93" // U+f053 +#define ICON_MD_RESTAURANT "\xee\x95\xac" // U+e56c +#define ICON_MD_RESTAURANT_MENU "\xee\x95\xa1" // U+e561 +#define ICON_MD_RESTORE "\xee\xa2\xb3" // U+e8b3 +#define ICON_MD_RESTORE_FROM_TRASH "\xee\xa4\xb8" // U+e938 +#define ICON_MD_RESTORE_PAGE "\xee\xa4\xa9" // U+e929 +#define ICON_MD_REVIEWS "\xef\x81\x94" // U+f054 +#define ICON_MD_RICE_BOWL "\xef\x87\xb5" // U+f1f5 +#define ICON_MD_RING_VOLUME "\xee\x83\x91" // U+e0d1 +#define ICON_MD_ROCKET "\xee\xae\xa5" // U+eba5 +#define ICON_MD_ROCKET_LAUNCH "\xee\xae\x9b" // U+eb9b +#define ICON_MD_ROLLER_SHADES "\xee\xb0\x92" // U+ec12 +#define ICON_MD_ROLLER_SHADES_CLOSED "\xee\xb0\x91" // U+ec11 +#define ICON_MD_ROLLER_SKATING "\xee\xaf\x8d" // U+ebcd +#define ICON_MD_ROOFING "\xef\x88\x81" // U+f201 +#define ICON_MD_ROOM "\xee\xa2\xb4" // U+e8b4 +#define ICON_MD_ROOM_PREFERENCES "\xef\x86\xb8" // U+f1b8 +#define ICON_MD_ROOM_SERVICE "\xee\xad\x89" // U+eb49 +#define ICON_MD_ROTATE_90_DEGREES_CCW "\xee\x90\x98" // U+e418 +#define ICON_MD_ROTATE_90_DEGREES_CW "\xee\xaa\xab" // U+eaab +#define ICON_MD_ROTATE_LEFT "\xee\x90\x99" // U+e419 +#define ICON_MD_ROTATE_RIGHT "\xee\x90\x9a" // U+e41a +#define ICON_MD_ROUNDABOUT_LEFT "\xee\xae\x99" // U+eb99 +#define ICON_MD_ROUNDABOUT_RIGHT "\xee\xae\xa3" // U+eba3 +#define ICON_MD_ROUNDED_CORNER "\xee\xa4\xa0" // U+e920 +#define ICON_MD_ROUTE "\xee\xab\x8d" // U+eacd +#define ICON_MD_ROUTER "\xee\x8c\xa8" // U+e328 +#define ICON_MD_ROWING "\xee\xa4\xa1" // U+e921 +#define ICON_MD_RSS_FEED "\xee\x83\xa5" // U+e0e5 +#define ICON_MD_RSVP "\xef\x81\x95" // U+f055 +#define ICON_MD_RTT "\xee\xa6\xad" // U+e9ad +#define ICON_MD_RULE "\xef\x87\x82" // U+f1c2 +#define ICON_MD_RULE_FOLDER "\xef\x87\x89" // U+f1c9 +#define ICON_MD_RUN_CIRCLE "\xee\xbd\xaf" // U+ef6f +#define ICON_MD_RUNNING_WITH_ERRORS "\xee\x94\x9d" // U+e51d +#define ICON_MD_RV_HOOKUP "\xee\x99\x82" // U+e642 +#define ICON_MD_SAFETY_CHECK "\xee\xaf\xaf" // U+ebef +#define ICON_MD_SAFETY_DIVIDER "\xee\x87\x8c" // U+e1cc +#define ICON_MD_SAILING "\xee\x94\x82" // U+e502 +#define ICON_MD_SANITIZER "\xef\x88\x9d" // U+f21d +#define ICON_MD_SATELLITE "\xee\x95\xa2" // U+e562 +#define ICON_MD_SATELLITE_ALT "\xee\xac\xba" // U+eb3a +#define ICON_MD_SAVE "\xee\x85\xa1" // U+e161 +#define ICON_MD_SAVE_ALT "\xee\x85\xb1" // U+e171 +#define ICON_MD_SAVE_AS "\xee\xad\xa0" // U+eb60 +#define ICON_MD_SAVED_SEARCH "\xee\xa8\x91" // U+ea11 +#define ICON_MD_SAVINGS "\xee\x8b\xab" // U+e2eb +#define ICON_MD_SCALE "\xee\xad\x9f" // U+eb5f +#define ICON_MD_SCANNER "\xee\x8c\xa9" // U+e329 +#define ICON_MD_SCATTER_PLOT "\xee\x89\xa8" // U+e268 +#define ICON_MD_SCHEDULE "\xee\xa2\xb5" // U+e8b5 +#define ICON_MD_SCHEDULE_SEND "\xee\xa8\x8a" // U+ea0a +#define ICON_MD_SCHEMA "\xee\x93\xbd" // U+e4fd +#define ICON_MD_SCHOOL "\xee\xa0\x8c" // U+e80c +#define ICON_MD_SCIENCE "\xee\xa9\x8b" // U+ea4b +#define ICON_MD_SCORE "\xee\x89\xa9" // U+e269 +#define ICON_MD_SCOREBOARD "\xee\xaf\x90" // U+ebd0 +#define ICON_MD_SCREEN_LOCK_LANDSCAPE "\xee\x86\xbe" // U+e1be +#define ICON_MD_SCREEN_LOCK_PORTRAIT "\xee\x86\xbf" // U+e1bf +#define ICON_MD_SCREEN_LOCK_ROTATION "\xee\x87\x80" // U+e1c0 +#define ICON_MD_SCREEN_ROTATION "\xee\x87\x81" // U+e1c1 +#define ICON_MD_SCREEN_ROTATION_ALT "\xee\xaf\xae" // U+ebee +#define ICON_MD_SCREEN_SEARCH_DESKTOP "\xee\xbd\xb0" // U+ef70 +#define ICON_MD_SCREEN_SHARE "\xee\x83\xa2" // U+e0e2 +#define ICON_MD_SCREENSHOT "\xef\x81\x96" // U+f056 +#define ICON_MD_SCREENSHOT_MONITOR "\xee\xb0\x88" // U+ec08 +#define ICON_MD_SCUBA_DIVING "\xee\xaf\x8e" // U+ebce +#define ICON_MD_SD "\xee\xa7\x9d" // U+e9dd +#define ICON_MD_SD_CARD "\xee\x98\xa3" // U+e623 +#define ICON_MD_SD_CARD_ALERT "\xef\x81\x97" // U+f057 +#define ICON_MD_SD_STORAGE "\xee\x87\x82" // U+e1c2 +#define ICON_MD_SEARCH "\xee\xa2\xb6" // U+e8b6 +#define ICON_MD_SEARCH_OFF "\xee\xa9\xb6" // U+ea76 +#define ICON_MD_SECURITY "\xee\x8c\xaa" // U+e32a +#define ICON_MD_SECURITY_UPDATE "\xef\x81\x98" // U+f058 +#define ICON_MD_SECURITY_UPDATE_GOOD "\xef\x81\x99" // U+f059 +#define ICON_MD_SECURITY_UPDATE_WARNING "\xef\x81\x9a" // U+f05a +#define ICON_MD_SEGMENT "\xee\xa5\x8b" // U+e94b +#define ICON_MD_SELECT_ALL "\xee\x85\xa2" // U+e162 +#define ICON_MD_SELF_IMPROVEMENT "\xee\xa9\xb8" // U+ea78 +#define ICON_MD_SELL "\xef\x81\x9b" // U+f05b +#define ICON_MD_SEND "\xee\x85\xa3" // U+e163 +#define ICON_MD_SEND_AND_ARCHIVE "\xee\xa8\x8c" // U+ea0c +#define ICON_MD_SEND_TIME_EXTENSION "\xee\xab\x9b" // U+eadb +#define ICON_MD_SEND_TO_MOBILE "\xef\x81\x9c" // U+f05c +#define ICON_MD_SENSOR_DOOR "\xef\x86\xb5" // U+f1b5 +#define ICON_MD_SENSOR_OCCUPIED "\xee\xb0\x90" // U+ec10 +#define ICON_MD_SENSOR_WINDOW "\xef\x86\xb4" // U+f1b4 +#define ICON_MD_SENSORS "\xee\x94\x9e" // U+e51e +#define ICON_MD_SENSORS_OFF "\xee\x94\x9f" // U+e51f +#define ICON_MD_SENTIMENT_DISSATISFIED "\xee\xa0\x91" // U+e811 +#define ICON_MD_SENTIMENT_NEUTRAL "\xee\xa0\x92" // U+e812 +#define ICON_MD_SENTIMENT_SATISFIED "\xee\xa0\x93" // U+e813 +#define ICON_MD_SENTIMENT_SATISFIED_ALT "\xee\x83\xad" // U+e0ed +#define ICON_MD_SENTIMENT_VERY_DISSATISFIED "\xee\xa0\x94" // U+e814 +#define ICON_MD_SENTIMENT_VERY_SATISFIED "\xee\xa0\x95" // U+e815 +#define ICON_MD_SET_MEAL "\xef\x87\xaa" // U+f1ea +#define ICON_MD_SETTINGS "\xee\xa2\xb8" // U+e8b8 +#define ICON_MD_SETTINGS_ACCESSIBILITY "\xef\x81\x9d" // U+f05d +#define ICON_MD_SETTINGS_APPLICATIONS "\xee\xa2\xb9" // U+e8b9 +#define ICON_MD_SETTINGS_BACKUP_RESTORE "\xee\xa2\xba" // U+e8ba +#define ICON_MD_SETTINGS_BLUETOOTH "\xee\xa2\xbb" // U+e8bb +#define ICON_MD_SETTINGS_BRIGHTNESS "\xee\xa2\xbd" // U+e8bd +#define ICON_MD_SETTINGS_CELL "\xee\xa2\xbc" // U+e8bc +#define ICON_MD_SETTINGS_DISPLAY "\xee\xa2\xbd" // U+e8bd +#define ICON_MD_SETTINGS_ETHERNET "\xee\xa2\xbe" // U+e8be +#define ICON_MD_SETTINGS_INPUT_ANTENNA "\xee\xa2\xbf" // U+e8bf +#define ICON_MD_SETTINGS_INPUT_COMPONENT "\xee\xa3\x80" // U+e8c0 +#define ICON_MD_SETTINGS_INPUT_COMPOSITE "\xee\xa3\x81" // U+e8c1 +#define ICON_MD_SETTINGS_INPUT_HDMI "\xee\xa3\x82" // U+e8c2 +#define ICON_MD_SETTINGS_INPUT_SVIDEO "\xee\xa3\x83" // U+e8c3 +#define ICON_MD_SETTINGS_OVERSCAN "\xee\xa3\x84" // U+e8c4 +#define ICON_MD_SETTINGS_PHONE "\xee\xa3\x85" // U+e8c5 +#define ICON_MD_SETTINGS_POWER "\xee\xa3\x86" // U+e8c6 +#define ICON_MD_SETTINGS_REMOTE "\xee\xa3\x87" // U+e8c7 +#define ICON_MD_SETTINGS_SUGGEST "\xef\x81\x9e" // U+f05e +#define ICON_MD_SETTINGS_SYSTEM_DAYDREAM "\xee\x87\x83" // U+e1c3 +#define ICON_MD_SETTINGS_VOICE "\xee\xa3\x88" // U+e8c8 +#define ICON_MD_SEVERE_COLD "\xee\xaf\x93" // U+ebd3 +#define ICON_MD_SHAPE_LINE "\xef\xa3\x93" // U+f8d3 +#define ICON_MD_SHARE "\xee\xa0\x8d" // U+e80d +#define ICON_MD_SHARE_ARRIVAL_TIME "\xee\x94\xa4" // U+e524 +#define ICON_MD_SHARE_LOCATION "\xef\x81\x9f" // U+f05f +#define ICON_MD_SHELVES "\xef\xa1\xae" // U+f86e +#define ICON_MD_SHIELD "\xee\xa7\xa0" // U+e9e0 +#define ICON_MD_SHIELD_MOON "\xee\xaa\xa9" // U+eaa9 +#define ICON_MD_SHOP "\xee\xa3\x89" // U+e8c9 +#define ICON_MD_SHOP_2 "\xee\x86\x9e" // U+e19e +#define ICON_MD_SHOP_TWO "\xee\xa3\x8a" // U+e8ca +#define ICON_MD_SHOPIFY "\xee\xaa\x9d" // U+ea9d +#define ICON_MD_SHOPPING_BAG "\xef\x87\x8c" // U+f1cc +#define ICON_MD_SHOPPING_BASKET "\xee\xa3\x8b" // U+e8cb +#define ICON_MD_SHOPPING_CART "\xee\xa3\x8c" // U+e8cc +#define ICON_MD_SHOPPING_CART_CHECKOUT "\xee\xae\x88" // U+eb88 +#define ICON_MD_SHORT_TEXT "\xee\x89\xa1" // U+e261 +#define ICON_MD_SHORTCUT "\xef\x81\xa0" // U+f060 +#define ICON_MD_SHOW_CHART "\xee\x9b\xa1" // U+e6e1 +#define ICON_MD_SHOWER "\xef\x81\xa1" // U+f061 +#define ICON_MD_SHUFFLE "\xee\x81\x83" // U+e043 +#define ICON_MD_SHUFFLE_ON "\xee\xa7\xa1" // U+e9e1 +#define ICON_MD_SHUTTER_SPEED "\xee\x90\xbd" // U+e43d +#define ICON_MD_SICK "\xef\x88\xa0" // U+f220 +#define ICON_MD_SIGN_LANGUAGE "\xee\xaf\xa5" // U+ebe5 +#define ICON_MD_SIGNAL_CELLULAR_0_BAR "\xef\x82\xa8" // U+f0a8 +#define ICON_MD_SIGNAL_CELLULAR_4_BAR "\xee\x87\x88" // U+e1c8 +#define ICON_MD_SIGNAL_CELLULAR_ALT "\xee\x88\x82" // U+e202 +#define ICON_MD_SIGNAL_CELLULAR_ALT_1_BAR "\xee\xaf\x9f" // U+ebdf +#define ICON_MD_SIGNAL_CELLULAR_ALT_2_BAR "\xee\xaf\xa3" // U+ebe3 +#define ICON_MD_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_0_BAR "\xef\x82\xac" // U+f0ac +#define ICON_MD_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_4_BAR "\xee\x87\x8d" // U+e1cd +#define ICON_MD_SIGNAL_CELLULAR_NO_SIM "\xee\x87\x8e" // U+e1ce +#define ICON_MD_SIGNAL_CELLULAR_NODATA "\xef\x81\xa2" // U+f062 +#define ICON_MD_SIGNAL_CELLULAR_NULL "\xee\x87\x8f" // U+e1cf +#define ICON_MD_SIGNAL_CELLULAR_OFF "\xee\x87\x90" // U+e1d0 +#define ICON_MD_SIGNAL_WIFI_0_BAR "\xef\x82\xb0" // U+f0b0 +#define ICON_MD_SIGNAL_WIFI_4_BAR "\xee\x87\x98" // U+e1d8 +#define ICON_MD_SIGNAL_WIFI_4_BAR_LOCK "\xee\x87\x99" // U+e1d9 +#define ICON_MD_SIGNAL_WIFI_BAD "\xef\x81\xa3" // U+f063 +#define ICON_MD_SIGNAL_WIFI_CONNECTED_NO_INTERNET_4 "\xef\x81\xa4" // U+f064 +#define ICON_MD_SIGNAL_WIFI_OFF "\xee\x87\x9a" // U+e1da +#define ICON_MD_SIGNAL_WIFI_STATUSBAR_4_BAR "\xef\x81\xa5" // U+f065 +#define ICON_MD_SIGNAL_WIFI_STATUSBAR_CONNECTED_NO_INTERNET_4 "\xef\x81\xa6" // U+f066 +#define ICON_MD_SIGNAL_WIFI_STATUSBAR_NULL "\xef\x81\xa7" // U+f067 +#define ICON_MD_SIGNPOST "\xee\xae\x91" // U+eb91 +#define ICON_MD_SIM_CARD "\xee\x8c\xab" // U+e32b +#define ICON_MD_SIM_CARD_ALERT "\xee\x98\xa4" // U+e624 +#define ICON_MD_SIM_CARD_DOWNLOAD "\xef\x81\xa8" // U+f068 +#define ICON_MD_SINGLE_BED "\xee\xa9\x88" // U+ea48 +#define ICON_MD_SIP "\xef\x81\xa9" // U+f069 +#define ICON_MD_SKATEBOARDING "\xee\x94\x91" // U+e511 +#define ICON_MD_SKIP_NEXT "\xee\x81\x84" // U+e044 +#define ICON_MD_SKIP_PREVIOUS "\xee\x81\x85" // U+e045 +#define ICON_MD_SLEDDING "\xee\x94\x92" // U+e512 +#define ICON_MD_SLIDESHOW "\xee\x90\x9b" // U+e41b +#define ICON_MD_SLOW_MOTION_VIDEO "\xee\x81\xa8" // U+e068 +#define ICON_MD_SMART_BUTTON "\xef\x87\x81" // U+f1c1 +#define ICON_MD_SMART_DISPLAY "\xef\x81\xaa" // U+f06a +#define ICON_MD_SMART_SCREEN "\xef\x81\xab" // U+f06b +#define ICON_MD_SMART_TOY "\xef\x81\xac" // U+f06c +#define ICON_MD_SMARTPHONE "\xee\x8c\xac" // U+e32c +#define ICON_MD_SMOKE_FREE "\xee\xad\x8a" // U+eb4a +#define ICON_MD_SMOKING_ROOMS "\xee\xad\x8b" // U+eb4b +#define ICON_MD_SMS "\xee\x98\xa5" // U+e625 +#define ICON_MD_SMS_FAILED "\xee\x98\xa6" // U+e626 +#define ICON_MD_SNAPCHAT "\xee\xa9\xae" // U+ea6e +#define ICON_MD_SNIPPET_FOLDER "\xef\x87\x87" // U+f1c7 +#define ICON_MD_SNOOZE "\xee\x81\x86" // U+e046 +#define ICON_MD_SNOWBOARDING "\xee\x94\x93" // U+e513 +#define ICON_MD_SNOWING "\xee\xa0\x8f" // U+e80f +#define ICON_MD_SNOWMOBILE "\xee\x94\x83" // U+e503 +#define ICON_MD_SNOWSHOEING "\xee\x94\x94" // U+e514 +#define ICON_MD_SOAP "\xef\x86\xb2" // U+f1b2 +#define ICON_MD_SOCIAL_DISTANCE "\xee\x87\x8b" // U+e1cb +#define ICON_MD_SOLAR_POWER "\xee\xb0\x8f" // U+ec0f +#define ICON_MD_SORT "\xee\x85\xa4" // U+e164 +#define ICON_MD_SORT_BY_ALPHA "\xee\x81\x93" // U+e053 +#define ICON_MD_SOS "\xee\xaf\xb7" // U+ebf7 +#define ICON_MD_SOUP_KITCHEN "\xee\x9f\x93" // U+e7d3 +#define ICON_MD_SOURCE "\xef\x87\x84" // U+f1c4 +#define ICON_MD_SOUTH "\xef\x87\xa3" // U+f1e3 +#define ICON_MD_SOUTH_AMERICA "\xee\x9f\xa4" // U+e7e4 +#define ICON_MD_SOUTH_EAST "\xef\x87\xa4" // U+f1e4 +#define ICON_MD_SOUTH_WEST "\xef\x87\xa5" // U+f1e5 +#define ICON_MD_SPA "\xee\xad\x8c" // U+eb4c +#define ICON_MD_SPACE_BAR "\xee\x89\x96" // U+e256 +#define ICON_MD_SPACE_DASHBOARD "\xee\x99\xab" // U+e66b +#define ICON_MD_SPATIAL_AUDIO "\xee\xaf\xab" // U+ebeb +#define ICON_MD_SPATIAL_AUDIO_OFF "\xee\xaf\xa8" // U+ebe8 +#define ICON_MD_SPATIAL_TRACKING "\xee\xaf\xaa" // U+ebea +#define ICON_MD_SPEAKER "\xee\x8c\xad" // U+e32d +#define ICON_MD_SPEAKER_GROUP "\xee\x8c\xae" // U+e32e +#define ICON_MD_SPEAKER_NOTES "\xee\xa3\x8d" // U+e8cd +#define ICON_MD_SPEAKER_NOTES_OFF "\xee\xa4\xaa" // U+e92a +#define ICON_MD_SPEAKER_PHONE "\xee\x83\x92" // U+e0d2 +#define ICON_MD_SPEED "\xee\xa7\xa4" // U+e9e4 +#define ICON_MD_SPELLCHECK "\xee\xa3\x8e" // U+e8ce +#define ICON_MD_SPLITSCREEN "\xef\x81\xad" // U+f06d +#define ICON_MD_SPOKE "\xee\xa6\xa7" // U+e9a7 +#define ICON_MD_SPORTS "\xee\xa8\xb0" // U+ea30 +#define ICON_MD_SPORTS_BAR "\xef\x87\xb3" // U+f1f3 +#define ICON_MD_SPORTS_BASEBALL "\xee\xa9\x91" // U+ea51 +#define ICON_MD_SPORTS_BASKETBALL "\xee\xa8\xa6" // U+ea26 +#define ICON_MD_SPORTS_CRICKET "\xee\xa8\xa7" // U+ea27 +#define ICON_MD_SPORTS_ESPORTS "\xee\xa8\xa8" // U+ea28 +#define ICON_MD_SPORTS_FOOTBALL "\xee\xa8\xa9" // U+ea29 +#define ICON_MD_SPORTS_GOLF "\xee\xa8\xaa" // U+ea2a +#define ICON_MD_SPORTS_GYMNASTICS "\xee\xaf\x84" // U+ebc4 +#define ICON_MD_SPORTS_HANDBALL "\xee\xa8\xb3" // U+ea33 +#define ICON_MD_SPORTS_HOCKEY "\xee\xa8\xab" // U+ea2b +#define ICON_MD_SPORTS_KABADDI "\xee\xa8\xb4" // U+ea34 +#define ICON_MD_SPORTS_MARTIAL_ARTS "\xee\xab\xa9" // U+eae9 +#define ICON_MD_SPORTS_MMA "\xee\xa8\xac" // U+ea2c +#define ICON_MD_SPORTS_MOTORSPORTS "\xee\xa8\xad" // U+ea2d +#define ICON_MD_SPORTS_RUGBY "\xee\xa8\xae" // U+ea2e +#define ICON_MD_SPORTS_SCORE "\xef\x81\xae" // U+f06e +#define ICON_MD_SPORTS_SOCCER "\xee\xa8\xaf" // U+ea2f +#define ICON_MD_SPORTS_TENNIS "\xee\xa8\xb2" // U+ea32 +#define ICON_MD_SPORTS_VOLLEYBALL "\xee\xa8\xb1" // U+ea31 +#define ICON_MD_SQUARE "\xee\xac\xb6" // U+eb36 +#define ICON_MD_SQUARE_FOOT "\xee\xa9\x89" // U+ea49 +#define ICON_MD_SSID_CHART "\xee\xad\xa6" // U+eb66 +#define ICON_MD_STACKED_BAR_CHART "\xee\xa7\xa6" // U+e9e6 +#define ICON_MD_STACKED_LINE_CHART "\xef\x88\xab" // U+f22b +#define ICON_MD_STADIUM "\xee\xae\x90" // U+eb90 +#define ICON_MD_STAIRS "\xef\x86\xa9" // U+f1a9 +#define ICON_MD_STAR "\xee\xa0\xb8" // U+e838 +#define ICON_MD_STAR_BORDER "\xee\xa0\xba" // U+e83a +#define ICON_MD_STAR_BORDER_PURPLE500 "\xef\x82\x99" // U+f099 +#define ICON_MD_STAR_HALF "\xee\xa0\xb9" // U+e839 +#define ICON_MD_STAR_OUTLINE "\xef\x81\xaf" // U+f06f +#define ICON_MD_STAR_PURPLE500 "\xef\x82\x9a" // U+f09a +#define ICON_MD_STAR_RATE "\xef\x83\xac" // U+f0ec +#define ICON_MD_STARS "\xee\xa3\x90" // U+e8d0 +#define ICON_MD_START "\xee\x82\x89" // U+e089 +#define ICON_MD_STAY_CURRENT_LANDSCAPE "\xee\x83\x93" // U+e0d3 +#define ICON_MD_STAY_CURRENT_PORTRAIT "\xee\x83\x94" // U+e0d4 +#define ICON_MD_STAY_PRIMARY_LANDSCAPE "\xee\x83\x95" // U+e0d5 +#define ICON_MD_STAY_PRIMARY_PORTRAIT "\xee\x83\x96" // U+e0d6 +#define ICON_MD_STICKY_NOTE_2 "\xef\x87\xbc" // U+f1fc +#define ICON_MD_STOP "\xee\x81\x87" // U+e047 +#define ICON_MD_STOP_CIRCLE "\xee\xbd\xb1" // U+ef71 +#define ICON_MD_STOP_SCREEN_SHARE "\xee\x83\xa3" // U+e0e3 +#define ICON_MD_STORAGE "\xee\x87\x9b" // U+e1db +#define ICON_MD_STORE "\xee\xa3\x91" // U+e8d1 +#define ICON_MD_STORE_MALL_DIRECTORY "\xee\x95\xa3" // U+e563 +#define ICON_MD_STOREFRONT "\xee\xa8\x92" // U+ea12 +#define ICON_MD_STORM "\xef\x81\xb0" // U+f070 +#define ICON_MD_STRAIGHT "\xee\xae\x95" // U+eb95 +#define ICON_MD_STRAIGHTEN "\xee\x90\x9c" // U+e41c +#define ICON_MD_STREAM "\xee\xa7\xa9" // U+e9e9 +#define ICON_MD_STREETVIEW "\xee\x95\xae" // U+e56e +#define ICON_MD_STRIKETHROUGH_S "\xee\x89\x97" // U+e257 +#define ICON_MD_STROLLER "\xef\x86\xae" // U+f1ae +#define ICON_MD_STYLE "\xee\x90\x9d" // U+e41d +#define ICON_MD_SUBDIRECTORY_ARROW_LEFT "\xee\x97\x99" // U+e5d9 +#define ICON_MD_SUBDIRECTORY_ARROW_RIGHT "\xee\x97\x9a" // U+e5da +#define ICON_MD_SUBJECT "\xee\xa3\x92" // U+e8d2 +#define ICON_MD_SUBSCRIPT "\xef\x84\x91" // U+f111 +#define ICON_MD_SUBSCRIPTIONS "\xee\x81\xa4" // U+e064 +#define ICON_MD_SUBTITLES "\xee\x81\x88" // U+e048 +#define ICON_MD_SUBTITLES_OFF "\xee\xbd\xb2" // U+ef72 +#define ICON_MD_SUBWAY "\xee\x95\xaf" // U+e56f +#define ICON_MD_SUMMARIZE "\xef\x81\xb1" // U+f071 +#define ICON_MD_SUNNY "\xee\xa0\x9a" // U+e81a +#define ICON_MD_SUNNY_SNOWING "\xee\xa0\x99" // U+e819 +#define ICON_MD_SUPERSCRIPT "\xef\x84\x92" // U+f112 +#define ICON_MD_SUPERVISED_USER_CIRCLE "\xee\xa4\xb9" // U+e939 +#define ICON_MD_SUPERVISOR_ACCOUNT "\xee\xa3\x93" // U+e8d3 +#define ICON_MD_SUPPORT "\xee\xbd\xb3" // U+ef73 +#define ICON_MD_SUPPORT_AGENT "\xef\x83\xa2" // U+f0e2 +#define ICON_MD_SURFING "\xee\x94\x95" // U+e515 +#define ICON_MD_SURROUND_SOUND "\xee\x81\x89" // U+e049 +#define ICON_MD_SWAP_CALLS "\xee\x83\x97" // U+e0d7 +#define ICON_MD_SWAP_HORIZ "\xee\xa3\x94" // U+e8d4 +#define ICON_MD_SWAP_HORIZONTAL_CIRCLE "\xee\xa4\xb3" // U+e933 +#define ICON_MD_SWAP_VERT "\xee\xa3\x95" // U+e8d5 +#define ICON_MD_SWAP_VERT_CIRCLE "\xee\xa3\x96" // U+e8d6 +#define ICON_MD_SWAP_VERTICAL_CIRCLE "\xee\xa3\x96" // U+e8d6 +#define ICON_MD_SWIPE "\xee\xa7\xac" // U+e9ec +#define ICON_MD_SWIPE_DOWN "\xee\xad\x93" // U+eb53 +#define ICON_MD_SWIPE_DOWN_ALT "\xee\xac\xb0" // U+eb30 +#define ICON_MD_SWIPE_LEFT "\xee\xad\x99" // U+eb59 +#define ICON_MD_SWIPE_LEFT_ALT "\xee\xac\xb3" // U+eb33 +#define ICON_MD_SWIPE_RIGHT "\xee\xad\x92" // U+eb52 +#define ICON_MD_SWIPE_RIGHT_ALT "\xee\xad\x96" // U+eb56 +#define ICON_MD_SWIPE_UP "\xee\xac\xae" // U+eb2e +#define ICON_MD_SWIPE_UP_ALT "\xee\xac\xb5" // U+eb35 +#define ICON_MD_SWIPE_VERTICAL "\xee\xad\x91" // U+eb51 +#define ICON_MD_SWITCH_ACCESS_SHORTCUT "\xee\x9f\xa1" // U+e7e1 +#define ICON_MD_SWITCH_ACCESS_SHORTCUT_ADD "\xee\x9f\xa2" // U+e7e2 +#define ICON_MD_SWITCH_ACCOUNT "\xee\xa7\xad" // U+e9ed +#define ICON_MD_SWITCH_CAMERA "\xee\x90\x9e" // U+e41e +#define ICON_MD_SWITCH_LEFT "\xef\x87\x91" // U+f1d1 +#define ICON_MD_SWITCH_RIGHT "\xef\x87\x92" // U+f1d2 +#define ICON_MD_SWITCH_VIDEO "\xee\x90\x9f" // U+e41f +#define ICON_MD_SYNAGOGUE "\xee\xaa\xb0" // U+eab0 +#define ICON_MD_SYNC "\xee\x98\xa7" // U+e627 +#define ICON_MD_SYNC_ALT "\xee\xa8\x98" // U+ea18 +#define ICON_MD_SYNC_DISABLED "\xee\x98\xa8" // U+e628 +#define ICON_MD_SYNC_LOCK "\xee\xab\xae" // U+eaee +#define ICON_MD_SYNC_PROBLEM "\xee\x98\xa9" // U+e629 +#define ICON_MD_SYSTEM_SECURITY_UPDATE "\xef\x81\xb2" // U+f072 +#define ICON_MD_SYSTEM_SECURITY_UPDATE_GOOD "\xef\x81\xb3" // U+f073 +#define ICON_MD_SYSTEM_SECURITY_UPDATE_WARNING "\xef\x81\xb4" // U+f074 +#define ICON_MD_SYSTEM_UPDATE "\xee\x98\xaa" // U+e62a +#define ICON_MD_SYSTEM_UPDATE_ALT "\xee\xa3\x97" // U+e8d7 +#define ICON_MD_SYSTEM_UPDATE_TV "\xee\xa3\x97" // U+e8d7 +#define ICON_MD_TAB "\xee\xa3\x98" // U+e8d8 +#define ICON_MD_TAB_UNSELECTED "\xee\xa3\x99" // U+e8d9 +#define ICON_MD_TABLE_BAR "\xee\xab\x92" // U+ead2 +#define ICON_MD_TABLE_CHART "\xee\x89\xa5" // U+e265 +#define ICON_MD_TABLE_RESTAURANT "\xee\xab\x86" // U+eac6 +#define ICON_MD_TABLE_ROWS "\xef\x84\x81" // U+f101 +#define ICON_MD_TABLE_VIEW "\xef\x86\xbe" // U+f1be +#define ICON_MD_TABLET "\xee\x8c\xaf" // U+e32f +#define ICON_MD_TABLET_ANDROID "\xee\x8c\xb0" // U+e330 +#define ICON_MD_TABLET_MAC "\xee\x8c\xb1" // U+e331 +#define ICON_MD_TAG "\xee\xa7\xaf" // U+e9ef +#define ICON_MD_TAG_FACES "\xee\x90\xa0" // U+e420 +#define ICON_MD_TAKEOUT_DINING "\xee\xa9\xb4" // U+ea74 +#define ICON_MD_TAP_AND_PLAY "\xee\x98\xab" // U+e62b +#define ICON_MD_TAPAS "\xef\x87\xa9" // U+f1e9 +#define ICON_MD_TASK "\xef\x81\xb5" // U+f075 +#define ICON_MD_TASK_ALT "\xee\x8b\xa6" // U+e2e6 +#define ICON_MD_TAXI_ALERT "\xee\xbd\xb4" // U+ef74 +#define ICON_MD_TELEGRAM "\xee\xa9\xab" // U+ea6b +#define ICON_MD_TEMPLE_BUDDHIST "\xee\xaa\xb3" // U+eab3 +#define ICON_MD_TEMPLE_HINDU "\xee\xaa\xaf" // U+eaaf +#define ICON_MD_TERMINAL "\xee\xae\x8e" // U+eb8e +#define ICON_MD_TERRAIN "\xee\x95\xa4" // U+e564 +#define ICON_MD_TEXT_DECREASE "\xee\xab\x9d" // U+eadd +#define ICON_MD_TEXT_FIELDS "\xee\x89\xa2" // U+e262 +#define ICON_MD_TEXT_FORMAT "\xee\x85\xa5" // U+e165 +#define ICON_MD_TEXT_INCREASE "\xee\xab\xa2" // U+eae2 +#define ICON_MD_TEXT_ROTATE_UP "\xee\xa4\xba" // U+e93a +#define ICON_MD_TEXT_ROTATE_VERTICAL "\xee\xa4\xbb" // U+e93b +#define ICON_MD_TEXT_ROTATION_ANGLEDOWN "\xee\xa4\xbc" // U+e93c +#define ICON_MD_TEXT_ROTATION_ANGLEUP "\xee\xa4\xbd" // U+e93d +#define ICON_MD_TEXT_ROTATION_DOWN "\xee\xa4\xbe" // U+e93e +#define ICON_MD_TEXT_ROTATION_NONE "\xee\xa4\xbf" // U+e93f +#define ICON_MD_TEXT_SNIPPET "\xef\x87\x86" // U+f1c6 +#define ICON_MD_TEXTSMS "\xee\x83\x98" // U+e0d8 +#define ICON_MD_TEXTURE "\xee\x90\xa1" // U+e421 +#define ICON_MD_THEATER_COMEDY "\xee\xa9\xa6" // U+ea66 +#define ICON_MD_THEATERS "\xee\xa3\x9a" // U+e8da +#define ICON_MD_THERMOSTAT "\xef\x81\xb6" // U+f076 +#define ICON_MD_THERMOSTAT_AUTO "\xef\x81\xb7" // U+f077 +#define ICON_MD_THUMB_DOWN "\xee\xa3\x9b" // U+e8db +#define ICON_MD_THUMB_DOWN_ALT "\xee\xa0\x96" // U+e816 +#define ICON_MD_THUMB_DOWN_OFF_ALT "\xee\xa7\xb2" // U+e9f2 +#define ICON_MD_THUMB_UP "\xee\xa3\x9c" // U+e8dc +#define ICON_MD_THUMB_UP_ALT "\xee\xa0\x97" // U+e817 +#define ICON_MD_THUMB_UP_OFF_ALT "\xee\xa7\xb3" // U+e9f3 +#define ICON_MD_THUMBS_UP_DOWN "\xee\xa3\x9d" // U+e8dd +#define ICON_MD_THUNDERSTORM "\xee\xaf\x9b" // U+ebdb +#define ICON_MD_TIKTOK "\xee\xa9\xbe" // U+ea7e +#define ICON_MD_TIME_TO_LEAVE "\xee\x98\xac" // U+e62c +#define ICON_MD_TIMELAPSE "\xee\x90\xa2" // U+e422 +#define ICON_MD_TIMELINE "\xee\xa4\xa2" // U+e922 +#define ICON_MD_TIMER "\xee\x90\xa5" // U+e425 +#define ICON_MD_TIMER_10 "\xee\x90\xa3" // U+e423 +#define ICON_MD_TIMER_10_SELECT "\xef\x81\xba" // U+f07a +#define ICON_MD_TIMER_3 "\xee\x90\xa4" // U+e424 +#define ICON_MD_TIMER_3_SELECT "\xef\x81\xbb" // U+f07b +#define ICON_MD_TIMER_OFF "\xee\x90\xa6" // U+e426 +#define ICON_MD_TIPS_AND_UPDATES "\xee\x9e\x9a" // U+e79a +#define ICON_MD_TIRE_REPAIR "\xee\xaf\x88" // U+ebc8 +#define ICON_MD_TITLE "\xee\x89\xa4" // U+e264 +#define ICON_MD_TOC "\xee\xa3\x9e" // U+e8de +#define ICON_MD_TODAY "\xee\xa3\x9f" // U+e8df +#define ICON_MD_TOGGLE_OFF "\xee\xa7\xb5" // U+e9f5 +#define ICON_MD_TOGGLE_ON "\xee\xa7\xb6" // U+e9f6 +#define ICON_MD_TOKEN "\xee\xa8\xa5" // U+ea25 +#define ICON_MD_TOLL "\xee\xa3\xa0" // U+e8e0 +#define ICON_MD_TONALITY "\xee\x90\xa7" // U+e427 +#define ICON_MD_TOPIC "\xef\x87\x88" // U+f1c8 +#define ICON_MD_TORNADO "\xee\x86\x99" // U+e199 +#define ICON_MD_TOUCH_APP "\xee\xa4\x93" // U+e913 +#define ICON_MD_TOUR "\xee\xbd\xb5" // U+ef75 +#define ICON_MD_TOYS "\xee\x8c\xb2" // U+e332 +#define ICON_MD_TRACK_CHANGES "\xee\xa3\xa1" // U+e8e1 +#define ICON_MD_TRAFFIC "\xee\x95\xa5" // U+e565 +#define ICON_MD_TRAIN "\xee\x95\xb0" // U+e570 +#define ICON_MD_TRAM "\xee\x95\xb1" // U+e571 +#define ICON_MD_TRANSCRIBE "\xef\xa3\xac" // U+f8ec +#define ICON_MD_TRANSFER_WITHIN_A_STATION "\xee\x95\xb2" // U+e572 +#define ICON_MD_TRANSFORM "\xee\x90\xa8" // U+e428 +#define ICON_MD_TRANSGENDER "\xee\x96\x8d" // U+e58d +#define ICON_MD_TRANSIT_ENTEREXIT "\xee\x95\xb9" // U+e579 +#define ICON_MD_TRANSLATE "\xee\xa3\xa2" // U+e8e2 +#define ICON_MD_TRAVEL_EXPLORE "\xee\x8b\x9b" // U+e2db +#define ICON_MD_TRENDING_DOWN "\xee\xa3\xa3" // U+e8e3 +#define ICON_MD_TRENDING_FLAT "\xee\xa3\xa4" // U+e8e4 +#define ICON_MD_TRENDING_NEUTRAL "\xee\xa3\xa4" // U+e8e4 +#define ICON_MD_TRENDING_UP "\xee\xa3\xa5" // U+e8e5 +#define ICON_MD_TRIP_ORIGIN "\xee\x95\xbb" // U+e57b +#define ICON_MD_TROLLEY "\xef\xa1\xab" // U+f86b +#define ICON_MD_TROUBLESHOOT "\xee\x87\x92" // U+e1d2 +#define ICON_MD_TRY "\xef\x81\xbc" // U+f07c +#define ICON_MD_TSUNAMI "\xee\xaf\x98" // U+ebd8 +#define ICON_MD_TTY "\xef\x86\xaa" // U+f1aa +#define ICON_MD_TUNE "\xee\x90\xa9" // U+e429 +#define ICON_MD_TUNGSTEN "\xef\x81\xbd" // U+f07d +#define ICON_MD_TURN_LEFT "\xee\xae\xa6" // U+eba6 +#define ICON_MD_TURN_RIGHT "\xee\xae\xab" // U+ebab +#define ICON_MD_TURN_SHARP_LEFT "\xee\xae\xa7" // U+eba7 +#define ICON_MD_TURN_SHARP_RIGHT "\xee\xae\xaa" // U+ebaa +#define ICON_MD_TURN_SLIGHT_LEFT "\xee\xae\xa4" // U+eba4 +#define ICON_MD_TURN_SLIGHT_RIGHT "\xee\xae\x9a" // U+eb9a +#define ICON_MD_TURNED_IN "\xee\xa3\xa6" // U+e8e6 +#define ICON_MD_TURNED_IN_NOT "\xee\xa3\xa7" // U+e8e7 +#define ICON_MD_TV "\xee\x8c\xb3" // U+e333 +#define ICON_MD_TV_OFF "\xee\x99\x87" // U+e647 +#define ICON_MD_TWO_WHEELER "\xee\xa7\xb9" // U+e9f9 +#define ICON_MD_TYPE_SPECIMEN "\xef\xa3\xb0" // U+f8f0 +#define ICON_MD_U_TURN_LEFT "\xee\xae\xa1" // U+eba1 +#define ICON_MD_U_TURN_RIGHT "\xee\xae\xa2" // U+eba2 +#define ICON_MD_UMBRELLA "\xef\x86\xad" // U+f1ad +#define ICON_MD_UNARCHIVE "\xee\x85\xa9" // U+e169 +#define ICON_MD_UNDO "\xee\x85\xa6" // U+e166 +#define ICON_MD_UNFOLD_LESS "\xee\x97\x96" // U+e5d6 +#define ICON_MD_UNFOLD_LESS_DOUBLE "\xef\xa3\x8f" // U+f8cf +#define ICON_MD_UNFOLD_MORE "\xee\x97\x97" // U+e5d7 +#define ICON_MD_UNFOLD_MORE_DOUBLE "\xef\xa3\x90" // U+f8d0 +#define ICON_MD_UNPUBLISHED "\xef\x88\xb6" // U+f236 +#define ICON_MD_UNSUBSCRIBE "\xee\x83\xab" // U+e0eb +#define ICON_MD_UPCOMING "\xef\x81\xbe" // U+f07e +#define ICON_MD_UPDATE "\xee\xa4\xa3" // U+e923 +#define ICON_MD_UPDATE_DISABLED "\xee\x81\xb5" // U+e075 +#define ICON_MD_UPGRADE "\xef\x83\xbb" // U+f0fb +#define ICON_MD_UPLOAD "\xef\x82\x9b" // U+f09b +#define ICON_MD_UPLOAD_FILE "\xee\xa7\xbc" // U+e9fc +#define ICON_MD_USB "\xee\x87\xa0" // U+e1e0 +#define ICON_MD_USB_OFF "\xee\x93\xba" // U+e4fa +#define ICON_MD_VACCINES "\xee\x84\xb8" // U+e138 +#define ICON_MD_VAPE_FREE "\xee\xaf\x86" // U+ebc6 +#define ICON_MD_VAPING_ROOMS "\xee\xaf\x8f" // U+ebcf +#define ICON_MD_VERIFIED "\xee\xbd\xb6" // U+ef76 +#define ICON_MD_VERIFIED_USER "\xee\xa3\xa8" // U+e8e8 +#define ICON_MD_VERTICAL_ALIGN_BOTTOM "\xee\x89\x98" // U+e258 +#define ICON_MD_VERTICAL_ALIGN_CENTER "\xee\x89\x99" // U+e259 +#define ICON_MD_VERTICAL_ALIGN_TOP "\xee\x89\x9a" // U+e25a +#define ICON_MD_VERTICAL_DISTRIBUTE "\xee\x81\xb6" // U+e076 +#define ICON_MD_VERTICAL_SHADES "\xee\xb0\x8e" // U+ec0e +#define ICON_MD_VERTICAL_SHADES_CLOSED "\xee\xb0\x8d" // U+ec0d +#define ICON_MD_VERTICAL_SPLIT "\xee\xa5\x89" // U+e949 +#define ICON_MD_VIBRATION "\xee\x98\xad" // U+e62d +#define ICON_MD_VIDEO_CALL "\xee\x81\xb0" // U+e070 +#define ICON_MD_VIDEO_CAMERA_BACK "\xef\x81\xbf" // U+f07f +#define ICON_MD_VIDEO_CAMERA_FRONT "\xef\x82\x80" // U+f080 +#define ICON_MD_VIDEO_CHAT "\xef\xa2\xa0" // U+f8a0 +#define ICON_MD_VIDEO_COLLECTION "\xee\x81\x8a" // U+e04a +#define ICON_MD_VIDEO_FILE "\xee\xae\x87" // U+eb87 +#define ICON_MD_VIDEO_LABEL "\xee\x81\xb1" // U+e071 +#define ICON_MD_VIDEO_LIBRARY "\xee\x81\x8a" // U+e04a +#define ICON_MD_VIDEO_SETTINGS "\xee\xa9\xb5" // U+ea75 +#define ICON_MD_VIDEO_STABLE "\xef\x82\x81" // U+f081 +#define ICON_MD_VIDEOCAM "\xee\x81\x8b" // U+e04b +#define ICON_MD_VIDEOCAM_OFF "\xee\x81\x8c" // U+e04c +#define ICON_MD_VIDEOGAME_ASSET "\xee\x8c\xb8" // U+e338 +#define ICON_MD_VIDEOGAME_ASSET_OFF "\xee\x94\x80" // U+e500 +#define ICON_MD_VIEW_AGENDA "\xee\xa3\xa9" // U+e8e9 +#define ICON_MD_VIEW_ARRAY "\xee\xa3\xaa" // U+e8ea +#define ICON_MD_VIEW_CAROUSEL "\xee\xa3\xab" // U+e8eb +#define ICON_MD_VIEW_COLUMN "\xee\xa3\xac" // U+e8ec +#define ICON_MD_VIEW_COMFORTABLE "\xee\x90\xaa" // U+e42a +#define ICON_MD_VIEW_COMFY "\xee\x90\xaa" // U+e42a +#define ICON_MD_VIEW_COMFY_ALT "\xee\xad\xb3" // U+eb73 +#define ICON_MD_VIEW_COMPACT "\xee\x90\xab" // U+e42b +#define ICON_MD_VIEW_COMPACT_ALT "\xee\xad\xb4" // U+eb74 +#define ICON_MD_VIEW_COZY "\xee\xad\xb5" // U+eb75 +#define ICON_MD_VIEW_DAY "\xee\xa3\xad" // U+e8ed +#define ICON_MD_VIEW_HEADLINE "\xee\xa3\xae" // U+e8ee +#define ICON_MD_VIEW_IN_AR "\xee\xa7\xbe" // U+e9fe +#define ICON_MD_VIEW_KANBAN "\xee\xad\xbf" // U+eb7f +#define ICON_MD_VIEW_LIST "\xee\xa3\xaf" // U+e8ef +#define ICON_MD_VIEW_MODULE "\xee\xa3\xb0" // U+e8f0 +#define ICON_MD_VIEW_QUILT "\xee\xa3\xb1" // U+e8f1 +#define ICON_MD_VIEW_SIDEBAR "\xef\x84\x94" // U+f114 +#define ICON_MD_VIEW_STREAM "\xee\xa3\xb2" // U+e8f2 +#define ICON_MD_VIEW_TIMELINE "\xee\xae\x85" // U+eb85 +#define ICON_MD_VIEW_WEEK "\xee\xa3\xb3" // U+e8f3 +#define ICON_MD_VIGNETTE "\xee\x90\xb5" // U+e435 +#define ICON_MD_VILLA "\xee\x96\x86" // U+e586 +#define ICON_MD_VISIBILITY "\xee\xa3\xb4" // U+e8f4 +#define ICON_MD_VISIBILITY_OFF "\xee\xa3\xb5" // U+e8f5 +#define ICON_MD_VOICE_CHAT "\xee\x98\xae" // U+e62e +#define ICON_MD_VOICE_OVER_OFF "\xee\xa5\x8a" // U+e94a +#define ICON_MD_VOICEMAIL "\xee\x83\x99" // U+e0d9 +#define ICON_MD_VOLCANO "\xee\xaf\x9a" // U+ebda +#define ICON_MD_VOLUME_DOWN "\xee\x81\x8d" // U+e04d +#define ICON_MD_VOLUME_DOWN_ALT "\xee\x9e\x9c" // U+e79c +#define ICON_MD_VOLUME_MUTE "\xee\x81\x8e" // U+e04e +#define ICON_MD_VOLUME_OFF "\xee\x81\x8f" // U+e04f +#define ICON_MD_VOLUME_UP "\xee\x81\x90" // U+e050 +#define ICON_MD_VOLUNTEER_ACTIVISM "\xee\xa9\xb0" // U+ea70 +#define ICON_MD_VPN_KEY "\xee\x83\x9a" // U+e0da +#define ICON_MD_VPN_KEY_OFF "\xee\xad\xba" // U+eb7a +#define ICON_MD_VPN_LOCK "\xee\x98\xaf" // U+e62f +#define ICON_MD_VRPANO "\xef\x82\x82" // U+f082 +#define ICON_MD_WALLET "\xef\xa3\xbf" // U+f8ff +#define ICON_MD_WALLET_GIFTCARD "\xee\xa3\xb6" // U+e8f6 +#define ICON_MD_WALLET_MEMBERSHIP "\xee\xa3\xb7" // U+e8f7 +#define ICON_MD_WALLET_TRAVEL "\xee\xa3\xb8" // U+e8f8 +#define ICON_MD_WALLPAPER "\xee\x86\xbc" // U+e1bc +#define ICON_MD_WAREHOUSE "\xee\xae\xb8" // U+ebb8 +#define ICON_MD_WARNING "\xee\x80\x82" // U+e002 +#define ICON_MD_WARNING_AMBER "\xef\x82\x83" // U+f083 +#define ICON_MD_WASH "\xef\x86\xb1" // U+f1b1 +#define ICON_MD_WATCH "\xee\x8c\xb4" // U+e334 +#define ICON_MD_WATCH_LATER "\xee\xa4\xa4" // U+e924 +#define ICON_MD_WATCH_OFF "\xee\xab\xa3" // U+eae3 +#define ICON_MD_WATER "\xef\x82\x84" // U+f084 +#define ICON_MD_WATER_DAMAGE "\xef\x88\x83" // U+f203 +#define ICON_MD_WATER_DROP "\xee\x9e\x98" // U+e798 +#define ICON_MD_WATERFALL_CHART "\xee\xa8\x80" // U+ea00 +#define ICON_MD_WAVES "\xee\x85\xb6" // U+e176 +#define ICON_MD_WAVING_HAND "\xee\x9d\xa6" // U+e766 +#define ICON_MD_WB_AUTO "\xee\x90\xac" // U+e42c +#define ICON_MD_WB_CLOUDY "\xee\x90\xad" // U+e42d +#define ICON_MD_WB_INCANDESCENT "\xee\x90\xae" // U+e42e +#define ICON_MD_WB_IRIDESCENT "\xee\x90\xb6" // U+e436 +#define ICON_MD_WB_SHADE "\xee\xa8\x81" // U+ea01 +#define ICON_MD_WB_SUNNY "\xee\x90\xb0" // U+e430 +#define ICON_MD_WB_TWIGHLIGHT "\xee\xa8\x82" // U+ea02 +#define ICON_MD_WB_TWILIGHT "\xee\x87\x86" // U+e1c6 +#define ICON_MD_WC "\xee\x98\xbd" // U+e63d +#define ICON_MD_WEB "\xee\x81\x91" // U+e051 +#define ICON_MD_WEB_ASSET "\xee\x81\xa9" // U+e069 +#define ICON_MD_WEB_ASSET_OFF "\xee\x93\xb7" // U+e4f7 +#define ICON_MD_WEB_STORIES "\xee\x96\x95" // U+e595 +#define ICON_MD_WEBHOOK "\xee\xae\x92" // U+eb92 +#define ICON_MD_WECHAT "\xee\xaa\x81" // U+ea81 +#define ICON_MD_WEEKEND "\xee\x85\xab" // U+e16b +#define ICON_MD_WEST "\xef\x87\xa6" // U+f1e6 +#define ICON_MD_WHATSAPP "\xee\xaa\x9c" // U+ea9c +#define ICON_MD_WHATSHOT "\xee\xa0\x8e" // U+e80e +#define ICON_MD_WHEELCHAIR_PICKUP "\xef\x86\xab" // U+f1ab +#define ICON_MD_WHERE_TO_VOTE "\xee\x85\xb7" // U+e177 +#define ICON_MD_WIDGETS "\xee\x86\xbd" // U+e1bd +#define ICON_MD_WIDTH_FULL "\xef\xa3\xb5" // U+f8f5 +#define ICON_MD_WIDTH_NORMAL "\xef\xa3\xb6" // U+f8f6 +#define ICON_MD_WIDTH_WIDE "\xef\xa3\xb7" // U+f8f7 +#define ICON_MD_WIFI "\xee\x98\xbe" // U+e63e +#define ICON_MD_WIFI_1_BAR "\xee\x93\x8a" // U+e4ca +#define ICON_MD_WIFI_2_BAR "\xee\x93\x99" // U+e4d9 +#define ICON_MD_WIFI_CALLING "\xee\xbd\xb7" // U+ef77 +#define ICON_MD_WIFI_CALLING_3 "\xef\x82\x85" // U+f085 +#define ICON_MD_WIFI_CHANNEL "\xee\xad\xaa" // U+eb6a +#define ICON_MD_WIFI_FIND "\xee\xac\xb1" // U+eb31 +#define ICON_MD_WIFI_LOCK "\xee\x87\xa1" // U+e1e1 +#define ICON_MD_WIFI_OFF "\xee\x99\x88" // U+e648 +#define ICON_MD_WIFI_PASSWORD "\xee\xad\xab" // U+eb6b +#define ICON_MD_WIFI_PROTECTED_SETUP "\xef\x83\xbc" // U+f0fc +#define ICON_MD_WIFI_TETHERING "\xee\x87\xa2" // U+e1e2 +#define ICON_MD_WIFI_TETHERING_ERROR "\xee\xab\x99" // U+ead9 +#define ICON_MD_WIFI_TETHERING_ERROR_ROUNDED "\xef\x82\x86" // U+f086 +#define ICON_MD_WIFI_TETHERING_OFF "\xef\x82\x87" // U+f087 +#define ICON_MD_WIND_POWER "\xee\xb0\x8c" // U+ec0c +#define ICON_MD_WINDOW "\xef\x82\x88" // U+f088 +#define ICON_MD_WINE_BAR "\xef\x87\xa8" // U+f1e8 +#define ICON_MD_WOMAN "\xee\x84\xbe" // U+e13e +#define ICON_MD_WOMAN_2 "\xef\xa3\xa7" // U+f8e7 +#define ICON_MD_WOO_COMMERCE "\xee\xa9\xad" // U+ea6d +#define ICON_MD_WORDPRESS "\xee\xaa\x9f" // U+ea9f +#define ICON_MD_WORK "\xee\xa3\xb9" // U+e8f9 +#define ICON_MD_WORK_HISTORY "\xee\xb0\x89" // U+ec09 +#define ICON_MD_WORK_OFF "\xee\xa5\x82" // U+e942 +#define ICON_MD_WORK_OUTLINE "\xee\xa5\x83" // U+e943 +#define ICON_MD_WORKSPACE_PREMIUM "\xee\x9e\xaf" // U+e7af +#define ICON_MD_WORKSPACES "\xee\x86\xa0" // U+e1a0 +#define ICON_MD_WORKSPACES_FILLED "\xee\xa8\x8d" // U+ea0d +#define ICON_MD_WORKSPACES_OUTLINE "\xee\xa8\x8f" // U+ea0f +#define ICON_MD_WRAP_TEXT "\xee\x89\x9b" // U+e25b +#define ICON_MD_WRONG_LOCATION "\xee\xbd\xb8" // U+ef78 +#define ICON_MD_WYSIWYG "\xef\x87\x83" // U+f1c3 +#define ICON_MD_YARD "\xef\x82\x89" // U+f089 +#define ICON_MD_YOUTUBE_SEARCHED_FOR "\xee\xa3\xba" // U+e8fa +#define ICON_MD_ZOOM_IN "\xee\xa3\xbf" // U+e8ff +#define ICON_MD_ZOOM_IN_MAP "\xee\xac\xad" // U+eb2d +#define ICON_MD_ZOOM_OUT "\xee\xa4\x80" // U+e900 +#define ICON_MD_ZOOM_OUT_MAP "\xee\x95\xab" // U+e56b \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp new file mode 100644 index 00000000..077c7025 --- /dev/null +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -0,0 +1,672 @@ +//#==============================================================# +//|| PCH Include || +//#==============================================================# +#include "SHpch.h" + +#include "IconsMaterialDesign.h" +#include "IconsFontAwesome6.h" +#include "DragDrop/SHDragDrop.hpp" + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Tools/SHLogger.h" +#include "Tools/SHException.h" + +#include "ECS_Base/Managers/SHSystemManager.h" + +#include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" + +#include "SHEditor.h" +#include "SHEditorWidgets.hpp" + +#include "Math/Transform/SHTransformSystem.h" + +//#==============================================================# +//|| Editor Window Includes || +//#==============================================================# +#include "EditorWindow/SHEditorWindowManager.h" +#include "EditorWindow/SHEditorWindowIncludes.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include +#include +#include + +//#==============================================================# +//|| ImGui Backend Includes || +//#==============================================================# +#include +#include + +#include "Assets/SHAssetManager.h" +#include "Assets/Asset Types/SHSceneAsset.h" +#include "Graphics/MiddleEnd/Interface/SHMousePickSystem.h" +#include "Scene/SHSceneManager.h" +#include "Serialization/SHSerialization.h" +#include "Tools/SHDebugDraw.h" + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + registration::enumeration("Style") + ( + value("SHADE", SHEditor::Style::SHADE), + value("DARK", SHEditor::Style::DARK), + value("LIGHT", SHEditor::Style::LIGHT), + value("CLASSIC", SHEditor::Style::CLASSIC) + ); +} + +const std::string USER_LAYOUT_PATH{ std::string(ASSET_ROOT) + "/Editor/Layouts/UserLayout.ini" }; +const std::string DEFAULT_LAYOUT_PATH{ std::string(ASSET_ROOT) + "/Editor/Layouts/Default.ini" }; +const std::string FONT_FOLDER_PATH{ std::string(ASSET_ROOT) + "/Editor/Fonts/"}; + + +namespace SHADE +{ + //#==============================================================# + //|| Init static members || + //#==============================================================# + //Handle SHEditor::imguiCommandPool; + //Handle SHEditor::imguiCommandBuffer; + //std::vector SHEditor::selectedEntities; + + //#==============================================================# + //|| Public Member Functions || + //#==============================================================# + void SHEditor::Init() + { + IMGUI_CHECKVERSION(); + if(auto context = ImGui::CreateContext()) + { + if(context == nullptr) + { + SHLOG_CRITICAL("Failed to create ImGui Context") + } + } + + //Add editor windows + SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); + + SHEditorWindowManager::CreateEditorWindow(); + + io = &ImGui::GetIO(); + + io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io->ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; //Enable for Multi-Viewports + io->ConfigFlags |= ImGuiConfigFlags_DockingEnable; //Enable docking + io->IniFilename = USER_LAYOUT_PATH.data(); + io->ConfigWindowsMoveFromTitleBarOnly = true; + InitLayout(); + + InitFonts(); + + auto id = SHFamilyID::GetID(); + auto id2 = SHFamilyID::GetID(); + auto id3 = SHFamilyID::GetID(); + + InitBackend(); + + SetStyle(Style::SHADE); + + + for (const auto& window : SHEditorWindowManager::editorWindows | std::views::values) + { + window->Init(); + } + + /* Editor View Gridlines */ + SetUpGridLines(true, true); + // Handle state changes so that we only show in edit mode + std::shared_ptr> stateChangeEventReceiver + { + std::make_shared>(this, &SHEditor::onEditorStateChanged) + }; + SHEventManager::SubscribeTo(SH_EDITOR_ON_PLAY_EVENT, std::dynamic_pointer_cast(stateChangeEventReceiver)); + SHEventManager::SubscribeTo(SH_EDITOR_ON_PAUSE_EVENT, std::dynamic_pointer_cast(stateChangeEventReceiver)); + SHEventManager::SubscribeTo(SH_EDITOR_ON_STOP_EVENT, std::dynamic_pointer_cast(stateChangeEventReceiver)); + + SHLOG_INFO("Successfully initialised SHADE Engine Editor") + } + + void SHEditor::Update(double const dt) + { + (void)dt; + NewFrame(); + for (const auto& window : SHEditorWindowManager::editorWindows | std::views::values) + { + if(window->isOpen) + { + window->Update(); + } + } + + RenderSceneNamePrompt(); + RenderUnsavedChangesPrompt(); + //PollPicking(); + + if(ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyReleased(ImGuiKey_Z)) + { + SHCommandManager::RedoCommand(); + } + else if(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyReleased(ImGuiKey_Z)) + { + SHCommandManager::UndoCommand(); + } + if(ImGui::IsKeyReleased(ImGuiKey_F5)) + { + Play(); + } + else if (ImGui::IsKeyReleased(ImGuiKey_F6)) + { + Pause(); + } + else if (ImGui::IsKeyReleased(ImGuiKey_F7)) + { + Stop(); + } + + Render(); + } + + void SHEditor::Render() + { + ImGui::Render(); + if (io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } + } + + void SHEditor::RenderSceneNamePrompt() noexcept + { + if(isSceneNamePromptOpen) + { + ImGui::OpenPopup(sceneNamePromptName.data()); + } + + if(ImGui::BeginPopupModal(sceneNamePromptName.data(), &isSceneNamePromptOpen)) + { + static std::string newSceneName{}; + ImGui::Text("Enter new scene name"); + ImGui::InputText("##name", &newSceneName); + ImGui::BeginDisabled(newSceneName.empty()); + if(ImGui::Button("Save")) + { + SaveScene(newSceneName); + newSceneName.clear(); + isSceneNamePromptOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndDisabled(); + ImGui::SameLine(); + if(ImGui::Button("Cancel")) + { + isSceneNamePromptOpen = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + + void SHEditor::RenderUnsavedChangesPrompt() noexcept + { + if(isUnsavedChangesPromptOpen) + { + ImGui::OpenPopup(unsavedChangesPromptName.data()); + } + + if(ImGui::BeginPopupModal(unsavedChangesPromptName.data(), &isUnsavedChangesPromptOpen)) + { + ImGui::Text("You have unsaved changes!"); + if(ImGui::Button("Save")) + { + isSceneNamePromptOpen = true; + } + ImGui::SameLine(); + if(ImGui::Button("Cancel")) + { + isUnsavedChangesPromptOpen = false; + ImGui::CloseCurrentPopup(); + } + } + } + + void SHEditor::InitLayout() noexcept + { + if(!std::filesystem::exists(io->IniFilename)) + { + std::filesystem::copy_file(DEFAULT_LAYOUT_PATH.data(), io->IniFilename); + } + //eventually load preferred layout here + } + + void SHEditor::InitFonts() noexcept + { + ImFont* mainFont = io->Fonts->AddFontFromFileTTF(std::string(FONT_FOLDER_PATH + "Segoe UI.ttf").data(), 20.f);//TODO: Change to config based assets path + + ImFontConfig icons_config{}; icons_config.MergeMode = true; icons_config.GlyphOffset.y = 5.f; + constexpr ImWchar icon_ranges_fa[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + ImFont* UIFontFA = io->Fonts->AddFontFromFileTTF(std::string(FONT_FOLDER_PATH + "fa-solid-900.ttf").data(), 20.f, &icons_config, icon_ranges_fa); //TODO: Change to config based assets path + constexpr ImWchar icon_ranges_md[] = { ICON_MIN_MD, ICON_MAX_16_MD, 0 }; + ImFont* UIFontMD = io->Fonts->AddFontFromFileTTF(std::string(FONT_FOLDER_PATH + "MaterialIcons-Regular.ttf").data(), 20.f, &icons_config, icon_ranges_md); //TODO: Change to config based assets path + io->Fonts->Build(); + } + + void SHEditor::SetUpGridLines(bool drawGrid, bool drawAxes) + { + // Clear existing lines + SHDebugDraw::ClearPersistentDraws(); + + static constexpr float DELTA = 1.0f; + static constexpr int EXTENT_COUNT = static_cast(500.0f /* TODO: Remove hard code */ / DELTA); + static constexpr float LINE_HALF_LENGTH = (DELTA * static_cast(EXTENT_COUNT)) * 0.5f; + + // Render Grid + static const SHColour GRID_COL = { 0.2f, 0.2f, 0.2f, 1.0f }; + if (drawGrid) + { + for (int i = 1; i < EXTENT_COUNT; ++i) + { + // X-Axis Lines + SHDebugDraw::PersistentLine + ( + GRID_COL, + SHVec3 { -LINE_HALF_LENGTH, 0.0f, i * DELTA }, + SHVec3 { LINE_HALF_LENGTH, 0.0f, i * DELTA } + ); + SHDebugDraw::PersistentLine + ( + GRID_COL, + SHVec3 { -LINE_HALF_LENGTH, 0.0f, i * -DELTA }, + SHVec3 { LINE_HALF_LENGTH, 0.0f, i * -DELTA } + ); + // Y-Axis Lines + SHDebugDraw::PersistentLine + ( + GRID_COL, + SHVec3 { i * DELTA, 0.0f, -LINE_HALF_LENGTH }, + SHVec3 { i * DELTA, 0.0f, LINE_HALF_LENGTH } + ); + SHDebugDraw::PersistentLine + ( + GRID_COL, + SHVec3 { -i * DELTA, 0.0f, -LINE_HALF_LENGTH }, + SHVec3 { -i * DELTA, 0.0f, LINE_HALF_LENGTH } + ); + } + } + + // Render World Axes + if (drawAxes || drawGrid) + { + const SHColour X_AXIS_COL = drawAxes ? SHColour::RED : GRID_COL; + const SHColour Y_AXIS_COL = drawAxes ? SHColour::GREEN : GRID_COL; + const SHColour Z_AXIS_COL = drawAxes ? SHColour::BLUE : GRID_COL; + // X + SHDebugDraw::PersistentLine + ( + X_AXIS_COL, + SHVec3 { -LINE_HALF_LENGTH, 0.0f, 0.0f }, + SHVec3 { LINE_HALF_LENGTH, 0.0f, 0.0f } + ); + // Y + SHDebugDraw::PersistentLine + ( + Y_AXIS_COL, + SHVec3 { 0.0f, -LINE_HALF_LENGTH, 0.0f }, + SHVec3 { 0.0f, LINE_HALF_LENGTH, 0.0f } + ); + // Z + SHDebugDraw::PersistentLine + ( + Z_AXIS_COL, + SHVec3 { 0.0f, 0.0f, -LINE_HALF_LENGTH }, + SHVec3 { 0.0f, 0.0f, LINE_HALF_LENGTH } + ); + } + } + + SHEventHandle SHEditor::onEditorStateChanged(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + switch (editorState) + { + case State::PAUSE: + case State::STOP: + SetUpGridLines(true, true); + break; + case State::PLAY: + default: + SHDebugDraw::ClearPersistentDraws(); + break; + } + return eventData->handle; + } + + void SHEditor::Exit() + { + for (const auto& window : SHEditorWindowManager::editorWindows | std::views::values) + { + window->Init(); + } + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + } + + void SHEditor::SetStyle(Style style) + { + switch (style) + { + default: + case Style::SHADE: + { + ImGuiStyle& imStyle = ImGui::GetStyle(); + ImVec4* colors = imStyle.Colors; + colors[ImGuiCol_Text] = ImVec4(0.706f, 0.729f, 0.757f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.172f, 0.184f, 0.203f, 1.f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.19f, 0.19f, 0.19f, 0.29f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.24f); + colors[ImGuiCol_FrameBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); + colors[ImGuiCol_TitleBg] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_TitleBgActive] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_TitleBgCollapsed] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_MenuBarBg] = ImVec4(0.129f, 0.141f, 0.157f, 1.f); + colors[ImGuiCol_ScrollbarBg] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.54f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f); + colors[ImGuiCol_CheckMark] = ImVec4(0.627f, 0.239f, 0.761f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f); + colors[ImGuiCol_Button] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.15f, 0.15f, 0.15f, 0.54f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.00f, 0.00f, 0.36f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.20f, 0.22f, 0.23f, 0.33f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_MenuBarBg]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f); + colors[ImGuiCol_Tab] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.14f, 0.14f, 0.14f, 0.8f); + colors[ImGuiCol_TabUnfocused] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_TabUnfocusedActive] = colors[ImGuiCol_WindowBg]; + colors[ImGuiCol_DockingPreview] = ImVec4(0.627f, 0.239f, 0.761f, 1.00f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.855f, 0.6f, 0.941f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.73f, 0.73f, 0.73f, 0.7f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.141f, 0.141f, 0.141f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = colors[ImGuiCol_NavHighlight]; + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.2f, 0.2f, 0.2f, 0.65f); + + imStyle.WindowPadding = ImVec2(8.00f, 8.00f); + imStyle.FramePadding = ImVec2(5.00f, 2.00f); + imStyle.CellPadding = ImVec2(6.00f, 8.00f); + imStyle.ItemSpacing = ImVec2(6.00f, 6.00f); + imStyle.ItemInnerSpacing = ImVec2(6.00f, 6.00f); + imStyle.TouchExtraPadding = ImVec2(0.00f, 0.00f); + imStyle.IndentSpacing = 25; + imStyle.ScrollbarSize = 15; + imStyle.GrabMinSize = 10; + imStyle.WindowBorderSize = 0.6f; + imStyle.ChildBorderSize = 1; + imStyle.PopupBorderSize = 1; + imStyle.FrameBorderSize = 1; + imStyle.TabBorderSize = 1; + imStyle.WindowRounding = 7; + imStyle.ChildRounding = 4; + imStyle.FrameRounding = 3; + imStyle.PopupRounding = 4; + imStyle.ScrollbarRounding = 9; + imStyle.GrabRounding = 3; + imStyle.LogSliderDeadzone = 4; + imStyle.TabRounding = 4; + imStyle.WindowMenuButtonPosition = ImGuiDir_None; + } + break; + case Style::DARK: ImGui::StyleColorsDark(); break; + case Style::LIGHT: ImGui::StyleColorsLight(); break; + case Style::CLASSIC: ImGui::StyleColorsClassic(); break; + } + + } + + //#==============================================================# + //|| Private Member Functions || + //#==============================================================# + void SHEditor::InitBackend() + { +#ifdef SHEDITOR + if(ImGui_ImplSDL2_InitForVulkan(sdlWindow) == false) + { + SHLOG_CRITICAL("Editor backend initialisation; Failed to perform SDL initialisation for Vulkan") + } + + const auto* gfxSystem = SHSystemManager::GetSystem(); + + ImGui_ImplVulkan_InitInfo initInfo{}; + initInfo.Instance = SHVkInstance::GetVkInstance(); + initInfo.PhysicalDevice = gfxSystem->GetPhysicalDevice()->GetVkPhysicalDevice(); + initInfo.Device = gfxSystem->GetDevice()->GetVkLogicalDevice(); + initInfo.Queue = gfxSystem->GetQueue()->GetVkQueue(); + initInfo.DescriptorPool = gfxSystem->GetDescriptorPool()->GetVkHandle(); + initInfo.MinImageCount = initInfo.ImageCount = gfxSystem->GetSwapchain()->GetNumImages(); + initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + + imguiCommandPool = gfxSystem->GetDevice()->CreateCommandPool(SH_QUEUE_FAMILY_ARRAY_INDEX::GRAPHICS, SH_CMD_POOL_RESET::POOL_BASED, true); + imguiCommandBuffer = imguiCommandPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + + //auto const& renderers = gfxSystem->GetDefaultViewport()->GetRenderers(); + auto const& renderers = gfxSystem->GetEditorViewport()->GetRenderers(); + + SHASSERT(!renderers.empty(), "No Renderers available") + auto renderGraph = renderers[SHGraphicsConstants::RenderGraphIndices::EDITOR]->GetRenderGraph(); + auto renderPass = renderGraph->GetNode("ImGui Node")->GetRenderpass(); + + if(ImGui_ImplVulkan_Init(&initInfo, renderPass->GetVkRenderpass()) == false) + { + SHLOG_CRITICAL("Editor backend initialisation; Failed to initialise Vulkan backend") + } + + imguiCommandBuffer->BeginRecording(); + if(ImGui_ImplVulkan_CreateFontsTexture(imguiCommandBuffer->GetVkCommandBuffer()) == false) + { + SHLOG_CRITICAL("Editor backend initialisation; Failed to create fonts texture for Vulkan backend") + } + imguiCommandBuffer->EndRecording(); + gfxSystem->GetQueue()->SubmitCommandBuffer({ imguiCommandBuffer }, {}, {}, vk::PipelineStageFlagBits::eNone, {}); + + gfxSystem->GetDevice()->WaitIdle(); + + ImGui_ImplVulkan_DestroyFontUploadObjects(); + + renderGraph->GetNode("ImGui Node")->GetSubpass("ImGui Draw")->AddExteriorDrawCalls([](Handle& cmd) + { + cmd->BeginLabeledSegment("ImGui Draw"); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd->GetVkCommandBuffer()); + cmd->EndLabeledSegment(); + }); +#endif + } + + void SHEditor::PollPicking() + { + if (auto gfxSystem = SHSystemManager::GetSystem()) + { + auto viewportWindow = SHEditorWindowManager::GetEditorWindow(); + if (viewportWindow->isWindowHovered && !viewportWindow->transformGizmo.isManipulating && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + EntityID pickedEID = gfxSystem->GetMousePickSystem()->GetPickedEntity(); + if(pickedEID == MAX_EID) + return; + if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) + { + if (const auto hierarchyPanel = SHEditorWindowManager::GetEditorWindow()) + { + hierarchyPanel->SetScrollTo(pickedEID); + } + selectedEntities.clear(); + } + selectedEntities.push_back(pickedEID); + } + } + } + + void SHEditor::NewScene() + { + if(shWindow->IsUnsavedChanges()) + { + //Unsaved changes prompt + sceneToLoad = 0; + isUnsavedChangesPromptOpen = true; + } + else + { + SHSceneManager::RestartScene(0); + shWindow->ToggleUnsavedChanges(); + } + } + + bool SHEditor::SaveScene(std::string const& newSceneName) + { + auto const data = SHAssetManager::GetData(SHSceneManager::GetCurrentSceneAssetID()); + if (!data) + { + if (newSceneName.empty()) + { + //Prompt for scene name + isSceneNamePromptOpen = true; + return false; + } + //Else We have a new name + + SHSceneManager::SetCurrentSceneName(newSceneName); + SHSceneManager::SetCurrentSceneAssetID(SHAssetManager::CreateNewAsset(AssetType::SCENE, newSceneName)); + } + //Get data, if data is null, asset doesn't exist, prompt for a name and create a new asset with the name + + //serialize the scene + if(SHSerialization::SerializeSceneToFile(SHSceneManager::GetCurrentSceneAssetID())) + { + if(shWindow->IsUnsavedChanges()) + shWindow->ToggleUnsavedChanges(); + + return true; + } + return false; + } + + void SHEditor::LoadScene(AssetID const& assetID) noexcept + { + if(shWindow->IsUnsavedChanges()) + { + //Unsaved changes prompt + isUnsavedChangesPromptOpen = true; + sceneToLoad = assetID; + } + else + { + //Load the scene + sceneToLoad = 0; + SHSceneManager::RestartScene(assetID); + } + } + + void SHEditor::Play() + { + if(editorState == State::PLAY) + return; + if (SaveScene()) + { + const SHEditorStateChangeEvent STATE_CHANGE_EVENT + { + .previousState = editorState + }; + editorState = State::PLAY; + SHCommandManager::SwapStacks(); + SHEventManager::BroadcastEvent(STATE_CHANGE_EVENT, SH_EDITOR_ON_PLAY_EVENT); + } + } + + void SHEditor::Pause() + { + if (editorState == State::PAUSE) + return; + const SHEditorStateChangeEvent STATE_CHANGE_EVENT + { + .previousState = editorState + }; + editorState = State::PAUSE; + SHEventManager::BroadcastEvent(STATE_CHANGE_EVENT, SH_EDITOR_ON_PAUSE_EVENT); + } + + void SHEditor::Stop() + { + if (editorState == State::STOP) + return; + const SHEditorStateChangeEvent STATE_CHANGE_EVENT + { + .previousState = editorState + }; + editorState = SHEditor::State::STOP; + SHCommandManager::SwapStacks(); + SHEventManager::BroadcastEvent(STATE_CHANGE_EVENT, SH_EDITOR_ON_STOP_EVENT); + LoadScene(SHSceneManager::GetCurrentSceneAssetID()); + } + + void SHEditor::NewFrame() + { + SDL_Event event; + while (SDL_PollEvent(&event) != 0) + { + ImGui_ImplSDL2_ProcessEvent(&event); + } + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + ImGuizmo::BeginFrame(); + } + + + void SHEditor::EditorRoutine::Execute(double dt) noexcept + { + reinterpret_cast(system)->Update(dt); + } + +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/SHEditor.h b/SHADE_Engine/src/Editor/SHEditor.h new file mode 100644 index 00000000..5897c8b7 --- /dev/null +++ b/SHADE_Engine/src/Editor/SHEditor.h @@ -0,0 +1,175 @@ +#pragma once + +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "SH_API.h" +#include "ECS_Base/SHECSMacros.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Resource/SHHandle.h" +#include "EditorWindow/SHEditorWindow.h" +#include "Tools/SHLog.h" +#include "Gizmos/SHTransformGizmo.h" +#include "Events/SHEventDefines.h" +#include "Events/SHEvent.h" +#include "Graphics/Windowing/SHWindow.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include + +#include "Assets/SHAssetMacros.h" + +namespace SHADE +{ + //#==============================================================# + //|| Forward Declarations || + //#==============================================================# + class SHVkCommandBuffer; + class SHVkCommandPool; + + + + /** + * @brief SHEditor static class contains editor variables and implementation of editor functions. + * + */ + class SH_API SHEditor final : public SHSystem + { + public: + + + class SH_API EditorRoutine final : public SHSystemRoutine + { + public: + EditorRoutine():SHSystemRoutine("Editor routine", true) {}; + void Execute(double dt) noexcept override final; + }; + + enum class State : uint8_t + { + PLAY, + PAUSE, + STOP + }; + + /** + * @brief Style options + * + */ + enum class Style : uint8_t + { + SHADE, + DARK, + LIGHT, + CLASSIC + }; + + /** + * @brief Initialise the editor + * + * @param sdlWindow pointer to SDL_Window object created in application + */ + void Init(); + + /** + * @brief Update the editor and add to ImGui DrawList + * + * @param dt Delta-time of the frame + */ + void Update(double dt); + + /** + * @brief Safely shutdown the editor + * + */ + void Exit(); + + /** + * @brief Set the Style for the editor + * + * @param style Desired style + */ + void SetStyle(Style style); + + /** + * @brief Initialise Backend for ImGui (SDL and Vulkan backend) + * + * @param sdlWindow Pointer to SDL_Window + */ + void InitBackend(); + + void SetSDLWindow(SDL_Window* inSDLWindow){sdlWindow = inSDLWindow;}; + void SetSHWindow(SHWindow* inWindow){shWindow = inWindow;} + + void PollPicking(); + + void NewScene(); + + bool SaveScene(std::string const& newSceneName = {}); + + void LoadScene(AssetID const& assetID) noexcept; + + void Play(); + void Pause(); + void Stop(); + + // List of selected entities + std::vector selectedEntities; + + State editorState = State::STOP; + + private: + /** + * @brief Start new frame for editor + * + */ + void NewFrame(); + /** + * @brief Perform ImGui and ImGui Backend Render + * + */ + void Render(); + + void RenderSceneNamePrompt() noexcept; + + void RenderUnsavedChangesPrompt() noexcept; + + void InitLayout() noexcept; + + void InitFonts() noexcept; + + void SetUpGridLines(bool drawGrid, bool drawAxes); + + SHEventHandle onEditorStateChanged(SHEventPtr eventPtr); + + bool isSceneNamePromptOpen = false; + + bool isUnsavedChangesPromptOpen = false; + + static constexpr std::string_view sceneNamePromptName = "Save scene as..."; + static constexpr std::string_view unsavedChangesPromptName = "Unsaved Changes"; + + AssetID sceneToLoad = 0; + + // Handle to command pool used for ImGui Vulkan Backend + Handle imguiCommandPool; + // Handle to command buffer used for ImGui Vulkan Backend + Handle imguiCommandBuffer; + + SDL_Window* sdlWindow {nullptr}; + SHWindow* shWindow {nullptr}; + + ImGuiIO* io{nullptr}; + + //SHTransformGizmo transformGizmo; + };//class SHEditor +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp new file mode 100644 index 00000000..40e08042 --- /dev/null +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -0,0 +1,352 @@ +/************************************************************************************//*! +\file EditorUI.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 7, 2021 +\brief Contains the implementation of the EditorUI class. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Header +#include "SHpch.h" +// Primary Header +#include "SHEditorUI.h" +// External Dependencies +#include +#include "SHEditorWidgets.hpp" +#include "ECS_Base/Managers/SHEntityManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - ID Stack */ + /*-----------------------------------------------------------------------------------*/ + void SHEditorUI::PushID(const std::string& id) + { + ImGui::PushID(id.c_str()); + } + + void SHEditorUI::PushID(int id) + { + ImGui::PushID(id); + } + + void SHEditorUI::PopID() + { + ImGui::PopID(); + } + + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Indent */ + /*-----------------------------------------------------------------------------------*/ + void SHEditorUI::Indent() + { + ImGui::Indent(); + } + void SHEditorUI::Unindent() + { + ImGui::Unindent(); + } + + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Organizers */ + /*-----------------------------------------------------------------------------------*/ + bool SHEditorUI::CollapsingHeader(const std::string& title, bool* isHovered) + { + const bool OPENED = ImGui::CollapsingHeader(title.c_str(), ImGuiTreeNodeFlags_DefaultOpen); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + return OPENED; + } + + void SHEditorUI::SameLine() + { + ImGui::SameLine(); + } + + void SHEditorUI::Separator() + { + ImGui::Separator(); + } + + bool SHEditorUI::IsItemHovered() + { + return ImGui::IsItemHovered(); + } + + bool SHEditorUI::BeginMenu(const std::string& label) + { + return ImGui::BeginMenu(label.data()); + } + + bool SHEditorUI::BeginMenu(const std::string& label, const char* icon) + { + return ImGui::BeginMenu(std::format("{} {}", icon, label.data()).data()); + } + + void SHEditorUI::EndMenu() + { + ImGui::EndMenu(); + } + + void SHEditorUI::BeginTooltip() + { + ImGui::BeginTooltip(); + } + + void SHEditorUI::EndTooltip() + { + ImGui::EndTooltip(); + } + + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Pop Ups */ + /*-----------------------------------------------------------------------------------*/ + bool SHEditorUI::BeginPopup(const std::string& label) + { + return ImGui::BeginPopup(label.c_str()); + } + + bool SHEditorUI::BeginPopupContextItem(const std::string& label) + { + return ImGui::BeginPopupContextItem(label.data()); + } + + void SHEditorUI::EndPopup() + { + ImGui::EndPopup(); + } + void SHEditorUI::OpenPopup(const std::string& label) + { + ImGui::OpenPopup(label.c_str()); + } + + bool SHEditorUI::MenuItem(const std::string& label) + { + return ImGui::MenuItem(label.c_str()); + } + + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Widgets */ + /*-----------------------------------------------------------------------------------*/ + void SHEditorUI::Text(const std::string& title) + { + ImGui::Text(title.c_str()); + } + bool SHEditorUI::SmallButton(const std::string& title) + { + return ImGui::SmallButton(title.c_str()); + } + bool SHEditorUI::Button(const std::string& title) + { + return ImGui::Button(title.c_str()); + } + + bool SHEditorUI::Selectable(const std::string& label) + { + return ImGui::Selectable(label.data()); + } + + bool SHEditorUI::Selectable(const std::string& label, const char* icon) + { + return ImGui::Selectable(std::format("{} {}", icon, label).data()); + } + + bool SHEditorUI::InputCheckbox(const std::string& label, bool& value, bool* isHovered) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + return ImGui::Checkbox("##", &value); + } + bool SHEditorUI::InputInt(const std::string& label, int& value, bool* isHovered) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + return ImGui::DragInt("##", &value, 0.001f, + std::numeric_limits::min(), + std::numeric_limits::max(), + "%d", + ImGuiInputTextFlags_EnterReturnsTrue); + } + bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered) + { + int signedVal = static_cast(value); + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + const bool CHANGED = InputInt("##", signedVal); + if (CHANGED) + { + signedVal = std::clamp(signedVal, 0, std::numeric_limits::max()); + value = static_cast(signedVal); + } + return CHANGED; + } + bool SHEditorUI::InputFloat(const std::string& label, float& value, bool* isHovered) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + return ImGui::DragFloat("##", &value, 0.001f, + std::numeric_limits::lowest(), + std::numeric_limits::max(), + "%.3f", + ImGuiInputTextFlags_EnterReturnsTrue); + } + bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered) + { + float val = value; + const bool CHANGED = InputFloat(label, val, isHovered); + if (CHANGED) + { + value = static_cast(val); + } + return CHANGED; + } + bool SHEditorUI::InputSlider(const std::string& label, int min, int max, int& value, bool* isHovered /*= nullptr*/) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + return ImGui::SliderInt("##", &value, + static_cast(min), static_cast(max), "%d", + ImGuiInputTextFlags_EnterReturnsTrue); + } + + bool SHEditorUI::InputSlider(const std::string& label, unsigned int min, unsigned int max, unsigned int& value, bool* isHovered /*= nullptr*/) + { + int val = static_cast(value); + const bool CHANGED = InputSlider(label, min, max, val, isHovered); + if (CHANGED) + { + value = static_cast(val); + } + + return CHANGED; + } + + bool SHEditorUI::InputSlider(const std::string& label, float min, float max, float& value, bool* isHovered) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + return ImGui::SliderFloat("##", &value, + static_cast(min), static_cast(max), "%.3f", + ImGuiInputTextFlags_EnterReturnsTrue); + } + + bool SHEditorUI::InputSlider(const std::string& label, double min, double max, double& value, bool* isHovered /*= nullptr*/) + { + float val = static_cast(value); + const bool CHANGED = InputSlider(label, min, max, val, isHovered); + if (CHANGED) + { + value = static_cast(val); + } + + return CHANGED; + } + + bool SHEditorUI::InputVec2(const std::string& label, SHVec2& value, bool* isHovered) + { + static const std::vector COMPONENT_LABELS = { "X", "Y" }; + return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered); + } + bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered) + { + static const std::vector COMPONENT_LABELS = { "X", "Y", "Z"}; + return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, 0.1f, "%.3f", float{}, float{}, 0, isHovered); + } + + bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered) + { + std::array buffer = { '\0' }; + strcpy_s(buffer.data(), TEXT_FIELD_MAX_LENGTH, value.c_str()); + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + const bool CHANGED = ImGui::InputText("##", &buffer[0], TEXT_FIELD_MAX_LENGTH); + if (CHANGED) + { + value = std::string(buffer.data(), buffer.data() + TEXT_FIELD_MAX_LENGTH); + } + return CHANGED; + } + + bool SHEditorUI::InputGameObjectField(const std::string& label, uint32_t& value, bool* isHovered, bool alwaysNull) + { + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + SHEntity* entity = SHEntityManager::GetEntityByID(value); + std::ostringstream oss; + if (!alwaysNull && entity) + { + oss << value << ": " << entity->name; + } + std::string entityName = oss.str(); + bool changed = ImGui::InputText("##", &entityName, ImGuiInputTextFlags_ReadOnly); + if (SHDragDrop::BeginTarget()) + { + if (const std::vector* payload = SHDragDrop::AcceptPayload>(SHDragDrop::DRAG_EID)) + { + if (!payload->empty()) + { + value = payload->at(0); + changed = true; + } + SHDragDrop::EndTarget(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Clear")) + { + value = MAX_EID; + changed = true; + } + + return changed; + } + + bool SHEditorUI::InputEnumCombo(const std::string& label, int& v, const std::vector& enumNames, bool* isHovered) + { + // Clamp input value + const std::string& INITIAL_NAME = v >= static_cast(enumNames.size()) ? "Unknown" : enumNames[v]; + bool b = false; + + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + if (ImGui::BeginCombo("##", INITIAL_NAME.c_str(), ImGuiComboFlags_None)) + { + for (int i = 0; i < enumNames.size(); ++i) + { + const bool IS_SELECTED = v == i; + if (ImGui::Selectable(enumNames[i].c_str(), IS_SELECTED)) + { + v = i; + b = true; + } + if (IS_SELECTED) + { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + return b; + } +} diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h new file mode 100644 index 00000000..f450ac0d --- /dev/null +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -0,0 +1,343 @@ +/************************************************************************************//*! +\file SHEditorUI.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\par email: t.yanchongclarence\@digipen.edu +\date Sep 27, 2022 +\brief Defines a class that contains wrapper functions for ImGui. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Standard Library +#include // std::function +#include // std::string +// Project Includes +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" +#include "Math/SHMatrix.h" + +namespace SHADE +{ + /// + /// Static class that contains useful functions for Editor UI using ImGui. + /// + class SH_API SHEditorUI final + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + /// + /// Maximum length of a string supported by InputTextField() + /// + static constexpr size_t TEXT_FIELD_MAX_LENGTH = 256; + + /*-----------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - ID Stack */ + /*-----------------------------------------------------------------------------*/ + /// + /// Marks the start of a stack of ImGui widgets with the specified id. + ///
+ /// Wraps up ImGui::PushID(). + ///
+ /// String-based ID. + static void PushID(const std::string& id); + /// + /// Marks the start of a stack of ImGui widgets with the specified id. + ///
+ /// Wraps up ImGui::PushID(). + ///
+ /// Integer-based ID. + static void PushID(int id); + /// + /// Marks the end of a stack of ImGui widgets from the last PushID() call. + ///
+ /// Wraps up ImGui::PopID(). + ///
+ static void PopID(); + + /*-----------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Indent */ + /*-----------------------------------------------------------------------------*/ + /// + /// Indents the widgets rendered after this call. + ///
+ /// Wraps up ImGui::Indent(). + ///
+ static void Indent(); + /// + /// Unindents the widgets rendered after this call. + ///
+ /// Wraps up ImGui::Unindent(). + ///
+ static void Unindent(); + + /*-----------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Organizers */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a collapsing title header. + ///
+ /// Wraps up ImGui::CollapsingHeader(). + ///
+ /// Label for the header. + /// Label used to identify this widget. + /// Whether or not the pop up is open. + static bool BeginPopup(const std::string& label); + static bool BeginPopupContextItem(const std::string& label); + /// + /// Marks the end of a definition of a mini pop up that can show options. + ///
+ /// Wraps up ImGui::EndPopup(). + ///
+ static void EndPopup(); + /// + /// Opens the popup that was defined with the specified label. + ///
+ /// Wraps up ImGui::OpenPopup(). + ///
+ static void OpenPopup(const std::string& label); + /// + /// Creates a menu item in the list of items for a mini popup. + ///
+ /// Wraps up ImGui::MenuItem(). + ///
+ /// Label used to identify this widget. + /// Whether or not the menu item was selected. + static bool MenuItem(const std::string& label); + + /*-----------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Widgets */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a visual text widget. + ///
+ /// Wraps up ImGui::Text(). + ///
+ /// Text to display. + static void Text(const std::string& title); + /// + /// Creates a small inline button widget. + ///
+ /// Wraps up ImGui::SmallButton(). + ///
+ /// Text to display. + /// True if button was pressed. + static bool SmallButton(const std::string& title); + /// + /// Creates a inline button widget. + ///
+ /// Wraps up ImGui::Button(). + ///
+ /// Text to display. + /// True if button was pressed. + static bool Button(const std::string& title); + static bool Selectable(const std::string& label); + static bool Selectable(const std::string& label, const char* icon); + /// + /// Creates a checkbox widget for boolean input. + ///
+ /// Wraps up ImGui::Checkbox(). + ///
+ /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Minimum value of the slider. + /// Maximum value of the slider. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Minimum value of the slider. + /// Maximum value of the slider. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Minimum value of the slider. + /// Maximum value of the slider. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Minimum value of the slider. + /// Maximum value of the slider. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// Label used to identify this widget. + /// Reference to the variable to store the result. + /// + /// If set, the field displayed will always be blank regardless of specified + /// GameObject. + /// + /// True if the value was changed. + static bool InputGameObjectField(const std::string& label, uint32_t& value, bool* isHovered = nullptr, bool alwaysNull = false); + /// + /// Creates a combo box for enumeration input. + /// + /// The type of enum to input. + /// The name of the input. + /// The reference to the value to modify. + /// The maximum value of the enum. + /// + /// Conversion function from the type of enum to C-style string. + /// + /// The name of the input. + /// The reference to the value to modify. + /// Vector of names for each enumeration value. + /// + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Widgets */ + /*-----------------------------------------------------------------------------------*/ + template + inline bool SHEditorUI::InputEnumCombo(const std::string& label, Enum& v, int maxVal, std::function toStrFn, bool* isHovered) + { + std::vector values; + for (int i = 0; i <= maxVal; ++i) + { + values.emplace_back(static_cast(i)); + } + bool b = false; + + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + if (ImGui::BeginCombo(label.c_str(), toStrFn(v), ImGuiComboFlags_None)) + { + for (int i = 0; i <= maxVal; ++i) + { + const auto VALUE = values[i]; + const bool IS_SELECTED = v == VALUE; + if (ImGui::Selectable(toStrFn(VALUE), IS_SELECTED)) + { + v = VALUE; + b = true; + } + if (IS_SELECTED) + { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + return b; + } +} diff --git a/SHADE_Engine/src/Editor/SHEditorWidgets.hpp b/SHADE_Engine/src/Editor/SHEditorWidgets.hpp new file mode 100644 index 00000000..0855d68d --- /dev/null +++ b/SHADE_Engine/src/Editor/SHEditorWidgets.hpp @@ -0,0 +1,716 @@ +#pragma once +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include +#include +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Math/SHMath.h" +#include "Command/SHCommandManager.h" +#include "SHImGuiHelpers.hpp" +#include "SH_API.h" + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include +#include +#include + +#include "DragDrop/SHDragDrop.hpp" + +namespace SHADE +{ + class SH_API SHEditorWidgets + { + public: + //#==============================================================# + //|| Constructor || + //#==============================================================# + SHEditorWidgets() = delete; + + //#==============================================================# + //|| Custom Widgets || + //#==============================================================# + inline static ImVector panelStack{}; + static void BeginPanel(std::string_view const& name, const ImVec2& size) + { + ImGui::BeginGroup(); + + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.2f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + ImGui::BeginGroup(); + + ImVec2 effectiveSize = size; + if (size.x < 0.0f) + effectiveSize.x = ImGui::GetContentRegionAvail().x; + else + effectiveSize.x = size.x; + ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f)); + + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::BeginGroup(); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::TextUnformatted(name.data()); + auto labelMin = ImGui::GetItemRectMin(); + auto labelMax = ImGui::GetItemRectMax(); + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y)); + ImGui::BeginGroup(); + + ImGui::PopStyleVar(2); + + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f; + + ImGui::GetCurrentWindow()->Size.x -= frameHeight; + + auto itemWidth = ImGui::CalcItemWidth(); + ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight)); + + panelStack.push_back(ImRect(labelMin, labelMax)); + } + + static void EndPanel() + { + ImGui::PopItemWidth(); + + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.2f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + + ImGui::EndGroup(); + + ImGui::EndGroup(); + + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y)); + + ImGui::EndGroup(); + + auto itemMin = ImGui::GetItemRectMin(); + auto itemMax = ImGui::GetItemRectMax(); + + auto labelRect = panelStack.back(); + panelStack.pop_back(); + + ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f; + ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f)); + labelRect.Min.x -= itemSpacing.x; + labelRect.Max.x += itemSpacing.x; + for (int i = 0; i < 4; ++i) + { + switch (i) + { + // left half-plane + case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break; + // right half-plane + case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break; + // top + case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break; + // bottom + case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break; + } + + ImGui::GetWindowDrawList()->AddRect( + frameRect.Min, frameRect.Max, + ImColor(ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)), + halfFrame.x); + + ImGui::PopClipRect(); + } + + ImGui::PopStyleVar(2); + + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f; + + ImGui::GetCurrentWindow()->Size.x += frameHeight; + + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + + ImGui::EndGroup(); + } + + static bool Splitter(bool verticalSplit, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterAxisSize = -1.0f) + { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (verticalSplit ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + (verticalSplit ? ImVec2(thickness, splitterAxisSize) : ImVec2(splitterAxisSize, thickness)); + return ImGui::SplitterBehavior(bb, id, verticalSplit ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, minSize1, minSize2, 0.0f); + } + + template + static bool DragN(const std::string& label, std::vectorconst& componentLabels, + std::vector values, float speed = 0.1f, const char* displayFormat = "", T valueMin = T(), T valueMax = T(), + ImGuiSliderFlags flags = 0, bool* isHovered = nullptr) + { + const ImGuiWindow* const window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiContext& g = *GImGui; + bool valueChanged = false; + ImGui::BeginGroup(); + ImGui::PushID(label.c_str()); + PushMultiItemsWidthsAndLabels(componentLabels, 0.0f); + ImGui::BeginColumns("DragVecCol", 2, ImGuiOldColumnFlags_NoBorder | ImGuiOldColumnFlags_NoResize); + ImGui::SetColumnWidth(-1, 80.0f); + ImGui::Text(label.c_str()); + if (isHovered) + *isHovered |= ImGui::IsItemHovered(); + ImGui::NextColumn(); + for (std::size_t i = 0; i < N; ++i) + { + ImGui::PushID(static_cast(i)); + ImGui::TextUnformatted(componentLabels[i].c_str(), ImGui::FindRenderedTextEnd(componentLabels[i].c_str())); + if (isHovered) + *isHovered |= ImGui::IsItemHovered(); + ImGui::SameLine(); + ImGui::SetNextItemWidth(80.0f); + valueChanged |= ImGui::DragFloat("##v", values[i], speed, valueMin, valueMax, displayFormat, flags); + if (isHovered) + *isHovered |= ImGui::IsItemHovered(); + const ImVec2 min = ImGui::GetItemRectMin(); + const ImVec2 max = ImGui::GetItemRectMax(); + const float spacing = g.Style.FrameRounding; + const float halfSpacing = spacing / 2; + + window->DrawList->AddLine({ min.x + spacing, max.y - halfSpacing }, { max.x - spacing, max.y - halfSpacing }, + ImGuiColors::colors[i], 4); + + ImGui::SameLine(0, g.Style.ItemInnerSpacing.x); + ImGui::PopItemWidth(); + ImGui::PopID(); + } + ImGui::EndColumns(); + ImGui::PopID(); + ImGui::EndGroup(); + + return valueChanged; + } + + static bool DragVec2(const std::string& label, std::vectorconst& componentLabels, std::function get, + std::function set, bool const& isAnAngleInRad = false, std::string_view const& tooltip = {}, float speed = 0.1f, const char* displayFormat = "%.3f", float valueMin = 0.0f, float valueMax = 0.0f, + ImGuiSliderFlags flags = 0) + { + SHVec2 values = get(); + if(isAnAngleInRad) + { + values = {SHMath::RadiansToDegrees(values.x), SHMath::RadiansToDegrees(values.y)}; + } + bool const changed = DragN(label, componentLabels, { &values.x, &values.y }, speed, displayFormat, valueMin, valueMax, flags); + static bool startRecording = false; + if (changed) + { + if(isAnAngleInRad) + { + values = {SHMath::DegreesToRadians(values.x), SHMath::DegreesToRadians(values.y)}; + } + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), values, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + startRecording = false; + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + static bool DragVec3(const std::string& label, std::vectorconst& componentLabels, std::function get, + std::function set, bool const& isAnAngleInRad = false, std::string_view const& tooltip = {}, float speed = 0.1f, const char* displayFormat = "%.3f", float valueMin = 0.0f, float valueMax = 0.0f, + ImGuiSliderFlags flags = 0) + { + SHVec3 values = get(); + if(isAnAngleInRad) + { + values = {SHMath::RadiansToDegrees(values.x), SHMath::RadiansToDegrees(values.y), SHMath::RadiansToDegrees(values.z)}; + } + bool isHovered = false; + bool const changed = DragN(label, componentLabels, { &values.x, &values.y, &values.z }, speed, displayFormat, valueMin, valueMax, flags, &isHovered); + static bool startRecording = false; + if (changed) + { + SHVec3 old = get(); + if(isAnAngleInRad) + { + values = {SHMath::DegreesToRadians(values.x), SHMath::DegreesToRadians(values.y), SHMath::DegreesToRadians(values.z)}; + } + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(old, values, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + if (!tooltip.empty()) + { + if (isHovered) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + static bool DragVec4(const std::string& label, std::vectorconst& componentLabels, std::function get, + std::function set, bool const& isAnAngleInRad = false, std::string_view const& tooltip = {}, float speed = 0.1f, const char* displayFormat = "%.3f", float valueMin = 0.0f, float valueMax = 0.0f, + ImGuiSliderFlags flags = 0) + { + SHVec4 values = get(); + if(isAnAngleInRad) + { + values = {SHMath::RadiansToDegrees(values.x), SHMath::RadiansToDegrees(values.y), SHMath::RadiansToDegrees(values.z), SHMath::RadiansToDegrees(values.w)}; + } + bool const changed = DragN(label, componentLabels, { &values.x, &values.y, &values.z, &values.w }, speed, displayFormat, valueMin, valueMax, flags); + static bool startRecording = false; + if (changed) + { + if(isAnAngleInRad) + { + values = {SHMath::DegreesToRadians(values.x), SHMath::DegreesToRadians(values.y), SHMath::DegreesToRadians(values.z), SHMath::DegreesToRadians(values.w)}; + } + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), values, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + //#==============================================================# + //|| Widget Extensions || + //#==============================================================# + + static void TextLabel(std::string_view const& text, bool sameLine = true) + { + const ImVec2 textSize = ImGui::CalcTextSize(text.data(), NULL, true); + if (textSize.x > 0.0f) + { + ImGui::Text(text.data()); + ImGui::SameLine(); + } + } + + static bool CheckBox(std::string_view const& label, std::function get, std::function set, std::string_view const& tooltip = {}) + { + bool value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool const changed = ImGui::Checkbox("##", &value); + if (changed) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), false); + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + template + static bool RadioButton(std::vector const& label, std::vector const& listTypes, std::function get, std::function set, std::string_view const& tooltip = {}) + { + T type = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + //TextLabel(label); + for (size_t i = 0; i < listTypes.size(); i++) + { + if (ImGui::RadioButton(label[i].c_str(), type == listTypes[i])) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), listTypes[i], set)), false); + } + ImGui::SameLine(); + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return true; + } + + static bool InputText(const std::string& label, const std::function get, + const std::function set, std::string_view const& tooltip = {}, + ImGuiInputTextFlags flag = 0, ImGuiInputTextCallback callback = (ImGuiInputTextCallback)0, void* userData = (void*)0) + { + std::string text = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool const changed = ImGui::InputText("##", &text, flag, callback, userData); + if (changed) + { + if (ImGui::IsItemDeactivatedAfterEdit()) + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), text, set)), false); + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + template + static bool DragDropReadOnlyField(std::string const& label, std::string_view const& fieldVTextValue, std::function const& get, std::function const& set, SHDragDrop::DragDropTag const& dragDropTag, std::string_view const& tooltip = {}) + { + std::string text = fieldVTextValue.data(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool changed = ImGui::InputText("##", &text, ImGuiInputTextFlags_ReadOnly, nullptr, nullptr); + if(SHDragDrop::BeginTarget()) + { + if(T* payload = SHDragDrop::AcceptPayload(dragDropTag)) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), *payload, set)), false); + changed = true; + SHDragDrop::EndTarget(); + } + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + + template + static bool DragScalar(const std::string& label, ImGuiDataType data_type, std::function get, std::function set, + float speed = 1.0f, T p_min = T(), T p_max = T(), const char* displayFormat = "%.3f", std::string_view const& tooltip = {}, ImGuiSliderFlags flags = 0) + { + T value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + const bool hasChange = ImGui::DragScalar("##", data_type, &value, speed, &p_min, &p_max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + + static bool DragFloat(const std::string_view& label, std::function get, std::function set, std::string_view const& tooltip = {}, + float speed = 0.1f, float p_min = float(), float p_max = float(), const char* displayFormat = "%.3f", ImGuiSliderFlags flags = 0) + { + float value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + const bool hasChange = ImGui::DragFloat("##", &value, speed, p_min, p_max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + + static bool DragInt(const std::string& label, std::function get, std::function set, std::string_view const& tooltip = {}, + float speed = 1.0f, int p_min = int(), int p_max = int(), const char* displayFormat = "%d", ImGuiSliderFlags flags = 0) + { + int value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + const bool hasChange = ImGui::DragInt("##", &value, speed, p_min, p_max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + template + static bool SliderScalar(const std::string& label, ImGuiDataType data_type, T min, T max, std::function get, std::function set, std::string_view const& tooltip = {}, + const char* displayFormat = "%.3f", ImGuiSliderFlags flags = 0) + { + T value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool const hasChange = ImGui::SliderScalar("##", data_type, &value, &min, &max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + + static bool SliderFloat(const std::string& label, float const& min, float const& max, std::function get, std::function set, std::string_view const& tooltip = {}, + const char* displayFormat = "%.3f", ImGuiSliderFlags flags = 0) + { + float value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool const hasChange = ImGui::SliderFloat("##", &value, min, max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + + static bool SliderInt(const std::string& label, int min, int max, std::function get, std::function set, std::string_view const& tooltip = {}, + const char* displayFormat = "%d", ImGuiSliderFlags flags = 0) + { + int value = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + TextLabel(label); + bool const hasChange = ImGui::SliderInt("##", &value, min, max, displayFormat, flags); + static bool startRecording = false; + if (hasChange) + { + + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), value, set)), startRecording); + if (!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return hasChange; + } + + static bool ComboBox(const std::string& label, std::vector list, std::function get, std::function set, std::string_view const& tooltip = {}) + { + bool edited = false; + int selected = get(); + ImGui::BeginGroup(); + ImGui::PushID(label.c_str()); + TextLabel(label); + ImGui::SameLine(); + + if (edited = ImGui::Combo("##Combo", &selected, list.data(), static_cast(list.size()))) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), selected, set)), false); + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return edited; + } + + static bool ColorPicker(const std::string_view& label, std::function get, std::function set, std::string_view const& tooltip = {}, float speed = 0.1f, const char* displayFormat = "%.3f", float valueMin = 0.0f, float valueMax = 0.0f, + ImGuiSliderFlags flags = 0) + { + bool changed = false; + ImGui::BeginGroup(); + ImGui::PushID(label.data()); + + SHVec4 values = get(); + //changed |= DragN(label.data(), {"R", "G", "B", "A"}, { &values.x, &values.y, &values.z, &values.w }, speed, displayFormat, valueMin, valueMax, flags); + //ImGui::SameLine(); + TextLabel(label); + changed = ImGui::ColorEdit4("##Col4", &values.x, ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB ); + static bool startRecording = false; + if(changed) + { + SHCommandManager::PerformCommand(std::reinterpret_pointer_cast(std::make_shared>(get(), values, set)), startRecording); + if(!startRecording) + startRecording = true; + } + if (startRecording && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + startRecording = false; + } + ImGui::PopID(); + ImGui::EndGroup(); + if (!tooltip.empty()) + { + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text(tooltip.data()); + ImGui::EndTooltip(); + } + } + return changed; + } + }; + +}//namespace SHADE diff --git a/SHADE_Engine/src/Editor/SHImGuiHelpers.hpp b/SHADE_Engine/src/Editor/SHImGuiHelpers.hpp new file mode 100644 index 00000000..0c7c1ecf --- /dev/null +++ b/SHADE_Engine/src/Editor/SHImGuiHelpers.hpp @@ -0,0 +1,73 @@ +#pragma once + +//#==============================================================# +//|| STL Includes || +//#==============================================================# +#include + +//#==============================================================# +//|| SHADE Includes || +//#==============================================================# +#include "Math/SHMath.h" + +//#==============================================================# +//|| SHADE-ImGui Math Conversions || +//#==============================================================# +#ifndef SH_IM_MATH +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const SHADE::SHVec2& vec) {x = vec.x; y = vec.y;} \ + operator SHADE::SHVec2() const {return SHADE::SHVec2(x,y);} +#define IM_VEC3_CLASS_EXTRA \ + ImVec3(const SHADE::SHVec3& vec) {x = vec.x; y = vec.y; z = vec.z;} \ + operator SHADE::SHVec3() const {return SHADE::SHVec3(x,y,z);} +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const SHADE::SHVec4& vec) {x = vec.x; y = vec.y; z = vec.z; w = vec.w;} \ + operator SHADE::SHVec4() const {return SHADE::SHVec4(x,y,z,w);} +#endif + +#define IMGUI_DEFINE_MATH_OPERATORS + +//#==============================================================# +//|| Library Includes || +//#==============================================================# +#include +#include + + +namespace SHADE +{ + namespace ImGuiColors + { + constexpr ImVec4 red = {1.0f, 0.0f, 0.0f, 1.f}; + constexpr ImVec4 green = {0.0f, 1.0f, 0.0f, 1.f}; + constexpr ImVec4 blue = {0.0f, 0.0f, 1.0f, 1.f}; + constexpr ImVec4 white = {1.0f, 1.0f, 1.0f, 1.f}; + + constexpr int colors_red = 0; + constexpr int colors_green = 1; + constexpr int colors_blue = 2; + constexpr int colors_white = 3; + constexpr ImU32 colors[] = { + 0xBB0000FF, // red + 0xBB00FF00, // green + 0xBBFF0000, // blue + 0xBBFFFFFF, // white + }; + } + + static void PushMultiItemsWidthsAndLabels(const std::vector& labels, float wFull) + { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (wFull <= 0.0f) + wFull = ImGui::GetContentRegionAvail().x; + const auto size = labels.size(); + const float w_item_one = + ImMax(1.0f, (wFull - (static_cast(size) - 1.0f) * (style.ItemInnerSpacing.x * 2.0f)) / static_cast( + size)) - + style.ItemInnerSpacing.x; + for (int i = 0; i < size; i++) + window->DC.ItemWidthStack.push_back(w_item_one - ImGui::CalcTextSize(labels[i].c_str()).x); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); + } +} //namespace SHADE diff --git a/SHADE_Engine/src/Events/SHEvent.h b/SHADE_Engine/src/Events/SHEvent.h new file mode 100644 index 00000000..6d246f3d --- /dev/null +++ b/SHADE_Engine/src/Events/SHEvent.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * \file SHEvent.h + * \author Loh Xiao Qi + * \brief Event class declaration + * + * \copyright Copyright (c) 2022 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior written consent + * of Digipen Institute of Technology is prohibited. +******************************************************************************/ +#pragma once + +#include "SHEventDefines.h" + +namespace SHADE +{ + struct SHEvent + { + SHEventIdentifier type; + SHEventHandle handle; + }; + + template + struct SHEventSpec : SHEvent + { + SHEventSpec(SHEventIdentifier t, SHEventHandle e, std::shared_ptr dp) + :SHEvent{ t, e }, data{ dp } {} + + std::shared_ptr data; + }; + + using SHEventPtr = std::shared_ptr; +} diff --git a/SHADE_Engine/src/Events/SHEventDefines.h b/SHADE_Engine/src/Events/SHEventDefines.h new file mode 100644 index 00000000..d7bbf5f0 --- /dev/null +++ b/SHADE_Engine/src/Events/SHEventDefines.h @@ -0,0 +1,21 @@ +#pragma once +#include "SHpch.h" + +typedef uint32_t SHEventIdentifier; +typedef uint32_t SHEventHandle; + +//Add your event identifiers here: +constexpr SHEventIdentifier SH_EXAMPLE_EVENT { 0 }; +constexpr SHEventIdentifier SH_ENTITY_DESTROYED_EVENT { 1 }; +constexpr SHEventIdentifier SH_ENTITY_CREATION_EVENT { 2 }; +constexpr SHEventIdentifier SH_COMPONENT_ADDED_EVENT { 3 }; +constexpr SHEventIdentifier SH_COMPONENT_REMOVED_EVENT { 4 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_CHANGE_PARENT_EVENT { 5 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_ADD_CHILD_EVENT { 6 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_REMOVE_CHILD_EVENT { 7 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_ADDED_EVENT { 8 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 9 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 10 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 11 }; +constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 12 }; + diff --git a/SHADE_Engine/src/Events/SHEventManager.hpp b/SHADE_Engine/src/Events/SHEventManager.hpp new file mode 100644 index 00000000..490c6b10 --- /dev/null +++ b/SHADE_Engine/src/Events/SHEventManager.hpp @@ -0,0 +1,156 @@ +/****************************************************************************** + * \file SHEventManager.hpp + * \author Loh Xiao Qi + * \brief Class declaration for event manager. + * + * \copyright Copyright (c) 2022 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior written consent + * of Digipen Institute of Technology is prohibited. +******************************************************************************/ +#pragma once + +#include "SHpch.h" +#include "SHEvent.h" +#include "SHEventReceiver.h" +#include "SH_API.h" + +/****************************************************************************** + INSTRUCTIONS FOR USE: + On broadcaster side: + 1. Create a struct/class to contain the data that you would need to send + in the event. + 2. Create unique event identifier in SHEventDefines.h, follow the example + provided. + 3. When ready to send the event, call + SHEventManager::BroadcastEvent(exampleClass, EVENT_IDENTIFIER); + + NOTE: If your custom struct to contain data requires a deep copy, please + overload the assignment operator accordingly. It is fine to send + a single object of a basic type such as int/float. + + Headers required: SHEventManager.hpp + + On Receiver side: + 1. Create a function with the signature: + SHEventHandle FunctionName(SHEventPtr); + + 2. In the init function of the class, copy the below in and replace the + necessary: + + std::shared_ptr> thisReceiver{ + std::make_shared>(this, &ReceiverClass::ReceiveFunction) + }; + ReceiverPtr receiver = std::dynamic_pointer_cast(thisReceiver); + SHEventManager::SubscribeTo(EVENT_IDENTIFIER, receiver); + + ReceiverClass is the class that the receiver is in. E.g., SHPhysicsSystem + + 3. Note: The EventIdentifier should match all that is defined in + SHEventDefines.h so check there. When the receiver catches the event, it + needs to know the struct that the broadcaster is using to cast the event + ptr as such: + + reinterpret_cast>>(event) + + 4. Inside the new ptr should be a shared pointer of const CustomClass type. + + Headers required: SHEventManager.hpp, SHEventReceiver.h + + If you have any questions/suggestions for improvement lmk. +******************************************************************************/ + +namespace SHADE +{ + //using ResponseFunction = std::function; + using ReceiverPtr = std::shared_ptr; + using ResponseVec = std::vector; + + using EventManagerListener = std::function; + + + class SH_API SHEventManager + { + private: + + // Registry for broadcasters and subscribers + inline static std::unordered_map packageReceiverRegistry; + + inline static SHEventHandle handleCounter {0}; + + /**************************************************************************** + * \param ListenerConstPtr - Const pointer to listener that sent event. + * \param EventType - Event data + + * \brief Broadcast event to all receivers that are subscribed to this + * listener. + ****************************************************************************/ + static void Broadcast(SHEventPtr event) + { + ResponseVec& receivers{ packageReceiverRegistry[event->type] }; + for (auto& receiver : receivers) + { + receiver->Receive(event); + } + } + + /**************************************************************************** + * \param ReceiverPtr - Pointer to receiver + * \param ListenerConstPtr - Const pointer to listener that receiver is + * subscribing to. + + * \brief Registers receiver as a subscriber to listener in the registry. + ****************************************************************************/ + static void RegisterReceiverToType(SHEventIdentifier pkgType, ReceiverPtr receiver) + { + if (packageReceiverRegistry.find(pkgType) == packageReceiverRegistry.end()) + { + packageReceiverRegistry.emplace(pkgType, std::vector{ receiver }); + } + else + { + packageReceiverRegistry[pkgType].emplace_back(receiver); + } + } + + public: + + /**************************************************************************** + * \param ListenerConstPtr - Const pointer to listener that sent event. + * \param EventType - Templated type for every type of event + + * \brief Receives event from the listeners. + ****************************************************************************/ + static void CatchEvent(SHEventPtr event) + { + + // Do something with the event + + Broadcast(event); + } + + /**************************************************************************** + * \param ResponseFunction - function pointer from receiver to be passed + * into event manager to be called when events are broadcasted. + * \param SHPackageType - package type that corresponding subscriber is + * subscribing to. + + * \brief Links a function pointer from a subscriber to a particular + * package type + ****************************************************************************/ + static void SubscribeTo(SHEventIdentifier pkgType, ReceiverPtr receiver) + { + RegisterReceiverToType(pkgType, receiver); + } + + template + static void BroadcastEvent(T data, SHEventIdentifier eventType) + { + std::shared_ptr ptr = std::make_shared(data); + + CatchEvent( + std::make_shared>(eventType, handleCounter++, ptr) + ); + } + }; + +} diff --git a/SHADE_Engine/src/Events/SHEventReceiver.h b/SHADE_Engine/src/Events/SHEventReceiver.h new file mode 100644 index 00000000..e2edd4dc --- /dev/null +++ b/SHADE_Engine/src/Events/SHEventReceiver.h @@ -0,0 +1,33 @@ +#pragma once + +#include "SHEvent.h" + +namespace SHADE +{ + class SHEventReceiver + { + private: + public: + virtual void Receive(SHEventPtr) = 0; + }; + + template + class SHEventReceiverSpec : public SHEventReceiver + { + private: + T* object; + SHEventHandle(T::*callback)(SHEventPtr); + + public: + SHEventReceiverSpec(T* obj, SHEventHandle(T::* cb)(SHEventPtr)) + :SHEventReceiver(), object{ obj }, callback{ cb } + { + + } + + void Receive(SHEventPtr evt) override + { + (object->*callback)(evt); + } + }; +} diff --git a/SHADE_Engine/src/FRC/SHFramerateController.cpp b/SHADE_Engine/src/FRC/SHFramerateController.cpp new file mode 100644 index 00000000..0791d628 --- /dev/null +++ b/SHADE_Engine/src/FRC/SHFramerateController.cpp @@ -0,0 +1,154 @@ +/********************************************************************* + * \file SHFramerateController.cpp + * \author Ryan Wang Nian Jing + * \brief Definition for functions of the framerate controller + * Handles changing of scenes and manages loop (timestep, etc.) + * + * \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + + +//TODO Legacy code. Delete soon + +#include +#include +#include +#include "SHFramerateController.h" +#include "../Tools/SHLogger.h" + +namespace SHADE +{ + double SHFrameRateController::rawDeltaTime = 0.0; + std::chrono::steady_clock::time_point SHFrameRateController::prevFrameTime = std::chrono::high_resolution_clock::now(); + + void SHFrameRateController::UpdateFRC() noexcept + { + std::chrono::duration deltaTime; + deltaTime = std::chrono::high_resolution_clock::now() - prevFrameTime; + prevFrameTime = std::chrono::high_resolution_clock::now(); + rawDeltaTime = deltaTime.count(); + } +} + +//TODO Legacy code. Delete soon +#if 0 +namespace SHADE +{ + //Init statics + double SHFramerateController::fixedTimestep = 0.01; + SHScene* SHFramerateController::previousScene = nullptr; + SHScene* SHFramerateController::currentScene = nullptr; + SHScene* SHFramerateController::nextScene = nullptr; + bool SHFramerateController::toRestart = false; + bool SHFramerateController::toQuit = false; + + //Scene manager loop + void SHFramerateController::Run(SHScene* firstScene) + { + if (firstScene == nullptr) + { + SHLOG_ERROR("Do not pass a nullptr as the firstScene"); + return; + } + + //Set quit and restart flags to false + toQuit = false; + toRestart = false; + + //Set the first scene to run + previousScene = firstScene; + currentScene = firstScene; + nextScene = firstScene; + + while (!toQuit) + { + if (toRestart) + { + //Restart current scene + currentScene = previousScene; + nextScene = previousScene; + toRestart = false; + } + else + { + //Move to a new scene + currentScene->Load(); + } + + //Call init function of current scene + currentScene->Init(); + + //Have an initial value + //This frame time will fluctuate + //SHOULD be larger than the fixed timestep + //TODO this might need to be changed + double variableLastFrameTime = fixedTimestep; + + //Time accumulator for meshing between fixed and variable timesteps + double accumulator = 0.0; + + //Start state loop + while (currentScene == nextScene && !toQuit && !toRestart) + { + //Use of new STL timing functions + //https://en.cppreference.com/w/cpp/chrono + std::chrono::duration deltaTime; + + auto startTime = std::chrono::high_resolution_clock::now(); + + //Whittle down the accumulator by continuously simulating + for (; accumulator > fixedTimestep; accumulator -= fixedTimestep) + { + MSG msg; + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + //TODO change to double + currentScene->Update((float)fixedTimestep); + } + + //Interpolation + //Manage the alpha value well + //https://randomascii.wordpress.com/2012/02/13/dont-store-that-in-a-float/ + //Key points: + //1) Any time you add or subtract floats of widely varying + // magnitudes, you need to watch for loss of precision + //2) Sometimes using double instead of float is the correct + // solution, but often a more stable algorithm is more important + //3) calcT() should probably use double (to give sufficient + // precision after many hours of gameplay) + + //TODO awaiting approval to use this + //double alpha = accumulator / fixedTimestep; + + //assert alpha does not go out of range + + currentScene->Render(); + + auto endTime = std::chrono::high_resolution_clock::now(); + deltaTime = endTime - startTime; + variableLastFrameTime = deltaTime.count(); + + //Increase accumulator + accumulator += variableLastFrameTime; + } + + //Free once out of scene loop + currentScene->Free(); + + //Check if not to restart state + //If so, unload + if (!toRestart) currentScene->Unload(); + + //Shift forward scenes + previousScene = currentScene; + currentScene = nextScene; + } + } +} +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/FRC/SHFramerateController.h b/SHADE_Engine/src/FRC/SHFramerateController.h new file mode 100644 index 00000000..dbabbac5 --- /dev/null +++ b/SHADE_Engine/src/FRC/SHFramerateController.h @@ -0,0 +1,105 @@ +/********************************************************************* + * \file SHFramerateController.h + * \author Ryan Wang Nian Jing + * \brief Declaration for the framerate controller + * Handles changing of scenes and manages loop (timestep, etc.) + * + * \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_FRAMERATECONTROLLER_H +#define SH_FRAMERATECONTROLLER_H +#pragma once + +#include +#include "SH_API.h" + +namespace SHADE +{ + class SH_API SHFrameRateController + { + private: + //Varying delta time. The actual time it took for every frame + static double rawDeltaTime; + static std::chrono::steady_clock::time_point prevFrameTime; + + + public: + //Gets the raw delta time + static inline double GetRawDeltaTime() noexcept + { + return rawDeltaTime; + } + + //Updates the raw delta time accordingly + static void UpdateFRC() noexcept; + + }; + +} + + + +//TODO Legacy code. Delete soon +#if 0 +#include "../Scene/SHScene.h" + +namespace SHADE +{ + class SHFramerateController + { + private: + //scene pointers + static SHScene* previousScene; + static SHScene* currentScene; + static SHScene* nextScene; + + //Flags + //Whether the flag has been raised for the game to be quit + static bool toQuit; + + //Whether the flag has been raised for the current scene to restart + static bool toRestart; + + public: + //Fixed timestep value for physics. Default at 1/100th of a second. + //Should be lower than the variable refresh rate + static double fixedTimestep; + + //Scene Manager Loop + //This loop is vital to the game because it runs for as long as the game + //runs. Before entering, initialise vital systems for game. After exiting, + //free these vital systems before finishing the main() function and + //terminating the game + //Parameter of firstScene is what scene the game should start with + static void Run(SHScene* firstScene); + + //Set the flag to restart the current game scene + static inline void RestartScene() { toRestart = true; } + + //Set the flag to halt running of the scene manager and quit the game + static inline void QuitGame() { toQuit = true; } + + //Set the next scene to be excuted + //This will tell the scene manager to + //halt execution of the current scene and prepare + //execution of the next + static inline void SetNextScene(SHScene* const next) { nextScene = next; } + + + + }; + + + +} +#endif + + + + + + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Filesystem/SHFileSystem.cpp b/SHADE_Engine/src/Filesystem/SHFileSystem.cpp index 5663dadd..a28f70ca 100644 --- a/SHADE_Engine/src/Filesystem/SHFileSystem.cpp +++ b/SHADE_Engine/src/Filesystem/SHFileSystem.cpp @@ -1,135 +1,221 @@ +/*************************************************************************//** + * \file SHFileSystem.cpp + * \author Loh Xiao Qi + * \date 30 October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ #include "SHpch.h" #include "SHFileSystem.h" -#include "fileapi.h" #include -#include #include +#include + +#include "Assets/SHAssetMetaHandler.h" namespace SHADE { - char const FOLDER_MAX_COUNT {15}; - - std::unordered_map> SHFileSystem::folders; - FolderPointer SHFileSystem::root {nullptr}; - - SHFolder::SHFolder(FolderHandle id, FolderName name) - :id{ id }, name{ name }, subFolders(0), folded{ false }, path{""} - { - } - - FolderLocation SHFileSystem::CreateNewFolderHere(FolderName name, FolderLocation here) noexcept - { - if (here == 0) - { - if (!folders.contains(0)) - { - folders[0] = std::make_unique(0, "root"); - } - - auto const count = static_cast(folders[here]->subFolders.size()); - - assert(count < FOLDER_MAX_COUNT, "Max subfolders reached\n"); - - auto const location = static_cast(count); - - CreateFolder(folders[0]->path, here, location, name); - - return location; - } - - assert(folders.contains(here), "Folder creation location does not exist/invalid\n"); - - auto const count = static_cast(folders[here]->subFolders.size()); - - FolderHandle location = here; - location <<= FOLDER_BIT_ALLOCATE; - location |= count; - - assert(count < FOLDER_MAX_COUNT, "Max subfolders reached\n"); - CreateFolder(folders[0]->path, here, location, name); - - return location; - } bool SHFileSystem::DeleteFolder(FolderPointer location) noexcept { - assert(folders.contains(location->id), "Delete target does not exist/invalid.\n"); - - for (auto const& subFolder : folders[location->id]->subFolders) - { - DeleteFolder(subFolder); - } - - RemoveDirectoryA(folders[location->id]->path.c_str()); + //TODO IMPLEMENT return true; } - void SHFileSystem::StartupFillDirectories(FolderPath path) noexcept + bool SHFileSystem::IsRecognised(char const* ext) noexcept + { + for (auto const& e : EXTENSIONS) + { + if (strcmp(ext, e.data()) == 0) + { + return true; + } + } + + return false; + } + + bool SHFileSystem::IsCompilable(std::string ext) noexcept + { + for (auto const& external : EXTERNALS) + { + if (ext == external) + { + return true; + } + } + + return false; + } + + bool SHFileSystem::MatchExtention(FileExt raw, FileExt compiled) noexcept + { + if (raw == GLSL_EXTENSION) + { + if (compiled == SHADER_EXTENSION || + compiled == SHADER_BUILT_IN_EXTENSION) + { + return true; + } + } + else if (raw == DDS_EXTENSION) + { + if (compiled == TEXTURE_EXTENSION) + { + return true; + } + } + else if (raw == FBX_EXTENSION) + { + if (compiled == MODEL_EXTENSION) + { + return true; + } + } + else if (raw == GLTF_EXTENSION) + { + if (compiled == MODEL_EXTENSION) + { + return true; + } + } + + return false; + } + + void SHFileSystem::BuildDirectory( + FolderPath path, + FolderPointer& root, + std::unordered_map& assetCollection, + std::vector& toGenerate) noexcept { - std::queue folderQueue; - - folderQueue.push(RegisterFolder(path, 0, 0, "Root")); + std::stack folderStack; + root = new SHFolder("root"); + root->path = path; + folderStack.push(root); - while (!folderQueue.empty()) + while (!folderStack.empty()) { - auto folder = folderQueue.front(); - folderQueue.pop(); - FolderCounter count = 0; + auto const folder = folderStack.top(); + folderStack.pop(); - for (auto const& dirEntry : std::filesystem::directory_iterator(folder->path)) + std::vector assets; + + // Get all subfolders/files in this current folder + for (auto& dirEntry : std::filesystem::directory_iterator(folder->path)) { + auto path = dirEntry.path(); + path.make_preferred(); if (!dirEntry.is_directory()) { - continue; + if (path.extension().string() == META_EXTENSION) + { + assets.push_back(SHAssetMetaHandler::RetrieveMetaData(path)); + } + else + { + folder->files.emplace_back( + path.stem().string(), + path.string(), + path.extension().string(), + nullptr, + IsCompilable(path.extension().string()), + false + ); + } + continue; } - FolderLocation location = folder->id; - location <<= FOLDER_BIT_ALLOCATE; - location |= ++count; + // If item is folder + if (path.stem().string() == "bin" + || path.stem().string() == "obj" + || !std::filesystem::exists(path)) + { + SHLOG_INFO("[FileSystem] Skipped paths in directory building: {}", path.string()); + continue; + } - std::string name = dirEntry.path().string(); - name = name.substr(name.find_last_of('/') + 1, name.length() - name.find_last_of('/')); - - FolderPointer newFolder{ RegisterFolder( - dirEntry.path().string(), - folder->id, - location, - name) - }; - - folderQueue.push(newFolder); - folder->subFolders.push_back(newFolder); + auto newFolder{ folder->CreateSubFolderHere(path.stem().string()) }; + folderStack.push(newFolder); } + + for (auto& file : folder->files) + { + if (!IsRecognised(file.ext.c_str())) + { + continue; + } + + bool found{ false }; + for (auto const& asset : assets) + { + if (!assetCollection.contains(asset.id)) + { + assetCollection.emplace(asset.id, asset); + } + if (file.name == asset.name) + { + AssetPath path{ file.path }; + if (SHAssetMetaHandler::GetTypeFromExtension(path.extension().string()) == asset.type) + { + file.assetMeta = &assetCollection[asset.id]; + found = true; + break; + } + } + } + + if (!found) + { + toGenerate.push_back(&file); + } + } + + for (auto i {0}; i < folder->files.size(); ++i) + { + auto& file = folder->files[i]; + if (file.compilable) + { + for (auto j{ 0 }; j < folder->files.size(); ++j) + { + auto& check = folder->files[j]; + if (i == j || check.compilable) + { + continue; + } + + if (file.name == check.name) + { + if (MatchExtention(file.ext, check.ext)) + { + file.compiled = true; + } + } + } + } + } } } - FolderPointer SHFileSystem::CreateFolder(FolderPath path, FolderLocation parent, FolderHandle location, FolderName name) noexcept - { - assert( - CreateDirectoryA(path.c_str(), nullptr), - "Failed to create folder\n" - ); + void SHFileSystem::DestroyDirectory(FolderPointer root) noexcept + { + std::stack folderStack; + folderStack.push(root); - folders[location] = std::make_unique(location, name); - folders[location]->path = path; - folders[parent]->subFolders.push_back(folders[location].get()); + while(!folderStack.empty()) + { + auto const folder = folderStack.top(); + folderStack.pop(); - return FolderMakeHelper(path, parent, location, name); - } + for (auto const& ptr : folder->subFolders) + { + folderStack.push(ptr); + } - FolderPointer SHFileSystem::RegisterFolder(FolderPath path, FolderLocation parent, FolderHandle location, - FolderName name) noexcept - { - return FolderMakeHelper(path, parent, location, name); - } - - FolderPointer SHFileSystem::FolderMakeHelper(FolderPath path, FolderLocation parent, FolderHandle location, - FolderName name) noexcept - { - folders[location] = std::make_unique(location, name); - folders[location]->path = path; - folders[parent]->subFolders.push_back(folders[location].get()); - - return folders[location].get(); - } + delete folder; + } + } } diff --git a/SHADE_Engine/src/Filesystem/SHFileSystem.h b/SHADE_Engine/src/Filesystem/SHFileSystem.h index 9b8b94a2..4bace233 100644 --- a/SHADE_Engine/src/Filesystem/SHFileSystem.h +++ b/SHADE_Engine/src/Filesystem/SHFileSystem.h @@ -1,57 +1,35 @@ +/****************************************************************************** + * \file SHFileSystem.h + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ #pragma once -#include -#include -#include +#include "SHFolder.h" #include namespace SHADE { - class SHFolder; - - typedef unsigned char FolderCounter; - typedef unsigned char FileCounter; - typedef uint64_t FolderLocation; - typedef uint64_t FolderHandle; - typedef std::string FolderName; - typedef std::string FolderPath; - typedef SHFolder* FolderPointer; - - constexpr char FOLDER_BIT_ALLOCATE{ 4 }; - constexpr char FOLDER_MAX_DEPTH{ 16 }; - - class SHFolder - { - public: - SHFolder(FolderHandle id, FolderName name); - - FolderHandle id; - FolderName name; - std::vector subFolders; - - bool folded; - - private: - FolderPath path; - friend class SHFileSystem; - }; class SHFileSystem { public: - static FolderLocation CreateNewFolderHere(FolderName name, FolderLocation here = 0) noexcept; - - static bool DeleteFolder(FolderPointer location) noexcept; - - static void StartupFillDirectories(FolderPath path) noexcept; + static void BuildDirectory( + FolderPath path, + FolderPointer& root, + std::unordered_map& assetCollection, + std::vector& toGenerate) noexcept; + static void DestroyDirectory(FolderPointer root) noexcept; + static bool IsRecognised(char const*) noexcept; private: - static FolderPointer root; - - static std::unordered_map> folders; - - static FolderPointer CreateFolder(FolderPath path, FolderLocation parent, FolderHandle location, FolderName name) noexcept; - static FolderPointer RegisterFolder(FolderPath path, FolderLocation parent, FolderHandle location, FolderName name) noexcept; - static FolderPointer FolderMakeHelper(FolderPath path, FolderLocation parent, FolderHandle location, FolderName name) noexcept; + static bool DeleteFolder(FolderPointer location) noexcept; + static bool IsCompilable(std::string ext) noexcept; + static bool MatchExtention(FileExt raw, FileExt compiled) noexcept; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Filesystem/SHFolder.cpp b/SHADE_Engine/src/Filesystem/SHFolder.cpp new file mode 100644 index 00000000..716eec4b --- /dev/null +++ b/SHADE_Engine/src/Filesystem/SHFolder.cpp @@ -0,0 +1,38 @@ +/*************************************************************************//** + * \file SHFolder.cpp + * \author Loh Xiao Qi + * \date 30 October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#include "SHpch.h" +#include "SHFolder.h" + +namespace SHADE +{ + SHFolder::SHFolder(FolderName name) + :name{ name }, subFolders(0), folded{ false }, path{ "" } + { + } + + FolderPointer SHFolder::CreateSubFolderHere(FolderName name) + { + for (auto const& folder : subFolders) + { + if (name == folder->name) + { + SHLOG_ERROR("Unable to create subfolder {} at {} as it already exists", name, folder->name); + return nullptr; + } + } + + auto result = new SHFolder(name); + result->path = path + "/" + name; + subFolders.push_back(result); + + return result; + } +} diff --git a/SHADE_Engine/src/Filesystem/SHFolder.h b/SHADE_Engine/src/Filesystem/SHFolder.h new file mode 100644 index 00000000..234e6f19 --- /dev/null +++ b/SHADE_Engine/src/Filesystem/SHFolder.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * \file SHFolder.h + * \author Loh Xiao Qi + * \date 28 October 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include +#include +#include "Assets/SHAsset.h" + +namespace SHADE +{ + class SHFolder; + + typedef unsigned char FolderCounter; + typedef unsigned char FileCounter; + typedef std::string FolderName; + typedef std::string FileName; + typedef std::string FolderPath; + typedef std::string FilePath; + typedef std::string FileExt; + typedef SHFolder* FolderPointer; + + struct SHFile + { + FileName name; + FilePath path; + FileExt ext; + SHAsset const* assetMeta; + bool compilable; + bool compiled; + }; + + class SHFolder + { + public: + SHFolder(FolderName name); + + FolderName name; + std::vector subFolders; + std::vector files; + + bool folded; + + FolderPointer CreateSubFolderHere(FolderName name); + FolderPath path; + }; +} diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp index 4158d3c3..36108628 100644 --- a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp @@ -35,11 +35,40 @@ namespace SHADE } } + void SHVkBuffer::ResizeNoCopy(uint32_t newSize) + { + Destroy(); + vk::Buffer tempBuffer; + std::tie(tempBuffer, std::ignore) = createBuffer(newSize); + vkBuffer = tempBuffer; + } + + void SHVkBuffer::ResizeReplace(uint32_t newSize, void* data, uint32_t srcSize) + { + Destroy(); + Init(newSize, data, srcSize, bufferUsageFlags, allocCreateInfo.usage, allocCreateInfo.flags); + } + + void SHVkBuffer::FlushAllocation(uint32_t srcOffset, uint32_t dstOffset) noexcept + { + vmaFlushAllocation(vmaAllocator, alloc, srcOffset, dstOffset); + } + vk::Buffer SHVkBuffer::GetVkBuffer(void) const noexcept { return vkBuffer; } + vk::BufferUsageFlags SHVkBuffer::GetUsageBits(void) const noexcept + { + return bufferUsageFlags; + } + + uint32_t SHVkBuffer::GetSizeStored(void) const noexcept + { + return sizeStored; + } + /***************************************************************************/ /*! @@ -53,8 +82,7 @@ namespace SHADE /***************************************************************************/ void SHVkBuffer::Map(void) noexcept { - if (!boundToCoherent) - vmaMapMemory(vmaAllocator, alloc, &mappedPtr); + vmaMapMemory(vmaAllocator, alloc, &mappedPtr); } /***************************************************************************/ @@ -71,11 +99,8 @@ namespace SHADE /***************************************************************************/ void SHVkBuffer::Unmap(void) noexcept { - if (!boundToCoherent) - { - vmaUnmapMemory(vmaAllocator, alloc); - mappedPtr = nullptr; - } + vmaUnmapMemory(vmaAllocator, alloc); + mappedPtr = nullptr; } /***************************************************************************/ @@ -113,9 +138,7 @@ namespace SHADE Otherwise, only the copying is carried out. In the instance where memory is non-coherent but HOST_VISIBLE, we want to - write to data and then unmap and flush it immediately. If you want to write - to memory in random-access fashion, consider, mapping, writing a few - things, unmapping then flushing. + write to data and then unmap and flush it immediately. \param vmaAllocator The VMA allocator object. @@ -136,18 +159,11 @@ namespace SHADE /***************************************************************************/ void SHVkBuffer::MapWriteUnmap(void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept { - if (!boundToCoherent) - { - // map from host visible memory to pointer, do a DMA, and then unmap - Map(); - WriteToMemory(data, sizeToWrite, srcOffset, dstOffset); - Unmap(); - } - else - { - if (mappedPtr) - std::memcpy(static_cast(mappedPtr) + dstOffset, static_cast(data) + srcOffset, sizeToWrite); - } + // map from host visible memory to pointer, do a DMA, and then unmap + Map(); + WriteToMemory(data, sizeToWrite, srcOffset, dstOffset); + Unmap(); + FlushAllocation(srcOffset, dstOffset); } /***************************************************************************/ @@ -193,17 +209,19 @@ namespace SHADE // results of allocation VmaAllocation stagingAlloc; - // To get around VMA's usage for C version of vulkan, create a temp first..., + // To get around VMA's usage for C version of Vulkan, create a temp first..., VkBuffer tempBuffer{}; // Create the buffer... vmaCreateBuffer(vmaAllocator, - &bufferInfo.operator VkBufferCreateInfo & (), // TODO: Verify if this works (can use renderdoc to check buffer variables?) + &bufferInfo.operator VkBufferCreateInfo & (), // TODO: Verify if this works (can use RenderDoc to check buffer variables?) &allocCreateInfo, &tempBuffer, &stagingAlloc, &allocInfo); + SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eDeviceMemory, allocInfo.deviceMemory, "[Memory] Staging - " + name); // then assign it to the hpp version stagingBuffer = tempBuffer; + SET_VK_OBJ_NAME(device, vk::ObjectType::eBuffer, stagingBuffer, "[Buffer] Staging - " + name); // Just map, copy then unmap void* stagingBufferMappedPtr = nullptr; @@ -219,6 +237,36 @@ namespace SHADE vmaUnmapMemory(vmaAllocator, stagingAlloc); } + std::pair SHVkBuffer::createBuffer(uint32_t size) + { + // Modify the size based on the parameter + sizeStored = size; + bufferCreateInfo.size = sizeStored; + + // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo + VmaAllocationInfo allocInfo; + + // To get around VMA's usage for C version of vulkan, create a temp first..., + VkBuffer tempBuffer{}; + + // Create the buffer... + auto result = vmaCreateBuffer(vmaAllocator, + &bufferCreateInfo.operator VkBufferCreateInfo & (), + &allocCreateInfo, + &tempBuffer, &alloc, &allocInfo); +#ifdef _DEBUG + if (!name.empty()) + SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eDeviceMemory, allocInfo.deviceMemory, "[Memory] " + name); +#endif + + if (result != VK_SUCCESS) + SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan buffer. "); + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created buffer. "); + + return { tempBuffer, allocInfo }; + } + /***************************************************************************/ /*! @@ -227,30 +275,31 @@ namespace SHADE */ /***************************************************************************/ - SHVkBuffer::SHVkBuffer(std::reference_wrapper allocator) noexcept - : vkBuffer{} - , stagingBuffer{} - , sizeStored{ 0 } - , mappedPtr{ nullptr } - , alloc {nullptr} - , randomAccessOptimized{false} - , boundToCoherent {false} - , vmaAllocator{allocator} - { - - } + SHVkBuffer::SHVkBuffer(Handle logicalDevice, std::reference_wrapper allocator) noexcept + : vkBuffer{} + , stagingBuffer{} + , sizeStored{ 0 } + , mappedPtr{ nullptr } + , alloc {nullptr} + , randomAccessOptimized{false} + , vmaAllocator{allocator} + , device { logicalDevice } + {} SHVkBuffer::SHVkBuffer( + Handle logicalDevice, uint32_t inSize, void* data, uint32_t srcSize, std::reference_wrapper allocator, vk::BufferUsageFlags bufferUsage, + const std::string& name, VmaMemoryUsage memUsage, VmaAllocationCreateFlags allocFlags ) noexcept - : SHVkBuffer(allocator) + : SHVkBuffer(logicalDevice, allocator) { + this->name = name; Init(inSize, data, srcSize, bufferUsage, memUsage, allocFlags); } @@ -258,12 +307,15 @@ namespace SHADE : vkBuffer{std::move (rhs.vkBuffer)} , stagingBuffer{ std::move (rhs.stagingBuffer)} , sizeStored{ std::move (rhs.sizeStored) } - , mappedPtr{ nullptr } + , mappedPtr{ std::move(rhs.mappedPtr) } , alloc{ std::move (rhs.alloc) } , randomAccessOptimized{ rhs.randomAccessOptimized } - , boundToCoherent{ rhs.boundToCoherent} , vmaAllocator{ rhs.vmaAllocator } , bufferUsageFlags {rhs.bufferUsageFlags} + , bufferCreateInfo { rhs.bufferCreateInfo } + , allocCreateInfo { rhs.allocCreateInfo } + , name { std::move(rhs.name) } + , device { rhs.device } { rhs.vkBuffer = VK_NULL_HANDLE; @@ -277,13 +329,16 @@ namespace SHADE vkBuffer = std::move(rhs.vkBuffer); stagingBuffer = std::move(rhs.stagingBuffer); sizeStored = std::move(rhs.sizeStored); - mappedPtr = nullptr; + mappedPtr = std::move(rhs.mappedPtr); alloc = std::move(rhs.alloc); randomAccessOptimized = rhs.randomAccessOptimized; - boundToCoherent = rhs.boundToCoherent; vmaAllocator = std::move (rhs.vmaAllocator); rhs.vkBuffer = VK_NULL_HANDLE; + bufferCreateInfo = rhs.bufferCreateInfo; + allocCreateInfo = rhs.allocCreateInfo; bufferUsageFlags = rhs.bufferUsageFlags; + name = std::move(rhs.name); + device = rhs.device; return *this; } @@ -342,46 +397,27 @@ namespace SHADE { sizeStored = inSize; - // For creation of buffer - vk::BufferCreateInfo bufferInfo{}; + // Set the buffer flags + bufferUsageFlags = bufferUsage; // initialize size and usage (vertex, index, uniform, etc) - bufferInfo.size = sizeStored; - bufferInfo.usage = bufferUsage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; + bufferCreateInfo = vk::BufferCreateInfo{}; + bufferCreateInfo.usage = bufferUsageFlags; + bufferCreateInfo.sharingMode = vk::SharingMode::eExclusive; // Prepare allocation parameters for call to create buffers later - VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo = VmaAllocationCreateInfo{}; allocCreateInfo.usage = memUsage; // If vma allocation flags include dedicated bit, immediately activate dst bit if (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) - bufferInfo.usage |= vk::BufferUsageFlagBits::eTransferDst; - + bufferCreateInfo.usage |= vk::BufferUsageFlagBits::eTransferDst; allocCreateInfo.flags = allocFlags; - // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo - VmaAllocationInfo allocInfo; - - // To get around VMA's usage for C version of vulkan, create a temp first..., - VkBuffer tempBuffer{}; - - // Create the buffer... - auto result = vmaCreateBuffer(vmaAllocator, - &bufferInfo.operator VkBufferCreateInfo &(), - &allocCreateInfo, - &tempBuffer, &alloc, &allocInfo); - - if (result != VK_SUCCESS) - SHVulkanDebugUtil::ReportVkError(vk::Result (result), "Failed to create vulkan buffer. "); - else - SHVulkanDebugUtil::ReportVkSuccess("Successfully created buffer. "); - - // ...then assign it to the hpp version + auto [tempBuffer, allocInfo] = createBuffer(sizeStored); vkBuffer = tempBuffer; - - // Set the buffer flags - bufferUsageFlags = bufferInfo.usage; + if (!name.empty()) + SET_VK_OBJ_NAME(device, vk::ObjectType::eBuffer, vkBuffer, "[Buffer] " + name); // This probably means that a HOST_CACHED memory type is used on allocation if (allocFlags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) @@ -404,18 +440,20 @@ namespace SHADE // mainly host visible. Can be cached (need to flush/invalidate), uncached (always coherent) and coherent (virtual). if(memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { - // If memory is marked to be coherent between CPU and GPU (no need flush/invalidate) (TODO: Verify if VMA_ALLOCATION_CREATE_MAPPED_BIT is used when VMA_MEMORY_USAGE_AUTO is set) - // TODO: also verify that coherent bit = pointer is already mapped - if (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) - { - boundToCoherent = true; + const bool CREATE_MAPPED = allocFlags & VMA_ALLOCATION_CREATE_MAPPED_BIT; + + if (CREATE_MAPPED) mappedPtr = allocInfo.pMappedData; - } else mappedPtr = nullptr; if (data) - MapWriteUnmap(data, srcSize, 0, 0); + { + if (CREATE_MAPPED) + WriteToMemory(data, srcSize, 0, 0); + else + MapWriteUnmap(data, srcSize, 0, 0); + } } else { @@ -454,7 +492,7 @@ namespace SHADE /***************************************************************************/ void SHVkBuffer::Destroy(void) noexcept { - vmaDestroyBuffer(vmaAllocator, vkBuffer, alloc); + if (vkBuffer) + vmaDestroyBuffer(vmaAllocator, vkBuffer, alloc); } - } diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h index 2ad3e4e9..1119342c 100644 --- a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h @@ -3,7 +3,7 @@ #include "Graphics/SHVulkanIncludes.h" #include "vk_mem_alloc.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { @@ -12,6 +12,7 @@ namespace SHADE //using SHVkBufferUsageBits = vk::BufferUsageFlagBits; class SHVkCommandBuffer; + class SHVkLogicalDevice; class SHVkBuffer { @@ -41,35 +42,43 @@ namespace SHADE //! If initialized with vma random access flag, this is true bool randomAccessOptimized; - //! Whether or not this buffer is bound to coherent memory - bool boundToCoherent; - //! buffer usage info flags vk::BufferUsageFlags bufferUsageFlags; + vk::BufferCreateInfo bufferCreateInfo; + VmaAllocationCreateInfo allocCreateInfo; + //! Reference to the allocator //VmaAllocator const& vmaAllocator; std::reference_wrapper vmaAllocator; + //! Name of this buffer if any + std::string name; + //! Handle to the logical device that created this buffer + Handle device; + /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ void PrepStagingBuffer (void* data, uint32_t srcSize) noexcept; + std::pair createBuffer(uint32_t size); public: /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ SHVkBuffer (void) noexcept = delete; - SHVkBuffer (std::reference_wrapper allocator) noexcept; + SHVkBuffer (Handle logicalDevice, std::reference_wrapper allocator) noexcept; SHVkBuffer ( - uint32_t inSize, + Handle logicalDevice, + uint32_t inSize, void* data, uint32_t srcSize, std::reference_wrapper allocator, - vk::BufferUsageFlags bufferUsage, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + vk::BufferUsageFlags bufferUsage, + const std::string& name = "", + VmaMemoryUsage memUsage = VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlags allocFlags = {} ) noexcept; SHVkBuffer(SHVkBuffer&& rhs) noexcept; SHVkBuffer& operator=(SHVkBuffer&& rhs) noexcept; @@ -79,26 +88,42 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ - void Init ( - uint32_t inSize, - void* data, - uint32_t srcSize, - vk::BufferUsageFlags bufferUsage, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags - ) noexcept; - void Destroy (void) noexcept; + void Init ( + uint32_t inSize, + void* data, + uint32_t srcSize, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept; + void Destroy (void) noexcept; - void Map (void) noexcept; - void Unmap (void) noexcept; - void WriteToMemory (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; - void MapWriteUnmap (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; + void Map (void) noexcept; + void Unmap (void) noexcept; + void WriteToMemory (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; + void MapWriteUnmap (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; void TransferToDeviceResource(Handle const& cmdBufferHdl) noexcept; + void ResizeNoCopy (uint32_t newSize); + void ResizeReplace (uint32_t newSize, void* data, uint32_t srcSize); + void FlushAllocation (uint32_t srcOffset, uint32_t dstOffset) noexcept; /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - vk::Buffer GetVkBuffer (void) const noexcept; + vk::Buffer GetVkBuffer (void) const noexcept; + vk::BufferUsageFlags GetUsageBits(void) const noexcept; + uint32_t GetSizeStored (void) const noexcept; + + template + T GetDataFromMappedPointer(uint32_t index) const noexcept + { + if (mappedPtr && index < sizeStored / sizeof (T)) + { + return (static_cast(mappedPtr))[index]; + } + else + return {}; + }; }; } diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp index 6a307230..cc35303b 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp @@ -8,13 +8,16 @@ #include "Graphics/Framebuffer/SHVkFramebuffer.h" #include "Graphics/Pipeline/SHVkPipeline.h" #include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/SHVkUtil.h" namespace SHADE { /***************************************************************************/ /*! - + \brief Frees the command buffer. @@ -22,19 +25,19 @@ namespace SHADE /***************************************************************************/ SHVkCommandBuffer::~SHVkCommandBuffer(void) noexcept { - if (vkCommandBuffer) + if (vkCommandBuffer && parentPool) parentPool->GetLogicalDevice()->GetVkLogicalDevice().freeCommandBuffers(parentPool->GetVkCommandPool(), commandBufferCount, &vkCommandBuffer); } /***************************************************************************/ /*! - + \brief - Only the command buffer is allocated using - VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT bit, is resetting + Only the command buffer is allocated using + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT bit, is resetting individually permitted. Otherwise, throw exception. IMPORTANT NOTE: the command buffer cannot be in the pending state!!! - + */ /***************************************************************************/ void SHVkCommandBuffer::Reset(void) @@ -65,10 +68,10 @@ namespace SHADE /***************************************************************************/ /*! - + \brief Begins the command buffer. - + */ /***************************************************************************/ void SHVkCommandBuffer::BeginRecording(void) noexcept @@ -76,7 +79,10 @@ namespace SHADE // Check if command buffer is ready to record. if (cmdBufferState != SH_CMD_BUFFER_STATE::INITIAL) { - SHLOG_ERROR("Command buffer not in initial state, cannot begin recording. "); + SHLOG_ERROR("Command buffer not in initial state, cannot begin recording. Command buffer could be: \n" + "- corrupted and in invalid state\n" + "- in executable state\n" + "- in pending state\n"); return; } @@ -99,14 +105,17 @@ namespace SHADE // Set the state to recording if the call above succeeded. cmdBufferState = SH_CMD_BUFFER_STATE::RECORDING; + + // Reset segment count + segmentDepth = 0; } /***************************************************************************/ /*! - + \brief End the recording of a command buffer. - + */ /***************************************************************************/ void SHVkCommandBuffer::EndRecording(void) noexcept @@ -116,21 +125,21 @@ namespace SHADE SHLOG_ERROR("Command Buffer not in recording state, cannot end recording. "); return; } - + vkCommandBuffer.end(); cmdBufferState = SH_CMD_BUFFER_STATE::EXECUTABLE; } /***************************************************************************/ /*! - + \brief - Begins a renderpass in the command buffer. 2 important things to note - here, the command buffer used MUST be a primary command buffer and + Begins a renderpass in the command buffer. 2 important things to note + here, the command buffer used MUST be a primary command buffer and command buffer MUST be in a recording state. - + \param renderpassHdl - Renderpass for obvious reasons. + Renderpass for obvious reasons. \param framebufferHdl Framebuffer required in the begin info. @@ -140,7 +149,7 @@ namespace SHADE \param extent Extent of the render area in the framebuffer. - + */ /***************************************************************************/ void SHVkCommandBuffer::BeginRenderpass(Handle const& renderpassHdl, Handle const& framebufferHdl, vk::Offset2D offset, vk::Extent2D extent) noexcept @@ -165,7 +174,7 @@ namespace SHADE vk::RenderPassBeginInfo renderPassInfo{}; renderPassInfo.renderPass = renderpassHdl->GetVkRenderpass(); renderPassInfo.framebuffer = framebufferHdl->GetVkFramebuffer(); - + // If the extent passed in is 0, use the framebuffer dimensions instead. if (extent.width == 0 && extent.height == 0) renderPassInfo.renderArea.extent = framebufferExtent; @@ -182,19 +191,21 @@ namespace SHADE // Check if render area is optimal if (!IsRenderAreaOptimal(renderpassHdl, framebufferExtent, renderPassInfo.renderArea)) + { SHLOG_ERROR("Render area in renderpass begin info is not optimal. See Vulkan vkGetRenderAreaGranularity for details."); + } // Begin the render pass - vkCommandBuffer.beginRenderPass (&renderPassInfo, vk::SubpassContents::eInline); + vkCommandBuffer.beginRenderPass(&renderPassInfo, vk::SubpassContents::eInline); } /***************************************************************************/ /*! - + \brief Ends a renderpass. - + */ /***************************************************************************/ void SHVkCommandBuffer::EndRenderpass(void) noexcept @@ -202,16 +213,21 @@ namespace SHADE vkCommandBuffer.endRenderPass(); } + void SHVkCommandBuffer::NextSubpass(void) noexcept + { + vkCommandBuffer.nextSubpass(commandBufferType == SH_CMD_BUFFER_TYPE::PRIMARY ? vk::SubpassContents::eInline : vk::SubpassContents::eSecondaryCommandBuffers); + } + /***************************************************************************/ /*! - + \brief Sets the viewport dynamically for the command buffer. #NoteToSelf: Dynamic state will not affect pipelines that don't use dynamic state so there isn't a need to do any checks. Also, setting dynamic state like this only needs to happen ONCE per command buffer UNLESS a different viewport is to be used for different drawing commands. - + \param vpWidth viewport width @@ -242,10 +258,10 @@ namespace SHADE \param vpMaxDepth viewport maximum depth value - + */ /***************************************************************************/ - void SHVkCommandBuffer::SetviewportScissor(float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX /*= 0.0f*/, float vpY /*= 0.0f*/, int32_t sX /*= 0.0f*/, int32_t sY /*= 0.0f*/, float vpMinDepth /*= 0.0f*/, float vpMaxDepth /*= 1.0f*/) noexcept + void SHVkCommandBuffer::SetViewportScissor(float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX /*= 0.0f*/, float vpY /*= 0.0f*/, int32_t sX /*= 0.0f*/, int32_t sY /*= 0.0f*/, float vpMinDepth /*= 0.0f*/, float vpMaxDepth /*= 1.0f*/) noexcept { vk::Viewport dynamicViewport { @@ -269,15 +285,20 @@ namespace SHADE } + void SHVkCommandBuffer::SetLineWidth(float lineWidth) noexcept + { + vkCommandBuffer.setLineWidth(lineWidth); + } + /***************************************************************************/ /*! - + \brief Binds a pipeline object to the command buffer. - + \param pipelineHdl The pipeline to bind. - + */ /***************************************************************************/ void SHVkCommandBuffer::BindPipeline(Handle const& pipelineHdl) noexcept @@ -287,7 +308,7 @@ namespace SHADE SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound. "); return; } - boundPipelineLayoutHdl = pipelineHdl->GetPipelineLayout(); + bindPointData[static_cast(pipelineHdl->GetPipelineType())].boundPipelineLayoutHdl = pipelineHdl->GetPipelineLayout(); vkCommandBuffer.bindPipeline(pipelineHdl->GetPipelineBindPoint(), pipelineHdl->GetVkPipeline()); } @@ -295,7 +316,7 @@ namespace SHADE /*! \brief - Binds a buffer to the vertex buffer binding point specified in + Binds a buffer to the vertex buffer binding point specified in bindingPoint. \param bindingPoint @@ -309,29 +330,32 @@ namespace SHADE */ /***************************************************************************/ - void SHVkCommandBuffer::BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept + void SHVkCommandBuffer::BindVertexBuffer(uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept { if (cmdBufferState == SH_CMD_BUFFER_STATE::RECORDING) { - auto bufferHandle = buffer->GetVkBuffer(); - vkCommandBuffer.bindVertexBuffers (bindingPoint, 1, &bufferHandle, &offset); + if (buffer) + { + auto bufferHandle = buffer->GetVkBuffer(); + vkCommandBuffer.bindVertexBuffers(bindingPoint, 1, &bufferHandle, &offset); + } } } /***************************************************************************/ /*! - + \brief Binds an index buffer to the pipeline. - + \param buffer The buffer to bind. \param startingIndex - The starting index in the index buffer. For example, 0 would mean - starting at the beginning. 5 would mean starting at byte offset + The starting index in the index buffer. For example, 0 would mean + starting at the beginning. 5 would mean starting at byte offset size(uint32_t) * 5. - + */ /***************************************************************************/ void SHVkCommandBuffer::BindIndexBuffer(Handle const& buffer, uint32_t startingIndex) const noexcept @@ -339,28 +363,34 @@ namespace SHADE if (cmdBufferState == SH_CMD_BUFFER_STATE::RECORDING) { auto bufferHandle = buffer->GetVkBuffer(); - vkCommandBuffer.bindIndexBuffer (bufferHandle, sizeof (uint32_t) * startingIndex, vk::IndexType::eUint32); + vkCommandBuffer.bindIndexBuffer(bufferHandle, sizeof(uint32_t) * startingIndex, vk::IndexType::eUint32); } } + void SHVkCommandBuffer::BindDescriptorSet(Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets) + { + uint32_t bindPointIndex = static_cast(bindPoint); + vkCommandBuffer.bindDescriptorSets(SHVkUtil::GetPipelineBindPointFromType(bindPoint), bindPointData[bindPointIndex].boundPipelineLayoutHdl->GetVkPipelineLayout(), firstSet, descSetGroup->GetVkHandle(), dynamicOffsets); + } + /***************************************************************************/ /*! - + \brief Calls vkCmdDraw. - + \param vertexCount How many vertices to draw \param instanceCount Number of instances to draw - + \param firstVertex First vertex in the buffer of vertices to start from \param firstInstance First instance to start from. - + */ /***************************************************************************/ void SHVkCommandBuffer::DrawArrays(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept @@ -370,30 +400,30 @@ namespace SHADE SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound. "); return; } - vkCommandBuffer.draw (vertexCount, instanceCount, firstVertex, firstInstance); + vkCommandBuffer.draw(vertexCount, instanceCount, firstVertex, firstInstance); } /***************************************************************************/ /*! - + \brief Issues a non-instanced indexed draw call. - + \param indexCount Number of indices to draw. \param firstIndex Starting index. if the array was 0, 2, 5, 4, and we indicated this to be - 1. The draw call would start from index 2. + 1. The draw call would start from index 2. \param vertexOffset - Starting vertex offset. This would indicate that vertex pulling should + Starting vertex offset. This would indicate that vertex pulling should start from a certain vertex. So a vertex offset of 3 (for example) would mean an index of 0 would mean the 3rd vertex. - + */ /***************************************************************************/ - void SHVkCommandBuffer::DrawIndexed (uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept + void SHVkCommandBuffer::DrawIndexed(uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept { if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) { @@ -408,29 +438,149 @@ namespace SHADE /***************************************************************************/ /*! - - \brief - Calls vkCmdPushConstants and submits data stored in command buffer. - + + \brief + Issues a multi indirect draw call. + + \param indirectDrawData + SHVkBuffer containing the data for the multi indirect draw call. + \param drawCount + Number of multi indirect draw sub-calls stored in indirectDrawData. + */ /***************************************************************************/ - void SHVkCommandBuffer::SubmitPushConstants(void) const noexcept + + void SHVkCommandBuffer::DrawMultiIndirect(Handle indirectDrawData, uint32_t drawCount) { - vkCommandBuffer.pushConstants(boundPipelineLayoutHdl->GetVkPipelineLayout(), - boundPipelineLayoutHdl->GetPushConstantInterface().GetShaderStageFlags(), - 0, - boundPipelineLayoutHdl->GetPushConstantInterface().GetSize(), pushConstantData); + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound."); + return; + } + + if (indirectDrawData) + vkCommandBuffer.drawIndexedIndirect(indirectDrawData->GetVkBuffer(), 0, drawCount, sizeof(vk::DrawIndexedIndirectCommand)); + } + + void SHVkCommandBuffer::ComputeDispatch(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ) noexcept + { + vkCommandBuffer.dispatch (groupCountX, groupCountY, groupCountZ); + } + + void SHVkCommandBuffer::CopyBufferToImage(const vk::Buffer& src, const vk::Image& dst, const std::vector& copyInfo) + { + vkCommandBuffer.copyBufferToImage + ( + src, dst, vk::ImageLayout::eTransferDstOptimal, + static_cast(copyInfo.size()), copyInfo.data() + ); + } + + void SHVkCommandBuffer::CopyImageToBuffer(const vk::Image& src, const vk::Buffer& dst, const std::vector& copyInfo) + { + vkCommandBuffer.copyImageToBuffer (src, vk::ImageLayout::eTransferSrcOptimal, dst, copyInfo); + } + + void SHVkCommandBuffer::PipelineBarrier( + vk::PipelineStageFlags srcStage, + vk::PipelineStageFlags dstStage, + vk::DependencyFlags deps, + std::vector const& memoryBarriers, + std::vector const& bufferMemoryBarriers, + std::vector const& imageMemoryBarriers + ) const noexcept + { + vkCommandBuffer.pipelineBarrier( + srcStage, + dstStage, + deps, + memoryBarriers, + bufferMemoryBarriers, + imageMemoryBarriers + ); + } + + bool SHVkCommandBuffer::IsReadyToSubmit(void) const noexcept + { + return cmdBufferState == SH_CMD_BUFFER_STATE::EXECUTABLE; + } + + void SHVkCommandBuffer::HandlePostSubmit(void) noexcept + { + SetState(SH_CMD_BUFFER_STATE::PENDING); + } + + void SHVkCommandBuffer::BeginLabeledSegment(const std::string& label) noexcept + { +#ifdef _DEBUG + static const std::array SEGMENT_COLOURS = + { + SHColour::LIGHTPINK, + SHColour::LIGHTBLUE, + SHColour::LIGHTGREEN, + SHColour::YELLOW, + SHColour::PINK, + SHColour::TEAL, + SHColour::LIME, + SHColour::ORANGE, + SHColour::VIOLET, + SHColour::MAROON, + SHColour::DARKGREEN, + SHColour::SANDYBROWN + }; + + const SHColour COLOR = SEGMENT_COLOURS[segmentDepth]; + ++segmentDepth; + if (segmentDepth >= static_cast(SEGMENT_COLOURS.size())) + segmentDepth = 0; + vkCommandBuffer.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT().setPLabelName(label.data()).setColor({ COLOR.x, COLOR.y, COLOR.z, COLOR.w })); +#endif + } + + void SHVkCommandBuffer::EndLabeledSegment() noexcept + { +#ifdef _DEBUG + vkCommandBuffer.endDebugUtilsLabelEXT(); + segmentDepth = std::max(segmentDepth - 1, 0); +#endif + } + + //void SHVkCommandBuffer::PipelineBarrier(vk::PipelineStageFlags ) const noexcept + //{ + // //vkCommandBuffer.pipelineBarrier() + //} + + void SHVkCommandBuffer::ForceSetPipelineLayout(Handle pipelineLayout, SH_PIPELINE_TYPE pipelineType) noexcept + { + bindPointData[static_cast(pipelineType)].boundPipelineLayoutHdl = pipelineLayout; } /***************************************************************************/ /*! - + + \brief + Calls vkCmdPushConstants and submits data stored in command buffer. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::SubmitPushConstants(SH_PIPELINE_TYPE bindPoint) const noexcept + { + auto layoutHdl = bindPointData[static_cast(bindPoint)].boundPipelineLayoutHdl; + vkCommandBuffer.pushConstants(layoutHdl->GetVkPipelineLayout(), + layoutHdl->GetPushConstantInterface().GetShaderStageFlags(), + 0, + layoutHdl->GetPushConstantInterface().GetSize(), pushConstantData); + } + + /***************************************************************************/ + /*! + \brief Simply returns the command buffer handle. - - \return + + \return The command buffer handle. - + */ /***************************************************************************/ vk::CommandBuffer const& SHVkCommandBuffer::GetVkCommandBuffer(void) const noexcept @@ -440,11 +590,11 @@ namespace SHADE /***************************************************************************/ /*! - + \brief - See https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetRenderAreaGranularity.html + See https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetRenderAreaGranularity.html or look up vkGetRenderAreaGranularity. - + \param renderpassHdl Renderpass to get info from. @@ -454,30 +604,28 @@ namespace SHADE \param renderArea For the comparison. Again, look it up on the webpage. - \return + \return If optimal, true. otherwise false. - + */ /***************************************************************************/ bool SHVkCommandBuffer::IsRenderAreaOptimal(Handle const& renderpassHdl, vk::Extent2D const& framebufferExtent, vk::Rect2D const& renderArea) const noexcept { vk::Extent2D granularity = parentPool->GetLogicalDevice()->GetVkLogicalDevice().getRenderAreaGranularity(renderpassHdl->GetVkRenderpass()); - return (renderArea.offset.x % granularity.width == 0 && renderArea.offset.y % granularity.height == 0 && - (renderArea.extent.width % granularity.width || renderArea.offset.x + renderArea.extent.width == framebufferExtent.width) && - (renderArea.extent.height % granularity.height || renderArea.offset.y + renderArea.extent.height == framebufferExtent.height)); + return (renderArea.offset.x % granularity.width == 0 && renderArea.offset.y % granularity.height == 0 && (renderArea.extent.width % granularity.width || renderArea.offset.x + renderArea.extent.width == framebufferExtent.width) && (renderArea.extent.height % granularity.height || renderArea.offset.y + renderArea.extent.height == framebufferExtent.height)); } /***************************************************************************/ /*! - + \brief Setter for the state of the command buffer. - + \param state - - \return - + + \return + */ /***************************************************************************/ void SHVkCommandBuffer::SetState(SH_CMD_BUFFER_STATE state) noexcept @@ -487,12 +635,12 @@ namespace SHADE /***************************************************************************/ /*! - + \brief Returns the state of the command buffer. - - \return - + + \return + */ /***************************************************************************/ SH_CMD_BUFFER_STATE SHVkCommandBuffer::GetState(void) const noexcept @@ -502,14 +650,14 @@ namespace SHADE /***************************************************************************/ /*! - + \brief Creates a command buffer. Cmd buffer can be primary or secondary. If secondary, flags will automatically have renderpass continue bit. Command pool used to create this command buffer will determine whether or not - this buffer will be allocated with + this buffer will be allocated with VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT using the reset mode. - + \param logicalDevice Need a logical device to create a buffer. @@ -518,7 +666,7 @@ namespace SHADE \param type Type of the command buffer; primary or secondary. - + */ /***************************************************************************/ SHVkCommandBuffer::SHVkCommandBuffer(Handle const& commandPool, SH_CMD_BUFFER_TYPE type) noexcept @@ -528,7 +676,8 @@ namespace SHADE , parentPoolResetMode{ SH_CMD_POOL_RESET::POOL_BASED } , usageFlags{} , commandBufferCount{ 0 } - , parentPool{commandPool} + , parentPool{ commandPool } + , pushConstantData{} { vk::CommandBufferAllocateInfo allocateInfo{}; @@ -568,54 +717,54 @@ namespace SHADE commandBufferType = type; commandBufferCount = allocateInfo.commandBufferCount; - if (parentPool->GetIsTransient ()) + if (parentPool->GetIsTransient()) usageFlags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; if (commandBufferType == SH_CMD_BUFFER_TYPE::SECONDARY) usageFlags |= vk::CommandBufferUsageFlagBits::eRenderPassContinue; - + // Reset all the push constant data to 0 - memset (pushConstantData, 0, PUSH_CONSTANT_SIZE); + memset(pushConstantData, 0, PUSH_CONSTANT_SIZE); } /***************************************************************************/ /*! - + \brief Move ctor. Invalidates Vulkan handles. - + \param rhs the other command buffer. - + */ /***************************************************************************/ SHVkCommandBuffer::SHVkCommandBuffer(SHVkCommandBuffer&& rhs) noexcept - : vkCommandBuffer {std::move (rhs.vkCommandBuffer)} - , cmdBufferState {rhs.cmdBufferState} - , commandBufferType {rhs.commandBufferType} - , parentPoolResetMode {rhs.parentPoolResetMode} - , usageFlags {rhs.usageFlags} - , commandBufferCount {rhs.commandBufferCount} - , parentPool {rhs.parentPool} - , boundPipelineLayoutHdl{rhs.boundPipelineLayoutHdl } + : vkCommandBuffer{ std::move(rhs.vkCommandBuffer) } + , cmdBufferState{ rhs.cmdBufferState } + , commandBufferType{ rhs.commandBufferType } + , parentPoolResetMode{ rhs.parentPoolResetMode } + , usageFlags{ rhs.usageFlags } + , commandBufferCount{ rhs.commandBufferCount } + , parentPool{ rhs.parentPool } + , bindPointData{ std::move (rhs.bindPointData)} { - memcpy (pushConstantData, rhs.pushConstantData, PUSH_CONSTANT_SIZE); + memcpy(pushConstantData, rhs.pushConstantData, PUSH_CONSTANT_SIZE); rhs.vkCommandBuffer = VK_NULL_HANDLE; } /***************************************************************************/ /*! - + \brief Move assignment operator. Invalidates Vulkan handles. - + \param rhs The other Vulkan Handle. - - \return + + \return a reference itself. - + */ /***************************************************************************/ SHVkCommandBuffer& SHVkCommandBuffer::operator=(SHVkCommandBuffer&& rhs) noexcept @@ -630,7 +779,7 @@ namespace SHADE usageFlags = rhs.usageFlags; commandBufferCount = rhs.commandBufferCount; parentPool = rhs.parentPool; - boundPipelineLayoutHdl = rhs.boundPipelineLayoutHdl; + bindPointData = std::move(rhs.bindPointData); memcpy(pushConstantData, rhs.pushConstantData, PUSH_CONSTANT_SIZE); rhs.vkCommandBuffer = VK_NULL_HANDLE; diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h index 08fc45f7..fc348487 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h @@ -4,8 +4,10 @@ #include "Graphics/SHVulkanIncludes.h" #include "Graphics/SHVulkanDefines.h" #include "SHCommandPoolResetMode.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" #include "Graphics/Pipeline/SHVkPipelineLayout.h" +#include "Graphics/Pipeline/SHPipelineType.h" +#include "Math/SHColour.h" namespace SHADE { @@ -15,6 +17,8 @@ namespace SHADE class SHVkFramebuffer; class SHVkPipeline; class SHVkBuffer; + class SHVkImage; + class SHVkDescriptorSetGroup; enum class SH_CMD_BUFFER_TYPE { @@ -34,10 +38,17 @@ namespace SHADE class SHVkCommandBuffer { friend class SHVkCommandPool; - friend class ResourceLibrary; + friend class SHResourceLibrary; static constexpr uint16_t PUSH_CONSTANT_SIZE = 512; + private: + struct PipelineBindPointData + { + //! The currently bound pipeline + Handle boundPipelineLayoutHdl; + }; + /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ @@ -64,11 +75,14 @@ namespace SHADE //! The command pool that this command buffer belongs to Handle parentPool; - //! The currently bound pipeline - Handle boundPipelineLayoutHdl; + //! Every command buffer will have a set of pipeline bind point specific data + std::array(SH_PIPELINE_TYPE::NUM_TYPES)> bindPointData; //! The push constant data for the command buffer - uint8_t pushConstantData[PUSH_CONSTANT_SIZE]; + uint8_t pushConstantData[PUSH_CONSTANT_SIZE]; + + //! Depth of segmenting of the command buffer (used for debug data) + int segmentDepth; /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER FUNCTIONS */ @@ -97,31 +111,60 @@ namespace SHADE void Reset(void); // Begins and Ends - void BeginRecording (void) noexcept; + void BeginRecording () noexcept; void EndRecording (void) noexcept; void BeginRenderpass (Handle const& renderpassHdl, Handle const& framebufferHdl, vk::Offset2D offset = {0, 0}, vk::Extent2D extent = {0, 0}) noexcept; void EndRenderpass (void) noexcept; + void NextSubpass (void) noexcept; // Dynamic State - void SetviewportScissor (float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX = 0.0f, float vpY = 0.0f, int32_t sX = 0.0f, int32_t sY = 0.0f, float vpMinDepth = 0.0f, float vpMaxDepth = 1.0f) noexcept; + void SetViewportScissor (float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX = 0.0f, float vpY = 0.0f, int32_t sX = 0.0f, int32_t sY = 0.0f, float vpMinDepth = 0.0f, float vpMaxDepth = 1.0f) noexcept; + void SetLineWidth (float lineWidth) noexcept; // Binding Commands void BindPipeline (Handle const& pipelineHdl) noexcept; void BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept; void BindIndexBuffer (Handle const& buffer, uint32_t startingIndex) const noexcept; + void BindDescriptorSet (Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets); // Draw Commands void DrawArrays (uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept; void DrawIndexed (uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept; + void DrawMultiIndirect (Handle indirectDrawData, uint32_t drawCount); + + // Compute Commands + void ComputeDispatch (uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ) noexcept; + + // Buffer Copy + void CopyBufferToImage (const vk::Buffer& src, const vk::Image& dst, const std::vector& copyInfo); + void CopyImageToBuffer (const vk::Image& src, const vk::Buffer& dst, const std::vector& copyInfo); + + // memory barriers + void PipelineBarrier ( + vk::PipelineStageFlags srcStage, + vk::PipelineStageFlags dstStage, + vk::DependencyFlags deps, + std::vector const& memoryBarriers, + std::vector const& bufferMemoryBarriers, + std::vector const& imageMemoryBarriers + ) const noexcept; + + bool IsReadyToSubmit (void) const noexcept; + void HandlePostSubmit (void) noexcept; + + // Debugging + void BeginLabeledSegment(const std::string& label) noexcept; + void EndLabeledSegment() noexcept; // Push Constant variable setting template - void SetPushConstantVariable(std::string variableName, T const& data) noexcept + void SetPushConstantVariable(std::string variableName, T const& data, SH_PIPELINE_TYPE bindPoint) noexcept { - memcpy (static_cast(pushConstantData) + boundPipelineLayoutHdl->GetPushConstantInterface().GetOffset(variableName), &data, sizeof (T)); + memcpy (static_cast(pushConstantData) + bindPointData[static_cast(bindPoint)].boundPipelineLayoutHdl->GetPushConstantInterface().GetOffset(variableName), &data, sizeof (T)); }; + void ForceSetPipelineLayout (Handle pipelineLayout, SH_PIPELINE_TYPE pipelineType) noexcept; - void SubmitPushConstants (void) const noexcept; + void SubmitPushConstants (SH_PIPELINE_TYPE bindPoint) const noexcept; /*-----------------------------------------------------------------------*/ /* GETTERS AND SETTERS */ diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp index 5cf4bea4..375ece4d 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp @@ -2,7 +2,7 @@ #include "SHVkCommandPool.h" #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Instance/SHVkInstance.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" #include "Tools/SHLogger.h" namespace SHADE @@ -102,8 +102,6 @@ namespace SHADE logicalDeviceHdl = rhs.logicalDeviceHdl; transient = rhs.transient; - static_cast&>(*this) = static_cast&>(rhs); - rhs.vkCommandPool = VK_NULL_HANDLE; return *this; @@ -150,20 +148,19 @@ namespace SHADE logicalDeviceHdl->GetVkLogicalDevice().resetCommandPool(vkCommandPool, vk::CommandPoolResetFlagBits::eReleaseResources); for (auto& primary : primaries) { - if (primary->GetState() != SH_CMD_BUFFER_STATE::PENDING) - primary->SetState(SH_CMD_BUFFER_STATE::INITIAL); - else - SHLOG_ERROR("Primary command buffer in pending state, could not reset. "); + // #NoteToSelf: Since there is no way to set the state of a command buffer back to initial, we just hard set it to initial. Ditto for secondaries. + //if (primary->GetState() != SH_CMD_BUFFER_STATE::PENDING) + primary->SetState(SH_CMD_BUFFER_STATE::INITIAL); + + // From the spec: Any primary command buffer allocated from another VkCommandPool that is in the recording or // executable state and has a secondary command buffer allocated from commandPool recorded into it, // becomes invalid. TODO: Might want to check and throw exception for these conditions after making sure this actually happens using validation layers. } for (auto& secondary : secondaries) { - if (secondary->GetState() != SH_CMD_BUFFER_STATE::PENDING) - secondary->SetState(SH_CMD_BUFFER_STATE::INITIAL); - else - SHLOG_ERROR("Secondary command buffer in pending state, could not reset. "); + //if (secondary->GetState() != SH_CMD_BUFFER_STATE::PENDING) + secondary->SetState(SH_CMD_BUFFER_STATE::INITIAL); // TODO: Ditto from TODO in primary check } diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h index 2bb290a7..fcc0ce41 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h @@ -6,7 +6,7 @@ #include "Graphics/Queues/SHVkQueue.h" #include "SHCommandPoolResetMode.h" #include "SHVkCommandBuffer.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" namespace SHADE { diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp index d5f0dfe0..3ca5c94d 100644 --- a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp +++ b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp @@ -52,7 +52,7 @@ namespace SHADE { vk::DebugUtilsMessengerCreateInfoEXT debMsgCreateInfo; InitializeDebugCreateInfo(debMsgCreateInfo, - GenMessengerSeverity(SH_DEBUG_MSG_SEV::S_VERBOSE, SH_DEBUG_MSG_SEV::S_WARNING, SH_DEBUG_MSG_SEV::S_ERROR), + GenMessengerSeverity(SH_DEBUG_MSG_SEV::S_INFO, SH_DEBUG_MSG_SEV::S_VERBOSE, SH_DEBUG_MSG_SEV::S_WARNING, SH_DEBUG_MSG_SEV::S_ERROR), GenMessengerType(SH_DEBUG_MSG_TYPE::T_GENERAL, SH_DEBUG_MSG_TYPE::T_VALIDATION, SH_DEBUG_MSG_TYPE::T_PERFORMANCE)); if (vk::Result result = SHVkInstance::GetVkInstance().createDebugUtilsMessengerEXT(&debMsgCreateInfo, nullptr, &debugMessenger); result != vk::Result::eSuccess) diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h index 577090a5..79a34353 100644 --- a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h +++ b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h @@ -8,10 +8,10 @@ namespace SHADE enum class SH_DEBUG_MSG_SEV : VkDebugUtilsMessageSeverityFlagsEXT { S_VERBOSE = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - S_INFO = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - S_WARNING = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - S_ERROR = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - S_FLAG = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + S_INFO = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, + S_WARNING = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, + S_ERROR = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + S_FLAG = VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT, }; enum class SH_DEBUG_MSG_TYPE : VkDebugUtilsMessageTypeFlagsEXT diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp index d63a65f7..fd39da24 100644 --- a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp +++ b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp @@ -59,7 +59,7 @@ namespace SHADE */ /***************************************************************************/ - void SHVulkanDebugUtil::ReportVkWarning(vk::Result vkResult, std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + void SHVulkanDebugUtil::ReportVkWarning(vk::Result vkResult, std::string_view message) noexcept { //std::cout << location.file_name() << ": " << location.function_name() << "|" << location.line() << "|" << // location.column() << "|: Warning: " << SHDebugUtil::VkResultToString(vkResult) << " | " << message << std::endl; @@ -88,17 +88,18 @@ namespace SHADE */ /***************************************************************************/ - void SHVulkanDebugUtil::ReportVkError(vk::Result vkResult, std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + void SHVulkanDebugUtil::ReportVkError(vk::Result vkResult, std::string_view message) noexcept { std::string toLogger = "Vulkan Warning: " + std::string(SHVulkanDebugUtil::VkResultToString(vkResult)) + " | " + std::string(message); SHLOGV_ERROR(toLogger); + std::cout << std::endl; } - void SHVulkanDebugUtil::ReportVkSuccess(std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + void SHVulkanDebugUtil::ReportVkSuccess(std::string_view message) noexcept { - SHLOGV_INFO(message); + //SHLOGV_INFO(message); } /***************************************************************************/ diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h index 7bf583bb..af4ca3ef 100644 --- a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h +++ b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h @@ -15,9 +15,9 @@ namespace SHADE public: static VKAPI_ATTR VkBool32 VKAPI_CALL GenericDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageSeverityFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData); - static void ReportVkWarning(vk::Result vkResult, std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; - static void ReportVkError(vk::Result vkResult, std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; - static void ReportVkSuccess(std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; + static void ReportVkWarning(vk::Result vkResult, std::string_view message) noexcept; + static void ReportVkError(vk::Result vkResult, std::string_view message) noexcept; + static void ReportVkSuccess(std::string_view message) noexcept; }; diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.cpp new file mode 100644 index 00000000..6cd7ddfd --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.cpp @@ -0,0 +1,62 @@ +#include "SHpch.h" +#include "SHDescriptorSetUpdater.h" + +namespace SHADE +{ + + SHDescriptorWriteInfo::SHDescriptorWriteInfo(SHDescriptorWriteInfo&& rhs) noexcept + : descImageInfos{ std::move(rhs.descImageInfos) } + , descBufferInfos{ std::move(rhs.descBufferInfos) } + , descTexelBufferInfos{std::move (rhs.descTexelBufferInfos)} + { + + } + + SHDescriptorWriteInfo::SHDescriptorWriteInfo(void) noexcept + : descImageInfos{} + , descBufferInfos{} + , descTexelBufferInfos{} + { + + } + + + SHDescriptorWriteInfo& SHDescriptorWriteInfo::operator=(SHDescriptorWriteInfo&& rhs) noexcept + { + if (&rhs == this) + return *this; + + descImageInfos = std::move(rhs.descImageInfos); + descBufferInfos = std::move(rhs.descBufferInfos); + descTexelBufferInfos = std::move(rhs.descTexelBufferInfos); + + return *this; + } + + + SHDescriptorSetUpdater::SHDescriptorSetUpdater(SHDescriptorSetUpdater&& rhs) noexcept + : writeInfos{ std::move(rhs.writeInfos) } + , writeHashMap {std::move (rhs.writeHashMap)} + { + + } + + SHDescriptorSetUpdater::SHDescriptorSetUpdater(void) noexcept + : writeInfos{} + , writeHashMap{} + { + + } + + SHDescriptorSetUpdater& SHDescriptorSetUpdater::operator=(SHDescriptorSetUpdater&& rhs) noexcept + { + if (&rhs == this) + return *this; + + writeInfos = std::move (rhs.writeInfos); + writeHashMap = std::move (rhs.writeHashMap); + + return *this; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.h b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.h new file mode 100644 index 00000000..8595d2a0 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorSetUpdater.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/Shaders/SHShaderReflected.h" + +namespace SHADE +{ + // Vulkan doesn't use all of the information when looking at a writeDescriptorSet. It all + // depends on the descriptor type. This struct plays it safe by having members that would + // accommodate all types of descriptors. + class SHDescriptorWriteInfo + { + //! When we want to update a descriptor that is an image, it goes in here + std::vector descImageInfos; + + //! When we want to update a descriptor that is a buffer, it goes in here + std::vector descBufferInfos; + + //! When we want to update a descriptor that is an texel buffer, it goes in here + std::vector descTexelBufferInfos; + + public: + SHDescriptorWriteInfo (void) noexcept; + SHDescriptorWriteInfo (SHDescriptorWriteInfo&& rhs) noexcept; + SHDescriptorWriteInfo& operator= (SHDescriptorWriteInfo&& rhs) noexcept; + + friend class SHVkDescriptorSetGroup; + friend class SHDescriptorSetUpdater; + }; + + class SHDescriptorSetUpdater + { + private: + //! When we want to update descriptor sets, this will get passed into vkUpdateDescriptorSets. + //! Each write will correspond to a binding from a set. If the binding is a variable + //! sized binding, pImageInfo (e.g.) will point to an array of vk::DescriptorImageInfo. + std::vector writeInfos; + + //! When we want to update a write, we need to use this to identify the index of the write. + std::unordered_map writeHashMap; + + + public: + SHDescriptorSetUpdater (void) noexcept; + SHDescriptorSetUpdater(SHDescriptorSetUpdater&& rhs) noexcept; + SHDescriptorSetUpdater& operator= (SHDescriptorSetUpdater&& rhs) noexcept; + + public: + friend class SHVkDescriptorSetGroup; + }; +} + diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp index 87d43255..e5618c9c 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp @@ -7,32 +7,52 @@ namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Constructor/Destructor */ - /*---------------------------------------------------------------------------------*/ - SHVkDescriptorPool::SHVkDescriptorPool(Handle device, const Config& config) - : device { device } + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHVkDescriptorPool::SHVkDescriptorPool(Handle device, const Config& config) + : device{ device } + { + // Create the Pool + const vk::DescriptorPoolCreateInfo POOL_CREATE_INFO { - // Create the Pool - const vk::DescriptorPoolCreateInfo POOL_CREATE_INFO - { - .flags = config.Flags, - .maxSets = config.MaxSets, - .poolSizeCount = static_cast(config.Limits.size()), - .pPoolSizes = config.Limits.data() - }; - pool = device->GetVkLogicalDevice().createDescriptorPool(POOL_CREATE_INFO); - } + .flags = config.Flags, + .maxSets = config.MaxSets, + .poolSizeCount = static_cast(config.Limits.size()), + .pPoolSizes = config.Limits.data() + }; + pool = device->GetVkLogicalDevice().createDescriptorPool(POOL_CREATE_INFO); + } - SHVkDescriptorPool::~SHVkDescriptorPool() noexcept - { - if (pool) - device->GetVkLogicalDevice().destroyDescriptorPool(pool); - } + SHVkDescriptorPool::SHVkDescriptorPool(SHVkDescriptorPool&& rhs) noexcept + : ISelfHandle (rhs) + , device{ rhs.device } + , pool{ rhs.pool } + { + rhs.pool = VK_NULL_HANDLE; + } - std::vector> SHVkDescriptorPool::Allocate(const std::vector>& layouts, std::vector const& variableDescCounts) - { - SHVkInstance::GetResourceManager().Create(device, GetHandle(), layouts, variableDescCounts); - return {}; - } + SHVkDescriptorPool::~SHVkDescriptorPool() noexcept + { + if (pool) + device->GetVkLogicalDevice().destroyDescriptorPool(pool); + } + + SHVkDescriptorPool& SHVkDescriptorPool::operator=(SHVkDescriptorPool&& rhs) noexcept + { + if (&rhs == this) + return *this; + + device = rhs.device; + pool = rhs.pool; + + rhs.pool = VK_NULL_HANDLE; + + return *this; + } + + Handle SHVkDescriptorPool::Allocate(const std::vector>& layouts, std::vector const& variableDescCounts) + { + return SHVkInstance::GetResourceManager().Create(device, GetHandle(), layouts, variableDescCounts); + } } diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h index c3059b8b..c822829a 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h @@ -2,7 +2,7 @@ // Project Includes #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { @@ -39,7 +39,10 @@ namespace SHADE std::vector Limits = { { vk::DescriptorType::eCombinedImageSampler, 100 }, - { vk::DescriptorType::eUniformBuffer, 100 } + { vk::DescriptorType::eUniformBuffer, 100 }, + { vk::DescriptorType::eUniformBufferDynamic, 100 }, + { vk::DescriptorType::eStorageImage, 100}, + { vk::DescriptorType::eStorageBufferDynamic, 100 } }; /// /// Maximum number of descriptor sets allowed @@ -65,7 +68,7 @@ namespace SHADE /// SHVkDescriptorPool(Handle device, const Config& config = {}); SHVkDescriptorPool(const SHVkDescriptorPool&) = delete; - SHVkDescriptorPool(SHVkDescriptorPool&& rhs) noexcept = default; + SHVkDescriptorPool(SHVkDescriptorPool&& rhs) noexcept; /// /// Destructor which will unload and deallocate all resources for this Pool. /// @@ -75,7 +78,7 @@ namespace SHADE /* Overloaded Operators */ /*-----------------------------------------------------------------------------*/ SHVkDescriptorPool& operator=(const SHVkDescriptorPool&) = delete; - SHVkDescriptorPool& operator=(SHVkDescriptorPool&& rhs) noexcept = default; + SHVkDescriptorPool& operator=(SHVkDescriptorPool&& rhs) noexcept; /*-----------------------------------------------------------------------------*/ /* Getter Functions */ @@ -101,7 +104,7 @@ namespace SHADE /// Handles to the created Descriptor Sets. If this DescriptorPool has run out of /// space, lesser number of Handles will be returned. /// - std::vector> Allocate(const std::vector>& layouts, std::vector const& variableDescCounts); + Handle Allocate(const std::vector>& layouts, std::vector const& variableDescCounts); private: /*-----------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp index 6bdc5601..de68c583 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp @@ -7,6 +7,11 @@ #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" #include "Tools/SHLogger.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Images/SHVkSampler.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/SHVkUtil.h" namespace SHADE { @@ -39,17 +44,24 @@ namespace SHADE : device{deviceHdl} , descPool {pool} , descSets{} + , layoutsUsed {layouts} { // Create the layout for each concurrent frame std::vector vkLayouts{ layouts.size() }; - for (auto& layout : layouts) + + //for (auto& layout : layouts) + for (uint32_t i = 0; i < layouts.size(); ++i) { - vkLayouts.push_back(layout->GetVkHandle()); + vkLayouts[i] = layouts[i]->GetVkHandle(); + setIndexing.emplace(layouts[i]->GetSetIndex(), i); } // Check for variable descriptor count if (variableDescCounts.size() != layouts.size()) + { SHLOG_ERROR("Number of variable descriptor counts does not match number of layouts. If a layout does not use variable counts, pass in 0. "); + return; + } // Prepare variable descriptor counts vk::DescriptorSetVariableDescriptorCountAllocateInfo variableAllocInfo{}; @@ -67,6 +79,54 @@ namespace SHADE // allocate descriptor sets descSets = device->GetVkLogicalDevice().allocateDescriptorSets(DESC_SET_LAYOUT_CREATE_INFO); + + // Now we want to prepare the write descriptor sets info for writing later. + for (uint32_t i = 0; i < layouts.size(); ++i) + { + auto const& bindings = layouts[i]->GetBindings(); + for (auto& binding : bindings) + { + BindingAndSetHash writeHash = binding.BindPoint; + writeHash |= static_cast(layouts[i]->GetSetIndex()) << 32; + + // new write for the binding + updater.writeInfos.emplace_back(); + updater.writeHashMap.try_emplace(writeHash, static_cast(updater.writeInfos.size()) - 1u); + auto& writeInfo = updater.writeInfos.back(); + + // Descriptor count for the write descriptor set. Usually this is set to 1, but if binding is variable sized, set to info passed in + uint32_t descriptorCount = (binding.flags & vk::DescriptorBindingFlagBits::eVariableDescriptorCount) ? variableDescCounts[i] : 1; + + switch (binding.Type) + { + //case vk::DescriptorType::eSampler: + //case vk::DescriptorType::eSampledImage: + case vk::DescriptorType::eCombinedImageSampler: + case vk::DescriptorType::eStorageImage: + case vk::DescriptorType::eInputAttachment: + writeInfo.descImageInfos.resize(descriptorCount); + break; + case vk::DescriptorType::eUniformTexelBuffer: + case vk::DescriptorType::eStorageTexelBuffer: + case vk::DescriptorType::eUniformBuffer: + case vk::DescriptorType::eUniformBufferDynamic: + case vk::DescriptorType::eStorageBuffer: + case vk::DescriptorType::eStorageBufferDynamic: + writeInfo.descBufferInfos.resize (descriptorCount); + break; + //case vk::DescriptorType::eUniformBufferDynamic: + // break; + //case vk::DescriptorType::eStorageBufferDynamic: + // break; + //case vk::DescriptorType::eInputAttachment: + // break; + //case vk::DescriptorType::eInlineUniformBlock: + // break; + default: + break; + } + } + } } /***************************************************************************/ @@ -82,4 +142,114 @@ namespace SHADE if (!descSets.empty()) device->GetVkLogicalDevice().freeDescriptorSets(descPool->GetVkHandle(), descSets); } + + /***************************************************************************/ + /*! + + \brief + Modifies a descriptor write info. #NoteToSelf: This function does NOT + need to modify the writeDescSets. Those are already linked before. + + \param imageViewsAndSamplers + Image and view samplers + + */ + /***************************************************************************/ + void SHVkDescriptorSetGroup::ModifyWriteDescImage(uint32_t set, uint32_t binding, std::span, Handle, vk::ImageLayout>> const& imageViewsAndSamplers) noexcept + { + // Find the target writeDescSet + BindingAndSetHash writeHash = binding; + writeHash |= static_cast(set) << 32; + auto& writeInfo = updater.writeInfos[updater.writeHashMap.at(writeHash)]; + + if (imageViewsAndSamplers.size() > writeInfo.descImageInfos.size()) + { + SHLOG_ERROR("Attempting write too many descriptors into descriptor set. Failed to write to vk::WriteDescriptorSet. "); + return; + } + + for (uint32_t i = 0; i < imageViewsAndSamplers.size(); ++i) + { + // write sampler and image view + auto& [view, sampler, layout] = imageViewsAndSamplers[i]; + writeInfo.descImageInfos[i].imageView = view->GetImageView(); + writeInfo.descImageInfos[i].sampler = sampler ? sampler->GetVkSampler() : nullptr; + writeInfo.descImageInfos[i].imageLayout = layout; + } + } + + void SHVkDescriptorSetGroup::ModifyWriteDescBuffer(uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept + { + // Find the target writeDescSet + BindingAndSetHash writeHash = binding; + writeHash |= static_cast(set) << 32; + auto& writeInfo = updater.writeInfos[updater.writeHashMap.at(writeHash)]; + + if (buffers.size() > writeInfo.descBufferInfos.size()) + { + SHLOG_ERROR("Attempting write too many descriptors into descriptor set. Failed to write to vk::WriteDescriptorSet. "); + } + + for (uint32_t i = 0; i < buffers.size(); ++i) + { + // write sampler and image view + auto& buffer = buffers[i]; + writeInfo.descBufferInfos[i].buffer = buffer->GetVkBuffer(); + writeInfo.descBufferInfos[i].offset = offset; + writeInfo.descBufferInfos[i].range = range; + } + + } + + void SHVkDescriptorSetGroup::UpdateDescriptorSetImages(uint32_t set, uint32_t binding) noexcept + { + vk::WriteDescriptorSet writeDescSet{}; + + // Get binding + set hash + BindingAndSetHash bsHash = SHVkUtil::GenBindingSetHash(set, binding); + + // to index a set + uint32_t setIndex = setIndexing[set]; + + // to index a write for a binding + uint32_t writeInfoIndex = updater.writeHashMap[bsHash]; + + // Initialize info for write + writeDescSet.descriptorType = layoutsUsed[setIndex]->GetBindings()[binding].Type; + writeDescSet.dstArrayElement = 0; + writeDescSet.dstSet = descSets[setIndex]; + writeDescSet.dstBinding = binding; + + writeDescSet.pImageInfo = updater.writeInfos[writeInfoIndex].descImageInfos.data(); + writeDescSet.descriptorCount = static_cast(updater.writeInfos[writeInfoIndex].descImageInfos.size()); + + device->UpdateDescriptorSet(writeDescSet); + } + + void SHVkDescriptorSetGroup::UpdateDescriptorSetBuffer(uint32_t set, uint32_t binding) noexcept + { + vk::WriteDescriptorSet writeDescSet{}; + + // Get binding + set hash + BindingAndSetHash bsHash = SHVkUtil::GenBindingSetHash(set, binding); + + // to index a set + uint32_t setIndex = setIndexing[set]; + + // to index a write for a binding + uint32_t writeInfoIndex = updater.writeHashMap[bsHash]; + + // Initialize info for write + writeDescSet.descriptorType = layoutsUsed[setIndex]->GetBindings()[binding].Type; + writeDescSet.dstArrayElement = 0; + writeDescSet.dstSet = descSets[setIndex]; + writeDescSet.dstBinding = binding; + + writeDescSet.pBufferInfo = updater.writeInfos[writeInfoIndex].descBufferInfos.data(); + writeDescSet.descriptorCount = static_cast(updater.writeInfos[writeInfoIndex].descBufferInfos.size()); + + device->UpdateDescriptorSet(writeDescSet); + + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h index b95859bb..3f42afcc 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h @@ -1,71 +1,110 @@ - #pragma once +#pragma once + +#include // Project Includes #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" +#include "Graphics/Shaders/SHShaderReflected.h" +#include "SHDescriptorSetUpdater.h" namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*---------------------------------------------------------------------------------*/ - class SHVkLogicalDevice; - class SHVkDescriptorPool; - class SHVkDescriptorSetLayout; + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkDescriptorPool; + class SHVkDescriptorSetLayout; + class SHVkSampler; + class SHVkImage; + class SHVkImageView; + class SHVkBuffer; - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// + /// + class SHVkDescriptorSetGroup + { + public: + using viewSamplerLayout = std::tuple, Handle, vk::ImageLayout>; + + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ /// - /// + /// Constructs a Descriptor Set with the specified layout using the specified + /// pool meant for use with the specified surface. This Set will be created with + /// multiple Vulkan Descriptor Set objects based on the max number of concurrent + /// frames for the specified surface. /// - class SHVkDescriptorSetGroup - { - public: - /*-----------------------------------------------------------------------------*/ - /* Constructor/Destructors */ - /*-----------------------------------------------------------------------------*/ - /// - /// Constructs a Descriptor Set with the specified layout using the specified - /// pool meant for use with the specified surface. This Set will be created with - /// multiple Vulkan Descriptor Set objects based on the max number of concurrent - /// frames for the specified surface. - /// - /// Vulkan logical device used to create the Set. - /// Descriptor Pool used to create the Set. - /// Descriptor Set Layout to create the Set with. - SHVkDescriptorSetGroup(Handle deviceHdl, Handle pool, - std::vector> const& layouts, - std::vector const& variableDescCounts); - SHVkDescriptorSetGroup(const SHVkDescriptorSetGroup&) = delete; - SHVkDescriptorSetGroup(SHVkDescriptorSetGroup&& rhs) noexcept = default; - /// - /// Destructor which will unload and deallocate all resources for this Descriptor Set. - /// - ~SHVkDescriptorSetGroup() noexcept; + /// Vulkan logical device used to create the Set. + /// Descriptor Pool used to create the Set. + /// Descriptor Set Layout to create the Set with. + SHVkDescriptorSetGroup(Handle deviceHdl, Handle pool, + std::vector> const& layouts, + std::vector const& variableDescCounts); + SHVkDescriptorSetGroup(const SHVkDescriptorSetGroup&) = delete; + SHVkDescriptorSetGroup(SHVkDescriptorSetGroup&& rhs) noexcept = default; + /// + /// Destructor which will unload and deallocate all resources for this Descriptor Set. + /// + ~SHVkDescriptorSetGroup() noexcept; - /*-----------------------------------------------------------------------------*/ - /* Overloaded Operators */ - /*-----------------------------------------------------------------------------*/ - SHVkDescriptorSetGroup& operator=(const SHVkDescriptorSetGroup&) = delete; - SHVkDescriptorSetGroup& operator=(SHVkDescriptorSetGroup&& rhs) noexcept = default; + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetGroup& operator=(const SHVkDescriptorSetGroup&) = delete; + SHVkDescriptorSetGroup& operator=(SHVkDescriptorSetGroup&& rhs) noexcept = default; - /*-----------------------------------------------------------------------------*/ - /* Getter Functions */ - /*-----------------------------------------------------------------------------*/ - /// - /// Retrieves the handle to the Vulkan Descriptor Set handle. - /// - /// Handle to the Vulkan Descriptor Set. - [[nodiscard]] - inline const std::vector& GetVkHandle() { return descSets; } + /*-----------------------------------------------------------------------------*/ + /* Public member functions */ + /*-----------------------------------------------------------------------------*/ + void UpdateDescriptorSetImages(uint32_t set, uint32_t binding) noexcept; + void UpdateDescriptorSetBuffer(uint32_t set, uint32_t binding) noexcept; - private: - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - Handle device; - Handle descPool; - std::vector descSets; - }; + void ModifyWriteDescImage(uint32_t set, uint32_t binding, std::span, Handle, vk::ImageLayout>> const& imageViewsAndSamplers) noexcept; + void ModifyWriteDescBuffer (uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept; + + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the handle to the Vulkan Descriptor Set handle. + /// + /// Handle to the Vulkan Descriptor Set. + [[nodiscard]] + inline const std::vector& GetVkHandle() { return descSets; } + inline const uint32_t GetNumDescriptorSets(void) const noexcept { return static_cast(descSets.size()); } + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + //! Device required to allocate descriptor sets + Handle device; + + //! Descriptor pool to allocate descriptor sets + Handle descPool; + + //! Sometimes when we pass in a layout, the set of the layout used in the + //! shader cannot be used to index into descSets. This is to mitigate that issue + //! when we update descriptor sets. + std::unordered_map setIndexing; + + //! Descriptor sets + std::vector descSets; + + //! Layouts used to create this descriptor set group + std::vector> layoutsUsed; + + //! for updating descriptor sets. We want to cache this so that we don't create the + //! write structs at runtime. + SHDescriptorSetUpdater updater; + + }; } diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp index 36eaa8e8..4be8cc9e 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp @@ -1,15 +1,18 @@ #include "SHPch.h" #include "SHVkDescriptorSetLayout.h" #include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Images/SHVkSampler.h" namespace SHADE { /*---------------------------------------------------------------------------------*/ /* Constructor/Destructor */ /*---------------------------------------------------------------------------------*/ - SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(Handle device, const std::vector& bindings) + SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(Handle device, SetIndex set, const std::vector& bindings, bool genImmutableSamplers/* = false*/) : device { device } , layoutDesc { bindings } + , setIndex {set} + , immutableSampler{} { // Check if auto-binding point calculation configuration is valid bool autoCalc = false; @@ -25,6 +28,25 @@ namespace SHADE } } + vk::Sampler tempVkSampler = nullptr; + if (genImmutableSamplers) + { + // Create sampler + immutableSampler = device->CreateSampler( + { + .minFilter = vk::Filter::eLinear, + .magFilter = vk::Filter::eLinear, + .addressMode = vk::SamplerAddressMode::eRepeat, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .minLod = -1000, + .maxLod = 1000 + } + ); + + tempVkSampler = immutableSampler->GetVkSampler(); + } + + // Fill up VK bindings with auto calculated bind points if needed std::vector layoutBindings; layoutBindings.reserve(bindings.size()); @@ -38,7 +60,7 @@ namespace SHADE .descriptorType = binding.Type, .descriptorCount = binding.DescriptorCount, .stageFlags = binding.Stage, - .pImmutableSamplers = nullptr // We will create our own samplers + .pImmutableSamplers = genImmutableSamplers ? &tempVkSampler : nullptr, }; layoutBindings.emplace_back(VK_BINDING); @@ -74,6 +96,8 @@ namespace SHADE : device {rhs.device} , setLayout {rhs.setLayout} , layoutDesc{std::move (rhs.layoutDesc)} + , setIndex{ rhs.setIndex } + , immutableSampler{ rhs.immutableSampler } { rhs.setLayout = VK_NULL_HANDLE; } @@ -85,6 +109,28 @@ namespace SHADE device->GetVkLogicalDevice().destroyDescriptorSetLayout(setLayout); } + std::vector const& SHVkDescriptorSetLayout::GetBindings(void) const noexcept + { + return layoutDesc; + } + + SetIndex SHVkDescriptorSetLayout::GetSetIndex(void) const noexcept + { + return setIndex; + } + + uint32_t SHVkDescriptorSetLayout::GetNumDynamicOffsetsRequired(void) const noexcept + { + uint32_t numDynamicBindings = 0; + for (auto& binding : layoutDesc) + { + if (binding.Type == vk::DescriptorType::eUniformBufferDynamic || binding.Type == vk::DescriptorType::eStorageBufferDynamic) + ++numDynamicBindings; + } + + return numDynamicBindings; + } + SHVkDescriptorSetLayout& SHVkDescriptorSetLayout::operator=(SHVkDescriptorSetLayout&& rhs) noexcept { if (&rhs == this) @@ -93,6 +139,8 @@ namespace SHADE device = rhs.device; setLayout = rhs.setLayout; layoutDesc = std::move(rhs.layoutDesc); + setIndex = rhs.setIndex; + immutableSampler = rhs.immutableSampler; rhs.setLayout = VK_NULL_HANDLE; diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h index 590fd787..caa3c057 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h @@ -2,7 +2,7 @@ // Project Includes #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { @@ -10,6 +10,7 @@ namespace SHADE /* Forward Declarations */ /*---------------------------------------------------------------------------------*/ class SHVkLogicalDevice; + class SHVkSampler; /*---------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -74,7 +75,7 @@ namespace SHADE /// /// /// - SHVkDescriptorSetLayout(Handle device, const std::vector& bindings); + SHVkDescriptorSetLayout(Handle device, SetIndex setIndex, const std::vector& bindings, bool genImmutableSamplers = false); SHVkDescriptorSetLayout(const SHVkDescriptorSetLayout&) = delete; SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept; /// @@ -96,6 +97,9 @@ namespace SHADE /// /// Handle to the Vulkan Descriptor Set Layout handle. inline const vk::DescriptorSetLayout& GetVkHandle() const { return setLayout; } + std::vector const& GetBindings (void) const noexcept; + SetIndex GetSetIndex (void) const noexcept; + uint32_t GetNumDynamicOffsetsRequired (void) const noexcept; private: /*-----------------------------------------------------------------------------*/ @@ -104,5 +108,7 @@ namespace SHADE Handle device; vk::DescriptorSetLayout setLayout; std::vector layoutDesc; // Stores description of the layout + SetIndex setIndex; // Index of the set + Handle immutableSampler; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp index aa442805..808ce750 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp @@ -15,6 +15,8 @@ #include "Graphics/Pipeline/SHVkPipeline.h" #include "Graphics/Framebuffer/SHVkFramebuffer.h" #include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Images/SHVkSampler.h" namespace SHADE { @@ -84,6 +86,17 @@ namespace SHADE } + uint32_t SHVkLogicalDevice::ComputeAlignedBufferSize(uint32_t originalSize, size_t alignmentSize) const noexcept + { + uint32_t alignedSize = originalSize; + //uint32_t minBuffer + if (alignmentSize > 0) + { + alignedSize = (alignedSize + static_cast(alignmentSize) - 1) & ~(alignmentSize - 1); + } + return alignedSize; + } + /***************************************************************************/ /*! @@ -173,16 +186,23 @@ namespace SHADE vk::PhysicalDeviceFeatures features{}; // ADD MORE FEATURES HERE IF NEEDED // point and lines fill mode - features.fillModeNonSolid = true; + features.fillModeNonSolid = VK_TRUE; features.samplerAnisotropy = VK_TRUE; + features.multiDrawIndirect = VK_TRUE; + features.independentBlend = VK_TRUE; // for wide lines features.wideLines = true; + vk::PhysicalDeviceDescriptorIndexingFeatures descIndexingFeature{}; + descIndexingFeature.descriptorBindingVariableDescriptorCount = true; + descIndexingFeature.shaderSampledImageArrayNonUniformIndexing = true; + descIndexingFeature.runtimeDescriptorArray = true; + // Prepare to create the device vk::DeviceCreateInfo deviceCreateInfo { - .pNext = nullptr, + .pNext = &descIndexingFeature, .queueCreateInfoCount = static_cast(queueCreateInfos.size()), .pQueueCreateInfos = queueCreateInfos.data(), .enabledLayerCount = 0, // deprecated and ignored @@ -200,25 +220,13 @@ namespace SHADE else { SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Logical Device. "); + SET_VK_OBJ_NAME(this, vk::ObjectType::eDevice, vkLogicalDevice, "Logical Device"); } InitializeVMA(); - // TODO: Create pipeline caches - // TODO: Create Descriptor pools - //auto poolSizes = std::array - //{ - // SHDescriptorPoolSize {SHDescriptorType::COMBINED_SAMPLER, 1000} // hard coded descriptor count - //}; - - //SHDescriptorPoolParams poolParams - //{ - // .poolSizes = poolSizes, - // .maxDescriptorSets = 1000, - //}; - - //descriptorPool.Initialize(*this, poolParams); - //deviceStorage.Init(*this, queueFamilyIndices.indices[static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::GRAPHICS)].value()); + uboBufferMemoryAlignment = parentPhysicalDeviceHdl->GetDeviceProperties().limits.minUniformBufferOffsetAlignment; + ssboBufferMemoryAlignment = parentPhysicalDeviceHdl->GetDeviceProperties().limits.minStorageBufferOffsetAlignment; } SHVkLogicalDevice::SHVkLogicalDevice(SHVkLogicalDevice&& rhs) noexcept @@ -227,6 +235,8 @@ namespace SHADE , vmaAllocator{rhs.vmaAllocator} , nonDedicatedBestIndex {0} , parentPhysicalDeviceHdl {rhs.parentPhysicalDeviceHdl} + , uboBufferMemoryAlignment{ 0 } + , ssboBufferMemoryAlignment{ 0 } { rhs.vkLogicalDevice = VK_NULL_HANDLE; } @@ -245,6 +255,24 @@ namespace SHADE vkLogicalDevice.destroy(nullptr); } + SHVkLogicalDevice& SHVkLogicalDevice::operator=(SHVkLogicalDevice&& rhs) noexcept + { + if (this == &rhs) + return *this; + + vkLogicalDevice = std::move (rhs.vkLogicalDevice); + queueFamilyIndices = std::move (rhs.queueFamilyIndices); + vmaAllocator = rhs.vmaAllocator; + nonDedicatedBestIndex = 0; + parentPhysicalDeviceHdl = rhs.parentPhysicalDeviceHdl; + uboBufferMemoryAlignment = rhs.uboBufferMemoryAlignment; + ssboBufferMemoryAlignment = rhs.ssboBufferMemoryAlignment; + + rhs.vkLogicalDevice = VK_NULL_HANDLE; + + return *this; + } + /***************************************************************************/ /*! @@ -295,6 +323,16 @@ namespace SHADE return VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM; } + uint32_t SHVkLogicalDevice::PadUBOSize(uint32_t originalSize) const noexcept + { + return ComputeAlignedBufferSize(originalSize, uboBufferMemoryAlignment); + } + + uint32_t SHVkLogicalDevice::PadSSBOSize(uint32_t originalSize) const noexcept + { + return ComputeAlignedBufferSize(originalSize, ssboBufferMemoryAlignment); + } + /***************************************************************************/ /*! @@ -382,9 +420,9 @@ namespace SHADE */ /***************************************************************************/ - Handle SHVkLogicalDevice::CreateBuffer(uint32_t inSize, void* data, uint32_t srcSize, vk::BufferUsageFlags bufferUsage, VmaMemoryUsage memUsage, VmaAllocationCreateFlags allocFlags) const noexcept + Handle SHVkLogicalDevice::CreateBuffer(uint32_t inSize, void* data, uint32_t srcSize, vk::BufferUsageFlags bufferUsage, VmaMemoryUsage memUsage, VmaAllocationCreateFlags allocFlags, const std::string& name) const noexcept { - return SHVkInstance::GetResourceManager().Create(inSize, data, srcSize, std::cref(vmaAllocator), bufferUsage, memUsage, allocFlags); + return SHVkInstance::GetResourceManager().Create(GetHandle(), inSize, data, srcSize, std::cref(vmaAllocator), bufferUsage, name, memUsage, allocFlags); } /***************************************************************************/ @@ -418,7 +456,12 @@ namespace SHADE /***************************************************************************/ Handle SHVkLogicalDevice::CreateImage(uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) const noexcept { - return SHVkInstance::GetResourceManager().Create(std::cref(vmaAllocator), w, h, levels, format, usage, create); + return SHVkInstance::GetResourceManager().Create(GetHandle(), &vmaAllocator, w, h, levels, format, usage, create); + } + + Handle SHVkLogicalDevice::CreateImage(SHImageCreateParams const& imageDetails, unsigned char* data, uint32_t dataSize, std::span inMipOffsets, VmaMemoryUsage memUsage, VmaAllocationCreateFlags allocFlags) noexcept + { + return SHVkInstance::GetResourceManager().Create(GetHandle(), &vmaAllocator, imageDetails, data, dataSize, inMipOffsets, memUsage, allocFlags); } /***************************************************************************/ @@ -460,7 +503,12 @@ namespace SHADE */ /***************************************************************************/ - Handle SHVkLogicalDevice::CreatePipelineLayout(SHPipelineLayoutParams& pipelineLayoutParams) noexcept + Handle SHVkLogicalDevice::CreatePipelineLayout(SHPipelineLayoutParams const& pipelineLayoutParams) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutParams); + } + + Handle SHVkLogicalDevice::CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy const& pipelineLayoutParams) noexcept { return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutParams); } @@ -481,12 +529,22 @@ namespace SHADE */ /***************************************************************************/ - Handle SHVkLogicalDevice::CreatePipeline(Handle const& pipelineLayoutHdl, SHVkPipelineState const* const state, Handle const& renderpassHdl, uint32_t subpass, SH_PIPELINE_TYPE type) noexcept + Handle SHVkLogicalDevice::CreateGraphicsPipeline(Handle const& pipelineLayoutHdl, SHVkPipelineState const* const state, Handle const& renderpassHdl, Handle subpass) noexcept { - return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutHdl, state, renderpassHdl, subpass, type); + return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutHdl, state, renderpassHdl, subpass); } + Handle SHVkLogicalDevice::CreateComputePipeline(Handle const& pipelineLayoutHdl) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutHdl); + } + + Handle SHVkLogicalDevice::CreateSampler(const SHVkSamplerParams& params) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), params); + } + Handle SHVkLogicalDevice::CreateRenderpass(std::span const vkDescriptions, std::vector const& subpasses) noexcept { return SHVkInstance::GetResourceManager().Create (GetHandle(), vkDescriptions, subpasses); @@ -503,10 +561,22 @@ namespace SHADE } - Handle SHVkLogicalDevice::CreateDescriptorSetLayout(std::vector const& bindings) noexcept + Handle SHVkLogicalDevice::CreateDescriptorSetLayout(SetIndex setIndex, std::vector const& bindings, bool genImmutableSamplers/* = false*/) noexcept { - return SHVkInstance::GetResourceManager().Create (GetHandle(), bindings); + return SHVkInstance::GetResourceManager().Create (GetHandle(), setIndex, bindings, genImmutableSamplers); + } + Handle SHVkLogicalDevice::CreateDescriptorPools(const SHVkDescriptorPool::Config& config /*= {}*/) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), config); + + } + + Handle SHVkLogicalDevice::CreateDescriptorSetGroup(Handle pool, + std::vector> const& layouts, + std::vector const& variableDescCounts) noexcept + { + return SHVkInstance::GetResourceManager().Create(GetHandle(), pool, layouts, variableDescCounts); } /***************************************************************************/ @@ -541,6 +611,27 @@ namespace SHADE return SHVkInstance::GetResourceManager().Create(GetHandle()); } + /***************************************************************************/ + /*! + + \brief + Writes to descriptor sets. + + \param writeDescSets + Descriptor sets to write to. + + */ + /***************************************************************************/ + void SHVkLogicalDevice::UpdateDescriptorSets(std::vector const& writeDescSets) noexcept + { + vkLogicalDevice.updateDescriptorSets(writeDescSets, {}); + } + + void SHVkLogicalDevice::UpdateDescriptorSet(vk::WriteDescriptorSet const& writeDescSet) noexcept + { + vkLogicalDevice.updateDescriptorSets(1, &writeDescSet, 0, {}); + } + /***************************************************************************/ /*! @@ -629,6 +720,11 @@ namespace SHADE return static_cast(SH_Q_FAM::INVALID); } + Handle SHVkLogicalDevice::GetParentPhysicalDevice(void) const noexcept + { + return parentPhysicalDeviceHdl; + } + //SHDescriptorPool const& SHLogicalDevice::GetDescriptorPool(void) const noexcept //{ // return descriptorPool; diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h index b8eec993..158c20b2 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h @@ -1,5 +1,4 @@ -#ifndef SH_LOGICAL_DEVICE_H -#define SH_LOGICAL_DEVICE_H +#pragma once #include #include @@ -8,8 +7,8 @@ #include "Graphics/SHVulkanIncludes.h" #include "Graphics/Devices/SHVkPhysicalDevice.h" #include "Graphics/Queues/SHVkQueue.h" -#include "Resource/Handle.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHHandle.h" +#include "Resource/SHResourceLibrary.h" #include "Graphics/Swapchain/SHSwapchainParams.h" #include "Graphics/Commands/SHCommandPoolResetMode.h" #include "Graphics/Commands/SHVkCommandPool.h" @@ -17,8 +16,9 @@ #include "Graphics/Pipeline/SHPipelineState.h" #include "Graphics/Pipeline/SHPipelineType.h" #include "vk_mem_alloc.h" -//#include "Graphics/DescriptorSets/SHDescriptorPool.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" #include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/Images/SHVkImage.h" namespace SHADE { @@ -29,7 +29,6 @@ namespace SHADE class SHVkSurface; class SHVkSwapchain; class SHVkBuffer; - class SHVkImage; class SHVkFence; class SHVkSemaphore; class SHVkShaderModule; @@ -38,6 +37,10 @@ namespace SHADE class SHVkFramebuffer; class SHVkImageView; class SHShaderBlockInterface; + class SHVkDescriptorSetGroup; + class SHSubpass; + class SHVkSampler; + struct SHVkSamplerParams; /***************************************************************************/ /*! @@ -63,11 +66,14 @@ namespace SHADE class SHVkLogicalDevice : public ISelfHandle { private: - /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ - //vk::DeviceSize bufferMemoryAlignment{ 64 }; + //! UBO alignment + vk::DeviceSize uboBufferMemoryAlignment; + + //! SSBO alignment + vk::DeviceSize ssboBufferMemoryAlignment; //! Vulkan handle vk::Device vkLogicalDevice; @@ -95,6 +101,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ void InitializeVMA (void) noexcept; void InitializeQueues (std::initializer_list queueCreateParams) noexcept; + uint32_t ComputeAlignedBufferSize(uint32_t originalSize, size_t typeSize) const noexcept; public: /*-----------------------------------------------------------------------*/ @@ -106,14 +113,18 @@ namespace SHADE ~SHVkLogicalDevice (void) noexcept; SHVkLogicalDevice& operator= (SHVkLogicalDevice const& rhs) noexcept = default; - SHVkLogicalDevice& operator= (SHVkLogicalDevice&& rhs) noexcept = default; + SHVkLogicalDevice& operator= (SHVkLogicalDevice&& rhs) noexcept; /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ + // Miscellaneous functions void WaitIdle (void) noexcept; uint32_t FindMemoryType (uint32_t typeFilter, vk::MemoryPropertyFlags properties); - + uint32_t PadUBOSize(uint32_t originalSize) const noexcept; + uint32_t PadSSBOSize(uint32_t originalSize) const noexcept; + + // creation functions Handle CreateSurface (HWND const& windowHandle) const noexcept; Handle CreateSwapchain ( Handle const& surfaceHdl, @@ -134,18 +145,28 @@ namespace SHADE uint32_t srcSize, vk::BufferUsageFlags bufferUsage, VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + VmaAllocationCreateFlags allocFlags, + const std::string& name = "" ) const noexcept; Handle CreateImage ( - uint32_t w, - uint32_t h, - uint8_t levels, - vk::Format format, - vk::ImageUsageFlags usage, - vk::ImageCreateFlags create + uint32_t w, + uint32_t h, + uint8_t levels, + vk::Format format, + vk::ImageUsageFlags usage, + vk::ImageCreateFlags create ) const noexcept; - + + Handle CreateImage ( + SHImageCreateParams const& imageDetails, + unsigned char* data, + uint32_t dataSize, + std::span inMipOffsets, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept; + Handle CreateShaderModule ( std::vector const& binaryData, std::string entryPoint, @@ -153,29 +174,70 @@ namespace SHADE std::string const& shaderName ) noexcept; - Handle CreatePipeline ( + Handle CreateGraphicsPipeline ( Handle const& pipelineLayoutHdl, SHVkPipelineState const* const state, Handle const& renderpassHdl, - uint32_t subpass, - SH_PIPELINE_TYPE type + Handle subpass ) noexcept; + + Handle CreateComputePipeline ( + Handle const& pipelineLayoutHdl + ) noexcept; + + Handle CreateSampler (const SHVkSamplerParams& params) noexcept; Handle CreateRenderpass (std::span const vkDescriptions, std::vector const& subpasses) noexcept; Handle CreateRenderpass (std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept; Handle CreateFramebuffer (Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept; - Handle CreateDescriptorSetLayout (std::vector const& bindings) noexcept; - Handle CreatePipelineLayout (SHPipelineLayoutParams& pipelineLayoutParams) noexcept; + Handle CreateDescriptorSetLayout (SetIndex setIndex, std::vector const& bindings, bool genImmutableSamplers = false) noexcept; + Handle CreateDescriptorPools (const SHVkDescriptorPool::Config& config = {}) noexcept; + Handle CreateDescriptorSetGroup(Handle pool, + std::vector> const& layouts, + std::vector const& variableDescCounts) noexcept; + Handle CreatePipelineLayout(SHPipelineLayoutParams const& pipelineLayoutParams) noexcept; + Handle CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy const& pipelineLayoutParams) noexcept; Handle CreateFence (void) const noexcept; Handle CreateSemaphore (void) const noexcept; + void UpdateDescriptorSets(std::vector const& writeDescSets) noexcept; + void UpdateDescriptorSet(vk::WriteDescriptorSet const& writeDescSet) noexcept; + + /*-----------------------------------------------------------------------*/ + /* Debug Tools */ + /*-----------------------------------------------------------------------*/ +#ifdef _DEBUG + /// + /// Sets a Vulkan HPP object's name for debugging purposes. This function will not be + /// compiled outside of Debug configurations. Hence, it is advised to use provided + /// macro function SET_VK_OBJ_NAME() instead of using this function directly. + /// + /// Type of the object. + /// Handle to the Vulkan Object to name. + /// Object's name. + template + inline void SetVulkanObjectName(vk::ObjectType objType, T objHandle, const std::string& objName); + /// + /// Sets a Vulkan object's name for debugging purposes. This function will not be + /// compiled outside of Debug configurations. Hence, it is advised to use provided + /// macro function SET_VK_OBJ_NAME_VK() instead of using this function directly. + /// + /// Type of the object. + /// Handle to the Vulkan Object to name. + /// Object's name. + template + inline void SetVulkanObjectNameVk(vk::ObjectType objType, T objVkHandle, const std::string& objName); +#endif + /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - vk::Device const& GetVkLogicalDevice (void) const noexcept; - Handle const& GetQueue (SH_Q_FAM queueFamArrayIndex, uint8_t queueIndex) const; - VmaAllocator const& GetVMAAllocator (void) noexcept; - SHQueueFamilyIndex GetQueueFamilyIndex (SH_Q_FAM family) const noexcept; + vk::Device const& GetVkLogicalDevice (void) const noexcept; + Handle const& GetQueue (SH_Q_FAM queueFamArrayIndex, uint8_t queueIndex) const; + VmaAllocator const& GetVMAAllocator (void) noexcept; + SHQueueFamilyIndex GetQueueFamilyIndex (SH_Q_FAM family) const noexcept; + + Handle GetParentPhysicalDevice (void) const noexcept; //vk::DeviceSize GetBufferAlignment (void) const noexcept; //SHDescriptorPool const& GetDescriptorPool(void) const noexcept; @@ -183,4 +245,4 @@ namespace SHADE }; } -#endif +#include "SHVkLogicalDevice.hpp" diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.hpp b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.hpp new file mode 100644 index 00000000..e8de27fb --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.hpp @@ -0,0 +1,61 @@ +/************************************************************************************//*! +\file SHVkLogicalDevice.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 4, 2022 +\brief Contains implementation of inline and template functions of + SHVkLogicalDevice. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +#include "SHVkLogicalDevice.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Debug Tools */ + /*-----------------------------------------------------------------------------------*/ +#ifdef _DEBUG + template + void SHVkLogicalDevice::SetVulkanObjectName(vk::ObjectType objType, T objHandle, const std::string& objName) + { + if (objName.empty()) + return; + + vk::DebugUtilsObjectNameInfoEXT info; + info.objectType = objType; + info.objectHandle = (uint64_t) static_cast(objHandle); + info.pObjectName = objName.data(); + vkLogicalDevice.setDebugUtilsObjectNameEXT(info); + } + template + void SHVkLogicalDevice::SetVulkanObjectNameVk(vk::ObjectType objType, T objVkHandle, const std::string& objName) + { + if (objName.empty()) + return; + + vk::DebugUtilsObjectNameInfoEXT info; + info.objectType = objType; + info.objectHandle = (uint64_t) objVkHandle; + info.pObjectName = objName.data(); + vkLogicalDevice.setDebugUtilsObjectNameEXT(info); + } +#endif +} + +/*-------------------------------------------------------------------------------------*/ +/* Macro Definitions */ +/*-------------------------------------------------------------------------------------*/ +#ifdef _DEBUG +#define SET_VK_OBJ_NAME(DEVICE, OBJ_TYPE, OBJ_HDL, OBJ_NAME) \ + DEVICE->SetVulkanObjectName(OBJ_TYPE, OBJ_HDL, OBJ_NAME); +#define SET_VK_OBJ_NAME_VK(DEVICE, OBJ_TYPE, OBJ_HDL, OBJ_NAME) \ + DEVICE->SetVulkanObjectNameVk(OBJ_TYPE, OBJ_HDL, OBJ_NAME); +#else +#define SET_VK_OBJ_NAME(DEVICE, OBJ_TYPE, OBJ_HDL, OBJ_NAME) +#define SET_VK_OBJ_NAME_VK(DEVICE, OBJ_TYPE, OBJ_HDL, OBJ_NAME) +#endif diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp index efe624a5..53b352b7 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp @@ -26,7 +26,8 @@ namespace SHADE queueFamilyProperties = vkPhysicalDevice.getQueueFamilyProperties(); - deviceFeatures = vkPhysicalDevice.getFeatures(); + deviceFeatures = vkPhysicalDevice.getFeatures2(); + } SHVkPhysicalDevice::SHVkPhysicalDevice(SHVkPhysicalDevice&& rhs) noexcept diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h index c47ab9a9..a273da74 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h @@ -19,7 +19,7 @@ namespace SHADE vk::PhysicalDeviceProperties deviceProperties{}; vk::PhysicalDeviceMemoryProperties memoryProperties{}; - vk::PhysicalDeviceFeatures deviceFeatures{}; + vk::PhysicalDeviceFeatures2 deviceFeatures{}; std::vector queueFamilyProperties{}; public: diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp index 511af8fc..3cf0a8e6 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp @@ -1,4 +1,4 @@ -#include "SHPch.h" +#include "SHpch.h" #include #include #include "SHVkPhysicalDeviceLibrary.h" @@ -180,10 +180,13 @@ namespace SHADE return; } - SHLOG_ERROR("Successfully queried Physical Devices:"); + #ifdef DEBUG + SHLOG_TRACE("Successfully queried Physical Devices:"); for (auto const& device : physicalDevices) { - SHLOG_ERROR(std::string_view (std::string("\t-") + GetDeviceTypeName(device.getProperties().deviceType) + device.getProperties().deviceName.operator std::string())); + SHLOG_TRACE(std::string_view (std::string("\t-") + GetDeviceTypeName(device.getProperties().deviceType) + device.getProperties().deviceName.operator std::string())); } + #endif + } } diff --git a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp index 1386134f..76e627d3 100644 --- a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp +++ b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp @@ -98,6 +98,51 @@ namespace SHADE return *this; } + void SHVkFramebuffer::HandleResize(Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept + { + width = inWidth; + height = inHeight; + + for (auto& attachment : attachments) + { + // Not sure if its an error to pass in diff dimension images. + if (attachment->GetParentImage()->GetWidth() != (*attachments.begin())->GetParentImage()->GetWidth() || attachment->GetParentImage()->GetHeight() != (*attachments.begin())->GetParentImage()->GetHeight()) + { + SHLOG_ERROR("Dimensions of images not same as each other. Cannot create framebuffer."); + return; + } + } + + std::vector vkAttachments(attachments.size()); + + uint32_t i = 0; + for(auto const& attachment : attachments) + { + vkAttachments[i] = attachment->GetImageView(); + ++i; + } + + vk::FramebufferCreateInfo createInfo + { + .renderPass = renderpassHdl->GetVkRenderpass(), + .attachmentCount = static_cast(vkAttachments.size()), + .pAttachments = vkAttachments.data(), + .width = width, + .height = height, + .layers = 1 // TODO: Find out why this is 1 + }; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createFramebuffer(&createInfo, nullptr, &vkFramebuffer); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create framebuffer. "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created framebuffer. "); + } + } + /***************************************************************************/ /*! diff --git a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h index fa9161e8..4a02408c 100644 --- a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h +++ b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h @@ -2,7 +2,7 @@ #define SH_VK_FRAMEBUFFER_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include namespace SHADE @@ -37,6 +37,8 @@ namespace SHADE SHVkFramebuffer(SHVkFramebuffer&& rhs) noexcept; SHVkFramebuffer& operator=(SHVkFramebuffer&& rhs) noexcept; + void HandleResize (Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept; + /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp index 11fbe5dd..33bed1b5 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp @@ -5,14 +5,127 @@ #include "Tools/SHLogger.h" #include "SHVkImageView.h" #include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Buffers/SHVkBuffer.h" namespace SHADE { + /***************************************************************************/ + /*! + + \brief + If an image is a GPU only resource, we need to prep a staging buffer + to use for transferring data to the GPU. #NoteToSelf: I don't really + like this because its duplicate code. Should try to find a way to utilize + the logical device for this. + + \param data + Data to transfer. + + \param srcSize + Size in bytes of the data. + + */ + /***************************************************************************/ + void SHVkImage::PrepStagingBuffer(const void* data, uint32_t srcSize) noexcept + { + // For creation of buffer + vk::BufferCreateInfo bufferInfo{}; + + // size stored same as GPU buffer + bufferInfo.size = srcSize; + + // We just want to set the transfer bit + bufferInfo.usage = vk::BufferUsageFlagBits::eTransferSrc; + + // sharing mode exclusive + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + // Set to auto detect bits + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + + // We want to just write all at once. Using random access bit could make this slow + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + + // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo + VmaAllocationInfo allocInfo; + + // results of allocation + VmaAllocation stagingAlloc; + + // To get around VMA's usage for C version of vulkan, create a temp first..., + VkBuffer tempBuffer{}; + + // Create the buffer... + vmaCreateBuffer(*vmaAllocator, + &bufferInfo.operator VkBufferCreateInfo & (), // TODO: Verify if this works (can use renderdoc to check buffer variables?) + &allocCreateInfo, + &tempBuffer, &stagingAlloc, &allocInfo); + SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eDeviceMemory, allocInfo.deviceMemory, "[Memory] Staging Buffer for Image"); + + // then assign it to the hpp version + stagingBuffer = tempBuffer; + + // Just map, copy then unmap + void* stagingBufferMappedPtr = nullptr; + vmaMapMemory(*vmaAllocator, stagingAlloc, &stagingBufferMappedPtr); + + if (stagingBufferMappedPtr) + std::memcpy(static_cast(stagingBufferMappedPtr), static_cast(data), srcSize); + + const VkDeviceSize offsets = 0; + const VkDeviceSize sizes = srcSize; + vmaFlushAllocations(*vmaAllocator, 1, &stagingAlloc, &offsets, &sizes); + + vmaUnmapMemory(*vmaAllocator, stagingAlloc); + } + + void SHVkImage::CreateFramebufferImage(void) noexcept + { + vk::ImageCreateInfo imageCreateInfo{}; + imageCreateInfo.imageType = vk::ImageType::e2D; + imageCreateInfo.extent.width = width; + imageCreateInfo.extent.height = height; + imageCreateInfo.extent.depth = depth; + imageCreateInfo.mipLevels = mipLevelCount; + imageCreateInfo.arrayLayers = layerCount; + imageCreateInfo.format = imageFormat; + imageCreateInfo.tiling = vk::ImageTiling::eOptimal; + imageCreateInfo.initialLayout = vk::ImageLayout::eUndefined; + imageCreateInfo.usage = usageFlags; + imageCreateInfo.sharingMode = vk::SharingMode::eExclusive; + imageCreateInfo.samples = vk::SampleCountFlagBits::e1; + imageCreateInfo.flags = createFlags; + + + // Prepare allocation parameters for call to create images later + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + allocCreateInfo.flags = {}; // TODO: Make sure the vk::MemoryPropertyFlags returned from vmaGetAllocationMemoryProperties has the device local bit set + + VmaAllocationInfo allocInfo{}; + + VkImage tempImage; + auto result = vmaCreateImage(*vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + vkImage = tempImage; + //SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eImage, vkImage, "[Image] "); + SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eDeviceMemory, allocInfo.deviceMemory, "[Device Memory] Image Memory"); + + if (result != VK_SUCCESS) + SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan image. "); + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created image. "); + } + SHVkImage::SHVkImage( - VmaAllocator const& vmaAllocator, - SHImageCreateParams const& imageDetails, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + Handle logicalDeviceHdl, + VmaAllocator const* allocator, + SHImageCreateParams const& imageDetails, + const unsigned char* data, + uint32_t dataSize, + std::span inMipOffsets, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags ) noexcept : imageType { imageDetails.imageType } , width{ imageDetails.width } @@ -23,12 +136,15 @@ namespace SHADE , imageFormat{ imageDetails.imageFormat } , usageFlags{} , createFlags{} + , vmaAllocator{allocator} + , mipOffsets { inMipOffsets } + , boundToCoherent{false} + , randomAccessOptimized {false} + , mappedPtr{nullptr} + , device { logicalDeviceHdl } { - for (auto& bit : imageDetails.usageBits) - usageFlags |= bit; - - for (auto& bit : imageDetails.createBits) - createFlags |= bit; + usageFlags = imageDetails.usageFlags; + createFlags = imageDetails.createFlags; // If marked as 2D array compatible, image type MUST be 3D if (createFlags & vk::ImageCreateFlagBits::e2DArrayCompatible) @@ -64,58 +180,54 @@ namespace SHADE VmaAllocationInfo allocInfo{}; VkImage tempImage; - vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo&(), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + auto result = vmaCreateImage(*vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + //SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eImage, vkImage, "[Image] "); + SET_VK_OBJ_NAME_VK(device, vk::ObjectType::eDeviceMemory, allocInfo.deviceMemory, "[Device Memory] Image Memory"); + + if (result != VK_SUCCESS) + SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan image. "); + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created image. "); + vkImage = tempImage; - //if (allocFlags & ) + // At this point the image and device memory have been created. + + if (allocFlags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) + randomAccessOptimized = true; + + // TODO: This constructor can only create a GPU only resource for now. Due to time constraint, I was trying to create a ctor + // fast to finish up the ImGUI backend. In the future, there definitely needs to be more versatility to the constructor. + + // Get the memory property flags + VkMemoryPropertyFlags memPropFlags; + vmaGetAllocationMemoryProperties(*vmaAllocator, alloc, &memPropFlags); + + // mainly host visible. Can be cached (need to flush/invalidate), uncached (always coherent) and coherent (virtual). + //if (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + //{ + // // If memory is marked to be coherent between CPU and GPU (no need flush/invalidate) (TODO: Verify if VMA_ALLOCATION_CREATE_MAPPED_BIT is used when VMA_MEMORY_USAGE_AUTO is set) + // // TODO: also verify that coherent bit = pointer is already mapped + // if (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + // { + // boundToCoherent = true; + // mappedPtr = allocInfo.pMappedData; + // } + // else + // mappedPtr = nullptr; + + // if (data) + // MapWriteUnmap(data, srcSize, 0, 0); + //} + //else + //{ + // We can prep first so that we can do transfers later via 1 cmd buffer recording + PrepStagingBuffer(data, dataSize); + + //} } - /***************************************************************************/ - /*! - - \brief - This is mainly used for images that aren't created internally because - they cannot be created in the traditional way (e.g. swapchain images). - - \param inVkImage - Image already created outside - - \param width - Width of the image - - \param height - Height of the image - - \param depth - Depth of the image - - \param levels - Number of levels in the image - - \param arrayLayers - if the image is an array, this value will be > 1. - - \param imageFormat - Format of the image - - */ - /***************************************************************************/ - SHVkImage::SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept - : vkImage (inVkImage) - , width{ inWidth } - , height{ inHeight } - , depth{ inDepth } - , mipLevelCount{ levels } - , layerCount{ arrayLayers } - , imageFormat{ format } - , usageFlags{flags} - , alloc{} - , imageType{type} - , createFlags{} - { - } - - SHVkImage::SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept + SHVkImage::SHVkImage(Handle logicalDeviceHdl, VmaAllocator const* allocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept : width {w} , height{h} , depth {1} @@ -124,45 +236,81 @@ namespace SHADE , imageFormat{format} , usageFlags{usage} , createFlags {create} + , vmaAllocator {allocator} + , device { logicalDeviceHdl } { - vk::ImageCreateInfo imageCreateInfo{}; - imageCreateInfo.imageType = vk::ImageType::e2D; - imageCreateInfo.extent.width = width; - imageCreateInfo.extent.height = height; - imageCreateInfo.extent.depth = depth; - imageCreateInfo.mipLevels = mipLevelCount; - imageCreateInfo.arrayLayers = layerCount; - imageCreateInfo.format = imageFormat; - imageCreateInfo.tiling = vk::ImageTiling::eOptimal; - imageCreateInfo.initialLayout = vk::ImageLayout::eUndefined; - imageCreateInfo.usage = usageFlags; - imageCreateInfo.sharingMode = vk::SharingMode::eExclusive; - imageCreateInfo.samples = vk::SampleCountFlagBits::e1; - imageCreateInfo.flags = createFlags; - - - // Prepare allocation parameters for call to create images later - VmaAllocationCreateInfo allocCreateInfo{}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; - allocCreateInfo.flags = {}; // TODO: Make sure the vk::MemoryPropertyFlags returned from vmaGetAllocationMemoryProperties has the device local bit set - - VmaAllocationInfo allocInfo{}; - - VkImage tempImage; - auto result = vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); - vkImage = tempImage; - - if (result != VK_SUCCESS) - SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan image. "); - else - SHVulkanDebugUtil::ReportVkSuccess("Successfully created image. "); + CreateFramebufferImage(); } - Handle SHVkImage::CreateImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept + Handle SHVkImage::CreateImageView(Handle inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept { return SHVkInstance::GetResourceManager().Create(inLogicalDeviceHdl, parent, createParams); } + void SHVkImage::TransferToDeviceResource(Handle cmdBufferHdl) noexcept + { + // prepare copy regions + std::vector copyRegions{mipOffsets.size()}; + + for (uint32_t i = 0; i < mipOffsets.size(); ++i) + { + copyRegions[i].bufferOffset = mipOffsets[i]; + copyRegions[i].bufferRowLength = 0; // for padding + copyRegions[i].bufferImageHeight = 0; // for padding + copyRegions[i].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; // TODO: Need to change this to base it off image format. + copyRegions[i].imageSubresource.mipLevel = i; + copyRegions[i].imageSubresource.baseArrayLayer = 0; // TODO: Array textures not supported yet + copyRegions[i].imageSubresource.layerCount = layerCount; + copyRegions[i].imageOffset = vk::Offset3D{ 0,0,0 }; + copyRegions[i].imageExtent = vk::Extent3D{ width >> i, height >> i, 1 }; + } + + cmdBufferHdl->CopyBufferToImage(stagingBuffer, vkImage, copyRegions); + } + + /***************************************************************************/ + /*! + + \brief + Does not perform any image transitions but prepares a barrier for image + transitioning. Pipeline barrier will be issued outside this call after + this preparation function, or at least, it should be. + + \param oldLayout + Old layout of the image. + + \param newLayout + new layout of the image to transition to. + + \param barrier + Barrier to modify to prepare the image for transitioning. + + */ + /***************************************************************************/ + void SHVkImage::PrepareImageTransitionInfo(vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::ImageMemoryBarrier& barrier) noexcept + { + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vkImage; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; // TODO: Need to change this to base it off image format. + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevelCount; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = layerCount; + } + + void SHVkImage::HandleResizeFramebufferImage(uint32_t newWidth, uint32_t newHeight) noexcept + { + vmaDestroyImage(*vmaAllocator, vkImage, alloc); + + width = newWidth; + height = newHeight; + + CreateFramebufferImage(); + } + void SHVkImage::LinkWithExteriorImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t layers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept { vkImage = inVkImage; diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.h b/SHADE_Engine/src/Graphics/Images/SHVkImage.h index 53066075..ba459def 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImage.h +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.h @@ -3,13 +3,14 @@ #include "SHImageViewDetails.h" #include "Graphics/SHVulkanDefines.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" #include "vk_mem_alloc.h" namespace SHADE { class SHVkLogicalDevice; class SHVkImageView; + class SHVkCommandBuffer; struct SHImageCreateParams { @@ -35,10 +36,10 @@ namespace SHADE vk::Format imageFormat; //! Image usage bits - std::span usageBits; + vk::ImageUsageFlags usageFlags; //! Image create flags - std::span createBits; + vk::ImageCreateFlags createFlags; }; class SHVkImage @@ -47,6 +48,12 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ + //! Pointer to the vma allocator. #NoteToSelf: Not super proud of this being a pointer. + //! The only reason why this is a pointer is because a reference_wrapper cannot be default constructed. + //! And the reason why we want a default constructor is because sometimes we don't want to create images + //! but merely link them from outside (swapchain images) + VmaAllocator const* vmaAllocator; + //! 1D, 2D or 3D vk::ImageType imageType = vk::ImageType::e2D; @@ -80,22 +87,50 @@ namespace SHADE //! allocation object containing details of an allocation VmaAllocation alloc{}; + //! Whether or not this image is HOST_VISIBLE and random access optimized + bool randomAccessOptimized; + + //! Whether or not the memory the image is bound to is memory coherent (updates on CPU can be seen on GPU without flushing cache) + bool boundToCoherent; + + //! Persistently mapped pointer if applicable (will be void if image is + //! not created with the correct flags). Note that this is only used for + //! persistent mapping. One time updates do not use this pointer. + void* mappedPtr; + + //! Staging buffer for images purely in the GPU + vk::Buffer stagingBuffer; + + //! Mipmap offsets for initializing the vk::BufferImageCopy during transfer to GPU resource + std::span mipOffsets; + + //! Handle to the device that creates these images + Handle device; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void PrepStagingBuffer(const void* data, uint32_t srcSize) noexcept; + void CreateFramebufferImage (void) noexcept; + public: /*-----------------------------------------------------------------------*/ /* CTOR AND DTOR */ /*-----------------------------------------------------------------------*/ SHVkImage(void) noexcept = default; - // TODO: Might need to add flags to parameters SHVkImage( - VmaAllocator const& vmaAllocator, - SHImageCreateParams const& imageDetails, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + Handle logicalDeviceHdl, + VmaAllocator const* allocator, + SHImageCreateParams const& imageDetails, + const unsigned char* data, + uint32_t dataSize, + std::span inMipOffsets, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags ) noexcept; - SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept; - SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept; + SHVkImage(Handle logicalDeviceHdl, VmaAllocator const* allocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept; SHVkImage(SHVkImage&& rhs) noexcept = default; SHVkImage& operator=(SHVkImage && rhs) noexcept = default; @@ -103,8 +138,11 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - Handle CreateImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept; - + Handle CreateImageView (Handle inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept; + void TransferToDeviceResource (Handle cmdBufferHdl) noexcept; + void PrepareImageTransitionInfo (vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::ImageMemoryBarrier& barrier) noexcept; + void HandleResizeFramebufferImage(uint32_t newWidth, uint32_t newHeight) noexcept; + /*-----------------------------------------------------------------------*/ /* GETTERS AND SETTERS */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp b/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp index 9d12d7cd..44b5718c 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp +++ b/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp @@ -6,6 +6,67 @@ namespace SHADE { + + void SHVkImageView::Create(void) noexcept + { + auto parentImageCreateFlags = parentImage->GetImageeCreateFlags(); + + // 2D array image type means parent image must be 2D array compatible + if (imageViewDetails.viewType == vk::ImageViewType::e2DArray) + { + if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::e2DArrayCompatible)) + { + SHLOG_ERROR("Failed to create image view. Parent image not 2D array compatible. "); + return; + } + } + + // Check if its possible for the image view to have different format than parent image + if (imageViewDetails.format != parentImage->GetImageFormat()) + { + if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::eMutableFormat)) + { + SHLOG_ERROR("Failed to create image view. Format for image view not same as image but image not initialized with mutable format bit. "); + return; + } + } + + + vk::ImageViewCreateInfo viewCreateInfo + { + .pNext = nullptr, // Can be used to override with a VkImageViewUsageCreateInfo to override usage. See Vulkan spec page 877 for more information + .image = parentImage->GetVkImage(), + .viewType = imageViewDetails.viewType, + .format = imageViewDetails.format, + .components + { + .r = vk::ComponentSwizzle::eR, + .g = vk::ComponentSwizzle::eG, + .b = vk::ComponentSwizzle::eB, + .a = vk::ComponentSwizzle::eA, + }, + .subresourceRange + { + .aspectMask = imageViewDetails.imageAspectFlags, + .baseMipLevel = imageViewDetails.baseMipLevel, + .levelCount = imageViewDetails.mipLevelCount, + .baseArrayLayer = imageViewDetails.baseArrayLayer, + .layerCount = imageViewDetails.layerCount, + }, + }; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createImageView(&viewCreateInfo, nullptr, &vkImageView); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create image view! "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created image view. "); + } + + } + /***************************************************************************/ /*! @@ -18,70 +79,12 @@ namespace SHADE */ /***************************************************************************/ SHVkImageView::SHVkImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) noexcept - : parentImage{ } + : parentImage{ parent } , vkImageView{} - , imageViewDetails{} + , imageViewDetails{createParams} , logicalDeviceHdl {inLogicalDeviceHdl} { - auto parentImageCreateFlags = parent->GetImageeCreateFlags(); - - // 2D array image type means parent image must be 2D array compatible - if (createParams.viewType == vk::ImageViewType::e2DArray) - { - if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::e2DArrayCompatible)) - { - SHLOG_ERROR("Failed to create image view. Parent image not 2D array compatible. "); - return; - } - } - - // Check if its possible for the image view to have different format than parent image - if (createParams.format != parent->GetImageFormat()) - { - if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::eMutableFormat)) - { - SHLOG_ERROR("Failed to create image view. Format for image view not same as image but image not initialized with mutable format bit. "); - return; - } - } - - - vk::ImageViewCreateInfo viewCreateInfo - { - .pNext = nullptr, // Can be used to override with a VkImageViewUsageCreateInfo to override usage. See Vulkan spec page 877 for more information - .image = parent->GetVkImage(), - .viewType = createParams.viewType, - .format = createParams.format, - .components - { - .r = vk::ComponentSwizzle::eR, - .g = vk::ComponentSwizzle::eG, - .b = vk::ComponentSwizzle::eB, - .a = vk::ComponentSwizzle::eA, - }, - .subresourceRange - { - .aspectMask = createParams.imageAspectFlags, - .baseMipLevel = createParams.baseMipLevel, - .levelCount = createParams.mipLevelCount, - .baseArrayLayer = createParams.baseArrayLayer, - .layerCount = createParams.layerCount, - }, - }; - - if (auto result = inLogicalDeviceHdl->GetVkLogicalDevice().createImageView(&viewCreateInfo, nullptr, &vkImageView); result != vk::Result::eSuccess) - { - SHVulkanDebugUtil::ReportVkError(result, "Failed to create image view! "); - return; - } - else - { - SHVulkanDebugUtil::ReportVkSuccess("Successfully created image view. "); - } - - // After success, THEN assign variables - parentImage = parent; - imageViewDetails = createParams; + Create(); } SHVkImageView::SHVkImageView(SHVkImageView&& rhs) noexcept @@ -94,6 +97,17 @@ namespace SHADE } + void SHVkImageView::ViewNewImage(Handle const& parent, SHImageViewDetails const& createParams) noexcept + { + imageViewDetails = createParams; + parentImage = parent; + + if (vkImageView) + logicalDeviceHdl->GetVkLogicalDevice().destroyImageView(vkImageView, nullptr); + + Create(); + } + Handle const& SHVkImageView::GetParentImage(void) const noexcept { return parentImage; diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImageView.h b/SHADE_Engine/src/Graphics/Images/SHVkImageView.h index afa357ef..79bb5dca 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImageView.h +++ b/SHADE_Engine/src/Graphics/Images/SHVkImageView.h @@ -2,7 +2,7 @@ #define SH_VK_IMAGE_VIEW_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include "SHImageViewDetails.h" namespace SHADE @@ -25,12 +25,17 @@ namespace SHADE //! Logical Device needed for creation and destruction Handle logicalDeviceHdl; + //! Create new image view + void Create (void) noexcept; + public: SHVkImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) noexcept; ~SHVkImageView(void) noexcept; SHVkImageView(SHVkImageView&& rhs) noexcept; SHVkImageView& operator=(SHVkImageView&& rhs) noexcept; + void ViewNewImage (Handle const& parent, SHImageViewDetails const& createParams) noexcept; + /*-----------------------------------------------------------------------*/ /* GETTERS AND SETTERS */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Images/SHVkSampler.cpp b/SHADE_Engine/src/Graphics/Images/SHVkSampler.cpp new file mode 100644 index 00000000..7443b6e2 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkSampler.cpp @@ -0,0 +1,65 @@ +/************************************************************************************//*! +\file SHVkSampler.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 26, 2022 +\brief Contains definitions for all of the functions of the SHVkSampler class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHVkSampler.h" + +#include "Graphics/Devices/SHVkLogicalDevice.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------------*/ + SHVkSampler::SHVkSampler(Handle logicalDevice, const SHVkSamplerParams& params) noexcept + : device { logicalDevice } + { + const vk::SamplerCreateInfo SAMPLER_CREATE_INFO + { + .magFilter = params.magFilter, + .minFilter = params.minFilter, + .mipmapMode = params.mipmapMode, + .addressModeU = params.addressMode, + .addressModeV = params.addressMode, + .addressModeW = params.addressMode, + .maxAnisotropy = 1.0f, + .minLod = params.minLod, + .maxLod = params.maxLod, + }; + + // Create the sampler + vkSampler = device->GetVkLogicalDevice().createSampler(SAMPLER_CREATE_INFO); + } + + SHVkSampler::SHVkSampler(SHVkSampler&& rhs) noexcept + : vkSampler{ rhs.vkSampler } + , device{ rhs.device } + { + rhs.vkSampler = nullptr; + } + + SHVkSampler::~SHVkSampler() noexcept + { + if (vkSampler) + device->GetVkLogicalDevice().destroySampler(vkSampler); + } + + /*-----------------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------------*/ + SHADE::SHVkSampler& SHVkSampler::operator=(SHVkSampler&& rhs) noexcept + { + vkSampler = rhs.vkSampler; + device = rhs.device; + rhs.vkSampler = nullptr; + return *this; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Images/SHVkSampler.h b/SHADE_Engine/src/Graphics/Images/SHVkSampler.h new file mode 100644 index 00000000..3a989553 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkSampler.h @@ -0,0 +1,80 @@ +/************************************************************************************//*! +\file SHVkSampler.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 26, 2022 +\brief Contains definitions of the SHVkSampler class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Holds parameters for constructing the SHVkSampler. + */ + /*************************************************************************************/ + struct SHVkSamplerParams + { + vk::Filter minFilter = vk::Filter::eLinear; + vk::Filter magFilter = vk::Filter::eLinear; + vk::SamplerAddressMode addressMode = vk::SamplerAddressMode::eClampToEdge; + vk::SamplerMipmapMode mipmapMode = vk::SamplerMipmapMode::eLinear; + float minLod = 0; + float maxLod = 0; + }; + + /*************************************************************************************/ + /*! + \brief + Wrapper for a VkSampler. + */ + /*************************************************************************************/ + class SHVkSampler + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + SHVkSampler(Handle logicalDevice, const SHVkSamplerParams& params = {}) noexcept; + SHVkSampler(SHVkSampler&& rhs) noexcept; + ~SHVkSampler() noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + SHVkSampler& operator=(SHVkSampler&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + vk::Sampler GetVkSampler(void) const noexcept { return vkSampler; } + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + vk::Sampler vkSampler; //! The Vulkan sampler handler + Handle device; //! Stored device for deallocating the object + }; +} + diff --git a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp index 6688f9a6..edfe4b46 100644 --- a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp +++ b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp @@ -15,7 +15,7 @@ namespace SHADE bool SHVkInstance::validationLayersOn; vk::Instance SHVkInstance::vkInstance; SHVkDebugMessenger SHVkInstance::debugMessenger; - ResourceManager SHVkInstance::resourceManager; + SHResourceHub SHVkInstance::resourceManager; /***************************************************************************/ /*! @@ -155,7 +155,7 @@ namespace SHADE SHVkDebugMessenger::GenMessengerType(SH_DEBUG_MSG_TYPE::T_GENERAL, SH_DEBUG_MSG_TYPE::T_VALIDATION, SH_DEBUG_MSG_TYPE::T_PERFORMANCE)); instanceDbgInfo.pfnUserCallback = SHVulkanDebugUtil::GenericDebugCallback; - instanceInfo.pNext = static_cast(&instanceDbgInfo); + //instanceInfo.pNext = static_cast(&instanceDbgInfo); } // Finally create the instance @@ -258,7 +258,7 @@ namespace SHADE return vkInstance; } - ResourceManager& SHVkInstance::GetResourceManager(void) noexcept + SHResourceHub& SHVkInstance::GetResourceManager(void) noexcept { return resourceManager; } diff --git a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h index a6fb4f0f..55adc774 100644 --- a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h +++ b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h @@ -18,7 +18,7 @@ written consent of DigiPen Institute of Technology is prohibited. #include "Graphics/Debugging/SHVkDebugMessenger.h" #include "Graphics/Devices/SHVkPhysicalDevice.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" namespace SHADE @@ -61,7 +61,7 @@ namespace SHADE static SHVkDebugMessenger debugMessenger; //! Resource management for vulkan project - static ResourceManager resourceManager; + static SHResourceHub resourceManager; /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER FUNCTIONS */ @@ -85,7 +85,7 @@ namespace SHADE /* Getters and Setters */ /*-----------------------------------------------------------------------*/ static vk::Instance const& GetVkInstance (void) noexcept; - static ResourceManager& GetResourceManager(void) noexcept; + static SHResourceHub& GetResourceManager(void) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp new file mode 100644 index 00000000..1829096f --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp @@ -0,0 +1,503 @@ +/************************************************************************************//*! +\file SHBatch.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains the definition of SHBatch's functions. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHBatch.h" + +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/SHVkUtil.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* SHBatch - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + SHBatch::SHBatch(Handle pipeline) + : pipeline{ pipeline } + { + if (!pipeline) + throw std::invalid_argument("Attempted to create a SHBatch with an invalid SHPipeline!"); + + // Mark all as dirty + setAllDirtyFlags(); + } + + void SHBatch::Add(const SHRenderable* renderable) + { + // Ignore if null + if (!renderable->GetMesh()) + return; + + // Check if we have a SubBatch with the same mesh yet + auto subBatch = std::find_if(subBatches.begin(), subBatches.end(), [&](const SHSubBatch& batch) + { + return batch.Mesh == renderable->GetMesh(); + }); + + // Create one if not found + if (subBatch == subBatches.end()) + { + subBatches.emplace_back(renderable->GetMesh()); + subBatch = subBatches.end() - 1; + } + + // Add renderable in + subBatch->Renderables.insert(renderable->GetEID()); + + // Also add material instance in + referencedMatInstances.insert(renderable->GetMaterial()); + + // Mark all as dirty + setAllDirtyFlags(); + } + + void SHBatch::Remove(const SHRenderable* renderable) + { + // Check if we have a SubBatch with the existing mesh yet (if changed, we use the old mesh) + Handle prevSubBatchMesh = renderable->HasMeshChanged() ? renderable->GetPrevMesh() : renderable->GetMesh(); + auto subBatch = std::find_if(subBatches.begin(), subBatches.end(), [&](const SHSubBatch& batch) + { + return batch.Mesh == prevSubBatchMesh; + }); + + // Attempt to remove if it exists + if (subBatch == subBatches.end()) + return; + + subBatch->Renderables.erase(renderable->GetEID()); + + // Check if other renderables in subBatches contain the same material instance + bool matUnused = true; + Handle matToCheck = renderable->HasMaterialChanged() ? renderable->GetPrevMaterial() : renderable->GetMaterial(); + for (const auto& sb : subBatches) + { + // Check material usage + for (const auto& rendId : sb.Renderables) + { + auto rend = SHComponentManager::GetComponent(rendId); + if (rend) + { + if (rend->GetMaterial() == matToCheck) + { + matUnused = false; + break; + } + } + else + { + SHLOG_WARNING("[SHBatch] Entity with a missing SHRenderable found!"); + } + } + } + + // Material is no longer in this library, so we remove it + if (matUnused) + referencedMatInstances.erase(renderable->HasChanged() ? renderable->GetPrevMaterial() : renderable->GetMaterial()); + + // Mesh is no longer in this batch, so we remove the associated sub batch + if (subBatch->Renderables.empty()) + subBatches.erase(subBatch); + + // Mark all as dirty + setAllDirtyFlags(); + } + + void SHBatch::Clear() + { + subBatches.clear(); + + // Clear CPU buffers + drawData.clear(); + transformData.clear(); + instancedIntegerData.clear(); + matPropsData.reset(); + matPropsDataSize = 0; + + + // Clear GPU buffers + for (int i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + drawDataBuffer[i].Free(); + transformDataBuffer[i].Free(); + instancedIntegerBuffer[i].Free(); + matPropsBuffer[i].Free(); + } + } + + void SHBatch::UpdateMaterialBuffer(uint32_t frameIndex, Handle descPool) + { + if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS) + { + SHLOG_WARNING("[SHBatch] Attempted to update transform buffers with an invalid frame index."); + return; + } + + // Check if there are even material properties to update + if (!matPropsData) + return; + + // Check if any materials have changed + bool hasChanged = false; + for (const auto& material : referencedMatInstances) + { + if (material->HasChanged()) + { + hasChanged = true; + break; + } + } + + // We need to update all the material buffers if the materials have changed + if (hasChanged) + { + for (auto& dirt : matBufferDirty) + dirt = true; + } + + // Check if this frame's buffer is dirty + if (!matBufferDirty[frameIndex]) + return; + + // Build CPU Buffer + char* propsCurrPtr = matPropsData.get(); + for (auto& subBatch : subBatches) + for (auto rendId : subBatch.Renderables) + { + const SHRenderable* renderable = SHComponentManager::GetComponent(rendId); + if (renderable) + { + renderable->GetMaterial()->ExportProperties(propsCurrPtr); + } + else + { + SHLOG_WARNING("[SHBatch] Entity with a missing SHRenderable found!"); + } + //propsCurrPtr += singleMatPropAlignedSize; + propsCurrPtr += singleMatPropSize; + } + + // Transfer to GPU + rebuildMaterialBuffers(frameIndex, descPool); + + // This frame is updated + matBufferDirty[frameIndex] = false; + } + + void SHBatch::UpdateTransformBuffer(uint32_t frameIndex) + { + if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS) + { + SHLOG_WARNING("[SHBatch] Attempted to update transform buffers with an invalid frame index."); + return; + } + + // Reset Transform Data + transformData.clear(); + + // Populate on the CPU + for (auto& subBatch : subBatches) + for (auto rendId : subBatch.Renderables) + { + // Transform + auto transform = SHComponentManager::GetComponent(rendId); + if (transform) + { + transformData.emplace_back(transform->GetTRS()); + } + else + { + SHLOG_WARNING("[SHBatch] Entity contianing a SHRenderable with no SHTransformComponent found!"); + transformData.emplace_back(); + } + } + + // Transfer to GPU + if (transformDataBuffer[frameIndex]) + transformDataBuffer[frameIndex]->WriteToMemory(transformData.data(), static_cast(transformData.size() * sizeof(SHMatrix)), 0, 0); + } + + void SHBatch::UpdateInstancedIntegerBuffer(uint32_t frameIndex) + { + if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS) + { + SHLOG_WARNING("[SHBatch] Attempted to update eid buffers with an invalid frame index."); + return; + } + + // Reset Transform Data + instancedIntegerData.clear(); + + // Populate on the CPU + for (auto& subBatch : subBatches) + for (auto rendId : subBatch.Renderables) + { + auto* renderable = SHComponentManager::GetComponent(rendId); + instancedIntegerData.emplace_back(SHInstancedIntegerData + { + rendId, + renderable->GetLightLayer() + } + ); + } + + // Transfer to GPU + if (instancedIntegerBuffer[frameIndex]) + instancedIntegerBuffer[frameIndex]->WriteToMemory(instancedIntegerData.data(), static_cast(instancedIntegerData.size() * sizeof(SHInstancedIntegerData)), 0, 0); + + } + + void SHBatch::Build(Handle _device, Handle descPool, uint32_t frameIndex) + { + if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS) + { + SHLOG_WARNING("[SHBatch] Attempted to update build batch buffers with an invalid frame index."); + return; + } + + // Save logical device + device = _device; + + // No need to build as there are no changes + if (!isDirty[frameIndex]) + return; + + // Count number of elements + size_t numTotalElements = 0; + for (const auto& subBatch : subBatches) + { + numTotalElements += subBatch.Renderables.size(); + } + + // Generate CPU buffers if there are changes + if (isCPUBuffersDirty) + { + // - Draw data + drawData.reserve(subBatches.size()); + drawData.clear(); + // - Transform data + transformData.reserve(numTotalElements); + transformData.clear(); + // - EID data + instancedIntegerData.reserve(numTotalElements); + instancedIntegerData.clear(); + + + // - Material Properties Data + const Handle SHADER_INFO = pipeline->GetPipelineLayout()->GetShaderBlockInterface + ( + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, + vk::ShaderStageFlagBits::eFragment + ); + const bool EMPTY_MAT_PROPS = !SHADER_INFO; + Byte matPropTotalBytes = 0; + if (!EMPTY_MAT_PROPS) + { + singleMatPropSize = SHADER_INFO->GetBytesRequired(); + singleMatPropAlignedSize = device->PadSSBOSize(static_cast(singleMatPropSize)); + matPropTotalBytes = numTotalElements * singleMatPropSize; + if (matPropsDataSize < matPropTotalBytes) + { + matPropsData.reset(new char[matPropTotalBytes]); + matPropsDataSize = matPropTotalBytes; + } + } + + // Build Sub Batches + uint32_t nextInstanceIndex = 0; + char* propsCurrPtr = matPropsData.get(); + for (auto& subBatch : subBatches) + { + // Create command + const uint32_t CURR_INSTANCES = static_cast(subBatch.Renderables.size()); + drawData.emplace_back(vk::DrawIndexedIndirectCommand + { + .indexCount = subBatch.Mesh->IndexCount, + .instanceCount = CURR_INSTANCES, + .firstIndex = subBatch.Mesh->FirstIndex, + .vertexOffset = subBatch.Mesh->FirstVertex, + .firstInstance = nextInstanceIndex + }); + nextInstanceIndex += CURR_INSTANCES; + + // Fill in buffers (CPU) + for (auto rendId : subBatch.Renderables) + { + // Transform + auto transform = SHComponentManager::GetComponent_s(rendId); + if (!transform) + { + SHLOG_WARNING("[SHBatch] Entity contianing a SHRenderable with no SHTransformComponent found!"); + transformData.emplace_back(); + } + else + { + transformData.emplace_back(transform->GetTRS()); + } + + const SHRenderable* renderable = SHComponentManager::GetComponent(rendId); + instancedIntegerData.emplace_back(SHInstancedIntegerData + { + rendId, + renderable->GetLightLayer() + } + ); + + // Material Properties + if (!EMPTY_MAT_PROPS) + { + if (renderable) + { + renderable->GetMaterial()->ExportProperties(propsCurrPtr); + } + else + { + SHLOG_WARNING("[SHBatch] Entity with a missing SHRenderable found!"); + } + //propsCurrPtr += singleMatPropAlignedSize; + propsCurrPtr += singleMatPropSize; + } + } + } + + // Successfully update CPU buffers + isCPUBuffersDirty = false; + } + + // Send all buffered data to the GPU buffers + using BuffUsage = vk::BufferUsageFlagBits; + // - Draw Data + const uint32_t DRAW_DATA_BYTES = static_cast(drawData.size() * sizeof(vk::DrawIndexedIndirectCommand)); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, drawDataBuffer[frameIndex], drawData.data(), DRAW_DATA_BYTES, + BuffUsage::eIndirectBuffer, + "Batch Draw Data Buffer" + ); + // - Transform Buffer + const uint32_t TF_DATA_BYTES = static_cast(transformData.size() * sizeof(SHMatrix)); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, transformDataBuffer[frameIndex], transformData.data(), TF_DATA_BYTES, + BuffUsage::eVertexBuffer, + "Batch Transform Buffer" + ); + const uint32_t EID_DATA_BYTES = static_cast(instancedIntegerData.size() * sizeof(SHInstancedIntegerData)); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, instancedIntegerBuffer[frameIndex], instancedIntegerData.data(), EID_DATA_BYTES, + BuffUsage::eVertexBuffer, + "Batch Instance Data Buffer" + ); + // - Material Properties Buffer + rebuildMaterialBuffers(frameIndex, descPool); + + // Mark this frame as no longer dirty + isDirty[frameIndex] = false; + } + + /*---------------------------------------------------------------------------------*/ + /* SHBatch - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void SHBatch::Draw(Handle cmdBuffer, uint32_t frameIndex) + { + if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS) + { + SHLOG_WARNING("[SHBatch] Attempted to draw a batch with an invalid frame index."); + return; + } + + // Bind all required objects before drawing + static std::array dynamicOffset{ 0 }; + cmdBuffer->BeginLabeledSegment("SHBatch"); + cmdBuffer->BindPipeline(pipeline); + cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::TRANSFORM, transformDataBuffer[frameIndex], 0); + cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::INTEGER_DATA, instancedIntegerBuffer[frameIndex], 0); + if (matPropsDescSet[frameIndex]) + { + cmdBuffer->BindDescriptorSet + ( + matPropsDescSet[frameIndex], + SH_PIPELINE_TYPE::GRAPHICS, + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + dynamicOffset + ); + } + cmdBuffer->DrawMultiIndirect(drawDataBuffer[frameIndex], static_cast(drawData.size())); + cmdBuffer->EndLabeledSegment(); + } + + /*---------------------------------------------------------------------------------*/ + /* SHBatch - Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void SHBatch::setAllDirtyFlags() + { + for (bool& dirt : isDirty) + dirt = true; + isCPUBuffersDirty = true; + } + + void SHBatch::rebuildMaterialBuffers(uint32_t frameIndex, Handle descPool) + { + if (matPropsData) + { + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, matPropsBuffer[frameIndex], matPropsData.get(), static_cast(matPropsDataSize), + vk::BufferUsageFlagBits::eStorageBuffer, + "Batch Material Data" + ); + + if (!matPropsDescSet[frameIndex]) + { + matPropsDescSet[frameIndex] = descPool->Allocate + ( + { SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE] }, + { 0 } + ); +#ifdef _DEBUG + const auto& DESC_SETS = matPropsDescSet[frameIndex]->GetVkHandle(); + for (auto descSet : DESC_SETS) + { + SET_VK_OBJ_NAME(device, vk::ObjectType::eDescriptorSet, descSet, "[Descriptor Set] Batch Material Data"); + } +#endif + } + std::array, 1> bufferList = { matPropsBuffer[frameIndex] }; + matPropsDescSet[frameIndex]->ModifyWriteDescBuffer + ( + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, + bufferList, + 0, static_cast(matPropsDataSize) + ); + matPropsDescSet[frameIndex]->UpdateDescriptorSetBuffer + ( + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA + ); + } + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h new file mode 100644 index 00000000..c9dd4eda --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h @@ -0,0 +1,135 @@ +/************************************************************************************//*! +\file SHBatch.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains the definition of SHSubBatch and SHBatch. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +#include +// External Dependencies +#include "Graphics/SHVulkanIncludes.h" +// Project Includes +#include "Resource/SHHandle.h" +#include "Graphics/MiddleEnd/Interface/SHMaterial.h" +#include "Math/SHMatrix.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "ECS_Base/SHECSMacros.h" +#include "Graphics/MiddleEnd/Interface/SHInstancedIntegerData.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkCommandBuffer; + class SHVkPipeline; + class SHMesh; + class SHRenderable; + class SHVkLogicalDevice; + class SHMaterialInstance; + class SHVkDescriptorSetGroup; + class SHVkDescriptorPool; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Describes a segment of the sub batch operation. + */ + /***********************************************************************************/ + struct SHSubBatch + { + public: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle Mesh; + std::unordered_set Renderables; + }; + /***********************************************************************************/ + /*! + \brief + Describes a segment of the sub batch operation. + */ + /***********************************************************************************/ + class SHBatch + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHBatch(Handle pipeline); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + void Add(const SHRenderable* renderable); + void Remove(const SHRenderable* renderable); + void Clear(); + void UpdateMaterialBuffer(uint32_t frameIndex, Handle descPool); + void UpdateTransformBuffer(uint32_t frameIndex); + void UpdateInstancedIntegerBuffer(uint32_t frameIndex); + void Build(Handle device, Handle descPool, uint32_t frameIndex) ; + void Draw(Handle cmdBuffer, uint32_t frameIndex); + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetPipeline() const noexcept { return pipeline; }; + bool IsEmpty() const noexcept { return subBatches.empty(); } + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definition */ + /*-----------------------------------------------------------------------------*/ + using TripleBool = std::array; + using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + using TripleDescSet = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Resources + Handle device; + // Batch Properties + Handle pipeline; + std::unordered_set> referencedMatInstances; + TripleBool matBufferDirty; + // Batch Tree + std::vector subBatches; + TripleBool isDirty; + // CPU Buffers + std::vector drawData; + std::vector transformData; + std::vector instancedIntegerData; + std::unique_ptr matPropsData; + Byte matPropsDataSize = 0; + Byte singleMatPropAlignedSize = 0; + Byte singleMatPropSize = 0; + bool isCPUBuffersDirty = true; + // GPU Buffers + TripleBuffer drawDataBuffer; + TripleBuffer transformDataBuffer; + TripleBuffer instancedIntegerBuffer; + TripleBuffer matPropsBuffer; + TripleDescSet matPropsDescSet; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + void setAllDirtyFlags(); + void rebuildMaterialBuffers(uint32_t frameIndex, Handle descPool); + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.cpp new file mode 100644 index 00000000..dc44e7f9 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.cpp @@ -0,0 +1,134 @@ +/************************************************************************************//*! +\file SHBatcher.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHBatcher.h" +// STL Includes +#include +// Project Includes +#include "SHSuperBatch.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void SHBatcher::Init(Handle _renderGraph) + { + renderGraph = _renderGraph; + } + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void SHBatcher::PrepareBatches() + { + // Iterate through all renderables and send it into the batching + auto& renderables = SHComponentManager::GetDense(); + for (auto iter = renderables.cbegin(); iter != renderables.cend(); ++iter) + { + AddToBatch(&(*iter)); + } + } + + void SHBatcher::AddToBatch(SHRenderable const* renderable) + { + // Get Subpass Index for this renderables pipeline + const Handle SUBPASS = renderable->GetMaterial()->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass(); + + + // Check if there is a SuperBatch for the specific Subpass + auto superBatch = std::find_if(superBatches.begin(), superBatches.end(), [&](Handle superBatch) + { + return superBatch->GetSubpass() == SUBPASS; + }); + + // Ignore and emit a warning if it doesn't exist + if (superBatch == superBatches.end()) + { + SHLOG_WARNING("A renderable was attempted to be added to a SuperBatch which does not exist."); + return; + } + + // Add the Renderable + (*superBatch)->Add(renderable); + } + + void SHBatcher::RemoveFromBatch(SHRenderable const* renderable) + { + // Get Subpass Index for this renderables pipeline + const Handle SUBPASS = renderable->GetMaterial()->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass(); + + // Check if there is a SuperBatch for the specific RenderPass + auto superBatch = std::find_if(superBatches.begin(), superBatches.end(), [&](Handle superBatch) + { + return superBatch->GetSubpass() == SUBPASS; + }); + + // Remove if it exists + if (superBatch == superBatches.end()) + { + SHLOG_WARNING("A renderable was attempted to be removed from a SuperBatch which does not exist."); + return; + } + + (*superBatch)->Remove(renderable); + } + + void SHBatcher::FinaliseBatches(Handle device, Handle descPool, uint32_t frameIndex) + { + // Build SuperBatches + for (auto& batch : superBatches) + { + batch->Build(device, descPool, frameIndex); + } + } + + void SHBatcher::ClearBatches() + { + for (auto& batch : superBatches) + { + batch->Clear(); + } + superBatches.clear(); + } + + void SHBatcher::UpdateBuffers(uint32_t frameIndex, Handle descPool) + { + for (auto& batch : superBatches) + { + batch->UpdateBuffers(frameIndex, descPool); + } + } + + void SHBatcher::RegisterSuperBatch(Handle superBatch) + { + superBatches.emplace_back(superBatch); + } + + void SHBatcher::DeregisterSuperBatch(Handle superBatch) + { + auto sbIter = std::find(superBatches.begin(), superBatches.end(), superBatch); + if (sbIter == superBatches.end()) + return; + + superBatches.erase(sbIter); + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.h b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.h new file mode 100644 index 00000000..f299906c --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatcher.h @@ -0,0 +1,66 @@ +/************************************************************************************//*! +\file SHBatcher.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHRenderable; + class SHRenderGraph; + class SHSuperBatch; + class SHVkLogicalDevice; + class SHVkCommandBuffer; + class SHVkDescriptorPool; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Handles batching of all Renderables assigned to a specific Renderer. + */ + /***********************************************************************************/ + class SHBatcher + { + public: + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + void Init(Handle _renderGraph); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + void PrepareBatches(); + void AddToBatch(SHRenderable const* renderable); + void RemoveFromBatch(SHRenderable const* renderable); + void FinaliseBatches(Handle device, Handle descPool, uint32_t frameIndex); + void ClearBatches(); + void UpdateBuffers(uint32_t frameIndex, Handle descPool); + void RegisterSuperBatch(Handle superBatch); + void DeregisterSuperBatch(Handle superBatch); + + private: + Handle renderGraph; + // Children + std::vector> superBatches; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.cpp new file mode 100644 index 00000000..b827652e --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.cpp @@ -0,0 +1,114 @@ +/************************************************************************************//*! +\file SHSuperBatch.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains the definition of SHSuperBatch's functions. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHSuperBatch.h" + +#include "SHBatch.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*---------------------------------------------------------------------------------*/ + + SHSuperBatch::SHSuperBatch(Handle sp) + : subpass{ sp } + {} + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void SHSuperBatch::Add(const SHRenderable* renderable) noexcept + { + const Handle PIPELINE = renderable->GetMaterial()->GetBaseMaterial()->GetPipeline(); + + // Check if we have a batch with the same pipeline first + auto batch = std::find_if(batches.begin(), batches.end(), [&](const SHBatch& batch) + { + return batch.GetPipeline() == PIPELINE; + }); + + + // Create one if not found + if (batch == batches.end()) + { + batches.emplace_back(PIPELINE); + batch = batches.end() - 1; + } + + // Add renderable in + batch->Add(renderable); + } + + void SHSuperBatch::Remove(const SHRenderable* renderable) noexcept + { + const Handle PIPELINE = renderable->GetMaterial()->GetBaseMaterial()->GetPipeline(); + + // Check if we have a Batch with the same pipeline yet + auto batch = std::find_if(batches.begin(), batches.end(), [&](const SHBatch& batch) + { + return batch.GetPipeline() == PIPELINE; + }); + + // Attempt to remove if it exists + if (batch == batches.end()) + return; + + batch->Remove(renderable); + + // If batch is empty, remove batch + if (batch->IsEmpty()) + batches.erase(batch); + } + + void SHSuperBatch::Clear() noexcept + { + for (auto& batch : batches) + { + batch.Clear(); + } + batches.clear(); + } + + void SHSuperBatch::UpdateBuffers(uint32_t frameIndex, Handle descPool) + { + for (auto& batch : batches) + { + batch.UpdateMaterialBuffer(frameIndex, descPool); + batch.UpdateTransformBuffer(frameIndex); + batch.UpdateInstancedIntegerBuffer(frameIndex); + } + } + + void SHSuperBatch::Build(Handle device, Handle descPool, uint32_t frameIndex) noexcept + { + // Build all batches + for (auto& batch : batches) + { + batch.Build(device, descPool, frameIndex); + } + } + + void SHSuperBatch::Draw(Handle cmdBuffer, uint32_t frameIndex) noexcept + { + // Build all batches + for (auto& batch : batches) + { + batch.Draw(cmdBuffer, frameIndex); + } + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.h b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.h new file mode 100644 index 00000000..9877e04e --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHSuperBatch.h @@ -0,0 +1,76 @@ +/************************************************************************************//*! +\file SHSuperBatch.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains the definition of SHSuperBatch. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Graphics/SHVulkanIncludes.h" +// Project Includes +#include "Resource/SHHandle.h" +#include "SHBatch.h" +#include "Graphics/Pipeline/SHVkPipeline.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkCommandBuffer; + class SHMesh; + class SHRenderable; + class SHVkLogicalDevice; + class SHSubpass; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Stores and manages data for all of the batches associated with a specific SubPass + within a RenderGraph. + */ + /***********************************************************************************/ + class SHSuperBatch + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHSuperBatch(Handle sp); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + void Add(const SHRenderable* renderable) noexcept; + void Remove(const SHRenderable* renderable) noexcept; + void Clear() noexcept; + void UpdateBuffers(uint32_t frameIndex, Handle descPool); + void Build(Handle device, Handle descPool, uint32_t frameIndex) noexcept; + void Draw(Handle cmdBuffer, uint32_t frameIndex) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetSubpass() const noexcept { return subpass; }; + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Batch Properties + Handle subpass; + // Children + std::vector batches; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp new file mode 100644 index 00000000..de42d9a3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp @@ -0,0 +1,141 @@ +#include "SHpch.h" +#include "SHGraphicsGlobalData.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Pipeline/SHPipelineState.h" +#include "Graphics/Pipeline/SHVkPipelineLayout.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/MiddleEnd/Lights/SHLightData.h" +#include "Tools/SHUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Definitions */ + /*-----------------------------------------------------------------------------------*/ + std::vector> SHGraphicsGlobalData::globalDescSetLayouts; + SHVertexInputState SHGraphicsGlobalData::defaultVertexInputState; + Handle SHGraphicsGlobalData::dummyPipelineLayout; + + void SHGraphicsGlobalData::InitHighFrequencyGlobalData(void) noexcept + { + + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + void SHGraphicsGlobalData::InitDescSetLayouts(Handle logicalDevice) noexcept + { + SHVkDescriptorSetLayout::Binding genericDataBinding + { + .Type = vk::DescriptorType::eUniformBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::GENERIC_DATA, + .DescriptorCount = 1, + }; + + SHVkDescriptorSetLayout::Binding texturesBinding + { + .Type = vk::DescriptorType::eCombinedImageSampler, + .Stage = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::IMAGE_AND_SAMPLERS_DATA, + .DescriptorCount = 2000, // we can have up to 2000 textures for now + .flags = vk::DescriptorBindingFlagBits::eVariableDescriptorCount, + }; + + // For global data (generic data and textures) + Handle staticGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS, { genericDataBinding, texturesBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, staticGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] Static Globals"); + + + std::vector lightBindings{}; + + // This is the binding we use to count the lights (binding 0) + lightBindings.push_back(SHVkDescriptorSetLayout::Binding + { + .Type = vk::DescriptorType::eUniformBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = 0, + .DescriptorCount = 1, + + }); + + for (uint32_t i = 1; i <= SHUtilities::ConvertEnum(SH_LIGHT_TYPE::NUM_TYPES); ++i) + { + lightBindings.push_back (SHVkDescriptorSetLayout::Binding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eCompute, + .BindPoint = i, + .DescriptorCount = 1, + }); + } + + // For Dynamic global data (lights) + Handle dynamicGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, lightBindings); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, dynamicGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] Dynamic Globals"); + + // For High frequency global data (camera) + SHVkDescriptorSetLayout::Binding cameraDataBinding + { + .Type = vk::DescriptorType::eUniformBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eCompute, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA, + .DescriptorCount = 1, + }; + Handle cameraDataGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, { cameraDataBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, cameraDataGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] High Frequency Globals"); + + // For per instance data (transforms, materials, etc.) + SHVkDescriptorSetLayout::Binding materialDataBinding + { + .Type = vk::DescriptorType::eStorageBufferDynamic, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, + .DescriptorCount = 1, + }; + Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, { materialDataBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material Globals"); + + globalDescSetLayouts.push_back(staticGlobalLayout); + globalDescSetLayouts.push_back(dynamicGlobalLayout); + globalDescSetLayouts.push_back(cameraDataGlobalLayout); + globalDescSetLayouts.push_back(materialDataPerInstanceLayout); + + + dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{globalDescSetLayouts}); + } + + void SHGraphicsGlobalData::InitDefaultVertexInputState(void) noexcept + { + defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_3D) }); // positions at binding 0 + defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_2D) }); // UVs at binding 1 + defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_3D) }); // Normals at binding 2 + defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_3D) }); // Tangents at binding 3 + defaultVertexInputState.AddBinding(true, true, { SHVertexAttribute(SHAttribFormat::MAT_4D) }); // Transform at binding 4 - 7 (4 slots) + defaultVertexInputState.AddBinding(true, true, { SHVertexAttribute(SHAttribFormat::UINT32_2D) }); // Instanced integer data at index 8 + } + + void SHGraphicsGlobalData::Init(Handle logicalDevice) noexcept + { + InitDescSetLayouts(logicalDevice); + InitDefaultVertexInputState(); + } + + std::vector> const& SHGraphicsGlobalData::GetDescSetLayouts(void) noexcept + { + return globalDescSetLayouts; + } + + + SHVertexInputState const& SHGraphicsGlobalData::GetDefaultViState(void) noexcept + { + return defaultVertexInputState; + } + + Handle SHGraphicsGlobalData::GetDummyPipelineLayout(void) noexcept + { + return dummyPipelineLayout; + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h new file mode 100644 index 00000000..439acba5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h @@ -0,0 +1,49 @@ +#pragma once + +#include "SH_API.h" +#include "Graphics/Pipeline/SHPipelineState.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkDescriptorSetLayout; + class SHVkDescriptorSetGroup; + class SHVkPipelineLayout; + + class SH_API SHGraphicsGlobalData + { + private: + //! Global descriptor set layouts. Used to allocate descriptor sets + static std::vector> globalDescSetLayouts; + + //! Default vertex input state (used by everything). + static SHVertexInputState defaultVertexInputState; + + //! Since we want to bind global data but can't do so without a pipeline layout, + //! we create a dummy pipeline layout to use it for binding. + static Handle dummyPipelineLayout; + + static void InitHighFrequencyGlobalData (void) noexcept; + static void InitDescSetLayouts (Handle logicalDevice) noexcept; + static void InitDefaultVertexInputState (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------*/ + SHGraphicsGlobalData() = delete; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void Init (Handle logicalDevice) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + static std::vector> const& GetDescSetLayouts (void) noexcept; + static SHVertexInputState const& GetDefaultViState (void) noexcept; + static Handle GetDummyPipelineLayout (void) noexcept; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.cpp new file mode 100644 index 00000000..194febbf --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.cpp @@ -0,0 +1,171 @@ +/************************************************************************************//*! +\file SHCamera.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" + +#include "SHCamera.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* View Set Functions */ + /*---------------------------------------------------------------------------------*/ + void SHCamera::SetLookAt(const SHVec3& pos, const SHVec3& target, const SHVec3& up) + { + SHVec3 view = target - pos; view = SHVec3::Normalise(view); + SHVec3 right = SHVec3::Cross(view, up); right = SHVec3::Normalise(right); + const SHVec3 UP = SHVec3::Cross(view, right); + + //viewMatrix = SHMatrix::Identity; + //viewMatrix(0, 0) = UP[0]; + //viewMatrix(1, 0) = UP[1]; + //viewMatrix(2, 0) = UP[2]; + //viewMatrix(0, 1) = right[0]; + //viewMatrix(1, 1) = right[1]; + //viewMatrix(2, 1) = right[2]; + //viewMatrix(0, 2) = view[0]; + //viewMatrix(1, 2) = view[1]; + //viewMatrix(2, 2) = view[2]; + //viewMatrix(3, 0) = -UP.Dot(pos); + //viewMatrix(3, 1) = -right.Dot(pos); + //viewMatrix(3, 2) = -view.Dot(pos); + + viewMatrix = SHMatrix::Identity; + viewMatrix(0, 0) = right[0]; + viewMatrix(0, 1) = right[1]; + viewMatrix(0, 2) = right[2]; + + viewMatrix(1, 0) = UP[0]; + viewMatrix(1, 1) = UP[1]; + viewMatrix(1, 2) = UP[2]; + + viewMatrix(2, 0) = view[0]; + viewMatrix(2, 1) = view[1]; + viewMatrix(2, 2) = view[2]; + + viewMatrix(0, 3) = -right.Dot(pos); + viewMatrix(1, 3) = -UP.Dot(pos); + viewMatrix(2, 3) = -view.Dot(pos); + + + isDirty = true; + } + + /*---------------------------------------------------------------------------------*/ + /* Projection Set Functions */ + /*---------------------------------------------------------------------------------*/ + void SHCamera::SetPerspective(float fov, float width, float height, float zNear, float zFar) + { + const float ASPECT_RATIO = width / height; + const float TAN_HALF_FOV = tan(fov * 0.5f); + projMatrix = SHMatrix::Identity; + projMatrix(0, 0) = 1.0f / (ASPECT_RATIO * TAN_HALF_FOV); + projMatrix(1, 1) = 1.0f / TAN_HALF_FOV; + projMatrix(2, 2) = zFar / (zFar - zNear); + projMatrix(2, 3) = 1.0f; + projMatrix(3, 2) = -(zFar * zNear) / (zFar - zNear); + + isDirty = true; + } + void SHCamera::SetOrthographic(float width, float height, float zNear, float zFar) + { + const float R = width * 0.5f; + const float L = -R; + const float T = height * 0.5f; + const float B = -T; + + projMatrix = SHMatrix::Identity; + projMatrix(0, 0) = 2.0f / (R - L); + projMatrix(1, 1) = 2.0f / (B - T); + projMatrix(2, 2) = 1.0f / (zFar - zNear); + projMatrix(3, 0) = -(R + L) / (R - L); + projMatrix(3, 1) = -(B + T) / (B - T); + projMatrix(3, 2) = -zNear / (zFar - zNear); + + isDirty = true; + } + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + SHMatrix SHCamera::GetViewMatrix() + { + updateMatrices(); + return viewMatrix; + } + + SHMatrix SHCamera::GetProjectionMatrix() + { + updateMatrices(); + return projMatrix; + } + SHMatrix SHCamera::GetViewProjectionMatrix() + { + updateMatrices(); + return vpMatrix; + } + + SHMatrix SHCamera::GetInverseViewMatrix() const + { + return inverseVpMatrix; + } + + SHMatrix SHCamera::GetInverseProjectionMatrix() const + { + return inverseProjMatrix; + } + + SHMatrix SHCamera::GetInverseViewProjectionMatrix() + { + updateMatrices(); + return inverseViewMatrix; + } + + /*---------------------------------------------------------------------------------*/ + /* Mapping Functions */ + /*---------------------------------------------------------------------------------*/ + SHVec3 SHCamera::ScreenToWorld(const SHVec3& vec) const + { + return multiplyHomogenous(inverseVpMatrix, vec); + } + SHVec3 SHCamera::WorldToScreen(const SHVec3& vec) const + { + return multiplyHomogenous(vpMatrix, vec); + } + SHVec3 SHCamera::CameraToWorld(const SHVec3& vec) const + { + return multiplyHomogenous(inverseViewMatrix, vec); + } + SHVec3 SHCamera::WorldToCamera(const SHVec3& vec) const + { + return multiplyHomogenous(viewMatrix, vec); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void SHCamera::updateMatrices() + { + if (isDirty) + { + vpMatrix = viewMatrix * projMatrix; + inverseVpMatrix = SHMatrix::Inverse(vpMatrix); + } + } + + SHVec3 SHCamera::multiplyHomogenous(const SHMatrix& mat, const SHVec3& vec) + { + const SHVec4 HOMO_VEC = { vec[0], vec[1], vec[2], 1.0f }; + const SHVec4 RESULT = mat * HOMO_VEC; + return SHVec3 { RESULT[0], RESULT[1], RESULT[2] }; + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.h new file mode 100644 index 00000000..b23614aa --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHCamera.h @@ -0,0 +1,82 @@ +/************************************************************************************//*! +\file SHCamera.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Math/SHMath.h" + +namespace SHADE +{ + /***********************************************************************************/ + /*! + \brief + Object that manages the view and projection transformations for rendering + a 3D scene. + */ + /***********************************************************************************/ + class SHCamera + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + /*-----------------------------------------------------------------------------*/ + /* View Set Functions */ + /*-----------------------------------------------------------------------------*/ + void SetLookAt(const SHVec3& pos, const SHVec3& target, const SHVec3& up); + + /*-----------------------------------------------------------------------------*/ + /* Projection Set Functions */ + /*-----------------------------------------------------------------------------*/ + void SetPerspective(float fov, float width, float height, float zNear, float zFar); + void SetOrthographic(float width, float height, float zNear, float zFar); + + //void SetPerspectiveMatrixExplicit ( + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + SHMatrix GetViewMatrix(); + SHMatrix GetProjectionMatrix(); + SHMatrix GetViewProjectionMatrix(); + SHMatrix GetInverseViewMatrix() const; + SHMatrix GetInverseProjectionMatrix() const; + SHMatrix GetInverseViewProjectionMatrix(); + + /*-----------------------------------------------------------------------------*/ + /* Mapping Functions */ + /*-----------------------------------------------------------------------------*/ + SHVec3 ScreenToWorld(const SHVec3& vec) const; + SHVec3 WorldToScreen(const SHVec3& vec) const; + SHVec3 CameraToWorld(const SHVec3& vec) const; + SHVec3 WorldToCamera(const SHVec3& vec) const; + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + SHMatrix viewMatrix; + SHMatrix projMatrix; + SHMatrix vpMatrix; + SHMatrix inverseViewMatrix; + SHMatrix inverseProjMatrix; + SHMatrix inverseVpMatrix; + bool isDirty = true; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + void updateMatrices(); + static SHVec3 multiplyHomogenous(const SHMatrix& mat, const SHVec3& vec); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp new file mode 100644 index 00000000..60262607 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -0,0 +1,332 @@ +/************************************************************************************//*! +\file SHDebugDrawSystem.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 16, 2022 +\brief Contains the definition of functions of the SHDebugDrawSystem class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHDebugDrawSystem.h" +// STL Includes +#include +// Project Includes +#include "Assets/Asset Types/SHModelAsset.h" +#include "../Meshes/SHPrimitiveGenerator.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "SHGraphicsSystem.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "../../SHVkUtil.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* DrawRoutine */ + /*---------------------------------------------------------------------------------*/ + SHDebugDrawSystem::ProcessPointsRoutine::ProcessPointsRoutine() + : SHSystemRoutine("Debug Draw", true) + { + SystemFamily::GetID(); + } + + void SHDebugDrawSystem::ProcessPointsRoutine::Execute(double dt) noexcept + { + auto gfxSys = SHSystemManager::GetSystem(); + if (!gfxSys) + { + SHLOG_WARNING("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; + } + + // Get the system + SHDebugDrawSystem* system = static_cast(GetSystem()); + + // Get current frame index + const uint32_t FRAME_IDX = gfxSys->GetCurrentFrameIndex(); + + /* Non-Persistent Buffer */ + // Update the buffer + system->numPoints[FRAME_IDX] = system->points.size(); + const uint32_t DATA_SIZE = sizeof(PointVertex) * system->points.size(); + if (DATA_SIZE > 0) + { + system->vertexBuffers[FRAME_IDX]->WriteToMemory(system->points.data(), DATA_SIZE, 0, 0); + } + + // Reset for next frame + system->points.clear(); + + /* Persistent Buffer */ + // Check if there are changes + if (system->persistentBuffersCleared[FRAME_IDX] + || + system->numPersistentPoints[FRAME_IDX] != system->persistentPoints.size()) + { + // Update Buffer + system->numPersistentPoints[FRAME_IDX] = system->persistentPoints.size(); + const uint32_t DATA_SIZE = sizeof(PointVertex) * system->persistentPoints.size(); + if (DATA_SIZE > 0) + { + system->persistentVertexBuffers[FRAME_IDX]->WriteToMemory(system->persistentPoints.data(), DATA_SIZE, 0, 0); + } + + // Reset Flag + system->persistentBuffersCleared[FRAME_IDX] = false; + } + } + + /*---------------------------------------------------------------------------------*/ + /* SHSystem overrides */ + /*---------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::Init() + { + // Register function for subpass + const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); + auto const& RENDERERS = GFX_SYSTEM->GetDefaultViewport()->GetRenderers(); + auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); + auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); + subPass->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer) + { + // Get Current frame index + const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); + + // Don't draw if no points + if (numPoints[FRAME_IDX] > 0) + { + cmdBuffer->BeginLabeledSegment("SHDebugDraw"); + cmdBuffer->BindPipeline(GFX_SYSTEM->GetDebugDrawPipeline()); + cmdBuffer->SetLineWidth(LineWidth); + cmdBuffer->BindVertexBuffer(0, vertexBuffers[FRAME_IDX], 0); + cmdBuffer->DrawArrays(numPoints[FRAME_IDX], 1, 0, 0); + } + }); + auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); + subPassWithDepth->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer) + { + // Get Current frame index + const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); + + // Don't draw if no points + if (numPersistentPoints[FRAME_IDX] > 0) + { + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Persistent)"); + cmdBuffer->BindPipeline(GFX_SYSTEM->GetDebugDrawDepthPipeline()); + cmdBuffer->SetLineWidth(LineWidth); + cmdBuffer->BindVertexBuffer(0, persistentVertexBuffers[FRAME_IDX], 0); + cmdBuffer->DrawArrays(numPersistentPoints[FRAME_IDX], 1, 0, 0); + cmdBuffer->EndLabeledSegment(); + } + }); + + // Reset trackers + std::fill_n(numPoints.begin(), numPoints.size(), 0); + std::fill_n(numPersistentPoints.begin(), numPersistentPoints.size(), 0); + for (bool& cleared : persistentBuffersCleared) + cleared = true; + + // Allocate buffers + // - Non-Persistent Draws + static constexpr uint32_t BUFFER_SIZE = MAX_POINTS * sizeof(PointVertex); + for (Handle& bufHandle : vertexBuffers) + { + bufHandle = GFX_SYSTEM->GetDevice()->CreateBuffer + ( + BUFFER_SIZE, + nullptr, + 0, + vk::BufferUsageFlagBits::eVertexBuffer, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, + "Debug Draw Non-Persistent Vertex Buffer" + ); + } + // - Persistent Draws + for (Handle& bufHandle : persistentVertexBuffers) + { + bufHandle = GFX_SYSTEM->GetDevice()->CreateBuffer + ( + BUFFER_SIZE, + nullptr, + 0, + vk::BufferUsageFlagBits::eVertexBuffer, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, + "Debug Draw Persistent Vertex Buffer" + ); + } + } + + void SHDebugDrawSystem::Exit() + { + for (auto vertexBuffer : vertexBuffers) + { + if (vertexBuffer) + vertexBuffer.Free(); + } + for (auto vertexBuffer : persistentVertexBuffers) + { + if (vertexBuffer) + vertexBuffer.Free(); + } + } + + /*---------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*---------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::DrawLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + { + drawLine(points, color, startPt, endPt); + } + + void SHDebugDrawSystem::DrawTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + { + drawPoly(points, color, { pt1, pt2, pt3 }); + } + + void SHDebugDrawSystem::DrawQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + { + drawPoly(points, color, { pt1, pt2, pt3, pt4 }); + } + + void SHDebugDrawSystem::DrawPoly(const SHVec4& color, std::initializer_list pointList) + { + drawPoly(points, color, pointList.begin(), pointList.end()); + } + + void SHDebugDrawSystem::DrawCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + { + drawCube(points, color, pos, size); + } + + void SHDebugDrawSystem::DrawSphere(const SHVec4& color, const SHVec3& pos, double radius) + { + drawSphere(points, color, pos, radius); + } + + /*---------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*---------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::DrawPersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + { + drawLine(persistentPoints, color, startPt, endPt); + } + + void SHDebugDrawSystem::DrawPersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + { + drawPoly(persistentPoints, color, { pt1, pt2, pt3 }); + } + + void SHDebugDrawSystem::DrawPersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + { + drawPoly(persistentPoints, color, { pt1, pt2, pt3, pt4 }); + } + + void SHDebugDrawSystem::DrawPersistentPoly(const SHVec4& color, std::initializer_list pointList) + { + drawPoly(persistentPoints, color, pointList.begin(), pointList.end()); + } + + void SHDebugDrawSystem::DrawPersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + { + drawCube(persistentPoints, color, pos, size); + } + + void SHDebugDrawSystem::DrawPersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) + { + drawSphere(persistentPoints, color, pos, radius); + } + + void SHDebugDrawSystem::ClearPersistentDraws() + { + persistentPoints.clear(); + for (bool& cleared : persistentBuffersCleared) + cleared = true; + } + + void SHDebugDrawSystem::drawLine(std::vector& storage, const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + { + if (storage.size() > MAX_POINTS) + { + SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); + return; + } + + storage.emplace_back(PointVertex{ startPt, color }); + storage.emplace_back(PointVertex{ endPt, color }); + } + + void SHDebugDrawSystem::drawLineSet(std::vector& storage, const SHVec4& color, std::initializer_list pointList) + { + drawLineSet(storage, color, pointList.begin(), pointList.end()); + } + + void SHDebugDrawSystem::drawPoly(std::vector& storage, const SHVec4& color, std::initializer_list pointList) + { + drawPoly(storage, color, pointList.begin(), pointList.end()); + } + + void SHDebugDrawSystem::drawCube(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& size) + { + static const SHVec3 EXTENTS = SHVec3{ 0.5f, 0.5f, 0.5f }; + static const SHVec3 UNIT_BOT_LEFT_FRONT = SHVec3{ pos - EXTENTS }; + static const SHVec3 UNIT_BOT_RIGHT_FRONT = SHVec3{ pos + SHVec3 { EXTENTS.x, -EXTENTS.y, -EXTENTS.z } }; + static const SHVec3 UNIT_BOT_RIGHT_BACK = SHVec3{ pos + SHVec3 { EXTENTS.x, -EXTENTS.y, EXTENTS.z } }; + static const SHVec3 UNIT_BOT_LEFT_BACK = SHVec3{ pos + SHVec3 { -EXTENTS.x, -EXTENTS.y, EXTENTS.z } }; + static const SHVec3 UNIT_TOP_LEFT_BACK = SHVec3{ pos + SHVec3 { -EXTENTS.x, EXTENTS.y, EXTENTS.z } }; + static const SHVec3 UNIT_TOP_RIGHT_FRONT = SHVec3{ pos + SHVec3 { EXTENTS.x, EXTENTS.y, -EXTENTS.z } }; + static const SHVec3 UNIT_TOP_LEFT_FRONT = SHVec3{ pos + SHVec3 { -EXTENTS.x, EXTENTS.y, -EXTENTS.z } }; + static const SHVec3 UNIT_TOP_RIGHT_BACK = SHVec3{ pos + EXTENTS }; + + const SHVec3 BOT_LEFT_BACK = UNIT_BOT_LEFT_BACK * size; + const SHVec3 BOT_RIGHT_BACK = UNIT_BOT_RIGHT_BACK * size; + const SHVec3 BOT_LEFT_FRONT = UNIT_BOT_LEFT_FRONT * size; + const SHVec3 BOT_RIGHT_FRONT = UNIT_BOT_RIGHT_FRONT * size; + const SHVec3 TOP_LEFT_BACK = UNIT_TOP_LEFT_BACK * size; + const SHVec3 TOP_RIGHT_BACK = UNIT_TOP_RIGHT_BACK * size; + const SHVec3 TOP_LEFT_FRONT = UNIT_TOP_LEFT_FRONT * size; + const SHVec3 TOP_RIGHT_FRONT = UNIT_TOP_RIGHT_FRONT * size; + + drawLineSet + ( + storage, + color, + { + // Bottom Square + BOT_LEFT_BACK , BOT_RIGHT_BACK, + BOT_RIGHT_BACK , BOT_RIGHT_FRONT, + BOT_RIGHT_FRONT, BOT_LEFT_FRONT, + BOT_LEFT_FRONT , BOT_LEFT_BACK, + // Top Square + TOP_LEFT_BACK , TOP_RIGHT_BACK, + TOP_RIGHT_BACK , TOP_RIGHT_FRONT, + TOP_RIGHT_FRONT, TOP_LEFT_FRONT, + TOP_LEFT_FRONT , TOP_LEFT_BACK, + // Middle Lines + TOP_LEFT_BACK , BOT_LEFT_BACK, + TOP_RIGHT_BACK , BOT_RIGHT_BACK, + TOP_RIGHT_FRONT, BOT_RIGHT_FRONT, + TOP_LEFT_FRONT , BOT_LEFT_FRONT + } + ); + } + + void SHDebugDrawSystem::drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius) + { + //if (spherePoints.empty()) + { + spherePoints.clear(); + // Generate + static const SHMeshData SPHERE = SHPrimitiveGenerator::Sphere(); + for (const auto& idx : SPHERE.Indices) + { + spherePoints.emplace_back(SPHERE.VertexPositions[idx] * radius + pos); + } + } + drawLineSet(storage, color, spherePoints.begin(), spherePoints.end()); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h new file mode 100644 index 00000000..20ddcd42 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -0,0 +1,253 @@ +/************************************************************************************//*! +\file SHDebugDrawSystem.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 16, 2022 +\brief Contains the definition of the SHDebugDrawSystem class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "SH_API.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Resource/SHHandle.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "SHGraphicsConstants.h" +#include "Math/SHColour.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHVkBuffer; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /// + /// Manages the Debug Draw system. + /// + class SH_API SHDebugDrawSystem final : public SHSystem + { + public: + /*---------------------------------------------------------------------------------*/ + /* System Routines */ + /*---------------------------------------------------------------------------------*/ + class SH_API ProcessPointsRoutine final : public SHSystemRoutine + { + public: + ProcessPointsRoutine(); + virtual void Execute(double dt) noexcept override final; + }; + + /*---------------------------------------------------------------------------------*/ + /* SHSystem overrides */ + /*---------------------------------------------------------------------------------*/ + virtual void Init() override final; + virtual void Exit() override final; + + /*---------------------------------------------------------------------------------*/ + /* Configuration Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Configures the line width used to draw all lines in the Debug Draw system. + /// + float LineWidth = 1.0f; + + /*---------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space. + /// + /// Colour of the line. + /// First point of the line. + /// Second point of the line. + void DrawLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + /// + /// Renders a triangle indicated by three points in world space. + /// + /// Colour of the triangle. + /// First point of the triangle. + /// Second point of the triangle. + /// Third point of the triangle. + void DrawTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); + /// + /// Renders a quadrilateral indicated by four points in world space. + /// + /// Colour of the quadrilateral. + /// First point of the triangle. + /// Second point of the quadrilateral. + /// Third point of the quadrilateral. + /// Third point of the quadrilateral. + void DrawQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + /// + /// Renders a polygon indicated by the specified set of points in world space. + /// + /// Colour of the polygon. + /// List of points for the polygon. + void DrawPoly(const SHVec4& color, std::initializer_list pointList); + /// + /// Renders a polygon indicated by the specified set of points in world space. + /// + /// Iterator for a STL-like container. + /// Colour of the polygon. + /// + /// Iterator to the first point of the point container. + /// + /// + /// One past last iterator of the point container. + /// + template + void DrawPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd); + /// + /// Renders a wireframe cube centered around the position specified in world space. + /// + /// Colour of the cube. + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + void DrawCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); + /// + /// Renders a wireframe sphere centered around the position specified in world space. + /// + /// Colour of the sphere. + /// Position where the sphere wil be centered at. + /// Size of the rendered sphere. + void DrawSphere(const SHVec4& color, const SHVec3& pos, double radius); + + /*---------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space that will persist until + /// ClearPersistentDraws() is called. These lines are depth tested. + /// + /// Colour of the line. + /// First point of the line. + /// Second point of the line. + void DrawPersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + /// + /// Renders a triangle indicated by three points in world space that will persist + /// until ClearPersistentDraws() is called. These lines are depth tested. + /// + /// Colour of the triangle. + /// First point of the triangle. + /// Second point of the triangle. + /// Third point of the triangle. + void DrawPersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); + /// + /// Renders a quadrilateral indicated by four points in world space that will persist + /// until ClearPersistentDraws() is called. These lines are depth tested. + /// + /// Colour of the quadrilateral. + /// First point of the triangle. + /// Second point of the quadrilateral. + /// Third point of the quadrilateral. + /// Third point of the quadrilateral. + void DrawPersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + /// + /// Renders a polygon indicated by the specified set of points in world space that + /// will persist until ClearPersistentDraws() is called. These lines are depth + /// tested. + /// + /// Colour of the polygon. + /// List of points for the polygon. + void DrawPersistentPoly(const SHVec4& color, std::initializer_list pointList); + /// + /// Renders a polygon indicated by the specified set of points in world space that + /// will persist until ClearPersistentDraws() is called. These lines are depth + /// tested. + /// + /// Iterator for a STL-like container. + /// Colour of the polygon. + /// + /// Iterator to the first point of the point container. + /// + /// + /// One past last iterator of the point container. + /// + template + void DrawPersistentPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd); + /// + /// Renders a wireframe cube centered around the position specified in world space + /// that will persist until ClearPersistentDraws() is called. These lines are depth + /// tested. + /// + /// Colour of the cube. + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + void DrawPersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); + /// + /// Renders a wireframe sphere centered around the position specified in world space + /// that will persist until ClearPersistentDraws() is called. These lines are depth + /// tested. + /// + /// Colour of the sphere. + /// Position where the sphere wil be centered at. + /// Size of the rendered sphere. + void DrawPersistentSphere(const SHVec4& color, const SHVec3& pos, double radius); + /// + /// Clears any persistent drawn debug primitives. + /// + void ClearPersistentDraws(); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + struct SH_API PointVertex + { + SHVec4 Position; + SHVec4 Color; + }; + using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + using TripleUInt = std::array; + using TripleBool = std::array; + + /*---------------------------------------------------------------------------------*/ + /* Constants */ + /*---------------------------------------------------------------------------------*/ + static constexpr uint32_t MAX_POINTS = 100'000; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + // CPU Buffers + std::vector points; + std::vector persistentPoints; + // GPU Buffers + TripleBuffer vertexBuffers; + TripleUInt numPoints; + TripleBuffer persistentVertexBuffers; + TripleUInt numPersistentPoints; + TripleBool persistentBuffersCleared; + // Cached Points for polygon drawing + std::vector spherePoints; + + /*---------------------------------------------------------------------------------*/ + /* Helper Draw Functions */ + /*---------------------------------------------------------------------------------*/ + void drawLine(std::vector& storage, const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + void drawLineSet(std::vector& storage, const SHVec4& color, std::initializer_list pointList); + template + void drawLineSet(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd); + void drawPoly(std::vector& storage, const SHVec4& color, std::initializer_list pointList); + template + void drawPoly(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd); + void drawCube(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& size); + void drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius); + }; +} + +#include "SHDebugDrawSystem.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp new file mode 100644 index 00000000..2a171e73 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp @@ -0,0 +1,89 @@ +/************************************************************************************//*! +\file SHDebugDrawSystem.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 16, 2022 +\brief Contains the definition of template functions the SHDebugDrawSystem + class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +#include "SHDebugDrawSystem.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + template + void SHDebugDrawSystem::DrawPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd) + { + drawPoly(points, color, pointListBegin, pointListEnd); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + template + void SHDebugDrawSystem::drawLineSet(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd) + { + // Ensure dereferenced type is SHVec3 + static_assert(std::is_same_v>, "Parameters to DrawPoly must be SHVec3."); + + // Check if points exceeded max + if (storage.size() > MAX_POINTS) + { + SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); + return; + } + + const size_t POINTS_COUNT = pointListEnd - pointListBegin; + // Invalid polygon + if (POINTS_COUNT < 2) + { + SHLOG_WARNING("[SHDebugDraw] Invalid polygon provided to DrawPoly()."); + return; + } + + const size_t POINTS_ROUNDED_COUNT = POINTS_COUNT / 2 * 2; + for (auto pointIter = pointListBegin; pointIter != (pointListBegin + POINTS_ROUNDED_COUNT); ++pointIter) + { + storage.emplace_back(PointVertex{ *pointIter, color }); + } + } + template + void SHDebugDrawSystem::drawPoly(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd) + { + // Ensure dereferenced type is SHVec3 + static_assert(std::is_same_v>, "Parameters to DrawPoly must be SHVec3."); + + // Check if points exceeded max + if (storage.size() > MAX_POINTS) + { + SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); + return; + } + + const size_t POINTS_COUNT = pointListEnd - pointListBegin; + // Invalid polygon + if (POINTS_COUNT < 2) + { + SHLOG_WARNING("[SHDebugDraw] Invalid polygon provided to DrawPoly()."); + return; + } + + // Trace the polygon + for (auto pointIter = pointListBegin + 1; pointIter != pointListEnd; ++pointIter) + { + storage.emplace_back(PointVertex{ *(pointIter - 1), color }); + storage.emplace_back(PointVertex{ *pointIter , color }); + } + + // Close the line loop + storage.emplace_back(PointVertex{ *(pointListEnd - 1), color }); + storage.emplace_back(PointVertex{ *pointListBegin , color }); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h new file mode 100644 index 00000000..0a67cd9f --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -0,0 +1,190 @@ +/************************************************************************************//*! +\file SHGraphicsSystem.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains definition of constants that relate to the SHGraphicsSystem. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include + +namespace SHADE +{ + /***********************************************************************************/ + /*! + \brief + Contains definition of constants related to the Graphics System. + */ + /***********************************************************************************/ + struct SHGraphicsConstants + { + public: + struct RenderGraphIndices + { + static constexpr uint32_t WORLD = 0; + static constexpr uint32_t EDITOR = 0; + }; + + struct DescriptorSetIndex + { + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for static global values like generic data, and + texture samplers + */ + /***************************************************************************/ + static constexpr uint32_t STATIC_GLOBALS = 0; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for dynamic global values like lights. + */ + /***************************************************************************/ + static constexpr uint32_t DYNAMIC_GLOBALS = 1; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for high frequency changing global values like + camera matrices. + */ + /***************************************************************************/ + static constexpr uint32_t HIGH_FREQUENCY_GLOBALS = 2; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for per-instance/material changing values. + */ + /***************************************************************************/ + static constexpr uint32_t PER_INSTANCE = 3; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for render graph resources. + */ + /***************************************************************************/ + static constexpr uint32_t RENDERGRAPH_RESOURCE = 4; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for render graph node compute resources. For data + that we wish to pass to compute shaders in the render graph, this is + the set to use. Unlike the sets from 1 to 3, this set index does not have + hard coded bindings and is NOT part of the layouts included in the global + data. + */ + /***************************************************************************/ + static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5; + + }; + + struct DescriptorSetBindings + { + /***************************************************************************/ + /*! + \brief + DescriptorSet binding for generic data. + + */ + /***************************************************************************/ + static constexpr uint32_t GENERIC_DATA = 0; + + /***************************************************************************/ + /*! + \brief + DescriptorSet binding for combined image sampler data. + + */ + /***************************************************************************/ + static constexpr uint32_t IMAGE_AND_SAMPLERS_DATA = 1; + + /***************************************************************************/ + /*! + \brief + DescriptorSet binding for combined image sampler data. + + */ + /***************************************************************************/ + static constexpr uint32_t LIGHTING_COUNT = 0; + + /***************************************************************************/ + /*! + \brief + DescriptorSet binding for lights. + + */ + /***************************************************************************/ + static constexpr uint32_t CAMERA_DATA = 0; + + /***************************************************************************/ + /*! + \brief + DescriptorSet binding for per instance material values. + */ + /***************************************************************************/ + static constexpr uint32_t BATCHED_PER_INST_DATA = 0; + + }; + + struct VertexBufferBindings + { + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the transform matrix buffer. + */ + /***************************************************************************/ + static constexpr uint32_t POSITION = 0; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the transform matrix buffer. + */ + /***************************************************************************/ + static constexpr uint32_t TEX_COORD = 1; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the transform matrix buffer. + */ + /***************************************************************************/ + static constexpr uint32_t NORMAL = 2; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the transform matrix buffer. + */ + /***************************************************************************/ + static constexpr uint32_t TANGENT = 3; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the transform matrix buffer. + */ + /***************************************************************************/ + static constexpr uint32_t TRANSFORM = 4; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the eid buffer. + */ + /***************************************************************************/ + static constexpr uint32_t INTEGER_DATA = 5; + + }; + + /*******************************************************************************/ + /*! + \brief + Number of frame buffers used by the Graphics System. + */ + /*******************************************************************************/ + static constexpr int NUM_FRAME_BUFFERS = 3; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index ad70a18f..15c8ec5d 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -1,238 +1,997 @@ /************************************************************************************//*! -\file SHGraphicsSystem.cpp +\file SHGrphicsSystem.cpp \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Aug 21, 2022 -\brief +\brief + - Copyright (C) 2022 DigiPen Institute of Technology. -Reproduction or disclosure of this file or its contents without the prior written consent +Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ -#include "SHPch.h" +#include "SHpch.h" #include "SHGraphicsSystem.h" #include "Graphics/Instance/SHVkInstance.h" #include "Graphics/Windowing/Surface/SHVkSurface.h" #include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Camera/SHCameraSystem.h" +#include "Editor/SHEditor.h" +#include "ECS_Base/Managers/SHSystemManager.h" //#include "SHRenderer.h" #include "Graphics/Windowing/SHWindow.h" #include "Graphics/MiddleEnd/PerFrame/SHPerFrameData.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Graphics/MiddleEnd/Interface/SHMaterial.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/MiddleEnd/Batching/SHSuperBatch.h" +#include "SHGraphicsConstants.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Images/SHVkSampler.h" +#include "Assets/Asset Types/SHTextureAsset.h" +#include "Graphics/MiddleEnd/Interface/SHMousePickSystem.h" +#include "Graphics/MiddleEnd/Lights/SHLightingSubSystem.h" +#include "Assets/SHAssetManager.h" +#include "Resource/SHResourceManager.h" +#include "Graphics/SHVkUtil.h" +#include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" +#include "../Meshes/SHPrimitiveGenerator.h" namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Constructor/Destructors */ - /*---------------------------------------------------------------------------------*/ - SHGraphicsSystem::SHGraphicsSystem(SHWindow& window) - { - // Save the SHWindow - this->window = &window; +#pragma region INIT_EXIT - // Set Up Instance - SHVkInstance::Init(true, true, true); + void SHGraphicsSystem::InitBoilerplate(void) noexcept + { + /*-----------------------------------------------------------------------*/ + /* BACKEND BOILERPLATE */ + /*-----------------------------------------------------------------------*/ + // Set Up Instance + #ifdef _DEBUG + SHVkInstance::Init(true, true, true); + #else + SHVkInstance::Init(true, false, true); + #endif - // Get Physical Device and Construct Logical Device - // TODO: Provide configuration for these options - physicalDevice = SHVkInstance::CreatePhysicalDevice(SH_PHYSICAL_DEVICE_TYPE::BEST); - device = SHVkInstance::CreateLogicalDevice({ SHQueueParams(SH_Q_FAM::GRAPHICS, SH_QUEUE_SELECT::DEDICATED), SHQueueParams(SH_Q_FAM::TRANSFER, SH_QUEUE_SELECT::DEDICATED) }, physicalDevice); + // Get Physical Device and Construct Logical Device + physicalDevice = SHVkInstance::CreatePhysicalDevice(SH_PHYSICAL_DEVICE_TYPE::BEST); + device = SHVkInstance::CreateLogicalDevice({ SHQueueParams(SH_Q_FAM::GRAPHICS, SH_QUEUE_SELECT::DEDICATED), SHQueueParams(SH_Q_FAM::TRANSFER, SH_QUEUE_SELECT::DEDICATED) }, physicalDevice); - // Construct surface - surface = device->CreateSurface(window.GetHWND()); + // Construct surface + surface = device->CreateSurface(window->GetHWND()); - // Construct Swapchain - auto windowDims = window.GetWindowSize(); - swapchain = device->CreateSwapchain(surface, windowDims.first, windowDims.second, SHSwapchainParams - { - .surfaceImageFormats {vk::Format::eB8G8R8A8Unorm, vk::Format::eR8G8B8A8Unorm, vk::Format::eB8G8R8Unorm, vk::Format::eR8G8B8Unorm}, - .depthFormats {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - .presentModes {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate}, - .vsyncOn = false, // TODO: Set to true when shipping game - }); + // Construct Swapchain + auto windowDims = window->GetWindowSize(); + swapchain = device->CreateSwapchain(surface, windowDims.first, windowDims.second, SHSwapchainParams + { + .surfaceImageFormats {vk::Format::eB8G8R8A8Unorm, vk::Format::eR8G8B8A8Unorm, vk::Format::eB8G8R8Unorm, vk::Format::eR8G8B8Unorm}, + .depthFormats {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + .presentModes {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate}, + .vsyncOn = false, // TODO: Set to true when shipping game + }); - window.RegisterWindowSizeCallback([&]([[maybe_unused]] uint32_t width, [[maybe_unused]] uint32_t height) - { - renderContext.SetIsResized(true); - }); + // Register callback to notify render context upon a window resize + window->RegisterWindowSizeCallback([&]([[maybe_unused]] uint32_t width, [[maybe_unused]] uint32_t height) + { + if (width == 0 || height == 0) + return; - // Create graphics queue - queue = device->GetQueue(SH_Q_FAM::GRAPHICS, 0); - + PrepareResize(resizeWidth, resizeHeight); + }); - // Create Render Context - renderContext.Init - ( - device, - SHPerFrameDataParams - { - .swapchainHdl = swapchain, - .numThreads = 1, - .cmdPoolQueueFamilyType = SH_Q_FAM::GRAPHICS, - .cmdPoolResetMode = SH_CMD_POOL_RESET::POOL_BASED, - .cmdBufferTransient = true, - } - ); + window->RegisterWindowCloseCallback([&](void) + { + renderContext.SetWindowIsDead(true); + } + ); + // Create graphics queue + graphicsQueue = device->GetQueue(SH_Q_FAM::GRAPHICS, 0); + transferQueue = device->GetQueue(SH_Q_FAM::TRANSFER, 0); - // Create Frame and Command Buffers - for (int i = 0; i < NUM_FRAME_BUFFERS; ++i) - { - //frameBuffers[i] = device->CreateFramebuffer(renderPass, { renderContext.GetFrameData(i).swapchainImageViewHdl }, windowDims[0], windowDims[1]); - SHPerFrameData& frameData = renderContext.GetFrameData(i); - if (frameData.cmdPoolHdls.empty()) - throw std::runtime_error("No command pools available!"); + // Create Render Context + renderContext.Init + ( + device, + SHPerFrameDataParams + { + .swapchainHdl = swapchain, + .numThreads = 1, + .cmdPoolQueueFamilyType = SH_Q_FAM::GRAPHICS, + .cmdPoolResetMode = SH_CMD_POOL_RESET::POOL_BASED, + .cmdBufferTransient = true, + } + ); - Handle commandPool = frameData.cmdPoolHdls[0]; - //commandBuffers[i] = commandPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); // works - } + // Create a descriptor pool + descPool = device->CreateDescriptorPools(); + + // Create generic command buffer + graphicsCmdPool = device->CreateCommandPool(SH_QUEUE_FAMILY_ARRAY_INDEX::GRAPHICS, SH_CMD_POOL_RESET::POOL_BASED, true); + + // Load Built In Shaders + static constexpr AssetID VS_DEFAULT = 39210065; defaultVertShader = SHResourceManager::LoadOrGet(VS_DEFAULT); + static constexpr AssetID FS_DEFAULT = 46377769; defaultFragShader = SHResourceManager::LoadOrGet(FS_DEFAULT); + static constexpr AssetID VS_DEBUG = 48002439; debugVertShader = SHResourceManager::LoadOrGet(VS_DEBUG); + static constexpr AssetID FS_DEBUG = 36671027; debugFragShader = SHResourceManager::LoadOrGet(FS_DEBUG); + static constexpr AssetID CS_COMPOSITE = 45072428; deferredCompositeShader = SHResourceManager::LoadOrGet(CS_COMPOSITE); + static constexpr AssetID SSAO = 38430899; ssaoShader = SHResourceManager::LoadOrGet(SSAO); + static constexpr AssetID SSAO_BLUR = 39760835; ssaoBlurShader = SHResourceManager::LoadOrGet(SSAO_BLUR); + } + + void SHGraphicsSystem::InitSceneRenderGraph(void) noexcept + { + /*-----------------------------------------------------------------------*/ + /* MIDDLE END SETUP + - Viewports + - Renderer + - Render graph in renderers + - Render graph command buffer semaphores + - Default vertex input state + - Global data + /*-----------------------------------------------------------------------*/ + auto windowDims = window->GetWindowSize(); + + auto cameraSystem = SHSystemManager::GetSystem(); + + // Set Up Cameras + screenCamera = resourceManager.Create(); + screenCamera->SetLookAt(SHVec3(0.0f, 0.0f, -1.0f), SHVec3(0.0f, 0.0f, 1.0f), SHVec3(0.0f, 1.0f, 0.0f)); + screenCamera->SetOrthographic(static_cast(windowDims.first), static_cast(windowDims.second), 0.01f, 100.0f); + + worldCamera = resourceManager.Create(); + worldCamera->SetLookAt(SHVec3(0.0f, 0.0f, 0.0f), SHVec3(0.0f, 0.0f, -2.0f), SHVec3(0.0f, 1.0f, 0.0f)); + worldCamera->SetPerspective(90.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 100.0f); + + // Create Default Viewport + worldViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(window->GetWindowSize().first), static_cast(window->GetWindowSize().second), 0.0f, 1.0f)); + + // Get render graph from default viewport world renderer + worldRenderGraph = resourceManager.Create(); + + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + { + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + } + + /*-----------------------------------------------------------------------*/ + /* SCENE RENDER GRAPH RESOURCES */ + /*-----------------------------------------------------------------------*/ + // Initialize world render graph + worldRenderGraph->Init("World Render Graph", device, swapchain); + worldRenderGraph->AddResource("Position", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + worldRenderGraph->AddResource("Normals", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + //worldRenderGraph->AddResource("Tangents", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + worldRenderGraph->AddResource("Albedo", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second); + worldRenderGraph->AddResource("Depth Buffer", { SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL }, windowDims.first, windowDims.second, vk::Format::eD32SfloatS8Uint); + worldRenderGraph->AddResource("Entity ID", { SH_ATT_DESC_TYPE_FLAGS::COLOR }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + worldRenderGraph->AddResource("Light Layer Indices", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + worldRenderGraph->AddResource("Scene", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second); + worldRenderGraph->AddResource("SSAO", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + worldRenderGraph->AddResource("SSAO Blur", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + + /*-----------------------------------------------------------------------*/ + /* MAIN NODE */ + /*-----------------------------------------------------------------------*/ + auto gBufferNode = worldRenderGraph->AddNode("G-Buffer", + { + "Position", + "Entity ID", + "Light Layer Indices", + "Normals", + //"Tangents", + "Albedo", + "Depth Buffer", + "Scene", + "SSAO", + "SSAO Blur" + }, + {}); // no predecessors + + /*-----------------------------------------------------------------------*/ + /* G-BUFFER SUBPASS INIT */ + /*-----------------------------------------------------------------------*/ + auto gBufferSubpass = gBufferNode->AddSubpass("G-Buffer Write"); + gBufferSubpass->AddColorOutput("Position"); + gBufferSubpass->AddColorOutput("Entity ID"); + gBufferSubpass->AddColorOutput("Light Layer Indices"); + gBufferSubpass->AddColorOutput("Normals"); + //gBufferSubpass->AddColorOutput("Tangents"); + gBufferSubpass->AddColorOutput("Albedo"); + gBufferSubpass->AddDepthOutput("Depth Buffer", SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL); + + /*-----------------------------------------------------------------------*/ + /* SSAO PASS AND DATA INIT */ + /*-----------------------------------------------------------------------*/ + ssaoStorage = resourceManager.Create(); + + ssaoTransferCmdBuffer = graphicsCmdPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + SET_VK_OBJ_NAME(device, vk::ObjectType::eCommandBuffer, ssaoTransferCmdBuffer->GetVkCommandBuffer(), "[Command Buffer] SSAO Pass (Graphics)"); + ssaoTransferCmdBuffer->BeginRecording(); + + ssaoStorage->Init(device, ssaoTransferCmdBuffer); + + ssaoTransferCmdBuffer->EndRecording(); + graphicsQueue->SubmitCommandBuffer({ ssaoTransferCmdBuffer }); + // Set up Debug Draw Passes + // - Depth Tested + auto debugDrawNodeDepth = worldRenderGraph->AddNode("Debug Draw with Depth", { "Scene", "Depth Buffer" }, {"G-Buffer"}); + auto debugDrawDepthSubpass = debugDrawNodeDepth->AddSubpass("Debug Draw with Depth"); + debugDrawDepthSubpass->AddColorOutput("Scene"); + debugDrawDepthSubpass->AddDepthOutput("Depth Buffer"); + // - No Depth Test + auto debugDrawNode = worldRenderGraph->AddNode("Debug Draw", { "Scene" }, { "Debug Draw with Depth" }); + auto debugDrawSubpass = debugDrawNode->AddSubpass("Debug Draw"); + debugDrawSubpass->AddColorOutput("Scene"); + + graphicsQueue->WaitIdle(); + + ssaoStorage->PrepareRotationVectorsVkData(device); + + Handle ssaoPass = gBufferNode->AddNodeCompute("SSAO", ssaoShader, { "Position", "Normals", "SSAO" }); + auto ssaoDataBuffer = ssaoStorage->GetBuffer(); + ssaoPass->ModifyWriteDescBufferComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_BUFFER_BINDING, { &ssaoDataBuffer, 1 }, 0, ssaoStorage->GetBuffer()->GetSizeStored()); + auto viewSamplerLayout = ssaoStorage->GetViewSamplerLayout(); + + ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, {&viewSamplerLayout, 1}); + + + Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, {"SSAO", "SSAO Blur"}); + + /*-----------------------------------------------------------------------*/ + /* DEFERRED COMPOSITE SUBPASS INIT */ + /*-----------------------------------------------------------------------*/ + gBufferNode->AddNodeCompute("Deferred Composite", deferredCompositeShader, {"Position", "Normals", "Albedo", "Light Layer Indices", "SSAO Blur", "Scene"}); + + // Dummy Node + auto dummyNode = worldRenderGraph->AddNode("Dummy Pass", { "Scene" }, { "Debug Draw" }); // no predecessors + auto dummySubpass = dummyNode->AddSubpass("Dummy Subpass"); + dummySubpass->AddInput("Scene"); + + /*-----------------------------------------------------------------------*/ + /* GENERATE RENDER GRAPH */ + /*-----------------------------------------------------------------------*/ + // Generate world render graph + worldRenderGraph->Generate(); + + /*-----------------------------------------------------------------------*/ + /* BIND RENDER GRAPH TO RENDERER */ + /*-----------------------------------------------------------------------*/ + // Add world renderer to default viewport + worldRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], worldRenderGraph); + worldRenderer->SetCamera(worldCamera); + + worldRenderer->SetCameraDirector(cameraSystem->CreateDirector()); + + // Create debug draw pipeline + debugDrawPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawPipeline->GetVkPipeline(), "[Pipeline] Debug Draw"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw Pipeline Layout"); + debugDrawDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawDepthPipeline->GetVkPipeline(), "[Pipeline Layout] Debug Draw with Depth Test"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw with Depth Test Pipeline Layout"); + } + + void SHGraphicsSystem::InitMiddleEnd(void) noexcept + { + SHGraphicsGlobalData::Init(device); + + InitSceneRenderGraph(); + +#ifdef SHEDITOR + InitEditorRenderGraph(); +#endif + + // Create Semaphore + for (auto& semaHandle : graphSemaphores) + { + semaHandle = device->CreateSemaphore(); + } + } + + void SHGraphicsSystem::InitSubsystems(void) noexcept + { + + mousePickSystem = resourceManager.Create(); + + std::vector> cmdPools; + cmdPools.reserve(swapchain->GetNumImages()); + for (uint32_t i = 0; i < swapchain->GetNumImages(); ++i) + cmdPools.push_back(renderContext.GetFrameData(i).cmdPoolHdls[0]); + + // Mouse picking system for the editor (Will still run with editor disabled) + mousePickSystem->Init(device, cmdPools, worldRenderGraph->GetRenderGraphResource("Entity ID")); + + // Register the post offscreen render to the system + postOffscreenRender = resourceManager.Create(); + postOffscreenRender->Init(device, worldRenderGraph->GetRenderGraphResource("Scene"), descPool); + + lightingSubSystem = resourceManager.Create(); + lightingSubSystem->Init(device, descPool); + } + + void SHGraphicsSystem::InitBuiltInResources(void) + { + // Create default texture + std::array defaultTextureData = { 255, 255, 255, 255 }; + std::vector mipOffsets{}; + mipOffsets.push_back(0); + defaultTexture = AddTexture(4, defaultTextureData.data(), 1, 1, SHTexture::TextureFormat::eR8G8B8A8Unorm, mipOffsets); + BuildTextures(); + + // Create default meshes + primitiveMeshes[static_cast(PrimitiveType::Cube)] = SHPrimitiveGenerator::Cube(meshLibrary); + primitiveMeshes[static_cast(PrimitiveType::Sphere)] = SHPrimitiveGenerator::Sphere(meshLibrary); + BuildMeshBuffers(); + + // Create default materials + defaultMaterial = AddMaterial + ( + defaultVertShader, defaultFragShader, + worldRenderGraph->GetNode("G-Buffer")->GetSubpass("G-Buffer Write") + ); + defaultMaterial->SetProperty("data.textureIndex", defaultTexture->TextureArrayIndex); + } + +#ifdef SHEDITOR + void SHGraphicsSystem::InitEditorRenderGraph(void) noexcept + { + auto windowDims = window->GetWindowSize(); + + // Create Default Viewport + editorViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 1.0f)); + + // Get render graph from viewport editor renderer + editorRenderGraph = resourceManager.Create(); + + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + + editorRenderGraph->Init("Editor Render Graph", device, swapchain); + editorRenderGraph->AddResource("Present", { SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second); + + + auto imguiNode = editorRenderGraph->AddNode("ImGui Node", { "Present"}, {}); + auto imguiSubpass = imguiNode->AddSubpass("ImGui Draw"); + imguiSubpass->AddColorOutput("Present"); + + // Generate world render graph + editorRenderGraph->Generate(); + + // Add world renderer to default viewport + editorRenderer = editorViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], editorRenderGraph); + editorRenderer->SetCamera(worldCamera); + } +#endif + + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*---------------------------------------------------------------------------------*/ + void SHGraphicsSystem::Init(void) + { + InitBoilerplate(); + InitMiddleEnd(); + InitSubsystems(); + InitBuiltInResources(); + } + + void SHGraphicsSystem::Exit(void) + { + } + +#pragma endregion INIT_EXIT + +#pragma region LIFECYCLE + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + /***************************************************************************/ + /*! + + \brief + Main render loop. Loops through viewports -> renderers -> invoke render + graphs sequentially using semaphores. + + \param dt + + \return + + */ + /***************************************************************************/ + void SHGraphicsSystem::Run(double) noexcept + { + if (window->IsMinimized() || renderContext.GetWindowIsDead()) + { + restoredFromMinimize = true; + return; + } + + if (restoredFromMinimize) + return; + + // Frame data for the current frame + auto const& frameData = renderContext.GetCurrentFrameData(); + uint32_t frameIndex = renderContext.GetCurrentFrame(); + + // semaphore index. This is for render graphs to have semaphores to signal that the next render graph will use to wait on. + bool semIndex = 0; + + using BufferPair = std::pair, uint32_t>; + const std::initializer_list MESH_DATA = + { + std::make_pair(meshLibrary.GetVertexPositionsBuffer(), SHGraphicsConstants::VertexBufferBindings::POSITION), + std::make_pair(meshLibrary.GetVertexTexCoordsBuffer(), SHGraphicsConstants::VertexBufferBindings::TEX_COORD), + std::make_pair(meshLibrary.GetVertexNormalsBuffer(), SHGraphicsConstants::VertexBufferBindings::NORMAL), + std::make_pair(meshLibrary.GetVertexTangentsBuffer(), SHGraphicsConstants::VertexBufferBindings::TANGENT), + std::make_pair(meshLibrary.GetIndexBuffer(), 0), + }; + + renderContext.ResetFence(); + + // Bind textures + + auto cameraSystem = SHSystemManager::GetSystem(); + + + { +#ifdef SHEDITOR + auto editorSystem = SHSystemManager::GetSystem(); + if (editorSystem->editorState != SHEditor::State::PLAY) + lightingSubSystem->Run(cameraSystem->GetEditorCamera()->GetViewMatrix(), frameIndex); + else + lightingSubSystem->Run(worldRenderer->GetCameraDirector()->GetViewMatrix(), frameIndex); +#else + lightingSubSystem->Run(worldRenderer->GetCameraDirector()->GetViewMatrix(), frameIndex); +#endif + } + + // For every viewport + for (int vpIndex = 0; vpIndex < static_cast(viewports.size()); ++vpIndex) + { + auto& renderers = viewports[vpIndex]->GetRenderers(); + + // For every renderer + for (int renIndex = 0; renIndex < static_cast(renderers.size()); ++renIndex) + { + /*-----------------------------------------------------------------------*/ + /* Renderer start */ + /*-----------------------------------------------------------------------*/ + // get command buffer of the renderer in the current frame + auto currentCmdBuffer = renderers[renIndex]->GetCommandBuffer(frameIndex); + + // Begin recording the command buffer + currentCmdBuffer->BeginRecording(); + + // set viewport and scissor + uint32_t w = static_cast(viewports[vpIndex]->GetWidth()); + uint32_t h = static_cast(viewports[vpIndex]->GetHeight()); + currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); + + // Force set the pipeline layout + currentCmdBuffer->ForceSetPipelineLayout(SHGraphicsGlobalData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::GRAPHICS); + currentCmdBuffer->ForceSetPipelineLayout(SHGraphicsGlobalData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::COMPUTE); + + // Bind all the buffers required for meshes + for (auto& [buffer, bindingPoint] : MESH_DATA) + { + if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eVertexBuffer) + currentCmdBuffer->BindVertexBuffer(bindingPoint, buffer, 0); + else if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eIndexBuffer) + currentCmdBuffer->BindIndexBuffer(buffer, 0); + } + + lightingSubSystem->BindDescSet(currentCmdBuffer, frameIndex); + + // Bind textures + auto textureDescSet = texLibrary.GetTextureDescriptorSetGroup(); + if (textureDescSet) + { + std::array texDynamicOffset {0}; + currentCmdBuffer->BindDescriptorSet + ( + textureDescSet, + SH_PIPELINE_TYPE::GRAPHICS, + 0, + texDynamicOffset + ); + } + + // bind camera data + //renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); + +#ifdef SHEDITOR + if (renderers[renIndex] == worldRenderer) + { + auto editorSystem = SHSystemManager::GetSystem(); + if (editorSystem->editorState != SHEditor::State::PLAY) + worldRenderer->UpdateDataAndBind(currentCmdBuffer, frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix()); + else + renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); + } + else + renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +#else + renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +#endif + + // Draw the scene + renderers[renIndex]->Draw(frameIndex, descPool); + + // End the command buffer recording + currentCmdBuffer->EndRecording(); + /*-----------------------------------------------------------------------*/ + /* Renderer end */ + /*-----------------------------------------------------------------------*/ + + // submit a command buffer from the current render graph and make it wait for the previous render graph before submitting it to GPU. + graphicsQueue->SubmitCommandBuffer + ( + { currentCmdBuffer }, + { (vpIndex == viewports.size() - 1 && renIndex == renderers.size() - 1) ? frameData.semRenderFinishHdl : graphSemaphores[!semIndex] }, + { (vpIndex == 0 && renIndex == 0) ? frameData.semImgAvailableHdl : graphSemaphores[semIndex] }, + { vk::PipelineStageFlagBits::eColorAttachmentOutput }, + { (vpIndex == viewports.size() - 1 && renIndex == renderers.size() - 1) ? frameData.fenceHdl : Handle{} } + ); + + semIndex = !semIndex; + } + } + } + + /***************************************************************************/ + /*! + + \brief + Checks for window resize and acquire next image in swapchain. + */ + /***************************************************************************/ + void SHGraphicsSystem::BeginRender() + { + if (window->IsMinimized() || renderContext.GetWindowIsDead()) + { + restoredFromMinimize = true; + return; + } + if (restoredFromMinimize) + { + return; + } - /*-----------------------------------------------------------------------*/ - /* RENDERGRAPH TESTING */ - /*-----------------------------------------------------------------------*/ + // Finalise all batches + for (auto vp : viewports) + for (auto renderer : vp->GetRenderers()) + { + renderer->GetRenderGraph()->FinaliseBatch(renderContext.GetCurrentFrame(), descPool); + } - renderGraph.Init(device, swapchain); - renderGraph.AddResource("Position", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); - renderGraph.AddResource("Normals", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); - renderGraph.AddResource("Composite", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); - renderGraph.AddResource("Downscale", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); - renderGraph.AddResource("Present", SH_ATT_DESC_TYPE::COLOR_PRESENT, windowDims.first, windowDims.second); - auto node = renderGraph.AddNode("G-Buffer", { "Position", "Normals", "Composite" }, {}); // no predecessors - - // First subpass to write to G-Buffer - auto writeSubpass = node->AddSubpass("G-Buffer Write"); - writeSubpass->AddColorOutput("Position"); - writeSubpass->AddColorOutput("Normals"); - - // Second subpass to read from G-Buffer - auto compositeSubpass = node->AddSubpass("G-Buffer Composite"); - compositeSubpass->AddColorOutput("Composite"); - compositeSubpass->AddInput("Normals"); - compositeSubpass->AddInput("Position"); - - auto compositeNode = renderGraph.AddNode("Bloom", { "Composite", "Downscale", "Present"}, {"G-Buffer"}); - auto bloomSubpass = compositeNode->AddSubpass("Downsample"); - bloomSubpass->AddInput("Composite"); - bloomSubpass->AddColorOutput("Downscale"); - bloomSubpass->AddColorOutput("Present"); - - renderGraph.Generate(); - - - /*-----------------------------------------------------------------------*/ - /* RENDERGRAPH END TESTING */ - /*-----------------------------------------------------------------------*/ - } - SHGraphicsSystem::~SHGraphicsSystem() - { - //renderPass.Free(); - renderContext.Destroy(); - queue.Free(); - swapchain.Free(); - surface.Free(); - device.Free(); - - SHVkInstance::Destroy(); - } - - /*---------------------------------------------------------------------------------*/ - /* Lifecycle Functions */ - /*---------------------------------------------------------------------------------*/ - /***************************************************************************/ - /*! - - \brief - Checks for window resize and acquire next image in swapchain. - - - - */ - /***************************************************************************/ - void SHGraphicsSystem::BeginRender() - { + // Resize auto windowDims = window->GetWindowSize(); if (renderContext.GetResizeAndReset()) - { + { device->WaitIdle(); - // Resize the swapchain - swapchain->Resize(surface, windowDims.first, windowDims.second); + HandleResize(); + } - renderContext.HandleResize(); - } + const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); - const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); - - // #BackEndTest: For for the fence initialized by queue submit - renderContext.WaitForFence(); + // #BackEndTest: For for the fence initialized by queue submit + renderContext.WaitForFence(); - // #BackEndTest: Acquire the next image in the swapchain available - renderContext.AcquireNextIamge(); + // #BackEndTest: Acquire the next image in the swapchain available + renderContext.AcquireNextIamge(); + const uint32_t CURR_FRAME_IDX_2 = renderContext.GetCurrentFrame(); - // #BackEndTest: Get the current frame from frame manager - auto& currFrameData = renderContext.GetCurrentFrameData(); + // #BackEndTest: Get the current frame from frame manager + auto& currFrameData = renderContext.GetCurrentFrameData(); - // #BackEndTest: Reset command pool - if (currFrameData.cmdPoolHdls.empty()) - throw std::runtime_error("No command pools available!"); - currFrameData.cmdPoolHdls[0]->Reset(); + // #BackEndTest: Reset command pool + if (currFrameData.cmdPoolHdls.empty()) + throw std::runtime_error("No command pools available!"); + currFrameData.cmdPoolHdls[0]->Reset(); - } - /***************************************************************************/ - /*! - - \brief - Check if need to resize and advance the frame in the render context. - - */ - /***************************************************************************/ - void SHGraphicsSystem::EndRender() - { - const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); - auto& currFrameData = renderContext.GetCurrentFrameData(); + } - // #BackEndTest: Prepare to present current image - vk::PresentInfoKHR presentInfo{}; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &currFrameData.semRenderFinishHdl->GetVkSem(); - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapchain->GetVkSwapchain(); - presentInfo.pImageIndices = &CURR_FRAME_IDX; + /***************************************************************************/ + /*! - // #BackEndTest: queues an image for presentation - if (auto result = device->GetQueue(SH_Q_FAM::GRAPHICS, 0)->GetVkQueue().presentKHR(&presentInfo); result != vk::Result::eSuccess) - { - // If swapchain is incompatible/outdated - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) - { - auto windowDims = window->GetWindowSize(); - swapchain->Resize(surface, windowDims.first, windowDims.second); + \brief + Check if need to resize and advance the frame in the render context. - renderContext.HandleResize(); + */ + /***************************************************************************/ + void SHGraphicsSystem::EndRender() + { + if (window->IsMinimized() || renderContext.GetWindowIsDead()) + { + restoredFromMinimize = true; + return; + } - } - } - - // #BackEndTest: Cycle frame count - renderContext.AdvanceFrame(); - } - //Handle SHGraphicsSystem::AddRenderer() - //{ - // return Handle(); - //} - //void SHGraphicsSystem::RemoveRenderer(Handle renderer) - //{ - //} - Handle SHGraphicsSystem::AddSegment(const VkViewport& viewport, Handle imageToUse) - { - return Handle(); - } - void SHGraphicsSystem::RemoveSegment(Handle segment) - { - } + if (restoredFromMinimize) + { + restoredFromMinimize = false; + return; + } + + + const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); + auto& currFrameData = renderContext.GetCurrentFrameData(); + + mousePickSystem->Run(graphicsQueue, CURR_FRAME_IDX); + + // #BackEndTest: queues an image for presentation + if (auto result = graphicsQueue->Present(swapchain, { currFrameData.semRenderFinishHdl }, CURR_FRAME_IDX); result != vk::Result::eSuccess) + { + // If swapchain is incompatible/outdated + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) + { + HandleResize(); + } + } + + // #BackEndTest: Cycle frame count + renderContext.AdvanceFrame(); + } + +#pragma endregion LIFECYCLE + +#pragma region ADD_REMOVE_BUILD + + Handle SHGraphicsSystem::AddViewport(const vk::Viewport& viewport) + { + // Create the viewport + auto vp = SHVkInstance::GetResourceManager().Create(device, viewport); + viewports.emplace_back(vp); + return vp; + } + + void SHGraphicsSystem::RemoveViewport(Handle viewport) + { + // Check if the viewport exists + auto iter = std::find(viewports.begin(), viewports.end(), viewport); + if (iter == viewports.end()) + throw std::invalid_argument("Attempted to remove viewport that does not belong to this Graphics System."); + + // Remove + viewport.Free(); + viewports.erase(iter); + } + + Handle SHGraphicsSystem::AddMaterial(Handle vertShader, Handle fragShader, Handle subpass) + { + // Retrieve pipeline from pipeline storage or create if unavailable + auto shaderPair = std::make_pair(vertShader, fragShader); + + // Create material + auto mat = resourceManager.Create(); + + auto renderGraphNode = subpass->GetParentNode(); + auto pipeline = renderGraphNode->GetOrCreatePipeline(std::make_pair(vertShader, fragShader), subpass); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, pipeline->GetVkPipeline(), "[Pipeline] Custom Pipeline"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, pipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Custom Pipeline Layout"); + + mat->SetPipeline(pipeline); + + return mat; + } + + void SHGraphicsSystem::RemoveMaterial(Handle material) + { + resourceManager.Free(material); + } + + Handle SHGraphicsSystem::AddOrGetBaseMaterialInstance(Handle material) + { + return materialInstanceCache.CreateOrGet(resourceManager, material); + } + + SHADE::Handle SHGraphicsSystem::AddOrGetBaseMaterialInstance() + { + return AddOrGetBaseMaterialInstance(defaultMaterial); + } + + SHADE::Handle SHGraphicsSystem::AddMaterialInstanceCopy(Handle materialInst) + { + return resourceManager.Create(materialInst->GetBaseMaterial()); + } + + void SHGraphicsSystem::RemoveMaterialInstance(Handle materialInstance) + { + resourceManager.Free(materialInstance); + } + + /*---------------------------------------------------------------------------------*/ + /* Mesh Registration Functions */ + /*---------------------------------------------------------------------------------*/ + SHADE::Handle SHGraphicsSystem::AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices) + { + return meshLibrary.AddMesh(vertexCount, positions, texCoords, tangents, normals, indexCount, indices); + } + + void SHGraphicsSystem::RemoveMesh(Handle mesh) + { + meshLibrary.RemoveMesh(mesh); + } + + void SHGraphicsSystem::BuildMeshBuffers() + { + transferCmdBuffer = graphicsCmdPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + SET_VK_OBJ_NAME(device, vk::ObjectType::eCommandBuffer, transferCmdBuffer->GetVkCommandBuffer(), "[Command Buffer] Mesh Buffer Building (Transfer)"); + device->WaitIdle(); + transferCmdBuffer->BeginRecording(); + meshLibrary.BuildBuffers(device, transferCmdBuffer); + transferCmdBuffer->EndRecording(); + graphicsQueue->SubmitCommandBuffer({ transferCmdBuffer }); + device->WaitIdle(); + transferCmdBuffer.Free(); transferCmdBuffer = {}; + } + + Handle SHGraphicsSystem::GetMeshPrimitive(PrimitiveType type) const noexcept + { + switch (type) + { + case PrimitiveType::Cube: + case PrimitiveType::Sphere: + return primitiveMeshes[static_cast(type)]; + default: + return {}; + } + } + + /*---------------------------------------------------------------------------------*/ + /* Texture Registration Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHGraphicsSystem::AddTexture(const SHTextureAsset& texAsset) + { + const int MIPS = texAsset.mipOffsets.size(); + auto sampler = samplerCache.GetSampler(device, SHVkSamplerParams { .maxLod = static_cast(MIPS) }); + SET_VK_OBJ_NAME(device, vk::ObjectType::eSampler, sampler->GetVkSampler(), "[Sampler] Mips " + std::to_string(MIPS)); + return texLibrary.Add(texAsset, sampler); + } + + SHADE::Handle SHGraphicsSystem::AddTexture(uint32_t pixelCount, const SHTexture::PixelChannel* const pixelData, uint32_t width, uint32_t height, SHTexture::TextureFormat format, std::vector mipOffsets) + { + const int MIPS = mipOffsets.size(); + auto sampler = samplerCache.GetSampler(device, SHVkSamplerParams{ .maxLod = static_cast(MIPS) }); + SET_VK_OBJ_NAME(device, vk::ObjectType::eSampler, sampler->GetVkSampler(), "[Sampler] Mips " + std::to_string(MIPS)); + return texLibrary.Add(pixelCount, pixelData, width, height, format, mipOffsets, sampler); + } + + void SHGraphicsSystem::RemoveTexture(Handle tex) + { + texLibrary.Remove(tex); + } + + void SHGraphicsSystem::BuildTextures() + { + graphicsTexCmdBuffer = graphicsCmdPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + SET_VK_OBJ_NAME(device, vk::ObjectType::eCommandBuffer, graphicsTexCmdBuffer->GetVkCommandBuffer(), "[Command Buffer] Texture Building (Graphics)"); + device->WaitIdle(); + texLibrary.BuildTextures + ( + device, graphicsTexCmdBuffer, graphicsQueue, descPool + ); + device->WaitIdle(); + graphicsTexCmdBuffer.Free(); graphicsTexCmdBuffer = {}; + } + + Handle SHGraphicsSystem::GetTextureHandle(SHTexture::Index textureId) const + { + return texLibrary.GetTextureHandle(textureId); + } + +#pragma endregion ADD_REMOVE + +#pragma region ROUTINES + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - BeginRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHGraphicsSystem::BeginRoutine::BeginRoutine() + : SHSystemRoutine("Graphics System Frame Set Up", true) + {} + + void SHGraphicsSystem::BeginRoutine::Execute(double) noexcept + { + SHResourceManager::FinaliseChanges(); + reinterpret_cast(system)->BeginRender(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - RenderRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHGraphicsSystem::RenderRoutine::RenderRoutine() + : SHSystemRoutine("Graphics System Render", true) + {} + + void SHGraphicsSystem::RenderRoutine::Execute(double dt) noexcept + { + reinterpret_cast(system)->Run(dt); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - EndRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHGraphicsSystem::EndRoutine::EndRoutine() + : SHSystemRoutine("Graphics System Frame Clean Up", true) + {} + + void SHGraphicsSystem::EndRoutine::Execute(double) noexcept + { + reinterpret_cast(system)->EndRender(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - BatcherDispatcherRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHGraphicsSystem::BatcherDispatcherRoutine::BatcherDispatcherRoutine() + : SHSystemRoutine("Graphics System Batcher Dispatcher", true) + {} + + void SHGraphicsSystem::BatcherDispatcherRoutine::Execute(double) noexcept + { + auto& renderables = SHComponentManager::GetDense(); + for (auto& renderable : renderables) + { + if (!renderable.HasChanged()) + continue; + + // Remove from the SuperBatch it is previously in (prevMat if mat has changed) + Handle prevMaterial = renderable.HasMaterialChanged() ? renderable.GetPrevMaterial() : renderable.GetMaterial(); + if (prevMaterial) + { + Handle oldSuperBatch = prevMaterial->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass()->GetSuperBatch(); + oldSuperBatch->Remove(&renderable); + } + + // Add to new SuperBatch if there is a material + // Add to new SuperBatch if there is a material and a mesh to render + Handle newMatInstance = renderable.GetMaterial(); + if (newMatInstance && renderable.GetMesh()) + { + Handle newSuperBatch = newMatInstance->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass()->GetSuperBatch(); + newSuperBatch->Add(&renderable); + } + + // Unset change flag + renderable.ResetChangedFlag(); + } + } +#pragma endregion ROUTINES + +#pragma region MISC + + void SHGraphicsSystem::PrepareResize(uint32_t newWidth, uint32_t newHeight) noexcept + { + resizeWidth = newWidth; + resizeHeight = newHeight; + + renderContext.SetIsResized(true); + } + + void SHGraphicsSystem::HandleResize(void) noexcept + { + device->WaitIdle(); + + if (window->IsMinimized() || renderContext.GetWindowIsDead()) + return; + + graphSemaphores[0].Free(); + graphSemaphores[1].Free(); + + auto windowDims = window->GetWindowSize(); + + // Resize the swapchain + swapchain->Resize(surface, windowDims.first, windowDims.second); + + renderContext.HandleResize(); + + worldRenderGraph->HandleResize(resizeWidth, resizeHeight); + editorRenderGraph->HandleResize(windowDims.first, windowDims.second); + + mousePickSystem->HandleResize(); + postOffscreenRender->HandleResize(); + + worldViewport->SetWidth(static_cast(resizeWidth)); + worldViewport->SetHeight(static_cast(resizeHeight)); + + worldCamera->SetPerspective(90.0f, static_cast(resizeWidth), static_cast(resizeHeight), 0.0f, 100.0f); + + auto cameraSystem = SHSystemManager::GetSystem(); +#ifdef SHEDITOR + cameraSystem->GetEditorCamera()->SetWidth(static_cast(resizeWidth)); + cameraSystem->GetEditorCamera()->SetHeight(static_cast(resizeHeight)); +#else + +#endif + + for (auto& semaHandle : graphSemaphores) + semaHandle = device->CreateSemaphore(); + + + } + + void SHGraphicsSystem::AwaitGraphicsExecution() + { + device->WaitIdle(); + } + + void SHGraphicsSystem::SetWindow(SHWindow* wind) noexcept + { + window = wind; + } + + + Handle SHGraphicsSystem::GetPrimaryRenderpass() const noexcept + { + return worldRenderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); + } + + Handle SHGraphicsSystem::createDebugDrawPipeline(Handle renderPass, Handle subpass) + { + auto pipelineLayout = resourceManager.Create + ( + device, SHPipelineLayoutParams + { + .shaderModules = { debugVertShader, debugFragShader }, + .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() + } + ); + auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); + pipeline->GetPipelineState().SetRasterizationState(SHRasterizationState + { + .polygonMode = vk::PolygonMode::eLine, + .cull_mode = vk::CullModeFlagBits::eNone + }); + pipeline->GetPipelineState().SetInputAssemblyState(SHInputAssemblyState + { + .topology = vk::PrimitiveTopology::eLineList + }); + + SHVertexInputState debugDrawVertexInputState; + debugDrawVertexInputState.AddBinding(false, true, { SHVertexAttribute(SHAttribFormat::FLOAT_4D), SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); + pipeline->GetPipelineState().SetVertexInputState(debugDrawVertexInputState); + SHColorBlendState colorBlendState{}; + colorBlendState.logic_op_enable = VK_FALSE; + colorBlendState.logic_op = vk::LogicOp::eCopy; + + auto const& subpassColorReferences = subpass->GetColorAttachmentReferences(); + colorBlendState.attachments.reserve(subpassColorReferences.size()); + + for (auto& att : subpassColorReferences) + { + colorBlendState.attachments.push_back(vk::PipelineColorBlendAttachmentState + { + .blendEnable = SHVkUtil::IsBlendCompatible(subpass->GetFormatFromAttachmentReference(att.attachment)), + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOne, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }); + } + + pipeline->GetPipelineState().SetColorBlenState(colorBlendState); + pipeline->ConstructPipeline(); + + return pipeline; + } + +#pragma endregion MISC } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index e1dff379..a5a5ada0 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -3,11 +3,11 @@ \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Aug 21, 2022 -\brief +\brief + - Copyright (C) 2022 DigiPen Institute of Technology. -Reproduction or disclosure of this file or its contents without the prior written consent +Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ #pragma once @@ -17,113 +17,430 @@ of DigiPen Institute of Technology is prohibited. #include // Project Includes -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include "Graphics/SHVulkanIncludes.h" #include "Graphics/MiddleEnd/PerFrame/SHRenderContext.h" #include "Graphics/RenderGraph/SHRenderGraph.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" +#include "SHMeshLibrary.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.h" +#include "../Textures/SHTextureLibrary.h" +#include "../Textures/SHVkSamplerCache.h" +#include "Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.h" +#include "Graphics/MiddleEnd/Lights/SHLightingSubSystem.h" +#include "Graphics/MiddleEnd/PostProcessing/SHSSAO.h" namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*---------------------------------------------------------------------------------*/ - class SHVkPhysicalDevice; - class SHVkLogicalDevice; - class SHVkSurface; - class SHVkSwapchain; - class SHScreenSegment; - //class SHRenderer; - class SHWindow; - class SHVkImage; - class SHVkFramebuffer; - class SHVkCommandBuffer; + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkPhysicalDevice; + class SHVkLogicalDevice; + class SHVkSurface; + class SHVkSwapchain; + class SHScreenSegment; + class SHWindow; + class SHVkImage; + class SHVkFramebuffer; + class SHVkCommandBuffer; + class SHRenderer; + class SHViewport; + class SHCamera; + class SHVkShaderModule; + class SHMaterial; + class SHMaterialInstance; + class SHMousePickSystem; - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - /***********************************************************************************/ - /*! - \brief - Represents an axis aligned box on the screen to render to along with the - specified Image to render to that spot. - */ - /***********************************************************************************/ - struct SHScreenSegment - { - VkViewport Viewport; - Handle ImageToUse; - }; + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Type of built-in primitive meshes that are available. + */ + /***********************************************************************************/ + enum class PrimitiveType + { + Cube, + Sphere + }; + static constexpr int MAX_PRIMITIVE_TYPES = 2; - /***********************************************************************************/ - /*! - \brief - Manages the lifecycle and provides an interface for rendering multiple objects to - portions of the screen. - */ - /***********************************************************************************/ - class SHGraphicsSystem - { - public: - /*-----------------------------------------------------------------------------*/ - /* Constants */ - /*-----------------------------------------------------------------------------*/ - static constexpr int NUM_FRAME_BUFFERS = 3; + /***********************************************************************************/ + /*! + \brief + Manages the lifecycle and provides an interface for rendering multiple objects to + portions of the screen. + */ + /***********************************************************************************/ + class SH_API SHGraphicsSystem : public SHSystem + { + private: + void InitBoilerplate (void) noexcept; + void InitSceneRenderGraph (void) noexcept; + void InitMiddleEnd (void) noexcept; + void InitSubsystems (void) noexcept; + void InitBuiltInResources (void); - /*-----------------------------------------------------------------------------*/ - /* Constructor/Destructors */ - /*-----------------------------------------------------------------------------*/ - SHGraphicsSystem(SHWindow& window); - ~SHGraphicsSystem(); +#ifdef SHEDITOR + void InitEditorRenderGraph (void) noexcept; +#endif - /*-----------------------------------------------------------------------------*/ - /* Lifecycle Functions */ - /*-----------------------------------------------------------------------------*/ - void BeginRender(); - void EndRender(); + public: + class SH_API BeginRoutine final : public SHSystemRoutine + { + public: + BeginRoutine(); + virtual void Execute(double dt) noexcept override final; + }; + class SH_API RenderRoutine final : public SHSystemRoutine + { + public: + RenderRoutine(); + virtual void Execute(double dt) noexcept override final; + }; + class SH_API EndRoutine final : public SHSystemRoutine + { + public: + EndRoutine(); + virtual void Execute(double dt) noexcept override final; + }; + class SH_API BatcherDispatcherRoutine final : public SHSystemRoutine + { + public: + BatcherDispatcherRoutine(); + virtual void Execute(double dt) noexcept override final; + }; - /*-----------------------------------------------------------------------------*/ - /* Renderers Registration Functions */ - /*-----------------------------------------------------------------------------*/ - //Handle AddRenderer(); - //void RemoveRenderer(Handle renderer); + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr int NUM_FRAME_BUFFERS = 3; - /*-----------------------------------------------------------------------------*/ - /* Viewport Registration Functions */ - /*-----------------------------------------------------------------------------*/ - Handle AddSegment(const VkViewport& viewport, Handle imageToUse); - void RemoveSegment(Handle segment); + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHGraphicsSystem(void) = default; + ~SHGraphicsSystem(void) noexcept = default; - /*-----------------------------------------------------------------------------*/ - /* Getters (Temporary) */ - /*-----------------------------------------------------------------------------*/ - Handle GetDevice() const { return device; } - Handle GetSwapchain() const { return swapchain; } - Handle GetSurface() const { return surface; } - //Handle GetRenderPass() const { return renderPass; } + /*-----------------------------------------------------------------------------*/ + /* SHSystem overrides */ + /*-----------------------------------------------------------------------------*/ + virtual void Init(void) override final; + void Run(double dt) noexcept; + virtual void Exit(void) override final; + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + void BeginRender(); + void EndRender(); + + /*-----------------------------------------------------------------------------*/ + /* Viewport Registration Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddViewport(const vk::Viewport& viewport); + void RemoveViewport(Handle viewport); + + /*-----------------------------------------------------------------------------*/ + /* Material Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddMaterial(Handle vertShader, Handle fragShader, Handle subpass); + void RemoveMaterial(Handle material); + Handle AddOrGetBaseMaterialInstance(); + Handle AddOrGetBaseMaterialInstance(Handle material); + Handle AddMaterialInstanceCopy(Handle materialInst); + void RemoveMaterialInstance(Handle materialInstance); + Handle GetDefaultMaterial() { return defaultMaterial; } + Handle GetDefaultMaterialInstance() { return AddOrGetBaseMaterialInstance(defaultMaterial); } + + /*-----------------------------------------------------------------------------*/ + /* Mesh Registration Functions */ + /*-----------------------------------------------------------------------------*/ + /*******************************************************************************/ + /*! + + \brief + Adds a mesh to the Mesh Library. But this does not mean that the meshes have + been added yet. A call to "BuildBuffers()" is required to transfer all + meshes into the GPU. + + \param vertexCount + Number of vertices in this Mesh. + \param positions + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + positions. + \param texCoords + Pointer to the first in a contiguous array of SHMathVec2s that define vertex + texture coordinates. + \param tangents + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + tangents. + \param normals + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + normals. + \param indexCount + Number of indices in this mesh. + \param indices + Pointer to the first in a contiguous array of uint32_ts that define mesh + indices. + + \return + Handle to the created Mesh. This is not valid to be used until a call to + BuildBuffers(). + + */ + /*******************************************************************************/ + Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices); + /*******************************************************************************/ + /*! + + \brief + Removes a mesh from the MeshLibrary. But this does not mean that the meshes + have been removed yet. A call to "BuildBuffers()" is required to finalise all + changes. + + \param mesh + Handle to the mesh to remove. + + */ + /*******************************************************************************/ + void RemoveMesh(Handle mesh); + /***************************************************************************/ + /*! + + \brief + Finalises all changes to the MeshLibrary into the GPU buffers. + + */ + /***************************************************************************/ + void BuildMeshBuffers(); + /*******************************************************************************/ + /*! + + \brief + Retrieves the built-in mesh specified. + + \param type + Type of built-in mesh to retrieve. + + \returns + Handle to the mesh that was specfied. However, if an invalid type is specified, + a null Handle will be returned. + */ + /*******************************************************************************/ + Handle GetMeshPrimitive(PrimitiveType type) const noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Texture Registration Functions */ + /*-----------------------------------------------------------------------------*/ + /*******************************************************************************/ + /*! + + \brief + Adds a texture to the Texture Library. But this does not mean that the + textures have been added yet. A call to "BuildTextures()" is required to + transfer all textures into the GPU. + + \param pixelCount + Number of pixels in this Mesh. + \param positions + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + positions. + \param format + Format of the texture loaded in. + + \return + Handle to the created Texture. This is not valid to be used until a call to + BuildImages(). + + */ + /*******************************************************************************/ + Handle AddTexture(const SHTextureAsset& texAsset); + Handle AddTexture(uint32_t pixelCount, const SHTexture::PixelChannel* const pixelData, uint32_t width, uint32_t height, SHTexture::TextureFormat format, std::vector mipOffsets); + /*******************************************************************************/ + /*! + + \brief + Removes a mesh from the Texture Library. But this does not mean that the + textures have been removed yet. A call to "BuildTextures()" is required to + finalise all changes. + + \param mesh + Handle to the Texture to remove. + + */ + /*******************************************************************************/ + void RemoveTexture(Handle tex); + /***************************************************************************/ + /*! + + \brief + Finalises all changes to the Texture Library into the GPU buffers. + + \param cmdBuffer + Command buffer used to set up transfers of data in the GPU memory. This + call must be preceded by calls to cmdBuffer's BeginRecording() and ended + with EndRecording(). Do recall to also submit the cmdBuffer to a transfer + queue. + */ + /***************************************************************************/ + void BuildTextures(); + /***************************************************************************/ + /*! + * + \brief + Retrieves the texture handle at the specified texture index. + + \param textureId + Index of the texture to look for. + + \returns + Handle to the texture + + */ + /***************************************************************************/ + Handle GetTextureHandle(SHTexture::Index textureId) const; + /***************************************************************************/ + /*! + * + \brief + Retrieves the handle to the default texture. A white 1x1 texture. + + \returns + Handle to the default texture. + + */ + /***************************************************************************/ + Handle GetDefaultTexture() const noexcept { return defaultTexture; } + + void PrepareResize(uint32_t newWidth, uint32_t newHeight) noexcept; + void HandleResize(void) noexcept; + void AwaitGraphicsExecution(); + + /*-----------------------------------------------------------------------------*/ + /* Setters */ + /*-----------------------------------------------------------------------------*/ + void SetWindow(SHWindow* wind) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Getters (Temporary) */ + /*-----------------------------------------------------------------------------*/ + Handle GetDevice() const { return device; } + Handle GetSwapchain() const { return swapchain; } + Handle GetSurface() const { return surface; } + Handle GetPhysicalDevice() const { return physicalDevice; } + Handle GetQueue() const { return graphicsQueue; } + Handle GetDescriptorPool() const { return descPool; } + Handle GetDefaultViewport() const {return worldViewport;} +#ifdef SHEDITOR + Handle GetEditorViewport () const {return editorViewport;}; +#endif + Handle GetMousePickSystem(void) const noexcept {return mousePickSystem;}; + Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRender;}; + Handle GetPrimaryRenderpass() const noexcept; + Handle GetDebugDrawPipeline(void) const noexcept { return debugDrawPipeline; } + Handle GetDebugDrawDepthPipeline(void) const noexcept { return debugDrawDepthPipeline; } + uint32_t GetCurrentFrameIndex(void) const noexcept { return renderContext.GetCurrentFrame(); } + + /*-----------------------------------------------------------------------------*/ + /* Getters */ + /*-----------------------------------------------------------------------------*/ + SHWindow* GetWindow() noexcept { return window; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr std::string_view G_BUFFER_RENDER_GRAPH_NODE_NAME = "G-Buffer"; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Owned Vulkan Resources + Handle physicalDevice; + Handle device; + Handle surface; + Handle swapchain; + Handle graphicsQueue; + Handle transferQueue; + Handle descPool; + Handle graphicsCmdPool; + Handle transferCmdBuffer; + Handle ssaoTransferCmdBuffer; + Handle graphicsTexCmdBuffer; + SHRenderContext renderContext; + std::array, 2> graphSemaphores; + // Not Owned Resources + SHWindow* window = nullptr; + + // Middle End Resources + SHResourceHub resourceManager; + SHMeshLibrary meshLibrary; + SHTextureLibrary texLibrary; + SHSamplerCache samplerCache; + SHMaterialInstanceCache materialInstanceCache; + + // Viewports +#ifdef SHEDITOR + Handle editorViewport; + Handle editorRenderer; + Handle editorRenderGraph; +#endif + + Handle worldViewport; // Whole screen + std::vector> viewports; // Additional viewports + + // Temp renderers + Handle worldRenderer; + + // Temp Cameras + Handle worldCamera; + Handle screenCamera; + + // Built-In Shaders + Handle defaultVertShader; + Handle defaultFragShader; + Handle debugVertShader; + Handle debugFragShader; + Handle deferredCompositeShader; + Handle ssaoShader; + Handle ssaoBlurShader; - private: - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - // Owned Resources - Handle physicalDevice; - Handle device; - Handle surface; - Handle swapchain; - Handle queue; - //Handle renderPass; // Potentially bring out? - std::vector screenSegments; - SHRenderContext renderContext; - //std::array, NUM_FRAME_BUFFERS> frameBuffers; - //std::array, NUM_FRAME_BUFFERS> commandBuffers; - // Not Owned Resources - SHWindow* window; - // Renderers - //Handle debugWorldRenderer; - //Handle debugScreenRenderer; - //std::vector renderers; - SHRenderGraph renderGraph; + // Built-In Materials + Handle defaultMaterial; + Handle debugDrawPipeline; + Handle debugDrawDepthPipeline; - }; + // Built-In Textures + Handle defaultTexture; + + // Built-In Meshes + std::array, MAX_PRIMITIVE_TYPES> primitiveMeshes; + + // Render Graphs + Handle worldRenderGraph; + + // Sub systems + Handle mousePickSystem; + Handle postOffscreenRender; + Handle lightingSubSystem; + Handle ssaoStorage; + + uint32_t resizeWidth = 1; + uint32_t resizeHeight = 1; + bool restoredFromMinimize = false; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + Handle createDebugDrawPipeline(Handle renderPass, Handle subpass); + }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.cpp new file mode 100644 index 00000000..1ad46e04 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.cpp @@ -0,0 +1,77 @@ +/************************************************************************************//*! +\file SHGraphicsSystemInterface.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definitions of the functions of the static + SHGraphicsSystemInterface class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "SHGraphicsSystemInterface.h" +// Project Includes +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Graphics/Windowing/SHWindow.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*-----------------------------------------------------------------------------------*/ + uint32_t SHGraphicsSystemInterface::GetWindowWidth() + { + auto gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem) + { + const auto WND = gfxSystem->GetWindow(); + return WND->GetWindowSize().first; + } + + SHLOG_WARNING("[SHGraphicsSystemInterface] Failed to get window width. Value of 0 returned instead."); + return 0; + } + + uint32_t SHGraphicsSystemInterface::GetWindowHeight() + { + auto gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem) + { + const auto WND = gfxSystem->GetWindow(); + return WND->GetWindowSize().second; + } + + SHLOG_WARNING("[SHGraphicsSystemInterface] Failed to get window height. Value of 0 returned instead."); + return 0; + } + + bool SHGraphicsSystemInterface::IsFullscreen() + { + auto gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem) + { + const auto WND = gfxSystem->GetWindow(); + return WND->GetWindowData().isFullscreen; + } + + SHLOG_WARNING("[SHGraphicsSystemInterface] Failed to get window fullscreen status. Value of false returned instead."); + return false; + } + + void SHGraphicsSystemInterface::CloseWindow() + { + auto gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem) + { + auto WND = gfxSystem->GetWindow(); + return WND->Close(); + } + + SHLOG_WARNING("[SHGraphicsSystemInterface] Failed to close window."); + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.h new file mode 100644 index 00000000..5bc77ed9 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.h @@ -0,0 +1,52 @@ +/************************************************************************************//*! +\file SHGraphicsSystemInterface.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definition of the SHGraphicsSystemInterface static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Static class that wraps up certain functions in the SHGraphicsSystem so that + /// accessing it from SHADE_Managed would not cause issues due to C++20 features. + /// + class SH_API SHGraphicsSystemInterface final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructor */ + /*---------------------------------------------------------------------------------*/ + SHGraphicsSystemInterface() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Retrieves the current window width. + /// + /// The current window width. + static uint32_t GetWindowWidth(); + /// + /// Retrieves the current window height. + /// + /// The current window height. + static uint32_t GetWindowHeight(); + /// + /// Retrieves the current window fullscreen status. + /// + /// The current window fullscreen status.. + static bool IsFullscreen(); + /// + /// Closes the current window, and depending on the implementation, should also + /// close the application. + /// + static void CloseWindow(); + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHInstancedIntegerData.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHInstancedIntegerData.h new file mode 100644 index 00000000..8ba5adf8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHInstancedIntegerData.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ECS_Base/SHECSMacros.h" + +namespace SHADE +{ + struct SHInstancedIntegerData + { + EntityID eid; + uint32_t lightLayer; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.cpp new file mode 100644 index 00000000..b27f48b9 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.cpp @@ -0,0 +1,96 @@ +#include "SHpch.h" +#include "SHMaterial.h" + +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "SHGraphicsConstants.h" +#include "Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Pipeline Functions */ + /*---------------------------------------------------------------------------------*/ + void SHMaterial::SetPipeline(Handle _pipeline) + { + pipeline = _pipeline; + + // Set up properties based on the pipeline + if (!pipeline) + { + // Clear memory and all that + propMemory.reset(); + return; + } + + // Allocate memory for properties + const Handle SHADER_INFO = GetShaderBlockInterface(); + propMemorySize = SHADER_INFO ? SHADER_INFO->GetBytesRequired() : 0; + if (propMemorySize <= 0) + { + propMemory.reset(); + } + else + { + propMemory.reset(new char[propMemorySize]); + } + ResetProperties(); + } + + Handle SHMaterial::GetPipeline() const + { + return pipeline; + } + + /*---------------------------------------------------------------------------------*/ + /* Property Functions */ + /*---------------------------------------------------------------------------------*/ + void SHMaterial::ResetProperties() + { + // Reset all the properties to default values + if (propMemory) + memset(propMemory.get(), 0, propMemorySize); + + // Initialize Vectors to all 1.0 by default + const Handle SHADER_INFO = GetShaderBlockInterface(); + for (int i = 0; i < SHADER_INFO->GetVariableCount(); ++i) + { + const auto& VAR = SHADER_INFO->GetVariable(i); + switch (VAR->type) + { + case SHShaderBlockInterface::Variable::Type::VECTOR3: + setPropertyUnsafe(VAR->offset, SHVec3::One); + break; + case SHShaderBlockInterface::Variable::Type::VECTOR4: + setPropertyUnsafe(VAR->offset, SHVec4::One); + break; + } + } + } + + void SHMaterial::ExportProperties(void* dest) const noexcept + { + if (propMemory) + memcpy(dest, propMemory.get(), propMemorySize); + } + + size_t SHMaterial::GetPropertiesMemorySize() const noexcept + { + const Handle SHADER_INFO = GetShaderBlockInterface(); + return SHADER_INFO ? SHADER_INFO->GetBytesRequired() : 0; + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHMaterial::GetShaderBlockInterface() const noexcept + { + return pipeline->GetPipelineLayout()->GetShaderBlockInterface + ( + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, + vk::ShaderStageFlagBits::eFragment + ); + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.h new file mode 100644 index 00000000..964f9e34 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.h @@ -0,0 +1,87 @@ +/************************************************************************************//*! +\file SHMaterial.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains the class definition of SHMaterial. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" +#include "SHCommonTypes.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkPipeline; + class SHShaderBlockInterface; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Describes a Pipeline along with it's associated properties for this instance. + */ + /***********************************************************************************/ + class SHMaterial + { + public: + /*-----------------------------------------------------------------------------*/ + /* Pipeline Functions */ + /*-----------------------------------------------------------------------------*/ + void SetPipeline(Handle _pipeline); + Handle GetPipeline() const; + + /*-----------------------------------------------------------------------------*/ + /* Property Functions */ + /*-----------------------------------------------------------------------------*/ + template + void SetProperty(const std::string& key, const T& value); + template + void SetProperty(uint32_t memOffset, const T& value); + template + T& GetProperty(const std::string& key); + template + const T& GetProperty(const std::string& key) const; + template + T& GetProperty(uint32_t memOffset); + template + const T& GetProperty(uint32_t memOffset) const; + void ResetProperties(); + void ExportProperties(void* dest) const noexcept; + Byte GetPropertiesMemorySize() const noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Query Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetShaderBlockInterface() const noexcept; + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle pipeline; + std::unique_ptr propMemory; + Byte propMemorySize = 0; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + template + inline void setPropertyUnsafe(uint32_t memOffset, const T& value); // SetProperty() but without checks + }; +} + +#include "SHMaterial.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.hpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.hpp new file mode 100644 index 00000000..f81cfa5c --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterial.hpp @@ -0,0 +1,90 @@ +/************************************************************************************//*! +\file SHMaterial.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains the template function definitions of SHMaterial. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +#include "SHMaterial.h" + +#include "Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h" + + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Property Functions */ + /*---------------------------------------------------------------------------------*/ + template + void SHMaterial::SetProperty(const std::string& key, const T& value) + { + const auto SHADER_INFO = GetShaderBlockInterface(); + const auto PROP_INFO = SHADER_INFO->GetVariable(key); + if (PROP_INFO == nullptr) + { + throw std::invalid_argument("Attempted to set an invalid property!"); + } + + // Get offset and modify the memory directly + T* dataPtr = reinterpret_cast(propMemory.get() + PROP_INFO->offset); + *dataPtr = value; + } + + template + void SHMaterial::SetProperty(uint32_t memOffset, const T& value) + { + // Check if out of bounds + if (memOffset + sizeof(T) > propMemorySize) + throw std::invalid_argument("Attempted to set an invalid property!"); + // Set + setPropertyUnsafe(memOffset, value); + } + + template + T& SHMaterial::GetProperty(const std::string& key) + { + const auto SHADER_INFO = GetShaderBlockInterface(); + const auto PROP_INFO = SHADER_INFO->GetVariable(key); + if (PROP_INFO == nullptr) + { + throw std::invalid_argument("Attempted to retrieve an invalid property!"); + } + + // Get offset and return the memory directly + T* dataPtr = reinterpret_cast(propMemory.get() + PROP_INFO->offset); + return *dataPtr; + } + template + const T& SHMaterial::GetProperty(const std::string& key) const + { + return const_cast(const_cast(this)->GetProperty(key)); + } + + template + const T& SHMaterial::GetProperty(uint32_t memOffset) const + { + // Check if out of bounds + if (memOffset + sizeof(T) > propMemorySize) + throw std::invalid_argument("Attempted to retrieve an invalid property!"); + return *(reinterpret_cast(propMemory.get() + memOffset)); + } + + template + T& SHMaterial::GetProperty(uint32_t memOffset) + { + return const_cast(const_cast(this)->GetProperty(memOffset)); + } + + template + void SHMaterial::setPropertyUnsafe(uint32_t memOffset, const T& value) + { + (*reinterpret_cast(propMemory.get() + memOffset)) = value; + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.cpp new file mode 100644 index 00000000..350580bf --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.cpp @@ -0,0 +1,80 @@ +/************************************************************************************//*! +\file SHMaterialInstance.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2022 +\brief Contains the definition of functions of SHMaterialInstance. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHMaterialInstance.h" + +#include "SHGraphicsConstants.h" +#include "SHMaterial.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructor */ + /*-----------------------------------------------------------------------------------*/ + SHMaterialInstance::SHMaterialInstance(Handle material) + : baseMaterial { material } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Property Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHMaterialInstance::ResetProperties() noexcept + { + + // Reset all the properties to default values + memset(dataStore.get(), 0, dataStoreSize); + overrideData.clear(); + dataStore.reset(); + } + + void SHMaterialInstance::ExportProperties(void* dest) + { + if (!baseMaterial) + throw std::runtime_error("[SHMaterialInstance] Attempted to set export a Material Instance with no base Material!"); + + // Copy default data from Material + baseMaterial->ExportProperties(dest); + + // Follow up with copying our data over + const auto SHADER_INFO = getShaderBlockInterface(); + for (const auto& data : overrideData) + { + // Get memory offset to the data + const SHShaderBlockInterface::Variable* variable = SHADER_INFO->GetVariable(static_cast(data.Index)); + if (variable == nullptr) + { + SHLOG_WARNING("[SHMaterialInstance] Invalid override data indices provided. Skipping."); + continue; + } + const auto DATA_OFFSET = variable->offset; + memcpy(static_cast(dest) + DATA_OFFSET, dataStore.get() + data.StoredDataOffset, data.DataSize); + } + + // Data was exported so unflag + dataWasChanged = false; + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHMaterialInstance::getShaderBlockInterface() const noexcept + { + return baseMaterial->GetPipeline()->GetPipelineLayout()->GetShaderBlockInterface + ( + SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, + vk::ShaderStageFlagBits::eFragment + ); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.h new file mode 100644 index 00000000..b6fcc830 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.h @@ -0,0 +1,91 @@ +/************************************************************************************//*! +\file SHMaterialInstance.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2022 +\brief Contains the class definition of SHMaterialInstance. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" +#include "Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h" +#include "SH_API.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHMaterial; + class SHGraphicsSystem; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Describes an instance of a SHMaterial that can be used to describe how to render + a SHRenderable. + */ + /***********************************************************************************/ + class SH_API SHMaterialInstance + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + struct OverrideData + { + size_t Index; + size_t DataSize; + size_t StoredDataOffset; + }; + + /*-----------------------------------------------------------------------------*/ + /* Constructor */ + /*-----------------------------------------------------------------------------*/ + SHMaterialInstance(Handle material = {}); + + /*-----------------------------------------------------------------------------*/ + /* Property Functions */ + /*-----------------------------------------------------------------------------*/ + template + void SetProperty(const std::string& key, const T& value); + template + T& GetProperty(const std::string& key); + template + const T& GetProperty(const std::string& key) const; + void ResetProperties() noexcept; + void ExportProperties(void* dest); + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetBaseMaterial() const noexcept { return baseMaterial; } + bool HasChanged() const noexcept { return dataWasChanged; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle baseMaterial; + std::vector overrideData; + std::unique_ptr dataStore; + size_t dataStoreSize = 0; + bool dataWasChanged = false; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + Handle getShaderBlockInterface() const noexcept; + }; +} + +#include "SHMaterialInstance.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.hpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.hpp new file mode 100644 index 00000000..e70631ea --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMaterialInstance.hpp @@ -0,0 +1,88 @@ +/************************************************************************************//*! +\file SHMaterialInstance.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2022 +\brief Contains the definition of functions templates of SHMaterialInstance. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +#include "SHMaterialInstance.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Property Functions */ + /*---------------------------------------------------------------------------------*/ + template + void SHMaterialInstance::SetProperty(const std::string& key, const T& value) + { + const auto SHADER_INFO = getShaderBlockInterface(); + const auto PROP_INFO = SHADER_INFO->GetVariable(key); + if (PROP_INFO == nullptr) + { + throw std::invalid_argument("Attempted to set an invalid property!"); + } + + // Allocate data store if it was empty + if (dataStore == nullptr) + { + dataStoreSize = SHADER_INFO->GetBytesRequired(); + dataStore.reset(new char[dataStoreSize]); + } + + OverrideData od; + od.Index = SHADER_INFO->GetVariableIndex(key); + od.DataSize = sizeof(T); + if (overrideData.empty()) + { + od.StoredDataOffset = 0; + } + else + { + const OverrideData& lastInsertedData = overrideData.back(); + od.StoredDataOffset = lastInsertedData.StoredDataOffset + lastInsertedData.DataSize; + } + + // Get offset and modify the memory directly + T* dataPtr = reinterpret_cast(dataStore.get() + od.StoredDataOffset); + *dataPtr = value; + + // Save the override data information + overrideData.emplace_back(std::move(od)); + + // Flag + dataWasChanged = true; + } + template + T& SHMaterialInstance::GetProperty(const std::string& key) + { + const auto SHADER_INFO = getShaderBlockInterface(); + const auto PROP_INFO = SHADER_INFO->GetVariable(key); + if (PROP_INFO == nullptr) + { + throw std::invalid_argument("Attempted to get an invalid property!"); + } + + // Search Override Data for the property + uint32_t PROP_IDX = SHADER_INFO->GetVariableIndex(key); + auto prop = std::find_if(overrideData.begin(), overrideData.end(), [&](const OverrideData& data) + { + return PROP_IDX == data.Index; + }); + if (prop == overrideData.end()) + throw std::invalid_argument("Attempted to get an property that was not set previously!"); + + // Get offset and return the memory directly + T* dataPtr = reinterpret_cast(dataStore.get() + prop->StoredDataOffset); + return *dataPtr; + } + template + const T& SHMaterialInstance::GetProperty(const std::string& key) const + { + return const_cast(const_cast(this)->GetProperty(key)); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp new file mode 100644 index 00000000..d34c1b7d --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp @@ -0,0 +1,206 @@ +/************************************************************************************//*! +\file SHMeshLibrary.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains definitions for all of the functions of the classes that deal + with storage and management of vertex and index buffers of meshes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHMeshLibrary.h" + +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "../../SHVkUtil.h" + +namespace SHADE +{ + SHADE::Handle SHMeshLibrary::AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices) + { + isDirty = true; + + auto handle = meshes.Create(); + meshAddJobs.emplace_back( MeshAddJob + { + vertexCount, + positions, + texCoords, + tangents, + normals, + indexCount, + indices, + handle + }); + return handle; + } + + void SHMeshLibrary::RemoveMesh(Handle mesh) + { + if (!mesh) + throw std::invalid_argument("Attempted to remove a Mesh that did not belong to the Mesh Library!"); + + meshRemoveJobs.emplace_back(mesh); + isDirty = true; + } + + void SHMeshLibrary::BuildBuffers(Handle device, Handle cmdBuffer) + { + // No changes + if (!isDirty) + return; + + // Remove + if (!meshRemoveJobs.empty()) + { + // - Remove from order list + for (const auto& meshToRemove : meshRemoveJobs) + { + auto searchResult = std::find(meshOrder.begin(), meshOrder.end(), meshToRemove); + // Shouldn't happen, ignore + if (searchResult == meshOrder.end()) + continue; + + // Remove from mesh list + meshOrder.erase(searchResult); + } + meshRemoveJobs.clear(); + // - Shift existing elements in to close up the gaps + int32_t nextVertInsertPoint = 0; + uint32_t nextIdxInsertPoint = 0; + for (auto& mesh : meshOrder) + { + // Check if already in the correct place + if (nextVertInsertPoint != mesh->FirstVertex) + { + /* There's a gap, we need to shift */ + // Vertices + vertPosStorage.erase(vertPosStorage.begin() + nextVertInsertPoint, vertPosStorage.begin() + mesh->FirstVertex); + vertTexCoordStorage.erase(vertTexCoordStorage.begin() + nextVertInsertPoint, vertTexCoordStorage.begin() + mesh->FirstVertex); + vertTangentStorage.erase(vertTangentStorage.begin() + nextVertInsertPoint, vertTangentStorage.begin() + mesh->FirstVertex); + vertNormalStorage.erase(vertNormalStorage.begin() + nextVertInsertPoint, vertNormalStorage.begin() + mesh->FirstVertex); + // - Update mesh data + mesh->FirstVertex = nextVertInsertPoint; + + // Indices + indexStorage.erase(indexStorage.begin() + nextIdxInsertPoint, indexStorage.begin() + mesh->FirstIndex); + // - Update mesh data + mesh->FirstIndex = nextIdxInsertPoint; + + // Prepare for next + nextVertInsertPoint += mesh->VertexCount; + nextIdxInsertPoint += mesh->IndexCount; + } + } + } + + // Add + if (!meshAddJobs.empty()) + { + // - Compute updated size + size_t newVertElems = vertPosStorage.size(); + size_t newIdxElems = indexStorage.size(); + for (const auto& addJob : meshAddJobs) + { + newVertElems += addJob.VertexCount; + newIdxElems += addJob.IndexCount; + } + // - Reserve new memory + vertPosStorage .reserve(newVertElems); + vertTexCoordStorage.reserve(newVertElems); + vertTangentStorage .reserve(newVertElems); + vertNormalStorage .reserve(newVertElems); + indexStorage .reserve(newIdxElems); + // - Append new data + for (auto& addJob : meshAddJobs) + { + // Update handle + SHMesh& meshData = *addJob.Handle; + meshData = SHMesh + { + .FirstVertex = static_cast(vertPosStorage.size()), + .VertexCount = static_cast(addJob.VertexCount), + .FirstIndex = static_cast(indexStorage.size()), + .IndexCount = static_cast(addJob.IndexCount), + }; + + // Copy into storage + vertPosStorage.insert + ( + vertPosStorage.end(), + addJob.VertexPositions, addJob.VertexPositions + addJob.VertexCount + ); + vertTexCoordStorage.insert + ( + vertTexCoordStorage.end(), + addJob.VertexTexCoords, addJob.VertexTexCoords + addJob.VertexCount + ); + vertTangentStorage.insert + ( + vertTangentStorage.end(), + addJob.VertexTangents, addJob.VertexTangents + addJob.VertexCount + ); + vertNormalStorage.insert + ( + vertNormalStorage.end(), + addJob.VertexNormals, addJob.VertexNormals + addJob.VertexCount + ); + indexStorage.insert + ( + indexStorage.end(), + addJob.Indices, addJob.Indices + addJob.IndexCount + ); + } + meshAddJobs.clear(); + } + + // Send to GPU + using BuffUsage = vk::BufferUsageFlagBits; + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertPosBuffer, + vertPosStorage.data(), + static_cast(vertPosStorage.size()) * sizeof(SHMesh::VertexPosition), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Positions" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertTexCoordBuffer, + vertTexCoordStorage.data(), + static_cast(vertTexCoordStorage.size()) * sizeof(SHMesh::VertexTexCoord), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex TexCoords" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertTangentBuffer, + vertTangentStorage.data(), + static_cast(vertTangentStorage.size()) * sizeof(SHMesh::VertexTangent), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Tangents" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertNormalBuffer, + vertNormalStorage.data(), + static_cast(vertNormalStorage.size()) * sizeof(SHMesh::VertexNormal), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Normals" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, indexBuffer, + indexStorage.data(), + static_cast(indexStorage.size()) * sizeof(SHMesh::Index), + BuffUsage::eIndexBuffer, + "Mesh Library Indices" + ); + + isDirty = false; + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h new file mode 100644 index 00000000..b0cbdce1 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h @@ -0,0 +1,188 @@ +/************************************************************************************//*! +\file SHMeshLibrary.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains definitions for all of the classes that deal with storage and + management of vertex and index buffers of meshes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" +#include "Resource/SHResourceLibrary.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkLogicalDevice; + class SHVkCommandBuffer; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Represents a single mesh that is stored in a SHMeshLibrary. + */ + /***********************************************************************************/ + class SHMesh + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using Index = uint32_t; + using VertexPosition = SHVec3; + using VertexTexCoord = SHVec2; + using VertexTangent = SHVec3; + using VertexNormal = SHVec3; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + int32_t FirstVertex; + uint32_t VertexCount; + uint32_t FirstIndex; + uint32_t IndexCount; + }; + /***********************************************************************************/ + /*! + \brief + Manages storage for all Meshes in the Graphics System as a single set of Vertex + and Index Buffers. + */ + /***********************************************************************************/ + class SHMeshLibrary + { + public: + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /*******************************************************************************/ + /*! + + \brief + Adds a mesh to the Mesh Library. But this does not mean that the meshes have + been added yet. A call to "BuildBuffers()" is required to transfer all + meshes into the GPU. + + \param vertexCount + Number of vertices in this Mesh. + \param positions + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + positions. + \param texCoords + Pointer to the first in a contiguous array of SHMathVec2s that define vertex + texture coordinates. + \param tangents + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + tangents. + \param normals + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + normals. + \param indexCount + Number of indices in this mesh. + \param indices + Pointer to the first in a contiguous array of uint32_ts that define mesh + indicies. + + \return + Handle to the created Mesh. This is not valid to be used until a call to + BuildBuffers(). + + */ + /*******************************************************************************/ + Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices); + /*******************************************************************************/ + /*! + + \brief + Removes a mesh from the MeshLibrary. But this does not mean that the meshes + have been removed yet. A call to "BuildBuffers()" is required to finalise all + changes. + + \param mesh + Handle to the mesh to remove. + + */ + /*******************************************************************************/ + void RemoveMesh(Handle mesh); + /***************************************************************************/ + /*! + + \brief + Finalises all changes to the MeshLibrary into the GPU buffers. + + \param device + Device used to create and update the buffers. + \param cmdBuffer + Command buffer used to set up transfers of data in the GPU memory. This + call must be preceded by calls to cmdBuffer's BeginRecording() and ended + with EndRecording(). Do recall to also submit the cmdBuffer to a transfer + queue. + */ + /***************************************************************************/ + void BuildBuffers(Handle device, Handle cmdBuffer); + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetVertexPositionsBuffer() const noexcept { return vertPosBuffer; } + Handle GetVertexTexCoordsBuffer() const noexcept { return vertTexCoordBuffer; } + Handle GetVertexTangentsBuffer() const noexcept { return vertTangentBuffer; } + Handle GetVertexNormalsBuffer() const noexcept { return vertNormalBuffer; } + Handle GetIndexBuffer() const { return indexBuffer; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definition */ + /*-----------------------------------------------------------------------------*/ + struct MeshAddJob + { + uint32_t VertexCount = 0; + const SHMesh::VertexPosition* VertexPositions = nullptr; + const SHMesh::VertexTexCoord* VertexTexCoords = nullptr; + const SHMesh::VertexTangent * VertexTangents = nullptr; + const SHMesh::VertexNormal * VertexNormals = nullptr; + uint32_t IndexCount = 0; + const SHMesh::Index * Indices = nullptr; + Handle Handle; + }; + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Manipulation Queues + std::vector meshAddJobs; + std::vector> meshRemoveJobs; + // Tracking + SHResourceLibrary meshes{}; + std::vector> meshOrder; + // CPU Storage + std::vector vertPosStorage; + std::vector vertTexCoordStorage; + std::vector vertTangentStorage; + std::vector vertNormalStorage; + std::vector indexStorage; + // GPU Storage + Handle vertPosBuffer{}; + Handle vertTexCoordBuffer{}; + Handle vertTangentBuffer{}; + Handle vertNormalBuffer{}; + Handle indexBuffer {}; + // Flags + bool isDirty = true; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.cpp new file mode 100644 index 00000000..86d85c16 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.cpp @@ -0,0 +1,92 @@ +#include "SHpch.h" +#include "SHMousePickSystem.h" +#include "Input/SHInputManager.h" +#include "Graphics/Commands/SHVkCommandPool.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Synchronization/SHVkFence.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/SHVkUtil.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +//#include "Graphics/MiddleEnd/Interface/SHInstancedIntegerData.h" + +namespace SHADE +{ + void SHMousePickSystem::Init(Handle device, std::span> cmdPools, Handle eidAttachment) noexcept + { + logicalDevice = device; + + pickedEID = MAX_EID; + + // Create command buffers + for (auto& pool : cmdPools) + { + commandBuffers.push_back(pool->RequestCommandBuffer (SH_CMD_BUFFER_TYPE::PRIMARY)); + } + + // assign the attachment + entityIDAttachment = eidAttachment; + + HandleResize(); + } + + void SHMousePickSystem::Run(Handle queue, uint32_t frameIndex) noexcept + { + // if input detected + if (SHInputManager::GetKeyUp(SHInputManager::SH_KEYCODE::LMB)) + { + afterCopyFence->Reset(); + + // begin command buffer for recording + commandBuffers[frameIndex]->BeginRecording(); + + // transition the image for optimized copying + entityIDAttachment->TransitionImage (vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, commandBuffers[frameIndex], vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, 0); + + // copy the image here. Last argument is 0 because the attachment isn't a swapchain image. + entityIDAttachment->CopyToBuffer(imageDataDstBuffer, commandBuffers[frameIndex], 0); + + // end command buffer for recording + commandBuffers[frameIndex]->EndRecording(); + + // submit the command buffer to copy image to buffer + queue->SubmitCommandBuffer({ commandBuffers[frameIndex] }, {}, {}, vk::PipelineStageFlagBits::eColorAttachmentOutput, afterCopyFence); + + // wait for the copy to be done + afterCopyFence->Wait(true, std::numeric_limits::max()); + + pickedEID = imageDataDstBuffer->GetDataFromMappedPointer(static_cast(viewportMousePos.y) * entityIDAttachment->GetWidth() + static_cast(viewportMousePos.x)); + + } + } + + void SHMousePickSystem::HandleResize(void) noexcept + { + if (afterCopyFence) + { + afterCopyFence->Reset(); + afterCopyFence.Free(); + } + + if (imageDataDstBuffer) + imageDataDstBuffer.Free(); + + // Create the fence + afterCopyFence = logicalDevice->CreateFence(); + + uint32_t bufferSize = entityIDAttachment->GetWidth() * entityIDAttachment->GetHeight() * SHVkUtil::GetBytesPerPixelFromFormat(entityIDAttachment->GetResourceFormat()); + + // Create the buffer + imageDataDstBuffer = logicalDevice->CreateBuffer(bufferSize, nullptr, bufferSize, vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_AUTO, VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, "Mouse Pick Image Data Destination"); + } + + void SHMousePickSystem::SetViewportMousePos(SHVec2 vpMousePos) noexcept + { + viewportMousePos = vpMousePos; + } + + EntityID SHMousePickSystem::GetPickedEntity(void) const noexcept + { + return pickedEID; + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.h new file mode 100644 index 00000000..85e86969 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMousePickSystem.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Graphics/RenderGraph/SHRenderGraphResource.h" +#include "ECS_Base/SHECSMacros.h" +#include "Math/Vector/SHVec2.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkCommandPool; + class SHVkCommandBuffer; + class SHVkFence; + class SHVkQueue; + class SHVkBuffer; + class SHViewport; + + class SHMousePickSystem + { + private: + Handle logicalDevice; + + //! Handle to the render graph resource that will contain the entity IDs + Handle entityIDAttachment; + + //! Command buffers meant for copying image to buffer + std::vector> commandBuffers; + + //! After the attachment has copied its data to a buffer, we want to signal this fence + Handle afterCopyFence; + + //! buffer to contain the attachment data after copying + Handle imageDataDstBuffer; + + //! eid picked from screen + EntityID pickedEID; + + //! mouse position relative to the viewport window displaying the world + SHVec2 viewportMousePos; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Init(Handle device, std::span> cmdPools, Handle eidAttachment) noexcept; + void Run (Handle queue, uint32_t frameIndex) noexcept; + void HandleResize (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + void SetViewportMousePos (SHVec2 vpMousePos) noexcept; + + EntityID GetPickedEntity (void) const noexcept; + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.cpp new file mode 100644 index 00000000..8b41a979 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.cpp @@ -0,0 +1,84 @@ +#include "SHpch.h" +#include "SHPostOffscreenRenderSystem.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Images/SHVkSampler.h" +#include "Graphics/RenderGraph/SHRenderGraphResource.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + This function basically creates the entities required for offscreen + rendering. It takes in a render graph resource. Side note: it creates + a descriptor set layout that is similar to the one created in imgui. This + is so that the descriptor set passed to imGui won't be invalid. + + \param logicalDevice + For vulkan object creation + + \param renderGraphResource + The resource in which has been + + \param descriptorPool + + \return + + */ + /***************************************************************************/ + void SHPostOffscreenRenderSystem::Init(Handle logicalDevice, Handle renderGraphResource, Handle descriptorPool) noexcept + { + offscreenRender = renderGraphResource; + + // Create sampler + offscreenRenderSampler = logicalDevice->CreateSampler( + { + .minFilter = vk::Filter::eLinear, + .magFilter = vk::Filter::eLinear, + .addressMode = vk::SamplerAddressMode::eRepeat, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .minLod = -1000, + .maxLod = 1000 + } + ); + + // Create descriptor set layout binding + SHVkDescriptorSetLayout::Binding imageBinding + { + .Type = vk::DescriptorType::eCombinedImageSampler, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = 0, + .DescriptorCount = 1, + }; + + // Create descriptor set layout + offscreenRenderDescSetLayout = logicalDevice->CreateDescriptorSetLayout(0, { imageBinding }, false); + + // Create descriptor set + offscreenRenderDescSet = descriptorPool->Allocate({ offscreenRenderDescSetLayout }, { 1 }); + + HandleResize(); + } + + void SHPostOffscreenRenderSystem::HandleResize(void) noexcept + { + std::vector combinedImageSampler + { + std::make_tuple(offscreenRender->GetImageView(), offscreenRenderSampler, vk::ImageLayout::eShaderReadOnlyOptimal), + }; + + // Register the image view and sampler with the descriptor set. Now whenever rendering to the offscreen image is done, the descriptor set will see the change + offscreenRenderDescSet->ModifyWriteDescImage(0, 0, combinedImageSampler); + offscreenRenderDescSet->UpdateDescriptorSetImages(0, 0); + } + + Handle SHPostOffscreenRenderSystem::GetDescriptorSetGroup(void) const noexcept + { + return offscreenRenderDescSet; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.h new file mode 100644 index 00000000..7a236eaf --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHPostOffscreenRenderSystem.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Resource/SHHandle.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkDescriptorSetLayout; + class SHVkDescriptorSetGroup; + class SHVkDescriptorPool; + class SHVkSampler; + class SHRenderGraphResource; + + class SHPostOffscreenRenderSystem + { + private: + Handle offscreenRender; + + Handle offscreenRenderDescSetLayout; + Handle offscreenRenderDescSet; + Handle offscreenRenderSampler; + + public: + void Init (Handle logicalDevice, Handle renderGraphResource, Handle descriptorPool) noexcept; + //void Run () + + void HandleResize (void) noexcept; + + Handle GetDescriptorSetGroup (void) const noexcept; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp index eec4d28a..e728ff68 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp @@ -1,2 +1,2 @@ -#include "SHPch.h" +#include "SHpch.h" #include "SHRenderTarget.h" diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp new file mode 100644 index 00000000..c5511606 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp @@ -0,0 +1,135 @@ +/************************************************************************************//*! +\file SHRenderable.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 19, 2022 +\brief Contains the definition of functions of the SHRenderable Component class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" + +#include "SHRenderable.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "SHGraphicsSystem.h" +#include "SHMaterialInstance.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* SHComponent Lifecycle Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderable::OnCreate() + { + matChanged = true; + sharedMaterial = {}; + material = {}; + oldMaterial = {}; + + lightLayer = 1; + } + + void SHRenderable::OnDestroy() + { + // Remove from SuperBatch + if (sharedMaterial) + { + Handle superBatch = sharedMaterial->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass()->GetSuperBatch(); + superBatch->Remove(this); + } + + // Free resources + if (material) + { + material.Free(); + material = {}; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Material Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderable::SetMaterial(Handle materialInstance) + { + // Ignore if same material set + if (!material && sharedMaterial == materialInstance) + return; + + // Flag that material was changed + matChanged = true; + + // Free copies of materials if any + if (material) + { + oldMaterial = material; + material.Free(); + material = {}; + } + else if (sharedMaterial) + { + oldMaterial = sharedMaterial; + } + + // Update the material + sharedMaterial = materialInstance; + } + + Handle SHRenderable::GetMaterial() const + { + if (material) + return material; + + return sharedMaterial; + } + + Handle SHRenderable::GetModifiableMaterial() + { + // Create a copy if it wasn't created + if (!material) + { + SHGraphicsSystem* gfxSystem = SHSystemManager::GetSystem(); + material = gfxSystem->AddMaterialInstanceCopy(sharedMaterial); + } + + return material; + } + + /*-----------------------------------------------------------------------------------*/ + /* Mesh Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderable::SetMesh(Handle newMesh) + { + oldMesh = mesh; + mesh = newMesh; + meshChanged = true; + } + + /*-----------------------------------------------------------------------------------*/ + /* Light Functions */ + /*-----------------------------------------------------------------------------------*/ + uint8_t SHRenderable::GetLightLayer(void) const noexcept + { + return lightLayer; + } + + /*-----------------------------------------------------------------------------------*/ + /* Batcher Dispatcher Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderable::ResetChangedFlag() + { + matChanged = false; + meshChanged = false; + oldMaterial = {}; + oldMesh = {}; + } +} + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::class_("Renderable Component"); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h new file mode 100644 index 00000000..8893c43b --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h @@ -0,0 +1,92 @@ +/************************************************************************************//*! +\file SHRenderable.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 19, 2022 +\brief Contains the definition of the SHRenderable Component class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include +// Project Includes +#include "Resource/SHHandle.h" +#include "ECS_Base/Components/SHComponent.h" +#include "Math/SHMatrix.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHMaterialInstance; + class SHMaterial; + class SHMesh; + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Represents an object that should be rendered. + */ + /*************************************************************************************/ + class SH_API SHRenderable final : public SHComponent + { + public: + /*-------------------------------------------------------------------------------*/ + /* SHComponent Lifecycle Functions */ + /*-------------------------------------------------------------------------------*/ + void OnCreate() override final; + void OnDestroy() override final; + + /*-------------------------------------------------------------------------------*/ + /* Material Functions */ + /*-------------------------------------------------------------------------------*/ + void SetMaterial(Handle materialInstance); + Handle GetMaterial() const; + Handle GetModifiableMaterial(); + Handle GetPrevMaterial() const noexcept { return oldMaterial; } + bool HasMaterialChanged() const noexcept { return matChanged; } + + /*-------------------------------------------------------------------------------*/ + /* Mesh Functions */ + /*-------------------------------------------------------------------------------*/ + void SetMesh(Handle newMesh); + Handle GetMesh() const noexcept { return mesh; } + Handle GetPrevMesh() const noexcept { return oldMesh; } + bool HasMeshChanged() const noexcept { return meshChanged; } + + /*-------------------------------------------------------------------------------*/ + /* Light Functions */ + /*-------------------------------------------------------------------------------*/ + uint8_t GetLightLayer (void) const noexcept; + + /*-------------------------------------------------------------------------------*/ + /* Batcher Dispatcher Functions */ + /*-------------------------------------------------------------------------------*/ + bool HasChanged() const noexcept { return matChanged || meshChanged; } // Whether or not the mesh or material has changed + void ResetChangedFlag(); // TODO: Lock it so that only SHBatcherDispatcher can access this + + private: + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + Handle mesh; + Handle oldMesh; + bool meshChanged = true; + Handle sharedMaterial; + Handle material; + bool matChanged = true; + Handle oldMaterial; + uint8_t lightLayer; + + RTTR_ENABLE() + }; +} + diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp new file mode 100644 index 00000000..63d374eb --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp @@ -0,0 +1,137 @@ +/************************************************************************************//*! +\file SHRenderer.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" + +#include "SHRenderer.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "SHViewport.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "SHMaterial.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Camera/SHCameraDirector.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------------*/ + SHRenderer::SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph) + : viewport { viewport } + , renderGraph { renderGraph } + { + commandBuffers.resize(static_cast(numFrames)); + + for (uint32_t i = 0; i < commandBuffers.size(); ++i) + commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + + cameraDescriptorSet = descriptorPool->Allocate({ cameraDescLayout }, { 1 }); + +#ifdef _DEBUG + const auto& CAM_DESC_SETS = cameraDescriptorSet->GetVkHandle(); + for (int i = 0; i < static_cast(CAM_DESC_SETS.size()); ++i) + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSet, CAM_DESC_SETS[i], "[Descriptor Set] Camera Data Frame #" + std::to_string(i)); +#endif + + cameraDataAlignedSize = logicalDevice->PadUBOSize(sizeof(SHShaderCameraData)); + + cameraBuffer = logicalDevice->CreateBuffer(cameraDataAlignedSize * numFrames, nullptr, cameraDataAlignedSize * numFrames, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_AUTO, VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, "Camera Data"); + + std::array cameraBufferArray{cameraBuffer}; + + cameraDescriptorSet->ModifyWriteDescBuffer(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA, std::span>{ cameraBufferArray.data(), cameraBufferArray.size()}, 0, sizeof (SHShaderCameraData)); + + cameraDescriptorSet->UpdateDescriptorSetBuffer(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA); + } + + SHRenderer::~SHRenderer(void) + { + //for (auto& cmdBuffer : commandBuffers) + //{ + // cmdBuffer.Free(); + //} + } + + /*-----------------------------------------------------------------------------------*/ + /* Camera Registration */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderer::SetCamera(Handle _camera) + { + camera = _camera; + } + + void SHRenderer::SetCameraDirector(Handle director) noexcept + { + cameraDirector = director; + } + + /*-----------------------------------------------------------------------------------*/ + /* Drawing Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHRenderer::Draw(uint32_t frameIndex, Handle descPool) noexcept + { + renderGraph->Execute(frameIndex, commandBuffers[frameIndex], descPool); + } + + void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept + { + if (camera && cameraDirector) + { + //UpdateDataAndBind(cmdBuffer, frameIndex, SHMatrix::Transpose(cameraDirector->GetVPMatrix())); + UpdateDataAndBind(cmdBuffer, frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix()); + } + } + + void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept + { + SetViewProjectionMatrix(viewMatrix, projMatrix); + + //cpuCameraData.viewProjectionMatrix = camera->GetViewProjectionMatrix(); + cameraBuffer->WriteToMemory(&cpuCameraData, sizeof(SHShaderCameraData), 0, cameraDataAlignedSize * frameIndex); + + std::array dynamicOffsets{ frameIndex * cameraDataAlignedSize }; + + cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::GRAPHICS, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); + cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); + } + + void SHRenderer::UpdateCameraDataToBuffer(void) noexcept + { + } + + void SHRenderer::SetViewProjectionMatrix(SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept + { + //cpuCameraData.viewProjectionMatrix = camera->GetViewMatrix() * camera->GetProjectionMatrix(); + cpuCameraData.viewProjectionMatrix = SHMatrix::Transpose(projMatrix * viewMatrix); + cpuCameraData.viewMatrix = SHMatrix::Transpose(viewMatrix); + cpuCameraData.projectionMatrix = SHMatrix::Transpose(projMatrix); + } + + Handle SHRenderer::GetRenderGraph(void) const noexcept + { + return renderGraph; + } + + Handle SHRenderer::GetCommandBuffer(uint32_t frameIndex) const noexcept + { + return commandBuffers[frameIndex]; + } + + Handle SHRenderer::GetCameraDirector(void) const noexcept + { + return cameraDirector; + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h new file mode 100644 index 00000000..140cf53b --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h @@ -0,0 +1,120 @@ +/************************************************************************************//*! +\file SHRenderer.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include + +// Project Includes +#include "SHCamera.h" +#include "Resource/SHHandle.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" +#include "Math/SHMath.h" +#include + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkRenderpass; + class SHVkFramebuffer; + class SHMaterial; + class SHVkLogicalDevice; + class SHViewport; + class SHVkImageView; + class SHVkCommandBuffer; + class SHCamera; + class SHVkDescriptorSetGroup; + class SHGraphicsGlobalData; + class SHVkDescriptorPool; + class SHVkBuffer; + class SHCameraDirector; + + struct SHShaderCameraData + { + SHVec4 cameraPosition; + SHMatrix viewProjectionMatrix; + SHMatrix viewMatrix; + SHMatrix projectionMatrix; + }; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Brings together a set of Cameras and objects with various Pipelines to render + them as part of a set. Multiple Renderers can exist to render objects differently + in a separate step. + */ + /***********************************************************************************/ + class SHRenderer + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph); + ~SHRenderer(void); + + /*-----------------------------------------------------------------------------*/ + /* Camera Registration */ + /*-----------------------------------------------------------------------------*/ + void SetCamera(Handle _camera); + void SetCameraDirector (Handle director) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Drawing Functions */ + /*-----------------------------------------------------------------------------*/ + void Draw(uint32_t frameIndex, Handle descPool) noexcept; + void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept; + void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; + void UpdateCameraDataToBuffer (void) noexcept; + void SetViewProjectionMatrix (SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Setters and Getters */ + /*-----------------------------------------------------------------------------*/ + Handle GetRenderGraph (void) const noexcept; + Handle GetCommandBuffer(uint32_t frameIndex) const noexcept; + Handle GetCameraDirector (void) const noexcept; + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + //! Vulkan UBOs need to be aligned, this is pad SHShaderCameraData struct + uint32_t cameraDataAlignedSize; + + Handle viewport; + Handle camera; + Handle renderGraph; + Handle cameraDescriptorSet; + Handle cameraBuffer; + + Handle cameraDirector; + + // we really only need 1 copy even though we need %swapchainImages copies for + // GPU. + SHShaderCameraData cpuCameraData; + + //! Command buffers for the render graph + std::vector> commandBuffers; + + + + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp new file mode 100644 index 00000000..df9e244e --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp @@ -0,0 +1,98 @@ +/************************************************************************************//*! +\file SHViewport.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 27, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHViewport.h" + +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Tools/SHLogger.h" +#include "SHRenderer.h" +#include "Resource/SHResourceLibrary.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHViewport::SHViewport(Handle device, const vk::Viewport& viewport) + : device { device } + , viewport { viewport } + {} + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void SHViewport::SetUp(Handle commandBuffer) + { + commandBuffer->SetViewportScissor + ( + viewport.x, + viewport.y, + static_cast(viewport.width), + static_cast(viewport.height), + viewport.minDepth, + viewport.maxDepth + ); + } + + /*---------------------------------------------------------------------------------*/ + /* Renderer Registration Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHViewport::AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph) + { + // Create the renderer + auto renderer = resourceManager.Create(device, numFrames, cmdPools, descriptorPool, cameraDescLayout, GetHandle(), renderGraph); + + // Store + renderers.emplace_back(renderer); + + // Return + return renderer; + } + void SHViewport::RemoveRenderer(Handle renderer) + { + auto iter = std::find(renderers.begin(), renderers.end(), renderer); + if (iter == renderers.end()) + { + SHLOG_WARNING("Attempted to remove a Renderer that does not belong to a viewport!"); + return; + } + + // Remove it + iter->Free(); + renderers.erase(iter); + } + + void SHViewport::SetWidth(float w) noexcept + { + viewport.width = w; + } + + void SHViewport::SetHeight(float h) noexcept + { + viewport.height = h; + + } + + void SHViewport::SetX(float x) noexcept + { + viewport.x = x; + } + + void SHViewport::SetY(float y) noexcept + { + viewport.y = y; + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h new file mode 100644 index 00000000..26c0a6bd --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h @@ -0,0 +1,94 @@ +/************************************************************************************//*! +\file SHViewport.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 27, 2022 +\brief Contains the class definition of SHViewport. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// External Dependencies +#include "Graphics/SHVulkanIncludes.h" +// Project Includes +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHRenderer; + class SHVkCommandBuffer; + class SHVkLogicalDevice; + class SHVkImageView; + class SHResourceHub; + class SHRenderGraph; + class SHVkDescriptorPool; + class SHVkDescriptorSetLayout; + class SHVkCommandPool; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Brings together a set of Renderers that render to the same segment of an image. + */ + /***********************************************************************************/ + class SHViewport : public ISelfHandle + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHViewport(Handle device, const vk::Viewport& viewport); + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + void SetUp(Handle commandBuffer); + + /*-----------------------------------------------------------------------------*/ + /* Renderers Registration Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph); + void RemoveRenderer(Handle renderer); + + /*-----------------------------------------------------------------------------*/ + /* Setters */ + /*-----------------------------------------------------------------------------*/ + void SetWidth(float w) noexcept; + void SetHeight (float h) noexcept; + void SetX (float x) noexcept; + void SetY (float y) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Getters */ + /*-----------------------------------------------------------------------------*/ + float GetX() const { return viewport.x; } + float GetY() const { return viewport.y; } + float GetWidth() const { return viewport.width; } + float GetHeight() const { return viewport.height; } + float GetMinDepth() const { return viewport.minDepth; } + float GetMaxDepth() const { return viewport.maxDepth; } + std::vector>& GetRenderers() { return renderers; } + + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle device; + vk::Viewport viewport; + std::vector> renderers; + }; + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.cpp new file mode 100644 index 00000000..2ea6bc8b --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.cpp @@ -0,0 +1,181 @@ +#include "SHpch.h" +#include "SHLightComponent.h" + +namespace SHADE +{ + + + void SHLightComponent::OnCreate(void) + { + lightData.Reset(); + SetType(SH_LIGHT_TYPE::DIRECTIONAL); + //indexInBuffer = std::numeric_limits::max(); + isActive = true; + //Unbind(); + } + + + void SHLightComponent::OnDestroy(void) + { + + } + + void SHLightComponent::SetPosition(SHVec3 const& position) noexcept + { + lightData.position = position; + //MakeDirty(); + } + + + void SHLightComponent::SetType(SH_LIGHT_TYPE type) noexcept + { + lightData.type = type; + //MakeDirty(); + } + + + void SHLightComponent::SetDirection(SHVec3 const& direction) noexcept + { + lightData.direction = direction; + //MakeDirty(); + } + + + void SHLightComponent::SetColor(SHVec4 const& color) noexcept + { + lightData.color = color; + //MakeDirty(); + } + + void SHLightComponent::ModifyCullingMask(uint8_t layerIndex, bool value) noexcept + { + if (value) + lightData.cullingMask |= (1u << layerIndex); + else + lightData.cullingMask &= ~(1u << layerIndex); + + //MakeDirty(); + } + + void SHLightComponent::SetCullingMask(uint32_t const& value) noexcept + { + lightData.cullingMask = value; + } + + void SHLightComponent::SetAllLayers(void) noexcept + { + lightData.cullingMask = std::numeric_limits::max(); + //MakeDirty(); + } + + + void SHLightComponent::ClearAllLayers(void) noexcept + { + lightData.cullingMask = 0; + //MakeDirty(); + } + + //void SHLightComponent::MakeDirty(void) noexcept + //{ + // dirty = true; + //} + + //void SHLightComponent::ClearDirtyFlag(void) noexcept + //{ + // dirty = false; + //} + +// void SHLightComponent::Unbind(void) noexcept +// { +// bound = false; +// MakeDirty(); +// } +// +// void SHLightComponent::SetBound(uint32_t inIndexInBuffer) noexcept +//{ +// bound = true; +// indexInBuffer = inIndexInBuffer; +// } + + + void SHLightComponent::SetStrength(float value) noexcept + { + lightData.strength = value; + //MakeDirty(); + } + + SHLightData const& SHLightComponent::GetLightData(void) const noexcept + { + return lightData; + } + + SHVec3 const& SHLightComponent::GetPosition(void) const noexcept + { + return lightData.position; + } + + SH_LIGHT_TYPE SHLightComponent::GetType(void) const noexcept + { + return lightData.type; + } + + SHVec3 const& SHLightComponent::GetDirection(void) const noexcept + { + return lightData.direction; + } + + SHVec4 const& SHLightComponent::GetColor(void) const noexcept + { + return lightData.color; + } + + uint32_t const& SHLightComponent::GetCullingMask(void) const noexcept + { + return lightData.cullingMask; + } + + + //bool SHLightComponent::IsDirty(void) const noexcept + //{ + // return dirty; + //} + + //bool SHLightComponent::GetBound(void) const noexcept + //{ + // return bound; + //} + + //uint32_t SHLightComponent::GetIndexInBuffer(void) const noexcept + //{ + // return indexInBuffer; + //} + + float SHLightComponent::GetStrength(void) const noexcept + { + return lightData.strength; + } + +} + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::enumeration("Light Type") + ( + value("Directional", SH_LIGHT_TYPE::DIRECTIONAL), + value("Point", SH_LIGHT_TYPE::POINT), + value("Spot", SH_LIGHT_TYPE::SPOT), + value("Ambient", SH_LIGHT_TYPE::AMBIENT) + ); + + registration::class_("Light Component") + .property("Position", &SHLightComponent::GetPosition, &SHLightComponent::SetPosition) + .property("Type", &SHLightComponent::GetType, &SHLightComponent::SetType) + .property("Direction", &SHLightComponent::GetDirection, &SHLightComponent::SetDirection) + .property("Color", &SHLightComponent::GetColor, &SHLightComponent::SetColor) + .property("Layer", &SHLightComponent::GetCullingMask, &SHLightComponent::SetCullingMask) + .property("Strength", &SHLightComponent::GetStrength, &SHLightComponent::SetStrength) + ; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.h b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.h new file mode 100644 index 00000000..6b35559c --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightComponent.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include "ECS_Base/Components/SHComponent.h" +#include "SHLightData.h" + +namespace SHADE +{ + + class SH_API SHLightComponent final : public SHComponent + { + private: + //! General data for the light. This will purely be CPU bound. Whatever gets sent to the + //! GPU depends on the type of the light. + SHLightData lightData; + + //! Since the lighting system is gonna be self contained and light weight, we store this + //! so that we only write this to the CPU buffer when this light component change, we don't + //! rewrite everything. However we still write to the GPU buffer when everything changes. + //uint32_t indexInBuffer; + + ////! If the light component changed some value we mark this true. + //bool dirty; + + ////! If the light's data is already in the buffers, this will be set to true. + //bool bound; + + + public: + /*-----------------------------------------------------------------------*/ + /* LIFECYCLE FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void OnCreate (void) override final; + void OnDestroy (void) override final; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + void SetPosition (SHVec3 const& position) noexcept; // serialized + void SetType (SH_LIGHT_TYPE type) noexcept; // serialized + void SetDirection (SHVec3 const& direction) noexcept; // serialized + void SetColor (SHVec4 const& color) noexcept; // serialized + void ModifyCullingMask (uint8_t layerIndex, bool value) noexcept; // serialized + void SetCullingMask (uint32_t const& value) noexcept; + void SetAllLayers (void) noexcept; // serialized + void ClearAllLayers (void) noexcept; // serialized + //void MakeDirty (void) noexcept; + //void ClearDirtyFlag (void) noexcept; + //void Unbind (void) noexcept; + //void SetBound (uint32_t inIndexInBuffer) noexcept; + void SetStrength (float value) noexcept; // serialized + + + SHLightData const& GetLightData (void) const noexcept; + SHVec3 const& GetPosition (void) const noexcept; // serialized + SH_LIGHT_TYPE GetType (void) const noexcept; // serialized + SHVec3 const& GetDirection (void) const noexcept; // serialized + SHVec4 const& GetColor (void) const noexcept; // serialized + uint32_t const& GetCullingMask (void) const noexcept; // serialized + //bool IsDirty (void) const noexcept; + //bool GetBound (void) const noexcept; + uint32_t GetIndexInBuffer (void) const noexcept; + float GetStrength (void) const noexcept; + RTTR_ENABLE() + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.cpp new file mode 100644 index 00000000..8e8f0783 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.cpp @@ -0,0 +1,21 @@ +#include "SHpch.h" +#include "SHLightData.h" + +namespace SHADE +{ + void SHLightData::Reset(void) noexcept + { + // no culling is done. + cullingMask = std::numeric_limits::max(); + + // reset position to 0 + position = SHVec3::Zero; + + // direction just point in positive z axis + direction = SHVec3::Forward; + + // Diffuse color set to 1 + color = SHVec4::One; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.h b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.h new file mode 100644 index 00000000..e9a02c1a --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightData.h @@ -0,0 +1,60 @@ +#pragma once + +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" + +namespace SHADE +{ + enum class SH_LIGHT_TYPE : uint32_t + { + DIRECTIONAL = 0, + POINT, + SPOT, + AMBIENT, + NUM_TYPES + }; + + /***************************************************************************/ + /*! + + \class + Every light will essentially be using this struct. However, when passing + light data over to the GPU, the light data will be split according to + type for more optimal cache access. + + */ + /***************************************************************************/ + struct SHLightData + { + //! position of the light + SHVec3 position; + + //! Type of the light + SH_LIGHT_TYPE type; + + //! direction of the light + SHVec3 direction; + + //! Each bit in this 32 bit field will represent a layer. If the bit is set, + //! when a fragment is being evaluated, the shader will use the fragment's + //! layer value to AND with the light's. If result is 1, do lighting calculations. + uint32_t cullingMask; + + //! Diffuse color emitted by the light + SHVec4 color; + + //! Strength of the light + float strength; + + + void Reset (void) noexcept; + //! TODO: + //! - Add cut off. (inner and outer). + //! - Add constant, linear and quadratic for attenuation + //! - Specular color if needed. see below. + + //! Specular color + //SHVec4 specularColor; + }; + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp new file mode 100644 index 00000000..51eaf5f1 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp @@ -0,0 +1,528 @@ +#include "SHpch.h" +#include "SHLightingSubSystem.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Tools/SHUtilities.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "SHLightComponent.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "SHLightComponent.h" +#include "Math/Vector/SHVec4.h" +#include "Math/SHMatrix.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + This function takes an address in the CPU container and writes light + component data to it. What gets written depends on the light type. + + \param address + The address to write to. + + \param lightComp + The light component with the data to write from. + + \param lightType + The type of the light + + \return + + */ + /***************************************************************************/ + void SHLightingSubSystem::PerTypeData::WriteLightToAddress(void* address, SHMatrix const& viewMat, SHLightComponent* lightComp) noexcept + { + auto const& lightData = lightComp->GetLightData(); + switch (lightData.type) + { + case SH_LIGHT_TYPE::DIRECTIONAL: + { + SHDirectionalLightData* lightPtr = reinterpret_cast(address); + + // #NoteToSelf: NEED TO TRANSPOSE HERE BECAUSE MULTIPLICATION IS ROW MAJOR. + SHVec4 transformedDir = SHMatrix::Transpose(viewMat) * SHVec4(lightData.direction[0], lightData.direction[1], lightData.direction[2], 0.0f); + + lightPtr->cullingMask = lightData.cullingMask; + lightPtr->direction = SHVec3 (transformedDir.x, transformedDir.y, transformedDir.z); + //lightPtr->direction = lightData.direction; + lightPtr->diffuseColor = lightData.color; + lightPtr->active = lightComp->isActive; + break; + } + case SH_LIGHT_TYPE::POINT: + break; + case SH_LIGHT_TYPE::SPOT: + break; + case SH_LIGHT_TYPE::AMBIENT: + { + SHAmbientLightData* lightPtr = reinterpret_cast(address); + lightPtr->ambientColor = lightData.color; + lightPtr->strength = lightData.strength; + lightPtr->cullingMask = lightData.cullingMask; + lightPtr->active = lightComp->isActive; + break; + } + case SH_LIGHT_TYPE::NUM_TYPES: + break; + default: + break; + + } + } + + + /***************************************************************************/ + /*! + + \brief + Initializes type, intermediate data and buffer. dirty will be true. + + \param lightType + type of the light. + + */ + /***************************************************************************/ + void SHLightingSubSystem::PerTypeData::InitializeData(Handle logicalDevice, SH_LIGHT_TYPE type) noexcept + { + // initialize the type + lightType = type; + + // boilerplate + intermediateData = nullptr; + + // Get data required for struct + lightDataSize = GetLightTypeSize(type); + lightDataAlignedSize = logicalDevice->PadSSBOSize(lightDataSize); + + // So create some data! + Expand(logicalDevice); + } + + /***************************************************************************/ + /*! + + \brief + Expands both the CPU container and the GPU buffer when the number of + lights have exceeded the capacity. + + */ + /***************************************************************************/ + void SHLightingSubSystem::PerTypeData::Expand(Handle logicalDevice) noexcept + { + if (lightDataSize == 0) + { + SHLOG_ERROR ("One of the types of lights have not been accounted for. Make sure lightDataAlignmentSize is not nullptr."); + return; + } + + // we want to wait for the command buffers to finish using the buffers first + logicalDevice->WaitIdle(); + + // First time we are initializing lights + if (intermediateData == nullptr) + { + // max lights should start of at STARTING_NUM_LIGHTS lights + maxLights = STARTING_NUM_LIGHTS; + numLights = 0; + + // Initialize the data for lights + intermediateData = std::make_unique(lightDataSize * maxLights); + + lightDataTotalAlignedSize = logicalDevice->PadSSBOSize(lightDataAlignedSize * maxLights); + + // We want to initialize 3 times the amount of data required. + dataBuffer = logicalDevice->CreateBuffer(lightDataTotalAlignedSize * SHGraphicsConstants::NUM_FRAME_BUFFERS, nullptr, lightDataTotalAlignedSize * SHGraphicsConstants::NUM_FRAME_BUFFERS, vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, "Light Data"); + } + else + { + // save old number of lights + uint32_t const OLD_MAX_LIGHTS = maxLights; + + // before we increase the number of lights, create space to store old data. + std::unique_ptr oldData = std::make_unique(lightDataSize * OLD_MAX_LIGHTS); + + // copy data over. + std::memcpy (oldData.get(), intermediateData.get(), lightDataSize * OLD_MAX_LIGHTS); + + // now we start to expand.... + + // double space for lights + maxLights *= 2; + + // calculate total + padding + lightDataTotalAlignedSize = logicalDevice->PadSSBOSize(lightDataAlignedSize * maxLights); + + // destroy old data and initialize container for double the amount of data. + intermediateData = std::make_unique(lightDataSize * maxLights); + + // copy old data to new container + std::memcpy(intermediateData.get(), oldData.get(), lightDataSize * OLD_MAX_LIGHTS); + + // Resize the GPU buffer. TODO: Replace with Resize no copy here + dataBuffer->ResizeReplace(maxLights * lightDataTotalAlignedSize * SHGraphicsConstants::NUM_FRAME_BUFFERS, oldData.get(), lightDataTotalAlignedSize * OLD_MAX_LIGHTS); + } + + + } + + /***************************************************************************/ + /*! + + \brief + Gets the size required to store data for a light type. + + \param type + Type of a light. + + \return + Size required to store a light based on type. + + */ + /***************************************************************************/ + uint32_t SHLightingSubSystem::PerTypeData::GetLightTypeSize(SH_LIGHT_TYPE type) noexcept + { + switch (type) + { + case SH_LIGHT_TYPE::DIRECTIONAL: + // TOOD: Change after creating point light struct + return sizeof(SHDirectionalLightData); + case SH_LIGHT_TYPE::POINT: + return 4; + case SH_LIGHT_TYPE::SPOT: + // TOOD: Change after creating spot light struct + return 4; + case SH_LIGHT_TYPE::AMBIENT: + return sizeof(SHAmbientLightData); + return 4; + case SH_LIGHT_TYPE::NUM_TYPES: + default: + return 4; + + } + } + + + Handle SHLightingSubSystem::PerTypeData::GetDataBuffer(void) const noexcept + { + return dataBuffer; + } + + + uint32_t SHLightingSubSystem::PerTypeData::GetAlignmentSize(void) const noexcept + { + return lightDataTotalAlignedSize; + } + + uint32_t SHLightingSubSystem::PerTypeData::GetDataSize(void) const noexcept + { + return lightDataSize; + } + + uint32_t SHLightingSubSystem::PerTypeData::GetNumLights(void) const noexcept + { + return numLights; + } + + uint32_t SHLightingSubSystem::PerTypeData::GetMaxLights(void) const noexcept + { + return maxLights; + } + + + /***************************************************************************/ + /*! + + \brief + This function takes in a light comp in the event that its data has not + been placed in the buffer yet. It also checks if the size of the buffer + is big enough to hold the new light. If the buffer is too small, expand + it. + + \param lightComp + The light component to add. + + */ + /***************************************************************************/ + void SHLightingSubSystem::PerTypeData::AddLight(Handle logicalDevice, SHLightComponent* unboundLight, SHMatrix const& viewMat, bool& expanded) noexcept + { + if (unboundLight) + { + // capacity is full + if (numLights == maxLights) + { + // expand first + Expand(logicalDevice); + + expanded = true; + } + + // Now that the container is big enough, bind the new light + + // Get address of write location + void* writeLocation = reinterpret_cast(intermediateData.get()) + (lightDataAlignedSize * numLights); + + // Write the light data to address + WriteLightToAddress(writeLocation, viewMat, unboundLight); + + // Set the light component to be bound to that location + //unboundLight->SetBound(numLights); + + // Increase light count + ++numLights; + } + } + + /***************************************************************************/ + /*! + + \brief + Modify the data at a specific light address. + + \param lightComp + The light component to write. + + */ + /***************************************************************************/ + //void SHLightingSubSystem::PerTypeData::ModifyLight(SHLightComponent* lightComp) noexcept + //{ + // void* writeLocation = reinterpret_cast(intermediateData.get()) + (lightDataAlignedSize * lightComp->GetIndexInBuffer()); + // WriteLightToAddress(writeLocation, lightComp); + //} + + void SHLightingSubSystem::PerTypeData::WriteToGPU(uint32_t frameIndex) noexcept + { + if (intermediateData) + { + // we want to write to the offset of the current frame + dataBuffer->WriteToMemory(intermediateData.get(), lightDataAlignedSize * numLights, 0, lightDataAlignedSize * maxLights * frameIndex); + } + } + + void SHLightingSubSystem::PerTypeData::ResetNumLights(void) noexcept + { + numLights = 0; + } + + /***************************************************************************/ + /*! + + \brief + Update descriptor sets. We want to call this every time we expand buffers. + + \param binding + The binding in the set we want to update. + + */ + /***************************************************************************/ + void SHLightingSubSystem::UpdateDescSet(uint32_t binding) noexcept + { + auto buffer = perTypeData[binding].GetDataBuffer(); + + // We bind the buffer with the correct desc set binding + lightingDataDescSet->ModifyWriteDescBuffer(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, + binding + 1, // we want to +1 here because the first binding is reserved for count + { &buffer, 1 }, + 0, + perTypeData[binding].GetDataSize() * perTypeData[binding].GetMaxLights()); + + lightingDataDescSet->UpdateDescriptorSetBuffer(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, binding + 1); // +1 here, same reason. see above + } + + /***************************************************************************/ + /*! + + \brief + Computes dynamic offsets. + + */ + /***************************************************************************/ + void SHLightingSubSystem::ComputeDynamicOffsets(void) noexcept + { + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + dynamicOffsets[i][0] = i * lightCountsAlignedSize; + // Even if the first binding is a count, we want to account for that too + for (uint32_t j = 0; j < static_cast(SH_LIGHT_TYPE::NUM_TYPES); ++j) + { + auto const& typeData = perTypeData[j]; + { + // +1 because 1st reserved for count + dynamicOffsets[i][j + 1] = i * typeData.GetAlignmentSize(); + } + } + } + } + + void SHLightingSubSystem::ResetNumLights(void) noexcept + { + for (auto& data : perTypeData) + { + data.ResetNumLights(); + } + } + + /***************************************************************************/ + /*! + + \brief + Initializes per light type data. This includes buffers and descriptor + sets. + + + */ + /***************************************************************************/ + void SHLightingSubSystem::Init(Handle device, Handle descPool) noexcept + { + SHComponentManager::CreateComponentSparseSet(); + + logicalDevice = device; + uint32_t constexpr NUM_LIGHT_TYPES = SHUtilities::ConvertEnum(SH_LIGHT_TYPE::NUM_TYPES); + + std::vector variableSizes{ NUM_LIGHT_TYPES }; + std::fill (variableSizes.begin(), variableSizes.end(), 1); + + // Create the descriptor set + lightingDataDescSet = descPool->Allocate({ SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS] }, variableSizes); +#ifdef _DEBUG + const auto& CAM_DESC_SETS = lightingDataDescSet->GetVkHandle(); + for (int i = 0; i < static_cast(CAM_DESC_SETS.size()); ++i) + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSet, CAM_DESC_SETS[i], "[Descriptor Set] Light Data Frame #" + std::to_string(i)); +#endif + + for (uint32_t i = 0; i < NUM_LIGHT_TYPES; ++i) + { + // initialize all the data first. We add more lights here as we add more types. + perTypeData[i].InitializeData(logicalDevice, static_cast(i)); + UpdateDescSet(i); + + // no lights at first + lightCountsData[i] = 0; + } + + lightCountsAlignedSize = sizeof (uint32_t) * NUM_LIGHT_TYPES; + lightCountsAlignedSize = logicalDevice->PadUBOSize(lightCountsAlignedSize); + + // Create the GPU buffer to hold light count + lightCountsBuffer = logicalDevice->CreateBuffer(lightCountsAlignedSize * SHGraphicsConstants::NUM_FRAME_BUFFERS, nullptr, lightCountsAlignedSize * SHGraphicsConstants::NUM_FRAME_BUFFERS, vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_AUTO, VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, "Light Count Data"); + + lightingDataDescSet->ModifyWriteDescBuffer(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::LIGHTING_COUNT, {&lightCountsBuffer, 1}, 0, sizeof (uint32_t) * NUM_LIGHT_TYPES); + lightingDataDescSet->UpdateDescriptorSetBuffer(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::LIGHTING_COUNT); + + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + dynamicOffsets[i].resize(NUM_LIGHT_TYPES + 1); // +1 for the count + } + + //numLightComponents = 0; + } + + /***************************************************************************/ + /*! + + \brief + Loops through every single light component and checks for dirty light + data. If light data is dirty, rewrite to the CPU container. We also want + to bind the descriptor set for the light data. + + */ + /***************************************************************************/ + void SHLightingSubSystem::Run(SHMatrix const& viewMat, uint32_t frameIndex) noexcept + { + static uint32_t constexpr NUM_LIGHT_TYPES = SHUtilities::ConvertEnum(SH_LIGHT_TYPE::NUM_TYPES); + + auto& lightComps = SHComponentManager::GetDense(); + bool expanded = false; + + // First we reset the number of lights. We do this every frame so that we can count how many lights we have + ResetNumLights(); + + //bool rewrite = false; + + //if (numLightComponents > lightComps.size()) + //{ + // rewrite = true; + // ResetNumLights(); + //} + + //numLightComponents = lightComps.size(); + + for (auto& light : lightComps) + { + auto enumValue = SHUtilities::ConvertEnum(light.GetLightData().type); + + // First we want to make sure the light is already bound to the system. if it + // isn't, we write it to the correct buffer. + //if (!light.GetBound() || rewrite) + { + perTypeData[enumValue].AddLight(logicalDevice, &light, viewMat, expanded); + + // add to light count + //++lightCountsData[enumValue]; + } + + // if there was modification to the light data + //if (light.IsDirty()) + { + // Write the data to the CPU + //perTypeData[enumValue].ModifyLight(&light); + + // Light is now updated in the container + //light.ClearDirtyFlag(); + } + } + + // Write data to GPU + for (auto& data : perTypeData) + { + data.WriteToGPU(frameIndex); + } + + for (uint32_t i = 0; i < NUM_LIGHT_TYPES; ++i) + { + lightCountsData[i] = perTypeData[i].GetNumLights(); + } + + lightCountsBuffer->WriteToMemory(lightCountsData.data(), static_cast(lightCountsData.size()) * sizeof (uint32_t), 0, lightCountsAlignedSize * frameIndex); + + // If any of the buffers got expanded, the descriptor set is invalid because the expanded buffer + // is a new buffer. If some expansion was detected, update descriptor sets. + if (expanded) + { + uint32_t constexpr NUM_LIGHT_TYPES = SHUtilities::ConvertEnum(SH_LIGHT_TYPE::NUM_TYPES); + for (uint32_t i = 0; i < NUM_LIGHT_TYPES; ++i) + { + UpdateDescSet(i); + } + } + + // compute dynamic offsets. We don't actually have to compute every frame but its pretty lightweight, + // so we do it anyway. #NoteToSelf: if at any point it affects performance, do a check before computing. + ComputeDynamicOffsets(); + + + } + + /***************************************************************************/ + /*! + + \brief + Does nothing for now. + + */ + /***************************************************************************/ + void SHLightingSubSystem::Exit(void) noexcept + { + + } + + void SHLightingSubSystem::BindDescSet(Handle cmdBuffer, uint32_t frameIndex) noexcept + { + //Bind descriptor set(We bind at an offset because the buffer holds NUM_FRAME_BUFFERS sets of data). + cmdBuffer->BindDescriptorSet(lightingDataDescSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, { dynamicOffsets[frameIndex] }); + + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h new file mode 100644 index 00000000..ae6caead --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h @@ -0,0 +1,168 @@ +#pragma once + +#include "Resource/SHHandle.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" +#include "SHLightData.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkDescriptorPool; + class SHVkDescriptorSetGroup; + class SHVkDescriptorSetLayout; + class SHVkBuffer; + class SHLightComponent; + class SHVkCommandBuffer; + + // Represents how the data will be interpreted in GPU. we want to copy to a container of these before passing to GPU. + struct SHDirectionalLightData + { + //! Direction of the light + SHVec3 direction; + + //! Represents if the light is active or not + uint32_t active; + + //! Each bit in this 32 bit field will represent a layer. If the bit is set, + //! when a fragment is being evaluated, the shader will use the fragment's + //! layer value to AND with the light's. If result is 1, do lighting calculations. + uint32_t cullingMask; + + //! Diffuse color emitted by the light + alignas (16) SHVec4 diffuseColor; + + }; + + // Represents how the data will be interpreted in GPU. we want to copy to a container of these before passing to GPU. + struct SHAmbientLightData + { + //! Diffuse color emitted by the light + SHVec4 ambientColor; + + //! Strength of the ambient light + float strength; + + //! Represents if the light is active or not + uint32_t active; + + //! Each bit in this 32 bit field will represent a layer. If the bit is set, + //! when a fragment is being evaluated, the shader will use the fragment's + //! layer value to AND with the light's. If result is 1, do lighting calculations. + uint32_t cullingMask; + + + }; + + class SH_API SHLightingSubSystem + { + private: + + class PerTypeData + { + private: + /*-----------------------------------------------------------------------*/ + /* STATIC MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + static constexpr uint32_t STARTING_NUM_LIGHTS = 50; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Capacity of the container. + uint32_t maxLights; + + //! SSBOs need to be aligned. This is to pad descriptor offset + uint32_t lightDataTotalAlignedSize; + + //! size needed to store 1 struct object + uint32_t lightDataSize; + + uint32_t lightDataAlignedSize; + + //! type of the light. Will be used later when we want to expand + SH_LIGHT_TYPE lightType; + + //! number of lights currently alive. + uint32_t numLights; + + //! GPU buffer required to store GPU data + Handle dataBuffer; + + //! Before data gets copied to the GPU, it goes into here first. Data here is aligned to whatever struct is + //! used to represent data in this container. Note this will store only 1 copy of all the lights, compared + //! to the GPU that stores NUM_FRAME_BUFFERS copies. + std::unique_ptr intermediateData; + + void WriteLightToAddress (void* address, SHMatrix const& viewMat, SHLightComponent* lightComp) noexcept; + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void InitializeData (Handle logicalDevice, SH_LIGHT_TYPE type) noexcept; + void Expand (Handle logicalDevice) noexcept; + void AddLight (Handle logicalDevice, SHLightComponent* unboundLight, SHMatrix const& viewMat, bool& expanded) noexcept; + //void ModifyLight (SHLightComponent* lightComp) noexcept; + void WriteToGPU (uint32_t frameIndex) noexcept; + void ResetNumLights (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS */ + /*-----------------------------------------------------------------------*/ + static uint32_t GetLightTypeSize (SH_LIGHT_TYPE type) noexcept; + Handle GetDataBuffer (void) const noexcept; + uint32_t GetAlignmentSize (void) const noexcept; + uint32_t GetDataSize (void) const noexcept; + uint32_t GetNumLights (void) const noexcept; + uint32_t GetMaxLights (void) const noexcept; + }; + + private: + + //! logical device used for creation + Handle logicalDevice; + + //! The descriptor set that will hold the lighting data. Each binding will hold a buffer, NUM_FRAMES times the size required. + Handle lightingDataDescSet; + + //! Each type will have some data associated with it for processing + std::array(SH_LIGHT_TYPE::NUM_TYPES)> perTypeData; + + //! Container to store dynamic offsets for binding descriptor sets + std::array, static_cast(SHGraphicsConstants::NUM_FRAME_BUFFERS)> dynamicOffsets; + + //! holds the data that represents how many lights are in the scene + std::array(SH_LIGHT_TYPE::NUM_TYPES)> lightCountsData; + + //! GPU buffer to hold lightCountData + Handle lightCountsBuffer; + + //! For padding in the buffer + uint32_t lightCountsAlignedSize; + + //! Number of SHLightComponents recorded. If at the beginning of the run function the size returned by the dense + //! set is less than the size recorded, rewrite all light components into the its respective buffers. If its more, + //! don't do anything. + //uint32_t numLightComponents; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void UpdateDescSet (uint32_t binding) noexcept; + void ComputeDynamicOffsets (void) noexcept; + void ResetNumLights (void) noexcept; + + public: + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Init (Handle device, Handle descPool) noexcept; + void Run (SHMatrix const& viewMat, uint32_t frameIndex) noexcept; + void Exit (void) noexcept; + + void BindDescSet (Handle cmdBuffer, uint32_t frameIndex) noexcept; + + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.cpp new file mode 100644 index 00000000..624956da --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.cpp @@ -0,0 +1,47 @@ +/************************************************************************************//*! +\file SHMaterialInstanceCache.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 25, 2022 +\brief Contains the definition of SHMaterialInstanceCache's functions. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHMaterialInstanceCache.h" + +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "Resource/SHResourceLibrary.h" + + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + SHADE::Handle SHMaterialInstanceCache::CreateOrGet(SHResourceHub& manager, Handle material) + { + // Check if there is already an existing instance + auto matInst = cache.find(material); + if (matInst == cache.end()) + { + // Create and return + return cache.emplace(material, manager.Create(material)).first->second; + } + + return matInst->second; + } + + void SHMaterialInstanceCache::Remove(Handle material) + { + cache.erase(material); + } + + void SHMaterialInstanceCache::Clear() + { + cache.clear(); + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.h b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.h new file mode 100644 index 00000000..6612faf6 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialInstanceCache.h @@ -0,0 +1,93 @@ +/************************************************************************************//*! +\file SHMaterialInstanceCache.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 25, 2022 +\brief Contains the definition of SHMaterialInstanceCache. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHMaterial; + class SHMaterialInstance; + class SHResourceHub; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Creates and caches base SHMaterialInstances. Note that base SHMaterialInstances + refer to SHMaterialInstances with no overrides. + */ + /*************************************************************************************/ + class SHMaterialInstanceCache + { + public: + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + + \brief + + + \param material + Material to get the SHMaterialInstance for. + + \return + Handle to the base SHMaterialInstance that is mapped to SHMaterial. + + */ + /***********************************************************************************/ + Handle CreateOrGet(SHResourceHub& manager, Handle material); + /***********************************************************************************/ + /*! + + \brief + Removes a SHMaterialInstance from the cache with a matching material. + + \param material + Handle to a SHMaterial that is used to check for removal. + + */ + /***********************************************************************************/ + void Remove(Handle material); + /***********************************************************************************/ + /*! + + \brief + Removes all SHMaterialInstances in the cache. + + */ + /***********************************************************************************/ + void Clear(); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + using MaterialMap = std::unordered_map, Handle>; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + MaterialMap cache; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.cpp new file mode 100644 index 00000000..0dabc4e8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.cpp @@ -0,0 +1,74 @@ +/************************************************************************************//*! +\file SHMaterialSpec.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 2, 2022 +\brief Contains the function definitions of SHMaterialSpec. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHMaterialSpec.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Resource/SHResourceManager.h" +#include "Graphics/MiddleEnd/Interface/SHMaterial.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------------*/ + SHMaterialSpec::SHMaterialSpec(const SHMaterial& material) + { + // Get Shader Handles + const auto& SHADERS = material.GetPipeline()->GetPipelineLayout()->GetShaderModules(); + Handle vShaderMod, fShaderMod; + for (const auto& shader : SHADERS) + { + const auto FLAG_BITS = shader->GetShaderStageFlagBits(); + if (FLAG_BITS & vk::ShaderStageFlagBits::eVertex) + vShaderMod = shader; + else if (FLAG_BITS & vk::ShaderStageFlagBits::eFragment) + fShaderMod = shader; + } + vertexShader = SHResourceManager::GetAssetID(vShaderMod).value_or(0); + fragShader = SHResourceManager::GetAssetID(fShaderMod).value_or(0); + subpassName = material.GetPipeline()->GetPipelineState().GetSubpass()->GetName(); + + // Write Properties + Handle pipelineProperties = material.GetShaderBlockInterface(); + for (int i = 0; i < static_cast(pipelineProperties->GetVariableCount()); ++i) + { + const SHShaderBlockInterface::Variable* VARIABLE = pipelineProperties->GetVariable(i); + if (!VARIABLE) + break; + const std::string& VAR_NAME = pipelineProperties->GetVariableName(i); + YAML::Node propNode; + switch (VARIABLE->type) + { + case SHADE::SHShaderBlockInterface::Variable::Type::FLOAT: + propNode = material.GetProperty(VARIABLE->offset); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::INT: + propNode = material.GetProperty(VARIABLE->offset); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR2: + propNode = material.GetProperty(VARIABLE->offset); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR3: + propNode = material.GetProperty(VARIABLE->offset); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR4: + propNode = material.GetProperty(VARIABLE->offset); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::OTHER: + default: + continue; + break; + } + properties[VAR_NAME.data()] = propNode; + } + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.h b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.h new file mode 100644 index 00000000..9c2177c3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Materials/SHMaterialSpec.h @@ -0,0 +1,51 @@ +/************************************************************************************//*! +\file SHMaterialSpec.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the struct definition of SHMaterialSpec. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Standard Library +#include +#include +#include +// Project Includes +#include "Assets/SHAssetMacros.h" +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declaration */ + /*-----------------------------------------------------------------------------------*/ + class SHMaterial; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Describes a Material's serialized properties. A representation of a material that is + independent of GPU resources. + */ + /*************************************************************************************/ + struct SHMaterialSpec + { + AssetID vertexShader; + AssetID fragShader; + std::string subpassName; + YAML::Node properties; + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + SHMaterialSpec() = default; + explicit SHMaterialSpec(const SHMaterial& material); + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp new file mode 100644 index 00000000..60decded --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp @@ -0,0 +1,314 @@ +/************************************************************************************//*! +\file SHPrimitiveGenerator.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 30, 2022 +\brief Contains definitions for all of the functions of the classes that deal + with storage and management of vertex and index buffers of meshes. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHPrimitiveGenerator.h" + +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHMeshData SHPrimitiveGenerator::cubeMesh; + SHMeshData SHPrimitiveGenerator::sphereMesh; + + /*-----------------------------------------------------------------------------------*/ + /* Primitive Generation Functions */ + /*-----------------------------------------------------------------------------------*/ + SHMeshData SHPrimitiveGenerator::Cube() noexcept + { + SHMeshData mesh; + + mesh.VertexPositions = + { + // front + SHVec3(-0.5f, -0.5f, 0.5f), + SHVec3(0.5f, -0.5f, 0.5f), + SHVec3(0.5f, 0.5f, 0.5f), + SHVec3(-0.5f, 0.5f, 0.5f), + + // back + SHVec3(-0.5f, -0.5f, -0.5f), + SHVec3(-0.5f, 0.5f, -0.5f), + SHVec3(0.5f, 0.5f, -0.5f), + SHVec3(0.5f, -0.5f, -0.5f), + + // top + SHVec3(-0.5f, 0.5f, -0.5f), + SHVec3(-0.5f, 0.5f, 0.5f), + SHVec3(0.5f, 0.5f, 0.5f), + SHVec3(0.5f, 0.5f, -0.5f), + + // bottom + SHVec3(-0.5f, -0.5f, -0.5f), + SHVec3(0.5f, -0.5f, -0.5f), + SHVec3(0.5f, -0.5f, 0.5f), + SHVec3(-0.5f, -0.5f, 0.5f), + + // right + SHVec3(0.5f, -0.5f, -0.5f), + SHVec3(0.5f, 0.5f, -0.5f), + SHVec3(0.5f, 0.5f, 0.5f), + SHVec3(0.5f, -0.5f, 0.5f), + + // left + SHVec3(-0.5f, -0.5f, -0.5f), + SHVec3(-0.5f, -0.5f, 0.5f), + SHVec3(-0.5f, 0.5f, 0.5f), + SHVec3(-0.5f, 0.5f, -0.5f) + }; + + mesh.VertexTexCoords = + { + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f), + + + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f), + + + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f), + + + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f), + + + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f), + + + SHVec2(0.0f, 1.0f), + SHVec2(1.0f, 1.0f), + SHVec2(1.0f, 0.0f), + SHVec2(0.0f, 0.0f) + }; + + mesh.VertexTangents = + { + // front + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + + // back + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f), + + // top + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + + // bottom + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + + // right + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + + // left + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f) + + }; + + mesh.VertexNormals = + { + // front + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f), + SHVec3(0.0f, 0.0f, 1.0f), + + //back + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + SHVec3(0.0f, 0.0f, -1.0f), + + // top + SHVec3(0.0f, 1.0f, 0.0f), + SHVec3(0.0f, 1.0f, 0.0f), + SHVec3(0.0f, 1.0f, 0.0f), + SHVec3(0.0f, 1.0f, 0.0f), + + // bottom + SHVec3(0.0f, -1.0f, 0.0f), + SHVec3(0.0f, -1.0f, 0.0f), + SHVec3(0.0f, -1.0f, 0.0f), + SHVec3(0.0f, -1.0f, 0.0f), + + // right + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + SHVec3(1.0f, 0.0f, 0.0f), + + // left + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f), + SHVec3(-1.0f, 0.0f, 0.0f) + }; + + mesh.Indices = + { + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + 12, 13, 14, 12, 14, 15, + 16, 17, 18, 16, 18, 19, + 20, 21, 22, 20, 22, 23 + }; + + return mesh; + } + + Handle SHPrimitiveGenerator::Cube(SHMeshLibrary& meshLibrary) noexcept + { + if (cubeMesh.VertexPositions.empty()) + cubeMesh = Cube(); + return addMeshDataTo(cubeMesh, meshLibrary); + } + + Handle SHPrimitiveGenerator::Cube(SHGraphicsSystem& gfxSystem) noexcept + { + if (cubeMesh.VertexPositions.empty()) + cubeMesh = Cube(); + return addMeshDataTo(cubeMesh, gfxSystem); + } + + SHADE::SHMeshData SHPrimitiveGenerator::Sphere() noexcept + { + SHMeshData meshData; + + const int LAT_SLICES = 12; + const int LONG_SLICES = 12; + float radius = 1; + for (int latNum = 0; latNum <= LAT_SLICES; ++latNum) + { + float theta = static_cast(latNum * std::numbers::pi / LAT_SLICES); + float sinTheta = sin(theta); + float cosTheta = cos(theta); + + for (int longNum = 0; longNum <= LONG_SLICES; ++longNum) + { + float phi = static_cast(longNum * 2 * std::numbers::pi / LONG_SLICES); + float sinPhi = sin(phi); + float cosPhi = cos(phi); + + const SHVec3 NORMAL = SHVec3(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta); + meshData.VertexNormals.emplace_back(NORMAL); + meshData.VertexTangents.emplace_back(/* TODO */); + meshData.VertexTexCoords.emplace_back + ( + 1.0f - (longNum / static_cast(LONG_SLICES)), + 1.0f - (latNum / static_cast(LAT_SLICES)) + ); + meshData.VertexPositions.emplace_back(radius * NORMAL.x, radius * NORMAL.y, radius * NORMAL.z); + } + } + + for (int latNumber{}; latNumber < LAT_SLICES; latNumber++) + { + for (int longNumber{}; longNumber < LONG_SLICES; longNumber++) + { + const auto FIRST = (latNumber * (LONG_SLICES + 1)) + longNumber; + const auto SECOND = (FIRST + LONG_SLICES + 1); + + meshData.Indices.emplace_back(FIRST); + meshData.Indices.emplace_back(SECOND); + meshData.Indices.emplace_back(FIRST + 1); + + meshData.Indices.emplace_back(SECOND); + meshData.Indices.emplace_back(SECOND + 1); + meshData.Indices.emplace_back(FIRST + 1); + } + } + + return meshData; + } + + SHADE::Handle SHPrimitiveGenerator::Sphere(SHMeshLibrary& meshLibrary) noexcept + { + if (sphereMesh.VertexPositions.empty()) + sphereMesh = Sphere(); + + return addMeshDataTo(sphereMesh, meshLibrary); + } + + SHADE::Handle SHPrimitiveGenerator::Sphere(SHGraphicsSystem& gfxSystem) noexcept + { + if (sphereMesh.VertexPositions.empty()) + sphereMesh = Sphere(); + + return addMeshDataTo(sphereMesh, gfxSystem); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshData& meshData, SHMeshLibrary& meshLibrary) noexcept + { + return meshLibrary.AddMesh + ( + static_cast(meshData.VertexPositions.size()), + meshData.VertexPositions.data(), + meshData.VertexTexCoords.data(), + meshData.VertexTangents.data(), + meshData.VertexNormals.data(), + static_cast(meshData.Indices.size()), + meshData.Indices.data() + ); + } + + SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshData& meshData, SHGraphicsSystem& gfxSystem) noexcept + { + return gfxSystem.AddMesh + ( + static_cast(meshData.VertexPositions.size()), + meshData.VertexPositions.data(), + meshData.VertexTexCoords.data(), + meshData.VertexTangents.data(), + meshData.VertexNormals.data(), + static_cast(meshData.Indices.size()), + meshData.Indices.data() + ); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h new file mode 100644 index 00000000..7719e4c3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h @@ -0,0 +1,133 @@ +/************************************************************************************//*! +\file SHPrimitiveGenerator.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 18, 2022 +\brief Contains the static class definition of SHPrimitiveGenerator. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Math/SHMath.h" +#include "Assets/Asset Types/SHModelAsset.h" +#include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHGraphicsSystem; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Static class that contains functions for generating 3D primitives. + */ + /*************************************************************************************/ + class SH_API SHPrimitiveGenerator + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + SHPrimitiveGenerator() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Primitive Generation Functions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Produces a cube and stores the data in a SHMeshData object. + + \return + SHMeshData object containing vertex data for the cube. + */ + /***********************************************************************************/ + [[nodiscard]] static SHMeshData Cube() noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a cube and constructs a SHMesh using the SHMeshLibrary provided. + + \param meshLibrary + Reference to the SHMeshLibrary to produce and store a cube mesh in. + + \return + SHMesh object that points to the generated cube mesh in the SHMeshLibrary. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle Cube(SHMeshLibrary& meshLibrary) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a cube and constructs a SHMesh using the SHGraphicsSystem provided. + + \param gfxSystem + Reference to the SHGraphicsSystem to produce and store a cube mesh in. + + \return + SHMesh object that points to the generated cube mesh in the SHGraphicsSystem. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle Cube(SHGraphicsSystem& gfxSystem) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a sphere and stores the data in a SHMeshData object. + + \return + SHMeshData object containing vertex data for the sphere. + */ + /***********************************************************************************/ + [[nodiscard]] static SHMeshData Sphere() noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a sphere and constructs a SHMesh using the SHMeshLibrary provided. + + \param meshLibrary + Reference to the SHMeshLibrary to produce and store a sphere mesh in. + + \return + SHMesh object that points to the generated sphere mesh in the SHMeshLibrary. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle Sphere(SHMeshLibrary& meshLibrary) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a sphere and constructs a SHMesh using the SHGraphicsSystem provided. + + \param gfxSystem + Reference to the SHGraphicsSystem to produce and store a sphere mesh in. + + \return + SHMesh object that points to the generated sphere mesh in the SHGraphicsSystem. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle Sphere(SHGraphicsSystem& gfxSystem) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static Handle addMeshDataTo(const SHMeshData& meshData, SHMeshLibrary& meshLibrary) noexcept; + static Handle addMeshDataTo(const SHMeshData& meshData, SHGraphicsSystem& gfxSystem) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + static SHMeshData cubeMesh; + static SHMeshData sphereMesh; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp index bbbdd015..96db1ad1 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp @@ -26,7 +26,7 @@ namespace SHADE */ /***************************************************************************/ - void SHPerFrameData::Recreate(Handle const& logicalDeviceHdl) noexcept + void SHPerFrameData::Recreate(Handle logicalDeviceHdl) noexcept { // Swapchain recreation means the images are just relinked to SHVkImages. Handles will remain the same. There is no need for this line. //swapchainImageHdl = params.swapchainHdl->GetSwapchainImage(frameIndex); @@ -44,14 +44,17 @@ namespace SHADE // Create image views for the swapchain swapchainImageViewHdl = swapchainImageHdl->CreateImageView(logicalDeviceHdl, swapchainImageHdl, viewDetails); + SET_VK_OBJ_NAME(logicalDeviceHdl, vk::ObjectType::eImageView, swapchainImageViewHdl->GetImageView(), "[Image View] Swap Chain"); // Create a fence fenceHdl = logicalDeviceHdl->CreateFence(); + SET_VK_OBJ_NAME(logicalDeviceHdl, vk::ObjectType::eFence, fenceHdl->GetVkFence(), "[Fence] Swap Chain"); // scope makes it easier to navigate semImgAvailableHdl = logicalDeviceHdl->SHVkLogicalDevice::CreateSemaphore(); + SET_VK_OBJ_NAME(logicalDeviceHdl, vk::ObjectType::eSemaphore, semImgAvailableHdl->GetVkSem(), "[Semaphore] Swap Chain Image Available"); semRenderFinishHdl = logicalDeviceHdl->SHVkLogicalDevice::CreateSemaphore(); - + SET_VK_OBJ_NAME(logicalDeviceHdl, vk::ObjectType::eSemaphore, semRenderFinishHdl->GetVkSem(), "[Semaphore] Swap Chain Render Finish"); } /***************************************************************************/ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h index 5ac38503..d19764c2 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h @@ -63,7 +63,7 @@ namespace SHADE /* PRIVATE MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ // These are made into functions (instead of ctor and dtor) because we want to call these functions again when we resize the window - void Recreate (Handle const& logicalDeviceHdl) noexcept; + void Recreate (Handle logicalDeviceHdl) noexcept; void Destroy (void); friend class SHRenderContext; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp index f31653a8..1d24d6f7 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp @@ -50,9 +50,14 @@ namespace SHADE for (uint32_t j = 0; j < params.numThreads; ++j) { - frameData[i].cmdPoolHdls.push_back(logicalDeviceHdl->CreateCommandPool(params.cmdPoolQueueFamilyType, params.cmdPoolResetMode, params.cmdBufferTransient)); + auto cmdPool = logicalDeviceHdl->CreateCommandPool(params.cmdPoolQueueFamilyType, params.cmdPoolResetMode, params.cmdBufferTransient); + SET_VK_OBJ_NAME + ( + logicalDeviceHdl, vk::ObjectType::eCommandPool, cmdPool->GetVkCommandPool(), + "[Command Pool] Render Context #" + std::to_string(i) + " Pool #" + std::to_string(j) + ); + frameData[i].cmdPoolHdls.push_back(cmdPool); } - } // Initialize all the info. @@ -134,6 +139,7 @@ namespace SHADE { SHLOG_ERROR("Frame index retrieved from vkAcquireNextImageKHR is not the same as currentFrame."); } + currentFrame = frameIndex; } @@ -168,6 +174,11 @@ namespace SHADE isResized = resized; } + void SHRenderContext::SetWindowIsDead(bool dead) noexcept + { + windowIsDead = dead; + } + /***************************************************************************/ /*! @@ -197,6 +208,11 @@ namespace SHADE return currentFrame; } + bool SHRenderContext::GetResized(void) noexcept + { + return isResized; + } + bool SHRenderContext::GetResizeAndReset(void) noexcept { bool b = isResized; @@ -204,4 +220,9 @@ namespace SHADE return b; } + bool SHRenderContext::GetWindowIsDead(void) const noexcept + { + return windowIsDead; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h index 7ed8cb82..bf186922 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h @@ -3,6 +3,7 @@ #include #include "SHPerFrameData.h" +#include "SH_API.h" namespace SHADE { @@ -16,7 +17,7 @@ namespace SHADE //! render context in SHADE engine has is that it requires users to call these explicitly in the middle end. While there //! is little reason the flow of the render context should deviate from its intended usage, we want to leave it up to //! users to explicitly call functions from here so we don't risk losing opportunities for different usage. - class SHRenderContext + class SH_API SHRenderContext { private: //! container of frame data. Note that the manager owns the data, but the frame data themselves do not own anything. @@ -35,6 +36,7 @@ namespace SHADE uint32_t currentFrame; bool isResized{ false }; + bool windowIsDead {false}; public: SHRenderContext(void) noexcept; @@ -50,12 +52,15 @@ namespace SHADE bool WaitForFence (void) noexcept; void ResetFence (void) noexcept; - void SetIsResized (bool resized) noexcept; + void SetIsResized (bool resized) noexcept; + void SetWindowIsDead (bool dead) noexcept; SHPerFrameData& GetCurrentFrameData(void) noexcept; SHPerFrameData& GetFrameData (uint32_t index) noexcept; uint32_t GetCurrentFrame (void) const noexcept; + bool GetResized (void) noexcept; bool GetResizeAndReset (void) noexcept; + bool GetWindowIsDead (void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp new file mode 100644 index 00000000..495a3d37 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp @@ -0,0 +1,80 @@ +#include "SHpch.h" +#include "SHPipelineLibrary.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/RenderGraph/SHSubpass.h" +#include "Graphics/SHVkUtil.h" + +namespace SHADE +{ + + Handle SHPipelineLibrary::CreateGraphicsPipelines(std::pair, Handle> const& vsFsPair, Handle renderpass, Handle subpass) noexcept + { + SHPipelineLayoutParams params + { + .shaderModules = {vsFsPair.first, vsFsPair.second}, + .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() + }; + + // Create the pipeline layout + auto pipelineLayout = logicalDevice->CreatePipelineLayout(params); + + // Create the pipeline and configure the default vertex input state + auto newPipeline = logicalDevice->CreateGraphicsPipeline(pipelineLayout, nullptr, renderpass, subpass); + newPipeline->GetPipelineState().SetVertexInputState(SHGraphicsGlobalData::GetDefaultViState()); + + SHColorBlendState colorBlendState{}; + colorBlendState.logic_op_enable = VK_FALSE; + colorBlendState.logic_op = vk::LogicOp::eCopy; + + auto const& subpassColorReferences = subpass->GetColorAttachmentReferences(); + colorBlendState.attachments.reserve(subpassColorReferences.size()); + + for (auto& att : subpassColorReferences) + { + colorBlendState.attachments.push_back(vk::PipelineColorBlendAttachmentState + { + .blendEnable = SHVkUtil::IsBlendCompatible (subpass->GetFormatFromAttachmentReference(att.attachment)) ? true : false, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOne, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + } + ); + } + + newPipeline->GetPipelineState().SetColorBlenState(colorBlendState); + + // Actually construct the pipeline + newPipeline->ConstructPipeline(); + + // Emplace the new pipeline + graphicsPipelines.emplace (vsFsPair, newPipeline); + + return newPipeline; + } + + void SHPipelineLibrary::Init(Handle device) noexcept + { + logicalDevice = device; + } + + Handle SHPipelineLibrary::GetGraphicsPipeline(std::pair, Handle> const& vsFsPair) noexcept + { + // return the pipeline requested for + if (graphicsPipelines.contains(vsFsPair)) + return graphicsPipelines.at(vsFsPair); + else + return {}; + } + + bool SHPipelineLibrary::CheckGraphicsPipelineExistence(std::pair, Handle> const& vsFsPair) noexcept + { + // Returns if a pipeline exists or not + return graphicsPipelines.contains(vsFsPair); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h new file mode 100644 index 00000000..aeb023c5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Graphics/Pipeline/SHVkPipeline.h" + +namespace SHADE +{ + class SHVRenderpass; + class SHVkDescriptorSetLayouts; + class SHVkPipeline; + class SHSubpass; + class SHGraphicsGlobalData; + + // Pipeline library is a PURELY MIDDLE END SYSTEM. It is responsible for only creating pipelines from shaders and caching + // them so that they don't need to be recreated again. + class SHPipelineLibrary + { + private: + // TOOD: Move this somewhere eventually. Can use for other things + + //! Logical Device required for creation of pipelines + Handle logicalDevice; + + //! a map of pipelines that are hashed using a pair of shader module handles + std::unordered_map, Handle>, Handle> graphicsPipelines; + + public: + void Init (Handle device) noexcept; + + // Draw pipeline functions. used only when creating pipelines for drawing using a vertex and fragment shader + Handle CreateGraphicsPipelines ( + std::pair, Handle> const& vsFsPair, + Handle renderpass, + Handle subpass + ) noexcept; + Handle GetGraphicsPipeline (std::pair, Handle> const& vsFsPair) noexcept; + bool CheckGraphicsPipelineExistence (std::pair, Handle> const& vsFsPair) noexcept; + + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.cpp new file mode 100644 index 00000000..2bf32fd8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.cpp @@ -0,0 +1,138 @@ +#include "SHpch.h" +#include "SHSSAO.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Images/SHVkSampler.h" +#include + +namespace SHADE +{ + void SHSSAO::Init(Handle logicalDevice, Handle cmdBuffer) noexcept + { + // Initialize a distribution to get values from 0 to 1 + std::uniform_real_distribution distrib{0.0f, 1.0f}; + + // generator for random number + std::default_random_engine generator; + + // generate samples + for (uint32_t i = 0; i < NUM_SAMPLES; ++i) + { + //SHVec3 temp + //{ + // distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + // distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + // distrib(generator), // 0.0f - 1.0f so that sample space is a hemisphere + //}; + + //temp = SHVec3::Normalise(temp); + //temp *= distrib(generator); + + //// This makes sure that most points are closer to fragment's position + //float scale = 1.0f / static_cast(NUM_SAMPLES); + //scale = std::lerp(0.1f, 1.0f, scale * scale); + //temp *= scale; + //samples[i] = SHVec4 (temp.x, temp.y, temp.z, 0.0f); + + + samples[i] = SHVec4 + { + distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + distrib(generator), // 0.0f - 1.0f so that sample space is a hemisphere + 0.0f + }; + + // This makes sure that most points are closer to fragment's position + float scale = 1.0f / static_cast(NUM_SAMPLES); + scale = std::lerp(0.1f, 1.0f, scale * scale); + samples[i] *= scale; + } + + // generate rotation vector + for (uint32_t i = 0; i < NUM_ROTATION_VECTORS; ++i) + { + rotationVectors[i] = SHVec4 + { + distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + distrib(generator) * 2.0f - 1.0f, // -1.0f - 1.0f + 0.0f, // we want to rotate about the z axis in tangent space + 0.0f + }; + } + + SHImageCreateParams imageDetails = + { + .imageType = vk::ImageType::e2D, + .width = NUM_ROTATION_VECTORS_W, + .height = NUM_ROTATION_VECTORS_H, + .depth = 1, + .levels = 1, + .arrayLayers = 1, + .imageFormat = vk::Format::eR32G32B32A32Sfloat, + .usageFlags = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, + .createFlags = {} + }; + + uint32_t mipOffset = 0; + rotationVectorsImage = logicalDevice->CreateImage(imageDetails, reinterpret_cast( rotationVectors.data()), static_cast(sizeof(rotationVectors)), {&mipOffset, 1}, VMA_MEMORY_USAGE_AUTO, {}); + + vk::ImageMemoryBarrier transferBarrier{}; + rotationVectorsImage->PrepareImageTransitionInfo(vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, transferBarrier); + + cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, {transferBarrier}); + + rotationVectorsImage->TransferToDeviceResource(cmdBuffer); + + vk::ImageMemoryBarrier shaderReadBarrier{}; + rotationVectorsImage->PrepareImageTransitionInfo(vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, shaderReadBarrier); + cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader, {}, {}, {}, { shaderReadBarrier }); + + + // Get aligned size for buffer + uint32_t alignedSize = logicalDevice->PadSSBOSize(sizeof (samples)); + + // Create buffer + ssaoDataBuffer = logicalDevice->CreateBuffer(alignedSize, samples.data(), alignedSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_AUTO, {}); + + ssaoDataBuffer->TransferToDeviceResource(cmdBuffer); + } + + void SHSSAO::PrepareRotationVectorsVkData(Handle logicalDevice) noexcept + { + SHImageViewDetails DETAILS = + { + .viewType = vk::ImageViewType::e2D, + .format = vk::Format::eR32G32B32A32Sfloat, + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + }; + rotationVectorsImageView = rotationVectorsImage->CreateImageView(logicalDevice, rotationVectorsImage, DETAILS); + + SHVkSamplerParams samplerParams + { + .minFilter = vk::Filter::eNearest, + .magFilter = vk::Filter::eNearest, + .addressMode = vk::SamplerAddressMode::eRepeat, + .mipmapMode = vk::SamplerMipmapMode::eNearest, + .maxLod = 1u + }; + + rotationVectorsSampler = logicalDevice->CreateSampler(samplerParams); + } + + SHVkDescriptorSetGroup::viewSamplerLayout SHSSAO::GetViewSamplerLayout(void) const noexcept + { + return std::make_tuple(rotationVectorsImageView, rotationVectorsSampler, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + Handle SHSSAO::GetBuffer(void) const noexcept + { + return ssaoDataBuffer; + } + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.h b/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.h new file mode 100644 index 00000000..c7c133c5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PostProcessing/SHSSAO.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Resource/SHHandle.h" +#include "Graphics/SHVulkanIncludes.h" +#include "Math/Vector/SHVec4.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" + +namespace SHADE +{ + class SHVkBuffer; + class SHVkLogicalDevice; + class SHVkCommandBuffer; + class SHVkImage; + class SHVkImageView; + class SHVkSampler; + + class SHSSAO + { + public: + static constexpr uint32_t DESC_SET_BUFFER_BINDING = 0; + static constexpr uint32_t DESC_SET_IMAGE_BINDING = 1; + + private: + static constexpr uint32_t NUM_SAMPLES = 64; + static constexpr uint32_t NUM_ROTATION_VECTORS_W = 4; + static constexpr uint32_t NUM_ROTATION_VECTORS_H = 4; + static constexpr uint32_t NUM_ROTATION_VECTORS = NUM_ROTATION_VECTORS_W * NUM_ROTATION_VECTORS_H; + + private: + //! distances from a pixel we want to sample + std::array samples; + + //! For passing SSAO samples and kernel to GPU + Handle ssaoDataBuffer; + + std::array rotationVectors; + + Handle rotationVectorsImage; + Handle rotationVectorsImageView; + Handle rotationVectorsSampler; + + public: + void Init (Handle logicalDevice, Handle cmdBuffer) noexcept; + void PrepareRotationVectorsVkData (Handle logicalDevice) noexcept; + SHVkDescriptorSetGroup::viewSamplerLayout GetViewSamplerLayout (void) const noexcept; + + Handle GetBuffer (void) const noexcept; + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp deleted file mode 100644 index 23d6323c..00000000 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "SHPch.h" -#include "SHShaderModuleLibrary.h" -#include "Graphics/Devices/SHVkLogicalDevice.h" - -namespace SHADE -{ - /***************************************************************************/ - /*! - - \brief - Imports all shader binaries from the source library. - - \param logicalDeviceHdl - For creating shader modules. - - \param sourceLib - The source library class that stores the container of shader binary data. - - */ - /***************************************************************************/ - void SHShaderModuleLibrary::ImportFromSourceLibrary(Handle& logicalDeviceHdl, SHShaderSourceLibrary const& sourceLib) noexcept - { - auto const& sources = sourceLib.GetSourceLibrary(); - for (auto const& source : sources) - { - vk::ShaderStageFlagBits shaderType{}; - switch (source.shaderType) - { - case SH_SHADER_TYPE::VERTEX: - shaderType = vk::ShaderStageFlagBits::eVertex; - break; - case SH_SHADER_TYPE::FRAGMENT: - shaderType = vk::ShaderStageFlagBits::eFragment; - break; - case SH_SHADER_TYPE::COMPUTE: - shaderType = vk::ShaderStageFlagBits::eCompute; - break; - default: - shaderType = vk::ShaderStageFlagBits::eVertex; - break; - } - - Handle newShaderModule = logicalDeviceHdl->CreateShaderModule(source.spirvBinary, "main", shaderType, source.name); - shaderModules.emplace(source.id, newShaderModule); - stringToID.emplace(source.name, source.id); - } - } - - /***************************************************************************/ - /*! - - \brief - Gets the shader module based on module name. - - \param shaderName - - \return - - */ - /***************************************************************************/ - Handle SHShaderModuleLibrary::GetShaderModule(std::string shaderName) const noexcept - { - if (stringToID.contains(shaderName)) - return shaderModules.at(stringToID.at(shaderName)); - else - return {}; - } - -} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h deleted file mode 100644 index ed942833..00000000 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef SH_SHADER_MODULE_LIBRARY_H -#define SH_SHADER_MODULE_LIBRARY_H - -#include "Graphics/Shaders/SHVkShaderModule.h" -#include "SHShaderSourceLibrary.h" -#include - -namespace SHADE -{ - class SHVkLogicalDevice; - - /* - * The purpose of this shader module library is to be separate from the source library. The source library contains - * pure shader binary data that contains no vulkan related objects. Every time we load on unload a scene/level, - * this class and the source library class is cleared of its modules and recreated. - */ - class SHShaderModuleLibrary - { - private: - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER VARIABLES */ - /*-----------------------------------------------------------------------*/ - //! Stored shader modules - std::unordered_map> shaderModules; - - //! We want some sort of interface with strings, instead of ints - std::map stringToID; - - public: - /*-----------------------------------------------------------------------*/ - /* PUBLIC MEMBER FUNCTIONS */ - /*-----------------------------------------------------------------------*/ - void ImportFromSourceLibrary(Handle& logicalDeviceHdl, SHShaderSourceLibrary const& sourceLib) noexcept; - - /*-----------------------------------------------------------------------*/ - /* SETTERS AND GETTERS */ - /*-----------------------------------------------------------------------*/ - Handle GetShaderModule(std::string shaderName) const noexcept; - }; -} - -#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp deleted file mode 100644 index dd0f1612..00000000 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include "SHPch.h" -#include -#include -#include "SHShaderSourceLibrary.h" -#include "Tools/SHLogger.h" - -namespace SHADE -{ - /***************************************************************************/ - /*! - - \brief - Initializes the directory to take assets from. TODO: Only temporary until - the resource manager is implemented. - - \param directory - - \return - - */ - /***************************************************************************/ - void SHShaderSourceLibrary::Init (std::string directory) noexcept - { - shaderDirectory = directory; - if (shaderDirectory.back() != '/') - { - shaderDirectory += '/'; - } - } - - /***************************************************************************/ - /*! - - \brief - Private member function to compile a shader STRING source to binary and - returns a vector of 4 bytes. - - \param glslSource - The GLSL string source. - - \param type - Type of the shader: vertex, fragment, compute, etc. - - \param opLevel - Optimization level. - - \return - Returns a vector of the binary data. - - */ - /***************************************************************************/ - std::vector SHShaderSourceLibrary::CompileToBinary(std::string const& glslSource, char const* const spirvFilename, SH_SHADER_TYPE type, shaderc_optimization_level opLevel /*= shaderc_optimization_level_zero*/) - { - // shaderc compiler - shaderc::Compiler compiler; - shaderc::CompileOptions options; - - options.AddMacroDefinition("MY_DEFINE", "1"); - - // Set optimization levels - if (opLevel != shaderc_optimization_level_zero) - options.SetOptimizationLevel(opLevel); - - // Attempt to get the shaderc equivalent shader stage - shaderc_shader_kind shaderKind; - switch (type) - { - case SH_SHADER_TYPE::VERTEX: - shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; - break; - case SH_SHADER_TYPE::FRAGMENT: - shaderKind = shaderc_shader_kind::shaderc_glsl_fragment_shader; - break; - case SH_SHADER_TYPE::COMPUTE: - shaderKind = shaderc_shader_kind::shaderc_glsl_compute_shader; - break; - default: - shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; - break; - } - - // Compile the shader and get the result - shaderc::SpvCompilationResult compileResult = compiler.CompileGlslToSpv(glslSource, shaderKind, spirvFilename, options); - - if (compileResult.GetCompilationStatus() != shaderc_compilation_status_success) - { - SHLOG_ERROR("Shaderc failed to compile GLSL shader to binary | " + compileResult.GetErrorMessage()); - } - - return { compileResult.begin(), compileResult.end() }; - } - - /***************************************************************************/ - /*! - - \brief - TODO: Delete after file IO is implemented. Loads a shader from disk. - - \param filePath - file path to the shader in the asset directory. - - \return - Returns the data in the file in string form. - - */ - /***************************************************************************/ - std::string SHShaderSourceLibrary::GetStringFromFile(char const* filePath) noexcept - { - // Retrieve contents from filePath - // Ensure ifstream objects can throw exceptions - std::ifstream iFile; - iFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); - std::string fileContent = ""; - - try - { - // Open file - // Read file's buffer contents into streams - iFile.open(filePath); - std::stringstream fileStream; - fileStream << iFile.rdbuf(); - - fileContent = fileStream.str(); - - // Close file handler - iFile.close(); - } - catch (std::ifstream::failure e) - { - std::cerr << "File was not successfully read" << filePath << std::endl; - } - - return fileContent; - - } - - /***************************************************************************/ - /*! - - \brief - Load a shader into the library. - - \param filePath - file path to the shader in the asset directory. - - */ - /***************************************************************************/ - bool SHShaderSourceLibrary::LoadShader (uint32_t id, std::string glslFile, SH_SHADER_TYPE type, bool checkSpirvOutdated/* = true*/, bool recompileAnyway /*= false*/) noexcept - { - //if (sourceLibrary.contains(id)) - //{ - // SHLOG_ERROR("Shader with ID passed in already exists. Use a different ID"); - // return false; - //} - - std::string fullGLSLPath = shaderDirectory + glslFile; - auto path = std::filesystem::path(fullGLSLPath); - - if (path.extension() != ".glsl") - { - SHLOG_ERROR("Shader is not GLSL file, failed to load shader. "); - return false; - } - - std::string spirvFilepath = path.replace_extension("spv").string(); - - SHShaderData newShaderData{}; - newShaderData.shaderType = type; - - // spirv file - std::ifstream spirvFile(spirvFilepath, std::ios::ate | std::ios::binary); - - // If we disable spirv validation, file is not checked - if (!recompileAnyway && - spirvFile.is_open() && - (checkSpirvOutdated ? (std::filesystem::last_write_time(spirvFilepath) > std::filesystem::last_write_time(fullGLSLPath)) : true)) - { - // Get file size of binary - uint32_t fileSize = static_cast(spirvFile.tellg()); - - // resize container to store binary - newShaderData.spirvBinary.resize(fileSize / sizeof(uint32_t)); - - // Read data from binary file to container - spirvFile.seekg(0); - spirvFile.read(reinterpret_cast(newShaderData.spirvBinary.data()), fileSize); - - // close file - spirvFile.close(); - - } - else - { - // Use glslc to generate spirv file - newShaderData.spirvBinary = CompileToBinary(GetStringFromFile(fullGLSLPath.c_str()), spirvFilepath.c_str(), type); - - std::ofstream binaryFile(spirvFilepath, std::ios::binary); - - if (binaryFile.is_open()) - { - // write all data to binary file - binaryFile.write(reinterpret_cast(newShaderData.spirvBinary.data()), newShaderData.spirvBinary.size() * sizeof(uint32_t)); - } - else - { - SHLOG_ERROR("Failed to modify spirv file. "); - return false; - } - } - - newShaderData.name = glslFile; - newShaderData.id = id; - - sourceLibrary.emplace_back(std::move (newShaderData)); - return true; - } - - /***************************************************************************/ - /*! - - \brief - Gets the entire source library. - - \return - The container of binary data. - - */ - /***************************************************************************/ - std::vector const& SHShaderSourceLibrary::GetSourceLibrary(void) const noexcept - { - return sourceLibrary; - } - - /***************************************************************************/ - /*! - - \brief - Move ctor for shader data. - - \param rhs - The other shader data - - */ - /***************************************************************************/ - SHShaderData::SHShaderData(SHShaderData&& rhs) noexcept - : spirvBinary{ std::move (rhs.spirvBinary)} - , shaderType{std::move (rhs.shaderType)} - , name{ std::move (rhs.name)} - , id {std::move (rhs.id)} - { - - } - - /***************************************************************************/ - /*! - - \brief - Default ctor for shader data. Does nothing. - - */ - /***************************************************************************/ - SHShaderData::SHShaderData(void) noexcept - : spirvBinary{} - , shaderType{SH_SHADER_TYPE::VERTEX} - , name{ } - , id{ } - - { - - } - -} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h deleted file mode 100644 index 505afa97..00000000 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SH_SHADER_SOURCE_LIBRARY_H -#define SH_SHADER_SOURCE_LIBRARY_H - -#include -#include "SHShaderType.h" -#include "shaderc/shaderc.hpp" - -namespace SHADE -{ - struct SHShaderData - { - /*-----------------------------------------------------------------------*/ - /* MEMBER VARIABLES */ - /*-----------------------------------------------------------------------*/ - //! container storing the spirv binary - std::vector spirvBinary; - - //! For the compilation of the shader. Vulkan backend will use it too - SH_SHADER_TYPE shaderType; - - //! Name of the shader file (without parent path) - std::string name; - - //! id of the shader - uint32_t id; - - SHShaderData(void) noexcept; - SHShaderData(SHShaderData&& rhs) noexcept; - }; - - // TODO: This class is purely temporary and will be converted/changed when XQ implements his resource manager - class SHShaderSourceLibrary - { - private: - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER VARIABLES */ - /*-----------------------------------------------------------------------*/ - //! Stores all the source data. Take note that the source here is GLSL source and NOT binary data. - //! Binary data gets passed to the backend to convert to spirv. - std::vector sourceLibrary; - - //! The directory where the shaders are located. - std::string shaderDirectory; - - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER FUNCTIONS */ - /*-----------------------------------------------------------------------*/ - std::vector CompileToBinary(std::string const& glslSource, char const* const spirvFilename, SH_SHADER_TYPE type, shaderc_optimization_level opLevel = shaderc_optimization_level_zero); - - // TODO: Delete after file IO is implemented - std::string GetStringFromFile(char const* filePath) noexcept; - - public: - /*-----------------------------------------------------------------------*/ - /* PUBLIC MEMBER FUNCTIONS */ - /*-----------------------------------------------------------------------*/ - void Init (std::string directory) noexcept; - bool LoadShader (uint32_t id, std::string glslFile, SH_SHADER_TYPE type, bool checkSpirvOutdated = true, bool recompileAnyway = false) noexcept; - - /*-----------------------------------------------------------------------*/ - /* SETTERS AND GETTERS */ - /*-----------------------------------------------------------------------*/ - std::vector const& GetSourceLibrary(void) const noexcept; - }; -} - -#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h deleted file mode 100644 index bf4828b5..00000000 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SH_SHADER_TYPE_H -#define SH_SHADER_TYPE_H - -namespace SHADE -{ - enum class SH_SHADER_TYPE - { - VERTEX, - FRAGMENT, - COMPUTE - }; -} - -#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp new file mode 100644 index 00000000..b92ccddf --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp @@ -0,0 +1,228 @@ +/************************************************************************************//*! +\file SHTextureLibrary.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 20, 2022 +\brief Contains definitions for all of the functions of the classes that deal + with storage and management of buffers for textures. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHTextureLibrary.h" + +#include "Graphics/SHVulkanIncludes.h" + +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/SHVkUtil.h" +#include "Tools/SHLogger.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Assets/Asset Types/SHTextureAsset.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHTextureLibrary::Add(const SHTextureAsset& texAsset, Handle sampler) + { + return Add + ( + texAsset.numBytes, + texAsset.pixelData, + texAsset.width, texAsset.height, + texAsset.format, + texAsset.mipOffsets, + sampler + ); + } + + Handle SHTextureLibrary::Add(uint32_t pixelCount, const SHTexture::PixelChannel* const pixelData, uint32_t width, uint32_t height, SHTexture::TextureFormat format, std::vector mipOffsets, Handle sampler) + { + isDirty = true; + + auto handle = resourceManager.Create(); + addJobs.emplace_back(AddJob { pixelCount, pixelData, format, sampler, mipOffsets, width, height, handle }); + return handle; + } + + void SHTextureLibrary::Remove(Handle texture) + { + if (!texture) + throw std::invalid_argument("Attempted to remove a Texture that did not belong to the Texture Library!"); + + removeJobs.emplace_back(texture); + isDirty = true; + } + + void SHTextureLibrary::BuildTextures(Handle device, Handle cmdBuffer, Handle graphicsQueue, Handle descPool) + { + // Don't do anything if there are no updates + if (!isDirty) + return; + + /* Remove Textures */ + // TODO + + /* Add Textures */ + // Load Textures - Transitions + std::vector pipelineBarriers(addJobs.size()); + for (int i = 0; auto& job : addJobs) + { + job.Image = resourceManager.Create + ( + device, + &device->GetVMAAllocator(), + SHImageCreateParams + { + .imageType = vk::ImageType::e2D, + .width = job.Width, + .height = job.Height, + .depth = 1, + .levels = static_cast(job.MipOffsets.size()), + .arrayLayers = 1, + .imageFormat = job.TextureFormat, + .usageFlags = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, + .createFlags = {} + }, + job.PixelData, + job.PixelCount, + job.MipOffsets, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits {} + ); + job.Image->PrepareImageTransitionInfo(vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, pipelineBarriers[i]); + ++i; + } + vk::PipelineStageFlagBits srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + vk::PipelineStageFlagBits dstStage = vk::PipelineStageFlagBits::eTopOfPipe; + preparePipelineBarriers(vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, srcStage, dstStage, pipelineBarriers); + cmdBuffer->BeginRecording(); + cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, pipelineBarriers); + + // Copy + for (auto& job : addJobs) + { + job.Image->TransferToDeviceResource(cmdBuffer); + } + + // Transition + for (int i = 0; auto & job : addJobs) + { + // Transition + job.Image->PrepareImageTransitionInfo(vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, pipelineBarriers[i]); + ++i; + } + preparePipelineBarriers(vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, srcStage, dstStage, pipelineBarriers); + cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, pipelineBarriers); + + // Execute Commands + cmdBuffer->EndRecording(); + graphicsQueue->SubmitCommandBuffer({ cmdBuffer }); + device->WaitIdle(); + graphicsQueue->GetVkQueue().waitIdle(); + + // Create Image View + for (auto& job : addJobs) + { + const SHImageViewDetails DETAILS + { + .viewType = vk::ImageViewType::e2D, + .format = job.TextureFormat, + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = static_cast(job.MipOffsets.size()), + .baseArrayLayer = 0, + .layerCount = 1 + }; + job.TextureHandle->ImageView = job.Image->CreateImageView(device, job.Image, DETAILS); + SET_VK_OBJ_NAME(device, vk::ObjectType::eImageView, job.TextureHandle->ImageView->GetImageView(), "[Image View] Texture Library Texture #" + std::to_string(job.TextureHandle->TextureArrayIndex)); + } + + // Add Textures + for (auto& job : addJobs) + { + texOrder.emplace_back(job.TextureHandle); + combinedImageSamplers.emplace_back(std::make_tuple(job.TextureHandle->ImageView, job.Sampler, vk::ImageLayout::eShaderReadOnlyOptimal)); + job.TextureHandle->TextureArrayIndex = static_cast(texOrder.size()) - 1U; + + SET_VK_OBJ_NAME(device, vk::ObjectType::eImage, job.Image->GetVkImage(), "[Image] Texture Library Texture #" + std::to_string(job.TextureHandle->TextureArrayIndex)); + SET_VK_OBJ_NAME(device, vk::ObjectType::eImageView, job.TextureHandle->ImageView->GetImageView(), "[Image View] Texture Library Texture #" + std::to_string(job.TextureHandle->TextureArrayIndex)); + } + addJobs.clear(); + + /* Build Descriptor Set with all the Textures only if there are textures */ + if (!texOrder.empty()) + { + if (texDescriptors) + { + texDescriptors.Free(); + } + texDescriptors = descPool->Allocate + ( + { SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS] }, + { static_cast(texOrder.size()) } + ); +#ifdef _DEBUG + for (auto set : texDescriptors->GetVkHandle()) + SET_VK_OBJ_NAME(device, vk::ObjectType::eDescriptorSet, set, "[Descriptor Set] Static Globals"); +#endif + texDescriptors->ModifyWriteDescImage + ( + SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS, + SHGraphicsConstants::DescriptorSetBindings::IMAGE_AND_SAMPLERS_DATA, + combinedImageSamplers + ); + texDescriptors->UpdateDescriptorSetImages + ( + SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS, + SHGraphicsConstants::DescriptorSetBindings::IMAGE_AND_SAMPLERS_DATA + ); + } + + isDirty = false; + } + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void SHTextureLibrary::preparePipelineBarriers(vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::PipelineStageFlagBits& srcStage, vk::PipelineStageFlagBits& dstStage, std::vector& barriers) + { + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eTransfer; + + for (auto& barrier : barriers) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + } + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + srcStage = vk::PipelineStageFlagBits::eTransfer; + + // TODO, what if we want to access in compute shader + dstStage = vk::PipelineStageFlagBits::eFragmentShader; + + for (auto& barrier : barriers) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + } + } + else + { + SHLOG_ERROR("Image layouts are invalid. "); + } + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.h new file mode 100644 index 00000000..5b7e4914 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.h @@ -0,0 +1,186 @@ +/************************************************************************************//*! +\file SHTextureLibrary.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 20, 2022 +\brief Contains definitions for all of the classes that deal with storage and + management of textures. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// Project Includes +#include "Resource/SHHandle.h" +#include "Resource/SHResourceLibrary.h" +#include "Math/SHMath.h" +#include "Graphics/SHVulkanIncludes.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkLogicalDevice; + class SHVkCommandBuffer; + class SHVkImage; + class SHVkImageView; + class SHVkQueue; + class SHVkDescriptorPool; + class SHVkDescriptorSetLayout; + class SHVkDescriptorSetGroup; + class SHVkSampler; + struct SHTextureAsset; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + class SHTexture + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using PixelChannel = unsigned char; + using TextureFormat = vk::Format; // TODO: Change + using Index = uint32_t; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle ImageView; + Index TextureArrayIndex = std::numeric_limits::max(); + }; + /***********************************************************************************/ + /*! + \brief + Manages storage for all textures in the Graphics System as a single set of + textures. + */ + /***********************************************************************************/ + class SHTextureLibrary + { + public: + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /*******************************************************************************/ + /*! + + \brief + Adds a texture to the Texture Library. But this does not mean that the + textures have been added yet. A call to "BuildTextures()" is required to + transfer all textures into the GPU. + + \param pixelCount + Number of pixels in this Mesh. + \param positions + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + positions. + \param format + Format of the texture loaded in. + + \return + Handle to the created Texture. This is not valid to be used until a call to + BuildImages(). + + */ + /*******************************************************************************/ + + Handle Add(const SHTextureAsset& texAsset, Handle sampler); + Handle Add(uint32_t pixelCount, const SHTexture::PixelChannel* const pixelData, uint32_t width, uint32_t height, SHTexture::TextureFormat format, std::vector mipOffsets, Handle sampler); + /*******************************************************************************/ + /*! + + \brief + Removes a mesh from the Texture Library. But this does not mean that the + textures have been removed yet. A call to "BuildTextures()" is required to + finalise all changes. + + \param mesh + Handle to the mesh to remove. + + */ + /*******************************************************************************/ + void Remove(Handle mesh); + /***************************************************************************/ + /*! + + \brief + Finalises all changes to the Texture Library into the GPU buffers. + + \param device + Device used to create and update the buffers. + \param cmdBuffer + Command buffer used to set up transfers of data in the GPU memory. This + call must be preceded by calls to cmdBuffer's BeginRecording() and ended + with EndRecording(). Do recall to also submit the cmdBuffer to a transfer + queue. + */ + /***************************************************************************/ + void BuildTextures(Handle device, Handle cmdBuffer, Handle graphicsQueue, Handle descPool); + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetTextureDescriptorSetGroup() const noexcept { return texDescriptors; } + /***************************************************************************/ + /*! + * + \brief + Retrieves the texture handle at the specified texture index. + + \param textureId + Index of the texture to look for. + + \returns + Handle to the texture + + */ + /***************************************************************************/ + Handle GetTextureHandle(SHTexture::Index textureId) const { return texOrder[textureId]; }; + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definition */ + /*-----------------------------------------------------------------------------*/ + struct AddJob + { + uint32_t PixelCount = 0; + const SHTexture::PixelChannel* PixelData = nullptr; + SHTexture::TextureFormat TextureFormat = {}; + Handle Sampler; + std::vector MipOffsets; + uint32_t Width; + uint32_t Height; + Handle TextureHandle; + Handle Image; + }; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Manipulation Queues + std::vector addJobs; + std::vector> removeJobs; + // Tracking + SHResourceHub resourceManager; + std::vector> texOrder; + // CPU Storage + std::vector, Handle, vk::ImageLayout>> combinedImageSamplers; + // GPU Storage + Handle texDescriptors; + // Flags + bool isDirty = true; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + void preparePipelineBarriers(vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::PipelineStageFlagBits& srcStage, vk::PipelineStageFlagBits& dstStage, std::vector& barriers); + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.cpp new file mode 100644 index 00000000..6938dae5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.cpp @@ -0,0 +1,75 @@ +/************************************************************************************//*! +\file SHVkSamplerCache.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Mar 16, 2022 +\brief Contains the implementation for the SHVkSamplerCache class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHVkSamplerCache.h" + +// Standard Library +#include +// Project Header +#include "Graphics/Images/SHVkSampler.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Utility Functions */ + /*---------------------------------------------------------------------------------*/ + void SHSamplerCache::Clear() + { + samplersMap.clear(); + } + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHSamplerCache::GetSampler(Handle device, const SHVkSamplerParams& params) + { + // Get the hash to check if it was cached + const RawSamplerHash HASH = calcHash + ( + params.minFilter, + params.magFilter, + params.addressMode, + params.mipmapMode, + params.minLod, + params.maxLod + ); + + // Check if it was cached + auto sampler = samplersMap.find(HASH); + if (sampler == samplersMap.end()) + { + const auto BUILD_RESULT = samplersMap.emplace(HASH, device->CreateSampler(params)); + sampler = BUILD_RESULT.first; + } + + return sampler->second; + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + SHSamplerCache::RawSamplerHash SHSamplerCache::calcHash(vk::Filter minFilter, vk::Filter magFilter, vk::SamplerAddressMode addressMode, vk::SamplerMipmapMode mipmapMode, float minLod, float maxLod) + { + static auto charHasher = std::hash{}; + static auto floatHasher = std::hash{}; + + const RawSamplerHash H1 = charHasher(static_cast(minFilter)); + const RawSamplerHash H2 = charHasher(static_cast(magFilter)); + const RawSamplerHash H3 = charHasher(static_cast(addressMode)); + const RawSamplerHash H4 = charHasher(static_cast(mipmapMode)); + const RawSamplerHash H5 = floatHasher(minLod); + const RawSamplerHash H6 = floatHasher(maxLod); + + return H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3) ^ (H5 << 4) ^ (H6 << 5); + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.h b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.h new file mode 100644 index 00000000..1adbcb8c --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHVkSamplerCache.h @@ -0,0 +1,80 @@ +/************************************************************************************//*! +\file SHSamplerCache.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Mar 16, 2022 +\brief Contains the interface for the SHSamplerCache class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Standard Libraries +#include +// External Dependencies +#include "Graphics/SHVulkanIncludes.h" +// Project Includes +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkSampler; + struct SHVkSamplerParams; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Class that is responsible for caching and providing Samplers to the user for + sampling textures. + */ + /*************************************************************************************/ + class SHSamplerCache + { + public: + /*---------------------------------------------------------------------------------*/ + /* Utility Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Clears the cache, destroying all created samplers. + /// + void Clear(); + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Retrieves a Vulkan Sampler of the specified type. If this was retrieved + /// before, a cached copy of the Vulkan Sampler will be provided. + /// + /// Logical device to create the sampler with if needed. + /// Describes the parameters for the sampler. + /// Handle to the SHVkSampler object specified. + Handle GetSampler(Handle device, const SHVkSamplerParams& params); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + using RawSamplerHash = size_t; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + // Resources + std::unordered_map> samplersMap; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static RawSamplerHash calcHash(vk::Filter minFilter, vk::Filter magFilter, vk::SamplerAddressMode addressMode, vk::SamplerMipmapMode mipmapMode, float minLod, float maxLod); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h index 961b3f4c..010bed0e 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h @@ -2,8 +2,10 @@ #define SH_PIPELINE_LAYOUT_PARAMS_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Graphics/SHVulkanDefines.h" +#include "Resource/SHHandle.h" #include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include namespace SHADE { @@ -23,7 +25,22 @@ namespace SHADE //! used just for textures or lights for example). In that case, we still //! want to use the layout to initialize the pipeline layout but we do not //! want to use it for allocating descriptor sets. - std::vector> globalDescSetLayouts = {}; + std::vector> const& globalDescSetLayouts = {}; + + //! Since both SPIRV-Reflect and GLSL don't provide ways to describe UBOs or + //! SSBOs as dynamic, we need to do it ourselves. This will store bindings + //! which we will use when parsing for descriptor set layouts. If a parsed + //! binding is in this container, we make that binding's descriptor dynamic. + std::unordered_set dynamicBufferBindings; + }; + + struct SHPipelineLayoutParamsDummy + { + /*-----------------------------------------------------------------------*/ + /* MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + + std::vector> const& globalDescSetLayouts = {}; }; } diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp index 8179444f..c7ada11f 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp @@ -1,5 +1,6 @@ #include "SHPch.h" #include "SHPipelineState.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" namespace SHADE { @@ -61,10 +62,10 @@ namespace SHADE renderpassHdl = inRenderpassHdl; } - void SHVkPipelineState::SetSubpassIndex(uint32_t index) noexcept + void SHVkPipelineState::SetSubpass(Handle sp) noexcept { dirty = true; - subpassIndex = index; + subpass = sp; } SHVertexInputState const& SHVkPipelineState::GetVertexInputState(void) const noexcept @@ -107,9 +108,9 @@ namespace SHADE return renderpassHdl; } - uint32_t SHVkPipelineState::GetSubpassIndex(void) const noexcept + Handle SHVkPipelineState::GetSubpass(void) noexcept { - return subpassIndex; + return subpass; } void SHVkPipelineState::SetDirty(bool isDirty) noexcept @@ -117,7 +118,7 @@ namespace SHADE dirty = isDirty; } - std::tuple SHVertexInputState::GetInfoFromAttribFormat(SHAttribFormat attribFormat) const noexcept + std::tuple SHVertexInputState::GetInfoFromAttribFormat(SHAttribFormat attribFormat) const noexcept { switch (attribFormat) { @@ -138,6 +139,15 @@ namespace SHADE case SHAttribFormat::MAT_4D: return std::make_tuple(4, 16, vk::Format::eR32G32B32A32Sfloat); break; + + case SHAttribFormat::UINT32_1D: + return std::make_tuple(1, 4, vk::Format::eR32Uint); + case SHAttribFormat::UINT32_2D: + return std::make_tuple(1, 8, vk::Format::eR32G32Uint); + case SHAttribFormat::UINT32_3D: + return std::make_tuple(1, 12, vk::Format::eR32G32B32Uint); + case SHAttribFormat::UINT32_4D: + return std::make_tuple(1, 16, vk::Format::eR32G32B32A32Uint); } return std::make_tuple(0, 0, vk::Format::eR32Sfloat); } @@ -200,6 +210,8 @@ namespace SHADE // Offset is 0 at first (for first element) uint32_t offset = 0; + binding.binding = static_cast(bindings.size() - 1); + // for every attribute passed in for (auto const& attrib : inAttribs) { diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h index 7c24aab7..4c8d679a 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h @@ -8,6 +8,8 @@ namespace SHADE { + class SHSubpass; + // This is mainly for non-interleaved attributes where we have a separate buffer for // each vertex attribute. struct SHVertexInputState @@ -120,7 +122,7 @@ namespace SHADE bool depthWrite{ VK_TRUE }; // Note: Using Reversed depth-buffer for increased precision, so Greater depth values are kept - vk::CompareOp depthCompare{ vk::CompareOp::eGreater }; + vk::CompareOp depthCompare{ vk::CompareOp::eLess }; bool depthBounds{ VK_FALSE }; @@ -133,11 +135,11 @@ namespace SHADE struct SHColorBlendState { - VkBool32 logic_op_enable{ VK_TRUE }; + VkBool32 logic_op_enable{ VK_FALSE }; - vk::LogicOp logic_op{ VK_LOGIC_OP_COPY }; + vk::LogicOp logic_op{ vk::LogicOp::eCopy }; - std::vector attachments; + std::vector attachments{}; }; // TODO: Specialization constants @@ -152,31 +154,31 @@ namespace SHADE bool dirty{ false }; //! Vertex input state - SHVertexInputState vertexInputState; + SHVertexInputState vertexInputState; //! Input assembly - SHInputAssemblyState inputAssemblyState; + SHInputAssemblyState inputAssemblyState; //! Rasterization state - SHRasterizationState rasterizationState; + SHRasterizationState rasterizationState; //! Viewport state - SHViewportState viewportState; + SHViewportState viewportState; //! Multisample state - SHMultisampleState multisampleState; + SHMultisampleState multisampleState; //! Depth Stencil state - SHDepthStencilState depthStencilState; + SHDepthStencilState depthStencilState; //! Color blend state - SHColorBlendState colorBlendState; + SHColorBlendState colorBlendState; //! Renderpass that is compatible with the pipeline - Handle renderpassHdl; + Handle renderpassHdl; //! Subpass index - uint32_t subpassIndex; + Handle subpass; friend class SHVkPipeline; public: @@ -189,7 +191,7 @@ namespace SHADE /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - template , SHVertexInputState>>> + template , SHVertexInputState>>> void SetVertexInputState (T&& state) noexcept; void SetInputAssemblyState(SHInputAssemblyState const& state) noexcept; void SetRasterizationState(SHRasterizationState const& state) noexcept; @@ -198,17 +200,17 @@ namespace SHADE void SetDepthStencilState (SHDepthStencilState const& state) noexcept; void SetColorBlenState (SHColorBlendState const& state) noexcept; void SetRenderpass (Handle const& inRenderpassHdl) noexcept; - void SetSubpassIndex (uint32_t index) noexcept; + void SetSubpass (Handle sp) noexcept; - SHVertexInputState const& GetVertexInputState (void) const noexcept; - SHInputAssemblyState const& GetInputAssemblyState (void) const noexcept; - SHRasterizationState const& GetRasterizationState (void) const noexcept; - SHViewportState const& GetViewportState (void) const noexcept; - SHMultisampleState const& GetMultisampleState (void) const noexcept; - SHDepthStencilState const& GetDepthStencilState (void) const noexcept; - SHColorBlendState const& GetColorBlenState (void) const noexcept; - Handle const& GetRenderpass (void) const noexcept; - uint32_t GetSubpassIndex (void) const noexcept; + SHVertexInputState const& GetVertexInputState (void) const noexcept; + SHInputAssemblyState const& GetInputAssemblyState (void) const noexcept; + SHRasterizationState const& GetRasterizationState (void) const noexcept; + SHViewportState const& GetViewportState (void) const noexcept; + SHMultisampleState const& GetMultisampleState (void) const noexcept; + SHDepthStencilState const& GetDepthStencilState (void) const noexcept; + SHColorBlendState const& GetColorBlenState (void) const noexcept; + Handle const& GetRenderpass (void) const noexcept; + Handle GetSubpass (void) noexcept; void SetDirty(bool isDirty) noexcept; }; diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h index e7f5b6a8..2c1c80ff 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h @@ -5,9 +5,13 @@ namespace SHADE { enum class SH_PIPELINE_TYPE { - GRAPHICS, + GRAPHICS = 0, COMPUTE, + RAY_TRACING, + NUM_TYPES, }; + + } #endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp index 263ae578..973ae72f 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp @@ -3,6 +3,8 @@ #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Shaders/SHVkShaderModule.h" #include "Graphics/Debugging/SHVulkanDebugUtil.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" +#include "Graphics/SHVkUtil.h" namespace SHADE { @@ -154,7 +156,7 @@ namespace SHADE gpCreateInfo.layout = pipelineLayout->GetVkPipelineLayout(); gpCreateInfo.renderPass = pipelineState.GetRenderpass()->GetVkRenderpass(); - gpCreateInfo.subpass = pipelineState.GetSubpassIndex(); + gpCreateInfo.subpass = pipelineState.GetSubpass()->GetIndex(); gpCreateInfo.basePipelineHandle = VK_NULL_HANDLE; gpCreateInfo.basePipelineIndex = -1; @@ -170,6 +172,29 @@ namespace SHADE void SHVkPipeline::CreateComputePipeline(void) noexcept { + auto shaderModule = pipelineLayout->GetShaderModules()[0]; + + vk::PipelineShaderStageCreateInfo shaderStageCreateInfo + { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = shaderModule->GetVkShaderModule(), + .pName = shaderModule->GetEntryPoint().c_str(), + }; + + vk::ComputePipelineCreateInfo cpCreateInfo + { + .flags = {}, + .stage = shaderStageCreateInfo, + .layout = pipelineLayout->GetVkPipelineLayout(), + }; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createComputePipelines({}, 1, &cpCreateInfo, nullptr, &vkPipeline); result != vk::Result::eSuccess) + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Compute Pipeline. "); + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Compute Pipeline. "); + created = true; + } } @@ -178,7 +203,7 @@ namespace SHADE /*! \brief - Non-default ctor. + Non-default ctor for creating graphics pipeline. \param inLogicalDeviceHdl Needed for creation and destruction. @@ -199,14 +224,12 @@ namespace SHADE The subpass that this pipeline will be used in. If state is not nullptr, this parameter is ignored. - \param type - The type of the pipeline. */ /***************************************************************************/ - SHVkPipeline::SHVkPipeline(Handle const& inLogicalDeviceHdl, Handle const& inPipelineLayout, SHVkPipelineState const* const state, Handle const& renderpassHdl, uint32_t subpass, SH_PIPELINE_TYPE type) noexcept - : pipelineState{ } // copy the pipeline state - , pipelineType {type} + SHVkPipeline::SHVkPipeline(Handle const& inLogicalDeviceHdl, Handle const& inPipelineLayout, SHVkPipelineState const* const state, Handle const& renderpassHdl, Handle subpass) noexcept + : pipelineState{ } + , pipelineType {SH_PIPELINE_TYPE::GRAPHICS} , vkPipeline {VK_NULL_HANDLE} , logicalDeviceHdl{ inLogicalDeviceHdl } , pipelineLayout { inPipelineLayout } @@ -222,8 +245,8 @@ namespace SHADE } else { - pipelineState.SetRenderpass(renderpassHdl); - pipelineState.SetSubpassIndex(subpass); + pipelineState.SetRenderpass (renderpassHdl); + pipelineState.SetSubpass (subpass); } } @@ -246,7 +269,34 @@ namespace SHADE , logicalDeviceHdl{ rhs.logicalDeviceHdl } , pipelineLayout { rhs.pipelineLayout } { - vkPipeline = VK_NULL_HANDLE; + rhs.vkPipeline = VK_NULL_HANDLE; + } + + /***************************************************************************/ + /*! + + \brief + Just to differentiate between compute and graphics pipeline, we will + have a constructor that takes in less parameters; sufficient for the + compute pipeline to be created. + + \param inLogicalDeviceHdl + \param inPipelineLayout + + \return + + */ + /***************************************************************************/ + SHVkPipeline::SHVkPipeline(Handle const& inLogicalDeviceHdl, Handle const& inPipelineLayout) noexcept + : pipelineState{ } + , pipelineType{ SH_PIPELINE_TYPE::COMPUTE } + , vkPipeline{ VK_NULL_HANDLE } + , logicalDeviceHdl{ inLogicalDeviceHdl } + , pipelineLayout{ inPipelineLayout } + , created{ false } + + { + } /***************************************************************************/ @@ -259,7 +309,8 @@ namespace SHADE /***************************************************************************/ SHVkPipeline::~SHVkPipeline(void) noexcept { - logicalDeviceHdl->GetVkLogicalDevice().destroyPipeline(vkPipeline, nullptr); + if (vkPipeline) + logicalDeviceHdl->GetVkLogicalDevice().destroyPipeline(vkPipeline, nullptr); } /***************************************************************************/ @@ -287,7 +338,7 @@ namespace SHADE created = rhs.created; logicalDeviceHdl = rhs.logicalDeviceHdl; - vkPipeline = VK_NULL_HANDLE; + rhs.vkPipeline = VK_NULL_HANDLE; return *this; } @@ -373,18 +424,7 @@ namespace SHADE /***************************************************************************/ vk::PipelineBindPoint SHVkPipeline::GetPipelineBindPoint(void) const noexcept { - switch (pipelineType) - { - case SH_PIPELINE_TYPE::GRAPHICS: - return vk::PipelineBindPoint::eGraphics; - case SH_PIPELINE_TYPE::COMPUTE: - return vk::PipelineBindPoint::eCompute; - break; - default: - return vk::PipelineBindPoint::eGraphics; - break; - - } + return SHVkUtil::GetPipelineBindPointFromType(pipelineType); } /***************************************************************************/ @@ -424,4 +464,9 @@ namespace SHADE return pipelineLayout; } + SH_PIPELINE_TYPE SHVkPipeline::GetPipelineType(void) const noexcept + { + return pipelineType; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h index 9a9d78f4..7e313ed6 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h @@ -3,12 +3,13 @@ #include "SHPipelineState.h" #include "SHPipelineType.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include "Graphics/Pipeline/SHVkPipelineLayout.h" namespace SHADE { class SHVkLogicalDevice; + class SHSubpass; class SHVkPipeline { @@ -46,12 +47,15 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ + //! For constructing a graphics pipeline SHVkPipeline (Handle const& inLogicalDeviceHdl, Handle const& inPipelineLayout, SHVkPipelineState const* const state, Handle const& renderpassHdl, - uint32_t subpass, - SH_PIPELINE_TYPE type) noexcept; + Handle subpass) noexcept; + //! For constructing a compute pipeline + SHVkPipeline(Handle const& inLogicalDeviceHdl, + Handle const& inPipelineLayout) noexcept; SHVkPipeline (SHVkPipeline&& rhs) noexcept; ~SHVkPipeline (void) noexcept; @@ -74,6 +78,7 @@ namespace SHADE vk::Pipeline GetVkPipeline (void) const noexcept; bool GetIsCreated (void) const noexcept; Handle GetPipelineLayout (void) const noexcept; + SH_PIPELINE_TYPE GetPipelineType (void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp index cc738aed..47b2e010 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp @@ -165,6 +165,28 @@ namespace SHADE newBinding.Stage = shaderModule->GetShaderStageFlagBits(); newBinding.Type = descBindingInfo.ConvertFromReflectDescType(reflectedBinding->descriptor_type); + // Here we want to check if a binding is supposed to be dynamic. If it is, make it dynamic. + if (newBinding.Type == vk::DescriptorType::eUniformBuffer || newBinding.Type == vk::DescriptorType::eStorageBuffer) + { + for (auto& bsHash : dynamicBufferBindings) + { + uint32_t set = static_cast(bsHash >> 32); + uint32_t binding = static_cast(bsHash & 0xFFFFFFFF); + if (set == CURRENT_SET && binding == newBinding.BindPoint) + { + switch (newBinding.Type) + { + case vk::DescriptorType::eUniformBuffer: + newBinding.Type = vk::DescriptorType::eUniformBufferDynamic; + break; + case vk::DescriptorType::eStorageBuffer: + newBinding.Type = vk::DescriptorType::eStorageBufferDynamic; + break; + } + } + } + } + // In reality, the check for variable descriptor sets do not exists in spirv-reflect. Fortunately, when a shader // defines a boundless descriptor binding in the shader, the information reflected makes the array dimensions // contain a 1 element of value 1. Knowing that having an array [1] doesn't make sense, we can use this to @@ -198,7 +220,7 @@ namespace SHADE // 1 descriptor set layout for every descriptor set detected. for (auto const& set : setsWithBindings) { - auto newDescriptorSetLayout = logicalDeviceHdl->CreateDescriptorSetLayout(set.second); + auto newDescriptorSetLayout = logicalDeviceHdl->CreateDescriptorSetLayout(set.first, set.second); descriptorSetLayoutsAllocate.push_back(newDescriptorSetLayout); } @@ -216,22 +238,32 @@ namespace SHADE /***************************************************************************/ void SHVkPipelineLayout::PrepareVkDescriptorSetLayouts(void) noexcept { - // Settle allocate layouts first - vkDescriptorSetLayoutsAllocate.reserve(descriptorSetLayoutsAllocate.size()); - for (auto const& layout : descriptorSetLayoutsAllocate) - { - vkDescriptorSetLayoutsAllocate.emplace_back(layout->GetVkHandle()); - } - // pipeline layouts contain global layouts first, then layouts for allocation + descriptorSetLayoutsPipeline.reserve(descriptorSetLayoutsAllocate.size() + descriptorSetLayoutsGlobal.size()); vkDescriptorSetLayoutsPipeline.reserve(descriptorSetLayoutsAllocate.size() + descriptorSetLayoutsGlobal.size()); // First we insert the global layouts for (auto const& layout : descriptorSetLayoutsGlobal) + { + descriptorSetLayoutsPipeline.emplace_back(layout); + //vkDescriptorSetLayoutsPipeline.emplace_back(layout->GetVkHandle()); + } + + // Then the allocate layouts + vkDescriptorSetLayoutsAllocate.reserve(descriptorSetLayoutsAllocate.size()); + for (auto const& layout : descriptorSetLayoutsAllocate) + { + descriptorSetLayoutsPipeline.emplace_back(layout); + vkDescriptorSetLayoutsAllocate.emplace_back(layout->GetVkHandle()); + } + + for (auto const& layout : descriptorSetLayoutsPipeline) + { vkDescriptorSetLayoutsPipeline.emplace_back(layout->GetVkHandle()); + } // Then we append layouts for allocation at the back of the vector - std::copy(vkDescriptorSetLayoutsAllocate.begin(), vkDescriptorSetLayoutsAllocate.end(), std::back_inserter(vkDescriptorSetLayoutsPipeline)); + //std::copy(vkDescriptorSetLayoutsAllocate.begin(), vkDescriptorSetLayoutsAllocate.end(), std::back_inserter(vkDescriptorSetLayoutsPipeline)); } /***************************************************************************/ @@ -254,12 +286,6 @@ namespace SHADE // Clear pc ranges vkPcRanges.clear(); - // Kill all descriptor set layouts - for (auto& layout : descriptorSetLayoutsGlobal) - SHVkInstance::GetResourceManager().Free(layout); - - descriptorSetLayoutsGlobal.clear(); - for (auto& layout : descriptorSetLayoutsAllocate) SHVkInstance::GetResourceManager().Free(layout); @@ -285,16 +311,18 @@ namespace SHADE */ /***************************************************************************/ - SHVkPipelineLayout::SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams& pipelineLayoutParams) noexcept + SHVkPipelineLayout::SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams const& pipelineLayoutParams) noexcept : vkPipelineLayout {VK_NULL_HANDLE} - , shaderModules{std::move (pipelineLayoutParams.shaderModules)} + , shaderModules{pipelineLayoutParams.shaderModules} , logicalDeviceHdl {inLogicalDeviceHdl} , pushConstantInterface{} , vkPcRanges{} , descriptorSetLayoutsGlobal{pipelineLayoutParams.globalDescSetLayouts } // do a copy, some other pipeline layout might need this , descriptorSetLayoutsAllocate{} , vkDescriptorSetLayoutsAllocate{} + , descriptorSetLayoutsPipeline{} , vkDescriptorSetLayoutsPipeline{} + , dynamicBufferBindings{std::move (pipelineLayoutParams.dynamicBufferBindings)} { for (auto& mod : shaderModules) { @@ -308,6 +336,47 @@ namespace SHADE RecreateIfNeeded (); } + + SHVkPipelineLayout::SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParamsDummy const& pipelineLayoutParams) noexcept + : vkPipelineLayout{ VK_NULL_HANDLE } + , shaderModules{ } + , logicalDeviceHdl{ inLogicalDeviceHdl } + , pushConstantInterface{} + , vkPcRanges{} + , descriptorSetLayoutsGlobal{} + , descriptorSetLayoutsAllocate{} + , vkDescriptorSetLayoutsAllocate{} + , descriptorSetLayoutsPipeline{} + , vkDescriptorSetLayoutsPipeline{} + + { + vkDescriptorSetLayoutsPipeline.resize(pipelineLayoutParams.globalDescSetLayouts.size()); + for (uint32_t i = 0; auto& layout : vkDescriptorSetLayoutsPipeline) + { + layout = pipelineLayoutParams.globalDescSetLayouts[i]->GetVkHandle(); + ++i; + } + + vk::PipelineLayoutCreateInfo plCreateInfo{}; + + // Set push constant data to pipeline layout + plCreateInfo.pushConstantRangeCount = 0; + plCreateInfo.pPushConstantRanges = nullptr; + + // To initialize the descriptor set layouts for the pipeline layout. + plCreateInfo.setLayoutCount = static_cast(vkDescriptorSetLayoutsPipeline.size()); + plCreateInfo.pSetLayouts = vkDescriptorSetLayoutsPipeline.data(); + + if (auto const RESULT = logicalDeviceHdl->GetVkLogicalDevice().createPipelineLayout(&plCreateInfo, nullptr, &vkPipelineLayout); RESULT != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(RESULT, "Failed to create Pipeline Layout. "); + return; + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Pipeline Layout. "); + + } + /***************************************************************************/ /*! @@ -328,7 +397,8 @@ namespace SHADE , descriptorSetLayoutsGlobal {std::move (rhs.descriptorSetLayoutsGlobal)} , descriptorSetLayoutsAllocate {std::move (rhs.descriptorSetLayoutsAllocate)} , vkDescriptorSetLayoutsAllocate{std::move (rhs.vkDescriptorSetLayoutsAllocate)} - , vkDescriptorSetLayoutsPipeline{std::move (rhs.vkDescriptorSetLayoutsAllocate)} + , descriptorSetLayoutsPipeline { std::move(rhs.descriptorSetLayoutsPipeline) } + , vkDescriptorSetLayoutsPipeline{ std::move(rhs.vkDescriptorSetLayoutsPipeline) } { rhs.vkPipelineLayout = VK_NULL_HANDLE; } @@ -401,6 +471,16 @@ namespace SHADE return {}; } + std::vector> const& SHVkPipelineLayout::GetDescriptorSetLayoutsPipeline(void) const noexcept + { + return descriptorSetLayoutsPipeline; + } + + std::vector> const& SHVkPipelineLayout::GetDescriptorSetLayoutsAllocate(void) const noexcept + { + return descriptorSetLayoutsAllocate; + } + SHVkPipelineLayout& SHVkPipelineLayout::operator=(SHVkPipelineLayout&& rhs) noexcept { if (&rhs == this) @@ -414,7 +494,8 @@ namespace SHADE descriptorSetLayoutsGlobal = std::move(rhs.descriptorSetLayoutsGlobal); descriptorSetLayoutsAllocate = std::move(rhs.descriptorSetLayoutsAllocate); vkDescriptorSetLayoutsAllocate = std::move(rhs.vkDescriptorSetLayoutsAllocate); - vkDescriptorSetLayoutsPipeline = std::move(rhs.vkDescriptorSetLayoutsAllocate); + descriptorSetLayoutsPipeline = std::move(rhs.descriptorSetLayoutsPipeline); + vkDescriptorSetLayoutsPipeline = std::move(rhs.vkDescriptorSetLayoutsPipeline); rhs.vkPipelineLayout = VK_NULL_HANDLE; diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h index bce827a7..e2af02a9 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h @@ -29,6 +29,9 @@ namespace SHADE //! Push constant interface SHPushConstantInterface pushConstantInterface; + //! See SHPipelineLayoutParams for details + std::unordered_set dynamicBufferBindings; + //! Push constant ranges std::vector vkPcRanges; @@ -42,6 +45,9 @@ namespace SHADE //! We want to store this also because we need to allocate later std::vector vkDescriptorSetLayoutsAllocate; + //! Store for descriptor set group creation + std::vector> descriptorSetLayoutsPipeline; + //! Store for pipeline layout recreation std::vector vkDescriptorSetLayoutsPipeline; @@ -57,7 +63,8 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ - SHVkPipelineLayout (Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams& pipelineLayoutParams) noexcept; + SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams const& pipelineLayoutParams) noexcept; + SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParamsDummy const& pipelineLayoutParams) noexcept; SHVkPipelineLayout (SHVkPipelineLayout&& rhs) noexcept; ~SHVkPipelineLayout (void) noexcept; SHVkPipelineLayout& operator= (SHVkPipelineLayout&& rhs) noexcept; @@ -70,10 +77,12 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - std::vector> const& GetShaderModules (void) const noexcept; - vk::PipelineLayout GetVkPipelineLayout (void) const noexcept; - SHPushConstantInterface const& GetPushConstantInterface (void) const noexcept; - Handle GetShaderBlockInterface (uint32_t set, uint32_t binding, vk::ShaderStageFlagBits shaderStage) const noexcept; + std::vector> const& GetShaderModules (void) const noexcept; + vk::PipelineLayout GetVkPipelineLayout (void) const noexcept; + SHPushConstantInterface const& GetPushConstantInterface (void) const noexcept; + Handle GetShaderBlockInterface (uint32_t set, uint32_t binding, vk::ShaderStageFlagBits shaderStage) const noexcept; + std::vector> const& GetDescriptorSetLayoutsPipeline(void) const noexcept; + std::vector> const& GetDescriptorSetLayoutsAllocate(void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp index 9f8c529c..dcb3ff6a 100644 --- a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp +++ b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp @@ -5,6 +5,7 @@ #include "Graphics/Synchronization/SHVkSemaphore.h" #include "Graphics/Synchronization/SHVkFence.h" #include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" namespace SHADE @@ -31,14 +32,10 @@ namespace SHADE } - vk::Queue SHVkQueue::GetVkQueue(void) const noexcept + void SHVkQueue::SubmitCommandBuffer(std::initializer_list> cmdBuffers, std::initializer_list> signalSems /*= {}*/, std::initializer_list> waitSems /*= {}*/, vk::PipelineStageFlags waitDstStageMask /*= {}*/, Handle const& optionalFence /*= {}*/) noexcept { - return vkQueue; - } - - void SHVkQueue::SubmitCommandBuffer(std::initializer_list> cmdBuffers, std::initializer_list> signalSems /*= {}*/, std::initializer_list> waitSems /*= {}*/, vk::PipelineStageFlagBits waitDstStageMask /*= {}*/, Handle const& optionalFence /*= {}*/) noexcept - { - std::vector vkWaitSems{ waitSems.size() }; + // prepare wait sems + std::array vkWaitSems{ }; uint32_t i = 0; for (auto& sem : waitSems) { @@ -46,7 +43,8 @@ namespace SHADE ++i; } - std::vector vkSignalSems{ signalSems.size() }; + // prepare signal sems + std::array vkSignalSems{ }; i = 0; for (auto& sem : signalSems) { @@ -54,33 +52,86 @@ namespace SHADE ++i; } - std::vector vkCmdBuffers{ cmdBuffers.size() }; + // prepare cmd buffers + std::array vkCmdBuffers{ }; i = 0; for (auto& cmdBuffer : cmdBuffers) { + // Check if command buffer is in executable state + if (!cmdBuffer->IsReadyToSubmit()) + { + SHLOG_ERROR("Command buffer is not in executable state. Cannot submit command buffer. "); + return; + } + vkCmdBuffers[i] = cmdBuffer->GetVkCommandBuffer(); ++i; } - vk::PipelineStageFlags mask = waitDstStageMask; - + // Prepare submit info vk::SubmitInfo submitInfo { - .waitSemaphoreCount = static_cast(vkWaitSems.size()), + .waitSemaphoreCount = static_cast(waitSems.size()), .pWaitSemaphores = vkWaitSems.data(), - .pWaitDstStageMask = &mask, - .commandBufferCount = static_cast(vkCmdBuffers.size()), + .pWaitDstStageMask = &waitDstStageMask, + .commandBufferCount = static_cast(cmdBuffers.size()), .pCommandBuffers = vkCmdBuffers.data(), - .signalSemaphoreCount = static_cast(vkSignalSems.size()), + .signalSemaphoreCount = static_cast(signalSems.size()), .pSignalSemaphores = vkSignalSems.data(), }; - // #BackEndTest: Submit the queue + // Submit the queue if (auto result = vkQueue.submit(1, &submitInfo, (optionalFence) ? optionalFence->GetVkFence() : nullptr); result != vk::Result::eSuccess) { SHVulkanDebugUtil::ReportVkError(result, "Failed to submit command buffer. "); } + else // if success + { + // Change all command buffers to pending state + for (Handle cmdBuffer : cmdBuffers) + { + cmdBuffer->HandlePostSubmit(); + } + } + } + vk::Result SHVkQueue::Present(Handle const& swapchain, std::initializer_list> waitSems, uint32_t frameIndex) noexcept + { + vk::PresentInfoKHR presentInfo{}; + + // prepare wait sems + std::array vkWaitSems{ }; + uint32_t i = 0; + for (auto& sem : waitSems) + { + vkWaitSems[i] = sem->GetVkSem(); + ++i; + } + + // prepare presentation information + presentInfo.waitSemaphoreCount = static_cast(waitSems.size()); + presentInfo.pWaitSemaphores = vkWaitSems.data(); + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain->GetVkSwapchain(); + presentInfo.pImageIndices = &frameIndex; + + // Present swapchain image. + auto result = vkQueue.presentKHR(&presentInfo); + if (result != vk::Result::eSuccess) + { + SHLOGV_ERROR ("Failed to present swapchain image. "); + } + return result; + } + + vk::Queue SHVkQueue::GetVkQueue() noexcept + { + return vkQueue; + } + + void SHVkQueue::WaitIdle(void) const noexcept + { + vkQueue.waitIdle(); } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h index 203ae59e..f27f3b0b 100644 --- a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h +++ b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h @@ -3,7 +3,7 @@ #include "Graphics/SHVulkanIncludes.h" #include "Graphics/SHVulkanDefines.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { @@ -11,6 +11,7 @@ namespace SHADE class SHVkCommandBuffer; class SHVkFence; class SHVkSemaphore; + class SHVkSwapchain; enum class SH_QUEUE_FAMILY_ARRAY_INDEX : std::size_t { @@ -45,9 +46,12 @@ namespace SHADE public: SHVkQueue(vk::Queue inVkQueue, SHQueueFamilyIndex parent, Handle const& inLogicalDeviceHdl) noexcept; - vk::Queue GetVkQueue(void) const noexcept; - void SubmitCommandBuffer(std::initializer_list> cmdBuffers, std::initializer_list> signalSems = {}, std::initializer_list> waitSems = {}, vk::PipelineStageFlagBits waitDstStageMask = {}, Handle const& fence = {}) noexcept; + void SubmitCommandBuffer (std::initializer_list> cmdBuffers, std::initializer_list> signalSems = {}, std::initializer_list> waitSems = {}, vk::PipelineStageFlags waitDstStageMask = {}, Handle const& fence = {}) noexcept; + vk::Result Present (Handle const& swapchain, std::initializer_list> waitSems, uint32_t frameIndex) noexcept; + vk::Queue GetVkQueue() noexcept; + + void WaitIdle (void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.cpp new file mode 100644 index 00000000..6e01269e --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.cpp @@ -0,0 +1,7 @@ +#include "SHpch.h" +#include "SHAttachmentDescInitParams.h" + +namespace SHADE +{ + +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.h b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.h new file mode 100644 index 00000000..845a19a4 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescInitParams.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Resource/SHHandle.h" + +namespace SHADE +{ + class SHRenderGraphResource; + + struct SHAttachmentDescInitParams + { + Handle resourceHdl; + bool dontClearOnLoad{false}; + }; +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescriptionType.h b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescriptionType.h new file mode 100644 index 00000000..c4d44ea8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHAttachmentDescriptionType.h @@ -0,0 +1,17 @@ +#pragma once + +namespace SHADE +{ + // Used for attachment description creation for renderpass node + enum class SH_ATT_DESC_TYPE_FLAGS + { + COLOR = 0x01, + COLOR_PRESENT = 0x02, + DEPTH = 0x04, + STENCIL = 0x08, + DEPTH_STENCIL = 0x10, + INPUT = 0x20, + STORAGE = 0x40 + }; + +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp index a86913e4..500bcf04 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp @@ -1,4 +1,4 @@ -#include "SHPch.h" +#include "SHPch.h" #include "SHRenderGraph.h" #include "Graphics/Swapchain/SHVkSwapchain.h" #include "Graphics/Framebuffer/SHVkFramebuffer.h" @@ -6,517 +6,22 @@ #include "Graphics/Images/SHVkImage.h" #include "Graphics/Images/SHVkImageView.h" #include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "Graphics/Buffers/SHVkBuffer.h" #include "Tools/SHLogger.h" +#include "SHAttachmentDescInitParams.h" +#include "SHRenderGraphStorage.h" +#include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" namespace SHADE { - /***************************************************************************/ - /*! - \brief - Non-default ctor for the resource. Using the type of the resource, we - decide whether or not we create a resource or link with a swapchain - resource (image). - - \param logicalDevice - Logical device required to create an image resource if the type is NOT - SH_ATT_DESC_TYPE::COLOR_PRESENT. - - \param swapchain - Swapchain required to get swapchain image if the type IS - SH_ATT_DESC_TYPE::COLOR_PRESENT. - - \param type - Type of the image resource. - - \param format - Format of the image resource. - - \param w - Width of the image resource. - - \param h - Height of the image resource. - - \param levels - Number of mipmap levels of the image resource. - - \param createFlags - Create flags used when an image resource needs to be created. - - */ - /***************************************************************************/ - SHRenderGraphResource::SHRenderGraphResource(Handle const& logicalDevice, Handle const& swapchain, std::string const& name, SH_ATT_DESC_TYPE type, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageCreateFlagBits createFlags) noexcept - : resourceType{ type } - , resourceFormat{ format } - , images{} - , imageViews{} - , width{ w } - , height{ h } - , mipLevels{ levels } - , resourceName{ name } - { - // If the resource type is an arbitrary image and not swapchain image - if (type != SH_ATT_DESC_TYPE::COLOR_PRESENT) - { - vk::ImageAspectFlags imageAspectFlags; - vk::ImageUsageFlags usage = {}; - - // Check the resource type and set image usage flags and image aspect flags accordingly - switch (resourceType) - { - case SH_ATT_DESC_TYPE::COLOR: - usage |= vk::ImageUsageFlagBits::eColorAttachment; - imageAspectFlags |= vk::ImageAspectFlagBits::eColor; - break; - case SH_ATT_DESC_TYPE::DEPTH: - usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; - imageAspectFlags |= vk::ImageAspectFlagBits::eDepth; - break; - case SH_ATT_DESC_TYPE::STENCIL: - usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; - imageAspectFlags |= vk::ImageAspectFlagBits::eStencil; - break; - case SH_ATT_DESC_TYPE::DEPTH_STENCIL: - usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; - imageAspectFlags |= vk::ImageAspectFlagBits::eStencil | vk::ImageAspectFlagBits::eDepth; - break; - } - - // The resource is not a swapchain image, just use the first slot of the vector - images.push_back(logicalDevice->CreateImage(width, height, mipLevels, resourceFormat, usage, createFlags)); - - // prepare image view details - SHImageViewDetails viewDetails - { - .viewType = vk::ImageViewType::e2D, - .format = images[0]->GetImageFormat(), - .imageAspectFlags = imageAspectFlags, - .baseMipLevel = 0, - .mipLevelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1, - }; - - // just 1 image view created - imageViews.push_back(images[0]->CreateImageView(logicalDevice, images[0], viewDetails)); - } - else // if swapchain image resource - { - // Prepare image view details - SHImageViewDetails viewDetails - { - .viewType = vk::ImageViewType::e2D, - .format = swapchain->GetSurfaceFormatKHR().format, - .imageAspectFlags = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .mipLevelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }; - - // We want an image handle for every swapchain image - images.resize(swapchain->GetNumImages()); - imageViews.resize(swapchain->GetNumImages()); - - for (uint32_t i = 0; i < swapchain->GetNumImages(); ++i) - { - images[i] = swapchain->GetSwapchainImage(i); - imageViews[i] = images[i]->CreateImageView(logicalDevice, images[i], viewDetails); - } - } - } - - /***************************************************************************/ - /*! - - \brief - Move ctor for resource. - - \param rhs - The other resource. - - */ - /***************************************************************************/ - SHRenderGraphResource::SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept - : resourceName{ std::move(rhs.resourceName) } - , resourceType{ std::move(rhs.resourceType) } - , images{ std::move(rhs.images) } - , imageViews{ std::move(rhs.imageViews) } - , resourceFormat{ std::move(rhs.resourceFormat) } - , width{ rhs.width } - , height{ rhs.height } - , mipLevels{ rhs.mipLevels } + SHRenderGraph::ResourceInstruction::ResourceInstruction(char const* resourceName, bool dontClearOnLoad /*= false*/) noexcept + : resourceName{ resourceName } + , dontClearOnLoad{ dontClearOnLoad } { } - /***************************************************************************/ - /*! - - \brief - Move assignment operator. - - \param rhs - The other resource. - - \return - - */ - /***************************************************************************/ - SHRenderGraphResource& SHRenderGraphResource::operator=(SHRenderGraphResource&& rhs) noexcept - { - if (this == &rhs) - return *this; - - resourceName = std::move(rhs.resourceName); - resourceType = std::move(rhs.resourceType); - images = std::move(rhs.images); - imageViews = std::move(rhs.imageViews); - resourceFormat = std::move(rhs.resourceFormat); - width = rhs.width; - height = rhs.height; - mipLevels = rhs.mipLevels; - - return *this; - } - - /***************************************************************************/ - /*! - - \brief - Destructor for resource. - - */ - /***************************************************************************/ - SHRenderGraphResource::~SHRenderGraphResource(void) noexcept - { - - } - - /***************************************************************************/ - /*! - - \brief - Subpass non-default constructor. Simply initializes variables. - - \param mapping - Mapping from a resource handle to an attachment reference referencing - the resource. - - \param resources - A mapping from string to render graph resource. - - */ - /***************************************************************************/ - SHRenderGraphNode::SHSubpass::SHSubpass(std::unordered_map const* mapping, std::unordered_map> const* resources) noexcept - : resourceAttachmentMapping{ mapping } - , ptrToResources{ resources } - { - - } - - /***************************************************************************/ - /*! - - \brief - Move constructor for subpass. - - \param rhs - The subpass the move from. - - */ - /***************************************************************************/ - SHRenderGraphNode::SHSubpass::SHSubpass(SHSubpass&& rhs) noexcept - : colorReferences{ std::move(rhs.colorReferences) } - , depthReferences{ std::move(rhs.depthReferences) } - , inputReferences{ std::move(rhs.inputReferences) } - , resourceAttachmentMapping{ rhs.resourceAttachmentMapping } - , ptrToResources{ rhs.ptrToResources } - { - - } - - /***************************************************************************/ - /*! - - \brief - Move assignment operator for subpass. - - \param rhs - subpass to move from. - - */ - /***************************************************************************/ - SHRenderGraphNode::SHSubpass& SHRenderGraphNode::SHSubpass::operator=(SHSubpass&& rhs) noexcept - { - if (this == &rhs) - return *this; - - colorReferences = std::move(rhs.colorReferences); - depthReferences = std::move(rhs.depthReferences); - inputReferences = std::move(rhs.inputReferences); - resourceAttachmentMapping = rhs.resourceAttachmentMapping; - ptrToResources = rhs.ptrToResources; - - return *this; - } - - /***************************************************************************/ - /*! - - \brief - Adds a color output to a subpass. Takes in a string and finds the - attachment index to create the vk::SubpassReference. - - \param resourceToReference - Resource name to find resource to attach. - - */ - /***************************************************************************/ - void SHRenderGraphNode::SHSubpass::AddColorOutput(std::string resourceToReference) noexcept - { - colorReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eColorAttachmentOptimal }); - } - - /***************************************************************************/ - /*! - - \brief - Adds a depth output to a subpass. Takes in a string and finds the - attachment index to create the vk::SubpassReference. - - \param resourceToReference - Resource name to find resource to attach. - - \param attachmentDescriptionType - Depending on the type of the resource, initialize the image layout - appropriately. - - */ - /***************************************************************************/ - void SHRenderGraphNode::SHSubpass::AddDepthOutput(std::string resourceToReference, SH_ATT_DESC_TYPE attachmentDescriptionType) noexcept - { - vk::ImageLayout imageLayout; - switch (attachmentDescriptionType) - { - case SH_ATT_DESC_TYPE::DEPTH: - imageLayout = vk::ImageLayout::eDepthAttachmentOptimal; - break; - case SH_ATT_DESC_TYPE::STENCIL: - imageLayout = vk::ImageLayout::eStencilAttachmentOptimal; - break; - case SH_ATT_DESC_TYPE::DEPTH_STENCIL: - imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - break; - default: - //Invalid - return; - } - depthReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), imageLayout }); - } - - /***************************************************************************/ - /*! - - \brief - Adds a input output to a subpass. Takes in a string and finds the - attachment index to create the vk::SubpassReference. - - \param resourceToReference - Resource name to find resource to attach. - - */ - /***************************************************************************/ - void SHRenderGraphNode::SHSubpass::AddInput(std::string resourceToReference) noexcept - { - inputReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eShaderReadOnlyOptimal }); - } - - /***************************************************************************/ - /*! - - \brief - Creates a renderpass for the node. Uses subpass and attachment - descriptions already configured beforehand in the render graph. - - */ - /***************************************************************************/ - void SHRenderGraphNode::CreateRenderpass(void) noexcept - { - renderpass = logicalDeviceHdl->CreateRenderpass(attachmentDescriptions, spDescs, spDeps); - } - - /***************************************************************************/ - /*! - - \brief - Creates a framebuffer from the images used in the renderpass. - - */ - /***************************************************************************/ - void SHRenderGraphNode::CreateFramebuffer(void) noexcept - { - for (uint32_t i = 0; i < framebuffers.size(); ++i) - { - std::vector> imageViews(attResources.size()); - uint32_t fbWidth = std::numeric_limits::max(); - uint32_t fbHeight = std::numeric_limits::max(); - - for (uint32_t j = 0; j < attResources.size(); ++j) - { - uint32_t imageViewIndex = (attResources[j]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) ? i : 0; - imageViews[j] = attResources[j]->imageViews[imageViewIndex]; - - // We want the minimum dimensions for the framebuffer because the image attachments referenced cannot have dimensions smaller than the framebuffer's - if (fbWidth > attResources[j]->width) - fbWidth = attResources[j]->width; - if (fbHeight > attResources[j]->height) - fbHeight = attResources[j]->height; - } - - - framebuffers[i] = logicalDeviceHdl->CreateFramebuffer(renderpass, imageViews, fbWidth, fbHeight); - } - } - - /***************************************************************************/ - /*! - - \brief - Render Graph node constructor. Note that we do not create the renderpass - yet. This is because layouts of attachment descriptions facilitate image - transitions and we cannot know guarantee layouts until we've seen all - renderpasses and their subpasses in the graph. - - \param swapchain - Swapchain required to query number of images as parameters for number - of framebuffers to create. - - \param attachmentDescriptionTypes - - - \return - - */ - /***************************************************************************/ - SHRenderGraphNode::SHRenderGraphNode(ResourceManager& rm, Handle const& logicalDevice, Handle const& swapchain, std::vector> attRes, std::vector> predecessors, std::unordered_map> const* resources) noexcept - : logicalDeviceHdl{ logicalDevice } - , renderpass{} - , framebuffers{} - , prereqNodes{ std::move(predecessors) } - , attachmentDescriptions{} - , resourceAttachmentMapping{} - , attResources{ std::move(attRes) } - , subpasses{} - , executed{ false } - , configured{ false } - , resourceManager{ rm } - , ptrToResources{ resources } - { - attachmentDescriptions.resize(attResources.size()); - - bool containsSwapchainImage = false; - for (uint32_t i = 0; i < attResources.size(); ++i) - { - // As mentioned above we don't initialize much here because it's dependent on how other renderpasses are configured. - vk::AttachmentDescription& newDesc = attachmentDescriptions[i]; - newDesc.samples = vk::SampleCountFlagBits::e1; - - // We set this to clear first. If later we find out that some predecessor is writing to the same attachment, - // we set the pred's storeOp to eStore and "this" loadOp to eLoad. - newDesc.loadOp = vk::AttachmentLoadOp::eClear; - newDesc.storeOp = vk::AttachmentStoreOp::eDontCare; - - newDesc.stencilLoadOp = vk::AttachmentLoadOp::eClear; - newDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - - newDesc.format = attResources[i]->resourceFormat; - - if (attResources[i]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) - containsSwapchainImage = true; - - resourceAttachmentMapping.try_emplace(attResources[i].GetId().Raw, i); - } - - if (!containsSwapchainImage) - framebuffers.resize(1); - else - framebuffers.resize(swapchain->GetNumImages()); - - // At this point, we could configure framebuffers if we had the renderpass object but we don't so their creation has to be - // deferred to when renderpasses are also created. - } - - SHRenderGraphNode::SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept - : resourceManager{ rhs.resourceManager } - , logicalDeviceHdl{ rhs.logicalDeviceHdl } - , renderpass{ rhs.renderpass } - , framebuffers{ std::move(rhs.framebuffers) } - , prereqNodes{ std::move(rhs.prereqNodes) } - , attachmentDescriptions{ std::move(rhs.attachmentDescriptions) } - , attResources{ std::move(rhs.attResources) } - , subpasses{ std::move(rhs.subpasses) } - , resourceAttachmentMapping{ std::move(rhs.resourceAttachmentMapping) } - , subpassIndexing{ std::move(rhs.subpassIndexing) } - , configured{ rhs.configured } - , executed{ rhs.executed } - , ptrToResources{ rhs.ptrToResources } - { - rhs.renderpass = {}; - } - - SHRenderGraphNode& SHRenderGraphNode::operator=(SHRenderGraphNode&& rhs) noexcept - { - if (&rhs == this) - return *this; - - resourceManager = rhs.resourceManager; - logicalDeviceHdl = rhs.logicalDeviceHdl; - renderpass = rhs.renderpass; - framebuffers = std::move(rhs.framebuffers); - prereqNodes = std::move(rhs.prereqNodes); - attachmentDescriptions = std::move(rhs.attachmentDescriptions); - attResources = std::move(rhs.attResources); - subpasses = std::move(rhs.subpasses); - resourceAttachmentMapping = std::move(rhs.resourceAttachmentMapping); - subpassIndexing = std::move(rhs.subpassIndexing); - ptrToResources = std::move(rhs.ptrToResources); - - rhs.renderpass = {}; - - return *this; - } - - /***************************************************************************/ - /*! - - \brief - Add subpasses to the renderpass and returns a reference to it. - - \param subpassName - Name of the subpass. - - \return - Handle to the new subpass. - - */ - /***************************************************************************/ - Handle SHRenderGraphNode::AddSubpass(std::string subpassName) noexcept - { - // if subpass already exists, don't add. - if (subpassIndexing.contains(subpassName)) - { - SHLOG_ERROR("Subpass already exists."); - return{}; - } - - // Add subpass to container and create mapping for it - subpasses.emplace_back(resourceManager.Create(&resourceAttachmentMapping, ptrToResources)); - subpassIndexing.try_emplace(subpassName, subpasses.size() - 1); - return subpasses.at(subpassIndexing[subpassName]); - } - /***************************************************************************/ /*! @@ -544,17 +49,18 @@ namespace SHADE */ /***************************************************************************/ - void SHRenderGraph::AddResource(std::string resourceName, SH_ATT_DESC_TYPE type, uint32_t w /*= static_cast(-1)*/, uint32_t h /*= static_cast(-1)*/, vk::Format format/* = vk::Format::eB8G8R8A8Unorm*/, uint32_t levels /*= 1*/, vk::ImageCreateFlagBits createFlags /*= {}*/) + void SHRenderGraph::AddResource(std::string resourceName, std::initializer_list typeFlags, uint32_t w /*= static_cast(-1)*/, uint32_t h /*= static_cast(-1)*/, vk::Format format/* = vk::Format::eB8G8R8A8Unorm*/, uint8_t levels /*= 1*/, vk::ImageUsageFlagBits usageFlags/* = {}*/, vk::ImageCreateFlagBits createFlags /*= {}*/) { // If we set to if (w == static_cast(-1) && h == static_cast(-1)) { - w = swapchainHdl->GetSwapchainImage(0)->GetWidth(); - w = swapchainHdl->GetSwapchainImage(0)->GetHeight(); - format = swapchainHdl->GetSurfaceFormatKHR().format; + w = renderGraphStorage->swapchain->GetSwapchainImage(0)->GetWidth(); + h = renderGraphStorage->swapchain->GetSwapchainImage(0)->GetHeight(); + format = renderGraphStorage->swapchain->GetSurfaceFormatKHR().format; } - graphResources.try_emplace(resourceName, resourceManager.Create(logicalDeviceHdl, swapchainHdl, resourceName, type, format, w, h, levels, createFlags)); + auto resource = resourceManager->Create(renderGraphStorage, resourceName, typeFlags, format, w, h, levels, usageFlags, createFlags); + renderGraphStorage->graphResources->try_emplace(resourceName, resource); } /***************************************************************************/ @@ -572,39 +78,42 @@ namespace SHADE // First we want to take all the attachment descriptions and initialize the // finalLayout to whatever layout is specified in the last subpass that references the attachment. - for (auto& node : nodes) + for (uint32_t i = 0; auto& node : nodes) { - // key is handle ID, value is pair (first is initial layout, second is final layout). - std::unordered_map resourceAttLayouts; + // key is handle ID, value is final layout. + std::unordered_map resourceAttFinalLayouts; if (node->subpasses.empty()) { SHLOG_ERROR("Node does not contain a subpass. Cannot configure attachment descriptions as a result. "); return; } + // attempt to get all final layouts for all resources for (auto& subpass : node->subpasses) { for (auto& color : subpass->colorReferences) { - if (node->attResources[color.attachment]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) - resourceAttLayouts[color.attachment] = vk::ImageLayout::ePresentSrcKHR; + // If final renderpass and attachment is a COLOR_PRESENT resource, make resource transition to present after last subpass + if (i == nodes.size() - 1 && (node->attResources[color.attachment]->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT))) + resourceAttFinalLayouts[color.attachment] = vk::ImageLayout::ePresentSrcKHR; else - resourceAttLayouts[color.attachment] = color.layout; + resourceAttFinalLayouts[color.attachment] = color.layout; } for (auto& depth : subpass->depthReferences) - resourceAttLayouts[depth.attachment] = depth.layout; + resourceAttFinalLayouts[depth.attachment] = depth.layout; for (auto& input : subpass->inputReferences) - resourceAttLayouts[input.attachment] = input.layout; + resourceAttFinalLayouts[input.attachment] = input.layout; } - for (uint32_t i = 0; i < node->attachmentDescriptions.size(); ++i) + for (uint32_t j = 0; j < node->attachmentDescriptions.size(); ++j) { - auto& att = node->attachmentDescriptions[i]; + auto& att = node->attachmentDescriptions[j]; att.initialLayout = vk::ImageLayout::eUndefined; - att.finalLayout = resourceAttLayouts[i]; + att.finalLayout = resourceAttFinalLayouts[j]; } + ++i; } // at this point all attachment descs will have their final layouts initialized as if they were standalone and did @@ -634,7 +143,10 @@ namespace SHADE attDesc.loadOp = vk::AttachmentLoadOp::eLoad; predAttDesc.storeOp = vk::AttachmentStoreOp::eStore; - // TODO: Stecil load and store + attDesc.stencilLoadOp = vk::AttachmentLoadOp::eLoad; + attDesc.stencilStoreOp = vk::AttachmentStoreOp::eStore; + + // TODO: Stencil load and store // When an image is done being used in a renderpass, the image layout will end up being the finalLayout // value of the attachment description. We want this to carry over to the next renderpass; specifically @@ -647,16 +159,16 @@ namespace SHADE } /***************************************************************************/ - /*! - + /*! + \brief Configures the supasses; mainly the subpass descriptions and the subpass dependencies involved between subpasses. - - - \return - - */ + + + \return + + */ /***************************************************************************/ void SHRenderGraph::ConfigureSubpasses(void) noexcept { @@ -713,10 +225,18 @@ namespace SHADE for (auto& inputAtt : subpass->inputReferences) { auto resource = node->attResources[inputAtt.attachment]; - if (resource->resourceType == SH_ATT_DESC_TYPE::COLOR) - colorRead |= (1 << i); - else if (resource->resourceType == SH_ATT_DESC_TYPE::DEPTH_STENCIL) - depthRead |= (1 << i); + if (resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::INPUT)) + { + if (resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR) || + resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) + colorRead |= (1 << i); + else if (resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL)) + depthRead |= (1 << i); + } + else + { + SHLOG_ERROR("While configuring subpass, an input reference was detected but the resource to be used is not marked as SH_ATT_DESC_TYPE_FLAGS::INPUT. "); + } } ++i; @@ -780,17 +300,20 @@ namespace SHADE dep.dstAccessMask = dstAccess; dep.srcStageMask = srcStage; + + // initialize input descriptors + node->subpasses[i]->CreateInputDescriptors(); } } } /***************************************************************************/ - /*! - + /*! + \brief Simply loops through all nodes and create renderpasses. - - */ + + */ /***************************************************************************/ void SHRenderGraph::ConfigureRenderpasses(void) noexcept { @@ -801,12 +324,12 @@ namespace SHADE } /***************************************************************************/ - /*! - + /*! + \brief Simply loops through all nodes and create framebuffers. - - */ + + */ /***************************************************************************/ void SHRenderGraph::ConfigureFramebuffers(void) noexcept { @@ -820,7 +343,7 @@ namespace SHADE /*! \brief - Init function. Doesn't do much except initialize device and swapchain + Init function. Doesn't do much except initialize device and swapchain handle. Graph should start out empty. \param swapchain @@ -831,32 +354,62 @@ namespace SHADE */ /***************************************************************************/ - void SHRenderGraph::Init(Handle const& logicalDevice, Handle const& swapchain) noexcept + void SHRenderGraph::Init(std::string graphName, Handle logicalDevice, Handle swapchain) noexcept { - logicalDeviceHdl = logicalDevice; - swapchainHdl = swapchain; + resourceManager = std::make_shared(); + + renderGraphStorage = resourceManager->Create(); + renderGraphStorage->graphResources = resourceManager->Create>>(); + + renderGraphStorage->logicalDevice = logicalDevice; + renderGraphStorage->swapchain = swapchain; + + renderGraphStorage->resourceManager = resourceManager; + renderGraphStorage->descriptorPool = logicalDevice->CreateDescriptorPools(); + + name = std::move(graphName); } /***************************************************************************/ - /*! - + /*! + \brief Default ctor, doesn't do much. - - - \return - - */ + + + \return + + */ /***************************************************************************/ SHRenderGraph::SHRenderGraph(void) noexcept - : logicalDeviceHdl{ } - , swapchainHdl{ } + : renderGraphStorage{} , nodes{} - , graphResources{} - , resourceManager{} - + , resourceManager{nullptr} { + } + SHRenderGraph::SHRenderGraph(SHRenderGraph&& rhs) noexcept + : renderGraphStorage{ rhs.renderGraphStorage } + , nodeIndexing{ std::move(rhs.nodeIndexing) } + , nodes{ std::move(rhs.nodes) } + , resourceManager{ std::move(rhs.resourceManager) } + , name { std::move(rhs.name) } + { + + } + + SHRenderGraph& SHRenderGraph::operator=(SHRenderGraph&& rhs) noexcept + { + if (&rhs == this) + return *this; + + renderGraphStorage = rhs.renderGraphStorage; + nodeIndexing = std::move(rhs.nodeIndexing); + nodes = std::move(rhs.nodes); + resourceManager = std::move(rhs.resourceManager); + name = std::move(rhs.name); + + return *this; } /***************************************************************************/ @@ -877,7 +430,7 @@ namespace SHADE */ /***************************************************************************/ - Handle SHRenderGraph::AddNode(std::string nodeName, std::initializer_list resourceNames, std::initializer_list predecessorNodes) noexcept + SHADE::Handle SHRenderGraph::AddNode(std::string nodeName, std::initializer_list resourceInstruction, std::initializer_list predecessorNodes) noexcept { if (nodeIndexing.contains(nodeName)) { @@ -885,12 +438,19 @@ namespace SHADE return {}; } - std::vector> resources; - for (auto const& name : resourceNames) + std::vector descInitParams; + for (auto const& instruction : resourceInstruction) { // If the resource that the new node is requesting for exists, allow the graph to reference it - if (graphResources.contains(name)) - resources.push_back(graphResources.at(name)); + if (renderGraphStorage->graphResources->contains(instruction.resourceName)) + { + descInitParams.push_back( + { + .resourceHdl = renderGraphStorage->graphResources->at(instruction.resourceName), + .dontClearOnLoad = instruction.dontClearOnLoad, + } + ); + } else { SHLOG_ERROR("Resource doesn't exist in graph yet. Cannot create new node."); @@ -912,9 +472,9 @@ namespace SHADE } } - nodes.emplace_back(resourceManager.Create(resourceManager, logicalDeviceHdl, swapchainHdl, std::move(resources), std::move(predecessors), &graphResources)); - nodeIndexing.emplace(nodeName, nodes.size() - 1); - return nodes.at(nodeIndexing[nodeName]); + auto node = nodes.emplace_back(resourceManager->Create(nodeName, renderGraphStorage, std::move(descInitParams), std::move(predecessors))); + nodeIndexing.emplace(std::move(nodeName), static_cast(nodes.size()) - 1u); + return node; } /***************************************************************************/ @@ -932,10 +492,77 @@ namespace SHADE /***************************************************************************/ void SHRenderGraph::Generate(void) noexcept { + CheckForNodeComputes(); ConfigureAttachmentDescriptions(); ConfigureSubpasses(); ConfigureRenderpasses(); ConfigureFramebuffers(); } + /***************************************************************************/ + /*! + + \brief + This function goes through all renderpasses and checks for existence of + node computes. If they exist, adds dummy subpasses to transition resources + into general. + + */ + /***************************************************************************/ + void SHRenderGraph::CheckForNodeComputes(void) noexcept + { + for (auto& node : nodes) + { + node->AddDummySubpassIfNeeded(); + } + } + + // TODO: The graph scope buffers were meant to bind vertex buffers and index buffers for meshes. Find a + // better way to manage these + void SHRenderGraph::Execute(uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept + { + cmdBuffer->BeginLabeledSegment(name); + for (auto& node : nodes) + node->Execute(cmdBuffer, descPool, frameIndex); + cmdBuffer->EndLabeledSegment(); + } + + void SHRenderGraph::FinaliseBatch(uint32_t frameIndex, Handle descPool) + { + for (auto& node : nodes) + { + node->FinaliseBatch(frameIndex, descPool); + } + } + + void SHRenderGraph::HandleResize(uint32_t newWidth, uint32_t newHeight) noexcept + { + // resize resources + for (auto& [name, resource] : *renderGraphStorage->graphResources) + resource->HandleResize(newWidth, newHeight); + + for (auto& node : nodes) + { + node->HandleResize(); + } + } + + Handle SHRenderGraph::GetNode(std::string const& nodeName) const noexcept + { + if (nodeIndexing.contains(nodeName)) + return nodes[nodeIndexing.at(nodeName)]; + + return {}; + } + + + Handle SHRenderGraph::GetRenderGraphResource(std::string const& resourceName) const noexcept + { + if (renderGraphStorage->graphResources->contains(resourceName)) + { + return renderGraphStorage->graphResources->at(resourceName); + } + return {}; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h index f81e84bd..741cc522 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h @@ -2,10 +2,21 @@ #define SH_RENDER_GRAPH_H #include "Graphics/Renderpass/SHVkRenderpass.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" +#include "SH_API.h" +#include "Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h" +#include "Graphics/MiddleEnd/Batching/SHSuperBatch.h" +#include "../MiddleEnd/Batching/SHBatcher.h" +#include "SHRenderGraphNode.h" +#include "SHSubpass.h" +#include "SHRenderGraphResource.h" +#include "SHRenderGraphNode.h" +#include "SHSubpass.h" +#include "SHAttachmentDescriptionType.h" #include #include +#include namespace SHADE { @@ -15,177 +26,26 @@ namespace SHADE class SHVkImage; class SHVkImageView; class SHVkFramebuffer; + class SHVkCommandPool; + class SHVkCommandBuffer; + class SHRenderGraphNode; + class SHGraphicsGlobalData; + class SHVkDescriptorPool; + class SHRenderGraphStorage; - // Used for attachment description creation for renderpass node - enum class SH_ATT_DESC_TYPE - { - COLOR, - COLOR_PRESENT, - DEPTH, - STENCIL, - DEPTH_STENCIL, - }; - - class SHRenderGraphResource - { - private: - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER VARIABLES */ - /*-----------------------------------------------------------------------*/ - //! Name of the resource - std::string resourceName; - - //! Used for initializing image layouts - SH_ATT_DESC_TYPE resourceType; - - //! The resource itself (this is a vector because if the resource happens - //! to be a swapchain image, then we need however many frames in flight). - //! However when it's not a swapchain image, we want this container to be size 1 - //! because writing to these images will not interfere with images in the previous - //! frame, unlike the swapchain image. - std::vector> images; - - //! Views to resources (vector because same rationale as images. see above). - std::vector> imageViews; - - //! Image format - vk::Format resourceFormat; - - //! width of the resource - uint32_t width; - - //! Height of the resource - uint32_t height; - - //! Number of mipmap levels - uint8_t mipLevels; - public: - /*-----------------------------------------------------------------------*/ - /* CTORS AND DTORS */ - /*-----------------------------------------------------------------------*/ - SHRenderGraphResource(Handle const& logicalDevice, Handle const& swapchain, std::string const& name, SH_ATT_DESC_TYPE type, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageCreateFlagBits createFlags) noexcept; - SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept; - SHRenderGraphResource& operator=(SHRenderGraphResource&& rhs) noexcept; - ~SHRenderGraphResource (void) noexcept; - - friend class SHRenderGraphNode; - friend class SHRenderGraph; - }; - - class SHRenderGraphNode + class SH_API SHRenderGraph { public: - - class SHSubpass + struct ResourceInstruction { - public: - SHSubpass(std::unordered_map const* mapping, std::unordered_map> const* ptrToResources) noexcept; - SHSubpass(SHSubpass&& rhs) noexcept; - SHSubpass& operator=(SHSubpass&& rhs) noexcept; + std::string resourceName; + bool dontClearOnLoad; - void AddColorOutput (std::string resourceToReference) noexcept; - void AddDepthOutput (std::string resourceToReference, SH_ATT_DESC_TYPE attachmentDescriptionType = SH_ATT_DESC_TYPE::DEPTH_STENCIL) noexcept; - void AddInput (std::string resourceToReference) noexcept; - - private: - /*---------------------------------------------------------------------*/ - /* PRIVATE MEMBER VARIABLES */ - /*---------------------------------------------------------------------*/ - //! Color attachments - std::vector colorReferences; - - //! Depth attachments - std::vector depthReferences; - - //! Input attachments - std::vector inputReferences; - - //! For getting attachment reference indices using handles - std::unordered_map const* resourceAttachmentMapping; - - //! Pointer to resources in the render graph (for getting handle IDs) - std::unordered_map> const* ptrToResources; - - friend class SHRenderGraphNode; - friend class SHRenderGraph; + ResourceInstruction (char const* resourceName, bool dontClearOnLoad = false) noexcept; }; private: - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER VARIABLES */ - /*-----------------------------------------------------------------------*/ - ResourceManager& resourceManager; - //! For Vulkan object creation - Handle logicalDeviceHdl; - - //! Each node will have a renderpass and each renderpass will have its own subpasses. - //! These subpasses will execute sequentially. - Handle renderpass; - - //! Framebuffers used in this renderpass. If renderpass contains usage of a swapchain image - //! used for presenting, then we cannot use just 1 framebuffer, we need to have 1 for however many frames in flight. - std::vector> framebuffers; - - //! Nodes that must finish execution before this node is executed will be in this container - std::vector> prereqNodes; - - //! Container of Attachment descriptions - std::vector attachmentDescriptions; - - //! Resources used in this renderpass - std::vector> attResources; - - //! Vector of subpasses - std::vector> subpasses; - - //! Descriptions to pass to renderpass for renderpass creation. We want to keep this here because - std::vector spDescs; - - //! Subpass dependencies for renderpass creation - std::vector spDeps; - - //! For indexing resources fast - std::unordered_map resourceAttachmentMapping; - - //! For indexing subpasses - std::map subpassIndexing; - - //! Pointer to resources in the render graph (for getting handle IDs) - std::unordered_map> const* ptrToResources; - - //! Whether or not the node has finished execution - bool executed; - - //! Whether or not the node has been configured already or not - bool configured; - - - /*-----------------------------------------------------------------------*/ - /* PRIVATE MEMBER FUNCTIONS */ - /*-----------------------------------------------------------------------*/ - void CreateRenderpass (void) noexcept; - void CreateFramebuffer(void) noexcept; - - public: - /*-----------------------------------------------------------------------*/ - /* CTORS AND DTORS */ - /*-----------------------------------------------------------------------*/ - SHRenderGraphNode (ResourceManager& rm, Handle const& logicalDevice, Handle const& swapchain, std::vector> attRes, std::vector> predecessors, std::unordered_map> const* resources) noexcept; - SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept; - SHRenderGraphNode& operator= (SHRenderGraphNode&& rhs) noexcept; - - /*-----------------------------------------------------------------------*/ - /* PUBLIC MEMBER FUNCTIONS */ - /*-----------------------------------------------------------------------*/ - Handle AddSubpass (std::string subpassName) noexcept; - - friend class SHRenderGraph; - }; - - class SHRenderGraph - { - private: /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ @@ -197,10 +57,8 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ - Handle logicalDeviceHdl; - //! swapchain used for querying image count - Handle swapchainHdl; + Handle renderGraphStorage; //! For indexing render graph node container std::map nodeIndexing; @@ -208,25 +66,39 @@ namespace SHADE //! Render graph nodes std::vector> nodes; - //! Resources that exist for the entire render graph - std::unordered_map> graphResources; - //! Resource library for graph handles - ResourceManager resourceManager; + std::shared_ptr resourceManager; + + //! Name of the RenderGraph + std::string name; public: /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ SHRenderGraph (void) noexcept; + SHRenderGraph(SHRenderGraph&& rhs) noexcept; + SHRenderGraph& operator=(SHRenderGraph&& rhs) noexcept; + /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - void Init (Handle const& logicalDevice, Handle const& swapchain) noexcept; - void AddResource(std::string resourceName, SH_ATT_DESC_TYPE type, uint32_t w = static_cast(-1), uint32_t h = static_cast(-1), vk::Format format = vk::Format::eB8G8R8A8Unorm, uint32_t levels = 1, vk::ImageCreateFlagBits createFlags = {}); - Handle AddNode (std::string nodeName, std::initializer_list resourceNames, std::initializer_list predecessorNodes) noexcept; - void Generate (void) noexcept; + void Init (std::string graphName, Handle logicalDevice, Handle swapchain) noexcept; + void AddResource(std::string resourceName, std::initializer_list typeFlags, uint32_t w = static_cast(-1), uint32_t h = static_cast(-1), vk::Format format = vk::Format::eB8G8R8A8Unorm, uint8_t levels = 1, vk::ImageUsageFlagBits usageFlags = {}, vk::ImageCreateFlagBits createFlags = {}); + Handle AddNode (std::string nodeName, std::initializer_list resourceInstruction, std::initializer_list predecessorNodes) noexcept; + + void Generate (void) noexcept; + void CheckForNodeComputes (void) noexcept; + void Execute (uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept; + void FinaliseBatch (uint32_t frameIndex, Handle descPool); + void HandleResize (uint32_t newWidth, uint32_t newHeight) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + Handle GetNode (std::string const& nodeName) const noexcept; + Handle GetRenderGraphResource (std::string const& resourceName) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp new file mode 100644 index 00000000..b3b5b58b --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp @@ -0,0 +1,408 @@ +#include "SHpch.h" +#include "SHRenderGraphNode.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "SHRenderGraphResource.h" +#include "SHSubpass.h" +#include "SHRenderGraphStorage.h" +#include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" +#include "Graphics/SHVkUtil.h" + +namespace SHADE +{ + + /***************************************************************************/ + /*! + + \brief + Creates a renderpass for the node. Uses subpass and attachment + descriptions already configured beforehand in the render graph. + + */ + /***************************************************************************/ + void SHRenderGraphNode::CreateRenderpass(void) noexcept + { + renderpass = graphStorage->logicalDevice->CreateRenderpass(attachmentDescriptions, spDescs, spDeps); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eRenderPass, renderpass->GetVkRenderpass(), "[RenderPass] " + name); + } + + /***************************************************************************/ + /*! + + \brief + Creates a framebuffer from the images used in the renderpass. + + */ + /***************************************************************************/ + void SHRenderGraphNode::CreateFramebuffer(void) noexcept + { + for (uint32_t i = 0; i < framebuffers.size(); ++i) + { + std::vector> imageViews(attResources.size()); + uint32_t fbWidth = std::numeric_limits::max(); + uint32_t fbHeight = std::numeric_limits::max(); + + for (uint32_t j = 0; j < attResources.size(); ++j) + { + uint32_t imageViewIndex = (attResources[j]->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) ? i : 0; + imageViews[j] = attResources[j]->imageViews[imageViewIndex]; + + // We want the minimum dimensions for the framebuffer because the image attachments referenced cannot have dimensions smaller than the framebuffer's + if (fbWidth > attResources[j]->width) + fbWidth = attResources[j]->width; + if (fbHeight > attResources[j]->height) + fbHeight = attResources[j]->height; + } + + + framebuffers[i] = graphStorage->logicalDevice->CreateFramebuffer(renderpass, imageViews, fbWidth, fbHeight); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eFramebuffer, framebuffers[i]->GetVkFramebuffer(), "[Framebuffer] " + name + std::to_string(i)); + } + } + + void SHRenderGraphNode::HandleResize(void) noexcept + { + renderpass->HandleResize(); + + for (uint32_t i = 0; i < framebuffers.size(); ++i) + { + std::vector> imageViews(attResources.size()); + uint32_t fbWidth = std::numeric_limits::max(); + uint32_t fbHeight = std::numeric_limits::max(); + + for (uint32_t j = 0; j < attResources.size(); ++j) + { + uint32_t imageViewIndex = (attResources[j]->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) ? i : 0; + imageViews[j] = attResources[j]->imageViews[imageViewIndex]; + + // We want the minimum dimensions for the framebuffer because the image attachments referenced cannot have dimensions smaller than the framebuffer's + if (fbWidth > attResources[j]->width) + fbWidth = attResources[j]->width; + if (fbHeight > attResources[j]->height) + fbHeight = attResources[j]->height; + } + + framebuffers[i]->HandleResize(renderpass, imageViews, fbWidth, fbHeight); + } + + for (auto& subpass : subpasses) + { + subpass->HandleResize(); + } + + for (auto& nodeCompute : nodeComputes) + { + nodeCompute->HandleResize(); + } + } + + /***************************************************************************/ + /*! + + \brief + Render Graph node constructor. Note that we do not create the renderpass + yet. This is because layouts of attachment descriptions facilitate image + transitions and we cannot know guarantee layouts until we've seen all + renderpasses and their subpasses in the graph. + + \param swapchain + Swapchain required to query number of images as parameters for number + of framebuffers to create. + + \param attachmentDescriptionTypes + + + \return + + */ + /***************************************************************************/ + SHRenderGraphNode::SHRenderGraphNode(std::string nodeName, Handle renderGraphStorage, std::vector attDescInitParams, std::vector> predecessors) noexcept + : graphStorage{ renderGraphStorage} + , renderpass{} + , framebuffers{} + , prereqNodes{ std::move(predecessors) } + , attachmentDescriptions{} + , resourceAttachmentMapping{} + , attResources{ } + , subpasses{} + , executed{ false } + , configured{ false } + , nodeComputes{} + , name { std::move(nodeName) } + { + // pipeline library initialization + pipelineLibrary.Init(graphStorage->logicalDevice); + + // Store all the handles to resources + attResources.reserve (attDescInitParams.size()); + for (auto& param : attDescInitParams) + attResources.push_back(param.resourceHdl); + + // We have as many descriptions as we have resources + attachmentDescriptions.resize(attResources.size()); + + bool containsSwapchainImage = false; + for (uint32_t i = 0; i < attResources.size(); ++i) + { + // As mentioned above we don't initialize much here because it's dependent on how other renderpasses are configured. + vk::AttachmentDescription& newDesc = attachmentDescriptions[i]; + newDesc.samples = vk::SampleCountFlagBits::e1; + + // We set this to clear first. If later we find out that some predecessor is writing to the same attachment, + // we set the pred's storeOp to eStore and "this" loadOp to eLoad. + newDesc.loadOp = vk::AttachmentLoadOp::eClear; + newDesc.storeOp = vk::AttachmentStoreOp::eStore; + + newDesc.stencilLoadOp = vk::AttachmentLoadOp::eClear; + newDesc.stencilStoreOp = vk::AttachmentStoreOp::eStore; + + newDesc.format = attResources[i]->resourceFormat; + + if (attResources[i]->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) + containsSwapchainImage = true; + + resourceAttachmentMapping.try_emplace(attResources[i].GetId().Raw, i); + } + + if (!containsSwapchainImage) + framebuffers.resize(1); + else + framebuffers.resize(graphStorage->swapchain->GetNumImages()); + + // At this point, we could configure framebuffers if we had the renderpass object but we don't so their creation has to be + // deferred to when renderpasses are also created. + } + + SHRenderGraphNode::SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept + : graphStorage{ rhs.graphStorage} + , renderpass{ rhs.renderpass } + , framebuffers{ std::move(rhs.framebuffers) } + , prereqNodes{ std::move(rhs.prereqNodes) } + , attachmentDescriptions{ std::move(rhs.attachmentDescriptions) } + , attResources{ std::move(rhs.attResources) } + , subpasses{ std::move(rhs.subpasses) } + , resourceAttachmentMapping{ std::move(rhs.resourceAttachmentMapping) } + , subpassIndexing{ std::move(rhs.subpassIndexing) } + , configured{ rhs.configured } + , executed{ rhs.executed } + , pipelineLibrary{ std::move(rhs.pipelineLibrary) } + , batcher{ std::move(rhs.batcher) } + , spDescs{ std::move(rhs.spDescs) } + , spDeps{ std::move(rhs.spDeps) } + , nodeComputes{ std::move(rhs.nodeComputes) } + , name { std::move(rhs.name) } + + { + rhs.renderpass = {}; + } + + SHRenderGraphNode& SHRenderGraphNode::operator=(SHRenderGraphNode&& rhs) noexcept + { + if (&rhs == this) + return *this; + + graphStorage = rhs.graphStorage; + renderpass = rhs.renderpass; + framebuffers = std::move(rhs.framebuffers); + prereqNodes = std::move(rhs.prereqNodes); + attachmentDescriptions = std::move(rhs.attachmentDescriptions); + attResources = std::move(rhs.attResources); + subpasses = std::move(rhs.subpasses); + resourceAttachmentMapping = std::move(rhs.resourceAttachmentMapping); + subpassIndexing = std::move(rhs.subpassIndexing); + pipelineLibrary = std::move(rhs.pipelineLibrary); + batcher = std::move(rhs.batcher); + spDescs = std::move(rhs.spDescs); + spDeps = std::move(rhs.spDeps); + nodeComputes = std::move(rhs.nodeComputes); + name = std::move(rhs.name); + + rhs.renderpass = {}; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Add subpasses to the renderpass and returns a reference to it. + + \param subpassName + Name of the subpass. + + \return + Handle to the new subpass. + + */ + /***************************************************************************/ + Handle SHRenderGraphNode::AddSubpass(std::string subpassName) noexcept + { + // if subpass already exists, don't add. + if (subpassIndexing.contains(subpassName)) + { + SHLOG_ERROR("Subpass already exists."); + return{}; + } + + // Add subpass to container and create mapping for it + subpasses.emplace_back + ( + graphStorage->resourceManager->Create + ( + subpassName, + graphStorage, GetHandle(), static_cast(subpasses.size()), + &resourceAttachmentMapping + ) + ); + subpassIndexing.try_emplace(subpassName, static_cast(subpasses.size()) - 1u); + Handle subpass = subpasses.back(); + subpass->Init(*graphStorage->resourceManager); + + // Register the SuperBatch + batcher.RegisterSuperBatch(subpass->GetSuperBatch()); + + return subpass; + } + + Handle SHRenderGraphNode::AddNodeCompute(std::string nodeName, Handle computeShaderModule, std::initializer_list resources, std::unordered_set&& dynamicBufferBindings, float numWorkGroupScale/* = 1.0f*/) noexcept + { + // Look for the required resources in the graph + std::vector> nodeComputeResources{}; + nodeComputeResources.reserve(resources.size()); + + for (auto& resourceName : resources) + { + auto resource = graphStorage->graphResources->at(resourceName); + nodeComputeResources.push_back(resource); + } + + // Create the subpass compute with the resources + auto nodeCompute = graphStorage->resourceManager->Create(std::move(nodeName), graphStorage, computeShaderModule, std::move(nodeComputeResources), std::move (dynamicBufferBindings), nodeComputes.empty()); + nodeComputes.push_back(nodeCompute); + + return nodeCompute; + } + + /***************************************************************************/ + /*! + + \brief + This function checks all node computes and adds a subpass to transition + all needed resources to general. + + */ + /***************************************************************************/ + void SHRenderGraphNode::AddDummySubpassIfNeeded(void) noexcept + { + if (!nodeComputes.empty()) + { + // we save the resource names involved + std::unordered_set resourcesInvolved; + for (auto& compute : nodeComputes) + { + for (auto& resource : compute->resources) + { + resourcesInvolved.emplace(resource->GetName()); + } + } + + // insert them all for a subpass to transition them. This subpass is the last subpass + auto dummySubpass = AddSubpass("dummy"); + for (auto& resource : resourcesInvolved) + { + dummySubpass->AddGeneralInput(resource); + + if (SHVkUtil::IsDepthStencilAttachment(graphStorage->graphResources->at(resource)->GetResourceFormat())) + dummySubpass->AddGeneralDepthOutput(resource); + else + dummySubpass->AddGeneralColorOutput(resource); + } + } + } + + void SHRenderGraphNode::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + { + uint32_t framebufferIndex = (framebuffers.size() > 1) ? frameIndex : 0; + commandBuffer->BeginRenderpass(renderpass, framebuffers[framebufferIndex]); + + for (uint32_t i = 0; i < subpasses.size(); ++i) + { + subpasses[i]->Execute(commandBuffer, descPool, frameIndex); + + // Go to next subpass if not last subpass + if (i != static_cast(subpasses.size()) - 1u) + commandBuffer->NextSubpass(); + } + + commandBuffer->EndRenderpass(); + + + // Execute all subpass computes + for (auto& sbCompute : nodeComputes) + { + sbCompute->Execute(commandBuffer, frameIndex); + } + } + + Handle SHRenderGraphNode::GetOrCreatePipeline(std::pair, Handle> const& vsFsPair, Handle subpass) noexcept + { + // verify subpass exists + if (subpass->GetIndex() >= subpasses.size()) + { + SHLOG_ERROR("Subpass index passed in is not valid. RenderGraphNode does not have that many passes. "); + return {}; + } + + + Handle pipeline = pipelineLibrary.GetGraphicsPipeline(vsFsPair); + if (!pipeline) + { + pipeline = pipelineLibrary.CreateGraphicsPipelines + ( + vsFsPair, + renderpass, + subpass + ); + } + + return pipeline; + } + + void SHRenderGraphNode::FinaliseBatch(uint32_t frameIndex, Handle descPool) + { + batcher.FinaliseBatches(graphStorage->logicalDevice, descPool, frameIndex); + } + + /***************************************************************************/ + /*! + + \brief + Get the renderpass from the node. + + \return + Handle to the renderpass. + + */ + /***************************************************************************/ + Handle SHRenderGraphNode::GetRenderpass(void) const noexcept + { + return renderpass; + } + + Handle SHRenderGraphNode::GetSubpass(std::string_view subpassName) const noexcept + { + return subpasses[subpassIndexing.at(subpassName.data())]; + } + + Handle SHRenderGraphNode::GetResource(uint32_t resourceIndex) const noexcept + { + if (resourceIndex < attResources.size()) + return attResources[resourceIndex]; + else + return {}; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h new file mode 100644 index 00000000..4fdac45c --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include "SHAttachmentDescriptionType.h" +#include "Graphics/SHVulkanIncludes.h" +#include "SH_API.h" +#include "Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h" +#include "Graphics/MiddleEnd/Batching/SHBatcher.h" +#include "SHAttachmentDescInitParams.h" + +namespace SHADE +{ + class SHResourceHub; + class SHVkFramebuffer; + class SHRenderGraphResource; + class SHVkLogicalDevice; + class SHVkRenderpass; + class SHVkDescriptorPool; + class SHGraphicsGlobalData; + class SHRenderGraphStorage; + class SHRenderGraphNodeCompute; + + class SH_API SHRenderGraphNode : public ISelfHandle + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! For Vulkan object creation + //Handle logicalDeviceHdl; + Handle graphStorage; + + //! Each node will have a renderpass and each renderpass will have its own subpasses. + //! These subpasses will execute sequentially. + Handle renderpass; + + //! Framebuffers used in this renderpass. If renderpass contains usage of a swapchain image + //! used for presenting, then we cannot use just 1 framebuffer, we need to have 1 for however many frames in flight. + std::vector> framebuffers; + + //! Nodes that must finish execution before this node is executed will be in this container + std::vector> prereqNodes; + + //! Container of Attachment descriptions + std::vector attachmentDescriptions; + + //! Resources used in this renderpass + std::vector> attResources; + + //! Vector of subpasses + std::vector> subpasses; + + //! Descriptions to pass to renderpass for renderpass creation. We want to keep this here because + std::vector spDescs; + + //! Subpass dependencies for renderpass creation + std::vector spDeps; + + //! For indexing resources fast + std::unordered_map resourceAttachmentMapping; + + //! For indexing subpasses + std::map subpassIndexing; + + //! Every renderpass will require a pipeline library that will contain pipelines compatible with this renderpass + SHPipelineLibrary pipelineLibrary; + + //! Sometimes we want the subpass to do something to the images instead + //! of drawing objects on the image (i.e. compute). + std::vector> nodeComputes; + + //! Whether or not the node has finished execution + bool executed; + + //! Whether or not the node has been configured already or not + bool configured; + + //! Manages batching for this RenderPass + SHBatcher batcher; + + //! Name of this node + std::string name; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void CreateRenderpass(void) noexcept; + void CreateFramebuffer(void) noexcept; + void HandleResize (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHRenderGraphNode(std::string nodeName, Handle renderGraphStorage, std::vector attDescInitParams, std::vector> predecessors) noexcept; + SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept; + SHRenderGraphNode& operator= (SHRenderGraphNode&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + Handle AddSubpass(std::string subpassName) noexcept; + Handle AddNodeCompute(std::string nodeName, Handle computeShaderModule, std::initializer_list resources, std::unordered_set&& dynamicBufferBindings = {}, float numWorkGroupScale = 1.0f) noexcept; + void AddDummySubpassIfNeeded (void) noexcept; + + // TODO: RemoveSubpass() + void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + Handle GetOrCreatePipeline(std::pair, Handle> const& vsFsPair, Handle subpass) noexcept; + void FinaliseBatch(uint32_t frameIndex, Handle descPool); + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + Handle GetRenderpass(void) const noexcept; + Handle GetSubpass(std::string_view subpassName) const noexcept; + Handle GetResource (uint32_t resourceIndex) const noexcept; + friend class SHRenderGraph; + }; + +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp new file mode 100644 index 00000000..f4a103f7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp @@ -0,0 +1,196 @@ +#include "SHpch.h" +#include "SHRenderGraphNodeCompute.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Descriptors/SHVkDescriptorPool.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Pipeline/SHVkPipelineLayout.h" +#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "SHRenderGraphStorage.h" +#include "SHRenderGraphResource.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" + +namespace SHADE +{ + SHRenderGraphNodeCompute::SHRenderGraphNodeCompute(std::string nodeName, Handle graphStorage, Handle computeShaderModule, std::vector>&& subpassComputeResources, std::unordered_set&& dynamicBufferBindings, bool followingEndRP, float inNumWorkGroupScale/* = 1.0f*/) noexcept + : computePipeline{} + , pipelineLayout{} + , resources{} + , groupSizeX{0} + , groupSizeY{0} + , followingEndRenderpass {followingEndRP} + , numWorkGroupScale {std::clamp(inNumWorkGroupScale, 0.0f, 1.0f)} + , computeResource{} + , name { std::move(nodeName) } + { + SHPipelineLayoutParams pipelineLayoutParams + { + .shaderModules = {computeShaderModule}, + .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts(), + .dynamicBufferBindings = std::move(dynamicBufferBindings), + }; + + // Create pipeline layout from parameters + pipelineLayout = graphStorage->logicalDevice->CreatePipelineLayout(pipelineLayoutParams); + + // Create the compute pipeline + computePipeline = graphStorage->logicalDevice->CreateComputePipeline(pipelineLayout); + + // and construct it + computePipeline->ConstructPipeline(); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::ePipelineLayout, pipelineLayout->GetVkPipelineLayout(), "[Compute Pipeline Layout] " + name); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::ePipeline, computePipeline->GetVkPipeline(), "[Compute Pipeline] " + name); + + // save the resources + resources = std::move (subpassComputeResources); + + //Get the descriptor set layouts required to allocate. We only want the ones for allocate because + //global descriptors are already bound in the main system. + auto const& graphResourceLayout = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline()[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE]; + + // Allocate descriptor sets to hold the images for reading (STORAGE_IMAGE) + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + graphResourceDescSets[i] = graphStorage->descriptorPool->Allocate({graphResourceLayout}, { 1 }); +#ifdef _DEBUG + for (auto set : graphResourceDescSets[i]->GetVkHandle()) + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eDescriptorSet, set, "[Descriptor Set] " + name + " #" + std::to_string(i)); +#endif + } + + + auto const& layouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline(); + + if (layouts.size() == SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE + 1) + { + // create compute resources + computeResource = graphStorage->resourceManager->Create(); + auto computeResourceLayout = layouts[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE]; + computeResource->descSet = graphStorage->descriptorPool->Allocate({ computeResourceLayout }, { 1 }); +#ifdef _DEBUG + for (auto set : computeResource->descSet->GetVkHandle()) + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eDescriptorSet, set, "[Descriptor Set] " + name + " Resources"); +#endif + + // Allocate for descriptor offsets + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + computeResource->dynamicOffsets[i].resize(computeResourceLayout->GetNumDynamicOffsetsRequired()); + + // 1st set all start at 0 + for (auto& index : computeResource->dynamicOffsets[0]) + index = 0; + } + + HandleResize(); + } + + void SHRenderGraphNodeCompute::Execute(Handle cmdBuffer, uint32_t frameIndex) noexcept + { + // bind the compute pipeline + cmdBuffer->BindPipeline(computePipeline); + + // bind descriptor sets + cmdBuffer->BindDescriptorSet(graphResourceDescSets[frameIndex], SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, {}); + + if (computeResource) + { + cmdBuffer->BindDescriptorSet(computeResource->descSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, computeResource->dynamicOffsets[frameIndex]); + } + + // dispatch compute + cmdBuffer->ComputeDispatch(groupSizeX, groupSizeY, 1); + + cmdBuffer->PipelineBarrier((followingEndRenderpass) ? vk::PipelineStageFlagBits::eFragmentShader : vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eFragmentShader, + {}, {}, {}, memoryBarriers[frameIndex]); + } + + void SHRenderGraphNodeCompute::HandleResize(void) noexcept + { + // Get the layout for the render graph resource. We can index it this way because the container returned is a container of layouts that includes the global ones + auto pipelineDescSetLayouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline()[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE]; + + // everything below here needs resizing + for (uint32_t frameIndex = 0; frameIndex < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++frameIndex) + { + uint32_t i = 0; + + // loop through bindings and write descriptor sets + for (auto& binding : pipelineDescSetLayouts->GetBindings()) + { + uint32_t imageIndex = (resources[i]->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) ? frameIndex : 0; + + SHVkDescriptorSetGroup::viewSamplerLayout vsl = std::make_tuple(resources[i]->GetImageView(imageIndex), Handle{}, vk::ImageLayout::eGeneral); + graphResourceDescSets[frameIndex]->ModifyWriteDescImage(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint, { &vsl, 1 }); + graphResourceDescSets[frameIndex]->UpdateDescriptorSetImages(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint); + ++i; + } + } + + // Get the group size from the max width and height + uint32_t maxWidth = 0, maxHeight = 0; + for (auto& resource : resources) + { + maxWidth = std::max(resource->GetWidth(), maxWidth); + maxHeight = std::max(resource->GetHeight(), maxHeight); + } + + groupSizeX = maxWidth / workGroupSizeX; + groupSizeY = maxHeight / workGroupSizeY; + + for (uint32_t i = 0; auto& barriers : memoryBarriers) + { + barriers.clear(); + + for (auto& resource : resources) + { + vk::AccessFlags srcAccessMask = (followingEndRenderpass) ? vk::AccessFlagBits::eInputAttachmentRead : (vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); + barriers.push_back(vk::ImageMemoryBarrier + { + .srcAccessMask = srcAccessMask, + .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = resource->GetImage((resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) ? i : 0)->GetVkImage(), + .subresourceRange = vk::ImageSubresourceRange + { + .aspectMask = resource->imageAspectFlags, + .baseMipLevel = 0, + .levelCount = resource->mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + } + }); + } + + ++i; + } + } + + void SHRenderGraphNodeCompute::SetDynamicOffsets(std::span perFrameSizes) noexcept + { + for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) + { + auto dynamicOffsets = computeResource->dynamicOffsets[i]; + for (uint32_t j = 0; j < perFrameSizes.size(); ++j) + dynamicOffsets[j] = perFrameSizes[j] * i; + } + } + + void SHRenderGraphNodeCompute::ModifyWriteDescBufferComputeResource(uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept + { + computeResource->descSet->ModifyWriteDescBuffer(set, binding, buffers, offset, range); + computeResource->descSet->UpdateDescriptorSetBuffer(set, binding); + } + + void SHRenderGraphNodeCompute::ModifyWriteDescImageComputeResource(uint32_t set, uint32_t binding, std::span const& viewSamplerLayouts) noexcept + { + computeResource->descSet->ModifyWriteDescImage(set, binding, viewSamplerLayouts); + computeResource->descSet->UpdateDescriptorSetImages(set, binding); + + } + +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h new file mode 100644 index 00000000..580f018c --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h @@ -0,0 +1,87 @@ +#pragma once + +#include "Resource/SHHandle.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/SHVulkanIncludes.h" +#include +#include +#include + +namespace SHADE +{ + class SHVkPipeline; + class SHVkDescriptorPool; + class SHVkLogicalDevice; + class SHVkPipelineLayout; + class SHRenderGraphStorage; + class SHRenderGraphResource; + class SHVkShaderModule; + class SHVkCommandBuffer; + class SHVkBuffer; + + + class SHRenderGraphNodeCompute + { + private: + // Binding of set SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE + struct ComputeResource + { + //! Descriptor set (initialized by parent class) + Handle descSet {}; + + //! Dynamic offsets into these resources (initialized from the outside). + std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS> dynamicOffsets {}; + }; + + private: + static constexpr uint32_t workGroupSizeX = 16; + static constexpr uint32_t workGroupSizeY = 16; + + //! To run the dispatch command + Handle computePipeline; + + //! Pipeline layout for the pipeline creation + Handle pipelineLayout; + + //! Descriptor set group to hold the images for reading (STORAGE_IMAGE) + std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS> graphResourceDescSets; + + //! Compute resources + Handle computeResource; + + //! vector of resources needed by the subpass compute + std::vector> resources; + + //! X dimension work group size. Should scale with resource size. + uint32_t groupSizeX; + + //! Y dimension work group size + uint32_t groupSizeY; + + float numWorkGroupScale; + + bool followingEndRenderpass; + + std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS> memoryBarriers; + + //! Name of this node + std::string name; + + public: + SHRenderGraphNodeCompute(std::string nodeName, Handle graphStorage, Handle computeShaderModule, std::vector>&& subpassComputeResources, std::unordered_set&& dynamicBufferBindings, bool followingEndRP, float inNumWorkGroupScale = 1.0f) noexcept; + + void Execute (Handle cmdBuffer, uint32_t frameIndex) noexcept; + void HandleResize (void) noexcept; + + void SetDynamicOffsets (std::span perFrameSizes) noexcept; + + void ModifyWriteDescBufferComputeResource (uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept; + void ModifyWriteDescImageComputeResource(uint32_t set, uint32_t binding, std::span const& viewSamplerLayouts) noexcept; + + + friend class SHRenderGraph; + friend class SHRenderGraphNode; + }; +} + diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.cpp new file mode 100644 index 00000000..4d4099c6 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.cpp @@ -0,0 +1,350 @@ +#include "SHpch.h" +#include "SHRenderGraphResource.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/SHVkUtil.h" +#include "SHRenderGraphStorage.h" + +namespace SHADE +{ + + /***************************************************************************/ + /*! + + \brief + Non-default ctor for the resource. Using the type of the resource, we + decide whether or not we create a resource or link with a swapchain + resource (image). + + \param logicalDevice + Logical device required to create an image resource if the type is NOT + SH_ATT_DESC_TYPE::COLOR_PRESENT. + + \param swapchain + Swapchain required to get swapchain image if the type IS + SH_ATT_DESC_TYPE::COLOR_PRESENT. + + \param type + Type of the image resource. + + \param format + Format of the image resource. + + \param w + Width of the image resource. + + \param h + Height of the image resource. + + \param levels + Number of mipmap levels of the image resource. + + \param createFlags + Create flags used when an image resource needs to be created. + + */ + /***************************************************************************/ + SHRenderGraphResource::SHRenderGraphResource(Handle renderGraphStorage, std::string const& name, std::initializer_list typeFlags, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageUsageFlagBits usageFlags, vk::ImageCreateFlagBits createFlags) noexcept + : graphStorage{renderGraphStorage} + , resourceTypeFlags{ } + , resourceFormat{ format } + , images{} + , imageViews{} + , width{ w } + , height{ h } + , mipLevels{ levels } + , resourceName{ name } + { + // If the resource type is an arbitrary image and not swapchain image + if (typeFlags.size() == 1 && *typeFlags.begin() == SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT) + { + resourceTypeFlags |= static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT); + + // Prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = graphStorage->swapchain->GetSurfaceFormatKHR().format, + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + // We want an image handle for every swapchain image + images.resize(graphStorage->swapchain->GetNumImages()); + imageViews.resize(graphStorage->swapchain->GetNumImages()); + + for (uint32_t i = 0; i < graphStorage->swapchain->GetNumImages(); ++i) + { + images[i] = graphStorage->swapchain->GetSwapchainImage(i); + imageViews[i] = images[i]->CreateImageView(graphStorage->logicalDevice, images[i], viewDetails); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eImageView, imageViews[i]->GetImageView(), "[Image View] " + resourceName + " #" + std::to_string(i)); + } + } + else // if swapchain image resource + { + imageAspectFlags = vk::ImageAspectFlags{}; + usage = usageFlags; + + for (auto& type : typeFlags) + { + // store the flags + resourceTypeFlags |= static_cast(type); + + // Check the resource type and set image usage flags and image aspect flags accordingly + switch (type) + { + case SH_ATT_DESC_TYPE_FLAGS::COLOR: + usage |= vk::ImageUsageFlagBits::eColorAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eColor; + break; + case SH_ATT_DESC_TYPE_FLAGS::DEPTH: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eDepth; + break; + case SH_ATT_DESC_TYPE_FLAGS::STENCIL: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eStencil; + break; + case SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eStencil | vk::ImageAspectFlagBits::eDepth; + break; + case SH_ATT_DESC_TYPE_FLAGS::INPUT: + usage |= vk::ImageUsageFlagBits::eInputAttachment; + usage |= vk::ImageUsageFlagBits::eSampled; + break; + case SH_ATT_DESC_TYPE_FLAGS::STORAGE: + usage |= vk::ImageUsageFlagBits::eStorage; + break; + case SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT: + { + SHLOG_ERROR ("COLOR_PRESENT cannot be with other resource type flags. "); + return; + } + } + } + + // The resource is not a swapchain image, just use the first slot of the vector + auto image = graphStorage->logicalDevice->CreateImage(width, height, mipLevels, resourceFormat, usage, createFlags); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eImage, image->GetVkImage(), "[Image] " + resourceName); + images.push_back(image); + + // prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = images[0]->GetImageFormat(), + .imageAspectFlags = imageAspectFlags, + .baseMipLevel = 0, + .mipLevelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + // just 1 image view created + auto imageView = images[0]->CreateImageView(graphStorage->logicalDevice, images[0], viewDetails); + imageViews.push_back(imageView); + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eImageView, imageView->GetImageView(), "[Image View] " + resourceName); + } + } + + /***************************************************************************/ + /*! + + \brief + Move ctor for resource. + + \param rhs + The other resource. + + */ + /***************************************************************************/ + SHRenderGraphResource::SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept + : resourceName{ std::move(rhs.resourceName) } + , resourceTypeFlags{ std::move(rhs.resourceTypeFlags) } + , images{ std::move(rhs.images) } + , imageViews{ std::move(rhs.imageViews) } + , resourceFormat{ std::move(rhs.resourceFormat) } + , width{ rhs.width } + , height{ rhs.height } + , mipLevels{ rhs.mipLevels } + , imageAspectFlags{ rhs.imageAspectFlags } + , graphStorage{rhs.graphStorage} + { + + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator. + + \param rhs + The other resource. + + \return + + */ + /***************************************************************************/ + SHRenderGraphResource& SHRenderGraphResource::operator=(SHRenderGraphResource&& rhs) noexcept + { + if (this == &rhs) + return *this; + + resourceName = std::move(rhs.resourceName); + resourceTypeFlags = std::move(rhs.resourceTypeFlags); + images = std::move(rhs.images); + imageViews = std::move(rhs.imageViews); + resourceFormat = std::move(rhs.resourceFormat); + width = rhs.width; + height = rhs.height; + mipLevels = rhs.mipLevels; + imageAspectFlags = rhs.imageAspectFlags; + graphStorage = rhs.graphStorage; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Destructor for resource. + + */ + /***************************************************************************/ + SHRenderGraphResource::~SHRenderGraphResource(void) noexcept + { + + } + + void SHRenderGraphResource::HandleResize(uint32_t newWidth, uint32_t newHeight) noexcept + { + width = newWidth; + height = newHeight; + + if ((resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) == 0) + { + // prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = images[0]->GetImageFormat(), + .imageAspectFlags = imageAspectFlags, + .baseMipLevel = 0, + .mipLevelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + for (uint32_t i = 0; i < images.size(); ++i) + { + images[i]->HandleResizeFramebufferImage(width, height); + imageViews[i]->ViewNewImage(images[i], viewDetails); + } + } + else + { + // Prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = graphStorage->swapchain->GetSurfaceFormatKHR().format, + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + for (uint32_t i = 0; i < graphStorage->swapchain->GetNumImages(); ++i) + { + images[i] = graphStorage->swapchain->GetSwapchainImage(i); + imageViews[i]->ViewNewImage(images[i], viewDetails); + } + } + } + + void SHRenderGraphResource::TransitionImage(vk::ImageLayout oldLayout, vk::ImageLayout newLayout, Handle commandBuffer, vk::PipelineStageFlagBits srcStage, vk::PipelineStageFlagBits dstStage, uint32_t frameIndex) noexcept + { + vk::ImageMemoryBarrier barrier; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = images[frameIndex]->GetVkImage(), + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; // TODO: Need to change this to base it off image format. + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; // always 1 since we wont have array of images for attachments + + commandBuffer->PipelineBarrier(srcStage, dstStage, {}, {}, {}, {barrier}); + } + + void SHRenderGraphResource::CopyToBuffer(Handle dstBuffer, Handle commandBuffer, uint32_t frameIndex) const noexcept + { + vk::ImageSubresourceLayers subResource + { + .aspectMask = SHVkUtil::IsDepthOnlyFormat(resourceFormat) ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1 + }; + + vk::BufferImageCopy region + { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = subResource, + .imageOffset = vk::Offset3D {0,0,0}, + .imageExtent = vk::Extent3D {width, height, 1} + }; + + commandBuffer->CopyImageToBuffer(images[frameIndex]->GetVkImage(), dstBuffer->GetVkBuffer(), {region}); + } + + vk::Format SHRenderGraphResource::GetResourceFormat(void) const noexcept + { + return resourceFormat; + } + + + uint32_t SHRenderGraphResource::GetWidth(void) const noexcept + { + return width; + } + + uint32_t SHRenderGraphResource::GetHeight(void) const noexcept + { + return height; + } + + Handle SHRenderGraphResource::GetImageView(uint32_t index /*= NON_SWAPCHAIN_RESOURCE_INDEX*/) const noexcept + { + return imageViews [index]; + } + + Handle SHRenderGraphResource::GetImage(uint32_t index /*= NON_SWAPCHAIN_RESOURCE_INDEX*/) const noexcept + { + return images[index]; + } + + uint8_t SHRenderGraphResource::GetMipLevels(void) const noexcept + { + return mipLevels; + } + + std::string SHRenderGraphResource::GetName(void) const noexcept + { + return resourceName; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.h new file mode 100644 index 00000000..e2fc5d8d --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphResource.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include "SHAttachmentDescriptionType.h" +#include +#include "Resource/SHHandle.h" +#include "Graphics/SHVulkanIncludes.h" +#include "SH_API.h" + +namespace SHADE +{ + class SHVkImage; + class SHVkImageView; + class SHVkLogicalDevice; + class SHVkSwapchain; + class SHVkCommandBuffer; + class SHVkBuffer; + class SHRenderGraphStorage; + + static constexpr uint32_t NON_SWAPCHAIN_RESOURCE_INDEX = 0; + + class SH_API SHRenderGraphResource + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Storage from the render graph + Handle graphStorage; + + //! Name of the resource + std::string resourceName; + + //! Used for initializing image layouts + SHRenderGraphResourceFlags resourceTypeFlags; + + //! The resource itself (this is a vector because if the resource happens + //! to be a swapchain image, then we need however many frames in flight). + //! However when it's not a swapchain image, we want this container to be size 1 + //! because writing to these images will not interfere with images in the previous + //! frame, unlike the swapchain image. + std::vector> images; + + //! Views to resources (vector because same rationale as images. see above). + std::vector> imageViews; + + //! Image format + vk::Format resourceFormat; + + //! width of the resource + uint32_t width; + + //! Height of the resource + uint32_t height; + + //! Number of mipmap levels + uint8_t mipLevels; + + //! image aspect flags + vk::ImageAspectFlags imageAspectFlags; + + //! usage flags + vk::ImageUsageFlags usage = {}; + + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHRenderGraphResource(Handle renderGraphStorage, std::string const& name, std::initializer_list typeFlags, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageUsageFlagBits usageFlags, vk::ImageCreateFlagBits createFlags) noexcept; + SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept; + SHRenderGraphResource& operator=(SHRenderGraphResource&& rhs) noexcept; + ~SHRenderGraphResource(void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void HandleResize (uint32_t newWidth, uint32_t newHeight) noexcept; + void TransitionImage(vk::ImageLayout oldLayout, vk::ImageLayout newLayout, Handle commandBuffer, vk::PipelineStageFlagBits srcStage, vk::PipelineStageFlagBits dstStage, uint32_t frameIndex) noexcept; + void CopyToBuffer (Handle dstBuffer, Handle commandBuffer, uint32_t frameIndex = 0) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Format GetResourceFormat (void) const noexcept; + uint32_t GetWidth (void) const noexcept; + uint32_t GetHeight (void) const noexcept; + Handle GetImageView (uint32_t index = NON_SWAPCHAIN_RESOURCE_INDEX) const noexcept; + Handle GetImage (uint32_t index = NON_SWAPCHAIN_RESOURCE_INDEX) const noexcept; + uint8_t GetMipLevels (void) const noexcept; + std::string GetName (void) const noexcept; + + friend class SHRenderGraphNode; + friend class SHRenderGraph; + friend class SHSubpass; + friend class SHRenderGraphNodeCompute; + }; +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h new file mode 100644 index 00000000..54ef705a --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Resource/SHHandle.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkSwapchain; + class SHGraphicsGlobalData; + class SHVkDescriptorPool; + class SHRenderGraphResource; + + class SHRenderGraphStorage + { + //! Logical device for creation of vulkan objects + Handle logicalDevice; + + //! swapchain handle + Handle swapchain; + + //! Resource manager for creation of objects + std::shared_ptr resourceManager; + + //! Descriptor pool for the descriptor sets to be created in the subpasses + Handle descriptorPool; + + //! For accessing resources anywhere in the graph + Handle>> graphResources; + + //SHRenderGraphStorage(void) noexcept; + //SHRenderGraphStorage(SHRenderGraphStorage&& rhs) noexcept; + //SHRenderGraphStorage& operator=(SHRenderGraphStorage&& rhs) noexcept; + + friend class SHRenderGraph; + friend class SHRenderGraphNode; + friend class SHSubpass; + friend class SHRenderGraphResource; + friend class SHRenderGraphNodeCompute; + }; + + +} diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp new file mode 100644 index 00000000..2ed84d92 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp @@ -0,0 +1,426 @@ +#include "SHpch.h" +#include "SHSubpass.h" +#include "Graphics/MiddleEnd/Batching/SHSuperBatch.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "SHRenderGraphNode.h" +#include "SHRenderGraphResource.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "SHRenderGraphNode.h" +#include "SHRenderGraphStorage.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Images/SHVkSampler.h" +#include "SHRenderGraphResource.h" + +namespace SHADE +{ + + /***************************************************************************/ + /*! + + \brief + Subpass non-default constructor. Simply initializes variables. + + \param mapping + Mapping from a resource handle to an attachment reference referencing + the resource. + + \param resources + A mapping from string to render graph resource. + + */ + /***************************************************************************/ + SHSubpass::SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept + : resourceAttachmentMapping{ mapping } + , parentNode{ parent } + , subpassIndex{ index } + , superBatch{} + , colorReferences{} + , depthReferences{} + , inputReferences{} + , name { name } + , graphStorage{ renderGraphStorage } + , inputImageDescriptors {SHGraphicsConstants::NUM_FRAME_BUFFERS} + { + } + + /***************************************************************************/ + /*! + + \brief + Move constructor for subpass. + + \param rhs + The subpass the move from. + + */ + /***************************************************************************/ + SHSubpass::SHSubpass(SHSubpass&& rhs) noexcept + : subpassIndex{ std::move(rhs.subpassIndex) } + , parentNode{ std::move(rhs.parentNode) } + , superBatch{ std::move(rhs.superBatch) } + , colorReferences{ std::move(rhs.colorReferences) } + , depthReferences{ std::move(rhs.depthReferences) } + , inputReferences{ std::move(rhs.inputReferences) } + , resourceAttachmentMapping{ rhs.resourceAttachmentMapping } + , descriptorSetLayout{ rhs.descriptorSetLayout } + , exteriorDrawCalls{ std::move(rhs.exteriorDrawCalls) } + , graphStorage{ rhs.graphStorage } + , inputNames{ std::move(rhs.inputNames) } + , inputImageDescriptors{ std::move(rhs.inputImageDescriptors) } + , inputDescriptorLayout{ rhs.inputDescriptorLayout } + , inputSamplers{ rhs.inputSamplers } + , name { rhs.name } + { + + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator for subpass. + + \param rhs + subpass to move from. + + */ + /***************************************************************************/ + SHSubpass& SHSubpass::operator=(SHSubpass&& rhs) noexcept + { + if (this == &rhs) + return *this; + + subpassIndex = std::move(rhs.subpassIndex); + parentNode = std::move(rhs.parentNode); + superBatch = std::move(rhs.superBatch); + colorReferences = std::move(rhs.colorReferences); + depthReferences = std::move(rhs.depthReferences); + inputReferences = std::move(rhs.inputReferences); + resourceAttachmentMapping = rhs.resourceAttachmentMapping; + descriptorSetLayout = rhs.descriptorSetLayout; + exteriorDrawCalls = std::move(rhs.exteriorDrawCalls); + graphStorage = rhs.graphStorage; + inputNames = std::move(rhs.inputNames); + inputImageDescriptors = std::move(rhs.inputImageDescriptors); + inputDescriptorLayout = rhs.inputDescriptorLayout; + inputSamplers = rhs.inputSamplers; + name = std::move(rhs.name); + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Adds a color output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + */ + /***************************************************************************/ + void SHSubpass::AddColorOutput(std::string resourceToReference) noexcept + { + colorReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eColorAttachmentOptimal }); + } + + void SHSubpass::AddGeneralColorOutput(std::string resourceToReference) noexcept + { + colorReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eGeneral }); + } + + /***************************************************************************/ + /*! + + \brief + Adds a depth output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + \param attachmentDescriptionType + Depending on the type of the resource, initialize the image layout + appropriately. + + */ + /***************************************************************************/ + void SHSubpass::AddDepthOutput(std::string resourceToReference, SH_ATT_DESC_TYPE_FLAGS attachmentDescriptionType) noexcept + { + vk::ImageLayout imageLayout; + switch (attachmentDescriptionType) + { + case SH_ATT_DESC_TYPE_FLAGS::DEPTH: + imageLayout = vk::ImageLayout::eDepthAttachmentOptimal; + break; + case SH_ATT_DESC_TYPE_FLAGS::STENCIL: + imageLayout = vk::ImageLayout::eStencilAttachmentOptimal; + break; + case SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL: + imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + break; + default: + //Invalid + return; + } + depthReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), imageLayout }); + } + + void SHSubpass::AddGeneralDepthOutput(std::string resourceToReference) noexcept + { + depthReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eGeneral }); + + } + + /***************************************************************************/ + /*! + + \brief + Adds a input output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + */ + /***************************************************************************/ + void SHSubpass::AddInput(std::string resourceToReference) noexcept + { + inputReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eShaderReadOnlyOptimal }); + + inputNames.push_back(resourceToReference); + } + + void SHSubpass::AddGeneralInput(std::string resourceToReference) noexcept + { + inputReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eGeneral }); + } + + void SHSubpass::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + { + commandBuffer->BeginLabeledSegment(name); + // Ensure correct transforms are provided + superBatch->UpdateBuffers(frameIndex, descPool); + + // Draw all the batches + superBatch->Draw(commandBuffer, frameIndex); + + // Draw all the exterior draw calls + for (auto& drawCall : exteriorDrawCalls) + { + drawCall(commandBuffer); + } + commandBuffer->EndLabeledSegment(); + } + + void SHSubpass::HandleResize(void) noexcept + { + UpdateWriteDescriptors(); + } + + void SHSubpass::AddExteriorDrawCalls(std::function&)> const& newDrawCall) noexcept + { + exteriorDrawCalls.push_back(newDrawCall); + } + + void SHSubpass::Init(SHResourceHub& resourceManager) noexcept + { + superBatch = resourceManager.Create(GetHandle()); + + } + + void SHSubpass::CreateInputDescriptors(void) noexcept + { + if (inputNames.empty()) + return; + + std::vector bindings{}; + + for (auto& input : inputReferences) + { + SHVkDescriptorSetLayout::Binding newBinding + { + .Type = (input.layout == vk::ImageLayout::eShaderReadOnlyOptimal) ? vk::DescriptorType::eInputAttachment : vk::DescriptorType::eStorageImage, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = static_cast(bindings.size()), + .DescriptorCount = 1, + .flags = {}, + }; + + bindings.push_back(newBinding); + } + + // We build a new descriptor set layout to store our images + inputDescriptorLayout = graphStorage->logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, bindings); + + // we store a sampler if its an input attachment. if it is storage image, no need sampler, store an empty handle. + for (uint32_t i = 0; i < bindings.size(); ++i) + { + if (bindings[i].Type == vk::DescriptorType::eInputAttachment) + { + auto newSampler = graphStorage->logicalDevice->CreateSampler(SHVkSamplerParams + { + .minFilter = vk::Filter::eLinear, + .magFilter = vk::Filter::eLinear, + .addressMode = vk::SamplerAddressMode::eRepeat, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .minLod = -1000, + .maxLod = 1000 + } + ); + + inputSamplers.push_back(newSampler); + } + else + { + inputSamplers.push_back({}); + } + } + + //// maybe do this in handle resize? + //UpdateWriteDescriptors(); + } + + void SHSubpass::UpdateWriteDescriptors(void) noexcept + { + if (inputNames.empty()) + return; + + auto const& bindings = inputDescriptorLayout->GetBindings(); + + std::vector variableCounts{ static_cast(bindings.size()) }; + std::fill (variableCounts.begin(), variableCounts.end(), 0u); + + + // For every frame's descriptor set + for (auto& group : inputImageDescriptors) + { + if (group) + group.Free(); + + group = graphStorage->descriptorPool->Allocate({ inputDescriptorLayout }, variableCounts); +#ifdef _DEBUG + const auto& GROUP_HANDLES = group->GetVkHandle(); + for (int i = 0; i < static_cast(GROUP_HANDLES.size()); ++i) + SET_VK_OBJ_NAME(graphStorage->logicalDevice, vk::ObjectType::eDescriptorSet, GROUP_HANDLES[i], "[Descriptor Set] " + name + " #" + std::to_string(i)); +#endif + + uint32_t i = 0; + for (auto& binding : bindings) + { + // get the resource + auto resource = graphStorage->graphResources->at(inputNames[binding.BindPoint]); + + // If resource is swapchain image, get the correct image, if not just get 0. + uint32_t viewIndex = (resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)) ? i : 0; + + // layout is GENERAL if image is meant to be used as storage image, if not use SHADER_READ_ONLY_OPTINAL + vk::ImageLayout descriptorLayout = (binding.Type == vk::DescriptorType::eStorageImage) ? vk::ImageLayout::eGeneral : vk::ImageLayout::eShaderReadOnlyOptimal; + + // Update descriptor sets + auto args = std::make_tuple(resource->GetImageView(viewIndex), inputSamplers[i], descriptorLayout); + group->ModifyWriteDescImage(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint, std::span{&args, 1}); + group->UpdateDescriptorSetImages(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint); + } + + ++i; + } + } + + //void SHSubpass::InitComputeBarriers(void) noexcept + //{ + // std::unordered_set handleBarriers{}; + + // // we will have swapchainNumImages vectors of vector of barriers + // subpassComputeBarriers.resize(graphStorage->swapchain->GetNumImages()); + + // for (auto sbCompute : subpassComputes) + // { + // // for every resource the subpass compute is using + // for (auto resource : sbCompute->resources) + // { + // // Get the resource handle + // uint64_t resourceRaw = resource.GetId().Raw; + + // // if the barrier is not registered + // if (!handleBarriers.contains(resourceRaw)) + // { + // // If the resource is a swapchain image + // bool isSwapchainImage = (resource->resourceTypeFlags & static_cast(SH_ATT_DESC_TYPE_FLAGS::COLOR_PRESENT)); + // for (uint32_t i = 0; i < graphStorage->swapchain->GetNumImages(); ++i) + // { + // // if swapchain image, we want the index of the swapchain image, if not take base image + // uint32_t imageIndex = isSwapchainImage ? i : 0; + + // // Prepare image barrier + // vk::ImageMemoryBarrier imageBarrier + // { + // .oldLayout = colorReferences[resourceAttachmentMapping->at(resource.GetId().Raw)].layout, + // .newLayout = vk::ImageLayout::eGeneral, + // .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + // .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + // .image = resource->GetImage(imageIndex)->GetVkImage(), + // .subresourceRange = + // { + // .aspectMask = resource->imageAspectFlags, + // .levelCount = resource->GetMipLevels(), + // .baseArrayLayer = 0, + // .layerCount = 1 + // } + // }; + + // // push the barrier + // subpassComputeBarriers[i].push_back(imageBarrier); + // } + + // // Image transition registered + // handleBarriers.emplace(resourceRaw); + // } + // } + // } + //} + + /***************************************************************************/ + /*! + + \brief + Getter for parent renderpass. + + \return + Returns the parent renderpass the subpass belongs to. + + */ + /***************************************************************************/ + Handle const& SHSubpass::GetParentNode(void) const noexcept + { + return parentNode; + } + + SHADE::SHSubPassIndex SHSubpass::GetIndex() const noexcept + { + return subpassIndex; + } + + Handle SHSubpass::GetSuperBatch(void) const noexcept + { + return superBatch; + } + + std::vector const& SHSubpass::GetColorAttachmentReferences(void) const noexcept + { + return colorReferences; + } + + vk::Format SHSubpass::GetFormatFromAttachmentReference(uint32_t attachmentReference) const noexcept + { + return parentNode->GetResource(attachmentReference)->GetResourceFormat(); + } + + const std::string& SHSubpass::GetName() const + { + return name; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h new file mode 100644 index 00000000..c82ebdd0 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h @@ -0,0 +1,128 @@ +#pragma once + +#include "SHAttachmentDescriptionType.h" +#include +#include "SH_API.h" +#include "Resource/SHHandle.h" +#include "Graphics/SHVulkanIncludes.h" + + +namespace SHADE +{ + class SHRenderGraphNode; + class SHSuperBatch; + class SHRenderGraphResource; + class SHVkCommandBuffer; + class SHVkDescriptorSetLayout; + class SHVkDescriptorSetGroup; + class SHVkDescriptorPool; + class SHRenderGraphStorage; + class SHVkShaderModule; + class SHVkSampler; + + class SH_API SHSubpass : public ISelfHandle + { + private: + /*---------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*---------------------------------------------------------------------*/ + Handle graphStorage; + + //! The index of the subpass in the render graph + uint32_t subpassIndex; + + //! The parent renderpass that this subpass belongs to + Handle parentNode; + + //! + Handle superBatch; + + //! Descriptor set layout to hold attachments + Handle descriptorSetLayout; + + //! Color attachments + std::vector colorReferences; + + //! Depth attachments + std::vector depthReferences; + + //! Input attachments + std::vector inputReferences; + + //! This is mainly for when we want to retrieve resources using names. + std::vector inputNames; + + //! For getting attachment reference indices using handles + std::unordered_map const* resourceAttachmentMapping; + + //! Descriptor set group to hold the images for input + std::vector> inputImageDescriptors; + + //! Descriptor set layout for allocating descriptor set for inputs + Handle inputDescriptorLayout; + + std::vector> inputSamplers; + + + ////! subpass compute image barriers. We do this because every frame has a different + ////! swapchain image. If the resource we want to transition is not a swapchain image, + ////! we duplicate the barrier anyway, not much memory wasted. ;) + //std::vector> subpassComputeBarriers{}; + + + //! Sometimes there exists entities that we want to render onto a render target + //! but don't want it to come from the batching system. An example would be ImGUI. + //! For these entities we want to link a function from the outside and draw them + //! after we draw everything from the batch. Because of this, these draw calls + //! are always the last things drawn, so DO NOT USE THIS FUNCTIONALITY FOR ANYTHING + //! COMPLEX. + std::vector&)>> exteriorDrawCalls; + /// For identifying subpasses + std::string name; + + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept; + SHSubpass(SHSubpass&& rhs) noexcept; + SHSubpass& operator=(SHSubpass&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + // Preparation functions + void AddColorOutput(std::string resourceToReference) noexcept; + void AddGeneralColorOutput(std::string resourceToReference) noexcept; + void AddDepthOutput(std::string resourceToReference, SH_ATT_DESC_TYPE_FLAGS attachmentDescriptionType = SH_ATT_DESC_TYPE_FLAGS::DEPTH_STENCIL) noexcept; + void AddGeneralDepthOutput(std::string resourceToReference) noexcept; + void AddInput(std::string resourceToReference) noexcept; + void AddGeneralInput (std::string resourceToReference) noexcept; + void AddExteriorDrawCalls(std::function&)> const& newDrawCall) noexcept; + + // Runtime functions + void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + void HandleResize (void) noexcept; + + void Init(SHResourceHub& resourceManager) noexcept; + + //void InitComputeBarriers (void) noexcept; + void CreateInputDescriptors (void) noexcept; + void UpdateWriteDescriptors (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + Handle const& GetParentNode(void) const noexcept; + SHSubPassIndex GetIndex() const noexcept; + Handle GetSuperBatch(void) const noexcept; + std::vector const& GetColorAttachmentReferences (void) const noexcept; + vk::Format GetFormatFromAttachmentReference (uint32_t attachmentReference) const noexcept; + const std::string& GetName() const; + + friend class SHRenderGraphNode; + friend class SHRenderGraph; + friend class SHSubpass; + }; +} diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h index a44f4f27..8cbf72af 100644 --- a/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h @@ -2,7 +2,7 @@ #define SH_VK_ATTACHMENT_DESC_GEN_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp index 53e3326e..74128ec8 100644 --- a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp @@ -29,14 +29,22 @@ namespace SHADE /***************************************************************************/ SHVkRenderpass::SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::vector const& subpasses) noexcept : logicalDeviceHdl {inLogicalDeviceHdl} + , numAttDescs {static_cast(vkDescriptions.size())} + , vkSubpassDescriptions{} + , vkSubpassDeps{} , clearColors{} + , vkAttachmentDescriptions{} { - // TODO: temporary only - clearColors[0].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; - clearColors[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + for (uint32_t i = 0; i < vkDescriptions.size(); ++i) + { + if (SHVkUtil::IsDepthStencilAttachment(vkDescriptions[i].format)) + clearColors[i].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + else + clearColors[i].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; + } + vk::RenderPassCreateInfo renderPassCreateInfo{}; - std::vector subpassDeps; // For validating the depth ref auto isValidDepthRef = [&](vk::AttachmentReference const& depthRef) -> bool @@ -44,13 +52,16 @@ namespace SHADE return !(depthRef.attachment == static_cast (-1) && depthRef.layout == vk::ImageLayout::eUndefined); }; + for (uint32_t i = 0; i < vkDescriptions.size(); ++i) + vkAttachmentDescriptions[i] = vkDescriptions[i]; + uint32_t subpassIndex = 0; if (!subpasses.empty()) { for (auto& subpass : subpasses) { - subpassDescriptions.emplace_back(); - auto& spDesc = subpassDescriptions.back(); + vkSubpassDescriptions.emplace_back(); + auto& spDesc = vkSubpassDescriptions.back(); spDesc.pColorAttachments = subpass.colorRefs.data(); spDesc.colorAttachmentCount = static_cast(subpass.colorRefs.size()); @@ -82,18 +93,18 @@ namespace SHADE }; // Push a new dependency - subpassDeps.push_back(dependency); + vkSubpassDeps.push_back(dependency); ++subpassIndex; } // Renderpass create info for render pass creation - renderPassCreateInfo.attachmentCount = static_cast(vkDescriptions.size()); - renderPassCreateInfo.pAttachments = vkDescriptions.data(); - renderPassCreateInfo.subpassCount = static_cast(subpassDescriptions.size()); - renderPassCreateInfo.pSubpasses = subpassDescriptions.data(); - renderPassCreateInfo.dependencyCount = static_cast(subpassDeps.size()); - renderPassCreateInfo.pDependencies = subpassDeps.data(); + renderPassCreateInfo.attachmentCount = static_cast(vkAttachmentDescriptions.size()); + renderPassCreateInfo.pAttachments = vkAttachmentDescriptions.data(); + renderPassCreateInfo.subpassCount = static_cast(vkSubpassDescriptions.size()); + renderPassCreateInfo.pSubpasses = vkSubpassDescriptions.data(); + renderPassCreateInfo.dependencyCount = static_cast(vkSubpassDeps.size()); + renderPassCreateInfo.pDependencies = vkSubpassDeps.data(); } // No subpasses passed in, create a default one. @@ -164,24 +175,37 @@ namespace SHADE SHVkRenderpass::SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept : logicalDeviceHdl{ inLogicalDeviceHdl } + , numAttDescs{ static_cast(vkDescriptions.size()) } , clearColors{} + , vkSubpassDescriptions{ } + , vkSubpassDeps{ } { - // TODO: temporary only - clearColors[0].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; - clearColors[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); - - subpassDescriptions.resize (spDescs.size()); - for (uint32_t i = 0; i < subpassDescriptions.size(); ++i) + for (uint32_t i = 0; i < vkDescriptions.size(); ++i) { - subpassDescriptions[i] = spDescs[i]; + if (SHVkUtil::IsDepthStencilAttachment(vkDescriptions[i].format)) + clearColors[i].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + else + clearColors[i].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; } + vkAttachmentDescriptions.resize(vkDescriptions.size()); + for (uint32_t i = 0; i < vkDescriptions.size(); ++i) + vkAttachmentDescriptions[i] = vkDescriptions[i]; + + vkSubpassDescriptions.resize (spDescs.size()); + for (uint32_t i = 0; i < vkSubpassDescriptions.size(); ++i) + vkSubpassDescriptions[i] = spDescs[i]; + + vkSubpassDeps.resize(spDeps.size()); + for (uint32_t i = 0; i < vkSubpassDeps.size(); ++i) + vkSubpassDeps[i] = spDeps[i]; + vk::RenderPassCreateInfo renderPassCreateInfo{}; renderPassCreateInfo.attachmentCount = static_cast(vkDescriptions.size()); renderPassCreateInfo.pAttachments = vkDescriptions.data(); - renderPassCreateInfo.subpassCount = static_cast(subpassDescriptions.size()); - renderPassCreateInfo.pSubpasses = subpassDescriptions.data(); + renderPassCreateInfo.subpassCount = static_cast(vkSubpassDescriptions.size()); + renderPassCreateInfo.pSubpasses = vkSubpassDescriptions.data(); renderPassCreateInfo.dependencyCount = static_cast(spDeps.size()); renderPassCreateInfo.pDependencies = spDeps.data(); @@ -199,8 +223,11 @@ namespace SHADE SHVkRenderpass::SHVkRenderpass(SHVkRenderpass&& rhs) noexcept : vkRenderpass {rhs.vkRenderpass} , logicalDeviceHdl {rhs.logicalDeviceHdl} - , subpassDescriptions {std::move (rhs.subpassDescriptions)} - , clearColors {std::move (rhs.clearColors)} + , vkSubpassDescriptions{ std::move(rhs.vkSubpassDescriptions) } + , vkSubpassDeps{ std::move(rhs.vkSubpassDeps) } + , clearColors{ std::move(rhs.clearColors) } + , vkAttachmentDescriptions{ std::move(rhs.vkAttachmentDescriptions) } + , numAttDescs{rhs.numAttDescs} { rhs.vkRenderpass = VK_NULL_HANDLE; } @@ -213,8 +240,11 @@ namespace SHADE vkRenderpass = rhs.vkRenderpass; logicalDeviceHdl = rhs.logicalDeviceHdl; - subpassDescriptions = std::move(rhs.subpassDescriptions); + vkSubpassDescriptions = std::move(rhs.vkSubpassDescriptions); + vkSubpassDeps = std::move(rhs.vkSubpassDeps); clearColors = std::move(rhs.clearColors); + vkAttachmentDescriptions = std::move(rhs.vkAttachmentDescriptions); + numAttDescs = std::move(rhs.numAttDescs); rhs.vkRenderpass = VK_NULL_HANDLE; @@ -223,10 +253,36 @@ namespace SHADE SHVkRenderpass::~SHVkRenderpass(void) noexcept { - logicalDeviceHdl->GetVkLogicalDevice().destroyRenderPass(vkRenderpass, nullptr); + if (vkRenderpass) + logicalDeviceHdl->GetVkLogicalDevice().destroyRenderPass(vkRenderpass, nullptr); } + void SHVkRenderpass::HandleResize(void) noexcept + { + logicalDeviceHdl->GetVkLogicalDevice().destroyRenderPass(vkRenderpass, nullptr); + + vk::RenderPassCreateInfo renderPassCreateInfo{}; + + renderPassCreateInfo.attachmentCount = static_cast(vkAttachmentDescriptions.size()); + renderPassCreateInfo.pAttachments = vkAttachmentDescriptions.data(); + renderPassCreateInfo.subpassCount = static_cast(vkSubpassDescriptions.size()); + renderPassCreateInfo.pSubpasses = vkSubpassDescriptions.data(); + renderPassCreateInfo.dependencyCount = static_cast(vkSubpassDeps.size()); + renderPassCreateInfo.pDependencies = vkSubpassDeps.data(); + + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createRenderPass(&renderPassCreateInfo, nullptr, &vkRenderpass); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Renderpass. "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Renderpass. "); + } + + } + vk::RenderPass SHVkRenderpass::GetVkRenderpass(void) const noexcept { return vkRenderpass; diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h index 6ffd6c38..c265e603 100644 --- a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h @@ -3,7 +3,7 @@ #include "SHVkAttachDescGen.h" #include "SHVkSubpassParams.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include namespace SHADE @@ -17,7 +17,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* STATIC CONSTEXPR VALUES */ /*-----------------------------------------------------------------------*/ - static constexpr uint32_t NUM_CLEAR_COLORS = 2; + static constexpr uint32_t NUM_CLEAR_COLORS = 10; /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ @@ -29,11 +29,20 @@ namespace SHADE Handle logicalDeviceHdl; //! Container of subpass information used to construct subpasses - std::vector subpassDescriptions; + std::vector vkSubpassDescriptions; + + //! Container of subpass dependencies used to create renderpass + std::vector vkSubpassDeps; + + //! Attachment descriptions + std::vector vkAttachmentDescriptions; //! Clear colors for the color and depth std::array clearColors; + // number of attachment descriptions + uint32_t numAttDescs; + public: /*-----------------------------------------------------------------------*/ /* CTOR AND DTOR */ @@ -46,6 +55,8 @@ namespace SHADE SHVkRenderpass(SHVkRenderpass&& rhs) noexcept; SHVkRenderpass& operator=(SHVkRenderpass&& rhs) noexcept; + void HandleResize (void) noexcept; + /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h index bf789c54..6f722f5a 100644 --- a/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h @@ -2,7 +2,7 @@ #define SH_VK_SUBPASS_PARAMS_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include namespace SHADE diff --git a/SHADE_Engine/src/Graphics/SHVkUtil.cpp b/SHADE_Engine/src/Graphics/SHVkUtil.cpp index 2fa03fbf..a9ac543f 100644 --- a/SHADE_Engine/src/Graphics/SHVkUtil.cpp +++ b/SHADE_Engine/src/Graphics/SHVkUtil.cpp @@ -1,6 +1,10 @@ #include "SHPch.h" #include "SHVkUtil.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" + namespace SHADE { @@ -19,4 +23,123 @@ namespace SHADE IsDepthOnlyFormat(format); } + bool SHVkUtil::IsBlendCompatible(vk::Format format) noexcept + { + // TODO: Update with all formats + switch (format) + { + case vk::Format::eR32Sint: + case vk::Format::eR32G32Sint: + case vk::Format::eR32G32B32Sint: + case vk::Format::eR32G32B32A32Sint: + return false; + case vk::Format::eR32Sfloat: + case vk::Format::eR32G32Sfloat: + case vk::Format::eR32G32B32Sfloat: + case vk::Format::eR32G32B32A32Sfloat: + return true; + } + return false; + } + + uint32_t SHVkUtil::GetBytesPerPixelFromFormat(vk::Format format) noexcept + { + // TODO: Update with all formats + switch (format) + { + case vk::Format::eR32Sint: + case vk::Format::eR32Uint: + case vk::Format::eR32Sfloat: + return 4; + case vk::Format::eR32G32Sint: + case vk::Format::eR32G32Uint: + case vk::Format::eR32G32Sfloat: + return 8; + case vk::Format::eR32G32B32Sint: + case vk::Format::eR32G32B32Uint: + case vk::Format::eR32G32B32Sfloat: + return 12; + case vk::Format::eR32G32B32A32Sint: + case vk::Format::eR32G32B32A32Uint: + case vk::Format::eR32G32B32A32Sfloat: + return 16; + } + return 0; + } + + vk::PipelineBindPoint SHVkUtil::GetPipelineBindPointFromType(SH_PIPELINE_TYPE pipelineType) noexcept + { + switch (pipelineType) + { + case SH_PIPELINE_TYPE::GRAPHICS: + return vk::PipelineBindPoint::eGraphics; + case SH_PIPELINE_TYPE::COMPUTE: + return vk::PipelineBindPoint::eCompute; + case SH_PIPELINE_TYPE::RAY_TRACING: + return vk::PipelineBindPoint::eRayTracingKHR; + default: + return vk::PipelineBindPoint::eGraphics; + } + } + + void SHVkUtil::EnsureBufferAndCopyData(Handle device, Handle cmdBuffer, Handle& bufferHandle, void* src, uint32_t size, vk::BufferUsageFlagBits usage, const std::string& name) + { + if (bufferHandle) + { + // Resize + bufferHandle->ResizeReplace(size, src, size); + } + else + { + // Create new + using BuffUsage = vk::BufferUsageFlagBits; + bufferHandle = device->CreateBuffer + ( + size, + src, + size, + usage | BuffUsage::eTransferDst, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, + name + ); + } + + // Order transfers + bufferHandle->TransferToDeviceResource(cmdBuffer); + } + + void SHVkUtil::EnsureBufferAndCopyHostVisibleData(Handle device, Handle& bufferHandle, void* src, uint32_t size, vk::BufferUsageFlagBits usage, const std::string& name) + { + if (bufferHandle) + { + // Resize + bufferHandle->ResizeReplace(size, src, size); // TODO: Set to host visible method? + } + else + { + // Create new + using BuffUsage = vk::BufferUsageFlagBits; + bufferHandle = device->CreateBuffer + ( + size, + src, + size, + usage, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, + name + ); + } + } + + BindingAndSetHash SHVkUtil::GenBindingSetHash(uint32_t set, uint32_t binding) noexcept + { + // Find the target writeDescSet + BindingAndSetHash writeHash = binding; + writeHash |= static_cast(set) << 32; + + return writeHash; + } + } diff --git a/SHADE_Engine/src/Graphics/SHVkUtil.h b/SHADE_Engine/src/Graphics/SHVkUtil.h index 4513f59c..07e8f02e 100644 --- a/SHADE_Engine/src/Graphics/SHVkUtil.h +++ b/SHADE_Engine/src/Graphics/SHVkUtil.h @@ -3,13 +3,86 @@ #include "SHVulkanIncludes.h" +#include "Resource/SHHandle.h" +#include "Graphics/Pipeline/SHPipelineType.h" + namespace SHADE { + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkCommandBuffer; + class SHVkBuffer; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ class SHVkUtil { - public: - static bool IsDepthOnlyFormat (vk::Format format) noexcept; - static bool IsDepthStencilAttachment(vk::Format format) noexcept; + public: + static bool IsDepthOnlyFormat (vk::Format format) noexcept; + static bool IsDepthStencilAttachment (vk::Format format) noexcept; + static bool IsBlendCompatible (vk::Format format) noexcept; + static uint32_t GetBytesPerPixelFromFormat (vk::Format format) noexcept; + static vk::PipelineBindPoint GetPipelineBindPointFromType (SH_PIPELINE_TYPE pipelineType) noexcept; + + /***********************************************************************************/ + /*! + + \brief + Ensures that the specified bufferHandle contains a non-host visible buffer that + fits the specified size and creates it if it does not exist. This follows by + issuing a transfer command into the created buffer. + + All created buffers will use VMA_MEMORY_USAGE_AUTO and + VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + + \param device + Device used to create the SHVkBuffer. + \param cmdBuffer + CommandBuffer which commands for issuing transferring the data to the GPU. + \param bufferHandle + Reference to the handle that holds any existing buffer or will be used to hold the + created buffer. + \param src + Data to copy from. + \param size + Size of data to copy. + \param usage + Usage flags for the buffer. + + */ + /***********************************************************************************/ + static void EnsureBufferAndCopyData(Handle device, Handle cmdBuffer, Handle& bufferHandle, void* src, uint32_t size, vk::BufferUsageFlagBits usage, const std::string& name = ""); + /***********************************************************************************/ + /*! + + \brief + Ensures that the specified bufferHandle contains a host visible buffer that fits + the specified size and creates it if it does not exist. This follows by copying + the data from the specified source to the buffer. + + All created buffers will use VMA_MEMORY_USAGE_AUTO and + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT. + + \param device + Device used to create the SHVkBuffer. + \param bufferHandle + Reference to the handle that holds any existing buffer or will be used to hold the + created buffer. + \param src + Data to copy from. + \param size + Size of data to copy. + \param usage + Usage flags for the buffer. + + */ + /***********************************************************************************/ + static void EnsureBufferAndCopyHostVisibleData(Handle device, Handle& bufferHandle, void* src, uint32_t size, vk::BufferUsageFlagBits usage, const std::string& name = ""); + + static BindingAndSetHash GenBindingSetHash (uint32_t set, uint32_t binding) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/SHVulkanDefines.h b/SHADE_Engine/src/Graphics/SHVulkanDefines.h index 542f379d..a0a6d57c 100644 --- a/SHADE_Engine/src/Graphics/SHVulkanDefines.h +++ b/SHADE_Engine/src/Graphics/SHVulkanDefines.h @@ -5,7 +5,14 @@ namespace SHADE { + /*-----------------------------------------------------------------------*/ + /* TYPE DEFINTIIONS */ + /*-----------------------------------------------------------------------*/ using SHQueueFamilyIndex = uint32_t; + using BindingAndSetHash = uint64_t; + using SetIndex = uint32_t; + using SHSubPassIndex = uint32_t; + using SHRenderGraphResourceFlags = uint32_t; } diff --git a/SHADE_Engine/src/Graphics/SHVulkanIncludes.h b/SHADE_Engine/src/Graphics/SHVulkanIncludes.h index 472226a0..63aa672b 100644 --- a/SHADE_Engine/src/Graphics/SHVulkanIncludes.h +++ b/SHADE_Engine/src/Graphics/SHVulkanIncludes.h @@ -6,5 +6,6 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #define VULKAN_HPP_NO_NODISCARD_WARNINGS #include +#include "Graphics/SHVulkanDefines.h" #endif diff --git a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp index 2135b9dd..67c83266 100644 --- a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp +++ b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp @@ -1,36 +1,60 @@ #include "SHPch.h" #include "SHShaderBlockInterface.h" +#include "Tools/SHLogger.h" namespace SHADE { void SHShaderBlockInterface::AddVariable(std::string name, Variable&& newVariable) noexcept { - variables.try_emplace (std::move(name), std::move (newVariable)); + // Check if this is already inside + if (variableIndexing.contains(name)) + { + SHLOG_WARNING("[SHShaderBlockInterface] Attempted to specify a variable with a duplicate name. This will be ignored."); + return; + } + variables.emplace_back(std::move(newVariable)); + variableNames.emplace_back(name); + variableIndexing.try_emplace(std::move(name), static_cast(variables.size() - 1)); } SHShaderBlockInterface::Variable const* const SHShaderBlockInterface::GetVariable(std::string const& variableName) const noexcept { - if (variables.contains(variableName)) - return &variables.at(variableName); + if (variableIndexing.contains(variableName)) + return &variables.at(variableIndexing.at(variableName)); return nullptr; } - SHShaderBlockInterface::SHShaderBlockInterface(void) noexcept - : variables{} - , bytesRequired{ 0 } + SHADE::SHShaderBlockInterface::Variable const* const SHShaderBlockInterface::GetVariable(uint32_t index) const noexcept { + if (index < variableIndexing.size()) + return &variables.at(index); + return nullptr; } - SHShaderBlockInterface::SHShaderBlockInterface(SHShaderBlockInterface&& rhs) noexcept - : variables{ std::move(rhs.variables) } - , bytesRequired {std::move (rhs.bytesRequired)} + uint32_t SHShaderBlockInterface::GetVariableIndex(std::string const& variableName) const { - + if (!variableIndexing.contains(variableName)) + throw std::invalid_argument("Attempted to retrieve index to variable that does not exist!"); + + return variableIndexing.at(variableName); } + const std::string& SHShaderBlockInterface::GetVariableName(uint32_t index) const noexcept + { + if (index < variableNames.size()) + return variableNames.at(index); + + return {}; + } + + size_t SHShaderBlockInterface::GetVariableCount() const noexcept + { + return variables.size(); + } + void SHShaderBlockInterface::SetBytesRequired(uint32_t bytes) noexcept { bytesRequired = bytes; @@ -41,21 +65,4 @@ namespace SHADE { return bytesRequired; } - - SHADE::SHShaderBlockInterface& SHShaderBlockInterface::operator=(SHShaderBlockInterface&& rhs) noexcept - { - if (&rhs == this) - return *this; - - variables = std::move(rhs.variables); - bytesRequired = std::move(rhs.bytesRequired); - - return *this; - } - - SHShaderBlockInterface::~SHShaderBlockInterface(void) noexcept - { - variables.clear(); - } - } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h index 38c01bde..8b7ccb97 100644 --- a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h +++ b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h @@ -2,36 +2,46 @@ #include +#include "SH_API.h" + namespace SHADE { - class SHShaderBlockInterface + class SH_API SHShaderBlockInterface { public: struct Variable { + enum class Type + { + OTHER, + FLOAT, + INT, + VECTOR2, + VECTOR3, + VECTOR4 + }; //! Offset of the variable in the block uint32_t offset; + Type type; }; private: - //! container of variable information - std::unordered_map variables; + //! containers of variable information + std::vector variables; + std::vector variableNames; + std::unordered_map variableIndexing; //! bytes required by the block (includes padding). This variable is required - uint32_t bytesRequired; + uint32_t bytesRequired = 0; public: void AddVariable (std::string name, Variable&& newVariable) noexcept; Variable const* const GetVariable (std::string const& variableName) const noexcept; - - /*-----------------------------------------------------------------------*/ - /* CTORS AND DTORS */ - /*-----------------------------------------------------------------------*/ - SHShaderBlockInterface(void) noexcept; - ~SHShaderBlockInterface(void) noexcept; - SHShaderBlockInterface(SHShaderBlockInterface&& rhs) noexcept; - SHShaderBlockInterface& operator=(SHShaderBlockInterface&& rhs) noexcept; + Variable const* const GetVariable(uint32_t index) const noexcept; + uint32_t GetVariableIndex(std::string const& variableName) const; + const std::string& GetVariableName(uint32_t index) const noexcept; + size_t GetVariableCount() const noexcept; /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ diff --git a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp index 8beb7d98..96fa77ab 100644 --- a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp +++ b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp @@ -97,22 +97,61 @@ namespace SHADE switch (member.type_description->op) { case SpvOp::SpvOpTypeFloat: - interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + interfaceHdl->AddVariable + ( + parentVarName + std::string(member.name), + SHShaderBlockInterface::Variable + ( + parentOffset + member.offset, + SHShaderBlockInterface::Variable::Type::FLOAT + ) + ); biggestAlignment = std::max (biggestAlignment, 4u); break; case SpvOp::SpvOpTypeVector: - interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + SHShaderBlockInterface::Variable::Type varType; + switch (dim) + { + case 2: varType = SHShaderBlockInterface::Variable::Type::VECTOR2; break; + case 3: varType = SHShaderBlockInterface::Variable::Type::VECTOR3; break; + case 4: varType = SHShaderBlockInterface::Variable::Type::VECTOR4; break; + default: varType = SHShaderBlockInterface::Variable::Type::OTHER; break; + } + interfaceHdl->AddVariable + ( + parentVarName + std::string(member.name), + SHShaderBlockInterface::Variable(parentOffset + member.offset, varType) + ); if (dim == 3) dim = 4; biggestAlignment = std::max (biggestAlignment, dim * member.type_description->traits.numeric.scalar.width / 8); break; case SpvOp::SpvOpTypeInt: - interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + interfaceHdl->AddVariable + ( + parentVarName + std::string(member.name), + SHShaderBlockInterface::Variable + ( + parentOffset + member.offset, + SHShaderBlockInterface::Variable::Type::INT + ) + ); biggestAlignment = std::max(biggestAlignment, 4u); break; case SpvOp::SpvOpTypeStruct: + case SpvOp::SpvOpTypeRuntimeArray: recurseForInfo(&member, interfaceHdl, member.offset, biggestAlignment, parentVarName + std::string(member.name) + "."); break; + case SpvOp::SpvOpTypeArray: + interfaceHdl->AddVariable(parentVarName + std::string (member.name), + SHShaderBlockInterface::Variable + ( + parentOffset + member.offset, + SHShaderBlockInterface::Variable::Type::OTHER + ) + ); + biggestAlignment = std::max(biggestAlignment, member.size); + break; } } }; @@ -176,6 +215,9 @@ namespace SHADE return vk::DescriptorType::eStorageBufferDynamic; case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: return vk::DescriptorType::eInputAttachment; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE: + return vk::DescriptorType::eStorageImage; + break; default: return vk::DescriptorType::eCombinedImageSampler; break; @@ -189,7 +231,7 @@ namespace SHADE Handle SHShaderDescriptorBindingInfo::GetShaderBlockInterface(uint32_t set, uint32_t binding) const noexcept { - SHShaderDescriptorBindingInfo::BindingAndSetHash hash = binding; + BindingAndSetHash hash = binding; hash |= static_cast(set) << 32; if (blockInterfaces.contains(hash)) return blockInterfaces.at(hash); diff --git a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h index 1250b54f..3986f823 100644 --- a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h +++ b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h @@ -11,9 +11,6 @@ namespace SHADE { struct SHShaderDescriptorBindingInfo { - public: - using BindingAndSetHash = uint64_t; - private: /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ diff --git a/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h index 2973d734..492710f2 100644 --- a/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h +++ b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h @@ -2,7 +2,7 @@ #define SH_VK_SHADER_MODULE_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" #include "SHShaderReflected.h" #include diff --git a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp index 4dd6f9d6..f46d5d17 100644 --- a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp +++ b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp @@ -19,10 +19,14 @@ namespace SHADE vkPresentModes = physicalDeviceHdl->GetVkPhysicalDevice().getSurfacePresentModesKHR(surfaceHdl->GetVkSurface()); if (vkSurfaceFormats.size() == 0) + { SHLOG_ERROR("Failed to get surface formats from the physical device. "); + } if (vkPresentModes.size() == 0) + { SHLOG_ERROR("Failed to get present modes from the physical device. "); + } } vk::SurfaceFormatKHR SHVkSwapchain::ChooseSwapSurfaceFormat(std::vector const& surfaceFormats) const noexcept diff --git a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h index 28bd7a4e..384665d3 100644 --- a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h +++ b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h @@ -4,7 +4,7 @@ #include #include #include "Graphics/SHVulkanIncludes.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" #include "Graphics/Swapchain/SHSwapchainParams.h" namespace SHADE diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h index 02445d42..7c48a5f6 100644 --- a/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h @@ -2,7 +2,7 @@ #define SH_VK_FENCE_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h index 57f7d7df..f03cad65 100644 --- a/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h @@ -2,7 +2,7 @@ #define SH_VK_SEMAPHORE_H #include "Graphics/SHVulkanIncludes.h" -#include "Resource/Handle.h" +#include "Resource/SHHandle.h" namespace SHADE { diff --git a/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h index fd8ee3d1..b7191c21 100644 --- a/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h +++ b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h @@ -18,7 +18,13 @@ namespace SHADE // that a mat2 can be interpreted as (x, y, x, y), (o, o, o, o) instead of (x, y, o, o), (o, o, o, o)? MAT_2D, MAT_3D, - MAT_4D + MAT_4D, + + // integer formats + UINT32_1D, + UINT32_2D, + UINT32_3D, + UINT32_4D, }; struct SHVertexAttribute diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp b/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp index 6ac6672b..22ca5eba 100644 --- a/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp +++ b/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp @@ -1,7 +1,8 @@ #include "SHPch.h" #include "SHWindowMap.h" #include "SHWindow.h" - +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Input/SHInputManager.h" namespace SHADE { @@ -52,13 +53,14 @@ namespace SHADE if (wndData.isFullscreen) { - dwExStyle = WS_EX_APPWINDOW | WS_EX_ACCEPTFILES; + dwExStyle = WS_EX_APPWINDOW; dwStyle = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + dwExStyle |= WS_EX_ACCEPTFILES; } else { - dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; - + dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + dwExStyle |= WS_EX_ACCEPTFILES; if (wndData.frameEnabled) { dwStyle = WNDSTYLE::SHWS_WINDOWED; @@ -85,7 +87,7 @@ namespace SHADE } } - //DPI_AWARENESS_CONTEXT prevDPIContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + DPI_AWARENESS_CONTEXT prevDPIContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); RECT windowRect; windowRect.left = wndData.x; //or CW_USEDEFAULT ? @@ -95,13 +97,16 @@ namespace SHADE AdjustWindowRectEx(&windowRect, dwStyle, false, dwExStyle); //Create window - wndHWND = CreateWindowEx(0, (LPWSTR) wndData.name.c_str(), (LPWSTR)wndData.title.c_str(), dwStyle, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, parent, NULL, hInstance, NULL); - + wndHWND = CreateWindowEx(dwExStyle, (LPWSTR) wndData.name.c_str(), (LPWSTR)wndData.title.c_str(), dwStyle, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, parent, NULL, hInstance, NULL); + if (!wndHWND) { //DWORD err = GetLastError(); return false; } + BOOL help = ChangeWindowMessageFilter (WM_DROPFILES, MSGFLT_ADD); + help &= ChangeWindowMessageFilter (WM_COPYDATA, MSGFLT_ADD); + help &= ChangeWindowMessageFilter (0x0049, MSGFLT_ADD); if (wndData.isVisible) { @@ -145,11 +150,11 @@ namespace SHADE SetWindowText(wndHWND, LPCWSTR(wndData.title.c_str())); } - SHWindow::SHVec2 SHWindow::GetPosition() const + SHWindow::WindowSize SHWindow::GetPosition() const { RECT rect; GetWindowRect(wndHWND, &rect); - return SHVec2(static_cast(rect.left), static_cast(rect.top)); + return WindowSize(static_cast(rect.left), static_cast(rect.top)); } void SHWindow::SetPosition(unsigned x, unsigned y) @@ -159,18 +164,18 @@ namespace SHADE wndData.y = y; } - SHWindow::SHVec2 SHWindow::GetWindowSize() const + SHWindow::WindowSize SHWindow::GetWindowSize() const { RECT rect; GetClientRect(wndHWND, &rect); - return SHVec2(static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)); + return WindowSize(static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)); } - SHWindow::SHVec2 SHWindow::GetCurrentDisplaySize() const + SHWindow::WindowSize SHWindow::GetCurrentDisplaySize() const { unsigned screenWidth = GetSystemMetrics(SM_CXSCREEN); unsigned screenHeight = GetSystemMetrics(SM_CYSCREEN); - return SHVec2(screenWidth, screenHeight); + return WindowSize(screenWidth, screenHeight); } void SHWindow::SetMouseVisible(bool show) @@ -222,7 +227,7 @@ namespace SHADE return true; { MSG Message; - while (PeekMessageW(&Message, NULL, 0, 0, PM_REMOVE)) + while (PeekMessageW(&Message, wndHWND, 0, 0, PM_REMOVE)) { if (WM_QUIT == Message.message) { @@ -254,7 +259,7 @@ namespace SHADE return wndHWND; } - const WindowData SHWindow::GetWindowData() + const WindowData SHWindow::GetWindowData() const noexcept { return wndData; } @@ -270,6 +275,26 @@ namespace SHADE windowResizeCallbacks.erase(callbackid); } + SHWindow::CALLBACKID SHWindow::RegisterWindowCloseCallback(WindowCloseCallbackFn windowCloseCallback) + { + windowCloseCallbacks.try_emplace(windowResizeCallbackCount, windowCloseCallback); + return windowCloseCallbackCount++; + } + + void SHWindow::UnregisterWindowCloseCallback(CALLBACKID const& callbackid) + { + windowCloseCallbacks.erase(callbackid); + } + + void SHWindow::ToggleUnsavedChanges() noexcept + { + unsavedChanges = !unsavedChanges; + std::wstring title = wndData.title; + if(unsavedChanges) + title.append(L"*"); + SetWindowText(wndHWND, title.data()); + } + LRESULT SHWindow::WndProcStatic(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { auto window = windowMap.GetWindow(hwnd); @@ -305,11 +330,13 @@ namespace SHADE case WM_CREATE: OnCreate(hwnd, reinterpret_cast(wparam)); break; + case WM_QUIT: + case WM_CLOSE: case WM_DESTROY: OnDestroy(); return 0; case WM_DROPFILES: - //OnFileDrop(reinterpret_cast(wparam)); + OnFileDrop(reinterpret_cast(wparam)); break; case WM_ENTERSIZEMOVE: case WM_EXITSIZEMOVE: @@ -339,6 +366,11 @@ namespace SHADE OnPosChange(reinterpret_cast(lparam)); break; } + case WM_MOUSEWHEEL: + { + SHInputManager::PollWheelVerticalDelta(wparam); + break; + } default: return ::DefWindowProc(hwnd, msg, wparam, lparam); } @@ -355,19 +387,49 @@ namespace SHADE } + void SHADE::SHWindow::OnClose() + { + for (const auto& callbackFn : windowCloseCallbacks | std::views::values) + { + callbackFn(); + } + } + void SHWindow::OnDestroy() { + OnClose(); + DragAcceptFiles(wndHWND, false); this->Destroy(); } - //void SHWindow::OnFileDrop(HDROP drop) - //{ - //} + void SHWindow::OnFileDrop(HDROP drop) + { + + int const numFiles = static_cast(DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0)); + for(int i = 0; i < numFiles; ++i) + { + //char fileNameBuffer[MAX_PATH]; + std::wstring fileNameBuffer; + fileNameBuffer.reserve(MAX_PATH); + DragQueryFile(drop, static_cast(i), fileNameBuffer.data(), MAX_PATH); + std::string name(fileNameBuffer.begin(), fileNameBuffer.end()); + SHLOG_INFO("Dropped: {}", name) + } + DragFinish(drop); + } void SHWindow::OnSize([[maybe_unused]] UINT msg,[[maybe_unused]] UINT type, SIZE size) { wndData.width = static_cast(size.cx); wndData.height = static_cast(size.cy); + + if (type == SIZE_MINIMIZED) + { + wndData.isMinimised = true; + } + else + wndData.isMinimised = false; + for (auto const& entry : windowResizeCallbacks) { entry.second(static_cast(wndData.width), static_cast(wndData.height)); diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWindow.h b/SHADE_Engine/src/Graphics/Windowing/SHWindow.h index a70058a1..06df9036 100644 --- a/SHADE_Engine/src/Graphics/Windowing/SHWindow.h +++ b/SHADE_Engine/src/Graphics/Windowing/SHWindow.h @@ -2,14 +2,16 @@ #define SH_WINDOW_H #include +#include #include #include #include "SHWindowMap.h" +#include "SH_API.h" namespace SHADE { constexpr uint16_t MAX_BUFFER = 1024; - + enum WNDSTYLE : DWORD { SHWS_WINDOWED = WS_OVERLAPPEDWINDOW, @@ -17,7 +19,7 @@ namespace SHADE }; struct WindowData - { + { unsigned x = 0; unsigned y = 0; @@ -41,12 +43,12 @@ namespace SHADE bool closable = true; bool minimizable = true; - + bool maximizable = true; //bool canFullscreen = true; - unsigned bgColor = WHITE_BRUSH; + unsigned bgColor = DKGRAY_BRUSH; //bool transparent = false; @@ -55,11 +57,13 @@ namespace SHADE bool shadowEnabled = true; bool isVisible = true; - + bool isFullscreen = false; bool modal = false; + bool isMinimised = false; + std::wstring title = L"SHADE ENGINE"; std::wstring name = L"SHADEEngineApp"; @@ -67,11 +71,12 @@ namespace SHADE std::string icoPath = ""; }; - class SHWindow + class SH_API SHWindow { public: - using SHVec2 = std::pair; + using WindowSize = std::pair; typedef std::function WindowResizeCallbackFn; + typedef std::function WindowCloseCallbackFn; typedef uint16_t CALLBACKID; SHWindow(); @@ -87,22 +92,22 @@ namespace SHADE void SetTitle(std::wstring title); - SHVec2 GetPosition() const; + WindowSize GetPosition() const; void SetPosition(unsigned x, unsigned y); //void SetPosition(SHMathVec2U); - SHVec2 GetWindowSize() const; + WindowSize GetWindowSize() const; //Get size of display the window is in (whichever window contains the window origin) - SHVec2 GetCurrentDisplaySize() const; + WindowSize GetCurrentDisplaySize() const; void SetMouseVisible(bool show); void SetMousePosition(unsigned x, unsigned y); //unsigned GetBGColor(); - + void SetBGColor(unsigned color); void Minimize(); @@ -118,11 +123,16 @@ namespace SHADE HWND GetHWND(); - const WindowData GetWindowData(); + const WindowData GetWindowData() const noexcept; CALLBACKID RegisterWindowSizeCallback(WindowResizeCallbackFn); void UnregisterWindowSizeCallback(CALLBACKID const& callbackid); + CALLBACKID RegisterWindowCloseCallback(WindowCloseCallbackFn); + void UnregisterWindowCloseCallback(CALLBACKID const& callbackid); + bool IsMinimized() const { return wndData.isMinimised; } + void ToggleUnsavedChanges() noexcept; + bool IsUnsavedChanges() const noexcept{return unsavedChanges;} protected: static LRESULT CALLBACK WndProcStatic(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); @@ -153,12 +163,16 @@ namespace SHADE HFONT font; std::unordered_map windowResizeCallbacks; + std::unordered_map windowCloseCallbacks; CALLBACKID windowResizeCallbackCount{}; - //TODO: Shift to events abstraction + CALLBACKID windowCloseCallbackCount{}; + + bool unsavedChanges = false; void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + void OnClose(); void OnDestroy(); - //void OnFileDrop(HDROP drop); + void OnFileDrop(HDROP drop); void OnSize(UINT msg, UINT type, SIZE size); void OnPosChange(LPWINDOWPOS pos); void OnPaint(HDC hdc, LPPAINTSTRUCT paint); diff --git a/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h index 0328fecc..bf01dc9c 100644 --- a/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h +++ b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h @@ -3,7 +3,7 @@ #include #include "Graphics/SHVulkanIncludes.h" -#include "Resource/ResourceLibrary.h" +#include "Resource/SHResourceLibrary.h" namespace SHADE { diff --git a/SHADE_Engine/src/Input/SHInputManager.cpp b/SHADE_Engine/src/Input/SHInputManager.cpp new file mode 100644 index 00000000..4849a772 --- /dev/null +++ b/SHADE_Engine/src/Input/SHInputManager.cpp @@ -0,0 +1,823 @@ +/********************************************************************* + * \file SHInputManager.cpp + * \author Ryan Wang Nian Jing + * \brief Definition of input manager. + * Handles input from keyboard and mouse. Soon to include controller. + * + * \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#pragma once +#include +#include "SHInputManager.h" +#include "../Tools/SHException.h" + +namespace SHADE +{ + /*------------------------------------------------------------------------*/ + /* Static defines */ + /*------------------------------------------------------------------------*/ + + bool SHInputManager::controllerInUse = false; + + std::map SHInputManager::bindings; + + unsigned SHInputManager::keyCount = 0; + bool SHInputManager::keys[MAX_KEYS] = {}; + bool SHInputManager::keysLast[MAX_KEYS] = {}; + double SHInputManager::keysHeldTime[MAX_KEYS] = {}; + double SHInputManager::keysReleasedTime[MAX_KEYS] = {}; + + unsigned SHInputManager::keyToggleCount = 0; + bool SHInputManager::keysToggle[MAX_KEYS] = {}; + bool SHInputManager::keysToggleLast[MAX_KEYS] = {}; + double SHInputManager::keysToggleOnTime[MAX_KEYS] = {}; + double SHInputManager::keysToggleOffTime[MAX_KEYS] = {}; + + int SHInputManager::mouseScreenX = 0; + int SHInputManager::mouseScreenY = 0; + int SHInputManager::mouseScreenXLast = 0; + int SHInputManager::mouseScreenYLast = 0; + double SHInputManager::mouseVelocityX = 0; + double SHInputManager::mouseVelocityY = 0; + int SHInputManager::mouseWheelVerticalDelta = 0; + int SHInputManager::mouseWheelVerticalDeltaPoll = 0; + + unsigned char SHInputManager::controllersConnectedCount = 0; + unsigned SHInputManager::controllersInputCount[XUSER_MAX_COUNT] = {}; + unsigned SHInputManager::controllersButtonCount[XUSER_MAX_COUNT] = {}; + short SHInputManager::controllers[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT] = {}; + short SHInputManager::controllersLast[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT] = {}; + double SHInputManager::controllersHeldTime[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT] = {}; + double SHInputManager::controllersReleasedTime[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT]; + + //Internal helper function for splitting between inputs + bool SHInputManager::controllerConsideredHeld(size_t inputIdx, short value) noexcept + { + if (inputIdx >= MAX_CONTROLLER_INPUT) return false; //Bounds check + else + { + if (inputIdx < NUM_CONTROLLER_BUTTON) + { + return static_cast(value); + } + else if (inputIdx < NUM_CONTROLLER_BUTTON + NUM_CONTROLLER_TRIGGER) + { + return (value > XINPUT_GAMEPAD_TRIGGER_THRESHOLD); + } + else if (inputIdx < NUM_CONTROLLER_BUTTON + NUM_CONTROLLER_TRIGGER + NUM_CONTROLLER_TRIGGER) + { + return (std::abs(value) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); + } + else + { + return (std::abs(value) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); + } + } + } + + //Internal helper function for getting normalised input value + double SHInputManager::controllerNormalisedValue(size_t inputIdx, short value) noexcept + { + if (inputIdx >= MAX_CONTROLLER_INPUT) return 0.0; //Bounds check + else + { + if (inputIdx < NUM_CONTROLLER_BUTTON) + { + return static_cast(value); + } + else if (inputIdx < NUM_CONTROLLER_BUTTON + NUM_CONTROLLER_TRIGGER) //8-bit triggers, 0 to 255 + { + return static_cast(value) / static_cast(UCHAR_MAX); + } + else //16-bit thumbsticks, -32768 to 32767 + { + return static_cast(value) / static_cast(SHRT_MAX); + } + } + } + + void SHInputManager::UpdateInput(double dt) noexcept + { + //Keyboard and Mouse Buttons//////////////////////////////////////////////// + //Write to lastKeys + memcpy(keysLast, keys, sizeof(keys)); + + //Poll + unsigned char keyboardState[MAX_KEYS] = {}; + SecureZeroMemory(keyboardState, sizeof(keyboardState)); + //if (GetKeyboardState(keyboardState) == false) return; + bool keyboardStateGot = GetKeyboardState(keyboardState); + SHASSERT(keyboardStateGot, "SHInputManager:GetKeyboardState() failed ({})", GetLastError()); + keyCount = 0; + keyToggleCount = 0; + for (size_t i = 0; i < MAX_KEYS; ++i) + { + //Ignore shift, ctrl and alt since they are split to left and right + if (static_cast(i) == SH_KEYCODE::SHIFT || + static_cast(i) == SH_KEYCODE::CTRL || + static_cast(i) == SH_KEYCODE::ALT) + continue; + + //Pressed state + if (keyboardState[i] & 0b10000000) + { + ++keyCount; + keys[i] = true; + } + else keys[i] = false; + + //Toggle state + if (keyboardState[i] & 0b00000001) + { + ++keyToggleCount; + keysToggle[i] = true; + } + else keysToggle[i] = false; + } + + //Timers + for (size_t i = 0; i < MAX_KEYS; ++i) + { + if (keys[i]) //Key is down + { + if (!keysLast[i]) //Key was just pressed + { + keysHeldTime[i] = 0.0; //Reset timer + } + keysHeldTime[i] += dt; //Tick up + } + else //Key is up + { + if (keysLast[i]) //Key was just released + { + keysReleasedTime[i] = 0.0; //Reset timer + } + keysReleasedTime[i] += dt; //Tick up + } + } + + //Mouse Positioning///////////////////////////////////// + //https://stackoverflow.com/a/6423739 + + //Set last positioning + mouseScreenXLast = mouseScreenX; + mouseScreenYLast = mouseScreenY; + + //Get cursor position, even when it is outside window + POINT p; + GetCursorPos(&p); + mouseScreenX = p.x; + mouseScreenY = p.y; + + //Velocity + mouseVelocityX = static_cast(mouseScreenX - mouseScreenXLast) / dt; + mouseVelocityY = static_cast(mouseScreenY - mouseScreenYLast) / dt; + + //Mouse wheel vertical delta updating + mouseWheelVerticalDelta = 0; + mouseWheelVerticalDelta = mouseWheelVerticalDeltaPoll; + mouseWheelVerticalDeltaPoll = 0; + + //Controllers////////////////////////////////////////////////////////////// + + controllersConnectedCount = 0; + + //Set last controller states + memcpy(controllersLast, controllers, sizeof(controllers)); + + //Reset controller states + SecureZeroMemory(&controllers, sizeof(controllers)); + + //https://learn.microsoft.com/en-us/windows/win32/xinput/getting-started-with-xinput#getting-controller-state + for (DWORD c = 0; c < XUSER_MAX_COUNT; ++c) + { + controllersInputCount[c] = 0; + controllersButtonCount[c] = 0; + + XINPUT_STATE state; + SecureZeroMemory(&state, sizeof(XINPUT_STATE)); + + //Get the state of controller from XInput + DWORD result = XInputGetState(c, &state); + + //Write gamepad data + if (result == ERROR_SUCCESS) + { + ++controllersConnectedCount; + + //DIGITAL BUTTONS/////////////////////////////////////// + + //DPAD UP + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_UP)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_UP)] = 0; + } + + //DPAD DOWN + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_DOWN)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_DOWN)] = 0; + } + + //DPAD LEFT + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_LEFT)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_LEFT)] = 0; + } + + //DPAD RIGHT + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_RIGHT)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::DPAD_RIGHT)] = 0; + } + + //START + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) + { + controllers[c][static_cast(SH_CONTROLLERCODE::START)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::START)] = 0; + } + + //BACK + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) + { + controllers[c][static_cast(SH_CONTROLLERCODE::BACK)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::BACK)] = 0; + } + + //LEFT THUMBSTICK BUTTON + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) + { + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_THUMBSTICK)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_THUMBSTICK)] = 0; + } + + //RIGHT THUMBSTICK BUTTON + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) + { + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_THUMBSTICK)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_THUMBSTICK)] = 0; + } + + //LEFT SHOULDER BUTTON + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) + { + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_SHOULDER)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_SHOULDER)] = 0; + } + + //RIGHT SHOULDER BUTTON + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) + { + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_SHOULDER)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_SHOULDER)] = 0; + } + + //A + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) + { + controllers[c][static_cast(SH_CONTROLLERCODE::A)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::A)] = 0; + } + + //B + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) + { + controllers[c][static_cast(SH_CONTROLLERCODE::B)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::B)] = 0; + } + + //X + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X) + { + controllers[c][static_cast(SH_CONTROLLERCODE::X)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::X)] = 0; + } + + //Y + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) + { + controllers[c][static_cast(SH_CONTROLLERCODE::Y)] = 1; + ++controllersInputCount[c]; + ++controllersButtonCount[c]; + } + else + { + controllers[c][static_cast(SH_CONTROLLERCODE::Y)] = 0; + } + + //8 BIT VALUES (0 - 255)/////////////////////////////////// + + //LEFT TRIGGER + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_TRIGGER)] = state.Gamepad.bLeftTrigger; + if (state.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) + { + ++controllersInputCount[c]; //Registered as held + } + + //RIGHT TRIGGER + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_TRIGGER)] = state.Gamepad.bRightTrigger; + if (state.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) + { + ++controllersInputCount[c]; //Registered as held + } + + //16 BIT VALUES (0 - 65535)//////////////////////////////// + + //LEFT THUMBSTICK X + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_THUMBSTICK_X)] = state.Gamepad.sThumbLX; + if (std::abs(state.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) + { + ++controllersInputCount[c]; + } + + //LEFT THUMBSTICK Y + controllers[c][static_cast(SH_CONTROLLERCODE::LEFT_THUMBSTICK_Y)] = state.Gamepad.sThumbLY; + if (std::abs(state.Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) + { + ++controllersInputCount[c]; + } + + //RIGHT THUMBSTICK X + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_THUMBSTICK_X)] = state.Gamepad.sThumbRX; + if (std::abs(state.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) + { + ++controllersInputCount[c]; + } + + //RIGHT THUMBSTICK Y + controllers[c][static_cast(SH_CONTROLLERCODE::RIGHT_THUMBSTICK_Y)] = state.Gamepad.sThumbRY; + if (std::abs(state.Gamepad.sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) + { + ++controllersInputCount[c]; + } + } + + //Timers and updating if controller is presently in use + for (size_t i = 0; i < MAX_CONTROLLER_INPUT; ++i) + { + if (controllerConsideredHeld(i, controllers[c][i])) //Considered on + { + controllerInUse = true; + if (!controllerConsideredHeld(i, controllersLast[c][i])) //Just on + { + controllersHeldTime[c][i] = 0.0; //Reset timer + } + controllersHeldTime[c][i] += dt; //Tick up + } + else //Considered off + { + if (controllerConsideredHeld(i, controllersLast[c][i])) //Just off + { + controllersReleasedTime[c][i] = 0.0; //Reset timer + } + controllersReleasedTime[c][i] += dt; //Tick up + } + } + } + } + + bool SHInputManager::AnyKeyDown(SH_KEYCODE* firstDetected) noexcept + { + for (size_t i = 0; i < MAX_KEYS; ++i) + { + if (keys[i] && !keysLast[i]) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyKey(SH_KEYCODE* firstDetected) noexcept + { + for (size_t i = 0; i < MAX_KEYS; ++i) + { + if (keys[i]) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyKeyUp(SH_KEYCODE* firstDetected) noexcept + { + for (size_t i = 0; i < MAX_KEYS; ++i) + { + if (!keys[i] && keysLast[i]) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + //Any controller input being held + //For analog, this means going being deadzone values + bool SHInputManager::AnyControllerInput(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < MAX_CONTROLLER_INPUT; ++i) + { + if (controllerConsideredHeld(i, controllers[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyControllerInputDown(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < MAX_CONTROLLER_INPUT; ++i) + { + if (controllerConsideredHeld(i, controllers[cNum][i]) && !controllerConsideredHeld(i, controllersLast[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyControllerInputUp(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < MAX_CONTROLLER_INPUT; ++i) + { + if (!controllerConsideredHeld(i, controllers[cNum][i]) && controllerConsideredHeld(i, controllersLast[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyControllerButton(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < NUM_CONTROLLER_BUTTON; ++i) + { + if (controllerConsideredHeld(i, controllers[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyControllerButtonDown(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < NUM_CONTROLLER_BUTTON; ++i) + { + if (controllerConsideredHeld(i, controllers[cNum][i]) && !controllerConsideredHeld(i, controllersLast[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + bool SHInputManager::AnyControllerButtonUp(SH_CONTROLLERCODE* firstDetected, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + for (size_t i = 0; i < NUM_CONTROLLER_BUTTON; ++i) + { + if (!controllerConsideredHeld(i, controllers[cNum][i]) && controllerConsideredHeld(i, controllersLast[cNum][i])) + { + if (firstDetected) *firstDetected = static_cast(i); + return true; + } + } + return false; + } + + //Only get of largest magnitude + double SHInputManager::GetBindingAxis(std::string bindingName, size_t cNum) noexcept + { + //Over keycodes, prioritise positive + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKey(k)) return 1.0; + } + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKey(k)) return -1.0; + } + + double largestMagnitude = 0.0; + + //Over controllerCodes + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + double newValue = 0.0; + if (GetControllerInput(c, &newValue, nullptr, nullptr, cNum)) + if (std::abs(newValue) > std::abs(largestMagnitude)) largestMagnitude = newValue; + } + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + double newValue = 0.0; + if (GetControllerInput(c, &newValue, nullptr, nullptr, cNum)) + if (std::abs(newValue) > std::abs(largestMagnitude)) largestMagnitude = -newValue; + } + + return largestMagnitude; + } + + bool SHInputManager::GetBindingPositiveButton(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKey(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + if (GetControllerInput(c, nullptr, nullptr, nullptr, cNum)) return true; + } + + return false; + } + + bool SHInputManager::GetBindingNegativeButton(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKey(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + if (GetControllerInput(c, nullptr, nullptr, nullptr, cNum)) return true; + } + + return false; + } + + bool SHInputManager::GetBindingPositiveButtonDown(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKeyDown(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + if (GetControllerInputDown(c, nullptr, cNum)) return true; + } + + return false; + } + + bool SHInputManager::GetBindingNegativeButtonDown(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKeyDown(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + if (GetControllerInputDown(c, nullptr, cNum)) return true; + } + + return false; + } + + bool SHInputManager::GetBindingPositiveButtonUp(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKeyUp(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + if (GetControllerInputUp(c, nullptr, cNum)) return true; + } + + return false; + } + + bool SHInputManager::GetBindingNegativeButtonUp(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return false; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKeyUp(k)) return true; + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + if (GetControllerInputUp(c, nullptr, cNum)) return true; + } + + return false; + } + + //Fetches longest hold time + double SHInputManager::GetBindingPositiveHeldTime(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0.0; + + double maxHeldTime = 0.0; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKeyHeldTime(k) > maxHeldTime) maxHeldTime = GetKeyHeldTime(k); + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + if (GetControllerInputHeldTime(c, cNum) > maxHeldTime) maxHeldTime = GetControllerInputHeldTime(c); + } + + return maxHeldTime; + } + + double SHInputManager::GetBindingNegativeHeldTime(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0.0; + + double maxHeldTime = 0.0; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKeyHeldTime(k) > maxHeldTime) maxHeldTime = GetKeyHeldTime(k); + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + if (GetControllerInputHeldTime(c, cNum) > maxHeldTime) maxHeldTime = GetControllerInputHeldTime(c); + } + + return maxHeldTime; + } + + //Fetches shortest release time + double SHInputManager::GetBindingPositiveReleasedTime(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0.0; + + double minReleaseTime = _HUGE_ENUF; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) + { + if (GetKeyReleasedTime(k) < minReleaseTime) minReleaseTime = GetKeyReleasedTime(k); + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) + { + if (GetControllerInputReleasedTime(c, cNum) < minReleaseTime) minReleaseTime = GetControllerInputReleasedTime(c); + } + + return minReleaseTime; + } + + double SHInputManager::GetBindingNegativeReleasedTime(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0.0; + + double minReleaseTime = _HUGE_ENUF; + + //Over keycodes + for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) + { + if (GetKeyReleasedTime(k) < minReleaseTime) minReleaseTime = GetKeyReleasedTime(k); + } + + //Over controller buttons + for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) + { + if (GetControllerInputReleasedTime(c, cNum) < minReleaseTime) minReleaseTime = GetControllerInputReleasedTime(c); + } + + return minReleaseTime; + } + + //Only for mouse movement + //Get largest delta + double SHInputManager::GetBindingMouseVelocity(std::string bindingName, size_t cNum) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0.0; + + //Mouse velocity + double velX = 0.0; + double velY = 0.0; + GetMouseVelocity(&velX, &velY); + + return bindings[bindingName].mouseXPositiveMultiplier * velX + bindings[bindingName].mouseYPositiveMultiplier * velY; + } + +} //namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Input/SHInputManager.h b/SHADE_Engine/src/Input/SHInputManager.h new file mode 100644 index 00000000..04e5871d --- /dev/null +++ b/SHADE_Engine/src/Input/SHInputManager.h @@ -0,0 +1,959 @@ +/********************************************************************* + * \file SHInputManager.h + * \author Ryan Wang Nian Jing + * \brief Declaration of input manager. + * Handles input from keyboard and mouse. Soon to include controller. + * + * \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#pragma once +#include +#include +#include +#include "../../SHADE_Managed/src/SHpch.h" +#include "SH_API.h" +#pragma comment(lib, "xinput.lib") + +namespace SHADE +{ + class SH_API SHInputManager + { + public: + + public: + /*------------------------------------------------------------------------*/ + /* Enumerations */ + /*------------------------------------------------------------------------*/ + enum class SH_KEYCODE + { + LMB = 0X01, + RMB = 0X02, + CANCEL = 0X03, + MMB = 0X04, + XMB1 = 0X05, + XMB2 = 0X06, + + BACKSPACE = 0X08, + TAB = 0X09, + + CLEAR = 0X0C, + ENTER = 0X0D, + + SHIFT = 0X10, //USE LEFT OR RIGHT SHIFT INSTEAD + CTRL = 0X11, //USE LEFT OR RIGHT CTRL INSTEAD + ALT = 0X12, //USE LEFT OR RIGHT ALT INSTEAD + PAUSE = 0X13, + CAPS_LOCK = 0X14, + IME_KANA = 0X15, + IME_HANGUL = 0X15, + IME_ON = 0X16, + IME_JUNJA = 0X17, + IME_FINAL = 0X18, + IME_HANJA = 0X19, + IME_KANJI = 0X19, + IME_OFF = 0X1A, + ESCAPE = 0X1B, + IME_CONVERT = 0X1C, + IME_NONCONVERT = 0X1D, + IME_ACCEPT = 0X1E, + IME_MODECHANGE = 0X1F, + SPACE = 0X20, + PAGE_UP = 0X21, + PAGE_DOWN = 0X22, + END = 0X23, + HOME = 0X24, + LEFT_ARROW = 0X25, + UP_ARROW = 0X26, + RIGHT_ARROW = 0X27, + DOWN_ARROW = 0X28, + SELECT = 0X29, + PRINT = 0X2A, + EXECUTE = 0X2B, + PRINT_SCREEN = 0X2C, + INSERT = 0X2D, + DEL = 0X2E, + HELP = 0X2F, + + NUMBER_0 = 0X30, + NUMBER_1, + NUMBER_2, + NUMBER_3, + NUMBER_4, + NUMBER_5, + NUMBER_6, + NUMBER_7, + NUMBER_8, + NUMBER_9, + + A = 0X41, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + LEFT_WINDOWS = 0X5B, + RIGHT_WINDOWS, + APPS, + + SLEEP = 0X5F, + + NUMPAD_0 = 0X60, + NUMPAD_1, + NUMPAD_2, + NUMPAD_3, + NUMPAD_4, + NUMPAD_5, + NUMPAD_6, + NUMPAD_7, + NUMPAD_8, + NUMPAD_9, + + MULTIPLY = 0X6A, + ADD, + SEPARATOR, + SUBTRACT, + DECIMAL, + DIVIDE, + + F1 = 0X70, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + NUM_LOCK = 0X90, + SCROLL_LOCK = 0X91, + + OEM_PC98_NUMPAD_EQUAL = 0X92, + OEM_FUJITSU_DICTIONARY = 0X92, + OEM_FUJITSU_UNREGISTER, + OEM_FUJITSU_REGISTER, + OEM_FUJITSU_LEFT_THUMB, + OEM_FUJITSU_RIGHT_THUMB, + + LEFT_SHIFT = 0XA0, + RIGHT_SHIFT, + LEFT_CTRL, + RIGHT_CTRL, + LEFT_ALT, + RIGHT_ALT, + BROWSER_BACK, + BROWSER_FORWARD, + BROWSER_REFRESH, + BROWSER_STOP, + BROWSER_SEARCH, + BROWSER_FAVOURITES, + BROWSER_HOME, + VOLUME_MUTE, + VOLUME_DOWN, + VOLUME_UP, + MEDIA_NEXT_TRACK, + MEDIA_PREVIOUS_TRACK, + MEDIA_STOP, + MEDIA_PLAY_PAUSE, + LAUNCH_MAIL, + LAUNCH_MEDIA_SELECT, + LAUNCH_APP_1, + LAUNCH_APP_2, + + OEM_1 = 0XBA, + OEM_PLUS, + OEM_COMMA, + OEM_MINUS, + OEM_PERIOD, + OEM_2, + OEM_3, + + GAMEPAD_A = 0XC3, + GAMEPAD_B, + GAMEPAD_X, + GAMEPAD_Y, + GAMEPAD_RIGHTSHOULDER, + GAMEPAD_LEFTSHOULDER, + GAMEPAD_LEFTTRIGGER, + GAMEPAD_RIGHTTRIGGER, + GAMEPAD_DPAD_UP, + GAMEPAD_DPAD_DOWN, + GAMEPAD_DPAD_LEFT, + GAMEPAD_DPAD_RIGHT, + GAMEPAD_MENU, + GAMEPAD_VIEW, + GAMEPAD_LEFT_THUMBSTICK_BUTTON, + GAMEPAD_RIGHT_THUMBSTICK_BUTTON, + GAMEPAD_LEFT_THUMBSTICK_UP, + GAMEPAD_LEFT_THUMBSTICK_DOWN, + GAMEPAD_LEFT_THUMBSTICK_RIGHT, + GAMEPAD_LEFT_THUMBSTICK_LEFT, + GAMEPAD_RIGHT_THUMBSTICK_UP, + GAMEPAD_RIGHT_THUMBSTICK_DOWN, + GAMEPAD_RIGHT_THUMBSTICK_RIGHT, + GAMEPAD_RIGHT_THUMBSTICK_LEFT, + + OEM_4, + OEM_5, + OEM_6, + OEM_7, + OEM_8, + + OEM_AX = 0XE1, + OEM_102, + OEM_ICO_HELP, + OEM_ICO_00, + IME_PROCESS, + OEM_ICO_CLEAR, + PACKET, + + OEM_RESET = 0XE9, + OEM_JUMP, + OEM_PA1, + OEM_PA2, + OEM_PA3, + OEM_WSCTRL, + OEM_CUSEL, + OEM_ATTN, + OEM_FINISH, + OEM_COPY, + OEM_AUTO, + OEM_ENLW, + OEM_BACKTAB, + + ATTN, + CRSEL, + EXSEL, + EREOF, + PLAY, + ZOOM, + NONAME, + PA_1, + OEM_CLEAR + }; + + enum class SH_CONTROLLERCODE + { + //Digital + DPAD_UP, + DPAD_DOWN, + DPAD_LEFT, + DPAD_RIGHT, + START, + BACK, + LEFT_THUMBSTICK, + RIGHT_THUMBSTICK, + LEFT_SHOULDER, + RIGHT_SHOULDER, + A, + B, + X, + Y, + + //1 Byte Unsigned Analog + LEFT_TRIGGER, + RIGHT_TRIGGER, + + //2 Byte Signed Analog + LEFT_THUMBSTICK_X, + LEFT_THUMBSTICK_Y, + RIGHT_THUMBSTICK_X, + RIGHT_THUMBSTICK_Y + }; + + private: + /*------------------------------------------------------------------------*/ + /* Struct for logical bindings */ + /*------------------------------------------------------------------------*/ + struct SH_API SHLogicalBindingData + { + //Key codes mapped to positive + std::set positiveKeyCodes; + + //Key codes mapped to negative + std::set negativeKeyCodes; + + //Controller Codes mapped to positive + std::set positiveControllerCodes; + + //Controller Codes mapped to negative + std::set negativeControllerCodes; + + //Mouse movement mapped to axes? + double mouseXPositiveMultiplier; + double mouseYPositiveMultiplier; + }; + + public: + //Updates current state of the input, with dt to be fetched from FRC + //TODO should dt be fixed or variable? + static void UpdateInput(double dt) noexcept; + + /*------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*------------------------------------------------------------------------*/ + SHInputManager() noexcept = default; + ~SHInputManager() noexcept = default; + + SHInputManager(const SHInputManager&) = delete; + SHInputManager(SHInputManager&&) = delete; + + SHInputManager& operator= (const SHInputManager&) = delete; + SHInputManager& operator= (SHInputManager&&) = delete; + + /*------------------------------------------------------------------------*/ + /* Member Functions */ + /*------------------------------------------------------------------------*/ + + //Needs to be linked to WM_MOUSEWHEEL in wndProc + static inline void PollWheelVerticalDelta(WPARAM wParam) noexcept + { + mouseWheelVerticalDeltaPoll += GET_WHEEL_DELTA_WPARAM(wParam); + } + + //For testing purposes + //static void PrintCurrentState() noexcept; + + /*------------------------------------------------------------------------*/ + /* Input state accessors (KB & M) */ + /*------------------------------------------------------------------------*/ + //Get how many keys are presently down + static inline unsigned GetKeyCount() noexcept + { + return keyCount; + } + + //How many keys are presently toggled + static inline unsigned GetKeyToggleCount() noexcept + { + return keyToggleCount; + } + + //Any key pressed in THIS FRAME ONLY + //Keys being held beforehand don't count + //Output parameter is which key was first to be detected + static bool AnyKeyDown(SH_KEYCODE* firstKey = nullptr) noexcept; + + //Any key is being held down + //Return false if no key being held + //Output parameter is which key was first to be detected + static bool AnyKey(SH_KEYCODE* firstKey = nullptr) noexcept; + + //Any key released in THIS FRAME ONLY + //Keys that are released beforehand don't count + //Output parameter is which key was first to be detected + static bool AnyKeyUp(SH_KEYCODE* firstKey = nullptr) noexcept; + + //Check if a particular key was pressed down in THIS FRAME ONLY + //Keys being held beforehand don't count + //Output parameter is how long the key has been released for prior + static inline bool GetKeyDown (SH_KEYCODE key, + double* releasedTime = nullptr) noexcept + { + if (releasedTime) *releasedTime = keysReleasedTime[static_cast(key)]; + return (keys[static_cast(key)] && !keysLast[static_cast(key)]); + } + + //Check if a particular key was toggled on in THIS FRAME ONLY + //Keys that stay toggled on afterwards don't count + //Output parameter is how long the key has been toggled off for prior + static inline bool GetKeyToggleOn (SH_KEYCODE key, + double* toggleOffTime = nullptr) noexcept + { + if (toggleOffTime) + *toggleOffTime = keysToggleOffTime[static_cast(key)]; + return (keysToggle[static_cast(key)] && + !keysToggleLast[static_cast(key)]); + } + + //Check if a particular key is presently being held down on + //Output parameter is how long the key has been held and released + static inline bool GetKey(SH_KEYCODE key, + double* heldTime = nullptr, double* releasedTime = nullptr) noexcept + { + if (heldTime) *heldTime = keysHeldTime[static_cast(key)]; + if (releasedTime) *releasedTime = keysReleasedTime[static_cast(key)]; + return keys[static_cast(key)]; + } + + //Check if a particular key is presently toggled on + //Output parameter is how long the key has been toggled on and off + static inline bool GetKeyToggle(SH_KEYCODE key, + double* onTime = nullptr, double* offTime = nullptr) noexcept + { + if (onTime) *onTime = keysToggleOnTime[static_cast(key)]; + if (offTime) *offTime = keysToggleOffTime[static_cast(key)]; + return keysToggle[static_cast(key)]; + } + + //Check if a particular key was released in THIS FRAME ONLY + //Keys already released beforehand don't count + //Output parameter is how long the key has been held for prior + static inline bool GetKeyUp(SH_KEYCODE key, + double* heldTime = nullptr) noexcept + { + if (heldTime) *heldTime = keysHeldTime[static_cast(key)]; + return (!keys[static_cast(key)] && keysLast[static_cast(key)]); + } + + //Check if a particular key was toggled off in THIS FRAME ONLY + //Keys that stay toggled off afterwards don't count + //Output parameter is how long the key has been toggled on for prior + static inline bool GetKeyToggleOff(SH_KEYCODE key, + double* toggleOnTime = nullptr) noexcept + { + if (toggleOnTime) + *toggleOnTime = keysToggleOnTime[static_cast(key)]; + return (!keysToggle[static_cast(key)] && + keysToggleLast[static_cast(key)]); + } + + //Mouse///////////////////////////////////////////////////// + + //Get the mouse location with respect to the screen + static inline void GetMouseScreenPosition (int* x = nullptr, + int* y = nullptr) noexcept + { + if (x) *x = mouseScreenX; + if (y) *y = mouseScreenY; + } + + //Get the mouse location with respect to current window + static inline void GetMouseWindowPosition (int* x = nullptr, + int* y = nullptr) noexcept + { + POINT p{ mouseScreenX, mouseScreenY }; + ScreenToClient(GetActiveWindow(), &p); + if (x) *x = p.x; + if (y) *y = p.y; + } + + //Get the mouse velocity + //Two output parameters for x and y velocitites + //In pixels per second for both + static inline void GetMouseVelocity(double* x = nullptr, + double* y = nullptr) noexcept + { + if (x) *x = mouseVelocityX; + if (y) *y = mouseVelocityY; + } + + //Get the mouse wheel vertical delta + static inline int GetMouseWheelVerticalDelta() noexcept + { + return mouseWheelVerticalDelta; + } + + /*------------------------------------------------------------------------*/ + /* Input state accessors (KB & M) */ + /*------------------------------------------------------------------------*/ + + //How many controller inputs of any kind are being used now + static inline unsigned GetControllerInputCount(size_t cNum = 0) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0; + return controllersInputCount[cNum]; + } + + //How many controller buttons are being pressed now + //Subtract from getControllerInputCount() for analog triggers / thumbsticks + static inline unsigned GetControllerButtonCount(size_t cNum = 0) noexcept + { + if (cNum >= XUSER_MAX_COUNT) return 0; + return controllersButtonCount[cNum]; + } + + //Any controller input being held + //For analog, this means going being deadzone values + //controllerNum should be between 0 and 3 + static bool AnyControllerInput(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //Any controller input activated in THIS FRAME ONLY + //For analog, this means going being deadzone values + //controllerNum should be between 0 and 3 + static bool AnyControllerInputDown(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //Any controller input deactivated in THIS FRAME ONLY + //For analog, this means going below deadzone values + //controllerNum should be between 0 and 3 + static bool AnyControllerInputUp(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //Any DIGITAL controller buttons being held + //controllerNum should be between 0 and 3 + static bool AnyControllerButton(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //Any DIGITAL controller button activated in THIS FRAME ONLY + //controllerNum should be between 0 and 3 + static bool AnyControllerButtonDown(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //Any DIGITAL controller button deactivated in THIS FRAME ONLY + //controllerNum should be between 0 and 3 + static bool AnyControllerButtonUp(SH_CONTROLLERCODE* firstDetected = nullptr, size_t controllerNum = 0) noexcept; + + //If controller input is being held in the current frame + //normalisedValue is the value of the input between 0 and 1, relevant for trigger and thumbstick data + //controllerNum should be between 0 and 3 + static inline bool GetControllerInput(SH_CONTROLLERCODE input, + double* normalisedValue = nullptr, + double* heldTime = nullptr, + double* releasedTime = nullptr, + size_t controllerNum = 0) noexcept + { + if (controllerNum >= XUSER_MAX_COUNT) return false; + if (normalisedValue) *normalisedValue = controllerNormalisedValue(static_cast(input), controllers[controllerNum][static_cast(input)]); + if (heldTime) *heldTime = controllersHeldTime[controllerNum][static_cast(input)]; + if (releasedTime) *releasedTime = controllersReleasedTime[controllerNum][static_cast(input)]; + return controllerConsideredHeld(static_cast(input), controllers[controllerNum][static_cast(input)]); + } + + //If controller input was considered to be held down in THIS FRAME ONLY + //controllerNum should be between 0 and 3 + static inline bool GetControllerInputDown(SH_CONTROLLERCODE input, + double* releasedTime = nullptr, + size_t controllerNum = 0) noexcept + { + if (controllerNum >= XUSER_MAX_COUNT) return false; + if (releasedTime) *releasedTime = controllersReleasedTime[controllerNum][static_cast(input)]; + return (controllerConsideredHeld(static_cast(input), controllers[controllerNum][static_cast(input)]) && + !controllerConsideredHeld(static_cast(input), controllersLast[controllerNum][static_cast(input)])); + } + + //If controller input was considered to be released in THIS FRAME ONLY + //controllerNum should be between 0 and 3 + static inline bool GetControllerInputUp(SH_CONTROLLERCODE input, + double* releasedTime = nullptr, + size_t controllerNum = 0) noexcept + { + if (controllerNum >= XUSER_MAX_COUNT) return false; + if (releasedTime) *releasedTime = controllersReleasedTime[controllerNum][static_cast(input)]; + return (!controllerConsideredHeld(static_cast(input), controllers[controllerNum][static_cast(input)]) && + controllerConsideredHeld(static_cast(input), controllersLast[controllerNum][static_cast(input)])); + } + + /*------------------------------------------------------------------------*/ + /* Timing accessors */ + /*------------------------------------------------------------------------*/ + + //Keyboard///////////// + + //How long has this key been held down for + static inline double GetKeyHeldTime(SH_KEYCODE key) noexcept + { + return keysHeldTime[static_cast(key)]; + } + + //How long has this key been released for + static inline double GetKeyReleasedTime(SH_KEYCODE key) noexcept + { + return keysReleasedTime[static_cast(key)]; + } + + //How long has this key been toggled on for + static inline double GetKeyToggleOnTime(SH_KEYCODE key) noexcept + { + return keysToggleOnTime[static_cast(key)]; + } + + //How long has this keen been toggled off for + static inline double GetKeyToggleOffTime(SH_KEYCODE key) noexcept + { + return keysToggleOffTime[static_cast(key)]; + } + + //Controller////////////////////// + + //How long has this controller input been considered to be held down for + static inline double GetControllerInputHeldTime(SH_CONTROLLERCODE code, + size_t controllerNum = 0) noexcept + { + if (controllerNum >= XUSER_MAX_COUNT) return 0.0; + return controllersHeldTime[controllerNum][static_cast(code)]; + } + + //How long has this controller input been considered to be released for + static inline double GetControllerInputReleasedTime(SH_CONTROLLERCODE code, + size_t controllerNum = 0) noexcept + { + if (controllerNum >= XUSER_MAX_COUNT) return 0.0; + return controllersReleasedTime[controllerNum][static_cast(code)]; + } + + /*------------------------------------------------------------------------*/ + /* Binding Functions */ + /*------------------------------------------------------------------------*/ + + //Add a new binding to the map + static inline void BindingsAdd(std::string newBindingName) noexcept + { + bindings.insert({ newBindingName, SHLogicalBindingData() }); + } + + //Remove a binding from the map + //Returns 1 if found and removed, 0 if not found + static inline size_t BindingsRemove(std::string targetBindingName) noexcept + { + return bindings.erase(targetBindingName); + } + + //Clears all bindings from the list + static inline void BindingsClear() noexcept + { + bindings.clear(); + } + + //Get the number of bindings present + static inline size_t BindingsCount() noexcept + { + return bindings.size(); + } + + //Check positive keycodes to binding + static inline std::set const& BindingsGetPositiveKeyCodes(std::string bindingName) noexcept + { + return bindings[bindingName].positiveKeyCodes; + } + + //Add positive SH_KEYCODE to binding + static inline void BindingsAddPositiveKeyCode(std::string targetBindingName, + SH_KEYCODE toAdd) noexcept + { + bindings[targetBindingName].positiveKeyCodes.insert(toAdd); + } + + //Remove positive SH_KEYCODE from binding + //If toRemove found and removed, returns 1. Otherwise, 0. + static inline size_t BindingsRemovePositiveKeyCode(std::string targetBindingName, + SH_KEYCODE toRemove) noexcept + { + return bindings[targetBindingName].positiveKeyCodes.erase(toRemove); + } + + //Check negative keycodes to binding + static inline std::set const& BindingsGetNegativeKeyCodes(std::string bindingName) noexcept + { + return bindings[bindingName].negativeKeyCodes; + } + + //Add negative SH_KEYCODE to binding + static inline void BindingsAddNegativeKeyCode(std::string targetBindingName, + SH_KEYCODE toAdd) noexcept + { + bindings[targetBindingName].negativeKeyCodes.insert(toAdd); + } + + //Remove negative SH_KEYCODE from binding + //If toRemove found and removed, returns 1. Otherwise, 0. + static inline size_t BindingsRemoveNegativeKeyCode(std::string targetBindingName, + SH_KEYCODE toRemove) noexcept + { + return bindings[targetBindingName].negativeKeyCodes.erase(toRemove); + } + + //Check positive controllercodes to binding + static inline std::set const& BindingsGetPositiveControllerCodes(std::string bindingName) noexcept + { + return bindings[bindingName].positiveControllerCodes; + } + + //Add positive SH_CONTROLLERCODE to binding + static inline void BindingsAddPositiveControllerCode(std::string targetBindingName, + SH_CONTROLLERCODE toAdd) noexcept + { + bindings[targetBindingName].positiveControllerCodes.insert(toAdd); + } + + //Remove positive SH_CONTROLLERCODE from binding + //If toRemove found and removed, returns 1. Otherwise, 0. + static inline size_t BindingsRemovePositiveControllerCode(std::string targetBindingName, + SH_CONTROLLERCODE toRemove) noexcept + { + return bindings[targetBindingName].positiveControllerCodes.erase(toRemove); + } + + //Check negative controllercodes to binding + static inline std::set const& BindingsGetNegativeControllerCodes(std::string bindingName) noexcept + { + return bindings[bindingName].negativeControllerCodes; + } + + //Add negative SH_CONTROLLERCODE to binding + static inline void BindingsAddNegativeControllerCode(std::string targetBindingName, + SH_CONTROLLERCODE toAdd) noexcept + { + bindings[targetBindingName].negativeControllerCodes.insert(toAdd); + } + + //Remove negative SH_CONTROLLERCODE from binding + //If toRemove found and removed, returns 1. Otherwise, 0. + static inline size_t BindingsRemoveNegativeControllerCode(std::string targetBindingName, + SH_CONTROLLERCODE toRemove) noexcept + { + return bindings[targetBindingName].negativeControllerCodes.erase(toRemove); + } + + //Mouse movement bindings + + static inline double const BindingsGetMouseXPositiveMultiplier(std::string bindingName) noexcept + { + return bindings[bindingName].mouseXPositiveMultiplier; + } + + static inline void BindingsSetMouseXPositiveMultiplier(std::string bindingName, double newValue) noexcept + { + bindings[bindingName].mouseXPositiveMultiplier = newValue; + } + + static inline double const BindingsGetMouseYPositiveMultiplier(std::string bindingName) noexcept + { + return bindings[bindingName].mouseXPositiveMultiplier; + } + + static inline void BindingsSetMouseYPositiveMultiplier(std::string bindingName, double newValue) noexcept + { + bindings[bindingName].mouseXPositiveMultiplier = newValue; + } + + //Get the axis value of binding, between -1 and 1 + static double GetBindingAxis(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Whether binding is being held or not + //Does not work for mouse movement + static bool GetBindingPositiveButton(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButton(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Whether binding is pressed down IN THIS FRAME ONLY + //Does not work for mouse movement + static bool GetBindingPositiveButtonDown(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButtonDown(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Whether binding is released IN THIS FRAME ONLY + //Does not work for mouse movement + static bool GetBindingPositiveButtonUp(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButtonUp(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Binding times + + //Does not work for mouse movement + static double GetBindingPositiveHeldTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingNegativeHeldTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Does not work for mouse movement + static double GetBindingPositiveReleasedTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingNegativeReleasedTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + + //Binding mouse velocity + //Only for mouse movement + static double GetBindingMouseVelocity(std::string bindingName, size_t controllerNumber = 0) noexcept; + + /*------------------------------------------------------------------------*/ + /* Other Functions */ + /*------------------------------------------------------------------------*/ + + //Mouse//////////////////////// + + //Move mouse cursor to a position on the screen + static inline void SetMouseScreenPosition(int x = 0, int y = 0) noexcept + { + SetCursorPos(x, y); + } + + //Move mouse cursor to a position on the active window + static inline void SetMouseWindowPosition(int x = 0, int y = 0) noexcept + { + POINT p{ x, y }; + ClientToScreen(GetActiveWindow(), &p); + SetCursorPos(p.x, p.y); + } + + private: + /*------------------------------------------------------------------------*/ + /* Constants */ + /*------------------------------------------------------------------------*/ + static constexpr size_t MAX_KEYS = UCHAR_MAX + 1; + + //How many recognised controller inputs there are + //To see the list, see the enum class in this file + static constexpr size_t MAX_CONTROLLER_INPUT = 20; + + //On/off button count + static constexpr size_t NUM_CONTROLLER_BUTTON = 14; + + //8-bit trigger values + static constexpr size_t NUM_CONTROLLER_TRIGGER = 2; + + //16-bit thumbstick values + static constexpr size_t NUM_CONTROLLER_THUMBSTICK = 4; + + /*------------------------------------------------------------------------*/ + /* Data Members */ + /*------------------------------------------------------------------------*/ + + //If the last input is from controller(s) or KB/M + //True if from controller(s), False if from KB/M + //Useful for switching control hints between controllers and KB/M + static bool controllerInUse; + + //BINDINGS////////////////////////////////////////////////////////////////// + + //Key is for binding names, they must be unique + //Multiple physical inputs per virtual/logical input are to be added to + //sets inside the logicalBinding values + //TODO make this an array of 4 / 5 users for multiplayer support + static std::map bindings; + + //KEYBOARD AND MOUSE BUTTONS//////////////////////////////////////////////// + + //How many keys are presently being pressed + static unsigned keyCount; + + //Key states of all keys presently + //true for being pressed, false for released + static bool keys[MAX_KEYS]; + + //Key states of all keys in the last frame + //true for being pressed, false for released + static bool keysLast[MAX_KEYS]; + + //Key held durations + //Stops ticking up when released + //Will be reset when held again + static double keysHeldTime[MAX_KEYS]; + + //Key released durations + //Stops ticking up when held + //Will be reset when off again + static double keysReleasedTime[MAX_KEYS]; + + //How many keys are presently being toggled + static unsigned keyToggleCount; + + //Toggle key states of keys (not neccessarily just caps/num/scroll locks) + static bool keysToggle[MAX_KEYS]; + + //Toggle key states of keys in the last frame + static bool keysToggleLast[MAX_KEYS]; + + //Key toggle durations + //Stops ticking up when untoggled + //Will be reset when toggled again + static double keysToggleOnTime[MAX_KEYS]; + + //Key untoggle durations + //Stops ticking up when toggled + //Will be reset when untoggled again + static double keysToggleOffTime[MAX_KEYS]; + + //MOUSE VARIABLES/////////////////////////////////////////////////////////// + + //Present horizontal positioning of the mouse WRT the screen + //Increasing rightwards + static int mouseScreenX; + //Present vertical positioning of the mouse WRT the screen + //Increasing downwards + static int mouseScreenY; + + //Horizontal positioning of the mouse WRT screen in last frame + //Increasing rightwards + static int mouseScreenXLast; + //Vertical positioning of the mouse WRT screen in the last frame + //Increasing downwards + static int mouseScreenYLast; + + //The velocity at which the mouse is being moved horizontally (px/s) + //Rightwards is positive + static double mouseVelocityX; + //The velocity at which the mouse is being moved vertically (px/s) + //Downwards is positive + static double mouseVelocityY; + + //For polling mouse wheel events, not to be read + static int mouseWheelVerticalDeltaPoll; + //Mouse wheel vertical rotation speed. Positive is rotation AWAY from user + static int mouseWheelVerticalDelta; + + //CONTROLLER VARIABLES////////////////////////////////////////////////////// + + //Count how many controllers are presently connected + //Useful for if the game is to decide to take in controller or KB/M input + //Between 0 and 4 (inclusive) + static unsigned char controllersConnectedCount; + + //How many inputs (of any kind) on the controller are being used now + //Includes analog triggers and thumbsticks + static unsigned controllersInputCount[XUSER_MAX_COUNT]; + + //How many DIGITAL buttons on the controller are being pressed now + static unsigned controllersButtonCount[XUSER_MAX_COUNT]; + + //Current state of controllers + //First index is for controller number + //Second index is for input type + static short controllers[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT]; + + //State of controllers in the last frame + //First index is for controller number + //Second index is for input type + static short controllersLast[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT]; + + //Held and released times + + //Controller held durations + //Stops ticking up when released + //Will be reset when held again + static double controllersHeldTime[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT]; + + //Key released durations + //Stops ticking up when held + //Will be reset when off again + static double controllersReleasedTime[XUSER_MAX_COUNT][MAX_CONTROLLER_INPUT]; + + /*------------------------------------------------------------------------*/ + /* Internal Helper Functions */ + /*------------------------------------------------------------------------*/ + + //Internal helper function for checking if input is considered held or not + static bool controllerConsideredHeld(size_t inputIdx, short value) noexcept; + + //Internal helper function for getting normalised controller value + static double controllerNormalisedValue(size_t inputIdx, short value) noexcept; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.cpp b/SHADE_Engine/src/Math/Geometry/SHBox.cpp new file mode 100644 index 00000000..cf094a9d --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHBox.cpp @@ -0,0 +1,241 @@ +/**************************************************************************************** + * \file SHBoundingBox.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a 3-Dimensional Axis Aligned Bounding Box + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHBox.h" +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Math/SHRay.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBox::SHBox() noexcept + : RelativeExtents { SHVec3::One } + { + type = Type::BOX; + } + + SHBox::SHBox(const SHVec3& c, const SHVec3& hE) noexcept + : RelativeExtents { SHVec3::One } + { + type = Type::BOX; + + Center = c; + Extents = hE; + } + + + SHBox::SHBox(const SHBox& rhs) noexcept + { + if (this == &rhs) + return; + + type = Type::BOX; + + Center = rhs.Center; + Extents = rhs.Extents; + RelativeExtents = rhs.RelativeExtents; + } + + SHBox::SHBox(SHBox&& rhs) noexcept + { + type = Type::BOX; + + Center = rhs.Center; + Extents = rhs.Extents; + RelativeExtents = rhs.RelativeExtents; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBox& SHBox::operator=(const SHBox& rhs) noexcept + { + if (rhs.type != Type::BOX) + { + SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") + } + else if (this != &rhs) + { + Center = rhs.Center; + Extents = rhs.Extents; + RelativeExtents = rhs.RelativeExtents; + } + + return *this; + } + + SHBox& SHBox::operator=(SHBox&& rhs) noexcept + { + if (rhs.type != Type::BOX) + { + SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") + } + else + { + Center = rhs.Center; + Extents = rhs.Extents; + RelativeExtents = rhs.RelativeExtents; + } + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3 SHBox::GetCenter() const noexcept + { + return Center; + } + + SHVec3 SHBox::GetWorldExtents() const noexcept + { + return Extents; + } + + const SHVec3& SHBox::GetRelativeExtents() const noexcept + { + return RelativeExtents; + } + + SHVec3 SHBox::GetMin() const noexcept + { + return SHVec3{ Center.x - Extents.x, Center.y - Extents.y, Center.z - Extents.z }; + } + + SHVec3 SHBox::GetMax() const noexcept + { + return SHVec3{ Center.x + Extents.x, Center.y + Extents.y, Center.z + Extents.z }; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHBox::SetCenter(const SHVec3& newCenter) noexcept + { + Center = newCenter; + } + + void SHBox::SetWorldExtents(const SHVec3& newWorldExtents) noexcept + { + Extents = newWorldExtents; + } + + void SHBox::SetRelativeExtents(const SHVec3& newRelativeExtents) noexcept + { + RelativeExtents = newRelativeExtents; + } + + void SHBox::SetMin(const SHVec3& min) noexcept + { + const SHVec3 MAX = GetMax(); + + Center = SHVec3::Lerp(min, MAX, 0.5f); + Extents = SHVec3::Abs((MAX - min) * 0.5f); + } + + void SHBox::SetMax(const SHVec3& max) noexcept + { + const SHVec3 MIN = GetMin(); + + Center = SHVec3::Lerp(MIN, max, 0.5f); + Extents = SHVec3::Abs((max - MIN) * 0.5f); + } + + void SHBox::SetMinMax(const SHVec3& min, const SHVec3& max) noexcept + { + Center = SHVec3::Lerp(min, max, 0.5f); + Extents = SHVec3::Abs((max - min) * 0.5f); + } + + std::vector SHBox::GetVertices() const noexcept + { + std::vector vertices{ 8 }; + GetCorners(vertices.data()); + return vertices; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHBox::TestPoint(const SHVec3& point) noexcept + { + return BoundingBox::Contains(point); + } + + bool SHBox::Raycast(const SHRay& ray, float& distance) noexcept + { + return BoundingBox::Intersects(ray.position, ray.direction, distance); + } + + bool SHBox::Contains(const SHBox& rhs) const noexcept + { + return BoundingBox::Contains(rhs); + } + + float SHBox::Volume() const noexcept + { + return 8.0f * (Extents.x * Extents.y * Extents.z); + } + + float SHBox::SurfaceArea() const noexcept + { + return 8.0f * ((Extents.x * Extents.y) + + (Extents.x * Extents.z) + + (Extents.y * Extents.z)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBox SHBox::Combine(const SHBox& lhs, const SHBox& rhs) noexcept + { + SHBox result; + CreateMerged(result, lhs, rhs); + return result; + } + + bool SHBox::Intersect(const SHBox& lhs, const SHBox& rhs) noexcept + { + return lhs.Intersects(rhs); + } + + SHBox SHBox::BuildFromBoxes(const SHBox* boxes, size_t numBoxes) noexcept + { + SHBox result; + + for (size_t i = 1; i < numBoxes; ++i) + CreateMerged(result, boxes[i - 1], boxes[i]); + + return result; + } + + SHBox SHBox::BuildFromVertices(const SHVec3* vertices, size_t numVertices, size_t stride) noexcept + { + SHBox result; + CreateFromPoints(result, numVertices, vertices, stride); + return result; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.h b/SHADE_Engine/src/Math/Geometry/SHBox.h new file mode 100644 index 00000000..0ea950ab --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHBox.h @@ -0,0 +1,105 @@ +/**************************************************************************************** + * \file SHBoundingBox.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a 3-Dimensional Axis Aligned Bounding Box + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SHShape.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHBox : public SHShape, + private DirectX::BoundingBox + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t NUM_VERTICES = 8; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + ~SHBox () override = default; + + SHBox () noexcept; + SHBox (const SHVec3& center, const SHVec3& halfExtents) noexcept; + SHBox (const SHBox& rhs) noexcept; + SHBox (SHBox&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHBox& operator= (const SHBox& rhs) noexcept; + SHBox& operator= (SHBox&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec3 GetCenter () const noexcept; + [[nodiscard]] SHVec3 GetWorldExtents () const noexcept; + [[nodiscard]] const SHVec3& GetRelativeExtents () const noexcept; + [[nodiscard]] SHVec3 GetMin () const noexcept; + [[nodiscard]] SHVec3 GetMax () const noexcept; + [[nodiscard]] std::vector GetVertices () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetCenter (const SHVec3& newCenter) noexcept; + void SetWorldExtents (const SHVec3& newWorldExtents) noexcept; + void SetRelativeExtents (const SHVec3& newRelativeExtents) noexcept; + void SetMin (const SHVec3& min) noexcept; + void SetMax (const SHVec3& max) noexcept; + void SetMinMax (const SHVec3& min, const SHVec3& max) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool TestPoint (const SHVec3& point) noexcept override; + [[nodiscard]] bool Raycast (const SHRay& ray, float& distance) noexcept override; + + [[nodiscard]] bool Contains (const SHBox& rhs) const noexcept; + [[nodiscard]] float Volume () const noexcept; + [[nodiscard]] float SurfaceArea () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHBox Combine (const SHBox& lhs, const SHBox& rhs) noexcept; + [[nodiscard]] static bool Intersect (const SHBox& lhs, const SHBox& rhs) noexcept; + [[nodiscard]] static SHBox BuildFromBoxes (const SHBox* boxes, size_t numBoxes) noexcept; + [[nodiscard]] static SHBox BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHVec3 RelativeExtents; + }; + + +} // namespace SHADE + diff --git a/SHADE_Engine/src/Math/Geometry/SHShape.cpp b/SHADE_Engine/src/Math/Geometry/SHShape.cpp new file mode 100644 index 00000000..2f869029 --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHShape.cpp @@ -0,0 +1,35 @@ +/**************************************************************************************** + * \file SHShape.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHShape::SHShape() + : type { Type::NONE } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHShape::Type SHShape::GetType() const noexcept + { + return type; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHShape.h b/SHADE_Engine/src/Math/Geometry/SHShape.h new file mode 100644 index 00000000..62198897 --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHShape.h @@ -0,0 +1,82 @@ +/**************************************************************************************** + * \file SHShape.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "SH_API.h" +#include "Math/SHRay.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHShape + { + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class Type + { + BOX + , SPHERE + , CAPSULE + , CONVEX_HULL + + , COUNT + , NONE = -1 + }; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + bool isIntersecting; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + virtual ~SHShape () = default; + + SHShape (const SHShape&) = default; + SHShape (SHShape&&) = default; + + SHShape& operator=(const SHShape&) = default; + SHShape& operator=(SHShape&&) = default; + + SHShape(); + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] Type GetType () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] virtual bool TestPoint (const SHVec3& point) noexcept = 0; + [[nodiscard]] virtual bool Raycast (const SHRay& ray, float& distance) noexcept = 0; + + protected: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + Type type; + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.cpp b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp new file mode 100644 index 00000000..d310e30e --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp @@ -0,0 +1,199 @@ +/**************************************************************************************** + * \file SHBoundingSphere.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Bounding Sphere + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHSphere.h" +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Math/SHRay.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphere::SHSphere() noexcept + : RelativeRadius { 1.0f } + { + type = Type::SPHERE; + } + + SHSphere::SHSphere(const SHVec3& center, float radius) noexcept + : RelativeRadius { 1.0f } + { + type = Type::SPHERE; + + Center = center; + Radius = radius; + } + + SHSphere::SHSphere(const SHSphere& rhs) noexcept + { + if (this == &rhs) + return; + + type = Type::SPHERE; + + Center = rhs.Center; + Radius = rhs.Radius; + RelativeRadius = rhs.RelativeRadius; + } + + SHSphere::SHSphere(SHSphere&& rhs) noexcept + { + type = Type::SPHERE; + + Center = rhs.Center; + Radius = rhs.Radius; + RelativeRadius = rhs.RelativeRadius; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphere& SHSphere::operator=(const SHSphere& rhs) noexcept + { + if (rhs.type != Type::SPHERE) + { + SHLOG_WARNING("Cannot assign a non-sphere to a sphere!") + } + else if (this != &rhs) + { + Center = rhs.Center; + Radius = rhs.Radius; + RelativeRadius = rhs.RelativeRadius; + } + + return *this; + } + + SHSphere& SHSphere::operator=(SHSphere&& rhs) noexcept + { + if (rhs.type != Type::SPHERE) + { + SHLOG_WARNING("Cannot assign a non-sphere to a sphere!") + } + else + { + Center = rhs.Center; + Radius = rhs.Radius; + RelativeRadius = rhs.RelativeRadius; + } + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3 SHSphere::GetCenter() const noexcept + { + return Center; + } + + float SHSphere::GetWorldRadius() const noexcept + { + return Radius; + } + + float SHSphere::GetRelativeRadius() const noexcept + { + return RelativeRadius; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHSphere::SetCenter(const SHVec3& center) noexcept + { + Center = center; + } + + void SHSphere::SetWorldRadius(float newWorldRadius) noexcept + { + Radius = newWorldRadius; + } + + void SHSphere::SetRelativeRadius(float newRelativeRadius) noexcept + { + RelativeRadius = newRelativeRadius; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHSphere::TestPoint(const SHVec3& point) noexcept + { + return BoundingSphere::Contains(point); + } + + bool SHSphere::Raycast(const SHRay& ray, float& distance) noexcept + { + return Intersects(ray.position, ray.direction, distance); + } + + bool SHSphere::Contains(const SHSphere& rhs) const noexcept + { + return BoundingSphere::Contains(rhs); + } + + float SHSphere::Volume() const noexcept + { + return (4.0f / 3.0f) * SHMath::PI * (Radius * Radius * Radius); + } + + float SHSphere::SurfaceArea() const noexcept + { + return 4.0f * SHMath::PI * (Radius * Radius); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphere SHSphere::Combine(const SHSphere& lhs, const SHSphere& rhs) noexcept + { + SHSphere result; + CreateMerged(result, lhs, rhs); + return result; + } + + bool SHSphere::Intersect(const SHSphere& lhs, const SHSphere& rhs) noexcept + { + return lhs.Intersects(rhs); + } + + SHSphere SHSphere::BuildFromSpheres(const SHSphere* spheres, size_t numSpheres) noexcept + { + SHSphere result; + + for (size_t i = 1; i < numSpheres; ++i) + CreateMerged(result, spheres[i - 1], spheres[i]); + + return result; + } + + SHSphere SHSphere::BuildFromVertices(const SHVec3* vertices, size_t numVertices, size_t stride) noexcept + { + SHSphere result; + CreateFromPoints(result, numVertices, vertices, stride); + return result; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.h b/SHADE_Engine/src/Math/Geometry/SHSphere.h new file mode 100644 index 00000000..c13076aa --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.h @@ -0,0 +1,92 @@ +/**************************************************************************************** + * \file SHBoundingSphere.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Bounding Sphere. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SHShape.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHSphere : public SHShape, + private DirectX::BoundingSphere + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHSphere () noexcept; + SHSphere (const SHVec3& center, float radius) noexcept; + SHSphere (const SHSphere& rhs) noexcept; + SHSphere (SHSphere&& rhs) noexcept; + + ~SHSphere () override = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHSphere& operator= (const SHSphere& rhs) noexcept; + SHSphere& operator= (SHSphere&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec3 GetCenter () const noexcept; + [[nodiscard]] float GetWorldRadius () const noexcept; + [[nodiscard]] float GetRelativeRadius () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetCenter (const SHVec3& center) noexcept; + void SetWorldRadius (float newWorldRadius) noexcept; + void SetRelativeRadius (float newRelativeRadius) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool TestPoint (const SHVec3& point) noexcept override; + [[nodiscard]] bool Raycast (const SHRay& ray, float& distance) noexcept override; + + [[nodiscard]] bool Contains (const SHSphere& rhs) const noexcept; + [[nodiscard]] float Volume () const noexcept; + [[nodiscard]] float SurfaceArea () const noexcept; + + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHSphere Combine (const SHSphere& lhs, const SHSphere& rhs) noexcept; + [[nodiscard]] static bool Intersect (const SHSphere& lhs, const SHSphere& rhs) noexcept; + [[nodiscard]] static SHSphere BuildFromSpheres (const SHSphere* spheres, size_t numSpheres) noexcept; + [[nodiscard]] static SHSphere BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + float RelativeRadius; + + }; +} // namespace SHADE diff --git a/SHADE_Engine/src/Math/SHColour.cpp b/SHADE_Engine/src/Math/SHColour.cpp new file mode 100644 index 00000000..fc2f2a08 --- /dev/null +++ b/SHADE_Engine/src/Math/SHColour.cpp @@ -0,0 +1,464 @@ +/**************************************************************************************** + * \file SHColour.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Colour. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + + +#include + +// Primary Header +#include "SHColour.h" +// Project Headers +#include "SHMathHelpers.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHColour SHColour::BEIGE = DirectX::Colors::Beige; + const SHColour SHColour::BLACK = DirectX::Colors::Black; + const SHColour SHColour::BLUE = DirectX::Colors::Blue; + const SHColour SHColour::BROWN = DirectX::Colors::Brown; + const SHColour SHColour::CHOCOLATE = DirectX::Colors::Chocolate; + const SHColour SHColour::CORAL = DirectX::Colors::Coral; + const SHColour SHColour::CRIMSON = DirectX::Colors::Crimson; + const SHColour SHColour::CYAN = DirectX::Colors::Cyan; + const SHColour SHColour::DARKBLUE = DirectX::Colors::DarkBlue; + const SHColour SHColour::DARKGRAY = DirectX::Colors::DarkGray; + const SHColour SHColour::DARKGREEN = DirectX::Colors::DarkGreen; + const SHColour SHColour::DARKMAGENTA = DirectX::Colors::DarkMagenta; + const SHColour SHColour::DARKORANGE = DirectX::Colors::DarkOrange; + const SHColour SHColour::DARKRED = DirectX::Colors::DarkRed; + const SHColour SHColour::DEEPPINK = DirectX::Colors::DeepPink; + const SHColour SHColour::FORESTGREEN = DirectX::Colors::ForestGreen; + const SHColour SHColour::FUCHSIA = DirectX::Colors::Fuchsia; + const SHColour SHColour::GOLD = DirectX::Colors::Gold; + const SHColour SHColour::GRAY = DirectX::Colors::Gray; + const SHColour SHColour::GREEN = DirectX::Colors::Green; + const SHColour SHColour::HOTPINK = DirectX::Colors::HotPink; + const SHColour SHColour::INDIGO = DirectX::Colors::Indigo; + const SHColour SHColour::LAVENDER = DirectX::Colors::Lavender; + const SHColour SHColour::LIGHTBLUE = DirectX::Colors::LightBlue; + const SHColour SHColour::LIGHTGRAY = DirectX::Colors::LightGray; + const SHColour SHColour::LIGHTGREEN = DirectX::Colors::LightGreen; + const SHColour SHColour::LIGHTPINK = DirectX::Colors::LightPink; + const SHColour SHColour::LIGHTYELLOW = DirectX::Colors::LightYellow; + const SHColour SHColour::LIME = DirectX::Colors::Lime; + const SHColour SHColour::LIMEGREEN = DirectX::Colors::LimeGreen; + const SHColour SHColour::MAGENTA = DirectX::Colors::Magenta; + const SHColour SHColour::MAROON = DirectX::Colors::Maroon; + const SHColour SHColour::MEDIUMBLUE = DirectX::Colors::MediumBlue; + const SHColour SHColour::MEDIUMPURPLE = DirectX::Colors::MediumPurple; + const SHColour SHColour::NAVY = DirectX::Colors::Navy; + const SHColour SHColour::OLIVE = DirectX::Colors::Olive; + const SHColour SHColour::ORANGE = DirectX::Colors::Orange; + const SHColour SHColour::ORCHID = DirectX::Colors::Orchid; + const SHColour SHColour::PINK = DirectX::Colors::Pink; + const SHColour SHColour::PURPLE = DirectX::Colors::Purple; + const SHColour SHColour::RED = DirectX::Colors::Red; + const SHColour SHColour::ROYALBLUE = DirectX::Colors::RoyalBlue; + const SHColour SHColour::SALMON = DirectX::Colors::Salmon; + const SHColour SHColour::SANDYBROWN = DirectX::Colors::SandyBrown; + const SHColour SHColour::SILVER = DirectX::Colors::Silver; + const SHColour SHColour::SKYBLUE = DirectX::Colors::SkyBlue; + const SHColour SHColour::SLATEGRAY = DirectX::Colors::SlateGray; + const SHColour SHColour::SNOW = DirectX::Colors::Snow; + const SHColour SHColour::STEELBLUE = DirectX::Colors::SteelBlue; + const SHColour SHColour::TAN = DirectX::Colors::Tan; + const SHColour SHColour::TEAL = DirectX::Colors::Teal; + const SHColour SHColour::TURQUOISE = DirectX::Colors::Turquoise; + const SHColour SHColour::VIOLET = DirectX::Colors::Violet; + const SHColour SHColour::WHITE = DirectX::Colors::White; + const SHColour SHColour::YELLOW = DirectX::Colors::Yellow; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHColour::SHColour() noexcept + : XMFLOAT4 { 0.0f, 0.0f, 0.0f, 1.0f } + {} + + SHColour::SHColour(float r, float g, float b) noexcept + : XMFLOAT4 { r, g, b, 1.0f } + {} + + SHColour::SHColour(float r, float g, float b, float a) noexcept + : XMFLOAT4 { r, g, b, a } + {} + + SHColour::SHColour(uint8_t r, uint8_t g, uint8_t b) noexcept + : XMFLOAT4 + { + static_cast(r) / 255.0f, + static_cast(g) / 255.0f, + static_cast(b) / 255.0f, + 1.0f + } + {} + + SHColour::SHColour(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept + : XMFLOAT4 + { + static_cast(r) / 255.0f, + static_cast(g) / 255.0f, + static_cast(b) / 255.0f, + static_cast(a) / 255.0f + } + {} + + SHColour::SHColour(uint32_t rgba) noexcept + : XMFLOAT4 { 0.0f, 0.0f, 0.0f, 1.0f } + { + const SHColour32 TMP { ._32bitValue = rgba }; + + x = static_cast(TMP._8bitValues[0]) / 255.0f; + y = static_cast(TMP._8bitValues[1]) / 255.0f; + z = static_cast(TMP._8bitValues[2]) / 255.0f; + w = static_cast(TMP._8bitValues[3]) / 255.0f; + } + + SHColour::SHColour(const SHVec3& rgb) noexcept + : XMFLOAT4 { rgb.x, rgb.y, rgb.z, 1.0f } + {} + + SHColour::SHColour(const SHVec4& rgba) noexcept + : XMFLOAT4 { rgba.x, rgba.y, rgba.z, rgba.w } + {} + + SHColour::SHColour(const DirectX::XMFLOAT3& rgb) noexcept + : XMFLOAT4 { rgb.x, rgb.y, rgb.z, 1.0f } + {} + + SHColour::SHColour(const DirectX::XMFLOAT4& rgba) noexcept + : XMFLOAT4 { rgba.x, rgba.y, rgba.z, rgba.w } + {} + + SHColour::SHColour(const DirectX::XMVECTORF32& colour) noexcept + : XMFLOAT4 + { + XMVectorGetX(colour), + XMVectorGetY(colour), + XMVectorGetZ(colour), + XMVectorGetW(colour) + } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHColour::operator XMVECTOR() const noexcept + { + return XMLoadFloat4(this); + } + + SHColour::operator SHVec4() const noexcept + { + return SHVec4{ *this }; + } + + SHColour& SHColour::operator+=(const SHColour& rhs) noexcept + { + return *this = *this + rhs; + } + + SHColour& SHColour::operator-=(const SHColour& rhs) noexcept + { + return *this = *this - rhs; + } + + SHColour& SHColour::operator*=(const SHColour& rhs) noexcept + { + return *this = *this * rhs; + } + + SHColour& SHColour::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHColour& SHColour::operator/=(const SHColour& rhs) noexcept + { + return *this = *this / rhs; + } + + SHColour SHColour::operator+(const SHColour& rhs) const noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorAdd(*this, rhs)); + return result; + } + + SHColour SHColour::operator-(const SHColour& rhs) const noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorSubtract(*this, rhs)); + return result; + } + + SHColour SHColour::operator-() const noexcept + { + return SHColour{ -x, -y, -z, -w }; + } + + SHColour SHColour::operator*(const SHColour& rhs) const noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorMultiply(*this, rhs)); + return result; + } + + SHColour SHColour::operator*(float rhs) const noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorScale(*this, rhs)); + return result; + } + + SHColour SHColour::operator/(const SHColour& rhs) const noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorDivide(*this, rhs)); + return result; + } + + bool SHColour::operator==(const SHColour& rhs) const noexcept + { + return XMColorEqual(*this, rhs); + } + + bool SHColour::operator!=(const SHColour& rhs) const noexcept + { + return XMColorNotEqual(*this, rhs); + } + + float& SHColour::operator[](int index) + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + } + } + + float& SHColour::operator[](size_t index) + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + } + } + + float SHColour::operator[](int index) const + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + } + } + + float SHColour::operator[](size_t index) const + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + } + } + + SHColour operator* (float lhs, const SHColour& rhs) noexcept + { + SHColour result; + + XMStoreFloat4(&result, XMVectorScale(rhs, lhs)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHColourHSV SHColour::ToHSV() noexcept + { + SHColourHSV hsv; + + const float MIN = SHMath::Min({ x, y, z }); + const float MAX = SHMath::Max({ x, y, z }); + + hsv.v = MAX; + + const float DELTA = MAX - MIN; + hsv.s = (MAX != 0.0f) ? DELTA / MAX : 0.0f; + + static const float SIN_60 = sin(SHMath::DegreesToRadians(60.0f)); + + if (DELTA == 0.0f) + hsv.h = 0.0f; + else if (x == MAX) + hsv.h = (y - z) / DELTA; + else if (y == MAX) + hsv.h = 2.0f + (z - x) / DELTA; + else + hsv.h = 4.0f + (x - y) / DELTA; + + hsv.h *= 60; + if (hsv.h < 0.0f) + hsv.h += 360.f; + + return hsv; + } + + void SHColour::Negate() noexcept + { + XMStoreFloat4(this, XMColorNegative(*this)); + } + + void SHColour::Saturate() noexcept + { + XMStoreFloat4(this, XMVectorSaturate(*this)); + } + + void SHColour::AdjustSaturation(float saturation) noexcept + { + XMStoreFloat4(this, XMColorAdjustSaturation(*this, saturation)); + } + + void SHColour::AdjustContrast(float contrast) noexcept + { + XMStoreFloat4(this, XMColorAdjustContrast(*this, contrast)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHColour SHColour::Modulate(const SHColour& lhs, const SHColour& rhs) noexcept + { + SHColour result; + XMStoreFloat4(&result, XMColorModulate(lhs, rhs)); + return result; + } + + SHColour SHColour::FromHSV(float hue, float saturation, float value) + { + if (hue < 0.0f || saturation < 0.0f || value < 0.0f) + throw std::invalid_argument("One or more of the hsv values are invalid!"); + + SHColour colour; + + if (saturation == 0.0f) + { + colour.x = colour.y = colour.z = value; + } + else + { + hue /= 60.0f; + + const int SECTOR = static_cast(hue); + + const float F = hue - static_cast(SECTOR); + const float P = value * (1.0f - saturation); + const float Q = value * (1.0f - saturation * F); + const float T = value * (1.0f - saturation * (1.0f - F)); + + switch (SECTOR) + { + case 0: + { + colour.x = value; + colour.y = T; + colour.z = P; + + break; + } + case 1: + { + colour.x = Q; + colour.y = value; + colour.z = P; + + break; + } + case 2: + { + colour.x = P; + colour.y = value; + colour.z = T; + + break; + } + case 3: + { + colour.x = P; + colour.y = Q; + colour.z = value; + + break; + } + case 4: + { + colour.x = T; + colour.y = P; + colour.z = value; + + break; + } + default: + { + colour.x = value; + colour.y = P; + colour.z = Q; + + break; + } + } + } + + return colour; + } + + SHColour SHColour::FromHSV(const SHColourHSV& hsv) + { + return FromHSV(hsv.h, hsv.s, hsv.v); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHColour.h b/SHADE_Engine/src/Math/SHColour.h new file mode 100644 index 00000000..5dac0edd --- /dev/null +++ b/SHADE_Engine/src/Math/SHColour.h @@ -0,0 +1,193 @@ +/**************************************************************************************** + * \file SHColour.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Colour. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SH_API.h" +#include "Vector/SHVec3.h" +#include "Vector/SHVec4.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHColourHSV + { + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + float h = 0.0f; + float s = 0.0f; + float v = 0.0f; + }; + + union SH_API SHColour32 + { + uint32_t _32bitValue; + uint8_t _8bitValues[4]; + }; + + class SH_API SHColour : public DirectX::XMFLOAT4 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHColour () noexcept; + SHColour (float r, float g, float b) noexcept; + SHColour (float r, float g, float b, float a) noexcept; + SHColour (uint8_t r, uint8_t g, uint8_t b) noexcept; + SHColour (uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + SHColour (uint32_t rgba) noexcept; + + SHColour (const SHVec3& rgb) noexcept; + SHColour (const SHVec4& rgba) noexcept; + SHColour (const DirectX::XMFLOAT3& rgb) noexcept; + SHColour (const DirectX::XMFLOAT4& rgba) noexcept; + SHColour (const DirectX::XMVECTORF32& rgba) noexcept; + + SHColour (const SHColour&) = default; + SHColour (SHColour&&) = default; + + ~SHColour () = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHColour& operator= (const SHColour&) = default; + SHColour& operator= (SHColour&&) = default; + + operator DirectX::XMVECTOR () const noexcept; + operator SHVec4 () const noexcept; + + SHColour& operator+= (const SHColour& rhs) noexcept; + SHColour& operator-= (const SHColour& rhs) noexcept; + SHColour& operator*= (const SHColour& rhs) noexcept; + SHColour& operator*= (float rhs) noexcept; + SHColour& operator/= (const SHColour& rhs) noexcept; + + [[nodiscard]] SHColour operator+ (const SHColour& rhs) const noexcept; + [[nodiscard]] SHColour operator- (const SHColour& rhs) const noexcept; + [[nodiscard]] SHColour operator- () const noexcept; + [[nodiscard]] SHColour operator* (const SHColour& rhs) const noexcept; + [[nodiscard]] SHColour operator* (float rhs) const noexcept; + [[nodiscard]] SHColour operator/ (const SHColour& rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHColour& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHColour& rhs) const noexcept; + + [[nodiscard]] float& operator[] (int index); + [[nodiscard]] float& operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] inline float& r() noexcept { return x; } + [[nodiscard]] inline float& g() noexcept { return y; } + [[nodiscard]] inline float& b() noexcept { return z; } + [[nodiscard]] inline float& a() noexcept { return w; } + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + SHColourHSV ToHSV () noexcept; + + void Negate () noexcept; + void Saturate () noexcept; + + void AdjustSaturation (float saturation) noexcept; + void AdjustContrast (float contrast) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHColour Modulate (const SHColour& lhs, const SHColour& rhs) noexcept; + [[nodiscard]] static SHColour FromHSV (float hue, float saturation, float value); + [[nodiscard]] static SHColour FromHSV (const SHColourHSV& hsv); + + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t SIZE = 4U; + + static const SHColour BEIGE ; + static const SHColour BLACK ; + static const SHColour BLUE ; + static const SHColour BROWN ; + static const SHColour CHOCOLATE ; + static const SHColour CORAL ; + static const SHColour CRIMSON ; + static const SHColour CYAN ; + static const SHColour DARKBLUE ; + static const SHColour DARKGRAY ; + static const SHColour DARKGREEN ; + static const SHColour DARKMAGENTA ; + static const SHColour DARKORANGE ; + static const SHColour DARKRED ; + static const SHColour DEEPPINK ; + static const SHColour FORESTGREEN ; + static const SHColour FUCHSIA ; + static const SHColour GOLD ; + static const SHColour GRAY ; + static const SHColour GREEN ; + static const SHColour HOTPINK ; + static const SHColour INDIGO ; + static const SHColour LAVENDER ; + static const SHColour LIGHTBLUE ; + static const SHColour LIGHTGRAY ; + static const SHColour LIGHTGREEN ; + static const SHColour LIGHTPINK ; + static const SHColour LIGHTYELLOW ; + static const SHColour LIME ; + static const SHColour LIMEGREEN ; + static const SHColour MAGENTA ; + static const SHColour MAROON ; + static const SHColour MEDIUMBLUE ; + static const SHColour MEDIUMPURPLE; + static const SHColour NAVY ; + static const SHColour OLIVE ; + static const SHColour ORANGE ; + static const SHColour ORCHID ; + static const SHColour PINK ; + static const SHColour PURPLE ; + static const SHColour RED ; + static const SHColour ROYALBLUE ; + static const SHColour SALMON ; + static const SHColour SANDYBROWN ; + static const SHColour SILVER ; + static const SHColour SKYBLUE ; + static const SHColour SLATEGRAY ; + static const SHColour SNOW ; + static const SHColour STEELBLUE ; + static const SHColour TAN ; + static const SHColour TEAL ; + static const SHColour TURQUOISE ; + static const SHColour VIOLET ; + static const SHColour WHITE ; + static const SHColour YELLOW ; + }; + + SHColour operator* (float lhs, const SHColour& rhs) noexcept; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Math/SHMath.h b/SHADE_Engine/src/Math/SHMath.h index 9763abe2..3a24d6ef 100644 --- a/SHADE_Engine/src/Math/SHMath.h +++ b/SHADE_Engine/src/Math/SHMath.h @@ -6,4 +6,9 @@ #include "Vector/SHVec3.h" #include "Vector/SHVec4.h" -#include "SHMatrix.h" \ No newline at end of file +#include "SHQuaternion.h" +#include "SHMatrix.h" + +#include "SHColour.h" + +#include "Transform/SHTransform.h" \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMathHelpers.h b/SHADE_Engine/src/Math/SHMathHelpers.h index 9135230e..427011a6 100644 --- a/SHADE_Engine/src/Math/SHMathHelpers.h +++ b/SHADE_Engine/src/Math/SHMathHelpers.h @@ -15,6 +15,9 @@ #include #include +// Project Headers +#include "SH_API.h" + namespace SHADE { @@ -35,7 +38,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHMath + class SH_API SHMath { public: /*---------------------------------------------------------------------------------*/ @@ -56,25 +59,37 @@ namespace SHADE /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - static void Initialise (); + static void Initialise (); template - [[nodiscard]] static constexpr T DegreesToRadians (T angleInDeg); + [[nodiscard]] static T Min (T lhs, T rhs); template - [[nodiscard]] static constexpr T RadiansToDegrees (T angleInRad); + [[nodiscard]] static T Min (const std::initializer_list& values); template - [[nodiscard]] static T Lerp (T a, T b, T alpha); + [[nodiscard]] static T Max (T lhs, T rhs); template - [[nodiscard]] static T ClampedLerp (T a, T b, T alpha, T alphaMin, T alphaMax); + [[nodiscard]] static T Max (const std::initializer_list& values); template - [[nodiscard]] static T Wrap (T value, T min, T max); + [[nodiscard]] static T DegreesToRadians (T angleInDeg); + + template + [[nodiscard]] static T RadiansToDegrees (T angleInRad); + + template + [[nodiscard]] static T Lerp (T a, T b, T alpha); + + template + [[nodiscard]] static T ClampedLerp (T a, T b, T alpha, T alphaMin, T alphaMax); + + template + [[nodiscard]] static T Wrap (T value, T min, T max); template - [[nodiscard]] static T GenerateRandomNumber (T lowerBound = 0, T upperBound = 1); + [[nodiscard]] static T GenerateRandomNumber (T lowerBound = 0, T upperBound = 1); /** * @brief Compares two floating-point values for equality within given tolerances. @@ -86,7 +101,7 @@ namespace SHADE * @returns True if the values are equal within the specified tolerances. */ template - [[nodiscard]] static bool CompareFloat (T lhs, T rhs, T absTolerance = EPSILON, T relTolerance = EPSILON); + [[nodiscard]] static bool CompareFloat (T lhs, T rhs, T absTolerance = EPSILON, T relTolerance = EPSILON); private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Math/SHMathHelpers.hpp b/SHADE_Engine/src/Math/SHMathHelpers.hpp index f0a1de12..cdbe1643 100644 --- a/SHADE_Engine/src/Math/SHMathHelpers.hpp +++ b/SHADE_Engine/src/Math/SHMathHelpers.hpp @@ -13,11 +13,8 @@ // Primary Header #include "SHMathHelpers.h" -#include #include -// TODOs (Diren): Include pch? - namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -25,13 +22,51 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ template - constexpr T SHMath::DegreesToRadians(T angleInDeg) + T SHMath::Min(T lhs, T rhs) + { + return lhs < rhs ? lhs : rhs; + } + + template + T SHMath::Min(const std::initializer_list& values) + { + T min = *(values.begin()); + + for (auto value : values) + { + min = Min(min, value); + } + + return min; + } + + template + T SHMath::Max(T lhs, T rhs) + { + return lhs > rhs ? lhs : rhs; + } + + template + T SHMath::Max(const std::initializer_list& values) + { + T max = *(values.begin()); + + for (auto value : values) + { + max = Min(max, value); + } + + return max; + } + + template + T SHMath::DegreesToRadians(T angleInDeg) { return angleInDeg * static_cast(PI / 180.0f); } template - constexpr T SHMath::RadiansToDegrees(T angleInRad) + T SHMath::RadiansToDegrees(T angleInRad) { return angleInRad * static_cast(180.0f / PI); } @@ -68,6 +103,9 @@ namespace SHADE template T SHMath::GenerateRandomNumber(T lowerBound, T upperBound) { + if (lowerBound > upperBound) + std::swap(lowerBound, upperBound); + if constexpr (IsIntegral) { std::uniform_int_distribution distribution(lowerBound, upperBound); @@ -82,9 +120,10 @@ namespace SHADE template - bool CompareFloat(T lhs, T rhs, T absTolerance, T relTolerance) + bool SHMath::CompareFloat(T lhs, T rhs, T absTolerance, T relTolerance) { - return std::fabs(lhs - rhs) <= std::max(absTolerance, relTolerance * std::max(abs(lhs), abs(rhs))); + const T RTOL = relTolerance * Max(std::fabs(lhs), std::fabs(rhs)); + return std::fabs(lhs - rhs) <= Max(absTolerance, RTOL); } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMatrix.cpp b/SHADE_Engine/src/Math/SHMatrix.cpp index 8e8281d0..5f082ae5 100644 --- a/SHADE_Engine/src/Math/SHMatrix.cpp +++ b/SHADE_Engine/src/Math/SHMatrix.cpp @@ -191,7 +191,7 @@ namespace SHADE SHVec4 SHMatrix::operator*(const SHVec4& rhs) const noexcept { - return SHVec4::Transform3D(rhs, *this); + return SHVec4::Transform(rhs, *this); } SHMatrix SHMatrix::operator*(float rhs) const noexcept @@ -295,32 +295,33 @@ namespace SHADE ) != 0; } + SHMatrix::operator XMMATRIX() const noexcept + { + return XMLoadFloat4x4(this); + } + SHMatrix operator*(float lhs, const SHMatrix& rhs) noexcept { return rhs * lhs; } - /*-----------------------------------------------------------------------------------*/ /* Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ void SHMatrix::Transpose() noexcept { - const XMMATRIX M = XMLoadFloat4x4(this); - XMStoreFloat4x4(this, XMMatrixTranspose(M)); + XMStoreFloat4x4(this, XMMatrixTranspose(*this)); } void SHMatrix::Invert() noexcept { - const XMMATRIX M = XMLoadFloat4x4(this); - XMStoreFloat4x4(this, XMMatrixInverse(nullptr, M)); + XMStoreFloat4x4(this, XMMatrixInverse(nullptr, *this)); } float SHMatrix::Determinant() const noexcept { - const XMMATRIX M = XMLoadFloat4x4(this); - return XMVectorGetX(XMMatrixDeterminant(M)); + return XMVectorGetX(XMMatrixDeterminant(*this)); } std::string SHMatrix::ToString() const noexcept @@ -334,6 +335,37 @@ namespace SHADE return ss.str(); } + bool SHMatrix::Decompose(SHVec3& translation, SHVec3& rotation, SHVec3& scale) const noexcept + { + XMVECTOR s, r, t; + + if (!XMMatrixDecompose(&s, &r, &t, *this)) + return false; + + SHQuaternion orientation; + + XMStoreFloat3(&scale, s); + XMStoreFloat4(&orientation, r); + XMStoreFloat3(&translation, t); + + rotation = orientation.ToEuler(); + + return true; + } + + bool SHMatrix::Decompose(SHVec3& translation, SHQuaternion& orientation, SHVec3& scale) const noexcept + { + XMVECTOR s, r, t; + + if (!XMMatrixDecompose(&s, &r, &t, *this)) + return false; + + XMStoreFloat3(&scale, s); + XMStoreFloat4(&orientation, r); + XMStoreFloat3(&translation, t); + + return true; + } /*-----------------------------------------------------------------------------------*/ /* Static Function Member Definitions */ @@ -343,8 +375,7 @@ namespace SHADE { SHMatrix result; - const XMMATRIX M = XMLoadFloat4x4(&matrix); - XMStoreFloat4x4(&result, XMMatrixTranspose(M)); + XMStoreFloat4x4(&result, XMMatrixTranspose(matrix)); return result; } @@ -352,8 +383,7 @@ namespace SHADE { SHMatrix result; - const XMMATRIX M = XMLoadFloat4x4(&matrix); - XMStoreFloat4x4(&result, XMMatrixInverse(nullptr, M)); + XMStoreFloat4x4(&result, XMMatrixInverse(nullptr, matrix)); return result; } @@ -362,16 +392,14 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixTranslation(x, y, z)); - result.Transpose(); return result; } SHMatrix SHMatrix::Translate(const SHVec3& pos) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixTranslation(pos.x, pos.y, pos.z)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixTranslation(pos.x, pos.y, pos.z)); return result; } @@ -379,28 +407,23 @@ namespace SHADE { SHMatrix result; - const XMVECTOR A = XMLoadFloat3(&axis); - XMStoreFloat4x4(&result, XMMatrixRotationAxis(A, angleInRad)); - - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationAxis(axis, angleInRad)); return result; } SHMatrix SHMatrix::Rotate(float yaw, float pitch, float roll) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYaw(pitch, yaw, roll)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYaw(pitch, yaw, roll)); return result; } SHMatrix SHMatrix::Rotate(const SHVec3& eulerAngles) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYaw(eulerAngles.x, eulerAngles.y, eulerAngles.z)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYawFromVector(eulerAngles)); return result; } @@ -408,61 +431,55 @@ namespace SHADE { SHMatrix result; - const XMVECTOR Q = XMLoadFloat4(&q); - XMStoreFloat4x4(&result, XMMatrixRotationQuaternion(Q)); - - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationQuaternion(q)); return result; } SHMatrix SHMatrix::RotateX(float angleInRad) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixRotationX(angleInRad)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationX(angleInRad)); return result; } SHMatrix SHMatrix::RotateY(float angleInRad) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixRotationY(angleInRad)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationY(angleInRad)); return result; } SHMatrix SHMatrix::RotateZ(float angleInRad) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixRotationZ(angleInRad)); - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixRotationZ(angleInRad)); return result; } SHMatrix SHMatrix::Scale(float uniformScaleFactor) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixScaling(uniformScaleFactor, uniformScaleFactor, uniformScaleFactor)); + XMStoreFloat4x4(&result, XMMatrixScaling(uniformScaleFactor, uniformScaleFactor, uniformScaleFactor)); return result; } SHMatrix SHMatrix::Scale(float x, float y, float z) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixScaling(x, y, z)); + XMStoreFloat4x4(&result, XMMatrixScaling(x, y, z)); return result; } SHMatrix SHMatrix::Scale(const SHVec3& scale) noexcept { SHMatrix result; - XMStoreFloat4x4(&result, XMMatrixScaling(scale.x, scale.y, scale.z)); + XMStoreFloat4x4(&result, XMMatrixScalingFromVector(scale)); return result; } @@ -470,13 +487,7 @@ namespace SHADE { SHMatrix result; - const XMVECTOR EYE = XMLoadFloat3(&eye); - const XMVECTOR TGT = XMLoadFloat3(&target); - const XMVECTOR UP = XMLoadFloat3(&up); - - XMStoreFloat4x4(&result, XMMatrixLookAtRH(EYE, TGT, UP)); - - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixLookAtRH(eye, target, up)); return result; } @@ -484,13 +495,7 @@ namespace SHADE { SHMatrix result; - const XMVECTOR EYE = XMLoadFloat3(&eye); - const XMVECTOR TGT = XMLoadFloat3(&target); - const XMVECTOR UP = XMLoadFloat3(&up); - - XMStoreFloat4x4(&result, XMMatrixLookAtLH(EYE, TGT, UP)); - - result.Transpose(); + XMStoreFloat4x4(&result, XMMatrixLookAtLH(eye, target, up)); return result; } @@ -500,8 +505,8 @@ namespace SHADE const SHVec3 FWD_HAT = SHVec3::Normalise(-forward); - const XMVECTOR Z_HAT = XMVector3Normalize(XMLoadFloat3(&FWD_HAT)); - const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(XMLoadFloat3(&up), Z_HAT)); + const XMVECTOR Z_HAT = XMVector3Normalize(FWD_HAT); + const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(up, Z_HAT)); const XMVECTOR Y_HAT = XMVector3Cross(Z_HAT, X_HAT); XMStoreFloat3(reinterpret_cast(&result._11), X_HAT); @@ -512,7 +517,6 @@ namespace SHADE result._42 = pos.y; result._43 = pos.z; - result.Transpose(); return result; } @@ -522,8 +526,8 @@ namespace SHADE const SHVec3 FWD_HAT = SHVec3::Normalise(forward); - const XMVECTOR Z_HAT = XMVector3Normalize(XMLoadFloat3(&FWD_HAT)); - const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(XMLoadFloat3(&up), Z_HAT)); + const XMVECTOR Z_HAT = XMVector3Normalize(FWD_HAT); + const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(up, Z_HAT)); const XMVECTOR Y_HAT = XMVector3Cross(Z_HAT, X_HAT); XMStoreFloat3(reinterpret_cast(&result._11), X_HAT); @@ -534,7 +538,6 @@ namespace SHADE result._42 = pos.x; result._43 = pos.x; - result.Transpose(); return result; } @@ -543,8 +546,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixPerspectiveFovRH(fov, aspectRatio, nearPlane, farPlane)); - - result.Transpose(); return result; } @@ -553,8 +554,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixPerspectiveFovLH(fov, aspectRatio, nearPlane, farPlane)); - - result.Transpose(); return result; } @@ -563,8 +562,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixPerspectiveRH(width, height, nearPlane, farPlane)); - - result.Transpose(); return result; } @@ -573,8 +570,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixPerspectiveLH(width, height, nearPlane, farPlane)); - - result.Transpose(); return result; } @@ -583,8 +578,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixOrthographicRH(width, height, nearPlane, farPlane)); - - result.Transpose(); return result; } @@ -593,8 +586,6 @@ namespace SHADE SHMatrix result; XMStoreFloat4x4(&result, XMMatrixOrthographicLH(width, height, nearPlane, farPlane)); - - result.Transpose(); return result; } diff --git a/SHADE_Engine/src/Math/SHMatrix.h b/SHADE_Engine/src/Math/SHMatrix.h index 2aab05ab..4d8f1bfe 100644 --- a/SHADE_Engine/src/Math/SHMatrix.h +++ b/SHADE_Engine/src/Math/SHMatrix.h @@ -13,6 +13,8 @@ #include #include +// Project Headers +#include "SH_API.h" #include "Vector/SHVec4.h" namespace SHADE @@ -22,7 +24,6 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ class SHVec2; class SHVec3; - class SHVec4; class SHQuaternion; /*-----------------------------------------------------------------------------------*/ @@ -32,7 +33,7 @@ namespace SHADE /** * @brief Interface for a Column-Major Row Vector 4x4 Matrix. */ - class SHMatrix : public DirectX::XMFLOAT4X4 + class SH_API SHMatrix : public DirectX::XMFLOAT4X4 { public: /*---------------------------------------------------------------------------------*/ @@ -49,96 +50,119 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHMatrix (const SHMatrix& rhs) = default; - SHMatrix (SHMatrix&& rhs) = default; - ~SHMatrix () = default; + SHMatrix (const SHMatrix& rhs) = default; + SHMatrix (SHMatrix&& rhs) = default; + ~SHMatrix () = default; - SHMatrix () noexcept; - SHMatrix ( const SHVec4& r0, - const SHVec4& r1, - const SHVec4& r2, - const SHVec4& r3 = SHVec4::UnitW - ) noexcept; - SHMatrix ( - float m00, float m01, float m02, float m03, - float m10, float m11, float m12, float m13, - float m20, float m21, float m22, float m23, - float m30 = 0.0f, float m31 = 0.0f, float m32 = 0.0f, float m33 = 1.0f - ) noexcept; + SHMatrix () noexcept; + SHMatrix + ( + const SHVec4& r0, + const SHVec4& r1, + const SHVec4& r2, + const SHVec4& r3 = SHVec4::UnitW + ) noexcept; + SHMatrix + ( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30 = 0.0f, float m31 = 0.0f, float m32 = 0.0f, float m33 = 1.0f + ) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - SHMatrix& operator= (const SHMatrix& rhs) = default; - SHMatrix& operator= (SHMatrix&& rhs) = default; + SHMatrix& operator= (const SHMatrix& rhs) = default; + SHMatrix& operator= (SHMatrix&& rhs) = default; - SHMatrix& operator+= (const SHMatrix& rhs) noexcept; - SHMatrix& operator-= (const SHMatrix& rhs) noexcept; - SHMatrix& operator*= (const SHMatrix& rhs) noexcept; - SHMatrix& operator*= (float rhs) noexcept; - SHMatrix& operator/= (const SHMatrix& rhs) noexcept; - SHMatrix& operator/= (float rhs) noexcept; + operator DirectX::XMMATRIX () const noexcept; - SHMatrix operator+ (const SHMatrix& rhs) const noexcept; - SHMatrix operator- (const SHMatrix& rhs) const noexcept; - SHMatrix operator- () const noexcept; - SHMatrix operator* (const SHMatrix& rhs) const noexcept; - SHVec3 operator* (const SHVec3& rhs) const noexcept; - SHVec4 operator* (const SHVec4& rhs) const noexcept; - SHMatrix operator* (float rhs) const noexcept; - SHMatrix operator/ (const SHMatrix& rhs) const noexcept; - SHMatrix operator/ (float rhs) const noexcept; + SHMatrix& operator+= (const SHMatrix& rhs) noexcept; + SHMatrix& operator-= (const SHMatrix& rhs) noexcept; + SHMatrix& operator*= (const SHMatrix& rhs) noexcept; + SHMatrix& operator*= (float rhs) noexcept; + SHMatrix& operator/= (const SHMatrix& rhs) noexcept; + SHMatrix& operator/= (float rhs) noexcept; - bool operator== (const SHMatrix& rhs) const noexcept; - bool operator!= (const SHMatrix& rhs) const noexcept; + [[nodiscard]] SHMatrix operator+ (const SHMatrix& rhs) const noexcept; + [[nodiscard]] SHMatrix operator- (const SHMatrix& rhs) const noexcept; + [[nodiscard]] SHMatrix operator- () const noexcept; + [[nodiscard]] SHMatrix operator* (const SHMatrix& rhs) const noexcept; + [[nodiscard]] SHVec3 operator* (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec4 operator* (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHMatrix operator* (float rhs) const noexcept; + [[nodiscard]] SHMatrix operator/ (const SHMatrix& rhs) const noexcept; + [[nodiscard]] SHMatrix operator/ (float rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHMatrix& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHMatrix& rhs) const noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - void Transpose () noexcept; - void Invert () noexcept; + void Transpose () noexcept; + void Invert () noexcept; - [[nodiscard]] float Determinant () const noexcept; - [[nodiscard]] std::string ToString () const noexcept; + [[nodiscard]] float Determinant () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + /** + * @brief Decomposes a transformation matrix into translation, euler angles and scale. + * @param[out] scale The scaling factor of the matrix. + * @param[out] rotation The euler angles of the matrix. + * @param[out] translation The translation of the matrix. + * @return True if decomposition was successful. + */ + bool Decompose (SHVec3& translation, SHVec3& rotation, SHVec3& scale) const noexcept; + + /** + * @brief Decomposes a transformation matrix into translation, orientation and scale. + * @param[out] scale The scaling factor of the matrix. + * @param[out] orientation The orientation of the matrix. + * @param[out] translation The translation of the matrix. + * @return True if decomposition was successful. + */ + bool Decompose (SHVec3& translation, SHQuaternion& orientation, SHVec3& scale) const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHMatrix Transpose (const SHMatrix& matrix) noexcept; - [[nodiscard]] static SHMatrix Inverse (const SHMatrix& matrix) noexcept; + [[nodiscard]] static SHMatrix Transpose (const SHMatrix& matrix) noexcept; + [[nodiscard]] static SHMatrix Inverse (const SHMatrix& matrix) noexcept; - [[nodiscard]] static SHMatrix Translate (float x, float y, float z) noexcept; - [[nodiscard]] static SHMatrix Translate (const SHVec3& pos) noexcept; + [[nodiscard]] static SHMatrix Translate (float x, float y, float z) noexcept; + [[nodiscard]] static SHMatrix Translate (const SHVec3& pos) noexcept; - [[nodiscard]] static SHMatrix Rotate (const SHVec3& axis, float angleInRad) noexcept; - [[nodiscard]] static SHMatrix Rotate (float yaw, float pitch, float roll) noexcept; - [[nodiscard]] static SHMatrix Rotate (const SHVec3& eulerAngles) noexcept; - [[nodiscard]] static SHMatrix Rotate (const SHQuaternion& q) noexcept; - [[nodiscard]] static SHMatrix RotateX (float angleInRad) noexcept; - [[nodiscard]] static SHMatrix RotateY (float angleInRad) noexcept; - [[nodiscard]] static SHMatrix RotateZ (float angleInRad) noexcept; + [[nodiscard]] static SHMatrix Rotate (const SHVec3& axis, float angleInRad) noexcept; + [[nodiscard]] static SHMatrix Rotate (float yaw, float pitch, float roll) noexcept; + [[nodiscard]] static SHMatrix Rotate (const SHVec3& eulerAngles) noexcept; + [[nodiscard]] static SHMatrix Rotate (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHMatrix RotateX (float angleInRad) noexcept; + [[nodiscard]] static SHMatrix RotateY (float angleInRad) noexcept; + [[nodiscard]] static SHMatrix RotateZ (float angleInRad) noexcept; - [[nodiscard]] static SHMatrix Scale (float uniformScaleFactor) noexcept; - [[nodiscard]] static SHMatrix Scale (float x, float y, float z) noexcept; - [[nodiscard]] static SHMatrix Scale (const SHVec3& scale) noexcept; + [[nodiscard]] static SHMatrix Scale (float uniformScaleFactor) noexcept; + [[nodiscard]] static SHMatrix Scale (float x, float y, float z) noexcept; + [[nodiscard]] static SHMatrix Scale (const SHVec3& scale) noexcept; - [[nodiscard]] static SHMatrix LookAtRH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; - [[nodiscard]] static SHMatrix LookAtLH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; - [[nodiscard]] static SHMatrix CamToWorldRH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; - [[nodiscard]] static SHMatrix CamToWorldLH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; - [[nodiscard]] static SHMatrix PerspectiveFovRH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; - [[nodiscard]] static SHMatrix PerspectiveFovLH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; - [[nodiscard]] static SHMatrix PerspectiveRH (float width, float height, float nearPlane, float farPlane) noexcept; - [[nodiscard]] static SHMatrix PerspectiveLH (float width, float height, float nearPlane, float farPlane) noexcept; - [[nodiscard]] static SHMatrix OrthographicRH (float width, float height, float nearPlane, float farPlane) noexcept; - [[nodiscard]] static SHMatrix OrthographicLH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix LookAtRH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix LookAtLH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix CamToWorldRH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix CamToWorldLH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix PerspectiveFovRH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveFovLH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveRH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveLH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix OrthographicRH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix OrthographicLH (float width, float height, float nearPlane, float farPlane) noexcept; // TODO(Diren): Billboard, Shadow, Projection & Reflection }; - SHMatrix operator*(float lhs, const SHMatrix& rhs) noexcept; + SHMatrix operator*(float lhs, const SHMatrix& rhs) noexcept; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHQuaternion.cpp b/SHADE_Engine/src/Math/SHQuaternion.cpp index 208f131d..3564916a 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.cpp +++ b/SHADE_Engine/src/Math/SHQuaternion.cpp @@ -15,6 +15,7 @@ // Project Headers #include "Vector/SHVec3.h" #include "SHMatrix.h" +#include "SHMathHelpers.h" #include "Tools/SHLogger.h" using namespace DirectX; @@ -35,36 +36,23 @@ namespace SHADE : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) {} + SHQuaternion::SHQuaternion(const SHVec4& vec4) noexcept + : XMFLOAT4( vec4.x, vec4.y, vec4.z, vec4.w ) + {} + SHQuaternion::SHQuaternion(float _x, float _y, float _z, float _w) noexcept : XMFLOAT4( _x, _y, _z, _w ) {} - SHQuaternion::SHQuaternion(float yaw, float pitch, float roll) noexcept + SHQuaternion::SHQuaternion(const reactphysics3d::Vector3& rp3dEuler) noexcept : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) { - XMStoreFloat4(this, XMQuaternionRotationRollPitchYaw(pitch, yaw, roll)); + XMStoreFloat4(this, XMQuaternionRotationRollPitchYawFromVector(SHVec3 { rp3dEuler })); } - SHQuaternion::SHQuaternion(const SHVec3& eulerAngles) noexcept - : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) - { - const XMVECTOR V = XMLoadFloat3(&eulerAngles); - XMStoreFloat4(this, XMQuaternionRotationRollPitchYawFromVector(V)); - } - - SHQuaternion::SHQuaternion(const SHVec3& axis, float angleInRad) noexcept - : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) - { - const XMVECTOR AXIS = XMLoadFloat3(&axis); - XMStoreFloat4(this, XMQuaternionRotationAxis(AXIS, angleInRad)); - } - - SHQuaternion::SHQuaternion(const SHMatrix& rotationMatrix) noexcept - : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) - { - const XMMATRIX M = XMLoadFloat4x4(&rotationMatrix); - XMStoreFloat4(this, XMQuaternionRotationMatrix(M)); - } + SHQuaternion::SHQuaternion(const reactphysics3d::Quaternion& rp3dQuat) noexcept + : XMFLOAT4( rp3dQuat.x, rp3dQuat.y, rp3dQuat.z, rp3dQuat.w ) + {} /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ @@ -99,10 +87,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); - - XMStoreFloat4(&result, XMVectorAdd(Q1, Q2)); + XMStoreFloat4(&result, XMVectorAdd(*this, rhs)); return result; } @@ -110,10 +95,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); - - XMStoreFloat4(&result, XMVectorSubtract(Q1, Q2)); + XMStoreFloat4(&result, XMVectorSubtract(*this, rhs)); return result; } @@ -121,9 +103,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q = XMLoadFloat4(this); - - XMStoreFloat4(&result, XMVectorNegate(Q)); + XMStoreFloat4(&result, XMVectorNegate(*this)); return result; } @@ -131,10 +111,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); - - XMStoreFloat4(&result, XMQuaternionMultiply(Q1, Q2)); + XMStoreFloat4(&result, XMQuaternionMultiply(*this, rhs)); return result; } @@ -142,9 +119,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q = XMLoadFloat4(this); - - XMStoreFloat4(&result, XMVectorScale(Q, rhs)); + XMStoreFloat4(&result, XMVectorScale(*this, rhs)); return result; } @@ -152,27 +127,33 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMQuaternionInverse(XMLoadFloat4(&rhs)); - - XMStoreFloat4(&result, XMQuaternionMultiply(Q1, Q2)); + XMStoreFloat4(&result, XMQuaternionMultiply(*this, XMQuaternionInverse(rhs))); return result; } bool SHQuaternion::operator==(const SHQuaternion& rhs) const noexcept { - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); - - return XMQuaternionEqual(Q1, Q2); + return XMQuaternionEqual(*this, rhs); } bool SHQuaternion::operator!=(const SHQuaternion& rhs) const noexcept { - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); + return XMQuaternionNotEqual(*this, rhs); + } - return XMQuaternionNotEqual(Q1, Q2); + SHQuaternion::operator reactphysics3d::Quaternion() const noexcept + { + return reactphysics3d::Quaternion{ x, y, z, w }; + } + + SHQuaternion::operator reactphysics3d::Vector3() const noexcept + { + return reactphysics3d::Vector3{ ToEuler() }; + } + + SHQuaternion::operator XMVECTOR() const noexcept + { + return XMLoadFloat4(this); } SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept @@ -180,57 +161,89 @@ namespace SHADE return rhs * lhs; } + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHQuaternion::GetAngle() const noexcept + { + XMVECTOR axis; + float angle; + + XMQuaternionToAxisAngle(&axis, &angle, *this); + return angle; + } + + SHVec4 SHQuaternion::GetAxisAngle() const noexcept + { + XMVECTOR axis; + float angle; + + XMQuaternionToAxisAngle(&axis, &angle, *this); + + + return SHVec4{XMVectorGetX(axis), XMVectorGetY(axis), XMVectorGetZ(axis), angle}; + } + + + /*-----------------------------------------------------------------------------------*/ /* Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ void SHQuaternion::Invert() noexcept { - const XMVECTOR Q = XMLoadFloat4(this); - XMStoreFloat4(this, XMQuaternionInverse(Q)); + XMStoreFloat4(this, XMQuaternionInverse(*this)); } float SHQuaternion::Length() const noexcept { - const XMVECTOR Q = XMLoadFloat4(this); - return XMVectorGetX(XMQuaternionLength(Q)); + return XMVectorGetX(XMQuaternionLength(*this)); } float SHQuaternion::LengthSquared() const noexcept { - const XMVECTOR Q = XMLoadFloat4(this); - return XMVectorGetX(XMQuaternionLengthSq(Q)); + return XMVectorGetX(XMQuaternionLengthSq(*this)); } float SHQuaternion::Dot(const SHQuaternion& rhs) const noexcept { - const XMVECTOR Q1 = XMLoadFloat4(this); - const XMVECTOR Q2 = XMLoadFloat4(&rhs); - - return XMVectorGetX(XMQuaternionDot(Q1, Q2)); - } - - SHQuaternion SHQuaternion::RotateTowards(const SHQuaternion&, float) const noexcept - { - SHQuaternion result; - - // TODO (Diren) - - return result; + return XMVectorGetX(XMQuaternionDot(*this, rhs)); } SHVec3 SHQuaternion::ToEuler() const noexcept { - // TODO (Diren) + const float XX = x * x; + const float YY = y * y; + const float ZZ = z * z; - return SHVec3::Zero; + const float M_31 = 2.f * x * z + 2.f * y * w; + const float M_32 = 2.f * y * z - 2.f * x * w; + const float M_33 = 1.f - 2.f * XX - 2.f * YY; + + const float CY = sqrtf(M_33 * M_33 + M_31 * M_31); + const float CX = atan2f(-M_32, CY); + if (CY > 16.0f * SHMath::EPSILON) + { + const float M_12 = 2.f * x * y + 2.f * z * w; + const float M_22 = 1.f - 2.f * XX - 2.f * ZZ; + + return SHVec3(CX, atan2f(M_31, M_33), atan2f(M_12, M_22)); + } + else + { + const float m11 = 1.f - 2.f * YY - 2.f * ZZ; + const float m21 = 2.f * x * y - 2.f * z * w; + + return SHVec3(CX, 0.f, atan2f(-m21, m11)); + } } std::string SHQuaternion::ToString() const noexcept { std::stringstream ss; ss << std::fixed << std::setprecision(3); - ss << "<" << x << ", " << y << ", " << z << ", " << w <<">"; + ss << "<" << w << ", " << x << ", " << y << ", " << z <<">"; return ss.str(); } @@ -238,13 +251,43 @@ namespace SHADE /* Static Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ + SHQuaternion SHQuaternion::FromEuler(const SHVec3& eulerAngles) noexcept + { + SHQuaternion result; + + XMStoreFloat4(&result, XMQuaternionRotationRollPitchYawFromVector(eulerAngles)); + return result; + } + + SHQuaternion SHQuaternion::FromPitchYawRoll(float pitch, float yaw, float roll) noexcept + { + SHQuaternion result; + + XMStoreFloat4(&result, XMQuaternionRotationRollPitchYaw(pitch, yaw, roll)); + return result; + } + + SHQuaternion SHQuaternion::FromAxisAngle(const SHVec3& axis, float angle) noexcept + { + SHQuaternion result; + + XMStoreFloat4(&result, XMQuaternionRotationAxis(axis, angle)); + return result; + } + + SHQuaternion SHQuaternion::FromRotationMatrix(const SHMatrix& rotationMatrix) noexcept + { + SHQuaternion result; + + XMStoreFloat4(&result, XMQuaternionRotationMatrix(rotationMatrix)); + return result; + } + SHQuaternion SHQuaternion::Normalise(const SHQuaternion& q) noexcept { SHQuaternion result; - const XMVECTOR Q = XMLoadFloat4(&q); - - XMStoreFloat4(&result, XMQuaternionNormalize(Q)); + XMStoreFloat4(&result, XMQuaternionNormalize(q)); return result; } @@ -252,9 +295,7 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q = XMLoadFloat4(&q); - - XMStoreFloat4(&result, XMQuaternionConjugate(Q)); + XMStoreFloat4(&result, XMQuaternionConjugate(q)); return result; } @@ -262,26 +303,37 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q = XMLoadFloat4(&q); - XMStoreFloat4(&result, XMQuaternionInverse(Q)); - + XMStoreFloat4(&result, XMQuaternionInverse(q)); return result; } - float SHQuaternion::Angle(const SHQuaternion&, const SHQuaternion&) noexcept + float SHQuaternion::Angle(const SHQuaternion& q1, const SHQuaternion& q2) noexcept { - // TODO (Diren) + XMVECTOR R = XMQuaternionMultiply(XMQuaternionConjugate(q1), q2); - return 0.0f; + const float RS = XMVectorGetW(R); + R = XMVector3Length(R); + return 2.0f * atan2f(XMVectorGetX(R), RS); } - SHQuaternion SHQuaternion::Lerp(const SHQuaternion&, const SHQuaternion&, float) noexcept + SHQuaternion SHQuaternion::Lerp(const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept { SHQuaternion result; - // TODO (Diren) + XMVECTOR R = XMVectorZero(); + if (XMVector4GreaterOrEqual(XMVector4Dot(q1, q2), R)) + { + R = XMVectorLerp(q1, q2, t); + } + else + { + const XMVECTOR X0 = XMVectorMultiply(q1, XMVectorReplicate(1.f - t)); + const XMVECTOR X1 = XMVectorMultiply(q2, XMVectorReplicate(t)); + R = XMVectorSubtract(X0, X1); + } + XMStoreFloat4(&result, XMQuaternionNormalize(R)); return result; } @@ -289,19 +341,105 @@ namespace SHADE { SHQuaternion result; - const XMVECTOR Q1 = XMLoadFloat4(&q1); - const XMVECTOR Q2 = XMLoadFloat4(&q2); - - XMStoreFloat4(&result, XMQuaternionSlerp(Q1, Q2, t)); + XMStoreFloat4(&result, XMQuaternionSlerp(q1, q2, t)); return result; } - SHQuaternion SHQuaternion::Rotate(const SHVec3& , const SHVec3&) noexcept + SHQuaternion SHQuaternion::ClampedLerp(const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin, float tMax) noexcept + { + return Lerp(q1, q2, std::clamp(t, tMin, tMax)); + } + + + SHQuaternion SHQuaternion::ClampedSlerp(const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin, float tMax) noexcept + { + return Slerp(q1, q2, std::clamp(t, tMin, tMax)); + } + + SHQuaternion SHQuaternion::FromToRotation(const SHVec3& from, const SHVec3& to) noexcept + { + // Melax, "The Shortest Arc Quaternion", Game Programming Gems + + SHQuaternion result; + + const XMVECTOR F = XMVector3Normalize(from); + const XMVECTOR T = XMVector3Normalize(to); + + const float dot = XMVectorGetX(XMVector3Dot(F, T)); + if (dot >= 1.f) + { + result = Identity; + } + else if (dot <= -1.f) + { + XMVECTOR axis = XMVector3Cross(F, SHVec3::Right); + if (XMVector3NearEqual(XMVector3LengthSq(axis), g_XMZero, g_XMEpsilon)) + { + axis = XMVector3Cross(F, SHVec3::Up); + } + + const XMVECTOR Q = XMQuaternionRotationAxis(axis, XM_PI); + XMStoreFloat4(&result, Q); + } + else + { + const XMVECTOR C = XMVector3Cross(F, T); + XMStoreFloat4(&result, C); + + const float s = sqrtf((1.f + dot) * 2.f); + result.x /= s; + result.y /= s; + result.z /= s; + result.w = s * 0.5f; + } + + return result; + } + + SHQuaternion SHQuaternion::LookRotation(const SHVec3& forward, const SHVec3& up) noexcept { SHQuaternion result; - // TODO (Diren) + const SHQuaternion Q1 = FromToRotation(SHVec3::Forward, forward); + const XMVECTOR C = XMVector3Cross(forward, up); + if (XMVector3NearEqual(XMVector3LengthSq(C), g_XMZero, g_XMEpsilon)) + { + // forward and up are co-linear + return Q1; + } + + SHVec3 qU; + XMStoreFloat3(&qU, XMQuaternionMultiply(Q1, SHVec3::Up)); + + const SHQuaternion Q2 = FromToRotation(qU, up); + + XMStoreFloat4(&result, XMQuaternionMultiply(Q2, Q1)); + + return result; + } + + SHQuaternion SHQuaternion::RotateTowards(const SHQuaternion& from, const SHQuaternion& to, float maxAngleInRad) noexcept + { + SHQuaternion result; + + // We can use the conjugate here instead of inverse assuming q1 & q2 are normalized. + const XMVECTOR R = XMQuaternionMultiply(XMQuaternionConjugate(from), to); + + const float RS = XMVectorGetW(R); + const XMVECTOR L = XMVector3Length(R); + const float angle = 2.f * atan2f(XMVectorGetX(L), RS); + if (angle > maxAngleInRad) + { + const XMVECTOR delta = XMQuaternionRotationAxis(R, maxAngleInRad); + const XMVECTOR Q = XMQuaternionMultiply(delta, from); + XMStoreFloat4(&result, Q); + } + else + { + // Don't overshoot. + result = to; + } return result; } diff --git a/SHADE_Engine/src/Math/SHQuaternion.h b/SHADE_Engine/src/Math/SHQuaternion.h index 27088284..fa5b5d36 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.h +++ b/SHADE_Engine/src/Math/SHQuaternion.h @@ -11,8 +11,13 @@ #pragma once #include +#include + #include +// Project Headers +#include "SH_API.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -20,13 +25,14 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ class SHVec3; + class SHVec4; class SHMatrix; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHQuaternion : public DirectX::XMFLOAT4 + class SH_API SHQuaternion : public DirectX::XMFLOAT4 { public: /*---------------------------------------------------------------------------------*/ @@ -39,68 +45,91 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHQuaternion (const SHQuaternion& rhs) = default; - SHQuaternion (SHQuaternion&& rhs) = default; + SHQuaternion (const SHQuaternion& rhs) = default; + SHQuaternion (SHQuaternion&& rhs) = default; - SHQuaternion () noexcept; - SHQuaternion (float x, float y, float z, float w) noexcept; - SHQuaternion (float yaw, float pitch, float roll) noexcept; - SHQuaternion (const SHVec3& eulerAngles) noexcept; - SHQuaternion (const SHVec3& axis, float angleInRad) noexcept; - SHQuaternion (const SHMatrix& rotationMatrix) noexcept; + SHQuaternion () noexcept; + SHQuaternion (const SHVec4& vec4) noexcept; + SHQuaternion (float x, float y, float z, float w) noexcept; + + // Conversion from other math types + + SHQuaternion (const reactphysics3d::Vector3& rp3dEuler) noexcept; + SHQuaternion (const reactphysics3d::Quaternion& rp3dQuat) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHQuaternion& operator= (const SHQuaternion& rhs) = default; - [[nodiscard]] SHQuaternion& operator= (SHQuaternion&& rhs) = default; + SHQuaternion& operator= (const SHQuaternion& rhs) = default; + SHQuaternion& operator= (SHQuaternion&& rhs) = default; + + SHQuaternion& operator+= (const SHQuaternion& rhs) noexcept; + SHQuaternion& operator-= (const SHQuaternion& rhs) noexcept; + SHQuaternion& operator*= (const SHQuaternion& rhs) noexcept; + SHQuaternion& operator*= (float rhs) noexcept; + SHQuaternion& operator/= (const SHQuaternion& rhs) noexcept; - [[nodiscard]] SHQuaternion& operator+= (const SHQuaternion& rhs) noexcept; - [[nodiscard]] SHQuaternion& operator-= (const SHQuaternion& rhs) noexcept; - [[nodiscard]] SHQuaternion& operator*= (const SHQuaternion& rhs) noexcept; - [[nodiscard]] SHQuaternion& operator*= (float rhs) noexcept; - [[nodiscard]] SHQuaternion& operator/= (const SHQuaternion& rhs) noexcept; + [[nodiscard]] SHQuaternion operator+ (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator- (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator- () const noexcept; + [[nodiscard]] SHQuaternion operator* (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator* (float rhs) const noexcept; + [[nodiscard]] SHQuaternion operator/ (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHQuaternion operator+ (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHQuaternion operator- (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHQuaternion operator- () const noexcept; - [[nodiscard]] SHQuaternion operator* (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHQuaternion operator* (float rhs) const noexcept; - [[nodiscard]] SHQuaternion operator/ (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] bool operator== (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] bool operator== (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] bool operator!= (const SHQuaternion& rhs) const noexcept; + // Conversion to other math types used by SHADE + + operator reactphysics3d::Quaternion () const noexcept; + operator reactphysics3d::Vector3 () const noexcept; + operator DirectX::XMVECTOR () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] float GetAngle () const noexcept; + [[nodiscard]] SHVec4 GetAxisAngle () const noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - void Invert () noexcept; + void Invert () noexcept; - [[nodiscard]] float Length () const noexcept; - [[nodiscard]] float LengthSquared () const noexcept; - [[nodiscard]] float Dot (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHQuaternion RotateTowards (const SHQuaternion& target, float maxAngleInRad) const noexcept; + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] float Dot (const SHQuaternion& rhs) const noexcept; - [[nodiscard]] SHVec3 ToEuler () const noexcept; - [[nodiscard]] std::string ToString () const noexcept; + [[nodiscard]] SHVec3 ToEuler () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHQuaternion Normalise (const SHQuaternion& q) noexcept; - [[nodiscard]] static SHQuaternion Conjugate (const SHQuaternion& q) noexcept; - [[nodiscard]] static SHQuaternion Inverse (const SHQuaternion& q) noexcept; - [[nodiscard]] static float Angle (const SHQuaternion& q1, const SHQuaternion& q2) noexcept; + [[nodiscard]] static SHQuaternion FromEuler (const SHVec3& eulerAngles) noexcept; + [[nodiscard]] static SHQuaternion FromPitchYawRoll (float pitch, float yaw, float roll) noexcept; + [[nodiscard]] static SHQuaternion FromAxisAngle (const SHVec3& axis, float angle) noexcept; + [[nodiscard]] static SHQuaternion FromRotationMatrix(const SHMatrix& rotationMatrix) noexcept; - [[nodiscard]] static SHQuaternion Lerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; - [[nodiscard]] static SHQuaternion Slerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; + [[nodiscard]] static SHQuaternion Normalise (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHQuaternion Conjugate (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHQuaternion Inverse (const SHQuaternion& q) noexcept; + [[nodiscard]] static float Angle (const SHQuaternion& q1, const SHQuaternion& q2) noexcept; - [[nodiscard]] static SHQuaternion Rotate (const SHVec3& from, const SHVec3& to) noexcept; + [[nodiscard]] static SHQuaternion Lerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; + [[nodiscard]] static SHQuaternion Slerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; + [[nodiscard]] static SHQuaternion ClampedLerp (const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; + [[nodiscard]] static SHQuaternion ClampedSlerp (const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; + + [[nodiscard]] static SHQuaternion FromToRotation (const SHVec3& from, const SHVec3& to) noexcept; + [[nodiscard]] static SHQuaternion LookRotation (const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHQuaternion RotateTowards (const SHQuaternion& from, const SHQuaternion& to, float maxAngleInRad) noexcept; }; - SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept; + SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHRay.cpp b/SHADE_Engine/src/Math/SHRay.cpp new file mode 100644 index 00000000..87f12b81 --- /dev/null +++ b/SHADE_Engine/src/Math/SHRay.cpp @@ -0,0 +1,60 @@ +/**************************************************************************************** + * \file SHRay.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Ray. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHRay.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRay::SHRay() noexcept + : direction { 0.0f, 0.0f, 1.0f } + {} + + SHRay::SHRay(const SHVec3& pos, const SHVec3& dir) noexcept + : position { pos } + , direction { dir } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHRay::operator==(const SHRay& rhs) noexcept + { + const XMVECTOR LHS_POS = XMLoadFloat3(&position); + const XMVECTOR RHS_POS = XMLoadFloat3(&rhs.position); + + const XMVECTOR LHS_DIR = XMLoadFloat3(&direction); + const XMVECTOR RHS_DIR = XMLoadFloat3(&rhs.direction); + + return XMVector3Equal(LHS_POS, RHS_POS) && XMVector3NotEqual(LHS_DIR, RHS_DIR); + } + + bool SHRay::operator!=(const SHRay& rhs) noexcept + { + const XMVECTOR LHS_POS = XMLoadFloat3(&position); + const XMVECTOR RHS_POS = XMLoadFloat3(&rhs.position); + + const XMVECTOR LHS_DIR = XMLoadFloat3(&direction); + const XMVECTOR RHS_DIR = XMLoadFloat3(&rhs.direction); + + return XMVector3NotEqual(LHS_POS, RHS_POS) || XMVector3NotEqual(LHS_DIR, RHS_DIR); + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHRay.h b/SHADE_Engine/src/Math/SHRay.h new file mode 100644 index 00000000..29d55b16 --- /dev/null +++ b/SHADE_Engine/src/Math/SHRay.h @@ -0,0 +1,54 @@ +/**************************************************************************************** + * \file SHRay.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Ray. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SH_API.h" +#include "Vector/SHVec3.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHRay + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHVec3 position; + SHVec3 direction; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHRay() noexcept; + SHRay(const SHVec3& pos, const SHVec3& dir) noexcept; + SHRay(const SHRay& rhs) noexcept = default; + SHRay(SHRay&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHRay& operator= (const SHRay& rhs) noexcept = default; + SHRay& operator= (SHRay&& rhs) noexcept = default; + + [[nodiscard]] bool operator==(const SHRay& rhs) noexcept; + [[nodiscard]] bool operator!=(const SHRay& rhs) noexcept; + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransform.cpp b/SHADE_Engine/src/Math/Transform/SHTransform.cpp new file mode 100644 index 00000000..ef7c5fda --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransform.cpp @@ -0,0 +1,70 @@ +/**************************************************************************************** + * \file SHTransform.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Transform. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHTransform.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHTransform SHTransform::Identity { SHVec3::Zero, SHVec3::Zero, SHVec3::One }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHTransform::SHTransform() noexcept + : position { SHVec3::Zero } + , orientation { SHQuaternion::Identity } + , scale { SHVec3::One } + {} + + SHTransform::SHTransform(const SHVec3& pos, const SHVec3& rot, const SHVec3& scl) noexcept + : position { pos } + , orientation { SHQuaternion::FromEuler(rot) } + , scale { scl } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHTransform::operator==(const SHTransform& rhs) const noexcept + { + return !(position != rhs.position || orientation != rhs.orientation || scale != rhs.scale); + } + + bool SHTransform::operator!=(const SHTransform& rhs) const noexcept + { + return (position != rhs.position || orientation != rhs.orientation || scale != rhs.scale); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHMatrix& SHTransform::ComputeTRS() + { + + const SHMatrix T = SHMatrix::Translate(position); + const SHMatrix R = SHMatrix::Rotate(orientation); + const SHMatrix S = SHMatrix::Scale(scale); + + trs = S * R * T; + + return trs; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransform.h b/SHADE_Engine/src/Math/Transform/SHTransform.h new file mode 100644 index 00000000..2e7d236c --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransform.h @@ -0,0 +1,69 @@ +/**************************************************************************************** + * \file SHTransform.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Transform. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "SH_API.h" +#include "Math/Vector/SHVec3.h" +#include "Math/SHQuaternion.h" +#include "Math/SHMatrix.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHTransform + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static const SHTransform Identity; + + SHVec3 position; + SHQuaternion orientation; + SHVec3 scale; + + SHMatrix trs; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHTransform (const SHTransform&) = default; + SHTransform (SHTransform&&) = default; + ~SHTransform () = default; + + SHTransform () noexcept; + SHTransform (const SHVec3& pos, const SHVec3& rot, const SHVec3& scl) noexcept; + SHTransform (const SHVec3& pos, const SHQuaternion& quat, const SHVec3& scl) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHTransform& operator= (const SHTransform&) = default; + SHTransform& operator= (SHTransform&&) = default; + + [[nodiscard]] bool operator==(const SHTransform& rhs) const noexcept; + [[nodiscard]] bool operator!=(const SHTransform& rhs) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + const SHMatrix& ComputeTRS(); + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransformComponent.cpp b/SHADE_Engine/src/Math/Transform/SHTransformComponent.cpp new file mode 100644 index 00000000..fa0befa3 --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransformComponent.cpp @@ -0,0 +1,191 @@ +/**************************************************************************************** + * \file SHTransformComponent.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Transform Component + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHTransformComponent.h" +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Reflection/SHReflectionMetadata.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHTransformComponent::SHTransformComponent() noexcept + : SHComponent {} + , dirty { true } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHTransformComponent::HasChanged() const noexcept + { + return dirty; + } + + const SHVec3& SHTransformComponent::GetLocalPosition() const noexcept + { + return local.position; + } + + const SHVec3& SHTransformComponent::GetLocalRotation() const noexcept + { + return localRotation; + } + + const SHQuaternion& SHTransformComponent::GetLocalOrientation() const noexcept + { + return local.orientation; + } + + const SHVec3& SHTransformComponent::GetLocalScale() const noexcept + { + return local.scale; + } + + const SHVec3& SHTransformComponent::GetWorldPosition() const noexcept + { + return world.position; + } + + const SHVec3& SHTransformComponent::GetWorldRotation() const noexcept + { + return worldRotation; + } + + const SHQuaternion& SHTransformComponent::GetWorldOrientation() const noexcept + { + return world.orientation; + } + + const SHVec3& SHTransformComponent::GetWorldScale() const noexcept + { + return world.scale; + } + + const SHMatrix& SHTransformComponent::GetLocalToWorld() const noexcept + { + return local.trs; + } + + SHMatrix SHTransformComponent::GetWorldToLocal() const noexcept + { + return SHMatrix::Inverse(local.trs); + } + + const SHMatrix& SHTransformComponent::GetTRS() const noexcept + { + return world.trs; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHTransformComponent::SetLocalPosition(const SHVec3& newLocalPosition) noexcept + { + dirty = true; + local.position = newLocalPosition; + } + + void SHTransformComponent::SetLocalRotation(const SHVec3& newLocalRotation) noexcept + { + dirty = true; + + localRotation = newLocalRotation; + updateQueue.push({ UpdateCommandType::LOCAL_ROTATION, newLocalRotation }); + } + + void SHTransformComponent::SetLocalRotation(float pitch, float yaw, float roll) noexcept + { + dirty = true; + + localRotation.x = pitch; + localRotation.y = yaw; + localRotation.z = roll; + + updateQueue.push({ UpdateCommandType::LOCAL_ROTATION, SHVec3{pitch, yaw, roll} }); + } + + void SHTransformComponent::SetLocalOrientation(const SHQuaternion& newLocalOrientation) noexcept + { + dirty = true; + + local.orientation = newLocalOrientation; + updateQueue.push({ UpdateCommandType::LOCAL_ORIENTATION, newLocalOrientation }); + } + + void SHTransformComponent::SetLocalScale(const SHVec3& newLocalScale) noexcept + { + dirty = true; + local.scale = newLocalScale; + } + + void SHTransformComponent::SetWorldPosition(const SHVec3& newWorldPosition) noexcept + { + dirty = true; + + world.position = newWorldPosition; + updateQueue.push({ UpdateCommandType::WORLD_POSITION, newWorldPosition }); + } + + void SHTransformComponent::SetWorldRotation(const SHVec3& newWorldRotation) noexcept + { + dirty = true; + + worldRotation = newWorldRotation; + updateQueue.push({ UpdateCommandType::WORLD_ROTATION, newWorldRotation }); + } + + void SHTransformComponent::SetWorldRotation(float pitch, float yaw, float roll) noexcept + { + dirty = true; + + worldRotation.x = pitch; + worldRotation.y = yaw; + worldRotation.z = roll; + + updateQueue.push({ UpdateCommandType::WORLD_ROTATION, SHVec3{ pitch, yaw, roll} }); + } + + void SHTransformComponent::SetWorldOrientation(const SHQuaternion& newWorldOrientation) noexcept + { + dirty = true; + + world.orientation = newWorldOrientation; + updateQueue.push({ UpdateCommandType::WORLD_ORIENTATION, newWorldOrientation }); + } + + void SHTransformComponent::SetWorldScale(const SHVec3& newWorldScale) noexcept + { + dirty = true; + + world.scale = newWorldScale; + updateQueue.push({ UpdateCommandType::WORLD_SCALE, newWorldScale }); + } + +} // namespace SHADE + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::class_("Transform Component") + .property("Translate" ,&SHTransformComponent::GetLocalPosition ,&SHTransformComponent::SetLocalPosition ) (metadata(META::tooltip, "Translate")) + .property("Rotate" ,&SHTransformComponent::GetLocalRotation ,select_overload(&SHTransformComponent::SetLocalRotation)) (metadata(META::tooltip, "Rotate"), metadata(META::angleInRad, true)) + .property("Scale" ,&SHTransformComponent::GetLocalScale ,&SHTransformComponent::SetLocalScale ) (metadata(META::tooltip, "Scale")); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransformComponent.h b/SHADE_Engine/src/Math/Transform/SHTransformComponent.h new file mode 100644 index 00000000..2fe67bdd --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransformComponent.h @@ -0,0 +1,142 @@ +/**************************************************************************************** + * \file SHTransformComponent.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Transform Component + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +#include + +// Project Headers +#include "SH_API.h" +#include "ECS_Base/Components/SHComponent.h" +#include "SHTransform.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHTransformComponent : public SHComponent + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHTransformSystem; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + ~SHTransformComponent () override = default; + + SHTransformComponent () noexcept; + SHTransformComponent (const SHTransformComponent& rhs) noexcept = default; + SHTransformComponent (SHTransformComponent&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHTransformComponent& operator=(const SHTransformComponent& rhs) noexcept = default; + SHTransformComponent& operator=(SHTransformComponent&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool HasChanged () const noexcept; + + [[nodiscard]] const SHVec3& GetLocalPosition () const noexcept; + [[nodiscard]] const SHVec3& GetLocalRotation () const noexcept; + [[nodiscard]] const SHQuaternion& GetLocalOrientation () const noexcept; + [[nodiscard]] const SHVec3& GetLocalScale () const noexcept; + [[nodiscard]] const SHVec3& GetWorldPosition () const noexcept; + [[nodiscard]] const SHVec3& GetWorldRotation () const noexcept; + [[nodiscard]] const SHQuaternion& GetWorldOrientation () const noexcept; + [[nodiscard]] const SHVec3& GetWorldScale () const noexcept; + + [[nodiscard]] const SHMatrix& GetLocalToWorld () const noexcept; + [[nodiscard]] SHMatrix GetWorldToLocal () const noexcept; + + [[nodiscard]] const SHMatrix& GetTRS () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetLocalPosition (const SHVec3& newLocalPosition) noexcept; + void SetLocalRotation (const SHVec3& newLocalRotation) noexcept; + void SetLocalRotation (float pitch, float yaw, float roll) noexcept; + void SetLocalOrientation (const SHQuaternion& newLocalOrientation) noexcept; + void SetLocalScale (const SHVec3& newLocalScale) noexcept; + void SetWorldPosition (const SHVec3& newWorldPosition) noexcept; + void SetWorldRotation (const SHVec3& newWorldRotation) noexcept; + void SetWorldRotation (float pitch, float yaw, float roll) noexcept; + void SetWorldOrientation (const SHQuaternion& newWorldOrientation) noexcept; + void SetWorldScale (const SHVec3& newWorldScale) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + // Differentiate between rotation and orientation for setters + // Setting a quaternion directly is different from using euler angle rotations. + + enum class UpdateCommandType + { + LOCAL_ROTATION + , LOCAL_ORIENTATION + , WORLD_POSITION + , WORLD_ROTATION + , WORLD_ORIENTATION + , WORLD_SCALE + }; + + struct UpdateCommand + { + public: + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + + UpdateCommandType type; + SHVec4 data; + }; + + using UpdateQueue = std::queue; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + bool dirty; + + // We store euler angle rotations separately to interface with transform quaternions. + // Reading quaternions are unreliable. + + SHVec3 localRotation; // Stored in Radians + SHVec3 worldRotation; // Stored in Radians + + SHTransform local; // Local TRS holds Local To World Transform + SHTransform world; + + UpdateQueue updateQueue; + + RTTR_ENABLE() + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp b/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp new file mode 100644 index 00000000..94c133dd --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp @@ -0,0 +1,305 @@ +/**************************************************************************************** + * \file SHTransformSystem.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Transform System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHTransformSystem.h" + +// Project Headers +#include "Scene/SHSceneManager.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHTransformSystem::TransformPostLogicUpdate::TransformPostLogicUpdate() + : SHSystemRoutine { "Transform Post-Logic Update", true } + {} + + SHTransformSystem::TransformPostPhysicsUpdate::TransformPostPhysicsUpdate() + : SHSystemRoutine { "Transform Post-Physics Update", false } + {} + + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHTransformSystem::TransformPostLogicUpdate::Execute(double) noexcept + { + // Get the current scene graph to traverse and update + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + UpdateEntity(SCENE_GRAPH.GetRoot(), !IsRunInEditorPause); + } + + void SHTransformSystem::TransformPostPhysicsUpdate::Execute(double) noexcept + { + // Get the current scene graph to traverse and update + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + UpdateEntity(SCENE_GRAPH.GetRoot(), IsRunInEditorPause); + } + + void SHTransformSystem::Init() + { + const std::shared_ptr CHANGE_PARENT_RECEIVER { std::make_shared>(this, &SHTransformSystem::ChangeParent) }; + const ReceiverPtr CHANGE_PARENT_RECEIVER_PTR = std::dynamic_pointer_cast(CHANGE_PARENT_RECEIVER); + SHEventManager::SubscribeTo(SH_SCENEGRAPH_CHANGE_PARENT_EVENT, CHANGE_PARENT_RECEIVER_PTR); + } + + void SHTransformSystem::Exit() + { + + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHTransformSystem::UpdateChildrenLocalTransforms(SHSceneNode* node) + { + // Structure is similar to update entity, albeit without a queue to do being a forced update + for (const auto* child : node->GetChildren()) + { + if (auto* childTransform = SHComponentManager::GetComponent_s(child->GetEntityID()); childTransform) + { + const bool IS_NODE_ACTIVE = child->IsActive(); + if (IS_NODE_ACTIVE && childTransform->isActive) + { + // Recompute local transform and store localToWorld Matrix + SHMatrix localToWorld = SHMatrix::Identity; + SHMatrix worldToLocal = SHMatrix::Identity; + + const auto* parent = SHComponentManager::GetComponent_s(node->GetEntityID()); + + if (parent != nullptr) // Not the root + { + localToWorld = parent->GetTRS(); + worldToLocal = SHMatrix::Inverse(localToWorld); + } + + // Maintain World Transform and recompute Local Transform + + // Compute Local Position + childTransform->local.position = SHVec3::Transform(childTransform->world.position, worldToLocal); + + + childTransform->localRotation = childTransform->worldRotation; + childTransform->local.scale = childTransform->world.scale; + + if (parent) + { + // Compute Local Rotation + childTransform->localRotation -= parent->GetLocalRotation(); + + // Compute Local Scale + childTransform->local.scale /= parent->GetLocalScale(); + } + + childTransform->local.trs = localToWorld; + } + } + } + } + + void SHTransformSystem::UpdateEntity(const SHSceneNode* node, bool clearDirtyFlag) + { + const auto* NODE_TRANSFORM = SHComponentManager::GetComponent_s(node->GetEntityID()); + const bool HAS_PARENT_CHANGED = NODE_TRANSFORM && NODE_TRANSFORM->dirty; + + for (const auto* child : node->GetChildren()) + { + auto* childTransform = SHComponentManager::GetComponent_s(child->GetEntityID()); + if (childTransform) + { + // Only update if node in hierarchy and component are both active + const bool IS_NODE_ACTIVE = child->IsActive(); + if (IS_NODE_ACTIVE && childTransform->isActive) + { + if (childTransform->dirty || HAS_PARENT_CHANGED) + { + UpdateTransform(*childTransform, NODE_TRANSFORM); + childTransform->dirty = true; + } + } + } + + UpdateEntity(child, clearDirtyFlag); + + // Clear dirty flag after all children are updated + if (childTransform && clearDirtyFlag) + childTransform->dirty = false; + } + } + + void SHTransformSystem::UpdateTransform(SHTransformComponent& tf, const SHTransformComponent* parent) + { + SHMatrix localToWorld = SHMatrix::Identity; + SHMatrix worldToLocal = SHMatrix::Identity; + + bool convertRotation = true; + + if (parent) + { + localToWorld = parent->GetTRS(); + worldToLocal = SHMatrix::Inverse(localToWorld); + } + + while (!tf.updateQueue.empty()) + { + const auto& UPDATE_COMMAND = tf.updateQueue.front(); + + switch (UPDATE_COMMAND.type) + { + case SHTransformComponent::UpdateCommandType::LOCAL_ROTATION: + { + convertRotation = true; + break; + } + case SHTransformComponent::UpdateCommandType::LOCAL_ORIENTATION: + { + convertRotation = false; + break; + } + case SHTransformComponent::UpdateCommandType::WORLD_POSITION: + { + tf.local.position = SHVec3::Transform(UPDATE_COMMAND.data.ToVec3(), worldToLocal); + break; + } + case SHTransformComponent::UpdateCommandType::WORLD_ROTATION: + { + tf.localRotation = UPDATE_COMMAND.data.ToVec3(); + if (parent) + tf.localRotation -= parent->GetLocalRotation(); + + convertRotation = true; + + break; + } + case SHTransformComponent::UpdateCommandType::WORLD_ORIENTATION: + { + tf.local.orientation = UPDATE_COMMAND.data; + if (parent) + tf.local.orientation /= parent->GetLocalOrientation(); + + convertRotation = false; + + break; + } + case SHTransformComponent::UpdateCommandType::WORLD_SCALE: + { + tf.local.scale = UPDATE_COMMAND.data.ToVec3(); + if (parent) + tf.local.scale /= parent->GetLocalScale(); + + break; + } + // Redundant + default: break; + } + + tf.updateQueue.pop(); + } + + tf.local.trs = localToWorld; + + // Compute world transforms + tf.world.position = SHVec3::Transform(tf.local.position, localToWorld); + tf.world.scale = tf.local.scale * (parent ? parent->GetLocalScale() : SHVec3::One); + + + + if (convertRotation) + { + tf.worldRotation = tf.localRotation + (parent ? parent->GetLocalRotation() : SHVec3::Zero); + + // Set the orientation + // Wrap rotations between -720 and 720 and convert to radians + SHVec3 worldRotRad, localRotRad; + for (size_t i = 0; i < SHVec3::SIZE; ++i) + { + worldRotRad[i] = SHMath::Wrap(tf.worldRotation[i], -2.0f * SHMath::TWO_PI, 2.0f * SHMath::TWO_PI); + localRotRad[i] = SHMath::Wrap(tf.localRotation[i], -2.0f * SHMath::TWO_PI, 2.0f * SHMath::TWO_PI); + } + + tf.world.orientation = SHQuaternion::FromEuler(worldRotRad); + tf.local.orientation = SHQuaternion::FromEuler(localRotRad); + } + else + { + tf.world.orientation = (parent ? parent->GetLocalOrientation() : SHQuaternion::Identity) * tf.local.orientation; + + // Set the euler angle rotations + tf.worldRotation = tf.world.orientation.ToEuler(); + tf.localRotation = tf.local.orientation.ToEuler(); + } + + tf.world.ComputeTRS(); + } + + SHEventHandle SHTransformSystem::ChangeParent(SHEventPtr changeParentEvent) + { + const auto& EVENT_DATA = reinterpret_cast*>(changeParentEvent.get()); + + auto* node = EVENT_DATA->data->node; + auto* tf = SHComponentManager::GetComponent_s(node->GetEntityID()); + + if (tf == nullptr) + return EVENT_DATA->handle; + + // Recompute local transform and store localToWorld Matrix + SHMatrix localToWorld = SHMatrix::Identity; + SHMatrix worldToLocal = SHMatrix::Identity; + + auto* newParent = EVENT_DATA->data->newParent; + const auto* PARENT_TF = SHComponentManager::GetComponent_s(newParent->GetEntityID()); + if (PARENT_TF != nullptr) // Not the root + { + localToWorld = PARENT_TF->GetTRS(); + worldToLocal = SHMatrix::Inverse(localToWorld); + } + + // Maintain World Transform and recompute Local Transform + + // Compute Local Position + tf->local.position = SHVec3::Transform(tf->world.position, worldToLocal); + + + tf->localRotation = tf->worldRotation; + tf->local.scale = tf->world.scale; + + if (PARENT_TF != nullptr) + { + // Compute Local Rotation + tf->localRotation -= PARENT_TF->GetLocalRotation(); + + // Compute Local Scale + tf->local.scale /= PARENT_TF->GetLocalScale(); + } + + tf->local.trs = localToWorld; + + // Propagate maintaining world transform down the branch + UpdateChildrenLocalTransforms(node); + + return EVENT_DATA->handle; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Transform/SHTransformSystem.h b/SHADE_Engine/src/Math/Transform/SHTransformSystem.h new file mode 100644 index 00000000..bb373f3a --- /dev/null +++ b/SHADE_Engine/src/Math/Transform/SHTransformSystem.h @@ -0,0 +1,103 @@ +/**************************************************************************************** + * \file SHTransformSystem.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the Transform System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Scene/SHSceneGraph.h" +#include "SHTransformComponent.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHTransformSystem final : public SHSystem + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHTransformSystem () = default; + ~SHTransformSystem () override = default; + + SHTransformSystem (const SHTransformSystem&) = delete; + SHTransformSystem (SHTransformSystem&&) = delete; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHTransformSystem& operator= (const SHTransformSystem&) = delete; + SHTransformSystem& operator= (SHTransformSystem&&) = delete; + + /*---------------------------------------------------------------------------------*/ + /* System Routines */ + /*---------------------------------------------------------------------------------*/ + + class SH_API TransformPostLogicUpdate final: public SHSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + TransformPostLogicUpdate (); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void Execute(double dt) noexcept override; + }; + + class SH_API TransformPostPhysicsUpdate final: public SHSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + TransformPostPhysicsUpdate (); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void Execute(double dt) noexcept override; + }; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void Init () override; + void Exit () override; + + private: + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + static void UpdateChildrenLocalTransforms (SHSceneNode* node); + + static void UpdateEntity (const SHSceneNode* node, bool clearDirtyFlag); + static void UpdateTransform (SHTransformComponent& tf, const SHTransformComponent* parent = nullptr); + + // Event Handlers + + SHEventHandle ChangeParent (SHEventPtr changeParentEvent); + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.cpp b/SHADE_Engine/src/Math/Vector/SHVec2.cpp index 72c80a50..195a8b14 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec2.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec2.cpp @@ -38,14 +38,31 @@ namespace SHADE : XMFLOAT2( 0.0f, 0.0f ) {} + SHVec2::SHVec2(const XMFLOAT2& xmfloat2) noexcept + : XMFLOAT2 ( xmfloat2.x, xmfloat2.y ) + {} + + SHVec2::SHVec2(float n) noexcept + : XMFLOAT2( n, n ) + {} + SHVec2::SHVec2(float _x, float _y) noexcept : XMFLOAT2( _x, _y ) {} + SHVec2::SHVec2(const reactphysics3d::Vector2& rp3dVec2) noexcept + : XMFLOAT2( rp3dVec2.x, rp3dVec2.y ) + {} + /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ + SHVec2::operator XMVECTOR() const noexcept + { + return XMLoadFloat2(this); + } + SHVec2& SHVec2::operator+=(const SHVec2& rhs) noexcept { return *this = *this + rhs; @@ -79,22 +96,16 @@ namespace SHADE SHVec2 SHVec2::operator+(const SHVec2& rhs) const noexcept { SHVec2 result; - - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - XMStoreFloat2(&result, XMVectorAdd(V1, V2)); + XMStoreFloat2(&result, XMVectorAdd(*this, rhs)); return result; } SHVec2 SHVec2::operator-(const SHVec2& rhs) const noexcept { SHVec2 result; - - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - XMStoreFloat2(&result, XMVectorSubtract(V1, V2)); + XMStoreFloat2(&result, XMVectorSubtract(*this, rhs)); return result; } @@ -106,62 +117,46 @@ namespace SHADE SHVec2 SHVec2::operator*(const SHVec2& rhs) const noexcept { SHVec2 result; - - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - XMStoreFloat2(&result, XMVectorMultiply(V1, V2)); + XMStoreFloat2(&result, XMVectorMultiply(*this, rhs)); return result; } SHVec2 SHVec2::operator*(float rhs) const noexcept { SHVec2 result; - - const XMVECTOR V = XMLoadFloat2(this); - XMStoreFloat2(&result, XMVectorScale(V, rhs)); + XMStoreFloat2(&result, XMVectorScale(*this, rhs)); return result; } SHVec2 SHVec2::operator/(const SHVec2& rhs) const noexcept { SHVec2 result; - - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - XMStoreFloat2(&result, XMVectorDivide(V1, V2)); + XMStoreFloat2(&result, XMVectorDivide(*this, rhs)); return result; } SHVec2 SHVec2::operator/(float rhs) const noexcept { SHVec2 result; - - const XMVECTOR V = XMLoadFloat2(this); - XMStoreFloat2(&result, XMVectorScale(V, 1.0f / rhs)); + XMStoreFloat2(&result, XMVectorScale(*this, 1.0f / rhs)); return result; } bool SHVec2::operator==(const SHVec2& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - - return XMVector2Equal(V1, V2); + return XMVector2Equal(*this, rhs); } bool SHVec2::operator!=(const SHVec2& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - - return XMVector2NotEqual(V1, V2); + return XMVector2NotEqual(*this, rhs); } - float SHVec2::operator[](int index) + float& SHVec2::operator[](int index) { if (index >= SIZE || index < 0) throw std::invalid_argument("Index out of range!"); @@ -170,11 +165,10 @@ namespace SHADE { case 0: return x; case 1: return y; - default: return 0.0f; } } - float SHVec2::operator[](size_t index) + float& SHVec2::operator[](size_t index) { if (index >= SIZE) throw std::invalid_argument("Index out of range!"); @@ -183,7 +177,6 @@ namespace SHADE { case 0: return x; case 1: return y; - default: return 0.0f; } } @@ -196,7 +189,6 @@ namespace SHADE { case 0: return x; case 1: return y; - default: return 0.0f; } } @@ -209,17 +201,19 @@ namespace SHADE { case 0: return x; case 1: return y; - default: return 0.0f; } } + SHVec2::operator reactphysics3d::Vector2() const noexcept + { + return reactphysics3d::Vector2{ x, y }; + } + SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept { SHVec2 result; - - const XMVECTOR V = XMLoadFloat2(&rhs); - XMStoreFloat2(&result, XMVectorScale(V, lhs)); + XMStoreFloat2(&result, XMVectorScale(rhs, lhs)); return result; } @@ -229,16 +223,12 @@ namespace SHADE float SHVec2::Length() const noexcept { - const XMVECTOR V = XMLoadFloat2(this); - - return XMVectorGetX(XMVector2Length(V)); + return XMVectorGetX(XMVector2Length(*this)); } float SHVec2::LengthSquared() const noexcept { - const XMVECTOR V = XMLoadFloat2(this); - - return XMVectorGetX(XMVector2LengthSq(V)); + return XMVectorGetX(XMVector2LengthSq(*this)); } std::string SHVec2::ToString() const noexcept @@ -251,20 +241,14 @@ namespace SHADE float SHVec2::Dot(const SHVec2& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - - return XMVectorGetX(XMVector2Dot(V1, V2)); + return XMVectorGetX(XMVector2Dot(*this, rhs)); } SHVec2 SHVec2::Cross(const SHVec2& rhs) const noexcept { SHVec2 result; - const XMVECTOR V1 = XMLoadFloat2(this); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - - XMStoreFloat2(&result, XMVector2Cross(V1, V2)); + XMStoreFloat2(&result, XMVector2Cross(*this, rhs)); return result; } @@ -276,9 +260,7 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V = XMLoadFloat2(&vec2); - - XMStoreFloat2(&result, XMVector2Normalize(V)); + XMStoreFloat2(&result, XMVector2Normalize(vec2)); return result; } @@ -297,10 +279,10 @@ namespace SHADE SHVec2 result; - XMVECTOR min = XMLoadFloat2(&(*vec2s.begin())); + XMVECTOR min = *vec2s.begin(); for (auto it = vec2s.begin() + 1; it != vec2s.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat2(&(*it)); + const XMVECTOR tmp = *it; min = XMVectorMin(min, tmp); } @@ -318,10 +300,10 @@ namespace SHADE SHVec2 result; - XMVECTOR max = XMLoadFloat2(&(*vec2s.begin())); + XMVECTOR max = *vec2s.begin(); for (auto it = vec2s.begin() + 1; it != vec2s.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat2(&(*it)); + const XMVECTOR tmp = *it; max = XMVectorMax(max, tmp); } @@ -333,11 +315,7 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V = XMLoadFloat2(&v); - const XMVECTOR MIN = XMLoadFloat2(&vMin); - const XMVECTOR MAX = XMLoadFloat2(&vMax); - - XMStoreFloat2(&result, XMVectorClamp(V, MIN, MAX)); + XMStoreFloat2(&result, XMVectorClamp(v, vMin, vMax)); return result; } @@ -345,10 +323,7 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V1 = XMLoadFloat2(&a); - const XMVECTOR V2 = XMLoadFloat2(&b); - - XMStoreFloat2(&result, XMVectorLerp(V1, V2, t)); + XMStoreFloat2(&result, XMVectorLerp(a, b, t)); return result; } @@ -369,10 +344,7 @@ namespace SHADE float SHVec2::Angle(const SHVec2& lhs, const SHVec2& rhs) noexcept { - const XMVECTOR V1 = XMLoadFloat2(&lhs); - const XMVECTOR V2 = XMLoadFloat2(&rhs); - - return XMVectorGetX(XMVector2AngleBetweenVectors(V1, V2)); + return XMVectorGetX(XMVector2AngleBetweenVectors(lhs, rhs)); } float SHVec2::Dot(const SHVec2& lhs, const SHVec2& rhs) noexcept @@ -384,11 +356,10 @@ namespace SHADE { SHVec2 result; - const XMVECTOR U = XMLoadFloat2(&u); const float V_DOT_U = Dot(v, u); const float U_LENSQ = u.LengthSquared(); - XMStoreFloat2(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + XMStoreFloat2(&result, XMVectorScale(u, V_DOT_U / U_LENSQ)); return result; } @@ -396,10 +367,8 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V = XMLoadFloat2(&v); - const XMVECTOR N = XMLoadFloat2(&normal); - XMStoreFloat2(&result, XMVector2Reflect(V, N)); + XMStoreFloat2(&result, XMVector2Reflect(v, normal)); return result; } @@ -407,10 +376,9 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V = XMLoadFloat2(&v); const XMMATRIX R = XMMatrixRotationZ(angleInRad); - XMStoreFloat2(&result, XMVector2Transform(V, R)); + XMStoreFloat2(&result, XMVector2Transform(v, R)); return result; } @@ -418,10 +386,9 @@ namespace SHADE { SHVec2 result; - const XMVECTOR V = XMLoadFloat2(&v); const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); - XMStoreFloat2(&result, XMVector2TransformCoord(V, TF)); + XMStoreFloat2(&result, XMVector2TransformCoord(v, TF)); return result; } diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.h b/SHADE_Engine/src/Math/Vector/SHVec2.h index a64d4bb0..e780d3ac 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec2.h +++ b/SHADE_Engine/src/Math/Vector/SHVec2.h @@ -11,9 +11,14 @@ #pragma once #include +#include + #include #include +// Project Headers +#include "SH_API.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -25,7 +30,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHVec2 : public DirectX::XMFLOAT2 + class SH_API SHVec2 : public DirectX::XMFLOAT2 { public: /*---------------------------------------------------------------------------------*/ @@ -45,53 +50,65 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHVec2 (const SHVec2& rhs) = default; - SHVec2 (SHVec2&& rhs) = default; - ~SHVec2 () = default; + SHVec2 (const SHVec2& rhs) = default; + SHVec2 (SHVec2&& rhs) = default; + ~SHVec2 () = default; - SHVec2 () noexcept; - SHVec2 (float x, float y) noexcept; + SHVec2 () noexcept; + SHVec2 (const XMFLOAT2& xmfloat2) noexcept; + SHVec2 (float n) noexcept; + SHVec2 (float x, float y) noexcept; + + // Conversion from other math types to SHADE + + SHVec2 (const reactphysics3d::Vector2& rp3dVec2) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHVec2& operator= (const SHVec2& rhs) = default; - [[nodiscard]] SHVec2& operator= (SHVec2&& rhs) = default; + SHVec2& operator= (const SHVec2& rhs) = default; + SHVec2& operator= (SHVec2&& rhs) = default; - [[nodiscard]] SHVec2& operator+= (const SHVec2& rhs) noexcept; - [[nodiscard]] SHVec2& operator-= (const SHVec2& rhs) noexcept; - [[nodiscard]] SHVec2& operator*= (const SHVec2& rhs) noexcept; - [[nodiscard]] SHVec2& operator*= (float rhs) noexcept; - [[nodiscard]] SHVec2& operator/= (const SHVec2& rhs) noexcept; - [[nodiscard]] SHVec2& operator/= (float rhs) noexcept; + // Conversion to other math types used by SHADE - [[nodiscard]] SHVec2 operator+ (const SHVec2& rhs) const noexcept; - [[nodiscard]] SHVec2 operator- (const SHVec2& rhs) const noexcept; - [[nodiscard]] SHVec2 operator- () const noexcept; - [[nodiscard]] SHVec2 operator* (const SHVec2& rhs) const noexcept; - [[nodiscard]] SHVec2 operator* (float rhs) const noexcept; - [[nodiscard]] SHVec2 operator/ (const SHVec2& rhs) const noexcept; - [[nodiscard]] SHVec2 operator/ (float rhs) const noexcept; + operator DirectX::XMVECTOR () const noexcept; + operator reactphysics3d::Vector2 () const noexcept; - [[nodiscard]] bool operator== (const SHVec2& rhs) const noexcept; - [[nodiscard]] bool operator!= (const SHVec2& rhs) const noexcept; + SHVec2& operator+= (const SHVec2& rhs) noexcept; + SHVec2& operator-= (const SHVec2& rhs) noexcept; + SHVec2& operator*= (const SHVec2& rhs) noexcept; + SHVec2& operator*= (float rhs) noexcept; + SHVec2& operator/= (const SHVec2& rhs) noexcept; + SHVec2& operator/= (float rhs) noexcept; - [[nodiscard]] float operator[] (int index); - [[nodiscard]] float operator[] (size_t index); - [[nodiscard]] float operator[] (int index) const; - [[nodiscard]] float operator[] (size_t index) const; + [[nodiscard]] SHVec2 operator+ (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator- (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator- () const noexcept; + [[nodiscard]] SHVec2 operator* (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec2 operator/ (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator/ (float rhs) const noexcept; + [[nodiscard]] bool operator== (const SHVec2& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec2& rhs) const noexcept; + + [[nodiscard]] float& operator[] (int index); + [[nodiscard]] float& operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; + + /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] float Length () const noexcept; - [[nodiscard]] float LengthSquared () const noexcept; - [[nodiscard]] std::string ToString () const noexcept; + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; - [[nodiscard]] float Dot (const SHVec2& rhs) const noexcept; - [[nodiscard]] SHVec2 Cross (const SHVec2& rhs) const noexcept; + [[nodiscard]] float Dot (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 Cross (const SHVec2& rhs) const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ @@ -117,6 +134,6 @@ namespace SHADE [[nodiscard]] static float Cross (const SHVec2& lhs, const SHVec2& rhs) noexcept; }; - SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept; + SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.cpp b/SHADE_Engine/src/Math/Vector/SHVec3.cpp index 73030f9c..cbd8ca32 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec3.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec3.cpp @@ -14,6 +14,7 @@ #include "SHVec3.h" // Project Headers #include "Math/SHMatrix.h" +#include "Math/SHQuaternion.h" #include "Tools/SHLogger.h" using namespace DirectX; @@ -23,6 +24,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Static Data Member Definitions */ /*-----------------------------------------------------------------------------------*/ + SHVec3 const SHVec3::Zero { 0.0f, 0.0f, 0.0f }; SHVec3 const SHVec3::One { 1.0f, 1.0f, 1.0f }; SHVec3 const SHVec3::Left { -1.0f, 0.0f, 0.0f }; @@ -43,14 +45,36 @@ namespace SHADE : XMFLOAT3( 0.0f, 0.0f, 0.0f ) {} + SHVec3::SHVec3(const XMFLOAT3& xmfloat3) noexcept + : XMFLOAT3 ( xmfloat3.x, xmfloat3.y, xmfloat3.z ) + {} + + SHVec3::SHVec3(float n) noexcept + : XMFLOAT3( n, n, n ) + {} + SHVec3::SHVec3(float _x, float _y, float _z) noexcept : XMFLOAT3( _x, _y, _z ) {} + SHVec3::SHVec3(const reactphysics3d::Vector3& rp3dVec3) noexcept + : XMFLOAT3( rp3dVec3.x, rp3dVec3.y, rp3dVec3.z ) + {} + + SHVec3::SHVec3(const reactphysics3d::Quaternion& rp3dVec3) noexcept + : XMFLOAT3( SHQuaternion{rp3dVec3}.ToEuler() ) + {} + /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ + SHVec3::operator XMVECTOR() const noexcept + { + return XMLoadFloat3(this); + } + + SHVec3& SHVec3::operator+=(const SHVec3& rhs) noexcept { return *this = *this + rhs; @@ -84,22 +108,16 @@ namespace SHADE SHVec3 SHVec3::operator+(const SHVec3& rhs) const noexcept { SHVec3 result; - - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - XMStoreFloat3(&result, XMVectorAdd(V1, V2)); + XMStoreFloat3(&result, XMVectorAdd(*this, rhs)); return result; } SHVec3 SHVec3::operator-(const SHVec3& rhs) const noexcept { SHVec3 result; - - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - XMStoreFloat3(&result, XMVectorSubtract(V1, V2)); + XMStoreFloat3(&result, XMVectorSubtract(*this, rhs)); return result; } @@ -112,62 +130,46 @@ namespace SHADE SHVec3 SHVec3::operator*(const SHVec3& rhs) const noexcept { SHVec3 result; - - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - XMStoreFloat3(&result, XMVectorMultiply(V1, V2)); + XMStoreFloat3(&result, XMVectorMultiply(*this, rhs)); return result; } SHVec3 SHVec3::operator*(float rhs) const noexcept { SHVec3 result; - - const XMVECTOR V = XMLoadFloat3(this); - XMStoreFloat3(&result, XMVectorScale(V, rhs)); + XMStoreFloat3(&result, XMVectorScale(*this, rhs)); return result; } SHVec3 SHVec3::operator/(const SHVec3& rhs) const noexcept { SHVec3 result; - - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - XMStoreFloat3(&result, XMVectorDivide(V1, V2)); + XMStoreFloat3(&result, XMVectorDivide(*this, rhs)); return result; } SHVec3 SHVec3::operator/(float rhs) const noexcept { SHVec3 result; - - const XMVECTOR V = XMLoadFloat3(this); - XMStoreFloat3(&result, XMVectorScale(V, 1.0f / rhs)); + XMStoreFloat3(&result, XMVectorScale(*this, 1.0f / rhs)); return result; } bool SHVec3::operator==(const SHVec3& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - - return XMVector3Equal(V1, V2); + return XMVector3Equal(*this, rhs); } bool SHVec3::operator!=(const SHVec3& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - - return XMVector3NotEqual(V1, V2); + return XMVector3NotEqual(*this, rhs); } - float SHVec3::operator[](int index) + float& SHVec3::operator[](int index) { if (index >= SIZE || index < 0) throw std::invalid_argument("Index out of range!"); @@ -177,11 +179,10 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; - default: return 0.0f; } } - float SHVec3::operator[](size_t index) + float& SHVec3::operator[](size_t index) { if (index >= SIZE) throw std::invalid_argument("Index out of range!"); @@ -191,7 +192,6 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; - default: return 0.0f; } } @@ -205,7 +205,6 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; - default: return 0.0f; } } @@ -219,17 +218,24 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; - default: return 0.0f; } } + SHVec3::operator reactphysics3d::Vector3() const noexcept + { + return reactphysics3d::Vector3{ x, y , z }; + } + + SHVec3::operator reactphysics3d::Quaternion() const noexcept + { + return reactphysics3d::Quaternion::fromEulerAngles(x, y, z); + } + SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept { SHVec3 result; - - const XMVECTOR V = XMLoadFloat3(&rhs); - XMStoreFloat3(&result, XMVectorScale(V, lhs)); + XMStoreFloat3(&result, XMVectorScale(rhs, lhs)); return result; } @@ -239,16 +245,12 @@ namespace SHADE float SHVec3::Length() const noexcept { - const XMVECTOR V = XMLoadFloat3(this); - - return XMVectorGetX(XMVector3Length(V)); + return XMVectorGetX(XMVector3Length(*this)); } float SHVec3::LengthSquared() const noexcept { - const XMVECTOR V = XMLoadFloat3(this); - - return XMVectorGetX(XMVector3LengthSq(V)); + return XMVectorGetX(XMVector3LengthSq(*this)); } std::string SHVec3::ToString() const noexcept @@ -261,20 +263,14 @@ namespace SHADE float SHVec3::Dot(const SHVec3& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - - return XMVectorGetX(XMVector3Dot(V1, V2)); + return XMVectorGetX(XMVector3Dot(*this, rhs)); } SHVec3 SHVec3::Cross(const SHVec3& rhs) const noexcept { SHVec3 result; - const XMVECTOR V1 = XMLoadFloat3(this); - const XMVECTOR V2 = XMLoadFloat3(&rhs); - - XMStoreFloat3(&result, XMVector3Cross(V1, V2)); + XMStoreFloat3(&result, XMVector3Cross(*this, rhs)); return result; } @@ -286,9 +282,7 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - - XMStoreFloat3(&result, XMVector3Normalize(V)); + XMStoreFloat3(&result, XMVector3Normalize(v)); return result; } @@ -307,10 +301,10 @@ namespace SHADE SHVec3 result; - XMVECTOR min = XMLoadFloat3(&(*vs.begin())); + XMVECTOR min = *vs.begin(); for (auto it = vs.begin() + 1; it != vs.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat3(&(*it)); + const XMVECTOR tmp = *it; min = XMVectorMin(min, tmp); } @@ -328,10 +322,10 @@ namespace SHADE SHVec3 result; - XMVECTOR max = XMLoadFloat3(&(*vs.begin())); + XMVECTOR max = *vs.begin(); for (auto it = vs.begin() + 1; it != vs.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat3(&(*it)); + const XMVECTOR tmp = *it; max = XMVectorMax(max, tmp); } @@ -343,11 +337,7 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - const XMVECTOR MIN = XMLoadFloat3(&vMin); - const XMVECTOR MAX = XMLoadFloat3(&vMax); - - XMStoreFloat3(&result, XMVectorClamp(V, MIN, MAX)); + XMStoreFloat3(&result, XMVectorClamp(v, vMin, vMax)); return result; } @@ -355,10 +345,7 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V1 = XMLoadFloat3(&a); - const XMVECTOR V2 = XMLoadFloat3(&b); - - XMStoreFloat3(&result, XMVectorLerp(V1, V2, t)); + XMStoreFloat3(&result, XMVectorLerp(a, b, t)); return result; } @@ -382,7 +369,7 @@ namespace SHADE const XMVECTOR V1 = XMLoadFloat3(&lhs); const XMVECTOR V2 = XMLoadFloat3(&rhs); - return XMVectorGetX(XMVector3AngleBetweenVectors(V1, V2)); + return XMVectorGetX(XMVector3AngleBetweenVectors(lhs, rhs)); } float SHVec3::Dot(const SHVec3& lhs, const SHVec3& rhs) noexcept @@ -399,22 +386,18 @@ namespace SHADE { SHVec3 result; - const XMVECTOR U = XMLoadFloat3(&u); const float V_DOT_U = Dot(v, u); const float U_LENSQ = u.LengthSquared(); - XMStoreFloat3(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + XMStoreFloat3(&result, XMVectorScale(u, V_DOT_U / U_LENSQ)); return result; } SHVec3 SHVec3::Reflect(const SHVec3& v, const SHVec3& normal) noexcept { SHVec3 result; - - const XMVECTOR V = XMLoadFloat3(&v); - const XMVECTOR N = XMLoadFloat3(&normal); - XMStoreFloat3(&result, XMVector3Reflect(V, N)); + XMStoreFloat3(&result, XMVector3Reflect(v, normal)); return result; } @@ -422,12 +405,9 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); + const XMVECTOR Q = XMQuaternionRotationAxis(axis, angleInRad); - const XMVECTOR AXIS = XMLoadFloat3(&axis); - const XMVECTOR Q = XMQuaternionRotationAxis(AXIS, angleInRad); - - XMStoreFloat3(&result, XMVector3Rotate(V, Q)); + XMStoreFloat3(&result, XMVector3Rotate(v, Q)); return result; } @@ -435,10 +415,9 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - const XMMATRIX R = XMMatrixRotationX(angleInRad); + const XMMATRIX R = XMMatrixRotationX(angleInRad); - XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + XMStoreFloat3(&result, XMVector3TransformCoord(v, R)); return result; } @@ -446,10 +425,9 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - const XMMATRIX R = XMMatrixRotationY(angleInRad); + const XMMATRIX R = XMMatrixRotationY(angleInRad); - XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + XMStoreFloat3(&result, XMVector3TransformCoord(v, R)); return result; } @@ -457,10 +435,9 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - const XMMATRIX R = XMMatrixRotationZ(angleInRad); + const XMMATRIX R = XMMatrixRotationZ(angleInRad); - XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + XMStoreFloat3(&result, XMVector3TransformCoord(v, R)); return result; } @@ -468,10 +445,7 @@ namespace SHADE { SHVec3 result; - const XMVECTOR V = XMLoadFloat3(&v); - const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); - - XMStoreFloat3(&result, XMVector3TransformCoord(V, TF)); + XMStoreFloat3(&result, XMVector3TransformCoord(v, transformMtx)); return result; } } \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.h b/SHADE_Engine/src/Math/Vector/SHVec3.h index e172e824..eab96b30 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec3.h +++ b/SHADE_Engine/src/Math/Vector/SHVec3.h @@ -11,9 +11,15 @@ #pragma once #include +#include +#include + #include #include +// Project Headers +#include "SH_API.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -25,7 +31,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHVec3 : public DirectX::XMFLOAT3 + class SH_API SHVec3 : public DirectX::XMFLOAT3 { public: /*---------------------------------------------------------------------------------*/ @@ -50,62 +56,74 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHVec3 (const SHVec3& rhs) = default; - SHVec3 (SHVec3&& rhs) = default; - ~SHVec3 () = default; + SHVec3 (const SHVec3& rhs) = default; + SHVec3 (SHVec3&& rhs) = default; + ~SHVec3 () = default; - SHVec3 () noexcept; - SHVec3 (float x, float y, float z) noexcept; + SHVec3 () noexcept; + SHVec3 (const XMFLOAT3& xmfloat3) noexcept; + SHVec3 (float n) noexcept; + SHVec3 (float x, float y, float z) noexcept; + + // Conversion from other math types to SHADE + + SHVec3 (const reactphysics3d::Vector3& rp3dVec3) noexcept; + SHVec3 (const reactphysics3d::Quaternion& rp3dVec3) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHVec3& operator= (const SHVec3& rhs) = default; - [[nodiscard]] SHVec3& operator= (SHVec3&& rhs) = default; + SHVec3& operator= (const SHVec3& rhs) = default; + SHVec3& operator= (SHVec3&& rhs) = default; - [[nodiscard]] SHVec3& operator+= (const SHVec3& rhs) noexcept; - [[nodiscard]] SHVec3& operator-= (const SHVec3& rhs) noexcept; - [[nodiscard]] SHVec3& operator*= (const SHVec3& rhs) noexcept; - [[nodiscard]] SHVec3& operator*= (float rhs) noexcept; - [[nodiscard]] SHVec3& operator/= (const SHVec3& rhs) noexcept; - [[nodiscard]] SHVec3& operator/= (float rhs) noexcept; + // Conversion to other math types used by SHADE - [[nodiscard]] SHVec3 operator+ (const SHVec3& rhs) const noexcept; - [[nodiscard]] SHVec3 operator- (const SHVec3& rhs) const noexcept; - [[nodiscard]] SHVec3 operator- () const noexcept; - [[nodiscard]] SHVec3 operator* (const SHVec3& rhs) const noexcept; - [[nodiscard]] SHVec3 operator* (float rhs) const noexcept; - [[nodiscard]] SHVec3 operator/ (const SHVec3& rhs) const noexcept; - [[nodiscard]] SHVec3 operator/ (float rhs) const noexcept; + operator reactphysics3d::Vector3 () const noexcept; + operator reactphysics3d::Quaternion () const noexcept; + operator DirectX::XMVECTOR () const noexcept; - [[nodiscard]] bool operator== (const SHVec3& rhs) const noexcept; - [[nodiscard]] bool operator!= (const SHVec3& rhs) const noexcept; + SHVec3& operator+= (const SHVec3& rhs) noexcept; + SHVec3& operator-= (const SHVec3& rhs) noexcept; + SHVec3& operator*= (const SHVec3& rhs) noexcept; + SHVec3& operator*= (float rhs) noexcept; + SHVec3& operator/= (const SHVec3& rhs) noexcept; + SHVec3& operator/= (float rhs) noexcept; - [[nodiscard]] float operator[] (int index); - [[nodiscard]] float operator[] (size_t index); - [[nodiscard]] float operator[] (int index) const; - [[nodiscard]] float operator[] (size_t index) const; + [[nodiscard]] SHVec3 operator+ (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator- (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator- () const noexcept; + [[nodiscard]] SHVec3 operator* (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec3 operator/ (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator/ (float rhs) const noexcept; + [[nodiscard]] bool operator== (const SHVec3& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec3& rhs) const noexcept; + + [[nodiscard]] float& operator[] (int index); + [[nodiscard]] float& operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] float Length () const noexcept; - [[nodiscard]] float LengthSquared () const noexcept; - [[nodiscard]] std::string ToString () const noexcept; + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; - [[nodiscard]] float Dot (const SHVec3& rhs) const noexcept; - [[nodiscard]] SHVec3 Cross (const SHVec3& rhs) const noexcept; + [[nodiscard]] float Dot (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 Cross (const SHVec3& rhs) const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHVec3 Normalise (const SHVec3& v) noexcept; - [[nodiscard]] static SHVec3 Abs (const SHVec3& v) noexcept; - [[nodiscard]] static SHVec3 Min (const std::initializer_list& vs) noexcept; - [[nodiscard]] static SHVec3 Max (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec3 Normalise (const SHVec3& v) noexcept; + [[nodiscard]] static SHVec3 Abs (const SHVec3& v) noexcept; + [[nodiscard]] static SHVec3 Min (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec3 Max (const std::initializer_list& vs) noexcept; [[nodiscard]] static SHVec3 Clamp (const SHVec3& v, const SHVec3& vMin, const SHVec3& vMax) noexcept; [[nodiscard]] static SHVec3 Lerp (const SHVec3& a, const SHVec3& b, float t) noexcept; [[nodiscard]] static SHVec3 ClampedLerp (const SHVec3& a, const SHVec3& b, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; @@ -124,6 +142,6 @@ namespace SHADE [[nodiscard]] static SHVec3 Transform (const SHVec3& v, const SHMatrix& transformMtx) noexcept; }; - SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept; + SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec4.cpp b/SHADE_Engine/src/Math/Vector/SHVec4.cpp index 5d75af33..943d540e 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec4.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec4.cpp @@ -14,6 +14,7 @@ #include "SHVec4.h" // Project Headers #include "Math/SHMatrix.h" +#include "Math/SHColour.h" #include "Tools/SHLogger.h" using namespace DirectX; @@ -38,6 +39,14 @@ namespace SHADE : XMFLOAT4( 0.0f, 0.0f, 0.0f, 0.0f ) {} + SHVec4::SHVec4(const SHVec3& vec3) noexcept + : XMFLOAT4( vec3.x, vec3.y, vec3.z, 1.0f ) + {} + + SHVec4::SHVec4(const XMFLOAT4& xmfloat4) noexcept + : XMFLOAT4( xmfloat4.x, xmfloat4.y, xmfloat4.z, xmfloat4.w ) + {} + SHVec4::SHVec4(float _x, float _y, float _z, float _w) noexcept : XMFLOAT4( _x, _y, _z, _w ) {} @@ -46,6 +55,11 @@ namespace SHADE /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ + SHVec4::operator XMVECTOR() const noexcept + { + return XMLoadFloat4(this); + } + SHVec4& SHVec4::operator+=(const SHVec4& rhs) noexcept { return *this = *this + rhs; @@ -79,22 +93,16 @@ namespace SHADE SHVec4 SHVec4::operator+(const SHVec4& rhs) const noexcept { SHVec4 result; - - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - XMStoreFloat4(&result, XMVectorAdd(V1, V2)); + XMStoreFloat4(&result, XMVectorAdd(*this, rhs)); return result; } SHVec4 SHVec4::operator-(const SHVec4& rhs) const noexcept { SHVec4 result; - - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - XMStoreFloat4(&result, XMVectorSubtract(V1, V2)); + XMStoreFloat4(&result, XMVectorSubtract(*this, rhs)); return result; } @@ -106,62 +114,46 @@ namespace SHADE SHVec4 SHVec4::operator*(const SHVec4& rhs) const noexcept { SHVec4 result; - - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - XMStoreFloat4(&result, XMVectorMultiply(V1, V2)); + XMStoreFloat4(&result, XMVectorMultiply(*this, rhs)); return result; } SHVec4 SHVec4::operator*(float rhs) const noexcept { SHVec4 result; - - const XMVECTOR V = XMLoadFloat4(this); - XMStoreFloat4(&result, XMVectorScale(V, rhs)); + XMStoreFloat4(&result, XMVectorScale(*this, rhs)); return result; } SHVec4 SHVec4::operator/(const SHVec4& rhs) const noexcept { SHVec4 result; - - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - XMStoreFloat4(&result, XMVectorDivide(V1, V2)); + XMStoreFloat4(&result, XMVectorDivide(*this, rhs)); return result; } SHVec4 SHVec4::operator/(float rhs) const noexcept { SHVec4 result; - - const XMVECTOR V = XMLoadFloat4(this); - XMStoreFloat4(&result, XMVectorScale(V, 1.0f / rhs)); + XMStoreFloat4(&result, XMVectorScale(*this, 1.0f / rhs)); return result; } bool SHVec4::operator==(const SHVec4& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVector4Equal(V1, V2); + return XMVector4Equal(*this, rhs); } bool SHVec4::operator!=(const SHVec4& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVector4NotEqual(V1, V2); + return XMVector4NotEqual(*this, rhs); } - float SHVec4::operator[](int index) + float& SHVec4::operator[](int index) { if (index >= SIZE || index < 0) throw std::invalid_argument("Index out of range!"); @@ -172,11 +164,10 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; - default: return 0.0f; } } - float SHVec4::operator[](size_t index) + float& SHVec4::operator[](size_t index) { if (index >= SIZE) throw std::invalid_argument("Index out of range!"); @@ -187,7 +178,6 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; - default: return 0.0f; } } @@ -202,7 +192,6 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; - default: return 0.0f; } } @@ -217,17 +206,14 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; - default: return 0.0f; } } SHVec4 operator* (float lhs, const SHVec4& rhs) noexcept { SHVec4 result; - - const XMVECTOR V = XMLoadFloat4(&rhs); - XMStoreFloat4(&result, XMVectorScale(V, lhs)); + XMStoreFloat4(&result, XMVectorScale(rhs, lhs)); return result; } @@ -237,30 +223,22 @@ namespace SHADE float SHVec4::Length() const noexcept { - const XMVECTOR V = XMLoadFloat4(this); - - return XMVectorGetX(XMVector4Length(V)); + return XMVectorGetX(XMVector4Length(*this)); } float SHVec4::Length3D() const noexcept { - const XMVECTOR V = XMLoadFloat4(this); - - return XMVectorGetX(XMVector3Length(V)); + return XMVectorGetX(XMVector3Length(*this)); } float SHVec4::LengthSquared() const noexcept { - const XMVECTOR V = XMLoadFloat4(this); - - return XMVectorGetX(XMVector4LengthSq(V)); + return XMVectorGetX(XMVector4LengthSq(*this)); } float SHVec4::LengthSquared3D() const noexcept { - const XMVECTOR V = XMLoadFloat4(this); - - return XMVectorGetX(XMVector3LengthSq(V)); + return XMVectorGetX(XMVector3LengthSq(*this)); } std::string SHVec4::ToString() const noexcept @@ -273,28 +251,19 @@ namespace SHADE float SHVec4::Dot(const SHVec4& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVectorGetX(XMVector4Dot(V1, V2)); + return XMVectorGetX(XMVector4Dot(*this, rhs)); } float SHVec4::Dot3D(const SHVec4& rhs) const noexcept { - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVectorGetX(XMVector3Dot(V1, V2)); + return XMVectorGetX(XMVector3Dot(*this, rhs)); } SHVec4 SHVec4::Cross3D(const SHVec4& rhs) const noexcept { SHVec4 result; - const XMVECTOR V1 = XMLoadFloat4(this); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - XMStoreFloat4(&result, XMVector3Cross(V1, V2)); + XMStoreFloat4(&result, XMVector3Cross(*this, rhs)); result.w = 1.0f; return result; } @@ -303,14 +272,15 @@ namespace SHADE { SHVec4 result; - const XMVECTOR V3 = XMLoadFloat4(this); - const XMVECTOR V1 = XMLoadFloat4(&v1); - const XMVECTOR V2 = XMLoadFloat4(&v2); - - XMStoreFloat4(&result, XMVector4Cross(V3, V1, V2)); + XMStoreFloat4(&result, XMVector4Cross(*this, v1, v2)); return result; } + SHVec3 SHVec4::ToVec3() const noexcept + { + return SHVec3{ x, y, z }; + } + /*-----------------------------------------------------------------------------------*/ /* Static Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -319,9 +289,7 @@ namespace SHADE { SHVec4 result; - const XMVECTOR V = XMLoadFloat4(&v); - - XMStoreFloat4(&result, XMVector4Normalize(V)); + XMStoreFloat4(&result, XMVector4Normalize(v)); return result; } @@ -329,9 +297,7 @@ namespace SHADE { SHVec4 result; - const XMVECTOR V = XMLoadFloat4(&v); - - XMStoreFloat4(&result, XMVector3Normalize(V)); + XMStoreFloat4(&result, XMVector3Normalize(v)); result.w = 1.0f; return result; } @@ -351,10 +317,10 @@ namespace SHADE SHVec4 result; - XMVECTOR min = XMLoadFloat4(&(*vs.begin())); + XMVECTOR min = *vs.begin(); for (auto it = vs.begin() + 1; it != vs.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat4(&(*it)); + const XMVECTOR tmp = *it; min = XMVectorMin(min, tmp); } @@ -372,10 +338,10 @@ namespace SHADE SHVec4 result; - XMVECTOR max = XMLoadFloat4(&(*vs.begin())); + XMVECTOR max = *vs.begin(); for (auto it = vs.begin() + 1; it != vs.end(); ++it) { - const XMVECTOR tmp = XMLoadFloat4(&(*it)); + const XMVECTOR tmp = *it; max = XMVectorMax(max, tmp); } @@ -387,11 +353,7 @@ namespace SHADE { SHVec4 result; - const XMVECTOR V = XMLoadFloat4(&v); - const XMVECTOR MIN = XMLoadFloat4(&vMin); - const XMVECTOR MAX = XMLoadFloat4(&vMax); - - XMStoreFloat4(&result, XMVectorClamp(V, MIN, MAX)); + XMStoreFloat4(&result, XMVectorClamp(v, vMin, vMax)); return result; } @@ -399,10 +361,7 @@ namespace SHADE { SHVec4 result; - const XMVECTOR V1 = XMLoadFloat4(&a); - const XMVECTOR V2 = XMLoadFloat4(&b); - - XMStoreFloat4(&result, XMVectorLerp(V1, V2, t)); + XMStoreFloat4(&result, XMVectorLerp(a, b, t)); return result; } @@ -433,18 +392,12 @@ namespace SHADE float SHVec4::Angle(const SHVec4& lhs, const SHVec4& rhs) noexcept { - const XMVECTOR V1 = XMLoadFloat4(&lhs); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVectorGetX(XMVector4AngleBetweenVectors(V1, V2)); + return XMVectorGetX(XMVector4AngleBetweenVectors(lhs,rhs)); } float SHVec4::Angle3D(const SHVec4& lhs, const SHVec4& rhs) noexcept { - const XMVECTOR V1 = XMLoadFloat4(&lhs); - const XMVECTOR V2 = XMLoadFloat4(&rhs); - - return XMVectorGetX(XMVector3AngleBetweenVectors(V1, V2)); + return XMVectorGetX(XMVector3AngleBetweenVectors(lhs,rhs)); } float SHVec4::Dot(const SHVec4& lhs, const SHVec4& rhs) noexcept @@ -471,11 +424,10 @@ namespace SHADE { SHVec4 result; - const XMVECTOR U = XMLoadFloat4(&u); const float V_DOT_U = Dot(v, u); const float U_LENSQ = u.LengthSquared(); - XMStoreFloat4(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + XMStoreFloat4(&result, XMVectorScale(u, V_DOT_U / U_LENSQ)); return result; } @@ -483,11 +435,10 @@ namespace SHADE { SHVec4 result; - const XMVECTOR U = XMLoadFloat4(&u); const float V_DOT_U = Dot3D(v, u); const float U_LENSQ = u.LengthSquared3D(); - XMStoreFloat4(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + XMStoreFloat4(&result, XMVectorScale(u, V_DOT_U / U_LENSQ)); result.w = 1.0f; return result; } @@ -495,11 +446,8 @@ namespace SHADE SHVec4 SHVec4::Reflect(const SHVec4& v, const SHVec4& normal) noexcept { SHVec4 result; - - const XMVECTOR V = XMLoadFloat4(&v); - const XMVECTOR N = XMLoadFloat4(&normal); - XMStoreFloat4(&result, XMVector4Reflect(V, N)); + XMStoreFloat4(&result, XMVector4Reflect(v, normal)); result.w = 1.0f; return result; } @@ -507,23 +455,25 @@ namespace SHADE SHVec4 SHVec4::Reflect3D(const SHVec4& v, const SHVec4& normal) noexcept { SHVec4 result; - - const XMVECTOR V = XMLoadFloat4(&v); - const XMVECTOR N = XMLoadFloat4(&normal); - XMStoreFloat4(&result, XMVector3Reflect(V, N)); + XMStoreFloat4(&result, XMVector3Reflect(v, normal)); result.w = 1.0f; return result; } + SHVec4 SHVec4::Transform(const SHVec4& v, const SHMatrix& transformMtx) noexcept + { + SHVec4 result; + + XMStoreFloat4(&result, XMVector4Transform(v, transformMtx)); + return result; + } + SHVec4 SHVec4::Transform3D(const SHVec4& v, const SHMatrix& transformMtx) noexcept { SHVec4 result; - const XMVECTOR V = XMLoadFloat4(&v); - const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); - - XMStoreFloat4(&result, XMVector3TransformCoord(V, TF)); + XMStoreFloat4(&result, XMVector3TransformCoord(v, transformMtx)); return result; } } \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec4.h b/SHADE_Engine/src/Math/Vector/SHVec4.h index c4caf2c8..3c509039 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec4.h +++ b/SHADE_Engine/src/Math/Vector/SHVec4.h @@ -14,18 +14,24 @@ #include #include +// Project Headers +#include "SH_API.h" +#include "SHVec3.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ + class SHMatrix; + class SHColour; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHVec4 : public DirectX::XMFLOAT4 + class SH_API SHVec4 : public DirectX::XMFLOAT4 { public: /*---------------------------------------------------------------------------------*/ @@ -45,67 +51,73 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHVec4 (const SHVec4& rhs) = default; - SHVec4 (SHVec4&& rhs) = default; - ~SHVec4 () = default; + SHVec4 (const SHVec4& rhs) = default; + SHVec4 (SHVec4&& rhs) = default; + ~SHVec4 () = default; - SHVec4 () noexcept; - SHVec4 (float x, float y, float z, float w) noexcept; + SHVec4 () noexcept; + SHVec4 (const SHVec3& vec3) noexcept; + SHVec4 (const XMFLOAT4& xmfloat4) noexcept; + SHVec4 (float x, float y, float z, float w) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHVec4& operator= (const SHVec4& rhs) = default; - [[nodiscard]] SHVec4& operator= (SHVec4&& rhs) = default; - - [[nodiscard]] SHVec4& operator+= (const SHVec4& rhs) noexcept; - [[nodiscard]] SHVec4& operator-= (const SHVec4& rhs) noexcept; - [[nodiscard]] SHVec4& operator*= (const SHVec4& rhs) noexcept; - [[nodiscard]] SHVec4& operator*= (float rhs) noexcept; - [[nodiscard]] SHVec4& operator/= (const SHVec4& rhs) noexcept; - [[nodiscard]] SHVec4& operator/= (float rhs) noexcept; + SHVec4& operator= (const SHVec4& rhs) = default; + SHVec4& operator= (SHVec4&& rhs) = default; - [[nodiscard]] SHVec4 operator+ (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 operator- (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 operator- () const noexcept; - [[nodiscard]] SHVec4 operator* (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 operator* (float rhs) const noexcept; - [[nodiscard]] SHVec4 operator/ (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 operator/ (float rhs) const noexcept; + operator DirectX::XMVECTOR () const noexcept; - [[nodiscard]] bool operator== (const SHVec4& rhs) const noexcept; - [[nodiscard]] bool operator!= (const SHVec4& rhs) const noexcept; + SHVec4& operator+= (const SHVec4& rhs) noexcept; + SHVec4& operator-= (const SHVec4& rhs) noexcept; + SHVec4& operator*= (const SHVec4& rhs) noexcept; + SHVec4& operator*= (float rhs) noexcept; + SHVec4& operator/= (const SHVec4& rhs) noexcept; + SHVec4& operator/= (float rhs) noexcept; - [[nodiscard]] float operator[] (int index); - [[nodiscard]] float operator[] (size_t index); - [[nodiscard]] float operator[] (int index) const; - [[nodiscard]] float operator[] (size_t index) const; + [[nodiscard]] SHVec4 operator+ (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator- (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator- () const noexcept; + [[nodiscard]] SHVec4 operator* (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec4 operator/ (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator/ (float rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHVec4& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec4& rhs) const noexcept; + + [[nodiscard]] float& operator[] (int index); + [[nodiscard]] float& operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] float Length () const noexcept; - [[nodiscard]] float Length3D () const noexcept; - [[nodiscard]] float LengthSquared () const noexcept; - [[nodiscard]] float LengthSquared3D () const noexcept; - [[nodiscard]] std::string ToString () const noexcept; + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float Length3D () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] float LengthSquared3D () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; - [[nodiscard]] float Dot (const SHVec4& rhs) const noexcept; - [[nodiscard]] float Dot3D (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 Cross3D (const SHVec4& rhs) const noexcept; - [[nodiscard]] SHVec4 Cross (const SHVec4& v1, const SHVec4& v2) const noexcept; + [[nodiscard]] float Dot (const SHVec4& rhs) const noexcept; + [[nodiscard]] float Dot3D (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 Cross3D (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 Cross (const SHVec4& v1, const SHVec4& v2) const noexcept; + + [[nodiscard]] SHVec3 ToVec3 () const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHVec4 Normalise (const SHVec4& v) noexcept; - [[nodiscard]] static SHVec4 Normalise3D (const SHVec4& v) noexcept; - [[nodiscard]] static SHVec4 Abs (const SHVec4& v) noexcept; - [[nodiscard]] static SHVec4 Min (const std::initializer_list& vs) noexcept; - [[nodiscard]] static SHVec4 Max (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec4 Normalise (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Normalise3D (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Abs (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Min (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec4 Max (const std::initializer_list& vs) noexcept; [[nodiscard]] static SHVec4 Clamp (const SHVec4& v, const SHVec4& vMin, const SHVec4& vMax) noexcept; [[nodiscard]] static SHVec4 Lerp (const SHVec4& a, const SHVec4& b, float t) noexcept; [[nodiscard]] static SHVec4 ClampedLerp (const SHVec4& a, const SHVec4& b, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; @@ -124,10 +136,11 @@ namespace SHADE [[nodiscard]] static SHVec4 Project3D (const SHVec4& v, const SHVec4& u) noexcept; [[nodiscard]] static SHVec4 Reflect (const SHVec4& v, const SHVec4& normal) noexcept; [[nodiscard]] static SHVec4 Reflect3D (const SHVec4& v, const SHVec4& normal) noexcept; + [[nodiscard]] static SHVec4 Transform (const SHVec4& v, const SHMatrix& transformMtx) noexcept; [[nodiscard]] static SHVec4 Transform3D (const SHVec4& v, const SHMatrix& transformMtx) noexcept; }; - SHVec4 operator* (float lhs, const SHVec4& rhs) noexcept; + SHVec4 operator* (float lhs, const SHVec4& rhs) noexcept; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp new file mode 100644 index 00000000..43ad05ca --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp @@ -0,0 +1,93 @@ +/**************************************************************************************** + * \file SHCollisionInfo.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for Collision Info. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionInfo.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionInfo::SHCollisionInfo() noexcept + : collisionState { State::INVALID } + { + ids[ENTITY_A] = MAX_EID; + ids[ENTITY_B] = MAX_EID; + ids[COLLIDER_A] = std::numeric_limits::max(); + ids[COLLIDER_B] = std::numeric_limits::max(); + } + + SHCollisionInfo::SHCollisionInfo(EntityID entityA, EntityID entityB) noexcept + : collisionState { State::INVALID } + { + ids[ENTITY_A] = entityA; + ids[ENTITY_B] = entityB; + ids[COLLIDER_A] = std::numeric_limits::max(); + ids[COLLIDER_B] = std::numeric_limits::max(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHCollisionInfo::operator==(const SHCollisionInfo& rhs) const noexcept + { + return value[0] == rhs.value[0] && value[1] == rhs.value[1]; + } + + bool SHCollisionInfo::operator!=(const SHCollisionInfo& rhs) const noexcept + { + return value[0] != rhs.value[0] || value[1] != rhs.value[1]; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + EntityID SHCollisionInfo::GetEntityA() const noexcept + { + return ids[ENTITY_A]; + } + + EntityID SHCollisionInfo::GetEntityB() const noexcept + { + return ids[ENTITY_B]; + } + + const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyA() const noexcept + { + return SHComponentManager::GetComponent_s(ids[ENTITY_A]); + } + + const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyB() const noexcept + { + return SHComponentManager::GetComponent_s(ids[ENTITY_B]); + } + + const SHCollisionShape* SHCollisionInfo::GetColliderA() const noexcept + { + return &SHComponentManager::GetComponent(ids[ENTITY_A])->GetCollisionShape(ids[COLLIDER_A]); + } + + const SHCollisionShape* SHCollisionInfo::GetColliderB() const noexcept + { + return &SHComponentManager::GetComponent(ids[ENTITY_B])->GetCollisionShape(ids[COLLIDER_B]); + } + + SHCollisionInfo::State SHCollisionInfo::GetCollisionState() const noexcept + { + return collisionState; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h new file mode 100644 index 00000000..d2dad647 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h @@ -0,0 +1,102 @@ +/**************************************************************************************** + * \file SHCollisionInfo.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for Collision Information for Collision & Triggers. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Interface/SHColliderComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHCollisionInfo + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHCollisionListener; + + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class State + { + ENTER + , STAY + , EXIT + + , TOTAL + , INVALID = -1 + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionInfo () noexcept; + SHCollisionInfo (EntityID entityA, EntityID entityB) noexcept; + + + SHCollisionInfo (const SHCollisionInfo& rhs) = default; + SHCollisionInfo (SHCollisionInfo&& rhs) = default; + ~SHCollisionInfo () = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + bool operator== (const SHCollisionInfo& rhs) const noexcept; + bool operator!= (const SHCollisionInfo& rhs) const noexcept; + + SHCollisionInfo& operator= (const SHCollisionInfo& rhs) = default; + SHCollisionInfo& operator= (SHCollisionInfo&& rhs) = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] EntityID GetEntityA () const noexcept; + [[nodiscard]] EntityID GetEntityB () const noexcept; + [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyA () const noexcept; + [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyB () const noexcept; + [[nodiscard]] const SHCollisionShape* GetColliderA () const noexcept; + [[nodiscard]] const SHCollisionShape* GetColliderB () const noexcept; + [[nodiscard]] State GetCollisionState () const noexcept; + + private: + + static constexpr uint32_t ENTITY_A = 0; + static constexpr uint32_t ENTITY_B = 1; + static constexpr uint32_t COLLIDER_A = 2; + static constexpr uint32_t COLLIDER_B = 3; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + union + { + uint64_t value[2]; // EntityValue, ColliderIndexValue + uint32_t ids [4]; // EntityA, EntityB, ColliderIndexA, ColliderIndexB + }; + + State collisionState; + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp new file mode 100644 index 00000000..e8379b09 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp @@ -0,0 +1,236 @@ +/**************************************************************************************** + * \file SHCollisionListener.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collision Listener. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionListener.h" + +// Project Headers +#include "Physics/PhysicsObject/SHPhysicsObject.h" +#include "Physics/System/SHPhysicsSystem.h" + +/*-------------------------------------------------------------------------------------*/ +/* Local Helper Functions */ +/*-------------------------------------------------------------------------------------*/ + +uint32_t matchColliders(const SHADE::SHPhysicsObject&physicsObject, const rp3d::Entity colliderID) +{ + for (uint32_t i = 0; i < physicsObject.GetCollisionBody()->getNbColliders(); ++i) + { + const auto* collider = physicsObject.GetCollisionBody()->getCollider(i); + if (collider->getEntity() == colliderID) + return i; + } + + return std::numeric_limits::max(); +} + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionListener::SHCollisionListener() noexcept + : system { nullptr } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const std::vector& SHCollisionListener::GetCollisionInfoContainer() const noexcept + { + return collisionInfoContainer; + } + + const std::vector& SHCollisionListener::GetTriggerInfoContainer() const noexcept + { + return triggerInfoContainer; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionListener::BindToSystem(SHPhysicsSystem* physicsSystem) noexcept + { + system = physicsSystem; + } + + void SHCollisionListener::BindToWorld(rp3d::PhysicsWorld* world) noexcept + { + if (!world) + return; + + world->setEventListener(this); + } + + void SHCollisionListener::CleanContainers() noexcept + { + static const auto CLEAR = [](std::vector& container) + { + for (auto eventIter = container.begin(); eventIter != container.end();) + { + const bool CLEAR_EVENT = eventIter->GetCollisionState() == SHCollisionInfo::State::EXIT + || eventIter->GetCollisionState() == SHCollisionInfo::State::INVALID; + + if (CLEAR_EVENT) + eventIter = container.erase(eventIter); + else + ++eventIter; + } + }; + + CLEAR(collisionInfoContainer); + CLEAR(triggerInfoContainer); + } + + void SHCollisionListener::ClearContainers() noexcept + { + collisionInfoContainer.clear(); + triggerInfoContainer.clear(); + } + + void SHCollisionListener::onContact(const rp3d::CollisionCallback::CallbackData& callbackData) + { + for (uint32_t i = 0; i < callbackData.getNbContactPairs(); ++i) + { + const auto CONTACT_PAIR = callbackData.getContactPair(i); + const SHCollisionInfo NEW_INFO = generateCollisionInfo(CONTACT_PAIR); + + updateInfoContainers(NEW_INFO, collisionInfoContainer); + } + } + + void SHCollisionListener::onTrigger(const rp3d::OverlapCallback::CallbackData& callbackData) + { + for (uint32_t i = 0; i < callbackData.getNbOverlappingPairs(); ++i) + { + const auto OVERLAP_PAIR = callbackData.getOverlappingPair(i); + const SHCollisionInfo NEW_INFO = generateTriggerInfo(OVERLAP_PAIR); + + updateInfoContainers(NEW_INFO, triggerInfoContainer); + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionListener::updateInfoContainers(const SHCollisionInfo& collisionEvent, std::vector& container) noexcept + { + const auto IT = std::ranges::find_if(container.begin(), container.end(), [&](const SHCollisionInfo& info) + { + const bool ENTITY_MATCH = (info.ids[0] == collisionEvent.ids[0] && info.ids[1] == collisionEvent.ids[1]) + || (info.ids[0] == collisionEvent.ids[1] && info.ids[1] == collisionEvent.ids[0]); + const bool COLLIDERS_MATCH = (info.ids[2] == collisionEvent.ids[2] && info.ids[3] == collisionEvent.ids[3]) + || (info.ids[2] == collisionEvent.ids[3] && info.ids[3] == collisionEvent.ids[2]); + return ENTITY_MATCH && COLLIDERS_MATCH; + }); + + if (IT == container.end()) + container.emplace_back(collisionEvent); + else + IT->collisionState = collisionEvent.collisionState; + } + + SHCollisionInfo SHCollisionListener::generateCollisionInfo(const rp3d::CollisionCallback::ContactPair& cp) const noexcept + { + SHCollisionInfo cInfo; + + // Update collision state + cInfo.collisionState = static_cast(cp.getEventType()); + + // Match body and collider for collision event + const rp3d::Entity body1 = cp.getBody1()->getEntity(); + const rp3d::Entity body2 = cp.getBody2()->getEntity(); + const rp3d::Entity collider1 = cp.getCollider1()->getEntity(); + const rp3d::Entity collider2 = cp.getCollider2()->getEntity(); + + // Find and match both ids + bool matched[2] = { false, false }; + + + for (auto& [entityID, physicsObject] : system->GetPhysicsObjects()) + { + // Match body 1 + if (matched[SHCollisionInfo::ENTITY_A] == false && physicsObject.GetCollisionBody()->getEntity() == body1) + { + cInfo.ids[SHCollisionInfo::ENTITY_A] = entityID; + cInfo.ids[SHCollisionInfo::COLLIDER_A] = matchColliders(physicsObject, collider1); + + matched[SHCollisionInfo::ENTITY_A] = true; + } + + // Match body 2 + if (matched[SHCollisionInfo::ENTITY_B] == false && physicsObject.GetCollisionBody()->getEntity() == body2) + { + cInfo.ids[SHCollisionInfo::ENTITY_B] = entityID; + cInfo.ids[SHCollisionInfo::COLLIDER_B] = matchColliders(physicsObject, collider2); + + matched[SHCollisionInfo::ENTITY_B] = true; + } + + if (matched[SHCollisionInfo::ENTITY_A] == true && matched[SHCollisionInfo::ENTITY_B] == true) + return cInfo; + } + + return cInfo; + } + + SHCollisionInfo SHCollisionListener::generateTriggerInfo(const rp3d::OverlapCallback::OverlapPair& cp) const noexcept + { + SHCollisionInfo cInfo; + + // Update collision state + cInfo.collisionState = static_cast(cp.getEventType()); + + // Match body and collider for collision event + const rp3d::Entity body1 = cp.getBody1()->getEntity(); + const rp3d::Entity body2 = cp.getBody2()->getEntity(); + const rp3d::Entity collider1 = cp.getCollider1()->getEntity(); + const rp3d::Entity collider2 = cp.getCollider2()->getEntity(); + + // Find and match both ids + bool matched[2] = { false, false }; + + + for (auto& [entityID, physicsObject] : system->GetPhysicsObjects()) + { + // Match body 1 + if (matched[SHCollisionInfo::ENTITY_A] == false && physicsObject.GetCollisionBody()->getEntity() == body1) + { + cInfo.ids[SHCollisionInfo::ENTITY_A] = entityID; + cInfo.ids[SHCollisionInfo::COLLIDER_A] = matchColliders(physicsObject, collider1); + + matched[SHCollisionInfo::ENTITY_A] = true; + } + + // Match body 2 + if (matched[SHCollisionInfo::ENTITY_B] == false && physicsObject.GetCollisionBody()->getEntity() == body2) + { + cInfo.ids[SHCollisionInfo::ENTITY_B] = entityID; + cInfo.ids[SHCollisionInfo::COLLIDER_B] = matchColliders(physicsObject, collider2); + + matched[SHCollisionInfo::ENTITY_B] = true; + } + + if (matched[SHCollisionInfo::ENTITY_A] == true && matched[SHCollisionInfo::ENTITY_B] == true) + return cInfo; + } + + return cInfo; + } + + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h new file mode 100644 index 00000000..6262b946 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h @@ -0,0 +1,81 @@ +/**************************************************************************************** + * \file SHCollisionListener.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Listener. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// External Dependencies +#include + +// Project Headers +#include "SH_API.h" +#include "SHCollisionInfo.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + class SHPhysicsSystem; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHCollisionListener final : public rp3d::EventListener + { + public: + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionListener() noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] const std::vector& GetCollisionInfoContainer () const noexcept; + [[nodiscard]] const std::vector& GetTriggerInfoContainer () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void BindToSystem (SHPhysicsSystem* physicsSystem) noexcept; + void BindToWorld (rp3d::PhysicsWorld* world) noexcept; + void CleanContainers () noexcept; + void ClearContainers () noexcept; + + void onContact (const rp3d::CollisionCallback::CallbackData& callbackData) override; + void onTrigger (const rp3d::OverlapCallback::CallbackData& callbackData) override; + + private: + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsSystem* system; + std::vector collisionInfoContainer; + std::vector triggerInfoContainer; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + static void updateInfoContainers (const SHCollisionInfo& collisionEvent, std::vector& container) noexcept; + + SHCollisionInfo generateCollisionInfo (const rp3d::CollisionCallback::ContactPair& cp) const noexcept; + SHCollisionInfo generateTriggerInfo (const rp3d::OverlapCallback::OverlapPair& cp) const noexcept; + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp new file mode 100644 index 00000000..877d238f --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp @@ -0,0 +1,206 @@ +/**************************************************************************************** + * \file SHColliderComponent.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collider Component. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHColliderComponent.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/SHMathHelpers.h" +#include "Physics/System/SHPhysicsSystem.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHColliderComponent::SHColliderComponent() noexcept + : system { nullptr } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + + const SHVec3& SHColliderComponent::GetPosition() const noexcept + { + return position; + } + + const SHQuaternion& SHColliderComponent::GetOrientation() const noexcept + { + return orientation; + } + + SHVec3 SHColliderComponent::GetRotation() const noexcept + { + return orientation.ToEuler(); + } + + const SHVec3& SHColliderComponent::GetScale() const noexcept + { + return scale; + } + + const SHColliderComponent::CollisionShapes& SHColliderComponent::GetCollisionShapes() const noexcept + { + return collisionShapes; + } + + SHCollisionShape& SHColliderComponent::GetCollisionShape(int index) + { + if (index < 0 || static_cast(index) >= collisionShapes.size()) + throw std::invalid_argument("Out-of-range access!"); + + return collisionShapes[index]; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHColliderComponent::OnCreate() + { + auto* physicsSystem = SHSystemManager::GetSystem(); + if (!physicsSystem) + { + SHLOG_ERROR("Physics System does not exist to link with Physics Components!") + return; + } + + system = physicsSystem; + } + + void SHColliderComponent::OnDestroy() + { + + } + + void SHColliderComponent::RecomputeCollisionShapes() noexcept + { + for (auto& collisionShape : collisionShapes) + { + switch (collisionShape.GetType()) + { + case SHCollisionShape::Type::BOX: + { + auto* box = reinterpret_cast(collisionShape.shape); + const SHVec3& RELATIVE_EXTENTS = box->GetRelativeExtents(); + + // Recompute world extents based on new scale and fixed relative extents + const SHVec3 WORLD_EXTENTS = RELATIVE_EXTENTS * (scale * 0.5f); + box->SetWorldExtents(WORLD_EXTENTS); + + continue; + } + case SHCollisionShape::Type::SPHERE: + { + auto* sphere = reinterpret_cast(collisionShape.shape); + const float RELATIVE_RADIUS = sphere->GetRelativeRadius(); + + // Recompute world radius based on new scale and fixed radius + const float MAX_SCALE = SHMath::Max({ scale.x, scale.y, scale.z }); + const float WORLD_RADIUS = RELATIVE_RADIUS * MAX_SCALE * 0.5f; + sphere->SetWorldRadius(WORLD_RADIUS); + + continue; + } + default: continue; + } + } + } + + int SHColliderComponent::AddBoundingBox(const SHVec3& halfExtents, const SHVec3& posOffset, const SHVec3& rotOffset) noexcept + { + if (!system) + { + SHLOG_ERROR("Physics system does not exist, unable to add Box Collider!") + return -1; + } + + static constexpr auto TYPE = SHCollisionShape::Type::BOX; + + auto& collider = collisionShapes.emplace_back(SHCollisionShape{ GetEID(), TYPE }); + + collider.entityID = GetEID(); + collider.SetPositionOffset(posOffset); + collider.SetRotationOffset(rotOffset); + collider.SetBoundingBox(halfExtents); + + // Notify Physics System + const int NEW_SHAPE_INDEX = static_cast(collisionShapes.size()) - 1; + + system->AddCollisionShape(GetEID(), NEW_SHAPE_INDEX); + return NEW_SHAPE_INDEX; + } + + int SHColliderComponent::AddBoundingSphere(float radius, const SHVec3& posOffset) noexcept + { + if (!system) + { + SHLOG_ERROR("Physics system does not exist, unable to add Sphere Collider!") + return -1; + } + + static constexpr auto TYPE = SHCollisionShape::Type::SPHERE; + + auto& collider = collisionShapes.emplace_back(SHCollisionShape{ GetEID(), TYPE }); + + collider.entityID = GetEID(); + collider.SetPositionOffset(posOffset); + collider.SetBoundingSphere(radius); + + // Notify Physics System + const int NEW_SHAPE_INDEX = static_cast(collisionShapes.size()) - 1; + + system->AddCollisionShape(GetEID(), NEW_SHAPE_INDEX); + return NEW_SHAPE_INDEX; + } + + void SHColliderComponent::RemoveCollider(int index) + { + if (index < 0 || static_cast(index) >= collisionShapes.size()) + throw std::invalid_argument("Out-of-range access!"); + + int idx = 0; + auto it = collisionShapes.begin(); + for (; it != collisionShapes.end(); ++it) + { + if (idx == index) + break; + + ++idx; + } + + it = collisionShapes.erase(it); + + // Notify Physics System + if (!system) + { + SHLOG_ERROR("Physics system does not exist, unable to remove Collider!") + return; + } + + system->RemoveCollisionShape(GetEID(), index); + } + +} // namespace SHADE + +RTTR_REGISTRATION +{ + using namespace rttr; + using namespace SHADE; + + registration::class_("Collider Component"); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h new file mode 100644 index 00000000..0781f3cf --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h @@ -0,0 +1,109 @@ +/**************************************************************************************** + * \file SHColliderComponent.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collider Component. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/Components/SHComponent.h" +#include "Math/Geometry/SHBox.h" +#include "Math/Geometry/SHSphere.h" +#include "SHCollisionShape.h" + +//namespace SHADE +//{ +// class SHPhysicsSystem; +//} + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHColliderComponent : public SHComponent + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsSystem; + friend class SHPhysicsObject; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using CollisionShapes = std::vector; + + public: + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHColliderComponent () noexcept; + SHColliderComponent (const SHColliderComponent& rhs) noexcept = default; + SHColliderComponent (SHColliderComponent&& rhs) noexcept = default; + ~SHColliderComponent () override = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHColliderComponent& operator=(const SHColliderComponent& rhs) noexcept = default; + SHColliderComponent& operator=(SHColliderComponent&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool HasChanged () const noexcept; + + [[nodiscard]] const SHVec3& GetPosition () const noexcept; + [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; + [[nodiscard]] SHVec3 GetRotation () const noexcept; + [[nodiscard]] const SHVec3& GetScale () const noexcept; + + [[nodiscard]] const CollisionShapes& GetCollisionShapes() const noexcept; + [[nodiscard]] SHCollisionShape& GetCollisionShape (int index); + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void OnCreate () override; + void OnDestroy () override; + + void RecomputeCollisionShapes () noexcept; + + void RemoveCollider (int index); + + int AddBoundingBox (const SHVec3& halfExtents = SHVec3::One, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero) noexcept; + int AddBoundingSphere (float radius = 1.0f, const SHVec3& posOffset = SHVec3::Zero) noexcept; + + private: + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsSystem* system; + + SHVec3 position; + SHQuaternion orientation; + SHVec3 scale; + CollisionShapes collisionShapes; + + RTTR_ENABLE() + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp new file mode 100644 index 00000000..e63895d5 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp @@ -0,0 +1,337 @@ +/**************************************************************************************** + * \file SHCollider.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collider. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionShape.h" +// Project Headers +#include "Math/Geometry/SHBox.h" +#include "Math/Geometry/SHSphere.h" +#include "Math/SHMathHelpers.h" +#include "Reflection/SHReflectionMetadata.h" +#include "SHColliderComponent.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionShape::SHCollisionShape(EntityID eid, Type colliderType, const SHPhysicsMaterial& physicsMaterial) + : type { colliderType } + , entityID { eid } + , isTrigger { false } + , dirty { true } + , shape { nullptr } + , material { physicsMaterial } + { + switch (type) + { + case Type::BOX: + { + shape = new SHBox{ SHVec3::Zero, SHVec3::One }; + break; + } + case Type::SPHERE: + { + shape = new SHSphere{ SHVec3::Zero, 0.5f }; + break; + } + default: break; + } + } + + SHCollisionShape::SHCollisionShape(const SHCollisionShape& rhs) noexcept + : type { rhs.type} + , entityID { rhs.entityID } + , isTrigger { rhs.isTrigger } + , dirty { true } + , shape { nullptr } + , material { rhs.material } + , positionOffset { rhs.positionOffset } + { + CopyShape(rhs.shape); + } + + SHCollisionShape::SHCollisionShape(SHCollisionShape&& rhs) noexcept + : type { rhs.type} + , entityID { rhs.entityID } + , isTrigger { rhs.isTrigger } + , dirty { true } + , shape { nullptr } + , material { rhs.material } + , positionOffset { rhs.positionOffset } + { + CopyShape(rhs.shape); + } + + SHCollisionShape::~SHCollisionShape() noexcept + { + shape = nullptr; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionShape& SHCollisionShape::operator=(const SHCollisionShape& rhs) noexcept + { + if (this == &rhs) + return *this; + + type = rhs.type; + entityID = rhs.entityID; + isTrigger = rhs.isTrigger; + dirty = true; + material = rhs.material; + positionOffset = rhs.positionOffset; + + delete shape; + CopyShape(rhs.shape); + + return *this; + } + + SHCollisionShape& SHCollisionShape::operator=(SHCollisionShape&& rhs) noexcept + { + type = rhs.type; + entityID = rhs.entityID; + isTrigger = rhs.isTrigger; + dirty = true; + material = rhs.material; + positionOffset = rhs.positionOffset; + + delete shape; + CopyShape(rhs.shape); + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHCollisionShape::HasChanged() const noexcept + { + return dirty; + } + + bool SHCollisionShape::IsTrigger() const noexcept + { + return isTrigger; + } + + SHCollisionShape::Type SHCollisionShape::GetType() const noexcept + { + return type; + } + + float SHCollisionShape::GetFriction() const noexcept + { + return material.GetFriction(); + } + + float SHCollisionShape::GetBounciness() const noexcept + { + return material.GetBounciness(); + } + + float SHCollisionShape::GetDensity() const noexcept + { + return material.GetDensity(); + } + + const SHPhysicsMaterial& SHCollisionShape::GetMaterial() const noexcept + { + return material; + } + + const SHVec3& SHCollisionShape::GetPositionOffset() const noexcept + { + return positionOffset; + } + + const SHVec3& SHCollisionShape::GetRotationOffset() const noexcept + { + return rotationOffset; + } + + const SHShape* SHCollisionShape::GetShape() const noexcept + { + return shape; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionShape::SetBoundingBox(const SHVec3& halfExtents) + { + dirty = true; + + const auto* COLLIDER = SHComponentManager::GetComponent(entityID); + auto* box = reinterpret_cast(shape); + + SHVec3 correctedHalfExtents = halfExtents; + + // Get current relative halfExtents for error checking. 0 is ignored + const SHVec3& CURRENT_RELATIVE_EXTENTS = box->GetRelativeExtents(); + for (size_t i = 0; i < SHVec3::SIZE; ++i) + { + if (SHMath::CompareFloat(halfExtents[i], 0.0f)) + correctedHalfExtents[i] = CURRENT_RELATIVE_EXTENTS[i]; + } + + // Set the half extents relative to world scale + const SHVec3 WORLD_EXTENTS = correctedHalfExtents * COLLIDER->GetScale() * 0.5f; + + if (type != Type::BOX) + { + type = Type::BOX; + + delete shape; + shape = new SHBox{ positionOffset, WORLD_EXTENTS }; + } + + box->SetWorldExtents(WORLD_EXTENTS); + box->SetRelativeExtents(correctedHalfExtents); + } + + void SHCollisionShape::SetBoundingSphere(float radius) + { + dirty = true; + + auto* sphere = reinterpret_cast(shape); + const auto* COLLIDER = SHComponentManager::GetComponent(entityID); + + // Get current relative halfExtents for error checking. 0 is ignored + const float CURRENT_RELATIVE_RADIUS = sphere->GetRelativeRadius(); + if (SHMath::CompareFloat(radius, 0.0f)) + radius = CURRENT_RELATIVE_RADIUS; + + // Set the radius relative to world scale + const SHVec3 WORLD_SCALE = COLLIDER->GetScale(); + const float MAX_SCALE = SHMath::Max({ WORLD_SCALE.x, WORLD_SCALE.y, WORLD_SCALE.z }); + const float WORLD_RADIUS = radius * MAX_SCALE * 0.5f; + + if (type != Type::SPHERE) + { + type = Type::SPHERE; + + delete shape; + shape = new SHSphere{ positionOffset, WORLD_RADIUS }; + } + + sphere->SetWorldRadius(WORLD_RADIUS); + sphere->SetRelativeRadius(radius); + } + + void SHCollisionShape::SetIsTrigger(bool trigger) noexcept + { + dirty = true; + isTrigger = trigger; + } + + void SHCollisionShape::SetFriction(float friction) noexcept + { + dirty = true; + material.SetFriction(friction); + } + + void SHCollisionShape::SetBounciness(float bounciness) noexcept + { + dirty = true; + material.SetBounciness(bounciness); + } + + void SHCollisionShape::SetDensity(float density) noexcept + { + dirty = true; + material.SetDensity(density); + } + + void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept + { + dirty = true; + material = newMaterial; + } + + void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept + { + dirty = true; + positionOffset = posOffset; + + switch (type) + { + case Type::BOX: + { + reinterpret_cast(shape)->SetCenter(positionOffset); + break; + } + case Type::SPHERE: + { + reinterpret_cast(shape)->SetCenter(positionOffset); + break; + } + default: break; + } + } + + void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept + { + dirty = true; + rotationOffset = rotOffset; + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionShape::CopyShape(const SHShape* rhs) + { + switch (type) + { + case Type::BOX: + { + const auto* RHS_BOX = reinterpret_cast(rhs); + + shape = new SHBox{ positionOffset, RHS_BOX->GetWorldExtents() }; + break; + } + case Type::SPHERE: + { + const auto* RHS_SPHERE = reinterpret_cast(rhs); + + shape = new SHSphere{ positionOffset, RHS_SPHERE->GetWorldRadius() }; + break; + } + default: break; + } + } + +} // namespace SHADE + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::enumeration("Collider Type") + ( + value("Box", SHCollisionShape::Type::BOX), + value("Sphere", SHCollisionShape::Type::SPHERE) + // TODO(Diren): Add More Shapes + ); + + registration::class_("Collider") + .property("Position Offset", &SHCollisionShape::GetPositionOffset, &SHCollisionShape::SetPositionOffset) + .property("Rotation Offset", &SHCollisionShape::GetRotationOffset, &SHCollisionShape::SetRotationOffset) (metadata(META::angleInRad, true)); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.h b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.h new file mode 100644 index 00000000..526428fd --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.h @@ -0,0 +1,126 @@ +/**************************************************************************************** + * \file SHCollider.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collider. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/Entity/SHEntity.h" +#include "Math/Geometry/SHShape.h" +#include "Math/SHQuaternion.h" +#include "SHPhysicsMaterial.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHCollisionShape + { + private: + + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHColliderComponent; + friend class SHPhysicsObject; + + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class Type + { + BOX + , SPHERE + , CAPSULE + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionShape (EntityID eid, Type colliderType = Type::BOX, const SHPhysicsMaterial& physicsMaterial = SHPhysicsMaterial::DEFAULT); + + SHCollisionShape (const SHCollisionShape& rhs) noexcept; + SHCollisionShape (SHCollisionShape&& rhs) noexcept; + ~SHCollisionShape () noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionShape& operator=(const SHCollisionShape& rhs) noexcept; + SHCollisionShape& operator=(SHCollisionShape&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool HasChanged () const noexcept; + + [[nodiscard]] bool IsTrigger () const noexcept; + + [[nodiscard]] Type GetType () const noexcept; + + [[nodiscard]] float GetFriction () const noexcept; + [[nodiscard]] float GetBounciness () const noexcept; + [[nodiscard]] float GetDensity () const noexcept; + [[nodiscard]] const SHPhysicsMaterial& GetMaterial () const noexcept; + + [[nodiscard]] const SHVec3& GetPositionOffset () const noexcept; + [[nodiscard]] const SHVec3& GetRotationOffset () const noexcept; + + [[nodiscard]] const SHShape* GetShape () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetBoundingBox (const SHVec3& halfExtents); + void SetBoundingSphere (float radius); + + void SetIsTrigger (bool isTrigger) noexcept; + void SetFriction (float friction) noexcept; + void SetBounciness (float bounciness) noexcept; + void SetDensity (float density) noexcept; + void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; + + void SetPositionOffset (const SHVec3& posOffset) noexcept; + void SetRotationOffset (const SHVec3& rotOffset) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + Type type; + EntityID entityID; // The entity this collider belongs to + bool isTrigger; + bool dirty; + SHShape* shape; + SHPhysicsMaterial material; + SHVec3 positionOffset; + SHVec3 rotationOffset; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void CopyShape(const SHShape* rhs); + + RTTR_ENABLE() + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.cpp b/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.cpp new file mode 100644 index 00000000..95141501 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.cpp @@ -0,0 +1,104 @@ +/**************************************************************************************** + * \file SHPhysicsMaterial.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics Material. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsMaterial.h" +// Project Headers +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHPhysicsMaterial SHPhysicsMaterial::DEFAULT { 0.4f, 0.0f, 1.0f }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsMaterial::SHPhysicsMaterial(float _friction, float _bounciness, float _density) noexcept + : friction { std::clamp(_friction, 0.0f, 1.0f) } + , bounciness{ std::clamp(_bounciness, 0.0f, 1.0f) } + , density { std::fabs(_density) } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHPhysicsMaterial::operator==(const SHPhysicsMaterial& rhs) const noexcept + { + return SHMath::CompareFloat(friction, rhs.friction) + && SHMath::CompareFloat(bounciness, rhs.bounciness) + && SHMath::CompareFloat(density, rhs.density); + } + + bool SHPhysicsMaterial::operator!=(const SHPhysicsMaterial& rhs) const noexcept + { + return !SHMath::CompareFloat(friction, rhs.friction) + || !SHMath::CompareFloat(bounciness, rhs.bounciness) + || !SHMath::CompareFloat(density, rhs.density); + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHPhysicsMaterial::GetFriction() const noexcept + { + return friction; + } + + float SHPhysicsMaterial::GetBounciness() const noexcept + { + return bounciness; + } + + float SHPhysicsMaterial::GetDensity() const noexcept + { + return density; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsMaterial::SetFriction(float newFriction) noexcept + { + if (newFriction < 0.0f || newFriction > 1.0f) + { + SHLOG_WARNING("Clamping friction of Physics Material between [0,1].") + } + friction = std::clamp(newFriction, 0.0f, 1.0f); + } + + void SHPhysicsMaterial::SetBounciness(float newBounciness) noexcept + { + if (newBounciness < 0.0f || newBounciness > 1.0f) + { + SHLOG_WARNING("Clamping bounciness of Physics Material between [0,1].") + } + bounciness = std::clamp(newBounciness, 0.0f, 1.0f); + } + + void SHPhysicsMaterial::SetDensity(float newDensity) noexcept + { + if (newDensity < 0.0f) + { + SHLOG_WARNING("Setting negative density of Physics Material to positive.") + } + density = std::fabs(newDensity); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.h b/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.h new file mode 100644 index 00000000..b3db1655 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.h @@ -0,0 +1,113 @@ +/**************************************************************************************** + * \file SHPhysicsMaterial.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics Material. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsMaterial + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static const SHPhysicsMaterial DEFAULT; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsMaterial (const SHPhysicsMaterial&) noexcept = default; + SHPhysicsMaterial (SHPhysicsMaterial&&) noexcept = default; + ~SHPhysicsMaterial() = default; + + /** + * @brief Default constructor for a physics material. + * @param friction The friction of the material. Clamped between [0,1]. Defaults to 0.4. + * @param bounciness The bounciness of the material. Clamped between [0,1]. + * @param density The mass density of the material. Always made positive. + */ + SHPhysicsMaterial (float friction = 0.4f, float bounciness = 0.0f, float density = 1.0f) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsMaterial& operator= (const SHPhysicsMaterial&) noexcept = default; + SHPhysicsMaterial& operator= (SHPhysicsMaterial&&) noexcept = default; + + bool operator==(const SHPhysicsMaterial& rhs) const noexcept; + bool operator!=(const SHPhysicsMaterial& rhs) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] float GetFriction () const noexcept; + [[nodiscard]] float GetBounciness () const noexcept; + [[nodiscard]] float GetDensity () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief Sets the friction coefficient of the physics material. + * @param newFriction The friction value to set. Clamped between [0,1]. + */ + void SetFriction (float newFriction) noexcept; + + /** + * @brief Sets the bounciness factor of the physics material. + * @param newBounciness The bounciness value to set. Clamped between [0,1]. + */ + void SetBounciness (float newBounciness) noexcept; + + /** + * @brief Sets the mass density of the physics material. + * @param newDensity The density value to set. Always made positive. + */ + void SetDensity (float newDensity) noexcept; + + private: + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief The friction coefficient of the physics object., clamped between [0,1].
+ * 0 means the object will never experience friction. + * 1 means the friction force against the object is equal to the applied force. + */ + float friction; + + /** + * @brief The bounciness factor of the physics object., clamped between [0,1].
+ * 0 means the object will never bounce. + * 1 means the object never loses energy on a bounce. + */ + float bounciness; + + /** + * @brief The density of the collider that determines the mass of the collision shape + * if it is automatically computed. Must be a positive number. + */ + float density; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp new file mode 100644 index 00000000..765decd8 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp @@ -0,0 +1,476 @@ +/**************************************************************************************** + * \file SHRigidBodyComponent.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Rigidbody Component. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// External Dependencies +#include + +// Primary Header +#include "SHRigidBodyComponent.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/SHMathHelpers.h" +#include "Physics/System/SHPhysicsSystem.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBodyComponent::SHRigidBodyComponent() noexcept + : type { Type::DYNAMIC } + , interpolate { true } + , flags { 0 } + , dirtyFlags { std::numeric_limits::max() } + , mass { 1.0f } + , drag { 0.01f } + , angularDrag { 0.01f } + , system { nullptr } + { + // Initialise default flags + flags |= 1U << 0; // Gravity set to true + flags |= 1U << 1; // Sleeping allowed + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHRigidBodyComponent::IsGravityEnabled() const noexcept + { + static constexpr int FLAG_POS = 0; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::IsAllowedToSleep() const noexcept + { + static constexpr int FLAG_POS = 1; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::IsInterpolating() const noexcept + { + return interpolate; + } + + SHRigidBodyComponent::Type SHRigidBodyComponent::GetType() const noexcept + { + return type; + } + + float SHRigidBodyComponent::GetMass() const noexcept + { + return mass; + } + + float SHRigidBodyComponent::GetDrag() const noexcept + { + return drag; + } + + float SHRigidBodyComponent::GetAngularDrag() const noexcept + { + return angularDrag; + } + + bool SHRigidBodyComponent::GetFreezePositionX() const noexcept + { + static constexpr int FLAG_POS = 2; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::GetFreezePositionY() const noexcept + { + static constexpr int FLAG_POS = 3; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::GetFreezePositionZ() const noexcept + { + static constexpr int FLAG_POS = 4; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::GetFreezeRotationX() const noexcept + { + static constexpr int FLAG_POS = 5; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::GetFreezeRotationY() const noexcept + { + static constexpr int FLAG_POS = 6; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBodyComponent::GetFreezeRotationZ() const noexcept + { + static constexpr int FLAG_POS = 7; + return flags & (1U << FLAG_POS); + } + + SHVec3 SHRigidBodyComponent::GetForce() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + return physicsObject->GetRigidBody()->getForce(); + + return SHVec3::Zero; + } + + SHVec3 SHRigidBodyComponent::GetTorque() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + return physicsObject->GetRigidBody()->getTorque(); + + return SHVec3::Zero; + } + + SHVec3 SHRigidBodyComponent::GetLinearVelocity() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + return physicsObject->GetRigidBody()->getLinearVelocity(); + + return SHVec3::Zero; + } + + SHVec3 SHRigidBodyComponent::GetAngularVelocity() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + return physicsObject->GetRigidBody()->getAngularVelocity(); + + return SHVec3::Zero; + } + + const SHVec3& SHRigidBodyComponent::GetPosition() const noexcept + { + return position; + } + + const SHQuaternion& SHRigidBodyComponent::GetOrientation() const noexcept + { + return orientation; + } + + SHVec3 SHRigidBodyComponent::GetRotation() const noexcept + { + return orientation.ToEuler(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBodyComponent::SetType(Type newType) noexcept + { + static constexpr int FLAG_POS = 8; + + if (type == newType) + return; + + type = newType; + dirtyFlags |= 1U << FLAG_POS; + } + + void SHRigidBodyComponent::SetGravityEnabled(bool enableGravity) noexcept + { + static constexpr int FLAG_POS = 0; + + if (type != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot enable gravity of a non-dynamic object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + enableGravity ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetIsAllowedToSleep(bool isAllowedToSleep) noexcept + { + static constexpr int FLAG_POS = 1; + + if (type != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot enable sleeping of a non-dynamic object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + isAllowedToSleep ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezePositionX(bool freezePositionX) noexcept + { + static constexpr int FLAG_POS = 2; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezePositionX ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezePositionY(bool freezePositionY) noexcept + { + static constexpr int FLAG_POS = 3; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezePositionY ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezePositionZ(bool freezePositionZ) noexcept + { + static constexpr int FLAG_POS = 4; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezePositionZ ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezeRotationX(bool freezeRotationX) noexcept + { + static constexpr int FLAG_POS = 5; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezeRotationX ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezeRotationY(bool freezeRotationY) noexcept + { + static constexpr int FLAG_POS = 6; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezeRotationY ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetFreezeRotationZ(bool freezeRotationZ) noexcept + { + static constexpr int FLAG_POS = 7; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + freezeRotationZ ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } + + void SHRigidBodyComponent::SetInterpolate(bool allowInterpolation) noexcept + { + interpolate = allowInterpolation; + } + + void SHRigidBodyComponent::SetMass(float newMass) noexcept + { + static constexpr int FLAG_POS = 9; + + if (newMass < 0.0f) + return; + + if (type != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot set mass of a non-dynamic object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + mass = newMass; + } + + void SHRigidBodyComponent::SetDrag(float newDrag) noexcept + { + static constexpr int FLAG_POS = 10; + + if (type != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot set drag of a non-dynamic object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + drag = newDrag; + } + + void SHRigidBodyComponent::SetAngularDrag(float newAngularDrag) noexcept + { + static constexpr int FLAG_POS = 11; + + if (type != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot set angular drag of a non-dynamic object {}", GetEID()) + return; + } + + dirtyFlags |= 1U << FLAG_POS; + angularDrag = newAngularDrag; + } + + void SHRigidBodyComponent::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept + { + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear velocity of a static object {}", GetEID()) + return; + } + + auto* physicsObject = system->GetPhysicsObject(GetEID()); + physicsObject->GetRigidBody()->setLinearVelocity(newLinearVelocity); + } + + void SHRigidBodyComponent::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept + { + static constexpr int FLAG_POS = 13; + + if (type == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular velocity of a static object {}", GetEID()) + return; + } + + auto* physicsObject = system->GetPhysicsObject(GetEID()); + physicsObject->GetRigidBody()->setAngularVelocity(newAngularVelocity); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBodyComponent::OnCreate() + { + auto* physicsSystem = SHSystemManager::GetSystem(); + if (!physicsSystem) + { + SHLOG_ERROR("Physics System does not exist to link with Physics Components!") + return; + } + + system = physicsSystem; + } + + void SHRigidBodyComponent::AddForce(const SHVec3& force) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyWorldForceAtCenterOfMass(force); + } + + void SHRigidBodyComponent::AddForceAtLocalPos(const SHVec3& force, const SHVec3& localPos) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyWorldForceAtLocalPosition(force, localPos); + } + + void SHRigidBodyComponent::AddForceAtWorldPos(const SHVec3& force, const SHVec3& worldPos) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyWorldForceAtWorldPosition(force, worldPos); + } + + void SHRigidBodyComponent::AddRelativeForce(const SHVec3& relativeForce) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyLocalForceAtCenterOfMass(relativeForce); + } + + void SHRigidBodyComponent::AddRelativeForceAtLocalPos(const SHVec3& relativeForce, const SHVec3& localPos) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyLocalForceAtLocalPosition(relativeForce, localPos); + } + + void SHRigidBodyComponent::AddRelativeForceAtWorldPos(const SHVec3& relativeForce, const SHVec3& worldPos) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyLocalForceAtWorldPosition(relativeForce, worldPos); + } + + void SHRigidBodyComponent::AddTorque(const SHVec3& torque) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyWorldTorque(torque); + } + + void SHRigidBodyComponent::AddRelativeTorque(const SHVec3& relativeTorque) const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->applyLocalTorque(relativeTorque); + } + + void SHRigidBodyComponent::ClearForces() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->resetForce(); + } + + void SHRigidBodyComponent::ClearTorque() const noexcept + { + if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) + physicsObject->GetRigidBody()->resetTorque(); + } + +} // namespace SHADE + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::enumeration("RigidBody Type") + ( + value("Static", SHRigidBodyComponent::Type::STATIC), + value("Kinematic", SHRigidBodyComponent::Type::KINEMATIC), + value("Dynamic", SHRigidBodyComponent::Type::DYNAMIC) + ); + + registration::class_("RigidBody Component") + .property("Type" , &SHRigidBodyComponent::GetType , &SHRigidBodyComponent::SetType ) + .property("Mass" , &SHRigidBodyComponent::GetMass , &SHRigidBodyComponent::SetMass ) + .property("Drag" , &SHRigidBodyComponent::GetDrag , &SHRigidBodyComponent::SetDrag ) + .property("Angular Drag" , &SHRigidBodyComponent::GetAngularDrag , &SHRigidBodyComponent::SetAngularDrag ) + .property("Use Gravity" , &SHRigidBodyComponent::IsGravityEnabled , &SHRigidBodyComponent::SetGravityEnabled ) + .property("Interpolate" , &SHRigidBodyComponent::IsInterpolating , &SHRigidBodyComponent::SetInterpolate ) + .property("Freeze Position X" , &SHRigidBodyComponent::GetFreezePositionX , &SHRigidBodyComponent::SetFreezePositionX ) + .property("Freeze Position Y" , &SHRigidBodyComponent::GetFreezePositionY , &SHRigidBodyComponent::SetFreezePositionY ) + .property("Freeze Position Z" , &SHRigidBodyComponent::GetFreezePositionZ , &SHRigidBodyComponent::SetFreezePositionZ ) + .property("Freeze Rotation X" , &SHRigidBodyComponent::GetFreezeRotationX , &SHRigidBodyComponent::SetFreezeRotationX ) + .property("Freeze Rotation Y" , &SHRigidBodyComponent::GetFreezeRotationY , &SHRigidBodyComponent::SetFreezeRotationY ) + .property("Freeze Rotation Z" , &SHRigidBodyComponent::GetFreezeRotationZ , &SHRigidBodyComponent::SetFreezeRotationZ ); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h new file mode 100644 index 00000000..f7062f96 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h @@ -0,0 +1,165 @@ +/**************************************************************************************** + * \file SHRigidBodyComponent.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Rigidbody Component. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/Components/SHComponent.h" +#include "Math/Vector/SHVec3.h" +#include "Math/SHQuaternion.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHRigidBodyComponent : public SHComponent + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsSystem; + friend class SHPhysicsObject; + + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class Type + { + STATIC + , KINEMATIC + , DYNAMIC + + , COUNT + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHRigidBodyComponent () noexcept; + SHRigidBodyComponent (const SHRigidBodyComponent& rhs) noexcept = default; + SHRigidBodyComponent (SHRigidBodyComponent&& rhs) noexcept = default; + ~SHRigidBodyComponent () override = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHRigidBodyComponent& operator=(const SHRigidBodyComponent& rhs) noexcept = default; + SHRigidBodyComponent& operator=(SHRigidBodyComponent&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool IsGravityEnabled () const noexcept; + [[nodiscard]] bool IsAllowedToSleep () const noexcept; + [[nodiscard]] bool IsInterpolating () const noexcept; + + [[nodiscard]] Type GetType () const noexcept; + [[nodiscard]] float GetMass () const noexcept; + [[nodiscard]] float GetDrag () const noexcept; + [[nodiscard]] float GetAngularDrag () const noexcept; + + [[nodiscard]] bool GetFreezePositionX () const noexcept; + [[nodiscard]] bool GetFreezePositionY () const noexcept; + [[nodiscard]] bool GetFreezePositionZ () const noexcept; + + [[nodiscard]] bool GetFreezeRotationX () const noexcept; + [[nodiscard]] bool GetFreezeRotationY () const noexcept; + [[nodiscard]] bool GetFreezeRotationZ () const noexcept; + + [[nodiscard]] SHVec3 GetForce () const noexcept; + [[nodiscard]] SHVec3 GetTorque () const noexcept; + [[nodiscard]] SHVec3 GetLinearVelocity () const noexcept; + [[nodiscard]] SHVec3 GetAngularVelocity () const noexcept; + + [[nodiscard]] const SHVec3& GetPosition () const noexcept; + [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; + [[nodiscard]] SHVec3 GetRotation () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetType (Type newType) noexcept; + + void SetGravityEnabled (bool enableGravity) noexcept; + void SetIsAllowedToSleep (bool isAllowedToSleep) noexcept; + void SetFreezePositionX (bool freezePositionX) noexcept; + void SetFreezePositionY (bool freezePositionY) noexcept; + void SetFreezePositionZ (bool freezePositionZ) noexcept; + void SetFreezeRotationX (bool freezeRotationX) noexcept; + void SetFreezeRotationY (bool freezeRotationY) noexcept; + void SetFreezeRotationZ (bool freezeRotationZ) noexcept; + void SetInterpolate (bool allowInterpolation) noexcept; + + void SetMass (float newMass) noexcept; + void SetDrag (float newDrag) noexcept; + void SetAngularDrag (float newAngularDrag) noexcept; + + void SetLinearVelocity (const SHVec3& newLinearVelocity) noexcept; + void SetAngularVelocity (const SHVec3& newAngularVelocity) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void OnCreate () override; + + void AddForce (const SHVec3& force) const noexcept; + void AddForceAtLocalPos (const SHVec3& force, const SHVec3& localPos) const noexcept; + void AddForceAtWorldPos (const SHVec3& force, const SHVec3& worldPos) const noexcept; + void AddRelativeForce (const SHVec3& relativeForce) const noexcept; + void AddRelativeForceAtLocalPos (const SHVec3& relativeForce, const SHVec3& localPos) const noexcept; + void AddRelativeForceAtWorldPos (const SHVec3& relativeForce, const SHVec3& worldPos) const noexcept; + void AddTorque (const SHVec3& torque) const noexcept; + void AddRelativeTorque (const SHVec3& relativeTorque) const noexcept; + + void ClearForces () const noexcept; + void ClearTorque () const noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t NUM_FLAGS = 8; + static constexpr size_t NUM_DIRTY_FLAGS = 12; + + Type type; + + bool interpolate; + uint8_t flags; // aZ aY aX lZ lY lX slp g + uint16_t dirtyFlags; // 0 0 0 0 aD d m t aZ aY aX lZ lY lX slp g + + float mass; + float drag; + float angularDrag; + + SHVec3 linearVelocity; + SHVec3 angularVelocity; + + SHPhysicsSystem* system; + + SHVec3 position; + SHQuaternion orientation; + + RTTR_ENABLE() + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp new file mode 100644 index 00000000..a52d3899 --- /dev/null +++ b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp @@ -0,0 +1,378 @@ +/**************************************************************************************** + * \file SHPhysicsObject.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics Object. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsObject.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Scene/SHSceneManager.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject::SHPhysicsObject(EntityID eid, rp3d::PhysicsCommon* physicsFactory, rp3d::PhysicsWorld* physicsWorld) noexcept + : entityID { eid } + , collidersActive { true } + , factory { physicsFactory } + , world { physicsWorld } + , rp3dBody { nullptr } + { + // Implicitly create a static body. + + const auto* TRANSFORM = SHComponentManager::GetComponent(eid); + + const rp3d::Transform RP3D_TRANSFORM { TRANSFORM->GetWorldPosition(), TRANSFORM->GetWorldOrientation() }; + + rp3dBody = world->createRigidBody(RP3D_TRANSFORM); + rp3dBody->setType(rp3d::BodyType::STATIC); + } + + SHPhysicsObject::~SHPhysicsObject() noexcept + { + factory = nullptr; + world = nullptr; + rp3dBody = nullptr; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3 SHPhysicsObject::GetPosition() const noexcept + { + return rp3dBody->getTransform().getPosition(); + } + + SHQuaternion SHPhysicsObject::GetOrientation() const noexcept + { + return rp3dBody->getTransform().getOrientation(); + } + + SHVec3 SHPhysicsObject::GetRotation() const noexcept + { + return SHQuaternion{ rp3dBody->getTransform().getOrientation() }.ToEuler(); + } + + rp3d::CollisionBody* SHPhysicsObject::GetCollisionBody() const noexcept + { + return rp3dBody; + } + + rp3d::RigidBody* SHPhysicsObject::GetRigidBody() const noexcept + { + return rp3dBody; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObject::SetStaticBody() const noexcept + { + if (!rp3dBody) + return; + + rp3dBody->setType(rp3d::BodyType::STATIC); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + int SHPhysicsObject::AddCollisionShape(int index) const + { + // Get collider component + auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); + if (!colliderComponent) + { + SHLOGV_ERROR("Unable to add Collision Shape to Entity {} due to Missing Collider Component!", entityID) + return -1; + } + + auto& collisionShape = colliderComponent->GetCollisionShape(index); + switch (collisionShape.GetType()) + { + // TODO(Diren): Add more collider shapes + + case SHCollisionShape::Type::BOX: addBoxShape(collisionShape); break; + case SHCollisionShape::Type::SPHERE: addSphereShape(collisionShape); break; + default: break; + } + + rp3dBody->updateLocalCenterOfMassFromColliders(); + rp3dBody->updateLocalInertiaTensorFromColliders(); + + return index; + } + + void SHPhysicsObject::RemoveCollisionShape(int index) const + { + const int NUM_COLLIDERS = static_cast(rp3dBody->getNbColliders()); + if (NUM_COLLIDERS == 0) + return; + + if (index < 0 || index >= NUM_COLLIDERS) + throw std::invalid_argument("Index out of range!"); + + auto* collider = rp3dBody->getCollider(index); + rp3dBody->removeCollider(collider); + } + + void SHPhysicsObject::RemoveAllCollisionShapes() const noexcept + { + int numColliders = static_cast(rp3dBody->getNbColliders()); + if (numColliders == 0) + return; + + while (numColliders - 1 >= 0) + { + auto* collider = rp3dBody->getCollider(numColliders - 1); + rp3dBody->removeCollider(collider); + + --numColliders; + } + } + + void SHPhysicsObject::SyncRigidBody(SHRigidBodyComponent& component) const noexcept + { + // This state is synced in the pre-update routine + if (!rp3dBody->isActive()) + return; + + if (component.dirtyFlags == 0) + return; + + for (size_t i = 0; i < SHRigidBodyComponent::NUM_DIRTY_FLAGS; ++i) + { + if (const bool IS_DIRTY = component.dirtyFlags & (1U << i); IS_DIRTY) + { + switch (i) + { + case 0: // Gravity + { + const bool IS_ENABLED = component.flags & (1U << i); + rp3dBody->enableGravity(IS_ENABLED); + + break; + } + case 1: // Sleeping + { + const bool IS_ENABLED = component.flags & (1U << i); + rp3dBody->setIsAllowedToSleep(IS_ENABLED); + + break; + } + case 2: // Lock Position X + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto positionLock = rp3dBody->getLinearLockAxisFactor(); + positionLock.x = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setLinearLockAxisFactor(positionLock); + + break; + } + case 3: // Lock Position Y + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto positionLock = rp3dBody->getLinearLockAxisFactor(); + positionLock.y = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setLinearLockAxisFactor(positionLock); + + break; + } + case 4: // Lock Position Z + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto positionLock = rp3dBody->getLinearLockAxisFactor(); + positionLock.z = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setLinearLockAxisFactor(positionLock); + + break; + } + case 5: // Lock Rotation X + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto rotationLock = rp3dBody->getAngularLockAxisFactor(); + rotationLock.x = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setAngularLockAxisFactor(rotationLock); + + break; + } + case 6: // Lock Rotation Y + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto rotationLock = rp3dBody->getAngularLockAxisFactor(); + rotationLock.y = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setAngularLockAxisFactor(rotationLock); + + break; + } + case 7: // Lock Rotation Z + { + const bool IS_ENABLED = component.flags & (1U << i); + + auto rotationLock = rp3dBody->getAngularLockAxisFactor(); + rotationLock.z = IS_ENABLED ? 0.0f : 1.0f; + rp3dBody->setAngularLockAxisFactor(rotationLock); + + break; + } + case 8: // Type + { + rp3dBody->setType(static_cast(component.type)); + + break; + } + case 9: // Mass + { + rp3dBody->setMass(component.mass); + rp3dBody->updateLocalCenterOfMassFromColliders(); + rp3dBody->updateLocalInertiaTensorFromColliders(); + + break; + } + case 10: // Drag + { + rp3dBody->setLinearDamping(component.drag); + + break; + } + case 11: // Angular Drag + { + rp3dBody->setAngularDamping(component.angularDrag); + + break; + } + default: break; + } + } + } + + component.dirtyFlags = 0; + } + + void SHPhysicsObject::SyncColliders(SHColliderComponent& component) const noexcept + { + // This state is synced in the pre-update routine + if (!rp3dBody->isActive()) + return; + + const int NUM_SHAPES = static_cast(component.collisionShapes.size()); + for (int i = 0; i < NUM_SHAPES; ++i) + { + auto& collisionShape = component.collisionShapes[i]; + + if (!collisionShape.dirty) + continue; + + switch (collisionShape.GetType()) + { + case SHCollisionShape::Type::BOX: syncBoxShape(i, collisionShape); break; + case SHCollisionShape::Type::SPHERE: syncSphereShape(i, collisionShape); break; + default: break; + } + + syncMaterial(i, collisionShape); + + collisionShape.dirty = false; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObject::syncMaterial(int colliderIndex, SHCollisionShape& collisionShape) const noexcept + { + auto& rp3dMaterial = rp3dBody->getCollider(colliderIndex)->getMaterial(); + rp3dMaterial.setFrictionCoefficient(collisionShape.GetFriction()); + rp3dMaterial.setBounciness(collisionShape.GetBounciness()); + rp3dMaterial.setMassDensity(collisionShape.GetDensity()); + } + + void SHPhysicsObject::addBoxShape(SHCollisionShape& boxShape) const noexcept + { + const rp3d::Transform OFFSETS + { + boxShape.GetPositionOffset() + , boxShape.GetRotationOffset() + }; + + const auto* BOX = reinterpret_cast(boxShape.GetShape()); + rp3d::BoxShape* newBox = factory->createBoxShape(BOX->GetWorldExtents()); + + rp3dBody->addCollider(newBox, OFFSETS); + } + + void SHPhysicsObject::syncBoxShape(int index, SHCollisionShape& boxShape) const noexcept + { + const auto* BOX = reinterpret_cast(boxShape.GetShape()); + + auto* rp3dCollider = rp3dBody->getCollider(index); + auto* rp3dBox = reinterpret_cast(rp3dCollider->getCollisionShape()); + + const rp3d::Transform OFFSETS + { + boxShape.GetPositionOffset() + , boxShape.GetRotationOffset() + }; + + rp3dCollider->setIsTrigger(boxShape.IsTrigger()); + rp3dCollider->setLocalToBodyTransform(OFFSETS); + + rp3dBox->setHalfExtents(BOX->GetWorldExtents()); + } + + void SHPhysicsObject::addSphereShape(SHCollisionShape& sphereShape) const noexcept + { + const rp3d::Transform OFFSETS + { + sphereShape.GetPositionOffset() + , sphereShape.GetRotationOffset() + }; + + const auto* SPHERE = reinterpret_cast(sphereShape.GetShape()); + rp3d::SphereShape* newSphere = factory->createSphereShape(SPHERE->GetWorldRadius()); + + rp3dBody->addCollider(newSphere, OFFSETS); + } + + void SHPhysicsObject::syncSphereShape(int index, SHCollisionShape& sphereShape) const noexcept + { + const auto* SPHERE = reinterpret_cast(sphereShape.GetShape()); + + auto* rp3dCollider = rp3dBody->getCollider(index); + auto* rp3dSphere = reinterpret_cast(rp3dCollider->getCollisionShape()); + + const rp3d::Transform OFFSETS + { + sphereShape.GetPositionOffset() + , sphereShape.GetRotationOffset() + }; + + rp3dCollider->setIsTrigger(sphereShape.IsTrigger()); + rp3dCollider->setLocalToBodyTransform(OFFSETS); + + rp3dSphere->setRadius(SPHERE->GetWorldRadius()); + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h new file mode 100644 index 00000000..5a0e62ac --- /dev/null +++ b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h @@ -0,0 +1,111 @@ +/**************************************************************************************** + * \file SHPhysicsObject.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics Object. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/SHColliderComponent.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsObject + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsSystem; + friend class SHPhysicsObjectManager; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsObject (EntityID eid, rp3d::PhysicsCommon* physicsFactory, rp3d::PhysicsWorld* physicsWorld) noexcept; + SHPhysicsObject (const SHPhysicsObject& rhs) noexcept = default; + SHPhysicsObject (SHPhysicsObject&& rhs) noexcept = default; + virtual ~SHPhysicsObject () noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsObject& operator=(const SHPhysicsObject& rhs) noexcept = default; + SHPhysicsObject& operator=(SHPhysicsObject&& rhs) noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec3 GetPosition () const noexcept; + [[nodiscard]] SHQuaternion GetOrientation () const noexcept; + [[nodiscard]] SHVec3 GetRotation () const noexcept; + + [[nodiscard]] rp3d::CollisionBody* GetCollisionBody () const noexcept; + [[nodiscard]] rp3d::RigidBody* GetRigidBody () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetStaticBody () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + int AddCollisionShape (int index) const; + void RemoveCollisionShape (int index) const; + void RemoveAllCollisionShapes () const noexcept; + + void SyncRigidBody (SHRigidBodyComponent& component) const noexcept; + void SyncColliders (SHColliderComponent& component) const noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + EntityID entityID; + bool collidersActive; // Only used to sync with SHADE components + + rp3d::PhysicsCommon* factory; + rp3d::PhysicsWorld* world; + + rp3d::RigidBody* rp3dBody; + rp3d::Transform prevTransform; // Cached transform for interpolation + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void syncMaterial (int colliderIndex, SHCollisionShape& collisionShape) const noexcept; + + // Box Shapes + + void addBoxShape (SHCollisionShape& boxShape) const noexcept; + void syncBoxShape (int index, SHCollisionShape& boxShape) const noexcept; + + // Sphere Shapes + + void addSphereShape (SHCollisionShape& sphereShape) const noexcept; + void syncSphereShape (int index, SHCollisionShape& sphereShape) const noexcept; + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp new file mode 100644 index 00000000..f8a4040f --- /dev/null +++ b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp @@ -0,0 +1,312 @@ +/**************************************************************************************** + * \file SHPhysicsObjectManager.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics Object Manager. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsObjectManager.h" + +// Project Headers +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Tools/SHUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager::CommandFunctionPtr SHPhysicsObjectManager::componentFunc[2][3] + { + addRigidBody , addCollider + , removeRigidBody , removeCollider + , addCollisionShape , removeCollisionShape + }; + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObjectManager::SetFactory(rp3d::PhysicsCommon& physicsFactory) noexcept + { + factory = &physicsFactory; + } + + void SHPhysicsObjectManager::SetWorld(rp3d::PhysicsWorld* physicsWorld) noexcept + { + world = physicsWorld; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject* SHPhysicsObjectManager::GetPhysicsObject(EntityID eid) noexcept + { + const auto it = physicsObjects.find(eid); + if (it == physicsObjects.end()) + return nullptr; + + return &it->second; + } + + const SHPhysicsObjectManager::PhysicsObjectEntityMap SHPhysicsObjectManager::GetPhysicsObjects() const noexcept + { + return physicsObjects; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObjectManager::AddRigidBody(EntityID eid) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::ADD + , .component = PhysicsComponents::RIGID_BODY + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::AddCollider(EntityID eid) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::ADD + , .component = PhysicsComponents::COLLIDER + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::AddCollisionShape(EntityID eid, int shapeIndex) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::ADD + , .component = PhysicsComponents::COLLISION_SHAPE + , .shapeIndex = shapeIndex + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::RemoveRigidBody(EntityID eid) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::REMOVE + , .component = PhysicsComponents::RIGID_BODY + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::RemoveCollider(EntityID eid) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::REMOVE + , .component = PhysicsComponents::COLLIDER + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::RemoveCollisionShape(EntityID eid, int shapeIndex) noexcept + { + const QueueCommand NEW_QUEUE_COMMAND + { + .eid = eid + , .command = QueueCommand::Command::REMOVE + , .component = PhysicsComponents::COLLISION_SHAPE + , .shapeIndex = shapeIndex + }; + + commandQueue.push(NEW_QUEUE_COMMAND); + } + + void SHPhysicsObjectManager::UpdateCommands() + { + if (commandQueue.empty()) + return; + + while (!commandQueue.empty()) + { + const QueueCommand COMMAND = commandQueue.front(); + commandQueue.pop(); + + // Check validity of command + if (COMMAND.command == QueueCommand::Command::INVALID || COMMAND.component == PhysicsComponents::INVALID) + continue; + + // Get physics components + const PhysicsComponentGroup COMPONENT_GROUP + { + .eid = COMMAND.eid + , .rigidBodyComponent = SHComponentManager::GetComponent_s(COMMAND.eid) + , .colliderComponent = SHComponentManager::GetComponent_s(COMMAND.eid) + }; + + // Delete any object that is missing both components + // We infer that a remove command has been pushed for these, but we will ignore those if both components have already been removed. + if (!COMPONENT_GROUP.rigidBodyComponent && !COMPONENT_GROUP.colliderComponent) + { + destroyPhysicsObject(COMMAND.eid); + wakeAllObjects(); + continue; + } + + // Find the physics Object. If none found and attempting to add, create an object. + SHPhysicsObject* physicsObject = GetPhysicsObject(COMMAND.eid); + if (!physicsObject && COMMAND.command == QueueCommand::Command::ADD) + physicsObject = createPhysicsObject(COMMAND.eid); + + componentFunc[SHUtilities::ConvertEnum(COMMAND.command)][SHUtilities::ConvertEnum(COMMAND.component)](COMMAND, physicsObject, COMPONENT_GROUP); + + // If any removal was done, wake all objects + if (COMMAND.command == QueueCommand::Command::REMOVE) + wakeAllObjects(); + } + } + + void SHPhysicsObjectManager::RemoveAllObjects() + { + // Destroy all objects and clear + for (auto& physicsObject : physicsObjects | std::views::values) + { + world->destroyRigidBody(physicsObject.GetRigidBody()); + physicsObject.rp3dBody = nullptr; + } + + physicsObjects.clear(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject* SHPhysicsObjectManager::createPhysicsObject(EntityID eid) noexcept + { + // Force transforms to sync + SHVec3 worldPos = SHVec3::Zero; + SHQuaternion worldRot = SHQuaternion::Identity; + + const SHTransformComponent* TRANSFORM = nullptr; + if (SHEntityManager::IsValidEID(eid)) + TRANSFORM = SHComponentManager::GetComponent_s(eid); + + if (TRANSFORM) + { + worldPos = TRANSFORM->GetWorldPosition(); + worldRot = TRANSFORM->GetWorldOrientation(); + } + + const rp3d::Transform RP3D_TRANSFORM{ worldPos, worldRot }; + + auto& newPhysicsObject = physicsObjects.emplace(eid, SHPhysicsObject{ eid, factory, world }).first->second; + newPhysicsObject.GetRigidBody()->setTransform(RP3D_TRANSFORM); + newPhysicsObject.prevTransform = RP3D_TRANSFORM; + + return &newPhysicsObject; + } + + void SHPhysicsObjectManager::destroyPhysicsObject(EntityID eid) noexcept + { + const auto ITER = physicsObjects.find(eid); + if (ITER == physicsObjects.end()) + { + // Assume the object has already been successfully destroyed + return; + } + + world->destroyRigidBody(ITER->second.GetRigidBody()); + ITER->second.rp3dBody = nullptr; + + physicsObjects.erase(eid); + } + + void SHPhysicsObjectManager::addRigidBody(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to add body!") + + if (!componentGroup.rigidBodyComponent) + { + SHLOG_ERROR("Entity {} is missing a Rigidbody Component. Unable to update physics object!", componentGroup.eid) + return; + } + + // A static rigid body is implicitly created on creation of a physics object. + // We only need to sync rigid bodies here in the event it is non-static. + + physicsObject->SyncRigidBody(*componentGroup.rigidBodyComponent); + } + + void SHPhysicsObjectManager::addCollider(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to add collider!") + + if (!componentGroup.colliderComponent) + { + SHLOG_ERROR("Entity {} is missing a Rigidbody Component. Unable to update physics object!", componentGroup.eid) + return; + } + + const int NUM_SHAPES = static_cast(componentGroup.colliderComponent->GetCollisionShapes().size()); + for (int i = 0; i < NUM_SHAPES; ++i) + physicsObject->AddCollisionShape(i); + + physicsObject->SyncColliders(*componentGroup.colliderComponent); + } + + void SHPhysicsObjectManager::removeRigidBody(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to remove body!") + + if (componentGroup.colliderComponent) + physicsObject->SetStaticBody(); + } + + void SHPhysicsObjectManager::removeCollider(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to remove collider!") + + physicsObject->RemoveAllCollisionShapes(); + } + + void SHPhysicsObjectManager::addCollisionShape(const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to add collision shape!") + + physicsObject->AddCollisionShape(command.shapeIndex); + } + + void SHPhysicsObjectManager::removeCollisionShape(const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) + { + SHASSERT(physicsObject != nullptr, "Valid physics object required to remove collision shape!") + + physicsObject->RemoveCollisionShape(command.shapeIndex); + } + + void SHPhysicsObjectManager::wakeAllObjects() noexcept + { + for (auto& physicsObject : physicsObjects | std::views::values) + physicsObject.GetRigidBody()->setIsSleeping(false); + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h new file mode 100644 index 00000000..641fd9df --- /dev/null +++ b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h @@ -0,0 +1,181 @@ +/**************************************************************************************** + * \file SHPhysicsObjectManager.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics Object Manager. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include + +#include + +// Project Headers +#include "SHPhysicsObject.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsObjectManager + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsSystem; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using PhysicsObjectEntityMap = std::unordered_map; + + public: + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class PhysicsComponents + { + RIGID_BODY + , COLLIDER + , COLLISION_SHAPE + + , TOTAL + , INVALID = -1 + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager () = default; + ~SHPhysicsObjectManager () = default; + + SHPhysicsObjectManager (const SHPhysicsObjectManager&) = delete; + SHPhysicsObjectManager (SHPhysicsObjectManager&&) = delete; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager& operator=(const SHPhysicsObjectManager&) = delete; + SHPhysicsObjectManager& operator=(SHPhysicsObjectManager&&) = delete; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHPhysicsObject* GetPhysicsObject (EntityID eid) noexcept; + [[nodiscard]] const PhysicsObjectEntityMap GetPhysicsObjects () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetFactory (rp3d::PhysicsCommon& physicsFactory) noexcept; + void SetWorld (rp3d::PhysicsWorld* physicsWorld) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void AddRigidBody (EntityID eid) noexcept; + void AddCollider (EntityID eid) noexcept; + void AddCollisionShape (EntityID eid, int shapeIndex) noexcept; + + void RemoveRigidBody (EntityID eid) noexcept; + void RemoveCollider (EntityID eid) noexcept; + void RemoveCollisionShape (EntityID eid, int shapeIndex) noexcept; + + void UpdateCommands (); + void RemoveAllObjects (); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct QueueCommand + { + /*-------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------*/ + + enum class Command + { + ADD + , REMOVE + + , INVALID = -1 + }; + + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + + EntityID eid = MAX_EID; + Command command = Command::INVALID; + PhysicsComponents component = PhysicsComponents::INVALID; + int shapeIndex = -1; // Only used when adding & removing collision shapes + }; + + struct PhysicsComponentGroup + { + public: + + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + + + EntityID eid = MAX_EID; + SHRigidBodyComponent* rigidBodyComponent = nullptr; + SHColliderComponent* colliderComponent = nullptr; + }; + + using CommandFunctionPtr = void(*)(const QueueCommand&, SHPhysicsObject*, const PhysicsComponentGroup&); + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static CommandFunctionPtr componentFunc[2][3]; // 2 commands, 3 components + + rp3d::PhysicsCommon* factory = nullptr; + rp3d::PhysicsWorld* world = nullptr; + + PhysicsObjectEntityMap physicsObjects; + std::queue commandQueue; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsObject* createPhysicsObject (EntityID eid) noexcept; + void destroyPhysicsObject (EntityID eid) noexcept; + + void wakeAllObjects () noexcept; + + static void addRigidBody (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + static void addCollider (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + static void removeRigidBody (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + static void removeCollider (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + + static void addCollisionShape (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + static void removeCollisionShape (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); + + + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsEvents.h b/SHADE_Engine/src/Physics/SHPhysicsEvents.h new file mode 100644 index 00000000..ae48a75b --- /dev/null +++ b/SHADE_Engine/src/Physics/SHPhysicsEvents.h @@ -0,0 +1,37 @@ +/**************************************************************************************** + * \file SHPhysicsUtils.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for some Physics Utilities + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Interface/SHCollisionShape.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SHPhysicsColliderAddedEvent + { + EntityID entityID; + SHCollisionShape::Type colliderType; + int colliderIndex; + }; + + struct SHPhysicsColliderRemovedEvent + { + EntityID entityID; + int colliderIndex; + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp new file mode 100644 index 00000000..85e76702 --- /dev/null +++ b/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp @@ -0,0 +1,71 @@ +/**************************************************************************************** + * \file SHPhysicsWorld.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics World. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsWorld.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsWorldState::SHPhysicsWorldState() noexcept + : world { nullptr } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Members Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsWorldState::CreateWorld(rp3d::PhysicsCommon& factory) + { + rp3d::PhysicsWorld::WorldSettings rp3dWorldSettings; + rp3dWorldSettings.gravity = settings.gravity; + rp3dWorldSettings.defaultVelocitySolverNbIterations = settings.numVelocitySolverIterations; + rp3dWorldSettings.defaultPositionSolverNbIterations = settings.numPositionSolverIterations; + rp3dWorldSettings.isSleepingEnabled = settings.sleepingEnabled; + + // These are my preferred default values. QoL for modifying these. + rp3dWorldSettings.defaultBounciness = 0.0f; + rp3dWorldSettings.defaultFrictionCoefficient = 0.4f; + + world = factory.createPhysicsWorld(rp3dWorldSettings); + world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::SPLIT_IMPULSES); + } + + void SHPhysicsWorldState::DestroyWorld(rp3d::PhysicsCommon& factory) + { + if (!world) + return; + + factory.destroyPhysicsWorld(world); + world = nullptr; + } + + void SHPhysicsWorldState::UpdateSettings() const noexcept + { + if (!world) + { + SHLOGV_ERROR("Unable to update Physics World settings without creating a world!") + return; + } + + world->setGravity(settings.gravity); + world->setNbIterationsVelocitySolver(settings.numVelocitySolverIterations); + world->setNbIterationsPositionSolver(settings.numPositionSolverIterations); + world->enableSleeping(settings.sleepingEnabled); + } + + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/SHPhysicsWorld.h new file mode 100644 index 00000000..091ae062 --- /dev/null +++ b/SHADE_Engine/src/Physics/SHPhysicsWorld.h @@ -0,0 +1,74 @@ +/**************************************************************************************** + * \file SHPhysicsWorld.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics World. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "Math/SHMath.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHPhysicsWorldState + { + public: + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct WorldSettings + { + public: + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + + SHVec3 gravity = SHVec3{ 0.0f, -9.81f, 0.0f }; + uint16_t numVelocitySolverIterations = 15; + uint16_t numPositionSolverIterations = 8; + bool sleepingEnabled = true; + }; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + rp3d::PhysicsWorld* world; + WorldSettings settings; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsWorldState() noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void CreateWorld (rp3d::PhysicsCommon& factory); + void DestroyWorld (rp3d::PhysicsCommon& factory); + + /** + * @brief Applies the current settings to the physics world. The world must be created + * before this is called. + */ + void UpdateSettings () const noexcept; + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp new file mode 100644 index 00000000..44875289 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -0,0 +1,233 @@ +/**************************************************************************************** + * \file SHPhysicsDebugDrawSystem.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics Debug Draw System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsDebugDrawSystem.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" +#include "Scene/SHSceneManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHPhysicsDebugDrawSystem::DebugDrawFunction SHPhysicsDebugDrawSystem::drawFunctions[SHPhysicsDebugDrawSystem::NUM_FLAGS] = + { + SHPhysicsDebugDrawSystem::drawColliders + , SHPhysicsDebugDrawSystem::drawColliderAABBs + , SHPhysicsDebugDrawSystem::drawBroadPhaseAABBs + , SHPhysicsDebugDrawSystem::drawContactPoints + , SHPhysicsDebugDrawSystem::drawContactNormals + }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsDebugDrawSystem::SHPhysicsDebugDrawSystem() noexcept + : debugDrawFlags { 0 } + , physicsSystem { nullptr } + , rp3dDebugRenderer { nullptr } + { + debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::COLLIDER)] = + debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::COLLIDER_AABB)] = SHColour::YELLOW; + debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::BROAD_PHASE_AABB)] = SHColour::CYAN; + debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::CONTACT_POINTS)] = SHColour::RED; + debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::CONTACT_NORMALS)] = SHColour::RED; + } + + SHPhysicsDebugDrawSystem::PhysicsDebugDrawRoutine::PhysicsDebugDrawRoutine() + : SHSystemRoutine { "Physics Debug Draw", true } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHPhysicsDebugDrawSystem::GetDebugDrawFlag(DebugDrawFlags flag) const noexcept + { + const auto INT_FLAG = SHUtilities::ConvertEnum(flag); + if (INT_FLAG < 0 || INT_FLAG >= NUM_FLAGS) + { + SHLOG_ERROR("Invalid Debug Draw Flag Passed {} in. Unable to get debug draw state!", INT_FLAG) + return false; + } + + return debugDrawFlags & 1U << SHUtilities::ConvertEnum(flag); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsDebugDrawSystem::SetDebugDrawFlag(DebugDrawFlags flag, bool value) noexcept + { + const auto INT_FLAG = SHUtilities::ConvertEnum(flag); + if (INT_FLAG < 0 || INT_FLAG >= NUM_FLAGS) + { + SHLOG_ERROR("Invalid Debug Draw Flag Passed {} in. Unable to set debug draw state!", INT_FLAG) + return; + } + + value ? (debugDrawFlags |= 1U << INT_FLAG) : (debugDrawFlags &= ~(1U << INT_FLAG)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsDebugDrawSystem::Init() + { + SystemFamily::GetID(); + + SHASSERT(physicsSystem == nullptr, "Non-existent physics system attached to the physics debug draw system!") + physicsSystem = SHSystemManager::GetSystem(); + } + + void SHPhysicsDebugDrawSystem::Exit() + { + physicsSystem = nullptr; + } + + void SHPhysicsDebugDrawSystem::PhysicsDebugDrawRoutine::Execute(double) noexcept + { + auto* system = reinterpret_cast(GetSystem()); + + for (int i = 0; i < SHUtilities::ConvertEnum(DebugDrawFlags::NUM_FLAGS); ++i) + { + const bool DRAW = (system->debugDrawFlags & (1U << i)) > 0; + if (DRAW) + drawFunctions[i](system->rp3dDebugRenderer); + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsDebugDrawSystem::drawColliders(rp3d::DebugRenderer* debugRenderer) noexcept + { + const auto& COLLIDER_SET = SHComponentManager::GetDense(); + for (const auto& COLLIDER : COLLIDER_SET) + { + // Skip inactive colliders + if (!SHSceneManager::CheckNodeAndComponentsActive(COLLIDER.GetEID())) + continue; + + for (auto& collisionShape : COLLIDER.GetCollisionShapes()) + { + switch (collisionShape.GetType()) + { + case SHCollisionShape::Type::BOX: debugDrawBox(COLLIDER, collisionShape); break; + case SHCollisionShape::Type::SPHERE: debugDrawSphere(COLLIDER, collisionShape); break; + default: break; + } + } + } + } + + void SHPhysicsDebugDrawSystem::drawColliderAABBs(rp3d::DebugRenderer* debugRenderer) noexcept + { + + } + + void SHPhysicsDebugDrawSystem::drawBroadPhaseAABBs(rp3d::DebugRenderer* debugRenderer) noexcept + { + + } + + void SHPhysicsDebugDrawSystem::drawContactPoints(rp3d::DebugRenderer* debugRenderer) noexcept + { + + } + + void SHPhysicsDebugDrawSystem::drawContactNormals(rp3d::DebugRenderer* debugRenderer) noexcept + { + + } + + void SHPhysicsDebugDrawSystem::debugDrawBox(const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept + { + static constexpr uint32_t NUM_BOX_VERTICES = 8; + static const SHVec3 boxVertices[NUM_BOX_VERTICES] + { + { 0.5f, 0.5f, -0.5f } // TOP_RIGHT_BACK + , { -0.5f, 0.5f, -0.5f } // TOP_LEFT_BACK + , { 0.5f, -0.5f, -0.5f } // BTM_RIGHT_BACK + , { -0.5f, -0.5f, -0.5f } // BTM_LEFT_BACK + , { 0.5f, 0.5f, 0.5f } // TOP_RIGHT_FRONT + , { -0.5f, 0.5f, 0.5f } // TOP_LEFT_FRONT + , { 0.5f, -0.5f, 0.5f } // BTM_RIGHT_FRONT + , { -0.5f, -0.5f, 0.5f } // BTM_LEFT_FRONT + }; + + auto* debugDrawSystem = SHSystemManager::GetSystem(); + if (debugDrawSystem == nullptr) + { + SHLOG_ERROR("Unable to get a debug draw system for Physics Debug Drawing!") + return; + } + + auto* BOX = reinterpret_cast(collisionShape.GetShape()); + + // Calculate final position & orientation + const SHVec3 FINAL_POS = colliderComponent.GetPosition() + collisionShape.GetPositionOffset(); + const SHQuaternion FINAL_ROT = colliderComponent.GetOrientation() * SHQuaternion::FromEuler(collisionShape.GetRotationOffset()); + + const SHMatrix BOX_TRS = SHMatrix::Scale(BOX->GetWorldExtents() * 2.0f) * SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(FINAL_POS); + + const SHColour COLLIDER_COLOUR = collisionShape.IsTrigger() ? SHColour::PURPLE : SHColour::GREEN; + + std::array transformedVertices; + for (uint32_t i = 0; i < NUM_BOX_VERTICES / 2; ++i) + { + const uint32_t IDX1 = i; + const uint32_t IDX2 = i + NUM_BOX_VERTICES / 2; + + transformedVertices[IDX1] = SHVec3::Transform(boxVertices[IDX1], BOX_TRS); + transformedVertices[IDX2] = SHVec3::Transform(boxVertices[IDX2], BOX_TRS); + + // Draw 4 line to connect the quads + debugDrawSystem->DrawLine(COLLIDER_COLOUR, transformedVertices[IDX1], transformedVertices[IDX2]); + } + + // A, B, C, D + std::array backQuad { transformedVertices[0], transformedVertices[1], transformedVertices[3], transformedVertices[2] }; + debugDrawSystem->DrawPoly(COLLIDER_COLOUR, backQuad.begin(), backQuad.end()); + // E, F, G, H + std::array frontQuad { transformedVertices[4], transformedVertices[5], transformedVertices[7], transformedVertices[6] }; + debugDrawSystem->DrawPoly(COLLIDER_COLOUR, frontQuad.begin(), frontQuad.end()); + } + + void SHPhysicsDebugDrawSystem::debugDrawSphere(const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept + { + auto* debugDrawSystem = SHSystemManager::GetSystem(); + if (debugDrawSystem == nullptr) + { + SHLOG_ERROR("Unable to get a debug draw system for Physics Debug Drawing!") + return; + } + + auto* SPHERE = reinterpret_cast(collisionShape.GetShape()); + + const SHColour COLLIDER_COLOUR = collisionShape.IsTrigger() ? SHColour::PURPLE : SHColour::GREEN; + + // Calculate final position & orientation + const SHVec3 FINAL_POS = colliderComponent.GetPosition() + collisionShape.GetPositionOffset(); + debugDrawSystem->DrawSphere(COLLIDER_COLOUR, FINAL_POS, SPHERE->GetWorldRadius()); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h new file mode 100644 index 00000000..53037ab2 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h @@ -0,0 +1,123 @@ +/**************************************************************************************** + * \file SHPhysicsDebugDrawSystem.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the Physics Debug Draw System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Math/SHColour.h" +#include "SHPhysicsSystem.h" +#include "Tools/SHUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsDebugDrawSystem final : public SHSystem + { + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + enum class DebugDrawFlags + { + COLLIDER + , COLLIDER_AABB + , BROAD_PHASE_AABB + , CONTACT_POINTS + , CONTACT_NORMALS + + , NUM_FLAGS + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsDebugDrawSystem() noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + bool GetDebugDrawFlag(DebugDrawFlags flag) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetDebugDrawFlag(DebugDrawFlags flag, bool value) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void Init() override; + void Exit() override; + + /*---------------------------------------------------------------------------------*/ + /* System Routines */ + /*---------------------------------------------------------------------------------*/ + + class SH_API PhysicsDebugDrawRoutine final : public SHSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + PhysicsDebugDrawRoutine(); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + void Execute(double dt) noexcept override; + }; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using DebugDrawFunction = void(*)(rp3d::DebugRenderer*) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr int NUM_FLAGS = SHUtilities::ConvertEnum(DebugDrawFlags::NUM_FLAGS); + + static const DebugDrawFunction drawFunctions[NUM_FLAGS]; + + uint8_t debugDrawFlags; + SHPhysicsSystem* physicsSystem; + rp3d::DebugRenderer* rp3dDebugRenderer; + SHColour debugColours[NUM_FLAGS]; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + static void drawColliders (rp3d::DebugRenderer* debugRenderer) noexcept; + static void drawColliderAABBs (rp3d::DebugRenderer* debugRenderer) noexcept; + static void drawBroadPhaseAABBs (rp3d::DebugRenderer* debugRenderer) noexcept; + static void drawContactPoints (rp3d::DebugRenderer* debugRenderer) noexcept; + static void drawContactNormals (rp3d::DebugRenderer* debugRenderer) noexcept; + + static void debugDrawBox (const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept; + static void debugDrawSphere (const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept; + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp new file mode 100644 index 00000000..33ba88e7 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -0,0 +1,429 @@ +/**************************************************************************************** + * \file SHPhysicsSystem.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsSystem.h" + +// Project Headers +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/SHEditor.h" +#include "Physics/SHPhysicsEvents.h" +#include "Scene/SHSceneManager.h" +#include "Scripting/SHScriptEngine.h" + +/*-------------------------------------------------------------------------------------*/ +/* Local Helper Functions */ +/*-------------------------------------------------------------------------------------*/ + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsSystem::SHPhysicsSystem() + : worldUpdated { false } + , interpolationFactor { 0.0 } + , fixedDT { DEFAULT_FIXED_STEP } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + double SHPhysicsSystem::GetFixedUpdateRate() const noexcept + { + return 1.0 / fixedDT; + } + + const SHPhysicsWorldState::WorldSettings& SHPhysicsSystem::GetWorldSettings() const noexcept + { + return worldState.settings; + } + + const std::vector& SHPhysicsSystem::GetAllCollisionInfo() const noexcept + { + return collisionListener.GetCollisionInfoContainer(); + } + + const std::vector& SHPhysicsSystem::GetAllTriggerInfo() const noexcept + { + return collisionListener.GetTriggerInfoContainer(); + } + + const SHPhysicsObject* SHPhysicsSystem::GetPhysicsObject(EntityID eid) noexcept + { + return objectManager.GetPhysicsObject(eid); + } + + + const SHPhysicsObjectManager::PhysicsObjectEntityMap& SHPhysicsSystem::GetPhysicsObjects() const noexcept + { + return objectManager.physicsObjects; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::SetFixedUpdateRate(double fixedUpdateRate) noexcept + { + fixedDT = 1.0 / fixedUpdateRate; + } + + void SHPhysicsSystem::SetWorldSettings(const SHPhysicsWorldState::WorldSettings& settings) noexcept + { + worldState.settings = settings; + worldState.UpdateSettings(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::Init() + { + // Subscribe to component events + const std::shared_ptr ADD_COMPONENT_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::addPhysicsComponent) }; + const ReceiverPtr ADD_COMPONENT_RECEIVER_PTR = std::dynamic_pointer_cast(ADD_COMPONENT_RECEIVER); + SHEventManager::SubscribeTo(SH_COMPONENT_ADDED_EVENT, ADD_COMPONENT_RECEIVER_PTR); + + const std::shared_ptr REMOVE_COMPONENT_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::removePhysicsComponent) }; + const ReceiverPtr REMOVE_COMPONENT_RECEIVER_PTR = std::dynamic_pointer_cast(REMOVE_COMPONENT_RECEIVER); + SHEventManager::SubscribeTo(SH_COMPONENT_REMOVED_EVENT, REMOVE_COMPONENT_RECEIVER_PTR); + + #ifdef SHEDITOR + + // Subscribe to Editor State Change Events + const std::shared_ptr ON_PLAY_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::onPlay) }; + const ReceiverPtr ON_PLAY_RECEIVER_PTR = std::dynamic_pointer_cast(ON_PLAY_RECEIVER); + SHEventManager::SubscribeTo(SH_EDITOR_ON_PLAY_EVENT, ON_PLAY_RECEIVER_PTR); + + const std::shared_ptr ON_STOP_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::onStop) }; + const ReceiverPtr ON_STOP_RECEIVER_PTR = std::dynamic_pointer_cast(ON_STOP_RECEIVER); + SHEventManager::SubscribeTo(SH_EDITOR_ON_STOP_EVENT, ON_STOP_RECEIVER_PTR); + + #endif + // Link Physics Object Manager with System + objectManager.SetFactory(factory); + + // Link Collision Listener with System + collisionListener.BindToSystem(this); + } + + void SHPhysicsSystem::Exit() + { + worldState.DestroyWorld(factory); + } + + void SHPhysicsSystem::ForceUpdate() + { + if (!worldState.world) + { + SHLOGV_ERROR("Unable to force update without a Physics world!") + return; + } + + auto* scriptingSystem = SHSystemManager::GetSystem(); + if (scriptingSystem == nullptr) + { + SHLOGV_ERROR("Unable to invoke FixedUpdate() on scripts due to missing SHScriptEngine!"); + } + + // Force the physics world to update once + if (scriptingSystem != nullptr) + scriptingSystem->ExecuteFixedUpdates(); + + worldState.world->update(static_cast(fixedDT)); + + // Sync transforms. No interpolation applied here + for (auto& [entityID, physicsObject] : objectManager.physicsObjects) + { + auto* transformComponent = SHComponentManager::GetComponent_s(entityID); + auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); + auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); + + postUpdateSyncTransforms + ( + physicsObject + , transformComponent + , rigidBodyComponent + , colliderComponent + , 1.0 // We use 1.0 here to avoid any interpolation + ); + } + } + + + void SHPhysicsSystem::AddCollisionShape(EntityID eid, int shapeIndex) + { + static const auto ADD_SHAPE = [&](EntityID entityID, int index) + { + objectManager.AddCollisionShape(entityID, index); + + const SHPhysicsColliderAddedEvent COLLIDER_ADDED_EVENT_DATA + { + .entityID = entityID + , .colliderType = SHComponentManager::GetComponent(entityID)->GetCollisionShape(index).GetType() + , .colliderIndex = index + }; + + SHEventManager::BroadcastEvent(COLLIDER_ADDED_EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); + }; + + #ifdef SHEDITOR + + const auto* EDITOR = SHSystemManager::GetSystem(); + if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) + ADD_SHAPE(eid, shapeIndex); + + #else + + ADD_SHAPE(eid, shapeIndex); + + #endif + } + + void SHPhysicsSystem::RemoveCollisionShape(EntityID eid, int shapeIndex) + { + static const auto REMOVE_SHAPE = [&](EntityID entityID, int index) + { + objectManager.RemoveCollisionShape(entityID, index); + + const SHPhysicsColliderRemovedEvent COLLIDER_REMOVED_EVENT_DATA + { + .entityID = entityID + , .colliderIndex = index + }; + + SHEventManager::BroadcastEvent(COLLIDER_REMOVED_EVENT_DATA, SH_PHYSICS_COLLIDER_REMOVED_EVENT); + }; + + #ifdef SHEDITOR + + const auto* EDITOR = SHSystemManager::GetSystem(); + if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) + REMOVE_SHAPE(eid, shapeIndex); + + #else + + REMOVE_SHAPE(eid, shapeIndex); + + #endif + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHEventHandle SHPhysicsSystem::addPhysicsComponent(SHEventPtr addComponentEvent) noexcept + { + const auto& EVENT_DATA = reinterpret_cast*>(addComponentEvent.get()); + + static const auto RIGID_BODY_ID = ComponentFamily::GetID(); + static const auto COLLIDER_ID = ComponentFamily::GetID(); + + const auto ADDED_ID = EVENT_DATA->data->addedComponentType; + const bool IS_PHYSICS_COMPONENT = ADDED_ID == RIGID_BODY_ID || ADDED_ID == COLLIDER_ID; + if (IS_PHYSICS_COMPONENT) + { + const EntityID EID = EVENT_DATA->data->eid; + + // We only add tell the physics object manager to add a component if the scene is played + + #ifdef SHEDITOR + + const auto* EDITOR = SHSystemManager::GetSystem(); + + if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) + ADDED_ID == RIGID_BODY_ID ? objectManager.AddRigidBody(EID) : objectManager.AddCollider(EID); + + #else + + ADDED_ID == RIGID_BODY_ID ? objectManager.AddRigidBody(EID) : objectManager.AddCollider(EID); + + #endif + } + + return EVENT_DATA->handle; + } + + SHEventHandle SHPhysicsSystem::removePhysicsComponent(SHEventPtr removeComponentEvent) noexcept + { + const auto& EVENT_DATA = reinterpret_cast*>(removeComponentEvent.get()); + + static const auto RIGID_BODY_ID = ComponentFamily::GetID(); + static const auto COLLIDER_ID = ComponentFamily::GetID(); + + const auto REMOVED_ID = EVENT_DATA->data->removedComponentType; + const bool IS_PHYSICS_COMPONENT = REMOVED_ID == RIGID_BODY_ID || REMOVED_ID == COLLIDER_ID; + if (IS_PHYSICS_COMPONENT) + { + const EntityID EID = EVENT_DATA->data->eid; + + // We only add tell the physics object manager to remove a component if the scene is played + + #ifdef SHEDITOR + + const auto* EDITOR = SHSystemManager::GetSystem(); + + if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) + REMOVED_ID == RIGID_BODY_ID ? objectManager.RemoveRigidBody(EID) : objectManager.RemoveCollider(EID); + + #else + + REMOVED_ID == RIGID_BODY_ID ? objectManager.RemoveRigidBody(EID) : objectManager.RemoveCollider(EID); + + #endif + } + + return EVENT_DATA->handle; + } + + SHEventHandle SHPhysicsSystem::onPlay(SHEventPtr onPlayEvent) + { + static const auto BUILD_PHYSICS_OBJECT = [&](SHSceneNode* node) + { + const EntityID EID = node->GetEntityID(); + + if (SHComponentManager::HasComponent(EID)) + objectManager.AddRigidBody(EID); + + if (SHComponentManager::HasComponent(EID)) + objectManager.AddCollider(EID); + }; + + //////////////////////////////// + + // Create physics world + worldState.CreateWorld(factory); + + // Link Collision Listener + collisionListener.BindToWorld(worldState.world); + + // Link with object manager & create all physics objects + objectManager.SetWorld(worldState.world); + + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + SCENE_GRAPH.Traverse(BUILD_PHYSICS_OBJECT); + + return onPlayEvent->handle; + } + + SHEventHandle SHPhysicsSystem::onStop(SHEventPtr onStopEvent) + { + // Remove all physics objects + objectManager.RemoveAllObjects(); + objectManager.SetWorld(nullptr); + + // Clear all collision info + // Collision listener is automatically unbound when world is destroyed + collisionListener.ClearContainers(); + + // Destroy the world + worldState.DestroyWorld(factory); + + return onStopEvent->handle; + } + + void SHPhysicsSystem::preUpdateSyncTransform + ( + SHPhysicsObject& physicsObject + , SHTransformComponent* transformComponent + , SHRigidBodyComponent* rigidBodyComponent + , SHColliderComponent* colliderComponent + ) noexcept + { + if (!transformComponent) + return; + + const SHVec3& WORLD_POS = transformComponent->GetWorldPosition(); + const SHQuaternion& WORLD_ROT = transformComponent->GetWorldOrientation(); + const SHVec3& WORLD_SCL = transformComponent->GetWorldScale(); + + const rp3d::Transform RP3D_TRANSFORM { WORLD_POS, WORLD_ROT }; + physicsObject.GetRigidBody()->setTransform(RP3D_TRANSFORM); + + if (rigidBodyComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) + { + rigidBodyComponent->position = WORLD_POS; + rigidBodyComponent->orientation = WORLD_ROT; + } + + if (colliderComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) + { + colliderComponent->position = WORLD_POS; + colliderComponent->orientation = WORLD_ROT; + colliderComponent->scale = WORLD_SCL; + + colliderComponent->RecomputeCollisionShapes(); + } + } + + void SHPhysicsSystem::postUpdateSyncTransforms + ( + SHPhysicsObject& physicsObject + , SHTransformComponent* transformComponent + , SHRigidBodyComponent* rigidBodyComponent + , SHColliderComponent* colliderComponent + , double interpolationFactor + ) noexcept + { + const rp3d::Transform& CURRENT_TF = physicsObject.GetRigidBody()->getTransform(); + auto renderPos = CURRENT_TF.getPosition(); + auto renderRot = CURRENT_TF.getOrientation(); + + // Cache transforms + if (physicsObject.GetRigidBody()->isActive()) + physicsObject.prevTransform = CURRENT_TF; + + // Sync with rigid bodies + if (rigidBodyComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) + { + // Skip static bodies + if (rigidBodyComponent->GetType() == SHRigidBodyComponent::Type::STATIC) + return; + + // Check if transform should be interpolated + if (rigidBodyComponent->IsInterpolating()) + { + // Interpolate transforms between current and predicted next transform + + const rp3d::Transform PREV_TF = physicsObject.prevTransform; + const rp3d::Transform INTERPOLATED_TF = rp3d::Transform::interpolateTransforms(PREV_TF, CURRENT_TF, static_cast(interpolationFactor)); + + renderPos = INTERPOLATED_TF.getPosition(); + renderRot = INTERPOLATED_TF.getOrientation(); + } + + rigidBodyComponent->position = CURRENT_TF.getPosition(); + rigidBodyComponent->orientation = CURRENT_TF.getOrientation(); + } + + // Sync with colliders + if (colliderComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) + { + colliderComponent->position = CURRENT_TF.getPosition(); + colliderComponent->orientation = CURRENT_TF.getOrientation(); + } + + // Set transform for rendering + if (transformComponent) + { + transformComponent->SetWorldPosition(renderPos); + transformComponent->SetWorldOrientation(renderRot); + } + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h new file mode 100644 index 00000000..3da7094b --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -0,0 +1,201 @@ +/**************************************************************************************** + * \file SHPhysicsSystem.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the Physics System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include + +// External Dependencies +#include + +// Project Headers +#include "ECS_Base/System/SHSystemRoutine.h" +#include "ECS_Base/System/SHFixedSystemRoutine.h" + +#include "Math/Transform/SHTransformComponent.h" + +#include "Physics/Collision/SHCollisionListener.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Physics/PhysicsObject//SHPhysicsObjectManager.h" +#include "Physics/SHPhysicsWorld.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsSystem final : public SHSystem + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsDebugDrawSystem; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsSystem(); + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] double GetFixedUpdateRate () const noexcept; + [[nodiscard]] const SHPhysicsWorldState::WorldSettings& GetWorldSettings () const noexcept; + + [[nodiscard]] const std::vector& GetAllCollisionInfo () const noexcept; + [[nodiscard]] const std::vector& GetAllTriggerInfo () const noexcept; + + [[nodiscard]] const SHPhysicsObject* GetPhysicsObject (EntityID eid) noexcept; + [[nodiscard]] const SHPhysicsObjectManager::PhysicsObjectEntityMap& GetPhysicsObjects () const noexcept; + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetFixedUpdateRate (double fixedUpdateRate) noexcept; + void SetWorldSettings (const SHPhysicsWorldState::WorldSettings& settings) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void Init () override; + void Exit () override; + + void ForceUpdate (); + + // Specific Handling for Collision Shapes as they are not under the Component System. + // This is done as events need to be sent out. + // TODO(Diren): Consider using a static method through the ColliderComponent. + + void AddCollisionShape (EntityID eid, int shapeIndex); + void RemoveCollisionShape (EntityID eid, int shapeIndex); + + /*---------------------------------------------------------------------------------*/ + /* System Routines */ + /*---------------------------------------------------------------------------------*/ + + class SH_API PhysicsPreUpdate final : public SHSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + PhysicsPreUpdate(); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void Execute(double dt) noexcept override; + + private: + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void syncRigidBodyActive (EntityID eid, SHPhysicsObject& physicsObject) const noexcept; + void syncColliderActive (EntityID eid, SHPhysicsObject& physicsObject) const noexcept; + static void syncOnPlay (EntityID eid, SHPhysicsObject& physicsObject) noexcept; + }; + + class SH_API PhysicsFixedUpdate final : public SHFixedSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + PhysicsFixedUpdate(); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void Execute (double dt) noexcept override; + }; + + class SH_API PhysicsPostUpdate final : public SHSystemRoutine + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + PhysicsPostUpdate(); + + /*-------------------------------------------------------------------------------*/ + /* Function Members */ + /*-------------------------------------------------------------------------------*/ + + void Execute(double dt) noexcept override; + }; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + // System data + + bool worldUpdated; + double interpolationFactor; + double fixedDT; + + // rp3d + + rp3d::PhysicsCommon factory; + + // Interface objects + + SHPhysicsWorldState worldState; + SHPhysicsObjectManager objectManager; + SHCollisionListener collisionListener; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + SHEventHandle addPhysicsComponent (SHEventPtr addComponentEvent) noexcept; + SHEventHandle removePhysicsComponent (SHEventPtr removeComponentEvent) noexcept; + + SHEventHandle onPlay (SHEventPtr onPlayEvent); + SHEventHandle onStop (SHEventPtr onStopEvent); + + + + static void preUpdateSyncTransform + ( + SHPhysicsObject& physicsObject + , SHTransformComponent* transformComponent + , SHRigidBodyComponent* rigidBodyComponent + , SHColliderComponent* colliderComponent + ) noexcept; + + static void postUpdateSyncTransforms + ( + SHPhysicsObject& physicsObject + , SHTransformComponent* transformComponent + , SHRigidBodyComponent* rigidBodyComponent + , SHColliderComponent* colliderComponent + , double interpolationFactor + ) noexcept; + + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp new file mode 100644 index 00000000..b142d54c --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp @@ -0,0 +1,64 @@ +/************************************************************************************//*! +\file SHPhysicsSystemInterface.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definitions of the functions of the static + SHPhysicsSystemInterface class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "SHPhysicsSystemInterface.h" +// Project Includes +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Physics/System/SHPhysicsSystem.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*-----------------------------------------------------------------------------------*/ + const std::vector& SHPhysicsSystemInterface::GetCollisionInfo() noexcept + { + static std::vector emptyVec; + + auto phySystem = SHSystemManager::GetSystem(); + if (phySystem) + { + return phySystem->GetAllCollisionInfo(); + } + + SHLOG_WARNING("[SHPhysicsSystemInterface] Failed to get collision events. Empty vector returned instead."); + return emptyVec; + } + const std::vector& SHPhysicsSystemInterface::GetTriggerInfo() noexcept + { + static std::vector emptyVec; + + auto phySystem = SHSystemManager::GetSystem(); + if (phySystem) + { + return phySystem->GetAllTriggerInfo(); + } + + SHLOG_WARNING("[SHPhysicsSystemInterface] Failed to get trigger events. Empty vector returned instead."); + return emptyVec; + } + + double SHPhysicsSystemInterface::GetFixedDT() noexcept + { + auto phySystem = SHSystemManager::GetSystem(); + if (phySystem) + { + return phySystem->GetFixedUpdateRate(); + } + + SHLOG_WARNING("[SHPhysicsSystemInterface] Failed to get fixed delta time. 0.0 returned instead."); + return 0.0; + } +} diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h new file mode 100644 index 00000000..bdd04686 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h @@ -0,0 +1,46 @@ +/************************************************************************************//*! +\file SHPhysicsSystemInterface.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definition of the SHGraphicsSystemInterface static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHCollisionInfo; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /// + /// Static class that wraps up certain functions in the SHPhysicsSystem so that + /// accessing it from SHADE_Managed would not cause issues due to C++20 features. + /// + class SH_API SHPhysicsSystemInterface final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructor */ + /*---------------------------------------------------------------------------------*/ + SHPhysicsSystemInterface() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*---------------------------------------------------------------------------------*/ + [[nodiscard]] static const std::vector& GetCollisionInfo() noexcept; + [[nodiscard]] static const std::vector& GetTriggerInfo() noexcept; + [[nodiscard]] static double GetFixedDT() noexcept; + }; +} diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp new file mode 100644 index 00000000..3e56ca14 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp @@ -0,0 +1,265 @@ +/**************************************************************************************** + * \file SHPhysicsSystemRoutines.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics System Routines + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsSystem.h" +// Project Headers +#include "ECS_Base/Managers/SHEntityManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/SHEditor.h" +#include "Scene/SHSceneManager.h" +#include "Scripting/SHScriptEngine.h" + +#include "Input/SHInputManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsSystem::PhysicsPreUpdate::PhysicsPreUpdate() + : SHSystemRoutine { "Physics PreUpdate", true } + {} + + SHPhysicsSystem::PhysicsFixedUpdate::PhysicsFixedUpdate() + : SHFixedSystemRoutine { DEFAULT_FIXED_STEP, "Physics FixedUpdate", false } + {} + + SHPhysicsSystem::PhysicsPostUpdate::PhysicsPostUpdate() + : SHSystemRoutine { "Physics PostUpdate", false } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsPreUpdate::Execute(double) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + + #ifdef SHEDITOR + + // Only Sync on Play. + // Otherwise, Components are only holding data until the world is built on play. + const auto* EDITOR = SHSystemManager::GetSystem(); + if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) + { + physicsSystem->objectManager.UpdateCommands(); + + for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) + { + // Ensure a valid physics Object + if (physicsObject.rp3dBody == nullptr) + continue; + + // Sync active states between SHADE & RP3D + syncRigidBodyActive(entityID, physicsObject); + syncColliderActive(entityID, physicsObject); + + syncOnPlay(entityID, physicsObject); + } + } + else + { + auto& rigidBodyDense = SHComponentManager::GetDense(); + auto& colliderDense = SHComponentManager::GetDense(); + + for (auto& rigidBodyComponent : rigidBodyDense) + { + const auto* TRANSFORM = SHComponentManager::GetComponent_s(rigidBodyComponent.GetEID()); + + if (TRANSFORM && TRANSFORM->HasChanged()) + { + rigidBodyComponent.position = TRANSFORM->GetWorldPosition(); + rigidBodyComponent.orientation = TRANSFORM->GetWorldOrientation(); + } + } + + for (auto& colliderComponent : colliderDense) + { + const auto* TRANSFORM = SHComponentManager::GetComponent_s(colliderComponent.GetEID()); + + if (TRANSFORM && TRANSFORM->HasChanged()) + { + colliderComponent.position = TRANSFORM->GetWorldPosition(); + colliderComponent.orientation = TRANSFORM->GetWorldOrientation(); + colliderComponent.scale = TRANSFORM->GetWorldScale(); + + colliderComponent.RecomputeCollisionShapes(); + } + } + } + + #else + + // Always sync Rigid Body & Collider Components with Physics Objects + // Do not check for an editor here + + physicsSystem->objectManager.UpdateCommands(); + + for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) + { + // Ensure a valid physics Object + if (physicsObject.rp3dBody == nullptr) + continue; + + syncRigidBodyActive(entityID, physicsObject); + syncColliderActive(entityID, physicsObject); + + syncOnPlay(entityID, physicsObject); + } + + #endif + } + + void SHPhysicsSystem::PhysicsFixedUpdate::Execute(double dt) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + auto* scriptingSystem = SHSystemManager::GetSystem(); + if (scriptingSystem == nullptr) + { + SHLOGV_ERROR("Unable to invoke FixedUpdate() on scripts due to missing SHScriptEngine!"); + } + + const double FIXED_DT = physicsSystem->fixedDT; + accumulatedTime += dt; + + int count = 0; + while (accumulatedTime > FIXED_DT) + { + if (scriptingSystem != nullptr) + scriptingSystem->ExecuteFixedUpdates(); + + physicsSystem->worldState.world->update(static_cast(FIXED_DT)); + + accumulatedTime -= FIXED_DT; + ++count; + } + + stats.numSteps = count; + physicsSystem->worldUpdated = count > 0; + + physicsSystem->interpolationFactor = accumulatedTime / fixedTimeStep; + } + + void SHPhysicsSystem::PhysicsPostUpdate::Execute(double) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + auto* scriptingSystem = SHSystemManager::GetSystem(); + + if (scriptingSystem == nullptr) + { + SHLOGV_ERROR("Unable to invoke collision and trigger script events due to missing SHScriptEngine!"); + } + + // Interpolate transforms for rendering + if (physicsSystem->worldUpdated) + { + for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) + { + auto* transformComponent = SHComponentManager::GetComponent_s(entityID); + auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); + auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); + + postUpdateSyncTransforms + ( + physicsObject + , transformComponent + , rigidBodyComponent + , colliderComponent + , physicsSystem->interpolationFactor + ); + } + + // Collision & Trigger messages + if (scriptingSystem != nullptr) + scriptingSystem->ExecuteCollisionFunctions(); + + // Since this function never runs when editor in not in play, execute the function anyway + physicsSystem->collisionListener.CleanContainers(); + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsPreUpdate::syncRigidBodyActive(EntityID eid, SHPhysicsObject& physicsObject) const noexcept + { + if (!SHComponentManager::HasComponent(eid)) + return; + + const bool IS_ACTIVE_IN_SCENE = SHSceneManager::CheckNodeAndComponentsActive(eid); + const bool IS_RP3D_BODY_ACTIVE = physicsObject.GetRigidBody()->isActive(); + + if (IS_ACTIVE_IN_SCENE != IS_RP3D_BODY_ACTIVE) + physicsObject.GetRigidBody()->setIsActive(IS_ACTIVE_IN_SCENE); + } + + void SHPhysicsSystem::PhysicsPreUpdate::syncColliderActive(EntityID eid, SHPhysicsObject& physicsObject) const noexcept + { + const auto* COLLIDER = SHComponentManager::GetComponent_s(eid); + if (!COLLIDER) + return; + + const bool IS_ACTIVE_IN_SCENE = SHSceneManager::CheckNodeAndComponentsActive(eid); + const bool IS_RP3D_COLLIDER_ACTIVE = physicsObject.collidersActive; + + if (IS_ACTIVE_IN_SCENE != IS_RP3D_COLLIDER_ACTIVE) + { + // HACK: If active state turned off, remove all collision shapes. If turned on, add them back. + auto* physicsSystem = reinterpret_cast(GetSystem()); + + const int NUM_SHAPES = static_cast(COLLIDER->GetCollisionShapes().size()); + if (IS_ACTIVE_IN_SCENE) + { + for (int i = 0; i < NUM_SHAPES; ++i) + physicsSystem->objectManager.AddCollisionShape(eid, i); + } + else + { + for (int i = NUM_SHAPES - 1; i >= 0; --i) + physicsSystem->objectManager.RemoveCollisionShape(eid, i); + } + + physicsObject.collidersActive = IS_ACTIVE_IN_SCENE; + } + } + + void SHPhysicsSystem::PhysicsPreUpdate::syncOnPlay(EntityID eid, SHPhysicsObject& physicsObject) noexcept + { + auto* transformComponent = SHComponentManager::GetComponent_s(eid); + auto* rigidBodyComponent = SHComponentManager::GetComponent_s(eid); + auto* colliderComponent = SHComponentManager::GetComponent_s(eid); + + // Sync transforms & physics components transforms + if (transformComponent && transformComponent->HasChanged()) + { + preUpdateSyncTransform + ( + physicsObject + , transformComponent + , rigidBodyComponent + , colliderComponent + ); + } + + // Sync Rigid Bodies + if (rigidBodyComponent) + physicsObject.SyncRigidBody(*rigidBodyComponent); + + // Sync Colliders + if (colliderComponent) + physicsObject.SyncColliders(*colliderComponent); + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Reflection/SHReflectionMetadata.h b/SHADE_Engine/src/Reflection/SHReflectionMetadata.h new file mode 100644 index 00000000..b4dc009c --- /dev/null +++ b/SHADE_Engine/src/Reflection/SHReflectionMetadata.h @@ -0,0 +1,12 @@ +#pragma once +#include "string_view" +namespace SHADE +{ + namespace META + { + constexpr const char* min = "MIN"; + constexpr const char* max = "MAX"; + constexpr const char* tooltip = "tooltip"; + constexpr const char* angleInRad = "angleInRad"; + } +} diff --git a/SHADE_Engine/src/Resource/Handle.hpp b/SHADE_Engine/src/Resource/Handle.hpp deleted file mode 100644 index d7f48ba1..00000000 --- a/SHADE_Engine/src/Resource/Handle.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once -// Primary Header -#include "Handle.h" - -namespace SHADE -{ - /*---------------------------------------------------------------------------------*/ - /* HandleBase - Usage Functions */ - /*---------------------------------------------------------------------------------*/ - inline HandleBase::Id HandleBase::GetId() const - { - return id; - } - - /*---------------------------------------------------------------------------------*/ - /* HandleBase - Overloaded Operators */ - /*---------------------------------------------------------------------------------*/ - inline HandleBase::operator bool() const - { - return id.Raw != INVALID_ID.Raw; - } - - /*---------------------------------------------------------------------------------*/ - /* Handle - Usage Functions */ - /*---------------------------------------------------------------------------------*/ - template - inline void Handle::Free() - { - library->Free(*this); - } - - /*---------------------------------------------------------------------------------*/ - /* Handle - Overloaded Operators */ - /*---------------------------------------------------------------------------------*/ - template - T& Handle::operator*() - { - return library->Get(*this); - } - - template - const T& Handle::operator*() const - { - return library->Get(*this); - } - - template - T* Handle::operator->() - { - return &library->Get(*this); - } - - template - const T* Handle::operator->() const - { - return &library->Get(*this); - } - - template - inline ISelfHandle::ISelfHandle(const ISelfHandle& rhs) - : handle { rhs.handle } - {} - - template - inline ISelfHandle::ISelfHandle(ISelfHandle&& rhs) - : handle { rhs.handle } - {} - - template - inline ISelfHandle& ISelfHandle::operator=(const ISelfHandle& rhs) - { - handle = rhs.handle; - return *this; - } - - template - inline ISelfHandle& ISelfHandle::operator=(ISelfHandle&& rhs) - { - handle = rhs.handle; - return *this; - } - - /*---------------------------------------------------------------------------------*/ - /* ISelfHandle - Usage Functions */ - /*---------------------------------------------------------------------------------*/ - template - inline Handle ISelfHandle::GetHandle() const - { - return handle; - } - template - inline void ISelfHandle::SetHandle(const ResourceLibrary&, Handle hdl) - { - handle = hdl; - } -} diff --git a/SHADE_Engine/src/Resource/Handle.h b/SHADE_Engine/src/Resource/SHHandle.h similarity index 65% rename from SHADE_Engine/src/Resource/Handle.h rename to SHADE_Engine/src/Resource/SHHandle.h index 34662112..49dd56b9 100644 --- a/SHADE_Engine/src/Resource/Handle.h +++ b/SHADE_Engine/src/Resource/SHHandle.h @@ -8,9 +8,10 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Forward Declarations */ /*---------------------------------------------------------------------------------*/ + class SHResourceLibraryBase; template - class ResourceLibrary; - + class SHResourceLibrary; + /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ @@ -25,6 +26,20 @@ namespace SHADE using std::runtime_error::runtime_error; }; + /// + /// Exception thrown when a generic Handle is being casted to the wrong type. + /// + class BadHandleCastException : std::runtime_error + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + BadHandleCastException() + : std::runtime_error("Attempted to cast a generic Handle to the wrong type. ") + {} + }; + /// /// Base implementation of the Handle that is not templated to allow for holding /// generic non-type-specific Handles. @@ -54,7 +69,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /* Usage Functions */ /*-----------------------------------------------------------------------------*/ - inline Id GetId() const; + inline Id GetId() const noexcept; /*-----------------------------------------------------------------------------*/ /* Overloaded Operators */ @@ -62,7 +77,7 @@ namespace SHADE /// /// Converts to true if this is a valid Handle. /// - inline operator bool() const; + inline operator bool() const noexcept; protected: /*-----------------------------------------------------------------------------*/ @@ -80,7 +95,7 @@ namespace SHADE /// Generic implementation of a Handle object /// /// Type of the handle. - template + template class Handle : public HandleBase { public: @@ -88,8 +103,18 @@ namespace SHADE /* Constructors */ /*-----------------------------------------------------------------------------*/ Handle() = default; + /// + /// Converts a generic/void Handle to a specific type. + /// Runtime type checking is enabled to ensure that Handles are only being casted + /// to the correct type. + /// + /// Generic handle to convert. + /// + /// Thrown if an invalid conversion is made. + /// + explicit Handle(const Handle& genericHandle); ~Handle() = default; - + /*-----------------------------------------------------------------------------*/ /* Usage Functions */ /*-----------------------------------------------------------------------------*/ @@ -101,6 +126,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /* Overloaded Operators */ /*-----------------------------------------------------------------------------*/ + inline bool operator==(const Handle& rhs) const noexcept; /// /// Returns the underlying object pointed to by the Handle. /// @@ -139,13 +165,54 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /* Data Members */ /*-----------------------------------------------------------------------------*/ - ResourceLibrary* library; + SHResourceLibrary* library = nullptr; /*-----------------------------------------------------------------------------*/ /* Friend Declarations */ /*-----------------------------------------------------------------------------*/ - friend class ResourceLibrary; - }; + friend class SHResourceLibrary; + friend class Handle; + }; + + /// + /// Template Specialization for Handle that represents a type-less Handle. + /// + template<> + class Handle : public HandleBase + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + Handle() = default; + template + explicit Handle(const Handle& handle); + ~Handle() = default; + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + template + inline bool operator==(const Handle& rhs) const noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Query Functions */ + /*-----------------------------------------------------------------------------*/ + inline SHResourceLibraryBase* GetLibrary() const; + + protected: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + SHResourceLibraryBase* library = nullptr; + + /*-----------------------------------------------------------------------------*/ + /* Friend Declarations */ + /*-----------------------------------------------------------------------------*/ + template + friend class Handle; + friend class Convert; + }; /// /// Interface that needs to be implemented by classes that want to store a Handle to @@ -154,7 +221,7 @@ namespace SHADE template class ISelfHandle { - public: + public: /*-----------------------------------------------------------------------------*/ /* Constructors */ /*-----------------------------------------------------------------------------*/ @@ -182,7 +249,7 @@ namespace SHADE /// /// Required to lock usage to ResourceLibrary only. /// Handle to set. - void SetHandle(const ResourceLibrary& rscLib, Handle hdl); + void SetHandle(const SHResourceLibrary& rscLib, Handle hdl); private: /*-----------------------------------------------------------------------------*/ @@ -190,6 +257,33 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ Handle handle; }; + } -#include "Handle.hpp" \ No newline at end of file +namespace std +{ + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// std::hash template specialization for Handle + /// + /// Type for the Handle. + template + struct hash> + { + std::size_t operator() (const SHADE::Handle& hdl) const; + }; + /// + /// std::hash template specialization for std::pair, Handle> + /// + /// Type for the first Handle. + /// Type for the second Handle. + template + struct hash, SHADE::Handle>> + { + std::size_t operator() (std::pair, SHADE::Handle> const& pair) const; + }; +} + +#include "SHHandle.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Resource/SHHandle.hpp b/SHADE_Engine/src/Resource/SHHandle.hpp new file mode 100644 index 00000000..ff544d3b --- /dev/null +++ b/SHADE_Engine/src/Resource/SHHandle.hpp @@ -0,0 +1,166 @@ +#pragma once +// Primary Header +#include "SHHandle.h" +#include "SHResourceLibrary.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* HandleBase - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + inline HandleBase::Id HandleBase::GetId() const noexcept + { + return id; + } + + /*---------------------------------------------------------------------------------*/ + /* HandleBase - Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + inline HandleBase::operator bool() const noexcept + { + return id.Raw != INVALID_ID.Raw; + } + + /*---------------------------------------------------------------------------------*/ + /* Handle - Constructors */ + /*---------------------------------------------------------------------------------*/ + template + Handle::Handle(const Handle& genericHandle) + : library { reinterpret_cast*>(genericHandle.library) } + { + id = genericHandle.id; + + // Check if valid + if (library != nullptr && library->GetType() != typeid(T)) + throw BadHandleCastException(); + } + + /*---------------------------------------------------------------------------------*/ + /* Handle - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + template + inline void Handle::Free() + { + library->Free(*this); + } + + /*---------------------------------------------------------------------------------*/ + /* Handle - Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + template + bool SHADE::Handle::operator==(const Handle& rhs) const noexcept + { + return id.Raw == rhs.id.Raw && library == rhs.library; + } + + template + T& Handle::operator*() + { + return library->Get(*this); + } + + template + const T& Handle::operator*() const + { + return library->Get(*this); + } + + template + T* Handle::operator->() + { + return &library->Get(*this); + } + + template + const T* Handle::operator->() const + { + return &library->Get(*this); + } + + /*---------------------------------------------------------------------------------*/ + /* Handle - Constructors */ + /*---------------------------------------------------------------------------------*/ + template + Handle::Handle(const Handle& handle) + : library{ static_cast(handle.library) } + { + id = handle.id; + } + + /*---------------------------------------------------------------------------------*/ + /* Handle - Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + template + bool SHADE::Handle::operator==(const Handle& rhs) const noexcept + { + return id.Raw == rhs.id.Raw && library == static_cast(rhs.library); + } + + SHResourceLibraryBase* SHADE::Handle::GetLibrary() const + { + return library; + } + + /*---------------------------------------------------------------------------------*/ + /* ISelfHandle - Constructors */ + /*---------------------------------------------------------------------------------*/ + template + inline ISelfHandle::ISelfHandle(const ISelfHandle& rhs) + : handle { rhs.handle } + {} + + template + inline ISelfHandle::ISelfHandle(ISelfHandle&& rhs) + : handle { rhs.handle } + {} + + /*---------------------------------------------------------------------------------*/ + /* ISelfHandle - Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + template + inline ISelfHandle& ISelfHandle::operator=(const ISelfHandle& rhs) + { + handle = rhs.handle; + return *this; + } + + template + inline ISelfHandle& ISelfHandle::operator=(ISelfHandle&& rhs) + { + handle = rhs.handle; + return *this; + } + + /*---------------------------------------------------------------------------------*/ + /* ISelfHandle - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + template + inline Handle ISelfHandle::GetHandle() const + { + return handle; + } + template + inline void ISelfHandle::SetHandle(const SHResourceLibrary&, Handle hdl) + { + handle = hdl; + } +} + +namespace std +{ + /*---------------------------------------------------------------------------------*/ + /* std::hash Template Specializations */ + /*---------------------------------------------------------------------------------*/ + template + std::size_t hash>::operator()(const SHADE::Handle& hdl) const + { + return std::hash{}(hdl.GetId().Raw); + } + + template + std::size_t hash, SHADE::Handle>>::operator()( + std::pair, SHADE::Handle> const& pair) const + { + return std::hash{}(pair.first.GetId().Raw) ^ std::hash{}(pair.second.GetId().Raw); + } +} diff --git a/SHADE_Engine/src/Resource/ResourceLibrary.cpp b/SHADE_Engine/src/Resource/SHResourceLibrary.cpp similarity index 70% rename from SHADE_Engine/src/Resource/ResourceLibrary.cpp rename to SHADE_Engine/src/Resource/SHResourceLibrary.cpp index 5a3ad9fe..db4a8f48 100644 --- a/SHADE_Engine/src/Resource/ResourceLibrary.cpp +++ b/SHADE_Engine/src/Resource/SHResourceLibrary.cpp @@ -1,16 +1,16 @@ #include "SHPch.h" -#include "ResourceLibrary.h" +#include "SHResourceLibrary.h" namespace SHADE { /*-----------------------------------------------------------------------------*/ /* Constructors/Destructors */ /*-----------------------------------------------------------------------------*/ - ResourceManager::~ResourceManager() + SHResourceHub::~SHResourceHub() { // Delete all resources libraries - for (const auto& deleter : deleters) + for (auto iter = deleters.rbegin(); iter != deleters.rend(); ++iter) { - deleter(); + (*iter)(); } deleters.clear(); } diff --git a/SHADE_Engine/src/Resource/ResourceLibrary.h b/SHADE_Engine/src/Resource/SHResourceLibrary.h similarity index 83% rename from SHADE_Engine/src/Resource/ResourceLibrary.h rename to SHADE_Engine/src/Resource/SHResourceLibrary.h index b05dc499..46ae4572 100644 --- a/SHADE_Engine/src/Resource/ResourceLibrary.h +++ b/SHADE_Engine/src/Resource/SHResourceLibrary.h @@ -6,24 +6,42 @@ #include // Project Headers -#include "Handle.h" +#include "SHHandle.h" #include "Resource/SparseSet.h" namespace SHADE { + /// + /// Base class for SHResourceLibrary that holds information about the library type. + /// + class SHResourceLibraryBase + { + public: + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + inline std::type_index GetType() { return libraryType; } + + protected: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + std::type_index libraryType = typeid(void); + }; + /// /// Generic Resource Library for a specified type of Resource. This object will own /// any resources created using it. /// /// Type of resources that this library stores. template - class ResourceLibrary + class SHResourceLibrary : public SHResourceLibraryBase { public: /*-----------------------------------------------------------------------------*/ /* Constructor */ /*-----------------------------------------------------------------------------*/ - ResourceLibrary(); + SHResourceLibrary(); /*-----------------------------------------------------------------------------*/ /* Usage Functions */ @@ -62,26 +80,26 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ SparseSet objects; SparseSet versionCounts; - std::queue freeList; + std::queue freeList; uint32_t lastIdx = 0; /*-----------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------*/ void assertHandleValid(Handle handle) const; - int getAvailableFreeIndex(); + uint32_t getAvailableFreeIndex(); }; /// /// Manages all resources in multiple ResourceLibraries. /// - class ResourceManager final + class SHResourceHub final { public: /*-----------------------------------------------------------------------------*/ /* Constructors/Destructors */ /*-----------------------------------------------------------------------------*/ - ~ResourceManager(); + ~SHResourceHub(); /*-----------------------------------------------------------------------------*/ /* Usage Functions */ @@ -118,7 +136,7 @@ namespace SHADE /// Handle to the resource object. /// Read-only reference to the resource object. template - const T& Get(Handle handle) const; + const T& Get(Handle handle) const; private: /*-----------------------------------------------------------------------------*/ @@ -136,10 +154,10 @@ namespace SHADE /* Helper Functions */ /*-----------------------------------------------------------------------------*/ template - ResourceLibrary& getLibrary(); + SHResourceLibrary& getLibrary(); template - const ResourceLibrary& getLibrary() const; + const SHResourceLibrary& getLibrary() const; }; } -#include "ResourceLibrary.hpp" \ No newline at end of file +#include "SHResourceLibrary.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Resource/ResourceLibrary.hpp b/SHADE_Engine/src/Resource/SHResourceLibrary.hpp similarity index 75% rename from SHADE_Engine/src/Resource/ResourceLibrary.hpp rename to SHADE_Engine/src/Resource/SHResourceLibrary.hpp index 2ad856c6..411f6bf5 100644 --- a/SHADE_Engine/src/Resource/ResourceLibrary.hpp +++ b/SHADE_Engine/src/Resource/SHResourceLibrary.hpp @@ -1,6 +1,6 @@ #pragma once // Primary Header -#include "ResourceLibrary.h" +#include "SHResourceLibrary.h" // Standard Library #include @@ -10,13 +10,14 @@ namespace SHADE /* ResourceLibrary - Constructor */ /*---------------------------------------------------------------------------------*/ template - ResourceLibrary::ResourceLibrary() + SHResourceLibrary::SHResourceLibrary() { // Type Checking - //static_assert(std::is_copy_assignable_v, "Resource Library's resources must be copy assignable."); - //static_assert(std::is_copy_constructible_v, "Resource Library's resources must be copy constructible."); static_assert(std::is_move_assignable_v, "Resource Library's resources must be move assignable."); static_assert(std::is_move_constructible_v, "Resource Library's resources must be move constructible."); + + // Keep track of the type for conversions + libraryType = typeid(T); } /*---------------------------------------------------------------------------------*/ @@ -24,7 +25,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ template template - Handle ResourceLibrary::Create(Args&&... args) + Handle SHResourceLibrary::Create(Args&&... args) { // Create the handle Handle handle; @@ -35,13 +36,13 @@ namespace SHADE } else { - handle.id.Data.Version = 0; - versionCounts.insert(handle.id.Data.Index, handle.id.Data.Version); + handle.id.Data.Version = 0U; + versionCounts.insert(static_cast(handle.id.Data.Index), handle.id.Data.Version); } handle.library = this; // Create the resource - [[maybe_unused]] T& obj = objects.insert(handle.id.Data.Index, std::forward(args) ...); + [[maybe_unused]] T& obj = objects.insert(static_cast(handle.id.Data.Index), std::forward(args) ...); // Handling SelfHandle assignment if constexpr (std::is_base_of_v, T>) @@ -55,7 +56,7 @@ namespace SHADE } template - void ResourceLibrary::Free(Handle handle) + void SHResourceLibrary::Free(Handle handle) { assertHandleValid(handle); @@ -63,7 +64,7 @@ namespace SHADE } template - T& ResourceLibrary::Get(Handle handle) + T& SHResourceLibrary::Get(Handle handle) { assertHandleValid(handle); @@ -71,7 +72,7 @@ namespace SHADE } template - const T& ResourceLibrary::Get(Handle handle) const + const T& SHResourceLibrary::Get(Handle handle) const { assertHandleValid(handle); @@ -82,14 +83,14 @@ namespace SHADE /* ResourceLibrary - Helper Functions */ /*---------------------------------------------------------------------------------*/ template - void ResourceLibrary::assertHandleValid(Handle handle) const + void SHResourceLibrary::assertHandleValid(Handle handle) const { if (!handle || handle.id.Data.Version != versionCounts[handle.id.Data.Index]) throw std::invalid_argument("Invalid handle provided!"); } template - inline int ResourceLibrary::getAvailableFreeIndex() + inline uint32_t SHResourceLibrary::getAvailableFreeIndex() { // Get from the free list if present if (!freeList.empty()) @@ -107,25 +108,25 @@ namespace SHADE /* ResourceManager - Usage Functions */ /*---------------------------------------------------------------------------------*/ template - Handle ResourceManager::Create(Args&&... args) + Handle SHResourceHub::Create(Args&&... args) { return getLibrary().Create(std::forward(args) ...); } template - void ResourceManager::Free(Handle handle) + void SHResourceHub::Free(Handle handle) { getLibrary().Free(handle); } template - T& ResourceManager::Get(Handle handle) + T& SHResourceHub::Get(Handle handle) { return getLibrary().Get(handle); } template - const T& ResourceManager::Get(Handle handle) const + const T& SHResourceHub::Get(Handle handle) const { return getLibrary().Get(handle); } @@ -134,18 +135,18 @@ namespace SHADE /* ResourceManager - Helper Functions */ /*-----------------------------------------------------------------------------*/ template - ResourceLibrary& ResourceManager::getLibrary() + SHResourceLibrary& SHResourceHub::getLibrary() { // Attempt to retrieve the library const std::type_index RSC_TYPE = typeid(T); if (resourceLibs.contains(RSC_TYPE)) { - return *static_cast*>(resourceLibs.at(RSC_TYPE)); + return *static_cast*>(resourceLibs.at(RSC_TYPE)); } else { // Construct library if doesn't exist - ResourceLibrary* lib = new ResourceLibrary(); + SHResourceLibrary* lib = new SHResourceLibrary(); resourceLibs.emplace(RSC_TYPE, static_cast(lib)); // Construct deleter to properly delete objects with void* @@ -156,8 +157,8 @@ namespace SHADE } template - const ResourceLibrary& ResourceManager::getLibrary() const + const SHResourceLibrary& SHResourceHub::getLibrary() const { - return const_cast(this).getLibrary(); + return const_cast(this).getLibrary(); } } diff --git a/SHADE_Engine/src/Resource/SHResourceManager.cpp b/SHADE_Engine/src/Resource/SHResourceManager.cpp new file mode 100644 index 00000000..dad9fd9f --- /dev/null +++ b/SHADE_Engine/src/Resource/SHResourceManager.cpp @@ -0,0 +1,106 @@ +/************************************************************************************//*! +\file SHResourceManager.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 21, 2022 +\brief Contains the definition of the functions of the SHResourceManager static + class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +// Primary Include +#include "SHResourceManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHResourceHub SHResourceManager::resourceHub; + std::unordered_map>> SHResourceManager::handlesMap; + std::unordered_map SHResourceManager::assetIdMap; + std::unordered_map> SHResourceManager::typedFreeFuncMap; + std::vector SHResourceManager::loadedAssetData; + bool SHResourceManager::textureChanged = false; + bool SHResourceManager::meshChanged = false; + + /*-----------------------------------------------------------------------------------*/ + /* Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + void SHResourceManager::Unload(AssetID assetId) + { + // Search each library for the asset ID and try to free it + Handle handle; + for (auto& typedHandleMap : handlesMap) + { + if (typedHandleMap.second.contains(assetId)) + { + // Save handle for later + handle = typedHandleMap.second[assetId]; + // Dispose + typedFreeFuncMap[typedHandleMap.first](assetId); + typedHandleMap.second.erase(assetId); + } + } + + // No handles were found + if (!handle) + return; + + for (auto& typedAssetIdsMap : assetIdMap) + { + if (typedAssetIdsMap.second.contains(handle)) + { + // Dispose + typedAssetIdsMap.second.erase(handle); + } + } + } + + void SHResourceManager::FinaliseChanges() + { + SHGraphicsSystem* gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem == nullptr) + throw std::runtime_error("[SHResourceManager] Attempted to load graphics resource without a SHGraphicsSystem installed."); + + if (meshChanged) + { + gfxSystem->BuildMeshBuffers(); + meshChanged = false; + } + if (textureChanged) + { + gfxSystem->BuildTextures(); + textureChanged = false; + } + + // Free CPU Resources + for (auto assetId : loadedAssetData) + { + SHAssetManager::Unload(assetId); + } + loadedAssetData.clear(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Query Functions */ + /*-----------------------------------------------------------------------------------*/ + std::optional SHResourceManager::GetAssetID(Handle handle) + { + const Handle GENERIC_HANDLE = Handle(handle); + + // Search each library for the asset ID and try to free it + for (auto& typedAssetIdsMap : assetIdMap) + { + if (typedAssetIdsMap.second.contains(GENERIC_HANDLE)) + { + return typedAssetIdsMap.second[GENERIC_HANDLE]; + } + } + + return {}; + } +} diff --git a/SHADE_Engine/src/Resource/SHResourceManager.h b/SHADE_Engine/src/Resource/SHResourceManager.h new file mode 100644 index 00000000..d660ada7 --- /dev/null +++ b/SHADE_Engine/src/Resource/SHResourceManager.h @@ -0,0 +1,164 @@ +/************************************************************************************//*! +\file SHResourceManager.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 21, 2022 +\brief Contains the definition of the SHResourceManager static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include + +namespace SHADE { class SHMaterial; } +// Project Includes +#include "SH_API.h" +#include "SHResourceLibrary.h" +#include "Assets/SHAssetMacros.h" +#include "Assets/Asset Types/SHModelAsset.h" +#include "Assets/Asset Types/SHTextureAsset.h" +#include "Assets/Asset Types/SHShaderAsset.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Graphics/MiddleEnd/Textures/SHTextureLibrary.h" +#include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" +#include "Graphics/MiddleEnd/Interface/SHMaterial.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Assets/Asset Types/SHMaterialAsset.h" + +namespace SHADE +{ + /// + /// Template structs that maps a resource to their loaded asset representation type. + /// + template + struct SHResourceLoader { using AssetType = void; }; + template<> struct SHResourceLoader { using AssetType = SHMeshData; }; + template<> struct SHResourceLoader { using AssetType = SHTextureAsset; }; + template<> struct SHResourceLoader { using AssetType = SHShaderAsset; }; + template<> struct SHResourceLoader { using AssetType = SHMaterialAsset; }; + template<> struct SHResourceLoader { using AssetType = SHMaterialSpec; }; +/// + /// Static class responsible for loading and caching runtime resources from their + /// serialised Asset IDs. + /// + class SH_API SHResourceManager + { + public: + /*---------------------------------------------------------------------------------*/ + /* Loading Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Loads or retrieves an existing loaded object of the specified type with the + /// specified asset ID. + /// Note that for specific types, the retrieved Handle may not be valid until after + /// FinaliseChanges() is called. + /// + /// + /// Type of resource to load. + /// + /// Asset ID of the resource to load. + /// Handle to a loaded runtime asset. + template + static Handle LoadOrGet(AssetID assetId); + template<> + static inline Handle LoadOrGet(AssetID assetId); + /// + /// Unloads an existing loaded asset. Attempting to unload an invalid Handle will + /// simply do nothing except emit a warning. + /// Faster than the untemplated version. + /// + /// Type of resource to unload. + /// Handle to the resource to unload. + template + static void Unload(Handle assetId); + /// + /// Unloads an existing loaded asset. Attempting to unload an invalid Handle will + /// simply do nothing except emit a warning. + /// Compared to the templated version, this function is slower as it requires + /// searching through the storage of all resource types. + /// + /// Handle to the resource to unload. + static void Unload(AssetID assetId); + /// + /// Needs to be called to finalise all changes to loads, unless at runtime. + /// + static void FinaliseChanges(); + + /*---------------------------------------------------------------------------------*/ + /* Query Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Retrieves the AssetID associated with a specified Handle. + /// Faster than the untemplated version. + /// + /// Type of resource to get the ID of. + /// Handle to get the AssetID of. + /// + /// AssetID for the specified Handle. If the Handle is invalid, there will be no + /// value. + /// + template + static std::optional GetAssetID(Handle handle); + /// + /// Retrieves the AssetID associated with a specified Handle. + /// Compared to the templated version, this function is slower as it requires + /// searching through the storage of all resource types. + /// + /// Handle to get the AssetID of. + /// + /// AssetID for the specified Handle. If the Handle is invalid, there will be no + /// value. + /// + static std::optional GetAssetID(Handle handle); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + using AssetHandleMap = std::unordered_map>; + using HandleAssetMap = std::unordered_map, AssetID>; + using AssetHandleMapRef = std::reference_wrapper; + using HandleAssetMapRef = std::reference_wrapper; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + // Handles + static SHResourceHub resourceHub; + static std::unordered_map handlesMap; + static std::unordered_map assetIdMap; + static std::unordered_map> typedFreeFuncMap; + // Pointers to temp CPU resources + static std::vector loadedAssetData; + // Dirty Flags + static bool meshChanged; + static bool textureChanged; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Retrieves or creates the AssetHandleMap for the specific type if it doesn't exist + /// + /// + /// The type of AssetHandleMap to retrieve. + /// + /// Reference to the AssetHandleMap of the specified type. + template + static std::pair getAssetHandleMap(); + /// + /// + /// + /// + /// + /// + template + static Handle load(AssetID assetId, const typename SHResourceLoader::AssetType& assetData); + }; +} + +#include "SHResourceManager.hpp" diff --git a/SHADE_Engine/src/Resource/SHResourceManager.hpp b/SHADE_Engine/src/Resource/SHResourceManager.hpp new file mode 100644 index 00000000..01d82a7b --- /dev/null +++ b/SHADE_Engine/src/Resource/SHResourceManager.hpp @@ -0,0 +1,319 @@ +/************************************************************************************//*! +\file SHResourceManager.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 21, 2022 +\brief Contains the definition of the function templates of the + SHResourceManager static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Primary Include +#include "SHResourceManager.h" +// External Dependencies +#include +// Project Includes +#include "Assets/SHAssetManager.h" +#include "Assets/Asset Types/SHAssetIncludes.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Tools/SHLog.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Serialization/SHYAMLConverters.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Loading Functions */ + /*-----------------------------------------------------------------------------------*/ + template + Handle SHResourceManager::LoadOrGet(AssetID assetId) + { + // Check if it is an unsupported type + if (!std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v + ) + { + static_assert(true, "Unsupported Resource Type specified for SHResourceManager."); + } + + /* Attempt to get existing loaded asset */ + auto [typedHandleMap, typedAssetIdMap] = getAssetHandleMap(); + if (typedHandleMap.get().contains(assetId)) + return Handle(typedHandleMap.get()[assetId]); + + /* Otherwise, we need to load it! */ + // Load Asset Data + const auto* assetData = SHAssetManager::GetData::AssetType>(assetId); + if (assetData == nullptr) + { + SHLog::Warning("[SHResourceManager] Attempted to load an asset with an invalid Asset ID."); + return {}; + } + + auto handle = load(assetId, *assetData); + Handle genericHandle = Handle(handle); + typedHandleMap.get().emplace(assetId, genericHandle); + typedAssetIdMap.get().emplace(genericHandle, assetId); + return handle; + } + + template<> + Handle SHResourceManager::LoadOrGet(AssetID assetId) + { + /* Attempt to get existing loaded asset */ + auto [typedHandleMap, typedAssetIdMap] = getAssetHandleMap(); + if (typedHandleMap.get().contains(assetId)) + return Handle(typedHandleMap.get()[assetId]); + + /* Otherwise, we need to load it! */ + // Get system + SHGraphicsSystem* gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem == nullptr) + throw std::runtime_error("[SHResourceManager] Attempted to load graphics resource without a SHGraphicsSystem installed."); + + // Get SHMaterialSpec + Handle matSpec = LoadOrGet(assetId); + if (!matSpec) + return {}; + + // Create the material + auto handle = load(assetId, *matSpec); + Handle genericHandle = Handle(handle); + typedHandleMap.get().emplace(assetId, genericHandle); + typedAssetIdMap.get().emplace(genericHandle, assetId); + return handle; + } + + template + void SHResourceManager::Unload(Handle asset) + { + // Check if it is an unsupported type + if (!std::is_same_v && !std::is_same_v) + { + static_assert(true, "Unsupported Resource Type specified for SHResourceManager."); + } + + /* Attempt to get existing loaded asset */ + auto [typedHandleMap, typedAssetIdMap] = getAssetHandleMap(); + if (typedHandleMap.get().contains(asset)) + { + // Remove from ResourceHub if SHMaterialSpec + if (std::is_same_v) + resourceHub.Free(asset); + + // Dispose + Handle handle = typedHandleMap.get()[asset]; + auto typedHandle = static_cast>(handle); + typedHandle.Free(); + typedAssetIdMap.get().erase(handle); + typedHandleMap.get().erase(asset); + } + else + { + // There's nothing to remove + SHLog::Warning("[SHResourceManager] Attempted to unload an invalid resource. Ignoring."); + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Query Functions */ + /*-----------------------------------------------------------------------------------*/ + template + std::optional SHResourceManager::GetAssetID(Handle handle) + { + const Handle GENERIC_HANDLE = Handle(handle); + auto [typedHandleMap, typedAssetIdMap] = getAssetHandleMap(); + if (typedAssetIdMap.get().contains(GENERIC_HANDLE)) + { + return typedAssetIdMap.get()[GENERIC_HANDLE]; + } + + return {}; + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + template + std::pair SHResourceManager::getAssetHandleMap() + { + const std::type_index TYPE = typeid(ResourceType); + + if (!handlesMap.contains(TYPE)) + { + handlesMap.emplace(TYPE, AssetHandleMap{}); + assetIdMap.emplace(TYPE, HandleAssetMap{}); + typedFreeFuncMap.emplace + ( + TYPE, + [TYPE](AssetID assetId) + { + static_cast>(SHResourceManager::handlesMap[TYPE][assetId]).Free(); + } + ); + } + return std::make_pair(std::ref(handlesMap[TYPE]), std::ref(assetIdMap[TYPE])); + } + + template + Handle SHResourceManager::load(AssetID assetId, const typename SHResourceLoader::AssetType& assetData) + { + // Get system + SHGraphicsSystem* gfxSystem = SHSystemManager::GetSystem(); + if (gfxSystem == nullptr) + throw std::runtime_error("[SHResourceManager] Attempted to load graphics resource without a SHGraphicsSystem installed."); + + // Meshes + if constexpr (std::is_same_v) + { + loadedAssetData.emplace_back(assetId); + meshChanged = true; + + return gfxSystem->AddMesh + ( + assetData.VertexPositions.size(), + assetData.VertexPositions.data(), + assetData.VertexTexCoords.data(), + assetData.VertexTangents.data(), + assetData.VertexNormals.data(), + assetData.Indices.size(), + assetData.Indices.data() + ); + } + // Textures + else if constexpr (std::is_same_v) + { + loadedAssetData.emplace_back(assetId); + textureChanged = true; + + return gfxSystem->AddTexture + ( + assetData.numBytes, + assetData.pixelData, + assetData.width, + assetData.height, + assetData.format, + assetData.mipOffsets + ); + } + // Shaders + else if constexpr (std::is_same_v) + { + auto shader = gfxSystem->GetDevice()->CreateShaderModule + ( + assetData.spirvBinary, + "main", + static_cast(assetData.shaderType), + assetData.name + ); + shader->Reflect(); + return shader; + } + // Material Spec + else if constexpr (std::is_same_v) + { + // Get the data we need to construct + auto matSpec = resourceHub.Create(); + YAML::convert::decode(*YAML::Load(assetData.data).begin(), *matSpec); + // Failed to load + if (matSpec->subpassName == "") + { + // Use default material + *matSpec = SHMaterialSpec(*gfxSystem->GetDefaultMaterial()); + } + + return matSpec; + } + // Materials + else if constexpr (std::is_same_v) + { + // Load shaders + auto vertexShader = SHResourceManager::LoadOrGet(assetData.vertexShader); + auto fragShader = SHResourceManager::LoadOrGet(assetData.fragShader); + + // Ensure that both shaders are present + if (!(vertexShader && fragShader)) + { + SHLOG_ERROR("[SHResourceManager] Failed to load material as shaders failed to be loaded."); + return {}; + } + + // Grab subpass from worldRenderer + auto renderPass = gfxSystem->GetPrimaryRenderpass(); + if (!renderPass) + { + SHLOG_ERROR("[SHResourceManager] Failed to load material as RenderPass could not be found."); + return {}; + } + auto subPass = renderPass->GetSubpass(assetData.subpassName); + if (!subPass) + { + SHLOG_ERROR("[SHResourceManager] Failed to load material as SubPass could not be found."); + return {}; + } + + // Create material + auto matHandle = gfxSystem->AddMaterial(vertexShader, fragShader, subPass); + + // Set properties for the material + Handle pipelineProperties = matHandle->GetShaderBlockInterface(); + for (int i = 0; i < static_cast(pipelineProperties->GetVariableCount()); ++i) + { + const std::string& PROP_NAME = pipelineProperties->GetVariableName(i); + const YAML::Node& PROP_NODE = assetData.properties[PROP_NAME.data()]; + if (PROP_NODE.IsDefined()) + { + const std::string& VAR_NAME = pipelineProperties->GetVariableName(i); + const SHShaderBlockInterface::Variable* VARIABLE = pipelineProperties->GetVariable(i); + switch (VARIABLE->type) + { + case SHADE::SHShaderBlockInterface::Variable::Type::FLOAT: + matHandle->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::INT: + { + Handle texture = LoadOrGet(PROP_NODE.as()); + // HACK: Need to split this out to a separate pass before loading the materials and subsequently, the scenes + gfxSystem->BuildTextures(); + + if (texture) + { + matHandle->SetProperty(VARIABLE->offset, texture->TextureArrayIndex); + } + else + { + SHLOG_WARNING("[] Attempted to load invalid texture! Setting to 0."); + matHandle->SetProperty(VARIABLE->offset, 0); + } + } + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR2: + matHandle->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR3: + matHandle->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::VECTOR4: + matHandle->SetProperty(VARIABLE->offset, PROP_NODE.as()); + break; + case SHADE::SHShaderBlockInterface::Variable::Type::OTHER: + default: + continue; + break; + } + } + } + + return matHandle; + } + } +} diff --git a/SHADE_Engine/src/Resource/SparseSet.h b/SHADE_Engine/src/Resource/SparseSet.h index 8a3f9c6e..fb4a8311 100644 --- a/SHADE_Engine/src/Resource/SparseSet.h +++ b/SHADE_Engine/src/Resource/SparseSet.h @@ -59,9 +59,9 @@ namespace SHADE SparseSet(); ~SparseSet() = default; - // Disallow moving or copying - SparseSet(const SparseSet&) = delete; - SparseSet(SparseSet&&) = delete; + //// Disallow moving or copying + //SparseSet(const SparseSet&) = delete; + //SparseSet(SparseSet&&) = delete; /*-----------------------------------------------------------------------------*/ /* Usage Functions */ diff --git a/SHADE_Engine/src/Resource/SparseSet.hpp b/SHADE_Engine/src/Resource/SparseSet.hpp index 70e15f29..816ca432 100644 --- a/SHADE_Engine/src/Resource/SparseSet.hpp +++ b/SHADE_Engine/src/Resource/SparseSet.hpp @@ -56,7 +56,7 @@ namespace SHADE throw std::invalid_argument("An element at this index does not exist!"); // Swap with the last element - const int BACK_IDX = denseArray.size() - 1; + const auto BACK_IDX = denseArray.size() - 1; std::swap(denseArray[sparseArray[idx]], denseArray.back()); denseArray.pop_back(); // Update the sparse set by swapping the indices @@ -70,13 +70,13 @@ namespace SHADE } template - SparseSet::reference SparseSet::at(index_type idx) + typename SparseSet::reference SparseSet::at(index_type idx) { return const_cast(static_cast&>(*this).at(idx)); } template - SparseSet::const_reference SparseSet::at(index_type idx) const + typename SparseSet::const_reference SparseSet::at(index_type idx) const { // Range Check if (idx >= sparseArray.size() || !contains(idx)) @@ -84,7 +84,7 @@ namespace SHADE return denseArray[sparseArray[idx]]; } template - SparseSet::size_type SparseSet::size() const + typename SparseSet::size_type SparseSet::size() const { return denseArray.size(); } @@ -105,12 +105,12 @@ namespace SHADE } template template - SparseSet::reference SparseSet::insert(index_type idx, Args && ...args) + typename SparseSet::reference SparseSet::insert(index_type idx, Args && ...args) { // We need to resize the array if (idx >= sparseArray.size()) { - const int NEW_SIZE = idx + 1; + const auto NEW_SIZE = idx + 1; sparseArray.resize(NEW_SIZE, INVALID); inverseSparseArray.resize(NEW_SIZE, INVALID); } @@ -123,7 +123,7 @@ namespace SHADE auto& insertedElem = denseArray.emplace_back(std::forward(args) ...); // Update sparse and inverse sparse arrays - const index_type DENSE_IDX = denseArray.size() - 1; + const auto DENSE_IDX = static_cast(denseArray.size()) - 1; sparseArray[idx] = DENSE_IDX; inverseSparseArray[DENSE_IDX] = idx; diff --git a/SHADE_Engine/src/SHCommonTypes.h b/SHADE_Engine/src/SHCommonTypes.h new file mode 100644 index 00000000..97ef7928 --- /dev/null +++ b/SHADE_Engine/src/SHCommonTypes.h @@ -0,0 +1,28 @@ +/************************************************************************************//*! +\file SHCommonTypes.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 8, 2022 +\brief Contains the definitions of type alias for commonly used units for + clarity and convenience. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Type used to mark a value that is supposed to represent a size in bytes. + */ + /***********************************************************************************/ + using Byte = size_t; +} diff --git a/SHADE_Engine/src/SH_API.h b/SHADE_Engine/src/SH_API.h new file mode 100644 index 00000000..64d1eebe --- /dev/null +++ b/SHADE_Engine/src/SH_API.h @@ -0,0 +1,36 @@ +/************************************************************************************//*! +\file SH_API.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 13, 2022 +\brief Contains dllexport and dllimport macros for the SHADE Engine. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Select the correct export system based on the compiler +#if defined SH_LIB +# define SH_API +#else +# if defined _WIN32 || defined __CYGWIN__ || defined _MSC_VER +# define SH_EXPORT __declspec(dllexport) +# define SH_IMPORT __declspec(dllimport) +# elif defined __GNUC__ && __GNUC__ >= 4 +# define SH_EXPORT __attribute__((visibility("default"))) +# define SH_IMPORT __attribute__((visibility("default"))) +# else /* Unsupported compiler */ +# define SH_EXPORT +# define SH_IMPORT +# endif + // Define the correct +# ifndef SH_API +# if defined SH_API_EXPORT +# define SH_API SH_EXPORT +# else +# define SH_API SH_IMPORT +# endif +# endif +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/SHpch.h b/SHADE_Engine/src/SHpch.h index 0342eedb..7e308829 100644 --- a/SHADE_Engine/src/SHpch.h +++ b/SHADE_Engine/src/SHpch.h @@ -9,7 +9,10 @@ #pragma once + + #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX // Windows Header Files #include // C RunTime Header Files @@ -30,3 +33,9 @@ #include #include #include +#include +#include + +#include "Common/SHCommonTypes.h" +#include "Tools/SHLogger.h" +#include "Tools/SHException.h" diff --git a/SHADE_Engine/src/Scene/SHScene.h b/SHADE_Engine/src/Scene/SHScene.h index 18aeffdc..1f06824e 100644 --- a/SHADE_Engine/src/Scene/SHScene.h +++ b/SHADE_Engine/src/Scene/SHScene.h @@ -12,6 +12,9 @@ #define SH_SCENE_H #include +#include "SHSceneGraph.h" +#include "Assets/SHAssetMacros.h" +#include "ECS_Base/General/SHFamily.h" namespace SHADE { @@ -19,16 +22,21 @@ namespace SHADE class SHScene { private: + SHSceneGraph sceneGraph; + protected: SHScene() = default; public: - std::string sceneName; - virtual ~SHScene() = default; + std::string sceneName; + AssetID sceneAssetID; + + SHSceneGraph& GetSceneGraph() noexcept { return sceneGraph; } + virtual void Load() = 0; virtual void Init() = 0; virtual void Update(float dt) = 0; @@ -37,6 +45,7 @@ namespace SHADE virtual void Unload() = 0; }; + template class SH_API SHFamilyID; } diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.cpp b/SHADE_Engine/src/Scene/SHSceneGraph.cpp index 9c99fb5a..6240b7bf 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.cpp +++ b/SHADE_Engine/src/Scene/SHSceneGraph.cpp @@ -1,22 +1,21 @@ /**************************************************************************************** * \file SHSceneGraph.cpp * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Scene Graph & Scene Nodes. + * \brief Implementation for a Scene Graph. * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent * of DigiPen Institute of Technology is prohibited. ****************************************************************************************/ -#include +#include "SHpch.h" // Primary Header #include "SHSceneGraph.h" // Project Headers -#include "ECS_Base/System/SHEntityManager.h" -#include "Tools/SHLogger.h" -#include "Tools/SHException.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Events/SHEventManager.hpp" namespace SHADE { @@ -24,59 +23,12 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHSceneNode::SHSceneNode(EntityID eid, SHSceneNode* parent) noexcept - : isActive { true } - , entityID { eid } - , parent { parent } - {} - - - SHSceneNode::SHSceneNode(const SHSceneNode& rhs) noexcept - : isActive { rhs.isActive } - , entityID { rhs.entityID } - , parent { rhs.parent } - { - std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); - } - - SHSceneNode::SHSceneNode(SHSceneNode&& rhs) noexcept - : isActive { rhs.isActive } - , entityID { rhs.entityID } - , parent { rhs.parent } - { - std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); - } - - SHSceneNode& SHSceneNode::operator=(const SHSceneNode& rhs) noexcept - { - if (this == &rhs) - return *this; - - isActive = rhs.isActive; - entityID = rhs.entityID; - parent = rhs.parent; - - children.clear(); - std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); - - return *this; - } - - SHSceneNode& SHSceneNode::operator=(SHSceneNode&& rhs) noexcept - { - isActive = rhs.isActive; - entityID = rhs.entityID; - parent = rhs.parent; - - children.clear(); - std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); - - return *this; - } - SHSceneGraph::SHSceneGraph() noexcept : root { nullptr } - {} + { + // The root is set to the maximum entity. It should not be interfaced with. + root = AllocateNode(MAX_EID); + } SHSceneGraph::~SHSceneGraph() noexcept { @@ -87,8 +39,15 @@ namespace SHADE #endif // Go through the map and release all the nodes - for (auto* node : entityNodeMap | std::views::values) - ReleaseNode(node); + for (auto*& node : entityNodeMap | std::views::values) + { + delete node; + node = nullptr; + } + + entityNodeMap.clear(); + + //delete root; #ifdef _DEBUG SHLOG_INFO("Scene Graph Destroyed Successfully!") @@ -99,47 +58,19 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - SHSceneNode* SHSceneNode::GetChild(EntityID childID) const noexcept - { - // Error handling - { - if (!SHEntityManager::IsValidEID(childID)) - { - SHLOG_ERROR("Child Entity {} is invalid! Unable to get child from Entity {}", childID, entityID) - return nullptr; - } - - if (children.empty()) - { - SHLOG_WARNING("Entity {} has no children!", entityID) - return nullptr; - } - } - - // Find child - const auto ENTITY_MATCH = [&](const SHSceneNode* node) { return node->GetEntityID() == childID; }; - - const auto CHILD_ITER = std::ranges::find_if(children.begin(), children.end(),ENTITY_MATCH); - if (CHILD_ITER == children.end()) - { - SHLOG_WARNING("Entity {} is not a child of Entity {}! Unable to retrieve child node!", childID, entityID) - return nullptr; - } - - return *CHILD_ITER; - } - - SHSceneNode* SHSceneGraph::GetRoot() const noexcept + const SHSceneNode* SHSceneGraph::GetRoot() const noexcept { if (root != nullptr) return root; - SHLOG_WARNING("Scene has no root object!") - return nullptr; + SHLOG_ERROR("Scene has no root object!") + return nullptr; } SHSceneNode* SHSceneGraph::GetNode(EntityID entityID) const noexcept { + //////////////////////////////////////// + // Error handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid! Unable to Get Scene node!", entityID) @@ -149,15 +80,18 @@ namespace SHADE const auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene! Unable to Get Scene node!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to Get Scene node!", entityID) return nullptr; } + //////////////////////////////////////// return NODE_ITER->second; } SHSceneNode* SHSceneGraph::GetParent(EntityID entityID) const noexcept { + //////////////////////////////////////// + // Error handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid! Unable to get Parent node!", entityID) @@ -167,16 +101,18 @@ namespace SHADE const auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene! Unable to get Parent node!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to get Parent node!", entityID) return nullptr; } + //////////////////////////////////////// return NODE_ITER->second->GetParent(); } SHSceneNode* SHSceneGraph::GetChild(EntityID entityID, SHSceneNode* childNode) const noexcept { - // Error Handling + //////////////////////////////////////// + // Error handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) @@ -186,7 +122,7 @@ namespace SHADE const auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) return nullptr; } @@ -203,12 +139,15 @@ namespace SHADE SHLOG_WARNING("Entity {} is not a child of Entity {}!", childNode->GetEntityID(), entityID) return nullptr; } + //////////////////////////////////////// return *CHILD_ITER; } SHSceneNode* SHSceneGraph::GetChild(EntityID entityID, EntityID childEntityID) const noexcept { + //////////////////////////////////////// + // Error handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) @@ -218,17 +157,18 @@ namespace SHADE const auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) return nullptr; } + //////////////////////////////////////// return NODE_ITER->second->GetChild(childEntityID); } const std::vector& SHSceneGraph::GetChildren(EntityID entityID) const noexcept { - // TODO(Diren): Discuss with team best way to handle this - + //////////////////////////////////////// + // Error handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) @@ -238,32 +178,43 @@ namespace SHADE const auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) return root->GetChildren(); } + //////////////////////////////////////// return NODE_ITER->second->GetChildren(); } + bool SHSceneGraph::IsActiveInHierarchy(EntityID entityID) const noexcept + { + //////////////////////////////////////// + // Error handling + if (!SHEntityManager::IsValidEID(entityID)) + { + SHLOG_ERROR("Entity {} is invalid!", entityID) + return false; + } + + const auto NODE_ITER = entityNodeMap.find(entityID); + if (NODE_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) + return false; + } + //////////////////////////////////////// + + return NODE_ITER->second->IsActive(); + } + /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHSceneNode::SetParent(SHSceneNode* parentNode) noexcept - { - if (parentNode == nullptr) - SHLOG_WARNING("Removing Entity {}'s parent", entityID) - - if (parentNode == parent) - return; - - parent = parentNode; - // Update parent's children - parent->AddChild(this); - } - - void SHSceneGraph::SetParent(EntityID entityID, SHSceneNode* parent) const noexcept + void SHSceneGraph::SetParent(EntityID entityID, SHSceneNode* newParent) noexcept { + //////////////////////////////////////// + // Error Handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) @@ -276,128 +227,109 @@ namespace SHADE SHLOG_WARNING("Entity {} cannot be found in the scene!", entityID) return; } + //////////////////////////////////////// - NODE_ITER->second->SetParent(parent); + const SHSceneGraphChangeParentEvent EVENT_DATA + { + .node = NODE_ITER->second + , .oldParent = NODE_ITER->second->GetParent() + , .newParent = newParent ? newParent : root + }; + + if (newParent == nullptr) + newParent = root; + + ChangeParent(NODE_ITER->second, newParent); + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_CHANGE_PARENT_EVENT); } - void SHSceneGraph::SetParent(EntityID entityID, EntityID parent) const noexcept + void SHSceneGraph::SetParent(EntityID entityID, EntityID newParent) noexcept { + //////////////////////////////////////// + // Error Handling + if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid! Unable to set parent of an invalid entity!", entityID) return; } - if (!SHEntityManager::IsValidEID(parent)) + if (!SHEntityManager::IsValidEID(newParent)) { - SHLOG_ERROR("Parent Entity {} is invalid! Unable to set Entity {}'s parent!", parent, entityID) + SHLOG_ERROR("Parent Entity {} is invalid! Unable to set Entity {}'s parent!", newParent, entityID) return; } auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene! Unable to set parent!", entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to set parent!", entityID) return; } - auto PARENT_ITER = entityNodeMap.find(entityID); + auto PARENT_ITER = entityNodeMap.find(newParent); if (PARENT_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} cannot be found in the scene! Unable to parent to Entity {}", parent, entityID) + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to parent to Entity {}", newParent, entityID) return; } + //////////////////////////////////////// + + const SHSceneGraphChangeParentEvent EVENT_DATA + { + .node = NODE_ITER->second + , .oldParent = NODE_ITER->second->GetParent() + , .newParent = PARENT_ITER->second + }; SHSceneNode* currentNode = NODE_ITER->second; - currentNode->SetParent(PARENT_ITER->second); + ChangeParent(currentNode, PARENT_ITER->second); + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_CHANGE_PARENT_EVENT); } /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHSceneNode::AddChild(SHSceneNode* newChild) noexcept - { - if (newChild == nullptr) - { - SHLOG_WARNING("Attempting to add a non-existent child to an entity!") - return; - } - - children.emplace_back(newChild); - } - - bool SHSceneNode::RemoveChild(EntityID childID) noexcept - { - if (!SHEntityManager::IsValidEID(childID)) - { - SHLOG_ERROR("Entity {} is invalid!", childID) - return false; - } - - SHSceneNode* removedChild = nullptr; - const auto ENTITY_MATCH = [&](SHSceneNode* node) - { - if (node->GetEntityID() == childID) - { - removedChild = node; - return true; - } - - return false; - }; - - children.end() = std::remove_if(children.begin(), children.end(), ENTITY_MATCH); - removedChild->parent = nullptr; - - return removedChild == nullptr; - } - - bool SHSceneNode::RemoveChild(SHSceneNode* childToRemove) noexcept - { - if (childToRemove == nullptr) - { - SHLOG_WARNING("Attempting to remove non-existent child from Entity {}", entityID) - return false; - } - - children.end() = std::remove(children.begin(), children.end(), childToRemove); - childToRemove->parent = nullptr; - - return true; - } - - void SHSceneNode::RemoveAllChildren() noexcept - { - for (const auto child : children) - child->parent = nullptr; - - children.clear(); - } - - SHSceneNode* SHSceneGraph::AddNode(EntityID entityID, SHSceneNode* parent) { + //////////////////////////////////////// + // Error Handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) return nullptr; } + //////////////////////////////////////// if (auto NODE_ITER = entityNodeMap.find(entityID); NODE_ITER != entityNodeMap.end()) { SHLOG_WARNING("Entity {} already exists in the scene!", entityID) return NODE_ITER->second; } - + SHSceneNode* newNode = AllocateNode(entityID); - newNode->SetParent(parent); + + if (parent == nullptr) + { + // Specific handling for root to avoid a warning when removing a non-existent child + newNode->parent = root; + root->children.emplace_back(newNode); + } + else + { + ChangeParent(newNode, parent); + } return newNode; } bool SHSceneGraph::RemoveNode(EntityID entityID) noexcept { + //////////////////////////////////////// + // Error Handling if (!SHEntityManager::IsValidEID(entityID)) { SHLOG_ERROR("Entity {} is invalid!", entityID) @@ -407,15 +339,15 @@ namespace SHADE auto NODE_ITER = entityNodeMap.find(entityID); if (NODE_ITER == entityNodeMap.end()) { - SHLOG_WARNING("Entity {} does not exist in the scene!", entityID) + SHLOG_ERROR("Entity {} does not exist in the scene!", entityID) return false; } + //////////////////////////////////////// // Remove reference of current node from parent SHSceneNode* currentNode = NODE_ITER->second; - SHSceneNode* parent = currentNode->GetParent(); - if (parent != nullptr) - parent->RemoveChild(currentNode); + if (currentNode->parent != nullptr) + RemoveChild(currentNode->parent, currentNode); ReleaseNode(currentNode); return true; @@ -424,9 +356,8 @@ namespace SHADE bool SHSceneGraph::RemoveNode(SHSceneNode* nodeToRemove) noexcept { // Remove reference of current node from parent - SHSceneNode* parent = nodeToRemove->GetParent(); - if (parent != nullptr) - parent->RemoveChild(nodeToRemove); + if (nodeToRemove->parent != nullptr) + RemoveChild(nodeToRemove->parent, nodeToRemove); ReleaseNode(nodeToRemove); return true; @@ -438,6 +369,96 @@ namespace SHADE ReleaseNode(node); } + bool SHSceneGraph::IsChildOf(EntityID entityID, SHSceneNode* targetNode) noexcept + { + //////////////////////////////////////// + // Error Handling + + if (!SHEntityManager::IsValidEID(entityID)) + { + SHLOG_ERROR("Entity {} is invalid!", entityID) + return false; + } + + auto NODE_ITER = entityNodeMap.find(entityID); + if (NODE_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to check child!", entityID) + return false; + } + + //////////////////////////////////////// + + // Handle self-checks + if (NODE_ITER->second == targetNode) + { + SHLOG_WARNING("Entity {} cannot be a child of itself!", entityID) + return false; + } + + // Search for a matching target until the root + const SHSceneNode* CURRENT_TARGET = NODE_ITER->second->parent; + while (CURRENT_TARGET != root) + { + if (CURRENT_TARGET == targetNode) + return true; + + CURRENT_TARGET = CURRENT_TARGET->parent; + } + + return false; + } + + bool SHSceneGraph::IsChildOf(EntityID entityID, EntityID targetID) noexcept + { + //////////////////////////////////////// + // Error Handling + + if (!SHEntityManager::IsValidEID(entityID)) + { + SHLOG_ERROR("Entity {} is invalid!", entityID) + return false; + } + + if (!SHEntityManager::IsValidEID(targetID)) + { + SHLOG_ERROR("Entity {} is invalid!", targetID) + return false; + } + + auto NODE_ITER = entityNodeMap.find(entityID); + if (NODE_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to check child!", entityID) + return false; + } + + auto TARGET_ITER = entityNodeMap.find(targetID); + if (TARGET_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene! Unable to check child!", targetID) + return false; + } + + //////////////////////////////////////// + + const SHSceneNode* CURRENT_TARGET = NODE_ITER->second->parent; + while (CURRENT_TARGET != root) + { + if (CURRENT_TARGET == TARGET_ITER->second) + return true; + + CURRENT_TARGET = CURRENT_TARGET->parent; + } + + return false; + } + + void SHSceneGraph::Traverse (const UnaryFunction& function) const + { + TraverseAndInvokeFunction(root, function); + } + /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -445,11 +466,6 @@ namespace SHADE SHSceneNode* SHSceneGraph::AllocateNode(EntityID entityID) { SHSceneNode* newNode = new SHSceneNode{entityID}; - - #ifdef _DEBUG - SHLOG_INFO("Allocated a new Scene Node for Entity {}!", entityID) - #endif - entityNodeMap.emplace(entityID, newNode); return newNode; } @@ -459,17 +475,88 @@ namespace SHADE SHASSERT(node != nullptr, "Attempting to release Invalid Node!") // Remove parent's reference to this node if there is a parent - if (node->GetParent() != nullptr) - node->GetParent()->RemoveChild(node); + if (node->parent != nullptr) + RemoveChild(node->parent, node); // Remove child's references to this node. Children end up as floating nodes. for (auto* child : node->GetChildren()) { - child->SetParent(nullptr); + ChangeParent(child, nullptr); } entityNodeMap.erase(node->GetEntityID()); delete node; } + void SHSceneGraph::ChangeParent(SHSceneNode* node, SHSceneNode* newParent) + { + // Handle self assignment + if (node->parent != nullptr && newParent != nullptr && node->parent->entityID == newParent->entityID) + return; + + // Remove child + if (node->parent) + RemoveChild(node->parent, node); + + if (newParent == nullptr) + { + SHLOG_WARNING("Removing Entity {}'s parent", node->entityID) + return; + } + + node->parent = newParent; + // Update parent's children + AddChild(newParent, node); + } + + void SHSceneGraph::AddChild(SHSceneNode* node, SHSceneNode* newChild) + { + SHASSERT(node != nullptr, "Attempting to modify a non-existent scene node!") + SHASSERT(newChild != nullptr, "Attempting to add a non-existent child to a SceneNode!") + + if (newChild->parent) + RemoveChild(newChild->parent, newChild); + + newChild->parent = node; + node->children.emplace_back(newChild); + + const SHSceneGraphAddChildEvent EVENT_DATA + { + .parent = node + , .childAdded = newChild + }; + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_ADD_CHILD_EVENT); + } + + void SHSceneGraph::RemoveChild(SHSceneNode* node, SHSceneNode* childToRemove) + { + SHASSERT(node != nullptr, "Attempting to modify a non-existent scene node!") + SHASSERT(childToRemove != nullptr, "Attempting to remove a non-existent child from a SceneNode!") + + auto childIter = std::find(node->children.begin(), node->children.end(), childToRemove); + if (childIter == node->children.end()) + return; + + childIter = node->children.erase(childIter); + childToRemove->parent = nullptr; + + const SHSceneGraphRemoveChildEvent EVENT_DATA + { + .parent = node + , .childRemoved = childToRemove + }; + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_REMOVE_CHILD_EVENT); + } + + void SHSceneGraph::TraverseAndInvokeFunction(const SHSceneNode* node, const UnaryFunction& function) + { + for (auto* child : node->children) + { + function(child); + TraverseAndInvokeFunction(child, function); + } + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.h b/SHADE_Engine/src/Scene/SHSceneGraph.h index 20830065..5747be7b 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.h +++ b/SHADE_Engine/src/Scene/SHSceneGraph.h @@ -1,7 +1,7 @@ /**************************************************************************************** * \file SHSceneGraph.h * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Scene Graph & Scene Nodes. + * \brief Interface for a Scene Graph. * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent @@ -14,6 +14,9 @@ // Project Headers #include "ECS_Base/Entity/SHEntity.h" +#include "SH_API.h" +#include "SHSceneNode.h" +#include "SHSceneGraphEvents.h" namespace SHADE { @@ -21,61 +24,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHSceneNode - { - public: - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - bool isActive; - - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - ~SHSceneNode () = default; - - SHSceneNode (EntityID eid, SHSceneNode* parent = nullptr) noexcept; - SHSceneNode (const SHSceneNode& rhs) noexcept; - SHSceneNode (SHSceneNode&& rhs) noexcept; - SHSceneNode& operator= (const SHSceneNode& rhs) noexcept; - SHSceneNode& operator= (SHSceneNode&& rhs) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] EntityID GetEntityID () const noexcept { return entityID ;} - [[nodiscard]] SHSceneNode* GetParent () const noexcept { return parent; } - [[nodiscard]] std::vector& GetChildren () noexcept { return children; } - - [[nodiscard]] SHSceneNode* GetChild (EntityID childID) const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetParent (SHSceneNode* parentNode) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void AddChild (SHSceneNode* newChild) noexcept; - - bool RemoveChild (EntityID childID) noexcept; - bool RemoveChild (SHSceneNode* childToRemove) noexcept; - - void RemoveAllChildren () noexcept; - - private: - EntityID entityID; - SHSceneNode* parent; - std::vector children; - }; - - class SHSceneGraph + class SH_API SHSceneGraph { public: /*---------------------------------------------------------------------------------*/ @@ -83,45 +32,54 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ using EntityNodeMap = std::unordered_map; + using UnaryFunction = std::function; + /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHSceneGraph () noexcept; - ~SHSceneGraph () noexcept; + SHSceneGraph () noexcept; + ~SHSceneGraph () noexcept; - SHSceneGraph (const SHSceneGraph&) = delete; - SHSceneGraph (SHSceneGraph&&) = delete; - SHSceneGraph& operator= (const SHSceneGraph&) = delete; - SHSceneGraph& operator= (SHSceneGraph&&) = delete; + SHSceneGraph (const SHSceneGraph&) = delete; + SHSceneGraph (SHSceneGraph&&) = delete; + SHSceneGraph& operator= (const SHSceneGraph&) = delete; + SHSceneGraph& operator= (SHSceneGraph&&) = delete; /*---------------------------------------------------------------------------------*/ /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHSceneNode* GetRoot () const noexcept; - [[nodiscard]] SHSceneNode* GetNode (EntityID entityID) const noexcept; - [[nodiscard]] SHSceneNode* GetParent (EntityID entityID) const noexcept; - [[nodiscard]] SHSceneNode* GetChild (EntityID entityID, SHSceneNode* childNode) const noexcept; - [[nodiscard]] SHSceneNode* GetChild (EntityID entityID, EntityID childEntityID) const noexcept; - [[nodiscard]] const std::vector& GetChildren (EntityID entityID) const noexcept; + [[nodiscard]] const SHSceneNode* GetRoot () const noexcept; + [[nodiscard]] SHSceneNode* GetNode (EntityID entityID) const noexcept; + [[nodiscard]] SHSceneNode* GetParent (EntityID entityID) const noexcept; + [[nodiscard]] SHSceneNode* GetChild (EntityID entityID, SHSceneNode* childNode) const noexcept; + [[nodiscard]] SHSceneNode* GetChild (EntityID entityID, EntityID childEntityID) const noexcept; + [[nodiscard]] const std::vector& GetChildren (EntityID entityID) const noexcept; + + [[nodiscard]] bool IsActiveInHierarchy (EntityID entityID) const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetParent (EntityID entityID, SHSceneNode* parent) const noexcept; - void SetParent (EntityID entityID, EntityID parent) const noexcept; + void SetParent (EntityID entityID, SHSceneNode* newParent) noexcept; + void SetParent (EntityID entityID, EntityID newParent) noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - SHSceneNode* AddNode (EntityID entityID, SHSceneNode* parent = nullptr); - bool RemoveNode (EntityID entityID) noexcept; - bool RemoveNode (SHSceneNode* nodeToRemove) noexcept; - void Reset () noexcept; + SHSceneNode* AddNode (EntityID entityID, SHSceneNode* parent = nullptr); + bool RemoveNode (EntityID entityID) noexcept; + bool RemoveNode (SHSceneNode* nodeToRemove) noexcept; + void Reset () noexcept; + + bool IsChildOf (EntityID entityID, SHSceneNode* targetNode) noexcept; + bool IsChildOf (EntityID entityID, EntityID targetID) noexcept; + + void Traverse (const UnaryFunction& function) const; private: /*---------------------------------------------------------------------------------*/ @@ -135,9 +93,14 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ - SHSceneNode* AllocateNode (EntityID entityID); - void ReleaseNode (SHSceneNode* node) noexcept; + SHSceneNode* AllocateNode (EntityID entityID); + void ReleaseNode (SHSceneNode* node) noexcept; + + void ChangeParent (SHSceneNode* node, SHSceneNode* newParent); + void AddChild (SHSceneNode* node, SHSceneNode* newChild); + void RemoveChild (SHSceneNode* node, SHSceneNode* childToRemove); + + static void TraverseAndInvokeFunction (const SHSceneNode* node, const UnaryFunction& function); }; - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneGraphEvents.h b/SHADE_Engine/src/Scene/SHSceneGraphEvents.h new file mode 100644 index 00000000..ccdf06be --- /dev/null +++ b/SHADE_Engine/src/Scene/SHSceneGraphEvents.h @@ -0,0 +1,41 @@ +/**************************************************************************************** + * \file SHSceneGraphEvents.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for Scene Graph Events. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "SHSceneNode.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Event Data Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SHSceneGraphChangeParentEvent + { + SHSceneNode* node; + SHSceneNode* oldParent; + SHSceneNode* newParent; + }; + + struct SHSceneGraphAddChildEvent + { + SHSceneNode* parent; + SHSceneNode* childAdded; + }; + + struct SHSceneGraphRemoveChildEvent + { + SHSceneNode* parent; + SHSceneNode* childRemoved; + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneManager.cpp b/SHADE_Engine/src/Scene/SHSceneManager.cpp index a60df922..be9c7755 100644 --- a/SHADE_Engine/src/Scene/SHSceneManager.cpp +++ b/SHADE_Engine/src/Scene/SHSceneManager.cpp @@ -10,11 +10,11 @@ *********************************************************************/ #include "SHpch.h" #include "SHSceneManager.h" -#include "ECS_Base/System/SHComponentManager.h" +#include "ECS_Base/Managers/SHComponentManager.h" //#include "Input/SHInputManager.h" //#include "Rendering/Window/SHRenderingWindow.h" -#include "ECS_Base/System/SHEntityManager.h" -#include "ECS_Base/System/SHSystemManager.h" +#include "ECS_Base/Managers/SHEntityManager.h" +#include "ECS_Base/Managers/SHSystemManager.h" //#include "FRC/SHFrameRateController.h" //#include "ECS_Base/System/SHApplication.h" @@ -29,7 +29,7 @@ namespace SHADE std::string SHSceneManager::newSceneName{}; uint32_t SHSceneManager::currentSceneID = UINT32_MAX; uint32_t SHSceneManager::nextSceneID = UINT32_MAX; - + AssetID SHSceneManager::currentSceneAssetID{}; SHScene* SHSceneManager::currentScene = nullptr; //SHScene* SHSceneManager::nextScene = nullptr; @@ -37,6 +37,12 @@ namespace SHADE std::function SHSceneManager::newScene = []() {}; //void (*SHSceneManager::prevSceneCreate)() = []() {}; + + SHSceneGraph& SHSceneManager::GetCurrentSceneGraph() noexcept + { + return currentScene->GetSceneGraph(); + } + void SHSceneManager::UpdateSceneManager() noexcept { if (sceneChanged == false) @@ -101,16 +107,18 @@ namespace SHADE } } - void SHSceneManager::RestartScene(std::string const& sceneName) noexcept + void SHSceneManager::RestartScene(AssetID const& assetID ) noexcept { - if (currentScene->sceneName != sceneName) + if (currentScene->sceneAssetID != assetID) { - cleanReload = true; - newSceneName = sceneName; + //cleanReload = true; + cleanReload = false; + //newSceneName = sceneName; } else cleanReload = false; + currentScene->sceneAssetID = assetID; nextSceneID = currentSceneID; sceneChanged = true; } @@ -145,4 +153,20 @@ namespace SHADE { return currentScene->sceneName; } + + void SHSceneManager::SetCurrentSceneName(std::string const& sceneName) noexcept + { + currentScene->sceneName = sceneName; + } + + AssetID SHSceneManager::GetCurrentSceneAssetID() noexcept + { + return currentScene->sceneAssetID; + } + + void SHSceneManager::SetCurrentSceneAssetID(AssetID const& newAssetID) + { + currentScene->sceneAssetID = newAssetID; + currentSceneAssetID = newAssetID; + } } diff --git a/SHADE_Engine/src/Scene/SHSceneManager.h b/SHADE_Engine/src/Scene/SHSceneManager.h index 4139309d..8f03b352 100644 --- a/SHADE_Engine/src/Scene/SHSceneManager.h +++ b/SHADE_Engine/src/Scene/SHSceneManager.h @@ -13,14 +13,20 @@ #define SH_SCENE_MANAGER_H -#include "ECS_Base/General/SHFamily.h" + #include "SHScene.h" #include +//Project Headers +#include "SH_API.h" +#include "ECS_Base/General/SHFamily.h" +#include "Assets/SHAssetMacros.h" +#include "ECS_Base/Managers/SHComponentManager.h" + namespace SHADE { - class SHSceneManager + class SH_API SHSceneManager { private: //boolean to check if the scene has been changed @@ -43,6 +49,9 @@ namespace SHADE //pointer to the current scene static SHScene* currentScene; + //Scene AssetID of the current scene + static AssetID currentSceneAssetID; + //Used in reloading scene. static std::string newSceneName; @@ -57,6 +66,15 @@ namespace SHADE //boolean to check if the programme has been terminated. static bool quit; + /******************************************************************** + * \brief + * Get the scene graph of the current scene. + * \return + * A reference to the scene graph of the current active scene. + ********************************************************************/ + static SHSceneGraph& GetCurrentSceneGraph() noexcept; + + /*!************************************************************************* * \brief * Initialize scene manager and loads a default scene @@ -67,11 +85,12 @@ namespace SHADE * None. ***************************************************************************/ template - static std::enable_if_t, void> InitSceneManager(std::string const& sceneName) noexcept + static std::enable_if_t, void> InitSceneManager(AssetID const& sceneAssetID) noexcept { //prevSceneCreate = newScene; - newScene = [sceneName]() { currentScene = new T(); currentScene->sceneName = sceneName; }; - nextSceneID = SHFamilyID::template GetID(); + newScene = [sceneAssetID]() { currentScene = new T(); currentScene->sceneAssetID = sceneAssetID; }; + //nextSceneID = SHFamilyID::GetID(); + nextSceneID = 0; sceneChanged = true; } @@ -85,19 +104,69 @@ namespace SHADE * None. ***************************************************************************/ template - static std::enable_if_t, void> ChangeScene(std::string const& sceneName) noexcept + static std::enable_if_t, void> ChangeScene(AssetID const& sceneAssetID) noexcept { //check if this new Scene is current Scene (Use RestartScene instead) - if (currentSceneID == SHFamilyID::template GetID()) + if (currentSceneID == SHFamilyID::GetID()) { return; } //prevSceneCreate = newScene; - newScene = [sceneName]() { currentScene = new T(); currentScene->sceneName; }; - nextSceneID = SHFamilyID::template GetID(); + newScene = [sceneAssetID]() { currentScene = new T(); currentScene->sceneAssetID = sceneAssetID; }; + nextSceneID = SHFamilyID::GetID(); sceneChanged = true; } + /******************************************************************** + * \brief + * Check if the Entity's scene node is active and all the + * components specified are active. + * This does not check if the entity HasComponent. Please use + * CheckNodeAndHasComponentActive for that. + * \param eid + * EntityID of the entity to check for. + * \return + * true if scene node is active and all the components specified + * are also active. + ********************************************************************/ + template + static std::enable_if_t<(... && std::is_base_of_v), bool> CheckNodeAndComponentsActive(EntityID eid) + { + return CheckNodeActive(eid) && (... && SHComponentManager::GetComponent_s(eid)->isActive); + } + + /******************************************************************** + * \brief + * Check if the Entity's scene node is active and all the + * components specified are active. + * This also checks to verify that the entity has such components. + * \param eid + * EntityID of the entity to check for. + * \return + * true if scene node is active and all the components specified + * are also active. + ********************************************************************/ + template + static std::enable_if_t<(... && std::is_base_of_v), bool> CheckNodeAndHasComponentsActive(EntityID eid) + { + return CheckNodeActive(eid) + && (... && SHComponentManager::HasComponent(eid)) + && (... && SHComponentManager::GetComponent_s(eid)->isActive); + } + + /******************************************************************** + * \brief + * Check if Scene node is active. + * \param eid + * EntityID of the entity to check for. + * \return + * true if scene node is active + ********************************************************************/ + static bool CheckNodeActive(EntityID eid) + { + return GetCurrentSceneGraph().IsActiveInHierarchy(eid); + } + /*!************************************************************************* * \brief @@ -123,11 +192,11 @@ namespace SHADE * Restarts current scene. Only Scene::Init() and Scene::Free() * Scene::Load() and Scene::Unload() will not be called. * Edit: allows for RestartScene to restart the scene with a different - * scene name. - * If a sceneName is different from the current one, Load and Unload will + * scene asset ID. + * If a scene asset id is different from the current one, Load and Unload will * run. ***************************************************************************/ - static void RestartScene(std::string const& sceneName ) noexcept; + static void RestartScene(AssetID const& assetID ) noexcept; /*!************************************************************************* * \brief @@ -150,7 +219,10 @@ namespace SHADE static void Exit() noexcept; static std::string GetSceneName() noexcept; - + static void SetCurrentSceneName(std::string const& sceneName) noexcept; + static AssetID GetCurrentSceneAssetID() noexcept; + //Only if scene doesn't exist, and scene asset id needs to be updated to the new one + static void SetCurrentSceneAssetID(AssetID const& newAssetID); }; diff --git a/SHADE_Engine/src/Scene/SHSceneNode.cpp b/SHADE_Engine/src/Scene/SHSceneNode.cpp new file mode 100644 index 00000000..8dac20bd --- /dev/null +++ b/SHADE_Engine/src/Scene/SHSceneNode.cpp @@ -0,0 +1,143 @@ +/**************************************************************************************** + * \file SHSceneNode.c[[ + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Scene Node. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHSceneNode.h" + +// Project Headers +#include "ECS_Base/Managers/SHEntityManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSceneNode::SHSceneNode(EntityID eid, SHSceneNode* parent) noexcept + : active { true } + , entityID { eid } + , parent { parent } + {} + + + SHSceneNode::SHSceneNode(const SHSceneNode& rhs) noexcept + : active { rhs.active } + , entityID { rhs.entityID } + , parent { rhs.parent } + { + std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); + } + + SHSceneNode::SHSceneNode(SHSceneNode&& rhs) noexcept + : active { rhs.active } + , entityID { rhs.entityID } + , parent { rhs.parent } + { + std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); + } + + SHSceneNode& SHSceneNode::operator=(const SHSceneNode& rhs) noexcept + { + if (this == &rhs) + return *this; + + active = rhs.active; + entityID = rhs.entityID; + parent = rhs.parent; + + children.clear(); + std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); + + return *this; + } + + SHSceneNode& SHSceneNode::operator=(SHSceneNode&& rhs) noexcept + { + active = rhs.active; + entityID = rhs.entityID; + parent = rhs.parent; + + children.clear(); + std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHSceneNode::IsActive() const noexcept + { + return active; + } + + EntityID SHSceneNode::GetEntityID() const noexcept + { + return entityID; + } + + SHSceneNode* SHSceneNode::GetParent() const noexcept + { + return parent; + } + + const std::vector& SHSceneNode::GetChildren() const noexcept + { + return children; + } + + SHSceneNode* SHSceneNode::GetChild(EntityID childID) const noexcept + { + //////////////////////////////////////// + // Error handling + if (!SHEntityManager::IsValidEID(childID)) + { + SHLOG_ERROR("Child Entity {} is invalid! Unable to get child from Entity {}", childID, entityID) + return nullptr; + } + + if (children.empty()) + { + SHLOG_WARNING("Entity {} has no children!", entityID) + return nullptr; + } + //////////////////////////////////////// + + // Find child + const auto ENTITY_MATCH = [&](const SHSceneNode* node) { return node->GetEntityID() == childID; }; + + const auto CHILD_ITER = std::ranges::find_if(children.begin(), children.end(),ENTITY_MATCH); + if (CHILD_ITER == children.end()) + { + SHLOG_WARNING("Entity {} is not a child of Entity {}! Unable to retrieve child node!", childID, entityID) + return nullptr; + } + + return *CHILD_ITER; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHSceneNode::SetActive(bool newActiveState) noexcept + { + active = newActiveState; + + for (auto* child : children) + { + child->SetActive(newActiveState); + } + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneNode.h b/SHADE_Engine/src/Scene/SHSceneNode.h new file mode 100644 index 00000000..87bd8d0b --- /dev/null +++ b/SHADE_Engine/src/Scene/SHSceneNode.h @@ -0,0 +1,82 @@ +/**************************************************************************************** + * \file SHSceneNode.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Scene Node. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/Entity/SHEntity.h" +#include "SH_API.h" +#include "SHSceneGraph.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + class SHSceneGraph; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHSceneNode + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHSceneGraph; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + ~SHSceneNode () = default; + + SHSceneNode (EntityID eid, SHSceneNode* parent = nullptr) noexcept; + SHSceneNode (const SHSceneNode& rhs) noexcept; + SHSceneNode (SHSceneNode&& rhs) noexcept; + SHSceneNode& operator= (const SHSceneNode& rhs) noexcept; + SHSceneNode& operator= (SHSceneNode&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool IsActive () const noexcept; + [[nodiscard]] EntityID GetEntityID () const noexcept; + [[nodiscard]] SHSceneNode* GetParent () const noexcept; + [[nodiscard]] const std::vector& GetChildren () const noexcept; + + [[nodiscard]] SHSceneNode* GetChild (EntityID childID) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetActive (bool newActiveState) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + bool active; + EntityID entityID; + SHSceneNode* parent; + std::vector children; + }; +} // namespace SHADE + diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp b/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp new file mode 100644 index 00000000..6226949e --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.cpp @@ -0,0 +1,198 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the definition of the SHDotNetRuntime class. + Implementation of code to set up code for SHDotNetRuntime is based on the + following repository: + https://github.com/mjrousos/SampleCoreCLRHost + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//**************************************************************************************/ +// Precompiled Header +#include +// Primary Header +#include "SHDotNetRuntime.h" +// Standard Library +#include +// External Dependencies +#include // PathRemoveFileSpecA +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHDotNetRuntime::SHDotNetRuntime(bool autoInit) + { + if (autoInit) + { + Init(); + } + } + + SHDotNetRuntime::~SHDotNetRuntime() + { + if (IsLoaded()) + { + try + { + Exit(); + } + catch (std::runtime_error& e) + { + SHLOG_ERROR(e.what()); + } + } + } + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void SHDotNetRuntime::Init() + { + // State checking, in case there was an unload before, we must ensure that the state is valid + if (initialised) + throw std::runtime_error("[DotNetRuntime] Failed to initialise as it was already initialised or was deinitialised into an invalid state."); + + // Get the current executable directory + std::string runtimePath(MAX_PATH, '\0'); + GetModuleFileNameA(nullptr, runtimePath.data(), MAX_PATH); + PathRemoveFileSpecA(runtimePath.data()); + // Since PathRemoveFileSpecA() removes from data(), the size is not updated, so we must manually update it + runtimePath.resize(std::strlen(runtimePath.data())); + + // Do not need to load the library if it was previously loaded + if (coreClr == nullptr) + { + // Construct the CoreCLR path + std::string coreClrPath(runtimePath); // Works + coreClrPath += "\\coreclr.dll"; + + // Load the CoreCLR DLL + coreClr = LoadLibraryExA(coreClrPath.c_str(), nullptr, 0); + if (!coreClr) + { + std::ostringstream oss; + oss << "[DotNetRuntime] Error #" << GetLastError() << " Failed to load CoreCLR from \"" << coreClrPath << "\"\n"; + throw std::runtime_error(oss.str()); + } + + // Step 2: Get CoreCLR hosting functions + initializeCoreClr = getCoreClrFunctionPtr("coreclr_initialize"); + createManagedDelegate = getCoreClrFunctionPtr("coreclr_create_delegate"); + shutdownCoreClr = getCoreClrFunctionPtr("coreclr_shutdown"); + } + + // Step 3: Construct AppDomain properties used when starting the runtime + // Construct the trusted platform assemblies (TPA) list + // This is the list of assemblies that .NET Core can load as + // trusted system assemblies (similar to the .NET Framework GAC). + // For this host (as with most), assemblies next to CoreCLR will + // be included in the TPA list + std::string tpaList = buildTpaList(runtimePath); + + // Define CoreCLR properties + std::array propertyKeys = + { + "TRUSTED_PLATFORM_ASSEMBLIES", // Trusted assemblies (like the GAC) + "APP_PATHS", // Directories to probe for application assemblies + // "APP_NI_PATHS", // Directories to probe for application native images (not used in this sample) + // "NATIVE_DLL_SEARCH_DIRECTORIES", // Directories to probe for native dlls (not used in this sample) + }; + std::array propertyValues = + { + tpaList.c_str(), + runtimePath.c_str() + }; + + // Step 4: Start the CoreCLR runtime + int result = initializeCoreClr + ( + runtimePath.c_str(), // AppDomain base path + "SHADEHost", // AppDomain friendly name + static_cast(propertyKeys.size()), // Property count + propertyKeys.data(), // Property names + propertyValues.data(), // Property values + &hostHandle, // Host handle + &domainId // AppDomain ID + ); + + // Check if intiialization of CoreCLR failed + throwIfFailed("[DotNetRuntime] Failed to initialize CoreCLR.", result); + + initialised = true; + SHLOG_INFO("[DotNetRuntime] Successfully loaded the .NET 5.0 Runtime."); + } + + void SHDotNetRuntime::Exit() + { + // State checking, in case there was an unload before, we must ensure that the state is valid + if (!initialised) + throw std::runtime_error("[DotNetRuntime] Failed to deinitialise as it was not initialised before."); + + // Shutdown CoreCLR + int result = shutdownCoreClr(hostHandle, domainId); + throwIfFailed("[DotNetRuntime] Failed to shut down CoreCLR.", result); + + // Unset pointers + hostHandle = nullptr; + domainId = 0; + initialised = false; + + SHLOG_INFO("[DotNetRuntime] Successfully shut down the .NET 5.0 Runtime."); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + std::string SHDotNetRuntime::buildTpaList(const std::string& directory) + { + // Constants + static const std::string SEARCH_PATH = directory + "\\*.dll"; + static constexpr char PATH_DELIMITER = ';'; + + // Create a osstream object to compile the string + std::ostringstream tpaList; + + // Search the current directory for the TPAs (.DLLs) + WIN32_FIND_DATAA findData; + HANDLE fileHandle = FindFirstFileA(SEARCH_PATH.c_str(), &findData); + if (fileHandle != INVALID_HANDLE_VALUE) + { + do + { + // Append the assembly to the list + tpaList << directory << '\\' << findData.cFileName << PATH_DELIMITER; + + // Note that the CLR does not guarantee which assembly will be loaded if an assembly + // is in the TPA list multiple times (perhaps from different paths or perhaps with different NI/NI.dll + // extensions. Therefore, a real host should probably add items to the list in priority order and only + // add a file if it's not already present on the list. + // + // For this simple sample, though, and because we're only loading TPA assemblies from a single path, + // and have no native images, we can ignore that complication. + } + while (FindNextFileA(fileHandle, &findData)); + FindClose(fileHandle); + } + + return tpaList.str(); + } + + void SHDotNetRuntime::throwIfFailed(const std::string& errMsg, int resultCode) + { + if (resultCode < 0) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(8) + << errMsg + << " Error 0x" << resultCode << "\n"; + throw std::runtime_error(oss.str()); + } + } +} diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.h b/SHADE_Engine/src/Scripting/SHDotNetRuntime.h new file mode 100644 index 00000000..efb9e54b --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.h @@ -0,0 +1,207 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the interface of a wrapper class for interfacing with the + .NET 5 Runtime. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//**************************************************************************************/ +#pragma once + +// Standard Libraries +#include // std::setfill, std::setw +#include // std::runtime_error +#include // std::string +#include // std::ostringstream +// External Dependencies +#include // HMODULE +#include // coreclr_* + +namespace SHADE +{ + /*************************************************************************************/ + /*! + + class SHDotNetRuntime + + \brief + Class that encapsulates the state of the .NET Core Runtime lifecycle. + + */ + /*************************************************************************************/ + + class SHDotNetRuntime + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + + \brief + Default constructor that immediately initializes the CoreCLR. + + \param autoInit + If true, loads the CoreCLR by calling Init(). + + */ + /***********************************************************************************/ + SHDotNetRuntime(bool autoInit = true); + /***********************************************************************************/ + /*! + + \brief + Destructor that unloads the CoreCLR if it has not been unloaded yet. + + */ + /***********************************************************************************/ + ~SHDotNetRuntime(); + + // Disallow copy and moving + SHDotNetRuntime(const SHDotNetRuntime&) = delete; + SHDotNetRuntime(SHDotNetRuntime&&) = delete; + + /*----------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*----------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + + \brief + Loads the CoreCLR and grabs pointers to bootstrapping functions and kickstarts the + CoreCLR. + + \throws std::runtime_error + Thrown if there is a failure in loading the CLR and related functions. + + */ + /***********************************************************************************/ + void Init(); + /***********************************************************************************/ + /*! + + \brief + Unloads the CoreCLR. + + \throws std::runtime_error + Thrown if there is a failure in unloading the CLR. + + */ + /***********************************************************************************/ + void Exit(); + + /*----------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*----------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + + \brief + Checks if the DotNetRuntime has successfully been initialised. + + \return + True if this DotNetRuntime has been initialised. + + */ + /***********************************************************************************/ + inline bool IsLoaded() const noexcept { return initialised; } + + /***********************************************************************************/ + /*! + + \brief + Retrieves a function pointer from the a CLR assembly based on the specified + assembly, type and function names. + + \tparam FunctionType + Type of the function pointer that the specified function name will provide. + \param assemblyName + Name of the CoreCLR assembly that contains the function. + \param typeName + Name of the CoreCLR type in the assembly that contains the function. Nested types + are separated by a period(.). + \param functionName + Name of the CoreCLR function to get a pointer to. + \return + Pointer to the function in the assembly that was specified. + + */ + /***********************************************************************************/ + template + FunctionType GetFunctionPtr(const std::string_view& assemblyName, + const std::string_view& typeName, + const std::string_view& functionName) const; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + bool initialised = false; + // References to CoreCLR key components + HMODULE coreClr = nullptr; + void* hostHandle = nullptr; + unsigned int domainId = 0; + // Function Pointers to CoreCLR functions + coreclr_initialize_ptr initializeCoreClr = nullptr; + coreclr_create_delegate_ptr createManagedDelegate = nullptr; + coreclr_shutdown_ptr shutdownCoreClr = nullptr; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + + \brief + Retrieves a function pointer from the CoreCLR based on the specified + function name. + + \tparam FunctionType + Type of the function pointer that the specified function name will provide. + \param functionName + Name of the CoreCLR function to get a pointer to. + \return + Pointer to the function in the CoreCLR that was specified. + + */ + /***********************************************************************************/ + template + FunctionType getCoreClrFunctionPtr(const std::string& functionName); + /***********************************************************************************/ + /*! + + \brief + Compiles a semicolon separated string of trusted platform assemblies by + searching the specified directory. + + \param directory + Path to the directory where the trusted platform assemblies reside. + \return + Semicolon separated string of trusted platform assemblies. + + */ + /***********************************************************************************/ + static std::string buildTpaList(const std::string& directory); + /***********************************************************************************/ + /*! + + \brief + Takes in a Win32 result code and throws an exception it if there is an error. + + \param errMsg + Error message to display if the resultCode is a failure code. + \param resultCode + Result code of the function to check. + + */ + /***********************************************************************************/ + static void throwIfFailed(const std::string& errMsg, int resultCode); + }; +} + +#include "SHDotNetRuntime.hpp" diff --git a/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp b/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp new file mode 100644 index 00000000..ae8f28e5 --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHDotNetRuntime.hpp @@ -0,0 +1,61 @@ +/*************************************************************************************//*! +\file SHDotNetRuntime.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 2, 2021 +\brief Contains the implementation of the template functions of the + DotNetRuntime class. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//**************************************************************************************/ +#pragma once + +// Primary Include +#include "SHDotNetRuntime.h" + +namespace SHADE +{ + template + FunctionType SHDotNetRuntime::GetFunctionPtr(const std::string_view & assemblyName, + const std::string_view & typeName, + const std::string_view & functionName) const + { + FunctionType managedDelegate = nullptr; + int result = createManagedDelegate + ( + hostHandle, + domainId, + assemblyName.data(), + typeName.data(), + functionName.data(), + reinterpret_cast(&managedDelegate) + ); + + // Check if it failed + if (result < 0) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(8) + << "[DotNetRuntime] Failed to get pointer to function \"" + << typeName << "." << functionName << "\" in assembly (" << assemblyName << "). " + << "Error 0x" << result << "\n"; + throw std::runtime_error(oss.str()); + } + + return managedDelegate; + } + template + FunctionType SHDotNetRuntime::getCoreClrFunctionPtr(const std::string& functionName) + { + FunctionType fPtr = reinterpret_cast(GetProcAddress(coreClr, functionName.c_str())); + if (!fPtr) + { + std::ostringstream oss; + oss << "[DotNetRuntime] Unable to get pointer to function: \"" << functionName << "\""; + throw std::runtime_error(oss.str()); + } + return fPtr; + } +} diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp new file mode 100644 index 00000000..05a144e3 --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -0,0 +1,655 @@ +/************************************************************************************//*! +\file SHScriptEngine.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2021 +\brief Contains the implementation for ScriptEngine class. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include +// Primary Header +#include "SHScriptEngine.h" +// Standard Library +#include // std::fstream +#include // std::filesystem::canonical, std::filesystem::remove +#include // std::shared_ptr +#include // std::this_thread::sleep_for +// Project Headers +#include "Tools/SHLogger.h" +#include "Tools/SHStringUtils.h" +#include "ECS_Base/Events/SHEntityDestroyedEvent.h" +#include "Events/SHEvent.h" +#include "Events/SHEventReceiver.h" +#include "Events/SHEventManager.hpp" +#include "Physics/System/SHPhysicsSystem.h" +#include "Physics/SHPhysicsEvents.h" +#include "Scene/SHSceneGraphEvents.h" + +#include "Assets/SHAssetMacros.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Definitions */ + /*----------------------------------------------------------------------------------*/ + const std::string SHScriptEngine::DEFAULT_CSHARP_NAMESPACE = std::string("SHADE"); + const std::string SHScriptEngine::CSPROJ_DIR = std::string(ASSET_ROOT) + "/Scripts"; + const std::string SHScriptEngine::CSPROJ_PATH = std::string(CSPROJ_DIR) + "\\SHADE_Scripting.csproj"; + + /*-----------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHScriptEngine::Init() + { + // Do not allow initialization if already initialised + if (dotNet.IsLoaded()) + { + SHLOG_ERROR("[ScriptEngine] Attempted to initialise an already loaded DotNetRuntime."); + return; + } + + dotNet.Init(); + + // Load all the helpers + loadFunctions(); + + // Generate script assembly if it hasn't been before + if (!fileExists(std::string(MANAGED_SCRIPT_LIB_NAME) + ".dll")) + { + BuildScriptAssembly(); + } + + // Initialise the CSharp Engine + csEngineInit(); + + // Register all events + registerEvents(); + } + void SHScriptEngine::UnloadScriptAssembly() + { + csEngineUnloadScripts(); + } + void SHScriptEngine::LoadScriptAssembly() + { + csEngineLoadScripts(); + } + void SHScriptEngine::ReloadScriptAssembly() + { + csEngineReloadScripts(); + } + void SHScriptEngine::ExecuteFixedUpdates() + { + csScriptsExecuteFixedUpdate(); + } + void SHScriptEngine::ExecuteCollisionFunctions() + { + csScriptsExecutePhysicsEvents(); + } + void SHScriptEngine::Exit() + { + // Do not allow deinitialization if not initialised + if (!dotNet.IsLoaded()) + { + SHLOG_ERROR("[ScriptEngine] Attempted to clean up an unloaded DotNetRuntime."); + return; + } + + // Clean up the CSharp Engine + csEngineExit(); + + // Shut down the CLR + dotNet.Exit(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Script Manipulation Functions */ + /*-----------------------------------------------------------------------------------*/ + bool SHScriptEngine::AddScript(EntityID entity, const std::string_view& scriptName) + { + return csScriptsAdd(entity, scriptName.data()); + } + void SHScriptEngine::RemoveAllScripts(EntityID entity) + { + csScriptsRemoveAll(entity); + } + void SHScriptEngine::RemoveAllScriptsImmediately(EntityID entity, bool callOnDestroy) + { + csScriptsRemoveAllImmediately(entity, callOnDestroy); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*---------------------------------------------------------------------------------*/ + bool SHScriptEngine::SerialiseScripts(EntityID entity, YAML::Node& scriptsNode) const + { + // Attempt to serialise the script + if (csScriptsSerialiseYaml(entity, &scriptsNode)) + return true; + + SHLOG_ERROR("[ScriptEngine] Failed to serialise scripts for entity #{}.", entity); + return false; + } + + /*-----------------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*-----------------------------------------------------------------------------------*/ + bool SHScriptEngine::DeserialiseScripts(EntityID entity, const YAML::Node& scriptsNode) const + { + return csScriptsDeserialiseYaml(entity, &scriptsNode); + } + + /*-----------------------------------------------------------------------------------*/ + /* Script Editor Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHScriptEngine::RenderScriptsInInspector(EntityID entity) const + { + csEditorRenderScripts(entity); + } + + void SHScriptEngine::UndoScriptInspectorChanges() const + { + csEditorUndo(); + } + + void SHScriptEngine::RedoScriptInspectorChanges() const + { + csEditorRedo(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Utility Functions */ + /*-----------------------------------------------------------------------------------*/ + bool SHScriptEngine::BuildScriptAssembly(bool debug, bool reload) + { + static const std::string BUILD_LOG_PATH = "../Build.log"; + + // Unload if we need to reload + if (reload) + { + UnloadScriptAssembly(); + } + + // Generate csproj file if it doesn't exist + if (!std::filesystem::exists(CSPROJ_PATH)) + { + GenerateScriptsCsProjFile(CSPROJ_PATH); + } + + // Prepare directory (delete useless files) + deleteFolder(CSPROJ_DIR + "/net5.0"); + deleteFolder(CSPROJ_DIR + "/ref"); + deleteFolder(CSPROJ_DIR + "/obj"); + deleteFolder(CSPROJ_DIR + "/bin"); + + // Attempt to build the assembly + std::ostringstream oss; + oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_INFO(oss.str()); + oss.str(""); + const bool BUILD_SUCCESS = execProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"" + generateBuildCommand(debug) + L" & exit\"" + ) == 0; + if (BUILD_SUCCESS) + { + // Copy to built dll to the working directory and replace + std::filesystem::copy_file("./tmp/SHADE_Scripting.dll", "SHADE_Scripting.dll", std::filesystem::copy_options::overwrite_existing); + + // If debug, we want to copy the PDB so that we can do script debugging + if (debug) + { + std::filesystem::copy_file("./tmp/SHADE_Scripting.pdb", "SHADE_Scripting.pdb", std::filesystem::copy_options::overwrite_existing); + } + + oss << "[ScriptEngine] Successfully built Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_INFO(oss.str()); + } + else + { + oss << "[ScriptEngine] Failed to build Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; + SHLOG_ERROR(oss.str()); + } + + // Clean up built files + deleteFolder("./tmp"); + deleteFolder(CSPROJ_DIR + "/bin"); + using namespace std::chrono_literals; + std::this_thread::sleep_for(50ms); // Not sure why this works but it prevents the folders from respawning + deleteFolder(CSPROJ_DIR + "/obj"); + + // Read the build log and output to the console + dumpBuildLog(BUILD_LOG_PATH); + // Delete the build log file since we no longer need it + deleteFile(BUILD_LOG_PATH); + + // If reloading, we need to load + if (reload) + { + LoadScriptAssembly(); + } + + return BUILD_SUCCESS; + } + + void SHScriptEngine::GenerateScriptsCsProjFile(const std::filesystem::path& path) const + { + // Compute relative path + const std::filesystem::path EXE_DIR = std::filesystem::current_path(); + const std::filesystem::path MANAGED_DLL_DIR = EXE_DIR / "SHADE_Managed.dll"; + const std::filesystem::path CS_DLL_DIR = EXE_DIR / "SHADE_CSharp.dll"; + + // Sample + static std::string_view FILE_CONTENTS_BEGIN = +"\n\ + \n\ + net5.0\n\ + x64\n\ + Release;Debug\n\ + \n\ + \n\ + .\\bin\\Release\n\ + x64\n\ + \n\ + \n\ + .\\bin\\Debug\n\ + x64\n\ + DEBUG;TRACE\n\ + false\n\ + full\n\ + true\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n"; + static std::string_view FILE_CONTENTS_MID = +" \n\ + \n"; + static std::string_view FILE_CONTENTS_END = +" \n\ + \n\ +"; + + // Attempt to create the file + std::ofstream file(path, std::ios::out | std::ios::trunc); + if (!file.is_open()) + throw std::runtime_error("Unable to create CsProj file!"); + + // Fill the file + const std::filesystem::path CSPROJ_DIR = path.parent_path(); + file << FILE_CONTENTS_BEGIN + << " " << std::filesystem::relative(MANAGED_DLL_DIR, CSPROJ_DIR).string() << "\n" + << FILE_CONTENTS_MID + << " " << std::filesystem::relative(CS_DLL_DIR, CSPROJ_DIR).string() << "\n" + << FILE_CONTENTS_END; + + // Close + file.close(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Event Handler Functions */ + /*-----------------------------------------------------------------------------------*/ + SHEventHandle SHScriptEngine::onEntityDestroyed(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + csScriptsRemoveAll(eventData->data->eid); + return eventData->handle; + } + + SHEventHandle SHScriptEngine::onColliderAdded(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + csColliderOnListChanged(eventData->data->entityID); + return eventData->handle; + } + + SHEventHandle SHScriptEngine::onColliderRemoved(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + csColliderOnListChanged(eventData->data->entityID); + return eventData->handle; + } + + SHEventHandle SHScriptEngine::onColliderComponentRemoved(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + if (eventData->data->removedComponentType == ComponentFamily::GetID()) + csColliderOnRemoved(eventData->data->eid); + return eventData->handle; + } + + SHEventHandle SHScriptEngine::onSceneNodeChildrenAdded(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + csSceneNodeChildrenChanged(eventData->data->parent->GetEntityID()); + return eventData->handle; + } + + SHEventHandle SHScriptEngine::onSceneNodeChildrenRemoved(SHEventPtr eventPtr) + { + auto eventData = reinterpret_cast*>(eventPtr.get()); + csSceneNodeChildrenChanged(eventData->data->parent->GetEntityID()); + return eventData->handle; + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHScriptEngine::loadFunctions() + { + std::ostringstream oss; + oss << "[ScriptEngine] Loading \"" << DEFAULT_CSHARP_LIB_NAME << "\" CLR library."; + SHLOG_INFO(oss.str()); + + // Load functions + csEngineInit = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "Init" + ); + csEngineLoadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "LoadScriptAssembly" + ); + csEngineUnloadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "UnloadScriptAssembly" + ); + csEngineReloadScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "ReloadScriptAssembly" + ); + csEngineExit = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".EngineInterface", + "Exit" + ); + csScriptsFrameSetUp = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "FrameSetUp" + ); + csScriptsExecuteFixedUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteFixedUpdate" + ); + csScriptsExecuteUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteUpdate" + ); + csScriptsExecuteLateUpdate = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteLateUpdate" + ); + csScriptsExecuteDrawGizmos = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteOnDrawGizmos" + ); + csScriptsExecutePhysicsEvents = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "ExecuteCollisionFunctions" + ); + csScriptsFrameCleanUp = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "FrameCleanUp" + ); + csScriptsAdd = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "AddScriptViaName" + ); + csScriptsRemoveAll = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "RemoveAllScripts" + ); + csScriptsRemoveAllImmediately = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "RemoveAllScriptsImmediately" + ); + csScriptsSerialiseYaml = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "SerialiseScripts" + ); + csScriptsDeserialiseYaml = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ScriptStore", + "DeserialiseScripts" + ); + csColliderOnListChanged = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Collider", + "OnCollisionShapeChanged" + ); + csColliderOnRemoved = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Collider", + "OnCollisionShapeRemoved" + ); + csSceneNodeChildrenChanged = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".ChildListCache", + "OnChildrenChanged" + ); + csEditorRenderScripts = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Editor", + "RenderScriptsInInspector" + ); + csEditorUndo = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Editor", + "Undo" + ); + csEditorRedo = dotNet.GetFunctionPtr + ( + DEFAULT_CSHARP_LIB_NAME, + DEFAULT_CSHARP_NAMESPACE + ".Editor", + "Redo" + ); + } + + void SHScriptEngine::registerEvents() + { + /* Entity */ + // Register for entity destroyed event + std::shared_ptr> destroyedEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onEntityDestroyed) + }; + SHEventManager::SubscribeTo(SH_ENTITY_DESTROYED_EVENT, std::dynamic_pointer_cast(destroyedEventReceiver)); + + /* Colliders */ + // Register for collider added event + std::shared_ptr> addedColliderEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onColliderAdded) + }; + SHEventManager::SubscribeTo(SH_PHYSICS_COLLIDER_ADDED_EVENT, std::dynamic_pointer_cast(addedColliderEventReceiver)); + // Register for collider removed event + std::shared_ptr> removedColliderEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onColliderRemoved) + }; + SHEventManager::SubscribeTo(SH_PHYSICS_COLLIDER_REMOVED_EVENT, std::dynamic_pointer_cast(removedColliderEventReceiver)); + // Register for collider component removed event + std::shared_ptr> removedColliderComponentEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onColliderComponentRemoved) + }; + SHEventManager::SubscribeTo(SH_COMPONENT_REMOVED_EVENT, std::dynamic_pointer_cast(removedColliderComponentEventReceiver)); + + /* SceneGraph */ + // Register for SceneNode child added event + std::shared_ptr> addChildEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onSceneNodeChildrenAdded) + }; + SHEventManager::SubscribeTo(SH_SCENEGRAPH_ADD_CHILD_EVENT, std::dynamic_pointer_cast(addChildEventReceiver)); + // Register for SceneNode child removed event + std::shared_ptr> removeChildEventReceiver + { + std::make_shared>(this, &SHScriptEngine::onSceneNodeChildrenRemoved) + }; + SHEventManager::SubscribeTo(SH_SCENEGRAPH_REMOVE_CHILD_EVENT, std::dynamic_pointer_cast(removeChildEventReceiver)); + } + + void SHScriptEngine::dumpBuildLog(const std::string_view& buildLogPath) + { + std::ifstream buildLog(buildLogPath); + + // Fail to open + if (!buildLog.is_open()) + return; + + // Process line by line + std::string line; + while (std::getline(buildLog, line)) + { + if (line.find("error") != line.npos) + { + SHLOG_ERROR(line); + } + else + { + SHLOG_WARNING(line); + } + } + } + void SHScriptEngine::deleteFile(const std::string& filePath) + { + try + { + std::filesystem::remove(std::filesystem::canonical(filePath)); + } + catch (...) {} // Ignore deletion failures + } + + void SHScriptEngine::deleteFolder(const std::string& filePath) + { + try + { + std::filesystem::remove_all(std::filesystem::canonical(filePath)); + } + catch (...) {} // Ignore deletion failures + } + + bool SHScriptEngine::fileExists(const std::filesystem::path& filePath) + { + std::error_code error; + if (std::filesystem::exists(filePath, error)) + { + return true; + } + return false; + } + + DWORD SHScriptEngine::execProcess(const std::wstring& path, const std::wstring& args) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, false, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[ScriptEngine] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtils::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Wait for execution to end + DWORD status; + while (true) + { + const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); + if (!EXEC_SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[ScriptEngine] Failed to query process. Error code: " << std::hex << err + << " (" << SHStringUtils::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Break only if process ends + if (status != STILL_ACTIVE) + { + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + return status; + } + } + } + + std::wstring SHScriptEngine::generateBuildCommand(bool debug) + { + std::wostringstream oss; + oss << "dotnet build \"" << SHStringUtils::StrToWstr(CSPROJ_PATH) << "\" -c "; + oss << debug ? "Debug" : "Release"; + oss << " -o \"./tmp/\" -fl -flp:LogFile=build.log;Verbosity=quiet -r \"win-x64\""; + return oss.str(); + } +} diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.h b/SHADE_Engine/src/Scripting/SHScriptEngine.h new file mode 100644 index 00000000..ef778627 --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.h @@ -0,0 +1,325 @@ +/************************************************************************************//*! +\file ScriptEngine.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2021 +\brief Contains the interface for ScriptEngine class. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +// External Dependencies +#include +// Project Headers +#include "SH_API.h" +#include "SHDotNetRuntime.h" +#include "ECS_Base/SHECSMacros.h" +#include "ECS_Base/Entity/SHEntity.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Events/SHEventDefines.h" +#include "Events/SHEvent.h" + +namespace SHADE +{ + /// + /// Manages initialisation of the DotNetRuntime and interfacing with CLR code written + /// and executed on .NET. + /// + class SH_API SHScriptEngine final : public SHSystem + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + class SH_API FrameSetUpRoutine final : public SHSystemRoutine + { + public: + FrameSetUpRoutine(); + void Execute(double dt) noexcept override final; + }; + class SH_API UpdateRoutine final : public SHSystemRoutine + { + public: + UpdateRoutine(); + void Execute(double dt) noexcept override final; + }; + class SH_API LateUpdateRoutine final : public SHSystemRoutine + { + public: + LateUpdateRoutine(); + void Execute(double dt) noexcept override final; + }; + class SH_API GizmosDrawRoutine final : public SHSystemRoutine + { + public: + GizmosDrawRoutine(); + void Execute(double dt) noexcept override final; + }; + class SH_API FrameCleanUpRoutine final : public SHSystemRoutine + { + public: + FrameCleanUpRoutine(); + void Execute(double dt) noexcept override final; + }; + + /*-----------------------------------------------------------------------------*/ + /* Constructor */ + /*-----------------------------------------------------------------------------*/ + SHScriptEngine() = default; + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Initialises the DotNetRuntime and retrieves function pointers to all + /// functions on the CLR used to interface with the engine. + /// + void Init() override; + /// + /// Loads the managed script assembly. Ensure this is only called after + /// UnloadScriptAssembly() has been called. + /// + void UnloadScriptAssembly(); + /// + /// Unloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// + void LoadScriptAssembly(); + /// + /// Reloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// + void ReloadScriptAssembly(); + /// + /// Executes the FixedUpdate()s of the Scripts that are attached to + /// Entities. + /// + void ExecuteFixedUpdates(); + /// + /// Executes the OnCollision*()s and OnTrigger*()s of the Scripts that are attached + /// to Entities. + /// + void ExecuteCollisionFunctions(); + /// + /// Shuts down the DotNetRuntime. + /// + void Exit() override; + + /*-----------------------------------------------------------------------------*/ + /* Script Manipulation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script to a specified Entity. Note that while you can call this + /// multiple times on a specified Entity, it will work for all intents and + /// purposes but GetScript<T>() (C# only) currently only + /// gives you the first Script added of the specified type. + /// + /// The entity to add a script to. + /// Type name of the script to add. + /// + /// True if successfully added. False otherwise with the error logged to the + /// console. + /// + bool AddScript(EntityID entity, const std::string_view& scriptName); + /// + /// Removes all Scripts attached to the specified Entity. Does not do anything + /// if the specified Entity is invalid or does not have any Scripts + /// attached. + /// + /// The entity to remove the scripts from. + void RemoveAllScripts(EntityID entity); + /// + /// Removes all Scripts attached to the specified Entity. Unlike + /// RemoveAllScripts(), this removes all the scripts immediately. + /// Does not do anything if the specified Entity is invalid or does not have any + /// Scripts attached. + /// + /// The entity to remove the scripts from. + /// + /// Whether or not to call OnDestroy on the scripts. This is ignored if not in + /// play mode. + /// + void RemoveAllScriptsImmediately(EntityID entity, bool callOnDestroy); + + /*-----------------------------------------------------------------------------*/ + /* Script Serialisation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Performs serialization of all scripts for the specified entity into the + /// YAML::Node specified. This node will contain all serialised scripts after + /// calling this function. + /// + /// The Entity to Serialise. + /// + /// YAML Node that will store the serialised scripts. + /// + /// True if successfully serialised. + bool SerialiseScripts(EntityID entity, YAML::Node& scriptsNode) const; + /// + /// Creates scripts and sets fields for the specified Entity based on the specified + /// YAML node. + /// + /// The Entity to deserialise a Script on to. + /// + /// YAML Node that contains the serialised script data. + /// + /// True if successfully deserialised. + bool DeserialiseScripts(EntityID entity, const YAML::Node& scriptsNode) const; + + /*-----------------------------------------------------------------------------*/ + /* Script Editor Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Renders the set of attached Scripts for the specified Entity into the + /// inspector. + ///
+ /// This function is meant for consumption from native code in the inspector + /// rendering code. + ///
+ /// The Entity to render the Scripts of. + void RenderScriptsInInspector(EntityID entity) const; + /// + /// Performs an undo for script inspector changes if it exists. + /// + void UndoScriptInspectorChanges() const; + /// + /// Performs a redo for script inspector changes if it exists. + /// + void RedoScriptInspectorChanges() const; + + /*-----------------------------------------------------------------------------*/ + /* Static Utility Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Utilises execution of a external batch file for invoking the dotnet build + /// tool to compile C# scripts in the Assets folder into the SHADE_Scripting + /// C# assembly DLL. + /// + /// + /// Whether or not a debug build will be built. Only debug built C# assemblies + /// can be debugged. + /// + /// + /// Whether or not we are reloading the assembly, if so, unload and then reload it. + /// + /// Whether or not the build succeeded. + bool BuildScriptAssembly(bool debug = false, bool reload = false); + /// + /// Generates a .csproj file for editing and compiling the C# scripts. + /// + /// File path to the generated file. + void GenerateScriptsCsProjFile(const std::filesystem::path& path = CSPROJ_PATH) const; + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using CsFuncPtr = void(*)(void); + using CsScriptManipFuncPtr = bool(*)(EntityID, const char*); + using CsScriptBasicFuncPtr = void(*)(EntityID); + using CsScriptOptionalFuncPtr = void(*)(EntityID, bool); + using CsScriptSerialiseYamlFuncPtr = bool(*)(EntityID, void*); + using CsScriptDeserialiseYamlFuncPtr = bool(*)(EntityID, const void*); + using CsScriptEditorFuncPtr = void(*)(EntityID); + using CsEventRelayFuncPtr = void(*)(EntityID); + + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr std::string_view DEFAULT_CSHARP_LIB_NAME = "SHADE_Managed"; + static constexpr std::string_view MANAGED_SCRIPT_LIB_NAME = "SHADE_Scripting"; + static const std::string CSPROJ_DIR; + static const std::string CSPROJ_PATH; + static const std::string DEFAULT_CSHARP_NAMESPACE; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + SHDotNetRuntime dotNet { false }; + // Function Pointers to CLR Code + // - Engine Lifecycle + CsFuncPtr csEngineInit = nullptr; + CsFuncPtr csEngineLoadScripts = nullptr; + CsFuncPtr csEngineUnloadScripts = nullptr; + CsFuncPtr csEngineReloadScripts = nullptr; + CsFuncPtr csEngineExit = nullptr; + // - Scripts Store + CsFuncPtr csScriptsFrameSetUp = nullptr; + CsFuncPtr csScriptsExecuteFixedUpdate = nullptr; + CsFuncPtr csScriptsExecuteUpdate = nullptr; + CsFuncPtr csScriptsExecuteLateUpdate = nullptr; + CsFuncPtr csScriptsExecuteDrawGizmos = nullptr; + CsFuncPtr csScriptsExecutePhysicsEvents = nullptr; + CsFuncPtr csScriptsFrameCleanUp = nullptr; + CsScriptManipFuncPtr csScriptsAdd = nullptr; + CsScriptBasicFuncPtr csScriptsRemoveAll = nullptr; + CsScriptOptionalFuncPtr csScriptsRemoveAllImmediately = nullptr; + CsScriptSerialiseYamlFuncPtr csScriptsSerialiseYaml = nullptr; + CsScriptDeserialiseYamlFuncPtr csScriptsDeserialiseYaml = nullptr; + // - Events + CsEventRelayFuncPtr csColliderOnListChanged = nullptr; + CsEventRelayFuncPtr csColliderOnRemoved = nullptr; + CsEventRelayFuncPtr csSceneNodeChildrenChanged = nullptr; + // - Editor + CsScriptEditorFuncPtr csEditorRenderScripts = nullptr; + CsFuncPtr csEditorUndo = nullptr; + CsFuncPtr csEditorRedo = nullptr; + + /*-----------------------------------------------------------------------------*/ + /* Event Handler Functions */ + /*-----------------------------------------------------------------------------*/ + SHEventHandle onEntityDestroyed(SHEventPtr eventPtr); + SHEventHandle onColliderAdded(SHEventPtr eventPtr); + SHEventHandle onColliderRemoved(SHEventPtr eventPtr); + SHEventHandle onColliderComponentRemoved(SHEventPtr eventPtr); + SHEventHandle onSceneNodeChildrenAdded(SHEventPtr eventPtr); + SHEventHandle onSceneNodeChildrenRemoved(SHEventPtr eventPtr); + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Loads all the function pointers to CLR code that we need to execute. + /// + void loadFunctions(); + /// + /// Registers events for the scripting system + /// + void registerEvents(); + /// + /// Reads the file via the specified path that represents a build log of error + /// and warning messages. + /// + /// + /// File path to the build log of script builds done by BuildScriptAssembly() to + /// dump and process. + /// + static void dumpBuildLog(const std::string_view& buildLogPath); + /// + /// Deletes the file as specified by the file path. + /// + /// File path to the file to delete. + static void deleteFile(const std::string& filePath); + /// + /// Deletes the folder and all files in it as specified by the file path. + /// + /// File path to the file to delete. + static void deleteFolder(const std::string& filePath); + /// + /// Checks if a specified file exists. + /// + /// File path to the file to check. + /// True if the file exists + static bool fileExists(const std::filesystem::path& filePath); + static DWORD execProcess(const std::wstring& path, const std::wstring& args); + static std::wstring generateBuildCommand(bool debug); + }; +} diff --git a/SHADE_Engine/src/Scripting/SHScriptEngineRoutines.cpp b/SHADE_Engine/src/Scripting/SHScriptEngineRoutines.cpp new file mode 100644 index 00000000..699776ca --- /dev/null +++ b/SHADE_Engine/src/Scripting/SHScriptEngineRoutines.cpp @@ -0,0 +1,74 @@ +/************************************************************************************//*! +\file SHScriptEngineRoutines.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2021 +\brief Contains the implementation or functions of SystemRoutines in the + SHScriptEngine class. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include +// Primary Header +#include "SHScriptEngine.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - FrameSetUpRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHScriptEngine::FrameSetUpRoutine::FrameSetUpRoutine() + : SHSystemRoutine("Script Engine Frame Set Up", false) + {} + void SHScriptEngine::FrameSetUpRoutine::Execute(double) noexcept + { + reinterpret_cast(system)->csScriptsFrameSetUp(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - UpdateRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHScriptEngine::UpdateRoutine::UpdateRoutine() + : SHSystemRoutine("Script Engine Update", false) + {} + void SHScriptEngine::UpdateRoutine::Execute(double) noexcept + { + reinterpret_cast(system)->csScriptsExecuteUpdate(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - LateUpdateRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHScriptEngine::LateUpdateRoutine::LateUpdateRoutine() + : SHSystemRoutine("Script Engine Late Update", false) + {} + void SHScriptEngine::LateUpdateRoutine::Execute(double) noexcept + { + reinterpret_cast(system)->csScriptsExecuteLateUpdate(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - GizmosDrawRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHScriptEngine::GizmosDrawRoutine::GizmosDrawRoutine() + : SHSystemRoutine("Script Engine Gizmos Draw", true) + {} + void SHScriptEngine::GizmosDrawRoutine::Execute(double dt) noexcept + { + reinterpret_cast(system)->csScriptsExecuteDrawGizmos(); + } + + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - FrameCleanUpRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHScriptEngine::FrameCleanUpRoutine::FrameCleanUpRoutine() + : SHSystemRoutine("Script Engine Frame Clean Up", true) + {} + void SHScriptEngine::FrameCleanUpRoutine::Execute(double) noexcept + { + reinterpret_cast(system)->csScriptsFrameCleanUp(); + } +} diff --git a/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.cpp b/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.cpp new file mode 100644 index 00000000..6fa4e9bf --- /dev/null +++ b/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.cpp @@ -0,0 +1,89 @@ +#include "SHpch.h" +#include "SHConfigurationManager.h" +#include "Tools/FileIO/SHFileIO.h" +#include "Serialization/SHSerializationHelper.hpp" + +namespace SHADE +{ + SHApplicationConfig SHConfigurationManager::applicationConfig; +#ifdef SHEDITOR + SHEditorConfig SHConfigurationManager::editorConfig; +#endif + + void SHConfigurationManager::SaveApplicationConfig() + { + YAML::Emitter out; + out << SHSerializationHelper::RTTRToNode(applicationConfig); + SHFileIO::WriteStringToFile(applicationConfigPath, out.c_str()); + } + + SHApplicationConfig& SHConfigurationManager::LoadApplicationConfig(WindowData* wndData) + { + if(!std::filesystem::exists(applicationConfigPath)) + { + SaveApplicationConfig(); + return applicationConfig; + } + + auto const node = YAML::Load(SHFileIO::GetStringFromFile(applicationConfigPath)); + auto properties = rttr::type::get().get_properties(); + for(auto const& property : properties) + { + if(node[property.get_name().data()].IsDefined()) + SHSerializationHelper::InitializeProperty(&applicationConfig, property, node[property.get_name().data()]); + } + + if(wndData != nullptr) + { + wndData->isFullscreen = applicationConfig.startInFullScreen; + wndData->title = std::wstring(applicationConfig.windowTitle.begin(), applicationConfig.windowTitle.end()); + wndData->width = static_cast(applicationConfig.windowSize.x); + wndData->height = static_cast(applicationConfig.windowSize.y); + } + + return applicationConfig; + } + +#ifdef SHEDITOR + void SHConfigurationManager::SaveEditorConfig() + { + YAML::Emitter out; + out << SHSerializationHelper::RTTRToNode(editorConfig); + SHFileIO::WriteStringToFile(editorConfigPath, out.c_str()); + } + + SHEditorConfig& SHConfigurationManager::LoadEditorConfig() + { + auto const node = YAML::Load(SHFileIO::GetStringFromFile(editorConfigPath)); + auto properties = rttr::type::get().get_properties(); + for(auto const& property : properties) + { + if(node[property.get_name().data()].IsDefined()) + SHSerializationHelper::InitializeProperty(&editorConfig, property, node[property.get_name().data()]); + } + return editorConfig; + } + + void SHConfigurationManager::FetchEditorCameraData() + { + + } + + void SHConfigurationManager::SetEditorCameraData() + { + + } +#endif +} + +RTTR_REGISTRATION +{ + using namespace rttr; + using namespace SHADE; + + registration::class_("Application Config") + .property("Start in Fullscreen", &SHApplicationConfig::startInFullScreen) + .property("Starting Scene ID", &SHApplicationConfig::startingSceneID) + .property("Window Size", &SHApplicationConfig::windowSize) + .property("Window Title", &SHApplicationConfig::windowTitle); +} diff --git a/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.h b/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.h new file mode 100644 index 00000000..767b8c2b --- /dev/null +++ b/SHADE_Engine/src/Serialization/Configurations/SHConfigurationManager.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include "Assets/SHAssetMacros.h" +#include "Graphics/Windowing/SHWindow.h" +#include "SH_API.h" +#include "Math/Vector/SHVec2.h" + +namespace SHADE +{ + + struct SHApplicationConfig + { + bool startInFullScreen{ false }; + AssetID startingSceneID{}; + SHVec2 windowSize {1920, 1080}; + std::string windowTitle {"SHADE Engine"}; + RTTR_ENABLE() + }; + + struct SHEditorConfig + { + + }; + + class SH_API SHConfigurationManager + { + public: + static inline std::string applicationConfigPath{ std::string(ASSET_ROOT) + "/Application.SHConfig"}; + static inline std::string editorConfigPath{ std::string(ASSET_ROOT) + "/Editor/Editor.SHConfig"}; + + static void SaveApplicationConfig(); + static SHApplicationConfig& LoadApplicationConfig(WindowData* wndData = nullptr); + static SHApplicationConfig applicationConfig; +#ifdef SHEDITOR + static void SaveEditorConfig(); + static SHEditorConfig& LoadEditorConfig(); + static SHEditorConfig editorConfig; + private: + static void FetchEditorCameraData(); + static void SetEditorCameraData(); +#endif + + }; +} diff --git a/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.cpp b/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.cpp new file mode 100644 index 00000000..8ab098b8 --- /dev/null +++ b/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.cpp @@ -0,0 +1,56 @@ +#include "SHpch.h" +#include "SHPrefabManager.h" + +namespace SHADE +{ + SHPrefabManager::PrefabMap SHPrefabManager::prefabMap{}; + + void SHPrefabManager::AddPrefab(AssetID const& prefabAssetID) noexcept + { + prefabMap.insert({ prefabAssetID, {} }); + } + + void SHPrefabManager::RemovePrefab(AssetID const& prefabAssetID) noexcept + { + prefabMap.erase(prefabAssetID); + } + + void SHPrefabManager::ClearPrefab(AssetID const& prefabAssetID) noexcept + { + if (prefabMap.contains(prefabAssetID)) + { + prefabMap[prefabAssetID].clear(); + } + } + + void SHPrefabManager::UpdateAllPrefabEntities(AssetID const& prefabAssetID) noexcept + { + //Loop through all entities and deserialize new data + } + + void SHPrefabManager::AddEntity(AssetID const& prefabAssetID, EntityID const& eid) noexcept + { + if (prefabMap.contains(prefabAssetID)) + { + prefabMap[prefabAssetID].insert(eid); + } + } + + void SHPrefabManager::RemoveEntity(AssetID const& prefabAssetID, EntityID const& eid) noexcept + { + if (prefabMap.contains(prefabAssetID)) + { + prefabMap[prefabAssetID].erase(eid); + } + } + + void SHPrefabManager::Clear() noexcept + { + prefabMap.clear(); + } + + bool SHPrefabManager::Empty() noexcept + { + return prefabMap.empty(); + } +} diff --git a/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.h b/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.h new file mode 100644 index 00000000..37c317ed --- /dev/null +++ b/SHADE_Engine/src/Serialization/Prefab/SHPrefabManager.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Assets/SHAssetMacros.h" +#include "ECS_Base/SHECSMacros.h" +#include +#include + + +namespace SHADE +{ + class SHPrefabManager + { + public: + using PrefabMap = std::unordered_map>; + + static void AddPrefab(AssetID const& prefabAssetID) noexcept; + static void RemovePrefab(AssetID const& prefabAssetID) noexcept; + static void ClearPrefab(AssetID const& prefabAssetID) noexcept; + static void UpdateAllPrefabEntities(AssetID const& prefabAssetID) noexcept; + static void AddEntity(AssetID const& prefabAssetID, EntityID const& eid) noexcept; + static void RemoveEntity(AssetID const& prefabAssetID, EntityID const& eid) noexcept; + static void Clear() noexcept; + static bool Empty() noexcept; + + private: + static PrefabMap prefabMap; + }; +} diff --git a/SHADE_Engine/src/Serialization/SHSerialization.cpp b/SHADE_Engine/src/Serialization/SHSerialization.cpp new file mode 100644 index 00000000..8e4e350c --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerialization.cpp @@ -0,0 +1,341 @@ +#include "SHpch.h" + +#include +#include "SHSerialization.h" +#include "SHSerializationHelper.hpp" + +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneManager.h" +#include "Tools/SHException.h" +#include "Assets/SHAssetManager.h" +#include + +#include "Assets/Asset Types/SHSceneAsset.h" +#include "Camera/SHCameraComponent.h" +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Lights/SHLightComponent.h" +#include "Scripting/SHScriptEngine.h" +#include "Tools/FileIO/SHFileIO.h" + +namespace SHADE +{ + bool SHSerialization::SerializeSceneToFile(AssetID const& sceneAssetID) + { + auto assetData = SHAssetManager::GetData(sceneAssetID); + if(!assetData) + { + SHLOG_ERROR("Asset does not exist: {}", sceneAssetID); + return false; + } + YAML::Emitter out; + SerializeSceneToEmitter(out); + assetData->data = out.c_str(); + + return SHAssetManager::SaveAsset(sceneAssetID); + } + + std::string SHSerialization::SerializeSceneToString() + { + YAML::Emitter out; + SerializeSceneToEmitter(out); + return std::basic_string(out.c_str()); + } + + void SHSerialization::SerializeSceneToEmitter(YAML::Emitter& out) + { + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + auto root = sceneGraph.GetRoot(); + + SHASSERT(root != nullptr, "Root is null. Failed to serialize scene to node."); + + auto const& children = root->GetChildren(); + out << YAML::BeginSeq; + + auto pred = [&out](SHSceneNode* node) {out << SerializeEntityToNode(node); }; + sceneGraph.Traverse(pred); + //out << SerializeEntityToNode(child); + + out << YAML::EndSeq; + } + + static EntityID DeserializeEntity(YAML::iterator& it, YAML::Node const& node, SHSerialization::CreatedEntitiesList& createdEntities, EntityID parentEID = MAX_EID) + { + EntityID eid{MAX_EID}, oldEID{MAX_EID}; + if (!node) + return eid; + if (node[EIDNode]) + oldEID = eid = node[EIDNode].as(); + std::string name = "UnnamedEntitiy"; + if (node[EntityNameNode]) + name = node[EntityNameNode].as(); + //Compile component IDs + const auto componentIDList = SHSerialization::GetComponentIDList(node[ComponentsNode]); + eid = SHEntityManager::CreateEntity(componentIDList, eid, name, parentEID); + createdEntities[oldEID] = eid; + //createdEntities.push_back(eid); + if (node[NumberOfChildrenNode]) + { + if (const int numOfChildren = node[NumberOfChildrenNode].as(); numOfChildren > 0) + { + ++it; + for (int i = 0; i < numOfChildren; ++i) + { + DeserializeEntity(it, (*it), createdEntities, eid); + if ((i + 1) < numOfChildren) + ++it; + } + } + } + + // Deserialise scripts + if (node[ScriptsNode]) + SHSystemManager::GetSystem()->DeserialiseScripts(eid, node[ScriptsNode]); + + return eid; + } + + std::string SHSerialization::DeserializeSceneFromFile(AssetID const& sceneAssetID) noexcept + { + auto assetData = SHAssetManager::GetData(sceneAssetID); + if(!assetData) + { + SHLOG_ERROR("Attempted to load scene that doesn't exist {}", sceneAssetID) + SHSceneManager::SetCurrentSceneAssetID(0); + return NewSceneName.data(); + } + YAML::Node entities = YAML::Load(assetData->data); + CreatedEntitiesList createdEntities{}; + + //Create Entities + for (auto it = entities.begin(); it != entities.end(); ++it) + { + DeserializeEntity(it, (*it), createdEntities); + } + if (createdEntities.empty()) + { + SHLOG_ERROR("Failed to create entities from deserializaiton") + return NewSceneName.data(); + } + auto entityVecIt = createdEntities.begin(); + AssetQueue assetQueue; + for (auto it = entities.begin(); it != entities.end(); ++it) + { + SHSerializationHelper::FetchAssetsFromComponent((*it)[ComponentsNode], createdEntities[(*it)[EIDNode].as()], assetQueue); + } + LoadAssetsFromAssetQueue(assetQueue); + //Initialize Entity + entityVecIt = createdEntities.begin(); + for (auto it = entities.begin(); it != entities.end(); ++it) + { + InitializeEntity(*it, createdEntities[(*it)[EIDNode].as()]); + } + + return assetData->name; + } + + void SHSerialization::EmitEntity(SHSceneNode* entityNode, YAML::Emitter& out) + { + out << SerializeEntityToNode(entityNode); + auto const& children = entityNode->GetChildren(); + for (auto const& child : children) + { + EmitEntity(child, out); + } + } + + std::string SHSerialization::SerializeEntitiesToString(std::vector const& entities) noexcept + { + YAML::Emitter out; + YAML::Node node; + auto const& sceneGraph = SHSceneManager::GetCurrentSceneGraph(); + out << YAML::BeginSeq; + for (auto const& eid : entities) + { + auto entityNode = sceneGraph.GetNode(eid); + EmitEntity(entityNode, out); + } + out << YAML::EndSeq; + return std::string(out.c_str()); + } + + //void SHSerialization::SerializeEntityToFile(std::filesystem::path const& path) + //{ + //} + + template, bool> = true> + static void AddComponentToComponentNode(YAML::Node& componentsNode, EntityID const& eid) + { + if (const ComponentType* component = SHComponentManager::GetComponent_s(eid)) + { + componentsNode[rttr::type::get().get_name().data()] = SHSerializationHelper::SerializeComponentToNode(component); + } + } + + template, bool> = true> + static void AddConvComponentToComponentNode(YAML::Node& componentsNode, EntityID const& eid) + { + if (ComponentType* component = SHComponentManager::GetComponent_s(eid)) + { + componentsNode[rttr::type::get().get_name().data()] = YAML::convert::encode(*component); + } + } + + + YAML::Node SHSerialization::SerializeEntityToNode(SHSceneNode* sceneNode) + { + YAML::Node node; + auto eid = sceneNode->GetEntityID(); + auto entity = SHEntityManager::GetEntityByID(eid); + if (!sceneNode && !entity) + { + node = YAML::Null; + return node; + } + node.SetStyle(YAML::EmitterStyle::Block); + node[EIDNode] = eid; + node[EntityNameNode] = entity->name; + node[IsActiveNode] = sceneNode->IsActive(); + auto const& children = sceneNode->GetChildren(); + node[NumberOfChildrenNode] = children.size(); + + YAML::Node components; + + AddComponentToComponentNode(components, eid); + AddComponentToComponentNode(components, eid); + AddConvComponentToComponentNode(components, eid); + AddComponentToComponentNode(components, eid); + AddComponentToComponentNode(components, eid); + AddConvComponentToComponentNode(components, eid); + + node[ComponentsNode] = components; + + YAML::Node scripts; + SHSystemManager::GetSystem()->SerialiseScripts(eid, scripts); + node[ScriptsNode] = scripts; + + return node; + } + + SHSerialization::CreatedEntitiesList SHSerialization::DeserializeEntitiesFromString(std::string const& data, EntityID const& parentEID) noexcept + { + if (data.empty()) + return {}; + YAML::Node entities = YAML::Load(data.c_str()); + EntityID eid{ MAX_EID }; + CreatedEntitiesList createdEntities{}; + for (auto it = entities.begin(); it != entities.end(); ++it) + { + eid = DeserializeEntity(it, *it, createdEntities, parentEID); + } + if (createdEntities.empty()) + { + SHLOG_ERROR("Failed to create entities from deserializaiton") + return createdEntities; + } + //auto entityVecIt = createdEntities.begin(); + for (auto it = entities.begin(); it != entities.end(); ++it) + { + InitializeEntity(*it, createdEntities[(*it)[EIDNode].as()]); + } + return createdEntities; + } + + template, bool> = true> + static void AddComponentID(std::vector& idList, YAML::Node const& componentNode) + { + if (componentNode[rttr::type::get().get_name().data()]) + idList.push_back(SHFamilyID::GetID()); + } + + std::vector SHSerialization::GetComponentIDList(YAML::Node const& componentsNode) + { + std::vector componentIDList; + + AddComponentID(componentIDList, componentsNode); + AddComponentID(componentIDList, componentsNode); + AddComponentID(componentIDList, componentsNode); + AddComponentID(componentIDList, componentsNode); + AddComponentID(componentIDList, componentsNode); + AddComponentID(componentIDList, componentsNode); + + return componentIDList; + } + + void SHSerialization::LoadAssetsFromAssetQueue(AssetQueue& assetQueue) + { + for (auto& [assetId, assetType] : assetQueue) + { + switch(assetType) + { + case AssetType::INVALID: break; + case AssetType::SHADER: break; + case AssetType::SHADER_BUILT_IN: break; + case AssetType::TEXTURE: + SHResourceManager::LoadOrGet(assetId); + break; + case AssetType::MESH: + SHResourceManager::LoadOrGet(assetId); + break; + case AssetType::SCENE: break; + case AssetType::PREFAB: break; + case AssetType::MATERIAL: + SHResourceManager::LoadOrGet(assetId); + break; + case AssetType::MAX_COUNT: break; + default: ; + } + } + SHResourceManager::FinaliseChanges(); + } + + void ResolveSerializedEntityID(YAML::Emitter& out, YAML::iterator& it, YAML::Node const& entityNode, SHSerialization::CreatedEntitiesList const& createdEntities) + { + EntityID eid = entityNode[EIDNode].as(); + YAML::Node resolvedNode = entityNode; + resolvedNode[EIDNode] = createdEntities.at(eid); + out << resolvedNode; + if (entityNode[NumberOfChildrenNode]) + { + if (const int numOfChildren = entityNode[NumberOfChildrenNode].as(); numOfChildren > 0) + { + ++it; + for (int i = 0; i < numOfChildren; ++i) + { + ResolveSerializedEntityID(out, it, (*it), createdEntities); + //DeserializeEntity(it, (*it), createdEntities, eid); + if ((i + 1) < numOfChildren) + ++it; + } + } + } + } + + std::string SHSerialization::ResolveSerializedEntityIndices(std::string serializedEntityData, CreatedEntitiesList const& createdEntities) noexcept + { + YAML::Node entities = YAML::Load(serializedEntityData); + YAML::Emitter out; + out << YAML::BeginSeq; + for (auto it = entities.begin(); it != entities.end(); ++it) + { + ResolveSerializedEntityID(out, it, (*it), createdEntities); + } + out << YAML::EndSeq; + return out.c_str(); + } + + void SHSerialization::InitializeEntity(YAML::Node const& entityNode, EntityID const& eid) + { + auto const componentsNode = entityNode[ComponentsNode]; + if (!componentsNode) + return; + SHSerializationHelper::InitializeComponentFromNode(componentsNode, eid); + SHSerializationHelper::InitializeComponentFromNode(componentsNode, eid); + SHSerializationHelper::InitializeComponentFromNode(componentsNode, eid); + SHSerializationHelper::ConvertNodeToComponent(componentsNode, eid); + SHSerializationHelper::ConvertNodeToComponent(componentsNode, eid); + SHSerializationHelper::InitializeComponentFromNode(componentsNode, eid); + } +} diff --git a/SHADE_Engine/src/Serialization/SHSerialization.h b/SHADE_Engine/src/Serialization/SHSerialization.h new file mode 100644 index 00000000..dd487662 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerialization.h @@ -0,0 +1,60 @@ +#pragma once + +#include "SH_API.h" +#include + + +#include "ECS_Base/SHECSMacros.h" +#include +#include + +namespace YAML +{ + class Emitter; + class Node; +} + +namespace SHADE +{ + class SHSceneNode; + + constexpr const char* ComponentsNode = "Components"; + constexpr const char* EntityNameNode = "Name"; + constexpr const char* EIDNode = "EID"; + constexpr const char* IsActiveNode = "IsActive"; + constexpr const char* NumberOfChildrenNode = "NumberOfChildren"; + constexpr const char* ScriptsNode = "Scripts"; + + class SH_API SHSerialization + { + public: + //Original EID : New EID + using CreatedEntitiesList = std::unordered_map; + + static bool SerializeSceneToFile(AssetID const& sceneAssetID); + static std::string SerializeSceneToString(); + static void SerializeSceneToEmitter(YAML::Emitter& out); + + static std::string DeserializeSceneFromFile(AssetID const& sceneAssetID) noexcept; + + + static void EmitEntity(SHSceneNode* entityNode, YAML::Emitter& out); + + static std::string SerializeEntitiesToString(std::vector const& entities) noexcept; + //static void SerializeEntityToFile(std::filesystem::path const& path); + static YAML::Node SerializeEntityToNode(SHSceneNode* sceneNode); + + static CreatedEntitiesList DeserializeEntitiesFromString(std::string const& data, EntityID const& parentEID = MAX_EID) noexcept; + + static std::vector GetComponentIDList(YAML::Node const& componentsNode); + + static void LoadAssetsFromAssetQueue(std::unordered_map& assetQueue); + + static std::string ResolveSerializedEntityIndices(std::string serializedEntityData, CreatedEntitiesList const& createdEntities) noexcept; + private: + //static void ResolveSerializedEntityID(YAML::Emitter& out, YAML::iterator& it, YAML::Node const& entityNode, CreatedEntitiesList const& createdEntities); + static void InitializeEntity(YAML::Node const& entityNode, EntityID const& eid); + + static constexpr std::string_view NewSceneName = "New Scene"; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp b/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp new file mode 100644 index 00000000..b062b348 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerializationHelper.hpp @@ -0,0 +1,280 @@ +#pragma once + +#include "SHYAMLConverters.h" +#include +#include "ECS_Base/Components/SHComponent.h" + +#include +#include + +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Tools/SHLog.h" + + +namespace SHADE +{ + using AssetQueue = std::unordered_map; + struct SHSerializationHelper + { + + + static YAML::Node RTTRToNode(const rttr::variant& var) + { + YAML::Node node; + auto varType = var.get_type(); + if (varType.is_sequential_container()) + { + for (auto const& elem : var.create_sequential_view()) + { + node.push_back(RTTRToNode(elem)); + } + } + if (varType == rttr::type::get()) + { + node = YAML::convert::encode(var.convert()); + } + else if (varType == rttr::type::get()) + { + node = YAML::convert::encode(var.convert()); + } + else if (varType == rttr::type::get()) + { + node = YAML::convert::encode(var.convert()); + } + else if (varType.is_arithmetic()) + { + bool ok = false; + if (varType == rttr::type::get()) + node = var.to_bool(); + else if (varType == rttr::type::get()) + node = var.to_int8(&ok); + else if (varType == rttr::type::get()) + node = var.to_int16(&ok); + else if (varType == rttr::type::get()) + node = var.to_int32(&ok); + else if (varType == rttr::type::get()) + node = var.to_int64(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint8(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint16(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint32(&ok); + else if (varType == rttr::type::get()) + node = var.to_uint64(&ok); + else if (varType == rttr::type::get()) + node = var.to_float(&ok); + else if (varType == rttr::type::get()) + node = var.to_double(&ok); + //else if (varType == rttr::type::get()) //same as uint8_t + // node = var.to_uint8(); + } + else if (varType.is_enumeration()) + { + bool ok = false; + auto result = var.to_string(&ok); + if (ok) + { + node = var.to_string(); + } + else + { + ok = false; + auto value = var.to_uint64(&ok); + if (ok) + node = value; + else + node = YAML::Null; + } + } + else if (varType == rttr::type::get()) + node = var.to_string(); + else + { + auto properties = var.get_type().get_properties(); + for (auto const& property : properties) + { + node[property.get_name().data()] = RTTRToNode(property.get_value(var)); + } + } + return node; + } + + template , bool> = true> + static std::string SerializeComponentToString(ComponentType* component) + { + return std::string(); + } + + template , bool> = true> + static void SerializeComponentToFile(ComponentType* component, std::filesystem::path const& path) + { + } + + template , bool> = true> + static YAML::Node SerializeComponentToNode(ComponentType* component) + { + YAML::Node node{}; + if (!component) + return node; + + auto componentType = rttr::type::get(); + node = RTTRToNode(*component); + + return node; + } + + template + static void InitializeProperty(Type* object, rttr::property const& prop, YAML::Node const& propertyNode) + { + auto propType = prop.get_type(); + if (propType == rttr::type::get()) + { + SHVec4 vec = propertyNode.as(); + prop.set_value(object, vec); + } + else if (propType == rttr::type::get()) + { + SHVec3 vec = propertyNode.as(); + prop.set_value(object, vec); + } + else if (propType == rttr::type::get()) + { + SHVec2 vec = propertyNode.as(); + prop.set_value(object, vec); + } + else if (propType.is_arithmetic()) + { + bool ok = false; + if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + } + else if (propType.is_enumeration()) + { + auto enumAlign = prop.get_enumeration(); + prop.set_value(object, enumAlign.name_to_value(propertyNode.as())); + } + else if (propType == rttr::type::get()) + prop.set_value(object, propertyNode.as()); + else + { + auto properties = propType.get_properties(); + for (auto const& property : properties) + { + if(propertyNode[property.get_name().data()].IsDefined()) + InitializeProperty(object, property, propertyNode[property.get_name().data()]); + } + } + } + + template , bool> = true> + static void InitializeComponentFromNode(YAML::Node const& componentsNode, EntityID const& eid) + { + ComponentType* component = SHComponentManager::GetComponent_s(eid); + if (componentsNode.IsNull() && !component) + return; + auto rttrType = rttr::type::get(); + auto componentNode = componentsNode[rttrType.get_name().data()]; + if (!componentNode.IsDefined()) + return; + auto properties = rttrType.get_properties(); + for (auto const& prop : properties) + { + if (componentNode[prop.get_name().data()].IsDefined()) + { + InitializeProperty(component, prop, componentNode[prop.get_name().data()]); + } + } + } + + template , bool> = true> + static YAML::Node GetComponentNode(YAML::Node const& componentsNode, EntityID const& eid) + { + auto component = SHComponentManager::GetComponent_s(eid); + if (componentsNode.IsNull() && !component) + return {}; + auto rttrType = rttr::type::get(); + auto componentNode = componentsNode[rttrType.get_name().data()]; + if (!componentNode.IsDefined()) + return {}; + return componentNode; + } + + template , bool> = true> + static void ConvertNodeToComponent(YAML::Node const& componentsNode, EntityID const& eid) + { + auto component = SHComponentManager::GetComponent_s(eid); + if (componentsNode.IsNull() && !component) + return; + + YAML::convert::decode(GetComponentNode(componentsNode, eid), *component); + } + + template , bool> = true> + static void FetchAssetsFromComponent(YAML::Node const& componentsNode, EntityID const& eid, AssetQueue& assetQueue) + { + } + + template<> + static void FetchAssetsFromComponent(YAML::Node const& componentsNode, EntityID const& eid, AssetQueue& assetQueue) + { + auto node = GetComponentNode(componentsNode, eid); + if(!node.IsDefined()) + return; + if (auto const& meshNode = node[YAML::convert::MESH_YAML_TAG.data()]; meshNode.IsDefined()) + { + assetQueue.insert({meshNode.as(), AssetType::MESH}); + //SHResourceManager::LoadOrGet(node[YAML::convert::MESH_YAML_TAG.data()].as()); + } + if (auto const& matNode = node[YAML::convert::MAT_YAML_TAG.data()]; matNode.IsDefined()) + { + auto const matAsset = SHAssetManager::GetData(matNode.as()); + if(matAsset) + { + SHMaterialSpec spec; + YAML::convert::decode(*YAML::Load(matAsset->data).begin(), spec); + if(spec.properties.IsDefined()) + { + auto fragShader = SHResourceManager::LoadOrGet(spec.fragShader); + auto interface = fragShader->GetReflectedData().GetDescriptorBindingInfo().GetShaderBlockInterface(SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA); + int const varCount = static_cast(interface->GetVariableCount()); + + for (int i = 0; i < varCount; ++i) + { + auto variable = interface->GetVariable(i); + if(variable->type != SHShaderBlockInterface::Variable::Type::INT) + continue; + const std::string& VAR_NAME = interface->GetVariableName(i); + if(VAR_NAME.empty()) + continue; + assetQueue.insert({spec.properties[VAR_NAME.data()].as(), AssetType::TEXTURE}); + } + } + } + //assetQueue.insert({matNode.as(), AssetType::MATERIAL}); + //SHResourceManager::LoadOrGet(node[YAML::convert::MAT_YAML_TAG.data()].as()); + } + } + }; +} diff --git a/SHADE_Engine/src/Serialization/SHSerializationTools.cpp b/SHADE_Engine/src/Serialization/SHSerializationTools.cpp new file mode 100644 index 00000000..86a74613 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerializationTools.cpp @@ -0,0 +1,67 @@ +/************************************************************************************//*! +\file SHSerializationTools.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 22, 2022 +\brief Contains the definition of functions of the SHSerializationTools class. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "SHSerializationTools.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* YAML Serialization Functions */ + /*-----------------------------------------------------------------------------------*/ + YAML::Node SHSerializationTools::ValToYAML(const SHVec2& vec) + { + YAML::Node node; + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = vec.x; + node["Y"] = vec.y; + return node; + } + YAML::Node SHSerializationTools::ValToYAML(const SHVec3& vec) + { + YAML::Node node; + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = vec.x; + node["Y"] = vec.y; + node["Z"] = vec.z; + return node; + } + YAML::Node SHSerializationTools::ValToYAML(const SHVec4& vec) + { + YAML::Node node; + node.SetStyle(YAML::EmitterStyle::Flow); + node["X"] = vec.x; + node["Y"] = vec.y; + node["Z"] = vec.z; + node["W"] = vec.w; + return node; + } + + /*-----------------------------------------------------------------------------------*/ + /* YAML Deserialization Functions */ + /*-----------------------------------------------------------------------------------*/ + SHVec2 SHSerializationTools::YAMLToVec2(const YAML::Node& node) + { + return SHVec2 { node["X"].as(), node["Y"].as() }; + } + + SHVec3 SHSerializationTools::YAMLToVec3(const YAML::Node& node) + { + return SHVec3 { node["X"].as(), node["Y"].as(), node["Z"].as() }; + } + + SHVec4 SHSerializationTools::YAMLToVec4(const YAML::Node& node) + { + return SHVec4 { node["X"].as(), node["Y"].as(), node["Z"].as(), node["W"].as() }; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Serialization/SHSerializationTools.h b/SHADE_Engine/src/Serialization/SHSerializationTools.h new file mode 100644 index 00000000..3a3f6645 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHSerializationTools.h @@ -0,0 +1,54 @@ +/************************************************************************************//*! +\file SHSerializationTools.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 22, 2022 +\brief Contains the class definition of SHSerializationTools. + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include +// Project Includes +#include "SH_API.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" + +namespace SHADE +{ + /*************************************************************************************/ + /*! + \brief + Static class that contains useful functions for converting values to YAML Nodes + and vice versa. + */ + /*************************************************************************************/ + class SH_API SHSerializationTools + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + SHSerializationTools() = delete; + + /*---------------------------------------------------------------------------------*/ + /* YAML Serialization Functions */ + /*---------------------------------------------------------------------------------*/ + static YAML::Node ValToYAML(const SHVec2& vec); + static YAML::Node ValToYAML(const SHVec3& vec); + static YAML::Node ValToYAML(const SHVec4& vec); + + /*---------------------------------------------------------------------------------*/ + /* YAML Deserialization Functions */ + /*---------------------------------------------------------------------------------*/ + static SHVec2 YAMLToVec2(const YAML::Node& node); + static SHVec3 YAMLToVec3(const YAML::Node& node); + static SHVec4 YAMLToVec4(const YAML::Node& node); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h new file mode 100644 index 00000000..d66a7506 --- /dev/null +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -0,0 +1,325 @@ +#pragma once +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" +#include "Math/Geometry/SHBox.h" +#include "Math/Geometry/SHSphere.h" +#include "Physics/Interface/SHCollisionShape.h" +#include "Resource/SHResourceManager.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" +#include "Graphics/MiddleEnd/Interface/SHMaterial.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +#include "SHSerializationTools.h" +#include "Physics/Interface/SHColliderComponent.h" +namespace YAML +{ + using namespace SHADE; + + template<> + struct convert + { + static constexpr const char* x = "x"; + static constexpr const char* y = "y"; + static constexpr const char* z = "z"; + static constexpr const char* w = "w"; + + static Node encode(SHVec4 const& rhs) + { + Node node; + node.SetStyle(EmitterStyle::Flow); + node[x] = rhs.x; + node[y] = rhs.y; + node[z] = rhs.z; + node[w] = rhs.w; + return node; + } + static bool decode(Node const& node, SHVec4& rhs) + { + if (node[x].IsDefined()) + rhs.x = node[x].as(); + if (node[y].IsDefined()) + rhs.y = node[y].as(); + if (node[z].IsDefined()) + rhs.z = node[z].as(); + if (node[w].IsDefined()) + rhs.w = node[w].as(); + return true; + } + }; + + template<> + struct convert + { + static constexpr const char* x = "x"; + static constexpr const char* y = "y"; + static constexpr const char* z = "z"; + + static Node encode(SHVec3 const& rhs) + { + Node node; + node.SetStyle(EmitterStyle::Flow); + node[x] = rhs.x; + node[y] = rhs.y; + node[z] = rhs.z; + return node; + } + static bool decode(Node const& node, SHVec3& rhs) + { + if (node[x].IsDefined()) + rhs.x = node[x].as(); + if (node[y].IsDefined()) + rhs.y = node[y].as(); + if (node[z].IsDefined()) + rhs.z = node[z].as(); + return true; + } + }; + + template<> + struct convert + { + static constexpr const char* x = "x"; + static constexpr const char* y = "y"; + + static Node encode(SHVec2 const& rhs) + { + Node node; + node.SetStyle(EmitterStyle::Flow); + node[x] = rhs.x; + node[y] = rhs.y; + return node; + } + static bool decode(Node const& node, SHVec2& rhs) + { + if (node[x].IsDefined()) + rhs.x = node[x].as(); + if (node[y].IsDefined()) + rhs.y = node[y].as(); + return true; + } + }; + + template<> + struct convert + { + static constexpr const char* IsTrigger = "Is Trigger"; + + static constexpr const char* Type = "Type"; + static constexpr const char* HalfExtents = "Half Extents"; + static constexpr const char* Radius = "Radius"; + + static constexpr const char* Friction = "Friction"; + static constexpr const char* Bounciness = "Bounciness"; + static constexpr const char* Density = "Density"; + static constexpr const char* PositionOffset = "Position Offset"; + + static Node encode(SHCollisionShape& rhs) + { + Node node; + + node[IsTrigger] = rhs.IsTrigger(); + + rttr::type const shapeRttrType = rttr::type::get(); + rttr::enumeration const enumAlign = shapeRttrType.get_enumeration(); + SHCollisionShape::Type colliderType = rhs.GetType(); + + node[Type] = enumAlign.value_to_name(colliderType).data(); + + switch (colliderType) + { + case SHCollisionShape::Type::BOX: + { + const auto* BOX = reinterpret_cast(rhs.GetShape()); + node[HalfExtents] = BOX->GetRelativeExtents(); + } + break; + case SHCollisionShape::Type::SPHERE: + { + const auto* SPHERE = reinterpret_cast(rhs.GetShape()); + node[Radius] = SPHERE->GetRelativeRadius(); + } + break; + case SHCollisionShape::Type::CAPSULE: break; + default:; + } + + node[Friction] = rhs.GetFriction(); + node[Bounciness] = rhs.GetBounciness(); + node[Density] = rhs.GetDensity(); + node[PositionOffset] = rhs.GetPositionOffset(); + + return node; + } + static bool decode(Node const& node, SHCollisionShape& rhs) + { + if (node[IsTrigger].IsDefined()) + rhs.SetIsTrigger(node[IsTrigger].as()); + if (!node[Type].IsDefined()) + return false; + rttr::type const shapeRttrType = rttr::type::get(); + rttr::enumeration const enumAlign = shapeRttrType.get_enumeration(); + bool ok; + const SHCollisionShape::Type colliderType = enumAlign.name_to_value(node[Type].as()).convert(&ok); + if (!ok) + return false; + switch (colliderType) + { + case SHCollisionShape::Type::BOX: + { + if (node[HalfExtents].IsDefined()) + rhs.SetBoundingBox(node[HalfExtents].as()); + } + break; + case SHCollisionShape::Type::SPHERE: + { + if (node[Radius].IsDefined()) + rhs.SetBoundingSphere(node[Radius].as()); + } + break; + case SHCollisionShape::Type::CAPSULE: break; + default:; + } + if (node[Friction].IsDefined()) + rhs.SetFriction(node[Friction].as()); + if (node[Bounciness].IsDefined()) + rhs.SetBounciness(rhs.GetBounciness()); + if (node[Density].IsDefined()) + rhs.SetDensity(node[Density].as()); + if (node[PositionOffset].IsDefined()) + rhs.SetPositionOffset(node[PositionOffset].as()); + + return true; + } + }; + + template<> + struct convert + { + static constexpr const char* Colliders = "Colliders"; + static Node encode(SHColliderComponent& rhs) + { + Node node, collidersNode; + auto const& colliders = rhs.GetCollisionShapes(); + int const numColliders = static_cast(colliders.size()); + for (int i = 0; i < numColliders; ++i) + { + auto& collider = rhs.GetCollisionShape(i); + Node colliderNode = convert::encode(collider); + if (colliderNode.IsDefined()) + collidersNode[i] = colliderNode; + } + node[Colliders] = collidersNode; + return node; + } + static bool decode(Node const& node, SHColliderComponent& rhs) + { + if (node[Colliders].IsDefined()) + { + int numColliders{}; + for (auto const& colliderNode : node[Colliders]) + { + rttr::type const shapeRttrType = rttr::type::get(); + rttr::enumeration const enumAlign = shapeRttrType.get_enumeration(); + bool ok = false; + const SHCollisionShape::Type colliderType = enumAlign.name_to_value(colliderNode[convert::Type].as()).convert(&ok); + if (!ok) + return false; + + switch (colliderType) + { + case SHCollisionShape::Type::BOX: rhs.AddBoundingBox(); break; + case SHCollisionShape::Type::SPHERE: rhs.AddBoundingSphere(); break; + case SHCollisionShape::Type::CAPSULE: break; + default:; + } + YAML::convert::decode(colliderNode, rhs.GetCollisionShape(numColliders++)); + } + } + return true; + } + }; + + template<> + struct convert + { + static constexpr std::string_view VERT_SHADER_YAML_TAG = "VertexShader"; + static constexpr std::string_view FRAG_SHADER_YAML_TAG = "FragmentShader"; + static constexpr std::string_view SUBPASS_YAML_TAG = "SubPass"; + static constexpr std::string_view PROPS_YAML_TAG = "Properties"; + + static YAML::Node encode(SHMaterialSpec const& rhs) + { + YAML::Node node; + node[VERT_SHADER_YAML_TAG.data()] = rhs.vertexShader; + node[FRAG_SHADER_YAML_TAG.data()] = rhs.fragShader; + node[SUBPASS_YAML_TAG.data()] = rhs.subpassName; + node[PROPS_YAML_TAG.data()] = rhs.properties; + return node; + } + + static bool decode(YAML::Node const& node, SHMaterialSpec& rhs) + { + // Retrieve Shader Asset IDs + if (node[VERT_SHADER_YAML_TAG.data()].IsDefined()) + rhs.vertexShader = node[VERT_SHADER_YAML_TAG.data()].as(); + if (node[FRAG_SHADER_YAML_TAG.data()].IsDefined()) + rhs.fragShader = node[FRAG_SHADER_YAML_TAG.data()].as(); + + // Retrieve Subpass + if (node[SUBPASS_YAML_TAG.data()].IsDefined()) + rhs.subpassName = node[SUBPASS_YAML_TAG.data()].as(); + + // Retrieve + if (node[PROPS_YAML_TAG.data()].IsDefined()) + rhs.properties = node[PROPS_YAML_TAG.data()]; + + return true; + } + }; + + template<> + struct convert + { + static constexpr std::string_view MESH_YAML_TAG = "Mesh"; + static constexpr std::string_view MAT_YAML_TAG = "Material"; + + static YAML::Node encode(SHRenderable const& rhs) + { + YAML::Node node; + node[MESH_YAML_TAG.data()] = SHResourceManager::GetAssetID(rhs.GetMesh()).value_or(0); + auto mat = rhs.GetMaterial(); + if (mat) + { + node[MAT_YAML_TAG.data()] = SHResourceManager::GetAssetID(rhs.GetMaterial()->GetBaseMaterial()).value_or(0); + } + else + { + node[MAT_YAML_TAG.data()] = 0; + } + return node; + } + static bool decode(YAML::Node const& node, SHRenderable& rhs) + { + if (node[MESH_YAML_TAG.data()].IsDefined()) + { + rhs.SetMesh(SHResourceManager::LoadOrGet(node[MESH_YAML_TAG.data()].as())); + } + if (node[MAT_YAML_TAG.data()].IsDefined()) + { + // Temporarily, use default material + auto gfxSystem = SHSystemManager::GetSystem(); + if (!gfxSystem) + return false; + Handle baseMat = SHResourceManager::LoadOrGet(node[MAT_YAML_TAG.data()].as()); + if (!baseMat) + { + baseMat = gfxSystem->GetDefaultMaterial(); + SHLog::Warning("[SHSerializationHelper] Unable to load specified material. Falling back to default material."); + } + rhs.SetMaterial(gfxSystem->AddOrGetBaseMaterialInstance(baseMat)); + } + return true; + } + }; +} diff --git a/SHADE_Engine/src/Tools/Dialog/SHWinDialog.cpp b/SHADE_Engine/src/Tools/Dialog/SHWinDialog.cpp new file mode 100644 index 00000000..bd13801a --- /dev/null +++ b/SHADE_Engine/src/Tools/Dialog/SHWinDialog.cpp @@ -0,0 +1,71 @@ +#include "SHpch.h" +#include "SHWinDialog.h" +#include +#include + +namespace SHADE +{ + void SHWinDialog::DisplayMessageBox(MessageBoxType const& messageBoxType, std::string const& title, std::string const& text) + { + if(messageBoxType == MessageBoxType::MB_MAX) + return; + + UINT flags = MB_APPLMODAL | MB_SETFOREGROUND | MB_OK; + flags |= static_cast(messageBoxType); + + const std::wstring wTitle(title.begin(), title.end()); + const std::wstring wText(text.begin(), text.end()); + + MessageBox(GetDesktopWindow(), wText.data(), wTitle.data(), flags); + } + + std::vector SHWinDialog::DisplayOpenDialog(OpenSaveConfig const& openSaveConfig) + { + const HWND hwnd = GetDesktopWindow(); + HRESULT hResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if(SUCCEEDED(hResult)) + { + IFileOpenDialog* pFileOpen; + + //Create Dialog object + hResult = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast(pFileOpen)); + + if(SUCCEEDED(hResult)) + { + //Show the open dialog box + hResult = pFileOpen->Show(hwnd); + + //Get file name from the dialoh box + if(SUCCEEDED(hResult)) + { + if(openSaveConfig.openMultiple) + { + IShellItemArray* pItemArray; + hResult = pFileOpen->GetResults(&pItemArray); + if(SUCCEEDED(hResult)) + { + + } + } + else + { + IShellItem* pItem; + hResult = pFileOpen->GetResult(&pItem); + if(SUCCEEDED(hResult)) + { + PWSTR pszFilePath; + hResult = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); + } + } + + } + } + } + return {}; + } + + std::vector SHWinDialog::DisplaySaveAsDialog(OpenSaveConfig const& openSaveConfig) + { + return{}; + } +} diff --git a/SHADE_Engine/src/Tools/Dialog/SHWinDialog.h b/SHADE_Engine/src/Tools/Dialog/SHWinDialog.h new file mode 100644 index 00000000..02fe07b9 --- /dev/null +++ b/SHADE_Engine/src/Tools/Dialog/SHWinDialog.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +namespace SHADE +{ + //https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox + enum class MessageBoxType + { + MB_ERROR = 0x00000010L, + MB_QUESTION = 0x00000020L, + MB_WARNING = 0x00000030L, + MB_INFO = 0x00000040L, + MB_MAX = 0 + }; + + struct OpenSaveConfig + { + using Extension = std::string; + using Extensions = std::vector; + using FileTypeDesc = std::pair; + using FilterList = std::vector; + + std::string title = "Open"; + bool openFolders = false; + bool openMultiple = false; + FilterList filterList{}; + }; + + struct SHWinDialog + { + static void DisplayMessageBox(MessageBoxType const& messageBoxType, std::string const& title, std::string const& text); + static std::vector DisplayOpenDialog(OpenSaveConfig const& openSaveConfig); + static std::vector DisplaySaveAsDialog(OpenSaveConfig const& openSaveConfig); + }; +} diff --git a/SHADE_Engine/src/Tools/FileIO/SHFieIO.cpp b/SHADE_Engine/src/Tools/FileIO/SHFieIO.cpp new file mode 100644 index 00000000..c4fec120 --- /dev/null +++ b/SHADE_Engine/src/Tools/FileIO/SHFieIO.cpp @@ -0,0 +1,31 @@ +#include "SHpch.h" +#include "SHFileIO.h" +#include + +namespace SHADE +{ + int SHFileIO::WriteStringToFile(std::filesystem::path const& filePath, std::string_view const& strView) + { + std::ofstream file(filePath); + if(file.good()) + { + file << strView; + file.close(); + return 1; + } + return 0; + } + + std::string SHFileIO::GetStringFromFile(std::filesystem::path const& filePath) + { + std::ifstream iFile; + iFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + std::string fileData{}; + iFile.open(filePath, std::iostream::binary); + std::stringstream ss; + ss << iFile.rdbuf(); + fileData = ss.str(); + iFile.close(); + return fileData; + } +} diff --git a/SHADE_Engine/src/Tools/FileIO/SHFileIO.h b/SHADE_Engine/src/Tools/FileIO/SHFileIO.h new file mode 100644 index 00000000..adc95d5b --- /dev/null +++ b/SHADE_Engine/src/Tools/FileIO/SHFileIO.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include +#include + +namespace SHADE +{ + struct SH_API SHFileIO + { + static int WriteStringToFile(std::filesystem::path const& filePath, std::string_view const& strView); + static std::string GetStringFromFile(std::filesystem::path const& file); + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHClipboardUtilities.cpp b/SHADE_Engine/src/Tools/SHClipboardUtilities.cpp new file mode 100644 index 00000000..061a5ec6 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHClipboardUtilities.cpp @@ -0,0 +1,49 @@ +#include "SHpch.h" +#include "SHClipboardUtilities.h" + +namespace SHADE +{ + void SHClipboardUtilities::WriteToClipboard(std::string const& str) noexcept + { + if(str.empty()) + return; + HWND const hwnd = GetDesktopWindow(); + OpenClipboard(hwnd); + EmptyClipboard(); + + auto const size = str.size() + 1; + const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, size); + if(hGlobal) + { + std::memcpy(GlobalLock(hGlobal), str.c_str(), size); + GlobalUnlock(hGlobal); + SetClipboardData(CF_TEXT, hGlobal); + } + else + { + SHLOG_ERROR("Failed to write to clipboard: {}", str.c_str()) + } + CloseClipboard(); + GlobalFree(hGlobal); + } + + std::string const SHClipboardUtilities::GetDataFromClipboard() noexcept + { + HWND const hwnd = GetDesktopWindow(); + if(!OpenClipboard(hwnd)) + { + SHLOG_ERROR("Failed to open clipboard") + return std::string(); + } + + if(HANDLE const dataHandle = GetClipboardData(CF_TEXT); dataHandle) + { + std::string data(static_cast(GlobalLock(dataHandle))); + GlobalUnlock(dataHandle); + CloseClipboard(); + return data; + } + CloseClipboard(); + return std::string(); + } +} diff --git a/SHADE_Engine/src/Tools/SHClipboardUtilities.h b/SHADE_Engine/src/Tools/SHClipboardUtilities.h new file mode 100644 index 00000000..7f3acdbe --- /dev/null +++ b/SHADE_Engine/src/Tools/SHClipboardUtilities.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace SHADE +{ + class SHClipboardUtilities + { + public: + static void WriteToClipboard(std::string const& str) noexcept; + static std::string const GetDataFromClipboard() noexcept; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp new file mode 100644 index 00000000..b8aa8b0e --- /dev/null +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -0,0 +1,109 @@ +/************************************************************************************//*! +\file SHDebugDrawSystem.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 2, 2022 +\brief Contains the definition of functions of the SHDebugDrawSystem class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Header +#include "SHpch.h" +// Primary Include +#include "SHDebugDraw.h" +// Project Includes +#include "Math/Vector/SHVec4.h" +#include "Math/SHColour.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" +#include "ECS_Base/Managers/SHSystemManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHDebugDrawSystem* SHDebugDraw::dbgDrawSys = nullptr; + + /*-----------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDraw::Init(SHDebugDrawSystem* sys) + { + dbgDrawSys = sys; + if (dbgDrawSys == nullptr) + { + SHLOG_ERROR("[SHDebugDraw] Invalid SHDebugDrawSystem provided for initialization!"); + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDraw::Line(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + { + dbgDrawSys->DrawLine(color, startPt, endPt); + } + + void SHDebugDraw::Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + { + dbgDrawSys->DrawTri(color, pt1, pt2, pt3); + } + + void SHDebugDraw::Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + { + dbgDrawSys->DrawQuad(color, pt1, pt2, pt3, pt4); + } + + void SHDebugDraw::Poly(const SHVec4& color, std::initializer_list pointList) + { + dbgDrawSys->DrawPoly(color, pointList); + } + + void SHDebugDraw::Cube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + { + dbgDrawSys->DrawCube(color, pos, size); + } + + void SHDebugDraw::Sphere(const SHVec4& color, const SHVec3& pos, double radius) + { + dbgDrawSys->DrawSphere(color, pos, radius); + } + + void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + { + dbgDrawSys->DrawPersistentLine(color, startPt, endPt); + } + + void SHDebugDraw::PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + { + dbgDrawSys->DrawPersistentTri(color, pt1, pt2, pt3); + } + + void SHDebugDraw::PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + { + dbgDrawSys->DrawPersistentQuad(color, pt1, pt2, pt3, pt4); + } + + void SHDebugDraw::PersistentPoly(const SHVec4& color, std::initializer_list pointList) + { + dbgDrawSys->DrawPersistentPoly(color, pointList); + } + + void SHDebugDraw::PersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + { + dbgDrawSys->DrawPersistentCube(color, pos, size); + } + + void SHDebugDraw::PersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) + { + dbgDrawSys->DrawPersistentSphere(color, pos, radius); + } + + void SHDebugDraw::ClearPersistentDraws() + { + dbgDrawSys->ClearPersistentDraws(); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h new file mode 100644 index 00000000..04504c3a --- /dev/null +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -0,0 +1,159 @@ +/************************************************************************************//*! +\file SHDebugDraw.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 2, 2022 +\brief Contains the definition of the SHDebugDraw static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHDebugDrawSystem; + class SHVec4; + class SHVec3; + class SHColour; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /// + /// Static helper class to make it easier to do debug drawing and enable support for + /// managed code to execute it withot dependency hell due to graphics system + /// dependencies. + /// + class SH_API SHDebugDraw final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Sets up the link with the SHDebugDrawSystem. Must be called after the + /// SHDebugDrawSystem is spawned. + /// + static void Init(SHDebugDrawSystem* sys); + + /*---------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space. + /// + /// Colour of the line. + /// First point of the line. + /// Second point of the line. + static void Line(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + /// + /// Renders a triangle indicated by three points in world space. + /// + /// Colour of the triangle. + /// First point of the triangle. + /// Second point of the triangle. + /// Third point of the triangle. + static void Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); + /// + /// Renders a quadrilateral indicated by four points in world space. + /// + /// Colour of the quadrilateral. + /// First point of the triangle. + /// Second point of the quadrilateral. + /// Third point of the quadrilateral. + /// Third point of the quadrilateral. + static void Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + /// + /// Renders a polygon indicated by the specified set of points in world space. + /// + /// Colour of the polygon. + /// List of points for the polygon. + static void Poly(const SHVec4& color, std::initializer_list pointList); + /// + /// Renders a wireframe cube centered around the position specified in world space. + /// + /// Colour of the cube. + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + static void Cube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); + /// + /// Renders a wireframe sphere centered around the position specified in world space. + /// + /// Colour of the sphere. + /// Position where the sphere wil be centered at. + /// Size of the rendered sphere. + static void Sphere(const SHVec4& color, const SHVec3& pos, double radius); + + /*---------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space that will persist until + /// ClearPersistentDraws() is called. + /// + /// Colour of the line. + /// First point of the line. + /// Second point of the line. + static void PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + /// + /// Renders a triangle indicated by three points in world space that will persist + /// until ClearPersistentDraws() is called. + /// + /// Colour of the triangle. + /// First point of the triangle. + /// Second point of the triangle. + /// Third point of the triangle. + static void PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); + /// + /// Renders a quadrilateral indicated by four points in world space that will persist + /// until ClearPersistentDraws() is called. + /// + /// Colour of the quadrilateral. + /// First point of the triangle. + /// Second point of the quadrilateral. + /// Third point of the quadrilateral. + /// Third point of the quadrilateral. + static void PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + /// + /// Renders a polygon indicated by the specified set of points in world space that + /// will persist until ClearPersistentDraws() is called. + /// + /// Colour of the polygon. + /// List of points for the polygon. + static void PersistentPoly(const SHVec4& color, std::initializer_list pointList); + /// + /// Renders a wireframe cube centered around the position specified in world space + /// that will persist until ClearPersistentDraws() is called. + /// + /// Colour of the cube. + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + static void PersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); + /// + /// Renders a wireframe sphere centered around the position specified in world space + /// that will persist until ClearPersistentDraws() is called. + /// + /// Colour of the sphere. + /// Position where the sphere wil be centered at. + /// Size of the rendered sphere. + static void PersistentSphere(const SHVec4& color, const SHVec3& pos, double radius); + /// + /// Clears any persistent drawn debug primitives. + /// + static void ClearPersistentDraws(); + + private: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + static SHDebugDrawSystem* dbgDrawSys; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDeque.h b/SHADE_Engine/src/Tools/SHDeque.h new file mode 100644 index 00000000..99df910a --- /dev/null +++ b/SHADE_Engine/src/Tools/SHDeque.h @@ -0,0 +1,69 @@ +#pragma once +#pragma once + +#include "SH_API.h" +#include + +namespace SHADE +{ + template + class SH_API SHDeque + { + public: + using ValueType = T; + using Pointer = T*; + using ValueRef = T&; + using ValueConstRef = T const&; + using SizeType = uint32_t; + using ContainerType = std::deque; + using ContainerTypeConstRef = std::deque; + + SHDeque(SizeType n) : max_size(n) {} + + ContainerTypeConstRef const& GetDeque() const + { + return deque; + } + + void Push(ValueConstRef obj) + { + if (deque.size() < max_size) + deque.push_front(std::move(obj)); + else + { + deque.pop_back(); + deque.push_front(std::move(obj)); + } + } + + bool Empty() + { + return deque.empty(); + } + + void Pop() + { + deque.pop_front(); + } + + ValueConstRef Top() + { + return deque.front(); + } + + SizeType Size() const noexcept + { + return deque.size(); + } + + void Clear() + { + deque.clear(); + } + + private: + int max_size; + ContainerType deque{}; + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHException.h b/SHADE_Engine/src/Tools/SHException.h index 251217eb..2e0b82a9 100644 --- a/SHADE_Engine/src/Tools/SHException.h +++ b/SHADE_Engine/src/Tools/SHException.h @@ -75,10 +75,13 @@ namespace SHADE }; } -#define SHASSERT(cond, msg) \ - if (!(cond)) \ - { \ - SHLOGV_CRITICAL(msg) \ - std::abort(); \ - } - +#ifdef _DEBUG + #define SHASSERT(cond, msg) \ + if (!(cond)) \ + { \ + SHLOGV_CRITICAL(msg) \ + std::abort(); \ + } +#else + #define SHASSERT(cond, msg) { } +#endif diff --git a/SHADE_Engine/src/Tools/SHExceptionHandler.h b/SHADE_Engine/src/Tools/SHExceptionHandler.h index dd1d7596..32cda608 100644 --- a/SHADE_Engine/src/Tools/SHExceptionHandler.h +++ b/SHADE_Engine/src/Tools/SHExceptionHandler.h @@ -12,13 +12,15 @@ #include +#include "SH_API.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHExceptionHandler + class SH_API SHExceptionHandler { public: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Tools/SHFileUtilties.cpp b/SHADE_Engine/src/Tools/SHFileUtilties.cpp new file mode 100644 index 00000000..0e75b16a --- /dev/null +++ b/SHADE_Engine/src/Tools/SHFileUtilties.cpp @@ -0,0 +1,30 @@ +/************************************************************************************//*! +\file SHFileUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 15, 2022 +\brief Contains the definition of functions of the SHFileUtilities static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Header +#include "SHpch.h" +// Primary Header +#include "SHFileUtilties.h" +// Standard Libraries +#include +// External Dependencies +#include // GetModuleFileName, PathRemoveFileSpec + +namespace SHADE +{ + void SHFileUtilities::SetWorkDirToExecDir() + { + TCHAR currentExecFilePath[MAX_PATH] = { '\0' }; + GetModuleFileName(nullptr, currentExecFilePath, MAX_PATH); + PathRemoveFileSpec(currentExecFilePath); + std::filesystem::current_path(currentExecFilePath); + } +} diff --git a/SHADE_Engine/src/Tools/SHFileUtilties.h b/SHADE_Engine/src/Tools/SHFileUtilties.h new file mode 100644 index 00000000..b9ba164b --- /dev/null +++ b/SHADE_Engine/src/Tools/SHFileUtilties.h @@ -0,0 +1,38 @@ +/************************************************************************************//*! +\file SHFileUtilities.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 15, 2022 +\brief Contains the SHFileUtilities static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +// Project Headers +#include "SH_API.h" + +namespace SHADE +{ + /*!************************************************************************************ + + \class SHFileUtilities + + \brief + Static class that contains functions for working with files and directories. + + **************************************************************************************/ + class SH_API SHFileUtilities + { + public: + /*!********************************************************************************** + + \brief + Sets the application's current working directory to the application executable's + directory. + + ************************************************************************************/ + static void SetWorkDirToExecDir(); + }; +} diff --git a/SHADE_Engine/src/Tools/SHLog.cpp b/SHADE_Engine/src/Tools/SHLog.cpp new file mode 100644 index 00000000..30a79338 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHLog.cpp @@ -0,0 +1,74 @@ +/************************************************************************************//*! +\file SHLog.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2022 +\brief Contains the definition of functions of the SHLog static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Header +#include "SHpch.h" +// Primary Header +#include "SHLog.h" +// Project Includes +#include "SHLogger.h" + +namespace SHADE +{ + void SHLog::Info(const std::string& msg) noexcept + { + SHLOG_INFO(msg) + } + + void SHLog::Warning(const std::string& msg) noexcept + { + SHLOG_WARNING(msg) + } + + + void SHLog::Error(const std::string& msg) noexcept + { + SHLOG_ERROR(msg) + } + + void SHLog::Critical(const std::string& msg) noexcept + { + SHLOG_CRITICAL(msg) + } + + void SHLog::Floor() noexcept + { + SHLOG_FLOOR() + } + +#ifdef _DEBUG + void SHLog::Trace(const std::string& msg) noexcept + { + SHLOG_TRACE(msg) + } +#endif + + void SHLog_Info(const char* msg) noexcept + { + SHLOG_INFO(msg) + } + + void SHLog_Warning(const char* msg) noexcept + { + SHLOG_WARNING(msg) + } + + void SHLog_Error(const char* msg) noexcept + { + SHLOG_ERROR(msg) + } + + void SHLog_Critical(const char* msg) noexcept + { + SHLOG_CRITICAL(msg) + } + +} diff --git a/SHADE_Engine/src/Tools/SHLog.h b/SHADE_Engine/src/Tools/SHLog.h new file mode 100644 index 00000000..91117da6 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHLog.h @@ -0,0 +1,56 @@ +/************************************************************************************//*! +\file SHLog.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 17, 2022 +\brief Contains the SHLog static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Standard Library +#include +// Project Headers +#include "SH_API.h" + +namespace SHADE +{ + /*!************************************************************************************ + + \class SHLog + + \brief + Static class that contains wrapper functions for SHLogger's macros. + + **************************************************************************************/ + class SH_API SHLog + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructor */ + /*---------------------------------------------------------------------------------*/ + SHLog() = delete; + + /*---------------------------------------------------------------------------------*/ + /* Logging Functions */ + /*---------------------------------------------------------------------------------*/ + static void Info(const std::string& msg) noexcept; + static void Warning(const std::string& msg) noexcept; + static void Error(const std::string& msg) noexcept; + static void Critical(const std::string& msg) noexcept; + static void Floor() noexcept; +#ifdef _DEBUG + static void Trace(const std::string& msg) noexcept; +#endif + }; + + extern "C" + { + static void SHLog_Info(const char* msg) noexcept; + static void SHLog_Warning(const char* msg) noexcept; + static void SHLog_Error(const char* msg) noexcept; + static void SHLog_Critical(const char* msg) noexcept; + } +} diff --git a/SHADE_Engine/src/Tools/SHLogger.cpp b/SHADE_Engine/src/Tools/SHLogger.cpp index 768dc084..9c1e76fc 100644 --- a/SHADE_Engine/src/Tools/SHLogger.cpp +++ b/SHADE_Engine/src/Tools/SHLogger.cpp @@ -30,8 +30,9 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Static Data Member Definitions */ /*-----------------------------------------------------------------------------------*/ - unsigned char SHLogger::configFlags = DEFAULT_CONFIG_FLAG; - SHLogger::DateFormat SHLogger::dateFormat = SHLogger::DateFormat::DD_MM_YY; + unsigned char SHLogger::configFlags = DEFAULT_CONFIG_FLAG; + SHLogger::DateFormat SHLogger::dateFormat = SHLogger::DateFormat::DD_MM_YY; + SHLogger::Logger SHLogger::logger = nullptr; std::string SHLogger::trivialPattern; std::string SHLogger::verbosePattern; @@ -142,7 +143,7 @@ namespace SHADE /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHLogger::Initialise(const Config& config) + SHLogger::Logger SHLogger::Initialise(const Config& config) { SetConfig(config); @@ -176,10 +177,10 @@ namespace SHADE FILE_SINK->set_pattern(trivialPattern + "%v"); // Create and register logger with spdlog - const auto LOGGER = std::make_shared(SHLOGGER_NAME, sinks.begin(), sinks.end()); - LOGGER->set_level(spdlog::level::trace); - LOGGER->flush_on(spdlog::level::trace); - register_logger(LOGGER); + logger = std::make_shared(SHLOGGER_NAME, sinks.begin(), sinks.end()); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::trace); + spdlog::register_logger(logger); // Flush every 3 seconds spdlog::flush_every(std::chrono::seconds(config.flushTime)); @@ -190,6 +191,8 @@ namespace SHADE { std::cout << "Log initialisation failed: " << e.what() << std::endl; } + + return logger; } void SHLogger::Shutdown() noexcept diff --git a/SHADE_Engine/src/Tools/SHLogger.h b/SHADE_Engine/src/Tools/SHLogger.h index 3c5c6f1b..e4a4928c 100644 --- a/SHADE_Engine/src/Tools/SHLogger.h +++ b/SHADE_Engine/src/Tools/SHLogger.h @@ -16,6 +16,7 @@ #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE #include +#include "SH_API.h" /*-------------------------------------------------------------------------------------*/ /* Macros */ @@ -28,13 +29,15 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SHLogger + class SH_API SHLogger { public: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ + using Logger = std::shared_ptr; + enum class ClockFormat { _12HR, _24HR }; enum class DateFormat @@ -71,33 +74,33 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static const std::string& GetTrivialPattern () noexcept { return trivialPattern; } - [[nodiscard]] static const std::string& GetVerbosePattern () noexcept { return verbosePattern; } + [[nodiscard]] static const std::string& GetTrivialPattern () noexcept { return trivialPattern; } + [[nodiscard]] static const std::string& GetVerbosePattern () noexcept { return verbosePattern; } /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - static void SetTrivialPattern (const std::string& pattern) noexcept { trivialPattern = pattern; } - static void SetVerbosePattern (const std::string& pattern) noexcept { verbosePattern = pattern; } + static void SetTrivialPattern (const std::string& pattern) noexcept { trivialPattern = pattern; } + static void SetVerbosePattern (const std::string& pattern) noexcept { verbosePattern = pattern; } - static void SetConfig (const Config& config) noexcept; + static void SetConfig (const Config& config) noexcept; - static void SetShowTime (bool showTime) noexcept; - static void SetShowDate (bool showDate) noexcept; - static void SetShowFunctionFileName (bool showFunctionFileName) noexcept; - static void SetShowFunctionLineNumber (bool showFunctionLineNumber) noexcept; + static void SetShowTime (bool showTime) noexcept; + static void SetShowDate (bool showDate) noexcept; + static void SetShowFunctionFileName (bool showFunctionFileName) noexcept; + static void SetShowFunctionLineNumber (bool showFunctionLineNumber) noexcept; - static void SetClockFormat (ClockFormat newClockFormat) noexcept; - static void SetDateFormat (DateFormat newDateFormat) noexcept; + static void SetClockFormat (ClockFormat newClockFormat) noexcept; + static void SetDateFormat (DateFormat newDateFormat) noexcept; - static void SetFileName (const std::string& logFileName) noexcept; - static void SetDirectoryPath (const std::filesystem::path& logDirectoryPath) noexcept; + static void SetFileName (const std::string& logFileName) noexcept; + static void SetDirectoryPath (const std::filesystem::path& logDirectoryPath) noexcept; - static void SetFlushTime (int seconds) noexcept; - static void SetFlushTime (size_t seconds) noexcept { spdlog::flush_every(std::chrono::seconds(seconds)); } + static void SetFlushTime (int seconds) noexcept; + static void SetFlushTime (size_t seconds) noexcept { spdlog::flush_every(std::chrono::seconds(seconds)); } /*---------------------------------------------------------------------------------*/ /* Function Members */ @@ -107,8 +110,8 @@ namespace SHADE * @brief Creates a console and a file to log to. * @param[in] config The configuration parameters for the logger. */ - static void Initialise (const Config& config = Config{}); - static void Shutdown () noexcept; + static Logger Initialise (const Config& config = Config{}); + static void Shutdown () noexcept; /** * @brief The next message logged by the logger will be set to follow the trivial pattern. @@ -144,7 +147,9 @@ namespace SHADE static constexpr short DEFAULT_CONSOLE_LEN = 1024; static unsigned char configFlags; // Initialised 0 0 FuncLine# FuncFileName Date TimeFormat Time - static DateFormat dateFormat; + static DateFormat dateFormat; + + static Logger logger; static std::string trivialPattern; static std::string verbosePattern; @@ -190,3 +195,6 @@ namespace SHADE // Misc Logging Macros #define SHLOG_FLOOR() SHADE::SHLogger::UseTrivialPattern(); SPDLOG_LOGGER_INFO(spdlog::get(SHLOGGER_NAME), "--------------------------------"); + +// For use outside the library to register the logger +#define SHLOG_REGISTER(logger) spdlog::register_logger(logger); spdlog::set_level(spdlog::level::level_enum::trace); diff --git a/SHADE_Engine/src/Tools/SHStringUtils.cpp b/SHADE_Engine/src/Tools/SHStringUtils.cpp new file mode 100644 index 00000000..a2594888 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.cpp @@ -0,0 +1,52 @@ +/************************************************************************************//*! +\file StringUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the definition of functions for working with strings. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Header +#include +// Primary Header +#include "SHStringUtils.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Utility Functions */ + /*---------------------------------------------------------------------------------*/ + std::vector SHStringUtils::Split(const std::string& str, const char& delim) + { + return Split(str, delim); + } + std::vector SHStringUtils::Split(const std::wstring& str, const wchar_t& delim) + { + return Split(str, delim); + } + std::string SHStringUtils::WstrToStr(const std::wstring& wstr) + { + static std::vector buffer; + const int STR_SIZE = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) + 1 /* Null Terminator */; + buffer.resize(STR_SIZE); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), buffer.data(), MAX_PATH, nullptr, nullptr); + return std::string(buffer.data()); + } + std::wstring SHStringUtils::StrToWstr(const std::string& str) + { + static std::vector buffer; + const int WSTR_SIZE = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0) + 1 /* Null Terminator */; + buffer.resize(WSTR_SIZE); + MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), buffer.data(), WSTR_SIZE); + return std::wstring(buffer.data()); + } + + std::string SHStringUtils::GetWin32ErrorMessage(unsigned long errorCode) + { + return std::system_category().message(errorCode); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHStringUtils.h b/SHADE_Engine/src/Tools/SHStringUtils.h new file mode 100644 index 00000000..1c895b99 --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.h @@ -0,0 +1,81 @@ +/************************************************************************************//*! +\file StringUtilities.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the declaration of functions for working with files and folders. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Standard Libraries +#include // std::basic_string +#include // std::vector + +namespace SHADE +{ + /// + /// Contains useful functions for operating on strings. + /// + class SHStringUtils + { + public: + /*-----------------------------------------------------------------------------*/ + /* Utility Functions */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// + /// Internal type of each element in the string. + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + template + static std::vector> Split(const std::basic_string& str, const T& delim); + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// Overload of Split() to allow for string literals to be accepted. + /// + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + static std::vector Split(const std::string& str, const char& delim); + /// + /// Splits a string separated by a specified delimiter into a vector of strings. + /// Overload of Split() to allow for wide string literals to be accepted. + /// + /// Read only reference to the string to split. + /// Read only reference to the delimiter. + /// Vector of strings that have been split. + static std::vector Split(const std::wstring& str, const wchar_t& delim); + /// + /// Converts a wstring to a string. + /// + /// wstring to convert. + /// The converted wstring in string form. + static std::string WstrToStr(const std::wstring& wstr); + /// + /// Converts a string to a wstring. + /// + /// string to convert. + /// The converted string in wstring form. + static std::wstring StrToWstr(const std::string& str); + /// + /// Retrieves the error message associated with a Win32 error code. + /// + /// Win32 error code to decode. + /// String that represents the Win32 error. + static std::string GetWin32ErrorMessage(unsigned long errorCode); + + private: + /*-------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*-------------------------------------------------------------------------------*/ + SHStringUtils() = delete; + }; +} + +#include "SHStringUtils.hpp" diff --git a/SHADE_Engine/src/Tools/SHStringUtils.hpp b/SHADE_Engine/src/Tools/SHStringUtils.hpp new file mode 100644 index 00000000..8b83187a --- /dev/null +++ b/SHADE_Engine/src/Tools/SHStringUtils.hpp @@ -0,0 +1,46 @@ +/************************************************************************************//*! +\file StringUtilities.hpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 29, 2021 +\brief Contains the implementation of template functions for working with files + and folders. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Primary Header +#include "SHStringUtils.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------------*/ + /* Template Function Definitions */ + /*-------------------------------------------------------------------------------*/ + template + inline std::vector> SHStringUtils::Split(const std::basic_string& str, const T& delim) + { + std::vector> results; + std::basic_string remaining = str; + + // Go through looking for delimiters + while (true) + { + const size_t DELIM_POS = remaining.find_first_of(delim); + results.emplace_back(remaining.substr(0, DELIM_POS)); + + // Check if we hit the end of the string + if (DELIM_POS == remaining.npos) + { + break; + } + + // Otherwise, cut the remainder + remaining = remaining.substr(DELIM_POS + 1); + } + + return results; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHUtilities.h b/SHADE_Engine/src/Tools/SHUtilities.h index 543c771c..6cdd91ee 100644 --- a/SHADE_Engine/src/Tools/SHUtilities.h +++ b/SHADE_Engine/src/Tools/SHUtilities.h @@ -35,13 +35,12 @@ namespace SHADE /** * @brief Converts an enum class member from it's type to any other type. * @tparam InputType Restricted to an enum class - * @tparam OutputType The type to convert the enum class member to. Defaults to int. + * @tparam OutputType The type to convert the enum class member to. Defaults to the underlying type. * @param[in] enumClassMember A member of the specified enum class. * @returns The value of the enum class member in the output type. */ - template + template > static constexpr OutputType ConvertEnum(InputType enumClassMember) noexcept; - }; } // namespace SHADE diff --git a/SHADE_Engine/src/Tools/SHUtilities.hpp b/SHADE_Engine/src/Tools/SHUtilities.hpp index 0e21a9d0..3f0668a2 100644 --- a/SHADE_Engine/src/Tools/SHUtilities.hpp +++ b/SHADE_Engine/src/Tools/SHUtilities.hpp @@ -24,5 +24,4 @@ namespace SHADE { return static_cast(enumClassMember); } - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/UI/SHCanvasComponent.h b/SHADE_Engine/src/UI/SHCanvasComponent.h new file mode 100644 index 00000000..2e9a54f1 --- /dev/null +++ b/SHADE_Engine/src/UI/SHCanvasComponent.h @@ -0,0 +1,35 @@ +#pragma once + +#include "SH_API.h" +#include "ECS_Base/Components/SHComponent.h" + + +namespace SHADE +{ + + class SH_API SHCanvasComponent final: public SHComponent + { + using CanvasSizeType = uint32_t; + + + public: + SHCanvasComponent(); + ~SHCanvasComponent() = default; + + void SetCanvasSize(CanvasSizeType width, CanvasSizeType height) noexcept; + void SetCanvasWidth(CanvasSizeType width) noexcept; + void SetCanvasHeight(CanvasSizeType height) noexcept; + + CanvasSizeType const GetCanvasWidth() const noexcept; + CanvasSizeType const GetCanvasHeight() const noexcept; + + private: + CanvasSizeType width; + CanvasSizeType height; + + + + }; + + +} \ No newline at end of file diff --git a/SHADE_Managed/premake5.lua b/SHADE_Managed/premake5.lua new file mode 100644 index 00000000..64f6e23e --- /dev/null +++ b/SHADE_Managed/premake5.lua @@ -0,0 +1,117 @@ +project "SHADE_Managed" + kind "SharedLib" + language "C++" + clr "NetCore" + dotnetframework "net5.0" + cppdialect "C++17" + targetdir (outputdir) + objdir (interdir) + systemversion "latest" + pchheader "SHpch.h" + pchsource "%{prj.location}/src/SHpch.cpp" + staticruntime "off" + + files + { + "%{prj.location}/src/**.hxx", + "%{prj.location}/src/**.h++", + "%{prj.location}/src/**.cxx", + "%{prj.location}/src/**.h", + "%{prj.location}/src/**.hpp", + "%{prj.location}/src/**.c", + "%{prj.location}/src/**.cpp", + } + + includedirs + { + "%{prj.location}/src", + } + + externalincludedirs + { + "%{IncludeDir.spdlog}/include", + "%{IncludeDir.imgui}", + "%{IncludeDir.imguizmo}", + "%{IncludeDir.imnodes}", + "%{IncludeDir.yamlcpp}", + "%{IncludeDir.SDL}\\include", + "%{IncludeDir.RTTR}/include", + "%{IncludeDir.dotnet}\\include", + "%{IncludeDir.reactphysics3d}\\include", + "%{IncludeDir.VULKAN}\\include", + "%{wks.location}/SHADE_Engine/src" + } + + libdirs + { + "%{IncludeDir.RTTR}/lib", + "%{IncludeDir.SDL}/lib" + } + + links + { + "yaml-cpp", + "imgui", + "SDL2.lib", + "SDL2main.lib", + "SHADE_Engine", + "SHADE_CSharp" + } + + disablewarnings + { + "4251" + } + + defines + { + "NOMINMAX" + } + + flags + { + "MultiProcessorCompile" + } + + disablewarnings + { + "4275", + "4635" + } + + + dependson + { + "yaml-cpp", + "imgui", + "SHADE_Engine" + } + + warnings 'Extra' + + filter "configurations:Debug" + symbols "On" + defines {"_DEBUG"} + links{"librttr_core_d.lib"} + + filter "configurations:Release" + optimize "On" + defines{"_RELEASE"} + links{"librttr_core.lib"} + + filter "configurations:Publish" + optimize "On" + defines{"_RELEASE"} + links{"librttr_core.lib"} + + require "vstudio" + + function docsElementCPP(cfg) + _p(3,'true') + end + + premake.override(premake.vstudio.vc2010.elements, "clCompile", function (oldfn, cfg) + return table.join(oldfn(cfg), { + docsElementCPP, + }) + end) \ No newline at end of file diff --git a/SHADE_Managed/src/AssemblyInfo.cxx b/SHADE_Managed/src/AssemblyInfo.cxx new file mode 100644 index 00000000..234bda73 --- /dev/null +++ b/SHADE_Managed/src/AssemblyInfo.cxx @@ -0,0 +1,39 @@ +/************************************************************************************//*! +\file AssemblyInfo.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 24, 2021 +\brief Defines the properties of this managed .NET Assembly. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" + +/*-------------------------------------------------------------------------------------*/ +/* Using Declarations */ +/*-------------------------------------------------------------------------------------*/ +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +/*-------------------------------------------------------------------------------------*/ +/* Assembly Properties */ +/*-------------------------------------------------------------------------------------*/ +[assembly:AssemblyTitleAttribute(L"SHADE_Managed")]; +[assembly:AssemblyDescriptionAttribute(L"")]; +[assembly:AssemblyConfigurationAttribute(L"")]; +[assembly:AssemblyCompanyAttribute(L"")]; +[assembly:AssemblyProductAttribute(L"SHADE_Managed")]; +[assembly:AssemblyCopyrightAttribute(L"Copyright (C) 2022 DigiPen Institute of Technology")]; +[assembly:AssemblyTrademarkAttribute(L"")]; +[assembly:AssemblyCultureAttribute(L"")]; + +[assembly:AssemblyVersionAttribute("1.0.*")]; + +[assembly:ComVisible(false)]; diff --git a/SHADE_Managed/src/Assets/Material.cxx b/SHADE_Managed/src/Assets/Material.cxx new file mode 100644 index 00000000..f4262c2a --- /dev/null +++ b/SHADE_Managed/src/Assets/Material.cxx @@ -0,0 +1,119 @@ +/************************************************************************************//*! +\file Material.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the implementation of the functions of the managed Material + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Material.hxx" +// Standard Library +#include +// Project Includes +#include "Utility/Convert.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Macro Definitions */ + /*---------------------------------------------------------------------------------*/ + #define SET_PROP(NATIVE_TYPE, MANAGED_TYPE) \ + (T::typeid == MANAGED_TYPE::typeid) \ + { \ + const NATIVE_TYPE VAL = safe_cast(System::Convert::ChangeType(value, MANAGED_TYPE::typeid)); \ + NativeObject->SetProperty(PROP_NAME, VAL); \ + } \ + + #define SET_PROP_CONVERT(NATIVE_TYPE, MANAGED_TYPE) \ + (T::typeid == MANAGED_TYPE::typeid) \ + { \ + const NATIVE_TYPE VAL = Convert::ToNative(safe_cast(System::Convert::ChangeType(value, MANAGED_TYPE::typeid))); \ + NativeObject->SetProperty(PROP_NAME, VAL); \ + } \ + + #define GET_PROP(NATIVE_TYPE, MANAGED_TYPE) \ + (T::typeid == MANAGED_TYPE::typeid) \ + { \ + return safe_cast(NativeObject->GetProperty(PROP_NAME)); \ + } \ + + #define GET_PROP_CONVERT(NATIVE_TYPE, MANAGED_TYPE) \ + (T::typeid == MANAGED_TYPE::typeid) \ + { \ + return safe_cast(Convert::ToCLI(NativeObject->GetProperty(PROP_NAME))); \ + } + + /*---------------------------------------------------------------------------------*/ + /* Explicit Template Instantiation */ + /*---------------------------------------------------------------------------------*/ + template ref class NativeAsset; + + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*---------------------------------------------------------------------------------*/ + Material::Material(Handle material) + : NativeAsset{ material } + {} + + /*---------------------------------------------------------------------------------*/ + /* Material Properties Functions */ + /*---------------------------------------------------------------------------------*/ + generic + void Material::SetProperty(System::String^ name, T value) + { + if (!NativeObject) + throw gcnew System::InvalidOperationException("Attempted to set property on an invalid material!"); + + // Call the correct one based on type + const std::string PROP_NAME = Convert::ToNative(name); + try + { + if SET_PROP (int, System::Int32) + else if SET_PROP (int, System::Int64) + else if SET_PROP (float, float) + else if SET_PROP (double, double) + else if SET_PROP_CONVERT(SHVec2, Vector2) + else if SET_PROP_CONVERT(SHVec3, Vector3) + // TODO: Vector4 + } + catch (const std::invalid_argument&) + { + throw gcnew System::ArgumentException("Attempted to modify an invalid property on a material."); + } + } + + generic + T Material::GetProperty(System::String^ name) + { + if (!NativeObject) + throw gcnew System::InvalidOperationException("[Material] Attempted to get property of an invalid material!"); + + // Call the correct one based on type + const std::string PROP_NAME = Convert::ToNative(name); + try + { + if GET_PROP (int, System::Int32) + else if GET_PROP (int, System::Int64) + else if GET_PROP (float, float) + else if GET_PROP (double, double) + else if GET_PROP_CONVERT(SHVec2, Vector2) + else if GET_PROP_CONVERT(SHVec3, Vector3) + // TODO: Vector4 + } + catch (const std::invalid_argument&) + { + throw gcnew System::ArgumentException("Attempted to retrieve a property on a material with an invalid type."); + } + + throw gcnew System::ArgumentException("Attempted to retrieve an invalid property on a material."); + } +} diff --git a/SHADE_Managed/src/Assets/Material.hxx b/SHADE_Managed/src/Assets/Material.hxx new file mode 100644 index 00000000..25cc96a6 --- /dev/null +++ b/SHADE_Managed/src/Assets/Material.hxx @@ -0,0 +1,81 @@ +/************************************************************************************//*! +\file Material.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the managed Mesh class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Resource/SHHandle.h" +#include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" +// Project Includes +#include "NativeAsset.hxx" +#include "Engine/GenericHandle.hxx" + +namespace SHADE +{ + /// + /// Managed counterpart of the native MaterialInstance object containing material + /// data that can be fed to Renderables for rendering. + /// + public ref class Material : public NativeAsset + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for the Material + /// + /// Handle to the native material object. + Material(Handle material); + + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + // TODO: Change Shader + + /*-----------------------------------------------------------------------------*/ + /* Material Properties Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Set the value of a specific property. + /// + /// Type of property to set. + /// Name of the property to set. + /// Value to set te property to. + /// + /// If this Material object is invalid. + /// + /// + /// If the name or type was specified that does not match the material's shader's + /// defined properties. + /// + generic + void SetProperty(System::String^ name, T value); + /// + /// Retrieves the value of a specified property on the material. + /// + /// Type of property to get. + /// Name of the property to get. + /// Value of that property on the material. + /// + /// If this Material object is invalid. + /// + /// + /// If the name or type was specified that does not match the material's shader's + /// defined properties. + /// + generic + T GetProperty(System::String^ name); + }; +} diff --git a/SHADE_Managed/src/Assets/Mesh.cxx b/SHADE_Managed/src/Assets/Mesh.cxx new file mode 100644 index 00000000..95a61ff6 --- /dev/null +++ b/SHADE_Managed/src/Assets/Mesh.cxx @@ -0,0 +1,34 @@ +/************************************************************************************//*! +\file Mesh.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the implementation of the functions of the managed Mesh class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Mesh.hxx" +// Project Headers +#include "Utility/Convert.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Explicit Template Instantiation */ + /*---------------------------------------------------------------------------------*/ + template ref class NativeAsset; + + /*---------------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*---------------------------------------------------------------------------------*/ + Mesh::Mesh(Handle mesh) + : NativeAsset { mesh } + {} +} diff --git a/SHADE_Managed/src/Assets/Mesh.hxx b/SHADE_Managed/src/Assets/Mesh.hxx new file mode 100644 index 00000000..8cd356ba --- /dev/null +++ b/SHADE_Managed/src/Assets/Mesh.hxx @@ -0,0 +1,41 @@ +/************************************************************************************//*! +\file Mesh.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the managed Mesh class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Resource/SHHandle.h" +#include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" +// Project Includes +#include "NativeAsset.hxx" +#include "Engine/GenericHandle.hxx" + +namespace SHADE +{ + /// + /// Managed counterpart of the native Mesh object containing vertex data that can + /// be fed to Renderables for rendering. + /// + public ref class Mesh : public NativeAsset + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for the Mesh + /// + /// Handle to the mesh object. + Mesh(Handle mesh); + }; +} diff --git a/SHADE_Managed/src/Assets/NativeAsset.cxx b/SHADE_Managed/src/Assets/NativeAsset.cxx new file mode 100644 index 00000000..674207a1 --- /dev/null +++ b/SHADE_Managed/src/Assets/NativeAsset.cxx @@ -0,0 +1,26 @@ +/************************************************************************************//*! +\file NativeAsset.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the explicit template instantiation for some types of the + templated managed NativeAsset class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +// Primary Include +#include "NativeAsset.hxx" +// Project Includes +#include "Engine/GenericHandle.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Explicit Tempalte Instantiations */ + /*---------------------------------------------------------------------------------*/ +} \ No newline at end of file diff --git a/SHADE_Managed/src/Assets/NativeAsset.h++ b/SHADE_Managed/src/Assets/NativeAsset.h++ new file mode 100644 index 00000000..a4cd94b4 --- /dev/null +++ b/SHADE_Managed/src/Assets/NativeAsset.h++ @@ -0,0 +1,49 @@ +/************************************************************************************//*! +\file NativeAsset.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of templated functions for the managed + NativeAsset classes. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Include +#include "NativeAsset.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + template + GenericHandle NativeAsset::NativeObjectHandle::get() + { + return nativeObjHandle; + } + template + Handle NativeAsset::NativeObject::get() + try + { + return Handle(Convert::ToNative(nativeObjHandle)); + } + catch (const BadHandleCastException&) + { + return Handle(); // Null handle + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + template + NativeAsset::NativeAsset(Handle nativeObj) + : nativeObjHandle{ Convert::ToCLI(Handle(nativeObj)) } + {} + +} diff --git a/SHADE_Managed/src/Assets/NativeAsset.hxx b/SHADE_Managed/src/Assets/NativeAsset.hxx new file mode 100644 index 00000000..68addb75 --- /dev/null +++ b/SHADE_Managed/src/Assets/NativeAsset.hxx @@ -0,0 +1,66 @@ +/************************************************************************************//*! +\file NativeAsset.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the template definition of the managed class that represents + native assets with a pointer to the corresponding native object. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +#include "Engine/GenericHandle.hxx" + +namespace SHADE +{ + /// + /// Generalised template class for a managed representation of a native asset + /// + /// + /// The type of the asset's native representation. + /// + template + public ref class NativeAsset + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Generic handle for the native object + /// + property GenericHandle NativeObjectHandle + { + GenericHandle get(); + } + /// + /// Copy of the Handle to the native object. + /// + property Handle NativeObject + { + Handle get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Constructors/Destructor */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for the native asset + /// + /// Native asset object. + NativeAsset(Handle ptr); + + protected: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + GenericHandle nativeObjHandle; + }; +} + +#include "NativeAsset.h++" diff --git a/SHADE_Managed/src/Components/Camera.cxx b/SHADE_Managed/src/Components/Camera.cxx new file mode 100644 index 00000000..0d0dbced --- /dev/null +++ b/SHADE_Managed/src/Components/Camera.cxx @@ -0,0 +1,136 @@ +#include "SHpch.h" + +#include "Camera.hxx" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Camera/SHCameraSystem.h" + + +namespace SHADE +{ + Camera::Camera(Entity entity) + :Component(entity) + { + + } + + + float Camera::Pitch::get() + { + return (GetNativeComponent()->GetPitch()); + } + + void Camera::Pitch::set(float val) + { + GetNativeComponent()->SetPitch(val); + } + float Camera::Yaw::get() + { + return (GetNativeComponent()->GetYaw()); + } + + void Camera::Yaw::set(float val) + { + GetNativeComponent()->SetYaw(val); + } + float Camera::Roll::get() + { + return (GetNativeComponent()->GetRoll()); + } + + void Camera::Roll::set(float val) + { + GetNativeComponent()->SetRoll(val); + } + float Camera::Width::get() + { + return (GetNativeComponent()->GetWidth()); + } + + void Camera::Width::set(float val) + { + GetNativeComponent()->SetWidth(val); + } + float Camera::Height::get() + { + return (GetNativeComponent()->GetHeight()); + } + + void Camera::Height::set(float val) + { + GetNativeComponent()->SetHeight(val); + } + float Camera::Near::get() + { + return (GetNativeComponent()->GetNear()); + } + + void Camera::Near::set(float val) + { + GetNativeComponent()->SetNear(val); + } + float Camera::Far::get() + { + return (GetNativeComponent()->GetFar()); + } + + void Camera::Far::set(float val) + { + GetNativeComponent()->SetFar(val); + } + float Camera::FOV::get() + { + return (GetNativeComponent()->GetFOV()); + } + + void Camera::FOV::set(float val) + { + GetNativeComponent()->SetFOV(val); + } + + Vector3 Camera::Position::get() + { + return Convert::ToCLI(GetNativeComponent()->GetPosition()); + } + + void Camera::Position::set(Vector3 val) + { + GetNativeComponent()->SetPosition(Convert::ToNative(val)); + } + + void Camera::SetMainCamera(size_t directorIndex) + { + auto system = SHSystemManager::GetSystem(); + system->SetMainCamera(GetNativeComponent()->GetEID(), directorIndex); + } + + void Camera::SetMainCamera() + { + SetMainCamera(0); + } + + void Camera::LookAt(Vector3 targetPosition) + { + auto system = SHSystemManager::GetSystem(); + system->CameraLookAt(*GetNativeComponent(), Convert::ToNative(targetPosition)); + } + + Vector3 Camera::GetForward() + { + auto system = SHSystemManager::GetSystem(); + SHVec3 forward, up, right; + system->GetCameraAxis(*GetNativeComponent(), forward, right, up); + return Convert::ToCLI(forward); + + } + + Vector3 Camera::GetRight() + { + auto system = SHSystemManager::GetSystem(); + SHVec3 forward, up, right; + system->GetCameraAxis(*GetNativeComponent(), forward, right, up); + return Convert::ToCLI(right); + + } + + +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/Camera.hxx b/SHADE_Managed/src/Components/Camera.hxx new file mode 100644 index 00000000..c6afeb6d --- /dev/null +++ b/SHADE_Managed/src/Components/Camera.hxx @@ -0,0 +1,72 @@ +#pragma once + +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" +#include "Math/Quaternion.hxx" +// External Dependencies +#include "Camera/SHCameraComponent.h" + +namespace SHADE +{ + public ref class Camera : public Component + { + internal: + Camera(Entity entity); + + public: + property float Pitch + { + float get(); + void set(float val); + } + property float Yaw + { + float get(); + void set(float val); + } + property float Roll + { + float get(); + void set(float val); + } + property float Width + { + float get(); + void set(float val); + } + property float Height + { + float get(); + void set(float val); + } + property float Near + { + float get(); + void set(float val); + } + property float Far + { + float get(); + void set(float val); + } + property float FOV + { + float get(); + void set(float val); + } + property Vector3 Position + { + Vector3 get(); + void set(Vector3 val); + } + + + void SetMainCamera(size_t directorIndex); + void SetMainCamera(); + void LookAt(Vector3 targetPosition); + Vector3 GetForward(); + Vector3 GetRight(); + + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/CameraArm.cxx b/SHADE_Managed/src/Components/CameraArm.cxx new file mode 100644 index 00000000..67fcf6cf --- /dev/null +++ b/SHADE_Managed/src/Components/CameraArm.cxx @@ -0,0 +1,51 @@ +#include "SHpch.h" +#include "CameraArm.hxx" + + +namespace SHADE +{ + CameraArm::CameraArm(Entity entity) + :Component(entity) + { + + } + + float CameraArm::Pitch::get() + { + return (GetNativeComponent()->GetPitch()); + } + + void CameraArm::Pitch::set(float val) + { + GetNativeComponent()->SetPitch(val); + } + float CameraArm::Yaw::get() + { + return (GetNativeComponent()->GetYaw()); + } + + void CameraArm::Yaw::set(float val) + { + GetNativeComponent()->SetYaw(val); + } + + float CameraArm::ArmLength::get() + { + return (GetNativeComponent()->GetArmLength()); + } + + void CameraArm::ArmLength::set(float val) + { + GetNativeComponent()->SetArmLength(val); + } + + bool CameraArm::LookAtCameraOrigin::get() + { + return GetNativeComponent()->lookAtCameraOrigin; + } + + void CameraArm::LookAtCameraOrigin::set(bool val) + { + GetNativeComponent()->lookAtCameraOrigin = val; + } +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/CameraArm.hxx b/SHADE_Managed/src/Components/CameraArm.hxx new file mode 100644 index 00000000..771771cf --- /dev/null +++ b/SHADE_Managed/src/Components/CameraArm.hxx @@ -0,0 +1,40 @@ +#pragma once + +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" + +// External Dependencies +#include "Camera/SHCameraArmComponent.h" + + +namespace SHADE +{ + public ref class CameraArm : public Component + { + internal: + CameraArm(Entity entity); + public: + property float Pitch + { + float get(); + void set(float val); + } + property float Yaw + { + float get(); + void set(float val); + } + property float ArmLength + { + float get(); + void set(float val); + } + property bool LookAtCameraOrigin + { + bool get(); + void set(bool val); + } + + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/Collider.cxx b/SHADE_Managed/src/Components/Collider.cxx new file mode 100644 index 00000000..1a53f9e1 --- /dev/null +++ b/SHADE_Managed/src/Components/Collider.cxx @@ -0,0 +1,257 @@ +/************************************************************************************//*! +\file Collider.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 20, 2022 +\brief Contains the definition of the functions of the managed Collider class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Collider.hxx" +#include "Utility/Debug.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* ColliderBound - Constructors */ + /*---------------------------------------------------------------------------------*/ + CollisionShape::CollisionShape(int arrayIdx, Entity attachedEntity) + : arrayIndex { arrayIdx } + , entity { attachedEntity } + {} + + /*---------------------------------------------------------------------------------*/ + /* ColliderBound - Setter Functions */ + /*---------------------------------------------------------------------------------*/ + void CollisionShape::updateArrayIndex(int index) + { + arrayIndex = index; + } + + /*---------------------------------------------------------------------------------*/ + /* BoxColliderBound - Constructors */ + /*---------------------------------------------------------------------------------*/ + BoxCollider::BoxCollider(int arrayIdx, Entity attachedEntity) + : CollisionShape { arrayIndex, attachedEntity } + {} + + /*---------------------------------------------------------------------------------*/ + /* BoxColliderBound - Properties */ + /*---------------------------------------------------------------------------------*/ + Vector3 BoxCollider::Center::get() + { + return Convert::ToCLI(getNativeBoundObject().GetCenter()); + } + void BoxCollider::Center::set(Vector3 value) + { + getNativeBoundObject().SetCenter(Convert::ToNative(value)); + } + Vector3 BoxCollider::HalfExtents::get() + { + return Convert::ToCLI(getNativeBoundObject().GetWorldExtents()); + } + void BoxCollider::HalfExtents::set(Vector3 value) + { + getNativeBoundObject().SetWorldExtents(Convert::ToNative(value)); + } + Vector3 BoxCollider::Min::get() + { + return Convert::ToCLI(getNativeBoundObject().GetMin()); + } + void BoxCollider::Min::set(Vector3 value) + { + getNativeBoundObject().SetMin(Convert::ToNative(value)); + } + Vector3 BoxCollider::Max::get() + { + return Convert::ToCLI(getNativeBoundObject().GetMax()); + } + void BoxCollider::Max::set(Vector3 value) + { + getNativeBoundObject().SetMax(Convert::ToNative(value)); + } + + /*---------------------------------------------------------------------------------*/ + /* BoxColliderBound - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + bool BoxCollider::TestPoint(Vector3 point) + { + return getNativeBoundObject().TestPoint(Convert::ToNative(point)); + } + bool BoxCollider::Raycast(Ray ray, float maxDistance) + { + return getNativeBoundObject().Raycast(Convert::ToNative(ray), maxDistance); + } + + /*---------------------------------------------------------------------------------*/ + /* BoxColliderBound - Properties */ + /*---------------------------------------------------------------------------------*/ + Vector3 SphereCollider::Center::get() + { + return Convert::ToCLI(getNativeBoundObject().GetCenter()); + } + void SphereCollider::Center::set(Vector3 value) + { + getNativeBoundObject().SetCenter(Convert::ToNative(value)); + } + float SphereCollider::Radius::get() + { + return getNativeBoundObject().GetWorldRadius(); + } + void SphereCollider::Radius::set(float value) + { + getNativeBoundObject().SetWorldRadius(value); + } + + /*---------------------------------------------------------------------------------*/ + /* SphereColliderBound - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + bool SphereCollider::TestPoint(Vector3 point) + { + return getNativeBoundObject().TestPoint(Convert::ToNative(point)); + } + bool SphereCollider::Raycast(Ray ray, float maxDistance) + { + return getNativeBoundObject().Raycast(Convert::ToNative(ray), maxDistance); + } + + /*---------------------------------------------------------------------------------*/ + /* SphereColliderBound - Constructors */ + /*---------------------------------------------------------------------------------*/ + SphereCollider::SphereCollider(int arrayIndex, Entity attachedEntity) + : CollisionShape{ arrayIndex, attachedEntity } + {} + + /*---------------------------------------------------------------------------------*/ + /* Collider - Constructors */ + /*---------------------------------------------------------------------------------*/ + Collider::Collider(Entity entity) + : Component(entity) + { + // Create lists if they don't exist + if (colliders == nullptr) + colliders = gcnew ColliderMap; + if (!colliders->ContainsKey(entity)) + colliders->Add(entity, gcnew WeakReferenceList()); + + // Store a weak reference + colliders[entity]->Add(gcnew System::WeakReference(this)); + } + + /*---------------------------------------------------------------------------------*/ + /* Collider - Properties */ + /*---------------------------------------------------------------------------------*/ + int Collider::CollisionShapeCount::get() + { + return static_cast(GetNativeComponent()->GetCollisionShapes().size()); + } + + /*---------------------------------------------------------------------------------*/ + /* Collider - ColliderBound Functions */ + /*---------------------------------------------------------------------------------*/ + CollisionShape^ Collider::GetCollisionShape(int index) + { + // Populate the list if it hasn't been + if (subColliderList == nullptr) + { + updateSubColliderList(); + } + + // Check if valid + if (index < 0 || index >= subColliderList->Count) + throw gcnew System::ArgumentException("[Collider] Invalid index for Collider Bound retrieval."); + + // Return the bound + return subColliderList[index]; + } + generic + T Collider::GetCollisionShape(int index) + { + return safe_cast(GetCollisionShape(index)); + } + + /*---------------------------------------------------------------------------------*/ + /* Event Handling Functions */ + /*---------------------------------------------------------------------------------*/ + void Collider::OnCollisionShapeRemoved(EntityID entity) + { + SAFE_NATIVE_CALL_BEGIN + // Check if there are any colliders to update + if (colliders == nullptr || !colliders->ContainsKey(entity)) + return; + + // Remove the key + colliders->Remove(entity); + SAFE_NATIVE_CALL_END("Collider.OnColliderRemoved") + } + + void Collider::OnCollisionShapeChanged(EntityID entity) + { + SAFE_NATIVE_CALL_BEGIN + // Check if there are any colliders to update + if (colliders == nullptr || !colliders->ContainsKey(entity)) + return; + + // Update any colliders + System::Collections::Generic::List^ toRemove = gcnew System::Collections::Generic::List(); + WeakReferenceList^ collidersList = colliders[entity]; + for each (System::WeakReference^ wr in collidersList) + { + Collider^ collider = safe_cast(wr->Target); + // Update collider bounds + if (collider && collider->subColliderList != nullptr) + collider->updateSubColliderList(); + else + toRemove->Add(wr); + } + + // Sweep through and clear any invalid references while we're at it + for each (System::WeakReference ^ wr in toRemove) + { + collidersList->Remove(wr); + } + SAFE_NATIVE_CALL_END("Collider.OnColliderBoundChanged") + } + + void Collider::updateSubColliderList() + { + // Prepare the list + if (subColliderList) + subColliderList->Clear(); + else + subColliderList = gcnew System::Collections::Generic::List(); + + // Populate the list + int i = 0; + for (const auto& collider : GetNativeComponent()->GetCollisionShapes()) + { + CollisionShape^ bound = nullptr; + switch (collider.GetType()) + { + case SHCollisionShape::Type::BOX: + bound = gcnew BoxCollider(i, Owner.GetEntity()); + break; + case SHCollisionShape::Type::SPHERE: + bound = gcnew SphereCollider(i, Owner.GetEntity()); + break; + case SHCollisionShape::Type::CAPSULE: + // TODO + break; + default: + Debug::LogWarning("[Collider] An invalid Collider Type was detected. Skipping."); + break; + } + ++i; + + // Add into list + subColliderList->Add(bound); + } + } +} diff --git a/SHADE_Managed/src/Components/Collider.h++ b/SHADE_Managed/src/Components/Collider.h++ new file mode 100644 index 00000000..6e165619 --- /dev/null +++ b/SHADE_Managed/src/Components/Collider.h++ @@ -0,0 +1,41 @@ +/************************************************************************************//*! +\file Collider.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 20, 2022 +\brief Contains the definition of templated functions for the managed Collider + and related classes. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Include +#include "Component.hxx" +namespace SHADE +{ + template + CollisionShapeType& SHADE::CollisionShape::getNativeBoundObject() + { + SHColliderComponent* collider = SHComponentManager::GetComponent_s(entity); + if (!collider) + throw gcnew System::InvalidOperationException("Unable to retrieve Collider component!"); + + try + { + auto& shape = collider->GetCollisionShape(arrayIndex); + if (shape.GetType() != SHCollisionShape::Type::BOX) + throw gcnew System::InvalidOperationException("Attempted to retrieve invalid ColliderBound."); + + return reinterpret_cast(shape); + } + catch (std::invalid_argument&) + { + throw gcnew System::IndexOutOfRangeException("Attempted to retrieve out of range ColliderBound!"); + } + } +} diff --git a/SHADE_Managed/src/Components/Collider.hxx b/SHADE_Managed/src/Components/Collider.hxx new file mode 100644 index 00000000..1711e8b9 --- /dev/null +++ b/SHADE_Managed/src/Components/Collider.hxx @@ -0,0 +1,264 @@ +/************************************************************************************//*! +\file Collider.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 20, 2022 +\brief Contains the definition of the managed Collider class with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Physics/Interface/SHColliderComponent.h" +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" +#include "Utility/Convert.hxx" +#include "Math/Ray.hxx" + +namespace SHADE +{ + /// + /// Base interface for all Collider Shapes. + /// + public ref class CollisionShape abstract + { + public: + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Checks if the specified point is within this shape's bounds. + /// + /// Point to test with. + /// True if the point is in the shape's bounds. + virtual bool TestPoint(Vector3 point) = 0; + /// + /// Computes a Raycast and checks if there is a collision with any object. + /// + /// The ray to cast. + /// Maximum distance for the raycast check. + /// True if the ray intersects with an object in the scene. + virtual bool Raycast(Ray ray, float maxDistance) = 0; + + protected: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + CollisionShape(int arrayIdx, Entity attachedEntity); + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + int arrayIndex; // Index into the colliders vector on the native object + Entity entity; // Entity holding the collider component that this collider bounds is on + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + template + CollisionShapeType& getNativeBoundObject(); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Setter Functions */ + /*-----------------------------------------------------------------------------*/ + void updateArrayIndex(int index); + }; + + /// + /// Box-shaped Collider Bound. + /// + public ref class BoxCollider : public CollisionShape + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Center of the Bounding Box formed by this bound. + /// + property Vector3 Center + { + Vector3 get(); + void set(Vector3 value); + } + /// + /// Half of the scale of the Bounding Box formed by this bound. + /// + property Vector3 HalfExtents + { + Vector3 get(); + void set(Vector3 value); + } + /// + /// Position of the bottom left back corner of the Bounding Box formed by this + /// bound. + /// + property Vector3 Min + { + Vector3 get(); + void set(Vector3 value); + } + /// + /// Position of the top right front corner of the Bounding Box formed by this + /// bound. + /// + property Vector3 Max + { + Vector3 get(); + void set(Vector3 value); + } + + /*-----------------------------------------------------------------------------*/ + /* ColliderBound Functions */ + /*-----------------------------------------------------------------------------*/ + /// + bool TestPoint(Vector3 point) override; + /// + bool Raycast(Ray ray, float maxDistance) override; + + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + BoxCollider(int arrayIndex, Entity attachedEntity); + }; + + /// + /// Sphere-shaped Collider Bound. + /// + public ref class SphereCollider : public CollisionShape + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Center of the Bounding Sphere formed by this bound. + /// + property Vector3 Center + { + Vector3 get(); + void set(Vector3 value); + } + /// + /// Radius of the Bounding Sphere formed by this bound. + /// + property float Radius + { + float get(); + void set(float value); + } + + /*-----------------------------------------------------------------------------*/ + /* ColliderBound Functions */ + /*-----------------------------------------------------------------------------*/ + /// + bool TestPoint(Vector3 point) override; + /// + bool Raycast(Ray ray, float maxDistance) override; + + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + SphereCollider(int arrayIndex, Entity attachedEntity); + }; + + /// + /// CLR version of the the SHADE Engine's SHColliderComponent. + /// A single Collider component can contain one or multiple Collider Bounds. + /// + public ref class Collider : public Component + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a Collider Component that represents a native SHColliderComponent + /// component tied to the specified Entity. + /// + /// Entity that this Component will be tied to. + Collider(Entity entity); + + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Total number of ColliderShapes in the Collider component. + /// + property int CollisionShapeCount + { + int get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves a ColliderBound at the specified index in the ColliderBound list. + /// + /// Index to retrieve a ColliderBound from. + /// ColliderBound for the specified index. + CollisionShape^ GetCollisionShape(int index); + /// + /// Retrieves a ColliderBound at the specified index in the ColliderBound list + /// and casts it to the appropriate type. + /// + /// Type of the ColliderBound to cast to. + /// Index to retrieve a ColliderBound from. + /// ColliderBound for the specified index. + generic where T:CollisionShape + T GetCollisionShape(int index); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Event Handling Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// To be called from native code when a collision shape has been removed. + /// + /// The entity which has it's collision shape removed. + static void OnCollisionShapeRemoved(EntityID entity); + /// + /// To be called from native code when a Collision Shape has been changed. + /// + /// + /// The entity which has it's collision shape changed. + /// + static void OnCollisionShapeChanged(EntityID entity); + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using WeakReferenceList = System::Collections::Generic::List; + using ColliderMap = System::Collections::Generic::Dictionary; + + /*-----------------------------------------------------------------------------*/ + /* Static Data Members */ + /*-----------------------------------------------------------------------------*/ + static ColliderMap^ colliders; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + System::Collections::Generic::List^ subColliderList; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + void updateSubColliderList(); + }; +} + +#include "Collider.h++" \ No newline at end of file diff --git a/SHADE_Managed/src/Components/Component.cxx b/SHADE_Managed/src/Components/Component.cxx new file mode 100644 index 00000000..7f56fad3 --- /dev/null +++ b/SHADE_Managed/src/Components/Component.cxx @@ -0,0 +1,112 @@ +/************************************************************************************//*! +\file Component.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 27, 2021 +\brief Contains the definition of the functions for the Component class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Components/Component.hxx" +// External Dependencies +#include "Engine/ECS.hxx" +// Project Headers +#include "Scripts/ScriptStore.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Component Access Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T BaseComponent::AddComponent() + { + return ECS::AddComponent(owner.GetEntity()); + } + generic + T BaseComponent::GetComponent() + { + return ECS::GetComponent(owner.GetEntity()); + } + generic + void BaseComponent::RemoveComponent() + { + ECS::RemoveComponent(owner.GetEntity()); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T BaseComponent::AddScript() + { + return ScriptStore::AddScript(owner.GetEntity()); + } + generic + T BaseComponent::GetScript() + { + return ScriptStore::GetScript(owner.GetEntity()); + } + + generic + System::Collections::Generic::IEnumerable^ BaseComponent::GetScripts() + { + return ScriptStore::GetScripts(owner.GetEntity()); + } + + generic + void BaseComponent::RemoveScript() + { + ScriptStore::RemoveScript(owner.GetEntity()); + } + + BaseComponent::operator bool(BaseComponent^ c) + { + return c != nullptr; + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + BaseComponent::BaseComponent(Entity entity) + : owner { entity } + {} + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + bool BaseComponent::Equals(BaseComponent^ other) + { + if (other == nullptr) + return false; + return owner == other->owner; + } + + /*---------------------------------------------------------------------------------*/ + /* Object */ + /*---------------------------------------------------------------------------------*/ + bool BaseComponent::Equals(Object^ o) + { + try + { + BaseComponent^ cmp = safe_cast(o); + return Equals(cmp); + } + catch (System::InvalidCastException^) + { + return false; + } + } + + int BaseComponent::GetHashCode() + { + return owner.GetHashCode(); + } +} diff --git a/SHADE_Managed/src/Components/Component.h++ b/SHADE_Managed/src/Components/Component.h++ new file mode 100644 index 00000000..e2a20998 --- /dev/null +++ b/SHADE_Managed/src/Components/Component.h++ @@ -0,0 +1,41 @@ +/************************************************************************************//*! +\file Component.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 27, 2021 +\brief Contains the definition of templated functions for the managed Component + classes. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Include +#include "Component.hxx" +// Project includes +#include "Utility/Convert.hxx" +#include "Engine/ECS.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + template + Component::Component(Entity entity) + : BaseComponent { entity } + {} + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + template + typename Component::NativeComponent* Component::GetNativeComponent() + { + return ECS::GetNativeComponent(owner.GetEntity()); + } +} diff --git a/SHADE_Managed/src/Components/Component.hxx b/SHADE_Managed/src/Components/Component.hxx new file mode 100644 index 00000000..a1d83eaf --- /dev/null +++ b/SHADE_Managed/src/Components/Component.hxx @@ -0,0 +1,208 @@ +/************************************************************************************//*! +\file Component.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 27, 2021 +\brief Contains the definition of the managed Component classes with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "ECS_Base/Components/SHComponent.h" +// Project Includes +#include "Engine/Entity.hxx" +#include "Scripts/Script.hxx" + +namespace SHADE +{ + /// + /// Class that serves as the base for a wrapper class to Components in native code. + /// + public ref class BaseComponent : public System::IEquatable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the GameObject that this Component belongs to. + /// + property GameObject Owner + { + GameObject get() { return owner; } + } + + /*-----------------------------------------------------------------------------*/ + /* Component Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Component to this GameObject. + /// + /// Type of the Component to add. + /// Reference to the Component that was added. + generic where T : BaseComponent + T AddComponent(); + /// + /// Gets a Component from this GameObject. + /// + /// Type of the Component to get. + /// + /// Reference to the Component or null if this GameObject does not have the + /// specified Component. + /// + generic where T : BaseComponent + T GetComponent(); + /// + /// Removes a Component from this GameObject. If no Component exists to begin + /// with, nothing happens. + /// + /// Type of the Component to get. + generic where T : BaseComponent + void RemoveComponent(); + + /*-----------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script of the specified type to this GameObject. + /// + /// Type of Script to add. + /// Reference to the created Script. + generic where T : ref class, Script + T AddScript(); + /// + /// Retrieves a Script of the specified type from this GameObject. + /// If multiple Scripts of the same specified type are added on the same + /// GameObject, this will retrieve the first one added. + /// + /// Type of Script to add. + /// Reference to the Script to retrieve. + generic where T : ref class, Script + T GetScript(); + /// + /// Retrieves a immutable list of Scripts of the specified type from this + /// GameObject. + /// + /// Type of Scripts to Get. + /// Immutable list of Scripts of the specified type. + generic where T : ref class, Script + System::Collections::Generic::IEnumerable^ GetScripts(); + /// + /// Removes all Scripts of the specified type from this GameObject. + /// + /// Type of Scripts to remove. + generic where T : ref class, Script + void RemoveScript(); + + /*-----------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------*/ + /// + /// Implicit conversion operator to enable checking if a component is null. + /// + /// Component to check. + static operator bool(BaseComponent^ c); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for BaseComponent to tie it to a specific Entity. + /// Constructors of derived Components should call this Constructor. + /// + /// Entity that this Component will be tied to. + BaseComponent(Entity entity); + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + /// + /// Entity that this Component belongs to. + /// + GameObject owner; + + public: + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(BaseComponent^ other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + }; + + /// + /// C++ template for the BaseComponent class used to generate common template-able + /// functions and types. + /// + /// + /// Type of the native component that this Component wraps. + /// + template + public ref class Component : public BaseComponent + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Type of the native component that this Component wraps. + /// + using NativeComponent = NativeType; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves a pointer to the native unmanaged component that is tied to the + /// Entity described by the owner value. + /// + /// + /// Pointer to the native component. Will be nullptr if it does not exist. + /// + /// + /// Thrown if the internal ID stored by this native component is invalid. + /// + /// + /// Thrown if an attempt to retrieve the native component fails. + /// + NativeComponent* GetNativeComponent(); + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for Component to tie it to a specific Entity. + /// Constructors of derived Components should call this Constructor. + /// + /// Entity that this Component will be tied to. + Component(Entity entity); + }; +} + +#include "Component.h++" diff --git a/SHADE_Managed/src/Components/Light.cxx b/SHADE_Managed/src/Components/Light.cxx new file mode 100644 index 00000000..a220c79a --- /dev/null +++ b/SHADE_Managed/src/Components/Light.cxx @@ -0,0 +1,79 @@ +/************************************************************************************//*! +\file Light.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 8, 2022 +\brief Contains the definition of the functions of the managed Light class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Light.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Light::Light(Entity entity) + : Component(entity) + {} + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + Vector3 Light::Position::get() + { + return Convert::ToCLI(GetNativeComponent()->GetPosition()); + } + void Light::Position::set(Vector3 value) + { + GetNativeComponent()->SetPosition(Convert::ToNative(value)); + } + Light::Type Light::LightType::get() + { + return static_cast(GetNativeComponent()->GetType()); + } + void Light::LightType::set(Light::Type value) + { + GetNativeComponent()->SetType(static_cast(value)); + } + Vector3 Light::Direction::get() + { + return Convert::ToCLI(GetNativeComponent()->GetDirection()); + } + void Light::Direction::set(Vector3 value) + { + GetNativeComponent()->SetDirection(Convert::ToNative(value)); + } + Color Light::Color::get() + { + return Convert::ToCLI(SHColour(GetNativeComponent()->GetColor())); + } + void Light::Color::set(SHADE::Color value) + { + GetNativeComponent()->SetColor(Convert::ToNative(value)); + } + System::UInt32 Light::CullingMask::get() + { + return GetNativeComponent()->GetCullingMask(); + } + void Light::CullingMask::set(System::UInt32 value) + { + GetNativeComponent()->SetCullingMask(value); + } + float Light::Strength::get() + { + return GetNativeComponent()->GetStrength(); + } + void Light::Strength::set(float value) + { + GetNativeComponent()->SetStrength(value); + } +} diff --git a/SHADE_Managed/src/Components/Light.hxx b/SHADE_Managed/src/Components/Light.hxx new file mode 100644 index 00000000..9abb05d5 --- /dev/null +++ b/SHADE_Managed/src/Components/Light.hxx @@ -0,0 +1,125 @@ +/************************************************************************************//*! +\file Light.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 8, 2022 +\brief Contains the definition of the managed Light class with the declaration + of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" +// External Dependencies +#include "Graphics/MiddleEnd/Lights/SHLightComponent.h" + +namespace SHADE +{ + /// + /// CLR version of the SHADE Engine's SHLightComponent. + /// + public ref class Light : public Component + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a Light Component that represents a native Light component tied to + /// the specified Entity. + /// + /// Entity that this Component will be tied to. + Light(Entity entity); + + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + /// + /// Supported types of the Light Component. + /// + enum class Type + { + /// + /// Light applied uniformly across the scene at a specified direction. + /// + Directional, + /// + /// Light that originates from a certain point in all directions. + /// Not implemented yet. + /// + Point, + /// + /// Light that originates from a certain point within a angle. + /// Not implemented yet. + /// + Spot, + /// + /// Light applied to all objects. Has no source point. + /// + Ambient + }; + + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Position of the light. Only works for Point Light (unimplemented). + /// + [System::ObsoleteAttribute("Not implemented yet.", true)] + property Vector3 Position + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Type of lighting that this Light component will apply onto the scene. + /// + property Type LightType + { + Type get(); + void set(Type val); + } + /// + /// Direction of the light. Only applicable for Directional Lights. + /// + property Vector3 Direction + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Colour of the Light. + /// + property SHADE::Color Color + { + SHADE::Color get(); + void set(SHADE::Color val); + } + /// + /// Culling mask that is used to control what types of Materials would be + /// affected by this Light. + /// + property System::UInt32 CullingMask + { + System::UInt32 get(); + void set(System::UInt32 val); + } + /// + /// Intensity of the Light + /// + property float Strength + { + float get(); + void set(float val); + } + }; +} + diff --git a/SHADE_Managed/src/Components/Renderable.cxx b/SHADE_Managed/src/Components/Renderable.cxx new file mode 100644 index 00000000..bc01bc03 --- /dev/null +++ b/SHADE_Managed/src/Components/Renderable.cxx @@ -0,0 +1,67 @@ +/************************************************************************************//*! +\file Renderable.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the functions of the managed Renderable class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Renderable.hxx" +#include "Assets/NativeAsset.hxx" +#include "Utility/Convert.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Renderable::Renderable(Entity entity) + : Component(entity) + {} + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + SHADE::Mesh^ Renderable::Mesh::get() + { + return gcnew SHADE::Mesh(GetNativeComponent()->GetMesh()); + } + void Renderable::Mesh::set(SHADE::Mesh^ value) + { + if (value == nullptr) + { + GetNativeComponent()->SetMesh(Handle()); + } + else + { + GetNativeComponent()->SetMesh(Handle(Convert::ToNative(value->NativeObjectHandle))); + } + } + SHADE::Material^ Renderable::Material::get() + { + return gcnew SHADE::Material(GetNativeComponent()->GetMaterial()); + } + void Renderable::Material::set(SHADE::Material^ value) + { + if (value == nullptr) + { + GetNativeComponent()->SetMaterial(Handle()); + } + else + { + GetNativeComponent()->SetMaterial(Handle(Convert::ToNative(value->NativeObjectHandle))); + } + } + System::Byte Renderable::LightLayer::get() + { + return GetNativeComponent()->GetLightLayer(); + } +} diff --git a/SHADE_Managed/src/Components/Renderable.hxx b/SHADE_Managed/src/Components/Renderable.hxx new file mode 100644 index 00000000..e8f11ef6 --- /dev/null +++ b/SHADE_Managed/src/Components/Renderable.hxx @@ -0,0 +1,73 @@ +/************************************************************************************//*! +\file Renderable.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the managed Renderable class with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" +#include "Math/Quaternion.hxx" +// External Dependencies +#include "Graphics/MiddleEnd/Interface/SHRenderable.h" +#include "Assets/Mesh.hxx" +#include "Assets/Material.hxx" + +namespace SHADE +{ + /// + /// CLR version of the SHADE Engine's SHRenderableComponent. + /// + public ref class Renderable : public Component + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a Renderable Component that represents a native Renderable + /// component tied to the specified Entity. + /// + /// Entity that this Component will be tied to. + Renderable(Entity entity); + + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Mesh used to render this Renderable. + /// + property SHADE::Mesh^ Mesh + { + SHADE::Mesh^ get(); + void set(SHADE::Mesh^ value); + } + /// + /// Material used to render this Renderable. + /// + property SHADE::Material^ Material + { + SHADE::Material^ get(); + void set(SHADE::Material^ value); + } + /// + /// Material used to render this Renderable. + /// + property System::Byte LightLayer + { + System::Byte get(); + } + }; +} + diff --git a/SHADE_Managed/src/Components/RigidBody.cxx b/SHADE_Managed/src/Components/RigidBody.cxx new file mode 100644 index 00000000..cdaa296a --- /dev/null +++ b/SHADE_Managed/src/Components/RigidBody.cxx @@ -0,0 +1,226 @@ +/************************************************************************************//*! +\file RigidBody.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 22, 2022 +\brief Contains the definition of the functions of the managed RigidBody class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "RigidBody.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + RigidBody::RigidBody(Entity entity) + : Component(entity) + {} + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + bool RigidBody::IsGravityEnabled::get() + { + return GetNativeComponent()->IsGravityEnabled(); + } + void RigidBody::IsGravityEnabled::set(bool value) + { + return GetNativeComponent()->SetGravityEnabled(value); + } + bool RigidBody::IsAllowedToSleep::get() + { + return GetNativeComponent()->IsAllowedToSleep(); + } + void RigidBody::IsAllowedToSleep::set(bool value) + { + return GetNativeComponent()->SetIsAllowedToSleep(value); + } + RigidBody::Type RigidBody::BodyType::get() + { + return static_cast(GetNativeComponent()->GetType()); + } + void RigidBody::BodyType::set(Type value) + { + return GetNativeComponent()->SetType(static_cast(value)); + } + float RigidBody::Mass::get() + { + return GetNativeComponent()->GetMass(); + } + void RigidBody::Mass::set(float value) + { + return GetNativeComponent()->SetMass(value); + } + float RigidBody::Drag::get() + { + return GetNativeComponent()->GetDrag(); + } + void RigidBody::Drag::set(float value) + { + return GetNativeComponent()->SetDrag(value); + } + float RigidBody::AngularDrag::get() + { + return GetNativeComponent()->GetAngularDrag(); + } + void RigidBody::AngularDrag::set(float value) + { + return GetNativeComponent()->SetAngularDrag(value); + } + bool RigidBody::FreezePositionX::get() + { + return GetNativeComponent()->GetFreezePositionX(); + } + void RigidBody::FreezePositionX::set(bool value) + { + return GetNativeComponent()->SetFreezePositionX(value); + } + bool RigidBody::FreezePositionY::get() + { + return GetNativeComponent()->GetFreezePositionY(); + } + void RigidBody::FreezePositionY::set(bool value) + { + return GetNativeComponent()->SetFreezePositionY(value); + } + bool RigidBody::FreezePositionZ::get() + { + return GetNativeComponent()->GetFreezePositionZ(); + } + void RigidBody::FreezePositionZ::set(bool value) + { + return GetNativeComponent()->SetFreezePositionZ(value); + } + bool RigidBody::FreezeRotationX::get() + { + return GetNativeComponent()->GetFreezeRotationX(); + } + void RigidBody::FreezeRotationX::set(bool value) + { + return GetNativeComponent()->SetFreezeRotationX(value); + } + bool RigidBody::FreezeRotationY::get() + { + return GetNativeComponent()->GetFreezeRotationY(); + } + void RigidBody::FreezeRotationY::set(bool value) + { + return GetNativeComponent()->SetFreezeRotationY(value); + } + bool RigidBody::FreezeRotationZ::get() + { + return GetNativeComponent()->GetFreezeRotationZ(); + } + void RigidBody::FreezeRotationZ::set(bool value) + { + return GetNativeComponent()->SetFreezeRotationZ(value); + } + Vector3 RigidBody::LinearVelocity::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLinearVelocity()); + } + void RigidBody::LinearVelocity::set(Vector3 value) + { + return GetNativeComponent()->SetLinearVelocity(Convert::ToNative(value)); + } + Vector3 RigidBody::AngularVelocity::get() + { + return Convert::ToCLI(GetNativeComponent()->GetAngularVelocity()); + } + void RigidBody::AngularVelocity::set(Vector3 value) + { + return GetNativeComponent()->SetAngularVelocity(Convert::ToNative(value)); + } + Vector3 RigidBody::Force::get() + { + return Convert::ToCLI(GetNativeComponent()->GetForce()); + } + Vector3 RigidBody::Torque::get() + { + return Convert::ToCLI(GetNativeComponent()->GetTorque()); + } + bool RigidBody::Interpolating::get() + { + return GetNativeComponent()->IsInterpolating(); + } + void RigidBody::Interpolating::set(bool value) + { + GetNativeComponent()->SetInterpolate(value); + } + + /*---------------------------------------------------------------------------------*/ + /* Force Functions */ + /*---------------------------------------------------------------------------------*/ + void RigidBody::AddForce(Vector3 force) + { + GetNativeComponent()->AddForce(Convert::ToNative(force)); + } + + void RigidBody::AddForceAtLocalPos(Vector3 force, Vector3 localPos) + { + GetNativeComponent()->AddForceAtLocalPos(Convert::ToNative(force), Convert::ToNative(localPos)); + } + + void RigidBody::AddForceAtWorldPos(Vector3 force, Vector3 worldPos) + { + GetNativeComponent()->AddForceAtWorldPos(Convert::ToNative(force), Convert::ToNative(worldPos)); + } + + void RigidBody::AddRelativeForce(Vector3 relativeForce) + { + GetNativeComponent()->AddRelativeForce(Convert::ToNative(relativeForce)); + } + + void RigidBody::AddRelativeForceAtLocalPos(Vector3 relativeForce, Vector3 localPos) + { + GetNativeComponent()->AddRelativeForceAtLocalPos(Convert::ToNative(relativeForce), Convert::ToNative(localPos)); + } + + void RigidBody::AddRelativeForceAtWorldPos(Vector3 relativeForce, Vector3 worldPos) + { + GetNativeComponent()->AddRelativeForceAtWorldPos(Convert::ToNative(relativeForce), Convert::ToNative(worldPos)); + } + + Vector3 RigidBody::GetForce() + { + return Convert::ToCLI(GetNativeComponent()->GetForce()); + } + + void RigidBody::ClearForces() + { + GetNativeComponent()->ClearForces(); + } + + /*---------------------------------------------------------------------------------*/ + /* Torque Functions */ + /*---------------------------------------------------------------------------------*/ + void RigidBody::AddTorque(Vector3 torque) + { + GetNativeComponent()->AddTorque(Convert::ToNative(torque)); + } + + void RigidBody::AddRelativeTorque(Vector3 relativeTorque) + { + GetNativeComponent()->AddRelativeTorque(Convert::ToNative(relativeTorque)); + } + + Vector3 RigidBody::GetTorque() + { + return Convert::ToCLI(GetNativeComponent()->GetTorque()); + } + + void RigidBody::ClearTorque() + { + GetNativeComponent()->ClearTorque(); + } + +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/RigidBody.hxx b/SHADE_Managed/src/Components/RigidBody.hxx new file mode 100644 index 00000000..8bfe34aa --- /dev/null +++ b/SHADE_Managed/src/Components/RigidBody.hxx @@ -0,0 +1,161 @@ +/************************************************************************************//*! +\file RigidBody.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 22, 2022 +\brief Contains the definition of the managed Collider class with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Physics/Interface/SHRigidBodyComponent.h" +// Project Includes +#include "Components/Component.hxx" + +namespace SHADE +{ + /// + /// CLR version of the the SHADE Engine's SHRigidBodyComponent. + /// + public ref class RigidBody : public Component + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a RigidBody Component that represents a native + /// SHRigidBodyComponent component tied to the specified Entity. + /// + /// Entity that this Component will be tied to. + RigidBody(Entity entity); + + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + enum class Type + { + Static, + Kinematic, + Dynamic + }; + + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + property bool IsGravityEnabled + { + bool get(); + void set(bool value); + } + property bool IsAllowedToSleep + { + bool get(); + void set(bool value); + } + property Type BodyType + { + Type get(); + void set(Type value); + } + property float Mass + { + float get(); + void set(float value); + } + property float Drag + { + float get(); + void set(float value); + } + property float AngularDrag + { + float get(); + void set(float value); + } + property bool FreezePositionX + { + bool get(); + void set(bool value); + } + property bool FreezePositionY + { + bool get(); + void set(bool value); + } + property bool FreezePositionZ + { + bool get(); + void set(bool value); + } + property bool FreezeRotationX + { + bool get(); + void set(bool value); + } + property bool FreezeRotationY + { + bool get(); + void set(bool value); + } + property bool FreezeRotationZ + { + bool get(); + void set(bool value); + } + property Vector3 LinearVelocity + { + Vector3 get(); + void set(Vector3 value); + } + property Vector3 AngularVelocity + { + Vector3 get(); + void set(Vector3 value); + } + property Vector3 Force + { + Vector3 get(); + } + property Vector3 Torque + { + Vector3 get(); + } + property bool Interpolating + { + bool get(); + void set(bool value); + } + + /*-----------------------------------------------------------------------------*/ + /* Force Functions */ + /*-----------------------------------------------------------------------------*/ + void AddForce(Vector3 force); + void AddForceAtLocalPos(Vector3 force, Vector3 localPos); + void AddForceAtWorldPos(Vector3 force, Vector3 worldPos); + void AddRelativeForce(Vector3 relativeForce); + void AddRelativeForceAtLocalPos(Vector3 relativeForce, Vector3 localPos); + void AddRelativeForceAtWorldPos(Vector3 relativeForce, Vector3 worldPos); + + Vector3 GetForce(); + void ClearForces(); + + /*-----------------------------------------------------------------------------*/ + /* Torque Functions */ + /*-----------------------------------------------------------------------------*/ + void AddTorque(Vector3 force); + void AddRelativeTorque(Vector3 relativeForce); + + Vector3 GetTorque(); + void ClearTorque(); + }; + +} \ No newline at end of file diff --git a/SHADE_Managed/src/Components/Transform.cxx b/SHADE_Managed/src/Components/Transform.cxx new file mode 100644 index 00000000..927ce87f --- /dev/null +++ b/SHADE_Managed/src/Components/Transform.cxx @@ -0,0 +1,97 @@ +/************************************************************************************//*! +\file Transform.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 23, 2022 +\brief Contains the definition of the functions of the managed Transform class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Transform.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + Vector3 Transform::LocalPosition::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLocalPosition()); + } + void Transform::LocalPosition::set(Vector3 val) + { + GetNativeComponent()->SetLocalPosition(Convert::ToNative(val)); + } + Quaternion Transform::LocalRotation::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLocalOrientation()); + } + void Transform::LocalRotation::set(Quaternion val) + { + GetNativeComponent()->SetLocalOrientation(Convert::ToNative(val)); + } + Vector3 Transform::LocalEulerAngles::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLocalRotation()); + } + void Transform::LocalEulerAngles::set(Vector3 val) + { + GetNativeComponent()->SetLocalRotation(Convert::ToNative(val)); + } + Vector3 Transform::LocalScale::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLocalScale()); + + } + void Transform::LocalScale::set(Vector3 val) + { + GetNativeComponent()->SetLocalScale(Convert::ToNative(val)); + } + Vector3 Transform::GlobalPosition::get() + { + return Convert::ToCLI(GetNativeComponent()->GetWorldPosition()); + } + void Transform::GlobalPosition::set(Vector3 val) + { + GetNativeComponent()->SetWorldPosition(Convert::ToNative(val)); + } + Quaternion Transform::GlobalRotation::get() + { + return Convert::ToCLI(GetNativeComponent()->GetLocalOrientation()); + } + void Transform::GlobalRotation::set(Quaternion val) + { + GetNativeComponent()->SetWorldOrientation(Convert::ToNative(val)); + } + Vector3 Transform::GlobalEulerAngles::get() + { + return Convert::ToCLI(GetNativeComponent()->GetWorldRotation()); + } + void Transform::GlobalEulerAngles::set(Vector3 val) + { + GetNativeComponent()->SetWorldRotation(Convert::ToNative(val)); + } + Vector3 Transform::GlobalScale::get() + { + return Convert::ToCLI(GetNativeComponent()->GetWorldScale()); + + } + void Transform::GlobalScale::set(Vector3 val) + { + GetNativeComponent()->SetWorldScale(Convert::ToNative(val)); + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Transform::Transform(Entity entity) + : Component(entity) + {} +} diff --git a/SHADE_Managed/src/Components/Transform.hxx b/SHADE_Managed/src/Components/Transform.hxx new file mode 100644 index 00000000..942c6224 --- /dev/null +++ b/SHADE_Managed/src/Components/Transform.hxx @@ -0,0 +1,112 @@ +/************************************************************************************//*! +\file Transform.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 23, 2022 +\brief Contains the definition of the managed Transform class with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Components/Component.hxx" +#include "Math/Vector3.hxx" +#include "Math/Quaternion.hxx" +// External Dependencies +#include "Math/Transform/SHTransformComponent.h" + +namespace SHADE +{ + /// + /// CLR version of the SHADE Engine's TransformComponent. + /// + public ref class Transform : public Component + { + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a Transform Component that represents a native Transform component + /// tied to the specified Entity. + /// + /// Entity that this Component will be tied to. + Transform(Entity entity); + + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Local position stored by this Transform. + /// + property Vector3 LocalPosition + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Local rotation quaternion stored by this Transform. + /// + property Quaternion LocalRotation + { + Quaternion get(); + void set(Quaternion val); + } + /// + /// Local euler angle rotations stored by this Transform. + /// + property Vector3 LocalEulerAngles + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Local scale stored by this Transform. + /// + property Vector3 LocalScale + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Global position stored by this Transform. + /// + property Vector3 GlobalPosition + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Global rotation quaternion stored by this Transform. + /// + property Quaternion GlobalRotation + { + Quaternion get(); + void set(Quaternion val); + } + /// + /// Global euler angle rotations stored by this Transform. + /// + property Vector3 GlobalEulerAngles + { + Vector3 get(); + void set(Vector3 val); + } + /// + /// Global scale stored by this Transform. + /// + property Vector3 GlobalScale + { + Vector3 get(); + void set(Vector3 val); + } + }; +} + diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx new file mode 100644 index 00000000..7b2e0982 --- /dev/null +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -0,0 +1,432 @@ +/************************************************************************************//*! +\file Editor.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 27, 2022 +\brief Contains the definition of the functions for the Editor managed + static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Editor/Editor.hxx" +// STL Includes +#include +// Project Headers +#include "Components/Component.hxx" +#include "Scripts/ScriptStore.hxx" +#include "Utility/Debug.hxx" +#include "Serialisation/ReflectionUtilities.hxx" +#include "Editor/IconsMaterialDesign.h" +#include "Editor/Command/SHCommandManager.h" +#include "Editor/Command/SHCommand.hpp" +#include "TooltipAttribute.hxx" +#include "RangeAttribute.hxx" +#include "Math/Vector2.hxx" +#include "Math/Vector3.hxx" +#include + +// Using Directives +using namespace System; +using namespace System::Collections::Generic; + +/*-------------------------------------------------------------------------------------*/ +/* Function Definitions */ +/*-------------------------------------------------------------------------------------*/ +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Script Rendering Functions */ + /*---------------------------------------------------------------------------------*/ + void Editor::RenderScriptsInInspector(Entity entity) + { + SAFE_NATIVE_CALL_BEGIN + // Get scripts + IEnumerable^ scripts = ScriptStore::GetAllScripts(entity); + + // Skip if no scripts + if (scripts != nullptr) + { + // Display each script if any + int index = 0; + for each (Script^ script in scripts) + { + renderScriptInInspector(entity, script, index++); + } + } + + // Render Add Script + RenderScriptAddButton(entity); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.RenderScriptsInInspector") + } + + void Editor::RenderScriptAddButton(Entity entity) + { + // Get list of Scripts + auto scriptTypes = ScriptStore::GetAvailableScriptList(); + + // Define pop up + if (SHEditorUI::BeginMenu("Add Script", ICON_MD_LIBRARY_ADD)) + { + for each (Type ^ type in scriptTypes) + { + if (SHEditorUI::Selectable(Convert::ToNative(type->Name))) + { + // Add the script + Script^ script; + ScriptStore::AddScriptViaNameWithRef(entity, type->Name, script); + registerUndoScriptAddAction(entity, script); + break; + } + } + + SHEditorUI::EndMenu(); + } + } + + /*---------------------------------------------------------------------------------*/ + /* UndoRedoStack Functions */ + /*---------------------------------------------------------------------------------*/ + void Editor::Undo() + { + SAFE_NATIVE_CALL_BEGIN + actionStack.Undo(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.Undo") + } + + void Editor::Redo() + { + SAFE_NATIVE_CALL_BEGIN + actionStack.Redo(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.Redo") + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void Editor::renderScriptInInspector(Entity entity, Script^ script, int index) + { + // Constants + const std::string LABEL = Convert::ToNative(script->GetType()->Name); + + // Header + SHEditorUI::PushID(index); + if (SHEditorUI::CollapsingHeader(LABEL)) + { + SHEditorUI::PushID(LABEL); + SHEditorUI::Indent(); + { + // Right Click Menu + renderScriptContextMenu(entity, script, index); + + // Go through all fields and output them + auto fields = ReflectionUtilities::GetInstanceFields(script); + int id = 0; + for each (auto field in fields) + { + // Ignore non-serialisable fields + if (!ReflectionUtilities::FieldIsSerialisable(field)) + continue; + + // Render the input field for this field + SHEditorUI::PushID(LABEL + std::to_string(id++)); + renderFieldInInspector(field, script); + SHEditorUI::PopID(); + } + + } + SHEditorUI::Unindent(); + SHEditorUI::PopID(); + } + else + { + renderScriptContextMenu(entity, script, index); + } + SHEditorUI::PopID(); + } + + void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object) + { + bool isHovered = false; + + const bool MODIFIED_PRIMITIVE = + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputInt , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputCheckbox, &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputFloat , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputDouble , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputVec2 , &isHovered) || + renderSpecificField(field, object, SHEditorUI::InputVec3 , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered) || + renderSpecificField(field, object, nullptr , &isHovered); + + if (!MODIFIED_PRIMITIVE) + { + // Any List + if (ReflectionUtilities::FieldIsList(field)) + { + System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + RangeAttribute^ rangeAttrib = hasAttribute(field); + System::Collections::IList^ iList = safe_cast(field->GetValue(object)); + + if (SHEditorUI::CollapsingHeader(Convert::ToNative(field->Name), &isHovered)) + { + if (SHEditorUI::Button("Add Item")) + { + System::Object^ obj = System::Activator::CreateInstance(listType); + iList->Add(obj); + registerUndoListAddAction(listType, iList, iList->Count - 1, obj); + } + for (int i = 0; i < iList->Count; ++i) + { + SHEditorUI::PushID(i); + System::Object^ obj = iList[i]; + System::Object^ oldObj = iList[i]; + if (renderFieldEditor(std::to_string(i), obj, rangeAttrib)) + { + iList[i] = obj; + registerUndoListChangeAction(listType, iList, i, obj, oldObj); + } + SHEditorUI::SameLine(); + if (SHEditorUI::Button("-")) + { + System::Object^ obj = iList[i]; + iList->RemoveAt(i); + registerUndoListRemoveAction(listType, iList, i, obj); + SHEditorUI::PopID(); + break; + } + SHEditorUI::PopID(); + } + } + } + else + { + array^ interfaces = field->FieldType->GetInterfaces(); + if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid) + { + array^ typeArgs = field->FieldType->GenericTypeArguments; + System::String^ title = field->Name + " : CallbackEvent<"; + for (int i = 0; i < typeArgs->Length; ++i) + { + title += typeArgs[i]->Name; + if (i < typeArgs->Length - 1) + title += ", "; + } + title += ">"; + if (SHEditorUI::CollapsingHeader(Convert::ToNative(title))) + { + // Constants + const std::string LABEL = Convert::ToNative(field->Name); + SHEditorUI::PushID(LABEL); + + ICallbackEvent^ callbackEvent = safe_cast(field->GetValue(object)); + if (callbackEvent == nullptr) + { + // Construct one since it was not constructed before + callbackEvent = safe_cast(System::Activator::CreateInstance(field->FieldType)); + } + for each (ICallbackAction ^ action in callbackEvent->Actions) + { + if (action->IsRuntimeAction) + continue; + + // Attempt to get the object if any + int entityId = static_cast(-1); + if (action->TargetObject) + { + Script^ script = safe_cast(action->TargetObject); + if (script) + { + entityId = static_cast(script->Owner.GetEntity()); + } + } + SHEditorUI::InputInt("", entityId); + SHEditorUI::SameLine(); + System::String^ methodName = ""; + if (action->TargetMethodName != nullptr) + { + methodName = action->TargetMethodName; + } + std::string methodNameNative = Convert::ToNative(methodName); + SHEditorUI::InputTextField("", methodNameNative); + SHEditorUI::SameLine(); + if (SHEditorUI::Button("-")) + { + callbackEvent->DeregisterAction(action); + break; + } + } + if (SHEditorUI::Button("Add Action")) + { + callbackEvent->RegisterAction(); + } + + SHEditorUI::PopID(); + } + } + else + { + return; + } + } + } + + // Check if the field has a specific attribute + TooltipAttribute^ toolTip = hasAttribute(field); + if (toolTip && isHovered) + { + SHEditorUI::BeginTooltip(); + SHEditorUI::Text(Convert::ToNative(toolTip->Description)); + SHEditorUI::EndTooltip(); + } + } + + bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib) + { + bool modified; + + const bool RENDERED = + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputInt , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputCheckbox, nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputFloat , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputDouble , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec2 , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, SHEditorUI::InputVec3 , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified) || + renderFieldEditor(fieldName, object, nullptr , nullptr, rangeAttrib, modified); + + return modified; + } + + bool Editor::renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered) + { + // Get all the names of the enums + const array^ ENUM_NAMES = object->GetType()->GetEnumNames(); + std::vector nativeEnumNames; + for each (String ^ str in ENUM_NAMES) + { + nativeEnumNames.emplace_back(Convert::ToNative(str)); + } + + int val = safe_cast(object); + int oldVal = val; + if (SHEditorUI::InputEnumCombo(fieldName, val, nativeEnumNames, isHovered)) + { + object = val; + return true; + } + + return false; + } + + void Editor::renderScriptContextMenu(Entity entity, Script^ script, int scriptIndex) + { + // Right Click Menu + if (SHEditorUI::BeginPopupContextItem("scriptContextMenu")) + { + if (SHEditorUI::Selectable("Delete Script", ICON_MD_DELETE)) + { + // Mark script for removal + ScriptStore::RemoveScript(entity, script); + registerUndoScriptRemoveAction(entity, script, scriptIndex); + } + SHEditorUI::EndPopup(); + } + } + + void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) + { + // Create command and add it into the undo stack + actionStack.Add(gcnew FieldChangeCommand(object, field, newData, oldData)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + void Editor::registerUndoListChangeAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData) + { + if (list == nullptr) + return; + + actionStack.Add(gcnew ListElementChangeCommand(list, index, newData, oldData)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + void Editor::registerUndoListAddAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data) + { + if (list == nullptr) + return; + + actionStack.Add(gcnew ListElementAddCommand(list, index, data)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + void Editor::registerUndoListRemoveAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data) + { + if (list == nullptr) + return; + + actionStack.Add(gcnew ListElementRemoveCommand(list, index, data)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + void Editor::registerUndoScriptAddAction(EntityID id, Script^ script) + { + if (script == nullptr) + return; + + actionStack.Add(gcnew ScriptAddCommand(id, script)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + void Editor::registerUndoScriptRemoveAction(EntityID id, Script^ script, int originalIndex) + { + if (script == nullptr) + return; + + actionStack.Add(gcnew ScriptRemoveCommand(id, script, originalIndex)); + + // Inform the C++ Undo-Redo stack + SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); + } + + generic + Attribute Editor::hasAttribute(System::Reflection::FieldInfo^ field) + { + array^ attributes = field->GetCustomAttributes(Attribute::typeid, false); + if (attributes->Length > 0) + { + return safe_cast(attributes[0]); + } + // Failed to find + return Attribute{}; + } +} diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ new file mode 100644 index 00000000..a186d7ea --- /dev/null +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -0,0 +1,203 @@ +/************************************************************************************//*! +\file Editor.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 10, 2022 +\brief Contains the definition of templated functions for the managed Editor + static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Include +#include "Editor.hxx" +// External Dependencies +#include "Editor/SHEditorUI.h" +// Project Includes +#include "Utility/Convert.hxx" + +namespace SHADE +{ + template + bool Editor::renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) + { + if constexpr (std::is_same_v) + { + if (fieldInfo->FieldType->IsSubclassOf(Enum::typeid)) + { + System::Object^ enumObj = fieldInfo->GetValue(object); + int oldVal = safe_cast(enumObj); + int val = oldVal; + if (renderEnumEditor + ( + Convert::ToNative(fieldInfo->Name), + enumObj, + isHovered + )) + { + fieldInfo->SetValue(object, safe_cast(enumObj)); + registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); + } + + return true; + } + } + else + { + if (fieldInfo->FieldType == ManagedType::typeid) + { + RangeAttribute^ rangeAttrib; + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + rangeAttrib = hasAttribute(fieldInfo); + } + + ManagedType oldVal = safe_cast(fieldInfo->GetValue(object)); + ManagedType val = oldVal; + if (renderFieldEditorInternal + ( + Convert::ToNative(fieldInfo->Name), + &val, + fieldEditor, + isHovered, + rangeAttrib + )) + { + fieldInfo->SetValue(object, val); + registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal); + } + + return true; + } + } + + return false; + } + + template + bool Editor::renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified) + { + modified = false; + + if constexpr (std::is_same_v) + { + if (object->GetType()->IsSubclassOf(Enum::typeid)) + { + modified = renderEnumEditor(fieldName, object, isHovered); + return true; + } + } + else + { + if (object->GetType() == ManagedType::typeid) + { + ManagedType managedVal = safe_cast(object); + cli::interior_ptr managedValPtr = &managedVal; + if (renderFieldEditorInternal(fieldName, managedValPtr, fieldEditor, isHovered, rangeAttrib)) + { + object = managedVal; + modified = true; + return true; + } + return false; + } + } + + return false; + } + + + template + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + { + // Retrieve the native version of the object + NativeType val; + if constexpr (IsPrimitiveTypeMatches_V) + { + val = safe_cast(*managedValPtr); + } + else + { + val = Convert::ToNative(*managedValPtr); + } + + // Throw into the SHEditorUI function + NativeType oldVal = val; + bool changed = false; + if (rangeAttrib) + { + // Do not allow bools for Sliders just in case + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + changed = SHEditorUI::InputSlider + ( + fieldName, + static_cast(rangeAttrib->Min), + static_cast(rangeAttrib->Max), + val, isHovered + ); + } + } + else + { + changed = fieldEditor(fieldName, val, isHovered); + } + + if (changed) + { + if constexpr (IsPrimitiveTypeMatches_V) + { + *managedValPtr = val; + } + else + { + + *managedValPtr = Convert::ToCLI(val); + } + + return true; + } + + return false; + } + template<> + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) + { + // Prevent issues where String^ is null due to being empty + if (*managedValPtr == nullptr) + *managedValPtr = ""; + + // Actual Field + std::string val = Convert::ToNative(*managedValPtr); + if (SHEditorUI::InputTextField(fieldName, val, isHovered)) + { + *managedValPtr = Convert::ToCLI(val); + return true; + } + + return false; + } + template<> + bool Editor::renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc, bool* isHovered, RangeAttribute^) + { + uint32_t entityId = managedValPtr->GetEntity(); + if (SHEditorUI::InputGameObjectField(fieldName, entityId, isHovered, !(*managedValPtr))) + { + GameObject newVal = GameObject(entityId); + if (entityId != MAX_EID) + { + // Null GameObject set + newVal = GameObject(entityId); + } + *managedValPtr = newVal; + return true; + } + + return false; + } +} diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx new file mode 100644 index 00000000..79625274 --- /dev/null +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -0,0 +1,202 @@ +/************************************************************************************//*! +\file Editor.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 27, 2022 +\brief Contains the definition of the managed Editor static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Engine/Entity.hxx" +#include "Scripts/Script.hxx" +#include "UndoRedoStack.hxx" +#include "RangeAttribute.hxx" + +namespace SHADE +{ + + template + using EditorFieldFunc = bool(*)(const std::string& label, NativeType& val, bool* isHovered); + + /// + /// Static class for Editor-related functions + /// + private ref class Editor abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Script Rendering Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Renders the set of attached Scripts for the specified Entity into the + /// inspector. + ///
+ /// This function is meant for consumption from native code in the inspector + /// rendering code. + ///
+ /// The Entity to render the Scripts of. + static void RenderScriptsInInspector(Entity entity); + /// + /// Renders a dropdown button that allows for the addition of Scripts onto the + /// specified Entity. + /// + /// The Entity to add Scripts to. + static void RenderScriptAddButton(Entity entity); + + /*-----------------------------------------------------------------------------*/ + /* UndoRedoStack Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Undoes the last script inspector change if there is any. + /// + static void Undo(); + /// + /// Redoes the last script inspector change if there is any. + /// + static void Redo(); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + static UndoRedoStack actionStack; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions - Inspector Rendering */ + /*-----------------------------------------------------------------------------*/ + /// + /// Renders a single specified Script's inspector. + /// + /// The Entity to render the Scripts of. + /// The Script to render the inspector for. + /// + /// Indices used internally to differentiate each rendered Script + /// inspector. This is required to open and close each Script's inspector + /// independently from each other. + /// + static void renderScriptInInspector(Entity entity, Script^ script, int index); + /// + /// Renders a field specified into the inspector. + /// + /// The field to render. + /// + /// The object that contains the data of the field to render. + /// + static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object); + /// + /// Renders a raw editor for a single value. + /// + /// The name of the field to render. + /// Tracking reference to the object to modify. + /// + /// If specified, will be used to constrain values. + /// + /// True if the value was modified. + static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, RangeAttribute^ rangeAttrib); + /// + /// Renders a ImGui field editor based on the type of parameters specified if the + /// type matches. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// + /// True if the field was rendered.. + template + static bool renderFieldEditor(const std::string& fieldName, System::Object^% object, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib, bool& modified); + /// + /// Renders a raw editor for a single enum value. + /// + /// The name of the field to render. + /// + /// Tracking reference to the object to modify. Must be an enum. + /// + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// True if the value was modified. + static bool renderEnumEditor(const std::string& fieldName, System::Object^% object, bool* isHovered); + /// + /// Checks if the specified field is of the specified native and managed type + /// equivalent and renders a ImGui field editor based on the specified field + /// editor function. Also handles fields that contain a RangeAttribute. + /// + /// Native type of the field. + /// Managed type of the field. + /// Describes the field to modify. + /// Object to modify that has the specified field. + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// True if the field is modified. + template + static bool renderSpecificField(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered); + /// + /// Renders a ImGui field editor based on the type of parameters specified. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// True if the field is modified. + template + static bool renderFieldEditorInternal(const std::string& fieldName, interior_ptr managedValPtr, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); + /// + /// Renders a context menu when right clicked for the scripts + /// + /// The Entity to render the Scripts of. + /// The Script to render the inspector for. + /// Index at which the Script is stored. + static void renderScriptContextMenu(Entity entity, Script^ script, int scriptIndex); + /*-----------------------------------------------------------------------------*/ + /* Helper Functions - Undo */ + /*-----------------------------------------------------------------------------*/ + static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + static void registerUndoListChangeAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData); + static void registerUndoListAddAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data); + static void registerUndoListRemoveAction(System::Type^ type, System::Collections::IList^ list, int index, System::Object^ data); + static void registerUndoScriptAddAction(EntityID id, Script^ script); + static void registerUndoScriptRemoveAction(EntityID id, Script^ script, int originalIndex); + /*-----------------------------------------------------------------------------*/ + /* Helper Functions - Others */ + /*-----------------------------------------------------------------------------*/ + /// + /// Checks if a specific field has the specified attribute + /// + /// Type of Attribute to check for. + /// The field to check. + /// The attribute to check for if it exists. Null otherwise. + generic where Attribute : System::Attribute + static Attribute hasAttribute(System::Reflection::FieldInfo^ field); + }; +} +#include "Editor.h++" diff --git a/SHADE_Managed/src/Editor/RangeAttribute.cxx b/SHADE_Managed/src/Editor/RangeAttribute.cxx new file mode 100644 index 00000000..0d548cf7 --- /dev/null +++ b/SHADE_Managed/src/Editor/RangeAttribute.cxx @@ -0,0 +1,39 @@ +/************************************************************************************//*! +\file RangeAttribute.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 18, 2022 +\brief Contains the definition of the functions of the managed Range Attribute + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "RangeAttribute.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + float RangeAttribute::Min::get() + { + return minVal; + } + float RangeAttribute::Max::get() + { + return maxVal; + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + RangeAttribute::RangeAttribute(float min, float max) + : minVal { min } + , maxVal { max } + {} +} diff --git a/SHADE_Managed/src/Editor/RangeAttribute.hxx b/SHADE_Managed/src/Editor/RangeAttribute.hxx new file mode 100644 index 00000000..a724816d --- /dev/null +++ b/SHADE_Managed/src/Editor/RangeAttribute.hxx @@ -0,0 +1,61 @@ +/************************************************************************************//*! +\file RangeAttribute.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 18, 2022 +\brief Contains the definition of the managed Range Attribute class with + the declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Simple attribute to constrain the range of values for a field on the editor. + /// + [System::AttributeUsage(System::AttributeTargets::Field)] + public ref class RangeAttribute : public System::Attribute + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Minimum value for the Ranged field. + /// + property float Min + { + float get(); + } + /// + /// Maximum value for the Ranged field. + /// + property float Max + { + float get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for a Tooltip attribute that fills in the description. + /// + /// Text to be shown when a field is hovered. + RangeAttribute(float min, float max); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + float minVal; + float maxVal; + }; +} + diff --git a/SHADE_Managed/src/Editor/TooltipAttribute.cxx b/SHADE_Managed/src/Editor/TooltipAttribute.cxx new file mode 100644 index 00000000..8d5a5d55 --- /dev/null +++ b/SHADE_Managed/src/Editor/TooltipAttribute.cxx @@ -0,0 +1,34 @@ +/************************************************************************************//*! +\file TooltipAttribute.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 18, 2022 +\brief Contains the definition of the functions of the managed Tooltip Attribute + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "TooltipAttribute.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + System::String^ TooltipAttribute::Description::get() + { + return desc; + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + TooltipAttribute::TooltipAttribute(System::String^ description) + : desc { description } + {} +} diff --git a/SHADE_Managed/src/Editor/TooltipAttribute.hxx b/SHADE_Managed/src/Editor/TooltipAttribute.hxx new file mode 100644 index 00000000..18cbec3a --- /dev/null +++ b/SHADE_Managed/src/Editor/TooltipAttribute.hxx @@ -0,0 +1,53 @@ +/************************************************************************************//*! +\file TooltipAttribute.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 18, 2022 +\brief Contains the definition of the managed Tooltip Attribute class with + the declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Simple attribute to provide a field in a script with a tooltip. + /// + [System::AttributeUsage(System::AttributeTargets::Field)] + public ref class TooltipAttribute : public System::Attribute + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Description that is to be shown in the Tooltip. + /// + property System::String^ Description + { + System::String^ get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for a Tooltip attribute that fills in the description. + /// + /// Text to be shown when a field is hovered. + TooltipAttribute(System::String^ description); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + System::String^ desc; + }; +} + diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.cxx b/SHADE_Managed/src/Editor/UndoRedoStack.cxx new file mode 100644 index 00000000..3d1f04e9 --- /dev/null +++ b/SHADE_Managed/src/Editor/UndoRedoStack.cxx @@ -0,0 +1,319 @@ +/************************************************************************************//*! +\file UndoRedoStack.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 29, 2022 +\brief Contains the definition of the functions for the UndoRedoStack managed + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "UndoRedoStack.hxx" +// External Dependencies +#include "Editor/SHEditorUI.h" +// Project Headers +#include "Utility/Debug.hxx" +#include "Utility/Convert.hxx" +#include "Scripts/ScriptStore.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* UndoRedoStack - Properties */ + /*---------------------------------------------------------------------------------*/ + bool UndoRedoStack::UndoActionPresent::get() + { + return commandStack->Count > 0 && latestActionIndex >= 0; + } + + bool UndoRedoStack::RedoActionPresent::get() + { + const int REDO_ACTION_INDEX = latestActionIndex + 1; + return REDO_ACTION_INDEX >= 0 && REDO_ACTION_INDEX < commandStack->Count; + } + + /*---------------------------------------------------------------------------------*/ + /* UndoRedoStack - Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void UndoRedoStack::Add(ICommand^ command) + { + // Erase any other actions ahead of the current action + if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1) + { + commandStack->RemoveRange(latestActionIndex, commandStack->Count - latestActionIndex); + } + + // Add the command + commandStack->Add(command); + + // Set the latest command + latestActionIndex = commandStack->Count - 1; + } + + void UndoRedoStack::Undo() + { + if (!UndoActionPresent) + return; + + ICommand^ cmd = commandStack[latestActionIndex]; + cmd->Unexceute(); + --latestActionIndex; + } + + void UndoRedoStack::Redo() + { + if (!RedoActionPresent) + return; + + const int REDO_ACTION_INDEX = latestActionIndex + 1; + ICommand^ cmd = commandStack[REDO_ACTION_INDEX]; + cmd->Execute(); + ++latestActionIndex; + } + + /*---------------------------------------------------------------------------------*/ + /* FieldChangeCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) + : objectToChange { obj } + , field { field } + , newData { newData } + , oldData { oldData } + {} + + /*---------------------------------------------------------------------------------*/ + /* FieldChangeCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool FieldChangeCommand::Execute() + { + if (field && objectToChange) + { + field->SetValue(objectToChange, newData); + return true; + } + + return false; + } + + bool FieldChangeCommand::Unexceute() + { + if (field && objectToChange) + { + field->SetValue(objectToChange, oldData); + return true; + } + + return false; + } + + bool FieldChangeCommand::Merge(ICommand^ command) + { + FieldChangeCommand^ otherCommand = safe_cast(command); + if (otherCommand == nullptr) + { + Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!"); + return false; + } + + // Only merge if they are workng on the same object and field + if (field == otherCommand->field && objectToChange == otherCommand->objectToChange) + { + newData = otherCommand->newData; + return true; + } + + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* ListElementChangeCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + ListElementChangeCommand::ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData) + : list { list } + , index { index } + , newData { newData } + , oldData { oldData } + {} + + /*---------------------------------------------------------------------------------*/ + /* ListElementChangeCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool ListElementChangeCommand::Execute() + { + if (list && index < list->Count) + { + list[index] = newData; + return true; + } + + return false; + } + + bool ListElementChangeCommand::Unexceute() + { + if (list && index < list->Count) + { + list[index] = oldData; + return true; + } + + return false; + } + bool ListElementChangeCommand::Merge(ICommand^ command) + { + ListElementChangeCommand^ otherCommand = safe_cast(command); + if (otherCommand == nullptr) + { + Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!"); + return false; + } + + if (command && list == otherCommand->list && index == otherCommand->index) + { + newData = otherCommand->newData; + return true; + } + } + + /*---------------------------------------------------------------------------------*/ + /* ListElementAddCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + ListElementAddCommand::ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data) + : list { list } + , addIndex { addIndex } + , data { data } + {} + + /*---------------------------------------------------------------------------------*/ + /* ListElementAddCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool ListElementAddCommand::Execute() + { + if (list) + { + list->Insert(addIndex, data); + return true; + } + + return false; + } + + bool ListElementAddCommand::Unexceute() + { + if (list && addIndex < list->Count) + { + list->RemoveAt(addIndex); + return true; + } + + return false; + } + + bool ListElementAddCommand::Merge(ICommand^) + { + // Not allowed + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* ListElementRemoveCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data) + : list { list } + , removeIndex { removeIndex } + , data { data } + {} + + /*---------------------------------------------------------------------------------*/ + /* ListElementRemoveCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool ListElementRemoveCommand::Execute() + { + if (list && removeIndex < list->Count) + { + list->RemoveAt(removeIndex); + return true; + } + + return false; + } + + bool ListElementRemoveCommand::Unexceute() + { + if (list) + { + list->Insert(removeIndex, data); + return true; + } + + return false; + } + + bool ListElementRemoveCommand::Merge(ICommand^) + { + // Not allowed + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* ScriptAddCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + ScriptAddCommand::ScriptAddCommand(EntityID id, Script^ script) + : entity { id } + , addedScript { script } + {} + + /*---------------------------------------------------------------------------------*/ + /* ScriptAddCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool ScriptAddCommand::Execute() + { + return ScriptStore::AddScript(entity, addedScript) != nullptr; + } + + bool ScriptAddCommand::Unexceute() + { + return ScriptStore::RemoveScript(entity, addedScript); + } + + bool ScriptAddCommand::Merge(ICommand^) + { + // Not allowed + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* ScriptRemoveCommand - Constructor */ + /*---------------------------------------------------------------------------------*/ + ScriptRemoveCommand::ScriptRemoveCommand(EntityID id, Script^ script, int index) + : entity { id } + , removedScript { script } + , originalIndex { index } + {} + + /*---------------------------------------------------------------------------------*/ + /* ScriptRemoveCommand - ICommand Functions */ + /*---------------------------------------------------------------------------------*/ + bool ScriptRemoveCommand::Execute() + { + return ScriptStore::RemoveScript(entity, removedScript); + } + + bool ScriptRemoveCommand::Unexceute() + { + return ScriptStore::AddScript(entity, removedScript, originalIndex) != nullptr; + } + + bool ScriptRemoveCommand::Merge(ICommand^) + { + // Not allowed + return false; + } +} diff --git a/SHADE_Managed/src/Editor/UndoRedoStack.hxx b/SHADE_Managed/src/Editor/UndoRedoStack.hxx new file mode 100644 index 00000000..c377e2b7 --- /dev/null +++ b/SHADE_Managed/src/Editor/UndoRedoStack.hxx @@ -0,0 +1,177 @@ +/************************************************************************************//*! +\file UndoRedoStack.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 29, 2022 +\brief Contains the definition of the managed UndoRedoStack class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +#include "Scripts/Script.hxx" + +namespace SHADE +{ + /// + /// Interface for command that fits into the UndoRedoStack which can perform + /// undo-able and redo-able operations. + /// + private interface class ICommand + { + /// + /// Executes an action. This is called when a "Redo" is performed. + /// + /// Whether the action was successful or not. + bool Execute(); + /// + /// Undoes an action. This is called when an "Undo" is performed. + /// + /// Whether the action was successful or not. + bool Unexceute(); + /// + /// Merges this command with another command. + /// + /// + /// Whether the merge was successful or not. + bool Merge(ICommand^ command); + }; + + private ref class FieldChangeCommand sealed : public ICommand + { + public: + FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Object^ objectToChange; + System::Reflection::FieldInfo^ field; + System::Object^ newData; + System::Object^ oldData; + }; + + + private ref class ListElementChangeCommand sealed : public ICommand + { + public: + ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::IList^ list; + int index; + System::Object^ newData; + System::Object^ oldData; + }; + + private ref class ListElementAddCommand sealed : public ICommand + { + public: + ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::IList^ list; + int addIndex; // New index of the added element + System::Object^ data; + }; + + private ref class ListElementRemoveCommand sealed : public ICommand + { + public: + ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + System::Collections::IList^ list; + int removeIndex; // Index of the element to remove at + System::Object^ data; + }; + + private ref class ScriptAddCommand sealed : public ICommand + { + public: + ScriptAddCommand(EntityID id, Script^ script); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + EntityID entity; + Script^ addedScript; + }; + + private ref class ScriptRemoveCommand sealed : public ICommand + { + public: + ScriptRemoveCommand(EntityID id, Script^ script, int index); + + bool Execute() override; + bool Unexceute() override; + bool Merge(ICommand^ command) override; + + private: + EntityID entity; + Script^ removedScript; + int originalIndex; + }; + + /// + /// Class that is able to store a stack of actions that can be done and redone. + /// + private ref class UndoRedoStack sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// True if there is an undoable action in the stack. + /// + property bool UndoActionPresent { bool get(); } + /// + /// True if there is a redoable action in the stack. + /// + property bool RedoActionPresent { bool get(); } + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a command onto the stack. + /// + /// + void Add(ICommand^ command); + /// + /// Undos the last added command if it exists. + /// + void Undo(); + /// + /// Redoes the last undo-ed command if it exists. + /// + void Redo(); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + int latestActionIndex = -1; + System::Collections::Generic::List^ commandStack = gcnew System::Collections::Generic::List(); + }; +} diff --git a/SHADE_Managed/src/Engine/Application.cxx b/SHADE_Managed/src/Engine/Application.cxx new file mode 100644 index 00000000..c19bafa6 --- /dev/null +++ b/SHADE_Managed/src/Engine/Application.cxx @@ -0,0 +1,71 @@ +/************************************************************************************//*! +\file Application.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definitions of the functions in the static managed + Application class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Application.hxx" +// External Dependencies +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Editor/SHEditor.h" +#include "Graphics/MiddleEnd/Interface/SHGraphicsSystemInterface.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + bool Application::IsPlaying::get() + { + auto editor = SHSystemManager::GetSystem(); + if (editor) + return editor->editorState == SHEditor::State::PLAY + || + editor->editorState == SHEditor::State::PAUSE; + + return true; + } + bool Application::IsPaused::get() + { + auto editor = SHSystemManager::GetSystem(); + if (editor) + return editor->editorState == SHEditor::State::PAUSE; + + return false; + } + int Application::WindowWidth::get() + { + return SHGraphicsSystemInterface::GetWindowWidth(); + } + int Application::WindowHeight::get() + { + return SHGraphicsSystemInterface::GetWindowWidth(); + } + bool Application::IsFullscreen::get() + { + return SHGraphicsSystemInterface::IsFullscreen(); + } + /*void Application::IsFullscreen::set(bool value) + { + return SHGraphicsSystemInterface::SetFullscreen(value); + }*/ + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void Application::Quit() + { + SHGraphicsSystemInterface::CloseWindow(); + } +} diff --git a/SHADE_Managed/src/Engine/Application.hxx b/SHADE_Managed/src/Engine/Application.hxx new file mode 100644 index 00000000..8629cf75 --- /dev/null +++ b/SHADE_Managed/src/Engine/Application.hxx @@ -0,0 +1,77 @@ +/************************************************************************************//*! +\file Application.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definitions of a managed static Application class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Static class that contains useful properties for querying the state of the + /// engine. + /// + public ref class Application abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Whether or not the engine is playing. This will always be true on Publish. + /// On Debug/Release builds, this is true when the editor is in Play Mode. It + /// will also be true even if the editor is in Play Mode but is paused. + /// + static property bool IsPlaying + { + bool get(); + } + /// + /// Whether or not the engine is in a paused state where script updates and + /// physics are not in play. + /// + static property bool IsPaused + { + bool get(); + } + /// + /// Retrieves the designated width of the current window. + /// + static property int WindowWidth + { + int get(); + } + /// + /// Retrieves the designated height of the current window. + /// + static property int WindowHeight + { + int get(); + } + /// + /// Whether or not the application is currently in fullscreen mode or not. + /// + static property bool IsFullscreen + { + bool get(); + // TODO: once implemented on SHADE_Engine + //void set(bool value); + } + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Marks the application to stop at the end of the current frame. + /// + static void Quit(); + }; +} diff --git a/SHADE_Managed/src/Engine/ChildListCache.cxx b/SHADE_Managed/src/Engine/ChildListCache.cxx new file mode 100644 index 00000000..b183646f --- /dev/null +++ b/SHADE_Managed/src/Engine/ChildListCache.cxx @@ -0,0 +1,89 @@ +/************************************************************************************//*! +\file ChildListCache.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 11, 2022 +\brief Contains the definition of the functions for the ChildListCache managed + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "ChildListCache.hxx" +// External Dependencies +#include "Scene/SHSceneManager.h" +// Project Headers +#include "Utility/Debug.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*---------------------------------------------------------------------------------*/ + ChildListCache::ChildEnumerable^ ChildListCache::GetChildList(Entity entity) + { + // Ignore if invalid + if (entity == MAX_EID) + return nullptr; + + // Check if in cache + if (cachedLists->ContainsKey(entity)) + return cachedLists[entity]; + + // Grab the native child list + auto node = GameObject(entity).GetSceneNode(); + if (!node || node->GetChildren().empty()) + return nullptr; + + // Otherwise + // - Create the list + ChildList^ list = gcnew ChildList(); + updateChildList(list, node); + // - Cache it + cachedLists[entity] = list; + + return list; + } + + void ChildListCache::UpdateChildList(Entity entity) + { + // Ignore if invalid + if (entity == MAX_EID) + return; + + // Check if in cache + if (!cachedLists->ContainsKey(entity)) + return; + + // Update + updateChildList(cachedLists[entity], GameObject(entity).GetSceneNode()); + } + + /*---------------------------------------------------------------------------------*/ + /* Event Handling Functions */ + /*---------------------------------------------------------------------------------*/ + void ChildListCache::OnChildrenChanged(EntityID entity) + { + SAFE_NATIVE_CALL_BEGIN + UpdateChildList(entity); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ChildListCache") + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void ChildListCache::updateChildList(ChildList^ list, const SHSceneNode* sceneNode) + { + list->Clear(); + for (auto node : sceneNode->GetChildren()) + { + list->Add(GameObject(node->GetEntityID())); + } + } +} diff --git a/SHADE_Managed/src/Engine/ChildListCache.hxx b/SHADE_Managed/src/Engine/ChildListCache.hxx new file mode 100644 index 00000000..1a2637d3 --- /dev/null +++ b/SHADE_Managed/src/Engine/ChildListCache.hxx @@ -0,0 +1,80 @@ +/************************************************************************************//*! +\file ChildListCache.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 11, 2022 +\brief Contains the definition of the ChildListCache managed class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "GameObject.hxx" + +namespace SHADE { } + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHSceneNode; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Static class that caches all the lists of children for GameObjects. + /// + private ref class ChildListCache abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using ChildList = System::Collections::Generic::List; + using ChildEnumerable = System::Collections::Generic::IEnumerable; + using ListMap = System::Collections::Generic::Dictionary; + + internal: + /*-----------------------------------------------------------------------------*/ + /* Static Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the children list for the specified Entity. + /// + /// + /// Enumerable read only list of an Entity's children. Null if entity is invalid + /// or there are no children. + /// + static ChildEnumerable^ GetChildList(Entity entity); + /// + /// Updates the children list for the specified Entity if it exists. + /// + static void UpdateChildList(Entity entity); + + /*-----------------------------------------------------------------------------*/ + /* Event Handling Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// To be + /// + static void OnChildrenChanged(EntityID entity); + + private: + /*-----------------------------------------------------------------------------*/ + /* Static Data Members */ + /*-----------------------------------------------------------------------------*/ + static ListMap^ cachedLists = gcnew ListMap(); + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static void updateChildList(ChildList^ list, const SHSceneNode* sceneNode); + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Engine/ECS.cxx b/SHADE_Managed/src/Engine/ECS.cxx new file mode 100644 index 00000000..76a6a5e2 --- /dev/null +++ b/SHADE_Managed/src/Engine/ECS.cxx @@ -0,0 +1,342 @@ +/************************************************************************************//*! +\file ECS.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the ECS managed static + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "ECS.hxx" +// Standard Library +#include +#include +// External Dependencies +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/Interface/SHColliderComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Scene/SHSceneManager.h" +#include "Scene/SHSceneGraph.h" +#include "Tools/SHLog.h" +#include "Graphics\MiddleEnd\Interface\SHRenderable.h" +// Project Headers +#include "Utility/Convert.hxx" +#include "Utility/Debug.hxx" +#include "Components/Transform.hxx" +#include "Components/RigidBody.hxx" +#include "Components/Collider.hxx" +#include "Components/Camera.hxx" +#include "Components/CameraArm.hxx" +#include "Components/Light.hxx" +#include "Components\Renderable.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Component Manipulation Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T ECS::AddComponent(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to add Component \"" + << msclr::interop::marshal_as(componentType->Name) + << "\" to invalid Entity."; + Debug::LogError(oss.str()); + return T(); + } + + // Add based on the correct component + for each(ComponentSet^ type in componentMap) + { + if (componentType == type->Type) + { + // Attempt to add + type->AddFunction(entity); + + // Return the managed component + return createManagedComponent(entity); + } + } + + std::ostringstream oss; + oss << "[ECS] Failed to add unsupported Component \"" + << Convert::ToNative(componentType->Name) + << "\" to Entity #" + << entity; + Debug::LogError(oss.str()); + return T(); + } + generic + T ECS::GetComponent(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to retrieve Component \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + return T(); + } + + // Get based on the correct component + for each(ComponentSet^ type in componentMap) + { + if (componentType == type->Type) + { + if (type->HasFunction(entity)) + { + return createManagedComponent(entity); + } + else + { + return T(); + } + } + } + + std::ostringstream oss; + oss << "[ECS] Failed to retrieve unsupported Component \"" + << Convert::ToNative(componentType->Name) + << "\" to Entity #" + << entity; + Debug::LogError(oss.str()); + return T(); + } + + generic + T ECS::GetComponentInChildren(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to retrieve Component \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + return T(); + } + + // Get Entity's SceneGraphNode + SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity); + if (sceneGraphNode == nullptr) + { + std::ostringstream oss; + oss << "[ECS_CLI] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + return T(); + } + + // Search direct children first + for (const auto& child : sceneGraphNode->GetChildren()) + { + T component = GetComponent(child->GetEntityID()); + if (component != nullptr) + return component; + } + + // Search their children + for (const auto& child : sceneGraphNode->GetChildren()) + { + T component = GetComponentInChildren(child->GetEntityID()); + if (component != nullptr) + return component; + } + + // None here + return T(); + } + + generic + System::Collections::Generic::IEnumerable^ ECS::GetComponentsInChildren(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to retrieve Component \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + return nullptr; + } + + // Search all elements via a iterative breadth first search + System::Collections::Generic::List^ results; + System::Collections::Generic::Queue^ searchSpace = gcnew System::Collections::Generic::Queue(); + // Start off with direct children + SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity); + if (entityNode == nullptr) + { + std::ostringstream oss; + oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + } + for (const auto& child : entityNode->GetChildren()) + { + searchSpace->Enqueue(child->GetEntityID()); + } + // Continue with all subsequent children + while (searchSpace->Count > 0) + { + // Check if this entity has the component we need + Entity curr = searchSpace->Dequeue(); + T component = GetComponent(curr); + if (component != nullptr) + { + // We only construct if we need to + if (results == nullptr) + results = gcnew System::Collections::Generic::List(); + results->Add(component); + } + + // Add children to the queue + SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(curr); + if (sceneGraphNode == nullptr) + { + std::ostringstream oss; + oss << "[ECS_CLI] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + continue; + } + for (const auto& child : sceneGraphNode->GetChildren()) + { + searchSpace->Enqueue(child->GetEntityID()); + } + } + + // None here + return results; + } + + generic + T ECS::EnsureComponent(EntityID entity) + { + if (HasComponent(entity)) + { + AddComponent(entity); + } + + return GetComponent(entity); + } + generic + bool ECS::HasComponent(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to check existence of Component \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + return false; + } + + // Add based on the correct component + for each(ComponentSet^ type in componentMap) + { + if (componentType == type->Type) + { + return type->HasFunction(entity); + } + } + + std::ostringstream oss; + oss << "[ECS] Attempted to check existence of unsupported Component \"" + << msclr::interop::marshal_as(componentType->Name) + << "\" from Entity #" + << entity; + Debug::LogError(oss.str()); + + return false; + } + generic + void ECS::RemoveComponent(EntityID entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ECS] Attempted to remove Component \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + } + + // Add based on the correct component + for each(ComponentSet^ type in componentMap) + { + if (componentType == type->Type) + { + type->RemoveFunction(entity); + return; + } + } + + std::ostringstream oss; + oss << "[ECS] Attempted to remove unsupported Component \"" + << msclr::interop::marshal_as(componentType->Name) + << "\" from Entity #" + << entity; + Debug::LogError(oss.str()); + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + static ECS::ECS() + { + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + componentMap.Add(createComponentSet()); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T ECS::createManagedComponent(EntityID entity) + { + using namespace System::Reflection; + + array^ params = gcnew array{ static_cast(entity) }; + return safe_cast(System::Activator::CreateInstance + ( + T::typeid, + BindingFlags::Instance | BindingFlags::NonPublic | BindingFlags::CreateInstance, + nullptr, params, nullptr) + ); + } +} diff --git a/SHADE_Managed/src/Engine/ECS.h++ b/SHADE_Managed/src/Engine/ECS.h++ new file mode 100644 index 00000000..f2588294 --- /dev/null +++ b/SHADE_Managed/src/Engine/ECS.h++ @@ -0,0 +1,52 @@ +/************************************************************************************//*! +\file ECS.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 27, 2021 +\brief Contains the definition of templated functions for the managed Component + classes. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Include +#include "ECS.hxx" +// External Dependencies +#include "ECS_Base/Managers/SHComponentManager.h" +#include "ECS_Base/Managers/SHEntityManager.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + template + NativeComponent* ECS::GetNativeComponent(Entity entity) + { + // Null Check + NativeComponent* component = SHComponentManager::GetComponent_s(entity); + if (component == nullptr) + throw gcnew System::NullReferenceException("Attempted to get a native Component that does not exist."); + + return component; + } + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + template + ECS::ComponentSet ECS::createComponentSet() + { + return ComponentSet + { + ManagedType::typeid, + SHComponentManager::AddComponent, + SHComponentManager::HasComponent, + SHComponentManager::RemoveComponent + }; + } +} diff --git a/SHADE_Managed/src/Engine/ECS.hxx b/SHADE_Managed/src/Engine/ECS.hxx new file mode 100644 index 00000000..18acf30d --- /dev/null +++ b/SHADE_Managed/src/Engine/ECS.hxx @@ -0,0 +1,187 @@ +/************************************************************************************//*! +\file ECS.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definitions of the GameObject managed class which define an + abstraction for working with Entities in managed code. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "ECS_Base/Managers/SHComponentManager.h" +// Project Includes +#include "Components/Component.hxx" + +namespace SHADE +{ + /// + /// Static class which contains functions that map Pls::ECS's Component manipulation + /// functions to managed generic functions. + /// + private ref class ECS abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Component Manipulation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Component to the specified Entity. + /// + /// Type of the Component to add. + /// + /// Entity object that should have the specified Component added to. + /// + /// Reference to the Component that was added. + generic where T : BaseComponent + static T AddComponent(EntityID entity); + /// + /// Gets a Component from the specified Entity. + /// + /// Type of the Component to get. + /// Entity object to get the Component from. + /// + /// Reference to the Component or null if the Entity does not have the + /// specified Component. + /// + generic where T : BaseComponent + static T GetComponent(EntityID entity); + /// + /// Retrieves the first Component from the specified GameObject's children that + /// matches the specified type. + /// + /// Type of the Component to get. + /// Entity object to get the Component from. + /// + /// Reference to the Component or null if the Entity does not have the + /// specified Component. + /// + generic where T : BaseComponent + static T GetComponentInChildren(EntityID entity); + /// + /// Retrieves a list of Components from the specified GameObject's children that + /// matches the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetComponentInChildren() instead. + /// This does not search the specified entity. + /// + /// Type of the Component to get. + /// Entity object to get the Component from. + /// + /// Newly allocated List of components. Will be null if no components are found. + /// + generic where T : BaseComponent + static System::Collections::Generic::IEnumerable^ GetComponentsInChildren(EntityID entity); + /// + /// Ensures a Component on the specified Entity. + /// + /// Type of the Component to ensure. + /// Entity object to ensure the Component on. + /// Reference to the Component. + generic where T : BaseComponent + static T EnsureComponent(EntityID entity); + /// + /// Checks if the specified Entity has the specified Component. + /// + /// Type of the Component to check for. + /// Entity object to check for the Component. + /// + /// True if the specified Entity has the specified Component. False otherwise. + /// + generic where T : BaseComponent + static bool HasComponent(EntityID entity); + /// + /// Removes a Component from the specified Entity. + /// + /// Type of the Component to remove. + /// + /// Entity object that should have the specified Component removed from/ + /// + generic where T : BaseComponent + static void RemoveComponent(EntityID entity); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Pointer to a function for Component manipulation operations. + /// + using ComponentFunc = void(*)(EntityID) noexcept; + using ComponentHasFunc = bool(*)(EntityID) noexcept; + /// + /// Contains a set of Component related data used for resolving operations for + /// each Component. + /// + value struct ComponentSet + { + public: + System::Type^ Type; + ComponentFunc AddFunction; + ComponentHasFunc HasFunction; + ComponentFunc RemoveFunction; + }; + + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves a pointer to the native unmanaged component of the specified + /// Entity. + /// + /// + /// Pointer to the native component. Will be nullptr if it does not exist. + /// + /// + /// Thrown if the Entity specified is invalid. + /// + /// + /// Thrown if an attempt to retrieve the native component fails. + /// + template + static NativeComponent* GetNativeComponent(Entity entity); + + private: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Static constructor to initialize static data + /// + static ECS(); + + /*-----------------------------------------------------------------------------*/ + /* Static Data Members */ + /*-----------------------------------------------------------------------------*/ + static System::Collections::Generic::List componentMap; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a ComponentSet for a pair of Native and Managed Components. + /// + /// Type of the Native Component. + /// Type of the Managed Component. + /// ComponentSet for the parameters specified. + template + static ComponentSet createComponentSet(); + /// + /// Creates an instance of the Managed representation of a Component with a + /// native Entity. + /// + /// Type of Component to create. + /// Native Entity that this Component is tied to. + /// The created Managed representation of the Component. + generic where T : BaseComponent + static T createManagedComponent(EntityID entity); + }; +} + +#include "ECS.h++" diff --git a/SHADE_Managed/src/Engine/EngineInterface.cxx b/SHADE_Managed/src/Engine/EngineInterface.cxx new file mode 100644 index 00000000..2009b2e5 --- /dev/null +++ b/SHADE_Managed/src/Engine/EngineInterface.cxx @@ -0,0 +1,138 @@ +/************************************************************************************//*! +\file EngineInterface.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the implementation of the managed EngineInterface static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "EngineInterface.hxx" +// Standard Libraries +#include +// Project Headers +#include "Utility/Convert.hxx" +#include "Utility/Debug.hxx" +#include "Scripts/ScriptStore.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Interop Static Functions */ + /*---------------------------------------------------------------------------------*/ + void EngineInterface::Init() + { + SAFE_NATIVE_CALL_BEGIN + // Set up exception handler + System::AppDomain::CurrentDomain->UnhandledException += exceptionHandler; + LoadScriptAssembly(); + Debug::Log("[EngineInterface] Successfully initialized managed runtime."); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.EngineInterface") + } + void EngineInterface::UnloadScriptAssembly() + { + SAFE_NATIVE_CALL_BEGIN + std::ostringstream oss; + oss << "[EngineInterface] Unloading " << Convert::ToNative(ManagedLibraryName) << ".dll"; + ScriptStore::Exit(); + + // Unload the script + scriptContext->Unload(); + scriptContext = nullptr; + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + + // Unload the assembly File + if (managedLibFile != nullptr) + { + managedLibFile->Close(); + managedLibFile = nullptr; + } + + oss.str(""); + oss << "[EngineInterface] Successfully unloaded " << Convert::ToNative(ManagedLibraryName) << ".dll"; + Debug::Log(oss.str()); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.EngineInterface") + } + void EngineInterface::LoadScriptAssembly() + { + SAFE_NATIVE_CALL_BEGIN + scriptContext = gcnew DisposableAssemblyLoadContext(); + loadManagedLibrary(); + ScriptStore::Init(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.EngineInterface") + } + void EngineInterface::ReloadScriptAssembly() + { + SAFE_NATIVE_CALL_BEGIN + // Stop scripts + UnloadScriptAssembly(); + // Reload assembly and restart scripts runtime + LoadScriptAssembly(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.EngineInterface") + } + void EngineInterface::Exit() + { + SAFE_NATIVE_CALL_BEGIN + // Clean up ScriptStore + ScriptStore::Exit(); + scriptContext->Unload(); + + // Release exception handler + System::AppDomain::CurrentDomain->UnhandledException -= exceptionHandler; + SAFE_NATIVE_CALL_END_N("SHADE_Managed.EngineInterface") + } + + /*---------------------------------------------------------------------------------*/ + /* Constructor */ + /*---------------------------------------------------------------------------------*/ + static EngineInterface::EngineInterface() + { + exceptionHandler = gcnew System::UnhandledExceptionEventHandler(unhandledExceptionHandler); + managedLibPath = System::Reflection::Assembly::GetExecutingAssembly()->Location->Replace("SHADE_Managed.dll", ManagedLibraryName + ".dll"); + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void EngineInterface::loadManagedLibrary() + { + using namespace System::IO; + + std::ostringstream oss; + try + { + oss << "[EngineInterface] Loading " << Convert::ToNative(ManagedLibraryName) << ".dll"; + managedLibFile = File::Open(managedLibPath, FileMode::Open, FileAccess::Read); + scriptContext->LoadFromStream(managedLibFile); + oss.str(""); + oss << "[EngineInterface] Successfully loaded " << Convert::ToNative(ManagedLibraryName) << ".dll"; + Debug::Log(oss.str()); + } + catch (System::Exception^ e) + { + oss << "[EngineInterface] Unable to load " << Convert::ToNative(ManagedLibraryName) << ".dll!" + << "(" << Convert::ToNative(e->ToString()) << ")"; + Debug::LogError(oss.str()); + } + } + + /*---------------------------------------------------------------------------------*/ + /* Exception Handler Functions */ + /*---------------------------------------------------------------------------------*/ + void EngineInterface::unhandledExceptionHandler(System::Object^, System::UnhandledExceptionEventArgs^ e) + { + std::ostringstream oss; + oss << "[EngineInterface] Unhandled managed exception: " + << Convert::ToNative(e->ExceptionObject->GetType()->ToString()) << ": " + << Convert::ToNative(e->ExceptionObject->ToString()); + Debug::LogError(oss.str()); + } +} diff --git a/SHADE_Managed/src/Engine/EngineInterface.hxx b/SHADE_Managed/src/Engine/EngineInterface.hxx new file mode 100644 index 00000000..37ded4eb --- /dev/null +++ b/SHADE_Managed/src/Engine/EngineInterface.hxx @@ -0,0 +1,90 @@ +/************************************************************************************//*! +\file EngineInterface.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definitions of the managed EngineInterface static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Utility/DisposableAssemblyLoadContext.hxx" + +namespace SHADE +{ + /// + /// Static class that contains the functions for interfacing with the core + /// SHADE Engine written in C++ for managing the lifecycle of managed code. + /// + private ref class EngineInterface abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + /// + /// Name of the Managed Library that contains the C# scripts written externally. + /// + literal System::String^ ManagedLibraryName = "SHADE_Scripting"; + + /*-----------------------------------------------------------------------------*/ + /* Interop Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Initialises all required components for managed code. + /// + static void Init(); + /// + /// Unloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// + static void UnloadScriptAssembly(); + /// + /// Loads the managed script assembly. Ensure this is only called after + /// UnloadScriptAssembly() has been called. + /// + static void LoadScriptAssembly(); + /// + /// Reloads the managed script assembly. + /// Take note that this will clear all existing scripts, ensure that the scene + /// is saved before doing so. + /// Equivalent to calling UnloadScriptAssembly() and then LoadScriptAssembly(). + /// + static void ReloadScriptAssembly(); + /// + /// Cleans up all required components for managed code. + /// + static void Exit(); + + private: + /*-----------------------------------------------------------------------------*/ + /* Constructor */ + /*-----------------------------------------------------------------------------*/ + static EngineInterface(); + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + static DisposableAssemblyLoadContext^ scriptContext; + static System::UnhandledExceptionEventHandler^ exceptionHandler; + static System::String^ managedLibPath; + static System::IO::FileStream^ managedLibFile; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static void loadManagedLibrary(); + + /*-----------------------------------------------------------------------------*/ + /* Exception Handler Functions */ + /*-----------------------------------------------------------------------------*/ + static void unhandledExceptionHandler(System::Object^ sender, System::UnhandledExceptionEventArgs^ e); + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Engine/Entity.cxx b/SHADE_Managed/src/Engine/Entity.cxx new file mode 100644 index 00000000..22e8a8c2 --- /dev/null +++ b/SHADE_Managed/src/Engine/Entity.cxx @@ -0,0 +1,28 @@ +/************************************************************************************//*! +\file Entity.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the EntityUtils managed + static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Entity.hxx" +// External Dependencies +#include "ECS_Base/Managers/SHEntityManager.h" + +namespace SHADE +{ + bool EntityUtils::IsValid(Entity^ entity) + { + return SHEntityManager::IsValidEID(static_cast(entity)); + } +} diff --git a/SHADE_Managed/src/Engine/Entity.hxx b/SHADE_Managed/src/Engine/Entity.hxx new file mode 100644 index 00000000..7be9340b --- /dev/null +++ b/SHADE_Managed/src/Engine/Entity.hxx @@ -0,0 +1,41 @@ +/************************************************************************************//*! +\file Entity.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definitions of a managed Entity identifier and declarations + of useful utility functions for working with Entity identifiers. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "ECS_Base/Entity/SHEntity.h" + +namespace SHADE +{ + /// + /// Managed representation of a native ECS Entity. + /// + using Entity = System::UInt32; + + /// + /// Static class that contains useful utility functions for working with Entity. + /// + private ref class EntityUtils abstract sealed + { + public: + /// + /// Checks if the specified entity is valid. This is done by checking if it + /// matches Pls::Entity::INVALID. + /// + /// The Entity to check. + /// True if the specified Entity is valid. + static bool IsValid(Entity^ entity); + }; +} diff --git a/SHADE_Managed/src/Engine/GameObject.cxx b/SHADE_Managed/src/Engine/GameObject.cxx new file mode 100644 index 00000000..200b2079 --- /dev/null +++ b/SHADE_Managed/src/Engine/GameObject.cxx @@ -0,0 +1,427 @@ +/************************************************************************************//*! +\file GameObject.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the GameObject managed class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "GameObject.hxx" +// External Dependencies +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Scene/SHSceneGraph.h" +// Project Headers +#include "ECS.hxx" +#include "Utility/Convert.hxx" +#include "Scripts/ScriptStore.hxx" +#include "Utility/Debug.hxx" +#include "ChildListCache.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + GameObject GameObject::Create() + { + return GameObject(SHEntityManager::CreateEntity()); + } + + void GameObject::Destroy(GameObject obj) + { + if (!obj.valid) + throw gcnew System::NullReferenceException("Attempted to destroy a null GameObject."); + SHEntityManager::DestroyEntity(static_cast(obj.GetEntity())); + } + + System::Nullable GameObject::Find(System::String^ name) + { + // Search the GameObjectLibrary for an Entity with the specified name + const auto ENTITY_ID = SHEntityManager::GetEntityByName(Convert::ToNative(name)); + if (ENTITY_ID == MAX_EID) + { + return {}; + } + + return GameObject(ENTITY_ID); + } + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + System::String^ GameObject::Name::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return Convert::ToCLI(GetNativeEntity().name); + + } + bool GameObject::IsActiveSelf::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return GetNativeEntity().GetActive(); + } + bool GameObject::IsActiveInHierarchy::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + auto node = SHSceneManager::GetCurrentSceneGraph().GetNode(GetEntity()); + if (!node) + { + Debug::LogWarning("Attempting to access a GameObject's ActiveInHierarchy state which does not exist. Assuming inactive."); + return false; + } + return node->IsActive(); + } + Entity GameObject::EntityId::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return entity; + } + GameObject GameObject::Parent::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + const auto* ROOT = SCENE_GRAPH.GetRoot(); + const auto* NODE = SCENE_GRAPH.GetNode(entity); + if (NODE == nullptr) + throw gcnew System::InvalidOperationException("Unable to retrieve SceneGraphNode for Entity " + entity.ToString()); + + const auto* PARENT = NODE->GetParent(); + return PARENT != ROOT ? GameObject(PARENT->GetEntityID()) : GameObject(); + } + void GameObject::Parent::set(GameObject newParent) + { + if (!valid) + throw gcnew System::NullReferenceException(); + auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + + if (newParent) + SCENE_GRAPH.SetParent(entity, newParent.EntityId); + else + SCENE_GRAPH.SetParent(entity, nullptr); + } + int GameObject::ChildCount::get() + { + if (!valid) + throw gcnew System::NullReferenceException(); + + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + const auto* ROOT = SCENE_GRAPH.GetRoot(); + const auto* NODE = SCENE_GRAPH.GetNode(entity); + if (NODE == nullptr) + throw gcnew System::InvalidOperationException("Unable to retrieve SceneGraphNode for Entity " + entity.ToString()); + + return static_cast(NODE->GetChildren().size()); + } + + /*---------------------------------------------------------------------------------*/ + /* GameObject Property Functions */ + /*---------------------------------------------------------------------------------*/ + void GameObject::SetName(System::String^ name) + { + if (!valid) + throw gcnew System::NullReferenceException(); + GetNativeEntity().name = Convert::ToNative(name); + } + void GameObject::SetActive(bool active) + { + if (!valid) + throw gcnew System::NullReferenceException(); + GetNativeEntity().SetActive(active); + } + + /*---------------------------------------------------------------------------------*/ + /* Component Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T GameObject::AddComponent() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ECS::AddComponent(entity); + } + + generic + T GameObject::GetComponent() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ECS::GetComponent(entity); + } + + generic + T GameObject::GetComponentInChildren() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ECS::GetComponentInChildren(entity); + } + + generic + System::Collections::Generic::IEnumerable^ GameObject::GetComponentsInChildren() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ECS::GetComponentsInChildren(entity); + } + + generic + T GameObject::EnsureComponent() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ECS::EnsureComponent(entity); + } + + generic + void GameObject::RemoveComponent() + { + if (!valid) + throw gcnew System::NullReferenceException(); + ECS::RemoveComponent(entity); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T GameObject::AddScript() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ScriptStore::AddScript(entity); + } + + generic + T GameObject::GetScript() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ScriptStore::GetScript(entity); + } + + generic + T GameObject::GetScriptInChildren() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ScriptStore::GetScriptInChildren(entity); + } + generic + System::Collections::Generic::IEnumerable^ GameObject::GetScriptsInChildren() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ScriptStore::GetScriptsInChildren(entity); + } + + generic + System::Collections::Generic::IEnumerable^ GameObject::GetScripts() + { + if (!valid) + throw gcnew System::NullReferenceException(); + return ScriptStore::GetScripts(entity); + } + + generic + void GameObject::RemoveScript() + { + if (!valid) + throw gcnew System::NullReferenceException(); + ScriptStore::RemoveScript(entity); + } + + /*---------------------------------------------------------------------------------*/ + /* Scene Graph Functions */ + /*---------------------------------------------------------------------------------*/ + void GameObject::DetachChildren() + { + // Validity Checks + if (!valid) + throw gcnew System::NullReferenceException(); + auto node = GetSceneNode(); + if (!node) + throw gcnew System::NullReferenceException(); + + // Unparent all children to the root + for (auto child : node->GetChildren()) + { + SHSceneManager::GetCurrentSceneGraph().SetParent(child->GetEntityID(), nullptr); + } + ChildListCache::UpdateChildList(entity); + } + + GameObject GameObject::GetChild(int index) + { + // Validity Checks + if (!valid) + throw gcnew System::NullReferenceException(); + auto node = GetSceneNode(); + if (!node) + throw gcnew System::NullReferenceException(); + + auto child = node->GetChild(index); + return child ? GameObject(child->GetEntityID()) : GameObject(); + } + + System::Collections::Generic::IEnumerable^ GameObject::GetChildren() + { + // Validity Checks + if (!valid) + throw gcnew System::NullReferenceException(); + return ChildListCache::GetChildList(entity); + } + + int GameObject::GetSiblingIndex() + { + throw gcnew System::NotImplementedException(); + } + + bool GameObject::IsChildOf(GameObject gameObj) + { + // Search parents recursively + auto node = GetSceneNode(); + while (node != nullptr) + { + if (node->GetEntityID() == gameObj.entity) + return true; + + // Go up higher + node = node->GetParent(); + } + return false; + } + + void GameObject::SetAsFirstSibling() + { + throw gcnew System::NotImplementedException(); + } + + void GameObject::SetAsLastSibling() + { + throw gcnew System::NotImplementedException(); + } + + void GameObject::SetSiblingIndex(int index) + { + throw gcnew System::NotImplementedException(); + } + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + GameObject::operator bool(GameObject gameObj) + { + return gameObj.valid; + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + GameObject::GameObject(const SHEntity& entity) + : entity{ entity.GetEID() } + , children{ gcnew System::Collections::ArrayList } + , valid{ true } + {} + + GameObject::GameObject(Entity entity) + : entity{ entity } + , children{ gcnew System::Collections::ArrayList } + , valid{ true } + {} + + /*---------------------------------------------------------------------------------*/ + /* Getters */ + /*---------------------------------------------------------------------------------*/ + SHEntity& GameObject::GetNativeEntity() + { + if (!valid) + throw gcnew System::NullReferenceException(); + SHEntity* nativeEntity = SHEntityManager::GetEntityByID(entity); + if (nativeEntity == nullptr) + throw gcnew System::InvalidOperationException("[GameObject] Unable to obtain native Entity for GameObject."); + + return *nativeEntity; + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + SHSceneNode* GameObject::GetSceneNode() + { + const auto& SCENE_GRAPH = SHSceneManager::GetCurrentSceneGraph(); + const auto* ROOT = SCENE_GRAPH.GetRoot(); + if (!ROOT) + return nullptr; + return SCENE_GRAPH.GetNode(entity); + } + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + bool GameObject::Equals(GameObject other) + { + return (!valid && !other.valid) || entity == other.entity; + } + + /*---------------------------------------------------------------------------------*/ + /* Object */ + /*---------------------------------------------------------------------------------*/ + bool GameObject::Equals(Object^ o) + { + try + { + GameObject^ cmp = safe_cast(o); + return Equals(cmp); + } + catch (System::InvalidCastException^) + { + return false; + } + } + + int GameObject::GetHashCode() + { + return entity.GetHashCode(); + } + + bool GameObject::operator==(GameObject lhs, GameObject rhs) + { + return lhs.Equals(rhs); + } + + bool GameObject::operator!=(GameObject lhs, GameObject rhs) + { + return !(lhs == rhs); + } + + /*---------------------------------------------------------------------------------*/ + /* IEnummerable */ + /*---------------------------------------------------------------------------------*/ + System::Collections::Generic::IEnumerator^ GameObject::GetEnumerator() + { + System::Collections::Generic::IEnumerable^ childList = GetChildren(); + if (childList == nullptr) + return System::Linq::Enumerable::Empty()->GetEnumerator(); + else + return childList->GetEnumerator(); + } + + System::Collections::IEnumerator^ GameObject::GetEnumeratorNonGeneric() + { + return GetEnumerator(); + } +} diff --git a/SHADE_Managed/src/Engine/GameObject.hxx b/SHADE_Managed/src/Engine/GameObject.hxx new file mode 100644 index 00000000..64d1b428 --- /dev/null +++ b/SHADE_Managed/src/Engine/GameObject.hxx @@ -0,0 +1,429 @@ +/************************************************************************************//*! +\file GameObject.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definitions of the GameObject managed class which define an + abstraction for working with Entities in managed code. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Entity.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + ref class Script; + ref class BaseComponent; + + /*---------------------------------------------------------------------------------*/ + /* Class Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Lightweight object for an Entity that allows for easy access to Component and + /// Script operations. + /// Can be set to a invalid/null GameObject by default construction. + /// Can also be iterated to access children. + /// + public value class GameObject : public System::IEquatable, public System::Collections::Generic::IEnumerable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a new GameObject in the current Scene. If multiple Scenes are loaded, + /// and you would like to create an object in a specific Scene, call the Scene's + /// CreateGameObject(). + /// + /// GameObject that represents the newly created GameObject. + static GameObject Create(); + /// + /// Destroys the specified GameObject. Note that the specified GameObject will no + /// longer be a valid GameObject after this function is called. + /// + /// The GameObject to be destroyed. + static void Destroy(GameObject obj); + /// + /// Retrieves a GameObject with the specified name. If there are multiple + /// GameObjects with the same name, the first found GameObject will be retrieved. + /// There is no guaranteed order of which GameObject is considered "first". + /// + /// Name of the GameObject to find. + /// GameObject that has the specified name. Null if not found. + static System::Nullable Find(System::String^ name); + + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Name of the object that this Entity represents. + /// + property System::String^ Name + { + System::String^ get(); + } + /// + /// Whether or not this Entity alone, is active. This does not mean that this + /// object is active in the scene. For example, if this Entity's parent is not + /// active, then this Entity would also be not active. + /// + property bool IsActiveSelf + { + bool get(); + } + /// + /// Whether or not this Entity is active in the Scene hierarchy. + /// + property bool IsActiveInHierarchy + { + bool get(); + } + /// + /// Native Entity ID value for this GameObject. + /// + property Entity EntityId + { + Entity get(); + } + /// + /// The parent entity for this GameObject. + /// + property GameObject Parent + { + GameObject get(); + void set(GameObject); + } + /// + /// Number of Children held by this GameObject + /// + property int ChildCount + { + int get(); + } + + /*-----------------------------------------------------------------------------*/ + /* GameObject Property Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Sets the name of this GameObject. + /// + /// The name to set. + void SetName(System::String^ name); + /// + /// Sets the active state of this GameObject. + ///
+ /// The actual "activeness" of this GameObject is still dependent on the parents' + /// active states. + ///
+ /// + /// Whether to activate or deactivate this GameObject. + /// + void SetActive(bool active); + + /*-----------------------------------------------------------------------------*/ + /* Component Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Component to this GameObject. + /// + /// Type of the Component to add. + /// Reference to the Component that was added. + generic where T : BaseComponent + T AddComponent(); + /// + /// Gets a Component from this GameObject. + /// + /// Type of the Component to get. + /// + /// Reference to the Component or null if this GameObject does not have the + /// specified Component. + /// + generic where T : BaseComponent + T GetComponent(); + /// + /// Retrieves the first Component from this GameObject's children that matches + /// the specified type. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of the Component to get. + /// + /// Reference to the Component or null if neither of this GameObject's children + /// does not have the specified Component. + /// + generic where T : BaseComponent + T GetComponentInChildren(); + /// + /// Retrieves a list of Components from this GameObject's children that matches + /// the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetComponentInChildren() instead. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of the Component to get. + /// + /// Newly allocated List of components. Will be null if no components are found. + /// + generic where T : BaseComponent + System::Collections::Generic::IEnumerable^ GetComponentsInChildren(); + /// + /// Ensures a Component on this GameObject. + /// + /// Type of the Component to ensure. + /// + /// Reference to the Component. + /// + generic where T : BaseComponent + T EnsureComponent(); + /// + /// Removes a Component from this GameObject. If no Component exists to begin + /// with, nothing happens. + /// + /// Type of the Component to get. + generic where T : BaseComponent + void RemoveComponent(); + + /*-----------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script of the specified type to this GameObject. + /// + /// Type of Script to add. + /// Reference to the created Script. + generic where T : ref class, Script + T AddScript(); + /// + /// Retrieves a Script of the specified type from this GameObject. + /// If multiple Scripts of the same specified type are added on the same + /// GameObject, this will retrieve the first one added. + /// + /// Type of Script to retrieve. + /// Reference to the Script to retrieve. + generic where T : ref class, Script + T GetScript(); + /// + /// Retrieves a Script of the specified type from child GameObjects. + /// If multiple Scripts of the same specified type are added on the same + /// child GameObject, this will retrieve the first one added. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of Script to retrieve. + /// Reference to the Script to retrieve. + generic where T : ref class, Script + T GetScriptInChildren(); + /// + /// Retrieves a list of Scripts from this GameObject's children that matches + /// the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetComponentInChildren() instead. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of the Component to get. + /// + /// Newly allocated List of components. Will be null if no components are found. + /// + generic where T : ref class, Script + System::Collections::Generic::IEnumerable^ GetScriptsInChildren(); + /// + /// Retrieves a immutable list of Scripts of the specified type from this + /// GameObject. + /// + /// Type of Scripts to retrieve. + /// Immutable list of Scripts of the specified type. + generic where T : ref class, Script + System::Collections::Generic::IEnumerable^ GetScripts(); + /// + /// Removes all Scripts of the specified type from this GameObject. + /// + /// Type of Scripts to remove. + generic where T : ref class, Script + void RemoveScript(); + + /*-----------------------------------------------------------------------------*/ + /* Scene Graph Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Unparents all children. Useful if you want to destroy the root of a hierarchy + /// without destroying the children. + /// + void DetachChildren(); + /// + /// Returns a child by index. + /// + /// Index of the child GameObject to retrieve. + /// + /// Handle to the GameObject if the index is valid. Invalid GameObject otherwise. + /// + GameObject GetChild(int index); + /// + /// Returns a cached enumerable container of child GameObjects of this + /// GameObject. + /// + /// + /// Enumerable container of child GameObjects of this GameObject. Null if + /// ChildCount is 0. + /// + System::Collections::Generic::IEnumerable^ GetChildren(); + /// + /// Gets the sibling index. Use GetSiblingIndex to find out the GameObjects + /// place in this hierarchy. When the sibling index of a GameObject is changed, + /// its order in the Hierarchy window will also change. + /// + /// + /// Index of this GameObject among the parent GameObject's children. + /// + [System::ObsoleteAttribute("Not yet implemented.", true)] + int GetSiblingIndex(); + /// + /// Checks if this GameObject a direct or indirect child of the specified + /// GameObject. + /// + /// + /// True if this GameObject is a child, deep child (child of a child) or + /// identical to this GameObject, otherwise false. + /// + bool IsChildOf(GameObject gameObj); + /// + /// Move the GameObject to the start of the parent GameObject's children list. + /// + [System::ObsoleteAttribute("Not yet implemented.", true)] + void SetAsFirstSibling(); + /// + /// Move the GameObject to the end of the parent GameObject's children list. + /// + [System::ObsoleteAttribute("Not yet implemented.", true)] + void SetAsLastSibling(); + /// + /// Move the GameObject to the specified position in the parent GameObject's + /// children list. An existing object at that position if any, will be pushed + /// to the next index (existing element will be at index + 1). + /// + /// + /// Position to place this GameObject at in the hierarchy. Clamped to between + /// [0, parent.ChildCount]. + /// + [System::ObsoleteAttribute("Not yet implemented.", true)] + void SetSiblingIndex(int index); + + /*-----------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------*/ + /// + /// Implicit conversion operator to enable checking if a GameObject is valid. + /// + /// GameObjects to check. + /// True if the GameObject is valid. + static operator bool(GameObject gameObj); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for the GameObject. + /// + /// + /// The ECS Entity that this GameObject should represent. + /// + GameObject(const SHEntity& entity); + /// + /// Constructor for the GameObject. + /// + /// + /// Managed numerical representation of the ECS Entity that this GameObject + /// should represent. + /// + GameObject(Entity entity); + + /*-----------------------------------------------------------------------------*/ + /* Getters */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the CLR Entity object that this GameObject represents. + /// + /// Entity object that this GameObject represents. + inline Entity GetEntity() { return entity; } + /// + /// Retrieves the native Entity object that this GameObject represents. + /// + /// Native Entity object that this GameObject represents. + SHEntity& GetNativeEntity(); + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the SceneNode for this GameObject's referenced entity. + /// + /// Pointer to the SceneNode for this GameObject.. + SHSceneNode* GetSceneNode(); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Entity entity; + System::Collections::ArrayList^ children; + bool valid; + + public: + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(GameObject other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + /// + /// Checks if two GameObject references are the same. + /// + /// GameObject to check. + /// Another GameObject to check with. + /// True if both Components are the same. + static bool operator==(GameObject lhs, GameObject rhs); + /// + /// Checks if two GameObject references are different. + /// + /// GameObject to check. + /// Another GameObject to check with. + /// True if both Components are different. + static bool operator!=(GameObject lhs, GameObject rhs); + + /*-----------------------------------------------------------------------------*/ + /* IEnummerable */ + /*-----------------------------------------------------------------------------*/ + /// + System::Collections::Generic::IEnumerator^ GetEnumerator() override; + /// + System::Collections::IEnumerator^ GetEnumeratorNonGeneric() override = System::Collections::IEnumerable::GetEnumerator; + }; + +} + diff --git a/SHADE_Managed/src/Engine/GenericHandle.cxx b/SHADE_Managed/src/Engine/GenericHandle.cxx new file mode 100644 index 00000000..41a69c18 --- /dev/null +++ b/SHADE_Managed/src/Engine/GenericHandle.cxx @@ -0,0 +1,47 @@ +/************************************************************************************//*! +\file Renderable.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the functions of the managed Renderable class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "GenericHandle.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + GenericHandle::GenericHandle(Handle handle) + : id { handle.GetId().Raw } + , library { reinterpret_cast(handle.GetLibrary()) } + {} + + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + System::UInt64 GenericHandle::Id::get() + { + return id; + } + System::IntPtr GenericHandle::Library::get() + { + return library; + } + + /*---------------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*---------------------------------------------------------------------------------*/ + GenericHandle::operator bool() + { + return library.ToPointer() != nullptr && id != System::UInt64::MaxValue; + } + +} diff --git a/SHADE_Managed/src/Engine/GenericHandle.hxx b/SHADE_Managed/src/Engine/GenericHandle.hxx new file mode 100644 index 00000000..3d77f54d --- /dev/null +++ b/SHADE_Managed/src/Engine/GenericHandle.hxx @@ -0,0 +1,68 @@ +/************************************************************************************//*! +\file Handle.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the managed GenericHandle struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "Resource/SHHandle.h" + +namespace SHADE +{ + /// + /// Managed version of the generic Handle. + /// + private value struct GenericHandle + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a GenericHandle for a native generic Handle. + /// + /// Handle to create a GenericHandle from. + explicit GenericHandle(Handle handle); + + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// The internal ID of the handle. + /// + property System::UInt64 Id + { + System::UInt64 get(); + } + /// + /// The library that the handle was issued by. + /// + property System::IntPtr Library + { + System::IntPtr get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + /// + /// Converts to true if this is a valid Handle. + /// + inline operator bool(); + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + System::UInt64 id; + System::IntPtr library; + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Engine/Time.cxx b/SHADE_Managed/src/Engine/Time.cxx new file mode 100644 index 00000000..8784ec90 --- /dev/null +++ b/SHADE_Managed/src/Engine/Time.cxx @@ -0,0 +1,42 @@ +/************************************************************************************//*! +\file Time.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 19, 2022 +\brief This file is present so that the properties in Time.hxx would be compiled + into the DLL. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// External Dependencies +#include "FRC/SHFramerateController.h" +#include "Physics/System/SHPhysicsSystemInterface.h" +// Primary Header +#include "Time.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + double Time::DeltaTime::get() + { + return SHFrameRateController::GetRawDeltaTime(); + } + + + float Time::DeltaTimeF::get() + { + return static_cast(SHFrameRateController::GetRawDeltaTime()); + } + double Time::FixedDeltaTime::get() + { + return SHPhysicsSystemInterface::GetFixedDT(); + } +} \ No newline at end of file diff --git a/SHADE_Managed/src/Engine/Time.hxx b/SHADE_Managed/src/Engine/Time.hxx new file mode 100644 index 00000000..c0f0ed62 --- /dev/null +++ b/SHADE_Managed/src/Engine/Time.hxx @@ -0,0 +1,54 @@ +/************************************************************************************//*! +\file Time.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 19, 2022 +\brief Contains the definition of the Time static class and the definition of + its properties. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Static class that contains the functions for working with time. + /// + public ref class Time abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Time taken to process the previous frame. + /// + static property double DeltaTime + { + double get(); + } + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Time taken to process the previous frame. + /// + static property float DeltaTimeF + { + float get(); + } + /// + /// Time taken for Physics simulations. You should use this for operations + /// within Script.FixedUpdate() + /// + static property double FixedDeltaTime + { + double get(); + } + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Graphics/Color.cxx b/SHADE_Managed/src/Graphics/Color.cxx new file mode 100644 index 00000000..cf1fff47 --- /dev/null +++ b/SHADE_Managed/src/Graphics/Color.cxx @@ -0,0 +1,154 @@ +/************************************************************************************//*! +\file Color.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 3, 2021 +\brief Contains the definition of the functions of the managed Color struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Graphics/Color.hxx" +// Standard Libraries +#include +// Project Includes +#include "Math/Math.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Color::Color(float _red) + : Color { _red, 0.0f, 0.0f, 1.0f } + {} + Color::Color(float _red, float _green) + : Color { _red, _green, 0.0f, 1.0f } + {} + Color::Color(float _red, float _green, float _blue) + : Color { _red, _green, _blue, 1.0f } + {} + Color::Color(float _red, float _green, float _blue, float _alpha) + : r { _red } + , g { _green } + , b { _blue } + , a { _alpha } + {} + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + bool Color::Equals(Object^ o) + { + try + { + Color col = safe_cast(o); + return Equals(col); + } + catch (System::InvalidCastException^) + { + return false; + } + } + + /*---------------------------------------------------------------------------------*/ + /* Object Overrides */ + /*---------------------------------------------------------------------------------*/ + bool Color::Equals(Color other) + { + return Math::CompareFloat(this->r, other.r) + && + Math::CompareFloat(this->g, other.g) + && + Math::CompareFloat(this->b, other.b) + && + Math::CompareFloat(this->a, other.a); + } + int Color::GetHashCode() + { + const int HASH = 19; + const int HASH2 = 23; + const int HASH3 = 29; + return r.GetHashCode() * HASH + + g.GetHashCode() * HASH2 + + b.GetHashCode() * HASH3 + + a.GetHashCode(); + } + + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + Color Color::Lerp(Color colA, Color colB, float t) + { + return LerpUnclamped(colA, colB, std::clamp(t, 0.0f, 1.0f)); + } + + Color Color::LerpUnclamped(Color colA, Color colB, float t) + { + return colA + ((colB - colA) * t); + } + Color Color::operator+(Color lhs, Color rhs) + { + return Color + ( + lhs.r + rhs.r, + lhs.g + rhs.g, + lhs.b + rhs.b, + lhs.a + rhs.a + ); + } + Color Color::operator-(Color lhs, Color rhs) + { + return Color + ( + lhs.r - rhs.r, + lhs.g - rhs.g, + lhs.b - rhs.b, + lhs.a - rhs.a + ); + } + Color Color::operator*(Color lhs, Color rhs) + { + return Color + ( + lhs.r * rhs.r, + lhs.g * rhs.g, + lhs.b * rhs.b, + lhs.a * rhs.a + ); + } + Color Color::operator*(Color lhs, float rhs) + { + return Color + ( + lhs.r * rhs, + lhs.g * rhs, + lhs.b * rhs, + lhs.a * rhs + ); + } + Color Color::operator/(Color lhs, float rhs) + { + return Color + ( + lhs.r / rhs, + lhs.g / rhs, + lhs.b / rhs, + lhs.a / rhs + ); + } + bool Color::operator==(Color lhs, Color rhs) + { + return lhs.Equals(rhs); + } + bool Color::operator!=(Color lhs, Color rhs) + { + return !(lhs == rhs); + } +} diff --git a/SHADE_Managed/src/Graphics/Color.hxx b/SHADE_Managed/src/Graphics/Color.hxx new file mode 100644 index 00000000..64152394 --- /dev/null +++ b/SHADE_Managed/src/Graphics/Color.hxx @@ -0,0 +1,277 @@ +/************************************************************************************//*! +\file Color.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2022 +\brief Contains the definition of the managed Color struct with the + declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// CLR version of the the SHADE Engine's Color struct which describes a Color + /// encoded using floating point numbers that range from 0.0f to 1.0f. + /// + [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential)] + public value struct Color : public System::IEquatable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Pure black. + /// + static property Color Black + { + Color get() { return Color(0.0f, 0.0f, 0.0f); } + } + /// + /// Light Gray, lighter than gray. + /// + static property Color LightGray + { + Color get() { return Color(0.827451f, 0.827451f, 0.827451f); } + } + /// + /// Gray, halfway between black and white. + /// + static property Color Gray + { + Color get() { return Color(0.5f, 0.5f, 0.5f); } + } + /// + /// Dark Gray, darker than gray. + /// + static property Color DarkGray + { + Color get() { return Color(0.622f, 0.622f, 0.622f); } + } + /// + /// Pure white. + /// + static property Color White + { + Color get() { return Color(1.0f, 1.0f, 1.0f); } + } + /// + /// Pure red. + /// + static property Color Red + { + Color get() { return Color(1.0f, 0.0f, 0.0f); } + } + /// + /// Pure green. + /// + static property Color Green + { + Color get() { return Color(0.0f, 1.0f, 0.0f); } + } + /// + /// Pure blue. + /// + static property Color Blue + { + Color get() { return Color(0.0f, 0.0f, 1.0f); } + } + /// + /// Pure cyan, mix of pure green and blue. + /// + static property Color Cyan + { + Color get() { return Color(0.0f, 1.0f, 1.0f); } + } + /// + /// Pure magenta, mix of pure red and blue. + /// + static property Color Magenta + { + Color get() { return Color(1.0f, 0.0f, 1.0f); } + } + /// + /// Pure yellow, mix of pure red and green. + /// + static property Color Yellow + { + Color get() { return Color(1.0f, 1.0f, 0.0f); } + } + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor to construct a Color with the specified components with the + /// green, blue and alpha component set to 1.0f. + /// + /// Red component to set. + Color(float _red); + /// + /// Constructor to construct a Color with the specified components with the + /// blue and alpha component set to 1.0f. + /// + /// Red component to set. + /// Green component to set. + Color(float _red, float _green); + /// + /// Constructor to construct a Color with the specified components with the + /// alpha component set to 1.0f. + /// + /// Red component to set. + /// Green component to set. + /// Blue component to set. + Color(float _red, float _green, float _blue); + /// + /// Constructor to construct a Color with the specified components. + /// + /// Red component to set. + /// Green component to set. + /// Blue component to set. + /// Alpha component to set. + Color(float _red, float _green, float _blue, float _alpha); + + /*-----------------------------------------------------------------------------*/ + /* Public Members */ + /*-----------------------------------------------------------------------------*/ + /// + /// Red component of the colour. Ranges from 0.0f to 1.0f. + /// + float r; + /// + /// Green component of the colour. Ranges from 0.0f to 1.0f. + /// + float g; + /// + /// Blue component of the colour. Ranges from 0.0f to 1.0f. + /// + float b; + /// + /// Alpha component of the colour. Ranges from 0.0f to 1.0f. + /// + float a; + + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(Color other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + + + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// + /// The start Color, returned when t = 0.0. + /// The end Color, returned when t = 1.0. + /// + /// Value used to interpolate between a and b which is clamped to + /// the range[0, 1]. + /// + /// The interpolated Vector3. + static Color Lerp(Color colA, Color colB, float t); + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// Unlike Lerp(), t is not clamped to a range at all. + /// + /// The start Color, returned when t = 0.0. + /// The end Color, returned when t = 1.0. + /// Value used to interpolate between a and b. + /// The interpolated Color. + static Color LerpUnclamped(Color colA, Color colB, float t); + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds two Colors together and returns the result. + /// + /// Color to add. + /// Another Color to add. + /// The result of lhs added to rhs + static Color operator+(Color lhs, Color rhs); + /// + /// Subtracts a Color from another Color and returns the result. + /// + /// Color to subtract from. + /// Another Color to subtract. + /// The result of rhs subtracted from lhs. + static Color operator-(Color lhs, Color rhs); + /// + /// Calculates the component-wise multiplication of two Colors and returns the + /// result. + /// + /// Color to multiply with. + /// Another Color to multiply with. + /// The result of rhs subtracted from lhs. + static Color operator*(Color lhs, Color rhs); + /// + /// Calculates the multiplication of a Color with a scalar value and returns + /// the result. + /// + /// Color to multiply with. + /// Scalar to multiply with. + /// The result of the scalar multiplication. + static Color operator*(Color lhs, float rhs); + /// + /// Calculates the division of a Color with a scalar value and returns + /// the result. + /// + /// Scalar to divide with. + /// Color to divide with. + /// The result of the scalar division. + static Color operator/(Color lhs, float rhs); + /// + /// Checks if two Colors are approximately equal. + /// + /// Color to compare. + /// Another Color to compare. + /// + /// True if all components are approximately equal within the default + /// tolerance value. + /// + static bool operator==(Color lhs, Color rhs); + /// + /// Checks if two Colors are not approximately equal. + /// + /// Color to compare. + /// Another Color to compare. + /// + /// True if all components are not approximately equal within the default + /// tolerance value. + /// + static bool operator!=(Color lhs, Color rhs); + }; +} diff --git a/SHADE_Managed/src/Input/Input.cxx b/SHADE_Managed/src/Input/Input.cxx new file mode 100644 index 00000000..f0ea0edc --- /dev/null +++ b/SHADE_Managed/src/Input/Input.cxx @@ -0,0 +1,109 @@ +/************************************************************************************//*! +\file Input.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 16, 2022 +\brief Contains the definition of the managed Input static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "Input.hxx" +#include "Utility/Convert.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + Vector3 Input::MousePosition::get() + { + int x, y; + SHInputManager::GetMouseWindowPosition(&x, &y); + return Vector3(static_cast(x), static_cast(y), 0.0f); + } + int Input::MouseScrollDelta::get() + { + return SHInputManager::GetMouseWheelVerticalDelta(); + } + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + bool Input::GetKey(KeyCode key) + { + return SHInputManager::GetKey(static_cast(key)); + } + + bool Input::GetKeyDown(KeyCode key) + { + return SHInputManager::GetKeyDown(static_cast(key)); + } + + bool Input::GetKeyUp(KeyCode key) + { + return SHInputManager::GetKeyUp(static_cast(key)); + } + + bool Input::GetMouseButton(MouseCode mouseButton) + { + return SHInputManager::GetKey(static_cast(mouseButton)); + } + + bool Input::GetMouseButtonDown(MouseCode mouseButton) + { + return SHInputManager::GetKeyDown(static_cast(mouseButton)); + } + + bool Input::GetMouseButtonUp(MouseCode mouseButton) + { + return SHInputManager::GetKeyUp(static_cast(mouseButton)); + } + + /*---------------------------------------------------------------------------------*/ + /* Cursor Functions */ + /*---------------------------------------------------------------------------------*/ + void Input::SetMousePosition(Vector2 pos) + { + SHInputManager::SetMouseWindowPosition + ( + static_cast(pos.x), + static_cast(pos.y) + ); + } + + /*---------------------------------------------------------------------------------*/ + /* Time Functions */ + /*---------------------------------------------------------------------------------*/ + double Input::GetKeyHeldTime(KeyCode key) + { + return SHInputManager::GetKeyHeldTime(static_cast(key)); + } + + double Input::GetKeyReleasedTime(KeyCode key) + { + return SHInputManager::GetKeyReleasedTime(static_cast(key)); + } + + double Input::GetMouseHeldTime(MouseCode key) + { + return SHInputManager::GetKeyHeldTime(static_cast(key)); + } + + double Input::GetMouseReleasedTime(MouseCode key) + { + return SHInputManager::GetKeyReleasedTime(static_cast(key)); + } + + Vector2 Input::GetMouseVelocity() + { + double velX, velY; + SHInputManager::GetMouseVelocity(&velX, &velY); + + return Convert::ToCLI(SHVec2{ (float)velX,(float)velY }); + } +} \ No newline at end of file diff --git a/SHADE_Managed/src/Input/Input.hxx b/SHADE_Managed/src/Input/Input.hxx new file mode 100644 index 00000000..875054cc --- /dev/null +++ b/SHADE_Managed/src/Input/Input.hxx @@ -0,0 +1,477 @@ +/************************************************************************************//*! +\file Input.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 16, 2022 +\brief Contains the definition of the managed Input static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +#include "Input/SHInputManager.h" +#include "Math/Vector2.hxx" +#include "Math/Vector3.hxx" + +namespace SHADE +{ + /// + /// Static class responsible for providing access to Input-related functionality. + /// + public ref class Input abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Represents the available supported keycodes that can be passed into the + /// key-based Input functions. + /// + /// Attempting to follow https://docs.unity3d.com/ScriptReference/KeyCode.html + /// Win32 keycodes are shift-insensitive, i.e. 'A' and 'a' are the same keycode and '1' and '!' are the same keycode + /// + enum class KeyCode : int + { + Backspace = static_cast(SHInputManager::SH_KEYCODE::BACKSPACE), + Delete = static_cast(SHInputManager::SH_KEYCODE::DEL), + Tab = static_cast(SHInputManager::SH_KEYCODE::TAB), + Clear = static_cast(SHInputManager::SH_KEYCODE::CLEAR), + Return = static_cast(SHInputManager::SH_KEYCODE::ENTER), + Pause = static_cast(SHInputManager::SH_KEYCODE::PAUSE), + Escape = static_cast(SHInputManager::SH_KEYCODE::ESCAPE), + Space = static_cast(SHInputManager::SH_KEYCODE::SPACE), + Keypad0 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_0), + Keypad1 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_1), + Keypad2 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_2), + Keypad3 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_3), + Keypad4 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_4), + Keypad5 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_5), + Keypad6 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_6), + Keypad7 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_7), + Keypad8 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_8), + Keypad9 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_9), + KeypadPeriod = static_cast(SHInputManager::SH_KEYCODE::DECIMAL), + KeypadDivide = static_cast(SHInputManager::SH_KEYCODE::DIVIDE), + KeypadMultiply = static_cast(SHInputManager::SH_KEYCODE::MULTIPLY), + KeypadMinus = static_cast(SHInputManager::SH_KEYCODE::SUBTRACT), + KeypadPlus = static_cast(SHInputManager::SH_KEYCODE::ADD), + KeypadEnter = static_cast(SHInputManager::SH_KEYCODE::ENTER), + //KeypadEquals + UpArrow = static_cast(SHInputManager::SH_KEYCODE::UP_ARROW), + DownArrow = static_cast(SHInputManager::SH_KEYCODE::DOWN_ARROW), + RightArrow = static_cast(SHInputManager::SH_KEYCODE::RIGHT_ARROW), + LeftArrow = static_cast(SHInputManager::SH_KEYCODE::LEFT_ARROW), + Insert = static_cast(SHInputManager::SH_KEYCODE::INSERT), + Home = static_cast(SHInputManager::SH_KEYCODE::HOME), + End = static_cast(SHInputManager::SH_KEYCODE::END), + PageUp = static_cast(SHInputManager::SH_KEYCODE::PAGE_UP), + PageDown = static_cast(SHInputManager::SH_KEYCODE::PAGE_DOWN), + F1 = static_cast(SHInputManager::SH_KEYCODE::F1), + F2 = static_cast(SHInputManager::SH_KEYCODE::F2), + F3 = static_cast(SHInputManager::SH_KEYCODE::F3), + F4 = static_cast(SHInputManager::SH_KEYCODE::F4), + F5 = static_cast(SHInputManager::SH_KEYCODE::F5), + F6 = static_cast(SHInputManager::SH_KEYCODE::F6), + F7 = static_cast(SHInputManager::SH_KEYCODE::F7), + F8 = static_cast(SHInputManager::SH_KEYCODE::F8), + F9 = static_cast(SHInputManager::SH_KEYCODE::F9), + F10 = static_cast(SHInputManager::SH_KEYCODE::F10), + F11 = static_cast(SHInputManager::SH_KEYCODE::F11), + F12 = static_cast(SHInputManager::SH_KEYCODE::F12), + F13 = static_cast(SHInputManager::SH_KEYCODE::F13), + F14 = static_cast(SHInputManager::SH_KEYCODE::F14), + F15 = static_cast(SHInputManager::SH_KEYCODE::F15), + F16 = static_cast(SHInputManager::SH_KEYCODE::F16), + F17 = static_cast(SHInputManager::SH_KEYCODE::F17), + F18 = static_cast(SHInputManager::SH_KEYCODE::F18), + F19 = static_cast(SHInputManager::SH_KEYCODE::F19), + F20 = static_cast(SHInputManager::SH_KEYCODE::F20), + F21 = static_cast(SHInputManager::SH_KEYCODE::F21), + F22 = static_cast(SHInputManager::SH_KEYCODE::F22), + F23 = static_cast(SHInputManager::SH_KEYCODE::F23), + F24 = static_cast(SHInputManager::SH_KEYCODE::F24), + Alpha0 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_0), + Alpha1 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_1), + Alpha2 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_2), + Alpha3 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_3), + Alpha4 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_4), + Alpha5 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_5), + Alpha6 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_6), + Alpha7 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_7), + Alpha8 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_8), + Alpha9 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_9), + //Exclaim + //DoubleQuote + //Hash + //Dollar + //Percent + //Ampersand + Quote = static_cast(SHInputManager::SH_KEYCODE::OEM_7), + //LeftParen + //RightParen + //Asterisk + //Plus + Comma = static_cast(SHInputManager::SH_KEYCODE::OEM_COMMA), + Minus = static_cast(SHInputManager::SH_KEYCODE::OEM_MINUS), + Period = static_cast(SHInputManager::SH_KEYCODE::OEM_PERIOD), + Slash = static_cast(SHInputManager::SH_KEYCODE::OEM_2), + //Colon + Semicolon = static_cast(SHInputManager::SH_KEYCODE::OEM_1), + //Less + Equals = static_cast(SHInputManager::SH_KEYCODE::OEM_PLUS), + //Greater + //Question + //At + LeftBracket = static_cast(SHInputManager::SH_KEYCODE::OEM_4), + Backslash = static_cast(SHInputManager::SH_KEYCODE::OEM_5), + RightBracket = static_cast(SHInputManager::SH_KEYCODE::OEM_6), + //Caret + //Underscore + BackQuote = static_cast(SHInputManager::SH_KEYCODE::OEM_3), + A = static_cast(SHInputManager::SH_KEYCODE::A), + B = static_cast(SHInputManager::SH_KEYCODE::B), + C = static_cast(SHInputManager::SH_KEYCODE::C), + D = static_cast(SHInputManager::SH_KEYCODE::D), + E = static_cast(SHInputManager::SH_KEYCODE::E), + F = static_cast(SHInputManager::SH_KEYCODE::F), + G = static_cast(SHInputManager::SH_KEYCODE::G), + H = static_cast(SHInputManager::SH_KEYCODE::H), + I = static_cast(SHInputManager::SH_KEYCODE::I), + J = static_cast(SHInputManager::SH_KEYCODE::J), + K = static_cast(SHInputManager::SH_KEYCODE::K), + L = static_cast(SHInputManager::SH_KEYCODE::L), + M = static_cast(SHInputManager::SH_KEYCODE::M), + N = static_cast(SHInputManager::SH_KEYCODE::N), + O = static_cast(SHInputManager::SH_KEYCODE::O), + P = static_cast(SHInputManager::SH_KEYCODE::P), + Q = static_cast(SHInputManager::SH_KEYCODE::Q), + R = static_cast(SHInputManager::SH_KEYCODE::R), + S = static_cast(SHInputManager::SH_KEYCODE::S), + T = static_cast(SHInputManager::SH_KEYCODE::T), + U = static_cast(SHInputManager::SH_KEYCODE::U), + V = static_cast(SHInputManager::SH_KEYCODE::V), + W = static_cast(SHInputManager::SH_KEYCODE::W), + X = static_cast(SHInputManager::SH_KEYCODE::X), + Y = static_cast(SHInputManager::SH_KEYCODE::Y), + Z = static_cast(SHInputManager::SH_KEYCODE::Z), + //LeftCurlyBracket + //Pipe + //RightCurlyBracket + //Tilde + NumLock = static_cast(SHInputManager::SH_KEYCODE::NUM_LOCK), + CapsLock = static_cast(SHInputManager::SH_KEYCODE::CAPS_LOCK), + ScrollLock = static_cast(SHInputManager::SH_KEYCODE::SCROLL_LOCK), + RightShift = static_cast(SHInputManager::SH_KEYCODE::RIGHT_SHIFT), + LeftShift = static_cast(SHInputManager::SH_KEYCODE::LEFT_SHIFT), + RightControl = static_cast(SHInputManager::SH_KEYCODE::RIGHT_CTRL), + LeftControl = static_cast(SHInputManager::SH_KEYCODE::LEFT_CTRL), + RightAlt = static_cast(SHInputManager::SH_KEYCODE::RIGHT_ALT), + LeftAlt = static_cast(SHInputManager::SH_KEYCODE::LEFT_ALT), + LeftWindows = static_cast(SHInputManager::SH_KEYCODE::LEFT_WINDOWS), + RightWindows = static_cast(SHInputManager::SH_KEYCODE::RIGHT_WINDOWS), + //AltGr + Help = static_cast(SHInputManager::SH_KEYCODE::HELP), + Print = static_cast(SHInputManager::SH_KEYCODE::PRINT), + SysReq = static_cast(SHInputManager::SH_KEYCODE::PRINT_SCREEN), + //Break + //Menu + //Mouse buttons use mouse codes, which are enums declared later + //TODO Controller input +#if 0 + Space = static_cast(SHInputManager::SH_KEYCODE::SPACE), + //Apostrophe = static_cast(SHInputManager::SH_KEYCODE::APOSTROPHE), + Comma = static_cast(SHInputManager::SH_KEYCODE::OEM_COMMA), + Minus = static_cast(SHInputManager::SH_KEYCODE::OEM_MINUS), + Period = static_cast(SHInputManager::SH_KEYCODE::OEM_PERIOD), + //Slash = static_cast(SHInputManager::SH_KEYCODE::SLASH), + Key0 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_0), + Key1 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_1), + Key2 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_2), + Key3 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_3), + Key4 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_4), + Key5 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_5), + Key6 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_6), + Key7 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_7), + Key8 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_8), + Key9 = static_cast(SHInputManager::SH_KEYCODE::NUMBER_9), + + //Semicolon = static_cast(SHInputManager::SH_KEYCODE::SEMICOLON), + //Equal = static_cast(SHInputManager::SH_KEYCODE::EQUAL), + + A = static_cast(SHInputManager::SH_KEYCODE::A), + B = static_cast(SHInputManager::SH_KEYCODE::B), + C = static_cast(SHInputManager::SH_KEYCODE::C), + D = static_cast(SHInputManager::SH_KEYCODE::D), + E = static_cast(SHInputManager::SH_KEYCODE::E), + F = static_cast(SHInputManager::SH_KEYCODE::F), + G = static_cast(SHInputManager::SH_KEYCODE::G), + H = static_cast(SHInputManager::SH_KEYCODE::H), + I = static_cast(SHInputManager::SH_KEYCODE::I), + J = static_cast(SHInputManager::SH_KEYCODE::J), + K = static_cast(SHInputManager::SH_KEYCODE::K), + L = static_cast(SHInputManager::SH_KEYCODE::L), + M = static_cast(SHInputManager::SH_KEYCODE::M), + N = static_cast(SHInputManager::SH_KEYCODE::N), + O = static_cast(SHInputManager::SH_KEYCODE::O), + P = static_cast(SHInputManager::SH_KEYCODE::P), + Q = static_cast(SHInputManager::SH_KEYCODE::Q), + R = static_cast(SHInputManager::SH_KEYCODE::R), + S = static_cast(SHInputManager::SH_KEYCODE::S), + T = static_cast(SHInputManager::SH_KEYCODE::T), + U = static_cast(SHInputManager::SH_KEYCODE::U), + V = static_cast(SHInputManager::SH_KEYCODE::V), + W = static_cast(SHInputManager::SH_KEYCODE::W), + X = static_cast(SHInputManager::SH_KEYCODE::X), + Y = static_cast(SHInputManager::SH_KEYCODE::Y), + Z = static_cast(SHInputManager::SH_KEYCODE::Z), + + //LeftBracket = static_cast(SHInputManager::SH_KEYCODE::LEFTBRACKET), + //BackSlash = static_cast(SHInputManager::SH_KEYCODE::BACKSLASH), + //RightBracket = static_cast(SHInputManager::SH_KEYCODE::RIGHTBRACKET), + //GraveAccent = static_cast(SHInputManager::SH_KEYCODE::GRAVEACCENT), + + //WORLD1 = static_cast(SHInputManager::SH_KEYCODE::WORLD1), + //WORLD2 = static_cast(SHInputManager::SH_KEYCODE::WORLD2), + + /* Function keys */ + Escape = static_cast(SHInputManager::SH_KEYCODE::ESCAPE), + Enter = static_cast(SHInputManager::SH_KEYCODE::ENTER), + Tab = static_cast(SHInputManager::SH_KEYCODE::TAB), + Backspace = static_cast(SHInputManager::SH_KEYCODE::BACKSPACE), + Insert = static_cast(SHInputManager::SH_KEYCODE::INSERT), + Delete = static_cast(SHInputManager::SH_KEYCODE::DEL), + Right = static_cast(SHInputManager::SH_KEYCODE::RIGHT_ARROW), + Left = static_cast(SHInputManager::SH_KEYCODE::LEFT_ARROW), + Down = static_cast(SHInputManager::SH_KEYCODE::DOWN_ARROW), + Up = static_cast(SHInputManager::SH_KEYCODE::UP_ARROW), + PageUp = static_cast(SHInputManager::SH_KEYCODE::PAGE_UP), + PageDown = static_cast(SHInputManager::SH_KEYCODE::PAGE_DOWN), + Home = static_cast(SHInputManager::SH_KEYCODE::HOME), + End = static_cast(SHInputManager::SH_KEYCODE::END), + CapsLock = static_cast(SHInputManager::SH_KEYCODE::CAPS_LOCK), + ScrollLock = static_cast(SHInputManager::SH_KEYCODE::SCROLL_LOCK), + NumLock = static_cast(SHInputManager::SH_KEYCODE::NUM_LOCK), + PrintScreen = static_cast(SHInputManager::SH_KEYCODE::PRINT_SCREEN), + Pause = static_cast(SHInputManager::SH_KEYCODE::PAUSE), + F1 = static_cast(SHInputManager::SH_KEYCODE::F1), + F2 = static_cast(SHInputManager::SH_KEYCODE::F2), + F3 = static_cast(SHInputManager::SH_KEYCODE::F3), + F4 = static_cast(SHInputManager::SH_KEYCODE::F4), + F5 = static_cast(SHInputManager::SH_KEYCODE::F5), + F6 = static_cast(SHInputManager::SH_KEYCODE::F6), + F7 = static_cast(SHInputManager::SH_KEYCODE::F7), + F8 = static_cast(SHInputManager::SH_KEYCODE::F8), + F9 = static_cast(SHInputManager::SH_KEYCODE::F9), + F10 = static_cast(SHInputManager::SH_KEYCODE::F10), + F11 = static_cast(SHInputManager::SH_KEYCODE::F11), + F12 = static_cast(SHInputManager::SH_KEYCODE::F12), + F13 = static_cast(SHInputManager::SH_KEYCODE::F13), + F14 = static_cast(SHInputManager::SH_KEYCODE::F14), + F15 = static_cast(SHInputManager::SH_KEYCODE::F15), + F16 = static_cast(SHInputManager::SH_KEYCODE::F16), + F17 = static_cast(SHInputManager::SH_KEYCODE::F17), + F18 = static_cast(SHInputManager::SH_KEYCODE::F18), + F19 = static_cast(SHInputManager::SH_KEYCODE::F19), + F20 = static_cast(SHInputManager::SH_KEYCODE::F20), + F21 = static_cast(SHInputManager::SH_KEYCODE::F21), + F22 = static_cast(SHInputManager::SH_KEYCODE::F22), + F23 = static_cast(SHInputManager::SH_KEYCODE::F23), + F24 = static_cast(SHInputManager::SH_KEYCODE::F24), + + /* Keypad */ + KeyPad0 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_0), + KeyPad1 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_1), + KeyPad2 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_2), + KeyPad3 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_3), + KeyPad4 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_4), + KeyPad5 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_5), + KeyPad6 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_6), + KeyPad7 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_7), + KeyPad8 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_8), + KeyPad9 = static_cast(SHInputManager::SH_KEYCODE::NUMPAD_9), + //KeyPadDecimal = static_cast(SHInputManager::SH_KEYCODE::KPDECIMAL), + //KeyPadDivide = static_cast(SHInputManager::SH_KEYCODE::KPDIVIDE), + //KeyPadMultiply = static_cast(SHInputManager::SH_KEYCODE::KPMULTIPLY), + //KeyPadSubtract = static_cast(SHInputManager::SH_KEYCODE::KPSUBTRACT), + //KeyPadAdd = static_cast(SHInputManager::SH_KEYCODE::KPADD), + //KeyPadEnter = static_cast(SHInputManager::SH_KEYCODE::KPENTER), + //KeyPadEqual = static_cast(SHInputManager::SH_KEYCODE::KEYPAD), + + Shift = static_cast(SHInputManager::SH_KEYCODE::SHIFT), + LeftControl = static_cast(SHInputManager::SH_KEYCODE::LEFT_CTRL), + LeftAlt = static_cast(SHInputManager::SH_KEYCODE::LEFT_ALT), + LeftSuper = static_cast(SHInputManager::SH_KEYCODE::LEFT_WINDOWS), + RightShift = static_cast(SHInputManager::SH_KEYCODE::RIGHT_SHIFT), + RightControl = static_cast(SHInputManager::SH_KEYCODE::RIGHT_CTRL), + RightAlt = static_cast(SHInputManager::SH_KEYCODE::RIGHT_ALT), + RightSuper = static_cast(SHInputManager::SH_KEYCODE::RIGHT_WINDOWS), + + /* Gamepad */ + JoystickA = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_A), + JoystickB = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_B), + JoystickX = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_X), + JoystickY = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_Y), + JoystickLeftBumper = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_LEFTSHOULDER), + JoystickRightBumper = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_RIGHTSHOULDER), + JoystickLeftTrigger = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_LEFTTRIGGER), + JoystickRightTrigger = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_RIGHTTRIGGER), + JoystickDPadUp = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_DPAD_UP), + JoystickDPadDown = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_DPAD_DOWN), + JoystickDPadLeft = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_DPAD_LEFT), + JoystickDPadRight = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_DPAD_RIGHT), + JoystickMenu = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_MENU), + JoystickView = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_VIEW), + JoystickLeftStick = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_LEFT_THUMBSTICK_BUTTON), + JoystickRightStick = static_cast(SHInputManager::SH_KEYCODE::GAMEPAD_RIGHT_THUMBSTICK_BUTTON), + + /* Unity Gamepad Mappings */ + JoystickButton0 = JoystickA, + JoystickButton1 = JoystickB, + JoystickButton2 = JoystickX, + JoystickButton3 = JoystickY, + JoystickButton4 = JoystickLeftBumper, + JoystickButton5 = JoystickRightBumper, + JoystickButton6 = JoystickView, + JoystickButton7 = JoystickMenu, + JoystickButton8 = JoystickLeftStick, + JoystickButton9 = JoystickRightStick +#endif + + }; + /// + /// Represents the available supported mouse keycodes that can be passed into the + /// mouse-button-based Input functions. + /// + enum class MouseCode : int + { + LeftButton = static_cast(SHInputManager::SH_KEYCODE::LMB), + RightButton = static_cast(SHInputManager::SH_KEYCODE::RMB), + MiddleButton = static_cast(SHInputManager::SH_KEYCODE::MMB), + Button3 = static_cast(SHInputManager::SH_KEYCODE::XMB1), + Button4 = static_cast(SHInputManager::SH_KEYCODE::XMB2) + }; + + /*-----------------------------------------------------------------------------*/ + /* Properites */ + /*-----------------------------------------------------------------------------*/ + /// + /// Mouse position in screen coordinates relative to the top left of the window. + /// This value is a Vector3 for compatibility with functions that have Vector3 + /// arguments. The z component of the Vector3 is always 0 + /// + static property Vector3 MousePosition + { + Vector3 get(); + } + /// + /// Amnount of vertical mouse scroll in this frame. + /// + static property int MouseScrollDelta + { + int get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Checks if a specified key is being held down. + /// This will also be true if GetKeyDown() is true. + /// + /// KeyCode of the key to check. + /// True while the user holds down the key specified. + static bool GetKey(KeyCode key); + /// + /// Checks if a specified key is pressed and was not pressed before. + /// + /// KeyCode of the key to check. + /// + /// True during the frame the user starts pressing down the key specified. + /// + static bool GetKeyDown(KeyCode key); + /// + /// Checks if a specified key is no longer pressed pressed and was pressed + /// before. + /// + /// KeyCode of the key to check. + /// + /// True during the frame the user releases the key identified by name. + /// + static bool GetKeyUp(KeyCode key); + /// + /// Checks if a specified mouse button is being held down. + /// This will also be true if GetMouseButtonDown() is true. + /// + /// MouseCode of the mouse button to check. + /// True while the user holds down the mouse button specified. + static bool GetMouseButton(MouseCode mouseButton); + /// + /// Checks if a specified mouse button is pressed and was not pressed before. + /// + /// MouseCode of the mouse button to check. + /// + /// True during the frame the user pressed the given mouse button. + /// + static bool GetMouseButtonDown(MouseCode mouseButton); + /// + /// Checks if a specified mouse button is no longer pressed and was pressed + /// before. + /// + /// MouseCode of the mouse button to check. + /// + /// True during the frame the user releases the given mouse button. + /// + static bool GetMouseButtonUp(MouseCode mouseButton); + + /*-----------------------------------------------------------------------------*/ + /* Cursor Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Sets the position of the mouse cursor relative to the top left corner of the + /// window. + /// + /// + /// Position of the mouse in window pixel coordinates to set. + /// + static void SetMousePosition(Vector2 pos); + + /*-----------------------------------------------------------------------------*/ + /* Timing Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the duration that the specified key has been held or was last held + /// for. + /// + /// The key to check. + /// Time in seconds that the key was held. + static double GetKeyHeldTime(KeyCode key); + /// + /// Retrieves the duration that the specified key has not been held or was last + /// not been held for. + /// + /// The key to check. + /// Time in seconds that the key was held. + static double GetKeyReleasedTime(KeyCode key); + /// + /// Retrieves the duration that the specified key has been held or was last held + /// for. + /// + /// The key to check. + /// Time in seconds that the key was held. + static double GetMouseHeldTime(MouseCode mouseButton); + /// + /// Retrieves the duration that the specified key has not been held or was last + /// not been held for. + /// + /// The key to check. + /// Time in seconds that the key was held. + static double GetMouseReleasedTime(MouseCode mouseButton); + + static Vector2 GetMouseVelocity(); + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Math/Math.cxx b/SHADE_Managed/src/Math/Math.cxx new file mode 100644 index 00000000..bc625f5b --- /dev/null +++ b/SHADE_Managed/src/Math/Math.cxx @@ -0,0 +1,68 @@ +/************************************************************************************//*! +\file Math.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 11, 2021 +\brief Contains the implementation of the functions of the managed Math struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Math/Math.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Utility Functions */ + /*---------------------------------------------------------------------------------*/ + float Math::Wrap(float value, float min, float max) + { + while (value < min) + { + value = max - (min - value); + } + while (value > max) + { + value = min + (value - max); + } + return value; + } + float Math::DegreesToRadians(float degrees) + { + return degrees * Deg2Rad; + } + float Math::RadiansToDegrees(float radians) + { + return radians * Rad2Deg; + } + float Math::Lerp(float a, float b, float t) + { + return LerpUnclamped(a, b, System::Math::Clamp(t, 0.0f, 1.0f)); + } + float Math::LerpUnclamped(float a, float b, float t) + { + return a + t * (b - a); + } + + float Math::InverseLerp(float a, float b, float value) + { + return (value - a) / (b - a); + } + + bool Math::CompareFloat(float a, float b) + { + return CompareFloat(a, b, Epsilon); + } + + bool Math::CompareFloat(float a, float b, float tolerance) + { + return System::MathF::Abs(a - b) < tolerance; + } + +} diff --git a/SHADE_Managed/src/Math/Math.hxx b/SHADE_Managed/src/Math/Math.hxx new file mode 100644 index 00000000..c6b8394e --- /dev/null +++ b/SHADE_Managed/src/Math/Math.hxx @@ -0,0 +1,110 @@ +/************************************************************************************//*! +\file Math.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 11, 2021 +\brief Contains the definition of the managed Math static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Contains utility Math functions. + /// + public ref class Math abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Static Constants */ + /*-----------------------------------------------------------------------------*/ + /// + /// Degrees-to-radians conversion constant + /// + static constexpr float Deg2Rad = System::Math::PI / 180.0f; + /// + /// Radians-to-degrees conversion constant + /// + static constexpr float Rad2Deg = 180.0f / System::Math::PI; + /// + /// Small value used for single precision floating point comparisons. + /// + static constexpr float Epsilon = 0.001f; + + /*-----------------------------------------------------------------------------*/ + /* Utility Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Wraps a value if they get to low or too high. + /// + /// Value to wrap. + /// Minimum value to wrap at. + /// Maximum value to wrap at. + /// Wrapped value. + static float Wrap(float value, float min, float max); + /// + /// Converts an angle from degree representation to radian representation. + /// + /// Degree-based angle to convert. + /// The specified angle in radians. + static float DegreesToRadians(float degrees); + /// + /// Converts an angle from radian representation to degree representation. + /// + /// Radian-based angle to convert. + /// The specified angle in degrees. + static float RadiansToDegrees(float radians); + /// + /// Linearly interpolates between a and b by t. + /// The parameter t is clamped to the range [0, 1]. + /// + /// The start value. + /// The end value. + /// The interpolation value between the two float. + /// The interpolated float result between the two float values. + static float Lerp(float a, float b, float t); + /// + /// Linearly interpolates between a and b by t. + /// The parameter t is not clamped and a value based on a and b is supported. + /// If t is less than zero, or greater than one, then LerpUnclamped will result + /// in a return value outside the range a to b. + /// + /// The start value. + /// The end value. + /// The interpolation value between the two float. + /// The interpolated float result between the two float values. + static float LerpUnclamped(float a, float b, float t); + /// + /// Calculates the linear parameter t that produces the interpolant value within + /// the range [a, b]. + /// + /// Start value. + /// End value. + /// Value between start and end. + /// Percentage of value between start and end. + static float InverseLerp(float a, float b, float value); + /// + /// Compares if two float values are close enough to be the same with a tolerance + /// of Epsilon. + /// + /// One of the values to compare. + /// The other value to compare. + /// True if a and b are practically the same. + static bool CompareFloat(float a, float b); + /// + /// Compares if two float values are close enough to be the same with the + /// specified tolerance value. + /// + /// One of the values to compare. + /// The other value to compare. + /// Tolerance for floating point comparison. + /// True if a and b are practically the same. + static bool CompareFloat(float a, float b, float tolerance); + }; +} diff --git a/SHADE_Managed/src/Math/Quaternion.cxx b/SHADE_Managed/src/Math/Quaternion.cxx new file mode 100644 index 00000000..863241ac --- /dev/null +++ b/SHADE_Managed/src/Math/Quaternion.cxx @@ -0,0 +1,170 @@ +/************************************************************************************//*! +\file Quaternion.cxx +\author Diren D Bharwani, diren.dbharwani, 390002520 +\par email: diren.dbharwani\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definitions of functions in the Quaternion struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Quaternion.hxx" +// External Dependencies +#include "Math/SHQuaternion.h" +#include "Math/Vector/SHVec4.h" +// Project Headers +#include "Utility/Convert.hxx" +#include "Math.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + + Quaternion::Quaternion(float _x, float _y, float _z, float _w) + : x { _x } + , y { _y } + , z { _z } + , w { _w } + {} + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + + void Quaternion::SetFromToRotation(Vector3 fromDirection, Vector3 toDirection) + { + const SHQuaternion R = SHQuaternion::FromToRotation(Convert::ToNative(fromDirection), Convert::ToNative(toDirection)); + *this = Convert::ToCLI(R); + } + + void Quaternion::SetLookRotation(Vector3 view, Vector3 up) + { + const SHQuaternion R = SHQuaternion::LookRotation(Convert::ToNative(view), Convert::ToNative(up)); + *this = Convert::ToCLI(R); + } + + void Quaternion::ToAngleAxis(float^% angle, Vector3^% axis) + { + const SHVec4 NATIVE_AXIS_ANGLE = Convert::ToNative(*this).GetAxisAngle(); + axis = Convert::ToCLI(NATIVE_AXIS_ANGLE.ToVec3()); + angle = NATIVE_AXIS_ANGLE.w; + } + + System::String^ Quaternion::ToString() + { + return ValueType::ToString(); + } + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + + bool Quaternion::Equals(Quaternion other) + { + const float DOT = Dot(*this, other); + return fabs(1.0f - DOT) <= Math::Epsilon; + } + + /*---------------------------------------------------------------------------------*/ + /* Object Overrides */ + /*---------------------------------------------------------------------------------*/ + + bool Quaternion::Equals(Object^ o) + { + return ValueType::Equals(o); + } + + int Quaternion::GetHashCode() + { + return ValueType::GetHashCode(); + } + + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + + float Quaternion::Angle(Quaternion a, Quaternion b) + { + return SHQuaternion::Angle(Convert::ToNative(a), Convert::ToNative(b)); + } + + Quaternion Quaternion::AngleAxis(float angle, Vector3 axis) + { + return Convert::ToCLI(SHQuaternion::FromAxisAngle(Convert::ToNative(axis), angle)); + } + + float Quaternion::Dot(Quaternion a, Quaternion b) + { + return (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); + } + + Quaternion Quaternion::Euler(float _x, float _y, float _z) + { + return Convert::ToCLI(SHQuaternion::FromPitchYawRoll(_x, _y, _z)); + } + + Quaternion Quaternion::FromToRotation(Vector3 fromDirection, Vector3 toDirection) + { + return Convert::ToCLI(SHQuaternion::FromToRotation(Convert::ToNative(fromDirection), Convert::ToNative(toDirection))); + } + + Quaternion Quaternion::Inverse(Quaternion rotation) + { + return Convert::ToCLI(SHQuaternion::Inverse(Convert::ToNative(rotation))); + } + + Quaternion Quaternion::Lerp(Quaternion a, Quaternion b, float t) + { + return Convert::ToCLI(SHQuaternion::ClampedLerp(Convert::ToNative(a), Convert::ToNative(b), t)); + } + + Quaternion Quaternion::LerpUnclamped(Quaternion a, Quaternion b, float t) + { + return Convert::ToCLI(SHQuaternion::Lerp(Convert::ToNative(a), Convert::ToNative(b), t)); + } + + Quaternion Quaternion::LookRotation(Vector3 forward, Vector3 upwards) + { + return Convert::ToCLI(SHQuaternion::LookRotation(Convert::ToNative(forward), Convert::ToNative(upwards))); + } + + Quaternion Quaternion::Normalize(Quaternion q) + { + return Convert::ToCLI(SHQuaternion::Normalise(Convert::ToNative(q))); + } + + Quaternion Quaternion::RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta) + { + return Convert::ToCLI(SHQuaternion::RotateTowards(Convert::ToNative(from), Convert::ToNative(to), Math::DegreesToRadians(maxDegreesDelta))); + } + + Quaternion Quaternion::Slerp(Quaternion a, Quaternion b, float t) + { + return Convert::ToCLI(SHQuaternion::ClampedSlerp(Convert::ToNative(a), Convert::ToNative(b), t)); + } + + Quaternion Quaternion::SlerpUnclamped(Quaternion a, Quaternion b, float t) + { + return Convert::ToCLI(SHQuaternion::Slerp(Convert::ToNative(a), Convert::ToNative(b), t)); + } + + + Quaternion Quaternion::operator*(Quaternion lhs, Quaternion rhs) + { + return Convert::ToCLI(Convert::ToNative(lhs) * Convert::ToNative(rhs)); + } + + bool Quaternion::operator==(Quaternion lhs, Quaternion rhs) + { + return lhs.Equals(rhs); + } +} \ No newline at end of file diff --git a/SHADE_Managed/src/Math/Quaternion.hxx b/SHADE_Managed/src/Math/Quaternion.hxx new file mode 100644 index 00000000..783038c9 --- /dev/null +++ b/SHADE_Managed/src/Math/Quaternion.hxx @@ -0,0 +1,237 @@ +/************************************************************************************//*! +\file Quaternion.hxx +\author Diren D Bharwani, diren.dbharwani, 390002520 +\par email: diren.dbharwani\@digipen.edu +\date Oct 23, 2022 +\brief Contains the definitions of Quaternion struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +#pragma once + +// Standard Libraries +#include +// Project Includes +#include "Vector3.hxx" + +namespace SHADE +{ + /// + /// CLR version of SHADE's Quaternion class that represents an orientation. + /// Designed to closely match Unity's Quaternion struct. + /// + [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential)] + public value struct Quaternion : public System::IEquatable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ +#pragma region Constants + + /// + /// Shorthand for writing Quaternion(0, 0, 0, 1). + /// + static initonly Quaternion Identity = Quaternion(0.0f, 0.0f, 0.0f, 1.0f); + +#pragma endregion + + /*-----------------------------------------------------------------------------*/ + /* Public Members */ + /*-----------------------------------------------------------------------------*/ + + /// + /// X-component of the Quaternion. + /// Don't modify this directly unless you know quaternions inside out. + /// + float x; + /// + /// Y-component of the Quaternion. + /// Don't modify this directly unless you know quaternions inside out. + /// + float y; + /// + /// Z-component of the Quaternion. + /// Don't modify this directly unless you know quaternions inside out. + /// + float z; + /// + /// W-component of the Quaternion. Do not directly modify quaternions. + /// + float w; + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Constructor to construct a Quaternion with the specified components. + /// + /// X-coordinate to set. + /// Y-coordinate to set. + /// Z-coordinate to set. + /// W-coordinate to set. + Quaternion(float _x, float _y, float _z, float _w); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Creates a rotation which rotates from fromDirection to toDirection.
+ /// Use this to create a rotation which starts at the first Vector (fromDirection) and rotates to the second Vector (toDirection). + /// These Vectors must be set up in a script. + ///
+ void SetFromToRotation(Vector3 fromDirection, Vector3 toDirection); + + /// + /// Creates a rotation with the specified forward and upwards directions.
+ /// The result is applied to this quaternion. + /// If used to orient a Transform, the Z axis will be aligned with forward and the Y axis with upwards, assuming these vectors are orthogonal. + /// Logs an error if the forward direction is zero. + ///
+ /// The direction to look in. + /// The vector that defines in which direction up is. + void SetLookRotation(Vector3 view, Vector3 up); + + /// + /// Converts a rotation to angle-axis representation (angles in degrees). + /// + void ToAngleAxis(float^% angle, Vector3^% axis); + + System::String^ ToString() override; + + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(Quaternion other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Returns the angle in degrees between two rotations a and b.
+ ///
+ /// The angle in degrees between the two vectors. + static float Angle(Quaternion a, Quaternion b); + + /// + /// Creates a rotation which rotates angle degrees around axis. + /// + static Quaternion AngleAxis(float angle, Vector3 axis); + + /// + /// The dot product between two rotations. + /// + static float Dot(Quaternion a, Quaternion b); + + /// + /// Returns a rotation that rotates y degrees around the y axis, x degrees around the x axis, and z degrees around the z axis; applied in that order. + /// + static Quaternion Euler(float _x, float _y, float _z); + + /// + /// Creates a rotation which rotates from fromDirection to toDirection. + /// + static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection); + + /// + /// Returns the Inverse of rotation. + /// + static Quaternion Inverse(Quaternion rotation); + + /// + /// Interpolates between a and b by t and normalizes the result afterwards. The parameter t is clamped to the range [0, 1]. + /// + /// Start value, returned when t = 0. + /// End value, returned when t = 1. + /// Interpolation ratio. + /// A quaternion interpolated between quaternions a and b. + static Quaternion Lerp(Quaternion a, Quaternion b, float t); + + /// + /// Interpolates between a and b by t and normalizes the result afterwards. The parameter t is not clamped. + /// + static Quaternion LerpUnclamped(Quaternion a, Quaternion b, float t); + + /// + /// Creates a rotation with the specified forward and upwards directions.
+ /// Z axis will be aligned with forward, X axis aligned with cross product between forward and upwards, and Y axis aligned with cross product between Z and X. + ///
+ static Quaternion LookRotation(Vector3 forward, Vector3 upwards); + + /// + /// Converts this quaternion to one with the same orientation but with a magnitude of 1. + /// + static Quaternion Normalize(Quaternion q); + + /// + /// Rotates a rotation from towards to.
+ /// The from quaternion is rotated towards to by an angular step of maxDegreesDelta (but note that the rotation will not overshoot). + /// Negative values of maxDegreesDelta will move away from to until the rotation is exactly the opposite direction. + ///
+ static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta); + + /// + /// Spherically interpolates between quaternions a and b by ratio t. The parameter t is clamped to the range [0, 1]. + /// + /// Start value, returned when t = 0. + /// End value, returned when t = 1. + /// Interpolation ratio. + /// A quaternion spherically interpolated between quaternions a and b. + static Quaternion Slerp(Quaternion a, Quaternion b, float t); + + /// + /// Spherically interpolates between a and b by t. The parameter t is not clamped. + /// + static Quaternion SlerpUnclamped(Quaternion a, Quaternion b, float t); + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + + /// + /// Combines rotations lhs and rhs. + /// + /// Left-hand side quaternion. + /// Right-hand side quaternion. + static Quaternion operator*(Quaternion lhs, Quaternion rhs); + + /// + /// Are two quaternions equal to each other? + /// + /// Left-hand side quaternion. + /// Right-hand side quaternion. + static bool operator==(Quaternion lhs, Quaternion rhs); + }; + +} // namespace SHADE diff --git a/SHADE_Managed/src/Math/Ray.cxx b/SHADE_Managed/src/Math/Ray.cxx new file mode 100644 index 00000000..ee614cbe --- /dev/null +++ b/SHADE_Managed/src/Math/Ray.cxx @@ -0,0 +1,28 @@ +/************************************************************************************//*! +\file Ray.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 20, 2022 +\brief Contains the definitions of functions of the Vector2 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Math/Ray.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Ray::Ray(Vector3 origin, Vector3 direction) + : Origin { origin } + , Direction{ direction } + {} +} \ No newline at end of file diff --git a/SHADE_Managed/src/Math/Ray.hxx b/SHADE_Managed/src/Math/Ray.hxx new file mode 100644 index 00000000..c50191f8 --- /dev/null +++ b/SHADE_Managed/src/Math/Ray.hxx @@ -0,0 +1,50 @@ +/************************************************************************************//*! +\file Ray.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 20, 2021 +\brief Contains the definitions of Vector2 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Vector3.hxx" + +namespace SHADE +{ + /// + /// CLR version of the the SHADE Engine's Ray class that represents a ray in + /// 3-Dimensional space. + /// + public value struct Ray + { + public: + /*-----------------------------------------------------------------------------*/ + /* Public Members */ + /*-----------------------------------------------------------------------------*/ + /// + /// The start point of the ray. + /// + Vector3 Origin; + /// + /// The direction that a ray travels in. + /// + Vector3 Direction; + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a ray starting at origin along direction. + /// + /// Source of the ray. + /// Direction the ray travels in. + Ray(Vector3 origin, Vector3 direction); + }; +} diff --git a/SHADE_Managed/src/Math/Vector2.cxx b/SHADE_Managed/src/Math/Vector2.cxx new file mode 100644 index 00000000..8242a11c --- /dev/null +++ b/SHADE_Managed/src/Math/Vector2.cxx @@ -0,0 +1,279 @@ +/************************************************************************************//*! +\file Vector2.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 2, 2021 +\brief Contains the definitions of functions of the Vector2 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Math/Vector2.hxx" +// Standard Libraries +#include +#include +// Project Headers +#include "Math.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Vector2::Vector2(float _x) + : Vector2 { _x, 0.0f } + {} + Vector2::Vector2(float _x, float _y) + : x { _x } + , y { _y } + {} + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void Vector2::Normalise() + { + *this = GetNormalised(); + } + + Vector2 Vector2::GetNormalised() + { + return *this / GetMagnitude(); + } + + float Vector2::GetMagnitude() + { + return sqrt(x * x + y * y); + } + + float Vector2::GetSqrMagnitude() + { + return x * x + y * y; + } + + float Vector2::AngleFromRightRadians() + { + return atan2(y, x); + } + + float Vector2::AngleFromRightDegrees() + { + return Math::RadiansToDegrees(AngleFromRightRadians()); + } + + bool Vector2::IsNearPoint(Vector2 point) + { + return IsNearPoint(point, Math::Epsilon); + } + + bool Vector2::IsNearPoint(Vector2 point, float tolerance) + { + return (*this - point).GetSqrMagnitude() < (tolerance * tolerance); + } + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + bool Vector2::Equals(Object^ o) + { + try + { + Vector2 vec = safe_cast(o); + return Equals(vec); + } + catch (System::InvalidCastException^) + { + return false; + } + } + + /*---------------------------------------------------------------------------------*/ + /* Object Overrides */ + /*---------------------------------------------------------------------------------*/ + bool Vector2::Equals(Vector2 other) + { + return IsNear(*this, other); + } + int Vector2::GetHashCode() + { + const int HASH = 19; + return x.GetHashCode() * HASH + y.GetHashCode(); + } + + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + bool Vector2::IsNear(Vector2 lhs, Vector2 rhs) + { + return IsNear(lhs, rhs, Math::Epsilon); + } + bool Vector2::IsNear(Vector2 lhs, Vector2 rhs, float tolerance) + { + return (std::abs(lhs.x) - std::abs(rhs.x)) < tolerance + && + (std::abs(lhs.y) - std::abs(rhs.y)) < tolerance; + } + float Vector2::Dot(Vector2 lhs, Vector2 rhs) + { + return lhs.x * rhs.x + lhs.y * rhs.y; + } + + Vector2 Vector2::Perpendicular(Vector2 lhs) + { + return Perpendicular(lhs, true); + } + + Vector2 Vector2::Perpendicular(Vector2 lhs, bool inward) + { + if (inward) + { + return Vector2 + ( + -lhs.y, lhs.x + ); + } + else + { + return Vector2 + ( + lhs.y, -lhs.x + ); + } + } + + Vector2 Vector2::Project(Vector2 vec, Vector2 direction) + { + return direction.GetNormalised() * vec.GetMagnitude(); + } + Vector2 Vector2::Reflect(Vector2 vec, Vector2 normal) + { + return vec - (Project(vec, normal.GetNormalised()) * 2.0f); + } + Vector2 Vector2::RotateRadians(Vector2 vec, float radians) + { + const float SINE = sin(radians); + const float COSINE = cos(radians); + + return Vector2 + ( + vec.x * COSINE - vec.y * SINE, + vec.x * SINE + vec.y * COSINE + ); + } + Vector2 Vector2::RotateDegrees(Vector2 vec, float degrees) + { + return RotateRadians(vec, Math::DegreesToRadians(degrees)); + } + Vector2 Vector2::Min(Vector2 lhs, Vector2 rhs) + { + float lx = lhs.x, rx = rhs.x; + float ly = lhs.y, ry = rhs.y; + + return Vector2(std::min(lx, rx), + std::min(ly, ry)); + } + Vector2 Vector2::Max(Vector2 lhs, Vector2 rhs) + { + float lx = lhs.x, rx = rhs.x; + float ly = lhs.y, ry = rhs.y; + + return Vector2(std::max(lx, rx), + std::max(ly, ry)); + } + Vector2 Vector2::Lerp(Vector2 a, Vector2 b, float t) + { + return LerpUnclamped(a, b, std::clamp(t, 0.0f, 1.0f)); + } + Vector2 Vector2::LerpUnclamped(Vector2 a, Vector2 b, float t) + { + return a + ((b - a) * t); + } + Vector2 Vector2::MoveTowards(Vector2 current, Vector2 target, float maxDistanceDelta) + { + // Ignore if it is exactly on the same point + if (current == target) + return target; + + // Calculate new position + Vector2 DELTA = (target - current).GetNormalised() * maxDistanceDelta; + Vector2 newPos = current + DELTA; + + // Check if check if is behind or ahead of target + Vector2 DIFF = target - newPos; + if (Dot(DELTA, DIFF) < 0.0f) + { + newPos = target; + } + return newPos; + } + Vector2 Vector2::operator+(Vector2 lhs, Vector2 rhs) + { + return Vector2 + ( + lhs.x + rhs.x, + lhs.y + rhs.y + ); + } + Vector2 Vector2::operator-(Vector2 lhs, Vector2 rhs) + { + return Vector2 + ( + lhs.x - rhs.x, + lhs.y - rhs.y + ); + } + Vector2 Vector2::operator*(Vector2 lhs, Vector2 rhs) + { + return Vector2 + ( + lhs.x * rhs.x, + lhs.y * rhs.y + ); + } + Vector2 Vector2::operator*(Vector2 lhs, double rhs) + { + return Vector2 + ( + lhs.x * static_cast(rhs), + lhs.y * static_cast(rhs) + ); + } + Vector2 Vector2::operator/(Vector2 lhs, double rhs) + { + return Vector2 + ( + lhs.x / static_cast(rhs), + lhs.y / static_cast(rhs) + ); + } + Vector2 Vector2::operator*(Vector2 lhs, float rhs) + { + return Vector2 + ( + lhs.x * rhs, + lhs.y * rhs + ); + } + Vector2 Vector2::operator/(Vector2 lhs, float rhs) + { + return Vector2 + ( + lhs.x / rhs, + lhs.y / rhs + ); + } + bool Vector2::operator==(Vector2 lhs, Vector2 rhs) + { + return lhs.Equals(rhs); + } + bool Vector2::operator!=(Vector2 lhs, Vector2 rhs) + { + return !(lhs == rhs); + } +} diff --git a/SHADE_Managed/src/Math/Vector2.hxx b/SHADE_Managed/src/Math/Vector2.hxx new file mode 100644 index 00000000..4877696b --- /dev/null +++ b/SHADE_Managed/src/Math/Vector2.hxx @@ -0,0 +1,412 @@ +/************************************************************************************//*! +\file Vector2.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 2, 2021 +\brief Contains the definitions of Vector2 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Standard Libraries +#include + +namespace SHADE +{ + /// + /// CLR version of SHADE Engine's Vector2 class that represents a 2-Dimensional Vector. + /// Designed to closely match Unity's Vector2 struct. + /// + [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential)] + public value struct Vector2 : public System::IEquatable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + #pragma region Constants + /// + /// Shorthand for writing Vector2(0, -1). + /// + static initonly Vector2 Down = Vector2(0.0f, -1.0f); + /// + /// Shorthand for writing Vector2(-1, 0). + /// + static initonly Vector2 Left = Vector2(-1.0f, 0.0f); + /// + /// Shorthand for writing Vector2(float.NegativeInfinity, + /// float.NegativeInfinity). + /// + static initonly Vector2 NegativeInfinity = Vector2(std::numeric_limits::lowest(), std::numeric_limits::lowest()); + /// + /// Shorthand for writing Vector2(1, 1). + /// + static initonly Vector2 One = Vector2(1.0f, 1.0f); + /// + /// Shorthand for writing Vector2(float.PositiveInfinity, + /// float.PositiveInfinity). + /// + static initonly Vector2 PositiveInfinity = Vector2(std::numeric_limits::max(), std::numeric_limits::max()); + /// + /// Shorthand for writing Vector2(1, 0). + /// + static initonly Vector2 Right = Vector2(1.0f, 0.0f); + /// + /// Shorthand for writing Vector2(0, 1). + /// + static initonly Vector2 Up = Vector2(0.0f, 1.0f); + /// + /// Shorthand for writing Vector2(0, 0). + /// + static initonly Vector2 Zero = Vector2(0.0f, 0.0f); + #pragma endregion + + /*-----------------------------------------------------------------------------*/ + /* Public Members */ + /*-----------------------------------------------------------------------------*/ + /// + /// X-component of the Vector2. + /// + float x; + /// + /// Y-component of the Vector2. + /// + float y; + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor to construct a Vector2 with the specified components with the + /// Y-component set to 0.0f. + /// + /// X-coordinate to set. + Vector2(float _x); + /// + /// Constructor to construct a Vector2 with the specified components.. + /// + /// X-coordinate to set. + /// Y-coordinate to set. + Vector2(float _x, float _y); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Normalises this current Vector2. This changes the data of this Vector2. + /// If you would like to get a copy, use GetNormalised() instead. + /// This function does nothing to a zero vector. + /// + void Normalise(); + /// + /// Creates a copy of this Vector2 and returns a normalized version. + /// + /// + /// Returns a normalised copy of this Vector2. + /// If this Vector2 is a zero vector, a zero vector will be returned. + /// + Vector2 GetNormalised(); + /// + /// Calculates and returns the magnitude of this Vector2. Note that this function + /// incurs a performance cost from the square root calculation. If you do not + /// need the precise magnitude, consider using GetSqrMagnitude() instead. + /// + /// Returns the length of this Vector2. + float GetMagnitude(); + /// + /// Calculates and returns the squared magnitude of this Vector2. + /// + /// Returns the squared length of this Vector2. + float GetSqrMagnitude(); + /// + /// Calculates and returns the angle of this vector from the right vector. This + /// function returns values between -Math.PI and Math.PI. + /// + /// Returns the angle of this vector from the right vector in radians. + float AngleFromRightRadians(); + /// + /// Calculates and returns the angle of this vector from the right vector. This + /// function returns values between -180.0f and 180.0f. + /// + /// Returns the angle of this vector from the right vector in degrees. + float AngleFromRightDegrees(); + /// + /// Checks if a specified point is near this Vector2 that represents a point with + /// a tolerance value of PLS_EPSILON. + /// + /// The other point to check if we are near. + /// + /// True if this Vector2 representing a point and the specified point are within + /// the range of the specified tolerance. False otherwise. + /// + bool IsNearPoint(Vector2 point); + /// + /// Checks if a specified point is near this Vector2 that represents a point. + /// + /// The other point to check if we are near. + /// + /// The amount of tolerance before we consider these points as "near". + /// + /// + /// True if this Vector2 representing a point and the specified point are within + /// the range of the specified tolerance. False otherwise. + /// + bool IsNearPoint(Vector2 point, float tolerance); + + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(Vector2 other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Checks if two specified Vector2s are near in value. + /// + /// Vector2 to check if is near in value. + /// Another Vector2 to check if is near in value. + /// + /// True if the two Vector2s are within the tolerance value specified + /// + static bool IsNear(Vector2 lhs, Vector2 rhs); + /// + /// Checks if two specified Vector2s are near in value. + /// + /// Vector2 to check if is near in value. + /// Another Vector2 to check if is near in value. + /// + /// Amount of tolerance to do the comparison with. + /// + /// + /// True if the two Vector2s are within the tolerance value specified + /// + static bool IsNear(Vector2 lhs, Vector2 rhs, float tolerance); + /// + /// Computes and returns the dot product of 2 specified Vector2s. + /// + /// Vector2 to calculate dot product with. + /// Another Vector2 to calculate dot product with. + /// + /// Scalar value representing the dot product of the two Vector2s. + /// + static float Dot(Vector2 lhs, Vector2 rhs); + /// + /// Computes the inward perpendicular Vector2 to the specified Vector2. + /// Equivalent to calling Perpendicular(lhs, true). This means, the + /// resultant Vector2 is rotated 90-degrees in a counter-clockwise. + /// + /// Vector2 to find a perpendicular of. + /// + /// The perpendicular Vector2 relative to the specified Vector2. + /// + static Vector2 Perpendicular(Vector2 lhs); + /// + /// Computes a perpendicular Vector2 to the specified Vector2. + /// + /// Vector2 to find a perpendicular of. + /// + /// Whether the inward perpendicular Vector is retrieved. If true, the + /// resultant vector is rotated 90-degrees in a counter-clockwise. + /// + /// The perpendicular Vector2 relative to the specified Vector2. + /// + static Vector2 Perpendicular(Vector2 lhs, bool inward); + /// + /// Computes and returns a Vector2 projection. + /// + /// Vector2 to project. + /// Vector2 to project onto. + /// The Vector2 that represents the projected vec onto direction. + static Vector2 Project(Vector2 vec, Vector2 direction); + /// + /// Reflects a Vector2 across another Vector2. + /// + /// A Vector2 to reflect. + /// A normal to reflect the Vector2 across. + /// The Vector2 that represents vec reflected across normal. + static Vector2 Reflect(Vector2 vec, Vector2 normal); + /// + /// Rotates a Vector2 on the Z-axis by a specified angle in an anti-clockwise + /// direction. + /// + /// A Vector2 to rotate. + /// + /// Angle to rotate the vector by in an anti-clockwise direction in radians. + /// + /// The Vector2 that represents the rotated vector. + static Vector2 RotateRadians(Vector2 vec, float radians); + /// + /// Rotates a Vector2 on the Z-axis by a specified angle in an anti-clockwise + /// direction. + /// + /// A Vector2 to rotate. + /// + /// Angle to rotate the vector by in an anti-clockwise direction in degrees. + /// + /// The Vector2 that represents the rotated vector. + static Vector2 RotateDegrees(Vector2 vec, float degrees); + /// + /// Computes and returns a Vector2 that is made from the smallest components of + /// the two specified Vector2s. + /// + /// Vector2 to calculate minimum Vector2 with. + /// Another Vector2 to calculate minimum Vector2 with. + /// + /// The Vector2 that contains the smallest components of the two specified + /// Vector2s. + /// + static Vector2 Min(Vector2 lhs, Vector2 rhs); + /// + /// Computes and returns a Vector2 that is made from the largest components of + /// the two specified Vector2s. + /// + /// Vector2 to calculate maximum Vector2 with. + /// Another Vector2 to calculate maximum Vector2 with. + /// + /// The Vector2 that contains the largest components of the two specified + /// Vector2s. + /// + static Vector2 Max(Vector2 lhs, Vector2 rhs); + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// + /// The start Vector2, returned when t = 0.0f. + /// The end Vector2, returned when t = 1.0f. + /// + /// Value used to interpolate between a and b which is clamped to + /// the range[0, 1]. + /// + /// The interpolated Vector2. + static Vector2 Lerp(Vector2 a, Vector2 b, float t); + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// Unlike Lerp(), t is not clamped to a range at all. + /// + /// The start Vector2, returned when t = 0.0f. + /// The end Vector2, returned when t = 1.0f. + /// Value used to interpolate between a and b. + /// The interpolated Vector2. + static Vector2 LerpUnclamped(Vector2 a, Vector2 b, float t); + /// + /// Moves a point current towards target. + /// Similar to Lerp(), however, the function will ensure that the distance never + /// exceeds maxDistanceDelta. Negative values of maxDistanceDelta pushes the + /// vector away from target + /// + /// The current position of the point. + /// The target position to move to. + /// Maximum distance moved per call. + /// Vector representing the moved point. + static Vector2 MoveTowards(Vector2 current, Vector2 target, float maxDistanceDelta); + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds two Vector2s together and returns the result. + /// + /// Vector2 to add. + /// Another Vector2 to add. + /// The result of lhs added to rhs + static Vector2 operator+(Vector2 lhs, Vector2 rhs); + /// + /// Subtracts a Vector2 from another Vector2 and returns the result. + /// + /// Vector2 to subtract from. + /// Another Vector2 to subtract. + /// The result of rhs subtracted from lhs. + static Vector2 operator-(Vector2 lhs, Vector2 rhs); + /// + /// Calculates the component-wise multiplication of two Vector2s and returns the + /// result. + /// + /// Vector2 to multiply with. + /// Another Vector2 to multiply with. + /// The result of rhs subtracted from lhs. + static Vector2 operator*(Vector2 lhs, Vector2 rhs); + /// + /// Calculates the multiplication of a Vector2 with a scalar value and returns + /// the result. + /// + /// Vector2 to multiply with. + /// Scalar to multiply with. + /// The result of the scalar multiplication. + static Vector2 operator*(Vector2 lhs, double rhs); + /// + /// Calculates the division of a Vector2 with a scalar value and returns + /// the result. + /// + /// Scalar to divide with. + /// Vector2 to divide with. + /// The result of the scalar division. + static Vector2 operator/(Vector2 lhs, double rhs); + /// + /// Calculates the multiplication of a Vector2 with a scalar value and returns + /// the result. + /// + /// Vector2 to multiply with. + /// Scalar to multiply with. + /// The result of the scalar multiplication. + static Vector2 operator*(Vector2 lhs, float rhs); + /// + /// Calculates the division of a Vector2 with a scalar value and returns + /// the result. + /// + /// Scalar to divide with. + /// Vector2 to divide with. + /// The result of the scalar division. + static Vector2 operator/(Vector2 lhs, float rhs); + /// + /// Checks if two Vector2s are approximately equal. This is equivalent to + /// calling Vector2.IsNear() with default tolerance values. + /// + /// Vector2 to compare. + /// Another Vector2 to compare. + /// + /// True if all components are approximately equal within the default + /// tolerance value. + /// + static bool operator==(Vector2 lhs, Vector2 rhs); + /// + /// Checks if two Vector2s are not approximately equal. This is equivalent to + /// calling !Vector2.IsNear() with default tolerance values. + /// + /// Vector2 to compare. + /// Another Vector2 to compare. + /// + /// True if all components are not approximately equal within the default + /// tolerance value. + /// + static bool operator!=(Vector2 lhs, Vector2 rhs); + }; +} diff --git a/SHADE_Managed/src/Math/Vector3.cxx b/SHADE_Managed/src/Math/Vector3.cxx new file mode 100644 index 00000000..f2286aa7 --- /dev/null +++ b/SHADE_Managed/src/Math/Vector3.cxx @@ -0,0 +1,297 @@ +/************************************************************************************//*! +\file Vector3.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 24, 2021 +\brief Contains the definitions of functions of the Vector3 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Vector3.hxx" +// Standard Libraries +#include +#include +// Project Headers +#include "Math.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Vector3::Vector3(float _x) + : Vector3 {_x, 0.0f, 0.0f} + {} + Vector3::Vector3(float _x, float _y) + : Vector3 {_x, _y, 0.0f} + {} + Vector3::Vector3(float _x, float _y, float _z) + : x { _x } + , y { _y } + , z { _z } + {} + Vector3::Vector3(Vector2 vec) + : Vector3(vec.x, vec.y) + {} + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void Vector3::Normalise() + { + *this = GetNormalised(); + } + + Vector3 Vector3::GetNormalised() + { + return *this / GetSqrMagnitude(); + } + + float Vector3::GetMagnitude() + { + return sqrt(x * x + y * y + z * z); + } + + float Vector3::GetSqrMagnitude() + { + return x * x + y * y + z * z; + } + + float Vector3::Angle2DFromRightRadians() + { + return atan2(y, x); + } + + float Vector3::Angle2DFromRightDegrees() + { + return Math::RadiansToDegrees(Angle2DFromRightRadians()); + } + + bool Vector3::IsNearPoint(Vector3 point) + { + return IsNearPoint(point, Math::Epsilon); + } + + bool Vector3::IsNearPoint(Vector3 point, float tolerance) + { + return (*this - point).GetSqrMagnitude() < (tolerance * tolerance); + } + + /*---------------------------------------------------------------------------------*/ + /* IEquatable */ + /*---------------------------------------------------------------------------------*/ + bool Vector3::Equals(Object^ o) + { + try + { + Vector3 vec = safe_cast(o); + return Equals(vec); + } + catch (System::InvalidCastException^) + { + return false; + } + } + + /*---------------------------------------------------------------------------------*/ + /* Object Overrides */ + /*---------------------------------------------------------------------------------*/ + bool Vector3::Equals(Vector3 other) + { + return IsNear(*this, other); + } + int Vector3::GetHashCode() + { + const int HASH = 19; + const int HASH2 = 23; + return x.GetHashCode() * HASH + y.GetHashCode() * HASH2 + z.GetHashCode(); + } + + /*---------------------------------------------------------------------------------*/ + /* Static Functions */ + /*---------------------------------------------------------------------------------*/ + bool Vector3::IsNear(Vector3 lhs, Vector3 rhs) + { + return IsNear(lhs, rhs, Math::Epsilon); + } + bool Vector3::IsNear(Vector3 lhs, Vector3 rhs, float tolerance) + { + return (std::abs(lhs.x) - std::abs(rhs.x)) < tolerance + && + (std::abs(lhs.y) - std::abs(rhs.y)) < tolerance + && + (std::abs(lhs.z) - std::abs(rhs.z)) < tolerance; + } + float Vector3::Dot(Vector3 lhs, Vector3 rhs) + { + return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; + } + Vector3 Vector3::Cross(Vector3 lhs, Vector3 rhs) + { + return Vector3(lhs.y * rhs.z - lhs.z * rhs.y, + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.x * rhs.y - lhs.y * rhs.x); + } + Vector3 Vector3::Project(Vector3 vec, Vector3 direction) + { + return direction.GetNormalised() * vec.GetMagnitude(); + } + Vector3 Vector3::Reflect(Vector3 vec, Vector3 normal) + { + return vec - (Project(vec, normal.GetNormalised()) * 2.0f); + } + Vector3 Vector3::RotateRadians(Vector3 vec, float radians) + { + const float SINE = sin(radians); + const float COSINE = cos(radians); + + return Vector3 + ( + vec.x * COSINE - vec.y * SINE, + vec.x * SINE + vec.y * COSINE, + vec.z + ); + } + Vector3 Vector3::RotateDegrees(Vector3 vec, float degrees) + { + return RotateRadians(vec, Math::DegreesToRadians(degrees)); + } + Vector3 Vector3::Min(Vector3 lhs, Vector3 rhs) + { + float lx = lhs.x, rx = rhs.x; + float ly = lhs.y, ry = rhs.y; + float lz = lhs.z, rz = rhs.z; + + return Vector3(std::min(lx, rx), + std::min(ly, ry), + std::min(lz, rz)); + } + Vector3 Vector3::Max(Vector3 lhs, Vector3 rhs) + { + float lx = lhs.x, rx = rhs.x; + float ly = lhs.y, ry = rhs.y; + float lz = lhs.z, rz = rhs.z; + + return Vector3(std::max(lx, rx), + std::max(ly, ry), + std::max(lz, rz)); + } + Vector3 Vector3::Lerp(Vector3 a, Vector3 b, float t) + { + return LerpUnclamped(a, b, std::clamp(t, 0.0f, 1.0f)); + } + Vector3 Vector3::LerpUnclamped(Vector3 a, Vector3 b, float t) + { + return a + ((b - a) * t); + } + Vector3 Vector3::MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta) + { + // Ignore if it is exactly on the same point + if (current == target) + return target; + + // Calculate new position + Vector3 DELTA = (target - current).GetNormalised() * maxDistanceDelta; + Vector3 newPos = current + DELTA; + + // Check if check if is behind or ahead of target + Vector3 DIFF = target - newPos; + if (Dot(DELTA, DIFF) < 0.0f) + { + newPos = target; + } + return newPos; + } + Vector3 Vector3::operator+(Vector3 lhs, Vector3 rhs) + { + return Vector3 + ( + lhs.x + rhs.x, + lhs.y + rhs.y, + lhs.z + rhs.z + ); + } + Vector3 Vector3::operator-(Vector3 lhs, Vector3 rhs) + { + return Vector3 + ( + lhs.x - rhs.x, + lhs.y - rhs.y, + lhs.z - rhs.z + ); + } + Vector3 Vector3::operator*(Vector3 lhs, Vector3 rhs) + { + return Vector3 + ( + lhs.x * rhs.x, + lhs.y * rhs.y, + lhs.z * rhs.z + ); + } + Vector3 Vector3::operator*(Vector3 lhs, double rhs) + { + return Vector3 + ( + lhs.x * static_cast(rhs), + lhs.y * static_cast(rhs), + lhs.z * static_cast(rhs) + ); + } + Vector3 Vector3::operator/(Vector3 lhs, double rhs) + { + return Vector3 + ( + lhs.x / static_cast(rhs), + lhs.y / static_cast(rhs), + lhs.z / static_cast(rhs) + ); + } + Vector3 Vector3::operator*(Vector3 lhs, float rhs) + { + return Vector3 + ( + lhs.x * rhs, + lhs.y * rhs, + lhs.z * rhs + ); + } + Vector3 Vector3::operator/(Vector3 lhs, float rhs) + { + return Vector3 + ( + lhs.x / rhs, + lhs.y / rhs, + lhs.z / rhs + ); + } + bool Vector3::operator==(Vector3 lhs, Vector3 rhs) + { + return lhs.Equals(rhs); + } + bool Vector3::operator!=(Vector3 lhs, Vector3 rhs) + { + return !(lhs == rhs); + } + + /*---------------------------------------------------------------------------------*/ + /* Conversion Operators */ + /*---------------------------------------------------------------------------------*/ + Vector3::operator Vector2(Vector3 vec) + { + return Vector2(vec.x, vec.y); + } + + Vector3::operator Vector3(Vector2 vec) + { + return Vector3(vec); + } +} diff --git a/SHADE_Managed/src/Math/Vector3.hxx b/SHADE_Managed/src/Math/Vector3.hxx new file mode 100644 index 00000000..189f2930 --- /dev/null +++ b/SHADE_Managed/src/Math/Vector3.hxx @@ -0,0 +1,443 @@ +/************************************************************************************//*! +\file Vector3.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 24, 2021 +\brief Contains the definitions of Vector3 struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +#pragma once + +// Standard Libraries +#include +// Project Includes +#include "Vector2.hxx" + +namespace SHADE +{ + /// + /// CLR version of SHADE Engine's Vector3 class that represents a 3-Dimensional Vector. + /// Designed to closely match Unity's Vector3 struct. + /// + [System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential)] + public value struct Vector3 : public System::IEquatable + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + #pragma region Constants + /// + /// Shorthand for writing Vector3(0, 0, -1). + /// + static initonly Vector3 Back = Vector3(0.0f, 0.0f, -1.0f); + /// + /// Shorthand for writing Vector3(0, -1, 0). + /// + static initonly Vector3 Down = Vector3(0.0f, -1.0f, 0.0f); + /// + /// Shorthand for writing Vector3(0, 0, 1). + /// + static initonly Vector3 Forward = Vector3(0.0f, 0.0f, 1.0f); + /// + /// Shorthand for writing Vector3(-1, 0, 0). + /// + static initonly Vector3 Left = Vector3(-1.0f, 0.0f, 0.0f); + /// + /// Shorthand for writing Vector3(float.NegativeInfinity, + /// float.NegativeInfinity, float.NegativeInfinity). + /// + static initonly Vector3 NegativeInfinity = Vector3(std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest()); + /// + /// Shorthand for writing Vector3(1, 1, 1). + /// + static initonly Vector3 One = Vector3(1.0f, 1.0f, 1.0f); + /// + /// Shorthand for writing Vector3(float.PositiveInfinity, + /// float.PositiveInfinity, float.PositiveInfinity). + /// + static initonly Vector3 PositiveInfinity = Vector3(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + /// + /// Shorthand for writing Vector3(1, 0, 0). + /// + static initonly Vector3 Right = Vector3(1.0f, 0.0f, 0.0f); + /// + /// Shorthand for writing Vector3(0, 1, 0). + /// + static initonly Vector3 Up = Vector3(0.0f, 1.0f, 0.0f); + /// + /// Shorthand for writing Vector3(0, 0, 0). + /// + static initonly Vector3 Zero = Vector3(0.0f, 0.0f, 0.0f); + #pragma endregion + + /*-----------------------------------------------------------------------------*/ + /* Public Members */ + /*-----------------------------------------------------------------------------*/ + /// + /// X-component of the Vector3. + /// + float x; + /// + /// Y-component of the Vector3. + /// + float y; + /// + /// Z-component of the Vector3. + /// + float z; + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor to construct a Vector3 with the specified components with the + /// Y and Z-component set to 0.0f. + /// + /// X-coordinate to set. + Vector3(float _x); + /// + /// Constructor to construct a Vector3 with the specified components with the + /// Z-component set to 0.0f. + /// + /// X-coordinate to set. + /// Y-coordinate to set. + Vector3(float _x, float _y); + /// + /// Constructor to construct a Vector3 with the specified components. + /// + /// X-coordinate to set. + /// Y-coordinate to set. + /// Z-coordinate to set. + Vector3(float _x, float _y, float _z); + /// + /// Conversion constructor to construct a Vector3 using a Vector2. + /// + /// + Vector3(Vector2 vec); + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Normalises this current Vector3. This changes the data of this Vector3. + /// If you would like to get a copy, use GetNormalised() instead. + /// This function does nothing to a zero vector. + /// + void Normalise(); + /// + /// Creates a copy of this Vector3 and returns a normalized version. + /// + /// + /// Returns a normalised copy of this Vector3. + /// If this Vector3 is a zero vector, a zero vector will be returned. + /// + Vector3 GetNormalised(); + /// + /// Calculates and returns the magnitude of this Vector3. Note that this function + /// incurs a performance cost from the square root calculation. If you do not + /// need the precise magnitude, consider using GetSqrMagnitude() instead. + /// + /// Returns the length of this Vector3. + float GetMagnitude(); + /// + /// Calculates and returns the squared magnitude of this Vector3. + /// + /// Returns the squared length of this Vector3. + float GetSqrMagnitude(); + /// + /// Calculates and returns the angle of this vector from the right vector. This + /// function returns values between -Math.PI and Math.PI. + /// + /// Returns the angle of this vector from the right vector in radians. + float Angle2DFromRightRadians(); + /// + /// Calculates and returns the angle of this vector from the right vector. This + /// function returns values between -180.0f and 180.0f. + /// + /// Returns the angle of this vector from the right vector in degrees. + float Angle2DFromRightDegrees(); + /// + /// Checks if a specified point is near this Vector3 that represents a point with + /// a tolerance value of PLS_EPSILON. + /// + /// The other point to check if we are near. + /// + /// True if this Vector3 representing a point and the specified point are within + /// the range of the specified tolerance. False otherwise. + /// + bool IsNearPoint(Vector3 point); + /// + /// Checks if a specified point is near this Vector3 that represents a point. + /// + /// The other point to check if we are near. + /// + /// The amount of tolerance before we consider these points as "near". + /// + /// + /// True if this Vector3 representing a point and the specified point are within + /// the range of the specified tolerance. False otherwise. + /// + bool IsNearPoint(Vector3 point, float tolerance); + + /*-----------------------------------------------------------------------------*/ + /* IEquatable */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with an object of the same type. + /// + /// The object to compare with. + /// True if both objects are the same. + virtual bool Equals(Vector3 other); + + /*-----------------------------------------------------------------------------*/ + /* Object */ + /*-----------------------------------------------------------------------------*/ + /// + /// Compares equality with another unboxed object. + /// + /// The unboxed object to compare with. + /// True if both objects are the same. + bool Equals(Object^ o) override; + /// + /// Gets a unique hash for this object. + /// + /// Unique hash for this object. + int GetHashCode() override; + + /*-----------------------------------------------------------------------------*/ + /* Static Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Checks if two specified Vector3s are near in value. + /// + /// Vector3 to check if is near in value. + /// Another Vector3 to check if is near in value. + /// + /// True if the two Vector3s are within the tolerance value specified + /// + static bool IsNear(Vector3 lhs, Vector3 rhs); + /// + /// Checks if two specified Vector3s are near in value. + /// + /// Vector3 to check if is near in value. + /// Another Vector3 to check if is near in value. + /// Amount of tolerance to do the comparison with. + /// + /// True if the two Vector3s are within the tolerance value specified + /// + static bool IsNear(Vector3 lhs, Vector3 rhs, float tolerance); + /// + /// Computes and returns the dot product of 2 specified Vector3s. + /// + /// Vector3 to calculate dot product with. + /// Another Vector3 to calculate dot product with. + /// Scalar value representing the dot product of the two Vector3s. + static float Dot(Vector3 lhs, Vector3 rhs); + /// + /// Computes and returns the cross product of 2 specified Vector3s. + /// + /// Vector3 to calculate cross product with. + /// Another Vector3 to calculate cross product with. + /// The cross product of the two Vector3s. + static Vector3 Cross(Vector3 lhs, Vector3 rhs); + /// + /// Computes and returns a Vector3 projection. + /// + /// Vector3 to project. + /// Vector3 to project onto. + /// The Vector3 that represents the projected vec onto direction. + static Vector3 Project(Vector3 vec, Vector3 direction); + /// + /// Reflects a Vector3 across another Vector3. + /// + /// A Vector3 to reflect. + /// A normal to reflect the Vector3 across. + /// The Vector3 that represents vec reflected across normal. + static Vector3 Reflect(Vector3 vec, Vector3 normal); + /// + /// Rotates a Vector3 on the Z-axis by a specified angle in an anti-clockwise + /// direction. + /// + /// A Vector3 to rotate. + /// + /// Angle to rotate the vector by in an anti-clockwise direction in radians. + /// + /// The Vector3 that represents the rotated vector. + static Vector3 RotateRadians(Vector3 vec, float radians); + /// + /// Rotates a Vector3 on the Z-axis by a specified angle in an anti-clockwise + /// direction. + /// + /// A Vector3 to rotate. + /// + /// Angle to rotate the vector by in an anti-clockwise direction in degrees. + /// + /// The Vector3 that represents the rotated vector. + static Vector3 RotateDegrees(Vector3 vec, float degrees); + /// + /// Computes and returns a Vector3 that is made from the smallest components of + /// the two specified Vector3s. + /// + /// Vector3 to calculate minimum Vector3 with. + /// Another Vector3 to calculate minimum Vector3 with. + /// + /// The Vector3 that contains the smallest components of the two specified + /// Vector3s. + /// + static Vector3 Min(Vector3 lhs, Vector3 rhs); + /// + /// Computes and returns a Vector3 that is made from the largest components of + /// the two specified Vector3s. + /// + /// Vector3 to calculate maximum Vector3 with. + /// Another Vector3 to calculate maximum Vector3 with. + /// + /// The Vector3 that contains the largest components of the two specified + /// Vector3s. + /// + static Vector3 Max(Vector3 lhs, Vector3 rhs); + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// + /// The start Vector3, returned when t = 0.0f. + /// The end Vector3, returned when t = 1.0f. + /// + /// Value used to interpolate between a and b which is clamped to + /// the range[0, 1]. + /// + /// The interpolated Vector3. + static Vector3 Lerp(Vector3 a, Vector3 b, float t); + /// + /// Linearly interpolates between two specified points. + /// This is most commonly used to find a point some fraction of the way along a + /// line between two endpoints. + /// Unlike Lerp(), t is not clamped to a range at all. + /// + /// The start Vector3, returned when t = 0.0f. + /// The end Vector3, returned when t = 1.0f. + /// Value used to interpolate between a and b. + /// The interpolated Vector3. + static Vector3 LerpUnclamped(Vector3 a, Vector3 b, float t); + /// + /// Moves a point current towards target. + /// Similar to Lerp(), however, the function will ensure that the distance never + /// exceeds maxDistanceDelta. Negative values of maxDistanceDelta pushes the + /// vector away from target + /// + /// The current position of the point. + /// The target position to move to. + /// Maximum distance moved per call. + /// Vector representing the moved point. + static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta); + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds two Vector3s together and returns the result. + /// + /// Vector3 to add. + /// Another Vector3 to add. + /// The result of lhs added to rhs + static Vector3 operator+(Vector3 lhs, Vector3 rhs); + /// + /// Subtracts a Vector3 from another Vector3 and returns the result. + /// + /// Vector3 to subtract from. + /// Another Vector3 to subtract. + /// The result of rhs subtracted from lhs. + static Vector3 operator-(Vector3 lhs, Vector3 rhs); + /// + /// Calculates the component-wise multiplication of two Vector3s and returns the + /// result. + /// + /// Vector3 to multiply with. + /// Another Vector3 to multiply with. + /// The result of rhs subtracted from lhs. + static Vector3 operator*(Vector3 lhs, Vector3 rhs); + /// + /// Calculates the multiplication of a Vector3 with a scalar value and returns + /// the result. + /// + /// Vector3 to multiply with. + /// Scalar to multiply with. + /// The result of the scalar multiplication. + static Vector3 operator*(Vector3 lhs, double rhs); + /// + /// Calculates the division of a Vector3 with a scalar value and returns + /// the result. + /// + /// Scalar to divide with. + /// Vector3 to divide with. + /// The result of the scalar division. + static Vector3 operator/(Vector3 lhs, double rhs); + /// + /// Calculates the multiplication of a Vector3 with a scalar value and returns + /// the result. + /// + /// Vector3 to multiply with. + /// Scalar to multiply with. + /// The result of the scalar multiplication. + static Vector3 operator*(Vector3 lhs, float rhs); + /// + /// Calculates the division of a Vector3 with a scalar value and returns + /// the result. + /// + /// Scalar to divide with. + /// Vector3 to divide with. + /// The result of the scalar division. + static Vector3 operator/(Vector3 lhs, float rhs); + /// + /// Checks if two Vector3s are approximately equal. This is equivalent to + /// calling Vector3.IsNear() with default tolerance values. + /// + /// Vector3 to compare. + /// Another Vector3 to compare. + /// + /// True if all components are approximately equal within the default + /// tolerance value. + /// + static bool operator==(Vector3 lhs, Vector3 rhs); + /// + /// Checks if two Vector3s are not approximately equal. This is equivalent to + /// calling !Vector3.IsNear() with default tolerance values. + /// + /// Vector3 to compare. + /// Another Vector3 to compare. + /// + /// True if all components are not approximately equal within the default + /// tolerance value. + /// + static bool operator!=(Vector3 lhs, Vector3 rhs); + + /*-----------------------------------------------------------------------------*/ + /* Conversion Operators */ + /*-----------------------------------------------------------------------------*/ + /// + /// Explicit conversion operator to enable explicit casting from a Vector3 to a + /// Vector2. + /// + /// Vector3 to convert from. + static explicit operator Vector2(Vector3 vec); + /// + /// Explicit conversion operator to enable explicit casting from a Vector2 to a + /// Vector3. + /// + /// Vector2 to convert from. + static explicit operator Vector3(Vector2 vec); + }; +} + diff --git a/SHADE_Managed/src/Physics/CollisionInfo.cxx b/SHADE_Managed/src/Physics/CollisionInfo.cxx new file mode 100644 index 00000000..135760db --- /dev/null +++ b/SHADE_Managed/src/Physics/CollisionInfo.cxx @@ -0,0 +1,36 @@ +/************************************************************************************//*! +\file CollisionInfo.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definition of the functions of the managed CollisionInfo + struct. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHpch.h" +#include "CollisionInfo.hxx" +#include "Components/RigidBody.hxx" +#include "Components/Collider.hxx" + +namespace SHADE +{ + Collider^ CollisionInfo::Collider::get() + { + return GameObject.GetComponent(); + } + + CollisionShape^ CollisionInfo::CollisionShape::get() + { + throw gcnew System::NotImplementedException(); + } + + RigidBody^ CollisionInfo::RigidBody::get() + { + return GameObject.GetComponent(); + } +} diff --git a/SHADE_Managed/src/Physics/CollisionInfo.hxx b/SHADE_Managed/src/Physics/CollisionInfo.hxx new file mode 100644 index 00000000..40cb9ccc --- /dev/null +++ b/SHADE_Managed/src/Physics/CollisionInfo.hxx @@ -0,0 +1,66 @@ +/************************************************************************************//*! +\file CollisionInfo.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 31, 2022 +\brief Contains the definition of the managed CollisionInfo struct with the + definition of its properties and declaration of functions. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once +// Project Includes +#include "Engine/GameObject.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + ref class RigidBody; + ref class Collider; + ref class CollisionShape; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Struct that describes a collision + /// + public value struct CollisionInfo + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// The GameObject whose collider you are colliding with. + /// + property GameObject GameObject; + /// + /// The Collider that you are colliding with. + /// + property Collider^ Collider + { + SHADE::Collider^ get(); + } + /// + /// The CollisionShape of the Collider that you are colliding with. + /// + property CollisionShape^ CollisionShape + { + SHADE::CollisionShape^ get(); + } + /// + /// The RigidBody that you are colliding with. + /// + property RigidBody^ RigidBody + { + SHADE::RigidBody^ get(); + } + }; +} diff --git a/SHADE_Managed/src/SHpch.cpp b/SHADE_Managed/src/SHpch.cpp new file mode 100644 index 00000000..2a36c693 --- /dev/null +++ b/SHADE_Managed/src/SHpch.cpp @@ -0,0 +1,10 @@ +/**************************************************************************************** + * \file SHpch.h + * \brief Empty source file for generating SHADE Engine's precompiled header. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include "SHpch.h" \ No newline at end of file diff --git a/SHADE_Managed/src/SHpch.h b/SHADE_Managed/src/SHpch.h new file mode 100644 index 00000000..b54a8a5b --- /dev/null +++ b/SHADE_Managed/src/SHpch.h @@ -0,0 +1,31 @@ +/**************************************************************************************** + * \file SHpch.h + * \brief Precompiled header file for SHADE Engine. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +// C RunTime Header Files +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/SHADE_Managed/src/Scripts/Script.cxx b/SHADE_Managed/src/Scripts/Script.cxx new file mode 100644 index 00000000..a2af38a3 --- /dev/null +++ b/SHADE_Managed/src/Scripts/Script.cxx @@ -0,0 +1,250 @@ +/************************************************************************************//*! +\file Script.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the Script managed class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Script.hxx" +// Project Headers +#include "Utility/Debug.hxx" +#include "ScriptStore.hxx" +#include "Engine/ECS.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Component Access Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T Script::AddComponent() + { + return owner.AddComponent(); + } + generic + T Script::GetComponent() + { + return owner.GetComponent(); + } + + generic + T Script::GetComponentInChildren() + { + return owner.GetComponentInChildren(); + } + + generic + System::Collections::Generic::IEnumerable^ Script::GetComponentsInChildren() + { + return owner.GetComponentsInChildren(); + } + + generic + T Script::EnsureComponent() + { + return owner.EnsureComponent(); + } + generic + void Script::RemoveComponent() + { + owner.RemoveComponent(); + } + + /*---------------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T Script::AddScript() + { + return ScriptStore::AddScript(owner.GetEntity()); + } + generic + T Script::GetScript() + { + return ScriptStore::GetScript(owner.GetEntity()); + } + + generic + T Script::GetScriptInChildren() + { + return ScriptStore::GetScriptInChildren(owner.GetEntity()); + } + generic + System::Collections::Generic::IEnumerable^ Script::GetScriptsInChildren() + { + return ScriptStore::GetScriptsInChildren(owner.GetEntity()); + } + + generic + System::Collections::Generic::IEnumerable^ Script::GetScripts() + { + return ScriptStore::GetScripts(owner.GetEntity()); + } + + generic + void Script::RemoveScript() + { + ScriptStore::RemoveScript(owner.GetEntity()); + } + + Script::operator bool(Script^ s) + { + return s != nullptr; + } + + /*---------------------------------------------------------------------------------*/ + /* "All-time" Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void Script::Initialize(GameObject newOwner) + { + owner = newOwner; + } + + void Script::OnAttached() + { + SAFE_NATIVE_CALL_BEGIN + onAttached(); + SAFE_NATIVE_CALL_END(this) + } + void Script::OnDetached() + { + SAFE_NATIVE_CALL_BEGIN + onDetatched(); + SAFE_NATIVE_CALL_END(this) + } + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void Script::Awake() + { + SAFE_NATIVE_CALL_BEGIN + awake(); + SAFE_NATIVE_CALL_END(this) + } + void Script::Start() + { + SAFE_NATIVE_CALL_BEGIN + start(); + SAFE_NATIVE_CALL_END(this) + } + void Script::FixedUpdate() + { + SAFE_NATIVE_CALL_BEGIN + fixedUpdate(); + SAFE_NATIVE_CALL_END(this) + } + void Script::Update() + { + SAFE_NATIVE_CALL_BEGIN + update(); + SAFE_NATIVE_CALL_END(this) + } + void Script::LateUpdate() + { + SAFE_NATIVE_CALL_BEGIN + lateUpdate(); + SAFE_NATIVE_CALL_END(this) + } + void Script::OnDrawGizmos() + { + SAFE_NATIVE_CALL_BEGIN + OnGizmosDrawOverriden = true; + onDrawGizmos(); + SAFE_NATIVE_CALL_END(this) + } + void Script::OnDestroy() + { + SAFE_NATIVE_CALL_BEGIN + onDestroy(); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnCollisionEnter(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onCollisionEnter(collision); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnCollisionStay(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onCollisionStay(collision); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnCollisionExit(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onCollisionExit(collision); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnTriggerEnter(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onTriggerEnter(collision); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnTriggerStay(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onTriggerStay(collision); + SAFE_NATIVE_CALL_END(this) + } + + void Script::OnTriggerExit(CollisionInfo collision) + { + SAFE_NATIVE_CALL_BEGIN + onTriggerExit(collision); + SAFE_NATIVE_CALL_END(this) + } + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + Script::Script() + : OnGizmosDrawOverriden { false } + {} + + /*---------------------------------------------------------------------------------*/ + /* Virtual "All-Time" Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void Script::onAttached() {} + void Script::onDetatched() {} + + /*---------------------------------------------------------------------------------*/ + /* Virtual Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void Script::awake() {} + void Script::start() {} + void Script::fixedUpdate() {} + void Script::update() {} + void Script::lateUpdate() {} + void Script::onDrawGizmos() + { + OnGizmosDrawOverriden = false; + } + void Script::onDestroy() {} + + /*---------------------------------------------------------------------------------*/ + /* Virtual Event Functions */ + /*---------------------------------------------------------------------------------*/ + void Script::onTriggerEnter(CollisionInfo) {} + void Script::onTriggerStay(CollisionInfo) {} + void Script::onTriggerExit(CollisionInfo) {} + void Script::onCollisionEnter(CollisionInfo) {} + void Script::onCollisionStay(CollisionInfo) {} + void Script::onCollisionExit(CollisionInfo) {} +} diff --git a/SHADE_Managed/src/Scripts/Script.hxx b/SHADE_Managed/src/Scripts/Script.hxx new file mode 100644 index 00000000..46736245 --- /dev/null +++ b/SHADE_Managed/src/Scripts/Script.hxx @@ -0,0 +1,409 @@ +/************************************************************************************//*! +\file Script.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the Script class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Engine/GameObject.hxx" +#include "Physics/CollisionInfo.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + ref class BaseComponent; + + /*---------------------------------------------------------------------------------*/ + /* Class Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Class that forms the basis of all "script"-objects that can be attached to + /// Entities to update each Entity's Components via C# code. + /// + public ref class Script + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// GameObject that this Script belongs to. + /// + property GameObject Owner + { + GameObject get() { return owner; } + } + + /*-----------------------------------------------------------------------------*/ + /* Component Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Component to the GameObject that this Script belongs to. + /// + /// + /// Type of the Component to add. Must be derived from BaseComponent. + /// + /// Reference to the Component that was added. + generic where T : BaseComponent + T AddComponent(); + /// + /// Gets a Component from the GameObject that this Script belongs to. + /// + /// + /// Type of the Component to get. Must be derived from BaseComponent. + /// + /// Reference to the Component that was retrieved. + generic where T : BaseComponent + T GetComponent(); + /// + /// Retrieves the first Component from this GameObject's children that matches + /// the specified type. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// + /// Type of the Component to get. Must be derived from BaseComponent. + /// + /// Reference to the Component that was retrieved. + generic where T : BaseComponent + T GetComponentInChildren(); + /// + /// Retrieves a list of Components from this GameObject's children that + /// matches the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetComponentInChildren() instead. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of the Component to get. + /// + /// Newly allocated List of components. Will be null if no components are found. + /// + generic where T : BaseComponent + System::Collections::Generic::IEnumerable^ GetComponentsInChildren(); + /// + /// Ensures a Component on the GameObject that this Script belongs to. + /// + /// + /// Type of the Component to ensure. Must be derived from BaseComponent. + /// + /// Reference to the Component. + generic where T : BaseComponent + T EnsureComponent(); + /// + /// Removes a Component from the GameObject that this Script belongs to. + /// + /// + /// Type of the Component to remove. Must be derived from BaseComponent. + /// + generic where T : BaseComponent + void RemoveComponent(); + + /*-----------------------------------------------------------------------------*/ + /* Script Access Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script to this GameObject. + /// + /// + /// Type of script to add. + /// This needs to be a default constructable Script. + /// + /// Reference to the script added + generic where T : ref class, Script + T AddScript(); + /// + /// Retrieves the first Script from this GameObject that matches the specified + /// type. + /// + /// + /// Type of script to get. + /// This needs to be a default constructable Script. + /// + /// Reference to the script added + generic where T : ref class, Script + T GetScript(); + /// + /// Retrieves the first Script from this GameObject's children that matches the + /// specified type. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// + /// Type of script to get. + /// This needs to be a default constructable Script. + /// + /// Reference to the script added + generic where T : ref class, Script + T GetScriptInChildren(); + /// + /// Retrieves a list of Scripts from this GameObject's children that matches + /// the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetComponentInChildren() instead. + /// Unlike Unity, we do not search this GameObject, only the children. + /// + /// Type of the Component to get. + /// + /// Newly allocated List of components. Will be null if no components are found. + /// + generic where T : ref class, Script + System::Collections::Generic::IEnumerable^ GetScriptsInChildren(); + /// + /// Retrieves a immutable list of scripts from the specified Entity that + /// matches the specified type. + ///
+ /// Note that this function allocates. It should be used sparingly. + ///
+ /// + /// Type of scripts to get. + /// This needs to be a default constructable Script. + /// + /// + /// Immutable list of references to scripts of the specified type. + /// + generic where T : ref class, Script + System::Collections::Generic::IEnumerable^ GetScripts(); + /// + /// Removes all Scripts of the specified type from this GameObject. + /// + /// + /// Type of script to remove. + /// This needs to be a default constructable Script. + /// + generic where T : ref class, Script + void RemoveScript(); + + /*-----------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------*/ + /// + /// Implicit conversion operator to enable checking if a component is null. + /// + /// Component to check. + static operator bool(Script^ s); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Fields */ + /*-----------------------------------------------------------------------------*/ + /// + /// If true, the OnGizmosDraw function was overridden. + /// + bool OnGizmosDrawOverriden; + + /*-----------------------------------------------------------------------------*/ + /* "All-Time" Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Used to initialize a Script with a GameObject. + /// + void Initialize(GameObject newOwner); + /// + /// Used to call onAttached(). This is called immediately when this script is + /// attached to a GameObject. + /// + void OnAttached(); + /// + /// Used to call onDetached(). This is called immediately when this script is + /// detached from a GameObject. + /// + void OnDetached(); + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Used to call awake(). This should be called on the first frame that the + /// attached GameObject is active if they are a part of the scene. + /// + void Awake(); + /// + /// Used to call start(). This should be called on the first frame that the + /// attached GameObject is active but always after Awake(). + /// + void Start(); + /// + /// Used to call fixedUpdate(). This should be called in sync with Physics + /// update steps and thus in most cases will execute more than Update() will. + /// This will be called immediately before a Physics update step. + /// + void FixedUpdate(); + /// + /// Used to call update(). This should be called every frame before physics and + /// collision resolution. + /// + void Update(); + /// + /// Used to call lateUpdate(). This should be called every frame after physics + /// and collision resolution but before rendering. + /// + void LateUpdate(); + /// + /// Used to call onDrawGizmos(). This should be called just before rendering + /// the scene. This will only be called when working in the editor. + /// + void OnDrawGizmos(); + /// + /// Used to call onDestroy(). This should be called at the end of the frame + /// where the attached GameObject or this script is destroyed directly or + /// indirectly due to destruction of the owner. + /// + void OnDestroy(); + + /*-----------------------------------------------------------------------------*/ + /* Event Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Used to call onCollisionEnter(). This should be called when a collision is + /// detected between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnCollisionEnter(CollisionInfo collision); + /// + /// Used to call onCollisionStay(). This should be called when a collision is + /// persistent between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnCollisionStay(CollisionInfo collision); + /// + /// Used to call onCollisionExit(). This should be called when a collision ends + /// between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnCollisionExit(CollisionInfo collision); + /// + /// Used to call onTriggerEnter(). This should be called when a trigger-type + /// collision is detected between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnTriggerEnter(CollisionInfo collision); + /// + /// Used to call onTriggerStay(). This should be called when a trigger-type + /// collision is detected between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnTriggerStay(CollisionInfo collision); + /// + /// Used to call onTriggerExit(). This should be called when a trigger-type + /// collision is detected between the attached GameObject and another GameObject. + /// + /// Information on the collision event. + void OnTriggerExit(CollisionInfo collision); + + protected: + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Default Constructor + /// + Script(); + + /*-----------------------------------------------------------------------------*/ + /* Virtual "All-Time" Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Called immediately once this script is attached to a GameObject. + /// + virtual void onAttached(); + /// + /// Called immediately once this script is detached from a GameObject. + /// + virtual void onDetatched(); + + /*-----------------------------------------------------------------------------*/ + /* Virtual Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Called on the first frame that the attached GameObject is active if they are + /// a part of the scene. + /// + virtual void awake(); + /// + /// Called on the first frame that the attached GameObject is active but always + /// after Awake(). + /// + virtual void start(); + /// + /// Called every frame in sync with Physics update steps and thus in most cases + /// will execute more than update() will. This will be called immediately before + /// a Physics update step. + /// + virtual void fixedUpdate(); + /// + /// Called every frame before physics and collision resolution. + /// + virtual void update(); + /// + /// Called every frame after physics and collision resolution but before + /// rendering. + /// + virtual void lateUpdate(); + /// + /// Called every frame just before rendering but only if working in the editor. + /// + virtual void onDrawGizmos(); + /// + /// Called just before the end of the frame where the attached GameObject or + /// this script is destroyed directly or indirectly due to destruction of the + /// owner. + /// + virtual void onDestroy(); + + /*-----------------------------------------------------------------------------*/ + /* Virtual Event Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Called when the attached GameObject has a trigger Collider and collides with + /// another GameObject with a Collider in the first frame of collision. + /// + /// Information on the collision event. + virtual void onTriggerEnter(CollisionInfo info); + /// + /// Called when the attached GameObject has a trigger Collider and collides with + /// another GameObject with a Collider in subsequent frames of collision. + /// + /// Information on the collision event. + virtual void onTriggerStay(CollisionInfo info); + /// + /// Called when the attached GameObject has a trigger Collider and leaves a + /// collision with another GameObject with a Collider2D. + /// + /// Information on the collision event. + virtual void onTriggerExit(CollisionInfo info); + /// + /// Called when the attached GameObject has a Collider and collides with + /// another GameObject with a Collider in the first frame of collision. + /// + /// Information on the collision event. + virtual void onCollisionEnter(CollisionInfo info); + /// + /// Called when the attached GameObject has a Collider and collides with + /// another GameObject with a Collider in subsequent frames of collision. + /// + /// Information on the collision event. + virtual void onCollisionStay(CollisionInfo info); + /// + /// Called when the attached GameObject has a Collider and leaves a + /// collision with another GameObject with a Collider2D. + /// + /// Information on the collision event. + virtual void onCollisionExit(CollisionInfo info); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + GameObject owner; + }; + +} diff --git a/SHADE_Managed/src/Scripts/ScriptStore.cxx b/SHADE_Managed/src/Scripts/ScriptStore.cxx new file mode 100644 index 00000000..2b1540b6 --- /dev/null +++ b/SHADE_Managed/src/Scripts/ScriptStore.cxx @@ -0,0 +1,880 @@ +/************************************************************************************//*! +\file ScriptStore.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the ScriptStore managed + static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "ScriptStore.hxx" +// Standard Libraries +#include +// External Dependencies +#include "ECS_Base/Managers/SHEntityManager.h" +#include "Tools/SHLog.h" +// Project Headers +#include "Utility/Debug.hxx" +#include "Utility/Convert.hxx" +#include "Script.hxx" +#include "Engine/Entity.hxx" +#include "Serialisation/SerialisationUtilities.hxx" +#include "Engine/Application.hxx" +#include "Physics/System/SHPhysicsSystemInterface.h" +#include "Physics/SHPhysicsEvents.h" +#include "Physics/Collision/SHCollisionInfo.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Scripts Manipulation Functions */ + /*---------------------------------------------------------------------------------*/ + generic + T ScriptStore::AddScript(Entity entity) + { + // Create the script and add it in + Script^ script = safe_cast(System::Activator::CreateInstance(T::typeid)); + + return safe_cast(AddScript(entity, script)); + } + + Script^ ScriptStore::AddScript(Entity entity, Script^ script) + { + return AddScript(entity, script, System::Int32::MaxValue); + } + + Script^ ScriptStore::AddScript(Entity entity, Script^ script, int index) + { + // Check if entity exists + if (!EntityUtils::IsValid(entity)) + throw gcnew System::ArgumentException("Invalid Entity provided to add a Script to."); + + System::Collections::Generic::List ^ entityScriptList; + + // Check if storage for scripts of this entity exists + if (!scripts.ContainsKey(entity)) + { + // Create a new list for this set of scripts + entityScriptList = gcnew System::Collections::Generic::List(); + scripts.Add(entity, entityScriptList); + } + else + { + entityScriptList = scripts[entity]; + } + + // Add the script in + script->Initialize(GameObject(entity)); + entityScriptList->Insert(System::Math::Clamp(index, 0, entityScriptList->Count), script); + if (Application::IsPlaying) + { + script->Awake(); + script->Start(); + } + else + { + awakeList.Add(script); + startList.Add(script); + } + script->OnAttached(); + + return script; + } + + bool ScriptStore::AddScriptViaName(Entity entity, System::String^ scriptName) + { + SAFE_NATIVE_CALL_BEGIN + Script^ script; + return AddScriptViaNameWithRef(entity, scriptName, script); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + return false; + } + + bool ScriptStore::AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, Script^% createdScript) + { + // Check if we are set up to get scripts + if (addScriptMethod == nullptr) + { + Debug::LogError("[ScriptStore] Native AddScript() was not loaded. Unable to add scripts."); + return false; + } + + // Get the script if it exists + System::Type^ scriptType = getScriptType(scriptName); + if (scriptType == nullptr) + { + std::ostringstream oss; + oss << "[ScriptStore] No Script named " + << Convert::ToNative(scriptName) + << " found!"; + Debug::LogError(oss.str()); + return false; + } + + // Otherwise, add the script + System::Reflection::MethodInfo^ method = addScriptMethod->MakeGenericMethod(scriptType); + try + { + array^ params = gcnew array{entity}; + createdScript = safe_cast(method->Invoke(nullptr, params)); + } + catch (System::Exception^ e) + { + std::ostringstream oss; + oss << "[ScriptStore] Failed to add Script named \"" << Convert::ToNative(scriptName) + << "\" to Entity #" << entity << "! (" << Convert::ToNative(e->GetType()->Name) << ")"; + oss << Convert::ToNative(e->ToString()); + Debug::LogError(oss.str()); + return false; + } + return true; + } + + generic + T ScriptStore::GetScript(Entity entity) + { + // Check if entity exists + if (!EntityUtils::IsValid(entity)) + throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from."); + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + { + return T(); + } + + // Search for and obtain + for each (Script^ script in scripts[entity]) + { + try + { + T actualScript = safe_cast(script); + return actualScript; + } + catch (System::InvalidCastException^) + { + continue; + } + } + + return T(); + } + + generic + T ScriptStore::GetScriptInChildren(Entity entity) + { + // Check if entity exists and is a valid GameObject + if (!EntityUtils::IsValid(entity)) + throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from."); + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + { + return T(); + } + + // Get Entity's SceneGraphNode + SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity); + if (sceneGraphNode == nullptr) + { + std::ostringstream oss; + oss << "[ECS_CLI] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + return T(); + } + + // Search direct children first + for (const auto& child : sceneGraphNode->GetChildren()) + { + T script = GetScript(child->GetEntityID()); + if (script != nullptr) + return script; + } + + // Search their children + for (const auto& child : sceneGraphNode->GetChildren()) + { + T script = GetScript(child->GetEntityID()); + if (script != nullptr) + return script; + } + + // None here + return T(); + } + + generic + System::Collections::Generic::IEnumerable^ ScriptStore::GetScriptsInChildren(Entity entity) + { + System::Type^ componentType = T::typeid; + + // Check if entity is correct + if (!SHEntityManager::IsValidEID(entity)) + { + std::ostringstream oss; + oss << "[ScriptStore] Attempted to retrieve Script \"" + << Convert::ToNative(componentType->Name) + << "\" from invalid Entity."; + Debug::LogError(oss.str()); + return nullptr; + } + + // Search all elements via a iterative breadth first search + System::Collections::Generic::List^ results; + System::Collections::Generic::Queue^ searchSpace = gcnew System::Collections::Generic::Queue(); + // Start off with direct children + SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity); + if (entityNode == nullptr) + { + std::ostringstream oss; + oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + } + for (const auto& child : entityNode->GetChildren()) + { + searchSpace->Enqueue(child->GetEntityID()); + } + // Continue with all subsequent children + while (searchSpace->Count > 0) + { + // Check if this entity has the component we need + Entity curr = searchSpace->Dequeue(); + T script = GetScript(curr); + if (script != nullptr) + { + // We only construct if we need to + if (results == nullptr) + results = gcnew System::Collections::Generic::List(); + results->Add(script); + } + + // Add children to the queue + SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(curr); + if (sceneGraphNode == nullptr) + { + std::ostringstream oss; + oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!"; + SHLog::Warning(oss.str()); + continue; + } + for (const auto& child : sceneGraphNode->GetChildren()) + { + searchSpace->Enqueue(child->GetEntityID()); + } + } + + // None here + return results; + } + + generic + System::Collections::Generic::IEnumerable^ ScriptStore::GetScripts(Entity entity) + { + // Check if entity exists and is a valid GameObject + if (!EntityUtils::IsValid(entity)) + throw gcnew System::ArgumentException("Invalid Entity provided to get a Script from."); + + // Create a list to store entries + System::Collections::Generic::List^ foundScripts = gcnew System::Collections::Generic::List(); + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + { + return foundScripts; + } + + // Search for and obtain + for each (Script^ script in scripts[entity]) + { + try + { + T actualScript = safe_cast(script); + foundScripts->Add(actualScript); + } + catch (System::InvalidCastException^) + { + continue; + } + } + + return foundScripts; + } + System::Collections::Generic::IEnumerable^ ScriptStore::GetAllScripts(Entity entity) + { + // Check if entity exists + if (!EntityUtils::IsValid(entity)) + return nullptr; + + // Check if entity exists in the script storage + if (scripts.ContainsKey(entity)) + { + return scripts[entity]; + } + return nullptr; + } + generic + void ScriptStore::RemoveScript(Entity entity) + { + // Check if entity exists + if (!EntityUtils::IsValid(entity)) + throw gcnew System::ArgumentException("Invalid Entity provided to remove a Script from."); + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + { + Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!"); + return; + } + + // Search for and obtain + for each (Script^ script in scripts[entity]) + { + try + { + safe_cast(script); + removeScript(script); + } + catch (System::InvalidCastException^) + { + continue; + } + } + } + bool ScriptStore::RemoveScript(Entity entity, Script^ script) + { + // Check if entity exists + if (!EntityUtils::IsValid(entity)) + { + Debug::LogError("[ScriptStore] Attempted to remove a Script from an invalid Entity!"); + return false; + } + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + { + Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!"); + return false; + } + + // Check if the script exists to begin with + if (!scripts[entity]->Contains(script)) + { + Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!"); + return false; + } + + // Script found, queue it for deletion + removeScript(script); + return true; + } + void ScriptStore::RemoveAllScripts(Entity entity) + { + SAFE_NATIVE_CALL_BEGIN + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + return; + + // Search for and clear + System::Collections::Generic::List^ scriptList = scripts[entity]; + for each (Script^ script in scriptList) + { + removeScript(script); + } + scriptList->Clear(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + void ScriptStore::RemoveAllScriptsImmediately(Entity entity, bool callOnDestroy) + { + SAFE_NATIVE_CALL_BEGIN + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + return; + + // Clear all + System::Collections::Generic::List^ scriptList = scripts[entity]; + for each (Script ^ script in scriptList) + { + // Call OnDestroy only if indicated and also in play mode + if (callOnDestroy) + { + script->OnDestroy(); + } + script->OnDetached(); + + // Remove scripts from awakening if they were not woken up to begin with + awakeList.Remove(script); + startList.Remove(script); + } + scriptList->Clear(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + void ScriptStore::Init() + { + // Create an enumerable list of script types + refreshScriptTypeList(); + // Get stored methods for interop variants of functions + getGenericMethods(); + } + void ScriptStore::FrameSetUp() + { + SAFE_NATIVE_CALL_BEGIN + // Clear the awake queue + for each (Script^ script in awakeList) + { + script->Awake(); + } + awakeList.Clear(); + + // Clear the start queue + for each (Script^ script in startList) + { + if (script->Owner.IsActiveInHierarchy) + { + script->Start(); + } + else + { + inactiveStartList.Add(script); + } + } + startList.Clear(); + for each (Script ^ script in startList) + { + startList.Add(script); + } + inactiveStartList.Clear(); + + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + void ScriptStore::FrameCleanUp() + { + SAFE_NATIVE_CALL_BEGIN + // Clear the queue + for each (Script^ script in disposalQueue) + {; + if (Application::IsPlaying) + { + script->OnDestroy(); + } + auto entity = script->Owner.GetEntity(); + auto scriptList = scripts[script->Owner.GetEntity()]; + scriptList->Remove(script); + if (scriptList->Count <= 0) + { + scripts.Remove(entity); + } + } + disposalQueue.Clear(); + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + void ScriptStore::Exit() + { + SAFE_NATIVE_CALL_BEGIN + // Run the deinit all scripts if needed + if (Application::IsPlaying) + { + Debug::Log("Running OnDestroy() for scripts."); + for each (System::Collections::Generic::KeyValuePair entity in scripts) + { + for each (Script^ script in entity.Value) + { + script->OnDestroy(); + } + } + } + + // Clear Script Storage + scripts.Clear(); + awakeList.Clear(); + startList.Clear(); + disposalQueue.Clear(); + scriptTypeList = nullptr; + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + + /*---------------------------------------------------------------------------------*/ + /* Script Information Functions */ + /*---------------------------------------------------------------------------------*/ + System::Collections::Generic::IEnumerable^ ScriptStore::GetAvailableScriptList() + { + return scriptTypeList; + } + + /*---------------------------------------------------------------------------------*/ + /* Script Execution Functions */ + /*---------------------------------------------------------------------------------*/ + void ScriptStore::ExecuteFixedUpdate() + { + SAFE_NATIVE_CALL_BEGIN + for each (System::Collections::Generic::KeyValuePair entity in scripts) + { + // Check active state + if (!isEntityActive(entity.Key)) + continue; + + // Update each script + ScriptList^ scripts = entity.Value; + for (int i = 0; i < scripts->Count; ++i) + { + scripts[i]->FixedUpdate(); + } + } + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + void ScriptStore::ExecuteUpdate() + { + SAFE_NATIVE_CALL_BEGIN + for each (System::Collections::Generic::KeyValuePair entity in scripts) + { + // Check active state + if (!isEntityActive(entity.Key)) + continue; + + // Update each script + ScriptList^ scripts = entity.Value; + for (int i = 0; i < scripts->Count; ++i) + { + scripts[i]->Update(); + } + } + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + void ScriptStore::ExecuteLateUpdate() + { + SAFE_NATIVE_CALL_BEGIN + for each (System::Collections::Generic::KeyValuePair entity in scripts) + { + // Check active state + if (!isEntityActive(entity.Key)) + continue; + + // Update each script + ScriptList^ scripts = entity.Value; + for (int i = 0; i < scripts->Count; ++i) + { + scripts[i]->LateUpdate(); + } + } + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + + void ScriptStore::ExecuteOnDrawGizmos() + { + SAFE_NATIVE_CALL_BEGIN + for each (System::Collections::Generic::KeyValuePair entity in scripts) + { + // Check active state + if (!isEntityActive(entity.Key)) + continue; + + // Update each script + ScriptList^ scripts = entity.Value; + for (int i = 0; i < scripts->Count; ++i) + { + scripts[i]->OnDrawGizmos(); + } + } + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + + void ScriptStore::ExecuteCollisionFunctions() + { + SAFE_NATIVE_CALL_BEGIN + /* Collisions */ + const auto& collisions = SHPhysicsSystemInterface::GetCollisionInfo(); + for (const auto& collisionInfo : collisions) + { + auto entities = + { + std::make_pair(collisionInfo.GetEntityA(), collisionInfo.GetEntityB()), + std::make_pair(collisionInfo.GetEntityB(), collisionInfo.GetEntityA()) + }; + for (auto entity : entities) + { + // Don't bother if this object has no scripts or is inactive + if (!isEntityActive(entity.first) || !scripts.ContainsKey(entity.first)) + continue; + + // Construct the collision state object + CollisionInfo info; + info.GameObject = GameObject(entity.second); + + // Call all of the script's functions + auto entityScripts = scripts[entity.first]; + if (entityScripts->Count > 0) + { + for (int i = 0; i < entityScripts->Count; ++i) + { + Script^ script = entityScripts[i]; + switch (collisionInfo.GetCollisionState()) + { + case SHCollisionInfo::State::ENTER: + script->OnCollisionEnter(info); + break; + case SHCollisionInfo::State::STAY: + script->OnCollisionStay(info); + break; + case SHCollisionInfo::State::EXIT: + script->OnCollisionExit(info); + break; + } + } + } + } + } + /* Triggers */ + const auto& triggers = SHPhysicsSystemInterface::GetTriggerInfo(); + for (const auto& triggerInfo : triggers) + { + auto entities = + { + std::make_pair(triggerInfo.GetEntityA(), triggerInfo.GetEntityB()), + std::make_pair(triggerInfo.GetEntityB(), triggerInfo.GetEntityA()) + }; + for (auto entity : entities) + { + // Don't bother if this object has no scripts or is inactive + if (!isEntityActive(entity.first) || !scripts.ContainsKey(entity.first)) + continue; + + // Construct the collision state object + CollisionInfo info; + info.GameObject = GameObject(entity.second); + + // Call all of the script's functions + auto entityScripts = scripts[entity.first]; + if (entityScripts->Count > 0) + { + for (int i = 0; i < entityScripts->Count; ++i) + { + Script^ script = entityScripts[i]; + switch (triggerInfo.GetCollisionState()) + { + case SHCollisionInfo::State::ENTER: + script->OnTriggerEnter(info); + break; + case SHCollisionInfo::State::STAY: + script->OnTriggerStay(info); + break; + case SHCollisionInfo::State::EXIT: + script->OnTriggerExit(info); + break; + } + } + } + } + } + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + } + + bool ScriptStore::SerialiseScripts(Entity entity, System::IntPtr yamlNodePtr) + { + SAFE_NATIVE_CALL_BEGIN + // Convert to pointer + YAML::Node* yamlNode = reinterpret_cast(yamlNodePtr.ToPointer()); + + // Check if yamlNode is valid + if (yamlNode == nullptr) + { + Debug::LogWarning("[ScriptStore] Attempted to serialise scripts with an invalid YAML Node! Skipping."); + return false; + } + + // Check if entity exists, otherwise nothing + if (!EntityUtils::IsValid(entity)) + { + Debug::LogWarning("[ScriptStore] Attempted to serialise scripts for an invalid Entity! Skipping."); + return false; + } + + // Check if entity exists in the script storage + if (!scripts.ContainsKey(entity)) + return true; + + // Serialise each script + yamlNode->SetStyle(YAML::EmitterStyle::Block); + System::Collections::Generic::List^ scriptList = scripts[entity]; + for each (Script^ script in scriptList) + { + SerialisationUtilities::Serialise(script, *yamlNode); + } + + return true; + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + return false; + } + + bool ScriptStore::DeserialiseScripts(Entity entity, System::IntPtr yamlNodePtr) + { + SAFE_NATIVE_CALL_BEGIN + // Convert to pointer + YAML::Node* yamlNode = reinterpret_cast(yamlNodePtr.ToPointer()); + + // Check if yamlNode is valid + if (yamlNode == nullptr) + { + Debug::LogWarning("[ScriptStore] Attempted to deserialise scripts with an invalid YAML Node! Skipping."); + return false; + } + + // Check if entity exists, otherwise nothing + if (!EntityUtils::IsValid(entity)) + { + Debug::LogWarning("[ScriptStore] Attempted to deserialise scripts for an invalid Entity! Skipping."); + return false; + } + + // Go through all elements in the node + for (YAML::Node& node : *yamlNode) + { + // Get the name of the script + if (!node["Type"]) + { + Debug::LogWarning("[ScriptStore] Script with no type detected, skipping."); + continue; + } + + System::String^ typeName = Convert::ToCLI(node["Type"].as()); + + // Create + Script^ script; + if (AddScriptViaNameWithRef(entity, typeName, script)) + { + // Copy the data in + SerialisationUtilities::Deserialise(script, node); + } + else + { + Debug::LogWarning("[ScriptStore] Script with unloaded type detected, skipping."); + } + } + return true; + + SAFE_NATIVE_CALL_END_N("SHADE_Managed.ScriptStore") + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void ScriptStore::removeScript(Script^ script) + { + // Prepare for disposal + disposalQueue.Add(script); + + // Also remove it from awake and start queues if they were created but not initialised + awakeList.Remove(script); + startList.Remove(script); + script->OnDetached(); + } + + namespace + { + /* Select Many */ + ref struct Pair + { + System::Reflection::Assembly^ assembly; + System::Type^ type; + }; + + System::Collections::Generic::IEnumerable^ selectorFunc(System::Reflection::Assembly^ assembly) + { + return assembly->GetExportedTypes(); + } + Pair^ resultSelectorFunc(System::Reflection::Assembly^ assembly, System::Type^ type) + { + Pair^ p = gcnew Pair(); + p->assembly = assembly; + p->type = type; + return p; + } + + /* Where */ + bool predicateFunc(Pair^ pair) + { + return pair->type->IsSubclassOf(Script::typeid) && !pair->type->IsAbstract; + } + + /* Select */ + System::Type^ selectorFunc(Pair^ pair) + { + return pair->type; + } + } + + void ScriptStore::refreshScriptTypeList() + { + using namespace System; + using namespace System::Reflection; + using namespace System::Linq; + using namespace System::Collections::Generic; + + /* Select Many: Types in Loaded Assemblies */ + IEnumerable^ assemblies = AppDomain::CurrentDomain->GetAssemblies(); + Func^>^ collectionSelector = gcnew Func^>(selectorFunc); + Func^ resultSelector = gcnew Func(resultSelectorFunc); + IEnumerable^ selectManyResult = Enumerable::SelectMany(assemblies, collectionSelector, resultSelector); + + /* Where: Are concrete Scripts */ + Func^ predicate = gcnew Func(predicateFunc); + IEnumerable^ whereResult = Enumerable::Where(selectManyResult, predicate); + + /* Select: Select them all */ + Func^ selector = gcnew Func(selectorFunc); + scriptTypeList = Enumerable::Select(whereResult, selector); + + // Log + std::ostringstream oss; + oss << "[ScriptStore] Successfully retrieved references to " << Enumerable::Count(scriptTypeList) + << " Script(s) from currently loaded assemblies."; + Debug::Log(oss.str()); + } + + void ScriptStore::getGenericMethods() + { + array^ paramTypes = gcnew array{ Entity::typeid }; + addScriptMethod = ScriptStore::typeid->GetMethod("AddScript", paramTypes); + if (addScriptMethod == nullptr) + { + Debug::LogError("[ScriptStore] Failed to get MethodInfo of \"AddScript()\". Adding of scripts from native code will fail."); + } + } + + System::Type^ ScriptStore::getScriptType(System::String^ scriptName) + { + // Remove any whitespaces just in case + scriptName = scriptName->Trim(); + + // Look for the correct script + for each (System::Type^ type in scriptTypeList) + { + if (type->FullName == scriptName || type->Name == scriptName) + { + return type; + } + } + + return nullptr; + } + + bool ScriptStore::isEntityActive(Entity entity) + { + // Invalid entity + if (!EntityUtils::IsValid(entity)) + return false; + + return GameObject(entity).IsActiveInHierarchy; + } +} diff --git a/SHADE_Managed/src/Scripts/ScriptStore.hxx b/SHADE_Managed/src/Scripts/ScriptStore.hxx new file mode 100644 index 00000000..78f8c787 --- /dev/null +++ b/SHADE_Managed/src/Scripts/ScriptStore.hxx @@ -0,0 +1,350 @@ +/************************************************************************************//*! +\file ScriptStore.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definitions of the GameObject managed class which define an + abstraction for working with Entities in managed code. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Engine/Entity.hxx" +#include "Script.hxx" +#include "Serialization/SHSerialization.h" + +namespace SHADE +{ + /// + /// Responsible for managing all scripts attached to Entities as well as executing + /// all lifecycle functions of scripts. + /// + private ref class ScriptStore abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Scripts Manipulation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Adds a Script to a specified Entity. + /// + /// + /// Type of script to add. + /// This needs to be a default constructable Script. + /// + /// The entity to add a script to. + /// Reference to the script added. + /// + /// If the specified Entity is invalid. + /// + generic where T : ref class, Script + static T AddScript(Entity entity); + /// + /// Adds a specified pre-constructed Script to a specified Entity. + /// + /// The entity to add a script to. + /// The pre-constructed Script to add. + /// Reference to the script added. + /// + /// If the specified Entity is invalid. + /// + static Script^ AddScript(Entity entity, Script^ script); + /// + /// Adds a specified pre-constructed Script to a specified Entity. + /// + /// The entity to add a script to. + /// The pre-constructed Script to add. + /// Location in the script list to add. + /// Reference to the script added. + /// + /// If the specified Entity is invalid. + /// + static Script^ AddScript(Entity entity, Script^ script, int index); + /// + /// Adds a Script to a specified Entity. + ///
+ /// This function is meant for consumption from native code. If you are writing + /// in C# or C++/CLI, use AddScript<T>() instead as it is faster. + ///
+ /// The entity to add a script to. + /// The entity to add a script to. + /// + /// True if successfully added. False otherwise with the error logged to the + /// console. + /// + static bool AddScriptViaName(Entity entity, System::String^ scriptName); + /// + /// Adds a Script to a specified Entity. + ///
+ /// This function is meant for consumption from native code or for serialisation + /// purposes. If you are writing in C# or C++/CLI and not doing serialisation, + /// use AddScript<T>() instead as it is faster. + ///
+ /// The entity to add a script to. + /// The entity to add a script to. + /// + /// Out parameter handle to the Script that was created. + /// + /// + /// True if successfully added. False otherwise with the error logged to the + /// console. + /// + static bool AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, [System::Runtime::InteropServices::Out] Script^% createdScript); + /// + /// Retrieves the first Script from the specified Entity that matches the + /// specified type. + /// + /// + /// Type of script to get. + /// This needs to be a default constructable Script. + /// + /// + /// The entity which the script to retrieve is attached. + /// + /// + /// Reference to the script. This can be null if no script of the specified + /// type is attached. + /// + /// + /// If the specified Entity is invalid. + /// + generic where T : ref class, Script + static T GetScript(Entity entity); + /// + /// Retrieves the first Script from the specified Entity's children that matches + /// the specified type. + /// + /// + /// Type of script to get. + /// This needs to be a default constructable Script. + /// + /// + /// The entity which the script to retrieve is attached. + /// + /// + /// Reference to the script. This can be null if no script of the specified + /// type is attached. + /// + /// + /// If the specified Entity is invalid. + /// + generic where T : ref class, Script + static T GetScriptInChildren(Entity entity); + /// + /// Retrieves the list of Scripts from the specified Entity and the Entity's + /// children that matches the specified type. + /// This function performs allocations. If expecting only 1 component, use + /// GetScriptInChildren() instead. + /// This does not search the specified entity. + /// + /// + /// Type of script to get. + /// This needs to be a default constructable Script. + /// + /// + /// The entity which the script to retrieve is attached. + /// + /// + /// Reference to the script. This can be null if no script of the specified + /// type is attached. + /// + /// + /// If the specified Entity is invalid. + /// + generic where T : ref class, Script + static System::Collections::Generic::IEnumerable^ GetScriptsInChildren(Entity entity); + /// + /// Retrieves a immutable list of scripts from the specified Entity that + /// matches the specified type. + ///
+ /// Note that this function allocates. It should be used sparingly. + ///
+ /// + /// Type of scripts to get. + /// This needs to be a default constructable Script. + /// + /// + /// The entity which the scripts to retrieve are attached. + /// + /// + /// Immutable list of references to scripts of the specified type. + /// + generic where T : ref class, Script + static System::Collections::Generic::IEnumerable^ GetScripts(Entity entity); + /// + /// Retrieves an immutable list of all scripts attached to a specified Entity. + /// + /// + /// The entity which the scripts to retrieve are attached. + /// + /// + /// Immutable list of references to scripts attached to the specified Entity. + /// This can also be null if there are no scripts at all or an invalid Entity + /// was specified. + /// + static System::Collections::Generic::IEnumerable^ GetAllScripts(Entity entity); + /// + /// Removes all Scripts of the specified type from the specified Entity. + /// + /// + /// Type of script to remove. + /// This needs to be a default constructable Script. + /// + /// The entity to remove the script from. + /// + /// If the specified Entity is invalid. + /// + generic where T : ref class, Script + static void RemoveScript(Entity entity); + /// + /// Removes a specific script from the + /// + /// The entity to remove the script from. + /// The script to remove. + /// True if successfully removed. False otherwise. + static bool RemoveScript(Entity entity, Script^ script); + /// + /// Removes all Scripts attached to the specified Entity. Does not do anything + /// if the specified Entity is invalid or does not have any Scripts + /// attached. + /// + /// The entity to remove the scripts from. + static void RemoveAllScripts(Entity entity); + /// + /// Removes all Scripts attached to the specified Entity. Unlike + /// RemoveAllScripts(), this removes all the scripts immediately. + /// Does not do anything if the specified Entity is invalid or does not have any + /// Scripts attached. + /// + /// The entity to remove the scripts from. + /// + /// Whether or not to call OnDestroy on the scripts.This is ignored if not in + /// play mode. + /// + static void RemoveAllScriptsImmediately(Entity entity, bool callOnDestroy); + + internal: + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Initializes the ScriptStore to allocate and pre-populate reflection data. + /// + static void Init(); + /// + /// Sets up scripts that were marked for initialization. This calls the Awake() + /// and Start() for Scripts that have yet to have done so. + /// + static void FrameSetUp(); + /// + /// Cleans up scripts that were marked for deletion. This calls the OnDestroy() + /// for these Scripts. + /// + static void FrameCleanUp(); + /// + /// Cleans up data stored in the ScriptStore to free up memory for garbage + /// collection. + /// + static void Exit(); + + /*-----------------------------------------------------------------------------*/ + /* Script Information Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves a immutable list of available scripts that can be added. + /// + /// Immutable list of available scripts that can be added. + static System::Collections::Generic::IEnumerable^ GetAvailableScriptList(); + + /*-----------------------------------------------------------------------------*/ + /* Script Execution Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Executes FixedUpdate() for all scripts. + /// + static void ExecuteFixedUpdate(); + /// + /// Executes Update() for all scripts. + /// + static void ExecuteUpdate(); + /// + /// Executes LateUpdate() for all scripts. + /// + static void ExecuteLateUpdate(); + /// + /// Executes OnDrawGizmos() for all scripts. + /// + static void ExecuteOnDrawGizmos(); + /// + /// Executes OnCollision*() and OnTrigger*() for all scripts. + /// + static void ExecuteCollisionFunctions(); + + /*-----------------------------------------------------------------------------*/ + /* Serialisation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Populates a YAML node with the scripts for a specified Entity. + ///

+ /// This function should only be called from native unmanaged code. + ///
+ /// The Entity to Serialise. + /// + /// Pointer to a YAML::Node that will be populated with all of the serialised + /// scripts and their associated fields. + /// + /// + /// True if serialisation is successful. False if the buffer is too small for + /// the serialised output. + /// + static bool SerialiseScripts(Entity entity, System::IntPtr yamlNode); + /// + /// Processes a YAML node that contains a list of multiple scripts to be loaded + /// into the specified Entity. + ///

+ /// This function should only be called from native unmanaged code. + ///
+ /// + /// The Entity to attach the deserialised Scripts to. + /// + /// + /// Pointer to the YAML::Node that contains serialized script data. + /// + /// + static bool DeserialiseScripts(Entity entity, System::IntPtr yamlNode); + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definition */ + /*-----------------------------------------------------------------------------*/ + using ScriptList = System::Collections::Generic::List; + using ScriptDictionary = System::Collections::Generic::Dictionary; + using ScriptSet = System::Collections::Generic::HashSet; + + /*-----------------------------------------------------------------------------*/ + /* Static Data Members */ + /*-----------------------------------------------------------------------------*/ + static ScriptDictionary scripts; + static ScriptSet awakeList; + static ScriptSet startList; + static ScriptSet inactiveStartList; + static ScriptSet disposalQueue; + static System::Collections::Generic::IEnumerable^ scriptTypeList; + static System::Reflection::MethodInfo^ addScriptMethod; + + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static void removeScript(Script^ script); + static void refreshScriptTypeList(); + static void getGenericMethods(); + static System::Type^ getScriptType(System::String^ scriptName); + static bool isEntityActive(Entity entity); + }; +} diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx new file mode 100644 index 00000000..f371686c --- /dev/null +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.cxx @@ -0,0 +1,52 @@ +/************************************************************************************//*! +\file ReflectionUtilities.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 6, 2021 +\brief Contains the definition of the functions for the ReflectionUtilities + managed static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Serialisation/ReflectionUtilities.hxx" +// Project Includes +#include "SerializeFieldAttribute.hxx" + +/*-------------------------------------------------------------------------------------*/ +/* Function Definitions */ +/*-------------------------------------------------------------------------------------*/ +namespace SHADE +{ + System::Collections::Generic::IEnumerable^ ReflectionUtilities::GetInstanceFields(System::Object^ object) + { + using namespace System::Reflection; + + return object->GetType()->GetFields + ( + BindingFlags::Public | BindingFlags::NonPublic | BindingFlags::Instance + ); + } + + bool ReflectionUtilities::FieldIsSerialisable(System::Reflection::FieldInfo^ fieldInfo) + { + return fieldInfo->IsPublic || fieldInfo->GetCustomAttributes(SerializeField::typeid, true)->Length > 0; + } + + bool ReflectionUtilities::FieldIsList(System::Reflection::FieldInfo^ fieldInfo) + { + return IsList(fieldInfo->FieldType); + } + + bool ReflectionUtilities::IsList(System::Type^ type) + { + return type->IsGenericType + && type->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition(); + } +} diff --git a/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx new file mode 100644 index 00000000..ae66cc34 --- /dev/null +++ b/SHADE_Managed/src/Serialisation/ReflectionUtilities.hxx @@ -0,0 +1,55 @@ +/************************************************************************************//*! +\file ReflectionUtilities.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 6, 2021 +\brief Contains the definition of the managed ReflectionUtilities static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Contains useful static functions for working with Reflection. + /// + private ref class ReflectionUtilities abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Utility Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves a set of all non-static (instance) fields from a specified object. + /// + /// The object to get non-static fields from. + /// Immutable list of non-static fields. + static System::Collections::Generic::IEnumerable^ GetInstanceFields(System::Object^ object); + /// + /// Checks if a specified field is a candidate for serialisation. This means that + /// the field is public or private with the [SerialiseField] attribute. + /// + /// The field to check. + /// + /// True if the specified field is a candidate for serialisation. + /// + static bool FieldIsSerialisable(System::Reflection::FieldInfo^ fieldInfo); + /// + /// Checks if the specified field is a generic List. + /// + /// The field to check. + /// True if fieldInfo is describing a generic List. + static bool FieldIsList(System::Reflection::FieldInfo^ fieldInfo); + /// + /// Checks if the specified type is a generic List type. + /// + /// The type to check. + /// True if type is a generic List. + static bool IsList(System::Type^ type); + }; +} \ No newline at end of file diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx new file mode 100644 index 00000000..147591a5 --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.cxx @@ -0,0 +1,263 @@ +/************************************************************************************//*! +\file SerialisationUtilities.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 6, 2021 +\brief Contains the definition of the functions for the SerialisationUtilities + managed static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Serialisation/SerialisationUtilities.hxx" +// Project Includes +#include "ReflectionUtilities.hxx" + +/*-------------------------------------------------------------------------------------*/ +/* File-Level Constants */ +/*-------------------------------------------------------------------------------------*/ +static const std::string_view SCRIPT_TYPE_YAMLTAG = "Type"; + +/*-------------------------------------------------------------------------------------*/ +/* Function Definitions */ +/*-------------------------------------------------------------------------------------*/ +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Serialisation Functions */ + /*---------------------------------------------------------------------------------*/ + void SerialisationUtilities::Serialise(System::Object^ object, YAML::Node& scriptListNode) + { + using namespace System::Reflection; + + // Create YAML object + YAML::Node scriptNode; + scriptNode.SetStyle(YAML::EmitterStyle::Block); + scriptNode[SCRIPT_TYPE_YAMLTAG.data()] = Convert::ToNative(object->GetType()->FullName); + + // Get all fields + System::Collections::Generic::IEnumerable^ fields = ReflectionUtilities::GetInstanceFields(object); + for each (FieldInfo^ field in fields) + { + // Ignore private and non-SerialiseField + if (!ReflectionUtilities::FieldIsSerialisable(field)) + continue; + + // Serialise + writeFieldIntoYaml(field, object, scriptNode); + } + + scriptListNode.push_back(scriptNode); + } + void SerialisationUtilities::Deserialise(Object^ object, YAML::Node& yamlNode) + { + using namespace System::Reflection; + + // Load the YAML + if (!yamlNode.IsMap()) + { + // Invalid + Debug::LogError + ( + System::String::Format("[SerialisationUtilities] Invalid YAML Node provided for deserialization of \"{0}\" script.", + object->GetType()->FullName) + ); + return; + } + // Get all fields + System::Collections::Generic::IEnumerable^ fields = ReflectionUtilities::GetInstanceFields(object); + for each (FieldInfo^ field in fields) + { + // Ignore private and non-SerialiseField + if (!ReflectionUtilities::FieldIsSerialisable(field)) + continue; + + // Deserialise + const std::string FIELD_NAME = Convert::ToNative(field->Name); + if (yamlNode[FIELD_NAME]) + { + writeYamlIntoField(field, object, yamlNode[FIELD_NAME]); + } + } + } + /*---------------------------------------------------------------------------------*/ + /* Serialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void SerialisationUtilities::writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode) + { + // Field YAML Node + YAML::Node fieldNode; + + // Retrieve string for the YAML + const bool PRIMITIVE_SERIALIZED = fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode) || + fieldInsertYaml(fieldInfo, object, fieldNode); + + // Serialization of more complex types + if (!PRIMITIVE_SERIALIZED) + { + if (ReflectionUtilities::FieldIsList(fieldInfo)) + { + System::Type^ listType = fieldInfo->FieldType->GenericTypeArguments[0]; + System::Collections::IList^ iList = safe_cast(fieldInfo->GetValue(object)); + + + fieldNode.SetStyle(YAML::EmitterStyle::Block); + for (int i = 0; i < iList->Count; ++i) + { + YAML::Node elemNode; + if (varInsertYaml(iList[i], elemNode)) + { + fieldNode.push_back(elemNode); + } + else + { + Debug::LogWarning(Convert::ToNative(System::String::Format + ( + "[SerialisationUtilities] Failed to parse element # {2} of \"{0}\" of \"{1}\" type for serialization.", + fieldInfo->Name, fieldInfo->FieldType, i) + )); + } + } + } + else // Not any of the supported types + { + Debug::LogWarning(Convert::ToNative(System::String::Format + ( + "[SerialisationUtilities] Failed to parse \"{0}\" of \"{1}\" type for serialization.", + fieldInfo->Name, fieldInfo->FieldType) + )); + return; + } + } + + // Store the field into YAML + yamlNode[Convert::ToNative(fieldInfo->Name)] = fieldNode; + } + + bool SerialisationUtilities::varInsertYaml(System::Object^ object, YAML::Node& fieldNode) + { + const bool INSERTED = + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode) || + varInsertYamlInternal(object, fieldNode); + return INSERTED; + } + + /*---------------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + bool SerialisationUtilities::writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + { + const bool ASSIGNED = + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml(fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node) || + fieldAssignYaml (fieldInfo, object, node); + if (!ASSIGNED) + { + if (ReflectionUtilities::FieldIsList(fieldInfo)) + { + System::Type^ elemType = fieldInfo->FieldType->GenericTypeArguments[0]; + System::Collections::IList^ iList = safe_cast(fieldInfo->GetValue(object)); + if (node.IsSequence()) + { + // Get list size + const int LIST_SIZE = static_cast(node.size()); + if (LIST_SIZE > 0) + { + // Get list type + array^ typeList = gcnew array{ elemType }; + System::Type^ listType = System::Collections::Generic::List::typeid->GetGenericTypeDefinition()->MakeGenericType(typeList); + // Create a list of the specified type + array^ params = gcnew array{ node.size() }; + System::Collections::IList^ list = safe_cast + ( + System::Activator::CreateInstance(listType, params) + ); + + // Populate the list + for (int i = 0; i < LIST_SIZE; ++i) + { + // Create the object + System::Object^ obj = System::Activator::CreateInstance(elemType); + + // Set it's value + if (varAssignYaml(obj, node[i])) + { + list->Add(obj); + } + } + fieldInfo->SetValue(object, list); + } + } + + return true; + } + } + + return ASSIGNED; + } + + bool SerialisationUtilities::varAssignYaml(System::Object^% object, YAML::Node& node) + { + const bool DESERIALISED = + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal(object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node) || + varAssignYamlInternal (object, node); + return DESERIALISED; + } +} diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ new file mode 100644 index 00000000..dde6705a --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.h++ @@ -0,0 +1,217 @@ +/************************************************************************************//*! +\file SerialisationUtilities.h++ +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Sep 16, 2022 +\brief Contains the definition of the template functions of the managed + ReflectionUtilities static class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Primary Header +#include "SerialisationUtilities.hxx" +// Project Includes +#include "Utility/Convert.hxx" +#include "Utility/Debug.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Serialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + template + bool SerialisationUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode) + { + // Handle null objects + System::Object^ fieldObject = fieldInfo->GetValue(object); + if (fieldObject == nullptr) + { + // Default construct if null + if (fieldInfo->FieldType == FieldType::typeid) + { + if constexpr (std::is_same_v) + { + fieldNode = 0; + } + else if constexpr (std::is_same_v) + { + fieldNode = ""; + } + else if constexpr (std::is_same_v) + { + fieldNode.SetStyle(YAML::EmitterStyle::Flow); + fieldNode.push_back(0.0f); + fieldNode.push_back(0.0f); + } + else if constexpr (std::is_same_v) + { + fieldNode.SetStyle(YAML::EmitterStyle::Flow); + fieldNode.push_back(0.0f); + fieldNode.push_back(0.0f); + fieldNode.push_back(0.0f); + } + else if constexpr (std::is_same_v) + { + fieldNode = MAX_EID; + } + else + { + fieldNode = FieldType(); + } + return true; + } + return false; + } + return varInsertYamlInternal(fieldObject, fieldNode); + } + template + bool SerialisationUtilities::varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode) + { + if constexpr (std::is_same_v) + { + if (object->GetType()->IsSubclassOf(System::Enum::typeid)) + { + fieldNode = std::to_string(safe_cast(object)); + return true; + } + } + else if constexpr (std::is_same_v) + { + if (object->GetType() == System::String::typeid) + { + System::String^ str = safe_cast(object); + fieldNode = Convert::ToNative(str); + return true; + } + } + else if constexpr (std::is_same_v) + { + if (object->GetType() == Vector2::typeid) + { + Vector2 vec = safe_cast(object); + fieldNode.SetStyle(YAML::EmitterStyle::Flow); + fieldNode.push_back(vec.x); + fieldNode.push_back(vec.y); + return true; + } + } + else if constexpr (std::is_same_v) + { + if (object->GetType() == Vector3::typeid) + { + Vector3 vec = safe_cast(object); + fieldNode.SetStyle(YAML::EmitterStyle::Flow); + fieldNode.push_back(vec.x); + fieldNode.push_back(vec.y); + fieldNode.push_back(vec.z); + return true; + } + } + else if constexpr (std::is_same_v) + { + if (object->GetType() == GameObject::typeid) + { + GameObject gameObj = safe_cast(object); + fieldNode = gameObj ? gameObj.GetEntity() : MAX_EID; + return true; + } + } + else + { + if (object->GetType() == FieldType::typeid) + { + FieldType value = safe_cast(object); + fieldNode = static_cast(value); + return true; + } + } + + return false; + } + + /*---------------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*---------------------------------------------------------------------------------*/ + template + bool SerialisationUtilities::fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node) + { + System::Object^ valueObj = fieldInfo->GetValue(object); + if (varAssignYamlInternal(valueObj, node)) + { + fieldInfo->SetValue(object, valueObj); + return true; + } + + return false; + } + + template + bool SerialisationUtilities::varAssignYamlInternal(System::Object^% object, YAML::Node& node) + { + if constexpr (std::is_same_v) + { + if (object->GetType()->IsSubclassOf(System::Enum::typeid)) + { + object = node.as(); + return true; + } + } + else + { + if (object->GetType() == FieldType::typeid) + { + if constexpr (std::is_same_v) + { + object = Convert::ToCLI(node.as()); + } + else if constexpr (std::is_same_v) + { + if (node.IsSequence() && node.size() == 2) + { + Vector2 vec; + vec.x = node[0].as(); + vec.y = node[1].as(); + object = vec; + } + else + { + return false; + } + } + else if constexpr (std::is_same_v) + { + if (node.IsSequence() && node.size() == 3) + { + Vector3 vec; + vec.x = node[0].as(); + vec.y = node[1].as(); + vec.z = node[2].as(); + object = vec; + } + else + { + return false; + } + } + else if constexpr (std::is_same_v) + { + const uint32_t EID = node.as(); + object = (EID == MAX_EID ? GameObject() : GameObject(EID)); + } + else + { + object = node.as(); + } + return true; + } + } + + return false; + } +} diff --git a/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx new file mode 100644 index 00000000..5b6fc69e --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerialisationUtilities.hxx @@ -0,0 +1,75 @@ +/************************************************************************************//*! +\file SerialisationUtilities.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 6, 2021 +\brief Contains the definition of the managed SerialisationUtilities static + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include +// Project Includes +#include "Math/Vector2.hxx" +#include "Math/Vector3.hxx" +#include "Engine/GameObject.hxx" + +namespace SHADE +{ + /// + /// Contains useful static functions for working with Serialisation of Managed data. + /// + private ref class SerialisationUtilities abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Serialisation Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Creates a JSON node that represents the specified object and its associated + /// serialisable fields. Public fields and fields marked with the SerialiseField + /// attribute will be serialised. + /// + /// The object to serialise. + static void Serialise(System::Object^ object, YAML::Node& yamlNode); + /// + /// Deserialises a YAML node that contains a map of Scripts and copies the + /// deserialised data into the specified object if there are matching fields. + /// + /// + /// The JSON string that contains the data to copy into this Script object. + /// + /// The object to copy deserialised data into. + static void Deserialise(System::Object^ object, YAML::Node& yamlNode); + + private: + /*-----------------------------------------------------------------------------*/ + /* Serialization Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static void writeFieldIntoYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& yamlNode); + template + static bool fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode); + static bool varInsertYaml(System::Object^ object, YAML::Node& fieldNode); + template + static bool varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode); + + /*-----------------------------------------------------------------------------*/ + /* Deserialization Helper Functions */ + /*-----------------------------------------------------------------------------*/ + static bool writeYamlIntoField(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + template + static bool fieldAssignYaml(System::Reflection::FieldInfo^ fieldInfo, Object^ object, YAML::Node& node); + static bool varAssignYaml(System::Object^% object, YAML::Node& node); + template> + static bool varAssignYamlInternal(System::Object^% object, YAML::Node& node); + }; +} + +#include "SerialisationUtilities.h++" \ No newline at end of file diff --git a/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx b/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx new file mode 100644 index 00000000..7a7bb83c --- /dev/null +++ b/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx @@ -0,0 +1,26 @@ +/************************************************************************************//*! +\file SerializeFieldAttribute.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 5, 2021 +\brief Contains the definition of the managed SerializeField Attribute class with + the declaration of functions for working with it. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Simple attribute to mark that a field in a Script should be serialised. + /// + [System::AttributeUsage(System::AttributeTargets::Field)] + public ref class SerializeField : public System::Attribute + {}; +} + diff --git a/SHADE_Managed/src/Utility/Convert.cxx b/SHADE_Managed/src/Utility/Convert.cxx new file mode 100644 index 00000000..3b1f0f38 --- /dev/null +++ b/SHADE_Managed/src/Utility/Convert.cxx @@ -0,0 +1,113 @@ +/************************************************************************************//*! +\file Convert.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the Convert managed static + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Convert.hxx" +// External Dependencies +#include +#include "ECS_Base/Managers/SHEntityManager.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* ECS Conversions */ + /*---------------------------------------------------------------------------------*/ + Entity Convert::ToCLI(SHEntity entity) + { + return static_cast(entity.GetEID()); + } + + /*---------------------------------------------------------------------------------*/ + /* Math Conversions */ + /*---------------------------------------------------------------------------------*/ + SHVec3 Convert::ToNative(Vector3 vec) + { + return SHVec3(vec.x, vec.y, vec.z); + } + + Vector3 Convert::ToCLI(const SHVec3& vec) + { + return Vector3(vec.x, vec.y, vec.z); + } + SHVec2 Convert::ToNative(Vector2 vec) + { + return SHVec2(vec.x, vec.y); + } + + Vector2 Convert::ToCLI(const SHVec2& vec) + { + return Vector2(vec.x, vec.y); + } + + SHQuaternion Convert::ToNative(Quaternion quat) + { + return SHQuaternion{ quat.x, quat.y, quat.z, quat.w }; + } + + Quaternion Convert::ToCLI(const SHQuaternion& quat) + { + return Quaternion{ quat.x, quat.y, quat.z, quat.w }; + } + + SHRay Convert::ToNative(Ray vec) + { + return SHRay(ToNative(vec.Origin), ToNative(vec.Direction)); + } + + Ray Convert::ToCLI(const SHRay& vec) + { + return Ray(ToCLI(vec.position), ToCLI(vec.direction)); + } + + SHColour Convert::ToNative(Color col) + { + return SHColour(col.r, col.g, col.b, col.a); + } + + Color Convert::ToCLI(const SHColour& vec) + { + return Color(vec.x, vec.y, vec.z, vec.w); + } + + /*---------------------------------------------------------------------------------*/ + /* String Conversions */ + /*---------------------------------------------------------------------------------*/ + std::string Convert::ToNative(System::String^ str) + { + return msclr::interop::marshal_as(str); + } + + System::String^ Convert::ToCLI(const std::string& str) + { + return msclr::interop::marshal_as(str); + } + + /*---------------------------------------------------------------------------------*/ + /* Handle Conversions */ + /*---------------------------------------------------------------------------------*/ + Handle Convert::ToNative(GenericHandle handle) + { + Handle nativeHandle; + nativeHandle.id.Raw = handle.Id; + nativeHandle.library = reinterpret_cast(handle.Library.ToPointer()); + return nativeHandle; + } + + GenericHandle Convert::ToCLI(Handle handle) + { + return GenericHandle(handle); + } +} diff --git a/SHADE_Managed/src/Utility/Convert.hxx b/SHADE_Managed/src/Utility/Convert.hxx new file mode 100644 index 00000000..4d0c5b59 --- /dev/null +++ b/SHADE_Managed/src/Utility/Convert.hxx @@ -0,0 +1,254 @@ +/************************************************************************************//*! +\file Convert.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the Convert static class and the + declaration of its functions. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include "ECS_Base/Entity/SHEntity.h" +#include "Math/Vector/SHVec2.h" +#include "Math/Vector/SHVec3.h" +#include "Math/SHQuaternion.h" +#include "Math/SHRay.h" +#include "Resource/SHHandle.h" + +// Project Includes +#include "Engine/Entity.hxx" +#include "Math/Vector2.hxx" +#include "Math/Vector3.hxx" +#include "Math/Quaternion.hxx" +#include "Math/Ray.hxx" +#include "Engine/GenericHandle.hxx" +#include "Math/SHColour.h" +#include "Graphics/Color.hxx" + +namespace SHADE +{ + /// + /// Provides functions easy and consistent syntax for converting between custom + /// managed and native types that are aligned. + /// + class Convert + { + public: + /*-----------------------------------------------------------------------------*/ + /* Deleted Destructors (Static Class) */ + /*-----------------------------------------------------------------------------*/ + Convert() = delete; + + /*-----------------------------------------------------------------------------*/ + /* ECS Conversions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Converts from a native Entity to a managed Entity (UInt32). + /// + /// Native Entity to convert from. + /// Managed representation of the specified Entity. + static Entity ToCLI(SHEntity entity); + + /*-----------------------------------------------------------------------------*/ + /* Math Conversions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Converts from a managed Vector3 to a native Vector3. + /// + /// The managed Vector3 to convert from. + /// Native copy of a managed Vector3. + static SHVec3 ToNative(Vector3 vec); + /// + /// Converts from a native Vector3 to a managed Vector3. + /// + /// The native Vector3 to convert from. + /// Managed copy of a native Vector3. + static Vector3 ToCLI(const SHVec3& vec); + /// + /// Converts from a managed Vector2 to a native Vector2. + /// + /// The managed Vector2 to convert from. + /// Native copy of a managed Vector2. + static SHVec2 ToNative(Vector2 vec); + /// + /// Converts from a native Vector2 to a managed Vector2. + /// + /// The native Vector2 to convert from. + /// Managed copy of a native Vector2. + static Vector2 ToCLI(const SHVec2& vec); + /// + /// Converts from a managed Quaternion to a native Quaternion. + /// + /// The managed Quaternion to convert from. + /// Native copy of a managed Quaternion. + static SHQuaternion ToNative(Quaternion quat); + /// + /// Converts from a native Quaternion to a managed Quaternion. + /// + /// The native Quaternion to convert from. + /// Managed copy of a native Quaternion. + static Quaternion ToCLI(const SHQuaternion& quat); + /// Converts from a managed Vector2 to a native Vector2. + /// + /// The managed Vector2 to convert from. + /// Native copy of a managed Vector2. + static SHRay ToNative(Ray vec); + /// + /// Converts from a native Vector2 to a managed Vector2. + /// + /// The native Vector2 to convert from. + /// Managed copy of a native Vector2. + static Ray ToCLI(const SHRay& vec); + /// Converts from a managed Color to a native Colour. + /// + /// The managed Color to convert from. + /// Native copy of a managed Color. + static SHColour ToNative(Color col); + /// + /// Converts from a native Colour to a managed Color. + /// + /// The native Colour to convert from. + /// Managed copy of a native Colour. + static Color ToCLI(const SHColour& vec); + + /*-----------------------------------------------------------------------------*/ + /* String Conversions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Converts from a managed String to a native std::string. + /// + /// The managed String to convert from. + /// Native copy of a managed String. + static std::string ToNative(System::String^ str); + /// + /// Converts from a native std::Stringto a managed String. + /// + /// The native std::string to convert from. + /// Managed copy of a native std::string. + static System::String^ ToCLI(const std::string& str); + + /*-----------------------------------------------------------------------------*/ + /* Handle Conversions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Converts from a managed GenericHandle to a Handle. + /// + /// GenericHandle to convert from. + /// Native generic Handle. + static Handle ToNative(GenericHandle handle); + /// + /// Converts from a native generic Handle to a managed GenericHandle. + /// + /// The native handle to convert. + /// Managed copy of the native Handle. + static GenericHandle ToCLI(Handle handle); + + }; + + /// + /// Checks if the specified type is matching between native C++ and the managed type. + /// + /// Type to check. + template + struct IsPrimitiveTypeMatches : public std::integral_constant + < + bool, + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> + > + {}; + /// + /// Short hand for IsPrimitiveTypeMatches::value + /// + /// Type to check. + template + inline constexpr bool IsPrimitiveTypeMatches_V = IsPrimitiveTypeMatches::value; + + /// + /// Type Transformer for managed types to native types. + /// + /// + /// Managed type to get the native type for. + /// + template + struct ToNativeType + { + public: + using Value = void; + static bool IsDefined() { return is_same_v; } + }; + template<> struct ToNativeType { using Value = int16_t; }; + template<> struct ToNativeType { using Value = int32_t; }; + template<> struct ToNativeType { using Value = int64_t; }; + template<> struct ToNativeType { using Value = uint16_t; }; + template<> struct ToNativeType { using Value = uint32_t; }; + template<> struct ToNativeType { using Value = uint64_t; }; + template<> struct ToNativeType { using Value = int8_t; }; + template<> struct ToNativeType { using Value = bool; }; + template<> struct ToNativeType { using Value = double; }; + template<> struct ToNativeType { using Value = float; }; + + /// + /// Alias for ToNativeType::Value + /// + /// + /// Managed type to get the native type for. + /// + template + using ToNativeType_T = typename ToNativeType::Value; + + /// + /// Type Transformer for native types to managed types. + /// + /// + /// Managed type to get the native type for. + /// + template + struct ToManagedType + { + public: + using Value = void; + static bool IsDefined() { return is_same_v; } + }; + template<> struct ToManagedType { using Value = System::Byte; }; + template<> struct ToManagedType { using Value = System::Int16; }; + template<> struct ToManagedType { using Value = System::Int32; }; + template<> struct ToManagedType { using Value = System::Int64; }; + template<> struct ToManagedType { using Value = System::UInt16; }; + template<> struct ToManagedType { using Value = System::UInt32; }; + template<> struct ToManagedType { using Value = System::UInt64; }; + template<> struct ToManagedType { using Value = bool; }; + template<> struct ToManagedType { using Value = double; }; + template<> struct ToManagedType { using Value = float; }; + + /// + /// Alias for ToManagedType::Value + /// + /// + /// Managed type to get the native type for. + /// + template + using ToManagedType_T = typename ToManagedType::Value; +} diff --git a/SHADE_Managed/src/Utility/Debug.cxx b/SHADE_Managed/src/Utility/Debug.cxx new file mode 100644 index 00000000..8a107ab3 --- /dev/null +++ b/SHADE_Managed/src/Utility/Debug.cxx @@ -0,0 +1,124 @@ +/************************************************************************************//*! +\file Debug.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 28, 2021 +\brief Contains the definition of the functions for the Debug managed static + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Debug.hxx" +// Standard Libraries +#include +// External Libraries +#include "Tools/SHLog.h" +// Project Headers +#include "Convert.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Logging Functions */ + /*---------------------------------------------------------------------------------*/ + void Debug::Log(const std::string& str) + { + SHLog::Info(str); + } + void Debug::Log(System::String^ str) + { + SHLog::Info(Convert::ToNative(str)); + } + + void Debug::Log(System::String^ str, Object^ owner) + { + Log(str, owner->GetType()->Name); + } + void Debug::Log(System::String^ str, System::String^ throwerName) + { + Log("[" + throwerName + "] " + str); + } + void Debug::Log(System::String^ str, const std::string& throwerName) + { + std::ostringstream oss; + oss << "[" << throwerName << "] " << Convert::ToNative(str); + Log(oss.str()); + } + void Debug::LogWarning(const std::string& str) + { + SHLog::Warning(str); + } + void Debug::LogWarning(System::String^ str) + { + SHLog::Warning(Convert::ToNative(str)); + } + void Debug::LogWarning(System::String^ str, Object^ thrower) + { + LogWarning(str, thrower->GetType()->Name); + } + void Debug::LogWarning(System::String^ str, System::String^ throwerName) + { + LogWarning("[" + throwerName + "] " + str); + } + + void Debug::LogWarning(System::String^ str, const std::string& throwerName) + { + std::ostringstream oss; + oss << "[" << throwerName << "] " << Convert::ToNative(str); + LogWarning(oss.str()); + } + void Debug::LogError(const std::string& str) + { + SHLog::Error(str); + } + void Debug::LogError(System::String^ str) + { + SHLog::Error(Convert::ToNative(str)); + } + void Debug::LogError(System::String^ str, Object^ thrower) + { + LogError(str, thrower->GetType()->Name); + } + void Debug::LogErrorNative(System::String^ str, const std::string& throwerName) + { + std::ostringstream oss; + oss << "[" << throwerName << "] -> " << Convert::ToNative(str); + LogError(oss.str()); + } + void Debug::LogError(System::String^ str, System::String^ throwerName) + { + LogError("[" + throwerName + "] " + str); + } + void Debug::LogException(System::Exception^ exception) + { + LogError("Unhandled exception: " + exception->ToString(), exception->Source); + } + + void Debug::LogException(System::Exception^ exception, Object^ thrower) + { + LogError("Unhandled exception: " + exception->ToString(), thrower->GetType()->Name); + } + void Debug::LogException(const std::exception& exception, Object^ thrower) + { + LogExceptionNative(exception, Convert::ToNative(thrower->GetType()->Name)); + } + void Debug::LogExceptionNative(System::Exception^ exception, const std::string& throwerName) + { + std::ostringstream oss; + oss << "[" << throwerName << "] Unhandled exception: " << Convert::ToNative(exception->ToString()); + LogError(oss.str()); + } + void Debug::LogExceptionNative(const std::exception& exception, const std::string& throwerName) + { + std::ostringstream oss; + oss << "[" << throwerName << "] Unhandled exception: " << exception.what(); + LogError(oss.str()); + } +} diff --git a/SHADE_Managed/src/Utility/Debug.hxx b/SHADE_Managed/src/Utility/Debug.hxx new file mode 100644 index 00000000..28f2bc88 --- /dev/null +++ b/SHADE_Managed/src/Utility/Debug.hxx @@ -0,0 +1,255 @@ +/************************************************************************************//*! +\file Debug.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 30, 2021 +\brief Contains the definition of the Debug static class and the declaration of + its functions. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Standard Library +#include +#include + +/*-------------------------------------------------------------------------------------*/ +/* Macro Functions */ +/*-------------------------------------------------------------------------------------*/ +/// +/// Macro expansion that is used together with SAFE_NATIVE_CALL_END or +/// SAFE_NATIVE_CALL_END_N to wrap the body of a function with a try and catch that +/// catches native and managed exceptions. This is needed to prevent crashes when calling +/// managed code from native code. +/// + +#define SAFE_NATIVE_CALL_BEGIN try { +/// +/// Macro expansion that is used together with SAFE_NATIVE_CALL_BEGIN or to wrap the body +/// of a function with a try and catch that catches native and managed exceptions. This +/// is needed to prevent crashes when calling managed code from native code. +///
+/// Use this instead of SAFE_NATIVE_CALL_END_N if passing in managed types as the owner. +///
+/// +/// The managed object that owns the function that this macro encapsulates. +/// +#define SAFE_NATIVE_CALL_END(OWNER) \ +} \ +catch (System::Exception^ e) \ +{ \ + Debug::LogException(e); \ +} \ +catch (const std::exception& e) \ +{ \ + Debug::LogException(e, OWNER); \ +} \ +catch (...) \ +{ \ + Debug::LogError("Unsupported native exception.", OWNER); \ +} \ +/// +/// Macro expansion that is used together with SAFE_NATIVE_CALL_BEGIN or to wrap the body +/// of a function with a try and catch that catches native and managed exceptions. This +/// is needed to prevent crashes when calling managed code from native code. +///
+/// Use this instead of SAFE_NATIVE_CALL_END if passing in a native string that specifies +/// the owner. +///
+/// +/// The managed object that owns the function that this macro encapsulates. +/// + +#define SAFE_NATIVE_CALL_END_N(OWNER) \ +} \ +catch (System::Exception^ e) \ +{ \ + Debug::LogExceptionNative(e, OWNER); \ +} \ +catch (const std::exception& e) \ +{ \ + Debug::LogExceptionNative(e, OWNER); \ +} \ +catch (...) \ +{ \ + Debug::LogErrorNative("Unsupported native exception.", OWNER); \ +} \ + +/*-------------------------------------------------------------------------------------*/ +/* Type Definitions */ +/*-------------------------------------------------------------------------------------*/ +namespace SHADE +{ + /// + /// Static class that contains the functions for working with time. + /// + public ref class Debug abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Logging Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Logs a message to the output. + /// + /// The string to output. + static void Log(const std::string& str); + /// + /// Logs a message to the output. + /// + /// The string to output. + static void Log(System::String^ str); + /// + /// Logs a message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Object that sent the message to label the message. + /// The name of the object will be used. + /// + static void Log(System::String^ str, Object^ owner); + /// + /// Logs a message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that sent the message to label the message. + /// The name of the object will be used. + /// + static void Log(System::String^ str, System::String^ throwerName); + /// + /// Logs a message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that sent the message to label the message. + /// The name of the object will be used. + /// + static void Log(System::String^ str, const std::string& throwerName); + /// + /// Logs a warning message to the output. + /// + /// The string to output. + static void LogWarning(const std::string& str); + /// + /// Logs a warning message to the output. + /// + /// The string to output. + static void LogWarning(System::String^ str); + /// + /// Logs a warning message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Object that threw the warning to label the warning message. + /// The name of the object will be used. + /// + static void LogWarning(System::String^ str, Object^ thrower); + /// + /// Logs a warning message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that threw the warning to label the warning message. + /// The name of the object will be used. + /// + static void LogWarning(System::String^ str, System::String^ throwerName); + /// + /// Logs a warning message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that threw the warning to label the warning message. + /// The name of the object will be used. + /// + static void LogWarning(System::String^ str, const std::string& throwerName); + /// + /// Logs a error message to the output. + /// + /// The string to output. + static void LogError(const std::string& str); + /// + /// Logs a error message to the output. + /// + /// The string to output. + static void LogError(System::String^ str); + /// + /// Logs a error message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Object that threw the error to label the error message. + /// The name of the object will be used. + /// + static void LogError(System::String^ str, Object^ thrower); + /// + /// Logs a error message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that threw the error to label the error message. + /// The name of the object will be used. + /// + static void LogErrorNative(System::String^ str, const std::string& throwerName); + /// + /// Logs a error message to the output with a label such that it looks like this: + /// "[Label] Message" + /// + /// The string to output. + /// + /// Name of the object that threw the error to label the error message. + /// The name of the object will be used. + /// + static void LogError(System::String^ str, System::String^ throwerName); + /// + /// Logs an exception that is formatted nicely to the output. + /// + /// Exception to log. + static void LogException(System::Exception^ exception); + /// + /// Logs an exception that is formatted nicely to the output. + /// + /// Exception to log. + /// + /// Object that threw the exception to label the exception message. + /// The name of the object will be used. + /// + static void LogException(System::Exception^ exception, Object^ thrower); + /// + /// Logs a native exception that is formatted nicely to the output. + /// Equivalent to calling + /// LogException(exception, Convert::ToNative(thrower->GetType()->Name)); + /// + /// Native exception to log. + /// + /// Object that threw the exception to label the exception message. + /// The name of the object will be used. + /// + static void LogException(const std::exception& exception, Object^ thrower); + /// + /// Logs an exception that is formatted nicely to the output. + /// + /// Name of the one responsible for the exception. + /// Exception to log. + static void LogExceptionNative(System::Exception^ exception, const std::string& throwerName); + /// + /// Logs a native exception that is formatted nicely to the output. + /// + /// Native exception to log. + /// Name of the one responsible for the exception. + static void LogExceptionNative(const std::exception& exception, const std::string& throwerName); + }; +} diff --git a/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.cxx b/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.cxx new file mode 100644 index 00000000..e19c4d06 --- /dev/null +++ b/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.cxx @@ -0,0 +1,36 @@ +/************************************************************************************//*! +\file DisposableAssemblyLoadContext.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Jan 20, 2022 +\brief Contains the implementation of the managed DisposableAssemblyLoadContext + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "DisposableAssemblyLoadContext.hxx" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor */ + /*---------------------------------------------------------------------------------*/ + DisposableAssemblyLoadContext::DisposableAssemblyLoadContext() + : AssemblyLoadContext { true } + {} + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + System::Reflection::Assembly^ DisposableAssemblyLoadContext::Load(System::Reflection::AssemblyName^) + { + return nullptr; + } +} diff --git a/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.hxx b/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.hxx new file mode 100644 index 00000000..14d612b3 --- /dev/null +++ b/SHADE_Managed/src/Utility/DisposableAssemblyLoadContext.hxx @@ -0,0 +1,39 @@ +/************************************************************************************//*! +\file DisposableAssemblyLoadContext.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Jan 20, 2022 +\brief Contains the definitions of the managed DisposableAssemblyLoadContext + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2021 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /// + /// Custom AssemblyLoadContext marked as collectible so that it can be unloaded. + /// + private ref class DisposableAssemblyLoadContext : public System::Runtime::Loader::AssemblyLoadContext + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor */ + /*-----------------------------------------------------------------------------*/ + /// + /// Default Constructor + /// + DisposableAssemblyLoadContext(); + + protected: + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + System::Reflection::Assembly^ Load(System::Reflection::AssemblyName^ assemblyName) override; + }; +} diff --git a/SHADE_Managed/src/Utility/Gizmos.cxx b/SHADE_Managed/src/Utility/Gizmos.cxx new file mode 100644 index 00000000..21636a5d --- /dev/null +++ b/SHADE_Managed/src/Utility/Gizmos.cxx @@ -0,0 +1,69 @@ +/************************************************************************************//*! +\file Gizmos.cxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 8, 2022 +\brief Contains the definition of the functions for the Convert managed static + class. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include "SHpch.h" +// Primary Header +#include "Gizmos.hxx" +#include "Convert.hxx" +#include "Tools/SHDebugDraw.h" +// External Dependencies + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Public Properties */ + /*---------------------------------------------------------------------------------*/ + Color Gizmos::Color::get() + { + return defaultColor; + } + void Gizmos::Color::set(SHADE::Color color) + { + defaultColor = color; + } + + /*---------------------------------------------------------------------------------*/ + /* Debug Draw Functions */ + /*---------------------------------------------------------------------------------*/ + void Gizmos::DrawLine(Vector3 from, Vector3 to) + { + DrawLine(from, to, defaultColor); + } + + void Gizmos::DrawLine(Vector3 from, Vector3 to, SHADE::Color color) + { + SHDebugDraw::Line(Convert::ToNative(color), Convert::ToNative(from), Convert::ToNative(to)); + } + + void Gizmos::DrawWireCube(Vector3 center, Vector3 extents) + { + DrawWireCube(center, extents, defaultColor); + } + + void Gizmos::DrawWireCube(Vector3 center, Vector3 extents, SHADE::Color color) + { + SHDebugDraw::Cube(Convert::ToNative(color), Convert::ToNative(center), Convert::ToNative(extents)); + } + + void Gizmos::DrawWireSphere(Vector3 center, float radius) + { + DrawWireSphere(center, radius, defaultColor); + } + + void Gizmos::DrawWireSphere(Vector3 center, float radius, SHADE::Color color) + { + SHDebugDraw::Sphere(Convert::ToNative(color), Convert::ToNative(center), radius); + } +} diff --git a/SHADE_Managed/src/Utility/Gizmos.hxx b/SHADE_Managed/src/Utility/Gizmos.hxx new file mode 100644 index 00000000..1878d867 --- /dev/null +++ b/SHADE_Managed/src/Utility/Gizmos.hxx @@ -0,0 +1,97 @@ +/************************************************************************************//*! +\file Gizmos.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 8, 2022 +\brief Contains the definition of the Gizmos static class and the + declaration of its functions. + + Note: This file is written in C++17/CLI. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "Math/Vector3.hxx" +#include "Graphics/Color.hxx" + +namespace SHADE +{ + /// + /// Provides functions for implementing debug drawing. + /// + public value class Gizmos abstract sealed + { + public: + /*-----------------------------------------------------------------------------*/ + /* Public Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Default colour that will be used for drawing debug primitives if the color + /// parameter is not specified. + /// + static property Color Color + { + SHADE::Color get(); + void set(SHADE::Color color); + } + /*-----------------------------------------------------------------------------*/ + /* Debug Draw Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space. + /// Uses Color to render. + /// + /// First point of the line. + /// Second point of the line. + static void DrawLine(Vector3 from, Vector3 to); + /// + /// Renders a line between two points in world space. + /// + /// First point of the line. + /// Second point of the line. + /// Colour of the line. + static void DrawLine(Vector3 from, Vector3 to, SHADE::Color color); + /// + /// Renders a wireframe cube centered around the position specified in world + /// space. + /// Uses Color to render. + /// + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + static void DrawWireCube(Vector3 center, Vector3 extents); + /// + /// Renders a wireframe cube centered around the position specified in world + /// space. + /// + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + /// Colour of the cube. + static void DrawWireCube(Vector3 center, Vector3 extents, SHADE::Color color); + /// + /// Renders a wireframe sphere centered around the position specified in world + /// space. + /// Uses Color to render. + /// + /// Position where the sphere wil be centered at. + /// Radius of the rendered sphere. + static void DrawWireSphere(Vector3 center, float radius); + /// + /// Renders a wireframe sphere centered around the position specified in world + /// space. + /// + /// Position where the sphere wil be centered at. + /// Radius of the rendered sphere. + /// Colour of the sphere. + static void DrawWireSphere(Vector3 center, float radius, SHADE::Color color); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + static SHADE::Color defaultColor = SHADE::Color::White; + }; +} diff --git a/generate.bat b/generate.bat index ce7f2916..b81fce81 100644 --- a/generate.bat +++ b/generate.bat @@ -1,2 +1,5 @@ +erase /s /q *.vcxproj +erase /s /q *.vcxproj.filters + call Premake\premake5 vs2019 PAUSE \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index d54f8e40..2164d649 100644 --- a/premake5.lua +++ b/premake5.lua @@ -8,23 +8,27 @@ workspace "SHADE" { "Debug", "Release", + "Publish" } flags { - "MultiProcessorCompile" + "MultiProcessorCompile" } - - outputdir = "%{cfg.buildcfg}_%{cfg.architecture}" - include "SHADE_Application" + outputdir = "%{wks.location}/bin/%{cfg.buildcfg}" + interdir = "%{wks.location}/bin_int" + include "SHADE_Engine" + include "SHADE_Application" + include "SHADE_Managed" + include "SHADE_CSharp" group "Dependencies" include "Dependencies/msdf" include "Dependencies/imgui" - include "Dependencies/spdlog" --include "Dependencies/tracy" include "Dependencies/yamlcpp" include "Dependencies/reactphysics3d" + include "Dependencies/ModelCompiler" group ""